@lark-apaas/fullstack-cli 1.1.59-alpha.20260718151739 → 1.1.59-alpha.20260719042040
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/index.js +462 -114
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs37 from "fs";
|
|
3
|
+
import path33 from "path";
|
|
4
4
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5
5
|
import { config as dotenvConfig } from "dotenv";
|
|
6
6
|
|
|
@@ -4921,7 +4921,7 @@ var PROMPT_PATTERNS = [
|
|
|
4921
4921
|
{ pattern: /proceed\?/i, answer: "y\n" }
|
|
4922
4922
|
];
|
|
4923
4923
|
async function executeShadcnAdd(registryItemPath) {
|
|
4924
|
-
return new Promise((
|
|
4924
|
+
return new Promise((resolve9) => {
|
|
4925
4925
|
let output = "";
|
|
4926
4926
|
const args = ["--yes", "shadcn@3.8.2", "add", registryItemPath];
|
|
4927
4927
|
const ptyProcess = pty.spawn("npx", args, {
|
|
@@ -4947,7 +4947,7 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
4947
4947
|
});
|
|
4948
4948
|
const timeoutId = setTimeout(() => {
|
|
4949
4949
|
ptyProcess.kill();
|
|
4950
|
-
|
|
4950
|
+
resolve9({
|
|
4951
4951
|
success: false,
|
|
4952
4952
|
files: [],
|
|
4953
4953
|
error: "\u6267\u884C\u8D85\u65F6"
|
|
@@ -4958,7 +4958,7 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
4958
4958
|
const success = exitCode === 0;
|
|
4959
4959
|
const filePaths = parseOutput(output);
|
|
4960
4960
|
const files = filePaths.map(toFileInfo);
|
|
4961
|
-
|
|
4961
|
+
resolve9({
|
|
4962
4962
|
success,
|
|
4963
4963
|
files,
|
|
4964
4964
|
error: success ? void 0 : output || `Process exited with code ${exitCode}`
|
|
@@ -4969,12 +4969,12 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
4969
4969
|
|
|
4970
4970
|
// src/commands/component/add.handler.ts
|
|
4971
4971
|
function runActionPluginInit() {
|
|
4972
|
-
return new Promise((
|
|
4972
|
+
return new Promise((resolve9) => {
|
|
4973
4973
|
execFile("fullstack-cli", ["action-plugin", "init"], { cwd: process.cwd(), stdio: "ignore" }, (error) => {
|
|
4974
4974
|
if (error) {
|
|
4975
4975
|
debug("action-plugin init \u5931\u8D25: %s", error.message);
|
|
4976
4976
|
}
|
|
4977
|
-
|
|
4977
|
+
resolve9();
|
|
4978
4978
|
});
|
|
4979
4979
|
});
|
|
4980
4980
|
}
|
|
@@ -8624,19 +8624,58 @@ async function buildPreviewServerArtifact(options) {
|
|
|
8624
8624
|
|
|
8625
8625
|
// src/commands/preview-artifact/producer.ts
|
|
8626
8626
|
import * as crypto2 from "crypto";
|
|
8627
|
-
import * as
|
|
8628
|
-
import * as
|
|
8627
|
+
import * as fs34 from "fs";
|
|
8628
|
+
import * as path30 from "path";
|
|
8629
8629
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
8630
8630
|
|
|
8631
8631
|
// src/commands/preview-artifact/generation.ts
|
|
8632
8632
|
import * as crypto from "crypto";
|
|
8633
8633
|
import * as fs30 from "fs";
|
|
8634
8634
|
import * as path26 from "path";
|
|
8635
|
-
var PREVIEW_ARTIFACT_SCHEMA_VERSION =
|
|
8635
|
+
var PREVIEW_ARTIFACT_SCHEMA_VERSION = 2;
|
|
8636
|
+
var PREVIEW_ARTIFACT_PROTOCOL_VERSION = 2;
|
|
8636
8637
|
var MANIFEST_FILE = "manifest.json";
|
|
8637
8638
|
var PROBE_FILE = "probe.json";
|
|
8638
8639
|
var SERVER_FILE = "server.bundle.cjs";
|
|
8639
8640
|
var CLIENT_FILE = "client.zip";
|
|
8641
|
+
var RUNTIME_ONLY_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
|
|
8642
|
+
"CLIENT_DEV_HOST",
|
|
8643
|
+
"CLIENT_DEV_PORT",
|
|
8644
|
+
"FORCE_AUTHN_ACCESS_KEY",
|
|
8645
|
+
"FORCE_AUTHN_ACCESS_SECRET",
|
|
8646
|
+
"FORCE_AUTHN_PREVIEW_SESSION_ID",
|
|
8647
|
+
"MIAODA_HMR_WS_TOKEN",
|
|
8648
|
+
"MIAODA_PREVIEW_RUN_ID",
|
|
8649
|
+
"PREVIEW_RUN_ID",
|
|
8650
|
+
"SANDBOX_COOKIE",
|
|
8651
|
+
"SANDBOX_ID",
|
|
8652
|
+
"SERVER_PORT",
|
|
8653
|
+
"preview_run_id"
|
|
8654
|
+
]);
|
|
8655
|
+
var RUNTIME_ONLY_ENVIRONMENT_PREFIXES = ["PREVIEW_ARTIFACT_"];
|
|
8656
|
+
function previewArtifactBuildEnvironment(environment, appBasePath) {
|
|
8657
|
+
const buildEnvironment = {};
|
|
8658
|
+
for (const [key, value] of Object.entries(environment ?? {})) {
|
|
8659
|
+
if (typeof value === "string" && !RUNTIME_ONLY_ENVIRONMENT_KEYS.has(key) && !RUNTIME_ONLY_ENVIRONMENT_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
8660
|
+
buildEnvironment[key] = value;
|
|
8661
|
+
}
|
|
8662
|
+
}
|
|
8663
|
+
buildEnvironment.CLIENT_BASE_PATH = appBasePath;
|
|
8664
|
+
buildEnvironment.NODE_ENV = environment?.NODE_ENV ?? "production";
|
|
8665
|
+
return buildEnvironment;
|
|
8666
|
+
}
|
|
8667
|
+
function previewArtifactBuildEnvironmentHash(environment, appBasePath) {
|
|
8668
|
+
const buildEnvironment = previewArtifactBuildEnvironment(
|
|
8669
|
+
environment,
|
|
8670
|
+
appBasePath
|
|
8671
|
+
);
|
|
8672
|
+
const canonical = Object.fromEntries(
|
|
8673
|
+
Object.entries(buildEnvironment).sort(
|
|
8674
|
+
([left], [right]) => left.localeCompare(right)
|
|
8675
|
+
)
|
|
8676
|
+
);
|
|
8677
|
+
return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
8678
|
+
}
|
|
8640
8679
|
function parseNodeMajor(nodeVersion) {
|
|
8641
8680
|
const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
|
|
8642
8681
|
if (!match) {
|
|
@@ -8683,8 +8722,10 @@ function createPreviewArtifactManifest(options) {
|
|
|
8683
8722
|
const generationDir = path26.resolve(options.generationDir);
|
|
8684
8723
|
const manifest = {
|
|
8685
8724
|
schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
|
|
8725
|
+
protocolVersion: PREVIEW_ARTIFACT_PROTOCOL_VERSION,
|
|
8686
8726
|
generationId: options.generationId,
|
|
8687
8727
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8728
|
+
buildEnvironmentSha256: options.buildEnvironmentSha256,
|
|
8688
8729
|
runtime: runtimeIdentity(
|
|
8689
8730
|
options.nodeVersion,
|
|
8690
8731
|
options.platform,
|
|
@@ -8723,6 +8764,7 @@ function recordPreviewArtifactProbe(options) {
|
|
|
8723
8764
|
coverage: {
|
|
8724
8765
|
html: options.coverage.html,
|
|
8725
8766
|
businessApi: options.coverage.businessApi,
|
|
8767
|
+
clientAssets: Array.from(new Set(options.coverage.clientAssets)).sort(),
|
|
8726
8768
|
actionPlugins: Array.from(new Set(options.coverage.actionPlugins)).sort()
|
|
8727
8769
|
}
|
|
8728
8770
|
};
|
|
@@ -8777,6 +8819,9 @@ function promotePreviewArtifactGeneration(generationDirInput) {
|
|
|
8777
8819
|
if (!probe.coverage.businessApi) {
|
|
8778
8820
|
reasons.push("probe did not verify business API");
|
|
8779
8821
|
}
|
|
8822
|
+
if (!Array.isArray(probe.coverage.clientAssets) || probe.coverage.clientAssets.length === 0) {
|
|
8823
|
+
reasons.push("probe did not verify client entry assets");
|
|
8824
|
+
}
|
|
8780
8825
|
const verifiedPlugins = new Set(probe.coverage.actionPlugins);
|
|
8781
8826
|
const missingPlugins = manifest.actionPlugins.filter(
|
|
8782
8827
|
(plugin) => !verifiedPlugins.has(plugin)
|
|
@@ -8804,11 +8849,18 @@ import * as net from "net";
|
|
|
8804
8849
|
import * as os4 from "os";
|
|
8805
8850
|
import * as path27 from "path";
|
|
8806
8851
|
import { spawn as spawn2, spawnSync as spawnSync7 } from "child_process";
|
|
8852
|
+
|
|
8853
|
+
// src/commands/preview-artifact/readiness.ts
|
|
8854
|
+
function isPreviewBusinessResponseReady(response) {
|
|
8855
|
+
return response.status !== 404 && response.status < 500 && !response.headers.get("content-type")?.includes("text/html");
|
|
8856
|
+
}
|
|
8857
|
+
|
|
8858
|
+
// src/commands/preview-artifact/probe.ts
|
|
8807
8859
|
function sleep(ms) {
|
|
8808
|
-
return new Promise((
|
|
8860
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
8809
8861
|
}
|
|
8810
8862
|
async function reservePort() {
|
|
8811
|
-
return new Promise((
|
|
8863
|
+
return new Promise((resolve9, reject) => {
|
|
8812
8864
|
const server = net.createServer();
|
|
8813
8865
|
server.unref();
|
|
8814
8866
|
server.once("error", reject);
|
|
@@ -8820,7 +8872,7 @@ async function reservePort() {
|
|
|
8820
8872
|
}
|
|
8821
8873
|
server.close((error) => {
|
|
8822
8874
|
if (error) reject(error);
|
|
8823
|
-
else
|
|
8875
|
+
else resolve9(address.port);
|
|
8824
8876
|
});
|
|
8825
8877
|
});
|
|
8826
8878
|
});
|
|
@@ -8871,6 +8923,77 @@ function rejectSymlinks(root) {
|
|
|
8871
8923
|
}
|
|
8872
8924
|
}
|
|
8873
8925
|
}
|
|
8926
|
+
function isPathInside(parent, candidate) {
|
|
8927
|
+
const relative5 = path27.relative(parent, candidate);
|
|
8928
|
+
return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative5);
|
|
8929
|
+
}
|
|
8930
|
+
function clientEntryAssetReferences(html, appBasePath) {
|
|
8931
|
+
const local = /* @__PURE__ */ new Set();
|
|
8932
|
+
const localExecutable = /* @__PURE__ */ new Set();
|
|
8933
|
+
const unsupportedExternal = /* @__PURE__ */ new Set();
|
|
8934
|
+
const tags = /<(script|link)\b[^>]*>/gi;
|
|
8935
|
+
const readAttribute = (tag, name) => new RegExp(`\\b${name}=["']([^"']+)["']`, "i").exec(tag)?.[1];
|
|
8936
|
+
const base = new URL(appBasePath, "http://preview.local");
|
|
8937
|
+
for (const match of html.matchAll(tags)) {
|
|
8938
|
+
const tagName = match[1]?.toLowerCase();
|
|
8939
|
+
const tag = match[0];
|
|
8940
|
+
let linkRel = [];
|
|
8941
|
+
if (tagName === "link") {
|
|
8942
|
+
linkRel = readAttribute(tag, "rel")?.toLowerCase().split(/\s+/) ?? [];
|
|
8943
|
+
if (!linkRel.some(
|
|
8944
|
+
(value2) => [
|
|
8945
|
+
"stylesheet",
|
|
8946
|
+
"modulepreload",
|
|
8947
|
+
"preload",
|
|
8948
|
+
"icon",
|
|
8949
|
+
"manifest"
|
|
8950
|
+
].includes(value2)
|
|
8951
|
+
)) {
|
|
8952
|
+
continue;
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
const value = readAttribute(
|
|
8956
|
+
tag,
|
|
8957
|
+
tagName === "script" ? "src" : "href"
|
|
8958
|
+
)?.trim();
|
|
8959
|
+
if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
|
|
8960
|
+
const resolved = new URL(value, base);
|
|
8961
|
+
if (resolved.origin !== base.origin) {
|
|
8962
|
+
if (tagName === "script" || linkRel.some(
|
|
8963
|
+
(value2) => ["stylesheet", "modulepreload", "preload"].includes(value2)
|
|
8964
|
+
)) {
|
|
8965
|
+
unsupportedExternal.add(resolved.toString());
|
|
8966
|
+
}
|
|
8967
|
+
continue;
|
|
8968
|
+
}
|
|
8969
|
+
local.add(resolved.pathname);
|
|
8970
|
+
if (tagName === "script") localExecutable.add(resolved.pathname);
|
|
8971
|
+
}
|
|
8972
|
+
return {
|
|
8973
|
+
local: Array.from(local).sort(),
|
|
8974
|
+
localExecutable: Array.from(localExecutable).sort(),
|
|
8975
|
+
unsupportedExternal: Array.from(unsupportedExternal).sort()
|
|
8976
|
+
};
|
|
8977
|
+
}
|
|
8978
|
+
function resolveClientEntryAsset(clientRoot, appBasePath, requestPath) {
|
|
8979
|
+
let decoded;
|
|
8980
|
+
try {
|
|
8981
|
+
decoded = decodeURIComponent(requestPath);
|
|
8982
|
+
} catch {
|
|
8983
|
+
return void 0;
|
|
8984
|
+
}
|
|
8985
|
+
const normalizedBase = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
|
|
8986
|
+
const relative5 = decoded.startsWith(normalizedBase) ? decoded.slice(normalizedBase.length) : decoded.replace(/^\/+/, "");
|
|
8987
|
+
if (!relative5 || relative5 === "index.html") return void 0;
|
|
8988
|
+
const candidate = path27.resolve(clientRoot, relative5);
|
|
8989
|
+
if (!isPathInside(clientRoot, candidate)) return void 0;
|
|
8990
|
+
try {
|
|
8991
|
+
const stat = fs31.lstatSync(candidate);
|
|
8992
|
+
return stat.isFile() && !stat.isSymbolicLink() ? candidate : void 0;
|
|
8993
|
+
} catch {
|
|
8994
|
+
return void 0;
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8874
8997
|
function extractClientArchive(archivePath, destination) {
|
|
8875
8998
|
if (fs31.existsSync(destination)) {
|
|
8876
8999
|
throw new Error("client archive destination must not already exist");
|
|
@@ -8928,6 +9051,7 @@ async function runPreviewArtifactProbe(options) {
|
|
|
8928
9051
|
const coverage = {
|
|
8929
9052
|
html: false,
|
|
8930
9053
|
businessApi: false,
|
|
9054
|
+
clientAssets: [],
|
|
8931
9055
|
actionPlugins: []
|
|
8932
9056
|
};
|
|
8933
9057
|
const reasons = [];
|
|
@@ -8940,10 +9064,8 @@ async function runPreviewArtifactProbe(options) {
|
|
|
8940
9064
|
path27.join(generationDir, "server.bundle.cjs"),
|
|
8941
9065
|
runtimeBundle
|
|
8942
9066
|
);
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
path27.join(runtimeRoot, "dist", "client")
|
|
8946
|
-
);
|
|
9067
|
+
const clientRoot = path27.join(runtimeRoot, "dist", "client");
|
|
9068
|
+
extractClientArchive(path27.join(generationDir, "client.zip"), clientRoot);
|
|
8947
9069
|
isolatedNodeModulesPresent = fs31.existsSync(
|
|
8948
9070
|
path27.join(runtimeRoot, "node_modules")
|
|
8949
9071
|
);
|
|
@@ -8974,12 +9096,34 @@ async function runPreviewArtifactProbe(options) {
|
|
|
8974
9096
|
`HTML probe failed with status ${htmlResponse?.status ?? "unreachable"}`
|
|
8975
9097
|
);
|
|
8976
9098
|
}
|
|
9099
|
+
if (coverage.html && htmlResponse) {
|
|
9100
|
+
const html = await htmlResponse.text();
|
|
9101
|
+
const entryReferences = clientEntryAssetReferences(
|
|
9102
|
+
html,
|
|
9103
|
+
options.appBasePath
|
|
9104
|
+
);
|
|
9105
|
+
const entryAssets = entryReferences.local;
|
|
9106
|
+
coverage.clientAssets = entryReferences.localExecutable;
|
|
9107
|
+
if (entryReferences.localExecutable.length === 0) {
|
|
9108
|
+
reasons.push("client HTML has no local executable entry asset");
|
|
9109
|
+
}
|
|
9110
|
+
for (const externalAsset of entryReferences.unsupportedExternal) {
|
|
9111
|
+
reasons.push(
|
|
9112
|
+
`external client entry asset is unsupported: ${externalAsset}`
|
|
9113
|
+
);
|
|
9114
|
+
}
|
|
9115
|
+
for (const entryAsset of entryAssets) {
|
|
9116
|
+
if (!resolveClientEntryAsset(clientRoot, options.appBasePath, entryAsset)) {
|
|
9117
|
+
reasons.push(`client entry asset is unavailable: ${entryAsset}`);
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
8977
9121
|
const apiResponse = await fetchUntil(
|
|
8978
9122
|
new URL(options.businessApiPath, origin).toString(),
|
|
8979
9123
|
timeoutAt
|
|
8980
9124
|
);
|
|
8981
9125
|
coverage.businessApi = Boolean(
|
|
8982
|
-
apiResponse && apiResponse
|
|
9126
|
+
apiResponse && isPreviewBusinessResponseReady(apiResponse)
|
|
8983
9127
|
);
|
|
8984
9128
|
if (!coverage.businessApi) {
|
|
8985
9129
|
reasons.push(
|
|
@@ -8990,10 +9134,16 @@ async function runPreviewArtifactProbe(options) {
|
|
|
8990
9134
|
await sleep(25);
|
|
8991
9135
|
}
|
|
8992
9136
|
if (fs31.existsSync(registryFile)) {
|
|
8993
|
-
const parsed = JSON.parse(
|
|
8994
|
-
|
|
9137
|
+
const parsed = JSON.parse(
|
|
9138
|
+
fs31.readFileSync(registryFile, "utf8")
|
|
9139
|
+
);
|
|
9140
|
+
if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) {
|
|
8995
9141
|
coverage.actionPlugins = parsed.filter((value) => typeof value === "string").sort();
|
|
9142
|
+
} else {
|
|
9143
|
+
reasons.push("Action Plugin registry evidence is invalid");
|
|
8996
9144
|
}
|
|
9145
|
+
} else {
|
|
9146
|
+
reasons.push("Action Plugin registry evidence is unavailable");
|
|
8997
9147
|
}
|
|
8998
9148
|
} catch (error) {
|
|
8999
9149
|
reasons.push(error instanceof Error ? error.message : String(error));
|
|
@@ -9013,14 +9163,40 @@ async function runPreviewArtifactProbe(options) {
|
|
|
9013
9163
|
return { ...probe, isolatedNodeModulesPresent };
|
|
9014
9164
|
}
|
|
9015
9165
|
|
|
9016
|
-
// src/commands/preview-artifact/
|
|
9166
|
+
// src/commands/preview-artifact/environment.ts
|
|
9017
9167
|
import * as fs32 from "fs";
|
|
9018
9168
|
import * as path28 from "path";
|
|
9169
|
+
var ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9170
|
+
function readPreviewArtifactEnvironmentFile(filePath) {
|
|
9171
|
+
const resolved = path28.resolve(filePath);
|
|
9172
|
+
const parsed = JSON.parse(fs32.readFileSync(resolved, "utf8"));
|
|
9173
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9174
|
+
throw new Error("preview artifact environment file must contain an object");
|
|
9175
|
+
}
|
|
9176
|
+
const environment = {};
|
|
9177
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
9178
|
+
if (!ENVIRONMENT_NAME.test(key) || typeof value !== "string") {
|
|
9179
|
+
throw new Error(`invalid environment variable in ${resolved}: ${key}`);
|
|
9180
|
+
}
|
|
9181
|
+
environment[key] = value;
|
|
9182
|
+
}
|
|
9183
|
+
return environment;
|
|
9184
|
+
}
|
|
9185
|
+
function previewArtifactRuntimeEnvironment(environment) {
|
|
9186
|
+
return {
|
|
9187
|
+
...environment,
|
|
9188
|
+
NODE_ENV: environment?.NODE_ENV ?? "production"
|
|
9189
|
+
};
|
|
9190
|
+
}
|
|
9191
|
+
|
|
9192
|
+
// src/commands/preview-artifact/store.ts
|
|
9193
|
+
import * as fs33 from "fs";
|
|
9194
|
+
import * as path29 from "path";
|
|
9019
9195
|
var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
9020
9196
|
function readCurrentPointer(storeRoot) {
|
|
9021
9197
|
try {
|
|
9022
9198
|
const pointer = readJson3(
|
|
9023
|
-
|
|
9199
|
+
path29.join(storeRoot, "current.json")
|
|
9024
9200
|
);
|
|
9025
9201
|
return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) ? pointer : void 0;
|
|
9026
9202
|
} catch {
|
|
@@ -9028,25 +9204,25 @@ function readCurrentPointer(storeRoot) {
|
|
|
9028
9204
|
}
|
|
9029
9205
|
}
|
|
9030
9206
|
function readJson3(filePath) {
|
|
9031
|
-
return JSON.parse(
|
|
9207
|
+
return JSON.parse(fs33.readFileSync(filePath, "utf8"));
|
|
9032
9208
|
}
|
|
9033
9209
|
function writeJsonAtomic2(filePath, value) {
|
|
9034
|
-
|
|
9210
|
+
fs33.mkdirSync(path29.dirname(filePath), { recursive: true });
|
|
9035
9211
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9036
|
-
|
|
9212
|
+
fs33.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
9037
9213
|
`, {
|
|
9038
9214
|
mode: 384
|
|
9039
9215
|
});
|
|
9040
|
-
|
|
9216
|
+
fs33.renameSync(temporaryPath, filePath);
|
|
9041
9217
|
}
|
|
9042
9218
|
function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
|
|
9043
9219
|
const retained = new Set(retainedGenerationIds);
|
|
9044
|
-
for (const entry of
|
|
9220
|
+
for (const entry of fs33.readdirSync(generationsRoot, {
|
|
9045
9221
|
withFileTypes: true
|
|
9046
9222
|
})) {
|
|
9047
9223
|
if (retained.has(entry.name)) continue;
|
|
9048
9224
|
try {
|
|
9049
|
-
|
|
9225
|
+
fs33.rmSync(path29.join(generationsRoot, entry.name), {
|
|
9050
9226
|
recursive: true,
|
|
9051
9227
|
force: true
|
|
9052
9228
|
});
|
|
@@ -9058,13 +9234,13 @@ function parseNodeMajor2(nodeVersion) {
|
|
|
9058
9234
|
const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
|
|
9059
9235
|
return match ? Number(match[1]) : null;
|
|
9060
9236
|
}
|
|
9061
|
-
function
|
|
9062
|
-
const
|
|
9063
|
-
return
|
|
9237
|
+
function isPathInside2(parent, candidate) {
|
|
9238
|
+
const relative5 = path29.relative(parent, candidate);
|
|
9239
|
+
return relative5 !== "" && !relative5.startsWith(`..${path29.sep}`) && relative5 !== ".." && !path29.isAbsolute(relative5);
|
|
9064
9240
|
}
|
|
9065
9241
|
function validateRegularDirectory(directoryPath, label) {
|
|
9066
9242
|
try {
|
|
9067
|
-
const stat =
|
|
9243
|
+
const stat = fs33.lstatSync(directoryPath);
|
|
9068
9244
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
9069
9245
|
return [`${label} must be a regular directory`];
|
|
9070
9246
|
}
|
|
@@ -9085,10 +9261,10 @@ function validateGeneration(generationDir, runtime) {
|
|
|
9085
9261
|
let probe;
|
|
9086
9262
|
try {
|
|
9087
9263
|
manifest = readJson3(
|
|
9088
|
-
|
|
9264
|
+
path29.join(generationDir, "manifest.json")
|
|
9089
9265
|
);
|
|
9090
9266
|
probe = readJson3(
|
|
9091
|
-
|
|
9267
|
+
path29.join(generationDir, "probe.json")
|
|
9092
9268
|
);
|
|
9093
9269
|
} catch (error) {
|
|
9094
9270
|
return {
|
|
@@ -9097,7 +9273,7 @@ function validateGeneration(generationDir, runtime) {
|
|
|
9097
9273
|
]
|
|
9098
9274
|
};
|
|
9099
9275
|
}
|
|
9100
|
-
if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
|
|
9276
|
+
if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || manifest.protocolVersion !== PREVIEW_ARTIFACT_PROTOCOL_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
|
|
9101
9277
|
reasons.push("generation schema version is incompatible");
|
|
9102
9278
|
}
|
|
9103
9279
|
if (!GENERATION_ID_PATTERN.test(manifest.generationId) || probe.generationId !== manifest.generationId) {
|
|
@@ -9109,8 +9285,14 @@ function validateGeneration(generationDir, runtime) {
|
|
|
9109
9285
|
if (!probe.success) {
|
|
9110
9286
|
reasons.push("generation runtime probe did not pass");
|
|
9111
9287
|
}
|
|
9112
|
-
|
|
9113
|
-
|
|
9288
|
+
if (!Array.isArray(probe.coverage?.clientAssets) || probe.coverage.clientAssets.length === 0) {
|
|
9289
|
+
reasons.push("generation client asset evidence is invalid");
|
|
9290
|
+
}
|
|
9291
|
+
if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
|
|
9292
|
+
reasons.push("build environment identity is invalid");
|
|
9293
|
+
}
|
|
9294
|
+
const serverPath = path29.join(generationDir, "server.bundle.cjs");
|
|
9295
|
+
const clientPath = path29.join(generationDir, "client.zip");
|
|
9114
9296
|
try {
|
|
9115
9297
|
if (sha256File(serverPath) !== manifest.files.server.sha256) {
|
|
9116
9298
|
reasons.push("server.bundle.cjs hash does not match manifest");
|
|
@@ -9132,11 +9314,11 @@ function validateGeneration(generationDir, runtime) {
|
|
|
9132
9314
|
return { manifest, reasons };
|
|
9133
9315
|
}
|
|
9134
9316
|
function publishPreviewArtifactGeneration(options) {
|
|
9135
|
-
const storeRoot =
|
|
9317
|
+
const storeRoot = path29.resolve(options.storeRoot);
|
|
9136
9318
|
const previousPointer = readCurrentPointer(storeRoot);
|
|
9137
|
-
const stagingRoot =
|
|
9138
|
-
const stagingDir =
|
|
9139
|
-
if (!
|
|
9319
|
+
const stagingRoot = path29.join(storeRoot, ".staging");
|
|
9320
|
+
const stagingDir = path29.resolve(options.stagingDir);
|
|
9321
|
+
if (!isPathInside2(stagingRoot, stagingDir)) {
|
|
9140
9322
|
return {
|
|
9141
9323
|
published: false,
|
|
9142
9324
|
reasons: [
|
|
@@ -9149,23 +9331,23 @@ function publishPreviewArtifactGeneration(options) {
|
|
|
9149
9331
|
return { published: false, reasons: validation.reasons };
|
|
9150
9332
|
}
|
|
9151
9333
|
const generationId = validation.manifest.generationId;
|
|
9152
|
-
const generationsRoot =
|
|
9153
|
-
const generationDir =
|
|
9154
|
-
|
|
9155
|
-
if (
|
|
9334
|
+
const generationsRoot = path29.join(storeRoot, "generations");
|
|
9335
|
+
const generationDir = path29.join(generationsRoot, generationId);
|
|
9336
|
+
fs33.mkdirSync(generationsRoot, { recursive: true });
|
|
9337
|
+
if (fs33.existsSync(generationDir)) {
|
|
9156
9338
|
return {
|
|
9157
9339
|
published: false,
|
|
9158
9340
|
reasons: [`generation already exists: ${generationId}`]
|
|
9159
9341
|
};
|
|
9160
9342
|
}
|
|
9161
9343
|
try {
|
|
9162
|
-
|
|
9344
|
+
fs33.renameSync(stagingDir, generationDir);
|
|
9163
9345
|
const pointer = {
|
|
9164
9346
|
schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
|
|
9165
9347
|
generationId,
|
|
9166
9348
|
publishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9167
9349
|
};
|
|
9168
|
-
writeJsonAtomic2(
|
|
9350
|
+
writeJsonAtomic2(path29.join(storeRoot, "current.json"), pointer);
|
|
9169
9351
|
pruneSupersededGenerations(
|
|
9170
9352
|
generationsRoot,
|
|
9171
9353
|
[generationId, previousPointer?.generationId].filter(
|
|
@@ -9183,10 +9365,10 @@ function publishPreviewArtifactGeneration(options) {
|
|
|
9183
9365
|
}
|
|
9184
9366
|
}
|
|
9185
9367
|
function resolveCurrentPreviewArtifact(options) {
|
|
9186
|
-
const storeRoot =
|
|
9368
|
+
const storeRoot = path29.resolve(options.storeRoot);
|
|
9187
9369
|
let pointer;
|
|
9188
9370
|
try {
|
|
9189
|
-
pointer = readJson3(
|
|
9371
|
+
pointer = readJson3(path29.join(storeRoot, "current.json"));
|
|
9190
9372
|
} catch (error) {
|
|
9191
9373
|
return {
|
|
9192
9374
|
resolved: false,
|
|
@@ -9205,7 +9387,7 @@ function resolveCurrentPreviewArtifact(options) {
|
|
|
9205
9387
|
if (nodeMajor == null) {
|
|
9206
9388
|
return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
|
|
9207
9389
|
}
|
|
9208
|
-
const generationDir =
|
|
9390
|
+
const generationDir = path29.join(
|
|
9209
9391
|
storeRoot,
|
|
9210
9392
|
"generations",
|
|
9211
9393
|
pointer.generationId
|
|
@@ -9224,6 +9406,12 @@ function resolveCurrentPreviewArtifact(options) {
|
|
|
9224
9406
|
reasons: ["current pointer does not match generation manifest"]
|
|
9225
9407
|
};
|
|
9226
9408
|
}
|
|
9409
|
+
if (validation.manifest.buildEnvironmentSha256 !== options.buildEnvironmentSha256) {
|
|
9410
|
+
return {
|
|
9411
|
+
resolved: false,
|
|
9412
|
+
reasons: ["build environment is incompatible with generation"]
|
|
9413
|
+
};
|
|
9414
|
+
}
|
|
9227
9415
|
return {
|
|
9228
9416
|
resolved: true,
|
|
9229
9417
|
generationId: pointer.generationId,
|
|
@@ -9248,27 +9436,27 @@ function runBuildCommand(projectRoot, buildCommand, environment) {
|
|
|
9248
9436
|
return [`production build failed with exit ${result.status ?? "unknown"}`];
|
|
9249
9437
|
}
|
|
9250
9438
|
function createClientArchive(clientDir, archivePath) {
|
|
9251
|
-
if (!
|
|
9439
|
+
if (!fs34.existsSync(path30.join(clientDir, "index.html"))) {
|
|
9252
9440
|
return [`client build output is missing: ${clientDir}`];
|
|
9253
9441
|
}
|
|
9254
9442
|
const result = spawnSync8("zip", ["-qry", archivePath, "."], {
|
|
9255
9443
|
cwd: clientDir,
|
|
9256
9444
|
encoding: "utf8"
|
|
9257
9445
|
});
|
|
9258
|
-
if (result.status === 0 &&
|
|
9446
|
+
if (result.status === 0 && fs34.existsSync(archivePath)) return [];
|
|
9259
9447
|
return [`client archive failed with exit ${result.status ?? "unknown"}`];
|
|
9260
9448
|
}
|
|
9261
9449
|
function readActionPlugins2(metadataFile) {
|
|
9262
|
-
const metadata = JSON.parse(
|
|
9450
|
+
const metadata = JSON.parse(fs34.readFileSync(metadataFile, "utf8"));
|
|
9263
9451
|
if (!Array.isArray(metadata.actionPlugins)) return [];
|
|
9264
9452
|
return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
|
|
9265
9453
|
}
|
|
9266
9454
|
async function producePreviewArtifactGeneration(options) {
|
|
9267
9455
|
const startedAt = Date.now();
|
|
9268
|
-
const projectRoot =
|
|
9269
|
-
const storeRoot =
|
|
9456
|
+
const projectRoot = path30.resolve(options.projectRoot);
|
|
9457
|
+
const storeRoot = path30.resolve(options.storeRoot);
|
|
9270
9458
|
const generationId = options.generationId ?? defaultGenerationId();
|
|
9271
|
-
const stagingDir =
|
|
9459
|
+
const stagingDir = path30.join(
|
|
9272
9460
|
storeRoot,
|
|
9273
9461
|
".staging",
|
|
9274
9462
|
`${generationId}.${process.pid}.${Date.now()}`
|
|
@@ -9280,16 +9468,20 @@ async function producePreviewArtifactGeneration(options) {
|
|
|
9280
9468
|
reasons,
|
|
9281
9469
|
...probe ? { probe } : {}
|
|
9282
9470
|
});
|
|
9283
|
-
|
|
9471
|
+
fs34.mkdirSync(stagingDir, { recursive: true });
|
|
9284
9472
|
try {
|
|
9473
|
+
const buildEnvironment = previewArtifactBuildEnvironment(
|
|
9474
|
+
options.environment,
|
|
9475
|
+
options.appBasePath
|
|
9476
|
+
);
|
|
9285
9477
|
const buildReasons = runBuildCommand(
|
|
9286
9478
|
projectRoot,
|
|
9287
9479
|
options.buildCommand ?? { command: "npm", args: ["run", "build:prod"] },
|
|
9288
|
-
|
|
9480
|
+
buildEnvironment
|
|
9289
9481
|
);
|
|
9290
9482
|
if (buildReasons.length > 0) return failed(buildReasons);
|
|
9291
|
-
const serverOutfile =
|
|
9292
|
-
const serverMetadataFile =
|
|
9483
|
+
const serverOutfile = path30.join(stagingDir, "server.bundle.cjs");
|
|
9484
|
+
const serverMetadataFile = path30.join(stagingDir, "server-build.json");
|
|
9293
9485
|
const serverResult = await buildPreviewServerArtifact({
|
|
9294
9486
|
projectRoot,
|
|
9295
9487
|
entry: "dist/main.js",
|
|
@@ -9298,25 +9490,29 @@ async function producePreviewArtifactGeneration(options) {
|
|
|
9298
9490
|
});
|
|
9299
9491
|
if (!serverResult.built) return failed(serverResult.reasons);
|
|
9300
9492
|
const archiveReasons = createClientArchive(
|
|
9301
|
-
|
|
9302
|
-
|
|
9493
|
+
path30.join(projectRoot, "dist", "client"),
|
|
9494
|
+
path30.join(stagingDir, "client.zip")
|
|
9303
9495
|
);
|
|
9304
9496
|
if (archiveReasons.length > 0) return failed(archiveReasons);
|
|
9305
9497
|
const actionPlugins = readActionPlugins2(serverMetadataFile);
|
|
9306
|
-
|
|
9498
|
+
fs34.rmSync(serverMetadataFile, { force: true });
|
|
9307
9499
|
createPreviewArtifactManifest({
|
|
9308
9500
|
generationDir: stagingDir,
|
|
9309
9501
|
generationId,
|
|
9310
9502
|
nodeVersion: process.version,
|
|
9311
9503
|
platform: process.platform,
|
|
9312
9504
|
arch: process.arch,
|
|
9505
|
+
buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
|
|
9506
|
+
options.environment,
|
|
9507
|
+
options.appBasePath
|
|
9508
|
+
),
|
|
9313
9509
|
actionPlugins
|
|
9314
9510
|
});
|
|
9315
9511
|
const probe = await runPreviewArtifactProbe({
|
|
9316
9512
|
generationDir: stagingDir,
|
|
9317
9513
|
appBasePath: options.appBasePath,
|
|
9318
9514
|
businessApiPath: options.businessApiPath,
|
|
9319
|
-
environment: options.environment
|
|
9515
|
+
environment: previewArtifactRuntimeEnvironment(options.environment)
|
|
9320
9516
|
});
|
|
9321
9517
|
if (!probe.success) return failed(probe.reasons, probe);
|
|
9322
9518
|
const promotion = promotePreviewArtifactGeneration(stagingDir);
|
|
@@ -9336,23 +9532,25 @@ async function producePreviewArtifactGeneration(options) {
|
|
|
9336
9532
|
} catch (error) {
|
|
9337
9533
|
return failed([error instanceof Error ? error.message : String(error)]);
|
|
9338
9534
|
} finally {
|
|
9339
|
-
|
|
9535
|
+
fs34.rmSync(stagingDir, { recursive: true, force: true });
|
|
9340
9536
|
}
|
|
9341
9537
|
}
|
|
9342
9538
|
|
|
9343
9539
|
// src/commands/preview-artifact/runtime.ts
|
|
9344
|
-
import * as
|
|
9540
|
+
import * as fs36 from "fs";
|
|
9345
9541
|
import * as net3 from "net";
|
|
9346
|
-
import * as
|
|
9542
|
+
import * as path32 from "path";
|
|
9347
9543
|
import { spawn as spawn3 } from "child_process";
|
|
9348
9544
|
|
|
9349
9545
|
// src/commands/preview-artifact/switcher.ts
|
|
9350
9546
|
import * as http from "http";
|
|
9351
9547
|
import * as net2 from "net";
|
|
9548
|
+
import * as fs35 from "fs";
|
|
9549
|
+
import * as path31 from "path";
|
|
9352
9550
|
function targetOrigin(target) {
|
|
9353
9551
|
return `http://${target.host}:${target.port}`;
|
|
9354
9552
|
}
|
|
9355
|
-
function proxyHttpRequest(target, request2, response) {
|
|
9553
|
+
function proxyHttpRequest(target, request2, response, fallback) {
|
|
9356
9554
|
const upstream = http.request(
|
|
9357
9555
|
{
|
|
9358
9556
|
host: target.host,
|
|
@@ -9362,6 +9560,13 @@ function proxyHttpRequest(target, request2, response) {
|
|
|
9362
9560
|
headers: request2.headers
|
|
9363
9561
|
},
|
|
9364
9562
|
(upstreamResponse) => {
|
|
9563
|
+
const upstreamContentType = upstreamResponse.headers["content-type"];
|
|
9564
|
+
const isHtmlFallback = typeof upstreamContentType === "string" && upstreamContentType.toLowerCase().includes("text/html");
|
|
9565
|
+
if (fallback && (upstreamResponse.statusCode === 404 || isHtmlFallback)) {
|
|
9566
|
+
upstreamResponse.resume();
|
|
9567
|
+
fallback();
|
|
9568
|
+
return;
|
|
9569
|
+
}
|
|
9365
9570
|
response.writeHead(
|
|
9366
9571
|
upstreamResponse.statusCode ?? 502,
|
|
9367
9572
|
upstreamResponse.headers
|
|
@@ -9382,6 +9587,72 @@ function proxyHttpRequest(target, request2, response) {
|
|
|
9382
9587
|
});
|
|
9383
9588
|
request2.pipe(upstream);
|
|
9384
9589
|
}
|
|
9590
|
+
function cachedAssetFile(request2, cachedAssetFiles) {
|
|
9591
|
+
if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
|
|
9592
|
+
if (!cachedAssetFiles) return void 0;
|
|
9593
|
+
const pathname = new URL(request2.url ?? "/", "http://preview.local").pathname;
|
|
9594
|
+
return cachedAssetFiles.get(pathname);
|
|
9595
|
+
}
|
|
9596
|
+
function contentType(filePath) {
|
|
9597
|
+
switch (path31.extname(filePath).toLowerCase()) {
|
|
9598
|
+
case ".js":
|
|
9599
|
+
case ".mjs":
|
|
9600
|
+
return "text/javascript; charset=utf-8";
|
|
9601
|
+
case ".css":
|
|
9602
|
+
return "text/css; charset=utf-8";
|
|
9603
|
+
case ".json":
|
|
9604
|
+
case ".map":
|
|
9605
|
+
return "application/json; charset=utf-8";
|
|
9606
|
+
case ".svg":
|
|
9607
|
+
return "image/svg+xml";
|
|
9608
|
+
case ".png":
|
|
9609
|
+
return "image/png";
|
|
9610
|
+
case ".jpg":
|
|
9611
|
+
case ".jpeg":
|
|
9612
|
+
return "image/jpeg";
|
|
9613
|
+
case ".gif":
|
|
9614
|
+
return "image/gif";
|
|
9615
|
+
case ".webp":
|
|
9616
|
+
return "image/webp";
|
|
9617
|
+
case ".ico":
|
|
9618
|
+
return "image/x-icon";
|
|
9619
|
+
case ".woff":
|
|
9620
|
+
return "font/woff";
|
|
9621
|
+
case ".woff2":
|
|
9622
|
+
return "font/woff2";
|
|
9623
|
+
case ".ttf":
|
|
9624
|
+
return "font/ttf";
|
|
9625
|
+
case ".wasm":
|
|
9626
|
+
return "application/wasm";
|
|
9627
|
+
default:
|
|
9628
|
+
return "application/octet-stream";
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
function serveCachedAsset(request2, response, filePath) {
|
|
9632
|
+
let stat;
|
|
9633
|
+
try {
|
|
9634
|
+
stat = fs35.lstatSync(filePath);
|
|
9635
|
+
} catch {
|
|
9636
|
+
response.writeHead(404).end();
|
|
9637
|
+
return;
|
|
9638
|
+
}
|
|
9639
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
9640
|
+
response.writeHead(404).end();
|
|
9641
|
+
return;
|
|
9642
|
+
}
|
|
9643
|
+
response.writeHead(200, {
|
|
9644
|
+
"content-type": contentType(filePath),
|
|
9645
|
+
"content-length": stat.size,
|
|
9646
|
+
"cache-control": "no-store"
|
|
9647
|
+
});
|
|
9648
|
+
if (request2.method === "HEAD") {
|
|
9649
|
+
response.end();
|
|
9650
|
+
return;
|
|
9651
|
+
}
|
|
9652
|
+
const stream = fs35.createReadStream(filePath);
|
|
9653
|
+
stream.on("error", () => response.destroy());
|
|
9654
|
+
stream.pipe(response);
|
|
9655
|
+
}
|
|
9385
9656
|
function proxyUpgrade(target, request2, socket, head) {
|
|
9386
9657
|
const upstream = net2.connect(target.port, target.host, () => {
|
|
9387
9658
|
const headers = Object.entries(request2.headers).flatMap(([name, value]) => {
|
|
@@ -9400,14 +9671,19 @@ ${headers}\r
|
|
|
9400
9671
|
upstream.on("error", () => socket.destroy());
|
|
9401
9672
|
socket.on("error", () => upstream.destroy());
|
|
9402
9673
|
}
|
|
9403
|
-
async function freshTargetReady(target, healthPath) {
|
|
9674
|
+
async function freshTargetReady(target, healthPath, businessPath) {
|
|
9404
9675
|
try {
|
|
9405
9676
|
const response = await fetch(new URL(healthPath, targetOrigin(target)), {
|
|
9406
9677
|
signal: AbortSignal.timeout(1e3)
|
|
9407
9678
|
});
|
|
9408
9679
|
if (response.status !== 200) return false;
|
|
9409
9680
|
const body = await response.json();
|
|
9410
|
-
|
|
9681
|
+
if (body.ready !== true) return false;
|
|
9682
|
+
const businessResponse = await fetch(
|
|
9683
|
+
new URL(businessPath, targetOrigin(target)),
|
|
9684
|
+
{ signal: AbortSignal.timeout(1e3), redirect: "manual" }
|
|
9685
|
+
);
|
|
9686
|
+
return isPreviewBusinessResponseReady(businessResponse);
|
|
9411
9687
|
} catch {
|
|
9412
9688
|
return false;
|
|
9413
9689
|
}
|
|
@@ -9420,8 +9696,8 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9420
9696
|
let closed = false;
|
|
9421
9697
|
let pollTimer;
|
|
9422
9698
|
let settleCutover;
|
|
9423
|
-
const cutover = new Promise((
|
|
9424
|
-
settleCutover =
|
|
9699
|
+
const cutover = new Promise((resolve9) => {
|
|
9700
|
+
settleCutover = resolve9;
|
|
9425
9701
|
});
|
|
9426
9702
|
const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
|
|
9427
9703
|
const server = http.createServer((request2, response) => {
|
|
@@ -9436,7 +9712,14 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9436
9712
|
);
|
|
9437
9713
|
return;
|
|
9438
9714
|
}
|
|
9439
|
-
|
|
9715
|
+
const assetFile = cachedAssetFile(request2, options.cachedAssetFiles);
|
|
9716
|
+
if (active === "cached" && assetFile) {
|
|
9717
|
+
serveCachedAsset(request2, response, assetFile);
|
|
9718
|
+
return;
|
|
9719
|
+
}
|
|
9720
|
+
const target = currentTarget();
|
|
9721
|
+
const fallback = active === "fresh" && assetFile ? () => serveCachedAsset(request2, response, assetFile) : void 0;
|
|
9722
|
+
proxyHttpRequest(target, request2, response, fallback);
|
|
9440
9723
|
});
|
|
9441
9724
|
server.on("upgrade", (request2, socket, head) => {
|
|
9442
9725
|
proxyUpgrade(currentTarget(), request2, socket, head);
|
|
@@ -9444,7 +9727,11 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9444
9727
|
const schedulePoll = () => {
|
|
9445
9728
|
if (closed || active === "fresh") return;
|
|
9446
9729
|
pollTimer = setTimeout(async () => {
|
|
9447
|
-
if (await freshTargetReady(
|
|
9730
|
+
if (await freshTargetReady(
|
|
9731
|
+
options.freshTarget,
|
|
9732
|
+
healthPath,
|
|
9733
|
+
options.freshBusinessPath
|
|
9734
|
+
)) {
|
|
9448
9735
|
active = "fresh";
|
|
9449
9736
|
settleCutover?.({
|
|
9450
9737
|
switched: true,
|
|
@@ -9464,7 +9751,7 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9464
9751
|
},
|
|
9465
9752
|
cutover,
|
|
9466
9753
|
start() {
|
|
9467
|
-
return new Promise((
|
|
9754
|
+
return new Promise((resolve9, reject) => {
|
|
9468
9755
|
server.once("error", reject);
|
|
9469
9756
|
server.listen(options.listenPort, listenHost, () => {
|
|
9470
9757
|
const address = server.address();
|
|
@@ -9473,7 +9760,7 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9473
9760
|
return;
|
|
9474
9761
|
}
|
|
9475
9762
|
schedulePoll();
|
|
9476
|
-
|
|
9763
|
+
resolve9({
|
|
9477
9764
|
host: listenHost,
|
|
9478
9765
|
port: address.port,
|
|
9479
9766
|
origin: `http://${listenHost}:${address.port}`
|
|
@@ -9492,8 +9779,8 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9492
9779
|
});
|
|
9493
9780
|
settleCutover = void 0;
|
|
9494
9781
|
}
|
|
9495
|
-
return new Promise((
|
|
9496
|
-
server.close(() =>
|
|
9782
|
+
return new Promise((resolve9) => {
|
|
9783
|
+
server.close(() => resolve9());
|
|
9497
9784
|
server.closeAllConnections?.();
|
|
9498
9785
|
});
|
|
9499
9786
|
}
|
|
@@ -9502,10 +9789,10 @@ function createPreviewArtifactSwitcher(options) {
|
|
|
9502
9789
|
|
|
9503
9790
|
// src/commands/preview-artifact/runtime.ts
|
|
9504
9791
|
function sleep2(ms) {
|
|
9505
|
-
return new Promise((
|
|
9792
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
9506
9793
|
}
|
|
9507
9794
|
async function reservePort2() {
|
|
9508
|
-
return new Promise((
|
|
9795
|
+
return new Promise((resolve9, reject) => {
|
|
9509
9796
|
const server = net3.createServer();
|
|
9510
9797
|
server.unref();
|
|
9511
9798
|
server.once("error", reject);
|
|
@@ -9517,7 +9804,7 @@ async function reservePort2() {
|
|
|
9517
9804
|
}
|
|
9518
9805
|
server.close((error) => {
|
|
9519
9806
|
if (error) reject(error);
|
|
9520
|
-
else
|
|
9807
|
+
else resolve9(address.port);
|
|
9521
9808
|
});
|
|
9522
9809
|
});
|
|
9523
9810
|
});
|
|
@@ -9534,27 +9821,53 @@ function stopProcessGroup2(child) {
|
|
|
9534
9821
|
}
|
|
9535
9822
|
}
|
|
9536
9823
|
}
|
|
9537
|
-
function
|
|
9538
|
-
const
|
|
9539
|
-
return
|
|
9824
|
+
function isPathInside3(parent, candidate) {
|
|
9825
|
+
const relative5 = path32.relative(parent, candidate);
|
|
9826
|
+
return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative5);
|
|
9540
9827
|
}
|
|
9541
9828
|
function ensureRegularRuntimeRoot(runtimeRoot) {
|
|
9542
|
-
|
|
9543
|
-
const stat =
|
|
9829
|
+
fs36.mkdirSync(runtimeRoot, { recursive: true });
|
|
9830
|
+
const stat = fs36.lstatSync(runtimeRoot);
|
|
9544
9831
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
9545
9832
|
throw new Error("runtime root must be a regular directory");
|
|
9546
9833
|
}
|
|
9547
9834
|
}
|
|
9835
|
+
function collectCachedAssetFiles(clientRoot, appBasePath) {
|
|
9836
|
+
const files = /* @__PURE__ */ new Map();
|
|
9837
|
+
const basePath = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
|
|
9838
|
+
const visit = (directory) => {
|
|
9839
|
+
for (const entry of fs36.readdirSync(directory, { withFileTypes: true })) {
|
|
9840
|
+
const absolute = path32.join(directory, entry.name);
|
|
9841
|
+
if (entry.isDirectory()) {
|
|
9842
|
+
visit(absolute);
|
|
9843
|
+
continue;
|
|
9844
|
+
}
|
|
9845
|
+
if (!entry.isFile() || entry.isSymbolicLink()) continue;
|
|
9846
|
+
const relative5 = path32.relative(clientRoot, absolute).split(path32.sep).join("/");
|
|
9847
|
+
if (relative5 === "index.html") continue;
|
|
9848
|
+
files.set(
|
|
9849
|
+
new URL(`/${relative5}`, "http://preview.local").pathname,
|
|
9850
|
+
absolute
|
|
9851
|
+
);
|
|
9852
|
+
files.set(
|
|
9853
|
+
new URL(relative5, `http://preview.local${basePath}`).pathname,
|
|
9854
|
+
absolute
|
|
9855
|
+
);
|
|
9856
|
+
}
|
|
9857
|
+
};
|
|
9858
|
+
visit(clientRoot);
|
|
9859
|
+
return files;
|
|
9860
|
+
}
|
|
9548
9861
|
function writeMarkerAtomic(markerFile, marker) {
|
|
9549
|
-
|
|
9862
|
+
fs36.mkdirSync(path32.dirname(markerFile), { recursive: true });
|
|
9550
9863
|
const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
|
|
9551
|
-
|
|
9864
|
+
fs36.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
|
|
9552
9865
|
`, {
|
|
9553
9866
|
mode: 384
|
|
9554
9867
|
});
|
|
9555
|
-
|
|
9868
|
+
fs36.renameSync(temporaryFile, markerFile);
|
|
9556
9869
|
}
|
|
9557
|
-
async function waitForCachedRuntime(origin, appBasePath, timeoutMs, child) {
|
|
9870
|
+
async function waitForCachedRuntime(origin, appBasePath, businessApiPath, timeoutMs, child) {
|
|
9558
9871
|
const timeoutAt = Date.now() + timeoutMs;
|
|
9559
9872
|
while (Date.now() < timeoutAt) {
|
|
9560
9873
|
if (child.exitCode != null || child.signalCode != null) {
|
|
@@ -9567,7 +9880,15 @@ async function waitForCachedRuntime(origin, appBasePath, timeoutMs, child) {
|
|
|
9567
9880
|
redirect: "manual",
|
|
9568
9881
|
signal: AbortSignal.timeout(1e3)
|
|
9569
9882
|
});
|
|
9570
|
-
if (response.status === 200)
|
|
9883
|
+
if (response.status === 200) {
|
|
9884
|
+
const businessResponse = await fetch(new URL(businessApiPath, origin), {
|
|
9885
|
+
redirect: "manual",
|
|
9886
|
+
signal: AbortSignal.timeout(1e3)
|
|
9887
|
+
});
|
|
9888
|
+
if (isPreviewBusinessResponseReady(businessResponse)) {
|
|
9889
|
+
return;
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9571
9892
|
} catch {
|
|
9572
9893
|
}
|
|
9573
9894
|
await sleep2(50);
|
|
@@ -9579,11 +9900,15 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9579
9900
|
storeRoot: options.storeRoot,
|
|
9580
9901
|
nodeVersion: process.version,
|
|
9581
9902
|
platform: process.platform,
|
|
9582
|
-
arch: process.arch
|
|
9903
|
+
arch: process.arch,
|
|
9904
|
+
buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
|
|
9905
|
+
options.environment,
|
|
9906
|
+
options.appBasePath
|
|
9907
|
+
)
|
|
9583
9908
|
});
|
|
9584
9909
|
if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
|
|
9585
|
-
const runtimeRoot =
|
|
9586
|
-
const markerFile =
|
|
9910
|
+
const runtimeRoot = path32.resolve(options.runtimeRoot);
|
|
9911
|
+
const markerFile = path32.resolve(options.markerFile);
|
|
9587
9912
|
let runtimeDir;
|
|
9588
9913
|
let cachedRuntime;
|
|
9589
9914
|
let switcher;
|
|
@@ -9593,25 +9918,26 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9593
9918
|
closed = true;
|
|
9594
9919
|
stopProcessGroup2(cachedRuntime);
|
|
9595
9920
|
await switcher?.close();
|
|
9596
|
-
|
|
9597
|
-
if (runtimeDir)
|
|
9921
|
+
fs36.rmSync(markerFile, { force: true });
|
|
9922
|
+
if (runtimeDir) fs36.rmSync(runtimeDir, { recursive: true, force: true });
|
|
9598
9923
|
};
|
|
9599
9924
|
try {
|
|
9600
9925
|
ensureRegularRuntimeRoot(runtimeRoot);
|
|
9601
|
-
if (!
|
|
9926
|
+
if (!isPathInside3(runtimeRoot, markerFile)) {
|
|
9602
9927
|
throw new Error("runtime marker must be inside the runtime root");
|
|
9603
9928
|
}
|
|
9604
|
-
runtimeDir =
|
|
9605
|
-
|
|
9929
|
+
runtimeDir = fs36.mkdtempSync(
|
|
9930
|
+
path32.join(runtimeRoot, `${resolved.generationId}-`)
|
|
9606
9931
|
);
|
|
9607
|
-
const runtimeBundle =
|
|
9608
|
-
|
|
9609
|
-
|
|
9932
|
+
const runtimeBundle = path32.join(runtimeDir, "server.bundle.cjs");
|
|
9933
|
+
fs36.copyFileSync(
|
|
9934
|
+
path32.join(resolved.generationDir, "server.bundle.cjs"),
|
|
9610
9935
|
runtimeBundle
|
|
9611
9936
|
);
|
|
9937
|
+
const clientRoot = path32.join(runtimeDir, "dist", "client");
|
|
9612
9938
|
extractClientArchive(
|
|
9613
|
-
|
|
9614
|
-
|
|
9939
|
+
path32.join(resolved.generationDir, "client.zip"),
|
|
9940
|
+
clientRoot
|
|
9615
9941
|
);
|
|
9616
9942
|
const cachedServerPort = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
|
|
9617
9943
|
cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
|
|
@@ -9620,7 +9946,7 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9620
9946
|
stdio: "ignore",
|
|
9621
9947
|
env: {
|
|
9622
9948
|
...process.env,
|
|
9623
|
-
...options.environment,
|
|
9949
|
+
...previewArtifactRuntimeEnvironment(options.environment),
|
|
9624
9950
|
SERVER_PORT: String(cachedServerPort)
|
|
9625
9951
|
}
|
|
9626
9952
|
});
|
|
@@ -9628,6 +9954,7 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9628
9954
|
await waitForCachedRuntime(
|
|
9629
9955
|
cachedOrigin,
|
|
9630
9956
|
options.appBasePath,
|
|
9957
|
+
options.businessApiPath,
|
|
9631
9958
|
options.startupTimeoutMs ?? 3e4,
|
|
9632
9959
|
cachedRuntime
|
|
9633
9960
|
);
|
|
@@ -9637,6 +9964,11 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9637
9964
|
generationId: resolved.generationId,
|
|
9638
9965
|
cachedTarget: { host: "127.0.0.1", port: cachedServerPort },
|
|
9639
9966
|
freshTarget: { host: "127.0.0.1", port: options.freshClientPort },
|
|
9967
|
+
freshBusinessPath: options.businessApiPath,
|
|
9968
|
+
cachedAssetFiles: collectCachedAssetFiles(
|
|
9969
|
+
clientRoot,
|
|
9970
|
+
options.appBasePath
|
|
9971
|
+
),
|
|
9640
9972
|
pollIntervalMs: options.pollIntervalMs
|
|
9641
9973
|
});
|
|
9642
9974
|
const address = await switcher.start();
|
|
@@ -9656,9 +9988,6 @@ async function startPreviewArtifactRuntime(options) {
|
|
|
9656
9988
|
runtimeMarker.status = "fresh-serving";
|
|
9657
9989
|
runtimeMarker.cutoverAt = result.at;
|
|
9658
9990
|
writeMarkerAtomic(markerFile, runtimeMarker);
|
|
9659
|
-
await sleep2(options.cachedDrainMs ?? 5e3);
|
|
9660
|
-
stopProcessGroup2(cachedRuntime);
|
|
9661
|
-
cachedRuntime = void 0;
|
|
9662
9991
|
}
|
|
9663
9992
|
return result;
|
|
9664
9993
|
});
|
|
@@ -9748,9 +10077,17 @@ var previewArtifactGenerationCommand = {
|
|
|
9748
10077
|
program.command(this.name).description(this.description).option("--project-root <dir>", "Application project root", process.cwd()).requiredOption("--store-root <dir>", "Immutable generation store root").option("--generation-id <id>", "Explicit immutable generation ID").requiredOption("--app-base-path <path>", "Application HTML probe path").requiredOption(
|
|
9749
10078
|
"--business-api-path <path>",
|
|
9750
10079
|
"Business API probe path that must not return 404/5xx"
|
|
10080
|
+
).requiredOption(
|
|
10081
|
+
"--environment-file <file>",
|
|
10082
|
+
"JSON application environment passed only to build/probe children"
|
|
9751
10083
|
).action(
|
|
9752
10084
|
async (options) => {
|
|
9753
|
-
const result = await producePreviewArtifactGeneration(
|
|
10085
|
+
const result = await producePreviewArtifactGeneration({
|
|
10086
|
+
...options,
|
|
10087
|
+
environment: readPreviewArtifactEnvironmentFile(
|
|
10088
|
+
options.environmentFile
|
|
10089
|
+
)
|
|
10090
|
+
});
|
|
9754
10091
|
console.log(JSON.stringify(result));
|
|
9755
10092
|
if (!result.published) process.exitCode = 2;
|
|
9756
10093
|
}
|
|
@@ -9799,9 +10136,20 @@ var previewArtifactRuntimeCommand = {
|
|
|
9799
10136
|
).requiredOption(
|
|
9800
10137
|
"--app-base-path <path>",
|
|
9801
10138
|
"Application HTML readiness path"
|
|
10139
|
+
).requiredOption(
|
|
10140
|
+
"--business-api-path <path>",
|
|
10141
|
+
"Business API path revalidated with the current runtime environment"
|
|
10142
|
+
).requiredOption(
|
|
10143
|
+
"--environment-file <file>",
|
|
10144
|
+
"JSON application environment passed only to cached application child"
|
|
9802
10145
|
).action(
|
|
9803
10146
|
async (options) => {
|
|
9804
|
-
const runtime = await startPreviewArtifactRuntime(
|
|
10147
|
+
const runtime = await startPreviewArtifactRuntime({
|
|
10148
|
+
...options,
|
|
10149
|
+
environment: readPreviewArtifactEnvironmentFile(
|
|
10150
|
+
options.environmentFile
|
|
10151
|
+
)
|
|
10152
|
+
});
|
|
9805
10153
|
if (!runtime.started) {
|
|
9806
10154
|
console.log(JSON.stringify(runtime));
|
|
9807
10155
|
process.exitCode = 2;
|
|
@@ -9855,12 +10203,12 @@ var commands = [
|
|
|
9855
10203
|
];
|
|
9856
10204
|
|
|
9857
10205
|
// src/index.ts
|
|
9858
|
-
var envPath =
|
|
9859
|
-
if (
|
|
10206
|
+
var envPath = path33.join(process.cwd(), ".env");
|
|
10207
|
+
if (fs37.existsSync(envPath)) {
|
|
9860
10208
|
dotenvConfig({ path: envPath });
|
|
9861
10209
|
}
|
|
9862
|
-
var __dirname =
|
|
9863
|
-
var pkg = JSON.parse(
|
|
10210
|
+
var __dirname = path33.dirname(fileURLToPath5(import.meta.url));
|
|
10211
|
+
var pkg = JSON.parse(fs37.readFileSync(path33.join(__dirname, "../package.json"), "utf-8"));
|
|
9864
10212
|
var cli = new FullstackCLI(pkg.version);
|
|
9865
10213
|
cli.useAll(commands);
|
|
9866
10214
|
cli.run();
|