diff --git a/.github/workflows/smoke-init-test.yml b/.github/workflows/smoke-init-test.yml
index 46e46dda9..98c963b44 100644
--- a/.github/workflows/smoke-init-test.yml
+++ b/.github/workflows/smoke-init-test.yml
@@ -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: |
diff --git a/cli/build/build-file.ts b/cli/build/build-file.ts
index c757868de..32bcc6aff 100644
--- a/cli/build/build-file.ts
+++ b/cli/build/build-file.ts
@@ -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 */
@@ -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,
}
diff --git a/cli/build/register.ts b/cli/build/register.ts
index 82913a583..af4bfc79d 100644
--- a/cli/build/register.ts
+++ b/cli/build/register.ts
@@ -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,
@@ -400,6 +401,7 @@ export const registerBuild = (program: Command) => {
ok: boolean
circuitJson?: unknown[]
hasErrors?: boolean
+ hasWarnings?: boolean
ignoredDrcByCategory?: DrcIgnoreCounts
isFatalError?: { errorType: string; message: string }
},
@@ -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
@@ -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
@@ -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) {
diff --git a/cli/build/utils/exit-build.ts b/cli/build/utils/exit-build.ts
index cb63a4be6..8d6b88b76 100644
--- a/cli/build/utils/exit-build.ts
+++ b/cli/build/utils/exit-build.ts
@@ -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)
}
diff --git a/lib/shared/push-snippet.ts b/lib/shared/push-snippet.ts
index 158f0abbd..dfdf13bc1 100644
--- a/lib/shared/push-snippet.ts
+++ b/lib/shared/push-snippet.ts
@@ -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"
@@ -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 }
}
diff --git a/tests/cli/build/build-config-options.test.ts b/tests/cli/build/build-config-options.test.ts
index e3625d275..5c9ba63e2 100644
--- a/tests/cli/build/build-config-options.test.ts
+++ b/tests/cli/build/build-config-options.test.ts
@@ -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")
@@ -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")
@@ -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")
@@ -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
diff --git a/tests/cli/build/build-ignore-drc-categories.test.ts b/tests/cli/build/build-ignore-drc-categories.test.ts
index 5e3507e87..aee7d4362 100644
--- a/tests/cli/build/build-ignore-drc-categories.test.ts
+++ b/tests/cli/build/build-ignore-drc-categories.test.ts
@@ -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 () => {
diff --git a/tests/cli/build/build-inject-props.test.ts b/tests/cli/build/build-inject-props.test.ts
index a280454f1..e43add7fb 100644
--- a/tests/cli/build/build-inject-props.test.ts
+++ b/tests/cli/build/build-inject-props.test.ts
@@ -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"))
@@ -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,
diff --git a/tests/cli/build/build-kicad-project-3d-models.test.ts b/tests/cli/build/build-kicad-project-3d-models.test.ts
index 425941334..6ed9b6791 100644
--- a/tests/cli/build/build-kicad-project-3d-models.test.ts
+++ b/tests/cli/build/build-kicad-project-3d-models.test.ts
@@ -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)
diff --git a/tests/cli/build/build-kicad-project-zip.test.ts b/tests/cli/build/build-kicad-project-zip.test.ts
index 1c77e40af..cd43ad2fd 100644
--- a/tests/cli/build/build-kicad-project-zip.test.ts
+++ b/tests/cli/build/build-kicad-project-zip.test.ts
@@ -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)
diff --git a/tests/cli/build/build-profile.test.ts b/tests/cli/build/build-profile.test.ts
index c0f69dc95..aea07d2db 100644
--- a/tests/cli/build/build-profile.test.ts
+++ b/tests/cli/build/build-profile.test.ts
@@ -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)")
diff --git a/tests/cli/build/build-routing-disabled.test.ts b/tests/cli/build/build-routing-disabled.test.ts
index 003f2a4c4..870b8ee9d 100644
--- a/tests/cli/build/build-routing-disabled.test.ts
+++ b/tests/cli/build/build-routing-disabled.test.ts
@@ -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)
diff --git a/tests/cli/build/build-site.test.ts b/tests/cli/build/build-site.test.ts
index 7dff7cbe5..235f94e16 100644
--- a/tests/cli/build/build-site.test.ts
+++ b/tests/cli/build/build-site.test.ts
@@ -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"),
@@ -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"),
diff --git a/tests/cli/build/build-step-config.test.ts b/tests/cli/build/build-step-config.test.ts
index d916eaf1d..d4893bdb8 100644
--- a/tests/cli/build/build-step-config.test.ts
+++ b/tests/cli/build/build-step-config.test.ts
@@ -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")
@@ -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(
diff --git a/tests/cli/build/build-with-drc-error.test.ts b/tests/cli/build/build-with-drc-error.test.ts
index caeae813b..9abab8587 100644
--- a/tests/cli/build/build-with-drc-error.test.ts
+++ b/tests/cli/build/build-with-drc-error.test.ts
@@ -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)
diff --git a/tests/cli/build/build-without-package-json.test.ts b/tests/cli/build/build-without-package-json.test.ts
index 5aae9642b..1a64e8c06 100644
--- a/tests/cli/build/build-without-package-json.test.ts
+++ b/tests/cli/build/build-without-package-json.test.ts
@@ -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")
@@ -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(
diff --git a/tests/cli/build/build.test.ts b/tests/cli/build/build.test.ts
index 4c84ab293..b9659e40d 100644
--- a/tests/cli/build/build.test.ts
+++ b/tests/cli/build/build.test.ts
@@ -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(
diff --git a/tests/cli/init/init-npm-import.test.ts b/tests/cli/init/init-npm-import.test.ts
index 0ad8b2448..892d4d076 100644
--- a/tests/cli/init/init-npm-import.test.ts
+++ b/tests/cli/init/init-npm-import.test.ts
@@ -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
diff --git a/tests/cli/transpile/transpile-link-glb.test.ts b/tests/cli/transpile/transpile-link-glb.test.ts
index 5b81516e1..5486fedc6 100644
--- a/tests/cli/transpile/transpile-link-glb.test.ts
+++ b/tests/cli/transpile/transpile-link-glb.test.ts
@@ -162,7 +162,7 @@ export default () =>
const { stderr: consumerBuildStderr } = await runCommand(
`tsci build ${consumerIndex}`,
)
- expect(consumerBuildStderr).toBe("")
+ expect(consumerBuildStderr).toContain("code 2")
const consumerCircuitJson = path.join(
consumerDir,
diff --git a/tests/cli/transpile/transpile-link.test.ts b/tests/cli/transpile/transpile-link.test.ts
index 46df1ae75..ed6f8fa35 100644
--- a/tests/cli/transpile/transpile-link.test.ts
+++ b/tests/cli/transpile/transpile-link.test.ts
@@ -127,7 +127,7 @@ export default () =>
const { stderr: consumerBuildStderr } = await runCommand(
`tsci build ${consumerIndex}`,
)
- expect(consumerBuildStderr).toBe("")
+ expect(consumerBuildStderr).toContain("code 2")
const consumerCircuitJson = path.join(
consumerDir,