diff --git a/packages/core/src/plugins/bindings.ts b/packages/core/src/plugins/bindings.ts index 7d1fc6ddf..9b135088c 100644 --- a/packages/core/src/plugins/bindings.ts +++ b/packages/core/src/plugins/bindings.ts @@ -45,6 +45,7 @@ export interface BindingsOptions { bindings?: Record; globals?: Record; wasmBindings?: Record; + textBlobBindings?: Record; serviceBindings?: ServiceBindingsOptions; } @@ -164,6 +165,16 @@ export class BindingsPlugin }) wasmBindings?: Record; + @Option({ + type: OptionType.OBJECT, + typeFormat: "NAME=PATH", + name: "text-blob", + description: "Text blob to bind", + logName: "Text Blob Bindings", + fromWrangler: ({ text_blobs }) => text_blobs, + }) + textBlobBindings?: Record; + @Option({ type: OptionType.OBJECT, typeFormat: "NAME=MOUNT[@ENV]", @@ -246,8 +257,9 @@ export class BindingsPlugin // 1) Wrangler [vars] // 2) .env Variables // 3) WASM Module Bindings - // 4) Service Bindings - // 5) Custom Bindings + // 4) Text blob Bindings + // 5) Service Bindings + // 6) Custom Bindings const bindings: Context = {}; const watch: string[] = []; @@ -281,12 +293,22 @@ export class BindingsPlugin } } - // 4) Load service bindings + // 4) Load text blobs from files + if (this.textBlobBindings) { + // eslint-disable-next-line prefer-const + for (let [name, textPath] of Object.entries(this.textBlobBindings)) { + textPath = path.resolve(this.ctx.rootPath, textPath); + bindings[name] = await fs.readFile(textPath, "utf-8"); + watch.push(textPath); + } + } + + // 5) Load service bindings for (const { name, service } of this.#processedServiceBindings) { bindings[name] = new Fetcher(service, this.#getServiceFetch); } - // 5) Copy user's arbitrary bindings + // 6) Copy user's arbitrary bindings Object.assign(bindings, this.bindings); return { globals: this.globals, bindings, watch }; diff --git a/packages/core/test/fixtures/lorem-ipsum.txt b/packages/core/test/fixtures/lorem-ipsum.txt new file mode 100644 index 000000000..fbc74b53a --- /dev/null +++ b/packages/core/test/fixtures/lorem-ipsum.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Parturient montes nascetur ridiculus mus mauris vitae ultricies leo integer. Nisi porta lorem mollis aliquam ut porttitor. Placerat duis ultricies lacus sed. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Hendrerit dolor magna eget est. Sed nisi lacus sed viverra. Id cursus metus aliquam eleifend mi in nulla posuere. Donec ultrices tincidunt arcu non sodales neque sodales ut etiam. Lectus proin nibh nisl condimentum id. Vitae tempus quam pellentesque nec nam aliquam sem. + +Ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant. Pellentesque massa placerat duis ultricies. Ultrices gravida dictum fusce ut placerat orci nulla. Quam pellentesque nec nam aliquam sem et tortor consequat id. Laoreet sit amet cursus sit amet dictum sit amet justo. Cursus metus aliquam eleifend mi in nulla. Mattis molestie a iaculis at erat pellentesque adipiscing commodo. Nulla aliquet porttitor lacus luctus. Tristique senectus et netus et. Donec adipiscing tristique risus nec feugiat in fermentum posuere urna. + +Hac habitasse platea dictumst quisque sagittis purus sit. Adipiscing commodo elit at imperdiet dui accumsan sit. Etiam erat velit scelerisque in dictum non consectetur a. Cras pulvinar mattis nunc sed blandit libero volutpat sed cras. Est velit egestas dui id ornare. Orci dapibus ultrices in iaculis. Quis vel eros donec ac odio tempor orci dapibus ultrices. Lectus arcu bibendum at varius vel. Eget egestas purus viverra accumsan in nisl nisi scelerisque eu. Elementum eu facilisis sed odio morbi quis. Etiam tempor orci eu lobortis elementum nibh tellus molestie. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Nunc lobortis mattis aliquam faucibus purus in massa. Quam id leo in vitae turpis massa. Mus mauris vitae ultricies leo integer malesuada nunc vel. + +Et malesuada fames ac turpis egestas sed tempus urna et. Donec massa sapien faucibus et molestie ac feugiat. Amet cursus sit amet dictum sit amet justo donec enim. Ut etiam sit amet nisl purus. Risus in hendrerit gravida rutrum quisque non tellus. Nibh mauris cursus mattis molestie a. Elementum nisi quis eleifend quam adipiscing vitae proin. Mi quis hendrerit dolor magna eget est lorem ipsum. Ut morbi tincidunt augue interdum velit euismod in pellentesque massa. Proin sagittis nisl rhoncus mattis rhoncus urna neque. Ullamcorper sit amet risus nullam eget felis. Sit amet tellus cras adipiscing enim eu. Quis eleifend quam adipiscing vitae proin sagittis nisl rhoncus. Convallis posuere morbi leo urna molestie at elementum eu facilisis. Sed adipiscing diam donec adipiscing tristique risus nec. Rhoncus aenean vel elit scelerisque mauris pellentesque. Leo in vitae turpis massa sed elementum tempus egestas sed. In hac habitasse platea dictumst quisque sagittis purus sit amet. Eros donec ac odio tempor orci. Eleifend mi in nulla posuere sollicitudin aliquam ultrices. + +Amet facilisis magna etiam tempor orci eu lobortis. In ornare quam viverra orci. Convallis convallis tellus id interdum velit laoreet id. Rutrum tellus pellentesque eu tincidunt tortor aliquam. Tincidunt dui ut ornare lectus sit amet est placerat in. Nibh sed pulvinar proin gravida hendrerit lectus. Ornare aenean euismod elementum nisi quis eleifend quam adipiscing vitae. Amet luctus venenatis lectus magna fringilla urna. Nec ultrices dui sapien eget mi proin sed. Magna ac placerat vestibulum lectus. Risus nec feugiat in fermentum posuere urna. Cursus turpis massa tincidunt dui. Turpis egestas integer eget aliquet nibh praesent. Tincidunt vitae semper quis lectus. Massa id neque aliquam vestibulum morbi blandit cursus risus. Egestas fringilla phasellus faucibus scelerisque. In massa tempor nec feugiat nisl pretium fusce. A lacus vestibulum sed arcu non odio. Adipiscing elit duis tristique sollicitudin nibh sit amet commodo nulla. Quam id leo in vitae turpis massa sed elementum. diff --git a/packages/core/test/plugins/bindings.spec.ts b/packages/core/test/plugins/bindings.spec.ts index 48d04330a..3b8ba0ee6 100644 --- a/packages/core/test/plugins/bindings.spec.ts +++ b/packages/core/test/plugins/bindings.spec.ts @@ -1,4 +1,5 @@ import assert from "assert"; +import { readFileSync } from "fs"; import fs from "fs/promises"; import path from "path"; import { setImmediate } from "timers/promises"; @@ -37,6 +38,9 @@ const fixturesPath = path.join(__dirname, "..", "..", "..", "test", "fixtures"); // its 2 integer parameters together and returns the result, it is from: // https://webassembly.github.io/wabt/demo/wat2wasm/ const addModulePath = path.join(fixturesPath, "add.wasm"); +// lorem-ipsum.txt is five paragraphs of lorem ipsum nonsense text +const loremIpsumPath = path.join(fixturesPath, "lorem-ipsum.txt"); +const loremIpsum = readFileSync(loremIpsumPath, "utf-8"); test("BindingsPlugin: parses options from argv", (t) => { let options = parsePluginArgv(BindingsPlugin, [ @@ -54,6 +58,10 @@ test("BindingsPlugin: parses options from argv", (t) => { "MODULE1=module1.wasm", "--wasm", "MODULE2=module2.wasm", + "--text-blob", + "TEXT1=text-blob-1.txt", + "--text-blob", + "TEXT2=text-blob-2.txt", "--service", "SERVICE1=service1", "--service", @@ -64,6 +72,7 @@ test("BindingsPlugin: parses options from argv", (t) => { bindings: { KEY1: "value1", KEY2: "value2" }, globals: { KEY3: "value3", KEY4: "value4" }, wasmBindings: { MODULE1: "module1.wasm", MODULE2: "module2.wasm" }, + textBlobBindings: { TEXT1: "text-blob-1.txt", TEXT2: "text-blob-2.txt" }, serviceBindings: { SERVICE1: "service1", SERVICE2: { service: "service2", environment: "development" }, @@ -96,6 +105,10 @@ test("BindingsPlugin: parses options from wrangler config", async (t) => { MODULE1: "module1.wasm", MODULE2: "module2.wasm", }, + text_blobs: { + TEXT1: "text-blob-1.txt", + TEXT2: "text-blob-2.txt", + }, experimental_services: [ { name: "SERVICE1", service: "service1", environment: "development" }, { name: "SERVICE2", service: "service2", environment: "production" }, @@ -109,6 +122,7 @@ test("BindingsPlugin: parses options from wrangler config", async (t) => { envPath: ".env.test", globals: { KEY5: "value5", KEY6: false, KEY7: 10 }, wasmBindings: { MODULE1: "module1.wasm", MODULE2: "module2.wasm" }, + textBlobBindings: { TEXT1: "text-blob-1.txt", TEXT2: "text-blob-2.txt" }, serviceBindings: { SERVICE1: { service: "service1", environment: "development" }, SERVICE2: { service: "service2", environment: "production" }, @@ -141,6 +155,7 @@ test("BindingsPlugin: logs options", (t) => { bindings: { KEY3: "value3", KEY4: "value4" }, globals: { KEY5: "value5", KEY6: "value6" }, wasmBindings: { MODULE1: "module1.wasm", MODULE2: "module2.wasm" }, + textBlobBindings: { TEXT1: "text-blob-1.txt", TEXT2: "text-blob-2.txt" }, serviceBindings: { SERVICE1: "service1", SERVICE2: { service: "service2", environment: "development" }, @@ -152,6 +167,7 @@ test("BindingsPlugin: logs options", (t) => { "Custom Bindings: KEY3, KEY4", "Custom Globals: KEY5, KEY6", "WASM Bindings: MODULE1, MODULE2", + "Text Blob Bindings: TEXT1, TEXT2", "Service Bindings: SERVICE1, SERVICE2", ]); logs = logPluginOptions(BindingsPlugin, { envPath: true }); @@ -259,22 +275,40 @@ test("BindingsPlugin: setup: loads WebAssembly bindings", async (t) => { result = await plugin.setup(); t.not(result.bindings?.ADD, undefined); }); + +test("BindingsPlugin: setup: loads text blob bindings", async (t) => { + let plugin = new BindingsPlugin(ctx, { + textBlobBindings: { LOREM_IPSUM: loremIpsumPath }, + }); + let result = await plugin.setup(); + t.is(result.bindings?.LOREM_IPSUM, loremIpsum); + + // Check resolves text blob bindings path relative to rootPath + plugin = new BindingsPlugin( + { log, compat, rootPath: path.dirname(loremIpsumPath) }, + { textBlobBindings: { LOREM_IPSUM: "lorem-ipsum.txt" } } + ); + result = await plugin.setup(); + t.is(result.bindings?.LOREM_IPSUM, loremIpsum); +}); + test("BindingsPlugin: setup: loads bindings from all sources", async (t) => { // Bindings should be loaded in this order, from lowest to highest priority: // 1) Wrangler [vars] // 2) .env Variables // 3) WASM Module Bindings - // 4) Service Bindings - // 5) Custom Bindings + // 4) Text Blob Bindings + // 5) Service Bindings + // 6) Custom Bindings // wranglerOptions should contain [kWranglerBindings] const wranglerOptions = parsePluginWranglerConfig(BindingsPlugin, { - vars: { A: "w", B: "w", C: "w", D: "w", E: "w" }, + vars: { A: "w", B: "w", C: "w", D: "w", E: "w", F: "w" }, }); const tmp = await useTmp(t); const envPath = path.join(tmp, ".env"); - await fs.writeFile(envPath, "A=env\nB=env\nC=env\nD=env"); + await fs.writeFile(envPath, "A=env\nB=env\nC=env\nD=env\nE=env"); const obj = { ping: "pong" }; const throws = () => { @@ -286,6 +320,12 @@ test("BindingsPlugin: setup: loads bindings from all sources", async (t) => { A: addModulePath, B: addModulePath, C: addModulePath, + D: addModulePath, + }, + textBlobBindings: { + A: loremIpsumPath, + B: loremIpsumPath, + C: loremIpsumPath, }, serviceBindings: { A: throws, B: throws }, bindings: { A: obj }, @@ -294,9 +334,10 @@ test("BindingsPlugin: setup: loads bindings from all sources", async (t) => { const result = await plugin.setup(); assert(result.bindings); - t.is(result.bindings.E, "w"); - t.is(result.bindings.D, "env"); - t.true(result.bindings.C instanceof WebAssembly.Module); + t.is(result.bindings.F, "w"); + t.is(result.bindings.E, "env"); + t.true(result.bindings.D instanceof WebAssembly.Module); + t.is(result.bindings.C, loremIpsum); t.true(result.bindings.B instanceof Fetcher); t.is(result.bindings.A, obj); }); diff --git a/packages/shared/src/wrangler.ts b/packages/shared/src/wrangler.ts index c90afa459..8f2a97800 100644 --- a/packages/shared/src/wrangler.ts +++ b/packages/shared/src/wrangler.ts @@ -35,6 +35,7 @@ export interface WranglerEnvironmentConfig { }; // inherited usage_model?: "bundled" | "unbound"; // inherited wasm_modules?: Record; // (probably) inherited + text_blobs?: Record; experimental_services?: { name: string; service: string;