proteum 2.1.0 → 2.1.2
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 +44 -98
- package/README.md +143 -10
- package/agents/framework/AGENTS.md +146 -886
- package/agents/project/AGENTS.md +73 -127
- package/agents/project/client/AGENTS.md +22 -93
- package/agents/project/client/pages/AGENTS.md +24 -26
- package/agents/project/server/routes/AGENTS.md +10 -8
- package/agents/project/server/services/AGENTS.md +22 -159
- package/agents/project/tests/AGENTS.md +11 -8
- package/cli/app/config.ts +7 -20
- package/cli/bin.js +8 -0
- package/cli/commands/command.ts +243 -0
- package/cli/commands/commandLocalRunner.js +198 -0
- package/cli/commands/create.ts +5 -0
- package/cli/commands/deploy/web.ts +1 -2
- package/cli/commands/dev.ts +98 -2
- package/cli/commands/doctor.ts +8 -74
- package/cli/commands/explain.ts +8 -186
- package/cli/commands/init.ts +2 -94
- package/cli/commands/trace.ts +228 -0
- package/cli/compiler/artifacts/commands.ts +217 -0
- package/cli/compiler/artifacts/manifest.ts +35 -21
- package/cli/compiler/artifacts/services.ts +300 -1
- package/cli/compiler/client/index.ts +43 -8
- package/cli/compiler/common/commands.ts +175 -0
- package/cli/compiler/common/index.ts +1 -1
- package/cli/compiler/common/proteumManifest.ts +15 -114
- package/cli/compiler/index.ts +25 -2
- package/cli/compiler/server/index.ts +31 -6
- package/cli/index.ts +1 -4
- package/cli/paths.ts +16 -1
- package/cli/presentation/commands.ts +104 -14
- package/cli/presentation/devSession.ts +22 -3
- package/cli/presentation/proteum_logo_400x400_square_icon.txt +400 -0
- package/cli/runtime/commands.ts +121 -4
- package/cli/scaffold/index.ts +720 -0
- package/cli/scaffold/templates.ts +344 -0
- package/cli/scaffold/types.ts +26 -0
- package/cli/tsconfig.json +4 -1
- package/cli/utils/check.ts +1 -1
- package/client/app/component.tsx +13 -9
- package/client/dev/profiler/index.tsx +2511 -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 +27 -3
- 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 +92 -0
- package/common/dev/proteumManifest.ts +135 -0
- package/common/dev/requestTrace.ts +115 -0
- package/common/env/proteumEnv.ts +284 -0
- package/common/router/index.ts +4 -22
- package/docs/dev-commands.md +93 -0
- package/docs/diagnostics.md +88 -0
- package/docs/request-tracing.md +132 -0
- package/eslint.js +11 -6
- package/package.json +3 -3
- package/server/app/commands.ts +35 -370
- package/server/app/commandsManager.ts +393 -0
- package/server/app/container/config.ts +11 -49
- package/server/app/container/console/index.ts +2 -3
- package/server/app/container/index.ts +5 -2
- package/server/app/container/trace/index.ts +364 -0
- package/server/app/devCommands.ts +192 -0
- package/server/app/devDiagnostics.ts +53 -0
- package/server/app/index.ts +29 -6
- package/server/index.ts +0 -1
- package/server/services/auth/index.ts +525 -61
- package/server/services/auth/router/index.ts +106 -7
- 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 +66 -4
- package/server/services/router/http/index.ts +173 -6
- package/server/services/router/index.ts +200 -12
- package/server/services/router/request/api.ts +30 -1
- package/server/services/router/response/index.ts +83 -10
- package/server/services/router/response/page/document.tsx +16 -0
- package/server/services/router/response/page/index.tsx +27 -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/types/global/utils.d.ts +7 -14
- package/Rte.zip +0 -0
- package/agents/project/agents.md.zip +0 -0
- package/doc/TODO.md +0 -71
- package/doc/front/router.md +0 -27
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/server/services/router/http/session.ts.old +0 -40
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
|
|
5
|
+
export type TProteumEnvName = 'local' | 'server';
|
|
6
|
+
export type TProteumEnvProfile = 'dev' | 'testing' | 'prod';
|
|
7
|
+
export type TProteumTraceCapture = 'summary' | 'resolve' | 'deep';
|
|
8
|
+
export type TProteumRequiredEnvVariable = {
|
|
9
|
+
key: TProteumRequiredEnvVariableKey;
|
|
10
|
+
possibleValues: string[];
|
|
11
|
+
provided: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type TProteumEnvInspection = {
|
|
14
|
+
loadedVariableKeys: string[];
|
|
15
|
+
requiredVariables: TProteumRequiredEnvVariable[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TProteumEnvConfig = {
|
|
19
|
+
name: TProteumEnvName;
|
|
20
|
+
profile: TProteumEnvProfile;
|
|
21
|
+
router: {
|
|
22
|
+
port: number;
|
|
23
|
+
currentDomain: string;
|
|
24
|
+
};
|
|
25
|
+
trace: {
|
|
26
|
+
enable: boolean;
|
|
27
|
+
requestsLimit: number;
|
|
28
|
+
eventsLimit: number;
|
|
29
|
+
capture: TProteumTraceCapture;
|
|
30
|
+
persistOnError: boolean;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type TProteumLoadedEnvConfig = TProteumEnvConfig & { version: string };
|
|
35
|
+
|
|
36
|
+
const dotenvFileNames = ['.env'];
|
|
37
|
+
const requiredProteumEnvVariableKeys = ['ENV_NAME', 'ENV_PROFILE', 'PORT', 'URL'] as const;
|
|
38
|
+
const optionalProteumEnvVariablePrefixes = ['TRACE_'] as const;
|
|
39
|
+
|
|
40
|
+
export type TProteumRequiredEnvVariableKey = (typeof requiredProteumEnvVariableKeys)[number];
|
|
41
|
+
|
|
42
|
+
const requiredProteumEnvVariablePossibleValues: Record<TProteumRequiredEnvVariableKey, string[]> = {
|
|
43
|
+
ENV_NAME: ['local', 'server'],
|
|
44
|
+
ENV_PROFILE: ['dev', 'testing', 'prod'],
|
|
45
|
+
PORT: ['integer between 1 and 65535'],
|
|
46
|
+
URL: ['absolute URL'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const envDefinitionHint = (appDir: string) => `Define it in process.env or ${appDir}/.env.`;
|
|
50
|
+
const isProvidedEnvValue = (value: string | undefined) => typeof value === 'string' && value.trim() !== '';
|
|
51
|
+
|
|
52
|
+
const formatRequiredEnvVariableStatus = (variable: TProteumRequiredEnvVariable) =>
|
|
53
|
+
`- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`;
|
|
54
|
+
|
|
55
|
+
const createProteumEnvError = ({
|
|
56
|
+
appDir,
|
|
57
|
+
message,
|
|
58
|
+
}: {
|
|
59
|
+
appDir: string;
|
|
60
|
+
message: string;
|
|
61
|
+
}) => {
|
|
62
|
+
const inspection = inspectProteumEnv(appDir);
|
|
63
|
+
|
|
64
|
+
return new Error(
|
|
65
|
+
[message, envDefinitionHint(appDir), '', 'Required env variables:', ...inspection.requiredVariables.map(formatRequiredEnvVariableStatus)].join(
|
|
66
|
+
'\n',
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const parseBooleanEnvValue = ({
|
|
72
|
+
key,
|
|
73
|
+
value,
|
|
74
|
+
appDir,
|
|
75
|
+
}: {
|
|
76
|
+
key: string;
|
|
77
|
+
value: string | undefined;
|
|
78
|
+
appDir: string;
|
|
79
|
+
}) => {
|
|
80
|
+
if (value === undefined || value === '') return undefined;
|
|
81
|
+
|
|
82
|
+
const normalized = value.trim().toLowerCase();
|
|
83
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
84
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
85
|
+
|
|
86
|
+
throw createProteumEnvError({
|
|
87
|
+
appDir,
|
|
88
|
+
message: `Invalid boolean value for ${key}: "${value}". Expected one of: 1, 0, true, false, yes, no, on, off.`,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const parseIntegerEnvValue = ({
|
|
93
|
+
key,
|
|
94
|
+
value,
|
|
95
|
+
appDir,
|
|
96
|
+
min = 1,
|
|
97
|
+
}: {
|
|
98
|
+
key: string;
|
|
99
|
+
value: string;
|
|
100
|
+
appDir: string;
|
|
101
|
+
min?: number;
|
|
102
|
+
}) => {
|
|
103
|
+
const parsed = Number.parseInt(value, 10);
|
|
104
|
+
if (Number.isNaN(parsed) || parsed < min) {
|
|
105
|
+
throw createProteumEnvError({
|
|
106
|
+
appDir,
|
|
107
|
+
message: `Invalid integer value for ${key}: "${value}". Expected an integer greater than or equal to ${min}.`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return parsed;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const getRequiredEnvValue = ({ key, appDir }: { key: TProteumRequiredEnvVariableKey; appDir: string }) => {
|
|
115
|
+
const value = process.env[key]?.trim();
|
|
116
|
+
if (value) return value;
|
|
117
|
+
|
|
118
|
+
throw createProteumEnvError({
|
|
119
|
+
appDir,
|
|
120
|
+
message: `Missing required Proteum env variable "${key}".`,
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const parseEnvName = (value: string, appDir: string): TProteumEnvName => {
|
|
125
|
+
if (value === 'local' || value === 'server') return value;
|
|
126
|
+
throw createProteumEnvError({
|
|
127
|
+
appDir,
|
|
128
|
+
message: `Invalid ENV_NAME "${value}". Expected "local" or "server".`,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const parseEnvProfile = (value: string, appDir: string): TProteumEnvProfile => {
|
|
133
|
+
if (value === 'dev' || value === 'testing' || value === 'prod') return value;
|
|
134
|
+
throw createProteumEnvError({
|
|
135
|
+
appDir,
|
|
136
|
+
message: `Invalid ENV_PROFILE "${value}". Expected "dev", "testing", or "prod".`,
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const parseTraceCapture = ({
|
|
141
|
+
value,
|
|
142
|
+
appDir,
|
|
143
|
+
}: {
|
|
144
|
+
value: string | undefined;
|
|
145
|
+
appDir: string;
|
|
146
|
+
}): TProteumTraceCapture | undefined => {
|
|
147
|
+
if (value === undefined || value === '') return undefined;
|
|
148
|
+
if (value === 'summary' || value === 'resolve' || value === 'deep') return value;
|
|
149
|
+
|
|
150
|
+
throw createProteumEnvError({
|
|
151
|
+
appDir,
|
|
152
|
+
message: `Invalid TRACE_CAPTURE "${value}". Expected "summary", "resolve", or "deep".`,
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const parseAbsoluteUrl = ({
|
|
157
|
+
key,
|
|
158
|
+
value,
|
|
159
|
+
appDir,
|
|
160
|
+
}: {
|
|
161
|
+
key: string;
|
|
162
|
+
value: string;
|
|
163
|
+
appDir: string;
|
|
164
|
+
}) => {
|
|
165
|
+
let url: URL;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
url = new URL(value);
|
|
169
|
+
} catch {
|
|
170
|
+
throw createProteumEnvError({
|
|
171
|
+
appDir,
|
|
172
|
+
message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
177
|
+
throw createProteumEnvError({
|
|
178
|
+
appDir,
|
|
179
|
+
message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return value;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const loadOptionalProteumDotenv = (appDir: string) => {
|
|
187
|
+
for (const filename of dotenvFileNames) {
|
|
188
|
+
const filepath = path.join(appDir, filename);
|
|
189
|
+
if (!fs.existsSync(filepath)) continue;
|
|
190
|
+
dotenv.config({ path: filepath, quiet: true });
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const getLoadedProteumEnvVariableKeys = () =>
|
|
195
|
+
Object.keys(process.env)
|
|
196
|
+
.filter(
|
|
197
|
+
(key) =>
|
|
198
|
+
requiredProteumEnvVariableKeys.includes(key as TProteumRequiredEnvVariableKey) ||
|
|
199
|
+
optionalProteumEnvVariablePrefixes.some((prefix) => key.startsWith(prefix)),
|
|
200
|
+
)
|
|
201
|
+
.sort((a, b) => a.localeCompare(b));
|
|
202
|
+
|
|
203
|
+
export const inspectProteumEnv = (appDir: string): TProteumEnvInspection => {
|
|
204
|
+
loadOptionalProteumDotenv(appDir);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
loadedVariableKeys: getLoadedProteumEnvVariableKeys(),
|
|
208
|
+
requiredVariables: requiredProteumEnvVariableKeys.map((key) => ({
|
|
209
|
+
key,
|
|
210
|
+
possibleValues: [...requiredProteumEnvVariablePossibleValues[key]],
|
|
211
|
+
provided: isProvidedEnvValue(process.env[key]),
|
|
212
|
+
})),
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const parseProteumEnvConfig = ({
|
|
217
|
+
appDir,
|
|
218
|
+
routerPortOverride,
|
|
219
|
+
}: {
|
|
220
|
+
appDir: string;
|
|
221
|
+
routerPortOverride?: number;
|
|
222
|
+
}): TProteumEnvConfig => {
|
|
223
|
+
loadOptionalProteumDotenv(appDir);
|
|
224
|
+
|
|
225
|
+
const name = parseEnvName(getRequiredEnvValue({ key: 'ENV_NAME', appDir }), appDir);
|
|
226
|
+
const profile = parseEnvProfile(getRequiredEnvValue({ key: 'ENV_PROFILE', appDir }), appDir);
|
|
227
|
+
const configuredRouterPort = parseIntegerEnvValue({
|
|
228
|
+
key: 'PORT',
|
|
229
|
+
value: getRequiredEnvValue({ key: 'PORT', appDir }),
|
|
230
|
+
appDir,
|
|
231
|
+
});
|
|
232
|
+
const currentDomain = parseAbsoluteUrl({
|
|
233
|
+
key: 'URL',
|
|
234
|
+
value: getRequiredEnvValue({ key: 'URL', appDir }),
|
|
235
|
+
appDir,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const traceEnable = parseBooleanEnvValue({
|
|
239
|
+
key: 'TRACE_ENABLE',
|
|
240
|
+
value: process.env.TRACE_ENABLE,
|
|
241
|
+
appDir,
|
|
242
|
+
});
|
|
243
|
+
const tracePersistOnError = parseBooleanEnvValue({
|
|
244
|
+
key: 'TRACE_PERSIST_ON_ERROR',
|
|
245
|
+
value: process.env.TRACE_PERSIST_ON_ERROR,
|
|
246
|
+
appDir,
|
|
247
|
+
});
|
|
248
|
+
const traceRequestsLimit = process.env.TRACE_REQUESTS_LIMIT?.trim();
|
|
249
|
+
const traceEventsLimit = process.env.TRACE_EVENTS_LIMIT?.trim();
|
|
250
|
+
const traceCapture = parseTraceCapture({
|
|
251
|
+
value: process.env.TRACE_CAPTURE?.trim(),
|
|
252
|
+
appDir,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
name,
|
|
257
|
+
profile,
|
|
258
|
+
router: {
|
|
259
|
+
port: routerPortOverride === undefined ? configuredRouterPort : routerPortOverride,
|
|
260
|
+
currentDomain,
|
|
261
|
+
},
|
|
262
|
+
trace: {
|
|
263
|
+
enable: traceEnable ?? profile === 'dev',
|
|
264
|
+
requestsLimit:
|
|
265
|
+
traceRequestsLimit === undefined || traceRequestsLimit === ''
|
|
266
|
+
? 200
|
|
267
|
+
: parseIntegerEnvValue({
|
|
268
|
+
key: 'TRACE_REQUESTS_LIMIT',
|
|
269
|
+
value: traceRequestsLimit,
|
|
270
|
+
appDir,
|
|
271
|
+
}),
|
|
272
|
+
eventsLimit:
|
|
273
|
+
traceEventsLimit === undefined || traceEventsLimit === ''
|
|
274
|
+
? 800
|
|
275
|
+
: parseIntegerEnvValue({
|
|
276
|
+
key: 'TRACE_EVENTS_LIMIT',
|
|
277
|
+
value: traceEventsLimit,
|
|
278
|
+
appDir,
|
|
279
|
+
}),
|
|
280
|
+
capture: traceCapture ?? 'resolve',
|
|
281
|
+
persistOnError: tracePersistOnError ?? profile === 'dev',
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
};
|
package/common/router/index.ts
CHANGED
|
@@ -106,8 +106,6 @@ export type TRouteModule<TRegisteredRoute = any> = {
|
|
|
106
106
|
__register: TAppArrowFunction<TRegisteredRoute>;
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
export type TDomainsList = { [endpointId: string]: string } & { current: string };
|
|
110
|
-
|
|
111
109
|
export const defaultOptions: Pick<TRouteOptions, 'priority'> = { priority: 0 };
|
|
112
110
|
|
|
113
111
|
/*----------------------------------
|
|
@@ -116,31 +114,15 @@ export const defaultOptions: Pick<TRouteOptions, 'priority'> = { priority: 0 };
|
|
|
116
114
|
export const buildUrl = (
|
|
117
115
|
path: string,
|
|
118
116
|
params: { [key: string]: any },
|
|
119
|
-
|
|
117
|
+
currentDomain: string,
|
|
120
118
|
absolute: boolean,
|
|
121
119
|
) => {
|
|
122
120
|
let prefix: string = '';
|
|
123
121
|
|
|
124
122
|
// Relative to domain
|
|
125
|
-
if (path[0] === '/' && absolute) prefix =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Extract domain ID from path
|
|
129
|
-
let domainId: string;
|
|
130
|
-
let slackPos = path.indexOf('/');
|
|
131
|
-
if (slackPos === -1) slackPos = path.length;
|
|
132
|
-
domainId = path.substring(1, slackPos);
|
|
133
|
-
path = path.substring(slackPos);
|
|
134
|
-
|
|
135
|
-
// Get domain
|
|
136
|
-
const domain = domains[domainId];
|
|
137
|
-
if (domain === undefined) throw new Error('Unknown API endpoint ID: ' + domainId);
|
|
138
|
-
|
|
139
|
-
// Return full url
|
|
140
|
-
prefix = domain;
|
|
141
|
-
|
|
142
|
-
// Absolute URL
|
|
143
|
-
}
|
|
123
|
+
if (path[0] === '/' && absolute) prefix = currentDomain;
|
|
124
|
+
else if (path[0] === '@')
|
|
125
|
+
throw new Error(`Proteum no longer supports Router.url() domain aliases. Use a root-relative path or absolute URL instead: "${path}".`);
|
|
144
126
|
|
|
145
127
|
// Path parapeters
|
|
146
128
|
const searchParams = new URLSearchParams();
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
The profiler also exposes the shared diagnostics surfaces for humans:
|
|
72
|
+
|
|
73
|
+
- `Explain` renders the same manifest-backed data as `proteum explain`
|
|
74
|
+
- `Doctor` renders the same manifest diagnostics as `proteum doctor`
|
|
75
|
+
|
|
76
|
+
For the shared diagnostics contract and the corresponding dev HTTP endpoints, see [diagnostics.md](diagnostics.md).
|
|
77
|
+
|
|
78
|
+
### HTTP Endpoints
|
|
79
|
+
|
|
80
|
+
The CLI remote mode and the profiler use the same dev-only endpoints:
|
|
81
|
+
|
|
82
|
+
- `GET /__proteum/commands`
|
|
83
|
+
- `POST /__proteum/commands/run`
|
|
84
|
+
|
|
85
|
+
These endpoints exist only in dev mode and are not available in production.
|
|
86
|
+
|
|
87
|
+
### Built-In Command
|
|
88
|
+
|
|
89
|
+
Proteum ships one framework command by default:
|
|
90
|
+
|
|
91
|
+
- `proteum/diagnostics/ping`
|
|
92
|
+
|
|
93
|
+
It returns the current app identifier, active env profile, and discovered root services so every dev app has a real command surface available immediately.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Diagnostics and Explainability
|
|
2
|
+
|
|
3
|
+
Proteum exposes two manifest-backed diagnostics surfaces:
|
|
4
|
+
|
|
5
|
+
- `proteum explain`: inspect the generated app structure
|
|
6
|
+
- `proteum doctor`: inspect manifest diagnostics
|
|
7
|
+
|
|
8
|
+
These are not separate models for different tools. They share the same generated snapshot and the same diagnostics contract.
|
|
9
|
+
|
|
10
|
+
## Shared Contract
|
|
11
|
+
|
|
12
|
+
The canonical snapshot lives in `./.proteum/manifest.json`.
|
|
13
|
+
|
|
14
|
+
Proteum uses that same manifest in four places:
|
|
15
|
+
|
|
16
|
+
- `proteum explain` for human-readable and `--json` output
|
|
17
|
+
- `proteum doctor` for human-readable and `--json` output
|
|
18
|
+
- the dev-only `__proteum/explain` and `__proteum/doctor` HTTP endpoints
|
|
19
|
+
- the `Explain` and `Doctor` tabs in the bottom profiler during `proteum dev`
|
|
20
|
+
|
|
21
|
+
This means the CLI, the dev HTTP endpoints, and the profiler all describe the same manifest-backed snapshot.
|
|
22
|
+
|
|
23
|
+
If a command such as `proteum explain`, `proteum doctor`, or `proteum refresh` regenerates `.proteum/manifest.json`, the next CLI call, HTTP call, or profiler refresh will reflect that same updated snapshot.
|
|
24
|
+
|
|
25
|
+
## CLI
|
|
26
|
+
|
|
27
|
+
Common usage:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
proteum explain
|
|
31
|
+
proteum explain --routes --controllers --commands
|
|
32
|
+
proteum explain --all --json
|
|
33
|
+
|
|
34
|
+
proteum doctor
|
|
35
|
+
proteum doctor --json
|
|
36
|
+
proteum doctor --strict
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`proteum explain --json` emits the selected manifest sections as machine-readable JSON.
|
|
40
|
+
|
|
41
|
+
`proteum doctor --json` emits:
|
|
42
|
+
|
|
43
|
+
- `summary.errors`
|
|
44
|
+
- `summary.warnings`
|
|
45
|
+
- `summary.strictFailed`
|
|
46
|
+
- `diagnostics`
|
|
47
|
+
|
|
48
|
+
## Dev HTTP Endpoints
|
|
49
|
+
|
|
50
|
+
In `profile: dev`, the running app exposes:
|
|
51
|
+
|
|
52
|
+
- `GET /__proteum/explain`
|
|
53
|
+
- `GET /__proteum/doctor`
|
|
54
|
+
|
|
55
|
+
`/__proteum/explain` supports optional section selection:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
GET /__proteum/explain?sections=routes,controllers,commands
|
|
59
|
+
GET /__proteum/explain?section=env§ion=diagnostics
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`/__proteum/doctor` supports optional strict mode:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
GET /__proteum/doctor?strict=true
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
These endpoints are intended for local tooling and are not available in production.
|
|
69
|
+
|
|
70
|
+
## Profiler
|
|
71
|
+
|
|
72
|
+
During `proteum dev`, the bottom profiler is the human-facing UI over the same dev diagnostics surfaces.
|
|
73
|
+
|
|
74
|
+
- `Explain` calls `/__proteum/explain`
|
|
75
|
+
- `Doctor` calls `/__proteum/doctor`
|
|
76
|
+
- `Commands` uses the dev command endpoints
|
|
77
|
+
- `Auth`, `Timeline`, `Routing`, `Controller`, `SSR`, `API`, and related panels remain request-trace views
|
|
78
|
+
|
|
79
|
+
Use the profiler when a human needs to browse the same data that an agent or CLI command can already inspect directly.
|
|
80
|
+
|
|
81
|
+
## Agent Workflow
|
|
82
|
+
|
|
83
|
+
For AI coding agents or automation:
|
|
84
|
+
|
|
85
|
+
1. Read `./.proteum/manifest.json` or run `proteum explain --json`.
|
|
86
|
+
2. Run `proteum doctor --json` to inspect framework diagnostics.
|
|
87
|
+
3. For request-time behavior, use `proteum trace ...` because traces are live runtime data, not manifest snapshots.
|
|
88
|
+
4. Open the profiler only when a human-readable view helps; it should agree with the CLI after refresh.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Request Tracing
|
|
2
|
+
|
|
3
|
+
Proteum ships with a dev-only in-memory request trace buffer so routing, controller execution, SSR, and render behavior can be inspected without attaching a debugger or scattering temporary logs through the runtime.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- tracing is available only when the app runs with `profile: dev`
|
|
8
|
+
- traces are exposed through `proteum trace` and the dev-only `__proteum/trace` HTTP endpoints
|
|
9
|
+
- explain/doctor are separate manifest-backed diagnostics surfaces; see [diagnostics.md](diagnostics.md)
|
|
10
|
+
- production requests are not traced by this feature
|
|
11
|
+
|
|
12
|
+
## Main Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
proteum trace requests
|
|
16
|
+
proteum trace latest
|
|
17
|
+
proteum trace show <requestId>
|
|
18
|
+
proteum trace arm --capture deep
|
|
19
|
+
proteum trace export <requestId>
|
|
20
|
+
proteum trace latest --url http://127.0.0.1:3010
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Before reproducing a bug or starting a new test pass:
|
|
24
|
+
|
|
25
|
+
- read the default port from `PORT` or `./.proteum/manifest.json`
|
|
26
|
+
- check whether a dev server is already running on that port
|
|
27
|
+
- if it is, inspect `proteum trace requests`, `proteum trace latest`, and `proteum trace show <requestId>` first so you can capture past errors and their context
|
|
28
|
+
|
|
29
|
+
Typical debugging flow:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
proteum trace arm --capture deep --port 3103
|
|
33
|
+
# reproduce the failing request once
|
|
34
|
+
proteum trace requests --port 3103
|
|
35
|
+
proteum trace show <requestId> --port 3103
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `--url http://host:port` when the dev server is reachable on a non-standard host and `--port` is not enough.
|
|
39
|
+
|
|
40
|
+
## What Gets Recorded
|
|
41
|
+
|
|
42
|
+
Depending on capture mode, traces can include:
|
|
43
|
+
|
|
44
|
+
- request start, finish, user identity, status code, and duration
|
|
45
|
+
- auth decode input and outcome, route auth decisions, matched auth rules, rule inputs/results, and session create or clear events
|
|
46
|
+
- direct controller route matches
|
|
47
|
+
- route resolution start, match, and deep-mode skip reasons
|
|
48
|
+
- controller start and result shape
|
|
49
|
+
- created router/context keys
|
|
50
|
+
- setup output keys and page data summaries
|
|
51
|
+
- SSR payload shape and serialized byte size
|
|
52
|
+
- render start/end timings and document output sizes
|
|
53
|
+
- normalized request errors
|
|
54
|
+
|
|
55
|
+
## Capture Modes
|
|
56
|
+
|
|
57
|
+
- `summary`: smallest capture, focused on request lifecycle and high-signal events
|
|
58
|
+
- `resolve`: adds route matching, controller, setup, and context milestones
|
|
59
|
+
- `deep`: adds route skip reasons and deeper summarized payload inspection for one request
|
|
60
|
+
|
|
61
|
+
Use `deep` selectively. It is for one-off investigation, not continuous capture.
|
|
62
|
+
|
|
63
|
+
## Profiler
|
|
64
|
+
|
|
65
|
+
During `proteum dev`, the bottom profiler renders the same live request traces.
|
|
66
|
+
|
|
67
|
+
- `Timeline` shows the full request event stream
|
|
68
|
+
- `Auth` filters the selected session down to auth-specific events so matched rules, tracking, and allow/deny outcomes can be inspected without scanning unrelated events
|
|
69
|
+
- expanding an auth event shows the summarized detail payload exactly as stored in the trace
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
Set trace behavior with env vars:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export TRACE_ENABLE=true
|
|
77
|
+
export TRACE_REQUESTS_LIMIT=200
|
|
78
|
+
export TRACE_EVENTS_LIMIT=800
|
|
79
|
+
export TRACE_CAPTURE=resolve
|
|
80
|
+
export TRACE_PERSIST_ON_ERROR=true
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Notes:
|
|
84
|
+
|
|
85
|
+
- `enable` and `persistOnError` still remain dev-only in the current runtime
|
|
86
|
+
- `capture` defaults to `resolve`
|
|
87
|
+
- `requestsLimit` defaults to `200`
|
|
88
|
+
- `eventsLimit` defaults to `800`
|
|
89
|
+
- `proteum dev` removes auto-persisted crash traces from `var/traces/` when the dev session stops
|
|
90
|
+
- explicit `proteum trace export` files under `var/traces/exports/` are left in place
|
|
91
|
+
|
|
92
|
+
## Memory Model
|
|
93
|
+
|
|
94
|
+
Traces are kept in memory per Node process.
|
|
95
|
+
|
|
96
|
+
- requests are stored in a ring buffer capped by `requestsLimit`
|
|
97
|
+
- the oldest request traces are evicted first
|
|
98
|
+
- each request is capped by `eventsLimit`
|
|
99
|
+
- once the event cap is reached, extra events are dropped and counted in `droppedEvents`
|
|
100
|
+
- payloads are summarized rather than stored as raw objects
|
|
101
|
+
|
|
102
|
+
Current summarization rules:
|
|
103
|
+
|
|
104
|
+
- arrays keep at most 10 sampled items
|
|
105
|
+
- objects keep at most 20 keys per level
|
|
106
|
+
- deep capture stops at depth 3
|
|
107
|
+
- long strings are truncated
|
|
108
|
+
|
|
109
|
+
## Redaction
|
|
110
|
+
|
|
111
|
+
Sensitive values are redacted before they enter the trace store.
|
|
112
|
+
|
|
113
|
+
This includes keys such as:
|
|
114
|
+
|
|
115
|
+
- `cookie`
|
|
116
|
+
- `authorization`
|
|
117
|
+
- `password`
|
|
118
|
+
- token-like fields such as `accessToken`, `refreshToken`, `apiKey`, `jwt`, and similar names
|
|
119
|
+
- `rawBody`
|
|
120
|
+
|
|
121
|
+
The goal is to make traces useful for debugging without turning the dev server into a secret dump.
|
|
122
|
+
|
|
123
|
+
## Dev HTTP Endpoints
|
|
124
|
+
|
|
125
|
+
These endpoints back the CLI:
|
|
126
|
+
|
|
127
|
+
- `GET /__proteum/trace/requests`
|
|
128
|
+
- `GET /__proteum/trace/latest`
|
|
129
|
+
- `GET /__proteum/trace/requests/:id`
|
|
130
|
+
- `POST /__proteum/trace/arm`
|
|
131
|
+
|
|
132
|
+
The CLI should be the primary interface. Use the HTTP endpoints when you need direct machine access from another local dev tool.
|
package/eslint.js
CHANGED
|
@@ -11,8 +11,8 @@ const defaultIgnores = [
|
|
|
11
11
|
'**/var/**',
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
`
|
|
14
|
+
const createZodTypeFactorySelector = (factoryName) =>
|
|
15
|
+
`CallExpression[callee.type='MemberExpression'][callee.computed=false][callee.object.type='Identifier'][callee.object.name=/^(schema|z|zod)$/][callee.property.name='${factoryName}']`;
|
|
16
16
|
|
|
17
17
|
const createProteumEslintConfig = ({ ignores = [] } = {}) => [
|
|
18
18
|
{
|
|
@@ -42,15 +42,20 @@ const createProteumEslintConfig = ({ ignores = [] } = {}) => [
|
|
|
42
42
|
'jsx-a11y': jsxA11yPlugin,
|
|
43
43
|
},
|
|
44
44
|
rules: {
|
|
45
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
45
46
|
'no-restricted-syntax': [
|
|
46
47
|
'error',
|
|
47
48
|
{
|
|
48
|
-
selector:
|
|
49
|
-
message: 'Do not use
|
|
49
|
+
selector: 'TSUnknownKeyword',
|
|
50
|
+
message: 'Do not use `unknown`; define an explicit type instead.',
|
|
50
51
|
},
|
|
51
52
|
{
|
|
52
|
-
selector:
|
|
53
|
-
message: 'Do not use
|
|
53
|
+
selector: createZodTypeFactorySelector('any'),
|
|
54
|
+
message: 'Do not use Zod `any()` schemas; define an explicit schema instead.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
selector: createZodTypeFactorySelector('unknown'),
|
|
58
|
+
message: 'Do not use Zod `unknown()` schemas; define an explicit schema instead.',
|
|
54
59
|
},
|
|
55
60
|
],
|
|
56
61
|
},
|