Skip to content

Commit 86c0139

Browse files
feat: auto-populating d1 cache data (#436)
* feat: auto-populating d1 cache data * checks for output directory / enabled * Update packages/cloudflare/src/cli/build/utils/populate-cache.ts Co-authored-by: conico974 <[email protected]> * add suggestions * rename onlyPopulate to onlyPopulateWithoutBuilding --------- Co-authored-by: conico974 <[email protected]>
1 parent e0ec01d commit 86c0139

File tree

8 files changed

+143
-24
lines changed

8 files changed

+143
-24
lines changed

.changeset/tame-icons-shave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
feat: auto-populating d1 cache data

examples/e2e/app-router/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"lint": "next lint",
1111
"clean": "rm -rf .turbo node_modules .next .open-next",
1212
"d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS revalidations\"",
13-
"d1:setup": "wrangler d1 execute NEXT_CACHE_D1 --file .open-next/cloudflare/cache-assets-manifest.sql",
14-
"build:worker": "pnpm opennextjs-cloudflare && pnpm d1:clean && pnpm d1:setup",
13+
"build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare --populateCache=local",
1514
"preview": "pnpm build:worker && pnpm wrangler dev",
1615
"e2e": "playwright test -c e2e/playwright.config.ts"
1716
},

packages/cloudflare/src/cli/args.ts

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,70 @@ import { mkdirSync, type Stats, statSync } from "node:fs";
22
import { resolve } from "node:path";
33
import { parseArgs } from "node:util";
44

5+
import type { CacheBindingMode } from "./build/utils/index.js";
6+
import { isCacheBindingMode } from "./build/utils/index.js";
7+
58
export function getArgs(): {
69
skipNextBuild: boolean;
710
skipWranglerConfigCheck: boolean;
811
outputDir?: string;
912
minify: boolean;
13+
populateCache?: { mode: CacheBindingMode; onlyPopulateWithoutBuilding: boolean };
1014
} {
11-
const { skipBuild, skipWranglerConfigCheck, output, noMinify } = parseArgs({
12-
options: {
13-
skipBuild: {
14-
type: "boolean",
15-
short: "s",
16-
default: false,
17-
},
18-
output: {
19-
type: "string",
20-
short: "o",
21-
},
22-
noMinify: {
23-
type: "boolean",
24-
default: false,
15+
const { skipBuild, skipWranglerConfigCheck, output, noMinify, populateCache, onlyPopulateCache } =
16+
parseArgs({
17+
options: {
18+
skipBuild: {
19+
type: "boolean",
20+
short: "s",
21+
default: false,
22+
},
23+
output: {
24+
type: "string",
25+
short: "o",
26+
},
27+
noMinify: {
28+
type: "boolean",
29+
default: false,
30+
},
31+
skipWranglerConfigCheck: {
32+
type: "boolean",
33+
default: false,
34+
},
35+
populateCache: {
36+
type: "string",
37+
},
38+
onlyPopulateCache: {
39+
type: "boolean",
40+
default: false,
41+
},
2542
},
26-
skipWranglerConfigCheck: {
27-
type: "boolean",
28-
default: false,
29-
},
30-
},
31-
allowPositionals: false,
32-
}).values;
43+
allowPositionals: false,
44+
}).values;
3345

3446
const outputDir = output ? resolve(output) : undefined;
3547

3648
if (outputDir) {
3749
assertDirArg(outputDir, "output", true);
3850
}
3951

52+
if (
53+
(populateCache !== undefined || onlyPopulateCache) &&
54+
(!populateCache?.length || !isCacheBindingMode(populateCache))
55+
) {
56+
throw new Error(`Error: missing mode for populate cache flag, expected 'local' | 'remote'`);
57+
}
58+
4059
return {
4160
outputDir,
4261
skipNextBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
4362
skipWranglerConfigCheck:
4463
skipWranglerConfigCheck ||
4564
["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)),
4665
minify: !noMinify,
66+
populateCache: populateCache
67+
? { mode: populateCache, onlyPopulateWithoutBuilding: !!onlyPopulateCache }
68+
: undefined,
4769
};
4870
}
4971

packages/cloudflare/src/cli/build/build.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
createOpenNextConfigIfNotExistent,
2222
createWranglerConfigIfNotExistent,
2323
ensureCloudflareConfig,
24+
populateCache,
2425
} from "./utils/index.js";
2526
import { getVersion } from "./utils/version.js";
2627

@@ -63,6 +64,11 @@ export async function build(projectOpts: ProjectOptions): Promise<void> {
6364
logger.info(`@opennextjs/cloudflare version: ${cloudflare}`);
6465
logger.info(`@opennextjs/aws version: ${aws}`);
6566

67+
if (projectOpts.populateCache?.onlyPopulateWithoutBuilding) {
68+
populateCache(options, config, projectOpts.populateCache.mode);
69+
return;
70+
}
71+
6672
if (projectOpts.skipNextBuild) {
6773
logger.warn("Skipping Next.js build");
6874
} else {
@@ -106,6 +112,10 @@ export async function build(projectOpts: ProjectOptions): Promise<void> {
106112
await createWranglerConfigIfNotExistent(projectOpts);
107113
}
108114

115+
if (projectOpts.populateCache) {
116+
populateCache(options, config, projectOpts.populateCache.mode);
117+
}
118+
109119
logger.info("OpenNext build complete.");
110120
}
111121

packages/cloudflare/src/cli/build/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./ensure-cf-config.js";
44
export * from "./extract-project-env-vars.js";
55
export * from "./needs-experimental-react.js";
66
export * from "./normalize-path.js";
7+
export * from "./populate-cache.js";
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { spawnSync } from "node:child_process";
2+
import { existsSync } from "node:fs";
3+
import path from "node:path";
4+
5+
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
6+
import logger from "@opennextjs/aws/logger.js";
7+
import type {
8+
IncludedIncrementalCache,
9+
IncludedTagCache,
10+
LazyLoadedOverride,
11+
OpenNextConfig,
12+
} from "@opennextjs/aws/types/open-next.js";
13+
import type { IncrementalCache, TagCache } from "@opennextjs/aws/types/overrides.js";
14+
15+
export type CacheBindingMode = "local" | "remote";
16+
17+
async function resolveCacheName(
18+
value:
19+
| IncludedIncrementalCache
20+
| IncludedTagCache
21+
| LazyLoadedOverride<IncrementalCache>
22+
| LazyLoadedOverride<TagCache>
23+
) {
24+
return typeof value === "function" ? (await value()).name : value;
25+
}
26+
27+
function runWrangler(opts: BuildOptions, mode: CacheBindingMode, args: string[]) {
28+
const result = spawnSync(
29+
opts.packager,
30+
["exec", "wrangler", ...args, mode === "remote" && "--remote"].filter((v): v is string => !!v),
31+
{
32+
shell: true,
33+
stdio: ["ignore", "ignore", "inherit"],
34+
}
35+
);
36+
37+
if (result.status !== 0) {
38+
logger.error("Failed to populate cache");
39+
process.exit(1);
40+
} else {
41+
logger.info("Successfully populated cache");
42+
}
43+
}
44+
45+
export async function populateCache(opts: BuildOptions, config: OpenNextConfig, mode: CacheBindingMode) {
46+
const { incrementalCache, tagCache } = config.default.override ?? {};
47+
48+
if (!existsSync(opts.outputDir)) {
49+
logger.error("Unable to populate cache: Open Next build not found");
50+
process.exit(1);
51+
}
52+
53+
if (!config.dangerous?.disableIncrementalCache && incrementalCache) {
54+
logger.info("Incremental cache does not need populating");
55+
}
56+
57+
if (!config.dangerous?.disableTagCache && !config.dangerous?.disableIncrementalCache && tagCache) {
58+
const name = await resolveCacheName(tagCache);
59+
switch (name) {
60+
case "d1-tag-cache": {
61+
logger.info("\nPopulating D1 tag cache...");
62+
63+
runWrangler(opts, mode, [
64+
"d1 execute",
65+
"NEXT_CACHE_D1",
66+
`--file ${JSON.stringify(path.join(opts.outputDir, "cloudflare/cache-assets-manifest.sql"))}`,
67+
]);
68+
break;
69+
}
70+
default:
71+
logger.info("Tag cache does not need populating");
72+
}
73+
}
74+
}
75+
76+
export function isCacheBindingMode(v: string | undefined): v is CacheBindingMode {
77+
return !!v && ["local", "remote"].includes(v);
78+
}

packages/cloudflare/src/cli/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { build } from "./build/build.js";
66

77
const nextAppDir = process.cwd();
88

9-
const { skipNextBuild, skipWranglerConfigCheck, outputDir, minify } = getArgs();
9+
const { skipNextBuild, skipWranglerConfigCheck, outputDir, minify, populateCache } = getArgs();
1010

1111
await build({
1212
sourceDir: nextAppDir,
1313
outputDir: resolve(outputDir ?? nextAppDir, ".open-next"),
1414
skipNextBuild,
1515
skipWranglerConfigCheck,
1616
minify,
17+
populateCache,
1718
});

packages/cloudflare/src/cli/project-options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { CacheBindingMode } from "./build/utils/index.js";
2+
13
export type ProjectOptions = {
24
// Next app root folder
35
sourceDir: string;
@@ -9,4 +11,5 @@ export type ProjectOptions = {
911
skipWranglerConfigCheck: boolean;
1012
// Whether minification of the worker should be enabled
1113
minify: boolean;
14+
populateCache?: { mode: CacheBindingMode; onlyPopulateWithoutBuilding: boolean };
1215
};

0 commit comments

Comments
 (0)