diff --git a/src/cli.ts b/src/cli.ts index 369530d..23ea7a6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,10 @@ import { createApp } from './app.ts'; +import { registerBuildCommand } from './commands/build.ts'; const program = createApp(); +registerBuildCommand(program); + program.parseAsync(process.argv).catch((err) => { console.error(err instanceof Error ? err.message : String(err)); process.exit(1); diff --git a/src/commands/build.ts b/src/commands/build.ts new file mode 100644 index 0000000..491c9fd --- /dev/null +++ b/src/commands/build.ts @@ -0,0 +1,54 @@ +import type { Command } from 'commander'; +import { getConfig } from '../config.ts'; +import { runCommand } from '../utils/process.ts'; +import * as output from '../utils/output.ts'; + +/** + * Registers the `build` command with the CLI program. + * + * @since TBD + * + * @param {Command} program - The Commander.js program instance. + * + * @returns {void} + */ +export function registerBuildCommand(program: Command): void { + program + .command('build') + .description('Run the build commands.') + .option('--dev', 'Run the dev build commands.') + .option('--root ', 'Set the root directory for running commands.') + .action(async (options: { dev?: boolean; root?: string }) => { + const config = getConfig(); + const buildSteps = config.getBuildCommands(options.dev); + const cwd = options.root ?? config.getWorkingDir(); + + output.log('Running build steps...'); + + for (const step of buildSteps) { + let cmd = step; + let bailOnFailure = true; + + if (cmd.startsWith('@')) { + bailOnFailure = false; + cmd = cmd.slice(1); + } + + output.section(`> ${cmd}`); + + const result = await runCommand(cmd, { + cwd, + }); + + if (result.exitCode !== 0) { + output.error(`[FAIL] Build step failed: ${cmd}`); + if (bailOnFailure) { + output.error('Exiting...'); + process.exit(result.exitCode); + } + } + } + + output.success('Build complete.'); + }); +} diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts new file mode 100644 index 0000000..1975c9d --- /dev/null +++ b/tests/commands/build.test.ts @@ -0,0 +1,84 @@ +import { + runPup, + writePuprc, + getPuprc, + createTempProject, + cleanupTempProjects, +} from '../helpers/setup.js'; + +describe('build command', () => { + let projectDir: string; + + beforeEach(() => { + projectDir = createTempProject(); + }); + + afterEach(() => { + cleanupTempProjects(); + }); + + it('should run build', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "fake project, yo"']; + writePuprc(puprc, projectDir); + + const result = await runPup('build', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('fake project, yo'); + }); + + it('should handle no build steps', async () => { + const puprc = getPuprc(); + puprc.build = []; + writePuprc(puprc, projectDir); + + const result = await runPup('build', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + }); + + it('should succeed with defaults when no .puprc exists (defaults to no build steps)', async () => { + const emptyDir = createTempProject(); + const result = await runPup('build', { cwd: emptyDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('Build complete'); + }); + + it('should pass env vars that are not defined in puprc.env', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "TOKEN=$PUP_TEST_UNIQUE_VAR"']; + writePuprc(puprc, projectDir); + + process.env.PUP_TEST_UNIQUE_VAR = 'test-token-12345'; + + try { + const result = await runPup('build', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('TOKEN=test-token-12345'); + } finally { + delete process.env.PUP_TEST_UNIQUE_VAR; + } + }); + + it('should run build in the specified --root directory', async () => { + const rootDir = createTempProject(); + + const puprc = getPuprc(); + puprc.build = ['pwd']; + writePuprc(puprc, projectDir); + + const result = await runPup(`build --root ${rootDir}`, { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain(rootDir); + }); + + it('should run build with dev flag', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "production build"']; + (puprc as Record).build_dev = ['echo "dev build"']; + writePuprc(puprc, projectDir); + + const result = await runPup('build --dev', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('dev build'); + }); +});