diff --git a/packages/agent/tsconfig.publish.json b/packages/agent/tsconfig.publish.json index ea6be8e9a..c216426de 100644 --- a/packages/agent/tsconfig.publish.json +++ b/packages/agent/tsconfig.publish.json @@ -1,3 +1,25 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules", + "test", + "tests", + "scripts", + "bench" + ] } diff --git a/packages/ai/tsconfig.publish.json b/packages/ai/tsconfig.publish.json index ea6be8e9a..c216426de 100644 --- a/packages/ai/tsconfig.publish.json +++ b/packages/ai/tsconfig.publish.json @@ -1,3 +1,25 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules", + "test", + "tests", + "scripts", + "bench" + ] } diff --git a/packages/coding-agent/tsconfig.publish.json b/packages/coding-agent/tsconfig.publish.json index ea6be8e9a..c216426de 100644 --- a/packages/coding-agent/tsconfig.publish.json +++ b/packages/coding-agent/tsconfig.publish.json @@ -1,3 +1,25 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules", + "test", + "tests", + "scripts", + "bench" + ] } diff --git a/packages/stats/tsconfig.publish.client.json b/packages/stats/tsconfig.publish.client.json new file mode 100644 index 000000000..7350ac6f0 --- /dev/null +++ b/packages/stats/tsconfig.publish.client.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.client.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src/client" + ], + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/packages/stats/tsconfig.publish.json b/packages/stats/tsconfig.publish.json index ea6be8e9a..c6a963a5c 100644 --- a/packages/stats/tsconfig.publish.json +++ b/packages/stats/tsconfig.publish.json @@ -1,3 +1,25 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "src/client", + "dist", + "node_modules", + "test", + "tests", + "scripts" + ] } diff --git a/packages/tui/tsconfig.publish.json b/packages/tui/tsconfig.publish.json index ea6be8e9a..c216426de 100644 --- a/packages/tui/tsconfig.publish.json +++ b/packages/tui/tsconfig.publish.json @@ -1,3 +1,25 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules", + "test", + "tests", + "scripts", + "bench" + ] } diff --git a/packages/utils/tsconfig.publish.json b/packages/utils/tsconfig.publish.json index ea6be8e9a..61f5ee4c6 100644 --- a/packages/utils/tsconfig.publish.json +++ b/packages/utils/tsconfig.publish.json @@ -1,3 +1,22 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "inlineSources": false, + "rootDir": "src", + "outDir": "dist/types", + "noCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules", + "test" + ] } diff --git a/scripts/ci-release-publish.ts b/scripts/ci-release-publish.ts index 7981b769e..2995f7222 100644 --- a/scripts/ci-release-publish.ts +++ b/scripts/ci-release-publish.ts @@ -1,62 +1,143 @@ #!/usr/bin/env bun +/** + * Publish workspace packages. + * + * For each public TypeScript package we: + * 1. Emit `.d.ts` declarations into `dist/types/` so consumers get + * stable types regardless of their tsconfig `lib`. + * 2. Rewrite `package.json` in place — every `types`/`exports[*].types` + * that points at `./src/*.ts(x)` is repointed to `./dist/types/*.d.ts` + * and `dist/types` (plus `dist/client` for `stats`) is added to + * `files`. The on-repo manifest keeps pointing at source so local + * dev resolves types without any build. + * 3. Invoke `bun publish` on the (now publish-shaped) manifest. + * + * Intended for CI. Mutates `package.json` in place — if you run this + * locally, expect a dirty working tree and `git restore` after. + */ import * as path from "node:path"; import { $ } from "bun"; interface PublishPackage { dir: string; + kind: "typescript" | "native"; + /** Extra build steps before manifest rewrite (e.g. esbuild bundles). */ + preBuild?: readonly (readonly string[])[]; + /** Extra entries to splice into `files`. */ + extraFiles?: readonly string[]; + /** Extra tsgo invocations beyond `tsconfig.publish.json`. */ + extraTypeConfigs?: readonly string[]; } -interface PackageJson { +type JsonValue = string | number | boolean | null | JsonObject | JsonValue[]; +interface JsonObject { + [key: string]: JsonValue; +} +interface PackageManifest extends JsonObject { + name?: string; private?: boolean; } const repoRoot = path.join(import.meta.dir, ".."); const isDryRun = process.argv.includes("--dry-run"); -const packageDirs: PublishPackage[] = [ - { dir: "packages/utils" }, - { dir: "packages/ai" }, - { dir: "packages/natives" }, - { dir: "packages/tui" }, - { dir: "packages/stats" }, - { dir: "packages/agent" }, - { dir: "packages/coding-agent" }, +const packages: PublishPackage[] = [ + { dir: "packages/utils", kind: "typescript" }, + { dir: "packages/ai", kind: "typescript" }, + { dir: "packages/natives", kind: "native" }, + { dir: "packages/tui", kind: "typescript" }, + { + dir: "packages/stats", + kind: "typescript", + preBuild: [["bun", "run", "build"]], + extraFiles: ["dist/client"], + extraTypeConfigs: ["tsconfig.publish.client.json"], + }, + { dir: "packages/agent", kind: "typescript" }, + { dir: "packages/coding-agent", kind: "typescript" }, ]; +function rewriteSrcPath(value: string): string { + if (!value.startsWith("./src/")) return value; + const rel = value.slice("./src/".length).replace(/\.tsx?$/, ""); + return `./dist/types/${rel}.d.ts`; +} -async function readPackageJson(packageDir: string): Promise { - return (await Bun.file(path.join(repoRoot, packageDir, "package.json")).json()) as PackageJson; +function rewriteExports(exports: JsonValue): JsonValue { + if (exports === null || typeof exports !== "object" || Array.isArray(exports)) return exports; + const src = exports as JsonObject; + const out: JsonObject = {}; + for (const key in src) { + const val = src[key]; + if ( + val !== null && + typeof val === "object" && + !Array.isArray(val) && + typeof (val as JsonObject).types === "string" && + ((val as JsonObject).types as string).startsWith("./src/") + ) { + const next: JsonObject = { ...(val as JsonObject) }; + next.types = rewriteSrcPath(next.types as string); + out[key] = next; + } else { + out[key] = val; + } + } + return out; +} + +async function rewriteManifest(pkgDir: string, extraFiles: readonly string[]): Promise { + const manifestPath = path.join(pkgDir, "package.json"); + const manifest = (await Bun.file(manifestPath).json()) as PackageManifest; + if (typeof manifest.types === "string" && manifest.types.startsWith("./src/")) { + manifest.types = rewriteSrcPath(manifest.types); + } + if (manifest.exports !== undefined) manifest.exports = rewriteExports(manifest.exports); + const files = Array.isArray(manifest.files) ? [...manifest.files] : []; + const hasDist = files.includes("dist"); + if (!hasDist && !files.includes("dist/types")) files.push("dist/types"); + for (const extra of extraFiles) { + if (!hasDist && !files.includes(extra)) files.push(extra); + } + manifest.files = files; + await Bun.write(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`); + return manifest; +} + +async function preparePackage(pkg: PublishPackage): Promise { + const pkgDir = path.join(repoRoot, pkg.dir); + if (pkg.kind === "native") { + return (await Bun.file(path.join(pkgDir, "package.json")).json()) as PackageManifest; + } + for (const argv of pkg.preBuild ?? []) { + await $`${argv}`.cwd(pkgDir); + } + await $`bun x tsgo -p tsconfig.publish.json`.cwd(pkgDir); + for (const cfg of pkg.extraTypeConfigs ?? []) { + await $`bun x tsgo -p ${cfg}`.cwd(pkgDir); + } + return rewriteManifest(pkgDir, pkg.extraFiles ?? []); } async function publishPackage(pkg: PublishPackage): Promise { - const packageJson = await readPackageJson(pkg.dir); - const packageName = path.basename(pkg.dir); - if (packageJson.private) { - console.log(`Skipping ${packageName} (private)`); + const pkgDir = path.join(repoRoot, pkg.dir); + const manifest = await preparePackage(pkg); + const name = manifest.name ?? path.basename(pkg.dir); + if (manifest.private) { + console.log(`Skipping ${name} (private)`); return; } - if (isDryRun) { console.log(`DRY RUN bun publish --access public --tolerate-republish (${pkg.dir})`); return; } - - console.log(`Publishing ${packageName}...`); - const result = await $`bun publish --access public --tolerate-republish`.cwd(path.join(repoRoot, pkg.dir)).quiet().nothrow(); + console.log(`Publishing ${name}…`); + const result = await $`bun publish --access public --tolerate-republish`.cwd(pkgDir).quiet().nothrow(); const output = `${result.stdout.toString()}${result.stderr.toString()}`.trim(); - if (result.exitCode === 0) { - if (output) console.log(output); - return; - } if (output) console.log(output); - - process.exit(result.exitCode ?? 1); + if (result.exitCode !== 0) process.exit(result.exitCode ?? 1); } -async function main(): Promise { - for (const pkg of packageDirs) { - await publishPackage(pkg); - } +for (const pkg of packages) { + await publishPackage(pkg); } - -await main();