From 5e4fff4dbf29b7976637882531202077201e9ef4 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sun, 8 Mar 2026 14:45:57 -0700 Subject: [PATCH 1/4] feat(vbare-compiler): emit schema version metadata --- typescript/vbare-compiler/src/cli.ts | 52 ++------- typescript/vbare-compiler/src/index.test.ts | 34 +++++- typescript/vbare-compiler/src/index.ts | 114 ++++++++++++++++++-- 3 files changed, 152 insertions(+), 48 deletions(-) diff --git a/typescript/vbare-compiler/src/cli.ts b/typescript/vbare-compiler/src/cli.ts index ad14962..a56e4bc 100644 --- a/typescript/vbare-compiler/src/cli.ts +++ b/typescript/vbare-compiler/src/cli.ts @@ -3,7 +3,7 @@ import { Command } from "commander"; import * as path from "path"; import * as fs from "node:fs/promises"; -import { compileSchema } from "./index"; +import { compileSchema, compileSchemaDirectory } from "./index"; const program = new Command(); @@ -21,43 +21,6 @@ async function isDirectory(p: string): Promise { } } -async function compileFolder(inputDir: string, outputDir: string, opts: { pedantic?: boolean; generator?: string }) { - const resolvedInput = path.resolve(inputDir); - const resolvedOut = path.resolve(outputDir); - - await fs.mkdir(resolvedOut, { recursive: true }); - - const entries = await fs.readdir(resolvedInput, { withFileTypes: true }); - const bareFiles = entries - .filter((e) => e.isFile() && e.name.endsWith(".bare")) - .map((e) => e.name) - .sort(); - - if (bareFiles.length === 0) { - console.error(`No .bare files found in ${resolvedInput}`); - process.exit(1); - } - - for (const file of bareFiles) { - const schemaPath = path.join(resolvedInput, file); - const base = file.replace(/\.bare$/, ""); - const outputPath = path.join(resolvedOut, `${base}.ts`); - - await compileSchema({ - schemaPath, - outputPath, - config: { - pedantic: opts.pedantic ?? false, - generator: (opts.generator as any) ?? "ts", - // Support legacy 'string' in fixtures without extra flags - legacy: true, - }, - }); - - console.log(`Compiled ${schemaPath} -> ${outputPath}`); - } -} - // Default usage: vbare-compiler program .argument("", "Input .bare file or folder containing .bare files") @@ -65,13 +28,19 @@ program .option("-d, --out-dir ", "Output directory (when input is a folder)", "dist") .option("--pedantic", "Enable pedantic mode", false) .option("--generator ", "Generator type (ts, js, dts, bare)", "ts") + .option("--runtime-import ", "Rewrite @bare-ts/lib imports to this runtime package") .action(async (input: string, options) => { try { const inputPath = path.resolve(input); if (await isDirectory(inputPath)) { - await compileFolder(inputPath, options.outDir, { - pedantic: options.pedantic, - generator: options.generator, + await compileSchemaDirectory({ + inputDir: inputPath, + outputDir: options.outDir, + config: { + pedantic: options.pedantic, + generator: options.generator, + }, + runtimeImportPath: options.runtimeImport, }); return; } @@ -88,6 +57,7 @@ program generator: options.generator, legacy: true, }, + runtimeImportPath: options.runtimeImport, }); console.log(`Compiled ${schemaPath} -> ${outputPath}`); diff --git a/typescript/vbare-compiler/src/index.test.ts b/typescript/vbare-compiler/src/index.test.ts index cf2255a..b82dc99 100644 --- a/typescript/vbare-compiler/src/index.test.ts +++ b/typescript/vbare-compiler/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { compileSchema } from "./index"; +import { compileSchema, compileSchemaDirectory } from "./index"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { tmpdir } from "node:os"; @@ -48,6 +48,7 @@ describe("compileSchema", () => { expect(output).toContain("export"); expect(output).toContain("Todo"); expect(output).toContain("App"); + expect(output).toContain("export const SCHEMA_VERSION = 1 as const"); }); it("should handle custom config options using fixtures", async () => { @@ -67,4 +68,35 @@ describe("compileSchema", () => { expect(output).toContain("readTodo"); expect(output).toContain("writeTodo"); }); + + it("should rewrite the runtime import when requested", async () => { + await compileSchema({ + schemaPath: v1Schema, + outputPath, + config: { + legacy: true, + }, + runtimeImportPath: "@rivetkit/bare-ts", + }); + + const output = await fs.readFile(outputPath, "utf-8"); + expect(output).toContain('@rivetkit/bare-ts'); + expect(output).not.toContain('@bare-ts/lib'); + }); + + it("should generate latest.ts for folder compiles", async () => { + const outDir = path.join(tempDir, "folder-out"); + + await compileSchemaDirectory({ + inputDir: fixturesDir, + outputDir: outDir, + config: { + legacy: true, + }, + }); + + const latest = await fs.readFile(path.join(outDir, "latest.ts"), "utf-8"); + expect(latest).toContain('export * from "./v3"'); + expect(latest).toContain("export const CURRENT_VERSION = 3 as const"); + }); }); diff --git a/typescript/vbare-compiler/src/index.ts b/typescript/vbare-compiler/src/index.ts index d168539..4c47bbf 100644 --- a/typescript/vbare-compiler/src/index.ts +++ b/typescript/vbare-compiler/src/index.ts @@ -6,10 +6,18 @@ export interface CompileOptions { schemaPath: string; outputPath: string; config?: Partial; + runtimeImportPath?: string; +} + +export interface CompileDirectoryOptions { + inputDir: string; + outputDir: string; + config?: Partial; + runtimeImportPath?: string; } export async function compileSchema(options: CompileOptions): Promise { - const { schemaPath, outputPath, config = {} } = options; + const { schemaPath, outputPath, config = {}, runtimeImportPath } = options; let schema = await fs.readFile(schemaPath, "utf-8"); @@ -45,11 +53,65 @@ export async function compileSchema(options: CompileOptions): Promise { let result = transform(schema, defaultConfig); - result = postProcessAssert(result); + result = postProcessGeneratedTs(result, { + schemaVersion: parseSchemaVersion(schemaPath), + runtimeImportPath, + }); await fs.writeFile(outputPath, result); } +export async function compileSchemaDirectory( + options: CompileDirectoryOptions, +): Promise { + const { inputDir, outputDir, config = {}, runtimeImportPath } = options; + const resolvedInput = path.resolve(inputDir); + const resolvedOutput = path.resolve(outputDir); + + await fs.mkdir(resolvedOutput, { recursive: true }); + + const entries = await fs.readdir(resolvedInput, { withFileTypes: true }); + const bareFiles = entries + .filter((entry) => entry.isFile() && entry.name.endsWith(".bare")) + .map((entry) => entry.name) + .sort(); + + if (bareFiles.length === 0) { + throw new Error(`No .bare files found in ${resolvedInput}`); + } + + let latestVersion: number | null = null; + + for (const file of bareFiles) { + const schemaPath = path.join(resolvedInput, file); + const base = file.replace(/\.bare$/, ""); + const outputPath = path.join(resolvedOutput, `${base}.ts`); + + await compileSchema({ + schemaPath, + outputPath, + config: { + pedantic: config.pedantic ?? false, + generator: (config.generator as any) ?? "ts", + legacy: config.legacy ?? true, + }, + runtimeImportPath, + }); + + const schemaVersion = parseSchemaVersion(schemaPath); + if (schemaVersion !== null) { + latestVersion = latestVersion === null ? schemaVersion : Math.max(latestVersion, schemaVersion); + } + } + + if (latestVersion !== null) { + await fs.writeFile( + path.join(resolvedOutput, "latest.ts"), + createLatestModule(latestVersion), + ); + } +} + const ASSERT_FUNCTION = ` function assert(condition: boolean, message?: string): asserts condition { if (!condition) throw new Error(message ?? "Assertion failed") @@ -58,16 +120,56 @@ function assert(condition: boolean, message?: string): asserts condition { `; /** - * Remove Node.js assert import and inject a custom assert function + * Post-process generated TypeScript to remove Node.js-only imports, optionally + * rewrite the runtime package import, and expose version metadata for vN schemas. */ -function postProcessAssert(code: string): string { +function postProcessGeneratedTs( + code: string, + options: { + schemaVersion: number | null; + runtimeImportPath?: string; + }, +): string { + const { schemaVersion, runtimeImportPath } = options; + // Remove Node.js assert import code = code.replace(/^import assert from "node:assert\/strict"/m, ""); + code = code.replace(/^import assert from "node:assert"/m, ""); + code = code.replace(/^import assert from "assert"/m, ""); + + if (runtimeImportPath) { + code = code.replace(/@bare-ts\/lib/g, runtimeImportPath); + } + + if (schemaVersion !== null) { + const defaultConfigLine = 'const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({})\n'; + if (!code.includes(defaultConfigLine)) { + throw new Error("Failed to find DEFAULT_CONFIG while injecting SCHEMA_VERSION"); + } - // INject new assert function - code += "\n" + ASSERT_FUNCTION; + code = code.replace( + defaultConfigLine, + `${defaultConfigLine}\nexport const SCHEMA_VERSION = ${schemaVersion} as const\n`, + ); + } + + // Inject new assert function + code += "\n" + ASSERT_FUNCTION; return code; } +function parseSchemaVersion(schemaPath: string): number | null { + const match = path.basename(schemaPath).match(/^v(\d+)\.bare$/); + return match ? Number.parseInt(match[1], 10) : null; +} + +function createLatestModule(latestVersion: number): string { + return `// @generated by @vbare/compiler +export * from "./v${latestVersion}"; + +export const CURRENT_VERSION = ${latestVersion} as const; +`; +} + export { type Config, transform } from "@bare-ts/tools"; From a1553c8fff6f2b6a43f769464f1e41a23678b2bc Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sun, 8 Mar 2026 14:48:44 -0700 Subject: [PATCH 2/4] fix(example-basic): use pnpm exec for compiler --- typescript/examples/basic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/examples/basic/package.json b/typescript/examples/basic/package.json index 1593243..fcf00d8 100644 --- a/typescript/examples/basic/package.json +++ b/typescript/examples/basic/package.json @@ -5,7 +5,7 @@ "description": "Basic example: compile fixtures and migrate between versions", "type": "module", "scripts": { - "build": "vbare-compiler ../../../fixtures/tests/basic/", + "build": "pnpm exec vbare-compiler ../../../fixtures/tests/basic/", "check-types": "tsc --noEmit", "test": "pnpm build && vitest run", "test:watch": "pnpm build && vitest watch" From 340897a57af5c82bf9a7d2a64788dfb7de837458 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sun, 8 Mar 2026 14:49:57 -0700 Subject: [PATCH 3/4] fix(example-basic): call compiler entrypoint directly --- typescript/examples/basic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/examples/basic/package.json b/typescript/examples/basic/package.json index fcf00d8..06904a5 100644 --- a/typescript/examples/basic/package.json +++ b/typescript/examples/basic/package.json @@ -5,7 +5,7 @@ "description": "Basic example: compile fixtures and migrate between versions", "type": "module", "scripts": { - "build": "pnpm exec vbare-compiler ../../../fixtures/tests/basic/", + "build": "node ../../vbare-compiler/dist/cli.js ../../../fixtures/tests/basic/", "check-types": "tsc --noEmit", "test": "pnpm build && vitest run", "test:watch": "pnpm build && vitest watch" From 8b4eee810bcd058a8406839360374d8318e4389d Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sun, 8 Mar 2026 14:55:08 -0700 Subject: [PATCH 4/4] fix(vbare-compiler): preserve bare-ts tool compatibility --- typescript/pnpm-lock.yaml | 25 ++++++++++++++++++------- typescript/vbare-compiler/package.json | 2 +- typescript/vbare-compiler/src/index.ts | 11 ++++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index dbcf366..682ab70 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: vbare-compiler: dependencies: '@bare-ts/tools': - specifier: ^0.16.0 - version: 0.16.0(@bare-ts/lib@0.4.0) + specifier: ^0.13.0 + version: 0.13.0(@bare-ts/lib@0.3.0) commander: specifier: ^11.1.0 version: 11.1.0 @@ -100,19 +100,25 @@ packages: '@babel/helper-validator-identifier': 7.27.1 dev: true + /@bare-ts/lib@0.3.0: + resolution: {integrity: sha512-0JlIu0R26CTMdEnmmKIuFvAS9Cd554MvH4BjI6iL+74TDYBCHhaCWIDJXU6+VuYPu0+/JDLV/KXQstK9Sxn+9A==} + engines: {node: '>= 12'} + dev: false + /@bare-ts/lib@0.4.0: resolution: {integrity: sha512-uTb12lcDkvwwwGb/4atNy/+2Xuksx88tlxKuwnDuxmsOFVo4R/WsE+fmAf2nWtzHDi7m6NDw8Ke9j+XlNBLoJg==} engines: {node: ^14.18.0 || >=16.0.0} dev: false - /@bare-ts/tools@0.16.0(@bare-ts/lib@0.4.0): - resolution: {integrity: sha512-oBZ/GrrfNnsmm0x7/tn9r88MH3fH640cGG7TwgFyuFF/Cu1EgafwcJtXotNTOKAhcM4mH94QTbexQY+mvyTDGA==} - engines: {node: '>=20.0.0'} + /@bare-ts/tools@0.13.0(@bare-ts/lib@0.3.0): + resolution: {integrity: sha512-53fjq3/B3HeFJG6FygJbbdAoqvVnexdWjoi2O95p4karcyXUzest+AJ4ruf9ppk4bl28Jn0jkVegsJgeNZt4OQ==} + engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: - '@bare-ts/lib': '>=0.3.0 <=0.4.0' + '@bare-ts/lib': ~0.3.0 dependencies: - '@bare-ts/lib': 0.4.0 + '@bare-ts/lib': 0.3.0 + commander: 10.0.0 dev: false /@bcoe/v8-coverage@1.0.2: @@ -791,6 +797,11 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /commander@10.0.0: + resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} + engines: {node: '>=14'} + dev: false + /commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} diff --git a/typescript/vbare-compiler/package.json b/typescript/vbare-compiler/package.json index d2629fa..8624533 100644 --- a/typescript/vbare-compiler/package.json +++ b/typescript/vbare-compiler/package.json @@ -26,7 +26,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@bare-ts/tools": "^0.16.0", + "@bare-ts/tools": "^0.13.0", "commander": "^11.1.0" }, "devDependencies": { diff --git a/typescript/vbare-compiler/src/index.ts b/typescript/vbare-compiler/src/index.ts index 4c47bbf..a9fccf3 100644 --- a/typescript/vbare-compiler/src/index.ts +++ b/typescript/vbare-compiler/src/index.ts @@ -142,14 +142,15 @@ function postProcessGeneratedTs( } if (schemaVersion !== null) { - const defaultConfigLine = 'const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({})\n'; - if (!code.includes(defaultConfigLine)) { - throw new Error("Failed to find DEFAULT_CONFIG while injecting SCHEMA_VERSION"); + const configPattern = + /(const\s+\w+\s*=\s*\/\* @__PURE__ \*\/ bare\.Config\(\{\}\)\n)/; + if (!configPattern.test(code)) { + throw new Error("Failed to find bare.Config while injecting SCHEMA_VERSION"); } code = code.replace( - defaultConfigLine, - `${defaultConfigLine}\nexport const SCHEMA_VERSION = ${schemaVersion} as const\n`, + configPattern, + `$1\nexport const SCHEMA_VERSION = ${schemaVersion} as const\n`, ); }