@lark-apaas/fullstack-cli 1.1.59-alpha.20260719124023 → 1.1.59-alpha.3

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 fs37 from "fs";
3
- import path33 from "path";
2
+ import fs29 from "fs";
3
+ import path25 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((resolve2) => {
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
+ resolve2({
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
+ resolve2({
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((resolve2) => {
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
+ resolve2();
4978
4978
  });
4979
4979
  });
4980
4980
  }
@@ -8098,1951 +8098,21 @@ async function preUploadStatic(options) {
8098
8098
  }
8099
8099
  }
8100
8100
 
8101
- // src/commands/build/preview-server-artifact.handler.ts
8102
- import * as fs29 from "fs";
8103
- import * as os3 from "os";
8104
- import * as path25 from "path";
8105
- import { builtinModules, createRequire as createRequire3 } from "module";
8106
- import { build } from "esbuild";
8107
- import { parse as parse2 } from "acorn";
8108
- import { simple as walkSimple } from "acorn-walk";
8109
- var STATIC_PLUGIN_REGISTRY_SYMBOL = "@lark-apaas/nestjs-capability/static-plugin-registry";
8110
- var STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION = 1;
8111
- var OPTIONAL_DEPENDENCY_STUBS = [
8112
- "@nestjs/microservices",
8113
- "@nestjs/microservices/microservices-module",
8114
- "@nestjs/websockets/socket-module",
8115
- "fsevents"
8116
- ];
8117
- var BUILTIN_MODULES = /* @__PURE__ */ new Set([
8118
- ...builtinModules,
8119
- ...builtinModules.map((moduleName) => `node:${moduleName}`)
8120
- ]);
8121
- function toAbsolutePath(projectRoot, candidate) {
8122
- return path25.isAbsolute(candidate) ? candidate : path25.resolve(projectRoot, candidate);
8123
- }
8124
- function readJson(filePath) {
8125
- return JSON.parse(fs29.readFileSync(filePath, "utf8"));
8126
- }
8127
- function findPackageRoot(entryPath) {
8128
- let current = path25.dirname(entryPath);
8129
- const filesystemRoot = path25.parse(current).root;
8130
- while (current !== filesystemRoot) {
8131
- if (fs29.existsSync(path25.join(current, "package.json"))) {
8132
- return current;
8133
- }
8134
- current = path25.dirname(current);
8135
- }
8136
- return null;
8137
- }
8138
- function findNativeAddons(root) {
8139
- const nativeAddons = [];
8140
- const pending = [root];
8141
- while (pending.length > 0) {
8142
- const current = pending.pop();
8143
- if (!current) {
8144
- continue;
8145
- }
8146
- for (const entry of fs29.readdirSync(current, { withFileTypes: true })) {
8147
- const entryPath = path25.join(current, entry.name);
8148
- if (entry.isSymbolicLink()) {
8149
- continue;
8150
- }
8151
- if (entry.isDirectory()) {
8152
- pending.push(entryPath);
8153
- } else if (entry.isFile() && entry.name.endsWith(".node")) {
8154
- nativeAddons.push(entryPath);
8155
- }
8156
- }
8157
- }
8158
- return nativeAddons.sort();
8159
- }
8160
- function resolveActionPlugins(projectRoot, packageJson) {
8161
- const configuredPlugins = packageJson.actionPlugins;
8162
- if (!configuredPlugins || typeof configuredPlugins !== "object") {
8163
- return { plugins: [], reasons: [] };
8164
- }
8165
- const projectRequire = createRequire3(path25.join(projectRoot, "package.json"));
8166
- const plugins = [];
8167
- const reasons = [];
8168
- for (const name of Object.keys(configuredPlugins).sort()) {
8169
- let entryPath;
8170
- try {
8171
- entryPath = projectRequire.resolve(name);
8172
- } catch (error) {
8173
- const message = error instanceof Error ? error.message : String(error);
8174
- reasons.push(`Action Plugin ${name} \u65E0\u6CD5\u89E3\u6790: ${message}`);
8175
- continue;
8176
- }
8177
- const packageRoot = findPackageRoot(entryPath);
8178
- if (!packageRoot) {
8179
- reasons.push(`Action Plugin ${name} \u65E0\u6CD5\u5B9A\u4F4D package.json: ${entryPath}`);
8180
- continue;
8181
- }
8182
- const nativeAddons = findNativeAddons(packageRoot);
8183
- if (nativeAddons.length > 0) {
8184
- const relativeAddons = nativeAddons.map(
8185
- (filePath) => path25.relative(packageRoot, filePath)
8186
- );
8187
- reasons.push(
8188
- `Action Plugin ${name} \u5305\u542B\u539F\u751F\u6269\u5C55\uFF0C\u4E0D\u80FD\u751F\u6210\u8DE8\u6C99\u7BB1\u5355 bundle: ${relativeAddons.join(", ")}`
8189
- );
8190
- continue;
8191
- }
8192
- const manifestPath = path25.join(packageRoot, "manifest.json");
8193
- let manifest;
8194
- if (fs29.existsSync(manifestPath)) {
8195
- try {
8196
- manifest = readJson(manifestPath);
8197
- } catch (error) {
8198
- const message = error instanceof Error ? error.message : String(error);
8199
- reasons.push(
8200
- `Action Plugin ${name} \u7684 manifest.json \u65E0\u6CD5\u89E3\u6790: ${message}`
8201
- );
8202
- continue;
8203
- }
8204
- }
8205
- plugins.push({ name, entryPath, manifest });
8206
- }
8207
- if (reasons.length === 0 && plugins.length > 0) {
8208
- try {
8209
- const capabilityRuntime = projectRequire(
8210
- "@lark-apaas/nestjs-capability"
8211
- );
8212
- if (capabilityRuntime.STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION !== STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION) {
8213
- reasons.push(
8214
- `@lark-apaas/nestjs-capability \u672A\u63D0\u4F9B\u517C\u5BB9\u7684 STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION=${STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION}`
8215
- );
8216
- }
8217
- } catch (error) {
8218
- const message = error instanceof Error ? error.message : String(error);
8219
- reasons.push(
8220
- `@lark-apaas/nestjs-capability \u65E0\u6CD5\u52A0\u8F7D\uFF0C\u7F3A\u5C11 STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION=${STATIC_PLUGIN_REGISTRY_PROTOCOL_VERSION}: ${message}`
8221
- );
8222
- }
8223
- }
8224
- return { plugins, reasons };
8225
- }
8226
- function generateBootstrap(entryPath, plugins) {
8227
- const lines = [`'use strict';`, `const __staticPluginRegistry = new Map();`];
8228
- plugins.forEach((plugin, index) => {
8229
- const moduleVariable = `__staticPlugin${index}`;
8230
- lines.push(
8231
- `const ${moduleVariable} = require(${JSON.stringify(plugin.entryPath)});`
8232
- );
8233
- lines.push(
8234
- `__staticPluginRegistry.set(${JSON.stringify(plugin.name)}, { module: ${moduleVariable}, manifest: ${JSON.stringify(plugin.manifest ?? null)} });`
8235
- );
8236
- });
8237
- lines.push(
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
- `}`,
8242
- `require(${JSON.stringify(entryPath)});`,
8243
- ""
8244
- );
8245
- return lines.join("\n");
8246
- }
8247
- function classTransformerStorageResolver(projectRoot) {
8248
- const projectRequire = createRequire3(path25.join(projectRoot, "package.json"));
8249
- return {
8250
- name: "class-transformer-storage-resolver",
8251
- setup(esbuild) {
8252
- esbuild.onResolve({ filter: /^class-transformer\/storage$/ }, () => {
8253
- try {
8254
- return {
8255
- path: projectRequire.resolve("class-transformer/cjs/storage.js")
8256
- };
8257
- } catch {
8258
- return void 0;
8259
- }
8260
- });
8261
- }
8262
- };
8263
- }
8264
- function optionalDependencyStubPlugin() {
8265
- const stubbedDependencies = /* @__PURE__ */ new Set();
8266
- const escapedNames = OPTIONAL_DEPENDENCY_STUBS.map(
8267
- (name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
8268
- );
8269
- const filter = new RegExp(`^(?:${escapedNames.join("|")})$`);
8270
- return {
8271
- stubbedDependencies,
8272
- plugin: {
8273
- name: "preview-optional-dependency-stubs",
8274
- setup(esbuild) {
8275
- esbuild.onResolve({ filter }, (args) => {
8276
- stubbedDependencies.add(args.path);
8277
- return {
8278
- path: args.path,
8279
- namespace: "preview-optional-dependency"
8280
- };
8281
- });
8282
- esbuild.onLoad(
8283
- { filter: /.*/, namespace: "preview-optional-dependency" },
8284
- (args) => ({
8285
- contents: `
8286
- const error = new Error(${JSON.stringify(
8287
- `Optional dependency is unavailable in Preview Artifact: ${args.path}`
8288
- )});
8289
- error.code = 'MODULE_NOT_FOUND';
8290
- throw error;
8291
- `,
8292
- loader: "js"
8293
- })
8294
- );
8295
- }
8296
- }
8297
- };
8298
- }
8299
- function isStaticString(node) {
8300
- if (!node) {
8301
- return false;
8302
- }
8303
- if (node.type === "Literal") {
8304
- return typeof node.value === "string";
8305
- }
8306
- return node.type === "TemplateLiteral" && node.expressions.length === 0;
8307
- }
8308
- function staticPropertyName(node) {
8309
- if (node.type !== "MemberExpression") {
8310
- return void 0;
8311
- }
8312
- if (!node.computed && node.property.type === "Identifier") {
8313
- return node.property.name;
8314
- }
8315
- if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string") {
8316
- return node.property.value;
8317
- }
8318
- return void 0;
8319
- }
8320
- function auditBundleClosure(outfile, stubbedDependencies) {
8321
- const ast = parse2(fs29.readFileSync(outfile, "utf8"), {
8322
- ecmaVersion: "latest",
8323
- sourceType: "script",
8324
- allowHashBang: true
8325
- });
8326
- let dynamicRequireCount = 0;
8327
- let dynamicImportCount = 0;
8328
- let createRequireCount = 0;
8329
- let requireResolveCount = 0;
8330
- let runtimeFileReadCount = 0;
8331
- const runtimeFileReads = /* @__PURE__ */ new Set([
8332
- "access",
8333
- "accessSync",
8334
- "createReadStream",
8335
- "open",
8336
- "openSync",
8337
- "readFile",
8338
- "readFileSync",
8339
- "readdir",
8340
- "readdirSync",
8341
- "stat",
8342
- "statSync"
8343
- ]);
8344
- walkSimple(ast, {
8345
- CallExpression(node) {
8346
- if (node.callee.type === "Identifier" && node.callee.name === "require" && !isStaticString(node.arguments[0])) {
8347
- dynamicRequireCount += 1;
8348
- }
8349
- if (node.callee.type === "Identifier" && node.callee.name === "createRequire" || staticPropertyName(node.callee) === "createRequire") {
8350
- createRequireCount += 1;
8351
- }
8352
- const propertyName = staticPropertyName(node.callee);
8353
- if (propertyName === "resolve" && node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && node.callee.object.name === "require") {
8354
- requireResolveCount += 1;
8355
- }
8356
- if (propertyName && runtimeFileReads.has(propertyName)) {
8357
- runtimeFileReadCount += 1;
8358
- }
8359
- },
8360
- ImportExpression(node) {
8361
- if (!isStaticString(node.source)) {
8362
- dynamicImportCount += 1;
8363
- }
8364
- }
8365
- });
8366
- const risks = [];
8367
- if (dynamicRequireCount > 0) {
8368
- risks.push(`bundle retains ${dynamicRequireCount} dynamic require call(s)`);
8369
- }
8370
- if (dynamicImportCount > 0) {
8371
- risks.push(`bundle retains ${dynamicImportCount} dynamic import call(s)`);
8372
- }
8373
- if (createRequireCount > 0 || requireResolveCount > 0) {
8374
- risks.push(
8375
- `bundle retains runtime module resolution (createRequire=${createRequireCount}, require.resolve=${requireResolveCount})`
8376
- );
8377
- }
8378
- if (runtimeFileReadCount > 0) {
8379
- risks.push(
8380
- `bundle retains ${runtimeFileReadCount} runtime filesystem read call(s)`
8381
- );
8382
- }
8383
- if (stubbedDependencies.length > 0) {
8384
- risks.push(
8385
- `bundle stubs optional dependencies: ${stubbedDependencies.join(", ")}`
8386
- );
8387
- }
8388
- return risks;
8389
- }
8390
- function collectExternalImports(metafile, outfile) {
8391
- const normalizedOutfile = path25.resolve(outfile);
8392
- const output = Object.entries(metafile.outputs).find(
8393
- ([outputPath]) => path25.resolve(outputPath) === normalizedOutfile
8394
- )?.[1];
8395
- if (!output) {
8396
- return [];
8397
- }
8398
- return Array.from(
8399
- new Set(
8400
- output.imports.filter((importRecord) => importRecord.external).map((importRecord) => importRecord.path).filter(
8401
- (importPath) => !BUILTIN_MODULES.has(importPath) && !importPath.startsWith("node:")
8402
- )
8403
- )
8404
- ).sort();
8405
- }
8406
- function formatBuildError(error) {
8407
- if (!error || typeof error !== "object" || !("errors" in error)) {
8408
- return [error instanceof Error ? error.message : String(error)];
8409
- }
8410
- const errors = error.errors;
8411
- if (!errors || errors.length === 0) {
8412
- return [error instanceof Error ? error.message : String(error)];
8413
- }
8414
- return errors.map((item) => {
8415
- const location = item.location?.file ? `${item.location.file}${item.location.line ? `:${item.location.line}` : ""}: ` : "";
8416
- return `${location}${item.text ?? "unknown esbuild error"}`;
8417
- });
8418
- }
8419
- function removePublishedArtifact(outfile, metadataFile) {
8420
- fs29.rmSync(outfile, { force: true });
8421
- fs29.rmSync(metadataFile, { force: true });
8422
- }
8423
- function bestEffortRemove(targetPath, options = {}) {
8424
- if (!targetPath) {
8425
- return;
8426
- }
8427
- try {
8428
- fs29.rmSync(targetPath, { force: true, recursive: options.recursive });
8429
- } catch {
8430
- }
8431
- }
8432
- function outputPathConflicts(entryPath, packageJsonPath, outfile, metadataFile) {
8433
- const reasons = [];
8434
- if (outfile === metadataFile) {
8435
- reasons.push("bundle \u4E0E metadata \u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u76F8\u540C");
8436
- }
8437
- const protectedInputs = /* @__PURE__ */ new Map([
8438
- [entryPath, "Nest \u7F16\u8BD1\u5165\u53E3"],
8439
- [packageJsonPath, "package.json"]
8440
- ]);
8441
- for (const [outputPath, outputName] of [
8442
- [outfile, "bundle"],
8443
- [metadataFile, "metadata"]
8444
- ]) {
8445
- const protectedName = protectedInputs.get(outputPath);
8446
- if (protectedName) {
8447
- reasons.push(
8448
- `${outputName} \u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6 ${protectedName}: ${outputPath}`
8449
- );
8450
- }
8451
- }
8452
- return reasons;
8453
- }
8454
- async function buildPreviewServerArtifact(options) {
8455
- const startedAt = Date.now();
8456
- const projectRoot = path25.resolve(options.projectRoot);
8457
- const entryPath = toAbsolutePath(
8458
- projectRoot,
8459
- options.entry ?? "dist/main.js"
8460
- );
8461
- const outfile = toAbsolutePath(
8462
- projectRoot,
8463
- options.outfile ?? ".preview-artifact/server.bundle.cjs"
8464
- );
8465
- const metadataFile = toAbsolutePath(
8466
- projectRoot,
8467
- options.metadataFile ?? `${outfile}.meta.json`
8468
- );
8469
- const packageJsonPath = path25.join(projectRoot, "package.json");
8470
- let pluginCount = 0;
8471
- let outputCleared = false;
8472
- let phase = "\u6821\u9A8C\u8F93\u5165";
8473
- let tempRoot;
8474
- let tempOutputRoot;
8475
- let tempMetadataFile;
8476
- const failed = (reasons) => ({
8477
- built: false,
8478
- elapsedMs: Date.now() - startedAt,
8479
- pluginCount,
8480
- reasons
8481
- });
8482
- const pathConflicts = outputPathConflicts(
8483
- entryPath,
8484
- packageJsonPath,
8485
- outfile,
8486
- metadataFile
8487
- );
8488
- if (pathConflicts.length > 0) {
8489
- return failed(pathConflicts);
8490
- }
8491
- if (!fs29.existsSync(entryPath)) {
8492
- return failed([`Nest \u7F16\u8BD1\u5165\u53E3\u4E0D\u5B58\u5728: ${entryPath}`]);
8493
- }
8494
- if (!fs29.existsSync(packageJsonPath)) {
8495
- return failed([`package.json \u4E0D\u5B58\u5728: ${packageJsonPath}`]);
8496
- }
8497
- try {
8498
- phase = `\u89E3\u6790 ${packageJsonPath}`;
8499
- const packageJson = readJson(packageJsonPath);
8500
- phase = "\u89E3\u6790 Action Plugin";
8501
- const { plugins, reasons } = resolveActionPlugins(projectRoot, packageJson);
8502
- pluginCount = plugins.length;
8503
- phase = "\u6E05\u7406\u65E7 Preview Artifact";
8504
- removePublishedArtifact(outfile, metadataFile);
8505
- outputCleared = true;
8506
- if (reasons.length > 0) {
8507
- return failed(reasons);
8508
- }
8509
- phase = "\u521B\u5EFA Preview Artifact \u4E34\u65F6\u76EE\u5F55";
8510
- fs29.mkdirSync(path25.dirname(outfile), { recursive: true });
8511
- fs29.mkdirSync(path25.dirname(metadataFile), { recursive: true });
8512
- tempRoot = fs29.mkdtempSync(
8513
- path25.join(os3.tmpdir(), "preview-server-artifact-")
8514
- );
8515
- tempOutputRoot = fs29.mkdtempSync(
8516
- path25.join(path25.dirname(outfile), ".preview-server-artifact-")
8517
- );
8518
- const bootstrapPath = path25.join(tempRoot, "bootstrap.cjs");
8519
- const tempOutfile = path25.join(tempOutputRoot, path25.basename(outfile));
8520
- tempMetadataFile = path25.join(
8521
- path25.dirname(metadataFile),
8522
- `.${path25.basename(metadataFile)}.${process.pid}.${Date.now()}.tmp`
8523
- );
8524
- fs29.writeFileSync(bootstrapPath, generateBootstrap(entryPath, plugins));
8525
- const optionalDependencies = optionalDependencyStubPlugin();
8526
- const buildOptions = {
8527
- absWorkingDir: projectRoot,
8528
- entryPoints: [bootstrapPath],
8529
- outfile: tempOutfile,
8530
- bundle: true,
8531
- platform: "node",
8532
- format: "cjs",
8533
- target: "node22",
8534
- treeShaking: true,
8535
- keepNames: true,
8536
- legalComments: "none",
8537
- sourcemap: false,
8538
- metafile: true,
8539
- logLevel: "silent",
8540
- plugins: [
8541
- classTransformerStorageResolver(projectRoot),
8542
- optionalDependencies.plugin
8543
- ]
8544
- };
8545
- phase = "\u6267\u884C esbuild";
8546
- const buildResult2 = await build(buildOptions);
8547
- if (!buildResult2.metafile) {
8548
- throw new Error("esbuild \u672A\u8FD4\u56DE metafile");
8549
- }
8550
- const externalImports = collectExternalImports(
8551
- buildResult2.metafile,
8552
- tempOutfile
8553
- );
8554
- if (externalImports.length > 0) {
8555
- return failed([`bundle \u6B8B\u7559\u8FD0\u884C\u65F6\u4F9D\u8D56: ${externalImports.join(", ")}`]);
8556
- }
8557
- phase = "\u5BA1\u8BA1 bundle \u8FD0\u884C\u65F6\u95ED\u5305";
8558
- const stubbedOptionalDependencies = Array.from(
8559
- optionalDependencies.stubbedDependencies
8560
- ).sort();
8561
- const closureRisks = auditBundleClosure(
8562
- tempOutfile,
8563
- stubbedOptionalDependencies
8564
- );
8565
- const closureStatus = closureRisks.length === 0 ? "static-audit-clean" : "runtime-validation-required";
8566
- const metadata = {
8567
- version: 1,
8568
- entry: entryPath,
8569
- outfile,
8570
- bundleBytes: fs29.statSync(tempOutfile).size,
8571
- inputFiles: Object.keys(buildResult2.metafile.inputs).length,
8572
- externalImports,
8573
- actionPlugins: plugins.map((plugin) => plugin.name),
8574
- closureStatus,
8575
- closureRisks,
8576
- stubbedOptionalDependencies,
8577
- runtimeProbeStatus: "not-run",
8578
- consumable: false,
8579
- elapsedMs: Date.now() - startedAt
8580
- };
8581
- phase = "\u53D1\u5E03 bundle \u4E0E metadata";
8582
- fs29.writeFileSync(
8583
- tempMetadataFile,
8584
- `${JSON.stringify(metadata, null, 2)}
8585
- `
8586
- );
8587
- fs29.renameSync(tempOutfile, outfile);
8588
- fs29.renameSync(tempMetadataFile, metadataFile);
8589
- return {
8590
- built: true,
8591
- outfile,
8592
- metadataFile,
8593
- bundleBytes: metadata.bundleBytes,
8594
- inputFiles: metadata.inputFiles,
8595
- externalImports,
8596
- pluginCount,
8597
- closureStatus,
8598
- closureRisks,
8599
- stubbedOptionalDependencies,
8600
- runtimeProbeStatus: "not-run",
8601
- consumable: false,
8602
- elapsedMs: metadata.elapsedMs
8603
- };
8604
- } catch (error) {
8605
- const reasons = formatBuildError(error).map(
8606
- (reason) => `${phase}: ${reason}`
8607
- );
8608
- if (outputCleared) {
8609
- try {
8610
- removePublishedArtifact(outfile, metadataFile);
8611
- } catch (cleanupError) {
8612
- reasons.push(
8613
- `\u6E05\u7406\u5931\u8D25\u4EA7\u7269: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`
8614
- );
8615
- }
8616
- }
8617
- return failed(reasons);
8618
- } finally {
8619
- bestEffortRemove(tempRoot, { recursive: true });
8620
- bestEffortRemove(tempOutputRoot, { recursive: true });
8621
- bestEffortRemove(tempMetadataFile);
8622
- }
8623
- }
8624
-
8625
- // src/commands/preview-artifact/producer.ts
8626
- import * as crypto2 from "crypto";
8627
- import * as fs34 from "fs";
8628
- import * as path30 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 = 2;
8636
- var PREVIEW_ARTIFACT_PROTOCOL_VERSION = 2;
8637
- var MANIFEST_FILE = "manifest.json";
8638
- var PROBE_FILE = "probe.json";
8639
- var SERVER_FILE = "server.bundle.cjs";
8640
- var CLIENT_FILE = "client.zip";
8641
- var RUNTIME_ONLY_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
8642
- "CLIENT_DEV_HOST",
8643
- "CLIENT_DEV_PORT",
8644
- "FORCE_AUTHN_ACCESS_KEY",
8645
- "FORCE_AUTHN_ACCESS_SECRET",
8646
- "FORCE_AUTHN_PREVIEW_SESSION_ID",
8647
- "MIAODA_HMR_WS_TOKEN",
8648
- "MIAODA_PREVIEW_RUN_ID",
8649
- "PREVIEW_RUN_ID",
8650
- "SANDBOX_COOKIE",
8651
- "SANDBOX_ID",
8652
- "SERVER_PORT",
8653
- "preview_run_id"
8654
- ]);
8655
- var RUNTIME_ONLY_ENVIRONMENT_PREFIXES = ["PREVIEW_ARTIFACT_"];
8656
- function previewArtifactBuildEnvironment(environment, appBasePath) {
8657
- const buildEnvironment = {};
8658
- for (const [key, value] of Object.entries(environment ?? {})) {
8659
- if (typeof value === "string" && !RUNTIME_ONLY_ENVIRONMENT_KEYS.has(key) && !RUNTIME_ONLY_ENVIRONMENT_PREFIXES.some((prefix) => key.startsWith(prefix))) {
8660
- buildEnvironment[key] = value;
8661
- }
8662
- }
8663
- buildEnvironment.CLIENT_BASE_PATH = appBasePath;
8664
- buildEnvironment.NODE_ENV = environment?.NODE_ENV ?? "production";
8665
- return buildEnvironment;
8666
- }
8667
- function previewArtifactBuildEnvironmentHash(environment, appBasePath) {
8668
- const buildEnvironment = previewArtifactBuildEnvironment(
8669
- environment,
8670
- appBasePath
8671
- );
8672
- const canonical = Object.fromEntries(
8673
- Object.entries(buildEnvironment).sort(
8674
- ([left], [right]) => left.localeCompare(right)
8675
- )
8676
- );
8677
- return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
8678
- }
8679
- function parseNodeMajor(nodeVersion) {
8680
- const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
8681
- if (!match) {
8682
- throw new Error(`invalid Node.js version: ${nodeVersion}`);
8683
- }
8684
- return Number(match[1]);
8685
- }
8686
- function sha256File(filePath) {
8687
- const hash = crypto.createHash("sha256");
8688
- hash.update(fs30.readFileSync(filePath));
8689
- return hash.digest("hex");
8690
- }
8691
- function fileIdentity(generationDir, relativePath) {
8692
- const filePath = path26.join(generationDir, relativePath);
8693
- const stat = fs30.lstatSync(filePath);
8694
- if (!stat.isFile() || stat.isSymbolicLink()) {
8695
- throw new Error(`artifact file must be a regular file: ${relativePath}`);
8696
- }
8697
- return {
8698
- path: relativePath,
8699
- bytes: stat.size,
8700
- sha256: sha256File(filePath)
8701
- };
8702
- }
8703
- function readJson2(filePath) {
8704
- return JSON.parse(fs30.readFileSync(filePath, "utf8"));
8705
- }
8706
- function writeJsonAtomic(filePath, value) {
8707
- fs30.mkdirSync(path26.dirname(filePath), { recursive: true });
8708
- const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
8709
- fs30.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
8710
- `, {
8711
- mode: 384
8712
- });
8713
- fs30.renameSync(temporaryPath, filePath);
8714
- }
8715
- function runtimeIdentity(nodeVersion, platform, arch) {
8716
- return { nodeMajor: parseNodeMajor(nodeVersion), platform, arch };
8717
- }
8718
- function sameRuntime(left, right) {
8719
- return left.nodeMajor === right.nodeMajor && left.platform === right.platform && left.arch === right.arch;
8720
- }
8721
- function createPreviewArtifactManifest(options) {
8722
- const generationDir = path26.resolve(options.generationDir);
8723
- const manifest = {
8724
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
8725
- protocolVersion: PREVIEW_ARTIFACT_PROTOCOL_VERSION,
8726
- generationId: options.generationId,
8727
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8728
- buildEnvironmentSha256: options.buildEnvironmentSha256,
8729
- runtime: runtimeIdentity(
8730
- options.nodeVersion,
8731
- options.platform,
8732
- options.arch
8733
- ),
8734
- files: {
8735
- server: fileIdentity(generationDir, SERVER_FILE),
8736
- client: fileIdentity(generationDir, CLIENT_FILE)
8737
- },
8738
- actionPlugins: Array.from(new Set(options.actionPlugins ?? [])).sort(),
8739
- runtimeProbeStatus: "not-run",
8740
- consumable: false
8741
- };
8742
- writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), manifest);
8743
- fs30.rmSync(path26.join(generationDir, PROBE_FILE), { force: true });
8744
- return manifest;
8745
- }
8746
- function recordPreviewArtifactProbe(options) {
8747
- const generationDir = path26.resolve(options.generationDir);
8748
- const manifest = readJson2(
8749
- path26.join(generationDir, MANIFEST_FILE)
8750
- );
8751
- const probe = {
8752
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
8753
- generationId: manifest.generationId,
8754
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
8755
- success: options.success,
8756
- reasons: options.reasons ?? [],
8757
- runtime: runtimeIdentity(
8758
- options.nodeVersion,
8759
- options.platform,
8760
- options.arch
8761
- ),
8762
- serverSha256: sha256File(path26.join(generationDir, SERVER_FILE)),
8763
- clientSha256: sha256File(path26.join(generationDir, CLIENT_FILE)),
8764
- coverage: {
8765
- html: options.coverage.html,
8766
- businessApi: options.coverage.businessApi,
8767
- clientAssets: Array.from(new Set(options.coverage.clientAssets)).sort(),
8768
- actionPlugins: Array.from(new Set(options.coverage.actionPlugins)).sort()
8769
- }
8770
- };
8771
- writeJsonAtomic(path26.join(generationDir, PROBE_FILE), probe);
8772
- return probe;
8773
- }
8774
- function promotePreviewArtifactGeneration(generationDirInput) {
8775
- const generationDir = path26.resolve(generationDirInput);
8776
- const reasons = [];
8777
- let manifest;
8778
- let probe;
8779
- try {
8780
- manifest = readJson2(
8781
- path26.join(generationDir, MANIFEST_FILE)
8782
- );
8783
- probe = readJson2(
8784
- path26.join(generationDir, PROBE_FILE)
8785
- );
8786
- } catch (error) {
8787
- return {
8788
- promoted: false,
8789
- reasons: [
8790
- `probe evidence missing or invalid: ${error instanceof Error ? error.message : String(error)}`
8791
- ]
8792
- };
8793
- }
8794
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
8795
- reasons.push("schema version mismatch");
8796
- }
8797
- if (probe.generationId !== manifest.generationId) {
8798
- reasons.push("probe generation identity mismatch");
8799
- }
8800
- if (!probe.success) {
8801
- reasons.push(`probe failed: ${probe.reasons.join("; ") || "unknown"}`);
8802
- }
8803
- if (!sameRuntime(manifest.runtime, probe.runtime)) {
8804
- reasons.push(
8805
- "probe runtime identity does not match build runtime identity"
8806
- );
8807
- }
8808
- const currentServerHash = sha256File(path26.join(generationDir, SERVER_FILE));
8809
- const currentClientHash = sha256File(path26.join(generationDir, CLIENT_FILE));
8810
- if (currentServerHash !== manifest.files.server.sha256 || currentServerHash !== probe.serverSha256) {
8811
- reasons.push("server.bundle.cjs hash does not match probe identity");
8812
- }
8813
- if (currentClientHash !== manifest.files.client.sha256 || currentClientHash !== probe.clientSha256) {
8814
- reasons.push("client.zip hash does not match probe identity");
8815
- }
8816
- if (!probe.coverage.html) {
8817
- reasons.push("probe did not verify HTML");
8818
- }
8819
- if (!probe.coverage.businessApi) {
8820
- reasons.push("probe did not verify business API");
8821
- }
8822
- if (!Array.isArray(probe.coverage.clientAssets) || probe.coverage.clientAssets.length === 0) {
8823
- reasons.push("probe did not verify client entry assets");
8824
- }
8825
- const verifiedPlugins = new Set(probe.coverage.actionPlugins);
8826
- const missingPlugins = manifest.actionPlugins.filter(
8827
- (plugin) => !verifiedPlugins.has(plugin)
8828
- );
8829
- if (missingPlugins.length > 0) {
8830
- reasons.push(
8831
- `probe did not verify Action Plugin: ${missingPlugins.join(", ")}`
8832
- );
8833
- }
8834
- if (reasons.length > 0) {
8835
- return { promoted: false, reasons };
8836
- }
8837
- const promotedManifest = {
8838
- ...manifest,
8839
- runtimeProbeStatus: "passed",
8840
- consumable: true
8841
- };
8842
- writeJsonAtomic(path26.join(generationDir, MANIFEST_FILE), promotedManifest);
8843
- return { promoted: true, manifest: promotedManifest };
8844
- }
8845
-
8846
- // src/commands/preview-artifact/probe.ts
8847
- import * as fs31 from "fs";
8848
- import * as net from "net";
8849
- import * as os4 from "os";
8850
- import * as path27 from "path";
8851
- import { spawn as spawn2, spawnSync as spawnSync7 } from "child_process";
8852
-
8853
- // src/commands/preview-artifact/readiness.ts
8854
- function isPreviewBusinessResponseReady(response) {
8855
- if (response.status === 401 || response.status === 403) return true;
8856
- return response.status !== 404 && response.status < 500 && !response.headers.get("content-type")?.includes("text/html");
8857
- }
8858
-
8859
- // src/commands/preview-artifact/probe.ts
8860
- function sleep(ms) {
8861
- return new Promise((resolve9) => setTimeout(resolve9, ms));
8862
- }
8863
- async function reservePort() {
8864
- return new Promise((resolve9, reject) => {
8865
- const server = net.createServer();
8866
- server.unref();
8867
- server.once("error", reject);
8868
- server.listen(0, "127.0.0.1", () => {
8869
- const address = server.address();
8870
- if (!address || typeof address === "string") {
8871
- server.close(() => reject(new Error("unable to reserve probe port")));
8872
- return;
8873
- }
8874
- server.close((error) => {
8875
- if (error) reject(error);
8876
- else resolve9(address.port);
8877
- });
8878
- });
8879
- });
8880
- }
8881
- function archiveEntries(archivePath) {
8882
- const listing = spawnSync7("unzip", ["-Z1", archivePath], {
8883
- encoding: "utf8"
8884
- });
8885
- if (listing.status !== 0) {
8886
- throw new Error(`client archive listing failed: ${listing.stderr.trim()}`);
8887
- }
8888
- const entries = listing.stdout.split("\n").filter(Boolean);
8889
- for (const entry of entries) {
8890
- const normalized = path27.posix.normalize(entry.replaceAll("\\", "/"));
8891
- if (path27.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
8892
- throw new Error(`client archive contains unsafe path: ${entry}`);
8893
- }
8894
- }
8895
- return entries;
8896
- }
8897
- function rejectArchivedSymlinks(archivePath) {
8898
- const listing = spawnSync7("unzip", ["-Z", "-l", archivePath], {
8899
- encoding: "utf8"
8900
- });
8901
- if (listing.status !== 0) {
8902
- throw new Error(
8903
- `client archive metadata listing failed: ${listing.stderr.trim()}`
8904
- );
8905
- }
8906
- for (const line of listing.stdout.split("\n")) {
8907
- if (/^l[rwxstST-]{9}\s/.test(line.trimStart())) {
8908
- throw new Error("client archive contains symlink");
8909
- }
8910
- }
8911
- }
8912
- function rejectSymlinks(root) {
8913
- const pending = [root];
8914
- while (pending.length > 0) {
8915
- const current = pending.pop();
8916
- if (!current) continue;
8917
- for (const entry of fs31.readdirSync(current, { withFileTypes: true })) {
8918
- const entryPath = path27.join(current, entry.name);
8919
- const stat = fs31.lstatSync(entryPath);
8920
- if (stat.isSymbolicLink()) {
8921
- throw new Error(`client archive contains symlink: ${entry.name}`);
8922
- }
8923
- if (stat.isDirectory()) pending.push(entryPath);
8924
- }
8925
- }
8926
- }
8927
- function isPathInside(parent, candidate) {
8928
- const relative5 = path27.relative(parent, candidate);
8929
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative5);
8930
- }
8931
- function clientEntryAssetReferences(html, appBasePath) {
8932
- const local = /* @__PURE__ */ new Set();
8933
- const localExecutable = /* @__PURE__ */ new Set();
8934
- const unsupportedExternal = /* @__PURE__ */ new Set();
8935
- const tags = /<(script|link)\b[^>]*>/gi;
8936
- const readAttribute = (tag, name) => new RegExp(`\\b${name}=["']([^"']+)["']`, "i").exec(tag)?.[1];
8937
- const base = new URL(appBasePath, "http://preview.local");
8938
- for (const match of html.matchAll(tags)) {
8939
- const tagName = match[1]?.toLowerCase();
8940
- const tag = match[0];
8941
- let linkRel = [];
8942
- if (tagName === "link") {
8943
- linkRel = readAttribute(tag, "rel")?.toLowerCase().split(/\s+/) ?? [];
8944
- if (!linkRel.some(
8945
- (value2) => [
8946
- "stylesheet",
8947
- "modulepreload",
8948
- "preload",
8949
- "icon",
8950
- "manifest"
8951
- ].includes(value2)
8952
- )) {
8953
- continue;
8954
- }
8955
- }
8956
- const value = readAttribute(
8957
- tag,
8958
- tagName === "script" ? "src" : "href"
8959
- )?.trim();
8960
- if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
8961
- const resolved = new URL(value, base);
8962
- if (resolved.origin !== base.origin) {
8963
- if (tagName === "script" || linkRel.some(
8964
- (value2) => ["stylesheet", "modulepreload", "preload"].includes(value2)
8965
- )) {
8966
- unsupportedExternal.add(resolved.toString());
8967
- }
8968
- continue;
8969
- }
8970
- local.add(resolved.pathname);
8971
- if (tagName === "script") localExecutable.add(resolved.pathname);
8972
- }
8973
- return {
8974
- local: Array.from(local).sort(),
8975
- localExecutable: Array.from(localExecutable).sort(),
8976
- unsupportedExternal: Array.from(unsupportedExternal).sort()
8977
- };
8978
- }
8979
- function resolveClientEntryAsset(clientRoot, appBasePath, requestPath) {
8980
- let decoded;
8981
- try {
8982
- decoded = decodeURIComponent(requestPath);
8983
- } catch {
8984
- return void 0;
8985
- }
8986
- const normalizedBase = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
8987
- const relative5 = decoded.startsWith(normalizedBase) ? decoded.slice(normalizedBase.length) : decoded.replace(/^\/+/, "");
8988
- if (!relative5 || relative5 === "index.html") return void 0;
8989
- const candidate = path27.resolve(clientRoot, relative5);
8990
- if (!isPathInside(clientRoot, candidate)) return void 0;
8991
- try {
8992
- const stat = fs31.lstatSync(candidate);
8993
- return stat.isFile() && !stat.isSymbolicLink() ? candidate : void 0;
8994
- } catch {
8995
- return void 0;
8996
- }
8997
- }
8998
- function extractClientArchive(archivePath, destination) {
8999
- if (fs31.existsSync(destination)) {
9000
- throw new Error("client archive destination must not already exist");
9001
- }
9002
- archiveEntries(archivePath);
9003
- rejectArchivedSymlinks(archivePath);
9004
- fs31.mkdirSync(destination, { recursive: true });
9005
- try {
9006
- const extraction = spawnSync7(
9007
- "unzip",
9008
- ["-q", archivePath, "-d", destination],
9009
- { encoding: "utf8" }
9010
- );
9011
- if (extraction.status !== 0) {
9012
- throw new Error(
9013
- `client archive extraction failed: ${extraction.stderr.trim()}`
9014
- );
9015
- }
9016
- rejectSymlinks(destination);
9017
- } catch (error) {
9018
- fs31.rmSync(destination, { recursive: true, force: true });
9019
- throw error;
9020
- }
9021
- }
9022
- async function fetchUntil(url, timeoutAt) {
9023
- while (Date.now() < timeoutAt) {
9024
- try {
9025
- return await fetch(url, {
9026
- redirect: "manual",
9027
- signal: AbortSignal.timeout(1e3)
9028
- });
9029
- } catch {
9030
- await sleep(50);
9031
- }
9032
- }
9033
- return null;
9034
- }
9035
- function stopProcessGroup(pid) {
9036
- if (!pid) return;
9037
- try {
9038
- process.kill(-pid, "SIGTERM");
9039
- } catch {
9040
- try {
9041
- process.kill(pid, "SIGTERM");
9042
- } catch {
9043
- return;
9044
- }
9045
- }
9046
- }
9047
- async function runPreviewArtifactProbe(options) {
9048
- const generationDir = path27.resolve(options.generationDir);
9049
- const runtimeRoot = fs31.mkdtempSync(
9050
- path27.join(os4.tmpdir(), "preview-artifact-probe-")
9051
- );
9052
- const coverage = {
9053
- html: false,
9054
- businessApi: false,
9055
- clientAssets: [],
9056
- actionPlugins: []
9057
- };
9058
- const reasons = [];
9059
- const registryFile = path27.join(runtimeRoot, "plugin-registry.json");
9060
- let isolatedNodeModulesPresent = false;
9061
- let child;
9062
- try {
9063
- const runtimeBundle = path27.join(runtimeRoot, "server.bundle.cjs");
9064
- fs31.copyFileSync(
9065
- path27.join(generationDir, "server.bundle.cjs"),
9066
- runtimeBundle
9067
- );
9068
- const clientRoot = path27.join(runtimeRoot, "dist", "client");
9069
- extractClientArchive(path27.join(generationDir, "client.zip"), clientRoot);
9070
- isolatedNodeModulesPresent = fs31.existsSync(
9071
- path27.join(runtimeRoot, "node_modules")
9072
- );
9073
- if (isolatedNodeModulesPresent) {
9074
- reasons.push("isolated runtime unexpectedly contains node_modules");
9075
- }
9076
- const port = await reservePort();
9077
- const timeoutAt = Date.now() + (options.timeoutMs ?? 12e4);
9078
- child = spawn2(process.execPath, [runtimeBundle], {
9079
- cwd: runtimeRoot,
9080
- detached: true,
9081
- stdio: "ignore",
9082
- env: {
9083
- ...process.env,
9084
- ...options.environment,
9085
- SERVER_PORT: String(port),
9086
- PREVIEW_ARTIFACT_PROBE_REGISTRY_FILE: registryFile
9087
- }
9088
- });
9089
- const origin = `http://127.0.0.1:${port}`;
9090
- const htmlResponse = await fetchUntil(
9091
- new URL(options.appBasePath, origin).toString(),
9092
- timeoutAt
9093
- );
9094
- coverage.html = htmlResponse?.status === 200;
9095
- if (!coverage.html) {
9096
- reasons.push(
9097
- `HTML probe failed with status ${htmlResponse?.status ?? "unreachable"}`
9098
- );
9099
- }
9100
- if (coverage.html && htmlResponse) {
9101
- const html = await htmlResponse.text();
9102
- const entryReferences = clientEntryAssetReferences(
9103
- html,
9104
- options.appBasePath
9105
- );
9106
- const entryAssets = entryReferences.local;
9107
- coverage.clientAssets = entryReferences.localExecutable;
9108
- if (entryReferences.localExecutable.length === 0) {
9109
- reasons.push("client HTML has no local executable entry asset");
9110
- for (const externalAsset of entryReferences.unsupportedExternal) {
9111
- reasons.push(
9112
- `external client entry asset is unsupported: ${externalAsset}`
9113
- );
9114
- }
9115
- }
9116
- for (const entryAsset of entryAssets) {
9117
- if (!resolveClientEntryAsset(clientRoot, options.appBasePath, entryAsset)) {
9118
- reasons.push(`client entry asset is unavailable: ${entryAsset}`);
9119
- }
9120
- }
9121
- }
9122
- const apiResponse = await fetchUntil(
9123
- new URL(options.businessApiPath, origin).toString(),
9124
- timeoutAt
9125
- );
9126
- coverage.businessApi = Boolean(
9127
- apiResponse && isPreviewBusinessResponseReady(apiResponse)
9128
- );
9129
- if (!coverage.businessApi) {
9130
- reasons.push(
9131
- `business API returned ${apiResponse?.status ?? "unreachable"}`
9132
- );
9133
- }
9134
- while (!fs31.existsSync(registryFile) && Date.now() < timeoutAt) {
9135
- await sleep(25);
9136
- }
9137
- if (fs31.existsSync(registryFile)) {
9138
- const parsed = JSON.parse(
9139
- fs31.readFileSync(registryFile, "utf8")
9140
- );
9141
- if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) {
9142
- coverage.actionPlugins = parsed.filter((value) => typeof value === "string").sort();
9143
- } else {
9144
- reasons.push("Action Plugin registry evidence is invalid");
9145
- }
9146
- } else {
9147
- reasons.push("Action Plugin registry evidence is unavailable");
9148
- }
9149
- } catch (error) {
9150
- reasons.push(error instanceof Error ? error.message : String(error));
9151
- } finally {
9152
- stopProcessGroup(child?.pid);
9153
- }
9154
- const probe = recordPreviewArtifactProbe({
9155
- generationDir,
9156
- success: reasons.length === 0,
9157
- reasons,
9158
- nodeVersion: process.version,
9159
- platform: process.platform,
9160
- arch: process.arch,
9161
- coverage
9162
- });
9163
- fs31.rmSync(runtimeRoot, { recursive: true, force: true });
9164
- return { ...probe, isolatedNodeModulesPresent };
9165
- }
9166
-
9167
- // src/commands/preview-artifact/environment.ts
9168
- import * as fs32 from "fs";
9169
- import * as path28 from "path";
9170
- var ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
9171
- function readPreviewArtifactEnvironmentFile(filePath) {
9172
- const resolved = path28.resolve(filePath);
9173
- const parsed = JSON.parse(fs32.readFileSync(resolved, "utf8"));
9174
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9175
- throw new Error("preview artifact environment file must contain an object");
9176
- }
9177
- const environment = {};
9178
- for (const [key, value] of Object.entries(parsed)) {
9179
- if (!ENVIRONMENT_NAME.test(key) || typeof value !== "string") {
9180
- throw new Error(`invalid environment variable in ${resolved}: ${key}`);
9181
- }
9182
- environment[key] = value;
9183
- }
9184
- return environment;
9185
- }
9186
- function previewArtifactRuntimeEnvironment(environment) {
9187
- return {
9188
- ...environment,
9189
- NODE_ENV: environment?.NODE_ENV ?? "production",
9190
- // Probe and switcher connect through 127.0.0.1. Linux may resolve
9191
- // `localhost` to IPv6-only ::1, so the isolated Nest process must bind to
9192
- // the same explicit private endpoint instead of inheriting app defaults.
9193
- SERVER_HOST: "127.0.0.1"
9194
- };
9195
- }
9196
-
9197
- // src/commands/preview-artifact/store.ts
9198
- import * as fs33 from "fs";
9199
- import * as path29 from "path";
9200
- var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9201
- function readCurrentPointer(storeRoot) {
9202
- try {
9203
- const pointer = readJson3(
9204
- path29.join(storeRoot, "current.json")
9205
- );
9206
- return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) ? pointer : void 0;
9207
- } catch {
9208
- return void 0;
9209
- }
9210
- }
9211
- function readJson3(filePath) {
9212
- return JSON.parse(fs33.readFileSync(filePath, "utf8"));
9213
- }
9214
- function writeJsonAtomic2(filePath, value) {
9215
- fs33.mkdirSync(path29.dirname(filePath), { recursive: true });
9216
- const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9217
- fs33.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9218
- `, {
9219
- mode: 384
9220
- });
9221
- fs33.renameSync(temporaryPath, filePath);
9222
- }
9223
- function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
9224
- const retained = new Set(retainedGenerationIds);
9225
- for (const entry of fs33.readdirSync(generationsRoot, {
9226
- withFileTypes: true
9227
- })) {
9228
- if (retained.has(entry.name)) continue;
9229
- try {
9230
- fs33.rmSync(path29.join(generationsRoot, entry.name), {
9231
- recursive: true,
9232
- force: true
9233
- });
9234
- } catch {
9235
- }
9236
- }
9237
- }
9238
- function parseNodeMajor2(nodeVersion) {
9239
- const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
9240
- return match ? Number(match[1]) : null;
9241
- }
9242
- function isPathInside2(parent, candidate) {
9243
- const relative5 = path29.relative(parent, candidate);
9244
- return relative5 !== "" && !relative5.startsWith(`..${path29.sep}`) && relative5 !== ".." && !path29.isAbsolute(relative5);
9245
- }
9246
- function validateRegularDirectory(directoryPath, label) {
9247
- try {
9248
- const stat = fs33.lstatSync(directoryPath);
9249
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
9250
- return [`${label} must be a regular directory`];
9251
- }
9252
- return [];
9253
- } catch (error) {
9254
- return [
9255
- `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}`
9256
- ];
9257
- }
9258
- }
9259
- function validateGeneration(generationDir, runtime) {
9260
- const reasons = validateRegularDirectory(
9261
- generationDir,
9262
- "generation directory"
9263
- );
9264
- if (reasons.length > 0) return { reasons };
9265
- let manifest;
9266
- let probe;
9267
- try {
9268
- manifest = readJson3(
9269
- path29.join(generationDir, "manifest.json")
9270
- );
9271
- probe = readJson3(
9272
- path29.join(generationDir, "probe.json")
9273
- );
9274
- } catch (error) {
9275
- return {
9276
- reasons: [
9277
- `generation metadata is invalid: ${error instanceof Error ? error.message : String(error)}`
9278
- ]
9279
- };
9280
- }
9281
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || manifest.protocolVersion !== PREVIEW_ARTIFACT_PROTOCOL_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9282
- reasons.push("generation schema version is incompatible");
9283
- }
9284
- if (!GENERATION_ID_PATTERN.test(manifest.generationId) || probe.generationId !== manifest.generationId) {
9285
- reasons.push("generation ID is invalid or inconsistent");
9286
- }
9287
- if (!manifest.consumable || manifest.runtimeProbeStatus !== "passed") {
9288
- reasons.push("generation is not consumable");
9289
- }
9290
- if (!probe.success) {
9291
- reasons.push("generation runtime probe did not pass");
9292
- }
9293
- if (!Array.isArray(probe.coverage?.clientAssets) || probe.coverage.clientAssets.length === 0) {
9294
- reasons.push("generation client asset evidence is invalid");
9295
- }
9296
- if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
9297
- reasons.push("build environment identity is invalid");
9298
- }
9299
- const serverPath = path29.join(generationDir, "server.bundle.cjs");
9300
- const clientPath = path29.join(generationDir, "client.zip");
9301
- try {
9302
- if (sha256File(serverPath) !== manifest.files.server.sha256) {
9303
- reasons.push("server.bundle.cjs hash does not match manifest");
9304
- }
9305
- if (sha256File(clientPath) !== manifest.files.client.sha256) {
9306
- reasons.push("client.zip hash does not match manifest");
9307
- }
9308
- } catch (error) {
9309
- reasons.push(
9310
- `generation files are unavailable: ${error instanceof Error ? error.message : String(error)}`
9311
- );
9312
- }
9313
- if (probe.serverSha256 !== manifest.files.server.sha256 || probe.clientSha256 !== manifest.files.client.sha256) {
9314
- reasons.push("probe file identity does not match manifest");
9315
- }
9316
- if (runtime && (runtime.nodeMajor !== manifest.runtime.nodeMajor || runtime.platform !== manifest.runtime.platform || runtime.arch !== manifest.runtime.arch)) {
9317
- reasons.push("runtime identity is incompatible with generation");
9318
- }
9319
- return { manifest, reasons };
9320
- }
9321
- function publishPreviewArtifactGeneration(options) {
9322
- const storeRoot = path29.resolve(options.storeRoot);
9323
- const previousPointer = readCurrentPointer(storeRoot);
9324
- const stagingRoot = path29.join(storeRoot, ".staging");
9325
- const stagingDir = path29.resolve(options.stagingDir);
9326
- if (!isPathInside2(stagingRoot, stagingDir)) {
9327
- return {
9328
- published: false,
9329
- reasons: [
9330
- "staging directory must be inside the store .staging directory"
9331
- ]
9332
- };
9333
- }
9334
- const validation = validateGeneration(stagingDir);
9335
- if (!validation.manifest || validation.reasons.length > 0) {
9336
- return { published: false, reasons: validation.reasons };
9337
- }
9338
- const generationId = validation.manifest.generationId;
9339
- const generationsRoot = path29.join(storeRoot, "generations");
9340
- const generationDir = path29.join(generationsRoot, generationId);
9341
- fs33.mkdirSync(generationsRoot, { recursive: true });
9342
- if (fs33.existsSync(generationDir)) {
9343
- return {
9344
- published: false,
9345
- reasons: [`generation already exists: ${generationId}`]
9346
- };
9347
- }
9348
- try {
9349
- fs33.renameSync(stagingDir, generationDir);
9350
- const pointer = {
9351
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9352
- generationId,
9353
- publishedAt: (/* @__PURE__ */ new Date()).toISOString()
9354
- };
9355
- writeJsonAtomic2(path29.join(storeRoot, "current.json"), pointer);
9356
- pruneSupersededGenerations(
9357
- generationsRoot,
9358
- [generationId, previousPointer?.generationId].filter(
9359
- (value) => Boolean(value)
9360
- )
9361
- );
9362
- return { published: true, generationId, generationDir };
9363
- } catch (error) {
9364
- return {
9365
- published: false,
9366
- reasons: [
9367
- `generation publication failed: ${error instanceof Error ? error.message : String(error)}`
9368
- ]
9369
- };
9370
- }
9371
- }
9372
- function resolveCurrentPreviewArtifact(options) {
9373
- const storeRoot = path29.resolve(options.storeRoot);
9374
- let pointer;
9375
- try {
9376
- pointer = readJson3(path29.join(storeRoot, "current.json"));
9377
- } catch (error) {
9378
- return {
9379
- resolved: false,
9380
- reasons: [
9381
- `current pointer is unavailable: ${error instanceof Error ? error.message : String(error)}`
9382
- ]
9383
- };
9384
- }
9385
- if (pointer.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || !GENERATION_ID_PATTERN.test(pointer.generationId)) {
9386
- return {
9387
- resolved: false,
9388
- reasons: ["current generation ID or schema version is invalid"]
9389
- };
9390
- }
9391
- const nodeMajor = parseNodeMajor2(options.nodeVersion);
9392
- if (nodeMajor == null) {
9393
- return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
9394
- }
9395
- const generationDir = path29.join(
9396
- storeRoot,
9397
- "generations",
9398
- pointer.generationId
9399
- );
9400
- const validation = validateGeneration(generationDir, {
9401
- nodeMajor,
9402
- platform: options.platform,
9403
- arch: options.arch
9404
- });
9405
- if (!validation.manifest || validation.reasons.length > 0) {
9406
- return { resolved: false, reasons: validation.reasons };
9407
- }
9408
- if (validation.manifest.generationId !== pointer.generationId) {
9409
- return {
9410
- resolved: false,
9411
- reasons: ["current pointer does not match generation manifest"]
9412
- };
9413
- }
9414
- if (validation.manifest.buildEnvironmentSha256 !== options.buildEnvironmentSha256) {
9415
- return {
9416
- resolved: false,
9417
- reasons: ["build environment is incompatible with generation"]
9418
- };
9419
- }
9420
- return {
9421
- resolved: true,
9422
- generationId: pointer.generationId,
9423
- generationDir,
9424
- manifest: validation.manifest
9425
- };
9426
- }
9427
-
9428
- // src/commands/preview-artifact/producer.ts
9429
- function defaultGenerationId() {
9430
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
9431
- return `${timestamp}-${crypto2.randomBytes(5).toString("hex")}`;
9432
- }
9433
- function runBuildCommand(projectRoot, buildCommand, environment) {
9434
- const result = spawnSync8(buildCommand.command, buildCommand.args, {
9435
- cwd: projectRoot,
9436
- env: { ...process.env, ...environment },
9437
- encoding: "utf8",
9438
- maxBuffer: 10 * 1024 * 1024
9439
- });
9440
- if (result.status === 0) return [];
9441
- return [`production build failed with exit ${result.status ?? "unknown"}`];
9442
- }
9443
- function createClientArchive(clientDir, archivePath) {
9444
- if (!fs34.existsSync(path30.join(clientDir, "index.html"))) {
9445
- return [`client build output is missing: ${clientDir}`];
9446
- }
9447
- const result = spawnSync8("zip", ["-qry", archivePath, "."], {
9448
- cwd: clientDir,
9449
- encoding: "utf8"
9450
- });
9451
- if (result.status === 0 && fs34.existsSync(archivePath)) return [];
9452
- return [`client archive failed with exit ${result.status ?? "unknown"}`];
9453
- }
9454
- function readActionPlugins2(metadataFile) {
9455
- const metadata = JSON.parse(fs34.readFileSync(metadataFile, "utf8"));
9456
- if (!Array.isArray(metadata.actionPlugins)) return [];
9457
- return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
9458
- }
9459
- async function producePreviewArtifactGeneration(options) {
9460
- const startedAt = Date.now();
9461
- const projectRoot = path30.resolve(options.projectRoot);
9462
- const storeRoot = path30.resolve(options.storeRoot);
9463
- const generationId = options.generationId ?? defaultGenerationId();
9464
- const stagingDir = path30.join(
9465
- storeRoot,
9466
- ".staging",
9467
- `${generationId}.${process.pid}.${Date.now()}`
9468
- );
9469
- const failed = (reasons, probe) => ({
9470
- published: false,
9471
- generationId,
9472
- elapsedMs: Date.now() - startedAt,
9473
- reasons,
9474
- ...probe ? { probe } : {}
9475
- });
9476
- fs34.mkdirSync(stagingDir, { recursive: true });
9477
- try {
9478
- const buildEnvironment = previewArtifactBuildEnvironment(
9479
- options.environment,
9480
- options.appBasePath
9481
- );
9482
- const buildReasons = runBuildCommand(
9483
- projectRoot,
9484
- options.buildCommand ?? { command: "npm", args: ["run", "build:prod"] },
9485
- buildEnvironment
9486
- );
9487
- if (buildReasons.length > 0) return failed(buildReasons);
9488
- const serverOutfile = path30.join(stagingDir, "server.bundle.cjs");
9489
- const serverMetadataFile = path30.join(stagingDir, "server-build.json");
9490
- const serverResult = await buildPreviewServerArtifact({
9491
- projectRoot,
9492
- entry: "dist/main.js",
9493
- outfile: serverOutfile,
9494
- metadataFile: serverMetadataFile
9495
- });
9496
- if (!serverResult.built) return failed(serverResult.reasons);
9497
- const archiveReasons = createClientArchive(
9498
- path30.join(projectRoot, "dist", "client"),
9499
- path30.join(stagingDir, "client.zip")
9500
- );
9501
- if (archiveReasons.length > 0) return failed(archiveReasons);
9502
- const actionPlugins = readActionPlugins2(serverMetadataFile);
9503
- fs34.rmSync(serverMetadataFile, { force: true });
9504
- createPreviewArtifactManifest({
9505
- generationDir: stagingDir,
9506
- generationId,
9507
- nodeVersion: process.version,
9508
- platform: process.platform,
9509
- arch: process.arch,
9510
- buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
9511
- options.environment,
9512
- options.appBasePath
9513
- ),
9514
- actionPlugins
9515
- });
9516
- const probe = await runPreviewArtifactProbe({
9517
- generationDir: stagingDir,
9518
- appBasePath: options.appBasePath,
9519
- businessApiPath: options.businessApiPath,
9520
- environment: previewArtifactRuntimeEnvironment(options.environment)
9521
- });
9522
- if (!probe.success) return failed(probe.reasons, probe);
9523
- const promotion = promotePreviewArtifactGeneration(stagingDir);
9524
- if (!promotion.promoted) return failed(promotion.reasons, probe);
9525
- const publication = publishPreviewArtifactGeneration({
9526
- storeRoot,
9527
- stagingDir
9528
- });
9529
- if (!publication.published) return failed(publication.reasons, probe);
9530
- return {
9531
- published: true,
9532
- generationId,
9533
- generationDir: publication.generationDir,
9534
- elapsedMs: Date.now() - startedAt,
9535
- probe
9536
- };
9537
- } catch (error) {
9538
- return failed([error instanceof Error ? error.message : String(error)]);
9539
- } finally {
9540
- fs34.rmSync(stagingDir, { recursive: true, force: true });
9541
- }
9542
- }
9543
-
9544
- // src/commands/preview-artifact/runtime.ts
9545
- import * as fs36 from "fs";
9546
- import * as net3 from "net";
9547
- import * as path32 from "path";
9548
- import { spawn as spawn3 } from "child_process";
9549
-
9550
- // src/commands/preview-artifact/switcher.ts
9551
- import * as http from "http";
9552
- import * as net2 from "net";
9553
- import * as fs35 from "fs";
9554
- import * as path31 from "path";
9555
- function targetOrigin(target) {
9556
- return `http://${target.host}:${target.port}`;
9557
- }
9558
- function proxyHttpRequest(target, request2, response, fallback) {
9559
- const upstream = http.request(
9560
- {
9561
- host: target.host,
9562
- port: target.port,
9563
- method: request2.method,
9564
- path: request2.url,
9565
- headers: request2.headers
9566
- },
9567
- (upstreamResponse) => {
9568
- const upstreamContentType = upstreamResponse.headers["content-type"];
9569
- const isHtmlFallback = typeof upstreamContentType === "string" && upstreamContentType.toLowerCase().includes("text/html");
9570
- if (fallback && (upstreamResponse.statusCode === 404 || isHtmlFallback)) {
9571
- upstreamResponse.resume();
9572
- fallback();
9573
- return;
9574
- }
9575
- response.writeHead(
9576
- upstreamResponse.statusCode ?? 502,
9577
- upstreamResponse.headers
9578
- );
9579
- upstreamResponse.pipe(response);
9580
- }
9581
- );
9582
- upstream.on("error", (error) => {
9583
- if (!response.headersSent) {
9584
- response.writeHead(502, { "content-type": "application/json" });
9585
- }
9586
- response.end(
9587
- JSON.stringify({
9588
- ready: false,
9589
- error: error.code ?? "upstream"
9590
- })
9591
- );
9592
- });
9593
- request2.pipe(upstream);
9594
- }
9595
- function cachedAssetFile(request2, cachedAssetFiles) {
9596
- if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
9597
- if (!cachedAssetFiles) return void 0;
9598
- const pathname = new URL(request2.url ?? "/", "http://preview.local").pathname;
9599
- return cachedAssetFiles.get(pathname);
9600
- }
9601
- function contentType(filePath) {
9602
- switch (path31.extname(filePath).toLowerCase()) {
9603
- case ".js":
9604
- case ".mjs":
9605
- return "text/javascript; charset=utf-8";
9606
- case ".css":
9607
- return "text/css; charset=utf-8";
9608
- case ".json":
9609
- case ".map":
9610
- return "application/json; charset=utf-8";
9611
- case ".svg":
9612
- return "image/svg+xml";
9613
- case ".png":
9614
- return "image/png";
9615
- case ".jpg":
9616
- case ".jpeg":
9617
- return "image/jpeg";
9618
- case ".gif":
9619
- return "image/gif";
9620
- case ".webp":
9621
- return "image/webp";
9622
- case ".ico":
9623
- return "image/x-icon";
9624
- case ".woff":
9625
- return "font/woff";
9626
- case ".woff2":
9627
- return "font/woff2";
9628
- case ".ttf":
9629
- return "font/ttf";
9630
- case ".wasm":
9631
- return "application/wasm";
9632
- default:
9633
- return "application/octet-stream";
9634
- }
9635
- }
9636
- function serveCachedAsset(request2, response, filePath) {
9637
- let stat;
9638
- try {
9639
- stat = fs35.lstatSync(filePath);
9640
- } catch {
9641
- response.writeHead(404).end();
9642
- return;
9643
- }
9644
- if (!stat.isFile() || stat.isSymbolicLink()) {
9645
- response.writeHead(404).end();
9646
- return;
9647
- }
9648
- response.writeHead(200, {
9649
- "content-type": contentType(filePath),
9650
- "content-length": stat.size,
9651
- "cache-control": "no-store"
9652
- });
9653
- if (request2.method === "HEAD") {
9654
- response.end();
9655
- return;
9656
- }
9657
- const stream = fs35.createReadStream(filePath);
9658
- stream.on("error", () => response.destroy());
9659
- stream.pipe(response);
9660
- }
9661
- function proxyUpgrade(target, request2, socket, head) {
9662
- const upstream = net2.connect(target.port, target.host, () => {
9663
- const headers = Object.entries(request2.headers).flatMap(([name, value]) => {
9664
- if (Array.isArray(value)) return value.map((item) => `${name}: ${item}`);
9665
- return value == null ? [] : [`${name}: ${value}`];
9666
- }).join("\r\n");
9667
- upstream.write(
9668
- `${request2.method ?? "GET"} ${request2.url ?? "/"} HTTP/${request2.httpVersion}\r
9669
- ${headers}\r
9670
- \r
9671
- `
9672
- );
9673
- if (head.length > 0) upstream.write(head);
9674
- socket.pipe(upstream).pipe(socket);
9675
- });
9676
- upstream.on("error", () => socket.destroy());
9677
- socket.on("error", () => upstream.destroy());
9678
- }
9679
- async function freshTargetReady(target, healthPath, businessPath) {
9680
- try {
9681
- const response = await fetch(new URL(healthPath, targetOrigin(target)), {
9682
- signal: AbortSignal.timeout(1e3)
9683
- });
9684
- if (response.status !== 200) return false;
9685
- const body = await response.json();
9686
- if (body.ready !== true) return false;
9687
- const businessResponse = await fetch(
9688
- new URL(businessPath, targetOrigin(target)),
9689
- { signal: AbortSignal.timeout(1e3), redirect: "manual" }
9690
- );
9691
- return isPreviewBusinessResponseReady(businessResponse);
9692
- } catch {
9693
- return false;
9694
- }
9695
- }
9696
- function createPreviewArtifactSwitcher(options) {
9697
- const listenHost = options.listenHost ?? "127.0.0.1";
9698
- const healthPath = options.freshHealthPath ?? "/dev/health";
9699
- const pollIntervalMs = options.pollIntervalMs ?? 100;
9700
- let active = "cached";
9701
- let closed = false;
9702
- let pollTimer;
9703
- let settleCutover;
9704
- const cutover = new Promise((resolve9) => {
9705
- settleCutover = resolve9;
9706
- });
9707
- const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
9708
- const server = http.createServer((request2, response) => {
9709
- if (request2.url?.split("?")[0] === "/dev/health") {
9710
- response.writeHead(200, { "content-type": "application/json" });
9711
- response.end(
9712
- JSON.stringify({
9713
- ready: true,
9714
- active,
9715
- generationId: options.generationId
9716
- })
9717
- );
9718
- return;
9719
- }
9720
- const assetFile = cachedAssetFile(request2, options.cachedAssetFiles);
9721
- if (active === "cached" && assetFile) {
9722
- serveCachedAsset(request2, response, assetFile);
9723
- return;
9724
- }
9725
- const target = currentTarget();
9726
- const fallback = active === "fresh" && assetFile ? () => serveCachedAsset(request2, response, assetFile) : void 0;
9727
- proxyHttpRequest(target, request2, response, fallback);
9728
- });
9729
- server.on("upgrade", (request2, socket, head) => {
9730
- proxyUpgrade(currentTarget(), request2, socket, head);
9731
- });
9732
- const schedulePoll = () => {
9733
- if (closed || active === "fresh") return;
9734
- pollTimer = setTimeout(async () => {
9735
- if (await freshTargetReady(
9736
- options.freshTarget,
9737
- healthPath,
9738
- options.freshBusinessPath
9739
- )) {
9740
- active = "fresh";
9741
- settleCutover?.({
9742
- switched: true,
9743
- active,
9744
- at: (/* @__PURE__ */ new Date()).toISOString()
9745
- });
9746
- settleCutover = void 0;
9747
- return;
9748
- }
9749
- schedulePoll();
9750
- }, pollIntervalMs);
9751
- pollTimer.unref();
9752
- };
9753
- return {
9754
- get active() {
9755
- return active;
9756
- },
9757
- cutover,
9758
- start() {
9759
- return new Promise((resolve9, reject) => {
9760
- server.once("error", reject);
9761
- server.listen(options.listenPort, listenHost, () => {
9762
- const address = server.address();
9763
- if (!address || typeof address === "string") {
9764
- reject(new Error("switcher did not expose a TCP address"));
9765
- return;
9766
- }
9767
- schedulePoll();
9768
- resolve9({
9769
- host: listenHost,
9770
- port: address.port,
9771
- origin: `http://${listenHost}:${address.port}`
9772
- });
9773
- });
9774
- });
9775
- },
9776
- close() {
9777
- closed = true;
9778
- if (pollTimer) clearTimeout(pollTimer);
9779
- if (settleCutover) {
9780
- settleCutover({
9781
- switched: false,
9782
- active,
9783
- at: (/* @__PURE__ */ new Date()).toISOString()
9784
- });
9785
- settleCutover = void 0;
9786
- }
9787
- return new Promise((resolve9) => {
9788
- server.close(() => resolve9());
9789
- server.closeAllConnections?.();
9790
- });
9791
- }
9792
- };
9793
- }
9794
-
9795
- // src/commands/preview-artifact/runtime.ts
9796
- function sleep2(ms) {
9797
- return new Promise((resolve9) => setTimeout(resolve9, ms));
9798
- }
9799
- async function reservePort2() {
9800
- return new Promise((resolve9, reject) => {
9801
- const server = net3.createServer();
9802
- server.unref();
9803
- server.once("error", reject);
9804
- server.listen(0, "127.0.0.1", () => {
9805
- const address = server.address();
9806
- if (!address || typeof address === "string") {
9807
- server.close(() => reject(new Error("unable to reserve runtime port")));
9808
- return;
9809
- }
9810
- server.close((error) => {
9811
- if (error) reject(error);
9812
- else resolve9(address.port);
9813
- });
9814
- });
9815
- });
9816
- }
9817
- function stopProcessGroup2(child) {
9818
- if (!child?.pid) return;
9819
- try {
9820
- process.kill(-child.pid, "SIGTERM");
9821
- } catch {
9822
- try {
9823
- child.kill("SIGTERM");
9824
- } catch {
9825
- return;
9826
- }
9827
- }
9828
- }
9829
- function isPathInside3(parent, candidate) {
9830
- const relative5 = path32.relative(parent, candidate);
9831
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative5);
9832
- }
9833
- function ensureRegularRuntimeRoot(runtimeRoot) {
9834
- fs36.mkdirSync(runtimeRoot, { recursive: true });
9835
- const stat = fs36.lstatSync(runtimeRoot);
9836
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
9837
- throw new Error("runtime root must be a regular directory");
9838
- }
9839
- }
9840
- function collectCachedAssetFiles(clientRoot, appBasePath) {
9841
- const files = /* @__PURE__ */ new Map();
9842
- const basePath = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
9843
- const visit = (directory) => {
9844
- for (const entry of fs36.readdirSync(directory, { withFileTypes: true })) {
9845
- const absolute = path32.join(directory, entry.name);
9846
- if (entry.isDirectory()) {
9847
- visit(absolute);
9848
- continue;
9849
- }
9850
- if (!entry.isFile() || entry.isSymbolicLink()) continue;
9851
- const relative5 = path32.relative(clientRoot, absolute).split(path32.sep).join("/");
9852
- if (relative5 === "index.html") continue;
9853
- files.set(
9854
- new URL(`/${relative5}`, "http://preview.local").pathname,
9855
- absolute
9856
- );
9857
- files.set(
9858
- new URL(relative5, `http://preview.local${basePath}`).pathname,
9859
- absolute
9860
- );
9861
- }
9862
- };
9863
- visit(clientRoot);
9864
- return files;
9865
- }
9866
- function writeMarkerAtomic(markerFile, marker) {
9867
- fs36.mkdirSync(path32.dirname(markerFile), { recursive: true });
9868
- const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
9869
- fs36.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
9870
- `, {
9871
- mode: 384
9872
- });
9873
- fs36.renameSync(temporaryFile, markerFile);
9874
- }
9875
- async function waitForCachedRuntime(origin, appBasePath, businessApiPath, timeoutMs, child) {
9876
- const timeoutAt = Date.now() + timeoutMs;
9877
- while (Date.now() < timeoutAt) {
9878
- if (child.exitCode != null || child.signalCode != null) {
9879
- throw new Error(
9880
- `cached runtime exited before readiness: ${child.exitCode ?? child.signalCode}`
9881
- );
9882
- }
9883
- try {
9884
- const response = await fetch(new URL(appBasePath, origin), {
9885
- redirect: "manual",
9886
- signal: AbortSignal.timeout(1e3)
9887
- });
9888
- if (response.status === 200) {
9889
- const businessResponse = await fetch(new URL(businessApiPath, origin), {
9890
- redirect: "manual",
9891
- signal: AbortSignal.timeout(1e3)
9892
- });
9893
- if (isPreviewBusinessResponseReady(businessResponse)) {
9894
- return;
9895
- }
9896
- }
9897
- } catch {
9898
- }
9899
- await sleep2(50);
9900
- }
9901
- throw new Error("cached runtime readiness timed out");
9902
- }
9903
- async function startPreviewArtifactRuntime(options) {
9904
- const resolved = resolveCurrentPreviewArtifact({
9905
- storeRoot: options.storeRoot,
9906
- nodeVersion: process.version,
9907
- platform: process.platform,
9908
- arch: process.arch,
9909
- buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
9910
- options.environment,
9911
- options.appBasePath
9912
- )
9913
- });
9914
- if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
9915
- const runtimeRoot = path32.resolve(options.runtimeRoot);
9916
- const markerFile = path32.resolve(options.markerFile);
9917
- let runtimeDir;
9918
- let cachedRuntime;
9919
- let switcher;
9920
- let closed = false;
9921
- const cleanup = async () => {
9922
- if (closed) return;
9923
- closed = true;
9924
- stopProcessGroup2(cachedRuntime);
9925
- await switcher?.close();
9926
- fs36.rmSync(markerFile, { force: true });
9927
- if (runtimeDir) fs36.rmSync(runtimeDir, { recursive: true, force: true });
9928
- };
9929
- try {
9930
- ensureRegularRuntimeRoot(runtimeRoot);
9931
- if (!isPathInside3(runtimeRoot, markerFile)) {
9932
- throw new Error("runtime marker must be inside the runtime root");
9933
- }
9934
- runtimeDir = fs36.mkdtempSync(
9935
- path32.join(runtimeRoot, `${resolved.generationId}-`)
9936
- );
9937
- const runtimeBundle = path32.join(runtimeDir, "server.bundle.cjs");
9938
- fs36.copyFileSync(
9939
- path32.join(resolved.generationDir, "server.bundle.cjs"),
9940
- runtimeBundle
9941
- );
9942
- const clientRoot = path32.join(runtimeDir, "dist", "client");
9943
- extractClientArchive(
9944
- path32.join(resolved.generationDir, "client.zip"),
9945
- clientRoot
9946
- );
9947
- const cachedServerPort = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
9948
- cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
9949
- cwd: runtimeDir,
9950
- detached: true,
9951
- stdio: "ignore",
9952
- env: {
9953
- ...process.env,
9954
- ...previewArtifactRuntimeEnvironment(options.environment),
9955
- SERVER_PORT: String(cachedServerPort)
9956
- }
9957
- });
9958
- const cachedOrigin = `http://127.0.0.1:${cachedServerPort}`;
9959
- await waitForCachedRuntime(
9960
- cachedOrigin,
9961
- options.appBasePath,
9962
- options.businessApiPath,
9963
- options.startupTimeoutMs ?? 3e4,
9964
- cachedRuntime
9965
- );
9966
- switcher = createPreviewArtifactSwitcher({
9967
- listenHost: options.listenHost,
9968
- listenPort: options.listenPort,
9969
- generationId: resolved.generationId,
9970
- cachedTarget: { host: "127.0.0.1", port: cachedServerPort },
9971
- freshTarget: { host: "127.0.0.1", port: options.freshClientPort },
9972
- freshBusinessPath: options.businessApiPath,
9973
- cachedAssetFiles: collectCachedAssetFiles(
9974
- clientRoot,
9975
- options.appBasePath
9976
- ),
9977
- pollIntervalMs: options.pollIntervalMs
9978
- });
9979
- const address = await switcher.start();
9980
- const runtimeMarker = {
9981
- schemaVersion: 1,
9982
- status: "cached-serving",
9983
- generationId: resolved.generationId,
9984
- publicPort: address.port,
9985
- cachedServerPort,
9986
- freshServerPort: options.freshServerPort,
9987
- freshClientPort: options.freshClientPort,
9988
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
9989
- };
9990
- writeMarkerAtomic(markerFile, runtimeMarker);
9991
- const cutover = switcher.cutover.then(async (result) => {
9992
- if (result.switched) {
9993
- runtimeMarker.status = "fresh-serving";
9994
- runtimeMarker.cutoverAt = result.at;
9995
- writeMarkerAtomic(markerFile, runtimeMarker);
9996
- }
9997
- return result;
9998
- });
9999
- return {
10000
- started: true,
10001
- generationId: resolved.generationId,
10002
- origin: address.origin,
10003
- port: address.port,
10004
- runtimeDir,
10005
- cutover,
10006
- close: cleanup
10007
- };
10008
- } catch (error) {
10009
- await cleanup();
10010
- return {
10011
- started: false,
10012
- reasons: [error instanceof Error ? error.message : String(error)]
10013
- };
10014
- }
10015
- }
10016
-
10017
8101
  // src/commands/build/index.ts
10018
8102
  var getTokenCommand = {
10019
8103
  name: "get-token",
10020
8104
  description: "Get artifact upload credential (STI token)",
10021
8105
  register(program) {
10022
- 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(
10023
- async (options) => {
10024
- await getToken(options);
10025
- }
10026
- );
8106
+ 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) => {
8107
+ await getToken(options);
8108
+ });
10027
8109
  }
10028
8110
  };
10029
8111
  var uploadStaticCommand = {
10030
8112
  name: "upload-static",
10031
8113
  description: "Upload shared/static files to TOS",
10032
8114
  register(program) {
10033
- program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option(
10034
- "--static-dir <dir>",
10035
- "Static files directory",
10036
- UPLOAD_STATIC_DEFAULTS.staticDir
10037
- ).option(
10038
- "--tosutil-path <path>",
10039
- "Path to tosutil binary",
10040
- UPLOAD_STATIC_DEFAULTS.tosutilPath
10041
- ).option(
10042
- "--endpoint <endpoint>",
10043
- "TOS endpoint",
10044
- UPLOAD_STATIC_DEFAULTS.endpoint
10045
- ).option("--region <region>", "TOS region", UPLOAD_STATIC_DEFAULTS.region).action(async (options) => {
8115
+ 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) => {
10046
8116
  await uploadStatic(options);
10047
8117
  });
10048
8118
  }
@@ -10056,142 +8126,10 @@ var preUploadStaticCommand = {
10056
8126
  });
10057
8127
  }
10058
8128
  };
10059
- var previewServerArtifactCommand = {
10060
- name: "preview-server-artifact",
10061
- description: "Build and audit a single-file Nest preview server artifact",
10062
- register(program) {
10063
- program.command(this.name).description(this.description).option("--project-root <dir>", "Application project root", process.cwd()).option("--entry <file>", "Compiled Nest entry", "dist/main.js").option(
10064
- "--outfile <file>",
10065
- "Output bundle",
10066
- ".preview-artifact/server.bundle.cjs"
10067
- ).option("--metadata-file <file>", "Output build metadata file").action(
10068
- async (options) => {
10069
- const result = await buildPreviewServerArtifact(options);
10070
- console.log(JSON.stringify(result));
10071
- if (!result.built) {
10072
- process.exitCode = 2;
10073
- }
10074
- }
10075
- );
10076
- }
10077
- };
10078
- var previewArtifactGenerationCommand = {
10079
- name: "preview-artifact-generation",
10080
- description: "Build, probe, promote, and atomically publish a Preview Artifact generation",
10081
- register(program) {
10082
- 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(
10083
- "--business-api-path <path>",
10084
- "Business API probe path that must not return 404/5xx"
10085
- ).requiredOption(
10086
- "--environment-file <file>",
10087
- "JSON application environment passed only to build/probe children"
10088
- ).action(
10089
- async (options) => {
10090
- const result = await producePreviewArtifactGeneration({
10091
- ...options,
10092
- environment: readPreviewArtifactEnvironmentFile(
10093
- options.environmentFile
10094
- )
10095
- });
10096
- console.log(JSON.stringify(result));
10097
- if (!result.published) process.exitCode = 2;
10098
- }
10099
- );
10100
- }
10101
- };
10102
- function parseTcpPort(value) {
10103
- const port = Number(value);
10104
- if (!Number.isInteger(port) || port < 0 || port > 65535) {
10105
- throw new Error(`invalid TCP port: ${value}`);
10106
- }
10107
- return port;
10108
- }
10109
- var previewArtifactRuntimeCommand = {
10110
- name: "preview-artifact-runtime",
10111
- description: "Serve a verified Preview Artifact and switch to the fresh dev service",
10112
- register(program) {
10113
- program.command(this.name).description(this.description).requiredOption("--store-root <dir>", "Immutable generation store root").option(
10114
- "--runtime-root <dir>",
10115
- "Ephemeral runtime root",
10116
- "/tmp/preview-artifact"
10117
- ).option(
10118
- "--marker-file <file>",
10119
- "Atomic runtime marker file",
10120
- "/tmp/preview-artifact/runtime.json"
10121
- ).option(
10122
- "--listen-port <port>",
10123
- "Stable public preview port",
10124
- parseTcpPort,
10125
- 8001
10126
- ).option(
10127
- "--cached-server-port <port>",
10128
- "Isolated cached Nest server port, 0 selects a free port",
10129
- parseTcpPort,
10130
- 0
10131
- ).option(
10132
- "--fresh-server-port <port>",
10133
- "Fresh Nest shadow port recorded for dev startup",
10134
- parseTcpPort,
10135
- 3001
10136
- ).option(
10137
- "--fresh-client-port <port>",
10138
- "Fresh Vite shadow port used as the cutover target",
10139
- parseTcpPort,
10140
- 8002
10141
- ).requiredOption(
10142
- "--app-base-path <path>",
10143
- "Application HTML readiness path"
10144
- ).requiredOption(
10145
- "--business-api-path <path>",
10146
- "Business API path revalidated with the current runtime environment"
10147
- ).requiredOption(
10148
- "--environment-file <file>",
10149
- "JSON application environment passed only to cached application child"
10150
- ).action(
10151
- async (options) => {
10152
- const runtime = await startPreviewArtifactRuntime({
10153
- ...options,
10154
- environment: readPreviewArtifactEnvironmentFile(
10155
- options.environmentFile
10156
- )
10157
- });
10158
- if (!runtime.started) {
10159
- console.log(JSON.stringify(runtime));
10160
- process.exitCode = 2;
10161
- return;
10162
- }
10163
- console.log(
10164
- JSON.stringify({
10165
- started: true,
10166
- generationId: runtime.generationId,
10167
- origin: runtime.origin,
10168
- runtimeDir: runtime.runtimeDir
10169
- })
10170
- );
10171
- void runtime.cutover.then((result) => {
10172
- console.log(JSON.stringify({ event: "fresh-cutover", ...result }));
10173
- });
10174
- const shutdown = async () => {
10175
- await runtime.close();
10176
- process.exit(0);
10177
- };
10178
- process.once("SIGTERM", () => void shutdown());
10179
- process.once("SIGINT", () => void shutdown());
10180
- }
10181
- );
10182
- }
10183
- };
10184
8129
  var buildCommandGroup = {
10185
8130
  name: "build",
10186
8131
  description: "Build related commands",
10187
- commands: [
10188
- getTokenCommand,
10189
- uploadStaticCommand,
10190
- preUploadStaticCommand,
10191
- previewServerArtifactCommand,
10192
- previewArtifactGenerationCommand,
10193
- previewArtifactRuntimeCommand
10194
- ]
8132
+ commands: [getTokenCommand, uploadStaticCommand, preUploadStaticCommand]
10195
8133
  };
10196
8134
 
10197
8135
  // src/commands/index.ts
@@ -10208,12 +8146,12 @@ var commands = [
10208
8146
  ];
10209
8147
 
10210
8148
  // src/index.ts
10211
- var envPath = path33.join(process.cwd(), ".env");
10212
- if (fs37.existsSync(envPath)) {
8149
+ var envPath = path25.join(process.cwd(), ".env");
8150
+ if (fs29.existsSync(envPath)) {
10213
8151
  dotenvConfig({ path: envPath });
10214
8152
  }
10215
- var __dirname = path33.dirname(fileURLToPath5(import.meta.url));
10216
- var pkg = JSON.parse(fs37.readFileSync(path33.join(__dirname, "../package.json"), "utf-8"));
8153
+ var __dirname = path25.dirname(fileURLToPath5(import.meta.url));
8154
+ var pkg = JSON.parse(fs29.readFileSync(path25.join(__dirname, "../package.json"), "utf-8"));
10217
8155
  var cli = new FullstackCLI(pkg.version);
10218
8156
  cli.useAll(commands);
10219
8157
  cli.run();