Skip to content

Commit b775f69

Browse files
authored
feat: build catalyst with opennextjs-cloudflare (#2493)
1 parent 7c80b0b commit b775f69

4 files changed

Lines changed: 150 additions & 12 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ bigcommerce-graphql.d.ts
2020
coverage/
2121
.history
2222
.unlighthouse
23+
.bigcommerce

packages/cli/src/commands/build.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Command } from 'commander';
22
import consola from 'consola';
3+
import { execa } from 'execa';
34
import { cp } from 'node:fs/promises';
45
import { join, relative, sep } from 'node:path';
56
import { addDevDependency, installDependencies, runScript } from 'nypm';
@@ -8,6 +9,7 @@ import { getModuleCliPath } from '../lib/get-module-cli-path';
89
import { mkTempDir } from '../lib/mk-temp-dir';
910

1011
const OPENNEXTJS_CLOUDFLARE_VERSION = '1.5.2';
12+
const WRANGLER_VERSION = '4.24.3';
1113

1214
const SKIP_DIRS = new Set([
1315
'node_modules',
@@ -38,9 +40,14 @@ export const build = new Command('build')
3840
try {
3941
consola.start('Copying project to temp directory...');
4042

41-
const cwd = process.cwd();
4243
const packageManager = 'pnpm';
4344

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+
4451
await cp(cwd, tmpDir, {
4552
recursive: true,
4653
force: true,
@@ -58,7 +65,7 @@ export const build = new Command('build')
5865
});
5966

6067
await addDevDependency(`@opennextjs/cloudflare@${OPENNEXTJS_CLOUDFLARE_VERSION}`, {
61-
cwd: join(tmpDir, 'core'),
68+
cwd: tmpCoreDir,
6269
packageManager,
6370
});
6471

@@ -75,12 +82,52 @@ export const build = new Command('build')
7582

7683
consola.start('Copying templates...');
7784

78-
await cp(join(getModuleCliPath(), 'templates'), join(tmpDir, 'core'), {
85+
await cp(join(getModuleCliPath(), 'templates'), tmpCoreDir, {
7986
recursive: true,
8087
force: true,
8188
});
8289

8390
consola.success('Templates copied');
91+
92+
consola.start('Building project...');
93+
94+
await execa('pnpm', ['exec', 'opennextjs-cloudflare', 'build'], {
95+
stdout: ['pipe', 'inherit'],
96+
cwd: tmpCoreDir,
97+
});
98+
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+
});
124+
125+
await cp(join(openNextOutDir, 'assets'), join(bigcommerceDistDir, 'assets'), {
126+
recursive: true,
127+
force: true,
128+
});
129+
130+
consola.success('Build copied to project');
84131
} catch (error) {
85132
consola.error(error);
86133
process.exitCode = 1;

packages/cli/tests/commands/build.spec.ts

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,91 @@
1-
import { describe, expect, test } from 'vitest';
1+
import { Command } from 'commander';
2+
import consola from 'consola';
3+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
24

3-
import { createFilter } from '../../src/commands/build';
5+
import { build, createFilter } from '../../src/commands/build';
6+
import { program } from '../../src/program';
7+
8+
const cleanup = vi.fn();
9+
10+
beforeAll(() => {
11+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12+
vi.spyOn(process, 'exit').mockImplementation(() => null as never);
13+
14+
consola.wrapAll();
15+
16+
vi.mock('../../src/lib/mk-temp-dir', () => ({
17+
mkTempDir: vi.fn(() => ['/tmp/test', cleanup]),
18+
}));
19+
20+
vi.mock('node:fs/promises', () => ({
21+
cp: vi.fn(),
22+
}));
23+
24+
vi.mock('nypm', () => ({
25+
installDependencies: vi.fn(),
26+
addDevDependency: vi.fn(),
27+
runScript: vi.fn(),
28+
}));
29+
30+
vi.mock('execa', () => ({
31+
execa: vi.fn(),
32+
}));
33+
});
34+
35+
beforeEach(() => {
36+
consola.mockTypes(() => vi.fn());
37+
});
38+
39+
afterEach(() => {
40+
vi.clearAllMocks();
41+
});
42+
43+
afterAll(() => {
44+
vi.restoreAllMocks();
45+
});
46+
47+
test('properly configured Command instance', () => {
48+
expect(build).toBeInstanceOf(Command);
49+
expect(build.name()).toBe('build');
50+
expect(build.options).toEqual(
51+
expect.arrayContaining([expect.objectContaining({ flags: '--keep-temp-dir' })]),
52+
);
53+
});
54+
55+
test('does not remove temp dir if --keep-temp-dir is passed', async () => {
56+
await program.parseAsync(['node', 'catalyst', 'build', '--keep-temp-dir']);
57+
expect(cleanup).not.toHaveBeenCalled();
58+
});
59+
60+
test('removes temp dir if --keep-temp-dir is not passed', async () => {
61+
await program.parseAsync(['node', 'catalyst', 'build']);
62+
expect(cleanup).toHaveBeenCalled();
63+
});
64+
65+
test('successfully builds project', async () => {
66+
await program.parseAsync(['node', 'catalyst', 'build']);
67+
68+
expect(consola.success).toHaveBeenCalledWith('Project built');
69+
expect(consola.success).toHaveBeenCalledWith('Build copied to project');
70+
});
71+
72+
test('handles error if cp throws during build', async () => {
73+
// Dynamically mock cp for this test only
74+
// eslint-disable-next-line import/dynamic-import-chunkname
75+
const cpModule = await import('node:fs/promises');
76+
const originalCp = cpModule.cp;
77+
const cpMock = vi.fn(() => {
78+
throw new Error('cp failed');
79+
});
80+
81+
cpModule.cp = cpMock;
82+
83+
await program.parseAsync(['node', 'catalyst', 'build']);
84+
85+
expect(consola.error).toHaveBeenCalledWith(expect.any(Error));
86+
87+
cpModule.cp = originalCp;
88+
});
489

590
describe('createFilter', () => {
691
const ROOT = '/my/project';

pnpm-lock.yaml

Lines changed: 12 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)