@xemahq/api-client-generator 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/client-package.d.ts.map +1 -1
- package/dist/lib/client-package.js +1 -1
- package/dist/lib/client-package.js.map +1 -1
- package/package.json +3 -4
- package/src/bin/xema-client-generate.ts +0 -11
- package/src/bin/xema-openapi.ts +0 -51
- package/src/index.ts +0 -9
- package/src/lib/client-package.ts +0 -232
- package/src/lib/client-readme.ts +0 -124
- package/src/lib/custom-fetch-template.ts +0 -198
- package/src/lib/default-stubs.ts +0 -422
- package/src/lib/extract-openapi.ts +0 -240
- package/src/lib/generate-clients.ts +0 -240
- package/src/lib/overrides.ts +0 -81
- package/src/lib/repo-post-process.ts +0 -106
- package/src/lib/seed-openapi-env.ts +0 -136
- package/src/lib/service-config.ts +0 -170
- package/src/lib/service-nest.ts +0 -58
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-package.d.ts","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AA6CD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAqBR;AASD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,UAAU,GAClB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"client-package.d.ts","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AA6CD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAqBR;AASD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,UAAU,GAClB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAoF1C"}
|
|
@@ -84,7 +84,7 @@ function ensureClientPackage(ctx, surface) {
|
|
|
84
84
|
version: resolveMonotonicVersion(existing.version, ctx.version),
|
|
85
85
|
main: './dist/index.js',
|
|
86
86
|
types: './dist/index.d.ts',
|
|
87
|
-
files: ['dist'
|
|
87
|
+
files: ['dist'],
|
|
88
88
|
scripts: {
|
|
89
89
|
...existingScripts,
|
|
90
90
|
build: 'tsc -p tsconfig.json',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-package.js","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":";;AAwEA,0DAwBC;AASD,
|
|
1
|
+
{"version":3,"file":"client-package.js","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/api-client-generator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Layer 1 — the single, central OpenAPI + ORVAL client generator for every Xema NestJS service. Replaces the per-service orval.config.ts / scripts/extract-openapi.ts / scripts/build-client-index.js trio with two bins (xema-openapi, xema-client-generate). Extracts one OpenAPI document per ApiSurface (public/internal/operator) using the @XemaApiSurface decorator metadata, generates one ORVAL client package per non-empty surface (all sharing the owning service's version), and stamps deterministic `xema` provenance metadata into each generated client package.json. No heuristics: a route ships in a surface only if it (or its controller) is explicitly classified.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xema",
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
"xema-client-generate": "dist/bin/xema-client-generate.js"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
|
-
"dist"
|
|
34
|
-
"src"
|
|
33
|
+
"dist"
|
|
35
34
|
],
|
|
36
35
|
"peerDependencies": {
|
|
37
36
|
"@nestjs/common": "^10 || ^11",
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
"prettier": "3.6.2",
|
|
64
63
|
"reflect-metadata": "0.2.2",
|
|
65
64
|
"typescript": "5.9.3",
|
|
66
|
-
"@xemahq/xema-decorators": "0.5.
|
|
65
|
+
"@xemahq/xema-decorators": "0.5.4"
|
|
67
66
|
},
|
|
68
67
|
"exports": {
|
|
69
68
|
".": {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* `xema-client-generate` — generate (and compile) one ORVAL client package per
|
|
4
|
-
* surface that has an `openapi.<surface>.json` spec in the current working
|
|
5
|
-
* directory. Run AFTER `xema-openapi`.
|
|
6
|
-
*
|
|
7
|
-
* Service package.json: `"client:generate": "xema-client-generate"`.
|
|
8
|
-
*/
|
|
9
|
-
import { runClientGenerateCli } from '../lib/generate-clients';
|
|
10
|
-
|
|
11
|
-
runClientGenerateCli();
|
package/src/bin/xema-openapi.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* `xema-openapi` — extract one OpenAPI document per ApiSurface from the NestJS
|
|
4
|
-
* service in the current working directory.
|
|
5
|
-
*
|
|
6
|
-
* Run from a service dir (where package.json carries `xema.api`). Self-registers
|
|
7
|
-
* ts-node + tsconfig-paths from the SERVICE's own toolchain so the service's TS
|
|
8
|
-
* `AppModule` compiles on import, then delegates to the generator.
|
|
9
|
-
*
|
|
10
|
-
* Service package.json: `"openapi": "NODE_PATH=node_modules xema-openapi"`.
|
|
11
|
-
*/
|
|
12
|
-
// `node:module` is a side-effect-free builtin, so a static import is
|
|
13
|
-
// equivalent to the `require()` this used to do (the file compiles to
|
|
14
|
-
// CommonJS) — and it drops an inline `import()` type annotation whose only
|
|
15
|
-
// job was to type that `require`. The DYNAMIC requires below are different:
|
|
16
|
-
// they resolve paths computed at runtime, after `ts-node.register()` has
|
|
17
|
-
// patched the require hook, and must stay `require()`.
|
|
18
|
-
import { Module } from 'node:module';
|
|
19
|
-
import { delimiter } from 'node:path';
|
|
20
|
-
|
|
21
|
-
function registerTsNode(cwd: string): void {
|
|
22
|
-
const tsNode = require.resolve('ts-node', { paths: [cwd] });
|
|
23
|
-
|
|
24
|
-
(require(tsNode) as { register: (o: unknown) => void }).register({
|
|
25
|
-
transpileOnly: true,
|
|
26
|
-
});
|
|
27
|
-
try {
|
|
28
|
-
const paths = require.resolve('tsconfig-paths/register', { paths: [cwd] });
|
|
29
|
-
require(paths);
|
|
30
|
-
} catch {
|
|
31
|
-
// tsconfig-paths is optional — services without path aliases don't need it.
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ensureNodePath(): void {
|
|
36
|
-
process.env.NODE_PATH = process.env.NODE_PATH
|
|
37
|
-
? `node_modules${delimiter}${process.env.NODE_PATH}`
|
|
38
|
-
: 'node_modules';
|
|
39
|
-
(Module as unknown as { _initPaths: () => void })._initPaths();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
ensureNodePath();
|
|
43
|
-
registerTsNode(process.cwd());
|
|
44
|
-
|
|
45
|
-
// Require AFTER ts-node is registered so the service's TS modules compile.
|
|
46
|
-
|
|
47
|
-
const { runOpenApiCli } = require('../lib/extract-openapi') as {
|
|
48
|
-
runOpenApiCli: () => void;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
runOpenApiCli();
|
package/src/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Public programmatic surface of the central API-client generator.
|
|
2
|
-
export * from './lib/service-config';
|
|
3
|
-
export * from './lib/overrides';
|
|
4
|
-
export * from './lib/seed-openapi-env';
|
|
5
|
-
export * from './lib/default-stubs';
|
|
6
|
-
export * from './lib/extract-openapi';
|
|
7
|
-
export * from './lib/repo-post-process';
|
|
8
|
-
export * from './lib/client-package';
|
|
9
|
-
export * from './lib/generate-clients';
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cpSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
} from 'node:fs';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
|
|
10
|
-
import { ApiSurface } from '@xemahq/xema-decorators';
|
|
11
|
-
|
|
12
|
-
import { CANONICAL_CUSTOM_FETCH } from './custom-fetch-template';
|
|
13
|
-
import {
|
|
14
|
-
surfaceClientDir,
|
|
15
|
-
surfaceClientPackageName,
|
|
16
|
-
type ResolvedServiceContext,
|
|
17
|
-
} from './service-config';
|
|
18
|
-
|
|
19
|
-
/** The authoritative package→Xema mapping stamped into each generated client. */
|
|
20
|
-
export interface GeneratedClientXemaBlock {
|
|
21
|
-
readonly kind: 'api-client';
|
|
22
|
-
readonly surface: ApiSurface;
|
|
23
|
-
readonly service: string;
|
|
24
|
-
readonly biome: string;
|
|
25
|
-
readonly target: 'server';
|
|
26
|
-
readonly generator: string;
|
|
27
|
-
readonly source: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const CLIENT_TSCONFIG = {
|
|
31
|
-
compilerOptions: {
|
|
32
|
-
target: 'ES2022',
|
|
33
|
-
module: 'commonjs',
|
|
34
|
-
lib: ['ES2022', 'DOM'],
|
|
35
|
-
types: [],
|
|
36
|
-
strict: true,
|
|
37
|
-
esModuleInterop: true,
|
|
38
|
-
skipLibCheck: true,
|
|
39
|
-
declaration: true,
|
|
40
|
-
outDir: './dist',
|
|
41
|
-
rootDir: './src',
|
|
42
|
-
},
|
|
43
|
-
include: ['src/**/*.ts'],
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
function generatorTag(): string {
|
|
47
|
-
const pkg = require('../../package.json') as { version: string };
|
|
48
|
-
return `@xemahq/api-client-generator@${pkg.version}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function recordOrEmpty(value: unknown): Record<string, unknown> {
|
|
52
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
53
|
-
? Object.fromEntries(Object.entries(value))
|
|
54
|
-
: {};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Parse a release semver core `x.y.z` (any prerelease/build suffix ignored). */
|
|
58
|
-
function parseVersionCore(v: string): [number, number, number] | null {
|
|
59
|
-
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
60
|
-
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Resolve the client `version` MONOTONICALLY. A published client MUST NEVER be
|
|
65
|
-
* downgraded by regeneration: we keep whichever of the existing client version
|
|
66
|
-
* and the service version is greater. A forward bump happens only when the
|
|
67
|
-
* service version genuinely exceeds the client's (an intentional, monotonic
|
|
68
|
-
* move). If either version is unparseable, the existing version is kept — the
|
|
69
|
-
* fail-safe default is "never downgrade".
|
|
70
|
-
*
|
|
71
|
-
* A fresh client (no existing version) adopts the service version.
|
|
72
|
-
*/
|
|
73
|
-
export function resolveMonotonicVersion(
|
|
74
|
-
existing: unknown,
|
|
75
|
-
serviceVersion: string,
|
|
76
|
-
): string {
|
|
77
|
-
if (typeof existing !== 'string' || existing.trim() === '') {
|
|
78
|
-
return serviceVersion;
|
|
79
|
-
}
|
|
80
|
-
const e = parseVersionCore(existing);
|
|
81
|
-
const s = parseVersionCore(serviceVersion);
|
|
82
|
-
if (!e || !s) {
|
|
83
|
-
return existing;
|
|
84
|
-
}
|
|
85
|
-
const [eMaj, eMin, ePat] = e;
|
|
86
|
-
const [sMaj, sMin, sPat] = s;
|
|
87
|
-
if (sMaj !== eMaj) {
|
|
88
|
-
return sMaj > eMaj ? serviceVersion : existing;
|
|
89
|
-
}
|
|
90
|
-
if (sMin !== eMin) {
|
|
91
|
-
return sMin > eMin ? serviceVersion : existing;
|
|
92
|
-
}
|
|
93
|
-
if (sPat !== ePat) {
|
|
94
|
-
return sPat > ePat ? serviceVersion : existing;
|
|
95
|
-
}
|
|
96
|
-
return existing;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Ensure the surface client package exists and carries the canonical stable
|
|
101
|
-
* metadata + the authoritative `xema` provenance block. Preserves any
|
|
102
|
-
* pre-existing `dependencies`/`peerDependencies` on an existing client.
|
|
103
|
-
*
|
|
104
|
-
* Returns the absolute client `src` directory (orval's output target).
|
|
105
|
-
*/
|
|
106
|
-
export function ensureClientPackage(
|
|
107
|
-
ctx: ResolvedServiceContext,
|
|
108
|
-
surface: ApiSurface,
|
|
109
|
-
): { clientDir: string; clientSrc: string } {
|
|
110
|
-
const clientDir = surfaceClientDir(ctx, surface);
|
|
111
|
-
const clientSrc = join(clientDir, 'src');
|
|
112
|
-
mkdirSync(clientSrc, { recursive: true });
|
|
113
|
-
|
|
114
|
-
const packageName = surfaceClientPackageName(ctx.api.serviceId, surface);
|
|
115
|
-
const xema: GeneratedClientXemaBlock = {
|
|
116
|
-
kind: 'api-client',
|
|
117
|
-
surface,
|
|
118
|
-
service: ctx.api.serviceId,
|
|
119
|
-
biome: ctx.api.biomeId,
|
|
120
|
-
target: 'server',
|
|
121
|
-
generator: generatorTag(),
|
|
122
|
-
source: `openapi.${surface}.json`,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const pkgPath = join(clientDir, 'package.json');
|
|
126
|
-
const existing = existsSync(pkgPath)
|
|
127
|
-
? (JSON.parse(readFileSync(pkgPath, 'utf8')) as Record<string, unknown>)
|
|
128
|
-
: {};
|
|
129
|
-
const existingScripts = recordOrEmpty(existing.scripts);
|
|
130
|
-
const existingDevDeps = recordOrEmpty(existing.devDependencies);
|
|
131
|
-
const existingPublishConfig = recordOrEmpty(existing.publishConfig);
|
|
132
|
-
// Start from the existing (hand-maintained) package and override ONLY the
|
|
133
|
-
// fields the generator is explicitly responsible for. Every other field
|
|
134
|
-
// authored by hand — `dependencies` (e.g. zod), `peerDependencies`, extra
|
|
135
|
-
// scripts (`clean`/`typecheck`), extra devDependencies (`@types/node`),
|
|
136
|
-
// license/repository/exports/etc. — is preserved verbatim. Regeneration is
|
|
137
|
-
// metadata-stable: it never silently drops hand-maintained config and, per
|
|
138
|
-
// {@link resolveMonotonicVersion}, never downgrades the package version.
|
|
139
|
-
const pkg: Record<string, unknown> = {
|
|
140
|
-
...existing,
|
|
141
|
-
name: packageName,
|
|
142
|
-
version: resolveMonotonicVersion(existing.version, ctx.version),
|
|
143
|
-
main: './dist/index.js',
|
|
144
|
-
types: './dist/index.d.ts',
|
|
145
|
-
// `!dist/**/*.map` is not decoration. Every base tsconfig in this fleet sets
|
|
146
|
-
// `declarationMap` + `sourceMap`, so every generated client emits `.map`
|
|
147
|
-
// files whose `sources` name `../../../src/*.ts` — and a client's `src` is
|
|
148
|
-
// gitignored and never packed. Shipping the maps alone gives a debugger a
|
|
149
|
-
// pointer that resolves to nothing, plus dead weight (one client carried
|
|
150
|
-
// 274 of them, half its tarball).
|
|
151
|
-
//
|
|
152
|
-
// Declared HERE rather than in each client, because this field is
|
|
153
|
-
// generator-OWNED: the comment above promises hand-maintained config is
|
|
154
|
-
// preserved, and it is — but `files` is on the override list, so a manifest
|
|
155
|
-
// fixed by hand is silently reverted on the next `client:generate`. That
|
|
156
|
-
// happened: 143 manifests were corrected and ~140 of them would have
|
|
157
|
-
// regressed on the next regeneration.
|
|
158
|
-
//
|
|
159
|
-
// The inclusion stays plain `dist`, NOT `dist/**/*`: pnpm's negation
|
|
160
|
-
// matching is depth-broken against a glob inclusion — nested maps leak while
|
|
161
|
-
// top-level ones are excluded — and `pnpm pack` is what actually publishes.
|
|
162
|
-
files: ['dist', '!dist/**/*.map'],
|
|
163
|
-
scripts: {
|
|
164
|
-
...existingScripts,
|
|
165
|
-
// Generator-owned: the client must build and build-before-publish.
|
|
166
|
-
build: 'tsc -p tsconfig.json',
|
|
167
|
-
prepublishOnly: 'npm run build',
|
|
168
|
-
},
|
|
169
|
-
// This block used to be replaced WHOLESALE, which silently dropped
|
|
170
|
-
// `access: "public"` from every client on every regeneration — contradicting
|
|
171
|
-
// the promise three lines above. npm refuses to publish a SCOPED package
|
|
172
|
-
// without it, so each regeneration quietly armed a publish failure, and the
|
|
173
|
-
// fix (re-adding `access`) had to be re-applied by hand every time.
|
|
174
|
-
//
|
|
175
|
-
// `access` defaults to `public` because every generated @xemahq client is a
|
|
176
|
-
// public package; a hand-authored override still wins. `registry` stays
|
|
177
|
-
// generator-owned.
|
|
178
|
-
publishConfig: {
|
|
179
|
-
access: 'public',
|
|
180
|
-
...existingPublishConfig,
|
|
181
|
-
registry: 'https://registry.npmjs.org/',
|
|
182
|
-
},
|
|
183
|
-
devDependencies: {
|
|
184
|
-
...existingDevDeps,
|
|
185
|
-
// Generator-owned: tsc is required to build the generated client.
|
|
186
|
-
typescript: 'catalog:',
|
|
187
|
-
},
|
|
188
|
-
// Generator-owned: the authoritative provenance block is always rewritten.
|
|
189
|
-
xema,
|
|
190
|
-
};
|
|
191
|
-
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
192
|
-
|
|
193
|
-
const tsconfigPath = join(clientDir, 'tsconfig.json');
|
|
194
|
-
if (!existsSync(tsconfigPath)) {
|
|
195
|
-
writeFileSync(
|
|
196
|
-
tsconfigPath,
|
|
197
|
-
`${JSON.stringify(CLIENT_TSCONFIG, null, 2)}\n`,
|
|
198
|
-
'utf8',
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
ensureCustomFetch(ctx, clientSrc);
|
|
203
|
-
|
|
204
|
-
return { clientDir, clientSrc };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* custom-fetch.ts is hand-maintained runtime config, identical across all
|
|
209
|
-
* clients. For an existing client it is left untouched. For a freshly
|
|
210
|
-
* scaffolded surface client we copy the service's public-surface sibling's
|
|
211
|
-
* custom-fetch when present (the most authoritative local copy), else write
|
|
212
|
-
* the canonical embedded template.
|
|
213
|
-
*/
|
|
214
|
-
function ensureCustomFetch(
|
|
215
|
-
ctx: ResolvedServiceContext,
|
|
216
|
-
clientSrc: string,
|
|
217
|
-
): void {
|
|
218
|
-
const target = join(clientSrc, 'custom-fetch.ts');
|
|
219
|
-
if (existsSync(target)) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const publicSibling = join(
|
|
223
|
-
surfaceClientDir(ctx, ApiSurface.Public),
|
|
224
|
-
'src',
|
|
225
|
-
'custom-fetch.ts',
|
|
226
|
-
);
|
|
227
|
-
if (existsSync(publicSibling)) {
|
|
228
|
-
cpSync(publicSibling, target);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
writeFileSync(target, CANONICAL_CUSTOM_FETCH, 'utf8');
|
|
232
|
-
}
|
package/src/lib/client-readme.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { ApiSurface } from '@xemahq/xema-decorators';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generated API clients carry a README emitted from the canonical package-README
|
|
8
|
-
* template (see the `write-package-readme` skill). It is fully generator-owned —
|
|
9
|
-
* never hand-edit a generated client's README; change this emitter and
|
|
10
|
-
* regenerate. The content is derived ONLY from the client's own identity (name,
|
|
11
|
-
* the owning service's title, the repo license) so it never needs updating when
|
|
12
|
-
* other packages change.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/** Teal accent — the banner taxonomy colour for a generated API client. */
|
|
16
|
-
const CLIENT_ACCENT = '#14B8A6';
|
|
17
|
-
|
|
18
|
-
function detectRepoSpdx(repoRoot: string): string {
|
|
19
|
-
const licensePath = join(repoRoot, 'LICENSE');
|
|
20
|
-
if (existsSync(licensePath)) {
|
|
21
|
-
const head = readFileSync(licensePath, 'utf8').slice(0, 200);
|
|
22
|
-
if (/Apache License/i.test(head)) {
|
|
23
|
-
return 'Apache-2.0';
|
|
24
|
-
}
|
|
25
|
-
if (/all rights reserved/i.test(head)) {
|
|
26
|
-
return 'Proprietary';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return 'Proprietary';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function banner(packageName: string, tagline: string): string {
|
|
33
|
-
return [
|
|
34
|
-
`<svg width="680" height="120" viewBox="0 0 680 120" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${packageName}">`,
|
|
35
|
-
` <rect width="680" height="120" rx="14" fill="#0B1020"/>`,
|
|
36
|
-
` <g transform="translate(28,34)">`,
|
|
37
|
-
` <path d="M26 0 L52 15 L52 45 L26 60 L0 45 L0 15 Z" fill="${CLIENT_ACCENT}" opacity="0.18"/>`,
|
|
38
|
-
` <path d="M26 12 L41 21 L41 39 L26 48 L11 39 L11 21 Z" fill="${CLIENT_ACCENT}"/>`,
|
|
39
|
-
` </g>`,
|
|
40
|
-
` <text x="92" y="52" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" font-size="22" fill="#F8FAFC" font-weight="700">${packageName}</text>`,
|
|
41
|
-
` <text x="92" y="80" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15" fill="#94A3B8">${tagline}</text>`,
|
|
42
|
-
` <text x="652" y="105" text-anchor="end" font-family="ui-sans-serif,system-ui,sans-serif" font-size="12" fill="#475569">xema.dev</text>`,
|
|
43
|
-
`</svg>`,
|
|
44
|
-
].join('\n');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Write `README.md` for a generated client package. Returns nothing; always
|
|
49
|
-
* overwrites (the README is generator-owned).
|
|
50
|
-
*/
|
|
51
|
-
export function ensureClientReadme(params: {
|
|
52
|
-
clientDir: string;
|
|
53
|
-
packageName: string;
|
|
54
|
-
serviceTitle: string;
|
|
55
|
-
surface: ApiSurface;
|
|
56
|
-
repoRoot: string;
|
|
57
|
-
}): void {
|
|
58
|
-
const { clientDir, packageName, serviceTitle, surface, repoRoot } = params;
|
|
59
|
-
const enc = encodeURIComponent(packageName);
|
|
60
|
-
const spdx = detectRepoSpdx(repoRoot);
|
|
61
|
-
const surfaceWord = surface === ApiSurface.Public ? '' : `${surface} `;
|
|
62
|
-
const tagline = `Typed, generated ${surfaceWord}HTTP client for the ${serviceTitle}.`;
|
|
63
|
-
const licenseLine =
|
|
64
|
-
spdx === 'Apache-2.0'
|
|
65
|
-
? 'Apache-2.0 © Xema — [xema.dev](https://xema.dev)'
|
|
66
|
-
: 'Proprietary — © Xema. All rights reserved. — [xema.dev](https://xema.dev)';
|
|
67
|
-
|
|
68
|
-
const md = `<!-- Generated by @xemahq/api-client-generator. Do not edit by hand. -->
|
|
69
|
-
<p align="center">
|
|
70
|
-
${banner(packageName, tagline)}
|
|
71
|
-
</p>
|
|
72
|
-
|
|
73
|
-
<p align="center">
|
|
74
|
-
<a href="https://xema.dev">Website</a> ·
|
|
75
|
-
<a href="https://www.npmjs.com/package/${packageName}">npm</a>
|
|
76
|
-
</p>
|
|
77
|
-
|
|
78
|
-
<p align="center">
|
|
79
|
-
<img alt="npm" src="https://img.shields.io/npm/v/${enc}?color=2563eb&label=npm">
|
|
80
|
-
<img alt="license" src="https://img.shields.io/npm/l/${enc}?color=10b981">
|
|
81
|
-
<img alt="types" src="https://img.shields.io/npm/types/${enc}?color=3178c6">
|
|
82
|
-
</p>
|
|
83
|
-
|
|
84
|
-
# ${packageName}
|
|
85
|
-
|
|
86
|
-
> ${tagline}
|
|
87
|
-
|
|
88
|
-
## Overview
|
|
89
|
-
|
|
90
|
-
Auto-generated TypeScript client for the **${serviceTitle}**. It exports typed
|
|
91
|
-
request functions and response models that mirror the service's OpenAPI surface,
|
|
92
|
-
so callers get end-to-end type safety without hand-writing HTTP calls. This
|
|
93
|
-
package is produced by \`@xemahq/api-client-generator\` and regenerated whenever
|
|
94
|
-
the service's API changes — do not edit it by hand.
|
|
95
|
-
|
|
96
|
-
## When to use it
|
|
97
|
-
|
|
98
|
-
- Use it from any TypeScript service or app that calls the ${serviceTitle} over HTTP.
|
|
99
|
-
- You get compile-time types for every endpoint and payload; regenerate to pick
|
|
100
|
-
up API changes rather than editing the client.
|
|
101
|
-
|
|
102
|
-
## Installation
|
|
103
|
-
|
|
104
|
-
\`\`\`bash
|
|
105
|
-
pnpm add ${packageName}
|
|
106
|
-
\`\`\`
|
|
107
|
-
|
|
108
|
-
## Usage
|
|
109
|
-
|
|
110
|
-
\`\`\`ts
|
|
111
|
-
// Every endpoint function and response model is exported from the package root.
|
|
112
|
-
import * as client from '${packageName}';
|
|
113
|
-
\`\`\`
|
|
114
|
-
|
|
115
|
-
Call the typed endpoint functions; request and response shapes are fully typed
|
|
116
|
-
from the service's OpenAPI spec.
|
|
117
|
-
|
|
118
|
-
## License
|
|
119
|
-
|
|
120
|
-
${licenseLine}
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
writeFileSync(join(clientDir, 'README.md'), md, 'utf8');
|
|
124
|
-
}
|