@studiocms/cfetch 0.2.1 → 0.3.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.
@@ -1,19 +1,39 @@
1
- import { createResolver } from "./utils/integration.js";
1
+ import { createResolver } from "./utils/integration.mjs";
2
+
3
+ //#region src/stub.ts
4
+ /**
5
+ * This file serves as a stub for TypeScript type definitions related to the cFetch integration.
6
+ * It defines the structure of virtual modules and the types that will be injected into the consuming project.
7
+ * The actual implementation of these types is provided in the corresponding .mjs files, but this stub allows
8
+ * for proper type checking and IntelliSense in development environments.
9
+ *
10
+ * The stub includes module declarations for 'virtual:cfetch/config' and 'c:fetch', defining the expected types
11
+ * and exports that will be available when using the cFetch integration in an Astro project.
12
+ *
13
+ * @remarks
14
+ * - The 'virtual:cfetch/config' module provides access to the cache configuration, which can be customized by users.
15
+ * - The 'c:fetch' module exports various types and functions related to the cached fetch functionality, including error types, parsers, and the main cFetch function.
16
+ *
17
+ * @module
18
+ */
2
19
  const { resolve } = createResolver(import.meta.url);
20
+ /**
21
+ * Stub content for TypeScript type definitions related to the cFetch integration.
22
+ */
3
23
  const stub = `
4
24
  declare module 'virtual:cfetch/config' {
5
- type CacheConfig = import("${resolve("./types.js")}").CacheConfigLive;
25
+ type CacheConfig = import("${resolve("./types.mjs")}").CacheConfigLive;
6
26
  const defaultConfig: CacheConfig;
7
27
  export default defaultConfig;
8
28
  }
9
29
 
10
30
  declare module 'c:fetch' {
11
- export type CacheConfig = import("${resolve("./types.js")}").CacheConfig;
12
- export type CachedResponse<T> = import("${resolve("./wrappers.js")}").CachedResponse<T>;
13
- export type CFetchConfig = import("${resolve("./wrappers.js")}").CFetchConfig;
31
+ export type CacheConfig = import("${resolve("./types.mjs")}").CacheConfig;
32
+ export type CachedResponse<T> = import("${resolve("./wrappers.mjs")}").CachedResponse<T>;
33
+ export type CFetchConfig = import("${resolve("./wrappers.mjs")}").CFetchConfig;
34
+ export type InvalidateCacheOptions = import("${resolve("./wrappers.mjs")}").InvalidateCacheOptions;
14
35
 
15
- export const Duration: typeof import("${resolve("./wrappers.js")}").Duration;
16
-
36
+ export const Duration: typeof import("${resolve("./wrappers.mjs")}").Duration;
17
37
  declare const FetchError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
18
38
  readonly _tag: "FetchError";
19
39
  } & Readonly<A>;
@@ -34,7 +54,7 @@ declare module 'c:fetch' {
34
54
  * @param _ - The Response object (ignored)
35
55
  * @returns A Promise that resolves to undefined
36
56
  */
37
- export const noOpParser: typeof import("${resolve("./wrappers.js")}").noOpParser;
57
+ export const noOpParser: typeof import("${resolve("./wrappers.mjs")}").noOpParser;
38
58
 
39
59
  /**
40
60
  * Fetches data from a URL with caching capabilities using Effect.
@@ -63,7 +83,26 @@ declare module 'c:fetch' {
63
83
  * - Cache hits are logged to console for debugging
64
84
  * - The effect is provided with CacheLive layer automatically
65
85
  */
66
- export const cFetchEffect: typeof import("${resolve("./wrappers.js")}").cFetchEffect;
86
+ export const cFetchEffect: typeof import("${resolve("./wrappers.mjs")}").cFetchEffect;
87
+
88
+ /**
89
+ * Invalidates cache entries based on specified keys or tags.
90
+ *
91
+ * @param opts - An object containing optional keys and tags for cache invalidation
92
+ * @param opts.keys - An array of specific cache keys to invalidate
93
+ * @param opts.tags - An array of tags; all cache entries associated with these tags will be invalidated
94
+ *
95
+ * @returns An Effect that performs the cache invalidation when executed
96
+ *
97
+ * @example
98
+ * \`\`\`typescript
99
+ * yield* invalidateCacheEffect({
100
+ * tags: ['user'],
101
+ * keys: ['user:123', 'user:456']
102
+ * });
103
+ * \`\`\`
104
+ */
105
+ export const invalidateCacheEffect: typeof import("${resolve("./wrappers.mjs")}").invalidateCacheEffect;
67
106
 
68
107
  /**
69
108
  * Creates an Effect that fetches JSON data from a URL with caching support.
@@ -77,7 +116,7 @@ declare module 'c:fetch' {
77
116
  * @param cacheConfig.key - Custom cache key to use instead of the default URL-based key
78
117
  * @returns An Effect that yields a CachedResponse containing the parsed JSON data, or fails with a FetchError
79
118
  */
80
- export const cFetchEffectJson: typeof import("${resolve("./wrappers.js")}").cFetchEffectJson;
119
+ export const cFetchEffectJson: typeof import("${resolve("./wrappers.mjs")}").cFetchEffectJson;
81
120
 
82
121
 
83
122
  /**
@@ -93,7 +132,7 @@ declare module 'c:fetch' {
93
132
  * @returns An Effect that resolves to a CachedResponse containing the response text,
94
133
  * or fails with a FetchError if the request fails.
95
134
  */
96
- export const cFetchEffectText: typeof import("${resolve("./wrappers.js")}").cFetchEffectText;
135
+ export const cFetchEffectText: typeof import("${resolve("./wrappers.mjs")}").cFetchEffectText;
97
136
 
98
137
  /**
99
138
  * Fetches a resource and returns it as a Blob with caching support.
@@ -107,7 +146,7 @@ declare module 'c:fetch' {
107
146
  *
108
147
  * @returns An Effect that resolves to a CachedResponse containing a Blob, or fails with a FetchError
109
148
  */
110
- export const cFetchEffectBlob: typeof import("${resolve("./wrappers.js")}").cFetchEffectBlob;
149
+ export const cFetchEffectBlob: typeof import("${resolve("./wrappers.mjs")}").cFetchEffectBlob;
111
150
 
112
151
  /**
113
152
  * Executes a cached fetch request with configurable caching behavior.
@@ -122,7 +161,7 @@ declare module 'c:fetch' {
122
161
  * @param cacheConfig.key - Custom cache key; if not provided, a key will be generated from the URL and options
123
162
  * @returns A Promise that resolves to a CachedResponse containing the parsed data
124
163
  */
125
- export const cFetch: typeof import("${resolve("./wrappers.js")}").cFetch;
164
+ export const cFetch: typeof import("${resolve("./wrappers.mjs")}").cFetch;
126
165
 
127
166
  /**
128
167
  * Fetches and parses JSON data from the specified URL with caching support.
@@ -136,7 +175,7 @@ declare module 'c:fetch' {
136
175
  * @param cacheConfig.key - Custom cache key to use instead of the default
137
176
  * @returns A Promise that resolves to a CachedResponse containing the parsed JSON data of type T
138
177
  */
139
- export const cFetchJson: typeof import("${resolve("./wrappers.js")}").cFetchJson;
178
+ export const cFetchJson: typeof import("${resolve("./wrappers.mjs")}").cFetchJson;
140
179
 
141
180
  /**
142
181
  * Fetches a URL and returns the response as text with caching support.
@@ -149,7 +188,7 @@ declare module 'c:fetch' {
149
188
  * @param cacheConfig.key - Custom cache key (defaults to URL if not provided)
150
189
  * @returns A promise that resolves to a CachedResponse containing the response text
151
190
  */
152
- export const cFetchText: typeof import("${resolve("./wrappers.js")}").cFetchText;
191
+ export const cFetchText: typeof import("${resolve("./wrappers.mjs")}").cFetchText;
153
192
 
154
193
  /**
155
194
  * Fetches a Blob resource from the specified URL with optional caching configuration.
@@ -162,11 +201,30 @@ declare module 'c:fetch' {
162
201
  * @param cacheConfig.key - Custom cache key for storing the response
163
202
  * @returns A Promise that resolves to a CachedResponse containing a Blob
164
203
  */
165
- export const cFetchBlob: typeof import("${resolve("./wrappers.js")}").cFetchBlob;
204
+ export const cFetchBlob: typeof import("${resolve("./wrappers.mjs")}").cFetchBlob;
205
+
206
+ /**
207
+ * Invalidates cache entries based on specified keys or tags.
208
+ *
209
+ * @param opts - An object containing optional keys and tags for cache invalidation
210
+ * @param opts.keys - An array of specific cache keys to invalidate
211
+ * @param opts.tags - An array of tags; all cache entries associated with these tags will be invalidated
212
+ *
213
+ * @returns A Promise that resolves when the cache invalidation is complete
214
+ *
215
+ * @example
216
+ * \`\`\`typescript
217
+ * await invalidateCache({
218
+ * tags: ['user'],
219
+ * keys: ['user:123', 'user:456']
220
+ * });
221
+ * \`\`\`
222
+ */
223
+ export const invalidateCache: typeof import("${resolve("./wrappers.mjs")}").invalidateCache;
166
224
  }
167
225
 
168
226
  `;
169
227
  var stub_default = stub;
170
- export {
171
- stub_default as default
172
- };
228
+
229
+ //#endregion
230
+ export { stub_default as default };
@@ -1,4 +1,6 @@
1
- import type { Duration } from 'effect';
1
+ import { Duration } from "effect";
2
+
3
+ //#region src/types.d.ts
2
4
  /**
3
5
  * Configuration options for caching behavior.
4
6
  *
@@ -12,8 +14,8 @@ import type { Duration } from 'effect';
12
14
  * };
13
15
  * ```
14
16
  */
15
- export type CacheConfig = {
16
- lifetime: Duration.DurationInput;
17
+ type CacheConfig = {
18
+ lifetime: Duration.DurationInput;
17
19
  };
18
20
  /**
19
21
  * Configuration options for cache lifetime management.
@@ -29,6 +31,8 @@ export type CacheConfig = {
29
31
  * };
30
32
  * ```
31
33
  */
32
- export type CacheConfigLive = {
33
- lifetime: number;
34
+ type CacheConfigLive = {
35
+ lifetime: number;
34
36
  };
37
+ //#endregion
38
+ export { CacheConfig, CacheConfigLive };
package/dist/types.mjs ADDED
@@ -0,0 +1 @@
1
+ export { };
@@ -1,23 +1,21 @@
1
- /**
2
- * This module contains Astro Integration Utilities
3
- * @module
4
- */
5
- import type path from 'node:path';
6
- import type { HookParameters } from 'astro';
7
- import type { PluginOption } from 'vite';
8
- export declare const isAbsolute: typeof path.isAbsolute;
9
- export declare const dirname: typeof path.dirname;
1
+ import { HookParameters } from "astro";
2
+ import path from "node:path";
3
+ import { PluginOption } from "vite";
4
+
5
+ //#region src/utils/integration.d.ts
6
+ declare const isAbsolute: typeof path.isAbsolute;
7
+ declare const dirname: typeof path.dirname;
10
8
  /**
11
9
  * Resolves a string path, resolving '.' and '.' segments and allowing paths above the root.
12
10
  *
13
- * @param path - The path to normalise.
11
+ * @param path - The path to normalize.
14
12
  * @param allowAboveRoot - Whether to allow the resulting path to be above the root directory.
15
- * @returns the normalised path string.
13
+ * @returns the normalized path string.
16
14
  */
17
- export declare function normalizeString(path: string, allowAboveRoot: boolean): string;
18
- export declare function normalizeWindowsPath(input?: string): string;
19
- export declare const resolve: typeof path.resolve;
20
- export type Hooks = Required<Astro.IntegrationHooks>;
15
+ declare function normalizeString(path: string, allowAboveRoot: boolean): string;
16
+ declare function normalizeWindowsPath(input?: string): string;
17
+ declare const resolve: typeof path.resolve;
18
+ type Hooks = Required<Astro.IntegrationHooks>;
21
19
  /**
22
20
  * Allows resolving paths relatively to the integration folder easily. Call it like this:
23
21
  *
@@ -33,15 +31,15 @@ export type Hooks = Required<Astro.IntegrationHooks>;
33
31
  *
34
32
  * This way, you do not have to add your plugin to your package.json `exports`.
35
33
  */
36
- export declare const createResolver: (_base: string) => {
37
- resolve: (...path: Array<string>) => string;
34
+ declare const createResolver: (_base: string) => {
35
+ resolve: (...path: Array<string>) => string;
38
36
  };
39
37
  /**
40
38
  * A utility to be used on an Astro hook.
41
39
  *
42
40
  * @see defineUtility
43
41
  */
44
- export type HookUtility<THook extends keyof Hooks, TArgs extends Array<any>, TReturn> = (params: HookParameters<THook>, ...args: TArgs) => TReturn;
42
+ type HookUtility<THook extends keyof Hooks, TArgs extends Array<any>, TReturn> = (params: HookParameters<THook>, ...args: TArgs) => TReturn;
45
43
  /**
46
44
  * Allows defining a type-safe function requiring all the params of a given hook.
47
45
  * It uses currying to make TypeScript happy.
@@ -57,7 +55,7 @@ export type HookUtility<THook extends keyof Hooks, TArgs extends Array<any>, TRe
57
55
  * });
58
56
  * ```
59
57
  */
60
- export declare const defineUtility: <THook extends keyof Hooks>(_hook: THook) => <TArgs extends Array<any>, T>(fn: HookUtility<THook, TArgs, T>) => HookUtility<THook, TArgs, T>;
58
+ declare const defineUtility: <THook extends keyof Hooks>(_hook: THook) => <TArgs extends Array<any>, T>(fn: HookUtility<THook, TArgs, T>) => HookUtility<THook, TArgs, T>;
61
59
  /**
62
60
  * Checks for the existence of a Vite plugin inside the Astro config.
63
61
  *
@@ -74,8 +72,8 @@ export declare const defineUtility: <THook extends keyof Hooks>(_hook: THook) =>
74
72
  * })
75
73
  * ```
76
74
  */
77
- export declare const hasVitePlugin: HookUtility<"astro:config:setup", [{
78
- plugin: string | PluginOption;
75
+ declare const hasVitePlugin: HookUtility<"astro:config:setup", [{
76
+ plugin: string | PluginOption;
79
77
  }], boolean>;
80
78
  /**
81
79
  * Adds a [vite plugin](https://vitejs.dev/guide/using-plugins) to the
@@ -96,14 +94,14 @@ export declare const hasVitePlugin: HookUtility<"astro:config:setup", [{
96
94
  * })
97
95
  * ```
98
96
  */
99
- export declare const addVitePlugin: HookUtility<"astro:config:setup", [{
100
- plugin: PluginOption;
101
- warnDuplicated?: boolean;
97
+ declare const addVitePlugin: HookUtility<"astro:config:setup", [{
98
+ plugin: PluginOption;
99
+ warnDuplicated?: boolean;
102
100
  }], void>;
103
101
  type VirtualImport = {
104
- id: string;
105
- content: string;
106
- context?: 'server' | 'client' | undefined;
102
+ id: string;
103
+ content: string;
104
+ context?: 'server' | 'client' | undefined;
107
105
  };
108
106
  type Imports = Record<string, string> | Array<VirtualImport>;
109
107
  /**
@@ -139,9 +137,10 @@ type Imports = Record<string, string> | Array<VirtualImport>;
139
137
  * console.log(config.foo) // "bar"
140
138
  * ```
141
139
  */
142
- export declare const addVirtualImports: HookUtility<"astro:config:setup", [{
143
- name: string;
144
- imports: Imports;
145
- __enableCorePowerDoNotUseOrYouWillBeFired?: boolean;
140
+ declare const addVirtualImports: HookUtility<"astro:config:setup", [{
141
+ name: string;
142
+ imports: Imports;
143
+ __enableCorePowerDoNotUseOrYouWillBeFired?: boolean;
146
144
  }], void>;
147
- export {};
145
+ //#endregion
146
+ export { HookUtility, Hooks, addVirtualImports, addVitePlugin, createResolver, defineUtility, dirname, hasVitePlugin, isAbsolute, normalizeString, normalizeWindowsPath, resolve };
@@ -0,0 +1,276 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ //#region src/utils/integration.ts
4
+ const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
5
+ const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
6
+ const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
7
+ const isAbsolute = (p) => _IS_ABSOLUTE_RE.test(p);
8
+ const dirname = (p) => {
9
+ const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
10
+ if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) segments[0] += "/";
11
+ return segments.join("/") || (isAbsolute(p) ? "/" : ".");
12
+ };
13
+ function cwd() {
14
+ if (typeof process !== "undefined" && typeof process.cwd === "function") return process.cwd().replace(/\\/g, "/");
15
+ return "/";
16
+ }
17
+ /**
18
+ * Resolves a string path, resolving '.' and '.' segments and allowing paths above the root.
19
+ *
20
+ * @param path - The path to normalize.
21
+ * @param allowAboveRoot - Whether to allow the resulting path to be above the root directory.
22
+ * @returns the normalized path string.
23
+ */
24
+ function normalizeString(path, allowAboveRoot) {
25
+ let res = "";
26
+ let lastSegmentLength = 0;
27
+ let lastSlash = -1;
28
+ let dots = 0;
29
+ let char = null;
30
+ for (let index = 0; index <= path.length; ++index) {
31
+ if (index < path.length) char = path[index];
32
+ else if (char === "/") break;
33
+ else char = "/";
34
+ if (char === "/") {
35
+ if (lastSlash === index - 1 || dots === 1) {} else if (dots === 2) {
36
+ if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
37
+ if (res.length > 2) {
38
+ const lastSlashIndex = res.lastIndexOf("/");
39
+ if (lastSlashIndex === -1) {
40
+ res = "";
41
+ lastSegmentLength = 0;
42
+ } else {
43
+ res = res.slice(0, lastSlashIndex);
44
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
45
+ }
46
+ lastSlash = index;
47
+ dots = 0;
48
+ continue;
49
+ }
50
+ if (res.length > 0) {
51
+ res = "";
52
+ lastSegmentLength = 0;
53
+ lastSlash = index;
54
+ dots = 0;
55
+ continue;
56
+ }
57
+ }
58
+ if (allowAboveRoot) {
59
+ res += res.length > 0 ? "/.." : "..";
60
+ lastSegmentLength = 2;
61
+ }
62
+ } else {
63
+ if (res.length > 0) res += `/${path.slice(lastSlash + 1, index)}`;
64
+ else res = path.slice(lastSlash + 1, index);
65
+ lastSegmentLength = index - lastSlash - 1;
66
+ }
67
+ lastSlash = index;
68
+ dots = 0;
69
+ } else if (char === "." && dots !== -1) ++dots;
70
+ else dots = -1;
71
+ }
72
+ return res;
73
+ }
74
+ function normalizeWindowsPath(input = "") {
75
+ if (!input) return input;
76
+ return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
77
+ }
78
+ const resolve = (...arguments_) => {
79
+ arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
80
+ let resolvedPath = "";
81
+ let resolvedAbsolute = false;
82
+ for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
83
+ const path = index >= 0 ? arguments_[index] : cwd();
84
+ if (!path || path.length === 0) continue;
85
+ resolvedPath = `${path}/${resolvedPath}`;
86
+ resolvedAbsolute = isAbsolute(path);
87
+ }
88
+ resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
89
+ if (resolvedAbsolute && !isAbsolute(resolvedPath)) return `/${resolvedPath}`;
90
+ return resolvedPath.length > 0 ? resolvedPath : ".";
91
+ };
92
+ /**
93
+ * Allows resolving paths relatively to the integration folder easily. Call it like this:
94
+ *
95
+ * @param {string} _base - The location you want to create relative references from. `import.meta.url` is usually what you'll want.
96
+ *
97
+ * @see https://astro-integration-kit.netlify.app/core/create-resolver/
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const { resolve } = createResolver(import.meta.url);
102
+ * const pluginPath = resolve("./plugin.ts");
103
+ * ```
104
+ *
105
+ * This way, you do not have to add your plugin to your package.json `exports`.
106
+ */
107
+ const createResolver = (_base) => {
108
+ let base = _base;
109
+ if (base.startsWith("file://")) base = dirname(fileURLToPath(base));
110
+ return { resolve: (...path) => resolve(base, ...path) };
111
+ };
112
+ /**
113
+ * Allows defining a type-safe function requiring all the params of a given hook.
114
+ * It uses currying to make TypeScript happy.
115
+ *
116
+ * @param {string} _hook
117
+ *
118
+ * @see https://astro-integration-kit.netlify.app/core/define-utility/
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const test = defineUtility("astro:config:setup")((params, foo: boolean) => {
123
+ * return "bar";
124
+ * });
125
+ * ```
126
+ */
127
+ const defineUtility = (_hook) => (fn) => fn;
128
+ function getPluginNames(plugins) {
129
+ const names = [];
130
+ if (plugins) for (const plugin of plugins) {
131
+ if (!plugin) continue;
132
+ if (Array.isArray(plugin)) {
133
+ names.push(...getPluginNames(plugin));
134
+ continue;
135
+ }
136
+ if (plugin instanceof Promise) continue;
137
+ names.push(plugin.name);
138
+ }
139
+ return names;
140
+ }
141
+ /**
142
+ * Checks for the existence of a Vite plugin inside the Astro config.
143
+ *
144
+ * @param {import("astro").HookParameters<"astro:config:setup">} params
145
+ * @param {Params} options
146
+ * @param {string | import("vite").PluginOption} options.plugin
147
+ *
148
+ * @see https://astro-integration-kit.netlify.app/utilities/has-vite-plugin/
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * hasVitePlugin(params, {
153
+ * plugin: "vite-plugin-my-integration",
154
+ * })
155
+ * ```
156
+ */
157
+ const hasVitePlugin = defineUtility("astro:config:setup")(({ config }, { plugin }) => {
158
+ if (!plugin || plugin instanceof Promise) return false;
159
+ const currentPlugins = new Set(getPluginNames(config?.vite?.plugins));
160
+ const plugins = /* @__PURE__ */ new Set();
161
+ if (typeof plugin === "string") plugins.add(plugin);
162
+ if (typeof plugin === "object") if (Array.isArray(plugin)) {
163
+ const names = new Set(getPluginNames(plugin));
164
+ for (const name of names) plugins.add(name);
165
+ } else plugins.add(plugin.name);
166
+ return [...plugins].some((name) => currentPlugins.has(name));
167
+ });
168
+ /**
169
+ * Adds a [vite plugin](https://vitejs.dev/guide/using-plugins) to the
170
+ * Astro config.
171
+ *
172
+ * @param {import("astro").HookParameters<"astro:config:setup">} params
173
+ * @param {object} options
174
+ * @param {import("vite").PluginOption} options.plugin
175
+ * @param {boolean} [options.warnDuplicated=true]
176
+ *
177
+ * @see https://astro-integration-kit.netlify.app/utilities/add-vite-plugin/
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * addVitePlugin(params, {
182
+ * plugin,
183
+ * warnDuplicated: true,
184
+ * })
185
+ * ```
186
+ */
187
+ const addVitePlugin = defineUtility("astro:config:setup")((params, { plugin, warnDuplicated = true }) => {
188
+ const { updateConfig, logger } = params;
189
+ if (warnDuplicated && hasVitePlugin(params, { plugin })) logger.warn(`The Vite plugin "${plugin.name}" is already present in your Vite configuration, this plugin may not behave correctly.`);
190
+ updateConfig({ vite: { plugins: [plugin] } });
191
+ });
192
+ const incrementPluginName = (name) => {
193
+ let count = 1;
194
+ return `${name.replace(/-(\d+)$/, (_, c) => {
195
+ count = Number.parseInt(c) + 1;
196
+ return "";
197
+ })}-${count}`;
198
+ };
199
+ const resolveVirtualModuleId = (id) => {
200
+ return `\0${id}`;
201
+ };
202
+ const createVirtualModule = (name, _imports, bypassCoreValidation) => {
203
+ const imports = Array.isArray(_imports) ? _imports : Object.entries(_imports).map(([id, content]) => ({
204
+ id,
205
+ content,
206
+ context: void 0
207
+ }));
208
+ const duplicatedImports = {};
209
+ for (const { id, context } of imports) {
210
+ duplicatedImports[id] ??= [];
211
+ duplicatedImports[id]?.push(...context === void 0 ? ["server", "client"] : [context]);
212
+ }
213
+ for (const [id, contexts] of Object.entries(duplicatedImports)) if (contexts.length !== [...new Set(contexts)].length) throw new Error(`Virtual import with id "${id}" has been registered several times with conflicting contexts.`);
214
+ const resolutionMap = Object.fromEntries(imports.map(({ id }) => {
215
+ if (!bypassCoreValidation && id.startsWith("astro:")) throw new Error(`Virtual import name prefix can't be "astro:" (for "${id}") because it's reserved for Astro core.`);
216
+ return [resolveVirtualModuleId(id), id];
217
+ }));
218
+ return {
219
+ name,
220
+ resolveId(id) {
221
+ if (imports.find((_import) => _import.id === id)) return resolveVirtualModuleId(id);
222
+ },
223
+ load(id, options) {
224
+ const resolution = resolutionMap[id];
225
+ if (resolution) {
226
+ const context = options?.ssr ? "server" : "client";
227
+ const data = imports.find((_import) => _import.id === resolution && (_import.context === void 0 || _import.context === context));
228
+ if (data) return data.content;
229
+ }
230
+ }
231
+ };
232
+ };
233
+ /**
234
+ * Creates a Vite virtual module and updates the Astro config.
235
+ * Virtual imports are useful for passing things like config options, or data computed within the integration.
236
+ *
237
+ * @param {import("astro").HookParameters<"astro:config:setup">} params
238
+ * @param {object} options
239
+ * @param {string} options.name
240
+ * @param {Imports} options.imports
241
+ *
242
+ * @see https://astro-integration-kit.netlify.app/utilities/add-virtual-imports/
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * // my-integration/index.ts
247
+ * import { addVirtualImports } from "astro-integration-kit";
248
+ *
249
+ * addVirtualImports(params, {
250
+ * name: 'my-integration',
251
+ * imports: {
252
+ * 'virtual:my-integration/config': `export default ${ JSON.stringify({foo: "bar"}) }`,
253
+ * },
254
+ * });
255
+ * ```
256
+ *
257
+ * This is then readable anywhere else in your integration:
258
+ *
259
+ * ```ts
260
+ * // myIntegration/src/component/layout.astro
261
+ * import config from "virtual:my-integration/config";
262
+ *
263
+ * console.log(config.foo) // "bar"
264
+ * ```
265
+ */
266
+ const addVirtualImports = defineUtility("astro:config:setup")((params, { name, imports, __enableCorePowerDoNotUseOrYouWillBeFired = false }) => {
267
+ let pluginName = `vite-plugin-${name}`;
268
+ while (hasVitePlugin(params, { plugin: pluginName })) pluginName = incrementPluginName(pluginName);
269
+ addVitePlugin(params, {
270
+ warnDuplicated: false,
271
+ plugin: createVirtualModule(pluginName, imports, __enableCorePowerDoNotUseOrYouWillBeFired)
272
+ });
273
+ });
274
+
275
+ //#endregion
276
+ export { addVirtualImports, addVitePlugin, createResolver, defineUtility, dirname, hasVitePlugin, isAbsolute, normalizeString, normalizeWindowsPath, resolve };