From 8818f5516ca909cc941deb953b6359030a8c0301 Mon Sep 17 00:00:00 2001 From: MrBBot Date: Fri, 28 Apr 2023 13:20:16 +0100 Subject: [PATCH 01/25] Improve Workers Sites asset sync reliability (#3098) * Improve Workers Sites asset sync reliability - When splitting upload into buckets, just store the file path, not the full content. This means we have to read the contents twice, but avoids buffering all content before uploading, causing OOMs. - Limit in-flight bulk upload requests to 5, avoiding `Too many bulk operations already in progress.` error. - Fix logging of upload progress. Previous, progress was logged per upload bucket, which doesn't really make sense to end users. Now, upload progress is across all files in all buckets. - Only log first 100 changed assets by default. The rest can be shown by setting `WRANGLER_LOG=debug`. This avoids console spam when uploading sites with 1000s of files. A little bit of colour has also been added to the diff. :) Closes #2223 Closes #2245 * fixup! Improve Workers Sites asset sync reliability Move fetching list log * fixup! Improve Workers Sites asset sync reliability Read files for upload in serial * fixup! Improve Workers Sites asset sync reliability Add test for upload failing * fixup! Improve Workers Sites asset sync reliability Ensure publish tests not dependent on bucket upload order --- .changeset/clever-radios-cover.md | 10 + .../wrangler/src/__tests__/publish.test.ts | 794 ++++++++++++++---- packages/wrangler/src/kv/helpers.ts | 11 +- packages/wrangler/src/sites.ts | 216 +++-- 4 files changed, 791 insertions(+), 240 deletions(-) create mode 100644 .changeset/clever-radios-cover.md diff --git a/.changeset/clever-radios-cover.md b/.changeset/clever-radios-cover.md new file mode 100644 index 000000000000..2a5c81f62300 --- /dev/null +++ b/.changeset/clever-radios-cover.md @@ -0,0 +1,10 @@ +--- +"wrangler": minor +--- + +fix: improve Workers Sites asset upload reliability + +- Wrangler no longer buffers all assets into memory before uploading. This should prevent out-of-memory errors when publishing sites with many large files. +- Wrangler now limits the number of in-flight asset upload requests to 5, fixing the `Too many bulk operations already in progress` error. +- Wrangler now correctly logs upload progress. Previously, the reported percentage was per upload request group, not across all assets. +- Wrangler no longer logs all assets to the console by default. Instead, it will just log the first 100. The rest can be shown by setting the `WRANGLER_LOG=debug` environment variable. A splash of colour has also been added. diff --git a/packages/wrangler/src/__tests__/publish.test.ts b/packages/wrangler/src/__tests__/publish.test.ts index df3abef4fee8..5886ce7771d2 100644 --- a/packages/wrangler/src/__tests__/publish.test.ts +++ b/packages/wrangler/src/__tests__/publish.test.ts @@ -10,6 +10,7 @@ import { printBundleSize, printOffendingDependencies, } from "../bundle-reporter"; +import { logger } from "../logger"; import { writeAuthConfigFile } from "../user"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; import { mockAuthDomain } from "./helpers/mock-auth-domain"; @@ -68,6 +69,7 @@ describe("publish", () => { setIsTTY(true); mockLastDeploymentRequest(); mockDeploymentsListRequest(); + logger.loggerLevel = "log"; }); afterEach(() => { @@ -1551,12 +1553,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -1604,12 +1607,16 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets); await runWrangler("publish --config ./my-site/wrangler.toml"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -1695,12 +1702,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -1744,12 +1752,16 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets); await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -1786,12 +1798,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -1977,12 +1990,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading subdir/file-1.txt... - Uploading as subdir/file-1.2ca234f380.txt... - Reading subdir/file-2.txt... - Uploading as subdir/file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + subdir/file-1.2ca234f380.txt (uploading new version of subdir/file-1.txt) + + subdir/file-2.5938485188.txt (uploading new version of subdir/file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2025,12 +2039,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading subdir/file-1.txt... - Uploading as subdir/file-1.2ca234f380.txt... - Reading subdir/file-2.txt... - Uploading as subdir/file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + subdir/file-1.2ca234f380.txt (uploading new version of subdir/file-1.txt) + + subdir/file-2.5938485188.txt (uploading new version of subdir/file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2082,12 +2097,16 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + subdir/file-1.2ca234f380.txt (uploading new version of subdir/file-1.txt) + + subdir/file-2.5938485188.txt (uploading new version of subdir/file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading subdir/file-1.txt... - Uploading as subdir/file-1.2ca234f380.txt... - Reading subdir/file-2.txt... - Uploading as subdir/file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2140,12 +2159,16 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2192,12 +2215,16 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2242,12 +2269,16 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets); await runWrangler("publish --env some-env --legacy-env false"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (some-env) (TIMINGS) Published test-name (some-env) (TIMINGS) @@ -2293,12 +2324,16 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets); await runWrangler("publish --env some-env --legacy-env true"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name-some-env (TIMINGS) Published test-name-some-env (TIMINGS) @@ -2338,11 +2373,7 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Skipping - already uploaded. - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2380,10 +2411,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish --site-include file-1.txt"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2421,10 +2457,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish --site-exclude file-2.txt"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2463,10 +2504,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2505,10 +2551,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2547,10 +2598,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish --site-include file-1.txt"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2589,10 +2645,15 @@ addEventListener('fetch', event => {});` ); await runWrangler("publish --site-exclude file-2.txt"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2633,10 +2694,15 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets.slice(0, 1)); await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + directory-1/file-1.2ca234f380.txt (uploading new version of directory-1/file-1.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading directory-1/file-1.txt... - Uploading as directory-1/file-1.2ca234f380.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2681,10 +2747,15 @@ addEventListener('fetch', event => {});` mockUploadAssetsToKVRequest(kvNamespace.id, assets.slice(2)); await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + .well-known/file-2.5938485188.txt (uploading new version of .well-known/file-2.txt) + Uploading 1 new asset... + Uploaded 100% [1 out of 1]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading .well-known/file-2.txt... - Uploading as .well-known/file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2730,13 +2801,15 @@ addEventListener('fetch', event => {});` `"File too-large-file.txt is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits"` ); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + large-file.0ea0637a45.txt (uploading new version of large-file.txt)" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading large-file.txt... - Uploading as large-file.0ea0637a45.txt... - Reading too-large-file.txt... - - If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" - `); + " + If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] File too-large-file.txt is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits @@ -2772,7 +2845,9 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); // We expect this to be uploaded in 4 batches - + expect(requests.length).toEqual(4); + // Buckets may be uploaded in any order, so sort them before we assert + requests.sort((a, b) => a.uploads[0].key.localeCompare(b.uploads[0].key)); // The first batch has 11 files expect(requests[0].uploads.length).toEqual(11); // The next batch has 5 files @@ -2790,60 +2865,49 @@ addEventListener('fetch', event => {});` } } - expect(std).toMatchInlineSnapshot(` - Object { - "debug": "", - "err": "", - "info": "", - "out": "Reading file-00.txt... - Uploading as file-00.be5be5dd26.txt... - Reading file-01.txt... - Uploading as file-01.4842d35994.txt... - Reading file-02.txt... - Uploading as file-02.990572ec63.txt... - Reading file-03.txt... - Uploading as file-03.9d7dda9045.txt... - Reading file-04.txt... - Uploading as file-04.2b6fac6382.txt... - Reading file-05.txt... - Uploading as file-05.55762dc758.txt... - Reading file-06.txt... - Uploading as file-06.f408a6b020.txt... - Reading file-07.txt... - Uploading as file-07.64c051715b.txt... - Reading file-08.txt... - Uploading as file-08.d286789adb.txt... - Reading file-09.txt... - Uploading as file-09.6838c183a8.txt... - Reading file-10.txt... - Uploading as file-10.6e03221d2a.txt... - Reading file-11.txt... - Uploading as file-11.37d3fb2eff.txt... - Reading file-12.txt... - Uploading as file-12.b3556942f8.txt... - Reading file-13.txt... - Uploading as file-13.680caf51b1.txt... - Reading file-14.txt... - Uploading as file-14.51e88468f0.txt... - Reading file-15.txt... - Uploading as file-15.8e3fedb394.txt... - Reading file-16.txt... - Uploading as file-16.c81c5e426f.txt... - Reading file-17.txt... - Uploading as file-17.4b2ae3c47b.txt... - Reading file-18.txt... - Uploading as file-18.07f245e02b.txt... - Reading file-19.txt... - Uploading as file-19.f0d69f705d.txt... - ↗️ Done syncing assets + expect(std.debug).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) https://test-name.test-sub-domain.workers.dev - Current Deployment ID: Galaxy-Class", - "warn": "", - } + Current Deployment ID: Galaxy-Class" + `); + // Mask all but last upload progress message as upload order unknown + // (regexp replaces all single/double-digit percentages, i.e. not 100%) + expect(std.info.replace(/Uploaded \d\d?% \[\d+/g, "Uploaded X% [X")) + .toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-00.be5be5dd26.txt (uploading new version of file-00.txt) + + file-01.4842d35994.txt (uploading new version of file-01.txt) + + file-02.990572ec63.txt (uploading new version of file-02.txt) + + file-03.9d7dda9045.txt (uploading new version of file-03.txt) + + file-04.2b6fac6382.txt (uploading new version of file-04.txt) + + file-05.55762dc758.txt (uploading new version of file-05.txt) + + file-06.f408a6b020.txt (uploading new version of file-06.txt) + + file-07.64c051715b.txt (uploading new version of file-07.txt) + + file-08.d286789adb.txt (uploading new version of file-08.txt) + + file-09.6838c183a8.txt (uploading new version of file-09.txt) + + file-10.6e03221d2a.txt (uploading new version of file-10.txt) + + file-11.37d3fb2eff.txt (uploading new version of file-11.txt) + + file-12.b3556942f8.txt (uploading new version of file-12.txt) + + file-13.680caf51b1.txt (uploading new version of file-13.txt) + + file-14.51e88468f0.txt (uploading new version of file-14.txt) + + file-15.8e3fedb394.txt (uploading new version of file-15.txt) + + file-16.c81c5e426f.txt (uploading new version of file-16.txt) + + file-17.4b2ae3c47b.txt (uploading new version of file-17.txt) + + file-18.07f245e02b.txt (uploading new version of file-18.txt) + + file-19.f0d69f705d.txt (uploading new version of file-19.txt) + Uploading 20 new assets... + Uploaded X% [X out of 20] + Uploaded X% [X out of 20] + Uploaded X% [X out of 20] + Uploaded 100% [20 out of 20]" `); + expect(std.warn).toMatchInlineSnapshot(`""`); + expect(std.err).toMatchInlineSnapshot(`""`); }); it("should error if the asset key is over 512 characters", async () => { @@ -2874,11 +2938,14 @@ addEventListener('fetch', event => {});` `"The asset path key \\"folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.3da0d0cd12.txt\\" exceeds the maximum key size limit of 512. See https://developers.cloudflare.com/workers/platform/limits#kv-limits\\","` ); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload..." + `); expect(std.out).toMatchInlineSnapshot(` - "Reading folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.txt... - - If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" - `); + " + If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] The asset path key \\"folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/folder/file.3da0d0cd12.txt\\" exceeds the maximum key size limit of 512. See https://developers.cloudflare.com/workers/platform/limits#kv-limits\\", @@ -2928,14 +2995,20 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + = file-1.2ca234f380.txt (already uploaded file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + - file-3.somehash.txt (removing as stale) + - file-4.anotherhash.txt (removing as stale) + Uploading 1 new asset... + Skipped uploading 1 existing asset. + Uploaded 100% [1 out of 1] + Removing 2 stale assets..." + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Skipping - already uploaded. - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - Deleting file-3.somehash.txt from the asset store... - Deleting file-4.anotherhash.txt from the asset store... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -2985,12 +3058,16 @@ addEventListener('fetch', event => {});` await runWrangler("publish"); process.chdir("../"); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]" + `); expect(std.out).toMatchInlineSnapshot(` - "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -3028,12 +3105,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -3072,12 +3150,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) @@ -3089,6 +3168,352 @@ addEventListener('fetch', event => {});` } `); }); + + it("should abort other bucket uploads if one bucket upload fails", async () => { + // Write 9 20MiB files, should end up with 3 buckets + const content = "X".repeat(20 * 1024 * 1024); + const assets = Array.from({ length: 9 }, (_, index) => ({ + filePath: `file-${index}.txt`, + content, + })); + + const kvNamespace = { + title: "__test-name-workers_sites_assets", + id: "__test-name-workers_sites_assets-id", + }; + writeWranglerToml({ + main: "./index.js", + site: { + bucket: "assets", + }, + }); + writeWorkerSource(); + writeAssets(assets); + mockUploadWorkerRequest(); + mockSubDomainRequest(); + mockListKVNamespacesRequest(kvNamespace); + mockKeyListRequest(kvNamespace.id, []); + + let requestCount = 0; + const bulkUrl = + "*/accounts/:accountId/storage/kv/namespaces/:namespaceId/bulk"; + msw.use( + rest.put(bulkUrl, async (req, res, ctx) => { + expect(req.params.accountId).toEqual("some-account-id"); + expect(req.params.namespaceId).toEqual(kvNamespace.id); + requestCount++; + return res( + ctx.status(500), + ctx.json( + createFetchResult([], false, [ + { code: 1000, message: "Whoops! Something went wrong!" }, + ]) + ) + ); + }) + ); + + await expect( + runWrangler("publish") + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"A request to the Cloudflare API (/accounts/some-account-id/storage/kv/namespaces/__test-name-workers_sites_assets-id/bulk) failed."` + ); + + expect(requestCount).toBeLessThan(3); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-0.f0d69f705d.txt (uploading new version of file-0.txt) + + file-1.f0d69f705d.txt (uploading new version of file-1.txt) + + file-2.f0d69f705d.txt (uploading new version of file-2.txt) + + file-3.f0d69f705d.txt (uploading new version of file-3.txt) + + file-4.f0d69f705d.txt (uploading new version of file-4.txt) + + file-5.f0d69f705d.txt (uploading new version of file-5.txt) + + file-6.f0d69f705d.txt (uploading new version of file-6.txt) + + file-7.f0d69f705d.txt (uploading new version of file-7.txt) + + file-8.f0d69f705d.txt (uploading new version of file-8.txt) + Uploading 9 new assets... + Upload failed, aborting..." + `); + }); + + describe("should truncate diff with over 100 assets unless debug log level set", () => { + beforeEach(() => { + const assets = Array.from({ length: 110 }, (_, index) => ({ + filePath: `file-${`${index}`.padStart(3, "0")}.txt`, + content: "X", + })); + + const kvNamespace = { + title: "__test-name-workers_sites_assets", + id: "__test-name-workers_sites_assets-id", + }; + writeWranglerToml({ + main: "./index.js", + site: { + bucket: "assets", + }, + }); + writeWorkerSource(); + writeAssets(assets); + mockUploadWorkerRequest(); + mockSubDomainRequest(); + mockListKVNamespacesRequest(kvNamespace); + mockKeyListRequest(kvNamespace.id, []); + mockUploadAssetsToKVRequest(kvNamespace.id); + }); + + it("default log level", async () => { + await runWrangler("publish"); + expect(std).toMatchInlineSnapshot(` + Object { + "debug": "", + "err": "", + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-000.010257e8bb.txt (uploading new version of file-000.txt) + + file-001.010257e8bb.txt (uploading new version of file-001.txt) + + file-002.010257e8bb.txt (uploading new version of file-002.txt) + + file-003.010257e8bb.txt (uploading new version of file-003.txt) + + file-004.010257e8bb.txt (uploading new version of file-004.txt) + + file-005.010257e8bb.txt (uploading new version of file-005.txt) + + file-006.010257e8bb.txt (uploading new version of file-006.txt) + + file-007.010257e8bb.txt (uploading new version of file-007.txt) + + file-008.010257e8bb.txt (uploading new version of file-008.txt) + + file-009.010257e8bb.txt (uploading new version of file-009.txt) + + file-010.010257e8bb.txt (uploading new version of file-010.txt) + + file-011.010257e8bb.txt (uploading new version of file-011.txt) + + file-012.010257e8bb.txt (uploading new version of file-012.txt) + + file-013.010257e8bb.txt (uploading new version of file-013.txt) + + file-014.010257e8bb.txt (uploading new version of file-014.txt) + + file-015.010257e8bb.txt (uploading new version of file-015.txt) + + file-016.010257e8bb.txt (uploading new version of file-016.txt) + + file-017.010257e8bb.txt (uploading new version of file-017.txt) + + file-018.010257e8bb.txt (uploading new version of file-018.txt) + + file-019.010257e8bb.txt (uploading new version of file-019.txt) + + file-020.010257e8bb.txt (uploading new version of file-020.txt) + + file-021.010257e8bb.txt (uploading new version of file-021.txt) + + file-022.010257e8bb.txt (uploading new version of file-022.txt) + + file-023.010257e8bb.txt (uploading new version of file-023.txt) + + file-024.010257e8bb.txt (uploading new version of file-024.txt) + + file-025.010257e8bb.txt (uploading new version of file-025.txt) + + file-026.010257e8bb.txt (uploading new version of file-026.txt) + + file-027.010257e8bb.txt (uploading new version of file-027.txt) + + file-028.010257e8bb.txt (uploading new version of file-028.txt) + + file-029.010257e8bb.txt (uploading new version of file-029.txt) + + file-030.010257e8bb.txt (uploading new version of file-030.txt) + + file-031.010257e8bb.txt (uploading new version of file-031.txt) + + file-032.010257e8bb.txt (uploading new version of file-032.txt) + + file-033.010257e8bb.txt (uploading new version of file-033.txt) + + file-034.010257e8bb.txt (uploading new version of file-034.txt) + + file-035.010257e8bb.txt (uploading new version of file-035.txt) + + file-036.010257e8bb.txt (uploading new version of file-036.txt) + + file-037.010257e8bb.txt (uploading new version of file-037.txt) + + file-038.010257e8bb.txt (uploading new version of file-038.txt) + + file-039.010257e8bb.txt (uploading new version of file-039.txt) + + file-040.010257e8bb.txt (uploading new version of file-040.txt) + + file-041.010257e8bb.txt (uploading new version of file-041.txt) + + file-042.010257e8bb.txt (uploading new version of file-042.txt) + + file-043.010257e8bb.txt (uploading new version of file-043.txt) + + file-044.010257e8bb.txt (uploading new version of file-044.txt) + + file-045.010257e8bb.txt (uploading new version of file-045.txt) + + file-046.010257e8bb.txt (uploading new version of file-046.txt) + + file-047.010257e8bb.txt (uploading new version of file-047.txt) + + file-048.010257e8bb.txt (uploading new version of file-048.txt) + + file-049.010257e8bb.txt (uploading new version of file-049.txt) + + file-050.010257e8bb.txt (uploading new version of file-050.txt) + + file-051.010257e8bb.txt (uploading new version of file-051.txt) + + file-052.010257e8bb.txt (uploading new version of file-052.txt) + + file-053.010257e8bb.txt (uploading new version of file-053.txt) + + file-054.010257e8bb.txt (uploading new version of file-054.txt) + + file-055.010257e8bb.txt (uploading new version of file-055.txt) + + file-056.010257e8bb.txt (uploading new version of file-056.txt) + + file-057.010257e8bb.txt (uploading new version of file-057.txt) + + file-058.010257e8bb.txt (uploading new version of file-058.txt) + + file-059.010257e8bb.txt (uploading new version of file-059.txt) + + file-060.010257e8bb.txt (uploading new version of file-060.txt) + + file-061.010257e8bb.txt (uploading new version of file-061.txt) + + file-062.010257e8bb.txt (uploading new version of file-062.txt) + + file-063.010257e8bb.txt (uploading new version of file-063.txt) + + file-064.010257e8bb.txt (uploading new version of file-064.txt) + + file-065.010257e8bb.txt (uploading new version of file-065.txt) + + file-066.010257e8bb.txt (uploading new version of file-066.txt) + + file-067.010257e8bb.txt (uploading new version of file-067.txt) + + file-068.010257e8bb.txt (uploading new version of file-068.txt) + + file-069.010257e8bb.txt (uploading new version of file-069.txt) + + file-070.010257e8bb.txt (uploading new version of file-070.txt) + + file-071.010257e8bb.txt (uploading new version of file-071.txt) + + file-072.010257e8bb.txt (uploading new version of file-072.txt) + + file-073.010257e8bb.txt (uploading new version of file-073.txt) + + file-074.010257e8bb.txt (uploading new version of file-074.txt) + + file-075.010257e8bb.txt (uploading new version of file-075.txt) + + file-076.010257e8bb.txt (uploading new version of file-076.txt) + + file-077.010257e8bb.txt (uploading new version of file-077.txt) + + file-078.010257e8bb.txt (uploading new version of file-078.txt) + + file-079.010257e8bb.txt (uploading new version of file-079.txt) + + file-080.010257e8bb.txt (uploading new version of file-080.txt) + + file-081.010257e8bb.txt (uploading new version of file-081.txt) + + file-082.010257e8bb.txt (uploading new version of file-082.txt) + + file-083.010257e8bb.txt (uploading new version of file-083.txt) + + file-084.010257e8bb.txt (uploading new version of file-084.txt) + + file-085.010257e8bb.txt (uploading new version of file-085.txt) + + file-086.010257e8bb.txt (uploading new version of file-086.txt) + + file-087.010257e8bb.txt (uploading new version of file-087.txt) + + file-088.010257e8bb.txt (uploading new version of file-088.txt) + + file-089.010257e8bb.txt (uploading new version of file-089.txt) + + file-090.010257e8bb.txt (uploading new version of file-090.txt) + + file-091.010257e8bb.txt (uploading new version of file-091.txt) + + file-092.010257e8bb.txt (uploading new version of file-092.txt) + + file-093.010257e8bb.txt (uploading new version of file-093.txt) + + file-094.010257e8bb.txt (uploading new version of file-094.txt) + + file-095.010257e8bb.txt (uploading new version of file-095.txt) + + file-096.010257e8bb.txt (uploading new version of file-096.txt) + + file-097.010257e8bb.txt (uploading new version of file-097.txt) + + file-098.010257e8bb.txt (uploading new version of file-098.txt) + + file-099.010257e8bb.txt (uploading new version of file-099.txt) + (truncating changed assets log, set \`WRANGLER_LOG=debug\` environment variable to see full diff) + Uploading 110 new assets... + Uploaded 100% [110 out of 110]", + "out": "↗️ Done syncing assets + Total Upload: xx KiB / gzip: xx KiB + Uploaded test-name (TIMINGS) + Published test-name (TIMINGS) + https://test-name.test-sub-domain.workers.dev + Current Deployment ID: Galaxy-Class", + "warn": "", + } + `); + }); + + it("debug log level", async () => { + logger.loggerLevel = "debug"; + await runWrangler("publish"); + + const diffRegexp = /^ [+=-]/; + const diff = std.debug + .split("\n") + .filter((line) => diffRegexp.test(line)) + .join("\n"); + expect(diff).toMatchInlineSnapshot(` + " + file-000.010257e8bb.txt (uploading new version of file-000.txt) + + file-001.010257e8bb.txt (uploading new version of file-001.txt) + + file-002.010257e8bb.txt (uploading new version of file-002.txt) + + file-003.010257e8bb.txt (uploading new version of file-003.txt) + + file-004.010257e8bb.txt (uploading new version of file-004.txt) + + file-005.010257e8bb.txt (uploading new version of file-005.txt) + + file-006.010257e8bb.txt (uploading new version of file-006.txt) + + file-007.010257e8bb.txt (uploading new version of file-007.txt) + + file-008.010257e8bb.txt (uploading new version of file-008.txt) + + file-009.010257e8bb.txt (uploading new version of file-009.txt) + + file-010.010257e8bb.txt (uploading new version of file-010.txt) + + file-011.010257e8bb.txt (uploading new version of file-011.txt) + + file-012.010257e8bb.txt (uploading new version of file-012.txt) + + file-013.010257e8bb.txt (uploading new version of file-013.txt) + + file-014.010257e8bb.txt (uploading new version of file-014.txt) + + file-015.010257e8bb.txt (uploading new version of file-015.txt) + + file-016.010257e8bb.txt (uploading new version of file-016.txt) + + file-017.010257e8bb.txt (uploading new version of file-017.txt) + + file-018.010257e8bb.txt (uploading new version of file-018.txt) + + file-019.010257e8bb.txt (uploading new version of file-019.txt) + + file-020.010257e8bb.txt (uploading new version of file-020.txt) + + file-021.010257e8bb.txt (uploading new version of file-021.txt) + + file-022.010257e8bb.txt (uploading new version of file-022.txt) + + file-023.010257e8bb.txt (uploading new version of file-023.txt) + + file-024.010257e8bb.txt (uploading new version of file-024.txt) + + file-025.010257e8bb.txt (uploading new version of file-025.txt) + + file-026.010257e8bb.txt (uploading new version of file-026.txt) + + file-027.010257e8bb.txt (uploading new version of file-027.txt) + + file-028.010257e8bb.txt (uploading new version of file-028.txt) + + file-029.010257e8bb.txt (uploading new version of file-029.txt) + + file-030.010257e8bb.txt (uploading new version of file-030.txt) + + file-031.010257e8bb.txt (uploading new version of file-031.txt) + + file-032.010257e8bb.txt (uploading new version of file-032.txt) + + file-033.010257e8bb.txt (uploading new version of file-033.txt) + + file-034.010257e8bb.txt (uploading new version of file-034.txt) + + file-035.010257e8bb.txt (uploading new version of file-035.txt) + + file-036.010257e8bb.txt (uploading new version of file-036.txt) + + file-037.010257e8bb.txt (uploading new version of file-037.txt) + + file-038.010257e8bb.txt (uploading new version of file-038.txt) + + file-039.010257e8bb.txt (uploading new version of file-039.txt) + + file-040.010257e8bb.txt (uploading new version of file-040.txt) + + file-041.010257e8bb.txt (uploading new version of file-041.txt) + + file-042.010257e8bb.txt (uploading new version of file-042.txt) + + file-043.010257e8bb.txt (uploading new version of file-043.txt) + + file-044.010257e8bb.txt (uploading new version of file-044.txt) + + file-045.010257e8bb.txt (uploading new version of file-045.txt) + + file-046.010257e8bb.txt (uploading new version of file-046.txt) + + file-047.010257e8bb.txt (uploading new version of file-047.txt) + + file-048.010257e8bb.txt (uploading new version of file-048.txt) + + file-049.010257e8bb.txt (uploading new version of file-049.txt) + + file-050.010257e8bb.txt (uploading new version of file-050.txt) + + file-051.010257e8bb.txt (uploading new version of file-051.txt) + + file-052.010257e8bb.txt (uploading new version of file-052.txt) + + file-053.010257e8bb.txt (uploading new version of file-053.txt) + + file-054.010257e8bb.txt (uploading new version of file-054.txt) + + file-055.010257e8bb.txt (uploading new version of file-055.txt) + + file-056.010257e8bb.txt (uploading new version of file-056.txt) + + file-057.010257e8bb.txt (uploading new version of file-057.txt) + + file-058.010257e8bb.txt (uploading new version of file-058.txt) + + file-059.010257e8bb.txt (uploading new version of file-059.txt) + + file-060.010257e8bb.txt (uploading new version of file-060.txt) + + file-061.010257e8bb.txt (uploading new version of file-061.txt) + + file-062.010257e8bb.txt (uploading new version of file-062.txt) + + file-063.010257e8bb.txt (uploading new version of file-063.txt) + + file-064.010257e8bb.txt (uploading new version of file-064.txt) + + file-065.010257e8bb.txt (uploading new version of file-065.txt) + + file-066.010257e8bb.txt (uploading new version of file-066.txt) + + file-067.010257e8bb.txt (uploading new version of file-067.txt) + + file-068.010257e8bb.txt (uploading new version of file-068.txt) + + file-069.010257e8bb.txt (uploading new version of file-069.txt) + + file-070.010257e8bb.txt (uploading new version of file-070.txt) + + file-071.010257e8bb.txt (uploading new version of file-071.txt) + + file-072.010257e8bb.txt (uploading new version of file-072.txt) + + file-073.010257e8bb.txt (uploading new version of file-073.txt) + + file-074.010257e8bb.txt (uploading new version of file-074.txt) + + file-075.010257e8bb.txt (uploading new version of file-075.txt) + + file-076.010257e8bb.txt (uploading new version of file-076.txt) + + file-077.010257e8bb.txt (uploading new version of file-077.txt) + + file-078.010257e8bb.txt (uploading new version of file-078.txt) + + file-079.010257e8bb.txt (uploading new version of file-079.txt) + + file-080.010257e8bb.txt (uploading new version of file-080.txt) + + file-081.010257e8bb.txt (uploading new version of file-081.txt) + + file-082.010257e8bb.txt (uploading new version of file-082.txt) + + file-083.010257e8bb.txt (uploading new version of file-083.txt) + + file-084.010257e8bb.txt (uploading new version of file-084.txt) + + file-085.010257e8bb.txt (uploading new version of file-085.txt) + + file-086.010257e8bb.txt (uploading new version of file-086.txt) + + file-087.010257e8bb.txt (uploading new version of file-087.txt) + + file-088.010257e8bb.txt (uploading new version of file-088.txt) + + file-089.010257e8bb.txt (uploading new version of file-089.txt) + + file-090.010257e8bb.txt (uploading new version of file-090.txt) + + file-091.010257e8bb.txt (uploading new version of file-091.txt) + + file-092.010257e8bb.txt (uploading new version of file-092.txt) + + file-093.010257e8bb.txt (uploading new version of file-093.txt) + + file-094.010257e8bb.txt (uploading new version of file-094.txt) + + file-095.010257e8bb.txt (uploading new version of file-095.txt) + + file-096.010257e8bb.txt (uploading new version of file-096.txt) + + file-097.010257e8bb.txt (uploading new version of file-097.txt) + + file-098.010257e8bb.txt (uploading new version of file-098.txt) + + file-099.010257e8bb.txt (uploading new version of file-099.txt) + + file-100.010257e8bb.txt (uploading new version of file-100.txt) + + file-101.010257e8bb.txt (uploading new version of file-101.txt) + + file-102.010257e8bb.txt (uploading new version of file-102.txt) + + file-103.010257e8bb.txt (uploading new version of file-103.txt) + + file-104.010257e8bb.txt (uploading new version of file-104.txt) + + file-105.010257e8bb.txt (uploading new version of file-105.txt) + + file-106.010257e8bb.txt (uploading new version of file-106.txt) + + file-107.010257e8bb.txt (uploading new version of file-107.txt) + + file-108.010257e8bb.txt (uploading new version of file-108.txt) + + file-109.010257e8bb.txt (uploading new version of file-109.txt)" + `); + expect(std.info).toMatchInlineSnapshot(` + "Fetching list of already uploaded assets... + Building list of assets to upload... + Uploading 110 new assets... + Uploaded 100% [110 out of 110]" + `); + }); + }); }); describe("workers_dev setting", () => { @@ -6656,12 +7081,13 @@ addEventListener('fetch', event => {});` Object { "debug": "", "err": "", - "info": "", - "out": "Reading file-1.txt... - Uploading as file-1.2ca234f380.txt... - Reading file-2.txt... - Uploading as file-2.5938485188.txt... - ↗️ Done syncing assets + "info": "Fetching list of already uploaded assets... + Building list of assets to upload... + + file-1.2ca234f380.txt (uploading new version of file-1.txt) + + file-2.5938485188.txt (uploading new version of file-2.txt) + Uploading 2 new assets... + Uploaded 100% [2 out of 2]", + "out": "↗️ Done syncing assets Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) Published test-name (TIMINGS) diff --git a/packages/wrangler/src/kv/helpers.ts b/packages/wrangler/src/kv/helpers.ts index 0f46fb126853..a1edb5a7b955 100644 --- a/packages/wrangler/src/kv/helpers.ts +++ b/packages/wrangler/src/kv/helpers.ts @@ -8,7 +8,7 @@ import type { Config } from "../config"; const API_MAX = 10000; // The const below are halved from the API's true capacity to help avoid // hammering it with large requests. -const BATCH_KEY_MAX = API_MAX / 2; +export const BATCH_KEY_MAX = API_MAX / 2; type KvArgs = { binding?: string; @@ -256,7 +256,7 @@ export async function deleteKVKeyValue( /** * Formatter for converting e.g. 5328 --> 5,328 */ -const formatNumber = new Intl.NumberFormat("en-US", { +export const formatNumber = new Intl.NumberFormat("en-US", { notation: "standard", }).format; @@ -279,7 +279,8 @@ export async function putKVBulkKeyValue( accountId: string, namespaceId: string, keyValues: KeyValue[], - quiet = false + quiet = false, + abortSignal?: AbortSignal ) { for (let index = 0; index < keyValues.length; index += BATCH_KEY_MAX) { if (!quiet && keyValues.length > BATCH_KEY_MAX) { @@ -292,7 +293,9 @@ export async function putKVBulkKeyValue( method: "PUT", body: JSON.stringify(keyValues.slice(index, index + BATCH_KEY_MAX)), headers: { "Content-Type": "application/json" }, - } + }, + undefined, + abortSignal ); } diff --git a/packages/wrangler/src/sites.ts b/packages/wrangler/src/sites.ts index 0ec3b2fb0804..d6170a873631 100644 --- a/packages/wrangler/src/sites.ts +++ b/packages/wrangler/src/sites.ts @@ -1,6 +1,7 @@ import assert from "node:assert"; import { readdir, readFile, stat } from "node:fs/promises"; import * as path from "node:path"; +import chalk from "chalk"; import ignore from "ignore"; import xxhash from "xxhash-wasm"; import { @@ -9,8 +10,10 @@ import { listKVNamespaces, putKVBulkKeyValue, deleteKVBulkKeyValue, + BATCH_KEY_MAX, + formatNumber, } from "./kv/helpers"; -import { logger } from "./logger"; +import { logger, LOGGER_LEVELS } from "./logger"; import type { Config } from "./config"; import type { KeyValue } from "./kv/helpers"; import type { XXHashAPI } from "xxhash-wasm"; @@ -92,6 +95,15 @@ async function createKVNamespaceIfNotAlreadyExisting( }; } +const MAX_DIFF_LINES = 100; +const MAX_BUCKET_SIZE = 98 * 1000 * 1000; +const MAX_BUCKET_KEYS = BATCH_KEY_MAX; +const MAX_BATCH_OPERATIONS = 5; + +function pluralise(count: number) { + return count === 1 ? "" : "s"; +} + /** * Upload the assets found within the `dirPath` directory to the sites assets KV namespace for * the worker given by `scriptName`. @@ -116,13 +128,13 @@ export async function syncAssets( if (siteAssets === undefined) { return { manifest: undefined, namespace: undefined }; } - if (dryRun) { logger.log("(Note: doing a dry run, not uploading or deleting anything.)"); return { manifest: undefined, namespace: undefined }; } assert(accountId, "Missing accountId"); + // Create assets namespace if it doesn't exist const title = `__${scriptName}-workers_sites_assets${ preview ? "_preview" : "" }`; @@ -131,55 +143,81 @@ export async function syncAssets( title, accountId ); - - // let's get all the keys in this namespace + // Get all existing keys in asset namespace + logger.info("Fetching list of already uploaded assets..."); const namespaceKeysResponse = await listKVNamespaceKeys(accountId, namespace); const namespaceKeys = new Set(namespaceKeysResponse.map((x) => x.name)); - const manifest: Record = {}; - - // A batch of uploads where each bucket has to be less than 98mb - const uploadBuckets: KeyValue[][] = []; - // The "live" bucket that we'll keep filling until it's just below 98mb - let uploadBucket: KeyValue[] = []; - // A size counter for the live bucket - let uploadBucketSize = 0; - - const include = createPatternMatcher(siteAssets.includePatterns, false); - const exclude = createPatternMatcher(siteAssets.excludePatterns, true); - const hasher = await xxhash(); - const assetDirectory = path.join( siteAssets.baseDirectory, siteAssets.assetDirectory ); + const include = createPatternMatcher(siteAssets.includePatterns, false); + const exclude = createPatternMatcher(siteAssets.excludePatterns, true); + const hasher = await xxhash(); + + // Find and validate all assets before we make any changes (can't store base64 + // contents in memory for upload as users may have *lots* of files, and we + // don't want to OOM: https://github.com/cloudflare/workers-sdk/issues/2223) + + const manifest: Record = {}; + type PathKey = [path: string, key: string]; + // A batch of uploads where each bucket has to be less than 100 MiB and + // contain less than 10,000 keys (although we limit to 98 MB and 5000 keys) + const uploadBuckets: PathKey[][] = []; + // The "live" bucket we'll keep filling until it's just below the size limit + let uploadBucket: PathKey[] = []; + // Current size of the live bucket in bytes (just base64 encoded values) + let uploadBucketSize = 0; + + let uploadCount = 0; + let skipCount = 0; + + // Always log the first MAX_DIFF_LINES lines, then require the debug log level + let diffCount = 0; + function logDiff(line: string) { + const level = logger.loggerLevel; + if (LOGGER_LEVELS[level] >= LOGGER_LEVELS.debug) { + // If we're logging as debug level, we want *all* diff lines to be logged + // at debug level, not just the first MAX_DIFF_LINES + logger.debug(line); + } else if (diffCount < MAX_DIFF_LINES) { + // Otherwise, log the first MAX_DIFF_LINES diffs at info level... + logger.info(line); + } else if (diffCount === MAX_DIFF_LINES) { + // ...and warn when we start to truncate it + const msg = + " (truncating changed assets log, set `WRANGLER_LOG=debug` environment variable to see full diff)"; + logger.info(chalk.dim(msg)); + } + diffCount++; + } + + logger.info("Building list of assets to upload..."); for await (const absAssetFile of getFilesInFolder(assetDirectory)) { const assetFile = path.relative(assetDirectory, absAssetFile); - if (!include(assetFile)) { - continue; - } - if (exclude(assetFile)) { - continue; - } + if (!include(assetFile) || exclude(assetFile)) continue; - logger.log(`Reading ${assetFile}...`); const content = await readFile(absAssetFile, "base64"); - await validateAssetSize(absAssetFile, assetFile); - // while KV accepts files that are 25 MiB **before** b64 encoding + // While KV accepts files that are 25 MiB **before** b64 encoding // the overall bucket size must be below 100 MB **after** b64 encoding - const assetSize = Buffer.from(content).length; + const assetSize = Buffer.byteLength(content); + await validateAssetSize(absAssetFile, assetFile); const assetKey = hashAsset(hasher, assetFile, content); validateAssetKey(assetKey); - // now put each of the files into kv if (!namespaceKeys.has(assetKey)) { - logger.log(`Uploading as ${assetKey}...`); - - // Check if adding this asset to the bucket would - // push it over the 98 MiB limit KV bulk API limit - if (uploadBucketSize + assetSize > 98 * 1000 * 1000) { - // If so, move the current bucket into the batch, - // and reset the counter/bucket + logDiff( + chalk.green(` + ${assetKey} (uploading new version of ${assetFile})`) + ); + + // Check if adding this asset to the bucket would push it over the KV + // bulk API limits + if ( + uploadBucketSize + assetSize > MAX_BUCKET_SIZE || + uploadBucket.length + 1 > MAX_BUCKET_KEYS + ) { + // If so, record the current bucket and reset it uploadBuckets.push(uploadBucket); uploadBucketSize = 0; uploadBucket = []; @@ -187,13 +225,11 @@ export async function syncAssets( // Update the bucket and the size counter uploadBucketSize += assetSize; - uploadBucket.push({ - key: assetKey, - value: content, - base64: true, - }); + uploadBucket.push([absAssetFile, assetKey]); + uploadCount++; } else { - logger.log(`Skipping - already uploaded.`); + logDiff(chalk.dim(` = ${assetKey} (already uploaded ${assetFile})`)); + skipCount++; } // Remove the key from the set so we know what we've already uploaded @@ -203,23 +239,99 @@ export async function syncAssets( const manifestKey = urlSafe(path.relative(assetDirectory, absAssetFile)); manifest[manifestKey] = assetKey; } + // Add the last (potentially only or empty) bucket to the batch + if (uploadBucket.length > 0) uploadBuckets.push(uploadBucket); - // Add the last (potentially only) bucket to the batch - uploadBuckets.push(uploadBucket); - - // keys now contains all the files we're deleting for (const key of namespaceKeys) { - logger.log(`Deleting ${key} from the asset store...`); + logDiff(chalk.red(` - ${key} (removing as stale)`)); + } + + // Upload new assets, with 5 concurrent uploaders + if (uploadCount > 0) { + const s = pluralise(uploadCount); + logger.info(`Uploading ${formatNumber(uploadCount)} new asset${s}...`); + } + if (skipCount > 0) { + const s = pluralise(skipCount); + logger.info( + `Skipped uploading ${formatNumber(skipCount)} existing asset${s}.` + ); } + let uploadedCount = 0; + const controller = new AbortController(); + const uploaders = Array.from(Array(MAX_BATCH_OPERATIONS)).map(async () => { + while (!controller.signal.aborted) { + // Get the next bucket to upload. If there is none, stop this uploader. + // JavaScript is single(ish)-threaded, so we don't need to worry about + // parallel access here. + const nextBucket = uploadBuckets.shift(); + if (nextBucket === undefined) break; + + // Read all files in the bucket as base64 + // TODO(perf): consider streaming the bulk upload body, rather than + // buffering all base64 contents then JSON-stringifying. This probably + // doesn't matter *too* much: we know buckets will be about 100MB, so + // with 5 uploaders, we could load about 500MB into memory (+ extra + // object keys/tags/copies/etc). + const bucket: KeyValue[] = []; + for (const [absAssetFile, assetKey] of nextBucket) { + bucket.push({ + key: assetKey, + value: await readFile(absAssetFile, "base64"), + base64: true, + }); + if (controller.signal.aborted) break; + } - // upload each bucket in parallel - const bucketsToPut = []; - for (const bucket of uploadBuckets) { - bucketsToPut.push(putKVBulkKeyValue(accountId, namespace, bucket)); + // Upload the bucket to the KV namespace, suppressing logs, we do our own + try { + await putKVBulkKeyValue( + accountId, + namespace, + bucket, + /* quiet */ true, + controller.signal + ); + } catch (e) { + // https://developer.mozilla.org/en-US/docs/Web/API/DOMException#error_names + // https://github.com/nodejs/undici/blob/a3efc9814447001a43a976f1c64adc41995df7e3/lib/core/errors.js#L89 + if ( + typeof e === "object" && + e !== null && + "name" in e && + // @ts-expect-error `e.name` should be typed `unknown`, fixed in + // TypeScript 4.9 + e.name === "AbortError" + ) { + break; + } + throw e; + } + uploadedCount += nextBucket.length; + const percent = Math.floor((100 * uploadedCount) / uploadCount); + logger.info( + `Uploaded ${percent}% [${formatNumber( + uploadedCount + )} out of ${formatNumber(uploadCount)}]` + ); + } + }); + try { + // Wait for all uploaders to complete, or one to fail + await Promise.all(uploaders); + } catch (e) { + // If any uploader fails, abort the others + logger.info(`Upload failed, aborting...`); + controller.abort(); + throw e; } - await Promise.all(bucketsToPut); - // then delete all the assets that aren't used anymore + // Delete stale assets + const deleteCount = namespaceKeys.size; + if (deleteCount > 0) { + const s = pluralise(deleteCount); + logger.info(`Removing ${formatNumber(deleteCount)} stale asset${s}...`); + } await deleteKVBulkKeyValue(accountId, namespace, Array.from(namespaceKeys)); logger.log("↗️ Done syncing assets"); From 2540e3291ca42eb5d6fe6040686e029214768b26 Mon Sep 17 00:00:00 2001 From: Joshua Johnson Date: Mon, 1 May 2023 10:55:20 -0500 Subject: [PATCH 02/25] Fix R2 bucket delete test and increase timeout on publish test (#3117) --- packages/wrangler/e2e/publish.test.ts | 2 +- packages/wrangler/e2e/r2.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/wrangler/e2e/publish.test.ts b/packages/wrangler/e2e/publish.test.ts index 0e3daafa31f3..5f63efc92774 100644 --- a/packages/wrangler/e2e/publish.test.ts +++ b/packages/wrangler/e2e/publish.test.ts @@ -74,7 +74,7 @@ describe("publish", async () => { expect(stderr).toMatchInlineSnapshot('""'); workersDev = matchWorkersDev(rawStdout); - await setTimeout(2_000); + await setTimeout(5_000); await expect( fetch(`https://${workerName}.${workersDev}`).then((r) => r.text()) ).resolves.toMatchInlineSnapshot('"Hello World!"'); diff --git a/packages/wrangler/e2e/r2.test.ts b/packages/wrangler/e2e/r2.test.ts index 347cec5f7999..8011eaccfe31 100644 --- a/packages/wrangler/e2e/r2.test.ts +++ b/packages/wrangler/e2e/r2.test.ts @@ -103,6 +103,7 @@ describe("r2", async () => { }); const { stdout, stderr } = await runIn(root, { [bucketName]: "wrangler-smoke-test-bucket", + [process.env.CLOUDFLARE_ACCOUNT_ID as string]: "CLOUDFLARE_ACCOUNT_ID", })` exits(1) { $ ${RUN} r2 object put ${`${bucketName}/testr2`} --file test-r2.txt --content-type text/html @@ -114,7 +115,7 @@ describe("r2", async () => { If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" `); expect(stderr).toMatchInlineSnapshot(` - "X [ERROR] Failed to fetch /accounts/c1813c2bcbf20b513d80d455cb110774/r2/buckets/wrangler-smoke-test-bucket/objects/testr2 - 404: Not Found); + "X [ERROR] Failed to fetch /accounts/CLOUDFLARE_ACCOUNT_ID/r2/buckets/wrangler-smoke-test-bucket/objects/testr2 - 404: Not Found); " `); From 53a61f33796d9f8131f98557f734555c53ad642d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 13:26:23 -0400 Subject: [PATCH 03/25] Version Packages (#3105) Co-authored-by: github-actions[bot] --- .changeset/clever-radios-cover.md | 10 ---------- package-lock.json | 2 +- packages/wrangler/CHANGELOG.md | 11 +++++++++++ packages/wrangler/package.json | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 .changeset/clever-radios-cover.md diff --git a/.changeset/clever-radios-cover.md b/.changeset/clever-radios-cover.md deleted file mode 100644 index 2a5c81f62300..000000000000 --- a/.changeset/clever-radios-cover.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"wrangler": minor ---- - -fix: improve Workers Sites asset upload reliability - -- Wrangler no longer buffers all assets into memory before uploading. This should prevent out-of-memory errors when publishing sites with many large files. -- Wrangler now limits the number of in-flight asset upload requests to 5, fixing the `Too many bulk operations already in progress` error. -- Wrangler now correctly logs upload progress. Previously, the reported percentage was per upload request group, not across all assets. -- Wrangler no longer logs all assets to the console by default. Instead, it will just log the first 100. The rest can be shown by setting the `WRANGLER_LOG=debug` environment variable. A splash of colour has also been added. diff --git a/package-lock.json b/package-lock.json index 93a318fe388a..6cc9e73eeed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45450,7 +45450,7 @@ "dev": true }, "packages/wrangler": { - "version": "2.17.0", + "version": "2.18.0", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index 99444a8860c4..8c42c6a0cb4f 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,16 @@ # wrangler +## 2.18.0 + +### Minor Changes + +- [#3098](https://github.com/cloudflare/workers-sdk/pull/3098) [`8818f551`](https://github.com/cloudflare/workers-sdk/commit/8818f5516ca909cc941deb953b6359030a8c0301) Thanks [@mrbbot](https://github.com/mrbbot)! - fix: improve Workers Sites asset upload reliability + + - Wrangler no longer buffers all assets into memory before uploading. This should prevent out-of-memory errors when publishing sites with many large files. + - Wrangler now limits the number of in-flight asset upload requests to 5, fixing the `Too many bulk operations already in progress` error. + - Wrangler now correctly logs upload progress. Previously, the reported percentage was per upload request group, not across all assets. + - Wrangler no longer logs all assets to the console by default. Instead, it will just log the first 100. The rest can be shown by setting the `WRANGLER_LOG=debug` environment variable. A splash of colour has also been added. + ## 2.17.0 ### Minor Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 594b410b6f79..c530df75756b 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "2.17.0", + "version": "2.18.0", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "wrangler", From c32f514ca40e8b13dc9e86fdc76577b9adeb70f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Tue, 2 May 2023 17:11:14 +0100 Subject: [PATCH 04/25] [wrangler] feat: Support for Constellation AI (#3091) * [wrangler] feat: Support for Constellation AI Added initial commands to manage Constallation AI projects/models from Wrangler. * test: disable colors on tables when testing --------- Co-authored-by: Pete Bacon Darwin --- .changeset/tall-geckos-try.md | 5 + .../src/__tests__/constellation.test.ts | 371 ++++++++++++++++++ packages/wrangler/src/__tests__/index.test.ts | 2 + .../src/__tests__/mtls-certificates.test.ts | 1 + .../wrangler/src/__tests__/publish.test.ts | 4 +- packages/wrangler/src/__tests__/user.test.ts | 2 +- .../src/constellation/createProject.tsx | 51 +++ .../src/constellation/deleteProject.ts | 51 +++ .../src/constellation/deleteProjectModel.ts | 68 ++++ packages/wrangler/src/constellation/index.ts | 75 ++++ .../src/constellation/listCatalog.tsx | 35 ++ .../wrangler/src/constellation/listModel.tsx | 41 ++ .../src/constellation/listProject.tsx | 28 ++ .../src/constellation/listRuntime.tsx | 28 ++ .../wrangler/src/constellation/options.ts | 17 + packages/wrangler/src/constellation/types.ts | 17 + .../src/constellation/uploadModel.tsx | 64 +++ packages/wrangler/src/constellation/utils.ts | 90 +++++ .../src/environment-variables/factory.ts | 3 +- packages/wrangler/src/index.ts | 10 + packages/wrangler/src/logger.ts | 6 +- packages/wrangler/src/user/user.ts | 1 + 22 files changed, 965 insertions(+), 5 deletions(-) create mode 100644 .changeset/tall-geckos-try.md create mode 100644 packages/wrangler/src/__tests__/constellation.test.ts create mode 100644 packages/wrangler/src/constellation/createProject.tsx create mode 100644 packages/wrangler/src/constellation/deleteProject.ts create mode 100644 packages/wrangler/src/constellation/deleteProjectModel.ts create mode 100644 packages/wrangler/src/constellation/index.ts create mode 100644 packages/wrangler/src/constellation/listCatalog.tsx create mode 100644 packages/wrangler/src/constellation/listModel.tsx create mode 100644 packages/wrangler/src/constellation/listProject.tsx create mode 100644 packages/wrangler/src/constellation/listRuntime.tsx create mode 100644 packages/wrangler/src/constellation/options.ts create mode 100644 packages/wrangler/src/constellation/types.ts create mode 100644 packages/wrangler/src/constellation/uploadModel.tsx create mode 100644 packages/wrangler/src/constellation/utils.ts diff --git a/.changeset/tall-geckos-try.md b/.changeset/tall-geckos-try.md new file mode 100644 index 000000000000..17ae0d2c14a0 --- /dev/null +++ b/.changeset/tall-geckos-try.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Added initial commands for integrating with Constellation AI. diff --git a/packages/wrangler/src/__tests__/constellation.test.ts b/packages/wrangler/src/__tests__/constellation.test.ts new file mode 100644 index 000000000000..8c71b2cd6043 --- /dev/null +++ b/packages/wrangler/src/__tests__/constellation.test.ts @@ -0,0 +1,371 @@ +import * as fs from "node:fs"; +import { rest } from "msw"; +import { endEventLoop } from "./helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { createFetchResult, msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +describe("constellation help", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + it("should show help when no argument is passed", async () => { + await runWrangler("constellation"); + await endEventLoop(); + + expect(std.out).toMatchInlineSnapshot(` + "wrangler constellation + + 🤖 Interact with Constellation AI models + + Commands: + wrangler constellation project Manage your projects + wrangler constellation model Manage your models + wrangler constellation catalog Check the curated model catalog + wrangler constellation runtime Check the suported runtimes + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); + + it("should show help when an invalid argument is passed", async () => { + await expect(() => runWrangler("constellation asdf")).rejects.toThrow( + "Unknown argument: asdf" + ); + + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Unknown argument: asdf + + " + `); + expect(std.out).toMatchInlineSnapshot(` + " + wrangler constellation + + 🤖 Interact with Constellation AI models + + Commands: + wrangler constellation project Manage your projects + wrangler constellation model Manage your models + wrangler constellation catalog Check the curated model catalog + wrangler constellation runtime Check the suported runtimes + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); +}); + +describe("constellation commands", () => { + mockAccountId(); + mockApiToken(); + runInTempDir(); + const { setIsTTY } = useMockIsTTY(); + + const std = mockConsoleMethods(); + + beforeEach(() => { + // @ts-expect-error we're using a very simple setTimeout mock here + jest.spyOn(global, "setTimeout").mockImplementation((fn, _period) => { + setImmediate(fn); + }); + setIsTTY(true); + }); + + afterEach(() => { + clearDialogs(); + }); + + it("should handle project creation", async () => { + mockConstellationRequest(); + await runWrangler("constellation project create new_project ONNX"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ✅ Successfully created Project \\"new_project3\\"!" + `); + }); + + it("should handle project listing", async () => { + mockConstellationRequest(); + await runWrangler("constellation project list"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ┌──────────────────────────────────────┬──────────────┬─────────┬─────────────────────────────┐ + │ id │ name │ runtime │ created_at │ + ├──────────────────────────────────────┼──────────────┼─────────┼─────────────────────────────┤ + │ 4806cdcf-9aa7-4fa2-b6a1-77fe9e196680 │ new_project3 │ ONNX │ 2023-04-28T13:25:58.513105Z │ + └──────────────────────────────────────┴──────────────┴─────────┴─────────────────────────────┘" + `); + }); + + it("should handle project deletion", async () => { + mockConstellationRequest(); + mockConfirm({ + text: "Ok to proceed?", + result: true, + }); + + await runWrangler("constellation project delete new_project3"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + About to delete Project 'new_project3' (4806cdcf-9aa7-4fa2-b6a1-77fe9e196680). + Deleting... + Deleted 'new_project3' successfully." + `); + }); + + it("should handle catalog list", async () => { + mockConstellationRequest(); + await runWrangler("constellation catalog list"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ┌──────────────────────────────────────┬──────────────────────┬─────────────────┬───────────────┐ + │ project_id │ project_name │ project_runtime │ models │ + ├──────────────────────────────────────┼──────────────────────┼─────────────────┼───────────────┤ + │ b162a29d-0a6d-4155-bedf-54a01fc8d0ef │ image-classification │ ONNX │ squeezenet1_1 │ + └──────────────────────────────────────┴──────────────────────┴─────────────────┴───────────────┘" + `); + }); + + it("should handle runtime list", async () => { + mockConstellationRequest(); + await runWrangler("constellation runtime list"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ┌─────────┐ + │ name │ + ├─────────┤ + │ ONNX │ + ├─────────┤ + │ XGBoost │ + └─────────┘" + `); + }); + + it("should handle model upload", async () => { + mockConstellationRequest(); + await fs.promises.writeFile("model.onnx", `model`); + await runWrangler( + "constellation model upload new_project3 model2 model.onnx" + ); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ✅ Successfully uploaded Model \\"model2\\"!" + `); + }); + + it("should handle model list", async () => { + mockConstellationRequest(); + await runWrangler("constellation model list new_project3"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + ┌──────────────────────────────────────┬──────────────────────────────────────┬────────┬─────────────┬─────────────────────────────┐ + │ id │ project_id │ name │ description │ created_at │ + ├──────────────────────────────────────┼──────────────────────────────────────┼────────┼─────────────┼─────────────────────────────┤ + │ 450bb086-3c09-4991-a0cc-eed48c504ae0 │ 9d478427-dea6-4988-9b16-f6f8888d974c │ model1 │ │ 2023-04-28T11:15:14.806217Z │ + ├──────────────────────────────────────┼──────────────────────────────────────┼────────┼─────────────┼─────────────────────────────┤ + │ 2dd35b4e-0c7a-4c7a-a9e2-e33c0e17bc02 │ 9d478427-dea6-4988-9b16-f6f8888d974c │ model2 │ │ 2023-04-28T13:50:37.494090Z │ + └──────────────────────────────────────┴──────────────────────────────────────┴────────┴─────────────┴─────────────────────────────┘" + `); + }); + + it("should handle model deletion", async () => { + mockConstellationRequest(); + mockConfirm({ + text: "Ok to proceed?", + result: true, + }); + + await runWrangler("constellation model delete new_project3 model2"); + expect(std.out).toMatchInlineSnapshot(` + "-------------------- + 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose + 🚧 To give feedback, visit https://discord.gg/cloudflaredev + -------------------- + + About to delete Model 'model2' (2dd35b4e-0c7a-4c7a-a9e2-e33c0e17bc02). + Deleting... + Deleted 'model2' successfully." + `); + }); +}); + +/** Create a mock handler for Constellation API */ +function mockConstellationRequest() { + msw.use( + rest.get("*/accounts/:accountId/constellation/project", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + id: "4806cdcf-9aa7-4fa2-b6a1-77fe9e196680", + name: "new_project3", + runtime: "ONNX", + created_at: "2023-04-28T13:25:58.513105Z", + }, + ], + true + ) + ) + ); + }), + rest.post( + "*/accounts/:accountId/constellation/project", + (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + { + id: "4806cdcf-9aa7-4fa2-b6a1-77fe9e196680", + name: "new_project3", + runtime: "ONNX", + created_at: "2023-04-28T13:25:58.513105Z", + }, + true + ) + ) + ); + } + ), + rest.delete( + "*/accounts/:accountId/constellation/project/4806cdcf-9aa7-4fa2-b6a1-77fe9e196680", + (req, res, ctx) => { + return res.once(ctx.json(createFetchResult(null, true))); + } + ), + rest.get("*/accounts/:accountId/constellation/catalog", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + project: { + id: "b162a29d-0a6d-4155-bedf-54a01fc8d0ef", + name: "image-classification", + runtime: "ONNX", + created_at: "2023-04-27T18:55:38.417187Z", + }, + models: [ + { + id: "edb202d3-f4ac-43ab-8762-3ae6b43c4c57", + project_id: "b162a29d-0a6d-4155-bedf-54a01fc8d0ef", + name: "squeezenet1_1", + description: null, + created_at: "2023-04-27T18:56:15.305087Z", + }, + ], + }, + ], + true + ) + ) + ); + }), + rest.get("*/accounts/:accountId/constellation/runtime", (req, res, ctx) => { + return res.once(ctx.json(createFetchResult(["ONNX", "XGBoost"], true))); + }), + rest.post( + "*/accounts/:accountId/constellation/project/4806cdcf-9aa7-4fa2-b6a1-77fe9e196680/model", + (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + { + id: "2dd35b4e-0c7a-4c7a-a9e2-e33c0e17bc02", + project_id: "4806cdcf-9aa7-4fa2-b6a1-77fe9e196680", + name: "model2", + description: null, + created_at: "2023-04-28T13:50:37.494090Z", + }, + true + ) + ) + ); + } + ), + rest.get( + "*/accounts/:accountId/constellation/project/4806cdcf-9aa7-4fa2-b6a1-77fe9e196680/model", + (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + id: "450bb086-3c09-4991-a0cc-eed48c504ae0", + project_id: "9d478427-dea6-4988-9b16-f6f8888d974c", + name: "model1", + description: null, + created_at: "2023-04-28T11:15:14.806217Z", + }, + { + id: "2dd35b4e-0c7a-4c7a-a9e2-e33c0e17bc02", + project_id: "9d478427-dea6-4988-9b16-f6f8888d974c", + name: "model2", + description: null, + created_at: "2023-04-28T13:50:37.494090Z", + }, + ], + true + ) + ) + ); + } + ), + rest.delete( + "*/accounts/:accountId/constellation/project/4806cdcf-9aa7-4fa2-b6a1-77fe9e196680/model/2dd35b4e-0c7a-4c7a-a9e2-e33c0e17bc02", + (req, res, ctx) => { + return res.once(ctx.json(createFetchResult(null, true))); + } + ) + ); +} diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 0f3807385293..51985eaaa37b 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -49,6 +49,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler constellation 🤖 Interact with Constellation AI models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare @@ -102,6 +103,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler constellation 🤖 Interact with Constellation AI models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare diff --git a/packages/wrangler/src/__tests__/mtls-certificates.test.ts b/packages/wrangler/src/__tests__/mtls-certificates.test.ts index bd61509d5e7c..81cb974531ae 100644 --- a/packages/wrangler/src/__tests__/mtls-certificates.test.ts +++ b/packages/wrangler/src/__tests__/mtls-certificates.test.ts @@ -379,6 +379,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler constellation 🤖 Interact with Constellation AI models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare diff --git a/packages/wrangler/src/__tests__/publish.test.ts b/packages/wrangler/src/__tests__/publish.test.ts index 5886ce7771d2..04b4f0438dd5 100644 --- a/packages/wrangler/src/__tests__/publish.test.ts +++ b/packages/wrangler/src/__tests__/publish.test.ts @@ -140,7 +140,7 @@ describe("publish", () => { expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) @@ -180,7 +180,7 @@ describe("publish", () => { expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index 740bb9b8dfbe..15efbc682689 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -60,7 +60,7 @@ describe("User", () => { expect(counter).toBe(1); expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ diff --git a/packages/wrangler/src/constellation/createProject.tsx b/packages/wrangler/src/constellation/createProject.tsx new file mode 100644 index 000000000000..e60588abfed7 --- /dev/null +++ b/packages/wrangler/src/constellation/createProject.tsx @@ -0,0 +1,51 @@ +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { takeName } from "./options"; +import { constellationBetaWarning } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { Project } from "./types"; + +export function options(yargs: CommonYargsArgv) { + return takeName(yargs) + .positional("runtime", { + describe: "The name of the runtime to use", + type: "string", + demandOption: true, + }) + .epilogue(constellationBetaWarning); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ name, runtime, config }): Promise => { + const accountId = await requireAuth(config); + + logger.log(constellationBetaWarning); + + let proj: Project; + try { + proj = await fetchResult(`/accounts/${accountId}/constellation/project`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name, + runtime, + }), + }); + } catch (e) { + if ((e as { code: number }).code === 7409) { + throw new Error("A project with that name already exists"); + } + throw e; + } + + logger.log(`✅ Successfully created Project "${proj.name}"!`); + } +); diff --git a/packages/wrangler/src/constellation/deleteProject.ts b/packages/wrangler/src/constellation/deleteProject.ts new file mode 100644 index 000000000000..8fdbedf8ba4e --- /dev/null +++ b/packages/wrangler/src/constellation/deleteProject.ts @@ -0,0 +1,51 @@ +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { takeName } from "./options"; +import { constellationBetaWarning, getProjectByName } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return takeName(yargs) + .option("force", { + describe: "Skip confirmation", + type: "boolean", + alias: "f", + default: false, + }) + .epilogue(constellationBetaWarning); +} +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ name, force, config }): Promise => { + const accountId = await requireAuth(config); + logger.log(constellationBetaWarning); + + const proj = await getProjectByName(config, accountId, name); + + logger.log(`About to delete Project '${name}' (${proj.id}).`); + if (!force) { + const response = await confirm(`Ok to proceed?`); + if (!response) { + logger.log(`Not deleting.`); + return; + } + + logger.log("Deleting..."); + } + + await fetchResult( + `/accounts/${accountId}/constellation/project/${proj.id}`, + { + method: "DELETE", + } + ); + + logger.log(`Deleted '${name}' successfully.`); + } +); diff --git a/packages/wrangler/src/constellation/deleteProjectModel.ts b/packages/wrangler/src/constellation/deleteProjectModel.ts new file mode 100644 index 000000000000..948de8f2a7f9 --- /dev/null +++ b/packages/wrangler/src/constellation/deleteProjectModel.ts @@ -0,0 +1,68 @@ +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { takeName } from "./options"; +import { + constellationBetaWarning, + getProjectByName, + getProjectModelByName, +} from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { Project, Model } from "./types"; + +export function options(yargs: CommonYargsArgv) { + return takeName(yargs) + .positional("modelName", { + describe: "The name of the uploaded model", + type: "string", + demandOption: true, + }) + .option("force", { + describe: "Skip confirmation", + type: "boolean", + alias: "f", + default: false, + }) + .epilogue(constellationBetaWarning); +} +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ name, modelName, force, config }): Promise => { + const accountId = await requireAuth(config); + logger.log(constellationBetaWarning); + + const proj: Project = await getProjectByName(config, accountId, name); + + const model: Model = await getProjectModelByName( + config, + accountId, + proj, + modelName + ); + + logger.log(`About to delete Model '${modelName}' (${model.id}).`); + if (!force) { + const response = await confirm(`Ok to proceed?`); + if (!response) { + logger.log(`Not deleting.`); + return; + } + + logger.log("Deleting..."); + } + + await fetchResult( + `/accounts/${accountId}/constellation/project/${proj.id}/model/${model.id}`, + { + method: "DELETE", + } + ); + + logger.log(`Deleted '${modelName}' successfully.`); + } +); diff --git a/packages/wrangler/src/constellation/index.ts b/packages/wrangler/src/constellation/index.ts new file mode 100644 index 000000000000..dcd4a8b6fb9a --- /dev/null +++ b/packages/wrangler/src/constellation/index.ts @@ -0,0 +1,75 @@ +import * as CreateProject from "./createProject"; +import * as DeleteProject from "./deleteProject"; +import * as DeleteProjectModel from "./deleteProjectModel"; +import * as ListCatalog from "./listCatalog"; +import * as ListModel from "./listModel"; +import * as ListProject from "./listProject"; +import * as ListRuntime from "./listRuntime"; +import * as UploadModel from "./uploadModel"; +import type { CommonYargsArgv } from "../yargs-types"; + +export function constellation(yargs: CommonYargsArgv) { + return yargs + .command("project", "Manage your projects", (constProjYargs) => { + return constProjYargs + .command( + "list", + "List your projects", + ListProject.options, + ListProject.handler + ) + .command( + "create ", + "Create project", + CreateProject.options, + CreateProject.handler + ) + .command( + "delete ", + "Delete project", + DeleteProject.options, + DeleteProject.handler + ); + }) + .command("model", "Manage your models", (constModelYargs) => { + return constModelYargs + .command( + "upload ", + "Upload a model for an existing project", + UploadModel.options, + UploadModel.handler + ) + .command( + "list ", + "List models of a project", + ListModel.options, + ListModel.handler + ) + .command( + "delete ", + "Delete a model of a project", + DeleteProjectModel.options, + DeleteProjectModel.handler + ); + }) + .command( + "catalog", + "Check the curated model catalog", + (constCatalogYargs) => { + return constCatalogYargs.command( + "list", + "List catalog models", + ListCatalog.options, + ListCatalog.handler + ); + } + ) + .command("runtime", "Check the suported runtimes", (constRuntimeYargs) => { + return constRuntimeYargs.command( + "list", + "List suported runtimes", + ListRuntime.options, + ListRuntime.handler + ); + }); +} diff --git a/packages/wrangler/src/constellation/listCatalog.tsx b/packages/wrangler/src/constellation/listCatalog.tsx new file mode 100644 index 000000000000..9ad72d11ea9a --- /dev/null +++ b/packages/wrangler/src/constellation/listCatalog.tsx @@ -0,0 +1,35 @@ +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { asJson } from "./options"; +import { constellationBetaWarning, listCatalogEntries } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return asJson(yargs).epilogue(constellationBetaWarning); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ json, config }): Promise => { + const accountId = await requireAuth(config); + const entries = await listCatalogEntries(accountId); + + if (json) { + logger.log(JSON.stringify(entries, null, 2)); + } else { + logger.log(constellationBetaWarning); + logger.table( + entries.map((entry) => ({ + project_id: entry.project.id, + project_name: entry.project.name, + project_runtime: entry.project.runtime, + models: entry.models.map((model) => model.name).join(","), + })) + ); + } + } +); diff --git a/packages/wrangler/src/constellation/listModel.tsx b/packages/wrangler/src/constellation/listModel.tsx new file mode 100644 index 000000000000..2214fe59a54d --- /dev/null +++ b/packages/wrangler/src/constellation/listModel.tsx @@ -0,0 +1,41 @@ +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { takeName } from "./options"; +import { + constellationBetaWarning, + getProjectByName, + listModels, +} from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { Project } from "./types"; + +export function options(yargs: CommonYargsArgv) { + return takeName(yargs) + .option("json", { + describe: "return output as clean JSON", + type: "boolean", + default: false, + }) + .epilogue(constellationBetaWarning); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ name, json, config }): Promise => { + const accountId = await requireAuth(config); + const proj: Project = await getProjectByName(config, accountId, name); + + const models = await listModels(accountId, proj); + + if (json) { + logger.log(JSON.stringify(models, null, 2)); + } else { + logger.log(constellationBetaWarning); + logger.table(models); + } + } +); diff --git a/packages/wrangler/src/constellation/listProject.tsx b/packages/wrangler/src/constellation/listProject.tsx new file mode 100644 index 000000000000..d5f216abc563 --- /dev/null +++ b/packages/wrangler/src/constellation/listProject.tsx @@ -0,0 +1,28 @@ +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { asJson } from "./options"; +import { constellationBetaWarning, listProjects } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return asJson(yargs).epilogue(constellationBetaWarning); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ json, config }): Promise => { + const accountId = await requireAuth(config); + const projs = await listProjects(accountId); + + if (json) { + logger.log(JSON.stringify(projs, null, 2)); + } else { + logger.log(constellationBetaWarning); + logger.table(projs); + } + } +); diff --git a/packages/wrangler/src/constellation/listRuntime.tsx b/packages/wrangler/src/constellation/listRuntime.tsx new file mode 100644 index 000000000000..1be3a07a177f --- /dev/null +++ b/packages/wrangler/src/constellation/listRuntime.tsx @@ -0,0 +1,28 @@ +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { asJson } from "./options"; +import { constellationBetaWarning, listRuntimes } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return asJson(yargs).epilogue(constellationBetaWarning); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ json, config }): Promise => { + const accountId = await requireAuth(config); + const runtimes = await listRuntimes(accountId); + + if (json) { + logger.log(JSON.stringify(runtimes, null, 2)); + } else { + logger.log(constellationBetaWarning); + logger.table(runtimes.map((runtime) => ({ name: runtime }))); + } + } +); diff --git a/packages/wrangler/src/constellation/options.ts b/packages/wrangler/src/constellation/options.ts new file mode 100644 index 000000000000..f97625fb5e45 --- /dev/null +++ b/packages/wrangler/src/constellation/options.ts @@ -0,0 +1,17 @@ +import type { CommonYargsArgv } from "../yargs-types"; + +export function takeName(yargs: CommonYargsArgv) { + return yargs.positional("name", { + describe: "The name of the project", + type: "string", + demandOption: true, + }); +} + +export function asJson(yargs: CommonYargsArgv) { + return yargs.option("json", { + describe: "return output as clean JSON", + type: "boolean", + default: false, + }); +} diff --git a/packages/wrangler/src/constellation/types.ts b/packages/wrangler/src/constellation/types.ts new file mode 100644 index 000000000000..bfc265a19b30 --- /dev/null +++ b/packages/wrangler/src/constellation/types.ts @@ -0,0 +1,17 @@ +export type Project = { + id: string; + name: string; + runtime: string; +}; + +export type Model = { + id: string; + project_id: string; + name: string; + description: string; +}; + +export type CatalogEntry = { + project: Project; + models: Model[]; +}; diff --git a/packages/wrangler/src/constellation/uploadModel.tsx b/packages/wrangler/src/constellation/uploadModel.tsx new file mode 100644 index 000000000000..6b1808650dda --- /dev/null +++ b/packages/wrangler/src/constellation/uploadModel.tsx @@ -0,0 +1,64 @@ +import { readFileSync } from "node:fs"; +import { FormData, File } from "undici"; +import { fetchResult } from "../cfetch"; +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { takeName } from "./options"; +import { constellationBetaWarning, getProjectByName } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; +import type { Model } from "./types"; + +export function options(yargs: CommonYargsArgv) { + return takeName(yargs) + .positional("modelName", { + describe: "The name of the uploaded model", + type: "string", + demandOption: true, + }) + .positional("modelFile", { + describe: "The name of the local file with the model contents", + type: "string", + demandOption: true, + }) + .epilogue(constellationBetaWarning); +} +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ name, modelName, modelFile, config }): Promise => { + const accountId = await requireAuth(config); + logger.log(constellationBetaWarning); + + const proj = await getProjectByName(config, accountId, name); + + const formData = new FormData(); + formData.set( + "file", + new File([readFileSync(modelFile)], modelFile, { + type: "application/octet-stream", + }) + ); + formData.set("name", modelName); + + let model: Model; + try { + model = await fetchResult( + `/accounts/${accountId}/constellation/project/${proj.id}/model`, + { + method: "POST", + body: formData, + } + ); + } catch (e) { + if ((e as { code: number }).code === 7408) { + throw new Error("A model with that name already exists"); + } + throw e; + } + + logger.log(`✅ Successfully uploaded Model "${model.name}"!`); + } +); diff --git a/packages/wrangler/src/constellation/utils.ts b/packages/wrangler/src/constellation/utils.ts new file mode 100644 index 000000000000..b49c21d14fba --- /dev/null +++ b/packages/wrangler/src/constellation/utils.ts @@ -0,0 +1,90 @@ +import { fetchResult } from "../cfetch"; +import { getEnvironmentVariableFactory } from "../environment-variables/factory"; +import type { Config } from "../config"; +import type { Project, Model, CatalogEntry } from "./types"; + +export const getConstellationWarningFromEnv = getEnvironmentVariableFactory({ + variableName: "NO_CONSTELLATION_WARNING", +}); + +export const constellationBetaWarning = + getConstellationWarningFromEnv() !== undefined + ? "" + : "--------------------\n🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic\n🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose\n🚧 To give feedback, visit https://discord.gg/cloudflaredev\n--------------------\n"; + +export const getProjectByName = async ( + config: Config, + accountId: string, + name: string +): Promise => { + const allProjects = await listProjects(accountId); + const matchingProj = allProjects.find((proj) => proj.name === name); + if (!matchingProj) { + throw new Error(`Couldn't find Project with name '${name}'`); + } + return matchingProj; +}; + +export const getProjectModelByName = async ( + config: Config, + accountId: string, + proj: Project, + modelName: string +): Promise => { + const allModels = await listModels(accountId, proj); + const matchingModel = allModels.find((model) => model.name === modelName); + if (!matchingModel) { + throw new Error(`Couldn't find Model with name '${modelName}'`); + } + return matchingModel; +}; + +export async function constellationList( + accountId: string, + partialUrl: string +): Promise> { + const pageSize = 50; + let page = 1; + const results = []; + while (results.length % pageSize === 0) { + const json: Array = await fetchResult( + `/accounts/${accountId}/constellation/${partialUrl}`, + {}, + new URLSearchParams({ + per_page: pageSize.toString(), + page: page.toString(), + }) + ); + page++; + results.push(...json); + if (json.length < pageSize) { + break; + } + } + return results; +} + +export const listCatalogEntries = async ( + accountId: string +): Promise> => { + return await constellationList(accountId, "catalog"); +}; + +export const listModels = async ( + accountId: string, + proj: Project +): Promise> => { + return constellationList(accountId, `project/${proj.id}/model`); +}; + +export const listProjects = async ( + accountId: string +): Promise> => { + return await constellationList(accountId, "project"); +}; + +export const listRuntimes = async ( + accountId: string +): Promise> => { + return await constellationList(accountId, "runtime"); +}; diff --git a/packages/wrangler/src/environment-variables/factory.ts b/packages/wrangler/src/environment-variables/factory.ts index 62c9e4cbe97c..7b656b4f6714 100644 --- a/packages/wrangler/src/environment-variables/factory.ts +++ b/packages/wrangler/src/environment-variables/factory.ts @@ -14,7 +14,8 @@ type VariableNames = | "WRANGLER_AUTH_URL" | "WRANGLER_TOKEN_URL" | "WRANGLER_REVOKE_URL" - | "WRANGLER_CF_AUTHORIZATION_TOKEN"; + | "WRANGLER_CF_AUTHORIZATION_TOKEN" + | "NO_CONSTELLATION_WARNING"; type DeprecatedNames = | "CF_ACCOUNT_ID" diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 32e34b6d8bba..53e9981719e2 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -7,6 +7,7 @@ import makeCLI from "yargs"; import { version as wranglerVersion } from "../package.json"; import { isBuildFailure } from "./bundle"; import { loadDotEnv, readConfig } from "./config"; +import { constellation } from "./constellation"; import { d1 } from "./d1"; import { deleteHandler, deleteOptions } from "./delete"; import { @@ -431,6 +432,15 @@ export function createCLIParser(argv: string[]) { return d1(d1Yargs.command(subHelp)); }); + // ai + wrangler.command( + "constellation", + "🤖 Interact with Constellation AI models", + (aiYargs) => { + return constellation(aiYargs.command(subHelp)); + } + ); + // pubsub wrangler.command( "pubsub", diff --git a/packages/wrangler/src/logger.ts b/packages/wrangler/src/logger.ts index 5f7ebab6a303..a9f110a48346 100644 --- a/packages/wrangler/src/logger.ts +++ b/packages/wrangler/src/logger.ts @@ -61,7 +61,11 @@ export class Logger { const keys: Keys[] = data.length === 0 ? [] : (Object.keys(data[0]) as Keys[]); const t = new CLITable({ - head: keys.map((k) => chalk.bold.blue(k)), + head: keys, + style: { + head: chalk.level ? ["blue"] : [], + border: chalk.level ? ["gray"] : [], + }, }); t.push(...data.map((row) => keys.map((k) => row[k]))); return this.doLog("log", [t.toString()]); diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index ccb8f58af52a..1553558100bd 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -347,6 +347,7 @@ const Scopes = { "See and change Cloudflare Pages projects, settings and deployments.", "zone:read": "Grants read level access to account zone.", "ssl_certs:write": "See and manage mTLS certificates for your account", + "constellation:write": "Manage Constellation AI projects/models", } as const; /** From 7b12604e24434a8b4343480bebf3b1e6737336a0 Mon Sep 17 00:00:00 2001 From: Sidhartha Chatterjee Date: Wed, 3 May 2023 15:20:21 +0100 Subject: [PATCH 05/25] fix(pages-shared): Strip %09 when redirecting (#3131) * fix(pages-shared): Strip %09 when redirecting * Add changeset * Update packages/pages-shared/__tests__/asset-server/handler.test.ts Co-authored-by: Daniel Walsh * Add tabs --------- Co-authored-by: Daniel Walsh --- .changeset/thirty-fireants-stare.md | 5 +++++ .../pages-shared/__tests__/asset-server/handler.test.ts | 9 +++++++++ .../__tests__/asset-server/responses.test.ts | 3 +++ packages/pages-shared/asset-server/responses.ts | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-fireants-stare.md diff --git a/.changeset/thirty-fireants-stare.md b/.changeset/thirty-fireants-stare.md new file mode 100644 index 000000000000..3d1ce84a1a28 --- /dev/null +++ b/.changeset/thirty-fireants-stare.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/pages-shared": patch +--- + +Prevent protocol-less URL redirects for %09 (with backslashes) diff --git a/packages/pages-shared/__tests__/asset-server/handler.test.ts b/packages/pages-shared/__tests__/asset-server/handler.test.ts index d031d65ee785..be34f6d35ad7 100644 --- a/packages/pages-shared/__tests__/asset-server/handler.test.ts +++ b/packages/pages-shared/__tests__/asset-server/handler.test.ts @@ -107,6 +107,15 @@ describe("asset-server handler", () => { expect(response.status).toBe(308); expect(response.headers.get("Location")).toEqual("/www.example.com///"); } + { + const { response } = await getTestResponse({ + request: "/%09/www.example.com/%09/index/", + metadata, + findAssetEntryForPath, + }); + expect(response.status).toBe(308); + expect(response.headers.get("Location")).toEqual("/ /www.example.com/ /"); + } { const { response } = await getTestResponse({ request: "/%5Cwww.example.com/%5C/index/", diff --git a/packages/pages-shared/__tests__/asset-server/responses.test.ts b/packages/pages-shared/__tests__/asset-server/responses.test.ts index e4f539462572..841ab4637343 100644 --- a/packages/pages-shared/__tests__/asset-server/responses.test.ts +++ b/packages/pages-shared/__tests__/asset-server/responses.test.ts @@ -43,5 +43,8 @@ describe("stripLeadingDoubleSlashes", () => { expect(stripLeadingDoubleSlashes("/foo/\\/bar")).toMatchInlineSnapshot( `"/foo/\\\\/bar"` ); + expect(stripLeadingDoubleSlashes("/%09foo")).toMatchInlineSnapshot( + `"/foo"` + ); }); }); diff --git a/packages/pages-shared/asset-server/responses.ts b/packages/pages-shared/asset-server/responses.ts index ea272abd4aff..5003d002412d 100644 --- a/packages/pages-shared/asset-server/responses.ts +++ b/packages/pages-shared/asset-server/responses.ts @@ -11,7 +11,7 @@ function mergeHeaders(base: HeadersInit, extra: HeadersInit) { } export function stripLeadingDoubleSlashes(location: string) { - return location.replace(/^(\/|%2F|%2f|%5C|%5c|\\)+(.*)/, "/$2"); + return location.replace(/^(\/|%2F|%2f|%5C|%5c|%09|\\)+(.*)/, "/$2"); } export class OkResponse extends Response { From b503336cb3a03bb5cff7841196e144eafb6f2ef6 Mon Sep 17 00:00:00 2001 From: Sidhartha Chatterjee Date: Wed, 3 May 2023 18:46:04 +0100 Subject: [PATCH 06/25] pages-shared: Strip spaces in urls between leading slashes (#3132) --- .changeset/moody-eyes-sparkle.md | 5 +++++ packages/pages-shared/__tests__/asset-server/handler.test.ts | 2 +- packages/pages-shared/asset-server/responses.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/moody-eyes-sparkle.md diff --git a/.changeset/moody-eyes-sparkle.md b/.changeset/moody-eyes-sparkle.md new file mode 100644 index 000000000000..c414c393a80f --- /dev/null +++ b/.changeset/moody-eyes-sparkle.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/pages-shared": patch +--- + +pages-shared: Strip spaces in urls diff --git a/packages/pages-shared/__tests__/asset-server/handler.test.ts b/packages/pages-shared/__tests__/asset-server/handler.test.ts index be34f6d35ad7..ff9eb70ef5a2 100644 --- a/packages/pages-shared/__tests__/asset-server/handler.test.ts +++ b/packages/pages-shared/__tests__/asset-server/handler.test.ts @@ -114,7 +114,7 @@ describe("asset-server handler", () => { findAssetEntryForPath, }); expect(response.status).toBe(308); - expect(response.headers.get("Location")).toEqual("/ /www.example.com/ /"); + expect(response.headers.get("Location")).toEqual("/www.example.com/ /"); } { const { response } = await getTestResponse({ diff --git a/packages/pages-shared/asset-server/responses.ts b/packages/pages-shared/asset-server/responses.ts index 5003d002412d..5c1873b2b738 100644 --- a/packages/pages-shared/asset-server/responses.ts +++ b/packages/pages-shared/asset-server/responses.ts @@ -11,7 +11,7 @@ function mergeHeaders(base: HeadersInit, extra: HeadersInit) { } export function stripLeadingDoubleSlashes(location: string) { - return location.replace(/^(\/|%2F|%2f|%5C|%5c|%09|\\)+(.*)/, "/$2"); + return location.replace(/^(\/|%2F|%2f|%5C|%5c|%09|\s|\\)+(.*)/g, "/$2"); } export class OkResponse extends Response { From a0294393a6f26b6edce7ce20060859aa5c659d19 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 14:09:38 -0400 Subject: [PATCH 07/25] Version Packages (#3122) Co-authored-by: github-actions[bot] --- .changeset/moody-eyes-sparkle.md | 5 ----- .changeset/tall-geckos-try.md | 5 ----- .changeset/thirty-fireants-stare.md | 5 ----- package-lock.json | 4 ++-- packages/pages-shared/CHANGELOG.md | 8 ++++++++ packages/pages-shared/package.json | 2 +- packages/wrangler/CHANGELOG.md | 6 ++++++ packages/wrangler/package.json | 2 +- 8 files changed, 18 insertions(+), 19 deletions(-) delete mode 100644 .changeset/moody-eyes-sparkle.md delete mode 100644 .changeset/tall-geckos-try.md delete mode 100644 .changeset/thirty-fireants-stare.md diff --git a/.changeset/moody-eyes-sparkle.md b/.changeset/moody-eyes-sparkle.md deleted file mode 100644 index c414c393a80f..000000000000 --- a/.changeset/moody-eyes-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@cloudflare/pages-shared": patch ---- - -pages-shared: Strip spaces in urls diff --git a/.changeset/tall-geckos-try.md b/.changeset/tall-geckos-try.md deleted file mode 100644 index 17ae0d2c14a0..000000000000 --- a/.changeset/tall-geckos-try.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"wrangler": minor ---- - -Added initial commands for integrating with Constellation AI. diff --git a/.changeset/thirty-fireants-stare.md b/.changeset/thirty-fireants-stare.md deleted file mode 100644 index 3d1ce84a1a28..000000000000 --- a/.changeset/thirty-fireants-stare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@cloudflare/pages-shared": patch ---- - -Prevent protocol-less URL redirects for %09 (with backslashes) diff --git a/package-lock.json b/package-lock.json index 6cc9e73eeed1..81f20e8dc021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44485,7 +44485,7 @@ }, "packages/pages-shared": { "name": "@cloudflare/pages-shared", - "version": "0.4.0", + "version": "0.4.1", "dependencies": { "@miniflare/core": "2.13.0" }, @@ -45450,7 +45450,7 @@ "dev": true }, "packages/wrangler": { - "version": "2.18.0", + "version": "2.19.0", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", diff --git a/packages/pages-shared/CHANGELOG.md b/packages/pages-shared/CHANGELOG.md index 84e58f6f6dd4..fa55ad508d29 100644 --- a/packages/pages-shared/CHANGELOG.md +++ b/packages/pages-shared/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloudflare/pages-shared +## 0.4.1 + +### Patch Changes + +- [#3132](https://github.com/cloudflare/workers-sdk/pull/3132) [`b503336c`](https://github.com/cloudflare/workers-sdk/commit/b503336cb3a03bb5cff7841196e144eafb6f2ef6) Thanks [@sidharthachatterjee](https://github.com/sidharthachatterjee)! - pages-shared: Strip spaces in urls + +* [#3131](https://github.com/cloudflare/workers-sdk/pull/3131) [`7b12604e`](https://github.com/cloudflare/workers-sdk/commit/7b12604e24434a8b4343480bebf3b1e6737336a0) Thanks [@sidharthachatterjee](https://github.com/sidharthachatterjee)! - Prevent protocol-less URL redirects for %09 (with backslashes) + ## 0.4.0 ### Minor Changes diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 5ec9dc5f5e7e..161758c9d5dc 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/pages-shared", - "version": "0.4.0", + "version": "0.4.1", "repository": { "type": "git", "url": "https://github.com/cloudflare/workers-sdk.git", diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index 8c42c6a0cb4f..73fb3c2e5074 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,11 @@ # wrangler +## 2.19.0 + +### Minor Changes + +- [#3091](https://github.com/cloudflare/workers-sdk/pull/3091) [`c32f514c`](https://github.com/cloudflare/workers-sdk/commit/c32f514ca40e8b13dc9e86fdc76577b9adeb70f5) Thanks [@edevil](https://github.com/edevil)! - Added initial commands for integrating with Constellation AI. + ## 2.18.0 ### Minor Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index c530df75756b..40cd15fefa37 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "2.18.0", + "version": "2.19.0", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "wrangler", From 823258cdf7f41747963d87bdb018b510f26184b6 Mon Sep 17 00:00:00 2001 From: "Jacob Hands [CF]" Date: Wed, 3 May 2023 15:15:13 -0500 Subject: [PATCH 08/25] fix: Remove global flag in pages-shared regex (#3136) --- .changeset/young-phones-behave.md | 5 +++++ fixtures/pages-simple-assets/tests/index.test.ts | 8 ++++++++ .../__tests__/asset-server/responses.test.ts | 13 ++++++++++++- packages/pages-shared/asset-server/responses.ts | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/young-phones-behave.md diff --git a/.changeset/young-phones-behave.md b/.changeset/young-phones-behave.md new file mode 100644 index 000000000000..0355942ee526 --- /dev/null +++ b/.changeset/young-phones-behave.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/pages-shared": patch +--- + +fix: Remove global flag in pages-shared regex diff --git a/fixtures/pages-simple-assets/tests/index.test.ts b/fixtures/pages-simple-assets/tests/index.test.ts index b023f226c5c4..b2b82baec7e7 100644 --- a/fixtures/pages-simple-assets/tests/index.test.ts +++ b/fixtures/pages-simple-assets/tests/index.test.ts @@ -39,5 +39,13 @@ describe.concurrent("Pages Functions", () => { expect(response.status).toEqual(308); expect(response.headers.get("Location")).toEqual("/www.example.com/"); } + { + const response = await fetch( + `http://${ip}:${port}/%09/www.example.com/index/`, + { redirect: "manual" } + ); + expect(response.status).toEqual(308); + expect(response.headers.get("Location")).toEqual("/www.example.com/"); + } }); }); diff --git a/packages/pages-shared/__tests__/asset-server/responses.test.ts b/packages/pages-shared/__tests__/asset-server/responses.test.ts index 841ab4637343..7e5d1174adee 100644 --- a/packages/pages-shared/__tests__/asset-server/responses.test.ts +++ b/packages/pages-shared/__tests__/asset-server/responses.test.ts @@ -1,7 +1,7 @@ import { stripLeadingDoubleSlashes } from "../../asset-server/responses"; describe("stripLeadingDoubleSlashes", () => { - it("strips extra leading `/`, `%2F` and `%2f`s", () => { + it("strips extra leading `/`, `%2F`, `%2f`s, spaces, and tabs", () => { expect(stripLeadingDoubleSlashes("/")).toMatchInlineSnapshot(`"/"`); expect(stripLeadingDoubleSlashes("/%2Ffoo")).toMatchInlineSnapshot( `"/foo"` @@ -46,5 +46,16 @@ describe("stripLeadingDoubleSlashes", () => { expect(stripLeadingDoubleSlashes("/%09foo")).toMatchInlineSnapshot( `"/foo"` ); + expect(stripLeadingDoubleSlashes("/%09/foo")).toMatchInlineSnapshot( + `"/foo"` + ); + // Unencoded space / tab + expect(stripLeadingDoubleSlashes("/%09/foo/%09/ / /")).toMatchInlineSnapshot( + `"/foo/%09/ / /"` + ); + // Unencoded space + expect(stripLeadingDoubleSlashes("/ /foo")).toMatchInlineSnapshot(`"/foo"`); + // Unencoded tab + expect(stripLeadingDoubleSlashes("/ /foo")).toMatchInlineSnapshot(`"/foo"`); }); }); diff --git a/packages/pages-shared/asset-server/responses.ts b/packages/pages-shared/asset-server/responses.ts index 5c1873b2b738..74a08a4e521d 100644 --- a/packages/pages-shared/asset-server/responses.ts +++ b/packages/pages-shared/asset-server/responses.ts @@ -11,7 +11,7 @@ function mergeHeaders(base: HeadersInit, extra: HeadersInit) { } export function stripLeadingDoubleSlashes(location: string) { - return location.replace(/^(\/|%2F|%2f|%5C|%5c|%09|\s|\\)+(.*)/g, "/$2"); + return location.replace(/^(\/|%2F|%2f|%5C|%5c|%09|\s|\\)+(.*)/, "/$2"); } export class OkResponse extends Response { From 98e66301e3c824f77916e69e01a9cbbecec15421 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 22:00:02 +0100 Subject: [PATCH 09/25] Version Packages (#3137) Co-authored-by: github-actions[bot] --- .changeset/young-phones-behave.md | 5 ----- package-lock.json | 2 +- packages/pages-shared/CHANGELOG.md | 6 ++++++ packages/pages-shared/package.json | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/young-phones-behave.md diff --git a/.changeset/young-phones-behave.md b/.changeset/young-phones-behave.md deleted file mode 100644 index 0355942ee526..000000000000 --- a/.changeset/young-phones-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@cloudflare/pages-shared": patch ---- - -fix: Remove global flag in pages-shared regex diff --git a/package-lock.json b/package-lock.json index 81f20e8dc021..a1d515a35648 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44485,7 +44485,7 @@ }, "packages/pages-shared": { "name": "@cloudflare/pages-shared", - "version": "0.4.1", + "version": "0.4.2", "dependencies": { "@miniflare/core": "2.13.0" }, diff --git a/packages/pages-shared/CHANGELOG.md b/packages/pages-shared/CHANGELOG.md index fa55ad508d29..8fce44711f5c 100644 --- a/packages/pages-shared/CHANGELOG.md +++ b/packages/pages-shared/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloudflare/pages-shared +## 0.4.2 + +### Patch Changes + +- [#3136](https://github.com/cloudflare/workers-sdk/pull/3136) [`823258cd`](https://github.com/cloudflare/workers-sdk/commit/823258cdf7f41747963d87bdb018b510f26184b6) Thanks [@jahands](https://github.com/jahands)! - fix: Remove global flag in pages-shared regex + ## 0.4.1 ### Patch Changes diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 161758c9d5dc..3781eca76a23 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/pages-shared", - "version": "0.4.1", + "version": "0.4.2", "repository": { "type": "git", "url": "https://github.com/cloudflare/workers-sdk.git", From e351afcff4f265f85ff3e4674cc3083eb5cd5027 Mon Sep 17 00:00:00 2001 From: Greg Brimble Date: Fri, 5 May 2023 02:01:08 +0100 Subject: [PATCH 10/25] `_worker.js/` directory support in Pages (#2966) * `_worker.js/` directory support in Pages * Refactor some of the shared no bundle logic and address misc PR comments --- .changeset/cuddly-rules-rest.md | 5 + fixtures/local-mode-tests/package.json | 21 +--- .../pages-workerjs-directory/package.json | 18 ++++ .../public/_worker.js/add.wasm | Bin 0 -> 41 bytes .../public/_worker.js/index.js | 23 +++++ .../public/_worker.js/other-script.js | 1 + .../public/_worker.js/static.js | 1 + .../public/index.html | 1 + .../tests/index.test.ts | 45 +++++++++ .../pages-workerjs-directory/tsconfig.json | 12 +++ .../__tests__/pages/functions-build.test.ts | 70 +++++++++++++ packages/wrangler/src/api/dev.ts | 5 + packages/wrangler/src/api/pages/publish.tsx | 40 +++++--- packages/wrangler/src/bundle.ts | 7 +- packages/wrangler/src/dev.tsx | 13 ++- packages/wrangler/src/dev/dev.tsx | 2 + packages/wrangler/src/dev/start-server.ts | 87 ++++++++-------- packages/wrangler/src/dev/use-esbuild.ts | 93 ++++++++++-------- packages/wrangler/src/entry.ts | 3 +- packages/wrangler/src/pages/build.ts | 46 +++++---- packages/wrangler/src/pages/dev.ts | 30 ++++-- .../src/pages/functions/buildWorker.ts | 57 +++++++++++ 22 files changed, 438 insertions(+), 142 deletions(-) create mode 100644 .changeset/cuddly-rules-rest.md create mode 100644 fixtures/pages-workerjs-directory/package.json create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/add.wasm create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/index.js create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/other-script.js create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/static.js create mode 100644 fixtures/pages-workerjs-directory/public/index.html create mode 100644 fixtures/pages-workerjs-directory/tests/index.test.ts create mode 100644 fixtures/pages-workerjs-directory/tsconfig.json diff --git a/.changeset/cuddly-rules-rest.md b/.changeset/cuddly-rules-rest.md new file mode 100644 index 000000000000..2b61ef0d788d --- /dev/null +++ b/.changeset/cuddly-rules-rest.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +feat: Add support for the undocumented `_worker.js/` directory in Pages diff --git a/fixtures/local-mode-tests/package.json b/fixtures/local-mode-tests/package.json index 08c068ae8bf1..cb065cdd3b98 100644 --- a/fixtures/local-mode-tests/package.json +++ b/fixtures/local-mode-tests/package.json @@ -9,25 +9,8 @@ "main": "index.js", "scripts": { "check:type": "tsc && tsc -p tests/tsconfig.json", - "test": "cross-env NODE_ENV=local-testing NODE_OPTIONS=--experimental-vm-modules npx jest --forceExit", - "test:ci": "cross-env NODE_ENV=local-testing NODE_OPTIONS=--experimental-vm-modules npx jest --forceExit" - }, - "jest": { - "restoreMocks": true, - "testRegex": ".*.(test|spec)\\.[jt]sx?$", - "testTimeout": 30000, - "transform": { - "^.+\\.c?(t|j)sx?$": [ - "esbuild-jest", - { - "sourcemap": true - } - ] - }, - "transformIgnorePatterns": [ - "node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)", - "wrangler-dist/cli.js" - ] + "test": "npx vitest", + "test:ci": "npx vitest" }, "devDependencies": { "@cloudflare/workers-types": "^4.20221111.1", diff --git a/fixtures/pages-workerjs-directory/package.json b/fixtures/pages-workerjs-directory/package.json new file mode 100644 index 000000000000..a2fd7e5e418b --- /dev/null +++ b/fixtures/pages-workerjs-directory/package.json @@ -0,0 +1,18 @@ +{ + "name": "pages-workerjs-directory", + "version": "0.0.0", + "private": true, + "sideEffects": false, + "scripts": { + "check:type": "tsc", + "dev": "npx wrangler pages dev public --port 8794", + "test": "npx vitest", + "test:ci": "npx vitest" + }, + "devDependencies": { + "undici": "^5.9.1" + }, + "engines": { + "node": ">=16.13" + } +} diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/add.wasm b/fixtures/pages-workerjs-directory/public/_worker.js/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/index.js b/fixtures/pages-workerjs-directory/public/_worker.js/index.js new file mode 100644 index 000000000000..fb55bdb57c75 --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/index.js @@ -0,0 +1,23 @@ +import staticMod from "./static.js"; +import add from "./add.wasm"; + +export default { + async fetch(request, env) { + const { pathname } = new URL(request.url); + + if (pathname === "/wasm") { + const addModule = await WebAssembly.instantiate(add); + return new Response(addModule.exports.add(1, 2).toString()); + } + + if (pathname === "/static") { + return new Response(staticMod); + } + + if (pathname !== "/") { + return new Response((await import(`./${pathname.slice(1)}`)).default); + } + + return env.ASSETS.fetch(request); + }, +}; diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js b/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js new file mode 100644 index 000000000000..58c57157d36c --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js @@ -0,0 +1 @@ +export default "test"; diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/static.js b/fixtures/pages-workerjs-directory/public/_worker.js/static.js new file mode 100644 index 000000000000..dffa9ed5541b --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/static.js @@ -0,0 +1 @@ +export default "static"; diff --git a/fixtures/pages-workerjs-directory/public/index.html b/fixtures/pages-workerjs-directory/public/index.html new file mode 100644 index 000000000000..9f735a6307ae --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/index.html @@ -0,0 +1 @@ +

Hello, world!

diff --git a/fixtures/pages-workerjs-directory/tests/index.test.ts b/fixtures/pages-workerjs-directory/tests/index.test.ts new file mode 100644 index 000000000000..d4446d65f562 --- /dev/null +++ b/fixtures/pages-workerjs-directory/tests/index.test.ts @@ -0,0 +1,45 @@ +import { execSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path, { join, resolve } from "node:path"; +import { fetch } from "undici"; +import { describe, it } from "vitest"; +import { runWranglerPagesDev } from "../../shared/src/run-wrangler-long-lived"; + +describe.concurrent("Pages _worker.js/ directory", () => { + it("should support non-bundling with 'dev'", async ({ expect }) => { + const { ip, port, stop } = await runWranglerPagesDev( + resolve(__dirname, ".."), + "public", + ["--port=0"] + ); + await expect( + fetch(`http://${ip}:${port}/`).then((resp) => resp.text()) + ).resolves.toContain("Hello, world!"); + await expect( + fetch(`http://${ip}:${port}/wasm`).then((resp) => resp.text()) + ).resolves.toContain("3"); + await expect( + fetch(`http://${ip}:${port}/static`).then((resp) => resp.text()) + ).resolves.toContain("static"); + await expect( + fetch(`http://${ip}:${port}/other-script`).then((resp) => resp.text()) + ).resolves.toContain("test"); + await stop(); + }); + + it("should bundle", async ({ expect }) => { + const dir = tmpdir(); + const file = join(dir, "./_worker.bundle"); + + execSync( + `npx wrangler pages functions build --build-output-directory public --outfile ${file} --bindings="{\\"d1_databases\\":{\\"FOO\\":{}}}"`, + { + cwd: path.resolve(__dirname, ".."), + } + ); + + expect(readFileSync(file, "utf-8")).toContain("D1_ERROR"); + expect(readFileSync(file, "utf-8")).toContain('"static"'); + }); +}); diff --git a/fixtures/pages-workerjs-directory/tsconfig.json b/fixtures/pages-workerjs-directory/tsconfig.json new file mode 100644 index 000000000000..6eb14e3584b7 --- /dev/null +++ b/fixtures/pages-workerjs-directory/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "esModuleInterop": true, + "module": "CommonJS", + "lib": ["ES2020"], + "types": ["node"], + "moduleResolution": "node", + "noEmit": true + }, + "include": ["tests", "../../node-types.d.ts"] +} diff --git a/packages/wrangler/src/__tests__/pages/functions-build.test.ts b/packages/wrangler/src/__tests__/pages/functions-build.test.ts index bf28996512c0..01b8947eec7e 100644 --- a/packages/wrangler/src/__tests__/pages/functions-build.test.ts +++ b/packages/wrangler/src/__tests__/pages/functions-build.test.ts @@ -449,4 +449,74 @@ export default { hello.js:2:36: ERROR: Could not resolve \\"node:async_hooks\\"" `); }); + + it("should compile a _worker.js/ directory", async () => { + mkdirSync("public"); + mkdirSync("public/_worker.js"); + writeFileSync( + "public/_worker.js/index.js", + ` +import { cat } from "./cat.js"; + +export default { + async fetch(request, env) { + return new Response("Hello from _worker.js/index.js" + cat); + }, +};` + ); + writeFileSync( + "public/_worker.js/cat.js", + ` +export const cat = "cat";` + ); + + await runWrangler(`pages functions build --outfile=public/_worker.bundle`); + + expect(existsSync("public/_worker.bundle")).toBe(true); + expect(std.out).toMatchInlineSnapshot(` + "🚧 'wrangler pages ' is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose + ✨ Compiled Worker successfully" + `); + + const workerBundleContents = readFileSync("public/_worker.bundle", "utf-8"); + const workerBundleWithConstantData = replaceRandomWithConstantData( + workerBundleContents, + [ + [/------formdata-undici-0.[0-9]*/g, "------formdata-undici-0.test"], + [/bundledWorker-0.[0-9]*.mjs/g, "bundledWorker-0.test.mjs"], + [/bundledWorker-0.[0-9]*.map/g, "bundledWorker-0.test.map"], + ] + ); + + expect(workerBundleWithConstantData).toMatchInlineSnapshot(` + "------formdata-undici-0.test + Content-Disposition: form-data; name=\\"metadata\\" + + {\\"main_module\\":\\"bundledWorker-0.test.mjs\\"} + ------formdata-undici-0.test + Content-Disposition: form-data; name=\\"bundledWorker-0.test.mjs\\"; filename=\\"bundledWorker-0.test.mjs\\" + Content-Type: application/javascript+module + + import { cat } from \\"./cat.js\\"; + var worker_default = { + async fetch(request, env) { + return new Response(\\"Hello from _worker.js/index.js\\" + cat); + } + }; + export { + worker_default as default + }; + //# sourceMappingURL=bundledWorker-0.test.mjs.map + + ------formdata-undici-0.test + Content-Disposition: form-data; name=\\"cat.js\\"; filename=\\"cat.js\\" + Content-Type: application/javascript+module + + + export const cat = \\"cat\\"; + ------formdata-undici-0.test--" + `); + + expect(std.err).toMatchInlineSnapshot(`""`); + }); }); diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index 3d62c86b6f61..35d08d399af2 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -3,6 +3,7 @@ import { startApiDev, startDev } from "../dev"; import { logger } from "../logger"; import type { Environment } from "../config"; +import type { Rule } from "../config/environment"; import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types"; import type { RequestInit, Response, RequestInfo } from "undici"; @@ -42,6 +43,9 @@ export interface UnstableDevOptions { bucket_name: string; preview_bucket_name?: string; }[]; + processEntrypoint?: boolean; + moduleRoot?: string; + rules?: Rule[]; logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"] inspect?: boolean; local?: boolean; @@ -150,6 +154,7 @@ export async function unstable_dev( }, config: options?.config, env: options?.env, + processEntrypoint: !!options?.processEntrypoint, bundle: options?.bundle, compatibilityDate: options?.compatibilityDate, compatibilityFlags: options?.compatibilityFlags, diff --git a/packages/wrangler/src/api/pages/publish.tsx b/packages/wrangler/src/api/pages/publish.tsx index 5d7eb431b3b4..36efa2b7e014 100644 --- a/packages/wrangler/src/api/pages/publish.tsx +++ b/packages/wrangler/src/api/pages/publish.tsx @@ -1,4 +1,4 @@ -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, lstatSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve as resolvePath } from "node:path"; import { cwd } from "node:process"; @@ -14,6 +14,7 @@ import { import { buildRawWorker, checkRawWorker, + traverseAndBuildWorkerJSDirectory, } from "../../pages/functions/buildWorker"; import { validateRoutes } from "../../pages/functions/routes-validation"; import { upload } from "../../pages/upload"; @@ -65,7 +66,7 @@ interface PagesPublishOptions { /** * Whether to run bundling on `_worker.js` before deploying. - * Default: false + * Default: true */ bundle?: boolean; @@ -95,9 +96,12 @@ export async function publish({ _redirects: string | undefined, _routesGenerated: string | undefined, _routesCustom: string | undefined, + _workerJSIsDirectory = false, _workerJS: string | undefined; - const workerScriptPath = resolvePath(directory, "_worker.js"); + bundle = bundle ?? true; + + const _workerPath = resolvePath(directory, "_worker.js"); try { _headers = readFileSync(join(directory, "_headers"), "utf-8"); @@ -116,7 +120,10 @@ export async function publish({ } catch {} try { - _workerJS = readFileSync(workerScriptPath, "utf-8"); + _workerJSIsDirectory = lstatSync(_workerPath).isDirectory(); + if (!_workerJSIsDirectory) { + _workerJS = readFileSync(_workerPath, "utf-8"); + } } catch {} // Grab the bindings from the API, we need these for shims and other such hacky inserts @@ -240,16 +247,23 @@ export async function publish({ * Advanced Mode * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode * - * When using a _worker.js file, the entire /functions directory is ignored + * When using a _worker.js file or _worker.js/ directory, the entire /functions directory is ignored * – this includes its routing and middleware characteristics. */ - if (_workerJS) { + if (_workerJSIsDirectory) { + workerBundle = await traverseAndBuildWorkerJSDirectory({ + workerJSDirectory: _workerPath, + buildOutputDirectory: directory, + d1Databases, + nodejsCompat, + }); + } else if (_workerJS) { if (bundle) { const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); workerBundle = await buildRawWorker({ - workerScriptPath, + workerScriptPath: _workerPath, outfile, - directory: directory ?? ".", + directory, local: false, sourcemap: true, watch: false, @@ -258,17 +272,19 @@ export async function publish({ nodejsCompat, }); } else { - await checkRawWorker(workerScriptPath, () => {}); - // TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769 + await checkRawWorker(_workerPath, () => {}); + // TODO: Let users configure this in the future. workerBundle = { modules: [], dependencies: {}, stop: undefined, - resolvedEntryPointPath: workerScriptPath, + resolvedEntryPointPath: _workerPath, bundleType: "esm", }; } + } + if (_workerJS || _workerJSIsDirectory) { const workerBundleContents = await createUploadWorkerBundleContents( workerBundle as BundleResult ); @@ -302,7 +318,7 @@ export async function publish({ * Pages Functions * https://developers.cloudflare.com/pages/platform/functions/ */ - if (builtFunctions && !_workerJS) { + if (builtFunctions && !_workerJS && !_workerJSIsDirectory) { const workerBundleContents = await createUploadWorkerBundleContents( workerBundle as BundleResult ); diff --git a/packages/wrangler/src/bundle.ts b/packages/wrangler/src/bundle.ts index 7ab69373ee45..0e1f5efaf024 100644 --- a/packages/wrangler/src/bundle.ts +++ b/packages/wrangler/src/bundle.ts @@ -117,6 +117,8 @@ export async function bundleWorker( entry: Entry, destination: string, options: { + // When `bundle` is set to false, we apply shims to the Worker, but won't pull in any imports + bundle?: boolean; serveAssetsFromWorker: boolean; assets?: StaticAssetsConfig; betaD1Shims?: string[]; @@ -149,6 +151,7 @@ export async function bundleWorker( } ): Promise { const { + bundle = true, serveAssetsFromWorker, betaD1Shims, doBindings, @@ -350,7 +353,7 @@ export async function bundleWorker( const buildOptions: esbuild.BuildOptions & { metafile: true } = { entryPoints: [inputEntry.file], - bundle: true, + bundle, absWorkingDir: entry.directory, outdir: destination, entryNames: entryName || path.parse(entry.file).name, @@ -362,7 +365,7 @@ export async function bundleWorker( } : {}), inject, - external: ["__STATIC_CONTENT_MANIFEST"], + external: bundle ? ["__STATIC_CONTENT_MANIFEST"] : undefined, format: entry.format === "modules" ? "esm" : "iife", target: COMMON_ESBUILD_OPTIONS.target, sourcemap: sourcemap ?? true, // this needs to use ?? to accept false diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 24bc81ab1c19..046f067e1df0 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -28,7 +28,7 @@ import { printWranglerBanner, } from "./index"; import type { Config, Environment } from "./config"; -import type { Route } from "./config/environment"; +import type { Route, Rule } from "./config/environment"; import type { LoggerLevel } from "./logger"; import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types"; import type { CfWorkerInit } from "./worker"; @@ -334,6 +334,9 @@ export type AdditionalDevProps = { preview_bucket_name?: string; }[]; d1Databases?: Environment["d1_databases"]; + processEntrypoint?: boolean; + moduleRoot?: string; + rules?: Rule[]; }; type StartDevOptions = DevArguments & @@ -424,7 +427,8 @@ export async function startDev(args: StartDevOptions) { zone={zoneId} host={host} routes={routes} - rules={getRules(configParam)} + processEntrypoint={!!args.processEntrypoint} + rules={args.rules ?? getRules(configParam)} legacyEnv={isLegacyEnv(configParam)} minify={args.minify ?? configParam.minify} legacyNodeCompat={legacyNodeCompat} @@ -560,7 +564,8 @@ export async function startApiDev(args: StartDevOptions) { zone: zoneId, host: host, routes: routes, - rules: getRules(configParam), + processEntrypoint: !!args.processEntrypoint, + rules: args.rules ?? getRules(configParam), legacyEnv: isLegacyEnv(configParam), minify: args.minify ?? configParam.minify, legacyNodeCompat, @@ -687,7 +692,7 @@ async function validateDevServerSettings( config: Config ) { const entry = await getEntry( - { assets: args.assets, script: args.script }, + { assets: args.assets, script: args.script, moduleRoot: args.moduleRoot }, config, "dev" ); diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index 998332e7c15e..29583a585daa 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -116,6 +116,7 @@ export type DevProps = { initialPort: number; initialIp: string; inspectorPort: number; + processEntrypoint: boolean; rules: Config["rules"]; accountId: string | undefined; initialMode: "local" | "remote"; @@ -271,6 +272,7 @@ function DevSession(props: DevSessionProps) { entry: props.entry, destination: directory, jsxFactory: props.jsxFactory, + processEntrypoint: props.processEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( diff --git a/packages/wrangler/src/dev/start-server.ts b/packages/wrangler/src/dev/start-server.ts index 3a56f1ef0ee0..de010964a142 100644 --- a/packages/wrangler/src/dev/start-server.ts +++ b/packages/wrangler/src/dev/start-server.ts @@ -92,6 +92,7 @@ export async function startDevServer( entry: props.entry, destination: directory.name, jsxFactory: props.jsxFactory, + processEntrypoint: props.processEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( @@ -205,6 +206,7 @@ async function runEsbuild({ destination, jsxFactory, jsxFragment, + processEntrypoint, rules, assets, betaD1Shims, @@ -227,6 +229,7 @@ async function runEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; + processEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; betaD1Shims?: string[]; @@ -247,49 +250,55 @@ async function runEsbuild({ }): Promise { if (!destination) return; - const { - resolvedEntryPointPath, - bundleType, - modules, - dependencies, - sourceMapPath, - }: Awaited> = noBundle - ? await traverseModuleGraph(entry, rules) - : await bundleWorker(entry, destination, { - serveAssetsFromWorker, - jsxFactory, - jsxFragment, - rules, - tsconfig, - minify, - legacyNodeCompat, - nodejsCompat, - define, - checkFetch: true, - assets: assets && { - ...assets, - // disable the cache in dev - bypassCache: true, - }, - betaD1Shims, - workerDefinitions, - services, - firstPartyWorkerDevFacade, - targetConsumer: "dev", // We are starting a dev server - testScheduled, - local, - experimentalLocal, - doBindings, - }); + let traverseModuleGraphResult: + | Awaited> + | undefined; + let bundleResult: Awaited> | undefined; + if (noBundle) { + traverseModuleGraphResult = await traverseModuleGraph(entry, rules); + } + + if (processEntrypoint || !noBundle) { + bundleResult = await bundleWorker(entry, destination, { + bundle: !noBundle, + disableModuleCollection: noBundle, + serveAssetsFromWorker, + jsxFactory, + jsxFragment, + rules, + tsconfig, + minify, + legacyNodeCompat, + nodejsCompat, + define, + checkFetch: true, + assets: assets && { + ...assets, + // disable the cache in dev + bypassCache: true, + }, + betaD1Shims, + workerDefinitions, + services, + firstPartyWorkerDevFacade, + targetConsumer: "dev", // We are starting a dev server + testScheduled, + local, + experimentalLocal, + doBindings, + }); + } return { id: 0, entry, - path: resolvedEntryPointPath, - type: bundleType, - modules, - dependencies, - sourceMapPath, + path: bundleResult?.resolvedEntryPointPath ?? entry.file, + type: + bundleResult?.bundleType ?? + (entry.format === "modules" ? "esm" : "commonjs"), + modules: traverseModuleGraphResult?.modules ?? bundleResult?.modules ?? [], + dependencies: bundleResult?.dependencies ?? {}, + sourceMapPath: bundleResult?.sourceMapPath, }; } diff --git a/packages/wrangler/src/dev/use-esbuild.ts b/packages/wrangler/src/dev/use-esbuild.ts index 9e7121eeb01c..2285eb71676e 100644 --- a/packages/wrangler/src/dev/use-esbuild.ts +++ b/packages/wrangler/src/dev/use-esbuild.ts @@ -26,6 +26,7 @@ export function useEsbuild({ destination, jsxFactory, jsxFragment, + processEntrypoint, rules, assets, serveAssetsFromWorker, @@ -49,6 +50,7 @@ export function useEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; + processEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; define: Config["define"]; @@ -100,45 +102,48 @@ export function useEsbuild({ async function build() { if (!destination) return; - const { - resolvedEntryPointPath, - bundleType, - modules, - dependencies, - stop, - sourceMapPath, - }: Awaited> = noBundle - ? await traverseModuleGraph(entry, rules) - : await bundleWorker(entry, destination, { - serveAssetsFromWorker, - jsxFactory, - jsxFragment, - rules, - watch: watchMode, - tsconfig, - minify, - legacyNodeCompat, - nodejsCompat, - betaD1Shims, - doBindings: durableObjects.bindings, - define, - checkFetch: true, - assets: assets && { - ...assets, - // disable the cache in dev - bypassCache: true, - }, - workerDefinitions, - services, - firstPartyWorkerDevFacade, - local, - targetConsumer, - testScheduled, - experimentalLocal, - }); + let traverseModuleGraphResult: + | Awaited> + | undefined; + let bundleResult: Awaited> | undefined; + if (noBundle) { + traverseModuleGraphResult = await traverseModuleGraph(entry, rules); + } + + if (processEntrypoint || !noBundle) { + bundleResult = await bundleWorker(entry, destination, { + bundle: !noBundle, + disableModuleCollection: noBundle, + serveAssetsFromWorker, + jsxFactory, + jsxFragment, + rules, + watch: watchMode, + tsconfig, + minify, + legacyNodeCompat, + nodejsCompat, + betaD1Shims, + doBindings: durableObjects.bindings, + define, + checkFetch: true, + assets: assets && { + ...assets, + // disable the cache in dev + bypassCache: true, + }, + workerDefinitions, + services, + firstPartyWorkerDevFacade, + local, + targetConsumer, + testScheduled, + experimentalLocal, + }); + } // Capture the `stop()` method to use as the `useEffect()` destructor. - stopWatching = stop; + stopWatching = bundleResult?.stop; // if "noBundle" is true, then we need to manually watch the entry point and // trigger "builds" when it changes @@ -156,11 +161,14 @@ export function useEsbuild({ setBundle({ id: 0, entry, - path: resolvedEntryPointPath, - type: bundleType, - modules, - dependencies, - sourceMapPath, + path: bundleResult?.resolvedEntryPointPath ?? entry.file, + type: + bundleResult?.bundleType ?? + (entry.format === "modules" ? "esm" : "commonjs"), + modules: + traverseModuleGraphResult?.modules ?? bundleResult?.modules ?? [], + dependencies: bundleResult?.dependencies ?? {}, + sourceMapPath: bundleResult?.sourceMapPath, }); } @@ -180,6 +188,7 @@ export function useEsbuild({ jsxFactory, jsxFragment, serveAssetsFromWorker, + processEntrypoint, rules, tsconfig, exit, diff --git a/packages/wrangler/src/entry.ts b/packages/wrangler/src/entry.ts index 5f7da5e4355b..8984704d6bff 100644 --- a/packages/wrangler/src/entry.ts +++ b/packages/wrangler/src/entry.ts @@ -35,6 +35,7 @@ export async function getEntry( script?: string; format?: CfScriptFormat | undefined; assets?: string | undefined; + moduleRoot?: string; }, config: Config, command: "dev" | "publish" | "types" @@ -113,7 +114,7 @@ export async function getEntry( file, directory, format, - moduleRoot: config.base_dir ?? path.dirname(file), + moduleRoot: args.moduleRoot ?? config.base_dir ?? path.dirname(file), }; } diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index 268d85d43594..af17e3b77115 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { existsSync, lstatSync, mkdirSync, writeFileSync } from "node:fs"; import { basename, dirname, relative, resolve as resolvePath } from "node:path"; import { createUploadWorkerBundleContents } from "../api/pages/create-worker-bundle-contents"; import { FatalError } from "../errors"; @@ -12,7 +12,10 @@ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning, } from "./errors"; -import { buildRawWorker } from "./functions/buildWorker"; +import { + buildRawWorker, + traverseAndBuildWorkerJSDirectory, +} from "./functions/buildWorker"; import { pagesBetaWarning } from "./utils"; import type { BundleResult } from "../bundle"; import type { @@ -208,21 +211,30 @@ export const Handler = async (args: PagesBuildArgs) => { * and if we were able to resolve _worker.js */ if (workerScriptPath) { - /** - * `buildRawWorker` builds `_worker.js`, but doesn't give us the bundle - * we want to return, which includes the external dependencies (like wasm, - * binary, text). Let's output that build result to memory and only write - * to disk once we have the final bundle - */ - bundle = await buildRawWorker({ - workerScriptPath, - outdir, - directory: buildOutputDirectory, - local: false, - sourcemap, - watch, - betaD1Shims: d1Databases, - }); + if (lstatSync(workerScriptPath).isDirectory()) { + bundle = await traverseAndBuildWorkerJSDirectory({ + workerJSDirectory: workerScriptPath, + buildOutputDirectory, + d1Databases, + nodejsCompat, + }); + } else { + /** + * `buildRawWorker` builds `_worker.js`, but doesn't give us the bundle + * we want to return, which includes the external dependencies (like wasm, + * binary, text). Let's output that build result to memory and only write + * to disk once we have the final bundle + */ + bundle = await buildRawWorker({ + workerScriptPath, + outdir, + directory: buildOutputDirectory, + local: false, + sourcemap, + watch, + betaD1Shims: d1Databases, + }); + } } else { try { /** diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index edf1ed96ee3c..d4dc3afe8077 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -1,5 +1,5 @@ import { execSync, spawn } from "node:child_process"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, lstatSync, readFileSync } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { watch } from "chokidar"; @@ -255,7 +255,12 @@ export const Handler = async ({ directory !== undefined ? join(directory, singleWorkerScriptPath) : singleWorkerScriptPath; + const usingWorkerDirectory = + existsSync(workerScriptPath) && lstatSync(workerScriptPath).isDirectory(); const usingWorkerScript = existsSync(workerScriptPath); + // TODO: Here lies a known bug. If you specify both `--bundle` and `--no-bundle`, this behavior is undefined and you will get unexpected results. + // There is no sane way to get the true value out of yargs, so here we are. + const enableBundling = bundle ?? !noBundle; const functionsDirectory = "./functions"; let usingFunctions = !usingWorkerScript && existsSync(functionsDirectory); @@ -270,9 +275,6 @@ export const Handler = async ({ await checkRawWorker(workerScriptPath, () => scriptReadyResolve()); }; - // TODO: Here lies a known bug. If you specify both `--bundle` and `--no-bundle`, this behavior is undefined and you will get unexpected results. - // There is no sane way to get the true value out of yargs, so here we are. - const enableBundling = bundle ?? !noBundle; if (enableBundling) { // We want to actually run the `_worker.js` script through the bundler // So update the final path to the script that will be uploaded and @@ -281,7 +283,9 @@ export const Handler = async ({ runBuild = async () => { try { await buildRawWorker({ - workerScriptPath, + workerScriptPath: usingWorkerDirectory + ? join(workerScriptPath, "index.js") + : workerScriptPath, outfile: scriptPath, directory: directory ?? ".", nodejsCompat, @@ -396,7 +400,10 @@ export const Handler = async ({ let entrypoint = scriptPath; // custom _routes.json apply only to Functions or Advanced Mode Pages projects - if (directory && (usingFunctions || usingWorkerScript)) { + if ( + directory && + (usingFunctions || usingWorkerScript || usingWorkerDirectory) + ) { const routesJSONPath = join(directory, "_routes.json"); if (existsSync(routesJSONPath)) { @@ -538,6 +545,17 @@ export const Handler = async ({ r2: r2s.map((binding) => { return { binding: binding.toString(), bucket_name: "" }; }), + processEntrypoint: true, + moduleRoot: workerScriptPath, + rules: usingWorkerDirectory + ? [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + : undefined, + bundle: enableBundling, persist, persistTo, inspect: undefined, diff --git a/packages/wrangler/src/pages/functions/buildWorker.ts b/packages/wrangler/src/pages/functions/buildWorker.ts index fb742862cb9e..cb995d08cc31 100644 --- a/packages/wrangler/src/pages/functions/buildWorker.ts +++ b/packages/wrangler/src/pages/functions/buildWorker.ts @@ -7,7 +7,9 @@ import { bundleWorker } from "../../bundle"; import { FatalError } from "../../errors"; import { logger } from "../../logger"; import { getBasePath } from "../../paths"; +import traverseModuleGraph from "../../traverse-module-graph"; import { D1_BETA_PREFIX } from "../../worker"; +import type { BundleResult } from "../../bundle"; import type { Plugin } from "esbuild"; export type Options = { @@ -160,6 +162,7 @@ export type RawOptions = { outfile?: string; outdir?: string; directory: string; + bundle?: boolean; minify?: boolean; sourcemap?: boolean; watch?: boolean; @@ -184,6 +187,7 @@ export function buildRawWorker({ outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`), outdir, directory, + bundle = true, minify = false, sourcemap = false, watch = false, @@ -203,6 +207,7 @@ export function buildRawWorker({ }, outdir ? resolve(outdir) : resolve(outfile), { + bundle, minify, sourcemap, watch, @@ -226,6 +231,58 @@ export function buildRawWorker({ ); } +export async function traverseAndBuildWorkerJSDirectory({ + workerJSDirectory, + buildOutputDirectory, + d1Databases, + nodejsCompat, +}: { + workerJSDirectory: string; + buildOutputDirectory: string; + d1Databases?: string[]; + nodejsCompat?: boolean; +}): Promise { + const entrypoint = resolve(join(workerJSDirectory, "index.js")); + + const traverseModuleGraphResult = await traverseModuleGraph( + { + file: entrypoint, + directory: resolve(workerJSDirectory), + format: "modules", + moduleRoot: resolve(workerJSDirectory), + }, + [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + ); + + const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); + const bundleResult = await buildRawWorker({ + workerScriptPath: entrypoint, + bundle: false, + outfile, + directory: buildOutputDirectory, + local: false, + sourcemap: true, + watch: false, + onEnd: () => {}, + betaD1Shims: d1Databases, + nodejsCompat, + }); + + return { + modules: traverseModuleGraphResult.modules, + dependencies: bundleResult.dependencies, + resolvedEntryPointPath: bundleResult.resolvedEntryPointPath, + bundleType: bundleResult.bundleType, + stop: bundleResult.stop, + sourceMapPath: bundleResult.sourceMapPath, + }; +} + /** * Creates an esbuild plugin that can notify Wrangler (via the `onEnd()`) * when the build completes. From 5079f4767f862cb7c42f4b2b5484b0391fbe5fae Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 5 May 2023 15:42:21 +0100 Subject: [PATCH 11/25] fix: do not render "value of stdout.lastframe() is undefined" if the output is an empty string (#2912) Fixes #2907 --- .changeset/funny-spies-melt.md | 7 +++++++ packages/wrangler/src/utils/render.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/funny-spies-melt.md diff --git a/.changeset/funny-spies-melt.md b/.changeset/funny-spies-melt.md new file mode 100644 index 000000000000..a008aca4f13d --- /dev/null +++ b/.changeset/funny-spies-melt.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: do not render "value of stdout.lastframe() is undefined" if the output is an empty string + +Fixes #2907 diff --git a/packages/wrangler/src/utils/render.ts b/packages/wrangler/src/utils/render.ts index be1d1b13d1c0..956e3f7b75a9 100644 --- a/packages/wrangler/src/utils/render.ts +++ b/packages/wrangler/src/utils/render.ts @@ -83,7 +83,7 @@ export const render = (tree: ReactElement): Instance => { }); return { - output: stdout.lastFrame() || LASTFRAME_UNDEFINED, + output: stdout.lastFrame() ?? LASTFRAME_UNDEFINED, stdout, stderr, cleanup: instance.cleanup, From d078800804899c3c8e083260f8cfdfc0397d6110 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Fri, 5 May 2023 16:10:23 +0100 Subject: [PATCH 12/25] nodejs compat functions build (#3133) --- .changeset/metal-buckets-fly.md | 5 +++++ .../__tests__/pages/functions-build.test.ts | 6 ++++++ .../wrangler/src/__tests__/publish.test.ts | 2 +- packages/wrangler/src/bundle.ts | 20 +++++++++++++++---- packages/wrangler/src/pages/build.ts | 1 + .../src/pages/functions/buildPlugin.ts | 1 + .../src/pages/functions/buildWorker.ts | 2 ++ 7 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 .changeset/metal-buckets-fly.md diff --git a/.changeset/metal-buckets-fly.md b/.changeset/metal-buckets-fly.md new file mode 100644 index 000000000000..d8077c5d5e7c --- /dev/null +++ b/.changeset/metal-buckets-fly.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +fix pages building not taking into account the nodejs_compat flag (and improve the related error message) diff --git a/packages/wrangler/src/__tests__/pages/functions-build.test.ts b/packages/wrangler/src/__tests__/pages/functions-build.test.ts index 01b8947eec7e..75e7fa1c9bc4 100644 --- a/packages/wrangler/src/__tests__/pages/functions-build.test.ts +++ b/packages/wrangler/src/__tests__/pages/functions-build.test.ts @@ -448,6 +448,12 @@ export default { "Build failed with 1 error: hello.js:2:36: ERROR: Could not resolve \\"node:async_hooks\\"" `); + expect(std.err).toContain( + 'The package "node:async_hooks" wasn\'t found on the file system but is built into node.' + ); + expect(std.err).toContain( + 'Add the "nodejs_compat" compatibility flag to your Pages project to enable Node.js compatibility.' + ); }); it("should compile a _worker.js/ directory", async () => { diff --git a/packages/wrangler/src/__tests__/publish.test.ts b/packages/wrangler/src/__tests__/publish.test.ts index 04b4f0438dd5..dc18a8c444bf 100644 --- a/packages/wrangler/src/__tests__/publish.test.ts +++ b/packages/wrangler/src/__tests__/publish.test.ts @@ -7224,7 +7224,7 @@ export default{ expect( esbuild.formatMessagesSync(err?.errors ?? [], { kind: "error" }).join() ).toMatch( - /The package "path" wasn't found on the file system but is built into node\.\s+Add "node_compat = true" to your wrangler\.toml file to enable Node compatibility\./ + /The package "path" wasn't found on the file system but is built into node\.\s+Add "node_compat = true" to your wrangler\.toml file to enable Node.js compatibility\./ ); }); diff --git a/packages/wrangler/src/bundle.ts b/packages/wrangler/src/bundle.ts index 0e1f5efaf024..a14dfbbe801e 100644 --- a/packages/wrangler/src/bundle.ts +++ b/packages/wrangler/src/bundle.ts @@ -77,15 +77,25 @@ export function isBuildFailure(err: unknown): err is esbuild.BuildFailure { * Rewrites esbuild BuildFailures for failing to resolve Node built-in modules * to suggest enabling Node compat as opposed to `platform: "node"`. */ -export function rewriteNodeCompatBuildFailure(err: esbuild.BuildFailure) { +export function rewriteNodeCompatBuildFailure( + err: esbuild.BuildFailure, + forPages = false +) { for (const error of err.errors) { const match = nodeBuiltinResolveErrorText.exec(error.text); if (match !== null) { + const issue = `The package "${match[1]}" wasn't found on the file system but is built into node.`; + + const instructionForUser = `${ + forPages + ? 'Add the "nodejs_compat" compatibility flag to your Pages project' + : 'Add "node_compat = true" to your wrangler.toml file' + } to enable Node.js compatibility.`; + error.notes = [ { location: null, - text: `The package "${match[1]}" wasn't found on the file system but is built into node. -Add "node_compat = true" to your wrangler.toml file to enable Node compatibility.`, + text: `${issue}\n${instructionForUser}`, }, ]; } @@ -148,6 +158,7 @@ export async function bundleWorker( // TODO: Rip these out https://github.com/cloudflare/workers-sdk/issues/2153 disableModuleCollection?: boolean; isOutfile?: boolean; + forPages?: boolean; } ): Promise { const { @@ -179,6 +190,7 @@ export async function bundleWorker( plugins, disableModuleCollection, isOutfile, + forPages, } = options; // We create a temporary directory for any oneoff files we @@ -412,7 +424,7 @@ export async function bundleWorker( result = await esbuild.build(buildOptions); } catch (e) { if (!legacyNodeCompat && isBuildFailure(e)) - rewriteNodeCompatBuildFailure(e); + rewriteNodeCompatBuildFailure(e, forPages); throw e; } diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index af17e3b77115..5b4816b6997b 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -233,6 +233,7 @@ export const Handler = async (args: PagesBuildArgs) => { sourcemap, watch, betaD1Shims: d1Databases, + nodejsCompat, }); } } else { diff --git a/packages/wrangler/src/pages/functions/buildPlugin.ts b/packages/wrangler/src/pages/functions/buildPlugin.ts index 565ebb28ff28..178c6ad4522f 100644 --- a/packages/wrangler/src/pages/functions/buildPlugin.ts +++ b/packages/wrangler/src/pages/functions/buildPlugin.ts @@ -108,6 +108,7 @@ export function buildPlugin({ targetConsumer: local ? "dev" : "publish", local, experimentalLocal: false, + forPages: true, } ); } diff --git a/packages/wrangler/src/pages/functions/buildWorker.ts b/packages/wrangler/src/pages/functions/buildWorker.ts index cb995d08cc31..e5b8fc42c3b6 100644 --- a/packages/wrangler/src/pages/functions/buildWorker.ts +++ b/packages/wrangler/src/pages/functions/buildWorker.ts @@ -153,6 +153,7 @@ export function buildWorker({ targetConsumer: local ? "dev" : "publish", local, experimentalLocal: false, + forPages: true, } ); } @@ -227,6 +228,7 @@ export function buildRawWorker({ targetConsumer: local ? "dev" : "publish", local, experimentalLocal: false, + forPages: true, } ); } From 78530f4290915343716e60cfac6f951ebd30d20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Somhairle=20MacLe=C3=B2id?= Date: Fri, 5 May 2023 18:54:17 +0100 Subject: [PATCH 13/25] Fix lock file (#3156) --- package-lock.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index a1d515a35648..93b62f33c61c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -250,6 +250,15 @@ "node": ">=16.13" } }, + "fixtures/pages-workerjs-directory": { + "version": "0.0.0", + "devDependencies": { + "undici": "^5.9.1" + }, + "engines": { + "node": ">=16.13" + } + }, "fixtures/pages-workerjs-wasm-app": { "version": "0.0.1", "devDependencies": { @@ -29402,6 +29411,10 @@ "resolved": "fixtures/pages-workerjs-app", "link": true }, + "node_modules/pages-workerjs-directory": { + "resolved": "fixtures/pages-workerjs-directory", + "link": true + }, "node_modules/pages-workerjs-wasm-app": { "resolved": "fixtures/pages-workerjs-wasm-app", "link": true @@ -81928,6 +81941,12 @@ "undici": "^5.9.1" } }, + "pages-workerjs-directory": { + "version": "file:fixtures/pages-workerjs-directory", + "requires": { + "undici": "^5.9.1" + } + }, "pages-workerjs-wasm-app": { "version": "file:fixtures/pages-workerjs-wasm-app", "requires": { From 5fd080c88ee7991cde107f8723f06ea2fd2c651d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Somhairle=20MacLe=C3=B2id?= Date: Fri, 5 May 2023 19:45:24 +0100 Subject: [PATCH 14/25] Support sourcemaps in DevTools (#3140) * Support sourcemaps in DevTools * Support Miniflare source mapping --- .changeset/six-chicken-shave.md | 7 ++ fixtures/sites-app/wrangler.toml | 1 + fixtures/worker-ts/package.json | 13 ++ fixtures/worker-ts/src/index.ts | 34 ++++++ fixtures/worker-ts/tsconfig.json | 13 ++ fixtures/worker-ts/wrangler.toml | 3 + package-lock.json | 32 +++++ ...d-README-detailing-the-setup-process.patch | 4 +- ...pport-viewing-files-over-the-network.patch | 4 +- .../0003-Show-fallback-image-on-Safari.patch | 4 +- ...port-previewing-subrequest-responses.patch | 4 +- ...tter-Firefox-support-for-network-tab.patch | 4 +- ...VX-292-Remove-unsupported-network-UI.patch | 4 +- .../0007-Remove-unsupported-profiler-UI.patch | 4 +- ...overlay-on-the-memory-tab-in-Firefox.patch | 4 +- ...l-parameter-for-forcing-the-theme-fr.patch | 4 +- ...r-text-colour-in-dark-mode-in-browse.patch | 4 +- ...0011-Fallback-to-location-for-domain.patch | 4 +- .../0012-Hide-unsupported-settings-UI.patch | 4 +- ...bugger-sidebar-and-disable-breakpoin.patch | 4 +- ...ping-to-improve-connection-stability.patch | 62 +++++----- .../patches/0015-Support-sourcemaps.patch | 60 ++++++++++ packages/wrangler/src/bundle.ts | 8 +- packages/wrangler/src/dev/dev.tsx | 5 +- packages/wrangler/src/dev/local.tsx | 4 + packages/wrangler/src/dev/remote.tsx | 2 + packages/wrangler/src/dev/start-server.ts | 2 + packages/wrangler/src/dev/use-esbuild.ts | 3 + packages/wrangler/src/entry.ts | 6 + packages/wrangler/src/inspect.ts | 113 +++++++++++++++++- .../wrangler/src/traverse-module-graph.ts | 1 + 31 files changed, 355 insertions(+), 66 deletions(-) create mode 100644 .changeset/six-chicken-shave.md create mode 100644 fixtures/worker-ts/package.json create mode 100644 fixtures/worker-ts/src/index.ts create mode 100644 fixtures/worker-ts/tsconfig.json create mode 100644 fixtures/worker-ts/wrangler.toml create mode 100644 packages/wrangler-devtools/patches/0015-Support-sourcemaps.patch diff --git a/.changeset/six-chicken-shave.md b/.changeset/six-chicken-shave.md new file mode 100644 index 000000000000..654e24631d2f --- /dev/null +++ b/.changeset/six-chicken-shave.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +feat: Support sourcemaps in DevTools + +Intercept requests from DevTools in Wrangler to inject sourcemaps and enable folders in the Sources Panel of DevTools. When errors are thrown in your Worker, DevTools should now show your source file in the Sources panel, rather than Wrangler's bundled output. diff --git a/fixtures/sites-app/wrangler.toml b/fixtures/sites-app/wrangler.toml index 2fa43a3566c5..7c074f846c87 100644 --- a/fixtures/sites-app/wrangler.toml +++ b/fixtures/sites-app/wrangler.toml @@ -1,2 +1,3 @@ name = "sites-app" site = { bucket = "./public" } +main = "src/modules.js" diff --git a/fixtures/worker-ts/package.json b/fixtures/worker-ts/package.json new file mode 100644 index 000000000000..9fefef2bfb68 --- /dev/null +++ b/fixtures/worker-ts/package.json @@ -0,0 +1,13 @@ +{ + "name": "worker-ts", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler publish", + "start": "wrangler dev" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20230419.0", + "wrangler": "2.19.0" + } +} diff --git a/fixtures/worker-ts/src/index.ts b/fixtures/worker-ts/src/index.ts new file mode 100644 index 000000000000..e550fd3fdfa6 --- /dev/null +++ b/fixtures/worker-ts/src/index.ts @@ -0,0 +1,34 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `wrangler dev src/index.ts` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `wrangler publish src/index.ts --name my-worker` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ +export interface Env { + // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ + // MY_KV_NAMESPACE: KVNamespace; + // + // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ + // MY_DURABLE_OBJECT: DurableObjectNamespace; + // + // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ + // MY_BUCKET: R2Bucket; + // + // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ + // MY_SERVICE: Fetcher; +} + +export default { + async fetch( + request: Request, + env: Env, + ctx: ExecutionContext + ): Promise { + const url = new URL(request.url); + if (url.pathname === "/error") throw new Error("Hello Error"); + return new Response("Hello World!"); + }, +}; diff --git a/fixtures/worker-ts/tsconfig.json b/fixtures/worker-ts/tsconfig.json new file mode 100644 index 000000000000..258e03918a00 --- /dev/null +++ b/fixtures/worker-ts/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["es2021"], + "module": "es2022", + "types": ["@cloudflare/workers-types/2022-11-30"], + "noEmit": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/fixtures/worker-ts/wrangler.toml b/fixtures/worker-ts/wrangler.toml new file mode 100644 index 000000000000..0404b911ae95 --- /dev/null +++ b/fixtures/worker-ts/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker-ts" +main = "src/index.ts" +compatibility_date = "2023-05-04" diff --git a/package-lock.json b/package-lock.json index 93b62f33c61c..67d9b0627754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -402,6 +402,19 @@ "version": "1.0.1", "license": "ISC" }, + "fixtures/worker-ts": { + "version": "0.0.0", + "devDependencies": { + "@cloudflare/workers-types": "^4.20230419.0", + "wrangler": "2.19.0" + } + }, + "fixtures/worker-ts/node_modules/@cloudflare/workers-types": { + "version": "4.20230419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230419.0.tgz", + "integrity": "sha512-MfNBlHrI/ekRkbLtdAo23D4hkXF+3QD92OCwuXxCUK73HtMHuBqkMp9T/8KFbKNRCnz7PzUderc7Jr5m3eeW3g==", + "dev": true + }, "fixtures/workers-chat-demo": { "version": "1.0.0" }, @@ -43414,6 +43427,10 @@ "microevent.ts": "~0.1.1" } }, + "node_modules/worker-ts": { + "resolved": "fixtures/worker-ts", + "link": true + }, "node_modules/worker-turso-ts": { "resolved": "templates/worker-turso-ts", "link": true @@ -93547,6 +93564,21 @@ "microevent.ts": "~0.1.1" } }, + "worker-ts": { + "version": "file:fixtures/worker-ts", + "requires": { + "@cloudflare/workers-types": "^4.20230419.0", + "wrangler": "2.19.0" + }, + "dependencies": { + "@cloudflare/workers-types": { + "version": "4.20230419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230419.0.tgz", + "integrity": "sha512-MfNBlHrI/ekRkbLtdAo23D4hkXF+3QD92OCwuXxCUK73HtMHuBqkMp9T/8KFbKNRCnz7PzUderc7Jr5m3eeW3g==", + "dev": true + } + } + }, "worker-turso-ts": { "version": "file:templates/worker-turso-ts", "requires": { diff --git a/packages/wrangler-devtools/patches/0001-Add-README-detailing-the-setup-process.patch b/packages/wrangler-devtools/patches/0001-Add-README-detailing-the-setup-process.patch index 865a1018c500..efa89c37c7b1 100644 --- a/packages/wrangler-devtools/patches/0001-Add-README-detailing-the-setup-process.patch +++ b/packages/wrangler-devtools/patches/0001-Add-README-detailing-the-setup-process.patch @@ -1,7 +1,7 @@ From b2985a087935838fdf6a6f0dba21afed484849c7 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Wed, 11 Jan 2023 17:46:48 +0000 -Subject: [PATCH 01/14] Add README detailing the setup process +Subject: [PATCH 01/15] Add README detailing the setup process --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -74,5 +74,5 @@ index 8a057be23c..4e009509b2 100644 -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0002-Support-viewing-files-over-the-network.patch b/packages/wrangler-devtools/patches/0002-Support-viewing-files-over-the-network.patch index b408b725f52e..cb33a393ad35 100644 --- a/packages/wrangler-devtools/patches/0002-Support-viewing-files-over-the-network.patch +++ b/packages/wrangler-devtools/patches/0002-Support-viewing-files-over-the-network.patch @@ -1,7 +1,7 @@ From cc7b688a2427d7506a2f4111887a5e243fb841d4 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Thu, 12 Jan 2023 15:33:43 +0000 -Subject: [PATCH 02/14] Support viewing files over the network +Subject: [PATCH 02/15] Support viewing files over the network --- front_end/core/sdk/Target.ts | 4 ++++ @@ -93,5 +93,5 @@ index ac0d4aaf68..29c726e185 100644 ] } -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0003-Show-fallback-image-on-Safari.patch b/packages/wrangler-devtools/patches/0003-Show-fallback-image-on-Safari.patch index c367116c4e9e..d5a76bbe1f14 100644 --- a/packages/wrangler-devtools/patches/0003-Show-fallback-image-on-Safari.patch +++ b/packages/wrangler-devtools/patches/0003-Show-fallback-image-on-Safari.patch @@ -1,7 +1,7 @@ From a742a62c90c73c8c7e766eaaad9003200a447729 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Mon, 16 Jan 2023 16:51:11 +0000 -Subject: [PATCH 03/14] Show fallback image on Safari +Subject: [PATCH 03/15] Show fallback image on Safari --- config/gni/devtools_grd_files.gni | 1 + @@ -1815,5 +1815,5 @@ index 1edaec9604..945aa6d816 100644 + -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0004-Support-previewing-subrequest-responses.patch b/packages/wrangler-devtools/patches/0004-Support-previewing-subrequest-responses.patch index 77687552963c..bb80ae589d32 100644 --- a/packages/wrangler-devtools/patches/0004-Support-previewing-subrequest-responses.patch +++ b/packages/wrangler-devtools/patches/0004-Support-previewing-subrequest-responses.patch @@ -1,7 +1,7 @@ From 6c4581cd2e985e8e94aa3c9091faec45743da6e4 Mon Sep 17 00:00:00 2001 From: bcoll Date: Mon, 16 Jan 2023 14:26:52 +0000 -Subject: [PATCH 04/14] Support previewing subrequest responses +Subject: [PATCH 04/15] Support previewing subrequest responses --- front_end/core/sdk/NetworkManager.ts | 9 +++++++-- @@ -61,5 +61,5 @@ index 213ca7fecd..ef5c568e96 100644 import '../../panels/js_profiler/js_profiler-meta.js'; -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0005-Better-Firefox-support-for-network-tab.patch b/packages/wrangler-devtools/patches/0005-Better-Firefox-support-for-network-tab.patch index c56c710441a0..b7134d444394 100644 --- a/packages/wrangler-devtools/patches/0005-Better-Firefox-support-for-network-tab.patch +++ b/packages/wrangler-devtools/patches/0005-Better-Firefox-support-for-network-tab.patch @@ -1,7 +1,7 @@ From b91cdddb4b2576149ba2c184f011073140f16e9b Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Thu, 19 Jan 2023 15:47:52 +0000 -Subject: [PATCH 05/14] Better Firefox support for network tab +Subject: [PATCH 05/15] Better Firefox support for network tab --- front_end/entrypoints/js_app/js_app.ts | 2 ++ @@ -45,5 +45,5 @@ index a7ec8ccf32..fc80d2769d 100644 return; } -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0006-DEVX-292-Remove-unsupported-network-UI.patch b/packages/wrangler-devtools/patches/0006-DEVX-292-Remove-unsupported-network-UI.patch index d25a50a9b07b..2373fc8bfd5c 100644 --- a/packages/wrangler-devtools/patches/0006-DEVX-292-Remove-unsupported-network-UI.patch +++ b/packages/wrangler-devtools/patches/0006-DEVX-292-Remove-unsupported-network-UI.patch @@ -1,7 +1,7 @@ From 1a267cd3d143892fb0886907a75e3eb295b5b150 Mon Sep 17 00:00:00 2001 From: bcoll Date: Mon, 16 Jan 2023 15:43:50 +0000 -Subject: [PATCH 06/14] DEVX-292 Remove unsupported network UI +Subject: [PATCH 06/15] DEVX-292 Remove unsupported network UI --- front_end/panels/network/NetworkPanel.ts | 35 ------------------------ @@ -75,5 +75,5 @@ index 304bb09af1..aaf80fbd50 100644 this.rightToolbar.appendSeparator(); this.rightToolbar.appendToolbarItem(new UI.Toolbar.ToolbarSettingToggle( -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0007-Remove-unsupported-profiler-UI.patch b/packages/wrangler-devtools/patches/0007-Remove-unsupported-profiler-UI.patch index 3302259502d4..ca3fe7fa2437 100644 --- a/packages/wrangler-devtools/patches/0007-Remove-unsupported-profiler-UI.patch +++ b/packages/wrangler-devtools/patches/0007-Remove-unsupported-profiler-UI.patch @@ -1,7 +1,7 @@ From 7312af32a23a62acc2f5661187ef90b01381c0de Mon Sep 17 00:00:00 2001 From: bcoll Date: Mon, 16 Jan 2023 16:32:45 +0000 -Subject: [PATCH 07/14] Remove unsupported profiler UI +Subject: [PATCH 07/15] Remove unsupported profiler UI --- front_end/panels/profiler/HeapProfilerPanel.ts | 2 +- @@ -35,5 +35,5 @@ index c4680b48be..c2fe5b775f 100644 this.profileViewToolbar = new UI.Toolbar.Toolbar('', this.toolbarElement); this.profileViewToolbar.makeWrappable(true); -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0008-Show-an-overlay-on-the-memory-tab-in-Firefox.patch b/packages/wrangler-devtools/patches/0008-Show-an-overlay-on-the-memory-tab-in-Firefox.patch index 73459788d555..90fab9698f8d 100644 --- a/packages/wrangler-devtools/patches/0008-Show-an-overlay-on-the-memory-tab-in-Firefox.patch +++ b/packages/wrangler-devtools/patches/0008-Show-an-overlay-on-the-memory-tab-in-Firefox.patch @@ -1,7 +1,7 @@ From 70e79d4a4e1821021d0df627a23d62ede6c558ba Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Thu, 19 Jan 2023 18:49:47 +0000 -Subject: [PATCH 08/14] Show an overlay on the memory tab in Firefox +Subject: [PATCH 08/15] Show an overlay on the memory tab in Firefox --- front_end/entrypoint_template.html | 18 ++++++++++++++++-- @@ -71,5 +71,5 @@ index 693621a273..9b330e7cf0 100644 flex: auto; position: relative; -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0009-Support-theme-url-parameter-for-forcing-the-theme-fr.patch b/packages/wrangler-devtools/patches/0009-Support-theme-url-parameter-for-forcing-the-theme-fr.patch index 4601bf5a60ea..808600d5e0a4 100644 --- a/packages/wrangler-devtools/patches/0009-Support-theme-url-parameter-for-forcing-the-theme-fr.patch +++ b/packages/wrangler-devtools/patches/0009-Support-theme-url-parameter-for-forcing-the-theme-fr.patch @@ -1,7 +1,7 @@ From 0edd0dbfe2704ef6bc527cbfa68dd35989d95cc5 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Fri, 20 Jan 2023 19:19:37 +0000 -Subject: [PATCH 09/14] Support theme= url parameter for forcing the theme from +Subject: [PATCH 09/15] Support theme= url parameter for forcing the theme from outside --- @@ -41,5 +41,5 @@ index 05736bc0b8..530028d5b0 100644 this.customSheets.clear(); this.dispatchEvent(new ThemeChangeEvent()); -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0010-Basic-support-for-text-colour-in-dark-mode-in-browse.patch b/packages/wrangler-devtools/patches/0010-Basic-support-for-text-colour-in-dark-mode-in-browse.patch index fb7dfb004d79..f0ee790cd8ee 100644 --- a/packages/wrangler-devtools/patches/0010-Basic-support-for-text-colour-in-dark-mode-in-browse.patch +++ b/packages/wrangler-devtools/patches/0010-Basic-support-for-text-colour-in-dark-mode-in-browse.patch @@ -1,7 +1,7 @@ From 9d34de7c9010659ece1be008ee852a21e06bff55 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Mon, 23 Jan 2023 15:12:38 +0000 -Subject: [PATCH 10/14] Basic support for text colour in dark mode in browsers +Subject: [PATCH 10/15] Basic support for text colour in dark mode in browsers that don't implement :host-context --- @@ -46,5 +46,5 @@ index b451da60eb..8828dd8f07 100644 -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0011-Fallback-to-location-for-domain.patch b/packages/wrangler-devtools/patches/0011-Fallback-to-location-for-domain.patch index 69a6d74ad886..4b32f255996f 100644 --- a/packages/wrangler-devtools/patches/0011-Fallback-to-location-for-domain.patch +++ b/packages/wrangler-devtools/patches/0011-Fallback-to-location-for-domain.patch @@ -1,7 +1,7 @@ From 0091abac4c9631649485198cc36b49ebd4fd40dd Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Thu, 26 Jan 2023 15:27:04 +0000 -Subject: [PATCH 11/14] Fallback to location for domain +Subject: [PATCH 11/15] Fallback to location for domain --- front_end/panels/sources/NavigatorView.ts | 3 ++- @@ -22,5 +22,5 @@ index c7a64ab432..ca32b5202e 100644 const parsedURL = new Common.ParsedURL.ParsedURL(projectOrigin); -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0012-Hide-unsupported-settings-UI.patch b/packages/wrangler-devtools/patches/0012-Hide-unsupported-settings-UI.patch index 994b0d8a5845..cde756c7177f 100644 --- a/packages/wrangler-devtools/patches/0012-Hide-unsupported-settings-UI.patch +++ b/packages/wrangler-devtools/patches/0012-Hide-unsupported-settings-UI.patch @@ -1,7 +1,7 @@ From deeac26dadb8f2d7f5653c1994eed41f9e220af2 Mon Sep 17 00:00:00 2001 From: bcoll Date: Thu, 26 Jan 2023 15:30:34 +0000 -Subject: [PATCH 12/14] Hide unsupported settings UI +Subject: [PATCH 12/15] Hide unsupported settings UI - Show CORS errors in console setting - Show XHR requests in console setting @@ -62,5 +62,5 @@ index df0806e90d..0b20193865 100644 + display: none; +} -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0013-DEVX-380-Hide-debugger-sidebar-and-disable-breakpoin.patch b/packages/wrangler-devtools/patches/0013-DEVX-380-Hide-debugger-sidebar-and-disable-breakpoin.patch index ed9895c2587b..217f0d7886aa 100644 --- a/packages/wrangler-devtools/patches/0013-DEVX-380-Hide-debugger-sidebar-and-disable-breakpoin.patch +++ b/packages/wrangler-devtools/patches/0013-DEVX-380-Hide-debugger-sidebar-and-disable-breakpoin.patch @@ -1,7 +1,7 @@ From cde74d1233f0e89563cfe4d458af197f21c22f18 Mon Sep 17 00:00:00 2001 From: bcoll Date: Wed, 15 Feb 2023 15:07:45 +0000 -Subject: [PATCH 13/14] DEVX-380 Hide debugger sidebar and disable breakpoints +Subject: [PATCH 13/15] DEVX-380 Hide debugger sidebar and disable breakpoints - Hides debugger sidebar - Removes toggle debugger sidebar command @@ -87,5 +87,5 @@ index c5e95ab535..9c53bca1b3 100644 settingName: 'navigatorGroupByFolder', settingType: Common.Settings.SettingType.BOOLEAN, -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0014-Add-ping-to-improve-connection-stability.patch b/packages/wrangler-devtools/patches/0014-Add-ping-to-improve-connection-stability.patch index e28dca797e18..a6ed720c8fbc 100644 --- a/packages/wrangler-devtools/patches/0014-Add-ping-to-improve-connection-stability.patch +++ b/packages/wrangler-devtools/patches/0014-Add-ping-to-improve-connection-stability.patch @@ -1,46 +1,44 @@ -From b6b9e129a3f09863a82d4e0af0cfd234fd0b8f1d Mon Sep 17 00:00:00 2001 +From 37cafef0a7f76f3c623e1c0016244b07f4dfc5d9 Mon Sep 17 00:00:00 2001 From: Samuel Macleod -Date: Fri, 31 Mar 2023 23:20:07 +0100 -Subject: [PATCH 14/14] Add ping to improve connection stability +Date: Thu, 4 May 2023 03:21:58 +0100 +Subject: [PATCH 14/15] Add ping to improve connection stability --- - front_end/core/sdk/Connections.ts | 9 +++++++++ + front_end/core/protocol_client/InspectorBackend.ts | 9 +++++++++ 1 file changed, 9 insertions(+) -diff --git a/front_end/core/sdk/Connections.ts b/front_end/core/sdk/Connections.ts -index f8ac89d283..cb963a973c 100644 ---- a/front_end/core/sdk/Connections.ts -+++ b/front_end/core/sdk/Connections.ts -@@ -83,6 +83,7 @@ export class WebSocketConnection implements ProtocolClient.InspectorBackend.Conn - #onWebSocketDisconnect: (() => void)|null; - #connected: boolean; - #messages: string[]; +diff --git a/front_end/core/protocol_client/InspectorBackend.ts b/front_end/core/protocol_client/InspectorBackend.ts +index bc7ccf9c08..7d033a7b26 100644 +--- a/front_end/core/protocol_client/InspectorBackend.ts ++++ b/front_end/core/protocol_client/InspectorBackend.ts +@@ -249,6 +249,8 @@ export class SessionRouter { + proxyConnection: ((Connection | undefined)|null), + }>; + #pendingScripts: (() => void)[]; + #pingInterval: ReturnType; - constructor(url: Platform.DevToolsPath.UrlString, onWebSocketDisconnect: () => void) { - this.#socket = new WebSocket(url); - this.#socket.onerror = this.onError.bind(this); -@@ -99,6 +100,13 @@ export class WebSocketConnection implements ProtocolClient.InspectorBackend.Conn - this.#onWebSocketDisconnect = onWebSocketDisconnect; - this.#connected = false; - this.#messages = []; -+ let highPingId = 100_000; ++ + + constructor(connection: Connection) { + this.#connectionInternal = connection; +@@ -266,11 +268,18 @@ export class SessionRouter { + this.#connectionInternal.setOnMessage(this.onMessage.bind(this)); + + this.#connectionInternal.setOnDisconnect(reason => { ++ clearInterval(this.#pingInterval) + const session = this.#sessions.get(''); + if (session) { + session.target.dispose(reason); + } + }); + this.#pingInterval = setInterval(() => { -+ this.sendRawMessage(JSON.stringify({ ++ this.#connectionInternal.sendRawMessage(JSON.stringify({ + method: "Runtime.getIsolateId", -+ id: highPingId++, ++ id: this.nextMessageId(), + })); + }, 10_000); } - setOnMessage(onMessage: (arg0: (Object|string)) => void): void { -@@ -151,6 +159,7 @@ export class WebSocketConnection implements ProtocolClient.InspectorBackend.Conn - this.#socket = null; - } - this.#onWebSocketDisconnect = null; -+ clearInterval(this.#pingInterval) - } - - sendRawMessage(message: string): void { + registerSession(target: TargetBase, sessionId: string, proxyConnection?: Connection|null): void { -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/packages/wrangler-devtools/patches/0015-Support-sourcemaps.patch b/packages/wrangler-devtools/patches/0015-Support-sourcemaps.patch new file mode 100644 index 000000000000..b9c8770dcbfa --- /dev/null +++ b/packages/wrangler-devtools/patches/0015-Support-sourcemaps.patch @@ -0,0 +1,60 @@ +From 85fcb93878fac45074cc24d4e215ede8b9082339 Mon Sep 17 00:00:00 2001 +From: Samuel Macleod +Date: Thu, 4 May 2023 03:22:33 +0100 +Subject: [PATCH 15/15] Support sourcemaps: + + * Recognise `wrangler://` URLs as "special", and always load them with Network.loadNetworkResource + + * Support a `text` property on the response to `Network.loadNetworkResource` to support providing a raw response, rather than a response stream + + * Enable the experimental `AUTHORED_DEPLOYED_GROUPING` and `JUST_MY_CODE` by default, for a better splitting of sourcemapped/deployed Worker code, and to su +--- + front_end/core/common/ParsedURL.ts | 2 +- + front_end/core/sdk/PageResourceLoader.ts | 4 +++- + front_end/entrypoints/main/MainImpl.ts | 2 ++ + 3 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/front_end/core/common/ParsedURL.ts b/front_end/core/common/ParsedURL.ts +index 37c79c85c1..bc52e4c7ce 100644 +--- a/front_end/core/common/ParsedURL.ts ++++ b/front_end/core/common/ParsedURL.ts +@@ -355,7 +355,7 @@ export class ParsedURL { + // Return special URLs as-is. + const trimmedHref = href.trim(); + if (trimmedHref.startsWith('data:') || trimmedHref.startsWith('blob:') || trimmedHref.startsWith('javascript:') || +- trimmedHref.startsWith('mailto:')) { ++ trimmedHref.startsWith('mailto:') || trimmedHref.startsWith('wrangler:')) { + return href as Platform.DevToolsPath.UrlString; + } + +diff --git a/front_end/core/sdk/PageResourceLoader.ts b/front_end/core/sdk/PageResourceLoader.ts +index b057267115..23d7872699 100644 +--- a/front_end/core/sdk/PageResourceLoader.ts ++++ b/front_end/core/sdk/PageResourceLoader.ts +@@ -307,7 +307,9 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper void) | undefined; sourceMapPath?: string | undefined; + sourceMapMetadata?: SourceMapMetadata | undefined; }; type StaticAssetsConfig = @@ -380,7 +382,7 @@ export async function bundleWorker( external: bundle ? ["__STATIC_CONTENT_MANIFEST"] : undefined, format: entry.format === "modules" ? "esm" : "iife", target: COMMON_ESBUILD_OPTIONS.target, - sourcemap: sourcemap ?? true, // this needs to use ?? to accept false + sourcemap: sourcemap ?? true, // Include a reference to the output folder in the sourcemap. // This is omitted by default, but we need it to properly resolve source paths in error output. sourceRoot: destination, @@ -470,6 +472,10 @@ export async function bundleWorker( bundleType, stop: result.stop, sourceMapPath, + sourceMapMetadata: { + tmpDir: tmpDir.path, + entryDirectory: entry.directory, + }, }; } diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index 29583a585daa..4ed39b251b2e 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -188,6 +188,7 @@ function InteractiveDevSession(props: DevProps) { inspect: props.inspect, localProtocol: props.localProtocol, forceLocal: props.forceLocal, + worker: props.name, }); ip = props.initialIp; @@ -350,6 +351,7 @@ function DevSession(props: DevSessionProps) { experimentalLocal={props.experimentalLocal} accountId={props.accountId} experimentalLocalRemoteKv={props.experimentalLocalRemoteKv} + sourceMapPath={bundle?.sourceMapPath} /> ) : ( + s.includes(tmpDir) || s.includes("wrangler/templates") + ? idx + : null + ) + .filter((i: number | null) => i !== null); + + const entryDirectory = props.sourceMapMetadata.entryDirectory; + + sourceMap.sources = sourceMap.sources.map( + (s: string) => + // These are never loaded by Wrangler or DevTools. However, the presence of a scheme is required for DevTools to show the path as folders in the Sources view + // The scheme is intentially not the same as for the sourceMappingURL + // Without this difference in scheme, DevTools will not strip prefix `../` path elements from top level folders (../node_modules -> node_modules, for instance) + `worker://${props.name}/${path.relative(entryDirectory, s)}` + ); + + sendMessageToLocalWebSocket({ + data: JSON.stringify({ + id: message.id, + result: { + resource: { + success: true, + text: JSON.stringify(sourceMap), + }, + }, + }), + }); + return; + } + } catch (e) { + logger.debug(e); + // Ignore errors, fallthrough to the remote inspector + } try { assert( remoteWebSocket, @@ -517,11 +588,28 @@ export default function useInspector(props: InspectorProps) { } /** Send a message from the local websocket to the remote websocket */ - function sendMessageToLocalWebSocket(event: MessageEvent) { + function sendMessageToLocalWebSocket(event: Pick) { assert( localWebSocket, "Trying to send a message to an undefined `localWebSocket`" ); + try { + // Intercept Debugger.scriptParsed responses to inject URL schemes + const message = JSON.parse(event.data as string); + if (message.method === "Debugger.scriptParsed") { + // Add the worker:// scheme conditionally, since some module types already have schemes (e.g. wasm) + message.params.url = new URL( + message.params.url, + `worker://${props.name}` + ).href; + localWebSocket.send(JSON.stringify(message)); + return; + } + } catch (e) { + logger.debug(e); + // Ignore errors, fallthrough to the local websocket + } + localWebSocket.send(event.data); } @@ -555,7 +643,13 @@ export default function useInspector(props: InspectorProps) { ); } }; - }, [localWebSocket, remoteWebSocket]); + }, [ + localWebSocket, + remoteWebSocket, + props.name, + props.sourceMapMetadata, + props.sourceMapPath, + ]); } // Credit: https://stackoverflow.com/a/2117523 @@ -744,8 +838,15 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void { /** * Opens the chrome debugger */ -export const openInspector = async (inspectorPort: number) => { - const url = `https://devtools.devprod.cloudflare.dev/js_app?theme=systemPreferred&ws=localhost:${inspectorPort}/ws`; +export const openInspector = async ( + inspectorPort: number, + worker: string | undefined +) => { + const query = new URLSearchParams(); + query.set("theme", "systemPreferred"); + query.set("ws", `localhost:${inspectorPort}/ws`); + if (worker) query.set("domain", worker); + const url = `https://devtools.devprod.cloudflare.dev/js_app?${query.toString()}`; const errorMessage = "Failed to open inspector.\nInspector depends on having a Chromium-based browser installed, maybe you need to install one?"; diff --git a/packages/wrangler/src/traverse-module-graph.ts b/packages/wrangler/src/traverse-module-graph.ts index d4db707943de..2454875435d0 100644 --- a/packages/wrangler/src/traverse-module-graph.ts +++ b/packages/wrangler/src/traverse-module-graph.ts @@ -49,5 +49,6 @@ export default async function traverseModuleGraph( bundleType, stop: undefined, sourceMapPath: undefined, + sourceMapMetadata: undefined, }; } From a97344d4a6267cd600c94f9f6fd6561407abd6f9 Mon Sep 17 00:00:00 2001 From: lrapoport-cf <107272160+lrapoport-cf@users.noreply.github.com> Date: Mon, 8 May 2023 10:01:12 -0400 Subject: [PATCH 15/25] Update pr template checkboxes to bullets (#3158) --- .github/pull_request_template.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1b92f2a6bde3..cbadea84a00e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,9 +11,9 @@ Fixes # [insert GH or internal issue number(s)]. - [ ] Tests - [ ] Changeset ([Changeset guidelines](https://github.com/cloudflare/workers-sdk/blob/main/CONTRIBUTING.md#changesets)) -**Reviewer has performed the following, where applicable:** +**Reviewer is to perform the following, as applicable:** -- [ ] Checked for inclusion of relevant tests -- [ ] Checked for inclusion of a relevant changeset -- [ ] Checked for creation of associated docs updates -- [ ] Manually pulled down the changes and spot-tested +- Checked for inclusion of relevant tests +- Checked for inclusion of a relevant changeset +- Checked for creation of associated docs updates +- Manually pulled down the changes and spot-tested From 133c0423ccb4c2b35a1dd26157ce9a24c6a743bb Mon Sep 17 00:00:00 2001 From: Zeb Piasecki Date: Mon, 8 May 2023 12:55:11 -0400 Subject: [PATCH 16/25] [wrangler] feat: add support for placement in wrangler config (#3095) * [wrangler] feat: add support for placement in wrangler config Allows a placement object in the wrangler config with a mode of off or smart to configure Smart placement. * increase specificity for workers placement * Add example to placement changeset --- .changeset/short-bottles-smell.md | 12 +++++++++ .../src/__tests__/configuration.test.ts | 8 ++++++ .../pages/create-worker-bundle-contents.ts | 1 + packages/wrangler/src/config/environment.ts | 7 +++++ packages/wrangler/src/config/validation.ts | 27 +++++++++++++++++++ .../wrangler/src/create-worker-upload-form.ts | 4 +++ packages/wrangler/src/dev/remote.tsx | 1 + packages/wrangler/src/init.ts | 5 ++++ packages/wrangler/src/publish/publish.ts | 7 ++++- packages/wrangler/src/secret/index.ts | 1 + packages/wrangler/src/worker.ts | 5 ++++ 11 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .changeset/short-bottles-smell.md diff --git a/.changeset/short-bottles-smell.md b/.changeset/short-bottles-smell.md new file mode 100644 index 000000000000..e32fcf01b3b4 --- /dev/null +++ b/.changeset/short-bottles-smell.md @@ -0,0 +1,12 @@ +--- +"wrangler": minor +--- + +feat: add support for placement in wrangler config + +Allows a `placement` object in the wrangler config with a mode of `off` or `smart` to configure [Smart placement](https://developers.cloudflare.com/workers/platform/smart-placement/). Enabling Smart Placement can be done in your `wrangler.toml` like: + +```toml +[placement] +mode = "smart" +``` diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index e7012f1ee6e3..86d0fafb9ae5 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -84,6 +84,7 @@ describe("normalizeAndValidateConfig()", () => { first_party_worker: undefined, keep_vars: undefined, logpush: undefined, + placement: undefined, }); expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(false); @@ -955,6 +956,9 @@ describe("normalizeAndValidateConfig()", () => { node_compat: true, first_party_worker: true, logpush: true, + placement: { + mode: "smart", + }, }; const { config, diagnostics } = normalizeAndValidateConfig( @@ -1030,6 +1034,9 @@ describe("normalizeAndValidateConfig()", () => { node_compat: "INVALID", first_party_worker: "INVALID", logpush: "INVALID", + placement: { + mode: "INVALID", + }, } as unknown as RawEnvironment; const { config, diagnostics } = normalizeAndValidateConfig( @@ -1093,6 +1100,7 @@ describe("normalizeAndValidateConfig()", () => { - Expected \\"name\\" to be of type string, alphanumeric and lowercase with dashes only but got 111. - Expected \\"main\\" to be of type string but got 1333. - Expected \\"usage_model\\" field to be one of [\\"bundled\\",\\"unbound\\"] but got \\"INVALID\\". + - Expected \\"placement.mode\\" field to be one of [\\"off\\",\\"smart\\"] but got \\"INVALID\\". - The field \\"define.DEF1\\" should be a string but got 1777. - Expected \\"no_bundle\\" to be of type boolean but got \\"INVALID\\". - Expected \\"minify\\" to be of type boolean but got \\"INVALID\\". diff --git a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts index db90ab954d4a..e44c16a03533 100644 --- a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts +++ b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts @@ -70,6 +70,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData { usage_model: undefined, keepVars: undefined, logpush: undefined, + placement: undefined, }; return createWorkerUploadForm(worker); diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index ae9bdab8d2d1..092c47851b01 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -265,6 +265,13 @@ interface EnvironmentInheritable { * @inheritable */ logpush: boolean | undefined; + + /** + * Specify how the worker should be located to minimize round-trip time. + * + * More details: https://developers.cloudflare.com/workers/platform/smart-placement/ + */ + placement: { mode: "off" | "smart" } | undefined; } export type DurableObjectBindings = { diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 5fbd63662125..51b063349b06 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -873,6 +873,32 @@ function validateRoutes( ); } +function normalizeAndValidatePlacement( + diagnostics: Diagnostics, + topLevelEnv: Environment | undefined, + rawEnv: RawEnvironment +): Config["placement"] { + if (rawEnv.placement) { + validateRequiredProperty( + diagnostics, + "placement", + "mode", + rawEnv.placement.mode, + "string", + ["off", "smart"] + ); + } + + return inheritable( + diagnostics, + topLevelEnv, + rawEnv, + "placement", + () => true, + undefined + ); +} + /** * Validate top-level environment configuration and return the normalized values. */ @@ -1060,6 +1086,7 @@ function normalizeAndValidateEnvironment( isOneOf("bundled", "unbound"), undefined ), + placement: normalizeAndValidatePlacement(diagnostics, topLevelEnv, rawEnv), build, workers_dev, // Not inherited fields diff --git a/packages/wrangler/src/create-worker-upload-form.ts b/packages/wrangler/src/create-worker-upload-form.ts index 5899e844d4d0..592979bd9621 100644 --- a/packages/wrangler/src/create-worker-upload-form.ts +++ b/packages/wrangler/src/create-worker-upload-form.ts @@ -4,6 +4,7 @@ import type { CfWorkerInit, CfModuleType, CfDurableObjectMigrations, + CfPlacement, } from "./worker.js"; export function toMimeType(type: CfModuleType): string { @@ -71,6 +72,7 @@ export interface WorkerMetadata { bindings: WorkerMetadataBinding[]; keep_bindings?: WorkerMetadataBinding["type"][]; logpush?: boolean; + placement?: CfPlacement; // Allow unsafe.metadata to add arbitary properties at runtime [key: string]: unknown; } @@ -89,6 +91,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { compatibility_flags, keepVars, logpush, + placement, } = worker; let { modules } = worker; @@ -320,6 +323,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { capnp_schema: bindings.logfwdr?.schema, ...(keepVars && { keep_bindings: ["plain_text", "json"] }), ...(logpush !== undefined && { logpush }), + ...(placement && { placement }), }; if (bindings.unsafe?.metadata !== undefined) { diff --git a/packages/wrangler/src/dev/remote.tsx b/packages/wrangler/src/dev/remote.tsx index f6d9461c0aa5..162668e65bf7 100644 --- a/packages/wrangler/src/dev/remote.tsx +++ b/packages/wrangler/src/dev/remote.tsx @@ -576,6 +576,7 @@ async function createRemoteWorkerInit(props: { usage_model: props.usageModel, keepVars: true, logpush: false, + placement: undefined, // no placement in dev }; return init; diff --git a/packages/wrangler/src/init.ts b/packages/wrangler/src/init.ts index 9f1249553a39..6b0bb26b55f3 100644 --- a/packages/wrangler/src/init.ts +++ b/packages/wrangler/src/init.ts @@ -80,6 +80,7 @@ export type ServiceMetadataRes = { usage_model: "bundled" | "unbound"; compatibility_date: string; last_deployed_from?: "wrangler" | "dash" | "api"; + placement_mode?: "smart"; }; }; created_on: string; @@ -868,6 +869,10 @@ async function getWorkerConfig( new Date().toISOString().substring(0, 10), ...routeOrRoutesToConfig, usage_model: serviceEnvMetadata.script.usage_model, + placement: + serviceEnvMetadata.script.placement_mode === "smart" + ? { mode: "smart" } + : undefined, ...(durableObjectClassNames.length ? { migrations: [ diff --git a/packages/wrangler/src/publish/publish.ts b/packages/wrangler/src/publish/publish.ts index 54edff74c897..8c163fe4d571 100644 --- a/packages/wrangler/src/publish/publish.ts +++ b/packages/wrangler/src/publish/publish.ts @@ -35,7 +35,7 @@ import type { import type { Entry } from "../entry"; import type { PutConsumerBody } from "../queues/client"; import type { AssetPaths } from "../sites"; -import type { CfWorkerInit } from "../worker"; +import type { CfWorkerInit, CfPlacement } from "../worker"; type Props = { config: Config; @@ -571,6 +571,10 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m }); } + // The upload API only accepts an empty string or no specified placement for the "off" mode. + const placement: CfPlacement | undefined = + config.placement?.mode === "smart" ? { mode: "smart" } : undefined; + const worker: CfWorkerInit = { name: scriptName, main: { @@ -586,6 +590,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m usage_model: config.usage_model, keepVars, logpush: props.logpush !== undefined ? props.logpush : config.logpush, + placement, }; // As this is not deterministic for testing, we detect if in a jest environment and run asynchronously diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 7f305c42c394..c3f0be359d26 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -123,6 +123,7 @@ export const secret = (secretYargs: CommonYargsArgv) => { usage_model: undefined, keepVars: false, // this doesn't matter since it's a new script anyway logpush: false, + placement: undefined, }), } ); diff --git a/packages/wrangler/src/worker.ts b/packages/wrangler/src/worker.ts index 59689434c006..9d02b12b5ac1 100644 --- a/packages/wrangler/src/worker.ts +++ b/packages/wrangler/src/worker.ts @@ -203,6 +203,10 @@ export interface CfDurableObjectMigrations { }[]; } +export interface CfPlacement { + mode: "smart"; +} + /** * Options for creating a `CfWorker`. */ @@ -246,6 +250,7 @@ export interface CfWorkerInit { usage_model: "bundled" | "unbound" | undefined; keepVars: boolean | undefined; logpush: boolean | undefined; + placement: CfPlacement | undefined; } export interface CfWorkerContext { From 5b234cfd554aff08d065b96d7d49dfb36f40caa3 Mon Sep 17 00:00:00 2001 From: Joshua Johnson Date: Mon, 8 May 2023 13:50:25 -0500 Subject: [PATCH 17/25] Add output for tail being in sampling mode (#3146) --- .changeset/witty-pumpkins-swim.md | 5 +++ .../__tests__/pages-deployment-tail.test.ts | 2 + packages/wrangler/src/__tests__/tail.test.ts | 45 ++++++++++++++++--- packages/wrangler/src/tail/createTail.ts | 9 ++++ packages/wrangler/src/tail/printing.ts | 10 +++++ 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 .changeset/witty-pumpkins-swim.md diff --git a/.changeset/witty-pumpkins-swim.md b/.changeset/witty-pumpkins-swim.md new file mode 100644 index 000000000000..db8b8e39f191 --- /dev/null +++ b/.changeset/witty-pumpkins-swim.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Added output for tail being in "sampling mode" diff --git a/packages/wrangler/src/__tests__/pages-deployment-tail.test.ts b/packages/wrangler/src/__tests__/pages-deployment-tail.test.ts index 7e4d234562c2..14dd73c40a85 100644 --- a/packages/wrangler/src/__tests__/pages-deployment-tail.test.ts +++ b/packages/wrangler/src/__tests__/pages-deployment-tail.test.ts @@ -13,6 +13,7 @@ import type { ScheduledEvent, AlarmEvent, EmailEvent, + TailInfo, } from "../tail/createTail"; import type { RequestInit } from "undici"; import type WebSocket from "ws"; @@ -655,6 +656,7 @@ function isRequest( | RequestEvent | AlarmEvent | EmailEvent + | TailInfo | undefined | null ): event is RequestEvent { diff --git a/packages/wrangler/src/__tests__/tail.test.ts b/packages/wrangler/src/__tests__/tail.test.ts index 5c72d8aaca4b..581b2233833d 100644 --- a/packages/wrangler/src/__tests__/tail.test.ts +++ b/packages/wrangler/src/__tests__/tail.test.ts @@ -14,6 +14,7 @@ import type { ScheduledEvent, AlarmEvent, EmailEvent, + TailInfo, } from "../tail/createTail"; import type { RequestInit } from "undici"; import type WebSocket from "ws"; @@ -56,10 +57,10 @@ describe("tail", () => { await runWrangler("tail durable-object--websocket--response"); expect(std.out).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(` -"▲ [WARNING] Beginning log collection requires restarting the Durable Objects associated with durable-object--websocket--response. Any WebSocket connections or other non-persisted state will be lost as part of this restart. + "▲ [WARNING] Beginning log collection requires restarting the Durable Objects associated with durable-object--websocket--response. Any WebSocket connections or other non-persisted state will be lost as part of this restart. -" -`); + " + `); expect(std.err).toMatchInlineSnapshot(`""`); }); it("creates and then delete tails", async () => { @@ -503,10 +504,31 @@ describe("tail", () => { ) .replace(mockTailExpiration.toISOString(), "[mock expiration date]") ).toMatchInlineSnapshot(` - "Successfully created tail, expires at [mock expiration date] - Connected to test-worker, waiting for logs... - Email from:${mockEmailEventFrom} to:${mockEmailEventTo} size:${mockEmailEventSize} @ [mock event timestamp] - Ok" - `); + "Successfully created tail, expires at [mock expiration date] + Connected to test-worker, waiting for logs... + Email from:from@example.com to:to@example.com size:45416 @ [mock event timestamp] - Ok" + `); + }); + + it("logs tail overload message", async () => { + const api = mockWebsocketAPIs(); + await runWrangler("tail test-worker --format pretty"); + + const event = generateTailInfo(); + const message = generateMockEventMessage({ event }); + const serializedMessage = serialize(message); + + api.ws.send(serializedMessage); + expect( + std.out.replace( + mockTailExpiration.toISOString(), + "[mock expiration date]" + ) + ).toMatchInlineSnapshot(` + "Successfully created tail, expires at [mock expiration date] + Connected to test-worker, waiting for logs... + Tail is currently in sampling mode due to the high volume of messages. To prevent messages from being dropped consider adding filters." + `); }); it("should not crash when the tail message has a void event", async () => { @@ -675,6 +697,7 @@ function isRequest( | RequestEvent | AlarmEvent | EmailEvent + | TailInfo | undefined | null ): event is RequestEvent { @@ -956,3 +979,11 @@ function generateMockEmailEvent(opts?: Partial): EmailEvent { rawSize: opts?.rawSize || mockEmailEventSize, }; } + +function generateTailInfo(): TailInfo { + return { + message: + "Tail is currently in sampling mode due to the high volume of messages. To prevent messages from being dropped consider adding filters.", + type: "overload", + }; +} diff --git a/packages/wrangler/src/tail/createTail.ts b/packages/wrangler/src/tail/createTail.ts index bd447a98dd1e..89d470726155 100644 --- a/packages/wrangler/src/tail/createTail.ts +++ b/packages/wrangler/src/tail/createTail.ts @@ -252,6 +252,7 @@ export type TailEventMessage = { | ScheduledEvent | AlarmEvent | EmailEvent + | TailInfo | undefined | null; }; @@ -404,3 +405,11 @@ export type EmailEvent = { */ rawSize: number; }; + +/** + * Message from tail with information about the tail itself + */ +export type TailInfo = { + message: string; + type: string; +}; diff --git a/packages/wrangler/src/tail/printing.ts b/packages/wrangler/src/tail/printing.ts index 43d1bf5a2708..abf609a28f4f 100644 --- a/packages/wrangler/src/tail/printing.ts +++ b/packages/wrangler/src/tail/printing.ts @@ -1,9 +1,11 @@ +import chalk from "chalk"; import { logger } from "../logger"; import type { AlarmEvent, EmailEvent, RequestEvent, ScheduledEvent, + TailInfo, TailEventMessage, } from "./createTail"; import type { Outcome } from "./filters"; @@ -48,6 +50,10 @@ export function prettyPrintLogs(data: WebSocket.RawData): void { ).toLocaleString(); logger.log(`Alarm @ ${datetime} - ${outcome}`); + } else if (isTailInfo(eventMessage.event)) { + if (eventMessage.event.type === "overload") { + logger.log(`${chalk.red.bold(eventMessage.event.message)}`); + } } else { // Unknown event type const outcome = prettifyOutcome(eventMessage.outcome); @@ -103,6 +109,10 @@ function isAlarmEvent(event: TailEventMessage["event"]): event is AlarmEvent { return Boolean(event && "scheduledTime" in event && !("cron" in event)); } +function isTailInfo(event: TailEventMessage["event"]): event is TailInfo { + return Boolean(event && "message" in event && "type" in event); +} + function prettifyOutcome(outcome: Outcome): string { switch (outcome) { case "ok": From 738e52f13f141a5d8ba13a299cf106a9477b9df2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 15:58:18 -0400 Subject: [PATCH 18/25] Version Packages (#3149) Co-authored-by: github-actions[bot] --- .changeset/cuddly-rules-rest.md | 5 ----- .changeset/funny-spies-melt.md | 7 ------- .changeset/metal-buckets-fly.md | 5 ----- .changeset/short-bottles-smell.md | 12 ------------ .changeset/six-chicken-shave.md | 7 ------- .changeset/witty-pumpkins-swim.md | 5 ----- fixtures/worker-ts/package.json | 2 +- package-lock.json | 6 +++--- packages/wrangler/CHANGELOG.md | 29 +++++++++++++++++++++++++++++ packages/wrangler/package.json | 2 +- 10 files changed, 34 insertions(+), 46 deletions(-) delete mode 100644 .changeset/cuddly-rules-rest.md delete mode 100644 .changeset/funny-spies-melt.md delete mode 100644 .changeset/metal-buckets-fly.md delete mode 100644 .changeset/short-bottles-smell.md delete mode 100644 .changeset/six-chicken-shave.md delete mode 100644 .changeset/witty-pumpkins-swim.md diff --git a/.changeset/cuddly-rules-rest.md b/.changeset/cuddly-rules-rest.md deleted file mode 100644 index 2b61ef0d788d..000000000000 --- a/.changeset/cuddly-rules-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"wrangler": minor ---- - -feat: Add support for the undocumented `_worker.js/` directory in Pages diff --git a/.changeset/funny-spies-melt.md b/.changeset/funny-spies-melt.md deleted file mode 100644 index a008aca4f13d..000000000000 --- a/.changeset/funny-spies-melt.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": patch ---- - -fix: do not render "value of stdout.lastframe() is undefined" if the output is an empty string - -Fixes #2907 diff --git a/.changeset/metal-buckets-fly.md b/.changeset/metal-buckets-fly.md deleted file mode 100644 index d8077c5d5e7c..000000000000 --- a/.changeset/metal-buckets-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"wrangler": patch ---- - -fix pages building not taking into account the nodejs_compat flag (and improve the related error message) diff --git a/.changeset/short-bottles-smell.md b/.changeset/short-bottles-smell.md deleted file mode 100644 index e32fcf01b3b4..000000000000 --- a/.changeset/short-bottles-smell.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"wrangler": minor ---- - -feat: add support for placement in wrangler config - -Allows a `placement` object in the wrangler config with a mode of `off` or `smart` to configure [Smart placement](https://developers.cloudflare.com/workers/platform/smart-placement/). Enabling Smart Placement can be done in your `wrangler.toml` like: - -```toml -[placement] -mode = "smart" -``` diff --git a/.changeset/six-chicken-shave.md b/.changeset/six-chicken-shave.md deleted file mode 100644 index 654e24631d2f..000000000000 --- a/.changeset/six-chicken-shave.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": minor ---- - -feat: Support sourcemaps in DevTools - -Intercept requests from DevTools in Wrangler to inject sourcemaps and enable folders in the Sources Panel of DevTools. When errors are thrown in your Worker, DevTools should now show your source file in the Sources panel, rather than Wrangler's bundled output. diff --git a/.changeset/witty-pumpkins-swim.md b/.changeset/witty-pumpkins-swim.md deleted file mode 100644 index db8b8e39f191..000000000000 --- a/.changeset/witty-pumpkins-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"wrangler": patch ---- - -Added output for tail being in "sampling mode" diff --git a/fixtures/worker-ts/package.json b/fixtures/worker-ts/package.json index 9fefef2bfb68..7cdc147aa87f 100644 --- a/fixtures/worker-ts/package.json +++ b/fixtures/worker-ts/package.json @@ -8,6 +8,6 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20230419.0", - "wrangler": "2.19.0" + "wrangler": "2.20.0" } } diff --git a/package-lock.json b/package-lock.json index 67d9b0627754..be2edd74a47c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -406,7 +406,7 @@ "version": "0.0.0", "devDependencies": { "@cloudflare/workers-types": "^4.20230419.0", - "wrangler": "2.19.0" + "wrangler": "2.20.0" } }, "fixtures/worker-ts/node_modules/@cloudflare/workers-types": { @@ -45480,7 +45480,7 @@ "dev": true }, "packages/wrangler": { - "version": "2.19.0", + "version": "2.20.0", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", @@ -93568,7 +93568,7 @@ "version": "file:fixtures/worker-ts", "requires": { "@cloudflare/workers-types": "^4.20230419.0", - "wrangler": "2.19.0" + "wrangler": "2.20.0" }, "dependencies": { "@cloudflare/workers-types": { diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index 73fb3c2e5074..2ace1fd6e783 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,34 @@ # wrangler +## 2.20.0 + +### Minor Changes + +- [#2966](https://github.com/cloudflare/workers-sdk/pull/2966) [`e351afcf`](https://github.com/cloudflare/workers-sdk/commit/e351afcff4f265f85ff3e4674cc3083eb5cd5027) Thanks [@GregBrimble](https://github.com/GregBrimble)! - feat: Add support for the undocumented `_worker.js/` directory in Pages + +* [#3095](https://github.com/cloudflare/workers-sdk/pull/3095) [`133c0423`](https://github.com/cloudflare/workers-sdk/commit/133c0423ccb4c2b35a1dd26157ce9a24c6a743bb) Thanks [@zebp](https://github.com/zebp)! - feat: add support for placement in wrangler config + + Allows a `placement` object in the wrangler config with a mode of `off` or `smart` to configure [Smart placement](https://developers.cloudflare.com/workers/platform/smart-placement/). Enabling Smart Placement can be done in your `wrangler.toml` like: + + ```toml + [placement] + mode = "smart" + ``` + +- [#3140](https://github.com/cloudflare/workers-sdk/pull/3140) [`5fd080c8`](https://github.com/cloudflare/workers-sdk/commit/5fd080c88ee7991cde107f8723f06ea2fd2c651d) Thanks [@penalosa](https://github.com/penalosa)! - feat: Support sourcemaps in DevTools + + Intercept requests from DevTools in Wrangler to inject sourcemaps and enable folders in the Sources Panel of DevTools. When errors are thrown in your Worker, DevTools should now show your source file in the Sources panel, rather than Wrangler's bundled output. + +### Patch Changes + +- [#2912](https://github.com/cloudflare/workers-sdk/pull/2912) [`5079f476`](https://github.com/cloudflare/workers-sdk/commit/5079f4767f862cb7c42f4b2b5484b0391fbe5fae) Thanks [@petebacondarwin](https://github.com/petebacondarwin)! - fix: do not render "value of stdout.lastframe() is undefined" if the output is an empty string + + Fixes #2907 + +* [#3133](https://github.com/cloudflare/workers-sdk/pull/3133) [`d0788008`](https://github.com/cloudflare/workers-sdk/commit/d078800804899c3c8e083260f8cfdfc0397d6110) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - fix pages building not taking into account the nodejs_compat flag (and improve the related error message) + +- [#3146](https://github.com/cloudflare/workers-sdk/pull/3146) [`5b234cfd`](https://github.com/cloudflare/workers-sdk/commit/5b234cfd554aff08d065b96d7d49dfb36f40caa3) Thanks [@jspspike](https://github.com/jspspike)! - Added output for tail being in "sampling mode" + ## 2.19.0 ### Minor Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 40cd15fefa37..38f59547f736 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "2.19.0", + "version": "2.20.0", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "wrangler", From 88ff9d7d848a4f2bb0f836c3b2758434334d5144 Mon Sep 17 00:00:00 2001 From: Max Rozen <3822106+rozenmd@users.noreply.github.com> Date: Tue, 9 May 2023 11:43:38 +0200 Subject: [PATCH 19/25] [wrangler] teach d1 about location hints (#3168) --- .changeset/stale-onions-wait.md | 5 +++++ packages/wrangler/src/d1/constants.ts | 1 + packages/wrangler/src/d1/create.tsx | 26 ++++++++++++++++++-------- packages/wrangler/src/d1/types.ts | 7 +++++++ 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 .changeset/stale-onions-wait.md diff --git a/.changeset/stale-onions-wait.md b/.changeset/stale-onions-wait.md new file mode 100644 index 000000000000..1e77ec692660 --- /dev/null +++ b/.changeset/stale-onions-wait.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +feat: (alpha) - make it possible to give D1 a hint on where it should create the database diff --git a/packages/wrangler/src/d1/constants.ts b/packages/wrangler/src/d1/constants.ts index 05ec00921848..f6a0b8911297 100644 --- a/packages/wrangler/src/d1/constants.ts +++ b/packages/wrangler/src/d1/constants.ts @@ -1,2 +1,3 @@ export const DEFAULT_MIGRATION_PATH = "./migrations"; export const DEFAULT_MIGRATION_TABLE = "d1_migrations"; +export const LOCATION_CHOICES = ["weur", "eeur", "apac", "wnam", "enam"]; diff --git a/packages/wrangler/src/d1/create.tsx b/packages/wrangler/src/d1/create.tsx index 69bdbe5383ed..f8da6200bafa 100644 --- a/packages/wrangler/src/d1/create.tsx +++ b/packages/wrangler/src/d1/create.tsx @@ -5,12 +5,13 @@ import { withConfig } from "../config"; import { logger } from "../logger"; import { requireAuth } from "../user"; import { renderToString } from "../utils/render"; +import { LOCATION_CHOICES } from "./constants"; import { d1BetaWarning } from "./utils"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; -import type { Database } from "./types"; +import type { DatabaseCreationResult } from "./types"; export function Options(yargs: CommonYargsArgv) { return yargs @@ -19,17 +20,23 @@ export function Options(yargs: CommonYargsArgv) { type: "string", demandOption: true, }) + .option("location", { + describe: + "A hint for the primary location of the new DB. Options:\nweur: Western Europe\neeur: Eastern Europe\napac: Asia Pacific\nwnam: Western North America\nenam: Eastern North America \n", + type: "string", + choices: LOCATION_CHOICES, + }) .epilogue(d1BetaWarning); } type HandlerOptions = StrictYargsOptionsToInterface; export const Handler = withConfig( - async ({ name, config }): Promise => { + async ({ name, config, location }): Promise => { const accountId = await requireAuth(config); logger.log(d1BetaWarning); - let db: Database; + let db: DatabaseCreationResult; try { db = await fetchResult(`/accounts/${accountId}/d1/database`, { method: "POST", @@ -38,6 +45,7 @@ export const Handler = withConfig( }, body: JSON.stringify({ name, + ...(location && { primary_location_hint: location }), }), }); } catch (e) { @@ -50,14 +58,16 @@ export const Handler = withConfig( logger.log( renderToString( - ✅ Successfully created DB '{db.name}'! -   - Add the following to your wrangler.toml to connect to it from a - Worker: + ✅ Successfully created DB '{db.name}' + {db.created_in_region + ? ` in region ${db.created_in_region}` + : location + ? ` using primary location hint ${location}` + : ``}   - [[ d1_databases ]] + [[d1_databases]] binding = "DB" # i.e. available in your Worker on env.DB diff --git a/packages/wrangler/src/d1/types.ts b/packages/wrangler/src/d1/types.ts index 43ec33d92602..766b84c8913d 100644 --- a/packages/wrangler/src/d1/types.ts +++ b/packages/wrangler/src/d1/types.ts @@ -23,3 +23,10 @@ export type Migration = { name: string; applied_at: string; }; + +export type DatabaseCreationResult = { + uuid: string; + name: string; + primary_location_hint?: string; + created_in_region?: string; +}; From 6ccc4fa672204ec671a1c99db04b4acbd52d5f20 Mon Sep 17 00:00:00 2001 From: oustn <16217347+oustn@users.noreply.github.com> Date: Wed, 10 May 2023 02:24:00 +0800 Subject: [PATCH 20/25] [wrangler] Fix local mode the registry server closed (#3048) * fix: fix local mode the registry server closed * test: add devRegistry restart unit test & update changeset --------- Co-authored-by: Max Rozen <3822106+rozenmd@users.noreply.github.com> Co-authored-by: zengk --- .changeset/gentle-yaks-repeat.md | 9 +++ .../src/__tests__/unstableDev.test.ts | 61 +++++++++++++++++++ packages/wrangler/src/dev-registry.ts | 11 ++++ 3 files changed, 81 insertions(+) create mode 100644 .changeset/gentle-yaks-repeat.md create mode 100644 packages/wrangler/src/__tests__/unstableDev.test.ts diff --git a/.changeset/gentle-yaks-repeat.md b/.changeset/gentle-yaks-repeat.md new file mode 100644 index 000000000000..6f84cd0dc616 --- /dev/null +++ b/.changeset/gentle-yaks-repeat.md @@ -0,0 +1,9 @@ +--- +"wrangler": patch +--- + +Fix: fix local registry server closed + +Closes [#1920](https://github.com/cloudflare/workers-sdk/issues/1920). Sometimes start the local dev server will kill +the devRegistry server so that the devRegistry server can't be used. We can listen the devRegistry server close event +and reset server to `null`. When registerWorker is called, we can check if the server is `null` and start a new server. diff --git a/packages/wrangler/src/__tests__/unstableDev.test.ts b/packages/wrangler/src/__tests__/unstableDev.test.ts new file mode 100644 index 000000000000..ba4ba36c3851 --- /dev/null +++ b/packages/wrangler/src/__tests__/unstableDev.test.ts @@ -0,0 +1,61 @@ +import { fetch } from "undici"; +import { + stopWorkerRegistry, + registerWorker, + startWorkerRegistry, +} from "../dev-registry"; + +jest.unmock("undici"); + +/** + * Sometimes the devRegistry killed by some reason, the register worker will to restart it. + */ +describe("unstable devRegistry testing", () => { + afterAll(async () => { + await stopWorkerRegistry(); + }); + + it("should start the devRegistry if the devRegistry not start", async () => { + await registerWorker("test", { + port: 6789, + protocol: "http", + host: "localhost", + mode: "local", + durableObjects: [{ name: "testing", className: "testing" }], + }); + const resp = await fetch("http://localhost:6284/workers"); + if (resp) { + const parsedResp = (await resp.json()) as { + test: unknown; + }; + expect(parsedResp.test).toBeTruthy(); + } + }); + + it("should not restart the devRegistry if the devRegistry already start", async () => { + await startWorkerRegistry(); + + await fetch("http://localhost:6284/workers/init", { + method: "POST", + body: JSON.stringify({}), + }); + + await registerWorker("test", { + port: 6789, + protocol: "http", + host: "localhost", + mode: "local", + durableObjects: [{ name: "testing", className: "testing" }], + }); + + const resp = await fetch("http://localhost:6284/workers"); + if (resp) { + const parsedResp = (await resp.json()) as { + test: unknown; + init: unknown; + }; + expect(parsedResp.init).toBeTruthy(); + expect(parsedResp.test).toBeTruthy(); + } + }); +}); diff --git a/packages/wrangler/src/dev-registry.ts b/packages/wrangler/src/dev-registry.ts index 092018c1721e..5a5c2d244b9c 100644 --- a/packages/wrangler/src/dev-registry.ts +++ b/packages/wrangler/src/dev-registry.ts @@ -94,6 +94,13 @@ export async function startWorkerRegistry() { throw err; } }); + + /** + * The registry server may close. Reset the server to null for restart. + */ + server.on("close", () => { + server = null; + }); } } @@ -112,6 +119,10 @@ export async function registerWorker( name: string, definition: WorkerDefinition ) { + /** + * Prevent the dev registry be closed. + */ + await startWorkerRegistry(); try { return await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, { method: "POST", From 7e580718dad6853714b812e0cdcd970ac22338e1 Mon Sep 17 00:00:00 2001 From: Jacob M-G Evans <27247160+JacobMGEvans@users.noreply.github.com> Date: Tue, 9 May 2023 14:40:45 -0500 Subject: [PATCH 21/25] [Solarflare] Color tweaks for in curly brackets (#3142) * Updated the documentation for developing on solarflare in Workers-SDK * Fixed the color issue with #eeffff being used inside of curly brackets --- .changeset/sour-timers-grin.md | 5 +++++ packages/solarflare-theme/README.md | 4 ++-- ...sion-quickstart.md => solarflare-quickstart.md} | 14 ++++++++------ .../themes/cloudflare-light-color-theme.json | 4 ++-- 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 .changeset/sour-timers-grin.md rename packages/solarflare-theme/{vsc-extension-quickstart.md => solarflare-quickstart.md} (56%) diff --git a/.changeset/sour-timers-grin.md b/.changeset/sour-timers-grin.md new file mode 100644 index 000000000000..424e123e9f5d --- /dev/null +++ b/.changeset/sour-timers-grin.md @@ -0,0 +1,5 @@ +--- +"solarflare-theme": patch +--- + +Adjusted the text color inside formatted curly brackets. Color was #eeffff is now #62676A which is uniform with the rest of the light theme. diff --git a/packages/solarflare-theme/README.md b/packages/solarflare-theme/README.md index 4e663c363ece..d18fe4e3f6f3 100644 --- a/packages/solarflare-theme/README.md +++ b/packages/solarflare-theme/README.md @@ -1,3 +1,3 @@ -# Solarflare Theme README +# Solarflare Theme -The Solarflare theme is an official VSCode theme for utilization with modified VSCode for Web in the Cloudflare dashboard. +The Solarflare theme is an official VSCode theme for utilization with a modified VSCode for Web in the Cloudflare dashboard. diff --git a/packages/solarflare-theme/vsc-extension-quickstart.md b/packages/solarflare-theme/solarflare-quickstart.md similarity index 56% rename from packages/solarflare-theme/vsc-extension-quickstart.md rename to packages/solarflare-theme/solarflare-quickstart.md index 886a56ff5022..bbe17a4b5626 100644 --- a/packages/solarflare-theme/vsc-extension-quickstart.md +++ b/packages/solarflare-theme/solarflare-quickstart.md @@ -1,4 +1,4 @@ -# Welcome to your VS Code Extension +# Developing Solarflare Theme ## What's in the folder @@ -8,9 +8,10 @@ ## Get up and running straight away -- Press `F5` to open a new window with your extension loaded. -- Open `File > Preferences > Color Themes` and pick your color theme. -- Open a file that has a language associated. The languages' configured grammar will tokenize the text and assign 'scopes' to the tokens. To examine these scopes, invoke the `Developer: Inspect Editor Tokens and Scopes` command from the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac). +- In the monorepo, you will need to open VSCode directly in `packages/solarflare-theme` to run the follow steps. + - Press `F5` to open a new window with your extension loaded. + - Open `File > Preferences > Color Themes` and pick your color theme. + - Open a file that has a language associated. The languages' configured grammar will tokenize the text and assign 'scopes' to the tokens. To examine these scopes, invoke the `Developer: Inspect Editor Tokens and Scopes` command from the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac). ## Make changes @@ -22,7 +23,8 @@ To learn more about scopes and how they're used, check out the [color theme](https://code.visualstudio.com/api/extension-guides/color-theme) documentation. -## Install your extension + diff --git a/packages/solarflare-theme/themes/cloudflare-light-color-theme.json b/packages/solarflare-theme/themes/cloudflare-light-color-theme.json index 213f404d50e5..75f24ab329f9 100644 --- a/packages/solarflare-theme/themes/cloudflare-light-color-theme.json +++ b/packages/solarflare-theme/themes/cloudflare-light-color-theme.json @@ -3,7 +3,7 @@ "colors": { "focusBorder": "#4693FF", "editor.background": "#ffffff", - "editor.foreground": "#eeffff", + "editor.foreground": "#62676A", "list.activeSelectionIconForeground": "#ffffff", "activityBar.background": "#f2f2f2", "activityBarBadge.background": "#0051c3", @@ -23,7 +23,7 @@ "name": "Colors", "scope": ["constant.other.color", "support.variable"], "settings": { - "foreground": "#2C3E50" + "foreground": "#eeffff" } }, { From 1b67a405b7777efc401a44f59389ef00654b439d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Thu, 11 May 2023 14:18:59 +0100 Subject: [PATCH 22/25] [wrangler] fix: constellation command help (#3153) Improved help for constellation commands and added optional parameter on model upload to specify a model description. --- .changeset/serious-crews-mix.md | 7 +++++ .../src/constellation/deleteProjectModel.ts | 16 ++++++++--- packages/wrangler/src/constellation/index.ts | 6 ++-- .../wrangler/src/constellation/listModel.tsx | 16 ++++++++--- .../src/constellation/uploadModel.tsx | 28 ++++++++++++++++--- 5 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 .changeset/serious-crews-mix.md diff --git a/.changeset/serious-crews-mix.md b/.changeset/serious-crews-mix.md new file mode 100644 index 000000000000..08f09df5f75d --- /dev/null +++ b/.changeset/serious-crews-mix.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +[wrangler] fix: constellation command help + +Changed `name` to `projectName` in some Constellation commands that interacted with projects. Also added the possibility to specify a model description when uploading it. diff --git a/packages/wrangler/src/constellation/deleteProjectModel.ts b/packages/wrangler/src/constellation/deleteProjectModel.ts index 948de8f2a7f9..088301adca15 100644 --- a/packages/wrangler/src/constellation/deleteProjectModel.ts +++ b/packages/wrangler/src/constellation/deleteProjectModel.ts @@ -3,7 +3,6 @@ import { withConfig } from "../config"; import { confirm } from "../dialogs"; import { logger } from "../logger"; import { requireAuth } from "../user"; -import { takeName } from "./options"; import { constellationBetaWarning, getProjectByName, @@ -16,7 +15,12 @@ import type { import type { Project, Model } from "./types"; export function options(yargs: CommonYargsArgv) { - return takeName(yargs) + return yargs + .positional("projectName", { + describe: "The name of the project", + type: "string", + demandOption: true, + }) .positional("modelName", { describe: "The name of the uploaded model", type: "string", @@ -32,11 +36,15 @@ export function options(yargs: CommonYargsArgv) { } type HandlerOptions = StrictYargsOptionsToInterface; export const handler = withConfig( - async ({ name, modelName, force, config }): Promise => { + async ({ projectName, modelName, force, config }): Promise => { const accountId = await requireAuth(config); logger.log(constellationBetaWarning); - const proj: Project = await getProjectByName(config, accountId, name); + const proj: Project = await getProjectByName( + config, + accountId, + projectName + ); const model: Model = await getProjectModelByName( config, diff --git a/packages/wrangler/src/constellation/index.ts b/packages/wrangler/src/constellation/index.ts index dcd4a8b6fb9a..8e5303a34206 100644 --- a/packages/wrangler/src/constellation/index.ts +++ b/packages/wrangler/src/constellation/index.ts @@ -34,19 +34,19 @@ export function constellation(yargs: CommonYargsArgv) { .command("model", "Manage your models", (constModelYargs) => { return constModelYargs .command( - "upload ", + "upload ", "Upload a model for an existing project", UploadModel.options, UploadModel.handler ) .command( - "list ", + "list ", "List models of a project", ListModel.options, ListModel.handler ) .command( - "delete ", + "delete ", "Delete a model of a project", DeleteProjectModel.options, DeleteProjectModel.handler diff --git a/packages/wrangler/src/constellation/listModel.tsx b/packages/wrangler/src/constellation/listModel.tsx index 2214fe59a54d..743feb24ca92 100644 --- a/packages/wrangler/src/constellation/listModel.tsx +++ b/packages/wrangler/src/constellation/listModel.tsx @@ -1,7 +1,6 @@ import { withConfig } from "../config"; import { logger } from "../logger"; import { requireAuth } from "../user"; -import { takeName } from "./options"; import { constellationBetaWarning, getProjectByName, @@ -14,7 +13,12 @@ import type { import type { Project } from "./types"; export function options(yargs: CommonYargsArgv) { - return takeName(yargs) + return yargs + .positional("projectName", { + describe: "The name of the project", + type: "string", + demandOption: true, + }) .option("json", { describe: "return output as clean JSON", type: "boolean", @@ -25,9 +29,13 @@ export function options(yargs: CommonYargsArgv) { type HandlerOptions = StrictYargsOptionsToInterface; export const handler = withConfig( - async ({ name, json, config }): Promise => { + async ({ projectName, json, config }): Promise => { const accountId = await requireAuth(config); - const proj: Project = await getProjectByName(config, accountId, name); + const proj: Project = await getProjectByName( + config, + accountId, + projectName + ); const models = await listModels(accountId, proj); diff --git a/packages/wrangler/src/constellation/uploadModel.tsx b/packages/wrangler/src/constellation/uploadModel.tsx index 6b1808650dda..c091336b0758 100644 --- a/packages/wrangler/src/constellation/uploadModel.tsx +++ b/packages/wrangler/src/constellation/uploadModel.tsx @@ -4,7 +4,6 @@ import { fetchResult } from "../cfetch"; import { withConfig } from "../config"; import { logger } from "../logger"; import { requireAuth } from "../user"; -import { takeName } from "./options"; import { constellationBetaWarning, getProjectByName } from "./utils"; import type { CommonYargsArgv, @@ -13,7 +12,12 @@ import type { import type { Model } from "./types"; export function options(yargs: CommonYargsArgv) { - return takeName(yargs) + return yargs + .positional("projectName", { + describe: "The name of the project", + type: "string", + demandOption: true, + }) .positional("modelName", { describe: "The name of the uploaded model", type: "string", @@ -24,15 +28,27 @@ export function options(yargs: CommonYargsArgv) { type: "string", demandOption: true, }) + .option("description", { + describe: "include a description of the model", + type: "string", + demandOption: false, + alias: "d", + }) .epilogue(constellationBetaWarning); } type HandlerOptions = StrictYargsOptionsToInterface; export const handler = withConfig( - async ({ name, modelName, modelFile, config }): Promise => { + async ({ + projectName, + modelName, + modelFile, + description, + config, + }): Promise => { const accountId = await requireAuth(config); logger.log(constellationBetaWarning); - const proj = await getProjectByName(config, accountId, name); + const proj = await getProjectByName(config, accountId, projectName); const formData = new FormData(); formData.set( @@ -43,6 +59,10 @@ export const handler = withConfig( ); formData.set("name", modelName); + if (description !== undefined) { + formData.set("description", description); + } + let model: Model; try { model = await fetchResult( From cc2adc2e8b28d1e38563bf72085d44e330563b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Thu, 11 May 2023 16:04:30 +0100 Subject: [PATCH 23/25] [wrangler] feat: Support for Browser Workers (#3135) --- .changeset/twenty-donkeys-drum.md | 8 +++ .../src/__tests__/configuration.test.ts | 59 +++++++++++++++++++ .../pages/create-worker-bundle-contents.ts | 1 + packages/wrangler/src/config/environment.ts | 9 +++ packages/wrangler/src/config/index.ts | 8 +++ packages/wrangler/src/config/validation.ts | 37 ++++++++++++ .../wrangler/src/create-worker-upload-form.ts | 8 +++ packages/wrangler/src/dev.tsx | 1 + packages/wrangler/src/init.ts | 7 +++ packages/wrangler/src/publish/publish.ts | 1 + packages/wrangler/src/secret/index.ts | 1 + packages/wrangler/src/worker.ts | 9 +++ 12 files changed, 149 insertions(+) create mode 100644 .changeset/twenty-donkeys-drum.md diff --git a/.changeset/twenty-donkeys-drum.md b/.changeset/twenty-donkeys-drum.md new file mode 100644 index 000000000000..878664590db0 --- /dev/null +++ b/.changeset/twenty-donkeys-drum.md @@ -0,0 +1,8 @@ +--- +"wrangler": minor +--- + +[wrangler] feat: Support for Browser Workers + +These bindings allow one to use puppeteer to control a browser +in a worker script. diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index 86d0fafb9ae5..1ee434115df4 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -62,6 +62,7 @@ describe("normalizeAndValidateConfig()", () => { rules: [], site: undefined, text_blobs: undefined, + browser: undefined, triggers: { crons: [], }, @@ -1559,6 +1560,64 @@ describe("normalizeAndValidateConfig()", () => { }); }); + describe("[browser]", () => { + it("should error if browser is an array", () => { + const { diagnostics } = normalizeAndValidateConfig( + { browser: [] } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"browser\\" should be an object but got []." + `); + }); + + it("should error if browser is a string", () => { + const { diagnostics } = normalizeAndValidateConfig( + { browser: "BAD" } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"browser\\" should be an object but got \\"BAD\\"." + `); + }); + + it("should error if browser is a number", () => { + const { diagnostics } = normalizeAndValidateConfig( + { browser: 999 } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"browser\\" should be an object but got 999." + `); + }); + + it("should error if browser is null", () => { + const { diagnostics } = normalizeAndValidateConfig( + { browser: null } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"browser\\" should be an object but got null." + `); + }); + }); + describe("[kv_namespaces]", () => { it("should error if kv_namespaces is an object", () => { const { diagnostics } = normalizeAndValidateConfig( diff --git a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts index e44c16a03533..fbba83e80280 100644 --- a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts +++ b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts @@ -52,6 +52,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData { send_email: undefined, wasm_modules: undefined, text_blobs: undefined, + browser: undefined, data_blobs: undefined, durable_objects: undefined, queues: undefined, diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index 092c47851b01..ee587e57f9c5 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -491,6 +491,15 @@ interface EnvironmentNonInheritable { dataset?: string; }[]; + /** + * A browser that will be usable from the worker. + */ + browser: + | { + binding: string; + } + | undefined; + /** * "Unsafe" tables for features that aren't directly supported by wrangler. * diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index 9f6a705ea2f5..7b5f910791c8 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -105,6 +105,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { services, analytics_engine_datasets, text_blobs, + browser, unsafe, vars, wasm_modules, @@ -273,6 +274,13 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { }); } + if (browser !== undefined) { + output.push({ + type: "Browser", + entries: [{ key: "Name", value: browser.binding }], + }); + } + if (unsafe?.bindings !== undefined && unsafe.bindings.length > 0) { output.push({ type: "Unsafe", diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 51b063349b06..07ab4d5b3868 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -1233,6 +1233,16 @@ function normalizeAndValidateEnvironment( validateUnsafeSettings(envName), {} ), + browser: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "browser", + validateBrowserBinding(envName), + undefined + ), zone_id: rawEnv.zone_id, no_bundle: inheritable( diagnostics, @@ -1690,6 +1700,30 @@ const validateCflogfwdrBinding: ValidatorFn = (diagnostics, field, value) => { return isValid; }; +const validateBrowserBinding = + (envName: string): ValidatorFn => + (diagnostics, field, value, config) => { + const fieldPath = + config === undefined ? `${field}` : `env.${envName}.${field}`; + + if (typeof value !== "object" || value === null || Array.isArray(value)) { + diagnostics.errors.push( + `The field "${fieldPath}" should be an object but got ${JSON.stringify( + value + )}.` + ); + return false; + } + + let isValid = true; + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push(`binding should have a string "binding" field.`); + isValid = false; + } + + return isValid; + }; + /** * Check that the given field is a valid "unsafe" binding object. * @@ -1716,6 +1750,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "wasm_module", "data_blob", "text_blob", + "browser", "kv_namespace", "durable_object_namespace", "d1_database", @@ -2025,6 +2060,7 @@ const validateBindingsHaveUniqueNames = ( r2_buckets, analytics_engine_datasets, text_blobs, + browser, unsafe, vars, define, @@ -2040,6 +2076,7 @@ const validateBindingsHaveUniqueNames = ( "R2 Bucket": getBindingNames(r2_buckets), "Analytics Engine Dataset": getBindingNames(analytics_engine_datasets), "Text Blob": getBindingNames(text_blobs), + Browser: getBindingNames(browser), Unsafe: getBindingNames(unsafe), "Environment Variable": getBindingNames(vars), Definition: getBindingNames(define), diff --git a/packages/wrangler/src/create-worker-upload-form.ts b/packages/wrangler/src/create-worker-upload-form.ts index 592979bd9621..34a70f528ba7 100644 --- a/packages/wrangler/src/create-worker-upload-form.ts +++ b/packages/wrangler/src/create-worker-upload-form.ts @@ -31,6 +31,7 @@ export type WorkerMetadataBinding = | { type: "json"; name: string; json: unknown } | { type: "wasm_module"; name: string; part: string } | { type: "text_blob"; name: string; part: string } + | { type: "browser"; name: string } | { type: "data_blob"; name: string; part: string } | { type: "kv_namespace"; name: string; namespace_id: string } | { @@ -220,6 +221,13 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { ); } + if (bindings.browser !== undefined) { + metadataBindings.push({ + name: bindings.browser.binding, + type: "browser", + }); + } + for (const [name, filePath] of Object.entries(bindings.text_blobs || {})) { metadataBindings.push({ name, diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 046f067e1df0..8a9ab11184fb 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -865,6 +865,7 @@ function getBindings( }, wasm_modules: configParam.wasm_modules, text_blobs: configParam.text_blobs, + browser: configParam.browser, data_blobs: configParam.data_blobs, durable_objects: { bindings: [ diff --git a/packages/wrangler/src/init.ts b/packages/wrangler/src/init.ts index 6b0bb26b55f3..d1a3aa5f78d5 100644 --- a/packages/wrangler/src/init.ts +++ b/packages/wrangler/src/init.ts @@ -947,6 +947,13 @@ export function mapBindings(bindings: WorkerMetadataBinding[]): RawConfig { }; } break; + case "browser": + { + configObj.browser = { + binding: binding.name, + }; + } + break; case "r2_bucket": { configObj.r2_buckets = [ diff --git a/packages/wrangler/src/publish/publish.ts b/packages/wrangler/src/publish/publish.ts index 8c163fe4d571..c8db4d1f2150 100644 --- a/packages/wrangler/src/publish/publish.ts +++ b/packages/wrangler/src/publish/publish.ts @@ -538,6 +538,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m send_email: config.send_email, vars: { ...config.vars, ...props.vars }, wasm_modules: config.wasm_modules, + browser: config.browser, text_blobs: { ...config.text_blobs, ...(assets.manifest && diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index c3f0be359d26..8563f8efa5b0 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -109,6 +109,7 @@ export const secret = (secretYargs: CommonYargsArgv) => { services: [], analytics_engine_datasets: [], wasm_modules: {}, + browser: undefined, text_blobs: {}, data_blobs: {}, dispatch_namespaces: [], diff --git a/packages/wrangler/src/worker.ts b/packages/wrangler/src/worker.ts index 9d02b12b5ac1..bb81ce592e47 100644 --- a/packages/wrangler/src/worker.ts +++ b/packages/wrangler/src/worker.ts @@ -104,6 +104,14 @@ export interface CfTextBlobBindings { [key: string]: string; } +/** + * A binding to a browser + */ + +export interface CfBrowserBinding { + binding: string; +} + /** * A binding to a data blob (in service-worker format) */ @@ -232,6 +240,7 @@ export interface CfWorkerInit { send_email: CfSendEmailBindings[] | undefined; wasm_modules: CfWasmModuleBindings | undefined; text_blobs: CfTextBlobBindings | undefined; + browser: CfBrowserBinding | undefined; data_blobs: CfDataBlobBindings | undefined; durable_objects: { bindings: CfDurableObject[] } | undefined; queues: CfQueue[] | undefined; From 7512d4cc3cb3a0d3d6d766aeb1f912fdb8493d0b Mon Sep 17 00:00:00 2001 From: MrBBot Date: Thu, 11 May 2023 17:29:55 +0100 Subject: [PATCH 24/25] chore: upgrade `miniflare` to `2.14.0` (#3150) --- .changeset/wild-olives-compete.md | 6 + package-lock.json | 1192 ++++++----------------- packages/pages-shared/package.json | 6 +- packages/wrangler/package.json | 8 +- templates/worker-speedtest/package.json | 4 +- templates/worker-turso-ts/package.json | 2 +- 6 files changed, 295 insertions(+), 923 deletions(-) create mode 100644 .changeset/wild-olives-compete.md diff --git a/.changeset/wild-olives-compete.md b/.changeset/wild-olives-compete.md new file mode 100644 index 000000000000..7558383b6530 --- /dev/null +++ b/.changeset/wild-olives-compete.md @@ -0,0 +1,6 @@ +--- +"@cloudflare/pages-shared": minor +"wrangler": minor +--- + +chore: upgrade `miniflare` to [`2.14.0`](https://github.com/cloudflare/miniflare/releases/tag/v2.14.0) diff --git a/package-lock.json b/package-lock.json index be2edd74a47c..089957c43797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6907,12 +6907,12 @@ } }, "node_modules/@miniflare/cache": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.13.0.tgz", - "integrity": "sha512-y3SdN3SVyPECWmLAEGkkrv0RB+LugEPs/FeXn8QtN9aE1vyj69clOAgmsDzoh1DpFfFsLKRiv05aWs4m79P8Xw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.14.0.tgz", + "integrity": "sha512-0mz0OCzTegiX75uMURLJpDo3DaOCSx9M0gv7NMFWDbK/XrvjoENiBZiKu98UBM5fts0qtK19a+MfB4aT0uBCFg==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "http-cache-semantics": "^4.1.0", "undici": "5.20.0" }, @@ -6921,11 +6921,11 @@ } }, "node_modules/@miniflare/cli-parser": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.13.0.tgz", - "integrity": "sha512-Nx1PIfuMZ3mK9Dg/JojWZAjHR16h1pcdCFSqYln/ME7y5ifx+P1E5UkShWUQ1cBlibNaltjbJ2n/7stSAsIGPQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.14.0.tgz", + "integrity": "sha512-6Wb0jTMqwI7GRGAhz9WOF8AONUsXXPmwu+Qhg+tnRWtQpJ3DYd5dku1N04L9L1R7np/mD8RrycLyCdg3hLZ3aA==", "dependencies": { - "@miniflare/shared": "2.13.0", + "@miniflare/shared": "2.14.0", "kleur": "^4.1.4" }, "engines": { @@ -6933,14 +6933,14 @@ } }, "node_modules/@miniflare/core": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.13.0.tgz", - "integrity": "sha512-YJ/C0J3k+7xn4gvlMpvePnM3xC8nOnkweW96cc0IA8kJ1JSmScOO2tZ7rrU1RyDgp6StkAtQBw4yC0wYeFycBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.14.0.tgz", + "integrity": "sha512-BjmV/ZDwsKvXnJntYHt3AQgzVKp/5ZzWPpYWoOnUSNxq6nnRCQyvFvjvBZKnhubcmJCLSqegvz0yHejMA90CTA==", "dependencies": { "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/watcher": "2.13.0", + "@miniflare/queues": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/watcher": "2.14.0", "busboy": "^1.6.0", "dotenv": "^10.0.0", "kleur": "^4.1.4", @@ -6961,25 +6961,25 @@ } }, "node_modules/@miniflare/d1": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.13.0.tgz", - "integrity": "sha512-OslqjO8iTcvzyrC0spByftMboRmHJEyHyTHnlKkjWDGdQQztEOjso2Xj+3I4SZIeUYvbzDRhKLS2QXI9a8LS5A==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.14.0.tgz", + "integrity": "sha512-9YoeLAkZuWGAu9BMsoctHoMue0xHzJYZigAJWGvWrqSFT1gBaT+RlUefQCHXggi8P7sOJ1+BKlsWAhkB5wfMWQ==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0" + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.7" } }, "node_modules/@miniflare/durable-objects": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.13.0.tgz", - "integrity": "sha512-CRGVBPO9vY4Fc3aV+pdPRVVeYIt64vQqvw+BJbyW+TQtqVP2CGQeziJGnCfcONNNKyooZxGyUkHewUypyH+Qhg==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.14.0.tgz", + "integrity": "sha512-P8eh1P62BPGpj+MCb1i1lj7Tlt/G3BMmnxHp9duyb0Wro/ILVGPQskZl+iq7DHq1w3C+n0+6/E1B44ff+qn0Mw==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/storage-memory": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/storage-memory": "2.14.0", "undici": "5.20.0" }, "engines": { @@ -6987,12 +6987,12 @@ } }, "node_modules/@miniflare/html-rewriter": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.13.0.tgz", - "integrity": "sha512-XhN7Icyzvtvu+o/A0hrnSiSmla78seCaNwQ9M1TDHxt352I/ahPX4wtPXs6GbKqY0/i+V6yoG2KGFRQ/j59cQQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.14.0.tgz", + "integrity": "sha512-7CJZk3xZkxK8tGNofnhgWcChZ8YLx6MhAdN2nn6ONSXrK/TevzEKdL8bnVv1OJ6J8Y23OxvfinOhufr33tMS8g==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "html-rewriter-wasm": "^0.4.1", "undici": "5.20.0" }, @@ -7001,13 +7001,13 @@ } }, "node_modules/@miniflare/http-server": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.13.0.tgz", - "integrity": "sha512-aMS/nUMTKP15hKnyZboeuWCiqmNrrCu+XRBY/TxDDl07iXcLpiHGf3oVv+yXxXkWlJHJVCbK7i/nXSNPllRMSw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.14.0.tgz", + "integrity": "sha512-APaBlvGRAW+W18ph5ruPXX26/iKdByPz1tZH1OjPAKBDAiKFZSGek4QzUmQALBWLx5VMTMrt7QIe7KE4nM4sdw==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/web-sockets": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/web-sockets": "2.14.0", "kleur": "^4.1.4", "selfsigned": "^2.0.0", "undici": "5.20.0", @@ -7039,33 +7039,34 @@ } }, "node_modules/@miniflare/kv": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.13.0.tgz", - "integrity": "sha512-J0AS5x3g/YVOmHMxMAZs07nRXRvSo9jyuC0eikTBf+4AABvBIyvVYmdTjYNjCmr8O5smcfWBX5S27HelD3aAAQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.14.0.tgz", + "integrity": "sha512-FHAnVjmhV/VHxgjNf2whraz+k7kfMKlfM+5gO8WT6HrOsWxSdx8OueWVScnOuuDkSeUg5Ctrf5SuztTV8Uy1cg==", "dependencies": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/queues": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.13.0.tgz", - "integrity": "sha512-Gf/a6M1mJL03iOvNqh3JNahcBfvEMPHnO28n0gkCoyYWGvddIr9lwCdFIa0qwNJsC1fIDRxhPg8PZ5cQLBMwRA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.14.0.tgz", + "integrity": "sha512-flS4MqlgBKyv6QBqKD0IofjmMDW9wP1prUNQy2wWPih9lA6bFKmml3VdFeDsPnWtE2J67K0vCTf5kj1Q0qdW1w==", "dependencies": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.7" } }, "node_modules/@miniflare/r2": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.13.0.tgz", - "integrity": "sha512-/5k6GHOYMNV/oBtilV9HDXBkJUrx8oXVigG5vxbnzEGRXyVRmR+Glzu7mFT8JiE94XiEbXHk9Qvu1S5Dej3wBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.14.0.tgz", + "integrity": "sha512-+WJJP4J0QzY69HPrG6g5OyW23lJ02WHpHZirCxwPSz8CajooqZCJVx+qvUcNmU8MyKASbUZMWnH79LysuBh+jA==", "dependencies": { - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "undici": "5.20.0" }, "engines": { @@ -7073,23 +7074,23 @@ } }, "node_modules/@miniflare/runner-vm": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.13.0.tgz", - "integrity": "sha512-VmKtF2cA8HmTuLXor1THWY0v+DmaobPct63iLcgWIaUdP3MIvL+9X8HDXFAviCR7bCTe6MKxckHkaOj0IE0aJQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.14.0.tgz", + "integrity": "sha512-01CmNzv74u0RZgT/vjV/ggDzECXTG88ZJAKhXyhAx0s2DOLIXzsGHn6pUJIsfPCrtj8nfqtTCp1Vf0UMVWSpmw==", "dependencies": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/scheduler": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.13.0.tgz", - "integrity": "sha512-AOaQanoR4NjVEzVGWHnrL15A7aMx+d9AKLJhSDF7KaP+4NrT2Wo2BQuXCpn5oStx3itOdlQpMfqQ139e/I8WhQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.14.0.tgz", + "integrity": "sha512-/D28OeY/HxcOyY0rkPc2qU/wzxCcUv3/F7NRpgDix37sMkYjAAS51ehVIAkPwAdFEMdantcyVuHNQ2kO1xbT+Q==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "cron-schedule": "^3.0.4" }, "engines": { @@ -7097,9 +7098,9 @@ } }, "node_modules/@miniflare/shared": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.13.0.tgz", - "integrity": "sha512-m8YFQzKmbjberrV9hPzNcQjNCXxjTjXUpuNrIGjAJO7g+BDztUHaZbdd26H9maBDlkeiWxA3hf0mDyCT/6MCMA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.14.0.tgz", + "integrity": "sha512-O0jAEdMkp8BzrdFCfMWZu76h4Cq+tt3/oDtcTFgzum3fRW5vUhIi/5f6bfndu6rkGbSlzxwor8CJWpzityXGug==", "dependencies": { "@types/better-sqlite3": "^7.6.0", "kleur": "^4.1.4", @@ -7111,60 +7112,60 @@ } }, "node_modules/@miniflare/shared-test-environment": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/shared-test-environment/-/shared-test-environment-2.13.0.tgz", - "integrity": "sha512-I90e0hVdsR0pD0JZoetlw03gpQ05WnXPCHOdhBxROTrfy+YLP19zIFgvJpKC8WyKrcwABRwetWx7tIMI03qU0g==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared-test-environment/-/shared-test-environment-2.14.0.tgz", + "integrity": "sha512-Iarxqo9hR4Gi6i7iF/hXJbHuPCXTZbA4z91Gwhet8dhK6c0zWGU3xi7zjr5XTaawd/cuR1g6bi0cwt/10bAEsg==", "dev": true, "dependencies": { "@cloudflare/workers-types": "^4.20221111.1", - "@miniflare/cache": "2.13.0", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", - "@miniflare/kv": "2.13.0", - "@miniflare/queues": "2.13.0", - "@miniflare/r2": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/sites": "2.13.0", - "@miniflare/storage-memory": "2.13.0", - "@miniflare/web-sockets": "2.13.0" + "@miniflare/cache": "2.14.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", + "@miniflare/kv": "2.14.0", + "@miniflare/queues": "2.14.0", + "@miniflare/r2": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/sites": "2.14.0", + "@miniflare/storage-memory": "2.14.0", + "@miniflare/web-sockets": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/sites": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.13.0.tgz", - "integrity": "sha512-/tuzIu00o6CF2tkSv01q02MgEShXBSKx85h9jwWvc+6u7prGacAOer0FA1YNRFbE+t9QIfutAkoPGMA9zYf8+Q==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.14.0.tgz", + "integrity": "sha512-qI8MFZpD1NV+g+HQ/qheDVwscKzwG58J+kAVTU/1fgub2lMLsxhE3Mmbi5AIpyIiJ7Q5Sezqga234CEkHkS7dA==", "dependencies": { - "@miniflare/kv": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/storage-file": "2.13.0" + "@miniflare/kv": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/storage-file": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/storage-file": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.13.0.tgz", - "integrity": "sha512-LuAeAAY5046rq5U1eFLVkz+ppiFEWytWacpkQw92DvVKFFquZcXSj6WPxZF4rSs23WDk+rdcwuLekbb52aDR7A==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.14.0.tgz", + "integrity": "sha512-Ps0wHhTO+ie33a58efI0p/ppFXSjlbYmykQXfYtMeVLD60CKl+4Lxor0+gD6uYDFbhMWL5/GMDvyr4AM87FA+Q==", "dependencies": { - "@miniflare/shared": "2.13.0", - "@miniflare/storage-memory": "2.13.0" + "@miniflare/shared": "2.14.0", + "@miniflare/storage-memory": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/storage-memory": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.13.0.tgz", - "integrity": "sha512-FnkYcBNXa/ym1ksNilNZycg9WYYKo6cWKplVBeSthRon3e8QY6t3n7/XRseBUo7O6mhDybVTy4wNCP1R2nBiEw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.14.0.tgz", + "integrity": "sha512-5aFjEiTSNrHJ+iAiGMCA/TVPnNMrnokG5r0vKrwj4knbf8pisgfP04x18zCgOlG7kaIWNmqdO/vtVT5BIioiSQ==", "dependencies": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.13" @@ -7262,23 +7263,23 @@ } }, "node_modules/@miniflare/watcher": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.13.0.tgz", - "integrity": "sha512-teAacWcpMStoBLbLae95IUaL5lPzjPlXa9lhK9CbRaio/KRMibTMRGWrYos3IVGQRZvklvLwcms/nTvgcdb6yw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.14.0.tgz", + "integrity": "sha512-O8Abg2eHpGmcZb8WyUaA6Av1Mqt5bSrorzz4CrWwsvJHBdekZPIX0GihC9vn327d/1pKRs81YTiSAfBoSZpVIw==", "dependencies": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" }, "engines": { "node": ">=16.13" } }, "node_modules/@miniflare/web-sockets": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.13.0.tgz", - "integrity": "sha512-+U2/HCf+BetRIgjAnNQjkuN6UeAjQmXifhQC+7CCaX834XJhrKXoR6z2xr2xkg1qj0qQs4D2jWG0KzrO5OUpug==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.14.0.tgz", + "integrity": "sha512-lB1CB4rBq0mbCuh55WgIEH4L3c4/i4MNDBfrQL+6r+wGcr/BJUqF8BHpsfAt5yHWUJVtK5mlMeesS/xpg4Ao1w==", "dependencies": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "undici": "5.20.0", "ws": "^8.2.2" }, @@ -22820,18 +22821,18 @@ } }, "node_modules/jest-environment-miniflare": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/jest-environment-miniflare/-/jest-environment-miniflare-2.13.0.tgz", - "integrity": "sha512-OEKqr5aok88jVMGV1yd/ywQhrkeNeD3RDG4xzVEt2x1eDOs1hszhZRfEMkytSKWT+Bw7Xu+CeVteut46sEPZBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/jest-environment-miniflare/-/jest-environment-miniflare-2.14.0.tgz", + "integrity": "sha512-fcxoLyFKbK8kNg7xtdkVlLpzp+IAR5JWo49ylR7+HsCUSZGhfrTSZUE83zzleM2u56nFSxJhGfhqkiZkk27MVg==", "dev": true, "dependencies": { "@jest/environment": ">=27", "@jest/fake-timers": ">=27", "@jest/types": ">=27", - "@miniflare/queues": "2.13.0", - "@miniflare/runner-vm": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/shared-test-environment": "2.13.0", + "@miniflare/queues": "2.14.0", + "@miniflare/runner-vm": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/shared-test-environment": "2.14.0", "jest-mock": ">=27", "jest-util": ">=27" }, @@ -27785,27 +27786,27 @@ } }, "node_modules/miniflare": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.13.0.tgz", - "integrity": "sha512-ayNhVa4a6bZiOuHtrPmOt4BCYcmW1fBQ/+qGL85smq1m2OBBm3aUs6f4ISf38xH8tk+qewgmAywetyVtn6KHPw==", - "dependencies": { - "@miniflare/cache": "2.13.0", - "@miniflare/cli-parser": "2.13.0", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", - "@miniflare/http-server": "2.13.0", - "@miniflare/kv": "2.13.0", - "@miniflare/queues": "2.13.0", - "@miniflare/r2": "2.13.0", - "@miniflare/runner-vm": "2.13.0", - "@miniflare/scheduler": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/sites": "2.13.0", - "@miniflare/storage-file": "2.13.0", - "@miniflare/storage-memory": "2.13.0", - "@miniflare/web-sockets": "2.13.0", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.14.0.tgz", + "integrity": "sha512-xBOUccq1dm5riOfaqoMWCC1uCqT71EW0x4akQQuGYgm+Q44N1ETEmzXSbFVroJgOHe8Hwpqxo2D7OOFwqFevew==", + "dependencies": { + "@miniflare/cache": "2.14.0", + "@miniflare/cli-parser": "2.14.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", + "@miniflare/http-server": "2.14.0", + "@miniflare/kv": "2.14.0", + "@miniflare/queues": "2.14.0", + "@miniflare/r2": "2.14.0", + "@miniflare/runner-vm": "2.14.0", + "@miniflare/scheduler": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/sites": "2.14.0", + "@miniflare/storage-file": "2.14.0", + "@miniflare/storage-memory": "2.14.0", + "@miniflare/web-sockets": "2.14.0", "kleur": "^4.1.4", "semiver": "^1.1.0", "source-map-support": "^0.5.20", @@ -27818,7 +27819,7 @@ "node": ">=16.13" }, "peerDependencies": { - "@miniflare/storage-redis": "2.13.0", + "@miniflare/storage-redis": "2.14.0", "cron-schedule": "^3.0.4", "ioredis": "^4.27.9" }, @@ -44517,11 +44518,11 @@ "name": "@cloudflare/pages-shared", "version": "0.4.2", "dependencies": { - "@miniflare/core": "2.13.0" + "@miniflare/core": "2.14.0" }, "devDependencies": { - "@miniflare/cache": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", + "@miniflare/cache": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", @@ -45486,13 +45487,13 @@ "@cloudflare/kv-asset-handler": "^0.2.0", "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.16.3", - "miniflare": "2.13.0", + "miniflare": "2.14.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", @@ -54421,8 +54422,8 @@ "eslint-config-prettier": "^8.5.0", "eslint-config-typescript": "^3.0.0", "jest": "^28.1.3", - "jest-environment-miniflare": "^2.13.0", - "miniflare": "^2.13.0", + "jest-environment-miniflare": "^2.14.0", + "miniflare": "^2.14.0", "prettier": "^2.7.1", "ts-jest": "^28.0.7", "wrangler": "^2.0.0" @@ -54753,7 +54754,7 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20230321.0", "typescript": "^5.0.3", - "wrangler": "2.13.0" + "wrangler": "^2.13.0" } }, "templates/worker-turso-ts/node_modules/@cloudflare/workers-types": { @@ -54762,326 +54763,11 @@ "integrity": "sha512-zyRFz9AUS0tbg3/kJ+3zxvp9fl/O9yOJlChih/o86hhOqRMcZVbWefYAvFPidRvYUHM5YTG1wjU1bF9FFckRVg==", "dev": true }, - "templates/worker-turso-ts/node_modules/@miniflare/cache": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.12.1.tgz", - "integrity": "sha512-6Pj5avy53qULTa13gWxGTDBuwX0yAzr4Zkzb0ZBh40bcbHp4vRkOk7PvHBoxV0M76JxQDHotGaW+ik510z5Xrg==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "http-cache-semantics": "^4.1.0", - "undici": "5.20.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/cli-parser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.12.1.tgz", - "integrity": "sha512-iCh4wEyQow8Dha+zpKhjCCXEp6QWbsvE18H5CgeUFT1pX4B+akYIHzdn47Cr5zpuYyjenoL78bAz0IIHIeyeWw==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1", - "kleur": "^4.1.4" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/core": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.12.1.tgz", - "integrity": "sha512-729xXL6uoMgtja5J7B2WdWAjFfxb74Pk2QqM3VqkWqY3XNlKWI7+ofvb8S6kI6uFEPGj4ma263uYkEAgsvzBWg==", - "dev": true, - "dependencies": { - "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/watcher": "2.12.1", - "busboy": "^1.6.0", - "dotenv": "^10.0.0", - "kleur": "^4.1.4", - "set-cookie-parser": "^2.4.8", - "undici": "5.20.0", - "urlpattern-polyfill": "^4.0.3" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/d1": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.12.1.tgz", - "integrity": "sha512-2ldT7xEC7KxoaEJ7nCY9/AB/xwPjbm3mrmpiIspT0b5OgS640Pe9EU4c5bSmzGoUbLvwF+jb+LhLE1QaEbWkBw==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.7" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/durable-objects": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.12.1.tgz", - "integrity": "sha512-/n9WIxvHavVUgT+Nf280wNOcmJQBG+eZuqOlORWW9RmXXbAzqzS2Mk2lmRDCzbq3xTXAcsndx6cdarQLNRUzBg==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/storage-memory": "2.12.1", - "undici": "5.20.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/html-rewriter": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.12.1.tgz", - "integrity": "sha512-yezYzGRBxy7d/oomAUEftdnL4fq6YIek82LtQlXgzcdcbBDnkYADj8WqGV41tAI+V2+rjrFEc1RuCXx/I1yISw==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "html-rewriter-wasm": "^0.4.1", - "undici": "5.20.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/http-server": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.12.1.tgz", - "integrity": "sha512-nC6POgDKFHxnyXbKCdR9FGZSsu5frXQUETvSVcoETd5RP+Iws0xZ+XkzVMqiiIZk3ifUC9LzdGUOD0J2PlhHJw==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/web-sockets": "2.12.1", - "kleur": "^4.1.4", - "selfsigned": "^2.0.0", - "undici": "5.20.0", - "ws": "^8.2.2", - "youch": "^2.2.2" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/kv": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.12.1.tgz", - "integrity": "sha512-8h8wLDMEaWaKAqYTwrckOcNjAz52bzDyLmU4t/lh1/AQOE9eSg/T+H6xQCv0fPGrWPeHmG8iXaFI1JQ+CtkcHw==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/queues": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.12.1.tgz", - "integrity": "sha512-L/YJkWWvg1RS3sCB5DLZOsf/kAmkwhvshpl+LmGQT7z/PYXlplbBmuhPwVBXaHqZdYE7063XfTzgAIhVPoo72Q==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.7" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/r2": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.12.1.tgz", - "integrity": "sha512-xp8fSSap6o5xSAWp9BtOGgZ4tuf5iHTqrfbAH66LF151j8y69eQtQJ5pxpSvrDJok/F1VOLGc4ihSLmUqxyXhw==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1", - "undici": "5.20.0" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/runner-vm": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.12.1.tgz", - "integrity": "sha512-pGY/aoQzbvyXOGR6/d3hv5/QsyUXGGbOxAyXdvjlz8h7ZiKOX4dBRS5TUAPS0kb/ofUWCyoYJi8dCVwRGdTYRw==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/scheduler": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.12.1.tgz", - "integrity": "sha512-AbOP8YpWNqR/t7zMuTmn6q27USCDBQaYaULRVaNNfCsxMTXAUjYfM85iFvnV9mshw+K0HIEU4zR4Xjd2FeJubg==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "cron-schedule": "^3.0.4" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/shared": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.12.1.tgz", - "integrity": "sha512-N8sHNM5vcvjvO+znQ7Mbqf0FChRlWxy/svUpQf1GGpii9aTXzOTWB+WkFvJrJNx44SUReEGxUAzxpdeWnHahmA==", - "dev": true, - "dependencies": { - "@types/better-sqlite3": "^7.6.0", - "kleur": "^4.1.4", - "npx-import": "^1.1.4", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/sites": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.12.1.tgz", - "integrity": "sha512-LW4r82cfGJvmJFwoBdXfsRcdDggVf8ppjMZGU3zk7xo+u5yD1uHzO2Arf3XbKNiOp7f9WyC/mXxs4zxF605iLA==", - "dev": true, - "dependencies": { - "@miniflare/kv": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/storage-file": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/storage-file": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.12.1.tgz", - "integrity": "sha512-eq5wzBwxQC5GVxBfji9svb9FRdSOlA8D8DTgzL29DDjuOYtG9j8ydOlo0J7/2MB/Gq0HYFUHYWHhrklzzwdKQQ==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1", - "@miniflare/storage-memory": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/storage-memory": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.12.1.tgz", - "integrity": "sha512-E9jbrX0L9N7YIHXj2G4td1EKboVLBdHkwh7RvKEZBwOhxDze5h+jMOou57NIbfC5kLOZPOC1fGXjzpp7xUUE6w==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/watcher": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.12.1.tgz", - "integrity": "sha512-3IG/6g38id5ppbZHB/gMfEvoIEFYdmTTLRsHaPNyWIk/r3LMhHLluVsMcs+Lr/fphkPk6Diou4cBLD2GeeoP7A==", - "dev": true, - "dependencies": { - "@miniflare/shared": "2.12.1" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/@miniflare/web-sockets": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.12.1.tgz", - "integrity": "sha512-Z+zqZqhVdrbmTQf+ETP5H1TPdXC2tUiYPiHRLWTHUks6VVkuwnUtIKxNPBEBXjCjKYYEm8VLclUAt+0yTucLWA==", - "dev": true, - "dependencies": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "undici": "5.20.0", - "ws": "^8.2.2" - }, - "engines": { - "node": ">=16.13" - } - }, - "templates/worker-turso-ts/node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "templates/worker-turso-ts/node_modules/itty-router": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/itty-router/-/itty-router-3.0.12.tgz", "integrity": "sha512-s98XTPhle6GGbaFf0kYrOD3Q8gyhnqvOqkwYijC3AmkceNKqWUp13YHg6dWmqmVv4pP7l7c94XI92I0EXVGO0w==" }, - "templates/worker-turso-ts/node_modules/miniflare": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.12.1.tgz", - "integrity": "sha512-pym6gzg8AQZ1NRChRV1hC4K55N49wndoaDEVRMvZPJrFsmGkNnXkWmlvmZ7SB3BN5UkP5MZwKhLqiJ49Ry8tFA==", - "dev": true, - "dependencies": { - "@miniflare/cache": "2.12.1", - "@miniflare/cli-parser": "2.12.1", - "@miniflare/core": "2.12.1", - "@miniflare/d1": "2.12.1", - "@miniflare/durable-objects": "2.12.1", - "@miniflare/html-rewriter": "2.12.1", - "@miniflare/http-server": "2.12.1", - "@miniflare/kv": "2.12.1", - "@miniflare/queues": "2.12.1", - "@miniflare/r2": "2.12.1", - "@miniflare/runner-vm": "2.12.1", - "@miniflare/scheduler": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/sites": "2.12.1", - "@miniflare/storage-file": "2.12.1", - "@miniflare/storage-memory": "2.12.1", - "@miniflare/web-sockets": "2.12.1", - "kleur": "^4.1.4", - "semiver": "^1.1.0", - "source-map-support": "^0.5.20", - "undici": "5.20.0" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "@miniflare/storage-redis": "2.12.1", - "cron-schedule": "^3.0.4", - "ioredis": "^4.27.9" - }, - "peerDependenciesMeta": { - "@miniflare/storage-redis": { - "optional": true - }, - "cron-schedule": { - "optional": true - }, - "ioredis": { - "optional": true - } - } - }, "templates/worker-turso-ts/node_modules/typescript": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", @@ -55095,60 +54781,6 @@ "node": ">=12.20" } }, - "templates/worker-turso-ts/node_modules/wrangler": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-2.13.0.tgz", - "integrity": "sha512-hU7RpOjDcyOlKO0xuNEOKINwSA1lh5nSkTH8aKKdatv2Ryt5gSS26RwS49QpZCYJGxGHzhHPr++TlSggOAsEVA==", - "dev": true, - "dependencies": { - "@cloudflare/kv-asset-handler": "^0.2.0", - "@esbuild-plugins/node-globals-polyfill": "^0.1.1", - "@esbuild-plugins/node-modules-polyfill": "^0.1.4", - "@miniflare/core": "2.12.1", - "@miniflare/d1": "2.12.1", - "@miniflare/durable-objects": "2.12.1", - "blake3-wasm": "^2.1.5", - "chokidar": "^3.5.3", - "esbuild": "0.16.3", - "miniflare": "2.12.1", - "nanoid": "^3.3.3", - "path-to-regexp": "^6.2.0", - "selfsigned": "^2.0.1", - "source-map": "^0.7.4", - "xxhash-wasm": "^1.0.1" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "templates/worker-turso-ts/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "templates/worker-typescript": { "name": "template-worker-typescript", "version": "0.0.0", @@ -59317,9 +58949,9 @@ "@cloudflare/pages-shared": { "version": "file:packages/pages-shared", "requires": { - "@miniflare/cache": "2.13.0", - "@miniflare/core": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", + "@miniflare/cache": "2.14.0", + "@miniflare/core": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", @@ -61127,34 +60759,34 @@ } }, "@miniflare/cache": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.13.0.tgz", - "integrity": "sha512-y3SdN3SVyPECWmLAEGkkrv0RB+LugEPs/FeXn8QtN9aE1vyj69clOAgmsDzoh1DpFfFsLKRiv05aWs4m79P8Xw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.14.0.tgz", + "integrity": "sha512-0mz0OCzTegiX75uMURLJpDo3DaOCSx9M0gv7NMFWDbK/XrvjoENiBZiKu98UBM5fts0qtK19a+MfB4aT0uBCFg==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "http-cache-semantics": "^4.1.0", "undici": "5.20.0" } }, "@miniflare/cli-parser": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.13.0.tgz", - "integrity": "sha512-Nx1PIfuMZ3mK9Dg/JojWZAjHR16h1pcdCFSqYln/ME7y5ifx+P1E5UkShWUQ1cBlibNaltjbJ2n/7stSAsIGPQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.14.0.tgz", + "integrity": "sha512-6Wb0jTMqwI7GRGAhz9WOF8AONUsXXPmwu+Qhg+tnRWtQpJ3DYd5dku1N04L9L1R7np/mD8RrycLyCdg3hLZ3aA==", "requires": { - "@miniflare/shared": "2.13.0", + "@miniflare/shared": "2.14.0", "kleur": "^4.1.4" } }, "@miniflare/core": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.13.0.tgz", - "integrity": "sha512-YJ/C0J3k+7xn4gvlMpvePnM3xC8nOnkweW96cc0IA8kJ1JSmScOO2tZ7rrU1RyDgp6StkAtQBw4yC0wYeFycBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.14.0.tgz", + "integrity": "sha512-BjmV/ZDwsKvXnJntYHt3AQgzVKp/5ZzWPpYWoOnUSNxq6nnRCQyvFvjvBZKnhubcmJCLSqegvz0yHejMA90CTA==", "requires": { "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/watcher": "2.13.0", + "@miniflare/queues": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/watcher": "2.14.0", "busboy": "^1.6.0", "dotenv": "^10.0.0", "kleur": "^4.1.4", @@ -61171,44 +60803,44 @@ } }, "@miniflare/d1": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.13.0.tgz", - "integrity": "sha512-OslqjO8iTcvzyrC0spByftMboRmHJEyHyTHnlKkjWDGdQQztEOjso2Xj+3I4SZIeUYvbzDRhKLS2QXI9a8LS5A==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.14.0.tgz", + "integrity": "sha512-9YoeLAkZuWGAu9BMsoctHoMue0xHzJYZigAJWGvWrqSFT1gBaT+RlUefQCHXggi8P7sOJ1+BKlsWAhkB5wfMWQ==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0" + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0" } }, "@miniflare/durable-objects": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.13.0.tgz", - "integrity": "sha512-CRGVBPO9vY4Fc3aV+pdPRVVeYIt64vQqvw+BJbyW+TQtqVP2CGQeziJGnCfcONNNKyooZxGyUkHewUypyH+Qhg==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.14.0.tgz", + "integrity": "sha512-P8eh1P62BPGpj+MCb1i1lj7Tlt/G3BMmnxHp9duyb0Wro/ILVGPQskZl+iq7DHq1w3C+n0+6/E1B44ff+qn0Mw==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/storage-memory": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/storage-memory": "2.14.0", "undici": "5.20.0" } }, "@miniflare/html-rewriter": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.13.0.tgz", - "integrity": "sha512-XhN7Icyzvtvu+o/A0hrnSiSmla78seCaNwQ9M1TDHxt352I/ahPX4wtPXs6GbKqY0/i+V6yoG2KGFRQ/j59cQQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.14.0.tgz", + "integrity": "sha512-7CJZk3xZkxK8tGNofnhgWcChZ8YLx6MhAdN2nn6ONSXrK/TevzEKdL8bnVv1OJ6J8Y23OxvfinOhufr33tMS8g==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "html-rewriter-wasm": "^0.4.1", "undici": "5.20.0" } }, "@miniflare/http-server": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.13.0.tgz", - "integrity": "sha512-aMS/nUMTKP15hKnyZboeuWCiqmNrrCu+XRBY/TxDDl07iXcLpiHGf3oVv+yXxXkWlJHJVCbK7i/nXSNPllRMSw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.14.0.tgz", + "integrity": "sha512-APaBlvGRAW+W18ph5ruPXX26/iKdByPz1tZH1OjPAKBDAiKFZSGek4QzUmQALBWLx5VMTMrt7QIe7KE4nM4sdw==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/web-sockets": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/web-sockets": "2.14.0", "kleur": "^4.1.4", "selfsigned": "^2.0.0", "undici": "5.20.0", @@ -61225,52 +60857,53 @@ } }, "@miniflare/kv": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.13.0.tgz", - "integrity": "sha512-J0AS5x3g/YVOmHMxMAZs07nRXRvSo9jyuC0eikTBf+4AABvBIyvVYmdTjYNjCmr8O5smcfWBX5S27HelD3aAAQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.14.0.tgz", + "integrity": "sha512-FHAnVjmhV/VHxgjNf2whraz+k7kfMKlfM+5gO8WT6HrOsWxSdx8OueWVScnOuuDkSeUg5Ctrf5SuztTV8Uy1cg==", "requires": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" } }, "@miniflare/queues": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.13.0.tgz", - "integrity": "sha512-Gf/a6M1mJL03iOvNqh3JNahcBfvEMPHnO28n0gkCoyYWGvddIr9lwCdFIa0qwNJsC1fIDRxhPg8PZ5cQLBMwRA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.14.0.tgz", + "integrity": "sha512-flS4MqlgBKyv6QBqKD0IofjmMDW9wP1prUNQy2wWPih9lA6bFKmml3VdFeDsPnWtE2J67K0vCTf5kj1Q0qdW1w==", "requires": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" } }, "@miniflare/r2": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.13.0.tgz", - "integrity": "sha512-/5k6GHOYMNV/oBtilV9HDXBkJUrx8oXVigG5vxbnzEGRXyVRmR+Glzu7mFT8JiE94XiEbXHk9Qvu1S5Dej3wBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.14.0.tgz", + "integrity": "sha512-+WJJP4J0QzY69HPrG6g5OyW23lJ02WHpHZirCxwPSz8CajooqZCJVx+qvUcNmU8MyKASbUZMWnH79LysuBh+jA==", "requires": { - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "undici": "5.20.0" } }, "@miniflare/runner-vm": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.13.0.tgz", - "integrity": "sha512-VmKtF2cA8HmTuLXor1THWY0v+DmaobPct63iLcgWIaUdP3MIvL+9X8HDXFAviCR7bCTe6MKxckHkaOj0IE0aJQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.14.0.tgz", + "integrity": "sha512-01CmNzv74u0RZgT/vjV/ggDzECXTG88ZJAKhXyhAx0s2DOLIXzsGHn6pUJIsfPCrtj8nfqtTCp1Vf0UMVWSpmw==", "requires": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" } }, "@miniflare/scheduler": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.13.0.tgz", - "integrity": "sha512-AOaQanoR4NjVEzVGWHnrL15A7aMx+d9AKLJhSDF7KaP+4NrT2Wo2BQuXCpn5oStx3itOdlQpMfqQ139e/I8WhQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.14.0.tgz", + "integrity": "sha512-/D28OeY/HxcOyY0rkPc2qU/wzxCcUv3/F7NRpgDix37sMkYjAAS51ehVIAkPwAdFEMdantcyVuHNQ2kO1xbT+Q==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "cron-schedule": "^3.0.4" } }, "@miniflare/shared": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.13.0.tgz", - "integrity": "sha512-m8YFQzKmbjberrV9hPzNcQjNCXxjTjXUpuNrIGjAJO7g+BDztUHaZbdd26H9maBDlkeiWxA3hf0mDyCT/6MCMA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.14.0.tgz", + "integrity": "sha512-O0jAEdMkp8BzrdFCfMWZu76h4Cq+tt3/oDtcTFgzum3fRW5vUhIi/5f6bfndu6rkGbSlzxwor8CJWpzityXGug==", "requires": { "@types/better-sqlite3": "^7.6.0", "kleur": "^4.1.4", @@ -61279,51 +60912,51 @@ } }, "@miniflare/shared-test-environment": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/shared-test-environment/-/shared-test-environment-2.13.0.tgz", - "integrity": "sha512-I90e0hVdsR0pD0JZoetlw03gpQ05WnXPCHOdhBxROTrfy+YLP19zIFgvJpKC8WyKrcwABRwetWx7tIMI03qU0g==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/shared-test-environment/-/shared-test-environment-2.14.0.tgz", + "integrity": "sha512-Iarxqo9hR4Gi6i7iF/hXJbHuPCXTZbA4z91Gwhet8dhK6c0zWGU3xi7zjr5XTaawd/cuR1g6bi0cwt/10bAEsg==", "dev": true, "requires": { "@cloudflare/workers-types": "^4.20221111.1", - "@miniflare/cache": "2.13.0", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", - "@miniflare/kv": "2.13.0", - "@miniflare/queues": "2.13.0", - "@miniflare/r2": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/sites": "2.13.0", - "@miniflare/storage-memory": "2.13.0", - "@miniflare/web-sockets": "2.13.0" + "@miniflare/cache": "2.14.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", + "@miniflare/kv": "2.14.0", + "@miniflare/queues": "2.14.0", + "@miniflare/r2": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/sites": "2.14.0", + "@miniflare/storage-memory": "2.14.0", + "@miniflare/web-sockets": "2.14.0" } }, "@miniflare/sites": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.13.0.tgz", - "integrity": "sha512-/tuzIu00o6CF2tkSv01q02MgEShXBSKx85h9jwWvc+6u7prGacAOer0FA1YNRFbE+t9QIfutAkoPGMA9zYf8+Q==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.14.0.tgz", + "integrity": "sha512-qI8MFZpD1NV+g+HQ/qheDVwscKzwG58J+kAVTU/1fgub2lMLsxhE3Mmbi5AIpyIiJ7Q5Sezqga234CEkHkS7dA==", "requires": { - "@miniflare/kv": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/storage-file": "2.13.0" + "@miniflare/kv": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/storage-file": "2.14.0" } }, "@miniflare/storage-file": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.13.0.tgz", - "integrity": "sha512-LuAeAAY5046rq5U1eFLVkz+ppiFEWytWacpkQw92DvVKFFquZcXSj6WPxZF4rSs23WDk+rdcwuLekbb52aDR7A==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.14.0.tgz", + "integrity": "sha512-Ps0wHhTO+ie33a58efI0p/ppFXSjlbYmykQXfYtMeVLD60CKl+4Lxor0+gD6uYDFbhMWL5/GMDvyr4AM87FA+Q==", "requires": { - "@miniflare/shared": "2.13.0", - "@miniflare/storage-memory": "2.13.0" + "@miniflare/shared": "2.14.0", + "@miniflare/storage-memory": "2.14.0" } }, "@miniflare/storage-memory": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.13.0.tgz", - "integrity": "sha512-FnkYcBNXa/ym1ksNilNZycg9WYYKo6cWKplVBeSthRon3e8QY6t3n7/XRseBUo7O6mhDybVTy4wNCP1R2nBiEw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.14.0.tgz", + "integrity": "sha512-5aFjEiTSNrHJ+iAiGMCA/TVPnNMrnokG5r0vKrwj4knbf8pisgfP04x18zCgOlG7kaIWNmqdO/vtVT5BIioiSQ==", "requires": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" } }, "@miniflare/tre": { @@ -61393,20 +61026,20 @@ } }, "@miniflare/watcher": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.13.0.tgz", - "integrity": "sha512-teAacWcpMStoBLbLae95IUaL5lPzjPlXa9lhK9CbRaio/KRMibTMRGWrYos3IVGQRZvklvLwcms/nTvgcdb6yw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.14.0.tgz", + "integrity": "sha512-O8Abg2eHpGmcZb8WyUaA6Av1Mqt5bSrorzz4CrWwsvJHBdekZPIX0GihC9vn327d/1pKRs81YTiSAfBoSZpVIw==", "requires": { - "@miniflare/shared": "2.13.0" + "@miniflare/shared": "2.14.0" } }, "@miniflare/web-sockets": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.13.0.tgz", - "integrity": "sha512-+U2/HCf+BetRIgjAnNQjkuN6UeAjQmXifhQC+7CCaX834XJhrKXoR6z2xr2xkg1qj0qQs4D2jWG0KzrO5OUpug==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.14.0.tgz", + "integrity": "sha512-lB1CB4rBq0mbCuh55WgIEH4L3c4/i4MNDBfrQL+6r+wGcr/BJUqF8BHpsfAt5yHWUJVtK5mlMeesS/xpg4Ao1w==", "requires": { - "@miniflare/core": "2.13.0", - "@miniflare/shared": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/shared": "2.14.0", "undici": "5.20.0", "ws": "^8.2.2" }, @@ -76197,18 +75830,18 @@ } }, "jest-environment-miniflare": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/jest-environment-miniflare/-/jest-environment-miniflare-2.13.0.tgz", - "integrity": "sha512-OEKqr5aok88jVMGV1yd/ywQhrkeNeD3RDG4xzVEt2x1eDOs1hszhZRfEMkytSKWT+Bw7Xu+CeVteut46sEPZBw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/jest-environment-miniflare/-/jest-environment-miniflare-2.14.0.tgz", + "integrity": "sha512-fcxoLyFKbK8kNg7xtdkVlLpzp+IAR5JWo49ylR7+HsCUSZGhfrTSZUE83zzleM2u56nFSxJhGfhqkiZkk27MVg==", "dev": true, "requires": { "@jest/environment": ">=27", "@jest/fake-timers": ">=27", "@jest/types": ">=27", - "@miniflare/queues": "2.13.0", - "@miniflare/runner-vm": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/shared-test-environment": "2.13.0", + "@miniflare/queues": "2.14.0", + "@miniflare/runner-vm": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/shared-test-environment": "2.14.0", "jest-mock": ">=27", "jest-util": ">=27" }, @@ -79594,27 +79227,27 @@ "dev": true }, "miniflare": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.13.0.tgz", - "integrity": "sha512-ayNhVa4a6bZiOuHtrPmOt4BCYcmW1fBQ/+qGL85smq1m2OBBm3aUs6f4ISf38xH8tk+qewgmAywetyVtn6KHPw==", - "requires": { - "@miniflare/cache": "2.13.0", - "@miniflare/cli-parser": "2.13.0", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", - "@miniflare/http-server": "2.13.0", - "@miniflare/kv": "2.13.0", - "@miniflare/queues": "2.13.0", - "@miniflare/r2": "2.13.0", - "@miniflare/runner-vm": "2.13.0", - "@miniflare/scheduler": "2.13.0", - "@miniflare/shared": "2.13.0", - "@miniflare/sites": "2.13.0", - "@miniflare/storage-file": "2.13.0", - "@miniflare/storage-memory": "2.13.0", - "@miniflare/web-sockets": "2.13.0", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.14.0.tgz", + "integrity": "sha512-xBOUccq1dm5riOfaqoMWCC1uCqT71EW0x4akQQuGYgm+Q44N1ETEmzXSbFVroJgOHe8Hwpqxo2D7OOFwqFevew==", + "requires": { + "@miniflare/cache": "2.14.0", + "@miniflare/cli-parser": "2.14.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", + "@miniflare/http-server": "2.14.0", + "@miniflare/kv": "2.14.0", + "@miniflare/queues": "2.14.0", + "@miniflare/r2": "2.14.0", + "@miniflare/runner-vm": "2.14.0", + "@miniflare/scheduler": "2.14.0", + "@miniflare/shared": "2.14.0", + "@miniflare/sites": "2.14.0", + "@miniflare/storage-file": "2.14.0", + "@miniflare/storage-memory": "2.14.0", + "@miniflare/web-sockets": "2.14.0", "kleur": "^4.1.4", "semiver": "^1.1.0", "source-map-support": "^0.5.20", @@ -90621,8 +90254,8 @@ "eslint-config-prettier": "^8.5.0", "eslint-config-typescript": "^3.0.0", "jest": "^28.1.3", - "jest-environment-miniflare": "^2.13.0", - "miniflare": "^2.13.0", + "jest-environment-miniflare": "^2.14.0", + "miniflare": "^2.14.0", "prettier": "^2.7.1", "ts-jest": "^28.0.7", "wrangler": "^2.0.0" @@ -93586,7 +93219,7 @@ "@libsql/client": "^0.1.1", "itty-router": "^3.0.12", "typescript": "^5.0.3", - "wrangler": "2.13.0" + "wrangler": "^2.13.0" }, "dependencies": { "@cloudflare/workers-types": { @@ -93595,283 +93228,16 @@ "integrity": "sha512-zyRFz9AUS0tbg3/kJ+3zxvp9fl/O9yOJlChih/o86hhOqRMcZVbWefYAvFPidRvYUHM5YTG1wjU1bF9FFckRVg==", "dev": true }, - "@miniflare/cache": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/cache/-/cache-2.12.1.tgz", - "integrity": "sha512-6Pj5avy53qULTa13gWxGTDBuwX0yAzr4Zkzb0ZBh40bcbHp4vRkOk7PvHBoxV0M76JxQDHotGaW+ik510z5Xrg==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "http-cache-semantics": "^4.1.0", - "undici": "5.20.0" - } - }, - "@miniflare/cli-parser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/cli-parser/-/cli-parser-2.12.1.tgz", - "integrity": "sha512-iCh4wEyQow8Dha+zpKhjCCXEp6QWbsvE18H5CgeUFT1pX4B+akYIHzdn47Cr5zpuYyjenoL78bAz0IIHIeyeWw==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1", - "kleur": "^4.1.4" - } - }, - "@miniflare/core": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/core/-/core-2.12.1.tgz", - "integrity": "sha512-729xXL6uoMgtja5J7B2WdWAjFfxb74Pk2QqM3VqkWqY3XNlKWI7+ofvb8S6kI6uFEPGj4ma263uYkEAgsvzBWg==", - "dev": true, - "requires": { - "@iarna/toml": "^2.2.5", - "@miniflare/queues": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/watcher": "2.12.1", - "busboy": "^1.6.0", - "dotenv": "^10.0.0", - "kleur": "^4.1.4", - "set-cookie-parser": "^2.4.8", - "undici": "5.20.0", - "urlpattern-polyfill": "^4.0.3" - } - }, - "@miniflare/d1": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/d1/-/d1-2.12.1.tgz", - "integrity": "sha512-2ldT7xEC7KxoaEJ7nCY9/AB/xwPjbm3mrmpiIspT0b5OgS640Pe9EU4c5bSmzGoUbLvwF+jb+LhLE1QaEbWkBw==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/durable-objects": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/durable-objects/-/durable-objects-2.12.1.tgz", - "integrity": "sha512-/n9WIxvHavVUgT+Nf280wNOcmJQBG+eZuqOlORWW9RmXXbAzqzS2Mk2lmRDCzbq3xTXAcsndx6cdarQLNRUzBg==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/storage-memory": "2.12.1", - "undici": "5.20.0" - } - }, - "@miniflare/html-rewriter": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/html-rewriter/-/html-rewriter-2.12.1.tgz", - "integrity": "sha512-yezYzGRBxy7d/oomAUEftdnL4fq6YIek82LtQlXgzcdcbBDnkYADj8WqGV41tAI+V2+rjrFEc1RuCXx/I1yISw==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "html-rewriter-wasm": "^0.4.1", - "undici": "5.20.0" - } - }, - "@miniflare/http-server": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/http-server/-/http-server-2.12.1.tgz", - "integrity": "sha512-nC6POgDKFHxnyXbKCdR9FGZSsu5frXQUETvSVcoETd5RP+Iws0xZ+XkzVMqiiIZk3ifUC9LzdGUOD0J2PlhHJw==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/web-sockets": "2.12.1", - "kleur": "^4.1.4", - "selfsigned": "^2.0.0", - "undici": "5.20.0", - "ws": "^8.2.2", - "youch": "^2.2.2" - } - }, - "@miniflare/kv": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/kv/-/kv-2.12.1.tgz", - "integrity": "sha512-8h8wLDMEaWaKAqYTwrckOcNjAz52bzDyLmU4t/lh1/AQOE9eSg/T+H6xQCv0fPGrWPeHmG8iXaFI1JQ+CtkcHw==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/queues": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/queues/-/queues-2.12.1.tgz", - "integrity": "sha512-L/YJkWWvg1RS3sCB5DLZOsf/kAmkwhvshpl+LmGQT7z/PYXlplbBmuhPwVBXaHqZdYE7063XfTzgAIhVPoo72Q==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/r2": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/r2/-/r2-2.12.1.tgz", - "integrity": "sha512-xp8fSSap6o5xSAWp9BtOGgZ4tuf5iHTqrfbAH66LF151j8y69eQtQJ5pxpSvrDJok/F1VOLGc4ihSLmUqxyXhw==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1", - "undici": "5.20.0" - } - }, - "@miniflare/runner-vm": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/runner-vm/-/runner-vm-2.12.1.tgz", - "integrity": "sha512-pGY/aoQzbvyXOGR6/d3hv5/QsyUXGGbOxAyXdvjlz8h7ZiKOX4dBRS5TUAPS0kb/ofUWCyoYJi8dCVwRGdTYRw==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/scheduler": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/scheduler/-/scheduler-2.12.1.tgz", - "integrity": "sha512-AbOP8YpWNqR/t7zMuTmn6q27USCDBQaYaULRVaNNfCsxMTXAUjYfM85iFvnV9mshw+K0HIEU4zR4Xjd2FeJubg==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "cron-schedule": "^3.0.4" - } - }, - "@miniflare/shared": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/shared/-/shared-2.12.1.tgz", - "integrity": "sha512-N8sHNM5vcvjvO+znQ7Mbqf0FChRlWxy/svUpQf1GGpii9aTXzOTWB+WkFvJrJNx44SUReEGxUAzxpdeWnHahmA==", - "dev": true, - "requires": { - "@types/better-sqlite3": "^7.6.0", - "kleur": "^4.1.4", - "npx-import": "^1.1.4", - "picomatch": "^2.3.1" - } - }, - "@miniflare/sites": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/sites/-/sites-2.12.1.tgz", - "integrity": "sha512-LW4r82cfGJvmJFwoBdXfsRcdDggVf8ppjMZGU3zk7xo+u5yD1uHzO2Arf3XbKNiOp7f9WyC/mXxs4zxF605iLA==", - "dev": true, - "requires": { - "@miniflare/kv": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/storage-file": "2.12.1" - } - }, - "@miniflare/storage-file": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/storage-file/-/storage-file-2.12.1.tgz", - "integrity": "sha512-eq5wzBwxQC5GVxBfji9svb9FRdSOlA8D8DTgzL29DDjuOYtG9j8ydOlo0J7/2MB/Gq0HYFUHYWHhrklzzwdKQQ==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1", - "@miniflare/storage-memory": "2.12.1" - } - }, - "@miniflare/storage-memory": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/storage-memory/-/storage-memory-2.12.1.tgz", - "integrity": "sha512-E9jbrX0L9N7YIHXj2G4td1EKboVLBdHkwh7RvKEZBwOhxDze5h+jMOou57NIbfC5kLOZPOC1fGXjzpp7xUUE6w==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/watcher": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/watcher/-/watcher-2.12.1.tgz", - "integrity": "sha512-3IG/6g38id5ppbZHB/gMfEvoIEFYdmTTLRsHaPNyWIk/r3LMhHLluVsMcs+Lr/fphkPk6Diou4cBLD2GeeoP7A==", - "dev": true, - "requires": { - "@miniflare/shared": "2.12.1" - } - }, - "@miniflare/web-sockets": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@miniflare/web-sockets/-/web-sockets-2.12.1.tgz", - "integrity": "sha512-Z+zqZqhVdrbmTQf+ETP5H1TPdXC2tUiYPiHRLWTHUks6VVkuwnUtIKxNPBEBXjCjKYYEm8VLclUAt+0yTucLWA==", - "dev": true, - "requires": { - "@miniflare/core": "2.12.1", - "@miniflare/shared": "2.12.1", - "undici": "5.20.0", - "ws": "^8.2.2" - } - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true - }, "itty-router": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/itty-router/-/itty-router-3.0.12.tgz", "integrity": "sha512-s98XTPhle6GGbaFf0kYrOD3Q8gyhnqvOqkwYijC3AmkceNKqWUp13YHg6dWmqmVv4pP7l7c94XI92I0EXVGO0w==" }, - "miniflare": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-2.12.1.tgz", - "integrity": "sha512-pym6gzg8AQZ1NRChRV1hC4K55N49wndoaDEVRMvZPJrFsmGkNnXkWmlvmZ7SB3BN5UkP5MZwKhLqiJ49Ry8tFA==", - "dev": true, - "requires": { - "@miniflare/cache": "2.12.1", - "@miniflare/cli-parser": "2.12.1", - "@miniflare/core": "2.12.1", - "@miniflare/d1": "2.12.1", - "@miniflare/durable-objects": "2.12.1", - "@miniflare/html-rewriter": "2.12.1", - "@miniflare/http-server": "2.12.1", - "@miniflare/kv": "2.12.1", - "@miniflare/queues": "2.12.1", - "@miniflare/r2": "2.12.1", - "@miniflare/runner-vm": "2.12.1", - "@miniflare/scheduler": "2.12.1", - "@miniflare/shared": "2.12.1", - "@miniflare/sites": "2.12.1", - "@miniflare/storage-file": "2.12.1", - "@miniflare/storage-memory": "2.12.1", - "@miniflare/web-sockets": "2.12.1", - "kleur": "^4.1.4", - "semiver": "^1.1.0", - "source-map-support": "^0.5.20", - "undici": "5.20.0" - } - }, "typescript": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", "dev": true - }, - "wrangler": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-2.13.0.tgz", - "integrity": "sha512-hU7RpOjDcyOlKO0xuNEOKINwSA1lh5nSkTH8aKKdatv2Ryt5gSS26RwS49QpZCYJGxGHzhHPr++TlSggOAsEVA==", - "dev": true, - "requires": { - "@cloudflare/kv-asset-handler": "^0.2.0", - "@esbuild-plugins/node-globals-polyfill": "^0.1.1", - "@esbuild-plugins/node-modules-polyfill": "^0.1.4", - "@miniflare/core": "2.12.1", - "@miniflare/d1": "2.12.1", - "@miniflare/durable-objects": "2.12.1", - "blake3-wasm": "^2.1.5", - "chokidar": "^3.5.3", - "esbuild": "0.16.3", - "fsevents": "~2.3.2", - "miniflare": "2.12.1", - "nanoid": "^3.3.3", - "path-to-regexp": "^6.2.0", - "selfsigned": "^2.0.1", - "source-map": "^0.7.4", - "xxhash-wasm": "^1.0.1" - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} } } }, @@ -94103,9 +93469,9 @@ "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@iarna/toml": "^3.0.0", "@microsoft/api-extractor": "^7.28.3", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", "@miniflare/tre": "3.0.0-next.13", "@types/better-sqlite3": "^7.6.0", "@types/busboy": "^1.5.0", @@ -94154,7 +93520,7 @@ "jest-fetch-mock": "^3.0.3", "jest-websocket-mock": "^2.3.0", "mime": "^3.0.0", - "miniflare": "2.13.0", + "miniflare": "2.14.0", "minimatch": "^5.1.0", "msw": "^0.49.1", "nanoid": "^3.3.3", diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 3781eca76a23..071aa2da79a1 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -43,11 +43,11 @@ ] }, "dependencies": { - "@miniflare/core": "2.13.0" + "@miniflare/core": "2.14.0" }, "devDependencies": { - "@miniflare/cache": "2.13.0", - "@miniflare/html-rewriter": "2.13.0", + "@miniflare/cache": "2.14.0", + "@miniflare/html-rewriter": "2.14.0", "@types/service-worker-mock": "^2.0.1", "concurrently": "^7.3.0", "glob": "^8.0.3", diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 38f59547f736..30ba5ca4a80a 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -102,13 +102,13 @@ "@cloudflare/kv-asset-handler": "^0.2.0", "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", - "@miniflare/core": "2.13.0", - "@miniflare/d1": "2.13.0", - "@miniflare/durable-objects": "2.13.0", + "@miniflare/core": "2.14.0", + "@miniflare/d1": "2.14.0", + "@miniflare/durable-objects": "2.14.0", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.16.3", - "miniflare": "2.13.0", + "miniflare": "2.14.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", diff --git a/templates/worker-speedtest/package.json b/templates/worker-speedtest/package.json index 7ef22e2932e2..988c2cbbb59c 100644 --- a/templates/worker-speedtest/package.json +++ b/templates/worker-speedtest/package.json @@ -15,8 +15,8 @@ "eslint-config-prettier": "^8.5.0", "eslint-config-typescript": "^3.0.0", "jest": "^28.1.3", - "jest-environment-miniflare": "^2.13.0", - "miniflare": "^2.13.0", + "jest-environment-miniflare": "^2.14.0", + "miniflare": "^2.14.0", "prettier": "^2.7.1", "ts-jest": "^28.0.7", "wrangler": "^2.0.0" diff --git a/templates/worker-turso-ts/package.json b/templates/worker-turso-ts/package.json index 02ed1e05ad73..8282996c9b6b 100644 --- a/templates/worker-turso-ts/package.json +++ b/templates/worker-turso-ts/package.json @@ -13,6 +13,6 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20230321.0", "typescript": "^5.0.3", - "wrangler": "2.13.0" + "wrangler": "^2.13.0" } } From 4d7781f78ace94d3f627ba0b8ea7e5662a0cbe1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Thu, 11 May 2023 19:28:04 +0100 Subject: [PATCH 25/25] [wrangler] feat: Constellation AI bindings (#3157) This introduces the Constellation AI bindings. Multiple of them can be added to a worker, each one configured with a project id. An additional JS module will still be needed by the worker developer to interact with these bindings. --- .changeset/tough-snails-check.md | 15 +++ .../src/__tests__/configuration.test.ts | 95 +++++++++++++++++++ .../src/__tests__/constellation.test.ts | 20 ++-- packages/wrangler/src/__tests__/index.test.ts | 4 +- .../src/__tests__/mtls-certificates.test.ts | 2 +- .../pages/create-worker-bundle-contents.ts | 1 + packages/wrangler/src/config/environment.ts | 16 ++++ packages/wrangler/src/config/index.ts | 13 +++ packages/wrangler/src/config/validation.ts | 51 ++++++++++ packages/wrangler/src/constellation/utils.ts | 2 +- .../wrangler/src/create-worker-upload-form.ts | 9 ++ packages/wrangler/src/dev.tsx | 2 + packages/wrangler/src/index.ts | 3 +- packages/wrangler/src/publish/publish.ts | 1 + packages/wrangler/src/secret/index.ts | 1 + packages/wrangler/src/type-generation.ts | 6 ++ packages/wrangler/src/user/user.ts | 2 +- packages/wrangler/src/worker.ts | 6 ++ 18 files changed, 233 insertions(+), 16 deletions(-) create mode 100644 .changeset/tough-snails-check.md diff --git a/.changeset/tough-snails-check.md b/.changeset/tough-snails-check.md new file mode 100644 index 000000000000..9d4951ee7643 --- /dev/null +++ b/.changeset/tough-snails-check.md @@ -0,0 +1,15 @@ +--- +"wrangler": minor +--- + +[wrangler] feat: Support for Constellation bindings + +Added support for a new type of safe bindings called "Constellation" +that allows interacting with uploaded models in the context of these +projects. + +```toml +[[constellation]] +binding = 'AI' +project_id = '9d478427-dea6-4988-9b16-f6f8888d974c' +``` diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index 1ee434115df4..7d0f54a60758 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -26,6 +26,7 @@ describe("normalizeAndValidateConfig()", () => { compatibility_flags: [], configPath: undefined, d1_databases: [], + constellation: [], dev: { ip: "0.0.0.0", local_protocol: "http", @@ -1831,6 +1832,100 @@ describe("normalizeAndValidateConfig()", () => { }); }); + describe("[constellation]", () => { + it("should error if constellation is an object", () => { + const { diagnostics } = normalizeAndValidateConfig( + { constellation: {} } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"constellation\\" should be an array but got {}." + `); + }); + + it("should error if constellation is a string", () => { + const { diagnostics } = normalizeAndValidateConfig( + { constellation: "BAD" } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"constellation\\" should be an array but got \\"BAD\\"." + `); + }); + + it("should error if constellation is a number", () => { + const { diagnostics } = normalizeAndValidateConfig( + { constellation: 999 } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"constellation\\" should be an array but got 999." + `); + }); + + it("should error if constellation is null", () => { + const { diagnostics } = normalizeAndValidateConfig( + { constellation: null } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"constellation\\" should be an array but got null." + `); + }); + + it("should accept valid bindings", () => { + const { diagnostics } = normalizeAndValidateConfig( + { + constellation: [{ binding: "VALID", project_id: "ID" }], + } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasErrors()).toBe(false); + }); + + it("should error if constellation.bindings are not valid", () => { + const { diagnostics } = normalizeAndValidateConfig( + { + constellation: [ + {}, + { binding: "VALID" }, + { binding: 2000, project: 2111 }, + ], + } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - \\"constellation[0]\\" bindings should have a string \\"binding\\" field but got {}. + - \\"constellation[0]\\" bindings must have a \\"project_id\\" field but got {}. + - \\"constellation[1]\\" bindings must have a \\"project_id\\" field but got {\\"binding\\":\\"VALID\\"}. + - \\"constellation[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2000,\\"project\\":2111}. + - \\"constellation[2]\\" bindings must have a \\"project_id\\" field but got {\\"binding\\":2000,\\"project\\":2111}." + `); + }); + }); + describe("[queues]", () => { it("should error if queues is not an object", () => { const { config, diagnostics } = normalizeAndValidateConfig( diff --git a/packages/wrangler/src/__tests__/constellation.test.ts b/packages/wrangler/src/__tests__/constellation.test.ts index 8c71b2cd6043..9d91180f1434 100644 --- a/packages/wrangler/src/__tests__/constellation.test.ts +++ b/packages/wrangler/src/__tests__/constellation.test.ts @@ -20,7 +20,7 @@ describe("constellation help", () => { expect(std.out).toMatchInlineSnapshot(` "wrangler constellation - 🤖 Interact with Constellation AI models + 🤖 Interact with Constellation models Commands: wrangler constellation project Manage your projects @@ -51,7 +51,7 @@ describe("constellation help", () => { " wrangler constellation - 🤖 Interact with Constellation AI models + 🤖 Interact with Constellation models Commands: wrangler constellation project Manage your projects @@ -94,7 +94,7 @@ describe("constellation commands", () => { await runWrangler("constellation project create new_project ONNX"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -108,7 +108,7 @@ describe("constellation commands", () => { await runWrangler("constellation project list"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -131,7 +131,7 @@ describe("constellation commands", () => { await runWrangler("constellation project delete new_project3"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -147,7 +147,7 @@ describe("constellation commands", () => { await runWrangler("constellation catalog list"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -165,7 +165,7 @@ describe("constellation commands", () => { await runWrangler("constellation runtime list"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -188,7 +188,7 @@ describe("constellation commands", () => { ); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -202,7 +202,7 @@ describe("constellation commands", () => { await runWrangler("constellation model list new_project3"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- @@ -227,7 +227,7 @@ describe("constellation commands", () => { await runWrangler("constellation model delete new_project3 model2"); expect(std.out).toMatchInlineSnapshot(` "-------------------- - 🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic + 🚧 Constellation is currently in open alpha and is not recommended for production data and traffic 🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose 🚧 To give feedback, visit https://discord.gg/cloudflaredev -------------------- diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 51985eaaa37b..7338ed7e3cae 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -49,7 +49,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database - wrangler constellation 🤖 Interact with Constellation AI models + wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare @@ -103,7 +103,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database - wrangler constellation 🤖 Interact with Constellation AI models + wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare diff --git a/packages/wrangler/src/__tests__/mtls-certificates.test.ts b/packages/wrangler/src/__tests__/mtls-certificates.test.ts index 81cb974531ae..4a55b3301c41 100644 --- a/packages/wrangler/src/__tests__/mtls-certificates.test.ts +++ b/packages/wrangler/src/__tests__/mtls-certificates.test.ts @@ -379,7 +379,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database - wrangler constellation 🤖 Interact with Constellation AI models + wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler login 🔓 Login to Cloudflare diff --git a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts index fbba83e80280..d42c58cc0d97 100644 --- a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts +++ b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts @@ -58,6 +58,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData { queues: undefined, r2_buckets: undefined, d1_databases: undefined, + constellation: undefined, services: undefined, analytics_engine_datasets: undefined, dispatch_namespaces: undefined, diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index ee587e57f9c5..facdd195ac0c 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -455,6 +455,22 @@ interface EnvironmentNonInheritable { database_internal_env?: string; }[]; + /** + * Specifies Constellation projects that are bound to this Worker environment. + * + * NOTE: This field is not automatically inherited from the top level environment, + * and so must be specified in every named environment. + * + * @default `[]` + * @nonInheritable + */ + constellation: { + /** The binding name used to refer to the project in the worker. */ + binding: string; + /** The id of the project. */ + project_id: string; + }[]; + /** * Specifies service bindings (worker-to-worker) that are bound to this Worker environment. * diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index 7b5f910791c8..9410fea3377f 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -100,6 +100,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { send_email, queues, d1_databases, + constellation, r2_buckets, logfwdr, services, @@ -208,6 +209,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { }); } + if (constellation !== undefined && constellation.length > 0) { + output.push({ + type: "Constellation Projects", + entries: constellation.map(({ binding, project_id }) => { + return { + key: binding, + value: project_id, + }; + }), + }); + } + if (r2_buckets !== undefined && r2_buckets.length > 0) { output.push({ type: "R2 Buckets", diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 07ab4d5b3868..90cb725bf59f 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -1,5 +1,6 @@ import path from "node:path"; import TOML from "@iarna/toml"; +import { getConstellationWarningFromEnv } from "../constellation/utils"; import { Diagnostics } from "./diagnostics"; import { deprecated, @@ -1172,6 +1173,16 @@ function normalizeAndValidateEnvironment( validateBindingArray(envName, validateD1Binding), [] ), + constellation: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "constellation", + validateBindingArray(envName, validateConstellationBinding), + [] + ), services: notInheritable( diagnostics, topLevelEnv, @@ -1754,6 +1765,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "kv_namespace", "durable_object_namespace", "d1_database", + "constellation", "r2_bucket", "service", "logfwdr", @@ -2044,6 +2056,45 @@ const validateD1Binding: ValidatorFn = (diagnostics, field, value) => { return isValid; }; +const validateConstellationBinding: ValidatorFn = ( + diagnostics, + field, + value +) => { + if (typeof value !== "object" || value === null) { + diagnostics.errors.push( + `"constellation" bindings should be objects, but got ${JSON.stringify( + value + )}` + ); + return false; + } + let isValid = true; + // Constellation bindings must have a binding and a project. + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push( + `"${field}" bindings should have a string "binding" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if (!isRequiredProperty(value, "project_id", "string")) { + diagnostics.errors.push( + `"${field}" bindings must have a "project_id" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if (isValid && getConstellationWarningFromEnv() === undefined) { + diagnostics.warnings.push( + "Constellation Bindings are currently in beta to allow the API to evolve before general availability.\nPlease report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose\nNote: Run this command with the environment variable NO_CONSTELLATION_WARNING=true to hide this message\n\nFor example: `export NO_CONSTELLATION_WARNING=true && wrangler `" + ); + } + return isValid; +}; + /** * Check that bindings whose names might conflict, don't. * diff --git a/packages/wrangler/src/constellation/utils.ts b/packages/wrangler/src/constellation/utils.ts index b49c21d14fba..11728565c39c 100644 --- a/packages/wrangler/src/constellation/utils.ts +++ b/packages/wrangler/src/constellation/utils.ts @@ -10,7 +10,7 @@ export const getConstellationWarningFromEnv = getEnvironmentVariableFactory({ export const constellationBetaWarning = getConstellationWarningFromEnv() !== undefined ? "" - : "--------------------\n🚧 Constellation AI is currently in open alpha and is not recommended for production data and traffic\n🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose\n🚧 To give feedback, visit https://discord.gg/cloudflaredev\n--------------------\n"; + : "--------------------\n🚧 Constellation is currently in open alpha and is not recommended for production data and traffic\n🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose\n🚧 To give feedback, visit https://discord.gg/cloudflaredev\n--------------------\n"; export const getProjectByName = async ( config: Config, diff --git a/packages/wrangler/src/create-worker-upload-form.ts b/packages/wrangler/src/create-worker-upload-form.ts index 34a70f528ba7..3393e8da4996 100644 --- a/packages/wrangler/src/create-worker-upload-form.ts +++ b/packages/wrangler/src/create-worker-upload-form.ts @@ -50,6 +50,7 @@ export type WorkerMetadataBinding = | { type: "queue"; name: string; queue_name: string } | { type: "r2_bucket"; name: string; bucket_name: string } | { type: "d1"; name: string; id: string; internalEnv?: string } + | { type: "constellation"; name: string; project: string } | { type: "service"; name: string; service: string; environment?: string } | { type: "analytics_engine"; name: string; dataset?: string } | { type: "dispatch_namespace"; name: string; namespace: string } @@ -165,6 +166,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { } ); + bindings.constellation?.forEach(({ binding, project_id }) => { + metadataBindings.push({ + name: binding, + type: "constellation", + project: project_id, + }); + }); + bindings.services?.forEach(({ binding, service, environment }) => { metadataBindings.push({ name: binding, diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 8a9ab11184fb..17e87fbacaf8 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -337,6 +337,7 @@ export type AdditionalDevProps = { processEntrypoint?: boolean; moduleRoot?: string; rules?: Rule[]; + constellation?: Environment["constellation"]; }; type StartDevOptions = DevArguments & @@ -929,6 +930,7 @@ function getBindings( }), ...(args.d1Databases || []), ]), + constellation: configParam.constellation, }; return bindings; diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 53e9981719e2..f853536bb588 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -435,7 +435,7 @@ export function createCLIParser(argv: string[]) { // ai wrangler.command( "constellation", - "🤖 Interact with Constellation AI models", + "🤖 Interact with Constellation models", (aiYargs) => { return constellation(aiYargs.command(subHelp)); } @@ -579,6 +579,7 @@ export function createCLIParser(argv: string[]) { unsafe: config.unsafe, rules: config.rules, queues: config.queues, + constellation: config.constellation, }; await generateTypes(configBindings, config); diff --git a/packages/wrangler/src/publish/publish.ts b/packages/wrangler/src/publish/publish.ts index c8db4d1f2150..38682f178608 100644 --- a/packages/wrangler/src/publish/publish.ts +++ b/packages/wrangler/src/publish/publish.ts @@ -553,6 +553,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m }), r2_buckets: config.r2_buckets, d1_databases: identifyD1BindingsAsBeta(config.d1_databases), + constellation: config.constellation, services: config.services, analytics_engine_datasets: config.analytics_engine_datasets, dispatch_namespaces: config.dispatch_namespaces, diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 8563f8efa5b0..051a0715a78a 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -106,6 +106,7 @@ export const secret = (secretYargs: CommonYargsArgv) => { queues: [], r2_buckets: [], d1_databases: [], + constellation: [], services: [], analytics_engine_datasets: [], wasm_modules: {}, diff --git a/packages/wrangler/src/type-generation.ts b/packages/wrangler/src/type-generation.ts index 844d378d61b0..7340e8143b0e 100644 --- a/packages/wrangler/src/type-generation.ts +++ b/packages/wrangler/src/type-generation.ts @@ -59,6 +59,12 @@ export async function generateTypes( } } + if (configToDTS.constellation) { + for (const service of configToDTS.constellation) { + envTypeStructure.push(`${service.binding}: Fetcher;`); + } + } + if (configToDTS.analytics_engine_datasets) { for (const analyticsEngine of configToDTS.analytics_engine_datasets) { envTypeStructure.push( diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 1553558100bd..b58527e1a321 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -347,7 +347,7 @@ const Scopes = { "See and change Cloudflare Pages projects, settings and deployments.", "zone:read": "Grants read level access to account zone.", "ssl_certs:write": "See and manage mTLS certificates for your account", - "constellation:write": "Manage Constellation AI projects/models", + "constellation:write": "Manage Constellation projects/models", } as const; /** diff --git a/packages/wrangler/src/worker.ts b/packages/wrangler/src/worker.ts index bb81ce592e47..69e00ef45387 100644 --- a/packages/wrangler/src/worker.ts +++ b/packages/wrangler/src/worker.ts @@ -155,6 +155,11 @@ export interface CfD1Database { migrations_dir?: string; } +export interface CfConstellation { + binding: string; + project_id: string; +} + interface CfService { binding: string; service: string; @@ -246,6 +251,7 @@ export interface CfWorkerInit { queues: CfQueue[] | undefined; r2_buckets: CfR2Bucket[] | undefined; d1_databases: CfD1Database[] | undefined; + constellation: CfConstellation[] | undefined; services: CfService[] | undefined; analytics_engine_datasets: CfAnalyticsEngineDataset[] | undefined; dispatch_namespaces: CfDispatchNamespace[] | undefined;