@lark-apaas/fullstack-cli 1.1.59-alpha.20260722071909 → 1.1.59-alpha.20260722165429

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.
Files changed (2) hide show
  1. package/dist/index.js +281 -69
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8261,6 +8261,57 @@ function classTransformerStorageResolver(projectRoot) {
8261
8261
  }
8262
8262
  };
8263
8263
  }
8264
+ function projectUsesNestjsDevtools(projectRoot) {
8265
+ const distRoot = path25.join(projectRoot, "dist");
8266
+ if (!fs29.existsSync(distRoot)) return false;
8267
+ const pending = [distRoot];
8268
+ while (pending.length > 0) {
8269
+ const current = pending.pop();
8270
+ if (!current) continue;
8271
+ for (const entry of fs29.readdirSync(current, { withFileTypes: true })) {
8272
+ const entryPath = path25.join(current, entry.name);
8273
+ if (entry.isDirectory()) {
8274
+ pending.push(entryPath);
8275
+ } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) {
8276
+ const source = fs29.readFileSync(entryPath, "utf8");
8277
+ if (source.includes("@lark-apaas/fullstack-nestjs-core") && /\bDevTools(?:V2)?Module\b/.test(source)) {
8278
+ return true;
8279
+ }
8280
+ }
8281
+ }
8282
+ }
8283
+ return false;
8284
+ }
8285
+ function previewNestjsRuntimeEntryPlugin(projectRoot) {
8286
+ const projectRequire = createRequire3(path25.join(projectRoot, "package.json"));
8287
+ let runtimeEntry;
8288
+ if (!projectUsesNestjsDevtools(projectRoot)) {
8289
+ try {
8290
+ runtimeEntry = projectRequire.resolve(
8291
+ "@lark-apaas/fullstack-nestjs-core/runtime"
8292
+ );
8293
+ } catch {
8294
+ runtimeEntry = void 0;
8295
+ }
8296
+ }
8297
+ let runtimeEntryUsed = false;
8298
+ return {
8299
+ used: () => runtimeEntryUsed,
8300
+ plugin: {
8301
+ name: "preview-nestjs-runtime-entry",
8302
+ setup(esbuild) {
8303
+ esbuild.onResolve(
8304
+ { filter: /^@lark-apaas\/fullstack-nestjs-core$/ },
8305
+ () => {
8306
+ if (!runtimeEntry) return void 0;
8307
+ runtimeEntryUsed = true;
8308
+ return { path: runtimeEntry };
8309
+ }
8310
+ );
8311
+ }
8312
+ }
8313
+ };
8314
+ }
8264
8315
  function optionalDependencyStubPlugin() {
8265
8316
  const stubbedDependencies = /* @__PURE__ */ new Set();
8266
8317
  const escapedNames = OPTIONAL_DEPENDENCY_STUBS.map(
@@ -8523,6 +8574,7 @@ async function buildPreviewServerArtifact(options) {
8523
8574
  );
8524
8575
  fs29.writeFileSync(bootstrapPath, generateBootstrap(entryPath, plugins));
8525
8576
  const optionalDependencies = optionalDependencyStubPlugin();
8577
+ const nestjsRuntimeEntry = previewNestjsRuntimeEntryPlugin(projectRoot);
8526
8578
  const buildOptions = {
8527
8579
  absWorkingDir: projectRoot,
8528
8580
  entryPoints: [bootstrapPath],
@@ -8538,6 +8590,7 @@ async function buildPreviewServerArtifact(options) {
8538
8590
  metafile: true,
8539
8591
  logLevel: "silent",
8540
8592
  plugins: [
8593
+ nestjsRuntimeEntry.plugin,
8541
8594
  classTransformerStorageResolver(projectRoot),
8542
8595
  optionalDependencies.plugin
8543
8596
  ]
@@ -8574,6 +8627,7 @@ async function buildPreviewServerArtifact(options) {
8574
8627
  closureStatus,
8575
8628
  closureRisks,
8576
8629
  stubbedOptionalDependencies,
8630
+ nestjsRuntimeEntryUsed: nestjsRuntimeEntry.used(),
8577
8631
  runtimeProbeStatus: "not-run",
8578
8632
  consumable: false,
8579
8633
  elapsedMs: Date.now() - startedAt
@@ -8623,7 +8677,7 @@ async function buildPreviewServerArtifact(options) {
8623
8677
  }
8624
8678
 
8625
8679
  // src/commands/preview-artifact/producer.ts
8626
- import * as crypto2 from "crypto";
8680
+ import * as crypto3 from "crypto";
8627
8681
  import * as fs34 from "fs";
8628
8682
  import * as path30 from "path";
8629
8683
  import { spawnSync as spawnSync8 } from "child_process";
@@ -8929,6 +8983,10 @@ function rejectSymlinks(root) {
8929
8983
  }
8930
8984
  }
8931
8985
  }
8986
+ function inspectClientArchive(archivePath) {
8987
+ archiveEntries(archivePath);
8988
+ rejectArchivedSymlinks(archivePath);
8989
+ }
8932
8990
  function isPathInside(parent, candidate) {
8933
8991
  const relative5 = path27.relative(parent, candidate);
8934
8992
  return relative5 !== "" && relative5 !== ".." && !relative5.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative5);
@@ -9004,8 +9062,7 @@ function extractClientArchive(archivePath, destination) {
9004
9062
  if (fs31.existsSync(destination)) {
9005
9063
  throw new Error("client archive destination must not already exist");
9006
9064
  }
9007
- archiveEntries(archivePath);
9008
- rejectArchivedSymlinks(archivePath);
9065
+ inspectClientArchive(archivePath);
9009
9066
  fs31.mkdirSync(destination, { recursive: true });
9010
9067
  try {
9011
9068
  const extraction = spawnSync7(
@@ -9024,6 +9081,40 @@ function extractClientArchive(archivePath, destination) {
9024
9081
  throw error;
9025
9082
  }
9026
9083
  }
9084
+ async function extractInspectedClientArchive(archivePath, destination) {
9085
+ if (fs31.existsSync(destination)) {
9086
+ throw new Error("client archive destination must not already exist");
9087
+ }
9088
+ fs31.mkdirSync(destination, { recursive: true });
9089
+ try {
9090
+ await new Promise((resolve9, reject) => {
9091
+ const extraction = spawn2(
9092
+ "unzip",
9093
+ ["-q", archivePath, "-d", destination],
9094
+ { stdio: ["ignore", "ignore", "pipe"] }
9095
+ );
9096
+ let stderr = "";
9097
+ extraction.stderr?.setEncoding("utf8");
9098
+ extraction.stderr?.on("data", (chunk) => {
9099
+ stderr = `${stderr}${String(chunk)}`.slice(-8 * 1024);
9100
+ });
9101
+ extraction.once("error", reject);
9102
+ extraction.once("close", (code) => {
9103
+ if (code === 0) resolve9();
9104
+ else
9105
+ reject(
9106
+ new Error(
9107
+ `client archive extraction failed: ${stderr.trim() || `exit ${code ?? "unknown"}`}`
9108
+ )
9109
+ );
9110
+ });
9111
+ });
9112
+ rejectSymlinks(destination);
9113
+ } catch (error) {
9114
+ fs31.rmSync(destination, { recursive: true, force: true });
9115
+ throw error;
9116
+ }
9117
+ }
9027
9118
  async function fetchUntil(url, timeoutAt) {
9028
9119
  while (Date.now() < timeoutAt) {
9029
9120
  try {
@@ -9200,6 +9291,7 @@ function previewArtifactRuntimeEnvironment(environment) {
9200
9291
  }
9201
9292
 
9202
9293
  // src/commands/preview-artifact/store.ts
9294
+ import * as crypto2 from "crypto";
9203
9295
  import * as fs33 from "fs";
9204
9296
  import * as path29 from "path";
9205
9297
  var GENERATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
@@ -9261,7 +9353,7 @@ function validateRegularDirectory(directoryPath, label) {
9261
9353
  ];
9262
9354
  }
9263
9355
  }
9264
- function validateGeneration(generationDir, runtime) {
9356
+ function validateGeneration(generationDir, runtime, verifyPayloads = true) {
9265
9357
  const reasons = validateRegularDirectory(
9266
9358
  generationDir,
9267
9359
  "generation directory"
@@ -9301,19 +9393,29 @@ function validateGeneration(generationDir, runtime) {
9301
9393
  if (!/^[a-f0-9]{64}$/.test(manifest.buildEnvironmentSha256)) {
9302
9394
  reasons.push("build environment identity is invalid");
9303
9395
  }
9304
- const serverPath = path29.join(generationDir, "server.bundle.cjs");
9305
- const clientPath = path29.join(generationDir, "client.zip");
9306
- try {
9307
- if (sha256File(serverPath) !== manifest.files.server.sha256) {
9308
- reasons.push("server.bundle.cjs hash does not match manifest");
9396
+ for (const [label, identity, expectedPath] of [
9397
+ ["server", manifest.files?.server, "server.bundle.cjs"],
9398
+ ["client", manifest.files?.client, "client.zip"]
9399
+ ]) {
9400
+ if (!identity || identity.path !== expectedPath || !Number.isSafeInteger(identity.bytes) || identity.bytes < 0 || !/^[a-f0-9]{64}$/.test(identity.sha256)) {
9401
+ reasons.push(`${label} file identity is invalid`);
9309
9402
  }
9310
- if (sha256File(clientPath) !== manifest.files.client.sha256) {
9311
- reasons.push("client.zip hash does not match manifest");
9403
+ }
9404
+ if (verifyPayloads && manifest.files?.server && manifest.files?.client) {
9405
+ const serverPath = path29.join(generationDir, "server.bundle.cjs");
9406
+ const clientPath = path29.join(generationDir, "client.zip");
9407
+ try {
9408
+ if (sha256File(serverPath) !== manifest.files.server.sha256) {
9409
+ reasons.push("server.bundle.cjs hash does not match manifest");
9410
+ }
9411
+ if (sha256File(clientPath) !== manifest.files.client.sha256) {
9412
+ reasons.push("client.zip hash does not match manifest");
9413
+ }
9414
+ } catch (error) {
9415
+ reasons.push(
9416
+ `generation files are unavailable: ${error instanceof Error ? error.message : String(error)}`
9417
+ );
9312
9418
  }
9313
- } catch (error) {
9314
- reasons.push(
9315
- `generation files are unavailable: ${error instanceof Error ? error.message : String(error)}`
9316
- );
9317
9419
  }
9318
9420
  if (probe.serverSha256 !== manifest.files.server.sha256 || probe.clientSha256 !== manifest.files.client.sha256) {
9319
9421
  reasons.push("probe file identity does not match manifest");
@@ -9374,7 +9476,7 @@ function publishPreviewArtifactGeneration(options) {
9374
9476
  };
9375
9477
  }
9376
9478
  }
9377
- function resolveCurrentPreviewArtifact(options) {
9479
+ function resolveCurrentPreviewArtifactMetadata(options) {
9378
9480
  const storeRoot = path29.resolve(options.storeRoot);
9379
9481
  let pointer;
9380
9482
  try {
@@ -9402,11 +9504,15 @@ function resolveCurrentPreviewArtifact(options) {
9402
9504
  "generations",
9403
9505
  pointer.generationId
9404
9506
  );
9405
- const validation = validateGeneration(generationDir, {
9406
- nodeMajor,
9407
- platform: options.platform,
9408
- arch: options.arch
9409
- });
9507
+ const validation = validateGeneration(
9508
+ generationDir,
9509
+ {
9510
+ nodeMajor,
9511
+ platform: options.platform,
9512
+ arch: options.arch
9513
+ },
9514
+ false
9515
+ );
9410
9516
  if (!validation.manifest || validation.reasons.length > 0) {
9411
9517
  return { resolved: false, reasons: validation.reasons };
9412
9518
  }
@@ -9429,11 +9535,85 @@ function resolveCurrentPreviewArtifact(options) {
9429
9535
  manifest: validation.manifest
9430
9536
  };
9431
9537
  }
9538
+ async function materializeVerifiedPreviewArtifactFile(options) {
9539
+ const source = path29.resolve(options.source);
9540
+ const destination = path29.resolve(options.destination);
9541
+ const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
9542
+ fs33.mkdirSync(path29.dirname(destination), { recursive: true });
9543
+ if (fs33.existsSync(destination)) {
9544
+ throw new Error(
9545
+ `materialization destination already exists: ${destination}`
9546
+ );
9547
+ }
9548
+ const noFollow = fs33.constants.O_NOFOLLOW ?? 0;
9549
+ let sourceHandle;
9550
+ let destinationHandle;
9551
+ try {
9552
+ sourceHandle = await fs33.promises.open(
9553
+ source,
9554
+ fs33.constants.O_RDONLY | noFollow
9555
+ );
9556
+ const sourceStat = await sourceHandle.stat();
9557
+ if (!sourceStat.isFile()) {
9558
+ throw new Error(`${options.identity.path} is not a regular file`);
9559
+ }
9560
+ destinationHandle = await fs33.promises.open(
9561
+ temporary,
9562
+ fs33.constants.O_WRONLY | fs33.constants.O_CREAT | fs33.constants.O_EXCL,
9563
+ 384
9564
+ );
9565
+ const hash = crypto2.createHash("sha256");
9566
+ const buffer = Buffer.allocUnsafe(256 * 1024);
9567
+ let totalBytes = 0;
9568
+ for (; ; ) {
9569
+ const { bytesRead } = await sourceHandle.read(
9570
+ buffer,
9571
+ 0,
9572
+ buffer.length,
9573
+ null
9574
+ );
9575
+ if (bytesRead === 0) break;
9576
+ hash.update(buffer.subarray(0, bytesRead));
9577
+ let written = 0;
9578
+ while (written < bytesRead) {
9579
+ const result = await destinationHandle.write(
9580
+ buffer,
9581
+ written,
9582
+ bytesRead - written,
9583
+ null
9584
+ );
9585
+ if (result.bytesWritten <= 0) {
9586
+ throw new Error(
9587
+ `${options.identity.path} materialization short write`
9588
+ );
9589
+ }
9590
+ written += result.bytesWritten;
9591
+ }
9592
+ totalBytes += bytesRead;
9593
+ }
9594
+ const sha256 = hash.digest("hex");
9595
+ if (sha256 !== options.identity.sha256) {
9596
+ throw new Error(`${options.identity.path} hash does not match manifest`);
9597
+ }
9598
+ if (totalBytes !== options.identity.bytes) {
9599
+ throw new Error(`${options.identity.path} bytes do not match manifest`);
9600
+ }
9601
+ await destinationHandle.sync();
9602
+ await destinationHandle.close();
9603
+ destinationHandle = void 0;
9604
+ await fs33.promises.rename(temporary, destination);
9605
+ return { bytes: totalBytes, sha256 };
9606
+ } finally {
9607
+ await destinationHandle?.close().catch(() => void 0);
9608
+ await sourceHandle?.close().catch(() => void 0);
9609
+ await fs33.promises.rm(temporary, { force: true }).catch(() => void 0);
9610
+ }
9611
+ }
9432
9612
 
9433
9613
  // src/commands/preview-artifact/producer.ts
9434
9614
  function defaultGenerationId() {
9435
9615
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "");
9436
- return `${timestamp}-${crypto2.randomBytes(5).toString("hex")}`;
9616
+ return `${timestamp}-${crypto3.randomBytes(5).toString("hex")}`;
9437
9617
  }
9438
9618
  function defaultPreviewBundleBuildCommands(projectRootInput) {
9439
9619
  const projectRoot = path30.resolve(projectRootInput);
@@ -9957,7 +10137,7 @@ async function startPreviewArtifactRuntime(options) {
9957
10137
  const resolved = measure(
9958
10138
  timings,
9959
10139
  "metadata_validate",
9960
- () => resolveCurrentPreviewArtifact({
10140
+ () => resolveCurrentPreviewArtifactMetadata({
9961
10141
  storeRoot: options.storeRoot,
9962
10142
  nodeVersion: process.version,
9963
10143
  platform: process.platform,
@@ -9993,53 +10173,85 @@ async function startPreviewArtifactRuntime(options) {
9993
10173
  );
9994
10174
  const runtimeBundle = path32.join(runtimeDir, "server.bundle.cjs");
9995
10175
  const serverSource = path32.join(resolved.generationDir, "server.bundle.cjs");
9996
- measure(
9997
- timings,
9998
- "server_materialize",
9999
- () => fs36.copyFileSync(serverSource, runtimeBundle),
10000
- { bytes: resolved.manifest.files.server.bytes }
10001
- );
10176
+ const clientArchive = path32.join(runtimeDir, "client.zip");
10177
+ const clientSource = path32.join(resolved.generationDir, "client.zip");
10002
10178
  const clientRoot = path32.join(runtimeDir, "dist", "client");
10003
- measure(
10004
- timings,
10005
- "client_archive_inspect_extract",
10006
- () => extractClientArchive(
10007
- path32.join(resolved.generationDir, "client.zip"),
10008
- clientRoot
10009
- ),
10010
- { bytes: resolved.manifest.files.client.bytes }
10011
- );
10012
- const cachedServerPort = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
10013
- cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
10014
- cwd: runtimeDir,
10015
- detached: true,
10016
- stdio: "ignore",
10017
- env: {
10018
- ...process.env,
10019
- ...previewArtifactRuntimeEnvironment(options.environment),
10020
- SERVER_PORT: String(cachedServerPort)
10021
- }
10022
- });
10023
- const cachedOrigin = `http://127.0.0.1:${cachedServerPort}`;
10024
- await measureAsync(
10025
- timings,
10026
- "backend_spawn_to_business_ready",
10027
- () => waitForCachedRuntime(
10028
- cachedOrigin,
10029
- options.appBasePath,
10030
- options.businessApiPath,
10031
- options.startupTimeoutMs ?? 3e4,
10032
- cachedRuntime
10033
- )
10034
- );
10035
- const cachedAssetFiles = measure(
10036
- timings,
10037
- "asset_index",
10038
- () => collectCachedAssetFiles(clientRoot, options.appBasePath)
10039
- );
10040
- timings[timings.length - 1].fileCount = new Set(
10041
- cachedAssetFiles.values()
10042
- ).size;
10179
+ const backendReady = (async () => {
10180
+ await measureAsync(
10181
+ timings,
10182
+ "server_materialize",
10183
+ () => materializeVerifiedPreviewArtifactFile({
10184
+ source: serverSource,
10185
+ destination: runtimeBundle,
10186
+ identity: resolved.manifest.files.server
10187
+ }),
10188
+ { bytes: resolved.manifest.files.server.bytes }
10189
+ );
10190
+ const cachedServerPort2 = options.cachedServerPort === 0 ? await reservePort2() : options.cachedServerPort;
10191
+ cachedRuntime = spawn3(process.execPath, [runtimeBundle], {
10192
+ cwd: runtimeDir,
10193
+ detached: true,
10194
+ stdio: "ignore",
10195
+ env: {
10196
+ ...process.env,
10197
+ ...previewArtifactRuntimeEnvironment(options.environment),
10198
+ SERVER_PORT: String(cachedServerPort2)
10199
+ }
10200
+ });
10201
+ const cachedOrigin = `http://127.0.0.1:${cachedServerPort2}`;
10202
+ await measureAsync(
10203
+ timings,
10204
+ "backend_spawn_to_business_ready",
10205
+ () => waitForCachedRuntime(
10206
+ cachedOrigin,
10207
+ options.appBasePath,
10208
+ options.businessApiPath,
10209
+ options.startupTimeoutMs ?? 3e4,
10210
+ cachedRuntime
10211
+ )
10212
+ );
10213
+ return cachedServerPort2;
10214
+ })();
10215
+ const assetsReady = (async () => {
10216
+ await measureAsync(
10217
+ timings,
10218
+ "client_materialize",
10219
+ () => materializeVerifiedPreviewArtifactFile({
10220
+ source: clientSource,
10221
+ destination: clientArchive,
10222
+ identity: resolved.manifest.files.client
10223
+ }),
10224
+ { bytes: resolved.manifest.files.client.bytes }
10225
+ );
10226
+ measure(
10227
+ timings,
10228
+ "client_archive_inspect",
10229
+ () => inspectClientArchive(clientArchive)
10230
+ );
10231
+ await measureAsync(
10232
+ timings,
10233
+ "client_extract",
10234
+ () => extractInspectedClientArchive(clientArchive, clientRoot)
10235
+ );
10236
+ const cachedAssetFiles2 = measure(
10237
+ timings,
10238
+ "asset_index",
10239
+ () => collectCachedAssetFiles(clientRoot, options.appBasePath)
10240
+ );
10241
+ timings[timings.length - 1].fileCount = new Set(
10242
+ cachedAssetFiles2.values()
10243
+ ).size;
10244
+ fs36.rmSync(clientArchive, { force: true });
10245
+ return cachedAssetFiles2;
10246
+ })();
10247
+ const [backendResult, assetsResult] = await Promise.allSettled([
10248
+ backendReady,
10249
+ assetsReady
10250
+ ]);
10251
+ if (assetsResult.status === "rejected") throw assetsResult.reason;
10252
+ if (backendResult.status === "rejected") throw backendResult.reason;
10253
+ const cachedServerPort = backendResult.value;
10254
+ const cachedAssetFiles = assetsResult.value;
10043
10255
  switcher = createPreviewArtifactSwitcher({
10044
10256
  listenHost: options.listenHost,
10045
10257
  listenPort: options.listenPort,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.59-alpha.20260722071909",
3
+ "version": "1.1.59-alpha.20260722165429",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",