@i-santos/create-package-starter 1.2.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.
- package/README.md +22 -1
- package/lib/run.js +174 -4
- 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:
|
|
@@ -79,10 +87,23 @@ All commands print a deterministic summary with:
|
|
|
79
87
|
- delete branch on merge
|
|
80
88
|
- auto-merge enabled
|
|
81
89
|
- squash-only merge policy
|
|
82
|
-
-
|
|
90
|
+
- set Actions workflow default permissions to `write` (with PR review approvals enabled for workflows)
|
|
91
|
+
- create/update branch ruleset with required PR, 0 approvals by default, stale review dismissal, resolved conversations, and deletion/force-push protection
|
|
83
92
|
|
|
84
93
|
If `gh` is missing or unauthenticated, command exits non-zero with actionable guidance.
|
|
85
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
|
+
|
|
86
107
|
## Trusted Publishing Note
|
|
87
108
|
|
|
88
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)
|
|
@@ -581,7 +627,7 @@ function createBaseRulesetPayload(defaultBranch) {
|
|
|
581
627
|
{
|
|
582
628
|
type: 'pull_request',
|
|
583
629
|
parameters: {
|
|
584
|
-
required_approving_review_count:
|
|
630
|
+
required_approving_review_count: 0,
|
|
585
631
|
dismiss_stale_reviews_on_push: true,
|
|
586
632
|
require_code_owner_review: false,
|
|
587
633
|
require_last_push_approval: false,
|
|
@@ -663,6 +709,115 @@ function upsertRuleset(deps, repo, rulesetPayload) {
|
|
|
663
709
|
return 'updated';
|
|
664
710
|
}
|
|
665
711
|
|
|
712
|
+
function updateWorkflowPermissions(deps, repo) {
|
|
713
|
+
const workflowPermissionsPayload = {
|
|
714
|
+
default_workflow_permissions: 'write',
|
|
715
|
+
can_approve_pull_request_reviews: true
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const result = ghApi(
|
|
719
|
+
deps,
|
|
720
|
+
'PUT',
|
|
721
|
+
`/repos/${repo}/actions/permissions/workflow`,
|
|
722
|
+
workflowPermissionsPayload
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
if (result.status !== 0) {
|
|
726
|
+
throw new Error(
|
|
727
|
+
`Failed to update workflow permissions: ${result.stderr || result.stdout}`.trim()
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
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
|
+
|
|
666
821
|
function setupGithub(args, dependencies = {}) {
|
|
667
822
|
const deps = {
|
|
668
823
|
exec: dependencies.exec || execCommand
|
|
@@ -674,10 +829,17 @@ function setupGithub(args, dependencies = {}) {
|
|
|
674
829
|
const rulesetPayload = createRulesetPayload(args);
|
|
675
830
|
const summary = createSummary();
|
|
676
831
|
|
|
677
|
-
summary.updatedScriptKeys.push(
|
|
832
|
+
summary.updatedScriptKeys.push(
|
|
833
|
+
'repository.default_branch',
|
|
834
|
+
'repository.delete_branch_on_merge',
|
|
835
|
+
'repository.allow_auto_merge',
|
|
836
|
+
'repository.merge_policy',
|
|
837
|
+
'actions.default_workflow_permissions'
|
|
838
|
+
);
|
|
678
839
|
|
|
679
840
|
if (args.dryRun) {
|
|
680
841
|
summary.warnings.push(`dry-run: would update repository settings for ${repo}`);
|
|
842
|
+
summary.warnings.push(`dry-run: would set actions workflow permissions to write for ${repo}`);
|
|
681
843
|
summary.warnings.push(`dry-run: would upsert ruleset "${rulesetPayload.name}" for refs/heads/${args.defaultBranch}`);
|
|
682
844
|
printSummary(`GitHub settings dry-run for ${repo}`, summary);
|
|
683
845
|
return;
|
|
@@ -697,6 +859,8 @@ function setupGithub(args, dependencies = {}) {
|
|
|
697
859
|
throw new Error(`Failed to update repository settings: ${patchRepo.stderr || patchRepo.stdout}`.trim());
|
|
698
860
|
}
|
|
699
861
|
|
|
862
|
+
updateWorkflowPermissions(deps, repo);
|
|
863
|
+
|
|
700
864
|
const upsertResult = upsertRuleset(deps, repo, rulesetPayload);
|
|
701
865
|
summary.overwrittenFiles.push(`github-ruleset:${upsertResult}`);
|
|
702
866
|
|
|
@@ -721,6 +885,11 @@ async function run(argv, dependencies = {}) {
|
|
|
721
885
|
return;
|
|
722
886
|
}
|
|
723
887
|
|
|
888
|
+
if (parsed.mode === 'setup-npm') {
|
|
889
|
+
setupNpm(parsed.args, dependencies);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
724
893
|
createNewPackage(parsed.args);
|
|
725
894
|
}
|
|
726
895
|
|
|
@@ -728,5 +897,6 @@ module.exports = {
|
|
|
728
897
|
run,
|
|
729
898
|
parseRepoFromRemote,
|
|
730
899
|
createBaseRulesetPayload,
|
|
731
|
-
setupGithub
|
|
900
|
+
setupGithub,
|
|
901
|
+
setupNpm
|
|
732
902
|
};
|