proteum 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -1
- package/README.md +375 -0
- package/agents/framework/AGENTS.md +917 -0
- package/agents/project/AGENTS.md +138 -0
- package/agents/{codex → project}/CODING_STYLE.md +3 -2
- package/agents/project/client/AGENTS.md +108 -0
- package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
- package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
- package/agents/project/server/services/AGENTS.md +170 -0
- package/agents/{codex → project}/tests/AGENTS.md +1 -0
- package/cli/app/config.ts +3 -2
- package/cli/app/index.ts +6 -66
- package/cli/bin.js +7 -2
- package/cli/commands/build.ts +94 -27
- package/cli/commands/check.ts +15 -1
- package/cli/commands/dev.ts +288 -132
- package/cli/commands/doctor.ts +108 -0
- package/cli/commands/explain.ts +226 -0
- package/cli/commands/init.ts +76 -70
- package/cli/commands/lint.ts +18 -1
- package/cli/commands/refresh.ts +16 -6
- package/cli/commands/typecheck.ts +14 -1
- package/cli/compiler/artifacts/controllers.ts +150 -0
- package/cli/compiler/artifacts/discovery.ts +132 -0
- package/cli/compiler/artifacts/manifest.ts +267 -0
- package/cli/compiler/artifacts/routing.ts +315 -0
- package/cli/compiler/artifacts/services.ts +480 -0
- package/cli/compiler/artifacts/shared.ts +12 -0
- package/cli/compiler/client/identite.ts +2 -1
- package/cli/compiler/client/index.ts +13 -3
- package/cli/compiler/common/controllers.ts +23 -28
- package/cli/compiler/common/files/style.ts +3 -4
- package/cli/compiler/common/generatedRouteModules.ts +333 -19
- package/cli/compiler/common/proteumManifest.ts +133 -0
- package/cli/compiler/index.ts +33 -896
- package/cli/compiler/server/index.ts +21 -4
- package/cli/context.ts +71 -0
- package/cli/index.ts +39 -181
- package/cli/presentation/commands.ts +208 -0
- package/cli/presentation/compileReporter.ts +65 -0
- package/cli/presentation/devSession.ts +70 -0
- package/cli/presentation/help.ts +193 -0
- package/cli/presentation/ink.ts +69 -0
- package/cli/presentation/layout.ts +83 -0
- package/cli/runtime/argv.ts +49 -0
- package/cli/runtime/command.ts +25 -0
- package/cli/runtime/commands.ts +221 -0
- package/cli/runtime/importEsm.ts +7 -0
- package/cli/runtime/verbose.ts +15 -0
- package/cli/utils/agents.ts +5 -4
- package/cli/utils/keyboard.ts +12 -6
- package/client/app/index.ts +0 -6
- package/client/services/router/index.tsx +1 -1
- package/client/services/router/response/index.tsx +2 -2
- package/common/dev/serverHotReload.ts +12 -0
- package/common/router/index.ts +3 -2
- package/common/router/layouts.ts +1 -1
- package/common/router/pageSetup.ts +1 -0
- package/package.json +10 -8
- package/prettier/router-registration-plugin.cjs +52 -0
- package/prettier.config.cjs +1 -0
- package/scripts/cleanup-generated-controllers.ts +2 -2
- package/scripts/fix-reference-app-typing.ts +2 -2
- package/scripts/format-router-registrations.ts +119 -0
- package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
- package/scripts/refactor-server-controllers.ts +19 -18
- package/scripts/refactor-server-runtime-aliases.ts +1 -1
- package/server/app/commands.ts +309 -25
- package/server/app/container/config.ts +1 -1
- package/server/app/container/index.ts +2 -2
- package/server/app/controller/index.ts +13 -4
- package/server/app/index.ts +53 -37
- package/server/app/service/container.ts +26 -28
- package/server/app/service/index.ts +10 -20
- package/server/app.tsconfig.json +9 -2
- package/server/index.ts +32 -1
- package/server/services/auth/index.ts +234 -15
- package/server/services/auth/router/index.ts +39 -7
- package/server/services/auth/router/request.ts +40 -8
- package/server/services/disks/index.ts +1 -1
- package/server/services/prisma/Facet.ts +2 -2
- package/server/services/prisma/index.ts +22 -5
- package/server/services/prisma/mariadb.ts +47 -0
- package/server/services/router/http/index.ts +9 -1
- package/server/services/router/index.ts +10 -4
- package/server/services/router/response/index.ts +26 -6
- package/types/auth-check-rules.test.ts +51 -0
- package/types/controller-request-context.test.ts +55 -0
- package/types/service-config.test.ts +39 -0
- package/agents/codex/AGENTS.md +0 -95
- package/agents/codex/client/AGENTS.md +0 -102
- package/agents/codex/server/services/AGENTS.md +0 -137
- package/server/services/models.7z +0 -0
- /package/agents/{codex → project}/agents.md.zip +0 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
import app from '../../app';
|
|
6
|
+
import cli from '../..';
|
|
7
|
+
import { TProteumManifestScope, TProteumManifestService } from '../common/proteumManifest';
|
|
8
|
+
import writeIfChanged from '../writeIfChanged';
|
|
9
|
+
import { findServiceDirectories } from './discovery';
|
|
10
|
+
import { normalizeAbsolutePath } from './shared';
|
|
11
|
+
|
|
12
|
+
type TServiceMetas = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
parent: string;
|
|
16
|
+
dependences: string[];
|
|
17
|
+
importationPath: string;
|
|
18
|
+
priority: number;
|
|
19
|
+
sourceDir: string;
|
|
20
|
+
metasFilepath: string;
|
|
21
|
+
scope: TProteumManifestScope;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type TParsedService = {
|
|
25
|
+
registeredName: string;
|
|
26
|
+
priority: number;
|
|
27
|
+
meta: TServiceMetas;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type TParsedAppBootstrap = {
|
|
31
|
+
rootServices: TParsedService[];
|
|
32
|
+
routerPlugins: TParsedService[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type TServicesAvailable = Record<string, TServiceMetas>;
|
|
36
|
+
|
|
37
|
+
const buildServicesAvailable = (): TServicesAvailable => {
|
|
38
|
+
const searchDirs = [
|
|
39
|
+
{ path: '@server/services/', priority: -1, root: path.join(cli.paths.core.root, 'server', 'services') },
|
|
40
|
+
{ path: '@/server/services/', priority: 0, root: path.join(app.paths.root, 'server', 'services') },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const servicesAvailable: TServicesAvailable = {};
|
|
44
|
+
|
|
45
|
+
for (const searchDir of searchDirs) {
|
|
46
|
+
const services = findServiceDirectories(searchDir.root);
|
|
47
|
+
|
|
48
|
+
for (const serviceDir of services) {
|
|
49
|
+
const metasFile = path.join(serviceDir, 'service.json');
|
|
50
|
+
const importationPath = searchDir.path + serviceDir.substring(searchDir.root.length + 1);
|
|
51
|
+
const serviceMetas = fs.readJsonSync(metasFile) as {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
parent: string;
|
|
55
|
+
dependences: string[];
|
|
56
|
+
priority?: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
servicesAvailable[serviceMetas.id] = {
|
|
60
|
+
importationPath,
|
|
61
|
+
priority: searchDir.priority,
|
|
62
|
+
sourceDir: normalizeAbsolutePath(serviceDir),
|
|
63
|
+
metasFilepath: normalizeAbsolutePath(metasFile),
|
|
64
|
+
scope: searchDir.path.startsWith('@server/services/') ? 'framework' : 'app',
|
|
65
|
+
...serviceMetas,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return servicesAvailable;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getAppServerEntryFilepath = () => {
|
|
74
|
+
const filepath = app.paths.server.entry;
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(filepath)) {
|
|
77
|
+
throw new Error(`Expected an explicit server application entrypoint at ${filepath}.`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return filepath;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const createSourceFile = (filepath: string) =>
|
|
84
|
+
ts.createSourceFile(
|
|
85
|
+
filepath,
|
|
86
|
+
fs.readFileSync(filepath, 'utf8'),
|
|
87
|
+
ts.ScriptTarget.Latest,
|
|
88
|
+
true,
|
|
89
|
+
filepath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const hasModifier = (node: ts.Node, kind: ts.SyntaxKind) => {
|
|
93
|
+
const modifiers = (node as ts.Node & { modifiers?: ts.NodeArray<ts.Modifier> }).modifiers;
|
|
94
|
+
|
|
95
|
+
return modifiers?.some((modifier) => modifier.kind === kind) ?? false;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getDefaultExportClassDeclaration = (sourceFile: ts.SourceFile) => {
|
|
99
|
+
for (const statement of sourceFile.statements) {
|
|
100
|
+
if (!ts.isClassDeclaration(statement)) continue;
|
|
101
|
+
if (!hasModifier(statement, ts.SyntaxKind.ExportKeyword)) continue;
|
|
102
|
+
if (!hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) continue;
|
|
103
|
+
|
|
104
|
+
return statement;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let defaultExportIdentifier: string | undefined;
|
|
108
|
+
|
|
109
|
+
for (const statement of sourceFile.statements) {
|
|
110
|
+
if (!ts.isExportAssignment(statement)) continue;
|
|
111
|
+
if (!ts.isIdentifier(statement.expression)) continue;
|
|
112
|
+
|
|
113
|
+
defaultExportIdentifier = statement.expression.text;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!defaultExportIdentifier) {
|
|
118
|
+
throw new Error(`Expected ${sourceFile.fileName} to default-export an Application subclass.`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const declaration = sourceFile.statements.find(
|
|
122
|
+
(statement): statement is ts.ClassDeclaration =>
|
|
123
|
+
ts.isClassDeclaration(statement) && statement.name?.text === defaultExportIdentifier,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!declaration) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Unable to resolve the default-exported Application class "${defaultExportIdentifier}" in ${sourceFile.fileName}.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return declaration;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const buildImportIndex = (sourceFile: ts.SourceFile) => {
|
|
136
|
+
const imports = new Map<string, string>();
|
|
137
|
+
|
|
138
|
+
for (const statement of sourceFile.statements) {
|
|
139
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
140
|
+
if (!statement.importClause) continue;
|
|
141
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
142
|
+
|
|
143
|
+
const importPath = statement.moduleSpecifier.text;
|
|
144
|
+
const { importClause } = statement;
|
|
145
|
+
|
|
146
|
+
if (importClause.name) {
|
|
147
|
+
imports.set(importClause.name.text, importPath);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const namedBindings = importClause.namedBindings;
|
|
151
|
+
if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;
|
|
152
|
+
|
|
153
|
+
for (const element of namedBindings.elements) {
|
|
154
|
+
imports.set(element.name.text, importPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return imports;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const unwrapExpression = (expression: ts.Expression): ts.Expression => {
|
|
162
|
+
if (ts.isParenthesizedExpression(expression)) return unwrapExpression(expression.expression);
|
|
163
|
+
if (ts.isAsExpression(expression)) return unwrapExpression(expression.expression);
|
|
164
|
+
if (ts.isSatisfiesExpression(expression)) return unwrapExpression(expression.expression);
|
|
165
|
+
if (ts.isNonNullExpression(expression)) return unwrapExpression(expression.expression);
|
|
166
|
+
|
|
167
|
+
return expression;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const getPropertyNameText = (propertyName: ts.PropertyName | undefined): string | undefined => {
|
|
171
|
+
if (!propertyName) return undefined;
|
|
172
|
+
if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName) || ts.isNumericLiteral(propertyName))
|
|
173
|
+
return propertyName.text;
|
|
174
|
+
|
|
175
|
+
return undefined;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const extractPriorityFromConfig = (configExpression?: ts.Expression) => {
|
|
179
|
+
if (!configExpression) return 0;
|
|
180
|
+
|
|
181
|
+
const expression = unwrapExpression(configExpression);
|
|
182
|
+
if (!ts.isObjectLiteralExpression(expression)) return 0;
|
|
183
|
+
|
|
184
|
+
const priorityProperty = expression.properties.find((property) => {
|
|
185
|
+
if (!ts.isPropertyAssignment(property)) return false;
|
|
186
|
+
|
|
187
|
+
return getPropertyNameText(property.name) === 'priority';
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!priorityProperty || !ts.isPropertyAssignment(priorityProperty)) return 0;
|
|
191
|
+
|
|
192
|
+
const priorityExpression = unwrapExpression(priorityProperty.initializer);
|
|
193
|
+
if (ts.isNumericLiteral(priorityExpression)) return Number(priorityExpression.text);
|
|
194
|
+
|
|
195
|
+
if (ts.isPrefixUnaryExpression(priorityExpression) && priorityExpression.operator === ts.SyntaxKind.MinusToken) {
|
|
196
|
+
const operand = unwrapExpression(priorityExpression.operand);
|
|
197
|
+
if (ts.isNumericLiteral(operand)) return -Number(operand.text);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return 0;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const resolveImportedService = (
|
|
204
|
+
expression: ts.LeftHandSideExpression,
|
|
205
|
+
imports: Map<string, string>,
|
|
206
|
+
servicesAvailable: TServicesAvailable,
|
|
207
|
+
) => {
|
|
208
|
+
if (!ts.isIdentifier(expression)) return undefined;
|
|
209
|
+
|
|
210
|
+
const importPath = imports.get(expression.text);
|
|
211
|
+
if (!importPath) return undefined;
|
|
212
|
+
|
|
213
|
+
return Object.values(servicesAvailable).find((serviceMeta) => serviceMeta.importationPath === importPath);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const parseServiceInstantiation = (
|
|
217
|
+
registeredName: string,
|
|
218
|
+
expression: ts.Expression,
|
|
219
|
+
imports: Map<string, string>,
|
|
220
|
+
servicesAvailable: TServicesAvailable,
|
|
221
|
+
configArgIndex: number,
|
|
222
|
+
): TParsedService | undefined => {
|
|
223
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
224
|
+
if (!ts.isNewExpression(unwrappedExpression)) return undefined;
|
|
225
|
+
|
|
226
|
+
const meta = resolveImportedService(unwrappedExpression.expression, imports, servicesAvailable);
|
|
227
|
+
if (!meta) return undefined;
|
|
228
|
+
|
|
229
|
+
const configExpression = unwrappedExpression.arguments?.[configArgIndex];
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
registeredName,
|
|
233
|
+
priority: extractPriorityFromConfig(configExpression),
|
|
234
|
+
meta,
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const getObjectLiteralProperty = (expression: ts.ObjectLiteralExpression, propertyName: string) =>
|
|
239
|
+
expression.properties.find((property) => {
|
|
240
|
+
if (!ts.isPropertyAssignment(property)) return false;
|
|
241
|
+
|
|
242
|
+
return getPropertyNameText(property.name) === propertyName;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const extractRouterPlugins = (
|
|
246
|
+
routerConfigExpression: ts.Expression | undefined,
|
|
247
|
+
imports: Map<string, string>,
|
|
248
|
+
servicesAvailable: TServicesAvailable,
|
|
249
|
+
) => {
|
|
250
|
+
if (!routerConfigExpression) return [];
|
|
251
|
+
|
|
252
|
+
const configExpression = unwrapExpression(routerConfigExpression);
|
|
253
|
+
if (!ts.isObjectLiteralExpression(configExpression)) return [];
|
|
254
|
+
|
|
255
|
+
const pluginsProperty = getObjectLiteralProperty(configExpression, 'plugins');
|
|
256
|
+
if (!pluginsProperty || !ts.isPropertyAssignment(pluginsProperty)) return [];
|
|
257
|
+
|
|
258
|
+
const pluginsExpression = unwrapExpression(pluginsProperty.initializer);
|
|
259
|
+
if (!ts.isObjectLiteralExpression(pluginsExpression)) return [];
|
|
260
|
+
|
|
261
|
+
const routerPlugins: TParsedService[] = [];
|
|
262
|
+
|
|
263
|
+
for (const property of pluginsExpression.properties) {
|
|
264
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
265
|
+
|
|
266
|
+
const registeredName = getPropertyNameText(property.name);
|
|
267
|
+
if (!registeredName) continue;
|
|
268
|
+
|
|
269
|
+
const routerPlugin = parseServiceInstantiation(registeredName, property.initializer, imports, servicesAvailable, 0);
|
|
270
|
+
if (!routerPlugin) continue;
|
|
271
|
+
|
|
272
|
+
routerPlugins.push(routerPlugin);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return routerPlugins;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const parseAppBootstrap = (servicesAvailable: TServicesAvailable): TParsedAppBootstrap => {
|
|
279
|
+
const sourceFile = createSourceFile(getAppServerEntryFilepath());
|
|
280
|
+
const imports = buildImportIndex(sourceFile);
|
|
281
|
+
const appClass = getDefaultExportClassDeclaration(sourceFile);
|
|
282
|
+
|
|
283
|
+
const rootServices: TParsedService[] = [];
|
|
284
|
+
let routerPlugins: TParsedService[] = [];
|
|
285
|
+
|
|
286
|
+
for (const member of appClass.members) {
|
|
287
|
+
if (!ts.isPropertyDeclaration(member) || !member.initializer) continue;
|
|
288
|
+
|
|
289
|
+
const registeredName = getPropertyNameText(member.name);
|
|
290
|
+
if (!registeredName) continue;
|
|
291
|
+
|
|
292
|
+
const rootService = parseServiceInstantiation(registeredName, member.initializer, imports, servicesAvailable, 1);
|
|
293
|
+
if (!rootService) continue;
|
|
294
|
+
|
|
295
|
+
rootServices.push(rootService);
|
|
296
|
+
|
|
297
|
+
if (rootService.meta.id === 'Core/Router') {
|
|
298
|
+
const initializer = unwrapExpression(member.initializer);
|
|
299
|
+
if (!ts.isNewExpression(initializer)) continue;
|
|
300
|
+
|
|
301
|
+
routerPlugins = extractRouterPlugins(initializer.arguments?.[1], imports, servicesAvailable);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (rootServices.length === 0) {
|
|
306
|
+
throw new Error(`No root services were found in ${sourceFile.fileName}.`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { rootServices, routerPlugins };
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const resolveManifestService = (service: TParsedService, parent: string): TProteumManifestService => ({
|
|
313
|
+
kind: 'service',
|
|
314
|
+
id: service.meta.id,
|
|
315
|
+
registeredName: service.registeredName,
|
|
316
|
+
metaName: service.meta.name,
|
|
317
|
+
parent,
|
|
318
|
+
priority: service.priority || service.meta.priority || 0,
|
|
319
|
+
importPath: service.meta.importationPath,
|
|
320
|
+
sourceDir: service.meta.sourceDir,
|
|
321
|
+
metasFilepath: service.meta.metasFilepath,
|
|
322
|
+
scope: service.meta.scope,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
export const generateServiceArtifacts = () => {
|
|
326
|
+
const servicesAvailable = buildServicesAvailable();
|
|
327
|
+
const { rootServices, routerPlugins } = parseAppBootstrap(servicesAvailable);
|
|
328
|
+
const appClassIdentifier = app.identity.identifier;
|
|
329
|
+
const containerServices = app.containerServices.map((serviceName) => "'" + serviceName + "'").join('|');
|
|
330
|
+
const appServices = rootServices.map((service) => resolveManifestService(service, 'app'));
|
|
331
|
+
const routerPluginServices = routerPlugins.map((service) => resolveManifestService(service, 'Router.plugins'));
|
|
332
|
+
|
|
333
|
+
writeIfChanged(
|
|
334
|
+
path.join(app.paths.client.generated, 'services.d.ts'),
|
|
335
|
+
`declare type ${appClassIdentifier} = import("@/server/index").default;
|
|
336
|
+
|
|
337
|
+
declare module "@app" {
|
|
338
|
+
|
|
339
|
+
import { ${appClassIdentifier} as ${appClassIdentifier}Client } from "@/client";
|
|
340
|
+
import ${appClassIdentifier}Server from "@/server/index";
|
|
341
|
+
|
|
342
|
+
export const Router: ${appClassIdentifier}Client['Router'];
|
|
343
|
+
|
|
344
|
+
${rootServices
|
|
345
|
+
.map((service) =>
|
|
346
|
+
service.registeredName !== 'Router'
|
|
347
|
+
? `export const ${service.registeredName}: ${appClassIdentifier}Server["${service.registeredName}"];`
|
|
348
|
+
: '',
|
|
349
|
+
)
|
|
350
|
+
.join('\n')}
|
|
351
|
+
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
declare module '@models/types' {
|
|
355
|
+
export * from '@generated/client/models';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
declare module '@common/errors' {
|
|
359
|
+
|
|
360
|
+
export * from '@common/errors/index';
|
|
361
|
+
export { default } from '@common/errors/index';
|
|
362
|
+
|
|
363
|
+
export const AuthRequired: typeof import('@common/errors/index').AuthRequired<FeatureKeys>;
|
|
364
|
+
export type AuthRequired = import('@common/errors/index').AuthRequired<FeatureKeys>;
|
|
365
|
+
|
|
366
|
+
export const UpgradeRequired: typeof import('@common/errors/index').UpgradeRequired<FeatureKeys>;
|
|
367
|
+
export type UpgradeRequired = import('@common/errors/index').UpgradeRequired<FeatureKeys>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
declare namespace preact.JSX {
|
|
371
|
+
interface HTMLAttributes {
|
|
372
|
+
src?: string;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
`,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
writeIfChanged(
|
|
379
|
+
path.join(app.paths.client.generated, 'models.ts'),
|
|
380
|
+
`export * from '@/var/prisma/browser';
|
|
381
|
+
`,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
writeIfChanged(
|
|
385
|
+
path.join(app.paths.client.generated, 'context.ts'),
|
|
386
|
+
`// TODO: move it into core (but how to make sure usecontext returns ${appClassIdentifier}'s context ?)
|
|
387
|
+
import React from 'react';
|
|
388
|
+
|
|
389
|
+
import type ${appClassIdentifier}Client from '@/client/index';
|
|
390
|
+
|
|
391
|
+
export type ClientContext = ${appClassIdentifier}Client["Router"]["context"];
|
|
392
|
+
|
|
393
|
+
export const ReactClientContext = React.createContext<ClientContext>({} as ClientContext);
|
|
394
|
+
export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext);`,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
writeIfChanged(
|
|
398
|
+
path.join(app.paths.common.generated, 'services.d.ts'),
|
|
399
|
+
`declare type ${appClassIdentifier} = import("@/server/index").default;
|
|
400
|
+
|
|
401
|
+
declare module '@models/types' {
|
|
402
|
+
export * from '@generated/common/models';
|
|
403
|
+
}`,
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
writeIfChanged(
|
|
407
|
+
path.join(app.paths.common.generated, 'models.ts'),
|
|
408
|
+
`export * from '@/var/prisma/browser';
|
|
409
|
+
`,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
fs.removeSync(path.join(app.paths.server.generated, 'app.ts'));
|
|
413
|
+
|
|
414
|
+
writeIfChanged(
|
|
415
|
+
path.join(app.paths.server.generated, 'models.ts'),
|
|
416
|
+
`export * from '@/var/prisma/client';
|
|
417
|
+
`,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
writeIfChanged(
|
|
421
|
+
path.join(app.paths.server.generated, 'services.d.ts'),
|
|
422
|
+
`type InstalledServices = import("@server/app").RootServicesOf<import("@/server/index").default>;
|
|
423
|
+
|
|
424
|
+
declare type ${appClassIdentifier} = import("@/server/index").default;
|
|
425
|
+
|
|
426
|
+
declare module "@app" {
|
|
427
|
+
|
|
428
|
+
import { ApplicationContainer } from '@server/app/container';
|
|
429
|
+
|
|
430
|
+
const ServerServices: (
|
|
431
|
+
Pick<
|
|
432
|
+
ApplicationContainer<InstalledServices>,
|
|
433
|
+
${containerServices}
|
|
434
|
+
>
|
|
435
|
+
&
|
|
436
|
+
${appClassIdentifier}
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
export = ServerServices
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
declare module '@server/app' {
|
|
443
|
+
|
|
444
|
+
import { Application } from "@server/app";
|
|
445
|
+
import { Environment } from "@server/app";
|
|
446
|
+
import { ServicesContainer } from "@server/app/service/container";
|
|
447
|
+
|
|
448
|
+
abstract class ApplicationWithServices extends Application<
|
|
449
|
+
ServicesContainer<InstalledServices>
|
|
450
|
+
> {}
|
|
451
|
+
|
|
452
|
+
export interface Exported {
|
|
453
|
+
Application: typeof ApplicationWithServices,
|
|
454
|
+
Environment: Environment,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const foo: Exported;
|
|
458
|
+
|
|
459
|
+
export = foo;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
declare module '@common/errors' {
|
|
463
|
+
|
|
464
|
+
export * from '@common/errors/index';
|
|
465
|
+
export { default } from '@common/errors/index';
|
|
466
|
+
|
|
467
|
+
export const AuthRequired: typeof import('@common/errors/index').AuthRequired<FeatureKeys>;
|
|
468
|
+
export type AuthRequired = import('@common/errors/index').AuthRequired<FeatureKeys>;
|
|
469
|
+
|
|
470
|
+
export const UpgradeRequired: typeof import('@common/errors/index').UpgradeRequired<FeatureKeys>;
|
|
471
|
+
export type UpgradeRequired = import('@common/errors/index').UpgradeRequired<FeatureKeys>;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
declare module '@models/types' {
|
|
475
|
+
export * from '@generated/server/models';
|
|
476
|
+
}`,
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
return { app: appServices, routerPlugins: routerPluginServices };
|
|
480
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
4
|
+
|
|
5
|
+
export const normalizeAbsolutePath = (value: string) => normalizePath(path.resolve(value));
|
|
6
|
+
|
|
7
|
+
export const getGeneratedImportPath = (fromDir: string, targetFile: string) => {
|
|
8
|
+
const relativeImportPath = path.relative(fromDir, targetFile).replace(/\\/g, '/');
|
|
9
|
+
const normalizedImportPath = relativeImportPath.startsWith('.') ? relativeImportPath : './' + relativeImportPath;
|
|
10
|
+
|
|
11
|
+
return normalizedImportPath.replace(/\.(ts|tsx|js|jsx)$/, '');
|
|
12
|
+
};
|
|
@@ -6,6 +6,7 @@ import fs from 'fs-extra';
|
|
|
6
6
|
|
|
7
7
|
// Type
|
|
8
8
|
import type { App } from '../../app';
|
|
9
|
+
import { logVerbose } from '../../runtime/verbose';
|
|
9
10
|
|
|
10
11
|
export default async (app: App, outputDir: string, enabled: boolean = true) => {
|
|
11
12
|
if (!enabled) return;
|
|
@@ -23,7 +24,7 @@ export default async (app: App, outputDir: string, enabled: boolean = true) => {
|
|
|
23
24
|
)
|
|
24
25
|
return;
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
logVerbose(`Generating identity assets ...`);
|
|
27
28
|
fs.emptyDirSync(outputDir);
|
|
28
29
|
|
|
29
30
|
const response = await favicons(logoPath, options);
|
|
@@ -14,6 +14,7 @@ import { createClientBundleAnalysisPlugins } from '../common/bundleAnalysis';
|
|
|
14
14
|
import { toRspackAliases } from '../common/rspackAliases';
|
|
15
15
|
import identityAssets from './identite';
|
|
16
16
|
import cli from '../..';
|
|
17
|
+
import { logVerbose } from '../../runtime/verbose';
|
|
17
18
|
|
|
18
19
|
// Type
|
|
19
20
|
import type { App } from '../../app';
|
|
@@ -54,7 +55,7 @@ export default function createCompiler(
|
|
|
54
55
|
mode: TCompileMode,
|
|
55
56
|
outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin',
|
|
56
57
|
): Configuration {
|
|
57
|
-
|
|
58
|
+
logVerbose(`Creating compiler for client (${mode}).`);
|
|
58
59
|
const dev = mode === 'dev';
|
|
59
60
|
const outputPath = app.outputPath(outputTarget);
|
|
60
61
|
|
|
@@ -133,12 +134,15 @@ export default function createCompiler(
|
|
|
133
134
|
include: [
|
|
134
135
|
app.paths.root + '/client',
|
|
135
136
|
cli.paths.core.root + '/client',
|
|
137
|
+
app.paths.client.generated,
|
|
136
138
|
|
|
137
139
|
app.paths.root + '/common',
|
|
138
140
|
cli.paths.core.root + '/common',
|
|
141
|
+
app.paths.common.generated,
|
|
139
142
|
|
|
140
143
|
app.paths.root + '/server',
|
|
141
144
|
cli.paths.core.root + '/server',
|
|
145
|
+
app.paths.server.generated,
|
|
142
146
|
],
|
|
143
147
|
loader: path.join(
|
|
144
148
|
cli.paths.core.root,
|
|
@@ -154,11 +158,16 @@ export default function createCompiler(
|
|
|
154
158
|
include: [
|
|
155
159
|
app.paths.root + '/client',
|
|
156
160
|
cli.paths.core.root + '/client',
|
|
161
|
+
app.paths.client.generated,
|
|
157
162
|
|
|
158
163
|
app.paths.root + '/common',
|
|
159
164
|
cli.paths.core.root + '/common',
|
|
165
|
+
app.paths.common.generated,
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
// Prisma 7 generates browser-safe TypeScript entrypoints under var/prisma.
|
|
168
|
+
app.paths.root + '/var/prisma',
|
|
169
|
+
|
|
170
|
+
app.paths.server.generated + '/models.ts',
|
|
162
171
|
],
|
|
163
172
|
rules: require('../common/scripts')({ app, side: 'client', dev }),
|
|
164
173
|
},
|
|
@@ -190,7 +199,8 @@ export default function createCompiler(
|
|
|
190
199
|
plugins: [
|
|
191
200
|
...(commonConfig.plugins || []),
|
|
192
201
|
|
|
193
|
-
|
|
202
|
+
// Extract CSS in dev too so SSR emits the same stylesheet links as production.
|
|
203
|
+
new rspack.CssExtractRspackPlugin({}),
|
|
194
204
|
|
|
195
205
|
...createClientBundleAnalysisPlugins(app, outputTarget),
|
|
196
206
|
],
|
|
@@ -11,7 +11,14 @@ import ts from 'typescript';
|
|
|
11
11
|
- TYPES
|
|
12
12
|
----------------------------------*/
|
|
13
13
|
|
|
14
|
-
export type
|
|
14
|
+
export type TControllerSourceLocation = { line: number; column: number };
|
|
15
|
+
|
|
16
|
+
export type TControllerMethodMeta = {
|
|
17
|
+
name: string;
|
|
18
|
+
inputCallsCount: number;
|
|
19
|
+
routePath: string;
|
|
20
|
+
sourceLocation: TControllerSourceLocation;
|
|
21
|
+
};
|
|
15
22
|
|
|
16
23
|
export type TControllerFileMeta = {
|
|
17
24
|
importPath: string;
|
|
@@ -21,9 +28,7 @@ export type TControllerFileMeta = {
|
|
|
21
28
|
methods: TControllerMethodMeta[];
|
|
22
29
|
};
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type TControllerSearchDir = { importPrefix: string; root: string; serviceRoots?: TControllerServiceRoot[] };
|
|
31
|
+
type TControllerSearchDir = { importPrefix: string; root: string };
|
|
27
32
|
|
|
28
33
|
/*----------------------------------
|
|
29
34
|
- HELPERS
|
|
@@ -31,35 +36,19 @@ type TControllerSearchDir = { importPrefix: string; root: string; serviceRoots?:
|
|
|
31
36
|
|
|
32
37
|
const getControllerSegments = (relativePath: string) => {
|
|
33
38
|
const segments = relativePath
|
|
34
|
-
.replace(/\.
|
|
39
|
+
.replace(/\.ts$/, '')
|
|
35
40
|
.split('/')
|
|
36
41
|
.filter(Boolean);
|
|
37
42
|
|
|
38
|
-
if (segments
|
|
43
|
+
if (segments[segments.length - 1] === 'index') {
|
|
39
44
|
segments.pop();
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
return segments;
|
|
43
48
|
};
|
|
44
49
|
|
|
45
|
-
const getControllerBasePathFromFilepath = (filepath: string, root: string
|
|
46
|
-
|
|
47
|
-
const serviceRoot = serviceRoots
|
|
48
|
-
.filter((candidate) => normalizedFilepath.startsWith(candidate.dir.replace(/\\/g, '/') + '/'))
|
|
49
|
-
.sort((a, b) => b.dir.length - a.dir.length)[0];
|
|
50
|
-
|
|
51
|
-
if (!serviceRoot) {
|
|
52
|
-
return getControllerSegments(path.relative(root, filepath).replace(/\\/g, '/')).join('/');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const segments = getControllerSegments(path.relative(serviceRoot.dir, filepath).replace(/\\/g, '/'));
|
|
56
|
-
|
|
57
|
-
if (segments[0]?.toLowerCase() === serviceRoot.alias.toLowerCase()) {
|
|
58
|
-
segments.shift();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return [serviceRoot.alias, ...segments].filter(Boolean).join('/');
|
|
62
|
-
};
|
|
50
|
+
const getControllerBasePathFromFilepath = (filepath: string, root: string) =>
|
|
51
|
+
getControllerSegments(path.relative(root, filepath).replace(/\\/g, '/')).join('/');
|
|
63
52
|
|
|
64
53
|
const getGeneratedClassName = (filepath: string) => {
|
|
65
54
|
const filename = path.basename(filepath, '.ts').replace(/[^A-Za-z0-9_$]+/g, '_');
|
|
@@ -85,7 +74,8 @@ const findControllerFiles = (dir: string): string[] => {
|
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
if (!dirent.isFile()) continue;
|
|
88
|
-
if (!dirent.name.endsWith('.
|
|
77
|
+
if (!dirent.name.endsWith('.ts')) continue;
|
|
78
|
+
if (dirent.name.endsWith('.d.ts')) continue;
|
|
89
79
|
|
|
90
80
|
files.push(filepath);
|
|
91
81
|
}
|
|
@@ -102,6 +92,12 @@ const parseSourceFile = (filepath: string, code: string) =>
|
|
|
102
92
|
filepath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
|
103
93
|
);
|
|
104
94
|
|
|
95
|
+
const getNodeLocation = (sourceFile: ts.SourceFile, node: ts.Node): TControllerSourceLocation => {
|
|
96
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
97
|
+
|
|
98
|
+
return { line: line + 1, column: character + 1 };
|
|
99
|
+
};
|
|
100
|
+
|
|
105
101
|
const hasModifier = (node: ts.Node, kind: ts.SyntaxKind) =>
|
|
106
102
|
!!node.modifiers?.some((modifier) => modifier.kind === kind);
|
|
107
103
|
|
|
@@ -187,9 +183,7 @@ export const indexControllers = (searchDirs: TControllerSearchDir[]) => {
|
|
|
187
183
|
if (!defaultClass) continue;
|
|
188
184
|
|
|
189
185
|
const className = defaultClass.name?.text || getGeneratedClassName(filepath);
|
|
190
|
-
const routeBasePath =
|
|
191
|
-
controllerPathOverride ||
|
|
192
|
-
getControllerBasePathFromFilepath(filepath, searchDir.root, searchDir.serviceRoots || []);
|
|
186
|
+
const routeBasePath = controllerPathOverride || getControllerBasePathFromFilepath(filepath, searchDir.root);
|
|
193
187
|
const methods: TControllerMethodMeta[] = [];
|
|
194
188
|
|
|
195
189
|
for (const member of defaultClass.members) {
|
|
@@ -208,6 +202,7 @@ export const indexControllers = (searchDirs: TControllerSearchDir[]) => {
|
|
|
208
202
|
name: methodName,
|
|
209
203
|
inputCallsCount,
|
|
210
204
|
routePath: [routeBasePath, methodName].filter(Boolean).join('/'),
|
|
205
|
+
sourceLocation: getNodeLocation(sourceFile, member.name),
|
|
211
206
|
});
|
|
212
207
|
}
|
|
213
208
|
|
|
@@ -3,13 +3,12 @@ import { rspack } from '@rspack/core';
|
|
|
3
3
|
|
|
4
4
|
import type { App } from '../../../app';
|
|
5
5
|
|
|
6
|
-
module.exports = (app: App, dev: boolean,
|
|
6
|
+
module.exports = (app: App, dev: boolean, _client: boolean) => {
|
|
7
7
|
const enableSourceMaps = dev;
|
|
8
|
-
const useStyleLoader = client && dev;
|
|
9
8
|
|
|
10
9
|
return [
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// Keep CSS delivery identical in dev and prod: extract files so SSR links stylesheets in both modes.
|
|
11
|
+
{ loader: rspack.CssExtractRspackPlugin.loader },
|
|
13
12
|
|
|
14
13
|
// Process external/third-party styles
|
|
15
14
|
{ exclude: [app.paths.root], loader: 'css-loader', options: { sourceMap: enableSourceMaps } },
|