@opennextjs/cloudflare 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -64
  2. package/dist/api/{get-cloudflare-context.d.ts → cloudflare-context.d.ts} +8 -1
  3. package/dist/api/cloudflare-context.js +96 -0
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/index.js +1 -1
  6. package/dist/api/kvCache.d.ts +0 -5
  7. package/dist/api/kvCache.js +15 -28
  8. package/dist/cli/build/build.d.ts +1 -1
  9. package/dist/cli/build/build.js +2 -12
  10. package/dist/cli/build/bundle-server.d.ts +4 -5
  11. package/dist/cli/build/bundle-server.js +39 -38
  12. package/dist/cli/build/open-next/compile-env-files.d.ts +1 -1
  13. package/dist/cli/build/open-next/compile-env-files.js +2 -2
  14. package/dist/cli/build/open-next/createServerBundle.js +3 -2
  15. package/dist/cli/build/patches/ast/optional-deps.d.ts +13 -0
  16. package/dist/cli/build/patches/ast/optional-deps.js +31 -0
  17. package/dist/cli/build/patches/ast/optional-deps.spec.d.ts +1 -0
  18. package/dist/cli/build/patches/ast/optional-deps.spec.js +89 -0
  19. package/dist/cli/build/patches/ast/util.d.ts +50 -0
  20. package/dist/cli/build/patches/ast/util.js +65 -0
  21. package/dist/cli/build/patches/ast/util.spec.d.ts +1 -0
  22. package/dist/cli/build/patches/ast/util.spec.js +43 -0
  23. package/dist/cli/build/patches/ast/vercel-og.d.ts +23 -0
  24. package/dist/cli/build/patches/ast/vercel-og.js +58 -0
  25. package/dist/cli/build/patches/ast/vercel-og.spec.d.ts +1 -0
  26. package/dist/cli/build/patches/ast/vercel-og.spec.js +22 -0
  27. package/dist/cli/build/patches/investigated/copy-package-cli-files.d.ts +4 -3
  28. package/dist/cli/build/patches/investigated/copy-package-cli-files.js +8 -5
  29. package/dist/cli/build/patches/investigated/index.d.ts +1 -0
  30. package/dist/cli/build/patches/investigated/index.js +1 -0
  31. package/dist/cli/build/patches/investigated/patch-cache.d.ts +2 -2
  32. package/dist/cli/build/patches/investigated/patch-cache.js +7 -6
  33. package/dist/cli/build/patches/investigated/patch-require.d.ts +1 -4
  34. package/dist/cli/build/patches/investigated/patch-require.js +1 -4
  35. package/dist/cli/build/patches/investigated/patch-vercel-og-library.d.ts +7 -0
  36. package/dist/cli/build/patches/investigated/patch-vercel-og-library.js +39 -0
  37. package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.d.ts +1 -0
  38. package/dist/cli/build/patches/investigated/patch-vercel-og-library.spec.js +50 -0
  39. package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.d.ts +2 -5
  40. package/dist/cli/build/patches/investigated/update-webpack-chunks-file/index.js +6 -6
  41. package/dist/cli/build/patches/to-investigate/inline-eval-manifest.d.ts +2 -2
  42. package/dist/cli/build/patches/to-investigate/inline-eval-manifest.js +21 -17
  43. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.d.ts +2 -2
  44. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest-require.js +4 -2
  45. package/dist/cli/build/patches/to-investigate/inline-next-require.d.ts +2 -2
  46. package/dist/cli/build/patches/to-investigate/inline-next-require.js +16 -12
  47. package/dist/cli/build/patches/to-investigate/patch-find-dir.d.ts +3 -6
  48. package/dist/cli/build/patches/to-investigate/patch-find-dir.js +11 -11
  49. package/dist/cli/build/patches/to-investigate/patch-read-file.d.ts +3 -3
  50. package/dist/cli/build/patches/to-investigate/patch-read-file.js +15 -16
  51. package/dist/cli/build/patches/to-investigate/wrangler-deps.d.ts +2 -2
  52. package/dist/cli/build/patches/to-investigate/wrangler-deps.js +36 -67
  53. package/dist/cli/build/utils/create-config-files.d.ts +1 -1
  54. package/dist/cli/build/utils/create-config-files.js +2 -2
  55. package/dist/cli/project-options.d.ts +7 -0
  56. package/dist/cli/project-options.js +1 -0
  57. package/package.json +8 -6
  58. package/templates/defaults/wrangler.json +1 -0
  59. package/dist/api/get-cloudflare-context.js +0 -42
  60. package/dist/cli/config.d.ts +0 -42
  61. package/dist/cli/config.js +0 -92
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { optionalDepRule } from "./optional-deps.js";
3
+ import { patchCode } from "./util.js";
4
+ describe("optional dependecy", () => {
5
+ it('should wrap a top-level require("caniuse-lite") in a try-catch', () => {
6
+ const code = `t = require("caniuse-lite");`;
7
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
8
+ "try {
9
+ t = require("caniuse-lite");
10
+ } catch {
11
+ throw new Error('The optional dependency "caniuse-lite" is not installed');
12
+ };"
13
+ `);
14
+ });
15
+ it('should wrap a top-level require("caniuse-lite/data") in a try-catch', () => {
16
+ const code = `t = require("caniuse-lite/data");`;
17
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
18
+ "try {
19
+ t = require("caniuse-lite/data");
20
+ } catch {
21
+ throw new Error('The optional dependency "caniuse-lite/data" is not installed');
22
+ };"
23
+ `);
24
+ });
25
+ it('should wrap e.exports = require("caniuse-lite") in a try-catch', () => {
26
+ const code = 'e.exports = require("caniuse-lite");';
27
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
28
+ "try {
29
+ e.exports = require("caniuse-lite");
30
+ } catch {
31
+ throw new Error('The optional dependency "caniuse-lite" is not installed');
32
+ };"
33
+ `);
34
+ });
35
+ it('should wrap module.exports = require("caniuse-lite") in a try-catch', () => {
36
+ const code = 'module.exports = require("caniuse-lite");';
37
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
38
+ "try {
39
+ module.exports = require("caniuse-lite");
40
+ } catch {
41
+ throw new Error('The optional dependency "caniuse-lite" is not installed');
42
+ };"
43
+ `);
44
+ });
45
+ it('should wrap exports.foo = require("caniuse-lite") in a try-catch', () => {
46
+ const code = 'exports.foo = require("caniuse-lite");';
47
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
48
+ "try {
49
+ exports.foo = require("caniuse-lite");
50
+ } catch {
51
+ throw new Error('The optional dependency "caniuse-lite" is not installed');
52
+ };"
53
+ `);
54
+ });
55
+ it('should not wrap require("lodash") in a try-catch', () => {
56
+ const code = 't = require("lodash");';
57
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`"t = require("lodash");"`);
58
+ });
59
+ it('should not wrap require("other-module") if it does not match caniuse-lite regex', () => {
60
+ const code = 't = require("other-module");';
61
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`"t = require("other-module");"`);
62
+ });
63
+ it("should not wrap a require() call already inside a try-catch", () => {
64
+ const code = `
65
+ try {
66
+ const t = require("caniuse-lite");
67
+ } catch {}
68
+ `;
69
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
70
+ "try {
71
+ const t = require("caniuse-lite");
72
+ } catch {}
73
+ "
74
+ `);
75
+ });
76
+ it("should handle require with subpath and not wrap if already in try-catch", () => {
77
+ const code = `
78
+ try {
79
+ const t = require("caniuse-lite/path");
80
+ } catch {}
81
+ `;
82
+ expect(patchCode(code, optionalDepRule)).toMatchInlineSnapshot(`
83
+ "try {
84
+ const t = require("caniuse-lite/path");
85
+ } catch {}
86
+ "
87
+ `);
88
+ });
89
+ });
@@ -0,0 +1,50 @@
1
+ import { type Edit, Lang, type NapiConfig, type SgNode } from "@ast-grep/napi";
2
+ /**
3
+ * fix has the same meaning as in yaml rules
4
+ * see https://ast-grep.github.io/guide/rewrite-code.html#using-fix-in-yaml-rule
5
+ */
6
+ export type RuleConfig = NapiConfig & {
7
+ fix?: string;
8
+ };
9
+ /**
10
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
11
+ *
12
+ * The rule must have a `fix` to rewrite the matched node.
13
+ *
14
+ * Tip: use https://ast-grep.github.io/playground.html to create rules.
15
+ *
16
+ * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
17
+ * @param root The root node
18
+ * @param once only apply once
19
+ * @returns A list of edits and a list of matches.
20
+ */
21
+ export declare function applyRule(rule: string | RuleConfig, root: SgNode, { once }?: {
22
+ once?: boolean | undefined;
23
+ }): {
24
+ edits: Edit[];
25
+ matches: SgNode<import("@ast-grep/napi/types/staticTypes").TypesMap, import("@ast-grep/napi/types/staticTypes").Kinds<import("@ast-grep/napi/types/staticTypes").TypesMap>>[];
26
+ };
27
+ /**
28
+ * Parse a file and obtain its root.
29
+ *
30
+ * @param path The file path
31
+ * @param lang The language to parse. Defaults to TypeScript.
32
+ * @returns The root for the file.
33
+ */
34
+ export declare function parseFile(path: string, lang?: Lang): SgNode<import("@ast-grep/napi/types/staticTypes").TypesMap, import("@ast-grep/napi/types/staticTypes").Kinds<import("@ast-grep/napi/types/staticTypes").TypesMap>>;
35
+ /**
36
+ * Patches the code from by applying the rule.
37
+ *
38
+ * This function is mainly for on off edits and tests,
39
+ * use `getRuleEdits` to apply multiple rules.
40
+ *
41
+ * @param code The source code
42
+ * @param rule The astgrep rule (yaml or NapiConfig)
43
+ * @param lang The language used by the source code
44
+ * @param lang Whether to apply the rule only once
45
+ * @returns The patched code
46
+ */
47
+ export declare function patchCode(code: string, rule: string | RuleConfig, { lang, once }?: {
48
+ lang?: Lang | undefined;
49
+ once?: boolean | undefined;
50
+ }): string;
@@ -0,0 +1,65 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { Lang, parse } from "@ast-grep/napi";
3
+ import yaml from "yaml";
4
+ /**
5
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
6
+ *
7
+ * The rule must have a `fix` to rewrite the matched node.
8
+ *
9
+ * Tip: use https://ast-grep.github.io/playground.html to create rules.
10
+ *
11
+ * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
12
+ * @param root The root node
13
+ * @param once only apply once
14
+ * @returns A list of edits and a list of matches.
15
+ */
16
+ export function applyRule(rule, root, { once = false } = {}) {
17
+ const ruleConfig = typeof rule === "string" ? yaml.parse(rule) : rule;
18
+ if (ruleConfig.transform) {
19
+ throw new Error("transform is not supported");
20
+ }
21
+ if (!ruleConfig.fix) {
22
+ throw new Error("no fix to apply");
23
+ }
24
+ const fix = ruleConfig.fix;
25
+ const matches = once ? [root.find(ruleConfig)].filter((m) => m !== null) : root.findAll(ruleConfig);
26
+ const edits = [];
27
+ matches.forEach((match) => {
28
+ edits.push(match.replace(
29
+ // Replace known placeholders by their value
30
+ fix
31
+ .replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) => match
32
+ .getMultipleMatches(name)
33
+ .map((n) => n.text())
34
+ .join(""))
35
+ .replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)));
36
+ });
37
+ return { edits, matches };
38
+ }
39
+ /**
40
+ * Parse a file and obtain its root.
41
+ *
42
+ * @param path The file path
43
+ * @param lang The language to parse. Defaults to TypeScript.
44
+ * @returns The root for the file.
45
+ */
46
+ export function parseFile(path, lang = Lang.TypeScript) {
47
+ return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
48
+ }
49
+ /**
50
+ * Patches the code from by applying the rule.
51
+ *
52
+ * This function is mainly for on off edits and tests,
53
+ * use `getRuleEdits` to apply multiple rules.
54
+ *
55
+ * @param code The source code
56
+ * @param rule The astgrep rule (yaml or NapiConfig)
57
+ * @param lang The language used by the source code
58
+ * @param lang Whether to apply the rule only once
59
+ * @returns The patched code
60
+ */
61
+ export function patchCode(code, rule, { lang = Lang.TypeScript, once = false } = {}) {
62
+ const node = parse(lang, code).root();
63
+ const { edits } = applyRule(rule, node, { once });
64
+ return node.commitEdits(edits);
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { patchCode } from "./util.js";
3
+ describe("patchCode", () => {
4
+ afterEach(() => {
5
+ vi.clearAllMocks();
6
+ });
7
+ it("should throw an error if rule has a transform", () => {
8
+ expect(() => patchCode(`console.log("hi")`, { rule: { pattern: "console.log($MSG)" }, transform: "not supported" })).toThrow(/not supported/);
9
+ });
10
+ it("should throw an error if rule has no fix", () => {
11
+ expect(() => patchCode(`console.log("hi")`, { rule: { pattern: "console.log($MSG)" } })).toThrow(/no fix/);
12
+ });
13
+ it("should accept yaml rules", () => {
14
+ const yamlRule = `
15
+ rule:
16
+ pattern: a
17
+ fix: b
18
+ `;
19
+ expect(patchCode(`a`, yamlRule)).toEqual("b");
20
+ });
21
+ it("should apply fix to a single match when once is true", () => {
22
+ expect(patchCode(`a+a`, { rule: { pattern: "a" }, fix: "b" }, { once: true })).toEqual("b+a");
23
+ });
24
+ it("should apply fix to all matches when once is false (default)", () => {
25
+ expect(patchCode(`a+a`, { rule: { pattern: "a" }, fix: "b" })).toEqual("b+b");
26
+ expect(patchCode(`a+a`, { rule: { pattern: "a" }, fix: "b" }, { once: false })).toEqual("b+b");
27
+ });
28
+ it("should handle no matches", () => {
29
+ expect(patchCode(`a`, { rule: { pattern: "b" }, fix: "c" })).toEqual("a");
30
+ });
31
+ it("should replace $PLACEHOLDER with match text", () => {
32
+ expect(patchCode(`console.log(message)`, { rule: { pattern: "console.log($MSG)" }, fix: "$MSG" })).toEqual("message");
33
+ });
34
+ it("should handle $PLACEHODLERS that are not found in matches", () => {
35
+ expect(patchCode(`console.log(message)`, { rule: { pattern: "console.log($MSG)" }, fix: "$WHAT$$$WHAT" })).toEqual("$WHAT");
36
+ });
37
+ it("should replace $$$PLACEHOLDER with match text", () => {
38
+ expect(patchCode(`console.log("hello" + world, "!")`, {
39
+ rule: { pattern: "console.log($$$ARGS)" },
40
+ fix: "$$$ARGS",
41
+ })).toEqual(`"hello" + world,"!"`);
42
+ });
43
+ });
@@ -0,0 +1,23 @@
1
+ import { SgNode } from "@ast-grep/napi";
2
+ export declare const vercelOgImportRule = "\nrule:\n pattern: $NODE\n kind: string\n regex: \"next/dist/compiled/@vercel/og/index\\\\.node\\\\.js\"\ninside:\n kind: arguments\n inside:\n kind: call_expression\n stopBy: end\n has:\n field: function\n regex: \"import\"\n\nfix: |-\n \"next/dist/compiled/@vercel/og/index.edge.js\"\n";
3
+ /**
4
+ * Patches Node.js imports for the library to be Edge imports.
5
+ *
6
+ * @param root Root node.
7
+ * @returns Results of applying the rule.
8
+ */
9
+ export declare function patchVercelOgImport(root: SgNode): {
10
+ edits: import("@ast-grep/napi").Edit[];
11
+ matches: SgNode<import("@ast-grep/napi/types/staticTypes.js").TypesMap, import("@ast-grep/napi/types/staticTypes.js").Kinds<import("@ast-grep/napi/types/staticTypes.js").TypesMap>>[];
12
+ };
13
+ export declare const vercelOgFallbackFontRule = "\nrule:\n kind: variable_declaration\n all:\n - has:\n kind: variable_declarator\n has:\n kind: identifier\n regex: ^fallbackFont$\n - has:\n kind: call_expression\n pattern: fetch(new URL(\"$PATH\", $$$REST))\n stopBy: end\n\nfix: |-\n async function getFallbackFont() {\n // .bin is used so that a loader does not need to be configured for .ttf files\n return (await import(\"$PATH.bin\")).default;\n }\n\n var fallbackFont = getFallbackFont();\n";
14
+ /**
15
+ * Patches the default font fetching to use a .bin import.
16
+ *
17
+ * @param root Root node.
18
+ * @returns Results of applying the rule.
19
+ */
20
+ export declare function patchVercelOgFallbackFont(root: SgNode): {
21
+ edits: import("@ast-grep/napi").Edit[];
22
+ matches: SgNode<import("@ast-grep/napi/types/staticTypes.js").TypesMap, import("@ast-grep/napi/types/staticTypes.js").Kinds<import("@ast-grep/napi/types/staticTypes.js").TypesMap>>[];
23
+ };
@@ -0,0 +1,58 @@
1
+ import { applyRule } from "./util.js";
2
+ export const vercelOgImportRule = `
3
+ rule:
4
+ pattern: $NODE
5
+ kind: string
6
+ regex: "next/dist/compiled/@vercel/og/index\\\\.node\\\\.js"
7
+ inside:
8
+ kind: arguments
9
+ inside:
10
+ kind: call_expression
11
+ stopBy: end
12
+ has:
13
+ field: function
14
+ regex: "import"
15
+
16
+ fix: |-
17
+ "next/dist/compiled/@vercel/og/index.edge.js"
18
+ `;
19
+ /**
20
+ * Patches Node.js imports for the library to be Edge imports.
21
+ *
22
+ * @param root Root node.
23
+ * @returns Results of applying the rule.
24
+ */
25
+ export function patchVercelOgImport(root) {
26
+ return applyRule(vercelOgImportRule, root);
27
+ }
28
+ export const vercelOgFallbackFontRule = `
29
+ rule:
30
+ kind: variable_declaration
31
+ all:
32
+ - has:
33
+ kind: variable_declarator
34
+ has:
35
+ kind: identifier
36
+ regex: ^fallbackFont$
37
+ - has:
38
+ kind: call_expression
39
+ pattern: fetch(new URL("$PATH", $$$REST))
40
+ stopBy: end
41
+
42
+ fix: |-
43
+ async function getFallbackFont() {
44
+ // .bin is used so that a loader does not need to be configured for .ttf files
45
+ return (await import("$PATH.bin")).default;
46
+ }
47
+
48
+ var fallbackFont = getFallbackFont();
49
+ `;
50
+ /**
51
+ * Patches the default font fetching to use a .bin import.
52
+ *
53
+ * @param root Root node.
54
+ * @returns Results of applying the rule.
55
+ */
56
+ export function patchVercelOgFallbackFont(root) {
57
+ return applyRule(vercelOgFallbackFontRule, root);
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { patchCode } from "./util";
3
+ import { vercelOgFallbackFontRule, vercelOgImportRule } from "./vercel-og";
4
+ describe("vercelOgImportRule", () => {
5
+ it("should rewrite a node import to an edge import", () => {
6
+ const code = `e.exports=import("next/dist/compiled/@vercel/og/index.node.js")`;
7
+ expect(patchCode(code, vercelOgImportRule)).toMatchInlineSnapshot(`"e.exports=import("next/dist/compiled/@vercel/og/index.edge.js")"`);
8
+ });
9
+ });
10
+ describe("vercelOgFallbackFontRule", () => {
11
+ it("should replace a fetch call for a font with an import", () => {
12
+ const code = `var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());`;
13
+ expect(patchCode(code, vercelOgFallbackFontRule)).toMatchInlineSnapshot(`
14
+ "async function getFallbackFont() {
15
+ // .bin is used so that a loader does not need to be configured for .ttf files
16
+ return (await import("./noto-sans-v27-latin-regular.ttf.bin")).default;
17
+ }
18
+
19
+ var fallbackFont = getFallbackFont();"
20
+ `);
21
+ });
22
+ });
@@ -1,6 +1,7 @@
1
1
  import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- import { Config } from "../../../config.js";
3
2
  /**
4
- * Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
3
+ * Copies
4
+ * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates`
5
+ * - `worker.js` to `.open-next/`
5
6
  */
6
- export declare function copyPackageCliFiles(packageDistDir: string, config: Config, openNextOptions: BuildOptions): void;
7
+ export declare function copyPackageCliFiles(packageDistDir: string, buildOpts: BuildOptions): void;
@@ -2,12 +2,15 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { getOutputWorkerPath } from "../../bundle-server.js";
4
4
  /**
5
- * Copies the template files present in the cloudflare adapter package into the standalone node_modules folder
5
+ * Copies
6
+ * - the template files present in the cloudflare adapter package to `.open-next/cloudflare-templates`
7
+ * - `worker.js` to `.open-next/`
6
8
  */
7
- export function copyPackageCliFiles(packageDistDir, config, openNextOptions) {
9
+ export function copyPackageCliFiles(packageDistDir, buildOpts) {
8
10
  console.log("# copyPackageTemplateFiles");
9
- const sourceDir = path.join(packageDistDir, "cli");
10
- const destinationDir = path.join(config.paths.internal.package, "cli");
11
+ const sourceDir = path.join(packageDistDir, "cli/templates");
12
+ const destinationDir = path.join(buildOpts.outputDir, "cloudflare-templates");
13
+ fs.mkdirSync(destinationDir, { recursive: true });
11
14
  fs.cpSync(sourceDir, destinationDir, { recursive: true });
12
- fs.copyFileSync(path.join(packageDistDir, "cli", "templates", "worker.js"), getOutputWorkerPath(openNextOptions));
15
+ fs.copyFileSync(path.join(packageDistDir, "cli/templates/worker.js"), getOutputWorkerPath(buildOpts));
13
16
  }
@@ -1,4 +1,5 @@
1
1
  export * from "./copy-package-cli-files.js";
2
2
  export * from "./patch-cache.js";
3
3
  export * from "./patch-require.js";
4
+ export * from "./patch-vercel-og-library.js";
4
5
  export * from "./update-webpack-chunks-file/index.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./copy-package-cli-files.js";
2
2
  export * from "./patch-cache.js";
3
3
  export * from "./patch-require.js";
4
+ export * from "./patch-vercel-og-library.js";
4
5
  export * from "./update-webpack-chunks-file/index.js";
@@ -1,4 +1,4 @@
1
- import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * Sets up the OpenNext cache handler in a Next.js build.
4
4
  *
@@ -10,4 +10,4 @@ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
10
10
  * build-time. Therefore, we have to manually override the default way that the cache handler is
11
11
  * instantiated with a dynamic require that uses a string literal for the path.
12
12
  */
13
- export declare function patchCache(code: string, openNextOptions: BuildOptions): Promise<string>;
13
+ export declare function patchCache(code: string, buildOpts: BuildOptions): Promise<string>;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
2
3
  import { normalizePath } from "../../utils/index.js";
3
4
  /**
4
5
  * Sets up the OpenNext cache handler in a Next.js build.
@@ -11,13 +12,13 @@ import { normalizePath } from "../../utils/index.js";
11
12
  * build-time. Therefore, we have to manually override the default way that the cache handler is
12
13
  * instantiated with a dynamic require that uses a string literal for the path.
13
14
  */
14
- export async function patchCache(code, openNextOptions) {
15
- const { appBuildOutputPath, outputDir, monorepoRoot } = openNextOptions;
15
+ export async function patchCache(code, buildOpts) {
16
+ const { outputDir } = buildOpts;
16
17
  // TODO: switch to cache.mjs
17
- const outputPath = path.join(outputDir, "server-functions", "default");
18
- const packagePath = path.relative(monorepoRoot, appBuildOutputPath);
19
- const cacheFile = path.join(outputPath, packagePath, "cache.cjs");
20
- return code.replace("const { cacheHandler } = this.nextConfig;", `const cacheHandler = null;
18
+ const outputPath = path.join(outputDir, "server-functions/default");
19
+ const cacheFile = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
20
+ return code.replace("const { cacheHandler } = this.nextConfig;", `
21
+ const cacheHandler = null;
21
22
  CacheHandler = require('${normalizePath(cacheFile)}').default;
22
23
  `);
23
24
  }
@@ -1,7 +1,4 @@
1
1
  /**
2
- * ESBuild does not support CJS format
3
- * See https://github.com/evanw/esbuild/issues/1921 and linked issues
4
- * Some of the solutions are based on `module.createRequire()` not implemented in workerd.
5
- * James on Aug 29: `module.createRequire()` is planned.
2
+ * Replaces webpack `__require` with actual `require`
6
3
  */
7
4
  export declare function patchRequire(code: string): string;
@@ -1,8 +1,5 @@
1
1
  /**
2
- * ESBuild does not support CJS format
3
- * See https://github.com/evanw/esbuild/issues/1921 and linked issues
4
- * Some of the solutions are based on `module.createRequire()` not implemented in workerd.
5
- * James on Aug 29: `module.createRequire()` is planned.
2
+ * Replaces webpack `__require` with actual `require`
6
3
  */
7
4
  export function patchRequire(code) {
8
5
  return code.replace(/__require\d?\(/g, "require(").replace(/__require\d?\./g, "require.");
@@ -0,0 +1,7 @@
1
+ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
2
+ /**
3
+ * Patches the usage of @vercel/og to be compatible with Cloudflare Workers.
4
+ *
5
+ * @param buildOpts Build options.
6
+ */
7
+ export declare function patchVercelOgLibrary(buildOpts: BuildOptions): void;
@@ -0,0 +1,39 @@
1
+ import { copyFileSync, existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
4
+ import { globSync } from "glob";
5
+ import { parseFile } from "../ast/util.js";
6
+ import { patchVercelOgFallbackFont, patchVercelOgImport } from "../ast/vercel-og.js";
7
+ /**
8
+ * Patches the usage of @vercel/og to be compatible with Cloudflare Workers.
9
+ *
10
+ * @param buildOpts Build options.
11
+ */
12
+ export function patchVercelOgLibrary(buildOpts) {
13
+ const { appBuildOutputPath, outputDir } = buildOpts;
14
+ const packagePath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts));
15
+ for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"))) {
16
+ const traceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" }));
17
+ const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js"));
18
+ if (!tracedNodePath)
19
+ continue;
20
+ const outputDir = path.join(packagePath, "node_modules/next/dist/compiled/@vercel/og");
21
+ const outputEdgePath = path.join(outputDir, "index.edge.js");
22
+ // Ensure the edge version is available in the OpenNext node_modules.
23
+ if (!existsSync(outputEdgePath)) {
24
+ const tracedEdgePath = path.join(path.dirname(traceInfoPath), tracedNodePath.replace("index.node.js", "index.edge.js"));
25
+ copyFileSync(tracedEdgePath, outputEdgePath);
26
+ // Change font fetches in the library to use imports.
27
+ const node = parseFile(outputEdgePath);
28
+ const { edits, matches } = patchVercelOgFallbackFont(node);
29
+ writeFileSync(outputEdgePath, node.commitEdits(edits));
30
+ const fontFileName = matches[0].getMatch("PATH").text();
31
+ renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
32
+ }
33
+ // Change node imports for the library to edge imports.
34
+ const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", "");
35
+ const node = parseFile(routeFilePath);
36
+ const { edits } = patchVercelOgImport(node);
37
+ writeFileSync(routeFilePath, node.commitEdits(edits));
38
+ }
39
+ }
@@ -0,0 +1,50 @@
1
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import mockFs from "mock-fs";
4
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
+ import { patchVercelOgLibrary } from "./patch-vercel-og-library";
6
+ const nodeModulesVercelOgDir = "node_modules/.pnpm/next@14.2.11/node_modules/next/dist/compiled/@vercel/og";
7
+ const nextServerOgNftPath = "examples/api/.next/server/app/og/route.js.nft.json";
8
+ const openNextFunctionDir = "examples/api/.open-next/server-functions/default/examples/api";
9
+ const openNextOgRoutePath = path.join(openNextFunctionDir, ".next/server/app/og/route.js");
10
+ const openNextVercelOgDir = path.join(openNextFunctionDir, "node_modules/next/dist/compiled/@vercel/og");
11
+ const buildOpts = {
12
+ appBuildOutputPath: "examples/api",
13
+ monorepoRoot: "",
14
+ outputDir: "examples/api/.open-next",
15
+ };
16
+ describe("patchVercelOgLibrary", () => {
17
+ beforeAll(() => {
18
+ mockFs();
19
+ mkdirSync(nodeModulesVercelOgDir, { recursive: true });
20
+ mkdirSync(path.dirname(nextServerOgNftPath), { recursive: true });
21
+ mkdirSync(path.dirname(openNextOgRoutePath), { recursive: true });
22
+ mkdirSync(openNextVercelOgDir, { recursive: true });
23
+ writeFileSync(nextServerOgNftPath, JSON.stringify({ version: 1, files: [`../../../../../../${nodeModulesVercelOgDir}/index.node.js`] }));
24
+ writeFileSync(path.join(nodeModulesVercelOgDir, "index.edge.js"), `var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());`);
25
+ writeFileSync(openNextOgRoutePath, `e.exports=import("next/dist/compiled/@vercel/og/index.node.js")`);
26
+ writeFileSync(path.join(openNextVercelOgDir, "index.node.js"), "");
27
+ writeFileSync(path.join(openNextVercelOgDir, "noto-sans-v27-latin-regular.ttf"), "");
28
+ });
29
+ afterAll(() => mockFs.restore());
30
+ it("should patch the open-next files correctly", () => {
31
+ patchVercelOgLibrary(buildOpts);
32
+ expect(readdirSync(openNextVercelOgDir)).toMatchInlineSnapshot(`
33
+ [
34
+ "index.edge.js",
35
+ "index.node.js",
36
+ "noto-sans-v27-latin-regular.ttf.bin",
37
+ ]
38
+ `);
39
+ expect(readFileSync(path.join(openNextVercelOgDir, "index.edge.js"), { encoding: "utf-8" }))
40
+ .toMatchInlineSnapshot(`
41
+ "async function getFallbackFont() {
42
+ // .bin is used so that a loader does not need to be configured for .ttf files
43
+ return (await import("./noto-sans-v27-latin-regular.ttf.bin")).default;
44
+ }
45
+
46
+ var fallbackFont = getFallbackFont();"
47
+ `);
48
+ expect(readFileSync(openNextOgRoutePath, { encoding: "utf-8" })).toMatchInlineSnapshot(`"e.exports=import("next/dist/compiled/@vercel/og/index.edge.js")"`);
49
+ });
50
+ });
@@ -1,8 +1,5 @@
1
- import { Config } from "../../../../config.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
4
- *
5
- * This hack is particularly bad as it indicates that files inside the output directory still get a hold of files from the outside: `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`
6
- * so this shows that not everything that's needed to deploy the application is in the output directory...
7
4
  */
8
- export declare function updateWebpackChunksFile(config: Config): Promise<void>;
5
+ export declare function updateWebpackChunksFile(buildOpts: BuildOptions): Promise<void>;
@@ -1,17 +1,17 @@
1
1
  import { readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
3
4
  import { getUpdatedWebpackChunksFileContent } from "./get-updated-webpack-chunks-file-content.js";
4
5
  /**
5
6
  * Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
6
- *
7
- * This hack is particularly bad as it indicates that files inside the output directory still get a hold of files from the outside: `${nextjsAppPaths.standaloneAppServerDir}/webpack-runtime.js`
8
- * so this shows that not everything that's needed to deploy the application is in the output directory...
9
7
  */
10
- export async function updateWebpackChunksFile(config) {
8
+ export async function updateWebpackChunksFile(buildOpts) {
11
9
  console.log("# updateWebpackChunksFile");
12
- const webpackRuntimeFile = join(config.paths.output.standaloneAppServer, "webpack-runtime.js");
10
+ const { outputDir } = buildOpts;
11
+ const dotNextServerDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server");
12
+ const webpackRuntimeFile = join(dotNextServerDir, "webpack-runtime.js");
13
13
  const fileContent = readFileSync(webpackRuntimeFile, "utf-8");
14
- const chunks = readdirSync(join(config.paths.output.standaloneAppServer, "chunks"))
14
+ const chunks = readdirSync(join(dotNextServerDir, "chunks"))
15
15
  .filter((chunk) => /^\d+\.js$/.test(chunk))
16
16
  .map((chunk) => {
17
17
  console.log(` - chunk ${chunk}`);
@@ -1,4 +1,4 @@
1
- import { Config } from "../../../config.js";
1
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
2
  /**
3
3
  * `evalManifest` relies on readFileSync so we need to patch the function so that it instead returns the content of the manifest files
4
4
  * which are known at build time
@@ -6,4 +6,4 @@ import { Config } from "../../../config.js";
6
6
  * Note: we could/should probably just patch readFileSync here or something, but here the issue is that after the readFileSync call
7
7
  * there is a vm `runInNewContext` call which we also don't support (source: https://github.com/vercel/next.js/blob/b1e32c5d1f/packages/next/src/server/load-manifest.ts#L88)
8
8
  */
9
- export declare function inlineEvalManifest(code: string, config: Config): string;
9
+ export declare function inlineEvalManifest(code: string, buildOpts: BuildOptions): string;