diff --git a/.changeset/sync-template-versions.md b/.changeset/sync-template-versions.md new file mode 100644 index 00000000..fe63757f --- /dev/null +++ b/.changeset/sync-template-versions.md @@ -0,0 +1,7 @@ +--- +"create-effex": patch +--- + +Sync `@effex/*` dependency versions in scaffolded templates with the current workspace versions. Previously templates pinned `@effex/dom`, `@effex/router`, `@effex/platform`, and `@effex/vite-plugin` to `^0.0.1`, which (under semver's strict caret behavior for `0.x.x`) resolved to long-obsolete pre-1.0 versions. Generated projects now reference the current major (e.g. `^1.1.0` / `^1.2.0`). + +Adds `scripts/sync-template-versions.mjs` and wires it into the Changesets `version` script so template versions track workspace versions automatically on every release. diff --git a/package.json b/package.json index a044b816..6aca0de0 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "bench": "vitest bench", "typecheck": "tsc --noEmit", "changeset": "changeset", - "version": "changeset version", + "version": "changeset version && pnpm sync-template-versions", + "sync-template-versions": "node scripts/sync-template-versions.mjs", "release": "pnpm build && changeset publish", "docs:gen": "rm -rf docs && typedoc", "prepare": "husky" diff --git a/packages/create-effex/templates/spa/package.json b/packages/create-effex/templates/spa/package.json index f9894a18..f15c6c9e 100644 --- a/packages/create-effex/templates/spa/package.json +++ b/packages/create-effex/templates/spa/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@effex/dom": "^0.0.1", - "@effex/router": "^0.0.1", + "@effex/dom": "^1.1.0", + "@effex/router": "^1.2.0", "effect": "^3.19.13" }, "devDependencies": { diff --git a/packages/create-effex/templates/ssg/package.json b/packages/create-effex/templates/ssg/package.json index 8c15a91f..4da3d7c0 100644 --- a/packages/create-effex/templates/ssg/package.json +++ b/packages/create-effex/templates/ssg/package.json @@ -9,14 +9,14 @@ "preview": "vite preview" }, "dependencies": { - "@effex/dom": "^0.0.1", - "@effex/platform": "^0.0.1", - "@effex/router": "^0.0.1", + "@effex/dom": "^1.1.0", + "@effex/platform": "^1.2.0", + "@effex/router": "^1.2.0", "@effect/platform": "^0.94.0", "effect": "^3.19.13" }, "devDependencies": { - "@effex/vite-plugin": "^0.0.1", + "@effex/vite-plugin": "^1.1.0", "typescript": "~5.9.3", "vite": "^7.0.0" } diff --git a/packages/create-effex/templates/ssr/package.json b/packages/create-effex/templates/ssr/package.json index 6a8368f5..477acd8c 100644 --- a/packages/create-effex/templates/ssr/package.json +++ b/packages/create-effex/templates/ssr/package.json @@ -9,15 +9,15 @@ "start": "node --import tsx dist/server/server.js" }, "dependencies": { - "@effex/dom": "^0.0.1", - "@effex/platform": "^0.0.1", - "@effex/router": "^0.0.1", + "@effex/dom": "^1.1.0", + "@effex/platform": "^1.2.0", + "@effex/router": "^1.2.0", "@effect/platform": "^0.94.0", "@effect/platform-node": "^0.104.0", "effect": "^3.19.13" }, "devDependencies": { - "@effex/vite-plugin": "^0.0.1", + "@effex/vite-plugin": "^1.1.0", "tsx": "^4.19.0", "typescript": "~5.9.3", "vite": "^7.0.0" diff --git a/scripts/sync-template-versions.mjs b/scripts/sync-template-versions.mjs new file mode 100755 index 00000000..2cad5539 --- /dev/null +++ b/scripts/sync-template-versions.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env node +/** + * Sync @effex/* dependency versions in create-effex templates with the + * current workspace versions. + * + * Reads the version field from each workspace @effex package, then walks + * every package.json under packages/create-effex/templates/ and rewrites + * matching @effex/* entries under `dependencies` and `devDependencies` + * to `^MAJOR.MINOR.PATCH`. + * + * Run as part of the release flow (after `changeset version`) so generated + * project scaffolds always reference the most recently bumped versions. + * + * Idempotent: re-running on already-synced files is a no-op. + */ + +import { readFile, writeFile, readdir, stat } from "node:fs/promises"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const repoRoot = dirname(dirname(__filename)); + +const PACKAGES_DIR = join(repoRoot, "packages"); +const TEMPLATES_DIR = join( + repoRoot, + "packages", + "create-effex", + "templates", +); + +/** + * Read every workspace package.json under packages/ and return a map of + * { "@effex/": "1.2.3" } for those that publish under @effex/*. + */ +const readWorkspaceVersions = async () => { + const entries = await readdir(PACKAGES_DIR); + const versions = {}; + + for (const entry of entries) { + const pkgPath = join(PACKAGES_DIR, entry, "package.json"); + let raw; + try { + raw = await readFile(pkgPath, "utf8"); + } catch { + continue; // not every dir has a package.json + } + const pkg = JSON.parse(raw); + if (typeof pkg.name === "string" && pkg.name.startsWith("@effex/")) { + versions[pkg.name] = pkg.version; + } + } + + return versions; +}; + +/** + * Rewrite @effex/* entries in a deps object to ^version. Mutates input. + * Returns true if anything changed. + */ +const rewriteDepsBlock = (deps, versions) => { + if (!deps || typeof deps !== "object") return false; + let changed = false; + for (const name of Object.keys(deps)) { + if (!name.startsWith("@effex/")) continue; + const target = versions[name]; + if (!target) continue; // unknown @effex package — leave alone + const desired = `^${target}`; + if (deps[name] !== desired) { + deps[name] = desired; + changed = true; + } + } + return changed; +}; + +/** + * Find every package.json under templates/, rewrite @effex/* deps to + * pinned-to-workspace versions, and write back. Returns the list of + * files that were updated. + */ +const syncTemplates = async (versions) => { + const updated = []; + const templateDirs = await readdir(TEMPLATES_DIR); + + for (const dir of templateDirs) { + const templatePath = join(TEMPLATES_DIR, dir); + const info = await stat(templatePath); + if (!info.isDirectory()) continue; + + const pkgPath = join(templatePath, "package.json"); + let raw; + try { + raw = await readFile(pkgPath, "utf8"); + } catch { + continue; // not every template has a package.json (e.g. base/) + } + + const pkg = JSON.parse(raw); + const depsChanged = rewriteDepsBlock(pkg.dependencies, versions); + const devDepsChanged = rewriteDepsBlock(pkg.devDependencies, versions); + + if (!depsChanged && !devDepsChanged) continue; + + // Preserve trailing newline if present in original. + const trailingNewline = raw.endsWith("\n") ? "\n" : ""; + await writeFile( + pkgPath, + JSON.stringify(pkg, null, 2) + trailingNewline, + "utf8", + ); + updated.push(pkgPath); + } + + return updated; +}; + +const main = async () => { + const versions = await readWorkspaceVersions(); + const known = Object.entries(versions); + + if (known.length === 0) { + console.error("[sync-template-versions] No @effex/* packages found"); + process.exit(1); + } + + console.log("[sync-template-versions] Workspace versions:"); + for (const [name, ver] of known) { + console.log(` ${name} @ ${ver}`); + } + + const updated = await syncTemplates(versions); + + if (updated.length === 0) { + console.log("[sync-template-versions] All templates already in sync."); + return; + } + + console.log( + `[sync-template-versions] Updated ${updated.length} template package.json file(s):`, + ); + for (const file of updated) { + console.log(` ${file.replace(repoRoot + "/", "")}`); + } +}; + +main().catch((err) => { + console.error("[sync-template-versions] Failed:", err); + process.exit(1); +});