@lark-apaas/fullstack-cli 1.1.59-alpha.20260718151739 → 1.1.59-alpha.20260719084243

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 +466 -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,44 @@ 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
+ // Probe and switcher connect through 127.0.0.1. Linux may resolve
9190
+ // `localhost` to IPv6-only ::1, so the isolated Nest process must bind to
9191
+ // the same explicit private endpoint instead of inheriting app defaults.
9192
+ SERVER_HOST: "127.0.0.1"
9193
+ };
9194
+ }
9195
+
9196
+ // src/commands/preview-artifact/store.ts
9197
+ import * as fs33 from "fs";
9198
+ import * as path29 from "path";
9019
9199
  var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9020
9200
  function readCurrentPointer(storeRoot) {
9021
9201
  try {
9022
9202
  const pointer = readJson3(
9023
- path28.join(storeRoot, "current.json")
9203
+ path29.join(storeRoot, "current.json")
9024
9204
  );
9025
9205
  return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) ? pointer : void 0;
9026
9206
  } catch {
@@ -9028,25 +9208,25 @@ function readCurrentPointer(storeRoot) {
9028
9208
  }
9029
9209
  }
9030
9210
  function readJson3(filePath) {
9031
- return JSON.parse(fs32.readFileSync(filePath, "utf8"));
9211
+ return JSON.parse(fs33.readFileSync(filePath, "utf8"));
9032
9212
  }
9033
9213
  function writeJsonAtomic2(filePath, value) {
9034
- fs32.mkdirSync(path28.dirname(filePath), { recursive: true });
9214
+ fs33.mkdirSync(path29.dirname(filePath), { recursive: true });
9035
9215
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9036
- fs32.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9216
+ fs33.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9037
9217
  `, {
9038
9218
  mode: 384
9039
9219
  });
9040
- fs32.renameSync(temporaryPath, filePath);
9220
+ fs33.renameSync(temporaryPath, filePath);
9041
9221
  }
9042
9222
  function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
9043
9223
  const retained = new Set(retainedGenerationIds);
9044
- for (const entry of fs32.readdirSync(generationsRoot, {
9224
+ for (const entry of fs33.readdirSync(generationsRoot, {
9045
9225
  withFileTypes: true
9046
9226
  })) {
9047
9227
  if (retained.has(entry.name)) continue;
9048
9228
  try {
9049
- fs32.rmSync(path28.join(generationsRoot, entry.name), {
9229
+ fs33.rmSync(path29.join(generationsRoot, entry.name), {
9050
9230
  recursive: true,
9051
9231
  force: true
9052
9232
  });
@@ -9058,13 +9238,13 @@ function parseNodeMajor2(nodeVersion) {
9058
9238
  const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
9059
9239
  return match ? Number(match[1]) : null;
9060
9240
  }
9061
- function isPathInside(parent, candidate) {
9062
- const relative4 = path28.relative(parent, candidate);
9063
- return relative4 !== "" && !relative4.startsWith(`..${path28.sep}`) && relative4 !== ".." && !path28.isAbsolute(relative4);
9241
+ function isPathInside2(parent, candidate) {
9242
+ const relative5 = path29.relative(parent, candidate);
9243
+ return relative5 !== "" && !relative5.startsWith(`..${path29.sep}`) && relative5 !== ".." && !path29.isAbsolute(relative5);
9064
9244
  }
9065
9245
  function validateRegularDirectory(directoryPath, label) {
9066
9246
  try {
9067
- const stat = fs32.lstatSync(directoryPath);
9247
+ const stat = fs33.lstatSync(directoryPath);
9068
9248
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
9069
9249
  return [`${label} must be a regular directory`];
9070
9250
  }
@@ -9085,10 +9265,10 @@ function validateGeneration(generationDir, runtime) {
9085
9265
  let probe;
9086
9266
  try {
9087
9267
  manifest = readJson3(
9088
- path28.join(generationDir, "manifest.json")
9268
+ path29.join(generationDir, "manifest.json")
9089
9269
  );
9090
9270
  probe = readJson3(
9091
- path28.join(generationDir, "probe.json")
9271
+ path29.join(generationDir, "probe.json")
9092
9272
  );
9093
9273
  } catch (error) {
9094
9274
  return {
@@ -9097,7 +9277,7 @@ function validateGeneration(generationDir, runtime) {
9097
9277
  ]
9098
9278
  };
9099
9279
  }
9100
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9280
+ if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || manifest.protocolVersion !== PREVIEW_ARTIFACT_PROTOCOL_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9101
9281
  reasons.push("generation schema version is incompatible");
9102
9282
  }
9103
9283
  if (!GENERATION_ID_PATTERN.test(manifest.generationId) || probe.generationId !== manifest.generationId) {
@@ -9109,8 +9289,14 @@ function validateGeneration(generationDir, runtime) {
9109
9289
  if (!probe.success) {
9110
9290
  reasons.push("generation runtime probe did not pass");
9111
9291
  }
9112
- const serverPath = path28.join(generationDir, "server.bundle.cjs");
9113
- const clientPath = path28.join(generationDir, "client.zip");
9292
+ if (!Array.isArray(probe.coverage?.clientAssets) || probe.coverage.clientAssets.length === 0) {
9293
+ reasons.push("generation client asset evidence is invalid");
9294
+ }
9295
+ if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
9296
+ reasons.push("build environment identity is invalid");
9297
+ }
9298
+ const serverPath = path29.join(generationDir, "server.bundle.cjs");
9299
+ const clientPath = path29.join(generationDir, "client.zip");
9114
9300
  try {
9115
9301
  if (sha256File(serverPath) !== manifest.files.server.sha256) {
9116
9302
  reasons.push("server.bundle.cjs hash does not match manifest");
@@ -9132,11 +9318,11 @@ function validateGeneration(generationDir, runtime) {
9132
9318
  return { manifest, reasons };
9133
9319
  }
9134
9320
  function publishPreviewArtifactGeneration(options) {
9135
- const storeRoot = path28.resolve(options.storeRoot);
9321
+ const storeRoot = path29.resolve(options.storeRoot);
9136
9322
  const previousPointer = readCurrentPointer(storeRoot);
9137
- const stagingRoot = path28.join(storeRoot, ".staging");
9138
- const stagingDir = path28.resolve(options.stagingDir);
9139
- if (!isPathInside(stagingRoot, stagingDir)) {
9323
+ const stagingRoot = path29.join(storeRoot, ".staging");
9324
+ const stagingDir = path29.resolve(options.stagingDir);
9325
+ if (!isPathInside2(stagingRoot, stagingDir)) {
9140
9326
  return {
9141
9327
  published: false,
9142
9328
  reasons: [
@@ -9149,23 +9335,23 @@ function publishPreviewArtifactGeneration(options) {
9149
9335
  return { published: false, reasons: validation.reasons };
9150
9336
  }
9151
9337
  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)) {
9338
+ const generationsRoot = path29.join(storeRoot, "generations");
9339
+ const generationDir = path29.join(generationsRoot, generationId);
9340
+ fs33.mkdirSync(generationsRoot, { recursive: true });
9341
+ if (fs33.existsSync(generationDir)) {
9156
9342
  return {
9157
9343
  published: false,
9158
9344
  reasons: [`generation already exists: ${generationId}`]
9159
9345
  };
9160
9346
  }
9161
9347
  try {
9162
- fs32.renameSync(stagingDir, generationDir);
9348
+ fs33.renameSync(stagingDir, generationDir);
9163
9349
  const pointer = {
9164
9350
  schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9165
9351
  generationId,
9166
9352
  publishedAt: (/* @__PURE__ */ new Date()).toISOString()
9167
9353
  };
9168
- writeJsonAtomic2(path28.join(storeRoot, "current.json"), pointer);
9354
+ writeJsonAtomic2(path29.join(storeRoot, "current.json"), pointer);
9169
9355
  pruneSupersededGenerations(
9170
9356
  generationsRoot,
9171
9357
  [generationId, previousPointer?.generationId].filter(
@@ -9183,10 +9369,10 @@ function publishPreviewArtifactGeneration(options) {
9183
9369
  }
9184
9370
  }
9185
9371
  function resolveCurrentPreviewArtifact(options) {
9186
- const storeRoot = path28.resolve(options.storeRoot);
9372
+ const storeRoot = path29.resolve(options.storeRoot);
9187
9373
  let pointer;
9188
9374
  try {
9189
- pointer = readJson3(path28.join(storeRoot, "current.json"));
9375
+ pointer = readJson3(path29.join(storeRoot, "current.json"));
9190
9376
  } catch (error) {
9191
9377
  return {
9192
9378
  resolved: false,
@@ -9205,7 +9391,7 @@ function resolveCurrentPreviewArtifact(options) {
9205
9391
  if (nodeMajor == null) {
9206
9392
  return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
9207
9393
  }
9208
- const generationDir = path28.join(
9394
+ const generationDir = path29.join(
9209
9395
  storeRoot,
9210
9396
  "generations",
9211
9397
  pointer.generationId
@@ -9224,6 +9410,12 @@ function resolveCurrentPreviewArtifact(options) {
9224
9410
  reasons: ["current pointer does not match generation manifest"]
9225
9411
  };
9226
9412
  }
9413
+ if (validation.manifest.buildEnvironmentSha256 !== options.buildEnvironmentSha256) {
9414
+ return {
9415
+ resolved: false,
9416
+ reasons: ["build environment is incompatible with generation"]
9417
+ };
9418
+ }
9227
9419
  return {
9228
9420
  resolved: true,
9229
9421
  generationId: pointer.generationId,
@@ -9248,27 +9440,27 @@ function runBuildCommand(projectRoot, buildCommand, environment) {
9248
9440
  return [`production build failed with exit ${result.status ?? "unknown"}`];
9249
9441
  }
9250
9442
  function createClientArchive(clientDir, archivePath) {
9251
- if (!fs33.existsSync(path29.join(clientDir, "index.html"))) {
9443
+ if (!fs34.existsSync(path30.join(clientDir, "index.html"))) {
9252
9444
  return [`client build output is missing: ${clientDir}`];
9253
9445
  }
9254
9446
  const result = spawnSync8("zip", ["-qry", archivePath, "."], {
9255
9447
  cwd: clientDir,
9256
9448
  encoding: "utf8"
9257
9449
  });
9258
- if (result.status === 0 && fs33.existsSync(archivePath)) return [];
9450
+ if (result.status === 0 && fs34.existsSync(archivePath)) return [];
9259
9451
  return [`client archive failed with exit ${result.status ?? "unknown"}`];
9260
9452
  }
9261
9453
  function readActionPlugins2(metadataFile) {
9262
- const metadata = JSON.parse(fs33.readFileSync(metadataFile, "utf8"));
9454
+ const metadata = JSON.parse(fs34.readFileSync(metadataFile, "utf8"));
9263
9455
  if (!Array.isArray(metadata.actionPlugins)) return [];
9264
9456
  return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
9265
9457
  }
9266
9458
  async function producePreviewArtifactGeneration(options) {
9267
9459
  const startedAt = Date.now();
9268
- const projectRoot = path29.resolve(options.projectRoot);
9269
- const storeRoot = path29.resolve(options.storeRoot);
9460
+ const projectRoot = path30.resolve(options.projectRoot);
9461
+ const storeRoot = path30.resolve(options.storeRoot);
9270
9462
  const generationId = options.generationId ?? defaultGenerationId();
9271
- const stagingDir = path29.join(
9463
+ const stagingDir = path30.join(
9272
9464
  storeRoot,
9273
9465
  ".staging",
9274
9466
  `${generationId}.${process.pid}.${Date.now()}`
@@ -9280,16 +9472,20 @@ async function producePreviewArtifactGeneration(options) {
9280
9472
  reasons,
9281
9473
  ...probe ? { probe } : {}
9282
9474
  });
9283
- fs33.mkdirSync(stagingDir, { recursive: true });
9475
+ fs34.mkdirSync(stagingDir, { recursive: true });
9284
9476
  try {
9477
+ const buildEnvironment = previewArtifactBuildEnvironment(
9478
+ options.environment,
9479
+ options.appBasePath
9480
+ );
9285
9481
  const buildReasons = runBuildCommand(
9286
9482
  projectRoot,
9287
9483
  options.buildCommand ?? { command: "npm", args: ["run", "build:prod"] },
9288
- options.environment
9484
+ buildEnvironment
9289
9485
  );
9290
9486
  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");
9487
+ const serverOutfile = path30.join(stagingDir, "server.bundle.cjs");
9488
+ const serverMetadataFile = path30.join(stagingDir, "server-build.json");
9293
9489
  const serverResult = await buildPreviewServerArtifact({
9294
9490
  projectRoot,
9295
9491
  entry: "dist/main.js",
@@ -9298,25 +9494,29 @@ async function producePreviewArtifactGeneration(options) {
9298
9494
  });
9299
9495
  if (!serverResult.built) return failed(serverResult.reasons);
9300
9496
  const archiveReasons = createClientArchive(
9301
- path29.join(projectRoot, "dist", "client"),
9302
- path29.join(stagingDir, "client.zip")
9497
+ path30.join(projectRoot, "dist", "client"),
9498
+ path30.join(stagingDir, "client.zip")
9303
9499
  );
9304
9500
  if (archiveReasons.length > 0) return failed(archiveReasons);
9305
9501
  const actionPlugins = readActionPlugins2(serverMetadataFile);
9306
- fs33.rmSync(serverMetadataFile, { force: true });
9502
+ fs34.rmSync(serverMetadataFile, { force: true });
9307
9503
  createPreviewArtifactManifest({
9308
9504
  generationDir: stagingDir,
9309
9505
  generationId,
9310
9506
  nodeVersion: process.version,
9311
9507
  platform: process.platform,
9312
9508
  arch: process.arch,
9509
+ buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
9510
+ options.environment,
9511
+ options.appBasePath
9512
+ ),
9313
9513
  actionPlugins
9314
9514
  });
9315
9515
  const probe = await runPreviewArtifactProbe({
9316
9516
  generationDir: stagingDir,
9317
9517
  appBasePath: options.appBasePath,
9318
9518
  businessApiPath: options.businessApiPath,
9319
- environment: options.environment
9519
+ environment: previewArtifactRuntimeEnvironment(options.environment)
9320
9520
  });
9321
9521
  if (!probe.success) return failed(probe.reasons, probe);
9322
9522
  const promotion = promotePreviewArtifactGeneration(stagingDir);
@@ -9336,23 +9536,25 @@ async function producePreviewArtifactGeneration(options) {
9336
9536
  } catch (error) {
9337
9537
  return failed([error instanceof Error ? error.message : String(error)]);
9338
9538
  } finally {
9339
- fs33.rmSync(stagingDir, { recursive: true, force: true });
9539
+ fs34.rmSync(stagingDir, { recursive: true, force: true });
9340
9540
  }
9341
9541
  }
9342
9542
 
9343
9543
  // src/commands/preview-artifact/runtime.ts
9344
- import * as fs34 from "fs";
9544
+ import * as fs36 from "fs";
9345
9545
  import * as net3 from "net";
9346
- import * as path30 from "path";
9546
+ import * as path32 from "path";
9347
9547
  import { spawn as spawn3 } from "child_process";
9348
9548
 
9349
9549
  // src/commands/preview-artifact/switcher.ts
9350
9550
  import * as http from "http";
9351
9551
  import * as net2 from "net";
9552
+ import * as fs35 from "fs";
9553
+ import * as path31 from "path";
9352
9554
  function targetOrigin(target) {
9353
9555
  return `http://${target.host}:${target.port}`;
9354
9556
  }
9355
- function proxyHttpRequest(target, request2, response) {
9557
+ function proxyHttpRequest(target, request2, response, fallback) {
9356
9558
  const upstream = http.request(
9357
9559
  {
9358
9560
  host: target.host,
@@ -9362,6 +9564,13 @@ function proxyHttpRequest(target, request2, response) {
9362
9564
  headers: request2.headers
9363
9565
  },
9364
9566
  (upstreamResponse) => {
9567
+ const upstreamContentType = upstreamResponse.headers["content-type"];
9568
+ const isHtmlFallback = typeof upstreamContentType === "string" && upstreamContentType.toLowerCase().includes("text/html");
9569
+ if (fallback && (upstreamResponse.statusCode === 404 || isHtmlFallback)) {
9570
+ upstreamResponse.resume();
9571
+ fallback();
9572
+ return;
9573
+ }
9365
9574
  response.writeHead(
9366
9575
  upstreamResponse.statusCode ?? 502,
9367
9576
  upstreamResponse.headers
@@ -9382,6 +9591,72 @@ function proxyHttpRequest(target, request2, response) {
9382
9591
  });
9383
9592
  request2.pipe(upstream);
9384
9593
  }
9594
+ function cachedAssetFile(request2, cachedAssetFiles) {
9595
+ if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
9596
+ if (!cachedAssetFiles) return void 0;
9597
+ const pathname = new URL(request2.url ?? "/", "http://preview.local").pathname;
9598
+ return cachedAssetFiles.get(pathname);
9599
+ }
9600
+ function contentType(filePath) {
9601
+ switch (path31.extname(filePath).toLowerCase()) {
9602
+ case ".js":
9603
+ case ".mjs":
9604
+ return "text/javascript; charset=utf-8";
9605
+ case ".css":
9606
+ return "text/css; charset=utf-8";
9607
+ case ".json":
9608
+ case ".map":
9609
+ return "application/json; charset=utf-8";
9610
+ case ".svg":
9611
+ return "image/svg+xml";
9612
+ case ".png":
9613
+ return "image/png";
9614
+ case ".jpg":
9615
+ case ".jpeg":
9616
+ return "image/jpeg";
9617
+ case ".gif":
9618
+ return "image/gif";
9619
+ case ".webp":
9620
+ return "image/webp";
9621
+ case ".ico":
9622
+ return "image/x-icon";
9623
+ case ".woff":
9624
+ return "font/woff";
9625
+ case ".woff2":
9626
+ return "font/woff2";
9627
+ case ".ttf":
9628
+ return "font/ttf";
9629
+ case ".wasm":
9630
+ return "application/wasm";
9631
+ default:
9632
+ return "application/octet-stream";
9633
+ }
9634
+ }
9635
+ function serveCachedAsset(request2, response, filePath) {
9636
+ let stat;
9637
+ try {
9638
+ stat = fs35.lstatSync(filePath);
9639
+ } catch {
9640
+ response.writeHead(404).end();
9641
+ return;
9642
+ }
9643
+ if (!stat.isFile() || stat.isSymbolicLink()) {
9644
+ response.writeHead(404).end();
9645
+ return;
9646
+ }
9647
+ response.writeHead(200, {
9648
+ "content-type": contentType(filePath),
9649
+ "content-length": stat.size,
9650
+ "cache-control": "no-store"
9651
+ });
9652
+ if (request2.method === "HEAD") {
9653
+ response.end();
9654
+ return;
9655
+ }
9656
+ const stream = fs35.createReadStream(filePath);
9657
+ stream.on("error", () => response.destroy());
9658
+ stream.pipe(response);
9659
+ }
9385
9660
  function proxyUpgrade(target, request2, socket, head) {
9386
9661
  const upstream = net2.connect(target.port, target.host, () => {
9387
9662
  const headers = Object.entries(request2.headers).flatMap(([name, value]) => {
@@ -9400,14 +9675,19 @@ ${headers}\r
9400
9675
  upstream.on("error", () => socket.destroy());
9401
9676
  socket.on("error", () => upstream.destroy());
9402
9677
  }
9403
- async function freshTargetReady(target, healthPath) {
9678
+ async function freshTargetReady(target, healthPath, businessPath) {
9404
9679
  try {
9405
9680
  const response = await fetch(new URL(healthPath, targetOrigin(target)), {
9406
9681
  signal: AbortSignal.timeout(1e3)
9407
9682
  });
9408
9683
  if (response.status !== 200) return false;
9409
9684
  const body = await response.json();
9410
- return body.ready === true;
9685
+ if (body.ready !== true) return false;
9686
+ const businessResponse = await fetch(
9687
+ new URL(businessPath, targetOrigin(target)),
9688
+ { signal: AbortSignal.timeout(1e3), redirect: "manual" }
9689
+ );
9690
+ return isPreviewBusinessResponseReady(businessResponse);
9411
9691
  } catch {
9412
9692
  return false;
9413
9693
  }
@@ -9420,8 +9700,8 @@ function createPreviewArtifactSwitcher(options) {
9420
9700
  let closed = false;
9421
9701
  let pollTimer;
9422
9702
  let settleCutover;
9423
- const cutover = new Promise((resolve8) => {
9424
- settleCutover = resolve8;
9703
+ const cutover = new Promise((resolve9) => {
9704
+ settleCutover = resolve9;
9425
9705
  });
9426
9706
  const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
9427
9707
  const server = http.createServer((request2, response) => {
@@ -9436,7 +9716,14 @@ function createPreviewArtifactSwitcher(options) {
9436
9716
  );
9437
9717
  return;
9438
9718
  }
9439
- proxyHttpRequest(currentTarget(), request2, response);
9719
+ const assetFile = cachedAssetFile(request2, options.cachedAssetFiles);
9720
+ if (active === "cached" && assetFile) {
9721
+ serveCachedAsset(request2, response, assetFile);
9722
+ return;
9723
+ }
9724
+ const target = currentTarget();
9725
+ const fallback = active === "fresh" && assetFile ? () => serveCachedAsset(request2, response, assetFile) : void 0;
9726
+ proxyHttpRequest(target, request2, response, fallback);
9440
9727
  });
9441
9728
  server.on("upgrade", (request2, socket, head) => {
9442
9729
  proxyUpgrade(currentTarget(), request2, socket, head);
@@ -9444,7 +9731,11 @@ function createPreviewArtifactSwitcher(options) {
9444
9731
  const schedulePoll = () => {
9445
9732
  if (closed || active === "fresh") return;
9446
9733
  pollTimer = setTimeout(async () => {
9447
- if (await freshTargetReady(options.freshTarget, healthPath)) {
9734
+ if (await freshTargetReady(
9735
+ options.freshTarget,
9736
+ healthPath,
9737
+ options.freshBusinessPath
9738
+ )) {
9448
9739
  active = "fresh";
9449
9740
  settleCutover?.({
9450
9741
  switched: true,
@@ -9464,7 +9755,7 @@ function createPreviewArtifactSwitcher(options) {
9464
9755
  },
9465
9756
  cutover,
9466
9757
  start() {
9467
- return new Promise((resolve8, reject) => {
9758
+ return new Promise((resolve9, reject) => {
9468
9759
  server.once("error", reject);
9469
9760
  server.listen(options.listenPort, listenHost, () => {
9470
9761
  const address = server.address();
@@ -9473,7 +9764,7 @@ function createPreviewArtifactSwitcher(options) {
9473
9764
  return;
9474
9765
  }
9475
9766
  schedulePoll();
9476
- resolve8({
9767
+ resolve9({
9477
9768
  host: listenHost,
9478
9769
  port: address.port,
9479
9770
  origin: `http://${listenHost}:${address.port}`
@@ -9492,8 +9783,8 @@ function createPreviewArtifactSwitcher(options) {
9492
9783
  });
9493
9784
  settleCutover = void 0;
9494
9785
  }
9495
- return new Promise((resolve8) => {
9496
- server.close(() => resolve8());
9786
+ return new Promise((resolve9) => {
9787
+ server.close(() => resolve9());
9497
9788
  server.closeAllConnections?.();
9498
9789
  });
9499
9790
  }
@@ -9502,10 +9793,10 @@ function createPreviewArtifactSwitcher(options) {
9502
9793
 
9503
9794
  // src/commands/preview-artifact/runtime.ts
9504
9795
  function sleep2(ms) {
9505
- return new Promise((resolve8) => setTimeout(resolve8, ms));
9796
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
9506
9797
  }
9507
9798
  async function reservePort2() {
9508
- return new Promise((resolve8, reject) => {
9799
+ return new Promise((resolve9, reject) => {
9509
9800
  const server = net3.createServer();
9510
9801
  server.unref();
9511
9802
  server.once("error", reject);
@@ -9517,7 +9808,7 @@ async function reservePort2() {
9517
9808
  }
9518
9809
  server.close((error) => {
9519
9810
  if (error) reject(error);
9520
- else resolve8(address.port);
9811
+ else resolve9(address.port);
9521
9812
  });
9522
9813
  });
9523
9814
  });
@@ -9534,27 +9825,53 @@ function stopProcessGroup2(child) {
9534
9825
  }
9535
9826
  }
9536
9827
  }
9537
- function isPathInside2(parent, candidate) {
9538
- const relative4 = path30.relative(parent, candidate);
9539
- return relative4 !== "" && relative4 !== ".." && !relative4.startsWith(`..${path30.sep}`) && !path30.isAbsolute(relative4);
9828
+ function isPathInside3(parent, candidate) {
9829
+ const relative5 = path32.relative(parent, candidate);
9830
+ return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative5);
9540
9831
  }
9541
9832
  function ensureRegularRuntimeRoot(runtimeRoot) {
9542
- fs34.mkdirSync(runtimeRoot, { recursive: true });
9543
- const stat = fs34.lstatSync(runtimeRoot);
9833
+ fs36.mkdirSync(runtimeRoot, { recursive: true });
9834
+ const stat = fs36.lstatSync(runtimeRoot);
9544
9835
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
9545
9836
  throw new Error("runtime root must be a regular directory");
9546
9837
  }
9547
9838
  }
9839
+ function collectCachedAssetFiles(clientRoot, appBasePath) {
9840
+ const files = /* @__PURE__ */ new Map();
9841
+ const basePath = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
9842
+ const visit = (directory) => {
9843
+ for (const entry of fs36.readdirSync(directory, { withFileTypes: true })) {
9844
+ const absolute = path32.join(directory, entry.name);
9845
+ if (entry.isDirectory()) {
9846
+ visit(absolute);
9847
+ continue;
9848
+ }
9849
+ if (!entry.isFile() || entry.isSymbolicLink()) continue;
9850
+ const relative5 = path32.relative(clientRoot, absolute).split(path32.sep).join("/");
9851
+ if (relative5 === "index.html") continue;
9852
+ files.set(
9853
+ new URL(`/${relative5}`, "http://preview.local").pathname,
9854
+ absolute
9855
+ );
9856
+ files.set(
9857
+ new URL(relative5, `http://preview.local${basePath}`).pathname,
9858
+ absolute
9859
+ );
9860
+ }
9861
+ };
9862
+ visit(clientRoot);
9863
+ return files;
9864
+ }
9548
9865
  function writeMarkerAtomic(markerFile, marker) {
9549
- fs34.mkdirSync(path30.dirname(markerFile), { recursive: true });
9866
+ fs36.mkdirSync(path32.dirname(markerFile), { recursive: true });
9550
9867
  const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
9551
- fs34.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9868
+ fs36.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9552
9869
  `, {
9553
9870
  mode: 384
9554
9871
  });
9555
- fs34.renameSync(temporaryFile, markerFile);
9872
+ fs36.renameSync(temporaryFile, markerFile);
9556
9873
  }
9557
- async function waitForCachedRuntime(origin, appBasePath, timeoutMs, child) {
9874
+ async function waitForCachedRuntime(origin, appBasePath, businessApiPath, timeoutMs, child) {
9558
9875
  const timeoutAt = Date.now() + timeoutMs;
9559
9876
  while (Date.now() < timeoutAt) {
9560
9877
  if (child.exitCode != null || child.signalCode != null) {
@@ -9567,7 +9884,15 @@ async function waitForCachedRuntime(origin, appBasePath, timeoutMs, child) {
9567
9884
  redirect: "manual",
9568
9885
  signal: AbortSignal.timeout(1e3)
9569
9886
  });
9570
- if (response.status === 200) return;
9887
+ if (response.status === 200) {
9888
+ const businessResponse = await fetch(new URL(businessApiPath, origin), {
9889
+ redirect: "manual",
9890
+ signal: AbortSignal.timeout(1e3)
9891
+ });
9892
+ if (isPreviewBusinessResponseReady(businessResponse)) {
9893
+ return;
9894
+ }
9895
+ }
9571
9896
  } catch {
9572
9897
  }
9573
9898
  await sleep2(50);
@@ -9579,11 +9904,15 @@ async function startPreviewArtifactRuntime(options) {
9579
9904
  storeRoot: options.storeRoot,
9580
9905
  nodeVersion: process.version,
9581
9906
  platform: process.platform,
9582
- arch: process.arch
9907
+ arch: process.arch,
9908
+ buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
9909
+ options.environment,
9910
+ options.appBasePath
9911
+ )
9583
9912
  });
9584
9913
  if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
9585
- const runtimeRoot = path30.resolve(options.runtimeRoot);
9586
- const markerFile = path30.resolve(options.markerFile);
9914
+ const runtimeRoot = path32.resolve(options.runtimeRoot);
9915
+ const markerFile = path32.resolve(options.markerFile);
9587
9916
  let runtimeDir;
9588
9917
  let cachedRuntime;
9589
9918
  let switcher;
@@ -9593,25 +9922,26 @@ async function startPreviewArtifactRuntime(options) {
9593
9922
  closed = true;
9594
9923
  stopProcessGroup2(cachedRuntime);
9595
9924
  await switcher?.close();
9596
- fs34.rmSync(markerFile, { force: true });
9597
- if (runtimeDir) fs34.rmSync(runtimeDir, { recursive: true, force: true });
9925
+ fs36.rmSync(markerFile, { force: true });
9926
+ if (runtimeDir) fs36.rmSync(runtimeDir, { recursive: true, force: true });
9598
9927
  };
9599
9928
  try {
9600
9929
  ensureRegularRuntimeRoot(runtimeRoot);
9601
- if (!isPathInside2(runtimeRoot, markerFile)) {
9930
+ if (!isPathInside3(runtimeRoot, markerFile)) {
9602
9931
  throw new Error("runtime marker must be inside the runtime root");
9603
9932
  }
9604
- runtimeDir = fs34.mkdtempSync(
9605
- path30.join(runtimeRoot, `${resolved.generationId}-`)
9933
+ runtimeDir = fs36.mkdtempSync(
9934
+ path32.join(runtimeRoot, `${resolved.generationId}-`)
9606
9935
  );
9607
- const runtimeBundle = path30.join(runtimeDir, "server.bundle.cjs");
9608
- fs34.copyFileSync(
9609
- path30.join(resolved.generationDir, "server.bundle.cjs"),
9936
+ const runtimeBundle = path32.join(runtimeDir, "server.bundle.cjs");
9937
+ fs36.copyFileSync(
9938
+ path32.join(resolved.generationDir, "server.bundle.cjs"),
9610
9939
  runtimeBundle
9611
9940
  );
9941
+ const clientRoot = path32.join(runtimeDir, "dist", "client");
9612
9942
  extractClientArchive(
9613
- path30.join(resolved.generationDir, "client.zip"),
9614
- path30.join(runtimeDir, "dist", "client")
9943
+ path32.join(resolved.generationDir, "client.zip"),
9944
+ clientRoot
9615
9945
  );
9616
9946
  const cachedServerPort = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
9617
9947
  cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
@@ -9620,7 +9950,7 @@ async function startPreviewArtifactRuntime(options) {
9620
9950
  stdio: "ignore",
9621
9951
  env: {
9622
9952
  ...process.env,
9623
- ...options.environment,
9953
+ ...previewArtifactRuntimeEnvironment(options.environment),
9624
9954
  SERVER_PORT: String(cachedServerPort)
9625
9955
  }
9626
9956
  });
@@ -9628,6 +9958,7 @@ async function startPreviewArtifactRuntime(options) {
9628
9958
  await waitForCachedRuntime(
9629
9959
  cachedOrigin,
9630
9960
  options.appBasePath,
9961
+ options.businessApiPath,
9631
9962
  options.startupTimeoutMs ?? 3e4,
9632
9963
  cachedRuntime
9633
9964
  );
@@ -9637,6 +9968,11 @@ async function startPreviewArtifactRuntime(options) {
9637
9968
  generationId: resolved.generationId,
9638
9969
  cachedTarget: { host: "127.0.0.1", port: cachedServerPort },
9639
9970
  freshTarget: { host: "127.0.0.1", port: options.freshClientPort },
9971
+ freshBusinessPath: options.businessApiPath,
9972
+ cachedAssetFiles: collectCachedAssetFiles(
9973
+ clientRoot,
9974
+ options.appBasePath
9975
+ ),
9640
9976
  pollIntervalMs: options.pollIntervalMs
9641
9977
  });
9642
9978
  const address = await switcher.start();
@@ -9656,9 +9992,6 @@ async function startPreviewArtifactRuntime(options) {
9656
9992
  runtimeMarker.status = "fresh-serving";
9657
9993
  runtimeMarker.cutoverAt = result.at;
9658
9994
  writeMarkerAtomic(markerFile, runtimeMarker);
9659
- await sleep2(options.cachedDrainMs ?? 5e3);
9660
- stopProcessGroup2(cachedRuntime);
9661
- cachedRuntime = void 0;
9662
9995
  }
9663
9996
  return result;
9664
9997
  });
@@ -9748,9 +10081,17 @@ var previewArtifactGenerationCommand = {
9748
10081
  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
10082
  "--business-api-path <path>",
9750
10083
  "Business API probe path that must not return 404/5xx"
10084
+ ).requiredOption(
10085
+ "--environment-file <file>",
10086
+ "JSON application environment passed only to build/probe children"
9751
10087
  ).action(
9752
10088
  async (options) => {
9753
- const result = await producePreviewArtifactGeneration(options);
10089
+ const result = await producePreviewArtifactGeneration({
10090
+ ...options,
10091
+ environment: readPreviewArtifactEnvironmentFile(
10092
+ options.environmentFile
10093
+ )
10094
+ });
9754
10095
  console.log(JSON.stringify(result));
9755
10096
  if (!result.published) process.exitCode = 2;
9756
10097
  }
@@ -9799,9 +10140,20 @@ var previewArtifactRuntimeCommand = {
9799
10140
  ).requiredOption(
9800
10141
  "--app-base-path <path>",
9801
10142
  "Application HTML readiness path"
10143
+ ).requiredOption(
10144
+ "--business-api-path <path>",
10145
+ "Business API path revalidated with the current runtime environment"
10146
+ ).requiredOption(
10147
+ "--environment-file <file>",
10148
+ "JSON application environment passed only to cached application child"
9802
10149
  ).action(
9803
10150
  async (options) => {
9804
- const runtime = await startPreviewArtifactRuntime(options);
10151
+ const runtime = await startPreviewArtifactRuntime({
10152
+ ...options,
10153
+ environment: readPreviewArtifactEnvironmentFile(
10154
+ options.environmentFile
10155
+ )
10156
+ });
9805
10157
  if (!runtime.started) {
9806
10158
  console.log(JSON.stringify(runtime));
9807
10159
  process.exitCode = 2;
@@ -9855,12 +10207,12 @@ var commands = [
9855
10207
  ];
9856
10208
 
9857
10209
  // src/index.ts
9858
- var envPath = path31.join(process.cwd(), ".env");
9859
- if (fs35.existsSync(envPath)) {
10210
+ var envPath = path33.join(process.cwd(), ".env");
10211
+ if (fs37.existsSync(envPath)) {
9860
10212
  dotenvConfig({ path: envPath });
9861
10213
  }
9862
- var __dirname = path31.dirname(fileURLToPath5(import.meta.url));
9863
- var pkg = JSON.parse(fs35.readFileSync(path31.join(__dirname, "../package.json"), "utf-8"));
10214
+ var __dirname = path33.dirname(fileURLToPath5(import.meta.url));
10215
+ var pkg = JSON.parse(fs37.readFileSync(path33.join(__dirname, "../package.json"), "utf-8"));
9864
10216
  var cli = new FullstackCLI(pkg.version);
9865
10217
  cli.useAll(commands);
9866
10218
  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.20260719084243",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",