Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d61d860
ENG-219: Add build command
d4mation Feb 11, 2026
6aaadb3
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
3095bde
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
689608e
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
a70b3b9
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
ff16a55
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
ea08446
ENG-219: Use createTempProject in build tests for isolation
d4mation Feb 11, 2026
c710c4b
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 11, 2026
d3c633d
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 12, 2026
046379e
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 13, 2026
0ce6c9d
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 13, 2026
67bd7fd
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 13, 2026
cda8080
ENG-219: Improve build command test assertions
d4mation Feb 13, 2026
ca95646
ENG-219: Merge app-bootstrap and update imports
d4mation Feb 13, 2026
26fd3e1
ENG-219: Remove unused workingDir arg from getConfig() call
d4mation Feb 13, 2026
030ce60
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-build
d4mation Feb 14, 2026
8698d9f
ENG-219: Remove `dist/` files
d4mation Feb 14, 2026
9ea1d91
ENG-219: Improve and clarify build env vars test
d4mation Feb 14, 2026
0abe46a
ENG-219: Remove unnecessary file
d4mation Feb 14, 2026
e72de6c
ENG-219: Remove accidentally committed action
d4mation Feb 14, 2026
8f99f64
ENG-219: Remove accidentally comitted .puprc
d4mation Feb 14, 2026
13242bc
ENG-219: Remove more accidentally comitted files
d4mation Feb 14, 2026
6ed1720
ENG-219: Add test for build command --root flag
d4mation Feb 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
54 changes: 54 additions & 0 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -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 <dir>', '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.');
});
}
84 changes: 84 additions & 0 deletions tests/commands/build.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>).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');
});
});