@i-santos/create-package-starter 1.3.0 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/lib/run.js +143 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,6 +9,7 @@ npx @i-santos/create-package-starter --name hello-package
9
9
  npx @i-santos/create-package-starter --name @i-santos/swarm --default-branch main
10
10
  npx @i-santos/create-package-starter init --dir ./existing-package
11
11
  npx @i-santos/create-package-starter setup-github --repo i-santos/firestack --dry-run
12
+ npx @i-santos/create-package-starter setup-npm --dir ./existing-package --publish-first
12
13
  ```
13
14
 
14
15
  ## Commands
@@ -36,6 +37,13 @@ Configure GitHub repository settings:
36
37
  - `--ruleset <path>` (optional JSON override)
37
38
  - `--dry-run` (prints intended operations only)
38
39
 
40
+ Bootstrap npm publishing:
41
+
42
+ - `setup-npm`
43
+ - `--dir <directory>` (default: current directory)
44
+ - `--publish-first` (run `npm publish --access public` only when package is not found on npm)
45
+ - `--dry-run` (prints intended operations only)
46
+
39
47
  ## Managed Standards
40
48
 
41
49
  The generated and managed baseline includes:
@@ -84,6 +92,18 @@ All commands print a deterministic summary with:
84
92
 
85
93
  If `gh` is missing or unauthenticated, command exits non-zero with actionable guidance.
86
94
 
95
+ ## setup-npm Behavior
96
+
97
+ `setup-npm` validates npm publish readiness:
98
+
99
+ - checks npm CLI availability
100
+ - checks npm authentication (`npm whoami`)
101
+ - checks whether package already exists on npm
102
+ - optionally performs first publish (`--publish-first`)
103
+ - prints next steps for Trusted Publisher configuration
104
+
105
+ Important: Trusted Publisher still needs manual setup in npm package settings.
106
+
87
107
  ## Trusted Publishing Note
88
108
 
89
109
  If package does not exist on npm yet, first publish may be manual:
package/lib/run.js CHANGED
@@ -25,13 +25,15 @@ function usage() {
25
25
  ' create-package-starter --name <name> [--out <directory>] [--default-branch <branch>]',
26
26
  ' create-package-starter init [--dir <directory>] [--force] [--cleanup-legacy-release] [--scope <scope>] [--default-branch <branch>]',
27
27
  ' create-package-starter setup-github [--repo <owner/repo>] [--default-branch <branch>] [--ruleset <path>] [--dry-run]',
28
+ ' create-package-starter setup-npm [--dir <directory>] [--publish-first] [--dry-run]',
28
29
  '',
29
30
  'Examples:',
30
31
  ' create-package-starter --name hello-package',
31
32
  ' create-package-starter --name @i-santos/swarm --out ./packages',
32
33
  ' create-package-starter init --dir ./my-package',
33
34
  ' create-package-starter init --cleanup-legacy-release',
34
- ' create-package-starter setup-github --repo i-santos/firestack --dry-run'
35
+ ' create-package-starter setup-github --repo i-santos/firestack --dry-run',
36
+ ' create-package-starter setup-npm --dir . --publish-first'
35
37
  ].join('\n');
36
38
  }
37
39
 
@@ -176,6 +178,43 @@ function parseSetupGithubArgs(argv) {
176
178
  return args;
177
179
  }
178
180
 
181
+ function parseSetupNpmArgs(argv) {
182
+ const args = {
183
+ dir: process.cwd(),
184
+ publishFirst: false,
185
+ dryRun: false
186
+ };
187
+
188
+ for (let i = 0; i < argv.length; i += 1) {
189
+ const token = argv[i];
190
+
191
+ if (token === '--dir') {
192
+ args.dir = parseValueFlag(argv, i, '--dir');
193
+ i += 1;
194
+ continue;
195
+ }
196
+
197
+ if (token === '--publish-first') {
198
+ args.publishFirst = true;
199
+ continue;
200
+ }
201
+
202
+ if (token === '--dry-run') {
203
+ args.dryRun = true;
204
+ continue;
205
+ }
206
+
207
+ if (token === '--help' || token === '-h') {
208
+ args.help = true;
209
+ continue;
210
+ }
211
+
212
+ throw new Error(`Invalid argument: ${token}\n\n${usage()}`);
213
+ }
214
+
215
+ return args;
216
+ }
217
+
179
218
  function parseArgs(argv) {
180
219
  if (argv[0] === 'init') {
181
220
  return {
@@ -191,6 +230,13 @@ function parseArgs(argv) {
191
230
  };
192
231
  }
193
232
 
233
+ if (argv[0] === 'setup-npm') {
234
+ return {
235
+ mode: 'setup-npm',
236
+ args: parseSetupNpmArgs(argv.slice(1))
237
+ };
238
+ }
239
+
194
240
  return {
195
241
  mode: 'create',
196
242
  args: parseCreateArgs(argv)
@@ -683,6 +729,95 @@ function updateWorkflowPermissions(deps, repo) {
683
729
  }
684
730
  }
685
731
 
732
+ function ensureNpmAvailable(deps) {
733
+ const version = deps.exec('npm', ['--version']);
734
+ if (version.status !== 0) {
735
+ throw new Error('npm CLI is required. Install npm and rerun.');
736
+ }
737
+ }
738
+
739
+ function ensureNpmAuthenticated(deps) {
740
+ const whoami = deps.exec('npm', ['whoami']);
741
+ if (whoami.status !== 0) {
742
+ throw new Error('npm CLI is not authenticated. Run "npm login" and rerun.');
743
+ }
744
+ }
745
+
746
+ function packageExistsOnNpm(deps, packageName) {
747
+ const view = deps.exec('npm', ['view', packageName, 'version', '--json']);
748
+ if (view.status === 0) {
749
+ return true;
750
+ }
751
+
752
+ const output = `${view.stderr || ''}\n${view.stdout || ''}`.toLowerCase();
753
+ if (output.includes('e404') || output.includes('not found') || output.includes('404')) {
754
+ return false;
755
+ }
756
+
757
+ throw new Error(`Failed to check package on npm: ${view.stderr || view.stdout}`.trim());
758
+ }
759
+
760
+ function setupNpm(args, dependencies = {}) {
761
+ const deps = {
762
+ exec: dependencies.exec || execCommand
763
+ };
764
+
765
+ const targetDir = path.resolve(args.dir);
766
+ if (!fs.existsSync(targetDir)) {
767
+ throw new Error(`Directory not found: ${targetDir}`);
768
+ }
769
+
770
+ const packageJsonPath = path.join(targetDir, 'package.json');
771
+ if (!fs.existsSync(packageJsonPath)) {
772
+ throw new Error(`package.json not found in ${targetDir}`);
773
+ }
774
+
775
+ const packageJson = readJsonFile(packageJsonPath);
776
+ if (!packageJson.name) {
777
+ throw new Error(`package.json in ${targetDir} must define "name".`);
778
+ }
779
+
780
+ ensureNpmAvailable(deps);
781
+ ensureNpmAuthenticated(deps);
782
+
783
+ const summary = createSummary();
784
+ summary.updatedScriptKeys.push('npm.auth', 'npm.package.lookup');
785
+
786
+ if (!packageJson.publishConfig || packageJson.publishConfig.access !== 'public') {
787
+ summary.warnings.push('package.json publishConfig.access is not "public". First publish may fail for public packages.');
788
+ }
789
+
790
+ const existsOnNpm = packageExistsOnNpm(deps, packageJson.name);
791
+ if (existsOnNpm) {
792
+ summary.skippedScriptKeys.push('npm.first_publish');
793
+ } else {
794
+ summary.updatedScriptKeys.push('npm.first_publish_required');
795
+ }
796
+
797
+ if (!existsOnNpm && !args.publishFirst) {
798
+ summary.warnings.push(`package "${packageJson.name}" was not found on npm. Run "create-package-starter setup-npm --dir ${targetDir} --publish-first" to perform first publish.`);
799
+ }
800
+
801
+ if (args.publishFirst) {
802
+ if (existsOnNpm) {
803
+ summary.warnings.push(`package "${packageJson.name}" already exists on npm. Skipping first publish.`);
804
+ } else if (args.dryRun) {
805
+ summary.warnings.push(`dry-run: would run "npm publish --access public" in ${targetDir}`);
806
+ } else {
807
+ const publish = deps.exec('npm', ['publish', '--access', 'public'], { cwd: targetDir });
808
+ if (publish.status !== 0) {
809
+ throw new Error(`First publish failed: ${(publish.stderr || publish.stdout || '').trim()}`);
810
+ }
811
+ summary.updatedScriptKeys.push('npm.first_publish_done');
812
+ }
813
+ }
814
+
815
+ summary.warnings.push('Configure npm Trusted Publisher manually in npm package settings after first publish.');
816
+ summary.warnings.push('Trusted Publisher requires owner, repository, workflow file (.github/workflows/release.yml), and branch (main by default).');
817
+
818
+ printSummary(`npm setup completed for ${packageJson.name}`, summary);
819
+ }
820
+
686
821
  function setupGithub(args, dependencies = {}) {
687
822
  const deps = {
688
823
  exec: dependencies.exec || execCommand
@@ -750,6 +885,11 @@ async function run(argv, dependencies = {}) {
750
885
  return;
751
886
  }
752
887
 
888
+ if (parsed.mode === 'setup-npm') {
889
+ setupNpm(parsed.args, dependencies);
890
+ return;
891
+ }
892
+
753
893
  createNewPackage(parsed.args);
754
894
  }
755
895
 
@@ -757,5 +897,6 @@ module.exports = {
757
897
  run,
758
898
  parseRepoFromRemote,
759
899
  createBaseRulesetPayload,
760
- setupGithub
900
+ setupGithub,
901
+ setupNpm
761
902
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i-santos/create-package-starter",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Scaffold new npm packages with a standardized Changesets release workflow",
5
5
  "license": "MIT",
6
6
  "author": "Igor Santos",