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.
Files changed (95) hide show
  1. package/AGENTS.md +44 -98
  2. package/README.md +143 -10
  3. package/agents/framework/AGENTS.md +146 -886
  4. package/agents/project/AGENTS.md +73 -127
  5. package/agents/project/client/AGENTS.md +22 -93
  6. package/agents/project/client/pages/AGENTS.md +24 -26
  7. package/agents/project/server/routes/AGENTS.md +10 -8
  8. package/agents/project/server/services/AGENTS.md +22 -159
  9. package/agents/project/tests/AGENTS.md +11 -8
  10. package/cli/app/config.ts +7 -20
  11. package/cli/bin.js +8 -0
  12. package/cli/commands/command.ts +243 -0
  13. package/cli/commands/commandLocalRunner.js +198 -0
  14. package/cli/commands/create.ts +5 -0
  15. package/cli/commands/deploy/web.ts +1 -2
  16. package/cli/commands/dev.ts +98 -2
  17. package/cli/commands/doctor.ts +8 -74
  18. package/cli/commands/explain.ts +8 -186
  19. package/cli/commands/init.ts +2 -94
  20. package/cli/commands/trace.ts +228 -0
  21. package/cli/compiler/artifacts/commands.ts +217 -0
  22. package/cli/compiler/artifacts/manifest.ts +35 -21
  23. package/cli/compiler/artifacts/services.ts +300 -1
  24. package/cli/compiler/client/index.ts +43 -8
  25. package/cli/compiler/common/commands.ts +175 -0
  26. package/cli/compiler/common/index.ts +1 -1
  27. package/cli/compiler/common/proteumManifest.ts +15 -114
  28. package/cli/compiler/index.ts +25 -2
  29. package/cli/compiler/server/index.ts +31 -6
  30. package/cli/index.ts +1 -4
  31. package/cli/paths.ts +16 -1
  32. package/cli/presentation/commands.ts +104 -14
  33. package/cli/presentation/devSession.ts +22 -3
  34. package/cli/presentation/proteum_logo_400x400_square_icon.txt +400 -0
  35. package/cli/runtime/commands.ts +121 -4
  36. package/cli/scaffold/index.ts +720 -0
  37. package/cli/scaffold/templates.ts +344 -0
  38. package/cli/scaffold/types.ts +26 -0
  39. package/cli/tsconfig.json +4 -1
  40. package/cli/utils/check.ts +1 -1
  41. package/client/app/component.tsx +13 -9
  42. package/client/dev/profiler/index.tsx +2511 -0
  43. package/client/dev/profiler/noop.tsx +5 -0
  44. package/client/dev/profiler/runtime.noop.ts +116 -0
  45. package/client/dev/profiler/runtime.ts +840 -0
  46. package/client/services/router/components/router.tsx +30 -2
  47. package/client/services/router/index.tsx +27 -3
  48. package/client/services/router/request/api.ts +133 -17
  49. package/commands/proteum/diagnostics.ts +11 -0
  50. package/common/dev/commands.ts +50 -0
  51. package/common/dev/diagnostics.ts +298 -0
  52. package/common/dev/profiler.ts +92 -0
  53. package/common/dev/proteumManifest.ts +135 -0
  54. package/common/dev/requestTrace.ts +115 -0
  55. package/common/env/proteumEnv.ts +284 -0
  56. package/common/router/index.ts +4 -22
  57. package/docs/dev-commands.md +93 -0
  58. package/docs/diagnostics.md +88 -0
  59. package/docs/request-tracing.md +132 -0
  60. package/eslint.js +11 -6
  61. package/package.json +3 -3
  62. package/server/app/commands.ts +35 -370
  63. package/server/app/commandsManager.ts +393 -0
  64. package/server/app/container/config.ts +11 -49
  65. package/server/app/container/console/index.ts +2 -3
  66. package/server/app/container/index.ts +5 -2
  67. package/server/app/container/trace/index.ts +364 -0
  68. package/server/app/devCommands.ts +192 -0
  69. package/server/app/devDiagnostics.ts +53 -0
  70. package/server/app/index.ts +29 -6
  71. package/server/index.ts +0 -1
  72. package/server/services/auth/index.ts +525 -61
  73. package/server/services/auth/router/index.ts +106 -7
  74. package/server/services/cron/CronTask.ts +73 -5
  75. package/server/services/cron/index.ts +34 -11
  76. package/server/services/fetch/index.ts +3 -10
  77. package/server/services/prisma/index.ts +66 -4
  78. package/server/services/router/http/index.ts +173 -6
  79. package/server/services/router/index.ts +200 -12
  80. package/server/services/router/request/api.ts +30 -1
  81. package/server/services/router/response/index.ts +83 -10
  82. package/server/services/router/response/page/document.tsx +16 -0
  83. package/server/services/router/response/page/index.tsx +27 -1
  84. package/skills/clean-project-code/SKILL.md +7 -2
  85. package/test-results/.last-run.json +4 -0
  86. package/types/aliases.d.ts +6 -0
  87. package/types/global/utils.d.ts +7 -14
  88. package/Rte.zip +0 -0
  89. package/agents/project/agents.md.zip +0 -0
  90. package/doc/TODO.md +0 -71
  91. package/doc/front/router.md +0 -27
  92. package/doc/workspace/workspace.png +0 -0
  93. package/doc/workspace/workspace2.png +0 -0
  94. package/doc/workspace/workspace_26.01.22.png +0 -0
  95. package/server/services/router/http/session.ts.old +0 -40
package/cli/app/config.ts CHANGED
@@ -2,20 +2,12 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
- /*
6
- NOTE: This is a copy of core/sever/app/config
7
- We can't import core deps here because it will cause the following error:
8
- "Can't use import when not a module"
9
- It will be possible to import core files when the CLI will be compiled as one output file with tsc
10
- And for that, we need to fix the TS errors for the CLI
11
- */
12
-
13
5
  // Npm
14
6
  import fs from 'fs-extra';
15
7
  import yaml from 'yaml';
16
8
 
17
9
  // Types
18
- import type { TEnvConfig } from '../../server/app/container/config';
10
+ import { parseProteumEnvConfig, type TProteumLoadedEnvConfig } from '../../common/env/proteumEnv';
19
11
  import { logVerbose } from '../runtime/verbose';
20
12
 
21
13
  /*----------------------------------
@@ -34,18 +26,13 @@ export default class ConfigParser {
34
26
  return yaml.parse(rawConfig);
35
27
  }
36
28
 
37
- public env(): TEnvConfig {
38
- // We assume that when we run 5htp dev, we're in local
39
- // Otherwise, we're in production environment (docker)
40
- logVerbose('[app] Using environment:', process.env.NODE_ENV);
41
- const envFileName = this.appDir + '/env.yaml';
42
- const envFile = this.loadYaml(envFileName);
29
+ public env(): TProteumLoadedEnvConfig {
30
+ logVerbose('[app] Loading Proteum env vars from process.env');
43
31
  return {
44
- ...envFile,
45
- router:
46
- this.routerPortOverride === undefined
47
- ? envFile.router
48
- : { ...envFile.router, port: this.routerPortOverride },
32
+ ...parseProteumEnvConfig({
33
+ appDir: this.appDir,
34
+ routerPortOverride: this.routerPortOverride,
35
+ }),
49
36
  version: 'CLI',
50
37
  };
51
38
  }
package/cli/bin.js CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  const path = require('path');
4
4
 
5
+ const clearInteractiveConsole = () => {
6
+ if (process.stdout.isTTY !== true || process.env.TERM === 'dumb') return;
7
+
8
+ process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
9
+ };
10
+
5
11
  /*
6
12
  Why this exists (npm i vs npm link difference)
7
13
 
@@ -30,6 +36,8 @@ if (!process.env.TS_NODE_IGNORE) {
30
36
  process.env.TS_NODE_PROJECT = path.join(__dirname, 'tsconfig.json');
31
37
  process.env.TS_NODE_TRANSPILE_ONLY = '1';
32
38
 
39
+ clearInteractiveConsole();
40
+
33
41
  require('ts-node/register/transpile-only');
34
42
 
35
43
  const { runCli } = require('./index.ts');
@@ -0,0 +1,243 @@
1
+ import got from 'got';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { UsageError } from 'clipanion';
5
+
6
+ import cli from '..';
7
+ import app from '../app';
8
+ import {
9
+ normalizeDevCommandPath,
10
+ type TDevCommandErrorResponse,
11
+ type TDevCommandExecution,
12
+ type TDevCommandRunResponse,
13
+ } from '../../common/dev/commands';
14
+
15
+ const localCommandResultMarker = '__PROTEUM_COMMAND_RESULT__';
16
+
17
+ const normalizeBaseUrl = (value: string) => value.replace(/\/+$/, '');
18
+
19
+ const getRouterPortFromManifest = () => {
20
+ const manifestFilepath = path.join(cli.args.workdir as string, '.proteum', 'manifest.json');
21
+ if (!require('fs-extra').existsSync(manifestFilepath)) return undefined;
22
+
23
+ const manifest = require('fs-extra').readJsonSync(manifestFilepath, { throws: false }) as
24
+ | { env?: { resolved?: { routerPort?: number } } }
25
+ | undefined;
26
+ const port = manifest?.env?.resolved?.routerPort;
27
+
28
+ if (typeof port !== 'number' || port <= 0) return undefined;
29
+
30
+ return String(port);
31
+ };
32
+
33
+ const getRouterPort = () => {
34
+ const overridePort = typeof cli.args.port === 'string' && cli.args.port ? cli.args.port : '';
35
+ if (overridePort) return overridePort;
36
+
37
+ const envPort = process.env.PORT?.trim();
38
+ if (envPort) return envPort;
39
+
40
+ const manifestPort = getRouterPortFromManifest();
41
+ if (manifestPort) return manifestPort;
42
+
43
+ throw new UsageError(
44
+ `Could not determine the router port from PORT or .proteum/manifest.json in ${cli.args.workdir as string}. Pass --port or --url explicitly.`,
45
+ );
46
+ };
47
+
48
+ const getRouterBaseUrls = () => {
49
+ const explicitUrl = typeof cli.args.url === 'string' && cli.args.url ? cli.args.url.trim() : '';
50
+ if (explicitUrl) return [normalizeBaseUrl(explicitUrl)];
51
+
52
+ const port = getRouterPort();
53
+ return [...new Set([`http://127.0.0.1:${port}`, `http://localhost:${port}`, `http://[::1]:${port}`])];
54
+ };
55
+
56
+ const getCommandErrorMessage = (body: TDevCommandErrorResponse | object | string | undefined, statusCode: number) => {
57
+ if (typeof body === 'object' && body !== null && 'error' in body && typeof body.error === 'string') {
58
+ return body.error;
59
+ }
60
+
61
+ return `Command request failed with status ${statusCode}.`;
62
+ };
63
+
64
+ const requestJson = async <TResponse>(pathname: string, options?: { method?: 'GET' | 'POST'; json?: object }) => {
65
+ const attempts: string[] = [];
66
+
67
+ for (const baseUrl of getRouterBaseUrls()) {
68
+ try {
69
+ const response = await got(`${baseUrl}${pathname}`, {
70
+ method: options?.method || 'GET',
71
+ json: options?.json,
72
+ responseType: 'json',
73
+ throwHttpErrors: false,
74
+ retry: { limit: 0 },
75
+ });
76
+
77
+ if (response.statusCode >= 400) {
78
+ throw new UsageError(
79
+ getCommandErrorMessage(response.body as TDevCommandErrorResponse | object | string | undefined, response.statusCode),
80
+ );
81
+ }
82
+
83
+ return response.body as TResponse;
84
+ } catch (error) {
85
+ if (error instanceof UsageError) throw error;
86
+
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ attempts.push(`${baseUrl}${pathname}: ${message}`);
89
+ }
90
+ }
91
+
92
+ throw new UsageError(
93
+ [
94
+ 'Could not reach the Proteum command server.',
95
+ ...attempts.map((attempt) => `- ${attempt}`),
96
+ 'Make sure the app is running with `proteum dev`, or omit --port/--url to run the command locally.',
97
+ ].join('\n'),
98
+ );
99
+ };
100
+
101
+ const printJson = (value: object) => {
102
+ console.log(JSON.stringify(value, null, 2));
103
+ };
104
+
105
+ const renderExecution = (execution: TDevCommandExecution) => {
106
+ const lines = [
107
+ `Command ${execution.command.path}`,
108
+ `- status=${execution.status} durationMs=${execution.durationMs}`,
109
+ `- source=${execution.command.filepath}:${execution.command.sourceLocation.line}:${execution.command.sourceLocation.column}`,
110
+ ];
111
+
112
+ if (execution.errorMessage) {
113
+ lines.push(`- error=${execution.errorMessage}`);
114
+ } else if (execution.result?.json !== undefined) {
115
+ lines.push('Result');
116
+ lines.push(JSON.stringify(execution.result.json, null, 2));
117
+ } else if (execution.result?.summary !== undefined) {
118
+ lines.push('Result');
119
+ lines.push(JSON.stringify(execution.result.summary, null, 2));
120
+ } else {
121
+ lines.push('- result=undefined');
122
+ }
123
+
124
+ return lines.join('\n');
125
+ };
126
+
127
+ const runLocalCommand = async (commandPath: string) => {
128
+ if (app.env.profile !== 'dev') {
129
+ throw new UsageError(`Proteum commands are only available when ENV_PROFILE=dev. Current profile: ${app.env.profile}.`);
130
+ }
131
+
132
+ const runnerFilepath = path.join(cli.paths.core.root, 'cli', 'commands', 'commandLocalRunner.js');
133
+
134
+ return await new Promise<TDevCommandExecution>((resolve, reject) => {
135
+ const stdoutChunks: Buffer[] = [];
136
+ const stderrChunks: Buffer[] = [];
137
+ const child = spawn(process.execPath, [runnerFilepath, app.paths.root, commandPath], {
138
+ cwd: app.paths.root,
139
+ env: { ...process.env },
140
+ stdio: ['ignore', 'pipe', 'pipe'],
141
+ });
142
+
143
+ child.stdout.on('data', (chunk: Buffer | string) => {
144
+ stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
145
+ });
146
+
147
+ child.stderr.on('data', (chunk: Buffer | string) => {
148
+ stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
149
+ });
150
+
151
+ child.on('error', (error) => reject(error));
152
+ child.on('close', (exitCode) => {
153
+ const stdout = Buffer.concat(stdoutChunks).toString('utf8');
154
+ const stderr = Buffer.concat(stderrChunks).toString('utf8');
155
+ const markerLine = stdout
156
+ .split(/\r?\n/)
157
+ .find((line) => line.startsWith(localCommandResultMarker));
158
+
159
+ if (stderr.trim()) {
160
+ process.stderr.write(stderr.endsWith('\n') ? stderr : `${stderr}\n`);
161
+ }
162
+
163
+ if (!markerLine) {
164
+ reject(
165
+ new Error(
166
+ [
167
+ `Local command runner exited without returning a structured result (exit code ${exitCode ?? 'unknown'}).`,
168
+ stdout.trim() ? `stdout:\n${stdout.trim()}` : undefined,
169
+ ]
170
+ .filter(Boolean)
171
+ .join('\n\n'),
172
+ ),
173
+ );
174
+ return;
175
+ }
176
+
177
+ const payload = JSON.parse(markerLine.slice(localCommandResultMarker.length)) as
178
+ | { execution: TDevCommandExecution }
179
+ | { error: string };
180
+
181
+ if ('execution' in payload) {
182
+ if (exitCode && exitCode !== 0) {
183
+ const error = new Error(payload.execution.errorMessage || `Command "${commandPath}" failed.`) as Error & {
184
+ execution?: TDevCommandExecution;
185
+ };
186
+
187
+ error.execution = payload.execution;
188
+ reject(error);
189
+ return;
190
+ }
191
+
192
+ resolve(payload.execution);
193
+ return;
194
+ }
195
+
196
+ reject(new Error(payload.error || `Command "${commandPath}" failed.`));
197
+ });
198
+ });
199
+ };
200
+
201
+ export const run = async () => {
202
+ const rawPath = typeof cli.args.path === 'string' ? cli.args.path : '';
203
+ const commandPath = normalizeDevCommandPath(rawPath);
204
+ const shouldPrintJson = cli.args.json === true;
205
+ const shouldUseRemoteServer =
206
+ (typeof cli.args.port === 'string' && cli.args.port.length > 0) ||
207
+ (typeof cli.args.url === 'string' && cli.args.url.length > 0);
208
+
209
+ if (!commandPath) {
210
+ throw new UsageError('A command path is required. Example: proteum command diagnostics/ping');
211
+ }
212
+
213
+ try {
214
+ const execution = shouldUseRemoteServer
215
+ ? (await requestJson<TDevCommandRunResponse>('/__proteum/commands/run', {
216
+ method: 'POST',
217
+ json: { path: commandPath },
218
+ })).execution
219
+ : await runLocalCommand(commandPath);
220
+
221
+ if (shouldPrintJson) {
222
+ printJson({ execution });
223
+ return;
224
+ }
225
+
226
+ console.log(renderExecution(execution));
227
+ } catch (error) {
228
+ if (error instanceof Error && 'execution' in error && typeof error.execution === 'object' && error.execution) {
229
+ const execution = error.execution as TDevCommandExecution;
230
+
231
+ if (shouldPrintJson) {
232
+ printJson({ execution });
233
+ } else {
234
+ console.log(renderExecution(execution));
235
+ }
236
+
237
+ process.exitCode = 1;
238
+ return;
239
+ }
240
+
241
+ throw error;
242
+ }
243
+ };
@@ -0,0 +1,198 @@
1
+ const path = require('path');
2
+ const net = require('net');
3
+ const { spawn } = require('child_process');
4
+ const tsNode = require('ts-node');
5
+
6
+ const resultMarker = '__PROTEUM_COMMAND_RESULT__';
7
+
8
+ const printPayload = (payload) => {
9
+ process.stdout.write(`${resultMarker}${JSON.stringify(payload)}\n`);
10
+ };
11
+
12
+ const fail = (error, execution) => {
13
+ if (execution) {
14
+ printPayload({ execution });
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+
19
+ printPayload({ error: error instanceof Error ? error.message : String(error) });
20
+ process.exitCode = 1;
21
+ };
22
+
23
+ const getAvailablePort = async () =>
24
+ await new Promise((resolve, reject) => {
25
+ const server = net.createServer();
26
+
27
+ server.once('error', reject);
28
+ server.listen(0, '127.0.0.1', () => {
29
+ const address = server.address();
30
+
31
+ if (!address || typeof address === 'string') {
32
+ server.close(() => reject(new Error('Could not determine a local port for the command runner.')));
33
+ return;
34
+ }
35
+
36
+ server.close((error) => {
37
+ if (error) {
38
+ reject(error);
39
+ return;
40
+ }
41
+
42
+ resolve(address.port);
43
+ });
44
+ });
45
+ });
46
+
47
+ const closeMultiCompiler = async (multiCompiler) =>
48
+ await new Promise((resolve, reject) => {
49
+ multiCompiler.close((error) => {
50
+ if (error) {
51
+ reject(error);
52
+ return;
53
+ }
54
+
55
+ resolve();
56
+ });
57
+ });
58
+
59
+ const runCompiler = async (compiler) => {
60
+ const multiCompiler = await compiler.create();
61
+
62
+ try {
63
+ await new Promise((resolve, reject) => {
64
+ multiCompiler.run((error, stats) => {
65
+ if (error) {
66
+ reject(error);
67
+ return;
68
+ }
69
+
70
+ if (stats && stats.hasErrors()) {
71
+ reject(new Error('Compilation failed for the local dev command runner.'));
72
+ return;
73
+ }
74
+
75
+ resolve();
76
+ });
77
+ });
78
+ } finally {
79
+ compiler.dispose();
80
+ await closeMultiCompiler(multiCompiler);
81
+ }
82
+ };
83
+
84
+ const waitForServerReady = async (child) =>
85
+ await new Promise((resolve, reject) => {
86
+ let settled = false;
87
+
88
+ const finish = (callback, value) => {
89
+ if (settled) return;
90
+ settled = true;
91
+ clearTimeout(timeout);
92
+ child.off('message', onMessage);
93
+ child.off('error', onError);
94
+ child.off('exit', onExit);
95
+ callback(value);
96
+ };
97
+
98
+ const onMessage = (message) => {
99
+ if (!message || message.type !== 'proteum:server-ready' || typeof message.publicUrl !== 'string') return;
100
+ finish(resolve, message.publicUrl);
101
+ };
102
+
103
+ const onError = (error) => finish(reject, error);
104
+ const onExit = (code, signal) =>
105
+ finish(reject, new Error(`Local command server exited before becoming ready (code=${code}, signal=${signal}).`));
106
+ const timeout = setTimeout(
107
+ () => finish(reject, new Error('Timed out while waiting for the local command server to become ready.')),
108
+ 30000,
109
+ );
110
+
111
+ child.on('message', onMessage);
112
+ child.once('error', onError);
113
+ child.once('exit', onExit);
114
+ });
115
+
116
+ const stopServerProcess = async (child) => {
117
+ if (!child || child.exitCode !== null || child.signalCode !== null) return;
118
+
119
+ child.kill('SIGTERM');
120
+
121
+ await new Promise((resolve) => {
122
+ const timeout = setTimeout(() => {
123
+ if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');
124
+ }, 5000);
125
+
126
+ child.once('exit', () => {
127
+ clearTimeout(timeout);
128
+ resolve();
129
+ });
130
+ });
131
+ };
132
+
133
+ const requestCommand = async (baseUrl, commandPath) => {
134
+ const response = await fetch(`${baseUrl}/__proteum/commands/run`, {
135
+ method: 'POST',
136
+ headers: { 'content-type': 'application/json' },
137
+ body: JSON.stringify({ path: commandPath }),
138
+ });
139
+ const body = await response.json();
140
+
141
+ if (body && typeof body === 'object' && body.execution) {
142
+ return { statusCode: response.status, execution: body.execution };
143
+ }
144
+
145
+ throw new Error((body && body.error) || `Command request failed with status ${response.status}.`);
146
+ };
147
+
148
+ (async () => {
149
+ const [, , appRootArg = '', commandPath = ''] = process.argv;
150
+ const appRoot = path.resolve(appRootArg);
151
+
152
+ if (!appRootArg || !commandPath) {
153
+ fail(new Error('commandLocalRunner requires <appRoot> and <commandPath>.'));
154
+ return;
155
+ }
156
+
157
+ process.chdir(appRoot);
158
+
159
+ tsNode.register({
160
+ transpileOnly: true,
161
+ project: path.join(__dirname, '..', 'tsconfig.json'),
162
+ files: true,
163
+ });
164
+
165
+ const port = await getAvailablePort();
166
+ const cli = require('../context.ts').default;
167
+ cli.setArgs({ workdir: appRoot, path: commandPath, port: String(port), url: '', json: true });
168
+
169
+ const app = require('../app/index.ts').default;
170
+ const Compiler = require('../compiler/index.ts').default;
171
+
172
+ if (app.env.profile !== 'dev') {
173
+ fail(new Error(`Proteum commands are only available when ENV_PROFILE=dev. Current profile: ${app.env.profile}.`));
174
+ return;
175
+ }
176
+
177
+ const compiler = new Compiler('dev');
178
+ await runCompiler(compiler);
179
+
180
+ const serverProcess = spawn(process.execPath, ['--preserve-symlinks', path.join(app.outputPath('dev'), 'server.js')], {
181
+ cwd: app.paths.root,
182
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
183
+ });
184
+
185
+ try {
186
+ const publicUrl = await waitForServerReady(serverProcess);
187
+ const { statusCode, execution } = await requestCommand(publicUrl, commandPath);
188
+
189
+ printPayload({ execution });
190
+ if (statusCode >= 400 || execution.status === 'error') {
191
+ process.exitCode = 1;
192
+ }
193
+ } finally {
194
+ await stopServerProcess(serverProcess);
195
+ }
196
+ })().catch((error) => {
197
+ fail(error);
198
+ });
@@ -0,0 +1,5 @@
1
+ import { runCreateScaffold } from '../scaffold';
2
+
3
+ export const run = async (): Promise<void> => {
4
+ await runCreateScaffold();
5
+ };
@@ -45,8 +45,7 @@ export async function run() {
45
45
  { spaces: 4 },
46
46
  );
47
47
 
48
- // Copy config file
49
- fs.copyFileSync(app.paths.root + (simulate ? '/env.yaml' : '/env.server.yaml'), temp + '/env.yaml');
48
+ // Deployment now relies on exported ENV_*, URL, TRACE_*, and PORT variables instead of copied env config files.
50
49
 
51
50
  // Compile & Run Docker
52
51
  await cli.shell(`docker compose up --build`);
@@ -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 '..';
@@ -20,7 +22,7 @@ import {
20
22
  import Compiler from '../compiler';
21
23
  import { createDevEventServer } from './devEvents';
22
24
  import { ensureProjectAgentSymlinks } from '../utils/agents';
23
- import { renderDevSession, renderServerReadyBanner } from '../presentation/devSession';
25
+ import { renderDevSession, renderServerReadyBanner, renderDevShutdownBanner } from '../presentation/devSession';
24
26
  import { logVerbose } from '../runtime/verbose';
25
27
 
26
28
  // Core
@@ -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) {
@@ -155,6 +174,7 @@ async function startApp(app: App) {
155
174
  await renderServerReadyBanner({
156
175
  appName: getDevAppName(app),
157
176
  publicUrl: message.publicUrl,
177
+ routerPort: app.env.router.port,
158
178
  }),
159
179
  );
160
180
  })();
@@ -237,6 +257,78 @@ function normalizeWatchPath(watchPath: string) {
237
257
  return path.resolve(watchPath).replace(/\\/g, '/').replace(/\/$/, '');
238
258
  }
239
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
+
240
332
  /*----------------------------------
241
333
  - MAIN PROCESS
242
334
  ----------------------------------*/
@@ -340,6 +432,7 @@ export const run = async () => {
340
432
  logVerbose('Watch callback. No compiler changes were tracked.');
341
433
  },
342
434
  );
435
+ const indexedSourceWatching = createIndexedSourceWatching({ compiler, watching });
343
436
 
344
437
  let shuttingDownPromise: Promise<void> | undefined;
345
438
 
@@ -349,10 +442,13 @@ export const run = async () => {
349
442
  devSessionStopping = true;
350
443
  shuttingDownPromise = (async () => {
351
444
  logVerbose('Stopping the Proteum dev session ...', reason);
445
+ await indexedSourceWatching.close();
352
446
  await closeWatching(watching);
353
447
  compiler.dispose();
354
448
  await stopApp(reason);
449
+ await cleanupPersistedDevTraces(app);
355
450
  await devEventServer.close();
451
+ console.info(await renderDevShutdownBanner());
356
452
  })();
357
453
 
358
454
  return shuttingDownPromise;