Skip to content

Commit 8527675

Browse files
feat: experimental workers assets can be ignored by adding a .assetsignore file (#6640)
* feat: experimental workers assets can be ignored by adding a .cfassetsignore file * fixup! feat: experimental workers assets can be ignored by adding a .cfassetsignore file
1 parent 4107f57 commit 8527675

File tree

6 files changed

+106
-13
lines changed

6 files changed

+106
-13
lines changed

.changeset/nasty-hats-rhyme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
feat: experimental workers assets can be ignored by adding a .assetsignore file
6+
7+
This file can be added to the root of the assets directory that is to be uploaded alongside the Worker
8+
when using `experimental_assets`.
9+
10+
The file follows the `.gitignore` syntax, and any matching paths will not be included in the upload.

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"editor.defaultFormatter": "esbenp.prettier-vscode",
33
"cSpell.words": [
44
"Abortable",
5+
"assetsignore",
56
"cfetch",
67
"chatgpt",
78
"clipboardy",
@@ -11,6 +12,7 @@
1112
"esbuild",
1213
"eslintcache",
1314
"execa",
15+
"filestat",
1416
"haikunate",
1517
"haikunator",
1618
"httplogs",

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4381,6 +4381,47 @@ addEventListener('fetch', event => {});`
43814381
});
43824382
});
43834383

4384+
it("should ignore assets that match patterns in an .assetsignore file in the root of the assets directory", async () => {
4385+
const assets = [
4386+
{ filePath: ".assetsignore", content: "*.bak\nsub-dir" },
4387+
{ filePath: "file-1.txt", content: "Content of file-1" },
4388+
{ filePath: "file-2.bak", content: "Content of file-2" },
4389+
{ filePath: "file-3.txt", content: "Content of file-3" },
4390+
{ filePath: "sub-dir/file-4.bak", content: "Content of file-4" },
4391+
{ filePath: "sub-dir/file-5.txt", content: "Content of file-5" },
4392+
];
4393+
writeAssets(assets, "some/path/assets");
4394+
writeWranglerToml(
4395+
{
4396+
experimental_assets: { directory: "assets" },
4397+
},
4398+
"some/path/wrangler.toml"
4399+
);
4400+
const bodies: AssetManifest[] = [];
4401+
await mockAUSRequest(bodies);
4402+
mockSubDomainRequest();
4403+
mockUploadWorkerRequest({
4404+
expectedExperimentalAssets: true,
4405+
expectedType: "none",
4406+
});
4407+
await runWrangler("deploy --config some/path/wrangler.toml");
4408+
expect(bodies.length).toBe(1);
4409+
expect(bodies[0]).toMatchInlineSnapshot(`
4410+
Object {
4411+
"manifest": Object {
4412+
"/file-1.txt": Object {
4413+
"hash": "0de3dd5df907418e9730fd2bd747bd5e",
4414+
"size": 17,
4415+
},
4416+
"/file-3.txt": Object {
4417+
"hash": "ff5016e92f039aa743a4ff7abb3180fa",
4418+
"size": 17,
4419+
},
4420+
},
4421+
}
4422+
`);
4423+
});
4424+
43844425
it("should resolve assets directory relative to cwd if using cli", async () => {
43854426
const assets = [{ filePath: "file-1.txt", content: "Content of file-1" }];
43864427
writeAssets(assets, "some/path/assets");

packages/wrangler/src/experimental-assets.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { logger, LOGGER_LEVELS } from "./logger";
1414
import { hashFile } from "./pages/hash";
1515
import { isJwtExpired } from "./pages/upload";
1616
import { APIError } from "./parse";
17+
import { createPatternMatcher } from "./utils/filesystem";
1718
import type { Config } from "./config";
1819
import type { ExperimentalAssets } from "./config/environment";
1920

@@ -220,10 +221,20 @@ export const buildAssetsManifest = async (dir: string) => {
220221
const files = await readdir(dir, { recursive: true });
221222
const manifest: AssetManifest = {};
222223
let counter = 0;
224+
225+
const ignoreFn = await createAssetIgnoreFunction(dir);
226+
223227
await Promise.all(
224228
files.map(async (file) => {
225229
const filepath = path.join(dir, file);
226230
const relativeFilepath = path.relative(dir, filepath);
231+
232+
if (ignoreFn?.(relativeFilepath)) {
233+
logger.debug("Ignoring asset:", relativeFilepath);
234+
// This file should not be included in the manifest.
235+
return;
236+
}
237+
227238
const filestat = await stat(filepath);
228239

229240
if (filestat.isSymbolicLink() || filestat.isDirectory()) {
@@ -361,3 +372,28 @@ const decodeFilepath = (filePath: string) => {
361372
.map((segment) => decodeURIComponent(segment))
362373
.join(path.sep);
363374
};
375+
376+
/**
377+
* Create a function for filtering out ignored assets.
378+
*
379+
* The generated function takes an asset path, relative to the asset directory,
380+
* and returns true if the asset should not be ignored.
381+
*/
382+
async function createAssetIgnoreFunction(dir: string) {
383+
const CF_ASSETS_IGNORE_FILENAME = ".assetsignore";
384+
385+
const cfAssetIgnorePath = path.resolve(dir, CF_ASSETS_IGNORE_FILENAME);
386+
387+
if (!existsSync(cfAssetIgnorePath)) {
388+
return null;
389+
}
390+
391+
const ignorePatterns = (
392+
await readFile(cfAssetIgnorePath, { encoding: "utf8" })
393+
).split("\n");
394+
395+
// Always ignore the `.assetsignore` file.
396+
ignorePatterns.push(CF_ASSETS_IGNORE_FILENAME);
397+
398+
return createPatternMatcher(ignorePatterns, true);
399+
}

packages/wrangler/src/sites.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import assert from "node:assert";
22
import { readdir, readFile, stat } from "node:fs/promises";
33
import * as path from "node:path";
44
import chalk from "chalk";
5-
import ignore from "ignore";
65
import xxhash from "xxhash-wasm";
76
import { UserError } from "./errors";
87
import {
@@ -17,6 +16,7 @@ import {
1716
putKVKeyValue,
1817
} from "./kv/helpers";
1918
import { logger, LOGGER_LEVELS } from "./logger";
19+
import { createPatternMatcher } from "./utils/filesystem";
2020
import type { Config } from "./config";
2121
import type { KeyValue } from "./kv/helpers";
2222
import type { XXHashAPI } from "xxhash-wasm";
@@ -391,18 +391,6 @@ export async function syncLegacyAssets(
391391
return { manifest, namespace };
392392
}
393393

394-
function createPatternMatcher(
395-
patterns: string[],
396-
exclude: boolean
397-
): (filePath: string) => boolean {
398-
if (patterns.length === 0) {
399-
return (_filePath) => !exclude;
400-
} else {
401-
const ignorer = ignore().add(patterns);
402-
return (filePath) => ignorer.test(filePath).ignored;
403-
}
404-
}
405-
406394
/**
407395
* validate that the passed-in file is below 25 MiB
408396
* **PRIOR** to base64 encoding. 25 MiB is a KV limit

packages/wrangler/src/utils/filesystem.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mkdirSync } from "fs";
22
import { mkdir } from "fs/promises";
33
import path from "path";
4+
import ignore from "ignore";
45

56
export async function ensureDirectoryExists(filepath: string) {
67
const dirpath = path.dirname(filepath);
@@ -13,3 +14,18 @@ export function ensureDirectoryExistsSync(filepath: string) {
1314

1415
mkdirSync(dirpath, { recursive: true });
1516
}
17+
18+
/**
19+
* Generate a function that can match relative filepaths against a list of gitignore formatted patterns.
20+
*/
21+
export function createPatternMatcher(
22+
patterns: string[],
23+
exclude: boolean
24+
): (filePath: string) => boolean {
25+
if (patterns.length === 0) {
26+
return (_filePath) => !exclude;
27+
} else {
28+
const ignorer = ignore().add(patterns);
29+
return (filePath) => ignorer.test(filePath).ignored;
30+
}
31+
}

0 commit comments

Comments
 (0)