alchemy 0.4.0 → 0.5.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.
Files changed (53) hide show
  1. package/README.md +3 -463
  2. package/lib/ai/data.d.ts +5 -0
  3. package/lib/ai/data.js +3 -0
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.js +82 -85
  6. package/lib/cloudflare/assets.d.ts +74 -0
  7. package/lib/cloudflare/assets.js +75 -0
  8. package/lib/cloudflare/bindings.d.ts +6 -1
  9. package/lib/cloudflare/bindings.js +6 -0
  10. package/lib/cloudflare/bound.d.ts +2 -1
  11. package/lib/cloudflare/custom-domain.d.ts +1 -15
  12. package/lib/cloudflare/custom-domain.js +1 -15
  13. package/lib/cloudflare/index.d.ts +1 -1
  14. package/lib/cloudflare/index.js +1 -1
  15. package/lib/cloudflare/r2-rest-state-store.js +52 -22
  16. package/lib/cloudflare/worker.d.ts +18 -0
  17. package/lib/cloudflare/worker.js +153 -4
  18. package/lib/internal/docs/providers.js +2 -1
  19. package/lib/os/exec.d.ts +103 -0
  20. package/lib/os/exec.js +103 -0
  21. package/lib/os/index.d.ts +1 -0
  22. package/lib/os/index.js +1 -0
  23. package/lib/secret.d.ts +2 -2
  24. package/lib/secret.js +2 -2
  25. package/lib/web/vitepress/vitepress.js +1 -1
  26. package/package.json +2 -1
  27. package/src/ai/data.ts +10 -0
  28. package/src/alchemy.ts +97 -100
  29. package/src/cloudflare/assets.ts +158 -0
  30. package/src/cloudflare/bindings.ts +11 -2
  31. package/src/cloudflare/bound.ts +4 -1
  32. package/src/cloudflare/custom-domain.ts +1 -15
  33. package/src/cloudflare/index.ts +1 -1
  34. package/src/cloudflare/r2-rest-state-store.ts +78 -34
  35. package/src/cloudflare/worker.ts +261 -6
  36. package/src/internal/docs/providers.ts +2 -1
  37. package/src/os/exec.ts +190 -0
  38. package/src/os/index.ts +1 -0
  39. package/src/secret.ts +2 -2
  40. package/src/web/vitepress/vitepress.ts +1 -1
  41. package/lib/cloudflare/generate-asset-manifest.d.ts +0 -2
  42. package/lib/cloudflare/generate-asset-manifest.js +0 -68
  43. package/lib/cloudflare/static-site-router.d.ts +0 -18
  44. package/lib/cloudflare/static-site-router.js +0 -115
  45. package/lib/cloudflare/static-site.d.ts +0 -211
  46. package/lib/cloudflare/static-site.js +0 -213
  47. package/lib/cloudflare/upload-asset-manifest.d.ts +0 -11
  48. package/lib/cloudflare/upload-asset-manifest.js +0 -56
  49. package/src/cloudflare/generate-asset-manifest.ts +0 -93
  50. package/src/cloudflare/static-site-router.ts +0 -157
  51. package/src/cloudflare/static-site.ts +0 -416
  52. package/src/cloudflare/upload-asset-manifest.ts +0 -85
  53. package/src/web/vitepress/index.md +0 -75
@@ -1,115 +0,0 @@
1
- import { Hono } from "hono";
2
- let isInit = false;
3
- const app = new Hono();
4
- export default {
5
- async fetch(request, env) {
6
- if (!isInit) {
7
- isInit = true;
8
- for (const [key, value] of Object.entries(env)) {
9
- if (key.startsWith("ROUTE_")) {
10
- const service = value;
11
- const patternKey = `__${key}`;
12
- if (!(patternKey in env)) {
13
- throw new Error(`Missing pattern key: ${patternKey}`);
14
- }
15
- const pattern = env[`__${key}`];
16
- // TODO(sam): we should support narrowing down METHOD
17
- app.all(pattern, (ctx) => service.fetch(ctx.req.raw));
18
- }
19
- }
20
- }
21
- return app.fetch(request, env);
22
- },
23
- };
24
- // fall back to assuming the request is a static file
25
- app.notFound(async (ctx) => {
26
- const request = ctx.req;
27
- const env = ctx.env;
28
- const url = new URL(request.url);
29
- const pathname = url.pathname.replace(/^\//, "");
30
- const filePath = pathname === "" ? env.INDEX_PAGE : pathname;
31
- // Return from cache if available
32
- const cachedResponse = await lookupCache();
33
- if (cachedResponse) {
34
- return cachedResponse;
35
- }
36
- // Fetch from KV
37
- {
38
- const object = await env.ASSETS.getWithMetadata(filePath, {
39
- type: "arrayBuffer",
40
- });
41
- if (object.value) {
42
- return await respond(200, filePath, object);
43
- }
44
- }
45
- {
46
- const guess = filePath + (filePath.endsWith("/") ? "" : "/") + "index.html";
47
- const object = await env.ASSETS.getWithMetadata(guess, {
48
- type: "arrayBuffer",
49
- });
50
- if (object.value) {
51
- return await respond(200, guess, object);
52
- }
53
- }
54
- {
55
- const guess = filePath + ".html";
56
- const object = await env.ASSETS.getWithMetadata(guess, {
57
- type: "arrayBuffer",
58
- });
59
- if (object.value) {
60
- return await respond(200, guess, object);
61
- }
62
- }
63
- // Handle error page
64
- if (env.ERROR_PAGE) {
65
- const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE, {
66
- type: "arrayBuffer",
67
- });
68
- if (object.value) {
69
- return await respond(404, env.ERROR_PAGE, object);
70
- }
71
- }
72
- else {
73
- const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE, {
74
- type: "arrayBuffer",
75
- });
76
- if (object.value) {
77
- return await respond(200, env.INDEX_PAGE, object);
78
- }
79
- }
80
- return new Response("Page Not Found", { status: 404 });
81
- async function lookupCache() {
82
- const cache = caches.default;
83
- const r = await cache.match(request.url);
84
- // cache does not exist
85
- if (!r)
86
- return;
87
- // cache exists but etag does not match
88
- if (r.headers.get("etag") !== __ASSET_MANIFEST__[filePath])
89
- return;
90
- // cache exists
91
- return r;
92
- }
93
- async function saveCache(response) {
94
- const cache = caches.default;
95
- await cache.put(request.url, response.clone());
96
- }
97
- async function respond(status, filePath, object) {
98
- // build response
99
- const headers = new Headers();
100
- if (__ASSET_MANIFEST__[filePath]) {
101
- headers.set("etag", __ASSET_MANIFEST__[filePath]);
102
- headers.set("content-type", object.metadata.contentType);
103
- headers.set("cache-control", object.metadata.cacheControl);
104
- }
105
- // Create response with the raw data directly
106
- const response = new Response(object.value, {
107
- status,
108
- headers,
109
- });
110
- if (request.method === "GET") {
111
- await saveCache(response);
112
- }
113
- return response;
114
- }
115
- });
@@ -1,211 +0,0 @@
1
- import type { Context } from "../context";
2
- import type { BundleProps } from "../esbuild/bundle";
3
- import { Resource } from "../resource";
4
- import { Worker } from "./worker";
5
- /**
6
- * Idea of using KV with timeout inspired by SST: https://github.com/sst/sst/blob/dev/platform/src/components/cloudflare/static-site.ts
7
- */
8
- /**
9
- * Properties for creating or updating a StaticSite
10
- */
11
- export interface StaticSiteProps {
12
- /**
13
- * Name for the site
14
- * This is mandatory - must be explicitly specified
15
- */
16
- name: string;
17
- /**
18
- * Path to the directory containing static assets
19
- */
20
- dir: string;
21
- /**
22
- * Custom Worker script to serve the site
23
- * If not provided, a default script will be used
24
- */
25
- workerScript?: string;
26
- /**
27
- * Worker script format
28
- * 'esm' - ECMAScript modules (default)
29
- * 'cjs' - CommonJS modules
30
- * @default 'esm'
31
- */
32
- format?: "esm" | "cjs";
33
- /**
34
- * Custom error page path (relative to directory)
35
- * @example "404.html"
36
- */
37
- errorPage?: string;
38
- /**
39
- * Index page filename
40
- * @default "index.html"
41
- */
42
- indexPage?: string;
43
- /**
44
- * Configure how the static site's assets are uploaded to KV
45
- */
46
- assets?: {
47
- /**
48
- * File options for configuring caching behavior
49
- */
50
- fileOptions?: {
51
- /**
52
- * File pattern(s) to match
53
- * Can be a glob string or array of glob strings
54
- */
55
- files: string | string[];
56
- /**
57
- * Files to ignore (glob patterns)
58
- */
59
- ignore?: string | string[];
60
- /**
61
- * Cache-Control header to set for matching files
62
- */
63
- cacheControl: string;
64
- /**
65
- * Content-Type to set (overrides auto-detection)
66
- */
67
- contentType?: string;
68
- }[];
69
- };
70
- /**
71
- * Whether the site should be deployed to production
72
- * @default true
73
- */
74
- production?: boolean;
75
- build?: {
76
- /**
77
- * Command to run before deploying the site
78
- * Will be executed in the shell to build the site
79
- */
80
- command?: string;
81
- };
82
- /**
83
- * Bundle options for the worker script
84
- */
85
- bundle?: Partial<BundleProps>;
86
- /**
87
- * Additional workers to route requests to.
88
- */
89
- routes?: {
90
- [key: string]: Worker;
91
- };
92
- }
93
- /**
94
- * Output returned after StaticSite creation/update
95
- */
96
- export interface StaticSite extends Resource<"cloudflare::StaticSite"> {
97
- /**
98
- * The ID of the worker
99
- */
100
- workerId: string;
101
- /**
102
- * Name for the site
103
- */
104
- name: string;
105
- /**
106
- * Path to the directory containing static assets
107
- */
108
- directory: string;
109
- /**
110
- * Worker script format
111
- */
112
- format?: "esm" | "cjs";
113
- /**
114
- * Custom error page path
115
- */
116
- errorPage?: string;
117
- /**
118
- * Index page filename
119
- */
120
- indexPage?: string;
121
- /**
122
- * Time at which the site was created
123
- */
124
- createdAt: number;
125
- /**
126
- * Time at which the site was last updated
127
- */
128
- updatedAt: number;
129
- /**
130
- * List of uploaded asset files
131
- */
132
- assets: string[];
133
- /**
134
- * Whether the site is deployed to production
135
- */
136
- production?: boolean;
137
- /**
138
- * The URL of the deployed site
139
- */
140
- url?: string;
141
- /**
142
- * Information about the backend worker if configured
143
- */
144
- routes?: Record<string, string>;
145
- }
146
- /**
147
- * A StaticSite resource deploys static web content to Cloudflare Workers, using KV for asset storage.
148
- * It provides an efficient way to serve static websites with global distribution and caching.
149
- *
150
- * @example
151
- * // Create a basic static site with default settings
152
- * const basicSite = await StaticSite("my-site", {
153
- * name: "my-site",
154
- * dir: "./dist",
155
- * url: true
156
- * });
157
- *
158
- * @example
159
- * // Create a static site with custom build command and minification disabled
160
- * const devSite = await StaticSite("dev-site", {
161
- * name: "dev-site",
162
- * dir: "./public",
163
- * build: {
164
- * command: "npm run build"
165
- * },
166
- * bundle: {
167
- * minify: false
168
- * }
169
- * });
170
- *
171
- * @example
172
- * // Create a static site with a backend API worker
173
- * const backend = await Worker("api-backend", {
174
- * name: "api-backend",
175
- * entrypoint: "./src/api.ts"
176
- * });
177
- *
178
- * const fullSite = await StaticSite("full-site", {
179
- * name: "full-site",
180
- * dir: "./dist",
181
- * routes: {
182
- * "/api/*": backend
183
- * }
184
- * });
185
- *
186
- * @example
187
- * // Create a static site with a custom domain using the CustomDomain resource
188
- * const site = await StaticSite("custom-site", {
189
- * name: "custom-site",
190
- * dir: "./www",
191
- * errorPage: "404.html",
192
- * indexPage: "home.html"
193
- * });
194
- *
195
- * // Then configure the custom domain separately
196
- * const domain = await CustomDomain("custom-domain", {
197
- * name: "www.example.com",
198
- * zoneId: "abcdef123456789",
199
- * workerName: site.name
200
- * });
201
- *
202
- * @example
203
- * // Create a static site with multiple domains (primary + redirects)
204
- * const site = await StaticSite("multi-domain-site", {
205
- * name: "multi-domain-site",
206
- * dir: "./public"
207
- * });
208
- *
209
- * @see https://developers.cloudflare.com/workers/platform/sites
210
- */
211
- export declare const StaticSite: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<StaticSite>, id: string, props: StaticSiteProps) => Promise<StaticSite>);
@@ -1,213 +0,0 @@
1
- import { exec } from "child_process";
2
- import { promises as fs } from "fs";
3
- import path from "node:path";
4
- import { promisify } from "util";
5
- import { Resource } from "../resource";
6
- import { createCloudflareApi } from "./api";
7
- import { generateAssetManifest } from "./generate-asset-manifest";
8
- import { KVNamespace } from "./kv-namespace";
9
- import { uploadAssetManifest } from "./upload-asset-manifest";
10
- import { Worker } from "./worker";
11
- const __dirname = import.meta.dirname;
12
- /**
13
- * A StaticSite resource deploys static web content to Cloudflare Workers, using KV for asset storage.
14
- * It provides an efficient way to serve static websites with global distribution and caching.
15
- *
16
- * @example
17
- * // Create a basic static site with default settings
18
- * const basicSite = await StaticSite("my-site", {
19
- * name: "my-site",
20
- * dir: "./dist",
21
- * url: true
22
- * });
23
- *
24
- * @example
25
- * // Create a static site with custom build command and minification disabled
26
- * const devSite = await StaticSite("dev-site", {
27
- * name: "dev-site",
28
- * dir: "./public",
29
- * build: {
30
- * command: "npm run build"
31
- * },
32
- * bundle: {
33
- * minify: false
34
- * }
35
- * });
36
- *
37
- * @example
38
- * // Create a static site with a backend API worker
39
- * const backend = await Worker("api-backend", {
40
- * name: "api-backend",
41
- * entrypoint: "./src/api.ts"
42
- * });
43
- *
44
- * const fullSite = await StaticSite("full-site", {
45
- * name: "full-site",
46
- * dir: "./dist",
47
- * routes: {
48
- * "/api/*": backend
49
- * }
50
- * });
51
- *
52
- * @example
53
- * // Create a static site with a custom domain using the CustomDomain resource
54
- * const site = await StaticSite("custom-site", {
55
- * name: "custom-site",
56
- * dir: "./www",
57
- * errorPage: "404.html",
58
- * indexPage: "home.html"
59
- * });
60
- *
61
- * // Then configure the custom domain separately
62
- * const domain = await CustomDomain("custom-domain", {
63
- * name: "www.example.com",
64
- * zoneId: "abcdef123456789",
65
- * workerName: site.name
66
- * });
67
- *
68
- * @example
69
- * // Create a static site with multiple domains (primary + redirects)
70
- * const site = await StaticSite("multi-domain-site", {
71
- * name: "multi-domain-site",
72
- * dir: "./public"
73
- * });
74
- *
75
- * @see https://developers.cloudflare.com/workers/platform/sites
76
- */
77
- export const StaticSite = Resource("cloudflare::StaticSite", {
78
- alwaysUpdate: true,
79
- }, async function (id, props) {
80
- // Create Cloudflare API client with automatic account discovery
81
- const api = await createCloudflareApi();
82
- if (this.phase === "delete") {
83
- // For delete operations, we'll rely on the Worker delete to clean up
84
- // Return empty output for deleted state
85
- return this.destroy();
86
- }
87
- // Validate that a name is provided
88
- if (!props.name) {
89
- throw new Error("StaticSite name is required - must be explicitly specified");
90
- }
91
- // Validate directory exists
92
- if (!props.dir) {
93
- throw new Error("Directory is required for StaticSite");
94
- }
95
- // Run build command if provided
96
- if (props.build?.command) {
97
- try {
98
- if (!this.quiet) {
99
- console.log(props.build.command);
100
- }
101
- const execAsync = promisify(exec);
102
- const { stdout, stderr } = await execAsync(props.build.command, {
103
- cwd: process.cwd(),
104
- });
105
- if (stdout && !this.quiet)
106
- console.log(stdout);
107
- }
108
- catch (error) {
109
- // Log detailed error information
110
- console.error(`Build command failed with exit code ${error.code}`);
111
- if (error.stdout)
112
- console.error(`Command stdout: ${error.stdout}`);
113
- if (error.stderr)
114
- console.error(`Command stderr: ${error.stderr}`);
115
- // Throw a more descriptive error that includes the exit code and stderr
116
- throw new Error(`Build command "${props.build.command}" failed with exit code ${error.code}:\n${error.stderr || error.message}`);
117
- }
118
- }
119
- try {
120
- const dirStat = await fs.stat(props.dir);
121
- if (!dirStat.isDirectory()) {
122
- throw new Error(`"${props.dir}" is not a directory`);
123
- }
124
- }
125
- catch (error) {
126
- throw new Error(`Directory "${props.dir}" does not exist: ${error.message}`);
127
- }
128
- // Use the provided name
129
- const siteName = props.name;
130
- const indexPage = props.indexPage || "index.html";
131
- // Step 1: Create or get the KV namespace for assets
132
- const [kv, assetManifest] = await Promise.all([
133
- KVNamespace("assets", {
134
- title: `${siteName}-assets`,
135
- }),
136
- generateAssetManifest(props.dir),
137
- ]);
138
- // Step 3: Upload assets to KV
139
- await uploadAssetManifest(api, kv.namespaceId, assetManifest);
140
- // Prepare the bindings for the worker
141
- const bindings = {
142
- ASSETS: kv,
143
- INDEX_PAGE: indexPage,
144
- // Add error page binding if specified
145
- ...(props.errorPage ? { ERROR_PAGE: props.errorPage } : {}),
146
- };
147
- const routes = {};
148
- // Create backend worker if configured
149
- if (props.routes) {
150
- for (const [path, worker] of Object.entries(props.routes)) {
151
- // @ts-ignore - TODO: need to use Resolved<Worker> to get the string ...
152
- routes[path] = worker.id;
153
- bindings[`ROUTE_${worker.id}`] = worker;
154
- }
155
- }
156
- // Create asset manifest banner for static site router
157
- const assetManifestBanner = `const __ASSET_MANIFEST__ = ${JSON.stringify(Object.fromEntries(assetManifest.map((item) => [item.key, item.hash])))};\n`;
158
- const bundleOptions = {
159
- ...props.bundle,
160
- options: {
161
- ...props.bundle?.options,
162
- banner: {
163
- js: assetManifestBanner,
164
- ...props.bundle?.options?.banner,
165
- },
166
- },
167
- };
168
- // Determine the entrypoint file - check for .ts first, fallback to .js
169
- let entrypointFile = "static-site-router.js";
170
- try {
171
- const tsFile = path.resolve(__dirname, "static-site-router.ts");
172
- await fs.access(tsFile);
173
- // If we reach here, the TypeScript file exists
174
- entrypointFile = "static-site-router.ts";
175
- }
176
- catch (error) {
177
- // TypeScript file doesn't exist, use JavaScript (default)
178
- }
179
- const worker = await Worker("worker", {
180
- name: siteName,
181
- url: true,
182
- format: props.format || "esm",
183
- // If a custom worker script is provided, use it, otherwise use the entrypoint from router
184
- ...(props.workerScript
185
- ? { script: props.workerScript }
186
- : {
187
- entrypoint: path.resolve(__dirname, entrypointFile),
188
- bundle: bundleOptions,
189
- }),
190
- bindings,
191
- env: {
192
- ...Object.fromEntries(Object.entries(routes).map(([key, value]) => [
193
- `__ROUTE_${value}`,
194
- key,
195
- ])),
196
- },
197
- });
198
- const now = Date.now();
199
- return this({
200
- workerId: worker.id,
201
- name: siteName,
202
- directory: props.dir,
203
- format: props.format || "esm",
204
- errorPage: props.errorPage,
205
- indexPage,
206
- assets: assetManifest.map((item) => item.key),
207
- createdAt: this.output?.createdAt || now,
208
- updatedAt: now,
209
- production: props.production !== false,
210
- url: worker.url,
211
- routes,
212
- });
213
- });
@@ -1,11 +0,0 @@
1
- import type { CloudflareApi } from "./api";
2
- import type { AssetManifest } from "./asset-manifest";
3
- type AssetItem = AssetManifest[number];
4
- type UploadResult = {
5
- item: AssetItem;
6
- success: boolean;
7
- status?: number;
8
- error?: unknown;
9
- };
10
- export declare function uploadAssetManifest(api: CloudflareApi, namespaceId: string, manifest: AssetManifest): Promise<UploadResult[]>;
11
- export {};
@@ -1,56 +0,0 @@
1
- import fs from "node:fs/promises";
2
- // Maximum number of concurrent uploads
3
- const MAX_CONCURRENT = 100; // Adjust based on API limits and performance testing
4
- export async function uploadAssetManifest(api, namespaceId, manifest) {
5
- const results = [];
6
- // Process in batches with controlled concurrency
7
- let index = 0;
8
- const activePromises = new Set();
9
- while (index < manifest.length || activePromises.size > 0) {
10
- // Fill up to MAX_CONCURRENT active uploads
11
- while (activePromises.size < MAX_CONCURRENT && index < manifest.length) {
12
- const item = manifest[index++];
13
- const promise = uploadItem(item).then((result) => {
14
- results.push(result);
15
- activePromises.delete(promise);
16
- });
17
- activePromises.add(promise);
18
- }
19
- // Wait for at least one promise to complete if we have any active
20
- if (activePromises.size > 0) {
21
- await Promise.race(activePromises);
22
- }
23
- }
24
- return results;
25
- async function uploadItem(item) {
26
- const content = await fs.readFile(item.source);
27
- // Create metadata object
28
- const metadata = {
29
- key: item.key,
30
- hash: item.hash,
31
- contentType: item.contentType,
32
- cacheControl: item.cacheControl,
33
- };
34
- // Create FormData for multipart upload
35
- const formData = new FormData();
36
- formData.append("metadata", JSON.stringify(metadata));
37
- // Add file content as 'value' - Fix for binary file corruption
38
- const blob = new Blob([new Uint8Array(content)], {
39
- type: item.contentType,
40
- });
41
- formData.append("value", blob);
42
- try {
43
- const response = await api.put(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/values/${item.key}`, formData);
44
- const result = { item, success: response.ok, status: response.status };
45
- if (!result.success) {
46
- console.warn(`Error uploading asset ${item.key}: ${result.status}`);
47
- }
48
- return result;
49
- }
50
- catch (error) {
51
- const result = { item, success: false, error };
52
- console.warn(`Error uploading asset ${item.key}: ${error}`);
53
- return result;
54
- }
55
- }
56
- }
@@ -1,93 +0,0 @@
1
- import crypto from "crypto";
2
- import { promises as fs } from "fs";
3
- import { glob } from "glob";
4
- import path from "path";
5
- import { getContentType } from "../util/content-type";
6
- import type {
7
- AssetManifest,
8
- AssetManifestEntry,
9
- FileOption,
10
- } from "./asset-manifest";
11
-
12
- export async function generateAssetManifest(
13
- dir: string,
14
- fileOptions: FileOption[] = [
15
- {
16
- files: "**",
17
- cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
18
- },
19
- {
20
- files: ["**/*.js", "**/*.css", "**/*.png", "**/*.webp"],
21
- cacheControl: "max-age=31536000,public,immutable",
22
- },
23
- ]
24
- ): Promise<AssetManifest> {
25
- // First, collect all file info based on fileOptions (reversed to prioritize later rules)
26
- const fileInfos: Array<{
27
- file: string;
28
- cacheControl: string;
29
- contentType?: string;
30
- }> = [];
31
- const filesProcessed: Set<string> = new Set();
32
-
33
- // Process file options in reverse order (later rules override earlier ones)
34
- // Use for...of to maintain order but allow await inside the loop
35
- const reversedOptions = [...fileOptions].reverse();
36
- for (const fileOption of reversedOptions) {
37
- const patterns = Array.isArray(fileOption.files)
38
- ? fileOption.files
39
- : [fileOption.files];
40
-
41
- // Get all files matching the pattern
42
- const files = (
43
- await Promise.all(
44
- patterns.map(async (p) => {
45
- // Use glob as async function instead of globSync
46
- return glob(p, {
47
- cwd: path.resolve(dir),
48
- nodir: true,
49
- dot: true,
50
- ignore: [
51
- ".git/**",
52
- "node_modules/**",
53
- ...(typeof fileOption.ignore === "string"
54
- ? [fileOption.ignore]
55
- : (fileOption.ignore ?? [])),
56
- ],
57
- });
58
- })
59
- )
60
- )
61
- .flat()
62
- .filter((file) => !filesProcessed.has(file));
63
-
64
- // Add files to processed set
65
- for (const file of files) {
66
- filesProcessed.add(file);
67
- fileInfos.push({
68
- file,
69
- cacheControl: fileOption.cacheControl,
70
- contentType: fileOption.contentType,
71
- });
72
- }
73
- }
74
-
75
- // Process all files in parallel
76
- const manifestEntries = await Promise.all(
77
- fileInfos.map(async ({ file, cacheControl, contentType }) => {
78
- const source = path.resolve(dir, file);
79
- const content = await fs.readFile(source);
80
- const hash = crypto.createHash("sha256").update(content).digest("hex");
81
-
82
- return {
83
- source,
84
- key: file,
85
- hash,
86
- cacheControl,
87
- contentType: contentType ?? getContentType(file),
88
- } satisfies AssetManifestEntry;
89
- })
90
- );
91
-
92
- return manifestEntries;
93
- }