diff --git a/.gitignore b/.gitignore index 56656af..688be4f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ raisely.json # dev folders stylesheets components +pages .raisely.json +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0f2d4a5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM node:12 + +WORKDIR /var/local + +CMD ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d2194a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: init update deploy start + +init: + npm install + node ./bin/raisely init + +update: + node ./bin/raisely update + +deploy: + node ./bin/raisely deploy + +start: + node ./bin/raisely start + diff --git a/README.md b/README.md index d671151..0a0ccb8 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ ![Raisely logo](https://raisely-themes.imgix.net/raisely/brand.raisely.svg) -The Raisely CLI is used to power local development of Raisely themes, syncing custom components and campaign styles to your local machine. +The Raisely CLI is used to power local development of Raisely themes, syncing custom components, campaign styles and pages to your local machine. For more about Raisely, see ## Overview -The Raisely CLI allows for fast and easy development on the Raisely platform. The CLI allows you to connect a directory on your local computer to a Raisely account. With the CLI you can update campaign stylesheets, and edit and create custom React components. +The Raisely CLI allows for fast and easy development on the Raisely platform. The CLI allows you to connect a directory on your local computer to a Raisely account. With the CLI you can update campaign stylesheets, pages and edit and create custom React components. The CLI is built on Node.js, so you'll need Node.js installed to use it. @@ -26,9 +26,9 @@ For other issues, [submit a support ticket](mailto:support@raisely.com). ## Commands - `raisely init` - start a new Raisely project, authenticate and sync your campaigns -- `raisely update` - update local copies of styles and components from the API +- `raisely update` - update local copies of styles, pages and components from the API - `raisely create [name]` - create a new custom component, optionally add the component name to the command (otherwise you will be asked for one) -- `raisely start` - starts watching for and uploading changes to styles and components +- `raisely start` - starts watching for and uploading changes to styles, pages and components ## CI/CD Usage @@ -41,8 +41,24 @@ Raisely CLI supports the following environment variables: _Note: All components are always synced, when they're present in the directory your syncing_ -With these environment variables set, run: `raisely deploy`. This will sync your local directory to the remote Raisely account, overwriting the styles and components on the destination campaign. +With these environment variables set, run: `raisely deploy`. This will sync your local directory to the remote Raisely account, overwriting the styles, pages and components on the destination campaign. ## Developing Contributions are welcome. The project is built with `commander`, `inquirer` and `ora` with a basic module structure. + +### Local Development With Docker + +Create a `.env` file in this directory containing the required environment variables: + +RAISELY_TOKEN= + +RAISELY_CAMPAIGNS= + +then run `docker-compose up` from this directory. + +In a new terminal window connect to the docker container: + +`docker exec -it raisely-cli /bin/bash` + +A Makefile provides tasks diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..812c497 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '2' + +services: + + raisely_cli: + container_name: "raisely_cli" + build: + context: . + dockerfile: Dockerfile + volumes: + - ".:/var/local" + environment: + - RAISELY_TOKEN=${RAISELY_TOKEN} + - RAISELY_CAMPAIGNS=${RAISELY_CAMPAIGNS} + restart: always diff --git a/src/actions/campaigns.js b/src/actions/campaigns.js index 18e6dbf..70f1373 100644 --- a/src/actions/campaigns.js +++ b/src/actions/campaigns.js @@ -95,3 +95,21 @@ export async function updateStyles({ path, files, css }, config) { config.apiUrl ); } + + +export async function updatePage(page, config) { + return await api( + { + path: `/pages/${page.uuid}?private=true`, + method: "PATCH", + json: { + data: page.data + }, + auth: { + bearer: config.token + } + }, + config.apiUrl + ); + +} \ No newline at end of file diff --git a/src/actions/sync.js b/src/actions/sync.js index f4ccd16..842c456 100644 --- a/src/actions/sync.js +++ b/src/actions/sync.js @@ -120,3 +120,79 @@ export async function syncComponents(config, workDir, filter) { return error(e, loader); } } + +export async function syncPages(config, workDir) { + const directory = path.join(workDir, "pages"); + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + + const loader = ora("Downloading campaign pages...").start(); + try { + for (const uuid of config.campaigns) { + const campaign = await api( + { + path: `/campaigns/${uuid}?private=true`, + auth: { + bearer: config.token + } + }, + config.apiUrl + ); + + const campaignDir = path.join(directory, campaign.data.path); + + if (!fs.existsSync(campaignDir)) { + fs.mkdirSync(campaignDir); + } + + const pages = await api( + { + path: `/campaigns/${uuid}/pages`, + qs: { + private: 1, + limit: 100 + }, + auth: { + bearer: config.token + } + }, + config.apiUrl + ); + + for (const page of pages.data) { + fs.writeFileSync( + path.join(campaignDir, `${page.internalTitle}.json`), + JSON.stringify( + { + uuid: page.uuid, + data: { // these are the editable page fields + title: page.title, + internalTitle: page.internalTitle, + body: page.body, + path: page.path, + status: page.status, + provider: page.provider, + condition: page.condition, + image: page.image, + metaTitle: page.metaTitle, + metaDescription: page.metaDescription, + socialTitle: page.socialTitle, + socialDescription: page.socialDescription + } + }, + null, + 4 + ) + ); + } + + } + + loader.succeed(); + } catch (e) { + console.log(e); + return error(e, loader); + } + +} \ No newline at end of file diff --git a/src/deploy.js b/src/deploy.js index 568ecc6..6d85306 100644 --- a/src/deploy.js +++ b/src/deploy.js @@ -5,7 +5,7 @@ import fs from "fs"; import path from "path"; import { welcome, log, br, error } from "./helpers"; -import { buildStyles, getCampaign } from "./actions/campaigns"; +import { buildStyles, getCampaign, updatePage } from "./actions/campaigns"; import { updateComponentFile, updateComponentConfig @@ -28,7 +28,7 @@ export default function deploy(program) { br(); } log( - `You will overwrite the styles and components in your campaign.`, + `You will overwrite the styles, components and pages in your campaign.`, "white" ); br(); @@ -94,6 +94,28 @@ export default function deploy(program) { loader.succeed(); } + // upload campaign pages + for (const campaignUuid of config.campaigns) { + const loader = ora(`Uploading pages for ${campaignUuid}`).start(); + const campaign = await getCampaign( + { uuid: campaignUuid }, + config.token, + config + ); + + const pagesDir = path.join(process.cwd(), "pages"); + const campaignDir = path.join(pagesDir, `${campaign.data.path}`); + + for (const file of fs.readdirSync(campaignDir)) { + const pageData = JSON.parse(fs.readFileSync( + path.join(campaignDir, file), + "utf8" + )) + await updatePage(pageData, config); + } + loader.succeed(); + } + br(); log(`All done!`); }); diff --git a/src/init.js b/src/init.js index 7007cf2..b494c92 100644 --- a/src/init.js +++ b/src/init.js @@ -5,7 +5,7 @@ import ora from "ora"; import { welcome, log, br, error } from "./helpers"; import { login } from "./actions/auth"; import { getCampaigns } from "./actions/campaigns"; -import { syncStyles, syncComponents } from "./actions/sync"; +import { syncStyles, syncComponents, syncPages } from "./actions/sync"; import { saveConfig } from "./config"; export default function init(program) { @@ -95,6 +95,9 @@ export default function init(program) { // sync down custom components await syncComponents(config, process.cwd()); + // sync down pages + await syncPages(config, process.cwd()); + br(); log("All done! You can start development by running:", "green"); br(); diff --git a/src/start.js b/src/start.js index 9b4ccd2..8636c97 100644 --- a/src/start.js +++ b/src/start.js @@ -5,7 +5,7 @@ import path from "path"; import glob from "glob-promise"; import { welcome, log, br, error } from "./helpers"; -import { updateStyles, buildStyles } from "./actions/campaigns"; +import { updateStyles, buildStyles, updatePage } from "./actions/campaigns"; import { updateComponentFile, updateComponentConfig @@ -33,6 +33,7 @@ export default function start(program) { // watch folders const stylesDir = path.join(process.cwd(), "stylesheets"); const componentsDir = path.join(process.cwd(), "components"); + const pagesDir = path.join(process.cwd(), "pages"); fs.watch( stylesDir, { encoding: "utf8", recursive: true }, @@ -99,5 +100,26 @@ export default function start(program) { loader.succeed(); } ); + + fs.watch( + pagesDir, + { encoding: "utf8", recursive: true }, + async (eventType, filename) => { + const loader = ora(`Saving ${filename}`).start(); + try { + const pageData = JSON.parse(fs.readFileSync( + path.join(pagesDir, `${filename}`), + "utf8" + )) + await updatePage(pageData, config); + } catch (e) { + return error(e, loader); + } + + loader.succeed(); + } + ); + + }); } diff --git a/src/update.js b/src/update.js index 27e1869..1e464e4 100644 --- a/src/update.js +++ b/src/update.js @@ -2,7 +2,7 @@ import chalk from "chalk"; import inquirer from "inquirer"; import { welcome, log, br, error } from "./helpers"; -import { syncStyles, syncComponents } from "./actions/sync"; +import { syncStyles, syncComponents, syncPages } from "./actions/sync"; import { loadConfig } from "./config"; export default function update(program) { @@ -48,6 +48,9 @@ export default function update(program) { // sync down custom components await syncComponents(config, process.cwd()); + // sync down pages + await syncPages(config, process.cwd()); + br(); log( `All done! Run ${chalk.bold.underline.white(