@octalmesh/seagull-core 0.0.2
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/LICENSE.md +21 -0
- package/README.md +52 -0
- package/dist/index.mjs +1364 -0
- package/package.json +45 -0
- package/src/config/loader.ts +353 -0
- package/src/config/publishing.ts +37 -0
- package/src/config/resolve-config-file.ts +38 -0
- package/src/config/schema.ts +270 -0
- package/src/config/template.ts +113 -0
- package/src/config/types.ts +139 -0
- package/src/generator/generator.ts +33 -0
- package/src/generator/registry.ts +51 -0
- package/src/generator/types.ts +38 -0
- package/src/generators/openapi-generator-cli/index.ts +1 -0
- package/src/generators/openapi-generator-cli/openapi-generator-cli.generator.ts +122 -0
- package/src/generators/openapi-generator-cli/patchers/go-module.patcher.ts +31 -0
- package/src/generators/openapi-generator-cli/patchers/maven.patcher.ts +44 -0
- package/src/generators/openapi-generator-cli/patchers/npm.patcher.ts +32 -0
- package/src/generators/openapi-generator-cli/patchers/patcher.ts +17 -0
- package/src/generators/openapi-typescript/index.ts +1 -0
- package/src/generators/openapi-typescript/openapi-typescript.generator.ts +62 -0
- package/src/git/git.ts +118 -0
- package/src/index.ts +45 -0
- package/src/process/exec.ts +53 -0
- package/src/process/resolve-bin.ts +41 -0
- package/src/readme/default-templates.ts +300 -0
- package/src/readme/readme-renderer.ts +69 -0
- package/src/redocly/redocly-sync.ts +67 -0
- package/src/version/version.ts +67 -0
- package/tsconfig.json +34 -0
- package/tsdown.config.ts +21 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The seagull config *schema* version - not the npm package's own version.
|
|
5
|
+
* Bumped only when the shape of `seagull.yaml` changes in a breaking way, so
|
|
6
|
+
* older configs fail with a clear "this config targets schema vN, seagull
|
|
7
|
+
* expects vM" error instead of a confusing validation failure on some
|
|
8
|
+
* unrelated field once the schema moves on.
|
|
9
|
+
*/
|
|
10
|
+
export const CONFIG_SCHEMA_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A free-form tree of leaf values, used for the `vars:` block in the CLI config.
|
|
14
|
+
* Nest however deep is useful - every leaf becomes addressable as
|
|
15
|
+
* `{vars.<dot.path>}` in templated fields.
|
|
16
|
+
*/
|
|
17
|
+
export interface VarsTree {
|
|
18
|
+
[key: string]: string | number | boolean | VarsTree;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const varsTreeSchema: z.ZodType<VarsTree> = z.lazy(() =>
|
|
22
|
+
z.record(
|
|
23
|
+
z.string(),
|
|
24
|
+
z.union([z.string(), z.number(), z.boolean(), varsTreeSchema]),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const githubSchema = z.object({
|
|
29
|
+
owner: z.string().min(1),
|
|
30
|
+
repo: z.string().min(1),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const pathsSchema = z.object({
|
|
34
|
+
dist: z.string().min(1).default("dist"),
|
|
35
|
+
specs: z.string().min(1).optional(),
|
|
36
|
+
docs: z.string().min(1).optional(),
|
|
37
|
+
sdk: z.string().min(1).optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const docsSchema = z.object({
|
|
41
|
+
server: z.object({
|
|
42
|
+
host: z.string().min(1),
|
|
43
|
+
port: z.number().int().positive(),
|
|
44
|
+
}),
|
|
45
|
+
metadata: z.object({
|
|
46
|
+
title: z.string().min(1),
|
|
47
|
+
description: z.string().min(1),
|
|
48
|
+
favicon: z.string().min(1),
|
|
49
|
+
baseServerUrl: z.string().min(1),
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const sdkToolSchema = z.enum([
|
|
54
|
+
"openapi-generator",
|
|
55
|
+
"openapi-typescript",
|
|
56
|
+
]);
|
|
57
|
+
export const sdkLangSchema = z.enum(["typescript", "go", "java"]);
|
|
58
|
+
export const sdkKindSchema = z.enum(["client", "server"]);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* `-g`/`--additional-properties` values: openapi-generator accepts strings,
|
|
62
|
+
* numbers and booleans, all rendered as `key=value` on the CLI.
|
|
63
|
+
*/
|
|
64
|
+
export const additionalPropertiesSchema = z
|
|
65
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
66
|
+
.default({});
|
|
67
|
+
|
|
68
|
+
export const mavenCoordsSchema = z.object({
|
|
69
|
+
groupId: z.string().min(1),
|
|
70
|
+
artifactId: z.string().min(1),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Publishing conventions - git branch/tag naming, and where registry-backed
|
|
75
|
+
* artifacts (npm, Maven) get pushed. Every field is a template supporting the
|
|
76
|
+
* usual `{...}` placeholders (`{service}`, `{id}`, `{github.*}`, `{vars.*}`,
|
|
77
|
+
* and for `tag` only, also `{version}`).
|
|
78
|
+
*
|
|
79
|
+
* Required at the root level - seagull has no built-in opinion on branch/tag
|
|
80
|
+
* naming or which registry to use, so this has to come from the config, not
|
|
81
|
+
* from a hardcoded convention baked into the tool. Per-generator
|
|
82
|
+
* (`generators.<id>.publishing`) and per-contract-artifact
|
|
83
|
+
* (`artifacts[].overrides.publishing`) blocks only need to override the
|
|
84
|
+
* fields that differ for that generator/artifact - see
|
|
85
|
+
* {@link publishingOverrideSchema}.
|
|
86
|
+
*/
|
|
87
|
+
export const publishingSchema = z.object({
|
|
88
|
+
/**
|
|
89
|
+
* Git branch artifacts publish to. Resolved once, at config-load time -
|
|
90
|
+
* no `{version}` available here, e.g. `"sdk/svc-{service}/{id}"`.
|
|
91
|
+
*/
|
|
92
|
+
branch: z.string().min(1),
|
|
93
|
+
/**
|
|
94
|
+
* Git tag artifacts are tagged with on publish. Resolved at publish time,
|
|
95
|
+
* once the version is known, e.g. `"svc-{service}-{id}-v{version}"`.
|
|
96
|
+
*/
|
|
97
|
+
tag: z.string().min(1),
|
|
98
|
+
/**
|
|
99
|
+
* Template for the `repository.url` field written into generated
|
|
100
|
+
* `package.json` (and shown in default README templates), e.g.
|
|
101
|
+
* `"https://github.com/{github.owner}/{github.repo}"`.
|
|
102
|
+
*/
|
|
103
|
+
repositoryUrl: z.string().min(1),
|
|
104
|
+
npm: z.object({
|
|
105
|
+
registry: z.string().min(1),
|
|
106
|
+
access: z.enum(["public", "restricted"]),
|
|
107
|
+
}),
|
|
108
|
+
maven: z.object({
|
|
109
|
+
repositoryId: z.string().min(1),
|
|
110
|
+
repositoryUrl: z.string().min(1),
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export type PublishingInput = z.infer<typeof publishingSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The generator-level / per-artifact-override form of {@link publishingSchema} -
|
|
118
|
+
* every field optional, since it only needs to override whichever fields
|
|
119
|
+
* differ from the root-level `publishing:` (which is guaranteed complete).
|
|
120
|
+
*/
|
|
121
|
+
export const publishingOverrideSchema = z.object({
|
|
122
|
+
branch: z.string().min(1).optional(),
|
|
123
|
+
tag: z.string().min(1).optional(),
|
|
124
|
+
repositoryUrl: z.string().min(1).optional(),
|
|
125
|
+
npm: z
|
|
126
|
+
.object({
|
|
127
|
+
registry: z.string().min(1).optional(),
|
|
128
|
+
access: z.enum(["public", "restricted"]).optional(),
|
|
129
|
+
})
|
|
130
|
+
.optional(),
|
|
131
|
+
maven: z
|
|
132
|
+
.object({
|
|
133
|
+
repositoryId: z.string().min(1).optional(),
|
|
134
|
+
repositoryUrl: z.string().min(1).optional(),
|
|
135
|
+
})
|
|
136
|
+
.optional(),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export type PublishingOverrideInput = z.infer<typeof publishingOverrideSchema>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The shape of a generator "recipe", without the cross-field checks below -
|
|
143
|
+
* kept separate so {@link artifactRefSchema}'s `overrides:` can `.partial()`
|
|
144
|
+
* it (zod rejects `.partial()` on a schema with `.check()` refinements
|
|
145
|
+
* attached).
|
|
146
|
+
*/
|
|
147
|
+
const generatorDefBaseSchema = z.object({
|
|
148
|
+
tool: sdkToolSchema,
|
|
149
|
+
lang: sdkLangSchema,
|
|
150
|
+
kind: sdkKindSchema,
|
|
151
|
+
/**
|
|
152
|
+
* `openapi-generator -g <generator>` value. Required when `tool` is
|
|
153
|
+
* `openapi-generator`.
|
|
154
|
+
*/
|
|
155
|
+
generator: z.string().min(1).optional(),
|
|
156
|
+
/** npm package name template, e.g. `"@{vars.org}/{service}-client"`. */
|
|
157
|
+
package: z.string().min(1).optional(),
|
|
158
|
+
goModule: z.string().min(1).optional(),
|
|
159
|
+
goPackageName: z.string().min(1).optional(),
|
|
160
|
+
maven: mavenCoordsSchema.optional(),
|
|
161
|
+
additionalProperties: additionalPropertiesSchema,
|
|
162
|
+
/**
|
|
163
|
+
* Optional path (relative to the config file's directory) to a custom README
|
|
164
|
+
* template for this artifact - supports the same `{...}` placeholders as
|
|
165
|
+
* naming templates, plus `{version}`, `{title}`, and `{artifact.*}`. If
|
|
166
|
+
* omitted, a built-in default template for the artifact's language/kind is
|
|
167
|
+
* used instead.
|
|
168
|
+
*/
|
|
169
|
+
readme: z.string().min(1).optional(),
|
|
170
|
+
/**
|
|
171
|
+
* Publishing conventions (branch/tag naming, registry URLs) for this
|
|
172
|
+
* generator specifically - overrides whichever fields differ from the
|
|
173
|
+
* root-level `publishing:` (required, see {@link publishingSchema}).
|
|
174
|
+
*/
|
|
175
|
+
publishing: publishingOverrideSchema.optional(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A single generator "recipe": which tool to invoke and how. Referenced by id
|
|
180
|
+
* from one or more contracts' `artifacts:` list.
|
|
181
|
+
*/
|
|
182
|
+
export const generatorDefSchema = generatorDefBaseSchema.check((ctx) => {
|
|
183
|
+
const value = ctx.value;
|
|
184
|
+
|
|
185
|
+
if (value.tool === "openapi-generator" && !value.generator) {
|
|
186
|
+
ctx.issues.push({
|
|
187
|
+
code: "custom",
|
|
188
|
+
message: '"generator" is required when tool is "openapi-generator"',
|
|
189
|
+
input: value,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (value.lang === "go" && !value.goModule) {
|
|
194
|
+
ctx.issues.push({
|
|
195
|
+
code: "custom",
|
|
196
|
+
message: '"goModule" is required for lang "go"',
|
|
197
|
+
input: value,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (value.lang === "java" && !value.maven) {
|
|
202
|
+
ctx.issues.push({
|
|
203
|
+
code: "custom",
|
|
204
|
+
message: '"maven" ({ groupId, artifactId }) is required for lang "java"',
|
|
205
|
+
input: value,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (value.lang === "typescript" && !value.package) {
|
|
210
|
+
ctx.issues.push({
|
|
211
|
+
code: "custom",
|
|
212
|
+
message: '"package" is required for lang "typescript"',
|
|
213
|
+
input: value,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
export type GeneratorDefInput = z.infer<typeof generatorDefSchema>;
|
|
219
|
+
|
|
220
|
+
export const generatorsSchema = z.record(z.string(), generatorDefSchema);
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A contract's reference to a generator by id. The plain-string form just runs
|
|
224
|
+
* that generator as-is; the object form lets one contract tweak a shared
|
|
225
|
+
* generator (extra/overridden `additionalProperties`, a different
|
|
226
|
+
* `maven`/`package`/`readme`/... value) without duplicating the whole recipe
|
|
227
|
+
* under a new id, and `as` renames the artifact's own id (output
|
|
228
|
+
* folder / branch / tag segment) if a contract needs two variants of the same
|
|
229
|
+
* base generator.
|
|
230
|
+
*/
|
|
231
|
+
export const artifactRefSchema = z.union([
|
|
232
|
+
z.string().min(1),
|
|
233
|
+
z.object({
|
|
234
|
+
generator: z.string().min(1),
|
|
235
|
+
as: z.string().min(1).optional(),
|
|
236
|
+
overrides: generatorDefBaseSchema.partial().optional(),
|
|
237
|
+
}),
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
export const contractSchema = z.object({
|
|
241
|
+
name: z.string().min(1),
|
|
242
|
+
title: z.string().min(1),
|
|
243
|
+
entrypoint: z.string().min(1),
|
|
244
|
+
artifacts: z.array(artifactRefSchema).min(1),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
export const rootConfigSchema = z.object({
|
|
248
|
+
/**
|
|
249
|
+
* The config schema version this file targets. Currently must be `1`
|
|
250
|
+
* (the only version that exists) - see {@link CONFIG_SCHEMA_VERSION}.
|
|
251
|
+
*/
|
|
252
|
+
configVersion: z.literal(CONFIG_SCHEMA_VERSION),
|
|
253
|
+
github: githubSchema,
|
|
254
|
+
vars: varsTreeSchema.default({}),
|
|
255
|
+
paths: pathsSchema.default({ dist: "dist" }),
|
|
256
|
+
docs: docsSchema,
|
|
257
|
+
/**
|
|
258
|
+
* Publishing conventions (branch/tag naming, registry URLs), applied to
|
|
259
|
+
* every artifact unless overridden per-generator or
|
|
260
|
+
* per-contract-artifact. Required - seagull has no built-in default here,
|
|
261
|
+
* see {@link publishingSchema}.
|
|
262
|
+
*/
|
|
263
|
+
publishing: publishingSchema,
|
|
264
|
+
generators: generatorsSchema,
|
|
265
|
+
contracts: z.array(contractSchema).min(1),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export type RootConfigInput = z.infer<typeof rootConfigSchema>;
|
|
269
|
+
export type ArtifactRefInput = z.infer<typeof artifactRefSchema>;
|
|
270
|
+
export type ContractInput = z.infer<typeof contractSchema>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const PLACEHOLDER = /\{([a-zA-Z0-9_.]+)}/g;
|
|
2
|
+
|
|
3
|
+
export type TemplateScope = Record<string, unknown>;
|
|
4
|
+
export type TemplateContext = Readonly<Record<string, string>>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Replaces every `{dot.path}` placeholder in `template` with its value from
|
|
8
|
+
* `context`, throwing a descriptive error if a placeholder has no match -
|
|
9
|
+
* a typo'd `{vars.org}` should fail loudly at config-load time, not silently
|
|
10
|
+
* produce a package named literally `@{vars.org}/...`.
|
|
11
|
+
*
|
|
12
|
+
* @param template - The template string, e.g. `"@{vars.org}/{service}-client"`.
|
|
13
|
+
* @param context - The flattened context to resolve placeholders against.
|
|
14
|
+
* @returns The interpolated string.
|
|
15
|
+
*/
|
|
16
|
+
export function interpolate(
|
|
17
|
+
template: string,
|
|
18
|
+
context: TemplateContext,
|
|
19
|
+
): string {
|
|
20
|
+
return template.replace(PLACEHOLDER, (_match, key: string) => {
|
|
21
|
+
const value = context[key];
|
|
22
|
+
|
|
23
|
+
if (value === undefined) {
|
|
24
|
+
const available = Object.keys(context).sort().join(", ");
|
|
25
|
+
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Unknown template placeholder "{${key}}" in "${template}" (available: ${available})`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively interpolates every string value in `value` (walking through plain
|
|
37
|
+
* objects), leaving non-string leaves untouched. Used to resolve
|
|
38
|
+
* `additionalProperties` maps, which may mix templated strings with plain
|
|
39
|
+
* booleans/numbers.
|
|
40
|
+
*
|
|
41
|
+
* @param value - The value (string, object, or primitive) to interpolate.
|
|
42
|
+
* @param context - The flattened context to resolve placeholders against.
|
|
43
|
+
* @returns A deep copy of `value` with every string interpolated.
|
|
44
|
+
*/
|
|
45
|
+
export function interpolateDeep<T>(value: T, context: TemplateContext): T {
|
|
46
|
+
// Every `as T` below asserts a value back into the exact shape it was
|
|
47
|
+
// destructured from - `T` is unconstrained here (it's whatever shape the
|
|
48
|
+
// caller's config value happens to be), which is the shape-preserving
|
|
49
|
+
// contract this function promises, not an actual type hole in practice.
|
|
50
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
const interpolated: string = interpolate(value, context);
|
|
53
|
+
|
|
54
|
+
return interpolated as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
return value.map((item) => interpolateDeep(item, context)) as T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (value !== null && typeof value === "object") {
|
|
62
|
+
return Object.fromEntries(
|
|
63
|
+
Object.entries(value as Record<string, unknown>).map(([key, child]) => [
|
|
64
|
+
key,
|
|
65
|
+
interpolateDeep(child, context),
|
|
66
|
+
]),
|
|
67
|
+
) as T;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* eslint-enable @typescript-eslint/no-unsafe-return */
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Flattens a nested scope object (e.g. `{ service: "auth", vars: { org: "x" } }`)
|
|
76
|
+
* into dot-path lookup keys (`{ service: "auth", "vars.org": "x" }`) for use
|
|
77
|
+
* with {@link interpolate}.
|
|
78
|
+
*
|
|
79
|
+
* @param scope - The nested scope to flatten.
|
|
80
|
+
* @returns A flat dot-path -> string map.
|
|
81
|
+
*/
|
|
82
|
+
export function buildTemplateContext(scope: TemplateScope): TemplateContext {
|
|
83
|
+
const out: Record<string, string> = {};
|
|
84
|
+
|
|
85
|
+
flatten(scope, "", out);
|
|
86
|
+
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function flatten(
|
|
91
|
+
value: unknown,
|
|
92
|
+
prefix: string,
|
|
93
|
+
out: Record<string, string>,
|
|
94
|
+
): void {
|
|
95
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
96
|
+
for (const [key, child] of Object.entries(
|
|
97
|
+
value as Record<string, unknown>,
|
|
98
|
+
)) {
|
|
99
|
+
flatten(child, prefix ? `${prefix}.${key}` : key, out);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Leaf values from parsed YAML/JSON config are always string/number/boolean
|
|
110
|
+
// (VarsTree's own type guarantees this) - String() is safe here.
|
|
111
|
+
/* eslint-disable-next-line @typescript-eslint/no-base-to-string */
|
|
112
|
+
out[prefix] = String(value);
|
|
113
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { VarsTree } from "./schema";
|
|
2
|
+
|
|
3
|
+
export type { VarsTree } from "./schema";
|
|
4
|
+
export type SdkTool = "openapi-generator" | "openapi-typescript";
|
|
5
|
+
export type SdkLang = "typescript" | "go" | "java";
|
|
6
|
+
export type SdkKind = "client" | "server";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fully resolved publishing conventions for one artifact.
|
|
10
|
+
*/
|
|
11
|
+
export interface ResolvedPublishing {
|
|
12
|
+
/** Fully resolved git branch name (no runtime-only placeholders left). */
|
|
13
|
+
branch: string;
|
|
14
|
+
/**
|
|
15
|
+
* Raw tag template - still contains `{version}`, resolved at publish
|
|
16
|
+
* time via `renderArtifactTag()` in `config/publishing.ts`.
|
|
17
|
+
*/
|
|
18
|
+
tagTemplate: string;
|
|
19
|
+
/** Resolved `repository.url` for generated package.json / README examples. */
|
|
20
|
+
repositoryUrl: string;
|
|
21
|
+
npmRegistry: string;
|
|
22
|
+
npmAccess: "public" | "restricted";
|
|
23
|
+
mavenRepositoryId: string;
|
|
24
|
+
mavenRepositoryUrl: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A single artifact a contract generates: a generator recipe from the CLI
|
|
29
|
+
* config, fully resolved (templates interpolated, overrides merged, paths made
|
|
30
|
+
* absolute) for one specific contract.
|
|
31
|
+
*/
|
|
32
|
+
export interface ResolvedArtifact {
|
|
33
|
+
/**
|
|
34
|
+
* The id this artifact is known by for this contract - the key under
|
|
35
|
+
* `generators:` it was resolved from, or its `as` override. Used as the
|
|
36
|
+
* output folder segment, and to derive the publish branch/tag.
|
|
37
|
+
*/
|
|
38
|
+
id: string;
|
|
39
|
+
|
|
40
|
+
tool: SdkTool;
|
|
41
|
+
lang: SdkLang;
|
|
42
|
+
kind: SdkKind;
|
|
43
|
+
|
|
44
|
+
/** `openapi-generator -g` value. Set only when `tool` is `openapi-generator`. */
|
|
45
|
+
generator?: string;
|
|
46
|
+
|
|
47
|
+
/** Absolute output directory: `<sdkDir>/<contract>/<id>`. */
|
|
48
|
+
outputDir: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fully resolved git branch name - convenience alias for
|
|
52
|
+
* `publishing.branch`.
|
|
53
|
+
*/
|
|
54
|
+
branch: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Publishing conventions (branch/tag/registry) for this artifact - see
|
|
58
|
+
* {@link ResolvedPublishing}.
|
|
59
|
+
*/
|
|
60
|
+
publishing: ResolvedPublishing;
|
|
61
|
+
|
|
62
|
+
additionalProperties: Record<string, string | number | boolean>;
|
|
63
|
+
|
|
64
|
+
package?: string;
|
|
65
|
+
goModule?: string;
|
|
66
|
+
goPackageName?: string;
|
|
67
|
+
maven?: { groupId: string; artifactId: string };
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Absolute path to a custom README template, if `readme:` was set for this
|
|
71
|
+
* generator/artifact. Falls back to a built-in default template when unset -
|
|
72
|
+
* see `core/readme/readme-renderer.ts`.
|
|
73
|
+
*/
|
|
74
|
+
readmeTemplate?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ResolvedContract {
|
|
78
|
+
name: string;
|
|
79
|
+
title: string;
|
|
80
|
+
/** Absolute path to the source `openapi.yaml`. */
|
|
81
|
+
entrypoint: string;
|
|
82
|
+
/**
|
|
83
|
+
* Path to the source `openapi.yaml`, relative to `rootDir` - what
|
|
84
|
+
* `redocly.yaml`'s `apis:` section wants.
|
|
85
|
+
*/
|
|
86
|
+
entrypointRelative: string;
|
|
87
|
+
artifacts: ResolvedArtifact[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* One (contract, artifact) pair - the flattened unit of work most commands
|
|
92
|
+
* actually iterate over.
|
|
93
|
+
*/
|
|
94
|
+
export interface ResolvedArtifactEntry {
|
|
95
|
+
contract: ResolvedContract;
|
|
96
|
+
artifact: ResolvedArtifact;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ResolvedConfig {
|
|
100
|
+
/**
|
|
101
|
+
* The config schema version this file targets - see `CONFIG_SCHEMA_VERSION`
|
|
102
|
+
* in `config/schema.ts`.
|
|
103
|
+
*/
|
|
104
|
+
configVersion: number;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Directory containing the config file - every relative path in the config
|
|
108
|
+
* (entrypoints, `paths.*`, `readme` templates, ...) resolves against this.
|
|
109
|
+
*/
|
|
110
|
+
rootDir: string;
|
|
111
|
+
|
|
112
|
+
paths: {
|
|
113
|
+
dist: string;
|
|
114
|
+
specs: string;
|
|
115
|
+
docs: string;
|
|
116
|
+
sdk: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
github: { owner: string; repo: string };
|
|
120
|
+
vars: VarsTree;
|
|
121
|
+
|
|
122
|
+
docs: {
|
|
123
|
+
server: { host: string; port: number };
|
|
124
|
+
metadata: {
|
|
125
|
+
title: string;
|
|
126
|
+
description: string;
|
|
127
|
+
favicon: string;
|
|
128
|
+
baseServerUrl: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
contracts: ResolvedContract[];
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Every (contract, artifact) pair across every contract, in config order -
|
|
136
|
+
* the flat list most commands iterate over.
|
|
137
|
+
*/
|
|
138
|
+
allArtifacts: ResolvedArtifactEntry[];
|
|
139
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SdkTool } from "../config/types";
|
|
2
|
+
import type { GenerateContext, PrepareContext } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The root primitive every concrete SDK generator implements.
|
|
6
|
+
*
|
|
7
|
+
* One instance per underlying tool (`openapi-generator-cli`,
|
|
8
|
+
* `openapi-typescript`, ...) - not one per language, since a single tool
|
|
9
|
+
* invocation (e.g. `openapi-generator-cli -g java`/`-g go`) already covers
|
|
10
|
+
* every language it supports. Language-specific behaviour (patching `go.mod`,
|
|
11
|
+
* `package.json`, `pom.xml`, ...) is composed in via patchers rather than
|
|
12
|
+
* living in per-language subclasses.
|
|
13
|
+
*/
|
|
14
|
+
export abstract class Generator {
|
|
15
|
+
abstract readonly tool: SdkTool;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Optional one-time setup step, run once per tool before any of that tool's
|
|
19
|
+
* {@link generate} calls - for tools like `openapi-typescript` that generate
|
|
20
|
+
* every contract's output in a single global invocation instead of one call
|
|
21
|
+
* per artifact.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Every (contract, artifact) pair using this generator's tool.
|
|
24
|
+
*/
|
|
25
|
+
prepare?(ctx: PrepareContext): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a single artifact.
|
|
29
|
+
*
|
|
30
|
+
* @param ctx - The contract, artifact, and resolved version to generate for.
|
|
31
|
+
*/
|
|
32
|
+
abstract generate(ctx: GenerateContext): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SdkTool } from "../config/types";
|
|
2
|
+
import type { Generator } from "./generator";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Looks up the concrete {@link Generator} implementation for a given tool name.
|
|
6
|
+
*/
|
|
7
|
+
export class GeneratorRegistry {
|
|
8
|
+
private readonly generators = new Map<SdkTool, Generator>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registers a generator implementation under its own {@link Generator.tool}.
|
|
12
|
+
*
|
|
13
|
+
* @param generator - The generator instance to register.
|
|
14
|
+
* @returns `this`, for chaining.
|
|
15
|
+
*/
|
|
16
|
+
register(generator: Generator): this {
|
|
17
|
+
this.generators.set(generator.tool, generator);
|
|
18
|
+
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the generator implementation for a given tool name.
|
|
24
|
+
*
|
|
25
|
+
* @param tool - The tool name, e.g. `"openapi-generator"`.
|
|
26
|
+
* @returns The registered generator.
|
|
27
|
+
* @throws Error if no generator is registered for that tool.
|
|
28
|
+
*/
|
|
29
|
+
resolve(tool: SdkTool): Generator {
|
|
30
|
+
const generator = this.generators.get(tool);
|
|
31
|
+
|
|
32
|
+
if (!generator) {
|
|
33
|
+
const available = [...this.generators.keys()].join(", ");
|
|
34
|
+
|
|
35
|
+
throw new Error(
|
|
36
|
+
`No generator implementation registered for tool "${tool}" (available: ${available})`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return generator;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* All distinct tools currently registered.
|
|
45
|
+
*
|
|
46
|
+
* @returns The registered tool names.
|
|
47
|
+
*/
|
|
48
|
+
tools(): SdkTool[] {
|
|
49
|
+
return [...this.generators.keys()];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResolvedArtifact,
|
|
3
|
+
ResolvedArtifactEntry,
|
|
4
|
+
ResolvedContract,
|
|
5
|
+
SdkTool,
|
|
6
|
+
} from "../config/types";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
ResolvedArtifact,
|
|
10
|
+
ResolvedArtifactEntry,
|
|
11
|
+
ResolvedContract,
|
|
12
|
+
SdkTool,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Passed once per tool to {@link Generator.prepare}, before any of that
|
|
16
|
+
* tool's {@link Generator.generate} calls run. */
|
|
17
|
+
export interface PrepareContext {
|
|
18
|
+
rootDir: string;
|
|
19
|
+
/**
|
|
20
|
+
* Every (contract, artifact) pair that uses this generator's `tool`, across
|
|
21
|
+
* all contracts.
|
|
22
|
+
*/
|
|
23
|
+
entries: ResolvedArtifactEntry[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Passed once per artifact to {@link Generator.generate}. */
|
|
27
|
+
export interface GenerateContext {
|
|
28
|
+
rootDir: string;
|
|
29
|
+
contract: ResolvedContract;
|
|
30
|
+
artifact: ResolvedArtifact;
|
|
31
|
+
version: string;
|
|
32
|
+
github: { owner: string; repo: string };
|
|
33
|
+
/**
|
|
34
|
+
* Absolute path to the contract's bundled JSON spec
|
|
35
|
+
* (`<specsDir>/<contract>.json`).
|
|
36
|
+
*/
|
|
37
|
+
specInputPath: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OpenApiGeneratorCli } from "./openapi-generator-cli.generator";
|