proteum 1.0.2 → 2.0.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 +101 -0
- package/agents/codex/AGENTS.md +95 -0
- package/agents/codex/CODING_STYLE.md +71 -0
- package/agents/codex/agents.md.zip +0 -0
- package/agents/codex/client/AGENTS.md +102 -0
- package/agents/codex/client/pages/AGENTS.md +35 -0
- package/agents/codex/server/routes/AGENTS.md +12 -0
- package/agents/codex/server/services/AGENTS.md +137 -0
- package/agents/codex/tests/AGENTS.md +8 -0
- package/cli/app/config.ts +13 -11
- package/cli/app/index.ts +74 -82
- package/cli/bin.js +1 -1
- package/cli/commands/build.ts +51 -14
- package/cli/commands/check.ts +19 -0
- package/cli/commands/deploy/app.ts +4 -8
- package/cli/commands/deploy/web.ts +16 -20
- package/cli/commands/dev.ts +189 -64
- package/cli/commands/devEvents.ts +106 -0
- package/cli/commands/init.ts +63 -57
- package/cli/commands/lint.ts +21 -0
- package/cli/commands/refresh.ts +18 -0
- package/cli/commands/typecheck.ts +18 -0
- package/cli/compiler/client/identite.ts +80 -53
- package/cli/compiler/client/index.ts +139 -213
- package/cli/compiler/common/bundleAnalysis.ts +94 -0
- package/cli/compiler/common/clientManifest.ts +67 -0
- package/cli/compiler/common/controllers.ts +288 -0
- package/cli/compiler/common/files/autres.ts +7 -18
- package/cli/compiler/common/files/images.ts +40 -37
- package/cli/compiler/common/files/style.ts +11 -22
- package/cli/compiler/common/generatedRouteModules.ts +368 -0
- package/cli/compiler/common/index.ts +31 -65
- package/cli/compiler/common/loaders/forbid-ssr-import.js +13 -0
- package/cli/compiler/common/rspackAliases.ts +13 -0
- package/cli/compiler/common/scripts.ts +37 -0
- package/cli/compiler/index.ts +781 -230
- package/cli/compiler/server/index.ts +59 -75
- package/cli/compiler/writeIfChanged.ts +21 -0
- package/cli/index.ts +71 -72
- package/cli/paths.ts +51 -57
- package/cli/print.ts +17 -11
- package/cli/tsconfig.json +5 -4
- package/cli/utils/agents.ts +100 -0
- package/cli/utils/check.ts +71 -0
- package/cli/utils/index.ts +1 -3
- package/cli/utils/keyboard.ts +8 -25
- package/cli/utils/runProcess.ts +30 -0
- package/client/app/component.tsx +29 -29
- package/client/app/index.ts +36 -57
- package/client/app/service.ts +7 -12
- package/client/app.tsconfig.json +2 -2
- package/client/components/Dialog/Manager.ssr.tsx +40 -0
- package/client/components/Dialog/Manager.tsx +119 -150
- package/client/components/Dialog/status.tsx +3 -3
- package/client/components/index.ts +1 -1
- package/client/components/types.d.ts +1 -3
- package/client/dev/hmr.ts +65 -0
- package/client/global.d.ts +2 -2
- package/client/hooks.ts +6 -9
- package/client/index.ts +2 -1
- package/client/islands/index.ts +7 -0
- package/client/islands/useDeferredModule.ts +199 -0
- package/client/pages/_layout/index.tsx +4 -12
- package/client/pages/useHeader.tsx +14 -21
- package/client/router.ts +27 -0
- package/client/services/router/components/Link.tsx +34 -27
- package/client/services/router/components/Page.tsx +6 -14
- package/client/services/router/components/router.ssr.tsx +36 -0
- package/client/services/router/components/router.tsx +63 -83
- package/client/services/router/index.tsx +185 -220
- package/client/services/router/request/api.ts +97 -119
- package/client/services/router/request/history.ts +2 -2
- package/client/services/router/request/index.ts +13 -12
- package/client/services/router/request/multipart.ts +72 -62
- package/client/services/router/response/index.tsx +68 -61
- package/client/services/router/response/page.ts +28 -32
- package/client/utils/dom.ts +17 -33
- package/common/app/index.ts +3 -3
- package/common/data/chaines/index.ts +22 -23
- package/common/data/dates.ts +35 -70
- package/common/data/markdown.ts +42 -39
- package/common/dev/serverHotReload.ts +26 -0
- package/common/errors/index.tsx +110 -142
- package/common/router/contracts.ts +29 -0
- package/common/router/index.ts +89 -108
- package/common/router/layouts.ts +34 -47
- package/common/router/pageSetup.ts +50 -0
- package/common/router/register.ts +53 -24
- package/common/router/request/api.ts +30 -36
- package/common/router/request/index.ts +2 -8
- package/common/router/response/index.ts +8 -15
- package/common/router/response/page.ts +70 -58
- package/common/utils.ts +1 -1
- package/doc/TODO.md +1 -1
- package/eslint.js +62 -0
- package/package.json +12 -47
- package/prettier.config.cjs +9 -0
- package/scripts/cleanup-generated-controllers.ts +62 -0
- package/scripts/fix-reference-app-typing.ts +490 -0
- package/scripts/refactor-client-app-imports.ts +244 -0
- package/scripts/refactor-client-pages.ts +587 -0
- package/scripts/refactor-server-controllers.ts +470 -0
- package/scripts/refactor-server-runtime-aliases.ts +360 -0
- package/scripts/restore-client-app-import-files.ts +41 -0
- package/scripts/restore-files-from-git-head.ts +20 -0
- package/scripts/update-codex-agents.ts +35 -0
- package/server/app/commands.ts +35 -64
- package/server/app/container/config.ts +48 -59
- package/server/app/container/console/index.ts +202 -248
- package/server/app/container/index.ts +33 -71
- package/server/app/controller/index.ts +61 -0
- package/server/app/index.ts +39 -105
- package/server/app/service/container.ts +41 -42
- package/server/app/service/index.ts +120 -147
- package/server/context.ts +1 -1
- package/server/index.ts +25 -1
- package/server/services/auth/index.ts +75 -115
- package/server/services/auth/router/index.ts +31 -32
- package/server/services/auth/router/request.ts +14 -16
- package/server/services/cron/CronTask.ts +13 -26
- package/server/services/cron/index.ts +14 -36
- package/server/services/disks/driver.ts +40 -58
- package/server/services/disks/drivers/local/index.ts +79 -90
- package/server/services/disks/drivers/s3/index.ts +116 -163
- package/server/services/disks/index.ts +23 -38
- package/server/services/email/index.ts +45 -104
- package/server/services/email/utils.ts +14 -27
- package/server/services/fetch/index.ts +53 -85
- package/server/services/prisma/Facet.ts +39 -91
- package/server/services/prisma/index.ts +74 -110
- package/server/services/router/generatedRuntime.ts +29 -0
- package/server/services/router/http/index.ts +78 -73
- package/server/services/router/http/multipart.ts +19 -42
- package/server/services/router/index.ts +378 -365
- package/server/services/router/request/api.ts +26 -25
- package/server/services/router/request/index.ts +44 -51
- package/server/services/router/request/service.ts +7 -11
- package/server/services/router/request/validation/zod.ts +111 -148
- package/server/services/router/response/index.ts +110 -125
- package/server/services/router/response/mask/Filter.ts +31 -72
- package/server/services/router/response/mask/index.ts +8 -15
- package/server/services/router/response/mask/selecteurs.ts +11 -25
- package/server/services/router/response/page/clientManifest.ts +25 -0
- package/server/services/router/response/page/document.tsx +199 -127
- package/server/services/router/response/page/index.tsx +89 -94
- package/server/services/router/service.ts +13 -15
- package/server/services/schema/index.ts +17 -26
- package/server/services/schema/request.ts +19 -33
- package/server/services/schema/router/index.ts +8 -11
- package/server/services/security/encrypt/aes/index.ts +15 -35
- package/server/utils/slug.ts +29 -35
- package/skills/clean-project-code/SKILL.md +63 -0
- package/skills/clean-project-code/agents/openai.yaml +4 -0
- package/tsconfig.common.json +4 -3
- package/tsconfig.json +4 -1
- package/types/aliases.d.ts +17 -21
- package/types/controller-input.test.ts +48 -0
- package/types/express-extra.d.ts +6 -0
- package/types/global/constants.d.ts +13 -0
- package/types/global/express-extra.d.ts +6 -0
- package/types/global/modules.d.ts +13 -16
- package/types/global/utils.d.ts +17 -49
- package/types/global/vendors.d.ts +62 -0
- package/types/icons.d.ts +65 -1
- package/types/uuid.d.ts +3 -0
- package/types/vendors.d.ts +62 -0
- package/cli/compiler/common/babel/index.ts +0 -170
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +0 -586
- package/cli/compiler/common/babel/routes/imports.ts +0 -127
- package/cli/compiler/common/babel/routes/routes.ts +0 -1130
- package/client/services/captcha/index.ts +0 -67
- package/client/services/socket/index.ts +0 -147
- package/common/data/rte/nodes.ts +0 -83
- package/common/data/stats.ts +0 -90
- package/common/utils/rte.ts +0 -183
- package/server/services/auth/old.ts +0 -277
- package/server/services/cache/commands.ts +0 -41
- package/server/services/cache/index.ts +0 -297
- package/server/services/cache/service.json +0 -6
- package/server/services/socket/index.ts +0 -162
- package/server/services/socket/scope.ts +0 -226
- package/server/services/socket/service.json +0 -6
- package/server/services_old/SocketClient.ts +0 -92
- package/server/services_old/Token.old.ts +0 -97
package/cli/compiler/index.ts
CHANGED
|
@@ -4,76 +4,78 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import webpack from 'webpack';
|
|
8
7
|
import fs from 'fs-extra';
|
|
9
8
|
import serialize from 'serialize-javascript';
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
const smp = new SpeedMeasurePlugin({ disable: true });
|
|
9
|
+
import { rspack, type Compiler as RspackCompiler } from '@rspack/core';
|
|
10
|
+
import ts from 'typescript';
|
|
13
11
|
|
|
14
12
|
// Core
|
|
15
13
|
import app from '../app';
|
|
16
14
|
import cli from '..';
|
|
17
15
|
import createServerConfig from './server';
|
|
18
16
|
import createClientConfig from './client';
|
|
19
|
-
import { TCompileMode } from './common';
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
import { TCompileMode, TCompileOutputTarget } from './common';
|
|
18
|
+
import {
|
|
19
|
+
indexControllers,
|
|
20
|
+
generateControllerClientTree,
|
|
21
|
+
printControllerTree,
|
|
22
|
+
type TControllerServiceRoot,
|
|
23
|
+
} from './common/controllers';
|
|
24
|
+
import { writeClientManifest } from './common/clientManifest';
|
|
25
|
+
import { getGeneratedRouteModuleFilepath, writeGeneratedRouteModule } from './common/generatedRouteModules';
|
|
26
|
+
import writeIfChanged from './writeIfChanged';
|
|
27
|
+
|
|
28
|
+
type TCompilerCallback = (compiler: RspackCompiler) => void;
|
|
22
29
|
|
|
23
30
|
type TServiceMetas = {
|
|
24
|
-
id: string
|
|
25
|
-
name: string
|
|
26
|
-
parent: string
|
|
27
|
-
dependences: string
|
|
28
|
-
importationPath: string
|
|
29
|
-
priority: number
|
|
30
|
-
}
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
parent: string;
|
|
34
|
+
dependences: string;
|
|
35
|
+
importationPath: string;
|
|
36
|
+
priority: number;
|
|
37
|
+
};
|
|
31
38
|
|
|
32
39
|
type TRegisteredService = {
|
|
33
|
-
id?: string
|
|
34
|
-
name: string
|
|
35
|
-
className: string
|
|
36
|
-
instanciation: (parentRef?: string) => string
|
|
37
|
-
priority: number
|
|
38
|
-
}
|
|
40
|
+
id?: string;
|
|
41
|
+
name: string;
|
|
42
|
+
className: string;
|
|
43
|
+
instanciation: (parentRef?: string, appRef?: string) => string;
|
|
44
|
+
priority: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type TClientRouteLoader = { filepath: string; chunkId: string; preload: boolean };
|
|
48
|
+
|
|
49
|
+
type TRecentCompilationResult = { succeeded: boolean; hash?: string; modifiedFiles?: string[] };
|
|
50
|
+
|
|
51
|
+
const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
39
52
|
|
|
40
53
|
/*----------------------------------
|
|
41
54
|
- FONCTION
|
|
42
55
|
----------------------------------*/
|
|
43
56
|
export default class Compiler {
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
public compiling: { [compiler: string]: Promise<void> } = {};
|
|
58
|
+
private recentCompilationResults: { [compiler: string]: TRecentCompilationResult } = {};
|
|
59
|
+
private recentModifiedFiles: { [compiler: string]: string[] } = {};
|
|
60
|
+
private refreshingGeneratedArtifacts?: Promise<void>;
|
|
46
61
|
|
|
47
62
|
public constructor(
|
|
48
63
|
private mode: TCompileMode,
|
|
49
|
-
private callbacks: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
private debug: boolean = false
|
|
54
|
-
) {
|
|
55
|
-
|
|
56
|
-
}
|
|
64
|
+
private callbacks: { before?: TCompilerCallback; after?: TCompilerCallback } = {},
|
|
65
|
+
private debug: boolean = false,
|
|
66
|
+
private outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin',
|
|
67
|
+
) {}
|
|
57
68
|
|
|
58
69
|
public cleanup() {
|
|
70
|
+
const outputPath = app.outputPath(this.outputTarget);
|
|
71
|
+
const generatedPublicEntries = new Set(['app']);
|
|
72
|
+
const outputPublicPath = path.join(outputPath, 'public');
|
|
73
|
+
const preserveDevOutput = this.mode === 'dev' && this.outputTarget === 'dev';
|
|
59
74
|
|
|
60
|
-
fs.emptyDirSync(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Dev: faster to use symlink
|
|
65
|
-
if (this.mode === 'dev')
|
|
66
|
-
fs.symlinkSync(
|
|
67
|
-
path.join(app.paths.public, publicFile),
|
|
68
|
-
path.join(app.paths.bin, 'public', publicFile)
|
|
69
|
-
);
|
|
70
|
-
// Prod: Symlink not always supported by CI / Containers solutions
|
|
71
|
-
else
|
|
72
|
-
fs.copySync(
|
|
73
|
-
path.join(app.paths.public, publicFile),
|
|
74
|
-
path.join(app.paths.bin, 'public', publicFile)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
75
|
+
if (!preserveDevOutput) fs.emptyDirSync(outputPath);
|
|
76
|
+
|
|
77
|
+
fs.ensureDirSync(outputPublicPath);
|
|
78
|
+
this.syncPublicEntries(outputPublicPath, generatedPublicEntries, this.mode === 'dev');
|
|
77
79
|
}
|
|
78
80
|
/* FIX issue with npm link
|
|
79
81
|
When we install a module with npm link, this module's deps are not installed in the parent project scope
|
|
@@ -83,10 +85,11 @@ export default class Compiler {
|
|
|
83
85
|
*/
|
|
84
86
|
public fixNpmLinkIssues() {
|
|
85
87
|
const corePath = path.join(app.paths.root, '/node_modules/proteum');
|
|
86
|
-
if (!fs.lstatSync(
|
|
88
|
+
if (!fs.lstatSync(corePath).isSymbolicLink())
|
|
87
89
|
return console.info("Not fixing npm issue because proteum wasn't installed with npm link.");
|
|
88
90
|
|
|
89
91
|
this.debug && console.info(`Fix NPM link issues ...`);
|
|
92
|
+
const outputPath = app.outputPath(this.outputTarget);
|
|
90
93
|
|
|
91
94
|
const appModules = path.join(app.paths.root, 'node_modules');
|
|
92
95
|
const coreModules = path.join(corePath, 'node_modules');
|
|
@@ -95,191 +98,718 @@ export default class Compiler {
|
|
|
95
98
|
// Modules are installed locally and not glbally as with with the 5htp package from NPM.
|
|
96
99
|
// So we need to symbilnk the http-core node_modules in one of the parents of server.js.
|
|
97
100
|
// It avoids errors like: "Error: Cannot find module 'intl'"
|
|
98
|
-
|
|
101
|
+
this.ensureSymlinkSync(coreModules, path.join(outputPath, 'node_modules'));
|
|
99
102
|
|
|
100
|
-
// Same problem: when 5htp-core is installed via npm link,
|
|
103
|
+
// Same problem: when 5htp-core is installed via npm link,
|
|
101
104
|
// Typescript doesn't detect React and shows mission JSX errors
|
|
102
105
|
const preactCoreModule = path.join(coreModules, 'preact');
|
|
103
106
|
const preactAppModule = path.join(appModules, 'preact');
|
|
104
107
|
const reactAppModule = path.join(appModules, 'react');
|
|
105
108
|
|
|
106
|
-
if (!fs.existsSync( preactAppModule
|
|
107
|
-
|
|
108
|
-
if (!fs.existsSync( reactAppModule ))
|
|
109
|
-
fs.symlinkSync( path.join(preactCoreModule, 'compat'), reactAppModule );
|
|
109
|
+
if (!fs.existsSync(preactAppModule)) fs.symlinkSync(preactCoreModule, preactAppModule);
|
|
110
|
+
if (!fs.existsSync(reactAppModule)) fs.symlinkSync(path.join(preactCoreModule, 'compat'), reactAppModule);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
private
|
|
113
|
+
private syncPublicEntries(outputPublicPath: string, generatedPublicEntries: Set<string>, useSymlinks: boolean) {
|
|
114
|
+
const publicFiles = new Set(
|
|
115
|
+
fs.readdirSync(app.paths.public).filter((publicFile) => !generatedPublicEntries.has(publicFile)),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
for (const existingPublicFile of fs.readdirSync(outputPublicPath)) {
|
|
119
|
+
if (generatedPublicEntries.has(existingPublicFile) || publicFiles.has(existingPublicFile)) continue;
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
fs.removeSync(path.join(outputPublicPath, existingPublicFile));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const publicFile of publicFiles) {
|
|
125
|
+
const sourcePath = path.join(app.paths.public, publicFile);
|
|
126
|
+
const outputFilePath = path.join(outputPublicPath, publicFile);
|
|
127
|
+
|
|
128
|
+
if (useSymlinks) {
|
|
129
|
+
this.ensureSymlinkSync(sourcePath, outputFilePath);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (fs.existsSync(outputFilePath)) fs.removeSync(outputFilePath);
|
|
134
|
+
|
|
135
|
+
fs.copySync(sourcePath, outputFilePath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private ensureSymlinkSync(targetPath: string, linkPath: string) {
|
|
140
|
+
fs.ensureDirSync(path.dirname(linkPath));
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const linkStats = fs.lstatSync(linkPath);
|
|
144
|
+
|
|
145
|
+
if (linkStats.isSymbolicLink()) {
|
|
146
|
+
const currentTarget = path.resolve(path.dirname(linkPath), fs.readlinkSync(linkPath));
|
|
147
|
+
if (currentTarget === path.resolve(targetPath)) return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fs.removeSync(linkPath);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fs.symlinkSync(targetPath, linkPath);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private findServices(dir: string) {
|
|
159
|
+
const blacklist = ['node_modules', 'proteum'];
|
|
115
160
|
const files: string[] = [];
|
|
116
161
|
const dirents = fs.readdirSync(dir, { withFileTypes: true });
|
|
117
162
|
|
|
118
163
|
for (let dirent of dirents) {
|
|
119
|
-
|
|
120
164
|
let fileName = dirent.name;
|
|
121
165
|
let filePath = path.resolve(dir, fileName);
|
|
122
166
|
|
|
123
|
-
if (blacklist.includes(
|
|
124
|
-
continue;
|
|
167
|
+
if (blacklist.includes(fileName)) continue;
|
|
125
168
|
|
|
126
169
|
// Define is we should recursively find service in the current item
|
|
127
170
|
let iterate: boolean = false;
|
|
128
171
|
if (dirent.isSymbolicLink()) {
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
iterate = true;
|
|
134
|
-
|
|
135
|
-
} else if (dirent.isDirectory())
|
|
136
|
-
iterate = true;
|
|
172
|
+
const realPath = path.resolve(dir, fs.readlinkSync(filePath));
|
|
173
|
+
const destinationInfos = fs.lstatSync(realPath);
|
|
174
|
+
if (destinationInfos.isDirectory()) iterate = true;
|
|
175
|
+
} else if (dirent.isDirectory()) iterate = true;
|
|
137
176
|
|
|
138
177
|
// Update the list of found services
|
|
139
178
|
if (iterate) {
|
|
140
|
-
files.push(
|
|
179
|
+
files.push(...this.findServices(filePath));
|
|
141
180
|
} else if (dirent.name === 'service.json') {
|
|
142
|
-
files.push(
|
|
181
|
+
files.push(path.dirname(filePath));
|
|
143
182
|
}
|
|
144
183
|
}
|
|
145
184
|
return files;
|
|
146
185
|
}
|
|
147
186
|
|
|
148
|
-
private
|
|
149
|
-
|
|
187
|
+
private findClientRouteFiles(dir: string): string[] {
|
|
188
|
+
return this.findRegisteredRouteFiles(dir, { excludeLayoutDirectories: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private findServerRouteFiles(dir: string): string[] {
|
|
192
|
+
return this.findRegisteredRouteFiles(dir);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private findRegisteredRouteFiles(dir: string, options: { excludeLayoutDirectories?: boolean } = {}): string[] {
|
|
196
|
+
if (!fs.existsSync(dir)) return [];
|
|
197
|
+
|
|
198
|
+
const files: string[] = [];
|
|
199
|
+
|
|
200
|
+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
201
|
+
const filePath = path.join(dir, dirent.name);
|
|
202
|
+
|
|
203
|
+
if (dirent.isDirectory()) {
|
|
204
|
+
if (options.excludeLayoutDirectories && dirent.name === '_layout') continue;
|
|
205
|
+
|
|
206
|
+
files.push(...this.findRegisteredRouteFiles(filePath, options));
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!dirent.isFile()) continue;
|
|
211
|
+
|
|
212
|
+
if (!/\.(ts|tsx)$/.test(dirent.name)) continue;
|
|
213
|
+
|
|
214
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
215
|
+
if (!this.hasRegisteredRouteDefinitions(filePath, content)) continue;
|
|
216
|
+
|
|
217
|
+
files.push(filePath);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return files;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private hasRegisteredRouteDefinitions(filepath: string, content: string) {
|
|
224
|
+
const sourceFile = ts.createSourceFile(
|
|
225
|
+
filepath,
|
|
226
|
+
content,
|
|
227
|
+
ts.ScriptTarget.Latest,
|
|
228
|
+
true,
|
|
229
|
+
filepath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return sourceFile.statements.some((statement) => {
|
|
233
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
234
|
+
if (!ts.isCallExpression(statement.expression)) return false;
|
|
235
|
+
if (!ts.isPropertyAccessExpression(statement.expression.expression)) return false;
|
|
236
|
+
|
|
237
|
+
const callee = statement.expression.expression;
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
ts.isIdentifier(callee.expression) &&
|
|
241
|
+
callee.expression.text === 'Router' &&
|
|
242
|
+
['page', 'error', 'get', 'post', 'put', 'delete', 'patch'].includes(callee.name.text)
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private findLayoutFiles(dir: string): string[] {
|
|
248
|
+
if (!fs.existsSync(dir)) return [];
|
|
249
|
+
|
|
250
|
+
const files: string[] = [];
|
|
251
|
+
|
|
252
|
+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
253
|
+
const filePath = path.join(dir, dirent.name);
|
|
254
|
+
|
|
255
|
+
if (dirent.isDirectory()) {
|
|
256
|
+
files.push(...this.findLayoutFiles(filePath));
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!dirent.isFile()) continue;
|
|
261
|
+
|
|
262
|
+
if (dirent.name !== 'index.tsx') continue;
|
|
263
|
+
|
|
264
|
+
if (!normalizePath(filePath).includes('/_layout/')) continue;
|
|
265
|
+
|
|
266
|
+
files.push(filePath);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return files;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private getGeneratedImportPath(fromDir: string, targetFile: string) {
|
|
273
|
+
const relativeImportPath = path.relative(fromDir, targetFile).replace(/\\/g, '/');
|
|
274
|
+
const normalizedImportPath = relativeImportPath.startsWith('.') ? relativeImportPath : './' + relativeImportPath;
|
|
275
|
+
|
|
276
|
+
return normalizedImportPath.replace(/\.(ts|tsx|js|jsx)$/, '');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private cleanupObsoleteGeneratedArtifacts() {
|
|
280
|
+
fs.removeSync(path.join(app.paths.client.generated, 'index.ts'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private readPreloadedRouteChunks() {
|
|
284
|
+
const preloadPath = path.join(app.paths.pages, 'preload.json');
|
|
285
|
+
|
|
286
|
+
if (!fs.existsSync(preloadPath)) return new Set<string>();
|
|
287
|
+
|
|
288
|
+
const content = fs.readJsonSync(preloadPath);
|
|
289
|
+
|
|
290
|
+
if (!Array.isArray(content))
|
|
291
|
+
throw new Error(`Invalid client/pages/preload.json format: expected an array of chunk ids.`);
|
|
292
|
+
|
|
293
|
+
return new Set<string>(content.filter((value): value is string => typeof value === 'string'));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private getGeneratedClientRouteModuleFilepath(filepath: string) {
|
|
297
|
+
return getGeneratedRouteModuleFilepath(app.paths.client.generated, app.paths.pages, filepath);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private getGeneratedServerRouteModuleFilepath(filepath: string) {
|
|
301
|
+
return getGeneratedRouteModuleFilepath(app.paths.server.generated, app.paths.root, filepath);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private generateClientRouteWrapperModules() {
|
|
305
|
+
const clientRouteFiles = this.findClientRouteFiles(app.paths.pages).sort((a, b) => a.localeCompare(b));
|
|
306
|
+
const routeSourceFilepaths = new Set(clientRouteFiles.map((filepath) => normalizePath(path.resolve(filepath))));
|
|
307
|
+
|
|
308
|
+
for (const filepath of clientRouteFiles) {
|
|
309
|
+
const pageChunk = cli.paths.getPageChunk(app, filepath);
|
|
310
|
+
|
|
311
|
+
writeGeneratedRouteModule({
|
|
312
|
+
outputFilepath: this.getGeneratedClientRouteModuleFilepath(filepath),
|
|
313
|
+
runtime: 'client',
|
|
314
|
+
side: 'client',
|
|
315
|
+
sourceFilepath: filepath,
|
|
316
|
+
clientRoute: { chunkId: pageChunk.chunkId, filepath: pageChunk.filepath },
|
|
317
|
+
routeSourceFilepaths,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
writeGeneratedRouteModule({
|
|
321
|
+
outputFilepath: this.getGeneratedServerRouteModuleFilepath(filepath),
|
|
322
|
+
runtime: 'server',
|
|
323
|
+
side: 'client',
|
|
324
|
+
sourceFilepath: filepath,
|
|
325
|
+
clientRoute: { chunkId: pageChunk.chunkId, filepath: pageChunk.filepath },
|
|
326
|
+
routeSourceFilepaths,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private generateServerRouteWrapperModules() {
|
|
332
|
+
const serverRouteFiles = this.findServerRouteFiles(path.join(app.paths.root, 'server', 'routes')).sort((a, b) =>
|
|
333
|
+
a.localeCompare(b),
|
|
334
|
+
);
|
|
335
|
+
const routeSourceFilepaths = new Set(serverRouteFiles.map((filepath) => normalizePath(path.resolve(filepath))));
|
|
336
|
+
|
|
337
|
+
for (const filepath of serverRouteFiles) {
|
|
338
|
+
writeGeneratedRouteModule({
|
|
339
|
+
outputFilepath: this.getGeneratedServerRouteModuleFilepath(filepath),
|
|
340
|
+
runtime: 'server',
|
|
341
|
+
side: 'server',
|
|
342
|
+
sourceFilepath: filepath,
|
|
343
|
+
routeSourceFilepaths,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private generateClientRoutesModule() {
|
|
349
|
+
const routeLoadersFile = path.join(app.paths.client.generated, 'routes.ts');
|
|
350
|
+
const preloadedChunks = this.readPreloadedRouteChunks();
|
|
351
|
+
|
|
352
|
+
const routes = this.findClientRouteFiles(app.paths.pages)
|
|
353
|
+
.sort((a, b) => a.localeCompare(b))
|
|
354
|
+
.map<TClientRouteLoader>((filepath) => {
|
|
355
|
+
const { chunkId } = cli.paths.getPageChunk(app, filepath);
|
|
356
|
+
|
|
357
|
+
return { filepath, chunkId, preload: preloadedChunks.has(chunkId) };
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const imports: string[] = [];
|
|
361
|
+
const routeEntries: string[] = [];
|
|
362
|
+
|
|
363
|
+
routes.forEach((route, index) => {
|
|
364
|
+
const normalizedImportPath = this.getGeneratedImportPath(
|
|
365
|
+
app.paths.client.generated,
|
|
366
|
+
this.getGeneratedClientRouteModuleFilepath(route.filepath),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
if (route.preload) {
|
|
370
|
+
const localIdentifier = `preloadedRoute${index}`;
|
|
371
|
+
imports.push(
|
|
372
|
+
`import { __register as ${localIdentifier} } from ${JSON.stringify(normalizedImportPath)};`,
|
|
373
|
+
);
|
|
374
|
+
routeEntries.push(
|
|
375
|
+
` ${JSON.stringify(route.chunkId)}: () => Promise.resolve({ __register: ${localIdentifier} }),`,
|
|
376
|
+
);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
routeEntries.push(
|
|
381
|
+
` ${JSON.stringify(route.chunkId)}: () => import(/* webpackChunkName: ${JSON.stringify(route.chunkId)} */ ${JSON.stringify(normalizedImportPath)}),`,
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const content = `/*----------------------------------
|
|
386
|
+
- GENERATED FILE
|
|
387
|
+
----------------------------------*/
|
|
388
|
+
|
|
389
|
+
// This file is generated by Proteum to avoid rebuilding the page loader map in Babel.
|
|
390
|
+
// Do not edit it manually.
|
|
391
|
+
|
|
392
|
+
${imports.join('\n')}
|
|
393
|
+
${imports.length ? '\n' : ''}const routes = {
|
|
394
|
+
${routeEntries.join('\n')}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export default routes;
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
writeIfChanged(routeLoadersFile, content);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private generateClientLayoutsModule() {
|
|
404
|
+
const layoutsFile = path.join(app.paths.client.generated, 'layouts.ts');
|
|
405
|
+
|
|
406
|
+
const layouts = this.findLayoutFiles(app.paths.pages)
|
|
407
|
+
.map((filepath) => {
|
|
408
|
+
const { chunkId } = cli.paths.getLayoutChunk(app, filepath);
|
|
409
|
+
const importPath = this.getGeneratedImportPath(app.paths.client.generated, filepath);
|
|
410
|
+
const relativePath = normalizePath(path.relative(app.paths.root, filepath));
|
|
411
|
+
const depth = relativePath.split('/').filter(Boolean).length;
|
|
412
|
+
|
|
413
|
+
return { filepath: relativePath, chunkId, depth, importPath };
|
|
414
|
+
})
|
|
415
|
+
.sort((a, b) => {
|
|
416
|
+
if (b.depth !== a.depth) return b.depth - a.depth;
|
|
417
|
+
return a.filepath.localeCompare(b.filepath);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const imports = layouts
|
|
421
|
+
.map((layout, index) => `import * as layoutModule${index} from ${JSON.stringify(layout.importPath)};`)
|
|
422
|
+
.join('\n');
|
|
423
|
+
|
|
424
|
+
const layoutEntries = layouts
|
|
425
|
+
.map((layout, index) => ` ${JSON.stringify(layout.chunkId)}: layoutModule${index},`)
|
|
426
|
+
.join('\n');
|
|
427
|
+
|
|
428
|
+
const orderedLayoutIds = layouts.map((layout) => ` ${JSON.stringify(layout.chunkId)},`).join('\n');
|
|
429
|
+
|
|
430
|
+
const content = `/*----------------------------------
|
|
431
|
+
- GENERATED FILE
|
|
432
|
+
----------------------------------*/
|
|
433
|
+
|
|
434
|
+
// This file is generated by Proteum from app layout files.
|
|
435
|
+
// Do not edit it manually.
|
|
436
|
+
|
|
437
|
+
${imports}
|
|
438
|
+
${imports ? '\n' : ''}const layouts = {
|
|
439
|
+
${layoutEntries}
|
|
440
|
+
};
|
|
150
441
|
|
|
442
|
+
export const layoutOrder = [
|
|
443
|
+
${orderedLayoutIds}
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
export default layouts;
|
|
447
|
+
`;
|
|
448
|
+
|
|
449
|
+
writeIfChanged(layoutsFile, content);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private generateServerRoutesModule() {
|
|
453
|
+
const routeModulesFile = path.join(app.paths.server.generated, 'routes.ts');
|
|
454
|
+
const serverRouteFiles = this.findServerRouteFiles(path.join(app.paths.root, 'server', 'routes'))
|
|
455
|
+
.sort((a, b) => a.localeCompare(b))
|
|
456
|
+
.map((filepath) => ({
|
|
457
|
+
filepath: normalizePath(path.relative(app.paths.root, filepath)),
|
|
458
|
+
importPath: this.getGeneratedImportPath(
|
|
459
|
+
app.paths.server.generated,
|
|
460
|
+
this.getGeneratedServerRouteModuleFilepath(filepath),
|
|
461
|
+
),
|
|
462
|
+
}));
|
|
463
|
+
|
|
464
|
+
const pageRouteFiles = this.findClientRouteFiles(app.paths.pages)
|
|
465
|
+
.sort((a, b) => a.localeCompare(b))
|
|
466
|
+
.map((filepath) => ({
|
|
467
|
+
filepath: normalizePath(path.relative(app.paths.root, filepath)),
|
|
468
|
+
importPath: this.getGeneratedImportPath(
|
|
469
|
+
app.paths.server.generated,
|
|
470
|
+
this.getGeneratedServerRouteModuleFilepath(filepath),
|
|
471
|
+
),
|
|
472
|
+
}));
|
|
473
|
+
|
|
474
|
+
const routeModules = [...serverRouteFiles, ...pageRouteFiles];
|
|
475
|
+
|
|
476
|
+
const imports = routeModules
|
|
477
|
+
.map(
|
|
478
|
+
(routeModule, index) =>
|
|
479
|
+
`const routeModule${index} = require(${JSON.stringify(routeModule.importPath)});`,
|
|
480
|
+
)
|
|
481
|
+
.join('\n');
|
|
482
|
+
|
|
483
|
+
const routeEntries = routeModules
|
|
484
|
+
.map(
|
|
485
|
+
(routeModule, index) => ` {
|
|
486
|
+
filepath: ${JSON.stringify(routeModule.filepath)},
|
|
487
|
+
register: routeModule${index}.__register,
|
|
488
|
+
},`,
|
|
489
|
+
)
|
|
490
|
+
.join('\n');
|
|
491
|
+
|
|
492
|
+
const content = `/*----------------------------------
|
|
493
|
+
- GENERATED FILE
|
|
494
|
+
----------------------------------*/
|
|
495
|
+
|
|
496
|
+
// This file is generated by Proteum from route registration files.
|
|
497
|
+
// Do not edit it manually.
|
|
498
|
+
|
|
499
|
+
import type { TRouteModule } from "@common/router";
|
|
500
|
+
${imports ? '\n' + imports : ''}
|
|
501
|
+
|
|
502
|
+
export type TGeneratedRouteModule = {
|
|
503
|
+
filepath: string,
|
|
504
|
+
register?: TRouteModule["__register"],
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const routeModules: TGeneratedRouteModule[] = [
|
|
508
|
+
${routeEntries}
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
export default routeModules;
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
writeIfChanged(routeModulesFile, content);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private generateRoutingModules() {
|
|
518
|
+
this.cleanupObsoleteGeneratedArtifacts();
|
|
519
|
+
this.generateClientRouteWrapperModules();
|
|
520
|
+
this.generateServerRouteWrapperModules();
|
|
521
|
+
this.generateServerRoutesModule();
|
|
522
|
+
this.generateClientRoutesModule();
|
|
523
|
+
this.generateClientLayoutsModule();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private indexControllers() {
|
|
527
|
+
const registeredServiceNamesById = new Map<string, string>(
|
|
528
|
+
Object.values(app.registered).flatMap((service: { id?: string; name?: string }) =>
|
|
529
|
+
service.id && service.name ? [[service.id, service.name]] : [],
|
|
530
|
+
),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const appControllerServiceRoots = this.findServices(path.join(app.paths.root, 'server', 'services'))
|
|
534
|
+
.map<TControllerServiceRoot | null>((serviceDir) => {
|
|
535
|
+
const metasFile = path.join(serviceDir, 'service.json');
|
|
536
|
+
const serviceMetas = fs.readJsonSync(metasFile) as { id?: string };
|
|
537
|
+
const alias = serviceMetas.id ? registeredServiceNamesById.get(serviceMetas.id) : undefined;
|
|
538
|
+
|
|
539
|
+
if (!alias) return null;
|
|
540
|
+
|
|
541
|
+
return { alias, dir: serviceDir };
|
|
542
|
+
})
|
|
543
|
+
.filter((serviceRoot): serviceRoot is TControllerServiceRoot => !!serviceRoot)
|
|
544
|
+
.sort((a, b) => b.dir.length - a.dir.length);
|
|
545
|
+
|
|
546
|
+
return indexControllers([
|
|
547
|
+
{ importPrefix: '@server/services/', root: path.join(cli.paths.core.root, 'server', 'services') },
|
|
548
|
+
{
|
|
549
|
+
importPrefix: '@/server/services/',
|
|
550
|
+
root: path.join(app.paths.root, 'server', 'services'),
|
|
551
|
+
serviceRoots: appControllerServiceRoots,
|
|
552
|
+
},
|
|
553
|
+
]);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private generateControllerModules() {
|
|
557
|
+
const controllers = this.indexControllers();
|
|
558
|
+
const clientTree = generateControllerClientTree(controllers);
|
|
559
|
+
|
|
560
|
+
const getControllerLeafMeta = (leaf: string) => {
|
|
561
|
+
const meta = JSON.parse(leaf) as {
|
|
562
|
+
routePath: string;
|
|
563
|
+
importPath: string;
|
|
564
|
+
className: string;
|
|
565
|
+
methodName: string;
|
|
566
|
+
hasInput: boolean;
|
|
567
|
+
};
|
|
568
|
+
const controllerIndex = controllers.findIndex((controller) => controller.importPath === meta.importPath);
|
|
569
|
+
|
|
570
|
+
if (controllerIndex === -1) {
|
|
571
|
+
throw new Error(`Unable to find controller import ${meta.importPath} while generating controller types.`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return { ...meta, controllerIndex };
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const runtimeLeaf = (leaf: string) => {
|
|
578
|
+
const meta = getControllerLeafMeta(leaf);
|
|
579
|
+
const resultType = `TControllerResult<Controller${meta.controllerIndex}, ${JSON.stringify(meta.methodName)}>`;
|
|
580
|
+
|
|
581
|
+
return meta.hasInput
|
|
582
|
+
? `(data) => api.createFetcher<${resultType}>('POST', ${JSON.stringify(meta.routePath)}, data)`
|
|
583
|
+
: `() => api.createFetcher<${resultType}>('POST', ${JSON.stringify(meta.routePath)})`;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const typeImports = controllers
|
|
587
|
+
.map((controller, index) => `import type Controller${index} from ${JSON.stringify(controller.importPath)};`)
|
|
588
|
+
.join('\n');
|
|
589
|
+
|
|
590
|
+
const typeLeaf = (leaf: string) => {
|
|
591
|
+
const meta = getControllerLeafMeta(leaf);
|
|
592
|
+
const fetcherType = `TControllerFetcher<Controller${meta.controllerIndex}, ${JSON.stringify(meta.methodName)}>`;
|
|
593
|
+
|
|
594
|
+
return meta.hasInput ? `(data: any) => ${fetcherType}` : `() => ${fetcherType}`;
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const createControllersContent = `/*----------------------------------
|
|
598
|
+
- GENERATED FILE
|
|
599
|
+
----------------------------------*/
|
|
600
|
+
|
|
601
|
+
// This file is generated by Proteum from server controller files.
|
|
602
|
+
// Do not edit it manually.
|
|
603
|
+
|
|
604
|
+
import type ApiClient from '@common/router/request/api';
|
|
605
|
+
import type { TFetcher } from '@common/router/request/api';
|
|
606
|
+
${typeImports ? '\n' + typeImports : ''}
|
|
607
|
+
|
|
608
|
+
type TControllerResult<TController, TMethod extends keyof TController> =
|
|
609
|
+
TController[TMethod] extends (...args: any[]) => infer TResult ? Awaited<TResult> : never;
|
|
610
|
+
|
|
611
|
+
type TControllerFetcher<TController, TMethod extends keyof TController> = TFetcher<TControllerResult<TController, TMethod>>;
|
|
612
|
+
|
|
613
|
+
export type TControllers = ${printControllerTree(clientTree, typeLeaf)};
|
|
614
|
+
|
|
615
|
+
export const createControllers = (
|
|
616
|
+
api: Pick<ApiClient, 'createFetcher'>
|
|
617
|
+
): TControllers => (
|
|
618
|
+
${printControllerTree(clientTree, runtimeLeaf)}
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
export default createControllers;
|
|
622
|
+
`;
|
|
623
|
+
|
|
624
|
+
writeIfChanged(path.join(app.paths.common.generated, 'controllers.ts'), createControllersContent);
|
|
625
|
+
|
|
626
|
+
writeIfChanged(
|
|
627
|
+
path.join(app.paths.client.generated, 'controllers.ts'),
|
|
628
|
+
`export { createControllers, default } from '@/common/.generated/controllers';
|
|
629
|
+
export type { TControllers } from '@/common/.generated/controllers';
|
|
630
|
+
`,
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
const controllerImports = controllers
|
|
634
|
+
.map((controller, index) => `import Controller${index} from ${JSON.stringify(controller.importPath)};`)
|
|
635
|
+
.join('\n');
|
|
636
|
+
|
|
637
|
+
const controllerEntries = controllers.flatMap((controller, controllerIndex) =>
|
|
638
|
+
controller.methods.map(
|
|
639
|
+
(method) => ` {
|
|
640
|
+
path: ${JSON.stringify('/api/' + method.routePath)},
|
|
641
|
+
Controller: Controller${controllerIndex},
|
|
642
|
+
method: ${JSON.stringify(method.name)},
|
|
643
|
+
},`,
|
|
644
|
+
),
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
writeIfChanged(
|
|
648
|
+
path.join(app.paths.server.generated, 'controllers.ts'),
|
|
649
|
+
`/*----------------------------------
|
|
650
|
+
- GENERATED FILE
|
|
651
|
+
----------------------------------*/
|
|
652
|
+
|
|
653
|
+
// This file is generated by Proteum from server controller files.
|
|
654
|
+
// Do not edit it manually.
|
|
655
|
+
|
|
656
|
+
import type Controller from '@server/app/controller';
|
|
657
|
+
${controllerImports ? '\n' + controllerImports : ''}
|
|
658
|
+
|
|
659
|
+
export type TGeneratedControllerDefinition = {
|
|
660
|
+
path: string,
|
|
661
|
+
Controller: new (request: any) => Controller,
|
|
662
|
+
method: string,
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const controllers: TGeneratedControllerDefinition[] = [
|
|
666
|
+
${controllerEntries.join('\n')}
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
export default controllers;
|
|
670
|
+
`,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private indexServices() {
|
|
151
675
|
// Index services
|
|
152
676
|
const searchDirs = [
|
|
153
677
|
// The less priority is the first
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
priority: -1,
|
|
157
|
-
root: path.join(cli.paths.core.root, 'server', 'services')
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
path: '@/server/services/',
|
|
161
|
-
priority: 0,
|
|
162
|
-
root: path.join(app.paths.root, 'server', 'services')
|
|
163
|
-
},
|
|
678
|
+
{ path: '@server/services/', priority: -1, root: path.join(cli.paths.core.root, 'server', 'services') },
|
|
679
|
+
{ path: '@/server/services/', priority: 0, root: path.join(app.paths.root, 'server', 'services') },
|
|
164
680
|
// Temp disabled because compile issue on vercel
|
|
165
681
|
//'': path.join(app.paths.root, 'node_modules'),
|
|
166
|
-
]
|
|
682
|
+
];
|
|
167
683
|
|
|
168
684
|
// Generate app class file
|
|
169
|
-
const servicesAvailable: {[id: string]: TServiceMetas} = {};
|
|
685
|
+
const servicesAvailable: { [id: string]: TServiceMetas } = {};
|
|
170
686
|
for (const searchDir of searchDirs) {
|
|
171
|
-
|
|
172
687
|
const services = this.findServices(searchDir.root);
|
|
173
688
|
|
|
174
689
|
for (const serviceDir of services) {
|
|
175
|
-
const metasFile = path.join(
|
|
690
|
+
const metasFile = path.join(serviceDir, 'service.json');
|
|
176
691
|
|
|
177
692
|
// The +1 is to remove the slash
|
|
178
|
-
const importationPath = searchDir.path + serviceDir.substring(
|
|
693
|
+
const importationPath = searchDir.path + serviceDir.substring(searchDir.root.length + 1);
|
|
179
694
|
|
|
180
695
|
const serviceMetas = require(metasFile);
|
|
181
696
|
|
|
182
|
-
servicesAvailable[
|
|
183
|
-
importationPath,
|
|
184
|
-
priority: searchDir.priority,
|
|
185
|
-
...serviceMetas,
|
|
186
|
-
};
|
|
697
|
+
servicesAvailable[serviceMetas.id] = { importationPath, priority: searchDir.priority, ...serviceMetas };
|
|
187
698
|
}
|
|
188
699
|
}
|
|
189
700
|
|
|
190
701
|
// Read app services
|
|
191
|
-
const imported: string[] = []
|
|
192
|
-
const referencedNames: {[serviceId: string]: string} = {} // ID to Name
|
|
702
|
+
const imported: string[] = [];
|
|
703
|
+
const referencedNames: { [serviceId: string]: string } = {}; // ID to Name
|
|
704
|
+
let serviceImportIndex = 0;
|
|
193
705
|
|
|
194
706
|
const refService = (serviceName: string, serviceConfig: any, level: number = 0): TRegisteredService => {
|
|
195
|
-
|
|
196
707
|
if (serviceConfig.refTo !== undefined) {
|
|
197
708
|
const refTo = serviceConfig.refTo;
|
|
198
709
|
return {
|
|
199
710
|
name: serviceName,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
711
|
+
className: serviceName,
|
|
712
|
+
instanciation: (_parentRef, appRef = 'this') => `${appRef}.${refTo}`,
|
|
713
|
+
priority: 0,
|
|
714
|
+
};
|
|
203
715
|
}
|
|
204
716
|
|
|
205
|
-
const serviceMetas = servicesAvailable[
|
|
717
|
+
const serviceMetas = servicesAvailable[serviceConfig.id];
|
|
206
718
|
if (serviceMetas === undefined)
|
|
207
|
-
throw new Error(
|
|
719
|
+
throw new Error(
|
|
720
|
+
`Service ${serviceConfig.id} not found. Referenced services: ${Object.keys(servicesAvailable).join('\n')}`,
|
|
721
|
+
);
|
|
208
722
|
|
|
209
723
|
const referencedName = referencedNames[serviceConfig.id];
|
|
210
724
|
if (referencedName !== undefined)
|
|
211
725
|
throw new Error(`Service ${serviceConfig.id} is already setup as ${referencedName}`);
|
|
212
|
-
|
|
213
|
-
// Generate index & typings
|
|
214
|
-
imported.push(`import ${serviceMetas.name} from "${serviceMetas.importationPath}";`);
|
|
215
726
|
|
|
216
|
-
|
|
217
|
-
|
|
727
|
+
// Generate index & typings
|
|
728
|
+
const importIdentifier = `${serviceMetas.name}Class${serviceImportIndex++}`;
|
|
729
|
+
imported.push(`import ${importIdentifier} from "${serviceMetas.importationPath}";`);
|
|
218
730
|
|
|
219
|
-
|
|
731
|
+
if (serviceConfig.name !== undefined) referencedNames[serviceConfig.id] = serviceConfig.name;
|
|
220
732
|
|
|
733
|
+
const processConfig = (config: any, level: number = 0, appRef: string = 'this') => {
|
|
221
734
|
let propsStr = '';
|
|
222
735
|
for (const key in config) {
|
|
223
736
|
const value = config[key];
|
|
224
737
|
|
|
225
738
|
if (!value || typeof value !== 'object')
|
|
226
739
|
propsStr += `"${key}":${serialize(value, { space: 4 })},\n`;
|
|
227
|
-
|
|
228
740
|
// Reference to a service
|
|
229
|
-
else if (value.type === 'service.setup' || value.type === 'service.ref')
|
|
230
|
-
|
|
231
|
-
|
|
741
|
+
else if (value.type === 'service.setup' || value.type === 'service.ref')
|
|
742
|
+
// TODO: more reliable way to detect a service reference
|
|
743
|
+
propsStr += `${key}:` + refService(key, value, level + 1).instanciation(undefined, appRef) + ',\n';
|
|
232
744
|
// Recursion
|
|
233
745
|
else if (level <= 4 && !Array.isArray(value))
|
|
234
|
-
propsStr += `"${key}":` + processConfig(value, level + 1) + ',\n';
|
|
235
|
-
|
|
236
|
-
else
|
|
237
|
-
propsStr += `"${key}":${serialize(value, { space: 4 })},\n`;
|
|
238
|
-
|
|
746
|
+
propsStr += `"${key}":` + processConfig(value, level + 1, appRef) + ',\n';
|
|
747
|
+
else propsStr += `"${key}":${serialize(value, { space: 4 })},\n`;
|
|
239
748
|
}
|
|
240
749
|
|
|
241
750
|
return `{ ${propsStr} }`;
|
|
242
|
-
}
|
|
243
|
-
const config = processConfig(serviceConfig.config || {});
|
|
751
|
+
};
|
|
244
752
|
|
|
245
753
|
// Generate the service instance
|
|
246
|
-
const instanciation = (parentRef?: string) =>
|
|
247
|
-
|
|
754
|
+
const instanciation = (parentRef?: string, appRef: string = 'this') => {
|
|
755
|
+
const config = processConfig(serviceConfig.config || {}, 0, appRef);
|
|
756
|
+
const typedRouterConfig =
|
|
757
|
+
serviceMetas.id === 'Core/Router' && parentRef
|
|
758
|
+
? `defineServiceConfig(${config} satisfies ConstructorParameters<typeof ${importIdentifier}>[1])`
|
|
759
|
+
: `defineServiceConfig(${config})`;
|
|
760
|
+
|
|
761
|
+
return `new ${importIdentifier}(
|
|
248
762
|
${parentRef ? `${parentRef},` : ''}
|
|
249
|
-
${
|
|
250
|
-
|
|
251
|
-
)
|
|
763
|
+
${typedRouterConfig},
|
|
764
|
+
${appRef}
|
|
765
|
+
)`;
|
|
766
|
+
};
|
|
252
767
|
|
|
253
768
|
return {
|
|
254
769
|
id: serviceConfig.id,
|
|
255
770
|
name: serviceName,
|
|
256
771
|
instanciation,
|
|
257
|
-
className:
|
|
772
|
+
className: importIdentifier,
|
|
258
773
|
priority: serviceConfig.config?.priority || serviceMetas.priority || 0,
|
|
259
774
|
};
|
|
260
|
-
}
|
|
775
|
+
};
|
|
261
776
|
|
|
262
|
-
const servicesCode = Object.values(app.registered).map(
|
|
777
|
+
const servicesCode = Object.values(app.registered).map((s) => refService(s.name, s, 0));
|
|
263
778
|
const sortedServices = servicesCode.sort((a, b) => a.priority - b.priority);
|
|
264
779
|
|
|
265
780
|
// Define the app class identifier
|
|
266
781
|
const appClassIdentifier = app.identity.identifier;
|
|
267
|
-
const containerServices = app.containerServices.map(
|
|
782
|
+
const containerServices = app.containerServices.map((s) => "'" + s + "'").join('|');
|
|
783
|
+
const generatedFactories = sortedServices
|
|
784
|
+
.map((service) => {
|
|
785
|
+
const factoryIdentifier = `create${service.className}`;
|
|
786
|
+
const instanceIdentifier = `${service.className}Instance`;
|
|
787
|
+
|
|
788
|
+
return `const ${factoryIdentifier} = (app: ${appClassIdentifier}) => ${service.instanciation('app', 'app')};
|
|
789
|
+
|
|
790
|
+
type ${instanceIdentifier} = ReturnType<typeof ${factoryIdentifier}>;`;
|
|
791
|
+
})
|
|
792
|
+
.join('\n\n');
|
|
268
793
|
|
|
269
794
|
// @/client/.generated/services.d.ts
|
|
270
|
-
|
|
271
|
-
path.join(
|
|
272
|
-
`declare
|
|
795
|
+
writeIfChanged(
|
|
796
|
+
path.join(app.paths.client.generated, 'services.d.ts'),
|
|
797
|
+
`declare type ${appClassIdentifier} = import("@/server/.generated/app").default;
|
|
798
|
+
|
|
799
|
+
declare module "@app" {
|
|
273
800
|
|
|
274
801
|
import { ${appClassIdentifier} as ${appClassIdentifier}Client } from "@/client";
|
|
275
802
|
import ${appClassIdentifier}Server from "@/server/.generated/app";
|
|
276
803
|
|
|
277
804
|
export const Router: ${appClassIdentifier}Client['Router'];
|
|
278
805
|
|
|
279
|
-
${sortedServices
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
806
|
+
${sortedServices
|
|
807
|
+
.map((service) =>
|
|
808
|
+
service.name !== 'Router'
|
|
809
|
+
? `export const ${service.name}: ${appClassIdentifier}Server["${service.name}"];`
|
|
810
|
+
: '',
|
|
811
|
+
)
|
|
812
|
+
.join('\n')}
|
|
283
813
|
|
|
284
814
|
}
|
|
285
815
|
|
|
@@ -299,91 +829,96 @@ declare module '@common/errors' {
|
|
|
299
829
|
export type UpgradeRequired = import('@common/errors/index').UpgradeRequired<FeatureKeys>;
|
|
300
830
|
}
|
|
301
831
|
|
|
302
|
-
declare module '@request' {
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
832
|
declare namespace preact.JSX {
|
|
307
833
|
interface HTMLAttributes {
|
|
308
834
|
src?: string;
|
|
309
835
|
}
|
|
310
836
|
}
|
|
311
|
-
|
|
837
|
+
`,
|
|
312
838
|
);
|
|
313
839
|
|
|
314
840
|
// @/client/.generated/context.ts
|
|
315
|
-
|
|
316
|
-
path.join(
|
|
317
|
-
`// TODO: move it into core (but how to make sure usecontext returns ${appClassIdentifier}'s context ?)
|
|
841
|
+
writeIfChanged(
|
|
842
|
+
path.join(app.paths.client.generated, 'context.ts'),
|
|
843
|
+
`// TODO: move it into core (but how to make sure usecontext returns ${appClassIdentifier}'s context ?)
|
|
318
844
|
import React from 'react';
|
|
319
845
|
|
|
320
|
-
import type ${appClassIdentifier}
|
|
321
|
-
import type { TRouterContext as TServerRouterRequestContext } from '@server/services/router/response';
|
|
322
|
-
import type { TRouterContext as TClientRouterRequestContext } from '@client/services/router/response';
|
|
323
|
-
import type ${appClassIdentifier}Client from '.';
|
|
846
|
+
import type ${appClassIdentifier}Client from '@/client/index';
|
|
324
847
|
|
|
325
|
-
|
|
326
|
-
// (it gets ClientApplication instead of ${appClassIdentifier}Client)
|
|
327
|
-
type ClientRequestContext = TClientRouterRequestContext<${appClassIdentifier}Client["Router"], ${appClassIdentifier}Client>;
|
|
328
|
-
type ServerRequestContext = TServerRouterRequestContext<${appClassIdentifier}Server["Router"]>
|
|
329
|
-
type UniversalServices = ClientRequestContext | ServerRequestContext
|
|
330
|
-
|
|
331
|
-
// Non-universla services are flagged as potentially undefined
|
|
332
|
-
export type ClientContext = (
|
|
333
|
-
UniversalServices
|
|
334
|
-
&
|
|
335
|
-
Partial<Omit<ClientRequestContext, keyof UniversalServices>>
|
|
336
|
-
&
|
|
337
|
-
{
|
|
338
|
-
Router: ${appClassIdentifier}Client["Router"],
|
|
339
|
-
}
|
|
340
|
-
)
|
|
848
|
+
export type ClientContext = ${appClassIdentifier}Client["Router"]["context"];
|
|
341
849
|
|
|
342
850
|
export const ReactClientContext = React.createContext<ClientContext>({} as ClientContext);
|
|
343
|
-
export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext)
|
|
851
|
+
export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext);`,
|
|
852
|
+
);
|
|
344
853
|
|
|
345
854
|
// @/common/.generated/services.d.ts
|
|
346
|
-
|
|
347
|
-
path.join(
|
|
348
|
-
`declare
|
|
855
|
+
writeIfChanged(
|
|
856
|
+
path.join(app.paths.common.generated, 'services.d.ts'),
|
|
857
|
+
`declare type ${appClassIdentifier} = import("@/server/.generated/app").default;
|
|
858
|
+
|
|
859
|
+
declare module '@models/types' {
|
|
349
860
|
export * from '@/var/prisma/index';
|
|
350
|
-
}
|
|
861
|
+
}`,
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// @/common/generated.d.ts
|
|
865
|
+
writeIfChanged(
|
|
866
|
+
path.join(app.paths.root, 'common', 'generated.d.ts'),
|
|
867
|
+
`/// <reference path="./.generated/services.d.ts" />
|
|
868
|
+
`,
|
|
351
869
|
);
|
|
352
870
|
|
|
353
871
|
// @/server/.generated/app.ts
|
|
354
|
-
|
|
355
|
-
path.join(
|
|
356
|
-
`
|
|
872
|
+
writeIfChanged(
|
|
873
|
+
path.join(app.paths.server.generated, 'app.ts'),
|
|
874
|
+
`
|
|
357
875
|
import { Application } from '@server/app/index';
|
|
358
876
|
import { ServicesContainer } from '@server/app/service/container';
|
|
359
877
|
|
|
360
878
|
${imported.join('\n')}
|
|
361
879
|
|
|
880
|
+
type TLooseServiceConfig<TConfig> =
|
|
881
|
+
TConfig extends (...args: any[]) => any ? TConfig
|
|
882
|
+
: TConfig extends Array<infer TItem> ? Array<TLooseServiceConfig<TItem>>
|
|
883
|
+
: TConfig extends object ? ({ [K in keyof TConfig]?: TLooseServiceConfig<TConfig[K]> } & Record<string, unknown>)
|
|
884
|
+
: TConfig;
|
|
885
|
+
|
|
886
|
+
const defineServiceConfig = <TConfig>(value: TConfig): TConfig => value;
|
|
887
|
+
|
|
888
|
+
${generatedFactories}
|
|
889
|
+
|
|
362
890
|
export default class ${appClassIdentifier} extends Application<ServicesContainer, CurrentUser> {
|
|
363
891
|
|
|
364
892
|
// Make sure the services typigs are reflecting the config and referring to the app
|
|
365
|
-
${sortedServices
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
893
|
+
${sortedServices
|
|
894
|
+
.map(
|
|
895
|
+
(service) =>
|
|
896
|
+
`public ${service.name}!: ${service.className}Instance;`,
|
|
897
|
+
)
|
|
898
|
+
.join('\n')}
|
|
899
|
+
|
|
900
|
+
protected registered: Record<string, { name: string; priority: number; start: () => import('@server/app/service').AnyService }> = {
|
|
901
|
+
${sortedServices
|
|
902
|
+
.map(
|
|
903
|
+
(service) =>
|
|
904
|
+
`"${service.id}": {
|
|
372
905
|
name: "${service.name}",
|
|
373
906
|
priority: ${service.priority},
|
|
374
|
-
start: () => ${service.
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
907
|
+
start: () => create${service.className}(this)
|
|
908
|
+
}`,
|
|
909
|
+
)
|
|
910
|
+
.join(',\n')}
|
|
911
|
+
};
|
|
378
912
|
}
|
|
379
913
|
|
|
380
914
|
|
|
381
|
-
|
|
915
|
+
`,
|
|
916
|
+
);
|
|
382
917
|
|
|
383
918
|
// @/server/.generated/services.d.ts
|
|
384
|
-
|
|
385
|
-
path.join(
|
|
386
|
-
`type InstalledServices = import('
|
|
919
|
+
writeIfChanged(
|
|
920
|
+
path.join(app.paths.server.generated, 'services.d.ts'),
|
|
921
|
+
`type InstalledServices = Record<string, import('@server/app/service').AnyService>;
|
|
387
922
|
|
|
388
923
|
declare type ${appClassIdentifier} = import("@/server/.generated/app").default;
|
|
389
924
|
|
|
@@ -392,9 +927,9 @@ declare module '@cli/app' {
|
|
|
392
927
|
type TSetupConfig<TConfig> =
|
|
393
928
|
TConfig extends (...args: any[]) => any ? TConfig
|
|
394
929
|
: TConfig extends Array<infer TItem> ? Array<TSetupConfig<TItem>>
|
|
395
|
-
: TConfig extends object ? {
|
|
396
|
-
[K in keyof TConfig]
|
|
397
|
-
}
|
|
930
|
+
: TConfig extends object ? ({
|
|
931
|
+
[K in keyof TConfig]?: TSetupConfig<TConfig[K]> | TServiceSetup | TServiceRef
|
|
932
|
+
} & Record<string, unknown>)
|
|
398
933
|
: TConfig;
|
|
399
934
|
|
|
400
935
|
type App = {
|
|
@@ -454,26 +989,6 @@ declare module '@server/app' {
|
|
|
454
989
|
export = foo;
|
|
455
990
|
}
|
|
456
991
|
|
|
457
|
-
declare module '@request' {
|
|
458
|
-
import type { TRouterContext } from '@server/services/router/response';
|
|
459
|
-
const routerContext: TRouterContext<${appClassIdentifier}["Router"]>;
|
|
460
|
-
export = routerContext;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
declare module '@models' {
|
|
464
|
-
import { Prisma, PrismaClient } from '@/var/prisma/index';
|
|
465
|
-
|
|
466
|
-
type ModelNames = Prisma.ModelName;
|
|
467
|
-
|
|
468
|
-
type ModelDelegates = {
|
|
469
|
-
[K in ModelNames]: PrismaClient[Uncapitalize<K>];
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
const models: ModelDelegates;
|
|
473
|
-
|
|
474
|
-
export = models;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
992
|
declare module '@common/errors' {
|
|
478
993
|
|
|
479
994
|
export * from '@common/errors/index';
|
|
@@ -488,42 +1003,73 @@ declare module '@common/errors' {
|
|
|
488
1003
|
|
|
489
1004
|
declare module '@models/types' {
|
|
490
1005
|
export * from '@/var/prisma/index';
|
|
491
|
-
}
|
|
1006
|
+
}`,
|
|
492
1007
|
);
|
|
493
1008
|
}
|
|
494
1009
|
|
|
495
|
-
|
|
496
|
-
|
|
1010
|
+
private async warmupApp() {
|
|
497
1011
|
await app.warmup();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private async refreshGeneratedArtifacts() {
|
|
1015
|
+
if (!this.refreshingGeneratedArtifacts) {
|
|
1016
|
+
this.refreshingGeneratedArtifacts = (async () => {
|
|
1017
|
+
this.indexServices();
|
|
1018
|
+
this.generateControllerModules();
|
|
1019
|
+
this.generateRoutingModules();
|
|
1020
|
+
})().finally(() => {
|
|
1021
|
+
this.refreshingGeneratedArtifacts = undefined;
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
await this.refreshingGeneratedArtifacts;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
public async refreshGeneratedTypings() {
|
|
1029
|
+
await this.warmupApp();
|
|
1030
|
+
await this.refreshGeneratedArtifacts();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
public consumeRecentCompilationResults() {
|
|
1034
|
+
const recentCompilationResults = { ...this.recentCompilationResults };
|
|
1035
|
+
this.recentCompilationResults = {};
|
|
1036
|
+
return recentCompilationResults;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
public async create() {
|
|
1040
|
+
await this.warmupApp();
|
|
498
1041
|
|
|
499
1042
|
this.cleanup();
|
|
500
1043
|
|
|
501
1044
|
this.fixNpmLinkIssues();
|
|
502
|
-
|
|
503
|
-
this.indexServices();
|
|
1045
|
+
await this.refreshGeneratedArtifacts();
|
|
504
1046
|
|
|
505
1047
|
// Create compilers
|
|
506
|
-
const multiCompiler =
|
|
507
|
-
|
|
508
|
-
|
|
1048
|
+
const multiCompiler = rspack([
|
|
1049
|
+
createServerConfig(app, this.mode, this.outputTarget),
|
|
1050
|
+
createClientConfig(app, this.mode, this.outputTarget),
|
|
509
1051
|
]);
|
|
510
1052
|
|
|
511
1053
|
for (const compiler of multiCompiler.compilers) {
|
|
512
|
-
|
|
513
1054
|
const name = compiler.name;
|
|
514
|
-
if (name === undefined)
|
|
515
|
-
throw new Error(`A name must be specified to each compiler.`);
|
|
1055
|
+
if (name === undefined) throw new Error(`A name must be specified to each compiler.`);
|
|
516
1056
|
|
|
517
1057
|
let timeStart = new Date();
|
|
518
1058
|
|
|
519
|
-
let finished: (
|
|
520
|
-
this.compiling[name] = new Promise((resolve) => finished = resolve);
|
|
1059
|
+
let finished: () => void;
|
|
1060
|
+
this.compiling[name] = new Promise((resolve) => (finished = resolve));
|
|
1061
|
+
|
|
1062
|
+
compiler.hooks.beforeRun.tapPromise(name, () => this.refreshGeneratedArtifacts());
|
|
1063
|
+
compiler.hooks.watchRun.tapPromise(name, () => this.refreshGeneratedArtifacts());
|
|
521
1064
|
|
|
522
1065
|
compiler.hooks.compile.tap(name, (compilation) => {
|
|
523
|
-
|
|
524
|
-
this.callbacks.before && this.callbacks.before( compiler );
|
|
1066
|
+
this.callbacks.before && this.callbacks.before(compiler);
|
|
525
1067
|
|
|
526
|
-
this.
|
|
1068
|
+
this.recentModifiedFiles[name] = [...(compiler.modifiedFiles ? [...compiler.modifiedFiles] : [])].map(
|
|
1069
|
+
(filepath) => normalizePath(path.resolve(filepath)),
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
this.compiling[name] = new Promise((resolve) => (finished = resolve));
|
|
527
1073
|
|
|
528
1074
|
timeStart = new Date();
|
|
529
1075
|
console.info(`[${name}] Compiling ...`);
|
|
@@ -531,22 +1077,29 @@ declare module '@models/types' {
|
|
|
531
1077
|
|
|
532
1078
|
/* TODO: Ne pas résoudre la promise tant que la recompilation des données indexées (icones, identité, ...)
|
|
533
1079
|
n'a pas été achevée */
|
|
534
|
-
compiler.hooks.done.tap(name, stats => {
|
|
1080
|
+
compiler.hooks.done.tap(name, (stats) => {
|
|
1081
|
+
const compilationSucceeded = !stats.hasErrors();
|
|
1082
|
+
this.recentCompilationResults[name] = {
|
|
1083
|
+
succeeded: compilationSucceeded,
|
|
1084
|
+
hash: typeof stats.hash === 'string' ? stats.hash : undefined,
|
|
1085
|
+
modifiedFiles: this.recentModifiedFiles[name] || [],
|
|
1086
|
+
};
|
|
535
1087
|
|
|
536
1088
|
// Shiow status
|
|
537
1089
|
const timeEnd = new Date();
|
|
538
1090
|
const time = timeEnd.getTime() - timeStart.getTime();
|
|
539
|
-
if (
|
|
540
|
-
|
|
1091
|
+
if (!compilationSucceeded) {
|
|
541
1092
|
console.info(stats.toString(compiler.options.stats));
|
|
542
1093
|
console.error(`[${name}] Failed to compile after ${time} ms`);
|
|
543
1094
|
|
|
544
1095
|
// Exit process with code 0, so the CI container can understand building failed
|
|
545
1096
|
// Only in prod, because in dev, we want the compiler watcher continue running
|
|
546
|
-
if (this.mode === 'prod')
|
|
547
|
-
process.exit(0);
|
|
548
|
-
|
|
1097
|
+
if (this.mode === 'prod') process.exit(0);
|
|
549
1098
|
} else {
|
|
1099
|
+
if (name === 'client') {
|
|
1100
|
+
writeClientManifest(stats, app.outputPath(this.outputTarget));
|
|
1101
|
+
}
|
|
1102
|
+
|
|
550
1103
|
this.debug && console.info(stats.toString(compiler.options.stats));
|
|
551
1104
|
console.info(`[${name}] Finished compilation after ${time} ms`);
|
|
552
1105
|
}
|
|
@@ -558,7 +1111,5 @@ declare module '@models/types' {
|
|
|
558
1111
|
}
|
|
559
1112
|
|
|
560
1113
|
return multiCompiler;
|
|
561
|
-
|
|
562
1114
|
}
|
|
563
|
-
|
|
564
1115
|
}
|