@lark-apaas/fullstack-cli 1.1.59-alpha.20260804110530 → 1.1.59-alpha.20260818101742

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 fs38 from "fs";
3
- import path34 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
 
@@ -847,6 +847,117 @@ var tweakImportsTransform = {
847
847
  }
848
848
  };
849
849
 
850
+ // src/commands/db/gen-dbschema/transforms/ast/fix-opclass.ts
851
+ import { Node as Node9 } from "ts-morph";
852
+ var TYPE_TO_OPCLASS = {
853
+ uuid: "uuid_ops",
854
+ date: "date_ops",
855
+ varchar: "text_ops",
856
+ text: "text_ops",
857
+ boolean: "bool_ops",
858
+ integer: "int4_ops",
859
+ serial: "int4_ops",
860
+ numeric: "numeric_ops",
861
+ jsonb: "jsonb_ops",
862
+ customTimestamptz: "timestamptz_ops",
863
+ bigint: "int8_ops",
864
+ smallint: "int2_ops",
865
+ real: "float4_ops",
866
+ doublePrecision: "float8_ops"
867
+ };
868
+ var fixOpclassTransform = {
869
+ name: "fix-opclass",
870
+ transform(ctx) {
871
+ const { sourceFile } = ctx;
872
+ sourceFile.forEachDescendant((node) => {
873
+ if (!Node9.isCallExpression(node)) return;
874
+ const expr = node.getExpression();
875
+ if (!Node9.isIdentifier(expr)) return;
876
+ const fnName = expr.getText();
877
+ if (fnName !== "pgTable") return;
878
+ const args = node.getArguments();
879
+ if (args.length < 3) return;
880
+ const tableColumns = collectTableColumns(args[1]);
881
+ if (!tableColumns.size) return;
882
+ const indexFn = args[2];
883
+ if (!Node9.isArrowFunction(indexFn)) return;
884
+ fixOpclassInIndexes(indexFn, tableColumns);
885
+ });
886
+ }
887
+ };
888
+ function collectTableColumns(columnsArg) {
889
+ const columns = /* @__PURE__ */ new Map();
890
+ if (!Node9.isObjectLiteralExpression(columnsArg)) return columns;
891
+ for (const prop of columnsArg.getProperties()) {
892
+ if (!Node9.isPropertyAssignment(prop)) continue;
893
+ const colName = prop.getName();
894
+ const initializer = prop.getInitializer();
895
+ if (!initializer) continue;
896
+ const colType = extractColumnType(initializer);
897
+ if (colType) {
898
+ columns.set(colName, colType);
899
+ }
900
+ }
901
+ return columns;
902
+ }
903
+ function extractColumnType(node) {
904
+ const text = node.getText();
905
+ for (const typeName of Object.keys(TYPE_TO_OPCLASS)) {
906
+ const pattern = new RegExp(`\\b${typeName}\\s*\\(`);
907
+ if (pattern.test(text)) {
908
+ return typeName;
909
+ }
910
+ }
911
+ return void 0;
912
+ }
913
+ function fixOpclassInIndexes(indexFn, tableColumns) {
914
+ const paramName = getArrowParam(indexFn);
915
+ if (!paramName) return;
916
+ indexFn.forEachDescendant((node) => {
917
+ if (!Node9.isCallExpression(node)) return;
918
+ const expr = node.getExpression();
919
+ if (!Node9.isPropertyAccessExpression(expr)) return;
920
+ if (expr.getName() !== "op") return;
921
+ const opArgs = node.getArguments();
922
+ if (opArgs.length !== 1) return;
923
+ const currentOp = opArgs[0];
924
+ if (!Node9.isStringLiteral(currentOp)) return;
925
+ const colName = resolveColumnName(expr.getExpression(), paramName);
926
+ if (!colName) return;
927
+ const colType = tableColumns.get(colName);
928
+ if (!colType) return;
929
+ const correctOp = TYPE_TO_OPCLASS[colType];
930
+ if (!correctOp) return;
931
+ const currentValue = currentOp.getLiteralValue();
932
+ if (currentValue !== correctOp) {
933
+ currentOp.setLiteralValue(correctOp);
934
+ }
935
+ });
936
+ }
937
+ function getArrowParam(node) {
938
+ if (!Node9.isArrowFunction(node)) return void 0;
939
+ const params = node.getParameters();
940
+ if (params.length !== 1) return void 0;
941
+ return params[0].getName();
942
+ }
943
+ function resolveColumnName(node, tableParam) {
944
+ if (Node9.isPropertyAccessExpression(node)) {
945
+ const obj = node.getExpression();
946
+ const propName = node.getName();
947
+ if (Node9.isIdentifier(obj) && obj.getText() === tableParam) {
948
+ return propName;
949
+ }
950
+ return resolveColumnName(obj, tableParam);
951
+ }
952
+ if (Node9.isCallExpression(node)) {
953
+ const callExpr = node.getExpression();
954
+ if (Node9.isPropertyAccessExpression(callExpr)) {
955
+ return resolveColumnName(callExpr.getExpression(), tableParam);
956
+ }
957
+ }
958
+ return void 0;
959
+ }
960
+
850
961
  // src/commands/db/gen-dbschema/transforms/ast/index.ts
851
962
  var defaultTransforms = [
852
963
  patchDefectsTransform,
@@ -865,6 +976,8 @@ var defaultTransforms = [
865
976
  // #9 Replace .defaultNow()
866
977
  removeSystemFieldsTransform,
867
978
  // #10 Remove conflicting system fields
979
+ fixOpclassTransform,
980
+ // #11 Fix multi-column index opclass mapping
868
981
  tweakImportsTransform
869
982
  // #12 Adjust imports
870
983
  ];
@@ -1901,7 +2014,7 @@ export class ${className}Module {}
1901
2014
  }
1902
2015
 
1903
2016
  // src/commands/db/gen-nest-resource/schema-parser.ts
1904
- import { Project as Project2, Node as Node9 } from "ts-morph";
2017
+ import { Project as Project2, Node as Node10 } from "ts-morph";
1905
2018
  var DrizzleSchemaParser = class {
1906
2019
  constructor(projectOptions) {
1907
2020
  this.project = new Project2(projectOptions);
@@ -1914,7 +2027,7 @@ var DrizzleSchemaParser = class {
1914
2027
  const declarations = statement.getDeclarations();
1915
2028
  for (const declaration of declarations) {
1916
2029
  const initializer = declaration.getInitializer();
1917
- if (initializer && Node9.isCallExpression(initializer)) {
2030
+ if (initializer && Node10.isCallExpression(initializer)) {
1918
2031
  const expression = initializer.getExpression();
1919
2032
  if (expression.getText() === "pgTable") {
1920
2033
  const tableInfo = this.parsePgTable(
@@ -1937,13 +2050,13 @@ var DrizzleSchemaParser = class {
1937
2050
  }
1938
2051
  const tableName = args[0].getText().replace(/['"]/g, "");
1939
2052
  const fieldsArg = args[1];
1940
- if (!Node9.isObjectLiteralExpression(fieldsArg)) {
2053
+ if (!Node10.isObjectLiteralExpression(fieldsArg)) {
1941
2054
  return null;
1942
2055
  }
1943
2056
  const fields = [];
1944
2057
  const properties = fieldsArg.getProperties();
1945
2058
  for (const prop of properties) {
1946
- if (Node9.isPropertyAssignment(prop)) {
2059
+ if (Node10.isPropertyAssignment(prop)) {
1947
2060
  const fieldName = prop.getName();
1948
2061
  const initializer = prop.getInitializer();
1949
2062
  const leadingComments = prop.getLeadingCommentRanges();
@@ -1951,7 +2064,7 @@ var DrizzleSchemaParser = class {
1951
2064
  if (leadingComments.length > 0) {
1952
2065
  comment = leadingComments.map((c) => c.getText()).join("\n").replace(/\/\//g, "").trim();
1953
2066
  }
1954
- if (initializer && Node9.isCallExpression(initializer)) {
2067
+ if (initializer && Node10.isCallExpression(initializer)) {
1955
2068
  const fieldInfo = this.parseField(fieldName, initializer, comment);
1956
2069
  fields.push(fieldInfo);
1957
2070
  }
@@ -1983,10 +2096,10 @@ var DrizzleSchemaParser = class {
1983
2096
  parseBaseType(callExpr, fieldInfo) {
1984
2097
  let current = callExpr;
1985
2098
  let baseCall = null;
1986
- while (Node9.isCallExpression(current)) {
2099
+ while (Node10.isCallExpression(current)) {
1987
2100
  baseCall = current;
1988
2101
  const expression2 = current.getExpression();
1989
- if (Node9.isPropertyAccessExpression(expression2)) {
2102
+ if (Node10.isPropertyAccessExpression(expression2)) {
1990
2103
  current = expression2.getExpression();
1991
2104
  } else {
1992
2105
  break;
@@ -1997,7 +2110,7 @@ var DrizzleSchemaParser = class {
1997
2110
  }
1998
2111
  const expression = baseCall.getExpression();
1999
2112
  let typeName = "";
2000
- if (Node9.isPropertyAccessExpression(expression)) {
2113
+ if (Node10.isPropertyAccessExpression(expression)) {
2001
2114
  typeName = expression.getName();
2002
2115
  } else {
2003
2116
  typeName = expression.getText();
@@ -2006,25 +2119,25 @@ var DrizzleSchemaParser = class {
2006
2119
  const args = baseCall.getArguments();
2007
2120
  if (args.length > 0) {
2008
2121
  const firstArg = args[0];
2009
- if (Node9.isStringLiteral(firstArg)) {
2122
+ if (Node10.isStringLiteral(firstArg)) {
2010
2123
  fieldInfo.columnName = firstArg.getLiteralText();
2011
- } else if (Node9.isObjectLiteralExpression(firstArg)) {
2124
+ } else if (Node10.isObjectLiteralExpression(firstArg)) {
2012
2125
  this.parseTypeConfig(firstArg, fieldInfo);
2013
- } else if (Node9.isArrayLiteralExpression(firstArg)) {
2126
+ } else if (Node10.isArrayLiteralExpression(firstArg)) {
2014
2127
  fieldInfo.enumValues = firstArg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
2015
2128
  }
2016
2129
  }
2017
- if (args.length > 1 && Node9.isObjectLiteralExpression(args[1])) {
2130
+ if (args.length > 1 && Node10.isObjectLiteralExpression(args[1])) {
2018
2131
  this.parseTypeConfig(args[1], fieldInfo);
2019
2132
  }
2020
2133
  }
2021
2134
  parseTypeConfig(objLiteral, fieldInfo) {
2022
- if (!Node9.isObjectLiteralExpression(objLiteral)) {
2135
+ if (!Node10.isObjectLiteralExpression(objLiteral)) {
2023
2136
  return;
2024
2137
  }
2025
2138
  const properties = objLiteral.getProperties();
2026
2139
  for (const prop of properties) {
2027
- if (Node9.isPropertyAssignment(prop)) {
2140
+ if (Node10.isPropertyAssignment(prop)) {
2028
2141
  const propName = prop.getName();
2029
2142
  const value = prop.getInitializer()?.getText();
2030
2143
  switch (propName) {
@@ -2056,9 +2169,9 @@ var DrizzleSchemaParser = class {
2056
2169
  }
2057
2170
  parseCallChain(callExpr, fieldInfo) {
2058
2171
  let current = callExpr;
2059
- while (Node9.isCallExpression(current)) {
2172
+ while (Node10.isCallExpression(current)) {
2060
2173
  const expression = current.getExpression();
2061
- if (Node9.isPropertyAccessExpression(expression)) {
2174
+ if (Node10.isPropertyAccessExpression(expression)) {
2062
2175
  const methodName = expression.getName();
2063
2176
  const args = current.getArguments();
2064
2177
  switch (methodName) {
@@ -2154,9 +2267,9 @@ async function parseAndGenerateNestResourceTemplate(options) {
2154
2267
  var require2 = createRequire(import.meta.url);
2155
2268
  async function run(options = {}) {
2156
2269
  let exitCode = 0;
2157
- const envPath2 = path2.resolve(process.cwd(), ".env");
2158
- if (fs4.existsSync(envPath2)) {
2159
- loadEnv({ path: envPath2 });
2270
+ const envPath = path2.resolve(process.cwd(), ".env");
2271
+ if (fs4.existsSync(envPath)) {
2272
+ loadEnv({ path: envPath });
2160
2273
  console.log("[gen-db-schema] \u2713 Loaded .env file");
2161
2274
  }
2162
2275
  const databaseUrl = process.env.SUDA_DATABASE_URL;
@@ -2455,6 +2568,12 @@ function buildDefaultRules(opts) {
2455
2568
  to: ".gitignore",
2456
2569
  line: ".agent/"
2457
2570
  },
2571
+ // 7a. 确保 .gitignore 包含 .npm_cache 目录
2572
+ {
2573
+ type: "add-line",
2574
+ to: ".gitignore",
2575
+ line: ".npm_cache"
2576
+ },
2458
2577
  // 8. 同步 .spark_project 配置文件(总是覆盖)
2459
2578
  {
2460
2579
  from: "templates/.spark_project",
@@ -2507,7 +2626,9 @@ var SYNC_PROFILES = {
2507
2626
  "design-stack": defaultProfile,
2508
2627
  "nestjs-react-fullstack": defaultProfile,
2509
2628
  "vite-react": viteReactProfile,
2510
- html: emptyProfile
2629
+ html: emptyProfile,
2630
+ // design-html 与 html 同语义:空 sync + 不激活 git hooks(彻底 no-op)
2631
+ "design-html": emptyProfile
2511
2632
  };
2512
2633
  function genSyncConfig(stack, opts = {}) {
2513
2634
  const factory = stack && SYNC_PROFILES[stack] || defaultProfile;
@@ -4921,7 +5042,7 @@ var PROMPT_PATTERNS = [
4921
5042
  { pattern: /proceed\?/i, answer: "y\n" }
4922
5043
  ];
4923
5044
  async function executeShadcnAdd(registryItemPath) {
4924
- return new Promise((resolve10) => {
5045
+ return new Promise((resolve2) => {
4925
5046
  let output = "";
4926
5047
  const args = ["--yes", "shadcn@3.8.2", "add", registryItemPath];
4927
5048
  const ptyProcess = pty.spawn("npx", args, {
@@ -4947,7 +5068,7 @@ async function executeShadcnAdd(registryItemPath) {
4947
5068
  });
4948
5069
  const timeoutId = setTimeout(() => {
4949
5070
  ptyProcess.kill();
4950
- resolve10({
5071
+ resolve2({
4951
5072
  success: false,
4952
5073
  files: [],
4953
5074
  error: "\u6267\u884C\u8D85\u65F6"
@@ -4958,7 +5079,7 @@ async function executeShadcnAdd(registryItemPath) {
4958
5079
  const success = exitCode === 0;
4959
5080
  const filePaths = parseOutput(output);
4960
5081
  const files = filePaths.map(toFileInfo);
4961
- resolve10({
5082
+ resolve2({
4962
5083
  success,
4963
5084
  files,
4964
5085
  error: success ? void 0 : output || `Process exited with code ${exitCode}`
@@ -4969,12 +5090,12 @@ async function executeShadcnAdd(registryItemPath) {
4969
5090
 
4970
5091
  // src/commands/component/add.handler.ts
4971
5092
  function runActionPluginInit() {
4972
- return new Promise((resolve10) => {
5093
+ return new Promise((resolve2) => {
4973
5094
  execFile("fullstack-cli", ["action-plugin", "init"], { cwd: process.cwd(), stdio: "ignore" }, (error) => {
4974
5095
  if (error) {
4975
5096
  debug("action-plugin init \u5931\u8D25: %s", error.message);
4976
5097
  }
4977
- resolve10();
5098
+ resolve2();
4978
5099
  });
4979
5100
  });
4980
5101
  }
@@ -5641,17 +5762,17 @@ function analyzeImports(sourceFile) {
5641
5762
  }
5642
5763
 
5643
5764
  // src/commands/migration/versions/v001_capability/code-migrator/analyzers/call-site-analyzer.ts
5644
- import { SyntaxKind as SyntaxKind4 } from "ts-morph";
5765
+ import { SyntaxKind as SyntaxKind5 } from "ts-morph";
5645
5766
  function analyzeCallSites(sourceFile, imports) {
5646
5767
  const callSites = [];
5647
5768
  const importMap = /* @__PURE__ */ new Map();
5648
5769
  for (const imp of imports) {
5649
5770
  importMap.set(imp.importName, imp.capabilityId);
5650
5771
  }
5651
- const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression);
5772
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind5.CallExpression);
5652
5773
  for (const callExpr of callExpressions) {
5653
5774
  const expression = callExpr.getExpression();
5654
- if (expression.getKind() === SyntaxKind4.Identifier) {
5775
+ if (expression.getKind() === SyntaxKind5.Identifier) {
5655
5776
  const functionName = expression.getText();
5656
5777
  const capabilityId = importMap.get(functionName);
5657
5778
  if (capabilityId) {
@@ -5664,11 +5785,11 @@ function analyzeCallSites(sourceFile, imports) {
5664
5785
  text: callExpr.getText()
5665
5786
  });
5666
5787
  }
5667
- } else if (expression.getKind() === SyntaxKind4.PropertyAccessExpression) {
5668
- const propAccess = expression.asKind(SyntaxKind4.PropertyAccessExpression);
5788
+ } else if (expression.getKind() === SyntaxKind5.PropertyAccessExpression) {
5789
+ const propAccess = expression.asKind(SyntaxKind5.PropertyAccessExpression);
5669
5790
  if (propAccess) {
5670
5791
  const objectExpr = propAccess.getExpression();
5671
- if (objectExpr.getKind() === SyntaxKind4.Identifier) {
5792
+ if (objectExpr.getKind() === SyntaxKind5.Identifier) {
5672
5793
  const objectName = objectExpr.getText();
5673
5794
  const capabilityId = importMap.get(objectName);
5674
5795
  if (capabilityId) {
@@ -5897,7 +6018,7 @@ function addInjection(sourceFile) {
5897
6018
  }
5898
6019
 
5899
6020
  // src/commands/migration/versions/v001_capability/code-migrator/transformers/call-site-transformer.ts
5900
- import { SyntaxKind as SyntaxKind5 } from "ts-morph";
6021
+ import { SyntaxKind as SyntaxKind6 } from "ts-morph";
5901
6022
  var DEFAULT_ACTION_NAME = "run";
5902
6023
  function generateNewCallText(capabilityId, actionName, args) {
5903
6024
  const argsText = args.trim() || "{}";
@@ -5912,19 +6033,19 @@ function transformCallSites(sourceFile, imports) {
5912
6033
  });
5913
6034
  }
5914
6035
  let replacedCount = 0;
5915
- const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind5.CallExpression);
6036
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind6.CallExpression);
5916
6037
  const sortedCalls = [...callExpressions].sort((a, b) => b.getStart() - a.getStart());
5917
6038
  for (const callExpr of sortedCalls) {
5918
6039
  const expression = callExpr.getExpression();
5919
6040
  let importInfo;
5920
- if (expression.getKind() === SyntaxKind5.Identifier) {
6041
+ if (expression.getKind() === SyntaxKind6.Identifier) {
5921
6042
  const functionName = expression.getText();
5922
6043
  importInfo = importMap.get(functionName);
5923
- } else if (expression.getKind() === SyntaxKind5.PropertyAccessExpression) {
5924
- const propAccess = expression.asKind(SyntaxKind5.PropertyAccessExpression);
6044
+ } else if (expression.getKind() === SyntaxKind6.PropertyAccessExpression) {
6045
+ const propAccess = expression.asKind(SyntaxKind6.PropertyAccessExpression);
5925
6046
  if (propAccess) {
5926
6047
  const objectExpr = propAccess.getExpression();
5927
- if (objectExpr.getKind() === SyntaxKind5.Identifier) {
6048
+ if (objectExpr.getKind() === SyntaxKind6.Identifier) {
5928
6049
  const objectName = objectExpr.getText();
5929
6050
  importInfo = importMap.get(objectName);
5930
6051
  }
@@ -7577,8 +7698,10 @@ function sanitizeStructuredLog(value) {
7577
7698
  delete sanitized.pid;
7578
7699
  return sanitized;
7579
7700
  }
7701
+ var TRANSIENT_CONNECTION_ERROR_PATTERN = /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENETUNREACH|proxy error|\[Proxy\] (?:Error:\s*$|Error during|Connection error|Non-connection error|Headers already sent|Service (?:recovered|did not recover))/i;
7580
7702
  function hasErrorInStdLines(lines) {
7581
- const combined = lines.join("\n");
7703
+ const filtered = lines.filter((line) => !TRANSIENT_CONNECTION_ERROR_PATTERN.test(line));
7704
+ const combined = filtered.join("\n");
7582
7705
  if (!combined) return false;
7583
7706
  const strong = [
7584
7707
  /compiled with errors/i,
@@ -7597,7 +7720,7 @@ function hasErrorInStdLines(lines) {
7597
7720
  /\b0\s+errors?\b/i,
7598
7721
  /Server Error \d{3}/i
7599
7722
  ];
7600
- return lines.some((line) => {
7723
+ return filtered.some((line) => {
7601
7724
  const text = line.trim();
7602
7725
  if (!text) return false;
7603
7726
  if (ignorePatterns.some((re) => re.test(text))) return false;
@@ -8098,2838 +8221,62 @@ async function preUploadStatic(options) {
8098
8221
  }
8099
8222
  }
8100
8223
 
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 projectUsesNestjsDevtools(projectRoot) {
8265
- const distRoot = path25.join(projectRoot, "dist");
8266
- if (!fs29.existsSync(distRoot)) return false;
8267
- const pending = [distRoot];
8268
- while (pending.length > 0) {
8269
- const current = pending.pop();
8270
- if (!current) continue;
8271
- for (const entry of fs29.readdirSync(current, { withFileTypes: true })) {
8272
- const entryPath = path25.join(current, entry.name);
8273
- if (entry.isDirectory()) {
8274
- pending.push(entryPath);
8275
- } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) {
8276
- const source = fs29.readFileSync(entryPath, "utf8");
8277
- if (source.includes("@lark-apaas/fullstack-nestjs-core") && /\bDevTools(?:V2)?Module\b/.test(source)) {
8278
- return true;
8279
- }
8280
- }
8281
- }
8282
- }
8283
- return false;
8284
- }
8285
- function previewNestjsRuntimeEntryPlugin(projectRoot) {
8286
- const projectRequire = createRequire3(path25.join(projectRoot, "package.json"));
8287
- let runtimeEntry;
8288
- if (!projectUsesNestjsDevtools(projectRoot)) {
8289
- try {
8290
- runtimeEntry = projectRequire.resolve(
8291
- "@lark-apaas/fullstack-nestjs-core/runtime"
8292
- );
8293
- } catch {
8294
- runtimeEntry = void 0;
8295
- }
8296
- }
8297
- let runtimeEntryUsed = false;
8298
- return {
8299
- used: () => runtimeEntryUsed,
8300
- plugin: {
8301
- name: "preview-nestjs-runtime-entry",
8302
- setup(esbuild) {
8303
- esbuild.onResolve(
8304
- { filter: /^@lark-apaas\/fullstack-nestjs-core$/ },
8305
- () => {
8306
- if (!runtimeEntry) return void 0;
8307
- runtimeEntryUsed = true;
8308
- return { path: runtimeEntry };
8309
- }
8310
- );
8311
- }
8312
- }
8313
- };
8314
- }
8315
- function optionalDependencyStubPlugin() {
8316
- const stubbedDependencies = /* @__PURE__ */ new Set();
8317
- const escapedNames = OPTIONAL_DEPENDENCY_STUBS.map(
8318
- (name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
8319
- );
8320
- const filter = new RegExp(`^(?:${escapedNames.join("|")})$`);
8321
- return {
8322
- stubbedDependencies,
8323
- plugin: {
8324
- name: "preview-optional-dependency-stubs",
8325
- setup(esbuild) {
8326
- esbuild.onResolve({ filter }, (args) => {
8327
- stubbedDependencies.add(args.path);
8328
- return {
8329
- path: args.path,
8330
- namespace: "preview-optional-dependency"
8331
- };
8332
- });
8333
- esbuild.onLoad(
8334
- { filter: /.*/, namespace: "preview-optional-dependency" },
8335
- (args) => ({
8336
- contents: `
8337
- const error = new Error(${JSON.stringify(
8338
- `Optional dependency is unavailable in Preview Artifact: ${args.path}`
8339
- )});
8340
- error.code = 'MODULE_NOT_FOUND';
8341
- throw error;
8342
- `,
8343
- loader: "js"
8344
- })
8345
- );
8346
- }
8347
- }
8348
- };
8349
- }
8350
- function isStaticString(node) {
8351
- if (!node) {
8352
- return false;
8353
- }
8354
- if (node.type === "Literal") {
8355
- return typeof node.value === "string";
8356
- }
8357
- return node.type === "TemplateLiteral" && node.expressions.length === 0;
8358
- }
8359
- function staticPropertyName(node) {
8360
- if (node.type !== "MemberExpression") {
8361
- return void 0;
8362
- }
8363
- if (!node.computed && node.property.type === "Identifier") {
8364
- return node.property.name;
8365
- }
8366
- if (node.computed && node.property.type === "Literal" && typeof node.property.value === "string") {
8367
- return node.property.value;
8368
- }
8369
- return void 0;
8370
- }
8371
- function auditBundleClosure(outfile, stubbedDependencies) {
8372
- const ast = parse2(fs29.readFileSync(outfile, "utf8"), {
8373
- ecmaVersion: "latest",
8374
- sourceType: "script",
8375
- allowHashBang: true
8376
- });
8377
- let dynamicRequireCount = 0;
8378
- let dynamicImportCount = 0;
8379
- let createRequireCount = 0;
8380
- let requireResolveCount = 0;
8381
- let runtimeFileReadCount = 0;
8382
- const runtimeFileReads = /* @__PURE__ */ new Set([
8383
- "access",
8384
- "accessSync",
8385
- "createReadStream",
8386
- "open",
8387
- "openSync",
8388
- "readFile",
8389
- "readFileSync",
8390
- "readdir",
8391
- "readdirSync",
8392
- "stat",
8393
- "statSync"
8394
- ]);
8395
- walkSimple(ast, {
8396
- CallExpression(node) {
8397
- if (node.callee.type === "Identifier" && node.callee.name === "require" && !isStaticString(node.arguments[0])) {
8398
- dynamicRequireCount += 1;
8399
- }
8400
- if (node.callee.type === "Identifier" && node.callee.name === "createRequire" || staticPropertyName(node.callee) === "createRequire") {
8401
- createRequireCount += 1;
8402
- }
8403
- const propertyName = staticPropertyName(node.callee);
8404
- if (propertyName === "resolve" && node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && node.callee.object.name === "require") {
8405
- requireResolveCount += 1;
8406
- }
8407
- if (propertyName && runtimeFileReads.has(propertyName)) {
8408
- runtimeFileReadCount += 1;
8409
- }
8410
- },
8411
- ImportExpression(node) {
8412
- if (!isStaticString(node.source)) {
8413
- dynamicImportCount += 1;
8414
- }
8415
- }
8416
- });
8417
- const risks = [];
8418
- if (dynamicRequireCount > 0) {
8419
- risks.push(`bundle retains ${dynamicRequireCount} dynamic require call(s)`);
8420
- }
8421
- if (dynamicImportCount > 0) {
8422
- risks.push(`bundle retains ${dynamicImportCount} dynamic import call(s)`);
8423
- }
8424
- if (createRequireCount > 0 || requireResolveCount > 0) {
8425
- risks.push(
8426
- `bundle retains runtime module resolution (createRequire=${createRequireCount}, require.resolve=${requireResolveCount})`
8427
- );
8428
- }
8429
- if (runtimeFileReadCount > 0) {
8430
- risks.push(
8431
- `bundle retains ${runtimeFileReadCount} runtime filesystem read call(s)`
8432
- );
8433
- }
8434
- if (stubbedDependencies.length > 0) {
8435
- risks.push(
8436
- `bundle stubs optional dependencies: ${stubbedDependencies.join(", ")}`
8437
- );
8438
- }
8439
- return risks;
8440
- }
8441
- function collectExternalImports(metafile, outfile) {
8442
- const normalizedOutfile = path25.resolve(outfile);
8443
- const output = Object.entries(metafile.outputs).find(
8444
- ([outputPath]) => path25.resolve(outputPath) === normalizedOutfile
8445
- )?.[1];
8446
- if (!output) {
8447
- return [];
8448
- }
8449
- return Array.from(
8450
- new Set(
8451
- output.imports.filter((importRecord) => importRecord.external).map((importRecord) => importRecord.path).filter(
8452
- (importPath) => !BUILTIN_MODULES.has(importPath) && !importPath.startsWith("node:")
8453
- )
8454
- )
8455
- ).sort();
8456
- }
8457
- function formatBuildError(error) {
8458
- if (!error || typeof error !== "object" || !("errors" in error)) {
8459
- return [error instanceof Error ? error.message : String(error)];
8460
- }
8461
- const errors = error.errors;
8462
- if (!errors || errors.length === 0) {
8463
- return [error instanceof Error ? error.message : String(error)];
8464
- }
8465
- return errors.map((item) => {
8466
- const location = item.location?.file ? `${item.location.file}${item.location.line ? `:${item.location.line}` : ""}: ` : "";
8467
- return `${location}${item.text ?? "unknown esbuild error"}`;
8468
- });
8469
- }
8470
- function removePublishedArtifact(outfile, metadataFile) {
8471
- fs29.rmSync(outfile, { force: true });
8472
- fs29.rmSync(metadataFile, { force: true });
8473
- }
8474
- function bestEffortRemove(targetPath, options = {}) {
8475
- if (!targetPath) {
8476
- return;
8477
- }
8478
- try {
8479
- fs29.rmSync(targetPath, { force: true, recursive: options.recursive });
8480
- } catch {
8481
- }
8482
- }
8483
- function outputPathConflicts(entryPath, packageJsonPath, outfile, metadataFile) {
8484
- const reasons = [];
8485
- if (outfile === metadataFile) {
8486
- reasons.push("bundle \u4E0E metadata \u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u76F8\u540C");
8487
- }
8488
- const protectedInputs = /* @__PURE__ */ new Map([
8489
- [entryPath, "Nest \u7F16\u8BD1\u5165\u53E3"],
8490
- [packageJsonPath, "package.json"]
8491
- ]);
8492
- for (const [outputPath, outputName] of [
8493
- [outfile, "bundle"],
8494
- [metadataFile, "metadata"]
8495
- ]) {
8496
- const protectedName = protectedInputs.get(outputPath);
8497
- if (protectedName) {
8498
- reasons.push(
8499
- `${outputName} \u8F93\u51FA\u8DEF\u5F84\u4E0D\u80FD\u8986\u76D6 ${protectedName}: ${outputPath}`
8500
- );
8501
- }
8502
- }
8503
- return reasons;
8504
- }
8505
- async function buildPreviewServerArtifact(options) {
8506
- const startedAt = Date.now();
8507
- const projectRoot = path25.resolve(options.projectRoot);
8508
- const entryPath = toAbsolutePath(
8509
- projectRoot,
8510
- options.entry ?? "dist/main.js"
8511
- );
8512
- const outfile = toAbsolutePath(
8513
- projectRoot,
8514
- options.outfile ?? ".preview-artifact/server.bundle.cjs"
8515
- );
8516
- const metadataFile = toAbsolutePath(
8517
- projectRoot,
8518
- options.metadataFile ?? `${outfile}.meta.json`
8519
- );
8520
- const packageJsonPath = path25.join(projectRoot, "package.json");
8521
- let pluginCount = 0;
8522
- let outputCleared = false;
8523
- let phase = "\u6821\u9A8C\u8F93\u5165";
8524
- let tempRoot;
8525
- let tempOutputRoot;
8526
- let tempMetadataFile;
8527
- const failed = (reasons) => ({
8528
- built: false,
8529
- elapsedMs: Date.now() - startedAt,
8530
- pluginCount,
8531
- reasons
8532
- });
8533
- const pathConflicts = outputPathConflicts(
8534
- entryPath,
8535
- packageJsonPath,
8536
- outfile,
8537
- metadataFile
8538
- );
8539
- if (pathConflicts.length > 0) {
8540
- return failed(pathConflicts);
8541
- }
8542
- if (!fs29.existsSync(entryPath)) {
8543
- return failed([`Nest \u7F16\u8BD1\u5165\u53E3\u4E0D\u5B58\u5728: ${entryPath}`]);
8544
- }
8545
- if (!fs29.existsSync(packageJsonPath)) {
8546
- return failed([`package.json \u4E0D\u5B58\u5728: ${packageJsonPath}`]);
8547
- }
8548
- try {
8549
- phase = `\u89E3\u6790 ${packageJsonPath}`;
8550
- const packageJson = readJson(packageJsonPath);
8551
- phase = "\u89E3\u6790 Action Plugin";
8552
- const { plugins, reasons } = resolveActionPlugins(projectRoot, packageJson);
8553
- pluginCount = plugins.length;
8554
- phase = "\u6E05\u7406\u65E7 Preview Artifact";
8555
- removePublishedArtifact(outfile, metadataFile);
8556
- outputCleared = true;
8557
- if (reasons.length > 0) {
8558
- return failed(reasons);
8559
- }
8560
- phase = "\u521B\u5EFA Preview Artifact \u4E34\u65F6\u76EE\u5F55";
8561
- fs29.mkdirSync(path25.dirname(outfile), { recursive: true });
8562
- fs29.mkdirSync(path25.dirname(metadataFile), { recursive: true });
8563
- tempRoot = fs29.mkdtempSync(
8564
- path25.join(os3.tmpdir(), "preview-server-artifact-")
8565
- );
8566
- tempOutputRoot = fs29.mkdtempSync(
8567
- path25.join(path25.dirname(outfile), ".preview-server-artifact-")
8568
- );
8569
- const bootstrapPath = path25.join(tempRoot, "bootstrap.cjs");
8570
- const tempOutfile = path25.join(tempOutputRoot, path25.basename(outfile));
8571
- tempMetadataFile = path25.join(
8572
- path25.dirname(metadataFile),
8573
- `.${path25.basename(metadataFile)}.${process.pid}.${Date.now()}.tmp`
8574
- );
8575
- fs29.writeFileSync(bootstrapPath, generateBootstrap(entryPath, plugins));
8576
- const optionalDependencies = optionalDependencyStubPlugin();
8577
- const nestjsRuntimeEntry = previewNestjsRuntimeEntryPlugin(projectRoot);
8578
- const buildOptions = {
8579
- absWorkingDir: projectRoot,
8580
- entryPoints: [bootstrapPath],
8581
- outfile: tempOutfile,
8582
- bundle: true,
8583
- platform: "node",
8584
- format: "cjs",
8585
- target: "node22",
8586
- treeShaking: true,
8587
- keepNames: true,
8588
- legalComments: "none",
8589
- sourcemap: false,
8590
- metafile: true,
8591
- logLevel: "silent",
8592
- plugins: [
8593
- nestjsRuntimeEntry.plugin,
8594
- classTransformerStorageResolver(projectRoot),
8595
- optionalDependencies.plugin
8596
- ]
8597
- };
8598
- phase = "\u6267\u884C esbuild";
8599
- const buildResult2 = await build(buildOptions);
8600
- if (!buildResult2.metafile) {
8601
- throw new Error("esbuild \u672A\u8FD4\u56DE metafile");
8602
- }
8603
- const externalImports = collectExternalImports(
8604
- buildResult2.metafile,
8605
- tempOutfile
8606
- );
8607
- if (externalImports.length > 0) {
8608
- return failed([`bundle \u6B8B\u7559\u8FD0\u884C\u65F6\u4F9D\u8D56: ${externalImports.join(", ")}`]);
8609
- }
8610
- phase = "\u5BA1\u8BA1 bundle \u8FD0\u884C\u65F6\u95ED\u5305";
8611
- const stubbedOptionalDependencies = Array.from(
8612
- optionalDependencies.stubbedDependencies
8613
- ).sort();
8614
- const closureRisks = auditBundleClosure(
8615
- tempOutfile,
8616
- stubbedOptionalDependencies
8617
- );
8618
- const closureStatus = closureRisks.length === 0 ? "static-audit-clean" : "runtime-validation-required";
8619
- const metadata = {
8620
- version: 1,
8621
- entry: entryPath,
8622
- outfile,
8623
- bundleBytes: fs29.statSync(tempOutfile).size,
8624
- inputFiles: Object.keys(buildResult2.metafile.inputs).length,
8625
- externalImports,
8626
- actionPlugins: plugins.map((plugin) => plugin.name),
8627
- closureStatus,
8628
- closureRisks,
8629
- stubbedOptionalDependencies,
8630
- nestjsRuntimeEntryUsed: nestjsRuntimeEntry.used(),
8631
- runtimeProbeStatus: "not-run",
8632
- consumable: false,
8633
- elapsedMs: Date.now() - startedAt
8634
- };
8635
- phase = "\u53D1\u5E03 bundle \u4E0E metadata";
8636
- fs29.writeFileSync(
8637
- tempMetadataFile,
8638
- `${JSON.stringify(metadata, null, 2)}
8639
- `
8640
- );
8641
- fs29.renameSync(tempOutfile, outfile);
8642
- fs29.renameSync(tempMetadataFile, metadataFile);
8643
- return {
8644
- built: true,
8645
- outfile,
8646
- metadataFile,
8647
- bundleBytes: metadata.bundleBytes,
8648
- inputFiles: metadata.inputFiles,
8649
- externalImports,
8650
- pluginCount,
8651
- closureStatus,
8652
- closureRisks,
8653
- stubbedOptionalDependencies,
8654
- runtimeProbeStatus: "not-run",
8655
- consumable: false,
8656
- elapsedMs: metadata.elapsedMs
8657
- };
8658
- } catch (error) {
8659
- const reasons = formatBuildError(error).map(
8660
- (reason) => `${phase}: ${reason}`
8661
- );
8662
- if (outputCleared) {
8663
- try {
8664
- removePublishedArtifact(outfile, metadataFile);
8665
- } catch (cleanupError) {
8666
- reasons.push(
8667
- `\u6E05\u7406\u5931\u8D25\u4EA7\u7269: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`
8668
- );
8669
- }
8670
- }
8671
- return failed(reasons);
8672
- } finally {
8673
- bestEffortRemove(tempRoot, { recursive: true });
8674
- bestEffortRemove(tempOutputRoot, { recursive: true });
8675
- bestEffortRemove(tempMetadataFile);
8676
- }
8677
- }
8678
-
8679
- // src/commands/preview-artifact/producer.ts
8680
- import * as crypto4 from "crypto";
8681
- import * as fs35 from "fs";
8682
- import * as path31 from "path";
8683
- import { spawnSync as spawnSync9 } from "child_process";
8684
-
8685
- // src/commands/preview-artifact/generation.ts
8686
- import * as crypto2 from "crypto";
8687
- import * as fs31 from "fs";
8688
- import * as path27 from "path";
8689
-
8690
- // src/commands/preview-artifact/workspace-identity.ts
8691
- import * as crypto from "crypto";
8692
- import { spawnSync as spawnSync7 } from "child_process";
8693
- import * as fs30 from "fs";
8694
- import * as os4 from "os";
8695
- import * as path26 from "path";
8696
- var PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION = 1;
8697
- var WORKSPACE_IDENTITY_DOMAIN = "workspace-content:v1\n";
8698
- var WORKSPACE_BACKUP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
8699
- var GIT_OBJECT_ID_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
8700
- var SOURCE_TREE_ID_PATTERN = /^git-tree-(sha1|sha256):([a-f0-9]{40}|[a-f0-9]{64})$/;
8701
- var WORKSPACE_VERSION_PATTERN = /^sha256:[a-f0-9]{64}$/;
8702
- function normalizeSourceTreeIncludePaths(values) {
8703
- if (!values || values.length === 0) return [];
8704
- const normalized = /* @__PURE__ */ new Set();
8705
- for (const rawValue of values) {
8706
- if (typeof rawValue !== "string" || rawValue.includes("\0") || rawValue.includes("\n") || rawValue.includes("\r") || rawValue.includes("\\")) {
8707
- throw new Error(`invalid source tree include path: ${String(rawValue)}`);
8708
- }
8709
- const value = path26.posix.normalize(rawValue.trim());
8710
- if (!value || value === ".." || value.startsWith("../") || path26.posix.isAbsolute(value)) {
8711
- throw new Error(`invalid source tree include path: ${rawValue}`);
8712
- }
8713
- if (value === ".") return [];
8714
- normalized.add(value.replace(/\/$/, ""));
8715
- }
8716
- return [...normalized].sort((left, right) => left.localeCompare(right));
8717
- }
8718
- function createPreviewArtifactSourceTreeValidation(enabled, includePaths) {
8719
- return enabled ? {
8720
- enabled: true,
8721
- includePaths: normalizeSourceTreeIncludePaths(includePaths)
8722
- } : { enabled: false, includePaths: [] };
8723
- }
8724
- function previewArtifactSourceTreeValidation(identity) {
8725
- if (identity.sourceTreeValidation == null) {
8726
- return identity.sourceTreeId != null || identity.workspaceVersion != null ? { enabled: true, includePaths: [] } : { enabled: false, includePaths: [] };
8727
- }
8728
- return identity.sourceTreeValidation;
8729
- }
8730
- function samePreviewArtifactSourceTreeValidation(left, right) {
8731
- return left.enabled === right.enabled && left.includePaths.length === right.includePaths.length && left.includePaths.every(
8732
- (value, index) => value === right.includePaths[index]
8733
- );
8734
- }
8735
- function runGit(projectRoot, args, environment, allowFailure = false) {
8736
- const result = spawnSync7("git", args, {
8737
- cwd: projectRoot,
8738
- env: { ...process.env, ...environment },
8739
- encoding: "utf8",
8740
- maxBuffer: 10 * 1024 * 1024
8741
- });
8742
- if (result.status === 0) return result.stdout.trim();
8743
- if (allowFailure) return void 0;
8744
- const diagnostic = `${result.stderr ?? ""}
8745
- ${result.stdout ?? ""}`.trim().slice(-8 * 1024);
8746
- throw new Error(
8747
- `git ${args.join(" ")} failed with exit ${result.status ?? "unknown"}${diagnostic ? `: ${diagnostic}` : ""}`
8748
- );
8749
- }
8750
- function resolveGitPath(projectRoot, relativePath) {
8751
- return path26.isAbsolute(relativePath) ? relativePath : path26.resolve(projectRoot, relativePath);
8752
- }
8753
- function writeJsonAtomic(filePath, value) {
8754
- fs30.mkdirSync(path26.dirname(filePath), { recursive: true });
8755
- const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
8756
- try {
8757
- fs30.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
8758
- `, {
8759
- mode: 384
8224
+ // src/commands/build/index.ts
8225
+ var getTokenCommand = {
8226
+ name: "get-token",
8227
+ description: "Get artifact upload credential (STI token)",
8228
+ register(program) {
8229
+ 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) => {
8230
+ await getToken(options);
8760
8231
  });
8761
- fs30.renameSync(temporaryPath, filePath);
8762
- } finally {
8763
- fs30.rmSync(temporaryPath, { force: true });
8764
- }
8765
- }
8766
- function gitObjectFormat(projectRoot, treeId) {
8767
- const reported = runGit(
8768
- projectRoot,
8769
- ["rev-parse", "--show-object-format"],
8770
- void 0,
8771
- true
8772
- );
8773
- if (reported === "sha1" || reported === "sha256") return reported;
8774
- if (treeId.length === 40) return "sha1";
8775
- if (treeId.length === 64) return "sha256";
8776
- throw new Error(`unsupported Git tree object ID: ${treeId}`);
8777
- }
8778
- function workspaceVersionForSourceTreeId(sourceTreeId) {
8779
- if (!SOURCE_TREE_ID_PATTERN.test(sourceTreeId)) {
8780
- throw new Error(`invalid sourceTreeId: ${sourceTreeId}`);
8781
- }
8782
- return `sha256:${crypto.createHash("sha256").update(`${WORKSPACE_IDENTITY_DOMAIN}${sourceTreeId}`).digest("hex")}`;
8783
- }
8784
- function validatePreviewArtifactWorkspaceManifest(manifest) {
8785
- const reasons = [];
8786
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION) {
8787
- reasons.push("workspace manifest schema version is incompatible");
8788
8232
  }
8789
- reasons.push(...validatePreviewArtifactWorkspaceIdentity(manifest));
8790
- if (typeof manifest.appId !== "string" || manifest.appId.length === 0 || manifest.appId.length > 256) {
8791
- reasons.push("workspace application ID is invalid");
8233
+ };
8234
+ var uploadStaticCommand = {
8235
+ name: "upload-static",
8236
+ description: "Upload shared/static files to TOS",
8237
+ register(program) {
8238
+ 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) => {
8239
+ await uploadStatic(options);
8240
+ });
8792
8241
  }
8793
- if (typeof manifest.createdAt !== "string" || !Number.isFinite(Date.parse(manifest.createdAt))) {
8794
- reasons.push("workspace manifest creation time is invalid");
8242
+ };
8243
+ var preUploadStaticCommand = {
8244
+ name: "pre-upload-static",
8245
+ description: "Get TOS upload info and output as env vars for build.sh eval",
8246
+ register(program) {
8247
+ program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").action(async (options) => {
8248
+ await preUploadStatic(options);
8249
+ });
8795
8250
  }
8796
- return reasons;
8797
- }
8798
- function validatePreviewArtifactWorkspaceIdentity(identity) {
8799
- const reasons = [];
8800
- if (!WORKSPACE_BACKUP_ID_PATTERN.test(identity.workspaceBackupId)) {
8801
- reasons.push("workspace backup ID is invalid");
8251
+ };
8252
+ var buildCommandGroup = {
8253
+ name: "build",
8254
+ description: "Build related commands",
8255
+ commands: [getTokenCommand, uploadStaticCommand, preUploadStaticCommand]
8256
+ };
8257
+
8258
+ // src/commands/index.ts
8259
+ var commands = [
8260
+ genDbSchemaCommand,
8261
+ syncCommand,
8262
+ upgradeCommand,
8263
+ actionPluginCommandGroup,
8264
+ capabilityCommandGroup,
8265
+ componentCommandGroup,
8266
+ migrationCommand,
8267
+ readLogsCommand,
8268
+ buildCommandGroup
8269
+ ];
8270
+
8271
+ // src/index.ts
8272
+ for (const filename of [".env.local", ".env"]) {
8273
+ const envPath = path25.join(process.cwd(), filename);
8274
+ if (fs29.existsSync(envPath)) {
8275
+ dotenvConfig({ path: envPath });
8802
8276
  }
8803
- if (identity.commitId !== "UNBORN" && !GIT_OBJECT_ID_PATTERN.test(identity.commitId)) {
8804
- reasons.push("workspace commit ID is invalid");
8805
- }
8806
- const validation = previewArtifactSourceTreeValidation(identity);
8807
- let normalizedIncludePaths;
8808
- try {
8809
- normalizedIncludePaths = normalizeSourceTreeIncludePaths(
8810
- validation.includePaths
8811
- );
8812
- } catch {
8813
- reasons.push("workspace source tree include paths are invalid");
8814
- }
8815
- if (typeof validation.enabled !== "boolean" || !Array.isArray(validation.includePaths) || !normalizedIncludePaths || normalizedIncludePaths.length !== validation.includePaths.length || normalizedIncludePaths.some(
8816
- (value, index) => value !== validation.includePaths[index]
8817
- )) {
8818
- reasons.push("workspace source tree validation policy is invalid");
8819
- }
8820
- if (!validation.enabled) {
8821
- if (identity.sourceTreeId != null || identity.workspaceVersion != null) {
8822
- reasons.push(
8823
- "disabled source tree validation must not persist an identity"
8824
- );
8825
- }
8826
- return reasons;
8827
- }
8828
- if (typeof identity.sourceTreeId !== "string" || !SOURCE_TREE_ID_PATTERN.test(identity.sourceTreeId)) {
8829
- reasons.push("workspace source tree ID is invalid");
8830
- }
8831
- if (typeof identity.workspaceVersion !== "string" || !WORKSPACE_VERSION_PATTERN.test(identity.workspaceVersion)) {
8832
- reasons.push("workspace version is invalid");
8833
- } else if (typeof identity.sourceTreeId === "string") {
8834
- try {
8835
- if (workspaceVersionForSourceTreeId(identity.sourceTreeId) !== identity.workspaceVersion) {
8836
- reasons.push("workspace version does not match source tree ID");
8837
- }
8838
- } catch {
8839
- }
8840
- }
8841
- return reasons;
8842
- }
8843
- function readPreviewArtifactWorkspaceManifest(manifestFile) {
8844
- const manifest = JSON.parse(
8845
- fs30.readFileSync(path26.resolve(manifestFile), "utf8")
8846
- );
8847
- const reasons = validatePreviewArtifactWorkspaceManifest(manifest);
8848
- if (reasons.length > 0) {
8849
- throw new Error(reasons.join("; "));
8850
- }
8851
- return manifest;
8852
- }
8853
- function createPreviewArtifactWorkspaceManifest(options) {
8854
- if (!WORKSPACE_BACKUP_ID_PATTERN.test(options.workspaceBackupId)) {
8855
- throw new Error(
8856
- `invalid workspace backup ID: ${options.workspaceBackupId}`
8857
- );
8858
- }
8859
- if (!options.appId || options.appId.length > 256) {
8860
- throw new Error(`invalid application ID: ${options.appId}`);
8861
- }
8862
- const projectRoot = fs30.realpathSync(path26.resolve(options.projectRoot));
8863
- const repositoryRoot = runGit(projectRoot, ["rev-parse", "--show-toplevel"]);
8864
- if (!repositoryRoot || fs30.realpathSync(repositoryRoot) !== projectRoot) {
8865
- throw new Error("project root must be the Git repository root");
8866
- }
8867
- const sourceTreeValidation = createPreviewArtifactSourceTreeValidation(
8868
- options.validateSourceTree,
8869
- options.sourceTreeIncludePaths
8870
- );
8871
- let sourceTreeId;
8872
- let workspaceVersion;
8873
- if (sourceTreeValidation.enabled) {
8874
- const temporaryRoot = fs30.mkdtempSync(
8875
- path26.join(os4.tmpdir(), "preview-workspace-identity-")
8876
- );
8877
- try {
8878
- const temporaryIndex = path26.join(temporaryRoot, "index");
8879
- const temporaryObjects = path26.join(temporaryRoot, "objects");
8880
- fs30.mkdirSync(temporaryObjects, { recursive: true });
8881
- const gitObjectsPath = resolveGitPath(
8882
- projectRoot,
8883
- runGit(projectRoot, ["rev-parse", "--git-path", "objects"])
8884
- );
8885
- const isolatedGitEnvironment = {
8886
- GIT_INDEX_FILE: temporaryIndex,
8887
- GIT_OBJECT_DIRECTORY: temporaryObjects,
8888
- GIT_ALTERNATE_OBJECT_DIRECTORIES: gitObjectsPath,
8889
- GIT_OPTIONAL_LOCKS: "0"
8890
- };
8891
- runGit(projectRoot, ["read-tree", "--empty"], isolatedGitEnvironment);
8892
- const includedPaths = sourceTreeValidation.includePaths.length === 0 ? ["."] : sourceTreeValidation.includePaths.filter(
8893
- (includedPath) => fs30.existsSync(path26.join(projectRoot, includedPath))
8894
- );
8895
- if (includedPaths.length > 0) {
8896
- runGit(
8897
- projectRoot,
8898
- [
8899
- "add",
8900
- "-A",
8901
- "--",
8902
- ...includedPaths.map(
8903
- (includedPath) => includedPath === "." ? "." : `:(top,literal)${includedPath}`
8904
- )
8905
- ],
8906
- isolatedGitEnvironment
8907
- );
8908
- }
8909
- const treeId = runGit(
8910
- projectRoot,
8911
- ["write-tree"],
8912
- isolatedGitEnvironment
8913
- );
8914
- if (!treeId || !GIT_OBJECT_ID_PATTERN.test(treeId)) {
8915
- throw new Error(
8916
- `git write-tree returned an invalid object ID: ${treeId}`
8917
- );
8918
- }
8919
- sourceTreeId = `git-tree-${gitObjectFormat(
8920
- projectRoot,
8921
- treeId
8922
- )}:${treeId}`;
8923
- workspaceVersion = workspaceVersionForSourceTreeId(sourceTreeId);
8924
- } finally {
8925
- fs30.rmSync(temporaryRoot, { recursive: true, force: true });
8926
- }
8927
- }
8928
- const manifest = {
8929
- schemaVersion: PREVIEW_ARTIFACT_WORKSPACE_MANIFEST_SCHEMA_VERSION,
8930
- workspaceBackupId: options.workspaceBackupId,
8931
- appId: options.appId,
8932
- commitId: runGit(projectRoot, ["rev-parse", "HEAD"], void 0, true) ?? "UNBORN",
8933
- sourceTreeValidation,
8934
- ...sourceTreeId == null ? {} : { sourceTreeId },
8935
- ...workspaceVersion == null ? {} : { workspaceVersion },
8936
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
8937
- };
8938
- writeJsonAtomic(path26.resolve(options.manifestFile), manifest);
8939
- return manifest;
8940
- }
8941
-
8942
- // src/commands/preview-artifact/generation.ts
8943
- var PREVIEW_ARTIFACT_SCHEMA_VERSION = 4;
8944
- var PREVIEW_ARTIFACT_PROTOCOL_VERSION = 3;
8945
- var MANIFEST_FILE = "manifest.json";
8946
- var PROBE_FILE = "probe.json";
8947
- var SERVER_FILE = "server.bundle.cjs";
8948
- var CLIENT_FILE = "client.zip";
8949
- var RUNTIME_ONLY_ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
8950
- "CLIENT_DEV_HOST",
8951
- "CLIENT_DEV_PORT",
8952
- "FORCE_AUTHN_ACCESS_KEY",
8953
- "FORCE_AUTHN_ACCESS_SECRET",
8954
- "FORCE_AUTHN_PREVIEW_SESSION_ID",
8955
- "MIAODA_HMR_WS_TOKEN",
8956
- "MIAODA_PREVIEW_RUN_ID",
8957
- "PREVIEW_RUN_ID",
8958
- "SANDBOX_COOKIE",
8959
- "SANDBOX_ID",
8960
- "SERVER_PORT",
8961
- "preview_run_id"
8962
- ]);
8963
- var RUNTIME_ONLY_ENVIRONMENT_PREFIXES = [
8964
- "MIAODA_PREVIEW_PHASE_",
8965
- "PREVIEW_ARTIFACT_"
8966
- ];
8967
- function previewArtifactBuildEnvironment(environment, appBasePath) {
8968
- const buildEnvironment = {};
8969
- for (const [key, value] of Object.entries(environment ?? {})) {
8970
- if (typeof value === "string" && !RUNTIME_ONLY_ENVIRONMENT_KEYS.has(key) && !RUNTIME_ONLY_ENVIRONMENT_PREFIXES.some((prefix) => key.startsWith(prefix))) {
8971
- buildEnvironment[key] = value;
8972
- }
8973
- }
8974
- buildEnvironment.ASSETS_CDN_PATH = appBasePath;
8975
- buildEnvironment.CLIENT_BASE_PATH = appBasePath;
8976
- buildEnvironment.MIAODA_BUILD_TARGET = "preview-artifact";
8977
- buildEnvironment.NODE_ENV = "development";
8978
- return buildEnvironment;
8979
- }
8980
- function previewArtifactBuildEnvironmentHash(environment, appBasePath) {
8981
- const buildEnvironment = previewArtifactBuildEnvironment(
8982
- environment,
8983
- appBasePath
8984
- );
8985
- const canonical = Object.fromEntries(
8986
- Object.entries(buildEnvironment).sort(
8987
- ([left], [right]) => left.localeCompare(right)
8988
- )
8989
- );
8990
- return crypto2.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
8991
- }
8992
- function parseNodeMajor(nodeVersion) {
8993
- const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
8994
- if (!match) {
8995
- throw new Error(`invalid Node.js version: ${nodeVersion}`);
8996
- }
8997
- return Number(match[1]);
8998
- }
8999
- function sha256File(filePath) {
9000
- const hash = crypto2.createHash("sha256");
9001
- hash.update(fs31.readFileSync(filePath));
9002
- return hash.digest("hex");
9003
- }
9004
- function fileIdentity(generationDir, relativePath) {
9005
- const filePath = path27.join(generationDir, relativePath);
9006
- const stat = fs31.lstatSync(filePath);
9007
- if (!stat.isFile() || stat.isSymbolicLink()) {
9008
- throw new Error(`artifact file must be a regular file: ${relativePath}`);
9009
- }
9010
- return {
9011
- path: relativePath,
9012
- bytes: stat.size,
9013
- sha256: sha256File(filePath)
9014
- };
9015
- }
9016
- function readJson2(filePath) {
9017
- return JSON.parse(fs31.readFileSync(filePath, "utf8"));
9018
- }
9019
- function writeJsonAtomic2(filePath, value) {
9020
- fs31.mkdirSync(path27.dirname(filePath), { recursive: true });
9021
- const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9022
- fs31.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9023
- `, {
9024
- mode: 384
9025
- });
9026
- fs31.renameSync(temporaryPath, filePath);
9027
- }
9028
- function runtimeIdentity(nodeVersion, platform, arch) {
9029
- return { nodeMajor: parseNodeMajor(nodeVersion), platform, arch };
9030
- }
9031
- function sameRuntime(left, right) {
9032
- return left.nodeMajor === right.nodeMajor && left.platform === right.platform && left.arch === right.arch;
9033
- }
9034
- function createPreviewArtifactManifest(options) {
9035
- const generationDir = path27.resolve(options.generationDir);
9036
- const workspaceIdentityReasons = validatePreviewArtifactWorkspaceIdentity(
9037
- options.workspaceManifest
9038
- );
9039
- if (workspaceIdentityReasons.length > 0) {
9040
- throw new Error(workspaceIdentityReasons.join("; "));
9041
- }
9042
- const manifest = {
9043
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9044
- protocolVersion: PREVIEW_ARTIFACT_PROTOCOL_VERSION,
9045
- generationId: options.generationId,
9046
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9047
- workspaceBackupId: options.workspaceManifest.workspaceBackupId,
9048
- commitId: options.workspaceManifest.commitId,
9049
- sourceTreeValidation: options.workspaceManifest.sourceTreeValidation,
9050
- ...options.workspaceManifest.sourceTreeId == null ? {} : { sourceTreeId: options.workspaceManifest.sourceTreeId },
9051
- ...options.workspaceManifest.workspaceVersion == null ? {} : { workspaceVersion: options.workspaceManifest.workspaceVersion },
9052
- buildEnvironmentSha256: options.buildEnvironmentSha256,
9053
- runtime: runtimeIdentity(
9054
- options.nodeVersion,
9055
- options.platform,
9056
- options.arch
9057
- ),
9058
- files: {
9059
- server: fileIdentity(generationDir, SERVER_FILE),
9060
- client: fileIdentity(generationDir, CLIENT_FILE)
9061
- },
9062
- actionPlugins: Array.from(new Set(options.actionPlugins ?? [])).sort(),
9063
- runtimeProbeStatus: "not-run",
9064
- consumable: false
9065
- };
9066
- writeJsonAtomic2(path27.join(generationDir, MANIFEST_FILE), manifest);
9067
- fs31.rmSync(path27.join(generationDir, PROBE_FILE), { force: true });
9068
- return manifest;
9069
- }
9070
- function recordPreviewArtifactProbe(options) {
9071
- const generationDir = path27.resolve(options.generationDir);
9072
- const manifest = readJson2(
9073
- path27.join(generationDir, MANIFEST_FILE)
9074
- );
9075
- const probe = {
9076
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9077
- generationId: manifest.generationId,
9078
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
9079
- success: options.success,
9080
- reasons: options.reasons ?? [],
9081
- runtime: runtimeIdentity(
9082
- options.nodeVersion,
9083
- options.platform,
9084
- options.arch
9085
- ),
9086
- serverSha256: sha256File(path27.join(generationDir, SERVER_FILE)),
9087
- clientSha256: sha256File(path27.join(generationDir, CLIENT_FILE)),
9088
- coverage: {
9089
- html: options.coverage.html,
9090
- businessApi: options.coverage.businessApi,
9091
- clientAssets: Array.from(new Set(options.coverage.clientAssets)).sort(),
9092
- actionPlugins: Array.from(new Set(options.coverage.actionPlugins)).sort()
9093
- }
9094
- };
9095
- writeJsonAtomic2(path27.join(generationDir, PROBE_FILE), probe);
9096
- return probe;
9097
- }
9098
- function promotePreviewArtifactGeneration(generationDirInput) {
9099
- const generationDir = path27.resolve(generationDirInput);
9100
- const reasons = [];
9101
- let manifest;
9102
- let probe;
9103
- try {
9104
- manifest = readJson2(
9105
- path27.join(generationDir, MANIFEST_FILE)
9106
- );
9107
- probe = readJson2(
9108
- path27.join(generationDir, PROBE_FILE)
9109
- );
9110
- } catch (error) {
9111
- return {
9112
- promoted: false,
9113
- reasons: [
9114
- `probe evidence missing or invalid: ${error instanceof Error ? error.message : String(error)}`
9115
- ]
9116
- };
9117
- }
9118
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9119
- reasons.push("schema version mismatch");
9120
- }
9121
- if (probe.generationId !== manifest.generationId) {
9122
- reasons.push("probe generation identity mismatch");
9123
- }
9124
- if (!probe.success) {
9125
- reasons.push(`probe failed: ${probe.reasons.join("; ") || "unknown"}`);
9126
- }
9127
- if (!sameRuntime(manifest.runtime, probe.runtime)) {
9128
- reasons.push(
9129
- "probe runtime identity does not match build runtime identity"
9130
- );
9131
- }
9132
- const currentServerHash = sha256File(path27.join(generationDir, SERVER_FILE));
9133
- const currentClientHash = sha256File(path27.join(generationDir, CLIENT_FILE));
9134
- if (currentServerHash !== manifest.files.server.sha256 || currentServerHash !== probe.serverSha256) {
9135
- reasons.push("server.bundle.cjs hash does not match probe identity");
9136
- }
9137
- if (currentClientHash !== manifest.files.client.sha256 || currentClientHash !== probe.clientSha256) {
9138
- reasons.push("client.zip hash does not match probe identity");
9139
- }
9140
- if (!probe.coverage.html) {
9141
- reasons.push("probe did not verify HTML");
9142
- }
9143
- if (!probe.coverage.businessApi) {
9144
- reasons.push("probe did not verify business API");
9145
- }
9146
- if (!Array.isArray(probe.coverage.clientAssets) || probe.coverage.clientAssets.length === 0) {
9147
- reasons.push("probe did not verify client entry assets");
9148
- }
9149
- const verifiedPlugins = new Set(probe.coverage.actionPlugins);
9150
- const missingPlugins = manifest.actionPlugins.filter(
9151
- (plugin) => !verifiedPlugins.has(plugin)
9152
- );
9153
- if (missingPlugins.length > 0) {
9154
- reasons.push(
9155
- `probe did not verify Action Plugin: ${missingPlugins.join(", ")}`
9156
- );
9157
- }
9158
- if (reasons.length > 0) {
9159
- return { promoted: false, reasons };
9160
- }
9161
- const promotedManifest = {
9162
- ...manifest,
9163
- runtimeProbeStatus: "passed",
9164
- consumable: true
9165
- };
9166
- writeJsonAtomic2(path27.join(generationDir, MANIFEST_FILE), promotedManifest);
9167
- return { promoted: true, manifest: promotedManifest };
9168
- }
9169
-
9170
- // src/commands/preview-artifact/probe.ts
9171
- import * as fs32 from "fs";
9172
- import * as net from "net";
9173
- import * as os5 from "os";
9174
- import * as path28 from "path";
9175
- import { spawn as spawn2, spawnSync as spawnSync8 } from "child_process";
9176
-
9177
- // src/commands/preview-artifact/readiness.ts
9178
- function isPreviewBusinessResponseReady(response) {
9179
- if (response.status === 401 || response.status === 403) return true;
9180
- return response.status !== 404 && response.status < 500 && !response.headers.get("content-type")?.includes("text/html");
9181
- }
9182
-
9183
- // src/commands/preview-artifact/probe.ts
9184
- function sleep(ms) {
9185
- return new Promise((resolve10) => setTimeout(resolve10, ms));
9186
- }
9187
- async function reservePort() {
9188
- return new Promise((resolve10, reject) => {
9189
- const server = net.createServer();
9190
- server.unref();
9191
- server.once("error", reject);
9192
- server.listen(0, "127.0.0.1", () => {
9193
- const address = server.address();
9194
- if (!address || typeof address === "string") {
9195
- server.close(() => reject(new Error("unable to reserve probe port")));
9196
- return;
9197
- }
9198
- server.close((error) => {
9199
- if (error) reject(error);
9200
- else resolve10(address.port);
9201
- });
9202
- });
9203
- });
9204
- }
9205
- function archiveEntries(archivePath) {
9206
- const listing = spawnSync8("unzip", ["-Z1", archivePath], {
9207
- encoding: "utf8"
9208
- });
9209
- if (listing.status !== 0) {
9210
- throw new Error(`client archive listing failed: ${listing.stderr.trim()}`);
9211
- }
9212
- const entries = listing.stdout.split("\n").filter(Boolean);
9213
- for (const entry of entries) {
9214
- const normalized = path28.posix.normalize(entry.replaceAll("\\", "/"));
9215
- if (path28.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
9216
- throw new Error(`client archive contains unsafe path: ${entry}`);
9217
- }
9218
- }
9219
- return entries;
9220
- }
9221
- function rejectArchivedSymlinks(archivePath) {
9222
- const listing = spawnSync8("unzip", ["-Z", "-l", archivePath], {
9223
- encoding: "utf8"
9224
- });
9225
- if (listing.status !== 0) {
9226
- throw new Error(
9227
- `client archive metadata listing failed: ${listing.stderr.trim()}`
9228
- );
9229
- }
9230
- for (const line of listing.stdout.split("\n")) {
9231
- if (/^l[rwxstST-]{9}\s/.test(line.trimStart())) {
9232
- throw new Error("client archive contains symlink");
9233
- }
9234
- }
9235
- }
9236
- function rejectSymlinks(root) {
9237
- const pending = [root];
9238
- while (pending.length > 0) {
9239
- const current = pending.pop();
9240
- if (!current) continue;
9241
- for (const entry of fs32.readdirSync(current, { withFileTypes: true })) {
9242
- const entryPath = path28.join(current, entry.name);
9243
- const stat = fs32.lstatSync(entryPath);
9244
- if (stat.isSymbolicLink()) {
9245
- throw new Error(`client archive contains symlink: ${entry.name}`);
9246
- }
9247
- if (stat.isDirectory()) pending.push(entryPath);
9248
- }
9249
- }
9250
- }
9251
- function inspectClientArchive(archivePath) {
9252
- archiveEntries(archivePath);
9253
- rejectArchivedSymlinks(archivePath);
9254
- }
9255
- function isPathInside(parent, candidate) {
9256
- const relative5 = path28.relative(parent, candidate);
9257
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path28.sep}`) && !path28.isAbsolute(relative5);
9258
- }
9259
- function clientEntryAssetReferences(html, appBasePath) {
9260
- const local = /* @__PURE__ */ new Set();
9261
- const localExecutable = /* @__PURE__ */ new Set();
9262
- const unsupportedExternal = /* @__PURE__ */ new Set();
9263
- const tags = /<(script|link)\b[^>]*>/gi;
9264
- const readAttribute = (tag, name) => new RegExp(`\\b${name}=["']([^"']+)["']`, "i").exec(tag)?.[1];
9265
- const base = new URL(appBasePath, "http://preview.local");
9266
- for (const match of html.matchAll(tags)) {
9267
- const tagName = match[1]?.toLowerCase();
9268
- const tag = match[0];
9269
- let linkRel = [];
9270
- if (tagName === "link") {
9271
- linkRel = readAttribute(tag, "rel")?.toLowerCase().split(/\s+/) ?? [];
9272
- if (!linkRel.some(
9273
- (value2) => [
9274
- "stylesheet",
9275
- "modulepreload",
9276
- "preload",
9277
- "icon",
9278
- "manifest"
9279
- ].includes(value2)
9280
- )) {
9281
- continue;
9282
- }
9283
- }
9284
- const value = readAttribute(
9285
- tag,
9286
- tagName === "script" ? "src" : "href"
9287
- )?.trim();
9288
- if (!value || value.startsWith("data:") || value.startsWith("#")) continue;
9289
- const resolved = new URL(value, base);
9290
- if (resolved.origin !== base.origin) {
9291
- if (tagName === "script" || linkRel.some(
9292
- (value2) => ["stylesheet", "modulepreload", "preload"].includes(value2)
9293
- )) {
9294
- unsupportedExternal.add(resolved.toString());
9295
- }
9296
- continue;
9297
- }
9298
- local.add(resolved.pathname);
9299
- if (tagName === "script") localExecutable.add(resolved.pathname);
9300
- }
9301
- return {
9302
- local: Array.from(local).sort(),
9303
- localExecutable: Array.from(localExecutable).sort(),
9304
- unsupportedExternal: Array.from(unsupportedExternal).sort()
9305
- };
9306
- }
9307
- function resolveClientEntryAsset(clientRoot, appBasePath, requestPath) {
9308
- let decoded;
9309
- try {
9310
- decoded = decodeURIComponent(requestPath);
9311
- } catch {
9312
- return void 0;
9313
- }
9314
- const normalizedBase = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
9315
- const relative5 = decoded.startsWith(normalizedBase) ? decoded.slice(normalizedBase.length) : decoded.replace(/^\/+/, "");
9316
- if (!relative5 || relative5 === "index.html") return void 0;
9317
- const candidate = path28.resolve(clientRoot, relative5);
9318
- if (!isPathInside(clientRoot, candidate)) return void 0;
9319
- try {
9320
- const stat = fs32.lstatSync(candidate);
9321
- return stat.isFile() && !stat.isSymbolicLink() ? candidate : void 0;
9322
- } catch {
9323
- return void 0;
9324
- }
9325
- }
9326
- function extractClientArchive(archivePath, destination) {
9327
- if (fs32.existsSync(destination)) {
9328
- throw new Error("client archive destination must not already exist");
9329
- }
9330
- inspectClientArchive(archivePath);
9331
- fs32.mkdirSync(destination, { recursive: true });
9332
- try {
9333
- const extraction = spawnSync8(
9334
- "unzip",
9335
- ["-q", archivePath, "-d", destination],
9336
- { encoding: "utf8" }
9337
- );
9338
- if (extraction.status !== 0) {
9339
- throw new Error(
9340
- `client archive extraction failed: ${extraction.stderr.trim()}`
9341
- );
9342
- }
9343
- rejectSymlinks(destination);
9344
- } catch (error) {
9345
- fs32.rmSync(destination, { recursive: true, force: true });
9346
- throw error;
9347
- }
9348
- }
9349
- async function extractInspectedClientArchive(archivePath, destination) {
9350
- if (fs32.existsSync(destination)) {
9351
- throw new Error("client archive destination must not already exist");
9352
- }
9353
- fs32.mkdirSync(destination, { recursive: true });
9354
- try {
9355
- await new Promise((resolve10, reject) => {
9356
- const extraction = spawn2(
9357
- "unzip",
9358
- ["-q", archivePath, "-d", destination],
9359
- { stdio: ["ignore", "ignore", "pipe"] }
9360
- );
9361
- let stderr = "";
9362
- extraction.stderr?.setEncoding("utf8");
9363
- extraction.stderr?.on("data", (chunk) => {
9364
- stderr = `${stderr}${String(chunk)}`.slice(-8 * 1024);
9365
- });
9366
- extraction.once("error", reject);
9367
- extraction.once("close", (code) => {
9368
- if (code === 0) resolve10();
9369
- else
9370
- reject(
9371
- new Error(
9372
- `client archive extraction failed: ${stderr.trim() || `exit ${code ?? "unknown"}`}`
9373
- )
9374
- );
9375
- });
9376
- });
9377
- rejectSymlinks(destination);
9378
- } catch (error) {
9379
- fs32.rmSync(destination, { recursive: true, force: true });
9380
- throw error;
9381
- }
9382
- }
9383
- async function fetchUntil(url, timeoutAt) {
9384
- while (Date.now() < timeoutAt) {
9385
- try {
9386
- return await fetch(url, {
9387
- redirect: "manual",
9388
- signal: AbortSignal.timeout(1e3)
9389
- });
9390
- } catch {
9391
- await sleep(50);
9392
- }
9393
- }
9394
- return null;
9395
- }
9396
- function stopProcessGroup(pid) {
9397
- if (!pid) return;
9398
- try {
9399
- process.kill(-pid, "SIGTERM");
9400
- } catch {
9401
- try {
9402
- process.kill(pid, "SIGTERM");
9403
- } catch {
9404
- return;
9405
- }
9406
- }
9407
- }
9408
- async function runPreviewArtifactProbe(options) {
9409
- const generationDir = path28.resolve(options.generationDir);
9410
- const runtimeRoot = fs32.mkdtempSync(
9411
- path28.join(os5.tmpdir(), "preview-artifact-probe-")
9412
- );
9413
- const coverage = {
9414
- html: false,
9415
- businessApi: false,
9416
- clientAssets: [],
9417
- actionPlugins: []
9418
- };
9419
- const reasons = [];
9420
- const registryFile = path28.join(runtimeRoot, "plugin-registry.json");
9421
- let isolatedNodeModulesPresent = false;
9422
- let child;
9423
- try {
9424
- const runtimeBundle = path28.join(runtimeRoot, "server.bundle.cjs");
9425
- fs32.copyFileSync(
9426
- path28.join(generationDir, "server.bundle.cjs"),
9427
- runtimeBundle
9428
- );
9429
- const clientRoot = path28.join(runtimeRoot, "dist", "client");
9430
- extractClientArchive(path28.join(generationDir, "client.zip"), clientRoot);
9431
- isolatedNodeModulesPresent = fs32.existsSync(
9432
- path28.join(runtimeRoot, "node_modules")
9433
- );
9434
- if (isolatedNodeModulesPresent) {
9435
- reasons.push("isolated runtime unexpectedly contains node_modules");
9436
- }
9437
- const port = await reservePort();
9438
- const timeoutAt = Date.now() + (options.timeoutMs ?? 12e4);
9439
- child = spawn2(process.execPath, [runtimeBundle], {
9440
- cwd: runtimeRoot,
9441
- detached: true,
9442
- stdio: "ignore",
9443
- env: {
9444
- ...process.env,
9445
- ...options.environment,
9446
- SERVER_PORT: String(port),
9447
- PREVIEW_ARTIFACT_PROBE_REGISTRY_FILE: registryFile
9448
- }
9449
- });
9450
- const origin = `http://127.0.0.1:${port}`;
9451
- const htmlResponse = await fetchUntil(
9452
- new URL(options.appBasePath, origin).toString(),
9453
- timeoutAt
9454
- );
9455
- coverage.html = htmlResponse?.status === 200;
9456
- if (!coverage.html) {
9457
- reasons.push(
9458
- `HTML probe failed with status ${htmlResponse?.status ?? "unreachable"}`
9459
- );
9460
- }
9461
- if (coverage.html && htmlResponse) {
9462
- const html = await htmlResponse.text();
9463
- const entryReferences = clientEntryAssetReferences(
9464
- html,
9465
- options.appBasePath
9466
- );
9467
- const entryAssets = entryReferences.local;
9468
- coverage.clientAssets = entryReferences.localExecutable;
9469
- if (entryReferences.localExecutable.length === 0) {
9470
- reasons.push("client HTML has no local executable entry asset");
9471
- for (const externalAsset of entryReferences.unsupportedExternal) {
9472
- reasons.push(
9473
- `external client entry asset is unsupported: ${externalAsset}`
9474
- );
9475
- }
9476
- }
9477
- for (const entryAsset of entryAssets) {
9478
- if (!resolveClientEntryAsset(clientRoot, options.appBasePath, entryAsset)) {
9479
- reasons.push(`client entry asset is unavailable: ${entryAsset}`);
9480
- }
9481
- }
9482
- }
9483
- const apiResponse = await fetchUntil(
9484
- new URL(options.businessApiPath, origin).toString(),
9485
- timeoutAt
9486
- );
9487
- coverage.businessApi = Boolean(
9488
- apiResponse && isPreviewBusinessResponseReady(apiResponse)
9489
- );
9490
- if (!coverage.businessApi) {
9491
- reasons.push(
9492
- `business API returned ${apiResponse?.status ?? "unreachable"}`
9493
- );
9494
- }
9495
- while (!fs32.existsSync(registryFile) && Date.now() < timeoutAt) {
9496
- await sleep(25);
9497
- }
9498
- if (fs32.existsSync(registryFile)) {
9499
- const parsed = JSON.parse(
9500
- fs32.readFileSync(registryFile, "utf8")
9501
- );
9502
- if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) {
9503
- coverage.actionPlugins = parsed.filter((value) => typeof value === "string").sort();
9504
- } else {
9505
- reasons.push("Action Plugin registry evidence is invalid");
9506
- }
9507
- } else {
9508
- reasons.push("Action Plugin registry evidence is unavailable");
9509
- }
9510
- } catch (error) {
9511
- reasons.push(error instanceof Error ? error.message : String(error));
9512
- } finally {
9513
- stopProcessGroup(child?.pid);
9514
- }
9515
- const probe = recordPreviewArtifactProbe({
9516
- generationDir,
9517
- success: reasons.length === 0,
9518
- reasons,
9519
- nodeVersion: process.version,
9520
- platform: process.platform,
9521
- arch: process.arch,
9522
- coverage
9523
- });
9524
- fs32.rmSync(runtimeRoot, { recursive: true, force: true });
9525
- return { ...probe, isolatedNodeModulesPresent };
9526
- }
9527
-
9528
- // src/commands/preview-artifact/environment.ts
9529
- import * as fs33 from "fs";
9530
- import * as path29 from "path";
9531
- var ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
9532
- function readPreviewArtifactEnvironmentFile(filePath) {
9533
- const resolved = path29.resolve(filePath);
9534
- const parsed = JSON.parse(fs33.readFileSync(resolved, "utf8"));
9535
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9536
- throw new Error("preview artifact environment file must contain an object");
9537
- }
9538
- const environment = {};
9539
- for (const [key, value] of Object.entries(parsed)) {
9540
- if (!ENVIRONMENT_NAME.test(key) || typeof value !== "string") {
9541
- throw new Error(`invalid environment variable in ${resolved}: ${key}`);
9542
- }
9543
- environment[key] = value;
9544
- }
9545
- return environment;
9546
- }
9547
- function previewArtifactRuntimeEnvironment(environment) {
9548
- return {
9549
- ...environment,
9550
- NODE_ENV: "development",
9551
- // Probe and switcher connect through 127.0.0.1. Linux may resolve
9552
- // `localhost` to IPv6-only ::1, so the isolated Nest process must bind to
9553
- // the same explicit private endpoint instead of inheriting app defaults.
9554
- SERVER_HOST: "127.0.0.1"
9555
- };
9556
- }
9557
-
9558
- // src/commands/preview-artifact/store.ts
9559
- import * as crypto3 from "crypto";
9560
- import * as fs34 from "fs";
9561
- import * as path30 from "path";
9562
- var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9563
- var WORKSPACE_BACKUP_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9564
- var WORKSPACE_VERSION_PATTERN2 = /^sha256:[a-f0-9]{64}$/;
9565
- function readCurrentPointer(storeRoot) {
9566
- try {
9567
- const pointer = readJson3(
9568
- path30.join(storeRoot, "current.json")
9569
- );
9570
- return pointer.schemaVersion === PREVIEW_ARTIFACT_SCHEMA_VERSION && GENERATION_ID_PATTERN.test(pointer.generationId) && WORKSPACE_BACKUP_ID_PATTERN2.test(pointer.workspaceBackupId) && (pointer.workspaceVersion == null || WORKSPACE_VERSION_PATTERN2.test(pointer.workspaceVersion)) ? pointer : void 0;
9571
- } catch {
9572
- return void 0;
9573
- }
9574
- }
9575
- function readJson3(filePath) {
9576
- return JSON.parse(fs34.readFileSync(filePath, "utf8"));
9577
- }
9578
- function writeJsonAtomic3(filePath, value) {
9579
- fs34.mkdirSync(path30.dirname(filePath), { recursive: true });
9580
- const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9581
- fs34.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
9582
- `, {
9583
- mode: 384
9584
- });
9585
- fs34.renameSync(temporaryPath, filePath);
9586
- }
9587
- function pruneSupersededGenerations(generationsRoot, retainedGenerationIds) {
9588
- const retained = new Set(retainedGenerationIds);
9589
- for (const entry of fs34.readdirSync(generationsRoot, {
9590
- withFileTypes: true
9591
- })) {
9592
- if (retained.has(entry.name)) continue;
9593
- try {
9594
- fs34.rmSync(path30.join(generationsRoot, entry.name), {
9595
- recursive: true,
9596
- force: true
9597
- });
9598
- } catch {
9599
- }
9600
- }
9601
- }
9602
- function parseNodeMajor2(nodeVersion) {
9603
- const match = /^v?(\d+)(?:\.|$)/.exec(nodeVersion.trim());
9604
- return match ? Number(match[1]) : null;
9605
- }
9606
- function isPathInside2(parent, candidate) {
9607
- const relative5 = path30.relative(parent, candidate);
9608
- return relative5 !== "" && !relative5.startsWith(`..${path30.sep}`) && relative5 !== ".." && !path30.isAbsolute(relative5);
9609
- }
9610
- function validateRegularDirectory(directoryPath, label) {
9611
- try {
9612
- const stat = fs34.lstatSync(directoryPath);
9613
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
9614
- return [`${label} must be a regular directory`];
9615
- }
9616
- return [];
9617
- } catch (error) {
9618
- return [
9619
- `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}`
9620
- ];
9621
- }
9622
- }
9623
- function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9624
- const reasons = validateRegularDirectory(
9625
- generationDir,
9626
- "generation directory"
9627
- );
9628
- if (reasons.length > 0) return { reasons };
9629
- let manifest;
9630
- let probe;
9631
- try {
9632
- manifest = readJson3(
9633
- path30.join(generationDir, "manifest.json")
9634
- );
9635
- probe = readJson3(
9636
- path30.join(generationDir, "probe.json")
9637
- );
9638
- } catch (error) {
9639
- return {
9640
- reasons: [
9641
- `generation metadata is invalid: ${error instanceof Error ? error.message : String(error)}`
9642
- ]
9643
- };
9644
- }
9645
- if (manifest.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || manifest.protocolVersion !== PREVIEW_ARTIFACT_PROTOCOL_VERSION || probe.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION) {
9646
- reasons.push("generation schema version is incompatible");
9647
- }
9648
- if (!GENERATION_ID_PATTERN.test(manifest.generationId) || probe.generationId !== manifest.generationId) {
9649
- reasons.push("generation ID is invalid or inconsistent");
9650
- }
9651
- if (!manifest.consumable || manifest.runtimeProbeStatus !== "passed") {
9652
- reasons.push("generation is not consumable");
9653
- }
9654
- if (!probe.success) {
9655
- reasons.push("generation runtime probe did not pass");
9656
- }
9657
- if (!Array.isArray(probe.coverage?.clientAssets) || probe.coverage.clientAssets.length === 0) {
9658
- reasons.push("generation client asset evidence is invalid");
9659
- }
9660
- if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
9661
- reasons.push("build environment identity is invalid");
9662
- }
9663
- reasons.push(...validatePreviewArtifactWorkspaceIdentity(manifest));
9664
- for (const [label, identity, expectedPath] of [
9665
- ["server", manifest.files?.server, "server.bundle.cjs"],
9666
- ["client", manifest.files?.client, "client.zip"]
9667
- ]) {
9668
- if (!identity || identity.path !== expectedPath || !Number.isSafeInteger(identity.bytes) || identity.bytes < 0 || !/^[a-f0-9]{64}$/.test(identity.sha256)) {
9669
- reasons.push(`${label} file identity is invalid`);
9670
- }
9671
- }
9672
- if (verifyPayloads && manifest.files?.server && manifest.files?.client) {
9673
- const serverPath = path30.join(generationDir, "server.bundle.cjs");
9674
- const clientPath = path30.join(generationDir, "client.zip");
9675
- try {
9676
- if (sha256File(serverPath) !== manifest.files.server.sha256) {
9677
- reasons.push("server.bundle.cjs hash does not match manifest");
9678
- }
9679
- if (sha256File(clientPath) !== manifest.files.client.sha256) {
9680
- reasons.push("client.zip hash does not match manifest");
9681
- }
9682
- } catch (error) {
9683
- reasons.push(
9684
- `generation files are unavailable: ${error instanceof Error ? error.message : String(error)}`
9685
- );
9686
- }
9687
- }
9688
- if (probe.serverSha256 !== manifest.files.server.sha256 || probe.clientSha256 !== manifest.files.client.sha256) {
9689
- reasons.push("probe file identity does not match manifest");
9690
- }
9691
- if (runtime && (runtime.nodeMajor !== manifest.runtime.nodeMajor || runtime.platform !== manifest.runtime.platform || runtime.arch !== manifest.runtime.arch)) {
9692
- reasons.push("runtime identity is incompatible with generation");
9693
- }
9694
- return { manifest, reasons };
9695
- }
9696
- function publishPreviewArtifactGeneration(options) {
9697
- const storeRoot = path30.resolve(options.storeRoot);
9698
- const previousPointer = readCurrentPointer(storeRoot);
9699
- const stagingRoot = path30.join(storeRoot, ".staging");
9700
- const stagingDir = path30.resolve(options.stagingDir);
9701
- if (!isPathInside2(stagingRoot, stagingDir)) {
9702
- return {
9703
- published: false,
9704
- reasons: [
9705
- "staging directory must be inside the store .staging directory"
9706
- ]
9707
- };
9708
- }
9709
- const validation = validateGeneration(stagingDir);
9710
- if (!validation.manifest || validation.reasons.length > 0) {
9711
- return { published: false, reasons: validation.reasons };
9712
- }
9713
- const generationId = validation.manifest.generationId;
9714
- const generationsRoot = path30.join(storeRoot, "generations");
9715
- const generationDir = path30.join(generationsRoot, generationId);
9716
- fs34.mkdirSync(generationsRoot, { recursive: true });
9717
- if (fs34.existsSync(generationDir)) {
9718
- return {
9719
- published: false,
9720
- reasons: [`generation already exists: ${generationId}`]
9721
- };
9722
- }
9723
- try {
9724
- fs34.renameSync(stagingDir, generationDir);
9725
- const pointer = {
9726
- schemaVersion: PREVIEW_ARTIFACT_SCHEMA_VERSION,
9727
- generationId,
9728
- workspaceBackupId: validation.manifest.workspaceBackupId,
9729
- ...validation.manifest.workspaceVersion == null ? {} : { workspaceVersion: validation.manifest.workspaceVersion },
9730
- publishedAt: (/* @__PURE__ */ new Date()).toISOString()
9731
- };
9732
- writeJsonAtomic3(path30.join(storeRoot, "current.json"), pointer);
9733
- pruneSupersededGenerations(
9734
- generationsRoot,
9735
- [generationId, previousPointer?.generationId].filter(
9736
- (value) => Boolean(value)
9737
- )
9738
- );
9739
- return { published: true, generationId, generationDir };
9740
- } catch (error) {
9741
- return {
9742
- published: false,
9743
- reasons: [
9744
- `generation publication failed: ${error instanceof Error ? error.message : String(error)}`
9745
- ]
9746
- };
9747
- }
9748
- }
9749
- function resolveCurrentPreviewArtifactMetadata(options) {
9750
- if (!options.workspaceManifest) {
9751
- return {
9752
- resolved: false,
9753
- reasons: ["workspace manifest is unavailable"]
9754
- };
9755
- }
9756
- const workspaceManifestReasons = validatePreviewArtifactWorkspaceManifest(
9757
- options.workspaceManifest
9758
- );
9759
- if (workspaceManifestReasons.length > 0) {
9760
- return {
9761
- resolved: false,
9762
- reasons: workspaceManifestReasons
9763
- };
9764
- }
9765
- let requestedSourceTreeValidation;
9766
- try {
9767
- requestedSourceTreeValidation = createPreviewArtifactSourceTreeValidation(
9768
- options.sourceTreeValidation?.enabled,
9769
- options.sourceTreeValidation?.includePaths
9770
- );
9771
- } catch {
9772
- return {
9773
- resolved: false,
9774
- reasons: ["requested source tree validation policy is invalid"]
9775
- };
9776
- }
9777
- const storeRoot = path30.resolve(options.storeRoot);
9778
- let pointer;
9779
- try {
9780
- pointer = readJson3(path30.join(storeRoot, "current.json"));
9781
- } catch (error) {
9782
- return {
9783
- resolved: false,
9784
- reasons: [
9785
- `current pointer is unavailable: ${error instanceof Error ? error.message : String(error)}`
9786
- ]
9787
- };
9788
- }
9789
- if (pointer.schemaVersion !== PREVIEW_ARTIFACT_SCHEMA_VERSION || !GENERATION_ID_PATTERN.test(pointer.generationId) || !WORKSPACE_BACKUP_ID_PATTERN2.test(pointer.workspaceBackupId) || pointer.workspaceVersion != null && !WORKSPACE_VERSION_PATTERN2.test(pointer.workspaceVersion)) {
9790
- return {
9791
- resolved: false,
9792
- reasons: [
9793
- "current generation ID, workspace identity, or schema version is invalid"
9794
- ]
9795
- };
9796
- }
9797
- const nodeMajor = parseNodeMajor2(options.nodeVersion);
9798
- if (nodeMajor == null) {
9799
- return { resolved: false, reasons: ["runtime Node.js version is invalid"] };
9800
- }
9801
- const generationDir = path30.join(
9802
- storeRoot,
9803
- "generations",
9804
- pointer.generationId
9805
- );
9806
- const validation = validateGeneration(
9807
- generationDir,
9808
- {
9809
- nodeMajor,
9810
- platform: options.platform,
9811
- arch: options.arch
9812
- },
9813
- false
9814
- );
9815
- if (!validation.manifest || validation.reasons.length > 0) {
9816
- return { resolved: false, reasons: validation.reasons };
9817
- }
9818
- if (validation.manifest.generationId !== pointer.generationId || validation.manifest.workspaceBackupId !== pointer.workspaceBackupId || validation.manifest.workspaceVersion !== pointer.workspaceVersion) {
9819
- return {
9820
- resolved: false,
9821
- reasons: ["current pointer does not match generation manifest"]
9822
- };
9823
- }
9824
- if (requestedSourceTreeValidation.enabled) {
9825
- const generationSourceTreeValidation = previewArtifactSourceTreeValidation(
9826
- validation.manifest
9827
- );
9828
- const workspaceSourceTreeValidation = previewArtifactSourceTreeValidation(
9829
- options.workspaceManifest
9830
- );
9831
- if (!samePreviewArtifactSourceTreeValidation(
9832
- requestedSourceTreeValidation,
9833
- generationSourceTreeValidation
9834
- ) || !samePreviewArtifactSourceTreeValidation(
9835
- requestedSourceTreeValidation,
9836
- workspaceSourceTreeValidation
9837
- )) {
9838
- return {
9839
- resolved: false,
9840
- reasons: ["workspace source tree validation policy mismatch"]
9841
- };
9842
- }
9843
- if (options.workspaceManifest.workspaceVersion !== validation.manifest.workspaceVersion) {
9844
- return {
9845
- resolved: false,
9846
- reasons: ["workspace version mismatch"]
9847
- };
9848
- }
9849
- }
9850
- if (validation.manifest.buildEnvironmentSha256 !== options.buildEnvironmentSha256) {
9851
- return {
9852
- resolved: false,
9853
- reasons: ["build environment is incompatible with generation"]
9854
- };
9855
- }
9856
- return {
9857
- resolved: true,
9858
- generationId: pointer.generationId,
9859
- generationDir,
9860
- manifest: validation.manifest
9861
- };
9862
- }
9863
- async function materializeVerifiedPreviewArtifactFile(options) {
9864
- const source = path30.resolve(options.source);
9865
- const destination = path30.resolve(options.destination);
9866
- const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
9867
- fs34.mkdirSync(path30.dirname(destination), { recursive: true });
9868
- if (fs34.existsSync(destination)) {
9869
- throw new Error(
9870
- `materialization destination already exists: ${destination}`
9871
- );
9872
- }
9873
- const noFollow = fs34.constants.O_NOFOLLOW ?? 0;
9874
- let sourceHandle;
9875
- let destinationHandle;
9876
- try {
9877
- sourceHandle = await fs34.promises.open(
9878
- source,
9879
- fs34.constants.O_RDONLY | noFollow
9880
- );
9881
- const sourceStat = await sourceHandle.stat();
9882
- if (!sourceStat.isFile()) {
9883
- throw new Error(`${options.identity.path} is not a regular file`);
9884
- }
9885
- destinationHandle = await fs34.promises.open(
9886
- temporary,
9887
- fs34.constants.O_WRONLY | fs34.constants.O_CREAT | fs34.constants.O_EXCL,
9888
- 384
9889
- );
9890
- const hash = crypto3.createHash("sha256");
9891
- const buffer = Buffer.allocUnsafe(256 * 1024);
9892
- let totalBytes = 0;
9893
- for (; ; ) {
9894
- const { bytesRead } = await sourceHandle.read(
9895
- buffer,
9896
- 0,
9897
- buffer.length,
9898
- null
9899
- );
9900
- if (bytesRead === 0) break;
9901
- hash.update(buffer.subarray(0, bytesRead));
9902
- let written = 0;
9903
- while (written < bytesRead) {
9904
- const result = await destinationHandle.write(
9905
- buffer,
9906
- written,
9907
- bytesRead - written,
9908
- null
9909
- );
9910
- if (result.bytesWritten <= 0) {
9911
- throw new Error(
9912
- `${options.identity.path} materialization short write`
9913
- );
9914
- }
9915
- written += result.bytesWritten;
9916
- }
9917
- totalBytes += bytesRead;
9918
- }
9919
- const sha256 = hash.digest("hex");
9920
- if (sha256 !== options.identity.sha256) {
9921
- throw new Error(`${options.identity.path} hash does not match manifest`);
9922
- }
9923
- if (totalBytes !== options.identity.bytes) {
9924
- throw new Error(`${options.identity.path} bytes do not match manifest`);
9925
- }
9926
- await destinationHandle.sync();
9927
- await destinationHandle.close();
9928
- destinationHandle = void 0;
9929
- await fs34.promises.rename(temporary, destination);
9930
- return { bytes: totalBytes, sha256 };
9931
- } finally {
9932
- await destinationHandle?.close().catch(() => void 0);
9933
- await sourceHandle?.close().catch(() => void 0);
9934
- await fs34.promises.rm(temporary, { force: true }).catch(() => void 0);
9935
- }
9936
- }
9937
-
9938
- // src/commands/preview-artifact/producer.ts
9939
- function defaultGenerationId() {
9940
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
9941
- return `${timestamp}-${crypto4.randomBytes(5).toString("hex")}`;
9942
- }
9943
- function defaultPreviewBundleBuildCommands(projectRootInput) {
9944
- const projectRoot = path31.resolve(projectRootInput);
9945
- const executableSuffix = process.platform === "win32" ? ".cmd" : "";
9946
- const localBinary = (name) => path31.join(
9947
- projectRoot,
9948
- "node_modules",
9949
- ".bin",
9950
- `${name}${executableSuffix}`
9951
- );
9952
- const nodeBuildCommand = (label, binary, args) => process.platform === "win32" ? { label, command: binary, args } : { label, command: process.execPath, args: [binary, ...args] };
9953
- return [
9954
- nodeBuildCommand("server", localBinary("nest"), ["build"]),
9955
- nodeBuildCommand("client", localBinary("vite"), [
9956
- "build",
9957
- "--mode",
9958
- "development",
9959
- "--config",
9960
- "vite.config.ts"
9961
- ])
9962
- ];
9963
- }
9964
- function runBuildCommands(projectRoot, buildCommands, environment) {
9965
- for (const buildCommand of buildCommands) {
9966
- const result = spawnSync9(buildCommand.command, buildCommand.args, {
9967
- cwd: projectRoot,
9968
- env: { ...process.env, ...environment },
9969
- encoding: "utf8",
9970
- maxBuffer: 10 * 1024 * 1024
9971
- });
9972
- if (result.status !== 0) {
9973
- const diagnostic = `${result.stdout ?? ""}
9974
- ${result.stderr ?? ""}`.trim().slice(-8 * 1024);
9975
- return [
9976
- `preview bundle ${buildCommand.label} build failed with exit ${result.status ?? "unknown"}${result.error ? `: ${result.error.message}` : ""}${diagnostic ? `
9977
- ${diagnostic}` : ""}`
9978
- ];
9979
- }
9980
- }
9981
- return [];
9982
- }
9983
- function createClientArchive(clientDir, archivePath) {
9984
- if (!fs35.existsSync(path31.join(clientDir, "index.html"))) {
9985
- return [`client build output is missing: ${clientDir}`];
9986
- }
9987
- const result = spawnSync9("zip", ["-qry", archivePath, "."], {
9988
- cwd: clientDir,
9989
- encoding: "utf8"
9990
- });
9991
- if (result.status === 0 && fs35.existsSync(archivePath)) return [];
9992
- return [`client archive failed with exit ${result.status ?? "unknown"}`];
9993
- }
9994
- function readActionPlugins2(metadataFile) {
9995
- const metadata = JSON.parse(fs35.readFileSync(metadataFile, "utf8"));
9996
- if (!Array.isArray(metadata.actionPlugins)) return [];
9997
- return metadata.actionPlugins.filter((value) => typeof value === "string").sort();
9998
- }
9999
- async function producePreviewArtifactGeneration(options) {
10000
- const startedAt = Date.now();
10001
- const projectRoot = path31.resolve(options.projectRoot);
10002
- const storeRoot = path31.resolve(options.storeRoot);
10003
- const generationId = options.generationId ?? defaultGenerationId();
10004
- const stagingDir = path31.join(
10005
- storeRoot,
10006
- ".staging",
10007
- `${generationId}.${process.pid}.${Date.now()}`
10008
- );
10009
- const failed = (reasons, probe) => ({
10010
- published: false,
10011
- generationId,
10012
- elapsedMs: Date.now() - startedAt,
10013
- reasons,
10014
- ...probe ? { probe } : {}
10015
- });
10016
- fs35.mkdirSync(stagingDir, { recursive: true });
10017
- try {
10018
- const buildEnvironment = previewArtifactBuildEnvironment(
10019
- options.environment,
10020
- options.appBasePath
10021
- );
10022
- const buildCommands = options.buildCommands ?? (options.buildCommand ? [{ label: "custom", ...options.buildCommand }] : defaultPreviewBundleBuildCommands(projectRoot));
10023
- const buildReasons = runBuildCommands(
10024
- projectRoot,
10025
- buildCommands,
10026
- buildEnvironment
10027
- );
10028
- if (buildReasons.length > 0) return failed(buildReasons);
10029
- const serverOutfile = path31.join(stagingDir, "server.bundle.cjs");
10030
- const serverMetadataFile = path31.join(stagingDir, "server-build.json");
10031
- const serverResult = await buildPreviewServerArtifact({
10032
- projectRoot,
10033
- entry: "dist/main.js",
10034
- outfile: serverOutfile,
10035
- metadataFile: serverMetadataFile
10036
- });
10037
- if (!serverResult.built) return failed(serverResult.reasons);
10038
- const archiveReasons = createClientArchive(
10039
- path31.join(projectRoot, "dist", "client"),
10040
- path31.join(stagingDir, "client.zip")
10041
- );
10042
- if (archiveReasons.length > 0) return failed(archiveReasons);
10043
- const actionPlugins = readActionPlugins2(serverMetadataFile);
10044
- fs35.rmSync(serverMetadataFile, { force: true });
10045
- createPreviewArtifactManifest({
10046
- generationDir: stagingDir,
10047
- generationId,
10048
- nodeVersion: process.version,
10049
- platform: process.platform,
10050
- arch: process.arch,
10051
- buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
10052
- options.environment,
10053
- options.appBasePath
10054
- ),
10055
- workspaceManifest: options.workspaceManifest,
10056
- actionPlugins
10057
- });
10058
- const probe = await runPreviewArtifactProbe({
10059
- generationDir: stagingDir,
10060
- appBasePath: options.appBasePath,
10061
- businessApiPath: options.businessApiPath,
10062
- environment: previewArtifactRuntimeEnvironment(options.environment)
10063
- });
10064
- if (!probe.success) return failed(probe.reasons, probe);
10065
- const promotion = promotePreviewArtifactGeneration(stagingDir);
10066
- if (!promotion.promoted) return failed(promotion.reasons, probe);
10067
- const publication = publishPreviewArtifactGeneration({
10068
- storeRoot,
10069
- stagingDir
10070
- });
10071
- if (!publication.published) return failed(publication.reasons, probe);
10072
- return {
10073
- published: true,
10074
- generationId,
10075
- generationDir: publication.generationDir,
10076
- elapsedMs: Date.now() - startedAt,
10077
- probe
10078
- };
10079
- } catch (error) {
10080
- return failed([error instanceof Error ? error.message : String(error)]);
10081
- } finally {
10082
- fs35.rmSync(stagingDir, { recursive: true, force: true });
10083
- }
10084
- }
10085
-
10086
- // src/commands/preview-artifact/runtime.ts
10087
- import * as fs37 from "fs";
10088
- import * as net3 from "net";
10089
- import * as path33 from "path";
10090
- import { spawn as spawn3 } from "child_process";
10091
-
10092
- // src/commands/preview-artifact/switcher.ts
10093
- import * as http from "http";
10094
- import * as net2 from "net";
10095
- import * as fs36 from "fs";
10096
- import * as path32 from "path";
10097
- function targetOrigin(target) {
10098
- return `http://${target.host}:${target.port}`;
10099
- }
10100
- function proxyHttpRequest(target, request2, response, fallback) {
10101
- const upstream = http.request(
10102
- {
10103
- host: target.host,
10104
- port: target.port,
10105
- method: request2.method,
10106
- path: request2.url,
10107
- headers: request2.headers
10108
- },
10109
- (upstreamResponse) => {
10110
- const upstreamContentType = upstreamResponse.headers["content-type"];
10111
- const isHtmlFallback = typeof upstreamContentType === "string" && upstreamContentType.toLowerCase().includes("text/html");
10112
- if (fallback && (upstreamResponse.statusCode === 404 || isHtmlFallback)) {
10113
- upstreamResponse.resume();
10114
- fallback();
10115
- return;
10116
- }
10117
- response.writeHead(
10118
- upstreamResponse.statusCode ?? 502,
10119
- upstreamResponse.headers
10120
- );
10121
- upstreamResponse.pipe(response);
10122
- }
10123
- );
10124
- upstream.on("error", (error) => {
10125
- if (!response.headersSent) {
10126
- response.writeHead(502, { "content-type": "application/json" });
10127
- }
10128
- response.end(
10129
- JSON.stringify({
10130
- ready: false,
10131
- error: error.code ?? "upstream"
10132
- })
10133
- );
10134
- });
10135
- request2.pipe(upstream);
10136
- }
10137
- function cachedAssetFile(request2, cachedAssetFiles) {
10138
- if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
10139
- if (!cachedAssetFiles) return void 0;
10140
- const pathname = new URL(request2.url ?? "/", "http://preview.local").pathname;
10141
- return cachedAssetFiles.get(pathname);
10142
- }
10143
- function contentType(filePath) {
10144
- switch (path32.extname(filePath).toLowerCase()) {
10145
- case ".js":
10146
- case ".mjs":
10147
- return "text/javascript; charset=utf-8";
10148
- case ".css":
10149
- return "text/css; charset=utf-8";
10150
- case ".json":
10151
- case ".map":
10152
- return "application/json; charset=utf-8";
10153
- case ".svg":
10154
- return "image/svg+xml";
10155
- case ".png":
10156
- return "image/png";
10157
- case ".jpg":
10158
- case ".jpeg":
10159
- return "image/jpeg";
10160
- case ".gif":
10161
- return "image/gif";
10162
- case ".webp":
10163
- return "image/webp";
10164
- case ".ico":
10165
- return "image/x-icon";
10166
- case ".woff":
10167
- return "font/woff";
10168
- case ".woff2":
10169
- return "font/woff2";
10170
- case ".ttf":
10171
- return "font/ttf";
10172
- case ".wasm":
10173
- return "application/wasm";
10174
- default:
10175
- return "application/octet-stream";
10176
- }
10177
- }
10178
- function serveCachedAsset(request2, response, filePath) {
10179
- let stat;
10180
- try {
10181
- stat = fs36.lstatSync(filePath);
10182
- } catch {
10183
- response.writeHead(404).end();
10184
- return;
10185
- }
10186
- if (!stat.isFile() || stat.isSymbolicLink()) {
10187
- response.writeHead(404).end();
10188
- return;
10189
- }
10190
- response.writeHead(200, {
10191
- "content-type": contentType(filePath),
10192
- "content-length": stat.size,
10193
- "cache-control": "no-store"
10194
- });
10195
- if (request2.method === "HEAD") {
10196
- response.end();
10197
- return;
10198
- }
10199
- const stream = fs36.createReadStream(filePath);
10200
- stream.on("error", () => response.destroy());
10201
- stream.pipe(response);
10202
- }
10203
- function proxyUpgrade(target, request2, socket, head) {
10204
- const upstream = net2.connect(target.port, target.host, () => {
10205
- const headers = Object.entries(request2.headers).flatMap(([name, value]) => {
10206
- if (Array.isArray(value)) return value.map((item) => `${name}: ${item}`);
10207
- return value == null ? [] : [`${name}: ${value}`];
10208
- }).join("\r\n");
10209
- upstream.write(
10210
- `${request2.method ?? "GET"} ${request2.url ?? "/"} HTTP/${request2.httpVersion}\r
10211
- ${headers}\r
10212
- \r
10213
- `
10214
- );
10215
- if (head.length > 0) upstream.write(head);
10216
- socket.pipe(upstream).pipe(socket);
10217
- });
10218
- upstream.on("error", () => socket.destroy());
10219
- socket.on("error", () => upstream.destroy());
10220
- }
10221
- function targetAcceptsConnection(target, timeoutMs = 1e3) {
10222
- return new Promise((resolve10) => {
10223
- const socket = net2.connect(target.port, target.host);
10224
- let settled = false;
10225
- const finish = (ready) => {
10226
- if (settled) return;
10227
- settled = true;
10228
- socket.destroy();
10229
- resolve10(ready);
10230
- };
10231
- socket.setTimeout(timeoutMs);
10232
- socket.once("connect", () => finish(true));
10233
- socket.once("timeout", () => finish(false));
10234
- socket.once("error", () => finish(false));
10235
- });
10236
- }
10237
- async function freshTargetReady(clientTarget, serverTarget, healthPath) {
10238
- try {
10239
- const [clientReady, serverReady] = await Promise.all([
10240
- (async () => {
10241
- const response = await fetch(
10242
- new URL(healthPath, targetOrigin(clientTarget)),
10243
- { signal: AbortSignal.timeout(1e3) }
10244
- );
10245
- if (response.status !== 200) return false;
10246
- const body = await response.json();
10247
- return body.ready === true;
10248
- })(),
10249
- targetAcceptsConnection(serverTarget)
10250
- ]);
10251
- return clientReady && serverReady;
10252
- } catch {
10253
- return false;
10254
- }
10255
- }
10256
- function createPreviewArtifactSwitcher(options) {
10257
- const listenHost = options.listenHost ?? "127.0.0.1";
10258
- const healthPath = options.freshHealthPath ?? "/dev/health";
10259
- const pollIntervalMs = options.pollIntervalMs ?? 100;
10260
- let active = "cached";
10261
- let closed = false;
10262
- let pollTimer;
10263
- let settleCutover;
10264
- const cutover = new Promise((resolve10) => {
10265
- settleCutover = resolve10;
10266
- });
10267
- const currentTarget = () => active === "cached" ? options.cachedTarget : options.freshTarget;
10268
- const server = http.createServer((request2, response) => {
10269
- if (request2.url?.split("?")[0] === "/dev/health") {
10270
- response.writeHead(200, { "content-type": "application/json" });
10271
- response.end(
10272
- JSON.stringify({
10273
- schemaVersion: 1,
10274
- ready: active === "fresh",
10275
- previewReady: true,
10276
- active,
10277
- generationId: options.generationId
10278
- })
10279
- );
10280
- return;
10281
- }
10282
- const assetFile = cachedAssetFile(request2, options.cachedAssetFiles);
10283
- if (active === "cached" && assetFile) {
10284
- serveCachedAsset(request2, response, assetFile);
10285
- return;
10286
- }
10287
- const target = currentTarget();
10288
- const fallback = active === "fresh" && assetFile ? () => serveCachedAsset(request2, response, assetFile) : void 0;
10289
- proxyHttpRequest(target, request2, response, fallback);
10290
- });
10291
- server.on("upgrade", (request2, socket, head) => {
10292
- proxyUpgrade(currentTarget(), request2, socket, head);
10293
- });
10294
- const schedulePoll = () => {
10295
- if (closed || active === "fresh") return;
10296
- pollTimer = setTimeout(async () => {
10297
- if (await freshTargetReady(
10298
- options.freshTarget,
10299
- options.freshServerTarget,
10300
- healthPath
10301
- )) {
10302
- active = "fresh";
10303
- settleCutover?.({
10304
- switched: true,
10305
- active,
10306
- at: (/* @__PURE__ */ new Date()).toISOString()
10307
- });
10308
- settleCutover = void 0;
10309
- return;
10310
- }
10311
- schedulePoll();
10312
- }, pollIntervalMs);
10313
- pollTimer.unref();
10314
- };
10315
- return {
10316
- get active() {
10317
- return active;
10318
- },
10319
- cutover,
10320
- start() {
10321
- return new Promise((resolve10, reject) => {
10322
- server.once("error", reject);
10323
- server.listen(options.listenPort, listenHost, () => {
10324
- const address = server.address();
10325
- if (!address || typeof address === "string") {
10326
- reject(new Error("switcher did not expose a TCP address"));
10327
- return;
10328
- }
10329
- schedulePoll();
10330
- resolve10({
10331
- host: listenHost,
10332
- port: address.port,
10333
- origin: `http://${listenHost}:${address.port}`
10334
- });
10335
- });
10336
- });
10337
- },
10338
- close() {
10339
- closed = true;
10340
- if (pollTimer) clearTimeout(pollTimer);
10341
- if (settleCutover) {
10342
- settleCutover({
10343
- switched: false,
10344
- active,
10345
- at: (/* @__PURE__ */ new Date()).toISOString()
10346
- });
10347
- settleCutover = void 0;
10348
- }
10349
- return new Promise((resolve10) => {
10350
- server.close(() => resolve10());
10351
- server.closeAllConnections?.();
10352
- });
10353
- }
10354
- };
10355
- }
10356
-
10357
- // src/commands/preview-artifact/runtime.ts
10358
- function measure(timings, phase, operation, detail) {
10359
- const startedAt = Date.now();
10360
- try {
10361
- return operation();
10362
- } finally {
10363
- timings.push({ phase, durationMs: Date.now() - startedAt, ...detail });
10364
- }
10365
- }
10366
- async function measureAsync(timings, phase, operation, detail) {
10367
- const startedAt = Date.now();
10368
- try {
10369
- return await operation();
10370
- } finally {
10371
- timings.push({ phase, durationMs: Date.now() - startedAt, ...detail });
10372
- }
10373
- }
10374
- function sleep2(ms) {
10375
- return new Promise((resolve10) => setTimeout(resolve10, ms));
10376
- }
10377
- async function reservePort2() {
10378
- return new Promise((resolve10, reject) => {
10379
- const server = net3.createServer();
10380
- server.unref();
10381
- server.once("error", reject);
10382
- server.listen(0, "127.0.0.1", () => {
10383
- const address = server.address();
10384
- if (!address || typeof address === "string") {
10385
- server.close(() => reject(new Error("unable to reserve runtime port")));
10386
- return;
10387
- }
10388
- server.close((error) => {
10389
- if (error) reject(error);
10390
- else resolve10(address.port);
10391
- });
10392
- });
10393
- });
10394
- }
10395
- function stopProcessGroup2(child) {
10396
- if (!child?.pid) return;
10397
- try {
10398
- process.kill(-child.pid, "SIGTERM");
10399
- } catch {
10400
- try {
10401
- child.kill("SIGTERM");
10402
- } catch {
10403
- return;
10404
- }
10405
- }
10406
- }
10407
- function isPathInside3(parent, candidate) {
10408
- const relative5 = path33.relative(parent, candidate);
10409
- return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path33.sep}`) && !path33.isAbsolute(relative5);
10410
- }
10411
- function ensureRegularRuntimeRoot(runtimeRoot) {
10412
- fs37.mkdirSync(runtimeRoot, { recursive: true });
10413
- const stat = fs37.lstatSync(runtimeRoot);
10414
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
10415
- throw new Error("runtime root must be a regular directory");
10416
- }
10417
- }
10418
- function collectCachedAssetFiles(clientRoot, appBasePath) {
10419
- const files = /* @__PURE__ */ new Map();
10420
- const basePath = appBasePath.endsWith("/") ? appBasePath : `${appBasePath}/`;
10421
- const visit = (directory) => {
10422
- for (const entry of fs37.readdirSync(directory, { withFileTypes: true })) {
10423
- const absolute = path33.join(directory, entry.name);
10424
- if (entry.isDirectory()) {
10425
- visit(absolute);
10426
- continue;
10427
- }
10428
- if (!entry.isFile() || entry.isSymbolicLink()) continue;
10429
- const relative5 = path33.relative(clientRoot, absolute).split(path33.sep).join("/");
10430
- if (relative5 === "index.html") continue;
10431
- files.set(
10432
- new URL(`/${relative5}`, "http://preview.local").pathname,
10433
- absolute
10434
- );
10435
- files.set(
10436
- new URL(relative5, `http://preview.local${basePath}`).pathname,
10437
- absolute
10438
- );
10439
- }
10440
- };
10441
- visit(clientRoot);
10442
- return files;
10443
- }
10444
- function writeMarkerAtomic(markerFile, marker) {
10445
- fs37.mkdirSync(path33.dirname(markerFile), { recursive: true });
10446
- const temporaryFile = `${markerFile}.${process.pid}.${Date.now()}.tmp`;
10447
- fs37.writeFileSync(temporaryFile, `${JSON.stringify(marker, null, 2)}
10448
- `, {
10449
- mode: 384
10450
- });
10451
- fs37.renameSync(temporaryFile, markerFile);
10452
- }
10453
- async function waitForCachedRuntime(origin, appBasePath, businessApiPath, timeoutMs, child) {
10454
- const timeoutAt = Date.now() + timeoutMs;
10455
- while (Date.now() < timeoutAt) {
10456
- if (child.exitCode != null || child.signalCode != null) {
10457
- throw new Error(
10458
- `cached runtime exited before readiness: ${child.exitCode ?? child.signalCode}`
10459
- );
10460
- }
10461
- try {
10462
- const response = await fetch(new URL(appBasePath, origin), {
10463
- redirect: "manual",
10464
- signal: AbortSignal.timeout(1e3)
10465
- });
10466
- if (response.status === 200) {
10467
- const businessResponse = await fetch(new URL(businessApiPath, origin), {
10468
- redirect: "manual",
10469
- signal: AbortSignal.timeout(1e3)
10470
- });
10471
- if (isPreviewBusinessResponseReady(businessResponse)) {
10472
- return;
10473
- }
10474
- }
10475
- } catch {
10476
- }
10477
- await sleep2(50);
10478
- }
10479
- throw new Error("cached runtime readiness timed out");
10480
- }
10481
- async function startPreviewArtifactRuntime(options) {
10482
- const timings = [...options.initialTimings ?? []];
10483
- const resolved = measure(
10484
- timings,
10485
- "metadata_validate",
10486
- () => resolveCurrentPreviewArtifactMetadata({
10487
- storeRoot: options.storeRoot,
10488
- nodeVersion: process.version,
10489
- platform: process.platform,
10490
- arch: process.arch,
10491
- buildEnvironmentSha256: previewArtifactBuildEnvironmentHash(
10492
- options.environment,
10493
- options.appBasePath
10494
- ),
10495
- sourceTreeValidation: options.sourceTreeValidation,
10496
- workspaceManifest: options.workspaceManifest
10497
- })
10498
- );
10499
- if (!resolved.resolved) return { started: false, reasons: resolved.reasons };
10500
- const runtimeRoot = path33.resolve(options.runtimeRoot);
10501
- const markerFile = path33.resolve(options.markerFile);
10502
- let runtimeDir;
10503
- let cachedRuntime;
10504
- let switcher;
10505
- let closed = false;
10506
- const cleanup = async () => {
10507
- if (closed) return;
10508
- closed = true;
10509
- stopProcessGroup2(cachedRuntime);
10510
- await switcher?.close();
10511
- fs37.rmSync(markerFile, { force: true });
10512
- if (runtimeDir) fs37.rmSync(runtimeDir, { recursive: true, force: true });
10513
- };
10514
- try {
10515
- ensureRegularRuntimeRoot(runtimeRoot);
10516
- if (!isPathInside3(runtimeRoot, markerFile)) {
10517
- throw new Error("runtime marker must be inside the runtime root");
10518
- }
10519
- runtimeDir = fs37.mkdtempSync(
10520
- path33.join(runtimeRoot, `${resolved.generationId}-`)
10521
- );
10522
- const runtimeBundle = path33.join(runtimeDir, "server.bundle.cjs");
10523
- const serverSource = path33.join(resolved.generationDir, "server.bundle.cjs");
10524
- const clientArchive = path33.join(runtimeDir, "client.zip");
10525
- const clientSource = path33.join(resolved.generationDir, "client.zip");
10526
- const clientRoot = path33.join(runtimeDir, "dist", "client");
10527
- const assetsReady = (async () => {
10528
- await measureAsync(
10529
- timings,
10530
- "client_materialize",
10531
- () => materializeVerifiedPreviewArtifactFile({
10532
- source: clientSource,
10533
- destination: clientArchive,
10534
- identity: resolved.manifest.files.client
10535
- }),
10536
- { bytes: resolved.manifest.files.client.bytes }
10537
- );
10538
- measure(
10539
- timings,
10540
- "client_archive_inspect",
10541
- () => inspectClientArchive(clientArchive)
10542
- );
10543
- await measureAsync(
10544
- timings,
10545
- "client_extract",
10546
- () => extractInspectedClientArchive(clientArchive, clientRoot)
10547
- );
10548
- const cachedAssetFiles2 = measure(
10549
- timings,
10550
- "asset_index",
10551
- () => collectCachedAssetFiles(clientRoot, options.appBasePath)
10552
- );
10553
- timings[timings.length - 1].fileCount = new Set(
10554
- cachedAssetFiles2.values()
10555
- ).size;
10556
- fs37.rmSync(clientArchive, { force: true });
10557
- return cachedAssetFiles2;
10558
- })();
10559
- const backendReady = (async () => {
10560
- await measureAsync(
10561
- timings,
10562
- "server_materialize",
10563
- () => materializeVerifiedPreviewArtifactFile({
10564
- source: serverSource,
10565
- destination: runtimeBundle,
10566
- identity: resolved.manifest.files.server
10567
- }),
10568
- { bytes: resolved.manifest.files.server.bytes }
10569
- );
10570
- const cachedServerPort2 = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
10571
- cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
10572
- cwd: runtimeDir,
10573
- detached: true,
10574
- stdio: "ignore",
10575
- env: {
10576
- ...process.env,
10577
- ...previewArtifactRuntimeEnvironment(options.environment),
10578
- SERVER_PORT: String(cachedServerPort2)
10579
- }
10580
- });
10581
- const cachedOrigin = `http://127.0.0.1:${cachedServerPort2}`;
10582
- await measureAsync(
10583
- timings,
10584
- "backend_spawn_to_business_ready",
10585
- async () => {
10586
- await assetsReady;
10587
- await waitForCachedRuntime(
10588
- cachedOrigin,
10589
- options.appBasePath,
10590
- options.businessApiPath,
10591
- options.startupTimeoutMs ?? 3e4,
10592
- cachedRuntime
10593
- );
10594
- }
10595
- );
10596
- return cachedServerPort2;
10597
- })();
10598
- const [backendResult, assetsResult] = await Promise.allSettled([
10599
- backendReady,
10600
- assetsReady
10601
- ]);
10602
- if (assetsResult.status === "rejected") throw assetsResult.reason;
10603
- if (backendResult.status === "rejected") throw backendResult.reason;
10604
- const cachedServerPort = backendResult.value;
10605
- const cachedAssetFiles = assetsResult.value;
10606
- switcher = createPreviewArtifactSwitcher({
10607
- listenHost: options.listenHost,
10608
- listenPort: options.listenPort,
10609
- generationId: resolved.generationId,
10610
- cachedTarget: { host: "127.0.0.1", port: cachedServerPort },
10611
- freshTarget: { host: "127.0.0.1", port: options.freshClientPort },
10612
- freshServerTarget: { host: "127.0.0.1", port: options.freshServerPort },
10613
- cachedAssetFiles,
10614
- pollIntervalMs: options.pollIntervalMs
10615
- });
10616
- const address = await measureAsync(
10617
- timings,
10618
- "switcher_listen",
10619
- () => switcher.start()
10620
- );
10621
- const runtimeMarker = {
10622
- schemaVersion: 1,
10623
- status: "cached-serving",
10624
- generationId: resolved.generationId,
10625
- publicPort: address.port,
10626
- cachedServerPort,
10627
- freshServerPort: options.freshServerPort,
10628
- freshClientPort: options.freshClientPort,
10629
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
10630
- timings
10631
- };
10632
- measure(
10633
- timings,
10634
- "marker_write",
10635
- () => writeMarkerAtomic(markerFile, runtimeMarker)
10636
- );
10637
- writeMarkerAtomic(markerFile, runtimeMarker);
10638
- const cutover = switcher.cutover.then(async (result) => {
10639
- if (result.switched) {
10640
- runtimeMarker.status = "fresh-serving";
10641
- runtimeMarker.cutoverAt = result.at;
10642
- writeMarkerAtomic(markerFile, runtimeMarker);
10643
- }
10644
- return result;
10645
- });
10646
- return {
10647
- started: true,
10648
- generationId: resolved.generationId,
10649
- origin: address.origin,
10650
- port: address.port,
10651
- runtimeDir,
10652
- cutover,
10653
- close: cleanup
10654
- };
10655
- } catch (error) {
10656
- await cleanup();
10657
- return {
10658
- started: false,
10659
- reasons: [error instanceof Error ? error.message : String(error)]
10660
- };
10661
- }
10662
- }
10663
-
10664
- // src/commands/build/index.ts
10665
- var getTokenCommand = {
10666
- name: "get-token",
10667
- description: "Get artifact upload credential (STI token)",
10668
- register(program) {
10669
- 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(
10670
- async (options) => {
10671
- await getToken(options);
10672
- }
10673
- );
10674
- }
10675
- };
10676
- var uploadStaticCommand = {
10677
- name: "upload-static",
10678
- description: "Upload shared/static files to TOS",
10679
- register(program) {
10680
- program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").option(
10681
- "--static-dir <dir>",
10682
- "Static files directory",
10683
- UPLOAD_STATIC_DEFAULTS.staticDir
10684
- ).option(
10685
- "--tosutil-path <path>",
10686
- "Path to tosutil binary",
10687
- UPLOAD_STATIC_DEFAULTS.tosutilPath
10688
- ).option(
10689
- "--endpoint <endpoint>",
10690
- "TOS endpoint",
10691
- UPLOAD_STATIC_DEFAULTS.endpoint
10692
- ).option("--region <region>", "TOS region", UPLOAD_STATIC_DEFAULTS.region).action(async (options) => {
10693
- await uploadStatic(options);
10694
- });
10695
- }
10696
- };
10697
- var preUploadStaticCommand = {
10698
- name: "pre-upload-static",
10699
- description: "Get TOS upload info and output as env vars for build.sh eval",
10700
- register(program) {
10701
- program.command(this.name).description(this.description).requiredOption("--app-id <id>", "Application ID").action(async (options) => {
10702
- await preUploadStatic(options);
10703
- });
10704
- }
10705
- };
10706
- var previewServerArtifactCommand = {
10707
- name: "preview-server-artifact",
10708
- description: "Build and audit a single-file Nest preview server artifact",
10709
- register(program) {
10710
- 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(
10711
- "--outfile <file>",
10712
- "Output bundle",
10713
- ".preview-artifact/server.bundle.cjs"
10714
- ).option("--metadata-file <file>", "Output build metadata file").action(
10715
- async (options) => {
10716
- const result = await buildPreviewServerArtifact(options);
10717
- console.log(JSON.stringify(result));
10718
- if (!result.built) {
10719
- process.exitCode = 2;
10720
- }
10721
- }
10722
- );
10723
- }
10724
- };
10725
- var previewArtifactWorkspaceManifestCommand = {
10726
- name: "preview-artifact-workspace-manifest",
10727
- description: "Snapshot the Git-visible Workspace content identity for backup and cache validation",
10728
- register(program) {
10729
- program.command(this.name).description(this.description).option("--project-root <dir>", "Application project root", process.cwd()).requiredOption(
10730
- "--manifest-file <file>",
10731
- "Workspace Manifest sidecar output file"
10732
- ).requiredOption(
10733
- "--workspace-backup-id <id>",
10734
- "Physical Workspace backup identity"
10735
- ).requiredOption("--app-id <id>", "Application ID").option(
10736
- "--validate-source-tree",
10737
- "Calculate a sourceTreeId and bind cache consumption to it"
10738
- ).option(
10739
- "--source-tree-include <path>",
10740
- "Repository-relative file or directory included in sourceTreeId; repeatable",
10741
- (value, previous) => [...previous, value],
10742
- []
10743
- ).action(
10744
- (options) => {
10745
- const manifest = createPreviewArtifactWorkspaceManifest({
10746
- ...options,
10747
- sourceTreeIncludePaths: options.sourceTreeInclude
10748
- });
10749
- console.log(JSON.stringify(manifest));
10750
- }
10751
- );
10752
- }
10753
- };
10754
- var previewArtifactGenerationCommand = {
10755
- name: "preview-artifact-generation",
10756
- description: "Build, probe, promote, and atomically publish a Preview Artifact generation",
10757
- register(program) {
10758
- 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(
10759
- "--business-api-path <path>",
10760
- "Business API probe path that must not return 404/5xx"
10761
- ).requiredOption(
10762
- "--environment-file <file>",
10763
- "JSON application environment passed only to build/probe children"
10764
- ).requiredOption(
10765
- "--workspace-manifest-file <file>",
10766
- "Workspace Manifest sidecar created at backup start"
10767
- ).action(
10768
- async (options) => {
10769
- const result = await producePreviewArtifactGeneration({
10770
- ...options,
10771
- environment: readPreviewArtifactEnvironmentFile(
10772
- options.environmentFile
10773
- ),
10774
- workspaceManifest: readPreviewArtifactWorkspaceManifest(
10775
- options.workspaceManifestFile
10776
- )
10777
- });
10778
- console.log(JSON.stringify(result));
10779
- if (!result.published) process.exitCode = 2;
10780
- }
10781
- );
10782
- }
10783
- };
10784
- function parseTcpPort(value) {
10785
- const port = Number(value);
10786
- if (!Number.isInteger(port) || port < 0 || port > 65535) {
10787
- throw new Error(`invalid TCP port: ${value}`);
10788
- }
10789
- return port;
10790
- }
10791
- var previewArtifactRuntimeCommand = {
10792
- name: "preview-artifact-runtime",
10793
- description: "Serve a verified Preview Artifact and switch to the fresh dev service",
10794
- register(program) {
10795
- program.command(this.name).description(this.description).requiredOption("--store-root <dir>", "Immutable generation store root").option(
10796
- "--runtime-root <dir>",
10797
- "Ephemeral runtime root",
10798
- "/tmp/preview-artifact"
10799
- ).option(
10800
- "--marker-file <file>",
10801
- "Atomic runtime marker file",
10802
- "/tmp/preview-artifact/runtime.json"
10803
- ).option(
10804
- "--listen-port <port>",
10805
- "Stable public preview port",
10806
- parseTcpPort,
10807
- 8001
10808
- ).option(
10809
- "--cached-server-port <port>",
10810
- "Isolated cached Nest server port, 0 selects a free port",
10811
- parseTcpPort,
10812
- 0
10813
- ).option(
10814
- "--fresh-server-port <port>",
10815
- "Fresh Nest shadow port recorded for dev startup",
10816
- parseTcpPort,
10817
- 3001
10818
- ).option(
10819
- "--fresh-client-port <port>",
10820
- "Fresh Vite shadow port used as the cutover target",
10821
- parseTcpPort,
10822
- 8002
10823
- ).requiredOption(
10824
- "--app-base-path <path>",
10825
- "Application HTML readiness path"
10826
- ).requiredOption(
10827
- "--business-api-path <path>",
10828
- "Business API path revalidated with the current runtime environment"
10829
- ).requiredOption(
10830
- "--environment-file <file>",
10831
- "JSON application environment passed only to cached application child"
10832
- ).requiredOption(
10833
- "--workspace-manifest-file <file>",
10834
- "Workspace Manifest sidecar restored with this backup"
10835
- ).option(
10836
- "--validate-source-tree",
10837
- "Reject cache when its sourceTreeId does not match the Workspace backup"
10838
- ).option(
10839
- "--source-tree-include <path>",
10840
- "Repository-relative sourceTreeId scope; repeatable",
10841
- (value, previous) => [...previous, value],
10842
- []
10843
- ).action(
10844
- async (options) => {
10845
- const environmentLoadStartedAt = Date.now();
10846
- const environment = readPreviewArtifactEnvironmentFile(
10847
- options.environmentFile
10848
- );
10849
- const environmentLoadDurationMs = Date.now() - environmentLoadStartedAt;
10850
- const workspaceManifest = readPreviewArtifactWorkspaceManifest(
10851
- options.workspaceManifestFile
10852
- );
10853
- const sourceTreeValidation = createPreviewArtifactSourceTreeValidation(
10854
- options.validateSourceTree,
10855
- options.sourceTreeInclude
10856
- );
10857
- const runtime = await startPreviewArtifactRuntime({
10858
- ...options,
10859
- environment,
10860
- workspaceManifest,
10861
- sourceTreeValidation,
10862
- initialTimings: [
10863
- {
10864
- phase: "cli_bootstrap",
10865
- durationMs: Math.max(0, Math.round(process.uptime() * 1e3))
10866
- },
10867
- {
10868
- phase: "environment_load",
10869
- durationMs: environmentLoadDurationMs
10870
- }
10871
- ]
10872
- });
10873
- if (!runtime.started) {
10874
- console.log(JSON.stringify(runtime));
10875
- process.exitCode = 2;
10876
- return;
10877
- }
10878
- console.log(
10879
- JSON.stringify({
10880
- started: true,
10881
- generationId: runtime.generationId,
10882
- origin: runtime.origin,
10883
- runtimeDir: runtime.runtimeDir
10884
- })
10885
- );
10886
- void runtime.cutover.then((result) => {
10887
- console.log(JSON.stringify({ event: "fresh-cutover", ...result }));
10888
- });
10889
- const shutdown = async () => {
10890
- await runtime.close();
10891
- process.exit(0);
10892
- };
10893
- process.once("SIGTERM", () => void shutdown());
10894
- process.once("SIGINT", () => void shutdown());
10895
- }
10896
- );
10897
- }
10898
- };
10899
- var buildCommandGroup = {
10900
- name: "build",
10901
- description: "Build related commands",
10902
- commands: [
10903
- getTokenCommand,
10904
- uploadStaticCommand,
10905
- preUploadStaticCommand,
10906
- previewServerArtifactCommand,
10907
- previewArtifactWorkspaceManifestCommand,
10908
- previewArtifactGenerationCommand,
10909
- previewArtifactRuntimeCommand
10910
- ]
10911
- };
10912
-
10913
- // src/commands/index.ts
10914
- var commands = [
10915
- genDbSchemaCommand,
10916
- syncCommand,
10917
- upgradeCommand,
10918
- actionPluginCommandGroup,
10919
- capabilityCommandGroup,
10920
- componentCommandGroup,
10921
- migrationCommand,
10922
- readLogsCommand,
10923
- buildCommandGroup
10924
- ];
10925
-
10926
- // src/index.ts
10927
- var envPath = path34.join(process.cwd(), ".env");
10928
- if (fs38.existsSync(envPath)) {
10929
- dotenvConfig({ path: envPath });
10930
8277
  }
10931
- var __dirname = path34.dirname(fileURLToPath5(import.meta.url));
10932
- var pkg = JSON.parse(fs38.readFileSync(path34.join(__dirname, "../package.json"), "utf-8"));
8278
+ var __dirname = path25.dirname(fileURLToPath5(import.meta.url));
8279
+ var pkg = JSON.parse(fs29.readFileSync(path25.join(__dirname, "../package.json"), "utf-8"));
10933
8280
  var cli = new FullstackCLI(pkg.version);
10934
8281
  cli.useAll(commands);
10935
8282
  cli.run();