@roots/bud-compiler 6.20.0 → 6.22.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/README.md +2 -5
- package/lib/service/formatError.d.ts +3 -0
- package/lib/service/formatError.js +54 -0
- package/lib/service/index.d.ts +2 -8
- package/lib/service/index.js +47 -155
- package/lib/service/makeNoticeTitle.d.ts +3 -0
- package/lib/service/makeNoticeTitle.js +1 -0
- package/package.json +6 -8
- package/src/service/formatError.ts +78 -0
- package/src/service/index.ts +171 -0
- package/src/service/makeNoticeTitle.ts +5 -0
- package/src/service/index.tsx +0 -300
package/README.md
CHANGED
@@ -69,9 +69,6 @@ However, the amount of effort needed to maintain and develop new features and pr
|
|
69
69
|
<a href="https://worksitesafety.ca/careers/">
|
70
70
|
<img src="https://cdn.roots.io/app/uploads/worksite-safety.svg" alt="Worksite Safety" width="200" height="150"/>
|
71
71
|
</a>
|
72
|
-
<a href="https://www.
|
73
|
-
<img src="https://cdn.roots.io/app/uploads/
|
74
|
-
</a>
|
75
|
-
<a href="https://www.freave.com/">
|
76
|
-
<img src="https://cdn.roots.io/app/uploads/freave.svg" alt="Freave" width="200" height="150"/>
|
72
|
+
<a href="https://www.itineris.co.uk/">
|
73
|
+
<img src="https://cdn.roots.io/app/uploads/itineris.svg" alt="Itineris" width="200" height="150"/>
|
77
74
|
</a>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
import type { StatsCompilation, StatsError } from '@roots/bud-framework/config';
|
2
|
+
import type { ErrorWithSourceFile } from '@roots/bud-support/open';
|
3
|
+
export declare const makeErrorFormatter: (stats: StatsCompilation) => (error: StatsError) => ErrorWithSourceFile | StatsError;
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import isNull from '@roots/bud-support/isNull';
|
2
|
+
import isString from '@roots/bud-support/isString';
|
3
|
+
import logger from '@roots/bud-support/logger';
|
4
|
+
export const makeErrorFormatter = (stats) => (error) => {
|
5
|
+
try {
|
6
|
+
let file;
|
7
|
+
let module;
|
8
|
+
const ident = error.moduleId ?? error.moduleName;
|
9
|
+
/**
|
10
|
+
* In a perfect world webpack plugins would use the
|
11
|
+
* {@link StatsError.nameForCondition} property to identify the module.
|
12
|
+
*/
|
13
|
+
if (ident && stats?.children) {
|
14
|
+
module = stats.children
|
15
|
+
.flatMap(child => child?.modules)
|
16
|
+
.find(module => [module?.id, module?.name].includes(ident));
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* If the module is not found, we try to parse the error message
|
20
|
+
*/
|
21
|
+
if (!ident && error.message?.includes(`[stylelint]`)) {
|
22
|
+
// try to get the origin of the stylelint error,
|
23
|
+
// which is contained in the second line of the error message
|
24
|
+
const unparsedOrigin = error.message?.split(`\n`)?.[1];
|
25
|
+
// if the origin is not a string or too long, we return the error as-is
|
26
|
+
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
27
|
+
return error;
|
28
|
+
// extract absolute path and context relative name of module
|
29
|
+
const styleError = unparsedOrigin.match(/file:\/\/(.*)\x07(.*)\x1B]8;;/);
|
30
|
+
if (isNull(styleError))
|
31
|
+
return error;
|
32
|
+
// get parts of matched error
|
33
|
+
const [, file, name] = styleError;
|
34
|
+
// return enriched error
|
35
|
+
return { ...error, file, name, nameForCondition: file };
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* If the module is still not found, we return the error as-is
|
39
|
+
*/
|
40
|
+
if (!module)
|
41
|
+
return error;
|
42
|
+
/**
|
43
|
+
* We'll prefer the `nameForCondition` property if it exists,
|
44
|
+
* otherwise we'll use the `name` property.
|
45
|
+
*/
|
46
|
+
file = module.nameForCondition ?? module.name;
|
47
|
+
const name = module.name ?? error.name ?? `error`;
|
48
|
+
return { ...error, file, name };
|
49
|
+
}
|
50
|
+
catch (formatError) {
|
51
|
+
logger.warn(`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, formatError);
|
52
|
+
return error;
|
53
|
+
}
|
54
|
+
};
|
package/lib/service/index.d.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import type { Bud } from '@roots/bud-framework';
|
2
2
|
import type { Compiler as BudCompiler } from '@roots/bud-framework';
|
3
|
-
import type { MultiCompiler, MultiStats,
|
4
|
-
import type { ErrorWithSourceFile } from '@roots/bud-support/open';
|
3
|
+
import type { MultiCompiler, MultiStats, Webpack } from '@roots/bud-framework/config';
|
5
4
|
import { Service } from '@roots/bud-framework/service';
|
6
5
|
/**
|
7
6
|
* {@link BudCompiler} implementation
|
@@ -23,14 +22,10 @@ declare class Compiler extends Service implements BudCompiler {
|
|
23
22
|
* {@link BudCompiler.stats}
|
24
23
|
*/
|
25
24
|
stats: BudCompiler[`stats`];
|
26
|
-
/**
|
27
|
-
* {@link BudCompiler.onError}
|
28
|
-
*/
|
29
|
-
onError(error: Error | undefined): void;
|
30
25
|
/**
|
31
26
|
* {@link BudCompiler.onStats}
|
32
27
|
*/
|
33
|
-
onStats(stats:
|
28
|
+
onStats(stats: MultiStats): void;
|
34
29
|
/**
|
35
30
|
* {@link BudCompiler.compile}
|
36
31
|
*/
|
@@ -39,6 +34,5 @@ declare class Compiler extends Service implements BudCompiler {
|
|
39
34
|
* {@link Service.register}
|
40
35
|
*/
|
41
36
|
register?(bud: Bud): Promise<any>;
|
42
|
-
sourceErrors?(errors: Array<StatsError> | undefined): Array<ErrorWithSourceFile | StatsError>;
|
43
37
|
}
|
44
38
|
export { Compiler as default };
|
package/lib/service/index.js
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
import { __decorate } from "tslib";
|
2
|
-
import { jsx as _jsx } from "@roots/bud-support/jsx-runtime";
|
3
2
|
import { cpus } from 'node:os';
|
4
3
|
import process from 'node:process';
|
5
4
|
import { pathToFileURL } from 'node:url';
|
6
|
-
import { Display as DisplayError } from '@roots/bud-dashboard/components/error';
|
7
5
|
import { Service } from '@roots/bud-framework/service';
|
8
6
|
import { bind } from '@roots/bud-support/decorators/bind';
|
9
|
-
import { BudError
|
10
|
-
import isNull from '@roots/bud-support/isNull';
|
7
|
+
import { BudError } from '@roots/bud-support/errors';
|
11
8
|
import isNumber from '@roots/bud-support/isNumber';
|
12
|
-
import isString from '@roots/bud-support/isString';
|
13
9
|
import stripAnsi from '@roots/bud-support/strip-ansi';
|
10
|
+
import { makeErrorFormatter } from './formatError.js';
|
11
|
+
import { makeNoticeTitle } from './makeNoticeTitle.js';
|
14
12
|
/**
|
15
13
|
* {@link BudCompiler} implementation
|
16
14
|
*/
|
@@ -23,41 +21,40 @@ class Compiler extends Service {
|
|
23
21
|
* {@link BudCompiler.implementation}
|
24
22
|
*/
|
25
23
|
implementation;
|
26
|
-
/**
|
27
|
-
* {@link BudCompiler.onError}
|
28
|
-
*/
|
29
|
-
onError(error) {
|
30
|
-
process.exitCode = 1;
|
31
|
-
if (!error)
|
32
|
-
return;
|
33
|
-
this.app.server?.appliedMiddleware?.hot?.publish({ error });
|
34
|
-
const normalized = CompilerError.normalize(error, {
|
35
|
-
thrownBy: import.meta.url,
|
36
|
-
});
|
37
|
-
normalized.details = undefined;
|
38
|
-
this.app.notifier?.notify({
|
39
|
-
group: this.app.label,
|
40
|
-
message: normalized.message,
|
41
|
-
subtitle: normalized.name,
|
42
|
-
});
|
43
|
-
this.app.context.render(_jsx(DisplayError, { error: error }));
|
44
|
-
}
|
45
24
|
/**
|
46
25
|
* {@link BudCompiler.onStats}
|
47
26
|
*/
|
48
27
|
onStats(stats) {
|
49
|
-
|
50
|
-
|
51
|
-
:
|
52
|
-
|
53
|
-
|
28
|
+
this.stats = stats.toJson({
|
29
|
+
all: false,
|
30
|
+
children: {
|
31
|
+
all: false,
|
32
|
+
assets: true,
|
33
|
+
cached: true,
|
34
|
+
cachedAssets: true,
|
35
|
+
cachedModules: true,
|
36
|
+
entrypoints: true,
|
37
|
+
errorDetails: false,
|
38
|
+
errors: true,
|
39
|
+
errorsCount: true,
|
40
|
+
hash: true,
|
41
|
+
modules: true,
|
42
|
+
name: true,
|
43
|
+
outputPath: true,
|
44
|
+
timings: true,
|
45
|
+
warnings: true,
|
46
|
+
warningsCount: true,
|
47
|
+
},
|
48
|
+
});
|
49
|
+
this.app.dashboard.render(stats);
|
54
50
|
if (stats.hasErrors()) {
|
55
51
|
process.exitCode = 1;
|
52
|
+
const format = makeErrorFormatter(this.stats);
|
56
53
|
this.stats.children = this.stats.children?.map(child => ({
|
57
54
|
...child,
|
58
|
-
errors: child.errors
|
59
|
-
?
|
60
|
-
: child.errors ?? [],
|
55
|
+
errors: (child.errors
|
56
|
+
? child.errors?.map(format).filter(Boolean)
|
57
|
+
: child.errors) ?? [],
|
61
58
|
}));
|
62
59
|
this.stats.children
|
63
60
|
?.filter(child => isNumber(child.errorsCount) && child.errorsCount > 0)
|
@@ -71,7 +68,7 @@ class Compiler extends Service {
|
|
71
68
|
message: stripAnsi(error.message),
|
72
69
|
open: error.file ? pathToFileURL(error.file) : ``,
|
73
70
|
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
74
|
-
title: makeNoticeTitle(child),
|
71
|
+
title: makeNoticeTitle(this.app, child),
|
75
72
|
});
|
76
73
|
error.file && this.app.notifier.openEditor(error.file);
|
77
74
|
}
|
@@ -80,26 +77,19 @@ class Compiler extends Service {
|
|
80
77
|
}
|
81
78
|
});
|
82
79
|
}
|
83
|
-
this.stats.children
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
this.app.server?.publicUrl.href &&
|
97
|
-
this.app.context.browser &&
|
98
|
-
this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
|
99
|
-
}
|
100
|
-
catch (error) {
|
101
|
-
this.logger.error(error);
|
102
|
-
}
|
80
|
+
this.stats.children?.forEach(child => {
|
81
|
+
this.app.notifier.notify({
|
82
|
+
group: `${this.app.label}-${child.name}`,
|
83
|
+
message: child.modules
|
84
|
+
? `${child.modules.length} modules compiled`
|
85
|
+
: `Modules compiled successfully`,
|
86
|
+
open: this.app.server?.publicUrl.href,
|
87
|
+
subtitle: `Build successful`,
|
88
|
+
title: makeNoticeTitle(this.app, child),
|
89
|
+
});
|
90
|
+
this.app.server?.publicUrl.href &&
|
91
|
+
this.app.context.browser &&
|
92
|
+
this.app.notifier.openBrowser(this.app.server?.publicUrl.href);
|
103
93
|
});
|
104
94
|
}
|
105
95
|
/**
|
@@ -108,25 +98,16 @@ class Compiler extends Service {
|
|
108
98
|
async compile(bud) {
|
109
99
|
const config = !bud.hasChildren
|
110
100
|
? [await bud.build.make()]
|
111
|
-
: await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()
|
112
|
-
throw error;
|
113
|
-
})));
|
101
|
+
: await Promise.all(Object.values(bud.children).map(async (child) => await child.build.make()));
|
114
102
|
this.config = config?.filter(Boolean);
|
115
103
|
if (this.config.length > 1) {
|
116
104
|
this.config.parallelism = Math.max(cpus().length - 1, 1);
|
117
105
|
this.logger.info(`parallel compilations: ${this.config.parallelism}`);
|
118
106
|
}
|
119
|
-
await bud.hooks.fire(`compiler.before`, bud)
|
120
|
-
throw error;
|
121
|
-
});
|
122
|
-
this.logger.timeEnd(`initialize`);
|
107
|
+
await bud.hooks.fire(`compiler.before`, bud);
|
123
108
|
this.instance = this.implementation(this.config);
|
124
|
-
this.instance.hooks.done.tap(bud.label,
|
125
|
-
|
126
|
-
bud.hooks
|
127
|
-
.fire(`compiler.done`, bud, this.stats)
|
128
|
-
.catch(this.app.catch);
|
129
|
-
});
|
109
|
+
this.instance.hooks.done.tap(bud.label, this.onStats);
|
110
|
+
this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats => bud.hooks.fire(`compiler.done`, bud, stats));
|
130
111
|
return this.instance;
|
131
112
|
}
|
132
113
|
/**
|
@@ -139,72 +120,7 @@ class Compiler extends Service {
|
|
139
120
|
throw BudError.normalize(error);
|
140
121
|
});
|
141
122
|
}
|
142
|
-
sourceErrors(errors) {
|
143
|
-
if (!errors || !errors.length)
|
144
|
-
return [];
|
145
|
-
try {
|
146
|
-
return errors
|
147
|
-
?.map((error) => {
|
148
|
-
let file;
|
149
|
-
let module;
|
150
|
-
const ident = error.moduleId ?? error.moduleName;
|
151
|
-
/**
|
152
|
-
* In a perfect world webpack plugins would use the
|
153
|
-
* `nameForCondition` property to identify the module.
|
154
|
-
*/
|
155
|
-
if (ident && this.stats?.children) {
|
156
|
-
module = this.stats.children
|
157
|
-
.flatMap(child => child?.modules)
|
158
|
-
.find(module => [module?.id, module?.name].includes(ident));
|
159
|
-
}
|
160
|
-
/**
|
161
|
-
* If the module is not found, we try to parse the error message
|
162
|
-
*/
|
163
|
-
if (!ident && error.message?.includes(`[stylelint]`)) {
|
164
|
-
// try to get the origin of the stylelint error,
|
165
|
-
// which is contained in the second line of the error message
|
166
|
-
const unparsedOrigin = error.message?.split(`\n`)?.[1];
|
167
|
-
// if the origin is not a string or too long, we return the error as-is
|
168
|
-
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
169
|
-
return error;
|
170
|
-
// extract absolute path and context relative name of module
|
171
|
-
const styleError = unparsedOrigin.match(/file:\/\/(.*)\x07(.*)\x1B]8;;/);
|
172
|
-
if (isNull(styleError))
|
173
|
-
return error;
|
174
|
-
// get parts of matched error
|
175
|
-
const [, file, name] = styleError;
|
176
|
-
// return enriched error
|
177
|
-
return { ...error, file, name, nameForCondition: file };
|
178
|
-
}
|
179
|
-
/**
|
180
|
-
* If the module is still not found, we return the error as-is
|
181
|
-
*/
|
182
|
-
if (!module)
|
183
|
-
return error;
|
184
|
-
/**
|
185
|
-
* We'll prefer the `nameForCondition` property if it exists,
|
186
|
-
* otherwise we'll use the `name` property.
|
187
|
-
*/
|
188
|
-
if (module.nameForCondition) {
|
189
|
-
file = module.nameForCondition;
|
190
|
-
}
|
191
|
-
else if (module.name) {
|
192
|
-
file = this.app.path(`@src`, module.name);
|
193
|
-
}
|
194
|
-
const name = module.name ?? error.name ?? `error`;
|
195
|
-
return { ...error, file, name };
|
196
|
-
})
|
197
|
-
.filter(Boolean);
|
198
|
-
}
|
199
|
-
catch (error) {
|
200
|
-
this.logger.warn(`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`, error);
|
201
|
-
return errors;
|
202
|
-
}
|
203
|
-
}
|
204
123
|
}
|
205
|
-
__decorate([
|
206
|
-
bind
|
207
|
-
], Compiler.prototype, "onError", null);
|
208
124
|
__decorate([
|
209
125
|
bind
|
210
126
|
], Compiler.prototype, "onStats", null);
|
@@ -214,28 +130,4 @@ __decorate([
|
|
214
130
|
__decorate([
|
215
131
|
bind
|
216
132
|
], Compiler.prototype, "register", null);
|
217
|
-
__decorate([
|
218
|
-
bind
|
219
|
-
], Compiler.prototype, "sourceErrors", null);
|
220
|
-
const statsOptions = {
|
221
|
-
all: false,
|
222
|
-
children: {
|
223
|
-
all: false,
|
224
|
-
assets: true,
|
225
|
-
cached: true,
|
226
|
-
cachedAssets: true,
|
227
|
-
cachedModules: true,
|
228
|
-
entrypoints: true,
|
229
|
-
errorDetails: false,
|
230
|
-
errors: true,
|
231
|
-
errorsCount: true,
|
232
|
-
hash: true,
|
233
|
-
modules: true,
|
234
|
-
name: true,
|
235
|
-
outputPath: true,
|
236
|
-
timings: true,
|
237
|
-
warnings: true,
|
238
|
-
warningsCount: true,
|
239
|
-
},
|
240
|
-
};
|
241
133
|
export { Compiler as default };
|
@@ -0,0 +1 @@
|
|
1
|
+
export const makeNoticeTitle = (bud, child) => bud.label !== child.name ? `${bud.label} (${child.name})` : child.name;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@roots/bud-compiler",
|
3
|
-
"version": "6.
|
3
|
+
"version": "6.22.0",
|
4
4
|
"description": "Compilation handler",
|
5
5
|
"engines": {
|
6
6
|
"node": ">=16"
|
@@ -65,16 +65,14 @@
|
|
65
65
|
"types": "./lib/index.d.ts",
|
66
66
|
"module": "./lib/index.js",
|
67
67
|
"devDependencies": {
|
68
|
-
"@roots/bud-api": "6.
|
68
|
+
"@roots/bud-api": "6.22.0",
|
69
69
|
"@skypack/package-check": "0.2.2",
|
70
|
-
"@types/node": "20.
|
71
|
-
"@types/react": "18.2.45"
|
70
|
+
"@types/node": "20.12.8"
|
72
71
|
},
|
73
72
|
"dependencies": {
|
74
|
-
"@roots/bud-dashboard": "6.
|
75
|
-
"@roots/bud-framework": "6.
|
76
|
-
"@roots/bud-support": "6.
|
77
|
-
"react": "18.2.0",
|
73
|
+
"@roots/bud-dashboard": "6.22.0",
|
74
|
+
"@roots/bud-framework": "6.22.0",
|
75
|
+
"@roots/bud-support": "6.22.0",
|
78
76
|
"tslib": "2.6.2"
|
79
77
|
},
|
80
78
|
"volta": {
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import type {
|
2
|
+
StatsCompilation,
|
3
|
+
StatsError,
|
4
|
+
StatsModule,
|
5
|
+
} from '@roots/bud-framework/config'
|
6
|
+
import type {
|
7
|
+
ErrorWithSourceFile,
|
8
|
+
SourceFile,
|
9
|
+
} from '@roots/bud-support/open'
|
10
|
+
|
11
|
+
import isNull from '@roots/bud-support/isNull'
|
12
|
+
import isString from '@roots/bud-support/isString'
|
13
|
+
import logger from '@roots/bud-support/logger'
|
14
|
+
|
15
|
+
export const makeErrorFormatter =
|
16
|
+
(stats: StatsCompilation) =>
|
17
|
+
(error: StatsError): ErrorWithSourceFile | StatsError => {
|
18
|
+
try {
|
19
|
+
let file: SourceFile[`file`] | undefined
|
20
|
+
let module: StatsModule | undefined
|
21
|
+
|
22
|
+
const ident = error.moduleId ?? error.moduleName
|
23
|
+
|
24
|
+
/**
|
25
|
+
* In a perfect world webpack plugins would use the
|
26
|
+
* {@link StatsError.nameForCondition} property to identify the module.
|
27
|
+
*/
|
28
|
+
if (ident && stats?.children) {
|
29
|
+
module = stats.children
|
30
|
+
.flatMap(child => child?.modules)
|
31
|
+
.find(module => [module?.id, module?.name].includes(ident))
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* If the module is not found, we try to parse the error message
|
36
|
+
*/
|
37
|
+
if (!ident && error.message?.includes(`[stylelint]`)) {
|
38
|
+
// try to get the origin of the stylelint error,
|
39
|
+
// which is contained in the second line of the error message
|
40
|
+
const unparsedOrigin = error.message?.split(`\n`)?.[1]
|
41
|
+
|
42
|
+
// if the origin is not a string or too long, we return the error as-is
|
43
|
+
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
44
|
+
return error
|
45
|
+
|
46
|
+
// extract absolute path and context relative name of module
|
47
|
+
const styleError = unparsedOrigin.match(
|
48
|
+
/file:\/\/(.*)\x07(.*)\x1B]8;;/,
|
49
|
+
)
|
50
|
+
if (isNull(styleError)) return error
|
51
|
+
|
52
|
+
// get parts of matched error
|
53
|
+
const [, file, name] = styleError
|
54
|
+
// return enriched error
|
55
|
+
return {...error, file, name, nameForCondition: file}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* If the module is still not found, we return the error as-is
|
60
|
+
*/
|
61
|
+
if (!module) return error
|
62
|
+
|
63
|
+
/**
|
64
|
+
* We'll prefer the `nameForCondition` property if it exists,
|
65
|
+
* otherwise we'll use the `name` property.
|
66
|
+
*/
|
67
|
+
file = module.nameForCondition ?? module.name
|
68
|
+
|
69
|
+
const name = module.name ?? error.name ?? `error`
|
70
|
+
return {...error, file, name}
|
71
|
+
} catch (formatError) {
|
72
|
+
logger.warn(
|
73
|
+
`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`,
|
74
|
+
formatError,
|
75
|
+
)
|
76
|
+
return error
|
77
|
+
}
|
78
|
+
}
|
@@ -0,0 +1,171 @@
|
|
1
|
+
import type {Bud} from '@roots/bud-framework'
|
2
|
+
import type {Compiler as BudCompiler} from '@roots/bud-framework'
|
3
|
+
import type {
|
4
|
+
MultiCompiler,
|
5
|
+
MultiStats,
|
6
|
+
Webpack,
|
7
|
+
} from '@roots/bud-framework/config'
|
8
|
+
|
9
|
+
import {cpus} from 'node:os'
|
10
|
+
import process from 'node:process'
|
11
|
+
import {pathToFileURL} from 'node:url'
|
12
|
+
|
13
|
+
import {Service} from '@roots/bud-framework/service'
|
14
|
+
import {bind} from '@roots/bud-support/decorators/bind'
|
15
|
+
import {BudError} from '@roots/bud-support/errors'
|
16
|
+
import isNumber from '@roots/bud-support/isNumber'
|
17
|
+
import stripAnsi from '@roots/bud-support/strip-ansi'
|
18
|
+
|
19
|
+
import {makeErrorFormatter} from './formatError.js'
|
20
|
+
import {makeNoticeTitle} from './makeNoticeTitle.js'
|
21
|
+
|
22
|
+
/**
|
23
|
+
* {@link BudCompiler} implementation
|
24
|
+
*/
|
25
|
+
class Compiler extends Service implements BudCompiler {
|
26
|
+
/**
|
27
|
+
* {@link BudCompiler.config}
|
28
|
+
*/
|
29
|
+
public config: BudCompiler[`config`] = []
|
30
|
+
|
31
|
+
/**
|
32
|
+
* {@link BudCompiler.implementation}
|
33
|
+
*/
|
34
|
+
public implementation: BudCompiler[`implementation`] & typeof Webpack
|
35
|
+
|
36
|
+
/**
|
37
|
+
* {@link BudCompiler.instance}
|
38
|
+
*/
|
39
|
+
public declare instance: BudCompiler[`instance`]
|
40
|
+
|
41
|
+
/**
|
42
|
+
* {@link BudCompiler.stats}
|
43
|
+
*/
|
44
|
+
public declare stats: BudCompiler[`stats`]
|
45
|
+
|
46
|
+
/**
|
47
|
+
* {@link BudCompiler.onStats}
|
48
|
+
*/
|
49
|
+
@bind
|
50
|
+
public onStats(stats: MultiStats) {
|
51
|
+
this.stats = stats.toJson({
|
52
|
+
all: false,
|
53
|
+
children: {
|
54
|
+
all: false,
|
55
|
+
assets: true,
|
56
|
+
cached: true,
|
57
|
+
cachedAssets: true,
|
58
|
+
cachedModules: true,
|
59
|
+
entrypoints: true,
|
60
|
+
errorDetails: false,
|
61
|
+
errors: true,
|
62
|
+
errorsCount: true,
|
63
|
+
hash: true,
|
64
|
+
modules: true,
|
65
|
+
name: true,
|
66
|
+
outputPath: true,
|
67
|
+
timings: true,
|
68
|
+
warnings: true,
|
69
|
+
warningsCount: true,
|
70
|
+
},
|
71
|
+
})
|
72
|
+
this.app.dashboard.render(stats)
|
73
|
+
|
74
|
+
if (stats.hasErrors()) {
|
75
|
+
process.exitCode = 1
|
76
|
+
|
77
|
+
const format = makeErrorFormatter(this.stats)
|
78
|
+
this.stats.children = this.stats.children?.map(child => ({
|
79
|
+
...child,
|
80
|
+
errors:
|
81
|
+
(child.errors
|
82
|
+
? child.errors?.map(format).filter(Boolean)
|
83
|
+
: child.errors) ?? [],
|
84
|
+
}))
|
85
|
+
|
86
|
+
this.stats.children
|
87
|
+
?.filter(
|
88
|
+
child => isNumber(child.errorsCount) && child.errorsCount > 0,
|
89
|
+
)
|
90
|
+
.forEach(child => {
|
91
|
+
try {
|
92
|
+
const error = child.errors?.shift()
|
93
|
+
if (!error) return
|
94
|
+
|
95
|
+
this.app.notifier.notify({
|
96
|
+
group: `${this.app.label}-${child.name}`,
|
97
|
+
message: stripAnsi(error.message),
|
98
|
+
open: error.file ? pathToFileURL(error.file) : ``,
|
99
|
+
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
100
|
+
title: makeNoticeTitle(this.app, child),
|
101
|
+
})
|
102
|
+
|
103
|
+
error.file && this.app.notifier.openEditor(error.file)
|
104
|
+
} catch (error) {
|
105
|
+
this.logger.error(error)
|
106
|
+
}
|
107
|
+
})
|
108
|
+
}
|
109
|
+
|
110
|
+
this.stats.children?.forEach(child => {
|
111
|
+
this.app.notifier.notify({
|
112
|
+
group: `${this.app.label}-${child.name}`,
|
113
|
+
message: child.modules
|
114
|
+
? `${child.modules.length} modules compiled`
|
115
|
+
: `Modules compiled successfully`,
|
116
|
+
open: this.app.server?.publicUrl.href,
|
117
|
+
subtitle: `Build successful`,
|
118
|
+
title: makeNoticeTitle(this.app, child),
|
119
|
+
})
|
120
|
+
|
121
|
+
this.app.server?.publicUrl.href &&
|
122
|
+
this.app.context.browser &&
|
123
|
+
this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
|
124
|
+
})
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* {@link BudCompiler.compile}
|
129
|
+
*/
|
130
|
+
@bind
|
131
|
+
public async compile(bud: Bud): Promise<MultiCompiler> {
|
132
|
+
const config = !bud.hasChildren
|
133
|
+
? [await bud.build.make()]
|
134
|
+
: await Promise.all(
|
135
|
+
Object.values(bud.children).map(
|
136
|
+
async (child: Bud) => await child.build.make(),
|
137
|
+
),
|
138
|
+
)
|
139
|
+
|
140
|
+
this.config = config?.filter(Boolean)
|
141
|
+
|
142
|
+
if (this.config.length > 1) {
|
143
|
+
this.config.parallelism = Math.max(cpus().length - 1, 1)
|
144
|
+
this.logger.info(`parallel compilations: ${this.config.parallelism}`)
|
145
|
+
}
|
146
|
+
|
147
|
+
await bud.hooks.fire(`compiler.before`, bud)
|
148
|
+
|
149
|
+
this.instance = this.implementation(this.config)
|
150
|
+
this.instance.hooks.done.tap(bud.label, this.onStats)
|
151
|
+
this.instance.hooks.done.tap(`${bud.label}-compiler.done`, stats =>
|
152
|
+
bud.hooks.fire(`compiler.done`, bud, stats),
|
153
|
+
)
|
154
|
+
|
155
|
+
return this.instance
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* {@link Service.register}
|
160
|
+
*/
|
161
|
+
@bind
|
162
|
+
public override async register?(bud: Bud): Promise<any> {
|
163
|
+
this.implementation = await bud.module
|
164
|
+
.import(`@roots/bud-support/webpack`, import.meta.url)
|
165
|
+
.catch(error => {
|
166
|
+
throw BudError.normalize(error)
|
167
|
+
})
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
export {Compiler as default}
|
package/src/service/index.tsx
DELETED
@@ -1,300 +0,0 @@
|
|
1
|
-
import type {Bud} from '@roots/bud-framework'
|
2
|
-
import type {Compiler as BudCompiler} from '@roots/bud-framework'
|
3
|
-
import type {
|
4
|
-
MultiCompiler,
|
5
|
-
MultiStats,
|
6
|
-
Stats,
|
7
|
-
StatsCompilation,
|
8
|
-
StatsError,
|
9
|
-
Webpack,
|
10
|
-
} from '@roots/bud-framework/config'
|
11
|
-
import type {
|
12
|
-
ErrorWithSourceFile,
|
13
|
-
SourceFile,
|
14
|
-
} from '@roots/bud-support/open'
|
15
|
-
|
16
|
-
import {cpus} from 'node:os'
|
17
|
-
import process from 'node:process'
|
18
|
-
import {pathToFileURL} from 'node:url'
|
19
|
-
|
20
|
-
import {Display as DisplayError} from '@roots/bud-dashboard/components/error'
|
21
|
-
import {Service} from '@roots/bud-framework/service'
|
22
|
-
import {bind} from '@roots/bud-support/decorators/bind'
|
23
|
-
import {BudError, CompilerError} from '@roots/bud-support/errors'
|
24
|
-
import isNull from '@roots/bud-support/isNull'
|
25
|
-
import isNumber from '@roots/bud-support/isNumber'
|
26
|
-
import isString from '@roots/bud-support/isString'
|
27
|
-
import stripAnsi from '@roots/bud-support/strip-ansi'
|
28
|
-
|
29
|
-
/**
|
30
|
-
* {@link BudCompiler} implementation
|
31
|
-
*/
|
32
|
-
class Compiler extends Service implements BudCompiler {
|
33
|
-
/**
|
34
|
-
* {@link BudCompiler.config}
|
35
|
-
*/
|
36
|
-
public config: BudCompiler[`config`] = []
|
37
|
-
|
38
|
-
/**
|
39
|
-
* {@link BudCompiler.implementation}
|
40
|
-
*/
|
41
|
-
public implementation: BudCompiler[`implementation`] & typeof Webpack
|
42
|
-
|
43
|
-
/**
|
44
|
-
* {@link BudCompiler.instance}
|
45
|
-
*/
|
46
|
-
public declare instance: BudCompiler[`instance`]
|
47
|
-
|
48
|
-
/**
|
49
|
-
* {@link BudCompiler.stats}
|
50
|
-
*/
|
51
|
-
public declare stats: BudCompiler[`stats`]
|
52
|
-
|
53
|
-
/**
|
54
|
-
* {@link BudCompiler.onError}
|
55
|
-
*/
|
56
|
-
@bind
|
57
|
-
public onError(error: Error | undefined) {
|
58
|
-
process.exitCode = 1
|
59
|
-
if (!error) return
|
60
|
-
|
61
|
-
this.app.server?.appliedMiddleware?.hot?.publish({error})
|
62
|
-
|
63
|
-
const normalized = CompilerError.normalize(error, {
|
64
|
-
thrownBy: import.meta.url,
|
65
|
-
})
|
66
|
-
|
67
|
-
normalized.details = undefined
|
68
|
-
|
69
|
-
this.app.notifier?.notify({
|
70
|
-
group: this.app.label,
|
71
|
-
message: normalized.message,
|
72
|
-
subtitle: normalized.name,
|
73
|
-
})
|
74
|
-
|
75
|
-
this.app.context.render(<DisplayError error={error} />)
|
76
|
-
}
|
77
|
-
|
78
|
-
/**
|
79
|
-
* {@link BudCompiler.onStats}
|
80
|
-
*/
|
81
|
-
@bind
|
82
|
-
public onStats(stats: Stats & MultiStats) {
|
83
|
-
const makeNoticeTitle = (child: StatsCompilation) =>
|
84
|
-
this.app.label !== child.name
|
85
|
-
? `${this.app.label} (${child.name})`
|
86
|
-
: child.name
|
87
|
-
|
88
|
-
this.stats = stats.toJson(statsOptions)
|
89
|
-
this.app.context.render(this.app.dashboard.render(stats))
|
90
|
-
|
91
|
-
if (stats.hasErrors()) {
|
92
|
-
process.exitCode = 1
|
93
|
-
|
94
|
-
this.stats.children = this.stats.children?.map(child => ({
|
95
|
-
...child,
|
96
|
-
errors:
|
97
|
-
child.errors && this.sourceErrors
|
98
|
-
? this.sourceErrors(child.errors)
|
99
|
-
: child.errors ?? [],
|
100
|
-
}))
|
101
|
-
|
102
|
-
this.stats.children
|
103
|
-
?.filter(
|
104
|
-
child => isNumber(child.errorsCount) && child.errorsCount > 0,
|
105
|
-
)
|
106
|
-
.forEach(child => {
|
107
|
-
try {
|
108
|
-
const error = child.errors?.shift()
|
109
|
-
if (!error) return
|
110
|
-
|
111
|
-
this.app.notifier.notify({
|
112
|
-
group: `${this.app.label}-${child.name}`,
|
113
|
-
message: stripAnsi(error.message),
|
114
|
-
open: error.file ? pathToFileURL(error.file) : ``,
|
115
|
-
subtitle: error.file ? `Error in ${error.name}` : error.name,
|
116
|
-
title: makeNoticeTitle(child),
|
117
|
-
})
|
118
|
-
|
119
|
-
error.file && this.app.notifier.openEditor(error.file)
|
120
|
-
} catch (error) {
|
121
|
-
this.logger.error(error)
|
122
|
-
}
|
123
|
-
})
|
124
|
-
}
|
125
|
-
|
126
|
-
this.stats.children
|
127
|
-
?.filter(child => child.errorsCount === 0)
|
128
|
-
.forEach(child => {
|
129
|
-
try {
|
130
|
-
this.app.notifier.notify({
|
131
|
-
group: `${this.app.label}-${child.name}`,
|
132
|
-
message: child.modules
|
133
|
-
? `${child.modules.length} modules compiled`
|
134
|
-
: `Modules compiled successfully`,
|
135
|
-
open: this.app.server?.publicUrl.href,
|
136
|
-
subtitle: `Build successful`,
|
137
|
-
title: makeNoticeTitle(child),
|
138
|
-
})
|
139
|
-
|
140
|
-
this.app.server?.publicUrl.href &&
|
141
|
-
this.app.context.browser &&
|
142
|
-
this.app.notifier.openBrowser(this.app.server?.publicUrl.href)
|
143
|
-
} catch (error) {
|
144
|
-
this.logger.error(error)
|
145
|
-
}
|
146
|
-
})
|
147
|
-
}
|
148
|
-
/**
|
149
|
-
* {@link BudCompiler.compile}
|
150
|
-
*/
|
151
|
-
@bind
|
152
|
-
public async compile(bud: Bud): Promise<MultiCompiler> {
|
153
|
-
const config = !bud.hasChildren
|
154
|
-
? [await bud.build.make()]
|
155
|
-
: await Promise.all(
|
156
|
-
Object.values(bud.children).map(
|
157
|
-
async (child: Bud) =>
|
158
|
-
await child.build.make().catch(error => {
|
159
|
-
throw error
|
160
|
-
}),
|
161
|
-
),
|
162
|
-
)
|
163
|
-
|
164
|
-
this.config = config?.filter(Boolean)
|
165
|
-
|
166
|
-
if (this.config.length > 1) {
|
167
|
-
this.config.parallelism = Math.max(cpus().length - 1, 1)
|
168
|
-
this.logger.info(`parallel compilations: ${this.config.parallelism}`)
|
169
|
-
}
|
170
|
-
|
171
|
-
await bud.hooks.fire(`compiler.before`, bud).catch(error => {
|
172
|
-
throw error
|
173
|
-
})
|
174
|
-
|
175
|
-
this.logger.timeEnd(`initialize`)
|
176
|
-
|
177
|
-
this.instance = this.implementation(this.config)
|
178
|
-
|
179
|
-
this.instance.hooks.done.tap(bud.label, (stats: any) => {
|
180
|
-
this.onStats(stats)
|
181
|
-
bud.hooks
|
182
|
-
.fire(`compiler.done`, bud, this.stats)
|
183
|
-
.catch(this.app.catch)
|
184
|
-
})
|
185
|
-
|
186
|
-
return this.instance
|
187
|
-
}
|
188
|
-
|
189
|
-
/**
|
190
|
-
* {@link Service.register}
|
191
|
-
*/
|
192
|
-
@bind
|
193
|
-
public override async register?(bud: Bud): Promise<any> {
|
194
|
-
this.implementation = await bud.module
|
195
|
-
.import(`@roots/bud-support/webpack`, import.meta.url)
|
196
|
-
.catch(error => {
|
197
|
-
throw BudError.normalize(error)
|
198
|
-
})
|
199
|
-
}
|
200
|
-
|
201
|
-
@bind
|
202
|
-
public sourceErrors?(
|
203
|
-
errors: Array<StatsError> | undefined,
|
204
|
-
): Array<ErrorWithSourceFile | StatsError> {
|
205
|
-
if (!errors || !errors.length) return []
|
206
|
-
|
207
|
-
try {
|
208
|
-
return errors
|
209
|
-
?.map((error: StatsError): ErrorWithSourceFile | StatsError => {
|
210
|
-
let file: SourceFile[`file`] | undefined
|
211
|
-
let module: undefined | Webpack.StatsModule
|
212
|
-
|
213
|
-
const ident = error.moduleId ?? error.moduleName
|
214
|
-
|
215
|
-
/**
|
216
|
-
* In a perfect world webpack plugins would use the
|
217
|
-
* `nameForCondition` property to identify the module.
|
218
|
-
*/
|
219
|
-
if (ident && this.stats?.children) {
|
220
|
-
module = this.stats.children
|
221
|
-
.flatMap(child => child?.modules)
|
222
|
-
.find(module => [module?.id, module?.name].includes(ident))
|
223
|
-
}
|
224
|
-
|
225
|
-
/**
|
226
|
-
* If the module is not found, we try to parse the error message
|
227
|
-
*/
|
228
|
-
if (!ident && error.message?.includes(`[stylelint]`)) {
|
229
|
-
// try to get the origin of the stylelint error,
|
230
|
-
// which is contained in the second line of the error message
|
231
|
-
const unparsedOrigin = error.message?.split(`\n`)?.[1]
|
232
|
-
|
233
|
-
// if the origin is not a string or too long, we return the error as-is
|
234
|
-
if (!isString(unparsedOrigin) || unparsedOrigin.length > 100)
|
235
|
-
return error
|
236
|
-
|
237
|
-
// extract absolute path and context relative name of module
|
238
|
-
const styleError = unparsedOrigin.match(
|
239
|
-
/file:\/\/(.*)\x07(.*)\x1B]8;;/,
|
240
|
-
)
|
241
|
-
if (isNull(styleError)) return error
|
242
|
-
|
243
|
-
// get parts of matched error
|
244
|
-
const [, file, name] = styleError
|
245
|
-
// return enriched error
|
246
|
-
return {...error, file, name, nameForCondition: file}
|
247
|
-
}
|
248
|
-
|
249
|
-
/**
|
250
|
-
* If the module is still not found, we return the error as-is
|
251
|
-
*/
|
252
|
-
if (!module) return error
|
253
|
-
|
254
|
-
/**
|
255
|
-
* We'll prefer the `nameForCondition` property if it exists,
|
256
|
-
* otherwise we'll use the `name` property.
|
257
|
-
*/
|
258
|
-
if (module.nameForCondition) {
|
259
|
-
file = module.nameForCondition
|
260
|
-
} else if (module.name) {
|
261
|
-
file = this.app.path(`@src`, module.name)
|
262
|
-
}
|
263
|
-
|
264
|
-
const name = module.name ?? error.name ?? `error`
|
265
|
-
return {...error, file, name}
|
266
|
-
})
|
267
|
-
.filter(Boolean)
|
268
|
-
} catch (error) {
|
269
|
-
this.logger.warn(
|
270
|
-
`Problem parsing errors. This probably won't break anything but please report it: https://github.com/roots/bud/issues/new`,
|
271
|
-
error,
|
272
|
-
)
|
273
|
-
return errors
|
274
|
-
}
|
275
|
-
}
|
276
|
-
}
|
277
|
-
|
278
|
-
const statsOptions = {
|
279
|
-
all: false,
|
280
|
-
children: {
|
281
|
-
all: false,
|
282
|
-
assets: true,
|
283
|
-
cached: true,
|
284
|
-
cachedAssets: true,
|
285
|
-
cachedModules: true,
|
286
|
-
entrypoints: true,
|
287
|
-
errorDetails: false,
|
288
|
-
errors: true,
|
289
|
-
errorsCount: true,
|
290
|
-
hash: true,
|
291
|
-
modules: true,
|
292
|
-
name: true,
|
293
|
-
outputPath: true,
|
294
|
-
timings: true,
|
295
|
-
warnings: true,
|
296
|
-
warningsCount: true,
|
297
|
-
},
|
298
|
-
}
|
299
|
-
|
300
|
-
export {Compiler as default}
|