@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +173 -39
  3. package/dist/index.mjs +218 -152
  4. package/package.json +6 -6
  5. package/src/config/loader.test.ts +505 -0
  6. package/src/config/loader.ts +31 -12
  7. package/src/config/publishing.test.ts +92 -0
  8. package/src/config/publishing.ts +9 -5
  9. package/src/config/resolve-config-file.test.ts +60 -0
  10. package/src/config/schema.test.ts +466 -0
  11. package/src/config/schema.ts +16 -10
  12. package/src/config/spec-format.test.ts +54 -0
  13. package/src/config/spec-format.ts +48 -0
  14. package/src/config/template.test.ts +168 -0
  15. package/src/config/types.ts +3 -3
  16. package/src/generator/generator.test.ts +59 -0
  17. package/src/generator/registry.test.ts +84 -0
  18. package/src/generator/types.ts +9 -14
  19. package/src/generators/openapi-generator-cli/openapi-generator-cli.generator.test.ts +259 -0
  20. package/src/generators/openapi-generator-cli/patchers/go-module.patcher.test.ts +119 -0
  21. package/src/generators/openapi-generator-cli/patchers/maven.patcher.test.ts +141 -0
  22. package/src/generators/openapi-generator-cli/patchers/npm.patcher.test.ts +132 -0
  23. package/src/generators/openapi-typescript/openapi-typescript.generator.test.ts +190 -0
  24. package/src/git/git.test.ts +234 -0
  25. package/src/git/git.ts +50 -4
  26. package/src/index.ts +8 -0
  27. package/src/process/exec.test.ts +103 -0
  28. package/src/process/exec.ts +1 -2
  29. package/src/process/resolve-bin.test.ts +43 -0
  30. package/src/readme/default-templates.test.ts +179 -0
  31. package/src/readme/default-templates.ts +5 -10
  32. package/src/readme/readme-renderer.test.ts +121 -0
  33. package/src/readme/readme-renderer.ts +3 -7
  34. package/src/redocly/redocly-sync.test.ts +190 -0
  35. package/src/redocly/redocly-sync.ts +3 -1
  36. package/src/test-support/fixtures.ts +65 -0
  37. package/src/version/version.test.ts +93 -0
  38. package/src/version/version.ts +4 -2
@@ -0,0 +1,259 @@
1
+ import { mkdtemp, rm, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import type { GenerateContext } from "../../generator/types";
8
+ import { makeArtifact, makeContract } from "../../test-support/fixtures";
9
+
10
+ const runMock = vi.fn((..._args: unknown[]) => Promise.resolve());
11
+ const resolveBinPathMock = vi.fn(
12
+ (..._args: unknown[]) => "/fake/bin/openapi-generator-cli.js",
13
+ );
14
+ const patchMocks = {
15
+ go: vi.fn((..._args: unknown[]) => Promise.resolve()),
16
+ typescript: vi.fn((..._args: unknown[]) => Promise.resolve()),
17
+ java: vi.fn((..._args: unknown[]) => Promise.resolve()),
18
+ };
19
+
20
+ vi.mock("../../process/exec", () => ({
21
+ run: (...a: unknown[]) => runMock(...a),
22
+ }));
23
+ vi.mock("../../process/resolve-bin", () => ({
24
+ resolveBinPath: (...a: unknown[]) => resolveBinPathMock(...a),
25
+ }));
26
+ vi.mock("./patchers/go-module.patcher", () => ({
27
+ GoModulePatcher: class {
28
+ patch = patchMocks.go;
29
+ },
30
+ }));
31
+ vi.mock("./patchers/npm.patcher", () => ({
32
+ NpmPackagePatcher: class {
33
+ patch = patchMocks.typescript;
34
+ },
35
+ }));
36
+ vi.mock("./patchers/maven.patcher", () => ({
37
+ MavenPomPatcher: class {
38
+ patch = patchMocks.java;
39
+ },
40
+ }));
41
+
42
+ const { OpenApiGeneratorCli } =
43
+ await import("./openapi-generator-cli.generator");
44
+
45
+ function makeCtx(overrides: Partial<GenerateContext> = {}): GenerateContext {
46
+ return {
47
+ rootDir: "/repo",
48
+ contract: makeContract(),
49
+ artifact: makeArtifact(),
50
+ version: "1.0.0",
51
+ specInputPath: "/repo/dist/specs/auth.json",
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ describe("OpenApiGeneratorCli", () => {
57
+ let dir: string;
58
+
59
+ beforeEach(async () => {
60
+ dir = await mkdtemp(path.join(tmpdir(), "seagull-ogc-"));
61
+ runMock.mockClear();
62
+ resolveBinPathMock.mockClear();
63
+ patchMocks.go.mockClear();
64
+ patchMocks.typescript.mockClear();
65
+ patchMocks.java.mockClear();
66
+ });
67
+
68
+ afterEach(async () => {
69
+ await rm(dir, { recursive: true, force: true });
70
+ });
71
+
72
+ it("exposes 'tool' as \"openapi-generator\"", () => {
73
+ expect(new OpenApiGeneratorCli().tool).toBe("openapi-generator");
74
+ });
75
+
76
+ it("creates the artifact's output directory", async () => {
77
+ const outputDir = path.join(dir, "auth", "ts-client");
78
+
79
+ await new OpenApiGeneratorCli().generate(
80
+ makeCtx({ artifact: makeArtifact({ outputDir }) }),
81
+ );
82
+
83
+ await expect(stat(outputDir)).resolves.toMatchObject({});
84
+ });
85
+
86
+ it("throws when the artifact has no 'generator' value", async () => {
87
+ await expect(
88
+ new OpenApiGeneratorCli().generate(
89
+ makeCtx({
90
+ artifact: makeArtifact({ id: "weird", generator: undefined }),
91
+ }),
92
+ ),
93
+ ).rejects.toThrow(
94
+ /Artifact "weird" uses tool "openapi-generator" but has no "generator" value/,
95
+ );
96
+ });
97
+
98
+ it("resolves the openapi-generator-cli bin and invokes it with the expected flags", async () => {
99
+ const outputDir = path.join(dir, "auth", "ts-client");
100
+
101
+ await new OpenApiGeneratorCli().generate(
102
+ makeCtx({
103
+ artifact: makeArtifact({ outputDir, generator: "typescript-fetch" }),
104
+ specInputPath: "/repo/dist/specs/auth.json",
105
+ }),
106
+ );
107
+
108
+ expect(resolveBinPathMock).toHaveBeenCalledWith(
109
+ "@openapitools/openapi-generator-cli",
110
+ "openapi-generator-cli",
111
+ );
112
+ expect(runMock).toHaveBeenCalledWith(
113
+ "node",
114
+ [
115
+ "/fake/bin/openapi-generator-cli.js",
116
+ "generate",
117
+ "-i",
118
+ "/repo/dist/specs/auth.json",
119
+ "-g",
120
+ "typescript-fetch",
121
+ "-o",
122
+ outputDir,
123
+ expect.stringMatching(/^--additional-properties=/),
124
+ ],
125
+ "/repo",
126
+ );
127
+ });
128
+
129
+ it("derives npmName from artifact.package for typescript artifacts", async () => {
130
+ await new OpenApiGeneratorCli().generate(
131
+ makeCtx({
132
+ artifact: makeArtifact({
133
+ outputDir: dir,
134
+ lang: "typescript",
135
+ package: "@octalmesh/auth-client",
136
+ }),
137
+ }),
138
+ );
139
+
140
+ const [, args] = runMock.mock.calls[0]!;
141
+ const flag = (args as string[]).find((a) =>
142
+ a.startsWith("--additional-properties="),
143
+ )!;
144
+
145
+ expect(flag).toContain("npmName=@octalmesh/auth-client");
146
+ });
147
+
148
+ it("derives packageName from artifact.goPackageName for go artifacts", async () => {
149
+ await new OpenApiGeneratorCli().generate(
150
+ makeCtx({
151
+ artifact: makeArtifact({
152
+ outputDir: dir,
153
+ lang: "go",
154
+ package: undefined,
155
+ goModule: "github.com/octalmesh/ows-contracts",
156
+ goPackageName: "authclient",
157
+ }),
158
+ }),
159
+ );
160
+
161
+ const [, args] = runMock.mock.calls[0]!;
162
+ const flag = (args as string[]).find((a) =>
163
+ a.startsWith("--additional-properties="),
164
+ )!;
165
+
166
+ expect(flag).toContain("packageName=authclient");
167
+ });
168
+
169
+ it("derives groupId/artifactId/invokerPackage/apiPackage/modelPackage for java artifacts", async () => {
170
+ await new OpenApiGeneratorCli().generate(
171
+ makeCtx({
172
+ artifact: makeArtifact({
173
+ outputDir: dir,
174
+ lang: "java",
175
+ kind: "client",
176
+ package: undefined,
177
+ maven: { groupId: "com.octalmesh.auth", artifactId: "auth-client" },
178
+ }),
179
+ }),
180
+ );
181
+
182
+ const [, args] = runMock.mock.calls[0]!;
183
+ const flag = (args as string[]).find((a) =>
184
+ a.startsWith("--additional-properties="),
185
+ )!;
186
+
187
+ expect(flag).toContain("groupId=com.octalmesh.auth");
188
+ expect(flag).toContain("artifactId=auth-client");
189
+ expect(flag).toContain("invokerPackage=com.octalmesh.auth.client");
190
+ expect(flag).toContain("apiPackage=com.octalmesh.auth.client.api");
191
+ expect(flag).toContain("modelPackage=com.octalmesh.auth.client.model");
192
+ });
193
+
194
+ it("layers the artifact's own additionalProperties on top of derived ones, with explicit values winning", async () => {
195
+ await new OpenApiGeneratorCli().generate(
196
+ makeCtx({
197
+ artifact: makeArtifact({
198
+ outputDir: dir,
199
+ lang: "typescript",
200
+ package: "@octalmesh/auth-client",
201
+ additionalProperties: {
202
+ npmName: "@octalmesh/explicit-override",
203
+ supportsES6: true,
204
+ },
205
+ }),
206
+ }),
207
+ );
208
+
209
+ const [, args] = runMock.mock.calls[0]!;
210
+ const flag = (args as string[]).find((a) =>
211
+ a.startsWith("--additional-properties="),
212
+ )!;
213
+
214
+ expect(flag).toContain("npmName=@octalmesh/explicit-override");
215
+ expect(flag).toContain("supportsES6=true");
216
+ expect(flag).not.toContain("npmName=@octalmesh/auth-client,");
217
+ });
218
+
219
+ it("dispatches to the go patcher for lang 'go'", async () => {
220
+ await new OpenApiGeneratorCli().generate(
221
+ makeCtx({
222
+ artifact: makeArtifact({
223
+ outputDir: dir,
224
+ lang: "go",
225
+ package: undefined,
226
+ }),
227
+ }),
228
+ );
229
+
230
+ expect(patchMocks.go).toHaveBeenCalledOnce();
231
+ expect(patchMocks.typescript).not.toHaveBeenCalled();
232
+ expect(patchMocks.java).not.toHaveBeenCalled();
233
+ });
234
+
235
+ it("dispatches to the npm patcher for lang 'typescript'", async () => {
236
+ await new OpenApiGeneratorCli().generate(
237
+ makeCtx({
238
+ artifact: makeArtifact({ outputDir: dir, lang: "typescript" }),
239
+ }),
240
+ );
241
+
242
+ expect(patchMocks.typescript).toHaveBeenCalledOnce();
243
+ });
244
+
245
+ it("dispatches to the maven patcher for lang 'java'", async () => {
246
+ await new OpenApiGeneratorCli().generate(
247
+ makeCtx({
248
+ artifact: makeArtifact({
249
+ outputDir: dir,
250
+ lang: "java",
251
+ package: undefined,
252
+ maven: { groupId: "com.octalmesh.auth", artifactId: "auth-client" },
253
+ }),
254
+ }),
255
+ );
256
+
257
+ expect(patchMocks.java).toHaveBeenCalledOnce();
258
+ });
259
+ });
@@ -0,0 +1,119 @@
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+
7
+ import type { GenerateContext } from "../../../generator/types";
8
+ import { makeArtifact, makeContract } from "../../../test-support/fixtures";
9
+ import { GoModulePatcher } from "./go-module.patcher";
10
+
11
+ function makeCtx(overrides: Partial<GenerateContext> = {}): GenerateContext {
12
+ return {
13
+ rootDir: "/repo",
14
+ contract: makeContract(),
15
+ artifact: makeArtifact({
16
+ lang: "go",
17
+ package: undefined,
18
+ goModule: "github.com/octalmesh/ows-contracts",
19
+ goPackageName: "authclient",
20
+ }),
21
+ version: "1.0.0",
22
+ specInputPath: "/repo/dist/specs/auth.json",
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ describe("GoModulePatcher", () => {
28
+ let dir: string;
29
+ const patcher = new GoModulePatcher();
30
+
31
+ beforeEach(async () => {
32
+ dir = await mkdtemp(path.join(tmpdir(), "seagull-gomod-"));
33
+ });
34
+
35
+ afterEach(async () => {
36
+ await rm(dir, { recursive: true, force: true });
37
+ });
38
+
39
+ it("rewrites the 'module' line of an existing go.mod to the artifact's goModule", async () => {
40
+ await writeFile(
41
+ path.join(dir, "go.mod"),
42
+ ["module openapi", "", "go 1.21", ""].join("\n"),
43
+ );
44
+
45
+ await patcher.patch(
46
+ makeCtx({
47
+ artifact: makeArtifact({
48
+ outputDir: dir,
49
+ lang: "go",
50
+ package: undefined,
51
+ goModule: "github.com/octalmesh/ows-contracts",
52
+ }),
53
+ }),
54
+ );
55
+
56
+ const content = await readFile(path.join(dir, "go.mod"), "utf8");
57
+
58
+ expect(content).toContain("module github.com/octalmesh/ows-contracts");
59
+ expect(content).toContain("go 1.21");
60
+ });
61
+
62
+ it("only replaces the module line, leaving the rest of the file untouched", async () => {
63
+ await writeFile(
64
+ path.join(dir, "go.mod"),
65
+ ["module old/path", "", "require (", "\tfoo v1.0.0", ")", ""].join("\n"),
66
+ );
67
+
68
+ await patcher.patch(
69
+ makeCtx({
70
+ artifact: makeArtifact({
71
+ outputDir: dir,
72
+ lang: "go",
73
+ package: undefined,
74
+ goModule: "new/module/path",
75
+ }),
76
+ }),
77
+ );
78
+
79
+ const content = await readFile(path.join(dir, "go.mod"), "utf8");
80
+
81
+ expect(content).toBe(
82
+ ["module new/module/path", "", "require (", "\tfoo v1.0.0", ")", ""].join(
83
+ "\n",
84
+ ),
85
+ );
86
+ });
87
+
88
+ it("does nothing (no throw) when the artifact has no goModule", async () => {
89
+ await expect(
90
+ patcher.patch(
91
+ makeCtx({
92
+ artifact: makeArtifact({
93
+ outputDir: dir,
94
+ lang: "go",
95
+ package: undefined,
96
+ goModule: undefined,
97
+ }),
98
+ }),
99
+ ),
100
+ ).resolves.toBeUndefined();
101
+ });
102
+
103
+ it("silently does nothing when go.mod doesn't exist (e.g. go-server templates)", async () => {
104
+ await mkdir(dir, { recursive: true });
105
+
106
+ await expect(
107
+ patcher.patch(
108
+ makeCtx({
109
+ artifact: makeArtifact({
110
+ outputDir: dir,
111
+ lang: "go",
112
+ package: undefined,
113
+ goModule: "github.com/octalmesh/ows-contracts",
114
+ }),
115
+ }),
116
+ ),
117
+ ).resolves.toBeUndefined();
118
+ });
119
+ });
@@ -0,0 +1,141 @@
1
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+
7
+ import type { GenerateContext } from "../../../generator/types";
8
+ import { makeArtifact, makeContract } from "../../../test-support/fixtures";
9
+ import { MavenPomPatcher } from "./maven.patcher";
10
+
11
+ function makeCtx(overrides: Partial<GenerateContext> = {}): GenerateContext {
12
+ return {
13
+ rootDir: "/repo",
14
+ contract: makeContract(),
15
+ artifact: makeArtifact({
16
+ lang: "java",
17
+ package: undefined,
18
+ maven: { groupId: "com.octalmesh.auth", artifactId: "auth-client" },
19
+ }),
20
+ version: "1.0.0",
21
+ specInputPath: "/repo/dist/specs/auth.json",
22
+ ...overrides,
23
+ };
24
+ }
25
+
26
+ describe("MavenPomPatcher", () => {
27
+ let dir: string;
28
+ const patcher = new MavenPomPatcher();
29
+
30
+ beforeEach(async () => {
31
+ dir = await mkdtemp(path.join(tmpdir(), "seagull-pom-"));
32
+ });
33
+
34
+ afterEach(async () => {
35
+ await rm(dir, { recursive: true, force: true });
36
+ });
37
+
38
+ it("replaces the <version> element with the resolved version", async () => {
39
+ await writeFile(
40
+ path.join(dir, "pom.xml"),
41
+ ["<project>", " <version>0.0.0</version>", "</project>", ""].join("\n"),
42
+ );
43
+
44
+ await patcher.patch(
45
+ makeCtx({
46
+ version: "2.5.0",
47
+ artifact: makeArtifact({
48
+ outputDir: dir,
49
+ lang: "java",
50
+ package: undefined,
51
+ }),
52
+ }),
53
+ );
54
+
55
+ const content = await readFile(path.join(dir, "pom.xml"), "utf8");
56
+
57
+ expect(content).toContain("<version>2.5.0</version>");
58
+ });
59
+
60
+ it("appends <distributionManagement> using the artifact's resolved publishing config", async () => {
61
+ await writeFile(
62
+ path.join(dir, "pom.xml"),
63
+ ["<project>", " <version>0.0.0</version>", "</project>", ""].join("\n"),
64
+ );
65
+
66
+ await patcher.patch(
67
+ makeCtx({
68
+ artifact: makeArtifact({
69
+ outputDir: dir,
70
+ lang: "java",
71
+ package: undefined,
72
+ publishing: {
73
+ branch: "sdk/svc-auth/java-client",
74
+ tagTemplate: "svc-{service}-{id}-v{version}",
75
+ repositoryUrl: "https://github.com/OctalMesh/ows-contracts",
76
+ npmRegistry: "https://npm.pkg.github.com",
77
+ npmAccess: "public",
78
+ mavenRepositoryId: "github",
79
+ mavenRepositoryUrl:
80
+ "https://maven.pkg.github.com/OctalMesh/ows-contracts",
81
+ },
82
+ }),
83
+ }),
84
+ );
85
+
86
+ const content = await readFile(path.join(dir, "pom.xml"), "utf8");
87
+
88
+ expect(content).toContain("<distributionManagement>");
89
+ expect(content).toContain("<id>github</id>");
90
+ expect(content).toContain(
91
+ "<url>https://maven.pkg.github.com/OctalMesh/ows-contracts</url>",
92
+ );
93
+ expect(content.indexOf("</distributionManagement>")).toBeLessThan(
94
+ content.indexOf("</project>"),
95
+ );
96
+ });
97
+
98
+ it("does not duplicate <distributionManagement> if it's already present", async () => {
99
+ await writeFile(
100
+ path.join(dir, "pom.xml"),
101
+ [
102
+ "<project>",
103
+ " <version>0.0.0</version>",
104
+ " <distributionManagement>",
105
+ " <repository><id>existing</id></repository>",
106
+ " </distributionManagement>",
107
+ "</project>",
108
+ "",
109
+ ].join("\n"),
110
+ );
111
+
112
+ await patcher.patch(
113
+ makeCtx({
114
+ artifact: makeArtifact({
115
+ outputDir: dir,
116
+ lang: "java",
117
+ package: undefined,
118
+ }),
119
+ }),
120
+ );
121
+
122
+ const content = await readFile(path.join(dir, "pom.xml"), "utf8");
123
+
124
+ expect(content.split("<distributionManagement>").length - 1).toBe(1);
125
+ expect(content).toContain("<id>existing</id>");
126
+ });
127
+
128
+ it("silently does nothing when pom.xml is absent (e.g. Gradle build selected)", async () => {
129
+ await expect(
130
+ patcher.patch(
131
+ makeCtx({
132
+ artifact: makeArtifact({
133
+ outputDir: dir,
134
+ lang: "java",
135
+ package: undefined,
136
+ }),
137
+ }),
138
+ ),
139
+ ).resolves.toBeUndefined();
140
+ });
141
+ });
@@ -0,0 +1,132 @@
1
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+
7
+ import type { GenerateContext } from "../../../generator/types";
8
+ import { makeArtifact, makeContract } from "../../../test-support/fixtures";
9
+ import { NpmPackagePatcher } from "./npm.patcher";
10
+
11
+ function makeCtx(overrides: Partial<GenerateContext> = {}): GenerateContext {
12
+ return {
13
+ rootDir: "/repo",
14
+ contract: makeContract(),
15
+ artifact: makeArtifact(),
16
+ version: "1.0.0",
17
+ specInputPath: "/repo/dist/specs/auth.json",
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ describe("NpmPackagePatcher", () => {
23
+ let dir: string;
24
+ const patcher = new NpmPackagePatcher();
25
+
26
+ beforeEach(async () => {
27
+ dir = await mkdtemp(path.join(tmpdir(), "seagull-npmpkg-"));
28
+ await writeFile(
29
+ path.join(dir, "package.json"),
30
+ JSON.stringify(
31
+ { name: "@octalmesh/auth-client", version: "0.0.0", main: "index.js" },
32
+ null,
33
+ 2,
34
+ ),
35
+ );
36
+ });
37
+
38
+ afterEach(async () => {
39
+ await rm(dir, { recursive: true, force: true });
40
+ });
41
+
42
+ it("sets 'version' to the resolved version", async () => {
43
+ await patcher.patch(
44
+ makeCtx({ version: "3.1.4", artifact: makeArtifact({ outputDir: dir }) }),
45
+ );
46
+
47
+ const pkg = JSON.parse(
48
+ await readFile(path.join(dir, "package.json"), "utf8"),
49
+ ) as Record<string, unknown>;
50
+
51
+ expect(pkg.version).toBe("3.1.4");
52
+ });
53
+
54
+ it("sets 'repository' from artifact.publishing.repositoryUrl, git+ prefixed and .git suffixed", async () => {
55
+ await patcher.patch(
56
+ makeCtx({
57
+ artifact: makeArtifact({
58
+ outputDir: dir,
59
+ publishing: {
60
+ branch: "sdk/svc-auth/ts-client",
61
+ tagTemplate: "svc-{service}-{id}-v{version}",
62
+ repositoryUrl: "https://github.com/OctalMesh/ows-contracts",
63
+ npmRegistry: "https://npm.pkg.github.com",
64
+ npmAccess: "public",
65
+ mavenRepositoryId: "github",
66
+ mavenRepositoryUrl:
67
+ "https://maven.pkg.github.com/OctalMesh/ows-contracts",
68
+ },
69
+ }),
70
+ }),
71
+ );
72
+
73
+ const pkg = JSON.parse(
74
+ await readFile(path.join(dir, "package.json"), "utf8"),
75
+ ) as Record<string, unknown>;
76
+
77
+ expect(pkg.repository).toEqual({
78
+ type: "git",
79
+ url: "git+https://github.com/OctalMesh/ows-contracts.git",
80
+ });
81
+ });
82
+
83
+ it("sets 'publishConfig' from artifact.publishing.npmRegistry/npmAccess", async () => {
84
+ await patcher.patch(
85
+ makeCtx({
86
+ artifact: makeArtifact({
87
+ outputDir: dir,
88
+ publishing: {
89
+ branch: "sdk/svc-auth/ts-client",
90
+ tagTemplate: "svc-{service}-{id}-v{version}",
91
+ repositoryUrl: "https://github.com/OctalMesh/ows-contracts",
92
+ npmRegistry: "https://custom.registry.example.com",
93
+ npmAccess: "restricted",
94
+ mavenRepositoryId: "github",
95
+ mavenRepositoryUrl:
96
+ "https://maven.pkg.github.com/OctalMesh/ows-contracts",
97
+ },
98
+ }),
99
+ }),
100
+ );
101
+
102
+ const pkg = JSON.parse(
103
+ await readFile(path.join(dir, "package.json"), "utf8"),
104
+ ) as Record<string, unknown>;
105
+
106
+ expect(pkg.publishConfig).toEqual({
107
+ registry: "https://custom.registry.example.com",
108
+ access: "restricted",
109
+ });
110
+ });
111
+
112
+ it("preserves fields it doesn't own (e.g. 'main' emitted by openapi-generator-cli)", async () => {
113
+ await patcher.patch(
114
+ makeCtx({ artifact: makeArtifact({ outputDir: dir }) }),
115
+ );
116
+
117
+ const pkg = JSON.parse(
118
+ await readFile(path.join(dir, "package.json"), "utf8"),
119
+ ) as Record<string, unknown>;
120
+
121
+ expect(pkg.name).toBe("@octalmesh/auth-client");
122
+ expect(pkg.main).toBe("index.js");
123
+ });
124
+
125
+ it("rejects when package.json doesn't exist (unlike the go/maven patchers, this one is not tolerant)", async () => {
126
+ await rm(path.join(dir, "package.json"));
127
+
128
+ await expect(
129
+ patcher.patch(makeCtx({ artifact: makeArtifact({ outputDir: dir }) })),
130
+ ).rejects.toThrow();
131
+ });
132
+ });