Skip to content

Commit 5c8527c

Browse files
fix(cli): vendor thread sources in standalone templates
1 parent f539b0c commit 5c8527c

4 files changed

Lines changed: 82 additions & 0 deletions

File tree

packages/cli/src/helpers/scaffold.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,24 @@ describe("scaffoldFromTemplate", () => {
198198
packageManager?: string;
199199
dependencies: Record<string, string>;
200200
overrides?: Record<string, string>;
201+
scripts?: Record<string, string>;
201202
};
202203

203204
expect(packageJson.packageManager).toBe("bun@1.3.1");
204205
expect(packageJson.dependencies["@better-auth/core"]).toBe("1.5.6");
205206
expect(packageJson.dependencies["@better-auth/electron"]).toBe("1.5.6");
206207
expect(packageJson.dependencies["better-auth"]).toBe("1.5.6");
208+
expect(packageJson.dependencies["@chatjs/thread"]).toBeUndefined();
207209
expect(packageJson.overrides?.["@better-auth/core"]).toBe("1.5.6");
210+
expect(packageJson.scripts?.prebuild).not.toContain("@chatjs/thread");
211+
expect(existsSync(join(destination, "lib", "thread", "react.ts"))).toBe(
212+
true,
213+
);
214+
const chatStoreSource = await readFile(
215+
join(destination, "lib", "stores", "with-threads.ts"),
216+
"utf8",
217+
);
218+
expect(chatStoreSource).toContain('from "@/lib/thread"');
208219
});
209220

210221
it("rewrites the generated web app to be npm-friendly", async () => {

packages/cli/src/helpers/scaffold.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
createEmptyUiTemplate,
1414
} from "../utils/inject-tool";
1515
import { runCommand } from "../utils/run-command";
16+
import { vendorThreadPackage } from "./vendor-thread-package";
1617

1718
const CHAT_APP_EXCLUDED_SEGMENTS = new Set([
1819
"node_modules",
@@ -207,6 +208,11 @@ async function applyChatTemplateSourceTransforms(
207208
};
208209
packageJson.packageManager = rootPackageJson.packageManager;
209210
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
211+
212+
await vendorThreadPackage({
213+
destination,
214+
threadSourceDir: join(getRepoRoot(), "packages", "thread", "src"),
215+
});
210216
}
211217

212218
async function applyElectronTemplateSourceTransforms(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { cp, readdir, readFile, rm, writeFile } from "node:fs/promises";
2+
import { extname, join } from "node:path";
3+
4+
const THREAD_IMPORT_REPLACEMENTS = [
5+
["@chatjs/thread/react", "@/lib/thread/react"],
6+
["@chatjs/thread", "@/lib/thread"],
7+
] as const;
8+
9+
async function rewriteThreadImports(directory: string): Promise<void> {
10+
const entries = await readdir(directory, { withFileTypes: true });
11+
12+
await Promise.all(
13+
entries.map(async (entry) => {
14+
const path = join(directory, entry.name);
15+
if (entry.isDirectory()) {
16+
await rewriteThreadImports(path);
17+
return;
18+
}
19+
if (!(entry.isFile() && [".ts", ".tsx"].includes(extname(entry.name)))) {
20+
return;
21+
}
22+
23+
const source = await readFile(path, "utf8");
24+
let rewritten = source;
25+
for (const [packageImport, localImport] of THREAD_IMPORT_REPLACEMENTS) {
26+
rewritten = rewritten.replaceAll(packageImport, localImport);
27+
}
28+
if (rewritten !== source) {
29+
await writeFile(path, rewritten);
30+
}
31+
}),
32+
);
33+
}
34+
35+
export async function vendorThreadPackage(options: {
36+
destination: string;
37+
threadSourceDir: string;
38+
}): Promise<void> {
39+
const localThreadDir = join(options.destination, "lib", "thread");
40+
await rm(localThreadDir, { recursive: true, force: true });
41+
await cp(options.threadSourceDir, localThreadDir, { recursive: true });
42+
await rewriteThreadImports(options.destination);
43+
44+
const packageJsonPath = join(options.destination, "package.json");
45+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
46+
dependencies?: Record<string, string>;
47+
scripts?: Record<string, string>;
48+
};
49+
if (packageJson.dependencies) {
50+
delete packageJson.dependencies["@chatjs/thread"];
51+
}
52+
if (packageJson.scripts?.prebuild) {
53+
packageJson.scripts.prebuild = packageJson.scripts.prebuild.replace(
54+
"bun --filter @chatjs/thread build && ",
55+
"",
56+
);
57+
}
58+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
59+
}

scripts/sync-template.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "node:fs/promises";
1212
import { tmpdir } from "node:os";
1313
import { join, relative, resolve, sep } from "node:path";
14+
import { vendorThreadPackage } from "../packages/cli/src/helpers/vendor-thread-package";
1415

1516
const rootDir = resolve(import.meta.dir, "..");
1617
const isCheck = process.argv.includes("--check");
@@ -142,6 +143,11 @@ async function applyTemplateTransforms(destination: string): Promise<void> {
142143
);
143144
await writeFile(globalsCssPath, globalsCss);
144145

146+
await vendorThreadPackage({
147+
destination,
148+
threadSourceDir: join(rootDir, "packages", "thread", "src"),
149+
});
150+
145151
// Stamp the template with the monorepo-controlled Bun version at build time.
146152
const rootPackageJson = JSON.parse(
147153
await readFile(rootPackageJsonPath, "utf8")

0 commit comments

Comments
 (0)