create-alistt69-kit 0.1.9 → 0.1.11
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/README.md +1 -0
- package/package.json +2 -2
- package/src/core/apply-features.js +1 -1
- package/src/core/collect-project-info.js +2 -26
- package/src/core/create-project.js +1 -0
- package/src/core/render-project-readme.js +125 -12
- package/src/features/autoprefixer/index.js +9 -13
- package/src/features/define-feature.js +33 -0
- package/src/features/eslint/index.js +19 -24
- package/src/features/index.js +11 -1
- package/src/features/react-router/index.js +9 -13
- package/src/features/stylelint/index.js +11 -16
- package/src/utils/package-json.js +66 -42
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
[](https://www.npmjs.com/package/create-alistt69-kit)
|
|
13
13
|
[](https://nodejs.org/)
|
|
14
14
|
[](./LICENSE)
|
|
15
|
+
[](https://github.com/alistt69/create-alistt69-kit/actions/workflows/ci.yml)
|
|
15
16
|
</td>
|
|
16
17
|
</tr>
|
|
17
18
|
</table>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-alistt69-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Opinionated React + TypeScript + Webpack project generator by alistt69",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
35
|
"dev": "node ./bin/index.js",
|
|
36
|
-
"smoke": "node ./scripts/smoke.mjs"
|
|
36
|
+
"smoke": "node ./scripts/smoke/index.mjs"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18.18.0"
|
|
@@ -9,35 +9,11 @@ import {
|
|
|
9
9
|
text,
|
|
10
10
|
} from '@clack/prompts';
|
|
11
11
|
import process from 'node:process';
|
|
12
|
+
import { defaultFeatureIds, featurePromptOptions, availableFeatureIdSet } from '../features/index.js';
|
|
12
13
|
import { format } from '../utils/console-format.js';
|
|
13
14
|
import { allowedPackageManagers } from '../utils/package-manager.js';
|
|
14
15
|
|
|
15
|
-
const availableFeatures = [
|
|
16
|
-
{
|
|
17
|
-
value: 'eslint',
|
|
18
|
-
label: 'ESLint + Stylistic',
|
|
19
|
-
hint: 'JS/TS/React linting',
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
value: 'stylelint',
|
|
23
|
-
label: 'Stylelint',
|
|
24
|
-
hint: 'SCSS/CSS linting',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
value: 'autoprefixer',
|
|
28
|
-
label: 'Autoprefixer',
|
|
29
|
-
hint: 'PostCSS vendor prefixes',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
value: 'react-router',
|
|
33
|
-
label: 'React Router DOM',
|
|
34
|
-
hint: 'Routing + FSD-like app/pages/shared',
|
|
35
|
-
},
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const defaultFeatureIds = availableFeatures.map((feature) => feature.value);
|
|
39
16
|
const defaultPackageManager = 'npm';
|
|
40
|
-
const availableFeatureIdSet = new Set(defaultFeatureIds);
|
|
41
17
|
|
|
42
18
|
function handleCancel(value) {
|
|
43
19
|
if (!isCancel(value)) {
|
|
@@ -130,7 +106,7 @@ export async function collectProjectInfo(cliArgs = {}) {
|
|
|
130
106
|
|
|
131
107
|
selectedFeatureIds = handleCancel(await multiselect({
|
|
132
108
|
message: 'Remove unnecessary features',
|
|
133
|
-
options:
|
|
109
|
+
options: featurePromptOptions,
|
|
134
110
|
initialValues: defaultFeatureIds,
|
|
135
111
|
required: false,
|
|
136
112
|
}));
|
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
+
|
|
3
4
|
import { featuresById } from '../features/index.js';
|
|
4
5
|
import { readPackageJson } from '../utils/package-json.js';
|
|
5
6
|
import { getRunScriptCommand } from '../utils/package-manager.js';
|
|
6
7
|
|
|
8
|
+
const defaultTooling = [
|
|
9
|
+
{
|
|
10
|
+
title: 'React',
|
|
11
|
+
docs: 'https://react.dev/',
|
|
12
|
+
description: 'UI library',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: 'TypeScript',
|
|
16
|
+
docs: 'https://www.typescriptlang.org/',
|
|
17
|
+
description: 'Static typing',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'Webpack',
|
|
21
|
+
docs: 'https://webpack.js.org/',
|
|
22
|
+
description: 'Bundling and build pipeline',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: 'SCSS Modules',
|
|
26
|
+
docs: 'https://github.com/css-modules/css-modules',
|
|
27
|
+
description: 'Scoped styling',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: 'SVGR',
|
|
31
|
+
docs: 'https://react-svgr.com/',
|
|
32
|
+
description: 'SVGs as React components',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: 'Webpack Bundle Analyzer',
|
|
36
|
+
docs: 'https://github.com/webpack-contrib/webpack-bundle-analyzer',
|
|
37
|
+
description: 'Bundle size inspection',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
7
41
|
const scriptDescriptions = {
|
|
8
42
|
dev: 'Start development server',
|
|
9
43
|
start: 'Start development server',
|
|
@@ -17,6 +51,22 @@ const scriptDescriptions = {
|
|
|
17
51
|
'lint:styles:fix': 'Run Stylelint with autofix',
|
|
18
52
|
};
|
|
19
53
|
|
|
54
|
+
function formatMarkdownList(items, fallback = '- None') {
|
|
55
|
+
if (items.length === 0) {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return items.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatDefaultTooling() {
|
|
63
|
+
return formatMarkdownList(
|
|
64
|
+
defaultTooling.map(({ title, docs, description }) => (
|
|
65
|
+
`- [${title}](${docs}) — ${description}`
|
|
66
|
+
)),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
20
70
|
function formatFeatureList(selectedFeatureIds) {
|
|
21
71
|
if (selectedFeatureIds.length === 0) {
|
|
22
72
|
return '- None';
|
|
@@ -26,7 +76,13 @@ function formatFeatureList(selectedFeatureIds) {
|
|
|
26
76
|
.map((featureId) => {
|
|
27
77
|
const feature = featuresById.get(featureId);
|
|
28
78
|
|
|
29
|
-
|
|
79
|
+
if (!feature) {
|
|
80
|
+
return `- ${featureId}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return feature.hint
|
|
84
|
+
? `- ${feature.title} — ${feature.hint}`
|
|
85
|
+
: `- ${feature.title}`;
|
|
30
86
|
})
|
|
31
87
|
.join('\n');
|
|
32
88
|
}
|
|
@@ -38,20 +94,57 @@ function formatScripts(packageManager, scripts) {
|
|
|
38
94
|
return '_No scripts available._';
|
|
39
95
|
}
|
|
40
96
|
|
|
41
|
-
return entries
|
|
42
|
-
|
|
43
|
-
|
|
97
|
+
return entries
|
|
98
|
+
.map(([scriptName]) => {
|
|
99
|
+
const command = getRunScriptCommand(packageManager, scriptName);
|
|
100
|
+
const description = scriptDescriptions[scriptName] ?? 'Project script';
|
|
44
101
|
|
|
45
|
-
|
|
46
|
-
|
|
102
|
+
return `- \`${command}\` — ${description}`;
|
|
103
|
+
})
|
|
104
|
+
.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatQuickStart({
|
|
108
|
+
projectName,
|
|
109
|
+
packageManager,
|
|
110
|
+
shouldInstallDependencies,
|
|
111
|
+
}) {
|
|
112
|
+
const steps = [`cd ${projectName}`];
|
|
113
|
+
|
|
114
|
+
if (!shouldInstallDependencies) {
|
|
115
|
+
steps.push(packageManager === 'yarn' ? 'yarn' : `${packageManager} install`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
steps.push(getRunScriptCommand(packageManager, 'start'));
|
|
119
|
+
|
|
120
|
+
return [
|
|
121
|
+
shouldInstallDependencies
|
|
122
|
+
? 'Dependencies are already installed.'
|
|
123
|
+
: 'Install dependencies first, then start the development server.',
|
|
124
|
+
'',
|
|
125
|
+
'```bash',
|
|
126
|
+
...steps,
|
|
127
|
+
'```',
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatProjectStructure() {
|
|
132
|
+
return [
|
|
133
|
+
'- `public/` — static assets and HTML template',
|
|
134
|
+
'- `src/` — application source code',
|
|
135
|
+
'- `src/app/` — app bootstrap, providers, entry-level setup',
|
|
136
|
+
'- `src/styles/` — global styles and shared styling layer',
|
|
137
|
+
'- `config/build/` — split webpack configuration',
|
|
138
|
+
].join('\n');
|
|
47
139
|
}
|
|
48
140
|
|
|
49
141
|
export async function renderProjectReadme({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
142
|
+
projectPath,
|
|
143
|
+
projectName,
|
|
144
|
+
selectedFeatureIds,
|
|
145
|
+
packageManager,
|
|
146
|
+
shouldInstallDependencies,
|
|
147
|
+
}) {
|
|
55
148
|
const packageJson = await readPackageJson(projectPath);
|
|
56
149
|
|
|
57
150
|
const readmeContent = [
|
|
@@ -59,10 +152,30 @@ export async function renderProjectReadme({
|
|
|
59
152
|
'',
|
|
60
153
|
'Created with `create-alistt69-kit`.',
|
|
61
154
|
'',
|
|
62
|
-
'##
|
|
155
|
+
'## Overview',
|
|
156
|
+
'',
|
|
157
|
+
'Starter project based on React + TypeScript + Webpack with optional tooling selected during scaffolding.',
|
|
158
|
+
'',
|
|
159
|
+
'## Included by default',
|
|
160
|
+
'',
|
|
161
|
+
formatDefaultTooling(),
|
|
162
|
+
'',
|
|
163
|
+
'## Selected optional features',
|
|
63
164
|
'',
|
|
64
165
|
formatFeatureList(selectedFeatureIds),
|
|
65
166
|
'',
|
|
167
|
+
'## Quick start',
|
|
168
|
+
'',
|
|
169
|
+
formatQuickStart({
|
|
170
|
+
projectName,
|
|
171
|
+
packageManager,
|
|
172
|
+
shouldInstallDependencies,
|
|
173
|
+
}),
|
|
174
|
+
'',
|
|
175
|
+
'## Project structure',
|
|
176
|
+
'',
|
|
177
|
+
formatProjectStructure(),
|
|
178
|
+
'',
|
|
66
179
|
'## Available scripts',
|
|
67
180
|
'',
|
|
68
181
|
formatScripts(packageManager, packageJson.scripts ?? {}),
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
import { cp } from 'node:fs/promises';
|
|
2
1
|
import { dirname, resolve } from 'node:path';
|
|
3
2
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
import { defineFeature } from '../define-feature.js';
|
|
5
5
|
|
|
6
6
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
7
7
|
const currentDirPath = dirname(currentFilePath);
|
|
8
8
|
|
|
9
|
-
export const autoprefixerFeature = {
|
|
9
|
+
export const autoprefixerFeature = defineFeature({
|
|
10
10
|
id: 'autoprefixer',
|
|
11
11
|
title: 'Autoprefixer',
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
hint: 'PostCSS vendor prefixes',
|
|
13
|
+
packageJson: {
|
|
14
|
+
devDependencies: {
|
|
14
15
|
autoprefixer: '^10.4.21',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const filesDirPath = resolve(currentDirPath, 'files');
|
|
18
|
-
|
|
19
|
-
await cp(filesDirPath, projectPath, {
|
|
20
|
-
recursive: true,
|
|
21
|
-
});
|
|
16
|
+
},
|
|
22
17
|
},
|
|
23
|
-
|
|
18
|
+
copyFiles: resolve(currentDirPath, 'files'),
|
|
19
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cp } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { patchPackageJson } from '../utils/package-json.js';
|
|
4
|
+
|
|
5
|
+
export function defineFeature({
|
|
6
|
+
id,
|
|
7
|
+
title,
|
|
8
|
+
hint,
|
|
9
|
+
packageJson,
|
|
10
|
+
apply,
|
|
11
|
+
copyFiles,
|
|
12
|
+
}) {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
title,
|
|
16
|
+
hint,
|
|
17
|
+
async applyFeature(context) {
|
|
18
|
+
if (packageJson) {
|
|
19
|
+
await patchPackageJson(context.projectPath, packageJson);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (copyFiles) {
|
|
23
|
+
await cp(copyFiles, context.projectPath, {
|
|
24
|
+
recursive: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (apply) {
|
|
29
|
+
await apply(context);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,35 +1,30 @@
|
|
|
1
|
-
import { cp } from 'node:fs/promises';
|
|
2
1
|
import { dirname, resolve } from 'node:path';
|
|
3
2
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
import { defineFeature } from '../define-feature.js';
|
|
5
5
|
|
|
6
6
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
7
7
|
const currentDirPath = dirname(currentFilePath);
|
|
8
8
|
|
|
9
|
-
export const eslintFeature = {
|
|
9
|
+
export const eslintFeature = defineFeature({
|
|
10
10
|
id: 'eslint',
|
|
11
11
|
title: 'ESLint + Stylistic',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
hint: 'JS/TS/React linting',
|
|
13
|
+
packageJson: {
|
|
14
|
+
devDependencies: {
|
|
15
|
+
eslint: '^9.0.0',
|
|
16
|
+
'@eslint/js': '^9.0.0',
|
|
17
|
+
'@stylistic/eslint-plugin': '^5.0.0',
|
|
18
|
+
'typescript-eslint': '^8.0.0',
|
|
19
|
+
'eslint-plugin-import': '^2.0.0',
|
|
20
|
+
'eslint-plugin-react': '^7.0.0',
|
|
21
|
+
'eslint-plugin-react-hooks': '^7.0.0',
|
|
22
|
+
'eslint-plugin-unused-imports': '^4.0.0',
|
|
23
|
+
},
|
|
24
|
+
scripts: {
|
|
25
25
|
lint: 'eslint . --ext .js,.jsx,.ts,.tsx',
|
|
26
26
|
'lint:fix': 'eslint . --ext .js,.jsx,.ts,.tsx --fix',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const filesDirPath = resolve(currentDirPath, 'files');
|
|
30
|
-
|
|
31
|
-
await cp(filesDirPath, projectPath, {
|
|
32
|
-
recursive: true,
|
|
33
|
-
});
|
|
27
|
+
},
|
|
34
28
|
},
|
|
35
|
-
|
|
29
|
+
copyFiles: resolve(currentDirPath, 'files'),
|
|
30
|
+
});
|
package/src/features/index.js
CHANGED
|
@@ -12,4 +12,14 @@ export const features = [
|
|
|
12
12
|
|
|
13
13
|
export const featuresById = new Map(
|
|
14
14
|
features.map((feature) => [feature.id, feature]),
|
|
15
|
-
);
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const featurePromptOptions = features.map((feature) => ({
|
|
18
|
+
value: feature.id,
|
|
19
|
+
label: feature.title,
|
|
20
|
+
hint: feature.hint,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
export const defaultFeatureIds = features.map((feature) => feature.id);
|
|
24
|
+
|
|
25
|
+
export const availableFeatureIdSet = new Set(defaultFeatureIds);
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
import { cp } from 'node:fs/promises';
|
|
2
1
|
import { dirname, resolve } from 'node:path';
|
|
3
2
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
import { defineFeature } from '../define-feature.js';
|
|
5
5
|
|
|
6
6
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
7
7
|
const currentDirPath = dirname(currentFilePath);
|
|
8
8
|
|
|
9
|
-
export const reactRouterFeature = {
|
|
9
|
+
export const reactRouterFeature = defineFeature({
|
|
10
10
|
id: 'react-router',
|
|
11
11
|
title: 'React Router DOM',
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
hint: 'Routing + FSD-like app/pages/shared',
|
|
13
|
+
packageJson: {
|
|
14
|
+
dependencies: {
|
|
14
15
|
'react-router-dom': '^7.13.2',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const filesDirPath = resolve(currentDirPath, 'files');
|
|
18
|
-
|
|
19
|
-
await cp(filesDirPath, projectPath, {
|
|
20
|
-
recursive: true,
|
|
21
|
-
});
|
|
16
|
+
},
|
|
22
17
|
},
|
|
23
|
-
|
|
18
|
+
copyFiles: resolve(currentDirPath, 'files'),
|
|
19
|
+
});
|
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
import { cp } from 'node:fs/promises';
|
|
2
1
|
import { dirname, resolve } from 'node:path';
|
|
3
2
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
import { defineFeature } from '../define-feature.js';
|
|
5
5
|
|
|
6
6
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
7
7
|
const currentDirPath = dirname(currentFilePath);
|
|
8
8
|
|
|
9
|
-
export const stylelintFeature = {
|
|
9
|
+
export const stylelintFeature = defineFeature({
|
|
10
10
|
id: 'stylelint',
|
|
11
11
|
title: 'Stylelint',
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
hint: 'SCSS/CSS linting',
|
|
13
|
+
packageJson: {
|
|
14
|
+
devDependencies: {
|
|
14
15
|
stylelint: '^17.6.0',
|
|
15
16
|
'stylelint-config-standard-scss': '^17.0.0',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
await addScripts(projectPath, {
|
|
17
|
+
},
|
|
18
|
+
scripts: {
|
|
19
19
|
'lint:styles': 'stylelint "src/**/*.{scss,css}"',
|
|
20
20
|
'lint:styles:fix': 'stylelint "src/**/*.{scss,css}" --fix',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const filesDirPath = resolve(currentDirPath, 'files');
|
|
24
|
-
|
|
25
|
-
await cp(filesDirPath, projectPath, {
|
|
26
|
-
recursive: true,
|
|
27
|
-
});
|
|
21
|
+
},
|
|
28
22
|
},
|
|
29
|
-
|
|
23
|
+
copyFiles: resolve(currentDirPath, 'files'),
|
|
24
|
+
});
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
|
|
4
|
+
function sortRecordEntries(record) {
|
|
5
|
+
if (!record) {
|
|
6
|
+
return record;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return Object.fromEntries(
|
|
10
|
+
Object.entries(record).sort(([leftName], [rightName]) => (
|
|
11
|
+
leftName.localeCompare(rightName)
|
|
12
|
+
)),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function mergeRecordEntries(currentRecord, entriesToAdd) {
|
|
17
|
+
if (!entriesToAdd || Object.keys(entriesToAdd).length === 0) {
|
|
18
|
+
return currentRecord;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return sortRecordEntries({
|
|
22
|
+
...(currentRecord ?? {}),
|
|
23
|
+
...entriesToAdd,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizePackageJson(packageJson) {
|
|
28
|
+
packageJson.dependencies = sortRecordEntries(packageJson.dependencies);
|
|
29
|
+
packageJson.devDependencies = sortRecordEntries(packageJson.devDependencies);
|
|
30
|
+
packageJson.scripts = sortRecordEntries(packageJson.scripts);
|
|
31
|
+
|
|
32
|
+
return packageJson;
|
|
33
|
+
}
|
|
34
|
+
|
|
4
35
|
export async function readPackageJson(projectPath) {
|
|
5
36
|
const packageJsonPath = resolve(projectPath, 'package.json');
|
|
6
37
|
const content = await readFile(packageJsonPath, 'utf8');
|
|
@@ -13,61 +44,54 @@ export async function writePackageJson(projectPath, packageJson) {
|
|
|
13
44
|
|
|
14
45
|
await writeFile(
|
|
15
46
|
packageJsonPath,
|
|
16
|
-
`${JSON.stringify(packageJson, null, 4)}\n`,
|
|
47
|
+
`${JSON.stringify(normalizePackageJson(packageJson), null, 4)}\n`,
|
|
17
48
|
'utf8',
|
|
18
49
|
);
|
|
19
50
|
}
|
|
20
51
|
|
|
21
|
-
export async function
|
|
52
|
+
export async function updatePackageJson(projectPath, updater) {
|
|
22
53
|
const packageJson = await readPackageJson(projectPath);
|
|
23
54
|
|
|
24
|
-
packageJson
|
|
25
|
-
|
|
26
|
-
for (const [dependencyName, dependencyVersion] of Object.entries(dependenciesToAdd)) {
|
|
27
|
-
packageJson.dependencies[dependencyName] = dependencyVersion;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
packageJson.dependencies = Object.fromEntries(
|
|
31
|
-
Object.entries(packageJson.dependencies).sort(([leftName], [rightName]) => (
|
|
32
|
-
leftName.localeCompare(rightName)
|
|
33
|
-
)),
|
|
34
|
-
);
|
|
55
|
+
await updater(packageJson);
|
|
35
56
|
|
|
36
57
|
await writePackageJson(projectPath, packageJson);
|
|
37
|
-
}
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
packageJson.devDependencies ??= {};
|
|
59
|
+
return packageJson;
|
|
60
|
+
}
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
export async function patchPackageJson(projectPath, patch) {
|
|
63
|
+
return updatePackageJson(projectPath, (packageJson) => {
|
|
64
|
+
packageJson.dependencies = mergeRecordEntries(
|
|
65
|
+
packageJson.dependencies,
|
|
66
|
+
patch.dependencies,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
packageJson.devDependencies = mergeRecordEntries(
|
|
70
|
+
packageJson.devDependencies,
|
|
71
|
+
patch.devDependencies,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
packageJson.scripts = mergeRecordEntries(
|
|
75
|
+
packageJson.scripts,
|
|
76
|
+
patch.scripts,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
47
80
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
export async function addDependencies(projectPath, dependenciesToAdd) {
|
|
82
|
+
return patchPackageJson(projectPath, {
|
|
83
|
+
dependencies: dependenciesToAdd,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
53
86
|
|
|
54
|
-
|
|
87
|
+
export async function addDevDependencies(projectPath, dependenciesToAdd) {
|
|
88
|
+
return patchPackageJson(projectPath, {
|
|
89
|
+
devDependencies: dependenciesToAdd,
|
|
90
|
+
});
|
|
55
91
|
}
|
|
56
92
|
|
|
57
93
|
export async function addScripts(projectPath, scriptsToAdd) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (const [scriptName, scriptValue] of Object.entries(scriptsToAdd)) {
|
|
63
|
-
packageJson.scripts[scriptName] = scriptValue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
packageJson.scripts = Object.fromEntries(
|
|
67
|
-
Object.entries(packageJson.scripts).sort(([leftName], [rightName]) => (
|
|
68
|
-
leftName.localeCompare(rightName)
|
|
69
|
-
)),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
await writePackageJson(projectPath, packageJson);
|
|
94
|
+
return patchPackageJson(projectPath, {
|
|
95
|
+
scripts: scriptsToAdd,
|
|
96
|
+
});
|
|
73
97
|
}
|