Skip to content

Commit

Permalink
feat: add populate option when create api project
Browse files Browse the repository at this point in the history
Signed-off-by: Brian Nguyen <[email protected]>
  • Loading branch information
vanpho93 committed Jul 13, 2022
1 parent 9f8b846 commit 37fb8cf
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 6 deletions.
49 changes: 44 additions & 5 deletions commands/create-project-api.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -68,21 +70,27 @@ 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;
}

/**
* @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<Boolean>} 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);
Expand All @@ -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;
}
Expand All @@ -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<boolean>} 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
Expand All @@ -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`");
}
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ program
.description("Create a new Open Commerce project of one of several types")
.addArgument(new commander.Argument("<type>", "which project type to create").choices(["api", "storefront", "admin", "demo"]))
.argument("<name>", "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) => {
Expand Down
47 changes: 47 additions & 0 deletions tests/add-dependency.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
7 changes: 7 additions & 0 deletions tests/create-api-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
23 changes: 23 additions & 0 deletions utils/addDependency.js
Original file line number Diff line number Diff line change
@@ -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<boolean>} 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;
}
12 changes: 12 additions & 0 deletions utils/getLatestNpmVersion.js
Original file line number Diff line number Diff line change
@@ -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<string|null>} 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;
}

0 comments on commit 37fb8cf

Please sign in to comment.