proteum 2.1.2 → 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 +112 -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/command.ts +8 -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/session.ts +254 -0
- package/cli/commands/sessionLocalRunner.js +188 -0
- package/cli/commands/trace.ts +17 -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 +136 -7
- package/cli/presentation/devSession.ts +32 -7
- package/cli/runtime/commands.ts +193 -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 +2798 -417
- 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 +56 -1
- package/common/dev/session.ts +24 -0
- 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 +153 -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 +352 -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 +24 -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
|
@@ -6,25 +6,28 @@ import app from '../../app';
|
|
|
6
6
|
import cli from '../..';
|
|
7
7
|
import { TProteumManifestScope, TProteumManifestService } from '../common/proteumManifest';
|
|
8
8
|
import writeIfChanged from '../writeIfChanged';
|
|
9
|
-
import { findServiceDirectories } from './discovery';
|
|
10
9
|
import { normalizeAbsolutePath } from './shared';
|
|
11
10
|
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
type TImportedBinding =
|
|
12
|
+
| {
|
|
13
|
+
kind: 'default' | 'named';
|
|
14
|
+
importPath: string;
|
|
15
|
+
localName: string;
|
|
16
|
+
exportedName: string;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: 'namespace';
|
|
20
|
+
importPath: string;
|
|
21
|
+
localName: string;
|
|
22
|
+
};
|
|
23
23
|
|
|
24
24
|
type TParsedService = {
|
|
25
|
-
|
|
25
|
+
className: string;
|
|
26
|
+
importPath: string;
|
|
26
27
|
priority: number;
|
|
27
|
-
|
|
28
|
+
registeredName: string;
|
|
29
|
+
scope: TProteumManifestScope;
|
|
30
|
+
sourceFilepath?: string;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
type TParsedAppBootstrap = {
|
|
@@ -32,51 +35,28 @@ type TParsedAppBootstrap = {
|
|
|
32
35
|
routerPlugins: TParsedService[];
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
type TServicesAvailable = Record<string, TServiceMetas>;
|
|
36
38
|
type TCommandServiceStubSource = {
|
|
37
39
|
aliasImportPath: string;
|
|
38
40
|
filepath: string;
|
|
39
41
|
};
|
|
42
|
+
|
|
40
43
|
type TGeneratedCommandServiceStubs = {
|
|
41
44
|
declarations: string;
|
|
42
45
|
typeNamesByAliasImportPath: Map<string, string>;
|
|
43
46
|
};
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
];
|
|
48
|
+
type TResolvedImportSource = {
|
|
49
|
+
scope?: TProteumManifestScope;
|
|
50
|
+
sourceFilepath?: string;
|
|
51
|
+
};
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (const serviceDir of services) {
|
|
57
|
-
const metasFile = path.join(serviceDir, 'service.json');
|
|
58
|
-
const importationPath = searchDir.path + serviceDir.substring(searchDir.root.length + 1);
|
|
59
|
-
const serviceMetas = fs.readJsonSync(metasFile) as {
|
|
60
|
-
id: string;
|
|
61
|
-
name: string;
|
|
62
|
-
parent: string;
|
|
63
|
-
dependences: string[];
|
|
64
|
-
priority?: number;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
servicesAvailable[serviceMetas.id] = {
|
|
68
|
-
importationPath,
|
|
69
|
-
priority: searchDir.priority,
|
|
70
|
-
sourceDir: normalizeAbsolutePath(serviceDir),
|
|
71
|
-
metasFilepath: normalizeAbsolutePath(metasFile),
|
|
72
|
-
scope: searchDir.path.startsWith('@server/services/') ? 'framework' : 'app',
|
|
73
|
-
...serviceMetas,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
53
|
+
const coreServicesRoot = normalizeAbsolutePath(path.join(cli.paths.core.root, 'server', 'services'));
|
|
54
|
+
const appServicesRoot = normalizeAbsolutePath(path.join(app.paths.root, 'server', 'services'));
|
|
55
|
+
const coreServerRoot = normalizeAbsolutePath(path.join(cli.paths.core.root, 'server'));
|
|
56
|
+
const appServerRoot = normalizeAbsolutePath(path.join(app.paths.root, 'server'));
|
|
77
57
|
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
const moduleSourceCache = new Map<string, ts.SourceFile>();
|
|
59
|
+
const exportExpressionCache = new Map<string, ts.Expression | undefined>();
|
|
80
60
|
|
|
81
61
|
const getAppServerEntryFilepath = () => {
|
|
82
62
|
const filepath = app.paths.server.entry;
|
|
@@ -88,15 +68,23 @@ const getAppServerEntryFilepath = () => {
|
|
|
88
68
|
return filepath;
|
|
89
69
|
};
|
|
90
70
|
|
|
91
|
-
const createSourceFile = (filepath: string) =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
const createSourceFile = (filepath: string) => {
|
|
72
|
+
const normalizedFilepath = normalizeAbsolutePath(filepath);
|
|
73
|
+
const existing = moduleSourceCache.get(normalizedFilepath);
|
|
74
|
+
if (existing) return existing;
|
|
75
|
+
|
|
76
|
+
const sourceFile = ts.createSourceFile(
|
|
77
|
+
normalizedFilepath,
|
|
78
|
+
fs.readFileSync(normalizedFilepath, 'utf8'),
|
|
95
79
|
ts.ScriptTarget.Latest,
|
|
96
80
|
true,
|
|
97
|
-
|
|
81
|
+
normalizedFilepath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
|
98
82
|
);
|
|
99
83
|
|
|
84
|
+
moduleSourceCache.set(normalizedFilepath, sourceFile);
|
|
85
|
+
return sourceFile;
|
|
86
|
+
};
|
|
87
|
+
|
|
100
88
|
const hasModifier = (node: ts.Node, kind: ts.SyntaxKind) => {
|
|
101
89
|
const modifiers = (node as ts.Node & { modifiers?: ts.NodeArray<ts.Modifier> }).modifiers;
|
|
102
90
|
|
|
@@ -141,7 +129,7 @@ const getDefaultExportClassDeclaration = (sourceFile: ts.SourceFile) => {
|
|
|
141
129
|
};
|
|
142
130
|
|
|
143
131
|
const buildImportIndex = (sourceFile: ts.SourceFile) => {
|
|
144
|
-
const imports = new Map<string,
|
|
132
|
+
const imports = new Map<string, TImportedBinding>();
|
|
145
133
|
|
|
146
134
|
for (const statement of sourceFile.statements) {
|
|
147
135
|
if (!ts.isImportDeclaration(statement)) continue;
|
|
@@ -152,14 +140,35 @@ const buildImportIndex = (sourceFile: ts.SourceFile) => {
|
|
|
152
140
|
const { importClause } = statement;
|
|
153
141
|
|
|
154
142
|
if (importClause.name) {
|
|
155
|
-
imports.set(importClause.name.text,
|
|
143
|
+
imports.set(importClause.name.text, {
|
|
144
|
+
kind: 'default',
|
|
145
|
+
importPath,
|
|
146
|
+
localName: importClause.name.text,
|
|
147
|
+
exportedName: 'default',
|
|
148
|
+
});
|
|
156
149
|
}
|
|
157
150
|
|
|
158
151
|
const namedBindings = importClause.namedBindings;
|
|
159
|
-
if (!namedBindings
|
|
152
|
+
if (!namedBindings) continue;
|
|
153
|
+
|
|
154
|
+
if (ts.isNamespaceImport(namedBindings)) {
|
|
155
|
+
imports.set(namedBindings.name.text, {
|
|
156
|
+
kind: 'namespace',
|
|
157
|
+
importPath,
|
|
158
|
+
localName: namedBindings.name.text,
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!ts.isNamedImports(namedBindings)) continue;
|
|
160
164
|
|
|
161
165
|
for (const element of namedBindings.elements) {
|
|
162
|
-
imports.set(element.name.text,
|
|
166
|
+
imports.set(element.name.text, {
|
|
167
|
+
kind: 'named',
|
|
168
|
+
importPath,
|
|
169
|
+
localName: element.name.text,
|
|
170
|
+
exportedName: element.propertyName?.text || element.name.text,
|
|
171
|
+
});
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
174
|
|
|
@@ -183,26 +192,180 @@ const getPropertyNameText = (propertyName: ts.PropertyName | undefined): string
|
|
|
183
192
|
return undefined;
|
|
184
193
|
};
|
|
185
194
|
|
|
186
|
-
const
|
|
187
|
-
|
|
195
|
+
const resolveExistingModuleFilepath = (importPath: string) => {
|
|
196
|
+
const candidates = [
|
|
197
|
+
importPath,
|
|
198
|
+
`${importPath}.ts`,
|
|
199
|
+
`${importPath}.tsx`,
|
|
200
|
+
path.join(importPath, 'index.ts'),
|
|
201
|
+
path.join(importPath, 'index.tsx'),
|
|
202
|
+
];
|
|
188
203
|
|
|
189
|
-
const
|
|
190
|
-
|
|
204
|
+
for (const candidate of candidates) {
|
|
205
|
+
if (!fs.existsSync(candidate)) continue;
|
|
206
|
+
if (!fs.statSync(candidate).isFile()) continue;
|
|
191
207
|
|
|
192
|
-
|
|
208
|
+
return normalizeAbsolutePath(candidate);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return undefined;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const resolveImportSource = (importPath: string, fromFilepath: string): TResolvedImportSource => {
|
|
215
|
+
if (importPath.startsWith('@server/')) {
|
|
216
|
+
return {
|
|
217
|
+
scope: 'framework',
|
|
218
|
+
sourceFilepath: resolveExistingModuleFilepath(path.join(cli.paths.core.root, importPath.slice(1))),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (importPath.startsWith('@/')) {
|
|
223
|
+
return {
|
|
224
|
+
scope: 'app',
|
|
225
|
+
sourceFilepath: resolveExistingModuleFilepath(path.join(app.paths.root, importPath.slice(2))),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
230
|
+
const sourceFilepath = resolveExistingModuleFilepath(path.resolve(path.dirname(fromFilepath), importPath));
|
|
231
|
+
if (!sourceFilepath) return {};
|
|
232
|
+
|
|
233
|
+
if (sourceFilepath === appServerRoot || sourceFilepath.startsWith(`${appServerRoot}/`)) {
|
|
234
|
+
return { scope: 'app', sourceFilepath };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sourceFilepath === coreServerRoot || sourceFilepath.startsWith(`${coreServerRoot}/`)) {
|
|
238
|
+
return { scope: 'framework', sourceFilepath };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { sourceFilepath };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const getObjectLiteralProperty = (expression: ts.ObjectLiteralExpression, propertyName: string) =>
|
|
248
|
+
expression.properties.find((property) => {
|
|
193
249
|
if (!ts.isPropertyAssignment(property)) return false;
|
|
194
250
|
|
|
195
|
-
return getPropertyNameText(property.name) ===
|
|
251
|
+
return getPropertyNameText(property.name) === propertyName;
|
|
196
252
|
});
|
|
197
253
|
|
|
198
|
-
|
|
254
|
+
const readNumericLiteral = (expression: ts.Expression) => {
|
|
255
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
256
|
+
|
|
257
|
+
if (ts.isNumericLiteral(unwrappedExpression)) return Number(unwrappedExpression.text);
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
ts.isPrefixUnaryExpression(unwrappedExpression) &&
|
|
261
|
+
unwrappedExpression.operator === ts.SyntaxKind.MinusToken &&
|
|
262
|
+
ts.isNumericLiteral(unwrapExpression(unwrappedExpression.operand))
|
|
263
|
+
) {
|
|
264
|
+
return -Number((unwrapExpression(unwrappedExpression.operand) as ts.NumericLiteral).text);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return undefined;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const getExportedExpression = (moduleFilepath: string, exportName: string): ts.Expression | undefined => {
|
|
271
|
+
const cacheKey = `${normalizeAbsolutePath(moduleFilepath)}::${exportName}`;
|
|
272
|
+
if (exportExpressionCache.has(cacheKey)) return exportExpressionCache.get(cacheKey);
|
|
273
|
+
|
|
274
|
+
const sourceFile = createSourceFile(moduleFilepath);
|
|
275
|
+
const namedExports = new Map<string, string>();
|
|
276
|
+
|
|
277
|
+
for (const statement of sourceFile.statements) {
|
|
278
|
+
if (!ts.isExportDeclaration(statement) || !statement.exportClause || !ts.isNamedExports(statement.exportClause)) continue;
|
|
279
|
+
|
|
280
|
+
for (const element of statement.exportClause.elements) {
|
|
281
|
+
const exportedName = element.name.text;
|
|
282
|
+
const localName = element.propertyName?.text || element.name.text;
|
|
283
|
+
namedExports.set(exportedName, localName);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const localName = namedExports.get(exportName) || exportName;
|
|
288
|
+
|
|
289
|
+
for (const statement of sourceFile.statements) {
|
|
290
|
+
if (!ts.isVariableStatement(statement) || !hasModifier(statement, ts.SyntaxKind.ExportKeyword)) continue;
|
|
291
|
+
|
|
292
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
293
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== localName || !declaration.initializer) continue;
|
|
294
|
+
|
|
295
|
+
exportExpressionCache.set(cacheKey, declaration.initializer);
|
|
296
|
+
return declaration.initializer;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
exportExpressionCache.set(cacheKey, undefined);
|
|
301
|
+
return undefined;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const extractPriorityFromConfigExpression = (
|
|
305
|
+
configExpression: ts.Expression | undefined,
|
|
306
|
+
imports: Map<string, TImportedBinding>,
|
|
307
|
+
sourceFilepath: string,
|
|
308
|
+
seen = new Set<string>(),
|
|
309
|
+
): number => {
|
|
310
|
+
if (!configExpression) return 0;
|
|
311
|
+
|
|
312
|
+
const expression = unwrapExpression(configExpression);
|
|
199
313
|
|
|
200
|
-
|
|
201
|
-
|
|
314
|
+
if (ts.isObjectLiteralExpression(expression)) {
|
|
315
|
+
const priorityProperty = getObjectLiteralProperty(expression, 'priority');
|
|
316
|
+
if (!priorityProperty || !ts.isPropertyAssignment(priorityProperty)) return 0;
|
|
317
|
+
return readNumericLiteral(priorityProperty.initializer) || 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (ts.isCallExpression(expression) && ts.isPropertyAccessExpression(expression.expression)) {
|
|
321
|
+
const callee = expression.expression;
|
|
202
322
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
323
|
+
if (
|
|
324
|
+
ts.isIdentifier(callee.expression) &&
|
|
325
|
+
callee.expression.text === 'Services' &&
|
|
326
|
+
callee.name.text === 'config'
|
|
327
|
+
) {
|
|
328
|
+
return extractPriorityFromConfigExpression(expression.arguments[1], imports, sourceFilepath, seen);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (ts.isIdentifier(expression)) {
|
|
333
|
+
const imported = imports.get(expression.text);
|
|
334
|
+
if (!imported || imported.kind === 'namespace') return 0;
|
|
335
|
+
|
|
336
|
+
const resolved = resolveImportSource(imported.importPath, sourceFilepath);
|
|
337
|
+
if (!resolved.sourceFilepath) return 0;
|
|
338
|
+
|
|
339
|
+
const cacheKey = `${resolved.sourceFilepath}::${imported.exportedName}`;
|
|
340
|
+
if (seen.has(cacheKey)) return 0;
|
|
341
|
+
|
|
342
|
+
seen.add(cacheKey);
|
|
343
|
+
return extractPriorityFromConfigExpression(
|
|
344
|
+
getExportedExpression(resolved.sourceFilepath, imported.exportedName),
|
|
345
|
+
buildImportIndex(createSourceFile(resolved.sourceFilepath)),
|
|
346
|
+
resolved.sourceFilepath,
|
|
347
|
+
seen,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
352
|
+
const imported = imports.get(expression.expression.text);
|
|
353
|
+
if (!imported || imported.kind !== 'namespace') return 0;
|
|
354
|
+
|
|
355
|
+
const resolved = resolveImportSource(imported.importPath, sourceFilepath);
|
|
356
|
+
if (!resolved.sourceFilepath) return 0;
|
|
357
|
+
|
|
358
|
+
const exportName = expression.name.text;
|
|
359
|
+
const cacheKey = `${resolved.sourceFilepath}::${exportName}`;
|
|
360
|
+
if (seen.has(cacheKey)) return 0;
|
|
361
|
+
|
|
362
|
+
seen.add(cacheKey);
|
|
363
|
+
return extractPriorityFromConfigExpression(
|
|
364
|
+
getExportedExpression(resolved.sourceFilepath, exportName),
|
|
365
|
+
buildImportIndex(createSourceFile(resolved.sourceFilepath)),
|
|
366
|
+
resolved.sourceFilepath,
|
|
367
|
+
seen,
|
|
368
|
+
);
|
|
206
369
|
}
|
|
207
370
|
|
|
208
371
|
return 0;
|
|
@@ -210,50 +373,51 @@ const extractPriorityFromConfig = (configExpression?: ts.Expression) => {
|
|
|
210
373
|
|
|
211
374
|
const resolveImportedService = (
|
|
212
375
|
expression: ts.LeftHandSideExpression,
|
|
213
|
-
imports: Map<string,
|
|
214
|
-
|
|
376
|
+
imports: Map<string, TImportedBinding>,
|
|
377
|
+
sourceFilepath: string,
|
|
215
378
|
) => {
|
|
216
379
|
if (!ts.isIdentifier(expression)) return undefined;
|
|
217
380
|
|
|
218
|
-
const
|
|
219
|
-
if (!
|
|
381
|
+
const imported = imports.get(expression.text);
|
|
382
|
+
if (!imported || imported.kind === 'namespace') return undefined;
|
|
383
|
+
|
|
384
|
+
const resolved = resolveImportSource(imported.importPath, sourceFilepath);
|
|
385
|
+
if (!resolved.scope) return undefined;
|
|
220
386
|
|
|
221
|
-
return
|
|
387
|
+
return {
|
|
388
|
+
className: expression.text,
|
|
389
|
+
importPath: imported.importPath,
|
|
390
|
+
scope: resolved.scope,
|
|
391
|
+
sourceFilepath: resolved.sourceFilepath,
|
|
392
|
+
};
|
|
222
393
|
};
|
|
223
394
|
|
|
224
395
|
const parseServiceInstantiation = (
|
|
225
396
|
registeredName: string,
|
|
226
397
|
expression: ts.Expression,
|
|
227
|
-
imports: Map<string,
|
|
228
|
-
|
|
398
|
+
imports: Map<string, TImportedBinding>,
|
|
399
|
+
sourceFilepath: string,
|
|
229
400
|
configArgIndex: number,
|
|
230
401
|
): TParsedService | undefined => {
|
|
231
402
|
const unwrappedExpression = unwrapExpression(expression);
|
|
232
403
|
if (!ts.isNewExpression(unwrappedExpression)) return undefined;
|
|
233
404
|
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
405
|
+
const resolvedService = resolveImportedService(unwrappedExpression.expression, imports, sourceFilepath);
|
|
406
|
+
if (!resolvedService) return undefined;
|
|
236
407
|
|
|
237
408
|
const configExpression = unwrappedExpression.arguments?.[configArgIndex];
|
|
238
409
|
|
|
239
410
|
return {
|
|
411
|
+
...resolvedService,
|
|
240
412
|
registeredName,
|
|
241
|
-
priority:
|
|
242
|
-
meta,
|
|
413
|
+
priority: extractPriorityFromConfigExpression(configExpression, imports, sourceFilepath),
|
|
243
414
|
};
|
|
244
415
|
};
|
|
245
416
|
|
|
246
|
-
const getObjectLiteralProperty = (expression: ts.ObjectLiteralExpression, propertyName: string) =>
|
|
247
|
-
expression.properties.find((property) => {
|
|
248
|
-
if (!ts.isPropertyAssignment(property)) return false;
|
|
249
|
-
|
|
250
|
-
return getPropertyNameText(property.name) === propertyName;
|
|
251
|
-
});
|
|
252
|
-
|
|
253
417
|
const extractRouterPlugins = (
|
|
254
418
|
routerConfigExpression: ts.Expression | undefined,
|
|
255
|
-
imports: Map<string,
|
|
256
|
-
|
|
419
|
+
imports: Map<string, TImportedBinding>,
|
|
420
|
+
sourceFilepath: string,
|
|
257
421
|
) => {
|
|
258
422
|
if (!routerConfigExpression) return [];
|
|
259
423
|
|
|
@@ -274,7 +438,7 @@ const extractRouterPlugins = (
|
|
|
274
438
|
const registeredName = getPropertyNameText(property.name);
|
|
275
439
|
if (!registeredName) continue;
|
|
276
440
|
|
|
277
|
-
const routerPlugin = parseServiceInstantiation(registeredName, property.initializer, imports,
|
|
441
|
+
const routerPlugin = parseServiceInstantiation(registeredName, property.initializer, imports, sourceFilepath, 0);
|
|
278
442
|
if (!routerPlugin) continue;
|
|
279
443
|
|
|
280
444
|
routerPlugins.push(routerPlugin);
|
|
@@ -283,7 +447,7 @@ const extractRouterPlugins = (
|
|
|
283
447
|
return routerPlugins;
|
|
284
448
|
};
|
|
285
449
|
|
|
286
|
-
const parseAppBootstrap = (
|
|
450
|
+
const parseAppBootstrap = (): TParsedAppBootstrap => {
|
|
287
451
|
const sourceFile = createSourceFile(getAppServerEntryFilepath());
|
|
288
452
|
const imports = buildImportIndex(sourceFile);
|
|
289
453
|
const appClass = getDefaultExportClassDeclaration(sourceFile);
|
|
@@ -297,16 +461,16 @@ const parseAppBootstrap = (servicesAvailable: TServicesAvailable): TParsedAppBoo
|
|
|
297
461
|
const registeredName = getPropertyNameText(member.name);
|
|
298
462
|
if (!registeredName) continue;
|
|
299
463
|
|
|
300
|
-
const rootService = parseServiceInstantiation(registeredName, member.initializer, imports,
|
|
464
|
+
const rootService = parseServiceInstantiation(registeredName, member.initializer, imports, sourceFile.fileName, 1);
|
|
301
465
|
if (!rootService) continue;
|
|
302
466
|
|
|
303
467
|
rootServices.push(rootService);
|
|
304
468
|
|
|
305
|
-
if (rootService.
|
|
469
|
+
if (rootService.importPath === '@server/services/router') {
|
|
306
470
|
const initializer = unwrapExpression(member.initializer);
|
|
307
471
|
if (!ts.isNewExpression(initializer)) continue;
|
|
308
472
|
|
|
309
|
-
routerPlugins = extractRouterPlugins(initializer.arguments?.[1], imports,
|
|
473
|
+
routerPlugins = extractRouterPlugins(initializer.arguments?.[1], imports, sourceFile.fileName);
|
|
310
474
|
}
|
|
311
475
|
}
|
|
312
476
|
|
|
@@ -318,23 +482,10 @@ const parseAppBootstrap = (servicesAvailable: TServicesAvailable): TParsedAppBoo
|
|
|
318
482
|
};
|
|
319
483
|
|
|
320
484
|
const commandServiceSearchRoots = [
|
|
321
|
-
{ root:
|
|
322
|
-
{ root:
|
|
485
|
+
{ root: coreServicesRoot, prefix: '@server/services/' },
|
|
486
|
+
{ root: appServicesRoot, prefix: '@/server/services/' },
|
|
323
487
|
];
|
|
324
488
|
|
|
325
|
-
const resolveExistingModuleFilepath = (importPath: string) => {
|
|
326
|
-
const candidates = [importPath, `${importPath}.ts`, `${importPath}.tsx`, path.join(importPath, 'index.ts'), path.join(importPath, 'index.tsx')];
|
|
327
|
-
|
|
328
|
-
for (const candidate of candidates) {
|
|
329
|
-
if (!fs.existsSync(candidate)) continue;
|
|
330
|
-
if (!fs.statSync(candidate).isFile()) continue;
|
|
331
|
-
|
|
332
|
-
return normalizeAbsolutePath(candidate);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return undefined;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
489
|
const getCommandServiceAliasFromFilepath = (filepath: string) => {
|
|
339
490
|
const normalizedFilepath = normalizeAbsolutePath(filepath);
|
|
340
491
|
|
|
@@ -389,23 +540,21 @@ const isPrivateOrProtectedInstanceMember = (member: ts.ClassElement) =>
|
|
|
389
540
|
|
|
390
541
|
const getPropertyDeclarationType = (
|
|
391
542
|
property: ts.PropertyDeclaration,
|
|
392
|
-
imports: Map<string,
|
|
543
|
+
imports: Map<string, TImportedBinding>,
|
|
393
544
|
sourceFilepath: string,
|
|
394
545
|
getStubTypeName: (source: TCommandServiceStubSource) => string,
|
|
395
546
|
enqueueStub: (source: TCommandServiceStubSource) => void,
|
|
396
547
|
) => {
|
|
397
548
|
const initializer = property.initializer ? unwrapExpression(property.initializer) : undefined;
|
|
398
549
|
|
|
399
|
-
if (initializer && ts.isNewExpression(initializer)) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const nestedSource = resolveCommandServiceStubSource(nestedImportPath, sourceFilepath);
|
|
550
|
+
if (initializer && ts.isNewExpression(initializer) && ts.isIdentifier(initializer.expression)) {
|
|
551
|
+
const nestedImport = imports.get(initializer.expression.text);
|
|
552
|
+
if (nestedImport && nestedImport.kind !== 'namespace') {
|
|
553
|
+
const nestedSource = resolveCommandServiceStubSource(nestedImport.importPath, sourceFilepath);
|
|
404
554
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
555
|
+
if (nestedSource) {
|
|
556
|
+
enqueueStub(nestedSource);
|
|
557
|
+
return getStubTypeName(nestedSource);
|
|
409
558
|
}
|
|
410
559
|
}
|
|
411
560
|
}
|
|
@@ -483,7 +632,7 @@ const createCommandServiceStubDeclarations = (rootServices: TParsedService[]): T
|
|
|
483
632
|
};
|
|
484
633
|
|
|
485
634
|
for (const rootService of rootServices) {
|
|
486
|
-
const source = resolveCommandServiceStubSource(rootService.
|
|
635
|
+
const source = resolveCommandServiceStubSource(rootService.importPath, rootService.sourceFilepath);
|
|
487
636
|
if (source) enqueueStub(source);
|
|
488
637
|
}
|
|
489
638
|
|
|
@@ -564,20 +713,17 @@ ${classMembers.join('\n')}
|
|
|
564
713
|
|
|
565
714
|
const resolveManifestService = (service: TParsedService, parent: string): TProteumManifestService => ({
|
|
566
715
|
kind: 'service',
|
|
567
|
-
id: service.meta.id,
|
|
568
716
|
registeredName: service.registeredName,
|
|
569
|
-
|
|
717
|
+
className: service.className,
|
|
570
718
|
parent,
|
|
571
|
-
priority: service.priority
|
|
572
|
-
importPath: service.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
scope: service.meta.scope,
|
|
719
|
+
priority: service.priority,
|
|
720
|
+
importPath: service.importPath,
|
|
721
|
+
sourceFilepath: service.sourceFilepath,
|
|
722
|
+
scope: service.scope,
|
|
576
723
|
});
|
|
577
724
|
|
|
578
725
|
export const generateServiceArtifacts = () => {
|
|
579
|
-
const
|
|
580
|
-
const { rootServices, routerPlugins } = parseAppBootstrap(servicesAvailable);
|
|
726
|
+
const { rootServices, routerPlugins } = parseAppBootstrap();
|
|
581
727
|
const appClassIdentifier = app.identity.identifier;
|
|
582
728
|
const containerServices = app.containerServices.map((serviceName) => "'" + serviceName + "'").join('|');
|
|
583
729
|
const appServices = rootServices.map((service) => resolveManifestService(service, 'app'));
|
|
@@ -699,7 +845,8 @@ declare class ${appClassIdentifier} implements import("@server/app/commands").TC
|
|
|
699
845
|
Models?: import("@server/app/commands").TCommandApplication["Models"];
|
|
700
846
|
${rootServices
|
|
701
847
|
.map((service) => {
|
|
702
|
-
const
|
|
848
|
+
const source = resolveCommandServiceStubSource(service.importPath, service.sourceFilepath);
|
|
849
|
+
const typeName = source ? commandServiceStubs.typeNamesByAliasImportPath.get(source.aliasImportPath) || 'any' : 'any';
|
|
703
850
|
|
|
704
851
|
return ` ${service.registeredName}: ${typeName};`;
|
|
705
852
|
})
|
|
@@ -78,6 +78,7 @@ export default function createCompiler(
|
|
|
78
78
|
const outputPath = app.outputPath(outputTarget);
|
|
79
79
|
const installedCoreRoot = path.join(app.paths.root, 'node_modules', 'proteum');
|
|
80
80
|
const frameworkRoots = [cli.paths.core.root, installedCoreRoot];
|
|
81
|
+
const transpileModuleDirectories = app.transpileModuleDirectories;
|
|
81
82
|
|
|
82
83
|
const commonConfig = createCommonConfig(app, 'client', mode, outputTarget);
|
|
83
84
|
|
|
@@ -164,6 +165,7 @@ export default function createCompiler(
|
|
|
164
165
|
...frameworkRoots.map((rootPath) => rootPath + '/client'),
|
|
165
166
|
...frameworkRoots.map((rootPath) => rootPath + '/common'),
|
|
166
167
|
...frameworkRoots.map((rootPath) => rootPath + '/server'),
|
|
168
|
+
...transpileModuleDirectories,
|
|
167
169
|
],
|
|
168
170
|
loader: path.join(
|
|
169
171
|
cli.paths.core.root,
|
|
@@ -190,6 +192,7 @@ export default function createCompiler(
|
|
|
190
192
|
...frameworkRoots.map((rootPath) => rootPath + '/client'),
|
|
191
193
|
...frameworkRoots.map((rootPath) => rootPath + '/common'),
|
|
192
194
|
...frameworkRoots.map((rootPath) => rootPath + '/server'),
|
|
195
|
+
...transpileModuleDirectories,
|
|
193
196
|
],
|
|
194
197
|
rules: require('../common/scripts')({ app, side: 'client', dev }),
|
|
195
198
|
},
|
|
@@ -1,10 +1,58 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
1
3
|
// Plugons
|
|
2
4
|
import { rspack } from '@rspack/core';
|
|
5
|
+
import type { Root } from 'postcss';
|
|
3
6
|
|
|
4
7
|
import type { App } from '../../../app';
|
|
5
8
|
|
|
9
|
+
const normalizePath = (value: string) => path.resolve(value).replace(/\\/g, '/');
|
|
10
|
+
|
|
11
|
+
const isPathInsideDirectory = (filepath: string, directory: string) =>
|
|
12
|
+
filepath === directory || filepath.startsWith(directory + '/');
|
|
13
|
+
|
|
14
|
+
const createTailwindTranspileSourcesPlugin = (app: App) => {
|
|
15
|
+
const appRoot = normalizePath(app.paths.root);
|
|
16
|
+
const transpileSourceDirectories = Array.from(
|
|
17
|
+
new Set(app.transpileModuleDirectories.map((directory) => normalizePath(directory))),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (transpileSourceDirectories.length === 0) return null;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
postcssPlugin: 'proteum-tailwind-transpile-sources',
|
|
24
|
+
Once(root: Root) {
|
|
25
|
+
const sourceFile = root.source?.input?.file ? normalizePath(root.source.input.file) : '';
|
|
26
|
+
if (!sourceFile || !isPathInsideDirectory(sourceFile, appRoot)) return;
|
|
27
|
+
|
|
28
|
+
const sourceDirectory = path.dirname(sourceFile);
|
|
29
|
+
const existingSourceParams = new Set<string>();
|
|
30
|
+
|
|
31
|
+
root.walkAtRules('source', (rule) => {
|
|
32
|
+
existingSourceParams.add(String(rule.params).trim());
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
for (const transpileDirectory of [...transpileSourceDirectories].reverse()) {
|
|
36
|
+
const relativeSourceDirectory = path.relative(sourceDirectory, transpileDirectory).split(path.sep).join('/');
|
|
37
|
+
const sourceParam = JSON.stringify(
|
|
38
|
+
relativeSourceDirectory.startsWith('.') ? relativeSourceDirectory : `./${relativeSourceDirectory}`,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (existingSourceParams.has(sourceParam)) continue;
|
|
42
|
+
|
|
43
|
+
root.prepend({
|
|
44
|
+
name: 'source',
|
|
45
|
+
params: sourceParam,
|
|
46
|
+
type: 'atrule',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
6
53
|
module.exports = (app: App, dev: boolean, _client: boolean) => {
|
|
7
54
|
const enableSourceMaps = dev;
|
|
55
|
+
const tailwindTranspileSourcesPlugin = createTailwindTranspileSourcesPlugin(app);
|
|
8
56
|
|
|
9
57
|
return [
|
|
10
58
|
// Keep CSS delivery identical in dev and prod: extract files so SSR links stylesheets in both modes.
|
|
@@ -29,6 +77,10 @@ module.exports = (app: App, dev: boolean, _client: boolean) => {
|
|
|
29
77
|
options: {
|
|
30
78
|
postcssOptions: {
|
|
31
79
|
plugins: [
|
|
80
|
+
// Tailwind v4 only scans files that it knows about. When app code imports
|
|
81
|
+
// transpiled local packages, register those package roots explicitly so
|
|
82
|
+
// shared utility classes survive app splits and workspace extraction.
|
|
83
|
+
...(tailwindTranspileSourcesPlugin ? [tailwindTranspileSourcesPlugin] : []),
|
|
32
84
|
/* Tailwind V4 */ require('@tailwindcss/postcss')({
|
|
33
85
|
// Ensure Tailwind scans the application sources even if the build
|
|
34
86
|
// process is launched from another working directory (e.g. Docker).
|