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.
- package/AGENTS.md +22 -14
- package/README.md +109 -17
- package/agents/project/AGENTS.md +188 -25
- package/agents/project/CODING_STYLE.md +1 -0
- package/agents/project/client/AGENTS.md +13 -8
- package/agents/project/client/pages/AGENTS.md +17 -9
- package/agents/project/diagnostics.md +52 -0
- package/agents/project/optimizations.md +48 -0
- package/agents/project/server/routes/AGENTS.md +9 -6
- package/agents/project/server/services/AGENTS.md +10 -6
- package/agents/project/tests/AGENTS.md +11 -5
- package/cli/app/config.ts +13 -14
- package/cli/app/index.ts +58 -0
- package/cli/commands/connect.ts +45 -0
- package/cli/commands/dev.ts +37 -13
- package/cli/commands/diagnose.ts +286 -0
- package/cli/commands/doctor.ts +18 -5
- package/cli/commands/explain.ts +25 -0
- package/cli/commands/perf.ts +243 -0
- package/cli/commands/trace.ts +9 -1
- package/cli/commands/verify.ts +281 -0
- package/cli/compiler/artifacts/connectedProjects.ts +453 -0
- package/cli/compiler/artifacts/controllers.ts +198 -49
- package/cli/compiler/artifacts/discovery.ts +0 -34
- package/cli/compiler/artifacts/manifest.ts +95 -6
- package/cli/compiler/artifacts/routing.ts +2 -2
- package/cli/compiler/artifacts/services.ts +277 -130
- package/cli/compiler/client/index.ts +3 -0
- package/cli/compiler/common/files/style.ts +52 -0
- package/cli/compiler/common/generatedRouteModules.ts +34 -5
- package/cli/compiler/common/scripts.ts +11 -5
- package/cli/compiler/index.ts +2 -1
- package/cli/compiler/server/index.ts +3 -0
- package/cli/presentation/commands.ts +110 -7
- package/cli/presentation/devSession.ts +32 -7
- package/cli/runtime/commands.ts +165 -6
- package/cli/scaffold/index.ts +18 -27
- package/cli/scaffold/templates.ts +48 -28
- package/cli/utils/agents.ts +106 -13
- package/cli/utils/keyboard.ts +8 -0
- package/client/dev/profiler/ApexChart.tsx +66 -0
- package/client/dev/profiler/index.tsx +2508 -302
- package/client/dev/profiler/runtime.noop.ts +12 -0
- package/client/dev/profiler/runtime.ts +195 -4
- package/client/services/router/request/api.ts +6 -1
- package/common/applicationConfig.ts +173 -0
- package/common/applicationConfigLoader.ts +102 -0
- package/common/connectedProjects.ts +113 -0
- package/common/dev/connect.ts +267 -0
- package/common/dev/console.ts +31 -0
- package/common/dev/contractsDoctor.ts +128 -0
- package/common/dev/diagnostics.ts +59 -15
- package/common/dev/inspection.ts +491 -0
- package/common/dev/performance.ts +809 -0
- package/common/dev/profiler.ts +3 -0
- package/common/dev/proteumManifest.ts +31 -6
- package/common/dev/requestTrace.ts +52 -1
- package/common/env/proteumEnv.ts +176 -50
- package/common/router/index.ts +1 -0
- package/common/router/request/api.ts +2 -0
- package/config.ts +5 -0
- package/docs/dev-commands.md +5 -1
- package/docs/dev-sessions.md +90 -0
- package/docs/diagnostics.md +74 -11
- package/docs/request-tracing.md +50 -3
- package/package.json +1 -1
- package/server/app/container/config.ts +16 -87
- package/server/app/container/console/index.ts +42 -8
- package/server/app/container/index.ts +10 -2
- package/server/app/container/trace/index.ts +105 -0
- package/server/app/devDiagnostics.ts +138 -0
- package/server/app/index.ts +18 -8
- package/server/app/service/container.ts +0 -12
- package/server/app/service/index.ts +0 -2
- package/server/services/prisma/index.ts +121 -4
- package/server/services/router/http/index.ts +305 -11
- package/server/services/router/index.ts +116 -57
- package/server/services/router/request/api.ts +160 -19
- package/server/services/router/request/index.ts +8 -0
- package/server/services/router/response/index.ts +23 -1
- package/server/services/router/response/page/document.tsx +31 -14
- package/server/services/router/response/page/index.tsx +10 -0
- package/agents/framework/AGENTS.md +0 -177
- package/server/services/auth/router/service.json +0 -6
- package/server/services/auth/service.json +0 -6
- package/server/services/cron/service.json +0 -6
- package/server/services/disks/drivers/local/service.json +0 -6
- package/server/services/disks/drivers/s3/service.json +0 -6
- package/server/services/disks/service.json +0 -6
- package/server/services/fetch/service.json +0 -7
- package/server/services/prisma/service.json +0 -6
- package/server/services/router/service.json +0 -6
- package/server/services/schema/router/service.json +0 -6
- package/server/services/schema/service.json +0 -6
- 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 {
|
|
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
|
-
|
|
14
|
-
const
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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 =
|
|
55
|
-
|
|
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.
|
|
59
|
-
: `() => api.createFetcher<${resultType}>('POST', ${JSON.stringify(meta.
|
|
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 =
|
|
68
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
112
|
+
clientRoute: { chunkId: pageChunk.chunkId },
|
|
113
113
|
routeSourceFilepaths,
|
|
114
114
|
});
|
|
115
115
|
}
|