@jskit-ai/jskit-cli 0.2.92 → 0.2.96

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.92",
3
+ "version": "0.2.96",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,3 +1,6 @@
1
+ import {
2
+ createHash
3
+ } from "node:crypto";
1
4
  import {
2
5
  mkdir,
3
6
  readFile,
@@ -17,10 +20,11 @@ const {
17
20
  ModuleKind,
18
21
  ModuleResolutionKind,
19
22
  Project,
20
- ScriptTarget
23
+ ScriptTarget,
24
+ ts
21
25
  } = tsMorph;
22
26
 
23
- const HELPER_MAP_SCHEMA_VERSION = 1;
27
+ const HELPER_MAP_SCHEMA_VERSION = 2;
24
28
  const CODE_EXTENSIONS = new Set([".cjs", ".js", ".jsx", ".mjs", ".ts", ".tsx", ".vue"]);
25
29
  const APP_SCAN_ROOTS = Object.freeze(["src", "packages", "config", "server", "scripts"]);
26
30
  const EXCLUDED_DIR_NAMES = new Set([
@@ -50,6 +54,12 @@ async function readJsonFile(filePath) {
50
54
  return JSON.parse(await readFile(filePath, "utf8"));
51
55
  }
52
56
 
57
+ async function readFileHash(filePath) {
58
+ return createHash("sha256")
59
+ .update(await readFile(filePath))
60
+ .digest("hex");
61
+ }
62
+
53
63
  function normalizePackageDependencies(packageJson = {}) {
54
64
  const dependencies = {
55
65
  ...(packageJson.dependencies || {}),
@@ -89,37 +99,6 @@ function createExportAnalysisProject() {
89
99
  });
90
100
  }
91
101
 
92
- function kindFromDeclaration(declaration, exportName = "") {
93
- if (exportName === "default") {
94
- return "default";
95
- }
96
- switch (declaration.getKindName()) {
97
- case "ClassDeclaration":
98
- return "class";
99
- case "EnumDeclaration":
100
- return "enum";
101
- case "FunctionDeclaration":
102
- case "FunctionExpression":
103
- case "MethodDeclaration":
104
- return "function";
105
- case "InterfaceDeclaration":
106
- return "interface";
107
- case "TypeAliasDeclaration":
108
- return "type";
109
- case "VariableDeclaration": {
110
- const initializer = typeof declaration.getInitializer === "function"
111
- ? declaration.getInitializer()
112
- : null;
113
- const initializerKind = initializer?.getKindName?.() || "";
114
- return initializerKind === "ArrowFunction" || initializerKind === "FunctionExpression"
115
- ? "function"
116
- : "value";
117
- }
118
- default:
119
- return "export";
120
- }
121
- }
122
-
123
102
  function addSymbol(symbols, symbol) {
124
103
  if (!symbol.name) {
125
104
  return;
@@ -135,14 +114,116 @@ function addSymbol(symbols, symbol) {
135
114
  });
136
115
  }
137
116
 
117
+ function compilerNodeHasModifier(node, modifierKind) {
118
+ return Array.isArray(node?.modifiers) && node.modifiers.some((modifier) => modifier.kind === modifierKind);
119
+ }
120
+
121
+ function exportedDeclarationName(node) {
122
+ if (compilerNodeHasModifier(node, ts.SyntaxKind.DefaultKeyword)) {
123
+ return "default";
124
+ }
125
+ return String(node?.name?.text || node?.name?.escapedText || "").trim();
126
+ }
127
+
128
+ function addExportedDeclarationSymbol(symbols, node, kind = "export") {
129
+ const name = exportedDeclarationName(node);
130
+ if (!name) {
131
+ return;
132
+ }
133
+ addSymbol(symbols, {
134
+ kind: name === "default" ? "default" : kind,
135
+ name
136
+ });
137
+ }
138
+
139
+ function bindingNameTexts(bindingName, names = []) {
140
+ if (!bindingName) {
141
+ return names;
142
+ }
143
+ if (ts.isIdentifier(bindingName)) {
144
+ names.push(String(bindingName.text || ""));
145
+ return names;
146
+ }
147
+ if (ts.isObjectBindingPattern(bindingName) || ts.isArrayBindingPattern(bindingName)) {
148
+ for (const element of bindingName.elements || []) {
149
+ if (element.name) {
150
+ bindingNameTexts(element.name, names);
151
+ }
152
+ }
153
+ }
154
+ return names;
155
+ }
156
+
157
+ function addVariableStatementExports(symbols, statement) {
158
+ if (!compilerNodeHasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
159
+ return;
160
+ }
161
+ for (const declaration of statement.declarationList?.declarations || []) {
162
+ for (const name of bindingNameTexts(declaration.name)) {
163
+ addSymbol(symbols, {
164
+ kind: declaration.initializer &&
165
+ (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))
166
+ ? "function"
167
+ : "value",
168
+ name
169
+ });
170
+ }
171
+ }
172
+ }
173
+
174
+ function addNamedExportSymbols(symbols, exportClause) {
175
+ if (!exportClause) {
176
+ return;
177
+ }
178
+ if (ts.isNamespaceExport(exportClause)) {
179
+ addSymbol(symbols, {
180
+ kind: "export",
181
+ name: String(exportClause.name?.text || "")
182
+ });
183
+ return;
184
+ }
185
+ if (!ts.isNamedExports(exportClause)) {
186
+ return;
187
+ }
188
+ for (const specifier of exportClause.elements || []) {
189
+ addSymbol(symbols, {
190
+ kind: "export",
191
+ name: String(specifier.name?.text || "")
192
+ });
193
+ }
194
+ }
195
+
138
196
  function extractExportedSymbols(sourceFile) {
139
197
  const symbols = new Map();
140
- for (const [name, declarations] of sourceFile.getExportedDeclarations()) {
141
- for (const declaration of declarations) {
198
+ for (const statement of sourceFile.compilerNode.statements || []) {
199
+ if (ts.isExportDeclaration(statement)) {
200
+ addNamedExportSymbols(symbols, statement.exportClause);
201
+ continue;
202
+ }
203
+ if (ts.isExportAssignment(statement)) {
142
204
  addSymbol(symbols, {
143
- name,
144
- kind: kindFromDeclaration(declaration, name)
205
+ kind: "default",
206
+ name: "default"
145
207
  });
208
+ continue;
209
+ }
210
+ if (ts.isVariableStatement(statement)) {
211
+ addVariableStatementExports(symbols, statement);
212
+ continue;
213
+ }
214
+ if (!compilerNodeHasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
215
+ continue;
216
+ }
217
+ if (ts.isFunctionDeclaration(statement)) {
218
+ addExportedDeclarationSymbol(symbols, statement, "function");
219
+ } else if (ts.isClassDeclaration(statement)) {
220
+ addExportedDeclarationSymbol(symbols, statement, "class");
221
+ } else if (ts.isInterfaceDeclaration(statement)) {
222
+ addExportedDeclarationSymbol(symbols, statement, "interface");
223
+ } else if (ts.isTypeAliasDeclaration(statement)) {
224
+ addExportedDeclarationSymbol(symbols, statement, "type");
225
+ } else if (ts.isEnumDeclaration(statement)) {
226
+ addExportedDeclarationSymbol(symbols, statement, "enum");
146
227
  }
147
228
  }
148
229
 
@@ -277,11 +358,75 @@ function flattenPackageExports(exportsField) {
277
358
  });
278
359
  }
279
360
 
280
- async function collectJskitPackageExports(targetRoot, packageJson = {}) {
361
+ async function packageExportTargetRecords(packageRoot, exportTargets = []) {
362
+ const records = [];
363
+ for (const exportTarget of exportTargets) {
364
+ const normalizedTarget = exportTarget.target.replace(/^\.\//u, "");
365
+ const targetPath = path.join(packageRoot, normalizedTarget);
366
+ const ext = path.extname(targetPath);
367
+ if (!CODE_EXTENSIONS.has(ext) || !await pathExists(targetPath)) {
368
+ continue;
369
+ }
370
+ records.push({
371
+ absolutePath: targetPath,
372
+ hash: await readFileHash(targetPath),
373
+ subpath: exportTarget.subpath,
374
+ target: normalizedTarget.split(path.sep).join("/")
375
+ });
376
+ }
377
+ return records;
378
+ }
379
+
380
+ function packageExportFingerprint({
381
+ installedPackageJson = {},
382
+ packageName = "",
383
+ targetRecords = []
384
+ } = {}) {
385
+ return createHash("sha256")
386
+ .update(JSON.stringify({
387
+ name: packageName,
388
+ version: installedPackageJson.version || "",
389
+ targets: targetRecords.map((record) => ({
390
+ hash: record.hash,
391
+ subpath: record.subpath,
392
+ target: record.target
393
+ }))
394
+ }))
395
+ .digest("hex");
396
+ }
397
+
398
+ function cachedPackageExports(previousPackagesByName = new Map(), packageName = "", fingerprint = "") {
399
+ const previous = previousPackagesByName.get(packageName);
400
+ if (
401
+ !previous ||
402
+ previous.installed !== true ||
403
+ previous.fingerprint !== fingerprint ||
404
+ !Array.isArray(previous.exports)
405
+ ) {
406
+ return null;
407
+ }
408
+ return {
409
+ name: previous.name,
410
+ version: previous.version || "",
411
+ fingerprint: previous.fingerprint,
412
+ installed: true,
413
+ exports: previous.exports
414
+ };
415
+ }
416
+
417
+ function previousPackageMap(previousMap = null) {
418
+ return new Map(
419
+ (Array.isArray(previousMap?.jskitPackages) ? previousMap.jskitPackages : [])
420
+ .map((packageEntry) => [packageEntry.name, packageEntry])
421
+ .filter(([name]) => name)
422
+ );
423
+ }
424
+
425
+ async function collectJskitPackageExports(targetRoot, packageJson = {}, previousMap = null) {
281
426
  const packageNames = normalizePackageDependencies(packageJson)
282
427
  .filter((name) => name.startsWith("@jskit-ai/"));
283
428
  const packages = [];
284
- const project = createExportAnalysisProject();
429
+ const previousPackagesByName = previousPackageMap(previousMap);
285
430
 
286
431
  for (const packageName of packageNames) {
287
432
  const packageRoot = path.join(targetRoot, "node_modules", ...packageName.split("/"));
@@ -297,21 +442,28 @@ async function collectJskitPackageExports(targetRoot, packageJson = {}) {
297
442
 
298
443
  const installedPackageJson = await readJsonFile(packageJsonPath);
299
444
  const exportTargets = flattenPackageExports(installedPackageJson.exports || {});
445
+ const targetRecords = await packageExportTargetRecords(packageRoot, exportTargets);
446
+ const fingerprint = packageExportFingerprint({
447
+ installedPackageJson,
448
+ packageName,
449
+ targetRecords
450
+ });
451
+ const cachedEntry = cachedPackageExports(previousPackagesByName, packageName, fingerprint);
452
+ if (cachedEntry) {
453
+ packages.push(cachedEntry);
454
+ continue;
455
+ }
456
+
457
+ const project = createExportAnalysisProject();
300
458
  const exports = [];
301
- for (const exportTarget of exportTargets) {
302
- const normalizedTarget = exportTarget.target.replace(/^\.\//u, "");
303
- const targetPath = path.join(packageRoot, normalizedTarget);
304
- const ext = path.extname(targetPath);
305
- if (!CODE_EXTENSIONS.has(ext) || !await pathExists(targetPath)) {
306
- continue;
307
- }
459
+ for (const targetRecord of targetRecords) {
308
460
  const sourceFile = await addCodeFileToProject(project, {
309
- absolutePath: targetPath,
310
- relativePath: normalizedTarget
461
+ absolutePath: targetRecord.absolutePath,
462
+ relativePath: targetRecord.target
311
463
  });
312
464
  exports.push({
313
- subpath: exportTarget.subpath,
314
- target: normalizedTarget.split(path.sep).join("/"),
465
+ subpath: targetRecord.subpath,
466
+ target: targetRecord.target,
315
467
  exports: sourceFile ? extractExportedSymbols(sourceFile) : []
316
468
  });
317
469
  }
@@ -319,6 +471,7 @@ async function collectJskitPackageExports(targetRoot, packageJson = {}) {
319
471
  packages.push({
320
472
  name: packageName,
321
473
  version: installedPackageJson.version || "",
474
+ fingerprint,
322
475
  installed: true,
323
476
  exports
324
477
  });
@@ -383,7 +536,7 @@ function renderHelperMapMarkdown(map) {
383
536
  return `${lines.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
384
537
  }
385
538
 
386
- async function buildHelperMap({ targetRoot }) {
539
+ async function buildHelperMap({ targetRoot, previousMap = null }) {
387
540
  const packageJsonPath = path.join(targetRoot, "package.json");
388
541
  const packageJson = await readJsonFile(packageJsonPath);
389
542
  const map = {
@@ -396,7 +549,7 @@ async function buildHelperMap({ targetRoot }) {
396
549
  app: {
397
550
  files: await collectAppExports(targetRoot)
398
551
  },
399
- jskitPackages: await collectJskitPackageExports(targetRoot, packageJson)
552
+ jskitPackages: await collectJskitPackageExports(targetRoot, packageJson, previousMap)
400
553
  };
401
554
  return {
402
555
  ok: true,
@@ -430,12 +583,17 @@ async function readHelperMap({ targetRoot }) {
430
583
  }
431
584
 
432
585
  async function updateHelperMap({ targetRoot }) {
433
- const payload = await buildHelperMap({ targetRoot });
586
+ const helperMapJsonPath = path.join(targetRoot, HELPER_MAP_JSON_RELATIVE_PATH);
587
+ const currentJson = await pathExists(helperMapJsonPath)
588
+ ? await readFile(helperMapJsonPath, "utf8")
589
+ : "";
590
+ const previousMap = currentJson ? JSON.parse(currentJson) : null;
591
+ const payload = await buildHelperMap({
592
+ previousMap,
593
+ targetRoot
594
+ });
434
595
  const markdown = renderHelperMapMarkdown(payload.map);
435
596
  const json = `${JSON.stringify(payload.map, null, 2)}\n`;
436
- const currentJson = await pathExists(payload.helperMapJsonPath)
437
- ? await readFile(payload.helperMapJsonPath, "utf8")
438
- : "";
439
597
  const currentMarkdown = await pathExists(payload.helperMapMarkdownPath)
440
598
  ? await readFile(payload.helperMapMarkdownPath, "utf8")
441
599
  : "";