@lark-apaas/fullstack-cli 1.1.60 → 1.1.61-alpha.20260818172555

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.
@@ -0,0 +1,37 @@
1
+ interface ClientDependencyGraphEntry {
2
+ specifier: string;
3
+ packagePath: string;
4
+ }
5
+ interface ClientDependencyGraphPackage {
6
+ path: string;
7
+ version: string;
8
+ integrity: string | null;
9
+ edges: Record<string, string>;
10
+ }
11
+ interface ClientDependencyConfigFile {
12
+ path: string;
13
+ sha256: string;
14
+ }
15
+ interface ClientDependencyGraph {
16
+ schemaVersion: 1;
17
+ algorithm: 'npm-installed-client-closure-v2';
18
+ runtimeAbiHash: string;
19
+ resolveConditions: ['browser', 'development', 'module', 'import'];
20
+ configFiles: ClientDependencyConfigFile[];
21
+ configEnvironment: Record<string, string>;
22
+ entries: ClientDependencyGraphEntry[];
23
+ packages: ClientDependencyGraphPackage[];
24
+ }
25
+ interface BuildClientDependencyGraphResult {
26
+ graph: ClientDependencyGraph;
27
+ hash: string;
28
+ optimizeDependencies: string[];
29
+ }
30
+ declare function packageNameFromSpecifier(specifier: string): string | undefined;
31
+ declare function buildClientDependencyGraph(options: {
32
+ projectRoot: string;
33
+ runtimeAbiHash: string;
34
+ includeAllProductionDependencies?: boolean;
35
+ }): BuildClientDependencyGraphResult;
36
+
37
+ export { type BuildClientDependencyGraphResult, type ClientDependencyConfigFile, type ClientDependencyGraph, type ClientDependencyGraphEntry, type ClientDependencyGraphPackage, buildClientDependencyGraph, packageNameFromSpecifier };
@@ -0,0 +1,466 @@
1
+ // src/client-dependency-graph.ts
2
+ import crypto from "crypto";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { builtinModules } from "module";
6
+ import { Node, Project, SyntaxKind } from "ts-morph";
7
+ var BUILTIN_MODULES = /* @__PURE__ */ new Set([
8
+ ...builtinModules,
9
+ ...builtinModules.map((name) => `node:${name}`)
10
+ ]);
11
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
12
+ var STYLE_EXTENSIONS = [
13
+ ".css",
14
+ ".less",
15
+ ".sass",
16
+ ".scss",
17
+ ".styl",
18
+ ".stylus"
19
+ ];
20
+ function packageNameFromSpecifier(specifier) {
21
+ if (!specifier || specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("\0") || specifier.includes("://") || BUILTIN_MODULES.has(specifier)) {
22
+ return void 0;
23
+ }
24
+ const parts = specifier.split("/");
25
+ return specifier.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
26
+ }
27
+ function isStyleSpecifier(specifier) {
28
+ return STYLE_EXTENSIONS.some(
29
+ (extension) => new RegExp(`\\${extension}(?:$|\\?)`, "i").test(specifier)
30
+ );
31
+ }
32
+ function resolveLocalFile(specifier, importer, projectRoot, clientRoot) {
33
+ let candidate;
34
+ if (specifier.startsWith("@/")) {
35
+ candidate = path.join(clientRoot, "src", specifier.slice(2));
36
+ } else if (specifier.startsWith("@client/")) {
37
+ candidate = path.join(clientRoot, specifier.slice("@client/".length));
38
+ } else if (specifier.startsWith("@shared/")) {
39
+ candidate = path.join(
40
+ projectRoot,
41
+ "shared",
42
+ specifier.slice("@shared/".length)
43
+ );
44
+ } else if (specifier.startsWith(".") || path.isAbsolute(specifier)) {
45
+ candidate = path.resolve(path.dirname(importer), specifier);
46
+ }
47
+ if (!candidate) return void 0;
48
+ const projectRelative = path.relative(projectRoot, path.resolve(candidate));
49
+ if (projectRelative === ".." || projectRelative.startsWith(`..${path.sep}`) || path.isAbsolute(projectRelative)) {
50
+ throw new Error(
51
+ `MIAODA_CLIENT_SOURCE_PATH_ESCAPE: ${specifier} from ${path.relative(
52
+ projectRoot,
53
+ importer
54
+ )}`
55
+ );
56
+ }
57
+ const extensions = [...SOURCE_EXTENSIONS, ...STYLE_EXTENSIONS];
58
+ const candidates = [
59
+ candidate,
60
+ ...extensions.map((extension) => `${candidate}${extension}`),
61
+ ...extensions.map((extension) => path.join(candidate, `index${extension}`))
62
+ ];
63
+ const resolvedFile = candidates.find((file) => {
64
+ try {
65
+ return fs.statSync(file).isFile();
66
+ } catch {
67
+ return false;
68
+ }
69
+ });
70
+ if (!resolvedFile) return void 0;
71
+ const realProjectRoot = fs.realpathSync(projectRoot);
72
+ const realFile = fs.realpathSync(resolvedFile);
73
+ const relative = path.relative(realProjectRoot, realFile);
74
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
75
+ throw new Error(`MIAODA_CLIENT_SOURCE_SYMLINK_ESCAPE: ${resolvedFile}`);
76
+ }
77
+ return resolvedFile;
78
+ }
79
+ function discoverClientSpecifiers(projectRoot, scanAllSourceFiles = false) {
80
+ const clientRoot = fs.existsSync(path.join(projectRoot, "client")) ? path.join(projectRoot, "client") : projectRoot;
81
+ if (!fs.existsSync(path.join(clientRoot, "src"))) {
82
+ return { specifiers: [], optimizeDependencies: [] };
83
+ }
84
+ const project = new Project({
85
+ skipAddingFilesFromTsConfig: true,
86
+ compilerOptions: { allowJs: true, jsx: 2 }
87
+ });
88
+ const specifiers = /* @__PURE__ */ new Set();
89
+ const optimizeDependencies = /* @__PURE__ */ new Set();
90
+ const pending = SOURCE_EXTENSIONS.map(
91
+ (extension) => path.join(clientRoot, "src", `index${extension}`)
92
+ ).filter((file) => fs.existsSync(file));
93
+ if (scanAllSourceFiles) {
94
+ const visit = (directory) => {
95
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
96
+ const file = path.join(directory, entry.name);
97
+ if (entry.isSymbolicLink()) {
98
+ throw new Error(`MIAODA_CLIENT_SOURCE_SYMLINK_REJECTED: ${file}`);
99
+ }
100
+ if (entry.isDirectory()) visit(file);
101
+ else if (entry.isFile() && [...SOURCE_EXTENSIONS, ...STYLE_EXTENSIONS].includes(
102
+ path.extname(file)
103
+ )) {
104
+ pending.push(file);
105
+ }
106
+ }
107
+ };
108
+ visit(path.join(clientRoot, "src"));
109
+ }
110
+ const visited = /* @__PURE__ */ new Set();
111
+ const addBare = (specifier, optimize) => {
112
+ if (!packageNameFromSpecifier(specifier)) return;
113
+ specifiers.add(specifier);
114
+ if (optimize) optimizeDependencies.add(specifier);
115
+ };
116
+ const traverse = (specifier, importer, optimize = true) => {
117
+ const dependency = resolveLocalFile(
118
+ specifier,
119
+ importer,
120
+ projectRoot,
121
+ clientRoot
122
+ );
123
+ if (dependency) pending.push(dependency);
124
+ else addBare(specifier, optimize && !isStyleSpecifier(specifier));
125
+ };
126
+ while (pending.length > 0) {
127
+ const sourcePath = path.resolve(pending.pop());
128
+ if (visited.has(sourcePath)) continue;
129
+ visited.add(sourcePath);
130
+ if (STYLE_EXTENSIONS.includes(path.extname(sourcePath))) {
131
+ const css = fs.readFileSync(sourcePath, "utf8");
132
+ for (const match of css.matchAll(
133
+ /@(import|plugin)\s+(?:url\(\s*)?["']([^"']+)["']/g
134
+ )) {
135
+ traverse(match[2], sourcePath, false);
136
+ }
137
+ for (const match of css.matchAll(/@source\s+["']([^"']+)["']/g)) {
138
+ const marker = /(?:^|[\\/])node_modules[\\/]((?:@[^\\/]+[\\/])?[^\\/*?{}]+)/.exec(
139
+ match[1]
140
+ );
141
+ if (marker) addBare(marker[1].replace(/\\/g, "/"), false);
142
+ }
143
+ continue;
144
+ }
145
+ const sourceFile = project.addSourceFileAtPath(sourcePath);
146
+ for (const declaration of sourceFile.getImportDeclarations()) {
147
+ if (declaration.isTypeOnly()) continue;
148
+ const namedImports = declaration.getNamedImports();
149
+ const hasRuntimeBinding = Boolean(declaration.getDefaultImport()) || Boolean(declaration.getNamespaceImport()) || namedImports.length === 0 || namedImports.some((specifier) => !specifier.isTypeOnly());
150
+ if (hasRuntimeBinding) {
151
+ traverse(declaration.getModuleSpecifierValue(), sourcePath);
152
+ }
153
+ }
154
+ for (const declaration of sourceFile.getExportDeclarations()) {
155
+ if (declaration.isTypeOnly()) continue;
156
+ const namedExports = declaration.getNamedExports();
157
+ const specifier = declaration.getModuleSpecifierValue();
158
+ if (specifier && (namedExports.length === 0 || namedExports.some((namedExport) => !namedExport.isTypeOnly()))) {
159
+ traverse(specifier, sourcePath);
160
+ }
161
+ }
162
+ for (const call of sourceFile.getDescendantsOfKind(
163
+ SyntaxKind.CallExpression
164
+ )) {
165
+ const expression = call.getExpression();
166
+ const isDynamicImport = expression.getKind() === SyntaxKind.ImportKeyword;
167
+ const isRequire = Node.isIdentifier(expression) && expression.getText() === "require";
168
+ if (!isDynamicImport && !isRequire) continue;
169
+ const argument = call.getArguments()[0];
170
+ if (!argument || !Node.isStringLiteral(argument)) {
171
+ throw new Error(
172
+ `MIAODA_CLIENT_DYNAMIC_DEPENDENCY_UNSUPPORTED: ${path.relative(
173
+ projectRoot,
174
+ sourcePath
175
+ )}:${call.getStartLineNumber()}`
176
+ );
177
+ }
178
+ traverse(argument.getLiteralValue(), sourcePath);
179
+ }
180
+ }
181
+ return {
182
+ specifiers: [...specifiers].sort(),
183
+ optimizeDependencies: [...optimizeDependencies].sort()
184
+ };
185
+ }
186
+ function parentPackagePath(packagePath) {
187
+ const nestedMarker = packagePath.lastIndexOf("/node_modules/");
188
+ if (nestedMarker >= 0) return packagePath.slice(0, nestedMarker);
189
+ return packagePath.startsWith("node_modules/") ? "" : void 0;
190
+ }
191
+ function resolveLockedPackagePath(packages, fromPackagePath, packageName) {
192
+ let current = fromPackagePath;
193
+ while (current !== void 0) {
194
+ const candidate = current ? `${current}/node_modules/${packageName}` : `node_modules/${packageName}`;
195
+ if (packages[candidate]?.version) return candidate;
196
+ current = current ? parentPackagePath(current) : void 0;
197
+ }
198
+ return void 0;
199
+ }
200
+ function canonicalize(value) {
201
+ return JSON.stringify(value);
202
+ }
203
+ function clientConfigFiles(projectRoot) {
204
+ const fixedNames = [
205
+ "vite.config.ts",
206
+ "vite.config.mts",
207
+ "vite.config.js",
208
+ "vite.config.mjs",
209
+ "vite.config.cjs",
210
+ "postcss.config.js",
211
+ "postcss.config.cjs",
212
+ "postcss.config.mjs",
213
+ "tailwind.config.ts",
214
+ "tailwind.config.js",
215
+ "tailwind.config.cjs",
216
+ "tailwind.config.mjs",
217
+ "tsconfig.json",
218
+ "tsconfig.app.json",
219
+ "tsconfig.node.json",
220
+ ".env",
221
+ ".env.development",
222
+ ".env.development.local"
223
+ ];
224
+ const pending = fixedNames.map((name) => path.join(projectRoot, name));
225
+ for (const patchRootName of ["patches", ".yarn/patches"]) {
226
+ const patchRoot = path.join(projectRoot, patchRootName);
227
+ if (!fs.existsSync(patchRoot)) continue;
228
+ const visit = (directory) => {
229
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
230
+ const file = path.join(directory, entry.name);
231
+ if (entry.isSymbolicLink()) {
232
+ throw new Error(`MIAODA_CLIENT_CONFIG_SYMLINK_REJECTED: ${file}`);
233
+ }
234
+ if (entry.isDirectory()) visit(file);
235
+ else if (entry.isFile()) pending.push(file);
236
+ }
237
+ };
238
+ visit(patchRoot);
239
+ }
240
+ const visited = /* @__PURE__ */ new Set();
241
+ const result = [];
242
+ const resolveRelativeConfig = (specifier, importer) => {
243
+ if (!specifier.startsWith(".")) return void 0;
244
+ const candidate = path.resolve(path.dirname(importer), specifier);
245
+ const candidates = [
246
+ candidate,
247
+ ...[...SOURCE_EXTENSIONS, ".json"].map(
248
+ (extension) => `${candidate}${extension}`
249
+ ),
250
+ ...[...SOURCE_EXTENSIONS, ".json"].map(
251
+ (extension) => path.join(candidate, `index${extension}`)
252
+ )
253
+ ];
254
+ return candidates.find((file) => {
255
+ const relative = path.relative(projectRoot, file);
256
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) && fs.existsSync(file) && fs.statSync(file).isFile();
257
+ });
258
+ };
259
+ while (pending.length > 0) {
260
+ const file = path.resolve(pending.pop());
261
+ if (visited.has(file) || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
262
+ continue;
263
+ }
264
+ visited.add(file);
265
+ const content = fs.readFileSync(file);
266
+ result.push({
267
+ path: path.relative(projectRoot, file),
268
+ sha256: crypto.createHash("sha256").update(content).digest("hex")
269
+ });
270
+ if (SOURCE_EXTENSIONS.includes(path.extname(file))) {
271
+ const source = content.toString("utf8");
272
+ for (const match of source.matchAll(
273
+ /(?:from\s*|import\s*\(|require\s*\()\s*["']([^"']+)["']/g
274
+ )) {
275
+ const dependency = resolveRelativeConfig(match[1], file);
276
+ if (dependency) pending.push(dependency);
277
+ }
278
+ } else if (path.extname(file) === ".json") {
279
+ try {
280
+ const json = JSON.parse(content.toString("utf8"));
281
+ for (const specifier of [
282
+ json.extends,
283
+ ...(json.references ?? []).map((reference) => reference.path)
284
+ ]) {
285
+ if (!specifier) continue;
286
+ const dependency = resolveRelativeConfig(specifier, file);
287
+ if (dependency) pending.push(dependency);
288
+ }
289
+ } catch {
290
+ }
291
+ }
292
+ }
293
+ return result.sort((left, right) => left.path.localeCompare(right.path));
294
+ }
295
+ function clientConfigEnvironment() {
296
+ const names = [
297
+ "APP_FLAGS",
298
+ "ASSETS_CDN_PATH",
299
+ "BUILD_TOOL",
300
+ "CLIENT_BASE_PATH",
301
+ "DISABLE_INSPECTOR",
302
+ "FORCE_FRAMEWORK_BUILD_LOOSE_MODE",
303
+ "FORCE_FRAMEWORK_DOMAIN_MAIN",
304
+ "FORCE_FRAMEWORK_ENVIRONMENT",
305
+ "MIAODA_APP_ARCH_TYPE",
306
+ "MIAODA_APP_SOURCE",
307
+ "MIAODA_APP_TYPE",
308
+ "MIAODA_FONTS_MIRROR_OFF",
309
+ "NEED_ROUTES",
310
+ "STATIC_ASSETS_BASE_URL",
311
+ "__API_ROUTE_DEFINITIONS__",
312
+ "__PAGE_ROUTE_DEFINITIONS__"
313
+ ];
314
+ return Object.fromEntries(
315
+ names.map((name) => [
316
+ name,
317
+ crypto.createHash("sha256").update(process.env[name] ?? "").digest("hex")
318
+ ])
319
+ );
320
+ }
321
+ function installedDependencyLockFile(projectRoot) {
322
+ const hiddenLock = path.join(
323
+ projectRoot,
324
+ "node_modules",
325
+ ".package-lock.json"
326
+ );
327
+ if (fs.existsSync(hiddenLock)) {
328
+ if (fs.lstatSync(hiddenLock).isSymbolicLink() || !fs.statSync(hiddenLock).isFile()) {
329
+ throw new Error("MIAODA_NPM_HIDDEN_PACKAGE_LOCK_INVALID");
330
+ }
331
+ return hiddenLock;
332
+ }
333
+ return path.join(projectRoot, "package-lock.json");
334
+ }
335
+ function buildClientDependencyGraph(options) {
336
+ const projectRoot = path.resolve(options.projectRoot);
337
+ const packageLockFile = installedDependencyLockFile(projectRoot);
338
+ const lock = JSON.parse(
339
+ fs.readFileSync(packageLockFile, "utf8")
340
+ );
341
+ if (!lock.packages || (lock.lockfileVersion ?? 0) < 2) {
342
+ throw new Error("MIAODA_NPM_PACKAGE_LOCK_V2_REQUIRED");
343
+ }
344
+ const discovery = discoverClientSpecifiers(
345
+ projectRoot,
346
+ options.includeAllProductionDependencies === true
347
+ );
348
+ if (options.includeAllProductionDependencies) {
349
+ const packageJson = JSON.parse(
350
+ fs.readFileSync(path.join(projectRoot, "package.json"), "utf8")
351
+ );
352
+ for (const specifier of Object.keys(packageJson.dependencies ?? {})) {
353
+ if (!lock.packages[`node_modules/${specifier}`]?.version) continue;
354
+ if (!discovery.specifiers.includes(specifier)) {
355
+ discovery.specifiers.push(specifier);
356
+ }
357
+ }
358
+ }
359
+ const implicitSpecifiers = [
360
+ "react",
361
+ "react-dom",
362
+ "react/jsx-runtime",
363
+ "react/jsx-dev-runtime",
364
+ "clsx",
365
+ "echarts",
366
+ "echarts-for-react",
367
+ "@lark-apaas/client-toolkit/runtime",
368
+ ...process.env.DISABLE_INSPECTOR === "true" ? [] : ["@lark-apaas/miaoda-inspector"]
369
+ ];
370
+ for (const specifier of implicitSpecifiers) {
371
+ const packageName = packageNameFromSpecifier(specifier);
372
+ if (lock.packages[`node_modules/${packageName}`]?.version && !discovery.specifiers.includes(specifier)) {
373
+ discovery.specifiers.push(specifier);
374
+ discovery.optimizeDependencies.push(specifier);
375
+ }
376
+ }
377
+ discovery.specifiers.sort();
378
+ discovery.optimizeDependencies.sort();
379
+ const entries = discovery.specifiers.map((specifier) => {
380
+ const packageName = packageNameFromSpecifier(specifier);
381
+ const packagePath = resolveLockedPackagePath(
382
+ lock.packages,
383
+ "",
384
+ packageName
385
+ );
386
+ if (!packagePath) {
387
+ throw new Error(`MIAODA_CLIENT_DEPENDENCY_LOCK_MISSING: ${specifier}`);
388
+ }
389
+ return { specifier, packagePath };
390
+ });
391
+ const pending = [...new Set(entries.map((entry) => entry.packagePath))];
392
+ const graphPackages = /* @__PURE__ */ new Map();
393
+ while (pending.length > 0) {
394
+ const packagePath = pending.pop();
395
+ if (graphPackages.has(packagePath)) continue;
396
+ const lockedPackage = lock.packages[packagePath];
397
+ if (!lockedPackage?.version) {
398
+ throw new Error(`MIAODA_CLIENT_DEPENDENCY_LOCK_MISSING: ${packagePath}`);
399
+ }
400
+ const edges = {};
401
+ const addEdge = (name, required) => {
402
+ const resolvedPath = resolveLockedPackagePath(
403
+ lock.packages,
404
+ packagePath,
405
+ name
406
+ );
407
+ if (!resolvedPath) {
408
+ if (required) {
409
+ throw new Error(
410
+ `MIAODA_CLIENT_DEPENDENCY_EDGE_MISSING: ${packagePath} -> ${name}`
411
+ );
412
+ }
413
+ return;
414
+ }
415
+ edges[name] = resolvedPath;
416
+ pending.push(resolvedPath);
417
+ };
418
+ for (const name of Object.keys(lockedPackage.dependencies ?? {}).sort()) {
419
+ addEdge(name, true);
420
+ }
421
+ for (const name of Object.keys(
422
+ lockedPackage.optionalDependencies ?? {}
423
+ ).sort()) {
424
+ addEdge(name, false);
425
+ }
426
+ for (const name of Object.keys(
427
+ lockedPackage.peerDependencies ?? {}
428
+ ).sort()) {
429
+ addEdge(
430
+ name,
431
+ lockedPackage.peerDependenciesMeta?.[name]?.optional !== true
432
+ );
433
+ }
434
+ graphPackages.set(packagePath, {
435
+ path: packagePath,
436
+ version: lockedPackage.version,
437
+ integrity: lockedPackage.integrity ?? null,
438
+ edges: Object.fromEntries(
439
+ Object.entries(edges).sort(
440
+ ([left], [right]) => left.localeCompare(right)
441
+ )
442
+ )
443
+ });
444
+ }
445
+ const graph = {
446
+ schemaVersion: 1,
447
+ algorithm: "npm-installed-client-closure-v2",
448
+ runtimeAbiHash: options.runtimeAbiHash,
449
+ resolveConditions: ["browser", "development", "module", "import"],
450
+ configFiles: clientConfigFiles(projectRoot),
451
+ configEnvironment: clientConfigEnvironment(),
452
+ entries,
453
+ packages: [...graphPackages.values()].sort(
454
+ (left, right) => left.path.localeCompare(right.path)
455
+ )
456
+ };
457
+ return {
458
+ graph,
459
+ hash: crypto.createHash("sha256").update(canonicalize(graph)).digest("hex"),
460
+ optimizeDependencies: discovery.optimizeDependencies
461
+ };
462
+ }
463
+ export {
464
+ buildClientDependencyGraph,
465
+ packageNameFromSpecifier
466
+ };