diff --git a/AGENTS.md b/AGENTS.md index a7894f2a2..2c53e16aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,3 +80,11 @@ 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 1778256007 +# bump 1778299207 +# bump 1778342407 +# bump 1778385607 +# bump 1778515208 +# bump 1778558407 +# bump 1778601608 +# bump 1778644807 diff --git a/cli/build/register.ts b/cli/build/register.ts index ffc7b8397..436abbf44 100644 --- a/cli/build/register.ts +++ b/cli/build/register.ts @@ -732,7 +732,16 @@ export const registerBuild = (program: Command) => { const entryFile = fileArgIsDirectFile ? resolvedFileArgPath : transpileEntrypoint - if (!entryFile) { + const isRealTsEntrypoint = Boolean( + entryFile && + (entryFile.endsWith(".ts") || entryFile.endsWith(".tsx")), + ) + if ( + !entryFile || + (hasConfiguredIncludeBoardFiles && + !transpileExplicitlyRequested && + !isRealTsEntrypoint) + ) { if ( hasConfiguredIncludeBoardFiles && !transpileExplicitlyRequested diff --git a/lib/shared/get-entrypoint.ts b/lib/shared/get-entrypoint.ts index c2858b38d..770cce979 100644 --- a/lib/shared/get-entrypoint.ts +++ b/lib/shared/get-entrypoint.ts @@ -1,5 +1,6 @@ import * as fs from "node:fs" import * as path from "node:path" +import { globbySync } from "globby" import { loadProjectConfig } from "lib/project-config" import kleur from "kleur" @@ -202,6 +203,27 @@ 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 = globbySync( + ["**/*.circuit.json", "**/circuit.json"], + { + cwd: validatedProjectDir, + ignore: ["**/node_modules/**", "**/dist/**"], + }, + ) + .map((f) => path.resolve(validatedProjectDir, f)) + .filter( + (f) => fs.existsSync(f) && isValidDirectory(f, 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 '", diff --git a/tests/get-entrypoint.test.ts b/tests/get-entrypoint.test.ts index 28ee40c54..ff30ef7de 100644 --- a/tests/get-entrypoint.test.ts +++ b/tests/get-entrypoint.test.ts @@ -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 () => ', + ) + + 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")) +})