proteum 2.1.0-5 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AGENTS.md +37 -49
  2. package/README.md +52 -1
  3. package/agents/framework/AGENTS.md +104 -236
  4. package/agents/project/AGENTS.md +36 -70
  5. package/cli/commands/command.ts +243 -0
  6. package/cli/commands/commandLocalRunner.js +198 -0
  7. package/cli/commands/dev.ts +95 -1
  8. package/cli/commands/doctor.ts +8 -74
  9. package/cli/commands/explain.ts +8 -194
  10. package/cli/commands/trace.ts +8 -0
  11. package/cli/compiler/artifacts/commands.ts +217 -0
  12. package/cli/compiler/artifacts/manifest.ts +17 -2
  13. package/cli/compiler/artifacts/services.ts +291 -0
  14. package/cli/compiler/client/index.ts +13 -0
  15. package/cli/compiler/common/commands.ts +175 -0
  16. package/cli/compiler/common/proteumManifest.ts +15 -124
  17. package/cli/compiler/index.ts +25 -2
  18. package/cli/compiler/server/index.ts +3 -0
  19. package/cli/presentation/commands.ts +37 -5
  20. package/cli/runtime/commands.ts +29 -1
  21. package/cli/tsconfig.json +4 -1
  22. package/cli/utils/check.ts +1 -1
  23. package/client/app/component.tsx +11 -0
  24. package/client/dev/profiler/index.tsx +1511 -0
  25. package/client/dev/profiler/noop.tsx +5 -0
  26. package/client/dev/profiler/runtime.noop.ts +116 -0
  27. package/client/dev/profiler/runtime.ts +840 -0
  28. package/client/services/router/components/router.tsx +30 -2
  29. package/client/services/router/index.tsx +25 -0
  30. package/client/services/router/request/api.ts +133 -17
  31. package/commands/proteum/diagnostics.ts +11 -0
  32. package/common/dev/commands.ts +50 -0
  33. package/common/dev/diagnostics.ts +298 -0
  34. package/common/dev/profiler.ts +91 -0
  35. package/common/dev/proteumManifest.ts +135 -0
  36. package/common/dev/requestTrace.ts +28 -1
  37. package/docs/dev-commands.md +86 -0
  38. package/docs/request-tracing.md +2 -0
  39. package/package.json +1 -2
  40. package/server/app/commands.ts +35 -370
  41. package/server/app/commandsManager.ts +393 -0
  42. package/server/app/container/console/index.ts +0 -2
  43. package/server/app/container/trace/index.ts +88 -8
  44. package/server/app/devCommands.ts +192 -0
  45. package/server/app/devDiagnostics.ts +53 -0
  46. package/server/app/index.ts +27 -4
  47. package/server/services/cron/CronTask.ts +73 -5
  48. package/server/services/cron/index.ts +34 -11
  49. package/server/services/fetch/index.ts +3 -10
  50. package/server/services/prisma/index.ts +1 -1
  51. package/server/services/router/http/index.ts +132 -21
  52. package/server/services/router/index.ts +40 -4
  53. package/server/services/router/request/api.ts +30 -1
  54. package/skills/clean-project-code/SKILL.md +7 -2
  55. package/test-results/.last-run.json +4 -0
  56. package/types/aliases.d.ts +6 -0
@@ -0,0 +1,217 @@
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 { indexCommands } from '../common/commands';
8
+ import { TProteumManifestCommand } from '../common/proteumManifest';
9
+ import writeIfChanged from '../writeIfChanged';
10
+ import { normalizeAbsolutePath } from './shared';
11
+
12
+ const readServerTsconfigPaths = () => {
13
+ const serverTsconfigFilepath = path.join(app.paths.root, 'server', 'tsconfig.json');
14
+ const parsed = ts.readConfigFile(serverTsconfigFilepath, ts.sys.readFile);
15
+
16
+ if (parsed.error) {
17
+ throw new Error(`Unable to read ${serverTsconfigFilepath}: ${parsed.error.messageText}`);
18
+ }
19
+
20
+ const compilerOptions = (parsed.config?.compilerOptions || {}) as { paths?: Record<string, string[]> };
21
+
22
+ return compilerOptions.paths || {};
23
+ };
24
+
25
+ const createCommandsTsconfigContent = () =>
26
+ `${JSON.stringify(
27
+ {
28
+ extends: '../server/tsconfig.json',
29
+ compilerOptions: {
30
+ baseUrl: '..',
31
+ rootDir: '..',
32
+ paths: {
33
+ ...readServerTsconfigPaths(),
34
+ '@/server/index': ['./.proteum/server/commands.app.d.ts'],
35
+ '@models/types': ['./.proteum/server/models.ts'],
36
+ },
37
+ },
38
+ include: ['.', '../var/typings', '../node_modules/proteum/types/global', '../.proteum/server/commands.d.ts'],
39
+ },
40
+ null,
41
+ 4,
42
+ )}
43
+ `;
44
+
45
+ const legacyCommandsTsconfigContent = `{
46
+ "extends": "../server/tsconfig.json",
47
+ "include": [
48
+ ".",
49
+ "../var/typings",
50
+ "../node_modules/proteum/types/global",
51
+ "../.proteum/server/services.d.ts",
52
+ "../.proteum/server/commands.ts",
53
+ "../server/index.ts"
54
+ ]
55
+ }
56
+ `;
57
+
58
+ const transitionalCommandsTsconfigContent = `{
59
+ "extends": "../server/tsconfig.json",
60
+ "include": [
61
+ ".",
62
+ "../var/typings",
63
+ "../node_modules/proteum/types/global",
64
+ "../.proteum/server/services.d.ts",
65
+ "../server/index.ts"
66
+ ]
67
+ }
68
+ `;
69
+
70
+ const commandsOnlyTsconfigContent = `{
71
+ "extends": "../server/tsconfig.json",
72
+ "include": [
73
+ ".",
74
+ "../var/typings",
75
+ "../node_modules/proteum/types/global",
76
+ "../.proteum/server/commands.d.ts"
77
+ ]
78
+ }
79
+ `;
80
+
81
+ const commandsAliasesTsconfigContent = `{
82
+ "extends": "../server/tsconfig.json",
83
+ "compilerOptions": {
84
+ "baseUrl": "..",
85
+ "rootDir": "..",
86
+ "paths": {
87
+ "@/server/index": ["./.proteum/server/commands.app.d.ts"]
88
+ }
89
+ },
90
+ "include": [
91
+ ".",
92
+ "../var/typings",
93
+ "../node_modules/proteum/types/global",
94
+ "../.proteum/server/commands.d.ts"
95
+ ]
96
+ }
97
+ `;
98
+
99
+ const isManagedCommandsTsconfig = (content: string) => {
100
+ try {
101
+ const parsed = JSON.parse(content) as {
102
+ extends?: string;
103
+ include?: string[];
104
+ compilerOptions?: { baseUrl?: string; rootDir?: string };
105
+ };
106
+
107
+ if (parsed.extends !== '../server/tsconfig.json') return false;
108
+ if (JSON.stringify(parsed.include || []) !== JSON.stringify(['.', '../var/typings', '../node_modules/proteum/types/global', '../.proteum/server/commands.d.ts']))
109
+ return false;
110
+
111
+ if (parsed.compilerOptions?.baseUrl !== undefined && parsed.compilerOptions.baseUrl !== '..') return false;
112
+ if (parsed.compilerOptions?.rootDir !== undefined && parsed.compilerOptions.rootDir !== '..') return false;
113
+
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ };
119
+
120
+ const ensureCommandsTsconfig = () => {
121
+ const commandsRoot = path.join(app.paths.root, 'commands');
122
+ const commandsTsconfigFilepath = path.join(commandsRoot, 'tsconfig.json');
123
+ const nextContent = createCommandsTsconfigContent();
124
+
125
+ if (!fs.existsSync(commandsRoot)) return;
126
+
127
+ if (!fs.existsSync(commandsTsconfigFilepath)) {
128
+ writeIfChanged(commandsTsconfigFilepath, nextContent);
129
+ return;
130
+ }
131
+
132
+ const currentContent = fs.readFileSync(commandsTsconfigFilepath, 'utf8');
133
+ const generatedContents = new Set([
134
+ createCommandsTsconfigContent(),
135
+ legacyCommandsTsconfigContent,
136
+ transitionalCommandsTsconfigContent,
137
+ commandsOnlyTsconfigContent,
138
+ commandsAliasesTsconfigContent,
139
+ ]);
140
+
141
+ if (!generatedContents.has(currentContent) && !isManagedCommandsTsconfig(currentContent)) return;
142
+
143
+ writeIfChanged(commandsTsconfigFilepath, nextContent);
144
+ };
145
+
146
+ export const generateCommandArtifacts = () => {
147
+ ensureCommandsTsconfig();
148
+
149
+ const frameworkCommandsRoot = normalizeAbsolutePath(path.join(cli.paths.core.root, 'commands'));
150
+ const commands = indexCommands([
151
+ { importPrefix: `${frameworkCommandsRoot}/`, root: path.join(cli.paths.core.root, 'commands') },
152
+ { importPrefix: '@/commands/', root: path.join(app.paths.root, 'commands') },
153
+ ]);
154
+
155
+ const getManifestScopeFromImportPath = (importPath: string) =>
156
+ importPath.startsWith(`${frameworkCommandsRoot}/`) ? 'framework' : 'app';
157
+
158
+ const manifestCommands = commands.flatMap<TProteumManifestCommand>((command) =>
159
+ command.methods.map((method) => ({
160
+ className: command.className,
161
+ importPath: command.importPath,
162
+ filepath: normalizeAbsolutePath(command.filepath),
163
+ sourceLocation: method.sourceLocation,
164
+ commandBasePath: command.commandBasePath,
165
+ methodName: method.name,
166
+ path: method.path,
167
+ scope: getManifestScopeFromImportPath(command.importPath),
168
+ })),
169
+ );
170
+
171
+ const commandImports = commands
172
+ .map((command, index) => `import Command${index} from ${JSON.stringify(command.importPath)};`)
173
+ .join('\n');
174
+
175
+ const commandEntries = commands.flatMap((command, commandIndex) =>
176
+ command.methods.map(
177
+ (method) => ` {
178
+ path: ${JSON.stringify(method.path)},
179
+ className: ${JSON.stringify(command.className)},
180
+ importPath: ${JSON.stringify(command.importPath)},
181
+ filepath: ${JSON.stringify(normalizeAbsolutePath(command.filepath))},
182
+ sourceLocation: { line: ${method.sourceLocation.line}, column: ${method.sourceLocation.column} },
183
+ scope: ${JSON.stringify(getManifestScopeFromImportPath(command.importPath))},
184
+ Command: Command${commandIndex},
185
+ methodName: ${JSON.stringify(method.name)},
186
+ },`,
187
+ ),
188
+ );
189
+
190
+ writeIfChanged(
191
+ path.join(app.paths.server.generated, 'commands.ts'),
192
+ `/*----------------------------------
193
+ - GENERATED FILE
194
+ ----------------------------------*/
195
+
196
+ // This file is generated by Proteum from command files.
197
+ // Do not edit it manually.
198
+
199
+ import type { Commands } from '@server/app/commands';
200
+ import type { TDevCommandDefinition } from '@common/dev/commands';
201
+ ${commandImports ? '\n' + commandImports : ''}
202
+
203
+ export type TGeneratedCommandDefinition = TDevCommandDefinition & {
204
+ Command: new (app: any) => Commands<any>,
205
+ methodName: string,
206
+ }
207
+
208
+ const commands: TGeneratedCommandDefinition[] = [
209
+ ${commandEntries.join('\n')}
210
+ ];
211
+
212
+ export default commands;
213
+ `,
214
+ );
215
+
216
+ return manifestCommands;
217
+ };
@@ -8,6 +8,7 @@ import {
8
8
  import { reservedRouteSetupKeys, routeSetupOptionKeys } from '../../../common/router/pageSetup';
9
9
  import {
10
10
  TProteumManifest,
11
+ TProteumManifestCommand,
11
12
  TProteumManifestController,
12
13
  TProteumManifestDiagnostic,
13
14
  TProteumManifestLayout,
@@ -17,9 +18,11 @@ import { writeProteumManifest } from '../common/proteumManifest';
17
18
  import { normalizeAbsolutePath, normalizePath } from './shared';
18
19
 
19
20
  const collectManifestDiagnostics = ({
21
+ commands,
20
22
  controllers,
21
23
  routes,
22
24
  }: {
25
+ commands: TProteumManifestCommand[];
23
26
  controllers: TProteumManifestController[];
24
27
  routes: TProteumManifest['routes'];
25
28
  }) => {
@@ -173,6 +176,15 @@ const collectManifestDiagnostics = ({
173
176
  .join(', ')}.`,
174
177
  });
175
178
 
179
+ trackDuplicates(commands, (command) => command.path, {
180
+ code: 'command.duplicate-path',
181
+ level: 'error',
182
+ message: (command, others) =>
183
+ `Duplicate command path "${command.path}" also registered in ${others
184
+ .map((other) => normalizePath(path.relative(app.paths.root, other.filepath)))
185
+ .join(', ')}.`,
186
+ });
187
+
176
188
  const postServerRoutesByPath = new Map(
177
189
  routes.server
178
190
  .filter((route) => route.methodName === 'post' && !!route.path)
@@ -207,18 +219,20 @@ const collectManifestDiagnostics = ({
207
219
  export const writeCurrentProteumManifest = ({
208
220
  services,
209
221
  controllers,
222
+ commands,
210
223
  routes,
211
224
  layouts,
212
225
  }: {
213
226
  services: TProteumManifest['services'];
214
227
  controllers: TProteumManifestController[];
228
+ commands: TProteumManifestCommand[];
215
229
  routes: TProteumManifest['routes'];
216
230
  layouts: TProteumManifestLayout[];
217
231
  }) => {
218
232
  const envInspection = inspectProteumEnv(app.paths.root);
219
233
 
220
234
  const manifest: TProteumManifest = {
221
- version: 1,
235
+ version: 2,
222
236
  app: {
223
237
  root: normalizeAbsolutePath(app.paths.root),
224
238
  coreRoot: normalizeAbsolutePath(cli.paths.core.root),
@@ -257,9 +271,10 @@ export const writeCurrentProteumManifest = ({
257
271
  },
258
272
  services,
259
273
  controllers,
274
+ commands,
260
275
  routes,
261
276
  layouts,
262
- diagnostics: collectManifestDiagnostics({ controllers, routes }),
277
+ diagnostics: collectManifestDiagnostics({ commands, controllers, routes }),
263
278
  };
264
279
 
265
280
  writeProteumManifest(app.paths.root, manifest);
@@ -33,6 +33,14 @@ type TParsedAppBootstrap = {
33
33
  };
34
34
 
35
35
  type TServicesAvailable = Record<string, TServiceMetas>;
36
+ type TCommandServiceStubSource = {
37
+ aliasImportPath: string;
38
+ filepath: string;
39
+ };
40
+ type TGeneratedCommandServiceStubs = {
41
+ declarations: string;
42
+ typeNamesByAliasImportPath: Map<string, string>;
43
+ };
36
44
 
37
45
  const buildServicesAvailable = (): TServicesAvailable => {
38
46
  const searchDirs = [
@@ -309,6 +317,251 @@ const parseAppBootstrap = (servicesAvailable: TServicesAvailable): TParsedAppBoo
309
317
  return { rootServices, routerPlugins };
310
318
  };
311
319
 
320
+ const commandServiceSearchRoots = [
321
+ { root: normalizeAbsolutePath(path.join(cli.paths.core.root, 'server', 'services')), prefix: '@server/services/' },
322
+ { root: normalizeAbsolutePath(path.join(app.paths.root, 'server', 'services')), prefix: '@/server/services/' },
323
+ ];
324
+
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
+ const getCommandServiceAliasFromFilepath = (filepath: string) => {
339
+ const normalizedFilepath = normalizeAbsolutePath(filepath);
340
+
341
+ for (const searchRoot of commandServiceSearchRoots) {
342
+ if (!normalizedFilepath.startsWith(searchRoot.root + '/')) continue;
343
+
344
+ let relativePath = normalizedFilepath.substring(searchRoot.root.length + 1).replace(/\.(ts|tsx)$/, '');
345
+ if (relativePath.endsWith('/index')) relativePath = relativePath.substring(0, relativePath.length - '/index'.length);
346
+
347
+ return searchRoot.prefix + relativePath;
348
+ }
349
+
350
+ return undefined;
351
+ };
352
+
353
+ const resolveCommandServiceStubSource = (
354
+ importPath: string,
355
+ sourceFilepath?: string,
356
+ ): TCommandServiceStubSource | undefined => {
357
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
358
+ if (!sourceFilepath) return undefined;
359
+
360
+ const resolvedFilepath = resolveExistingModuleFilepath(path.resolve(path.dirname(sourceFilepath), importPath));
361
+ if (!resolvedFilepath) return undefined;
362
+
363
+ const aliasImportPath = getCommandServiceAliasFromFilepath(resolvedFilepath);
364
+ if (!aliasImportPath) return undefined;
365
+
366
+ return { aliasImportPath, filepath: resolvedFilepath };
367
+ }
368
+
369
+ const searchRoot = commandServiceSearchRoots.find((entry) => importPath.startsWith(entry.prefix));
370
+ if (!searchRoot) return undefined;
371
+
372
+ const relativeImportPath = importPath.substring(searchRoot.prefix.length);
373
+ const resolvedFilepath = resolveExistingModuleFilepath(path.join(searchRoot.root, relativeImportPath));
374
+ if (!resolvedFilepath) return undefined;
375
+
376
+ const aliasImportPath = getCommandServiceAliasFromFilepath(resolvedFilepath);
377
+ if (!aliasImportPath) return undefined;
378
+
379
+ return { aliasImportPath, filepath: resolvedFilepath };
380
+ };
381
+
382
+ const getCommandServiceStubTypeName = (aliasImportPath: string) =>
383
+ `ProteumCommandService_${aliasImportPath.replace(/[^A-Za-z0-9_$]+/g, '_')}`;
384
+
385
+ const isPrivateOrProtectedInstanceMember = (member: ts.ClassElement) =>
386
+ hasModifier(member, ts.SyntaxKind.PrivateKeyword) ||
387
+ hasModifier(member, ts.SyntaxKind.ProtectedKeyword) ||
388
+ hasModifier(member, ts.SyntaxKind.StaticKeyword);
389
+
390
+ const getPropertyDeclarationType = (
391
+ property: ts.PropertyDeclaration,
392
+ imports: Map<string, string>,
393
+ sourceFilepath: string,
394
+ getStubTypeName: (source: TCommandServiceStubSource) => string,
395
+ enqueueStub: (source: TCommandServiceStubSource) => void,
396
+ ) => {
397
+ const initializer = property.initializer ? unwrapExpression(property.initializer) : undefined;
398
+
399
+ if (initializer && ts.isNewExpression(initializer)) {
400
+ if (ts.isIdentifier(initializer.expression)) {
401
+ const nestedImportPath = imports.get(initializer.expression.text);
402
+ if (nestedImportPath) {
403
+ const nestedSource = resolveCommandServiceStubSource(nestedImportPath, sourceFilepath);
404
+
405
+ if (nestedSource) {
406
+ enqueueStub(nestedSource);
407
+ return getStubTypeName(nestedSource);
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ if (!initializer) return 'any';
414
+ if (ts.isArrayLiteralExpression(initializer)) return 'any[]';
415
+ if (ts.isObjectLiteralExpression(initializer)) return 'Record<string, any>';
416
+ if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) return 'string';
417
+ if (ts.isNumericLiteral(initializer)) return 'number';
418
+ if (initializer.kind === ts.SyntaxKind.TrueKeyword || initializer.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
419
+ if (initializer.kind === ts.SyntaxKind.NullKeyword) return 'null';
420
+
421
+ return 'any';
422
+ };
423
+
424
+ const getCommandMethodParameter = (parameter: ts.ParameterDeclaration, index: number) => {
425
+ const parameterName = ts.isIdentifier(parameter.name) ? parameter.name.text : `arg${index}`;
426
+
427
+ if (parameter.dotDotDotToken) return `...${parameterName}: any[]`;
428
+
429
+ return `${parameterName}${parameter.questionToken || parameter.initializer ? '?' : ''}: any`;
430
+ };
431
+
432
+ const isPromiseTypeNode = (typeNode?: ts.TypeNode) =>
433
+ !!typeNode &&
434
+ ts.isTypeReferenceNode(typeNode) &&
435
+ ts.isIdentifier(typeNode.typeName) &&
436
+ typeNode.typeName.text === 'Promise';
437
+
438
+ const isArrayLikeTypeNode = (typeNode?: ts.TypeNode): boolean => {
439
+ if (!typeNode) return false;
440
+ if (ts.isArrayTypeNode(typeNode)) return true;
441
+
442
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
443
+ if (typeNode.typeName.text === 'Array' || typeNode.typeName.text === 'ReadonlyArray') return true;
444
+
445
+ if (typeNode.typeName.text === 'Promise' && typeNode.typeArguments?.[0]) {
446
+ return isArrayLikeTypeNode(typeNode.typeArguments[0]);
447
+ }
448
+ }
449
+
450
+ return false;
451
+ };
452
+
453
+ const getCommandMethodReturnType = (method: ts.MethodDeclaration) => {
454
+ const isPromise = hasModifier(method, ts.SyntaxKind.AsyncKeyword) || isPromiseTypeNode(method.type);
455
+ const containsArrayResult = isArrayLikeTypeNode(method.type);
456
+
457
+ if (isPromise && containsArrayResult) return 'Promise<any[]>';
458
+ if (isPromise) return 'Promise<any>';
459
+ if (containsArrayResult) return 'any[]';
460
+
461
+ return 'any';
462
+ };
463
+
464
+ const createCommandServiceStubDeclarations = (rootServices: TParsedService[]): TGeneratedCommandServiceStubs => {
465
+ const stubs = new Map<string, string>();
466
+ const typeNamesByAliasImportPath = new Map<string, string>();
467
+ const pendingSources: TCommandServiceStubSource[] = [];
468
+ const seenSources = new Set<string>();
469
+ const getStubTypeName = (source: TCommandServiceStubSource) => {
470
+ const existingTypeName = typeNamesByAliasImportPath.get(source.aliasImportPath);
471
+ if (existingTypeName) return existingTypeName;
472
+
473
+ const typeName = getCommandServiceStubTypeName(source.aliasImportPath);
474
+ typeNamesByAliasImportPath.set(source.aliasImportPath, typeName);
475
+
476
+ return typeName;
477
+ };
478
+ const enqueueStub = (source: TCommandServiceStubSource) => {
479
+ if (seenSources.has(source.aliasImportPath)) return;
480
+
481
+ seenSources.add(source.aliasImportPath);
482
+ pendingSources.push(source);
483
+ };
484
+
485
+ for (const rootService of rootServices) {
486
+ const source = resolveCommandServiceStubSource(rootService.meta.importationPath);
487
+ if (source) enqueueStub(source);
488
+ }
489
+
490
+ while (pendingSources.length > 0) {
491
+ const source = pendingSources.shift()!;
492
+ const sourceFile = createSourceFile(source.filepath);
493
+ const imports = buildImportIndex(sourceFile);
494
+ let defaultClass: ts.ClassDeclaration | undefined;
495
+
496
+ try {
497
+ defaultClass = getDefaultExportClassDeclaration(sourceFile);
498
+ } catch {
499
+ defaultClass = undefined;
500
+ }
501
+
502
+ if (!defaultClass) {
503
+ stubs.set(
504
+ source.aliasImportPath,
505
+ `declare class ${getStubTypeName(source)} {
506
+ app: import("@/server/index").default;
507
+ [key: string]: any;
508
+ }`,
509
+ );
510
+ continue;
511
+ }
512
+
513
+ const className = getStubTypeName(source);
514
+ const classMembers = [` app: import("@/server/index").default;`];
515
+
516
+ for (const member of defaultClass.members) {
517
+ if (isPrivateOrProtectedInstanceMember(member)) continue;
518
+
519
+ if (ts.isPropertyDeclaration(member)) {
520
+ const propertyName = getPropertyNameText(member.name);
521
+ if (!propertyName) continue;
522
+
523
+ classMembers.push(
524
+ ` ${propertyName}: ${getPropertyDeclarationType(member, imports, source.filepath, getStubTypeName, enqueueStub)};`,
525
+ );
526
+ continue;
527
+ }
528
+
529
+ if (ts.isGetAccessorDeclaration(member)) {
530
+ const propertyName = getPropertyNameText(member.name);
531
+ if (!propertyName) continue;
532
+
533
+ classMembers.push(` ${propertyName}: any;`);
534
+ continue;
535
+ }
536
+
537
+ if (ts.isMethodDeclaration(member)) {
538
+ const methodName = getPropertyNameText(member.name);
539
+ if (!methodName) continue;
540
+
541
+ const parameters = member.parameters.map((parameter, index) => getCommandMethodParameter(parameter, index)).join(', ');
542
+ const returnType = getCommandMethodReturnType(member);
543
+
544
+ classMembers.push(` ${methodName}(${parameters}): ${returnType};`);
545
+ }
546
+ }
547
+
548
+ stubs.set(
549
+ source.aliasImportPath,
550
+ `declare class ${className} {
551
+ ${classMembers.join('\n')}
552
+ }`,
553
+ );
554
+ }
555
+
556
+ return {
557
+ declarations: Array.from(stubs.entries())
558
+ .sort(([left], [right]) => left.localeCompare(right))
559
+ .map(([, declaration]) => declaration)
560
+ .join('\n\n'),
561
+ typeNamesByAliasImportPath,
562
+ };
563
+ };
564
+
312
565
  const resolveManifestService = (service: TParsedService, parent: string): TProteumManifestService => ({
313
566
  kind: 'service',
314
567
  id: service.meta.id,
@@ -329,6 +582,7 @@ export const generateServiceArtifacts = () => {
329
582
  const containerServices = app.containerServices.map((serviceName) => "'" + serviceName + "'").join('|');
330
583
  const appServices = rootServices.map((service) => resolveManifestService(service, 'app'));
331
584
  const routerPluginServices = routerPlugins.map((service) => resolveManifestService(service, 'Router.plugins'));
585
+ const commandServiceStubs = createCommandServiceStubDeclarations(rootServices);
332
586
 
333
587
  writeIfChanged(
334
588
  path.join(app.paths.client.generated, 'services.d.ts'),
@@ -419,6 +673,43 @@ declare module '@models/types' {
419
673
 
420
674
  fs.removeSync(path.join(app.paths.server.generated, 'app.ts'));
421
675
 
676
+ writeIfChanged(
677
+ path.join(app.paths.server.generated, 'commands.d.ts'),
678
+ `declare type ${appClassIdentifier} = import("@/server/index").default;
679
+
680
+ declare module "@models/types" {
681
+ const Models: any;
682
+ export = Models;
683
+ }
684
+
685
+ export {};
686
+ `,
687
+ );
688
+
689
+ writeIfChanged(
690
+ path.join(app.paths.server.generated, 'commands.app.d.ts'),
691
+ `${commandServiceStubs.declarations}
692
+
693
+ declare class ${appClassIdentifier} implements import("@server/app/commands").TCommandApplication {
694
+ env: import("@server/app/commands").TCommandApplication["env"];
695
+ identity: import("@server/app/commands").TCommandApplication["identity"];
696
+ getRootServices: import("@server/app/commands").TCommandApplication["getRootServices"];
697
+ findService?: import("@server/app/commands").TCommandApplication["findService"];
698
+ models?: import("@server/app/commands").TCommandApplication["models"];
699
+ Models?: import("@server/app/commands").TCommandApplication["Models"];
700
+ ${rootServices
701
+ .map((service) => {
702
+ const typeName = commandServiceStubs.typeNamesByAliasImportPath.get(service.meta.importationPath) || 'any';
703
+
704
+ return ` ${service.registeredName}: ${typeName};`;
705
+ })
706
+ .join('\n')}
707
+ }
708
+
709
+ export default ${appClassIdentifier};
710
+ `,
711
+ );
712
+
422
713
  writeIfChanged(
423
714
  path.join(app.paths.server.generated, 'models.ts'),
424
715
  `export * from '@/var/prisma/client';
@@ -221,6 +221,19 @@ export default function createCompiler(
221
221
  plugins: [
222
222
  ...(commonConfig.plugins || []),
223
223
 
224
+ ...(dev
225
+ ? []
226
+ : [
227
+ new rspack.NormalModuleReplacementPlugin(
228
+ /^@client\/dev\/profiler$/,
229
+ cli.paths.core.root + '/client/dev/profiler/noop.tsx',
230
+ ),
231
+ new rspack.NormalModuleReplacementPlugin(
232
+ /^@client\/dev\/profiler\/runtime$/,
233
+ cli.paths.core.root + '/client/dev/profiler/runtime.noop.ts',
234
+ ),
235
+ ]),
236
+
224
237
  // Extract CSS in dev too so SSR emits the same stylesheet links as production.
225
238
  new rspack.CssExtractRspackPlugin({}),
226
239