@serwist/next 8.4.4 → 9.0.0-preview.1

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.
Files changed (68) hide show
  1. package/dist/index.d.ts +5 -4
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +43 -716
  4. package/dist/index.worker.d.ts +5 -0
  5. package/dist/index.worker.d.ts.map +1 -0
  6. package/dist/{index.browser.cjs → index.worker.js} +53 -26
  7. package/dist/internal-types.d.ts +7 -1
  8. package/dist/internal-types.d.ts.map +1 -0
  9. package/dist/sw-entry-worker.d.ts +1 -0
  10. package/dist/sw-entry-worker.d.ts.map +1 -0
  11. package/dist/sw-entry.d.ts +1 -0
  12. package/dist/sw-entry.d.ts.map +1 -0
  13. package/dist/sw-entry.js +5 -5
  14. package/dist/utils/find-first-truthy.d.ts +1 -0
  15. package/dist/utils/find-first-truthy.d.ts.map +1 -0
  16. package/dist/utils/get-content-hash.d.ts +1 -0
  17. package/dist/utils/get-content-hash.d.ts.map +1 -0
  18. package/dist/utils/get-file-hash.d.ts +1 -0
  19. package/dist/utils/get-file-hash.d.ts.map +1 -0
  20. package/dist/utils/get-package-version.d.ts +1 -0
  21. package/dist/utils/get-package-version.d.ts.map +1 -0
  22. package/dist/utils/index.d.ts +1 -0
  23. package/dist/utils/index.d.ts.map +1 -0
  24. package/dist/utils/load-tsconfig.d.ts +1 -0
  25. package/dist/utils/load-tsconfig.d.ts.map +1 -0
  26. package/dist/utils/logger.d.ts +1 -0
  27. package/dist/utils/logger.d.ts.map +1 -0
  28. package/dist/utils.d.ts +1 -0
  29. package/dist/utils.d.ts.map +1 -0
  30. package/dist/worker/defaultCache.d.ts +3 -0
  31. package/dist/worker/defaultCache.d.ts.map +1 -0
  32. package/dist/worker/definePageRuntimeCaching.d.ts +16 -0
  33. package/dist/worker/definePageRuntimeCaching.d.ts.map +1 -0
  34. package/package.json +48 -50
  35. package/src/index.ts +241 -0
  36. package/src/index.worker.ts +5 -0
  37. package/src/internal-types.ts +17 -0
  38. package/src/sw-entry-worker.ts +46 -0
  39. package/src/sw-entry.ts +64 -0
  40. package/src/utils/find-first-truthy.ts +15 -0
  41. package/src/utils/get-content-hash.ts +10 -0
  42. package/src/utils/get-file-hash.ts +4 -0
  43. package/src/utils/get-package-version.ts +16 -0
  44. package/src/utils/load-tsconfig.ts +27 -0
  45. package/src/utils/logger.ts +57 -0
  46. package/src/utils.ts +11 -0
  47. package/src/worker/defaultCache.ts +223 -0
  48. package/src/worker/definePageRuntimeCaching.ts +36 -0
  49. package/dist/index.browser.d.cts +0 -2
  50. package/dist/index.browser.d.ts +0 -2
  51. package/dist/index.browser.js +0 -208
  52. package/dist/index.cjs +0 -928
  53. package/dist/index.d.cts +0 -5
  54. package/dist/internal-types.d.cts +0 -9
  55. package/dist/sw-entry-worker.cjs +0 -35
  56. package/dist/sw-entry-worker.d.cts +0 -7
  57. package/dist/sw-entry.cjs +0 -43
  58. package/dist/sw-entry.d.cts +0 -6
  59. package/dist/types.d.cts +0 -81
  60. package/dist/types.d.ts +0 -81
  61. package/dist/utils/find-first-truthy.d.cts +0 -7
  62. package/dist/utils/get-content-hash.d.cts +0 -3
  63. package/dist/utils/get-file-hash.d.cts +0 -3
  64. package/dist/utils/get-package-version.d.cts +0 -6
  65. package/dist/utils/load-tsconfig.d.cts +0 -2
  66. package/dist/utils/logger.d.cts +0 -5
  67. package/dist/utils.d.cts +0 -4
  68. /package/{dist/utils/index.d.cts → src/utils/index.ts} +0 -0
package/src/index.ts ADDED
@@ -0,0 +1,241 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import type { NextInjectManifestOptions } from "@serwist/build";
5
+ import { validateNextInjectManifestOptions } from "@serwist/build/next";
6
+ import { InjectManifest } from "@serwist/webpack-plugin";
7
+ import { ChildCompilationPlugin, relativeToOutputPath } from "@serwist/webpack-plugin/internal";
8
+ import { CleanWebpackPlugin } from "clean-webpack-plugin";
9
+ import fg from "fast-glob";
10
+ import type { NextConfig } from "next";
11
+ import type { Compilation, Configuration, default as Webpack } from "webpack";
12
+
13
+ import type { ExcludeParams, SerwistNextOptions, SerwistNextOptionsKey } from "./internal-types.js";
14
+ import { getContentHash, getFileHash, loadTSConfig, logger } from "./utils/index.js";
15
+
16
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
17
+
18
+ const withSerwistInit = (pluginOptions: NextInjectManifestOptions): ((nextConfig?: NextConfig) => NextConfig) => {
19
+ return (nextConfig = {}) => ({
20
+ ...nextConfig,
21
+ webpack(config: Configuration, options) {
22
+ const webpack: typeof Webpack = options.webpack;
23
+ const { dev } = options;
24
+
25
+ const basePath = options.config.basePath || "/";
26
+
27
+ const tsConfigJson = loadTSConfig(options.dir, nextConfig?.typescript?.tsconfigPath);
28
+
29
+ const {
30
+ cacheOnNavigation,
31
+ disable,
32
+ scope = basePath,
33
+ swUrl,
34
+ register,
35
+ reloadOnOnline,
36
+ globPublicPatterns,
37
+ ...buildOptions
38
+ } = validateNextInjectManifestOptions(pluginOptions);
39
+
40
+ if (typeof nextConfig.webpack === "function") {
41
+ config = nextConfig.webpack(config, options);
42
+ }
43
+
44
+ if (disable) {
45
+ options.isServer && logger.info("Serwist is disabled.");
46
+ return config;
47
+ }
48
+
49
+ if (!config.plugins) {
50
+ config.plugins = [];
51
+ }
52
+
53
+ logger.event(`Compiling for ${options.isServer ? "server" : "client (static)"}...`);
54
+
55
+ const _sw = path.posix.join(basePath, swUrl);
56
+ const _scope = path.posix.join(scope, "/");
57
+
58
+ config.plugins.push(
59
+ new webpack.DefinePlugin({
60
+ "self.__SERWIST_SW_ENTRY.sw": `'${_sw}'`,
61
+ "self.__SERWIST_SW_ENTRY.scope": `'${_scope}'`,
62
+ "self.__SERWIST_SW_ENTRY.cacheOnNavigation": `${cacheOnNavigation}`,
63
+ "self.__SERWIST_SW_ENTRY.register": `${register}`,
64
+ "self.__SERWIST_SW_ENTRY.reloadOnOnline": `${reloadOnOnline}`,
65
+ } satisfies Record<`${SerwistNextOptionsKey}.${Exclude<keyof SerwistNextOptions, "swEntryWorker">}`, string | undefined>),
66
+ );
67
+
68
+ const swEntryJs = path.join(__dirname, "sw-entry.js");
69
+ const entry = config.entry as () => Promise<Record<string, string[] | string>>;
70
+ config.entry = async () => {
71
+ const entries = await entry();
72
+ if (entries["main.js"] && !entries["main.js"].includes(swEntryJs)) {
73
+ if (Array.isArray(entries["main.js"])) {
74
+ entries["main.js"].unshift(swEntryJs);
75
+ } else if (typeof entries["main.js"] === "string") {
76
+ entries["main.js"] = [swEntryJs, entries["main.js"]];
77
+ }
78
+ }
79
+ if (entries["main-app"] && !entries["main-app"].includes(swEntryJs)) {
80
+ if (Array.isArray(entries["main-app"])) {
81
+ entries["main-app"].unshift(swEntryJs);
82
+ } else if (typeof entries["main-app"] === "string") {
83
+ entries["main-app"] = [swEntryJs, entries["main-app"]];
84
+ }
85
+ }
86
+ return entries;
87
+ };
88
+
89
+ if (!options.isServer) {
90
+ if (!register) {
91
+ logger.info(
92
+ "Service worker won't be automatically registered as per the config, please call the following code in componentDidMount or useEffect:",
93
+ );
94
+
95
+ logger.info(" window.serwist.register()");
96
+
97
+ if (!tsConfigJson?.compilerOptions?.types?.includes("@serwist/next/typings")) {
98
+ logger.info("You may also want to add @serwist/next/typings to compilerOptions.types in your tsconfig.json/jsconfig.json.");
99
+ }
100
+ }
101
+
102
+ const {
103
+ swSrc: userSwSrc,
104
+ swDest: userSwDest,
105
+ additionalPrecacheEntries,
106
+ exclude,
107
+ manifestTransforms = [],
108
+ ...otherBuildOptions
109
+ } = buildOptions;
110
+
111
+ let swSrc = userSwSrc;
112
+ let swDest = userSwDest;
113
+
114
+ // If these two paths are not absolute, they will be resolved from `compilation.options.output.path`,
115
+ // which is `${options.dir}/${nextConfig.destDir}` for Next.js apps, rather than `${options.dir}`
116
+ // as an user would expect.
117
+ if (!path.isAbsolute(swSrc)) {
118
+ swSrc = path.join(options.dir, swSrc);
119
+ }
120
+ if (!path.isAbsolute(swDest)) {
121
+ swDest = path.join(options.dir, swDest);
122
+ }
123
+
124
+ const publicDir = path.resolve(options.dir, "public");
125
+ const destDir = path.dirname(swDest);
126
+
127
+ const shouldBuildSWEntryWorker = cacheOnNavigation;
128
+ let swEntryPublicPath: string | undefined = undefined;
129
+ let swEntryWorkerDest: string | undefined = undefined;
130
+
131
+ if (shouldBuildSWEntryWorker) {
132
+ const swEntryWorkerSrc = path.join(__dirname, "sw-entry-worker.js");
133
+ const swEntryName = `swe-worker-${getContentHash(swEntryWorkerSrc, dev)}.js`;
134
+ swEntryPublicPath = path.posix.join(basePath, swEntryName);
135
+ swEntryWorkerDest = path.join(destDir, swEntryName);
136
+ config.plugins.push(
137
+ new ChildCompilationPlugin({
138
+ src: swEntryWorkerSrc,
139
+ dest: swEntryWorkerDest,
140
+ }),
141
+ );
142
+ }
143
+ config.plugins.push(
144
+ new webpack.DefinePlugin({
145
+ "self.__SERWIST_SW_ENTRY.swEntryWorker": swEntryPublicPath && `'${swEntryPublicPath}'`,
146
+ } satisfies Record<`${SerwistNextOptionsKey}.${Extract<keyof SerwistNextOptions, "swEntryWorker">}`, string | undefined>),
147
+ );
148
+
149
+ logger.info(`Service worker: ${swDest}`);
150
+ logger.info(` URL: ${_sw}`);
151
+ logger.info(` Scope: ${_scope}`);
152
+
153
+ config.plugins.push(
154
+ new CleanWebpackPlugin({
155
+ cleanOnceBeforeBuildPatterns: [path.join(destDir, "swe-worker-*.js"), path.join(destDir, "swe-worker-*.js.map"), swDest],
156
+ }),
157
+ );
158
+
159
+ // Precache files in public folder
160
+ let resolvedManifestEntries = additionalPrecacheEntries;
161
+
162
+ if (!resolvedManifestEntries) {
163
+ const swDestFileName = path.basename(swDest);
164
+ const userPublicGlob = typeof globPublicPatterns === "string" ? [globPublicPatterns] : globPublicPatterns ?? ["**/*"];
165
+ const publicScan = fg.sync(
166
+ [
167
+ ...userPublicGlob,
168
+ // Forcibly include these in case the user outputs these files to `public`.
169
+ "!swe-worker-*.js",
170
+ "!swe-worker-*.js.map",
171
+ `!${swDestFileName.replace(/^\/+/, "")}`,
172
+ `!${swDestFileName.replace(/^\/+/, "")}.map`,
173
+ ],
174
+ {
175
+ cwd: publicDir,
176
+ },
177
+ );
178
+ resolvedManifestEntries = publicScan.map((f) => ({
179
+ url: path.posix.join(basePath, f),
180
+ revision: getFileHash(path.join(publicDir, f)),
181
+ }));
182
+ }
183
+
184
+ const publicPath = config.output?.publicPath;
185
+
186
+ config.plugins.push(
187
+ new InjectManifest({
188
+ swSrc,
189
+ swDest,
190
+ disablePrecacheManifest: dev,
191
+ additionalPrecacheEntries: dev ? [] : resolvedManifestEntries,
192
+ exclude: [
193
+ ...exclude,
194
+ ({ asset, compilation }: ExcludeParams) => {
195
+ // Same as how `@serwist/webpack-plugin` does it. It is always
196
+ // `relativeToOutputPath(compilation, originalSwDest)`.
197
+ const swDestRelativeOutput = relativeToOutputPath(compilation, swDest);
198
+ const swAsset = compilation.getAsset(swDestRelativeOutput);
199
+ return (
200
+ // We don't need the service worker to be cached.
201
+ asset.name === swAsset?.name ||
202
+ asset.name.startsWith("server/") ||
203
+ /^((app-|^)build-manifest\.json|react-loadable-manifest\.json)$/.test(asset.name) ||
204
+ (dev && !asset.name.startsWith("static/runtime/"))
205
+ );
206
+ },
207
+ ],
208
+ manifestTransforms: [
209
+ ...manifestTransforms,
210
+ async (manifestEntries, compilation) => {
211
+ // This path always uses forward slashes, so it is safe to use it in the following string replace.
212
+ const publicDirRelativeOutput = relativeToOutputPath(compilation as Compilation, publicDir);
213
+ // `publicPath` is always `${assetPrefix}/_next/` for Next.js apps.
214
+ const publicFilesPrefix = `${publicPath}${publicDirRelativeOutput}`;
215
+ const manifest = manifestEntries.map((m) => {
216
+ m.url = m.url.replace("/_next//static/image", "/_next/static/image").replace("/_next//static/media", "/_next/static/media");
217
+ // We remove `${publicPath}/${publicDirRelativeOutput}` because `assetPrefix`
218
+ // is not intended for files that are in the public directory and we also want
219
+ // to remove `/_next/${publicDirRelativeOutput}` from the URL, since that is not how
220
+ // we resolve files in the public directory.
221
+ if (m.url.startsWith(publicFilesPrefix)) {
222
+ m.url = path.posix.join(basePath, m.url.replace(publicFilesPrefix, ""));
223
+ }
224
+ m.url = m.url.replace(/\[/g, "%5B").replace(/\]/g, "%5D");
225
+ return m;
226
+ });
227
+ return { manifest, warnings: [] };
228
+ },
229
+ ],
230
+ ...otherBuildOptions,
231
+ }),
232
+ );
233
+ }
234
+
235
+ return config;
236
+ },
237
+ });
238
+ };
239
+
240
+ export default withSerwistInit;
241
+ export type { NextInjectManifestOptions as PluginOptions };
@@ -0,0 +1,5 @@
1
+ import { defaultCache } from "./worker/defaultCache.js";
2
+ import { type DefinePageRuntimeCachingOptions, type PageRuntimeCaching, definePageRuntimeCaching } from "./worker/definePageRuntimeCaching.js";
3
+
4
+ export { defaultCache, definePageRuntimeCaching };
5
+ export type { DefinePageRuntimeCachingOptions, PageRuntimeCaching };
@@ -0,0 +1,17 @@
1
+ import type { Asset, Compilation } from "webpack";
2
+
3
+ export type SerwistNextOptionsKey = "self.__SERWIST_SW_ENTRY";
4
+
5
+ export interface SerwistNextOptions {
6
+ sw: string;
7
+ scope: string;
8
+ cacheOnNavigation: boolean;
9
+ register: boolean;
10
+ reloadOnOnline: boolean;
11
+ swEntryWorker: string | undefined;
12
+ }
13
+
14
+ export interface ExcludeParams {
15
+ asset: Asset;
16
+ compilation: Compilation;
17
+ }
@@ -0,0 +1,46 @@
1
+ declare const self: WorkerGlobalScope & typeof globalThis;
2
+
3
+ export type MessageType =
4
+ | {
5
+ type: "__FRONTEND_NAV_CACHE__";
6
+ url: URL | string;
7
+ }
8
+ | {
9
+ type: "__START_URL_CACHE__";
10
+ url: URL | string;
11
+ };
12
+
13
+ self.onmessage = async (ev: MessageEvent<MessageType>) => {
14
+ switch (ev.data.type) {
15
+ case "__START_URL_CACHE__": {
16
+ const url = ev.data.url;
17
+ const response = await fetch(url);
18
+ if (!response.redirected) {
19
+ const startUrlCache = await caches.open("start-url");
20
+ return startUrlCache.put(url, response);
21
+ }
22
+ return Promise.resolve();
23
+ }
24
+ case "__FRONTEND_NAV_CACHE__": {
25
+ const url = ev.data.url;
26
+ const pagesCache = await caches.open("pages");
27
+
28
+ const isPageCached = !!(await pagesCache.match(url, {
29
+ ignoreSearch: true,
30
+ }));
31
+ if (isPageCached) {
32
+ return;
33
+ }
34
+
35
+ const page = await fetch(url);
36
+ if (!page.ok) {
37
+ return;
38
+ }
39
+ pagesCache.put(url, page.clone());
40
+
41
+ return Promise.resolve();
42
+ }
43
+ default:
44
+ return Promise.resolve();
45
+ }
46
+ };
@@ -0,0 +1,64 @@
1
+ import { Serwist } from "@serwist/window";
2
+
3
+ import type { SerwistNextOptions } from "./internal-types.js";
4
+ import type { MessageType } from "./sw-entry-worker.js";
5
+
6
+ declare global {
7
+ interface Window {
8
+ serwist: Serwist;
9
+ }
10
+ }
11
+ declare const self: Window &
12
+ typeof globalThis & {
13
+ // Do not dereference this, use its attributes directly or assign them to other variables.
14
+ // You should do the latter if you use an attribute multiple times.
15
+ __SERWIST_SW_ENTRY: SerwistNextOptions;
16
+ };
17
+
18
+ if (typeof window !== "undefined" && "serviceWorker" in navigator && typeof caches !== "undefined") {
19
+ let swEntryWorker: Worker | undefined;
20
+
21
+ if (self.__SERWIST_SW_ENTRY.swEntryWorker) {
22
+ swEntryWorker = new Worker(self.__SERWIST_SW_ENTRY.swEntryWorker);
23
+ }
24
+
25
+ window.serwist = new Serwist(window.location.origin + self.__SERWIST_SW_ENTRY.sw, {
26
+ scope: self.__SERWIST_SW_ENTRY.scope,
27
+ });
28
+
29
+ if (self.__SERWIST_SW_ENTRY.register) {
30
+ window.serwist.register();
31
+ }
32
+
33
+ if (self.__SERWIST_SW_ENTRY.cacheOnNavigation) {
34
+ const cacheOnNavigation = async (url?: string | URL | null | undefined) => {
35
+ if (!window.navigator.onLine || !url) {
36
+ return;
37
+ }
38
+ swEntryWorker?.postMessage({
39
+ type: "__FRONTEND_NAV_CACHE__",
40
+ url,
41
+ } satisfies MessageType);
42
+ };
43
+
44
+ const pushState = history.pushState;
45
+ history.pushState = (...args) => {
46
+ pushState.apply(history, args);
47
+ cacheOnNavigation(args[2]);
48
+ };
49
+
50
+ const replaceState = history.replaceState;
51
+ history.replaceState = (...args) => {
52
+ replaceState.apply(history, args);
53
+ cacheOnNavigation(args[2]);
54
+ };
55
+
56
+ window.addEventListener("online", () => {
57
+ cacheOnNavigation(window.location.pathname);
58
+ });
59
+ }
60
+
61
+ if (self.__SERWIST_SW_ENTRY.reloadOnOnline) {
62
+ window.addEventListener("online", () => location.reload());
63
+ }
64
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Find the first truthy value in an array.
3
+ * @param arr
4
+ * @param fn
5
+ * @returns
6
+ */
7
+ export const findFirstTruthy = <T, U>(arr: T[], fn: (elm: T) => U) => {
8
+ for (const i of arr) {
9
+ const resolved = fn(i);
10
+ if (resolved) {
11
+ return resolved;
12
+ }
13
+ }
14
+ return undefined;
15
+ };
@@ -0,0 +1,10 @@
1
+ import type fs from "node:fs";
2
+
3
+ import { getFileHash } from "./get-file-hash.js";
4
+
5
+ export const getContentHash = (file: fs.PathOrFileDescriptor, isDev: boolean) => {
6
+ if (isDev) {
7
+ return "development";
8
+ }
9
+ return getFileHash(file).slice(0, 16);
10
+ };
@@ -0,0 +1,4 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+
4
+ export const getFileHash = (file: fs.PathOrFileDescriptor) => crypto.createHash("md5").update(fs.readFileSync(file)).digest("hex");
@@ -0,0 +1,16 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const require = createRequire(import.meta.url);
4
+
5
+ /**
6
+ * Get a package's version
7
+ * @param packageName
8
+ * @returns
9
+ */
10
+ export const getPackageVersion = (packageName: string): string | undefined => {
11
+ try {
12
+ return require(`${packageName}/package.json`).version;
13
+ } catch {
14
+ return undefined;
15
+ }
16
+ };
@@ -0,0 +1,27 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import type { TsConfigJson as TSConfigJSON } from "type-fest";
5
+
6
+ import { findFirstTruthy } from "./find-first-truthy.js";
7
+
8
+ export const loadTSConfig = (baseDir: string, relativeTSConfigPath: string | undefined): TSConfigJSON | undefined => {
9
+ try {
10
+ // Find tsconfig.json file
11
+ const tsConfigPath = findFirstTruthy([relativeTSConfigPath ?? "tsconfig.json", "jsconfig.json"], (filePath) => {
12
+ const resolvedPath = path.join(baseDir, filePath);
13
+ return fs.existsSync(resolvedPath) ? resolvedPath : undefined;
14
+ });
15
+
16
+ if (!tsConfigPath) {
17
+ return undefined;
18
+ }
19
+
20
+ // Read tsconfig.json file
21
+ const tsConfigFile = JSON.parse(fs.readFileSync(tsConfigPath, "utf-8"));
22
+
23
+ return tsConfigFile;
24
+ } catch {
25
+ return undefined;
26
+ }
27
+ };
@@ -0,0 +1,57 @@
1
+ import chalk from "chalk";
2
+
3
+ const LOGGING_METHOD = ["wait", "error", "warn", "info", "event"] as const;
4
+
5
+ type LoggingMethods = (typeof LOGGING_METHOD)[number];
6
+
7
+ const mapLoggingMethodToConsole: Record<LoggingMethods, "log" | "error" | "warn" | "log"> = {
8
+ wait: "log",
9
+ error: "error",
10
+ warn: "warn",
11
+ info: "log",
12
+ event: "log",
13
+ };
14
+
15
+ const prefixes = {
16
+ wait: `${chalk.white(chalk.bold("○"))} (serwist)`,
17
+ error: `${chalk.red(chalk.bold("X"))} (serwist)`,
18
+ warn: `${chalk.yellow(chalk.bold("⚠"))} (serwist)`,
19
+ info: `${chalk.white(chalk.bold("○"))} (serwist)`,
20
+ event: `${chalk.green(chalk.bold("✓"))} (serwist)`,
21
+ };
22
+
23
+ const prefixedLog = (prefixType: LoggingMethods, ...message: any[]) => {
24
+ const consoleMethod = mapLoggingMethodToConsole[prefixType];
25
+ const prefix = prefixes[prefixType];
26
+
27
+ if ((message[0] === "" || message[0] === undefined) && message.length === 1) {
28
+ message.shift();
29
+ }
30
+
31
+ // If there's no message, don't print the prefix but a new line
32
+ if (message.length === 0) {
33
+ console[consoleMethod]("");
34
+ } else {
35
+ console[consoleMethod](` ${prefix}`, ...message);
36
+ }
37
+ };
38
+
39
+ export const wait = (...message: any[]) => {
40
+ prefixedLog("wait", ...message);
41
+ };
42
+
43
+ export const error = (...message: any[]) => {
44
+ prefixedLog("error", ...message);
45
+ };
46
+
47
+ export const warn = (...message: any[]) => {
48
+ prefixedLog("warn", ...message);
49
+ };
50
+
51
+ export const info = (...message: any[]) => {
52
+ prefixedLog("info", ...message);
53
+ };
54
+
55
+ export const event = (...message: any[]) => {
56
+ prefixedLog("event", ...message);
57
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,11 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+
4
+ export const getFileHash = (file: fs.PathOrFileDescriptor) => crypto.createHash("md5").update(fs.readFileSync(file)).digest("hex");
5
+
6
+ export const getContentHash = (file: fs.PathOrFileDescriptor, isDev: boolean) => {
7
+ if (isDev) {
8
+ return "development";
9
+ }
10
+ return getFileHash(file).slice(0, 16);
11
+ };