1 | "use strict";
|
2 |
|
3 | exports.__esModule = true;
|
4 | exports.WorkerError = void 0;
|
5 |
|
6 | const uuid = require(`uuid/v4`);
|
7 |
|
8 | const path = require(`path`);
|
9 |
|
10 | const hasha = require(`hasha`);
|
11 |
|
12 | const fs = require(`fs-extra`);
|
13 |
|
14 | const pDefer = require(`p-defer`);
|
15 |
|
16 | const _ = require(`lodash`);
|
17 |
|
18 | const {
|
19 | createContentDigest,
|
20 | slash
|
21 | } = require(`gatsby-core-utils`);
|
22 |
|
23 | const reporter = require(`gatsby-cli/lib/reporter`);
|
24 |
|
25 | const MESSAGE_TYPES = {
|
26 | JOB_CREATED: `JOB_CREATED`,
|
27 | JOB_COMPLETED: `JOB_COMPLETED`,
|
28 | JOB_FAILED: `JOB_FAILED`,
|
29 | JOB_NOT_WHITELISTED: `JOB_NOT_WHITELISTED`
|
30 | };
|
31 | let activityForJobs = null;
|
32 | let activeJobs = 0;
|
33 | let isListeningForMessages = false;
|
34 | let hasShownIPCDisabledWarning = false;
|
35 |
|
36 |
|
37 | const jobsInProcess = new Map();
|
38 |
|
39 |
|
40 | const externalJobsMap = new Map();
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | const convertPathsToAbsolute = filePath => {
|
49 | if (!path.isAbsolute(filePath)) {
|
50 | throw new Error(`${filePath} should be an absolute path.`);
|
51 | }
|
52 |
|
53 | return slash(filePath);
|
54 | };
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const createFileHash = path => hasha.fromFileSync(path, {
|
63 | algorithm: `sha1`
|
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 | let hasActiveJobs = null;
|
90 |
|
91 | const hasExternalJobsEnabled = () => process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `true` || process.env.ENABLE_GATSBY_EXTERNAL_JOBS === `1`;
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | const runLocalWorker = async (workerFn, job) => {
|
103 | await fs.ensureDir(job.outputDir);
|
104 | return new Promise((resolve, reject) => {
|
105 |
|
106 |
|
107 | setImmediate(() => {
|
108 | try {
|
109 | resolve(workerFn({
|
110 | inputPaths: job.inputPaths,
|
111 | outputDir: job.outputDir,
|
112 | args: job.args
|
113 | }));
|
114 | } catch (err) {
|
115 | reject(new WorkerError(err));
|
116 | }
|
117 | });
|
118 | });
|
119 | };
|
120 |
|
121 | const listenForJobMessages = () => {
|
122 | process.on(`message`, msg => {
|
123 | if (msg && msg.type && msg.payload && msg.payload.id && externalJobsMap.has(msg.payload.id)) {
|
124 | const {
|
125 | job,
|
126 | deferred
|
127 | } = externalJobsMap.get(msg.payload.id);
|
128 |
|
129 | switch (msg.type) {
|
130 | case MESSAGE_TYPES.JOB_COMPLETED:
|
131 | {
|
132 | deferred.resolve(msg.payload.result);
|
133 | break;
|
134 | }
|
135 |
|
136 | case MESSAGE_TYPES.JOB_FAILED:
|
137 | {
|
138 | deferred.reject(new WorkerError(msg.payload.error));
|
139 | break;
|
140 | }
|
141 |
|
142 | case MESSAGE_TYPES.JOB_NOT_WHITELISTED:
|
143 | {
|
144 | deferred.resolve(runJob(job, true));
|
145 | break;
|
146 | }
|
147 | }
|
148 |
|
149 | externalJobsMap.delete(msg.payload.id);
|
150 | }
|
151 | });
|
152 | };
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | const runExternalWorker = job => {
|
159 | const deferred = pDefer();
|
160 | externalJobsMap.set(job.id, {
|
161 | job,
|
162 | deferred
|
163 | });
|
164 | process.send({
|
165 | type: MESSAGE_TYPES.JOB_CREATED,
|
166 | payload: job
|
167 | });
|
168 | return deferred.promise;
|
169 | };
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | const runJob = (job, forceLocal = false) => {
|
181 | const {
|
182 | plugin
|
183 | } = job;
|
184 |
|
185 | try {
|
186 | const worker = require(path.posix.join(plugin.resolve, `gatsby-worker.js`));
|
187 |
|
188 | if (!worker[job.name]) {
|
189 | throw new Error(`No worker function found for ${job.name}`);
|
190 | }
|
191 |
|
192 | if (!forceLocal && !job.plugin.isLocal && hasExternalJobsEnabled()) {
|
193 | if (process.send) {
|
194 | if (!isListeningForMessages) {
|
195 | isListeningForMessages = true;
|
196 | listenForJobMessages();
|
197 | }
|
198 |
|
199 | return runExternalWorker(job);
|
200 | } else {
|
201 |
|
202 | if (!hasShownIPCDisabledWarning) {
|
203 | hasShownIPCDisabledWarning = true;
|
204 | reporter.warn(`Offloading of a job failed as IPC could not be detected. Running job locally.`);
|
205 | }
|
206 | }
|
207 | }
|
208 |
|
209 | return runLocalWorker(worker[job.name], job);
|
210 | } catch (err) {
|
211 | throw new Error(`We couldn't find a gatsby-worker.js(${plugin.resolve}/gatsby-worker.js) file for ${plugin.name}@${plugin.version}`);
|
212 | }
|
213 | };
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | exports.createInternalJob = (job, plugin) => {
|
224 |
|
225 |
|
226 | if (job.id && job.contentDigest) {
|
227 | return job;
|
228 | }
|
229 |
|
230 | const {
|
231 | name,
|
232 | inputPaths,
|
233 | outputDir,
|
234 | args
|
235 | } = job;
|
236 |
|
237 |
|
238 |
|
239 | const inputPathsWithContentDigest = inputPaths.map(path => {
|
240 | return {
|
241 | path: convertPathsToAbsolute(path),
|
242 | contentDigest: createFileHash(path)
|
243 | };
|
244 | });
|
245 |
|
246 |
|
247 | const internalJob = {
|
248 | id: uuid(),
|
249 | name,
|
250 | contentDigest: ``,
|
251 | inputPaths: inputPathsWithContentDigest,
|
252 | outputDir: convertPathsToAbsolute(outputDir),
|
253 | args,
|
254 | plugin: {
|
255 | name: plugin.name,
|
256 | version: plugin.version,
|
257 | resolve: plugin.resolve,
|
258 | isLocal: !plugin.resolve.includes(`/node_modules/`)
|
259 | }
|
260 | };
|
261 |
|
262 | internalJob.contentDigest = createContentDigest({
|
263 | name: job.name,
|
264 | inputPaths: internalJob.inputPaths.map(inputPath => inputPath.contentDigest),
|
265 | outputDir: internalJob.outputDir,
|
266 | args: internalJob.args,
|
267 | plugin: internalJob.plugin
|
268 | });
|
269 | return internalJob;
|
270 | };
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | exports.enqueueJob = async job => {
|
280 |
|
281 |
|
282 | if (jobsInProcess.has(job.contentDigest)) {
|
283 | return jobsInProcess.get(job.contentDigest).deferred.promise;
|
284 | }
|
285 |
|
286 | if (activeJobs === 0) {
|
287 | hasActiveJobs = pDefer();
|
288 | }
|
289 |
|
290 |
|
291 | activeJobs++;
|
292 |
|
293 | if (!activityForJobs) {
|
294 | activityForJobs = reporter.phantomActivity(`Running jobs v2`);
|
295 | activityForJobs.start();
|
296 | }
|
297 |
|
298 | const deferred = pDefer();
|
299 | jobsInProcess.set(job.contentDigest, {
|
300 | id: job.id,
|
301 | deferred
|
302 | });
|
303 |
|
304 | try {
|
305 | const result = await runJob(job);
|
306 |
|
307 | if (result != null && !_.isPlainObject(result)) {
|
308 | throw new Error(`Result of a worker should be an object, type of "${typeof result}" was given`);
|
309 | }
|
310 |
|
311 | deferred.resolve(result);
|
312 | } catch (err) {
|
313 | if (err instanceof Error) {
|
314 | deferred.reject(new WorkerError(err.message));
|
315 | }
|
316 |
|
317 | deferred.reject(new WorkerError(err));
|
318 | } finally {
|
319 |
|
320 | if (--activeJobs === 0) {
|
321 | hasActiveJobs.resolve();
|
322 | activityForJobs.end();
|
323 |
|
324 | activityForJobs = null;
|
325 | }
|
326 | }
|
327 |
|
328 | return deferred.promise;
|
329 | };
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | exports.getInProcessJobPromise = contentDigest => {
|
339 | var _jobsInProcess$get;
|
340 |
|
341 | return (_jobsInProcess$get = jobsInProcess.get(contentDigest)) === null || _jobsInProcess$get === void 0 ? void 0 : _jobsInProcess$get.deferred.promise;
|
342 | };
|
343 | /**
|
344 | * Remove a job from our inProgressQueue to reduce memory usage
|
345 | *
|
346 | * @param {string} contentDigest
|
347 | */
|
348 |
|
349 |
|
350 | exports.removeInProgressJob = contentDigest => {
|
351 | jobsInProcess.delete(contentDigest);
|
352 | };
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | exports.waitUntilAllJobsComplete = () => hasActiveJobs ? hasActiveJobs.promise : Promise.resolve();
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | exports.isJobStale = job => {
|
368 | const areInputPathsStale = job.inputPaths.some(inputPath => {
|
369 |
|
370 | if (!fs.existsSync(inputPath.path)) {
|
371 | return true;
|
372 | }
|
373 |
|
374 |
|
375 | const fileHash = createFileHash(inputPath.path);
|
376 | return fileHash !== inputPath.contentDigest;
|
377 | });
|
378 | return areInputPathsStale;
|
379 | };
|
380 |
|
381 | class WorkerError extends Error {
|
382 | constructor(message) {
|
383 | super(message);
|
384 | this.name = `WorkerError`;
|
385 | Error.captureStackTrace(this, WorkerError);
|
386 | }
|
387 |
|
388 | }
|
389 |
|
390 | exports.WorkerError = WorkerError;
|
391 |
|
\ | No newline at end of file |