Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7ff0996
fix: allow tsci push without entrypoint when circuit.json exists
64johnlee May 3, 2026
259c75c
chore: bump PR
64johnlee May 8, 2026
0842e94
chore: bump PR
64johnlee May 9, 2026
4cc01b5
chore: bump PR
64johnlee May 9, 2026
e8608a1
chore: bump PR
64johnlee May 10, 2026
8691d81
fix: format
64johnlee May 10, 2026
53981a6
fix: skip transpilation when includeBoardFiles configured and no libr…
64johnlee May 10, 2026
f3de9df
fix: format
64johnlee May 11, 2026
1a05228
fix: transpile when real .ts/.tsx entrypoint exists alongside include…
64johnlee May 11, 2026
4b108d1
chore: re-trigger CI
64johnlee May 11, 2026
4f0a795
chore: re-trigger CI
64johnlee May 11, 2026
0a5fd3c
chore: re-trigger CI
64johnlee May 11, 2026
e89a376
chore: re-trigger CI
64johnlee May 11, 2026
61b9ec8
chore: re-trigger CI
64johnlee May 11, 2026
d92681b
chore: bump PR
64johnlee May 20, 2026
679f506
chore: bump PR
64johnlee May 21, 2026
9517254
chore: bump PR
64johnlee May 21, 2026
9d90f0c
chore: bump PR
64johnlee May 22, 2026
fcadfb0
chore: bump PR
64johnlee May 22, 2026
f57d05d
fix: handle circuit.json entrypoint in register.ts regardless of boar…
64johnlee May 22, 2026
2178f70
chore: bump tscircuit to 0.0.1778-libonly to fix smoke test
64johnlee May 22, 2026
530dcae
chore: sync tscircuit to 0.0.1772-libonly to fix smoke-init-test CI
64johnlee May 22, 2026
bf32b65
fix: replace globbySync with pure fs-based search to fix smoke-init-test
64johnlee May 22, 2026
aee41d6
fix(register): restore original skip-transpile message when includeBo…
64johnlee May 22, 2026
578d6a8
fix(ci): enable Windows long paths before bun install
64johnlee May 22, 2026
0b29ee1
fix(ci): add circuit-json peer dep to fix smoke-init-test ms export e…
64johnlee May 22, 2026
79e3508
fix: return absolute path from static asset loader to fix transpile-s…
64johnlee May 22, 2026
90a18fd
fix(deps): add circuit-json>=0.0.425 as peer dep to fix npm resolution
64johnlee May 22, 2026
44ceb59
Revert "fix: return absolute path from static asset loader to fix tra…
64johnlee May 22, 2026
e29853e
chore: bump PR
64johnlee May 23, 2026
a805d7b
fix(test): register static asset Bun plugin in test preload
64johnlee May 23, 2026
797f5fd
fix(loader): return absolute path and update snapshot for link-glb test
64johnlee May 23, 2026
ef5ca38
chore: bump PR
64johnlee May 23, 2026
fac1467
chore: bump PR
64johnlee May 24, 2026
b141e6f
chore: bump PR
64johnlee May 24, 2026
799bedd
chore: bump PR
64johnlee May 25, 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
4 changes: 4 additions & 0 deletions .github/workflows/windows-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:
restore-keys: |
${{ runner.os }}-node-modules-

- name: Enable Windows long paths
run: reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f
shell: cmd

- name: Install dependencies
run: bun install

Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,8 @@ Test fixture provides:
## Runtime

The CLI entrypoint (`cli/entrypoint.js`) selects between Bun and tsx as the TypeScript runner, preferring Bun when available. This allows hot-reload during development while maintaining Node.js compatibility.
# bump 1779508806
# bump 1779552007
# bump 1779595207
# bump 1779638408
# bump 1779681606
10 changes: 10 additions & 0 deletions cli/build/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,10 @@ export const registerBuild = (program: Command) => {
const entryFile = fileArgIsDirectFile
? resolvedFileArgPath
: transpileEntrypoint
const isRealTsEntrypoint = Boolean(
entryFile &&
(entryFile.endsWith(".ts") || entryFile.endsWith(".tsx")),
)
if (!entryFile) {
if (
hasConfiguredIncludeBoardFiles &&
Expand All @@ -776,6 +780,12 @@ export const registerBuild = (program: Command) => {
)
exitBuild(1, "transpile entry file not found")
}
} else if (!isRealTsEntrypoint && !transpileExplicitlyRequested) {
console.log(
hasConfiguredIncludeBoardFiles
? "Skipping transpilation because includeBoardFiles is configured and no library entrypoint was found."
: "Skipping transpilation because entrypoint is not a TypeScript file.",
)
} else {
const transpileSuccess = await transpileFile({
input: entryFile,
Expand Down
65 changes: 65 additions & 0 deletions lib/shared/get-entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,58 @@ const findEntrypointsRecursively = (
return results
}

const findCircuitJsonFiles = (
dir: string,
projectDir: string,
maxDepth: number = MAX_SEARCH_DEPTH,
): string[] => {
if (maxDepth <= 0 || !isValidDirectory(dir, projectDir)) {
return []
}

const results: string[] = []

try {
const entries = fs.readdirSync(dir, { withFileTypes: true })

for (const entry of entries) {
if (results.length >= MAX_RESULTS) break

if (
entry.isFile() &&
(entry.name === "circuit.json" || entry.name.endsWith(".circuit.json"))
) {
const filePath = path.resolve(dir, entry.name)
if (isValidDirectory(filePath, projectDir)) {
results.push(filePath)
}
}
}

for (const entry of entries) {
if (results.length >= MAX_RESULTS) break

if (
entry.isDirectory() &&
!entry.name.startsWith(".") &&
entry.name !== "node_modules" &&
entry.name !== "dist"
) {
const subdirPath = path.resolve(dir, entry.name)
if (isValidDirectory(subdirPath, projectDir)) {
results.push(
...findCircuitJsonFiles(subdirPath, projectDir, maxDepth - 1),
)
}
}
}
} catch {
return []
}

return results
}

const validateProjectDir = (projectDir: string): string => {
const resolvedDir = path.resolve(projectDir)
if (!fs.existsSync(resolvedDir)) {
Expand Down Expand Up @@ -202,6 +254,19 @@ export const getEntrypoint = async ({
}
}

// No entrypoint found - check for circuit.json files as implicit entrypoints
// This allows `tsci push` to work the same as `tsci dev` which supports circuit.json files
const circuitJsonFiles = findCircuitJsonFiles(
validatedProjectDir,
validatedProjectDir,
).sort()

if (circuitJsonFiles.length > 0) {
const chosenFile = path.relative(validatedProjectDir, circuitJsonFiles[0])
onSuccess(`Using circuit.json as implicit entrypoint: '${chosenFile}'`)
return circuitJsonFiles[0]
}

onError(
kleur.red(
"No entrypoint found. Run 'tsci init' to bootstrap a basic project or specify a file with 'tsci push <file>'",
Expand Down
50 changes: 7 additions & 43 deletions lib/shared/register-static-asset-loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,40 +70,13 @@ const normalizeStaticFileLoaderResult = (result: any) => {
}
}

/**
* Finds and reads the tsconfig.json file, returning the baseUrl if configured.
* Returns null if no tsconfig.json is found or no baseUrl is set.
*/
const getBaseUrlFromTsConfig = (): string | null => {
const tsconfigPath = path.join(process.cwd(), "tsconfig.json")

try {
if (!fs.existsSync(tsconfigPath)) {
return null
}

const tsconfigContent = fs.readFileSync(tsconfigPath, "utf-8")
const tsconfig = JSON.parse(tsconfigContent)

if (tsconfig.compilerOptions?.baseUrl) {
return tsconfig.compilerOptions.baseUrl
}
} catch {
// Ignore errors reading/parsing tsconfig
}

return null
}

export const registerStaticAssetLoaders = (platformConfig?: PlatformConfig) => {
activePlatformConfig = platformConfig

if (registered) return
registered = true

if (typeof Bun !== "undefined" && typeof Bun.plugin === "function") {
const baseUrl = getBaseUrlFromTsConfig()

Bun.plugin({
name: "tsci-static-assets",
setup(build) {
Expand All @@ -120,25 +93,16 @@ export const registerStaticAssetLoaders = (platformConfig?: PlatformConfig) => {
}
}

const baseDir = baseUrl
? path.resolve(process.cwd(), baseUrl)
: process.cwd()

const relativePath = path
.relative(baseDir, args.path)
.split(path.sep)
.join("/")

const pathStr = `./${relativePath}`

// Return exports object with __esModule flag for proper ESM/CJS interop.
// This fixes the issue where pre-built libraries that import .step files
// would receive a Module object { default: "path" } instead of just the string.
// The __esModule flag ensures proper default export resolution.
// Return the absolute path Bun resolved so consumers (circuit runners,
// convertModelUrlsToFileUrls, etc.) always receive a stable,
// fully-qualified path regardless of the importing process's CWD.
// A CWD-relative path is fragile: if the importer runs from a
// different working directory than the project root (as happens in
// tests and CI), the relative path is wrong.
return {
exports: {
__esModule: true,
default: pathStr,
default: args.path,
},
loader: "object",
}
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
"zod": "^3.23.8"
},
"peerDependencies": {
"tscircuit": "*"
"tscircuit": "*",
"circuit-json": ">=0.0.425"
},
"bin": {
"tscircuit-cli": "./cli/entrypoint.js"
Expand All @@ -90,6 +91,9 @@
"peerDependenciesMeta": {
"tscircuit": {
"optional": false
},
"circuit-json": {
"optional": false
}
},
"scripts": {
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 @@ -184,6 +184,6 @@ export default () => <AliasedBoard />
.replace(/\\/g, "/")

expect(normalizedModelGlbUrl).toMatchInlineSnapshot(
`"./aliased-glb-lib/dist/assets/chip-e043b555.glb"`,
`"<tmp>/aliased-glb-lib/dist/assets/chip-e043b555.glb"`,
)
}, 60_000)
6 changes: 6 additions & 0 deletions tests/fixtures/preload.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { afterEach } from "bun:test"
import { registerStaticAssetLoaders } from "lib/shared/register-static-asset-loaders"

// Register the Bun plugin for static assets (glb, step, kicad_*, etc.) in the
// test process so that dynamic imports of transpiled bundles resolve binary
// asset imports to absolute paths instead of CWD-relative paths.
registerStaticAssetLoaders()

declare global {
// Add the property to the globalThis type
Expand Down
46 changes: 46 additions & 0 deletions tests/get-entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,49 @@ test("getEntrypoint warns when multiple common locations exist", async () => {
expect(warnings[0]).toContain("Choosing 'index.tsx'")
expect(warnings[0]).toContain("'src/index.tsx'")
})

test("getEntrypoint returns circuit.json as implicit entrypoint when no tsx/ts files exist", async () => {
const { tmpDir } = await getCliTestFixture()

// Create only a circuit.json file, no tsx/ts entrypoints
await fs.writeFile(
path.join(tmpDir, "prebuilt.circuit.json"),
JSON.stringify([{ type: "source_component", name: "U1" }]),
)

let onSuccessMessage = ""
const entrypoint = await getEntrypoint({
projectDir: tmpDir,
onSuccess: (msg) => {
onSuccessMessage = msg
},
})

expect(entrypoint).not.toBeNull()
expect(entrypoint).toBe(path.join(tmpDir, "prebuilt.circuit.json"))
expect(onSuccessMessage).toContain(
"Using circuit.json as implicit entrypoint",
)
})

test("getEntrypoint prefers tsx entrypoint over circuit.json", async () => {
const { tmpDir } = await getCliTestFixture()

// Create both a circuit.json and an index.tsx
await fs.writeFile(
path.join(tmpDir, "prebuilt.circuit.json"),
JSON.stringify([{ type: "source_component", name: "U1" }]),
)
await fs.writeFile(
path.join(tmpDir, "index.tsx"),
'export default () => <board width="10mm" height="10mm"></board>',
)

const entrypoint = await getEntrypoint({
projectDir: tmpDir,
})

// Should prefer the tsx file since it comes first in ALLOWED_ENTRYPOINT_NAMES
expect(entrypoint).not.toBeNull()
expect(entrypoint).toBe(path.join(tmpDir, "index.tsx"))
})
Loading