diff --git a/commands/create-project-api.js b/commands/create-project-api.js index f1b6f19..97f7c7e 100644 --- a/commands/create-project-api.js +++ b/commands/create-project-api.js @@ -1,10 +1,12 @@ -import { mkdir, writeFile } from "fs/promises"; +import { mkdir, writeFile, readFile } from "fs/promises"; import { parse, stringify } from "envfile"; import simpleGit from "simple-git"; import wget from "../utils/wget.js"; import getFilesFromRepo from "../utils/getFilesFromRepo.js"; import pathExists from "../utils/pathExists.js"; import Logger from "../utils/logger.js"; +import getLatestNpmVersion from "../utils/getLatestNpmVersion.js"; +import addDependency from "../utils/addDependency.js"; const reactionRepoRoot = "https://raw.githubusercontent.com/reactioncommerce/reaction/trunk"; @@ -68,11 +70,16 @@ async function getFileFromCore(fileName) { /** * @summary update dotenv file to point to local mongo * @param {String} envData - file extracted from the reaction repo + * @param {Object} options - Any options for project creation * @returns {String} updated env file */ -function updateEnv(envData) { +function updateEnv(envData, options = {}) { const env = parse(envData); env.MONGO_URL = "mongodb://localhost:27017/reaction"; + + if (options.populate) { + env.LOAD_SAMPLE_DATA = true; + } const updatedEnv = stringify(env); return updatedEnv; } @@ -80,9 +87,10 @@ function updateEnv(envData) { /** * @summary get files directory from core repo * @param {String} projectName - The name of the project we are creating + * @param {Object} options - Any options for project creation * @returns {Promise} True if success */ -async function getFilesFromCore(projectName) { +async function getFilesFromCore(projectName, options = {}) { // get files directly from repo so it's always up-to-date const packageJson = await getFileFromCore("package.json"); const updatedPackageJson = await updatePackageJson(packageJson, projectName); @@ -104,7 +112,7 @@ async function getFilesFromCore(projectName) { await writeFile(`${projectName}/.nvmrc`, nvmrc); const dotenv = await getFileFromCore(".env.example"); - const updatedDotEnv = updateEnv(dotenv); + const updatedDotEnv = updateEnv(dotenv, options); await writeFile(`${projectName}/.env`, updatedDotEnv); return true; } @@ -129,6 +137,32 @@ async function gitInitDirectory(projectName) { } } +/** + * @summary add the api plugin to project + * @param {String} projectName name of the project to create + * @param {String} pluginName The plugin name + * @returns {Promise} true for success + */ +async function addSampleDataPlugin(projectName) { + try { + const version = await getLatestNpmVersion("@reactioncommerce/api-plugin-sample-data"); + + await addDependency(projectName, "@reactioncommerce/api-plugin-sample-data", { version }); + + const pluginJsonPath = `${projectName}/plugins.json`; + const plugins = JSON.parse(await readFile(pluginJsonPath)); + plugins.sampleData = "@reactioncommerce/api-plugin-sample-data"; + + await writeFile(pluginJsonPath, JSON.stringify(plugins, null, "\t")); + Logger.info("Added the sample data plugin successfully."); + return true; + } catch (error) { + Logger.error(error); + Logger.warn("Can't add the sample data plugin by error. Please add it manual."); + return false; + } +} + /** * @summary clones projects locally from repo @@ -148,10 +182,15 @@ export default async function createProjectApi(projectName, options) { await getFilesFromRepo("/templates/api-project/", projectName); // copy files directly from core that we want to be current - await getFilesFromCore(projectName); + await getFilesFromCore(projectName, options); // git init the new project await gitInitDirectory(projectName); + // add the sample data plugin if request + if (options.populate) { + await addSampleDataPlugin(projectName); + } + Logger.success("Project creation complete. Change to your directory and run `npm install`"); } diff --git a/index.js b/index.js index 34f44ee..a56adbe 100755 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ program .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"])) .argument("", "what to name the project") - // .option("--populate") + .option("--populate", "Install the sample data plugin") .option("--skip-meteor-install", "Skip Meteor install when creating admin project") .option("--dont-start-demo", "Don't auto start the demo project after creation") .action((type, name, options) => { diff --git a/tests/add-dependency.js b/tests/add-dependency.js new file mode 100644 index 0000000..4adfeb0 --- /dev/null +++ b/tests/add-dependency.js @@ -0,0 +1,47 @@ +import fs from "fs/promises"; +import sinon from "sinon"; +import { expect } from "chai"; +import Logger from "../utils/logger.js"; +import addDependency from "../utils/addDependency.js"; + +describe("The addDependency function", () => { + const mockPackageJson = { + name: "test", + dependencies: { + "plugin-1": "1.0.0" + } + }; + it("should update the package.json with the added dependency", async () => { + const expectedPath = "testProject/package.json"; + const expectedPackageJson = { ...mockPackageJson }; + expectedPackageJson.dependencies.testPackage = "^1.0.0"; + + const loggerInfoStub = sinon.stub(Logger, "info"); + const writeFileStub = sinon.stub(fs, "writeFile"); + const readFileStub = sinon.stub(fs, "readFile").resolves(JSON.stringify(mockPackageJson)); + + const result = await addDependency("testProject", "testPackage", { version: "1.0.0" }); + + expect(loggerInfoStub.calledWith("Add the testPackage@^1.0.0 package to project")).eq(true); + expect(readFileStub.calledWith(expectedPath)).eq(true); + expect(writeFileStub.calledWith(expectedPath, JSON.stringify(expectedPackageJson, null, "\t"))).eq(true); + expect(result).eq(true); + }); + + it("should update the package.json with the added dependency and don't include the version option", async () => { + const expectedPath = "testProject/package.json"; + const expectedPackageJson = { ...mockPackageJson }; + expectedPackageJson.dependencies.testPackage = "latest"; + + const loggerInfoStub = sinon.stub(Logger, "info"); + const writeFileStub = sinon.stub(fs, "writeFile"); + const readFileStub = sinon.stub(fs, "readFile").resolves(JSON.stringify(mockPackageJson)); + + const result = await addDependency("testProject", "testPackage"); + + expect(loggerInfoStub.calledWith("Add the testPackage@latest package to project")).eq(true); + expect(readFileStub.calledWith(expectedPath)).eq(true); + expect(writeFileStub.calledWith(expectedPath, JSON.stringify(expectedPackageJson, null, "\t"))).eq(true); + expect(result).eq(true); + }); +}); diff --git a/tests/create-api-project.js b/tests/create-api-project.js index d63777b..3b29e4c 100644 --- a/tests/create-api-project.js +++ b/tests/create-api-project.js @@ -28,6 +28,13 @@ describe("The create-project-api command", () => { // eslint-disable-next-line jest/valid-expect expect(responseLines[1]).to.equal("reaction-cli: Project creation complete. Change to your directory and run `npm install`"); }).timeout(5000); + + it("should print the correct output when user run with --populate option", async () => { + const response = await execute("./index.js", ["create-project", "api", "myshop", "--populate"]); + const responseLines = response.trim().split(EOL); + expect(responseLines[2]).equal("reaction-cli: Added the sample data plugin successfully."); + expect(responseLines[3]).equal("reaction-cli: Project creation complete. Change to your directory and run `npm install`"); + }).timeout(350000); }); afterEach(async () => { diff --git a/utils/addDependency.js b/utils/addDependency.js new file mode 100644 index 0000000..dc71b4f --- /dev/null +++ b/utils/addDependency.js @@ -0,0 +1,23 @@ +import fs from "fs/promises"; +import Logger from "./logger.js"; + +/** + * @summary Add the dependencies for the package.json file + * @param {String} projectName projectName name of the project to add + * @param {String} packageName name of the package to add + * @param {Object} options the package name + * @returns {Promise} true for success + */ +export default async function addDependency(projectName, packageName, { version = "latest", rule = "^" } = {}) { + const versionString = version === "latest" ? version : `${rule}${version}`; + + Logger.info(`Add the ${packageName}@${versionString} package to project`); + + const packageJsonPath = `${projectName}/package.json`; + const packageJson = await fs.readFile(packageJsonPath); + const packageJsonData = JSON.parse(packageJson); + packageJsonData.dependencies[packageName] = versionString; + await fs.writeFile(packageJsonPath, JSON.stringify(packageJsonData, null, "\t")); + + return true; +} diff --git a/utils/getLatestNpmVersion.js b/utils/getLatestNpmVersion.js new file mode 100644 index 0000000..31d9aa8 --- /dev/null +++ b/utils/getLatestNpmVersion.js @@ -0,0 +1,12 @@ +import wget from "./wget.js"; + +/** + * @summary Get latest npm package version + * @param {String} packageName name of the npm package + * @returns {Promise} the version string of the npm package + */ +export default async function getLatestNpmVersion(packageName) { + const packageDesc = await wget(`https://registry.npmjs.org/${packageName}`); + const packageDescData = JSON.parse(packageDesc); + return packageDescData["dist-tags"].latest; +}