pkgref 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ # pkgref
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Add interactive dependency repository cloning, indexing, and updates
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # pkgref
2
+
3
+ [GitHub repository](https://github.com/yue4u/pkgref)
4
+
5
+ ```sh
6
+ pnpm dlx pkgref
7
+ # list package.json deps in interactive mode for select and clone repo to target dir (default to docs/pkg-reference).
8
+ pnpm dlx pkgref --pkgs=react,vitest --dir docs/repo
9
+ # specify pkg and dir to clone with cli param
10
+ pnpm dlx pkgref update
11
+ # fast-forward all clones in docs/pkg-reference
12
+ ```
13
+
14
+ ## Features
15
+
16
+ - list docs/example dir in cloned repo to root as index markdown for search
17
+
18
+ ## Future
19
+
20
+ - support more than package.json
21
+
22
+ ## Development
23
+
24
+ - Install dependencies:
25
+
26
+ ```bash
27
+ vp install
28
+ ```
29
+
30
+ - Run the unit tests:
31
+
32
+ ```bash
33
+ vp test
34
+ ```
35
+
36
+ - Build the library:
37
+
38
+ ```bash
39
+ vp pack
40
+ ```
package/dist/cli.d.mts ADDED
@@ -0,0 +1,31 @@
1
+ //#region src/types.d.ts
2
+ type RepositoryRef = {
3
+ cloneUrl: string;
4
+ key: string;
5
+ name: string;
6
+ };
7
+ type RepositoryGroup = RepositoryRef & {
8
+ packages: string[];
9
+ };
10
+ type IndexRepository = {
11
+ directory: string;
12
+ packages: string[];
13
+ root: string;
14
+ };
15
+ //#endregion
16
+ //#region src/cli.d.ts
17
+ type GitResult = {
18
+ ok: boolean;
19
+ output: string;
20
+ };
21
+ type GitRunner = (args: string[], cwd?: string) => Promise<GitResult>;
22
+ declare function runCli(argv?: string[], cwd?: string): Promise<0 | 1>;
23
+ declare function resolveRepository(packageName: string, cwd: string): Promise<RepositoryRef>;
24
+ declare function processRepository(group: RepositoryGroup, target: string, runGit?: GitRunner): Promise<{
25
+ failed: boolean;
26
+ index?: IndexRepository;
27
+ }>;
28
+ declare function pullRepository(root: string, name: string, prune?: boolean, runGit?: GitRunner): Promise<"dirty" | "failed" | "updated">;
29
+ declare function invalidCloneReason(root: string, expected: RepositoryGroup, runGit?: GitRunner): Promise<string | undefined>;
30
+ //#endregion
31
+ export { invalidCloneReason, processRepository, pullRepository, resolveRepository, runCli };
package/dist/cli.mjs ADDED
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env node
2
+ import { execFile } from "node:child_process";
3
+ import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
4
+ import { basename, join, relative, resolve, sep } from "node:path";
5
+ import { parseArgs, promisify } from "node:util";
6
+ import { cancel, confirm, isCancel, multiselect, note, text } from "@clack/prompts";
7
+ //#region package.json
8
+ var version = "0.0.1";
9
+ //#endregion
10
+ //#region src/core.ts
11
+ const INDEX_MARKER = "<!-- generated by pkgref; do not edit -->";
12
+ const AGENTS_START = "<!-- PKGREF START -->";
13
+ const AGENTS_END = "<!-- PKGREF END -->";
14
+ const DEPENDENCY_FIELDS = [
15
+ "dependencies",
16
+ "devDependencies",
17
+ "peerDependencies",
18
+ "optionalDependencies"
19
+ ];
20
+ const REFERENCE_DIRECTORIES = /* @__PURE__ */ new Set([
21
+ "doc",
22
+ "docs",
23
+ "example",
24
+ "examples"
25
+ ]);
26
+ const byName = (a, b) => a.name.localeCompare(b.name);
27
+ function discoverDependencies(manifest) {
28
+ return [...new Set(DEPENDENCY_FIELDS.flatMap((field) => Object.keys(manifest[field] ?? {})))].sort();
29
+ }
30
+ async function readManifest(path) {
31
+ try {
32
+ const manifest = JSON.parse(await readFile(path, "utf8"));
33
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) throw new Error("expected a JSON object");
34
+ return manifest;
35
+ } catch (error) {
36
+ const action = error.code ? "Cannot read" : "Invalid";
37
+ throw new Error(`${action} package manifest at ${path}: ${errorMessage(error)}`);
38
+ }
39
+ }
40
+ function parsePackageList(value) {
41
+ return [...new Set(value.split(",").map((name) => name.trim()).filter(Boolean))];
42
+ }
43
+ function renderAgentsReference(contents, indexPath) {
44
+ const block = `${AGENTS_START}
45
+
46
+ ## Package references
47
+
48
+ Before working with dependency APIs, consult the
49
+ [package reference index](${indexPath}).
50
+ It links to cloned upstream documentation and examples.
51
+
52
+ ${AGENTS_END}`;
53
+ const marked = new RegExp(`${AGENTS_START}[\\s\\S]*?${AGENTS_END}`);
54
+ return `${(marked.test(contents) ? contents.replace(marked, block) : contents.trim() ? `${contents.trimEnd()}\n\n${block}` : block).trimEnd()}\n`;
55
+ }
56
+ function repositoryFromMetadata(metadata) {
57
+ const repository = metadata.repository;
58
+ const url = typeof repository === "string" ? repository : repository?.url;
59
+ if (!url) throw new Error("package metadata does not declare a repository");
60
+ return normalizeRepository(url);
61
+ }
62
+ function normalizeRepository(rawUrl) {
63
+ let url = rawUrl.trim().replace(/^git\+/, "");
64
+ if (!url) throw new Error("repository URL is empty");
65
+ if (/^github:/i.test(url)) url = `https://github.com/${url.slice(7)}`;
66
+ else if (/^[\w.-]+\/[\w.-]+$/.test(url)) url = `https://github.com/${url}`;
67
+ else if (/^git@[^:]+:/.test(url)) url = url.replace(/^git@([^:]+):/, "ssh://git@$1/");
68
+ else if (url.startsWith("git://")) url = `https://${url.slice(6)}`;
69
+ if (!URL.canParse(url)) throw new Error(`unsupported repository URL: ${rawUrl}`);
70
+ const parsed = new URL(url);
71
+ if (![
72
+ "http:",
73
+ "https:",
74
+ "ssh:"
75
+ ].includes(parsed.protocol)) throw new Error(`unsupported repository protocol: ${parsed.protocol}`);
76
+ const path = parsed.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
77
+ const name = basename(path);
78
+ if (!parsed.hostname || !path || !name || name === "." || name === "..") throw new Error(`invalid repository URL: ${rawUrl}`);
79
+ const user = parsed.protocol === "ssh:" && parsed.username ? `${parsed.username}@` : "";
80
+ return {
81
+ cloneUrl: parsed.protocol === "ssh:" ? `ssh://${user}${parsed.hostname}/${path}.git` : `${parsed.protocol}//${parsed.host}/${path}.git`,
82
+ key: `${parsed.hostname}/${path}`.toLowerCase(),
83
+ name
84
+ };
85
+ }
86
+ function groupRepositories(packages) {
87
+ const groups = /* @__PURE__ */ new Map();
88
+ const folders = /* @__PURE__ */ new Map();
89
+ for (const { name, repository } of packages) {
90
+ const owner = folders.get(repository.name);
91
+ if (owner && owner !== repository.key) throw new Error(`repository folder collision: different repositories resolve to "${repository.name}"`);
92
+ folders.set(repository.name, repository.key);
93
+ const group = groups.get(repository.key);
94
+ if (group) group.packages.push(name);
95
+ else groups.set(repository.key, {
96
+ ...repository,
97
+ packages: [name]
98
+ });
99
+ }
100
+ return [...groups.values()].map((group) => ({
101
+ ...group,
102
+ packages: [...new Set(group.packages)].sort()
103
+ })).sort(byName);
104
+ }
105
+ async function findReferenceDirectories(repositoryRoot, targetRoot) {
106
+ const found = [];
107
+ async function walk(directory) {
108
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
109
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
110
+ const path = join(directory, entry.name);
111
+ if (REFERENCE_DIRECTORIES.has(entry.name.toLowerCase())) found.push({
112
+ label: posixPath(relative(repositoryRoot, path)),
113
+ path: posixPath(relative(targetRoot, path))
114
+ });
115
+ else await walk(path);
116
+ }
117
+ }
118
+ await walk(repositoryRoot);
119
+ return found.sort((a, b) => a.path.localeCompare(b.path));
120
+ }
121
+ async function renderIndex(repositories, targetRoot) {
122
+ const sections = await Promise.all([...repositories].sort((a, b) => a.directory.localeCompare(b.directory)).map(async ({ directory, packages, root }) => {
123
+ const references = await findReferenceDirectories(root, targetRoot);
124
+ const links = references.length ? references.map((item) => `- [${escapeLabel(item.label)}](./${encodePath(item.path)})`) : [`- [Repository root](./${encodePath(posixPath(relative(targetRoot, root)))})`];
125
+ return `## ${directory}
126
+
127
+ Packages: ${[...packages].sort().map((name) => `\`${name}\``).join(", ")}
128
+
129
+ ${links.join("\n")}`;
130
+ }));
131
+ return `${INDEX_MARKER}\n\n# Package references\n\n${sections.join("\n\n")}\n`;
132
+ }
133
+ function mergeIndexes(existing, generated) {
134
+ const oldIndex = splitIndex(existing);
135
+ const newIndex = splitIndex(generated);
136
+ const sections = new Map([...oldIndex.sections, ...newIndex.sections]);
137
+ return `${[oldIndex.preamble, ...[...sections].sort(([a], [b]) => a.localeCompare(b)).map(([, value]) => value)].filter(Boolean).join("\n\n")}\n`;
138
+ }
139
+ function errorMessage(error) {
140
+ return error instanceof Error ? error.message : String(error);
141
+ }
142
+ const posixPath = (path) => path.split(sep).join("/");
143
+ const encodePath = (path) => path.split("/").map(encodeURIComponent).join("/");
144
+ const escapeLabel = (label) => label.replaceAll("\\", "\\\\").replaceAll("[", "\\[").replaceAll("]", "\\]");
145
+ function splitIndex(contents) {
146
+ const matches = [...contents.matchAll(/^## (.+)$/gm)];
147
+ const sections = /* @__PURE__ */ new Map();
148
+ matches.forEach((match, index) => {
149
+ sections.set(match[1].trim(), contents.slice(match.index, matches[index + 1]?.index).trimEnd());
150
+ });
151
+ return {
152
+ preamble: contents.slice(0, matches[0]?.index ?? contents.length).trimEnd(),
153
+ sections
154
+ };
155
+ }
156
+ //#endregion
157
+ //#region src/cli.ts
158
+ const exec = promisify(execFile);
159
+ const DEFAULT_DIR = "docs/pkg-reference";
160
+ const CANCELLED = Symbol("cancelled");
161
+ const OPTIONS = {
162
+ dir: { type: "string" },
163
+ help: {
164
+ type: "boolean",
165
+ short: "h"
166
+ },
167
+ pkgs: { type: "string" },
168
+ version: {
169
+ type: "boolean",
170
+ short: "v"
171
+ }
172
+ };
173
+ const HELP = `pkgref - clone package source references
174
+
175
+ Usage:
176
+ pkgref [--pkgs=<name,...>] [--dir=<path>]
177
+ pkgref update [--dir=<path>]
178
+
179
+ Options:
180
+ --pkgs Comma-separated packages. Undeclared packages are allowed with a warning.
181
+ --dir Target directory (default: ${DEFAULT_DIR}).
182
+ -h, --help Show this help.
183
+ -v, --version Show the installed version.
184
+
185
+ Commands:
186
+ update Fast-forward all cloned repositories to their latest upstream revision.
187
+ `;
188
+ async function runCli(argv = process.argv.slice(2), cwd = process.cwd()) {
189
+ try {
190
+ const { values, positionals } = parseArgs({
191
+ args: argv,
192
+ allowPositionals: true,
193
+ options: OPTIONS,
194
+ strict: true
195
+ });
196
+ if (values.help) {
197
+ console.log(HELP);
198
+ return 0;
199
+ }
200
+ if (values.version) {
201
+ console.log(version);
202
+ return 0;
203
+ }
204
+ if (positionals.length > 1 || positionals[0] && positionals[0] !== "update") throw new Error(`unknown command: ${positionals.join(" ")}`);
205
+ if (positionals[0] === "update") {
206
+ if (values.pkgs !== void 0) throw new Error("--pkgs cannot be used with the update command");
207
+ return await updateRepositories(resolve(cwd, values.dir ?? DEFAULT_DIR));
208
+ }
209
+ return await cloneRepositories(cwd, values.pkgs, values.dir);
210
+ } catch (error) {
211
+ if (error === CANCELLED) return 0;
212
+ console.error(`Error: ${errorMessage(error)}`);
213
+ return 1;
214
+ }
215
+ }
216
+ async function updateRepositories(target) {
217
+ if (!await exists(target)) {
218
+ console.log(`No package reference directory found at ${target}.`);
219
+ return 0;
220
+ }
221
+ const repositories = [];
222
+ for (const entry of await readdir(target, { withFileTypes: true })) if (entry.isDirectory() && await exists(join(target, entry.name, ".git"))) repositories.push(entry.name);
223
+ repositories.sort();
224
+ if (!repositories.length) {
225
+ console.log(`No cloned repositories found in ${target}.`);
226
+ return 0;
227
+ }
228
+ const updated = (await Promise.all(repositories.map((name) => pullRepository(join(target, name), name, true)))).filter((result) => result === "updated").length;
229
+ const failed = updated !== repositories.length;
230
+ console.log(`Finished: ${updated} of ${repositories.length} repositories updated${failed ? " with errors" : ""}.`);
231
+ return failed ? 1 : 0;
232
+ }
233
+ async function cloneRepositories(cwd, packageArgument, directory) {
234
+ const manifestPath = join(cwd, "package.json");
235
+ const declared = discoverDependencies(await readManifest(manifestPath));
236
+ const interactive = packageArgument === void 0;
237
+ let addToAgents = false;
238
+ const packages = interactive ? await selectPackages(declared) : parsePackageList(packageArgument);
239
+ if (!interactive) {
240
+ if (!packages.length) throw new Error("--pkgs must contain at least one package name");
241
+ for (const name of packages.filter((name) => !declared.includes(name))) console.warn(`Warning: "${name}" is not declared in ${manifestPath}; resolving it anyway.`);
242
+ }
243
+ const resolved = (await Promise.all(packages.map(async (name) => {
244
+ try {
245
+ return {
246
+ name,
247
+ repository: await resolveRepository(name, cwd)
248
+ };
249
+ } catch (error) {
250
+ console.error(`Failed to resolve ${name}: ${errorMessage(error)}`);
251
+ return null;
252
+ }
253
+ }))).filter((item) => item !== null);
254
+ let failed = resolved.length !== packages.length;
255
+ const groups = groupRepositories(resolved);
256
+ if (!groups.length) return failed ? 1 : 0;
257
+ if (interactive) {
258
+ await confirmRepositories(groups);
259
+ directory = (directory ?? await ask(text({
260
+ message: "Where should package references be stored?",
261
+ initialValue: DEFAULT_DIR,
262
+ validate: (value) => !value?.trim() ? "Target directory is required." : void 0
263
+ }))).trim();
264
+ addToAgents = await confirmChoice("Add the package reference index to AGENTS.md?");
265
+ }
266
+ const target = resolve(cwd, directory ?? DEFAULT_DIR);
267
+ const indexPath = join(target, "INDEX.md");
268
+ const existingIndex = await readOwnedIndex(indexPath);
269
+ await mkdir(target, { recursive: true });
270
+ const indexed = [];
271
+ for (const group of groups) {
272
+ const result = await processRepository(group, target);
273
+ failed ||= result.failed;
274
+ if (result.index) indexed.push(result.index);
275
+ }
276
+ const generated = await renderIndex(indexed, target);
277
+ await writeFile(indexPath, existingIndex ? mergeIndexes(existingIndex, generated) : generated);
278
+ console.log(`Wrote ${indexPath}`);
279
+ if (addToAgents) await addAgentsReference(cwd, indexPath);
280
+ console.log(`Finished: ${indexed.length} repositories indexed, ${failed ? "with errors" : "successfully"}.`);
281
+ return failed ? 1 : 0;
282
+ }
283
+ async function resolveRepository(packageName, cwd) {
284
+ const installedManifest = join(cwd, "node_modules", packageName, "package.json");
285
+ if (await exists(installedManifest)) return repositoryFromMetadata(await readManifest(installedManifest));
286
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, { headers: { accept: "application/json" } });
287
+ if (!response.ok) throw new Error(`npm registry returned ${response.status} ${response.statusText}`);
288
+ return repositoryFromMetadata(await response.json());
289
+ }
290
+ async function processRepository(group, target, runGit = git) {
291
+ const root = join(target, group.name);
292
+ const index = {
293
+ directory: group.name,
294
+ packages: group.packages,
295
+ root
296
+ };
297
+ if (!await exists(root)) {
298
+ console.log(`Cloning ${group.name} (${group.packages.join(", ")})...`);
299
+ const result = await runGit([
300
+ "clone",
301
+ "--depth",
302
+ "1",
303
+ "--",
304
+ group.cloneUrl,
305
+ root
306
+ ]);
307
+ if (!result.ok) {
308
+ console.error(`Failed to clone ${group.name}: ${result.output}`);
309
+ return { failed: true };
310
+ }
311
+ return {
312
+ failed: false,
313
+ index
314
+ };
315
+ }
316
+ const invalidReason = await invalidCloneReason(root, group, runGit);
317
+ if (invalidReason) {
318
+ console.warn(`Skipping ${group.name}: ${invalidReason}`);
319
+ return { failed: false };
320
+ }
321
+ if (!process.stdin.isTTY || !process.stdout.isTTY || !await confirmUpdate(group.name)) {
322
+ console.log(`Skipped update for ${group.name}.`);
323
+ return {
324
+ failed: false,
325
+ index
326
+ };
327
+ }
328
+ return {
329
+ failed: await pullRepository(root, group.name, false, runGit) === "failed",
330
+ index
331
+ };
332
+ }
333
+ async function pullRepository(root, name, prune = false, runGit = git) {
334
+ const status = await runGit(["status", "--porcelain"], root);
335
+ if (!status.ok) {
336
+ console.error(`Failed to inspect ${name}: ${status.output}`);
337
+ return "failed";
338
+ }
339
+ if (status.output) {
340
+ console.warn(`Skipped ${name}: the worktree has local changes.`);
341
+ return "dirty";
342
+ }
343
+ console.log(`Updating ${name}...`);
344
+ const pull = await runGit([
345
+ "pull",
346
+ "--ff-only",
347
+ ...prune ? ["--prune"] : []
348
+ ], root);
349
+ if (!pull.ok) {
350
+ console.error(`Failed to update ${name}: ${pull.output}`);
351
+ return "failed";
352
+ }
353
+ console.log(`Updated ${name}.`);
354
+ return "updated";
355
+ }
356
+ async function invalidCloneReason(root, expected, runGit = git) {
357
+ const origin = await runGit([
358
+ "remote",
359
+ "get-url",
360
+ "origin"
361
+ ], root);
362
+ if (!origin.ok) return "the destination is not a Git clone with an origin";
363
+ try {
364
+ return normalizeRepository(origin.output).key === expected.key ? void 0 : "the existing clone has a different origin";
365
+ } catch (error) {
366
+ return `cannot read its origin: ${errorMessage(error)}`;
367
+ }
368
+ }
369
+ async function readOwnedIndex(path) {
370
+ const contents = await readOptional(path);
371
+ if (contents === void 0) return;
372
+ if (!contents.startsWith("<!-- generated by pkgref; do not edit -->")) throw new Error(`refusing to overwrite user-owned index at ${path}`);
373
+ return contents;
374
+ }
375
+ async function selectPackages(packages) {
376
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("--pkgs is required when pkgref is not running in an interactive terminal");
377
+ if (!packages.length) stop("No dependencies found in package.json.");
378
+ const selected = await ask(multiselect({
379
+ message: "Select packages to clone",
380
+ options: packages.map((value) => ({
381
+ label: value,
382
+ value
383
+ })),
384
+ required: false,
385
+ maxItems: 10
386
+ }));
387
+ if (!selected.length) stop("No packages selected.");
388
+ return selected;
389
+ }
390
+ async function confirmRepositories(repositories) {
391
+ note(repositories.map(({ name, packages, cloneUrl }) => `${name} (${packages.join(", ")})\n${cloneUrl}`).join("\n\n"), "Git repositories");
392
+ if (!await confirmChoice("Clone these Git repositories?", "Operation cancelled. No repositories were cloned.")) abort("Operation cancelled. No repositories were cloned.");
393
+ }
394
+ const confirmChoice = (message, cancelMessage) => ask(confirm({
395
+ message,
396
+ initialValue: true
397
+ }), cancelMessage);
398
+ async function confirmUpdate(name) {
399
+ const value = await confirm({
400
+ message: `${name} already exists. Update it?`,
401
+ initialValue: false
402
+ });
403
+ if (isCancel(value)) {
404
+ cancel("Update skipped.");
405
+ return false;
406
+ }
407
+ return value;
408
+ }
409
+ async function ask(answer, message = "Operation cancelled.") {
410
+ const value = await answer;
411
+ if (isCancel(value)) abort(message);
412
+ return value;
413
+ }
414
+ function abort(message) {
415
+ cancel(message);
416
+ throw CANCELLED;
417
+ }
418
+ function stop(message) {
419
+ console.log(message);
420
+ throw CANCELLED;
421
+ }
422
+ async function addAgentsReference(cwd, indexPath) {
423
+ const path = join(cwd, "AGENTS.md");
424
+ const link = relative(cwd, indexPath).split(sep).map(encodeURIComponent).join("/");
425
+ await writeFile(path, renderAgentsReference(await readOptional(path) ?? "", link));
426
+ console.log(`Updated ${path}`);
427
+ }
428
+ async function git(args, cwd) {
429
+ try {
430
+ const { stdout, stderr } = await exec("git", args, {
431
+ cwd,
432
+ encoding: "utf8"
433
+ });
434
+ return {
435
+ ok: true,
436
+ output: (stderr || stdout).trim()
437
+ };
438
+ } catch (error) {
439
+ const result = error;
440
+ return {
441
+ ok: false,
442
+ output: (result.stderr || result.stdout || result.message).trim()
443
+ };
444
+ }
445
+ }
446
+ async function readOptional(path) {
447
+ try {
448
+ return await readFile(path, "utf8");
449
+ } catch (error) {
450
+ if (error.code === "ENOENT") return;
451
+ throw error;
452
+ }
453
+ }
454
+ async function exists(path) {
455
+ try {
456
+ await access(path);
457
+ return true;
458
+ } catch {
459
+ return false;
460
+ }
461
+ }
462
+ if (import.meta.url === `file://${process.argv[1]}`) process.exitCode = await runCli();
463
+ //#endregion
464
+ export { invalidCloneReason, processRepository, pullRepository, resolveRepository, runCli };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "pkgref",
3
+ "version": "0.0.1",
4
+ "description": "Clone dependency source repositories and index their docs and examples.",
5
+ "homepage": "https://github.com/yue4u/pkgref#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/yue4u/pkgref/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/yue4u/pkgref.git"
13
+ },
14
+ "bin": {
15
+ "pkgref": "./dist/cli.mjs"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "type": "module",
21
+ "exports": {
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@clack/prompts": "^1.7.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^26.1.1",
32
+ "bumpp": "^11.1.0",
33
+ "typescript": "^7.0.2",
34
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.5",
35
+ "vite-plus": "0.2.5"
36
+ },
37
+ "devEngines": {
38
+ "packageManager": {
39
+ "name": "pnpm",
40
+ "version": "11.17.0",
41
+ "onFail": "download"
42
+ }
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
46
+ },
47
+ "scripts": {
48
+ "build": "vp pack",
49
+ "dev": "vp pack --watch",
50
+ "test": "vp test",
51
+ "check": "vp check"
52
+ }
53
+ }