proteum 2.0.0 → 2.1.0

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 (94) hide show
  1. package/AGENTS.md +13 -1
  2. package/README.md +375 -0
  3. package/agents/framework/AGENTS.md +917 -0
  4. package/agents/project/AGENTS.md +138 -0
  5. package/agents/{codex → project}/CODING_STYLE.md +3 -2
  6. package/agents/project/client/AGENTS.md +108 -0
  7. package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
  8. package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
  9. package/agents/project/server/services/AGENTS.md +170 -0
  10. package/agents/{codex → project}/tests/AGENTS.md +1 -0
  11. package/cli/app/config.ts +3 -2
  12. package/cli/app/index.ts +6 -66
  13. package/cli/bin.js +7 -2
  14. package/cli/commands/build.ts +94 -27
  15. package/cli/commands/check.ts +15 -1
  16. package/cli/commands/dev.ts +288 -132
  17. package/cli/commands/doctor.ts +108 -0
  18. package/cli/commands/explain.ts +226 -0
  19. package/cli/commands/init.ts +76 -70
  20. package/cli/commands/lint.ts +18 -1
  21. package/cli/commands/refresh.ts +16 -6
  22. package/cli/commands/typecheck.ts +14 -1
  23. package/cli/compiler/artifacts/controllers.ts +150 -0
  24. package/cli/compiler/artifacts/discovery.ts +132 -0
  25. package/cli/compiler/artifacts/manifest.ts +267 -0
  26. package/cli/compiler/artifacts/routing.ts +315 -0
  27. package/cli/compiler/artifacts/services.ts +480 -0
  28. package/cli/compiler/artifacts/shared.ts +12 -0
  29. package/cli/compiler/client/identite.ts +2 -1
  30. package/cli/compiler/client/index.ts +13 -3
  31. package/cli/compiler/common/controllers.ts +23 -28
  32. package/cli/compiler/common/files/style.ts +3 -4
  33. package/cli/compiler/common/generatedRouteModules.ts +333 -19
  34. package/cli/compiler/common/proteumManifest.ts +133 -0
  35. package/cli/compiler/index.ts +33 -896
  36. package/cli/compiler/server/index.ts +21 -4
  37. package/cli/context.ts +71 -0
  38. package/cli/index.ts +39 -181
  39. package/cli/presentation/commands.ts +208 -0
  40. package/cli/presentation/compileReporter.ts +65 -0
  41. package/cli/presentation/devSession.ts +70 -0
  42. package/cli/presentation/help.ts +193 -0
  43. package/cli/presentation/ink.ts +69 -0
  44. package/cli/presentation/layout.ts +83 -0
  45. package/cli/runtime/argv.ts +49 -0
  46. package/cli/runtime/command.ts +25 -0
  47. package/cli/runtime/commands.ts +221 -0
  48. package/cli/runtime/importEsm.ts +7 -0
  49. package/cli/runtime/verbose.ts +15 -0
  50. package/cli/utils/agents.ts +5 -4
  51. package/cli/utils/keyboard.ts +12 -6
  52. package/client/app/index.ts +0 -6
  53. package/client/services/router/index.tsx +1 -1
  54. package/client/services/router/response/index.tsx +2 -2
  55. package/common/dev/serverHotReload.ts +12 -0
  56. package/common/router/index.ts +3 -2
  57. package/common/router/layouts.ts +1 -1
  58. package/common/router/pageSetup.ts +1 -0
  59. package/package.json +10 -8
  60. package/prettier/router-registration-plugin.cjs +52 -0
  61. package/prettier.config.cjs +1 -0
  62. package/scripts/cleanup-generated-controllers.ts +2 -2
  63. package/scripts/fix-reference-app-typing.ts +2 -2
  64. package/scripts/format-router-registrations.ts +119 -0
  65. package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
  66. package/scripts/refactor-server-controllers.ts +19 -18
  67. package/scripts/refactor-server-runtime-aliases.ts +1 -1
  68. package/server/app/commands.ts +309 -25
  69. package/server/app/container/config.ts +1 -1
  70. package/server/app/container/index.ts +2 -2
  71. package/server/app/controller/index.ts +13 -4
  72. package/server/app/index.ts +53 -37
  73. package/server/app/service/container.ts +26 -28
  74. package/server/app/service/index.ts +10 -20
  75. package/server/app.tsconfig.json +9 -2
  76. package/server/index.ts +32 -1
  77. package/server/services/auth/index.ts +234 -15
  78. package/server/services/auth/router/index.ts +39 -7
  79. package/server/services/auth/router/request.ts +40 -8
  80. package/server/services/disks/index.ts +1 -1
  81. package/server/services/prisma/Facet.ts +2 -2
  82. package/server/services/prisma/index.ts +22 -5
  83. package/server/services/prisma/mariadb.ts +47 -0
  84. package/server/services/router/http/index.ts +9 -1
  85. package/server/services/router/index.ts +10 -4
  86. package/server/services/router/response/index.ts +26 -6
  87. package/types/auth-check-rules.test.ts +51 -0
  88. package/types/controller-request-context.test.ts +55 -0
  89. package/types/service-config.test.ts +39 -0
  90. package/agents/codex/AGENTS.md +0 -95
  91. package/agents/codex/client/AGENTS.md +0 -102
  92. package/agents/codex/server/services/AGENTS.md +0 -137
  93. package/server/services/models.7z +0 -0
  94. /package/agents/{codex → project}/agents.md.zip +0 -0
@@ -2,14 +2,39 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import ts from 'typescript';
4
4
 
5
+ import { getRouteSetupOptionKey } from '../../../common/router/pageSetup';
5
6
  import writeIfChanged from '../writeIfChanged';
6
7
 
7
8
  type TRouteSide = 'client' | 'server';
8
9
  type TRouteRuntime = 'client' | 'server';
10
+ export type TIndexedSourceLocation = { line: number; column: number };
11
+ export type TIndexedRouteTargetResolution = 'literal' | 'static-expression' | 'dynamic-expression';
9
12
 
10
13
  type TImportedService = { importedName: string; localName: string };
11
14
 
12
- type TRouteDefinition = { args: ts.NodeArray<ts.Expression>; methodName: string; serviceLocalName: string };
15
+ type TRouteDefinition = {
16
+ args: ts.NodeArray<ts.Expression>;
17
+ methodName: string;
18
+ serviceLocalName: string;
19
+ callExpression: ts.CallExpression;
20
+ };
21
+
22
+ export type TIndexedRouteDefinition = {
23
+ methodName: string;
24
+ serviceLocalName: string;
25
+ sourceLocation: TIndexedSourceLocation;
26
+ targetResolution: TIndexedRouteTargetResolution;
27
+ path?: string;
28
+ pathRaw?: string;
29
+ code?: number;
30
+ codeRaw?: string;
31
+ optionKeys: string[];
32
+ normalizedOptionKeys: string[];
33
+ invalidOptionKeys: string[];
34
+ reservedOptionKeys: string[];
35
+ optionsRaw?: string;
36
+ hasSetup: boolean;
37
+ };
13
38
 
14
39
  type TGeneratedClientRouteModuleOptions = { chunkId: string; filepath: string };
15
40
 
@@ -39,6 +64,164 @@ const normalizeFilepath = (value: string) => path.resolve(value).replace(/\\/g,
39
64
  const getNodeText = (sourceFile: ts.SourceFile, node: ts.Node) =>
40
65
  sourceFile.text.slice(node.getStart(sourceFile), node.getEnd());
41
66
 
67
+ const getNodeLocation = (sourceFile: ts.SourceFile, node: ts.Node): TIndexedSourceLocation => {
68
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
69
+
70
+ return { line: line + 1, column: character + 1 };
71
+ };
72
+
73
+ const getLiteralStringValue = (node: ts.Expression) => {
74
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
75
+ return undefined;
76
+ };
77
+
78
+ const getLiteralNumberValue = (node: ts.Expression) => {
79
+ if (!ts.isNumericLiteral(node)) return undefined;
80
+
81
+ const value = Number(node.text);
82
+
83
+ return Number.isFinite(value) ? value : undefined;
84
+ };
85
+
86
+ const getObjectLiteralPropertyKey = (name: ts.PropertyName) => {
87
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
88
+ if (ts.isComputedPropertyName(name)) return undefined;
89
+ return undefined;
90
+ };
91
+
92
+ const getObjectLiteralPropertyKeys = (node: ts.ObjectLiteralExpression) =>
93
+ node.properties.flatMap((property) => {
94
+ if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property)) {
95
+ const key = getObjectLiteralPropertyKey(property.name);
96
+ return key ? [key] : [];
97
+ }
98
+
99
+ if (ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
100
+ const key = getObjectLiteralPropertyKey(property.name);
101
+ return key ? [key] : [];
102
+ }
103
+
104
+ return [];
105
+ });
106
+
107
+ const tryEvaluateStaticExpression = (
108
+ node: ts.Expression,
109
+ bindingInitializers: Map<string, ts.Expression>,
110
+ resolvedBindings: Map<string, string | number | undefined>,
111
+ activeBindings = new Set<string>(),
112
+ ): string | number | undefined => {
113
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
114
+
115
+ if (ts.isNumericLiteral(node)) {
116
+ const value = Number(node.text);
117
+ return Number.isFinite(value) ? value : undefined;
118
+ }
119
+
120
+ if (ts.isParenthesizedExpression(node)) {
121
+ return tryEvaluateStaticExpression(node.expression, bindingInitializers, resolvedBindings, activeBindings);
122
+ }
123
+
124
+ if (ts.isIdentifier(node)) {
125
+ if (resolvedBindings.has(node.text)) return resolvedBindings.get(node.text);
126
+
127
+ const initializer = bindingInitializers.get(node.text);
128
+ if (!initializer || activeBindings.has(node.text)) return undefined;
129
+
130
+ activeBindings.add(node.text);
131
+ const value = tryEvaluateStaticExpression(initializer, bindingInitializers, resolvedBindings, activeBindings);
132
+ activeBindings.delete(node.text);
133
+ resolvedBindings.set(node.text, value);
134
+
135
+ return value;
136
+ }
137
+
138
+ if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken) {
139
+ const operand = tryEvaluateStaticExpression(node.operand, bindingInitializers, resolvedBindings, activeBindings);
140
+ return typeof operand === 'number' ? -operand : undefined;
141
+ }
142
+
143
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
144
+ const left = tryEvaluateStaticExpression(node.left, bindingInitializers, resolvedBindings, activeBindings);
145
+ const right = tryEvaluateStaticExpression(node.right, bindingInitializers, resolvedBindings, activeBindings);
146
+
147
+ if (left === undefined || right === undefined) return undefined;
148
+
149
+ if (typeof left === 'string' || typeof right === 'string') return String(left) + String(right);
150
+ if (typeof left === 'number' && typeof right === 'number') return left + right;
151
+
152
+ return undefined;
153
+ }
154
+
155
+ if (ts.isTemplateExpression(node)) {
156
+ let output = node.head.text;
157
+
158
+ for (const span of node.templateSpans) {
159
+ const value = tryEvaluateStaticExpression(span.expression, bindingInitializers, resolvedBindings, activeBindings);
160
+ if (value === undefined) return undefined;
161
+
162
+ output += String(value) + span.literal.text;
163
+ }
164
+
165
+ return output;
166
+ }
167
+
168
+ return undefined;
169
+ };
170
+
171
+ const collectStaticBindings = (sourceFile: ts.SourceFile) => {
172
+ const bindingInitializers = new Map<string, ts.Expression>();
173
+ const resolvedBindings = new Map<string, string | number | undefined>();
174
+
175
+ for (const statement of sourceFile.statements) {
176
+ if (!ts.isVariableStatement(statement)) continue;
177
+ if (!(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
178
+
179
+ for (const declaration of statement.declarationList.declarations) {
180
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
181
+
182
+ bindingInitializers.set(declaration.name.text, declaration.initializer);
183
+ }
184
+ }
185
+
186
+ for (const bindingName of bindingInitializers.keys()) {
187
+ if (resolvedBindings.has(bindingName)) continue;
188
+
189
+ const initializer = bindingInitializers.get(bindingName);
190
+ if (!initializer) continue;
191
+
192
+ resolvedBindings.set(
193
+ bindingName,
194
+ tryEvaluateStaticExpression(initializer, bindingInitializers, resolvedBindings, new Set([bindingName])),
195
+ );
196
+ }
197
+
198
+ return resolvedBindings;
199
+ };
200
+
201
+ const getRouteOptionMetadata = (node: ts.ObjectLiteralExpression | undefined) => {
202
+ const optionKeys = node ? getObjectLiteralPropertyKeys(node) : [];
203
+ const normalizedOptionKeys: string[] = [];
204
+ const invalidOptionKeys: string[] = [];
205
+ const reservedOptionKeys: string[] = [];
206
+
207
+ for (const optionKey of optionKeys) {
208
+ try {
209
+ const normalizedOptionKey = getRouteSetupOptionKey(optionKey);
210
+
211
+ if (normalizedOptionKey) {
212
+ normalizedOptionKeys.push(normalizedOptionKey);
213
+ continue;
214
+ }
215
+
216
+ invalidOptionKeys.push(optionKey);
217
+ } catch (error) {
218
+ reservedOptionKeys.push(optionKey);
219
+ }
220
+ }
221
+
222
+ return { optionKeys, normalizedOptionKeys, invalidOptionKeys, reservedOptionKeys };
223
+ };
224
+
42
225
  const routeModuleExtensions = ['.ts', '.tsx', '.js', '.jsx'];
43
226
 
44
227
  const resolveRouteImport = (sourceFilepath: string, moduleSpecifier: string, routeSourceFilepaths?: Set<string>) => {
@@ -151,6 +334,7 @@ const collectRouteDefinitions = (
151
334
  args: statement.expression.arguments,
152
335
  methodName: callee.name.text,
153
336
  serviceLocalName: callee.expression.text,
337
+ callExpression: statement.expression,
154
338
  });
155
339
 
156
340
  stripRanges.push({ start: statement.getStart(sourceFile), end: statement.getEnd() });
@@ -248,35 +432,28 @@ const buildDestructuring = (importedServices: TImportedService[]) => {
248
432
  return `const { ${parts.join(', ')} } = app;`;
249
433
  };
250
434
 
251
- const buildClientRegisterArgs = (
252
- sourceFile: ts.SourceFile,
253
- definition: TRouteDefinition,
254
- clientRoute: TGeneratedClientRouteModuleOptions,
255
- ) => {
435
+ const getClientRouteSignature = (sourceFile: ts.SourceFile, definition: TRouteDefinition) => {
256
436
  const [, ...routeArgs] = [...definition.args];
257
- const injectedOptions = `{ id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`;
258
437
 
259
438
  if (routeArgs.length === 1) {
260
- return [injectedOptions, getNodeText(sourceFile, routeArgs[0])];
439
+ return { hasSetup: false, renderArg: routeArgs[0] };
261
440
  }
262
441
 
263
442
  if (routeArgs.length === 2) {
264
443
  if (ts.isObjectLiteralExpression(routeArgs[0])) {
265
- return [
266
- `{ ...(${getNodeText(sourceFile, routeArgs[0])}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
267
- getNodeText(sourceFile, routeArgs[1]),
268
- ];
444
+ return { hasSetup: false, optionsArg: routeArgs[0], renderArg: routeArgs[1] };
269
445
  }
270
446
 
271
- return [injectedOptions, getNodeText(sourceFile, routeArgs[0]), getNodeText(sourceFile, routeArgs[1])];
447
+ return { hasSetup: true, setupArg: routeArgs[0], renderArg: routeArgs[1] };
272
448
  }
273
449
 
274
450
  if (routeArgs.length === 3 && ts.isObjectLiteralExpression(routeArgs[0])) {
275
- return [
276
- `{ ...(${getNodeText(sourceFile, routeArgs[0])}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
277
- getNodeText(sourceFile, routeArgs[1]),
278
- getNodeText(sourceFile, routeArgs[2]),
279
- ];
451
+ return {
452
+ hasSetup: true,
453
+ optionsArg: routeArgs[0],
454
+ setupArg: routeArgs[1],
455
+ renderArg: routeArgs[2],
456
+ };
280
457
  }
281
458
 
282
459
  throw new Error(
@@ -284,6 +461,36 @@ const buildClientRegisterArgs = (
284
461
  );
285
462
  };
286
463
 
464
+ const buildClientRegisterArgs = (
465
+ sourceFile: ts.SourceFile,
466
+ definition: TRouteDefinition,
467
+ clientRoute: TGeneratedClientRouteModuleOptions,
468
+ ) => {
469
+ const { optionsArg, setupArg, renderArg } = getClientRouteSignature(sourceFile, definition);
470
+ const injectedOptions = `{ id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`;
471
+
472
+ if (!optionsArg && !setupArg) {
473
+ return [injectedOptions, getNodeText(sourceFile, renderArg)];
474
+ }
475
+
476
+ if (optionsArg && !setupArg) {
477
+ return [
478
+ `{ ...(${getNodeText(sourceFile, optionsArg)}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
479
+ getNodeText(sourceFile, renderArg),
480
+ ];
481
+ }
482
+
483
+ if (!optionsArg && setupArg) {
484
+ return [injectedOptions, getNodeText(sourceFile, setupArg), getNodeText(sourceFile, renderArg)];
485
+ }
486
+
487
+ return [
488
+ `{ ...(${getNodeText(sourceFile, optionsArg!)}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
489
+ getNodeText(sourceFile, setupArg!),
490
+ getNodeText(sourceFile, renderArg),
491
+ ];
492
+ };
493
+
287
494
  const buildRegisterStatements = (
288
495
  sourceFile: ts.SourceFile,
289
496
  side: TRouteSide,
@@ -321,6 +528,113 @@ const buildRegisterStatements = (
321
528
  export const getGeneratedRouteModuleFilepath = (generatedRoot: string, sourceRoot: string, sourceFilepath: string) =>
322
529
  path.join(generatedRoot, 'route-modules', path.relative(sourceRoot, sourceFilepath));
323
530
 
531
+ export const indexRouteDefinitions = ({ side, sourceFilepath }: { side: TRouteSide; sourceFilepath: string }) => {
532
+ const code = fs.readFileSync(sourceFilepath, 'utf8');
533
+ const sourceFile = parseSourceFile(sourceFilepath, code);
534
+ const stripRanges: Array<{ start: number; end: number }> = [];
535
+ const importedServices = collectImportedServices(sourceFile, side, stripRanges);
536
+ const definitions = collectRouteDefinitions(sourceFile, importedServices, stripRanges);
537
+ const staticBindings = collectStaticBindings(sourceFile);
538
+
539
+ if (definitions.length === 0) {
540
+ throw new Error(`No route definitions were found in ${sourceFilepath}.`);
541
+ }
542
+
543
+ if (side === 'client' && definitions.length !== 1) {
544
+ throw new Error(
545
+ `Frontend route definition files can contain only one route definition. ${definitions.length} were found in ${sourceFilepath}.`,
546
+ );
547
+ }
548
+
549
+ return definitions.map<TIndexedRouteDefinition>((definition) => {
550
+ const sourceLocation = getNodeLocation(sourceFile, definition.callExpression);
551
+ const resolveStaticValue = (node: ts.Expression) => tryEvaluateStaticExpression(node, new Map(), staticBindings);
552
+
553
+ if (side === 'client') {
554
+ const targetArg = definition.args[0];
555
+ const clientSignature = getClientRouteSignature(sourceFile, definition);
556
+ const optionMetadata = getRouteOptionMetadata(clientSignature.optionsArg);
557
+ const resolvedStaticValue = resolveStaticValue(targetArg);
558
+
559
+ return definition.methodName === 'error'
560
+ ? {
561
+ methodName: definition.methodName,
562
+ serviceLocalName: definition.serviceLocalName,
563
+ sourceLocation,
564
+ targetResolution:
565
+ getLiteralNumberValue(targetArg) !== undefined
566
+ ? 'literal'
567
+ : typeof resolvedStaticValue === 'number'
568
+ ? 'static-expression'
569
+ : 'dynamic-expression',
570
+ code:
571
+ getLiteralNumberValue(targetArg) ??
572
+ (typeof resolvedStaticValue === 'number' ? resolvedStaticValue : undefined),
573
+ codeRaw: getNodeText(sourceFile, targetArg),
574
+ optionKeys: optionMetadata.optionKeys,
575
+ normalizedOptionKeys: optionMetadata.normalizedOptionKeys,
576
+ invalidOptionKeys: optionMetadata.invalidOptionKeys,
577
+ reservedOptionKeys: optionMetadata.reservedOptionKeys,
578
+ optionsRaw: clientSignature.optionsArg
579
+ ? getNodeText(sourceFile, clientSignature.optionsArg)
580
+ : undefined,
581
+ hasSetup: clientSignature.hasSetup,
582
+ }
583
+ : {
584
+ methodName: definition.methodName,
585
+ serviceLocalName: definition.serviceLocalName,
586
+ sourceLocation,
587
+ targetResolution:
588
+ getLiteralStringValue(targetArg) !== undefined
589
+ ? 'literal'
590
+ : typeof resolvedStaticValue === 'string'
591
+ ? 'static-expression'
592
+ : 'dynamic-expression',
593
+ path:
594
+ getLiteralStringValue(targetArg) ??
595
+ (typeof resolvedStaticValue === 'string' ? resolvedStaticValue : undefined),
596
+ pathRaw: getNodeText(sourceFile, targetArg),
597
+ optionKeys: optionMetadata.optionKeys,
598
+ normalizedOptionKeys: optionMetadata.normalizedOptionKeys,
599
+ invalidOptionKeys: optionMetadata.invalidOptionKeys,
600
+ reservedOptionKeys: optionMetadata.reservedOptionKeys,
601
+ optionsRaw: clientSignature.optionsArg
602
+ ? getNodeText(sourceFile, clientSignature.optionsArg)
603
+ : undefined,
604
+ hasSetup: clientSignature.hasSetup,
605
+ };
606
+ }
607
+
608
+ const targetArg = definition.args[0];
609
+ const optionsArg =
610
+ definition.args.length >= 3 && ts.isObjectLiteralExpression(definition.args[1])
611
+ ? definition.args[1]
612
+ : undefined;
613
+ const optionMetadata = getRouteOptionMetadata(optionsArg);
614
+ const resolvedPath = getLiteralStringValue(targetArg) ?? resolveStaticValue(targetArg);
615
+
616
+ return {
617
+ methodName: definition.methodName,
618
+ serviceLocalName: definition.serviceLocalName,
619
+ sourceLocation,
620
+ targetResolution:
621
+ getLiteralStringValue(targetArg) !== undefined
622
+ ? 'literal'
623
+ : typeof resolvedPath === 'string'
624
+ ? 'static-expression'
625
+ : 'dynamic-expression',
626
+ path: typeof resolvedPath === 'string' ? resolvedPath : undefined,
627
+ pathRaw: getNodeText(sourceFile, targetArg),
628
+ optionKeys: optionMetadata.optionKeys,
629
+ normalizedOptionKeys: optionMetadata.normalizedOptionKeys,
630
+ invalidOptionKeys: optionMetadata.invalidOptionKeys,
631
+ reservedOptionKeys: optionMetadata.reservedOptionKeys,
632
+ optionsRaw: optionsArg ? getNodeText(sourceFile, optionsArg) : undefined,
633
+ hasSetup: false,
634
+ };
635
+ });
636
+ };
637
+
324
638
  export const writeGeneratedRouteModule = ({
325
639
  outputFilepath,
326
640
  runtime,
@@ -346,7 +660,7 @@ export const writeGeneratedRouteModule = ({
346
660
  sourceFilepath,
347
661
  );
348
662
  const registerStatements = buildRegisterStatements(sourceFile, side, definitions, clientRoute);
349
- const runtimeAppImportPath = runtime === 'client' ? '@/client/index' : '@/server/.generated/app';
663
+ const runtimeAppImportPath = runtime === 'client' ? '@/client/index' : '@/server/index';
350
664
 
351
665
  const content = `/*----------------------------------
352
666
  - GENERATED FILE
@@ -0,0 +1,133 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+
4
+ import writeIfChanged from '../writeIfChanged';
5
+
6
+ export type TProteumManifestScope = 'app' | 'framework';
7
+ export type TProteumManifestSourceLocation = { line: number; column: number };
8
+ export type TProteumManifestRouteTargetResolution = 'literal' | 'static-expression' | 'dynamic-expression';
9
+ export type TProteumManifestDiagnosticLevel = 'warning' | 'error';
10
+
11
+ export type TProteumManifestDiagnostic = {
12
+ level: TProteumManifestDiagnosticLevel;
13
+ code: string;
14
+ message: string;
15
+ filepath: string;
16
+ sourceLocation?: TProteumManifestSourceLocation;
17
+ relatedFilepaths?: string[];
18
+ };
19
+
20
+ export type TProteumManifestService = {
21
+ kind: 'service' | 'ref';
22
+ id?: string;
23
+ registeredName: string;
24
+ metaName?: string;
25
+ parent: string;
26
+ priority: number;
27
+ importPath?: string;
28
+ sourceDir?: string;
29
+ metasFilepath?: string;
30
+ refTo?: string;
31
+ scope: TProteumManifestScope;
32
+ };
33
+
34
+ export type TProteumManifestController = {
35
+ className: string;
36
+ importPath: string;
37
+ filepath: string;
38
+ sourceLocation: TProteumManifestSourceLocation;
39
+ routeBasePath: string;
40
+ methodName: string;
41
+ inputCallsCount: number;
42
+ hasInput: boolean;
43
+ routePath: string;
44
+ httpPath: string;
45
+ clientAccessor: string;
46
+ scope: TProteumManifestScope;
47
+ };
48
+
49
+ export type TProteumManifestRoute = {
50
+ kind: 'client-page' | 'client-error' | 'server-route';
51
+ methodName: string;
52
+ serviceLocalName: string;
53
+ filepath: string;
54
+ sourceLocation: TProteumManifestSourceLocation;
55
+ targetResolution: TProteumManifestRouteTargetResolution;
56
+ path?: string;
57
+ pathRaw?: string;
58
+ code?: number;
59
+ codeRaw?: string;
60
+ optionKeys: string[];
61
+ normalizedOptionKeys: string[];
62
+ invalidOptionKeys: string[];
63
+ reservedOptionKeys: string[];
64
+ optionsRaw?: string;
65
+ hasSetup: boolean;
66
+ chunkId?: string;
67
+ chunkFilepath?: string;
68
+ scope: TProteumManifestScope;
69
+ };
70
+
71
+ export type TProteumManifestLayout = {
72
+ chunkId: string;
73
+ filepath: string;
74
+ importPath: string;
75
+ depth: number;
76
+ scope: TProteumManifestScope;
77
+ };
78
+
79
+ export type TProteumManifest = {
80
+ version: 1;
81
+ app: {
82
+ root: string;
83
+ coreRoot: string;
84
+ identityFilepath: string;
85
+ identity: {
86
+ name: string;
87
+ identifier: string;
88
+ description: string;
89
+ language?: string;
90
+ locale?: string;
91
+ title?: string;
92
+ titleSuffix?: string;
93
+ fullTitle?: string;
94
+ webDescription?: string;
95
+ version?: string;
96
+ };
97
+ };
98
+ conventions: {
99
+ routeSetupOptionKeys: string[];
100
+ reservedRouteSetupKeys: string[];
101
+ };
102
+ env: {
103
+ sourceFilepath: string;
104
+ loadedTopLevelKeys: string[];
105
+ requiredTopLevelKeys: string[];
106
+ };
107
+ services: {
108
+ app: TProteumManifestService[];
109
+ routerPlugins: TProteumManifestService[];
110
+ };
111
+ controllers: TProteumManifestController[];
112
+ routes: {
113
+ client: TProteumManifestRoute[];
114
+ server: TProteumManifestRoute[];
115
+ };
116
+ layouts: TProteumManifestLayout[];
117
+ diagnostics: TProteumManifestDiagnostic[];
118
+ };
119
+
120
+ export const getProteumManifestPath = (appRoot: string) => path.join(appRoot, '.proteum', 'manifest.json');
121
+
122
+ export const writeProteumManifest = (appRoot: string, manifest: TProteumManifest) =>
123
+ writeIfChanged(getProteumManifestPath(appRoot), JSON.stringify(manifest, null, 2) + '\n');
124
+
125
+ export const readProteumManifest = (appRoot: string) => {
126
+ const filepath = getProteumManifestPath(appRoot);
127
+
128
+ if (!fs.existsSync(filepath)) {
129
+ throw new Error(`Proteum manifest not found at ${filepath}. Run a Proteum command that refreshes generated artifacts first.`);
130
+ }
131
+
132
+ return fs.readJsonSync(filepath) as TProteumManifest;
133
+ };