proteum 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/AGENTS.md +9 -0
  2. package/cli/app/config.ts +61 -0
  3. package/cli/app/index.ts +227 -0
  4. package/cli/bin.js +35 -0
  5. package/cli/commands/build.ts +60 -0
  6. package/cli/commands/deploy/app.ts +29 -0
  7. package/cli/commands/deploy/web.ts +60 -0
  8. package/cli/commands/dev.ts +124 -0
  9. package/cli/commands/init.ts +85 -0
  10. package/cli/commands/refresh.ts +18 -0
  11. package/cli/compiler/client/identite.ts +69 -0
  12. package/cli/compiler/client/index.ts +343 -0
  13. package/cli/compiler/common/babel/index.ts +173 -0
  14. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  15. package/cli/compiler/common/babel/plugins/services.ts +586 -0
  16. package/cli/compiler/common/babel/routes/imports.ts +127 -0
  17. package/cli/compiler/common/babel/routes/routes.ts +1170 -0
  18. package/cli/compiler/common/files/autres.ts +39 -0
  19. package/cli/compiler/common/files/images.ts +42 -0
  20. package/cli/compiler/common/files/style.ts +82 -0
  21. package/cli/compiler/common/index.ts +165 -0
  22. package/cli/compiler/index.ts +585 -0
  23. package/cli/compiler/server/index.ts +220 -0
  24. package/cli/index.ts +213 -0
  25. package/cli/paths.ts +165 -0
  26. package/cli/print.ts +12 -0
  27. package/cli/tsconfig.json +42 -0
  28. package/cli/utils/index.ts +22 -0
  29. package/cli/utils/keyboard.ts +78 -0
  30. package/client/app/index.ts +2 -0
  31. package/client/components/Dialog/Manager.tsx +3 -49
  32. package/client/components/Dialog/index.less +3 -1
  33. package/client/components/index.ts +1 -2
  34. package/client/services/router/index.tsx +6 -16
  35. package/common/errors/index.tsx +12 -31
  36. package/package.json +58 -22
  37. package/server/app/container/config.ts +20 -1
  38. package/server/app/container/console/index.ts +1 -1
  39. package/server/services/auth/index.ts +62 -27
  40. package/server/services/auth/router/request.ts +17 -6
  41. package/server/services/router/http/index.ts +3 -3
  42. package/server/services/router/response/index.ts +1 -1
  43. package/server/services/schema/request.ts +28 -10
  44. package/server/utils/slug.ts +0 -3
  45. package/tsconfig.common.json +2 -1
  46. package/types/global/constants.d.ts +12 -0
  47. package/changelog.md +0 -5
  48. package/client/components/Button.tsx +0 -298
  49. package/client/components/Dialog/card.tsx +0 -208
  50. package/client/data/input.ts +0 -32
  51. package/client/pages/bug.tsx.old +0 -60
  52. package/templates/composant.tsx +0 -40
  53. package/templates/form.ts +0 -30
  54. package/templates/modal.tsx +0 -47
  55. package/templates/modele.ts +0 -56
  56. package/templates/page.tsx +0 -74
  57. package/templates/route.ts +0 -43
  58. package/templates/service.ts +0 -75
  59. package/vscode/copyimportationpath/.eslintrc.json +0 -24
  60. package/vscode/copyimportationpath/.vscodeignore +0 -12
  61. package/vscode/copyimportationpath/CHANGELOG.md +0 -9
  62. package/vscode/copyimportationpath/README.md +0 -3
  63. package/vscode/copyimportationpath/copyimportationpath-0.0.1.vsix +0 -0
  64. package/vscode/copyimportationpath/out/extension.js +0 -206
  65. package/vscode/copyimportationpath/out/extension.js.map +0 -1
  66. package/vscode/copyimportationpath/package-lock.json +0 -4536
  67. package/vscode/copyimportationpath/package.json +0 -86
  68. package/vscode/copyimportationpath/src/extension.ts +0 -300
  69. package/vscode/copyimportationpath/tsconfig.json +0 -22
  70. package/vscode/copyimportationpath/vsc-extension-quickstart.md +0 -42
@@ -0,0 +1,585 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import path from 'path';
7
+ import webpack from 'webpack';
8
+ import fs from 'fs-extra';
9
+ import serialize from 'serialize-javascript';
10
+
11
+ import SpeedMeasurePlugin from "speed-measure-webpack-v5-plugin";
12
+ const smp = new SpeedMeasurePlugin({ disable: true });
13
+
14
+ // Core
15
+ import app from '../app';
16
+ import cli from '..';
17
+ import createServerConfig from './server';
18
+ import createClientConfig from './client';
19
+ import { TCompileMode, TCompileOutputTarget } from './common';
20
+
21
+ type TCompilerCallback = (compiler: webpack.Compiler) => void
22
+
23
+ type TServiceMetas = {
24
+ id: string,
25
+ name: string,
26
+ parent: string,
27
+ dependences: string,
28
+ importationPath: string,
29
+ priority: number
30
+ }
31
+
32
+ type TRegisteredService = {
33
+ id?: string,
34
+ name: string,
35
+ className: string,
36
+ instanciation: (parentRef?: string) => string,
37
+ priority: number,
38
+ }
39
+
40
+ /*----------------------------------
41
+ - FONCTION
42
+ ----------------------------------*/
43
+ export default class Compiler {
44
+
45
+ public compiling: { [compiler: string]: Promise<void> } = {};
46
+
47
+ public constructor(
48
+ private mode: TCompileMode,
49
+ private callbacks: {
50
+ before?: TCompilerCallback,
51
+ after?: TCompilerCallback,
52
+ } = {},
53
+ private debug: boolean = false,
54
+ private outputTarget: TCompileOutputTarget = mode === 'dev' ? 'dev' : 'bin'
55
+ ) {
56
+
57
+ }
58
+
59
+ public cleanup() {
60
+ const outputPath = app.outputPath(this.outputTarget);
61
+ const generatedPublicEntries = new Set(['app']);
62
+
63
+ fs.emptyDirSync( outputPath );
64
+ fs.ensureDirSync( path.join(outputPath, 'public') )
65
+ const publicFiles = fs.readdirSync(app.paths.public);
66
+ for (const publicFile of publicFiles) {
67
+ if (generatedPublicEntries.has(publicFile))
68
+ continue;
69
+
70
+ // Dev: faster to use symlink
71
+ if (this.mode === 'dev')
72
+ fs.symlinkSync(
73
+ path.join(app.paths.public, publicFile),
74
+ path.join(outputPath, 'public', publicFile)
75
+ );
76
+ // Prod: Symlink not always supported by CI / Containers solutions
77
+ else
78
+ fs.copySync(
79
+ path.join(app.paths.public, publicFile),
80
+ path.join(outputPath, 'public', publicFile)
81
+ );
82
+ }
83
+ }
84
+ /* FIX issue with npm link
85
+ When we install a module with npm link, this module's deps are not installed in the parent project scope
86
+ Which causes some issues:
87
+ - The module's deps are not found by Typescript
88
+ - Including React, so VSCode shows that JSX is missing
89
+ */
90
+ public fixNpmLinkIssues() {
91
+ const corePath = path.join(app.paths.root, '/node_modules/proteum');
92
+ if (!fs.lstatSync( corePath ).isSymbolicLink())
93
+ return console.info("Not fixing npm issue because proteum wasn't installed with npm link.");
94
+
95
+ this.debug && console.info(`Fix NPM link issues ...`);
96
+ const outputPath = app.outputPath(this.outputTarget);
97
+
98
+ const appModules = path.join(app.paths.root, 'node_modules');
99
+ const coreModules = path.join(corePath, 'node_modules');
100
+
101
+ // When the 5htp package is installed from npm link,
102
+ // Modules are installed locally and not glbally as with with the 5htp package from NPM.
103
+ // So we need to symbilnk the http-core node_modules in one of the parents of server.js.
104
+ // It avoids errors like: "Error: Cannot find module 'intl'"
105
+ fs.symlinkSync( coreModules, path.join(outputPath, 'node_modules') );
106
+
107
+ // Same problem: when 5htp-core is installed via npm link,
108
+ // Typescript doesn't detect React and shows mission JSX errors
109
+ const preactCoreModule = path.join(coreModules, 'preact');
110
+ const preactAppModule = path.join(appModules, 'preact');
111
+ const reactAppModule = path.join(appModules, 'react');
112
+
113
+ if (!fs.existsSync( preactAppModule ))
114
+ fs.symlinkSync( preactCoreModule, preactAppModule );
115
+ if (!fs.existsSync( reactAppModule ))
116
+ fs.symlinkSync( path.join(preactCoreModule, 'compat'), reactAppModule );
117
+ }
118
+
119
+ private findServices( dir: string ) {
120
+
121
+ const blacklist = ['node_modules', 'proteum']
122
+ const files: string[] = [];
123
+ const dirents = fs.readdirSync(dir, { withFileTypes: true });
124
+
125
+ for (let dirent of dirents) {
126
+
127
+ let fileName = dirent.name;
128
+ let filePath = path.resolve(dir, fileName);
129
+
130
+ if (blacklist.includes( fileName ))
131
+ continue;
132
+
133
+ // Define is we should recursively find service in the current item
134
+ let iterate: boolean = false;
135
+ if (dirent.isSymbolicLink()) {
136
+
137
+ const realPath = path.resolve( dir, fs.readlinkSync(filePath) );
138
+ const destinationInfos = fs.lstatSync( realPath );
139
+ if (destinationInfos.isDirectory())
140
+ iterate = true;
141
+
142
+ } else if (dirent.isDirectory())
143
+ iterate = true;
144
+
145
+ // Update the list of found services
146
+ if (iterate) {
147
+ files.push( ...this.findServices(filePath) );
148
+ } else if (dirent.name === 'service.json') {
149
+ files.push( path.dirname(filePath) );
150
+ }
151
+ }
152
+ return files;
153
+ }
154
+
155
+ private indexServices() {
156
+
157
+
158
+ // Index services
159
+ const searchDirs = [
160
+ // The less priority is the first
161
+ {
162
+ path: '@server/services/',
163
+ priority: -1,
164
+ root: path.join(cli.paths.core.root, 'server', 'services')
165
+ },
166
+ {
167
+ path: '@/server/services/',
168
+ priority: 0,
169
+ root: path.join(app.paths.root, 'server', 'services')
170
+ },
171
+ // Temp disabled because compile issue on vercel
172
+ //'': path.join(app.paths.root, 'node_modules'),
173
+ ]
174
+
175
+ // Generate app class file
176
+ const servicesAvailable: {[id: string]: TServiceMetas} = {};
177
+ for (const searchDir of searchDirs) {
178
+
179
+ const services = this.findServices(searchDir.root);
180
+
181
+ for (const serviceDir of services) {
182
+ const metasFile = path.join( serviceDir, 'service.json');
183
+
184
+ // The +1 is to remove the slash
185
+ const importationPath = searchDir.path + serviceDir.substring( searchDir.root.length + 1 );
186
+
187
+ const serviceMetas = require(metasFile);
188
+
189
+ servicesAvailable[ serviceMetas.id ] = {
190
+ importationPath,
191
+ priority: searchDir.priority,
192
+ ...serviceMetas,
193
+ };
194
+ }
195
+ }
196
+
197
+ // Read app services
198
+ const imported: string[] = []
199
+ const referencedNames: {[serviceId: string]: string} = {} // ID to Name
200
+
201
+ const refService = (serviceName: string, serviceConfig: any, level: number = 0): TRegisteredService => {
202
+
203
+ if (serviceConfig.refTo !== undefined) {
204
+ const refTo = serviceConfig.refTo;
205
+ return {
206
+ name: serviceName,
207
+ instanciation: () => `this.${refTo}`,
208
+ priority: 0
209
+ }
210
+ }
211
+
212
+ const serviceMetas = servicesAvailable[ serviceConfig.id ];
213
+ if (serviceMetas === undefined)
214
+ throw new Error(`Service ${serviceConfig.id} not found. Referenced services: ${Object.keys(servicesAvailable).join('\n')}`);
215
+
216
+ const referencedName = referencedNames[serviceConfig.id];
217
+ if (referencedName !== undefined)
218
+ throw new Error(`Service ${serviceConfig.id} is already setup as ${referencedName}`);
219
+
220
+ // Generate index & typings
221
+ imported.push(`import ${serviceMetas.name} from "${serviceMetas.importationPath}";`);
222
+
223
+ if (serviceConfig.name !== undefined)
224
+ referencedNames[serviceConfig.id] = serviceConfig.name;
225
+
226
+ const processConfig = (config: any, level: number = 0) => {
227
+
228
+ let propsStr = '';
229
+ for (const key in config) {
230
+ const value = config[key];
231
+
232
+ if (!value || typeof value !== 'object')
233
+ propsStr += `"${key}":${serialize(value, { space: 4 })},\n`;
234
+
235
+ // Reference to a service
236
+ else if (value.type === 'service.setup' || value.type === 'service.ref') // TODO: more reliable way to detect a service reference
237
+ propsStr += `${key}:`+ refService(key, value, level + 1).instanciation() + ',\n'
238
+
239
+ // Recursion
240
+ else if (level <= 4 && !Array.isArray(value))
241
+ propsStr += `"${key}":` + processConfig(value, level + 1) + ',\n';
242
+
243
+ else
244
+ propsStr += `"${key}":${serialize(value, { space: 4 })},\n`;
245
+
246
+ }
247
+
248
+ return `{ ${propsStr} }`;
249
+ }
250
+ const config = processConfig(serviceConfig.config || {});
251
+
252
+ // Generate the service instance
253
+ const instanciation = (parentRef?: string) =>
254
+ `new ${serviceMetas.name}(
255
+ ${parentRef ? `${parentRef},` : ''}
256
+ ${config},
257
+ this
258
+ )`
259
+
260
+ return {
261
+ id: serviceConfig.id,
262
+ name: serviceName,
263
+ instanciation,
264
+ className: serviceMetas.name,
265
+ priority: serviceConfig.config?.priority || serviceMetas.priority || 0,
266
+ };
267
+ }
268
+
269
+ const servicesCode = Object.values(app.registered).map( s => refService(s.name, s, 0));
270
+ const sortedServices = servicesCode.sort((a, b) => a.priority - b.priority);
271
+
272
+ // Define the app class identifier
273
+ const appClassIdentifier = app.identity.identifier;
274
+ const containerServices = app.containerServices.map( s => "'" + s + "'").join('|');
275
+
276
+ // @/client/.generated/services.d.ts
277
+ fs.outputFileSync(
278
+ path.join( app.paths.client.generated, 'services.d.ts'),
279
+ `declare module "@app" {
280
+
281
+ import { ${appClassIdentifier} as ${appClassIdentifier}Client } from "@/client";
282
+ import ${appClassIdentifier}Server from "@/server/.generated/app";
283
+
284
+ export const Router: ${appClassIdentifier}Client['Router'];
285
+
286
+ ${sortedServices.map(service => service.name !== 'Router'
287
+ ? `export const ${service.name}: ${appClassIdentifier}Server["${service.name}"];`
288
+ : ''
289
+ ).join('\n')}
290
+
291
+ }
292
+
293
+ declare module '@models/types' {
294
+ export * from '@/var/prisma/index';
295
+ }
296
+
297
+ declare module '@common/errors' {
298
+
299
+ export * from '@common/errors/index';
300
+ export { default } from '@common/errors/index';
301
+
302
+ export const AuthRequired: typeof import('@common/errors/index').AuthRequired<FeatureKeys>;
303
+ export type AuthRequired = import('@common/errors/index').AuthRequired<FeatureKeys>;
304
+
305
+ export const UpgradeRequired: typeof import('@common/errors/index').UpgradeRequired<FeatureKeys>;
306
+ export type UpgradeRequired = import('@common/errors/index').UpgradeRequired<FeatureKeys>;
307
+ }
308
+
309
+ declare module '@request' {
310
+
311
+ }
312
+
313
+ declare namespace preact.JSX {
314
+ interface HTMLAttributes {
315
+ src?: string;
316
+ }
317
+ }
318
+ `
319
+ );
320
+
321
+ // @/client/.generated/context.ts
322
+ fs.outputFileSync(
323
+ path.join( app.paths.client.generated, 'context.ts'),
324
+ `// TODO: move it into core (but how to make sure usecontext returns ${appClassIdentifier}'s context ?)
325
+ import React from 'react';
326
+
327
+ import type ${appClassIdentifier}Server from '@/server/.generated/app';
328
+ import type { TRouterContext as TServerRouterRequestContext } from '@server/services/router/response';
329
+ import type { TRouterContext as TClientRouterRequestContext } from '@client/services/router/response';
330
+ import type ${appClassIdentifier}Client from '.';
331
+
332
+ // TO Fix: TClientRouterRequestContext is unable to get the right type of ${appClassIdentifier}Client["router"]
333
+ // (it gets ClientApplication instead of ${appClassIdentifier}Client)
334
+ type ClientRequestContext = TClientRouterRequestContext<${appClassIdentifier}Client["Router"], ${appClassIdentifier}Client>;
335
+ type ServerRequestContext = TServerRouterRequestContext<${appClassIdentifier}Server["Router"]>
336
+ type UniversalServices = ClientRequestContext | ServerRequestContext
337
+
338
+ // Non-universla services are flagged as potentially undefined
339
+ export type ClientContext = (
340
+ UniversalServices
341
+ &
342
+ Partial<Omit<ClientRequestContext, keyof UniversalServices>>
343
+ &
344
+ {
345
+ Router: ${appClassIdentifier}Client["Router"],
346
+ }
347
+ )
348
+
349
+ export const ReactClientContext = React.createContext<ClientContext>({} as ClientContext);
350
+ export default (): ClientContext => React.useContext<ClientContext>(ReactClientContext);`);
351
+
352
+ // @/common/.generated/services.d.ts
353
+ fs.outputFileSync(
354
+ path.join( app.paths.common.generated, 'services.d.ts'),
355
+ `declare module '@models/types' {
356
+ export * from '@/var/prisma/index';
357
+ }`
358
+ );
359
+
360
+ // @/server/.generated/app.ts
361
+ fs.outputFileSync(
362
+ path.join( app.paths.server.generated, 'app.ts'),
363
+ `
364
+ import { Application } from '@server/app/index';
365
+ import { ServicesContainer } from '@server/app/service/container';
366
+
367
+ ${imported.join('\n')}
368
+
369
+ export default class ${appClassIdentifier} extends Application<ServicesContainer, CurrentUser> {
370
+
371
+ // Make sure the services typigs are reflecting the config and referring to the app
372
+ ${sortedServices.map(service =>
373
+ `public ${service.name}!: ReturnType<${appClassIdentifier}["registered"]["${service.id}"]["start"]>;`
374
+ ).join('\n')}
375
+
376
+ protected registered = {
377
+ ${sortedServices.map(service =>
378
+ `"${service.id}": {
379
+ name: "${service.name}",
380
+ priority: ${service.priority},
381
+ start: () => ${service.instanciation('this')}
382
+ }`
383
+ ).join(',\n')}
384
+ } as const;
385
+ }
386
+
387
+
388
+ `);
389
+
390
+ // @/server/.generated/services.d.ts
391
+ fs.outputFileSync(
392
+ path.join( app.paths.server.generated, 'services.d.ts'),
393
+ `type InstalledServices = import('./services').Services;
394
+
395
+ declare type ${appClassIdentifier} = import("@/server/.generated/app").default;
396
+
397
+ declare module '@cli/app' {
398
+
399
+ type TSetupConfig<TConfig> =
400
+ TConfig extends (...args: any[]) => any ? TConfig
401
+ : TConfig extends Array<infer TItem> ? Array<TSetupConfig<TItem>>
402
+ : TConfig extends object ? {
403
+ [K in keyof TConfig]: TSetupConfig<TConfig[K]> | TServiceSetup | TServiceRef
404
+ }
405
+ : TConfig;
406
+
407
+ type App = {
408
+
409
+ env: TEnvConfig;
410
+
411
+ use: (referenceName: string) => TServiceRef;
412
+
413
+ setup: <TServiceName extends keyof ${appClassIdentifier}>(...args: [
414
+ // { user: app.setup('Core/User') }
415
+ servicePath: string,
416
+ serviceConfig?: {}
417
+ ] | [
418
+ // app.setup('User', 'Core/User')
419
+ serviceName: TServiceName,
420
+ servicePath: string,
421
+ serviceConfig?: TSetupConfig<${appClassIdentifier}[TServiceName]["config"]>
422
+ ]) => TServiceSetup;
423
+ }
424
+ const app: App;
425
+ export = app;
426
+ }
427
+
428
+ declare module "@app" {
429
+
430
+ import { ApplicationContainer } from '@server/app/container';
431
+
432
+ const ServerServices: (
433
+ Pick<
434
+ ApplicationContainer<InstalledServices>,
435
+ ${containerServices}
436
+ >
437
+ &
438
+ ${appClassIdentifier}
439
+ )
440
+
441
+ export = ServerServices
442
+ }
443
+
444
+ declare module '@server/app' {
445
+
446
+ import { Application } from "@server/app";
447
+ import { Environment } from "@server/app";
448
+ import { ServicesContainer } from "@server/app/service/container";
449
+
450
+ abstract class ApplicationWithServices extends Application<
451
+ ServicesContainer<InstalledServices>
452
+ > {}
453
+
454
+ export interface Exported {
455
+ Application: typeof ApplicationWithServices,
456
+ Environment: Environment,
457
+ }
458
+
459
+ const foo: Exported;
460
+
461
+ export = foo;
462
+ }
463
+
464
+ declare module '@request' {
465
+ import type { TRouterContext } from '@server/services/router/response';
466
+ const routerContext: TRouterContext<${appClassIdentifier}["Router"]>;
467
+ export = routerContext;
468
+ }
469
+
470
+ declare module '@models' {
471
+ import { Prisma, PrismaClient } from '@/var/prisma/index';
472
+
473
+ type ModelNames = Prisma.ModelName;
474
+
475
+ type ModelDelegates = {
476
+ [K in ModelNames]: PrismaClient[Uncapitalize<K>];
477
+ };
478
+
479
+ const models: ModelDelegates;
480
+
481
+ export = models;
482
+ }
483
+
484
+ declare module '@common/errors' {
485
+
486
+ export * from '@common/errors/index';
487
+ export { default } from '@common/errors/index';
488
+
489
+ export const AuthRequired: typeof import('@common/errors/index').AuthRequired<FeatureKeys>;
490
+ export type AuthRequired = import('@common/errors/index').AuthRequired<FeatureKeys>;
491
+
492
+ export const UpgradeRequired: typeof import('@common/errors/index').UpgradeRequired<FeatureKeys>;
493
+ export type UpgradeRequired = import('@common/errors/index').UpgradeRequired<FeatureKeys>;
494
+ }
495
+
496
+ declare module '@models/types' {
497
+ export * from '@/var/prisma/index';
498
+ }`
499
+ );
500
+ }
501
+
502
+ private async warmupApp() {
503
+
504
+ await app.warmup();
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
+
520
+ this.cleanup();
521
+
522
+ this.fixNpmLinkIssues();
523
+
524
+ this.indexServices();
525
+
526
+ // Create compilers
527
+ const multiCompiler = webpack([
528
+ smp.wrap( createServerConfig(app, this.mode, this.outputTarget) ),
529
+ smp.wrap( createClientConfig(app, this.mode, this.outputTarget) )
530
+ ]);
531
+
532
+ for (const compiler of multiCompiler.compilers) {
533
+
534
+ const name = compiler.name;
535
+ if (name === undefined)
536
+ throw new Error(`A name must be specified to each compiler.`);
537
+
538
+ let timeStart = new Date();
539
+
540
+ let finished: (() => void);
541
+ this.compiling[name] = new Promise((resolve) => finished = resolve);
542
+
543
+ compiler.hooks.compile.tap(name, (compilation) => {
544
+
545
+ this.callbacks.before && this.callbacks.before( compiler );
546
+
547
+ this.compiling[name] = new Promise((resolve) => finished = resolve);
548
+
549
+ timeStart = new Date();
550
+ console.info(`[${name}] Compiling ...`);
551
+ });
552
+
553
+ /* TODO: Ne pas résoudre la promise tant que la recompilation des données indexées (icones, identité, ...)
554
+ n'a pas été achevée */
555
+ compiler.hooks.done.tap(name, stats => {
556
+
557
+ // Shiow status
558
+ const timeEnd = new Date();
559
+ const time = timeEnd.getTime() - timeStart.getTime();
560
+ if (stats.hasErrors()) {
561
+
562
+ console.info(stats.toString(compiler.options.stats));
563
+ console.error(`[${name}] Failed to compile after ${time} ms`);
564
+
565
+ // Exit process with code 0, so the CI container can understand building failed
566
+ // Only in prod, because in dev, we want the compiler watcher continue running
567
+ if (this.mode === 'prod')
568
+ process.exit(0);
569
+
570
+ } else {
571
+ this.debug && console.info(stats.toString(compiler.options.stats));
572
+ console.info(`[${name}] Finished compilation after ${time} ms`);
573
+ }
574
+
575
+ // Mark as finished
576
+ finished();
577
+ delete this.compiling[name];
578
+ });
579
+ }
580
+
581
+ return multiCompiler;
582
+
583
+ }
584
+
585
+ }