proteum 2.0.0 → 2.1.0-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 (94) hide show
  1. package/AGENTS.md +13 -1
  2. package/README.md +375 -0
  3. package/agents/framework/AGENTS.md +917 -0
  4. package/agents/project/AGENTS.md +138 -0
  5. package/agents/{codex → project}/CODING_STYLE.md +3 -2
  6. package/agents/project/client/AGENTS.md +108 -0
  7. package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
  8. package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
  9. package/agents/project/server/services/AGENTS.md +170 -0
  10. package/agents/{codex → project}/tests/AGENTS.md +1 -0
  11. package/cli/app/config.ts +3 -2
  12. package/cli/app/index.ts +6 -66
  13. package/cli/bin.js +7 -2
  14. package/cli/commands/build.ts +94 -27
  15. package/cli/commands/check.ts +15 -1
  16. package/cli/commands/dev.ts +288 -132
  17. package/cli/commands/doctor.ts +108 -0
  18. package/cli/commands/explain.ts +226 -0
  19. package/cli/commands/init.ts +76 -70
  20. package/cli/commands/lint.ts +18 -1
  21. package/cli/commands/refresh.ts +16 -6
  22. package/cli/commands/typecheck.ts +14 -1
  23. package/cli/compiler/artifacts/controllers.ts +150 -0
  24. package/cli/compiler/artifacts/discovery.ts +132 -0
  25. package/cli/compiler/artifacts/manifest.ts +267 -0
  26. package/cli/compiler/artifacts/routing.ts +315 -0
  27. package/cli/compiler/artifacts/services.ts +488 -0
  28. package/cli/compiler/artifacts/shared.ts +12 -0
  29. package/cli/compiler/client/identite.ts +2 -1
  30. package/cli/compiler/client/index.ts +13 -3
  31. package/cli/compiler/common/controllers.ts +23 -28
  32. package/cli/compiler/common/files/style.ts +3 -4
  33. package/cli/compiler/common/generatedRouteModules.ts +333 -19
  34. package/cli/compiler/common/proteumManifest.ts +133 -0
  35. package/cli/compiler/index.ts +33 -896
  36. package/cli/compiler/server/index.ts +21 -4
  37. package/cli/context.ts +71 -0
  38. package/cli/index.ts +39 -181
  39. package/cli/presentation/commands.ts +208 -0
  40. package/cli/presentation/compileReporter.ts +65 -0
  41. package/cli/presentation/devSession.ts +70 -0
  42. package/cli/presentation/help.ts +193 -0
  43. package/cli/presentation/ink.ts +69 -0
  44. package/cli/presentation/layout.ts +83 -0
  45. package/cli/runtime/argv.ts +49 -0
  46. package/cli/runtime/command.ts +25 -0
  47. package/cli/runtime/commands.ts +221 -0
  48. package/cli/runtime/importEsm.ts +7 -0
  49. package/cli/runtime/verbose.ts +15 -0
  50. package/cli/utils/agents.ts +5 -4
  51. package/cli/utils/keyboard.ts +12 -6
  52. package/client/app/index.ts +0 -6
  53. package/client/services/router/index.tsx +1 -1
  54. package/client/services/router/response/index.tsx +2 -2
  55. package/common/dev/serverHotReload.ts +12 -0
  56. package/common/router/index.ts +3 -2
  57. package/common/router/layouts.ts +1 -1
  58. package/common/router/pageSetup.ts +1 -0
  59. package/package.json +10 -8
  60. package/prettier/router-registration-plugin.cjs +52 -0
  61. package/prettier.config.cjs +1 -0
  62. package/scripts/cleanup-generated-controllers.ts +2 -2
  63. package/scripts/fix-reference-app-typing.ts +2 -2
  64. package/scripts/format-router-registrations.ts +119 -0
  65. package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
  66. package/scripts/refactor-server-controllers.ts +19 -18
  67. package/scripts/refactor-server-runtime-aliases.ts +1 -1
  68. package/server/app/commands.ts +309 -25
  69. package/server/app/container/config.ts +1 -1
  70. package/server/app/container/index.ts +2 -2
  71. package/server/app/controller/index.ts +13 -4
  72. package/server/app/index.ts +53 -37
  73. package/server/app/service/container.ts +26 -28
  74. package/server/app/service/index.ts +10 -20
  75. package/server/app.tsconfig.json +9 -2
  76. package/server/index.ts +32 -1
  77. package/server/services/auth/index.ts +234 -15
  78. package/server/services/auth/router/index.ts +39 -7
  79. package/server/services/auth/router/request.ts +40 -8
  80. package/server/services/disks/index.ts +1 -1
  81. package/server/services/prisma/Facet.ts +2 -2
  82. package/server/services/prisma/index.ts +28 -5
  83. package/server/services/prisma/mariadb.ts +47 -0
  84. package/server/services/router/http/index.ts +9 -1
  85. package/server/services/router/index.ts +10 -4
  86. package/server/services/router/response/index.ts +26 -6
  87. package/types/auth-check-rules.test.ts +51 -0
  88. package/types/controller-request-context.test.ts +55 -0
  89. package/types/service-config.test.ts +39 -0
  90. package/agents/codex/AGENTS.md +0 -95
  91. package/agents/codex/client/AGENTS.md +0 -102
  92. package/agents/codex/server/services/AGENTS.md +0 -137
  93. package/server/services/models.7z +0 -0
  94. /package/agents/{codex → project}/agents.md.zip +0 -0
@@ -32,6 +32,18 @@ import type { App } from '../../app';
32
32
 
33
33
  const debug = false;
34
34
  const ssrScriptExtensions = ['.ssr.ts', '.ssr.tsx'];
35
+ const serverReactCompatCompilePrefixes = [
36
+ '@floating-ui',
37
+ '@mantine/',
38
+ '@radix-ui/',
39
+ 'aria-hidden',
40
+ 'react-number-format',
41
+ 'react-remove-scroll',
42
+ 'react-remove-scroll-bar',
43
+ 'react-style-singleton',
44
+ 'use-callback-ref',
45
+ 'use-sidecar',
46
+ ];
35
47
 
36
48
  const getDevGeneratedRuntimeEntries = (app: App) => ({
37
49
  __proteum_dev_routes: [app.paths.server.generated + '/routes.ts'],
@@ -108,10 +120,9 @@ export default function createCompiler(
108
120
  // TODO: proteum.conf: compile: include
109
121
  // Compile proteum modules
110
122
  request.startsWith('proteum') ||
111
- // Compile 5HTP modules
112
- request.startsWith('@mantine/') ||
113
- request.startsWith('react-number-format') ||
114
- request.startsWith('@floating-ui'));
123
+ // React-based UI packages must pass through the alias layer on the server,
124
+ // otherwise SSR can mix real React packages with the Preact compat runtime.
125
+ serverReactCompatCompilePrefixes.some((prefix) => request.startsWith(prefix)));
115
126
 
116
127
  //console.log('isNodeModule', request, isNodeModule);
117
128
 
@@ -144,13 +155,19 @@ export default function createCompiler(
144
155
  include: [
145
156
  app.paths.root + '/client',
146
157
  cli.paths.core.root + '/client',
158
+ app.paths.client.generated,
147
159
 
148
160
  app.paths.root + '/common',
149
161
  cli.paths.core.root + '/common',
162
+ app.paths.common.generated,
163
+
164
+ // Prisma 7 generates TypeScript entrypoints under var/prisma.
165
+ app.paths.root + '/var/prisma',
150
166
 
151
167
  // Dossiers server uniquement pour le bundle server
152
168
  app.paths.root + '/server',
153
169
  cli.paths.core.root + '/server',
170
+ app.paths.server.generated,
154
171
 
155
172
  // Complle 5HTP modules so they can refer to the framework instance and aliases
156
173
  // Temp disabled because compile issue on vercel
package/cli/context.ts ADDED
@@ -0,0 +1,71 @@
1
+ import cp from 'child_process';
2
+ import fs from 'fs-extra';
3
+
4
+ import Paths from './paths';
5
+
6
+ export type TArgsObject = { [key: string]: string | boolean | string[] | undefined };
7
+
8
+ export class CLIContext {
9
+ public args: TArgsObject = { workdir: process.cwd() };
10
+
11
+ public verbose = false;
12
+
13
+ public debug = false;
14
+
15
+ public packageJson: { [key: string]: any };
16
+
17
+ public constructor(public paths = new Paths(process.cwd())) {
18
+ this.debug && console.log(`[cli] 5HTP CLI`, process.env.npm_package_version);
19
+
20
+ this.debug && console.log(`[cli] Apply aliases ...`);
21
+ this.paths.applyAliases();
22
+
23
+ this.packageJson = this.loadPkg();
24
+ }
25
+
26
+ public setArgs(args: TArgsObject = {}) {
27
+ this.args = { workdir: process.cwd(), ...args };
28
+ this.verbose = this.args.verbose === true;
29
+ this.debug = this.verbose;
30
+ }
31
+
32
+ private loadPkg() {
33
+ return fs.readJSONSync(this.paths.core.root + '/package.json');
34
+ }
35
+
36
+ public shell(...commands: string[]) {
37
+ return new Promise<void>((resolve) => {
38
+ const fullCommand = commands
39
+ .map((command) => {
40
+ command = command.trim();
41
+
42
+ if (command.endsWith(';')) command = command.substring(0, command.length - 1);
43
+
44
+ return command;
45
+ })
46
+ .join(';');
47
+
48
+ this.verbose && console.log('$ ' + fullCommand);
49
+
50
+ const wrappedCommand = `bash -c '${fullCommand}'`;
51
+ this.verbose && console.log('Running command: ' + wrappedCommand);
52
+
53
+ const proc = cp.spawn(wrappedCommand, [], {
54
+ cwd: process.cwd(),
55
+ detached: false,
56
+ shell: true,
57
+ });
58
+
59
+ this.verbose && console.log(proc.exitCode);
60
+
61
+ proc.on('exit', () => {
62
+ this.verbose && console.log('Command finished.');
63
+ resolve();
64
+ });
65
+ });
66
+ }
67
+ }
68
+
69
+ const cli = new CLIContext();
70
+
71
+ export default cli;
package/cli/index.ts CHANGED
@@ -1,188 +1,46 @@
1
- #!/usr/bin/env -S npx ts-node
2
-
3
1
  process.traceDeprecation = true;
4
2
 
5
- /*----------------------------------
6
- - DEPENDANCES
7
- ----------------------------------*/
8
-
9
- // Npm
10
- import fs from 'fs-extra';
11
- import cp from 'child_process';
12
-
13
- // Libs
14
- import Paths from './paths';
15
-
16
- /*----------------------------------
17
- - TYPES
18
- ----------------------------------*/
19
-
20
- type TCliCommand = () => Promise<{ run: () => Promise<void> }>;
21
-
22
- type TArgsObject = { [key: string]: string | boolean | string[] };
23
-
24
- /*----------------------------------
25
- - CLASSE
26
- ----------------------------------*/
27
- /*
28
- IMPORTANT: The CLI must be independant of the app instance and libs
29
- */
30
- export class CLI {
31
- // Context
32
- public args: TArgsObject = {};
33
-
34
- public commandOptionDefaults: { [command: string]: TArgsObject } = {
35
- dev: { port: '', cache: true },
36
- build: { port: '', dev: false, prod: false, cache: false, analyze: false },
37
- lint: { fix: false },
38
- };
39
-
40
- public debug: boolean = false;
41
-
42
- public packageJson: { [key: string]: any };
43
-
44
- public constructor(public paths = new Paths(process.cwd())) {
45
- this.debug && console.log(`[cli] 5HTP CLI`, process.env.npm_package_version);
46
-
47
- this.debug && console.log(`[cli] Apply aliases ...`);
48
- this.paths.applyAliases();
49
-
50
- this.packageJson = this.loadPkg();
51
-
52
- this.start();
53
- }
54
-
55
- /*----------------------------------
56
- - COMMANDS
57
- ----------------------------------*/
58
- // Les importations asynchrones permettent d'accéder à l'instance de cli via un import
59
- // WARN: We load commands asynchonously, so the aliases are applied before the file is imported
60
- public commands: { [name: string]: TCliCommand } = {
61
- init: () => import('./commands/init'),
62
- dev: () => import('./commands/dev'),
63
- refresh: () => import('./commands/refresh'),
64
- build: () => import('./commands/build'),
65
- typecheck: () => import('./commands/typecheck'),
66
- lint: () => import('./commands/lint'),
67
- check: () => import('./commands/check'),
68
- };
69
-
70
- private loadPkg() {
71
- return fs.readJSONSync(this.paths.core.root + '/package.json');
72
- }
73
-
74
- public start() {
75
- const [, , commandName, ...argv] = process.argv;
76
-
77
- if (this.commands[commandName] === undefined) throw new Error(`Command ${commandName} does not exists.`);
78
-
79
- this.args = { ...(this.commandOptionDefaults[commandName] || {}) };
80
- this.args.workdir = process.cwd();
81
-
82
- let opt: string | null = null;
83
- for (const a of argv) {
84
- if (a.startsWith('-')) {
85
- opt = a.replace(/^-+/, '');
86
- if (opt.length === 0) throw new Error(`Unknown option: ${a}`);
87
-
88
- if (opt.startsWith('no-')) {
89
- const booleanOpt = opt.substring(3);
90
- if (!(booleanOpt in this.args)) throw new Error(`Unknown option: ${opt}`);
91
- if (typeof this.args[booleanOpt] !== 'boolean')
92
- throw new Error(`Option ${booleanOpt} does not support --no-${booleanOpt}.`);
93
-
94
- this.args[booleanOpt] = false;
95
- opt = null;
96
- continue;
97
- }
98
-
99
- if (!(opt in this.args)) throw new Error(`Unknown option: ${opt}`);
100
-
101
- // Init with default value
102
- if (typeof this.args[opt] === 'boolean') {
103
- this.args[opt] = true;
104
- opt = null;
105
- }
106
- } else if (opt !== null) {
107
- const curVal = this.args[opt];
108
-
109
- if (Array.isArray(curVal)) curVal.push(a);
110
- else this.args[opt] = a;
111
-
112
- opt = null;
113
- } else {
114
- this.args[a] = true;
115
- }
116
- }
117
-
118
- if (opt !== null && typeof this.args[opt] !== 'boolean') throw new Error(`Missing value for option: ${opt}`);
119
-
120
- this.runCommand(commandName);
3
+ import fs from 'fs';
4
+ import { Cli } from 'clipanion';
5
+
6
+ import cli from './context';
7
+ import { proteumCommandNames } from './presentation/commands';
8
+ import { renderCliOverview, renderCommandHelp, resolveCustomHelpRequest } from './presentation/help';
9
+ import { normalizeHelpArgv, normalizeLegacyArgv } from './runtime/argv';
10
+ import { createCli, registeredCommands } from './runtime/commands';
11
+
12
+ const hasInitScaffold = () => fs.existsSync(`${cli.paths.core.cli}/skeleton`);
13
+
14
+ export const runCli = async (argv: string[] = process.argv.slice(2)) => {
15
+ const normalizedArgv = normalizeHelpArgv(normalizeLegacyArgv(argv), proteumCommandNames);
16
+ const clipanion = createCli(String(cli.packageJson.version || ''));
17
+ const initAvailable = hasInitScaffold();
18
+ const helpRequest = resolveCustomHelpRequest(normalizedArgv);
19
+
20
+ if (helpRequest.kind === 'overview') {
21
+ process.stdout.write(
22
+ await renderCliOverview({
23
+ version: String(cli.packageJson.version || ''),
24
+ workdir: process.cwd(),
25
+ initAvailable,
26
+ }),
27
+ );
28
+ return;
121
29
  }
122
30
 
123
- public async runCommand(command: string) {
124
- this.debug && console.info(`Running command ${command}`, this.args);
125
-
126
- // Check existance
127
- if (this.commands[command] === undefined) throw new Error(`Command ${command} does not exists.`);
128
-
129
- const runner = await this.commands[command]();
130
- let exitCode = 0;
131
-
132
- // Running
133
- runner
134
- .run()
135
- .then(() => {
136
- this.debug && console.info(`Command ${command} finished.`);
137
- })
138
- .catch((e) => {
139
- exitCode = 1;
140
- console.error(`Error during execution of ${command}:`, e);
141
- })
142
- .finally(() => {
143
- process.exit(exitCode);
144
- });
31
+ if (helpRequest.kind === 'command') {
32
+ process.stdout.write(
33
+ await renderCommandHelp({
34
+ commandName: helpRequest.commandName,
35
+ definition: clipanion.definition(registeredCommands[helpRequest.commandName]),
36
+ workdir: process.cwd(),
37
+ initAvailable,
38
+ }),
39
+ );
40
+ return;
145
41
  }
146
42
 
147
- public shell(...commands: string[]) {
148
- return new Promise<void>(async (resolve) => {
149
- const fullCommand = commands
150
- .map((command) => {
151
- command = command.trim();
152
-
153
- if (command.endsWith(';')) command = command.substring(0, command.length - 1);
154
-
155
- return command;
156
- })
157
- .join(';');
158
-
159
- console.log('$ ' + fullCommand);
160
-
161
- /*const tempFile = this.paths.app.root + '/.exec.sh';
162
- fs.outputFileSync(tempFile, '#! /bin/bash\n' + fullCommand);
163
- const wrappedCommand = `tilix --new-process -e bash -c 'chmod +x "${tempFile}"; "${tempFile}"; echo "Entrée pour continuer"; read a;'`;*/
164
- const wrappedCommand = `bash -c '${fullCommand}'`;
165
- console.log('Running command: ' + wrappedCommand);
166
- //await this.waitForInput('enter');
167
-
168
- const proc = cp.spawn(wrappedCommand, [], {
169
- cwd: process.cwd(),
170
- detached: false,
171
- // Permer de lancer les commandes via des chaines pures (autrement, il faut separer chaque arg dans un tableau)
172
- // https://stackoverflow.com/questions/23487363/how-can-i-parse-a-string-into-appropriate-arguments-for-child-process-spawn
173
- shell: true,
174
- });
175
-
176
- console.log(proc.exitCode);
177
-
178
- proc.on('exit', function () {
179
- //fs.removeSync(tempFile);
180
-
181
- console.log('Command finished.');
182
- resolve();
183
- });
184
- });
185
- }
186
- }
43
+ await clipanion.runExit(normalizedArgv, Cli.defaultContext);
44
+ };
187
45
 
188
- export default new CLI();
46
+ export default cli;
@@ -0,0 +1,208 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ import type { TRow } from './layout';
5
+
6
+ export const proteumCommandNames = [
7
+ 'init',
8
+ 'dev',
9
+ 'refresh',
10
+ 'build',
11
+ 'typecheck',
12
+ 'lint',
13
+ 'check',
14
+ 'doctor',
15
+ 'explain',
16
+ ] as const;
17
+
18
+ export type TProteumCommandName = (typeof proteumCommandNames)[number];
19
+
20
+ type TProteumCommandExample = {
21
+ description: string;
22
+ command: string;
23
+ };
24
+
25
+ type TProteumCommandStatus = 'stable' | 'experimental';
26
+
27
+ export type TProteumCommandDoc = {
28
+ name: TProteumCommandName;
29
+ category: string;
30
+ summary: string;
31
+ usage: string;
32
+ bestFor: string;
33
+ examples: TProteumCommandExample[];
34
+ notes?: string[];
35
+ status?: TProteumCommandStatus;
36
+ };
37
+
38
+ export const proteumRecommendedFlow: TRow[] = [
39
+ { label: '1. proteum dev', value: 'Start the compiler, SSR server, and hot reload loop.' },
40
+ { label: '2. proteum refresh', value: 'Regenerate .proteum contracts and typings after source or framework changes.' },
41
+ { label: '3. proteum check', value: 'Refresh, typecheck, and lint before you commit or push.' },
42
+ { label: '4. proteum build --prod', value: 'Produce the production server and client bundles.' },
43
+ ];
44
+
45
+ export const proteumCommandGroups: Array<{ title: string; names: TProteumCommandName[] }> = [
46
+ { title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
47
+ { title: 'Quality gates', names: ['typecheck', 'lint', 'check'] },
48
+ { title: 'Manifest and contracts', names: ['doctor', 'explain'] },
49
+ { title: 'Project scaffolding', names: ['init'] },
50
+ ];
51
+
52
+ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> = {
53
+ init: {
54
+ name: 'init',
55
+ category: 'Project scaffolding',
56
+ summary: 'Scaffold a new Proteum project.',
57
+ usage: 'proteum init',
58
+ bestFor: 'Bootstrap a new app when the Proteum scaffold assets are installed in the current package.',
59
+ examples: [{ description: 'Create a new app interactively', command: 'proteum init' }],
60
+ notes: [
61
+ 'This command is still experimental.',
62
+ 'In source checkouts it requires `cli/skeleton` to exist.',
63
+ ],
64
+ status: 'experimental',
65
+ },
66
+ dev: {
67
+ name: 'dev',
68
+ category: 'Daily workflow',
69
+ summary: 'Start the local compiler, SSR server, and hot reload loop.',
70
+ usage: 'proteum dev [--port <port>] [--cache|--no-cache]',
71
+ bestFor:
72
+ 'Day-to-day app work. This is the main entrypoint used by the current reference apps during local development.',
73
+ examples: [
74
+ { description: 'Start the app on its configured router port', command: 'proteum dev' },
75
+ { description: 'Run a second Proteum app on another port', command: 'proteum dev --port 3101' },
76
+ {
77
+ description: 'Disable the filesystem cache while debugging compiler state',
78
+ command: 'proteum dev --no-cache',
79
+ },
80
+ ],
81
+ notes: ['Legacy single-dash long options remain supported, for example `proteum dev -port 3001`.'],
82
+ status: 'stable',
83
+ },
84
+ refresh: {
85
+ name: 'refresh',
86
+ category: 'Daily workflow',
87
+ summary: 'Refresh generated Proteum typings and artifacts.',
88
+ usage: 'proteum refresh',
89
+ bestFor:
90
+ 'Force regeneration of the framework-owned `.proteum` output when routes, controllers, services, or framework generation rules changed.',
91
+ examples: [
92
+ { description: 'Refresh generated contracts after source edits', command: 'proteum refresh' },
93
+ ],
94
+ notes: ['Use this when you need deterministic regeneration without starting the full dev loop.'],
95
+ status: 'stable',
96
+ },
97
+ build: {
98
+ name: 'build',
99
+ category: 'Daily workflow',
100
+ summary: 'Build the application.',
101
+ usage: 'proteum build [--prod] [--strict] [--cache] [--analyze] [--port <port>]',
102
+ bestFor: 'CI, release builds, and local verification of the production server and client output.',
103
+ examples: [
104
+ { description: 'Run the normal production build', command: 'proteum build --prod' },
105
+ {
106
+ description: 'Refresh typings, typecheck, then build in strict mode',
107
+ command: 'proteum build --prod --strict',
108
+ },
109
+ { description: 'Generate bundle analysis artifacts', command: 'proteum build --prod --analyze' },
110
+ { description: 'Reuse the filesystem cache during builds', command: 'proteum build --prod --cache' },
111
+ ],
112
+ notes: [
113
+ 'Legacy positional booleans remain supported, for example `proteum build prod strict analyze`.',
114
+ 'Use `--strict` when the build must refresh generated typings and fail on any TypeScript error before compilation starts.',
115
+ 'The production output is emitted under `bin/`.',
116
+ ],
117
+ status: 'stable',
118
+ },
119
+ typecheck: {
120
+ name: 'typecheck',
121
+ category: 'Quality gates',
122
+ summary: 'Run TypeScript typechecking for the application.',
123
+ usage: 'proteum typecheck',
124
+ bestFor: 'Fast verification that generated contracts and app code still satisfy the TypeScript surface.',
125
+ examples: [
126
+ { description: 'Typecheck every discovered client and server app tsconfig', command: 'proteum typecheck' },
127
+ ],
128
+ notes: ['Proteum refreshes generated typings before running TypeScript.'],
129
+ status: 'stable',
130
+ },
131
+ lint: {
132
+ name: 'lint',
133
+ category: 'Quality gates',
134
+ summary: 'Run ESLint for the application.',
135
+ usage: 'proteum lint [--fix]',
136
+ bestFor: 'Static code-quality validation across the current app root.',
137
+ examples: [
138
+ { description: 'Run ESLint in check mode', command: 'proteum lint' },
139
+ { description: 'Apply fixable lint changes', command: 'proteum lint --fix' },
140
+ ],
141
+ notes: ['Legacy positional usage such as `proteum lint fix` remains supported.'],
142
+ status: 'stable',
143
+ },
144
+ check: {
145
+ name: 'check',
146
+ category: 'Quality gates',
147
+ summary: 'Refresh typings, typecheck, then lint the application.',
148
+ usage: 'proteum check',
149
+ bestFor: 'One command before commits, pushes, or CI when you want the standard local validation path.',
150
+ examples: [{ description: 'Run the full default validation pipeline', command: 'proteum check' }],
151
+ notes: ['This command executes refresh, typecheck, then lint in that order.'],
152
+ status: 'stable',
153
+ },
154
+ doctor: {
155
+ name: 'doctor',
156
+ category: 'Manifest and contracts',
157
+ summary: 'Inspect the generated Proteum manifest diagnostics.',
158
+ usage: 'proteum doctor [--json] [--strict]',
159
+ bestFor:
160
+ 'Auditing manifest warnings and errors, especially in CI or when route/controller generation behaves unexpectedly.',
161
+ examples: [
162
+ { description: 'Print a human-readable diagnostic summary', command: 'proteum doctor' },
163
+ { description: 'Fail if any diagnostics exist', command: 'proteum doctor --strict' },
164
+ { description: 'Emit machine-readable diagnostics', command: 'proteum doctor --json' },
165
+ ],
166
+ notes: ['`--strict` is intended for CI and pre-release verification.'],
167
+ status: 'stable',
168
+ },
169
+ explain: {
170
+ name: 'explain',
171
+ category: 'Manifest and contracts',
172
+ summary: 'Explain the generated Proteum manifest.',
173
+ usage: 'proteum explain [--all|--app|--conventions|--env|--services|--controllers|--routes|--layouts|--diagnostics] [--json]',
174
+ bestFor:
175
+ 'Inspecting how source files became generated routes, controllers, layouts, services, and diagnostics without reading compiler internals.',
176
+ examples: [
177
+ { description: 'Show the default human summary', command: 'proteum explain' },
178
+ {
179
+ description: 'Inspect generated routes and controllers together',
180
+ command: 'proteum explain --routes --controllers',
181
+ },
182
+ { description: 'Emit the selected manifest sections as JSON', command: 'proteum explain --routes --json' },
183
+ ],
184
+ notes: ['Legacy positional section selection remains supported, for example `proteum explain routes services`.'],
185
+ status: 'stable',
186
+ },
187
+ };
188
+
189
+ export const isLikelyProteumAppRoot = (workdir: string) =>
190
+ fs.existsSync(path.join(workdir, 'package.json')) &&
191
+ fs.existsSync(path.join(workdir, 'identity.yaml')) &&
192
+ fs.existsSync(path.join(workdir, 'client')) &&
193
+ fs.existsSync(path.join(workdir, 'server'));
194
+
195
+ export const getInitAvailabilityNote = (initAvailable: boolean) =>
196
+ initAvailable
197
+ ? 'Scaffold assets are installed in this checkout.'
198
+ : 'This checkout does not include `cli/skeleton`, so `proteum init` is unavailable until the scaffold assets are restored.';
199
+
200
+ export const createClipanionUsage = (command: TProteumCommandDoc) => ({
201
+ category: command.category,
202
+ description: command.summary,
203
+ details: [
204
+ `Best for: ${command.bestFor}`,
205
+ ...(command.notes && command.notes.length > 0 ? [`Notes:\n${command.notes.map((note) => `- ${note}`).join('\n')}`] : []),
206
+ ].join('\n\n'),
207
+ examples: command.examples.map((example) => [example.description, example.command] as [string, string]),
208
+ });
@@ -0,0 +1,65 @@
1
+ const ansi = {
2
+ reset: '\u001b[0m',
3
+ bold: '\u001b[1m',
4
+ dim: '\u001b[2m',
5
+ red: '\u001b[31m',
6
+ green: '\u001b[32m',
7
+ cyan: '\u001b[36m',
8
+ } as const;
9
+
10
+ export type TCompileReporter = {
11
+ start: (compilerName: string, changedFiles: string[]) => void;
12
+ finish: (compilerName: string, options: { succeeded: boolean; durationMs: number }) => void;
13
+ stop: () => void;
14
+ };
15
+
16
+ const createNoopCompileReporter = (): TCompileReporter => ({
17
+ start() {},
18
+ finish() {},
19
+ stop() {},
20
+ });
21
+
22
+ const supportsColor = () => process.stdout.isTTY === true;
23
+
24
+ const colorize = (value: string, ...codes: string[]) => (supportsColor() ? `${codes.join('')}${value}${ansi.reset}` : value);
25
+
26
+ const formatDuration = (durationMs: number) => {
27
+ if (durationMs < 1000) return `${durationMs} ms`;
28
+ if (durationMs < 10_000) return `${(durationMs / 1000).toFixed(1)} s`;
29
+ return `${Math.round(durationMs / 1000)} s`;
30
+ };
31
+
32
+ const formatChangedFiles = (changedFilesCount: number) => {
33
+ if (changedFilesCount === 0) return 'initial build';
34
+ return `${changedFilesCount} changed file${changedFilesCount === 1 ? '' : 's'}`;
35
+ };
36
+
37
+ class ConsoleCompileReporter implements TCompileReporter {
38
+ public start(compilerName: string, changedFiles: string[]) {
39
+ console.info(
40
+ [
41
+ colorize('compiler:start', ansi.bold, ansi.cyan),
42
+ colorize(compilerName, ansi.bold),
43
+ colorize(formatChangedFiles(changedFiles.length), ansi.dim),
44
+ ].join(' '),
45
+ );
46
+ }
47
+
48
+ public finish(compilerName: string, { succeeded, durationMs }: { succeeded: boolean; durationMs: number }) {
49
+ console.info(
50
+ [
51
+ colorize(succeeded ? 'compiler:done' : 'compiler:fail', ansi.bold, succeeded ? ansi.green : ansi.red),
52
+ colorize(compilerName, ansi.bold),
53
+ colorize(formatDuration(durationMs), ansi.dim),
54
+ ].join(' '),
55
+ );
56
+ }
57
+
58
+ public stop() {}
59
+ }
60
+
61
+ export const createCompileReporter = ({ enabled }: { enabled: boolean }): TCompileReporter => {
62
+ if (!enabled) return createNoopCompileReporter();
63
+
64
+ return new ConsoleCompileReporter();
65
+ };