diff --git a/.eslintignore b/.eslintignore index 75f7b06..3ba4bd6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ mystore/* myadmin/* myshop/* +mykinetic/* diff --git a/.gitignore b/.gitignore index e37f118..101aa3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .env +.vscode node_modules mydir myadmin mystore +mykinetic diff --git a/README.md b/README.md index a3bd5fc..d89e6b8 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ reaction develop ### Congratulations!! You're ready to start developing with Open Commerce -## Add the Admin/Storefront +## Add the Admin/Kinetic/Storefront --- Open Commerce includes an Admin panel for managing your system plus an example storefront implementation so you can see how you would go about building your own. @@ -145,6 +145,33 @@ reaction develop ``` For more information about developing the admin you can go to [Mailchimp Open Commerce Documentation](https://mailchimp.com/developer/open-commerce/) +## Adding the Kinetic +The Kinetic project uses pnpm as package manager so ensure you have this installed in your local development. + +Using npm to install: +``` +npm install -g pnpm +``` +To add the kinetic project you can run: +``` +reaction create-project kinetic +``` +and a `your-kinetic-name` directory will be created in the new directory. + +Change to that directory by running: +``` + cd +``` +Then run: +``` +pnpm install +``` +and you can start the kinetic project by running: +``` +reaction develop +``` +The kinetic will be available on port https//localhost:3001 + ## Adding a Storefront To add the example storefront project so you can browse your installation just run: ``` diff --git a/commands/create-project-kinetic.js b/commands/create-project-kinetic.js new file mode 100644 index 0000000..f9af613 --- /dev/null +++ b/commands/create-project-kinetic.js @@ -0,0 +1,58 @@ +import { writeFile, readFile } from "fs/promises"; +import simpleGit from "simple-git"; +import { copy } from "fs-extra"; +import Logger from "../utils/logger.js"; + +/** + * @summary modify package.json for use as a thin development project + * @param {String} packageJson - The contents of package.json + * @param {String} projectName - The name of the project + * @returns {String} The modified contents + */ +function updatePackageJson(packageJson, projectName) { + const packageData = JSON.parse(packageJson); + packageData.name = projectName; + packageData.version = "1.0.0"; + packageData.projectType = "kinetic"; + return JSON.stringify(packageData, null, 2); +} + +/** + * @summary Update the core file for this project + * @param {String} projectName - The name of the project we are creating + * @returns {Promise} True if success + */ +async function updateCoreFile(projectName) { + const packageJsonPath = `${projectName}/package.json`; + const packageJson = await readFile(packageJsonPath, { encoding: "utf8", flag: "r" }); + const updatedPackageJson = updatePackageJson(packageJson, projectName); + await writeFile(packageJsonPath, updatedPackageJson); + return true; +} + +/** + * @summary clones projects locally from repo + * @param {String} projectName name of the project to create + * @returns {Boolean} true for success + */ +export default async function createProjectKinetic(projectName) { + Logger.info("Creating kinetic", { projectName }); + const gitOptions = { + baseDir: `${process.cwd()}`, + binary: "git", + maxConcurrentProcesses: 6 + }; + const git = simpleGit(gitOptions); + Logger.info("Cloning project"); + try { + await git.clone("git@github.com:reactioncommerce/kinetic.git", projectName); + } catch (error) { + Logger.error(error); + return false; + } + await updateCoreFile(projectName); + await copy(`${projectName}/.env.example`, `${projectName}/.env`); + Logger.success("Kinetic project created. You can change to this directory and run `pnpm install`"); + return true; +} + diff --git a/commands/create-project.js b/commands/create-project.js index 8c5ceb0..61bab29 100644 --- a/commands/create-project.js +++ b/commands/create-project.js @@ -2,6 +2,7 @@ import Logger from "../utils/logger.js"; import checkDependencies from "../utils/checkDependencies.js"; import createProjectApi from "./create-project-api.js"; import createProjectAdmin from "./create-project-admin.js"; +import createProjectKinetic from "./create-project-kinetic.js"; import createProjectStorefront from "./create-project-storefront.js"; import createProjectDemo from "./create-project-demo.js"; @@ -9,11 +10,13 @@ const methodMap = { api: createProjectApi, admin: createProjectAdmin, storefront: createProjectStorefront, + kinetic: createProjectKinetic, demo: createProjectDemo }; const extraDependencyMap = { - storefront: ["yarn"] + storefront: ["yarn"], + kinetic: ["pnpm"] }; /** diff --git a/commands/develop-kinetic.js b/commands/develop-kinetic.js new file mode 100644 index 0000000..9179cb5 --- /dev/null +++ b/commands/develop-kinetic.js @@ -0,0 +1,33 @@ +import { spawn } from "child_process"; +import diehard from "diehard"; +import Logger from "../utils/logger.js"; +import checkBeforeDevelop from "../utils/checkBeforeDevelop.js"; + +/** + * @summary start develop mode for kinetic + * @param {Object} options - Any options for project creation + * @returns {Boolean} true for success + */ +export default async function developKinetic(options) { + if (!await checkBeforeDevelop("kinetic")) return; + Logger.info("Starting Open Commerce Kinetic Application Server in dev mode", { options }); + const api = spawn("pnpm", ["run", "dev"]); + api.stdout.on("data", (data) => { + // eslint-disable-next-line no-console + console.log(data.toString().trim()); // Echo output of command to console + }); + + api.stderr.on("data", (data) => { + // eslint-disable-next-line no-console + console.log(data.toString().trim()); // Echo error output + }); + + diehard.register(async (signal, uncaughtErr, done) => { + if (signal === "SIGINT") { + Logger.warn("Shutting down from Ctrl-C"); + } + done(); + }); + + diehard.listen(); +} diff --git a/commands/develop.js b/commands/develop.js index f1337d6..3261fc1 100644 --- a/commands/develop.js +++ b/commands/develop.js @@ -3,19 +3,22 @@ import Logger from "../utils/logger.js"; import checkDependencies from "../utils/checkDependencies.js"; import developStorefront from "./develop-storefront.js"; import developAdmin from "./develop-admin.js"; +import developKinetic from "./develop-kinetic.js"; import developApi from "./develop-api.js"; const functionMap = { api: developApi, admin: developAdmin, + kinetic: developKinetic, storefront: developStorefront }; const extraDependencyMap = { - storefront: ["yarn"] + storefront: ["yarn"], + kinetic: ["pnpm"] }; -const validProjectTypes = ["api", "admin-meteor", "storefront-example"]; +const validProjectTypes = ["api", "admin-meteor", "storefront-example", "kinetic"]; /** * @summary check if projectType exists, if not return empty string @@ -44,6 +47,9 @@ async function getProjectType() { case "admin-meteor": Logger.info("Found project type: admin-meteor"); return "admin"; + case "kinetic": + Logger.info("Found project type: kinetic"); + return "kinetic"; case "storefront-example": Logger.info("Found project type: storefront-example"); return "storefront"; diff --git a/index.js b/index.js index 1fd78f8..ea34984 100755 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ program.version(pkg.version); program .command("create-project") .description("Create a new Open Commerce project of one of several types") - .addArgument(new commander.Argument("", "which project type to create").choices(["api", "storefront", "admin", "demo"])) + .addArgument(new commander.Argument("", "which project type to create").choices(["api", "storefront", "admin", "kinetic", "demo"])) .argument("", "what to name the project") // .option("--populate") .option("--skip-meteor-install", "Skip Meteor install when creating admin project") @@ -37,7 +37,7 @@ program .command("develop") .description("Run a project locally in development mode") .addArgument(new commander.Argument("[type]", "which project type to develop on") - .choices(["api", "storefront", "admin"])) + .choices(["api", "storefront", "admin", "kinetic"])) .option("--no-debug") .option("--no-mongo-shutdown", "don't shut down mongo on abort") .action((type, options) => { diff --git a/tests/create-kinetic-project.js b/tests/create-kinetic-project.js new file mode 100644 index 0000000..6fd620b --- /dev/null +++ b/tests/create-kinetic-project.js @@ -0,0 +1,32 @@ +/* eslint-disable jest/valid-expect */ +import { EOL } from "os"; +import { spawn } from "child_process"; +import rimraf from "rimraf"; +import { expect } from "chai"; +import { sync as cmdExists } from "command-exists"; +import getConfig from "../utils/getConfig.js"; +import execute from "./utils/execute.js"; + +const config = getConfig(); + +beforeEach(async () => { + await rimraf.sync("./mykinetic"); + // Mock that we have alredy used the command to bypass telemetry logs + config.set("runOnce", true); + if (!cmdExists("pnpm")) { + spawn("npm", ["install", "pnpm", "-g"]); + } +}); + +describe("The create-project-kinetic command", () => { + it("should print the correct output", async () => { + const response = await execute("./index.js", ["create-project", "kinetic", "mykinetic"]); + const responseLines = response.trim().split(EOL); + // eslint-disable-next-line jest/valid-expect + expect(responseLines[0]).to.equal('reaction-cli: Creating kinetic: {"projectName":"mykinetic"}'); + }).timeout(350000); // cloning the admin takes a long time +}); + +afterEach(async () => { + config.set("runOnce", false); +}); diff --git a/utils/checkBeforeDevelop.js b/utils/checkBeforeDevelop.js index e3aa0cf..907b632 100644 --- a/utils/checkBeforeDevelop.js +++ b/utils/checkBeforeDevelop.js @@ -18,12 +18,18 @@ async function checkForApi() { */ export default async function checkBeforeDevelop(type = "api") { if (!await pathExists("node_modules")) { - if (type === "storefront") { - Logger.error("It looks like you have not run `yarn install` in this directory"); - Logger.error("Please run `yarn install` and try again"); - } else { - Logger.error("It looks like you have not run `npm install` in this directory"); - Logger.error("Please run `npm install` and try again"); + switch (type) { + case "storefront": + Logger.error("It looks like you have not run `yarn install` in this directory"); + Logger.error("Please run `yarn install` and try again"); + break; + case "kinetic": + Logger.error("It looks like you have not run `pnpm install` in this directory"); + Logger.error("Please run `pnpm install` and try again"); + break; + default: + Logger.error("It looks like you have not run `npm install` in this directory"); + Logger.error("Please run `npm install` and try again"); } return false; }