Skip to content

Commit

Permalink
Avoid uploading the 'functions' directory as part of 'wrangler pages …
Browse files Browse the repository at this point in the history
…publish' (#2103)
  • Loading branch information
GregBrimble authored Nov 2, 2022
1 parent ab52f77 commit f1fd62a
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 12 deletions.
9 changes: 9 additions & 0 deletions .changeset/cool-nails-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"wrangler": patch
---

fix: Don't upload `functions/` directory as part of `wrangler pages publish`

If the root directory of a project was the same as the build output directory, we were previously uploading the `functions/` directory as static assets. This PR now ensures that the `functions/` files are only used to create Pages Functions and are no longer uploaded as static assets.

Additionally, we also now _do_ upload `_worker.js`, `_headers`, `_redirects` and `_routes.json` if they aren't immediate children of the build output directory. Previously, we'd ignore all files with this name regardless of location. For example, if you have a `public/blog/how-to-use-pages/_headers` file (where `public` is your build output directory), we will now upload the `_headers` file as a static asset.
41 changes: 41 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
"jest-fetch-mock": "^3.0.3",
"jest-websocket-mock": "^2.3.0",
"mime": "^3.0.0",
"minimatch": "^5.1.0",
"msw": "^0.47.1",
"npx-import": "^1.1.3",
"open": "^8.4.0",
Expand Down
110 changes: 110 additions & 0 deletions packages/wrangler/src/__tests__/pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,116 @@ and that at least one include rule is provided.
`);
});

it("should avoid uploading some files", async () => {
mkdirSync("some_dir/node_modules", { recursive: true });
mkdirSync("some_dir/functions", { recursive: true });

writeFileSync("logo.png", "foobar");
writeFileSync("some_dir/functions/foo.js", "func");
writeFileSync("some_dir/_headers", "headersfile");

writeFileSync("_headers", "headersfile");
writeFileSync("_redirects", "redirectsfile");
writeFileSync("_worker.js", "workerfile");
writeFileSync("_routes.json", "routesfile");
mkdirSync(".git");
writeFileSync(".git/foo", "gitfile");
writeFileSync("some_dir/node_modules/some_package", "nodefile");
mkdirSync("functions");
writeFileSync("functions/foo.js", "func");

setMockResponse(
"/pages/assets/check-missing",
"POST",
async (_, init) => {
const body = JSON.parse(init.body as string) as { hashes: string[] };
assertLater(() => {
expect(init.headers).toMatchObject({
Authorization: "Bearer <<funfetti-auth-jwt>>",
});
expect(body).toMatchObject({
hashes: [
"2082190357cfd3617ccfe04f340c6247",
"95dedb64e6d4940fc2e0f11f711cc2f4",
"09a79777abda8ccc8bdd51dd3ff8e9e9",
],
});
});
return body.hashes;
}
);

// Accumulate multiple requests then assert afterwards
const requests: RequestInit[] = [];
setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
requests.push(init);

return createFetchResult(null, true);
});

assertLater(() => {
expect(requests.length).toBe(3);

expect(requests[0].headers).toMatchObject({
Authorization: "Bearer <<funfetti-auth-jwt>>",
});

let body = JSON.parse(
requests[0].body as string
) as UploadPayloadFile[];
expect(body).toMatchObject([
{
key: "95dedb64e6d4940fc2e0f11f711cc2f4",
value: Buffer.from("headersfile").toString("base64"),
metadata: {
contentType: "application/octet-stream",
},
base64: true,
},
]);

expect(requests[1].headers).toMatchObject({
Authorization: "Bearer <<funfetti-auth-jwt>>",
});

body = JSON.parse(requests[1].body as string) as UploadPayloadFile[];
expect(body).toMatchObject([
{
key: "2082190357cfd3617ccfe04f340c6247",
value: Buffer.from("foobar").toString("base64"),
metadata: {
contentType: "image/png",
},
base64: true,
},
]);

expect(requests[2].headers).toMatchObject({
Authorization: "Bearer <<funfetti-auth-jwt>>",
});

body = JSON.parse(requests[2].body as string) as UploadPayloadFile[];
expect(body).toMatchObject([
{
key: "09a79777abda8ccc8bdd51dd3ff8e9e9",
value: Buffer.from("func").toString("base64"),
metadata: {
contentType: "application/javascript",
},
base64: true,
},
]);
});

await runWrangler("pages project upload .");

expect(std.out).toMatchInlineSnapshot(`
"✨ Success! Uploaded 3 files (TIMINGS)
✨ Upload complete!"
`);
});

it("should retry uploads", async () => {
writeFileSync("logo.txt", "foobar");

Expand Down
4 changes: 4 additions & 0 deletions packages/wrangler/src/is-interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
* or you're piping values from / to another process, etc
*/
export default function isInteractive(): boolean {
if (process.env.CF_PAGES === "1") {
return false;
}

try {
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
} catch {
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/pages/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { version as wranglerVersion } from "../../package.json";

export const MAX_ASSET_COUNT = 20_000;
export const MAX_ASSET_SIZE = 25 * 1024 * 1024;
export const PAGES_CONFIG_CACHE_FILENAME = "pages.json";
export const MAX_BUCKET_SIZE = 50 * 1024 * 1024;
export const MAX_BUCKET_FILE_COUNT = 5000;
Expand Down
32 changes: 20 additions & 12 deletions packages/wrangler/src/pages/upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { dirname, join, relative, resolve, sep } from "node:path";
import { render, Text } from "ink";
import Spinner from "ink-spinner";
import { getType } from "mime";
import { Minimatch } from "minimatch";
import PQueue from "p-queue";
import prettyBytes from "pretty-bytes";
import React from "react";
import { fetchResult } from "../cfetch";
import { FatalError } from "../errors";
import isInteractive from "../is-interactive";
import { logger } from "../logger";
import {
MAX_ASSET_COUNT,
MAX_ASSET_SIZE,
BULK_UPLOAD_CONCURRENCY,
MAX_BUCKET_FILE_COUNT,
MAX_BUCKET_SIZE,
Expand Down Expand Up @@ -96,10 +100,11 @@ export const upload = async (
"_redirects",
"_headers",
"_routes.json",
".DS_Store",
"node_modules",
".git",
];
"functions",
"**/.DS_Store",
"**/node_modules",
"**/.git",
].map((pattern) => new Minimatch(pattern));

const directory = resolve(args.directory);

Expand All @@ -121,10 +126,13 @@ export const upload = async (
await Promise.all(
files.map(async (file) => {
const filepath = join(dir, file);
const relativeFilepath = relative(startingDir, filepath);
const filestat = await stat(filepath);

if (IGNORE_LIST.includes(file)) {
return;
for (const minimatch of IGNORE_LIST) {
if (minimatch.match(relativeFilepath)) {
return;
}
}

if (filestat.isSymbolicLink()) {
Expand All @@ -134,12 +142,12 @@ export const upload = async (
if (filestat.isDirectory()) {
fileMap = await walk(filepath, fileMap, startingDir);
} else {
const name = relative(startingDir, filepath).split(sep).join("/");
const name = relativeFilepath.split(sep).join("/");

if (filestat.size > 25 * 1024 * 1024) {
if (filestat.size > MAX_ASSET_SIZE) {
throw new FatalError(
`Error: Pages only supports files up to ${prettyBytes(
25 * 1024 * 1024
MAX_ASSET_SIZE
)} in size\n${name} is ${prettyBytes(filestat.size)} in size`,
1
);
Expand All @@ -161,9 +169,9 @@ export const upload = async (

const fileMap = await walk(directory);

if (fileMap.size > 20000) {
if (fileMap.size > MAX_ASSET_COUNT) {
throw new FatalError(
`Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
`Error: Pages only supports up to ${MAX_ASSET_COUNT.toLocaleString()} files in a deployment. Ensure you have specified your build output directory correctly.`,
1
);
}
Expand Down Expand Up @@ -390,7 +398,7 @@ function Progress({ done, total }: { done: number; total: number }) {
return (
<>
<Text>
<Spinner type="earth" />
{isInteractive() ? <Spinner type="earth" /> : null}
{` Uploading... (${done}/${total})\n`}
</Text>
</>
Expand Down

0 comments on commit f1fd62a

Please sign in to comment.