@i-santos/create-package-starter 1.5.0-beta.2 → 1.5.0-beta.4
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 +17 -0
- package/lib/run.js +630 -36
- package/package.json +1 -1
- package/template/.github/workflows/ci.yml +14 -0
- package/template/.github/workflows/release.yml +4 -0
- package/template/gitignore +2 -0
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Scaffold and standardize npm packages with a Changesets-first release workflow.
|
|
|
8
8
|
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
|
+
npx @i-santos/create-package-starter init --dir . --with-github --with-beta --with-npm --yes
|
|
11
12
|
npx @i-santos/create-package-starter setup-github --repo i-santos/firestack --dry-run
|
|
12
13
|
npx @i-santos/create-package-starter setup-beta --dir . --beta-branch release/beta
|
|
13
14
|
npx @i-santos/create-package-starter promote-stable --dir . --type patch --summary "Promote beta to stable"
|
|
@@ -30,6 +31,14 @@ Bootstrap existing package:
|
|
|
30
31
|
- `--cleanup-legacy-release` (remove `release:beta*`, `release:stable*`, `release:promote*`, `release:rollback*`, `release:dist-tags`)
|
|
31
32
|
- `--scope <scope>` (optional placeholder helper for docs/templates)
|
|
32
33
|
- `--default-branch <branch>` (default: `main`)
|
|
34
|
+
- `--beta-branch <branch>` (default: `release/beta`)
|
|
35
|
+
- `--with-github` (run GitHub setup in same flow)
|
|
36
|
+
- `--with-npm` (run npm setup in same flow)
|
|
37
|
+
- `--with-beta` (run beta flow setup; implies `--with-github`)
|
|
38
|
+
- `--repo <owner/repo>` (optional; inferred from `remote.origin.url` when omitted)
|
|
39
|
+
- `--ruleset <path>` (optional JSON override for main ruleset payload)
|
|
40
|
+
- `--dry-run` (preview planned operations without mutating)
|
|
41
|
+
- `--yes` (skip confirmation prompts)
|
|
33
42
|
|
|
34
43
|
Configure GitHub repository settings:
|
|
35
44
|
|
|
@@ -88,6 +97,11 @@ The generated and managed baseline includes:
|
|
|
88
97
|
- Existing custom `check` script is preserved unless `--force`.
|
|
89
98
|
- Existing `@changesets/cli` version is preserved unless `--force`.
|
|
90
99
|
- Lowercase `.github/pull_request_template.md` is recognized as an existing equivalent template.
|
|
100
|
+
- If no `--with-*` flags are provided:
|
|
101
|
+
- TTY: asks interactively which external setup to run (`github`, `npm`, `beta`).
|
|
102
|
+
- non-TTY: runs local init only and prints warning with next steps.
|
|
103
|
+
- Integrated mode (`--with-github/--with-npm/--with-beta`) pre-validates everything first (gh auth, npm auth, repo/branch/ruleset/package checks) and fails fast before local mutations if validation fails.
|
|
104
|
+
- Integrated mode asks confirmation for sensitive external operations and ruleset/branch adoption conflicts (unless `--yes`).
|
|
91
105
|
|
|
92
106
|
## Output Summary Contract
|
|
93
107
|
|
|
@@ -122,6 +136,7 @@ If `gh` is missing or unauthenticated, command exits non-zero with actionable gu
|
|
|
122
136
|
- creates/preserves `.github/workflows/ci.yml` with beta+stable branch triggers
|
|
123
137
|
- ensures `release/beta` branch exists remotely (created from default branch if missing)
|
|
124
138
|
- applies beta branch protection ruleset on GitHub (including required CI matrix checks for Node 18 and 20)
|
|
139
|
+
- applies beta branch protection ruleset on GitHub with stable required check context (`required-check`)
|
|
125
140
|
- asks for confirmation before mutating repository settings and again before overwriting existing beta ruleset
|
|
126
141
|
- supports safe-merge by default and `--force` overwrite
|
|
127
142
|
- supports configurable beta branch (`release/beta` by default)
|
|
@@ -147,6 +162,8 @@ If `gh` is missing or unauthenticated, command exits non-zero with actionable gu
|
|
|
147
162
|
|
|
148
163
|
Important: Trusted Publisher still needs manual setup in npm package settings.
|
|
149
164
|
|
|
165
|
+
When npm setup runs inside orchestrated `init --with-npm`, first publish is automatic when package is not found on npm.
|
|
166
|
+
|
|
150
167
|
## Trusted Publishing Note
|
|
151
168
|
|
|
152
169
|
If package does not exist on npm yet, first publish may be manual:
|
package/lib/run.js
CHANGED
|
@@ -6,7 +6,9 @@ const readline = require('readline/promises');
|
|
|
6
6
|
const CHANGESETS_DEP = '@changesets/cli';
|
|
7
7
|
const CHANGESETS_DEP_VERSION = '^2.29.7';
|
|
8
8
|
const DEFAULT_BASE_BRANCH = 'main';
|
|
9
|
+
const DEFAULT_BETA_BRANCH = 'release/beta';
|
|
9
10
|
const DEFAULT_RULESET_NAME = 'Default main branch protection';
|
|
11
|
+
const REQUIRED_CHECK_CONTEXT = 'required-check';
|
|
10
12
|
|
|
11
13
|
const MANAGED_FILE_SPECS = [
|
|
12
14
|
['.changeset/config.json', '.changeset/config.json'],
|
|
@@ -17,14 +19,14 @@ const MANAGED_FILE_SPECS = [
|
|
|
17
19
|
['.github/CODEOWNERS', '.github/CODEOWNERS'],
|
|
18
20
|
['CONTRIBUTING.md', 'CONTRIBUTING.md'],
|
|
19
21
|
['README.md', 'README.md'],
|
|
20
|
-
['.gitignore', '
|
|
22
|
+
['.gitignore', 'gitignore']
|
|
21
23
|
];
|
|
22
24
|
|
|
23
25
|
function usage() {
|
|
24
26
|
return [
|
|
25
27
|
'Usage:',
|
|
26
28
|
' create-package-starter --name <name> [--out <directory>] [--default-branch <branch>]',
|
|
27
|
-
' create-package-starter init [--dir <directory>] [--force] [--cleanup-legacy-release] [--scope <scope>] [--default-branch <branch>]',
|
|
29
|
+
' create-package-starter init [--dir <directory>] [--force] [--cleanup-legacy-release] [--scope <scope>] [--default-branch <branch>] [--with-github] [--with-npm] [--with-beta] [--repo <owner/repo>] [--beta-branch <branch>] [--ruleset <path>] [--dry-run] [--yes]',
|
|
28
30
|
' create-package-starter setup-github [--repo <owner/repo>] [--default-branch <branch>] [--ruleset <path>] [--dry-run]',
|
|
29
31
|
' create-package-starter setup-beta [--dir <directory>] [--repo <owner/repo>] [--beta-branch <branch>] [--default-branch <branch>] [--force] [--dry-run] [--yes]',
|
|
30
32
|
' create-package-starter promote-stable [--dir <directory>] [--type patch|minor|major] [--summary <text>] [--dry-run]',
|
|
@@ -36,6 +38,7 @@ function usage() {
|
|
|
36
38
|
' create-package-starter init --dir ./my-package',
|
|
37
39
|
' create-package-starter init --cleanup-legacy-release',
|
|
38
40
|
' create-package-starter setup-github --repo i-santos/firestack --dry-run',
|
|
41
|
+
' create-package-starter init --dir . --with-github --with-beta --with-npm --yes',
|
|
39
42
|
' create-package-starter setup-beta --dir . --beta-branch release/beta',
|
|
40
43
|
' create-package-starter promote-stable --dir . --type patch --summary "Promote beta to stable"',
|
|
41
44
|
' create-package-starter setup-npm --dir . --publish-first'
|
|
@@ -95,7 +98,15 @@ function parseInitArgs(argv) {
|
|
|
95
98
|
force: false,
|
|
96
99
|
cleanupLegacyRelease: false,
|
|
97
100
|
defaultBranch: DEFAULT_BASE_BRANCH,
|
|
98
|
-
|
|
101
|
+
betaBranch: DEFAULT_BETA_BRANCH,
|
|
102
|
+
scope: '',
|
|
103
|
+
repo: '',
|
|
104
|
+
ruleset: '',
|
|
105
|
+
withGithub: false,
|
|
106
|
+
withNpm: false,
|
|
107
|
+
withBeta: false,
|
|
108
|
+
dryRun: false,
|
|
109
|
+
yes: false
|
|
99
110
|
};
|
|
100
111
|
|
|
101
112
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -119,6 +130,49 @@ function parseInitArgs(argv) {
|
|
|
119
130
|
continue;
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
if (token === '--beta-branch') {
|
|
134
|
+
args.betaBranch = parseValueFlag(argv, i, '--beta-branch');
|
|
135
|
+
i += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (token === '--repo') {
|
|
140
|
+
args.repo = parseValueFlag(argv, i, '--repo');
|
|
141
|
+
i += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (token === '--ruleset') {
|
|
146
|
+
args.ruleset = parseValueFlag(argv, i, '--ruleset');
|
|
147
|
+
i += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (token === '--with-github') {
|
|
152
|
+
args.withGithub = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (token === '--with-npm') {
|
|
157
|
+
args.withNpm = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (token === '--with-beta') {
|
|
162
|
+
args.withBeta = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (token === '--dry-run') {
|
|
167
|
+
args.dryRun = true;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (token === '--yes') {
|
|
172
|
+
args.yes = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
122
176
|
if (token === '--force') {
|
|
123
177
|
args.force = true;
|
|
124
178
|
continue;
|
|
@@ -223,7 +277,7 @@ function parseSetupNpmArgs(argv) {
|
|
|
223
277
|
function parseSetupBetaArgs(argv) {
|
|
224
278
|
const args = {
|
|
225
279
|
dir: process.cwd(),
|
|
226
|
-
betaBranch:
|
|
280
|
+
betaBranch: DEFAULT_BETA_BRANCH,
|
|
227
281
|
defaultBranch: DEFAULT_BASE_BRANCH,
|
|
228
282
|
force: false,
|
|
229
283
|
yes: false,
|
|
@@ -419,8 +473,11 @@ function copyDirRecursive(sourceDir, targetDir, variables, relativeBase = '') {
|
|
|
419
473
|
|
|
420
474
|
for (const entry of entries) {
|
|
421
475
|
const srcPath = path.join(sourceDir, entry.name);
|
|
422
|
-
const
|
|
423
|
-
|
|
476
|
+
const destinationEntryName = relativeBase === '' && entry.name === 'gitignore'
|
|
477
|
+
? '.gitignore'
|
|
478
|
+
: entry.name;
|
|
479
|
+
const destPath = path.join(targetDir, destinationEntryName);
|
|
480
|
+
const relativePath = path.posix.join(relativeBase, destinationEntryName);
|
|
424
481
|
|
|
425
482
|
if (entry.isDirectory()) {
|
|
426
483
|
createdFiles.push(...copyDirRecursive(srcPath, destPath, variables, relativePath));
|
|
@@ -485,6 +542,74 @@ function printSummary(title, summary) {
|
|
|
485
542
|
console.log(`warnings: ${list(summary.warnings)}`);
|
|
486
543
|
}
|
|
487
544
|
|
|
545
|
+
class StepReporter {
|
|
546
|
+
constructor() {
|
|
547
|
+
this.active = null;
|
|
548
|
+
this.frames = ['-', '\\', '|', '/'];
|
|
549
|
+
this.frameIndex = 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
canSpin() {
|
|
553
|
+
return Boolean(process.stdout.isTTY) && process.env.CI !== 'true';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
start(stepId, message) {
|
|
557
|
+
this.stop();
|
|
558
|
+
if (!this.canSpin()) {
|
|
559
|
+
logStep('run', message);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.active = {
|
|
564
|
+
id: stepId,
|
|
565
|
+
message,
|
|
566
|
+
timer: setInterval(() => {
|
|
567
|
+
const frame = this.frames[this.frameIndex % this.frames.length];
|
|
568
|
+
this.frameIndex += 1;
|
|
569
|
+
process.stdout.write(`\r${frame} ${message}`);
|
|
570
|
+
}, 80)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
end(status, message) {
|
|
575
|
+
if (this.active && this.active.timer) {
|
|
576
|
+
clearInterval(this.active.timer);
|
|
577
|
+
const finalLabel = status === 'ok'
|
|
578
|
+
? 'OK'
|
|
579
|
+
: status === 'warn'
|
|
580
|
+
? 'WARN'
|
|
581
|
+
: 'ERR';
|
|
582
|
+
process.stdout.write(`\r[${finalLabel}] ${message}\n`);
|
|
583
|
+
this.active = null;
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
logStep(status, message);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
ok(stepId, message) {
|
|
591
|
+
this.end('ok', message);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
warn(stepId, message) {
|
|
595
|
+
this.end('warn', message);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
fail(stepId, message) {
|
|
599
|
+
this.end('err', message);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
stop() {
|
|
603
|
+
if (!this.active || !this.active.timer) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
clearInterval(this.active.timer);
|
|
608
|
+
process.stdout.write('\r');
|
|
609
|
+
this.active = null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
488
613
|
function logStep(status, message) {
|
|
489
614
|
const labels = {
|
|
490
615
|
run: '[RUN ]',
|
|
@@ -517,6 +642,43 @@ async function confirmOrThrow(questionText) {
|
|
|
517
642
|
}
|
|
518
643
|
}
|
|
519
644
|
|
|
645
|
+
async function askYesNo(questionText, defaultValue = false) {
|
|
646
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
647
|
+
return defaultValue;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const rl = readline.createInterface({
|
|
651
|
+
input: process.stdin,
|
|
652
|
+
output: process.stdout
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
const suffix = defaultValue ? '[Y/n]' : '[y/N]';
|
|
657
|
+
const answer = await rl.question(`${questionText} ${suffix} `);
|
|
658
|
+
const normalized = answer.trim().toLowerCase();
|
|
659
|
+
|
|
660
|
+
if (!normalized) {
|
|
661
|
+
return defaultValue;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return normalized === 'y' || normalized === 'yes';
|
|
665
|
+
} finally {
|
|
666
|
+
rl.close();
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function mergeSummary(target, source) {
|
|
671
|
+
target.createdFiles.push(...source.createdFiles);
|
|
672
|
+
target.overwrittenFiles.push(...source.overwrittenFiles);
|
|
673
|
+
target.skippedFiles.push(...source.skippedFiles);
|
|
674
|
+
target.updatedScriptKeys.push(...source.updatedScriptKeys);
|
|
675
|
+
target.skippedScriptKeys.push(...source.skippedScriptKeys);
|
|
676
|
+
target.removedScriptKeys.push(...source.removedScriptKeys);
|
|
677
|
+
target.updatedDependencyKeys.push(...source.updatedDependencyKeys);
|
|
678
|
+
target.skippedDependencyKeys.push(...source.skippedDependencyKeys);
|
|
679
|
+
target.warnings.push(...source.warnings);
|
|
680
|
+
}
|
|
681
|
+
|
|
520
682
|
function ensureFileFromTemplate(targetPath, templatePath, options) {
|
|
521
683
|
const exists = fs.existsSync(targetPath);
|
|
522
684
|
|
|
@@ -524,6 +686,10 @@ function ensureFileFromTemplate(targetPath, templatePath, options) {
|
|
|
524
686
|
return 'skipped';
|
|
525
687
|
}
|
|
526
688
|
|
|
689
|
+
if (options.dryRun) {
|
|
690
|
+
return exists ? 'overwritten' : 'created';
|
|
691
|
+
}
|
|
692
|
+
|
|
527
693
|
const source = fs.readFileSync(templatePath, 'utf8');
|
|
528
694
|
const rendered = renderTemplateString(source, options.variables);
|
|
529
695
|
|
|
@@ -627,6 +793,7 @@ function upsertReleaseWorkflow(targetPath, templatePath, options) {
|
|
|
627
793
|
|
|
628
794
|
const result = ensureFileFromTemplate(targetPath, templatePath, {
|
|
629
795
|
force: options.force,
|
|
796
|
+
dryRun: options.dryRun,
|
|
630
797
|
variables: options.variables
|
|
631
798
|
});
|
|
632
799
|
return { result };
|
|
@@ -804,15 +971,16 @@ function configureExistingPackage(packageDir, templateDir, options) {
|
|
|
804
971
|
|
|
805
972
|
updateManagedFiles(packageDir, templateDir, {
|
|
806
973
|
force: options.force,
|
|
974
|
+
dryRun: options.dryRun,
|
|
807
975
|
variables: {
|
|
808
976
|
PACKAGE_NAME: packageName,
|
|
809
977
|
DEFAULT_BRANCH: options.defaultBranch,
|
|
810
|
-
BETA_BRANCH: options.betaBranch ||
|
|
978
|
+
BETA_BRANCH: options.betaBranch || DEFAULT_BETA_BRANCH,
|
|
811
979
|
SCOPE: deriveScope(options.scope, packageName)
|
|
812
980
|
}
|
|
813
981
|
}, summary);
|
|
814
982
|
|
|
815
|
-
if (packageJsonChanged) {
|
|
983
|
+
if (packageJsonChanged && !options.dryRun) {
|
|
816
984
|
writeJsonFile(packageJsonPath, packageJson);
|
|
817
985
|
}
|
|
818
986
|
|
|
@@ -843,7 +1011,7 @@ function createNewPackage(args) {
|
|
|
843
1011
|
const createdFiles = copyDirRecursive(templateDir, targetDir, {
|
|
844
1012
|
PACKAGE_NAME: args.name,
|
|
845
1013
|
DEFAULT_BRANCH: args.defaultBranch,
|
|
846
|
-
BETA_BRANCH:
|
|
1014
|
+
BETA_BRANCH: DEFAULT_BETA_BRANCH,
|
|
847
1015
|
SCOPE: deriveScope('', args.name)
|
|
848
1016
|
});
|
|
849
1017
|
|
|
@@ -856,13 +1024,100 @@ function createNewPackage(args) {
|
|
|
856
1024
|
printSummary(`Package created in ${targetDir}`, summary);
|
|
857
1025
|
}
|
|
858
1026
|
|
|
859
|
-
function initExistingPackage(args) {
|
|
1027
|
+
async function initExistingPackage(args, dependencies = {}) {
|
|
1028
|
+
const reporter = new StepReporter();
|
|
1029
|
+
const selections = await resolveInitSelections(args);
|
|
860
1030
|
const packageRoot = path.resolve(__dirname, '..');
|
|
861
1031
|
const templateDir = path.join(packageRoot, 'template');
|
|
862
1032
|
const targetDir = path.resolve(args.dir);
|
|
1033
|
+
const overallSummary = createSummary();
|
|
1034
|
+
const deps = {
|
|
1035
|
+
exec: dependencies.exec || execCommand
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
if (!selections.withGithub && !selections.withNpm && !selections.withBeta && !process.stdin.isTTY) {
|
|
1039
|
+
overallSummary.warnings.push('No --with-* flags were provided in non-interactive mode. Only local init was applied.');
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const context = prevalidateInitExecution(args, selections, dependencies, reporter);
|
|
1043
|
+
await confirmInitPlan(args, selections, context, overallSummary);
|
|
863
1044
|
|
|
864
|
-
|
|
865
|
-
|
|
1045
|
+
reporter.start('local-init', 'Applying local package bootstrap...');
|
|
1046
|
+
const localSummary = configureExistingPackage(targetDir, templateDir, {
|
|
1047
|
+
...args,
|
|
1048
|
+
dryRun: args.dryRun,
|
|
1049
|
+
betaBranch: args.betaBranch
|
|
1050
|
+
});
|
|
1051
|
+
mergeSummary(overallSummary, localSummary);
|
|
1052
|
+
reporter.ok('local-init', args.dryRun ? 'Local package bootstrap previewed.' : 'Local package bootstrap applied.');
|
|
1053
|
+
|
|
1054
|
+
if (selections.withGithub && selections.withBeta) {
|
|
1055
|
+
ensureBetaWorkflowTriggers(
|
|
1056
|
+
targetDir,
|
|
1057
|
+
templateDir,
|
|
1058
|
+
{
|
|
1059
|
+
force: args.force,
|
|
1060
|
+
dryRun: args.dryRun,
|
|
1061
|
+
defaultBranch: args.defaultBranch,
|
|
1062
|
+
betaBranch: args.betaBranch,
|
|
1063
|
+
packageName: context.packageName,
|
|
1064
|
+
scope: deriveScope(args.scope, context.packageName)
|
|
1065
|
+
},
|
|
1066
|
+
overallSummary,
|
|
1067
|
+
reporter
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
let repo = context.repo;
|
|
1072
|
+
if (selections.withGithub) {
|
|
1073
|
+
const githubSummary = createSummary();
|
|
1074
|
+
const mainResult = applyGithubMainSetup(
|
|
1075
|
+
{
|
|
1076
|
+
repo: context.repo,
|
|
1077
|
+
defaultBranch: args.defaultBranch,
|
|
1078
|
+
ruleset: args.ruleset,
|
|
1079
|
+
dryRun: args.dryRun
|
|
1080
|
+
},
|
|
1081
|
+
{ exec: deps.exec },
|
|
1082
|
+
githubSummary,
|
|
1083
|
+
reporter
|
|
1084
|
+
);
|
|
1085
|
+
repo = mainResult.repo;
|
|
1086
|
+
|
|
1087
|
+
if (selections.withBeta) {
|
|
1088
|
+
applyGithubBetaSetup(
|
|
1089
|
+
{
|
|
1090
|
+
betaBranch: args.betaBranch,
|
|
1091
|
+
defaultBranch: args.defaultBranch,
|
|
1092
|
+
dryRun: args.dryRun
|
|
1093
|
+
},
|
|
1094
|
+
{ exec: deps.exec },
|
|
1095
|
+
githubSummary,
|
|
1096
|
+
reporter,
|
|
1097
|
+
repo
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
mergeSummary(overallSummary, githubSummary);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (selections.withNpm) {
|
|
1105
|
+
const npmSummary = runNpmSetup(
|
|
1106
|
+
{
|
|
1107
|
+
dir: targetDir,
|
|
1108
|
+
dryRun: args.dryRun,
|
|
1109
|
+
publishFirst: false
|
|
1110
|
+
},
|
|
1111
|
+
{ exec: deps.exec },
|
|
1112
|
+
{
|
|
1113
|
+
reporter,
|
|
1114
|
+
publishMissingByDefault: true
|
|
1115
|
+
}
|
|
1116
|
+
);
|
|
1117
|
+
mergeSummary(overallSummary, npmSummary);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
printSummary(`Project initialized in ${targetDir}`, overallSummary);
|
|
866
1121
|
}
|
|
867
1122
|
|
|
868
1123
|
function execCommand(command, args, options = {}) {
|
|
@@ -925,6 +1180,17 @@ function createBaseRulesetPayload(defaultBranch) {
|
|
|
925
1180
|
require_last_push_approval: false,
|
|
926
1181
|
required_review_thread_resolution: true
|
|
927
1182
|
}
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
type: 'required_status_checks',
|
|
1186
|
+
parameters: {
|
|
1187
|
+
strict_required_status_checks_policy: true,
|
|
1188
|
+
required_status_checks: [
|
|
1189
|
+
{
|
|
1190
|
+
context: REQUIRED_CHECK_CONTEXT
|
|
1191
|
+
}
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
928
1194
|
}
|
|
929
1195
|
]
|
|
930
1196
|
};
|
|
@@ -961,10 +1227,7 @@ function createBetaRulesetPayload(betaBranch) {
|
|
|
961
1227
|
strict_required_status_checks_policy: true,
|
|
962
1228
|
required_status_checks: [
|
|
963
1229
|
{
|
|
964
|
-
context:
|
|
965
|
-
},
|
|
966
|
-
{
|
|
967
|
-
context: 'CI / check (20) (pull_request)'
|
|
1230
|
+
context: REQUIRED_CHECK_CONTEXT
|
|
968
1231
|
}
|
|
969
1232
|
]
|
|
970
1233
|
}
|
|
@@ -1127,6 +1390,15 @@ function findRulesetByName(deps, repo, name) {
|
|
|
1127
1390
|
return rulesets.find((ruleset) => ruleset.name === name) || null;
|
|
1128
1391
|
}
|
|
1129
1392
|
|
|
1393
|
+
function listRulesets(deps, repo) {
|
|
1394
|
+
const listResult = ghApi(deps, 'GET', `/repos/${repo}/rulesets`);
|
|
1395
|
+
if (listResult.status !== 0) {
|
|
1396
|
+
throw new Error(`Failed to list rulesets: ${listResult.stderr || listResult.stdout}`.trim());
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return parseJsonOutput(listResult.stdout || '[]', 'Failed to parse rulesets response from GitHub API.');
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1130
1402
|
function ensureNpmAvailable(deps) {
|
|
1131
1403
|
const version = deps.exec('npm', ['--version']);
|
|
1132
1404
|
if (version.status !== 0) {
|
|
@@ -1155,55 +1427,309 @@ function packageExistsOnNpm(deps, packageName) {
|
|
|
1155
1427
|
throw new Error(`Failed to check package on npm: ${view.stderr || view.stdout}`.trim());
|
|
1156
1428
|
}
|
|
1157
1429
|
|
|
1158
|
-
function
|
|
1430
|
+
async function resolveInitSelections(args) {
|
|
1431
|
+
const explicit = args.withGithub || args.withNpm || args.withBeta;
|
|
1432
|
+
const selected = {
|
|
1433
|
+
withGithub: args.withGithub,
|
|
1434
|
+
withNpm: args.withNpm,
|
|
1435
|
+
withBeta: args.withBeta
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
if (!explicit) {
|
|
1439
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1440
|
+
selected.withGithub = await askYesNo('Enable GitHub repository setup (rulesets/settings)?', false);
|
|
1441
|
+
selected.withNpm = await askYesNo('Enable npm setup (auth + package check + first publish if needed)?', false);
|
|
1442
|
+
selected.withBeta = selected.withGithub
|
|
1443
|
+
? await askYesNo(`Enable beta flow setup using branch "${args.betaBranch}"?`, true)
|
|
1444
|
+
: false;
|
|
1445
|
+
} else {
|
|
1446
|
+
selected.withGithub = false;
|
|
1447
|
+
selected.withNpm = false;
|
|
1448
|
+
selected.withBeta = false;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (selected.withBeta) {
|
|
1453
|
+
selected.withGithub = true;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
return selected;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function summarizePlannedInitActions(selections, args, context) {
|
|
1460
|
+
const lines = [
|
|
1461
|
+
'This init execution will apply:',
|
|
1462
|
+
'- local managed files/scripts/dependencies bootstrap'
|
|
1463
|
+
];
|
|
1464
|
+
|
|
1465
|
+
if (selections.withGithub) {
|
|
1466
|
+
lines.push(`- GitHub main settings/ruleset for ${context.repo}`);
|
|
1467
|
+
}
|
|
1468
|
+
if (selections.withBeta) {
|
|
1469
|
+
lines.push(`- beta branch flow for ${args.betaBranch} (create branch if missing + ruleset + workflow triggers)`);
|
|
1470
|
+
}
|
|
1471
|
+
if (selections.withNpm) {
|
|
1472
|
+
if (context.existsOnNpm) {
|
|
1473
|
+
lines.push(`- npm setup for ${context.packageName} (already published; no first publish)`);
|
|
1474
|
+
} else {
|
|
1475
|
+
lines.push(`- npm setup for ${context.packageName} (first publish will run automatically)`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
return lines.join('\n');
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function upsertCiWorkflow(targetPath, templatePath, options) {
|
|
1483
|
+
return upsertReleaseWorkflow(targetPath, templatePath, options);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function ensureBetaWorkflowTriggers(targetDir, templateDir, options, summary, reporter) {
|
|
1487
|
+
const workflowRelativePath = '.github/workflows/release.yml';
|
|
1488
|
+
const workflowTemplatePath = path.join(templateDir, workflowRelativePath);
|
|
1489
|
+
const workflowTargetPath = path.join(targetDir, workflowRelativePath);
|
|
1490
|
+
|
|
1491
|
+
const ciWorkflowRelativePath = '.github/workflows/ci.yml';
|
|
1492
|
+
const ciWorkflowTemplatePath = path.join(templateDir, ciWorkflowRelativePath);
|
|
1493
|
+
const ciWorkflowTargetPath = path.join(targetDir, ciWorkflowRelativePath);
|
|
1494
|
+
|
|
1495
|
+
const variables = {
|
|
1496
|
+
PACKAGE_NAME: options.packageName,
|
|
1497
|
+
DEFAULT_BRANCH: options.defaultBranch,
|
|
1498
|
+
BETA_BRANCH: options.betaBranch,
|
|
1499
|
+
SCOPE: options.scope
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
reporter.start('workflow-release', `Ensuring ${workflowRelativePath} includes stable+beta triggers...`);
|
|
1503
|
+
const workflowUpsert = upsertReleaseWorkflow(workflowTargetPath, workflowTemplatePath, {
|
|
1504
|
+
force: options.force,
|
|
1505
|
+
dryRun: options.dryRun,
|
|
1506
|
+
variables
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
if (workflowUpsert.result === 'created') {
|
|
1510
|
+
summary.createdFiles.push(workflowRelativePath);
|
|
1511
|
+
reporter.ok('workflow-release', `${workflowRelativePath} created.`);
|
|
1512
|
+
} else if (workflowUpsert.result === 'overwritten' || workflowUpsert.result === 'updated') {
|
|
1513
|
+
summary.overwrittenFiles.push(workflowRelativePath);
|
|
1514
|
+
reporter.ok('workflow-release', `${workflowRelativePath} updated.`);
|
|
1515
|
+
} else {
|
|
1516
|
+
summary.skippedFiles.push(workflowRelativePath);
|
|
1517
|
+
if (workflowUpsert.warning) {
|
|
1518
|
+
summary.warnings.push(workflowUpsert.warning);
|
|
1519
|
+
reporter.warn('workflow-release', workflowUpsert.warning);
|
|
1520
|
+
} else {
|
|
1521
|
+
reporter.warn('workflow-release', `${workflowRelativePath} already configured; kept as-is.`);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
reporter.start('workflow-ci', `Ensuring ${ciWorkflowRelativePath} includes stable+beta triggers...`);
|
|
1526
|
+
const ciWorkflowUpsert = upsertCiWorkflow(ciWorkflowTargetPath, ciWorkflowTemplatePath, {
|
|
1527
|
+
force: options.force,
|
|
1528
|
+
dryRun: options.dryRun,
|
|
1529
|
+
variables
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
if (ciWorkflowUpsert.result === 'created') {
|
|
1533
|
+
summary.createdFiles.push(ciWorkflowRelativePath);
|
|
1534
|
+
reporter.ok('workflow-ci', `${ciWorkflowRelativePath} created.`);
|
|
1535
|
+
} else if (ciWorkflowUpsert.result === 'overwritten' || ciWorkflowUpsert.result === 'updated') {
|
|
1536
|
+
summary.overwrittenFiles.push(ciWorkflowRelativePath);
|
|
1537
|
+
reporter.ok('workflow-ci', `${ciWorkflowRelativePath} updated.`);
|
|
1538
|
+
} else {
|
|
1539
|
+
summary.skippedFiles.push(ciWorkflowRelativePath);
|
|
1540
|
+
if (ciWorkflowUpsert.warning) {
|
|
1541
|
+
summary.warnings.push(ciWorkflowUpsert.warning);
|
|
1542
|
+
reporter.warn('workflow-ci', ciWorkflowUpsert.warning);
|
|
1543
|
+
} else {
|
|
1544
|
+
reporter.warn('workflow-ci', `${ciWorkflowRelativePath} already configured; kept as-is.`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function prevalidateInitExecution(args, selections, dependencies = {}, reporter = new StepReporter()) {
|
|
1159
1550
|
const deps = {
|
|
1160
1551
|
exec: dependencies.exec || execCommand
|
|
1161
1552
|
};
|
|
1162
1553
|
|
|
1554
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
1555
|
+
const templateDir = path.join(packageRoot, 'template');
|
|
1163
1556
|
const targetDir = path.resolve(args.dir);
|
|
1557
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1558
|
+
const result = {
|
|
1559
|
+
deps,
|
|
1560
|
+
targetDir,
|
|
1561
|
+
templateDir,
|
|
1562
|
+
packageJsonPath,
|
|
1563
|
+
repo: '',
|
|
1564
|
+
packageName: '',
|
|
1565
|
+
existsOnNpm: true,
|
|
1566
|
+
betaBranchExists: false,
|
|
1567
|
+
existingMainRuleset: null,
|
|
1568
|
+
existingBetaRuleset: null,
|
|
1569
|
+
mainRulesetPayload: null,
|
|
1570
|
+
betaRulesetPayload: createBetaRulesetPayload(args.betaBranch)
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
reporter.start('validate-local', 'Validating local project and templates...');
|
|
1164
1574
|
if (!fs.existsSync(targetDir)) {
|
|
1575
|
+
reporter.fail('validate-local', `Directory not found: ${targetDir}`);
|
|
1165
1576
|
throw new Error(`Directory not found: ${targetDir}`);
|
|
1166
1577
|
}
|
|
1167
1578
|
|
|
1168
|
-
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1169
1579
|
if (!fs.existsSync(packageJsonPath)) {
|
|
1580
|
+
reporter.fail('validate-local', `package.json not found in ${targetDir}`);
|
|
1170
1581
|
throw new Error(`package.json not found in ${targetDir}`);
|
|
1171
1582
|
}
|
|
1172
1583
|
|
|
1584
|
+
if (!fs.existsSync(templateDir)) {
|
|
1585
|
+
reporter.fail('validate-local', `Template not found in ${templateDir}`);
|
|
1586
|
+
throw new Error(`Template not found in ${templateDir}`);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1173
1589
|
const packageJson = readJsonFile(packageJsonPath);
|
|
1174
|
-
|
|
1175
|
-
|
|
1590
|
+
result.packageName = packageJson.name || packageDirFromName(path.basename(targetDir));
|
|
1591
|
+
reporter.ok('validate-local', 'Local project validation complete.');
|
|
1592
|
+
|
|
1593
|
+
if (selections.withGithub) {
|
|
1594
|
+
reporter.start('validate-gh', 'Validating GitHub CLI and authentication...');
|
|
1595
|
+
ensureGhAvailable(deps);
|
|
1596
|
+
reporter.ok('validate-gh', 'GitHub CLI available and authenticated.');
|
|
1597
|
+
|
|
1598
|
+
reporter.start('resolve-repo', 'Resolving repository target...');
|
|
1599
|
+
result.repo = resolveRepo({ repo: args.repo }, deps);
|
|
1600
|
+
reporter.ok('resolve-repo', `Using repository ${result.repo}.`);
|
|
1601
|
+
|
|
1602
|
+
reporter.start('validate-main-branch', `Checking default branch "${args.defaultBranch}"...`);
|
|
1603
|
+
if (!branchExists(deps, result.repo, args.defaultBranch)) {
|
|
1604
|
+
reporter.fail('validate-main-branch', `Default branch "${args.defaultBranch}" was not found in ${result.repo}.`);
|
|
1605
|
+
throw new Error(`Default branch "${args.defaultBranch}" not found in ${result.repo}.`);
|
|
1606
|
+
}
|
|
1607
|
+
reporter.ok('validate-main-branch', `Default branch "${args.defaultBranch}" found.`);
|
|
1608
|
+
|
|
1609
|
+
reporter.start('validate-rulesets', 'Loading existing GitHub rulesets...');
|
|
1610
|
+
const rulesets = listRulesets(deps, result.repo);
|
|
1611
|
+
result.mainRulesetPayload = createRulesetPayload(args);
|
|
1612
|
+
result.existingMainRuleset = rulesets.find((item) => item.name === result.mainRulesetPayload.name) || null;
|
|
1613
|
+
if (selections.withBeta) {
|
|
1614
|
+
result.existingBetaRuleset = rulesets.find((item) => item.name === result.betaRulesetPayload.name) || null;
|
|
1615
|
+
}
|
|
1616
|
+
reporter.ok('validate-rulesets', 'Ruleset scan completed.');
|
|
1617
|
+
|
|
1618
|
+
if (selections.withBeta) {
|
|
1619
|
+
reporter.start('validate-beta-branch', `Checking beta branch "${args.betaBranch}"...`);
|
|
1620
|
+
result.betaBranchExists = branchExists(deps, result.repo, args.betaBranch);
|
|
1621
|
+
reporter.ok(
|
|
1622
|
+
'validate-beta-branch',
|
|
1623
|
+
result.betaBranchExists
|
|
1624
|
+
? `Beta branch "${args.betaBranch}" already exists.`
|
|
1625
|
+
: `Beta branch "${args.betaBranch}" will be created from "${args.defaultBranch}".`
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (selections.withNpm) {
|
|
1631
|
+
reporter.start('validate-npm', 'Validating npm CLI and authentication...');
|
|
1632
|
+
ensureNpmAvailable(deps);
|
|
1633
|
+
ensureNpmAuthenticated(deps);
|
|
1634
|
+
reporter.ok('validate-npm', 'npm CLI available and authenticated.');
|
|
1635
|
+
|
|
1636
|
+
reporter.start('validate-package-publish', `Checking npm package status for ${result.packageName}...`);
|
|
1637
|
+
result.existsOnNpm = packageExistsOnNpm(deps, result.packageName);
|
|
1638
|
+
reporter.ok(
|
|
1639
|
+
'validate-package-publish',
|
|
1640
|
+
result.existsOnNpm
|
|
1641
|
+
? `Package ${result.packageName} already exists on npm.`
|
|
1642
|
+
: `Package ${result.packageName} does not exist on npm; first publish will run.`
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
return result;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
async function confirmInitPlan(args, selections, context, summary) {
|
|
1650
|
+
const hasExternalActions = selections.withGithub || selections.withNpm || selections.withBeta;
|
|
1651
|
+
const needsLocalForceConfirm = false;
|
|
1652
|
+
|
|
1653
|
+
if (!hasExternalActions && !needsLocalForceConfirm) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if (args.yes) {
|
|
1658
|
+
summary.warnings.push('Confirmation prompts skipped due to --yes.');
|
|
1659
|
+
return;
|
|
1176
1660
|
}
|
|
1177
1661
|
|
|
1662
|
+
await confirmOrThrow(summarizePlannedInitActions(selections, args, context));
|
|
1663
|
+
|
|
1664
|
+
if (args.force) {
|
|
1665
|
+
await confirmOrThrow('--force will overwrite managed files/scripts/dependencies when applicable.');
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (selections.withGithub && context.existingMainRuleset) {
|
|
1669
|
+
await confirmOrThrow(`Ruleset "${context.mainRulesetPayload.name}" already exists and will be overwritten.`);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (selections.withBeta && context.betaBranchExists) {
|
|
1673
|
+
await confirmOrThrow(`Branch "${args.betaBranch}" already exists and will be used as beta release flow branch.`);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (selections.withBeta && context.existingBetaRuleset) {
|
|
1677
|
+
await confirmOrThrow(`Ruleset "${context.betaRulesetPayload.name}" already exists and will be overwritten.`);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function runNpmSetup(args, dependencies = {}, options = {}) {
|
|
1682
|
+
const deps = {
|
|
1683
|
+
exec: dependencies.exec || execCommand
|
|
1684
|
+
};
|
|
1685
|
+
const reporter = options.reporter || new StepReporter();
|
|
1686
|
+
const summary = options.summary || createSummary();
|
|
1687
|
+
|
|
1688
|
+
const targetDir = path.resolve(args.dir);
|
|
1689
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1690
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
1691
|
+
const publishMissingByDefault = Boolean(options.publishMissingByDefault);
|
|
1692
|
+
const shouldPublishFirst = args.publishFirst || publishMissingByDefault;
|
|
1693
|
+
|
|
1694
|
+
reporter.start('npm-auth', 'Checking npm authentication...');
|
|
1178
1695
|
ensureNpmAvailable(deps);
|
|
1179
1696
|
ensureNpmAuthenticated(deps);
|
|
1697
|
+
reporter.ok('npm-auth', 'npm authentication validated.');
|
|
1180
1698
|
|
|
1181
|
-
const summary = createSummary();
|
|
1182
1699
|
summary.updatedScriptKeys.push('npm.auth', 'npm.package.lookup');
|
|
1183
1700
|
|
|
1184
1701
|
if (!packageJson.publishConfig || packageJson.publishConfig.access !== 'public') {
|
|
1185
1702
|
summary.warnings.push('package.json publishConfig.access is not "public". First publish may fail for public packages.');
|
|
1186
1703
|
}
|
|
1187
1704
|
|
|
1705
|
+
reporter.start('npm-exists', `Checking whether ${packageJson.name} exists on npm...`);
|
|
1188
1706
|
const existsOnNpm = packageExistsOnNpm(deps, packageJson.name);
|
|
1707
|
+
reporter.ok(
|
|
1708
|
+
'npm-exists',
|
|
1709
|
+
existsOnNpm
|
|
1710
|
+
? `Package ${packageJson.name} already exists on npm.`
|
|
1711
|
+
: `Package ${packageJson.name} is not published on npm yet.`
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1189
1714
|
if (existsOnNpm) {
|
|
1190
1715
|
summary.skippedScriptKeys.push('npm.first_publish');
|
|
1716
|
+
summary.warnings.push(`Package "${packageJson.name}" already exists. First publish is not required.`);
|
|
1191
1717
|
} else {
|
|
1192
1718
|
summary.updatedScriptKeys.push('npm.first_publish_required');
|
|
1193
1719
|
}
|
|
1194
1720
|
|
|
1195
|
-
if (!existsOnNpm && !
|
|
1721
|
+
if (!existsOnNpm && !shouldPublishFirst) {
|
|
1196
1722
|
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.`);
|
|
1197
1723
|
}
|
|
1198
1724
|
|
|
1199
|
-
if (
|
|
1200
|
-
if (
|
|
1201
|
-
summary.warnings.push(`package "${packageJson.name}" already exists on npm. Skipping first publish.`);
|
|
1202
|
-
} else if (args.dryRun) {
|
|
1725
|
+
if (!existsOnNpm && shouldPublishFirst) {
|
|
1726
|
+
if (args.dryRun) {
|
|
1203
1727
|
summary.warnings.push(`dry-run: would run "npm publish --access public" in ${targetDir}`);
|
|
1204
1728
|
} else {
|
|
1729
|
+
reporter.start('npm-publish', `Publishing first version of ${packageJson.name}...`);
|
|
1205
1730
|
const publish = deps.exec('npm', ['publish', '--access', 'public'], { cwd: targetDir, stdio: 'inherit' });
|
|
1206
1731
|
if (publish.status !== 0) {
|
|
1732
|
+
reporter.fail('npm-publish', 'First publish failed.');
|
|
1207
1733
|
const publishOutput = `${publish.stderr || ''}\n${publish.stdout || ''}`.toLowerCase();
|
|
1208
1734
|
const isOtpError = publishOutput.includes('eotp') || publishOutput.includes('one-time password');
|
|
1209
1735
|
|
|
@@ -1220,6 +1746,8 @@ function setupNpm(args, dependencies = {}) {
|
|
|
1220
1746
|
|
|
1221
1747
|
throw new Error('First publish failed. Check npm output above and try again.');
|
|
1222
1748
|
}
|
|
1749
|
+
|
|
1750
|
+
reporter.ok('npm-publish', `First publish for ${packageJson.name} completed.`);
|
|
1223
1751
|
summary.updatedScriptKeys.push('npm.first_publish_done');
|
|
1224
1752
|
}
|
|
1225
1753
|
}
|
|
@@ -1227,6 +1755,29 @@ function setupNpm(args, dependencies = {}) {
|
|
|
1227
1755
|
summary.warnings.push('Configure npm Trusted Publisher manually in npm package settings after first publish.');
|
|
1228
1756
|
summary.warnings.push('Trusted Publisher requires owner, repository, workflow file (.github/workflows/release.yml), and branch (main by default).');
|
|
1229
1757
|
|
|
1758
|
+
return summary;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function setupNpm(args, dependencies = {}) {
|
|
1762
|
+
const targetDir = path.resolve(args.dir);
|
|
1763
|
+
if (!fs.existsSync(targetDir)) {
|
|
1764
|
+
throw new Error(`Directory not found: ${targetDir}`);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1768
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1769
|
+
throw new Error(`package.json not found in ${targetDir}`);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const packageJson = readJsonFile(packageJsonPath);
|
|
1773
|
+
if (!packageJson.name) {
|
|
1774
|
+
throw new Error(`package.json in ${targetDir} must define "name".`);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
const summary = runNpmSetup(args, dependencies, {
|
|
1778
|
+
reporter: new StepReporter(),
|
|
1779
|
+
publishMissingByDefault: false
|
|
1780
|
+
});
|
|
1230
1781
|
printSummary(`npm setup completed for ${packageJson.name}`, summary);
|
|
1231
1782
|
}
|
|
1232
1783
|
|
|
@@ -1370,7 +1921,7 @@ async function setupBeta(args, dependencies = {}) {
|
|
|
1370
1921
|
`- set Actions workflow permissions to write`,
|
|
1371
1922
|
`- ensure branch "${args.betaBranch}" exists${doesBranchExist ? ' (already exists)' : ' (will be created)'}`,
|
|
1372
1923
|
`- apply branch protection ruleset "${betaRulesetPayload.name}"`,
|
|
1373
|
-
|
|
1924
|
+
`- require CI status check "${REQUIRED_CHECK_CONTEXT}" on beta branch`,
|
|
1374
1925
|
`- update local ${workflowRelativePath} and package.json beta scripts`
|
|
1375
1926
|
].join('\n')
|
|
1376
1927
|
);
|
|
@@ -1541,16 +2092,12 @@ function promoteStable(args, dependencies = {}) {
|
|
|
1541
2092
|
printSummary(`stable promotion prepared for ${targetDir}`, summary);
|
|
1542
2093
|
}
|
|
1543
2094
|
|
|
1544
|
-
function
|
|
2095
|
+
function applyGithubMainSetup(args, dependencies, summary, reporter) {
|
|
1545
2096
|
const deps = {
|
|
1546
2097
|
exec: dependencies.exec || execCommand
|
|
1547
2098
|
};
|
|
1548
|
-
|
|
1549
|
-
ensureGhAvailable(deps);
|
|
1550
|
-
|
|
1551
2099
|
const repo = resolveRepo(args, deps);
|
|
1552
2100
|
const rulesetPayload = createRulesetPayload(args);
|
|
1553
|
-
const summary = createSummary();
|
|
1554
2101
|
|
|
1555
2102
|
summary.updatedScriptKeys.push(
|
|
1556
2103
|
'repository.default_branch',
|
|
@@ -1564,10 +2111,10 @@ function setupGithub(args, dependencies = {}) {
|
|
|
1564
2111
|
summary.warnings.push(`dry-run: would update repository settings for ${repo}`);
|
|
1565
2112
|
summary.warnings.push(`dry-run: would set actions workflow permissions to write for ${repo}`);
|
|
1566
2113
|
summary.warnings.push(`dry-run: would upsert ruleset "${rulesetPayload.name}" for refs/heads/${args.defaultBranch}`);
|
|
1567
|
-
|
|
1568
|
-
return;
|
|
2114
|
+
return { repo, rulesetPayload };
|
|
1569
2115
|
}
|
|
1570
2116
|
|
|
2117
|
+
reporter.start('github-main-settings', 'Applying GitHub repository settings...');
|
|
1571
2118
|
const repoPayload = {
|
|
1572
2119
|
default_branch: args.defaultBranch,
|
|
1573
2120
|
delete_branch_on_merge: true,
|
|
@@ -1579,15 +2126,62 @@ function setupGithub(args, dependencies = {}) {
|
|
|
1579
2126
|
|
|
1580
2127
|
const patchRepo = ghApi(deps, 'PATCH', `/repos/${repo}`, repoPayload);
|
|
1581
2128
|
if (patchRepo.status !== 0) {
|
|
2129
|
+
reporter.fail('github-main-settings', 'Failed to update repository settings.');
|
|
1582
2130
|
throw new Error(`Failed to update repository settings: ${patchRepo.stderr || patchRepo.stdout}`.trim());
|
|
1583
2131
|
}
|
|
2132
|
+
reporter.ok('github-main-settings', 'Repository settings updated.');
|
|
1584
2133
|
|
|
2134
|
+
reporter.start('github-workflow-permissions', 'Applying GitHub Actions workflow permissions...');
|
|
1585
2135
|
updateWorkflowPermissions(deps, repo);
|
|
2136
|
+
reporter.ok('github-workflow-permissions', 'Workflow permissions configured.');
|
|
1586
2137
|
|
|
2138
|
+
reporter.start('github-main-ruleset', `Applying ruleset "${rulesetPayload.name}"...`);
|
|
1587
2139
|
const upsertResult = upsertRuleset(deps, repo, rulesetPayload);
|
|
2140
|
+
reporter.ok('github-main-ruleset', `Ruleset ${upsertResult}.`);
|
|
1588
2141
|
summary.overwrittenFiles.push(`github-ruleset:${upsertResult}`);
|
|
2142
|
+
return { repo, rulesetPayload };
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function applyGithubBetaSetup(args, dependencies, summary, reporter, repo) {
|
|
2146
|
+
const deps = {
|
|
2147
|
+
exec: dependencies.exec || execCommand
|
|
2148
|
+
};
|
|
2149
|
+
const betaRulesetPayload = createBetaRulesetPayload(args.betaBranch);
|
|
2150
|
+
|
|
2151
|
+
summary.updatedScriptKeys.push('github.beta_branch', 'github.beta_ruleset');
|
|
2152
|
+
|
|
2153
|
+
if (args.dryRun) {
|
|
2154
|
+
summary.warnings.push(`dry-run: would ensure branch "${args.betaBranch}" exists in ${repo}`);
|
|
2155
|
+
summary.warnings.push(`dry-run: would upsert ruleset "${betaRulesetPayload.name}" for refs/heads/${args.betaBranch}`);
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
reporter.start('github-beta-branch', `Ensuring branch "${args.betaBranch}" exists...`);
|
|
2160
|
+
const branchResult = ensureBranchExists(deps, repo, args.defaultBranch, args.betaBranch);
|
|
2161
|
+
if (branchResult === 'created') {
|
|
2162
|
+
summary.createdFiles.push(`github-branch:${args.betaBranch}`);
|
|
2163
|
+
reporter.ok('github-beta-branch', `Branch "${args.betaBranch}" created.`);
|
|
2164
|
+
} else {
|
|
2165
|
+
summary.skippedFiles.push(`github-branch:${args.betaBranch}`);
|
|
2166
|
+
reporter.warn('github-beta-branch', `Branch "${args.betaBranch}" already exists.`);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
reporter.start('github-beta-ruleset', `Applying beta ruleset "${betaRulesetPayload.name}"...`);
|
|
2170
|
+
const upsertResult = upsertRuleset(deps, repo, betaRulesetPayload);
|
|
2171
|
+
summary.overwrittenFiles.push(`github-beta-ruleset:${upsertResult}`);
|
|
2172
|
+
reporter.ok('github-beta-ruleset', `Beta ruleset ${upsertResult}.`);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function setupGithub(args, dependencies = {}) {
|
|
2176
|
+
const summary = createSummary();
|
|
2177
|
+
const deps = {
|
|
2178
|
+
exec: dependencies.exec || execCommand
|
|
2179
|
+
};
|
|
2180
|
+
ensureGhAvailable(deps);
|
|
1589
2181
|
|
|
1590
|
-
|
|
2182
|
+
const reporter = new StepReporter();
|
|
2183
|
+
const { repo } = applyGithubMainSetup(args, dependencies, summary, reporter);
|
|
2184
|
+
printSummary(args.dryRun ? `GitHub settings dry-run for ${repo}` : `GitHub settings applied to ${repo}`, summary);
|
|
1591
2185
|
}
|
|
1592
2186
|
|
|
1593
2187
|
async function run(argv, dependencies = {}) {
|
|
@@ -1599,7 +2193,7 @@ async function run(argv, dependencies = {}) {
|
|
|
1599
2193
|
}
|
|
1600
2194
|
|
|
1601
2195
|
if (parsed.mode === 'init') {
|
|
1602
|
-
initExistingPackage(parsed.args);
|
|
2196
|
+
await initExistingPackage(parsed.args, dependencies);
|
|
1603
2197
|
return;
|
|
1604
2198
|
}
|
|
1605
2199
|
|
package/package.json
CHANGED
|
@@ -28,3 +28,17 @@ jobs:
|
|
|
28
28
|
|
|
29
29
|
- name: Check
|
|
30
30
|
run: npm run check
|
|
31
|
+
|
|
32
|
+
required-check:
|
|
33
|
+
name: required-check
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
needs:
|
|
36
|
+
- check
|
|
37
|
+
if: ${{ always() }}
|
|
38
|
+
steps:
|
|
39
|
+
- name: Validate matrix result
|
|
40
|
+
run: |
|
|
41
|
+
if [ "${{ needs.check.result }}" != "success" ]; then
|
|
42
|
+
echo "check matrix failed"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
@@ -27,6 +27,9 @@ jobs:
|
|
|
27
27
|
cache: npm
|
|
28
28
|
registry-url: https://registry.npmjs.org
|
|
29
29
|
|
|
30
|
+
- name: Setup npm (latest)
|
|
31
|
+
run: npm install -g npm@latest
|
|
32
|
+
|
|
30
33
|
- name: Install
|
|
31
34
|
run: npm ci
|
|
32
35
|
|
|
@@ -42,3 +45,4 @@ jobs:
|
|
|
42
45
|
commit: "chore: release packages"
|
|
43
46
|
env:
|
|
44
47
|
GITHUB_TOKEN: ${{ secrets.CHANGESETS_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
|
48
|
+
NODE_AUTH_TOKEN: ""
|