@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 CHANGED
@@ -1,29 +1,32 @@
1
1
  # @i-santos/create-package-starter
2
2
 
3
- Scaffold new npm packages with a consistent release workflow.
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
- - `--release-cli-pkg <package>` (default: `@i-santos/release-cli`)
17
- - `--release-cli-version <version>` (default: `^0.1.0`)
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>] [--release-cli-pkg <pkg>] [--release-cli-version <versao>]',
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 --name hello-package --out ./packages',
16
- ' create-package-starter --name hello-package --release-cli-pkg @i-santos/release-cli --release-cli-version ^1.0.0'
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 parseArgs(argv) {
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 === '--release-cli-pkg') {
43
- args.releaseCliPkg = argv[i + 1];
44
- i += 1;
38
+ if (token === '--help' || token === '-h') {
39
+ args.help = true;
45
40
  continue;
46
41
  }
47
42
 
48
- if (token === '--release-cli-version') {
49
- args.releaseCliVersion = argv[i + 1];
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
- async function run(argv) {
108
- const args = parseArgs(argv);
133
+ function readJsonFile(filePath) {
134
+ let raw;
109
135
 
110
- if (args.help) {
111
- console.log(usage());
112
- return;
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
- if (!validateName(args.name)) {
116
- throw new Error('Erro: informe um nome válido com --name (ex: hello-package ou @i-santos/swarm).');
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
- if (!args.releaseCliPkg || !args.releaseCliVersion) {
120
- throw new Error('Erro: --release-cli-pkg e --release-cli-version devem ser informados corretamente.');
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": "0.1.2",
4
- "description": "Scaffold new npm packages with standardized release scripts",
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",
@@ -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
- ## Dependência de release
12
+ ## Fluxo de release
17
13
 
18
- Este pacote foi gerado para usar `__RELEASE_CLI_PKG__@__RELEASE_CLI_VERSION__`.
19
-
20
- ## CI/CD release
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.
@@ -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 && npm run release:publish"
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'