-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
194 lines (166 loc) · 6.13 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import callsites from 'callsites';
import chalk from 'chalk';
import deepmerge from 'deepmerge';
import Metalsmith from 'metalsmith';
import ora from 'ora';
export interface Options {
log?: (...data: unknown[]) => void;
}
const LEFT_MARGIN = 7;
const IGNORED_JAVASCRIPT_FILES = [
'metalsmith-tracer',
'next_tick.js',
'task_queues.js',
'timers.js',
];
const DEFAULT_PLUGIN_NAME = '(unnamed)';
const UNKNOWN_PLUGIN_NAMES = [DEFAULT_PLUGIN_NAME, 'metalsmith-if'];
const timePrefix = (milliseconds: number) => {
let value = milliseconds;
let suffix = 'ms';
if (milliseconds >= 1000) {
value = milliseconds / 1000;
suffix = 's';
}
const fixed = value.toFixed(1);
return ' '.repeat(LEFT_MARGIN - fixed.length - suffix.length) + fixed + suffix;
};
export default (realMetalsmith: Metalsmith, options: Options = {}) => {
const defaultedOptions = deepmerge(
{
log: console.log,
} satisfies Options,
options || {},
);
const spinner = ora({
prefixText: `\n${' '.repeat(LEFT_MARGIN - 5 - 1)}`,
spinner: 'arrow3',
});
let index = 0;
let count = 0;
const { use } = realMetalsmith;
// eslint-disable-next-line no-param-reassign
realMetalsmith.use = (plugin: Metalsmith.Plugin) => {
count += 1;
return use.apply(realMetalsmith, [
(files, metalsmith, done) => {
const start = process.hrtime();
// Show progress message
spinner.stop();
index += 1;
spinner.text = chalk.bold(`${index}/${count} (${((index / count) * 100).toFixed(1)}%)`);
spinner.start();
const doneInterceptor: Metalsmith.DoneCallback = (err?: Error) => {
const elapsed = process.hrtime(start);
const elapsedMs = (elapsed[0] * 1e9 + elapsed[1]) / 1000000;
let elapsedColor = chalk.green;
if (elapsedMs >= 100) {
elapsedColor = chalk.yellow;
}
if (elapsedMs >= 1000) {
elapsedColor = chalk.red;
}
// Find the call stack filenames
const filenames = callsites()
.slice(1)
.map((callsite) => callsite.getFileName())
.filter((filename): filename is string => filename !== null)
// Fix URL-like file paths
.map((filename) => decodeURIComponent(filename).replace('file://', ''));
// Find the first non-plugin file in the call stack. This is to prevent us from
// mis-identifying plugins or anonymous functions.
let firstNonNodeModulesIdx = filenames.length;
for (let i = 0; i < filenames.length; i += 1) {
const filename = filenames[i];
// Found first non-node_modules file, we're no longer in the plugin call stack
if (filename.indexOf('/node_modules/') === -1) {
firstNonNodeModulesIdx = i;
break;
}
// Found ware, Metalsmith uses this for plugin management,
// we're no longer in the plugin call stack
if (
filename.indexOf('/node_modules/ware') !== -1 &&
filename.indexOf('metalsmith-branch') === -1
) {
firstNonNodeModulesIdx = i;
break;
}
}
// Find all metalsmith-related packages
const eligiblePackages = filenames
.slice(0, firstNonNodeModulesIdx + 1)
.filter((filename) => filename)
.map((filename) => {
const match = filename.match(
/.+?[/\\]node_modules[/\\](@metalsmith\/[^/\\]+|metalsmith-[^/\\]+)[/\\]/,
);
return match ? match[1] : null;
})
.filter((filename) => filename && filename.indexOf('metalsmith-tracer') === -1)
.filter((val, idx, arr) => idx === 0 || arr[idx - 1] !== val);
// Find all eligible packages, potentially multiple if using metalsmith-branch
let pkgStr = eligiblePackages
.filter((eligiblePackage) => eligiblePackage !== 'metalsmith-branch')
.reverse()
.join(', ');
let pkgColor = chalk.reset;
if (!pkgStr) {
// If we didn't find any eligible packages, use the filename
pkgColor = chalk.blackBright;
pkgStr = (
filenames
.filter((filename) => filename.indexOf('/node_modules/') === -1)
.find(() => true) || ''
)
.replace(metalsmith.directory(), '')
.replace(/^[/\\]+/, '');
}
if (
!pkgStr ||
IGNORED_JAVASCRIPT_FILES.some((ignored) => pkgStr.indexOf(ignored) !== -1)
) {
pkgStr = '(unnamed)';
}
if (UNKNOWN_PLUGIN_NAMES.indexOf(pkgStr) !== -1) {
pkgColor = chalk.blackBright;
}
spinner.stop();
defaultedOptions.log(`${elapsedColor(timePrefix(elapsedMs))} ${pkgColor(pkgStr)}`);
done(err);
};
// Run the plugin but give it our new "done" callback
plugin(files, metalsmith, doneInterceptor);
// If the plugin has fewer than 3 arguments then it can't call the "done"
// callback, so assume it executed synchronously and call it manually.
if (plugin.length < 3) {
doneInterceptor();
}
},
]);
};
const { build } = realMetalsmith;
realMetalsmith.build = (callback?: Metalsmith.Callback) =>
new Promise((resolve) => {
defaultedOptions.log(
`${'-'.repeat(LEFT_MARGIN)} ${chalk.bold('Build process started')} ${'-'.repeat(LEFT_MARGIN)}`,
);
defaultedOptions.log();
const start = process.hrtime();
build.apply(realMetalsmith, [
(...args) => {
const elapsed = process.hrtime(start);
const elapsedMs = (elapsed[0] * 1e9 + elapsed[1]) / 1000000;
spinner.stop();
defaultedOptions.log();
defaultedOptions.log(`${chalk.bold(timePrefix(elapsedMs))} Total build time`);
defaultedOptions.log();
if (callback) {
callback(...args);
}
resolve(args[1]);
},
]);
});
return realMetalsmith;
};