|
1 | | -import { mkdirSync, type Stats, statSync } from "node:fs"; |
2 | | -import { resolve } from "node:path"; |
3 | | -import type { ParseArgsConfig } from "node:util"; |
4 | | -import { parseArgs } from "node:util"; |
| 1 | +import yargs from "yargs"; |
5 | 2 |
|
6 | | -import type { WranglerTarget } from "./utils/run-wrangler.js"; |
7 | | -import { getWranglerEnvironmentFlag, isWranglerTarget } from "./utils/run-wrangler.js"; |
| 3 | +import { buildCommand } from "./commands/build.js"; |
| 4 | +import { deployCommand } from "./commands/deploy.js"; |
| 5 | +import { populateCacheCommand } from "./commands/populate-cache.js"; |
| 6 | +import { previewCommand } from "./commands/preview.js"; |
| 7 | +import { uploadCommand } from "./commands/upload.js"; |
8 | 8 |
|
9 | | -export type Arguments = ( |
10 | | - | { |
11 | | - command: "build"; |
12 | | - skipNextBuild: boolean; |
13 | | - skipWranglerConfigCheck: boolean; |
14 | | - minify: boolean; |
15 | | - } |
16 | | - | { |
17 | | - command: "preview" | "deploy" | "upload"; |
18 | | - passthroughArgs: string[]; |
19 | | - cacheChunkSize?: number; |
20 | | - } |
21 | | - | { |
22 | | - command: "populateCache"; |
23 | | - target: WranglerTarget; |
24 | | - environment?: string; |
25 | | - cacheChunkSize?: number; |
26 | | - } |
27 | | -) & { outputDir?: string }; |
28 | | - |
29 | | -// Config for parsing CLI arguments |
30 | | -const config = { |
31 | | - allowPositionals: true, |
32 | | - strict: false, |
33 | | - options: { |
34 | | - skipBuild: { type: "boolean", short: "s", default: false }, |
35 | | - output: { type: "string", short: "o" }, |
36 | | - noMinify: { type: "boolean", default: false }, |
37 | | - skipWranglerConfigCheck: { type: "boolean", default: false }, |
38 | | - cacheChunkSize: { type: "string" }, |
39 | | - }, |
40 | | -} as const satisfies ParseArgsConfig; |
41 | | - |
42 | | -export function getArgs(): Arguments { |
43 | | - const { positionals, values } = parseArgs(config); |
44 | | - |
45 | | - const outputDir = typeof values.output === "string" ? resolve(values.output) : undefined; |
46 | | - if (outputDir) assertDirArg(outputDir, "output", true); |
47 | | - |
48 | | - switch (positionals[0]) { |
49 | | - case "build": |
50 | | - return { |
51 | | - command: "build", |
52 | | - outputDir, |
53 | | - skipNextBuild: |
54 | | - !!values.skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), |
55 | | - skipWranglerConfigCheck: |
56 | | - !!values.skipWranglerConfigCheck || |
57 | | - ["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)), |
58 | | - minify: !values.noMinify, |
59 | | - }; |
60 | | - case "preview": |
61 | | - case "deploy": |
62 | | - case "upload": |
63 | | - return { |
64 | | - command: positionals[0], |
65 | | - outputDir, |
66 | | - passthroughArgs: getPassthroughArgs(process.argv, config), |
67 | | - ...(values.cacheChunkSize && { cacheChunkSize: Number(values.cacheChunkSize) }), |
68 | | - }; |
69 | | - case "populateCache": |
70 | | - if (!isWranglerTarget(positionals[1])) { |
71 | | - throw new Error(`Error: invalid target for populating the cache, expected 'local' | 'remote'`); |
72 | | - } |
73 | | - return { |
74 | | - command: "populateCache", |
75 | | - outputDir, |
76 | | - target: positionals[1], |
77 | | - environment: getWranglerEnvironmentFlag(process.argv), |
78 | | - ...(values.cacheChunkSize && { cacheChunkSize: Number(values.cacheChunkSize) }), |
79 | | - }; |
80 | | - default: |
81 | | - throw new Error( |
82 | | - "Error: invalid command, expected 'build' | 'preview' | 'deploy' | 'upload' | 'populateCache'" |
83 | | - ); |
84 | | - } |
| 9 | +export function runCommand() { |
| 10 | + return yargs(process.argv.slice(2)) |
| 11 | + .scriptName("opennextjs-cloudflare") |
| 12 | + .parserConfiguration({ "unknown-options-as-args": true }) |
| 13 | + .command( |
| 14 | + "build", |
| 15 | + "Build an OpenNext Cloudflare worker", |
| 16 | + (c) => |
| 17 | + withWranglerOptions(c) |
| 18 | + .option("skipNextBuild", { |
| 19 | + type: "boolean", |
| 20 | + alias: ["skipBuild", "s"], |
| 21 | + default: ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), |
| 22 | + desc: "Skip building the Next.js app", |
| 23 | + }) |
| 24 | + .option("noMinify", { |
| 25 | + type: "boolean", |
| 26 | + alias: "s", |
| 27 | + default: false, |
| 28 | + desc: "Disable worker minification", |
| 29 | + }) |
| 30 | + .option("skipWranglerConfigCheck", { |
| 31 | + type: "boolean", |
| 32 | + alias: "s", |
| 33 | + default: ["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)), |
| 34 | + desc: "Skip checking for a Wrangler config", |
| 35 | + }), |
| 36 | + (args) => buildCommand(withWranglerPassthroughArgs(args)) |
| 37 | + ) |
| 38 | + .command( |
| 39 | + "preview", |
| 40 | + "Preview a built OpenNext app with a Wrangler dev server", |
| 41 | + (c) => withPopulateCacheOptions(c), |
| 42 | + (args) => previewCommand(withWranglerPassthroughArgs(args)) |
| 43 | + ) |
| 44 | + .command( |
| 45 | + "deploy", |
| 46 | + "Deploy a built OpenNext app to Cloudflare Workers", |
| 47 | + (c) => withPopulateCacheOptions(c), |
| 48 | + (args) => deployCommand(withWranglerPassthroughArgs(args)) |
| 49 | + ) |
| 50 | + .command( |
| 51 | + "upload", |
| 52 | + "Upload a built OpenNext app to Cloudflare Workers", |
| 53 | + (c) => withPopulateCacheOptions(c), |
| 54 | + (args) => uploadCommand(withWranglerPassthroughArgs(args)) |
| 55 | + ) |
| 56 | + .command("populateCache", "Populate the cache for a built Next.js app", (c) => |
| 57 | + c |
| 58 | + .command( |
| 59 | + "local", |
| 60 | + "Local dev server cache", |
| 61 | + (c) => withPopulateCacheOptions(c), |
| 62 | + (args) => populateCacheCommand("local", withWranglerPassthroughArgs(args)) |
| 63 | + ) |
| 64 | + .command( |
| 65 | + "remote", |
| 66 | + "Remote Cloudflare Worker cache", |
| 67 | + (c) => withPopulateCacheOptions(c), |
| 68 | + (args) => populateCacheCommand("remote", withWranglerPassthroughArgs(args)) |
| 69 | + ) |
| 70 | + .demandCommand(1, 1) |
| 71 | + ) |
| 72 | + .demandCommand(1, 1) |
| 73 | + .parse(); |
85 | 74 | } |
86 | 75 |
|
87 | | -export function getPassthroughArgs<T extends ParseArgsConfig>(args: string[], { options = {} }: T) { |
88 | | - const passthroughArgs: string[] = []; |
89 | | - |
90 | | - for (let i = 0; i < args.length; i++) { |
91 | | - if (args[i] === "--") { |
92 | | - passthroughArgs.push(...args.slice(i + 1)); |
93 | | - return passthroughArgs; |
94 | | - } |
95 | | - |
96 | | - // look for `--arg(=value)`, `-arg(=value)` |
97 | | - const [, name] = /^--?(\w[\w-]*)(=.+)?$/.exec(args[i]!) ?? []; |
98 | | - if (name && !(name in options)) { |
99 | | - passthroughArgs.push(args[i]!); |
100 | | - |
101 | | - // Array args can have multiple values |
102 | | - // ref https://github.com/yargs/yargs-parser/blob/main/README.md#greedy-arrays |
103 | | - while (i < args.length - 1 && !args[i + 1]?.startsWith("-")) { |
104 | | - passthroughArgs.push(args[++i]!); |
105 | | - } |
106 | | - } |
107 | | - } |
| 76 | +function withWranglerOptions<T extends yargs.Argv>(args: T) { |
| 77 | + return args |
| 78 | + .options("config", { |
| 79 | + type: "string", |
| 80 | + alias: "c", |
| 81 | + desc: "Wrangler config file path", |
| 82 | + }) |
| 83 | + .options("env", { |
| 84 | + type: "string", |
| 85 | + alias: "e", |
| 86 | + desc: "Wrangler environment", |
| 87 | + }); |
| 88 | +} |
108 | 89 |
|
109 | | - return passthroughArgs; |
| 90 | +function withPopulateCacheOptions<T extends yargs.Argv>(args: T) { |
| 91 | + return withWranglerOptions(args).options("cacheChunkSize", { |
| 92 | + type: "number", |
| 93 | + default: 25, |
| 94 | + desc: "Number of entries per chunk when populating the cache", |
| 95 | + }); |
110 | 96 | } |
111 | 97 |
|
112 | | -function assertDirArg(path: string, argName?: string, make?: boolean) { |
113 | | - let dirStats: Stats; |
114 | | - try { |
115 | | - dirStats = statSync(path); |
116 | | - } catch { |
117 | | - if (!make) { |
118 | | - throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a valid path`); |
119 | | - } |
120 | | - mkdirSync(path); |
121 | | - return; |
122 | | - } |
| 98 | +function getWranglerArgs(args: { |
| 99 | + _: (string | number)[]; |
| 100 | + config: string | undefined; |
| 101 | + env: string | undefined; |
| 102 | +}): string[] { |
| 103 | + return [ |
| 104 | + ...(args.config ? ["--config", args.config] : []), |
| 105 | + ...(args.env ? ["--env", args.env] : []), |
| 106 | + // Note: the first args in `_` will be the commands. |
| 107 | + ...args._.slice(args._[0] === "populateCache" ? 2 : 1).map((a) => `${a}`), |
| 108 | + ]; |
| 109 | +} |
123 | 110 |
|
124 | | - if (!dirStats.isDirectory()) { |
125 | | - throw new Error(`Error: the provided${argName ? ` "${argName}"` : ""} input is not a directory`); |
126 | | - } |
| 111 | +function withWranglerPassthroughArgs< |
| 112 | + T extends yargs.ArgumentsCamelCase<{ |
| 113 | + config: string | undefined; |
| 114 | + env: string | undefined; |
| 115 | + }>, |
| 116 | +>(args: T) { |
| 117 | + return { ...args, passthrough: getWranglerArgs(args) }; |
127 | 118 | } |
0 commit comments