proteum 2.1.3-1 → 2.1.6
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 +26 -11
- 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 +90 -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 +14 -25
- package/cli/scaffold/templates.ts +41 -27
- package/cli/utils/agents.ts +4 -2
- 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 +3 -1
- 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 +266 -0
- package/server/services/router/index.ts +50 -47
- 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 +5 -0
- 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,14 @@
|
|
|
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';
|
|
9
8
|
import {
|
|
10
9
|
TProteumManifest,
|
|
11
10
|
TProteumManifestCommand,
|
|
11
|
+
TProteumManifestConnectedProject,
|
|
12
12
|
TProteumManifestController,
|
|
13
13
|
TProteumManifestDiagnostic,
|
|
14
14
|
TProteumManifestLayout,
|
|
@@ -16,6 +16,18 @@ import {
|
|
|
16
16
|
} from '../common/proteumManifest';
|
|
17
17
|
import { writeProteumManifest } from '../common/proteumManifest';
|
|
18
18
|
import { normalizeAbsolutePath, normalizePath } from './shared';
|
|
19
|
+
import type { TResolvedConnectedProjectContract } from './connectedProjects';
|
|
20
|
+
|
|
21
|
+
const requiredGitignoreEntries = [
|
|
22
|
+
'/.proteum',
|
|
23
|
+
'/bin',
|
|
24
|
+
'/dev',
|
|
25
|
+
'/.cache',
|
|
26
|
+
'/var',
|
|
27
|
+
'/proteum.connected.json',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
const normalizeGitignoreEntry = (value: string) => value.trim().replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+$/, '');
|
|
19
31
|
|
|
20
32
|
const collectManifestDiagnostics = ({
|
|
21
33
|
commands,
|
|
@@ -206,6 +218,37 @@ const collectManifestDiagnostics = ({
|
|
|
206
218
|
});
|
|
207
219
|
}
|
|
208
220
|
|
|
221
|
+
const gitignoreFilepath = path.join(app.paths.root, '.gitignore');
|
|
222
|
+
|
|
223
|
+
if (!fs.existsSync(gitignoreFilepath)) {
|
|
224
|
+
pushDiagnostic({
|
|
225
|
+
level: 'warning',
|
|
226
|
+
code: 'app.gitignore-missing',
|
|
227
|
+
message: `Missing .gitignore. Proteum generated output should ignore ${requiredGitignoreEntries.join(', ')}.`,
|
|
228
|
+
filepath: gitignoreFilepath,
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
const entries = new Set(
|
|
232
|
+
fs.readFileSync(gitignoreFilepath, 'utf8')
|
|
233
|
+
.split(/\r?\n/)
|
|
234
|
+
.map((line) => line.replace(/#.*/, '').trim())
|
|
235
|
+
.filter(Boolean)
|
|
236
|
+
.map(normalizeGitignoreEntry),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
for (const requiredEntry of requiredGitignoreEntries) {
|
|
240
|
+
const normalizedRequiredEntry = normalizeGitignoreEntry(requiredEntry);
|
|
241
|
+
if (entries.has(normalizedRequiredEntry)) continue;
|
|
242
|
+
|
|
243
|
+
pushDiagnostic({
|
|
244
|
+
level: 'warning',
|
|
245
|
+
code: 'app.gitignore-generated-entry-missing',
|
|
246
|
+
message: `Add "${requiredEntry}" to .gitignore so Proteum generated output stays untracked.`,
|
|
247
|
+
filepath: gitignoreFilepath,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
209
252
|
return diagnostics.sort((a, b) => {
|
|
210
253
|
if (a.level !== b.level) return a.level === 'error' ? -1 : 1;
|
|
211
254
|
if (a.filepath !== b.filepath) return a.filepath.localeCompare(b.filepath);
|
|
@@ -218,25 +261,49 @@ const collectManifestDiagnostics = ({
|
|
|
218
261
|
|
|
219
262
|
export const writeCurrentProteumManifest = ({
|
|
220
263
|
services,
|
|
264
|
+
connectedProjects: resolvedConnectedProjects,
|
|
221
265
|
controllers,
|
|
222
266
|
commands,
|
|
223
267
|
routes,
|
|
224
268
|
layouts,
|
|
225
269
|
}: {
|
|
226
270
|
services: TProteumManifest['services'];
|
|
271
|
+
connectedProjects: TResolvedConnectedProjectContract[];
|
|
227
272
|
controllers: TProteumManifestController[];
|
|
228
273
|
commands: TProteumManifestCommand[];
|
|
229
274
|
routes: TProteumManifest['routes'];
|
|
230
275
|
layouts: TProteumManifestLayout[];
|
|
231
276
|
}) => {
|
|
232
|
-
const envInspection = inspectProteumEnv(app.paths.root);
|
|
277
|
+
const envInspection = inspectProteumEnv(app.paths.root, app.connectedProjects);
|
|
278
|
+
const connectedProjects: TProteumManifestConnectedProject[] = Object.entries(app.connectedProjects)
|
|
279
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
280
|
+
.map(([namespace, config]) => {
|
|
281
|
+
const connectedControllers = controllers.filter((controller) => controller.connectedProjectNamespace === namespace);
|
|
282
|
+
const connectedEnv = app.env.connectedProjects[namespace];
|
|
283
|
+
const resolvedConnectedProject = resolvedConnectedProjects.find((connectedProject) => connectedProject.namespace === namespace);
|
|
284
|
+
const contract = resolvedConnectedProject?.contract;
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
namespace,
|
|
288
|
+
packageName: contract?.packageName,
|
|
289
|
+
identityIdentifier: contract?.identity.identifier || connectedControllers[0]?.connectedProjectIdentifier,
|
|
290
|
+
identityName: contract?.identity.name,
|
|
291
|
+
sourceKind: resolvedConnectedProject?.sourceKind,
|
|
292
|
+
sourceValue: resolvedConnectedProject?.sourceValue || config.source,
|
|
293
|
+
cachedContractFilepath: resolvedConnectedProject?.cachedContractFilepath,
|
|
294
|
+
typingMode: resolvedConnectedProject?.typingMode,
|
|
295
|
+
urlInternal: connectedEnv?.urlInternal || config.urlInternal,
|
|
296
|
+
controllerCount: connectedControllers.length,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
233
299
|
|
|
234
300
|
const manifest: TProteumManifest = {
|
|
235
|
-
version:
|
|
301
|
+
version: 9,
|
|
236
302
|
app: {
|
|
237
303
|
root: normalizeAbsolutePath(app.paths.root),
|
|
238
304
|
coreRoot: normalizeAbsolutePath(cli.paths.core.root),
|
|
239
|
-
identityFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'identity.
|
|
305
|
+
identityFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'identity.config.ts')),
|
|
306
|
+
setupFilepath: normalizeAbsolutePath(path.join(app.paths.root, 'proteum.config.ts')),
|
|
240
307
|
identity: {
|
|
241
308
|
name: app.identity.name,
|
|
242
309
|
identifier: app.identity.identifier,
|
|
@@ -249,6 +316,21 @@ export const writeCurrentProteumManifest = ({
|
|
|
249
316
|
webDescription: app.identity.web?.description,
|
|
250
317
|
version: app.identity.web?.version,
|
|
251
318
|
},
|
|
319
|
+
setup: {
|
|
320
|
+
transpile: app.transpile.length > 0 ? [...app.transpile] : undefined,
|
|
321
|
+
connect:
|
|
322
|
+
Object.keys(app.connectedProjects).length > 0
|
|
323
|
+
? Object.fromEntries(
|
|
324
|
+
Object.entries(app.connectedProjects).map(([namespace, config]) => [
|
|
325
|
+
namespace,
|
|
326
|
+
{
|
|
327
|
+
...(config.source ? { source: config.source } : {}),
|
|
328
|
+
...(config.urlInternal ? { urlInternal: config.urlInternal } : {}),
|
|
329
|
+
},
|
|
330
|
+
]),
|
|
331
|
+
)
|
|
332
|
+
: undefined,
|
|
333
|
+
},
|
|
252
334
|
},
|
|
253
335
|
conventions: {
|
|
254
336
|
routeSetupOptionKeys: [...routeSetupOptionKeys],
|
|
@@ -267,8 +349,10 @@ export const writeCurrentProteumManifest = ({
|
|
|
267
349
|
profile: app.env.profile,
|
|
268
350
|
routerPort: app.env.router.port,
|
|
269
351
|
routerCurrentDomain: app.env.router.currentDomain,
|
|
352
|
+
routerInternalUrl: app.env.router.internalUrl,
|
|
270
353
|
},
|
|
271
354
|
},
|
|
355
|
+
connectedProjects,
|
|
272
356
|
services,
|
|
273
357
|
controllers,
|
|
274
358
|
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
|
}
|