proteum 2.1.0-5 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AGENTS.md +37 -49
  2. package/README.md +52 -1
  3. package/agents/framework/AGENTS.md +104 -236
  4. package/agents/project/AGENTS.md +36 -70
  5. package/cli/commands/command.ts +243 -0
  6. package/cli/commands/commandLocalRunner.js +198 -0
  7. package/cli/commands/dev.ts +95 -1
  8. package/cli/commands/doctor.ts +8 -74
  9. package/cli/commands/explain.ts +8 -194
  10. package/cli/commands/trace.ts +8 -0
  11. package/cli/compiler/artifacts/commands.ts +217 -0
  12. package/cli/compiler/artifacts/manifest.ts +17 -2
  13. package/cli/compiler/artifacts/services.ts +291 -0
  14. package/cli/compiler/client/index.ts +13 -0
  15. package/cli/compiler/common/commands.ts +175 -0
  16. package/cli/compiler/common/proteumManifest.ts +15 -124
  17. package/cli/compiler/index.ts +25 -2
  18. package/cli/compiler/server/index.ts +3 -0
  19. package/cli/presentation/commands.ts +37 -5
  20. package/cli/runtime/commands.ts +29 -1
  21. package/cli/tsconfig.json +4 -1
  22. package/cli/utils/check.ts +1 -1
  23. package/client/app/component.tsx +11 -0
  24. package/client/dev/profiler/index.tsx +1511 -0
  25. package/client/dev/profiler/noop.tsx +5 -0
  26. package/client/dev/profiler/runtime.noop.ts +116 -0
  27. package/client/dev/profiler/runtime.ts +840 -0
  28. package/client/services/router/components/router.tsx +30 -2
  29. package/client/services/router/index.tsx +25 -0
  30. package/client/services/router/request/api.ts +133 -17
  31. package/commands/proteum/diagnostics.ts +11 -0
  32. package/common/dev/commands.ts +50 -0
  33. package/common/dev/diagnostics.ts +298 -0
  34. package/common/dev/profiler.ts +91 -0
  35. package/common/dev/proteumManifest.ts +135 -0
  36. package/common/dev/requestTrace.ts +28 -1
  37. package/docs/dev-commands.md +86 -0
  38. package/docs/request-tracing.md +2 -0
  39. package/package.json +1 -2
  40. package/server/app/commands.ts +35 -370
  41. package/server/app/commandsManager.ts +393 -0
  42. package/server/app/container/console/index.ts +0 -2
  43. package/server/app/container/trace/index.ts +88 -8
  44. package/server/app/devCommands.ts +192 -0
  45. package/server/app/devDiagnostics.ts +53 -0
  46. package/server/app/index.ts +27 -4
  47. package/server/services/cron/CronTask.ts +73 -5
  48. package/server/services/cron/index.ts +34 -11
  49. package/server/services/fetch/index.ts +3 -10
  50. package/server/services/prisma/index.ts +1 -1
  51. package/server/services/router/http/index.ts +132 -21
  52. package/server/services/router/index.ts +40 -4
  53. package/server/services/router/request/api.ts +30 -1
  54. package/skills/clean-project-code/SKILL.md +7 -2
  55. package/test-results/.last-run.json +4 -0
  56. package/types/aliases.d.ts +6 -0
@@ -0,0 +1,393 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ import { Cli, Command as ClipanionCommand, Option, UsageError } from 'clipanion';
6
+
7
+ import type { Application } from './index';
8
+ import Service from '@server/app/service';
9
+ import { InputError, NotFound } from '@common/errors';
10
+
11
+ /*----------------------------------
12
+ - TYPES
13
+ ----------------------------------*/
14
+
15
+ type CommandCallback<TArgs extends any[]> = (...args: TArgs) => Promise<any>;
16
+
17
+ export type CommandsList = { [commandName: string]: RuntimeCommand };
18
+
19
+ export type RuntimeCommand<TArgs extends any[] = any[]> = {
20
+ name: string;
21
+ description: string;
22
+ run?: CommandCallback<TArgs>;
23
+ childrens: CommandsList;
24
+ };
25
+
26
+ /*----------------------------------
27
+ - SERVICE TYPES
28
+ ----------------------------------*/
29
+
30
+ const LogPrefix = `[commands]`;
31
+
32
+ export type Config = { debug: boolean };
33
+
34
+ export type Hooks = {};
35
+
36
+ type TCommandArgumentValue = string | number | boolean;
37
+ type TParsedCommandArgs = { [key: string]: TCommandArgumentValue | TCommandArgumentValue[] };
38
+
39
+ const commandValuePattern = /^-?(?:\d+|\d*\.\d+)$/;
40
+
41
+ const tokenizeCommandString = (commandString: string) => {
42
+ const tokens: string[] = [];
43
+ let currentToken = '';
44
+ let quote: '"' | "'" | undefined;
45
+ let escaping = false;
46
+
47
+ for (const character of commandString) {
48
+ if (escaping) {
49
+ currentToken += character;
50
+ escaping = false;
51
+ continue;
52
+ }
53
+
54
+ if (character === '\\') {
55
+ escaping = true;
56
+ continue;
57
+ }
58
+
59
+ if (quote) {
60
+ if (character === quote) {
61
+ quote = undefined;
62
+ continue;
63
+ }
64
+
65
+ currentToken += character;
66
+ continue;
67
+ }
68
+
69
+ if (character === '"' || character === "'") {
70
+ quote = character;
71
+ continue;
72
+ }
73
+
74
+ if (/\s/.test(character)) {
75
+ if (!currentToken) continue;
76
+
77
+ tokens.push(currentToken);
78
+ currentToken = '';
79
+ continue;
80
+ }
81
+
82
+ currentToken += character;
83
+ }
84
+
85
+ if (escaping) currentToken += '\\';
86
+ if (currentToken) tokens.push(currentToken);
87
+
88
+ return tokens;
89
+ };
90
+
91
+ const isOptionToken = (token: string) => /^-(?!\d+(\.\d+)?$).+/.test(token);
92
+
93
+ const normalizeCommandValue = (value: string): TCommandArgumentValue => {
94
+ if (value === 'true') return true;
95
+ if (value === 'false') return false;
96
+ if (commandValuePattern.test(value)) return Number(value);
97
+
98
+ return value;
99
+ };
100
+
101
+ const addParsedArgValue = (args: TParsedCommandArgs, key: string, value: TCommandArgumentValue) => {
102
+ const existingValue = args[key];
103
+
104
+ if (existingValue === undefined) {
105
+ args[key] = value;
106
+ return;
107
+ }
108
+
109
+ args[key] = Array.isArray(existingValue) ? [...existingValue, value] : [existingValue, value];
110
+ };
111
+
112
+ const parseCommandOptionTokens = (tokens: string[]) => {
113
+ const namedArguments: TParsedCommandArgs = {};
114
+ const positionalArguments: string[] = [];
115
+
116
+ let usePositionalOnly = false;
117
+
118
+ for (let index = 0; index < tokens.length; index++) {
119
+ const token = tokens[index];
120
+
121
+ if (usePositionalOnly) {
122
+ positionalArguments.push(token);
123
+ continue;
124
+ }
125
+
126
+ if (token === '--') {
127
+ usePositionalOnly = true;
128
+ continue;
129
+ }
130
+
131
+ if (token === '--help' || token === '-h') return { help: true as const, args: namedArguments, positionalArguments };
132
+
133
+ if (token.startsWith('--') && token.length > 2) {
134
+ const body = token.slice(2);
135
+
136
+ if (body.startsWith('no-') && body.length > 3) {
137
+ addParsedArgValue(namedArguments, body.slice(3), false);
138
+ continue;
139
+ }
140
+
141
+ const equalsIndex = body.indexOf('=');
142
+
143
+ if (equalsIndex >= 0) {
144
+ addParsedArgValue(
145
+ namedArguments,
146
+ body.slice(0, equalsIndex),
147
+ normalizeCommandValue(body.slice(equalsIndex + 1)),
148
+ );
149
+ continue;
150
+ }
151
+
152
+ const nextToken = tokens[index + 1];
153
+
154
+ if (nextToken !== undefined && !isOptionToken(nextToken)) {
155
+ addParsedArgValue(namedArguments, body, normalizeCommandValue(nextToken));
156
+ index++;
157
+ continue;
158
+ }
159
+
160
+ addParsedArgValue(namedArguments, body, true);
161
+ continue;
162
+ }
163
+
164
+ if (token.startsWith('-') && token.length > 1 && !commandValuePattern.test(token)) {
165
+ const body = token.slice(1);
166
+ const equalsIndex = body.indexOf('=');
167
+
168
+ if (equalsIndex >= 0) {
169
+ addParsedArgValue(
170
+ namedArguments,
171
+ body.slice(0, equalsIndex),
172
+ normalizeCommandValue(body.slice(equalsIndex + 1)),
173
+ );
174
+ continue;
175
+ }
176
+
177
+ const nextToken = tokens[index + 1];
178
+
179
+ if (body.length === 1 && nextToken !== undefined && !isOptionToken(nextToken)) {
180
+ addParsedArgValue(namedArguments, body, normalizeCommandValue(nextToken));
181
+ index++;
182
+ continue;
183
+ }
184
+
185
+ for (const shortFlag of body) addParsedArgValue(namedArguments, shortFlag, true);
186
+
187
+ continue;
188
+ }
189
+
190
+ positionalArguments.push(token);
191
+ }
192
+
193
+ return { help: false as const, args: namedArguments, positionalArguments };
194
+ };
195
+
196
+ /*----------------------------------
197
+ - SERVICE
198
+ ----------------------------------*/
199
+
200
+ export default class CommandsManager extends Service<Config, Hooks, Application> {
201
+ public priority = 2 as 2;
202
+
203
+ public commandsIndex: CommandsList = {};
204
+
205
+ private runtimeCli?: Cli;
206
+
207
+ public command<TArgs extends any[]>(
208
+ ...args:
209
+ | [name: string, description: string, childrens: RuntimeCommand[]]
210
+ | [name: string, description: string, run: CommandCallback<TArgs>, childrens?: RuntimeCommand[]]
211
+ ): RuntimeCommand {
212
+ let name: string, description: string;
213
+ let childrens: RuntimeCommand[] | undefined;
214
+ let run: CommandCallback<TArgs> | undefined;
215
+
216
+ if (typeof args[2] === 'object') [name, description, childrens] = args;
217
+ else [name, description, run, childrens] = args;
218
+
219
+ const command: RuntimeCommand = { name, description, run, childrens: childrens ? this.indexFromList(childrens) : {} };
220
+
221
+ return command;
222
+ }
223
+
224
+ private indexFromList(list: RuntimeCommand[]): CommandsList {
225
+ const index: CommandsList = {};
226
+ for (const command of list) index[command.name] = command;
227
+
228
+ return index;
229
+ }
230
+
231
+ private invalidateRuntimeCli() {
232
+ this.runtimeCli = undefined;
233
+ }
234
+
235
+ private createRuntimeCommandClass(command: RuntimeCommand, path: string[]) {
236
+ const manager = this;
237
+ const usage = ClipanionCommand.Usage({ description: command.description });
238
+
239
+ if (command.run === undefined) {
240
+ class RuntimeNamespaceCommand extends ClipanionCommand {
241
+ public static override paths = [path];
242
+ public static override usage = usage;
243
+
244
+ public async execute() {
245
+ throw new NotFound(`This command isn't runnable.`);
246
+ }
247
+ }
248
+
249
+ Object.defineProperty(RuntimeNamespaceCommand, 'name', {
250
+ value: `${path.map((segment) => segment.replace(/[^A-Za-z0-9]/g, '_')).join('_') || 'Root'}NamespaceCommand`,
251
+ });
252
+
253
+ return RuntimeNamespaceCommand;
254
+ }
255
+
256
+ class RuntimeRunnableCommand extends ClipanionCommand {
257
+ public static override paths = [path];
258
+ public static override usage = usage;
259
+
260
+ public proxy = Option.Proxy({ name: 'args' });
261
+
262
+ public async execute() {
263
+ return manager.executeRegisteredCommand(command, path, this.proxy);
264
+ }
265
+ }
266
+
267
+ Object.defineProperty(RuntimeRunnableCommand, 'name', {
268
+ value: `${path.map((segment) => segment.replace(/[^A-Za-z0-9]/g, '_')).join('_') || 'Root'}RunnableCommand`,
269
+ });
270
+
271
+ return RuntimeRunnableCommand;
272
+ }
273
+
274
+ private createRuntimeCli() {
275
+ const cli = new Cli({
276
+ binaryName: this.app.identity.identifier || 'app',
277
+ enableCapture: false,
278
+ });
279
+
280
+ const registerCommands = (commands: CommandsList, parentPath: string[] = []) => {
281
+ for (const command of Object.values(commands)) {
282
+ const path = [...parentPath, command.name];
283
+
284
+ cli.register(this.createRuntimeCommandClass(command, path));
285
+
286
+ if (Object.keys(command.childrens).length > 0) registerCommands(command.childrens, path);
287
+ }
288
+ };
289
+
290
+ registerCommands(this.commandsIndex);
291
+
292
+ return cli;
293
+ }
294
+
295
+ private getRuntimeCli() {
296
+ this.runtimeCli ??= this.createRuntimeCli();
297
+
298
+ return this.runtimeCli;
299
+ }
300
+
301
+ private async executeRegisteredCommand(command: RuntimeCommand, path: string[], proxyTokens: string[]) {
302
+ if (command.run === undefined) throw new NotFound(`This command isn't runnable.`);
303
+
304
+ const { help, args, positionalArguments } = parseCommandOptionTokens(proxyTokens);
305
+
306
+ this.config.debug &&
307
+ console.log(LogPrefix, `Run command path: ${path.join(' ')} | Parsed proxy tokens:`, {
308
+ proxyTokens,
309
+ args,
310
+ positionalArguments,
311
+ });
312
+
313
+ if (help) {
314
+ const cli = this.getRuntimeCli();
315
+ const runtimeCommand = cli.process(path, Cli.defaultContext);
316
+
317
+ return cli.usage(runtimeCommand, { detailed: true });
318
+ }
319
+
320
+ if (positionalArguments.length > 0) {
321
+ throw new UsageError(
322
+ `Unexpected positional arguments for "${path.join(' ')}": ${positionalArguments.join(', ')}.`,
323
+ );
324
+ }
325
+
326
+ const argsList = Object.values(args);
327
+
328
+ return command.run(...(argsList as Parameters<NonNullable<typeof command.run>>));
329
+ }
330
+
331
+ private createRuntimeCliApi(cli: Cli) {
332
+ return {
333
+ binaryLabel: cli.binaryLabel,
334
+ binaryName: cli.binaryName,
335
+ binaryVersion: cli.binaryVersion,
336
+ enableCapture: cli.enableCapture,
337
+ enableColors: cli.enableColors,
338
+ definitions: () => cli.definitions(),
339
+ definition: (commandClass: any) => cli.definition(commandClass),
340
+ error: (error: Error, opts?: any) => cli.error(error, opts),
341
+ format: (colored?: boolean) => cli.format(colored),
342
+ process: (input: string[]) => cli.process(input, Cli.defaultContext),
343
+ run: (input: string[]) => cli.run(input, Cli.defaultContext),
344
+ usage: (command?: any, opts?: any) => cli.usage(command, opts),
345
+ };
346
+ }
347
+
348
+ /*----------------------------------
349
+ - REGISTER
350
+ ----------------------------------*/
351
+ public fromList(list: RuntimeCommand[]) {
352
+ for (const command of list) {
353
+ if (this.commandsIndex[command.name] !== undefined)
354
+ throw new Error(`Tried to register command "${command.name}", but it already has been defined.`);
355
+
356
+ this.commandsIndex[command.name] = command;
357
+ }
358
+
359
+ this.invalidateRuntimeCli();
360
+ }
361
+
362
+ /*----------------------------------
363
+ - RUN
364
+ ----------------------------------*/
365
+ public async run(commandString: string) {
366
+ const tokens = tokenizeCommandString(commandString);
367
+
368
+ this.config.debug && console.log(LogPrefix, `Run command: ${commandString} | Tokens:`, tokens);
369
+
370
+ if (tokens.length === 0) throw new NotFound(`Command not found.`);
371
+
372
+ const cli = this.getRuntimeCli();
373
+
374
+ try {
375
+ const command = cli.process(tokens, Cli.defaultContext);
376
+
377
+ if (command.help) return cli.usage(command, { detailed: true });
378
+
379
+ command.context = Cli.defaultContext;
380
+ command.cli = this.createRuntimeCliApi(cli);
381
+
382
+ return await command.validateAndExecute();
383
+ } catch (error) {
384
+ if (error instanceof UsageError) throw new InputError(error.message);
385
+
386
+ if (error instanceof Error && ['UnknownSyntaxError', 'AmbiguousSyntaxError'].includes(error.name)) {
387
+ throw new NotFound(error.message);
388
+ }
389
+
390
+ throw error;
391
+ }
392
+ }
393
+ }
@@ -135,8 +135,6 @@ export default class Console {
135
135
  private container: typeof ApplicationContainer,
136
136
  private config: Config,
137
137
  ) {
138
- console.log('Setting up Console shell module.');
139
-
140
138
  const origLog = console.log;
141
139
 
142
140
  const Env = container.Environment;
@@ -5,6 +5,8 @@ import type ApplicationContainer from '..';
5
5
  import {
6
6
  traceCaptureModes,
7
7
  type TTraceCaptureMode,
8
+ type TTraceCall,
9
+ type TTraceCallOrigin,
8
10
  type TTraceEvent,
9
11
  type TTraceEventType,
10
12
  type TTraceSummaryValue,
@@ -53,12 +55,11 @@ const summarizeValue = (
53
55
  if (value === undefined) return { kind: 'undefined' };
54
56
  if (value === null) return null;
55
57
 
56
- const primitiveType = typeof value;
57
- if (primitiveType === 'string') return summarizeString(value);
58
- if (primitiveType === 'number' || primitiveType === 'boolean') return value;
59
- if (primitiveType === 'bigint') return { kind: 'bigint', value: value.toString() };
60
- if (primitiveType === 'symbol') return { kind: 'symbol', value: value.toString() };
61
- if (primitiveType === 'function') return { kind: 'function', name: value.name || 'anonymous' };
58
+ if (typeof value === 'string') return summarizeString(value);
59
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
60
+ if (typeof value === 'bigint') return { kind: 'bigint', value: value.toString() };
61
+ if (typeof value === 'symbol') return { kind: 'symbol', value: value.toString() };
62
+ if (typeof value === 'function') return { kind: 'function', name: value.name || 'anonymous' };
62
63
 
63
64
  if (value instanceof Date) return { kind: 'date', value: value.toISOString() };
64
65
  if (value instanceof Error) return summarizeError(value);
@@ -113,6 +114,9 @@ const summarizeDetails = (details: TTraceDetails, capture: TTraceCaptureMode) =>
113
114
  return summarized;
114
115
  };
115
116
 
117
+ const summarizeCaptureValue = (value: TTraceInspectable, capture: TTraceCaptureMode, key: string) =>
118
+ summarizeValue(value, capture === 'deep' ? 3 : 1, new WeakSet<object>(), [key]);
119
+
116
120
  const nowIso = () => new Date().toISOString();
117
121
 
118
122
  export default class Trace {
@@ -126,7 +130,7 @@ export default class Trace {
126
130
  ) {}
127
131
 
128
132
  public isEnabled() {
129
- return this.config.enable && this.container.Environment.profile === 'dev';
133
+ return __DEV__ && this.config.enable && this.container.Environment.profile === 'dev';
130
134
  }
131
135
 
132
136
  public armNextRequest(capture: string) {
@@ -138,7 +142,17 @@ export default class Trace {
138
142
  return capture;
139
143
  }
140
144
 
141
- public startRequest(input: { id: string; method: string; path: string; url: string; headers: object; data: object }) {
145
+ public startRequest(input: {
146
+ id: string;
147
+ method: string;
148
+ path: string;
149
+ url: string;
150
+ headers: object;
151
+ data: object;
152
+ profilerSessionId?: string;
153
+ profilerOrigin?: string;
154
+ profilerParentRequestId?: string;
155
+ }) {
142
156
  if (!this.isEnabled()) return;
143
157
 
144
158
  const capture = this.armedCapture ?? this.config.capture;
@@ -150,8 +164,12 @@ export default class Trace {
150
164
  path: input.path,
151
165
  url: input.url,
152
166
  capture,
167
+ profilerSessionId: input.profilerSessionId,
168
+ profilerOrigin: input.profilerOrigin,
169
+ profilerParentRequestId: input.profilerParentRequestId,
153
170
  startedAt: nowIso(),
154
171
  droppedEvents: 0,
172
+ calls: [],
155
173
  events: [],
156
174
  };
157
175
 
@@ -224,6 +242,64 @@ export default class Trace {
224
242
  }
225
243
  }
226
244
 
245
+ public startCall(
246
+ requestId: string,
247
+ input: {
248
+ origin: TTraceCallOrigin;
249
+ label: string;
250
+ method?: string;
251
+ path?: string;
252
+ fetcherId?: string;
253
+ parentId?: string;
254
+ requestDataKeys?: string[];
255
+ requestData?: TTraceInspectable;
256
+ },
257
+ ) {
258
+ const trace = this.requests.get(requestId);
259
+ if (!trace) return undefined;
260
+
261
+ const call: TTraceCall = {
262
+ id: `${requestId}:call:${trace.calls.length}`,
263
+ parentId: input.parentId,
264
+ origin: input.origin,
265
+ label: input.label,
266
+ method: input.method || '',
267
+ path: input.path || '',
268
+ fetcherId: input.fetcherId,
269
+ startedAt: nowIso(),
270
+ requestDataKeys: input.requestDataKeys || [],
271
+ requestData: input.requestData !== undefined ? summarizeCaptureValue(input.requestData, trace.capture, 'requestData') : undefined,
272
+ resultKeys: [],
273
+ };
274
+
275
+ trace.calls.push(call);
276
+ return call.id;
277
+ }
278
+
279
+ public finishCall(
280
+ requestId: string,
281
+ callId: string | undefined,
282
+ output: {
283
+ statusCode?: number;
284
+ errorMessage?: string;
285
+ resultKeys?: string[];
286
+ result?: TTraceInspectable;
287
+ } = {},
288
+ ) {
289
+ if (!callId) return;
290
+
291
+ const trace = this.requests.get(requestId);
292
+ const call = trace?.calls.find((candidate) => candidate.id === callId);
293
+ if (!trace || !call) return;
294
+
295
+ call.finishedAt = nowIso();
296
+ call.durationMs = Math.max(0, Date.parse(call.finishedAt) - Date.parse(call.startedAt));
297
+ call.statusCode = output.statusCode;
298
+ call.errorMessage = output.errorMessage;
299
+ call.resultKeys = output.resultKeys || [];
300
+ call.result = output.result !== undefined ? summarizeCaptureValue(output.result, trace.capture, 'result') : undefined;
301
+ }
302
+
227
303
  public listRequests(limit = 20): TRequestTraceListItem[] {
228
304
  return [...this.order]
229
305
  .reverse()
@@ -244,7 +320,11 @@ export default class Trace {
244
320
  droppedEvents: trace.droppedEvents,
245
321
  persistedFilepath: trace.persistedFilepath,
246
322
  errorMessage: trace.errorMessage,
323
+ profilerSessionId: trace.profilerSessionId,
324
+ profilerOrigin: trace.profilerOrigin,
325
+ profilerParentRequestId: trace.profilerParentRequestId,
247
326
  eventCount: trace.events.length,
327
+ callCount: trace.calls.length,
248
328
  }));
249
329
  }
250
330