everything-dev 2.0.0-rc.0 → 2.0.0-rc.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/dist/auth-login.cjs +1 -1
- package/dist/auth-login.mjs +1 -1
- package/dist/build.cjs +2 -2
- package/dist/build.mjs +2 -2
- package/dist/bundle-fs-resolve.cjs +9 -17
- package/dist/bundle-fs-resolve.cjs.map +1 -1
- package/dist/bundle-fs-resolve.d.cts +6 -0
- package/dist/bundle-fs-resolve.d.mts +6 -0
- package/dist/bundle-fs-resolve.mjs +9 -17
- package/dist/bundle-fs-resolve.mjs.map +1 -1
- package/dist/bundle-path.cjs +48 -0
- package/dist/bundle-path.cjs.map +1 -0
- package/dist/bundle-path.mjs +45 -0
- package/dist/bundle-path.mjs.map +1 -0
- package/dist/cli/init.cjs +13 -13
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.mjs +13 -13
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/status.cjs +69 -3
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.mjs +67 -4
- package/dist/cli/status.mjs.map +1 -1
- package/dist/cli/sync.cjs +1 -1
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +1 -1
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli/upgrade.cjs +2 -2
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +2 -2
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/cli.cjs +5 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +5 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/deploy.cjs +26 -16
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +26 -16
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/init.cjs +1 -1
- package/dist/commands/init.cjs.map +1 -1
- package/dist/commands/init.mjs +1 -1
- package/dist/commands/init.mjs.map +1 -1
- package/dist/commands/plugins.cjs +2 -2
- package/dist/commands/plugins.cjs.map +1 -1
- package/dist/commands/plugins.mjs +2 -2
- package/dist/commands/plugins.mjs.map +1 -1
- package/dist/commands/upgrade.cjs +1 -1
- package/dist/commands/upgrade.mjs +1 -1
- package/dist/components/dev-render.cjs +1 -1
- package/dist/components/dev-render.mjs +1 -1
- package/dist/config.cjs +3 -3
- package/dist/config.cjs.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +2 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +3 -0
- package/dist/contract.d.mts +3 -0
- package/dist/contract.meta.cjs +2 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +4 -1
- package/dist/contract.meta.d.mts +4 -1
- package/dist/contract.meta.mjs +2 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +2 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/db/driver.cjs +22 -2
- package/dist/db/driver.cjs.map +1 -1
- package/dist/db/driver.d.cts +13 -2
- package/dist/db/driver.d.mts +13 -2
- package/dist/db/driver.mjs +22 -3
- package/dist/db/driver.mjs.map +1 -1
- package/dist/db/index.cjs +1 -0
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +2 -2
- package/dist/deploy-diff.cjs +1 -1
- package/dist/deploy-diff.cjs.map +1 -1
- package/dist/deploy-diff.mjs +1 -1
- package/dist/deploy-diff.mjs.map +1 -1
- package/dist/dev-program.cjs +10 -2
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +10 -2
- package/dist/dev-program.mjs.map +1 -1
- package/dist/image-deploy.cjs +65 -17
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +64 -18
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/internal/manifest-normalizer.cjs.map +1 -1
- package/dist/internal/manifest-normalizer.mjs.map +1 -1
- package/dist/plugin.d.cts +902 -1
- package/dist/plugin.d.mts +902 -1
- package/dist/publish.cjs +51 -2
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +51 -4
- package/dist/publish.mjs.map +1 -1
- package/dist/resolution/session.cjs.map +1 -1
- package/dist/resolution/session.d.cts +5 -5
- package/dist/resolution/session.d.mts +5 -5
- package/dist/resolution/session.mjs.map +1 -1
- package/dist/types.d.cts +31 -1
- package/dist/types.d.mts +31 -1
- package/dist/ui/auth-guards.d.cts +78 -2
- package/dist/ui/auth-guards.d.mts +78 -2
- package/dist/ui/auth.cjs.map +1 -1
- package/dist/ui/auth.d.cts +4133 -7
- package/dist/ui/auth.d.mts +4133 -7
- package/dist/ui/auth.mjs.map +1 -1
- package/dist/ui/hydrate.cjs +2 -2
- package/dist/ui/hydrate.cjs.map +1 -1
- package/dist/ui/hydrate.mjs +2 -2
- package/dist/ui/hydrate.mjs.map +1 -1
- package/dist/utils/theme.cjs +2 -2
- package/dist/utils/theme.cjs.map +1 -1
- package/dist/utils/theme.mjs +2 -2
- package/dist/utils/theme.mjs.map +1 -1
- package/dist/version-manifest-resolve.cjs +55 -10
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.mjs +53 -10
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/dist/workspace.cjs +26 -13
- package/dist/workspace.cjs.map +1 -1
- package/dist/workspace.mjs +26 -14
- package/dist/workspace.mjs.map +1 -1
- package/package.json +11 -10
package/dist/image-deploy.cjs
CHANGED
|
@@ -58,21 +58,26 @@ function readFrameworkVersion(configDir) {
|
|
|
58
58
|
* Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a
|
|
59
59
|
* floating major tag (`v2`) on stable releases only. `latest` is held during
|
|
60
60
|
* prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to
|
|
61
|
-
* `:latest` keep serving the last stable image
|
|
61
|
+
* `:latest` keep serving the last stable image — and `:rc` is the floating
|
|
62
|
+
* ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on
|
|
63
|
+
* every prerelease deploy and freezes at the last rc-train build once the
|
|
64
|
+
* workspace releases a stable version.
|
|
62
65
|
*/
|
|
63
66
|
function computeImageTags(input) {
|
|
64
67
|
const tags = [`sha-${input.shortSha}`];
|
|
65
68
|
const match = input.version?.trim().match(SEMVER_PATTERN);
|
|
66
69
|
if (!match) return {
|
|
67
70
|
tags,
|
|
68
|
-
pushLatest: true
|
|
71
|
+
pushLatest: true,
|
|
72
|
+
pushRc: false
|
|
69
73
|
};
|
|
70
74
|
const [, major, minor, patch, prerelease] = match;
|
|
71
75
|
const exact = prerelease ? `v${major}.${minor}.${patch}-${prerelease}` : `v${major}.${minor}.${patch}`;
|
|
72
76
|
if (prerelease) return {
|
|
73
77
|
tags: [...tags, exact],
|
|
74
78
|
pushLatest: false,
|
|
75
|
-
latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image
|
|
79
|
+
latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,
|
|
80
|
+
pushRc: true
|
|
76
81
|
};
|
|
77
82
|
return {
|
|
78
83
|
tags: [
|
|
@@ -80,14 +85,49 @@ function computeImageTags(input) {
|
|
|
80
85
|
exact,
|
|
81
86
|
`v${major}`
|
|
82
87
|
],
|
|
83
|
-
pushLatest: true
|
|
88
|
+
pushLatest: true,
|
|
89
|
+
pushRc: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
93
|
+
/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */
|
|
94
|
+
function isValidImageDigest(digest) {
|
|
95
|
+
return DIGEST_PATTERN.test(digest);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decide the image leg's source: a digest handed over from a pre-pushed image
|
|
99
|
+
* (the CI image job) wins; otherwise the local docker build runs when the
|
|
100
|
+
* toolchain and Dockerfile are present (ADR 0021, as amended).
|
|
101
|
+
*/
|
|
102
|
+
function resolveDeployImagePlan(input) {
|
|
103
|
+
if (!input.imageRef) return {
|
|
104
|
+
kind: "skip",
|
|
105
|
+
reason: "set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image"
|
|
106
|
+
};
|
|
107
|
+
if (input.imageDigest) return {
|
|
108
|
+
kind: "prebuilt",
|
|
109
|
+
image: input.imageRef.image,
|
|
110
|
+
digest: input.imageDigest
|
|
111
|
+
};
|
|
112
|
+
if (!input.hasDocker) return {
|
|
113
|
+
kind: "skip",
|
|
114
|
+
reason: "docker is not available"
|
|
115
|
+
};
|
|
116
|
+
if (!input.hasDockerfile) return {
|
|
117
|
+
kind: "skip",
|
|
118
|
+
reason: "no Dockerfile at the config root"
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
kind: "build",
|
|
122
|
+
image: input.imageRef.image
|
|
84
123
|
};
|
|
85
124
|
}
|
|
86
125
|
/**
|
|
87
126
|
* Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version
|
|
88
|
-
* tags, push them, capture the digest,
|
|
89
|
-
* is a prerelease
|
|
90
|
-
* configured (child repos get
|
|
127
|
+
* tags, push them, capture the digest, then push the floating refs — `:latest`
|
|
128
|
+
* unless the version is a prerelease, and `:rc` when it is. Callers skip the
|
|
129
|
+
* leg with a notice when no image is configured (child repos get
|
|
130
|
+
* build+upload+publish only).
|
|
91
131
|
*/
|
|
92
132
|
async function buildAndPushImage(input) {
|
|
93
133
|
const shaResult = await require_run.run("git", [
|
|
@@ -103,13 +143,15 @@ async function buildAndPushImage(input) {
|
|
|
103
143
|
const detail = shaResult?.stderr.trim();
|
|
104
144
|
throw new Error(`Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? "unknown"}${detail ? `: ${detail}` : ""})`);
|
|
105
145
|
}
|
|
106
|
-
const { tags, pushLatest, latestHoldReason } = computeImageTags({
|
|
146
|
+
const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({
|
|
107
147
|
shortSha,
|
|
108
148
|
version: readFrameworkVersion(input.configDir)
|
|
109
149
|
});
|
|
110
150
|
const refs = tags.map((tag) => `${input.image}:${tag}`);
|
|
111
151
|
const latestRef = `${input.image}:latest`;
|
|
112
|
-
const
|
|
152
|
+
const rcRef = `${input.image}:rc`;
|
|
153
|
+
const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];
|
|
154
|
+
if (pushRc) buildRefs.push(rcRef);
|
|
113
155
|
console.log(` Building runtime image ${require_theme.colors.cyan(buildRefs.join(", "))}${pushLatest ? "" : require_theme.colors.yellow(" (latest held)")}`);
|
|
114
156
|
await require_run.run("docker", [
|
|
115
157
|
"build",
|
|
@@ -129,23 +171,27 @@ async function buildAndPushImage(input) {
|
|
|
129
171
|
digest ??= parseDigest(push.stdout);
|
|
130
172
|
if (input.verbose && push.stdout.trim()) console.log(require_theme.colors.dim(push.stdout.trim()));
|
|
131
173
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const pushLatestRef = await require_run.run("docker", ["push", latestRef], {
|
|
174
|
+
const pushFloating = async (ref) => {
|
|
175
|
+
const push = await require_run.run("docker", ["push", ref], {
|
|
135
176
|
cwd: input.configDir,
|
|
136
177
|
capture: true
|
|
137
178
|
});
|
|
138
|
-
if (!
|
|
139
|
-
digest ??= parseDigest(
|
|
140
|
-
if (input.verbose &&
|
|
141
|
-
|
|
142
|
-
}
|
|
179
|
+
if (!push || push.exitCode !== 0) throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? "unknown"}`);
|
|
180
|
+
digest ??= parseDigest(push.stdout);
|
|
181
|
+
if (input.verbose && push.stdout.trim()) console.log(require_theme.colors.dim(push.stdout.trim()));
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
let latestPushed = false;
|
|
185
|
+
if (pushLatest) latestPushed = await pushFloating(latestRef);
|
|
186
|
+
else console.log(require_theme.colors.yellow(` latest held: ${latestHoldReason}`));
|
|
187
|
+
const rcPushed = pushRc ? await pushFloating(rcRef) : false;
|
|
143
188
|
if (!digest) console.log(require_theme.colors.yellow(" Could not capture the image digest from push output"));
|
|
144
189
|
return {
|
|
145
190
|
image: input.image,
|
|
146
191
|
tag: tags[0],
|
|
147
192
|
tags,
|
|
148
193
|
latestPushed,
|
|
194
|
+
rcPushed,
|
|
149
195
|
digest
|
|
150
196
|
};
|
|
151
197
|
}
|
|
@@ -182,5 +228,7 @@ exports.buildAndPushImage = buildAndPushImage;
|
|
|
182
228
|
exports.computeImageTags = computeImageTags;
|
|
183
229
|
exports.deployImageToRailway = deployImageToRailway;
|
|
184
230
|
exports.hasDocker = hasDocker;
|
|
231
|
+
exports.isValidImageDigest = isValidImageDigest;
|
|
232
|
+
exports.resolveDeployImagePlan = resolveDeployImagePlan;
|
|
185
233
|
exports.resolveImageRef = resolveImageRef;
|
|
186
234
|
//# sourceMappingURL=image-deploy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-deploy.cjs","names":["run","readFileSync","join","colors","icons"],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n digest?: string;\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, and push `:latest` unless the version\n * is a prerelease. Callers skip the leg with a notice when no image is\n * configured (child repos get build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason } = computeImageTags({ shortSha, version });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const buildRefs = pushLatest ? [...refs, latestRef] : refs;\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n let latestPushed = false;\n if (pushLatest) {\n const pushLatestRef = await run(\"docker\", [\"push\", latestRef], {\n cwd: input.configDir,\n capture: true,\n });\n if (!pushLatestRef || pushLatestRef.exitCode !== 0) {\n throw new Error(\n `docker push ${latestRef} failed with exit code ${pushLatestRef?.exitCode ?? \"unknown\"}`,\n );\n }\n digest ??= parseDigest(pushLatestRef.stdout);\n if (input.verbose && pushLatestRef.stdout.trim()) {\n console.log(colors.dim(pushLatestRef.stdout.trim()));\n }\n latestPushed = true;\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0], tags, latestPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021): pull-only deploy of the pushed image digest — a\n * thin `FROM image@digest` Dockerfile pins the digest so Railway never\n * rebuilds; `RAILWAY_DOCKERFILE_PATH` points at the generated file.\n */\nexport async function deployImageToRailway(input: {\n image: string;\n digest: string;\n service: string;\n configDir: string;\n}): Promise<void> {\n const deployDir = join(input.configDir, \".bos\", \"deploy\");\n mkdirSync(deployDir, { recursive: true });\n const dockerfilePath = join(deployDir, \"Dockerfile\");\n writeFileSync(dockerfilePath, `FROM ${input.image}@${input.digest}\\n`);\n\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (pull-only)...`);\n const result = await run(\"railway\", [\"up\", \"--service\", input.service, \"--ci\"], {\n cwd: input.configDir,\n capture: true,\n env: { RAILWAY_DOCKERFILE_PATH: \".bos/deploy/Dockerfile\" },\n });\n if (result?.stdout) {\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n }\n if (!result || result.exitCode !== 0) {\n throw new Error(\n result?.stderr.trim()\n ? `railway up failed: ${result.stderr.trim().split(\"\\n\").slice(-3).join(\" \")}`\n : `railway up failed with exit code ${result?.exitCode ?? \"unknown\"}`,\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image-deploy.cjs","names":["run","readFileSync","join","colors","icons"],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021): pull-only deploy of the pushed image digest — a\n * thin `FROM image@digest` Dockerfile pins the digest so Railway never\n * rebuilds; `RAILWAY_DOCKERFILE_PATH` points at the generated file.\n */\nexport async function deployImageToRailway(input: {\n image: string;\n digest: string;\n service: string;\n configDir: string;\n}): Promise<void> {\n const deployDir = join(input.configDir, \".bos\", \"deploy\");\n mkdirSync(deployDir, { recursive: true });\n const dockerfilePath = join(deployDir, \"Dockerfile\");\n writeFileSync(dockerfilePath, `FROM ${input.image}@${input.digest}\\n`);\n\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (pull-only)...`);\n const result = await run(\"railway\", [\"up\", \"--service\", input.service, \"--ci\"], {\n cwd: input.configDir,\n capture: true,\n env: { RAILWAY_DOCKERFILE_PATH: \".bos/deploy/Dockerfile\" },\n });\n if (result?.stdout) {\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n }\n if (!result || result.exitCode !== 0) {\n throw new Error(\n result?.stderr.trim()\n ? `railway up failed: ${result.stderr.trim().split(\"\\n\").slice(-3).join(\" \")}`\n : `railway up failed with exit code ${result?.exitCode ?? \"unknown\"}`,\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
package/dist/image-deploy.mjs
CHANGED
|
@@ -58,21 +58,26 @@ function readFrameworkVersion(configDir) {
|
|
|
58
58
|
* Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a
|
|
59
59
|
* floating major tag (`v2`) on stable releases only. `latest` is held during
|
|
60
60
|
* prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to
|
|
61
|
-
* `:latest` keep serving the last stable image
|
|
61
|
+
* `:latest` keep serving the last stable image — and `:rc` is the floating
|
|
62
|
+
* ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on
|
|
63
|
+
* every prerelease deploy and freezes at the last rc-train build once the
|
|
64
|
+
* workspace releases a stable version.
|
|
62
65
|
*/
|
|
63
66
|
function computeImageTags(input) {
|
|
64
67
|
const tags = [`sha-${input.shortSha}`];
|
|
65
68
|
const match = input.version?.trim().match(SEMVER_PATTERN);
|
|
66
69
|
if (!match) return {
|
|
67
70
|
tags,
|
|
68
|
-
pushLatest: true
|
|
71
|
+
pushLatest: true,
|
|
72
|
+
pushRc: false
|
|
69
73
|
};
|
|
70
74
|
const [, major, minor, patch, prerelease] = match;
|
|
71
75
|
const exact = prerelease ? `v${major}.${minor}.${patch}-${prerelease}` : `v${major}.${minor}.${patch}`;
|
|
72
76
|
if (prerelease) return {
|
|
73
77
|
tags: [...tags, exact],
|
|
74
78
|
pushLatest: false,
|
|
75
|
-
latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image
|
|
79
|
+
latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,
|
|
80
|
+
pushRc: true
|
|
76
81
|
};
|
|
77
82
|
return {
|
|
78
83
|
tags: [
|
|
@@ -80,14 +85,49 @@ function computeImageTags(input) {
|
|
|
80
85
|
exact,
|
|
81
86
|
`v${major}`
|
|
82
87
|
],
|
|
83
|
-
pushLatest: true
|
|
88
|
+
pushLatest: true,
|
|
89
|
+
pushRc: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
93
|
+
/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */
|
|
94
|
+
function isValidImageDigest(digest) {
|
|
95
|
+
return DIGEST_PATTERN.test(digest);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decide the image leg's source: a digest handed over from a pre-pushed image
|
|
99
|
+
* (the CI image job) wins; otherwise the local docker build runs when the
|
|
100
|
+
* toolchain and Dockerfile are present (ADR 0021, as amended).
|
|
101
|
+
*/
|
|
102
|
+
function resolveDeployImagePlan(input) {
|
|
103
|
+
if (!input.imageRef) return {
|
|
104
|
+
kind: "skip",
|
|
105
|
+
reason: "set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image"
|
|
106
|
+
};
|
|
107
|
+
if (input.imageDigest) return {
|
|
108
|
+
kind: "prebuilt",
|
|
109
|
+
image: input.imageRef.image,
|
|
110
|
+
digest: input.imageDigest
|
|
111
|
+
};
|
|
112
|
+
if (!input.hasDocker) return {
|
|
113
|
+
kind: "skip",
|
|
114
|
+
reason: "docker is not available"
|
|
115
|
+
};
|
|
116
|
+
if (!input.hasDockerfile) return {
|
|
117
|
+
kind: "skip",
|
|
118
|
+
reason: "no Dockerfile at the config root"
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
kind: "build",
|
|
122
|
+
image: input.imageRef.image
|
|
84
123
|
};
|
|
85
124
|
}
|
|
86
125
|
/**
|
|
87
126
|
* Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version
|
|
88
|
-
* tags, push them, capture the digest,
|
|
89
|
-
* is a prerelease
|
|
90
|
-
* configured (child repos get
|
|
127
|
+
* tags, push them, capture the digest, then push the floating refs — `:latest`
|
|
128
|
+
* unless the version is a prerelease, and `:rc` when it is. Callers skip the
|
|
129
|
+
* leg with a notice when no image is configured (child repos get
|
|
130
|
+
* build+upload+publish only).
|
|
91
131
|
*/
|
|
92
132
|
async function buildAndPushImage(input) {
|
|
93
133
|
const shaResult = await run("git", [
|
|
@@ -103,13 +143,15 @@ async function buildAndPushImage(input) {
|
|
|
103
143
|
const detail = shaResult?.stderr.trim();
|
|
104
144
|
throw new Error(`Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? "unknown"}${detail ? `: ${detail}` : ""})`);
|
|
105
145
|
}
|
|
106
|
-
const { tags, pushLatest, latestHoldReason } = computeImageTags({
|
|
146
|
+
const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({
|
|
107
147
|
shortSha,
|
|
108
148
|
version: readFrameworkVersion(input.configDir)
|
|
109
149
|
});
|
|
110
150
|
const refs = tags.map((tag) => `${input.image}:${tag}`);
|
|
111
151
|
const latestRef = `${input.image}:latest`;
|
|
112
|
-
const
|
|
152
|
+
const rcRef = `${input.image}:rc`;
|
|
153
|
+
const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];
|
|
154
|
+
if (pushRc) buildRefs.push(rcRef);
|
|
113
155
|
console.log(` Building runtime image ${colors.cyan(buildRefs.join(", "))}${pushLatest ? "" : colors.yellow(" (latest held)")}`);
|
|
114
156
|
await run("docker", [
|
|
115
157
|
"build",
|
|
@@ -129,23 +171,27 @@ async function buildAndPushImage(input) {
|
|
|
129
171
|
digest ??= parseDigest(push.stdout);
|
|
130
172
|
if (input.verbose && push.stdout.trim()) console.log(colors.dim(push.stdout.trim()));
|
|
131
173
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const pushLatestRef = await run("docker", ["push", latestRef], {
|
|
174
|
+
const pushFloating = async (ref) => {
|
|
175
|
+
const push = await run("docker", ["push", ref], {
|
|
135
176
|
cwd: input.configDir,
|
|
136
177
|
capture: true
|
|
137
178
|
});
|
|
138
|
-
if (!
|
|
139
|
-
digest ??= parseDigest(
|
|
140
|
-
if (input.verbose &&
|
|
141
|
-
|
|
142
|
-
}
|
|
179
|
+
if (!push || push.exitCode !== 0) throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? "unknown"}`);
|
|
180
|
+
digest ??= parseDigest(push.stdout);
|
|
181
|
+
if (input.verbose && push.stdout.trim()) console.log(colors.dim(push.stdout.trim()));
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
let latestPushed = false;
|
|
185
|
+
if (pushLatest) latestPushed = await pushFloating(latestRef);
|
|
186
|
+
else console.log(colors.yellow(` latest held: ${latestHoldReason}`));
|
|
187
|
+
const rcPushed = pushRc ? await pushFloating(rcRef) : false;
|
|
143
188
|
if (!digest) console.log(colors.yellow(" Could not capture the image digest from push output"));
|
|
144
189
|
return {
|
|
145
190
|
image: input.image,
|
|
146
191
|
tag: tags[0],
|
|
147
192
|
tags,
|
|
148
193
|
latestPushed,
|
|
194
|
+
rcPushed,
|
|
149
195
|
digest
|
|
150
196
|
};
|
|
151
197
|
}
|
|
@@ -178,5 +224,5 @@ async function deployImageToRailway(input) {
|
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
//#endregion
|
|
181
|
-
export { buildAndPushImage, computeImageTags, deployImageToRailway, hasDocker, resolveImageRef };
|
|
227
|
+
export { buildAndPushImage, computeImageTags, deployImageToRailway, hasDocker, isValidImageDigest, resolveDeployImagePlan, resolveImageRef };
|
|
182
228
|
//# sourceMappingURL=image-deploy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n digest?: string;\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, and push `:latest` unless the version\n * is a prerelease. Callers skip the leg with a notice when no image is\n * configured (child repos get build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason } = computeImageTags({ shortSha, version });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const buildRefs = pushLatest ? [...refs, latestRef] : refs;\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n let latestPushed = false;\n if (pushLatest) {\n const pushLatestRef = await run(\"docker\", [\"push\", latestRef], {\n cwd: input.configDir,\n capture: true,\n });\n if (!pushLatestRef || pushLatestRef.exitCode !== 0) {\n throw new Error(\n `docker push ${latestRef} failed with exit code ${pushLatestRef?.exitCode ?? \"unknown\"}`,\n );\n }\n digest ??= parseDigest(pushLatestRef.stdout);\n if (input.verbose && pushLatestRef.stdout.trim()) {\n console.log(colors.dim(pushLatestRef.stdout.trim()));\n }\n latestPushed = true;\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0], tags, latestPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021): pull-only deploy of the pushed image digest — a\n * thin `FROM image@digest` Dockerfile pins the digest so Railway never\n * rebuilds; `RAILWAY_DOCKERFILE_PATH` points at the generated file.\n */\nexport async function deployImageToRailway(input: {\n image: string;\n digest: string;\n service: string;\n configDir: string;\n}): Promise<void> {\n const deployDir = join(input.configDir, \".bos\", \"deploy\");\n mkdirSync(deployDir, { recursive: true });\n const dockerfilePath = join(deployDir, \"Dockerfile\");\n writeFileSync(dockerfilePath, `FROM ${input.image}@${input.digest}\\n`);\n\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (pull-only)...`);\n const result = await run(\"railway\", [\"up\", \"--service\", input.service, \"--ci\"], {\n cwd: input.configDir,\n capture: true,\n env: { RAILWAY_DOCKERFILE_PATH: \".bos/deploy/Dockerfile\" },\n });\n if (result?.stdout) {\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n }\n if (!result || result.exitCode !== 0) {\n throw new Error(\n result?.stderr.trim()\n ? `railway up failed: ${result.stderr.trim().split(\"\\n\").slice(-3).join(\" \")}`\n : `railway up failed with exit code ${result?.exitCode ?? \"unknown\"}`,\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021): pull-only deploy of the pushed image digest — a\n * thin `FROM image@digest` Dockerfile pins the digest so Railway never\n * rebuilds; `RAILWAY_DOCKERFILE_PATH` points at the generated file.\n */\nexport async function deployImageToRailway(input: {\n image: string;\n digest: string;\n service: string;\n configDir: string;\n}): Promise<void> {\n const deployDir = join(input.configDir, \".bos\", \"deploy\");\n mkdirSync(deployDir, { recursive: true });\n const dockerfilePath = join(deployDir, \"Dockerfile\");\n writeFileSync(dockerfilePath, `FROM ${input.image}@${input.digest}\\n`);\n\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (pull-only)...`);\n const result = await run(\"railway\", [\"up\", \"--service\", input.service, \"--ci\"], {\n cwd: input.configDir,\n capture: true,\n env: { RAILWAY_DOCKERFILE_PATH: \".bos/deploy/Dockerfile\" },\n });\n if (result?.stdout) {\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n }\n if (!result || result.exitCode !== 0) {\n throw new Error(\n result?.stderr.trim()\n ? `railway up failed: ${result.stderr.trim().split(\"\\n\").slice(-3).join(\" \")}`\n : `railway up failed with exit code ${result?.exitCode ?? \"unknown\"}`,\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-normalizer.cjs","names":["readFileSync","readWorkspaceCatalog","join","existsSync"],"sources":["../../src/internal/manifest-normalizer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { glob } from \"glob\";\nimport { readWorkspaceCatalog } from \"../workspace-catalog\";\n\nconst FRAMEWORK_PACKAGES = [\"every-plugin\", \"everything-dev\"] as const;\n\nconst CATALOG_TOOL_PACKAGES = [\n \"@rspack/core\",\n \"@rspack/cli\",\n \"@rsbuild/core\",\n \"@rsbuild/plugin-react\",\n \"@module-federation/enhanced\",\n \"@module-federation/node\",\n \"@module-federation/rsbuild-plugin\",\n \"@module-federation/runtime-core\",\n \"@module-federation/sdk\",\n \"@module-federation/dts-plugin\",\n] as const;\n\ntype PackageJson = Record<string, unknown>;\n\ntype NormalizationSpec = {\n rootCatalog: Record<string, string>;\n frameworkVersions: Record<string, string>;\n};\n\ntype NormalizeManifestOptions = {\n resolveCatalogRefs: boolean;\n preserveCatalogRefs?: boolean;\n excludeFrameworkWorkspaces?: boolean;\n removeWorkspaceDeps?: string[];\n removeWorkspaces?: boolean;\n removePublishScripts?: boolean;\n};\n\nexport type NormalizeTreeOptions = NormalizeManifestOptions & {\n sourceRootDir: string;\n targetDir: string;\n};\n\nfunction readJson<T>(filePath: string): T {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n}\n\nfunction extractExactVersion(input: string | undefined): string | null {\n if (!input) return null;\n const match = input.match(/\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?/);\n return match ? match[0] : null;\n}\n\nfunction writeJson(filePath: string, value: PackageJson) {\n writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport function loadManifestNormalizationSpec(sourceRootDir: string): NormalizationSpec {\n const rootCatalog = readWorkspaceCatalog(sourceRootDir);\n const frameworkVersions: Record<string, string> = {};\n\n for (const packageName of FRAMEWORK_PACKAGES) {\n const sourcePackagePath = join(sourceRootDir, \"packages\", packageName, \"package.json\");\n const localPackagePath = join(import.meta.dirname, \"..\", \"..\", packageName, \"package.json\");\n const packageVersion = existsSync(localPackagePath)\n ? readJson<{ version: string }>(localPackagePath).version\n : existsSync(sourcePackagePath)\n ? readJson<{ version: string }>(sourcePackagePath).version\n : extractExactVersion(rootCatalog[packageName]);\n\n if (!packageVersion) {\n throw new Error(`Could not resolve version for ${packageName}`);\n }\n\n frameworkVersions[packageName] = packageVersion;\n rootCatalog[packageName] = `^${packageVersion}`;\n }\n\n return { rootCatalog, frameworkVersions };\n}\n\nfunction normalizeDependencyMap(\n map: Record<string, string>,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n const catalogPackages = new Set<string>([...FRAMEWORK_PACKAGES, ...CATALOG_TOOL_PACKAGES]);\n\n let modified = false;\n\n for (const [name, version] of Object.entries(map)) {\n if (options.preserveCatalogRefs && catalogPackages.has(name)) {\n if (version !== \"catalog:\") {\n map[name] = \"catalog:\";\n modified = true;\n }\n continue;\n }\n\n if (version.startsWith(\"workspace:\")) {\n const frameworkVersion = spec.frameworkVersions[name];\n if (frameworkVersion) {\n map[name] = `^${frameworkVersion}`;\n modified = true;\n continue;\n }\n\n if (options.removeWorkspaceDeps?.includes(name)) {\n delete map[name];\n modified = true;\n continue;\n }\n\n // A workspace dep on a non-framework member (e.g. better-near-auth)\n // cannot resolve in a generated child — the member does not exist\n // there. Children keep the catalog: protocol (the catalog pins the\n // published version); release staging resolves the real range.\n if (spec.rootCatalog[name]) {\n map[name] = options.preserveCatalogRefs ? \"catalog:\" : spec.rootCatalog[name];\n modified = true;\n }\n continue;\n }\n\n if (options.resolveCatalogRefs && version.startsWith(\"catalog:\")) {\n const resolved = spec.rootCatalog[name];\n if (resolved) {\n map[name] = resolved;\n modified = true;\n }\n }\n }\n\n return modified;\n}\n\nexport function normalizePackageManifest(\n pkg: PackageJson,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n let modified = false;\n\n for (const depField of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const deps = pkg[depField];\n if (!deps || typeof deps !== \"object\") continue;\n if (normalizeDependencyMap(deps as Record<string, string>, spec, options)) {\n modified = true;\n }\n }\n\n if (pkg.workspaces && typeof pkg.workspaces === \"object\") {\n const workspaces = pkg.workspaces as {\n packages?: string[];\n catalog?: Record<string, string>;\n };\n\n if (options.excludeFrameworkWorkspaces && Array.isArray(workspaces.packages)) {\n const nextPackages = workspaces.packages.filter(\n (entry) => !FRAMEWORK_PACKAGES.some((name) => entry === `packages/${name}`),\n );\n if (nextPackages.length !== workspaces.packages.length) {\n workspaces.packages = nextPackages;\n modified = true;\n }\n }\n\n if (workspaces.catalog && typeof workspaces.catalog === \"object\") {\n for (const [name, version] of Object.entries(workspaces.catalog)) {\n const resolved = spec.rootCatalog[name];\n if (resolved && resolved !== version) {\n workspaces.catalog[name] = resolved;\n modified = true;\n continue;\n }\n\n if (version === \"workspace:*\" && spec.frameworkVersions[name]) {\n workspaces.catalog[name] = `^${spec.frameworkVersions[name]}`;\n modified = true;\n }\n }\n }\n }\n\n if (options.removeWorkspaces && \"workspaces\" in pkg) {\n delete pkg.workspaces;\n modified = true;\n }\n\n if (options.removePublishScripts && pkg.scripts && typeof pkg.scripts === \"object\") {\n const scripts = pkg.scripts as Record<string, string>;\n let scriptsModified = false;\n for (const key of [\"prepublishOnly\", \"prepack\", \"prepare\", \"postpack\"]) {\n if (key in scripts) {\n delete scripts[key];\n scriptsModified = true;\n }\n }\n if (scriptsModified) {\n modified = true;\n if (Object.keys(scripts).length === 0) {\n delete pkg.scripts;\n }\n }\n }\n\n return modified;\n}\n\nexport async function normalizePackageManifestsInTree(opts: NormalizeTreeOptions) {\n const spec = loadManifestNormalizationSpec(opts.sourceRootDir);\n const files = await glob(\"**/package.json\", {\n cwd: opts.targetDir,\n nodir: true,\n dot: false,\n absolute: true,\n ignore: [\"**/node_modules/**\"],\n });\n\n const updatedFiles: string[] = [];\n\n for (const filePath of files) {\n const pkg = readJson<PackageJson>(filePath);\n if (normalizePackageManifest(pkg, spec, opts)) {\n writeJson(filePath, pkg);\n updatedFiles.push(filePath);\n }\n }\n\n return updatedFiles;\n}\n\nfunction shouldCopyPackageFile(sourceDir: string, filePath: string) {\n const relPath = relative(sourceDir, filePath);\n if (!relPath) return true;\n const segments = relPath.split(sep);\n return !segments.includes(\"node_modules\") && !segments.includes(\"tests\");\n}\n\nfunction stripDevelopmentExports(pkg: PackageJson) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n for (const key of Object.keys(exports as Record<string, unknown>)) {\n const entry = (exports as Record<string, unknown>)[key];\n if (entry && typeof entry === \"object\") {\n delete (entry as Record<string, unknown>).development;\n }\n }\n}\n\nexport function stageReleasePackage(opts: {\n repoRoot: string;\n packageName: string;\n outDir: string;\n}) {\n const sourceDir = join(opts.repoRoot, \"packages\", opts.packageName);\n\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(dirname(opts.outDir), { recursive: true });\n cpSync(sourceDir, opts.outDir, {\n recursive: true,\n filter: (filePath) => shouldCopyPackageFile(sourceDir, filePath),\n });\n rmSync(join(opts.outDir, \"tests\"), { recursive: true, force: true });\n\n const packageJsonPath = join(opts.outDir, \"package.json\");\n const spec = loadManifestNormalizationSpec(opts.repoRoot);\n const pkg = readJson<PackageJson>(packageJsonPath);\n\n normalizePackageManifest(pkg, spec, {\n resolveCatalogRefs: true,\n preserveCatalogRefs: false,\n removeWorkspaces: true,\n removePublishScripts: true,\n });\n\n stripDevelopmentExports(pkg);\n\n writeJson(packageJsonPath, pkg);\n}\n\nexport function stageReleasePackages(opts: {\n repoRoot: string;\n outDir: string;\n packageNames?: string[];\n}) {\n const packageNames = opts.packageNames ?? [...FRAMEWORK_PACKAGES];\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(opts.outDir, { recursive: true });\n\n for (const packageName of packageNames) {\n stageReleasePackage({\n repoRoot: opts.repoRoot,\n packageName,\n outDir: join(opts.outDir, packageName),\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manifest-normalizer.cjs","names":["readFileSync","readWorkspaceCatalog","join","existsSync"],"sources":["../../src/internal/manifest-normalizer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { glob } from \"glob\";\nimport { readWorkspaceCatalog } from \"../workspace-catalog\";\n\nconst FRAMEWORK_PACKAGES = [\"every-plugin\", \"everything-dev\"] as const;\n\nconst CATALOG_TOOL_PACKAGES = [\n \"@rspack/core\",\n \"@rspack/cli\",\n \"@rsbuild/core\",\n \"@rsbuild/plugin-react\",\n \"@module-federation/enhanced\",\n \"@module-federation/node\",\n \"@module-federation/rsbuild-plugin\",\n \"@module-federation/runtime-core\",\n \"@module-federation/sdk\",\n \"@module-federation/dts-plugin\",\n] as const;\n\ntype PackageJson = Record<string, unknown>;\n\ntype NormalizationSpec = {\n rootCatalog: Record<string, string>;\n frameworkVersions: Record<string, string>;\n};\n\ntype NormalizeManifestOptions = {\n resolveCatalogRefs: boolean;\n preserveCatalogRefs?: boolean;\n excludeFrameworkWorkspaces?: boolean;\n removeWorkspaceDeps?: string[];\n removeWorkspaces?: boolean;\n removePublishScripts?: boolean;\n};\n\nexport type NormalizeTreeOptions = NormalizeManifestOptions & {\n sourceRootDir: string;\n targetDir: string;\n};\n\nfunction readJson<T>(filePath: string): T {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n}\n\nfunction extractExactVersion(input: string | undefined): string | null {\n if (!input) return null;\n const match = input.match(/\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?/);\n return match ? match[0] : null;\n}\n\nfunction writeJson(filePath: string, value: PackageJson) {\n writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport function loadManifestNormalizationSpec(sourceRootDir: string): NormalizationSpec {\n const rootCatalog = readWorkspaceCatalog(sourceRootDir);\n const frameworkVersions: Record<string, string> = {};\n\n for (const packageName of FRAMEWORK_PACKAGES) {\n const sourcePackagePath = join(sourceRootDir, \"packages\", packageName, \"package.json\");\n const localPackagePath = join(import.meta.dirname, \"..\", \"..\", packageName, \"package.json\");\n const packageVersion = existsSync(localPackagePath)\n ? readJson<{ version: string }>(localPackagePath).version\n : existsSync(sourcePackagePath)\n ? readJson<{ version: string }>(sourcePackagePath).version\n : extractExactVersion(rootCatalog[packageName]);\n\n if (!packageVersion) {\n throw new Error(`Could not resolve version for ${packageName}`);\n }\n\n frameworkVersions[packageName] = packageVersion;\n rootCatalog[packageName] = `^${packageVersion}`;\n }\n\n return { rootCatalog, frameworkVersions };\n}\n\nfunction normalizeDependencyMap(\n map: Record<string, string>,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n const catalogPackages = new Set<string>([...FRAMEWORK_PACKAGES, ...CATALOG_TOOL_PACKAGES]);\n\n let modified = false;\n\n for (const [name, version] of Object.entries(map)) {\n if (options.preserveCatalogRefs && catalogPackages.has(name)) {\n if (version !== \"catalog:\") {\n map[name] = \"catalog:\";\n modified = true;\n }\n continue;\n }\n\n if (version.startsWith(\"workspace:\")) {\n const frameworkVersion = spec.frameworkVersions[name];\n if (frameworkVersion) {\n map[name] = `^${frameworkVersion}`;\n modified = true;\n continue;\n }\n\n if (options.removeWorkspaceDeps?.includes(name)) {\n delete map[name];\n modified = true;\n continue;\n }\n\n // A workspace dep on a non-framework member (e.g. better-near-auth)\n // cannot resolve in a generated child — the member does not exist\n // there. Children keep the catalog: protocol (the catalog pins the\n // published version); release staging resolves the real range.\n if (spec.rootCatalog[name]) {\n map[name] = options.preserveCatalogRefs ? \"catalog:\" : spec.rootCatalog[name];\n modified = true;\n }\n continue;\n }\n\n if (options.resolveCatalogRefs && version.startsWith(\"catalog:\")) {\n const resolved = spec.rootCatalog[name];\n if (resolved) {\n map[name] = resolved;\n modified = true;\n }\n }\n }\n\n return modified;\n}\n\nexport function normalizePackageManifest(\n pkg: PackageJson,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n let modified = false;\n\n for (const depField of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const deps = pkg[depField];\n if (!deps || typeof deps !== \"object\") continue;\n if (normalizeDependencyMap(deps as Record<string, string>, spec, options)) {\n modified = true;\n }\n }\n\n if (pkg.workspaces && typeof pkg.workspaces === \"object\") {\n const workspaces = pkg.workspaces as {\n packages?: string[];\n catalog?: Record<string, string>;\n };\n\n if (options.excludeFrameworkWorkspaces && Array.isArray(workspaces.packages)) {\n const nextPackages = workspaces.packages.filter(\n (entry) => !FRAMEWORK_PACKAGES.some((name) => entry === `packages/${name}`),\n );\n if (nextPackages.length !== workspaces.packages.length) {\n workspaces.packages = nextPackages;\n modified = true;\n }\n }\n\n if (workspaces.catalog && typeof workspaces.catalog === \"object\") {\n for (const [name, version] of Object.entries(workspaces.catalog)) {\n const resolved = spec.rootCatalog[name];\n if (resolved && resolved !== version) {\n workspaces.catalog[name] = resolved;\n modified = true;\n continue;\n }\n\n if (version === \"workspace:*\" && spec.frameworkVersions[name]) {\n workspaces.catalog[name] = `^${spec.frameworkVersions[name]}`;\n modified = true;\n }\n }\n }\n }\n\n if (options.removeWorkspaces && \"workspaces\" in pkg) {\n delete pkg.workspaces;\n modified = true;\n }\n\n if (options.removePublishScripts && pkg.scripts && typeof pkg.scripts === \"object\") {\n const scripts = pkg.scripts as Record<string, string>;\n let scriptsModified = false;\n for (const key of [\"prepublishOnly\", \"prepack\", \"prepare\", \"postpack\"]) {\n if (key in scripts) {\n delete scripts[key];\n scriptsModified = true;\n }\n }\n if (scriptsModified) {\n modified = true;\n if (Object.keys(scripts).length === 0) {\n delete pkg.scripts;\n }\n }\n }\n\n return modified;\n}\n\nexport async function normalizePackageManifestsInTree(opts: NormalizeTreeOptions) {\n const spec = loadManifestNormalizationSpec(opts.sourceRootDir);\n const files = await glob(\"**/package.json\", {\n cwd: opts.targetDir,\n nodir: true,\n dot: false,\n absolute: true,\n ignore: [\"**/node_modules/**\"],\n });\n\n const updatedFiles: string[] = [];\n\n for (const filePath of files) {\n const pkg = readJson<PackageJson>(filePath);\n if (normalizePackageManifest(pkg, spec, opts)) {\n writeJson(filePath, pkg);\n updatedFiles.push(filePath);\n }\n }\n\n return updatedFiles;\n}\n\nfunction shouldCopyPackageFile(sourceDir: string, filePath: string) {\n const relPath = relative(sourceDir, filePath);\n if (!relPath) return true;\n const segments = relPath.split(sep);\n return !segments.includes(\"node_modules\") && !segments.includes(\"tests\");\n}\n\nfunction stripDevelopmentExports(pkg: PackageJson) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n for (const key of Object.keys(exports as Record<string, unknown>)) {\n const entry = (exports as Record<string, unknown>)[key];\n if (entry && typeof entry === \"object\") {\n delete (entry as Record<string, unknown>).development;\n }\n }\n}\n\n// A published package ships only dist/ — a bare src pointer left after the\n// strip is a dangling path for npm consumers, so fail the release instead.\nfunction assertNoSourceExports(pkg: PackageJson, packageName: string) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n const dangling: string[] = [];\n for (const [key, entry] of Object.entries(exports as Record<string, unknown>)) {\n if (!entry || typeof entry !== \"object\") continue;\n for (const [condition, target] of Object.entries(entry as Record<string, unknown>)) {\n if (typeof target === \"string\" && target.startsWith(\"./src/\")) {\n dangling.push(`${key} (${condition}: ${target})`);\n }\n }\n }\n\n if (dangling.length > 0) {\n throw new Error(\n `${packageName}: exports still point at unpublished ./src/ paths after stripping ` +\n `development conditions. Map them to dist/ in the package manifest:\\n` +\n dangling.map((entry) => ` - ${entry}`).join(\"\\n\"),\n );\n }\n}\n\nexport function stageReleasePackage(opts: {\n repoRoot: string;\n packageName: string;\n outDir: string;\n}) {\n const sourceDir = join(opts.repoRoot, \"packages\", opts.packageName);\n\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(dirname(opts.outDir), { recursive: true });\n cpSync(sourceDir, opts.outDir, {\n recursive: true,\n filter: (filePath) => shouldCopyPackageFile(sourceDir, filePath),\n });\n rmSync(join(opts.outDir, \"tests\"), { recursive: true, force: true });\n\n const packageJsonPath = join(opts.outDir, \"package.json\");\n const spec = loadManifestNormalizationSpec(opts.repoRoot);\n const pkg = readJson<PackageJson>(packageJsonPath);\n\n normalizePackageManifest(pkg, spec, {\n resolveCatalogRefs: true,\n preserveCatalogRefs: false,\n removeWorkspaces: true,\n removePublishScripts: true,\n });\n\n stripDevelopmentExports(pkg);\n assertNoSourceExports(pkg, opts.packageName);\n\n writeJson(packageJsonPath, pkg);\n}\n\nexport function stageReleasePackages(opts: {\n repoRoot: string;\n outDir: string;\n packageNames?: string[];\n}) {\n const packageNames = opts.packageNames ?? [...FRAMEWORK_PACKAGES];\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(opts.outDir, { recursive: true });\n\n for (const packageName of packageNames) {\n stageReleasePackage({\n repoRoot: opts.repoRoot,\n packageName,\n outDir: join(opts.outDir, packageName),\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-normalizer.mjs","names":[],"sources":["../../src/internal/manifest-normalizer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { glob } from \"glob\";\nimport { readWorkspaceCatalog } from \"../workspace-catalog\";\n\nconst FRAMEWORK_PACKAGES = [\"every-plugin\", \"everything-dev\"] as const;\n\nconst CATALOG_TOOL_PACKAGES = [\n \"@rspack/core\",\n \"@rspack/cli\",\n \"@rsbuild/core\",\n \"@rsbuild/plugin-react\",\n \"@module-federation/enhanced\",\n \"@module-federation/node\",\n \"@module-federation/rsbuild-plugin\",\n \"@module-federation/runtime-core\",\n \"@module-federation/sdk\",\n \"@module-federation/dts-plugin\",\n] as const;\n\ntype PackageJson = Record<string, unknown>;\n\ntype NormalizationSpec = {\n rootCatalog: Record<string, string>;\n frameworkVersions: Record<string, string>;\n};\n\ntype NormalizeManifestOptions = {\n resolveCatalogRefs: boolean;\n preserveCatalogRefs?: boolean;\n excludeFrameworkWorkspaces?: boolean;\n removeWorkspaceDeps?: string[];\n removeWorkspaces?: boolean;\n removePublishScripts?: boolean;\n};\n\nexport type NormalizeTreeOptions = NormalizeManifestOptions & {\n sourceRootDir: string;\n targetDir: string;\n};\n\nfunction readJson<T>(filePath: string): T {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n}\n\nfunction extractExactVersion(input: string | undefined): string | null {\n if (!input) return null;\n const match = input.match(/\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?/);\n return match ? match[0] : null;\n}\n\nfunction writeJson(filePath: string, value: PackageJson) {\n writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport function loadManifestNormalizationSpec(sourceRootDir: string): NormalizationSpec {\n const rootCatalog = readWorkspaceCatalog(sourceRootDir);\n const frameworkVersions: Record<string, string> = {};\n\n for (const packageName of FRAMEWORK_PACKAGES) {\n const sourcePackagePath = join(sourceRootDir, \"packages\", packageName, \"package.json\");\n const localPackagePath = join(import.meta.dirname, \"..\", \"..\", packageName, \"package.json\");\n const packageVersion = existsSync(localPackagePath)\n ? readJson<{ version: string }>(localPackagePath).version\n : existsSync(sourcePackagePath)\n ? readJson<{ version: string }>(sourcePackagePath).version\n : extractExactVersion(rootCatalog[packageName]);\n\n if (!packageVersion) {\n throw new Error(`Could not resolve version for ${packageName}`);\n }\n\n frameworkVersions[packageName] = packageVersion;\n rootCatalog[packageName] = `^${packageVersion}`;\n }\n\n return { rootCatalog, frameworkVersions };\n}\n\nfunction normalizeDependencyMap(\n map: Record<string, string>,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n const catalogPackages = new Set<string>([...FRAMEWORK_PACKAGES, ...CATALOG_TOOL_PACKAGES]);\n\n let modified = false;\n\n for (const [name, version] of Object.entries(map)) {\n if (options.preserveCatalogRefs && catalogPackages.has(name)) {\n if (version !== \"catalog:\") {\n map[name] = \"catalog:\";\n modified = true;\n }\n continue;\n }\n\n if (version.startsWith(\"workspace:\")) {\n const frameworkVersion = spec.frameworkVersions[name];\n if (frameworkVersion) {\n map[name] = `^${frameworkVersion}`;\n modified = true;\n continue;\n }\n\n if (options.removeWorkspaceDeps?.includes(name)) {\n delete map[name];\n modified = true;\n continue;\n }\n\n // A workspace dep on a non-framework member (e.g. better-near-auth)\n // cannot resolve in a generated child — the member does not exist\n // there. Children keep the catalog: protocol (the catalog pins the\n // published version); release staging resolves the real range.\n if (spec.rootCatalog[name]) {\n map[name] = options.preserveCatalogRefs ? \"catalog:\" : spec.rootCatalog[name];\n modified = true;\n }\n continue;\n }\n\n if (options.resolveCatalogRefs && version.startsWith(\"catalog:\")) {\n const resolved = spec.rootCatalog[name];\n if (resolved) {\n map[name] = resolved;\n modified = true;\n }\n }\n }\n\n return modified;\n}\n\nexport function normalizePackageManifest(\n pkg: PackageJson,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n let modified = false;\n\n for (const depField of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const deps = pkg[depField];\n if (!deps || typeof deps !== \"object\") continue;\n if (normalizeDependencyMap(deps as Record<string, string>, spec, options)) {\n modified = true;\n }\n }\n\n if (pkg.workspaces && typeof pkg.workspaces === \"object\") {\n const workspaces = pkg.workspaces as {\n packages?: string[];\n catalog?: Record<string, string>;\n };\n\n if (options.excludeFrameworkWorkspaces && Array.isArray(workspaces.packages)) {\n const nextPackages = workspaces.packages.filter(\n (entry) => !FRAMEWORK_PACKAGES.some((name) => entry === `packages/${name}`),\n );\n if (nextPackages.length !== workspaces.packages.length) {\n workspaces.packages = nextPackages;\n modified = true;\n }\n }\n\n if (workspaces.catalog && typeof workspaces.catalog === \"object\") {\n for (const [name, version] of Object.entries(workspaces.catalog)) {\n const resolved = spec.rootCatalog[name];\n if (resolved && resolved !== version) {\n workspaces.catalog[name] = resolved;\n modified = true;\n continue;\n }\n\n if (version === \"workspace:*\" && spec.frameworkVersions[name]) {\n workspaces.catalog[name] = `^${spec.frameworkVersions[name]}`;\n modified = true;\n }\n }\n }\n }\n\n if (options.removeWorkspaces && \"workspaces\" in pkg) {\n delete pkg.workspaces;\n modified = true;\n }\n\n if (options.removePublishScripts && pkg.scripts && typeof pkg.scripts === \"object\") {\n const scripts = pkg.scripts as Record<string, string>;\n let scriptsModified = false;\n for (const key of [\"prepublishOnly\", \"prepack\", \"prepare\", \"postpack\"]) {\n if (key in scripts) {\n delete scripts[key];\n scriptsModified = true;\n }\n }\n if (scriptsModified) {\n modified = true;\n if (Object.keys(scripts).length === 0) {\n delete pkg.scripts;\n }\n }\n }\n\n return modified;\n}\n\nexport async function normalizePackageManifestsInTree(opts: NormalizeTreeOptions) {\n const spec = loadManifestNormalizationSpec(opts.sourceRootDir);\n const files = await glob(\"**/package.json\", {\n cwd: opts.targetDir,\n nodir: true,\n dot: false,\n absolute: true,\n ignore: [\"**/node_modules/**\"],\n });\n\n const updatedFiles: string[] = [];\n\n for (const filePath of files) {\n const pkg = readJson<PackageJson>(filePath);\n if (normalizePackageManifest(pkg, spec, opts)) {\n writeJson(filePath, pkg);\n updatedFiles.push(filePath);\n }\n }\n\n return updatedFiles;\n}\n\nfunction shouldCopyPackageFile(sourceDir: string, filePath: string) {\n const relPath = relative(sourceDir, filePath);\n if (!relPath) return true;\n const segments = relPath.split(sep);\n return !segments.includes(\"node_modules\") && !segments.includes(\"tests\");\n}\n\nfunction stripDevelopmentExports(pkg: PackageJson) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n for (const key of Object.keys(exports as Record<string, unknown>)) {\n const entry = (exports as Record<string, unknown>)[key];\n if (entry && typeof entry === \"object\") {\n delete (entry as Record<string, unknown>).development;\n }\n }\n}\n\nexport function stageReleasePackage(opts: {\n repoRoot: string;\n packageName: string;\n outDir: string;\n}) {\n const sourceDir = join(opts.repoRoot, \"packages\", opts.packageName);\n\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(dirname(opts.outDir), { recursive: true });\n cpSync(sourceDir, opts.outDir, {\n recursive: true,\n filter: (filePath) => shouldCopyPackageFile(sourceDir, filePath),\n });\n rmSync(join(opts.outDir, \"tests\"), { recursive: true, force: true });\n\n const packageJsonPath = join(opts.outDir, \"package.json\");\n const spec = loadManifestNormalizationSpec(opts.repoRoot);\n const pkg = readJson<PackageJson>(packageJsonPath);\n\n normalizePackageManifest(pkg, spec, {\n resolveCatalogRefs: true,\n preserveCatalogRefs: false,\n removeWorkspaces: true,\n removePublishScripts: true,\n });\n\n stripDevelopmentExports(pkg);\n\n writeJson(packageJsonPath, pkg);\n}\n\nexport function stageReleasePackages(opts: {\n repoRoot: string;\n outDir: string;\n packageNames?: string[];\n}) {\n const packageNames = opts.packageNames ?? [...FRAMEWORK_PACKAGES];\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(opts.outDir, { recursive: true });\n\n for (const packageName of packageNames) {\n stageReleasePackage({\n repoRoot: opts.repoRoot,\n packageName,\n outDir: join(opts.outDir, packageName),\n });\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,qBAAqB,CAAC,gBAAgB,gBAAgB;AAE5D,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAuBA,SAAS,SAAY,UAAqB;CACxC,OAAO,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACnD;AAEA,SAAS,oBAAoB,OAA0C;CACrE,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,QAAQ,MAAM,MAAM,mCAAmC;CAC7D,OAAO,QAAQ,MAAM,KAAK;AAC5B;AAEA,SAAS,UAAU,UAAkB,OAAoB;CACvD,cAAc,UAAU,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE,GAAG;AAC/D;AAEA,SAAgB,8BAA8B,eAA0C;CACtF,MAAM,cAAc,qBAAqB,aAAa;CACtD,MAAM,oBAA4C,CAAC;CAEnD,KAAK,MAAM,eAAe,oBAAoB;EAC5C,MAAM,oBAAoB,KAAK,eAAe,YAAY,aAAa,cAAc;EACrF,MAAM,mBAAmB,KAAK,YAAY,SAAS,MAAM,MAAM,aAAa,cAAc;EAC1F,MAAM,iBAAiB,WAAW,gBAAgB,IAC9C,SAA8B,gBAAgB,CAAC,CAAC,UAChD,WAAW,iBAAiB,IAC1B,SAA8B,iBAAiB,CAAC,CAAC,UACjD,oBAAoB,YAAY,YAAY;EAElD,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,iCAAiC,aAAa;EAGhE,kBAAkB,eAAe;EACjC,YAAY,eAAe,IAAI;CACjC;CAEA,OAAO;EAAE;EAAa;CAAkB;AAC1C;AAEA,SAAS,uBACP,KACA,MACA,SACA;CACA,MAAM,kCAAkB,IAAI,IAAY,CAAC,GAAG,oBAAoB,GAAG,qBAAqB,CAAC;CAEzF,IAAI,WAAW;CAEf,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,GAAG,GAAG;EACjD,IAAI,QAAQ,uBAAuB,gBAAgB,IAAI,IAAI,GAAG;GAC5D,IAAI,YAAY,YAAY;IAC1B,IAAI,QAAQ;IACZ,WAAW;GACb;GACA;EACF;EAEA,IAAI,QAAQ,WAAW,YAAY,GAAG;GACpC,MAAM,mBAAmB,KAAK,kBAAkB;GAChD,IAAI,kBAAkB;IACpB,IAAI,QAAQ,IAAI;IAChB,WAAW;IACX;GACF;GAEA,IAAI,QAAQ,qBAAqB,SAAS,IAAI,GAAG;IAC/C,OAAO,IAAI;IACX,WAAW;IACX;GACF;GAMA,IAAI,KAAK,YAAY,OAAO;IAC1B,IAAI,QAAQ,QAAQ,sBAAsB,aAAa,KAAK,YAAY;IACxE,WAAW;GACb;GACA;EACF;EAEA,IAAI,QAAQ,sBAAsB,QAAQ,WAAW,UAAU,GAAG;GAChE,MAAM,WAAW,KAAK,YAAY;GAClC,IAAI,UAAU;IACZ,IAAI,QAAQ;IACZ,WAAW;GACb;EACF;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,yBACd,KACA,MACA,SACA;CACA,IAAI,WAAW;CAEf,KAAK,MAAM,YAAY;EAAC;EAAgB;EAAmB;CAAkB,GAAG;EAC9E,MAAM,OAAO,IAAI;EACjB,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;EACvC,IAAI,uBAAuB,MAAgC,MAAM,OAAO,GACtE,WAAW;CAEf;CAEA,IAAI,IAAI,cAAc,OAAO,IAAI,eAAe,UAAU;EACxD,MAAM,aAAa,IAAI;EAKvB,IAAI,QAAQ,8BAA8B,MAAM,QAAQ,WAAW,QAAQ,GAAG;GAC5E,MAAM,eAAe,WAAW,SAAS,QACtC,UAAU,CAAC,mBAAmB,MAAM,SAAS,UAAU,YAAY,MAAM,CAC5E;GACA,IAAI,aAAa,WAAW,WAAW,SAAS,QAAQ;IACtD,WAAW,WAAW;IACtB,WAAW;GACb;EACF;EAEA,IAAI,WAAW,WAAW,OAAO,WAAW,YAAY,UACtD,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,WAAW,OAAO,GAAG;GAChE,MAAM,WAAW,KAAK,YAAY;GAClC,IAAI,YAAY,aAAa,SAAS;IACpC,WAAW,QAAQ,QAAQ;IAC3B,WAAW;IACX;GACF;GAEA,IAAI,YAAY,iBAAiB,KAAK,kBAAkB,OAAO;IAC7D,WAAW,QAAQ,QAAQ,IAAI,KAAK,kBAAkB;IACtD,WAAW;GACb;EACF;CAEJ;CAEA,IAAI,QAAQ,oBAAoB,gBAAgB,KAAK;EACnD,OAAO,IAAI;EACX,WAAW;CACb;CAEA,IAAI,QAAQ,wBAAwB,IAAI,WAAW,OAAO,IAAI,YAAY,UAAU;EAClF,MAAM,UAAU,IAAI;EACpB,IAAI,kBAAkB;EACtB,KAAK,MAAM,OAAO;GAAC;GAAkB;GAAW;GAAW;EAAU,GACnE,IAAI,OAAO,SAAS;GAClB,OAAO,QAAQ;GACf,kBAAkB;EACpB;EAEF,IAAI,iBAAiB;GACnB,WAAW;GACX,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAClC,OAAO,IAAI;EAEf;CACF;CAEA,OAAO;AACT;AAEA,eAAsB,gCAAgC,MAA4B;CAChF,MAAM,OAAO,8BAA8B,KAAK,aAAa;CAC7D,MAAM,QAAQ,MAAM,KAAK,mBAAmB;EAC1C,KAAK,KAAK;EACV,OAAO;EACP,KAAK;EACL,UAAU;EACV,QAAQ,CAAC,oBAAoB;CAC/B,CAAC;CAED,MAAM,eAAyB,CAAC;CAEhC,KAAK,MAAM,YAAY,OAAO;EAC5B,MAAM,MAAM,SAAsB,QAAQ;EAC1C,IAAI,yBAAyB,KAAK,MAAM,IAAI,GAAG;GAC7C,UAAU,UAAU,GAAG;GACvB,aAAa,KAAK,QAAQ;EAC5B;CACF;CAEA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"manifest-normalizer.mjs","names":[],"sources":["../../src/internal/manifest-normalizer.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { glob } from \"glob\";\nimport { readWorkspaceCatalog } from \"../workspace-catalog\";\n\nconst FRAMEWORK_PACKAGES = [\"every-plugin\", \"everything-dev\"] as const;\n\nconst CATALOG_TOOL_PACKAGES = [\n \"@rspack/core\",\n \"@rspack/cli\",\n \"@rsbuild/core\",\n \"@rsbuild/plugin-react\",\n \"@module-federation/enhanced\",\n \"@module-federation/node\",\n \"@module-federation/rsbuild-plugin\",\n \"@module-federation/runtime-core\",\n \"@module-federation/sdk\",\n \"@module-federation/dts-plugin\",\n] as const;\n\ntype PackageJson = Record<string, unknown>;\n\ntype NormalizationSpec = {\n rootCatalog: Record<string, string>;\n frameworkVersions: Record<string, string>;\n};\n\ntype NormalizeManifestOptions = {\n resolveCatalogRefs: boolean;\n preserveCatalogRefs?: boolean;\n excludeFrameworkWorkspaces?: boolean;\n removeWorkspaceDeps?: string[];\n removeWorkspaces?: boolean;\n removePublishScripts?: boolean;\n};\n\nexport type NormalizeTreeOptions = NormalizeManifestOptions & {\n sourceRootDir: string;\n targetDir: string;\n};\n\nfunction readJson<T>(filePath: string): T {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n}\n\nfunction extractExactVersion(input: string | undefined): string | null {\n if (!input) return null;\n const match = input.match(/\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?/);\n return match ? match[0] : null;\n}\n\nfunction writeJson(filePath: string, value: PackageJson) {\n writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport function loadManifestNormalizationSpec(sourceRootDir: string): NormalizationSpec {\n const rootCatalog = readWorkspaceCatalog(sourceRootDir);\n const frameworkVersions: Record<string, string> = {};\n\n for (const packageName of FRAMEWORK_PACKAGES) {\n const sourcePackagePath = join(sourceRootDir, \"packages\", packageName, \"package.json\");\n const localPackagePath = join(import.meta.dirname, \"..\", \"..\", packageName, \"package.json\");\n const packageVersion = existsSync(localPackagePath)\n ? readJson<{ version: string }>(localPackagePath).version\n : existsSync(sourcePackagePath)\n ? readJson<{ version: string }>(sourcePackagePath).version\n : extractExactVersion(rootCatalog[packageName]);\n\n if (!packageVersion) {\n throw new Error(`Could not resolve version for ${packageName}`);\n }\n\n frameworkVersions[packageName] = packageVersion;\n rootCatalog[packageName] = `^${packageVersion}`;\n }\n\n return { rootCatalog, frameworkVersions };\n}\n\nfunction normalizeDependencyMap(\n map: Record<string, string>,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n const catalogPackages = new Set<string>([...FRAMEWORK_PACKAGES, ...CATALOG_TOOL_PACKAGES]);\n\n let modified = false;\n\n for (const [name, version] of Object.entries(map)) {\n if (options.preserveCatalogRefs && catalogPackages.has(name)) {\n if (version !== \"catalog:\") {\n map[name] = \"catalog:\";\n modified = true;\n }\n continue;\n }\n\n if (version.startsWith(\"workspace:\")) {\n const frameworkVersion = spec.frameworkVersions[name];\n if (frameworkVersion) {\n map[name] = `^${frameworkVersion}`;\n modified = true;\n continue;\n }\n\n if (options.removeWorkspaceDeps?.includes(name)) {\n delete map[name];\n modified = true;\n continue;\n }\n\n // A workspace dep on a non-framework member (e.g. better-near-auth)\n // cannot resolve in a generated child — the member does not exist\n // there. Children keep the catalog: protocol (the catalog pins the\n // published version); release staging resolves the real range.\n if (spec.rootCatalog[name]) {\n map[name] = options.preserveCatalogRefs ? \"catalog:\" : spec.rootCatalog[name];\n modified = true;\n }\n continue;\n }\n\n if (options.resolveCatalogRefs && version.startsWith(\"catalog:\")) {\n const resolved = spec.rootCatalog[name];\n if (resolved) {\n map[name] = resolved;\n modified = true;\n }\n }\n }\n\n return modified;\n}\n\nexport function normalizePackageManifest(\n pkg: PackageJson,\n spec: NormalizationSpec,\n options: NormalizeManifestOptions,\n) {\n let modified = false;\n\n for (const depField of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const deps = pkg[depField];\n if (!deps || typeof deps !== \"object\") continue;\n if (normalizeDependencyMap(deps as Record<string, string>, spec, options)) {\n modified = true;\n }\n }\n\n if (pkg.workspaces && typeof pkg.workspaces === \"object\") {\n const workspaces = pkg.workspaces as {\n packages?: string[];\n catalog?: Record<string, string>;\n };\n\n if (options.excludeFrameworkWorkspaces && Array.isArray(workspaces.packages)) {\n const nextPackages = workspaces.packages.filter(\n (entry) => !FRAMEWORK_PACKAGES.some((name) => entry === `packages/${name}`),\n );\n if (nextPackages.length !== workspaces.packages.length) {\n workspaces.packages = nextPackages;\n modified = true;\n }\n }\n\n if (workspaces.catalog && typeof workspaces.catalog === \"object\") {\n for (const [name, version] of Object.entries(workspaces.catalog)) {\n const resolved = spec.rootCatalog[name];\n if (resolved && resolved !== version) {\n workspaces.catalog[name] = resolved;\n modified = true;\n continue;\n }\n\n if (version === \"workspace:*\" && spec.frameworkVersions[name]) {\n workspaces.catalog[name] = `^${spec.frameworkVersions[name]}`;\n modified = true;\n }\n }\n }\n }\n\n if (options.removeWorkspaces && \"workspaces\" in pkg) {\n delete pkg.workspaces;\n modified = true;\n }\n\n if (options.removePublishScripts && pkg.scripts && typeof pkg.scripts === \"object\") {\n const scripts = pkg.scripts as Record<string, string>;\n let scriptsModified = false;\n for (const key of [\"prepublishOnly\", \"prepack\", \"prepare\", \"postpack\"]) {\n if (key in scripts) {\n delete scripts[key];\n scriptsModified = true;\n }\n }\n if (scriptsModified) {\n modified = true;\n if (Object.keys(scripts).length === 0) {\n delete pkg.scripts;\n }\n }\n }\n\n return modified;\n}\n\nexport async function normalizePackageManifestsInTree(opts: NormalizeTreeOptions) {\n const spec = loadManifestNormalizationSpec(opts.sourceRootDir);\n const files = await glob(\"**/package.json\", {\n cwd: opts.targetDir,\n nodir: true,\n dot: false,\n absolute: true,\n ignore: [\"**/node_modules/**\"],\n });\n\n const updatedFiles: string[] = [];\n\n for (const filePath of files) {\n const pkg = readJson<PackageJson>(filePath);\n if (normalizePackageManifest(pkg, spec, opts)) {\n writeJson(filePath, pkg);\n updatedFiles.push(filePath);\n }\n }\n\n return updatedFiles;\n}\n\nfunction shouldCopyPackageFile(sourceDir: string, filePath: string) {\n const relPath = relative(sourceDir, filePath);\n if (!relPath) return true;\n const segments = relPath.split(sep);\n return !segments.includes(\"node_modules\") && !segments.includes(\"tests\");\n}\n\nfunction stripDevelopmentExports(pkg: PackageJson) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n for (const key of Object.keys(exports as Record<string, unknown>)) {\n const entry = (exports as Record<string, unknown>)[key];\n if (entry && typeof entry === \"object\") {\n delete (entry as Record<string, unknown>).development;\n }\n }\n}\n\n// A published package ships only dist/ — a bare src pointer left after the\n// strip is a dangling path for npm consumers, so fail the release instead.\nfunction assertNoSourceExports(pkg: PackageJson, packageName: string) {\n const exports = pkg.exports;\n if (!exports || typeof exports !== \"object\") return;\n\n const dangling: string[] = [];\n for (const [key, entry] of Object.entries(exports as Record<string, unknown>)) {\n if (!entry || typeof entry !== \"object\") continue;\n for (const [condition, target] of Object.entries(entry as Record<string, unknown>)) {\n if (typeof target === \"string\" && target.startsWith(\"./src/\")) {\n dangling.push(`${key} (${condition}: ${target})`);\n }\n }\n }\n\n if (dangling.length > 0) {\n throw new Error(\n `${packageName}: exports still point at unpublished ./src/ paths after stripping ` +\n `development conditions. Map them to dist/ in the package manifest:\\n` +\n dangling.map((entry) => ` - ${entry}`).join(\"\\n\"),\n );\n }\n}\n\nexport function stageReleasePackage(opts: {\n repoRoot: string;\n packageName: string;\n outDir: string;\n}) {\n const sourceDir = join(opts.repoRoot, \"packages\", opts.packageName);\n\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(dirname(opts.outDir), { recursive: true });\n cpSync(sourceDir, opts.outDir, {\n recursive: true,\n filter: (filePath) => shouldCopyPackageFile(sourceDir, filePath),\n });\n rmSync(join(opts.outDir, \"tests\"), { recursive: true, force: true });\n\n const packageJsonPath = join(opts.outDir, \"package.json\");\n const spec = loadManifestNormalizationSpec(opts.repoRoot);\n const pkg = readJson<PackageJson>(packageJsonPath);\n\n normalizePackageManifest(pkg, spec, {\n resolveCatalogRefs: true,\n preserveCatalogRefs: false,\n removeWorkspaces: true,\n removePublishScripts: true,\n });\n\n stripDevelopmentExports(pkg);\n assertNoSourceExports(pkg, opts.packageName);\n\n writeJson(packageJsonPath, pkg);\n}\n\nexport function stageReleasePackages(opts: {\n repoRoot: string;\n outDir: string;\n packageNames?: string[];\n}) {\n const packageNames = opts.packageNames ?? [...FRAMEWORK_PACKAGES];\n rmSync(opts.outDir, { recursive: true, force: true });\n mkdirSync(opts.outDir, { recursive: true });\n\n for (const packageName of packageNames) {\n stageReleasePackage({\n repoRoot: opts.repoRoot,\n packageName,\n outDir: join(opts.outDir, packageName),\n });\n }\n}\n"],"mappings":"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"}
|