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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ dist/
node_modules/
coverage/
.typeforge/
.openapi-codegen/
test/fixtures/layouts/
*.tsbuildinfo
.tmp/
typeforge.local.json
openapi-codegen.local.json
.env
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="./assets/typeforge-logo.png" alt="Typeforge logo" width="280" />
<img src="https://raw.githubusercontent.com/openmirai/typeforge/HEAD/assets/typeforge-logo.png" alt="Typeforge logo" width="280" />
</p>

# @openmirai/typeforge
Expand All @@ -11,10 +11,6 @@ Headless **OpenAPI / Swagger → TypeScript** codegen. The CLI is `typeforge`. I

You own `http.ts` (the `HTTPFetch` adapter). Generated files import that adapter — they do not invent axios/fetch calls inline.

## Migrating from `@openmirai/openapi-codegen`

Install `@openmirai/typeforge` and update package imports and scripts to use the canonical `typeforge` name. During migration, the package also exposes the legacy `openapi-codegen` binary and reads `openapi-codegen.json`, `openapi-codegen.local.json`, and the `openapiCodegen` package.json key. New projects created by `typeforge init` use the Typeforge names.

## What it generates

For each **source** (a named API, e.g. `atlas`), under `<apiRoot>/<source>/generated/`:
Expand Down
2 changes: 0 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

Install `@openmirai/typeforge` from [npmjs](https://www.npmjs.com/package/@openmirai/typeforge). Add a script so the binary resolves from `node_modules/.bin`:

The legacy `openapi-codegen` binary and configuration filenames remain readable during migration, but all new usage should use `typeforge`.

```json
{
"scripts": {
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openmirai/typeforge",
"version": "0.1.8",
"version": "0.2.0",
"description": "Typeforge: headless OpenAPI to TypeScript codegen CLI and HTTPFetch runtime",
"homepage": "https://github.com/openmirai/typeforge#readme",
"bugs": {
Expand All @@ -12,11 +12,9 @@
"url": "git+https://github.com/openmirai/typeforge.git"
},
"bin": {
"openapi-codegen": "./dist/cli.js",
"typeforge": "./dist/cli.js"
},
"files": [
"assets/typeforge-logo.png",
"dist"
],
"type": "module",
Expand Down Expand Up @@ -59,6 +57,7 @@
},
"scripts": {
"build": "tsdown",
"check:package": "node scripts/check-package.mjs",
"typecheck": "tsc -p tsconfig.json --pretty false && tsc -p tsconfig.tools.json --pretty false",
"format": "oxfmt --check .",
"format:fix": "oxfmt --write .",
Expand All @@ -69,7 +68,7 @@
"test:coverage": "vitest run --coverage",
"prepare": "husky",
"release": "release-it",
"verify": "pnpm format && pnpm lint && pnpm typecheck && pnpm build && pnpm test:coverage"
"verify": "pnpm format && pnpm lint && pnpm typecheck && pnpm build && pnpm check:package && pnpm test:coverage"
},
"dependencies": {
"chalk": "5.6.2"
Expand Down
47 changes: 47 additions & 0 deletions scripts/check-package.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { execFileSync } from "node:child_process";

const output = execFileSync(
"npm",
["pack", "--dry-run", "--json", "--ignore-scripts"],
{ encoding: "utf8" }
);
const [pack] = JSON.parse(output);

if (pack === undefined) {
throw new Error("npm pack did not return package metadata");
}

const paths = pack.files.map((file) => file.path);
const unexpected = paths.filter(
(path) =>
path !== "README.md" && path !== "package.json" && !path.startsWith("dist/")
);
const sourceMaps = paths.filter((path) => path.endsWith(".map"));

if (unexpected.length > 0) {
throw new Error(`Unexpected package files: ${unexpected.join(", ")}`);
}
if (sourceMaps.length > 0) {
throw new Error(
`Source maps must not be published: ${sourceMaps.join(", ")}`
);
}
if (pack.entryCount > 20) {
throw new Error(
`Package contains ${pack.entryCount} files; expected at most 20`
);
}
if (pack.size > 75_000) {
throw new Error(
`Package tarball is ${pack.size} bytes; expected at most 75000`
);
}
if (pack.unpackedSize > 125_000) {
throw new Error(
`Package is ${pack.unpackedSize} unpacked bytes; expected at most 125000`
);
}

process.stdout.write(
`typeforge package: ${pack.size} packed bytes, ${pack.unpackedSize} unpacked bytes, ${pack.entryCount} files\n`
);
35 changes: 1 addition & 34 deletions src/config/__tests__/load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function sortedStrings(values: Array<string>): Array<string> {
}

describe("config/load", () => {
it("prefers typeforge.json over legacy and package config", () => {
it("prefers typeforge.json over package config", () => {
const cwd = join(
process.cwd(),
"test/fixtures/layouts",
Expand All @@ -27,11 +27,6 @@ describe("config/load", () => {
JSON.stringify({ apiRoot: "packages/utils/src/api" }),
"utf8"
);
writeFileSync(
join(cwd, "openapi-codegen.json"),
JSON.stringify({ apiRoot: "legacy" }),
"utf8"
);
writeFileSync(
join(cwd, "package.json"),
JSON.stringify({ typeforge: { apiRoot: "ignored" } }),
Expand All @@ -41,34 +36,6 @@ describe("config/load", () => {
expect(loadProjectConfig(cwd).apiRoot).toBe("packages/utils/src/api");
});

it("reads legacy project config during migration", () => {
const jsonCwd = join(
process.cwd(),
"test/fixtures/layouts",
`config-legacy-json-${Date.now()}`
);
mkdirSync(jsonCwd, { recursive: true });
writeFileSync(
join(jsonCwd, "openapi-codegen.json"),
JSON.stringify({ apiRoot: "legacy/json" }),
"utf8"
);
expect(loadProjectConfig(jsonCwd).apiRoot).toBe("legacy/json");

const packageCwd = join(
process.cwd(),
"test/fixtures/layouts",
`config-legacy-package-${Date.now()}`
);
mkdirSync(packageCwd, { recursive: true });
writeFileSync(
join(packageCwd, "package.json"),
JSON.stringify({ openapiCodegen: { apiRoot: "legacy/package" } }),
"utf8"
);
expect(loadProjectConfig(packageCwd).apiRoot).toBe("legacy/package");
});

it("parses source.ts config fields", () => {
const cwd = join(
process.cwd(),
Expand Down
9 changes: 2 additions & 7 deletions src/config/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function readPackageConfig(path: string): TypeforgeConfig {

try {
const raw = readJsonObject(readFileSync(path, "utf8"));
const typeforge = raw["typeforge"] ?? raw["openapiCodegen"];
const typeforge = raw["typeforge"];
if (
typeof typeforge !== "object" ||
typeforge === null ||
Expand Down Expand Up @@ -201,15 +201,10 @@ function parseQueryExtends(content: string): QueryExtendsConfig | undefined {

export function loadProjectConfig(cwd: string): TypeforgeConfig {
const fromJson = readOptionalJson(resolve(cwd, "typeforge.json"));
const fromLegacyJson = readOptionalJson(resolve(cwd, "openapi-codegen.json"));
const fromPackage = readPackageConfig(resolve(cwd, "package.json"));

return {
apiRoot:
fromJson.apiRoot ??
fromLegacyJson.apiRoot ??
fromPackage.apiRoot ??
DEFAULT_API_ROOT,
apiRoot: fromJson.apiRoot ?? fromPackage.apiRoot ?? DEFAULT_API_ROOT,
};
}

Expand Down
3 changes: 0 additions & 3 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ export interface TypeforgeConfig {
apiRoot?: string;
}

/** @deprecated Use `TypeforgeConfig`. */
export type OpenApiCodegenConfig = TypeforgeConfig;

export type GenerationMode = "authoritative" | "merge";

export type NamingStrategy = "path" | "operationId";
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export { defineSourceConfig } from "./config/define";
export type {
GenerationMode,
NamingStrategy,
OpenApiCodegenConfig,
TypeforgeConfig,
QueryExtendsConfig,
SourceConfig,
Expand Down
10 changes: 4 additions & 6 deletions src/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,10 @@ export function initProject(options: InitOptions): {
const cwd = options.cwd ?? process.cwd();
const layout = options.layout ?? "monolith";
const configPath = resolve(cwd, "typeforge.json");
const legacyConfigPath = resolve(cwd, "openapi-codegen.json");
const projectConfig = loadProjectConfig(cwd);
const apiRoot =
existsSync(configPath) || existsSync(legacyConfigPath)
? (projectConfig.apiRoot ?? DEFAULT_API_ROOT)
: defaultApiRoot(layout);
const apiRoot = existsSync(configPath)
? (projectConfig.apiRoot ?? DEFAULT_API_ROOT)
: defaultApiRoot(layout);
const apiRootPath = resolve(cwd, apiRoot);
const sourceDir = join(apiRootPath, options.sourceKey);

Expand Down Expand Up @@ -200,7 +198,7 @@ export function initProject(options: InitOptions): {
}

const configWritePath = resolve(cwd, "typeforge.json");
if (!existsSync(configWritePath) && !existsSync(legacyConfigPath)) {
if (!existsSync(configWritePath)) {
const config: TypeforgeConfig = { apiRoot };
writeFileSync(
configWritePath,
Expand Down
8 changes: 1 addition & 7 deletions src/parser/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,7 @@ export function resolveSpecSource(
}

// 4. Local override file
const defaultLocalPath = "./typeforge.local.json";
const legacyLocalPath = "./openapi-codegen.local.json";
const localPath =
opts.localOverridePath ??
(existsSync(defaultLocalPath) || !existsSync(legacyLocalPath)
? defaultLocalPath
: legacyLocalPath);
const localPath = opts.localOverridePath ?? "./typeforge.local.json";
if (existsSync(localPath)) {
try {
const localData = readJsonObject(readFileSync(localPath, "utf8")),
Expand Down
23 changes: 0 additions & 23 deletions test/e2e/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,6 @@ describe("e2e cli init", () => {
"// keep me"
);
});

it("uses legacy project config without writing a duplicate", () => {
const root = join(fixtureRoot, "layouts", `cli-init-legacy-${Date.now()}`);
tempRoots.push(root);
mkdirSync(root, { recursive: true });
writeFileSync(
join(root, "openapi-codegen.json"),
JSON.stringify({ apiRoot: "legacy/api" }),
"utf8"
);

const result = runCli(root, [
"init",
"--source",
"atlas",
"--client",
"fetch",
]);

expect(result.exitCode).toBe(0);
expect(existsSync(join(root, "legacy/api/http.ts"))).toBe(true);
expect(existsSync(join(root, "typeforge.json"))).toBe(false);
});
});

describe("e2e cli generate", () => {
Expand Down
7 changes: 3 additions & 4 deletions tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ const shared = {
deps: {
neverBundle: true,
},
dts: {
sourcemap: true,
},
dts: true,
fixedExtension: false,
format: "esm",
minify: true,
outDir: "dist",
sourcemap: true,
sourcemap: false,
target: "es2022",
treeshake: true,
tsconfig: "tsconfig.json",
Expand Down
Loading