Skip to content

Commit 3837d71

Browse files
Abstract build steps to externalize the build configuration
1 parent f9f6e10 commit 3837d71

12 files changed

+1531
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import {ExtensionBuildOptions} from './extension.js'
2+
import {executeBuildSteps, BuildStepsConfig} from './build-steps.js'
3+
import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
4+
import {describe, expect, test} from 'vitest'
5+
import {inTemporaryDirectory, writeFile, readFile, mkdir, fileExists} from '@shopify/cli-kit/node/fs'
6+
import {joinPath} from '@shopify/cli-kit/node/path'
7+
import {Writable} from 'stream'
8+
9+
describe('build_steps integration', () => {
10+
test('executes copy_files step and copies files to output', async () => {
11+
await inTemporaryDirectory(async (tmpDir) => {
12+
// Setup: Create extension directory with assets
13+
const extensionDir = joinPath(tmpDir, 'extension')
14+
const assetsDir = joinPath(extensionDir, 'assets')
15+
const outputDir = joinPath(tmpDir, 'output')
16+
17+
await mkdir(extensionDir)
18+
await mkdir(assetsDir)
19+
await mkdir(outputDir)
20+
21+
// Create test files
22+
await writeFile(joinPath(assetsDir, 'logo.png'), 'fake-png-data')
23+
await writeFile(joinPath(assetsDir, 'style.css'), 'body { color: red; }')
24+
25+
// Create mock extension
26+
const mockExtension = {
27+
directory: extensionDir,
28+
outputPath: joinPath(outputDir, 'extension.js'),
29+
} as ExtensionInstance
30+
31+
// Create build steps config
32+
const stepsConfig: BuildStepsConfig = {
33+
steps: [
34+
{
35+
id: 'copy-assets',
36+
displayName: 'Copy Assets',
37+
type: 'copy_files',
38+
config: {
39+
strategy: 'pattern',
40+
definition: {
41+
source: 'assets',
42+
patterns: ['**/*'],
43+
},
44+
},
45+
},
46+
],
47+
}
48+
49+
const buildOptions: ExtensionBuildOptions = {
50+
stdout: new Writable({
51+
write(chunk, encoding, callback) {
52+
callback()
53+
},
54+
}),
55+
stderr: new Writable({
56+
write(chunk, encoding, callback) {
57+
callback()
58+
},
59+
}),
60+
app: {} as any,
61+
environment: 'production',
62+
}
63+
64+
// Execute: Call executeBuildSteps directly
65+
await executeBuildSteps(mockExtension, stepsConfig, buildOptions)
66+
67+
// Verify: Files were copied to output directory
68+
const logoExists = await fileExists(joinPath(outputDir, 'logo.png'))
69+
const styleExists = await fileExists(joinPath(outputDir, 'style.css'))
70+
71+
expect(logoExists).toBe(true)
72+
expect(styleExists).toBe(true)
73+
74+
const logoContent = await readFile(joinPath(outputDir, 'logo.png'))
75+
const styleContent = await readFile(joinPath(outputDir, 'style.css'))
76+
77+
expect(logoContent).toBe('fake-png-data')
78+
expect(styleContent).toBe('body { color: red; }')
79+
})
80+
})
81+
82+
test('executes multiple steps in sequence', async () => {
83+
await inTemporaryDirectory(async (tmpDir) => {
84+
// Setup: Create extension with two asset directories
85+
const extensionDir = joinPath(tmpDir, 'extension')
86+
const imagesDir = joinPath(extensionDir, 'images')
87+
const stylesDir = joinPath(extensionDir, 'styles')
88+
const outputDir = joinPath(tmpDir, 'output')
89+
90+
await mkdir(extensionDir)
91+
await mkdir(imagesDir)
92+
await mkdir(stylesDir)
93+
await mkdir(outputDir)
94+
95+
await writeFile(joinPath(imagesDir, 'logo.png'), 'logo-data')
96+
await writeFile(joinPath(stylesDir, 'main.css'), 'css-data')
97+
98+
const mockExtension = {
99+
directory: extensionDir,
100+
outputPath: joinPath(outputDir, 'extension.js'),
101+
} as ExtensionInstance
102+
103+
const stepsConfig: BuildStepsConfig = {
104+
steps: [
105+
{
106+
id: 'copy-images',
107+
displayName: 'Copy Images',
108+
type: 'copy_files',
109+
config: {
110+
strategy: 'pattern',
111+
definition: {
112+
source: 'images',
113+
patterns: ['**/*'],
114+
destination: 'assets/images',
115+
},
116+
},
117+
},
118+
{
119+
id: 'copy-styles',
120+
displayName: 'Copy Styles',
121+
type: 'copy_files',
122+
config: {
123+
strategy: 'pattern',
124+
definition: {
125+
source: 'styles',
126+
patterns: ['**/*'],
127+
destination: 'assets/styles',
128+
},
129+
},
130+
},
131+
],
132+
}
133+
134+
const buildOptions: ExtensionBuildOptions = {
135+
stdout: new Writable({
136+
write(chunk, encoding, callback) {
137+
callback()
138+
},
139+
}),
140+
stderr: new Writable({
141+
write(chunk, encoding, callback) {
142+
callback()
143+
},
144+
}),
145+
app: {} as any,
146+
environment: 'production',
147+
}
148+
149+
// Execute
150+
await executeBuildSteps(mockExtension, stepsConfig, buildOptions)
151+
152+
// Verify: Files from both steps were copied to correct destinations
153+
const logoExists = await fileExists(joinPath(outputDir, 'assets/images/logo.png'))
154+
const styleExists = await fileExists(joinPath(outputDir, 'assets/styles/main.css'))
155+
156+
expect(logoExists).toBe(true)
157+
expect(styleExists).toBe(true)
158+
})
159+
})
160+
161+
test('silently skips tomlKeys step when TOML key is absent from extension config', async () => {
162+
await inTemporaryDirectory(async (tmpDir) => {
163+
const extensionDir = joinPath(tmpDir, 'extension')
164+
const outputDir = joinPath(tmpDir, 'output')
165+
166+
await mkdir(extensionDir)
167+
await mkdir(outputDir)
168+
169+
// Extension has no configuration — static_root key is absent
170+
const mockExtension = {
171+
directory: extensionDir,
172+
outputPath: joinPath(outputDir, 'extension.js'),
173+
configuration: {},
174+
} as unknown as ExtensionInstance
175+
176+
const stepsConfig: BuildStepsConfig = {
177+
steps: [
178+
{
179+
id: 'copy-static',
180+
displayName: 'Copy Static Assets',
181+
type: 'copy_files',
182+
config: {
183+
strategy: 'files',
184+
definition: {files: [{tomlKey: 'static_root'}]},
185+
},
186+
},
187+
],
188+
}
189+
190+
const buildOptions: ExtensionBuildOptions = {
191+
stdout: new Writable({
192+
write(chunk, encoding, callback) {
193+
callback()
194+
},
195+
}),
196+
stderr: new Writable({
197+
write(chunk, encoding, callback) {
198+
callback()
199+
},
200+
}),
201+
app: {} as any,
202+
environment: 'production',
203+
}
204+
205+
// Should not throw — absent tomlKeys are silently skipped
206+
await expect(executeBuildSteps(mockExtension, stepsConfig, buildOptions)).resolves.not.toThrow()
207+
})
208+
})
209+
})

0 commit comments

Comments
 (0)