@opndev/rzilla 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/Changes ADDED
@@ -0,0 +1,5 @@
1
+ Revision history for @opndev/rzilla
2
+
3
+ {{ NEXT }}
4
+
5
+ * First release to an unsuspecting world
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Wesley Schwengle <wesleys@opperschaap.net>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # @opndev/rzilla
8
+
9
+ Release zilla for npm packages.
10
+
11
+ `rzil` lets you replace manual `package.json` maintenance with a
12
+ declarative `dist.toml` file.\
13
+ Inspired by Dist::Zilla, but for npm.
14
+
15
+ ## Philosophy
16
+
17
+ - `dist.toml` is the source of truth.
18
+ - `package.json` is a generated artifact.
19
+ - Strict where ambiguity is dangerous.
20
+ - Ergonomic where it does not matter.
21
+ - No automatic pushing.
22
+ - Deterministic releases.
23
+
24
+ ## Commands
25
+
26
+ The following commands are listed, but maybe incomplete. Please run the actual
27
+ script for an update to date listing.
28
+
29
+ ``` bash
30
+ rzil pkg # Generate package.json from dist.toml
31
+ rzil release # Run release workflow
32
+ rzil test # npm test
33
+ rzil build # Run build workflow
34
+ rzil clean # Clean the .build dir
35
+ rzil prereqs # List all the dependencies
36
+ ```
37
+
38
+ # dist.toml Example
39
+
40
+ ``` toml
41
+ name = "@opndev/rzilla"
42
+ description = "Release zilla for npm packages"
43
+ type = "module"
44
+ keywords = ["release","npm","changes","git"]
45
+
46
+ [license]
47
+ spdx = "MIT"
48
+ file = "LICENSES/MIT.txt"
49
+
50
+ [author]
51
+ name = "Your Name"
52
+ email = "you@example.com"
53
+
54
+ [repository]
55
+ remote = "origin"
56
+
57
+ [prereqs]
58
+ node = ">= 18 < 22"
59
+ npm = ">= 9"
60
+ lodash = "^4.17.0"
61
+
62
+ [prereqs.dev]
63
+ tap = "latest"
64
+ jsdoc = "0"
65
+
66
+ [prereqs.peer]
67
+ left-pad = "latest"
68
+
69
+ [gather]
70
+ main = "lib/index.mjs"
71
+ bin = "bin"
72
+ files = "lib/**/*.mjs"
73
+ include = ["README.md"]
74
+
75
+ [exports]
76
+ __DOT__ = "lib/index.mjs"
77
+ foo = "lib/foo.mjs"
78
+
79
+ [release]
80
+ changes = "Changes"
81
+ tagPrefix = "v"
82
+ bump = "patch"
83
+ access = "public"
84
+
85
+ [release.preflight]
86
+ dirty = "dist.toml"
87
+ airplane = false
88
+
89
+ [release.after]
90
+ commit = ["Changes", "dist.toml", "package.json"]
91
+ bump = true
92
+ ```
93
+
94
+ # Key Concepts
95
+
96
+ ## package.json is generated
97
+
98
+ Run:
99
+
100
+ ``` bash
101
+ rzil pkg
102
+ ```
103
+
104
+ This creates:
105
+
106
+ - `package.json`
107
+ - `bin` entries from `gather.bin`
108
+ - `exports` map
109
+ - dependencies from `[prereqs]`
110
+ - engines from `node` and `npm`
111
+ - repository + homepage derived from git remote
112
+
113
+ Do not edit `package.json` manually.
114
+
115
+
116
+ ## Prereqs
117
+
118
+ Prereqs or dependencies can be set via `[prereqs]` and friends:
119
+
120
+ ``` toml
121
+ [prereqs]
122
+ node = ">= 18 < 22"
123
+ npm = ">= 9"
124
+ foo = "^1.2.3"
125
+ bar = 0
126
+ baz = "latest"
127
+ ```
128
+
129
+ Mapping:
130
+
131
+ - `node` → `engines.node`
132
+ - `npm` → `engines.npm`
133
+ - others → `dependencies`
134
+ - `0` → `"*"`
135
+ - Spaces in ranges are normalized (`"> 18 < 22"` → `">18 <22"`)
136
+
137
+ ```toml
138
+ [prereqs.test]
139
+ tap = "latest"
140
+
141
+ [prereqs.peer]
142
+ left-pad = "latest"
143
+ ```
144
+
145
+ ## Autoprereqs
146
+
147
+ When enabled, rzil scans your source tree and fills in missing dependencies:
148
+
149
+ ``` toml
150
+ [autoprereqs]
151
+ # enabled when the table exists
152
+ # enabled = true
153
+ ignore = ["node:fs"]
154
+ ```
155
+
156
+ - Runtime imports (uses the `[gather]` paths/globs) are added to
157
+ `dependencies`.
158
+ - Test imports (from `t/`, `test/`, `tests/`, `__tests__/`) are added to
159
+ `devDependencies`.
160
+ - Inferred versions default to `"latest"`.
161
+ - Explicit entries in `[prereqs]` always win.
162
+ - Relative imports and Node builtins are ignored.
163
+
164
+ ## Gather
165
+
166
+ ``` toml
167
+ [gather]
168
+ main = "lib/index.mjs"
169
+ bin = ["bin", "cli"]
170
+ files = "lib/**/*.mjs"
171
+ include = ["README.md"]
172
+ ```
173
+
174
+ - `main` defines the root export `"."`
175
+ - `bin` auto-discovers CLI files
176
+ - `files` and `include` populate `package.json.files`
177
+ - `bin` supports string or array
178
+ - You don't need to add your license file, it is taken from `license.file`.
179
+
180
+
181
+ ## License
182
+
183
+ ``` toml
184
+ [license]
185
+ spdx = "MIT"
186
+ file = "LICENSES/MIT.txt"
187
+ ```
188
+
189
+ - `spdx` becomes `package.json.license`.
190
+ - `file` is written as `LICENSE` in the build artifact.
191
+
192
+
193
+ ## Exports
194
+
195
+ ``` toml
196
+ [exports]
197
+ __DOT__ = "lib/index.mjs"
198
+ foo = "lib/foo.mjs"
199
+
200
+ [exports.deny]
201
+ testing = true
202
+ ```
203
+
204
+ Rules:
205
+
206
+ - `__DOT__` → `"."`
207
+ - other keys → `"./key"`
208
+ - `exports.deny` removes subpaths
209
+ - `exports.deny.__DOT__` is forbidden
210
+
211
+ If `[exports]` exists, you fully control the export surface.
212
+
213
+
214
+ ## Repository
215
+
216
+ ``` toml
217
+ [repository]
218
+ remote = "origin"
219
+ provider = "github" # optional
220
+ ```
221
+
222
+ rzil:
223
+
224
+ - reads `git remote`
225
+ - derives `repository.url`
226
+ - derives `repository.homepage`
227
+ - derives `bugs.url` if `[bugtracker]` exists
228
+ - supports github, gitlab, codeberg, bitbucket
229
+ - supports private hosts with `provider`
230
+
231
+ rzil never pushes automatically.
232
+
233
+
234
+ ## Release Workflow
235
+
236
+ ``` bash
237
+ rzil release
238
+ ```
239
+
240
+ Steps:
241
+
242
+ 1. Check `{{ NEXT }}` in Changes has entries
243
+ 2. Run tests
244
+ 3. Check git dirty state (with allowlist)
245
+ 4. Create a build directory (`.build/<id>/` and `.build/current`)
246
+ 5. Copy only publishable files into the build directory
247
+ 6. Generate `package.json` (and other build artifacts) into the build
248
+ directory
249
+ 7. Finalize Changes
250
+ 8. Commit release
251
+ 9. Tag release
252
+ 10. `npm publish` from `.build/current` (unless airplane mode)
253
+ 11. Restore `{{ NEXT }}`
254
+ 12. Bump version (optional)
255
+ 13. Commit post-release files
256
+
257
+
258
+ You push manually.
259
+
260
+ Airplane mode disables network actions.
261
+
262
+ ## Status
263
+
264
+ Early-stage but functional.
265
+
266
+ ## Developer notes
267
+
268
+ ### Semver
269
+
270
+ This package does not adhere to semver. It's just a version number.
271
+
272
+ ### Lock files
273
+
274
+ Ignored. Keep your deps minimal and I'll promise to not break your code.
275
+
276
+ ### Code of Conduct
277
+
278
+ Be human.
279
+
@@ -0,0 +1,53 @@
1
+ // SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
2
+ //
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import crypto from "node:crypto";
8
+
9
+ async function mkdirp(p) {
10
+ await fs.mkdir(p, { recursive: true });
11
+ }
12
+
13
+ async function rmForce(p) {
14
+ await fs.rm(p, { recursive: true, force: true });
15
+ }
16
+
17
+ function randId() {
18
+ // short, readable, collision-resistant enough for builds
19
+ return crypto.randomBytes(8).toString("hex");
20
+ }
21
+
22
+ /**
23
+ * Creates .build/<id>/ and updates .build/current -> <id>
24
+ * Returns { buildRoot, buildDir, currentPath }
25
+ */
26
+ export async function createBuildDir({ buildRoot = ".build" } = {}) {
27
+ const id = randId();
28
+ const buildDir = path.join(buildRoot, id);
29
+ const currentPath = path.join(buildRoot, "current");
30
+
31
+ await mkdirp(buildDir);
32
+
33
+ // Replace current pointer atomically-ish:
34
+ // Create a temp symlink then rename it into place.
35
+ const tmpLink = path.join(buildRoot, `.current-tmp-${id}`);
36
+
37
+ // Ensure buildRoot exists
38
+ await mkdirp(buildRoot);
39
+
40
+ // Remove any stale temp link
41
+ await rmForce(tmpLink);
42
+
43
+ // Create symlink pointing to the *id directory*, relative for readability
44
+ // current -> <id>
45
+ await fs.symlink(id, tmpLink, "dir");
46
+
47
+ // Remove old current, then move tmp into place
48
+ await rmForce(currentPath);
49
+ await fs.rename(tmpLink, currentPath);
50
+
51
+ return { buildRoot, buildDir, currentPath, id };
52
+ }
53
+
@@ -0,0 +1,85 @@
1
+ // SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
2
+ //
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ import fs from "node:fs/promises";
6
+
7
+ export async function readChanges(path) {
8
+ return fs.readFile(path, "utf8");
9
+ }
10
+
11
+ export async function writeChanges(path, text) {
12
+ await fs.writeFile(path, text, "utf8");
13
+ }
14
+
15
+ function formatUtcStamp(date = new Date()) {
16
+ const pad = (n) => String(n).padStart(2, "0");
17
+ const y = date.getUTCFullYear();
18
+ const m = pad(date.getUTCMonth() + 1);
19
+ const d = pad(date.getUTCDate());
20
+ const hh = pad(date.getUTCHours());
21
+ const mm = pad(date.getUTCMinutes());
22
+ const ss = pad(date.getUTCSeconds());
23
+ return `${y}-${m}-${d} ${hh}:${mm}:${ss}Z`;
24
+ }
25
+
26
+ export function extractNextSection(changesText) {
27
+ const marker = "{{ NEXT }}";
28
+ const idx = changesText.indexOf(marker);
29
+ if (idx === -1) throw new Error(`Could not find ${marker} in Changes`);
30
+
31
+ const after = changesText.slice(idx + marker.length);
32
+
33
+ // End NEXT section at the first blank line before a version header like:
34
+ // 0.0.8 2024-06-04 ...
35
+ const match = after.match(/\n\s*\n(?=\d+\.\d+\.\d+\s+)/);
36
+ const endRel = match ? match.index + match[0].length : after.length;
37
+
38
+ const nextBody = after.slice(0, endRel);
39
+ return { nextBody };
40
+ }
41
+
42
+ export function nextHasEntries(changesText) {
43
+ const { nextBody } = extractNextSection(changesText);
44
+ const lines = nextBody.split("\n").map((l) => l.trim());
45
+ return lines.some((l) => l.startsWith("* ") || l.startsWith("- "));
46
+ }
47
+
48
+ export function finalizeNextToVersion(changesText, version) {
49
+ const marker = "{{ NEXT }}";
50
+ const stamp = formatUtcStamp();
51
+ const replacement = `${version} ${stamp}`;
52
+
53
+ if (!changesText.includes(marker)) {
54
+ throw new Error(`Could not find ${marker} in Changes`);
55
+ }
56
+
57
+ // Replace only the first occurrence
58
+ return changesText.replace(marker, replacement);
59
+ }
60
+
61
+ export function ensureNextInserted(changesText) {
62
+ const marker = "{{ NEXT }}";
63
+ if (changesText.includes(marker)) return changesText;
64
+
65
+ const lines = changesText.split("\n");
66
+
67
+ // Insert after the first blank line (usually after "Revision history for ...")
68
+ let insertAt = 1;
69
+ for (let i = 0; i < Math.min(lines.length, 20); i++) {
70
+ if (lines[i].trim() === "" && i > 0) {
71
+ insertAt = i + 1;
72
+ break;
73
+ }
74
+ }
75
+
76
+ const block = [
77
+ "{{ NEXT }}",
78
+ "",
79
+ " * ",
80
+ "",
81
+ ];
82
+
83
+ lines.splice(insertAt, 0, ...block);
84
+ return lines.join("\n");
85
+ }
@@ -0,0 +1,71 @@
1
+ // SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
2
+ //
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ import { readDistToml } from "../config.mjs";
9
+ import { asArray } from "@opndev/util";
10
+
11
+ import { createBuildDir } from "../builddir.mjs";
12
+ import { populateBuildDir } from "../populate.mjs";
13
+ import { runPkg } from "./pkg.mjs";
14
+
15
+ async function writeBuildLicense({ buildDir, license }) {
16
+ // dist.toml:
17
+ // [license]
18
+ // spdx = "MIT"
19
+ // file = "LICENSES/MIT.txt"
20
+ if (!license?.file) return;
21
+
22
+ const text = await fs.readFile(license.file, "utf8");
23
+ await fs.writeFile(path.join(buildDir, "LICENSE"), text, "utf8");
24
+ }
25
+
26
+ async function copyAfterBuild({ buildDir, copyList }) {
27
+ for (const rel of copyList) {
28
+ const src = path.join(buildDir, rel);
29
+ const dst = path.join(process.cwd(), rel);
30
+ await fs.mkdir(path.dirname(dst), { recursive: true });
31
+ await fs.copyFile(src, dst);
32
+ }
33
+ }
34
+
35
+ export async function runBuild() {
36
+ const { cfg } = await readDistToml("dist.toml");
37
+
38
+ // 1) Create build dir + .build/current
39
+ const { buildDir, currentPath } = await createBuildDir({ buildRoot: ".build" });
40
+
41
+ // 2) Populate build dir with publishable files only
42
+ {
43
+ const g = cfg.gather ?? {};
44
+ const files = asArray(g.files ?? "lib/**/*.{mjs,cjs,js}");
45
+ const include = asArray(g.include ?? []);
46
+ const bin = asArray(g.bin ?? []);
47
+ await populateBuildDir({
48
+ buildDir,
49
+ files,
50
+ include,
51
+ binDirs: bin, // NOTE: your populateBuildDir should treat these as globs if that's your gather model
52
+ });
53
+ }
54
+
55
+ // 3) Generate package.json into build dir (emitFiles=false because build dir is already pruned)
56
+ await runPkg({ outDir: buildDir, emitFiles: false });
57
+
58
+ // 4) Materialize LICENSE into build dir
59
+ await writeBuildLicense({ buildDir, license: cfg.license });
60
+
61
+ // 5) build.after.copy back into repo root (optional)
62
+ {
63
+ const copyList = asArray(cfg.build?.after?.copy ?? []);
64
+ if (copyList.length) {
65
+ await copyAfterBuild({ buildDir, copyList });
66
+ }
67
+ }
68
+
69
+ console.log(`Built distribution in ${currentPath}`);
70
+ }
71
+
@@ -0,0 +1,18 @@
1
+ // SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
2
+ //
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ export async function runClean() {
9
+ const buildDir = path.join(process.cwd(), ".build");
10
+
11
+ try {
12
+ await fs.rm(buildDir, { recursive: true, force: true });
13
+ console.log("Removed .build directory.");
14
+ } catch (err) {
15
+ throw new Error(`Failed to clean .build: ${err.message}`);
16
+ }
17
+ }
18
+
@@ -0,0 +1,114 @@
1
+ // SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>
2
+ //
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ import fs from "node:fs/promises";
6
+ import { readDistToml } from "../config.mjs";
7
+ import { uniq } from "@opndev/util";
8
+ import { deriveRepoAndBugs } from "../plugins/repository.mjs";
9
+ import { applyPrereqs } from "../plugins/prereqs.mjs";
10
+ import { buildExports } from "../plugins/exports.mjs";
11
+ import { gatherConfig, discoverBins, buildFilesList } from "../plugins/gather.mjs";
12
+ import { applyAutoPrereqs } from "../plugins/autoprereqs.mjs";
13
+ import { readVersion } from "../version.mjs";
14
+
15
+
16
+ async function basePkgFromCfg(cfg) {
17
+ const version = await readVersion(cfg);
18
+ const pkg = {
19
+ name: cfg.name,
20
+ version: version,
21
+ description: cfg.description,
22
+ type: cfg.type ?? "module",
23
+ keywords: cfg.keywords,
24
+ };
25
+
26
+
27
+ pkg.sideEffects = cfg.sideEffects ?? false;
28
+
29
+ if (typeof pkg.sideEffects !== "boolean")
30
+ throw new Error(`sideEffects must be boolean (true/false)`);
31
+
32
+ if (cfg.private != null && typeof cfg.private !== "boolean") {
33
+ throw new Error(`private must be boolean (true/false), got ${typeof cfg.private}`);
34
+ }
35
+
36
+ if (cfg.author) pkg.author = cfg.author;
37
+
38
+ if (cfg.license?.spdx) {
39
+ pkg.license = cfg.license.spdx;
40
+ if (cfg.private == null) cfg.private = false;
41
+ }
42
+ else {
43
+ pkg.license = "UNLICENSED";
44
+ if (cfg.private == null) cfg.private = true;
45
+ }
46
+
47
+ if (cfg.private) pkg.private = cfg.private;
48
+
49
+ if (cfg.scripts) pkg.scripts = cfg.scripts;
50
+
51
+ return pkg;
52
+ }
53
+
54
+ function sortKeysDeep(obj) {
55
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return obj;
56
+ const out = {};
57
+ for (const k of Object.keys(obj).sort()) out[k] = sortKeysDeep(obj[k]);
58
+ return out;
59
+ }
60
+
61
+ export async function runPkg({ outDir = ".", emitFiles = true, msg = true } = {}) {
62
+ const { cfg } = await readDistToml("dist.toml");
63
+
64
+ const pkg = await basePkgFromCfg(cfg);
65
+
66
+ const g = gatherConfig(cfg);
67
+ const { bin } = await discoverBins(g.binGlob);
68
+ if (Object.keys(bin).length) pkg.bin = bin;
69
+
70
+ if (emitFiles) {
71
+ const files = buildFilesList({
72
+ files: g.files,
73
+ include: g.include,
74
+ binGlob: g.binGlob,
75
+ });
76
+ if (files.length) pkg.files = files;
77
+ }
78
+
79
+ const ex = buildExports(cfg);
80
+ if (ex) pkg.exports = ex;
81
+
82
+ applyPrereqs(cfg, pkg);
83
+
84
+ const ap = cfg.autoprereqs;
85
+ if (ap && ap.enabled !== false) {
86
+ await applyAutoPrereqs(cfg, pkg);
87
+ }
88
+
89
+ if (cfg.repository) {
90
+ const info = await deriveRepoAndBugs({
91
+ repository: cfg.repository,
92
+ bugtracker: cfg.bugtracker ?? null,
93
+ });
94
+
95
+ pkg.repository = { type: cfg.repository.type ?? "git", url: `git+${info.remoteUrl ?? cfg.repository.url}` };
96
+ if (info.web) pkg.homepage = info.web;
97
+
98
+ if (cfg.bugtracker && info.bugsUrl) pkg.bugs = { url: info.bugsUrl };
99
+ }
100
+
101
+ // Fall back to cfg.bugtracker.url where needed
102
+ if (cfg.bugtracker && cfg.bugtracker.url) pkg.bugs = { url: cfg.bugtracker.url };
103
+
104
+ // cleanup empties
105
+ if (pkg.keywords && Array.isArray(pkg.keywords)) pkg.keywords = uniq(pkg.keywords);
106
+
107
+ const finalPkg = sortKeysDeep(pkg);
108
+
109
+ const outPath = `${outDir}/package.json`;
110
+ await fs.writeFile(outPath, JSON.stringify(finalPkg, null, 2) + "\n", "utf8");
111
+
112
+ if (msg) console.log("Generated package.json from dist.toml");
113
+ }
114
+
@@ -0,0 +1,47 @@
1
+ import { readDistToml } from "../config.mjs";
2
+ import { applyPrereqs } from "../plugins/prereqs.mjs";
3
+ import { applyAutoPrereqs } from "../plugins/autoprereqs.mjs";
4
+
5
+ function sortedEntries(obj) {
6
+ return Object.entries(obj ?? {}).sort(([a], [b]) => a.localeCompare(b));
7
+ }
8
+
9
+ function printSection(title, obj) {
10
+ const entries = sortedEntries(obj);
11
+ if (!entries.length) return;
12
+ console.log(`${title}:`);
13
+ for (const [k, v] of entries) console.log(` ${k} ${v}`);
14
+ console.log("");
15
+ }
16
+
17
+ export async function runPrereqs(opts = {}) {
18
+ const { cfg } = await readDistToml("dist.toml");
19
+
20
+ // Build a pkg-like object using the same merge logic as pkg generation.
21
+ const pkg = {};
22
+ applyPrereqs(cfg, pkg);
23
+
24
+ // autoprereqs is opt-in by table presence, and enabled unless enabled=false
25
+ const ap = cfg.autoprereqs;
26
+ if (ap && ap.enabled !== false) {
27
+ await applyAutoPrereqs(cfg, pkg);
28
+ }
29
+
30
+ const out = {
31
+ engines: pkg.engines ?? {},
32
+ dependencies: pkg.dependencies ?? {},
33
+ devDependencies: pkg.devDependencies ?? {},
34
+ peerDependencies: pkg.peerDependencies ?? {},
35
+ };
36
+
37
+ if (opts.json) {
38
+ console.log(JSON.stringify(out, null, 2));
39
+ return;
40
+ }
41
+
42
+ printSection("Engines", out.engines);
43
+ printSection("Runtime dependencies", out.dependencies);
44
+ printSection("Dev dependencies", out.devDependencies);
45
+ printSection("Peer dependencies", out.peerDependencies);
46
+ }
47
+