@commercetools-frontend/create-mc-app 20.10.1 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @commercetools-frontend/create-mc-app
2
2
 
3
+ ## 21.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`bb1f7d75`](https://github.com/commercetools/merchant-center-application-kit/commit/bb1f7d75ff54f7fef05c4d2b3328b88e400b4867) Thanks [@emmenko](https://github.com/emmenko)! - Drop Node.js `v12`. Recommended min Node.js version is `v14` or `v16`.
8
+
9
+ * [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`bb1f7d75`](https://github.com/commercetools/merchant-center-application-kit/commit/bb1f7d75ff54f7fef05c4d2b3328b88e400b4867) Thanks [@emmenko](https://github.com/emmenko)! - Following breaking changes were introduced:
10
+
11
+ - The starter template has been updated to use the new Org-level Custom Application features.
12
+ - The Custom Application config of the starter template has been converted from `.json` to `.mjs`, to allow importing and referencing constants.
13
+ - When installing the starter template using the `create-mc-app` CLI, the Custom Application config is updated with some of the user inputs, like `entryPointUriPath`.
14
+ - If no `entryPointUriPath` is provided, a random one is assigned.
15
+
16
+ For more information see [Release notes v21](https://docs.commercetools.com/custom-applications/releases/2022-01-31-custom-applications-v21).
17
+
18
+ ### Patch Changes
19
+
20
+ - [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`bb1f7d75`](https://github.com/commercetools/merchant-center-application-kit/commit/bb1f7d75ff54f7fef05c4d2b3328b88e400b4867) Thanks [@emmenko](https://github.com/emmenko)! - Use version range for Babel packages.
21
+
22
+ ## 21.0.0-rc.1
23
+
24
+ ### Patch Changes
25
+
26
+ - [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`5ea8baf1`](https://github.com/commercetools/merchant-center-application-kit/commit/5ea8baf1b2ca2661aac9a6a572d2c8e596ee0b2c) Thanks [@emmenko](https://github.com/emmenko)! - Use version range for Babel packages.
27
+
28
+ ## 21.0.0-rc.0
29
+
30
+ ### Major Changes
31
+
32
+ - [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`1c363fad`](https://github.com/commercetools/merchant-center-application-kit/commit/1c363fad7ab770a739ac8080358e41ae4af42074) Thanks [@emmenko](https://github.com/emmenko)! - Drop Node.js `v12`. Recommended min Node.js version is `v14` or `v16`.
33
+
34
+ * [#2430](https://github.com/commercetools/merchant-center-application-kit/pull/2430) [`e079fdcb`](https://github.com/commercetools/merchant-center-application-kit/commit/e079fdcb21ae7dddf14e554be1bd6e36f7346417) Thanks [@emmenko](https://github.com/emmenko)! - Following breaking changes were introduced:
35
+
36
+ - The starter template has been updated to use the new Org-level Custom Application features.
37
+ - The Custom Application config of the starter template has been converted from `.json` to `.mjs`, to allow importing and referencing constants.
38
+ - When installing the starter template using the `create-mc-app` CLI, the Custom Application config is updated with some of the user inputs, like `entryPointUriPath`.
39
+ - If no `entryPointUriPath` is provided, a random one is assigned.
40
+
41
+ For more information see [Release notes v21](https://docs.commercetools.com/custom-applications/releases/2022-01-31-custom-applications-v21).
42
+
43
+ ## 20.10.6
44
+
45
+ ### Patch Changes
46
+
47
+ - [#2386](https://github.com/commercetools/merchant-center-application-kit/pull/2386) [`d7fcf6fc`](https://github.com/commercetools/merchant-center-application-kit/commit/d7fcf6fc8495d4eae68e0a4f4c1f1b3e0e394454) Thanks [@emmenko](https://github.com/emmenko)! - Upgrade to Yarn v3
48
+
3
49
  ## 20.10.1
4
50
 
5
51
  ### Patch Changes
@@ -3,6 +3,7 @@
3
3
  const mri = require('mri');
4
4
  const Listr = require('listr');
5
5
  const {
6
+ isValidNodeVersion,
6
7
  shouldUseYarn,
7
8
  tasks,
8
9
  hintOutdatedVersion,
@@ -10,12 +11,14 @@ const {
10
11
  } = require('../src');
11
12
  const pkg = require('../package.json');
12
13
 
14
+ isValidNodeVersion(process.versions.node, pkg.engines.node);
15
+
13
16
  const currentVersion = pkg.version;
14
17
 
15
18
  async function execute() {
16
19
  const flags = mri(process.argv.slice(2), {
17
20
  alias: { help: ['h'] },
18
- default: { 'skip-install': false },
21
+ default: { 'skip-install': false, yes: false },
19
22
  });
20
23
  const commands = flags._;
21
24
 
@@ -31,13 +34,23 @@ async function execute() {
31
34
  Available options: ["starter"]
32
35
  --template-version <version> (optional) The version of the template to install [default "main"]
33
36
  --skip-install (optional) Skip installing the dependencies after cloning the template [default "false"]
37
+ --yes (optional) If set, the prompt options with default values will be skipped. [default "false"]
38
+ --entry-point-uri-path <value> (optional) The version of the template to install [default "starter-<hash>"]
39
+ --initial-project-key <value> (optional) A commercetools project key used for the initial login in development. By default, the value is prompted in the terminal.
34
40
  `);
35
41
  process.exit(0);
36
42
  }
37
- console.log(`Version: v${currentVersion}`);
43
+ console.log('');
44
+ console.log(`create-mc-app: v${currentVersion}`);
38
45
  hintOutdatedVersion(currentVersion);
39
46
  console.log('');
40
- const options = parseArguments(flags);
47
+
48
+ console.log(
49
+ `Documentation available at https://docs.commercetools.com/custom-applications`
50
+ );
51
+ console.log('');
52
+
53
+ const options = await parseArguments(flags);
41
54
 
42
55
  const taskList = new Listr(
43
56
  [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend/create-mc-app",
3
- "version": "20.10.1",
3
+ "version": "21.0.0",
4
4
  "description": "Create Merchant Center applications to quickly get up and running",
5
5
  "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
6
  "repository": {
@@ -11,21 +11,20 @@
11
11
  "homepage": "https://docs.commercetools.com/custom-applications",
12
12
  "keywords": ["javascript", "frontend", "react", "toolkit"],
13
13
  "license": "MIT",
14
- "private": false,
15
- "scripts": {},
16
14
  "publishConfig": {
17
15
  "access": "public"
18
16
  },
19
- "bin": {
20
- "create-mc-app": "./bin/create-mc-app.js"
21
- },
17
+ "bin": "./bin/cli.js",
22
18
  "dependencies": {
19
+ "@babel/core": "^7.16.10",
23
20
  "execa": "5.1.1",
24
21
  "listr": "0.14.3",
25
22
  "mri": "1.2.0",
23
+ "prettier": "2.5.1",
24
+ "rcfile": "1.0.3",
26
25
  "semver": "7.3.5"
27
26
  },
28
27
  "engines": {
29
- "node": ">=12 || >=14"
28
+ "node": ">=14"
30
29
  }
31
30
  }
package/src/index.js CHANGED
@@ -1,9 +1,10 @@
1
- const { shouldUseYarn } = require('./utils');
1
+ const { isValidNodeVersion, shouldUseYarn } = require('./utils');
2
2
  const tasks = require('./tasks');
3
3
  const hintOutdatedVersion = require('./hint-outdated-version');
4
4
  const parseArguments = require('./parse-arguments');
5
5
 
6
6
  module.exports = {
7
+ isValidNodeVersion,
7
8
  shouldUseYarn,
8
9
  tasks,
9
10
  hintOutdatedVersion,
@@ -1,12 +1,56 @@
1
1
  /* eslint-disable no-console */
2
2
  const path = require('path');
3
+ const util = require('util');
4
+ const readline = require('readline');
5
+ const crypto = require('crypto');
3
6
  const {
4
7
  throwIfTemplateIsNotSupported,
5
8
  throwIfProjectDirectoryExists,
9
+ throwIfInitialProjectKeyIsMissing,
6
10
  } = require('./validations');
7
11
  const { isSemVer } = require('./utils');
8
12
 
9
- module.exports = function parseArguments(flags) {
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+ const question = util.promisify(rl.question).bind(rl);
18
+
19
+ const getTemplateName = (flags) => flags.template || 'starter';
20
+ const getEntryPointUriPath = async (flags) => {
21
+ if (flags['entry-point-uri-path']) {
22
+ return flags['entry-point-uri-path'];
23
+ }
24
+
25
+ const templateName = getTemplateName(flags);
26
+ const randomEntryPointUriPath = `${templateName}-${crypto
27
+ .randomBytes(3)
28
+ .toString('hex')}`;
29
+
30
+ if (flags.yes) {
31
+ return randomEntryPointUriPath;
32
+ }
33
+
34
+ const answerEntryPointUriPath = await question(
35
+ `Provide the Custom Application entryPointUriPath (default "${randomEntryPointUriPath}"): `
36
+ );
37
+ return answerEntryPointUriPath || randomEntryPointUriPath;
38
+ };
39
+ const getInitialProjectKey = async (flags) => {
40
+ if (flags['initial-project-key']) {
41
+ return flags['initial-project-key'];
42
+ }
43
+
44
+ const initialProjectKey = await question(
45
+ `Provide the initial project key for local development: `
46
+ );
47
+
48
+ throwIfInitialProjectKeyIsMissing(initialProjectKey);
49
+
50
+ return initialProjectKey;
51
+ };
52
+
53
+ module.exports = async function parseArguments(flags) {
10
54
  const [projectDirectoryName] = flags._;
11
55
  if (!projectDirectoryName) {
12
56
  throw new Error('Missing required argument "<project-directory>"');
@@ -14,7 +58,7 @@ module.exports = function parseArguments(flags) {
14
58
  const projectDirectoryPath = path.resolve(projectDirectoryName);
15
59
 
16
60
  // Parse options
17
- const templateName = flags.template || 'starter';
61
+ const templateName = getTemplateName(flags);
18
62
  let tagOrBranchVersion = flags['template-version'] || 'main';
19
63
  tagOrBranchVersion =
20
64
  isSemVer(tagOrBranchVersion) && !tagOrBranchVersion.startsWith('v')
@@ -25,10 +69,18 @@ module.exports = function parseArguments(flags) {
25
69
  throwIfProjectDirectoryExists(projectDirectoryName, projectDirectoryPath);
26
70
  throwIfTemplateIsNotSupported(templateName);
27
71
 
72
+ // Read prompts
73
+ const entryPointUriPath = await getEntryPointUriPath(flags);
74
+ const initialProjectKey = await getInitialProjectKey(flags);
75
+
76
+ rl.close();
77
+
28
78
  return {
29
79
  projectDirectoryName,
30
80
  projectDirectoryPath,
31
81
  templateName,
32
82
  tagOrBranchVersion,
83
+ entryPointUriPath,
84
+ initialProjectKey,
33
85
  };
34
86
  };
@@ -1,42 +1,42 @@
1
1
  const os = require('os');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const { slugify } = require('../utils');
4
+ const babel = require('@babel/core');
5
+ const { resolveFilePathByExtension } = require('../utils');
5
6
 
6
- const entryPointVariableRegex = /entryPointUriPath\s?=\s?'(.*)';/;
7
+ function replaceEntryPointUriPathInConstants(filePath, options) {
8
+ const result = babel.transformFileSync(filePath, {
9
+ plugins: [
10
+ function replaceConstants() {
11
+ return {
12
+ visitor: {
13
+ VariableDeclarator(nodePath) {
14
+ if (nodePath.node.id.name === 'entryPointUriPath') {
15
+ nodePath.node.init = babel.types.stringLiteral(
16
+ options.entryPointUriPath
17
+ );
18
+ }
19
+ },
20
+ },
21
+ };
22
+ },
23
+ ],
24
+ retainLines: true,
25
+ });
26
+
27
+ fs.writeFileSync(filePath, result.code + os.EOL, {
28
+ encoding: 'utf8',
29
+ });
30
+ }
7
31
 
8
- // TODO: when we enable OIDC login as the default behavior, we also want to
9
- // update the following things:
10
- // * permission names (based on the entryPointUriPath)
11
32
  module.exports = function updateApplicationConstants(options) {
12
33
  return {
13
34
  title: 'Updating application constants',
14
35
  task: () => {
15
- const applicationConstantsPath = path.join(
16
- options.projectDirectoryPath,
17
- // TODO: support other file extensions?
18
- 'src/constants/application.js'
19
- );
20
- const appConstantsRaw = fs.readFileSync(applicationConstantsPath, {
21
- encoding: 'utf8',
22
- });
23
-
24
- // Set the entry point based on the package/folder name.
25
- const entryPointUriPath = slugify(options.projectDirectoryName);
26
-
27
- // TODO: use Babel AST?
28
- const updatedAppConstantsRaw = appConstantsRaw.replace(
29
- entryPointVariableRegex,
30
- `entryPointUriPath = '${entryPointUriPath}';`
31
- );
32
-
33
- fs.writeFileSync(
34
- applicationConstantsPath,
35
- updatedAppConstantsRaw + os.EOL,
36
- {
37
- encoding: 'utf8',
38
- }
36
+ const applicationConstantsPath = resolveFilePathByExtension(
37
+ path.join(options.projectDirectoryPath, 'src/constants')
39
38
  );
39
+ replaceEntryPointUriPathInConstants(applicationConstantsPath, options);
40
40
  },
41
41
  };
42
42
  };
@@ -1,38 +1,63 @@
1
- const os = require('os');
2
1
  const fs = require('fs');
3
2
  const path = require('path');
4
- const { slugify, wordify } = require('../utils');
3
+ const rcfile = require('rcfile');
4
+ const prettier = require('prettier');
5
+ const babel = require('@babel/core');
6
+ const { wordify, resolveFilePathByExtension } = require('../utils');
7
+
8
+ function replaceApplicationInfoInCustomApplicationConfig(filePath, options) {
9
+ const appName = wordify(options.entryPointUriPath);
10
+
11
+ const result = babel.transformFileSync(filePath, {
12
+ plugins: [
13
+ function replaceCustomApplicationConfig() {
14
+ return {
15
+ visitor: {
16
+ Identifier(nodePath) {
17
+ if (nodePath.isIdentifier({ name: 'name' })) {
18
+ nodePath.parent.value = babel.types.stringLiteral(appName);
19
+ }
20
+ if (nodePath.isIdentifier({ name: 'initialProjectKey' })) {
21
+ nodePath.parent.value = babel.types.stringLiteral(
22
+ options.initialProjectKey
23
+ );
24
+ }
25
+ if (nodePath.isIdentifier({ name: 'defaultLabel' })) {
26
+ if (
27
+ nodePath.findParent((parentPath) =>
28
+ parentPath.get('key').isIdentifier({ name: 'mainMenuLink' })
29
+ )
30
+ ) {
31
+ nodePath.parent.value = babel.types.stringLiteral(appName);
32
+ }
33
+ }
34
+ },
35
+ },
36
+ };
37
+ },
38
+ ],
39
+ retainLines: true,
40
+ });
41
+
42
+ const prettierConfig = rcfile('prettier', {
43
+ cwd: options.projectDirectoryPath,
44
+ });
45
+ const formattedData = prettier.format(result.code, prettierConfig);
46
+ fs.writeFileSync(filePath, formattedData, {
47
+ encoding: 'utf8',
48
+ });
49
+ }
5
50
 
6
51
  module.exports = function updateCustomApplicationConfig(options) {
7
- // NOTE: this only works assuming the template uses a JSON file.
8
52
  return {
9
- title: 'Updating custom-application-config.json',
53
+ title: 'Updating Custom Applications config',
10
54
  task: () => {
11
- const customApplicationConfigJsonPath = path.join(
12
- options.projectDirectoryPath,
13
- 'custom-application-config.json'
14
- );
15
- const appConfigJson = JSON.parse(
16
- fs.readFileSync(customApplicationConfigJsonPath, { encoding: 'utf8' })
55
+ const customApplicationConfigPath = resolveFilePathByExtension(
56
+ path.join(options.projectDirectoryPath, 'custom-application-config')
17
57
  );
18
-
19
- // Set the entry point based on the package/folder name.
20
- const entryPointUriPath = slugify(options.projectDirectoryName);
21
- const appName = wordify(entryPointUriPath);
22
-
23
- const updatedAppConfigJson = Object.assign({}, appConfigJson, {
24
- name: appName,
25
- entryPointUriPath,
26
- menuLinks: {
27
- ...appConfigJson.menuLinks,
28
- defaultLabel: appName,
29
- },
30
- });
31
-
32
- fs.writeFileSync(
33
- customApplicationConfigJsonPath,
34
- JSON.stringify(updatedAppConfigJson, null, 2) + os.EOL,
35
- { encoding: 'utf8' }
58
+ replaceApplicationInfoInCustomApplicationConfig(
59
+ customApplicationConfigPath,
60
+ options
36
61
  );
37
62
  },
38
63
  };
package/src/utils.js CHANGED
@@ -1,4 +1,20 @@
1
+ const fs = require('fs');
1
2
  const execa = require('execa');
3
+ const semver = require('semver');
4
+
5
+ const isValidNodeVersion = (currentNodeVersion, expectedVersionRange) => {
6
+ const hasValidNodeVersion = semver.satisfies(
7
+ currentNodeVersion,
8
+ expectedVersionRange
9
+ );
10
+
11
+ if (!hasValidNodeVersion) {
12
+ console.error(
13
+ `You are running Node ${currentNodeVersion} but create-mc-app requires Node ${expectedVersionRange}. Please update your version of Node.`
14
+ );
15
+ process.exit(1);
16
+ }
17
+ };
2
18
 
3
19
  const isSemVer = (version) => /^(v?)([0-9].[0-9].[0-9])+/.test(version);
4
20
 
@@ -21,10 +37,20 @@ const wordify = (slug) =>
21
37
  .map((word) => upperFirst(word))
22
38
  .join(' ');
23
39
 
40
+ const resolveFilePathByExtension = (requestedModule) => {
41
+ const fileExtension = ['.js', '.ts', '.mjs', '.cjs'].find((ext) => {
42
+ const filePath = `${requestedModule}${ext}`;
43
+ return fs.existsSync(filePath);
44
+ });
45
+ return `${requestedModule}${fileExtension}`;
46
+ };
47
+
24
48
  module.exports = {
49
+ isValidNodeVersion,
25
50
  isSemVer,
26
51
  shouldUseYarn,
27
52
  slugify,
28
53
  wordify,
29
54
  upperFirst,
55
+ resolveFilePathByExtension,
30
56
  };
@@ -47,8 +47,15 @@ const throwIfTemplateVersionDoesNotExist = (
47
47
  }
48
48
  };
49
49
 
50
+ const throwIfInitialProjectKeyIsMissing = (initialProjectKey) => {
51
+ if (!initialProjectKey) {
52
+ throw new Error(`Provide a valid project key that you have access to.`);
53
+ }
54
+ };
55
+
50
56
  module.exports = {
51
57
  throwIfTemplateIsNotSupported,
52
58
  throwIfProjectDirectoryExists,
53
59
  throwIfTemplateVersionDoesNotExist,
60
+ throwIfInitialProjectKeyIsMissing,
54
61
  };