proteum 2.1.3-1 → 2.1.7

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 (95) hide show
  1. package/AGENTS.md +22 -14
  2. package/README.md +109 -17
  3. package/agents/project/AGENTS.md +188 -25
  4. package/agents/project/CODING_STYLE.md +1 -0
  5. package/agents/project/client/AGENTS.md +13 -8
  6. package/agents/project/client/pages/AGENTS.md +17 -9
  7. package/agents/project/diagnostics.md +52 -0
  8. package/agents/project/optimizations.md +48 -0
  9. package/agents/project/server/routes/AGENTS.md +9 -6
  10. package/agents/project/server/services/AGENTS.md +10 -6
  11. package/agents/project/tests/AGENTS.md +11 -5
  12. package/cli/app/config.ts +13 -14
  13. package/cli/app/index.ts +58 -0
  14. package/cli/commands/connect.ts +45 -0
  15. package/cli/commands/dev.ts +37 -13
  16. package/cli/commands/diagnose.ts +286 -0
  17. package/cli/commands/doctor.ts +18 -5
  18. package/cli/commands/explain.ts +25 -0
  19. package/cli/commands/perf.ts +243 -0
  20. package/cli/commands/trace.ts +9 -1
  21. package/cli/commands/verify.ts +281 -0
  22. package/cli/compiler/artifacts/connectedProjects.ts +453 -0
  23. package/cli/compiler/artifacts/controllers.ts +198 -49
  24. package/cli/compiler/artifacts/discovery.ts +0 -34
  25. package/cli/compiler/artifacts/manifest.ts +95 -6
  26. package/cli/compiler/artifacts/routing.ts +2 -2
  27. package/cli/compiler/artifacts/services.ts +277 -130
  28. package/cli/compiler/client/index.ts +3 -0
  29. package/cli/compiler/common/files/style.ts +52 -0
  30. package/cli/compiler/common/generatedRouteModules.ts +34 -5
  31. package/cli/compiler/common/scripts.ts +11 -5
  32. package/cli/compiler/index.ts +2 -1
  33. package/cli/compiler/server/index.ts +3 -0
  34. package/cli/presentation/commands.ts +110 -7
  35. package/cli/presentation/devSession.ts +32 -7
  36. package/cli/runtime/commands.ts +165 -6
  37. package/cli/scaffold/index.ts +18 -27
  38. package/cli/scaffold/templates.ts +48 -28
  39. package/cli/utils/agents.ts +106 -13
  40. package/cli/utils/keyboard.ts +8 -0
  41. package/client/dev/profiler/ApexChart.tsx +66 -0
  42. package/client/dev/profiler/index.tsx +2508 -302
  43. package/client/dev/profiler/runtime.noop.ts +12 -0
  44. package/client/dev/profiler/runtime.ts +195 -4
  45. package/client/services/router/request/api.ts +6 -1
  46. package/common/applicationConfig.ts +173 -0
  47. package/common/applicationConfigLoader.ts +102 -0
  48. package/common/connectedProjects.ts +113 -0
  49. package/common/dev/connect.ts +267 -0
  50. package/common/dev/console.ts +31 -0
  51. package/common/dev/contractsDoctor.ts +128 -0
  52. package/common/dev/diagnostics.ts +59 -15
  53. package/common/dev/inspection.ts +491 -0
  54. package/common/dev/performance.ts +809 -0
  55. package/common/dev/profiler.ts +3 -0
  56. package/common/dev/proteumManifest.ts +31 -6
  57. package/common/dev/requestTrace.ts +52 -1
  58. package/common/env/proteumEnv.ts +176 -50
  59. package/common/router/index.ts +1 -0
  60. package/common/router/request/api.ts +2 -0
  61. package/config.ts +5 -0
  62. package/docs/dev-commands.md +5 -1
  63. package/docs/dev-sessions.md +90 -0
  64. package/docs/diagnostics.md +74 -11
  65. package/docs/request-tracing.md +50 -3
  66. package/package.json +1 -1
  67. package/server/app/container/config.ts +16 -87
  68. package/server/app/container/console/index.ts +42 -8
  69. package/server/app/container/index.ts +10 -2
  70. package/server/app/container/trace/index.ts +105 -0
  71. package/server/app/devDiagnostics.ts +138 -0
  72. package/server/app/index.ts +18 -8
  73. package/server/app/service/container.ts +0 -12
  74. package/server/app/service/index.ts +0 -2
  75. package/server/services/prisma/index.ts +121 -4
  76. package/server/services/router/http/index.ts +305 -11
  77. package/server/services/router/index.ts +116 -57
  78. package/server/services/router/request/api.ts +160 -19
  79. package/server/services/router/request/index.ts +8 -0
  80. package/server/services/router/response/index.ts +23 -1
  81. package/server/services/router/response/page/document.tsx +31 -14
  82. package/server/services/router/response/page/index.tsx +10 -0
  83. package/agents/framework/AGENTS.md +0 -177
  84. package/server/services/auth/router/service.json +0 -6
  85. package/server/services/auth/service.json +0 -6
  86. package/server/services/cron/service.json +0 -6
  87. package/server/services/disks/drivers/local/service.json +0 -6
  88. package/server/services/disks/drivers/s3/service.json +0 -6
  89. package/server/services/disks/service.json +0 -6
  90. package/server/services/fetch/service.json +0 -7
  91. package/server/services/prisma/service.json +0 -6
  92. package/server/services/router/service.json +0 -6
  93. package/server/services/schema/router/service.json +0 -6
  94. package/server/services/schema/service.json +0 -6
  95. package/server/services/security/encrypt/aes/service.json +0 -6
@@ -2,70 +2,212 @@ import path from 'path';
2
2
 
3
3
  import app from '../../app';
4
4
  import cli from '../..';
5
- import { generateControllerClientTree, indexControllers, printControllerTree } from '../common/controllers';
5
+ import { indexControllers, printControllerTree, type TControllerFileMeta } from '../common/controllers';
6
6
  import { TProteumManifestController } from '../common/proteumManifest';
7
7
  import writeIfChanged from '../writeIfChanged';
8
+ import { resolveConnectedProjectContracts, writeConnectedProjectContract } from './connectedProjects';
8
9
  import { normalizeAbsolutePath } from './shared';
9
10
 
11
+ const reservedConnectedContextKeys = new Set(['app', 'context', 'request', 'response', 'route', 'api', 'Router']);
12
+
10
13
  const getManifestScopeFromImportPath = (importPath: string) =>
11
14
  importPath.startsWith('@server/controllers/') ? 'framework' : 'app';
12
15
 
13
- export const generateControllerArtifacts = () => {
14
- const controllers = indexControllers([
16
+ const insertTreeLeaf = (tree: Record<string, any>, accessor: string, value: string) => {
17
+ const segments = accessor.split('.').filter(Boolean);
18
+ let cursor = tree;
19
+
20
+ for (let index = 0; index < segments.length; index += 1) {
21
+ const segment = segments[index];
22
+ const isLeaf = index === segments.length - 1;
23
+
24
+ if (isLeaf) {
25
+ cursor[segment] = value;
26
+ return;
27
+ }
28
+
29
+ cursor[segment] = cursor[segment] || {};
30
+ cursor = cursor[segment];
31
+ }
32
+ };
33
+
34
+ const buildLocalControllers = (): TControllerFileMeta[] =>
35
+ indexControllers([
15
36
  { importPrefix: '@server/controllers/', root: path.join(cli.paths.core.root, 'server', 'controllers') },
16
37
  { importPrefix: '@/server/controllers/', root: path.join(app.paths.root, 'server', 'controllers') },
17
38
  ]);
18
- const manifestControllers = controllers.flatMap<TProteumManifestController>((controller) =>
19
- controller.methods.map((method) => ({
20
- className: controller.className,
21
- importPath: controller.importPath,
22
- filepath: normalizeAbsolutePath(controller.filepath),
23
- sourceLocation: method.sourceLocation,
24
- routeBasePath: controller.routeBasePath,
25
- methodName: method.name,
26
- inputCallsCount: method.inputCallsCount,
27
- hasInput: method.inputCallsCount > 0,
28
- routePath: method.routePath,
29
- httpPath: '/api/' + method.routePath,
30
- clientAccessor: method.routePath.split('/').join('.'),
31
- scope: getManifestScopeFromImportPath(controller.importPath),
32
- })),
33
- );
34
- const clientTree = generateControllerClientTree(controllers);
35
39
 
36
- const getControllerLeafMeta = (leaf: string) => {
37
- const meta = JSON.parse(leaf) as {
38
- routePath: string;
39
- importPath: string;
40
- className: string;
41
- methodName: string;
42
- hasInput: boolean;
43
- };
44
- const controllerIndex = controllers.findIndex((controller) => controller.importPath === meta.importPath);
40
+ const assertConnectedProjectNamespaces = (localControllers: TControllerFileMeta[]) => {
41
+ const localTopLevelKeys = new Set<string>();
45
42
 
46
- if (controllerIndex === -1) {
47
- throw new Error(`Unable to find controller import ${meta.importPath} while generating controller types.`);
43
+ for (const controller of localControllers) {
44
+ for (const method of controller.methods) {
45
+ const topLevelKey = method.routePath.split('/')[0];
46
+ if (topLevelKey) localTopLevelKeys.add(topLevelKey);
48
47
  }
48
+ }
49
49
 
50
- return { ...meta, controllerIndex };
51
- };
50
+ for (const namespace of Object.keys(app.connectedProjects)) {
51
+ if (reservedConnectedContextKeys.has(namespace)) {
52
+ throw new Error(`Connected project namespace "${namespace}" collides with a reserved route context key.`);
53
+ }
54
+
55
+ if (localTopLevelKeys.has(namespace)) {
56
+ throw new Error(`Connected project namespace "${namespace}" collides with an existing local controller root.`);
57
+ }
58
+ }
59
+ };
60
+
61
+ export const generateControllerArtifacts = async () => {
62
+ const localControllers = buildLocalControllers();
63
+ assertConnectedProjectNamespaces(localControllers);
64
+ writeConnectedProjectContract(localControllers);
65
+
66
+ const connectedProjectContracts = await resolveConnectedProjectContracts(app.connectedProjects);
67
+ const manifestControllers: TProteumManifestController[] = [];
68
+ const runtimeTree: Record<string, any> = {};
69
+ const typeTree: Record<string, any> = {};
70
+ const typeImports: string[] = [];
71
+
72
+ localControllers.forEach((controller, index) => {
73
+ typeImports.push(`import type Controller${index} from ${JSON.stringify(controller.importPath)};`);
74
+
75
+ controller.methods.forEach((method) => {
76
+ const resultType = `TControllerResult<Controller${index}, ${JSON.stringify(method.name)}>`;
77
+ const clientAccessor = method.routePath.split('/').join('.');
78
+
79
+ manifestControllers.push({
80
+ className: controller.className,
81
+ importPath: controller.importPath,
82
+ filepath: normalizeAbsolutePath(controller.filepath),
83
+ sourceLocation: method.sourceLocation,
84
+ routeBasePath: controller.routeBasePath,
85
+ methodName: method.name,
86
+ inputCallsCount: method.inputCallsCount,
87
+ hasInput: method.inputCallsCount > 0,
88
+ routePath: method.routePath,
89
+ httpPath: '/api/' + method.routePath,
90
+ clientAccessor,
91
+ scope: getManifestScopeFromImportPath(controller.importPath),
92
+ });
93
+
94
+ insertTreeLeaf(
95
+ runtimeTree,
96
+ clientAccessor,
97
+ JSON.stringify({
98
+ connected: undefined,
99
+ hasInput: method.inputCallsCount > 0,
100
+ httpPath: '/api/' + method.routePath,
101
+ methodName: method.name,
102
+ resultType,
103
+ typeName: `Controller${index}`,
104
+ }),
105
+ );
106
+
107
+ insertTreeLeaf(
108
+ typeTree,
109
+ clientAccessor,
110
+ JSON.stringify({
111
+ hasInput: method.inputCallsCount > 0,
112
+ methodName: method.name,
113
+ typeName: `Controller${index}`,
114
+ }),
115
+ );
116
+ });
117
+ });
118
+
119
+ const connectedControllerTypeImports: string[] = [];
120
+ const connectedManifestControllers: TProteumManifestController[] = [];
121
+
122
+ connectedProjectContracts.forEach(({ namespace, cachedContractFilepath, contract, sourceKind, sourceValue, typeImportModuleSpecifier, typingMode }) => {
123
+ if (typingMode === 'local-typed' && typeImportModuleSpecifier) {
124
+ const typeName = `ConnectedControllers_${namespace.replace(/[^A-Za-z0-9_$]+/g, '_')}`;
125
+ connectedControllerTypeImports.push(
126
+ `import type { TConnectedControllers as ${typeName} } from ${JSON.stringify(typeImportModuleSpecifier)};`,
127
+ );
128
+ typeTree[namespace] = JSON.stringify({ rawType: typeName });
129
+ } else {
130
+ typeTree[namespace] = JSON.stringify({ runtimeOnly: true });
131
+ }
132
+
133
+ contract.controllers.forEach((controller) => {
134
+ const clientAccessor = `${namespace}.${controller.clientAccessor}`;
135
+ connectedManifestControllers.push({
136
+ className: controller.className,
137
+ importPath: `connected:${namespace}/${controller.importPath}`,
138
+ filepath:
139
+ sourceKind === 'file'
140
+ ? normalizeAbsolutePath(path.join(sourceValue, controller.relativeFilepath))
141
+ : cachedContractFilepath,
142
+ sourceLocation: controller.sourceLocation,
143
+ routeBasePath: controller.routeBasePath,
144
+ methodName: controller.methodName,
145
+ inputCallsCount: controller.inputCallsCount,
146
+ hasInput: controller.hasInput,
147
+ routePath: controller.routePath,
148
+ httpPath: controller.httpPath,
149
+ clientAccessor,
150
+ scope: 'connected',
151
+ connectedProjectNamespace: namespace,
152
+ connectedProjectIdentifier: contract.identity.identifier,
153
+ });
154
+
155
+ insertTreeLeaf(
156
+ runtimeTree,
157
+ clientAccessor,
158
+ JSON.stringify({
159
+ connected: {
160
+ controllerAccessor: controller.clientAccessor,
161
+ httpPath: controller.httpPath,
162
+ namespace,
163
+ },
164
+ hasInput: controller.hasInput,
165
+ httpPath: controller.httpPath,
166
+ methodName: controller.methodName,
167
+ resultType: 'unknown',
168
+ }),
169
+ );
170
+ });
171
+ });
52
172
 
53
173
  const runtimeLeaf = (leaf: string) => {
54
- const meta = getControllerLeafMeta(leaf);
55
- const resultType = `TControllerResult<Controller${meta.controllerIndex}, ${JSON.stringify(meta.methodName)}>`;
174
+ const meta = JSON.parse(leaf) as {
175
+ connected?: {
176
+ controllerAccessor: string;
177
+ httpPath: string;
178
+ namespace: string;
179
+ };
180
+ hasInput: boolean;
181
+ httpPath: string;
182
+ resultType: string;
183
+ };
184
+
185
+ const connectedOptions = meta.connected
186
+ ? `, { connected: ${JSON.stringify(meta.connected)} }`
187
+ : '';
56
188
 
57
189
  return meta.hasInput
58
- ? `(data) => api.createFetcher<${resultType}>('POST', ${JSON.stringify(meta.routePath)}, data)`
59
- : `() => api.createFetcher<${resultType}>('POST', ${JSON.stringify(meta.routePath)})`;
190
+ ? `(data) => api.createFetcher<${meta.resultType}>('POST', ${JSON.stringify(meta.httpPath)}, data${connectedOptions})`
191
+ : `() => api.createFetcher<${meta.resultType}>('POST', ${JSON.stringify(meta.httpPath)}, undefined${connectedOptions})`;
60
192
  };
61
193
 
62
- const typeImports = controllers
63
- .map((controller, index) => `import type Controller${index} from ${JSON.stringify(controller.importPath)};`)
64
- .join('\n');
65
-
66
194
  const typeLeaf = (leaf: string) => {
67
- const meta = getControllerLeafMeta(leaf);
68
- const fetcherType = `TControllerFetcher<Controller${meta.controllerIndex}, ${JSON.stringify(meta.methodName)}>`;
195
+ const meta = JSON.parse(leaf) as
196
+ | {
197
+ rawType: string;
198
+ }
199
+ | {
200
+ runtimeOnly: true;
201
+ }
202
+ | {
203
+ hasInput: boolean;
204
+ methodName: string;
205
+ typeName: string;
206
+ };
207
+
208
+ if ('rawType' in meta) return meta.rawType;
209
+ if ('runtimeOnly' in meta) return 'any';
210
+ const fetcherType = `TControllerFetcher<${meta.typeName}, ${JSON.stringify(meta.methodName)}>`;
69
211
 
70
212
  return meta.hasInput ? `(data: any) => ${fetcherType}` : `() => ${fetcherType}`;
71
213
  };
@@ -79,19 +221,19 @@ export const generateControllerArtifacts = () => {
79
221
 
80
222
  import type ApiClient from '@common/router/request/api';
81
223
  import type { TFetcher } from '@common/router/request/api';
82
- ${typeImports ? '\n' + typeImports : ''}
224
+ ${[...typeImports, ...connectedControllerTypeImports].join('\n') ? '\n' + [...typeImports, ...connectedControllerTypeImports].join('\n') : ''}
83
225
 
84
226
  type TControllerResult<TController, TMethod extends keyof TController> =
85
227
  TController[TMethod] extends (...args: any[]) => infer TResult ? Awaited<TResult> : never;
86
228
 
87
229
  type TControllerFetcher<TController, TMethod extends keyof TController> = TFetcher<TControllerResult<TController, TMethod>>;
88
230
 
89
- export type TControllers = ${printControllerTree(clientTree, typeLeaf)};
231
+ export type TControllers = ${printControllerTree(typeTree, typeLeaf)};
90
232
 
91
233
  export const createControllers = (
92
234
  api: Pick<ApiClient, 'createFetcher'>
93
235
  ): TControllers => (
94
- ${printControllerTree(clientTree, runtimeLeaf)}
236
+ ${printControllerTree(runtimeTree, runtimeLeaf)}
95
237
  );
96
238
 
97
239
  export default createControllers;
@@ -106,14 +248,16 @@ export type { TControllers } from '@generated/common/controllers';
106
248
  `,
107
249
  );
108
250
 
109
- const controllerImports = controllers
251
+ const controllerImports = localControllers
110
252
  .map((controller, index) => `import Controller${index} from ${JSON.stringify(controller.importPath)};`)
111
253
  .join('\n');
112
254
 
113
- const controllerEntries = controllers.flatMap((controller, controllerIndex) =>
255
+ const controllerEntries = localControllers.flatMap((controller, controllerIndex) =>
114
256
  controller.methods.map(
115
257
  (method) => ` {
116
258
  path: ${JSON.stringify('/api/' + method.routePath)},
259
+ filepath: ${JSON.stringify(normalizeAbsolutePath(controller.filepath))},
260
+ sourceLocation: { line: ${method.sourceLocation.line}, column: ${method.sourceLocation.column} },
117
261
  Controller: Controller${controllerIndex},
118
262
  method: ${JSON.stringify(method.name)},
119
263
  },`,
@@ -134,6 +278,8 @@ ${controllerImports ? '\n' + controllerImports : ''}
134
278
 
135
279
  export type TGeneratedControllerDefinition = {
136
280
  path: string,
281
+ filepath: string,
282
+ sourceLocation: { line: number, column: number },
137
283
  Controller: new (request: any) => Controller,
138
284
  method: string,
139
285
  }
@@ -146,5 +292,8 @@ export default controllers;
146
292
  `,
147
293
  );
148
294
 
149
- return manifestControllers;
295
+ return {
296
+ connectedProjects: connectedProjectContracts,
297
+ controllers: [...manifestControllers, ...connectedManifestControllers],
298
+ };
150
299
  };
@@ -5,40 +5,6 @@ import ts from 'typescript';
5
5
  import app from '../../app';
6
6
  import { normalizePath } from './shared';
7
7
 
8
- const ignoredServiceDirectories = new Set(['node_modules', 'proteum']);
9
-
10
- export const findServiceDirectories = (dir: string): string[] => {
11
- if (!fs.existsSync(dir)) return [];
12
-
13
- const directories: string[] = [];
14
-
15
- for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
16
- const filePath = path.resolve(dir, dirent.name);
17
-
18
- if (ignoredServiceDirectories.has(dirent.name)) continue;
19
-
20
- let shouldTraverse = false;
21
-
22
- if (dirent.isSymbolicLink()) {
23
- const realPath = path.resolve(dir, fs.readlinkSync(filePath));
24
- shouldTraverse = fs.lstatSync(realPath).isDirectory();
25
- } else if (dirent.isDirectory()) {
26
- shouldTraverse = true;
27
- }
28
-
29
- if (shouldTraverse) {
30
- directories.push(...findServiceDirectories(filePath));
31
- continue;
32
- }
33
-
34
- if (dirent.name === 'service.json') {
35
- directories.push(path.dirname(filePath));
36
- }
37
- }
38
-
39
- return directories;
40
- };
41
-
42
8
  const hasRegisteredRouteDefinitions = (filepath: string, content: string) => {
43
9
  const sourceFile = ts.createSourceFile(
44
10
  filepath,
@@ -1,14 +1,15 @@
1
+ import fs from 'fs';
1
2
  import path from 'path';
2
3
 
3
4
  import app from '../../app';
4
5
  import cli from '../..';
5
- import {
6
- inspectProteumEnv,
7
- } from '../../../common/env/proteumEnv';
6
+ import { inspectProteumEnv } from '../../../common/env/proteumEnv';
8
7
  import { reservedRouteSetupKeys, routeSetupOptionKeys } from '../../../common/router/pageSetup';
8
+ import { getProjectInstructionGitignoreEntries } from '../../utils/agents';
9
9
  import {
10
10
  TProteumManifest,
11
11
  TProteumManifestCommand,
12
+ TProteumManifestConnectedProject,
12
13
  TProteumManifestController,
13
14
  TProteumManifestDiagnostic,
14
15
  TProteumManifestLayout,
@@ -16,6 +17,18 @@ import {
16
17
  } from '../common/proteumManifest';
17
18
  import { writeProteumManifest } from '../common/proteumManifest';
18
19
  import { normalizeAbsolutePath, normalizePath } from './shared';
20
+ import type { TResolvedConnectedProjectContract } from './connectedProjects';
21
+
22
+ const requiredGitignoreEntries = [
23
+ '/.proteum',
24
+ '/bin',
25
+ '/dev',
26
+ '/.cache',
27
+ '/var',
28
+ '/proteum.connected.json',
29
+ ] as const;
30
+
31
+ const normalizeGitignoreEntry = (value: string) => value.trim().replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+$/, '');
19
32
 
20
33
  const collectManifestDiagnostics = ({
21
34
  commands,
@@ -27,6 +40,10 @@ const collectManifestDiagnostics = ({
27
40
  routes: TProteumManifest['routes'];
28
41
  }) => {
29
42
  const diagnostics: TProteumManifestDiagnostic[] = [];
43
+ const expectedGitignoreEntries = [
44
+ ...requiredGitignoreEntries,
45
+ ...getProjectInstructionGitignoreEntries({ coreRoot: cli.paths.core.root }),
46
+ ];
30
47
 
31
48
  const pushDiagnostic = (diagnostic: TProteumManifestDiagnostic) => {
32
49
  diagnostics.push(diagnostic);
@@ -206,6 +223,37 @@ const collectManifestDiagnostics = ({
206
223
  });
207
224
  }
208
225
 
226
+ const gitignoreFilepath = path.join(app.paths.root, '.gitignore');
227
+
228
+ if (!fs.existsSync(gitignoreFilepath)) {
229
+ pushDiagnostic({
230
+ level: 'warning',
231
+ code: 'app.gitignore-missing',
232
+ message: `Missing .gitignore. Proteum-managed paths should ignore ${expectedGitignoreEntries.join(', ')}.`,
233
+ filepath: gitignoreFilepath,
234
+ });
235
+ } else {
236
+ const entries = new Set(
237
+ fs.readFileSync(gitignoreFilepath, 'utf8')
238
+ .split(/\r?\n/)
239
+ .map((line) => line.replace(/#.*/, '').trim())
240
+ .filter(Boolean)
241
+ .map(normalizeGitignoreEntry),
242
+ );
243
+
244
+ for (const requiredEntry of expectedGitignoreEntries) {
245
+ const normalizedRequiredEntry = normalizeGitignoreEntry(requiredEntry);
246
+ if (entries.has(normalizedRequiredEntry)) continue;
247
+
248
+ pushDiagnostic({
249
+ level: 'warning',
250
+ code: 'app.gitignore-generated-entry-missing',
251
+ message: `Add "${requiredEntry}" to .gitignore so Proteum-managed paths stay untracked.`,
252
+ filepath: gitignoreFilepath,
253
+ });
254
+ }
255
+ }
256
+
209
257
  return diagnostics.sort((a, b) => {
210
258
  if (a.level !== b.level) return a.level === 'error' ? -1 : 1;
211
259
  if (a.filepath !== b.filepath) return a.filepath.localeCompare(b.filepath);
@@ -218,25 +266,49 @@ const collectManifestDiagnostics = ({
218
266
 
219
267
  export const writeCurrentProteumManifest = ({
220
268
  services,
269
+ connectedProjects: resolvedConnectedProjects,
221
270
  controllers,
222
271
  commands,
223
272
  routes,
224
273
  layouts,
225
274
  }: {
226
275
  services: TProteumManifest['services'];
276
+ connectedProjects: TResolvedConnectedProjectContract[];
227
277
  controllers: TProteumManifestController[];
228
278
  commands: TProteumManifestCommand[];
229
279
  routes: TProteumManifest['routes'];
230
280
  layouts: TProteumManifestLayout[];
231
281
  }) => {
232
- const envInspection = inspectProteumEnv(app.paths.root);
282
+ const envInspection = inspectProteumEnv(app.paths.root, app.connectedProjects);
283
+ const connectedProjects: TProteumManifestConnectedProject[] = Object.entries(app.connectedProjects)
284
+ .sort(([left], [right]) => left.localeCompare(right))
285
+ .map(([namespace, config]) => {
286
+ const connectedControllers = controllers.filter((controller) => controller.connectedProjectNamespace === namespace);
287
+ const connectedEnv = app.env.connectedProjects[namespace];
288
+ const resolvedConnectedProject = resolvedConnectedProjects.find((connectedProject) => connectedProject.namespace === namespace);
289
+ const contract = resolvedConnectedProject?.contract;
290
+
291
+ return {
292
+ namespace,
293
+ packageName: contract?.packageName,
294
+ identityIdentifier: contract?.identity.identifier || connectedControllers[0]?.connectedProjectIdentifier,
295
+ identityName: contract?.identity.name,
296
+ sourceKind: resolvedConnectedProject?.sourceKind,
297
+ sourceValue: resolvedConnectedProject?.sourceValue || config.source,
298
+ cachedContractFilepath: resolvedConnectedProject?.cachedContractFilepath,
299
+ typingMode: resolvedConnectedProject?.typingMode,
300
+ urlInternal: connectedEnv?.urlInternal || config.urlInternal,
301
+ controllerCount: connectedControllers.length,
302
+ };
303
+ });
233
304
 
234
305
  const manifest: TProteumManifest = {
235
- version: 2,
306
+ version: 9,
236
307
  app: {
237
308
  root: normalizeAbsolutePath(app.paths.root),
238
309
  coreRoot: normalizeAbsolutePath(cli.paths.core.root),
239
- identityFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'identity.yaml')),
310
+ identityFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'identity.config.ts')),
311
+ setupFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'proteum.config.ts')),
240
312
  identity: {
241
313
  name: app.identity.name,
242
314
  identifier: app.identity.identifier,
@@ -249,6 +321,21 @@ export const writeCurrentProteumManifest = ({
249
321
  webDescription: app.identity.web?.description,
250
322
  version: app.identity.web?.version,
251
323
  },
324
+ setup: {
325
+ transpile: app.transpile.length > 0 ? [...app.transpile] : undefined,
326
+ connect:
327
+ Object.keys(app.connectedProjects).length > 0
328
+ ? Object.fromEntries(
329
+ Object.entries(app.connectedProjects).map(([namespace, config]) => [
330
+ namespace,
331
+ {
332
+ ...(config.source ? { source: config.source } : {}),
333
+ ...(config.urlInternal ? { urlInternal: config.urlInternal } : {}),
334
+ },
335
+ ]),
336
+ )
337
+ : undefined,
338
+ },
252
339
  },
253
340
  conventions: {
254
341
  routeSetupOptionKeys: [...routeSetupOptionKeys],
@@ -267,8 +354,10 @@ export const writeCurrentProteumManifest = ({
267
354
  profile: app.env.profile,
268
355
  routerPort: app.env.router.port,
269
356
  routerCurrentDomain: app.env.router.currentDomain,
357
+ routerInternalUrl: app.env.router.internalUrl,
270
358
  },
271
359
  },
360
+ connectedProjects,
272
361
  services,
273
362
  controllers,
274
363
  commands,
@@ -100,7 +100,7 @@ const generateClientRouteWrapperModules = () => {
100
100
  runtime: 'client',
101
101
  side: 'client',
102
102
  sourceFilepath: filepath,
103
- clientRoute: { chunkId: pageChunk.chunkId, filepath: pageChunk.filepath },
103
+ clientRoute: { chunkId: pageChunk.chunkId },
104
104
  routeSourceFilepaths,
105
105
  });
106
106
 
@@ -109,7 +109,7 @@ const generateClientRouteWrapperModules = () => {
109
109
  runtime: 'server',
110
110
  side: 'client',
111
111
  sourceFilepath: filepath,
112
- clientRoute: { chunkId: pageChunk.chunkId, filepath: pageChunk.filepath },
112
+ clientRoute: { chunkId: pageChunk.chunkId },
113
113
  routeSourceFilepaths,
114
114
  });
115
115
  }