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.
- package/AGENTS.md +37 -49
- package/README.md +52 -1
- package/agents/framework/AGENTS.md +104 -236
- package/agents/project/AGENTS.md +36 -70
- package/cli/commands/command.ts +243 -0
- package/cli/commands/commandLocalRunner.js +198 -0
- package/cli/commands/dev.ts +95 -1
- package/cli/commands/doctor.ts +8 -74
- package/cli/commands/explain.ts +8 -194
- package/cli/commands/trace.ts +8 -0
- package/cli/compiler/artifacts/commands.ts +217 -0
- package/cli/compiler/artifacts/manifest.ts +17 -2
- package/cli/compiler/artifacts/services.ts +291 -0
- package/cli/compiler/client/index.ts +13 -0
- package/cli/compiler/common/commands.ts +175 -0
- package/cli/compiler/common/proteumManifest.ts +15 -124
- package/cli/compiler/index.ts +25 -2
- package/cli/compiler/server/index.ts +3 -0
- package/cli/presentation/commands.ts +37 -5
- package/cli/runtime/commands.ts +29 -1
- package/cli/tsconfig.json +4 -1
- package/cli/utils/check.ts +1 -1
- package/client/app/component.tsx +11 -0
- package/client/dev/profiler/index.tsx +1511 -0
- package/client/dev/profiler/noop.tsx +5 -0
- package/client/dev/profiler/runtime.noop.ts +116 -0
- package/client/dev/profiler/runtime.ts +840 -0
- package/client/services/router/components/router.tsx +30 -2
- package/client/services/router/index.tsx +25 -0
- package/client/services/router/request/api.ts +133 -17
- package/commands/proteum/diagnostics.ts +11 -0
- package/common/dev/commands.ts +50 -0
- package/common/dev/diagnostics.ts +298 -0
- package/common/dev/profiler.ts +91 -0
- package/common/dev/proteumManifest.ts +135 -0
- package/common/dev/requestTrace.ts +28 -1
- package/docs/dev-commands.md +86 -0
- package/docs/request-tracing.md +2 -0
- package/package.json +1 -2
- package/server/app/commands.ts +35 -370
- package/server/app/commandsManager.ts +393 -0
- package/server/app/container/console/index.ts +0 -2
- package/server/app/container/trace/index.ts +88 -8
- package/server/app/devCommands.ts +192 -0
- package/server/app/devDiagnostics.ts +53 -0
- package/server/app/index.ts +27 -4
- package/server/services/cron/CronTask.ts +73 -5
- package/server/services/cron/index.ts +34 -11
- package/server/services/fetch/index.ts +3 -10
- package/server/services/prisma/index.ts +1 -1
- package/server/services/router/http/index.ts +132 -21
- package/server/services/router/index.ts +40 -4
- package/server/services/router/request/api.ts +30 -1
- package/skills/clean-project-code/SKILL.md +7 -2
- package/test-results/.last-run.json +4 -0
- package/types/aliases.d.ts +6 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TProteumManifest,
|
|
3
|
+
TProteumManifestCommand,
|
|
4
|
+
TProteumManifestController,
|
|
5
|
+
TProteumManifestDiagnostic,
|
|
6
|
+
TProteumManifestLayout,
|
|
7
|
+
TProteumManifestRoute,
|
|
8
|
+
TProteumManifestService,
|
|
9
|
+
} from './proteumManifest';
|
|
10
|
+
|
|
11
|
+
export const explainSectionNames = ['app', 'conventions', 'env', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'] as const;
|
|
12
|
+
|
|
13
|
+
export type TExplainSectionName = (typeof explainSectionNames)[number];
|
|
14
|
+
export type THumanTextBlock = {
|
|
15
|
+
title: string;
|
|
16
|
+
items: string[];
|
|
17
|
+
empty?: string;
|
|
18
|
+
};
|
|
19
|
+
export type TDoctorResponse = {
|
|
20
|
+
summary: {
|
|
21
|
+
errors: number;
|
|
22
|
+
warnings: number;
|
|
23
|
+
strictFailed: boolean;
|
|
24
|
+
};
|
|
25
|
+
diagnostics: TProteumManifestDiagnostic[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
29
|
+
const emptyBlock = 'none';
|
|
30
|
+
|
|
31
|
+
export const formatManifestLocation = (line?: number, column?: number) =>
|
|
32
|
+
line && column ? `:${line}:${column}` : line ? `:${line}` : '';
|
|
33
|
+
|
|
34
|
+
export const formatManifestFilepath = (manifest: TProteumManifest, filepath: string) => {
|
|
35
|
+
const normalizedFilepath = normalizePath(filepath);
|
|
36
|
+
const normalizedAppRoot = normalizePath(manifest.app.root);
|
|
37
|
+
const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
|
|
38
|
+
|
|
39
|
+
if (normalizedFilepath === normalizedAppRoot) return '.';
|
|
40
|
+
if (normalizedFilepath.startsWith(normalizedAppRoot + '/')) return normalizedFilepath.slice(normalizedAppRoot.length + 1) || '.';
|
|
41
|
+
|
|
42
|
+
if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
|
|
43
|
+
if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
|
|
44
|
+
return `node_modules/proteum/${normalizedFilepath.slice(normalizedCoreRoot.length + 1)}`;
|
|
45
|
+
|
|
46
|
+
return normalizedFilepath;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const formatServiceItem = (manifest: TProteumManifest, service: TProteumManifestService) => {
|
|
50
|
+
if (service.kind === 'ref') {
|
|
51
|
+
return `${service.registeredName} -> ref ${service.refTo} [${service.parent}]`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const source = service.metasFilepath ? formatManifestFilepath(manifest, service.metasFilepath) : 'unknown';
|
|
55
|
+
return `${service.registeredName} -> ${service.id} (${service.metaName}) [${service.scope}] priority=${service.priority} source=${source}`;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const formatControllerItem = (manifest: TProteumManifest, controller: TProteumManifestController) =>
|
|
59
|
+
`${controller.clientAccessor} -> POST ${controller.httpPath} [${controller.scope}] input=${controller.hasInput ? 'yes' : 'no'} source=${formatManifestFilepath(manifest, controller.filepath)}${formatManifestLocation(controller.sourceLocation.line, controller.sourceLocation.column)}#${controller.methodName}`;
|
|
60
|
+
|
|
61
|
+
const formatCommandItem = (manifest: TProteumManifest, command: TProteumManifestCommand) =>
|
|
62
|
+
`${command.path} -> ${command.className}.${command.methodName} [${command.scope}] source=${formatManifestFilepath(manifest, command.filepath)}${formatManifestLocation(command.sourceLocation.line, command.sourceLocation.column)}`;
|
|
63
|
+
|
|
64
|
+
const formatRouteTarget = (route: TProteumManifestRoute) => {
|
|
65
|
+
if (route.kind === 'client-error') return route.code !== undefined ? String(route.code) : route.codeRaw || '?';
|
|
66
|
+
return route.path || route.pathRaw || '?';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const formatRouteItem = (manifest: TProteumManifest, route: TProteumManifestRoute) => {
|
|
70
|
+
const chunk = route.chunkId ? ` chunk=${route.chunkId}` : '';
|
|
71
|
+
const setup = route.hasSetup ? ' setup=yes' : ' setup=no';
|
|
72
|
+
const options = route.normalizedOptionKeys.length > 0 ? ` options=${route.normalizedOptionKeys.join(',')}` : '';
|
|
73
|
+
const resolution = route.targetResolution !== 'literal' ? ` resolution=${route.targetResolution}` : '';
|
|
74
|
+
|
|
75
|
+
return `${route.kind} ${route.methodName} ${formatRouteTarget(route)} [${route.scope}]${chunk}${setup}${options}${resolution} source=${formatManifestFilepath(manifest, route.filepath)}${formatManifestLocation(route.sourceLocation.line, route.sourceLocation.column)}`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const formatLayoutItem = (manifest: TProteumManifest, layout: TProteumManifestLayout) =>
|
|
79
|
+
`${layout.chunkId || 'root'} depth=${layout.depth} [${layout.scope}] source=${formatManifestFilepath(manifest, layout.filepath)}`;
|
|
80
|
+
|
|
81
|
+
const formatDiagnosticItem = (manifest: TProteumManifest, diagnostic: TProteumManifestDiagnostic) => {
|
|
82
|
+
const related =
|
|
83
|
+
diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
|
|
84
|
+
? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
|
|
85
|
+
: '';
|
|
86
|
+
|
|
87
|
+
return `[${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const pickExplainManifestSections = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
|
|
91
|
+
if (sectionNames.length === 0) return manifest;
|
|
92
|
+
|
|
93
|
+
const selected: Partial<TProteumManifest> = {};
|
|
94
|
+
|
|
95
|
+
for (const sectionName of sectionNames) {
|
|
96
|
+
selected[sectionName] = manifest[sectionName];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return selected;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const buildExplainSummaryItems = (manifest: TProteumManifest) => {
|
|
103
|
+
const errorsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
|
|
104
|
+
const warningsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
|
|
105
|
+
const providedRequiredEnvVariables = manifest.env.requiredVariables.filter((variable) => variable.provided).length;
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
`Proteum manifest: ${formatManifestFilepath(manifest, `${normalizePath(manifest.app.root)}/.proteum/manifest.json`)}`,
|
|
109
|
+
`App: ${manifest.app.identity.name} (${manifest.app.identity.identifier})`,
|
|
110
|
+
`Env vars: ${providedRequiredEnvVariables}/${manifest.env.requiredVariables.length} required provided`,
|
|
111
|
+
`Services: ${manifest.services.app.length} app, ${manifest.services.routerPlugins.length} router plugins`,
|
|
112
|
+
`Controllers: ${manifest.controllers.length}`,
|
|
113
|
+
`Commands: ${manifest.commands.length}`,
|
|
114
|
+
`Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
|
|
115
|
+
`Layouts: ${manifest.layouts.length}`,
|
|
116
|
+
`Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
|
|
117
|
+
'Use `proteum explain --json` for the full machine-readable manifest or pass section flags like `routes` and `services`.',
|
|
118
|
+
];
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const buildExplainBlocks = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]): THumanTextBlock[] => {
|
|
122
|
+
const blocks: THumanTextBlock[] = [];
|
|
123
|
+
|
|
124
|
+
for (const sectionName of sectionNames) {
|
|
125
|
+
if (sectionName === 'app') {
|
|
126
|
+
blocks.push({
|
|
127
|
+
title: 'App',
|
|
128
|
+
items: [
|
|
129
|
+
`root=${formatManifestFilepath(manifest, manifest.app.root)}`,
|
|
130
|
+
`coreRoot=${formatManifestFilepath(manifest, manifest.app.coreRoot)}`,
|
|
131
|
+
`identity=${formatManifestFilepath(manifest, manifest.app.identityFilepath)}`,
|
|
132
|
+
`name=${manifest.app.identity.name}`,
|
|
133
|
+
`identifier=${manifest.app.identity.identifier}`,
|
|
134
|
+
`title=${manifest.app.identity.fullTitle || manifest.app.identity.title || manifest.app.identity.name}`,
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (sectionName === 'conventions') {
|
|
141
|
+
blocks.push({
|
|
142
|
+
title: 'Conventions',
|
|
143
|
+
items: [
|
|
144
|
+
`routeSetupOptionKeys=${manifest.conventions.routeSetupOptionKeys.join(', ')}`,
|
|
145
|
+
`reservedRouteSetupKeys=${manifest.conventions.reservedRouteSetupKeys.join(', ')}`,
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (sectionName === 'env') {
|
|
152
|
+
blocks.push({
|
|
153
|
+
title: 'Env',
|
|
154
|
+
items: [
|
|
155
|
+
`source=${manifest.env.source}`,
|
|
156
|
+
`loadedVariableKeys=${manifest.env.loadedVariableKeys.join(', ') || 'none'}`,
|
|
157
|
+
...manifest.env.requiredVariables.map(
|
|
158
|
+
(variable) =>
|
|
159
|
+
`${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`,
|
|
160
|
+
),
|
|
161
|
+
`resolved.name=${manifest.env.resolved.name}`,
|
|
162
|
+
`resolved.profile=${manifest.env.resolved.profile}`,
|
|
163
|
+
`resolved.routerPort=${manifest.env.resolved.routerPort}`,
|
|
164
|
+
`resolved.routerCurrentDomain=${manifest.env.resolved.routerCurrentDomain}`,
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (sectionName === 'services') {
|
|
171
|
+
blocks.push(
|
|
172
|
+
{
|
|
173
|
+
title: 'App Services',
|
|
174
|
+
items: manifest.services.app.map((service) => formatServiceItem(manifest, service)),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
title: 'Router Plugins',
|
|
178
|
+
items: manifest.services.routerPlugins.map((service) => formatServiceItem(manifest, service)),
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (sectionName === 'controllers') {
|
|
185
|
+
blocks.push({
|
|
186
|
+
title: 'Controllers',
|
|
187
|
+
items: manifest.controllers.map((controller) => formatControllerItem(manifest, controller)),
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (sectionName === 'commands') {
|
|
193
|
+
blocks.push({
|
|
194
|
+
title: 'Commands',
|
|
195
|
+
items: manifest.commands.map((command) => formatCommandItem(manifest, command)),
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (sectionName === 'routes') {
|
|
201
|
+
blocks.push(
|
|
202
|
+
{
|
|
203
|
+
title: 'Client Routes',
|
|
204
|
+
items: manifest.routes.client.map((route) => formatRouteItem(manifest, route)),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: 'Server Routes',
|
|
208
|
+
items: manifest.routes.server.map((route) => formatRouteItem(manifest, route)),
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (sectionName === 'layouts') {
|
|
215
|
+
blocks.push({
|
|
216
|
+
title: 'Layouts',
|
|
217
|
+
items: manifest.layouts.map((layout) => formatLayoutItem(manifest, layout)),
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (sectionName === 'diagnostics') {
|
|
223
|
+
blocks.push({
|
|
224
|
+
title: 'Diagnostics',
|
|
225
|
+
items: manifest.diagnostics.map((diagnostic) => formatDiagnosticItem(manifest, diagnostic)),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return blocks;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const renderHumanBlock = (block: THumanTextBlock) => {
|
|
234
|
+
if (block.items.length === 0) return `${block.title}\n- ${block.empty || emptyBlock}`;
|
|
235
|
+
return `${block.title}\n${block.items.map((item) => `- ${item}`).join('\n')}`;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const renderExplainHuman = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
|
|
239
|
+
if (sectionNames.length === 0) return buildExplainSummaryItems(manifest).join('\n');
|
|
240
|
+
return buildExplainBlocks(manifest, sectionNames).map(renderHumanBlock).join('\n\n');
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const buildDoctorResponse = (manifest: TProteumManifest, strict = false): TDoctorResponse => {
|
|
244
|
+
const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
|
|
245
|
+
const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
summary: {
|
|
249
|
+
errors: errors.length,
|
|
250
|
+
warnings: warnings.length,
|
|
251
|
+
strictFailed: strict === true && manifest.diagnostics.length > 0,
|
|
252
|
+
},
|
|
253
|
+
diagnostics: manifest.diagnostics,
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export const buildDoctorBlocks = (manifest: TProteumManifest): THumanTextBlock[] => {
|
|
258
|
+
const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
|
|
259
|
+
const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
|
|
260
|
+
|
|
261
|
+
return [
|
|
262
|
+
{
|
|
263
|
+
title: 'Errors',
|
|
264
|
+
items: errors.map((diagnostic) => {
|
|
265
|
+
const related =
|
|
266
|
+
diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
|
|
267
|
+
? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
|
|
268
|
+
: '';
|
|
269
|
+
|
|
270
|
+
return `${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
|
|
271
|
+
}),
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
title: 'Warnings',
|
|
275
|
+
items: warnings.map((diagnostic) => {
|
|
276
|
+
const related =
|
|
277
|
+
diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
|
|
278
|
+
? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
|
|
279
|
+
: '';
|
|
280
|
+
|
|
281
|
+
return `${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
];
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const renderDoctorHuman = (manifest: TProteumManifest, strict = false) => {
|
|
288
|
+
const response = buildDoctorResponse(manifest, strict);
|
|
289
|
+
if (response.diagnostics.length === 0) return 'Proteum doctor\n- No manifest diagnostics were found.';
|
|
290
|
+
|
|
291
|
+
return [
|
|
292
|
+
'Proteum doctor',
|
|
293
|
+
`- ${response.summary.errors} errors`,
|
|
294
|
+
`- ${response.summary.warnings} warnings`,
|
|
295
|
+
'',
|
|
296
|
+
...buildDoctorBlocks(manifest).map(renderHumanBlock),
|
|
297
|
+
].join('\n');
|
|
298
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { TRequestTrace } from './requestTrace';
|
|
2
|
+
|
|
3
|
+
export const profilerTraceRequestIdHeader = 'x-proteum-trace-request-id';
|
|
4
|
+
export const profilerSessionIdHeader = 'x-proteum-profiler-session-id';
|
|
5
|
+
export const profilerOriginHeader = 'x-proteum-profiler-origin';
|
|
6
|
+
export const profilerParentRequestIdHeader = 'x-proteum-profiler-parent-request-id';
|
|
7
|
+
|
|
8
|
+
export type TProfilerUiState = 'expanded' | 'minimized' | 'pinned-handle';
|
|
9
|
+
export type TProfilerPanel =
|
|
10
|
+
| 'summary'
|
|
11
|
+
| 'timeline'
|
|
12
|
+
| 'routing'
|
|
13
|
+
| 'controller'
|
|
14
|
+
| 'ssr'
|
|
15
|
+
| 'api'
|
|
16
|
+
| 'explain'
|
|
17
|
+
| 'doctor'
|
|
18
|
+
| 'commands'
|
|
19
|
+
| 'cron'
|
|
20
|
+
| 'errors';
|
|
21
|
+
export type TProfilerNavigationSessionKind = 'initial-ssr' | 'client-navigation';
|
|
22
|
+
export type TProfilerSessionTraceKind = 'initial-root' | 'navigation-data' | 'async';
|
|
23
|
+
export type TProfilerNavigationStepStatus = 'pending' | 'completed' | 'error';
|
|
24
|
+
export type TProfilerCronTaskTrigger = 'scheduler' | 'manual' | 'autoexec';
|
|
25
|
+
export type TProfilerCronTaskRunStatus = 'completed' | 'error';
|
|
26
|
+
|
|
27
|
+
export type TProfilerCronTaskFrequency =
|
|
28
|
+
| { kind: 'cron'; value: string }
|
|
29
|
+
| { kind: 'date'; value: string };
|
|
30
|
+
|
|
31
|
+
export type TProfilerCronTask = {
|
|
32
|
+
name: string;
|
|
33
|
+
registeredAt: string;
|
|
34
|
+
frequency: TProfilerCronTaskFrequency;
|
|
35
|
+
autoexec: boolean;
|
|
36
|
+
automaticExecution: boolean;
|
|
37
|
+
nextInvocation?: string;
|
|
38
|
+
running: boolean;
|
|
39
|
+
lastTrigger?: TProfilerCronTaskTrigger;
|
|
40
|
+
lastRunStartedAt?: string;
|
|
41
|
+
lastRunFinishedAt?: string;
|
|
42
|
+
lastRunDurationMs?: number;
|
|
43
|
+
lastRunStatus?: TProfilerCronTaskRunStatus;
|
|
44
|
+
lastErrorMessage?: string;
|
|
45
|
+
runCount: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type TProfilerNavigationStep = {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
startedAt: string;
|
|
52
|
+
finishedAt?: string;
|
|
53
|
+
durationMs?: number;
|
|
54
|
+
status: TProfilerNavigationStepStatus;
|
|
55
|
+
details?: { [key: string]: string | number | boolean };
|
|
56
|
+
errorMessage?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type TProfilerSessionTrace = {
|
|
60
|
+
id: string;
|
|
61
|
+
kind: TProfilerSessionTraceKind;
|
|
62
|
+
label: string;
|
|
63
|
+
method: string;
|
|
64
|
+
path: string;
|
|
65
|
+
startedAt: string;
|
|
66
|
+
finishedAt?: string;
|
|
67
|
+
durationMs?: number;
|
|
68
|
+
status: 'pending' | 'completed' | 'error';
|
|
69
|
+
requestId?: string;
|
|
70
|
+
fetcherIds?: string[];
|
|
71
|
+
trace?: TRequestTrace;
|
|
72
|
+
errorMessage?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type TProfilerNavigationSession = {
|
|
76
|
+
id: string;
|
|
77
|
+
kind: TProfilerNavigationSessionKind;
|
|
78
|
+
label: string;
|
|
79
|
+
path: string;
|
|
80
|
+
url: string;
|
|
81
|
+
startedAt: string;
|
|
82
|
+
finishedAt?: string;
|
|
83
|
+
durationMs?: number;
|
|
84
|
+
status: 'active' | 'completed' | 'error';
|
|
85
|
+
requestId?: string;
|
|
86
|
+
routeLabel?: string;
|
|
87
|
+
routeChunkId?: string;
|
|
88
|
+
title?: string;
|
|
89
|
+
steps: TProfilerNavigationStep[];
|
|
90
|
+
traces: TProfilerSessionTrace[];
|
|
91
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export type TProteumManifestScope = 'app' | 'framework';
|
|
2
|
+
export type TProteumManifestSourceLocation = { line: number; column: number };
|
|
3
|
+
export type TProteumManifestRouteTargetResolution = 'literal' | 'static-expression' | 'dynamic-expression';
|
|
4
|
+
export type TProteumManifestDiagnosticLevel = 'warning' | 'error';
|
|
5
|
+
|
|
6
|
+
export type TProteumManifestDiagnostic = {
|
|
7
|
+
level: TProteumManifestDiagnosticLevel;
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
filepath: string;
|
|
11
|
+
sourceLocation?: TProteumManifestSourceLocation;
|
|
12
|
+
relatedFilepaths?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TProteumManifestService = {
|
|
16
|
+
kind: 'service' | 'ref';
|
|
17
|
+
id?: string;
|
|
18
|
+
registeredName: string;
|
|
19
|
+
metaName?: string;
|
|
20
|
+
parent: string;
|
|
21
|
+
priority: number;
|
|
22
|
+
importPath?: string;
|
|
23
|
+
sourceDir?: string;
|
|
24
|
+
metasFilepath?: string;
|
|
25
|
+
refTo?: string;
|
|
26
|
+
scope: TProteumManifestScope;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TProteumManifestController = {
|
|
30
|
+
className: string;
|
|
31
|
+
importPath: string;
|
|
32
|
+
filepath: string;
|
|
33
|
+
sourceLocation: TProteumManifestSourceLocation;
|
|
34
|
+
routeBasePath: string;
|
|
35
|
+
methodName: string;
|
|
36
|
+
inputCallsCount: number;
|
|
37
|
+
hasInput: boolean;
|
|
38
|
+
routePath: string;
|
|
39
|
+
httpPath: string;
|
|
40
|
+
clientAccessor: string;
|
|
41
|
+
scope: TProteumManifestScope;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type TProteumManifestCommand = {
|
|
45
|
+
className: string;
|
|
46
|
+
importPath: string;
|
|
47
|
+
filepath: string;
|
|
48
|
+
sourceLocation: TProteumManifestSourceLocation;
|
|
49
|
+
commandBasePath: string;
|
|
50
|
+
methodName: string;
|
|
51
|
+
path: string;
|
|
52
|
+
scope: TProteumManifestScope;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type TProteumManifestRoute = {
|
|
56
|
+
kind: 'client-page' | 'client-error' | 'server-route';
|
|
57
|
+
methodName: string;
|
|
58
|
+
serviceLocalName: string;
|
|
59
|
+
filepath: string;
|
|
60
|
+
sourceLocation: TProteumManifestSourceLocation;
|
|
61
|
+
targetResolution: TProteumManifestRouteTargetResolution;
|
|
62
|
+
path?: string;
|
|
63
|
+
pathRaw?: string;
|
|
64
|
+
code?: number;
|
|
65
|
+
codeRaw?: string;
|
|
66
|
+
optionKeys: string[];
|
|
67
|
+
normalizedOptionKeys: string[];
|
|
68
|
+
invalidOptionKeys: string[];
|
|
69
|
+
reservedOptionKeys: string[];
|
|
70
|
+
optionsRaw?: string;
|
|
71
|
+
hasSetup: boolean;
|
|
72
|
+
chunkId?: string;
|
|
73
|
+
chunkFilepath?: string;
|
|
74
|
+
scope: TProteumManifestScope;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type TProteumManifestLayout = {
|
|
78
|
+
chunkId: string;
|
|
79
|
+
filepath: string;
|
|
80
|
+
importPath: string;
|
|
81
|
+
depth: number;
|
|
82
|
+
scope: TProteumManifestScope;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type TProteumManifest = {
|
|
86
|
+
version: 2;
|
|
87
|
+
app: {
|
|
88
|
+
root: string;
|
|
89
|
+
coreRoot: string;
|
|
90
|
+
identityFilepath: string;
|
|
91
|
+
identity: {
|
|
92
|
+
name: string;
|
|
93
|
+
identifier: string;
|
|
94
|
+
description: string;
|
|
95
|
+
language?: string;
|
|
96
|
+
locale?: string;
|
|
97
|
+
title?: string;
|
|
98
|
+
titleSuffix?: string;
|
|
99
|
+
fullTitle?: string;
|
|
100
|
+
webDescription?: string;
|
|
101
|
+
version?: string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
conventions: {
|
|
105
|
+
routeSetupOptionKeys: string[];
|
|
106
|
+
reservedRouteSetupKeys: string[];
|
|
107
|
+
};
|
|
108
|
+
env: {
|
|
109
|
+
source: string;
|
|
110
|
+
loadedVariableKeys: string[];
|
|
111
|
+
requiredVariables: {
|
|
112
|
+
key: string;
|
|
113
|
+
possibleValues: string[];
|
|
114
|
+
provided: boolean;
|
|
115
|
+
}[];
|
|
116
|
+
resolved: {
|
|
117
|
+
name: string;
|
|
118
|
+
profile: string;
|
|
119
|
+
routerPort: number;
|
|
120
|
+
routerCurrentDomain: string;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
services: {
|
|
124
|
+
app: TProteumManifestService[];
|
|
125
|
+
routerPlugins: TProteumManifestService[];
|
|
126
|
+
};
|
|
127
|
+
controllers: TProteumManifestController[];
|
|
128
|
+
commands: TProteumManifestCommand[];
|
|
129
|
+
routes: {
|
|
130
|
+
client: TProteumManifestRoute[];
|
|
131
|
+
server: TProteumManifestRoute[];
|
|
132
|
+
};
|
|
133
|
+
layouts: TProteumManifestLayout[];
|
|
134
|
+
diagnostics: TProteumManifestDiagnostic[];
|
|
135
|
+
};
|
|
@@ -3,6 +3,10 @@ export const traceCaptureModes = ['summary', 'resolve', 'deep'] as const;
|
|
|
3
3
|
export type TTraceCaptureMode = (typeof traceCaptureModes)[number];
|
|
4
4
|
type TTracePrimitive = string | number | boolean;
|
|
5
5
|
|
|
6
|
+
export const traceCallOrigins = ['ssr-fetcher', 'api-batch-fetcher', 'client-async'] as const;
|
|
7
|
+
|
|
8
|
+
export type TTraceCallOrigin = (typeof traceCallOrigins)[number];
|
|
9
|
+
|
|
6
10
|
export const traceEventTypes = [
|
|
7
11
|
'request.start',
|
|
8
12
|
'request.user',
|
|
@@ -57,12 +61,34 @@ export type TTraceEvent = {
|
|
|
57
61
|
details: { [key: string]: TTraceSummaryValue };
|
|
58
62
|
};
|
|
59
63
|
|
|
64
|
+
export type TTraceCall = {
|
|
65
|
+
id: string;
|
|
66
|
+
parentId?: string;
|
|
67
|
+
origin: TTraceCallOrigin;
|
|
68
|
+
label: string;
|
|
69
|
+
method: string;
|
|
70
|
+
path: string;
|
|
71
|
+
fetcherId?: string;
|
|
72
|
+
startedAt: string;
|
|
73
|
+
finishedAt?: string;
|
|
74
|
+
durationMs?: number;
|
|
75
|
+
statusCode?: number;
|
|
76
|
+
errorMessage?: string;
|
|
77
|
+
requestDataKeys: string[];
|
|
78
|
+
requestData?: TTraceSummaryValue;
|
|
79
|
+
resultKeys: string[];
|
|
80
|
+
result?: TTraceSummaryValue;
|
|
81
|
+
};
|
|
82
|
+
|
|
60
83
|
export type TRequestTrace = {
|
|
61
84
|
id: string;
|
|
62
85
|
method: string;
|
|
63
86
|
path: string;
|
|
64
87
|
url: string;
|
|
65
88
|
capture: TTraceCaptureMode;
|
|
89
|
+
profilerSessionId?: string;
|
|
90
|
+
profilerOrigin?: string;
|
|
91
|
+
profilerParentRequestId?: string;
|
|
66
92
|
startedAt: string;
|
|
67
93
|
finishedAt?: string;
|
|
68
94
|
durationMs?: number;
|
|
@@ -71,10 +97,11 @@ export type TRequestTrace = {
|
|
|
71
97
|
droppedEvents: number;
|
|
72
98
|
persistedFilepath?: string;
|
|
73
99
|
errorMessage?: string;
|
|
100
|
+
calls: TTraceCall[];
|
|
74
101
|
events: TTraceEvent[];
|
|
75
102
|
};
|
|
76
103
|
|
|
77
|
-
export type TRequestTraceListItem = Omit<TRequestTrace, 'events'> & { eventCount: number };
|
|
104
|
+
export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls'> & { eventCount: number; callCount: number };
|
|
78
105
|
|
|
79
106
|
export type TRequestTraceListResponse = { requests: TRequestTraceListItem[] };
|
|
80
107
|
export type TRequestTraceResponse = { request: TRequestTrace };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
## Dev Commands
|
|
2
|
+
|
|
3
|
+
Proteum supports a dev-only internal command surface for testing, debugging, and one-off server-side execution that should not be exposed as a normal controller or route.
|
|
4
|
+
|
|
5
|
+
### Source Contract
|
|
6
|
+
|
|
7
|
+
- command files live under `./commands/**/*.ts`
|
|
8
|
+
- each file default-exports a class extending `Commands` from `@server/app/commands`
|
|
9
|
+
- every method with a body becomes a runnable command
|
|
10
|
+
- the command path is `file/path/methodName`
|
|
11
|
+
- `export const commandPath = 'Custom/path'` can override the file-derived base path
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Commands } from '@server/app/commands';
|
|
17
|
+
|
|
18
|
+
export default class DiagnosticsCommands extends Commands {
|
|
19
|
+
public async ping() {
|
|
20
|
+
return {
|
|
21
|
+
app: this.app.identity.identifier,
|
|
22
|
+
env: this.app.env.profile,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The example above is available as `diagnostics/ping`.
|
|
29
|
+
|
|
30
|
+
When `./commands` exists, Proteum also creates `./commands/tsconfig.json` plus a generated command typing surface under `.proteum/server/commands.d.ts`.
|
|
31
|
+
|
|
32
|
+
- command files inherit the server alias project
|
|
33
|
+
- `extends Commands` works without importing your app class
|
|
34
|
+
- `@/server/index` remains available inside `/commands` as a generated command-only app type alias
|
|
35
|
+
|
|
36
|
+
### CLI
|
|
37
|
+
|
|
38
|
+
Run a command locally:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
proteum command proteum/diagnostics/ping
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Local mode does this:
|
|
45
|
+
|
|
46
|
+
1. refreshes `.proteum` artifacts
|
|
47
|
+
2. picks a temporary local port
|
|
48
|
+
3. builds the dev server output
|
|
49
|
+
4. starts a temporary local dev server
|
|
50
|
+
5. runs the command through the dev-only command endpoint
|
|
51
|
+
6. prints the result and exits
|
|
52
|
+
|
|
53
|
+
Run a command against an existing dev instance:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
proteum command proteum/diagnostics/ping --port 3101
|
|
57
|
+
proteum command proteum/diagnostics/ping --url http://127.0.0.1:3101
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `--port` or `--url` when you want to reuse an existing `proteum dev` instance instead of building and starting a temporary local one.
|
|
61
|
+
|
|
62
|
+
### Profiler
|
|
63
|
+
|
|
64
|
+
In `proteum dev`, the bottom profiler exposes a `Commands` tab.
|
|
65
|
+
|
|
66
|
+
- it lists every generated command path
|
|
67
|
+
- it shows the backing class, method, scope, and source location
|
|
68
|
+
- clicking `Run now` executes the command through the running dev server
|
|
69
|
+
- the last result or error stays attached to that command row in the panel
|
|
70
|
+
|
|
71
|
+
### HTTP Endpoints
|
|
72
|
+
|
|
73
|
+
The CLI remote mode and the profiler use the same dev-only endpoints:
|
|
74
|
+
|
|
75
|
+
- `GET /__proteum/commands`
|
|
76
|
+
- `POST /__proteum/commands/run`
|
|
77
|
+
|
|
78
|
+
These endpoints exist only in dev mode and are not available in production.
|
|
79
|
+
|
|
80
|
+
### Built-In Command
|
|
81
|
+
|
|
82
|
+
Proteum ships one framework command by default:
|
|
83
|
+
|
|
84
|
+
- `proteum/diagnostics/ping`
|
|
85
|
+
|
|
86
|
+
It returns the current app identifier, active env profile, and discovered root services so every dev app has a real command surface available immediately.
|
package/docs/request-tracing.md
CHANGED
|
@@ -76,6 +76,8 @@ Notes:
|
|
|
76
76
|
- `capture` defaults to `resolve`
|
|
77
77
|
- `requestsLimit` defaults to `200`
|
|
78
78
|
- `eventsLimit` defaults to `800`
|
|
79
|
+
- `proteum dev` removes auto-persisted crash traces from `var/traces/` when the dev session stops
|
|
80
|
+
- explicit `proteum trace export` files under `var/traces/exports/` are left in place
|
|
79
81
|
|
|
80
82
|
## Memory Model
|
|
81
83
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proteum",
|
|
3
3
|
"description": "LLM-first Opinionated Typescript Framework for web applications.",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/proteum.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -70,7 +70,6 @@
|
|
|
70
70
|
"preact-render-to-string": "^6.6.1",
|
|
71
71
|
"prompts": "^2.4.2",
|
|
72
72
|
"replace-once": "^1.0.0",
|
|
73
|
-
"request": "^2.88.2",
|
|
74
73
|
"responsive-loader": "^3.1.2",
|
|
75
74
|
"serialize-javascript": "^6.0.2",
|
|
76
75
|
"sharp": "^0.34.3",
|