@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,300 @@
|
|
|
1
|
+
import { renderArtifactTag } from "../config/publishing";
|
|
2
|
+
import type {
|
|
3
|
+
ResolvedArtifact,
|
|
4
|
+
ResolvedContract,
|
|
5
|
+
VarsTree,
|
|
6
|
+
} from "../config/types";
|
|
7
|
+
|
|
8
|
+
export interface DefaultReadmeArgs {
|
|
9
|
+
contract: ResolvedContract;
|
|
10
|
+
artifact: ResolvedArtifact;
|
|
11
|
+
version: string;
|
|
12
|
+
github: { owner: string; repo: string };
|
|
13
|
+
vars: VarsTree;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders a sensible default `README.md` for a generated SDK package, used
|
|
18
|
+
* whenever the artifact has no custom `readme:` template configured. Covers
|
|
19
|
+
* every built-in `lang`/`kind` combination; anything more specific (house
|
|
20
|
+
* install instructions, extra usage notes) belongs in a custom template
|
|
21
|
+
* instead - see `readme-renderer.ts`. Registry URLs and branch/tag names
|
|
22
|
+
* come from the artifact's resolved `publishing:` config, so a repo that
|
|
23
|
+
* overrides its registry sees that reflected here automatically.
|
|
24
|
+
*
|
|
25
|
+
* @param args - The contract, artifact, version, and github/vars coordinates
|
|
26
|
+
* to render for.
|
|
27
|
+
* @returns The rendered README content.
|
|
28
|
+
*/
|
|
29
|
+
export function renderDefaultReadme({
|
|
30
|
+
contract,
|
|
31
|
+
artifact,
|
|
32
|
+
version,
|
|
33
|
+
github,
|
|
34
|
+
vars,
|
|
35
|
+
}: DefaultReadmeArgs): string {
|
|
36
|
+
const header = `# ${contract.title} - ${label(artifact)}
|
|
37
|
+
|
|
38
|
+
> Generated from \`${contract.entrypointRelative}\` in [${github.owner}/${github.repo}](https://github.com/${github.owner}/${github.repo}).
|
|
39
|
+
> Do not edit by hand - this package is regenerated and republished on every release.
|
|
40
|
+
|
|
41
|
+
Version: \`${version}\`
|
|
42
|
+
Source branch: \`${artifact.branch}\`
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
return `${header}\n${body(contract, artifact, version, github, vars)}\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//<editor-fold desc="README Template Helpers" defaultstate="collapsed">
|
|
49
|
+
|
|
50
|
+
function label(artifact: ResolvedArtifact): string {
|
|
51
|
+
const lang = { typescript: "TypeScript", go: "Go", java: "Java" }[
|
|
52
|
+
artifact.lang
|
|
53
|
+
];
|
|
54
|
+
const kind =
|
|
55
|
+
artifact.kind === "client"
|
|
56
|
+
? "Client SDK"
|
|
57
|
+
: artifact.lang === "typescript"
|
|
58
|
+
? "Server Types"
|
|
59
|
+
: "Server Stubs";
|
|
60
|
+
|
|
61
|
+
return `${lang} ${kind}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function body(
|
|
65
|
+
contract: ResolvedContract,
|
|
66
|
+
artifact: ResolvedArtifact,
|
|
67
|
+
version: string,
|
|
68
|
+
github: { owner: string; repo: string },
|
|
69
|
+
vars: VarsTree,
|
|
70
|
+
): string {
|
|
71
|
+
switch (`${artifact.lang}-${artifact.kind}`) {
|
|
72
|
+
case "typescript-client":
|
|
73
|
+
return tsClient(artifact, version);
|
|
74
|
+
case "typescript-server":
|
|
75
|
+
return tsServer(artifact, version);
|
|
76
|
+
case "go-client":
|
|
77
|
+
return goClient(contract, artifact, github, vars);
|
|
78
|
+
case "go-server":
|
|
79
|
+
return goServer(artifact);
|
|
80
|
+
case "java-client":
|
|
81
|
+
return javaClient(artifact, version);
|
|
82
|
+
case "java-server":
|
|
83
|
+
return javaServer(artifact, version);
|
|
84
|
+
default:
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//</editor-fold>
|
|
90
|
+
|
|
91
|
+
//<editor-fold desc="README Body Templates" defaultstate="collapsed">
|
|
92
|
+
|
|
93
|
+
function tsClient(artifact: ResolvedArtifact, version: string): string {
|
|
94
|
+
return `## Install
|
|
95
|
+
|
|
96
|
+
\`\`\`bash
|
|
97
|
+
npm config set <scope>:registry ${artifact.publishing.npmRegistry}
|
|
98
|
+
npm install ${artifact.package}@${version}
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
(Requires an authenticated \`.npmrc\` with a token for that registry.)
|
|
102
|
+
|
|
103
|
+
## Usage
|
|
104
|
+
|
|
105
|
+
\`\`\`ts
|
|
106
|
+
import { Configuration, DefaultApi } from "${artifact.package}";
|
|
107
|
+
|
|
108
|
+
const api = new DefaultApi(
|
|
109
|
+
new Configuration({ basePath: "https://api.your-domain.com" }),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const result = await api.someOperation();
|
|
113
|
+
\`\`\`
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function tsServer(artifact: ResolvedArtifact, version: string): string {
|
|
118
|
+
return `## Install
|
|
119
|
+
|
|
120
|
+
\`\`\`bash
|
|
121
|
+
npm config set <scope>:registry ${artifact.publishing.npmRegistry}
|
|
122
|
+
npm install --save-dev ${artifact.package}@${version}
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
(Requires an authenticated \`.npmrc\` with a token for that registry.)
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
\`\`\`ts
|
|
130
|
+
import type { components, operations } from "${artifact.package}";
|
|
131
|
+
|
|
132
|
+
type LoginResponses = operations["login"]["responses"];
|
|
133
|
+
|
|
134
|
+
// Example: an Express handler typed against the contract
|
|
135
|
+
app.post("/login", (req, res) => {
|
|
136
|
+
const body = req.body as components["schemas"]["LoginRequest"];
|
|
137
|
+
const response: LoginResponses[200]["content"]["application/json"] = {
|
|
138
|
+
// ...
|
|
139
|
+
};
|
|
140
|
+
res.json(response);
|
|
141
|
+
});
|
|
142
|
+
\`\`\`
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function goClient(
|
|
147
|
+
contract: ResolvedContract,
|
|
148
|
+
artifact: ResolvedArtifact,
|
|
149
|
+
github: { owner: string; repo: string },
|
|
150
|
+
vars: VarsTree,
|
|
151
|
+
): string {
|
|
152
|
+
// Illustrative only - renders the tag template with a literal "<version>"
|
|
153
|
+
// placeholder rather than an actual version, since this is documentation
|
|
154
|
+
// text showing the *pattern*, not a specific release.
|
|
155
|
+
const exampleTag = renderArtifactTag(
|
|
156
|
+
artifact,
|
|
157
|
+
contract.name,
|
|
158
|
+
"<version>",
|
|
159
|
+
github,
|
|
160
|
+
vars,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return `## Install
|
|
164
|
+
|
|
165
|
+
Go has no package registry, so this module is pulled directly from its
|
|
166
|
+
publishing branch:
|
|
167
|
+
|
|
168
|
+
\`\`\`bash
|
|
169
|
+
go get ${artifact.goModule}@${artifact.branch}
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
To pin an exact release instead of the branch head, use the matching tag:
|
|
173
|
+
|
|
174
|
+
\`\`\`bash
|
|
175
|
+
go get ${artifact.goModule}@${exampleTag}
|
|
176
|
+
\`\`\`
|
|
177
|
+
|
|
178
|
+
## Usage
|
|
179
|
+
|
|
180
|
+
\`\`\`go
|
|
181
|
+
import (
|
|
182
|
+
"context"
|
|
183
|
+
|
|
184
|
+
${artifact.goPackageName} "${artifact.goModule}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
func main() {
|
|
188
|
+
cfg := ${artifact.goPackageName}.NewConfiguration()
|
|
189
|
+
client := ${artifact.goPackageName}.NewAPIClient(cfg)
|
|
190
|
+
|
|
191
|
+
resp, _, err := client.DefaultAPI.SomeOperation(context.Background()).Execute()
|
|
192
|
+
_ = resp
|
|
193
|
+
_ = err
|
|
194
|
+
}
|
|
195
|
+
\`\`\`
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function goServer(artifact: ResolvedArtifact): string {
|
|
200
|
+
return `## Install
|
|
201
|
+
|
|
202
|
+
\`\`\`bash
|
|
203
|
+
go get ${artifact.goModule}@${artifact.branch}
|
|
204
|
+
\`\`\`
|
|
205
|
+
|
|
206
|
+
## Usage
|
|
207
|
+
|
|
208
|
+
Implement the generated \`${artifact.goPackageName}.*ApiServicer\` interfaces and
|
|
209
|
+
wire them into the generated router:
|
|
210
|
+
|
|
211
|
+
\`\`\`go
|
|
212
|
+
router := ${artifact.goPackageName}.NewRouter(
|
|
213
|
+
${artifact.goPackageName}.NewSomeApiController(yourServiceImpl),
|
|
214
|
+
)
|
|
215
|
+
\`\`\`
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function javaClient(artifact: ResolvedArtifact, version: string): string {
|
|
220
|
+
return `## Install (Maven)
|
|
221
|
+
|
|
222
|
+
\`\`\`xml
|
|
223
|
+
<dependency>
|
|
224
|
+
<groupId>${artifact.maven?.groupId}</groupId>
|
|
225
|
+
<artifactId>${artifact.maven?.artifactId}</artifactId>
|
|
226
|
+
<version>${version}</version>
|
|
227
|
+
</dependency>
|
|
228
|
+
\`\`\`
|
|
229
|
+
|
|
230
|
+
Add the repository to your \`settings.xml\` (or \`pom.xml\`) with credentials
|
|
231
|
+
for that repository:
|
|
232
|
+
|
|
233
|
+
\`\`\`xml
|
|
234
|
+
<repository>
|
|
235
|
+
<id>${artifact.publishing.mavenRepositoryId}</id>
|
|
236
|
+
<url>${artifact.publishing.mavenRepositoryUrl}</url>
|
|
237
|
+
</repository>
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
## Usage
|
|
241
|
+
|
|
242
|
+
Generated with \`library=restclient\` - Spring's \`RestClient\`, the current
|
|
243
|
+
recommended synchronous HTTP client for Spring apps:
|
|
244
|
+
|
|
245
|
+
\`\`\`java
|
|
246
|
+
@Configuration
|
|
247
|
+
public class SomeServiceClientConfig {
|
|
248
|
+
|
|
249
|
+
@Bean
|
|
250
|
+
public ApiClient someServiceApiClient(RestClient.Builder builder) {
|
|
251
|
+
ApiClient client = new ApiClient(builder.build());
|
|
252
|
+
client.setBasePath("https://api.your-domain.com");
|
|
253
|
+
return client;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@Bean
|
|
257
|
+
public DefaultApi someServiceApi(ApiClient someServiceApiClient) {
|
|
258
|
+
return new DefaultApi(someServiceApiClient);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
\`\`\`
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function javaServer(artifact: ResolvedArtifact, version: string): string {
|
|
266
|
+
return `## Install (Maven)
|
|
267
|
+
|
|
268
|
+
\`\`\`xml
|
|
269
|
+
<dependency>
|
|
270
|
+
<groupId>${artifact.maven?.groupId}</groupId>
|
|
271
|
+
<artifactId>${artifact.maven?.artifactId}</artifactId>
|
|
272
|
+
<version>${version}</version>
|
|
273
|
+
</dependency>
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
Add the repository to your \`settings.xml\` (or \`pom.xml\`) with credentials
|
|
277
|
+
for that repository:
|
|
278
|
+
|
|
279
|
+
\`\`\`xml
|
|
280
|
+
<repository>
|
|
281
|
+
<id>${artifact.publishing.mavenRepositoryId}</id>
|
|
282
|
+
<url>${artifact.publishing.mavenRepositoryUrl}</url>
|
|
283
|
+
</repository>
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
## Usage
|
|
287
|
+
|
|
288
|
+
This artifact only contains the generated Spring \`@RestController\` interfaces
|
|
289
|
+
(\`interfaceOnly=true\`) - implement them in your service:
|
|
290
|
+
|
|
291
|
+
\`\`\`java
|
|
292
|
+
@RestController
|
|
293
|
+
public class SomeController implements SomeApi {
|
|
294
|
+
// interface methods generated from the OpenAPI contract
|
|
295
|
+
}
|
|
296
|
+
\`\`\`
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//</editor-fold>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { renderArtifactTag } from "../config/publishing";
|
|
4
|
+
import { buildTemplateContext, interpolate } from "../config/template";
|
|
5
|
+
import type {
|
|
6
|
+
ResolvedArtifact,
|
|
7
|
+
ResolvedContract,
|
|
8
|
+
VarsTree,
|
|
9
|
+
} from "../config/types";
|
|
10
|
+
import { renderDefaultReadme } from "./default-templates";
|
|
11
|
+
|
|
12
|
+
export interface RenderReadmeArgs {
|
|
13
|
+
contract: ResolvedContract;
|
|
14
|
+
artifact: ResolvedArtifact;
|
|
15
|
+
version: string;
|
|
16
|
+
github: { owner: string; repo: string };
|
|
17
|
+
vars: VarsTree;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders the root-level `README.md` for a generated SDK package.
|
|
22
|
+
*
|
|
23
|
+
* If the artifact has a `readme:` path configured (resolved at config-load time
|
|
24
|
+
* to `artifact.readmeTemplate`), that file is read and interpolated with the
|
|
25
|
+
* same `{...}` placeholder engine naming templates use - `{service}`,
|
|
26
|
+
* `{title}`, `{version}`, `{vars.*}`, `{github.owner}`, `{github.repo}`, plus
|
|
27
|
+
* `{artifact.*}` (id/lang/kind/package/goModule/goPackageName/maven.groupId/
|
|
28
|
+
* maven.artifactId/branch/tag/npmRegistry/mavenRepositoryUrl). Otherwise,
|
|
29
|
+
* falls back to a built-in default template for the artifact's language/kind.
|
|
30
|
+
*
|
|
31
|
+
* @param args - The contract, artifact, version, and github/vars context to
|
|
32
|
+
* render for.
|
|
33
|
+
* @returns The rendered README content.
|
|
34
|
+
*/
|
|
35
|
+
export async function renderReadme(args: RenderReadmeArgs): Promise<string> {
|
|
36
|
+
if (!args.artifact.readmeTemplate) {
|
|
37
|
+
return renderDefaultReadme(args);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const raw = await readFile(args.artifact.readmeTemplate, "utf8");
|
|
41
|
+
const context = buildTemplateContext({
|
|
42
|
+
service: args.contract.name,
|
|
43
|
+
title: args.contract.title,
|
|
44
|
+
version: args.version,
|
|
45
|
+
github: args.github,
|
|
46
|
+
vars: args.vars,
|
|
47
|
+
artifact: {
|
|
48
|
+
id: args.artifact.id,
|
|
49
|
+
lang: args.artifact.lang,
|
|
50
|
+
kind: args.artifact.kind,
|
|
51
|
+
package: args.artifact.package,
|
|
52
|
+
goModule: args.artifact.goModule,
|
|
53
|
+
goPackageName: args.artifact.goPackageName,
|
|
54
|
+
maven: args.artifact.maven,
|
|
55
|
+
branch: args.artifact.branch,
|
|
56
|
+
tag: renderArtifactTag(
|
|
57
|
+
args.artifact,
|
|
58
|
+
args.contract.name,
|
|
59
|
+
args.version,
|
|
60
|
+
args.github,
|
|
61
|
+
args.vars,
|
|
62
|
+
),
|
|
63
|
+
npmRegistry: args.artifact.publishing.npmRegistry,
|
|
64
|
+
mavenRepositoryUrl: args.artifact.publishing.mavenRepositoryUrl,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return interpolate(raw, context);
|
|
69
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
5
|
+
|
|
6
|
+
import type { ResolvedConfig } from "../config/types";
|
|
7
|
+
|
|
8
|
+
const GENERATED_HEADER =
|
|
9
|
+
"# AUTO-GENERATED by Seagull from its config and 'redocly.base.yaml'.\n" +
|
|
10
|
+
"# Do not edit directly, edit those two files instead (this file is\n" +
|
|
11
|
+
"# regenerated on every lint/bundle/generate run).\n\n";
|
|
12
|
+
|
|
13
|
+
interface RedoclyBase {
|
|
14
|
+
extends?: string[];
|
|
15
|
+
rules?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Regenerates `redocly.yaml`'s `apis:` section from the resolved CLI config,
|
|
20
|
+
* merging it with the hand-authored `extends`/`rules` in `redocly.base.yaml`.
|
|
21
|
+
*
|
|
22
|
+
* The CLI config stays the single source of truth for which APIs exist and
|
|
23
|
+
* where their TypeScript server types land, instead of that being duplicated
|
|
24
|
+
* by hand into `redocly.yaml`.
|
|
25
|
+
*
|
|
26
|
+
* Called at the start of every command that shells out to `redocly` or
|
|
27
|
+
* `openapi-typescript` (both read `redocly.yaml` directly), so it's always
|
|
28
|
+
* up to date before those tools run.
|
|
29
|
+
*
|
|
30
|
+
* @param config - The resolved CLI config.
|
|
31
|
+
*/
|
|
32
|
+
export async function syncRedoclyConfig(config: ResolvedConfig): Promise<void> {
|
|
33
|
+
const basePath = path.join(config.rootDir, "redocly.base.yaml");
|
|
34
|
+
const base = parseYaml(await readFile(basePath, "utf8")) as RedoclyBase;
|
|
35
|
+
|
|
36
|
+
const apis = Object.fromEntries(
|
|
37
|
+
config.contracts.map((contract) => {
|
|
38
|
+
const typesArtifact = contract.artifacts.find(
|
|
39
|
+
(artifact) => artifact.tool === "openapi-typescript",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
`${contract.name}@v1`,
|
|
44
|
+
{
|
|
45
|
+
root: contract.entrypointRelative,
|
|
46
|
+
...(typesArtifact
|
|
47
|
+
? {
|
|
48
|
+
"x-openapi-ts": {
|
|
49
|
+
output: path.join(
|
|
50
|
+
path.relative(config.rootDir, typesArtifact.outputDir),
|
|
51
|
+
"index.d.ts",
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
: {}),
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const final = { extends: base.extends, apis, rules: base.rules };
|
|
62
|
+
|
|
63
|
+
await writeFile(
|
|
64
|
+
path.join(config.rootDir, "redocly.yaml"),
|
|
65
|
+
GENERATED_HEADER + stringifyYaml(final),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shape of a bundled OpenAPI document, narrowed to the one field this module
|
|
5
|
+
* cares about.
|
|
6
|
+
*/
|
|
7
|
+
export interface BundledSpec {
|
|
8
|
+
info?: {
|
|
9
|
+
version?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolves the version to stamp onto a single contract's generated SDK
|
|
15
|
+
* artifacts (npm/Maven packages, Go module tags, git branches, etc.).
|
|
16
|
+
*
|
|
17
|
+
* The single source of truth is that contract's own `info.version` field in its
|
|
18
|
+
* `openapi.yaml` - bump it there and every artifact, package, and git tag for
|
|
19
|
+
* that contract picks up the new version on the next release. Versions are
|
|
20
|
+
* resolved independently per contract: two services can be at different
|
|
21
|
+
* versions at the same time.
|
|
22
|
+
*
|
|
23
|
+
* `SDK_VERSION_OVERRIDE` (wired up from the release workflow's manual `version`
|
|
24
|
+
* input) bypasses the spec entirely and stamps every contract with the same
|
|
25
|
+
* given value. It exists for one-off emergency republishes, not routine
|
|
26
|
+
* releases. Routine releases should always go through `info.version`.
|
|
27
|
+
*
|
|
28
|
+
* @param spec - The parsed, bundled OpenAPI document for the contract.
|
|
29
|
+
* @param contractName - The contract name, used only for the error message.
|
|
30
|
+
* @returns The resolved version string (no leading `v`).
|
|
31
|
+
* @throws Error if no override is set and the spec has no `info.version`.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveVersion(
|
|
34
|
+
spec: BundledSpec,
|
|
35
|
+
contractName: string,
|
|
36
|
+
): string {
|
|
37
|
+
const override = process.env.SDK_VERSION_OVERRIDE?.trim();
|
|
38
|
+
|
|
39
|
+
if (override) {
|
|
40
|
+
return override.replace(/^v/, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const specVersion = spec.info?.version?.trim();
|
|
44
|
+
|
|
45
|
+
if (!specVersion) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`specs/${contractName}/openapi.yaml is missing "info.version" - set it ` +
|
|
48
|
+
`to a semver value. This field is the single source of truth for ` +
|
|
49
|
+
`the ${contractName} contract's SDK version.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return specVersion.replace(/^v/, "");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Computes a stable content hash of a bundled OpenAPI document's raw JSON text.
|
|
58
|
+
* Stamped alongside `VERSION` into every generated SDK package so
|
|
59
|
+
* `publish-sdk.ts` can tell a genuine no-op republish (same spec, same version)
|
|
60
|
+
* apart from a spec that changed without its `info.version` being bumped.
|
|
61
|
+
*
|
|
62
|
+
* @param raw - The raw bundled spec file contents (JSON text).
|
|
63
|
+
* @returns A `sha256` hex digest of the raw contents.
|
|
64
|
+
*/
|
|
65
|
+
export function hashSpec(raw: string): string {
|
|
66
|
+
return createHash("sha256").update(raw).digest("hex");
|
|
67
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
/* Environment */
|
|
6
|
+
"target": "es2024",
|
|
7
|
+
"lib": ["esnext"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"allowJs": false,
|
|
10
|
+
|
|
11
|
+
/* Modules */
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"allowImportingTsExtensions": false,
|
|
16
|
+
"erasableSyntaxOnly": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"resolveJsonModule": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
"noEmit": true,
|
|
22
|
+
|
|
23
|
+
/* Strict Type Checking */
|
|
24
|
+
"strict": true,
|
|
25
|
+
"skipLibCheck": true,
|
|
26
|
+
"noFallthroughCasesInSwitch": true,
|
|
27
|
+
"noImplicitOverride": true,
|
|
28
|
+
"noUnusedLocals": true,
|
|
29
|
+
"noUnusedParameters": true,
|
|
30
|
+
"noUncheckedIndexedAccess": true
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"include": ["src/**/*.ts", "*.config.ts"]
|
|
34
|
+
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from "tsdown";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tsdown configuration
|
|
5
|
+
*
|
|
6
|
+
* @see {@link https://tsdown.dev Tsdown documentation}
|
|
7
|
+
*/
|
|
8
|
+
// noinspection JSUnusedGlobalSymbols
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
entry: {
|
|
11
|
+
index: "src/index.ts",
|
|
12
|
+
},
|
|
13
|
+
platform: "node",
|
|
14
|
+
format: ["esm"],
|
|
15
|
+
target: "node22",
|
|
16
|
+
dts: {
|
|
17
|
+
entry: "src/index.ts",
|
|
18
|
+
},
|
|
19
|
+
clean: true,
|
|
20
|
+
sourcemap: true,
|
|
21
|
+
});
|