@i-santos/create-package-starter 0.1.2 → 1.1.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/README.md +19 -7
- package/lib/run.js +210 -35
- package/package.json +2 -2
- package/template/README.md +5 -11
- package/template/package.json +1 -6
- package/template/scripts/check.js +0 -4
package/README.md
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
# @i-santos/create-package-starter
|
|
2
2
|
|
|
3
|
-
Scaffold new npm packages with a
|
|
3
|
+
Scaffold new npm packages with a standardized Changesets release workflow.
|
|
4
4
|
|
|
5
5
|
## Install / Run
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx @i-santos/create-package-starter --name hello-package
|
|
9
9
|
npx @i-santos/create-package-starter --name @i-santos/swarm
|
|
10
|
+
npx @i-santos/create-package-starter init --dir ./existing-package
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
## Options
|
|
13
14
|
|
|
15
|
+
Create new package:
|
|
16
|
+
|
|
14
17
|
- `--name <name>` (required, supports `pkg` and `@scope/pkg`)
|
|
15
18
|
- `--out <directory>` (default: current directory)
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
Bootstrap existing package:
|
|
21
|
+
|
|
22
|
+
- `init`
|
|
23
|
+
- `--dir <directory>` (default: current directory)
|
|
24
|
+
- `--force` (overwrite managed files/scripts/dependency keys)
|
|
18
25
|
|
|
19
26
|
## Output
|
|
20
27
|
|
|
21
28
|
Generated package includes:
|
|
22
29
|
|
|
23
|
-
- `release:beta`
|
|
24
|
-
- `release:stable`
|
|
25
|
-
- `release:publish`
|
|
26
|
-
- `registry:start`
|
|
27
30
|
- `changeset`
|
|
28
31
|
- `version-packages`
|
|
29
32
|
- `release`
|
|
@@ -32,6 +35,15 @@ Generated package includes:
|
|
|
32
35
|
|
|
33
36
|
plus a minimal README, CHANGELOG, `.gitignore`, and check script.
|
|
34
37
|
|
|
38
|
+
## Existing Project Bootstrap
|
|
39
|
+
|
|
40
|
+
`init` configures an existing npm package directory in-place:
|
|
41
|
+
|
|
42
|
+
- ensures scripts `changeset`, `version-packages`, `release`
|
|
43
|
+
- ensures `@changesets/cli` in `devDependencies`
|
|
44
|
+
- creates (or preserves) `.changeset/config.json`, `.changeset/README.md`, and `.github/workflows/release.yml`
|
|
45
|
+
- default mode is safe-merge; use `--force` to overwrite managed files/keys
|
|
46
|
+
|
|
35
47
|
## Notes
|
|
36
48
|
|
|
37
49
|
- For scoped names, folder uses the short package name.
|
package/lib/run.js
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
-
const DEFAULT_RELEASE_CLI_PKG = '@i-santos/release-cli';
|
|
5
|
-
const DEFAULT_RELEASE_CLI_VERSION = '^0.1.0';
|
|
6
|
-
|
|
7
4
|
function usage() {
|
|
8
5
|
return [
|
|
9
6
|
'Uso:',
|
|
10
|
-
' create-package-starter --name <nome> [--out <diretorio>]
|
|
7
|
+
' create-package-starter --name <nome> [--out <diretorio>]',
|
|
8
|
+
' create-package-starter init [--dir <diretorio>] [--force]',
|
|
11
9
|
'',
|
|
12
10
|
'Exemplo:',
|
|
13
11
|
' create-package-starter --name hello-package',
|
|
14
|
-
' create-package-starter --name @i-santos/swarm',
|
|
15
|
-
' create-package-starter
|
|
16
|
-
' create-package-starter
|
|
12
|
+
' create-package-starter --name @i-santos/swarm --out ./packages',
|
|
13
|
+
' create-package-starter init --dir ./meu-pacote',
|
|
14
|
+
' create-package-starter init --force'
|
|
17
15
|
].join('\n');
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
function
|
|
18
|
+
function parseCreateArgs(argv) {
|
|
21
19
|
const args = {
|
|
22
|
-
out: process.cwd()
|
|
23
|
-
releaseCliPkg: DEFAULT_RELEASE_CLI_PKG,
|
|
24
|
-
releaseCliVersion: DEFAULT_RELEASE_CLI_VERSION
|
|
20
|
+
out: process.cwd()
|
|
25
21
|
};
|
|
26
22
|
|
|
27
23
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -39,18 +35,37 @@ function parseArgs(argv) {
|
|
|
39
35
|
continue;
|
|
40
36
|
}
|
|
41
37
|
|
|
42
|
-
if (token === '--
|
|
43
|
-
args.
|
|
44
|
-
i += 1;
|
|
38
|
+
if (token === '--help' || token === '-h') {
|
|
39
|
+
args.help = true;
|
|
45
40
|
continue;
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
throw new Error(`Argumento inválido: ${token}\n\n${usage()}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseInitArgs(argv) {
|
|
50
|
+
const args = {
|
|
51
|
+
dir: process.cwd(),
|
|
52
|
+
force: false
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
56
|
+
const token = argv[i];
|
|
57
|
+
|
|
58
|
+
if (token === '--dir') {
|
|
59
|
+
args.dir = argv[i + 1];
|
|
50
60
|
i += 1;
|
|
51
61
|
continue;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
if (token === '--force') {
|
|
65
|
+
args.force = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
if (token === '--help' || token === '-h') {
|
|
55
70
|
args.help = true;
|
|
56
71
|
continue;
|
|
@@ -62,6 +77,20 @@ function parseArgs(argv) {
|
|
|
62
77
|
return args;
|
|
63
78
|
}
|
|
64
79
|
|
|
80
|
+
function parseArgs(argv) {
|
|
81
|
+
if (argv[0] === 'init') {
|
|
82
|
+
return {
|
|
83
|
+
mode: 'init',
|
|
84
|
+
args: parseInitArgs(argv.slice(1))
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
mode: 'create',
|
|
90
|
+
args: parseCreateArgs(argv)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
function validateName(name) {
|
|
66
95
|
if (typeof name !== 'string') {
|
|
67
96
|
return false;
|
|
@@ -96,28 +125,154 @@ function copyDirRecursive(sourceDir, targetDir) {
|
|
|
96
125
|
|
|
97
126
|
function renderTemplateFile(filePath, variables) {
|
|
98
127
|
const source = fs.readFileSync(filePath, 'utf8');
|
|
99
|
-
const output = source
|
|
100
|
-
.replace(/__PACKAGE_NAME__/g, variables.packageName)
|
|
101
|
-
.replace(/__RELEASE_CLI_PKG__/g, variables.releaseCliPkg)
|
|
102
|
-
.replace(/__RELEASE_CLI_VERSION__/g, variables.releaseCliVersion);
|
|
128
|
+
const output = source.replace(/__PACKAGE_NAME__/g, variables.packageName);
|
|
103
129
|
|
|
104
130
|
fs.writeFileSync(filePath, output);
|
|
105
131
|
}
|
|
106
132
|
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
function readJsonFile(filePath) {
|
|
134
|
+
let raw;
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
try {
|
|
137
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new Error(`Erro ao ler ${filePath}: ${error.message}`);
|
|
113
140
|
}
|
|
114
141
|
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(raw);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw new Error(`Erro ao parsear JSON em ${filePath}: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function writeJsonFile(filePath, value) {
|
|
150
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function ensureFileFromTemplate(targetPath, templatePath, options) {
|
|
154
|
+
if (!fs.existsSync(templatePath)) {
|
|
155
|
+
throw new Error(`Erro: template não encontrado em ${templatePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const exists = fs.existsSync(targetPath);
|
|
159
|
+
|
|
160
|
+
if (exists && !options.force) {
|
|
161
|
+
return 'skipped';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
165
|
+
fs.copyFileSync(templatePath, targetPath);
|
|
166
|
+
|
|
167
|
+
if (exists) {
|
|
168
|
+
return 'overwritten';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return 'created';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function configureExistingPackage(packageDir, templateDir, force) {
|
|
175
|
+
if (!fs.existsSync(packageDir)) {
|
|
176
|
+
throw new Error(`Erro: diretório não encontrado: ${packageDir}`);
|
|
117
177
|
}
|
|
118
178
|
|
|
119
|
-
|
|
120
|
-
|
|
179
|
+
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
180
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
181
|
+
throw new Error(`Erro: package.json não encontrado em ${packageDir}.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
185
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
186
|
+
packageJson.devDependencies = packageJson.devDependencies || {};
|
|
187
|
+
|
|
188
|
+
const desiredScripts = {
|
|
189
|
+
changeset: 'changeset',
|
|
190
|
+
'version-packages': 'changeset version',
|
|
191
|
+
release: 'npm run check && changeset publish'
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const summary = {
|
|
195
|
+
createdFiles: [],
|
|
196
|
+
overwrittenFiles: [],
|
|
197
|
+
skippedFiles: [],
|
|
198
|
+
updatedScriptKeys: [],
|
|
199
|
+
skippedScriptKeys: [],
|
|
200
|
+
updatedDependencyKeys: [],
|
|
201
|
+
skippedDependencyKeys: []
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let packageJsonChanged = false;
|
|
205
|
+
|
|
206
|
+
for (const [key, value] of Object.entries(desiredScripts)) {
|
|
207
|
+
const exists = Object.prototype.hasOwnProperty.call(packageJson.scripts, key);
|
|
208
|
+
|
|
209
|
+
if (!exists || force) {
|
|
210
|
+
if (!exists || packageJson.scripts[key] !== value) {
|
|
211
|
+
packageJson.scripts[key] = value;
|
|
212
|
+
packageJsonChanged = true;
|
|
213
|
+
}
|
|
214
|
+
summary.updatedScriptKeys.push(key);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
summary.skippedScriptKeys.push(key);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const dependencyKey = '@changesets/cli';
|
|
222
|
+
const dependencyValue = '^2.29.7';
|
|
223
|
+
const depExists = Object.prototype.hasOwnProperty.call(packageJson.devDependencies, dependencyKey);
|
|
224
|
+
|
|
225
|
+
if (!depExists || force) {
|
|
226
|
+
if (!depExists || packageJson.devDependencies[dependencyKey] !== dependencyValue) {
|
|
227
|
+
packageJson.devDependencies[dependencyKey] = dependencyValue;
|
|
228
|
+
packageJsonChanged = true;
|
|
229
|
+
}
|
|
230
|
+
summary.updatedDependencyKeys.push(dependencyKey);
|
|
231
|
+
} else {
|
|
232
|
+
summary.skippedDependencyKeys.push(dependencyKey);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (packageJsonChanged) {
|
|
236
|
+
writeJsonFile(packageJsonPath, packageJson);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const fileSpecs = [
|
|
240
|
+
['.changeset/config.json', '.changeset/config.json'],
|
|
241
|
+
['.changeset/README.md', '.changeset/README.md'],
|
|
242
|
+
['.github/workflows/release.yml', '.github/workflows/release.yml']
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
for (const [targetRelativePath, templateRelativePath] of fileSpecs) {
|
|
246
|
+
const targetPath = path.join(packageDir, targetRelativePath);
|
|
247
|
+
const templatePath = path.join(templateDir, templateRelativePath);
|
|
248
|
+
const result = ensureFileFromTemplate(targetPath, templatePath, { force });
|
|
249
|
+
|
|
250
|
+
if (result === 'created') {
|
|
251
|
+
summary.createdFiles.push(targetRelativePath);
|
|
252
|
+
} else if (result === 'overwritten') {
|
|
253
|
+
summary.overwrittenFiles.push(targetRelativePath);
|
|
254
|
+
} else {
|
|
255
|
+
summary.skippedFiles.push(targetRelativePath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!packageJson.scripts.check) {
|
|
260
|
+
console.warn('Aviso: script "check" não encontrado. O script "release" executa "npm run check".');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`Projeto inicializado em ${packageDir}`);
|
|
264
|
+
console.log(`Arquivos criados: ${summary.createdFiles.length ? summary.createdFiles.join(', ') : 'nenhum'}`);
|
|
265
|
+
console.log(`Arquivos sobrescritos: ${summary.overwrittenFiles.length ? summary.overwrittenFiles.join(', ') : 'nenhum'}`);
|
|
266
|
+
console.log(`Arquivos ignorados: ${summary.skippedFiles.length ? summary.skippedFiles.join(', ') : 'nenhum'}`);
|
|
267
|
+
console.log(`Scripts atualizados: ${summary.updatedScriptKeys.length ? summary.updatedScriptKeys.join(', ') : 'nenhum'}`);
|
|
268
|
+
console.log(`Scripts preservados: ${summary.skippedScriptKeys.length ? summary.skippedScriptKeys.join(', ') : 'nenhum'}`);
|
|
269
|
+
console.log(`Dependências atualizadas: ${summary.updatedDependencyKeys.length ? summary.updatedDependencyKeys.join(', ') : 'nenhum'}`);
|
|
270
|
+
console.log(`Dependências preservadas: ${summary.skippedDependencyKeys.length ? summary.skippedDependencyKeys.join(', ') : 'nenhum'}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createNewPackage(args) {
|
|
274
|
+
if (!validateName(args.name)) {
|
|
275
|
+
throw new Error('Erro: informe um nome válido com --name (ex: hello-package ou @i-santos/swarm).');
|
|
121
276
|
}
|
|
122
277
|
|
|
123
278
|
const packageRoot = path.resolve(__dirname, '..');
|
|
@@ -137,18 +292,38 @@ async function run(argv) {
|
|
|
137
292
|
copyDirRecursive(templateDir, targetDir);
|
|
138
293
|
|
|
139
294
|
renderTemplateFile(path.join(targetDir, 'package.json'), {
|
|
140
|
-
packageName: args.name
|
|
141
|
-
releaseCliPkg: args.releaseCliPkg,
|
|
142
|
-
releaseCliVersion: args.releaseCliVersion
|
|
295
|
+
packageName: args.name
|
|
143
296
|
});
|
|
144
297
|
|
|
145
298
|
renderTemplateFile(path.join(targetDir, 'README.md'), {
|
|
146
|
-
packageName: args.name
|
|
147
|
-
releaseCliPkg: args.releaseCliPkg,
|
|
148
|
-
releaseCliVersion: args.releaseCliVersion
|
|
299
|
+
packageName: args.name
|
|
149
300
|
});
|
|
150
301
|
|
|
151
302
|
console.log(`Pacote criado em ${targetDir}`);
|
|
152
303
|
}
|
|
153
304
|
|
|
305
|
+
function initExistingPackage(args) {
|
|
306
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
307
|
+
const templateDir = path.join(packageRoot, 'template');
|
|
308
|
+
const targetDir = path.resolve(args.dir);
|
|
309
|
+
|
|
310
|
+
configureExistingPackage(targetDir, templateDir, args.force);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function run(argv) {
|
|
314
|
+
const parsed = parseArgs(argv);
|
|
315
|
+
|
|
316
|
+
if (parsed.args.help) {
|
|
317
|
+
console.log(usage());
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (parsed.mode === 'init') {
|
|
322
|
+
initExistingPackage(parsed.args);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
createNewPackage(parsed.args);
|
|
327
|
+
}
|
|
328
|
+
|
|
154
329
|
module.exports = { run };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@i-santos/create-package-starter",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Scaffold new npm packages with standardized release
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Scaffold new npm packages with a standardized Changesets release workflow",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Igor Santos",
|
|
7
7
|
"homepage": "https://github.com/i-santos/package-starter#readme",
|
package/template/README.md
CHANGED
|
@@ -5,19 +5,13 @@ Pacote criado pelo `@i-santos/create-package-starter`.
|
|
|
5
5
|
## Comandos
|
|
6
6
|
|
|
7
7
|
- `npm run check`
|
|
8
|
-
- `npm run registry:start`
|
|
9
|
-
- `npm run release:beta`
|
|
10
|
-
- `npm run release:stable`
|
|
11
|
-
- `npm run release:publish`
|
|
12
8
|
- `npm run changeset`
|
|
13
9
|
- `npm run version-packages`
|
|
14
10
|
- `npm run release`
|
|
15
11
|
|
|
16
|
-
##
|
|
12
|
+
## Fluxo de release
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- Workflow pronto em `.github/workflows/release.yml`.
|
|
23
|
-
- Configuração de changesets em `.changeset/config.json`.
|
|
14
|
+
1. Crie um changeset na PR: `npm run changeset`.
|
|
15
|
+
2. Faça merge na `main`.
|
|
16
|
+
3. O workflow `.github/workflows/release.yml` cria/atualiza a PR de release.
|
|
17
|
+
4. Ao merge da PR de release, o publish é executado no npm.
|
package/template/package.json
CHANGED
|
@@ -4,16 +4,11 @@
|
|
|
4
4
|
"description": "Pacote gerado pelo create-package-starter",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "node scripts/check.js",
|
|
7
|
-
"release:beta": "release-cli beta",
|
|
8
|
-
"release:stable": "release-cli stable",
|
|
9
|
-
"release:publish": "release-cli publish",
|
|
10
|
-
"registry:start": "release-cli registry http://127.0.0.1:4873",
|
|
11
7
|
"changeset": "changeset",
|
|
12
8
|
"version-packages": "changeset version",
|
|
13
|
-
"release": "npm run check &&
|
|
9
|
+
"release": "npm run check && changeset publish"
|
|
14
10
|
},
|
|
15
11
|
"devDependencies": {
|
|
16
|
-
"__RELEASE_CLI_PKG__": "__RELEASE_CLI_VERSION__",
|
|
17
12
|
"@changesets/cli": "^2.29.7"
|
|
18
13
|
}
|
|
19
14
|
}
|
|
@@ -7,10 +7,6 @@ const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
|
7
7
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
8
8
|
|
|
9
9
|
const requiredScripts = [
|
|
10
|
-
'release:beta',
|
|
11
|
-
'release:stable',
|
|
12
|
-
'release:publish',
|
|
13
|
-
'registry:start',
|
|
14
10
|
'changeset',
|
|
15
11
|
'version-packages',
|
|
16
12
|
'release'
|