proteum 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -1
- package/README.md +375 -0
- package/agents/framework/AGENTS.md +917 -0
- package/agents/project/AGENTS.md +138 -0
- package/agents/{codex → project}/CODING_STYLE.md +3 -2
- package/agents/project/client/AGENTS.md +108 -0
- package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
- package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
- package/agents/project/server/services/AGENTS.md +170 -0
- package/agents/{codex → project}/tests/AGENTS.md +1 -0
- package/cli/app/config.ts +3 -2
- package/cli/app/index.ts +6 -66
- package/cli/bin.js +7 -2
- package/cli/commands/build.ts +94 -27
- package/cli/commands/check.ts +15 -1
- package/cli/commands/dev.ts +288 -132
- package/cli/commands/doctor.ts +108 -0
- package/cli/commands/explain.ts +226 -0
- package/cli/commands/init.ts +76 -70
- package/cli/commands/lint.ts +18 -1
- package/cli/commands/refresh.ts +16 -6
- package/cli/commands/typecheck.ts +14 -1
- package/cli/compiler/artifacts/controllers.ts +150 -0
- package/cli/compiler/artifacts/discovery.ts +132 -0
- package/cli/compiler/artifacts/manifest.ts +267 -0
- package/cli/compiler/artifacts/routing.ts +315 -0
- package/cli/compiler/artifacts/services.ts +480 -0
- package/cli/compiler/artifacts/shared.ts +12 -0
- package/cli/compiler/client/identite.ts +2 -1
- package/cli/compiler/client/index.ts +13 -3
- package/cli/compiler/common/controllers.ts +23 -28
- package/cli/compiler/common/files/style.ts +3 -4
- package/cli/compiler/common/generatedRouteModules.ts +333 -19
- package/cli/compiler/common/proteumManifest.ts +133 -0
- package/cli/compiler/index.ts +33 -896
- package/cli/compiler/server/index.ts +21 -4
- package/cli/context.ts +71 -0
- package/cli/index.ts +39 -181
- package/cli/presentation/commands.ts +208 -0
- package/cli/presentation/compileReporter.ts +65 -0
- package/cli/presentation/devSession.ts +70 -0
- package/cli/presentation/help.ts +193 -0
- package/cli/presentation/ink.ts +69 -0
- package/cli/presentation/layout.ts +83 -0
- package/cli/runtime/argv.ts +49 -0
- package/cli/runtime/command.ts +25 -0
- package/cli/runtime/commands.ts +221 -0
- package/cli/runtime/importEsm.ts +7 -0
- package/cli/runtime/verbose.ts +15 -0
- package/cli/utils/agents.ts +5 -4
- package/cli/utils/keyboard.ts +12 -6
- package/client/app/index.ts +0 -6
- package/client/services/router/index.tsx +1 -1
- package/client/services/router/response/index.tsx +2 -2
- package/common/dev/serverHotReload.ts +12 -0
- package/common/router/index.ts +3 -2
- package/common/router/layouts.ts +1 -1
- package/common/router/pageSetup.ts +1 -0
- package/package.json +10 -8
- package/prettier/router-registration-plugin.cjs +52 -0
- package/prettier.config.cjs +1 -0
- package/scripts/cleanup-generated-controllers.ts +2 -2
- package/scripts/fix-reference-app-typing.ts +2 -2
- package/scripts/format-router-registrations.ts +119 -0
- package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
- package/scripts/refactor-server-controllers.ts +19 -18
- package/scripts/refactor-server-runtime-aliases.ts +1 -1
- package/server/app/commands.ts +309 -25
- package/server/app/container/config.ts +1 -1
- package/server/app/container/index.ts +2 -2
- package/server/app/controller/index.ts +13 -4
- package/server/app/index.ts +53 -37
- package/server/app/service/container.ts +26 -28
- package/server/app/service/index.ts +10 -20
- package/server/app.tsconfig.json +9 -2
- package/server/index.ts +32 -1
- package/server/services/auth/index.ts +234 -15
- package/server/services/auth/router/index.ts +39 -7
- package/server/services/auth/router/request.ts +40 -8
- package/server/services/disks/index.ts +1 -1
- package/server/services/prisma/Facet.ts +2 -2
- package/server/services/prisma/index.ts +22 -5
- package/server/services/prisma/mariadb.ts +47 -0
- package/server/services/router/http/index.ts +9 -1
- package/server/services/router/index.ts +10 -4
- package/server/services/router/response/index.ts +26 -6
- package/types/auth-check-rules.test.ts +51 -0
- package/types/controller-request-context.test.ts +55 -0
- package/types/service-config.test.ts +39 -0
- package/agents/codex/AGENTS.md +0 -95
- package/agents/codex/client/AGENTS.md +0 -102
- package/agents/codex/server/services/AGENTS.md +0 -137
- package/server/services/models.7z +0 -0
- /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
|
-
//
|
|
112
|
-
|
|
113
|
-
request.startsWith(
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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
|
+
};
|