@serwist/cli 8.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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/bin.js +539 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Google LLC, 2019 ShadowWalker w@weiw.io https://weiw.io, 2023 Serwist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-cli
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import meow from 'meow';
|
|
2
|
+
import updateNotifier from 'update-notifier';
|
|
3
|
+
import { injectManifest } from '@serwist/build';
|
|
4
|
+
import assert from 'assert';
|
|
5
|
+
import chokidar from 'chokidar';
|
|
6
|
+
import prettyBytes from 'pretty-bytes';
|
|
7
|
+
import upath from 'upath';
|
|
8
|
+
import { oneLine } from 'common-tags';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import fse from 'fs-extra';
|
|
12
|
+
import stringifyObject from 'stringify-object';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
14
|
+
import { glob } from 'glob';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
Copyright 2018 Google LLC
|
|
19
|
+
|
|
20
|
+
Use of this source code is governed by an MIT-style
|
|
21
|
+
license that can be found in the LICENSE file or at
|
|
22
|
+
https://opensource.org/licenses/MIT.
|
|
23
|
+
*/ const constants = {
|
|
24
|
+
defaultConfigFile: "serwist.config.js",
|
|
25
|
+
ignoredDirectories: [
|
|
26
|
+
"node_modules"
|
|
27
|
+
],
|
|
28
|
+
ignoredFileExtensions: [
|
|
29
|
+
"map"
|
|
30
|
+
],
|
|
31
|
+
ignoreURLParametersMatching: [
|
|
32
|
+
/^utm_/,
|
|
33
|
+
/^fbclid$/
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const errors = {
|
|
38
|
+
"missing-input": `params.input value was not set properly.`,
|
|
39
|
+
"missing-dest-dir-param": oneLine`Please provide the path to a directory in which
|
|
40
|
+
the libraries will be copied.`,
|
|
41
|
+
"invalid-common-js-module": oneLine`Please pass in a valid CommonJS module that
|
|
42
|
+
exports your configuration.`,
|
|
43
|
+
"config-validation-failed": `Your configuration is invalid:`,
|
|
44
|
+
"serwist-build-runtime-error": `Service worker generation failed:`,
|
|
45
|
+
"unknown-command": `Unknown command:`,
|
|
46
|
+
"no-file-extensions-found": oneLine`No files could be found that are suitable for
|
|
47
|
+
caching.`,
|
|
48
|
+
"no-file-extensions-selected": `Please select at least one file extension.`,
|
|
49
|
+
"invalid-sw-dest": oneLine`Please enter a valid path to use for the service worker
|
|
50
|
+
file that's created.`,
|
|
51
|
+
"glob-directory-invalid": oneLine`The path you entered isn't a valid directory.`,
|
|
52
|
+
"invalid-config-location": oneLine`Please enter a valid path to use for the saved
|
|
53
|
+
configuration file.`,
|
|
54
|
+
"sw-src-missing-injection-point": oneLine`That is not a valid source service worker
|
|
55
|
+
file. Please try again with a file containing
|
|
56
|
+
'self.__SW_MANIFEST'.`,
|
|
57
|
+
"no-search-parameters-supplied": oneLine`Please provide the url search param(s)
|
|
58
|
+
you would like to ignore.`,
|
|
59
|
+
"invalid-search-parameters-supplied": oneLine`Please provide the valid URL search parameter(s)
|
|
60
|
+
without the leading '/' or '?' (i.e. source,version,language).`
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const logger = {
|
|
64
|
+
debug: (...args)=>console.log(chalk.gray(...args)),
|
|
65
|
+
log: (...args)=>console.log(...args),
|
|
66
|
+
warn: (...args)=>console.warn(chalk.yellow(...args)),
|
|
67
|
+
error: (...args)=>console.error(chalk.red.bold(...args))
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const require = createRequire(import.meta.url);
|
|
71
|
+
// A really light wrapper on top of Node's require() to make it easier to stub
|
|
72
|
+
// out reading the configuration during tests.
|
|
73
|
+
function readConfig(configFile) {
|
|
74
|
+
return require(configFile);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The key used for the question/answer.
|
|
78
|
+
const name$3 = "configLocation";
|
|
79
|
+
const configLocationQuestion = {
|
|
80
|
+
name: name$3,
|
|
81
|
+
message: oneLine`Where would you like to save these configuration options?`,
|
|
82
|
+
type: "input",
|
|
83
|
+
default: constants.defaultConfigFile
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* @returns The answers from inquirer.
|
|
87
|
+
*/ function askQuestion$5() {
|
|
88
|
+
return inquirer.prompt([
|
|
89
|
+
configLocationQuestion
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
async function askConfigLocation() {
|
|
93
|
+
const answers = await askQuestion$5();
|
|
94
|
+
// The value of the answer when the question type is 'input' is String
|
|
95
|
+
// and it has a default value, the casting is safe.
|
|
96
|
+
const configLocation = answers[name$3].trim();
|
|
97
|
+
assert(configLocation, errors["invalid-config-location"]);
|
|
98
|
+
return configLocation;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The key used for the question/answer.
|
|
102
|
+
const name$2 = "globPatterns";
|
|
103
|
+
/**
|
|
104
|
+
* @param globDirectory The directory used for the root of globbing.
|
|
105
|
+
* @returns The unique file extensions corresponding
|
|
106
|
+
* to all of the files under globDirectory.
|
|
107
|
+
*/ async function getAllFileExtensions(globDirectory) {
|
|
108
|
+
// Use a pattern to match any file that contains a '.', since that signifies
|
|
109
|
+
// the presence of a file extension.
|
|
110
|
+
const files = await glob("**/*.*", {
|
|
111
|
+
cwd: globDirectory,
|
|
112
|
+
nodir: true,
|
|
113
|
+
ignore: [
|
|
114
|
+
...constants.ignoredDirectories.map((directory)=>`**/${directory}/**`),
|
|
115
|
+
...constants.ignoredFileExtensions.map((extension)=>`**/*.${extension}`)
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
const extensions = new Set();
|
|
119
|
+
for (const file of files){
|
|
120
|
+
const extension = upath.extname(file);
|
|
121
|
+
if (extension) {
|
|
122
|
+
// Get rid of the leading . character.
|
|
123
|
+
extensions.add(extension.replace(/^\./, ""));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return [
|
|
127
|
+
...extensions
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @param globDirectory The directory used for the root of globbing.
|
|
132
|
+
* @returns The answers from inquirer.
|
|
133
|
+
*/ async function askQuestion$4(globDirectory) {
|
|
134
|
+
// We need to get a list of extensions corresponding to files in the directory
|
|
135
|
+
// to use when asking the next question. That could potentially take some
|
|
136
|
+
// time, so we show a spinner and explanatory text.
|
|
137
|
+
const spinner = ora({
|
|
138
|
+
text: `Examining files in ${globDirectory}...`,
|
|
139
|
+
stream: process.stdout
|
|
140
|
+
}).start();
|
|
141
|
+
const fileExtensions = await getAllFileExtensions(globDirectory);
|
|
142
|
+
spinner.stop();
|
|
143
|
+
assert(fileExtensions.length > 0, errors["no-file-extensions-found"]);
|
|
144
|
+
return inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
name: name$2,
|
|
147
|
+
message: "Which file types would you like to precache?",
|
|
148
|
+
type: "checkbox",
|
|
149
|
+
choices: fileExtensions,
|
|
150
|
+
default: fileExtensions
|
|
151
|
+
}
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
async function askExtensionsToCache(globDirectory) {
|
|
155
|
+
const answers = await askQuestion$4(globDirectory);
|
|
156
|
+
// The return value is an array of strings with the selected values
|
|
157
|
+
// and there is a default, the casting is safe.
|
|
158
|
+
const extensions = answers[name$2];
|
|
159
|
+
assert(extensions.length > 0, errors["no-file-extensions-selected"]);
|
|
160
|
+
// glob isn't happy with a single option inside of a {} group, so use a
|
|
161
|
+
// pattern without a {} group when there's only one extension.
|
|
162
|
+
const extensionsPattern = extensions.length === 1 ? extensions[0] : `{${extensions.join(",")}}`;
|
|
163
|
+
return [
|
|
164
|
+
`**/*.${extensionsPattern}`
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const ROOT_PROMPT = "Please enter the path to the root of your web app:";
|
|
169
|
+
// The keys used for the questions/answers.
|
|
170
|
+
const questionRootDirectory = "globDirectory";
|
|
171
|
+
const questionManualInput = "manualDirectoryInput";
|
|
172
|
+
/**
|
|
173
|
+
* @returns The subdirectories of the current
|
|
174
|
+
* working directory, with hidden and ignored ones filtered out.
|
|
175
|
+
*/ async function getSubdirectories() {
|
|
176
|
+
return await glob("*/", {
|
|
177
|
+
ignore: constants.ignoredDirectories.map((directory)=>`${directory}/`)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* @returns The answers from inquirer.
|
|
182
|
+
*/ async function askQuestion$3() {
|
|
183
|
+
const subdirectories = await getSubdirectories();
|
|
184
|
+
if (subdirectories.length > 0) {
|
|
185
|
+
const manualEntryChoice = "Manually enter path";
|
|
186
|
+
return inquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
name: questionRootDirectory,
|
|
189
|
+
type: "list",
|
|
190
|
+
message: oneLine`What is the root of your web app (i.e. which directory do
|
|
191
|
+
you deploy)?`,
|
|
192
|
+
choices: subdirectories.concat([
|
|
193
|
+
new inquirer.Separator(),
|
|
194
|
+
manualEntryChoice
|
|
195
|
+
])
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: questionManualInput,
|
|
199
|
+
when: (answers)=>answers.globDirectory === manualEntryChoice,
|
|
200
|
+
message: ROOT_PROMPT
|
|
201
|
+
}
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
return inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
name: questionRootDirectory,
|
|
207
|
+
message: ROOT_PROMPT,
|
|
208
|
+
default: "."
|
|
209
|
+
}
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
async function askRootOfWebApp() {
|
|
213
|
+
const { manualDirectoryInput, globDirectory } = await askQuestion$3();
|
|
214
|
+
try {
|
|
215
|
+
const stat = await fse.stat(manualDirectoryInput || globDirectory);
|
|
216
|
+
assert(stat.isDirectory());
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw new Error(errors["glob-directory-invalid"]);
|
|
219
|
+
}
|
|
220
|
+
return manualDirectoryInput || globDirectory;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const START_URL_QUERY_PARAMS_PROMPT = "Please enter the search parameter(s) that you would like to ignore (separated by comma):";
|
|
224
|
+
// The keys used for the questions/answers.
|
|
225
|
+
const question_ignoreURLParametersMatching = "ignoreURLParametersMatching";
|
|
226
|
+
const question_shouldAskForIgnoreURLParametersMatching = "shouldAskForIgnoreURLParametersMatching";
|
|
227
|
+
/**
|
|
228
|
+
* @returns The answers from inquirer.
|
|
229
|
+
*/ async function askQuestion$2() {
|
|
230
|
+
return inquirer.prompt([
|
|
231
|
+
{
|
|
232
|
+
name: question_shouldAskForIgnoreURLParametersMatching,
|
|
233
|
+
message: oneLine`Does your web app manifest include search parameter(s)
|
|
234
|
+
in the 'start_url', other than 'utm_' or 'fbclid'
|
|
235
|
+
(like '?source=pwa')?`,
|
|
236
|
+
type: "confirm",
|
|
237
|
+
default: false
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: question_ignoreURLParametersMatching,
|
|
241
|
+
when: (answer)=>answer.shouldAskForIgnoreURLParametersMatching,
|
|
242
|
+
message: START_URL_QUERY_PARAMS_PROMPT,
|
|
243
|
+
type: "input"
|
|
244
|
+
}
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
async function askQueryParametersInStartUrl(defaultIgnoredSearchParameters = constants.ignoreURLParametersMatching) {
|
|
248
|
+
const { shouldAskForIgnoreURLParametersMatching, ignoreURLParametersMatching = "" } = await askQuestion$2();
|
|
249
|
+
if (!shouldAskForIgnoreURLParametersMatching) {
|
|
250
|
+
return defaultIgnoredSearchParameters;
|
|
251
|
+
}
|
|
252
|
+
assert(ignoreURLParametersMatching.length > 0, errors["no-search-parameters-supplied"]);
|
|
253
|
+
const ignoreSearchParameters = ignoreURLParametersMatching.trim().split(",").filter(Boolean);
|
|
254
|
+
assert(ignoreSearchParameters.length > 0, errors["no-search-parameters-supplied"]);
|
|
255
|
+
assert(ignoreSearchParameters.every((param)=>!param.match(/^[^\w|-]/g)), errors["invalid-search-parameters-supplied"]);
|
|
256
|
+
return defaultIgnoredSearchParameters.concat(ignoreSearchParameters.map((searchParam)=>new RegExp(`^${searchParam}`)));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// The key used for the question/answer.
|
|
260
|
+
const name$1 = "swDest";
|
|
261
|
+
/**
|
|
262
|
+
* @param defaultDir
|
|
263
|
+
* @returns The answers from inquirer.
|
|
264
|
+
*/ function askQuestion$1(defaultDir) {
|
|
265
|
+
return inquirer.prompt([
|
|
266
|
+
{
|
|
267
|
+
name: name$1,
|
|
268
|
+
message: `Where would you like your service worker file to be saved?`,
|
|
269
|
+
type: "input",
|
|
270
|
+
default: upath.join(defaultDir, "sw.js")
|
|
271
|
+
}
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
async function askSWDest(defaultDir = ".") {
|
|
275
|
+
const answers = await askQuestion$1(defaultDir);
|
|
276
|
+
// When prompt type is input the return type is string
|
|
277
|
+
// casting is safe
|
|
278
|
+
const swDest = answers[name$1].trim();
|
|
279
|
+
assert(swDest, errors["invalid-sw-dest"]);
|
|
280
|
+
return swDest;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The key used for the question/answer.
|
|
284
|
+
const name = "swSrc";
|
|
285
|
+
/**
|
|
286
|
+
* @returns The answers from inquirer.
|
|
287
|
+
*/ function askQuestion() {
|
|
288
|
+
return inquirer.prompt([
|
|
289
|
+
{
|
|
290
|
+
name,
|
|
291
|
+
message: oneLine`Where's your existing service worker file? To be used with
|
|
292
|
+
injectManifest, it should include a call to
|
|
293
|
+
'self.__SW_MANIFEST'`,
|
|
294
|
+
type: "input"
|
|
295
|
+
}
|
|
296
|
+
]);
|
|
297
|
+
}
|
|
298
|
+
async function askSWSrc() {
|
|
299
|
+
const answers = await askQuestion();
|
|
300
|
+
// When prompt type is input the return is string or null
|
|
301
|
+
return answers[name] ? answers[name].trim() : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function askQuestions(options = {}) {
|
|
305
|
+
const isInjectManifest = "injectManifest" in options;
|
|
306
|
+
const globDirectory = await askRootOfWebApp();
|
|
307
|
+
const globPatterns = await askExtensionsToCache(globDirectory);
|
|
308
|
+
const swSrc = isInjectManifest ? await askSWSrc() : undefined;
|
|
309
|
+
const swDest = await askSWDest(globDirectory);
|
|
310
|
+
const configLocation = await askConfigLocation();
|
|
311
|
+
// See https://github.com/GoogleChrome/workbox/issues/2985
|
|
312
|
+
const ignoreURLParametersMatching = isInjectManifest ? undefined : await askQueryParametersInStartUrl();
|
|
313
|
+
const config = {
|
|
314
|
+
globDirectory,
|
|
315
|
+
globPatterns,
|
|
316
|
+
swDest
|
|
317
|
+
};
|
|
318
|
+
if (swSrc) {
|
|
319
|
+
config.swSrc = swSrc;
|
|
320
|
+
}
|
|
321
|
+
if (ignoreURLParametersMatching) {
|
|
322
|
+
config.ignoreURLParametersMatching = ignoreURLParametersMatching;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
config,
|
|
326
|
+
configLocation
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function runWizard(options = {}) {
|
|
331
|
+
const { configLocation, config } = await askQuestions(options);
|
|
332
|
+
// See https://github.com/GoogleChrome/workbox/issues/2796
|
|
333
|
+
const contents = `module.exports = ${stringifyObject(config)};`;
|
|
334
|
+
await fse.writeFile(configLocation, contents);
|
|
335
|
+
logger.log(`To build your service worker, run
|
|
336
|
+
|
|
337
|
+
serwist injectManifest ${configLocation}
|
|
338
|
+
|
|
339
|
+
as part of a build process. See https://goo.gl/fdTQBf for details.`);
|
|
340
|
+
const configDocsURL = "injectManifest" in options ? "https://goo.gl/8bs14N" : "https://goo.gl/gVo87N";
|
|
341
|
+
logger.log(oneLine`You can further customize your service worker by making changes
|
|
342
|
+
to ${configLocation}. See ${configDocsURL} for details.`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Runs the specified build command with the provided configuration.
|
|
347
|
+
*
|
|
348
|
+
* @param options
|
|
349
|
+
*/ async function runBuildCommand({ config, watch }) {
|
|
350
|
+
const { count, filePaths, size, warnings } = await injectManifest(config);
|
|
351
|
+
for (const warning of warnings){
|
|
352
|
+
logger.warn(warning);
|
|
353
|
+
}
|
|
354
|
+
if (filePaths.length === 1) {
|
|
355
|
+
logger.log(`The service worker file was written to ${config.swDest}`);
|
|
356
|
+
} else {
|
|
357
|
+
const message = filePaths.sort().map((filePath)=>` • ${filePath}`).join(`\n`);
|
|
358
|
+
logger.log(`The service worker files were written to:\n${message}`);
|
|
359
|
+
}
|
|
360
|
+
logger.log(`The service worker will precache ${count} URLs, ` + `totaling ${prettyBytes(size)}.`);
|
|
361
|
+
if (watch) {
|
|
362
|
+
logger.log(`\nWatching for changes...`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const app = async (params)=>{
|
|
366
|
+
// This should not be a user-visible error, unless meow() messes something up.
|
|
367
|
+
assert(params && Array.isArray(params.input), errors["missing-input"]);
|
|
368
|
+
// Default to showing the help message if there's no command provided.
|
|
369
|
+
const [command = "help", option] = params.input;
|
|
370
|
+
switch(command){
|
|
371
|
+
case "wizard":
|
|
372
|
+
{
|
|
373
|
+
await runWizard(params.flags);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
case "copyLibraries":
|
|
377
|
+
{
|
|
378
|
+
logger.log("This feature is under maintenance. We are really sorry for the inconvenience.");
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case "injectManifest":
|
|
382
|
+
{
|
|
383
|
+
const configPath = upath.resolve(process.cwd(), option || constants.defaultConfigFile);
|
|
384
|
+
let config;
|
|
385
|
+
try {
|
|
386
|
+
config = readConfig(configPath);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
config = null;
|
|
389
|
+
if (error instanceof Error) {
|
|
390
|
+
logger.error(errors["invalid-common-js-module"]);
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (config === null) {
|
|
395
|
+
throw logger.error(errors["invalid-config-location"]);
|
|
396
|
+
}
|
|
397
|
+
logger.log(`Using configuration from ${configPath}.`);
|
|
398
|
+
// Determine whether we're in --watch mode, or one-off mode.
|
|
399
|
+
if (params?.flags?.watch) {
|
|
400
|
+
const options = {
|
|
401
|
+
ignoreInitial: true
|
|
402
|
+
};
|
|
403
|
+
if (config.globIgnores) {
|
|
404
|
+
options.ignored = config.globIgnores;
|
|
405
|
+
}
|
|
406
|
+
if (config.globDirectory) {
|
|
407
|
+
options.cwd = config.globDirectory;
|
|
408
|
+
}
|
|
409
|
+
if (config.globPatterns) {
|
|
410
|
+
chokidar.watch(config.globPatterns, options).on("all", async ()=>{
|
|
411
|
+
if (config === null) return;
|
|
412
|
+
await runBuildCommand({
|
|
413
|
+
config,
|
|
414
|
+
watch: true
|
|
415
|
+
});
|
|
416
|
+
}).on("ready", async ()=>{
|
|
417
|
+
if (config === null) return;
|
|
418
|
+
await runBuildCommand({
|
|
419
|
+
config,
|
|
420
|
+
watch: true
|
|
421
|
+
});
|
|
422
|
+
}).on("error", (err)=>{
|
|
423
|
+
logger.error(err.toString());
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
await runBuildCommand({
|
|
428
|
+
config,
|
|
429
|
+
watch: false
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case "help":
|
|
435
|
+
{
|
|
436
|
+
params.showHelp();
|
|
437
|
+
}
|
|
438
|
+
default:
|
|
439
|
+
{
|
|
440
|
+
throw new Error(errors["unknown-command"] + ` ` + command);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
Copyright 2018 Google LLC
|
|
447
|
+
|
|
448
|
+
Use of this source code is governed by an MIT-style
|
|
449
|
+
license that can be found in the LICENSE file or at
|
|
450
|
+
https://opensource.org/licenses/MIT.
|
|
451
|
+
*/ // Helper to parse out less relevant info from an Error's stack trace.
|
|
452
|
+
// Removes the initial portion, since that's obtained from error.message.
|
|
453
|
+
// Removes every stack frame earlier than the last instance of moduleName,
|
|
454
|
+
// since that's just frames related to the Node runtime/loader.
|
|
455
|
+
function cleanupStackTrace(error, moduleName) {
|
|
456
|
+
if (!error.stack) {
|
|
457
|
+
return "";
|
|
458
|
+
}
|
|
459
|
+
const frames = error.stack.split(`\n`);
|
|
460
|
+
let startFrame;
|
|
461
|
+
let lastFrame = 0;
|
|
462
|
+
frames.forEach((frame, index)=>{
|
|
463
|
+
if (startFrame === undefined && frame.includes(` at `)) {
|
|
464
|
+
startFrame = index;
|
|
465
|
+
}
|
|
466
|
+
if (frame.includes(`${moduleName}:`)) {
|
|
467
|
+
lastFrame = index;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
return frames.slice(startFrame, lastFrame + 1).join(`\n`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/*
|
|
474
|
+
Copyright 2018 Google LLC
|
|
475
|
+
|
|
476
|
+
Use of this source code is governed by an MIT-style
|
|
477
|
+
license that can be found in the LICENSE file or at
|
|
478
|
+
https://opensource.org/licenses/MIT.
|
|
479
|
+
*/ const helpText = `Usage:
|
|
480
|
+
$ serwist <command> [options]
|
|
481
|
+
|
|
482
|
+
Commands:
|
|
483
|
+
wizard [--injectManifest]
|
|
484
|
+
Runs the configuration wizard, which will generate a
|
|
485
|
+
config file based on answers to questions.
|
|
486
|
+
|
|
487
|
+
injectManifest [<path/to/config.js>] [--watch]
|
|
488
|
+
Takes an existing service worker file and creates a
|
|
489
|
+
copy of it with a precache manifest "injected" into
|
|
490
|
+
it. The precache manifest is generated based on the
|
|
491
|
+
options in the config file (defaults to serwist.config.js).
|
|
492
|
+
If --watch is provided, the CLI will stay running, and will
|
|
493
|
+
rebuild the service worker each time a file in the precache
|
|
494
|
+
manifest changes.
|
|
495
|
+
See https://bit.ly/wb-injectManifest
|
|
496
|
+
|
|
497
|
+
copyLibraries <path/to/parent/dir>
|
|
498
|
+
Makes a local copy of all of the Workbox libraries inside
|
|
499
|
+
a version directory at the location specified. This is intended
|
|
500
|
+
for developers using injectManifest who prefer using local,
|
|
501
|
+
rather than CDN hosted, libraries.
|
|
502
|
+
|
|
503
|
+
Config file:
|
|
504
|
+
In 'injectManifest' mode, the config file should be a
|
|
505
|
+
JavaScript file, in CommonJS module format.
|
|
506
|
+
By default, a config file named serwist.config.js in the current
|
|
507
|
+
directory is assumed, but this can be overridden.
|
|
508
|
+
|
|
509
|
+
Examples:
|
|
510
|
+
$ serwist wizard
|
|
511
|
+
$ serwist wizard --injectManifest
|
|
512
|
+
$ serwist injectManifest configs/serwist-dev-config.js
|
|
513
|
+
$ serwist copyLibraries build/
|
|
514
|
+
`;
|
|
515
|
+
|
|
516
|
+
void (async ()=>{
|
|
517
|
+
const params = meow(helpText, {
|
|
518
|
+
importMeta: import.meta
|
|
519
|
+
});
|
|
520
|
+
updateNotifier({
|
|
521
|
+
pkg: params.pkg
|
|
522
|
+
}).notify();
|
|
523
|
+
try {
|
|
524
|
+
await app(params);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof Error) {
|
|
527
|
+
// Show the full error and stack trace if we're run with --debug.
|
|
528
|
+
if (params.flags.debug) {
|
|
529
|
+
if (error.stack) {
|
|
530
|
+
logger.error(`\n${error.stack}`);
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
logger.error(`\n${error.message}`);
|
|
534
|
+
logger.debug(`${cleanupStackTrace(error, "app.js")}\n`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serwist/cli",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "@serwist/cli is the command line interface for Serwist.",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"keywords": [
|
|
10
|
+
"serwist",
|
|
11
|
+
"serwistjs",
|
|
12
|
+
"service worker",
|
|
13
|
+
"caching",
|
|
14
|
+
"fetch requests",
|
|
15
|
+
"offline",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=16.0.0"
|
|
20
|
+
},
|
|
21
|
+
"author": "Google's Web DevRel Team, Serwist's Team",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": "serwist/serwist",
|
|
24
|
+
"bugs": "https://github.com/serwist/serwist/issues",
|
|
25
|
+
"homepage": "https://github.com/serwist/serwist",
|
|
26
|
+
"bin": {
|
|
27
|
+
"serwist": "dist/bin.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"chalk": "5.3.0",
|
|
31
|
+
"chokidar": "3.5.3",
|
|
32
|
+
"common-tags": "1.8.2",
|
|
33
|
+
"fs-extra": "11.1.1",
|
|
34
|
+
"glob": "10.3.10",
|
|
35
|
+
"inquirer": "9.2.11",
|
|
36
|
+
"meow": "12.1.1",
|
|
37
|
+
"ora": "7.0.1",
|
|
38
|
+
"pretty-bytes": "6.1.1",
|
|
39
|
+
"stringify-object": "5.0.0",
|
|
40
|
+
"upath": "2.0.1",
|
|
41
|
+
"update-notifier": "6.0.2",
|
|
42
|
+
"@serwist/build": "8.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/common-tags": "1.8.3",
|
|
46
|
+
"@types/fs-extra": "11.0.2",
|
|
47
|
+
"@types/inquirer": "9.0.5",
|
|
48
|
+
"@types/stringify-object": "4.0.3",
|
|
49
|
+
"@types/update-notifier": "6.0.5",
|
|
50
|
+
"rollup": "3.28.1",
|
|
51
|
+
"@serwist/constants": "8.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "rimraf dist && cross-env NODE_ENV=production rollup --config rollup.config.js",
|
|
55
|
+
"lint": "eslint src --ext ts,tsx,js,jsx,cjs,mjs",
|
|
56
|
+
"typecheck": "tsc"
|
|
57
|
+
}
|
|
58
|
+
}
|