@beignet/cli 0.0.42 → 0.0.43

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 (58) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +19 -4
  3. package/dist/analysis/source-index.d.ts +1 -1
  4. package/dist/analysis/source-index.d.ts.map +1 -1
  5. package/dist/analysis/source-index.js +4 -2
  6. package/dist/analysis/source-index.js.map +1 -1
  7. package/dist/analysis/workspace.d.ts +3 -2
  8. package/dist/analysis/workspace.d.ts.map +1 -1
  9. package/dist/analysis/workspace.js +51 -39
  10. package/dist/analysis/workspace.js.map +1 -1
  11. package/dist/app-map.d.ts.map +1 -1
  12. package/dist/app-map.js +65 -4
  13. package/dist/app-map.js.map +1 -1
  14. package/dist/inspect.d.ts +5 -0
  15. package/dist/inspect.d.ts.map +1 -1
  16. package/dist/inspect.js +32 -135
  17. package/dist/inspect.js.map +1 -1
  18. package/dist/make/inbox.js +4 -4
  19. package/dist/make/payments.js +6 -6
  20. package/dist/make/tenancy.d.ts.map +1 -1
  21. package/dist/make/tenancy.js +12 -14
  22. package/dist/make/tenancy.js.map +1 -1
  23. package/dist/make.d.ts.map +1 -1
  24. package/dist/make.js +33 -23
  25. package/dist/make.js.map +1 -1
  26. package/dist/registry-edits.d.ts +7 -1
  27. package/dist/registry-edits.d.ts.map +1 -1
  28. package/dist/registry-edits.js +61 -43
  29. package/dist/registry-edits.js.map +1 -1
  30. package/dist/templates/agents.d.ts.map +1 -1
  31. package/dist/templates/agents.js +8 -1
  32. package/dist/templates/agents.js.map +1 -1
  33. package/dist/templates/base.js +2 -2
  34. package/dist/templates/base.js.map +1 -1
  35. package/dist/templates/db/mysql.js +3 -3
  36. package/dist/templates/db/postgres.js +3 -3
  37. package/dist/templates/db/sqlite.js +3 -3
  38. package/dist/templates/server.js +4 -4
  39. package/dist/templates/server.js.map +1 -1
  40. package/dist/templates/todos.js +2 -2
  41. package/package.json +2 -2
  42. package/skills/app-structure/SKILL.md +9 -0
  43. package/src/analysis/source-index.ts +12 -3
  44. package/src/analysis/workspace.ts +94 -52
  45. package/src/app-map.ts +82 -4
  46. package/src/inspect.ts +40 -150
  47. package/src/make/inbox.ts +4 -4
  48. package/src/make/payments.ts +6 -6
  49. package/src/make/tenancy.ts +12 -14
  50. package/src/make.ts +36 -24
  51. package/src/registry-edits.ts +81 -39
  52. package/src/templates/agents.ts +8 -1
  53. package/src/templates/base.ts +2 -2
  54. package/src/templates/db/mysql.ts +3 -3
  55. package/src/templates/db/postgres.ts +3 -3
  56. package/src/templates/db/sqlite.ts +3 -3
  57. package/src/templates/server.ts +4 -4
  58. package/src/templates/todos.ts +2 -2
@@ -2,7 +2,6 @@ import { readdir, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import ts from "typescript";
4
4
  import {
5
- directoryPath,
6
5
  loadBeignetConfig,
7
6
  normalizePath,
8
7
  type ResolvedBeignetConfig,
@@ -22,7 +21,7 @@ export type AnalysisWorkspace = {
22
21
  files: string[];
23
22
  fileSet: Set<string>;
24
23
  config: ResolvedBeignetConfig;
25
- atAliasRoot: string;
24
+ compilerOptions: ts.CompilerOptions;
26
25
  readSource(file: string): Promise<string>;
27
26
  readSourceFile(file: string): Promise<ts.SourceFile>;
28
27
  };
@@ -35,7 +34,7 @@ export async function createAnalysisWorkspace(
35
34
  const files = await listProjectFiles(targetDir);
36
35
  const fileSet = new Set(files);
37
36
  const config = await loadBeignetConfig(targetDir, files);
38
- const atAliasRoot = resolveAtAliasRoot(targetDir);
37
+ const compilerOptions = resolveCompilerOptions(targetDir);
39
38
  const sources = new Map<string, string>();
40
39
  const sourceFiles = new Map<string, ts.SourceFile>();
41
40
 
@@ -53,7 +52,7 @@ export async function createAnalysisWorkspace(
53
52
  files,
54
53
  fileSet,
55
54
  config,
56
- atAliasRoot,
55
+ compilerOptions,
57
56
  readSource,
58
57
  async readSourceFile(file) {
59
58
  const cached = sourceFiles.get(file);
@@ -110,66 +109,109 @@ export async function listProjectFiles(targetDir: string): Promise<string[]> {
110
109
  }
111
110
 
112
111
  export function resolveLocalSourceFile(
113
- workspace: Pick<AnalysisWorkspace, "atAliasRoot" | "fileSet">,
112
+ workspace: Pick<
113
+ AnalysisWorkspace,
114
+ "compilerOptions" | "fileSet" | "targetDir"
115
+ >,
114
116
  importerFile: string,
115
117
  importPath: string,
116
118
  ): string | undefined {
117
- let candidate: string;
118
- if (importPath.startsWith("@/")) {
119
- candidate = path.posix.join(
120
- workspace.atAliasRoot,
121
- importPath.slice("@/".length),
119
+ const resolved = resolveModule(workspace, importerFile, importPath);
120
+ if (!resolved) return undefined;
121
+
122
+ const file = normalizePath(
123
+ path.relative(workspace.targetDir, resolved.resolvedFileName),
124
+ );
125
+ if (file === ".." || file.startsWith("../")) return undefined;
126
+ return workspace.fileSet.has(file) ? file : undefined;
127
+ }
128
+
129
+ export function isLocalModuleSpecifier(
130
+ workspace: Pick<
131
+ AnalysisWorkspace,
132
+ "compilerOptions" | "fileSet" | "targetDir"
133
+ >,
134
+ importerFile: string,
135
+ importPath: string,
136
+ ): boolean {
137
+ const extension = path.posix.extname(importPath);
138
+ if (
139
+ extension &&
140
+ ![".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"].includes(
141
+ extension,
142
+ )
143
+ ) {
144
+ return false;
145
+ }
146
+ if (importPath.startsWith(".")) return true;
147
+
148
+ const resolved = resolveModule(workspace, importerFile, importPath);
149
+ if (resolved) {
150
+ const file = normalizePath(
151
+ path.relative(workspace.targetDir, resolved.resolvedFileName),
122
152
  );
123
- } else if (importPath.startsWith(".")) {
124
- candidate = normalizePath(
125
- path.join(path.dirname(importerFile), importPath),
153
+ return (
154
+ file !== ".." && !file.startsWith("../") && workspace.fileSet.has(file)
126
155
  );
127
- } else {
128
- return undefined;
129
156
  }
130
157
 
131
- const withoutExtension = candidate.replace(/\.(c|m)?tsx?$/, "");
132
- const candidates = [
133
- candidate,
134
- `${withoutExtension}.ts`,
135
- `${withoutExtension}.tsx`,
136
- `${withoutExtension}.mts`,
137
- `${withoutExtension}.cts`,
138
- `${withoutExtension}/index.ts`,
139
- `${withoutExtension}/index.tsx`,
140
- `${withoutExtension}/index.mts`,
141
- `${withoutExtension}/index.cts`,
142
- ];
143
-
144
- return candidates.find((file) => workspace.fileSet.has(file));
158
+ const pathPatterns = Object.keys(workspace.compilerOptions.paths ?? {});
159
+ if (
160
+ pathPatterns.some(
161
+ (pattern) =>
162
+ // A catch-all also matches every bare package name. Local matches for
163
+ // it resolve above or have an app-owned baseUrl prefix below.
164
+ pattern !== "*" && modulePatternMatches(pattern, importPath),
165
+ )
166
+ ) {
167
+ return true;
168
+ }
169
+
170
+ const baseUrl = workspace.compilerOptions.baseUrl;
171
+ if (!baseUrl) return false;
172
+ const firstSegment = importPath.split("/")[0];
173
+ const prefix = normalizePath(
174
+ path.relative(workspace.targetDir, path.resolve(baseUrl, firstSegment)),
175
+ );
176
+ for (const file of workspace.fileSet) {
177
+ if (file === prefix || file.startsWith(`${prefix}/`)) return true;
178
+ }
179
+ return false;
145
180
  }
146
181
 
147
- function resolveAtAliasRoot(targetDir: string): string {
148
- const parsed = ts.getParsedCommandLineOfConfigFile(
149
- path.join(targetDir, "tsconfig.json"),
150
- {},
151
- {
152
- ...ts.sys,
153
- onUnRecoverableConfigFileDiagnostic: () => {},
154
- },
182
+ function resolveModule(
183
+ workspace: Pick<AnalysisWorkspace, "compilerOptions" | "targetDir">,
184
+ importerFile: string,
185
+ importPath: string,
186
+ ): ts.ResolvedModuleFull | undefined {
187
+ return ts.resolveModuleName(
188
+ importPath,
189
+ path.join(workspace.targetDir, importerFile),
190
+ workspace.compilerOptions,
191
+ ts.sys,
192
+ ).resolvedModule;
193
+ }
194
+
195
+ function modulePatternMatches(pattern: string, importPath: string): boolean {
196
+ const wildcard = pattern.indexOf("*");
197
+ if (wildcard === -1) return pattern === importPath;
198
+ return (
199
+ importPath.startsWith(pattern.slice(0, wildcard)) &&
200
+ importPath.endsWith(pattern.slice(wildcard + 1))
155
201
  );
156
- const aliasTarget = parsed?.options.paths?.["@/*"]?.[0];
157
- if (!aliasTarget) return "";
202
+ }
158
203
 
159
- const targetWithoutWildcard = aliasTarget.replace(/[/\\]?\*$/, "");
160
- const baseUrl = parsed.options.baseUrl ?? targetDir;
161
- const relativeTarget = normalizePath(
162
- path.relative(targetDir, path.resolve(baseUrl, targetWithoutWildcard)),
204
+ function resolveCompilerOptions(targetDir: string): ts.CompilerOptions {
205
+ return (
206
+ ts.getParsedCommandLineOfConfigFile(
207
+ path.join(targetDir, "tsconfig.json"),
208
+ {},
209
+ {
210
+ ...ts.sys,
211
+ onUnRecoverableConfigFileDiagnostic: () => {},
212
+ },
213
+ )?.options ?? {}
163
214
  );
164
- if (
165
- relativeTarget === "" ||
166
- relativeTarget === "." ||
167
- relativeTarget === ".." ||
168
- relativeTarget.startsWith("../")
169
- ) {
170
- return "";
171
- }
172
- return directoryPath(relativeTarget);
173
215
  }
174
216
 
175
217
  function scriptKind(file: string): ts.ScriptKind {
package/src/app-map.ts CHANGED
@@ -276,6 +276,13 @@ export async function mapApp(
276
276
  }
277
277
  }
278
278
 
279
+ await addUnresolvedContractDeclarations({
280
+ workspace,
281
+ sourceIndex,
282
+ contractIds,
283
+ unresolved,
284
+ });
285
+
279
286
  const routeIds = new Map<string, string>();
280
287
  for (const route of inspection.routes) {
281
288
  const contractId = contractIds.get(symbolKey(route.file, route.exportName));
@@ -647,6 +654,7 @@ export async function mapApp(
647
654
  });
648
655
 
649
656
  await addTables({ workspace, sourceIndex, addNode });
657
+ await addUnresolvedLocalImports({ workspace, unresolved });
650
658
  await addFeatureDependencies({
651
659
  workspace,
652
660
  featuresPath,
@@ -894,8 +902,8 @@ async function addRouteGroups(args: {
894
902
  const missing = args.diagnostics.some(
895
903
  (diagnostic) =>
896
904
  diagnostic.code === "BEIGNET_ROUTE_GROUP_UNREGISTERED" &&
897
- diagnostic.message.includes(file) &&
898
- diagnostic.message.includes(exportName),
905
+ diagnostic.subject?.file === file &&
906
+ diagnostic.subject.exportName === exportName,
899
907
  );
900
908
  args.addNode({
901
909
  id,
@@ -1221,6 +1229,7 @@ async function addFeatureDependencies(args: {
1221
1229
  const from = featureForFile(file, args.featuresPath);
1222
1230
  if (!from || !args.featureNames.has(from)) continue;
1223
1231
  for (const reference of await localImportReferences(args.workspace, file)) {
1232
+ if (!reference.resolvedFile) continue;
1224
1233
  const to = featureForFile(reference.resolvedFile, args.featuresPath);
1225
1234
  if (!to || to === from || !args.featureNames.has(to)) continue;
1226
1235
  const key = `${from}->${to}`;
@@ -1261,6 +1270,75 @@ async function addFeatureDependencies(args: {
1261
1270
  }
1262
1271
  }
1263
1272
 
1273
+ async function addUnresolvedLocalImports(args: {
1274
+ workspace: AnalysisWorkspace;
1275
+ unresolved: AppMapUnresolvedReference[];
1276
+ }): Promise<void> {
1277
+ for (const file of args.workspace.files.filter(isTypeScriptSource)) {
1278
+ for (const reference of await localImportReferences(args.workspace, file)) {
1279
+ if (reference.resolvedFile) continue;
1280
+ args.unresolved.push({
1281
+ code: "local_import_unresolved",
1282
+ message: `Could not resolve local module ${JSON.stringify(reference.importPath)} imported by ${file}.`,
1283
+ source: {
1284
+ file,
1285
+ line: reference.line,
1286
+ column: reference.column,
1287
+ },
1288
+ });
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ async function addUnresolvedContractDeclarations(args: {
1294
+ workspace: AnalysisWorkspace;
1295
+ sourceIndex: ReturnType<typeof createSourceIndex>;
1296
+ contractIds: Map<string, string>;
1297
+ unresolved: AppMapUnresolvedReference[];
1298
+ }): Promise<void> {
1299
+ for (const file of appMapContractSourceFiles(args.workspace)) {
1300
+ const sourceFile = await args.workspace.readSourceFile(file);
1301
+ for (const { exportName, declaration } of exportedVariableDeclarations(
1302
+ sourceFile,
1303
+ )) {
1304
+ if (args.contractIds.has(symbolKey(file, exportName))) continue;
1305
+ const call = declaration.initializer
1306
+ ? findCall(declaration.initializer, new Set(["defineContract"]))
1307
+ : undefined;
1308
+ if (!call) continue;
1309
+
1310
+ args.unresolved.push({
1311
+ code: "contract_declaration_unresolved",
1312
+ message: `Could not resolve the HTTP method and path for contract ${exportName}.`,
1313
+ source: await args.sourceIndex.sourceLocation(
1314
+ file,
1315
+ declaration,
1316
+ exportName,
1317
+ ),
1318
+ });
1319
+ }
1320
+ }
1321
+ }
1322
+
1323
+ function appMapContractSourceFiles(workspace: AnalysisWorkspace): string[] {
1324
+ const contractsPath = directoryPath(workspace.config.paths.contracts);
1325
+ const featureOwned =
1326
+ contractsPath === directoryPath(workspace.config.paths.features);
1327
+
1328
+ return workspace.files.filter((file) => {
1329
+ if (!file.endsWith(".ts") || file.endsWith(".test.ts")) return false;
1330
+ if (featureOwned) {
1331
+ const prefix = `${contractsPath}/`;
1332
+ if (!file.startsWith(prefix)) return false;
1333
+ const parts = file.slice(prefix.length).split("/");
1334
+ return parts.length === 2 && parts[1] === "contracts.ts";
1335
+ }
1336
+ return (
1337
+ file === `${contractsPath}.ts` || file.startsWith(`${contractsPath}/`)
1338
+ );
1339
+ });
1340
+ }
1341
+
1264
1342
  async function registryRecord(args: {
1265
1343
  workspace: AnalysisWorkspace;
1266
1344
  sourceIndex: ReturnType<typeof createSourceIndex>;
@@ -1488,8 +1566,8 @@ function definitionStatus(
1488
1566
  return diagnostics.some(
1489
1567
  (diagnostic) =>
1490
1568
  relevant.includes(diagnostic.code) &&
1491
- diagnostic.message.includes(file) &&
1492
- diagnostic.message.includes(exportName),
1569
+ diagnostic.subject?.file === file &&
1570
+ diagnostic.subject.exportName === exportName,
1493
1571
  )
1494
1572
  ? "missing"
1495
1573
  : "registered";
package/src/inspect.ts CHANGED
@@ -59,6 +59,7 @@ import {
59
59
  appendToNamedArray,
60
60
  appendToOutboxRegistryArray,
61
61
  arrayInitializerInfo,
62
+ codeCharacterIndexes,
62
63
  identifiersFromArrayExpression,
63
64
  insertAfterImports,
64
65
  matchingDelimiterIndex,
@@ -130,6 +131,11 @@ export type InspectDiagnostic = {
130
131
  message: string;
131
132
  file?: string;
132
133
  contract?: string;
134
+ /** Stable declaration identity for diagnostics about a named app concept. */
135
+ subject?: {
136
+ file: string;
137
+ exportName: string;
138
+ };
133
139
  };
134
140
 
135
141
  export type { InspectFix } from "./doctor-fixes.js";
@@ -1065,73 +1071,19 @@ function balancedObjectSource(
1065
1071
  startIndex: number,
1066
1072
  ): string | undefined {
1067
1073
  if (source[startIndex] !== "{") return undefined;
1068
-
1069
- let depth = 0;
1070
- let quote: '"' | "'" | "`" | undefined;
1071
- let escaped = false;
1072
-
1073
- for (let index = startIndex; index < source.length; index += 1) {
1074
- const char = source[index];
1075
-
1076
- if (quote) {
1077
- if (escaped) {
1078
- escaped = false;
1079
- continue;
1080
- }
1081
- if (char === "\\") {
1082
- escaped = true;
1083
- continue;
1084
- }
1085
- if (char === quote) quote = undefined;
1086
- continue;
1087
- }
1088
-
1089
- if (char === '"' || char === "'" || char === "`") {
1090
- quote = char;
1091
- continue;
1092
- }
1093
-
1094
- if (char === "{") {
1095
- depth += 1;
1096
- continue;
1097
- }
1098
-
1099
- if (char === "}") {
1100
- depth -= 1;
1101
- if (depth === 0) return source.slice(startIndex, index + 1);
1102
- }
1103
- }
1104
-
1105
- return undefined;
1074
+ const endIndex = matchingDelimiterIndex(source, startIndex, "{", "}");
1075
+ return endIndex === -1 ? undefined : source.slice(startIndex, endIndex + 1);
1106
1076
  }
1107
1077
 
1108
1078
  function topLevelObjectKeys(body: string): string[] {
1109
1079
  const keys: string[] = [];
1110
1080
  let depth = 0;
1111
- let quote: '"' | "'" | "`" | undefined;
1112
- let escaped = false;
1081
+ let skipUntil = 0;
1113
1082
 
1114
- for (let index = 0; index < body.length; index += 1) {
1083
+ for (const index of codeCharacterIndexes(body)) {
1084
+ if (index < skipUntil) continue;
1115
1085
  const char = body[index];
1116
1086
 
1117
- if (quote) {
1118
- if (escaped) {
1119
- escaped = false;
1120
- continue;
1121
- }
1122
- if (char === "\\") {
1123
- escaped = true;
1124
- continue;
1125
- }
1126
- if (char === quote) quote = undefined;
1127
- continue;
1128
- }
1129
-
1130
- if (char === '"' || char === "'" || char === "`") {
1131
- quote = char;
1132
- continue;
1133
- }
1134
-
1135
1087
  if (char === "{" || char === "[" || char === "(") {
1136
1088
  depth += 1;
1137
1089
  continue;
@@ -1150,8 +1102,7 @@ function topLevelObjectKeys(body: string): string[] {
1150
1102
  const name = identifier[0];
1151
1103
  const next = body.slice(index + name.length).match(/^\s*:/);
1152
1104
  if (next) keys.push(name);
1153
-
1154
- index += name.length - 1;
1105
+ skipUntil = index + name.length;
1155
1106
  }
1156
1107
 
1157
1108
  return keys;
@@ -4018,6 +3969,7 @@ async function inspectWorkflowRegistrationDrift(
4018
3969
  severity: "error",
4019
3970
  code,
4020
3971
  file: centralExists ? centralFile : registry.indexFile,
3972
+ subject: workflowArtifactSubject(registry, member),
4021
3973
  message: centralExists
4022
3974
  ? `${workflowArtifactOrigin(registry, member)}, but it is not registered in ${registrationTarget} in ${centralFile}, so the ${noun} never runs. Import ${registry.registryName} and spread it into the ${kind} list.`
4023
3975
  : `${workflowArtifactOrigin(registry, member)}, but ${centralFile} does not exist, so the ${noun} never runs. Register ${registry.registryName} in ${centralFile} (or run \`${makeCommand}\` to create it).`,
@@ -4033,6 +3985,7 @@ async function inspectWorkflowRegistrationDrift(
4033
3985
  severity: "warning",
4034
3986
  code: "BEIGNET_OUTBOX_EVENT_UNREGISTERED",
4035
3987
  file: config.paths.outbox,
3988
+ subject: workflowArtifactSubject(registry, member),
4036
3989
  message: `${workflowArtifactOrigin(registry, member)} and listeners react to it, but it is not registered in the events list of defineOutboxRegistry({...}) in ${config.paths.outbox}, so outbox drain cannot deliver it. Import ${registry.registryName} and spread it into the events list.`,
4037
3990
  });
4038
3991
  }
@@ -4044,6 +3997,7 @@ async function inspectWorkflowRegistrationDrift(
4044
3997
  severity: "warning",
4045
3998
  code: "BEIGNET_OUTBOX_JOB_UNREGISTERED",
4046
3999
  file: config.paths.outbox,
4000
+ subject: workflowArtifactSubject(registry, member),
4047
4001
  message: `${workflowArtifactOrigin(registry, member)}, but it is not registered in the jobs list of defineOutboxRegistry({...}) in ${config.paths.outbox}, so outbox drain cannot deliver it. Import ${registry.registryName} and spread it into the jobs list.`,
4048
4002
  });
4049
4003
  }
@@ -4065,6 +4019,7 @@ async function inspectWorkflowRegistrationDrift(
4065
4019
  drift.listeners.wiringFile ??
4066
4020
  drift.listeners.eventBusFile ??
4067
4021
  registry.indexFile,
4022
+ subject: workflowArtifactSubject(registry, member),
4068
4023
  message: `${workflowArtifactOrigin(registry, member)}, but no registerListeners(...) call references it, so the listener never receives events. Register ${registry.registryName} with registerListeners(...) in ${listenerTarget}.`,
4069
4024
  });
4070
4025
  }
@@ -4170,6 +4125,16 @@ function workflowArtifactOrigin(
4170
4125
  return `${memberFile} declares ${member} (exported through ${registry.registryName} in ${registry.indexFile})`;
4171
4126
  }
4172
4127
 
4128
+ function workflowArtifactSubject(
4129
+ registry: FeatureWorkflowRegistry,
4130
+ member: string,
4131
+ ): NonNullable<InspectDiagnostic["subject"]> {
4132
+ return {
4133
+ file: registry.memberFiles.get(member) ?? registry.indexFile,
4134
+ exportName: member,
4135
+ };
4136
+ }
4137
+
4173
4138
  async function workflowRegistrationDrift(
4174
4139
  targetDir: string,
4175
4140
  files: string[],
@@ -5651,26 +5616,9 @@ function splitTopLevelArguments(source: string): string[] {
5651
5616
  const args: string[] = [];
5652
5617
  let start = 0;
5653
5618
  let depth = 0;
5654
- let inString: string | undefined;
5655
- let escaped = false;
5656
5619
 
5657
- for (let index = 0; index < source.length; index++) {
5620
+ for (const index of codeCharacterIndexes(source)) {
5658
5621
  const char = source[index];
5659
- if (inString) {
5660
- if (escaped) {
5661
- escaped = false;
5662
- } else if (char === "\\") {
5663
- escaped = true;
5664
- } else if (char === inString) {
5665
- inString = undefined;
5666
- }
5667
- continue;
5668
- }
5669
-
5670
- if (char === '"' || char === "'" || char === "`") {
5671
- inString = char;
5672
- continue;
5673
- }
5674
5622
  if (char === "(" || char === "{" || char === "[") {
5675
5623
  depth += 1;
5676
5624
  continue;
@@ -6455,6 +6403,8 @@ function dedupeDiagnostics(
6455
6403
  diagnostic.code,
6456
6404
  diagnostic.file ?? "",
6457
6405
  diagnostic.contract ?? "",
6406
+ diagnostic.subject?.file ?? "",
6407
+ diagnostic.subject?.exportName ?? "",
6458
6408
  diagnostic.message,
6459
6409
  ].join(":");
6460
6410
  if (seen.has(key)) continue;
@@ -6644,6 +6594,10 @@ async function inspectFeatureRouteRegistration(
6644
6594
  code: "BEIGNET_ROUTE_GROUP_UNREGISTERED",
6645
6595
  file: config.paths.server,
6646
6596
  contract: routeGroupContract.exportName,
6597
+ subject: {
6598
+ file: routeGroup.file,
6599
+ exportName: routeGroup.name,
6600
+ },
6647
6601
  message: contract
6648
6602
  ? `${routeGroup.file} declares ${routeGroup.name} for ${contract.exportName} (${contract.method} ${contract.path}), but ${routeGroup.name} is not registered in defineRoutes([...]) in ${config.paths.server}. Import ${routeGroup.name} and add it to the central route list.`
6649
6603
  : `${routeGroup.file} declares ${routeGroup.name} for ${routeGroupContract.exportName}, but ${routeGroup.name} is not registered in defineRoutes([...]) in ${config.paths.server}. Import ${routeGroup.name} and add it to the central route list.`,
@@ -6765,44 +6719,8 @@ function balancedArraySource(
6765
6719
  startIndex: number,
6766
6720
  ): string | undefined {
6767
6721
  if (source[startIndex] !== "[") return undefined;
6768
-
6769
- let depth = 0;
6770
- let quote: '"' | "'" | "`" | undefined;
6771
- let escaped = false;
6772
-
6773
- for (let index = startIndex; index < source.length; index += 1) {
6774
- const char = source[index];
6775
-
6776
- if (quote) {
6777
- if (escaped) {
6778
- escaped = false;
6779
- continue;
6780
- }
6781
- if (char === "\\") {
6782
- escaped = true;
6783
- continue;
6784
- }
6785
- if (char === quote) quote = undefined;
6786
- continue;
6787
- }
6788
-
6789
- if (char === '"' || char === "'" || char === "`") {
6790
- quote = char;
6791
- continue;
6792
- }
6793
-
6794
- if (char === "[") {
6795
- depth += 1;
6796
- continue;
6797
- }
6798
-
6799
- if (char === "]") {
6800
- depth -= 1;
6801
- if (depth === 0) return source.slice(startIndex, index + 1);
6802
- }
6803
- }
6804
-
6805
- return undefined;
6722
+ const endIndex = matchingDelimiterIndex(source, startIndex, "[", "]");
6723
+ return endIndex === -1 ? undefined : source.slice(startIndex, endIndex + 1);
6806
6724
  }
6807
6725
 
6808
6726
  function routeGroupContractRef(
@@ -6832,21 +6750,10 @@ function routeGroupContractRef(
6832
6750
  }
6833
6751
 
6834
6752
  function registeredRouteGroups(source: string): Set<string> {
6835
- const defineRoutesMatch = /defineRoutes(?:<[^>]+>)?\(\[/.exec(source);
6836
- if (!defineRoutesMatch) return new Set();
6837
-
6838
- const defineRoutesSource = source.slice(defineRoutesMatch.index);
6839
- const defineRoutesEnd = defineRoutesSource.indexOf("]),");
6840
- const searchSource =
6841
- defineRoutesEnd === -1
6842
- ? defineRoutesSource
6843
- : defineRoutesSource.slice(0, defineRoutesEnd);
6844
-
6845
- return new Set(
6846
- [...searchSource.matchAll(/\b([A-Za-z_$][\w$]*)\b/g)].map(
6847
- (match) => match[1],
6848
- ),
6849
- );
6753
+ const routes = firstDefineRoutesArgInfo(source);
6754
+ return routes?.text.startsWith("[")
6755
+ ? identifiersFromArrayExpression(routes.text)
6756
+ : new Set();
6850
6757
  }
6851
6758
 
6852
6759
  async function registeredRouteGroupsForServer(
@@ -7699,26 +7606,9 @@ function firstCallArgInfo(
7699
7606
  argStart: number,
7700
7607
  ): { text: string; start: number; end: number } | undefined {
7701
7608
  let depth = 0;
7702
- let inString: string | undefined;
7703
- let escaped = false;
7704
7609
 
7705
- for (let index = argStart; index < source.length; index++) {
7610
+ for (const index of codeCharacterIndexes(source, argStart)) {
7706
7611
  const char = source[index];
7707
- if (inString) {
7708
- if (escaped) {
7709
- escaped = false;
7710
- } else if (char === "\\") {
7711
- escaped = true;
7712
- } else if (char === inString) {
7713
- inString = undefined;
7714
- }
7715
- continue;
7716
- }
7717
-
7718
- if (char === '"' || char === "'" || char === "`") {
7719
- inString = char;
7720
- continue;
7721
- }
7722
7612
 
7723
7613
  if (char === "[" || char === "(" || char === "{") {
7724
7614
  depth++;
package/src/make/inbox.ts CHANGED
@@ -1437,17 +1437,17 @@ export const inboxNotifications = ${dialect.tableFunction}(
1437
1437
  \t\treadAt: ${column("read_at")},
1438
1438
  \t\tcreatedAt: ${column("created_at")}.notNull(),
1439
1439
  \t},
1440
- \t(table) => ({
1441
- \t\tunreadIdx: index("inbox_notifications_unread_idx").on(
1440
+ \t(table) => [
1441
+ \t\tindex("inbox_notifications_unread_idx").on(
1442
1442
  \t\t\ttable.userId,
1443
1443
  \t\t\ttable.readAt,
1444
1444
  \t\t),
1445
- \t\tcursorIdx: index("inbox_notifications_cursor_idx").on(
1445
+ \t\tindex("inbox_notifications_cursor_idx").on(
1446
1446
  \t\t\ttable.userId,
1447
1447
  \t\t\ttable.createdAt,
1448
1448
  \t\t\ttable.id,
1449
1449
  \t\t),
1450
- \t}),
1450
+ \t],
1451
1451
  );
1452
1452
  `;
1453
1453
  }
@@ -680,11 +680,11 @@ export const createCheckoutSessionInputSchema = z.object({
680
680
 
681
681
  export const createCheckoutSessionOutputSchema = z.object({
682
682
  \tsessionId: z.string(),
683
- \turl: z.string().url(),
683
+ \turl: z.url(),
684
684
  });
685
685
 
686
686
  export const createBillingPortalSessionOutputSchema = z.object({
687
- \turl: z.string().url(),
687
+ \turl: z.url(),
688
688
  });
689
689
 
690
690
  export const billingStatusOutputSchema = z.object({
@@ -1800,14 +1800,14 @@ export const billingAccounts = ${dialect.tableFunction}(
1800
1800
  \t\tlastEventId: ${idColumn("last_event_id")},
1801
1801
  \t\tupdatedAt: ${idColumn("updated_at")}.notNull(),
1802
1802
  \t},
1803
- \t(table) => ({
1804
- \t\tproviderCustomerUnique: uniqueIndex(
1803
+ \t(table) => [
1804
+ \t\tuniqueIndex(
1805
1805
  \t\t\t"billing_accounts_provider_customer_unique",
1806
1806
  \t\t).on(table.provider, table.customerId),
1807
- \t\tproviderSubscriptionUnique: uniqueIndex(
1807
+ \t\tuniqueIndex(
1808
1808
  \t\t\t"billing_accounts_provider_subscription_unique",
1809
1809
  \t\t).on(table.provider, table.subscriptionId),
1810
- \t}),
1810
+ \t],
1811
1811
  );
1812
1812
  `;
1813
1813
  }