dep-radius 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 prakticode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # dep-radius
2
+
3
+ [![npm](https://img.shields.io/npm/v/dep-radius)](https://www.npmjs.com/package/dep-radius)
4
+ [![CI](https://github.com/prakticode/dep-radius/actions/workflows/ci.yml/badge.svg)](https://github.com/prakticode/dep-radius/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/dep-radius)](LICENSE)
6
+
7
+ Which changes in a dependency update land on code you actually wrote.
8
+
9
+ ```sh
10
+ npx dep-radius # every direct dependency with an update waiting
11
+ npx dep-radius schemakit # one package
12
+ npx dep-radius schemakit@3.2.0 # an exact target
13
+ ```
14
+
15
+ ```
16
+ ◇ Read 412 files
17
+ ◇ Checked 38 packages in 9.4s
18
+
19
+ schemakit 3.1.4 → 3.2.0 minor published 3d ago REVIEW
20
+ surface changes ......... 12
21
+ changes you touch ....... 0
22
+ notes mentioning you .... 1 of 18, notes for 1/1 versions (github-release)
23
+
24
+ 3.2.0 ⚠️ The email pattern no longer accepts quoted local parts
25
+ you use: email
26
+ src/signup/schema.ts:14
27
+ src/billing/contact.ts:9 (via src/lib/validation.ts)
28
+
29
+ ...
30
+
31
+ 38 with an update 31 quiet · 7 review · 0 blocked · 12 up to date
32
+ ```
33
+
34
+ No account, no configuration file, no build. It reads the folder as it is: any installer, one
35
+ manifest or fifty, TypeScript or plain JavaScript.
36
+
37
+ ## How it decides
38
+
39
+ Two nets, counted apart and never merged into one reassuring number:
40
+
41
+ 1. **The type surface.** Both versions' declaration files are compared, and every changed or removed
42
+ export is matched against the names your code resolves into the package, through local re-export
43
+ files and values built in other files.
44
+ 2. **The release notes.** GitHub releases or the changelog, split into entries, kept only when they
45
+ name something you use.
46
+
47
+ | Verdict | Exit | Means |
48
+ | ------- | ---- | -------------------------------------------------------------------- |
49
+ | quiet | 0 | nothing you use changed, no note mentions it: merge without reading |
50
+ | review | 1 | here are the lines concerned, or here is what the tool could not see |
51
+ | blocked | 2 | an export you call was removed |
52
+
53
+ Anything the tool cannot see pushes towards review, never towards quiet: packages used only from
54
+ scripts, config strings or CSS, names passed around whole, packages with neither types nor notes. A
55
+ quiet verdict that stood on one net only is marked `*`.
56
+
57
+ ## Options
58
+
59
+ `--json`, `--markdown` (a pull request comment), `--offline`, `--min-age <duration>` (default 1d, or
60
+ the project's `minimumReleaseAge`), `--latest`, `--no-notes`, `--no-surface`, `--prod`, `--verbose`.
61
+ Set `GITHUB_TOKEN`, or log in with `gh`, to lift GitHub's limit from 60 to 5000 requests an hour.
62
+
63
+ `--json` is a stable contract, `schemaVersion: 1`, described by
64
+ [`brief-v1.schema.json`](../core/schema/brief-v1.schema.json), shipped in `@dep-radius/core` as
65
+ `@dep-radius/core/schema/brief-v1.schema.json`. Version 1 only ever gains fields; removing or
66
+ renaming one means version 2.
67
+
68
+ Everything downloaded is cached: a second run is offline and takes about as long as reading your
69
+ files. Type surfaces are keyed by tarball integrity, so they are the same for everyone.
70
+
71
+ ## Limits
72
+
73
+ - Behaviour changes are only seen through release notes.
74
+ - No type checker runs over your code: a member reached through a callback or a value from an
75
+ unfollowed place is matched by name only.
76
+ - A package whose declarations re-export another package's types is judged from its notes only.
77
+ - Release notes are read from GitHub and the package's own changelog.
78
+ - Transitive dependencies are out of scope; a lockfile and an install cooldown own that risk.
package/dist/cli.js ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { createRequire } from "node:module";
5
+ import { existsSync, statSync } from "node:fs";
6
+ import { runDebug } from "@dep-radius/core/debug";
7
+ import { createCtx, defaultCacheDir, parseDuration, renderJson, renderMarkdown, run, } from "@dep-radius/core";
8
+ import { renderTerminal } from "./render/terminal.js";
9
+ import { createProgressView } from "./render/progress.js";
10
+ const HELP = `radius: which changes in a dependency update land on code you wrote
11
+
12
+ usage
13
+ radius [path] every direct dependency with an update waiting
14
+ radius <pkg>[@<version|tag>] ... only these packages (a version analyses that exact target)
15
+
16
+ options
17
+ --json the brief as JSON (schemaVersion 1)
18
+ --markdown a pull request comment
19
+ --offline read the cache only, never the network
20
+ --min-age <duration> hold back versions younger than this (default 1d, or the project's
21
+ minimumReleaseAge); "0" disables
22
+ --latest target the newest version, across majors
23
+ --no-notes do not read release notes
24
+ --no-surface do not compare type surfaces
25
+ --prod skip devDependencies
26
+ --concurrency <n> network requests at once (default 8)
27
+ --cache-dir <dir> default $RADIUS_CACHE_DIR, then the platform cache folder
28
+ --verbose every note, every site, every quiet package
29
+ --version, --help
30
+
31
+ exit codes
32
+ 0 quiet: nothing you use changed 1 review: read the brief 2 blocked: something you use was removed
33
+ 3 the command itself failed
34
+
35
+ GITHUB_TOKEN, GH_TOKEN or a logged in gh raise the GitHub limit from 60 to 5000 requests an hour.
36
+ `;
37
+ async function main(argv) {
38
+ if (argv[0] === "debug")
39
+ return runDebug(argv.slice(1));
40
+ let parsed;
41
+ try {
42
+ parsed = parseArgs({
43
+ args: argv,
44
+ allowPositionals: true,
45
+ allowNegative: true,
46
+ options: {
47
+ json: { type: "boolean" },
48
+ markdown: { type: "boolean" },
49
+ offline: { type: "boolean" },
50
+ "min-age": { type: "string" },
51
+ latest: { type: "boolean" },
52
+ notes: { type: "boolean", default: true },
53
+ surface: { type: "boolean", default: true },
54
+ prod: { type: "boolean" },
55
+ concurrency: { type: "string" },
56
+ "cache-dir": { type: "string" },
57
+ verbose: { type: "boolean", short: "v" },
58
+ now: { type: "string" },
59
+ version: { type: "boolean" },
60
+ help: { type: "boolean", short: "h" },
61
+ },
62
+ });
63
+ }
64
+ catch (error) {
65
+ process.stderr.write(`radius: ${error.message}\n\n${HELP}`);
66
+ return 3;
67
+ }
68
+ const v = parsed.values;
69
+ if (v.help) {
70
+ process.stdout.write(HELP);
71
+ return 0;
72
+ }
73
+ if (v.version) {
74
+ process.stdout.write(`${cliVersion()}\n`);
75
+ return 0;
76
+ }
77
+ if (v.json && v.markdown) {
78
+ process.stderr.write("radius: --json and --markdown cannot be combined\n");
79
+ return 3;
80
+ }
81
+ let root = process.cwd();
82
+ const specs = [];
83
+ // a path is written like one (./x, ../x, /x, a/b that exists); a bare word is a package, so a
84
+ // folder that shares a package's name is reached as ./name
85
+ for (const p of parsed.positionals) {
86
+ const abs = resolve(p);
87
+ const pathLike = p === "." ||
88
+ p.startsWith("./") ||
89
+ p.startsWith("../") ||
90
+ p.startsWith("/") ||
91
+ p.includes("\\") ||
92
+ (p.includes("/") && !p.startsWith("@"));
93
+ if (pathLike && existsSync(abs) && statSync(abs).isDirectory())
94
+ root = abs;
95
+ else if (pathLike) {
96
+ process.stderr.write(`radius: ${p} is not a directory\n`);
97
+ return 3;
98
+ }
99
+ else
100
+ specs.push(p);
101
+ }
102
+ if (!existsSync(root)) {
103
+ process.stderr.write(`radius: ${root} does not exist\n`);
104
+ return 3;
105
+ }
106
+ const nowRaw = v.now ?? process.env.RADIUS_NOW;
107
+ const now = nowRaw ? Date.parse(nowRaw) : Date.now();
108
+ if (!Number.isFinite(now)) {
109
+ process.stderr.write(`radius: invalid --now ${nowRaw}\n`);
110
+ return 3;
111
+ }
112
+ let minAgeMs;
113
+ try {
114
+ minAgeMs =
115
+ v["min-age"] !== undefined ? parseDuration(v["min-age"]) : undefined;
116
+ }
117
+ catch (error) {
118
+ process.stderr.write(`radius: ${error.message}\n`);
119
+ return 3;
120
+ }
121
+ const format = v.json ? "json" : v.markdown ? "markdown" : "terminal";
122
+ const opts = {
123
+ root,
124
+ specs,
125
+ format,
126
+ offline: !!v.offline,
127
+ minAgeMs,
128
+ latest: !!v.latest,
129
+ notes: v.notes !== false,
130
+ surface: v.surface !== false,
131
+ prod: !!v.prod,
132
+ concurrency: Math.max(1, Number(v.concurrency ?? 8) || 8),
133
+ cacheDir: v["cache-dir"]
134
+ ? resolve(v["cache-dir"])
135
+ : defaultCacheDir(process.env),
136
+ verbose: !!v.verbose,
137
+ now,
138
+ color: format === "terminal" && !!process.stdout.isTTY && !process.env.NO_COLOR,
139
+ };
140
+ const ctx = createCtx(opts);
141
+ // a person watching gets progress on stderr; --json, --markdown, CI and pipes get none
142
+ const view = format === "terminal" &&
143
+ process.stderr.isTTY &&
144
+ // a pseudo terminal that reports no width wraps every frame after one character
145
+ (process.stderr.columns ?? 0) >= 40 &&
146
+ !process.env.CI
147
+ ? createProgressView(process.stderr)
148
+ : undefined;
149
+ let brief;
150
+ try {
151
+ brief = await run(opts, ctx, view ? (e) => view.onEvent(e) : undefined);
152
+ }
153
+ catch (error) {
154
+ view?.fail();
155
+ throw error;
156
+ }
157
+ view?.finish(brief);
158
+ const text = format === "json"
159
+ ? renderJson(brief)
160
+ : format === "markdown"
161
+ ? renderMarkdown(brief, now)
162
+ : renderTerminal(brief, {
163
+ color: opts.color,
164
+ verbose: opts.verbose,
165
+ now,
166
+ });
167
+ process.stdout.write(text);
168
+ return brief.exitCode;
169
+ }
170
+ main(process.argv.slice(2)).then((code) => {
171
+ process.exitCode = code;
172
+ }, (error) => {
173
+ process.stderr.write(`radius: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
174
+ process.exitCode = 3;
175
+ });
176
+ // the version of this package, which is what `radius --version` reports; the brief carries the engine's
177
+ function cliVersion() {
178
+ return createRequire(import.meta.url)("../package.json").version;
179
+ }
@@ -0,0 +1,53 @@
1
+ import { progress, spinner } from "@clack/prompts";
2
+ // Drawn on stderr, and only when a person is watching: the report on stdout stays byte for byte
3
+ // what a script or an agent reads.
4
+ export function createProgressView(output, now = Date.now) {
5
+ const started = now();
6
+ const reading = spinner({ output, withGuide: false });
7
+ reading.start("Reading project files");
8
+ let bar;
9
+ let files = 0;
10
+ const running = new Set();
11
+ return {
12
+ onEvent(e) {
13
+ if (e.type === "files") {
14
+ files = e.total;
15
+ reading.message(`Reading project files ${e.done}/${e.total}`);
16
+ }
17
+ else if (e.type === "packages") {
18
+ reading.stop(`Read ${files} ${files === 1 ? "file" : "files"}`);
19
+ if (e.total === 0)
20
+ return;
21
+ bar = progress({ output, withGuide: false, max: e.total, size: 30 });
22
+ bar.start(`Checking ${e.total} ${e.total === 1 ? "package" : "packages"}`);
23
+ }
24
+ else if (e.type === "package-start") {
25
+ running.add(e.name);
26
+ }
27
+ else if (bar) {
28
+ running.delete(e.name);
29
+ const percent = Math.round((100 * e.done) / e.total);
30
+ // every package starts at once, so names only help once a few slow ones remain
31
+ const waiting = running.size > 0 && running.size <= 3
32
+ ? ` waiting on ${[...running].join(", ")}`
33
+ : "";
34
+ bar.advance(1, `${e.done}/${e.total} ${percent}%${waiting}`);
35
+ }
36
+ },
37
+ finish(brief) {
38
+ const seconds = ((now() - started) / 1000).toFixed(1);
39
+ const checked = brief.packages.length + brief.upToDate;
40
+ const message = `Checked ${checked} ${checked === 1 ? "package" : "packages"} in ${seconds}s`;
41
+ if (bar)
42
+ bar.stop(message);
43
+ else
44
+ reading.stop(message);
45
+ },
46
+ fail() {
47
+ if (bar)
48
+ bar.error("Stopped");
49
+ else
50
+ reading.error("Stopped");
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,121 @@
1
+ import { styleText } from "node:util";
2
+ import { ago, coverageLabel, groupBriefs, oneNetOnly, opaqueLabel, siteLabel, sitesFor, surfaceLabel, } from "@dep-radius/core/render";
3
+ export function renderTerminal(brief, opts) {
4
+ const c = (style, s) => (opts.color ? styleText(style, s) : s);
5
+ const out = [];
6
+ const g = groupBriefs(brief);
7
+ const sources = [...new Set(brief.packages.map((p) => p.versionSource))].join(", ") ||
8
+ "none";
9
+ out.push(`${c("bold", "radius")} ${brief.root}`);
10
+ out.push(c("dim", `${brief.manifests} ${brief.manifests === 1 ? "manifest" : "manifests"} · ${brief.packages.length} with an update · ${brief.upToDate} up to date · versions from ${sources}`));
11
+ out.push("");
12
+ const siteCap = opts.verbose ? Number.POSITIVE_INFINITY : 5;
13
+ for (const p of g.detailed) {
14
+ out.push(...packageBlock(p, c, siteCap, opts));
15
+ out.push("");
16
+ }
17
+ if (g.quiet.length > 0) {
18
+ out.push(`${c("green", "quiet")} (${g.quiet.length}) ${c("dim", "merge without reading")}`);
19
+ const items = g.quiet.map((p) => `${p.pkg} ${p.from} → ${p.to}${oneNetOnly(p) ? "*" : ""}`);
20
+ if (opts.verbose)
21
+ for (const p of g.quiet)
22
+ out.push(` ${p.pkg} ${p.from} → ${p.to} ${c("dim", p.reasons.map((r) => r.detail).join("; "))}`);
23
+ else
24
+ out.push(` ${items.join(", ")}`);
25
+ if (!opts.verbose && g.quiet.some(oneNetOnly))
26
+ out.push(c("dim", " * judged on one net only: no types, or notes missing for some versions (--verbose)"));
27
+ out.push("");
28
+ }
29
+ if (g.unseen.length > 0) {
30
+ out.push(`${c("yellow", "cannot see how you use these")} (${g.unseen.length}) ${c("dim", "run your build or tests")}`);
31
+ for (const p of g.unseen)
32
+ out.push(` ${p.pkg} ${p.from} → ${p.to} ${c("dim", p.bump)} ${c("dim", opaqueLabel(p))}`);
33
+ out.push("");
34
+ }
35
+ if (brief.notAnalyzed.length > 0) {
36
+ out.push(c("dim", `not analysed (${brief.notAnalyzed.length})`));
37
+ for (const n of brief.notAnalyzed)
38
+ out.push(c("dim", ` ${n.pkg}: ${n.reason}`));
39
+ out.push("");
40
+ }
41
+ const globalLine = brief.global.map((x) => `${x.count} ${x.kind}`).join(", ");
42
+ if (globalLine)
43
+ out.push(c("dim", `whole project: ${globalLine}`));
44
+ if (brief.limits.length > 0) {
45
+ out.push(c("dim", "limits"));
46
+ for (const l of brief.limits)
47
+ out.push(c("dim", ` - ${l}`));
48
+ }
49
+ const rateLimited = brief.packages.some((p) => p.notes.perVersion.some((v) => v.reason === "rate-limited"));
50
+ if (rateLimited) {
51
+ out.push("");
52
+ out.push(c("yellow", "GitHub's rate limit was reached, so some release notes were not read: set GITHUB_TOKEN or run gh auth login."));
53
+ }
54
+ const count = (v) => brief.packages.filter((p) => p.verdict === v).length;
55
+ out.push("");
56
+ out.push(`${c("bold", `${brief.packages.length} with an update`)} ${c("green", `${count("quiet")} quiet`)} · ${c("yellow", `${count("review")} review`)} · ${c("red", `${count("blocked")} blocked`)} ${c("dim", `· ${brief.upToDate} up to date`)}`);
57
+ return `${out.join("\n")}\n`;
58
+ }
59
+ function packageBlock(p, c, siteCap, opts) {
60
+ const lines = [];
61
+ const verdict = p.verdict === "blocked"
62
+ ? c(["bold", "red"], "BLOCKED")
63
+ : c(["bold", "yellow"], "REVIEW");
64
+ lines.push(`${c("bold", p.pkg)} ${p.from} → ${p.to} ${p.bump} ${c("dim", `published ${ago(p.publishedAt, opts.now)}`)} ${verdict}`);
65
+ const direct = p.notes.matched.filter((m) => m.direct).length;
66
+ const possibly = p.notes.matched.length - direct;
67
+ lines.push(` surface changes ......... ${surfaceLabel(p)}`);
68
+ if (p.surface.status === "computed")
69
+ lines.push(` changes you touch ....... ${p.surface.touched.length}`);
70
+ lines.push(` notes mentioning you .... ${p.notes.coverage === "disabled" ? "not read" : direct}${possibly > 0 ? c("dim", ` (+${possibly} possibly)`) : ""}${c("dim", ` of ${p.notes.total}, ${coverageLabel(p)}`)}`);
71
+ lines.push(c("dim", ` used in ${p.usage.files} ${p.usage.files === 1 ? "file" : "files"}, ${p.usage.sites.length} sites · ${p.manifests.join(", ")}`));
72
+ const touchCap = opts.verbose ? Number.POSITIVE_INFINITY : 10;
73
+ for (const t of p.surface.touched.slice(0, touchCap)) {
74
+ lines.push("");
75
+ const label = t.bucket === "removed" ? c("red", "removed") : t.bucket;
76
+ lines.push(` ${label} ${c("bold", t.change.path)}${t.strength === "weak" ? c("dim", " (by member name)") : ""}`);
77
+ if (t.change.before)
78
+ lines.push(c("dim", ` before ${t.change.before.join(" | ").slice(0, 200)}`));
79
+ if (t.change.after)
80
+ lines.push(c("dim", ` after ${t.change.after.join(" | ").slice(0, 200)}`));
81
+ for (const s of t.sites.slice(0, siteCap))
82
+ lines.push(` ${siteLabel(s)}`);
83
+ if (t.sites.length > siteCap)
84
+ lines.push(c("dim", ` ... ${t.sites.length - siteCap} more`));
85
+ }
86
+ if (p.surface.touched.length > touchCap)
87
+ lines.push("", c("dim", ` ... ${p.surface.touched.length - touchCap} more changes you touch (--verbose)`));
88
+ const shownNotes = opts.verbose
89
+ ? p.notes.matched
90
+ : p.notes.matched.slice(0, 8);
91
+ if (shownNotes.length > 0)
92
+ lines.push("");
93
+ for (const m of shownNotes) {
94
+ const names = [...new Set(m.hits.map((h) => h.name))];
95
+ const tag = m.direct ? "" : c("dim", " possibly");
96
+ lines.push(` ${c("cyan", m.entry.version)} ${m.entry.title}${tag}`);
97
+ const strongNames = m.hits
98
+ .filter((h) => h.strength === "strong")
99
+ .map((h) => h.name);
100
+ const sites = sitesFor(p, strongNames.length > 0 ? strongNames : names);
101
+ lines.push(c("dim", ` you use: ${names.join(", ")}`));
102
+ for (const s of sites.slice(0, m.direct ? Math.min(3, siteCap) : 1))
103
+ lines.push(` ${siteLabel(s)}`);
104
+ if (sites.length > (m.direct ? Math.min(3, siteCap) : 1))
105
+ lines.push(c("dim", ` ... ${sites.length - (m.direct ? Math.min(3, siteCap) : 1)} more sites`));
106
+ }
107
+ if (p.notes.matched.length > shownNotes.length)
108
+ lines.push(c("dim", ` ... ${p.notes.matched.length - shownNotes.length} more notes (--verbose)`));
109
+ for (const e of p.notes.unattributedBreaking)
110
+ lines.push(` ${c("cyan", e.version)} ${e.title} ${c("dim", "breaking, names no API")}`);
111
+ lines.push("");
112
+ for (const r of p.reasons)
113
+ lines.push(c("dim", ` why: ${r.detail}`));
114
+ const skipped = p.skippedNewer.filter((s) => s.reason === "too-new");
115
+ if (skipped.length > 0) {
116
+ lines.push(c("dim", ` newer, held back: ${skipped.map((s) => `${s.version} (${s.publishedAt ? ago(s.publishedAt, opts.now) : "too new"})`).join(", ")}`));
117
+ }
118
+ if (p.alsoAvailable)
119
+ lines.push(c("dim", ` also available: ${p.alsoAvailable.version} (${p.alsoAvailable.bump}, not analysed; radius ${p.pkg}@${p.alsoAvailable.version})`));
120
+ return lines;
121
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "dep-radius",
3
+ "version": "0.1.0",
4
+ "description": "Tells you which changes in a dependency update land on code you actually wrote.",
5
+ "license": "MIT",
6
+ "author": "prakticode",
7
+ "homepage": "https://github.com/prakticode/dep-radius#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/prakticode/dep-radius.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/prakticode/dep-radius/issues"
15
+ },
16
+ "keywords": [
17
+ "dependencies",
18
+ "upgrade",
19
+ "semver",
20
+ "changelog",
21
+ "release-notes",
22
+ "renovate",
23
+ "dependabot",
24
+ "breaking-changes",
25
+ "typescript",
26
+ "cli"
27
+ ],
28
+ "type": "module",
29
+ "bin": {
30
+ "radius": "dist/cli.js",
31
+ "dep-radius": "dist/cli.js"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.12"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "@clack/prompts": "^1.8.0",
44
+ "@dep-radius/core": "^0.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@repo/eslint-config": "0.0.0",
48
+ "@repo/typescript-config": "0.0.0",
49
+ "@types/node": "^24.0.0",
50
+ "eslint": "^9.39.5",
51
+ "typescript": "~6.0.3",
52
+ "vitest": "^5.0.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "dev": "node src/cli.ts",
57
+ "typecheck": "tsc --noEmit",
58
+ "lint": "eslint --max-warnings 0",
59
+ "lint:fix": "eslint --fix",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "clean": "rm -rf dist .turbo"
63
+ }
64
+ }