|
1 | | -import { Command } from 'commander'; |
| 1 | +import { build as openNextBuild } from '@opennextjs/cloudflare/cli/build/build'; |
| 2 | +import { compileConfig, getNormalizedOptions } from '@opennextjs/cloudflare/cli/commands/utils'; |
| 3 | +import { Command, Option } from 'commander'; |
2 | 4 | import consola from 'consola'; |
3 | 5 | import { execa } from 'execa'; |
4 | | -import { cp } from 'node:fs/promises'; |
5 | | -import { join, relative, sep } from 'node:path'; |
6 | | -import { addDevDependency, installDependencies, runScript } from 'nypm'; |
| 6 | +import { copyFile, cp, rm, writeFile } from 'node:fs/promises'; |
| 7 | +import { join } from 'node:path'; |
7 | 8 |
|
8 | 9 | import { getModuleCliPath } from '../lib/get-module-cli-path'; |
9 | | -import { mkTempDir } from '../lib/mk-temp-dir'; |
| 10 | +import { getProjectConfig } from '../lib/project-config'; |
| 11 | +import { getWranglerConfig } from '../lib/wrangler-config'; |
10 | 12 |
|
11 | | -const OPENNEXTJS_CLOUDFLARE_VERSION = '1.5.2'; |
12 | 13 | const WRANGLER_VERSION = '4.24.3'; |
13 | 14 |
|
14 | | -const SKIP_DIRS = new Set([ |
15 | | - 'node_modules', |
16 | | - '.bigcommerce', |
17 | | - '.git', |
18 | | - '.turbo', |
19 | | - '.next', |
20 | | - '.vscode', |
21 | | - '.github', |
22 | | - '.changeset', |
23 | | - 'dist', |
24 | | -]); |
25 | | - |
26 | | -export function createFilter(root: string, skipDirs: Set<string>) { |
27 | | - return (src: string) => { |
28 | | - const rel = relative(root, src); |
29 | | - const parts = rel.split(sep); |
30 | | - |
31 | | - return !parts.some((part) => skipDirs.has(part)); |
32 | | - }; |
33 | | -} |
34 | | - |
35 | 15 | export const build = new Command('build') |
36 | | - .option('--keep-temp-dir', 'Keep the temporary directory after the build') |
| 16 | + .addOption( |
| 17 | + new Option( |
| 18 | + '--store-hash <hash>', |
| 19 | + 'BigCommerce store hash. Can be found in the URL of your store Control Panel.', |
| 20 | + ).env('BIGCOMMERCE_STORE_HASH'), |
| 21 | + ) |
| 22 | + .addOption( |
| 23 | + new Option('--framework <framework>', 'The framework to use for the build').choices([ |
| 24 | + 'catalyst', |
| 25 | + 'nextjs', |
| 26 | + ]), |
| 27 | + ) |
| 28 | + .option( |
| 29 | + '--project-uuid <uuid>', |
| 30 | + 'BigCommerce headless project UUID. Can be found via the BigCommerce API (GET /v3/headless/projects). Use this to link directly without fetching projects.', |
| 31 | + ) |
37 | 32 | .action(async (options) => { |
38 | | - const [tmpDir, rmTempDir] = await mkTempDir('catalyst-build-'); |
39 | | - |
40 | 33 | try { |
41 | | - consola.start('Copying project to temp directory...'); |
42 | | - |
43 | | - const packageManager = 'pnpm'; |
44 | | - |
45 | | - const cwd = process.cwd(); |
46 | | - const tmpCoreDir = join(tmpDir, 'core'); |
47 | | - const wranglerOutDir = join(tmpCoreDir, '.dist'); |
48 | | - const openNextOutDir = join(tmpCoreDir, '.open-next'); |
49 | | - const bigcommerceDistDir = join(cwd, '.bigcommerce', 'dist'); |
50 | | - |
51 | | - await cp(cwd, tmpDir, { |
52 | | - recursive: true, |
53 | | - force: true, |
54 | | - preserveTimestamps: true, |
55 | | - filter: createFilter(cwd, SKIP_DIRS), |
56 | | - }); |
57 | | - |
58 | | - consola.success(`Project copied to temp directory: ${tmpDir}`); |
59 | | - |
60 | | - consola.start('Installing dependencies...'); |
| 34 | + const projectConfig = getProjectConfig(); |
| 35 | + const framework = options.framework ?? projectConfig.get('framework'); |
| 36 | + const projectUuid = options.projectUuid ?? projectConfig.get('projectUuid'); |
61 | 37 |
|
62 | | - await installDependencies({ |
63 | | - cwd: tmpDir, |
64 | | - packageManager, |
65 | | - }); |
66 | | - |
67 | | - await addDevDependency(`@opennextjs/cloudflare@${OPENNEXTJS_CLOUDFLARE_VERSION}`, { |
68 | | - cwd: tmpCoreDir, |
69 | | - packageManager, |
70 | | - }); |
71 | | - |
72 | | - consola.success('Dependencies installed'); |
73 | | - |
74 | | - consola.start('Building dependencies...'); |
75 | | - |
76 | | - await runScript('build', { |
77 | | - cwd: join(tmpDir, 'packages', 'client'), |
78 | | - packageManager, |
79 | | - }); |
80 | | - |
81 | | - consola.success('Dependencies built'); |
82 | | - |
83 | | - consola.start('Copying templates...'); |
84 | | - |
85 | | - await cp(join(getModuleCliPath(), 'templates'), tmpCoreDir, { |
86 | | - recursive: true, |
87 | | - force: true, |
88 | | - }); |
89 | | - |
90 | | - consola.success('Templates copied'); |
91 | | - |
92 | | - consola.start('Building project...'); |
| 38 | + if (!projectUuid) { |
| 39 | + throw new Error('Project UUID is required. Please run `link` or provide `--project-uuid`'); |
| 40 | + } |
93 | 41 |
|
94 | | - await execa('pnpm', ['exec', 'opennextjs-cloudflare', 'build'], { |
95 | | - stdout: ['pipe', 'inherit'], |
96 | | - cwd: tmpCoreDir, |
97 | | - }); |
| 42 | + consola.info(`Building with framework \`${framework}\`...`); |
98 | 43 |
|
99 | | - await execa( |
100 | | - 'pnpm', |
101 | | - [ |
102 | | - 'dlx', |
103 | | - `wrangler@${WRANGLER_VERSION}`, |
104 | | - 'deploy', |
105 | | - '--keep-vars', |
106 | | - '--outdir', |
107 | | - wranglerOutDir, |
108 | | - '--dry-run', |
109 | | - ], |
110 | | - { |
111 | | - stdout: ['pipe', 'inherit'], |
112 | | - cwd: tmpCoreDir, |
113 | | - }, |
114 | | - ); |
115 | | - |
116 | | - consola.success('Project built'); |
117 | | - |
118 | | - consola.start('Copying build to project...'); |
119 | | - |
120 | | - await cp(wranglerOutDir, bigcommerceDistDir, { |
121 | | - recursive: true, |
122 | | - force: true, |
123 | | - }); |
| 44 | + if (framework === 'nextjs') { |
| 45 | + const nextBin = join(process.cwd(), 'node_modules', '.bin', 'next'); |
124 | 46 |
|
125 | | - await cp(join(openNextOutDir, 'assets'), join(bigcommerceDistDir, 'assets'), { |
126 | | - recursive: true, |
127 | | - force: true, |
128 | | - }); |
| 47 | + consola.start('Running `next build`...'); |
| 48 | + await execa(nextBin, ['build']); |
| 49 | + consola.success('Next.js build completed'); |
| 50 | + } |
129 | 51 |
|
130 | | - consola.success('Build copied to project'); |
| 52 | + if (framework === 'catalyst') { |
| 53 | + consola.start('Running `catalyst build`...'); |
| 54 | + |
| 55 | + // @todo get KV namespace from Ignition? |
| 56 | + const wranglerConfig = getWranglerConfig(projectUuid, 'placeholder-kv-namespace-id'); |
| 57 | + |
| 58 | + await copyFile( |
| 59 | + join(getModuleCliPath(), 'templates', 'open-next.config.ts'), |
| 60 | + join(process.cwd(), 'open-next.config.ts'), |
| 61 | + ); |
| 62 | + await writeFile( |
| 63 | + join(process.cwd(), '.bigcommerce', 'wrangler.jsonc'), |
| 64 | + JSON.stringify(wranglerConfig, null, 2), |
| 65 | + ); |
| 66 | + |
| 67 | + const { config: openNextConfig, buildDir } = await compileConfig(); |
| 68 | + const openNextBuildOptions = getNormalizedOptions(openNextConfig, buildDir); |
| 69 | + |
| 70 | + await openNextBuild( |
| 71 | + openNextBuildOptions, |
| 72 | + openNextConfig, |
| 73 | + { |
| 74 | + sourceDir: process.cwd(), |
| 75 | + skipNextBuild: false, |
| 76 | + skipWranglerConfigCheck: true, |
| 77 | + minify: false, |
| 78 | + }, |
| 79 | + // @ts-expect-error - @todo fix types, @opennextjs/cloudflare does not export the type |
| 80 | + wranglerConfig, |
| 81 | + ); |
| 82 | + |
| 83 | + consola.success('Catalyst build completed'); |
| 84 | + consola.start('Compiling build for deployment...'); |
| 85 | + |
| 86 | + await execa( |
| 87 | + 'pnpm', |
| 88 | + [ |
| 89 | + 'dlx', |
| 90 | + `wrangler@${WRANGLER_VERSION}`, |
| 91 | + 'deploy', |
| 92 | + '--config', |
| 93 | + join(process.cwd(), '.bigcommerce', 'wrangler.jsonc'), |
| 94 | + '--keep-vars', |
| 95 | + '--outdir', |
| 96 | + join(process.cwd(), '.bigcommerce', 'dist'), |
| 97 | + '--dry-run', |
| 98 | + ], |
| 99 | + { |
| 100 | + stdout: ['pipe', 'inherit'], |
| 101 | + cwd: process.cwd(), |
| 102 | + }, |
| 103 | + ); |
| 104 | + |
| 105 | + await cp( |
| 106 | + join(process.cwd(), '.open-next', 'assets'), |
| 107 | + join(process.cwd(), '.bigcommerce', 'dist', 'assets'), |
| 108 | + { |
| 109 | + recursive: true, |
| 110 | + force: true, |
| 111 | + }, |
| 112 | + ); |
| 113 | + |
| 114 | + consola.success('Build compiled and ready for deployment'); |
| 115 | + } |
131 | 116 | } catch (error) { |
132 | 117 | consola.error(error); |
133 | 118 | process.exitCode = 1; |
134 | 119 | } finally { |
135 | | - if (!options.keepTempDir) { |
136 | | - await rmTempDir(); |
137 | | - } |
| 120 | + await rm(join(process.cwd(), 'open-next.config.ts')).catch(() => { |
| 121 | + // noop to silence ENOENT error |
| 122 | + }); |
138 | 123 |
|
139 | 124 | process.exit(); |
140 | 125 | } |
|
0 commit comments