@serwist/utils 10.0.0-preview.1 → 10.0.0-preview.11

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.
@@ -0,0 +1,6 @@
1
+ import { nonNullable } from "./nonNullable.js";
2
+ import { parallel } from "./parallel.js";
3
+ export { nonNullable, parallel };
4
+ export { isAbsolute, resolveBasePath, slash, toUnix } from "./paths.js";
5
+ export type * from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACxE,mBAAmB,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -26,8 +26,18 @@ const parallel = async (limit, array, func)=>{
26
26
  return results;
27
27
  };
28
28
 
29
+ const slash = (str)=>{
30
+ return str.replace(/\\/g, "/");
31
+ };
29
32
  const toUnix = (p)=>{
30
- return p.replace(/\\/g, "/").replace(/(?<!^)\/+/g, "/");
33
+ return slash(p).replace(/(?<!^)\/+/g, "/");
34
+ };
35
+ const resolveBasePath = (base)=>{
36
+ if (isAbsolute(base)) return base;
37
+ return !base.startsWith("/") && !base.startsWith("./") ? `/${base}` : base;
38
+ };
39
+ const isAbsolute = (url)=>{
40
+ return url.match(/^(?:[a-z]+:)?\/\//i);
31
41
  };
32
42
 
33
- export { nonNullable, parallel, toUnix };
43
+ export { isAbsolute, nonNullable, parallel, resolveBasePath, slash, toUnix };
@@ -0,0 +1,7 @@
1
+ export { errors } from "./node/errors.js";
2
+ export { getCompositeDetails, getFileHash, getFileSize } from "./node/fs.js";
3
+ export { getStringDetails, getStringHash } from "./node/hash.js";
4
+ export type { Logger, LoggerOptions, LogLevel, LogOptions, LogType } from "./node/log.js";
5
+ export { createLogger, LogLevels } from "./node/log.js";
6
+ export { resolveEntry } from "./node/paths.js";
7
+ //# sourceMappingURL=index.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.node.d.ts","sourceRoot":"","sources":["../src/index.node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,202 @@
1
+ import { oneLine } from 'common-tags';
2
+ import crypto from 'node:crypto';
3
+ import fs, { readFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { red, yellow, cyan } from 'kolorist';
6
+ import path from 'node:path';
7
+
8
+ const errors = {
9
+ "unable-to-get-rootdir": "Unable to get the root directory of your web app.",
10
+ "no-extension": oneLine`Unable to detect a usable extension for a file in your web
11
+ app directory.`,
12
+ "invalid-file-manifest-name": oneLine`The File Manifest Name must have at least one
13
+ character.`,
14
+ "unable-to-get-file-manifest-name": "Unable to get a file manifest name.",
15
+ "invalid-sw-dest": `The 'swDest' value must be a valid path.`,
16
+ "unable-to-get-sw-name": "Unable to get a service worker file name.",
17
+ "unable-to-get-save-config": oneLine`An error occurred when asking to save details
18
+ in a config file.`,
19
+ "unable-to-get-file-hash": oneLine`An error occurred when attempting to create a
20
+ file hash.`,
21
+ "unable-to-get-file-size": oneLine`An error occurred when attempting to get a file
22
+ size.`,
23
+ "unable-to-glob-files": "An error occurred when globbing for files.",
24
+ "unable-to-make-manifest-directory": oneLine`Unable to make output directory for
25
+ file manifest.`,
26
+ "read-manifest-template-failure": "Unable to read template for file manifest",
27
+ "populating-manifest-tmpl-failed": oneLine`An error occurred when populating the
28
+ file manifest template.`,
29
+ "manifest-file-write-failure": "Unable to write the file manifest.",
30
+ "unable-to-make-sw-directory": oneLine`Unable to make the directories to output
31
+ the service worker path.`,
32
+ "sw-write-failure": "Unable to write the service worker file.",
33
+ "sw-write-failure-directory": oneLine`Unable to write the service worker file;
34
+ 'swDest' should be a full path to the file, not a path to a directory.`,
35
+ "unable-to-copy-serwist-libraries": oneLine`One or more of the Serwist libraries
36
+ could not be copied over to the destination directory: `,
37
+ "invalid-glob-directory": oneLine`The supplied globDirectory must be a path as a
38
+ string.`,
39
+ "invalid-dont-cache-bust": oneLine`The supplied 'dontCacheBustURLsMatching'
40
+ parameter must be a RegExp.`,
41
+ "invalid-exclude-files": "The excluded files should be an array of strings.",
42
+ "invalid-get-manifest-entries-input": oneLine`The input to
43
+ 'getFileManifestEntries()' must be an object.`,
44
+ "invalid-manifest-path": oneLine`The supplied manifest path is not a string with
45
+ at least one character.`,
46
+ "invalid-manifest-entries": oneLine`The manifest entries must be an array of
47
+ strings or JavaScript objects containing a url parameter.`,
48
+ "invalid-manifest-format": oneLine`The value of the 'format' option passed to
49
+ generateFileManifest() must be either 'iife' (the default) or 'es'.`,
50
+ "invalid-static-file-globs": oneLine`The 'globPatterns' value must be an array
51
+ of strings.`,
52
+ "invalid-templated-urls": oneLine`The 'templatedURLs' value should be an object
53
+ that maps URLs to either a string, or to an array of glob patterns.`,
54
+ "templated-url-matches-glob": oneLine`One of the 'templatedURLs' URLs is already
55
+ being tracked via 'globPatterns': `,
56
+ "invalid-glob-ignores": oneLine`The 'globIgnores' parameter must be an array of
57
+ glob pattern strings.`,
58
+ "manifest-entry-bad-url": oneLine`The generated manifest contains an entry without
59
+ a URL string. This is likely an error with @serwist/build.`,
60
+ "modify-url-prefix-bad-prefixes": oneLine`The 'modifyURLPrefix' parameter must be
61
+ an object with string key value pairs.`,
62
+ "invalid-inject-manifest-arg": oneLine`The input to 'injectManifest()' must be an
63
+ object.`,
64
+ "injection-point-not-found": oneLine`Unable to find a place to inject the manifest.
65
+ Please ensure that your service worker file contains the following: `,
66
+ "multiple-injection-points": oneLine`Please ensure that your 'swSrc' file contains
67
+ only one match for the following: `,
68
+ "useless-glob-pattern": oneLine`One of the glob patterns doesn't match any files.
69
+ Please remove or fix the following: `,
70
+ "bad-template-urls-asset": oneLine`There was an issue using one of the provided
71
+ 'templatedURLs'.`,
72
+ "invalid-generate-file-manifest-arg": oneLine`The input to generateFileManifest()
73
+ must be an Object.`,
74
+ "invalid-sw-src": `The 'swSrc' file can't be read.`,
75
+ "same-src-and-dest": oneLine`Unable to find a place to inject the manifest. This is
76
+ likely because swSrc and swDest are configured to the same file.
77
+ Please ensure that your swSrc file contains the following:`,
78
+ "no-module-name": oneLine`You must provide a moduleName parameter when calling
79
+ getModuleURL().`,
80
+ "bad-manifest-transforms-return-value": oneLine`The return value from a
81
+ manifestTransform should be an object with 'manifest' and optionally
82
+ 'warnings' properties.`,
83
+ "string-entry-warning": oneLine`Some items were passed to additionalPrecacheEntries
84
+ without revisioning info. This is generally NOT safe. Learn more at
85
+ https://bit.ly/wb-precache.`,
86
+ "cant-find-sourcemap": oneLine`The swSrc file refers to a sourcemap that can't be
87
+ opened:`,
88
+ "manifest-transforms": oneLine`When using manifestTransforms, you must provide
89
+ an array of functions.`
90
+ };
91
+
92
+ const getStringHash = (input)=>{
93
+ const md5 = crypto.createHash("md5");
94
+ md5.update(input);
95
+ return md5.digest("hex");
96
+ };
97
+ const getStringDetails = (url, str)=>({
98
+ file: url,
99
+ hash: getStringHash(str),
100
+ size: str.length
101
+ });
102
+
103
+ const getFileHash = (file)=>{
104
+ try {
105
+ return getStringHash(fs.readFileSync(file));
106
+ } catch (err) {
107
+ throw new Error(`${errors["unable-to-get-file-hash"]} '${err instanceof Error && err.message ? err.message : ""}'`);
108
+ }
109
+ };
110
+ const getFileSize = (file)=>{
111
+ try {
112
+ const stat = fs.statSync(file);
113
+ if (!stat.isFile()) {
114
+ return null;
115
+ }
116
+ return stat.size;
117
+ } catch (err) {
118
+ throw new Error(`${errors["unable-to-get-file-size"]} '${err instanceof Error && err.message ? err.message : ""}'`);
119
+ }
120
+ };
121
+ const getCompositeDetails = (compositeURL, dependencyDetails)=>{
122
+ let totalSize = 0;
123
+ let compositeHash = "";
124
+ for (const fileDetails of dependencyDetails){
125
+ totalSize += fileDetails.size;
126
+ compositeHash += fileDetails.hash === null ? "" : fileDetails.hash;
127
+ }
128
+ const md5 = crypto.createHash("md5");
129
+ md5.update(compositeHash);
130
+ const hashOfHashes = md5.digest("hex");
131
+ return {
132
+ file: compositeURL,
133
+ hash: hashOfHashes,
134
+ size: totalSize
135
+ };
136
+ };
137
+
138
+ const LogLevels = {
139
+ silent: 0,
140
+ error: 1,
141
+ warn: 2,
142
+ info: 3
143
+ };
144
+ const require = createRequire(import.meta.url);
145
+ const createLogger = (level = "info", options = {})=>{
146
+ const { prefix: _prefix = "serwist", showVersion = true, allowClearScreen = true } = options;
147
+ const packageJson = showVersion ? JSON.parse(readFileSync(require.resolve("@serwist/utils/package.json"), "utf-8")) : null;
148
+ const prefix = `${_prefix}${showVersion ? ` v${packageJson.version}` : ""}`;
149
+ const thresh = LogLevels[level];
150
+ const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI;
151
+ const clear = canClearScreen ? console.clear : ()=>{};
152
+ const output = (type, msg, options = {})=>{
153
+ if (thresh >= LogLevels[type]) {
154
+ const method = type === "info" ? "log" : type;
155
+ if (options.clear) clear();
156
+ let tag = type === "error" ? red(prefix) : type === "warn" ? yellow(prefix) : cyan(prefix);
157
+ console[method](`${options.skipLine ? "\n" : ""}${tag} ${msg}`);
158
+ }
159
+ };
160
+ const warnedMessages = new Set();
161
+ return {
162
+ info (msg, opts) {
163
+ output("info", msg, opts);
164
+ },
165
+ warn (msg, opts) {
166
+ output("warn", msg, opts);
167
+ },
168
+ warnOnce (msg, opts) {
169
+ if (warnedMessages.has(msg)) return;
170
+ output("warn", msg, opts);
171
+ warnedMessages.add(msg);
172
+ },
173
+ error (msg, opts) {
174
+ output("error", msg, opts);
175
+ },
176
+ clearScreen (type) {
177
+ if (thresh >= LogLevels[type]) {
178
+ clear();
179
+ }
180
+ }
181
+ };
182
+ };
183
+
184
+ const resolveEntry = (entry)=>{
185
+ if (fs.existsSync(entry)) {
186
+ const stats = fs.statSync(entry);
187
+ if (stats.isDirectory()) {
188
+ return resolveEntry(path.join(entry, "index"));
189
+ }
190
+ return entry;
191
+ }
192
+ const dir = path.dirname(entry);
193
+ if (fs.existsSync(dir)) {
194
+ const base = path.basename(entry);
195
+ const files = fs.readdirSync(dir);
196
+ const found = files.find((file)=>file.replace(/\.[^.]+$/, "") === base);
197
+ if (found) return path.join(dir, found);
198
+ }
199
+ return null;
200
+ };
201
+
202
+ export { LogLevels, createLogger, errors, getCompositeDetails, getFileHash, getFileSize, getStringDetails, getStringHash, resolveEntry };
@@ -0,0 +1,47 @@
1
+ export declare const errors: {
2
+ "unable-to-get-rootdir": string;
3
+ "no-extension": string;
4
+ "invalid-file-manifest-name": string;
5
+ "unable-to-get-file-manifest-name": string;
6
+ "invalid-sw-dest": string;
7
+ "unable-to-get-sw-name": string;
8
+ "unable-to-get-save-config": string;
9
+ "unable-to-get-file-hash": string;
10
+ "unable-to-get-file-size": string;
11
+ "unable-to-glob-files": string;
12
+ "unable-to-make-manifest-directory": string;
13
+ "read-manifest-template-failure": string;
14
+ "populating-manifest-tmpl-failed": string;
15
+ "manifest-file-write-failure": string;
16
+ "unable-to-make-sw-directory": string;
17
+ "sw-write-failure": string;
18
+ "sw-write-failure-directory": string;
19
+ "unable-to-copy-serwist-libraries": string;
20
+ "invalid-glob-directory": string;
21
+ "invalid-dont-cache-bust": string;
22
+ "invalid-exclude-files": string;
23
+ "invalid-get-manifest-entries-input": string;
24
+ "invalid-manifest-path": string;
25
+ "invalid-manifest-entries": string;
26
+ "invalid-manifest-format": string;
27
+ "invalid-static-file-globs": string;
28
+ "invalid-templated-urls": string;
29
+ "templated-url-matches-glob": string;
30
+ "invalid-glob-ignores": string;
31
+ "manifest-entry-bad-url": string;
32
+ "modify-url-prefix-bad-prefixes": string;
33
+ "invalid-inject-manifest-arg": string;
34
+ "injection-point-not-found": string;
35
+ "multiple-injection-points": string;
36
+ "useless-glob-pattern": string;
37
+ "bad-template-urls-asset": string;
38
+ "invalid-generate-file-manifest-arg": string;
39
+ "invalid-sw-src": string;
40
+ "same-src-and-dest": string;
41
+ "no-module-name": string;
42
+ "bad-manifest-transforms-return-value": string;
43
+ "string-entry-warning": string;
44
+ "cant-find-sourcemap": string;
45
+ "manifest-transforms": string;
46
+ };
47
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/node/errors.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFlB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import fs from "node:fs";
2
+ import type { FileDetails } from "../types.js";
3
+ export declare const getFileHash: (file: fs.PathOrFileDescriptor) => string;
4
+ export declare const getFileSize: (file: string) => number | null;
5
+ export declare const getCompositeDetails: (compositeURL: string, dependencyDetails: FileDetails[]) => FileDetails;
6
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/node/fs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,eAAO,MAAM,WAAW,GAAI,MAAM,EAAE,CAAC,oBAAoB,WAMxD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAUnD,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,cAAc,MAAM,EAAE,mBAAmB,WAAW,EAAE,KAAG,WAkB5F,CAAC"}
@@ -0,0 +1,5 @@
1
+ import crypto from "node:crypto";
2
+ import type { FileDetails } from "../types.js";
3
+ export declare const getStringHash: (input: crypto.BinaryLike) => string;
4
+ export declare const getStringDetails: (url: string, str: string) => FileDetails;
5
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/node/hash.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,CAAC,UAAU,KAAG,MAIxD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,WAI1D,CAAC"}
@@ -0,0 +1,21 @@
1
+ export type LogType = "error" | "warn" | "info";
2
+ export type LogLevel = LogType | "silent";
3
+ export interface LogOptions {
4
+ clear?: boolean;
5
+ skipLine?: boolean;
6
+ }
7
+ export declare const LogLevels: Record<LogLevel, number>;
8
+ export interface LoggerOptions {
9
+ prefix?: string;
10
+ showVersion?: boolean;
11
+ allowClearScreen?: boolean;
12
+ }
13
+ export interface Logger {
14
+ info(msg: string, options?: LogOptions): void;
15
+ warn(msg: string, options?: LogOptions): void;
16
+ warnOnce(msg: string, options?: LogOptions): void;
17
+ error(msg: string, options?: LogOptions): void;
18
+ clearScreen(type: LogType): void;
19
+ }
20
+ export declare const createLogger: (level?: LogLevel, options?: LoggerOptions) => Logger;
21
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/node/log.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAChD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC1C,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAK9C,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAClD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAID,eAAO,MAAM,YAAY,GAAI,QAAO,QAAiB,EAAE,UAAS,aAAkB,KAAG,MAqCpF,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolves a file path without extension. Also handles `/index` if the path
3
+ * actually points to a directory.
4
+ * @param ctx
5
+ * @param api
6
+ * @returns
7
+ */
8
+ export declare const resolveEntry: (entry: string) => string | null;
9
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/node/paths.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAqBrD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const nonNullable: <T>(value: T) => value is NonNullable<T>;
2
+ //# sourceMappingURL=nonNullable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nonNullable.d.ts","sourceRoot":"","sources":["../src/nonNullable.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,KAAK,IAAI,WAAW,CAAC,CAAC,CAA0C,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Executes many async functions in parallel. Returns the
3
+ * results from all functions as an array. Does not handle
4
+ * any error.
5
+ */
6
+ export declare const parallel: <T, K>(limit: number, array: readonly T[], func: (item: T) => Promise<K>) => Promise<K[]>;
7
+ //# sourceMappingURL=parallel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../src/parallel.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAU,CAAC,EAAE,CAAC,EAAE,OAAO,MAAM,EAAE,OAAO,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,EAAE,CA4BnH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const slash: (str: string) => string;
2
+ export declare const toUnix: (p: string) => string;
3
+ export declare const resolveBasePath: (base: string) => string;
4
+ export declare const isAbsolute: (url: string) => RegExpMatchArray | null;
5
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,WAEhC,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,WAE/B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,WAG3C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,4BAErC,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Makes certain fields in a object type required
3
+ *
4
+ * @example
5
+ * interface A {
6
+ * a?: string;
7
+ * b?: string;
8
+ * c?: string;
9
+ * }
10
+ * type B = RequiredFields<A, "b" | "c">;
11
+ * const b: B = {
12
+ * b: "hehe",
13
+ * c: "hehe",
14
+ * }; //valid
15
+ * const b: B = { a: "hehe" }; //invalid
16
+ * const c: B = { a: "hehe", b: "hehe" }; //invalid
17
+ */
18
+ export type Require<T, U extends keyof T> = T & Required<Pick<T, U>>;
19
+ /**
20
+ * Makes certain fields in a object type optional
21
+ *
22
+ * @example
23
+ * interface A {
24
+ * a: string;
25
+ * b: string;
26
+ * c: string;
27
+ * }
28
+ * type B = Optional<A, "b" | "c">;
29
+ * const b: B = { a: "hehe" }; //valid
30
+ * const b: B = {}; //invalid
31
+ */
32
+ export type Optional<T, U extends keyof T> = Omit<T, U> & Partial<Pick<T, U>>;
33
+ /**
34
+ * Makes an object type's hover overlay more readable
35
+ *
36
+ * @example
37
+ *
38
+ * interface A {
39
+ * b: string;
40
+ * c: boolean;
41
+ * }
42
+ *
43
+ * interface B {
44
+ * c: number;
45
+ * }
46
+ *
47
+ * type D = A | B; // Displayed as is written
48
+ *
49
+ * type C = Prettify<A | B>; // { b: string; c: boolean; } | { c: number; }
50
+ */
51
+ export type Prettify<T> = {
52
+ [K in keyof T]: T[K];
53
+ } & {};
54
+ export interface FileDetails {
55
+ file: string;
56
+ hash: string | null;
57
+ size: number;
58
+ }
59
+ export type MaybePromise<T> = T | Promise<T>;
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KACvB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACrB,GAAG,EAAE,CAAC;AAEP,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@serwist/utils",
3
- "version": "10.0.0-preview.1",
3
+ "version": "10.0.0-preview.11",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "description": "This module contains internal utilities used by Serwist packages.",
6
7
  "files": [
7
- "src"
8
+ "src",
9
+ "dist"
8
10
  ],
9
11
  "keywords": [
10
12
  "serwist",
@@ -19,22 +21,39 @@
19
21
  "homepage": "https://serwist.pages.dev",
20
22
  "main": "./dist/index.js",
21
23
  "types": "./dist/index.d.ts",
24
+ "typesVersions": {
25
+ "node": {
26
+ "*": [
27
+ "./dist/index.node.d.ts"
28
+ ]
29
+ }
30
+ },
22
31
  "exports": {
23
32
  ".": {
24
33
  "types": "./dist/index.d.ts",
25
34
  "default": "./dist/index.js"
26
35
  },
36
+ "./node": {
37
+ "types": "./dist/index.node.d.ts",
38
+ "default": "./dist/index.node.js"
39
+ },
27
40
  "./package.json": "./package.json"
28
41
  },
42
+ "dependencies": {
43
+ "common-tags": "1.8.2",
44
+ "kolorist": "1.8.0"
45
+ },
29
46
  "devDependencies": {
30
- "rollup": "4.34.8",
31
- "typescript": "5.7.3",
32
- "@serwist/configs": "10.0.0-preview.1"
47
+ "@types/common-tags": "1.8.4",
48
+ "rollup": "4.45.1",
49
+ "typescript": "5.8.3",
50
+ "@serwist/configs": "10.0.0-preview.11"
33
51
  },
34
52
  "scripts": {
35
53
  "build": "rimraf dist && NODE_ENV=production rollup --config rollup.config.js",
36
54
  "dev": "rollup --config rollup.config.js --watch",
37
55
  "lint": "biome lint ./src",
56
+ "qcheck": "biome check ./src",
38
57
  "typecheck": "tsc"
39
58
  }
40
59
  }
@@ -0,0 +1,6 @@
1
+ export { errors } from "./node/errors.js";
2
+ export { getCompositeDetails, getFileHash, getFileSize } from "./node/fs.js";
3
+ export { getStringDetails, getStringHash } from "./node/hash.js";
4
+ export type { Logger, LoggerOptions, LogLevel, LogOptions, LogType } from "./node/log.js";
5
+ export { createLogger, LogLevels } from "./node/log.js";
6
+ export { resolveEntry } from "./node/paths.js";
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { nonNullable } from "./nonNullable.js";
2
2
  import { parallel } from "./parallel.js";
3
- import { toUnix } from "./toUnix.js";
4
-
5
- export { nonNullable, parallel, toUnix };
6
3
 
4
+ export { nonNullable, parallel };
5
+ export { isAbsolute, resolveBasePath, slash, toUnix } from "./paths.js";
7
6
  export type * from "./types.js";
@@ -0,0 +1,92 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+ import { oneLine as ol } from "common-tags";
9
+
10
+ export const errors = {
11
+ "unable-to-get-rootdir": "Unable to get the root directory of your web app.",
12
+ "no-extension": ol`Unable to detect a usable extension for a file in your web
13
+ app directory.`,
14
+ "invalid-file-manifest-name": ol`The File Manifest Name must have at least one
15
+ character.`,
16
+ "unable-to-get-file-manifest-name": "Unable to get a file manifest name.",
17
+ "invalid-sw-dest": `The 'swDest' value must be a valid path.`,
18
+ "unable-to-get-sw-name": "Unable to get a service worker file name.",
19
+ "unable-to-get-save-config": ol`An error occurred when asking to save details
20
+ in a config file.`,
21
+ "unable-to-get-file-hash": ol`An error occurred when attempting to create a
22
+ file hash.`,
23
+ "unable-to-get-file-size": ol`An error occurred when attempting to get a file
24
+ size.`,
25
+ "unable-to-glob-files": "An error occurred when globbing for files.",
26
+ "unable-to-make-manifest-directory": ol`Unable to make output directory for
27
+ file manifest.`,
28
+ "read-manifest-template-failure": "Unable to read template for file manifest",
29
+ "populating-manifest-tmpl-failed": ol`An error occurred when populating the
30
+ file manifest template.`,
31
+ "manifest-file-write-failure": "Unable to write the file manifest.",
32
+ "unable-to-make-sw-directory": ol`Unable to make the directories to output
33
+ the service worker path.`,
34
+ "sw-write-failure": "Unable to write the service worker file.",
35
+ "sw-write-failure-directory": ol`Unable to write the service worker file;
36
+ 'swDest' should be a full path to the file, not a path to a directory.`,
37
+ "unable-to-copy-serwist-libraries": ol`One or more of the Serwist libraries
38
+ could not be copied over to the destination directory: `,
39
+ "invalid-glob-directory": ol`The supplied globDirectory must be a path as a
40
+ string.`,
41
+ "invalid-dont-cache-bust": ol`The supplied 'dontCacheBustURLsMatching'
42
+ parameter must be a RegExp.`,
43
+ "invalid-exclude-files": "The excluded files should be an array of strings.",
44
+ "invalid-get-manifest-entries-input": ol`The input to
45
+ 'getFileManifestEntries()' must be an object.`,
46
+ "invalid-manifest-path": ol`The supplied manifest path is not a string with
47
+ at least one character.`,
48
+ "invalid-manifest-entries": ol`The manifest entries must be an array of
49
+ strings or JavaScript objects containing a url parameter.`,
50
+ "invalid-manifest-format": ol`The value of the 'format' option passed to
51
+ generateFileManifest() must be either 'iife' (the default) or 'es'.`,
52
+ "invalid-static-file-globs": ol`The 'globPatterns' value must be an array
53
+ of strings.`,
54
+ "invalid-templated-urls": ol`The 'templatedURLs' value should be an object
55
+ that maps URLs to either a string, or to an array of glob patterns.`,
56
+ "templated-url-matches-glob": ol`One of the 'templatedURLs' URLs is already
57
+ being tracked via 'globPatterns': `,
58
+ "invalid-glob-ignores": ol`The 'globIgnores' parameter must be an array of
59
+ glob pattern strings.`,
60
+ "manifest-entry-bad-url": ol`The generated manifest contains an entry without
61
+ a URL string. This is likely an error with @serwist/build.`,
62
+ "modify-url-prefix-bad-prefixes": ol`The 'modifyURLPrefix' parameter must be
63
+ an object with string key value pairs.`,
64
+ "invalid-inject-manifest-arg": ol`The input to 'injectManifest()' must be an
65
+ object.`,
66
+ "injection-point-not-found": ol`Unable to find a place to inject the manifest.
67
+ Please ensure that your service worker file contains the following: `,
68
+ "multiple-injection-points": ol`Please ensure that your 'swSrc' file contains
69
+ only one match for the following: `,
70
+ "useless-glob-pattern": ol`One of the glob patterns doesn't match any files.
71
+ Please remove or fix the following: `,
72
+ "bad-template-urls-asset": ol`There was an issue using one of the provided
73
+ 'templatedURLs'.`,
74
+ "invalid-generate-file-manifest-arg": ol`The input to generateFileManifest()
75
+ must be an Object.`,
76
+ "invalid-sw-src": `The 'swSrc' file can't be read.`,
77
+ "same-src-and-dest": ol`Unable to find a place to inject the manifest. This is
78
+ likely because swSrc and swDest are configured to the same file.
79
+ Please ensure that your swSrc file contains the following:`,
80
+ "no-module-name": ol`You must provide a moduleName parameter when calling
81
+ getModuleURL().`,
82
+ "bad-manifest-transforms-return-value": ol`The return value from a
83
+ manifestTransform should be an object with 'manifest' and optionally
84
+ 'warnings' properties.`,
85
+ "string-entry-warning": ol`Some items were passed to additionalPrecacheEntries
86
+ without revisioning info. This is generally NOT safe. Learn more at
87
+ https://bit.ly/wb-precache.`,
88
+ "cant-find-sourcemap": ol`The swSrc file refers to a sourcemap that can't be
89
+ opened:`,
90
+ "manifest-transforms": ol`When using manifestTransforms, you must provide
91
+ an array of functions.`,
92
+ };
package/src/node/fs.ts ADDED
@@ -0,0 +1,45 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import type { FileDetails } from "../types.js";
4
+ import { errors } from "./errors.js";
5
+ import { getStringHash } from "./hash.js";
6
+
7
+ export const getFileHash = (file: fs.PathOrFileDescriptor) => {
8
+ try {
9
+ return getStringHash(fs.readFileSync(file));
10
+ } catch (err) {
11
+ throw new Error(`${errors["unable-to-get-file-hash"]} '${err instanceof Error && err.message ? err.message : ""}'`);
12
+ }
13
+ };
14
+
15
+ export const getFileSize = (file: string): number | null => {
16
+ try {
17
+ const stat = fs.statSync(file);
18
+ if (!stat.isFile()) {
19
+ return null;
20
+ }
21
+ return stat.size;
22
+ } catch (err) {
23
+ throw new Error(`${errors["unable-to-get-file-size"]} '${err instanceof Error && err.message ? err.message : ""}'`);
24
+ }
25
+ };
26
+
27
+ export const getCompositeDetails = (compositeURL: string, dependencyDetails: FileDetails[]): FileDetails => {
28
+ let totalSize = 0;
29
+ let compositeHash = "";
30
+
31
+ for (const fileDetails of dependencyDetails) {
32
+ totalSize += fileDetails.size;
33
+ compositeHash += fileDetails.hash === null ? "" : fileDetails.hash;
34
+ }
35
+
36
+ const md5 = crypto.createHash("md5");
37
+ md5.update(compositeHash);
38
+ const hashOfHashes = md5.digest("hex");
39
+
40
+ return {
41
+ file: compositeURL,
42
+ hash: hashOfHashes,
43
+ size: totalSize,
44
+ };
45
+ };
@@ -0,0 +1,14 @@
1
+ import crypto from "node:crypto";
2
+ import type { FileDetails } from "../types.js";
3
+
4
+ export const getStringHash = (input: crypto.BinaryLike): string => {
5
+ const md5 = crypto.createHash("md5");
6
+ md5.update(input);
7
+ return md5.digest("hex");
8
+ };
9
+
10
+ export const getStringDetails = (url: string, str: string): FileDetails => ({
11
+ file: url,
12
+ hash: getStringHash(str),
13
+ size: str.length,
14
+ });
@@ -0,0 +1,71 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { cyan, red, yellow } from "kolorist";
4
+
5
+ export type LogType = "error" | "warn" | "info";
6
+ export type LogLevel = LogType | "silent";
7
+ export interface LogOptions {
8
+ clear?: boolean;
9
+ skipLine?: boolean;
10
+ }
11
+ export const LogLevels: Record<LogLevel, number> = {
12
+ silent: 0,
13
+ error: 1,
14
+ warn: 2,
15
+ info: 3,
16
+ };
17
+
18
+ export interface LoggerOptions {
19
+ prefix?: string;
20
+ showVersion?: boolean;
21
+ allowClearScreen?: boolean;
22
+ }
23
+
24
+ export interface Logger {
25
+ info(msg: string, options?: LogOptions): void;
26
+ warn(msg: string, options?: LogOptions): void;
27
+ warnOnce(msg: string, options?: LogOptions): void;
28
+ error(msg: string, options?: LogOptions): void;
29
+ clearScreen(type: LogType): void;
30
+ }
31
+
32
+ const require = createRequire(import.meta.url);
33
+
34
+ export const createLogger = (level: LogLevel = "info", options: LoggerOptions = {}): Logger => {
35
+ const { prefix: _prefix = "serwist", showVersion = true, allowClearScreen = true } = options;
36
+ const packageJson = showVersion ? JSON.parse(readFileSync(require.resolve("@serwist/utils/package.json"), "utf-8")) : null;
37
+ const prefix = `${_prefix}${showVersion ? ` v${packageJson.version}` : ""}`;
38
+ const thresh = LogLevels[level];
39
+ const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI;
40
+ const clear = canClearScreen ? console.clear : () => {};
41
+ const output = (type: LogType, msg: string, options: LogOptions = {}) => {
42
+ if (thresh >= LogLevels[type]) {
43
+ const method = type === "info" ? "log" : type;
44
+ if (options.clear) clear();
45
+ let tag = type === "error" ? red(prefix) : type === "warn" ? yellow(prefix) : cyan(prefix);
46
+ console[method](`${options.skipLine ? "\n" : ""}${tag} ${msg}`);
47
+ }
48
+ };
49
+ const warnedMessages = new Set<string>();
50
+ return {
51
+ info(msg, opts) {
52
+ output("info", msg, opts);
53
+ },
54
+ warn(msg, opts) {
55
+ output("warn", msg, opts);
56
+ },
57
+ warnOnce(msg, opts) {
58
+ if (warnedMessages.has(msg)) return;
59
+ output("warn", msg, opts);
60
+ warnedMessages.add(msg);
61
+ },
62
+ error(msg, opts) {
63
+ output("error", msg, opts);
64
+ },
65
+ clearScreen(type) {
66
+ if (thresh >= LogLevels[type]) {
67
+ clear();
68
+ }
69
+ },
70
+ } satisfies Logger;
71
+ };
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ // Source: https://github.com/sveltejs/kit/blob/6419d3eaa7bf1b0a756b28f06a73f71fe042de0a/packages/kit/src/utils/filesystem.js
5
+ // License: MIT
6
+ /**
7
+ * Resolves a file path without extension. Also handles `/index` if the path
8
+ * actually points to a directory.
9
+ * @param ctx
10
+ * @param api
11
+ * @returns
12
+ */
13
+ export const resolveEntry = (entry: string): string | null => {
14
+ if (fs.existsSync(entry)) {
15
+ const stats = fs.statSync(entry);
16
+ if (stats.isDirectory()) {
17
+ return resolveEntry(path.join(entry, "index"));
18
+ }
19
+
20
+ return entry;
21
+ }
22
+ const dir = path.dirname(entry);
23
+
24
+ if (fs.existsSync(dir)) {
25
+ const base = path.basename(entry);
26
+ const files = fs.readdirSync(dir);
27
+
28
+ const found = files.find((file) => file.replace(/\.[^.]+$/, "") === base);
29
+
30
+ if (found) return path.join(dir, found);
31
+ }
32
+
33
+ return null;
34
+ };
package/src/paths.ts ADDED
@@ -0,0 +1,16 @@
1
+ export const slash = (str: string) => {
2
+ return str.replace(/\\/g, "/");
3
+ };
4
+
5
+ export const toUnix = (p: string) => {
6
+ return slash(p).replace(/(?<!^)\/+/g, "/");
7
+ };
8
+
9
+ export const resolveBasePath = (base: string) => {
10
+ if (isAbsolute(base)) return base;
11
+ return !base.startsWith("/") && !base.startsWith("./") ? `/${base}` : base;
12
+ };
13
+
14
+ export const isAbsolute = (url: string) => {
15
+ return url.match(/^(?:[a-z]+:)?\/\//i);
16
+ };
package/src/types.ts CHANGED
@@ -53,3 +53,11 @@ export type Optional<T, U extends keyof T> = Omit<T, U> & Partial<Pick<T, U>>;
53
53
  export type Prettify<T> = {
54
54
  [K in keyof T]: T[K];
55
55
  } & {};
56
+
57
+ export interface FileDetails {
58
+ file: string;
59
+ hash: string | null;
60
+ size: number;
61
+ }
62
+
63
+ export type MaybePromise<T> = T | Promise<T>;
package/src/toUnix.ts DELETED
@@ -1,3 +0,0 @@
1
- export const toUnix = (p: string) => {
2
- return p.replace(/\\/g, "/").replace(/(?<!^)\/+/g, "/");
3
- };