Skip to content

Commit a8f8574

Browse files
isaacroldanclaude
andcommitted
Replace build(options, lifecycle) with separate build() and bundle() methods
The lifecycle parameter was a flag argument signaling "this method does two things." Splitting into two named methods makes each one do one thing, and the composition (build then bundle) becomes plain code at the one call site that needs it: buildForBundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 881473d commit a8f8574

3 files changed

Lines changed: 27 additions & 24 deletions

File tree

packages/app/src/cli/models/extensions/extension-instance.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,15 @@ describe('build', async () => {
164164
const outputFilePath = joinPath(tmpDir, `dist/${extensionInstance.outputFileName}`)
165165

166166
// When
167-
await extensionInstance.build(options, 'build')
167+
await extensionInstance.build(options)
168168

169169
// Then
170170
const outputFileContent = await readFile(outputFilePath)
171171
expect(outputFileContent).toEqual('(()=>{})();')
172172
})
173173
})
174174

175-
test("'bundle' lifecycle still runs the build steps even when no 'bundle' group is declared", async () => {
175+
test('bundle() is a no-op when the spec declares no bundle lifecycle group', async () => {
176176
await inTemporaryDirectory(async (tmpDir) => {
177177
// Given — tax_calculation only declares a 'build' lifecycle group.
178178
const extensionInstance = await testTaxCalculationExtension(tmpDir)
@@ -185,12 +185,11 @@ describe('build', async () => {
185185

186186
const outputFilePath = joinPath(tmpDir, `dist/${extensionInstance.outputFileName}`)
187187

188-
// When — request the 'bundle' lifecycle. Composition still runs build steps even without a bundle group.
189-
await extensionInstance.build(options, 'bundle')
188+
// When — bundle() runs only the bundle steps; with none declared this is a no-op.
189+
await extensionInstance.bundle(options)
190190

191-
// Then
192-
const outputFileContent = await readFile(outputFilePath)
193-
expect(outputFileContent).toEqual('(()=>{})();')
191+
// Then — no output file is produced (build() was not called).
192+
await expect(readFile(outputFilePath)).rejects.toThrow()
194193
})
195194
})
196195

packages/app/src/cli/models/extensions/extension-instance.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,36 +317,40 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
317317
return Boolean(this.entrySourceFilePath.endsWith('.js') || this.entrySourceFilePath.endsWith('.ts'))
318318
}
319319

320-
async build(options: ExtensionBuildOptions, lifecycle: LifecyclePhase): Promise<void> {
321-
const {clientSteps = []} = this.specification
320+
async build(options: ExtensionBuildOptions): Promise<void> {
321+
await this.runLifecyclePhase('build', options)
322+
}
323+
324+
async bundle(options: ExtensionBuildOptions): Promise<void> {
325+
await this.runLifecyclePhase('bundle', options)
326+
}
327+
328+
async buildForBundle(options: ExtensionBuildOptions, bundleDirectory: string, outputId?: string) {
329+
this.outputPath = this.getOutputPathForDirectory(bundleDirectory, outputId)
330+
await this.build(options)
331+
await this.bundle(options)
332+
333+
const bundleInputPath = joinPath(bundleDirectory, this.getOutputFolderId(outputId))
334+
await this.keepBuiltSourcemapsLocally(bundleInputPath)
335+
}
336+
337+
private async runLifecyclePhase(phase: LifecyclePhase, options: ExtensionBuildOptions): Promise<void> {
338+
const steps = this.specification.clientSteps?.find((group) => group.lifecycle === phase)?.steps ?? []
339+
if (steps.length === 0) return
322340

323341
const context: BuildContext = {
324342
extension: this,
325343
options,
326344
stepResults: new Map(),
327345
}
328346

329-
// Phases compose additively: 'bundle' runs build steps first, then bundle steps.
330-
// Steps within each group preserve declaration order.
331-
const buildSteps = clientSteps.find((group) => group.lifecycle === 'build')?.steps ?? []
332-
const bundleSteps = clientSteps.find((group) => group.lifecycle === 'bundle')?.steps ?? []
333-
const steps = lifecycle === 'build' ? buildSteps : [...buildSteps, ...bundleSteps]
334-
335347
for (const step of steps) {
336348
// eslint-disable-next-line no-await-in-loop
337349
const result = await executeStep(step, context)
338350
context.stepResults.set(step.id, result)
339351
}
340352
}
341353

342-
async buildForBundle(options: ExtensionBuildOptions, bundleDirectory: string, outputId?: string) {
343-
this.outputPath = this.getOutputPathForDirectory(bundleDirectory, outputId)
344-
await this.build(options, 'bundle')
345-
346-
const bundleInputPath = joinPath(bundleDirectory, this.getOutputFolderId(outputId))
347-
await this.keepBuiltSourcemapsLocally(bundleInputPath)
348-
}
349-
350354
async copyIntoBundle(options: ExtensionBuildOptions, bundleDirectory: string, extensionUuid?: string) {
351355
const defaultOutputPath = this.outputPath
352356

packages/app/src/cli/services/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async function build(options: BuildOptions) {
4242
return {
4343
prefix: ext.localIdentifier,
4444
action: async (stdout: Writable, stderr: Writable, signal: AbortSignal) => {
45-
await ext.build({stdout, stderr, signal, app: options.app, environment: 'production'}, 'build')
45+
await ext.build({stdout, stderr, signal, app: options.app, environment: 'production'})
4646
},
4747
}
4848
}),

0 commit comments

Comments
 (0)