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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import fs30 from "fs";
3
- import path26 from "path";
2
+ import fs35 from "fs";
3
+ import path31 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((resolve3) => {
4924
+ return new Promise((resolve8) => {
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
- resolve3({
4950
+ resolve8({
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
- resolve3({
4961
+ resolve8({
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((resolve3) => {
4972
+ return new Promise((resolve8) => {
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
- resolve3();
4977
+ resolve8();
4978
4978
  });
4979
4979
  });
4980
4980
  }
@@ -8236,6 +8236,9 @@ function generateBootstrap(entryPath, plugins) {
8236
8236
  });
8237
8237
  lines.push(
8238
8238
  `globalThis[Symbol.for(${JSON.stringify(STATIC_PLUGIN_REGISTRY_SYMBOL)})] = __staticPluginRegistry;`,
8239
+ `if (process.env.PREVIEW_ARTIFACT_PROBE_REGISTRY_FILE) {`,
8240
+ ` require('node:fs').writeFileSync(process.env.PREVIEW_ARTIFACT_PROBE_REGISTRY_FILE, JSON.stringify(Array.from(__staticPluginRegistry.keys()).sort()));`,
8241
+ `}`,
8239
8242
  `require(${JSON.stringify(entryPath)});`,
8240
8243
  ""
8241
8244
  );
@@ -8619,21 +8622,1093 @@ async function buildPreviewServerArtifact(options) {
8619
8622
  }
8620
8623
  }
8621
8624
 
8625
+ // src/commands/preview-artifact/producer.ts
8626
+ import * as crypto2 from "crypto";
8627
+ import * as fs33 from "fs";
8628
+ import * as path29 from "path";
8629
+ import { spawnSync as spawnSync8 } from "child_process";
8630
+
8631
+ // src/commands/preview-artifact/generation.ts
8632
+ import * as crypto from "crypto";
8633
+ import * as fs30 from "fs";
8634
+ import * as path26 from "path";
8635
+ var PREVIEW_ARTIFACT_SCHEMA_VERSION = 1;
8636
+ var MANIFEST_FILE = "manifest.json";
8637
+ var PROBE_FILE = "probe.json";
8638
+ var SERVER_FILE = "server.bundle.cjs";
8639
+ var CLIENT_FILE = "client.zip";
8640
+ function parseNodeMajor(nodeVersion) {
8641
+ const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
8642
+ if (!match) {
8643
+ throw new Error(`invalid Node.js version: ${nodeVersion}`);
8644
+ }
8645
+ return Number(match[1]);
8646
+ }
8647
+ function sha256File(filePath) {
8648
+ const hash = crypto.createHash("sha256");
8649
+ hash.update(fs30.readFileSync(filePath));
8650
+ return hash.digest("hex");
8651
+ }
8652
+ function fileIdentity(generationDir, relativePath) {
8653
+ const filePath = path26.join(generationDir, relativePath);
8654
+ const stat = fs30.lstatSync(filePath);
8655
+ if (!stat.isFile() || stat.isSymbolicLink()) {
8656
+ throw new Error(`artifact file must be a regular file: ${relativePath}`);
8657
+ }
8658
+ return {
8659
+ path: relativePath,
8660
+ bytes: stat.size,
8661
+ sha256: sha256File(filePath)
8662
+ };
8663
+ }
8664
+ function readJson2(filePath) {
8665
+ return JSON.parse(fs30.readFileSync(filePath, "utf8"));
8666
+ }
8667
+ function writeJsonAtomic(filePath, value) {
8668
+ fs30.mkdirSync(path26.dirname(filePath), { recursive: true });
8669
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
8670
+ fs30.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
8671
+ `, {
8672
+ mode: 384
8673
+ });
8674
+ fs30.renameSync(temporaryPath, filePath);
8675
+ }
8676
+ function runtimeIdentity(nodeVersion, platform, arch) {
8677
+ return { nodeMajor: parseNodeMajor(nodeVersion), platform, arch };
8678
+ }
8679
+ function sameRuntime(left, right) {
8680
+ return left.nodeMajor === right.nodeMajor && left.platform === right.platform && left.arch === right.arch;
8681
+ }
8682
+ function createPreviewArtifactManifest(options) {
8683
+ const generationDir = path26.resolve(options.generationDir);
8684
+ const manifest = {
8685
+ schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
8686
+ generationId: options.generationId,
8687
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8688
+ runtime: runtimeIdentity(
8689
+ options.nodeVersion,
8690
+ options.platform,
8691
+ options.arch
8692
+ ),
8693
+ files: {
8694
+ server: fileIdentity(generationDir, SERVER_FILE),
8695
+ client: fileIdentity(generationDir, CLIENT_FILE)
8696
+ },
8697
+ actionPlugins: Array.from(new Set(options.actionPlugins ?? [])).sort(),
8698
+ runtimeProbeStatus: "not-run",
8699
+ consumable: false
8700
+ };
8701
+ writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), manifest);
8702
+ fs30.rmSync(path26.join(generationDir, PROBE_FILE), { force: true });
8703
+ return manifest;
8704
+ }
8705
+ function recordPreviewArtifactProbe(options) {
8706
+ const generationDir = path26.resolve(options.generationDir);
8707
+ const manifest = readJson2(
8708
+ path26.join(generationDir, MANIFEST_FILE)
8709
+ );
8710
+ const probe = {
8711
+ schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
8712
+ generationId: manifest.generationId,
8713
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
8714
+ success: options.success,
8715
+ reasons: options.reasons ?? [],
8716
+ runtime: runtimeIdentity(
8717
+ options.nodeVersion,
8718
+ options.platform,
8719
+ options.arch
8720
+ ),
8721
+ serverSha256: sha256File(path26.join(generationDir, SERVER_FILE)),
8722
+ clientSha256: sha256File(path26.join(generationDir, CLIENT_FILE)),
8723
+ coverage: {
8724
+ html: options.coverage.html,
8725
+ businessApi: options.coverage.businessApi,
8726
+ actionPlugins: Array.from(new Set(options.coverage.actionPlugins)).sort()
8727
+ }
8728
+ };
8729
+ writeJsonAtomic(path26.join(generationDir, PROBE_FILE), probe);
8730
+ return probe;
8731
+ }
8732
+ function promotePreviewArtifactGeneration(generationDirInput) {
8733
+ const generationDir = path26.resolve(generationDirInput);
8734
+ const reasons = [];
8735
+ let manifest;
8736
+ let probe;
8737
+ try {
8738
+ manifest = readJson2(
8739
+ path26.join(generationDir, MANIFEST_FILE)
8740
+ );
8741
+ probe = readJson2(
8742
+ path26.join(generationDir, PROBE_FILE)
8743
+ );
8744
+ } catch (error) {
8745
+ return {
8746
+ promoted: false,
8747
+ reasons: [
8748
+ `probe evidence missing or invalid: ${error instanceof Error ? error.message : String(error)}`
8749
+ ]
8750
+ };
8751
+ }
8752
+ if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
8753
+ reasons.push("schema version mismatch");
8754
+ }
8755
+ if (probe.generationId !== manifest.generationId) {
8756
+ reasons.push("probe generation identity mismatch");
8757
+ }
8758
+ if (!probe.success) {
8759
+ reasons.push(`probe failed: ${probe.reasons.join("; ") || "unknown"}`);
8760
+ }
8761
+ if (!sameRuntime(manifest.runtime, probe.runtime)) {
8762
+ reasons.push(
8763
+ "probe runtime identity does not match build runtime identity"
8764
+ );
8765
+ }
8766
+ const currentServerHash = sha256File(path26.join(generationDir, SERVER_FILE));
8767
+ const currentClientHash = sha256File(path26.join(generationDir, CLIENT_FILE));
8768
+ if (currentServerHash !== manifest.files.server.sha256 || currentServerHash !== probe.serverSha256) {
8769
+ reasons.push("server.bundle.cjs hash does not match probe identity");
8770
+ }
8771
+ if (currentClientHash !== manifest.files.client.sha256 || currentClientHash !== probe.clientSha256) {
8772
+ reasons.push("client.zip hash does not match probe identity");
8773
+ }
8774
+ if (!probe.coverage.html) {
8775
+ reasons.push("probe did not verify HTML");
8776
+ }
8777
+ if (!probe.coverage.businessApi) {
8778
+ reasons.push("probe did not verify business API");
8779
+ }
8780
+ const verifiedPlugins = new Set(probe.coverage.actionPlugins);
8781
+ const missingPlugins = manifest.actionPlugins.filter(
8782
+ (plugin) => !verifiedPlugins.has(plugin)
8783
+ );
8784
+ if (missingPlugins.length > 0) {
8785
+ reasons.push(
8786
+ `probe did not verify Action Plugin: ${missingPlugins.join(", ")}`
8787
+ );
8788
+ }
8789
+ if (reasons.length > 0) {
8790
+ return { promoted: false, reasons };
8791
+ }
8792
+ const promotedManifest = {
8793
+ ...manifest,
8794
+ runtimeProbeStatus: "passed",
8795
+ consumable: true
8796
+ };
8797
+ writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), promotedManifest);
8798
+ return { promoted: true, manifest: promotedManifest };
8799
+ }
8800
+
8801
+ // src/commands/preview-artifact/probe.ts
8802
+ import * as fs31 from "fs";
8803
+ import * as net from "net";
8804
+ import * as os4 from "os";
8805
+ import * as path27 from "path";
8806
+ import { spawn as spawn2, spawnSync as spawnSync7 } from "child_process";
8807
+ function sleep(ms) {
8808
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
8809
+ }
8810
+ async function reservePort() {
8811
+ return new Promise((resolve8, reject) => {
8812
+ const server = net.createServer();
8813
+ server.unref();
8814
+ server.once("error", reject);
8815
+ server.listen(0, "127.0.0.1", () => {
8816
+ const address = server.address();
8817
+ if (!address || typeof address === "string") {
8818
+ server.close(() => reject(new Error("unable to reserve probe port")));
8819
+ return;
8820
+ }
8821
+ server.close((error) => {
8822
+ if (error) reject(error);
8823
+ else resolve8(address.port);
8824
+ });
8825
+ });
8826
+ });
8827
+ }
8828
+ function archiveEntries(archivePath) {
8829
+ const listing = spawnSync7("unzip", ["-Z1", archivePath], {
8830
+ encoding: "utf8"
8831
+ });
8832
+ if (listing.status !== 0) {
8833
+ throw new Error(`client archive listing failed: ${listing.stderr.trim()}`);
8834
+ }
8835
+ const entries = listing.stdout.split("\n").filter(Boolean);
8836
+ for (const entry of entries) {
8837
+ const normalized = path27.posix.normalize(entry.replaceAll("\\", "/"));
8838
+ if (path27.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
8839
+ throw new Error(`client archive contains unsafe path: ${entry}`);
8840
+ }
8841
+ }
8842
+ return entries;
8843
+ }
8844
+ function rejectArchivedSymlinks(archivePath) {
8845
+ const listing = spawnSync7("unzip", ["-Z", "-l", archivePath], {
8846
+ encoding: "utf8"
8847
+ });
8848
+ if (listing.status !== 0) {
8849
+ throw new Error(
8850
+ `client archive metadata listing failed: ${listing.stderr.trim()}`
8851
+ );
8852
+ }
8853
+ for (const line of listing.stdout.split("\n")) {
8854
+ if (/^l[rwxstST-]{9}\s/.test(line.trimStart())) {
8855
+ throw new Error("client archive contains symlink");
8856
+ }
8857
+ }
8858
+ }
8859
+ function rejectSymlinks(root) {
8860
+ const pending = [root];
8861
+ while (pending.length > 0) {
8862
+ const current = pending.pop();
8863
+ if (!current) continue;
8864
+ for (const entry of fs31.readdirSync(current, { withFileTypes: true })) {
8865
+ const entryPath = path27.join(current, entry.name);
8866
+ const stat = fs31.lstatSync(entryPath);
8867
+ if (stat.isSymbolicLink()) {
8868
+ throw new Error(`client archive contains symlink: ${entry.name}`);
8869
+ }
8870
+ if (stat.isDirectory()) pending.push(entryPath);
8871
+ }
8872
+ }
8873
+ }
8874
+ function extractClientArchive(archivePath, destination) {
8875
+ if (fs31.existsSync(destination)) {
8876
+ throw new Error("client archive destination must not already exist");
8877
+ }
8878
+ archiveEntries(archivePath);
8879
+ rejectArchivedSymlinks(archivePath);
8880
+ fs31.mkdirSync(destination, { recursive: true });
8881
+ try {
8882
+ const extraction = spawnSync7(
8883
+ "unzip",
8884
+ ["-q", archivePath, "-d", destination],
8885
+ { encoding: "utf8" }
8886
+ );
8887
+ if (extraction.status !== 0) {
8888
+ throw new Error(
8889
+ `client archive extraction failed: ${extraction.stderr.trim()}`
8890
+ );
8891
+ }
8892
+ rejectSymlinks(destination);
8893
+ } catch (error) {
8894
+ fs31.rmSync(destination, { recursive: true, force: true });
8895
+ throw error;
8896
+ }
8897
+ }
8898
+ async function fetchUntil(url, timeoutAt) {
8899
+ while (Date.now() < timeoutAt) {
8900
+ try {
8901
+ return await fetch(url, {
8902
+ redirect: "manual",
8903
+ signal: AbortSignal.timeout(1e3)
8904
+ });
8905
+ } catch {
8906
+ await sleep(50);
8907
+ }
8908
+ }
8909
+ return null;
8910
+ }
8911
+ function stopProcessGroup(pid) {
8912
+ if (!pid) return;
8913
+ try {
8914
+ process.kill(-pid, "SIGTERM");
8915
+ } catch {
8916
+ try {
8917
+ process.kill(pid, "SIGTERM");
8918
+ } catch {
8919
+ return;
8920
+ }
8921
+ }
8922
+ }
8923
+ async function runPreviewArtifactProbe(options) {
8924
+ const generationDir = path27.resolve(options.generationDir);
8925
+ const runtimeRoot = fs31.mkdtempSync(
8926
+ path27.join(os4.tmpdir(), "preview-artifact-probe-")
8927
+ );
8928
+ const coverage = {
8929
+ html: false,
8930
+ businessApi: false,
8931
+ actionPlugins: []
8932
+ };
8933
+ const reasons = [];
8934
+ const registryFile = path27.join(runtimeRoot, "plugin-registry.json");
8935
+ let isolatedNodeModulesPresent = false;
8936
+ let child;
8937
+ try {
8938
+ const runtimeBundle = path27.join(runtimeRoot, "server.bundle.cjs");
8939
+ fs31.copyFileSync(
8940
+ path27.join(generationDir, "server.bundle.cjs"),
8941
+ runtimeBundle
8942
+ );
8943
+ extractClientArchive(
8944
+ path27.join(generationDir, "client.zip"),
8945
+ path27.join(runtimeRoot, "dist", "client")
8946
+ );
8947
+ isolatedNodeModulesPresent = fs31.existsSync(
8948
+ path27.join(runtimeRoot, "node_modules")
8949
+ );
8950
+ if (isolatedNodeModulesPresent) {
8951
+ reasons.push("isolated runtime unexpectedly contains node_modules");
8952
+ }
8953
+ const port = await reservePort();
8954
+ const timeoutAt = Date.now() + (options.timeoutMs ?? 12e4);
8955
+ child = spawn2(process.execPath, [runtimeBundle], {
8956
+ cwd: runtimeRoot,
8957
+ detached: true,
8958
+ stdio: "ignore",
8959
+ env: {
8960
+ ...process.env,
8961
+ ...options.environment,
8962
+ SERVER_PORT: String(port),
8963
+ PREVIEW_ARTIFACT_PROBE_REGISTRY_FILE: registryFile
8964
+ }
8965
+ });
8966
+ const origin = `http://127.0.0.1:${port}`;
8967
+ const htmlResponse = await fetchUntil(
8968
+ new URL(options.appBasePath, origin).toString(),
8969
+ timeoutAt
8970
+ );
8971
+ coverage.html = htmlResponse?.status === 200;
8972
+ if (!coverage.html) {
8973
+ reasons.push(
8974
+ `HTML probe failed with status ${htmlResponse?.status ?? "unreachable"}`
8975
+ );
8976
+ }
8977
+ const apiResponse = await fetchUntil(
8978
+ new URL(options.businessApiPath, origin).toString(),
8979
+ timeoutAt
8980
+ );
8981
+ coverage.businessApi = Boolean(
8982
+ apiResponse && apiResponse.status !== 404 && apiResponse.status < 500
8983
+ );
8984
+ if (!coverage.businessApi) {
8985
+ reasons.push(
8986
+ `business API returned ${apiResponse?.status ?? "unreachable"}`
8987
+ );
8988
+ }
8989
+ while (!fs31.existsSync(registryFile) && Date.now() < timeoutAt) {
8990
+ await sleep(25);
8991
+ }
8992
+ if (fs31.existsSync(registryFile)) {
8993
+ const parsed = JSON.parse(fs31.readFileSync(registryFile, "utf8"));
8994
+ if (Array.isArray(parsed)) {
8995
+ coverage.actionPlugins = parsed.filter((value) => typeof value === "string").sort();
8996
+ }
8997
+ }
8998
+ } catch (error) {
8999
+ reasons.push(error instanceof Error ? error.message : String(error));
9000
+ } finally {
9001
+ stopProcessGroup(child?.pid);
9002
+ }
9003
+ const probe = recordPreviewArtifactProbe({
9004
+ generationDir,
9005
+ success: reasons.length === 0,
9006
+ reasons,
9007
+ nodeVersion: process.version,
9008
+ platform: process.platform,
9009
+ arch: process.arch,
9010
+ coverage
9011
+ });
9012
+ fs31.rmSync(runtimeRoot, { recursive: true, force: true });
9013
+ return { ...probe, isolatedNodeModulesPresent };
9014
+ }
9015
+
9016
+ // src/commands/preview-artifact/store.ts
9017
+ import * as fs32 from "fs";
9018
+ import * as path28 from "path";
9019
+ var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9020
+ function readCurrentPointer(storeRoot) {
9021
+ try {
9022
+ const pointer = readJson3(
9023
+ path28.join(storeRoot, "current.json")
9024
+ );
9025
+ return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) ? pointer : void 0;
9026
+ } catch {
9027
+ return void 0;
9028
+ }
9029
+ }
9030
+ function readJson3(filePath) {
9031
+ return JSON.parse(fs32.readFileSync(filePath, "utf8"));
9032
+ }
9033
+ function writeJsonAtomic2(filePath, value) {
9034
+ fs32.mkdirSync(path28.dirname(filePath), { recursive: true });
9035
+ const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9036
+ fs32.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9037
+ `, {
9038
+ mode: 384
9039
+ });
9040
+ fs32.renameSync(temporaryPath, filePath);
9041
+ }
9042
+ function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
9043
+ const retained = new Set(retainedGenerationIds);
9044
+ for (const entry of fs32.readdirSync(generationsRoot, {
9045
+ withFileTypes: true
9046
+ })) {
9047
+ if (retained.has(entry.name)) continue;
9048
+ try {
9049
+ fs32.rmSync(path28.join(generationsRoot, entry.name), {
9050
+ recursive: true,
9051
+ force: true
9052
+ });
9053
+ } catch {
9054
+ }
9055
+ }
9056
+ }
9057
+ function parseNodeMajor2(nodeVersion) {
9058
+ const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
9059
+ return match ? Number(match[1]) : null;
9060
+ }
9061
+ function isPathInside(parent, candidate) {
9062
+ const relative4 = path28.relative(parent, candidate);
9063
+ return relative4 !== "" && !relative4.startsWith(`..${path28.sep}`) && relative4 !== ".." && !path28.isAbsolute(relative4);
9064
+ }
9065
+ function validateRegularDirectory(directoryPath, label) {
9066
+ try {
9067
+ const stat = fs32.lstatSync(directoryPath);
9068
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
9069
+ return [`${label} must be a regular directory`];
9070
+ }
9071
+ return [];
9072
+ } catch (error) {
9073
+ return [
9074
+ `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}`
9075
+ ];
9076
+ }
9077
+ }
9078
+ function validateGeneration(generationDir, runtime) {
9079
+ const reasons = validateRegularDirectory(
9080
+ generationDir,
9081
+ "generation directory"
9082
+ );
9083
+ if (reasons.length > 0) return { reasons };
9084
+ let manifest;
9085
+ let probe;
9086
+ try {
9087
+ manifest = readJson3(
9088
+ path28.join(generationDir, "manifest.json")
9089
+ );
9090
+ probe = readJson3(
9091
+ path28.join(generationDir, "probe.json")
9092
+ );
9093
+ } catch (error) {
9094
+ return {
9095
+ reasons: [
9096
+ `generation metadata is invalid: ${error instanceof Error ? error.message : String(error)}`
9097
+ ]
9098
+ };
9099
+ }
9100
+ if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9101
+ reasons.push("generation schema version is incompatible");
9102
+ }
9103
+ if (!GENERATION_ID_PATTERN.test(manifest.generationId) || probe.generationId !== manifest.generationId) {
9104
+ reasons.push("generation ID is invalid or inconsistent");
9105
+ }
9106
+ if (!manifest.consumable || manifest.runtimeProbeStatus !== "passed") {
9107
+ reasons.push("generation is not consumable");
9108
+ }
9109
+ if (!probe.success) {
9110
+ reasons.push("generation runtime probe did not pass");
9111
+ }
9112
+ const serverPath = path28.join(generationDir, "server.bundle.cjs");
9113
+ const clientPath = path28.join(generationDir, "client.zip");
9114
+ try {
9115
+ if (sha256File(serverPath) !== manifest.files.server.sha256) {
9116
+ reasons.push("server.bundle.cjs hash does not match manifest");
9117
+ }
9118
+ if (sha256File(clientPath) !== manifest.files.client.sha256) {
9119
+ reasons.push("client.zip hash does not match manifest");
9120
+ }
9121
+ } catch (error) {
9122
+ reasons.push(
9123
+ `generation files are unavailable: ${error instanceof Error ? error.message : String(error)}`
9124
+ );
9125
+ }
9126
+ if (probe.serverSha256 !== manifest.files.server.sha256 || probe.clientSha256 !== manifest.files.client.sha256) {
9127
+ reasons.push("probe file identity does not match manifest");
9128
+ }
9129
+ if (runtime && (runtime.nodeMajor !== manifest.runtime.nodeMajor || runtime.platform !== manifest.runtime.platform || runtime.arch !== manifest.runtime.arch)) {
9130
+ reasons.push("runtime identity is incompatible with generation");
9131
+ }
9132
+ return { manifest, reasons };
9133
+ }
9134
+ function publishPreviewArtifactGeneration(options) {
9135
+ const storeRoot = path28.resolve(options.storeRoot);
9136
+ const previousPointer = readCurrentPointer(storeRoot);
9137
+ const stagingRoot = path28.join(storeRoot, ".staging");
9138
+ const stagingDir = path28.resolve(options.stagingDir);
9139
+ if (!isPathInside(stagingRoot, stagingDir)) {
9140
+ return {
9141
+ published: false,
9142
+ reasons: [
9143
+ "staging directory must be inside the store .staging directory"
9144
+ ]
9145
+ };
9146
+ }
9147
+ const validation = validateGeneration(stagingDir);
9148
+ if (!validation.manifest || validation.reasons.length > 0) {
9149
+ return { published: false, reasons: validation.reasons };
9150
+ }
9151
+ 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)) {
9156
+ return {
9157
+ published: false,
9158
+ reasons: [`generation already exists: ${generationId}`]
9159
+ };
9160
+ }
9161
+ try {
9162
+ fs32.renameSync(stagingDir, generationDir);
9163
+ const pointer = {
9164
+ schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9165
+ generationId,
9166
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
9167
+ };
9168
+ writeJsonAtomic2(path28.join(storeRoot, "current.json"), pointer);
9169
+ pruneSupersededGenerations(
9170
+ generationsRoot,
9171
+ [generationId, previousPointer?.generationId].filter(
9172
+ (value) => Boolean(value)
9173
+ )
9174
+ );
9175
+ return { published: true, generationId, generationDir };
9176
+ } catch (error) {
9177
+ return {
9178
+ published: false,
9179
+ reasons: [
9180
+ `generation publication failed: ${error instanceof Error ? error.message : String(error)}`
9181
+ ]
9182
+ };
9183
+ }
9184
+ }
9185
+ function resolveCurrentPreviewArtifact(options) {
9186
+ const storeRoot = path28.resolve(options.storeRoot);
9187
+ let pointer;
9188
+ try {
9189
+ pointer = readJson3(path28.join(storeRoot, "current.json"));
9190
+ } catch (error) {
9191
+ return {
9192
+ resolved: false,
9193
+ reasons: [
9194
+ `current pointer is unavailable: ${error instanceof Error ? error.message : String(error)}`
9195
+ ]
9196
+ };
9197
+ }
9198
+ if (pointer.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || !GENERATION_ID_PATTERN.test(pointer.generationId)) {
9199
+ return {
9200
+ resolved: false,
9201
+ reasons: ["current generation ID or schema version is invalid"]
9202
+ };
9203
+ }
9204
+ const nodeMajor = parseNodeMajor2(options.nodeVersion);
9205
+ if (nodeMajor == null) {
9206
+ return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
9207
+ }
9208
+ const generationDir = path28.join(
9209
+ storeRoot,
9210
+ "generations",
9211
+ pointer.generationId
9212
+ );
9213
+ const validation = validateGeneration(generationDir, {
9214
+ nodeMajor,
9215
+ platform: options.platform,
9216
+ arch: options.arch
9217
+ });
9218
+ if (!validation.manifest || validation.reasons.length > 0) {
9219
+ return { resolved: false, reasons: validation.reasons };
9220
+ }
9221
+ if (validation.manifest.generationId !== pointer.generationId) {
9222
+ return {
9223
+ resolved: false,
9224
+ reasons: ["current pointer does not match generation manifest"]
9225
+ };
9226
+ }
9227
+ return {
9228
+ resolved: true,
9229
+ generationId: pointer.generationId,
9230
+ generationDir,
9231
+ manifest: validation.manifest
9232
+ };
9233
+ }
9234
+
9235
+ // src/commands/preview-artifact/producer.ts
9236
+ function defaultGenerationId() {
9237
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
9238
+ return `${timestamp}-${crypto2.randomBytes(5).toString("hex")}`;
9239
+ }
9240
+ function runBuildCommand(projectRoot, buildCommand, environment) {
9241
+ const result = spawnSync8(buildCommand.command, buildCommand.args, {
9242
+ cwd: projectRoot,
9243
+ env: { ...process.env, ...environment },
9244
+ encoding: "utf8",
9245
+ maxBuffer: 10 * 1024 * 1024
9246
+ });
9247
+ if (result.status === 0) return [];
9248
+ return [`production build failed with exit ${result.status ?? "unknown"}`];
9249
+ }
9250
+ function createClientArchive(clientDir, archivePath) {
9251
+ if (!fs33.existsSync(path29.join(clientDir, "index.html"))) {
9252
+ return [`client build output is missing: ${clientDir}`];
9253
+ }
9254
+ const result = spawnSync8("zip", ["-qry", archivePath, "."], {
9255
+ cwd: clientDir,
9256
+ encoding: "utf8"
9257
+ });
9258
+ if (result.status === 0 && fs33.existsSync(archivePath)) return [];
9259
+ return [`client archive failed with exit ${result.status ?? "unknown"}`];
9260
+ }
9261
+ function readActionPlugins2(metadataFile) {
9262
+ const metadata = JSON.parse(fs33.readFileSync(metadataFile, "utf8"));
9263
+ if (!Array.isArray(metadata.actionPlugins)) return [];
9264
+ return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
9265
+ }
9266
+ async function producePreviewArtifactGeneration(options) {
9267
+ const startedAt = Date.now();
9268
+ const projectRoot = path29.resolve(options.projectRoot);
9269
+ const storeRoot = path29.resolve(options.storeRoot);
9270
+ const generationId = options.generationId ?? defaultGenerationId();
9271
+ const stagingDir = path29.join(
9272
+ storeRoot,
9273
+ ".staging",
9274
+ `${generationId}.${process.pid}.${Date.now()}`
9275
+ );
9276
+ const failed = (reasons, probe) => ({
9277
+ published: false,
9278
+ generationId,
9279
+ elapsedMs: Date.now() - startedAt,
9280
+ reasons,
9281
+ ...probe ? { probe } : {}
9282
+ });
9283
+ fs33.mkdirSync(stagingDir, { recursive: true });
9284
+ try {
9285
+ const buildReasons = runBuildCommand(
9286
+ projectRoot,
9287
+ options.buildCommand ?? { command: "npm", args: ["run", "build:prod"] },
9288
+ options.environment
9289
+ );
9290
+ 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");
9293
+ const serverResult = await buildPreviewServerArtifact({
9294
+ projectRoot,
9295
+ entry: "dist/main.js",
9296
+ outfile: serverOutfile,
9297
+ metadataFile: serverMetadataFile
9298
+ });
9299
+ if (!serverResult.built) return failed(serverResult.reasons);
9300
+ const archiveReasons = createClientArchive(
9301
+ path29.join(projectRoot, "dist", "client"),
9302
+ path29.join(stagingDir, "client.zip")
9303
+ );
9304
+ if (archiveReasons.length > 0) return failed(archiveReasons);
9305
+ const actionPlugins = readActionPlugins2(serverMetadataFile);
9306
+ fs33.rmSync(serverMetadataFile, { force: true });
9307
+ createPreviewArtifactManifest({
9308
+ generationDir: stagingDir,
9309
+ generationId,
9310
+ nodeVersion: process.version,
9311
+ platform: process.platform,
9312
+ arch: process.arch,
9313
+ actionPlugins
9314
+ });
9315
+ const probe = await runPreviewArtifactProbe({
9316
+ generationDir: stagingDir,
9317
+ appBasePath: options.appBasePath,
9318
+ businessApiPath: options.businessApiPath,
9319
+ environment: options.environment
9320
+ });
9321
+ if (!probe.success) return failed(probe.reasons, probe);
9322
+ const promotion = promotePreviewArtifactGeneration(stagingDir);
9323
+ if (!promotion.promoted) return failed(promotion.reasons, probe);
9324
+ const publication = publishPreviewArtifactGeneration({
9325
+ storeRoot,
9326
+ stagingDir
9327
+ });
9328
+ if (!publication.published) return failed(publication.reasons, probe);
9329
+ return {
9330
+ published: true,
9331
+ generationId,
9332
+ generationDir: publication.generationDir,
9333
+ elapsedMs: Date.now() - startedAt,
9334
+ probe
9335
+ };
9336
+ } catch (error) {
9337
+ return failed([error instanceof Error ? error.message : String(error)]);
9338
+ } finally {
9339
+ fs33.rmSync(stagingDir, { recursive: true, force: true });
9340
+ }
9341
+ }
9342
+
9343
+ // src/commands/preview-artifact/runtime.ts
9344
+ import * as fs34 from "fs";
9345
+ import * as net3 from "net";
9346
+ import * as path30 from "path";
9347
+ import { spawn as spawn3 } from "child_process";
9348
+
9349
+ // src/commands/preview-artifact/switcher.ts
9350
+ import * as http from "http";
9351
+ import * as net2 from "net";
9352
+ function targetOrigin(target) {
9353
+ return `http://${target.host}:${target.port}`;
9354
+ }
9355
+ function proxyHttpRequest(target, request2, response) {
9356
+ const upstream = http.request(
9357
+ {
9358
+ host: target.host,
9359
+ port: target.port,
9360
+ method: request2.method,
9361
+ path: request2.url,
9362
+ headers: request2.headers
9363
+ },
9364
+ (upstreamResponse) => {
9365
+ response.writeHead(
9366
+ upstreamResponse.statusCode ?? 502,
9367
+ upstreamResponse.headers
9368
+ );
9369
+ upstreamResponse.pipe(response);
9370
+ }
9371
+ );
9372
+ upstream.on("error", (error) => {
9373
+ if (!response.headersSent) {
9374
+ response.writeHead(502, { "content-type": "application/json" });
9375
+ }
9376
+ response.end(
9377
+ JSON.stringify({
9378
+ ready: false,
9379
+ error: error.code ?? "upstream"
9380
+ })
9381
+ );
9382
+ });
9383
+ request2.pipe(upstream);
9384
+ }
9385
+ function proxyUpgrade(target, request2, socket, head) {
9386
+ const upstream = net2.connect(target.port, target.host, () => {
9387
+ const headers = Object.entries(request2.headers).flatMap(([name, value]) => {
9388
+ if (Array.isArray(value)) return value.map((item) => `${name}: ${item}`);
9389
+ return value == null ? [] : [`${name}: ${value}`];
9390
+ }).join("\r\n");
9391
+ upstream.write(
9392
+ `${request2.method ?? "GET"} ${request2.url ?? "/"} HTTP/${request2.httpVersion}\r
9393
+ ${headers}\r
9394
+ \r
9395
+ `
9396
+ );
9397
+ if (head.length > 0) upstream.write(head);
9398
+ socket.pipe(upstream).pipe(socket);
9399
+ });
9400
+ upstream.on("error", () => socket.destroy());
9401
+ socket.on("error", () => upstream.destroy());
9402
+ }
9403
+ async function freshTargetReady(target, healthPath) {
9404
+ try {
9405
+ const response = await fetch(new URL(healthPath, targetOrigin(target)), {
9406
+ signal: AbortSignal.timeout(1e3)
9407
+ });
9408
+ if (response.status !== 200) return false;
9409
+ const body = await response.json();
9410
+ return body.ready === true;
9411
+ } catch {
9412
+ return false;
9413
+ }
9414
+ }
9415
+ function createPreviewArtifactSwitcher(options) {
9416
+ const listenHost = options.listenHost ?? "127.0.0.1";
9417
+ const healthPath = options.freshHealthPath ?? "/dev/health";
9418
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
9419
+ let active = "cached";
9420
+ let closed = false;
9421
+ let pollTimer;
9422
+ let settleCutover;
9423
+ const cutover = new Promise((resolve8) => {
9424
+ settleCutover = resolve8;
9425
+ });
9426
+ const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
9427
+ const server = http.createServer((request2, response) => {
9428
+ if (request2.url?.split("?")[0] === "/dev/health") {
9429
+ response.writeHead(200, { "content-type": "application/json" });
9430
+ response.end(
9431
+ JSON.stringify({
9432
+ ready: true,
9433
+ active,
9434
+ generationId: options.generationId
9435
+ })
9436
+ );
9437
+ return;
9438
+ }
9439
+ proxyHttpRequest(currentTarget(), request2, response);
9440
+ });
9441
+ server.on("upgrade", (request2, socket, head) => {
9442
+ proxyUpgrade(currentTarget(), request2, socket, head);
9443
+ });
9444
+ const schedulePoll = () => {
9445
+ if (closed || active === "fresh") return;
9446
+ pollTimer = setTimeout(async () => {
9447
+ if (await freshTargetReady(options.freshTarget, healthPath)) {
9448
+ active = "fresh";
9449
+ settleCutover?.({
9450
+ switched: true,
9451
+ active,
9452
+ at: (/* @__PURE__ */ new Date()).toISOString()
9453
+ });
9454
+ settleCutover = void 0;
9455
+ return;
9456
+ }
9457
+ schedulePoll();
9458
+ }, pollIntervalMs);
9459
+ pollTimer.unref();
9460
+ };
9461
+ return {
9462
+ get active() {
9463
+ return active;
9464
+ },
9465
+ cutover,
9466
+ start() {
9467
+ return new Promise((resolve8, reject) => {
9468
+ server.once("error", reject);
9469
+ server.listen(options.listenPort, listenHost, () => {
9470
+ const address = server.address();
9471
+ if (!address || typeof address === "string") {
9472
+ reject(new Error("switcher did not expose a TCP address"));
9473
+ return;
9474
+ }
9475
+ schedulePoll();
9476
+ resolve8({
9477
+ host: listenHost,
9478
+ port: address.port,
9479
+ origin: `http://${listenHost}:${address.port}`
9480
+ });
9481
+ });
9482
+ });
9483
+ },
9484
+ close() {
9485
+ closed = true;
9486
+ if (pollTimer) clearTimeout(pollTimer);
9487
+ if (settleCutover) {
9488
+ settleCutover({
9489
+ switched: false,
9490
+ active,
9491
+ at: (/* @__PURE__ */ new Date()).toISOString()
9492
+ });
9493
+ settleCutover = void 0;
9494
+ }
9495
+ return new Promise((resolve8) => {
9496
+ server.close(() => resolve8());
9497
+ server.closeAllConnections?.();
9498
+ });
9499
+ }
9500
+ };
9501
+ }
9502
+
9503
+ // src/commands/preview-artifact/runtime.ts
9504
+ function sleep2(ms) {
9505
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
9506
+ }
9507
+ async function reservePort2() {
9508
+ return new Promise((resolve8, reject) => {
9509
+ const server = net3.createServer();
9510
+ server.unref();
9511
+ server.once("error", reject);
9512
+ server.listen(0, "127.0.0.1", () => {
9513
+ const address = server.address();
9514
+ if (!address || typeof address === "string") {
9515
+ server.close(() => reject(new Error("unable to reserve runtime port")));
9516
+ return;
9517
+ }
9518
+ server.close((error) => {
9519
+ if (error) reject(error);
9520
+ else resolve8(address.port);
9521
+ });
9522
+ });
9523
+ });
9524
+ }
9525
+ function stopProcessGroup2(child) {
9526
+ if (!child?.pid) return;
9527
+ try {
9528
+ process.kill(-child.pid, "SIGTERM");
9529
+ } catch {
9530
+ try {
9531
+ child.kill("SIGTERM");
9532
+ } catch {
9533
+ return;
9534
+ }
9535
+ }
9536
+ }
9537
+ function isPathInside2(parent, candidate) {
9538
+ const relative4 = path30.relative(parent, candidate);
9539
+ return relative4 !== "" && relative4 !== ".." && !relative4.startsWith(`..${path30.sep}`) && !path30.isAbsolute(relative4);
9540
+ }
9541
+ function ensureRegularRuntimeRoot(runtimeRoot) {
9542
+ fs34.mkdirSync(runtimeRoot, { recursive: true });
9543
+ const stat = fs34.lstatSync(runtimeRoot);
9544
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
9545
+ throw new Error("runtime root must be a regular directory");
9546
+ }
9547
+ }
9548
+ function writeMarkerAtomic(markerFile, marker) {
9549
+ fs34.mkdirSync(path30.dirname(markerFile), { recursive: true });
9550
+ const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
9551
+ fs34.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9552
+ `, {
9553
+ mode: 384
9554
+ });
9555
+ fs34.renameSync(temporaryFile, markerFile);
9556
+ }
9557
+ async function waitForCachedRuntime(origin, appBasePath, timeoutMs, child) {
9558
+ const timeoutAt = Date.now() + timeoutMs;
9559
+ while (Date.now() < timeoutAt) {
9560
+ if (child.exitCode != null || child.signalCode != null) {
9561
+ throw new Error(
9562
+ `cached runtime exited before readiness: ${child.exitCode ?? child.signalCode}`
9563
+ );
9564
+ }
9565
+ try {
9566
+ const response = await fetch(new URL(appBasePath, origin), {
9567
+ redirect: "manual",
9568
+ signal: AbortSignal.timeout(1e3)
9569
+ });
9570
+ if (response.status === 200) return;
9571
+ } catch {
9572
+ }
9573
+ await sleep2(50);
9574
+ }
9575
+ throw new Error("cached runtime readiness timed out");
9576
+ }
9577
+ async function startPreviewArtifactRuntime(options) {
9578
+ const resolved = resolveCurrentPreviewArtifact({
9579
+ storeRoot: options.storeRoot,
9580
+ nodeVersion: process.version,
9581
+ platform: process.platform,
9582
+ arch: process.arch
9583
+ });
9584
+ if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
9585
+ const runtimeRoot = path30.resolve(options.runtimeRoot);
9586
+ const markerFile = path30.resolve(options.markerFile);
9587
+ let runtimeDir;
9588
+ let cachedRuntime;
9589
+ let switcher;
9590
+ let closed = false;
9591
+ const cleanup = async () => {
9592
+ if (closed) return;
9593
+ closed = true;
9594
+ stopProcessGroup2(cachedRuntime);
9595
+ await switcher?.close();
9596
+ fs34.rmSync(markerFile, { force: true });
9597
+ if (runtimeDir) fs34.rmSync(runtimeDir, { recursive: true, force: true });
9598
+ };
9599
+ try {
9600
+ ensureRegularRuntimeRoot(runtimeRoot);
9601
+ if (!isPathInside2(runtimeRoot, markerFile)) {
9602
+ throw new Error("runtime marker must be inside the runtime root");
9603
+ }
9604
+ runtimeDir = fs34.mkdtempSync(
9605
+ path30.join(runtimeRoot, `${resolved.generationId}-`)
9606
+ );
9607
+ const runtimeBundle = path30.join(runtimeDir, "server.bundle.cjs");
9608
+ fs34.copyFileSync(
9609
+ path30.join(resolved.generationDir, "server.bundle.cjs"),
9610
+ runtimeBundle
9611
+ );
9612
+ extractClientArchive(
9613
+ path30.join(resolved.generationDir, "client.zip"),
9614
+ path30.join(runtimeDir, "dist", "client")
9615
+ );
9616
+ const cachedServerPort = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
9617
+ cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
9618
+ cwd: runtimeDir,
9619
+ detached: true,
9620
+ stdio: "ignore",
9621
+ env: {
9622
+ ...process.env,
9623
+ ...options.environment,
9624
+ SERVER_PORT: String(cachedServerPort)
9625
+ }
9626
+ });
9627
+ const cachedOrigin = `http://127.0.0.1:${cachedServerPort}`;
9628
+ await waitForCachedRuntime(
9629
+ cachedOrigin,
9630
+ options.appBasePath,
9631
+ options.startupTimeoutMs ?? 3e4,
9632
+ cachedRuntime
9633
+ );
9634
+ switcher = createPreviewArtifactSwitcher({
9635
+ listenHost: options.listenHost,
9636
+ listenPort: options.listenPort,
9637
+ generationId: resolved.generationId,
9638
+ cachedTarget: { host: "127.0.0.1", port: cachedServerPort },
9639
+ freshTarget: { host: "127.0.0.1", port: options.freshClientPort },
9640
+ pollIntervalMs: options.pollIntervalMs
9641
+ });
9642
+ const address = await switcher.start();
9643
+ const runtimeMarker = {
9644
+ schemaVersion: 1,
9645
+ status: "cached-serving",
9646
+ generationId: resolved.generationId,
9647
+ publicPort: address.port,
9648
+ cachedServerPort,
9649
+ freshServerPort: options.freshServerPort,
9650
+ freshClientPort: options.freshClientPort,
9651
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
9652
+ };
9653
+ writeMarkerAtomic(markerFile, runtimeMarker);
9654
+ const cutover = switcher.cutover.then(async (result) => {
9655
+ if (result.switched) {
9656
+ runtimeMarker.status = "fresh-serving";
9657
+ runtimeMarker.cutoverAt = result.at;
9658
+ writeMarkerAtomic(markerFile, runtimeMarker);
9659
+ await sleep2(options.cachedDrainMs ?? 5e3);
9660
+ stopProcessGroup2(cachedRuntime);
9661
+ cachedRuntime = void 0;
9662
+ }
9663
+ return result;
9664
+ });
9665
+ return {
9666
+ started: true,
9667
+ generationId: resolved.generationId,
9668
+ origin: address.origin,
9669
+ port: address.port,
9670
+ runtimeDir,
9671
+ cutover,
9672
+ close: cleanup
9673
+ };
9674
+ } catch (error) {
9675
+ await cleanup();
9676
+ return {
9677
+ started: false,
9678
+ reasons: [error instanceof Error ? error.message : String(error)]
9679
+ };
9680
+ }
9681
+ }
9682
+
8622
9683
  // src/commands/build/index.ts
8623
9684
  var getTokenCommand = {
8624
9685
  name: "get-token",
8625
9686
  description: "Get artifact upload credential (STI token)",
8626
9687
  register(program) {
8627
- program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").requiredOption("--scene <scene>", "Build scene (pipeline, static)").option("--commit-id <id>", "Git commit ID (required for pipeline scene)").action(async (options) => {
8628
- await getToken(options);
8629
- });
9688
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").requiredOption("--scene <scene>", "Build scene (pipeline, static)").option("--commit-id <id>", "Git commit ID (required for pipeline scene)").action(
9689
+ async (options) => {
9690
+ await getToken(options);
9691
+ }
9692
+ );
8630
9693
  }
8631
9694
  };
8632
9695
  var uploadStaticCommand = {
8633
9696
  name: "upload-static",
8634
9697
  description: "Upload shared/static files to TOS",
8635
9698
  register(program) {
8636
- program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option("--static-dir <dir>", "Static files directory", UPLOAD_STATIC_DEFAULTS.staticDir).option("--tosutil-path <path>", "Path to tosutil binary", UPLOAD_STATIC_DEFAULTS.tosutilPath).option("--endpoint <endpoint>", "TOS endpoint", UPLOAD_STATIC_DEFAULTS.endpoint).option("--region <region>", "TOS region", UPLOAD_STATIC_DEFAULTS.region).action(async (options) => {
9699
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option(
9700
+ "--static-dir <dir>",
9701
+ "Static files directory",
9702
+ UPLOAD_STATIC_DEFAULTS.staticDir
9703
+ ).option(
9704
+ "--tosutil-path <path>",
9705
+ "Path to tosutil binary",
9706
+ UPLOAD_STATIC_DEFAULTS.tosutilPath
9707
+ ).option(
9708
+ "--endpoint <endpoint>",
9709
+ "TOS endpoint",
9710
+ UPLOAD_STATIC_DEFAULTS.endpoint
9711
+ ).option("--region <region>", "TOS region", UPLOAD_STATIC_DEFAULTS.region).action(async (options) => {
8637
9712
  await uploadStatic(options);
8638
9713
  });
8639
9714
  }
@@ -8666,6 +9741,93 @@ var previewServerArtifactCommand = {
8666
9741
  );
8667
9742
  }
8668
9743
  };
9744
+ var previewArtifactGenerationCommand = {
9745
+ name: "preview-artifact-generation",
9746
+ description: "Build, probe, promote, and atomically publish a Preview Artifact generation",
9747
+ register(program) {
9748
+ 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
+ "--business-api-path <path>",
9750
+ "Business API probe path that must not return 404/5xx"
9751
+ ).action(
9752
+ async (options) => {
9753
+ const result = await producePreviewArtifactGeneration(options);
9754
+ console.log(JSON.stringify(result));
9755
+ if (!result.published) process.exitCode = 2;
9756
+ }
9757
+ );
9758
+ }
9759
+ };
9760
+ function parseTcpPort(value) {
9761
+ const port = Number(value);
9762
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
9763
+ throw new Error(`invalid TCP port: ${value}`);
9764
+ }
9765
+ return port;
9766
+ }
9767
+ var previewArtifactRuntimeCommand = {
9768
+ name: "preview-artifact-runtime",
9769
+ description: "Serve a verified Preview Artifact and switch to the fresh dev service",
9770
+ register(program) {
9771
+ program.command(this.name).description(this.description).requiredOption("--store-root <dir>", "Immutable generation store root").option(
9772
+ "--runtime-root <dir>",
9773
+ "Ephemeral runtime root",
9774
+ "/tmp/preview-artifact"
9775
+ ).option(
9776
+ "--marker-file <file>",
9777
+ "Atomic runtime marker file",
9778
+ "/tmp/preview-artifact/runtime.json"
9779
+ ).option(
9780
+ "--listen-port <port>",
9781
+ "Stable public preview port",
9782
+ parseTcpPort,
9783
+ 8001
9784
+ ).option(
9785
+ "--cached-server-port <port>",
9786
+ "Isolated cached Nest server port, 0 selects a free port",
9787
+ parseTcpPort,
9788
+ 0
9789
+ ).option(
9790
+ "--fresh-server-port <port>",
9791
+ "Fresh Nest shadow port recorded for dev startup",
9792
+ parseTcpPort,
9793
+ 3001
9794
+ ).option(
9795
+ "--fresh-client-port <port>",
9796
+ "Fresh Vite shadow port used as the cutover target",
9797
+ parseTcpPort,
9798
+ 8002
9799
+ ).requiredOption(
9800
+ "--app-base-path <path>",
9801
+ "Application HTML readiness path"
9802
+ ).action(
9803
+ async (options) => {
9804
+ const runtime = await startPreviewArtifactRuntime(options);
9805
+ if (!runtime.started) {
9806
+ console.log(JSON.stringify(runtime));
9807
+ process.exitCode = 2;
9808
+ return;
9809
+ }
9810
+ console.log(
9811
+ JSON.stringify({
9812
+ started: true,
9813
+ generationId: runtime.generationId,
9814
+ origin: runtime.origin,
9815
+ runtimeDir: runtime.runtimeDir
9816
+ })
9817
+ );
9818
+ void runtime.cutover.then((result) => {
9819
+ console.log(JSON.stringify({ event: "fresh-cutover", ...result }));
9820
+ });
9821
+ const shutdown = async () => {
9822
+ await runtime.close();
9823
+ process.exit(0);
9824
+ };
9825
+ process.once("SIGTERM", () => void shutdown());
9826
+ process.once("SIGINT", () => void shutdown());
9827
+ }
9828
+ );
9829
+ }
9830
+ };
8669
9831
  var buildCommandGroup = {
8670
9832
  name: "build",
8671
9833
  description: "Build related commands",
@@ -8673,7 +9835,9 @@ var buildCommandGroup = {
8673
9835
  getTokenCommand,
8674
9836
  uploadStaticCommand,
8675
9837
  preUploadStaticCommand,
8676
- previewServerArtifactCommand
9838
+ previewServerArtifactCommand,
9839
+ previewArtifactGenerationCommand,
9840
+ previewArtifactRuntimeCommand
8677
9841
  ]
8678
9842
  };
8679
9843
 
@@ -8691,12 +9855,12 @@ var commands = [
8691
9855
  ];
8692
9856
 
8693
9857
  // src/index.ts
8694
- var envPath = path26.join(process.cwd(), ".env");
8695
- if (fs30.existsSync(envPath)) {
9858
+ var envPath = path31.join(process.cwd(), ".env");
9859
+ if (fs35.existsSync(envPath)) {
8696
9860
  dotenvConfig({ path: envPath });
8697
9861
  }
8698
- var __dirname = path26.dirname(fileURLToPath5(import.meta.url));
8699
- var pkg = JSON.parse(fs30.readFileSync(path26.join(__dirname, "../package.json"), "utf-8"));
9862
+ var __dirname = path31.dirname(fileURLToPath5(import.meta.url));
9863
+ var pkg = JSON.parse(fs35.readFileSync(path31.join(__dirname, "../package.json"), "utf-8"));
8700
9864
  var cli = new FullstackCLI(pkg.version);
8701
9865
  cli.useAll(commands);
8702
9866
  cli.run();