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
package/cli/commands/dev.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// Npm
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { spawn, ChildProcess } from 'child_process';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import type { FSWatcher } from 'fs';
|
|
8
10
|
|
|
9
11
|
// Cor elibs
|
|
10
12
|
import cli from '..';
|
|
@@ -31,7 +33,7 @@ import { app, App } from '../app';
|
|
|
31
33
|
----------------------------------*/
|
|
32
34
|
|
|
33
35
|
// Watch rules shared by the dev compiler and hot reload gate.
|
|
34
|
-
const ignoredWatchPathPatterns = /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)|(\.proteum\/)/;
|
|
36
|
+
const ignoredWatchPathPatterns = /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)|(\.proteum\/)|(\/var\/traces\/)/;
|
|
35
37
|
const hotReloadableServerPathPatterns = [
|
|
36
38
|
/^client\/pages\//,
|
|
37
39
|
/^client\/components\//,
|
|
@@ -50,6 +52,7 @@ let cp: ChildProcess | undefined = undefined;
|
|
|
50
52
|
let devSessionStopping = false;
|
|
51
53
|
let appProcessOperation: Promise<void> = Promise.resolve();
|
|
52
54
|
type TDevWatching = ReturnType<Awaited<ReturnType<Compiler['create']>>['watch']>;
|
|
55
|
+
type TIndexedSourceWatching = { close: () => Promise<void> };
|
|
53
56
|
|
|
54
57
|
/*----------------------------------
|
|
55
58
|
- HELPERS
|
|
@@ -110,6 +113,22 @@ const createIgnoredWatchPattern = (outputPaths: string[]) =>
|
|
|
110
113
|
const getDevAppName = (app: App) =>
|
|
111
114
|
app.identity.web?.fullTitle || app.identity.web?.title || app.identity.name || app.packageJson.name || app.paths.root;
|
|
112
115
|
|
|
116
|
+
const cleanupPersistedDevTraces = async (app: App) => {
|
|
117
|
+
const tracesRoot = path.join(app.paths.root, 'var', 'traces');
|
|
118
|
+
if (!(await fs.pathExists(tracesRoot))) return;
|
|
119
|
+
|
|
120
|
+
const entries = await fs.readdir(tracesRoot);
|
|
121
|
+
const removableEntries = entries.filter((entry) => entry !== 'exports');
|
|
122
|
+
if (removableEntries.length === 0) return;
|
|
123
|
+
|
|
124
|
+
await Promise.all(removableEntries.map((entry) => fs.remove(path.join(tracesRoot, entry))));
|
|
125
|
+
|
|
126
|
+
const remainingEntries = await fs.readdir(tracesRoot).catch(() => []);
|
|
127
|
+
if (remainingEntries.length === 0) {
|
|
128
|
+
await fs.remove(tracesRoot);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
113
132
|
const signalAppProcess = (child: ChildProcess, signal: NodeJS.Signals) => {
|
|
114
133
|
try {
|
|
115
134
|
if (process.platform !== 'win32' && child.pid !== undefined) {
|
|
@@ -238,6 +257,78 @@ function normalizeWatchPath(watchPath: string) {
|
|
|
238
257
|
return path.resolve(watchPath).replace(/\\/g, '/').replace(/\/$/, '');
|
|
239
258
|
}
|
|
240
259
|
|
|
260
|
+
const indexedSourceWatchRules: { compilerName: 'server'; root: () => string; relativePathPattern: RegExp }[] = [
|
|
261
|
+
{ compilerName: 'server', root: () => app.paths.root, relativePathPattern: /^commands(?:\/|$)/ },
|
|
262
|
+
{ compilerName: 'server', root: () => cli.paths.core.root, relativePathPattern: /^commands(?:\/|$)/ },
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const closeFsWatcher = async (watcher: FSWatcher) => {
|
|
266
|
+
await new Promise<void>((resolve) => {
|
|
267
|
+
watcher.once('close', () => resolve());
|
|
268
|
+
watcher.close();
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const createIndexedSourceWatching = ({
|
|
273
|
+
compiler,
|
|
274
|
+
watching,
|
|
275
|
+
}: {
|
|
276
|
+
compiler: Compiler;
|
|
277
|
+
watching: TDevWatching;
|
|
278
|
+
}): TIndexedSourceWatching => {
|
|
279
|
+
const watchers: FSWatcher[] = [];
|
|
280
|
+
const pendingChanges = new Map<'server', Set<string>>();
|
|
281
|
+
let invalidateTimer: NodeJS.Timeout | undefined;
|
|
282
|
+
|
|
283
|
+
const flushInvalidate = () => {
|
|
284
|
+
invalidateTimer = undefined;
|
|
285
|
+
|
|
286
|
+
for (const [compilerName, changedFiles] of pendingChanges) {
|
|
287
|
+
compiler.noteManualModifiedFiles(compilerName, [...changedFiles]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
pendingChanges.clear();
|
|
291
|
+
logVerbose('Indexed source files changed. Invalidating the dev compiler to refresh generated artifacts.');
|
|
292
|
+
watching.invalidate();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const queueInvalidate = (compilerName: 'server', filepath: string) => {
|
|
296
|
+
const normalizedFilepath = normalizeWatchPath(filepath);
|
|
297
|
+
const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
|
|
298
|
+
|
|
299
|
+
changedFiles.add(normalizedFilepath);
|
|
300
|
+
pendingChanges.set(compilerName, changedFiles);
|
|
301
|
+
|
|
302
|
+
if (invalidateTimer) return;
|
|
303
|
+
invalidateTimer = setTimeout(flushInvalidate, 40);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
for (const watchRule of indexedSourceWatchRules) {
|
|
307
|
+
const rootPath = watchRule.root();
|
|
308
|
+
|
|
309
|
+
watchers.push(
|
|
310
|
+
fs.watch(rootPath, { recursive: true }, (eventType, filename) => {
|
|
311
|
+
const relativePath = typeof filename === 'string' ? filename.replace(/\\/g, '/').replace(/^\.\//, '') : '';
|
|
312
|
+
if (relativePath && !watchRule.relativePathPattern.test(relativePath)) return;
|
|
313
|
+
if (eventType !== 'rename' && relativePath) return;
|
|
314
|
+
|
|
315
|
+
queueInvalidate(watchRule.compilerName, relativePath ? path.join(rootPath, relativePath) : rootPath);
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
close: async () => {
|
|
322
|
+
if (invalidateTimer) {
|
|
323
|
+
clearTimeout(invalidateTimer);
|
|
324
|
+
invalidateTimer = undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
await Promise.all(watchers.map((watcher) => closeFsWatcher(watcher)));
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
|
|
241
332
|
/*----------------------------------
|
|
242
333
|
- MAIN PROCESS
|
|
243
334
|
----------------------------------*/
|
|
@@ -341,6 +432,7 @@ export const run = async () => {
|
|
|
341
432
|
logVerbose('Watch callback. No compiler changes were tracked.');
|
|
342
433
|
},
|
|
343
434
|
);
|
|
435
|
+
const indexedSourceWatching = createIndexedSourceWatching({ compiler, watching });
|
|
344
436
|
|
|
345
437
|
let shuttingDownPromise: Promise<void> | undefined;
|
|
346
438
|
|
|
@@ -350,9 +442,11 @@ export const run = async () => {
|
|
|
350
442
|
devSessionStopping = true;
|
|
351
443
|
shuttingDownPromise = (async () => {
|
|
352
444
|
logVerbose('Stopping the Proteum dev session ...', reason);
|
|
445
|
+
await indexedSourceWatching.close();
|
|
353
446
|
await closeWatching(watching);
|
|
354
447
|
compiler.dispose();
|
|
355
448
|
await stopApp(reason);
|
|
449
|
+
await cleanupPersistedDevTraces(app);
|
|
356
450
|
await devEventServer.close();
|
|
357
451
|
})();
|
|
358
452
|
|
package/cli/commands/doctor.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
1
|
import cli from '..';
|
|
4
2
|
import Compiler from '../compiler';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
type TProteumManifest,
|
|
8
|
-
type TProteumManifestDiagnostic,
|
|
9
|
-
} from '../compiler/common/proteumManifest';
|
|
3
|
+
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
4
|
+
import { buildDoctorResponse, renderDoctorHuman } from '@common/dev/diagnostics';
|
|
10
5
|
|
|
11
6
|
const allowedDoctorArgs = new Set(['json', 'strict']);
|
|
12
7
|
|
|
@@ -24,43 +19,6 @@ const validateDoctorArgs = () => {
|
|
|
24
19
|
}
|
|
25
20
|
};
|
|
26
21
|
|
|
27
|
-
const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
28
|
-
|
|
29
|
-
const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
|
|
30
|
-
const normalizedFilepath = normalizePath(filepath);
|
|
31
|
-
const normalizedAppRoot = normalizePath(manifest.app.root);
|
|
32
|
-
const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
|
|
33
|
-
|
|
34
|
-
if (normalizedFilepath === normalizedAppRoot) return '.';
|
|
35
|
-
if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
|
|
36
|
-
return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
|
|
37
|
-
|
|
38
|
-
if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
|
|
39
|
-
if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
|
|
40
|
-
return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
|
|
41
|
-
|
|
42
|
-
return normalizedFilepath;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const formatLocation = (diagnostic: TProteumManifestDiagnostic) =>
|
|
46
|
-
diagnostic.sourceLocation ? `:${diagnostic.sourceLocation.line}:${diagnostic.sourceLocation.column}` : '';
|
|
47
|
-
|
|
48
|
-
const renderGroup = (manifest: TProteumManifest, diagnostics: TProteumManifestDiagnostic[], title: string) => {
|
|
49
|
-
if (diagnostics.length === 0) return `${title}\n- none`;
|
|
50
|
-
|
|
51
|
-
return [
|
|
52
|
-
title,
|
|
53
|
-
...diagnostics.map((diagnostic) => {
|
|
54
|
-
const related =
|
|
55
|
-
diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
|
|
56
|
-
? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
|
|
57
|
-
: '';
|
|
58
|
-
|
|
59
|
-
return `- ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic)}${related}`;
|
|
60
|
-
}),
|
|
61
|
-
].join('\n');
|
|
62
|
-
};
|
|
63
|
-
|
|
64
22
|
export const run = async (): Promise<void> => {
|
|
65
23
|
validateDoctorArgs();
|
|
66
24
|
|
|
@@ -68,41 +26,17 @@ export const run = async (): Promise<void> => {
|
|
|
68
26
|
await compiler.refreshGeneratedTypings();
|
|
69
27
|
|
|
70
28
|
const manifest = readProteumManifest(cli.paths.appRoot);
|
|
71
|
-
const
|
|
72
|
-
const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
|
|
29
|
+
const response = buildDoctorResponse(manifest, cli.args.strict === true);
|
|
73
30
|
|
|
74
31
|
if (cli.args.json === true) {
|
|
75
|
-
console.log(
|
|
76
|
-
JSON.stringify(
|
|
77
|
-
{
|
|
78
|
-
summary: {
|
|
79
|
-
errors: errors.length,
|
|
80
|
-
warnings: warnings.length,
|
|
81
|
-
strictFailed: cli.args.strict === true && manifest.diagnostics.length > 0,
|
|
82
|
-
},
|
|
83
|
-
diagnostics: manifest.diagnostics,
|
|
84
|
-
},
|
|
85
|
-
null,
|
|
86
|
-
2,
|
|
87
|
-
),
|
|
88
|
-
);
|
|
89
|
-
} else if (manifest.diagnostics.length === 0) {
|
|
90
|
-
console.log('Proteum doctor\n- No manifest diagnostics were found.');
|
|
32
|
+
console.log(JSON.stringify(response, null, 2));
|
|
91
33
|
} else {
|
|
92
|
-
console.log(
|
|
93
|
-
[
|
|
94
|
-
'Proteum doctor',
|
|
95
|
-
`- ${errors.length} errors`,
|
|
96
|
-
`- ${warnings.length} warnings`,
|
|
97
|
-
'',
|
|
98
|
-
renderGroup(manifest, errors, 'Errors'),
|
|
99
|
-
'',
|
|
100
|
-
renderGroup(manifest, warnings, 'Warnings'),
|
|
101
|
-
].join('\n'),
|
|
102
|
-
);
|
|
34
|
+
console.log(renderDoctorHuman(manifest, cli.args.strict === true));
|
|
103
35
|
}
|
|
104
36
|
|
|
105
37
|
if (cli.args.strict === true && manifest.diagnostics.length > 0) {
|
|
106
|
-
throw new Error(
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Proteum doctor failed in strict mode with ${response.summary.errors} errors and ${response.summary.warnings} warnings.`,
|
|
40
|
+
);
|
|
107
41
|
}
|
|
108
42
|
};
|
package/cli/commands/explain.ts
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
1
|
import cli from '..';
|
|
4
2
|
import Compiler from '../compiler';
|
|
3
|
+
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
type TProteumManifestRoute,
|
|
12
|
-
type TProteumManifestService,
|
|
13
|
-
} from '../compiler/common/proteumManifest';
|
|
5
|
+
explainSectionNames,
|
|
6
|
+
pickExplainManifestSections,
|
|
7
|
+
renderExplainHuman,
|
|
8
|
+
type TExplainSectionName,
|
|
9
|
+
} from '@common/dev/diagnostics';
|
|
14
10
|
|
|
15
|
-
const explainSectionNames = ['app', 'conventions', 'env', 'services', 'controllers', 'routes', 'layouts', 'diagnostics'] as const;
|
|
16
11
|
const allowedExplainArgs = new Set(['json', 'all', ...explainSectionNames]);
|
|
17
12
|
|
|
18
|
-
type TExplainSectionName = (typeof explainSectionNames)[number];
|
|
19
|
-
|
|
20
13
|
const validateExplainArgs = () => {
|
|
21
14
|
const enabledArgs = Object.entries(cli.args)
|
|
22
15
|
.filter(([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true)
|
|
@@ -37,185 +30,6 @@ const getSelectedSections = (): TExplainSectionName[] => {
|
|
|
37
30
|
return explainSectionNames.filter((sectionName) => cli.args[sectionName] === true);
|
|
38
31
|
};
|
|
39
32
|
|
|
40
|
-
const pickManifestSections = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
|
|
41
|
-
if (sectionNames.length === 0) return manifest;
|
|
42
|
-
|
|
43
|
-
const selected: Record<string, unknown> = {};
|
|
44
|
-
|
|
45
|
-
for (const sectionName of sectionNames) {
|
|
46
|
-
selected[sectionName] = manifest[sectionName];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return selected;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
53
|
-
const formatLocation = (line?: number, column?: number) =>
|
|
54
|
-
line && column ? `:${line}:${column}` : line ? `:${line}` : '';
|
|
55
|
-
|
|
56
|
-
const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
|
|
57
|
-
const normalizedFilepath = normalizePath(filepath);
|
|
58
|
-
const normalizedAppRoot = normalizePath(manifest.app.root);
|
|
59
|
-
const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
|
|
60
|
-
|
|
61
|
-
if (normalizedFilepath === normalizedAppRoot) return '.';
|
|
62
|
-
if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
|
|
63
|
-
return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
|
|
64
|
-
|
|
65
|
-
if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
|
|
66
|
-
if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
|
|
67
|
-
return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
|
|
68
|
-
|
|
69
|
-
return normalizedFilepath;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const formatService = (manifest: TProteumManifest, service: TProteumManifestService) => {
|
|
73
|
-
if (service.kind === 'ref') {
|
|
74
|
-
return `- ${service.registeredName} -> ref ${service.refTo} [${service.parent}]`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const source = service.metasFilepath ? formatFilepath(manifest, service.metasFilepath) : 'unknown';
|
|
78
|
-
return `- ${service.registeredName} -> ${service.id} (${service.metaName}) [${service.scope}] priority=${service.priority} source=${source}`;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const formatController = (manifest: TProteumManifest, controller: TProteumManifestController) =>
|
|
82
|
-
`- ${controller.clientAccessor} -> POST ${controller.httpPath} [${controller.scope}] input=${controller.hasInput ? 'yes' : 'no'} source=${formatFilepath(manifest, controller.filepath)}${formatLocation(controller.sourceLocation.line, controller.sourceLocation.column)}#${controller.methodName}`;
|
|
83
|
-
|
|
84
|
-
const formatRouteTarget = (route: TProteumManifestRoute) => {
|
|
85
|
-
if (route.kind === 'client-error') return route.code !== undefined ? String(route.code) : route.codeRaw || '?';
|
|
86
|
-
return route.path || route.pathRaw || '?';
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const formatRoute = (manifest: TProteumManifest, route: TProteumManifestRoute) => {
|
|
90
|
-
const chunk = route.chunkId ? ` chunk=${route.chunkId}` : '';
|
|
91
|
-
const setup = route.hasSetup ? ' setup=yes' : ' setup=no';
|
|
92
|
-
const options = route.normalizedOptionKeys.length > 0 ? ` options=${route.normalizedOptionKeys.join(',')}` : '';
|
|
93
|
-
const resolution = route.targetResolution !== 'literal' ? ` resolution=${route.targetResolution}` : '';
|
|
94
|
-
|
|
95
|
-
return `- ${route.kind} ${route.methodName} ${formatRouteTarget(route)} [${route.scope}]${chunk}${setup}${options}${resolution} source=${formatFilepath(manifest, route.filepath)}${formatLocation(route.sourceLocation.line, route.sourceLocation.column)}`;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const formatLayout = (manifest: TProteumManifest, layout: TProteumManifestLayout) =>
|
|
99
|
-
`- ${layout.chunkId || 'root'} depth=${layout.depth} [${layout.scope}] source=${formatFilepath(manifest, layout.filepath)}`;
|
|
100
|
-
|
|
101
|
-
const formatDiagnostic = (manifest: TProteumManifest, diagnostic: TProteumManifestDiagnostic) => {
|
|
102
|
-
const related =
|
|
103
|
-
diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
|
|
104
|
-
? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
|
|
105
|
-
: '';
|
|
106
|
-
|
|
107
|
-
return `- [${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const printSection = (title: string, lines: string[]) => {
|
|
111
|
-
if (lines.length === 0) return `${title}\n- none`;
|
|
112
|
-
return `${title}\n${lines.join('\n')}`;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const renderSummary = (manifest: TProteumManifest) => {
|
|
116
|
-
const errorsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
|
|
117
|
-
const warningsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
|
|
118
|
-
const providedRequiredEnvVariables = manifest.env.requiredVariables.filter((variable) => variable.provided).length;
|
|
119
|
-
const lines = [
|
|
120
|
-
`Proteum manifest: ${formatFilepath(manifest, path.join(manifest.app.root, '.proteum', 'manifest.json'))}`,
|
|
121
|
-
`App: ${manifest.app.identity.name} (${manifest.app.identity.identifier})`,
|
|
122
|
-
`Env vars: ${providedRequiredEnvVariables}/${manifest.env.requiredVariables.length} required provided`,
|
|
123
|
-
`Services: ${manifest.services.app.length} app, ${manifest.services.routerPlugins.length} router plugins`,
|
|
124
|
-
`Controllers: ${manifest.controllers.length}`,
|
|
125
|
-
`Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
|
|
126
|
-
`Layouts: ${manifest.layouts.length}`,
|
|
127
|
-
`Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
|
|
128
|
-
'Use `proteum explain --json` for the full machine-readable manifest or pass section flags like `routes` and `services`.',
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
return lines.join('\n');
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const renderHuman = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
|
|
135
|
-
if (sectionNames.length === 0) return renderSummary(manifest);
|
|
136
|
-
|
|
137
|
-
const sections: string[] = [];
|
|
138
|
-
|
|
139
|
-
for (const sectionName of sectionNames) {
|
|
140
|
-
if (sectionName === 'app') {
|
|
141
|
-
sections.push(
|
|
142
|
-
printSection('App', [
|
|
143
|
-
`- root=${formatFilepath(manifest, manifest.app.root)}`,
|
|
144
|
-
`- coreRoot=${formatFilepath(manifest, manifest.app.coreRoot)}`,
|
|
145
|
-
`- identity=${formatFilepath(manifest, manifest.app.identityFilepath)}`,
|
|
146
|
-
`- name=${manifest.app.identity.name}`,
|
|
147
|
-
`- identifier=${manifest.app.identity.identifier}`,
|
|
148
|
-
`- title=${manifest.app.identity.fullTitle || manifest.app.identity.title || manifest.app.identity.name}`,
|
|
149
|
-
]),
|
|
150
|
-
);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (sectionName === 'conventions') {
|
|
155
|
-
sections.push(
|
|
156
|
-
printSection('Conventions', [
|
|
157
|
-
`- routeSetupOptionKeys=${manifest.conventions.routeSetupOptionKeys.join(', ')}`,
|
|
158
|
-
`- reservedRouteSetupKeys=${manifest.conventions.reservedRouteSetupKeys.join(', ')}`,
|
|
159
|
-
]),
|
|
160
|
-
);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (sectionName === 'env') {
|
|
165
|
-
sections.push(
|
|
166
|
-
printSection('Env', [
|
|
167
|
-
`- source=${manifest.env.source}`,
|
|
168
|
-
`- loadedVariableKeys=${manifest.env.loadedVariableKeys.join(', ') || 'none'}`,
|
|
169
|
-
...manifest.env.requiredVariables.map(
|
|
170
|
-
(variable) =>
|
|
171
|
-
`- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`,
|
|
172
|
-
),
|
|
173
|
-
`- resolved.name=${manifest.env.resolved.name}`,
|
|
174
|
-
`- resolved.profile=${manifest.env.resolved.profile}`,
|
|
175
|
-
`- resolved.routerPort=${manifest.env.resolved.routerPort}`,
|
|
176
|
-
`- resolved.routerCurrentDomain=${manifest.env.resolved.routerCurrentDomain}`,
|
|
177
|
-
]),
|
|
178
|
-
);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (sectionName === 'services') {
|
|
183
|
-
sections.push(
|
|
184
|
-
printSection('App Services', manifest.services.app.map((service) => formatService(manifest, service))),
|
|
185
|
-
printSection(
|
|
186
|
-
'Router Plugins',
|
|
187
|
-
manifest.services.routerPlugins.map((service) => formatService(manifest, service)),
|
|
188
|
-
),
|
|
189
|
-
);
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (sectionName === 'controllers') {
|
|
194
|
-
sections.push(printSection('Controllers', manifest.controllers.map((controller) => formatController(manifest, controller))));
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (sectionName === 'routes') {
|
|
199
|
-
sections.push(
|
|
200
|
-
printSection('Client Routes', manifest.routes.client.map((route) => formatRoute(manifest, route))),
|
|
201
|
-
printSection('Server Routes', manifest.routes.server.map((route) => formatRoute(manifest, route))),
|
|
202
|
-
);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (sectionName === 'layouts') {
|
|
207
|
-
sections.push(printSection('Layouts', manifest.layouts.map((layout) => formatLayout(manifest, layout))));
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (sectionName === 'diagnostics') {
|
|
212
|
-
sections.push(printSection('Diagnostics', manifest.diagnostics.map((diagnostic) => formatDiagnostic(manifest, diagnostic))));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return sections.join('\n\n');
|
|
217
|
-
};
|
|
218
|
-
|
|
219
33
|
export const run = async (): Promise<void> => {
|
|
220
34
|
validateExplainArgs();
|
|
221
35
|
|
|
@@ -226,9 +40,9 @@ export const run = async (): Promise<void> => {
|
|
|
226
40
|
const selectedSections = getSelectedSections();
|
|
227
41
|
|
|
228
42
|
if (cli.args.json === true) {
|
|
229
|
-
console.log(JSON.stringify(
|
|
43
|
+
console.log(JSON.stringify(pickExplainManifestSections(manifest, selectedSections), null, 2));
|
|
230
44
|
return;
|
|
231
45
|
}
|
|
232
46
|
|
|
233
|
-
console.log(
|
|
47
|
+
console.log(renderExplainHuman(manifest, selectedSections));
|
|
234
48
|
};
|
package/cli/commands/trace.ts
CHANGED
|
@@ -118,6 +118,7 @@ const renderTraceSummary = (request: TRequestTraceListItem) =>
|
|
|
118
118
|
`status=${request.statusCode ?? 'pending'}`,
|
|
119
119
|
`capture=${request.capture}`,
|
|
120
120
|
`events=${request.eventCount}`,
|
|
121
|
+
`calls=${request.callCount}`,
|
|
121
122
|
request.user ? `user=${request.user}` : '',
|
|
122
123
|
request.errorMessage ? `error=${request.errorMessage}` : '',
|
|
123
124
|
]
|
|
@@ -131,6 +132,13 @@ const renderTrace = (request: TRequestTrace) =>
|
|
|
131
132
|
`- started=${request.startedAt} durationMs=${request.durationMs ?? 'pending'} events=${request.events.length} dropped=${request.droppedEvents}`,
|
|
132
133
|
...(request.user ? [`- user=${request.user}`] : []),
|
|
133
134
|
...(request.persistedFilepath ? [`- persisted=${request.persistedFilepath}`] : []),
|
|
135
|
+
'Calls',
|
|
136
|
+
...(request.calls.length === 0
|
|
137
|
+
? ['- none']
|
|
138
|
+
: request.calls.map(
|
|
139
|
+
(call) =>
|
|
140
|
+
`- ${call.origin} ${call.label} ${call.method} ${call.path} status=${call.statusCode ?? 'pending'} durationMs=${call.durationMs ?? 'pending'} req=${call.requestDataKeys.join(',')} res=${call.resultKeys.join(',')}`,
|
|
141
|
+
)),
|
|
134
142
|
'Events',
|
|
135
143
|
...request.events.map(
|
|
136
144
|
(event) =>
|