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