@beignet/cli 0.0.51 → 0.0.53

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 (117) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +182 -19
  3. package/dist/analysis/layers.d.ts +2 -0
  4. package/dist/analysis/layers.d.ts.map +1 -1
  5. package/dist/analysis/layers.js +9 -2
  6. package/dist/analysis/layers.js.map +1 -1
  7. package/dist/analysis/port-wiring.d.ts +4 -2
  8. package/dist/analysis/port-wiring.d.ts.map +1 -1
  9. package/dist/analysis/port-wiring.js +41 -11
  10. package/dist/analysis/port-wiring.js.map +1 -1
  11. package/dist/analysis/source-files.d.ts +15 -0
  12. package/dist/analysis/source-files.d.ts.map +1 -0
  13. package/dist/analysis/source-files.js +52 -0
  14. package/dist/analysis/source-files.js.map +1 -0
  15. package/dist/analysis/source-index.d.ts +2 -1
  16. package/dist/analysis/source-index.d.ts.map +1 -1
  17. package/dist/analysis/source-index.js +34 -5
  18. package/dist/analysis/source-index.js.map +1 -1
  19. package/dist/analysis/workspace-routes.d.ts +8 -0
  20. package/dist/analysis/workspace-routes.d.ts.map +1 -0
  21. package/dist/analysis/workspace-routes.js +291 -0
  22. package/dist/analysis/workspace-routes.js.map +1 -0
  23. package/dist/analysis/workspace.d.ts +24 -3
  24. package/dist/analysis/workspace.d.ts.map +1 -1
  25. package/dist/analysis/workspace.js +184 -43
  26. package/dist/analysis/workspace.js.map +1 -1
  27. package/dist/app-map-changes.d.ts.map +1 -1
  28. package/dist/app-map-changes.js +24 -14
  29. package/dist/app-map-changes.js.map +1 -1
  30. package/dist/app-map.d.ts.map +1 -1
  31. package/dist/app-map.js +87 -81
  32. package/dist/app-map.js.map +1 -1
  33. package/dist/check.d.ts +12 -0
  34. package/dist/check.d.ts.map +1 -1
  35. package/dist/check.js +92 -25
  36. package/dist/check.js.map +1 -1
  37. package/dist/config.d.ts +7 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +19 -0
  40. package/dist/config.js.map +1 -1
  41. package/dist/git-changes.d.ts +17 -1
  42. package/dist/git-changes.d.ts.map +1 -1
  43. package/dist/git-changes.js +118 -84
  44. package/dist/git-changes.js.map +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +23 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/inspect.d.ts.map +1 -1
  49. package/dist/inspect.js +424 -129
  50. package/dist/inspect.js.map +1 -1
  51. package/dist/lint.d.ts.map +1 -1
  52. package/dist/lint.js +108 -43
  53. package/dist/lint.js.map +1 -1
  54. package/dist/make/shared.js +1 -1
  55. package/dist/make.js +18 -1
  56. package/dist/make.js.map +1 -1
  57. package/dist/mcp.js +1 -1
  58. package/dist/mcp.js.map +1 -1
  59. package/dist/provider-add.js +2 -2
  60. package/dist/provider-add.js.map +1 -1
  61. package/dist/provider-audit.d.ts.map +1 -1
  62. package/dist/provider-audit.js +22 -6
  63. package/dist/provider-audit.js.map +1 -1
  64. package/dist/templates/agents.d.ts.map +1 -1
  65. package/dist/templates/agents.js +36 -13
  66. package/dist/templates/agents.js.map +1 -1
  67. package/dist/templates/base.js +5 -5
  68. package/dist/templates/db/mysql.d.ts.map +1 -1
  69. package/dist/templates/db/mysql.js +51 -22
  70. package/dist/templates/db/mysql.js.map +1 -1
  71. package/dist/templates/db/postgres.d.ts.map +1 -1
  72. package/dist/templates/db/postgres.js +60 -20
  73. package/dist/templates/db/postgres.js.map +1 -1
  74. package/dist/templates/db/sqlite.d.ts.map +1 -1
  75. package/dist/templates/db/sqlite.js +51 -23
  76. package/dist/templates/db/sqlite.js.map +1 -1
  77. package/dist/templates/shadcn.js +1 -1
  78. package/dist/templates/shadcn.js.map +1 -1
  79. package/dist/templates/shared.js +6 -6
  80. package/dist/templates/shared.js.map +1 -1
  81. package/dist/templates/testing.d.ts.map +1 -1
  82. package/dist/templates/testing.js +7 -19
  83. package/dist/templates/testing.js.map +1 -1
  84. package/dist/test-discovery.d.ts +9 -0
  85. package/dist/test-discovery.d.ts.map +1 -0
  86. package/dist/test-discovery.js +40 -0
  87. package/dist/test-discovery.js.map +1 -0
  88. package/package.json +8 -5
  89. package/skills/app-structure/SKILL.md +74 -7
  90. package/src/analysis/layers.ts +13 -2
  91. package/src/analysis/port-wiring.ts +68 -11
  92. package/src/analysis/source-files.ts +61 -0
  93. package/src/analysis/source-index.ts +56 -4
  94. package/src/analysis/workspace-routes.ts +385 -0
  95. package/src/analysis/workspace.ts +302 -59
  96. package/src/app-map-changes.ts +41 -13
  97. package/src/app-map.ts +114 -103
  98. package/src/check.ts +115 -25
  99. package/src/config.ts +33 -0
  100. package/src/git-changes.ts +218 -86
  101. package/src/index.ts +33 -5
  102. package/src/inspect.ts +608 -156
  103. package/src/lint.ts +152 -48
  104. package/src/make/shared.ts +1 -1
  105. package/src/make.ts +31 -1
  106. package/src/mcp.ts +1 -1
  107. package/src/provider-add.ts +2 -2
  108. package/src/provider-audit.ts +30 -10
  109. package/src/templates/agents.ts +36 -13
  110. package/src/templates/base.ts +5 -5
  111. package/src/templates/db/mysql.ts +51 -22
  112. package/src/templates/db/postgres.ts +60 -20
  113. package/src/templates/db/sqlite.ts +51 -23
  114. package/src/templates/shadcn.ts +1 -1
  115. package/src/templates/shared.ts +6 -6
  116. package/src/templates/testing.ts +12 -19
  117. package/src/test-discovery.ts +53 -0
@@ -2,7 +2,16 @@ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import ts from "typescript";
4
4
  import type { ResolvedBeignetConfig } from "../config.js";
5
- import type { AnalysisWorkspace } from "./workspace.js";
5
+ import { sourceFileScriptKind } from "./source-files.js";
6
+ import {
7
+ createSourceIndex,
8
+ type ResolvedSourceReference,
9
+ type SourceIndex,
10
+ } from "./source-index.js";
11
+ import {
12
+ type AnalysisWorkspace,
13
+ createAnalysisWorkspace,
14
+ } from "./workspace.js";
6
15
 
7
16
  type PortAnalysisWorkspace = Pick<
8
17
  AnalysisWorkspace,
@@ -13,6 +22,7 @@ export type PortWiringAnalysis = {
13
22
  portsFileExists: boolean;
14
23
  portWiringFileExists: boolean;
15
24
  properties: Map<string, ts.PropertySignature>;
25
+ propertyFiles: Map<string, string>;
16
26
  declared: Set<string>;
17
27
  bound: Set<string>;
18
28
  deferred: Set<string>;
@@ -23,6 +33,7 @@ export type PortWiringAnalysis = {
23
33
 
24
34
  export async function analyzePortWiring(
25
35
  workspace: PortAnalysisWorkspace,
36
+ sourceIndex?: SourceIndex,
26
37
  ): Promise<PortWiringAnalysis> {
27
38
  const result: PortWiringAnalysis = {
28
39
  portsFileExists: workspace.fileSet.has(workspace.config.paths.ports),
@@ -30,6 +41,7 @@ export async function analyzePortWiring(
30
41
  workspace.config.paths.portWiring,
31
42
  ),
32
43
  properties: new Map(),
44
+ propertyFiles: new Map(),
33
45
  declared: new Set(),
34
46
  bound: new Set(),
35
47
  deferred: new Set(),
@@ -39,20 +51,31 @@ export async function analyzePortWiring(
39
51
  };
40
52
 
41
53
  let inferredAppPortsName: string | undefined;
54
+ let inferredAppPortsReference: ResolvedSourceReference | undefined;
42
55
  if (result.portsFileExists) {
43
- const sourceFile = await workspace.readSourceFile(
56
+ const reference = await sourceIndex?.resolveExport(
44
57
  workspace.config.paths.ports,
58
+ "AppPorts",
45
59
  );
60
+ const portsFile = reference?.file ?? workspace.config.paths.ports;
61
+ const portsName = reference?.exportName ?? "AppPorts";
62
+ const sourceFile = await workspace.readSourceFile(portsFile);
46
63
  const declarations = collectTypeDeclarations(sourceFile);
47
- inferredAppPortsName = inferredTypeQueryName("AppPorts", declarations);
64
+ inferredAppPortsName = inferredTypeQueryName(portsName, declarations);
65
+ if (inferredAppPortsName)
66
+ inferredAppPortsReference = await sourceIndex?.resolveName(
67
+ portsFile,
68
+ inferredAppPortsName,
69
+ );
48
70
  const collection = inferredAppPortsName
49
71
  ? { properties: [], indeterminate: false }
50
- : collectNamedTypeProperties("AppPorts", declarations, true);
72
+ : collectNamedTypeProperties(portsName, declarations, !sourceIndex);
51
73
  result.declarationIndeterminate = collection.indeterminate;
52
74
  for (const property of collection.properties) {
53
75
  const name = propertyName(property.name);
54
76
  if (name && !result.properties.has(name)) {
55
77
  result.properties.set(name, property);
78
+ result.propertyFiles.set(name, portsFile);
56
79
  result.declared.add(name);
57
80
  }
58
81
  }
@@ -62,12 +85,27 @@ export async function analyzePortWiring(
62
85
  if (inferredAppPortsName) result.declarationIndeterminate = true;
63
86
  return result;
64
87
  }
65
- const sourceFile = await workspace.readSourceFile(
88
+ const wiringReference = await sourceIndex?.resolveExport(
66
89
  workspace.config.paths.portWiring,
90
+ "initialPorts",
91
+ );
92
+ const sourceFile = await workspace.readSourceFile(
93
+ wiringReference?.file ?? workspace.config.paths.portWiring,
67
94
  );
68
95
  const values = collectVariableInitializers(sourceFile);
69
- const definitions = portDefinitionCalls(sourceFile);
70
- if (definitions.indeterminate) result.indeterminate = true;
96
+ const definitions = portDefinitionCalls(
97
+ sourceFile,
98
+ wiringReference?.declaration &&
99
+ ts.isVariableDeclaration(wiringReference.declaration) &&
100
+ ts.isIdentifier(wiringReference.declaration.name)
101
+ ? wiringReference.declaration.name.text
102
+ : wiringReference?.exportName,
103
+ );
104
+ if (
105
+ definitions.indeterminate ||
106
+ (sourceIndex && definitions.calls.length === 0)
107
+ )
108
+ result.indeterminate = true;
71
109
 
72
110
  for (const call of definitions.calls) {
73
111
  const objectArgument = call.arguments
@@ -123,7 +161,13 @@ export async function analyzePortWiring(
123
161
  if (inferredAppPortsName) {
124
162
  if (
125
163
  !definitions.indeterminate &&
126
- definitions.name === inferredAppPortsName
164
+ (sourceIndex
165
+ ? Boolean(
166
+ inferredAppPortsReference?.declaration &&
167
+ inferredAppPortsReference.declaration ===
168
+ wiringReference?.declaration,
169
+ )
170
+ : definitions.name === inferredAppPortsName)
127
171
  ) {
128
172
  result.appPortsInferredFromWiring = true;
129
173
  for (const name of result.bound) result.declared.add(name);
@@ -136,7 +180,10 @@ export async function analyzePortWiring(
136
180
  return result;
137
181
  }
138
182
 
139
- function portDefinitionCalls(sourceFile: ts.SourceFile): {
183
+ function portDefinitionCalls(
184
+ sourceFile: ts.SourceFile,
185
+ preferredName?: string,
186
+ ): {
140
187
  calls: ts.CallExpression[];
141
188
  name?: string;
142
189
  indeterminate: boolean;
@@ -171,8 +218,12 @@ function portDefinitionCalls(sourceFile: ts.SourceFile): {
171
218
  }
172
219
 
173
220
  const canonical = candidates.filter(
174
- (candidate) => candidate.name === "initialPorts" && candidate.exported,
221
+ (candidate) =>
222
+ candidate.name === (preferredName ?? "initialPorts") &&
223
+ candidate.exported,
175
224
  );
225
+ if (preferredName && canonical.length === 0)
226
+ return { calls: [], indeterminate: true };
176
227
  const exported = candidates.filter((candidate) => candidate.exported);
177
228
  const selected =
178
229
  canonical.length > 0
@@ -229,6 +280,12 @@ export async function analyzePortWiringFiles(options: {
229
280
  files: readonly string[];
230
281
  config: ResolvedBeignetConfig;
231
282
  }): Promise<PortWiringAnalysis> {
283
+ if (options.config.workspace.packages.length > 0) {
284
+ const workspace = await createAnalysisWorkspace(options.targetDir, {
285
+ config: options.config,
286
+ });
287
+ return analyzePortWiring(workspace, createSourceIndex(workspace));
288
+ }
232
289
  const sourceFiles = new Map<string, ts.SourceFile>();
233
290
  return analyzePortWiring({
234
291
  config: options.config,
@@ -241,7 +298,7 @@ export async function analyzePortWiringFiles(options: {
241
298
  await readFile(path.join(options.targetDir, file), "utf8"),
242
299
  ts.ScriptTarget.Latest,
243
300
  true,
244
- file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
301
+ sourceFileScriptKind(file),
245
302
  );
246
303
  sourceFiles.set(file, sourceFile);
247
304
  return sourceFile;
@@ -0,0 +1,61 @@
1
+ import path from "node:path";
2
+ import ts from "typescript";
3
+
4
+ export const typescriptAnalysisSourceFileExtensions = [
5
+ ".ts",
6
+ ".tsx",
7
+ ".mts",
8
+ ".cts",
9
+ ] as const;
10
+
11
+ export const javascriptAnalysisSourceFileExtensions = [
12
+ ".js",
13
+ ".jsx",
14
+ ".mjs",
15
+ ".cjs",
16
+ ] as const;
17
+
18
+ export const analysisSourceFileExtensions = [
19
+ ...typescriptAnalysisSourceFileExtensions,
20
+ ...javascriptAnalysisSourceFileExtensions,
21
+ ] as const;
22
+
23
+ const sourceFileExtensions = new Set<string>(analysisSourceFileExtensions);
24
+ const javascriptSourceFileExtensions = new Set<string>(
25
+ javascriptAnalysisSourceFileExtensions,
26
+ );
27
+ const declarationFilePattern = /\.d\.(?:ts|mts|cts)$/;
28
+
29
+ /** Whether an extension belongs to the JavaScript/TypeScript source family. */
30
+ export function isAnalysisSourceFileExtension(extension: string): boolean {
31
+ return sourceFileExtensions.has(extension);
32
+ }
33
+
34
+ /** Whether a project file can participate in Beignet source analysis. */
35
+ export function isAnalysisSourceFile(file: string): boolean {
36
+ return (
37
+ isAnalysisSourceFileExtension(path.posix.extname(file)) &&
38
+ !declarationFilePattern.test(file)
39
+ );
40
+ }
41
+
42
+ /** Remove a JavaScript or TypeScript source extension from a path. */
43
+ export function stripSourceFileExtension(file: string): string {
44
+ const extension = path.posix.extname(file);
45
+ return isAnalysisSourceFileExtension(extension)
46
+ ? file.slice(0, -extension.length)
47
+ : file;
48
+ }
49
+
50
+ /** Whether a source file uses JavaScript rather than TypeScript syntax. */
51
+ export function isJavaScriptSourceFile(file: string): boolean {
52
+ return javascriptSourceFileExtensions.has(path.posix.extname(file));
53
+ }
54
+
55
+ /** Select the TypeScript parser mode for a JavaScript or TypeScript file. */
56
+ export function sourceFileScriptKind(file: string): ts.ScriptKind {
57
+ if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
58
+ if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
59
+ if (isJavaScriptSourceFile(file)) return ts.ScriptKind.JS;
60
+ return ts.ScriptKind.TS;
61
+ }
@@ -53,6 +53,12 @@ export type SourceIndex = {
53
53
  resolveExpression(
54
54
  file: string,
55
55
  expression: ts.Expression,
56
+ locals?: ReadonlyMap<string, ts.Declaration>,
57
+ ): Promise<ResolvedSourceReference | undefined>;
58
+ resolveValue(
59
+ file: string,
60
+ expression: ts.Expression,
61
+ locals?: ReadonlyMap<string, ts.Declaration>,
56
62
  ): Promise<ResolvedSourceReference | undefined>;
57
63
  resolveExport(
58
64
  file: string,
@@ -131,7 +137,6 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
131
137
  ts.isNamedExports(statement.exportClause)
132
138
  ) {
133
139
  for (const element of statement.exportClause.elements) {
134
- if (statement.isTypeOnly || element.isTypeOnly) continue;
135
140
  index.localExports.set(
136
141
  element.name.text,
137
142
  element.propertyName?.text ?? element.name.text,
@@ -208,6 +213,9 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
208
213
  if (localDeclaration) {
209
214
  return { file, exportName, declaration: localDeclaration };
210
215
  }
216
+ const imported = localName ? index.imports.get(localName) : undefined;
217
+ if (imported?.kind === "named")
218
+ return resolveExport(imported.file, imported.exportName, visited);
211
219
 
212
220
  const reexport = index.reexports.get(exportName);
213
221
  if (reexport) {
@@ -227,7 +235,7 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
227
235
  return undefined;
228
236
  };
229
237
 
230
- return {
238
+ const sourceIndex: SourceIndex = {
231
239
  async resolveName(file, name) {
232
240
  const index = await readIndex(file);
233
241
  const local = index.locals.get(name);
@@ -244,11 +252,12 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
244
252
  }
245
253
  );
246
254
  },
247
- async resolveExpression(file, rawExpression) {
255
+ async resolveExpression(file, rawExpression, locals) {
248
256
  const expression = unwrapExpression(rawExpression);
249
257
  const index = await readIndex(file);
250
258
  if (ts.isIdentifier(expression)) {
251
- const local = index.declarations.get(expression.text);
259
+ const local =
260
+ locals?.get(expression.text) ?? index.locals.get(expression.text);
252
261
  if (local) {
253
262
  return { file, exportName: expression.text, declaration: local };
254
263
  }
@@ -267,6 +276,11 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
267
276
  ts.isPropertyAccessExpression(expression) &&
268
277
  ts.isIdentifier(expression.expression)
269
278
  ) {
279
+ if (
280
+ locals?.has(expression.expression.text) ||
281
+ index.locals.has(expression.expression.text)
282
+ )
283
+ return undefined;
270
284
  const binding = index.imports.get(expression.expression.text);
271
285
  if (binding?.kind !== "namespace") return undefined;
272
286
  return (
@@ -279,6 +293,43 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
279
293
 
280
294
  return undefined;
281
295
  },
296
+ async resolveValue(file, expression, locals) {
297
+ const visited = new Set<ts.Declaration>();
298
+ async function follow(
299
+ file: string,
300
+ rawExpression: ts.Expression,
301
+ locals?: ReadonlyMap<string, ts.Declaration>,
302
+ ): Promise<ResolvedSourceReference | undefined> {
303
+ const expression = unwrapExpression(rawExpression);
304
+ const reference = await sourceIndex.resolveExpression(
305
+ file,
306
+ expression,
307
+ locals,
308
+ );
309
+ const declaration = reference?.declaration;
310
+ if (!reference || !declaration || visited.has(declaration))
311
+ return undefined;
312
+ visited.add(declaration);
313
+ if (!ts.isVariableDeclaration(declaration) || !declaration.initializer)
314
+ return reference;
315
+ if (
316
+ !ts.isVariableDeclarationList(declaration.parent) ||
317
+ !(declaration.parent.flags & ts.NodeFlags.Const)
318
+ )
319
+ return undefined;
320
+ const initializer = unwrapExpression(declaration.initializer);
321
+ if (
322
+ !ts.isIdentifier(initializer) &&
323
+ !ts.isPropertyAccessExpression(initializer)
324
+ )
325
+ return reference;
326
+ const local =
327
+ ts.isIdentifier(expression) &&
328
+ locals?.get(expression.text) === declaration;
329
+ return follow(reference.file, initializer, local ? locals : undefined);
330
+ }
331
+ return follow(file, expression, locals);
332
+ },
282
333
  resolveExport(file, exportName) {
283
334
  return resolveExport(file, exportName);
284
335
  },
@@ -295,6 +346,7 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
295
346
  };
296
347
  },
297
348
  };
349
+ return sourceIndex;
298
350
  }
299
351
 
300
352
  export function exportedVariableDeclarations(