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,192 @@
1
+ import type { Application } from './index';
2
+ import type { Commands } from './commands';
3
+ import { normalizeDevCommandPath, type TDevCommandDefinition, type TDevCommandExecution } from '@common/dev/commands';
4
+ import type { TTraceSummaryValue } from '@common/dev/requestTrace';
5
+ import { NotFound } from '@common/errors';
6
+
7
+ export type TGeneratedCommandDefinition = TDevCommandDefinition & {
8
+ Command: new (app: Application) => Commands<any>;
9
+ };
10
+
11
+ type TSerializableValue = object | PrimitiveValue | bigint | symbol | null | undefined | (() => void);
12
+
13
+ const maxSummaryStringLength = 240;
14
+ const sensitiveKeyPattern =
15
+ /(^|\.)(authorization|cookie|set-cookie|password|pass|pwd|secret|token|refreshToken|accessToken|apiKey|apiSecret|secretAccessKey|accessKeyId|privateKey|session|jwt|rawBody)$/i;
16
+
17
+ const nowIso = () => new Date().toISOString();
18
+ const getDurationMs = (startedAt: string, finishedAt: string) => Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
19
+ const isSensitiveKeyPath = (keyPath: string[]) => sensitiveKeyPattern.test(keyPath.join('.'));
20
+ const summarizeString = (value: string) =>
21
+ value.length <= maxSummaryStringLength ? value : `${value.slice(0, maxSummaryStringLength)}…`;
22
+
23
+ const summarizeError = (error: Error): TTraceSummaryValue => ({
24
+ kind: 'error',
25
+ name: error.name,
26
+ message: error.message,
27
+ stack: error.stack?.split('\n').slice(0, 5).join('\n'),
28
+ });
29
+
30
+ const summarizeValue = (
31
+ value: TSerializableValue,
32
+ depth: number,
33
+ seen: WeakSet<object>,
34
+ keyPath: string[],
35
+ ): TTraceSummaryValue => {
36
+ if (isSensitiveKeyPath(keyPath)) return { kind: 'redacted', reason: `Sensitive key ${keyPath[keyPath.length - 1] || 'value'}` };
37
+ if (value === undefined) return { kind: 'undefined' };
38
+ if (value === null) return null;
39
+
40
+ if (typeof value === 'string') return summarizeString(value);
41
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
42
+ if (typeof value === 'bigint') return { kind: 'bigint', value: value.toString() };
43
+ if (typeof value === 'symbol') return { kind: 'symbol', value: value.toString() };
44
+ if (typeof value === 'function') return { kind: 'function', name: value.name || 'anonymous' };
45
+
46
+ if (value instanceof Date) return { kind: 'date', value: value.toISOString() };
47
+ if (value instanceof Error) return summarizeError(value);
48
+ if (Buffer.isBuffer(value)) return { kind: 'buffer', byteLength: value.byteLength };
49
+ if (value instanceof Map) return { kind: 'map', size: value.size };
50
+ if (value instanceof Set) return { kind: 'set', size: value.size };
51
+
52
+ if (seen.has(value)) {
53
+ return {
54
+ kind: 'object',
55
+ constructorName: value.constructor?.name || 'Object',
56
+ keys: [],
57
+ entries: {},
58
+ truncated: true,
59
+ };
60
+ }
61
+
62
+ seen.add(value);
63
+
64
+ if (Array.isArray(value)) {
65
+ if (depth <= 0) return { kind: 'array', length: value.length, items: [], truncated: value.length > 0 };
66
+
67
+ const items = value
68
+ .slice(0, 10)
69
+ .map((item, index) => summarizeValue(item as TSerializableValue, depth - 1, seen, [...keyPath, `[${index}]`]));
70
+
71
+ return { kind: 'array', length: value.length, items, truncated: value.length > items.length };
72
+ }
73
+
74
+ const constructorName = value.constructor?.name || 'Object';
75
+ const keys = Object.keys(value);
76
+ if (depth <= 0) {
77
+ return { kind: 'object', constructorName, keys, entries: {}, truncated: keys.length > 0 };
78
+ }
79
+
80
+ const entries: { [key: string]: TTraceSummaryValue } = {};
81
+ for (const key of keys.slice(0, 20)) {
82
+ const record = value as Record<string, TSerializableValue>;
83
+ entries[key] = summarizeValue(record[key], depth - 1, seen, [...keyPath, key]);
84
+ }
85
+
86
+ return { kind: 'object', constructorName, keys, entries, truncated: keys.length > Object.keys(entries).length };
87
+ };
88
+
89
+ const serializeJsonResult = (value: unknown) => {
90
+ if (value === undefined) return undefined;
91
+
92
+ try {
93
+ return JSON.parse(JSON.stringify(value)) as unknown;
94
+ } catch {
95
+ return undefined;
96
+ }
97
+ };
98
+
99
+ export class DevCommandExecutionError extends Error {
100
+ public constructor(
101
+ message: string,
102
+ public execution: TDevCommandExecution,
103
+ public cause?: unknown,
104
+ ) {
105
+ super(message);
106
+ this.name = 'DevCommandExecutionError';
107
+ }
108
+ }
109
+
110
+ const loadGeneratedCommandDefinitions = () =>
111
+ (((require('@generated/server/commands') as { default?: TGeneratedCommandDefinition[] }).default || []) as TGeneratedCommandDefinition[]).sort(
112
+ (a, b) => a.path.localeCompare(b.path),
113
+ );
114
+
115
+ export default class DevCommandsRegistry<TApplication extends Application = Application> {
116
+ private definitions = loadGeneratedCommandDefinitions();
117
+
118
+ public constructor(private app: TApplication) {}
119
+
120
+ public list() {
121
+ return this.definitions.map((definition) => ({
122
+ path: definition.path,
123
+ className: definition.className,
124
+ methodName: definition.methodName,
125
+ importPath: definition.importPath,
126
+ filepath: definition.filepath,
127
+ sourceLocation: definition.sourceLocation,
128
+ scope: definition.scope,
129
+ }));
130
+ }
131
+
132
+ private getDefinition(commandPath: string) {
133
+ const normalizedPath = normalizeDevCommandPath(commandPath);
134
+ const matchingDefinitions = this.definitions.filter((definition) => definition.path === normalizedPath);
135
+
136
+ if (matchingDefinitions.length === 0) {
137
+ throw new NotFound(`Command "${normalizedPath}" was not found.`);
138
+ }
139
+
140
+ if (matchingDefinitions.length > 1) {
141
+ throw new Error(`Command "${normalizedPath}" is ambiguous because it is registered more than once.`);
142
+ }
143
+
144
+ return matchingDefinitions[0];
145
+ }
146
+
147
+ public async run(commandPath: string): Promise<TDevCommandExecution> {
148
+ const definition = this.getDefinition(commandPath);
149
+ const startedAt = nowIso();
150
+
151
+ try {
152
+ const instance = new definition.Command(this.app);
153
+ const method = (instance as Record<string, unknown>)[definition.methodName];
154
+
155
+ if (typeof method !== 'function') {
156
+ throw new Error(
157
+ `Command "${definition.path}" could not be executed because ${definition.className}.${definition.methodName} is not callable.`,
158
+ );
159
+ }
160
+
161
+ const value = await method.call(instance);
162
+ const finishedAt = nowIso();
163
+
164
+ return {
165
+ command: this.list().find((command) => command.path === definition.path) || definition,
166
+ startedAt,
167
+ finishedAt,
168
+ durationMs: getDurationMs(startedAt, finishedAt),
169
+ status: 'completed',
170
+ result:
171
+ value === undefined
172
+ ? undefined
173
+ : {
174
+ json: serializeJsonResult(value),
175
+ summary: summarizeValue(value as TSerializableValue, 3, new WeakSet<object>(), ['result']),
176
+ },
177
+ };
178
+ } catch (error) {
179
+ const finishedAt = nowIso();
180
+ const execution: TDevCommandExecution = {
181
+ command: this.list().find((command) => command.path === definition.path) || definition,
182
+ startedAt,
183
+ finishedAt,
184
+ durationMs: getDurationMs(startedAt, finishedAt),
185
+ status: 'error',
186
+ errorMessage: error instanceof Error ? error.message : String(error),
187
+ };
188
+
189
+ throw new DevCommandExecutionError(execution.errorMessage || `Command "${definition.path}" failed.`, execution, error);
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ import type { Application } from './index';
5
+ import {
6
+ buildDoctorResponse,
7
+ explainSectionNames,
8
+ pickExplainManifestSections,
9
+ type TDoctorResponse,
10
+ type TExplainSectionName,
11
+ } from '@common/dev/diagnostics';
12
+ import type { TProteumManifest } from '@common/dev/proteumManifest';
13
+
14
+ const isExplainSectionName = (value: string): value is TExplainSectionName =>
15
+ explainSectionNames.includes(value as TExplainSectionName);
16
+
17
+ export default class DevDiagnosticsRegistry<TApplication extends Application = Application> {
18
+ public constructor(private app: TApplication) {}
19
+
20
+ private getManifestFilepath() {
21
+ return path.join(this.app.container.path.root, '.proteum', 'manifest.json');
22
+ }
23
+
24
+ public readManifest(): TProteumManifest {
25
+ const filepath = this.getManifestFilepath();
26
+ if (!fs.existsSync(filepath)) {
27
+ throw new Error(`Proteum manifest not found at ${filepath}. Run a Proteum command that refreshes generated artifacts first.`);
28
+ }
29
+
30
+ return fs.readJsonSync(filepath) as TProteumManifest;
31
+ }
32
+
33
+ public normalizeExplainSections(rawSections: string[]) {
34
+ const sections = [...new Set(rawSections.map((section) => section.trim()).filter(Boolean))];
35
+ const invalidSections = sections.filter((section) => !isExplainSectionName(section));
36
+
37
+ if (invalidSections.length > 0) {
38
+ throw new Error(
39
+ `Unknown explain section(s): ${invalidSections.join(', ')}. Allowed values: ${explainSectionNames.join(', ')}.`,
40
+ );
41
+ }
42
+
43
+ return sections as TExplainSectionName[];
44
+ }
45
+
46
+ public explain(sectionNames: TExplainSectionName[] = []) {
47
+ return pickExplainManifestSections(this.readManifest(), sectionNames);
48
+ }
49
+
50
+ public doctor(strict = false): TDoctorResponse {
51
+ return buildDoctorResponse(this.readManifest(), strict);
52
+ }
53
+ }
@@ -5,7 +5,9 @@
5
5
  // Core
6
6
  import AppContainer from './container';
7
7
  import ApplicationService, { AnyService } from './service';
8
- import CommandsManager from './commands';
8
+ import CommandsManager from './commandsManager';
9
+ import DevCommandsRegistry from './devCommands';
10
+ import DevDiagnosticsRegistry from './devDiagnostics';
9
11
  import ServicesContainer, { ServicesContainer as ServicesContainerClass, TServiceMetas } from './service/container';
10
12
 
11
13
  // Built-in
@@ -29,6 +31,10 @@ type Hooks = {
29
31
  error: { args: [error: Error, request?: ServerRequest<TServerRouter>] };
30
32
  };
31
33
 
34
+ export type TApplicationStartOptions = {
35
+ skipRootServices?: string[];
36
+ };
37
+
32
38
  export const Service = ServicesContainer;
33
39
 
34
40
  // Without prettify, we don't get a clear list of the class properties
@@ -116,21 +122,33 @@ export abstract class Application<
116
122
  ----------------------------------*/
117
123
 
118
124
  private commandsManager = new CommandsManager(this, { debug: true }, this);
125
+ private devCommandsRegistry?: DevCommandsRegistry<this>;
126
+ private devDiagnosticsRegistry?: DevDiagnosticsRegistry<this>;
119
127
 
120
128
  public command(...args: Parameters<CommandsManager['command']>) {
121
129
  return this.commandsManager.command(...args);
122
130
  }
123
131
 
132
+ public getDevCommands() {
133
+ this.devCommandsRegistry ??= new DevCommandsRegistry(this);
134
+ return this.devCommandsRegistry;
135
+ }
136
+
137
+ public getDevDiagnostics() {
138
+ this.devDiagnosticsRegistry ??= new DevDiagnosticsRegistry(this);
139
+ return this.devDiagnosticsRegistry;
140
+ }
141
+
124
142
  /*----------------------------------
125
143
  - LAUNCH
126
144
  ----------------------------------*/
127
145
 
128
- public async start() {
146
+ public async start(options: TApplicationStartOptions = {}) {
129
147
  console.log('Build date', BUILD_DATE);
130
148
  console.log('Core version', CORE_VERSION);
131
149
  const startTime = Date.now();
132
150
 
133
- const startingServices = await this.ready();
151
+ const startingServices = await this.ready(options);
134
152
  await Promise.all(startingServices);
135
153
  await this.runHook('ready');
136
154
 
@@ -181,8 +199,9 @@ export abstract class Application<
181
199
  return (service as AnyService & { ready: () => Promise<any> }).ready();
182
200
  }
183
201
 
184
- public async ready() {
202
+ public async ready(options: TApplicationStartOptions = {}) {
185
203
  const startingServices: Promise<any>[] = [];
204
+ const skippedRootServices = new Set(options.skipRootServices || []);
186
205
 
187
206
  const processService = async (_propKey: string, service: AnyService) => {
188
207
  if (service.status !== 'starting') return;
@@ -208,6 +227,10 @@ export abstract class Application<
208
227
 
209
228
  for (const [serviceName, service] of this.listRootServices()) {
210
229
  const rootService = service as AnyService;
230
+ if (skippedRootServices.has(serviceName)) {
231
+ rootService.status = 'stopped';
232
+ continue;
233
+ }
211
234
 
212
235
  // TODO: move to router
213
236
  // Application.on('service.ready')
@@ -10,16 +10,33 @@ import cronParser, { CronExpression } from 'cron-parser';
10
10
  ----------------------------------*/
11
11
 
12
12
  import type CronManager from '.';
13
+ import type {
14
+ TProfilerCronTask,
15
+ TProfilerCronTaskFrequency,
16
+ TProfilerCronTaskRunStatus,
17
+ TProfilerCronTaskTrigger,
18
+ } from '@common/dev/profiler';
13
19
 
14
20
  export type TFrequence = string | Date;
15
21
  export type TRunner = () => Promise<any>;
22
+ const nowIso = () => new Date().toISOString();
16
23
 
17
24
  /*----------------------------------
18
25
  - CLASS
19
26
  ----------------------------------*/
20
27
  export default class CronTask {
21
28
  public cron?: CronExpression;
29
+ public frequency!: TProfilerCronTaskFrequency;
22
30
  public nextInvocation?: Date;
31
+ public registeredAt = nowIso();
32
+ public running = false;
33
+ public lastTrigger?: TProfilerCronTaskTrigger;
34
+ public lastRunStartedAt?: string;
35
+ public lastRunFinishedAt?: string;
36
+ public lastRunDurationMs?: number;
37
+ public lastRunStatus?: TProfilerCronTaskRunStatus;
38
+ public lastErrorMessage?: string;
39
+ public runCount = 0;
23
40
 
24
41
  public constructor(
25
42
  private manager: CronManager,
@@ -35,6 +52,10 @@ export default class CronTask {
35
52
 
36
53
  public schedule(next: TFrequence) {
37
54
  this.cron = undefined;
55
+ this.frequency =
56
+ typeof next === 'string'
57
+ ? { kind: 'cron', value: next }
58
+ : { kind: 'date', value: next.toISOString() };
38
59
 
39
60
  // Cron expression
40
61
  if (typeof next === 'string') {
@@ -58,16 +79,63 @@ export default class CronTask {
58
79
  else this.nextInvocation = undefined;
59
80
  }
60
81
 
61
- public run(now: boolean = false) {
82
+ public toProfilerTask(): TProfilerCronTask {
83
+ return {
84
+ name: this.nom,
85
+ registeredAt: this.registeredAt,
86
+ frequency: this.frequency,
87
+ autoexec: Boolean(this.autoexec),
88
+ automaticExecution: this.manager.isAutomaticExecutionEnabled(),
89
+ nextInvocation: this.nextInvocation?.toISOString(),
90
+ running: this.running,
91
+ lastTrigger: this.lastTrigger,
92
+ lastRunStartedAt: this.lastRunStartedAt,
93
+ lastRunFinishedAt: this.lastRunFinishedAt,
94
+ lastRunDurationMs: this.lastRunDurationMs,
95
+ lastRunStatus: this.lastRunStatus,
96
+ lastErrorMessage: this.lastErrorMessage,
97
+ runCount: this.runCount,
98
+ };
99
+ }
100
+
101
+ public async run(now: boolean = false, trigger: TProfilerCronTaskTrigger = 'scheduler') {
62
102
  // Update invocation date
63
103
  const maintenant = new Date();
64
104
  const runnable = this.nextInvocation !== undefined && this.nextInvocation.valueOf() <= maintenant.valueOf();
65
105
  if (runnable) this.scheduleNext();
66
- else if (now === false) return;
106
+ else if (now === false) return false;
107
+
108
+ if (this.running) return false;
109
+
110
+ this.running = true;
111
+ this.lastTrigger = trigger;
112
+ const startedAt = nowIso();
113
+ this.lastRunStartedAt = startedAt;
114
+ this.lastRunFinishedAt = undefined;
115
+ this.lastRunDurationMs = undefined;
116
+ this.lastRunStatus = undefined;
117
+ this.lastErrorMessage = undefined;
67
118
 
68
119
  // Execution
69
- this.runner().then(() => {
70
- this.manager.config.debug && console.info(`Task runned.`);
71
- });
120
+ try {
121
+ await this.runner();
122
+ this.runCount += 1;
123
+ const finishedAt = nowIso();
124
+ this.lastRunFinishedAt = finishedAt;
125
+ this.lastRunDurationMs = Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
126
+ this.lastRunStatus = 'completed';
127
+ this.manager.config.debug && console.info(`[cron][${this.nom}] Task completed.`);
128
+ return true;
129
+ } catch (error) {
130
+ this.runCount += 1;
131
+ const finishedAt = nowIso();
132
+ this.lastRunFinishedAt = finishedAt;
133
+ this.lastRunDurationMs = Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
134
+ this.lastRunStatus = 'error';
135
+ this.lastErrorMessage = error instanceof Error ? error.message : String(error);
136
+ throw error;
137
+ } finally {
138
+ this.running = false;
139
+ }
72
140
  }
73
141
  }
@@ -6,6 +6,7 @@
6
6
  import type { Application } from '@server/app/index';
7
7
  import Service from '@server/app/service';
8
8
  import { NotFound } from '@common/errors';
9
+ import type { TProfilerCronTaskTrigger } from '@common/dev/profiler';
9
10
  import context from '@server/context';
10
11
 
11
12
  /*----------------------------------
@@ -32,7 +33,7 @@ export type Services = {};
32
33
 
33
34
  export default class CronManager extends Service<Config, Hooks, Application, Application> {
34
35
  public static taches: { [nom: string]: CronTask } = {};
35
- public static timer: NodeJS.Timeout;
36
+ public static timer?: NodeJS.Timeout;
36
37
 
37
38
  /*----------------------------------
38
39
  - LIFECICLE
@@ -40,8 +41,17 @@ export default class CronManager extends Service<Config, Hooks, Application, App
40
41
 
41
42
  public async ready() {
42
43
  clearInterval(CronManager.timer);
44
+ if (!this.isAutomaticExecutionEnabled()) {
45
+ this.config.debug && console.info('[cron] Automatic execution disabled in dev mode.');
46
+ return;
47
+ }
48
+
43
49
  CronManager.timer = setInterval(() => {
44
- for (const id in CronManager.taches) CronManager.taches[id].run();
50
+ for (const id in CronManager.taches) {
51
+ void this.runTask(id, false, 'scheduler').catch((error) => {
52
+ console.error(`[cron][${id}] Task failed.`, error);
53
+ });
54
+ }
45
55
  }, 10000);
46
56
  }
47
57
 
@@ -59,16 +69,12 @@ export default class CronManager extends Service<Config, Hooks, Application, App
59
69
  * @param autoexec true to execute the task immediatly
60
70
  * @returns The CronTask that just have been created
61
71
  */
62
- public task(nom: string, frequence: TFrequence, run: TRunner, autoexec?: boolean) {
63
- return new Promise<CronTask>((resolve, reject) => {
64
- context.run({ channelType: 'cron', channelId: nom }, async () => {
65
- CronManager.taches[nom] = new CronTask(this, nom, frequence, run, autoexec);
72
+ public async task(nom: string, frequence: TFrequence, run: TRunner, autoexec?: boolean) {
73
+ CronManager.taches[nom] = new CronTask(this, nom, frequence, run, autoexec);
66
74
 
67
- if (autoexec) await CronManager.taches[nom].run(true);
75
+ if (autoexec && this.isAutomaticExecutionEnabled()) await this.runTask(nom, true, 'autoexec');
68
76
 
69
- resolve(CronManager.taches[nom]);
70
- });
71
- });
77
+ return CronManager.taches[nom];
72
78
  }
73
79
 
74
80
  public async exec(nom: string) {
@@ -76,7 +82,8 @@ export default class CronManager extends Service<Config, Hooks, Application, App
76
82
 
77
83
  if (tache === undefined) throw new NotFound('Tâche NotFound: ' + nom);
78
84
 
79
- await tache.run(true);
85
+ await this.runTask(nom, true, 'manual');
86
+ return tache;
80
87
  }
81
88
  public get(): typeof CronManager.taches;
82
89
  public get(name: string): CronTask;
@@ -87,4 +94,20 @@ export default class CronManager extends Service<Config, Hooks, Application, App
87
94
  if (cron === undefined) throw new Error(`L'instance de la tâche cron ${name} n'a pas été trouvée`);
88
95
  return cron;
89
96
  }
97
+
98
+ public isAutomaticExecutionEnabled() {
99
+ return !__DEV__;
100
+ }
101
+
102
+ public listTasks() {
103
+ return Object.values(CronManager.taches).map((task) => task.toProfilerTask());
104
+ }
105
+
106
+ private async runTask(name: string, now: boolean, trigger: TProfilerCronTaskTrigger) {
107
+ const task = this.get(name);
108
+
109
+ return context.run({ channelType: 'cron', channelId: name }, async () => {
110
+ return task.run(now, trigger);
111
+ });
112
+ }
90
113
  }
@@ -7,9 +7,6 @@ import type { default as sharp, Sharp } from 'sharp';
7
7
  import fs from 'fs-extra';
8
8
  import got, { Method, Options, Response as GotResponse } from 'got';
9
9
 
10
- // Node
11
- import request from 'request';
12
-
13
10
  // Core: general
14
11
  import type { Application } from '@server/app/index';
15
12
  import Service from '@server/app/service';
@@ -109,14 +106,10 @@ export default class FetchService extends Service<Config, Hooks, Application, Ap
109
106
  ----------------------------------*/
110
107
 
111
108
  public toBuffer(uri: string): Promise<Buffer> {
112
- return new Promise<Buffer>((resolve, reject) => {
113
- request(uri, { encoding: null }, (err, res, body) => {
114
- if (err) return reject(err);
115
-
116
- if (!body) return reject(`Body is empty for ${uri}.`);
109
+ return got(uri, { responseType: 'buffer', throwHttpErrors: false }).then((response) => {
110
+ if (!response.body || response.body.length === 0) throw new Error(`Body is empty for ${uri}.`);
117
111
 
118
- resolve(body);
119
- });
112
+ return response.body;
120
113
  });
121
114
  }
122
115
 
@@ -104,7 +104,7 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
104
104
  public constructor(...args: TServiceArgs<ModelsManager>) {
105
105
  super(...args);
106
106
 
107
- dotenv.config();
107
+ dotenv.config({ quiet: true });
108
108
 
109
109
  const databaseUrl = process.env.DATABASE_URL;
110
110
  if (!databaseUrl)