@lark-apaas/fullstack-cli 1.1.59-alpha.20260724074215 → 1.1.59-alpha.20260802184501

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 +612 -225
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import fs37 from "fs";
3
- import path33 from "path";
2
+ import fs38 from "fs";
3
+ import path34 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((resolve9) => {
4924
+ return new Promise((resolve10) => {
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
- resolve9({
4950
+ resolve10({
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
- resolve9({
4961
+ resolve10({
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((resolve9) => {
4972
+ return new Promise((resolve10) => {
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
- resolve9();
4977
+ resolve10();
4978
4978
  });
4979
4979
  });
4980
4980
  }
@@ -8677,16 +8677,270 @@ async function buildPreviewServerArtifact(options) {
8677
8677
  }
8678
8678
 
8679
8679
  // src/commands/preview-artifact/producer.ts
8680
- import * as crypto3 from "crypto";
8681
- import * as fs34 from "fs";
8682
- import * as path30 from "path";
8683
- import { spawnSync as spawnSync8 } from "child_process";
8680
+ import * as crypto4 from "crypto";
8681
+ import * as fs35 from "fs";
8682
+ import * as path31 from "path";
8683
+ import { spawnSync as spawnSync9 } from "child_process";
8684
8684
 
8685
8685
  // src/commands/preview-artifact/generation.ts
8686
+ import * as crypto2 from "crypto";
8687
+ import * as fs31 from "fs";
8688
+ import * as path27 from "path";
8689
+
8690
+ // src/commands/preview-artifact/workspace-identity.ts
8686
8691
  import * as crypto from "crypto";
8692
+ import { spawnSync as spawnSync7 } from "child_process";
8687
8693
  import * as fs30 from "fs";
8694
+ import * as os4 from "os";
8688
8695
  import * as path26 from "path";
8689
- var PREVIEW_ARTIFACT_SCHEMA_VERSION = 3;
8696
+ var PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION = 1;
8697
+ var WORKSPACE_IDENTITY_DOMAIN = "workspace-content:v1\n";
8698
+ var WORKSPACE_BACKUP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
8699
+ var GIT_OBJECT_ID_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
8700
+ var SOURCE_TREE_ID_PATTERN = /^git-tree-(sha1|sha256):([a-f0-9]{40}|[a-f0-9]{64})$/;
8701
+ var WORKSPACE_VERSION_PATTERN = /^sha256:[a-f0-9]{64}$/;
8702
+ function normalizeSourceTreeIncludePaths(values) {
8703
+ if (!values || values.length === 0) return [];
8704
+ const normalized = /* @__PURE__ */ new Set();
8705
+ for (const rawValue of values) {
8706
+ if (typeof rawValue !== "string" || rawValue.includes("\0") || rawValue.includes("\n") || rawValue.includes("\r") || rawValue.includes("\\")) {
8707
+ throw new Error(`invalid source tree include path: ${String(rawValue)}`);
8708
+ }
8709
+ const value = path26.posix.normalize(rawValue.trim());
8710
+ if (!value || value === ".." || value.startsWith("../") || path26.posix.isAbsolute(value)) {
8711
+ throw new Error(`invalid source tree include path: ${rawValue}`);
8712
+ }
8713
+ if (value === ".") return [];
8714
+ normalized.add(value.replace(/\/$/, ""));
8715
+ }
8716
+ return [...normalized].sort((left, right) => left.localeCompare(right));
8717
+ }
8718
+ function createPreviewArtifactSourceTreeValidation(enabled, includePaths) {
8719
+ return enabled ? {
8720
+ enabled: true,
8721
+ includePaths: normalizeSourceTreeIncludePaths(includePaths)
8722
+ } : { enabled: false, includePaths: [] };
8723
+ }
8724
+ function previewArtifactSourceTreeValidation(identity) {
8725
+ if (identity.sourceTreeValidation == null) {
8726
+ return identity.sourceTreeId != null || identity.workspaceVersion != null ? { enabled: true, includePaths: [] } : { enabled: false, includePaths: [] };
8727
+ }
8728
+ return identity.sourceTreeValidation;
8729
+ }
8730
+ function samePreviewArtifactSourceTreeValidation(left, right) {
8731
+ return left.enabled === right.enabled && left.includePaths.length === right.includePaths.length && left.includePaths.every(
8732
+ (value, index) => value === right.includePaths[index]
8733
+ );
8734
+ }
8735
+ function runGit(projectRoot, args, environment, allowFailure = false) {
8736
+ const result = spawnSync7("git", args, {
8737
+ cwd: projectRoot,
8738
+ env: { ...process.env, ...environment },
8739
+ encoding: "utf8",
8740
+ maxBuffer: 10 * 1024 * 1024
8741
+ });
8742
+ if (result.status === 0) return result.stdout.trim();
8743
+ if (allowFailure) return void 0;
8744
+ const diagnostic = `${result.stderr ?? ""}
8745
+ ${result.stdout ?? ""}`.trim().slice(-8 * 1024);
8746
+ throw new Error(
8747
+ `git ${args.join(" ")} failed with exit ${result.status ?? "unknown"}${diagnostic ? `: ${diagnostic}` : ""}`
8748
+ );
8749
+ }
8750
+ function resolveGitPath(projectRoot, relativePath) {
8751
+ return path26.isAbsolute(relativePath) ? relativePath : path26.resolve(projectRoot, relativePath);
8752
+ }
8753
+ function writeJsonAtomic(filePath, value) {
8754
+ fs30.mkdirSync(path26.dirname(filePath), { recursive: true });
8755
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
8756
+ try {
8757
+ fs30.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
8758
+ `, {
8759
+ mode: 384
8760
+ });
8761
+ fs30.renameSync(temporaryPath, filePath);
8762
+ } finally {
8763
+ fs30.rmSync(temporaryPath, { force: true });
8764
+ }
8765
+ }
8766
+ function gitObjectFormat(projectRoot, treeId) {
8767
+ const reported = runGit(
8768
+ projectRoot,
8769
+ ["rev-parse", "--show-object-format"],
8770
+ void 0,
8771
+ true
8772
+ );
8773
+ if (reported === "sha1" || reported === "sha256") return reported;
8774
+ if (treeId.length === 40) return "sha1";
8775
+ if (treeId.length === 64) return "sha256";
8776
+ throw new Error(`unsupported Git tree object ID: ${treeId}`);
8777
+ }
8778
+ function workspaceVersionForSourceTreeId(sourceTreeId) {
8779
+ if (!SOURCE_TREE_ID_PATTERN.test(sourceTreeId)) {
8780
+ throw new Error(`invalid sourceTreeId: ${sourceTreeId}`);
8781
+ }
8782
+ return `sha256:${crypto.createHash("sha256").update(`${WORKSPACE_IDENTITY_DOMAIN}${sourceTreeId}`).digest("hex")}`;
8783
+ }
8784
+ function validatePreviewArtifactWorkspaceManifest(manifest) {
8785
+ const reasons = [];
8786
+ if (manifest.schemaVersion !== PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION) {
8787
+ reasons.push("workspace manifest schema version is incompatible");
8788
+ }
8789
+ reasons.push(...validatePreviewArtifactWorkspaceIdentity(manifest));
8790
+ if (typeof manifest.appId !== "string" || manifest.appId.length === 0 || manifest.appId.length > 256) {
8791
+ reasons.push("workspace application ID is invalid");
8792
+ }
8793
+ if (typeof manifest.createdAt !== "string" || !Number.isFinite(Date.parse(manifest.createdAt))) {
8794
+ reasons.push("workspace manifest creation time is invalid");
8795
+ }
8796
+ return reasons;
8797
+ }
8798
+ function validatePreviewArtifactWorkspaceIdentity(identity) {
8799
+ const reasons = [];
8800
+ if (!WORKSPACE_BACKUP_ID_PATTERN.test(identity.workspaceBackupId)) {
8801
+ reasons.push("workspace backup ID is invalid");
8802
+ }
8803
+ if (identity.commitId !== "UNBORN" && !GIT_OBJECT_ID_PATTERN.test(identity.commitId)) {
8804
+ reasons.push("workspace commit ID is invalid");
8805
+ }
8806
+ const validation = previewArtifactSourceTreeValidation(identity);
8807
+ let normalizedIncludePaths;
8808
+ try {
8809
+ normalizedIncludePaths = normalizeSourceTreeIncludePaths(
8810
+ validation.includePaths
8811
+ );
8812
+ } catch {
8813
+ reasons.push("workspace source tree include paths are invalid");
8814
+ }
8815
+ if (typeof validation.enabled !== "boolean" || !Array.isArray(validation.includePaths) || !normalizedIncludePaths || normalizedIncludePaths.length !== validation.includePaths.length || normalizedIncludePaths.some(
8816
+ (value, index) => value !== validation.includePaths[index]
8817
+ )) {
8818
+ reasons.push("workspace source tree validation policy is invalid");
8819
+ }
8820
+ if (!validation.enabled) {
8821
+ if (identity.sourceTreeId != null || identity.workspaceVersion != null) {
8822
+ reasons.push(
8823
+ "disabled source tree validation must not persist an identity"
8824
+ );
8825
+ }
8826
+ return reasons;
8827
+ }
8828
+ if (typeof identity.sourceTreeId !== "string" || !SOURCE_TREE_ID_PATTERN.test(identity.sourceTreeId)) {
8829
+ reasons.push("workspace source tree ID is invalid");
8830
+ }
8831
+ if (typeof identity.workspaceVersion !== "string" || !WORKSPACE_VERSION_PATTERN.test(identity.workspaceVersion)) {
8832
+ reasons.push("workspace version is invalid");
8833
+ } else if (typeof identity.sourceTreeId === "string") {
8834
+ try {
8835
+ if (workspaceVersionForSourceTreeId(identity.sourceTreeId) !== identity.workspaceVersion) {
8836
+ reasons.push("workspace version does not match source tree ID");
8837
+ }
8838
+ } catch {
8839
+ }
8840
+ }
8841
+ return reasons;
8842
+ }
8843
+ function readPreviewArtifactWorkspaceManifest(manifestFile) {
8844
+ const manifest = JSON.parse(
8845
+ fs30.readFileSync(path26.resolve(manifestFile), "utf8")
8846
+ );
8847
+ const reasons = validatePreviewArtifactWorkspaceManifest(manifest);
8848
+ if (reasons.length > 0) {
8849
+ throw new Error(reasons.join("; "));
8850
+ }
8851
+ return manifest;
8852
+ }
8853
+ function createPreviewArtifactWorkspaceManifest(options) {
8854
+ if (!WORKSPACE_BACKUP_ID_PATTERN.test(options.workspaceBackupId)) {
8855
+ throw new Error(
8856
+ `invalid workspace backup ID: ${options.workspaceBackupId}`
8857
+ );
8858
+ }
8859
+ if (!options.appId || options.appId.length > 256) {
8860
+ throw new Error(`invalid application ID: ${options.appId}`);
8861
+ }
8862
+ const projectRoot = fs30.realpathSync(path26.resolve(options.projectRoot));
8863
+ const repositoryRoot = runGit(projectRoot, ["rev-parse", "--show-toplevel"]);
8864
+ if (!repositoryRoot || fs30.realpathSync(repositoryRoot) !== projectRoot) {
8865
+ throw new Error("project root must be the Git repository root");
8866
+ }
8867
+ const sourceTreeValidation = createPreviewArtifactSourceTreeValidation(
8868
+ options.validateSourceTree,
8869
+ options.sourceTreeIncludePaths
8870
+ );
8871
+ let sourceTreeId;
8872
+ let workspaceVersion;
8873
+ if (sourceTreeValidation.enabled) {
8874
+ const temporaryRoot = fs30.mkdtempSync(
8875
+ path26.join(os4.tmpdir(), "preview-workspace-identity-")
8876
+ );
8877
+ try {
8878
+ const temporaryIndex = path26.join(temporaryRoot, "index");
8879
+ const temporaryObjects = path26.join(temporaryRoot, "objects");
8880
+ fs30.mkdirSync(temporaryObjects, { recursive: true });
8881
+ const gitObjectsPath = resolveGitPath(
8882
+ projectRoot,
8883
+ runGit(projectRoot, ["rev-parse", "--git-path", "objects"])
8884
+ );
8885
+ const isolatedGitEnvironment = {
8886
+ GIT_INDEX_FILE: temporaryIndex,
8887
+ GIT_OBJECT_DIRECTORY: temporaryObjects,
8888
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: gitObjectsPath,
8889
+ GIT_OPTIONAL_LOCKS: "0"
8890
+ };
8891
+ runGit(projectRoot, ["read-tree", "--empty"], isolatedGitEnvironment);
8892
+ const includedPaths = sourceTreeValidation.includePaths.length === 0 ? ["."] : sourceTreeValidation.includePaths.filter(
8893
+ (includedPath) => fs30.existsSync(path26.join(projectRoot, includedPath))
8894
+ );
8895
+ if (includedPaths.length > 0) {
8896
+ runGit(
8897
+ projectRoot,
8898
+ [
8899
+ "add",
8900
+ "-A",
8901
+ "--",
8902
+ ...includedPaths.map(
8903
+ (includedPath) => includedPath === "." ? "." : `:(top,literal)${includedPath}`
8904
+ )
8905
+ ],
8906
+ isolatedGitEnvironment
8907
+ );
8908
+ }
8909
+ const treeId = runGit(
8910
+ projectRoot,
8911
+ ["write-tree"],
8912
+ isolatedGitEnvironment
8913
+ );
8914
+ if (!treeId || !GIT_OBJECT_ID_PATTERN.test(treeId)) {
8915
+ throw new Error(
8916
+ `git write-tree returned an invalid object ID: ${treeId}`
8917
+ );
8918
+ }
8919
+ sourceTreeId = `git-tree-${gitObjectFormat(
8920
+ projectRoot,
8921
+ treeId
8922
+ )}:${treeId}`;
8923
+ workspaceVersion = workspaceVersionForSourceTreeId(sourceTreeId);
8924
+ } finally {
8925
+ fs30.rmSync(temporaryRoot, { recursive: true, force: true });
8926
+ }
8927
+ }
8928
+ const manifest = {
8929
+ schemaVersion: PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION,
8930
+ workspaceBackupId: options.workspaceBackupId,
8931
+ appId: options.appId,
8932
+ commitId: runGit(projectRoot, ["rev-parse", "HEAD"], void 0, true) ?? "UNBORN",
8933
+ sourceTreeValidation,
8934
+ ...sourceTreeId == null ? {} : { sourceTreeId },
8935
+ ...workspaceVersion == null ? {} : { workspaceVersion },
8936
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8937
+ };
8938
+ writeJsonAtomic(path26.resolve(options.manifestFile), manifest);
8939
+ return manifest;
8940
+ }
8941
+
8942
+ // src/commands/preview-artifact/generation.ts
8943
+ var PREVIEW_ARTIFACT_SCHEMA_VERSION = 4;
8690
8944
  var PREVIEW_ARTIFACT_PROTOCOL_VERSION = 3;
8691
8945
  var MANIFEST_FILE = "manifest.json";
8692
8946
  var PROBE_FILE = "probe.json";
@@ -8733,7 +8987,7 @@ function previewArtifactBuildEnvironmentHash(environment, appBasePath) {
8733
8987
  ([left], [right]) => left.localeCompare(right)
8734
8988
  )
8735
8989
  );
8736
- return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
8990
+ return crypto2.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
8737
8991
  }
8738
8992
  function parseNodeMajor(nodeVersion) {
8739
8993
  const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
@@ -8743,13 +8997,13 @@ function parseNodeMajor(nodeVersion) {
8743
8997
  return Number(match[1]);
8744
8998
  }
8745
8999
  function sha256File(filePath) {
8746
- const hash = crypto.createHash("sha256");
8747
- hash.update(fs30.readFileSync(filePath));
9000
+ const hash = crypto2.createHash("sha256");
9001
+ hash.update(fs31.readFileSync(filePath));
8748
9002
  return hash.digest("hex");
8749
9003
  }
8750
9004
  function fileIdentity(generationDir, relativePath) {
8751
- const filePath = path26.join(generationDir, relativePath);
8752
- const stat = fs30.lstatSync(filePath);
9005
+ const filePath = path27.join(generationDir, relativePath);
9006
+ const stat = fs31.lstatSync(filePath);
8753
9007
  if (!stat.isFile() || stat.isSymbolicLink()) {
8754
9008
  throw new Error(`artifact file must be a regular file: ${relativePath}`);
8755
9009
  }
@@ -8760,16 +9014,16 @@ function fileIdentity(generationDir, relativePath) {
8760
9014
  };
8761
9015
  }
8762
9016
  function readJson2(filePath) {
8763
- return JSON.parse(fs30.readFileSync(filePath, "utf8"));
9017
+ return JSON.parse(fs31.readFileSync(filePath, "utf8"));
8764
9018
  }
8765
- function writeJsonAtomic(filePath, value) {
8766
- fs30.mkdirSync(path26.dirname(filePath), { recursive: true });
9019
+ function writeJsonAtomic2(filePath, value) {
9020
+ fs31.mkdirSync(path27.dirname(filePath), { recursive: true });
8767
9021
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
8768
- fs30.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9022
+ fs31.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
8769
9023
  `, {
8770
9024
  mode: 384
8771
9025
  });
8772
- fs30.renameSync(temporaryPath, filePath);
9026
+ fs31.renameSync(temporaryPath, filePath);
8773
9027
  }
8774
9028
  function runtimeIdentity(nodeVersion, platform, arch) {
8775
9029
  return { nodeMajor: parseNodeMajor(nodeVersion), platform, arch };
@@ -8778,12 +9032,23 @@ function sameRuntime(left, right) {
8778
9032
  return left.nodeMajor === right.nodeMajor && left.platform === right.platform && left.arch === right.arch;
8779
9033
  }
8780
9034
  function createPreviewArtifactManifest(options) {
8781
- const generationDir = path26.resolve(options.generationDir);
9035
+ const generationDir = path27.resolve(options.generationDir);
9036
+ const workspaceIdentityReasons = validatePreviewArtifactWorkspaceIdentity(
9037
+ options.workspaceManifest
9038
+ );
9039
+ if (workspaceIdentityReasons.length > 0) {
9040
+ throw new Error(workspaceIdentityReasons.join("; "));
9041
+ }
8782
9042
  const manifest = {
8783
9043
  schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
8784
9044
  protocolVersion: PREVIEW_ARTIFACT_PROTOCOL_VERSION,
8785
9045
  generationId: options.generationId,
8786
9046
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9047
+ workspaceBackupId: options.workspaceManifest.workspaceBackupId,
9048
+ commitId: options.workspaceManifest.commitId,
9049
+ sourceTreeValidation: options.workspaceManifest.sourceTreeValidation,
9050
+ ...options.workspaceManifest.sourceTreeId == null ? {} : { sourceTreeId: options.workspaceManifest.sourceTreeId },
9051
+ ...options.workspaceManifest.workspaceVersion == null ? {} : { workspaceVersion: options.workspaceManifest.workspaceVersion },
8787
9052
  buildEnvironmentSha256: options.buildEnvironmentSha256,
8788
9053
  runtime: runtimeIdentity(
8789
9054
  options.nodeVersion,
@@ -8798,14 +9063,14 @@ function createPreviewArtifactManifest(options) {
8798
9063
  runtimeProbeStatus: "not-run",
8799
9064
  consumable: false
8800
9065
  };
8801
- writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), manifest);
8802
- fs30.rmSync(path26.join(generationDir, PROBE_FILE), { force: true });
9066
+ writeJsonAtomic2(path27.join(generationDir, MANIFEST_FILE), manifest);
9067
+ fs31.rmSync(path27.join(generationDir, PROBE_FILE), { force: true });
8803
9068
  return manifest;
8804
9069
  }
8805
9070
  function recordPreviewArtifactProbe(options) {
8806
- const generationDir = path26.resolve(options.generationDir);
9071
+ const generationDir = path27.resolve(options.generationDir);
8807
9072
  const manifest = readJson2(
8808
- path26.join(generationDir, MANIFEST_FILE)
9073
+ path27.join(generationDir, MANIFEST_FILE)
8809
9074
  );
8810
9075
  const probe = {
8811
9076
  schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
@@ -8818,8 +9083,8 @@ function recordPreviewArtifactProbe(options) {
8818
9083
  options.platform,
8819
9084
  options.arch
8820
9085
  ),
8821
- serverSha256: sha256File(path26.join(generationDir, SERVER_FILE)),
8822
- clientSha256: sha256File(path26.join(generationDir, CLIENT_FILE)),
9086
+ serverSha256: sha256File(path27.join(generationDir, SERVER_FILE)),
9087
+ clientSha256: sha256File(path27.join(generationDir, CLIENT_FILE)),
8823
9088
  coverage: {
8824
9089
  html: options.coverage.html,
8825
9090
  businessApi: options.coverage.businessApi,
@@ -8827,20 +9092,20 @@ function recordPreviewArtifactProbe(options) {
8827
9092
  actionPlugins: Array.from(new Set(options.coverage.actionPlugins)).sort()
8828
9093
  }
8829
9094
  };
8830
- writeJsonAtomic(path26.join(generationDir, PROBE_FILE), probe);
9095
+ writeJsonAtomic2(path27.join(generationDir, PROBE_FILE), probe);
8831
9096
  return probe;
8832
9097
  }
8833
9098
  function promotePreviewArtifactGeneration(generationDirInput) {
8834
- const generationDir = path26.resolve(generationDirInput);
9099
+ const generationDir = path27.resolve(generationDirInput);
8835
9100
  const reasons = [];
8836
9101
  let manifest;
8837
9102
  let probe;
8838
9103
  try {
8839
9104
  manifest = readJson2(
8840
- path26.join(generationDir, MANIFEST_FILE)
9105
+ path27.join(generationDir, MANIFEST_FILE)
8841
9106
  );
8842
9107
  probe = readJson2(
8843
- path26.join(generationDir, PROBE_FILE)
9108
+ path27.join(generationDir, PROBE_FILE)
8844
9109
  );
8845
9110
  } catch (error) {
8846
9111
  return {
@@ -8864,8 +9129,8 @@ function promotePreviewArtifactGeneration(generationDirInput) {
8864
9129
  "probe runtime identity does not match build runtime identity"
8865
9130
  );
8866
9131
  }
8867
- const currentServerHash = sha256File(path26.join(generationDir, SERVER_FILE));
8868
- const currentClientHash = sha256File(path26.join(generationDir, CLIENT_FILE));
9132
+ const currentServerHash = sha256File(path27.join(generationDir, SERVER_FILE));
9133
+ const currentClientHash = sha256File(path27.join(generationDir, CLIENT_FILE));
8869
9134
  if (currentServerHash !== manifest.files.server.sha256 || currentServerHash !== probe.serverSha256) {
8870
9135
  reasons.push("server.bundle.cjs hash does not match probe identity");
8871
9136
  }
@@ -8898,16 +9163,16 @@ function promotePreviewArtifactGeneration(generationDirInput) {
8898
9163
  runtimeProbeStatus: "passed",
8899
9164
  consumable: true
8900
9165
  };
8901
- writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), promotedManifest);
9166
+ writeJsonAtomic2(path27.join(generationDir, MANIFEST_FILE), promotedManifest);
8902
9167
  return { promoted: true, manifest: promotedManifest };
8903
9168
  }
8904
9169
 
8905
9170
  // src/commands/preview-artifact/probe.ts
8906
- import * as fs31 from "fs";
9171
+ import * as fs32 from "fs";
8907
9172
  import * as net from "net";
8908
- import * as os4 from "os";
8909
- import * as path27 from "path";
8910
- import { spawn as spawn2, spawnSync as spawnSync7 } from "child_process";
9173
+ import * as os5 from "os";
9174
+ import * as path28 from "path";
9175
+ import { spawn as spawn2, spawnSync as spawnSync8 } from "child_process";
8911
9176
 
8912
9177
  // src/commands/preview-artifact/readiness.ts
8913
9178
  function isPreviewBusinessResponseReady(response) {
@@ -8917,10 +9182,10 @@ function isPreviewBusinessResponseReady(response) {
8917
9182
 
8918
9183
  // src/commands/preview-artifact/probe.ts
8919
9184
  function sleep(ms) {
8920
- return new Promise((resolve9) => setTimeout(resolve9, ms));
9185
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
8921
9186
  }
8922
9187
  async function reservePort() {
8923
- return new Promise((resolve9, reject) => {
9188
+ return new Promise((resolve10, reject) => {
8924
9189
  const server = net.createServer();
8925
9190
  server.unref();
8926
9191
  server.once("error", reject);
@@ -8932,13 +9197,13 @@ async function reservePort() {
8932
9197
  }
8933
9198
  server.close((error) => {
8934
9199
  if (error) reject(error);
8935
- else resolve9(address.port);
9200
+ else resolve10(address.port);
8936
9201
  });
8937
9202
  });
8938
9203
  });
8939
9204
  }
8940
9205
  function archiveEntries(archivePath) {
8941
- const listing = spawnSync7("unzip", ["-Z1", archivePath], {
9206
+ const listing = spawnSync8("unzip", ["-Z1", archivePath], {
8942
9207
  encoding: "utf8"
8943
9208
  });
8944
9209
  if (listing.status !== 0) {
@@ -8946,15 +9211,15 @@ function archiveEntries(archivePath) {
8946
9211
  }
8947
9212
  const entries = listing.stdout.split("\n").filter(Boolean);
8948
9213
  for (const entry of entries) {
8949
- const normalized = path27.posix.normalize(entry.replaceAll("\\", "/"));
8950
- if (path27.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
9214
+ const normalized = path28.posix.normalize(entry.replaceAll("\\", "/"));
9215
+ if (path28.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
8951
9216
  throw new Error(`client archive contains unsafe path: ${entry}`);
8952
9217
  }
8953
9218
  }
8954
9219
  return entries;
8955
9220
  }
8956
9221
  function rejectArchivedSymlinks(archivePath) {
8957
- const listing = spawnSync7("unzip", ["-Z", "-l", archivePath], {
9222
+ const listing = spawnSync8("unzip", ["-Z", "-l", archivePath], {
8958
9223
  encoding: "utf8"
8959
9224
  });
8960
9225
  if (listing.status !== 0) {
@@ -8973,9 +9238,9 @@ function rejectSymlinks(root) {
8973
9238
  while (pending.length > 0) {
8974
9239
  const current = pending.pop();
8975
9240
  if (!current) continue;
8976
- for (const entry of fs31.readdirSync(current, { withFileTypes: true })) {
8977
- const entryPath = path27.join(current, entry.name);
8978
- const stat = fs31.lstatSync(entryPath);
9241
+ for (const entry of fs32.readdirSync(current, { withFileTypes: true })) {
9242
+ const entryPath = path28.join(current, entry.name);
9243
+ const stat = fs32.lstatSync(entryPath);
8979
9244
  if (stat.isSymbolicLink()) {
8980
9245
  throw new Error(`client archive contains symlink: ${entry.name}`);
8981
9246
  }
@@ -8988,8 +9253,8 @@ function inspectClientArchive(archivePath) {
8988
9253
  rejectArchivedSymlinks(archivePath);
8989
9254
  }
8990
9255
  function isPathInside(parent, candidate) {
8991
- const relative5 = path27.relative(parent, candidate);
8992
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative5);
9256
+ const relative5 = path28.relative(parent, candidate);
9257
+ return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path28.sep}`) && !path28.isAbsolute(relative5);
8993
9258
  }
8994
9259
  function clientEntryAssetReferences(html, appBasePath) {
8995
9260
  const local = /* @__PURE__ */ new Set();
@@ -9049,23 +9314,23 @@ function resolveClientEntryAsset(clientRoot, appBasePath, requestPath) {
9049
9314
  const normalizedBase = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
9050
9315
  const relative5 = decoded.startsWith(normalizedBase) ? decoded.slice(normalizedBase.length) : decoded.replace(/^\/+/, "");
9051
9316
  if (!relative5 || relative5 === "index.html") return void 0;
9052
- const candidate = path27.resolve(clientRoot, relative5);
9317
+ const candidate = path28.resolve(clientRoot, relative5);
9053
9318
  if (!isPathInside(clientRoot, candidate)) return void 0;
9054
9319
  try {
9055
- const stat = fs31.lstatSync(candidate);
9320
+ const stat = fs32.lstatSync(candidate);
9056
9321
  return stat.isFile() && !stat.isSymbolicLink() ? candidate : void 0;
9057
9322
  } catch {
9058
9323
  return void 0;
9059
9324
  }
9060
9325
  }
9061
9326
  function extractClientArchive(archivePath, destination) {
9062
- if (fs31.existsSync(destination)) {
9327
+ if (fs32.existsSync(destination)) {
9063
9328
  throw new Error("client archive destination must not already exist");
9064
9329
  }
9065
9330
  inspectClientArchive(archivePath);
9066
- fs31.mkdirSync(destination, { recursive: true });
9331
+ fs32.mkdirSync(destination, { recursive: true });
9067
9332
  try {
9068
- const extraction = spawnSync7(
9333
+ const extraction = spawnSync8(
9069
9334
  "unzip",
9070
9335
  ["-q", archivePath, "-d", destination],
9071
9336
  { encoding: "utf8" }
@@ -9077,17 +9342,17 @@ function extractClientArchive(archivePath, destination) {
9077
9342
  }
9078
9343
  rejectSymlinks(destination);
9079
9344
  } catch (error) {
9080
- fs31.rmSync(destination, { recursive: true, force: true });
9345
+ fs32.rmSync(destination, { recursive: true, force: true });
9081
9346
  throw error;
9082
9347
  }
9083
9348
  }
9084
9349
  async function extractInspectedClientArchive(archivePath, destination) {
9085
- if (fs31.existsSync(destination)) {
9350
+ if (fs32.existsSync(destination)) {
9086
9351
  throw new Error("client archive destination must not already exist");
9087
9352
  }
9088
- fs31.mkdirSync(destination, { recursive: true });
9353
+ fs32.mkdirSync(destination, { recursive: true });
9089
9354
  try {
9090
- await new Promise((resolve9, reject) => {
9355
+ await new Promise((resolve10, reject) => {
9091
9356
  const extraction = spawn2(
9092
9357
  "unzip",
9093
9358
  ["-q", archivePath, "-d", destination],
@@ -9100,7 +9365,7 @@ async function extractInspectedClientArchive(archivePath, destination) {
9100
9365
  });
9101
9366
  extraction.once("error", reject);
9102
9367
  extraction.once("close", (code) => {
9103
- if (code === 0) resolve9();
9368
+ if (code === 0) resolve10();
9104
9369
  else
9105
9370
  reject(
9106
9371
  new Error(
@@ -9111,7 +9376,7 @@ async function extractInspectedClientArchive(archivePath, destination) {
9111
9376
  });
9112
9377
  rejectSymlinks(destination);
9113
9378
  } catch (error) {
9114
- fs31.rmSync(destination, { recursive: true, force: true });
9379
+ fs32.rmSync(destination, { recursive: true, force: true });
9115
9380
  throw error;
9116
9381
  }
9117
9382
  }
@@ -9141,9 +9406,9 @@ function stopProcessGroup(pid) {
9141
9406
  }
9142
9407
  }
9143
9408
  async function runPreviewArtifactProbe(options) {
9144
- const generationDir = path27.resolve(options.generationDir);
9145
- const runtimeRoot = fs31.mkdtempSync(
9146
- path27.join(os4.tmpdir(), "preview-artifact-probe-")
9409
+ const generationDir = path28.resolve(options.generationDir);
9410
+ const runtimeRoot = fs32.mkdtempSync(
9411
+ path28.join(os5.tmpdir(), "preview-artifact-probe-")
9147
9412
  );
9148
9413
  const coverage = {
9149
9414
  html: false,
@@ -9152,19 +9417,19 @@ async function runPreviewArtifactProbe(options) {
9152
9417
  actionPlugins: []
9153
9418
  };
9154
9419
  const reasons = [];
9155
- const registryFile = path27.join(runtimeRoot, "plugin-registry.json");
9420
+ const registryFile = path28.join(runtimeRoot, "plugin-registry.json");
9156
9421
  let isolatedNodeModulesPresent = false;
9157
9422
  let child;
9158
9423
  try {
9159
- const runtimeBundle = path27.join(runtimeRoot, "server.bundle.cjs");
9160
- fs31.copyFileSync(
9161
- path27.join(generationDir, "server.bundle.cjs"),
9424
+ const runtimeBundle = path28.join(runtimeRoot, "server.bundle.cjs");
9425
+ fs32.copyFileSync(
9426
+ path28.join(generationDir, "server.bundle.cjs"),
9162
9427
  runtimeBundle
9163
9428
  );
9164
- const clientRoot = path27.join(runtimeRoot, "dist", "client");
9165
- extractClientArchive(path27.join(generationDir, "client.zip"), clientRoot);
9166
- isolatedNodeModulesPresent = fs31.existsSync(
9167
- path27.join(runtimeRoot, "node_modules")
9429
+ const clientRoot = path28.join(runtimeRoot, "dist", "client");
9430
+ extractClientArchive(path28.join(generationDir, "client.zip"), clientRoot);
9431
+ isolatedNodeModulesPresent = fs32.existsSync(
9432
+ path28.join(runtimeRoot, "node_modules")
9168
9433
  );
9169
9434
  if (isolatedNodeModulesPresent) {
9170
9435
  reasons.push("isolated runtime unexpectedly contains node_modules");
@@ -9227,12 +9492,12 @@ async function runPreviewArtifactProbe(options) {
9227
9492
  `business API returned ${apiResponse?.status ?? "unreachable"}`
9228
9493
  );
9229
9494
  }
9230
- while (!fs31.existsSync(registryFile) && Date.now() < timeoutAt) {
9495
+ while (!fs32.existsSync(registryFile) && Date.now() < timeoutAt) {
9231
9496
  await sleep(25);
9232
9497
  }
9233
- if (fs31.existsSync(registryFile)) {
9498
+ if (fs32.existsSync(registryFile)) {
9234
9499
  const parsed = JSON.parse(
9235
- fs31.readFileSync(registryFile, "utf8")
9500
+ fs32.readFileSync(registryFile, "utf8")
9236
9501
  );
9237
9502
  if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) {
9238
9503
  coverage.actionPlugins = parsed.filter((value) => typeof value === "string").sort();
@@ -9256,17 +9521,17 @@ async function runPreviewArtifactProbe(options) {
9256
9521
  arch: process.arch,
9257
9522
  coverage
9258
9523
  });
9259
- fs31.rmSync(runtimeRoot, { recursive: true, force: true });
9524
+ fs32.rmSync(runtimeRoot, { recursive: true, force: true });
9260
9525
  return { ...probe, isolatedNodeModulesPresent };
9261
9526
  }
9262
9527
 
9263
9528
  // src/commands/preview-artifact/environment.ts
9264
- import * as fs32 from "fs";
9265
- import * as path28 from "path";
9529
+ import * as fs33 from "fs";
9530
+ import * as path29 from "path";
9266
9531
  var ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
9267
9532
  function readPreviewArtifactEnvironmentFile(filePath) {
9268
- const resolved = path28.resolve(filePath);
9269
- const parsed = JSON.parse(fs32.readFileSync(resolved, "utf8"));
9533
+ const resolved = path29.resolve(filePath);
9534
+ const parsed = JSON.parse(fs33.readFileSync(resolved, "utf8"));
9270
9535
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9271
9536
  throw new Error("preview artifact environment file must contain an object");
9272
9537
  }
@@ -9291,40 +9556,42 @@ function previewArtifactRuntimeEnvironment(environment) {
9291
9556
  }
9292
9557
 
9293
9558
  // src/commands/preview-artifact/store.ts
9294
- import * as crypto2 from "crypto";
9295
- import * as fs33 from "fs";
9296
- import * as path29 from "path";
9559
+ import * as crypto3 from "crypto";
9560
+ import * as fs34 from "fs";
9561
+ import * as path30 from "path";
9297
9562
  var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9563
+ var WORKSPACE_BACKUP_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9564
+ var WORKSPACE_VERSION_PATTERN2 = /^sha256:[a-f0-9]{64}$/;
9298
9565
  function readCurrentPointer(storeRoot) {
9299
9566
  try {
9300
9567
  const pointer = readJson3(
9301
- path29.join(storeRoot, "current.json")
9568
+ path30.join(storeRoot, "current.json")
9302
9569
  );
9303
- return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) ? pointer : void 0;
9570
+ return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) && WORKSPACE_BACKUP_ID_PATTERN2.test(pointer.workspaceBackupId) && (pointer.workspaceVersion == null || WORKSPACE_VERSION_PATTERN2.test(pointer.workspaceVersion)) ? pointer : void 0;
9304
9571
  } catch {
9305
9572
  return void 0;
9306
9573
  }
9307
9574
  }
9308
9575
  function readJson3(filePath) {
9309
- return JSON.parse(fs33.readFileSync(filePath, "utf8"));
9576
+ return JSON.parse(fs34.readFileSync(filePath, "utf8"));
9310
9577
  }
9311
- function writeJsonAtomic2(filePath, value) {
9312
- fs33.mkdirSync(path29.dirname(filePath), { recursive: true });
9578
+ function writeJsonAtomic3(filePath, value) {
9579
+ fs34.mkdirSync(path30.dirname(filePath), { recursive: true });
9313
9580
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9314
- fs33.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9581
+ fs34.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9315
9582
  `, {
9316
9583
  mode: 384
9317
9584
  });
9318
- fs33.renameSync(temporaryPath, filePath);
9585
+ fs34.renameSync(temporaryPath, filePath);
9319
9586
  }
9320
9587
  function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
9321
9588
  const retained = new Set(retainedGenerationIds);
9322
- for (const entry of fs33.readdirSync(generationsRoot, {
9589
+ for (const entry of fs34.readdirSync(generationsRoot, {
9323
9590
  withFileTypes: true
9324
9591
  })) {
9325
9592
  if (retained.has(entry.name)) continue;
9326
9593
  try {
9327
- fs33.rmSync(path29.join(generationsRoot, entry.name), {
9594
+ fs34.rmSync(path30.join(generationsRoot, entry.name), {
9328
9595
  recursive: true,
9329
9596
  force: true
9330
9597
  });
@@ -9337,12 +9604,12 @@ function parseNodeMajor2(nodeVersion) {
9337
9604
  return match ? Number(match[1]) : null;
9338
9605
  }
9339
9606
  function isPathInside2(parent, candidate) {
9340
- const relative5 = path29.relative(parent, candidate);
9341
- return relative5 !== "" && !relative5.startsWith(`..${path29.sep}`) && relative5 !== ".." && !path29.isAbsolute(relative5);
9607
+ const relative5 = path30.relative(parent, candidate);
9608
+ return relative5 !== "" && !relative5.startsWith(`..${path30.sep}`) && relative5 !== ".." && !path30.isAbsolute(relative5);
9342
9609
  }
9343
9610
  function validateRegularDirectory(directoryPath, label) {
9344
9611
  try {
9345
- const stat = fs33.lstatSync(directoryPath);
9612
+ const stat = fs34.lstatSync(directoryPath);
9346
9613
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
9347
9614
  return [`${label} must be a regular directory`];
9348
9615
  }
@@ -9363,10 +9630,10 @@ function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9363
9630
  let probe;
9364
9631
  try {
9365
9632
  manifest = readJson3(
9366
- path29.join(generationDir, "manifest.json")
9633
+ path30.join(generationDir, "manifest.json")
9367
9634
  );
9368
9635
  probe = readJson3(
9369
- path29.join(generationDir, "probe.json")
9636
+ path30.join(generationDir, "probe.json")
9370
9637
  );
9371
9638
  } catch (error) {
9372
9639
  return {
@@ -9393,6 +9660,7 @@ function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9393
9660
  if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
9394
9661
  reasons.push("build environment identity is invalid");
9395
9662
  }
9663
+ reasons.push(...validatePreviewArtifactWorkspaceIdentity(manifest));
9396
9664
  for (const [label, identity, expectedPath] of [
9397
9665
  ["server", manifest.files?.server, "server.bundle.cjs"],
9398
9666
  ["client", manifest.files?.client, "client.zip"]
@@ -9402,8 +9670,8 @@ function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9402
9670
  }
9403
9671
  }
9404
9672
  if (verifyPayloads && manifest.files?.server && manifest.files?.client) {
9405
- const serverPath = path29.join(generationDir, "server.bundle.cjs");
9406
- const clientPath = path29.join(generationDir, "client.zip");
9673
+ const serverPath = path30.join(generationDir, "server.bundle.cjs");
9674
+ const clientPath = path30.join(generationDir, "client.zip");
9407
9675
  try {
9408
9676
  if (sha256File(serverPath) !== manifest.files.server.sha256) {
9409
9677
  reasons.push("server.bundle.cjs hash does not match manifest");
@@ -9426,10 +9694,10 @@ function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9426
9694
  return { manifest, reasons };
9427
9695
  }
9428
9696
  function publishPreviewArtifactGeneration(options) {
9429
- const storeRoot = path29.resolve(options.storeRoot);
9697
+ const storeRoot = path30.resolve(options.storeRoot);
9430
9698
  const previousPointer = readCurrentPointer(storeRoot);
9431
- const stagingRoot = path29.join(storeRoot, ".staging");
9432
- const stagingDir = path29.resolve(options.stagingDir);
9699
+ const stagingRoot = path30.join(storeRoot, ".staging");
9700
+ const stagingDir = path30.resolve(options.stagingDir);
9433
9701
  if (!isPathInside2(stagingRoot, stagingDir)) {
9434
9702
  return {
9435
9703
  published: false,
@@ -9443,23 +9711,25 @@ function publishPreviewArtifactGeneration(options) {
9443
9711
  return { published: false, reasons: validation.reasons };
9444
9712
  }
9445
9713
  const generationId = validation.manifest.generationId;
9446
- const generationsRoot = path29.join(storeRoot, "generations");
9447
- const generationDir = path29.join(generationsRoot, generationId);
9448
- fs33.mkdirSync(generationsRoot, { recursive: true });
9449
- if (fs33.existsSync(generationDir)) {
9714
+ const generationsRoot = path30.join(storeRoot, "generations");
9715
+ const generationDir = path30.join(generationsRoot, generationId);
9716
+ fs34.mkdirSync(generationsRoot, { recursive: true });
9717
+ if (fs34.existsSync(generationDir)) {
9450
9718
  return {
9451
9719
  published: false,
9452
9720
  reasons: [`generation already exists: ${generationId}`]
9453
9721
  };
9454
9722
  }
9455
9723
  try {
9456
- fs33.renameSync(stagingDir, generationDir);
9724
+ fs34.renameSync(stagingDir, generationDir);
9457
9725
  const pointer = {
9458
9726
  schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9459
9727
  generationId,
9728
+ workspaceBackupId: validation.manifest.workspaceBackupId,
9729
+ ...validation.manifest.workspaceVersion == null ? {} : { workspaceVersion: validation.manifest.workspaceVersion },
9460
9730
  publishedAt: (/* @__PURE__ */ new Date()).toISOString()
9461
9731
  };
9462
- writeJsonAtomic2(path29.join(storeRoot, "current.json"), pointer);
9732
+ writeJsonAtomic3(path30.join(storeRoot, "current.json"), pointer);
9463
9733
  pruneSupersededGenerations(
9464
9734
  generationsRoot,
9465
9735
  [generationId, previousPointer?.generationId].filter(
@@ -9477,10 +9747,37 @@ function publishPreviewArtifactGeneration(options) {
9477
9747
  }
9478
9748
  }
9479
9749
  function resolveCurrentPreviewArtifactMetadata(options) {
9480
- const storeRoot = path29.resolve(options.storeRoot);
9750
+ if (!options.workspaceManifest) {
9751
+ return {
9752
+ resolved: false,
9753
+ reasons: ["workspace manifest is unavailable"]
9754
+ };
9755
+ }
9756
+ const workspaceManifestReasons = validatePreviewArtifactWorkspaceManifest(
9757
+ options.workspaceManifest
9758
+ );
9759
+ if (workspaceManifestReasons.length > 0) {
9760
+ return {
9761
+ resolved: false,
9762
+ reasons: workspaceManifestReasons
9763
+ };
9764
+ }
9765
+ let requestedSourceTreeValidation;
9766
+ try {
9767
+ requestedSourceTreeValidation = createPreviewArtifactSourceTreeValidation(
9768
+ options.sourceTreeValidation?.enabled,
9769
+ options.sourceTreeValidation?.includePaths
9770
+ );
9771
+ } catch {
9772
+ return {
9773
+ resolved: false,
9774
+ reasons: ["requested source tree validation policy is invalid"]
9775
+ };
9776
+ }
9777
+ const storeRoot = path30.resolve(options.storeRoot);
9481
9778
  let pointer;
9482
9779
  try {
9483
- pointer = readJson3(path29.join(storeRoot, "current.json"));
9780
+ pointer = readJson3(path30.join(storeRoot, "current.json"));
9484
9781
  } catch (error) {
9485
9782
  return {
9486
9783
  resolved: false,
@@ -9489,17 +9786,19 @@ function resolveCurrentPreviewArtifactMetadata(options) {
9489
9786
  ]
9490
9787
  };
9491
9788
  }
9492
- if (pointer.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || !GENERATION_ID_PATTERN.test(pointer.generationId)) {
9789
+ if (pointer.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || !GENERATION_ID_PATTERN.test(pointer.generationId) || !WORKSPACE_BACKUP_ID_PATTERN2.test(pointer.workspaceBackupId) || pointer.workspaceVersion != null && !WORKSPACE_VERSION_PATTERN2.test(pointer.workspaceVersion)) {
9493
9790
  return {
9494
9791
  resolved: false,
9495
- reasons: ["current generation ID or schema version is invalid"]
9792
+ reasons: [
9793
+ "current generation ID, workspace identity, or schema version is invalid"
9794
+ ]
9496
9795
  };
9497
9796
  }
9498
9797
  const nodeMajor = parseNodeMajor2(options.nodeVersion);
9499
9798
  if (nodeMajor == null) {
9500
9799
  return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
9501
9800
  }
9502
- const generationDir = path29.join(
9801
+ const generationDir = path30.join(
9503
9802
  storeRoot,
9504
9803
  "generations",
9505
9804
  pointer.generationId
@@ -9516,12 +9815,38 @@ function resolveCurrentPreviewArtifactMetadata(options) {
9516
9815
  if (!validation.manifest || validation.reasons.length > 0) {
9517
9816
  return { resolved: false, reasons: validation.reasons };
9518
9817
  }
9519
- if (validation.manifest.generationId !== pointer.generationId) {
9818
+ if (validation.manifest.generationId !== pointer.generationId || validation.manifest.workspaceBackupId !== pointer.workspaceBackupId || validation.manifest.workspaceVersion !== pointer.workspaceVersion) {
9520
9819
  return {
9521
9820
  resolved: false,
9522
9821
  reasons: ["current pointer does not match generation manifest"]
9523
9822
  };
9524
9823
  }
9824
+ if (requestedSourceTreeValidation.enabled) {
9825
+ const generationSourceTreeValidation = previewArtifactSourceTreeValidation(
9826
+ validation.manifest
9827
+ );
9828
+ const workspaceSourceTreeValidation = previewArtifactSourceTreeValidation(
9829
+ options.workspaceManifest
9830
+ );
9831
+ if (!samePreviewArtifactSourceTreeValidation(
9832
+ requestedSourceTreeValidation,
9833
+ generationSourceTreeValidation
9834
+ ) || !samePreviewArtifactSourceTreeValidation(
9835
+ requestedSourceTreeValidation,
9836
+ workspaceSourceTreeValidation
9837
+ )) {
9838
+ return {
9839
+ resolved: false,
9840
+ reasons: ["workspace source tree validation policy mismatch"]
9841
+ };
9842
+ }
9843
+ if (options.workspaceManifest.workspaceVersion !== validation.manifest.workspaceVersion) {
9844
+ return {
9845
+ resolved: false,
9846
+ reasons: ["workspace version mismatch"]
9847
+ };
9848
+ }
9849
+ }
9525
9850
  if (validation.manifest.buildEnvironmentSha256 !== options.buildEnvironmentSha256) {
9526
9851
  return {
9527
9852
  resolved: false,
@@ -9536,33 +9861,33 @@ function resolveCurrentPreviewArtifactMetadata(options) {
9536
9861
  };
9537
9862
  }
9538
9863
  async function materializeVerifiedPreviewArtifactFile(options) {
9539
- const source = path29.resolve(options.source);
9540
- const destination = path29.resolve(options.destination);
9864
+ const source = path30.resolve(options.source);
9865
+ const destination = path30.resolve(options.destination);
9541
9866
  const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
9542
- fs33.mkdirSync(path29.dirname(destination), { recursive: true });
9543
- if (fs33.existsSync(destination)) {
9867
+ fs34.mkdirSync(path30.dirname(destination), { recursive: true });
9868
+ if (fs34.existsSync(destination)) {
9544
9869
  throw new Error(
9545
9870
  `materialization destination already exists: ${destination}`
9546
9871
  );
9547
9872
  }
9548
- const noFollow = fs33.constants.O_NOFOLLOW ?? 0;
9873
+ const noFollow = fs34.constants.O_NOFOLLOW ?? 0;
9549
9874
  let sourceHandle;
9550
9875
  let destinationHandle;
9551
9876
  try {
9552
- sourceHandle = await fs33.promises.open(
9877
+ sourceHandle = await fs34.promises.open(
9553
9878
  source,
9554
- fs33.constants.O_RDONLY | noFollow
9879
+ fs34.constants.O_RDONLY | noFollow
9555
9880
  );
9556
9881
  const sourceStat = await sourceHandle.stat();
9557
9882
  if (!sourceStat.isFile()) {
9558
9883
  throw new Error(`${options.identity.path} is not a regular file`);
9559
9884
  }
9560
- destinationHandle = await fs33.promises.open(
9885
+ destinationHandle = await fs34.promises.open(
9561
9886
  temporary,
9562
- fs33.constants.O_WRONLY | fs33.constants.O_CREAT | fs33.constants.O_EXCL,
9887
+ fs34.constants.O_WRONLY | fs34.constants.O_CREAT | fs34.constants.O_EXCL,
9563
9888
  384
9564
9889
  );
9565
- const hash = crypto2.createHash("sha256");
9890
+ const hash = crypto3.createHash("sha256");
9566
9891
  const buffer = Buffer.allocUnsafe(256 * 1024);
9567
9892
  let totalBytes = 0;
9568
9893
  for (; ; ) {
@@ -9601,24 +9926,24 @@ async function materializeVerifiedPreviewArtifactFile(options) {
9601
9926
  await destinationHandle.sync();
9602
9927
  await destinationHandle.close();
9603
9928
  destinationHandle = void 0;
9604
- await fs33.promises.rename(temporary, destination);
9929
+ await fs34.promises.rename(temporary, destination);
9605
9930
  return { bytes: totalBytes, sha256 };
9606
9931
  } finally {
9607
9932
  await destinationHandle?.close().catch(() => void 0);
9608
9933
  await sourceHandle?.close().catch(() => void 0);
9609
- await fs33.promises.rm(temporary, { force: true }).catch(() => void 0);
9934
+ await fs34.promises.rm(temporary, { force: true }).catch(() => void 0);
9610
9935
  }
9611
9936
  }
9612
9937
 
9613
9938
  // src/commands/preview-artifact/producer.ts
9614
9939
  function defaultGenerationId() {
9615
9940
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
9616
- return `${timestamp}-${crypto3.randomBytes(5).toString("hex")}`;
9941
+ return `${timestamp}-${crypto4.randomBytes(5).toString("hex")}`;
9617
9942
  }
9618
9943
  function defaultPreviewBundleBuildCommands(projectRootInput) {
9619
- const projectRoot = path30.resolve(projectRootInput);
9944
+ const projectRoot = path31.resolve(projectRootInput);
9620
9945
  const executableSuffix = process.platform === "win32" ? ".cmd" : "";
9621
- const localBinary = (name) => path30.join(
9946
+ const localBinary = (name) => path31.join(
9622
9947
  projectRoot,
9623
9948
  "node_modules",
9624
9949
  ".bin",
@@ -9638,7 +9963,7 @@ function defaultPreviewBundleBuildCommands(projectRootInput) {
9638
9963
  }
9639
9964
  function runBuildCommands(projectRoot, buildCommands, environment) {
9640
9965
  for (const buildCommand of buildCommands) {
9641
- const result = spawnSync8(buildCommand.command, buildCommand.args, {
9966
+ const result = spawnSync9(buildCommand.command, buildCommand.args, {
9642
9967
  cwd: projectRoot,
9643
9968
  env: { ...process.env, ...environment },
9644
9969
  encoding: "utf8",
@@ -9656,27 +9981,27 @@ ${diagnostic}` : ""}`
9656
9981
  return [];
9657
9982
  }
9658
9983
  function createClientArchive(clientDir, archivePath) {
9659
- if (!fs34.existsSync(path30.join(clientDir, "index.html"))) {
9984
+ if (!fs35.existsSync(path31.join(clientDir, "index.html"))) {
9660
9985
  return [`client build output is missing: ${clientDir}`];
9661
9986
  }
9662
- const result = spawnSync8("zip", ["-qry", archivePath, "."], {
9987
+ const result = spawnSync9("zip", ["-qry", archivePath, "."], {
9663
9988
  cwd: clientDir,
9664
9989
  encoding: "utf8"
9665
9990
  });
9666
- if (result.status === 0 && fs34.existsSync(archivePath)) return [];
9991
+ if (result.status === 0 && fs35.existsSync(archivePath)) return [];
9667
9992
  return [`client archive failed with exit ${result.status ?? "unknown"}`];
9668
9993
  }
9669
9994
  function readActionPlugins2(metadataFile) {
9670
- const metadata = JSON.parse(fs34.readFileSync(metadataFile, "utf8"));
9995
+ const metadata = JSON.parse(fs35.readFileSync(metadataFile, "utf8"));
9671
9996
  if (!Array.isArray(metadata.actionPlugins)) return [];
9672
9997
  return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
9673
9998
  }
9674
9999
  async function producePreviewArtifactGeneration(options) {
9675
10000
  const startedAt = Date.now();
9676
- const projectRoot = path30.resolve(options.projectRoot);
9677
- const storeRoot = path30.resolve(options.storeRoot);
10001
+ const projectRoot = path31.resolve(options.projectRoot);
10002
+ const storeRoot = path31.resolve(options.storeRoot);
9678
10003
  const generationId = options.generationId ?? defaultGenerationId();
9679
- const stagingDir = path30.join(
10004
+ const stagingDir = path31.join(
9680
10005
  storeRoot,
9681
10006
  ".staging",
9682
10007
  `${generationId}.${process.pid}.${Date.now()}`
@@ -9688,7 +10013,7 @@ async function producePreviewArtifactGeneration(options) {
9688
10013
  reasons,
9689
10014
  ...probe ? { probe } : {}
9690
10015
  });
9691
- fs34.mkdirSync(stagingDir, { recursive: true });
10016
+ fs35.mkdirSync(stagingDir, { recursive: true });
9692
10017
  try {
9693
10018
  const buildEnvironment = previewArtifactBuildEnvironment(
9694
10019
  options.environment,
@@ -9701,8 +10026,8 @@ async function producePreviewArtifactGeneration(options) {
9701
10026
  buildEnvironment
9702
10027
  );
9703
10028
  if (buildReasons.length > 0) return failed(buildReasons);
9704
- const serverOutfile = path30.join(stagingDir, "server.bundle.cjs");
9705
- const serverMetadataFile = path30.join(stagingDir, "server-build.json");
10029
+ const serverOutfile = path31.join(stagingDir, "server.bundle.cjs");
10030
+ const serverMetadataFile = path31.join(stagingDir, "server-build.json");
9706
10031
  const serverResult = await buildPreviewServerArtifact({
9707
10032
  projectRoot,
9708
10033
  entry: "dist/main.js",
@@ -9711,12 +10036,12 @@ async function producePreviewArtifactGeneration(options) {
9711
10036
  });
9712
10037
  if (!serverResult.built) return failed(serverResult.reasons);
9713
10038
  const archiveReasons = createClientArchive(
9714
- path30.join(projectRoot, "dist", "client"),
9715
- path30.join(stagingDir, "client.zip")
10039
+ path31.join(projectRoot, "dist", "client"),
10040
+ path31.join(stagingDir, "client.zip")
9716
10041
  );
9717
10042
  if (archiveReasons.length > 0) return failed(archiveReasons);
9718
10043
  const actionPlugins = readActionPlugins2(serverMetadataFile);
9719
- fs34.rmSync(serverMetadataFile, { force: true });
10044
+ fs35.rmSync(serverMetadataFile, { force: true });
9720
10045
  createPreviewArtifactManifest({
9721
10046
  generationDir: stagingDir,
9722
10047
  generationId,
@@ -9727,6 +10052,7 @@ async function producePreviewArtifactGeneration(options) {
9727
10052
  options.environment,
9728
10053
  options.appBasePath
9729
10054
  ),
10055
+ workspaceManifest: options.workspaceManifest,
9730
10056
  actionPlugins
9731
10057
  });
9732
10058
  const probe = await runPreviewArtifactProbe({
@@ -9753,21 +10079,21 @@ async function producePreviewArtifactGeneration(options) {
9753
10079
  } catch (error) {
9754
10080
  return failed([error instanceof Error ? error.message : String(error)]);
9755
10081
  } finally {
9756
- fs34.rmSync(stagingDir, { recursive: true, force: true });
10082
+ fs35.rmSync(stagingDir, { recursive: true, force: true });
9757
10083
  }
9758
10084
  }
9759
10085
 
9760
10086
  // src/commands/preview-artifact/runtime.ts
9761
- import * as fs36 from "fs";
10087
+ import * as fs37 from "fs";
9762
10088
  import * as net3 from "net";
9763
- import * as path32 from "path";
10089
+ import * as path33 from "path";
9764
10090
  import { spawn as spawn3 } from "child_process";
9765
10091
 
9766
10092
  // src/commands/preview-artifact/switcher.ts
9767
10093
  import * as http from "http";
9768
10094
  import * as net2 from "net";
9769
- import * as fs35 from "fs";
9770
- import * as path31 from "path";
10095
+ import * as fs36 from "fs";
10096
+ import * as path32 from "path";
9771
10097
  function targetOrigin(target) {
9772
10098
  return `http://${target.host}:${target.port}`;
9773
10099
  }
@@ -9815,7 +10141,7 @@ function cachedAssetFile(request2, cachedAssetFiles) {
9815
10141
  return cachedAssetFiles.get(pathname);
9816
10142
  }
9817
10143
  function contentType(filePath) {
9818
- switch (path31.extname(filePath).toLowerCase()) {
10144
+ switch (path32.extname(filePath).toLowerCase()) {
9819
10145
  case ".js":
9820
10146
  case ".mjs":
9821
10147
  return "text/javascript; charset=utf-8";
@@ -9852,7 +10178,7 @@ function contentType(filePath) {
9852
10178
  function serveCachedAsset(request2, response, filePath) {
9853
10179
  let stat;
9854
10180
  try {
9855
- stat = fs35.lstatSync(filePath);
10181
+ stat = fs36.lstatSync(filePath);
9856
10182
  } catch {
9857
10183
  response.writeHead(404).end();
9858
10184
  return;
@@ -9870,7 +10196,7 @@ function serveCachedAsset(request2, response, filePath) {
9870
10196
  response.end();
9871
10197
  return;
9872
10198
  }
9873
- const stream = fs35.createReadStream(filePath);
10199
+ const stream = fs36.createReadStream(filePath);
9874
10200
  stream.on("error", () => response.destroy());
9875
10201
  stream.pipe(response);
9876
10202
  }
@@ -9917,8 +10243,8 @@ function createPreviewArtifactSwitcher(options) {
9917
10243
  let closed = false;
9918
10244
  let pollTimer;
9919
10245
  let settleCutover;
9920
- const cutover = new Promise((resolve9) => {
9921
- settleCutover = resolve9;
10246
+ const cutover = new Promise((resolve10) => {
10247
+ settleCutover = resolve10;
9922
10248
  });
9923
10249
  const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
9924
10250
  const server = http.createServer((request2, response) => {
@@ -9972,7 +10298,7 @@ function createPreviewArtifactSwitcher(options) {
9972
10298
  },
9973
10299
  cutover,
9974
10300
  start() {
9975
- return new Promise((resolve9, reject) => {
10301
+ return new Promise((resolve10, reject) => {
9976
10302
  server.once("error", reject);
9977
10303
  server.listen(options.listenPort, listenHost, () => {
9978
10304
  const address = server.address();
@@ -9981,7 +10307,7 @@ function createPreviewArtifactSwitcher(options) {
9981
10307
  return;
9982
10308
  }
9983
10309
  schedulePoll();
9984
- resolve9({
10310
+ resolve10({
9985
10311
  host: listenHost,
9986
10312
  port: address.port,
9987
10313
  origin: `http://${listenHost}:${address.port}`
@@ -10000,8 +10326,8 @@ function createPreviewArtifactSwitcher(options) {
10000
10326
  });
10001
10327
  settleCutover = void 0;
10002
10328
  }
10003
- return new Promise((resolve9) => {
10004
- server.close(() => resolve9());
10329
+ return new Promise((resolve10) => {
10330
+ server.close(() => resolve10());
10005
10331
  server.closeAllConnections?.();
10006
10332
  });
10007
10333
  }
@@ -10026,10 +10352,10 @@ async function measureAsync(timings, phase, operation, detail) {
10026
10352
  }
10027
10353
  }
10028
10354
  function sleep2(ms) {
10029
- return new Promise((resolve9) => setTimeout(resolve9, ms));
10355
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
10030
10356
  }
10031
10357
  async function reservePort2() {
10032
- return new Promise((resolve9, reject) => {
10358
+ return new Promise((resolve10, reject) => {
10033
10359
  const server = net3.createServer();
10034
10360
  server.unref();
10035
10361
  server.once("error", reject);
@@ -10041,7 +10367,7 @@ async function reservePort2() {
10041
10367
  }
10042
10368
  server.close((error) => {
10043
10369
  if (error) reject(error);
10044
- else resolve9(address.port);
10370
+ else resolve10(address.port);
10045
10371
  });
10046
10372
  });
10047
10373
  });
@@ -10059,12 +10385,12 @@ function stopProcessGroup2(child) {
10059
10385
  }
10060
10386
  }
10061
10387
  function isPathInside3(parent, candidate) {
10062
- const relative5 = path32.relative(parent, candidate);
10063
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative5);
10388
+ const relative5 = path33.relative(parent, candidate);
10389
+ return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path33.sep}`) && !path33.isAbsolute(relative5);
10064
10390
  }
10065
10391
  function ensureRegularRuntimeRoot(runtimeRoot) {
10066
- fs36.mkdirSync(runtimeRoot, { recursive: true });
10067
- const stat = fs36.lstatSync(runtimeRoot);
10392
+ fs37.mkdirSync(runtimeRoot, { recursive: true });
10393
+ const stat = fs37.lstatSync(runtimeRoot);
10068
10394
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
10069
10395
  throw new Error("runtime root must be a regular directory");
10070
10396
  }
@@ -10073,14 +10399,14 @@ function collectCachedAssetFiles(clientRoot, appBasePath) {
10073
10399
  const files = /* @__PURE__ */ new Map();
10074
10400
  const basePath = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
10075
10401
  const visit = (directory) => {
10076
- for (const entry of fs36.readdirSync(directory, { withFileTypes: true })) {
10077
- const absolute = path32.join(directory, entry.name);
10402
+ for (const entry of fs37.readdirSync(directory, { withFileTypes: true })) {
10403
+ const absolute = path33.join(directory, entry.name);
10078
10404
  if (entry.isDirectory()) {
10079
10405
  visit(absolute);
10080
10406
  continue;
10081
10407
  }
10082
10408
  if (!entry.isFile() || entry.isSymbolicLink()) continue;
10083
- const relative5 = path32.relative(clientRoot, absolute).split(path32.sep).join("/");
10409
+ const relative5 = path33.relative(clientRoot, absolute).split(path33.sep).join("/");
10084
10410
  if (relative5 === "index.html") continue;
10085
10411
  files.set(
10086
10412
  new URL(`/${relative5}`, "http://preview.local").pathname,
@@ -10096,13 +10422,13 @@ function collectCachedAssetFiles(clientRoot, appBasePath) {
10096
10422
  return files;
10097
10423
  }
10098
10424
  function writeMarkerAtomic(markerFile, marker) {
10099
- fs36.mkdirSync(path32.dirname(markerFile), { recursive: true });
10425
+ fs37.mkdirSync(path33.dirname(markerFile), { recursive: true });
10100
10426
  const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
10101
- fs36.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
10427
+ fs37.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
10102
10428
  `, {
10103
10429
  mode: 384
10104
10430
  });
10105
- fs36.renameSync(temporaryFile, markerFile);
10431
+ fs37.renameSync(temporaryFile, markerFile);
10106
10432
  }
10107
10433
  async function waitForCachedRuntime(origin, appBasePath, businessApiPath, timeoutMs, child) {
10108
10434
  const timeoutAt = Date.now() + timeoutMs;
@@ -10145,12 +10471,14 @@ async function startPreviewArtifactRuntime(options) {
10145
10471
  buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
10146
10472
  options.environment,
10147
10473
  options.appBasePath
10148
- )
10474
+ ),
10475
+ sourceTreeValidation: options.sourceTreeValidation,
10476
+ workspaceManifest: options.workspaceManifest
10149
10477
  })
10150
10478
  );
10151
10479
  if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
10152
- const runtimeRoot = path32.resolve(options.runtimeRoot);
10153
- const markerFile = path32.resolve(options.markerFile);
10480
+ const runtimeRoot = path33.resolve(options.runtimeRoot);
10481
+ const markerFile = path33.resolve(options.markerFile);
10154
10482
  let runtimeDir;
10155
10483
  let cachedRuntime;
10156
10484
  let switcher;
@@ -10160,58 +10488,22 @@ async function startPreviewArtifactRuntime(options) {
10160
10488
  closed = true;
10161
10489
  stopProcessGroup2(cachedRuntime);
10162
10490
  await switcher?.close();
10163
- fs36.rmSync(markerFile, { force: true });
10164
- if (runtimeDir) fs36.rmSync(runtimeDir, { recursive: true, force: true });
10491
+ fs37.rmSync(markerFile, { force: true });
10492
+ if (runtimeDir) fs37.rmSync(runtimeDir, { recursive: true, force: true });
10165
10493
  };
10166
10494
  try {
10167
10495
  ensureRegularRuntimeRoot(runtimeRoot);
10168
10496
  if (!isPathInside3(runtimeRoot, markerFile)) {
10169
10497
  throw new Error("runtime marker must be inside the runtime root");
10170
10498
  }
10171
- runtimeDir = fs36.mkdtempSync(
10172
- path32.join(runtimeRoot, `${resolved.generationId}-`)
10499
+ runtimeDir = fs37.mkdtempSync(
10500
+ path33.join(runtimeRoot, `${resolved.generationId}-`)
10173
10501
  );
10174
- const runtimeBundle = path32.join(runtimeDir, "server.bundle.cjs");
10175
- const serverSource = path32.join(resolved.generationDir, "server.bundle.cjs");
10176
- const clientArchive = path32.join(runtimeDir, "client.zip");
10177
- const clientSource = path32.join(resolved.generationDir, "client.zip");
10178
- const clientRoot = path32.join(runtimeDir, "dist", "client");
10179
- const backendReady = (async () => {
10180
- await measureAsync(
10181
- timings,
10182
- "server_materialize",
10183
- () => materializeVerifiedPreviewArtifactFile({
10184
- source: serverSource,
10185
- destination: runtimeBundle,
10186
- identity: resolved.manifest.files.server
10187
- }),
10188
- { bytes: resolved.manifest.files.server.bytes }
10189
- );
10190
- const cachedServerPort2 = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
10191
- cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
10192
- cwd: runtimeDir,
10193
- detached: true,
10194
- stdio: "ignore",
10195
- env: {
10196
- ...process.env,
10197
- ...previewArtifactRuntimeEnvironment(options.environment),
10198
- SERVER_PORT: String(cachedServerPort2)
10199
- }
10200
- });
10201
- const cachedOrigin = `http://127.0.0.1:${cachedServerPort2}`;
10202
- await measureAsync(
10203
- timings,
10204
- "backend_spawn_to_business_ready",
10205
- () => waitForCachedRuntime(
10206
- cachedOrigin,
10207
- options.appBasePath,
10208
- options.businessApiPath,
10209
- options.startupTimeoutMs ?? 3e4,
10210
- cachedRuntime
10211
- )
10212
- );
10213
- return cachedServerPort2;
10214
- })();
10502
+ const runtimeBundle = path33.join(runtimeDir, "server.bundle.cjs");
10503
+ const serverSource = path33.join(resolved.generationDir, "server.bundle.cjs");
10504
+ const clientArchive = path33.join(runtimeDir, "client.zip");
10505
+ const clientSource = path33.join(resolved.generationDir, "client.zip");
10506
+ const clientRoot = path33.join(runtimeDir, "dist", "client");
10215
10507
  const assetsReady = (async () => {
10216
10508
  await measureAsync(
10217
10509
  timings,
@@ -10241,9 +10533,48 @@ async function startPreviewArtifactRuntime(options) {
10241
10533
  timings[timings.length - 1].fileCount = new Set(
10242
10534
  cachedAssetFiles2.values()
10243
10535
  ).size;
10244
- fs36.rmSync(clientArchive, { force: true });
10536
+ fs37.rmSync(clientArchive, { force: true });
10245
10537
  return cachedAssetFiles2;
10246
10538
  })();
10539
+ const backendReady = (async () => {
10540
+ await measureAsync(
10541
+ timings,
10542
+ "server_materialize",
10543
+ () => materializeVerifiedPreviewArtifactFile({
10544
+ source: serverSource,
10545
+ destination: runtimeBundle,
10546
+ identity: resolved.manifest.files.server
10547
+ }),
10548
+ { bytes: resolved.manifest.files.server.bytes }
10549
+ );
10550
+ const cachedServerPort2 = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
10551
+ cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
10552
+ cwd: runtimeDir,
10553
+ detached: true,
10554
+ stdio: "ignore",
10555
+ env: {
10556
+ ...process.env,
10557
+ ...previewArtifactRuntimeEnvironment(options.environment),
10558
+ SERVER_PORT: String(cachedServerPort2)
10559
+ }
10560
+ });
10561
+ const cachedOrigin = `http://127.0.0.1:${cachedServerPort2}`;
10562
+ await measureAsync(
10563
+ timings,
10564
+ "backend_spawn_to_business_ready",
10565
+ async () => {
10566
+ await assetsReady;
10567
+ await waitForCachedRuntime(
10568
+ cachedOrigin,
10569
+ options.appBasePath,
10570
+ options.businessApiPath,
10571
+ options.startupTimeoutMs ?? 3e4,
10572
+ cachedRuntime
10573
+ );
10574
+ }
10575
+ );
10576
+ return cachedServerPort2;
10577
+ })();
10247
10578
  const [backendResult, assetsResult] = await Promise.allSettled([
10248
10579
  backendReady,
10249
10580
  assetsReady
@@ -10371,6 +10702,35 @@ var previewServerArtifactCommand = {
10371
10702
  );
10372
10703
  }
10373
10704
  };
10705
+ var previewArtifactWorkspaceManifestCommand = {
10706
+ name: "preview-artifact-workspace-manifest",
10707
+ description: "Snapshot the Git-visible Workspace content identity for backup and cache validation",
10708
+ register(program) {
10709
+ program.command(this.name).description(this.description).option("--project-root <dir>", "Application project root", process.cwd()).requiredOption(
10710
+ "--manifest-file <file>",
10711
+ "Workspace Manifest sidecar output file"
10712
+ ).requiredOption(
10713
+ "--workspace-backup-id <id>",
10714
+ "Physical Workspace backup identity"
10715
+ ).requiredOption("--app-id <id>", "Application ID").option(
10716
+ "--validate-source-tree",
10717
+ "Calculate a sourceTreeId and bind cache consumption to it"
10718
+ ).option(
10719
+ "--source-tree-include <path>",
10720
+ "Repository-relative file or directory included in sourceTreeId; repeatable",
10721
+ (value, previous) => [...previous, value],
10722
+ []
10723
+ ).action(
10724
+ (options) => {
10725
+ const manifest = createPreviewArtifactWorkspaceManifest({
10726
+ ...options,
10727
+ sourceTreeIncludePaths: options.sourceTreeInclude
10728
+ });
10729
+ console.log(JSON.stringify(manifest));
10730
+ }
10731
+ );
10732
+ }
10733
+ };
10374
10734
  var previewArtifactGenerationCommand = {
10375
10735
  name: "preview-artifact-generation",
10376
10736
  description: "Build, probe, promote, and atomically publish a Preview Artifact generation",
@@ -10381,12 +10741,18 @@ var previewArtifactGenerationCommand = {
10381
10741
  ).requiredOption(
10382
10742
  "--environment-file <file>",
10383
10743
  "JSON application environment passed only to build/probe children"
10744
+ ).requiredOption(
10745
+ "--workspace-manifest-file <file>",
10746
+ "Workspace Manifest sidecar created at backup start"
10384
10747
  ).action(
10385
10748
  async (options) => {
10386
10749
  const result = await producePreviewArtifactGeneration({
10387
10750
  ...options,
10388
10751
  environment: readPreviewArtifactEnvironmentFile(
10389
10752
  options.environmentFile
10753
+ ),
10754
+ workspaceManifest: readPreviewArtifactWorkspaceManifest(
10755
+ options.workspaceManifestFile
10390
10756
  )
10391
10757
  });
10392
10758
  console.log(JSON.stringify(result));
@@ -10443,6 +10809,17 @@ var previewArtifactRuntimeCommand = {
10443
10809
  ).requiredOption(
10444
10810
  "--environment-file <file>",
10445
10811
  "JSON application environment passed only to cached application child"
10812
+ ).requiredOption(
10813
+ "--workspace-manifest-file <file>",
10814
+ "Workspace Manifest sidecar restored with this backup"
10815
+ ).option(
10816
+ "--validate-source-tree",
10817
+ "Reject cache when its sourceTreeId does not match the Workspace backup"
10818
+ ).option(
10819
+ "--source-tree-include <path>",
10820
+ "Repository-relative sourceTreeId scope; repeatable",
10821
+ (value, previous) => [...previous, value],
10822
+ []
10446
10823
  ).action(
10447
10824
  async (options) => {
10448
10825
  const environmentLoadStartedAt = Date.now();
@@ -10450,9 +10827,18 @@ var previewArtifactRuntimeCommand = {
10450
10827
  options.environmentFile
10451
10828
  );
10452
10829
  const environmentLoadDurationMs = Date.now() - environmentLoadStartedAt;
10830
+ const workspaceManifest = readPreviewArtifactWorkspaceManifest(
10831
+ options.workspaceManifestFile
10832
+ );
10833
+ const sourceTreeValidation = createPreviewArtifactSourceTreeValidation(
10834
+ options.validateSourceTree,
10835
+ options.sourceTreeInclude
10836
+ );
10453
10837
  const runtime = await startPreviewArtifactRuntime({
10454
10838
  ...options,
10455
10839
  environment,
10840
+ workspaceManifest,
10841
+ sourceTreeValidation,
10456
10842
  initialTimings: [
10457
10843
  {
10458
10844
  phase: "cli_bootstrap",
@@ -10498,6 +10884,7 @@ var buildCommandGroup = {
10498
10884
  uploadStaticCommand,
10499
10885
  preUploadStaticCommand,
10500
10886
  previewServerArtifactCommand,
10887
+ previewArtifactWorkspaceManifestCommand,
10501
10888
  previewArtifactGenerationCommand,
10502
10889
  previewArtifactRuntimeCommand
10503
10890
  ]
@@ -10517,12 +10904,12 @@ var commands = [
10517
10904
  ];
10518
10905
 
10519
10906
  // src/index.ts
10520
- var envPath = path33.join(process.cwd(), ".env");
10521
- if (fs37.existsSync(envPath)) {
10907
+ var envPath = path34.join(process.cwd(), ".env");
10908
+ if (fs38.existsSync(envPath)) {
10522
10909
  dotenvConfig({ path: envPath });
10523
10910
  }
10524
- var __dirname = path33.dirname(fileURLToPath5(import.meta.url));
10525
- var pkg = JSON.parse(fs37.readFileSync(path33.join(__dirname, "../package.json"), "utf-8"));
10911
+ var __dirname = path34.dirname(fileURLToPath5(import.meta.url));
10912
+ var pkg = JSON.parse(fs38.readFileSync(path34.join(__dirname, "../package.json"), "utf-8"));
10526
10913
  var cli = new FullstackCLI(pkg.version);
10527
10914
  cli.useAll(commands);
10528
10915
  cli.run();