@w5s/mrm-preset 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
Files changed (51) hide show
  1. package/.turbo/turbo-build.log +11 -0
  2. package/.turbo/turbo-docs.log +43 -0
  3. package/.turbo/turbo-format.log +7 -0
  4. package/.turbo/turbo-lint.log +7 -0
  5. package/.turbo/turbo-prepare.log +2 -0
  6. package/.turbo/turbo-test.log +19 -0
  7. package/CHANGELOG.md +237 -0
  8. package/README.md +1 -1
  9. package/bootstrap/index.js +43 -42
  10. package/ci/_gitlab/AutoDevopsInclude.gitlab-ci.yml +2 -0
  11. package/ci/github.js +47 -0
  12. package/ci/gitlab.js +1 -1
  13. package/ci/index.js +7 -2
  14. package/commitlint/index.js +5 -2
  15. package/config.json +1 -0
  16. package/contributing/index.js +1 -1
  17. package/contributing/templates/CODE_OF_CONDUCT.md +3 -4
  18. package/core/block.js +2 -3
  19. package/core/commitlint.js +3 -3
  20. package/core/cspell.js +1 -10
  21. package/core/eslint.js +25 -6
  22. package/{gitignore/template.js → core/git.ignore.js} +32 -6
  23. package/core/git.js +21 -18
  24. package/core/githooks.js +8 -7
  25. package/core/githubCI.js +56 -0
  26. package/core/jest.js +50 -69
  27. package/core/jsonFile.js +8 -7
  28. package/core/lintStaged.js +3 -3
  29. package/core/npm.js +52 -15
  30. package/core/pkg.js +92 -14
  31. package/core/project.js +6 -0
  32. package/core/semanticRelease.js +4 -4
  33. package/core/turbo.js +57 -0
  34. package/core/typedoc.js +7 -20
  35. package/core/vscode.js +1 -1
  36. package/cspell/index.js +20 -10
  37. package/editorconfig/index.js +3 -1
  38. package/eslint/index.js +81 -32
  39. package/githooks/index.js +47 -56
  40. package/gitignore/index.js +5 -22
  41. package/jest/index.js +13 -17
  42. package/lang/.eslintrc.json +4 -1
  43. package/lang/index.js +22 -19
  44. package/licenses/index.js +26 -0
  45. package/package.json +17 -12
  46. package/postconfigure/index.js +3 -3
  47. package/project/index.js +217 -156
  48. package/release/index.js +5 -5
  49. package/renovate/index.js +7 -5
  50. package/tsconfig.json +2 -1
  51. package/core/workspace.js +0 -6
package/core/cspell.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const { json } = require('mrm-core');
2
- const pkg = require('./pkg');
3
- const npm = require('./npm');
2
+ const npm = require('./npm.js');
4
3
  /**
5
4
  * @typedef {{
6
5
  * version: '0.2',
@@ -22,14 +21,6 @@ const npm = require('./npm');
22
21
  function cspell({ state, update }) {
23
22
  const hasCSpell = state === 'present';
24
23
 
25
- pkg.withPackageJson((packageFile) => {
26
- pkg.script(packageFile, {
27
- name: 'cspell',
28
- script: 'cspell',
29
- state,
30
- });
31
- });
32
-
33
24
  const cSpellFile = json('cSpell.json', {
34
25
  version: '0.2',
35
26
  language: 'en',
package/core/eslint.js CHANGED
@@ -1,14 +1,29 @@
1
1
  const { packageJson } = require('mrm-core');
2
-
2
+ const jsonFile = require('./jsonFile.js');
3
+ /**
4
+ * @typedef {import('eslint').Linter.Config} ESLintConfig
5
+ */
3
6
  /**
4
- * @param {import('eslint').Linter.Config} config
7
+ * @param {{
8
+ * state: 'present'|'absent',
9
+ * update: (config: ESLintConfig) => ESLintConfig
10
+ * }} config
5
11
  */
6
- function eslintConfig(config) {
12
+ function eslintConfig({ state, update }) {
7
13
  const packageFile = packageJson();
8
- packageFile.merge({ eslintConfig: config });
14
+
15
+ jsonFile.value(packageFile, {
16
+ path: 'eslintConfig',
17
+ state,
18
+ // @ts-ignore
19
+ update,
20
+ /** @type {ESLintConfig} */
21
+ // @ts-ignore
22
+ default: {},
23
+ });
24
+
9
25
  packageFile.save();
10
26
  }
11
- exports.eslintConfig = eslintConfig;
12
27
 
13
28
  /**
14
29
  *
@@ -19,4 +34,8 @@ function eslintIgnore(ignorePatterns) {
19
34
  packageFile.merge({ eslintIgnore: ignorePatterns });
20
35
  packageFile.save();
21
36
  }
22
- exports.eslintIgnore = eslintIgnore;
37
+
38
+ module.exports = {
39
+ eslintConfig,
40
+ eslintIgnore,
41
+ };
@@ -11,6 +11,7 @@ module.exports = {
11
11
  yarn-debug.log*
12
12
  yarn-error.log*
13
13
  lerna-debug.log*
14
+ .pnpm-debug.log*
14
15
 
15
16
  # Diagnostic reports (https://nodejs.org/api/report.html)
16
17
  report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@@ -47,8 +48,8 @@ module.exports = {
47
48
  node_modules/
48
49
  jspm_packages/
49
50
 
50
- # TypeScript v1 declaration files
51
- typings/
51
+ # Snowpack dependency directory (https://snowpack.dev/)
52
+ web_modules/
52
53
 
53
54
  # TypeScript cache
54
55
  *.tsbuildinfo
@@ -59,6 +60,9 @@ module.exports = {
59
60
  # Optional eslint cache
60
61
  .eslintcache
61
62
 
63
+ # Optional stylelint cache
64
+ .stylelintcache
65
+
62
66
  # Microbundle cache
63
67
  .rpt2_cache/
64
68
  .rts2_cache_cjs/
@@ -74,15 +78,20 @@ module.exports = {
74
78
  # Yarn Integrity file
75
79
  .yarn-integrity
76
80
 
77
- # dotenv environment variables file
81
+ # dotenv environment variable files
78
82
  .env
79
- .env.test
83
+ .env.development.local
84
+ .env.test.local
85
+ .env.production.local
86
+ .env.local
80
87
 
81
88
  # parcel-bundler cache (https://parceljs.org/)
82
89
  .cache
90
+ .parcel-cache
83
91
 
84
92
  # Next.js build output
85
93
  .next
94
+ out
86
95
 
87
96
  # Nuxt.js build / generate output
88
97
  .nuxt
@@ -92,11 +101,18 @@ module.exports = {
92
101
  .cache/
93
102
  # Comment in the public line in if your project uses Gatsby and not Next.js
94
103
  # https://nextjs.org/blog/next-9-1#public-directory-support
95
- public/
104
+ # public
96
105
 
97
106
  # vuepress build output
98
107
  .vuepress/dist
99
108
 
109
+ # vuepress v2.x temp and cache directory
110
+ .temp
111
+ .cache
112
+
113
+ # Docusaurus cache and generated files
114
+ .docusaurus
115
+
100
116
  # Serverless directories
101
117
  .serverless/
102
118
 
@@ -107,7 +123,17 @@ module.exports = {
107
123
  .dynamodb/
108
124
 
109
125
  # TernJS port file
110
- .tern-port`,
126
+ .tern-port
127
+
128
+ # Stores VSCode versions used for testing VSCode extensions
129
+ .vscode-test
130
+
131
+ # yarn v2
132
+ .yarn/cache
133
+ .yarn/unplugged
134
+ .yarn/build-state.yml
135
+ .yarn/install-state.gz
136
+ .pnp.*`,
111
137
  VisualStudioCode: `
112
138
  .vscode/*
113
139
  !.vscode/settings.json
package/core/git.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { file, ini } = require('mrm-core');
2
- const block = require('./block');
2
+ const block = require('./block.js');
3
3
 
4
4
  /**
5
5
  *
@@ -7,23 +7,9 @@ const block = require('./block');
7
7
  function remoteSync() {
8
8
  try {
9
9
  const gitConfig = ini('.git/config');
10
- const remoteURL = gitConfig.get('[remote "origin"]').url;
11
-
12
- return remoteURL
13
- ? [
14
- /**
15
- * 1. filter github URL
16
- * @param {string} returnValue
17
- */
18
- (returnValue) => returnValue.replace(/^git@github.com:/, 'https://github.com/'),
19
- /**
20
- * 2. filter gitlab URL
21
- * @param {string} returnValue
22
- */
23
- (returnValue) => returnValue.replace(/^git@gitlab.com:/, 'https://gitlab.com/'),
24
- ].reduce((returnValue, filter) => filter(returnValue), remoteURL)
25
- : remoteURL;
26
- } catch (error) {
10
+ const remoteURL = gitConfig.get('remote "origin"').url.trim();
11
+ return remoteURL;
12
+ } catch {
27
13
  // ignore error
28
14
  }
29
15
 
@@ -61,3 +47,20 @@ function gitIgnore(section, sectionContent) {
61
47
  });
62
48
  }
63
49
  exports.gitIgnore = gitIgnore;
50
+
51
+ /**
52
+ * @param {string[]} flags
53
+ */
54
+ function gitIgnoreTemplate(flags) {
55
+ // eslint-disable-next-line global-require, import/no-dynamic-require
56
+ const templateMap = require('./git.ignore.js');
57
+
58
+ flags.forEach((flag) => {
59
+ // @ts-ignore
60
+ if (templateMap[flag]) {
61
+ // @ts-ignore
62
+ gitIgnore(flag, templateMap[flag]);
63
+ }
64
+ });
65
+ }
66
+ exports.gitIgnoreTemplate = gitIgnoreTemplate;
package/core/githooks.js CHANGED
@@ -1,10 +1,10 @@
1
- const { execSync } = require('child_process');
2
- const path = require('path');
1
+ const { execSync } = require('node:child_process');
2
+ const path = require('node:path');
3
3
  const { file, packageJson, makeDirs } = require('mrm-core');
4
- const project = require('./project');
5
- const npm = require('./npm');
6
- const pkg = require('./pkg');
7
- const block = require('./block');
4
+ const project = require('./project.js');
5
+ const npm = require('./npm.js');
6
+ const pkg = require('./pkg.js');
7
+ const block = require('./block.js');
8
8
 
9
9
  /**
10
10
  * @param {{
@@ -44,7 +44,8 @@ function husky({ state }) {
44
44
  * }} options
45
45
  */
46
46
  function gitHook({ name, state, content }) {
47
- const hasHusky = Boolean(packageJson().get('devDependencies.husky'));
47
+ const packageFileDefault = packageJson();
48
+ const hasHusky = pkg.hasDependency(packageFileDefault, 'husky', 'dev');
48
49
  const hasGitHook = hasHusky && state === 'present';
49
50
  const hookDirectory = '.husky';
50
51
  const hookFileName = path.join(hookDirectory, name);
@@ -0,0 +1,56 @@
1
+ const { yaml } = require('mrm-core');
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
+
5
+ const FILE_PATH = '.github/workflows';
6
+
7
+ function isSupported() {
8
+ return fs.existsSync(FILE_PATH);
9
+ }
10
+ exports.isSupported = isSupported;
11
+
12
+ /**
13
+ * @typedef { string | {
14
+ * group: string,
15
+ * 'cancel-in-progress': boolean,
16
+ * }} GithubWorkflowConcurrency
17
+ */
18
+ // TODO: add strict typing
19
+ /**
20
+ * @typedef {{
21
+ * name?: string,
22
+ * jobs?: Record<string, any>
23
+ * on?: any,
24
+ * env?: Record<string, string>,
25
+ * permissions?: any,
26
+ * defaults?: Record<string, any>,
27
+ * concurrency?: GithubWorkflowConcurrency,
28
+ * }} GithubWorkflowConfig
29
+ */
30
+
31
+ /**
32
+ * @param {{
33
+ * state: 'present'|'absent',
34
+ * name: string,
35
+ * update?: (config: GithubWorkflowConfig) => GithubWorkflowConfig
36
+ * }} options
37
+ */
38
+ function workflow({ name, state, update }) {
39
+ const workflowFile = yaml(path.join(FILE_PATH, name));
40
+ if (state === 'present') {
41
+ /** @type {GithubWorkflowConfig} */
42
+ let value = workflowFile.get() || {
43
+ jobs: {},
44
+ };
45
+ if (update) {
46
+ value = update(value);
47
+ // @ts-ignore
48
+ workflowFile.set(value);
49
+ }
50
+
51
+ workflowFile.save();
52
+ } else {
53
+ workflowFile.delete();
54
+ }
55
+ }
56
+ exports.workflow = workflow;
package/core/jest.js CHANGED
@@ -1,9 +1,8 @@
1
1
  /* eslint-disable no-template-curly-in-string */
2
2
  const { packageJson } = require('mrm-core');
3
- const { vscodeSnippets } = require('./vscode');
4
- const pkg = require('./pkg');
5
- const npm = require('./npm');
6
- const project = require('./project');
3
+ const pkg = require('./pkg.js');
4
+ const npm = require('./npm.js');
5
+ const project = require('./project.js');
7
6
 
8
7
  /**
9
8
  * @param {{
@@ -13,38 +12,29 @@ const project = require('./project');
13
12
  function jest({ state }) {
14
13
  const packageFileDefault = packageJson();
15
14
  const hasJest = state === 'present';
16
- const hasTypescript = Boolean(packageFileDefault.get('devDependencies.typescript'));
17
- const hasWorkspaces = Boolean(packageFileDefault.get('workspaces'));
15
+ const hasWorkspaces = pkg.hasWorkspaces(packageFileDefault);
18
16
 
19
17
  pkg.withPackageJson((packageFile) => {
20
- if (hasJest) {
21
- const ignorePatterns = ['/node_modules/', '/docs/', '/lib/', '/build/', '/.cache/', '/public/'];
22
- packageFile.merge({
23
- jest: {
24
- coveragePathIgnorePatterns: ignorePatterns,
25
- testPathIgnorePatterns: ignorePatterns,
26
- },
27
- });
28
- if (hasTypescript) {
29
- packageFile.merge({
30
- jest: {
31
- globals: {
32
- 'ts-jest': {
33
- tsconfig: hasWorkspaces ? 'tsconfig.settings.json' : 'tsconfig.json',
34
- },
35
- },
36
- roots: hasWorkspaces ? ['<rootDir>/packages'] : null,
37
- transform: {
38
- '^.+\\.tsx?$': 'ts-jest',
39
- },
40
- },
41
- });
42
- } else {
43
- packageFile.unset('jest.globals.ts-jest').unset('jest.transform.^.+\\.tsx?$');
44
- }
45
- } else {
46
- packageFile.unset('jest');
47
- }
18
+ const ignorePatterns = ['/node_modules/', '/docs/', '/lib/', '/build/', '/.cache/', '/public/'];
19
+
20
+ pkg.value(packageFile, {
21
+ path: 'jest',
22
+ state: hasJest ? 'present' : 'absent',
23
+ update: hasWorkspaces
24
+ ? () => ({
25
+ preset: 'es-jest',
26
+ projects: packageFile
27
+ .get('workspaces.packages', packageFile.get('workspaces', []))
28
+ .map((/** @type {string} */ workspace) => `<rootDir>/${workspace}`),
29
+ })
30
+ : (config) => ({
31
+ ...config,
32
+ preset: 'es-jest',
33
+ coveragePathIgnorePatterns: ignorePatterns,
34
+ testPathIgnorePatterns: ignorePatterns,
35
+ }),
36
+ default: {},
37
+ });
48
38
 
49
39
  pkg.script(packageFile, {
50
40
  name: project.coverage,
@@ -56,48 +46,39 @@ function jest({ state }) {
56
46
  script: hasWorkspaces ? pkg.emptyScript : 'jest',
57
47
  state: !hasJest || hasWorkspaces ? 'default' : 'present',
58
48
  });
59
- pkg.script(packageFile, {
60
- name: `${project.test}:watch`,
61
- script: pkg.emptyScript,
62
- state: 'absent',
63
- });
64
49
  });
50
+
65
51
  // Dependencies
66
52
  npm.dependency({
67
53
  dev: true,
68
- name: ['jest', 'babel-jest', '@babel/core'],
54
+ name: ['jest', 'es-jest'],
69
55
  state: hasJest ? 'present' : 'absent',
70
56
  });
71
- npm.dependency({
72
- dev: true,
73
- name: ['ts-jest', '@types/jest'],
74
- state: hasJest && hasTypescript ? 'present' : 'absent',
75
- });
76
57
 
77
- vscodeSnippets({
78
- name: 'jest',
79
- snippets: {
80
- 'Jest Describe Block': {
81
- body: ["describe('${1:description}', () => {", '\t$0', '});'],
82
- description: 'Jest describe block',
83
- prefix: 'describe',
84
- scope: 'javascript,typescript',
85
- },
86
- 'Jest Expect': {
87
- body: 'expect($0)',
88
- description: 'Jest expect assertion',
89
- prefix: 'expect',
90
- scope: 'javascript,typescript',
91
- },
92
- 'Jest Test Block': {
93
- body: ["test('${1:description}', () => {", '\t$0', '});'],
94
- description: 'Jest test block',
95
- prefix: 'test',
96
- scope: 'javascript,typescript',
97
- },
98
- },
99
- state: hasJest ? 'present' : 'absent',
100
- });
58
+ // vscodeSnippets({
59
+ // name: 'jest',
60
+ // snippets: {
61
+ // 'Jest Describe Block': {
62
+ // body: ["describe('${1:description}', () => {", '\t$0', '});'],
63
+ // description: 'Jest describe block',
64
+ // prefix: 'describe',
65
+ // scope: 'javascript,typescript',
66
+ // },
67
+ // 'Jest Expect': {
68
+ // body: 'expect($0)',
69
+ // description: 'Jest expect assertion',
70
+ // prefix: 'expect',
71
+ // scope: 'javascript,typescript',
72
+ // },
73
+ // 'Jest Test Block': {
74
+ // body: ["test('${1:description}', () => {", '\t$0', '});'],
75
+ // description: 'Jest test block',
76
+ // prefix: 'test',
77
+ // scope: 'javascript,typescript',
78
+ // },
79
+ // },
80
+ // state: hasJest ? 'present' : 'absent',
81
+ // });
101
82
  }
102
83
 
103
84
  module.exports = {
package/core/jsonFile.js CHANGED
@@ -41,16 +41,17 @@ function setValue(jsonFile, path, pathValue) {
41
41
  */
42
42
  function value(jsonFile, { state, path, default: defaultValue, update: nextValue }) {
43
43
  if (state === 'present') {
44
- if (nextValue != null) {
45
- const resolvedValue = typeof nextValue === 'function' ? nextValue(getValue(jsonFile, path)) : nextValue;
46
- if (resolvedValue != null) {
47
- setValue(jsonFile, path, resolvedValue);
44
+ let currentValue = getValue(jsonFile, path);
45
+
46
+ if (currentValue == null) {
47
+ currentValue = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
48
+ if (currentValue != null) {
49
+ setValue(jsonFile, path, currentValue);
48
50
  }
49
51
  }
50
52
 
51
- const currentValue = getValue(jsonFile, path);
52
- if (currentValue == null) {
53
- const resolvedValue = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
53
+ if (nextValue != null) {
54
+ const resolvedValue = typeof nextValue === 'function' ? nextValue(getValue(jsonFile, path)) : nextValue;
54
55
  if (resolvedValue != null) {
55
56
  setValue(jsonFile, path, resolvedValue);
56
57
  }
@@ -1,6 +1,6 @@
1
- const pkg = require('./pkg');
2
- const npm = require('./npm');
3
- const jsonFile = require('./jsonFile');
1
+ const pkg = require('./pkg.js');
2
+ const npm = require('./npm.js');
3
+ const jsonFile = require('./jsonFile.js');
4
4
 
5
5
  /**
6
6
  * @typedef {Record<string, string|string[]>} LintStagedConfig
package/core/npm.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable default-param-last */
2
+ /* eslint-disable no-use-before-define */
1
3
  /* eslint-disable import/no-extraneous-dependencies */
2
4
  /* cSpell: disable */
3
5
  // @ts-check
@@ -16,25 +18,26 @@ const log = require('mrm-core/src/util/log');
16
18
  // @ts-ignore
17
19
  const execCommand = require('mrm-core/src/util/execCommand');
18
20
  // @ts-ignore
19
- const json = require('mrm-core/src/formats/json');
20
- // @ts-ignore
21
21
  const packageJson = require('mrm-core/src/files/packageJson');
22
22
  // @ts-ignore
23
23
  const MrmError = require('mrm-core/src/error');
24
+ const { yaml, json } = require('mrm-core');
24
25
 
25
26
  /**
26
- * @typedef Options
27
- * @property {boolean} [dev]
28
- * @property {boolean} [yarn]
29
- * @property {Record<string, string>} [versions]
27
+ * @typedef {{
28
+ * dev?: boolean,
29
+ * yarn?: boolean,
30
+ * versions?: Record<string, string>,
31
+ * }} Options
30
32
  */
31
33
 
32
34
  /**
33
- * @typedef RunOptions
34
- * @property {boolean} [dev]
35
- * @property {boolean} [remove]
36
- * @property {boolean} [stdio]
37
- * @property {string} [cwd]
35
+ * @typedef {{
36
+ * dev?: boolean,
37
+ * remove?: boolean,
38
+ * stdio?: boolean,
39
+ * cwd?: string,
40
+ * }} RunOptions
38
41
  */
39
42
 
40
43
  /**
@@ -132,7 +135,7 @@ function runNpm(deps, options = {}, exec) {
132
135
  function runYarn(deps, options = {}, exec) {
133
136
  const add = options.dev ? ['add', '--dev'] : ['add'];
134
137
  const remove = ['remove'];
135
- const args = (options.remove ? remove : add).concat(isUsingWorkspaces() ? ['-W'] : []).concat(deps);
138
+ const args = (options.remove ? remove : add).concat(isUsingWorkspaces() && !isYarnBerry() ? ['-W'] : []).concat(deps);
136
139
 
137
140
  return execCommand(exec, 'yarn', args, {
138
141
  cwd: options.cwd,
@@ -165,7 +168,7 @@ function getVersionedDep(dep, versions) {
165
168
  /**
166
169
  *
167
170
  * @param {Options} options
168
- * @returns {Record<string, string>}
171
+ * @returns {Record<string, string>} - map of package names to versions
169
172
  */
170
173
  function getOwnDependencies(options) {
171
174
  const pkg = packageJson({
@@ -180,7 +183,7 @@ function getOwnDependencies(options) {
180
183
  * Return version of installed npm package
181
184
  *
182
185
  * @param {string} name
183
- * @returns {string}
186
+ * @returns {string} - version
184
187
  */
185
188
  function getInstalledVersion(name) {
186
189
  return json(`./node_modules/${name}/package.json`).get('version');
@@ -193,7 +196,7 @@ function getInstalledVersion(name) {
193
196
  * @param {string[]} deps
194
197
  * @param {Record<string, string>} versions
195
198
  * @param {Options} options
196
- * @returns {string[]}
199
+ * @returns {string[]} - list of not installed dependencies
197
200
  */
198
201
  function getUnsatisfiedDeps(deps, versions, options) {
199
202
  const ownDependencies = getOwnDependencies(options);
@@ -246,6 +249,39 @@ function isUsingWorkspaces() {
246
249
  return Boolean(packageJson().get('workspaces'));
247
250
  }
248
251
 
252
+ function isYarnBerry() {
253
+ const yamlRC = yaml('.yarnrc.yml');
254
+ return yamlRC.exists() && !(yamlRC.get('yarnPath') || '').includes('.yarn/releases/yarn-1.');
255
+ }
256
+
257
+ /**
258
+ * @param {'npm'|`yarn@${'classic'|'berry'}`} defaultPackageManager
259
+ */
260
+ function bootstrap(defaultPackageManager) {
261
+ const packageFile = json(`./package.json`);
262
+ const isYarn = isUsingYarn() || defaultPackageManager.startsWith('yarn@');
263
+ if (!packageFile.get('packageManager') && isYarn) {
264
+ execCommand(undefined, 'yarn', ['set', 'version', 'berry']);
265
+ yaml('.yarnrc.yml')
266
+ .merge({
267
+ nodeLinker: 'node-modules',
268
+ })
269
+ .save();
270
+ // Downgrade
271
+ if (defaultPackageManager.endsWith('@classic')) {
272
+ execCommand(undefined, 'yarn', ['set', 'version', 'classic']);
273
+ }
274
+ }
275
+
276
+ // lock files
277
+ if (isYarn && !fs.existsSync('yarn.lock')) {
278
+ execCommand(undefined, 'yarn', ['install']);
279
+ }
280
+ if (!isYarn && !fs.existsSync('package-lock.json')) {
281
+ execCommand(undefined, 'npm', ['install']);
282
+ }
283
+ }
284
+
249
285
  /**
250
286
  * @param {{
251
287
  * name: string|string[]|Record<string, string>,
@@ -263,5 +299,6 @@ function dependency({ name, state, ...options }) {
263
299
  }
264
300
 
265
301
  module.exports = {
302
+ bootstrap,
266
303
  dependency,
267
304
  };