@octalmesh/seagull-core 0.0.2 → 0.1.1
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/CHANGELOG.md +38 -0
- package/README.md +173 -39
- package/dist/index.mjs +218 -152
- package/package.json +6 -6
- package/src/config/loader.test.ts +505 -0
- package/src/config/loader.ts +31 -12
- package/src/config/publishing.test.ts +92 -0
- package/src/config/publishing.ts +9 -5
- package/src/config/resolve-config-file.test.ts +60 -0
- package/src/config/schema.test.ts +466 -0
- package/src/config/schema.ts +16 -10
- package/src/config/spec-format.test.ts +54 -0
- package/src/config/spec-format.ts +48 -0
- package/src/config/template.test.ts +168 -0
- package/src/config/types.ts +3 -3
- package/src/generator/generator.test.ts +59 -0
- package/src/generator/registry.test.ts +84 -0
- package/src/generator/types.ts +9 -14
- package/src/generators/openapi-generator-cli/openapi-generator-cli.generator.test.ts +259 -0
- package/src/generators/openapi-generator-cli/patchers/go-module.patcher.test.ts +119 -0
- package/src/generators/openapi-generator-cli/patchers/maven.patcher.test.ts +141 -0
- package/src/generators/openapi-generator-cli/patchers/npm.patcher.test.ts +132 -0
- package/src/generators/openapi-typescript/openapi-typescript.generator.test.ts +190 -0
- package/src/git/git.test.ts +234 -0
- package/src/git/git.ts +50 -4
- package/src/index.ts +8 -0
- package/src/process/exec.test.ts +103 -0
- package/src/process/exec.ts +1 -2
- package/src/process/resolve-bin.test.ts +43 -0
- package/src/readme/default-templates.test.ts +179 -0
- package/src/readme/default-templates.ts +5 -10
- package/src/readme/readme-renderer.test.ts +121 -0
- package/src/readme/readme-renderer.ts +3 -7
- package/src/redocly/redocly-sync.test.ts +190 -0
- package/src/redocly/redocly-sync.ts +3 -1
- package/src/test-support/fixtures.ts +65 -0
- package/src/version/version.test.ts +93 -0
- package/src/version/version.ts +4 -2
package/dist/index.mjs
CHANGED
|
@@ -21,15 +21,14 @@ const varsTreeSchema = z.lazy(() => z.record(z.string(), z.union([
|
|
|
21
21
|
z.boolean(),
|
|
22
22
|
varsTreeSchema
|
|
23
23
|
])));
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
repo: z.string().min(1)
|
|
27
|
-
});
|
|
24
|
+
const specFormatSchema = z.enum(["json", "yaml"]);
|
|
25
|
+
const specFormatListSchema = z.union([specFormatSchema, z.array(specFormatSchema).min(1)]).default("json").transform((value) => Array.isArray(value) ? value : [value]).refine((formats) => new Set(formats).size === formats.length, { message: "paths.specFormat entries must be unique" });
|
|
28
26
|
const pathsSchema = z.object({
|
|
29
27
|
dist: z.string().min(1).default("dist"),
|
|
30
28
|
specs: z.string().min(1).optional(),
|
|
31
29
|
docs: z.string().min(1).optional(),
|
|
32
|
-
sdk: z.string().min(1).optional()
|
|
30
|
+
sdk: z.string().min(1).optional(),
|
|
31
|
+
specFormat: specFormatListSchema
|
|
33
32
|
});
|
|
34
33
|
const docsSchema = z.object({
|
|
35
34
|
server: z.object({
|
|
@@ -66,8 +65,8 @@ const mavenCoordsSchema = z.object({
|
|
|
66
65
|
/**
|
|
67
66
|
* Publishing conventions - git branch/tag naming, and where registry-backed
|
|
68
67
|
* artifacts (npm, Maven) get pushed. Every field is a template supporting the
|
|
69
|
-
* usual `{...}` placeholders (`{service}`, `{id}`, `{
|
|
70
|
-
*
|
|
68
|
+
* usual `{...}` placeholders (`{service}`, `{id}`, `{vars.*}`, and for `tag`
|
|
69
|
+
* only, also `{version}`).
|
|
71
70
|
*
|
|
72
71
|
* Required at the root level - seagull has no built-in opinion on branch/tag
|
|
73
72
|
* naming or which registry to use, so this has to come from the config, not
|
|
@@ -91,7 +90,7 @@ const publishingSchema = z.object({
|
|
|
91
90
|
/**
|
|
92
91
|
* Template for the `repository.url` field written into generated
|
|
93
92
|
* `package.json` (and shown in default README templates), e.g.
|
|
94
|
-
* `"https://github.com/{
|
|
93
|
+
* `"https://github.com/{vars.repository.owner}/{vars.repository.repo}"`.
|
|
95
94
|
*/
|
|
96
95
|
repositoryUrl: z.string().min(1),
|
|
97
96
|
npm: z.object({
|
|
@@ -207,13 +206,15 @@ const contractSchema = z.object({
|
|
|
207
206
|
});
|
|
208
207
|
const rootConfigSchema = z.object({
|
|
209
208
|
/**
|
|
210
|
-
* The config schema version this file targets. Currently must be `1`
|
|
209
|
+
* The config schema version this file targets. Currently, must be `1`
|
|
211
210
|
* (the only version that exists) - see {@link CONFIG_SCHEMA_VERSION}.
|
|
212
211
|
*/
|
|
213
212
|
configVersion: z.literal(1),
|
|
214
|
-
github: githubSchema,
|
|
215
213
|
vars: varsTreeSchema.default({}),
|
|
216
|
-
paths: pathsSchema.default({
|
|
214
|
+
paths: pathsSchema.default({
|
|
215
|
+
dist: "dist",
|
|
216
|
+
specFormat: ["json"]
|
|
217
|
+
}),
|
|
217
218
|
docs: docsSchema,
|
|
218
219
|
/**
|
|
219
220
|
* Publishing conventions (branch/tag naming, registry URLs), applied to
|
|
@@ -226,6 +227,131 @@ const rootConfigSchema = z.object({
|
|
|
226
227
|
contracts: z.array(contractSchema).min(1)
|
|
227
228
|
});
|
|
228
229
|
//#endregion
|
|
230
|
+
//#region src/git/git.ts
|
|
231
|
+
/**
|
|
232
|
+
* A git ref name (branch or tag) that's safe to pass as a CLI argument to
|
|
233
|
+
* `git`.
|
|
234
|
+
*
|
|
235
|
+
* @see {@link assertSafeRefName}
|
|
236
|
+
*/
|
|
237
|
+
const gitRefNameSchema = z.string().min(1).refine((value) => !value.startsWith("-"), { message: "must not start with \"-\" - git would parse it as a command-line option instead of a ref name" });
|
|
238
|
+
/**
|
|
239
|
+
* Throws if `name` isn't a safe git ref name - see {@link gitRefNameSchema}.
|
|
240
|
+
*
|
|
241
|
+
* @param name - The candidate branch/tag name.
|
|
242
|
+
* @param label - What to call it in the error message (e.g. `"branch"`).
|
|
243
|
+
*/
|
|
244
|
+
function assertSafeRefName(name, label) {
|
|
245
|
+
const result = gitRefNameSchema.safeParse(name);
|
|
246
|
+
if (!result.success) throw new Error(`Invalid git ${label} "${name}": ${result.error.issues[0]?.message}`);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Execute a git command in a given working directory and return the result.
|
|
250
|
+
*
|
|
251
|
+
* @param args - The command-line arguments to pass to the git command.
|
|
252
|
+
* @param cwd - The working directory in which to execute the git command.
|
|
253
|
+
* @returns The result of the git command.
|
|
254
|
+
*
|
|
255
|
+
* @see {@link GitResult} - The result of executing the git command.
|
|
256
|
+
*/
|
|
257
|
+
function git(args, cwd) {
|
|
258
|
+
const result = spawnSync("git", args, {
|
|
259
|
+
cwd,
|
|
260
|
+
encoding: "utf8"
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
status: result.status ?? 1,
|
|
264
|
+
stdout: (result.stdout ?? "").trim(),
|
|
265
|
+
stderr: (result.stderr ?? "").trim()
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if a remote branch exists in the given repository.
|
|
270
|
+
*
|
|
271
|
+
* @param repoRoot - The root directory of the repository.
|
|
272
|
+
* @param branch - The name of the branch to check.
|
|
273
|
+
* @returns Whether the remote branch exists (true) or not (false).
|
|
274
|
+
*/
|
|
275
|
+
function remoteBranchExists(repoRoot, branch) {
|
|
276
|
+
assertSafeRefName(branch, "branch");
|
|
277
|
+
return git([
|
|
278
|
+
"ls-remote",
|
|
279
|
+
"--exit-code",
|
|
280
|
+
"--heads",
|
|
281
|
+
"origin",
|
|
282
|
+
"--",
|
|
283
|
+
branch
|
|
284
|
+
], repoRoot).status === 0;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Check if a remote tag exists in the given repository.
|
|
288
|
+
*
|
|
289
|
+
* @param repoRoot - The root directory of the repository.
|
|
290
|
+
* @param tag - The name of the tag to check.
|
|
291
|
+
* @returns Whether the remote tag exists (true) or not (false).
|
|
292
|
+
*/
|
|
293
|
+
function tagExists(repoRoot, tag) {
|
|
294
|
+
assertSafeRefName(tag, "tag");
|
|
295
|
+
return git([
|
|
296
|
+
"ls-remote",
|
|
297
|
+
"--exit-code",
|
|
298
|
+
"--tags",
|
|
299
|
+
"origin",
|
|
300
|
+
"--",
|
|
301
|
+
tag
|
|
302
|
+
], repoRoot).status === 0;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Fetch a single tag's object from origin into the local repo, without
|
|
306
|
+
* fetching the rest of history/tags.
|
|
307
|
+
*
|
|
308
|
+
* @param repoRoot - The root directory of the repository.
|
|
309
|
+
* @param tag - The name of the tag to fetch.
|
|
310
|
+
* @returns The result of the underlying `git fetch` command.
|
|
311
|
+
*/
|
|
312
|
+
function fetchTag(repoRoot, tag) {
|
|
313
|
+
assertSafeRefName(tag, "tag");
|
|
314
|
+
return git([
|
|
315
|
+
"fetch",
|
|
316
|
+
"origin",
|
|
317
|
+
"--force",
|
|
318
|
+
"--",
|
|
319
|
+
`refs/tags/${tag}:refs/tags/${tag}`
|
|
320
|
+
], repoRoot);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Read a single file's content as it existed at a given git tag, without
|
|
324
|
+
* checking out a worktree.
|
|
325
|
+
*
|
|
326
|
+
* Returns `null` (rather than throwing) both when the tag can't be fetched and
|
|
327
|
+
* when the tag exists but doesn't contain the requested file - the latter is
|
|
328
|
+
* expected for tags published before that file was introduced, and callers
|
|
329
|
+
* should treat "unknown" the same as "no mismatch to report".
|
|
330
|
+
*
|
|
331
|
+
* @param repoRoot - The root directory of the repository.
|
|
332
|
+
* @param tag - The tag to read the file from.
|
|
333
|
+
* @param filePath - The path of the file within that tag's tree.
|
|
334
|
+
* @returns The file's content, or `null` if it couldn't be read.
|
|
335
|
+
*/
|
|
336
|
+
function readFileAtTag(repoRoot, tag, filePath) {
|
|
337
|
+
assertSafeRefName(tag, "tag");
|
|
338
|
+
if (filePath.length === 0) throw new Error("Invalid git file path: must not be empty");
|
|
339
|
+
if (fetchTag(repoRoot, tag).status !== 0) return null;
|
|
340
|
+
const show = git(["show", `${tag}:${filePath}`], repoRoot);
|
|
341
|
+
return show.status === 0 ? show.stdout.trim() : null;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Require that a git command succeeded, throwing an error with the given
|
|
345
|
+
* message if it did not.
|
|
346
|
+
*
|
|
347
|
+
* @param result - The result of the git command to check.
|
|
348
|
+
* @param message - The error message to throw if the command failed.
|
|
349
|
+
* @throws Error if the git command failed (non-zero exit code).
|
|
350
|
+
*/
|
|
351
|
+
function requireOk(result, message) {
|
|
352
|
+
if (result.status !== 0) throw new Error(`${message}: ${result.stderr || result.stdout}`);
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
229
355
|
//#region src/config/template.ts
|
|
230
356
|
const PLACEHOLDER = /\{([a-zA-Z0-9_.]+)}/g;
|
|
231
357
|
/**
|
|
@@ -295,7 +421,7 @@ function flatten(value, prefix, out) {
|
|
|
295
421
|
* @returns The validated (but not yet resolved) raw config.
|
|
296
422
|
*/
|
|
297
423
|
function readRawConfig(configPath) {
|
|
298
|
-
const raw = parse(readFileSync(configPath, "utf8"));
|
|
424
|
+
const raw = parse(readFileSync(configPath, "utf8"), { merge: true });
|
|
299
425
|
const result = rootConfigSchema.safeParse(raw);
|
|
300
426
|
if (!result.success) {
|
|
301
427
|
const issues = z.prettifyError(result.error);
|
|
@@ -413,13 +539,19 @@ function resolveArtifactRef(ref, generators, contractName) {
|
|
|
413
539
|
* @param rootPublishing - The required root-level `publishing:` config
|
|
414
540
|
* from the config file.
|
|
415
541
|
* @param context - The flattened template context for this artifact
|
|
416
|
-
* (`service`, `id`, `
|
|
542
|
+
* (`service`, `id`, `vars.*`).
|
|
543
|
+
* @param artifactLabel - `"<contract>/<artifact id>"`, used only to
|
|
544
|
+
* identify the offending artifact in the error
|
|
545
|
+
* thrown when `branch` resolves unsafely - see
|
|
546
|
+
* {@link assertSafeRefName}.
|
|
417
547
|
* @returns The fully resolved publishing conventions for this artifact.
|
|
418
548
|
*/
|
|
419
|
-
function resolvePublishing(generatorPublishing, rootPublishing, context) {
|
|
549
|
+
function resolvePublishing(generatorPublishing, rootPublishing, context, artifactLabel) {
|
|
420
550
|
const merged = applyPublishingOverride(rootPublishing, generatorPublishing);
|
|
551
|
+
const branch = interpolate(merged.branch, context);
|
|
552
|
+
assertSafeRefName(branch, `publishing.branch for artifact "${artifactLabel}"`);
|
|
421
553
|
return {
|
|
422
|
-
branch
|
|
554
|
+
branch,
|
|
423
555
|
tagTemplate: merged.tag,
|
|
424
556
|
repositoryUrl: interpolate(merged.repositoryUrl, context),
|
|
425
557
|
npmRegistry: interpolate(merged.npm.registry, context),
|
|
@@ -440,7 +572,7 @@ function resolvePublishing(generatorPublishing, rootPublishing, context) {
|
|
|
440
572
|
* @param sdkDir - Absolute path to the SDK output root (`<dist>/sdk`).
|
|
441
573
|
* @param contractName - The owning contract's name.
|
|
442
574
|
* @param contractContext - The flattened template context for this contract
|
|
443
|
-
* (`service`, `
|
|
575
|
+
* (`service`, `vars.*` - not yet `id`).
|
|
444
576
|
* @param rootPublishing - The required root-level `publishing:` config from
|
|
445
577
|
* the config file.
|
|
446
578
|
* @returns The fully resolved artifact.
|
|
@@ -451,7 +583,7 @@ function resolveArtifact(id, def, rootDir, sdkDir, contractName, contractContext
|
|
|
451
583
|
id
|
|
452
584
|
};
|
|
453
585
|
const resolved = interpolateDeep(def, context);
|
|
454
|
-
const publishing = resolvePublishing(def.publishing, rootPublishing, context);
|
|
586
|
+
const publishing = resolvePublishing(def.publishing, rootPublishing, context, `${contractName}/${id}`);
|
|
455
587
|
return {
|
|
456
588
|
id,
|
|
457
589
|
tool: resolved.tool,
|
|
@@ -478,18 +610,15 @@ function resolveArtifact(id, def, rootDir, sdkDir, contractName, contractContext
|
|
|
478
610
|
* resolved relative to this.
|
|
479
611
|
* @param sdkDir - Absolute path to the SDK output root (`<dist>/sdk`).
|
|
480
612
|
* @param generators - The full `generators:` map from the raw config.
|
|
481
|
-
* @param githubCtx - `{ owner, repo }`, exposed to templates as
|
|
482
|
-
* `{github.owner}`/`{github.repo}`.
|
|
483
613
|
* @param vars - The `vars:` tree from the raw config, exposed as
|
|
484
614
|
* `{vars.*}`.
|
|
485
615
|
* @param rootPublishing - The required root-level `publishing:` config from the
|
|
486
616
|
* config file.
|
|
487
617
|
* @returns The fully resolved contract.
|
|
488
618
|
*/
|
|
489
|
-
function resolveContract(input, rootDir, sdkDir, generators,
|
|
619
|
+
function resolveContract(input, rootDir, sdkDir, generators, vars, rootPublishing) {
|
|
490
620
|
const context = buildTemplateContext({
|
|
491
621
|
service: input.name,
|
|
492
|
-
github: githubCtx,
|
|
493
622
|
vars
|
|
494
623
|
});
|
|
495
624
|
const artifacts = input.artifacts.map((ref) => {
|
|
@@ -525,7 +654,7 @@ function loadConfig(configPath) {
|
|
|
525
654
|
const specsDir = raw.paths.specs ? path.resolve(rootDir, raw.paths.specs) : path.join(distDir, "specs");
|
|
526
655
|
const docsDir = raw.paths.docs ? path.resolve(rootDir, raw.paths.docs) : path.join(distDir, "docs");
|
|
527
656
|
const sdkDir = raw.paths.sdk ? path.resolve(rootDir, raw.paths.sdk) : path.join(distDir, "sdk");
|
|
528
|
-
const contracts = raw.contracts.map((contract) => resolveContract(contract, rootDir, sdkDir, raw.generators, raw.
|
|
657
|
+
const contracts = raw.contracts.map((contract) => resolveContract(contract, rootDir, sdkDir, raw.generators, raw.vars, raw.publishing));
|
|
529
658
|
return {
|
|
530
659
|
configVersion: raw.configVersion,
|
|
531
660
|
rootDir,
|
|
@@ -533,9 +662,9 @@ function loadConfig(configPath) {
|
|
|
533
662
|
dist: distDir,
|
|
534
663
|
specs: specsDir,
|
|
535
664
|
docs: docsDir,
|
|
536
|
-
sdk: sdkDir
|
|
665
|
+
sdk: sdkDir,
|
|
666
|
+
specFormat: raw.paths.specFormat
|
|
537
667
|
},
|
|
538
|
-
github: raw.github,
|
|
539
668
|
vars: raw.vars,
|
|
540
669
|
docs: raw.docs,
|
|
541
670
|
contracts,
|
|
@@ -559,20 +688,19 @@ function loadConfig(configPath) {
|
|
|
559
688
|
* `{service}`.
|
|
560
689
|
* @param version - The resolved SDK version, exposed to the template as
|
|
561
690
|
* `{version}`.
|
|
562
|
-
* @param github - `{ owner, repo }`, exposed as `{github.owner}`/
|
|
563
|
-
* `{github.repo}`.
|
|
564
691
|
* @param vars - The config's `vars:` tree, exposed as `{vars.*}`.
|
|
565
692
|
* @returns The rendered tag name.
|
|
566
693
|
*/
|
|
567
|
-
function renderArtifactTag(artifact, contractName, version,
|
|
694
|
+
function renderArtifactTag(artifact, contractName, version, vars) {
|
|
568
695
|
const context = buildTemplateContext({
|
|
569
696
|
service: contractName,
|
|
570
697
|
id: artifact.id,
|
|
571
698
|
version,
|
|
572
|
-
github,
|
|
573
699
|
vars
|
|
574
700
|
});
|
|
575
|
-
|
|
701
|
+
const tag = interpolate(artifact.publishing.tagTemplate, context);
|
|
702
|
+
assertSafeRefName(tag, `publishing.tag for artifact "${contractName}/${artifact.id}"`);
|
|
703
|
+
return tag;
|
|
576
704
|
}
|
|
577
705
|
//#endregion
|
|
578
706
|
//#region src/config/resolve-config-file.ts
|
|
@@ -604,6 +732,46 @@ function resolveConfigPath(cwd) {
|
|
|
604
732
|
throw new Error(`No CLI config found in ${cwd} - looked for: ${CONFIG_FILENAMES.join(", ")}. Create one of these, or pass --config <path>.`);
|
|
605
733
|
}
|
|
606
734
|
//#endregion
|
|
735
|
+
//#region src/config/spec-format.ts
|
|
736
|
+
/**
|
|
737
|
+
* The "primary" format among a `paths.specFormat` list - the one SDK generation,
|
|
738
|
+
* version/hash resolution, and the docs site actually read from when more than
|
|
739
|
+
* one format is configured. By convention, that's whichever format was listed
|
|
740
|
+
* first.
|
|
741
|
+
*
|
|
742
|
+
* @param formats - `config.paths.specFormat` (always non-empty).
|
|
743
|
+
* @returns The primary format.
|
|
744
|
+
*/
|
|
745
|
+
function primarySpecFormat(formats) {
|
|
746
|
+
return formats[0];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* The filename a contract's bundled spec is written to/read from, e.g.
|
|
750
|
+
* `auth.json` or `auth.yaml` - one place computing this so `bundle`, `generate`,
|
|
751
|
+
* and `docs generate` can't disagree about the extension.
|
|
752
|
+
*
|
|
753
|
+
* @param contractName - The contract's `name`.
|
|
754
|
+
* @param format - `config.paths.specFormat`.
|
|
755
|
+
* @returns The filename (no directory), e.g. `"auth.yaml"`.
|
|
756
|
+
*/
|
|
757
|
+
function specFilename(contractName, format) {
|
|
758
|
+
return `${contractName}.${format}`;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Parses a bundled spec's raw file contents according to its configured format.
|
|
762
|
+
* JSON and YAML are both valid inputs to Redocly/openapi-generator/
|
|
763
|
+
* openapi-typescript, so seagull's own parsing has to match: `JSON.parse`
|
|
764
|
+
* rejects trailing commas and comments that valid YAML permits, and would
|
|
765
|
+
* silently misparse a YAML document that happens to look JSON-ish.
|
|
766
|
+
*
|
|
767
|
+
* @param raw - The raw bundled spec file contents.
|
|
768
|
+
* @param format - `config.paths.specFormat`.
|
|
769
|
+
* @returns The parsed document.
|
|
770
|
+
*/
|
|
771
|
+
function parseBundledSpec(raw, format) {
|
|
772
|
+
return format === "json" ? JSON.parse(raw) : parse(raw, { merge: true });
|
|
773
|
+
}
|
|
774
|
+
//#endregion
|
|
607
775
|
//#region src/generator/generator.ts
|
|
608
776
|
/**
|
|
609
777
|
* The root primitive every concrete SDK generator implements.
|
|
@@ -677,8 +845,7 @@ function run(command, args, cwd) {
|
|
|
677
845
|
return new Promise((resolvePromise, reject) => {
|
|
678
846
|
spawn(command, args, {
|
|
679
847
|
cwd,
|
|
680
|
-
stdio: "inherit"
|
|
681
|
-
shell: true
|
|
848
|
+
stdio: "inherit"
|
|
682
849
|
}).on("close", (code) => {
|
|
683
850
|
if (code === 0) {
|
|
684
851
|
resolvePromise();
|
|
@@ -700,8 +867,7 @@ function run(command, args, cwd) {
|
|
|
700
867
|
function runSync(command, args, cwd) {
|
|
701
868
|
return spawnSync(command, args, {
|
|
702
869
|
cwd,
|
|
703
|
-
stdio: "inherit"
|
|
704
|
-
shell: true
|
|
870
|
+
stdio: "inherit"
|
|
705
871
|
}).status ?? 1;
|
|
706
872
|
}
|
|
707
873
|
//#endregion
|
|
@@ -732,106 +898,6 @@ function resolveBinPath(pkgName, binName) {
|
|
|
732
898
|
return path.join(pkgDir, bin);
|
|
733
899
|
}
|
|
734
900
|
//#endregion
|
|
735
|
-
//#region src/git/git.ts
|
|
736
|
-
/**
|
|
737
|
-
* Execute a git command in a given working directory and return the result.
|
|
738
|
-
*
|
|
739
|
-
* @param args - The command-line arguments to pass to the git command.
|
|
740
|
-
* @param cwd - The working directory in which to execute the git command.
|
|
741
|
-
* @returns The result of the git command.
|
|
742
|
-
*
|
|
743
|
-
* @see {@link GitResult} - The result of executing the git command.
|
|
744
|
-
*/
|
|
745
|
-
function git(args, cwd) {
|
|
746
|
-
const result = spawnSync("git", args, {
|
|
747
|
-
cwd,
|
|
748
|
-
encoding: "utf8"
|
|
749
|
-
});
|
|
750
|
-
return {
|
|
751
|
-
status: result.status ?? 1,
|
|
752
|
-
stdout: (result.stdout ?? "").trim(),
|
|
753
|
-
stderr: (result.stderr ?? "").trim()
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Check if a remote branch exists in the given repository.
|
|
758
|
-
*
|
|
759
|
-
* @param repoRoot - The root directory of the repository.
|
|
760
|
-
* @param branch - The name of the branch to check.
|
|
761
|
-
* @returns Whether the remote branch exists (true) or not (false).
|
|
762
|
-
*/
|
|
763
|
-
function remoteBranchExists(repoRoot, branch) {
|
|
764
|
-
return git([
|
|
765
|
-
"ls-remote",
|
|
766
|
-
"--exit-code",
|
|
767
|
-
"--heads",
|
|
768
|
-
"origin",
|
|
769
|
-
branch
|
|
770
|
-
], repoRoot).status === 0;
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Check if a remote tag exists in the given repository.
|
|
774
|
-
*
|
|
775
|
-
* @param repoRoot - The root directory of the repository.
|
|
776
|
-
* @param tag - The name of the tag to check.
|
|
777
|
-
* @returns Whether the remote tag exists (true) or not (false).
|
|
778
|
-
*/
|
|
779
|
-
function tagExists(repoRoot, tag) {
|
|
780
|
-
return git([
|
|
781
|
-
"ls-remote",
|
|
782
|
-
"--exit-code",
|
|
783
|
-
"--tags",
|
|
784
|
-
"origin",
|
|
785
|
-
tag
|
|
786
|
-
], repoRoot).status === 0;
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* Fetch a single tag's object from origin into the local repo, without
|
|
790
|
-
* fetching the rest of history/tags.
|
|
791
|
-
*
|
|
792
|
-
* @param repoRoot - The root directory of the repository.
|
|
793
|
-
* @param tag - The name of the tag to fetch.
|
|
794
|
-
* @returns The result of the underlying `git fetch` command.
|
|
795
|
-
*/
|
|
796
|
-
function fetchTag(repoRoot, tag) {
|
|
797
|
-
return git([
|
|
798
|
-
"fetch",
|
|
799
|
-
"origin",
|
|
800
|
-
`refs/tags/${tag}:refs/tags/${tag}`,
|
|
801
|
-
"--force"
|
|
802
|
-
], repoRoot);
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Read a single file's content as it existed at a given git tag, without
|
|
806
|
-
* checking out a worktree.
|
|
807
|
-
*
|
|
808
|
-
* Returns `null` (rather than throwing) both when the tag can't be fetched and
|
|
809
|
-
* when the tag exists but doesn't contain the requested file - the latter is
|
|
810
|
-
* expected for tags published before that file was introduced, and callers
|
|
811
|
-
* should treat "unknown" the same as "no mismatch to report".
|
|
812
|
-
*
|
|
813
|
-
* @param repoRoot - The root directory of the repository.
|
|
814
|
-
* @param tag - The tag to read the file from.
|
|
815
|
-
* @param filePath - The path of the file within that tag's tree.
|
|
816
|
-
* @returns The file's content, or `null` if it couldn't be read.
|
|
817
|
-
*/
|
|
818
|
-
function readFileAtTag(repoRoot, tag, filePath) {
|
|
819
|
-
if (fetchTag(repoRoot, tag).status !== 0) return null;
|
|
820
|
-
const show = git(["show", `${tag}:${filePath}`], repoRoot);
|
|
821
|
-
return show.status === 0 ? show.stdout.trim() : null;
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Require that a git command succeeded, throwing an error with the given
|
|
825
|
-
* message if it did not.
|
|
826
|
-
*
|
|
827
|
-
* @param result - The result of the git command to check.
|
|
828
|
-
* @param message - The error message to throw if the command failed.
|
|
829
|
-
* @throws Error if the git command failed (non-zero exit code).
|
|
830
|
-
*/
|
|
831
|
-
function requireOk(result, message) {
|
|
832
|
-
if (result.status !== 0) throw new Error(`${message}: ${result.stderr || result.stdout}`);
|
|
833
|
-
}
|
|
834
|
-
//#endregion
|
|
835
901
|
//#region src/version/version.ts
|
|
836
902
|
/**
|
|
837
903
|
* Resolves the version to stamp onto a single contract's generated SDK
|
|
@@ -861,12 +927,14 @@ function resolveVersion(spec, contractName) {
|
|
|
861
927
|
return specVersion.replace(/^v/, "");
|
|
862
928
|
}
|
|
863
929
|
/**
|
|
864
|
-
* Computes a stable content hash of a bundled OpenAPI document's raw
|
|
930
|
+
* Computes a stable content hash of a bundled OpenAPI document's raw file
|
|
931
|
+
* contents (JSON or YAML, whichever `paths.specFormat` produced).
|
|
932
|
+
*
|
|
865
933
|
* Stamped alongside `VERSION` into every generated SDK package so
|
|
866
934
|
* `publish-sdk.ts` can tell a genuine no-op republish (same spec, same version)
|
|
867
935
|
* apart from a spec that changed without its `info.version` being bumped.
|
|
868
936
|
*
|
|
869
|
-
* @param raw - The raw bundled spec file contents
|
|
937
|
+
* @param raw - The raw bundled spec file contents.
|
|
870
938
|
* @returns A `sha256` hex digest of the raw contents.
|
|
871
939
|
*/
|
|
872
940
|
function hashSpec(raw) {
|
|
@@ -883,19 +951,19 @@ function hashSpec(raw) {
|
|
|
883
951
|
* come from the artifact's resolved `publishing:` config, so a repo that
|
|
884
952
|
* overrides its registry sees that reflected here automatically.
|
|
885
953
|
*
|
|
886
|
-
* @param args - The contract, artifact, version, and
|
|
887
|
-
*
|
|
954
|
+
* @param args - The contract, artifact, version, and vars coordinates to render
|
|
955
|
+
* for.
|
|
888
956
|
* @returns The rendered README content.
|
|
889
957
|
*/
|
|
890
|
-
function renderDefaultReadme({ contract, artifact, version,
|
|
958
|
+
function renderDefaultReadme({ contract, artifact, version, vars }) {
|
|
891
959
|
return `${`# ${contract.title} - ${label(artifact)}
|
|
892
960
|
|
|
893
|
-
> Generated from \`${contract.entrypointRelative}\` in
|
|
961
|
+
> Generated from \`${contract.entrypointRelative}\` in ${artifact.publishing.repositoryUrl}.
|
|
894
962
|
> Do not edit by hand - this package is regenerated and republished on every release.
|
|
895
963
|
|
|
896
964
|
Version: \`${version}\`
|
|
897
965
|
Source branch: \`${artifact.branch}\`
|
|
898
|
-
`}\n${body(contract, artifact, version,
|
|
966
|
+
`}\n${body(contract, artifact, version, vars)}\n`;
|
|
899
967
|
}
|
|
900
968
|
function label(artifact) {
|
|
901
969
|
return `${{
|
|
@@ -904,11 +972,11 @@ function label(artifact) {
|
|
|
904
972
|
java: "Java"
|
|
905
973
|
}[artifact.lang]} ${artifact.kind === "client" ? "Client SDK" : artifact.lang === "typescript" ? "Server Types" : "Server Stubs"}`;
|
|
906
974
|
}
|
|
907
|
-
function body(contract, artifact, version,
|
|
975
|
+
function body(contract, artifact, version, vars) {
|
|
908
976
|
switch (`${artifact.lang}-${artifact.kind}`) {
|
|
909
977
|
case "typescript-client": return tsClient(artifact, version);
|
|
910
978
|
case "typescript-server": return tsServer(artifact, version);
|
|
911
|
-
case "go-client": return goClient(contract, artifact,
|
|
979
|
+
case "go-client": return goClient(contract, artifact, vars);
|
|
912
980
|
case "go-server": return goServer(artifact);
|
|
913
981
|
case "java-client": return javaClient(artifact, version);
|
|
914
982
|
case "java-server": return javaServer(artifact, version);
|
|
@@ -966,8 +1034,8 @@ app.post("/login", (req, res) => {
|
|
|
966
1034
|
\`\`\`
|
|
967
1035
|
`;
|
|
968
1036
|
}
|
|
969
|
-
function goClient(contract, artifact,
|
|
970
|
-
const exampleTag = renderArtifactTag(artifact, contract.name, "<version>",
|
|
1037
|
+
function goClient(contract, artifact, vars) {
|
|
1038
|
+
const exampleTag = renderArtifactTag(artifact, contract.name, "<version>", vars);
|
|
971
1039
|
return `## Install
|
|
972
1040
|
|
|
973
1041
|
Go has no package registry, so this module is pulled directly from its
|
|
@@ -1109,13 +1177,12 @@ public class SomeController implements SomeApi {
|
|
|
1109
1177
|
* If the artifact has a `readme:` path configured (resolved at config-load time
|
|
1110
1178
|
* to `artifact.readmeTemplate`), that file is read and interpolated with the
|
|
1111
1179
|
* same `{...}` placeholder engine naming templates use - `{service}`,
|
|
1112
|
-
* `{title}`, `{version}`, `{vars.*}`, `{
|
|
1113
|
-
*
|
|
1180
|
+
* `{title}`, `{version}`, `{vars.*}`, plus `{artifact.*}`
|
|
1181
|
+
* (id/lang/kind/package/goModule/goPackageName/maven.groupId/
|
|
1114
1182
|
* maven.artifactId/branch/tag/npmRegistry/mavenRepositoryUrl). Otherwise,
|
|
1115
1183
|
* falls back to a built-in default template for the artifact's language/kind.
|
|
1116
1184
|
*
|
|
1117
|
-
* @param args - The contract, artifact, version, and
|
|
1118
|
-
* render for.
|
|
1185
|
+
* @param args - The contract, artifact, version, and vars context to render for.
|
|
1119
1186
|
* @returns The rendered README content.
|
|
1120
1187
|
*/
|
|
1121
1188
|
async function renderReadme(args) {
|
|
@@ -1124,7 +1191,6 @@ async function renderReadme(args) {
|
|
|
1124
1191
|
service: args.contract.name,
|
|
1125
1192
|
title: args.contract.title,
|
|
1126
1193
|
version: args.version,
|
|
1127
|
-
github: args.github,
|
|
1128
1194
|
vars: args.vars,
|
|
1129
1195
|
artifact: {
|
|
1130
1196
|
id: args.artifact.id,
|
|
@@ -1135,7 +1201,7 @@ async function renderReadme(args) {
|
|
|
1135
1201
|
goPackageName: args.artifact.goPackageName,
|
|
1136
1202
|
maven: args.artifact.maven,
|
|
1137
1203
|
branch: args.artifact.branch,
|
|
1138
|
-
tag: renderArtifactTag(args.artifact, args.contract.name, args.version, args.
|
|
1204
|
+
tag: renderArtifactTag(args.artifact, args.contract.name, args.version, args.vars),
|
|
1139
1205
|
npmRegistry: args.artifact.publishing.npmRegistry,
|
|
1140
1206
|
mavenRepositoryUrl: args.artifact.publishing.mavenRepositoryUrl
|
|
1141
1207
|
}
|
|
@@ -1160,7 +1226,7 @@ const GENERATED_HEADER = "# AUTO-GENERATED by Seagull from its config and 'redoc
|
|
|
1160
1226
|
*/
|
|
1161
1227
|
async function syncRedoclyConfig(config) {
|
|
1162
1228
|
const basePath = path.join(config.rootDir, "redocly.base.yaml");
|
|
1163
|
-
const base = parse(await readFile(basePath, "utf8"));
|
|
1229
|
+
const base = parse(await readFile(basePath, "utf8"), { merge: true });
|
|
1164
1230
|
const apis = Object.fromEntries(config.contracts.map((contract) => {
|
|
1165
1231
|
const typesArtifact = contract.artifacts.find((artifact) => artifact.tool === "openapi-typescript");
|
|
1166
1232
|
return [`${contract.name}@v1`, {
|
|
@@ -1359,6 +1425,6 @@ var OpenApiTypescriptGenerator = class extends Generator {
|
|
|
1359
1425
|
}
|
|
1360
1426
|
};
|
|
1361
1427
|
//#endregion
|
|
1362
|
-
export { CONFIG_FILENAMES, CONFIG_SCHEMA_VERSION, Generator, GeneratorRegistry, OpenApiGeneratorCli, OpenApiTypescriptGenerator, git, hashSpec, loadConfig, readFileAtTag, remoteBranchExists, renderArtifactTag, renderReadme, requireOk, resolveBinPath, resolveConfigPath, resolveVersion, run, runSync, syncRedoclyConfig, tagExists };
|
|
1428
|
+
export { CONFIG_FILENAMES, CONFIG_SCHEMA_VERSION, Generator, GeneratorRegistry, OpenApiGeneratorCli, OpenApiTypescriptGenerator, assertSafeRefName, git, gitRefNameSchema, hashSpec, loadConfig, parseBundledSpec, primarySpecFormat, readFileAtTag, remoteBranchExists, renderArtifactTag, renderReadme, requireOk, resolveBinPath, resolveConfigPath, resolveVersion, run, runSync, specFilename, syncRedoclyConfig, tagExists };
|
|
1363
1429
|
|
|
1364
1430
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octalmesh/seagull-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The Seagull engine
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The Seagull engine.",
|
|
5
5
|
"author": "OctalMesh <contact@octalmesh.com> (https://octalmesh.com)",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://developers.octalmesh.com/seagull",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@openapitools/openapi-generator-cli": "^2.41.0",
|
|
35
|
-
"@redocly/cli": "^2.
|
|
35
|
+
"@redocly/cli": "^2.52.1",
|
|
36
36
|
"openapi-typescript": "^7.13.0",
|
|
37
|
-
"yaml": "^2.9.
|
|
38
|
-
"zod": "^4.
|
|
37
|
+
"yaml": "^2.9.1",
|
|
38
|
+
"zod": "^4.6.4"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsdown",
|