@knitli/astro-docs-template 0.2.4 → 2.2.5
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/package.json +1 -1
- package/scaffolding/mise.toml +5 -1
- package/scaffolding/package.json +4 -1
- package/scaffolding/src/content.config.ts +13 -0
- package/scaffolding/wrangler.jsonc +1 -1
- package/src/config.ts +20 -8
- package/src/index.test.ts +113 -40
- package/src/index.ts +11 -9
package/package.json
CHANGED
package/scaffolding/mise.toml
CHANGED
|
@@ -11,7 +11,7 @@ tools.bun = "latest"
|
|
|
11
11
|
|
|
12
12
|
[tasks.gentypes]
|
|
13
13
|
description = "Generate Cloudflare types for the project"
|
|
14
|
-
run = "bunx wrangler types"
|
|
14
|
+
run = "bunx wrangler types && bunx astro sync"
|
|
15
15
|
tools.bun = "latest"
|
|
16
16
|
tools.wrangler = "latest"
|
|
17
17
|
depends = ["installdeps"]
|
|
@@ -63,3 +63,7 @@ tools.biome = "latest"
|
|
|
63
63
|
description = "Fix linting issues using Biome"
|
|
64
64
|
run = "biome check . --write --changed"
|
|
65
65
|
tools.biome = "latest"
|
|
66
|
+
|
|
67
|
+
[tasks.ufix]
|
|
68
|
+
description = "Use unsafe fixes with Biome"
|
|
69
|
+
run = "biome check . --write --changed --unsafe"
|
package/scaffolding/package.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"astro": "bunx astro",
|
|
8
8
|
"prebuild": "bunx wrangler types; bunx astro sync",
|
|
9
9
|
"build": "bunx astro build",
|
|
10
|
+
"predeploy": "bun run build",
|
|
10
11
|
"deploy": "bun run build && bunx wrangler deploy",
|
|
11
12
|
"dev": "bunx astro dev",
|
|
12
13
|
"prepare": "test -f ./node_modules/bun/install.js && node ./node_modules/bun/install.js || true",
|
|
@@ -14,8 +15,10 @@
|
|
|
14
15
|
"wrangler": "bunx wrangler"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
18
|
+
"@astrojs/starlight": "^0.38.1",
|
|
17
19
|
"@knitli/astro-docs-template": "latest",
|
|
18
|
-
"@knitli/docs-components": "latest"
|
|
20
|
+
"@knitli/docs-components": "latest",
|
|
21
|
+
"astro": "^6.1.1"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
21
24
|
"@astrojs/check": "latest",
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { defineCollection } from "astro:content";
|
|
2
2
|
import { docsLoader } from "@astrojs/starlight/loaders";
|
|
3
3
|
import { docsSchema } from "@astrojs/starlight/schema";
|
|
4
|
+
import { changelogsLoader } from "starlight-changelogs/loader";
|
|
4
5
|
|
|
5
6
|
export const collections = {
|
|
6
7
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
|
8
|
+
changelogs: defineCollection({
|
|
9
|
+
loader: changelogsLoader([
|
|
10
|
+
{
|
|
11
|
+
provider: "keep-a-changelog",
|
|
12
|
+
base: "changelog",
|
|
13
|
+
changelog: "../../CHANGELOG.md",
|
|
14
|
+
pageSize: 10,
|
|
15
|
+
title: "Changelog",
|
|
16
|
+
pagefind: true,
|
|
17
|
+
},
|
|
18
|
+
]),
|
|
19
|
+
}),
|
|
7
20
|
};
|
package/src/config.ts
CHANGED
|
@@ -52,8 +52,10 @@ export const {
|
|
|
52
52
|
headlineLogoLight,
|
|
53
53
|
variables,
|
|
54
54
|
docsStyle,
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
knitliFaviconIco,
|
|
56
|
+
knitliFaviconSvg,
|
|
57
|
+
codeweaverPrimary,
|
|
58
|
+
codeweaverReverse,
|
|
57
59
|
} = DocsAssets;
|
|
58
60
|
|
|
59
61
|
const shikiCfg = {
|
|
@@ -201,7 +203,7 @@ const getIntegrations = (options: IntegrationOptions) => {
|
|
|
201
203
|
name: `${appName} Docs by Knitli`,
|
|
202
204
|
short_name: `${appName} Docs`,
|
|
203
205
|
input: {
|
|
204
|
-
favicons: [
|
|
206
|
+
favicons: [knitliFaviconSvg],
|
|
205
207
|
},
|
|
206
208
|
}),
|
|
207
209
|
cloudflarePagesHeaders({}),
|
|
@@ -317,6 +319,16 @@ const get_plugins = (options: PluginOptions) => {
|
|
|
317
319
|
return [...filtered, ...overrides];
|
|
318
320
|
};
|
|
319
321
|
|
|
322
|
+
const defaultHeadersConfig: OutgoingHttpHeaders = {
|
|
323
|
+
"X-Frame-Options": "DENY",
|
|
324
|
+
"X-Content-Type-Options": "nosniff",
|
|
325
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
326
|
+
"Permissions-Policy":
|
|
327
|
+
"accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
|
|
328
|
+
"content-security-policy":
|
|
329
|
+
"default-src 'self'; script-src 'self' 'unsafe-inline' static.cloudflareinsights.com zaraz.cloudflare.com; connect-src 'self' cloudflareinsights.com *.cloudflareinsights.com; img-src 'self' media.knitli.com data: avatars.githubusercontent.com ui-avatars.com media.knitli.com knitli.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; frame-ancestors 'none'",
|
|
330
|
+
};
|
|
331
|
+
|
|
320
332
|
export default function createConfig(options: DocsTemplateOptions) {
|
|
321
333
|
const {
|
|
322
334
|
appName,
|
|
@@ -334,7 +346,7 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
334
346
|
integrationConfigs,
|
|
335
347
|
unwantedIntegrations,
|
|
336
348
|
unwantedPlugins,
|
|
337
|
-
headersConfig,
|
|
349
|
+
headersConfig = defaultHeadersConfig,
|
|
338
350
|
} = options;
|
|
339
351
|
|
|
340
352
|
// https://astro.build/config
|
|
@@ -378,7 +390,7 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
378
390
|
],
|
|
379
391
|
},
|
|
380
392
|
server: {
|
|
381
|
-
headers: headersConfig,
|
|
393
|
+
headers: { ...defaultHeadersConfig, ...headersConfig },
|
|
382
394
|
},
|
|
383
395
|
trailingSlash: "always",
|
|
384
396
|
// Vite configuration for better bundling
|
|
@@ -478,10 +490,10 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
478
490
|
pagefind: true,
|
|
479
491
|
description: description,
|
|
480
492
|
logo: {
|
|
481
|
-
dark: logoDark,
|
|
482
|
-
light: logoLight,
|
|
493
|
+
dark: is_codeweaver ? codeweaverReverse : logoDark,
|
|
494
|
+
light: is_codeweaver ? codeweaverPrimary : logoLight,
|
|
483
495
|
alt: is_codeweaver ? "CodeWeaver Logo" : "Knitli Logo",
|
|
484
|
-
replacesTitle:
|
|
496
|
+
replacesTitle: !is_codeweaver,
|
|
485
497
|
},
|
|
486
498
|
editLink: {
|
|
487
499
|
baseUrl: `https://github.com/knitli/${appName.toLowerCase()}/edit/main/docs-site/src`,
|
package/src/index.test.ts
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
7
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
8
14
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
15
|
import {
|
|
10
16
|
addPieces,
|
|
17
|
+
type InitOptions,
|
|
11
18
|
initDocsTemplate,
|
|
12
19
|
mergePackageJsonDeps,
|
|
13
|
-
|
|
20
|
+
normalizeDependencyVersion,
|
|
14
21
|
PIECE_NAMES,
|
|
15
22
|
PIECES,
|
|
16
|
-
type InitOptions,
|
|
17
23
|
} from "./index";
|
|
18
24
|
|
|
19
25
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -29,32 +35,32 @@ const BASE_OPTIONS: InitOptions = {
|
|
|
29
35
|
workerName: "test-app-docs",
|
|
30
36
|
};
|
|
31
37
|
|
|
32
|
-
// ──
|
|
38
|
+
// ── normalizeDependencyVersion ────────────────────────────────────────────────
|
|
33
39
|
|
|
34
|
-
describe("
|
|
40
|
+
describe("normalizeDependencyVersion", () => {
|
|
35
41
|
it("converts workspace:* to *", () => {
|
|
36
|
-
expect(
|
|
42
|
+
expect(normalizeDependencyVersion("workspace:*")).toBe("*");
|
|
37
43
|
});
|
|
38
44
|
|
|
39
45
|
it("strips workspace: prefix from semver ranges, preserving the range", () => {
|
|
40
|
-
expect(
|
|
41
|
-
expect(
|
|
42
|
-
expect(
|
|
46
|
+
expect(normalizeDependencyVersion("workspace:^1.2.3")).toBe("^1.2.3");
|
|
47
|
+
expect(normalizeDependencyVersion("workspace:~1.0.0")).toBe("~1.0.0");
|
|
48
|
+
expect(normalizeDependencyVersion("workspace:>=2.0.0")).toBe(">=2.0.0");
|
|
43
49
|
});
|
|
44
50
|
|
|
45
51
|
it("converts catalog:dev-common to latest", () => {
|
|
46
|
-
expect(
|
|
52
|
+
expect(normalizeDependencyVersion("catalog:dev-common")).toBe("latest");
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
it("converts catalog:types to latest", () => {
|
|
50
|
-
expect(
|
|
56
|
+
expect(normalizeDependencyVersion("catalog:types")).toBe("latest");
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
it("leaves regular semver ranges unchanged", () => {
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
60
|
+
expect(normalizeDependencyVersion("^1.2.3")).toBe("^1.2.3");
|
|
61
|
+
expect(normalizeDependencyVersion("~2.0.0")).toBe("~2.0.0");
|
|
62
|
+
expect(normalizeDependencyVersion("latest")).toBe("latest");
|
|
63
|
+
expect(normalizeDependencyVersion("*")).toBe("*");
|
|
58
64
|
});
|
|
59
65
|
});
|
|
60
66
|
|
|
@@ -75,14 +81,16 @@ describe("mergePackageJsonDeps", () => {
|
|
|
75
81
|
expect(result.dependencies?.astro).toBe("^5.0.0");
|
|
76
82
|
});
|
|
77
83
|
|
|
78
|
-
it("
|
|
84
|
+
it("normalizes workspace:* in template deps", () => {
|
|
79
85
|
const existing = { name: "x" };
|
|
80
|
-
const template = {
|
|
86
|
+
const template = {
|
|
87
|
+
dependencies: { "@knitli/astro-docs-template": "workspace:*" },
|
|
88
|
+
};
|
|
81
89
|
const result = mergePackageJsonDeps(existing, template);
|
|
82
90
|
expect(result.dependencies?.["@knitli/astro-docs-template"]).toBe("*");
|
|
83
91
|
});
|
|
84
92
|
|
|
85
|
-
it("
|
|
93
|
+
it("normalizes catalog:* in template devDependencies", () => {
|
|
86
94
|
const existing = { name: "x" };
|
|
87
95
|
const template = { devDependencies: { typescript: "catalog:dev-common" } };
|
|
88
96
|
const result = mergePackageJsonDeps(existing, template);
|
|
@@ -91,7 +99,12 @@ describe("mergePackageJsonDeps", () => {
|
|
|
91
99
|
|
|
92
100
|
it("merges devDependencies", () => {
|
|
93
101
|
const existing = { name: "x", devDependencies: { vitest: "^4.0.0" } };
|
|
94
|
-
const template = {
|
|
102
|
+
const template = {
|
|
103
|
+
devDependencies: {
|
|
104
|
+
typescript: "catalog:dev-common",
|
|
105
|
+
"@types/node": "catalog:types",
|
|
106
|
+
},
|
|
107
|
+
};
|
|
95
108
|
const result = mergePackageJsonDeps(existing, template);
|
|
96
109
|
expect(result.devDependencies).toEqual({
|
|
97
110
|
vitest: "^4.0.0",
|
|
@@ -108,8 +121,18 @@ describe("mergePackageJsonDeps", () => {
|
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
it("does not touch name, version, scripts, or other fields", () => {
|
|
111
|
-
const existing = {
|
|
112
|
-
|
|
124
|
+
const existing = {
|
|
125
|
+
name: "my-app",
|
|
126
|
+
version: "1.2.3",
|
|
127
|
+
scripts: { build: "tsc" },
|
|
128
|
+
private: true,
|
|
129
|
+
};
|
|
130
|
+
const template = {
|
|
131
|
+
name: "template",
|
|
132
|
+
version: "0.0.1",
|
|
133
|
+
scripts: { dev: "astro dev" },
|
|
134
|
+
dependencies: { astro: "*" },
|
|
135
|
+
};
|
|
113
136
|
const result = mergePackageJsonDeps(existing, template);
|
|
114
137
|
expect(result.name).toBe("my-app");
|
|
115
138
|
expect(result.version).toBe("1.2.3");
|
|
@@ -159,7 +182,11 @@ describe("initDocsTemplate", () => {
|
|
|
159
182
|
|
|
160
183
|
it("always clobbers existing files (init always overwrites, no flag needed)", () => {
|
|
161
184
|
// Write an existing file that will be overwritten
|
|
162
|
-
writeFileSync(
|
|
185
|
+
writeFileSync(
|
|
186
|
+
join(tmpDir, "tsconfig.json"),
|
|
187
|
+
'{"compilerOptions":{}}',
|
|
188
|
+
"utf-8",
|
|
189
|
+
);
|
|
163
190
|
const created = initDocsTemplate(tmpDir, BASE_OPTIONS);
|
|
164
191
|
// tsconfig.json should have been re-created
|
|
165
192
|
expect(created.some((f) => f.includes("tsconfig.json"))).toBe(true);
|
|
@@ -192,7 +219,10 @@ describe("addPieces", () => {
|
|
|
192
219
|
});
|
|
193
220
|
|
|
194
221
|
it("creates the requested file when it does not exist", () => {
|
|
195
|
-
const created = addPieces(tmpDir, {
|
|
222
|
+
const created = addPieces(tmpDir, {
|
|
223
|
+
...BASE_OPTIONS,
|
|
224
|
+
pieces: ["tsconfig"],
|
|
225
|
+
});
|
|
196
226
|
expect(created.length).toBe(1);
|
|
197
227
|
expect(existsSync(join(tmpDir, "tsconfig.json"))).toBe(true);
|
|
198
228
|
});
|
|
@@ -200,18 +230,29 @@ describe("addPieces", () => {
|
|
|
200
230
|
it("skips an existing non-package.json file without --force", () => {
|
|
201
231
|
// Pre-populate the tsconfig so it already exists
|
|
202
232
|
writeFileSync(join(tmpDir, "tsconfig.json"), '{"existing":true}', "utf-8");
|
|
203
|
-
const created = addPieces(tmpDir, {
|
|
233
|
+
const created = addPieces(tmpDir, {
|
|
234
|
+
...BASE_OPTIONS,
|
|
235
|
+
pieces: ["tsconfig"],
|
|
236
|
+
});
|
|
204
237
|
expect(created.length).toBe(0);
|
|
205
238
|
// Original content should be intact
|
|
206
|
-
const content = JSON.parse(
|
|
239
|
+
const content = JSON.parse(
|
|
240
|
+
readFileSync(join(tmpDir, "tsconfig.json"), "utf-8"),
|
|
241
|
+
);
|
|
207
242
|
expect(content.existing).toBe(true);
|
|
208
243
|
});
|
|
209
244
|
|
|
210
245
|
it("overwrites an existing non-package.json file with --force", () => {
|
|
211
246
|
writeFileSync(join(tmpDir, "tsconfig.json"), '{"existing":true}', "utf-8");
|
|
212
|
-
const created = addPieces(tmpDir, {
|
|
247
|
+
const created = addPieces(tmpDir, {
|
|
248
|
+
...BASE_OPTIONS,
|
|
249
|
+
pieces: ["tsconfig"],
|
|
250
|
+
force: true,
|
|
251
|
+
});
|
|
213
252
|
expect(created.length).toBe(1);
|
|
214
|
-
const content = JSON.parse(
|
|
253
|
+
const content = JSON.parse(
|
|
254
|
+
readFileSync(join(tmpDir, "tsconfig.json"), "utf-8"),
|
|
255
|
+
);
|
|
215
256
|
expect(content.existing).toBeUndefined();
|
|
216
257
|
});
|
|
217
258
|
|
|
@@ -238,13 +279,19 @@ describe("addPieces", () => {
|
|
|
238
279
|
private: true,
|
|
239
280
|
dependencies: { express: "^4.0.0" },
|
|
240
281
|
};
|
|
241
|
-
writeFileSync(
|
|
282
|
+
writeFileSync(
|
|
283
|
+
join(tmpDir, "package.json"),
|
|
284
|
+
JSON.stringify(existingPkg, null, 2),
|
|
285
|
+
"utf-8",
|
|
286
|
+
);
|
|
242
287
|
|
|
243
288
|
const created = addPieces(tmpDir, { ...BASE_OPTIONS, pieces: ["deps"] });
|
|
244
289
|
// Should be modified (not 0 files)
|
|
245
290
|
expect(created.length).toBe(1);
|
|
246
291
|
|
|
247
|
-
const merged = JSON.parse(
|
|
292
|
+
const merged = JSON.parse(
|
|
293
|
+
readFileSync(join(tmpDir, "package.json"), "utf-8"),
|
|
294
|
+
);
|
|
248
295
|
// Existing non-dep fields are preserved
|
|
249
296
|
expect(merged.name).toBe("my-other-project");
|
|
250
297
|
expect(merged.version).toBe("3.0.0");
|
|
@@ -256,11 +303,17 @@ describe("addPieces", () => {
|
|
|
256
303
|
expect(merged.devDependencies?.["@astrojs/check"]).toBeDefined();
|
|
257
304
|
});
|
|
258
305
|
|
|
259
|
-
it("
|
|
260
|
-
writeFileSync(
|
|
306
|
+
it("normalizes workspace:* references when merging", () => {
|
|
307
|
+
writeFileSync(
|
|
308
|
+
join(tmpDir, "package.json"),
|
|
309
|
+
'{"name":"x","dependencies":{}}',
|
|
310
|
+
"utf-8",
|
|
311
|
+
);
|
|
261
312
|
addPieces(tmpDir, { ...BASE_OPTIONS, pieces: ["deps"] });
|
|
262
|
-
const merged = JSON.parse(
|
|
263
|
-
|
|
313
|
+
const merged = JSON.parse(
|
|
314
|
+
readFileSync(join(tmpDir, "package.json"), "utf-8"),
|
|
315
|
+
);
|
|
316
|
+
// workspace:* should be normalized away
|
|
264
317
|
const allVersions = Object.values({
|
|
265
318
|
...merged.dependencies,
|
|
266
319
|
...merged.devDependencies,
|
|
@@ -270,18 +323,32 @@ describe("addPieces", () => {
|
|
|
270
323
|
});
|
|
271
324
|
|
|
272
325
|
it("throws a helpful error when existing package.json is not valid JSON", () => {
|
|
273
|
-
writeFileSync(
|
|
326
|
+
writeFileSync(
|
|
327
|
+
join(tmpDir, "package.json"),
|
|
328
|
+
"{ this is not valid json",
|
|
329
|
+
"utf-8",
|
|
330
|
+
);
|
|
274
331
|
expect(() =>
|
|
275
332
|
addPieces(tmpDir, { ...BASE_OPTIONS, pieces: ["deps"] }),
|
|
276
333
|
).toThrow(/Failed to parse existing package\.json/);
|
|
277
334
|
});
|
|
278
335
|
|
|
279
336
|
it("replaces existing package.json wholesale with --force", () => {
|
|
280
|
-
const existingPkg = {
|
|
281
|
-
|
|
337
|
+
const existingPkg = {
|
|
338
|
+
name: "my-other-project",
|
|
339
|
+
version: "3.0.0",
|
|
340
|
+
scripts: { start: "node server.js" },
|
|
341
|
+
};
|
|
342
|
+
writeFileSync(
|
|
343
|
+
join(tmpDir, "package.json"),
|
|
344
|
+
JSON.stringify(existingPkg, null, 2),
|
|
345
|
+
"utf-8",
|
|
346
|
+
);
|
|
282
347
|
|
|
283
348
|
addPieces(tmpDir, { ...BASE_OPTIONS, pieces: ["deps"], force: true });
|
|
284
|
-
const result = JSON.parse(
|
|
349
|
+
const result = JSON.parse(
|
|
350
|
+
readFileSync(join(tmpDir, "package.json"), "utf-8"),
|
|
351
|
+
);
|
|
285
352
|
// Should be the template's package.json content (with substitution applied)
|
|
286
353
|
expect(result.name).toBe(BASE_OPTIONS.name);
|
|
287
354
|
});
|
|
@@ -290,7 +357,10 @@ describe("addPieces", () => {
|
|
|
290
357
|
// ── content piece ─────────────────────────────────────────────────────────
|
|
291
358
|
|
|
292
359
|
it("copies directory pieces (starter-content)", () => {
|
|
293
|
-
const created = addPieces(tmpDir, {
|
|
360
|
+
const created = addPieces(tmpDir, {
|
|
361
|
+
...BASE_OPTIONS,
|
|
362
|
+
pieces: ["starter-content"],
|
|
363
|
+
});
|
|
294
364
|
expect(created.length).toBeGreaterThan(0);
|
|
295
365
|
expect(existsSync(join(tmpDir, "src/content/docs"))).toBe(true);
|
|
296
366
|
});
|
|
@@ -298,7 +368,10 @@ describe("addPieces", () => {
|
|
|
298
368
|
// ── multiple pieces ───────────────────────────────────────────────────────
|
|
299
369
|
|
|
300
370
|
it("can add multiple pieces at once", () => {
|
|
301
|
-
const created = addPieces(tmpDir, {
|
|
371
|
+
const created = addPieces(tmpDir, {
|
|
372
|
+
...BASE_OPTIONS,
|
|
373
|
+
pieces: ["tsconfig", "styles"],
|
|
374
|
+
});
|
|
302
375
|
expect(existsSync(join(tmpDir, "tsconfig.json"))).toBe(true);
|
|
303
376
|
expect(existsSync(join(tmpDir, "src/styles/custom.css"))).toBe(true);
|
|
304
377
|
expect(created.length).toBe(2);
|
package/src/index.ts
CHANGED
|
@@ -166,7 +166,7 @@ function copyDirRecursive(
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
169
|
+
* Normalize a dependency version specifier so that workspace-protocol and
|
|
170
170
|
* catalog entries from the Knitli monorepo become portable version strings
|
|
171
171
|
* usable in any project.
|
|
172
172
|
*
|
|
@@ -175,7 +175,7 @@ function copyDirRecursive(
|
|
|
175
175
|
* "catalog:something" → "latest"
|
|
176
176
|
* anything else → unchanged
|
|
177
177
|
*/
|
|
178
|
-
export function
|
|
178
|
+
export function normalizeDependencyVersion(version: string): string {
|
|
179
179
|
if (version.startsWith("workspace:")) {
|
|
180
180
|
const spec = version.slice("workspace:".length).trim();
|
|
181
181
|
if (spec === "" || spec === "*") return "*";
|
|
@@ -195,7 +195,7 @@ type PackageJsonLike = {
|
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
197
|
* Merge the `dependencies`, `devDependencies`, and `peerDependencies` from
|
|
198
|
-
* `templatePkg` into `existingPkg`,
|
|
198
|
+
* `templatePkg` into `existingPkg`, normalizing any monorepo-specific version
|
|
199
199
|
* specifiers so the result is portable. Template entries take precedence over
|
|
200
200
|
* existing entries for the same package name.
|
|
201
201
|
*
|
|
@@ -215,11 +215,11 @@ export function mergePackageJsonDeps(
|
|
|
215
215
|
for (const field of depFields) {
|
|
216
216
|
const templateDeps = templatePkg[field];
|
|
217
217
|
if (!templateDeps || Object.keys(templateDeps).length === 0) continue;
|
|
218
|
-
const
|
|
218
|
+
const normalized: PackageJsonDeps = {};
|
|
219
219
|
for (const [pkg, version] of Object.entries(templateDeps)) {
|
|
220
|
-
|
|
220
|
+
normalized[pkg] = normalizeDependencyVersion(version);
|
|
221
221
|
}
|
|
222
|
-
result[field] = { ...(existingPkg[field] ?? {}), ...
|
|
222
|
+
result[field] = { ...(existingPkg[field] ?? {}), ...normalized };
|
|
223
223
|
}
|
|
224
224
|
return result;
|
|
225
225
|
}
|
|
@@ -231,10 +231,10 @@ export function mergePackageJsonDeps(
|
|
|
231
231
|
* (with placeholder substitution).
|
|
232
232
|
* - If `force` is set, the file is overwritten wholesale.
|
|
233
233
|
* - Otherwise the `dependencies`, `devDependencies`, and `peerDependencies`
|
|
234
|
-
* from the template are merged into the existing file,
|
|
234
|
+
* from the template are merged into the existing file, normalizing any
|
|
235
235
|
* workspace-protocol / catalog version specifiers to portable equivalents.
|
|
236
236
|
*
|
|
237
|
-
* **Note**: the merged file is re-
|
|
237
|
+
* **Note**: the merged file is re-serialized with 2-space indentation using
|
|
238
238
|
* `JSON.stringify`. Any bespoke formatting or key ordering in the original
|
|
239
239
|
* file will not be preserved.
|
|
240
240
|
*/
|
|
@@ -258,7 +258,9 @@ function mergeOrCopyPackageJson(
|
|
|
258
258
|
|
|
259
259
|
let existingPkg: PackageJsonLike;
|
|
260
260
|
try {
|
|
261
|
-
existingPkg = JSON.parse(
|
|
261
|
+
existingPkg = JSON.parse(
|
|
262
|
+
readFileSync(destPath, "utf-8"),
|
|
263
|
+
) as PackageJsonLike;
|
|
262
264
|
} catch (err) {
|
|
263
265
|
const msg = err instanceof Error ? err.message : String(err);
|
|
264
266
|
throw new Error(
|