@beignet/cli 0.0.52 → 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 (68) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +131 -1
  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 +5 -0
  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 +39 -10
  10. package/dist/analysis/port-wiring.js.map +1 -1
  11. package/dist/analysis/source-index.d.ts +2 -1
  12. package/dist/analysis/source-index.d.ts.map +1 -1
  13. package/dist/analysis/source-index.js +34 -5
  14. package/dist/analysis/source-index.js.map +1 -1
  15. package/dist/analysis/workspace-routes.d.ts +8 -0
  16. package/dist/analysis/workspace-routes.d.ts.map +1 -0
  17. package/dist/analysis/workspace-routes.js +291 -0
  18. package/dist/analysis/workspace-routes.js.map +1 -0
  19. package/dist/analysis/workspace.d.ts +24 -3
  20. package/dist/analysis/workspace.d.ts.map +1 -1
  21. package/dist/analysis/workspace.js +166 -21
  22. package/dist/analysis/workspace.js.map +1 -1
  23. package/dist/app-map-changes.d.ts.map +1 -1
  24. package/dist/app-map-changes.js +19 -7
  25. package/dist/app-map-changes.js.map +1 -1
  26. package/dist/app-map.d.ts.map +1 -1
  27. package/dist/app-map.js +58 -55
  28. package/dist/app-map.js.map +1 -1
  29. package/dist/config.d.ts +7 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +19 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +20 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/inspect.d.ts.map +1 -1
  37. package/dist/inspect.js +89 -21
  38. package/dist/inspect.js.map +1 -1
  39. package/dist/lint.d.ts.map +1 -1
  40. package/dist/lint.js +106 -38
  41. package/dist/lint.js.map +1 -1
  42. package/dist/make/shared.js +1 -1
  43. package/dist/make.js +18 -1
  44. package/dist/make.js.map +1 -1
  45. package/dist/provider-add.js +2 -2
  46. package/dist/provider-add.js.map +1 -1
  47. package/dist/provider-audit.d.ts.map +1 -1
  48. package/dist/provider-audit.js +22 -6
  49. package/dist/provider-audit.js.map +1 -1
  50. package/dist/templates/shared.js +3 -3
  51. package/package.json +3 -2
  52. package/skills/app-structure/SKILL.md +42 -0
  53. package/src/analysis/layers.ts +9 -0
  54. package/src/analysis/port-wiring.ts +66 -10
  55. package/src/analysis/source-index.ts +56 -4
  56. package/src/analysis/workspace-routes.ts +385 -0
  57. package/src/analysis/workspace.ts +281 -34
  58. package/src/app-map-changes.ts +31 -5
  59. package/src/app-map.ts +75 -75
  60. package/src/config.ts +33 -0
  61. package/src/index.ts +25 -0
  62. package/src/inspect.ts +123 -16
  63. package/src/lint.ts +146 -43
  64. package/src/make/shared.ts +1 -1
  65. package/src/make.ts +31 -1
  66. package/src/provider-add.ts +2 -2
  67. package/src/provider-audit.ts +30 -10
  68. package/src/templates/shared.ts +3 -3
@@ -77,6 +77,15 @@ beignet db migrate
77
77
  beignet db status
78
78
  ```
79
79
 
80
+ ## Encryption keys
81
+
82
+ `beignet encryption key` prints a fresh key for `@beignet/core/encryption`;
83
+ `--json` returns `schemaVersion` and `key`. It does not read or overwrite env
84
+ files. Keep the output out of shared logs and wire the persistent key through
85
+ server env config and `infra/port-wiring.ts`. Retain keys needed by existing
86
+ records and backups during rotation. The starter does not configure encryption
87
+ until the app needs it; no encryption generator or provider preset is required.
88
+
80
89
  ## Explicit Registration
81
90
 
82
91
  Hand-written files do not run until registered:
@@ -174,6 +183,39 @@ root at `src/client/index.ts`. Other registries and app-bound builders are not
174
183
  relocated implicitly; override each path the app has moved, such as
175
184
  `listeners`, `schedules`, `tasks`, `outbox`, and their matching builder paths.
176
185
 
186
+ ## Workspace Source Packages
187
+
188
+ An app may set `workspace.packages` to explicit relative source-package
189
+ paths, including transitive dependencies. Each included package must have a
190
+ unique manifest name, belong to the app's declared dependency graph, and use a
191
+ separate root. Read the package's own Beignet config and TypeScript config
192
+ before changing its files. Package exports must resolve to source; do not guess
193
+ source from `dist` or declaration files.
194
+
195
+ Lint follows imports and reexports across these packages using each owner's
196
+ layers. Relative imports, TypeScript aliases, and `package.json#imports` aliases
197
+ that resolve outside the configured source roots also require the owning source
198
+ package in `workspace.packages`. Aliases to installed external dependencies remain
199
+ external imports.
200
+ Shared feature names in map/explain are qualified as
201
+ `@scope/package:feature`, and source evidence remains app-relative, including
202
+ `../`. Routes/doctor follow explicit server route composition and report
203
+ unresolved workspace registration instead of executing it. Trace the exported
204
+ server or the value returned by the exported Next.js server loader; unused
205
+ factory calls do not establish registration. Verified per-file Next.js handlers
206
+ do not require central registration. AppPorts and initialPorts may be reexported
207
+ through source entrypoints, including wiring aliases used by inferred AppPorts.
208
+ Import-then-export barrels and type-only port exports are supported. Match
209
+ contracts by their resolved declaration, including loader-local aliases, rather
210
+ than assuming export names are unique across packages.
211
+
212
+ Keep startup, lifecycle provider registration, env, database commands, and
213
+ operational registries app-owned. Those doctor checks still use app conventions;
214
+ a worker-only package can run lint without pretending to expose an HTTP server.
215
+ Generators and doctor repairs stay within the selected app. Never put a sibling
216
+ package path into `paths.*` to make a generator write there. See the CLI README's
217
+ workspace source packages section for the supported scope and fixture.
218
+
177
219
  ## Lint And Doctor
178
220
 
179
221
  Start unfamiliar app work with `beignet map`. Its human output summarizes each
@@ -7,6 +7,15 @@ import {
7
7
  } from "../config.js";
8
8
  import { isAppTestFile } from "../test-discovery.js";
9
9
  import { stripSourceFileExtension } from "./source-files.js";
10
+ import { type AnalysisWorkspace, projectSource } from "./workspace.js";
11
+
12
+ export function classifyWorkspaceSourcePath(
13
+ file: string,
14
+ workspace: AnalysisWorkspace,
15
+ ): SourceLayer {
16
+ const source = projectSource(workspace, file);
17
+ return classifySourcePath(source.file, source.project.config);
18
+ }
10
19
 
11
20
  export type SourceLayer =
12
21
  | "app"
@@ -3,7 +3,15 @@ import path from "node:path";
3
3
  import ts from "typescript";
4
4
  import type { ResolvedBeignetConfig } from "../config.js";
5
5
  import { sourceFileScriptKind } from "./source-files.js";
6
- import type { AnalysisWorkspace } from "./workspace.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";
7
15
 
8
16
  type PortAnalysisWorkspace = Pick<
9
17
  AnalysisWorkspace,
@@ -14,6 +22,7 @@ export type PortWiringAnalysis = {
14
22
  portsFileExists: boolean;
15
23
  portWiringFileExists: boolean;
16
24
  properties: Map<string, ts.PropertySignature>;
25
+ propertyFiles: Map<string, string>;
17
26
  declared: Set<string>;
18
27
  bound: Set<string>;
19
28
  deferred: Set<string>;
@@ -24,6 +33,7 @@ export type PortWiringAnalysis = {
24
33
 
25
34
  export async function analyzePortWiring(
26
35
  workspace: PortAnalysisWorkspace,
36
+ sourceIndex?: SourceIndex,
27
37
  ): Promise<PortWiringAnalysis> {
28
38
  const result: PortWiringAnalysis = {
29
39
  portsFileExists: workspace.fileSet.has(workspace.config.paths.ports),
@@ -31,6 +41,7 @@ export async function analyzePortWiring(
31
41
  workspace.config.paths.portWiring,
32
42
  ),
33
43
  properties: new Map(),
44
+ propertyFiles: new Map(),
34
45
  declared: new Set(),
35
46
  bound: new Set(),
36
47
  deferred: new Set(),
@@ -40,20 +51,31 @@ export async function analyzePortWiring(
40
51
  };
41
52
 
42
53
  let inferredAppPortsName: string | undefined;
54
+ let inferredAppPortsReference: ResolvedSourceReference | undefined;
43
55
  if (result.portsFileExists) {
44
- const sourceFile = await workspace.readSourceFile(
56
+ const reference = await sourceIndex?.resolveExport(
45
57
  workspace.config.paths.ports,
58
+ "AppPorts",
46
59
  );
60
+ const portsFile = reference?.file ?? workspace.config.paths.ports;
61
+ const portsName = reference?.exportName ?? "AppPorts";
62
+ const sourceFile = await workspace.readSourceFile(portsFile);
47
63
  const declarations = collectTypeDeclarations(sourceFile);
48
- inferredAppPortsName = inferredTypeQueryName("AppPorts", declarations);
64
+ inferredAppPortsName = inferredTypeQueryName(portsName, declarations);
65
+ if (inferredAppPortsName)
66
+ inferredAppPortsReference = await sourceIndex?.resolveName(
67
+ portsFile,
68
+ inferredAppPortsName,
69
+ );
49
70
  const collection = inferredAppPortsName
50
71
  ? { properties: [], indeterminate: false }
51
- : collectNamedTypeProperties("AppPorts", declarations, true);
72
+ : collectNamedTypeProperties(portsName, declarations, !sourceIndex);
52
73
  result.declarationIndeterminate = collection.indeterminate;
53
74
  for (const property of collection.properties) {
54
75
  const name = propertyName(property.name);
55
76
  if (name && !result.properties.has(name)) {
56
77
  result.properties.set(name, property);
78
+ result.propertyFiles.set(name, portsFile);
57
79
  result.declared.add(name);
58
80
  }
59
81
  }
@@ -63,12 +85,27 @@ export async function analyzePortWiring(
63
85
  if (inferredAppPortsName) result.declarationIndeterminate = true;
64
86
  return result;
65
87
  }
66
- const sourceFile = await workspace.readSourceFile(
88
+ const wiringReference = await sourceIndex?.resolveExport(
67
89
  workspace.config.paths.portWiring,
90
+ "initialPorts",
91
+ );
92
+ const sourceFile = await workspace.readSourceFile(
93
+ wiringReference?.file ?? workspace.config.paths.portWiring,
68
94
  );
69
95
  const values = collectVariableInitializers(sourceFile);
70
- const definitions = portDefinitionCalls(sourceFile);
71
- 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;
72
109
 
73
110
  for (const call of definitions.calls) {
74
111
  const objectArgument = call.arguments
@@ -124,7 +161,13 @@ export async function analyzePortWiring(
124
161
  if (inferredAppPortsName) {
125
162
  if (
126
163
  !definitions.indeterminate &&
127
- definitions.name === inferredAppPortsName
164
+ (sourceIndex
165
+ ? Boolean(
166
+ inferredAppPortsReference?.declaration &&
167
+ inferredAppPortsReference.declaration ===
168
+ wiringReference?.declaration,
169
+ )
170
+ : definitions.name === inferredAppPortsName)
128
171
  ) {
129
172
  result.appPortsInferredFromWiring = true;
130
173
  for (const name of result.bound) result.declared.add(name);
@@ -137,7 +180,10 @@ export async function analyzePortWiring(
137
180
  return result;
138
181
  }
139
182
 
140
- function portDefinitionCalls(sourceFile: ts.SourceFile): {
183
+ function portDefinitionCalls(
184
+ sourceFile: ts.SourceFile,
185
+ preferredName?: string,
186
+ ): {
141
187
  calls: ts.CallExpression[];
142
188
  name?: string;
143
189
  indeterminate: boolean;
@@ -172,8 +218,12 @@ function portDefinitionCalls(sourceFile: ts.SourceFile): {
172
218
  }
173
219
 
174
220
  const canonical = candidates.filter(
175
- (candidate) => candidate.name === "initialPorts" && candidate.exported,
221
+ (candidate) =>
222
+ candidate.name === (preferredName ?? "initialPorts") &&
223
+ candidate.exported,
176
224
  );
225
+ if (preferredName && canonical.length === 0)
226
+ return { calls: [], indeterminate: true };
177
227
  const exported = candidates.filter((candidate) => candidate.exported);
178
228
  const selected =
179
229
  canonical.length > 0
@@ -230,6 +280,12 @@ export async function analyzePortWiringFiles(options: {
230
280
  files: readonly string[];
231
281
  config: ResolvedBeignetConfig;
232
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
+ }
233
289
  const sourceFiles = new Map<string, ts.SourceFile>();
234
290
  return analyzePortWiring({
235
291
  config: options.config,
@@ -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(