proteum 1.0.0 → 1.0.3

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 (70) hide show
  1. package/AGENTS.md +9 -0
  2. package/cli/app/config.ts +61 -0
  3. package/cli/app/index.ts +227 -0
  4. package/cli/bin.js +35 -0
  5. package/cli/commands/build.ts +60 -0
  6. package/cli/commands/deploy/app.ts +29 -0
  7. package/cli/commands/deploy/web.ts +60 -0
  8. package/cli/commands/dev.ts +124 -0
  9. package/cli/commands/init.ts +85 -0
  10. package/cli/commands/refresh.ts +18 -0
  11. package/cli/compiler/client/identite.ts +69 -0
  12. package/cli/compiler/client/index.ts +343 -0
  13. package/cli/compiler/common/babel/index.ts +173 -0
  14. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  15. package/cli/compiler/common/babel/plugins/services.ts +586 -0
  16. package/cli/compiler/common/babel/routes/imports.ts +127 -0
  17. package/cli/compiler/common/babel/routes/routes.ts +1170 -0
  18. package/cli/compiler/common/files/autres.ts +39 -0
  19. package/cli/compiler/common/files/images.ts +42 -0
  20. package/cli/compiler/common/files/style.ts +82 -0
  21. package/cli/compiler/common/index.ts +165 -0
  22. package/cli/compiler/index.ts +585 -0
  23. package/cli/compiler/server/index.ts +220 -0
  24. package/cli/index.ts +213 -0
  25. package/cli/paths.ts +165 -0
  26. package/cli/print.ts +12 -0
  27. package/cli/tsconfig.json +42 -0
  28. package/cli/utils/index.ts +22 -0
  29. package/cli/utils/keyboard.ts +78 -0
  30. package/client/app/index.ts +2 -0
  31. package/client/components/Dialog/Manager.tsx +3 -49
  32. package/client/components/Dialog/index.less +3 -1
  33. package/client/components/index.ts +1 -2
  34. package/client/services/router/index.tsx +6 -16
  35. package/common/errors/index.tsx +12 -31
  36. package/package.json +58 -22
  37. package/server/app/container/config.ts +20 -1
  38. package/server/app/container/console/index.ts +1 -1
  39. package/server/services/auth/index.ts +62 -27
  40. package/server/services/auth/router/request.ts +17 -6
  41. package/server/services/router/http/index.ts +3 -3
  42. package/server/services/router/response/index.ts +1 -1
  43. package/server/services/schema/request.ts +28 -10
  44. package/server/utils/slug.ts +0 -3
  45. package/tsconfig.common.json +2 -1
  46. package/types/global/constants.d.ts +12 -0
  47. package/changelog.md +0 -5
  48. package/client/components/Button.tsx +0 -298
  49. package/client/components/Dialog/card.tsx +0 -208
  50. package/client/data/input.ts +0 -32
  51. package/client/pages/bug.tsx.old +0 -60
  52. package/templates/composant.tsx +0 -40
  53. package/templates/form.ts +0 -30
  54. package/templates/modal.tsx +0 -47
  55. package/templates/modele.ts +0 -56
  56. package/templates/page.tsx +0 -74
  57. package/templates/route.ts +0 -43
  58. package/templates/service.ts +0 -75
  59. package/vscode/copyimportationpath/.eslintrc.json +0 -24
  60. package/vscode/copyimportationpath/.vscodeignore +0 -12
  61. package/vscode/copyimportationpath/CHANGELOG.md +0 -9
  62. package/vscode/copyimportationpath/README.md +0 -3
  63. package/vscode/copyimportationpath/copyimportationpath-0.0.1.vsix +0 -0
  64. package/vscode/copyimportationpath/out/extension.js +0 -206
  65. package/vscode/copyimportationpath/out/extension.js.map +0 -1
  66. package/vscode/copyimportationpath/package-lock.json +0 -4536
  67. package/vscode/copyimportationpath/package.json +0 -86
  68. package/vscode/copyimportationpath/src/extension.ts +0 -300
  69. package/vscode/copyimportationpath/tsconfig.json +0 -22
  70. package/vscode/copyimportationpath/vsc-extension-quickstart.md +0 -42
@@ -0,0 +1,586 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import * as types from '@babel/types'
7
+ import type { NodePath, PluginObj } from '@babel/core';
8
+ import generate from '@babel/generator';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { parse } from '@babel/parser';
12
+ import traverse from '@babel/traverse';
13
+
14
+ // Core
15
+ import cli from '@cli';
16
+ import { App, TAppSide } from '../../../../app';
17
+
18
+ /*----------------------------------
19
+ - WEBPACK RULE
20
+ ----------------------------------*/
21
+
22
+ type TOptions = {
23
+ side: TAppSide,
24
+ app: App,
25
+ debug?: boolean
26
+ }
27
+
28
+ /**
29
+ * Extended source type: now includes "models"
30
+ * so we can differentiate how we rewrite references.
31
+ */
32
+ type TImportSource = 'container' | 'services' | 'models' | 'request';
33
+
34
+ module.exports = (options: TOptions) => (
35
+ [Plugin, options]
36
+ )
37
+
38
+ type TImportedIndex = {
39
+ local: string,
40
+ imported: string, // The original “imported” name
41
+ references: NodePath<types.Node>[], // reference paths
42
+ source: TImportSource // container | application | models
43
+ }
44
+
45
+ type TRoutesIndex = {
46
+ byFile: Map<string, Set<string>>,
47
+ all: Set<string>,
48
+ initialized: boolean
49
+ }
50
+
51
+ const routesIndexByAppRoot = new Map<string, TRoutesIndex>();
52
+
53
+ function isRouteDecoratorExpression(expression: types.Expression): boolean {
54
+ return (
55
+ // Handles the case of @Route without parameters
56
+ (types.isIdentifier(expression) && expression.name === 'Route')
57
+ ||
58
+ // Handles the case of @Route() with parameters
59
+ (
60
+ types.isCallExpression(expression)
61
+ &&
62
+ types.isIdentifier(expression.callee)
63
+ &&
64
+ expression.callee.name === 'Route'
65
+ )
66
+ );
67
+ }
68
+
69
+ function getRoutePathFromDecoratorExpression(expression: types.Expression): string | undefined {
70
+ if (
71
+ !types.isCallExpression(expression)
72
+ ||
73
+ !types.isIdentifier(expression.callee)
74
+ ||
75
+ expression.callee.name !== 'Route'
76
+ )
77
+ return;
78
+
79
+ const firstArg = expression.arguments[0];
80
+ if (!types.isStringLiteral(firstArg))
81
+ return;
82
+
83
+ return firstArg.value;
84
+ }
85
+
86
+ function extractRoutePathsFromCode(code: string, filename: string): Set<string> {
87
+ const routePaths = new Set<string>();
88
+
89
+ let ast: ReturnType<typeof parse> | undefined;
90
+ try {
91
+ ast = parse(code, {
92
+ sourceType: 'module',
93
+ sourceFilename: filename,
94
+ plugins: [
95
+ 'typescript',
96
+ 'decorators-legacy',
97
+ 'jsx',
98
+ 'classProperties',
99
+ 'classPrivateProperties',
100
+ 'classPrivateMethods',
101
+ 'dynamicImport',
102
+ 'importMeta',
103
+ 'optionalChaining',
104
+ 'nullishCoalescingOperator',
105
+ 'topLevelAwait',
106
+ ],
107
+ });
108
+ } catch {
109
+ return routePaths;
110
+ }
111
+
112
+ traverse(ast, {
113
+ ClassMethod(path) {
114
+ const { node } = path;
115
+ if (!node.decorators || node.key.type !== 'Identifier')
116
+ return;
117
+
118
+ for (const decorator of node.decorators) {
119
+ if (!isRouteDecoratorExpression(decorator.expression))
120
+ continue;
121
+
122
+ const routePath = getRoutePathFromDecoratorExpression(decorator.expression);
123
+ if (routePath)
124
+ routePaths.add(routePath);
125
+ }
126
+ }
127
+ });
128
+
129
+ return routePaths;
130
+ }
131
+
132
+ function listTsFiles(dirPath: string): string[] {
133
+ let entries: fs.Dirent[];
134
+ try {
135
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
136
+ } catch {
137
+ return [];
138
+ }
139
+
140
+ const files: string[] = [];
141
+ for (const entry of entries) {
142
+ const fullPath = path.join(dirPath, entry.name);
143
+ if (entry.isDirectory()) {
144
+ files.push(...listTsFiles(fullPath));
145
+ continue;
146
+ }
147
+
148
+ const isTs = fullPath.endsWith('.ts') || fullPath.endsWith('.tsx');
149
+ if (!isTs || fullPath.endsWith('.d.ts'))
150
+ continue;
151
+
152
+ files.push(fullPath);
153
+ }
154
+ return files;
155
+ }
156
+
157
+ function initializeRoutesIndex(appRoot: string, index: TRoutesIndex) {
158
+ const servicesDir = path.join(appRoot, 'server', 'services');
159
+ const files = listTsFiles(servicesDir);
160
+
161
+ for (const file of files) {
162
+ let code: string;
163
+ try {
164
+ code = fs.readFileSync(file, 'utf8');
165
+ } catch {
166
+ continue;
167
+ }
168
+
169
+ const routes = extractRoutePathsFromCode(code, file);
170
+ index.byFile.set(file, routes);
171
+ }
172
+
173
+ index.all.clear();
174
+ for (const routes of index.byFile.values()) {
175
+ for (const route of routes)
176
+ index.all.add(route);
177
+ }
178
+ index.initialized = true;
179
+ }
180
+
181
+ function getRoutesIndex(appRoot: string): TRoutesIndex {
182
+ let index = routesIndexByAppRoot.get(appRoot);
183
+ if (!index) {
184
+ index = {
185
+ byFile: new Map(),
186
+ all: new Set(),
187
+ initialized: false
188
+ };
189
+ routesIndexByAppRoot.set(appRoot, index);
190
+ }
191
+
192
+ if (!index.initialized) {
193
+ initializeRoutesIndex(appRoot, index);
194
+ }
195
+
196
+ return index;
197
+ }
198
+
199
+ function updateRoutesIndexForFile(index: TRoutesIndex, filename: string, routePaths: Set<string>) {
200
+ index.byFile.set(filename, routePaths);
201
+
202
+ index.all.clear();
203
+ for (const routes of index.byFile.values()) {
204
+ for (const route of routes)
205
+ index.all.add(route);
206
+ }
207
+ }
208
+
209
+ /*----------------------------------
210
+ - PLUGIN
211
+ ----------------------------------*/
212
+ function Plugin(babel, { app, side, debug }: TOptions) {
213
+
214
+ const t = babel.types as typeof types;
215
+
216
+ /*
217
+ Transforms:
218
+ import { MyService, Environment } from '@app';
219
+ import { MyModel } from '@models';
220
+ ...
221
+ MyService.method();
222
+ Environment.name;
223
+ MyModel.someCall();
224
+
225
+ To:
226
+ import container from '<path>/server/app/container';
227
+ ...
228
+ container.Environment.name;
229
+ this.app.MyService.method();
230
+ this.app.Models.client.MyModel.someCall();
231
+
232
+ Processed files:
233
+ @/server/services
234
+ */
235
+
236
+ const plugin: PluginObj<{
237
+
238
+ debug: boolean,
239
+
240
+ filename: string,
241
+ processFile: boolean,
242
+
243
+ // Count how many total imports we transform
244
+ importedCount: number,
245
+ routePaths: Set<string>,
246
+ routesIndex: TRoutesIndex,
247
+ contextGuardedClassMethods: WeakSet<types.ClassMethod>,
248
+ routeDecoratedClassMethods: WeakSet<types.ClassMethod>,
249
+
250
+ // For every local identifier, store info about how it should be rewritten
251
+ imported: {
252
+ [localName: string]: TImportedIndex
253
+ }
254
+ }> = {
255
+
256
+ pre(state) {
257
+ this.filename = state.opts.filename as string;
258
+ this.processFile = this.filename.startsWith(cli.paths.appRoot + '/server/services');
259
+
260
+ this.imported = {};
261
+
262
+ this.importedCount = 0;
263
+ this.debug = debug || false;
264
+
265
+ this.routePaths = new Set();
266
+
267
+ this.routesIndex = getRoutesIndex(app.paths.root);
268
+ this.contextGuardedClassMethods = new WeakSet();
269
+ this.routeDecoratedClassMethods = new WeakSet();
270
+ },
271
+
272
+ visitor: {
273
+
274
+ // Detect decored methods before other plugins remove decorators
275
+ Program(path) {
276
+
277
+ if (!this.processFile) return;
278
+
279
+ // Traverse the AST within the Program node
280
+ path.traverse({
281
+ ClassMethod: (subPath) => {
282
+ const { node } = subPath;
283
+ if (!node.decorators || node.key.type !== 'Identifier') return;
284
+
285
+ for (const decorator of node.decorators) {
286
+
287
+ const isRoute = isRouteDecoratorExpression(decorator.expression);
288
+
289
+ if (!isRoute) continue;
290
+
291
+ this.routeDecoratedClassMethods.add(node);
292
+
293
+ const routePath = getRoutePathFromDecoratorExpression(decorator.expression);
294
+ if (routePath)
295
+ this.routePaths.add(routePath);
296
+ }
297
+ }
298
+ });
299
+
300
+ updateRoutesIndexForFile(this.routesIndex, this.filename, this.routePaths);
301
+ },
302
+
303
+ /**
304
+ * Detect import statements from '@app' or '@models'
305
+ */
306
+ ImportDeclaration(path) {
307
+ if (!this.processFile) return;
308
+
309
+ const source = path.node.source.value;
310
+ if (source !== '@app' && source !== '@models' && source !== '@request') {
311
+ return;
312
+ }
313
+
314
+ // For '@app' and '@models', gather imported symbols
315
+ for (const specifier of path.node.specifiers) {
316
+ if (specifier.type !== 'ImportSpecifier') continue;
317
+ if (specifier.imported.type !== 'Identifier') continue;
318
+
319
+ this.importedCount++;
320
+
321
+ let importSource: TImportSource;
322
+ switch (source) {
323
+ case '@app':
324
+ // Distinguish whether it's a container service or an application service
325
+ if (app.containerServices.includes(specifier.imported.name)) {
326
+ importSource = 'container';
327
+ } else {
328
+ importSource = 'services';
329
+ }
330
+ break;
331
+ case '@request':
332
+ importSource = 'request';
333
+ break;
334
+ case '@models':
335
+ // source === '@models'
336
+ importSource = 'models';
337
+ break;
338
+ default:
339
+ throw new Error(`Unknown import source: ${source}`);
340
+ }
341
+
342
+ this.imported[specifier.local.name] = {
343
+ local: specifier.local.name,
344
+ imported: specifier.imported.name,
345
+ references: path.scope.bindings[specifier.local.name].referencePaths,
346
+ source: importSource
347
+ };
348
+ }
349
+
350
+ // Remove the original import line(s) and replace with any needed new import
351
+ // For @app imports, we might import "container" if needed
352
+ // For @models, we don’t import anything
353
+ const replaceWith: any[] = [];
354
+
355
+ // If this line had container references, add a default import for container
356
+ // Example: import container from '<root>/server/app/container'
357
+ if (source === '@app') {
358
+ replaceWith.push(
359
+ t.importDeclaration(
360
+ [t.importDefaultSpecifier(t.identifier('container'))],
361
+ t.stringLiteral(
362
+ cli.paths.core.root + '/server/app/container'
363
+ )
364
+ )
365
+ );
366
+ }
367
+
368
+ // Replace the original import statement with our new import(s) if any
369
+ // or remove it entirely if no container references exist.
370
+ path.replaceWithMultiple(replaceWith);
371
+ },
372
+
373
+ CallExpression(path) {
374
+ if (!this.processFile)
375
+ return;
376
+
377
+ const classMethodPath = path.findParent(p => p.isClassMethod()) as NodePath<types.ClassMethod> | null;
378
+ if (!classMethodPath)
379
+ return;
380
+
381
+ // Ignore constructors
382
+ if (classMethodPath.node.kind === 'constructor')
383
+ return;
384
+
385
+ const callee = path.node.callee;
386
+ if (!t.isMemberExpression(callee) && !(t as any).isOptionalMemberExpression?.(callee))
387
+ return;
388
+
389
+ // Build member chain segments: this.app.<Service>.<...>
390
+ const segments: string[] = [];
391
+ let current: any = callee;
392
+ while (t.isMemberExpression(current) || (t as any).isOptionalMemberExpression?.(current)) {
393
+ const prop = current.property;
394
+ if (t.isIdentifier(prop)) {
395
+ segments.unshift(prop.name);
396
+ } else if (t.isStringLiteral(prop)) {
397
+ segments.unshift(prop.value);
398
+ } else {
399
+ return;
400
+ }
401
+ current = current.object;
402
+ }
403
+
404
+ if (!t.isThisExpression(current))
405
+ return;
406
+
407
+ // Expect: this.app.<Service>.<method>
408
+ if (segments.length < 3 || segments[0] !== 'app')
409
+ return;
410
+
411
+ const serviceLocalName = segments[1];
412
+ const importedRef = this.imported[serviceLocalName];
413
+ if (!importedRef || importedRef.source !== 'services')
414
+ return;
415
+
416
+ const routePath = [
417
+ importedRef.imported || serviceLocalName,
418
+ ...segments.slice(2)
419
+ ].join('/');
420
+
421
+ if (!this.routesIndex.all.has(routePath))
422
+ return;
423
+
424
+ // Ensure the parent function checks that `context` exists
425
+ if (!this.contextGuardedClassMethods.has(classMethodPath.node)) {
426
+ const guard = t.ifStatement(
427
+ t.binaryExpression(
428
+ '===',
429
+ t.unaryExpression('typeof', t.identifier('context')),
430
+ t.stringLiteral('undefined')
431
+ ),
432
+ t.blockStatement([
433
+ t.throwStatement(
434
+ t.newExpression(t.identifier('Error'), [
435
+ t.stringLiteral('context variable should be passed in this function')
436
+ ])
437
+ )
438
+ ])
439
+ );
440
+ classMethodPath.get('body').unshiftContainer('body', guard);
441
+ this.contextGuardedClassMethods.add(classMethodPath.node);
442
+ }
443
+
444
+ // Ensure call arguments: second argument is `context`
445
+ const args = path.node.arguments;
446
+ if (args.length === 0) {
447
+ args.push(t.identifier('undefined'), t.identifier('context'));
448
+ } else if (args.length === 1) {
449
+ args.push(t.identifier('context'));
450
+ } else {
451
+ args[1] = t.identifier('context') as any;
452
+ }
453
+ },
454
+
455
+ // This visitor fires for every class method.
456
+ ClassMethod(path) {
457
+
458
+ // Must be a server service
459
+ if (!this.processFile || path.replaced) return;
460
+
461
+ // Must have a method name
462
+ if (path.node.key.type !== 'Identifier') return;
463
+
464
+ // Track whether this exact method is decorated with @Route.
465
+ const methodName = path.node.key.name;
466
+ const isRouteMethod = this.routeDecoratedClassMethods.has(path.node);
467
+ let params = path.node.params;
468
+
469
+ // Prefix references
470
+ path.traverse({ Identifier: (subPath) => {
471
+
472
+ const { node } = subPath;
473
+ const name = node.name;
474
+ const ref = this.imported[name];
475
+ if (!ref || !ref.references) {
476
+ return;
477
+ }
478
+
479
+ // Find a specific binding that hasn't been replaced yet
480
+ const foundBinding = ref.references.find(binding => {
481
+ return subPath.getPathLocation() === binding.getPathLocation();
482
+ });
483
+
484
+ if (!foundBinding || foundBinding.replaced)
485
+ return;
486
+
487
+ // Mark as replaced to avoid loops
488
+ foundBinding.replaced = true;
489
+
490
+ // Based on the source, replace the identifier with the proper MemberExpression
491
+ if (ref.source === 'container') {
492
+ // container.[identifier]
493
+ // e.g. container.Environment
494
+ subPath.replaceWith(
495
+ t.memberExpression(
496
+ t.identifier('container'),
497
+ subPath.node
498
+ )
499
+ );
500
+ }
501
+ else if (ref.source === 'services') {
502
+ // this.app.[identifier]
503
+ // e.g. this.app.MyService
504
+ subPath.replaceWith(
505
+ t.memberExpression(
506
+ t.memberExpression(
507
+ t.thisExpression(),
508
+ t.identifier('app')
509
+ ),
510
+ subPath.node
511
+ )
512
+ );
513
+ }
514
+ else if (ref.source === 'models') {
515
+ // this.app.Models.client.[identifier]
516
+ // e.g. this.app.Models.client.MyModel
517
+ subPath.replaceWith(
518
+ t.memberExpression(
519
+ t.memberExpression(
520
+ t.memberExpression(
521
+ t.memberExpression(
522
+ t.thisExpression(),
523
+ t.identifier('app')
524
+ ),
525
+ t.identifier('Models')
526
+ ),
527
+ t.identifier('client')
528
+ ),
529
+ subPath.node
530
+ )
531
+ );
532
+ }
533
+ else if (ref.source === 'request') {
534
+ // Only route handlers receive the `context` parameter.
535
+ if (!isRouteMethod) {
536
+ throw subPath.buildCodeFrameError(
537
+ `@request import "${ref.local}" can only be used inside @Route methods (found in "${methodName}").`
538
+ );
539
+ }
540
+
541
+ // this.app.Models.client.[identifier]
542
+ // e.g. this.app.Models.client.MyModel
543
+ subPath.replaceWith(
544
+ t.memberExpression(
545
+ t.identifier('context'),
546
+ subPath.node
547
+ )
548
+ );
549
+ }
550
+
551
+ } });
552
+
553
+ if (
554
+ isRouteMethod
555
+ &&
556
+ path.node.params.length < 2
557
+ ) {
558
+
559
+ // Expose router context variable via the second parameter
560
+ params = [
561
+ path.node.params[0] || t.objectPattern([]),
562
+ t.identifier('context'),
563
+ ];
564
+
565
+ // Apply changes
566
+ path.replaceWith(
567
+ t.classMethod(
568
+ path.node.kind,
569
+ path.node.key,
570
+ params,
571
+ path.node.body,
572
+ false,
573
+ false,
574
+ false,
575
+ path.node.async
576
+ )
577
+ );
578
+ }
579
+
580
+ //console.log("ROUTE METHOD", this.filename, methodName, generate(path.node).code);
581
+ }
582
+ }
583
+ };
584
+
585
+ return plugin;
586
+ }