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