@kungfu-tech/buildchain 0.0.0-bootstrap.0 → 2.0.13-alpha.11

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 (35) hide show
  1. package/README.md +262 -0
  2. package/bin/buildchain.mjs +222 -0
  3. package/docs/cli.md +124 -0
  4. package/docs/lifecycle-protocol.md +422 -0
  5. package/docs/publish-transaction.md +285 -0
  6. package/docs/reusable-build-surface.md +350 -0
  7. package/docs/web-surface-deployments.md +211 -0
  8. package/package.json +52 -1
  9. package/packages/core/README.md +15 -0
  10. package/packages/core/buildchain-config.js +721 -0
  11. package/packages/core/index.js +40 -0
  12. package/packages/core/package-manager.js +291 -0
  13. package/packages/core/publish-transaction.js +418 -0
  14. package/packages/core/release-line-dry-run.js +296 -0
  15. package/scripts/aggregate-build-summary.mjs +88 -0
  16. package/scripts/build-contract-core.mjs +731 -0
  17. package/scripts/check-inventory.mjs +325 -0
  18. package/scripts/init-repo.mjs +316 -0
  19. package/scripts/npm-publish-dry-run.mjs +176 -0
  20. package/scripts/npm-publish-transaction.mjs +268 -0
  21. package/scripts/publish-source-ref-resolver.mjs +113 -0
  22. package/scripts/release-line-dry-run.mjs +53 -0
  23. package/scripts/release-line-policy.mjs +141 -0
  24. package/scripts/release-transaction.mjs +212 -0
  25. package/scripts/resolve-build-contract.mjs +63 -0
  26. package/scripts/resolve-publish-gate.mjs +33 -0
  27. package/scripts/resolve-publish-source.mjs +99 -0
  28. package/scripts/run-lifecycle-core.mjs +162 -0
  29. package/scripts/run-lifecycle.mjs +40 -0
  30. package/scripts/strip-trailing-whitespace.mjs +14 -0
  31. package/scripts/tsup-action.config.mjs +19 -0
  32. package/scripts/verify-publish-source-lock.mjs +37 -0
  33. package/scripts/verify-release-pr.mjs +34 -0
  34. package/scripts/web-surface-core.mjs +382 -0
  35. package/scripts/web-surface.mjs +112 -0
@@ -0,0 +1,325 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const sharedActionTsupConfig = fs.readFileSync(path.join(root, "scripts/tsup-action.config.mjs"), "utf8");
6
+ const commonJsSourcePattern = /\b(require\s*\(|module\.exports|exports\.|require\.main|createRequire)\b/;
7
+ const requiredPaths = [
8
+ "README.md",
9
+ "bin/buildchain.mjs",
10
+ "docs/cli.md",
11
+ "scripts/release-line-dry-run.mjs",
12
+ "scripts/npm-publish-dry-run.mjs",
13
+ "scripts/npm-publish-transaction.mjs",
14
+ "docs/migration-inventory.md",
15
+ "docs/lifecycle-protocol.md",
16
+ "docs/ownership.md",
17
+ "tests/buildchain-inventory.json",
18
+ "buildchain.toml",
19
+ ".github/actionlint.yaml",
20
+ ".github/workflows/self-hosted-runner-smoke.yml",
21
+ ".github/workflows/buildchain-ref-promotion.yml",
22
+ ".github/workflows/npm-publish.yml",
23
+ ".github/workflows/verify.yml",
24
+ ".github/workflows/.build.yml",
25
+ ".github/workflows/build-surface-fixture.yml",
26
+ ".github/workflows/candidate-lab.yml",
27
+ "fixtures/libnode-shaped/buildchain.toml",
28
+ "fixtures/libnode-shaped/package.json"
29
+ ];
30
+
31
+ for (const rel of requiredPaths) {
32
+ if (!fs.existsSync(path.join(root, rel))) {
33
+ throw new Error(`missing required path: ${rel}`);
34
+ }
35
+ }
36
+
37
+ const rootPackage = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
38
+ if (rootPackage.name !== "@kungfu-tech/buildchain") {
39
+ throw new Error("root package must be @kungfu-tech/buildchain");
40
+ }
41
+ if (rootPackage.private !== false) {
42
+ throw new Error("root package must be publishable with private=false");
43
+ }
44
+ if (rootPackage.bin?.buildchain !== "./bin/buildchain.mjs") {
45
+ throw new Error("root package must expose the buildchain CLI");
46
+ }
47
+ if (rootPackage.exports?.["."] !== "./packages/core/index.js") {
48
+ throw new Error("root package must export packages/core/index.js");
49
+ }
50
+ if (rootPackage.publishConfig?.access !== "public") {
51
+ throw new Error("root package publishConfig.access must be public");
52
+ }
53
+ if (rootPackage.publishConfig?.registry !== "https://registry.npmjs.org/") {
54
+ throw new Error("root package publishConfig.registry must be npmjs");
55
+ }
56
+ for (const expectedFile of ["bin/", "scripts/*.mjs", "packages/core/", "docs/cli.md"]) {
57
+ if (!rootPackage.files?.includes(expectedFile)) {
58
+ throw new Error(`root package files must include ${expectedFile}`);
59
+ }
60
+ }
61
+ const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8");
62
+ if (!cliSource.startsWith("#!/usr/bin/env node")) {
63
+ throw new Error("bin/buildchain.mjs must be executable with a node shebang");
64
+ }
65
+ if (commonJsSourcePattern.test(cliSource)) {
66
+ throw new Error("bin/buildchain.mjs must use ESM syntax");
67
+ }
68
+ const releaseLineDryRunScript = fs.readFileSync(path.join(root, "scripts/release-line-dry-run.mjs"), "utf8");
69
+ for (const requiredSnippet of [
70
+ "explainReleaseLineDryRun",
71
+ "formatReleaseLineDryRun",
72
+ "--target-ref <ref>",
73
+ ]) {
74
+ if (!releaseLineDryRunScript.includes(requiredSnippet)) {
75
+ throw new Error(`release line dry-run script missing required snippet: ${requiredSnippet}`);
76
+ }
77
+ }
78
+ if (commonJsSourcePattern.test(releaseLineDryRunScript)) {
79
+ throw new Error("scripts/release-line-dry-run.mjs must use ESM syntax");
80
+ }
81
+ const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/npm-publish.yml"), "utf8");
82
+ const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/buildchain-ref-promotion.yml"), "utf8");
83
+ const npmDryRunScript = fs.readFileSync(path.join(root, "scripts/npm-publish-dry-run.mjs"), "utf8");
84
+ const npmPublishTransactionScript = fs.readFileSync(path.join(root, "scripts/npm-publish-transaction.mjs"), "utf8");
85
+ for (const requiredSnippet of [
86
+ "runs-on: ubuntu-24.04",
87
+ "workflow_dispatch:",
88
+ "Dry-run npm publish",
89
+ ]) {
90
+ if (!npmPublishWorkflow.includes(requiredSnippet)) {
91
+ throw new Error(`npm publish workflow missing required snippet: ${requiredSnippet}`);
92
+ }
93
+ }
94
+ for (const forbiddenSnippet of [
95
+ "tags:",
96
+ "Publish exact release tag",
97
+ "npm publish --access public --tag",
98
+ ]) {
99
+ if (npmPublishWorkflow.includes(forbiddenSnippet)) {
100
+ throw new Error(`npm publish dry-run workflow must not contain real publish snippet: ${forbiddenSnippet}`);
101
+ }
102
+ }
103
+ for (const requiredSnippet of [
104
+ "id-token: write",
105
+ "registry-url: \"https://registry.npmjs.org/\"",
106
+ "publish-transaction: \"true\"",
107
+ ]) {
108
+ if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
109
+ throw new Error(`buildchain ref promotion workflow missing npm transaction snippet: ${requiredSnippet}`);
110
+ }
111
+ }
112
+ for (const requiredSnippet of [
113
+ "distTag || (pkg.version.includes(\"-\") ? \"alpha\" : \"latest\")",
114
+ "\"publish\", \"--dry-run\", \"--access\", \"public\"",
115
+ "expectedTag && expectedTag !== exactTag",
116
+ ]) {
117
+ if (!npmDryRunScript.includes(requiredSnippet)) {
118
+ throw new Error(`npm publish dry-run script missing required snippet: ${requiredSnippet}`);
119
+ }
120
+ }
121
+ if (commonJsSourcePattern.test(npmDryRunScript)) {
122
+ throw new Error("scripts/npm-publish-dry-run.mjs must use ESM syntax");
123
+ }
124
+ for (const requiredSnippet of [
125
+ "BUILDCHAIN_PUBLISH_EVIDENCE",
126
+ "\"publish\", \"--access\", access, \"--tag\", distTag",
127
+ "artifact digest mismatch",
128
+ ]) {
129
+ if (!npmPublishTransactionScript.includes(requiredSnippet)) {
130
+ throw new Error(`npm publish transaction script missing required snippet: ${requiredSnippet}`);
131
+ }
132
+ }
133
+ if (commonJsSourcePattern.test(npmPublishTransactionScript)) {
134
+ throw new Error("scripts/npm-publish-transaction.mjs must use ESM syntax");
135
+ }
136
+ if (/runs-on:\s*self-hosted/.test(npmPublishWorkflow)) {
137
+ throw new Error("npm publish workflow must use GitHub-hosted runners for trusted publishing");
138
+ }
139
+
140
+ const inventory = JSON.parse(
141
+ fs.readFileSync(path.join(root, "tests/buildchain-inventory.json"), "utf8")
142
+ );
143
+
144
+ if (inventory.schemaVersion !== 2) {
145
+ throw new Error("inventory schemaVersion must be 2");
146
+ }
147
+
148
+ if (inventory.release !== "buildchain-v2") {
149
+ throw new Error("inventory release must be buildchain-v2");
150
+ }
151
+
152
+ if (inventory.stableRefs?.actions !== "kungfu-systems/buildchain/actions/<name>@v2") {
153
+ throw new Error("inventory stable action ref must point at @v2");
154
+ }
155
+
156
+ if (inventory.stableRefs?.workflows !== "kungfu-systems/buildchain/.github/workflows/<workflow>.yml@v2") {
157
+ throw new Error("inventory stable workflow ref must point at @v2");
158
+ }
159
+
160
+ if (!Array.isArray(inventory.workflowSources) || inventory.workflowSources.length < 1) {
161
+ throw new Error("workflowSources must include the migrated workflows repository");
162
+ }
163
+
164
+ if (!Array.isArray(inventory.retiredActionsExcluded) || inventory.retiredActionsExcluded.length === 0) {
165
+ throw new Error("retiredActionsExcluded must list retired legacy actions");
166
+ }
167
+
168
+ const actualActions = fs
169
+ .readdirSync(path.join(root, "actions"), { withFileTypes: true })
170
+ .filter((entry) => entry.isDirectory())
171
+ .map((entry) => entry.name)
172
+ .filter((name) => fs.existsSync(path.join(root, "actions", name, "action.yml")))
173
+ .sort();
174
+ const internalActions = Array.isArray(inventory.internalActions) ? inventory.internalActions : [];
175
+ if (!Array.isArray(inventory.migratedActions) || inventory.migratedActions.length !== 0) {
176
+ throw new Error("migratedActions must be empty; buildchain v2 only ships native actions");
177
+ }
178
+ const shippedActions = internalActions;
179
+ const shippedActionNames = shippedActions.map((action) => action.path.replace(/^actions\//, "")).sort();
180
+
181
+ if (JSON.stringify(actualActions) !== JSON.stringify(shippedActionNames)) {
182
+ throw new Error(
183
+ `action inventory mismatch. actual=${actualActions.join(",")} inventory=${shippedActionNames.join(",")}`
184
+ );
185
+ }
186
+
187
+ for (const retiredRepo of inventory.retiredActionsExcluded || []) {
188
+ const retiredPath = path.join(root, "actions", retiredRepo.replace(/^action-/, ""));
189
+ if (fs.existsSync(retiredPath)) {
190
+ throw new Error(`retired action must not be shipped in buildchain v2: ${retiredRepo}`);
191
+ }
192
+ }
193
+
194
+ for (const action of shippedActions) {
195
+ for (const key of ["path", "runtime", "build", "bundle"]) {
196
+ if (!action[key]) {
197
+ throw new Error(`migrated action entry is missing ${key}`);
198
+ }
199
+ }
200
+ if (action.runtime !== "node24") {
201
+ throw new Error(`${action.path} must use node24`);
202
+ }
203
+ if (action.build !== "tsup") {
204
+ throw new Error(`${action.path} must build with tsup`);
205
+ }
206
+ const actionPath = path.join(root, action.path);
207
+ const actionYmlPath = path.join(actionPath, "action.yml");
208
+ const packageJsonPath = path.join(actionPath, "package.json");
209
+ const tsupConfigPath = path.join(actionPath, "tsup.config.mjs");
210
+ const bundlePath = path.join(root, action.bundle);
211
+ for (const required of [actionYmlPath, packageJsonPath, bundlePath, path.join(actionPath, "README.md")]) {
212
+ if (!fs.existsSync(required)) {
213
+ throw new Error(`missing action artifact: ${path.relative(root, required)}`);
214
+ }
215
+ }
216
+ const actionYml = fs.readFileSync(actionYmlPath, "utf8");
217
+ if (!/using:\s*["']node24["']/.test(actionYml)) {
218
+ throw new Error(`${action.path}/action.yml must use node24`);
219
+ }
220
+ if (!/main:\s*["']dist\/index\.js["']/.test(actionYml)) {
221
+ throw new Error(`${action.path}/action.yml must point at dist/index.js`);
222
+ }
223
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
224
+ const bundleContent = fs.readFileSync(bundlePath, "utf8");
225
+ if (packageJson.type !== "module") {
226
+ throw new Error(`${action.path}/package.json must set type=module`);
227
+ }
228
+ if (!packageJson.scripts?.build?.includes("tsup ")) {
229
+ throw new Error(`${action.path}/package.json must define a tsup build`);
230
+ }
231
+ if (!packageJson.scripts.build.includes("--format esm")) {
232
+ throw new Error(`${action.path}/package.json build must emit ESM`);
233
+ }
234
+ if (!packageJson.scripts.build.includes("tsup-action.config.mjs")) {
235
+ throw new Error(`${action.path}/package.json build must use the shared action tsup config`);
236
+ }
237
+ const tsupConfig = fs.existsSync(tsupConfigPath) ? fs.readFileSync(tsupConfigPath, "utf8") : "";
238
+ if (
239
+ !packageJson.scripts.build.includes("--target node24") &&
240
+ !/target:\s*["']node24["']/.test(tsupConfig) &&
241
+ !/target:\s*["']node24["']/.test(sharedActionTsupConfig)
242
+ ) {
243
+ throw new Error(`${action.path}/package.json build must target node24`);
244
+ }
245
+ if (/(from|import)\s*["']@actions\//.test(bundleContent) || /require\(["']@actions\//.test(bundleContent)) {
246
+ throw new Error(`${action.path}/dist/index.js must bundle @actions dependencies`);
247
+ }
248
+ // Bundled third-party dependencies may still be wrapped with esbuild helper
249
+ // shims. The ESM contract is enforced at the action source and package
250
+ // boundary instead of by banning helper text inside generated bundles.
251
+ const sourceFiles = collectFiles(actionPath, [".js", ".ts", ".mjs"], ["dist"]);
252
+ for (const sourceFile of sourceFiles) {
253
+ const source = fs.readFileSync(sourceFile, "utf8");
254
+ if (commonJsSourcePattern.test(source)) {
255
+ throw new Error(`${path.relative(root, sourceFile)} must use ESM syntax`);
256
+ }
257
+ }
258
+ }
259
+
260
+ for (const file of collectFiles(path.join(root, "packages"), [".cjs"], [])) {
261
+ throw new Error(`CommonJS core package file is not allowed: ${path.relative(root, file)}`);
262
+ }
263
+
264
+ const safety = inventory.safety || {};
265
+ for (const key of ["forkPrSecretsDefault", "selfHostedRunnerDefault"]) {
266
+ if (safety[key] !== false) {
267
+ throw new Error(`safety.${key} must be false`);
268
+ }
269
+ }
270
+ if (safety.trustedEventGate !== true) {
271
+ throw new Error("safety.trustedEventGate must be true");
272
+ }
273
+ if (safety.reusableContract?.publishGateChannels !== true) {
274
+ throw new Error("safety.reusableContract.publishGateChannels must be true");
275
+ }
276
+ for (const key of ["publishGateSourceLock", "resolvedReleaseManifest", "packageSetPublishPlan"]) {
277
+ if (safety.reusableContract?.[key] !== true) {
278
+ throw new Error(`safety.reusableContract.${key} must be true`);
279
+ }
280
+ }
281
+ if (safety.npmPublish?.promotionTransaction !== true) {
282
+ throw new Error("safety.npmPublish.promotionTransaction must be true");
283
+ }
284
+ if (safety.npmPublish?.tagPushPublish !== false) {
285
+ throw new Error("safety.npmPublish.tagPushPublish must be false");
286
+ }
287
+ if (safety.npmPublish?.exactVersionsOnly !== true) {
288
+ throw new Error("safety.npmPublish.exactVersionsOnly must be true");
289
+ }
290
+ if (safety.npmPublish?.alphaDistTag !== "alpha") {
291
+ throw new Error("safety.npmPublish.alphaDistTag must be alpha");
292
+ }
293
+ if (safety.npmPublish?.stableDistTag !== "latest") {
294
+ throw new Error("safety.npmPublish.stableDistTag must be latest");
295
+ }
296
+ if (safety.npmPublish?.trustedPublishing !== true) {
297
+ throw new Error("safety.npmPublish.trustedPublishing must be true");
298
+ }
299
+ if (safety.npmPublish?.manualDryRun !== true) {
300
+ throw new Error("safety.npmPublish.manualDryRun must be true");
301
+ }
302
+ if (safety.npmPublish?.manualDryRunPublishes !== false) {
303
+ throw new Error("safety.npmPublish.manualDryRunPublishes must be false");
304
+ }
305
+
306
+ console.log("buildchain inventory check passed");
307
+
308
+ function collectFiles(dir, extensions, excludedDirNames) {
309
+ if (!fs.existsSync(dir)) {
310
+ return [];
311
+ }
312
+ const files = [];
313
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
314
+ if (excludedDirNames.includes(entry.name)) {
315
+ continue;
316
+ }
317
+ const item = path.join(dir, entry.name);
318
+ if (entry.isDirectory()) {
319
+ files.push(...collectFiles(item, extensions, excludedDirNames));
320
+ } else if (extensions.some((extension) => item.endsWith(extension))) {
321
+ files.push(item);
322
+ }
323
+ }
324
+ return files;
325
+ }
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { detectPackageManager, assertPackageManager } from "../packages/core/package-manager.js";
6
+
7
+ const BUILDCHAIN_WORKFLOW_REF = "kungfu-systems/buildchain/.github/workflows/.build.yml@v2";
8
+
9
+ function posixPath(value) {
10
+ return String(value || "").split(path.sep).join("/");
11
+ }
12
+
13
+ function repoName(cwd) {
14
+ return path.basename(path.resolve(cwd));
15
+ }
16
+
17
+ function detectOrDefaultPackageManager(cwd, requested) {
18
+ if (requested) {
19
+ return assertPackageManager(requested);
20
+ }
21
+ try {
22
+ return detectPackageManager(cwd).name;
23
+ } catch {
24
+ return "pnpm";
25
+ }
26
+ }
27
+
28
+ function packageLifecycle(manager) {
29
+ if (manager === "npm") {
30
+ return {
31
+ install: "npm ci",
32
+ build: "npm run build",
33
+ verify: "npm run check",
34
+ };
35
+ }
36
+ if (manager === "yarn") {
37
+ return {
38
+ install: "corepack yarn install --immutable",
39
+ build: "corepack yarn run build",
40
+ verify: "corepack yarn run check",
41
+ };
42
+ }
43
+ return {
44
+ install: "corepack pnpm install --frozen-lockfile",
45
+ build: "corepack pnpm run build",
46
+ verify: "corepack pnpm run check",
47
+ };
48
+ }
49
+
50
+ function packageToml(cwd, manager) {
51
+ const lifecycle = packageLifecycle(manager);
52
+ const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
53
+ const versionFiles = hasPackageJson
54
+ ? `
55
+ [[version.files]]
56
+ type = "json"
57
+ path = "package.json"
58
+ key = "version"
59
+ `
60
+ : "";
61
+ return `schema = 1
62
+
63
+ [project]
64
+ type = "package"
65
+ name = "${repoName(cwd)}"
66
+
67
+ [version]
68
+ required = ${hasPackageJson ? "true" : "false"}
69
+ strategy = "semver"
70
+ next = "auto"
71
+ ${versionFiles}
72
+ [lifecycle.install]
73
+ command = "${lifecycle.install}"
74
+
75
+ [lifecycle.build]
76
+ command = "${lifecycle.build}"
77
+
78
+ [lifecycle.verify]
79
+ command = "${lifecycle.verify}"
80
+ `;
81
+ }
82
+
83
+ function nativeToml(cwd) {
84
+ return `schema = 1
85
+
86
+ [project]
87
+ type = "package"
88
+ name = "${repoName(cwd)}"
89
+
90
+ [version]
91
+ required = false
92
+ strategy = "semver"
93
+ next = "auto"
94
+
95
+ [[version.files]]
96
+ type = "regex"
97
+ path = "CMakeLists.txt"
98
+ pattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'
99
+ replacement = '\${version}'
100
+
101
+ [lifecycle.configure]
102
+ commands = [
103
+ "cmake -S . -B build -DCMAKE_BUILD_TYPE=Release",
104
+ ]
105
+
106
+ [lifecycle.build]
107
+ commands = [
108
+ "cmake --build build --config Release",
109
+ ]
110
+
111
+ [lifecycle.verify]
112
+ commands = [
113
+ "ctest --test-dir build --output-on-failure",
114
+ ]
115
+ `;
116
+ }
117
+
118
+ function anchoredPackageToml(cwd, manager) {
119
+ const lifecycle = packageLifecycle(manager);
120
+ return `schema = 1
121
+
122
+ [project]
123
+ type = "package"
124
+ name = "${repoName(cwd)}"
125
+
126
+ [version]
127
+ required = true
128
+ strategy = "anchored"
129
+ next = "manual"
130
+ manifest = "release.json"
131
+
132
+ [[version.files]]
133
+ type = "json"
134
+ path = "package.json"
135
+ key = "version"
136
+
137
+ [lifecycle.install]
138
+ command = "${lifecycle.install}"
139
+
140
+ [lifecycle.build]
141
+ command = "${lifecycle.build}"
142
+
143
+ [lifecycle.verify]
144
+ command = "${lifecycle.verify}"
145
+ `;
146
+ }
147
+
148
+ function webSurfaceToml(cwd) {
149
+ const name = repoName(cwd);
150
+ return `schema = 1
151
+
152
+ [project]
153
+ type = "web-surface"
154
+ name = "${name}"
155
+ site = "${name}"
156
+
157
+ [channels.preview]
158
+ url_pattern = "https://{alias}.preview.example.com"
159
+ visibility = "ephemeral"
160
+ requires_auth = false
161
+ noindex = true
162
+
163
+ [channels.staging]
164
+ url = "https://staging.example.com"
165
+ visibility = "protected"
166
+ requires_auth = true
167
+ noindex = true
168
+ promotable = true
169
+
170
+ [channels.production]
171
+ url = "https://example.com"
172
+ visibility = "public"
173
+ requires_auth = false
174
+ noindex = false
175
+ canonical = true
176
+
177
+ [deploy.preview]
178
+ adapter = "aws-s3-cloudfront"
179
+ artifact_path = "dist"
180
+ bucket_ref = "AWS_PREVIEW_BUCKET"
181
+ distribution_ref = "AWS_PREVIEW_DISTRIBUTION"
182
+
183
+ [deploy.staging]
184
+ adapter = "aws-s3-cloudfront"
185
+ artifact_path = "dist"
186
+ bucket_ref = "AWS_STAGING_BUCKET"
187
+ distribution_ref = "AWS_STAGING_DISTRIBUTION"
188
+
189
+ [deploy.production]
190
+ adapter = "aws-s3-cloudfront"
191
+ artifact_path = "dist"
192
+ bucket_ref = "AWS_PRODUCTION_BUCKET"
193
+ distribution_ref = "AWS_PRODUCTION_DISTRIBUTION"
194
+
195
+ [security.staging]
196
+ requires_auth = true
197
+ noindex = true
198
+ isolated_providers = true
199
+
200
+ [lifecycle.build]
201
+ command = "corepack pnpm run build"
202
+
203
+ [lifecycle.verify]
204
+ command = "corepack pnpm run check"
205
+ `;
206
+ }
207
+
208
+ function workflowYaml({ runnerPreset, artifactName }) {
209
+ return `name: Build
210
+
211
+ on:
212
+ pull_request:
213
+ push:
214
+ branches:
215
+ - "dev/**"
216
+ - "alpha/**"
217
+ - "release/**"
218
+ - "publish-gate/**"
219
+
220
+ permissions:
221
+ contents: read
222
+
223
+ jobs:
224
+ build:
225
+ uses: ${BUILDCHAIN_WORKFLOW_REF}
226
+ with:
227
+ working-directory: "."
228
+ runner-preset: "${runnerPreset}"
229
+ artifact-name-template: "${artifactName}"
230
+ artifact-paths: |
231
+ dist
232
+ build/stage
233
+ `;
234
+ }
235
+
236
+ function writeIfAllowed(filePath, content, { force }) {
237
+ if (fs.existsSync(filePath) && !force) {
238
+ throw new Error(`${posixPath(filePath)} already exists; pass --force to overwrite`);
239
+ }
240
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
241
+ fs.writeFileSync(filePath, content);
242
+ return filePath;
243
+ }
244
+
245
+ export function initBuildchainRepo({
246
+ cwd = process.cwd(),
247
+ type = "package",
248
+ force = false,
249
+ packageManager = "",
250
+ runnerPreset = "github-hosted",
251
+ artifactName = "{repo}-{version}-{platform}",
252
+ } = {}) {
253
+ const resolvedCwd = path.resolve(cwd);
254
+ const manager = detectOrDefaultPackageManager(resolvedCwd, packageManager);
255
+ const toml = (() => {
256
+ if (type === "package") {
257
+ return packageToml(resolvedCwd, manager);
258
+ }
259
+ if (type === "native") {
260
+ return nativeToml(resolvedCwd);
261
+ }
262
+ if (type === "web-surface") {
263
+ return webSurfaceToml(resolvedCwd);
264
+ }
265
+ if (type === "anchored-package") {
266
+ return anchoredPackageToml(resolvedCwd, manager);
267
+ }
268
+ throw new Error("init --type must be one of package, native, web-surface, or anchored-package");
269
+ })();
270
+
271
+ const written = [
272
+ writeIfAllowed(path.join(resolvedCwd, "buildchain.toml"), toml, { force }),
273
+ writeIfAllowed(path.join(resolvedCwd, ".github", "workflows", "build.yml"), workflowYaml({
274
+ runnerPreset,
275
+ artifactName,
276
+ }), { force }),
277
+ ];
278
+
279
+ if (type === "anchored-package" && !fs.existsSync(path.join(resolvedCwd, "release.json"))) {
280
+ written.push(writeIfAllowed(path.join(resolvedCwd, "release.json"), "{\n \"upstream\": \"\",\n \"version\": \"0.0.0\"\n}\n", { force }));
281
+ }
282
+
283
+ return {
284
+ schemaVersion: 1,
285
+ type,
286
+ cwd: resolvedCwd,
287
+ packageManager: manager,
288
+ workflowRef: BUILDCHAIN_WORKFLOW_REF,
289
+ written: written.map((filePath) => posixPath(path.relative(resolvedCwd, filePath))),
290
+ };
291
+ }
292
+
293
+ function readArg(name, fallback = "") {
294
+ const index = process.argv.indexOf(`--${name}`);
295
+ if (index === -1) {
296
+ return fallback;
297
+ }
298
+ return process.argv[index + 1] || "";
299
+ }
300
+
301
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
302
+ try {
303
+ const result = initBuildchainRepo({
304
+ cwd: readArg("cwd", process.cwd()),
305
+ type: readArg("type", "package"),
306
+ force: process.argv.includes("--force"),
307
+ packageManager: readArg("package-manager", ""),
308
+ runnerPreset: readArg("runner-preset", "github-hosted"),
309
+ artifactName: readArg("artifact-name", "{repo}-{version}-{platform}"),
310
+ });
311
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
312
+ } catch (error) {
313
+ console.error(`init-repo: ${error.message}`);
314
+ process.exitCode = 1;
315
+ }
316
+ }