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.
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 +480 -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 +22 -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
@@ -0,0 +1,108 @@
1
+ import path from 'path';
2
+
3
+ import cli from '..';
4
+ import Compiler from '../compiler';
5
+ import {
6
+ readProteumManifest,
7
+ type TProteumManifest,
8
+ type TProteumManifestDiagnostic,
9
+ } from '../compiler/common/proteumManifest';
10
+
11
+ const allowedDoctorArgs = new Set(['json', 'strict']);
12
+
13
+ const validateDoctorArgs = () => {
14
+ const enabledArgs = Object.entries(cli.args)
15
+ .filter(([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true)
16
+ .map(([name]) => name);
17
+
18
+ const invalidArgs = enabledArgs.filter((arg) => !allowedDoctorArgs.has(arg));
19
+
20
+ if (invalidArgs.length > 0) {
21
+ throw new Error(
22
+ `Unknown doctor argument(s): ${invalidArgs.join(', ')}. Allowed values: ${[...allowedDoctorArgs].join(', ')}.`,
23
+ );
24
+ }
25
+ };
26
+
27
+ const normalizePath = (value: string) => value.replace(/\\/g, '/');
28
+
29
+ const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
30
+ const normalizedFilepath = normalizePath(filepath);
31
+ const normalizedAppRoot = normalizePath(manifest.app.root);
32
+ const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
33
+
34
+ if (normalizedFilepath === normalizedAppRoot) return '.';
35
+ if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
36
+ return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
37
+
38
+ if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
39
+ if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
40
+ return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
41
+
42
+ return normalizedFilepath;
43
+ };
44
+
45
+ const formatLocation = (diagnostic: TProteumManifestDiagnostic) =>
46
+ diagnostic.sourceLocation ? `:${diagnostic.sourceLocation.line}:${diagnostic.sourceLocation.column}` : '';
47
+
48
+ const renderGroup = (manifest: TProteumManifest, diagnostics: TProteumManifestDiagnostic[], title: string) => {
49
+ if (diagnostics.length === 0) return `${title}\n- none`;
50
+
51
+ return [
52
+ title,
53
+ ...diagnostics.map((diagnostic) => {
54
+ const related =
55
+ diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
56
+ ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
57
+ : '';
58
+
59
+ return `- ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic)}${related}`;
60
+ }),
61
+ ].join('\n');
62
+ };
63
+
64
+ export const run = async (): Promise<void> => {
65
+ validateDoctorArgs();
66
+
67
+ const compiler = new Compiler('dev');
68
+ await compiler.refreshGeneratedTypings();
69
+
70
+ const manifest = readProteumManifest(cli.paths.appRoot);
71
+ const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
72
+ const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
73
+
74
+ if (cli.args.json === true) {
75
+ console.log(
76
+ JSON.stringify(
77
+ {
78
+ summary: {
79
+ errors: errors.length,
80
+ warnings: warnings.length,
81
+ strictFailed: cli.args.strict === true && manifest.diagnostics.length > 0,
82
+ },
83
+ diagnostics: manifest.diagnostics,
84
+ },
85
+ null,
86
+ 2,
87
+ ),
88
+ );
89
+ } else if (manifest.diagnostics.length === 0) {
90
+ console.log('Proteum doctor\n- No manifest diagnostics were found.');
91
+ } else {
92
+ console.log(
93
+ [
94
+ 'Proteum doctor',
95
+ `- ${errors.length} errors`,
96
+ `- ${warnings.length} warnings`,
97
+ '',
98
+ renderGroup(manifest, errors, 'Errors'),
99
+ '',
100
+ renderGroup(manifest, warnings, 'Warnings'),
101
+ ].join('\n'),
102
+ );
103
+ }
104
+
105
+ if (cli.args.strict === true && manifest.diagnostics.length > 0) {
106
+ throw new Error(`Proteum doctor failed in strict mode with ${errors.length} errors and ${warnings.length} warnings.`);
107
+ }
108
+ };
@@ -0,0 +1,226 @@
1
+ import path from 'path';
2
+
3
+ import cli from '..';
4
+ import Compiler from '../compiler';
5
+ import {
6
+ readProteumManifest,
7
+ type TProteumManifestDiagnostic,
8
+ type TProteumManifest,
9
+ type TProteumManifestController,
10
+ type TProteumManifestLayout,
11
+ type TProteumManifestRoute,
12
+ type TProteumManifestService,
13
+ } from '../compiler/common/proteumManifest';
14
+
15
+ const explainSectionNames = ['app', 'conventions', 'env', 'services', 'controllers', 'routes', 'layouts', 'diagnostics'] as const;
16
+ const allowedExplainArgs = new Set(['json', 'all', ...explainSectionNames]);
17
+
18
+ type TExplainSectionName = (typeof explainSectionNames)[number];
19
+
20
+ const validateExplainArgs = () => {
21
+ const enabledArgs = Object.entries(cli.args)
22
+ .filter(([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true)
23
+ .map(([name]) => name);
24
+
25
+ const invalidArgs = enabledArgs.filter((arg) => !allowedExplainArgs.has(arg));
26
+
27
+ if (invalidArgs.length > 0) {
28
+ throw new Error(
29
+ `Unknown explain argument(s): ${invalidArgs.join(', ')}. Allowed values: ${[...allowedExplainArgs].join(', ')}.`,
30
+ );
31
+ }
32
+ };
33
+
34
+ const getSelectedSections = (): TExplainSectionName[] => {
35
+ if (cli.args.all === true) return [...explainSectionNames];
36
+
37
+ return explainSectionNames.filter((sectionName) => cli.args[sectionName] === true);
38
+ };
39
+
40
+ const pickManifestSections = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
41
+ if (sectionNames.length === 0) return manifest;
42
+
43
+ const selected: Record<string, unknown> = {};
44
+
45
+ for (const sectionName of sectionNames) {
46
+ selected[sectionName] = manifest[sectionName];
47
+ }
48
+
49
+ return selected;
50
+ };
51
+
52
+ const normalizePath = (value: string) => value.replace(/\\/g, '/');
53
+ const formatLocation = (line?: number, column?: number) =>
54
+ line && column ? `:${line}:${column}` : line ? `:${line}` : '';
55
+
56
+ const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
57
+ const normalizedFilepath = normalizePath(filepath);
58
+ const normalizedAppRoot = normalizePath(manifest.app.root);
59
+ const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
60
+
61
+ if (normalizedFilepath === normalizedAppRoot) return '.';
62
+ if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
63
+ return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
64
+
65
+ if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
66
+ if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
67
+ return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
68
+
69
+ return normalizedFilepath;
70
+ };
71
+
72
+ const formatService = (manifest: TProteumManifest, service: TProteumManifestService) => {
73
+ if (service.kind === 'ref') {
74
+ return `- ${service.registeredName} -> ref ${service.refTo} [${service.parent}]`;
75
+ }
76
+
77
+ const source = service.metasFilepath ? formatFilepath(manifest, service.metasFilepath) : 'unknown';
78
+ return `- ${service.registeredName} -> ${service.id} (${service.metaName}) [${service.scope}] priority=${service.priority} source=${source}`;
79
+ };
80
+
81
+ const formatController = (manifest: TProteumManifest, controller: TProteumManifestController) =>
82
+ `- ${controller.clientAccessor} -> POST ${controller.httpPath} [${controller.scope}] input=${controller.hasInput ? 'yes' : 'no'} source=${formatFilepath(manifest, controller.filepath)}${formatLocation(controller.sourceLocation.line, controller.sourceLocation.column)}#${controller.methodName}`;
83
+
84
+ const formatRouteTarget = (route: TProteumManifestRoute) => {
85
+ if (route.kind === 'client-error') return route.code !== undefined ? String(route.code) : route.codeRaw || '?';
86
+ return route.path || route.pathRaw || '?';
87
+ };
88
+
89
+ const formatRoute = (manifest: TProteumManifest, route: TProteumManifestRoute) => {
90
+ const chunk = route.chunkId ? ` chunk=${route.chunkId}` : '';
91
+ const setup = route.hasSetup ? ' setup=yes' : ' setup=no';
92
+ const options = route.normalizedOptionKeys.length > 0 ? ` options=${route.normalizedOptionKeys.join(',')}` : '';
93
+ const resolution = route.targetResolution !== 'literal' ? ` resolution=${route.targetResolution}` : '';
94
+
95
+ return `- ${route.kind} ${route.methodName} ${formatRouteTarget(route)} [${route.scope}]${chunk}${setup}${options}${resolution} source=${formatFilepath(manifest, route.filepath)}${formatLocation(route.sourceLocation.line, route.sourceLocation.column)}`;
96
+ };
97
+
98
+ const formatLayout = (manifest: TProteumManifest, layout: TProteumManifestLayout) =>
99
+ `- ${layout.chunkId || 'root'} depth=${layout.depth} [${layout.scope}] source=${formatFilepath(manifest, layout.filepath)}`;
100
+
101
+ const formatDiagnostic = (manifest: TProteumManifest, diagnostic: TProteumManifestDiagnostic) => {
102
+ const related =
103
+ diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
104
+ ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
105
+ : '';
106
+
107
+ return `- [${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
108
+ };
109
+
110
+ const printSection = (title: string, lines: string[]) => {
111
+ if (lines.length === 0) return `${title}\n- none`;
112
+ return `${title}\n${lines.join('\n')}`;
113
+ };
114
+
115
+ const renderSummary = (manifest: TProteumManifest) => {
116
+ const errorsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
117
+ const warningsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
118
+ const lines = [
119
+ `Proteum manifest: ${formatFilepath(manifest, path.join(manifest.app.root, '.proteum', 'manifest.json'))}`,
120
+ `App: ${manifest.app.identity.name} (${manifest.app.identity.identifier})`,
121
+ `Env keys: ${manifest.env.loadedTopLevelKeys.join(', ') || 'none'}`,
122
+ `Services: ${manifest.services.app.length} app, ${manifest.services.routerPlugins.length} router plugins`,
123
+ `Controllers: ${manifest.controllers.length}`,
124
+ `Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
125
+ `Layouts: ${manifest.layouts.length}`,
126
+ `Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
127
+ 'Use `proteum explain --json` for the full machine-readable manifest or pass section flags like `routes` and `services`.',
128
+ ];
129
+
130
+ return lines.join('\n');
131
+ };
132
+
133
+ const renderHuman = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
134
+ if (sectionNames.length === 0) return renderSummary(manifest);
135
+
136
+ const sections: string[] = [];
137
+
138
+ for (const sectionName of sectionNames) {
139
+ if (sectionName === 'app') {
140
+ sections.push(
141
+ printSection('App', [
142
+ `- root=${formatFilepath(manifest, manifest.app.root)}`,
143
+ `- coreRoot=${formatFilepath(manifest, manifest.app.coreRoot)}`,
144
+ `- identity=${formatFilepath(manifest, manifest.app.identityFilepath)}`,
145
+ `- name=${manifest.app.identity.name}`,
146
+ `- identifier=${manifest.app.identity.identifier}`,
147
+ `- title=${manifest.app.identity.fullTitle || manifest.app.identity.title || manifest.app.identity.name}`,
148
+ ]),
149
+ );
150
+ continue;
151
+ }
152
+
153
+ if (sectionName === 'conventions') {
154
+ sections.push(
155
+ printSection('Conventions', [
156
+ `- routeSetupOptionKeys=${manifest.conventions.routeSetupOptionKeys.join(', ')}`,
157
+ `- reservedRouteSetupKeys=${manifest.conventions.reservedRouteSetupKeys.join(', ')}`,
158
+ ]),
159
+ );
160
+ continue;
161
+ }
162
+
163
+ if (sectionName === 'env') {
164
+ sections.push(
165
+ printSection('Env', [
166
+ `- source=${formatFilepath(manifest, manifest.env.sourceFilepath)}`,
167
+ `- loadedTopLevelKeys=${manifest.env.loadedTopLevelKeys.join(', ') || 'none'}`,
168
+ `- requiredTopLevelKeys=${manifest.env.requiredTopLevelKeys.join(', ')}`,
169
+ ]),
170
+ );
171
+ continue;
172
+ }
173
+
174
+ if (sectionName === 'services') {
175
+ sections.push(
176
+ printSection('App Services', manifest.services.app.map((service) => formatService(manifest, service))),
177
+ printSection(
178
+ 'Router Plugins',
179
+ manifest.services.routerPlugins.map((service) => formatService(manifest, service)),
180
+ ),
181
+ );
182
+ continue;
183
+ }
184
+
185
+ if (sectionName === 'controllers') {
186
+ sections.push(printSection('Controllers', manifest.controllers.map((controller) => formatController(manifest, controller))));
187
+ continue;
188
+ }
189
+
190
+ if (sectionName === 'routes') {
191
+ sections.push(
192
+ printSection('Client Routes', manifest.routes.client.map((route) => formatRoute(manifest, route))),
193
+ printSection('Server Routes', manifest.routes.server.map((route) => formatRoute(manifest, route))),
194
+ );
195
+ continue;
196
+ }
197
+
198
+ if (sectionName === 'layouts') {
199
+ sections.push(printSection('Layouts', manifest.layouts.map((layout) => formatLayout(manifest, layout))));
200
+ continue;
201
+ }
202
+
203
+ if (sectionName === 'diagnostics') {
204
+ sections.push(printSection('Diagnostics', manifest.diagnostics.map((diagnostic) => formatDiagnostic(manifest, diagnostic))));
205
+ }
206
+ }
207
+
208
+ return sections.join('\n\n');
209
+ };
210
+
211
+ export const run = async (): Promise<void> => {
212
+ validateExplainArgs();
213
+
214
+ const compiler = new Compiler('dev');
215
+ await compiler.refreshGeneratedTypings();
216
+
217
+ const manifest = readProteumManifest(cli.paths.appRoot);
218
+ const selectedSections = getSelectedSections();
219
+
220
+ if (cli.args.json === true) {
221
+ console.log(JSON.stringify(pickManifestSections(manifest, selectedSections), null, 2));
222
+ return;
223
+ }
224
+
225
+ console.log(renderHuman(manifest, selectedSections));
226
+ };
@@ -8,6 +8,7 @@ import path from 'path';
8
8
  import prompts from 'prompts';
9
9
  import cmd from 'node-cmd';
10
10
  import replaceOnce from 'replace-once';
11
+ import { UsageError } from 'clipanion';
11
12
 
12
13
  // Cor elibs
13
14
  import cli from '..';
@@ -19,73 +20,78 @@ const filesToConfig = ['package.json', 'identity.yaml'];
19
20
  /*----------------------------------
20
21
  - COMMANDE
21
22
  ----------------------------------*/
22
- export const run = () =>
23
- new Promise<void>(async () => {
24
- const config = await prompts([
25
- {
26
- type: 'text',
27
- name: 'name',
28
- message: 'Project name ?',
29
- initial: 'MyProject',
30
- validate: (value) =>
31
- /[a-z0-9\-\.]/i.test(value) || 'Must only include alphanumeric characters, and - . ',
32
- },
33
- {
34
- type: 'text',
35
- name: 'dirname',
36
- message: 'Folder name ?',
37
- initial: (value) => value.toLowerCase(),
38
- validate: (value) =>
39
- /[a-z0-9\-\.]/.test(value) || 'Must only include lowercase alphanumeric characters, and - . ',
40
- },
41
- {
42
- type: 'text',
43
- name: 'description',
44
- message: 'Briefly describe your project to your mom:',
45
- initial: 'It will revolutionnize the world',
46
- validate: (value) =>
47
- /[a-z0-9\-\. ]/i.test(value) || 'Must only include alphanumeric characters, and - . ',
48
- },
49
- { type: 'toggle', name: 'microservice', message: 'Separate API from the UI servers ?' },
50
- ]);
51
-
52
- const placeholders = {
53
- PROJECT_NAME: config.name,
54
- PACKAGE_NAME: config.name.toLowerCase(),
55
- PROJECT_DESCRIPTION: config.description,
56
- };
57
-
58
- const paths = {
59
- skeleton: path.join(cli.paths.core.cli, 'skeleton'),
60
- project: path.join(process.cwd(), config.dirname),
61
- };
62
-
63
- // Copy skeleton to cwd/<project-name>
64
- console.info('Creating project skeleton ...');
65
- fs.copySync(paths.skeleton, paths.project);
66
-
67
- // Sync framework-owned Codex assets into the new project.
68
- ensureProjectAgentSymlinks({ appRoot: paths.project, coreRoot: cli.paths.core.root });
69
-
70
- // Replace placeholders
71
- console.info('Configuring project ...');
72
- for (const file of filesToConfig) {
73
- console.log('- ' + file);
74
-
75
- const filepath = path.join(paths.project, file);
76
- const content = fs.readFileSync(filepath, 'utf-8');
77
-
78
- const placeholders_keys = Object.keys(placeholders).map((k) => '{{ ' + k + ' }}');
79
- const values = Object.values(placeholders);
80
-
81
- fs.writeFileSync(filepath, replaceOnce(content, placeholders_keys, values));
82
- }
83
-
84
- // Npm install
85
- console.info('Installing packages ...');
86
- cmd.runSync(`cd "${paths.project}" && npm i`);
87
-
88
- // Run demo app
89
- /*console.info("Run demo ...");
90
- await cli.shell('5htp dev');*/
91
- });
23
+ export const run = async (): Promise<void> => {
24
+ const skeletonPath = path.join(cli.paths.core.cli, 'skeleton');
25
+
26
+ if (!fs.existsSync(skeletonPath)) {
27
+ throw new UsageError(
28
+ 'Proteum init is unavailable in this checkout because `cli/skeleton` is missing. Restore the scaffold assets or use an installed Proteum package that includes them.',
29
+ );
30
+ }
31
+
32
+ const config = await prompts([
33
+ {
34
+ type: 'text',
35
+ name: 'name',
36
+ message: 'Project name ?',
37
+ initial: 'MyProject',
38
+ validate: (value) => /[a-z0-9\-\.]/i.test(value) || 'Must only include alphanumeric characters, and - . ',
39
+ },
40
+ {
41
+ type: 'text',
42
+ name: 'dirname',
43
+ message: 'Folder name ?',
44
+ initial: (value) => value.toLowerCase(),
45
+ validate: (value) =>
46
+ /[a-z0-9\-\.]/.test(value) || 'Must only include lowercase alphanumeric characters, and - . ',
47
+ },
48
+ {
49
+ type: 'text',
50
+ name: 'description',
51
+ message: 'Briefly describe your project to your mom:',
52
+ initial: 'It will revolutionnize the world',
53
+ validate: (value) => /[a-z0-9\-\. ]/i.test(value) || 'Must only include alphanumeric characters, and - . ',
54
+ },
55
+ { type: 'toggle', name: 'microservice', message: 'Separate API from the UI servers ?' },
56
+ ]);
57
+
58
+ const placeholders = {
59
+ PROJECT_NAME: config.name,
60
+ PACKAGE_NAME: config.name.toLowerCase(),
61
+ PROJECT_DESCRIPTION: config.description,
62
+ };
63
+
64
+ const paths = {
65
+ skeleton: skeletonPath,
66
+ project: path.join(process.cwd(), config.dirname),
67
+ };
68
+
69
+ // Copy skeleton to cwd/<project-name>
70
+ console.info('Creating project skeleton ...');
71
+ fs.copySync(paths.skeleton, paths.project);
72
+
73
+ // Sync framework-owned Codex assets into the new project.
74
+ ensureProjectAgentSymlinks({ appRoot: paths.project, coreRoot: cli.paths.core.root });
75
+
76
+ // Replace placeholders
77
+ console.info('Configuring project ...');
78
+ for (const file of filesToConfig) {
79
+ console.log('- ' + file);
80
+
81
+ const filepath = path.join(paths.project, file);
82
+ const content = fs.readFileSync(filepath, 'utf-8');
83
+
84
+ const placeholders_keys = Object.keys(placeholders).map((k) => '{{ ' + k + ' }}');
85
+ const values = Object.values(placeholders);
86
+
87
+ fs.writeFileSync(filepath, replaceOnce(content, placeholders_keys, values));
88
+ }
89
+
90
+ // Npm install
91
+ console.info('Installing packages ...');
92
+ cmd.runSync(`cd "${paths.project}" && npm i`);
93
+
94
+ // Run demo app
95
+ /*console.info("Run demo ...");
96
+ await cli.shell('5htp dev');*/
97
+ };
@@ -1,11 +1,13 @@
1
1
  import cli from '..';
2
2
  import { runAppLint } from '../utils/check';
3
+ import { renderRows } from '../presentation/layout';
4
+ import { renderStep, renderSuccess, renderTitle } from '../presentation/ink';
3
5
 
4
6
  const allowedLintArgs = new Set(['fix']);
5
7
 
6
8
  const validateLintArgs = () => {
7
9
  const enabledArgs = Object.entries(cli.args)
8
- .filter(([name, value]) => name !== 'workdir' && value === true)
10
+ .filter(([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true)
9
11
  .map(([name]) => name);
10
12
 
11
13
  const invalidArgs = enabledArgs.filter((arg) => !allowedLintArgs.has(arg));
@@ -17,5 +19,20 @@ const validateLintArgs = () => {
17
19
  export const run = async (): Promise<void> => {
18
20
  validateLintArgs();
19
21
 
22
+ console.info(
23
+ [
24
+ await renderTitle(
25
+ 'PROTEUM LINT',
26
+ cli.args.fix === true ? 'Running ESLint with fix mode enabled.' : 'Running ESLint in check mode.',
27
+ ),
28
+ renderRows([
29
+ { label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot },
30
+ { label: 'fix', value: cli.args.fix === true ? 'enabled' : 'disabled' },
31
+ ]),
32
+ await renderStep('[1/1]', cli.args.fix === true ? 'Applying fixable ESLint changes.' : 'Checking ESLint rules.'),
33
+ ].join('\n\n'),
34
+ );
35
+
20
36
  await runAppLint({ fix: cli.args.fix === true });
37
+ console.info(await renderSuccess(cli.args.fix === true ? 'Lint fixes applied.' : 'Lint passed.'));
21
38
  };
@@ -4,15 +4,25 @@
4
4
 
5
5
  // Configs
6
6
  import Compiler from '../compiler';
7
+ import cli from '..';
8
+ import { renderRows } from '../presentation/layout';
9
+ import { renderStep, renderSuccess, renderTitle } from '../presentation/ink';
7
10
 
8
11
  /*----------------------------------
9
12
  - COMMAND
10
13
  ----------------------------------*/
11
- export const run = (): Promise<void> =>
12
- new Promise(async (resolve) => {
13
- const compiler = new Compiler('dev');
14
+ export const run = async (): Promise<void> => {
15
+ const compiler = new Compiler('dev');
14
16
 
15
- await compiler.refreshGeneratedTypings();
17
+ console.info(
18
+ [
19
+ await renderTitle('PROTEUM REFRESH', 'Regenerating framework-owned contracts and typings.'),
20
+ renderRows([{ label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot }]),
21
+ await renderStep('[1/1]', 'Refreshing `.proteum` artifacts and generated typings.'),
22
+ ].join('\n\n'),
23
+ );
16
24
 
17
- resolve();
18
- });
25
+ await compiler.refreshGeneratedTypings();
26
+
27
+ console.info(await renderSuccess('Generated artifacts and typings are up to date.'));
28
+ };
@@ -1,8 +1,12 @@
1
1
  import cli from '..';
2
2
  import { refreshGeneratedTypings, runAppTypecheck } from '../utils/check';
3
+ import { renderRows } from '../presentation/layout';
4
+ import { renderStep, renderSuccess, renderTitle } from '../presentation/ink';
3
5
 
4
6
  const validateTypecheckArgs = () => {
5
- const enabledArgs = Object.entries(cli.args).filter(([name, value]) => name !== 'workdir' && value === true);
7
+ const enabledArgs = Object.entries(cli.args).filter(
8
+ ([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true,
9
+ );
6
10
 
7
11
  if (enabledArgs.length > 0)
8
12
  throw new Error(
@@ -13,6 +17,15 @@ const validateTypecheckArgs = () => {
13
17
  export const run = async (): Promise<void> => {
14
18
  validateTypecheckArgs();
15
19
 
20
+ console.info(
21
+ [
22
+ await renderTitle('PROTEUM TYPECHECK', 'Refreshing generated contracts, then running TypeScript.'),
23
+ renderRows([{ label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot }]),
24
+ ].join('\n\n'),
25
+ );
26
+ console.info(await renderStep('[1/2]', 'Refreshing generated typings.'));
16
27
  await refreshGeneratedTypings();
28
+ console.info(await renderStep('[2/2]', 'Running TypeScript typechecking.'));
17
29
  await runAppTypecheck();
30
+ console.info(await renderSuccess('Typecheck passed.'));
18
31
  };