isolate-package 1.32.0 → 1.33.0-0

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.
@@ -15,9 +15,17 @@ vi.mock("fs-extra", () => ({
15
15
  vi.mock("~/lib/utils", () => ({
16
16
  filterPatchedDependencies: vi.fn(),
17
17
  getIsolateRelativeLogPath: vi.fn((p: string) => p),
18
+ getPackageName: vi.fn((spec: string) => {
19
+ if (spec.startsWith("@")) {
20
+ const parts = spec.split("@");
21
+ return `@${parts[1] ?? ""}`;
22
+ }
23
+ return spec.split("@")[0] ?? "";
24
+ }),
18
25
  getRootRelativeLogPath: vi.fn((p: string) => p),
19
26
  isRushWorkspace: vi.fn(() => false),
20
27
  readTypedJson: vi.fn(),
28
+ readTypedJsonSync: vi.fn(),
21
29
  readTypedYamlSync: vi.fn(),
22
30
  }));
23
31
 
@@ -29,16 +37,25 @@ vi.mock("~/lib/package-manager", () => ({
29
37
  /** Mock the pnpm lockfile readers */
30
38
  vi.mock("pnpm_lockfile_file_v8", () => ({
31
39
  readWantedLockfile: vi.fn(() => Promise.resolve(null)),
40
+ getLockfileImporterId: vi.fn(
41
+ (root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
42
+ ),
32
43
  }));
33
44
 
34
45
  vi.mock("pnpm_lockfile_file_v9", () => ({
35
46
  readWantedLockfile: vi.fn(() => Promise.resolve(null)),
47
+ getLockfileImporterId: vi.fn(
48
+ (root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
49
+ ),
36
50
  }));
37
51
 
38
52
  const fs = vi.mocked((await import("fs-extra")).default);
39
53
  const { filterPatchedDependencies, readTypedJson, readTypedYamlSync } =
40
54
  vi.mocked(await import("~/lib/utils"));
41
55
  const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
56
+ const { readWantedLockfile: readWantedLockfile_v9 } = vi.mocked(
57
+ await import("pnpm_lockfile_file_v9"),
58
+ );
42
59
 
43
60
  describe("copyPatches", () => {
44
61
  beforeEach(() => {
@@ -65,6 +82,8 @@ describe("copyPatches", () => {
65
82
 
66
83
  const result = await copyPatches({
67
84
  workspaceRootDir: "/workspace",
85
+ targetPackageDir: "/workspace/packages/test",
86
+ internalDepPackageNames: [],
68
87
  targetPackageManifest: { name: "test", version: "1.0.0" },
69
88
  isolateDir: "/workspace/isolate",
70
89
  packagesRegistry: {},
@@ -126,6 +145,8 @@ describe("copyPatches", () => {
126
145
 
127
146
  const result = await copyPatches({
128
147
  workspaceRootDir: "/workspace",
148
+ targetPackageDir: "/workspace/packages/test",
149
+ internalDepPackageNames: [],
129
150
  targetPackageManifest: { name: "test", version: "1.0.0" },
130
151
  isolateDir: "/workspace/isolate",
131
152
  packagesRegistry: {},
@@ -146,6 +167,8 @@ describe("copyPatches", () => {
146
167
 
147
168
  const result = await copyPatches({
148
169
  workspaceRootDir: "/workspace",
170
+ targetPackageDir: "/workspace/packages/test",
171
+ internalDepPackageNames: [],
149
172
  targetPackageManifest: { name: "test", version: "1.0.0" },
150
173
  isolateDir: "/workspace/isolate",
151
174
  packagesRegistry: {},
@@ -176,6 +199,8 @@ describe("copyPatches", () => {
176
199
 
177
200
  const result = await copyPatches({
178
201
  workspaceRootDir: "/workspace",
202
+ targetPackageDir: "/workspace/packages/test",
203
+ internalDepPackageNames: [],
179
204
  targetPackageManifest: targetManifest,
180
205
  isolateDir: "/workspace/isolate",
181
206
  packagesRegistry: {},
@@ -214,6 +239,8 @@ describe("copyPatches", () => {
214
239
 
215
240
  const result = await copyPatches({
216
241
  workspaceRootDir: "/workspace",
242
+ targetPackageDir: "/workspace/packages/test",
243
+ internalDepPackageNames: [],
217
244
  targetPackageManifest: targetManifest,
218
245
  isolateDir: "/workspace/isolate",
219
246
  packagesRegistry: {},
@@ -256,6 +283,8 @@ describe("copyPatches", () => {
256
283
 
257
284
  const result = await copyPatches({
258
285
  workspaceRootDir: "/workspace",
286
+ targetPackageDir: "/workspace/packages/test",
287
+ internalDepPackageNames: [],
259
288
  targetPackageManifest: targetManifest,
260
289
  isolateDir: "/workspace/isolate",
261
290
  packagesRegistry: {},
@@ -287,6 +316,8 @@ describe("copyPatches", () => {
287
316
 
288
317
  const result = await copyPatches({
289
318
  workspaceRootDir: "/workspace",
319
+ targetPackageDir: "/workspace/packages/test",
320
+ internalDepPackageNames: [],
290
321
  targetPackageManifest: targetManifest,
291
322
  isolateDir: "/workspace/isolate",
292
323
  packagesRegistry: {},
@@ -321,6 +352,8 @@ describe("copyPatches", () => {
321
352
 
322
353
  const result = await copyPatches({
323
354
  workspaceRootDir: "/workspace",
355
+ targetPackageDir: "/workspace/packages/test",
356
+ internalDepPackageNames: [],
324
357
  targetPackageManifest: targetManifest,
325
358
  isolateDir: "/workspace/isolate",
326
359
  packagesRegistry: {},
@@ -364,6 +397,8 @@ describe("copyPatches", () => {
364
397
 
365
398
  const result = await copyPatches({
366
399
  workspaceRootDir: "/workspace",
400
+ targetPackageDir: "/workspace/packages/test",
401
+ internalDepPackageNames: [],
367
402
  targetPackageManifest: targetManifest,
368
403
  isolateDir: "/workspace/isolate",
369
404
  packagesRegistry: {},
@@ -409,6 +444,8 @@ describe("copyPatches", () => {
409
444
 
410
445
  const result = await copyPatches({
411
446
  workspaceRootDir: "/workspace",
447
+ targetPackageDir: "/workspace/packages/test",
448
+ internalDepPackageNames: [],
412
449
  targetPackageManifest: targetManifest,
413
450
  isolateDir: "/workspace/isolate",
414
451
  packagesRegistry: {},
@@ -456,6 +493,8 @@ describe("copyPatches", () => {
456
493
 
457
494
  const result = await copyPatches({
458
495
  workspaceRootDir: "/workspace",
496
+ targetPackageDir: "/workspace/packages/test",
497
+ internalDepPackageNames: [],
459
498
  targetPackageManifest: targetManifest,
460
499
  isolateDir: "/workspace/isolate",
461
500
  packagesRegistry: {},
@@ -509,6 +548,8 @@ describe("copyPatches", () => {
509
548
 
510
549
  const result = await copyPatches({
511
550
  workspaceRootDir: "/workspace",
551
+ targetPackageDir: "/workspace/packages/test",
552
+ internalDepPackageNames: [],
512
553
  targetPackageManifest: consumerManifest,
513
554
  isolateDir: "/workspace/isolate",
514
555
  packagesRegistry: {
@@ -536,4 +577,88 @@ describe("copyPatches", () => {
536
577
  expect(reachable!.has("firebase-package")).toBe(true);
537
578
  expect(reachable!.has("tslib")).toBe(true);
538
579
  });
580
+
581
+ it("should pick up deep external-to-external transitives from the pnpm lockfile (regression: issue #167 follow-up)", async () => {
582
+ /**
583
+ * Target depends on `@react-pdf/renderer` (external). The patched
584
+ * `@react-pdf/render` is only a transitive of `@react-pdf/renderer`. The
585
+ * manifest walker can't see it because it can't open external manifests,
586
+ * so the lockfile walker has to surface it.
587
+ */
588
+ const targetManifest: PackageManifest = {
589
+ name: "consumer",
590
+ version: "1.0.0",
591
+ dependencies: { "@react-pdf/renderer": "^4.0.0" },
592
+ };
593
+
594
+ readTypedYamlSync.mockReturnValue({
595
+ patchedDependencies: {
596
+ "@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
597
+ },
598
+ });
599
+ readTypedJson.mockResolvedValue({
600
+ name: "root",
601
+ version: "1.0.0",
602
+ } as PackageManifest);
603
+
604
+ filterPatchedDependencies.mockReturnValue({
605
+ "@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
606
+ });
607
+
608
+ fs.existsSync.mockReturnValue(true);
609
+
610
+ usePackageManager.mockReturnValue({
611
+ name: "pnpm",
612
+ majorVersion: 9,
613
+ version: "9.0.0",
614
+ packageManagerString: "pnpm@9.0.0",
615
+ });
616
+
617
+ /**
618
+ * Fake v9 lockfile: target importer depends on @react-pdf/renderer, which
619
+ * has @react-pdf/render as its only resolved dep.
620
+ */
621
+ readWantedLockfile_v9.mockResolvedValue({
622
+ lockfileVersion: "9.0",
623
+ importers: {
624
+ "packages/consumer": {
625
+ specifiers: { "@react-pdf/renderer": "^4.0.0" },
626
+ dependencies: { "@react-pdf/renderer": "4.0.0" },
627
+ },
628
+ },
629
+ packages: {
630
+ "@react-pdf/renderer@4.0.0": {
631
+ resolution: { integrity: "sha512-x" },
632
+ dependencies: { "@react-pdf/render": "4.3.0" },
633
+ },
634
+ "@react-pdf/render@4.3.0": {
635
+ resolution: { integrity: "sha512-y" },
636
+ },
637
+ },
638
+ } as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
639
+
640
+ const result = await copyPatches({
641
+ workspaceRootDir: "/workspace",
642
+ targetPackageDir: "/workspace/packages/consumer",
643
+ internalDepPackageNames: [],
644
+ targetPackageManifest: targetManifest,
645
+ isolateDir: "/workspace/isolate",
646
+ packagesRegistry: {},
647
+ includeDevDependencies: false,
648
+ });
649
+
650
+ expect(result).toEqual({
651
+ "@react-pdf/render@4.3.0": {
652
+ path: "patches/@react-pdf__render@4.3.0.patch",
653
+ hash: "",
654
+ },
655
+ });
656
+
657
+ const filterCall = filterPatchedDependencies.mock.calls[0]?.[0];
658
+ expect(filterCall).toBeDefined();
659
+ const reachable = filterCall!.reachableDependencyNames;
660
+ expect(reachable).toBeInstanceOf(Set);
661
+ expect(reachable!.has("@react-pdf/renderer")).toBe(true);
662
+ expect(reachable!.has("@react-pdf/render")).toBe(true);
663
+ });
539
664
  });
@@ -18,23 +18,29 @@ import {
18
18
  readTypedJson,
19
19
  readTypedYamlSync,
20
20
  } from "~/lib/utils";
21
+ import { collectInstalledNamesFromBunLockfile } from "./collect-installed-names-bun";
22
+ import { collectInstalledNamesFromPnpmLockfile } from "./collect-installed-names-pnpm";
21
23
 
22
24
  export async function copyPatches({
23
25
  workspaceRootDir,
26
+ targetPackageDir,
24
27
  targetPackageManifest,
25
28
  packagesRegistry,
29
+ internalDepPackageNames,
26
30
  isolateDir,
27
31
  includeDevDependencies,
28
32
  }: {
29
33
  workspaceRootDir: string;
34
+ targetPackageDir: string;
30
35
  targetPackageManifest: PackageManifest;
31
36
  packagesRegistry: PackagesRegistry;
37
+ internalDepPackageNames: string[];
32
38
  isolateDir: string;
33
39
  includeDevDependencies: boolean;
34
40
  }): Promise<Record<string, PatchFile>> {
35
41
  const log = useLogger();
36
42
 
37
- const { name: packageManagerName } = usePackageManager();
43
+ const { name: packageManagerName, majorVersion } = usePackageManager();
38
44
 
39
45
  let patchedDependencies: Record<string, string> | undefined;
40
46
 
@@ -102,6 +108,37 @@ export async function copyPatches({
102
108
  includeDevDependencies,
103
109
  });
104
110
 
111
+ /**
112
+ * Manifest-based reachability misses external→external transitives because
113
+ * external manifests aren't loaded here. Walk the package-manager's
114
+ * lockfile to also pick up those names, so a patch for a deeply-nested
115
+ * external dep (e.g. `@react-pdf/render` reached via `@react-pdf/renderer`)
116
+ * survives isolation.
117
+ */
118
+ const lockfileInstalledNames =
119
+ packageManagerName === "pnpm"
120
+ ? await collectInstalledNamesFromPnpmLockfile({
121
+ workspaceRootDir,
122
+ targetPackageDir,
123
+ internalDepPackageNames,
124
+ packagesRegistry,
125
+ majorVersion,
126
+ includeDevDependencies,
127
+ })
128
+ : packageManagerName === "bun"
129
+ ? collectInstalledNamesFromBunLockfile({
130
+ workspaceRootDir,
131
+ targetPackageDir,
132
+ internalDepPackageNames,
133
+ packagesRegistry,
134
+ includeDevDependencies,
135
+ })
136
+ : new Set<string>();
137
+
138
+ for (const name of lockfileInstalledNames) {
139
+ reachableDependencyNames.add(name);
140
+ }
141
+
105
142
  const filteredPatches = filterPatchedDependencies({
106
143
  patchedDependencies,
107
144
  targetPackageManifest,
@@ -0,0 +1,189 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { PatchFile } from "~/lib/types";
3
+ import { writeIsolatePnpmWorkspace } from "./write-isolate-pnpm-workspace";
4
+
5
+ vi.mock("fs-extra", () => ({
6
+ default: {
7
+ copyFileSync: vi.fn(),
8
+ },
9
+ }));
10
+
11
+ vi.mock("~/lib/utils", () => ({
12
+ readTypedYamlSync: vi.fn(),
13
+ writeTypedYamlSync: vi.fn(),
14
+ }));
15
+
16
+ const fs = vi.mocked((await import("fs-extra")).default);
17
+ const { readTypedYamlSync, writeTypedYamlSync } = vi.mocked(
18
+ await import("~/lib/utils"),
19
+ );
20
+
21
+ const workspaceRootDir = "/workspace";
22
+ const isolateDir = "/workspace/isolate";
23
+
24
+ describe("writeIsolatePnpmWorkspace", () => {
25
+ beforeEach(() => {
26
+ vi.clearAllMocks();
27
+ });
28
+
29
+ afterEach(() => {
30
+ vi.restoreAllMocks();
31
+ });
32
+
33
+ it("retains only the patches that were copied", () => {
34
+ readTypedYamlSync.mockReturnValue({
35
+ packages: ["packages/*"],
36
+ patchedDependencies: {
37
+ "lodash@4.17.21": "patches/lodash@4.17.21.patch",
38
+ "react@18.2.0": "patches/react@18.2.0.patch",
39
+ "axios@1.6.0": "patches/axios@1.6.0.patch",
40
+ },
41
+ });
42
+
43
+ const copiedPatches: Record<string, PatchFile> = {
44
+ "lodash@4.17.21": {
45
+ path: "patches/lodash@4.17.21.patch",
46
+ hash: "abc",
47
+ },
48
+ };
49
+
50
+ writeIsolatePnpmWorkspace({
51
+ workspaceRootDir,
52
+ isolateDir,
53
+ copiedPatches,
54
+ });
55
+
56
+ expect(fs.copyFileSync).not.toHaveBeenCalled();
57
+ expect(writeTypedYamlSync).toHaveBeenCalledTimes(1);
58
+ expect(writeTypedYamlSync).toHaveBeenCalledWith(
59
+ "/workspace/isolate/pnpm-workspace.yaml",
60
+ {
61
+ packages: ["packages/*"],
62
+ patchedDependencies: {
63
+ "lodash@4.17.21": "patches/lodash@4.17.21.patch",
64
+ },
65
+ },
66
+ );
67
+ });
68
+
69
+ it("removes the patchedDependencies field when no patches were copied", () => {
70
+ readTypedYamlSync.mockReturnValue({
71
+ packages: ["packages/*"],
72
+ patchedDependencies: {
73
+ "lodash@4.17.21": "patches/lodash@4.17.21.patch",
74
+ },
75
+ });
76
+
77
+ writeIsolatePnpmWorkspace({
78
+ workspaceRootDir,
79
+ isolateDir,
80
+ copiedPatches: {},
81
+ });
82
+
83
+ expect(fs.copyFileSync).not.toHaveBeenCalled();
84
+ expect(writeTypedYamlSync).toHaveBeenCalledWith(
85
+ "/workspace/isolate/pnpm-workspace.yaml",
86
+ { packages: ["packages/*"] },
87
+ );
88
+ });
89
+
90
+ it("falls back to a verbatim copy when the file has no patchedDependencies field", () => {
91
+ readTypedYamlSync.mockReturnValue({
92
+ packages: ["packages/*"],
93
+ });
94
+
95
+ writeIsolatePnpmWorkspace({
96
+ workspaceRootDir,
97
+ isolateDir,
98
+ copiedPatches: {},
99
+ });
100
+
101
+ expect(writeTypedYamlSync).not.toHaveBeenCalled();
102
+ expect(fs.copyFileSync).toHaveBeenCalledWith(
103
+ "/workspace/pnpm-workspace.yaml",
104
+ "/workspace/isolate/pnpm-workspace.yaml",
105
+ );
106
+ });
107
+
108
+ it("preserves unrelated top-level fields", () => {
109
+ readTypedYamlSync.mockReturnValue({
110
+ packages: ["packages/*"],
111
+ onlyBuiltDependencies: ["esbuild"],
112
+ overrides: { foo: "1.0.0" },
113
+ patchedDependencies: {
114
+ "lodash@4.17.21": "patches/lodash@4.17.21.patch",
115
+ "react@18.2.0": "patches/react@18.2.0.patch",
116
+ },
117
+ });
118
+
119
+ const copiedPatches: Record<string, PatchFile> = {
120
+ "react@18.2.0": { path: "patches/react@18.2.0.patch", hash: "def" },
121
+ };
122
+
123
+ writeIsolatePnpmWorkspace({
124
+ workspaceRootDir,
125
+ isolateDir,
126
+ copiedPatches,
127
+ });
128
+
129
+ expect(writeTypedYamlSync).toHaveBeenCalledWith(
130
+ "/workspace/isolate/pnpm-workspace.yaml",
131
+ {
132
+ packages: ["packages/*"],
133
+ onlyBuiltDependencies: ["esbuild"],
134
+ overrides: { foo: "1.0.0" },
135
+ patchedDependencies: {
136
+ "react@18.2.0": "patches/react@18.2.0.patch",
137
+ },
138
+ },
139
+ );
140
+ });
141
+
142
+ it("copies verbatim when every patch is kept (preserving comments and order)", () => {
143
+ readTypedYamlSync.mockReturnValue({
144
+ packages: ["packages/*"],
145
+ patchedDependencies: {
146
+ "lodash@4.17.21": "patches/lodash@4.17.21.patch",
147
+ "react@18.2.0": "patches/react@18.2.0.patch",
148
+ },
149
+ });
150
+
151
+ const copiedPatches: Record<string, PatchFile> = {
152
+ "lodash@4.17.21": {
153
+ path: "patches/lodash@4.17.21.patch",
154
+ hash: "abc",
155
+ },
156
+ "react@18.2.0": { path: "patches/react@18.2.0.patch", hash: "def" },
157
+ };
158
+
159
+ writeIsolatePnpmWorkspace({
160
+ workspaceRootDir,
161
+ isolateDir,
162
+ copiedPatches,
163
+ });
164
+
165
+ expect(writeTypedYamlSync).not.toHaveBeenCalled();
166
+ expect(fs.copyFileSync).toHaveBeenCalledWith(
167
+ "/workspace/pnpm-workspace.yaml",
168
+ "/workspace/isolate/pnpm-workspace.yaml",
169
+ );
170
+ });
171
+
172
+ it("falls back to a verbatim copy when the yaml cannot be parsed", () => {
173
+ readTypedYamlSync.mockImplementation(() => {
174
+ throw new Error("bad yaml");
175
+ });
176
+
177
+ writeIsolatePnpmWorkspace({
178
+ workspaceRootDir,
179
+ isolateDir,
180
+ copiedPatches: {},
181
+ });
182
+
183
+ expect(writeTypedYamlSync).not.toHaveBeenCalled();
184
+ expect(fs.copyFileSync).toHaveBeenCalledWith(
185
+ "/workspace/pnpm-workspace.yaml",
186
+ "/workspace/isolate/pnpm-workspace.yaml",
187
+ );
188
+ });
189
+ });
@@ -0,0 +1,80 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import { useLogger } from "~/lib/logger";
4
+ import type { PatchFile, PnpmSettings } from "~/lib/types";
5
+ import { readTypedYamlSync, writeTypedYamlSync } from "~/lib/utils";
6
+
7
+ /**
8
+ * Copy `pnpm-workspace.yaml` from the workspace root to the isolate directory,
9
+ * filtering its `patchedDependencies` field so it only references patches that
10
+ * were actually copied to the isolate. Without this, `pnpm install` in the
11
+ * isolate fails when patches that don't apply to the target package are
12
+ * declared in the workspace root config (see issue #178).
13
+ *
14
+ * The yaml is only rewritten when filtering is required. The file is copied
15
+ * verbatim — preserving comments, key order, and trailing whitespace — when
16
+ * any of the following hold:
17
+ *
18
+ * - The source yaml cannot be read or parsed.
19
+ * - The parsed settings have no `patchedDependencies` field.
20
+ * - Every entry in `patchedDependencies` is also present in `copiedPatches`
21
+ * (no exclusions, so rewriting would only churn formatting).
22
+ *
23
+ * Otherwise, `patchedDependencies` is rewritten to the entries in
24
+ * `copiedPatches` (or removed entirely when none remain).
25
+ */
26
+ export function writeIsolatePnpmWorkspace({
27
+ workspaceRootDir,
28
+ isolateDir,
29
+ copiedPatches,
30
+ }: {
31
+ workspaceRootDir: string;
32
+ isolateDir: string;
33
+ copiedPatches: Record<string, PatchFile>;
34
+ }) {
35
+ const log = useLogger();
36
+ const sourcePath = path.join(workspaceRootDir, "pnpm-workspace.yaml");
37
+ const targetPath = path.join(isolateDir, "pnpm-workspace.yaml");
38
+
39
+ let settings: PnpmSettings | undefined;
40
+
41
+ try {
42
+ settings = readTypedYamlSync<PnpmSettings>(sourcePath);
43
+ } catch (error) {
44
+ log.warn(
45
+ `Could not read pnpm-workspace.yaml, falling back to verbatim copy: ${error instanceof Error ? error.message : String(error)}`,
46
+ );
47
+ fs.copyFileSync(sourcePath, targetPath);
48
+ return;
49
+ }
50
+
51
+ if (!settings || !settings.patchedDependencies) {
52
+ fs.copyFileSync(sourcePath, targetPath);
53
+ return;
54
+ }
55
+
56
+ /**
57
+ * If every patch declared in the source yaml was kept, copy verbatim so
58
+ * comments, ordering, and trailing whitespace are preserved.
59
+ */
60
+ const sourceSpecs = Object.keys(settings.patchedDependencies);
61
+ const copiedSpecs = new Set(Object.keys(copiedPatches));
62
+ const hasExclusions = sourceSpecs.some((spec) => !copiedSpecs.has(spec));
63
+
64
+ if (!hasExclusions) {
65
+ fs.copyFileSync(sourcePath, targetPath);
66
+ return;
67
+ }
68
+
69
+ const filteredEntries = Object.entries(copiedPatches).map(
70
+ ([spec, patchFile]) => [spec, patchFile.path] as const,
71
+ );
72
+
73
+ if (filteredEntries.length > 0) {
74
+ settings.patchedDependencies = Object.fromEntries(filteredEntries);
75
+ } else {
76
+ delete settings.patchedDependencies;
77
+ }
78
+
79
+ writeTypedYamlSync(targetPath, settings);
80
+ }