Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
"npmPublish": true
}
],
[
"@semantic-release/github",
{}
],
["@semantic-release/github", {}],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json"],
"assets": ["CHANGELOG.md", "package.json", "package-lock.json", "src/cli-core.ts"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 27 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,34 @@
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./react": {
"types": "./dist/react/index.d.ts",
"import": "./dist/react/index.js",
"require": "./dist/react/index.cjs"
"import": {
"types": "./dist/react/index.d.ts",
"default": "./dist/react/index.js"
},
"require": {
"types": "./dist/react/index.d.cts",
"default": "./dist/react/index.cjs"
}
},
"./zod": {
"types": "./dist/zod/index.d.ts",
"import": "./dist/zod/index.js",
"require": "./dist/zod/index.cjs"
"import": {
"types": "./dist/zod/index.d.ts",
"default": "./dist/zod/index.js"
},
"require": {
"types": "./dist/zod/index.d.cts",
"default": "./dist/zod/index.cjs"
}
},
"./package.json": "./package.json"
},
Expand All @@ -66,7 +81,7 @@
},
"packageManager": "npm@10.0.0",
"scripts": {
"build": "tsup && node scripts/chmod-cli.mjs",
"build": "node scripts/clean-dist.mjs && tsup && node scripts/chmod-cli.mjs",
"dev": "tsup --watch",
"test": "vitest run",
"test:watch": "vitest",
Expand All @@ -82,7 +97,8 @@
"prepublishOnly": "npm run build",
"preflight": "npm run lint && npm run typecheck && npm run test:coverage && npm run build",
"release:dry-run": "semantic-release --dry-run --no-ci",
"semantic-release": "semantic-release"
"semantic-release": "semantic-release",
"version": "node scripts/sync-version.mjs"
},
"peerDependencies": {
"react": ">=17.0.0",
Expand Down
8 changes: 8 additions & 0 deletions scripts/clean-dist.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Removes dist/ before a build. tsup runs the configs in tsup.config.ts in
// parallel, so letting one of them `clean` could delete another's output.
import { rmSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
rmSync(path.join(__dirname, "..", "dist"), { recursive: true, force: true });
24 changes: 24 additions & 0 deletions scripts/sync-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Keeps CLI_VERSION in src/cli-core.ts in sync with package.json.
//
// Runs as the npm `version` lifecycle script, i.e. whenever `npm version`
// bumps package.json (semantic-release's @semantic-release/npm does exactly
// that in its prepare step). The publish step then rebuilds dist via
// `prepublishOnly`, so `forgedata --version` reports the released version,
// and @semantic-release/git commits the updated source file.
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

const root = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
const { version } = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
const file = path.join(root, "src", "cli-core.ts");
const source = readFileSync(file, "utf8");
const pattern = /export const CLI_VERSION = "[^"]*";/;

if (!pattern.test(source)) {
console.error(`sync-version: could not find CLI_VERSION in ${file}`);
process.exit(1);
}

writeFileSync(file, source.replace(pattern, `export const CLI_VERSION = "${version}";`));
console.log(`sync-version: CLI_VERSION set to ${version}`);
9 changes: 5 additions & 4 deletions src/cli-core.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ForgeData } from "./ForgeData.js";

// Kept as a plain constant rather than read from package.json at runtime:
// JSON import assertions are still inconsistent across Node/Bun/Deno/bundlers,
// and this is purely cosmetic output for `--version`. Bump it alongside the
// version field in package.json on release.
export const CLI_VERSION = "0.2.0";
// JSON import assertions are still inconsistent across Node/Bun/Deno/bundlers.
// It is rewritten automatically by scripts/sync-version.mjs (the npm `version`
// lifecycle script, run by semantic-release when it bumps package.json), and
// tests/cli.test.ts asserts it matches package.json.
export const CLI_VERSION = "0.2.1";

export interface ParsedArgs {
command?: string;
Expand Down
10 changes: 10 additions & 0 deletions tests/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { parseArgs, runCli, CLI_VERSION, HELP_TEXT, type CliIO } from "../src/cli-core.js";

Expand Down Expand Up @@ -42,6 +43,15 @@ describe("parseArgs", () => {
});
});

describe("CLI_VERSION", () => {
it("matches the version in package.json", () => {
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as {
version: string;
};
expect(CLI_VERSION).toBe(pkg.version);
});
});

describe("runCli", () => {
it("prints the version for --version, -v, or the version command", () => {
for (const argv of [["--version"], ["-v"], ["version"]]) {
Expand Down
44 changes: 15 additions & 29 deletions tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,29 @@
import { defineConfig } from "tsup";

// dist/ is wiped by scripts/clean-dist.mjs before tsup runs: tsup builds these
// configs in parallel, so `clean: true` in one could delete another's output.

export default defineConfig([
{
entry: { index: "src/index.ts" },
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
clean: true,
splitting: false,
treeshake: true,
target: "es2020",
outExtension({ format }) {
return { js: format === "cjs" ? ".cjs" : ".js" };
},
},
{
entry: { "react/index": "src/react/index.tsx" },
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
clean: false,
splitting: false,
treeshake: true,
target: "es2020",
external: ["react"],
outExtension({ format }) {
return { js: format === "cjs" ? ".cjs" : ".js" };
// The library entry points are built together (with code splitting) so
// that `forgedatajs`, `forgedatajs/react` and `forgedatajs/zod` share one
// copy of the ForgeData class at runtime *and* in the emitted .d.ts files.
// Building them separately inlined a private copy of ForgeData into each
// subpath, so e.g. `fromZodSchema(new ForgeData(), schema)` failed to
// type-check (and `instanceof ForgeData` differed between entries).
entry: {
index: "src/index.ts",
"react/index": "src/react/index.tsx",
"zod/index": "src/zod/index.ts",
},
},
{
entry: { "zod/index": "src/zod/index.ts" },
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
clean: false,
splitting: false,
splitting: true,
treeshake: true,
target: "es2020",
external: ["zod"],
external: ["react", "zod"],
outExtension({ format }) {
return { js: format === "cjs" ? ".cjs" : ".js" };
},
Expand Down
Loading