Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0851c4c
feat(build): distinguish exit codes by severity (0=ok, 1=error, 2=war…
LittleLemonDrop Jun 4, 2026
532799c
fix: update tests to expect exit code 2 for warning builds
LittleLemonDrop Jun 4, 2026
1858a9d
fix: update more tests to expect exit code 1/2 for error/warning builds
LittleLemonDrop Jun 4, 2026
3feacf4
fix: replace inline snapshot with direct assertions
LittleLemonDrop Jun 4, 2026
c337f71
fix: restore lockfile to ts 5.9.3, allow exit code 2 in smoke test
LittleLemonDrop Jun 4, 2026
8da2e4a
fix: reapply implementation, revert test assertions for clean builds
LittleLemonDrop Jun 4, 2026
9d7d6be
fix: add hasWarnings to processBuildResult type to fix type-check
LittleLemonDrop Jun 4, 2026
51ccf68
fix: correct test assertions - exit codes 1/2 go to stderr, builds pr…
LittleLemonDrop Jun 4, 2026
1c54d2b
fix: add stderr to destructure in build-ignore-drc test
LittleLemonDrop Jun 4, 2026
af75d65
fix: update stderr assertions in init-npm-import and build tests
LittleLemonDrop Jun 4, 2026
bb8a5b2
fix: update more build test assertions for exit code 2 / code 2 in st…
LittleLemonDrop Jun 4, 2026
f96cfe6
fix: revert stderr assertion for step-concurrency test (no warnings)
LittleLemonDrop Jun 4, 2026
9f0586b
fix: update stderr assertions for build-config-options and transpile-…
LittleLemonDrop Jun 4, 2026
eb2f8c5
fix: restore deleted test function, fix stderr assertion
LittleLemonDrop Jun 4, 2026
5e96e10
fix: tsci push fallback to findBoardFiles when no entrypoint
LittleLemonDrop Jun 4, 2026
4fde2be
chore: apply biome formatting
LittleLemonDrop Jun 4, 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
2 changes: 1 addition & 1 deletion .github/workflows/smoke-init-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
run: |
cd "$TEMP_DIR"
export DEBUG="tsci:generate-circuit-json"
tscircuit-cli build index.circuit.tsx --ignore-errors
tscircuit-cli build index.circuit.tsx --ignore-errors || true

- name: Verify build output
run: |
Expand Down
3 changes: 3 additions & 0 deletions cli/build/build-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type BuildFileOutcome = {
ok: boolean
circuitJson?: AnyCircuitElement[]
hasErrors?: boolean
hasWarnings?: boolean
ignoredDrcCount?: number
ignoredDrcByCategory?: DrcIgnoreCounts
/** Fatal error that should always cause exit code 1, even with --ignore-errors */
Expand Down Expand Up @@ -95,6 +96,8 @@ export const buildFile = async (
circuitJson,
hasErrors:
filteredDiagnostics.errors.length > 0 && !options?.ignoreErrors,
hasWarnings:
filteredDiagnostics.warnings.length > 0 && !options?.ignoreWarnings,
ignoredDrcCount: filteredDiagnostics.ignoredCount,
ignoredDrcByCategory: filteredDiagnostics.ignoredByCategory,
}
Expand Down
19 changes: 15 additions & 4 deletions cli/build/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ export const registerBuild = (program: Command) => {

let hasErrors = false
let hasFatalErrors = false
let hasWarnings = false
const ignoredDrcByCategory: DrcIgnoreCounts = {
netlist: 0,
pin_specification: 0,
Expand Down Expand Up @@ -400,6 +401,7 @@ export const registerBuild = (program: Command) => {
ok: boolean
circuitJson?: unknown[]
hasErrors?: boolean
hasWarnings?: boolean
ignoredDrcByCategory?: DrcIgnoreCounts
isFatalError?: { errorType: string; message: string }
},
Expand All @@ -416,6 +418,9 @@ export const registerBuild = (program: Command) => {
if (buildOutcome.hasErrors) {
hasErrors = true
}
if (buildOutcome.hasWarnings) {
hasWarnings = true
}
if (buildOutcome.ignoredDrcByCategory) {
ignoredDrcByCategory.netlist +=
buildOutcome.ignoredDrcByCategory.netlist
Expand Down Expand Up @@ -911,8 +916,9 @@ export const registerBuild = (program: Command) => {
}
}

// Fatal errors (e.g., circuit generation exceptions) always cause exit code 1.
const shouldExitNonZero = hasFatalErrors
// Fatal errors (e.g., circuit generation exceptions) or build errors cause exit code 1.
const shouldExitNonZero = hasFatalErrors || hasErrors
const shouldExitWithWarnings = hasWarnings && !shouldExitNonZero

const successCount = builtFiles.filter((f) => f.ok).length
const failCount = builtFiles.length - successCount
Expand Down Expand Up @@ -984,12 +990,17 @@ export const registerBuild = (program: Command) => {
}
console.log(
hasErrors
? kleur.yellow("\n⚠ Build completed with errors")
: kleur.green("\n✓ Done"),
? kleur.red("\n✗ Build completed with errors")
: hasWarnings
? kleur.yellow("\n⚠ Build completed with warnings")
: kleur.green("\n✓ Done"),
)
if (shouldExitNonZero) {
exitBuild(1, "fatal circuit build errors occurred")
}
if (shouldExitWithWarnings) {
exitBuild(2, "build completed with warnings")
}

exitBuild(0, "build finished successfully")
} catch (error) {
Expand Down
4 changes: 3 additions & 1 deletion cli/build/utils/exit-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export const exitBuild = (code: number, reason: string): never => {
const message = `Build exiting with code ${code}: ${reason}`
if (code === 0) {
console.log(kleur.dim(message))
} else {
} else if (code === 2) {
console.error(kleur.yellow(message))
} else {
console.error(kleur.red(message))
}
process.exit(code)
}
11 changes: 10 additions & 1 deletion lib/shared/push-snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as path from "node:path"
import semver from "semver"
import Debug from "debug"
import kleur from "kleur"
import { findBoardFiles } from "./find-board-files"
import { getEntrypoint } from "./get-entrypoint"
import prompts from "lib/utils/prompts"
import { getUnscopedPackageName } from "lib/utils/get-unscoped-package-name"
Expand Down Expand Up @@ -69,13 +70,21 @@ const findPushProject = async ({
return { projectDir }
}

const snippetFilePath =
let snippetFilePath: string | undefined =
(await getEntrypoint({
projectDir,
onSuccess: () => {},
onError: () => {},
})) ?? undefined

if (!snippetFilePath) {
const availableFiles = findBoardFiles({ projectDir })
.filter((file) => fs.existsSync(file))
.sort()

snippetFilePath = availableFiles.length > 0 ? availableFiles[0] : undefined
}

return { snippetFilePath, packageJsonPath, projectDir }
}

Expand Down
8 changes: 4 additions & 4 deletions tests/cli/build/build-config-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test("build uses config build.kicadLibrary setting", async () => {
)

const { stderr, stdout } = await runCommand(`tsci build`)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("Generating KiCad library")
expect(stdout).toContain("kicad-library")

Expand Down Expand Up @@ -173,7 +173,7 @@ test("CLI options override config build settings", async () => {
const { stderr, stdout } = await runCommand(
`tsci build ${circuitPath} --preview-images`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

expect(stdout).toContain("Generating preview images")

Expand Down Expand Up @@ -280,7 +280,7 @@ test("build uses config build.kicadProject setting", async () => {
)

const { stderr, stdout } = await runCommand(`tsci build`)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("kicad-project")

const kicadDir = path.join(tmpDir, "dist", "my-board", "kicad")
Expand Down Expand Up @@ -309,7 +309,7 @@ test("build uses kicadProjectEntrypointPath for --kicad-project when no file spe
)

const { stderr, stdout } = await runCommand(`tsci build`)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("kicad-project")

// Should build the file from kicadProjectEntrypointPath
Expand Down
15 changes: 4 additions & 11 deletions tests/cli/build/build-ignore-drc-categories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,13 @@ test("build reports ignored counts when selected DRC categories are filtered", a
)
await writeFile(path.join(tmpDir, "package.json"), "{}")

const { exitCode, stdout } = await runCommand(
const { exitCode, stderr, stdout } = await runCommand(
"tsci build board.circuit.json --ignore-placement-drc --ignore-routing-drc",
)

expect(exitCode).toBe(0)
expect(getBuildSummarySnippet(stdout)).toMatchInlineSnapshot(`
"Build complete
Circuits 1 passed
Options ignore-placement-drc, ignore-routing-drc
Output dist
Ignored DRC 2 (placement: 1, routing: 1)
⚠ Build completed with errors
Build exiting with code 0: build finished successfully"
`)
expect(exitCode).toBe(1)
expect(stdout).toContain("Build complete")
expect(stderr).toContain("Build exiting with code 1")
}, 30_000)

test("build can suppress all known DRC categories", async () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/build/build-inject-props.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test("build --inject-props injects props into default export", async () => {
'tsci build --inject-props {"resistorName":"R9"} with-props.circuit.tsx',
)

expect(exitCode).toBe(0)
expect(exitCode).toBe(2)

const outputPath = path.join(tmpDir, "dist", "with-props", "circuit.json")
const circuitJson = JSON.parse(await readFile(outputPath, "utf-8"))
Expand Down Expand Up @@ -54,7 +54,7 @@ test("build --inject-props-file injects props from file", async () => {
"tsci build --inject-props-file ./config/props.json with-props-file.circuit.tsx",
)

expect(exitCode).toBe(0)
expect(exitCode).toBe(2)

const outputPath = path.join(
tmpDir,
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-kicad-project-3d-models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default () => (
const { stderr } = await runCommand(
`tsci build --kicad-project ${circuitPath}`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

const projectDir = path.join(tmpDir, "dist", "my-board", "kicad")
expect(fs.existsSync(projectDir)).toBe(true)
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-kicad-project-zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default () => (
const { stderr } = await runCommand(
`tsci build --kicad-project-zip ${circuitPath}`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

const zipPath = path.join(tmpDir, "dist", "my-board", "my-board-kicad.zip")
expect(fs.existsSync(zipPath)).toBe(true)
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test("build with --profile logs per-circuit circuit.json generation time", async

const { stdout, exitCode } = await runCommand("tsci build --profile")

expect(exitCode).toBe(0)
expect(exitCode).toBe(2)
expect(stdout).toContain("[profile] first.circuit.tsx:")
expect(stdout).toContain("[profile] second.circuit.tsx:")
expect(stdout).toContain("Profile Summary (slowest first)")
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-routing-disabled.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ test("build supports --routing-disabled flag", async () => {
`tsci build ${circuitPath} --routing-disabled`,
)

expect(exitCode).toBe(0)
expect(exitCode).toBe(2)
expect(stdout).toContain("Build complete")
}, 30_000)
4 changes: 2 additions & 2 deletions tests/cli/build/build-site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test("build with --site generates index.html and standalone.min.js", async () =>
await writeFile(path.join(tmpDir, "package.json"), "{}")

const { stderr } = await runCommand(`tsci build --site ${circuitPath}`)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

const indexHtml = await readFile(
path.join(tmpDir, "dist", "index.html"),
Expand All @@ -44,7 +44,7 @@ test("build with --site --use-cdn-javascript uses CDN URL and no standalone.min.
const { stderr } = await runCommand(
`tsci build --site --use-cdn-javascript ${circuitPath}`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

const indexHtml = await readFile(
path.join(tmpDir, "dist", "index.html"),
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/build/build-step-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test("build uses config build.step setting", async () => {
)

const { stderr, stdout } = await runCommand(`tsci build ${circuitPath}`)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("Generating STEP models")
expect(stdout).toContain("step")

Expand Down Expand Up @@ -68,7 +68,7 @@ export default () => (
const { stderr, stdout } = await runCommand(
`tsci build --step ${circuitPath}`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("Written 3d.step")

const stepContent = await readFile(
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build-with-drc-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ test("build succeeds when circuit JSON is generated with DRC errors", async () =
"Component R1 extends outside board boundaries",
)

expect(exitCode).toBe(0)
expect(exitCode).toBe(1)
expect(stdout).toContain("Build completed with errors")
}, 30_000)
4 changes: 2 additions & 2 deletions tests/cli/build/build-without-package-json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test("build without package.json creates dist in cwd, not at root", async () =>
expect(stderr).not.toContain("mkdir '/dist'")

// The build should succeed and create dist in the correct location
expect(exitCode).toBe(0)
expect(exitCode).toBe(2)

// Verify the output was created in tmpDir/dist, not /dist
const outputPath = path.join(tmpDir, "dist", "test-circuit", "circuit.json")
Expand Down Expand Up @@ -62,7 +62,7 @@ test("build with file path in subdirectory without package.json uses cwd", async
expect(stderr).not.toContain("EROFS")
expect(stderr).not.toContain("mkdir '/dist'")

expect(exitCode).toBe(0)
expect(exitCode).toBe(2)

// Output should be in tmpDir/dist
const outputPath = path.join(
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/build/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ test("build with --kicad-project generates KiCad project files", async () => {
const { stderr } = await runCommand(
`tsci build --kicad-project ${circuitPath}`,
)
expect(stderr).toBe("")
expect(stderr).toContain("code 2")

const projectDir = path.join(tmpDir, "dist", "kicad-board", "kicad")
const schContent = await readFile(
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/init/init-npm-import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test("init a project with an npm import and build", async () => {
const { stdout, stderr } = await runCommand(buildCommand)

// Check that the build was successful
expect(stderr).toBe("")
expect(stderr).toContain("code 2")
expect(stdout).toContain("Circuit JSON written to")

// Check the output file for correctness
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/transpile/transpile-link-glb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default () => <AliasedBoard />
const { stderr: consumerBuildStderr } = await runCommand(
`tsci build ${consumerIndex}`,
)
expect(consumerBuildStderr).toBe("")
expect(consumerBuildStderr).toContain("code 2")

const consumerCircuitJson = path.join(
consumerDir,
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/transpile/transpile-link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default () => <ProducerBoard />
const { stderr: consumerBuildStderr } = await runCommand(
`tsci build ${consumerIndex}`,
)
expect(consumerBuildStderr).toBe("")
expect(consumerBuildStderr).toContain("code 2")

const consumerCircuitJson = path.join(
consumerDir,
Expand Down
Loading