@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.
Files changed (2) hide show
  1. package/dist/index.js +462 -114
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import fs35 from "fs";
3
- import path31 from "path";
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((resolve8) => {
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
- resolve8({
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
- resolve8({
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((resolve8) => {
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
- resolve8();
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 fs33 from "fs";
8628
- import * as path29 from "path";
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 = 1;
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((resolve8) => setTimeout(resolve8, ms));
8860
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
8809
8861
  }
8810
8862
  async function reservePort() {
8811
- return new Promise((resolve8, reject) => {
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 resolve8(address.port);
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
- extractClientArchive(
8944
- path27.join(generationDir, "client.zip"),
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.status !== 404 && apiResponse.status < 500
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(fs31.readFileSync(registryFile, "utf8"));
8994
- if (Array.isArray(parsed)) {
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/store.ts
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
- path28.join(storeRoot, "current.json")
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(fs32.readFileSync(filePath, "utf8"));
9207
+ return JSON.parse(fs33.readFileSync(filePath, "utf8"));
9032
9208
  }
9033
9209
  function writeJsonAtomic2(filePath, value) {
9034
- fs32.mkdirSync(path28.dirname(filePath), { recursive: true });
9210
+ fs33.mkdirSync(path29.dirname(filePath), { recursive: true });
9035
9211
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9036
- fs32.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9212
+ fs33.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9037
9213
  `, {
9038
9214
  mode: 384
9039
9215
  });
9040
- fs32.renameSync(temporaryPath, filePath);
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 fs32.readdirSync(generationsRoot, {
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
- fs32.rmSync(path28.join(generationsRoot, entry.name), {
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 isPathInside(parent, candidate) {
9062
- const relative4 = path28.relative(parent, candidate);
9063
- return relative4 !== "" && !relative4.startsWith(`..${path28.sep}`) && relative4 !== ".." && !path28.isAbsolute(relative4);
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 = fs32.lstatSync(directoryPath);
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
- path28.join(generationDir, "manifest.json")
9264
+ path29.join(generationDir, "manifest.json")
9089
9265
  );
9090
9266
  probe = readJson3(
9091
- path28.join(generationDir, "probe.json")
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
- const serverPath = path28.join(generationDir, "server.bundle.cjs");
9113
- const clientPath = path28.join(generationDir, "client.zip");
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 = path28.resolve(options.storeRoot);
9317
+ const storeRoot = path29.resolve(options.storeRoot);
9136
9318
  const previousPointer = readCurrentPointer(storeRoot);
9137
- const stagingRoot = path28.join(storeRoot, ".staging");
9138
- const stagingDir = path28.resolve(options.stagingDir);
9139
- if (!isPathInside(stagingRoot, stagingDir)) {
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 = path28.join(storeRoot, "generations");
9153
- const generationDir = path28.join(generationsRoot, generationId);
9154
- fs32.mkdirSync(generationsRoot, { recursive: true });
9155
- if (fs32.existsSync(generationDir)) {
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
- fs32.renameSync(stagingDir, generationDir);
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(path28.join(storeRoot, "current.json"), pointer);
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 = path28.resolve(options.storeRoot);
9368
+ const storeRoot = path29.resolve(options.storeRoot);
9187
9369
  let pointer;
9188
9370
  try {
9189
- pointer = readJson3(path28.join(storeRoot, "current.json"));
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 = path28.join(
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 (!fs33.existsSync(path29.join(clientDir, "index.html"))) {
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 && fs33.existsSync(archivePath)) return [];
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(fs33.readFileSync(metadataFile, "utf8"));
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 = path29.resolve(options.projectRoot);
9269
- const storeRoot = path29.resolve(options.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 = path29.join(
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
- fs33.mkdirSync(stagingDir, { recursive: true });
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
- options.environment
9480
+ buildEnvironment
9289
9481
  );
9290
9482
  if (buildReasons.length > 0) return failed(buildReasons);
9291
- const serverOutfile = path29.join(stagingDir, "server.bundle.cjs");
9292
- const serverMetadataFile = path29.join(stagingDir, "server-build.json");
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
- path29.join(projectRoot, "dist", "client"),
9302
- path29.join(stagingDir, "client.zip")
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
- fs33.rmSync(serverMetadataFile, { force: true });
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
- fs33.rmSync(stagingDir, { recursive: true, force: true });
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 fs34 from "fs";
9540
+ import * as fs36 from "fs";
9345
9541
  import * as net3 from "net";
9346
- import * as path30 from "path";
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
- return body.ready === true;
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((resolve8) => {
9424
- settleCutover = resolve8;
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
- proxyHttpRequest(currentTarget(), request2, response);
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(options.freshTarget, healthPath)) {
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((resolve8, reject) => {
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
- resolve8({
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((resolve8) => {
9496
- server.close(() => resolve8());
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((resolve8) => setTimeout(resolve8, ms));
9792
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
9506
9793
  }
9507
9794
  async function reservePort2() {
9508
- return new Promise((resolve8, reject) => {
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 resolve8(address.port);
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 isPathInside2(parent, candidate) {
9538
- const relative4 = path30.relative(parent, candidate);
9539
- return relative4 !== "" && relative4 !== ".." && !relative4.startsWith(`..${path30.sep}`) && !path30.isAbsolute(relative4);
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
- fs34.mkdirSync(runtimeRoot, { recursive: true });
9543
- const stat = fs34.lstatSync(runtimeRoot);
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
- fs34.mkdirSync(path30.dirname(markerFile), { recursive: true });
9862
+ fs36.mkdirSync(path32.dirname(markerFile), { recursive: true });
9550
9863
  const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
9551
- fs34.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9864
+ fs36.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9552
9865
  `, {
9553
9866
  mode: 384
9554
9867
  });
9555
- fs34.renameSync(temporaryFile, markerFile);
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) return;
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 = path30.resolve(options.runtimeRoot);
9586
- const markerFile = path30.resolve(options.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
- fs34.rmSync(markerFile, { force: true });
9597
- if (runtimeDir) fs34.rmSync(runtimeDir, { recursive: true, force: true });
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 (!isPathInside2(runtimeRoot, markerFile)) {
9926
+ if (!isPathInside3(runtimeRoot, markerFile)) {
9602
9927
  throw new Error("runtime marker must be inside the runtime root");
9603
9928
  }
9604
- runtimeDir = fs34.mkdtempSync(
9605
- path30.join(runtimeRoot, `${resolved.generationId}-`)
9929
+ runtimeDir = fs36.mkdtempSync(
9930
+ path32.join(runtimeRoot, `${resolved.generationId}-`)
9606
9931
  );
9607
- const runtimeBundle = path30.join(runtimeDir, "server.bundle.cjs");
9608
- fs34.copyFileSync(
9609
- path30.join(resolved.generationDir, "server.bundle.cjs"),
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
- path30.join(resolved.generationDir, "client.zip"),
9614
- path30.join(runtimeDir, "dist", "client")
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(options);
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(options);
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 = path31.join(process.cwd(), ".env");
9859
- if (fs35.existsSync(envPath)) {
10206
+ var envPath = path33.join(process.cwd(), ".env");
10207
+ if (fs37.existsSync(envPath)) {
9860
10208
  dotenvConfig({ path: envPath });
9861
10209
  }
9862
- var __dirname = path31.dirname(fileURLToPath5(import.meta.url));
9863
- var pkg = JSON.parse(fs35.readFileSync(path31.join(__dirname, "../package.json"), "utf-8"));
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.59-alpha.20260718151739",
3
+ "version": "1.1.59-alpha.20260719042040",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",