proteum 1.0.2 → 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.
package/AGENTS.md ADDED
@@ -0,0 +1,9 @@
1
+ # Workflow
2
+
3
+ - Everytime I input error messages without any instructions, don't implement fixes.
4
+ Instead, ivestigate the potential causes of the errors, and for each:
5
+ 1. Evaluate / quantify the probabiliies
6
+ 2. Give why and
7
+ 3. Suggest how to fix it
8
+ - When you have finished your work, summarize in one top-level short sentence ALL the changes you made since the beginning of the WHOLE conversation. Output as "Commit message". Max 90 characters.
9
+
package/cli/app/config.ts CHANGED
@@ -24,7 +24,8 @@ export default class ConfigParser {
24
24
 
25
25
  public constructor(
26
26
  public appDir: string,
27
- public envName?: string
27
+ public envName?: string,
28
+ public routerPortOverride?: number
28
29
  ) {
29
30
 
30
31
  }
@@ -43,6 +44,12 @@ export default class ConfigParser {
43
44
  const envFile = this.loadYaml( envFileName );
44
45
  return {
45
46
  ...envFile,
47
+ router: this.routerPortOverride === undefined
48
+ ? envFile.router
49
+ : {
50
+ ...envFile.router,
51
+ port: this.routerPortOverride
52
+ },
46
53
  version: 'CLI'
47
54
  }
48
55
  }
package/cli/app/index.ts CHANGED
@@ -37,6 +37,23 @@ type TServiceSubservices = {
37
37
  [key: string]: TServiceSetup | TServiceRef
38
38
  }
39
39
 
40
+ const parseRouterPortOverride = (
41
+ rawPort: string | boolean | string[] | undefined
42
+ ): number | undefined => {
43
+
44
+ if (rawPort === undefined || rawPort === '')
45
+ return undefined;
46
+
47
+ if (typeof rawPort !== 'string')
48
+ throw new Error(`Invalid value for -port: expected a numeric value.`);
49
+
50
+ const port = Number(rawPort);
51
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
52
+ throw new Error(`Invalid value for -port: "${rawPort}". Expected an integer between 1 and 65535.`);
53
+
54
+ return port;
55
+ }
56
+
40
57
  /*----------------------------------
41
58
  - SERVICE
42
59
  ----------------------------------*/
@@ -49,6 +66,8 @@ export class App {
49
66
 
50
67
  public env: TEnvConfig;
51
68
 
69
+ public routerPortOverride?: number;
70
+
52
71
  public packageJson: {[key: string]: any};
53
72
 
54
73
  public buildId: number = Date.now();
@@ -57,6 +76,7 @@ export class App {
57
76
 
58
77
  root: cli.paths.appRoot,
59
78
  bin: path.join( cli.paths.appRoot, 'bin'),
79
+ dev: path.join( cli.paths.appRoot, 'dev'),
60
80
  data: path.join( cli.paths.appRoot, 'var', 'data'),
61
81
  public: path.join( cli.paths.appRoot, 'public'),
62
82
  pages: path.join( cli.paths.appRoot, 'client', 'pages'),
@@ -92,13 +112,25 @@ export class App {
92
112
  public constructor() {
93
113
 
94
114
  cli.debug && console.log(`[cli] Loading app config ...`);
95
- const configParser = new ConfigParser( cli.paths.appRoot );
115
+ this.routerPortOverride = parseRouterPortOverride( cli.args.port );
116
+
117
+ const configParser = new ConfigParser(
118
+ cli.paths.appRoot,
119
+ undefined,
120
+ this.routerPortOverride
121
+ );
96
122
  this.identity = configParser.identity();
97
123
  this.env = configParser.env();
98
124
  this.packageJson = this.loadPkg();
99
125
 
100
126
  }
101
127
 
128
+ public outputPath(target: 'dev' | 'bin') {
129
+ return target === 'dev'
130
+ ? this.paths.dev
131
+ : this.paths.bin;
132
+ }
133
+
102
134
  /*----------------------------------
103
135
  - ALIAS
104
136
  ----------------------------------*/
@@ -2,33 +2,59 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
- // Npm
6
- import prompts from 'prompts';
5
+ // Core
6
+ import cli from '..';
7
7
 
8
8
  // Configs
9
9
  import Compiler from '../compiler';
10
+ import type { TCompileMode } from '../compiler/common';
10
11
 
11
- /*----------------------------------
12
- - TYPES
13
- ----------------------------------*/
12
+ const allowedBuildArgs = new Set(['dev', 'prod', 'analyze', 'cache']);
14
13
 
15
14
  /*----------------------------------
16
15
  - COMMAND
17
16
  ----------------------------------*/
18
- export const run = (): Promise<void> => new Promise(async (resolve) => {
17
+ function resolveBuildMode(): TCompileMode {
18
+
19
+ const enabledArgs = Object.entries(cli.args)
20
+ .filter(([name, value]) => name !== 'workdir' && value === true)
21
+ .map(([name]) => name);
22
+
23
+ const invalidArgs = enabledArgs.filter(arg => !allowedBuildArgs.has(arg));
24
+ if (invalidArgs.length > 0)
25
+ throw new Error(`Unknown build argument(s): ${invalidArgs.join(', ')}. Allowed values: dev, prod, analyze, cache.`);
19
26
 
20
- const compiler = new Compiler('prod');
27
+ const requestedModes = enabledArgs.filter((arg): arg is TCompileMode =>
28
+ arg === 'dev' || arg === 'prod'
29
+ );
30
+ if (requestedModes.length > 1)
31
+ throw new Error(`Please specify only one build mode. Received: ${requestedModes.join(', ')}.`);
21
32
 
33
+ return requestedModes[0] ?? 'dev';
34
+ }
35
+
36
+ export const run = async (): Promise<void> => {
37
+
38
+ const mode = resolveBuildMode();
39
+ const compiler = new Compiler(mode, {}, false, 'bin');
22
40
  const multiCompiler = await compiler.create();
23
41
 
24
- multiCompiler.run((error, stats) => {
42
+ await new Promise<void>((resolve, reject) => {
43
+ multiCompiler.run((error, stats) => {
44
+
45
+ if (error) {
46
+ console.error("An error occurred during the compilation:", error);
47
+ reject(error);
48
+ return;
49
+ }
25
50
 
26
- if (error) {
27
- console.error("An error occurred during the compilation:", error);
28
- throw error;
29
- }
51
+ if (stats?.hasErrors()) {
52
+ reject(new Error(`Compilation failed for build mode "${mode}".`));
53
+ return;
54
+ }
30
55
 
31
- //resolve();
56
+ resolve();
57
+ });
32
58
 
33
59
  });
34
- });
60
+ };
@@ -3,7 +3,7 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Npm
6
- import fs from 'fs-extra';
6
+ import path from 'path';
7
7
  import { spawn, ChildProcess } from 'child_process';
8
8
 
9
9
  // Cor elibs
@@ -16,6 +16,8 @@ import Compiler from '../compiler';
16
16
  // Core
17
17
  import { app, App } from '../app';
18
18
 
19
+ const ignoredWatchPathPatterns = /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)/;
20
+
19
21
  /*----------------------------------
20
22
  - COMMANDE
21
23
  ----------------------------------*/
@@ -39,6 +41,7 @@ export const run = () => new Promise<void>(async () => {
39
41
  });
40
42
 
41
43
  const multiCompiler = await compiler.create();
44
+ const ignoredOutputPaths = [app.paths.bin, app.paths.dev].map(normalizeWatchPath);
42
45
 
43
46
  multiCompiler.watch({
44
47
 
@@ -51,7 +54,15 @@ export const run = () => new Promise<void>(async () => {
51
54
  // Ignore updated from:
52
55
  // - Node modules except 5HTP core (framework dev mode)
53
56
  // - Generated files during runtime (cause infinite loop. Ex: models.d.ts)
54
- ignored: /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)/
57
+ // - Webpack output folders (`./dev`, legacy `./bin`)
58
+ ignored: (watchPath: string) => {
59
+ const normalizedPath = normalizeWatchPath(watchPath);
60
+ return ignoredWatchPathPatterns.test(normalizedPath)
61
+ || ignoredOutputPaths.some(outputPath =>
62
+ normalizedPath === outputPath
63
+ || normalizedPath.startsWith(outputPath + '/')
64
+ );
65
+ }
55
66
 
56
67
  //aggregateTimeout: 1000,
57
68
  }, async (error, stats) => {
@@ -92,7 +103,7 @@ async function startApp( app: App ) {
92
103
  stopApp('Restart asked');
93
104
 
94
105
  console.info(`Launching new server ...`);
95
- cp = spawn('node', ['' + app.paths.bin + '/server.js', '--preserve-symlinks'], {
106
+ cp = spawn('node', ['' + app.outputPath('dev') + '/server.js', '--preserve-symlinks'], {
96
107
 
97
108
  // sdin, sdout, sderr
98
109
  stdio: ['inherit', 'inherit', 'inherit']
@@ -107,3 +118,7 @@ function stopApp( reason: string ) {
107
118
  }
108
119
 
109
120
  }
121
+
122
+ function normalizeWatchPath(watchPath: string) {
123
+ return path.resolve(watchPath).replace(/\\/g, '/').replace(/\/$/, '');
124
+ }
@@ -0,0 +1,18 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Configs
6
+ import Compiler from '../compiler';
7
+
8
+ /*----------------------------------
9
+ - COMMAND
10
+ ----------------------------------*/
11
+ export const run = (): Promise<void> => new Promise(async (resolve) => {
12
+
13
+ const compiler = new Compiler('dev');
14
+
15
+ await compiler.refreshGeneratedTypings();
16
+
17
+ resolve();
18
+ });
@@ -2,20 +2,18 @@
2
2
  import favicons from 'favicons';
3
3
  import fs from 'fs-extra';
4
4
 
5
- // Libs
6
- import cli from '../..';
7
-
8
5
  // Type
9
6
  import type App from '../../app';
10
7
 
11
- export default async ( app: App ) => {
8
+ export default async ( app: App, outputDir: string, enabled: boolean = true ) => {
12
9
 
13
- const dossierCache = app.paths.root + '/public/app';
10
+ if (!enabled)
11
+ return;
14
12
 
15
- if (!fs.existsSync(dossierCache)) {
13
+ if (!fs.existsSync(outputDir)) {
16
14
 
17
15
  console.info(`Generating identity assets ...`);
18
- fs.emptyDirSync(dossierCache);
16
+ fs.emptyDirSync(outputDir);
19
17
 
20
18
  const identity = app.identity;
21
19
 
@@ -45,7 +43,6 @@ export default async ( app: App ) => {
45
43
  appleStartup: false,
46
44
  coast: false,
47
45
  favicons: true,
48
- firefox: true,
49
46
  windows: true,
50
47
  yandex: false
51
48
  }
@@ -56,13 +53,13 @@ export default async ( app: App ) => {
56
53
 
57
54
  // Enregistrement images
58
55
  ...response.images.map((image) => {
59
- let destimg = dossierCache + '/' + image.name;
56
+ let destimg = outputDir + '/' + image.name;
60
57
  return fs.writeFile(destimg, image.contents);
61
58
  }),
62
59
 
63
60
  // Enregistrement fichiers
64
61
  ...response.files.map((fichier) => {
65
- let destfichier = dossierCache + '/' + fichier.name;
62
+ let destfichier = outputDir + '/' + fichier.name;
66
63
  return fs.writeFile(destfichier, fichier.contents);
67
64
  })
68
65
 
@@ -5,6 +5,7 @@
5
5
  // Npm
6
6
  import webpack from 'webpack';
7
7
  import fs from 'fs-extra';
8
+ import path from 'path';
8
9
 
9
10
  // Plugins
10
11
  const TerserPlugin = require('terser-webpack-plugin');
@@ -22,7 +23,7 @@ import WebpackAssetsManifest from 'webpack-assets-manifest';
22
23
  import PreactRefreshPlugin from '@prefresh/webpack';
23
24
 
24
25
  // Core
25
- import createCommonConfig, { TCompileMode, regex } from '../common';
26
+ import createCommonConfig, { TCompileMode, TCompileOutputTarget, regex } from '../common';
26
27
  import identityAssets from './identite';
27
28
  import cli from '../..';
28
29
 
@@ -34,15 +35,21 @@ const debug = false;
34
35
  /*----------------------------------
35
36
  - CONFIG
36
37
  ----------------------------------*/
37
- export default function createCompiler( app: App, mode: TCompileMode ): webpack.Configuration {
38
+ export default function createCompiler(
39
+ app: App,
40
+ mode: TCompileMode,
41
+ outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin'
42
+ ): webpack.Configuration {
38
43
 
39
44
  console.info(`Creating compiler for client (${mode}).`);
40
45
  const dev = mode === 'dev';
46
+ const buildDev = dev && outputTarget === 'bin';
47
+ const outputPath = app.outputPath(outputTarget);
41
48
 
42
- const commonConfig = createCommonConfig(app, 'client', mode);
49
+ const commonConfig = createCommonConfig(app, 'client', mode, outputTarget);
43
50
 
44
- // Pas besoin d'attendre que les assets soient générés pour lancer la compilation
45
- identityAssets(app);
51
+ // Smoke builds only validate compilation, so skip expensive static generation.
52
+ identityAssets(app, path.join(outputPath, 'public', 'app'), !buildDev);
46
53
 
47
54
  // Symlinks to public
48
55
  /*const publicDirs = fs.readdirSync(app.paths.root + '/public');
@@ -82,7 +89,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
82
89
  output: {
83
90
 
84
91
  pathinfo: dev,
85
- path: app.paths.bin + '/public',
92
+ path: outputPath + '/public',
86
93
  filename: '[name].js', // Output client.js
87
94
  assetModuleFilename: '[hash][ext]',
88
95
 
@@ -119,14 +126,14 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
119
126
  app.paths.root + '/server/.generated/models.ts',
120
127
 
121
128
  ],
122
- rules: require('../common/babel')(app, 'client', dev)
129
+ rules: require('../common/babel')(app, 'client', dev, buildDev)
123
130
  },
124
131
 
125
132
  // Les pages étan tà la fois compilées dans le bundle client et serveur
126
133
  // On ne compile les ressources (css) qu'une seule fois
127
134
  {
128
135
  test: regex.style,
129
- rules: require('../common/files/style')(app, dev, true),
136
+ rules: require('../common/files/style')(app, dev, true, buildDev),
130
137
 
131
138
  // Don't consider CSS imports dead code even if the
132
139
  // containing package claims to have no side effects.
@@ -157,43 +164,44 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
157
164
  new MiniCssExtractPlugin({})
158
165
  ]),
159
166
 
160
- // Emit a file with assets cli.paths
161
- // https://github.com/webdeveric/webpack-assets-manifest#options
162
- new WebpackAssetsManifest({
163
- output: app.paths.root + `/bin/asset-manifest.json`,
164
- publicPath: true,
165
- writeToDisk: true, // Force la copie du fichier sur e disque, au lieu d'en mémoire en mode dev
166
- customize: ({ key, value }) => {
167
- // You can prevent adding items to the manifest by returning false.
168
- if (key.toLowerCase().endsWith('.map')) return false;
169
- return { key, value };
170
- },
171
- done: (manifest, stats) => {
172
- // Write chunk-manifest.json.json
173
- const chunkFileName = app.paths.root + `/bin/chunk-manifest.json`;
174
- try {
175
- const fileFilter = file => !file.endsWith('.map');
176
- const addPath = file => manifest.getPublicPath(file);
177
- const chunkFiles = stats.compilation.chunkGroups.reduce((acc, c) => {
178
- acc[c.name] = [
179
- ...(acc[c.name] || []),
180
- ...c.chunks.reduce(
181
- (files, cc) => [
182
- ...files,
183
- ...[...cc.files].filter(fileFilter).map(addPath),
184
- ],
185
- [],
186
- ),
187
- ];
188
- return acc;
189
- }, Object.create(null));
190
- fs.writeFileSync(chunkFileName, JSON.stringify(chunkFiles, null, 4));
191
- } catch (err) {
192
- console.error(`ERROR: Cannot write ${chunkFileName}: `, err);
193
- if (!dev) process.exit(1);
194
- }
195
- },
196
- }),
167
+ ...(buildDev ? [] : [
168
+ // Emit runtime asset manifests only for runnable builds.
169
+ new WebpackAssetsManifest({
170
+ output: outputPath + `/asset-manifest.json`,
171
+ publicPath: true,
172
+ writeToDisk: true, // Force la copie du fichier sur e disque, au lieu d'en mémoire en mode dev
173
+ customize: ({ key, value }) => {
174
+ // You can prevent adding items to the manifest by returning false.
175
+ if (key.toLowerCase().endsWith('.map')) return false;
176
+ return { key, value };
177
+ },
178
+ done: (manifest, stats) => {
179
+ // Write chunk-manifest.json.json
180
+ const chunkFileName = outputPath + `/chunk-manifest.json`;
181
+ try {
182
+ const fileFilter = file => !file.endsWith('.map');
183
+ const addPath = file => manifest.getPublicPath(file);
184
+ const chunkFiles = stats.compilation.chunkGroups.reduce((acc, c) => {
185
+ acc[c.name] = [
186
+ ...(acc[c.name] || []),
187
+ ...c.chunks.reduce(
188
+ (files, cc) => [
189
+ ...files,
190
+ ...[...cc.files].filter(fileFilter).map(addPath),
191
+ ],
192
+ [],
193
+ ),
194
+ ];
195
+ return acc;
196
+ }, Object.create(null));
197
+ fs.writeFileSync(chunkFileName, JSON.stringify(chunkFiles, null, 4));
198
+ } catch (err) {
199
+ console.error(`ERROR: Cannot write ${chunkFileName}: `, err);
200
+ if (!dev) process.exit(1);
201
+ }
202
+ },
203
+ })
204
+ ]),
197
205
 
198
206
  ...(dev ? [
199
207
 
@@ -230,7 +238,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
230
238
 
231
239
  // CSP-safe in development: avoid any `eval`-based source map mode.
232
240
  // https://webpack.js.org/configuration/devtool/#devtool
233
- devtool: dev ? 'cheap-module-source-map' : 'source-map',
241
+ devtool: buildDev ? false : (dev ? 'cheap-module-source-map' : 'source-map'),
234
242
  /*devServer: {
235
243
  hot: true,
236
244
  },*/
@@ -5,6 +5,7 @@
5
5
  // Npm
6
6
  import type webpack from 'webpack';
7
7
  import PresetBabel, { Options } from '@babel/preset-env';
8
+ import path from 'path';
8
9
 
9
10
  import cli from '@cli';
10
11
  import type { TAppSide, App } from '@cli/app';
@@ -12,7 +13,7 @@ import type { TAppSide, App } from '@cli/app';
12
13
  /*----------------------------------
13
14
  - REGLES
14
15
  ----------------------------------*/
15
- module.exports = (app: App, side: TAppSide, dev: boolean): webpack.RuleSetRule[] => {
16
+ module.exports = (app: App, side: TAppSide, dev: boolean, buildDev: boolean = false): webpack.RuleSetRule[] => {
16
17
 
17
18
  const babelPresetEnvConfig: Options = side === 'client' ? {
18
19
 
@@ -64,7 +65,9 @@ module.exports = (app: App, side: TAppSide, dev: boolean): webpack.RuleSetRule[]
64
65
  // https://github.com/babel/babel-loader#options
65
66
 
66
67
  // ATTENTION: Ne prend pas toujours compte des màj des plugins babel
67
- cacheDirectory: dev || cli.args.cache === true,
68
+ cacheDirectory: (dev || cli.args.cache === true)
69
+ ? path.join(app.paths.cache, 'babel', side, buildDev ? 'build-dev' : (dev ? 'dev' : 'prod'))
70
+ : false,
68
71
  // Désactive car ralenti compilation
69
72
  cacheCompression: false,
70
73
 
@@ -650,55 +650,95 @@ function Plugin(babel, { app, side, debug }: TOptions) {
650
650
 
651
651
  function isProgramBodyLevelFunction(path: NodePath): boolean {
652
652
 
653
- const parent = path.parentPath;
654
- if (!parent)
655
- return false;
656
-
657
- // function Foo() {}
658
- if (parent.isProgram())
659
- return true;
660
-
661
- // export default function Foo() {} / export default () => {}
662
- if (
663
- parent.isExportDefaultDeclaration()
664
- &&
665
- parent.parentPath?.isProgram()
666
- )
667
- return true;
668
-
669
- // export const Foo = () => {}
670
- if (
671
- parent.isExportNamedDeclaration()
672
- &&
673
- parent.parentPath?.isProgram()
674
- )
675
- return true;
676
-
677
- // const Foo = () => {} (top-level) / export const Foo = () => {}
678
- if (parent.isVariableDeclarator()) {
653
+ let currentPath: NodePath | null = path;
654
+ while (currentPath) {
679
655
 
680
- const declaration = parent.parentPath;
681
- if (!declaration?.isVariableDeclaration())
656
+ const parent = currentPath.parentPath;
657
+ if (!parent)
682
658
  return false;
683
659
 
684
- const declarationParent = declaration.parentPath;
685
- if (!declarationParent)
686
- return false;
660
+ // function Foo() {}
661
+ if (parent.isProgram())
662
+ return true;
687
663
 
688
- if (declarationParent.isProgram())
664
+ // export default function Foo() {} / export default () => {}
665
+ if (
666
+ parent.isExportDefaultDeclaration()
667
+ &&
668
+ parent.parentPath?.isProgram()
669
+ )
689
670
  return true;
690
671
 
672
+ // export const Foo = () => {}
691
673
  if (
692
- declarationParent.isExportNamedDeclaration()
674
+ parent.isExportNamedDeclaration()
693
675
  &&
694
- declarationParent.parentPath?.isProgram()
676
+ parent.parentPath?.isProgram()
695
677
  )
696
678
  return true;
679
+
680
+ // const Foo = () => {} (top-level) / export const Foo = () => {}
681
+ if (parent.isVariableDeclarator()) {
682
+
683
+ const declaration = parent.parentPath;
684
+ if (!declaration?.isVariableDeclaration())
685
+ return false;
686
+
687
+ const declarationParent = declaration.parentPath;
688
+ if (!declarationParent)
689
+ return false;
690
+
691
+ if (declarationParent.isProgram())
692
+ return true;
693
+
694
+ if (
695
+ declarationParent.isExportNamedDeclaration()
696
+ &&
697
+ declarationParent.parentPath?.isProgram()
698
+ )
699
+ return true;
700
+ }
701
+
702
+ // Support top-level component wrappers such as React.forwardRef(...) and React.memo(...)
703
+ if (
704
+ parent.isCallExpression()
705
+ &&
706
+ isSupportedComponentWrapperCall(parent, currentPath)
707
+ ) {
708
+ currentPath = parent;
709
+ continue;
710
+ }
711
+
712
+ return false;
697
713
  }
698
714
 
699
715
  return false;
700
716
  }
701
717
 
718
+ function isSupportedComponentWrapperCall(
719
+ callPath: NodePath<types.CallExpression>,
720
+ wrappedPath: NodePath
721
+ ): boolean {
722
+
723
+ if (callPath.node.arguments[0] !== wrappedPath.node)
724
+ return false;
725
+
726
+ const callee = callPath.node.callee;
727
+ if (callee.type === 'Identifier')
728
+ return ['forwardRef', 'memo'].includes(callee.name);
729
+
730
+ if (
731
+ callee.type === 'MemberExpression'
732
+ &&
733
+ !callee.computed
734
+ &&
735
+ callee.property.type === 'Identifier'
736
+ )
737
+ return ['forwardRef', 'memo'].includes(callee.property.name);
738
+
739
+ return false;
740
+ }
741
+
702
742
  function ensureUseContextImport(path: NodePath<types.Program>, pluginState: TPluginState) {
703
743
 
704
744
  if (!pluginState.needsUseContextImport)
@@ -3,9 +3,9 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
3
3
 
4
4
  import type { App } from '../../../app';
5
5
 
6
- module.exports = (app: App, dev: Boolean, client: boolean) => {
6
+ module.exports = (app: App, dev: boolean, client: boolean, buildDev: boolean = false) => {
7
7
 
8
- console.log('app.paths.root', app.paths.root)
8
+ const enableSourceMaps = dev && !buildDev;
9
9
 
10
10
  return [
11
11
 
@@ -19,7 +19,7 @@ module.exports = (app: App, dev: Boolean, client: boolean) => {
19
19
  exclude: [app.paths.root],
20
20
  loader: 'css-loader',
21
21
  options: {
22
- sourceMap: dev
22
+ sourceMap: enableSourceMaps
23
23
  },
24
24
  },
25
25
 
@@ -30,7 +30,7 @@ module.exports = (app: App, dev: Boolean, client: boolean) => {
30
30
  options: {
31
31
  // CSS Loader https://github.com/webpack/css-loader
32
32
  importLoaders: 1, // let postcss run on @imports
33
- sourceMap: dev
33
+ sourceMap: enableSourceMaps
34
34
  },
35
35
  },
36
36
 
@@ -49,7 +49,9 @@ module.exports = (app: App, dev: Boolean, client: boolean) => {
49
49
  optimize: false,
50
50
  }),
51
51
  ///* Tailwind V3 */require('tailwindcss'),
52
- require('autoprefixer'),
52
+ ...(buildDev ? [] : [
53
+ require('autoprefixer'),
54
+ ]),
53
55
  ],
54
56
  },
55
57
  },
@@ -34,6 +34,7 @@ export const regex = {
34
34
  ----------------------------------*/
35
35
 
36
36
  export type TCompileMode = 'dev' | 'prod'
37
+ export type TCompileOutputTarget = 'dev' | 'bin'
37
38
 
38
39
  /*----------------------------------
39
40
  - BASE CONFIG
@@ -43,9 +44,11 @@ export default function createCommonConfig(
43
44
  app: App,
44
45
  side: TAppSide,
45
46
  mode: TCompileMode,
47
+ outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin',
46
48
  ): webpack.Configuration {
47
49
 
48
50
  const dev = mode === 'dev';
51
+ const buildDev = dev && outputTarget === 'bin';
49
52
  const config: webpack.Configuration = {
50
53
 
51
54
  // Project root
@@ -83,6 +86,8 @@ export default function createCommonConfig(
83
86
  BUILD_ID: JSON.stringify(app.buildId),
84
87
  APP_PATH: JSON.stringify(app.paths.root),
85
88
  APP_NAME: JSON.stringify(app.identity.web.title),
89
+ APP_OUTPUT_DIR: JSON.stringify(path.basename(app.outputPath(outputTarget))),
90
+ PROTEUM_ROUTER_PORT_OVERRIDE: JSON.stringify(app.routerPortOverride ?? null),
86
91
 
87
92
  }),
88
93
 
@@ -121,18 +126,18 @@ export default function createCommonConfig(
121
126
  // our own hints via the FileSizeReporter
122
127
  performance: false,
123
128
 
124
- // Don't attempt to continue if there are any errors.
125
- bail: !dev,
129
+ // Smoke builds should fail immediately on the first compilation error.
130
+ bail: buildDev || !dev,
126
131
 
127
- // Persistent cache speeds up cold starts and incremental rebuilds.
128
- cache: /*(dev || cli.args.cache === true) ? {
132
+ // Persistent cache speeds up repeated local build-dev invocations.
133
+ cache: (buildDev || cli.args.cache === true) ? {
129
134
  type: 'filesystem',
130
- cacheDirectory: path.join(app.paths.cache, 'webpack', side),
135
+ cacheDirectory: path.join(app.paths.cache, 'webpack', side, buildDev ? 'build-dev' : mode),
131
136
  compression: false,
132
137
  buildDependencies: {
133
138
  config: [__filename],
134
139
  },
135
- } : */false,
140
+ } : false,
136
141
 
137
142
  // Increase compilation performance
138
143
  profile: false,
@@ -16,7 +16,7 @@ import app from '../app';
16
16
  import cli from '..';
17
17
  import createServerConfig from './server';
18
18
  import createClientConfig from './client';
19
- import { TCompileMode } from './common';
19
+ import { TCompileMode, TCompileOutputTarget } from './common';
20
20
 
21
21
  type TCompilerCallback = (compiler: webpack.Compiler) => void
22
22
 
@@ -50,28 +50,34 @@ export default class Compiler {
50
50
  before?: TCompilerCallback,
51
51
  after?: TCompilerCallback,
52
52
  } = {},
53
- private debug: boolean = false
53
+ private debug: boolean = false,
54
+ private outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin'
54
55
  ) {
55
56
 
56
57
  }
57
58
 
58
59
  public cleanup() {
60
+ const outputPath = app.outputPath(this.outputTarget);
61
+ const generatedPublicEntries = new Set(['app']);
59
62
 
60
- fs.emptyDirSync( app.paths.bin );
61
- fs.ensureDirSync( path.join(app.paths.bin, 'public') )
63
+ fs.emptyDirSync( outputPath );
64
+ fs.ensureDirSync( path.join(outputPath, 'public') )
62
65
  const publicFiles = fs.readdirSync(app.paths.public);
63
66
  for (const publicFile of publicFiles) {
67
+ if (generatedPublicEntries.has(publicFile))
68
+ continue;
69
+
64
70
  // Dev: faster to use symlink
65
71
  if (this.mode === 'dev')
66
72
  fs.symlinkSync(
67
73
  path.join(app.paths.public, publicFile),
68
- path.join(app.paths.bin, 'public', publicFile)
74
+ path.join(outputPath, 'public', publicFile)
69
75
  );
70
76
  // Prod: Symlink not always supported by CI / Containers solutions
71
77
  else
72
78
  fs.copySync(
73
79
  path.join(app.paths.public, publicFile),
74
- path.join(app.paths.bin, 'public', publicFile)
80
+ path.join(outputPath, 'public', publicFile)
75
81
  );
76
82
  }
77
83
  }
@@ -87,6 +93,7 @@ export default class Compiler {
87
93
  return console.info("Not fixing npm issue because proteum wasn't installed with npm link.");
88
94
 
89
95
  this.debug && console.info(`Fix NPM link issues ...`);
96
+ const outputPath = app.outputPath(this.outputTarget);
90
97
 
91
98
  const appModules = path.join(app.paths.root, 'node_modules');
92
99
  const coreModules = path.join(corePath, 'node_modules');
@@ -95,7 +102,7 @@ export default class Compiler {
95
102
  // Modules are installed locally and not glbally as with with the 5htp package from NPM.
96
103
  // So we need to symbilnk the http-core node_modules in one of the parents of server.js.
97
104
  // It avoids errors like: "Error: Cannot find module 'intl'"
98
- fs.symlinkSync( coreModules, path.join(app.paths.bin, 'node_modules') );
105
+ fs.symlinkSync( coreModules, path.join(outputPath, 'node_modules') );
99
106
 
100
107
  // Same problem: when 5htp-core is installed via npm link,
101
108
  // Typescript doesn't detect React and shows mission JSX errors
@@ -492,10 +499,24 @@ declare module '@models/types' {
492
499
  );
493
500
  }
494
501
 
495
- public async create() {
502
+ private async warmupApp() {
496
503
 
497
504
  await app.warmup();
498
505
 
506
+ }
507
+
508
+ public async refreshGeneratedTypings() {
509
+
510
+ await this.warmupApp();
511
+
512
+ this.indexServices();
513
+
514
+ }
515
+
516
+ public async create() {
517
+
518
+ await this.warmupApp();
519
+
499
520
  this.cleanup();
500
521
 
501
522
  this.fixNpmLinkIssues();
@@ -504,8 +525,8 @@ declare module '@models/types' {
504
525
 
505
526
  // Create compilers
506
527
  const multiCompiler = webpack([
507
- smp.wrap( createServerConfig(app, this.mode) ),
508
- smp.wrap( createClientConfig(app, this.mode) )
528
+ smp.wrap( createServerConfig(app, this.mode, this.outputTarget) ),
529
+ smp.wrap( createClientConfig(app, this.mode, this.outputTarget) )
509
530
  ]);
510
531
 
511
532
  for (const compiler of multiCompiler.compilers) {
@@ -12,7 +12,7 @@ const TerserPlugin = require("terser-webpack-plugin");
12
12
 
13
13
  // Core
14
14
  import cli from '@cli';
15
- import createCommonConfig, { TCompileMode, regex } from '../common';
15
+ import createCommonConfig, { TCompileMode, TCompileOutputTarget, regex } from '../common';
16
16
 
17
17
  // Type
18
18
  import type { App } from '../../app';
@@ -39,12 +39,18 @@ const debug = false;
39
39
  /*----------------------------------
40
40
  - CONFIG
41
41
  ----------------------------------*/
42
- export default function createCompiler( app: App, mode: TCompileMode ): webpack.Configuration {
42
+ export default function createCompiler(
43
+ app: App,
44
+ mode: TCompileMode,
45
+ outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin'
46
+ ): webpack.Configuration {
43
47
 
44
48
  debug && console.info(`Creating compiler for server (${mode}).`);
45
49
  const dev = mode === 'dev';
50
+ const buildDev = dev && outputTarget === 'bin';
51
+ const outputPath = app.outputPath(outputTarget);
46
52
 
47
- const commonConfig = createCommonConfig(app, 'server', mode);
53
+ const commonConfig = createCommonConfig(app, 'server', mode, outputTarget);
48
54
  const { aliases } = app.aliases.server.forWebpack({
49
55
  modulesPath: app.paths.root + '/node_modules'
50
56
  });
@@ -73,7 +79,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
73
79
 
74
80
  libraryTarget: 'commonjs2',
75
81
 
76
- path: app.paths.bin,
82
+ path: outputPath,
77
83
  filename: '[name].js',
78
84
  publicPath: '/',
79
85
  assetModuleFilename: 'public/[hash][ext]',
@@ -106,6 +112,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
106
112
  request.startsWith('proteum')
107
113
  ||
108
114
  // Compile 5HTP modules
115
+ request.startsWith('@mantine/') ||
109
116
  request.startsWith('react-number-format') ||
110
117
  request.startsWith('@floating-ui')
111
118
  )
@@ -155,7 +162,7 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
155
162
  // Temp disabled because compile issue on vercel
156
163
  //...getCorePluginsList(app)
157
164
  ],
158
- rules: require('../common/babel')(app, 'server', dev)
165
+ rules: require('../common/babel')(app, 'server', dev, buildDev)
159
166
  },
160
167
 
161
168
  // Les pages étan tà la fois compilées dans le bundle client et serveur
@@ -197,7 +204,9 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
197
204
  },
198
205
 
199
206
  // https://webpack.js.org/configuration/devtool/#devtool
200
- devtool: dev
207
+ devtool: buildDev
208
+ ? false
209
+ : dev
201
210
  ? 'eval-source-map' // Recommended choice for development builds with high quality SourceMaps.
202
211
  : 'source-map', // Recommended choice for production builds with high quality SourceMaps.
203
212
 
@@ -208,4 +217,4 @@ export default function createCompiler( app: App, mode: TCompileMode ): webpack.
208
217
  };
209
218
 
210
219
  return config;
211
- };
220
+ };
package/cli/index.ts CHANGED
@@ -35,6 +35,19 @@ export class CLI {
35
35
 
36
36
  // Context
37
37
  public args: TArgsObject = {};
38
+
39
+ public commandOptionDefaults: { [command: string]: TArgsObject } = {
40
+ dev: {
41
+ port: '',
42
+ },
43
+ build: {
44
+ port: '',
45
+ dev: false,
46
+ prod: false,
47
+ analyze: false,
48
+ cache: false,
49
+ },
50
+ };
38
51
 
39
52
  public debug: boolean = false;
40
53
 
@@ -61,6 +74,7 @@ export class CLI {
61
74
  public commands: { [name: string]: TCliCommand } = {
62
75
  "init": () => import('./commands/init'),
63
76
  "dev": () => import('./commands/dev'),
77
+ "refresh": () => import('./commands/refresh'),
64
78
  "build": () => import('./commands/build'),
65
79
  }
66
80
 
@@ -75,20 +89,27 @@ export class CLI {
75
89
  if (this.commands[commandName] === undefined)
76
90
  throw new Error(`Command ${commandName} does not exists.`);
77
91
 
92
+ this.args = {
93
+ ...(this.commandOptionDefaults[commandName] || {})
94
+ };
78
95
  this.args.workdir = process.cwd();
79
96
 
80
97
  let opt: string | null = null;
81
98
  for (const a of argv) {
82
99
 
83
- if (a[0] === '-') {
100
+ if (a.startsWith('-')) {
84
101
 
85
- opt = a.substring(1);
102
+ opt = a.replace(/^-+/, '');
103
+ if (opt.length === 0)
104
+ throw new Error(`Unknown option: ${a}`);
86
105
  if (!(opt in this.args))
87
106
  throw new Error(`Unknown option: ${opt}`);
88
107
 
89
108
  // Init with default value
90
- if (typeof this.args[opt] === "boolean")
109
+ if (typeof this.args[opt] === "boolean") {
91
110
  this.args[opt] = true;
111
+ opt = null;
112
+ }
92
113
 
93
114
  } else if (opt !== null) {
94
115
 
@@ -108,6 +129,9 @@ export class CLI {
108
129
  }
109
130
  }
110
131
 
132
+ if (opt !== null && typeof this.args[opt] !== 'boolean')
133
+ throw new Error(`Missing value for option: ${opt}`);
134
+
111
135
  this.runCommand(commandName);
112
136
  }
113
137
 
@@ -186,4 +210,4 @@ export class CLI {
186
210
 
187
211
  }
188
212
 
189
- export default new CLI()
213
+ export default new CLI()
package/doc/TODO.md CHANGED
@@ -34,7 +34,7 @@ abstract class Controller<
34
34
 
35
35
  ```typescript
36
36
  //? /headhunter/missions/suggested'
37
- class Missions extends Controller<UniqueDomains["router"]> {
37
+ class Missions extends Controller<CrossPath["router"]> {
38
38
 
39
39
  auth = 'USER';
40
40
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proteum",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",
@@ -25,7 +25,6 @@
25
25
  "@babel/preset-env": "^7.15.6",
26
26
  "@babel/preset-react": "^7.14.5",
27
27
  "@babel/preset-typescript": "^7.15.0",
28
- "@lexical/react": "^0.18.0",
29
28
  "@prefresh/webpack": "^3.3.2",
30
29
  "@prisma/client": "^6.5.0",
31
30
  "@tailwindcss/postcss": "^4.1.17",
@@ -73,7 +72,6 @@
73
72
  "json5": "^2.2.0",
74
73
  "jsonwebtoken": "^8.5.1",
75
74
  "less-loader": "^10.0.1",
76
- "lexical": "^0.18.0",
77
75
  "load-script": "^2.0.0",
78
76
  "locale": "^0.1.0",
79
77
  "markdown-it": "^13.0.1",
@@ -15,6 +15,8 @@ import yaml from 'yaml';
15
15
  import type { TDomainsList } from '@common/router';
16
16
  import type { TLogProfile } from './console';
17
17
 
18
+ declare const PROTEUM_ROUTER_PORT_OVERRIDE: number | null;
19
+
18
20
  /*----------------------------------
19
21
  - TYPES
20
22
  ----------------------------------*/
@@ -114,6 +116,16 @@ export type AppConfig = {
114
116
 
115
117
  const debug = false;
116
118
 
119
+ const getRouterPortOverride = () => {
120
+ if (
121
+ typeof PROTEUM_ROUTER_PORT_OVERRIDE !== 'undefined'
122
+ && PROTEUM_ROUTER_PORT_OVERRIDE !== null
123
+ )
124
+ return PROTEUM_ROUTER_PORT_OVERRIDE;
125
+
126
+ return undefined;
127
+ }
128
+
117
129
  /*----------------------------------
118
130
  - LOADE
119
131
  ----------------------------------*/
@@ -138,8 +150,15 @@ export default class ConfigParser {
138
150
  console.log("[app] Using environment:", process.env.NODE_ENV);
139
151
  const envFileName = this.appDir + '/env.yaml';
140
152
  const envFile = this.loadYaml( envFileName );
153
+ const routerPortOverride = getRouterPortOverride();
141
154
  return {
142
155
  ...envFile,
156
+ router: routerPortOverride === undefined
157
+ ? envFile.router
158
+ : {
159
+ ...envFile.router,
160
+ port: routerPortOverride
161
+ },
143
162
  version: BUILD_DATE
144
163
  }
145
164
  }
@@ -200,4 +219,4 @@ export default class ConfigParser {
200
219
  deepExtend(fileConfig, loadYaml(fullpath));
201
220
 
202
221
  }
203
- }*/
222
+ }*/
@@ -115,7 +115,7 @@ export default class HttpServer {
115
115
  routes.use('/public', cors());
116
116
  routes.use(
117
117
  '/public',
118
- express.static( Container.path.root + '/bin/public', {
118
+ express.static( path.join(Container.path.root, APP_OUTPUT_DIR, 'public'), {
119
119
  dotfiles: 'deny',
120
120
  setHeaders: function setCustomCacheControl(res, path) {
121
121
 
@@ -214,4 +214,4 @@ export default class HttpServer {
214
214
  public async cleanup() {
215
215
  this.http.close();
216
216
  }
217
- }
217
+ }
@@ -7,9 +7,6 @@ import escapeStringRegexp from 'escape-regexp';
7
7
  import slugify from 'slugify';
8
8
  import { removeStopwords, eng } from 'stopword';
9
9
 
10
- // Core
11
- import type SQL from "@server/services/database";
12
-
13
10
  /*----------------------------------
14
11
  - TYPES
15
12
  ----------------------------------*/
@@ -0,0 +1,12 @@
1
+ declare const __DEV__: boolean;
2
+ declare const SERVER: boolean;
3
+
4
+ declare const CORE_VERSION: string;
5
+ declare const CORE_PATH: string;
6
+
7
+ declare const BUILD_DATE: string;
8
+ declare const BUILD_ID: number;
9
+
10
+ declare const APP_PATH: string;
11
+ declare const APP_NAME: string;
12
+ declare const APP_OUTPUT_DIR: string;