@_linked/cli 1.17.0 → 1.19.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 (66) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +124 -5
  3. package/defaults/setup-publish/github/workflows/pr.yml +29 -0
  4. package/defaults/setup-publish/github/workflows/publish.yml +17 -70
  5. package/lib/esm/app-release/app-assets-store.d.ts +23 -0
  6. package/lib/esm/app-release/app-assets-store.d.ts.map +1 -0
  7. package/lib/esm/app-release/app-assets-store.js +33 -0
  8. package/lib/esm/app-release/app-assets-store.js.map +1 -0
  9. package/lib/esm/app-release/create-release-manifest.d.ts +114 -0
  10. package/lib/esm/app-release/create-release-manifest.d.ts.map +1 -0
  11. package/lib/esm/app-release/create-release-manifest.js +238 -0
  12. package/lib/esm/app-release/create-release-manifest.js.map +1 -0
  13. package/lib/esm/app-release/paths.d.ts +5 -0
  14. package/lib/esm/app-release/paths.d.ts.map +1 -0
  15. package/lib/esm/app-release/paths.js +44 -0
  16. package/lib/esm/app-release/paths.js.map +1 -0
  17. package/lib/esm/app-release/publisher.d.ts +25 -0
  18. package/lib/esm/app-release/publisher.d.ts.map +1 -0
  19. package/lib/esm/app-release/publisher.js +238 -0
  20. package/lib/esm/app-release/publisher.js.map +1 -0
  21. package/lib/esm/app-release/resolve-build-target.d.ts +19 -0
  22. package/lib/esm/app-release/resolve-build-target.d.ts.map +1 -0
  23. package/lib/esm/app-release/resolve-build-target.js +30 -0
  24. package/lib/esm/app-release/resolve-build-target.js.map +1 -0
  25. package/lib/esm/app-release/static-assets.d.ts +13 -0
  26. package/lib/esm/app-release/static-assets.d.ts.map +1 -0
  27. package/lib/esm/app-release/static-assets.js +55 -0
  28. package/lib/esm/app-release/static-assets.js.map +1 -0
  29. package/lib/esm/app-release/types.d.ts +71 -0
  30. package/lib/esm/app-release/types.d.ts.map +1 -0
  31. package/lib/esm/app-release/types.js +2 -0
  32. package/lib/esm/app-release/types.js.map +1 -0
  33. package/lib/esm/cli.js +81 -19
  34. package/lib/esm/cli.js.map +1 -1
  35. package/lib/esm/commands/build-app.d.ts +55 -0
  36. package/lib/esm/commands/build-app.d.ts.map +1 -0
  37. package/lib/esm/commands/build-app.js +178 -0
  38. package/lib/esm/commands/build-app.js.map +1 -0
  39. package/lib/esm/commands/publish-app.d.ts +18 -0
  40. package/lib/esm/commands/publish-app.d.ts.map +1 -0
  41. package/lib/esm/commands/publish-app.js +124 -0
  42. package/lib/esm/commands/publish-app.js.map +1 -0
  43. package/lib/esm/commands/serve-app.d.ts +14 -0
  44. package/lib/esm/commands/serve-app.d.ts.map +1 -0
  45. package/lib/esm/commands/serve-app.js +49 -0
  46. package/lib/esm/commands/serve-app.js.map +1 -0
  47. package/lib/esm/commands/setup-publish.d.ts +39 -4
  48. package/lib/esm/commands/setup-publish.d.ts.map +1 -1
  49. package/lib/esm/commands/setup-publish.js +142 -83
  50. package/lib/esm/commands/setup-publish.js.map +1 -1
  51. package/lib/esm/index.d.ts +11 -0
  52. package/lib/esm/index.d.ts.map +1 -1
  53. package/lib/esm/index.js +9 -0
  54. package/lib/esm/index.js.map +1 -1
  55. package/lib/esm/lifecycle.d.ts +1 -1
  56. package/lib/esm/lifecycle.d.ts.map +1 -1
  57. package/lib/esm/lifecycle.js +17 -8
  58. package/lib/esm/lifecycle.js.map +1 -1
  59. package/package.json +2 -2
  60. package/defaults/setup-publish/dual-branch/changeset/README.md +0 -20
  61. package/defaults/setup-publish/dual-branch/changeset/config.json +0 -14
  62. package/defaults/setup-publish/dual-branch/github/workflows/changeset-check.yml +0 -43
  63. package/defaults/setup-publish/dual-branch/github/workflows/ci.yml +0 -44
  64. package/defaults/setup-publish/dual-branch/github/workflows/publish.yml +0 -191
  65. package/defaults/setup-publish/github/workflows/changeset-check.yml +0 -44
  66. package/defaults/setup-publish/github/workflows/ci.yml +0 -43
@@ -0,0 +1,18 @@
1
+ import type { IFileStore } from '@_linked/core/interfaces/IFileStore';
2
+ export interface PublishAppOptions {
3
+ appRoot?: string;
4
+ manifestPath?: string;
5
+ /** An already-resolved store (the `build-app --publish` path). */
6
+ store?: IFileStore;
7
+ /**
8
+ * Resolve the store lazily. Used instead of `store` so the release manifest
9
+ * is read and validated first: a Capacitor (non-publishable) manifest then
10
+ * fails with "only publishable web release manifests may be uploaded"
11
+ * instead of a confusing storage-configuration error.
12
+ */
13
+ resolveStore?: () => Promise<IFileStore>;
14
+ yes?: boolean;
15
+ }
16
+ export declare const redactPublishError: (error: unknown) => Error;
17
+ export declare const publishApp: (options: PublishAppOptions) => Promise<import("../index.js").PublishResult>;
18
+ //# sourceMappingURL=publish-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-app.d.ts","sourceRoot":"","sources":["../../../src/commands/publish-app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AAapE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IACzC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAWD,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,KAcnD,CAAC;AAaF,eAAO,MAAM,UAAU,GAAU,SAAS,iBAAiB,iDAsF1D,CAAC"}
@@ -0,0 +1,124 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { loadReleaseManifest, planReleasePublish, publishRelease, } from '../app-release/publisher.js';
11
+ const secretEnvironmentKeys = [
12
+ 'AWS_SECRET_ACCESS_KEY',
13
+ 'STATIC_AWS_SECRET_ACCESS_KEY',
14
+ 'UPLOADS_AWS_SECRET_ACCESS_KEY',
15
+ 'AWS_ACCESS_KEY_ID',
16
+ 'STATIC_AWS_ACCESS_KEY_ID',
17
+ 'UPLOADS_AWS_ACCESS_KEY_ID',
18
+ ];
19
+ export const redactPublishError = (error) => {
20
+ const source = error instanceof Error ? error : new Error(String(error));
21
+ let message = source.message;
22
+ let stack = source.stack;
23
+ for (const key of secretEnvironmentKeys) {
24
+ const value = process.env[key];
25
+ if (value) {
26
+ message = message.split(value).join('[REDACTED]');
27
+ stack = stack === null || stack === void 0 ? void 0 : stack.split(value).join('[REDACTED]');
28
+ }
29
+ }
30
+ source.message = message;
31
+ if (stack)
32
+ source.stack = stack;
33
+ return source;
34
+ };
35
+ const resolveStore = (options) => __awaiter(void 0, void 0, void 0, function* () {
36
+ if (!options.resolveStore) {
37
+ throw new Error('publishApp needs either a store or a resolveStore function to reach one');
38
+ }
39
+ return options.resolveStore();
40
+ });
41
+ export const publishApp = (options) => __awaiter(void 0, void 0, void 0, function* () {
42
+ const appRoot = options.appRoot || process.cwd();
43
+ let progressSpinner;
44
+ try {
45
+ // This command reuses a completed build. It validates the manifest and
46
+ // destination before dry-run output or any confirmed upload.
47
+ console.log('🔄 Validating release manifest and destination...');
48
+ // Read the manifest before touching storage config, so a manifest that can
49
+ // never be published says so before a store is resolved.
50
+ loadReleaseManifest(appRoot, options.manifestPath);
51
+ const store = options.store || (yield resolveStore(options));
52
+ const plan = planReleasePublish({
53
+ appRoot,
54
+ manifestPath: options.manifestPath,
55
+ store,
56
+ });
57
+ console.log(`Release: ${plan.releaseId}`);
58
+ console.log(`Destination: ${plan.destination.accessURL || '(no accessURL)'} under ${plan.destination.releasePrefix}/`);
59
+ console.log(`Artifacts: ${plan.files.length} (${plan.totalBytes} bytes)`);
60
+ if (!options.yes) {
61
+ console.log('Dry run only; pass --yes to upload this release.');
62
+ }
63
+ console.log('✅ Release plan validated');
64
+ if (options.yes) {
65
+ console.log('🔄 Uploading and verifying release artifacts...');
66
+ const total = plan.files.length + 1;
67
+ if (process.stdout.isTTY) {
68
+ // ora is ESM-only, so load it only for an interactive terminal. CI and
69
+ // PM2 use the periodic plain-text progress branch below.
70
+ const { default: ora } = yield import('ora');
71
+ progressSpinner = ora({
72
+ discardStdin: true,
73
+ text: `Publishing 0/${total} files`,
74
+ }).start();
75
+ }
76
+ }
77
+ let lastLoggedProgress = 0;
78
+ const result = yield publishRelease({
79
+ appRoot,
80
+ manifestPath: options.manifestPath,
81
+ store,
82
+ yes: options.yes,
83
+ onWarning: (message) => {
84
+ // Printed once per run, not once per file.
85
+ if (progressSpinner) {
86
+ progressSpinner.text = message;
87
+ }
88
+ console.warn(`⚠️ ${message}`);
89
+ },
90
+ onProgress: ({ completed, total }) => {
91
+ const message = `Publishing ${completed}/${total} files`;
92
+ if (progressSpinner) {
93
+ progressSpinner.text = message;
94
+ }
95
+ else if (completed === total || completed - lastLoggedProgress >= 25) {
96
+ console.log(message);
97
+ lastLoggedProgress = completed;
98
+ }
99
+ },
100
+ });
101
+ if (result.dryRun) {
102
+ console.log('✅ Dry run complete; no files uploaded');
103
+ }
104
+ else {
105
+ const completedMessage = `Release ${result.releaseId} uploaded: ${result.uploadedFiles} files, ${result.uploadedBytes} bytes`;
106
+ progressSpinner === null || progressSpinner === void 0 ? void 0 : progressSpinner.succeed(completedMessage);
107
+ if (!progressSpinner)
108
+ console.log(`✅ ${completedMessage}`);
109
+ progressSpinner = undefined;
110
+ if (result.unverified.length) {
111
+ console.warn(`⚠️ ${result.unverified.length} of ${result.uploadedFiles} objects could not be verified against a store hash.`);
112
+ }
113
+ }
114
+ return result;
115
+ }
116
+ catch (error) {
117
+ const reported = redactPublishError(error);
118
+ // The spinner line is the last thing on screen, so it carries the real
119
+ // reason rather than a bare "Release upload failed".
120
+ progressSpinner === null || progressSpinner === void 0 ? void 0 : progressSpinner.fail(reported.message);
121
+ throw reported;
122
+ }
123
+ });
124
+ //# sourceMappingURL=publish-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-app.js","sourceRoot":"","sources":["../../../src/commands/publish-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface ServeAppOptions {
2
+ appRoot?: string;
3
+ allowDevelopment?: boolean;
4
+ /** Profiles from `--env a,b`, passed on to the environment loader. */
5
+ environmentNames?: string[];
6
+ }
7
+ export interface ServeAppDependencies {
8
+ loadEnvironment?: () => Promise<void>;
9
+ startCompiledServer?: () => Promise<unknown>;
10
+ }
11
+ export declare const validateCompiledAppArtifacts: (appRoot: string) => void;
12
+ /** Start a compiled Linked backend without creating a Vite server or watcher. */
13
+ export declare const serveCompiledApp: (options?: ServeAppOptions, dependencies?: ServeAppDependencies) => Promise<unknown>;
14
+ //# sourceMappingURL=serve-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-app.d.ts","sourceRoot":"","sources":["../../../src/commands/serve-app.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AASD,eAAO,MAAM,4BAA4B,GAAI,SAAS,MAAM,KAAG,IAoB9D,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,GAC3B,UAAS,eAAoB,EAC7B,eAAc,oBAAyB,KACtC,OAAO,CAAC,OAAO,CAsBjB,CAAC"}
@@ -0,0 +1,49 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { ensureEnvironmentLoaded } from '../lifecycle.js';
13
+ const requiredBuildArtifacts = [
14
+ 'lib/App.js',
15
+ 'lib/routes.js',
16
+ 'lib/backend.js',
17
+ 'public/bundles/.vite/manifest.json',
18
+ ];
19
+ export const validateCompiledAppArtifacts = (appRoot) => {
20
+ for (const relativePath of requiredBuildArtifacts) {
21
+ const absolutePath = path.join(appRoot, relativePath);
22
+ if (!fs.existsSync(absolutePath)) {
23
+ throw new Error(`Compiled app artifact is missing: ${relativePath}. Run linked build-app before linked serve-app.`);
24
+ }
25
+ }
26
+ const manifestPath = path.join(appRoot, 'public/bundles/.vite/manifest.json');
27
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
28
+ if (!manifest || typeof manifest !== 'object' || Object.keys(manifest).length === 0) {
29
+ throw new Error('Compiled app artifact is invalid: public/bundles/.vite/manifest.json is empty');
30
+ }
31
+ };
32
+ /** Start a compiled Linked backend without creating a Vite server or watcher. */
33
+ export const serveCompiledApp = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}, dependencies = {}) {
34
+ const appRoot = options.appRoot || process.cwd();
35
+ const loadEnvironment = dependencies.loadEnvironment ||
36
+ (() => ensureEnvironmentLoaded(options.environmentNames));
37
+ const startCompiledServer = dependencies.startCompiledServer ||
38
+ (() => __awaiter(void 0, void 0, void 0, function* () {
39
+ const { startServer } = yield import('../cli-methods.js');
40
+ return startServer(false);
41
+ }));
42
+ yield loadEnvironment();
43
+ if (process.env.NODE_ENV === 'development' && !options.allowDevelopment) {
44
+ throw new Error('linked serve-app only runs compiled non-development builds. Use linked start for development.');
45
+ }
46
+ validateCompiledAppArtifacts(appRoot);
47
+ return startCompiledServer();
48
+ });
49
+ //# sourceMappingURL=serve-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-app.js","sourceRoot":"","sources":["../../../src/commands/serve-app.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAC,uBAAuB,EAAC,MAAM,iBAAiB,CAAC;AAcxD,MAAM,sBAAsB,GAAG;IAC7B,YAAY;IACZ,eAAe;IACf,gBAAgB;IAChB,oCAAoC;CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,OAAe,EAAQ,EAAE;IACpE,KAAK,MAAM,YAAY,IAAI,sBAAsB,EAAE,CAAC;QAClD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,qCAAqC,YAAY,iDAAiD,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAC5B,OAAO,EACP,oCAAoC,CACrC,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAGZ,EAAE,mDAFpB,UAA2B,EAAE,EAC7B,eAAqC,EAAE;IAEvC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACjD,MAAM,eAAe,GACnB,YAAY,CAAC,eAAe;QAC5B,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5D,MAAM,mBAAmB,GACvB,YAAY,CAAC,mBAAmB;QAChC,CAAC,GAAS,EAAE;YACV,MAAM,EAAC,WAAW,EAAC,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YACxD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAA,CAAC,CAAC;IAEL,MAAM,eAAe,EAAE,CAAC;IAExB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAC;IACJ,CAAC;IAED,4BAA4B,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,mBAAmB,EAAE,CAAC;AAC/B,CAAC,CAAA,CAAC"}
@@ -1,14 +1,47 @@
1
+ export declare const WORKFLOW_FILES: readonly ["pr.yml", "publish.yml"];
2
+ /**
3
+ * The uniform branch-protection profile every `@_linked` repo runs on `main`.
4
+ *
5
+ * The required check is `checks / Build & Test`, not `Build & Test`: GitHub reports a job invoked
6
+ * through `workflow_call` as `<caller-job-id> / <job name>`, and the caller job in pr.yml is
7
+ * `checks`. A repo pinning the bare name waits forever on a check that never reports.
8
+ */
9
+ export declare function buildBranchProtectionPayload(): {
10
+ required_status_checks: {
11
+ strict: boolean;
12
+ contexts: string[];
13
+ };
14
+ enforce_admins: boolean;
15
+ required_pull_request_reviews: {
16
+ required_approving_review_count: number;
17
+ dismiss_stale_reviews: boolean;
18
+ require_code_owner_reviews: boolean;
19
+ };
20
+ restrictions: any;
21
+ allow_force_pushes: boolean;
22
+ allow_deletions: boolean;
23
+ };
1
24
  export type SetupPublishOptions = {
2
25
  configureGithub?: boolean;
3
26
  scope?: 'core' | 'community';
27
+ /**
28
+ * @deprecated The dual-branch (`main` + `dev`) flow and its `@next` prereleases are retired —
29
+ * every package repo is `main`-only. Kept as an accepted no-op rather than removed because
30
+ * commander aborts the whole command on an unknown option, so a stale script or copied
31
+ * command line would fail to set the repo up at all instead of setting it up correctly.
32
+ */
4
33
  dualBranch?: boolean;
5
34
  grantTeam?: string;
6
35
  };
7
36
  /**
8
- * Set up a single-branch changesets publish workflow in the current package repo.
37
+ * Set up the changesets publish pipeline in the current package repo.
38
+ *
39
+ * The workflows are thin callers of the shared reusable workflows in `linked-fw/.github`
40
+ * (pinned `@v1`, a deliberately moving tag) — one edit there changes every package's pipeline,
41
+ * which is why nothing about the build or publish steps is scaffolded per repo any more.
9
42
  *
10
43
  * Installs:
11
- * - .github/workflows/{ci,publish,changeset-check}.yml
44
+ * - .github/workflows/{pr,publish}.yml — caller stubs, nothing else
12
45
  * - .changeset/config.json + README.md
13
46
  * - .changeset/initial-release.md
14
47
  * - .gitignore entries for node_modules, lib, yarn.lock, src-compiled-artifacts
@@ -16,8 +49,10 @@ export type SetupPublishOptions = {
16
49
  * publishConfig: {access: public}
17
50
  * - package-lock.json generated via npm install --package-lock-only
18
51
  *
19
- * With --configure-github: if `gh` CLI is available and authenticated, also sets
20
- * branch protection on main (strict + required "Build & Test" check).
52
+ * With --configure-github: if `gh` CLI is available and authenticated, also applies the uniform
53
+ * branch-protection profile on main and enables auto-merge.
21
54
  */
22
55
  export declare function setupPublish(opts?: SetupPublishOptions): Promise<void>;
56
+ /** Mutates `pkgJson` in place; returns whether anything changed. Pure enough to test. */
57
+ export declare function applyPackageJsonPatches(pkgJson: any, repoSlug: string): boolean;
23
58
  //# sourceMappingURL=setup-publish.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup-publish.d.ts","sourceRoot":"","sources":["../../../src/commands/setup-publish.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhF"}
1
+ {"version":3,"file":"setup-publish.d.ts","sourceRoot":"","sources":["../../../src/commands/setup-publish.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,cAAc,oCAAqC,CAAC;AAEjE;;;;;;GAMG;AACH,wBAAgB,4BAA4B;;;;;;;;;;;;;;EAkB3C;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DhF;AAwHD,yFAAyF;AACzF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAkD/E"}
@@ -19,11 +19,45 @@ const dirname__ = typeof __dirname !== 'undefined'
19
19
  // This file compiles to lib/{esm,cjs}/commands/setup-publish.js — go up three
20
20
  // levels to reach the package root, then into defaults/setup-publish.
21
21
  const TEMPLATE_ROOT = path.resolve(dirname__, '..', '..', '..', 'defaults', 'setup-publish');
22
+ // Both are callers of the shared reusable workflows. `publish.yml` in particular cannot be
23
+ // renamed: npm's trusted-publisher check validates the calling workflow's filename, and a
24
+ // mismatch only shows up as releases quietly needing manual approval again.
25
+ export const WORKFLOW_FILES = ['pr.yml', 'publish.yml'];
22
26
  /**
23
- * Set up a single-branch changesets publish workflow in the current package repo.
27
+ * The uniform branch-protection profile every `@_linked` repo runs on `main`.
28
+ *
29
+ * The required check is `checks / Build & Test`, not `Build & Test`: GitHub reports a job invoked
30
+ * through `workflow_call` as `<caller-job-id> / <job name>`, and the caller job in pr.yml is
31
+ * `checks`. A repo pinning the bare name waits forever on a check that never reports.
32
+ */
33
+ export function buildBranchProtectionPayload() {
34
+ return {
35
+ required_status_checks: { strict: false, contexts: ['checks / Build & Test'] },
36
+ // Admins included: the point of the gate is that no release skips the build, and the people
37
+ // most likely to push straight to main are the admins.
38
+ enforce_admins: true,
39
+ // Reviews enabled but zero approvals required. The publish workflow direct-merges the version
40
+ // PR (GitHub's auto-merge queue does not honor review-bypass allowances), so a non-zero count
41
+ // would stall every release; the block itself still has to exist for the profile to match.
42
+ required_pull_request_reviews: {
43
+ required_approving_review_count: 0,
44
+ dismiss_stale_reviews: false,
45
+ require_code_owner_reviews: false,
46
+ },
47
+ restrictions: null,
48
+ allow_force_pushes: false,
49
+ allow_deletions: false,
50
+ };
51
+ }
52
+ /**
53
+ * Set up the changesets publish pipeline in the current package repo.
54
+ *
55
+ * The workflows are thin callers of the shared reusable workflows in `linked-fw/.github`
56
+ * (pinned `@v1`, a deliberately moving tag) — one edit there changes every package's pipeline,
57
+ * which is why nothing about the build or publish steps is scaffolded per repo any more.
24
58
  *
25
59
  * Installs:
26
- * - .github/workflows/{ci,publish,changeset-check}.yml
60
+ * - .github/workflows/{pr,publish}.yml — caller stubs, nothing else
27
61
  * - .changeset/config.json + README.md
28
62
  * - .changeset/initial-release.md
29
63
  * - .gitignore entries for node_modules, lib, yarn.lock, src-compiled-artifacts
@@ -31,18 +65,18 @@ const TEMPLATE_ROOT = path.resolve(dirname__, '..', '..', '..', 'defaults', 'set
31
65
  * publishConfig: {access: public}
32
66
  * - package-lock.json generated via npm install --package-lock-only
33
67
  *
34
- * With --configure-github: if `gh` CLI is available and authenticated, also sets
35
- * branch protection on main (strict + required "Build & Test" check).
68
+ * With --configure-github: if `gh` CLI is available and authenticated, also applies the uniform
69
+ * branch-protection profile on main and enables auto-merge.
36
70
  */
37
71
  export function setupPublish() {
38
72
  return __awaiter(this, arguments, void 0, function* (opts = {}) {
39
73
  const cwd = process.cwd();
40
74
  const scope = opts.scope || 'core';
41
75
  const npmSecretName = scope === 'community' ? 'NPM_AUTH_TOKEN_CM' : 'NPM_AUTH_TOKEN';
42
- const templateDir = opts.dualBranch
43
- ? path.join(TEMPLATE_ROOT, 'dual-branch')
44
- : TEMPLATE_ROOT;
45
- console.log(chalk.magenta(`Setting up ${opts.dualBranch ? 'dual-branch (main + dev)' : 'single-branch (main only)'} publish workflow...`));
76
+ console.log(chalk.magenta('Setting up the publish pipeline (main only)...'));
77
+ if (opts.dualBranch) {
78
+ console.log(chalk.yellow(' ⚠ --dual-branch is deprecated and ignored: the main + dev flow and its @next prereleases are retired.'));
79
+ }
46
80
  console.log(` target: ${cwd}`);
47
81
  console.log(` npm secret: ${npmSecretName} (${scope})`);
48
82
  const pkgJsonPath = path.join(cwd, 'package.json');
@@ -55,9 +89,9 @@ export function setupPublish() {
55
89
  console.log(` repo: ${repoSlug}`);
56
90
  console.log('');
57
91
  // 1. Workflow files (with {{NPM_SECRET_NAME}} substitution in publish.yml)
58
- yield copyWorkflows(cwd, npmSecretName, templateDir);
92
+ yield copyWorkflows(cwd, npmSecretName);
59
93
  // 2. Changesets config + README
60
- yield copyChangesetConfig(cwd, repoSlug, templateDir);
94
+ yield copyChangesetConfig(cwd, repoSlug);
61
95
  // 3. Initial changeset
62
96
  yield writeInitialChangeset(cwd, pkgJson.name);
63
97
  // 4. .gitignore
@@ -106,22 +140,33 @@ function resolveRepoSlug(cwd, pkgJson) {
106
140
  return 'OWNER/REPO';
107
141
  });
108
142
  }
109
- function copyWorkflows(cwd, npmSecretName, templateDir) {
143
+ function copyWorkflows(cwd, npmSecretName) {
110
144
  return __awaiter(this, void 0, void 0, function* () {
111
- const srcDir = path.join(templateDir, 'github', 'workflows');
145
+ const srcDir = path.join(TEMPLATE_ROOT, 'github', 'workflows');
112
146
  const dstDir = path.join(cwd, '.github', 'workflows');
113
147
  fs.mkdirpSync(dstDir);
114
- for (const file of ['ci.yml', 'publish.yml', 'changeset-check.yml']) {
148
+ for (const file of WORKFLOW_FILES) {
115
149
  let content = fs.readFileSync(path.join(srcDir, file), 'utf8');
116
150
  content = content.replace(/\{\{NPM_SECRET_NAME\}\}/g, npmSecretName);
117
151
  fs.writeFileSync(path.join(dstDir, file), content);
118
152
  console.log(chalk.green(' ✓') + ` .github/workflows/${file}`);
119
153
  }
154
+ // ci.yml and changeset-check.yml became the two jobs of the shared pr.yml. Left behind on a
155
+ // repo that was set up before the consolidation they keep running, so the old bare
156
+ // `Build & Test` check reports alongside `checks / Build & Test` and re-setting a repo up
157
+ // looks like it worked while the duplicate gate quietly stays.
158
+ for (const stale of ['ci.yml', 'changeset-check.yml']) {
159
+ const stalePath = path.join(dstDir, stale);
160
+ if (fs.existsSync(stalePath)) {
161
+ fs.removeSync(stalePath);
162
+ console.log(chalk.green(' ✓') + ` removed superseded .github/workflows/${stale}`);
163
+ }
164
+ }
120
165
  });
121
166
  }
122
- function copyChangesetConfig(cwd, repoSlug, templateDir) {
167
+ function copyChangesetConfig(cwd, repoSlug) {
123
168
  return __awaiter(this, void 0, void 0, function* () {
124
- const srcDir = path.join(templateDir, 'changeset');
169
+ const srcDir = path.join(TEMPLATE_ROOT, 'changeset');
125
170
  const dstDir = path.join(cwd, '.changeset');
126
171
  fs.mkdirpSync(dstDir);
127
172
  let configContent = fs.readFileSync(path.join(srcDir, 'config.json'), 'utf8');
@@ -180,55 +225,7 @@ function updateGitignore(cwd) {
180
225
  }
181
226
  function patchPackageJson(pkgJsonPath, pkgJson, repoSlug) {
182
227
  return __awaiter(this, void 0, void 0, function* () {
183
- var _a;
184
- let modified = false;
185
- // `repository.url` must be set, and must match the repo the workflow builds in.
186
- //
187
- // `create-package` scaffolds it EMPTY, and npm rejects a provenance-signed publish whose
188
- // attestation cannot be matched against it:
189
- //
190
- // 422 Error verifying sigstore provenance bundle: Failed to validate repository
191
- // information: package.json "repository.url" is "", expected to match <repo>
192
- //
193
- // The slug is already resolved from `git remote` above, so there is no reason to leave a
194
- // package to discover this at its first release.
195
- if (repoSlug !== 'OWNER/REPO') {
196
- const url = `git+https://github.com/${repoSlug}.git`;
197
- const current = typeof pkgJson.repository === 'string'
198
- ? pkgJson.repository
199
- : (_a = pkgJson.repository) === null || _a === void 0 ? void 0 : _a.url;
200
- if (!current) {
201
- pkgJson.repository = { type: 'git', url };
202
- modified = true;
203
- }
204
- }
205
- if (!pkgJson.publishConfig) {
206
- pkgJson.publishConfig = { access: 'public' };
207
- modified = true;
208
- }
209
- else {
210
- if (pkgJson.publishConfig.access !== 'public') {
211
- pkgJson.publishConfig.access = 'public';
212
- modified = true;
213
- }
214
- // Strip provenance: true — it requires OIDC (id-token: write permission +
215
- // npm Trusted Publisher config), which conflicts with our NPM_AUTH_TOKEN
216
- // flow. Users who want provenance should use OIDC separately.
217
- if (pkgJson.publishConfig.provenance) {
218
- delete pkgJson.publishConfig.provenance;
219
- modified = true;
220
- }
221
- }
222
- pkgJson.devDependencies = pkgJson.devDependencies || {};
223
- if (!pkgJson.devDependencies['@changesets/cli']) {
224
- pkgJson.devDependencies['@changesets/cli'] = '^2.29.8';
225
- modified = true;
226
- }
227
- if (!pkgJson.devDependencies['@changesets/changelog-github']) {
228
- pkgJson.devDependencies['@changesets/changelog-github'] = '^0.5.2';
229
- modified = true;
230
- }
231
- if (modified) {
228
+ if (applyPackageJsonPatches(pkgJson, repoSlug)) {
232
229
  fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
233
230
  console.log(chalk.green(' ✓') + ' package.json (publishConfig + changesets devDeps)');
234
231
  }
@@ -237,6 +234,55 @@ function patchPackageJson(pkgJsonPath, pkgJson, repoSlug) {
237
234
  }
238
235
  });
239
236
  }
237
+ /** Mutates `pkgJson` in place; returns whether anything changed. Pure enough to test. */
238
+ export function applyPackageJsonPatches(pkgJson, repoSlug) {
239
+ var _a;
240
+ let modified = false;
241
+ // `repository.url` must be set, and must match the repo the workflow builds in.
242
+ //
243
+ // `create-package` scaffolds it EMPTY, and npm rejects a provenance-signed publish whose
244
+ // attestation cannot be matched against it:
245
+ //
246
+ // 422 Error verifying sigstore provenance bundle: Failed to validate repository
247
+ // information: package.json "repository.url" is "", expected to match <repo>
248
+ //
249
+ // The slug is already resolved from `git remote` above, so there is no reason to leave a
250
+ // package to discover this at its first release.
251
+ if (repoSlug !== 'OWNER/REPO') {
252
+ const url = `git+https://github.com/${repoSlug}.git`;
253
+ const current = typeof pkgJson.repository === 'string'
254
+ ? pkgJson.repository
255
+ : (_a = pkgJson.repository) === null || _a === void 0 ? void 0 : _a.url;
256
+ if (!current) {
257
+ pkgJson.repository = { type: 'git', url };
258
+ modified = true;
259
+ }
260
+ }
261
+ if (!pkgJson.publishConfig) {
262
+ pkgJson.publishConfig = { access: 'public' };
263
+ modified = true;
264
+ }
265
+ else {
266
+ if (pkgJson.publishConfig.access !== 'public') {
267
+ pkgJson.publishConfig.access = 'public';
268
+ modified = true;
269
+ }
270
+ // `provenance` is deliberately left alone. It used to be stripped because publishing went
271
+ // through NPM_AUTH_TOKEN only; the shared workflow now publishes with OIDC trusted publishing
272
+ // first (`npm publish --provenance`, id-token: write on both caller and reusable workflow) and
273
+ // falls back to the staged token release, so provenance is wanted, not a conflict.
274
+ }
275
+ pkgJson.devDependencies = pkgJson.devDependencies || {};
276
+ if (!pkgJson.devDependencies['@changesets/cli']) {
277
+ pkgJson.devDependencies['@changesets/cli'] = '^2.29.8';
278
+ modified = true;
279
+ }
280
+ if (!pkgJson.devDependencies['@changesets/changelog-github']) {
281
+ pkgJson.devDependencies['@changesets/changelog-github'] = '^0.5.2';
282
+ modified = true;
283
+ }
284
+ return modified;
285
+ }
240
286
  function ensureLockfile(cwd) {
241
287
  return __awaiter(this, void 0, void 0, function* () {
242
288
  // Always regenerate — package.json has just been patched (new devDeps etc.),
@@ -294,27 +340,23 @@ function configureGithub(repoSlug) {
294
340
  console.warn(chalk.yellow(' ⚠ `gh` CLI is not authenticated. Run `gh auth login` and retry.'));
295
341
  return;
296
342
  }
297
- const payload = JSON.stringify({
298
- required_status_checks: { strict: true, contexts: ['Build & Test'] },
299
- enforce_admins: false,
300
- required_pull_request_reviews: null,
301
- restrictions: null,
302
- allow_force_pushes: false,
303
- allow_deletions: false,
304
- });
343
+ const payload = JSON.stringify(buildBranchProtectionPayload());
305
344
  try {
306
345
  // Use --input - to pass the JSON payload via stdin
307
346
  yield execPromise(`echo '${payload.replace(/'/g, "'\\''")}' | gh api -X PUT /repos/${repoSlug}/branches/main/protection --input -`, false, false);
308
347
  console.log(chalk.green(' ✓') + ` branch protection enabled on ${repoSlug}/main`);
348
+ // A repo can pin a required check in a *ruleset* as well as in classic branch protection, and
349
+ // the two are stored separately — this call only writes the classic one, so a leftover ruleset
350
+ // keeps blocking merges on a check name that no longer reports.
351
+ console.log(chalk.gray(` · check for a competing ruleset: gh api /repos/${repoSlug}/rulesets`));
309
352
  }
310
353
  catch (err) {
311
354
  console.warn(chalk.yellow(' ⚠ Failed to set branch protection. Do it manually:'));
312
355
  console.warn(chalk.yellow(` https://github.com/${repoSlug}/settings/branches`));
313
356
  }
314
- // Enable "Allow auto-merge" so the post-release back-merge PR (dual-branch) self-merges once
315
- // checks pass. Harmless for single-branch repos. The publish workflow authors release/back-merge
316
- // PRs via the org App token (org secrets RELEASE_APP_ID / RELEASE_APP_PRIVATE_KEY) so their CI
317
- // runs without a manual approval gate.
357
+ // Enable "Allow auto-merge" so a PR can be queued to merge itself once checks pass. The publish
358
+ // workflow authors the Release PR via the org App token (org secrets RELEASE_APP_ID /
359
+ // RELEASE_APP_PRIVATE_KEY) so its CI runs without a manual approval gate.
318
360
  try {
319
361
  yield execPromise(`gh api -X PATCH /repos/${repoSlug} -F allow_auto_merge=true`, false, false);
320
362
  console.log(chalk.green(' ✓') + ` auto-merge enabled on ${repoSlug}`);
@@ -356,24 +398,41 @@ function printNextSteps(repoSlug, npmSecretName, configuredGithub, grantedTeam)
356
398
  console.log('');
357
399
  console.log(chalk.bold('Next steps:'));
358
400
  console.log('');
401
+ const [owner, repo] = repoSlug.split('/');
359
402
  console.log(` 1. Review the generated files, adjust as needed.`);
360
403
  console.log(` 2. Commit:`);
361
- console.log(chalk.cyan(' git add . && git commit -m "set up single-branch publish workflow"'));
404
+ console.log(chalk.cyan(' git add . && git commit -m "set up the publish pipeline"'));
362
405
  console.log(` 3. Push to main — the publish workflow will trigger.`);
363
406
  console.log('');
364
- console.log(chalk.bold('One-time org-level setup (if not already done):'));
407
+ console.log(chalk.bold('Register the trusted publisher on npmjs.com (before the first release):'));
408
+ console.log('');
409
+ console.log(` package → Settings → Trusted Publisher, matching the caller workflow exactly:`);
410
+ console.log(` organization: ${chalk.cyan(owner)} repository: ${chalk.cyan(repo)}`);
411
+ console.log(` workflow filename: ${chalk.cyan('publish.yml')} environment: ${chalk.cyan('(empty)')}`);
412
+ console.log(` "Allow npm publish": ${chalk.cyan('ticked')}`);
413
+ console.log('');
414
+ console.log(chalk.gray(` Without it the release still ships — the workflow falls back to a staged`));
415
+ console.log(chalk.gray(` npm release that a maintainer approves with 2FA. A typo in the organization`));
416
+ console.log(chalk.gray(` field looks exactly the same, so check that field first if a package that`));
417
+ console.log(chalk.gray(` should publish directly keeps staging instead.`));
418
+ console.log('');
419
+ // No per-repo secret grant step: NPM_AUTH_TOKEN, RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY are
420
+ // "All repositories" org secrets, which is the whole reason first-party and community packages
421
+ // live in separate orgs. Only mention what is genuinely still one-time-per-org.
422
+ console.log(chalk.bold('One-time org-level setup (already done on linked-fw):'));
365
423
  console.log('');
366
- console.log(` a. Create an org secret ${chalk.cyan(npmSecretName)} with a granular npm token`);
367
- console.log(` (2FA-bypass, write access to the @_linked* scope).`);
368
- console.log(` → https://github.com/organizations/${repoSlug.split('/')[0]}/settings/secrets/actions`);
424
+ console.log(` a. Org secrets ${chalk.cyan(npmSecretName)}, ${chalk.cyan('RELEASE_APP_ID')} and ${chalk.cyan('RELEASE_APP_PRIVATE_KEY')},`);
425
+ console.log(` visible to ${chalk.cyan('All repositories')} — there is no per-repo grant step.`);
426
+ console.log(` → https://github.com/organizations/${owner}/settings/secrets/actions`);
369
427
  console.log('');
370
428
  console.log(` b. Enable "Allow GitHub Actions to create and approve pull requests":`);
371
- console.log(` → https://github.com/organizations/${repoSlug.split('/')[0]}/settings/actions`);
429
+ console.log(` → https://github.com/organizations/${owner}/settings/actions`);
372
430
  if (!configuredGithub) {
373
431
  console.log('');
374
432
  console.log(chalk.bold('Branch protection:'));
375
433
  console.log(` Rerun with ${chalk.cyan('linked setup-publish --configure-github')} to configure automatically,`);
376
- console.log(` or set manually at https://github.com/${repoSlug}/settings/branches`);
434
+ console.log(` or set manually at https://github.com/${repoSlug}/settings/branches —`);
435
+ console.log(` the required check is ${chalk.cyan('checks / Build & Test')}, not ${chalk.cyan('Build & Test')}.`);
377
436
  }
378
437
  console.log('');
379
438
  }