@drops-it/dpm 1.0.5 → 1.1.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/README.md +24 -17
- package/dist/config.js +10 -4
- package/dist/index.js +237 -55
- package/dist/install.d.ts +12 -7
- package/dist/install.js +205 -234
- package/dist/runtime.d.ts +37 -0
- package/dist/runtime.js +234 -0
- package/package.json +1 -4
package/dist/install.js
CHANGED
|
@@ -1,63 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
-
import { homedir, tmpdir } from "node:os";
|
|
1
|
+
import { access, readdir, readFile, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
5
3
|
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
4
|
import * as tar from "tar";
|
|
8
|
-
import semver from "semver";
|
|
9
5
|
import { auditPackages, formatAuditReport, proposeFixes, } from "./audit.js";
|
|
10
6
|
import { c } from "./colors.js";
|
|
11
|
-
import { loadLockfile, readPackageJson, saveLockfile, writePackageJson, } from "./config.js";
|
|
7
|
+
import { loadConfig, loadLockfile, readPackageJson, saveLockfile, writePackageJson, } from "./config.js";
|
|
8
|
+
import { ensureInstallConfig, runtimeInstallEnv, spawnRuntime, } from "./runtime.js";
|
|
12
9
|
import { createUi } from "./ui.js";
|
|
13
|
-
const execFileAsync = promisify(execFile);
|
|
14
|
-
const execAsync = promisify(exec);
|
|
15
|
-
function quoteNpmArg(value) {
|
|
16
|
-
if (!/[ \t"&<>|^]/.test(value))
|
|
17
|
-
return value;
|
|
18
|
-
return `"${value.replace(/"/g, '\\"')}"`;
|
|
19
|
-
}
|
|
20
|
-
async function runNpm(args) {
|
|
21
|
-
if (process.platform === "win32") {
|
|
22
|
-
const cmdline = ["npm", ...args.map(quoteNpmArg)].join(" ");
|
|
23
|
-
return execAsync(cmdline, {
|
|
24
|
-
windowsHide: true,
|
|
25
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return execFileAsync("npm", args, {
|
|
29
|
-
windowsHide: true,
|
|
30
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
10
|
function asUi(log) {
|
|
34
11
|
if ("status" in log && typeof log.status === "function" && "clearStatus" in log) {
|
|
35
12
|
return log;
|
|
36
13
|
}
|
|
37
14
|
return createUi();
|
|
38
15
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
16
|
+
function setStatus(log, msg) {
|
|
17
|
+
if (log.status)
|
|
18
|
+
log.status(msg);
|
|
19
|
+
else
|
|
20
|
+
log.info(msg);
|
|
41
21
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await writeFile(tmp, tarball);
|
|
48
|
-
await tar.x({ file: tmp, cwd: target, strip: 1 });
|
|
49
|
-
await rm(tmp, { force: true });
|
|
22
|
+
function topLevelDeps(pkg) {
|
|
23
|
+
return {
|
|
24
|
+
...(pkg.dependencies || {}),
|
|
25
|
+
...(pkg.devDependencies || {}),
|
|
26
|
+
};
|
|
50
27
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
function readOverrides(pkg) {
|
|
29
|
+
const raw = pkg.overrides || {};
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
32
|
+
if (typeof value === "string")
|
|
33
|
+
out[name] = value;
|
|
57
34
|
}
|
|
58
|
-
|
|
59
|
-
return versions.includes(range) ? range : null;
|
|
60
|
-
return semver.maxSatisfying(versions, range);
|
|
35
|
+
return out;
|
|
61
36
|
}
|
|
62
37
|
/** Parse `name`, `name@1.2.3`, `name@^1`, `@scope/name`, `@scope/name@1.0.0`. */
|
|
63
38
|
export function parsePackageSpec(spec) {
|
|
@@ -89,196 +64,179 @@ async function ensurePackageJson(cwd, log) {
|
|
|
89
64
|
return pkg;
|
|
90
65
|
}
|
|
91
66
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return `^${latest}`;
|
|
67
|
+
/**
|
|
68
|
+
* Builds a DPM lock snapshot from installed node_modules for audit tooling.
|
|
69
|
+
*/
|
|
70
|
+
export async function syncLockFromNodeModules(cwd) {
|
|
71
|
+
const lock = { lockfileVersion: 1, packages: {} };
|
|
72
|
+
const root = path.join(cwd, "node_modules");
|
|
73
|
+
try {
|
|
74
|
+
await access(root);
|
|
101
75
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
function setStatus(log, msg) {
|
|
107
|
-
if (log.status)
|
|
108
|
-
log.status(msg);
|
|
109
|
-
else
|
|
110
|
-
log.info(msg);
|
|
111
|
-
}
|
|
112
|
-
function topLevelDeps(pkg) {
|
|
113
|
-
return {
|
|
114
|
-
...(pkg.dependencies || {}),
|
|
115
|
-
...(pkg.devDependencies || {}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function readOverrides(pkg) {
|
|
119
|
-
const raw = pkg.overrides || {};
|
|
120
|
-
const out = {};
|
|
121
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
122
|
-
if (typeof value === "string")
|
|
123
|
-
out[name] = value;
|
|
76
|
+
catch {
|
|
77
|
+
await saveLockfile(lock, cwd);
|
|
78
|
+
return lock;
|
|
124
79
|
}
|
|
125
|
-
|
|
80
|
+
async function visit(dir, scope) {
|
|
81
|
+
let entries;
|
|
82
|
+
try {
|
|
83
|
+
entries = await readdir(dir);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (entry === ".bin" || entry.startsWith("."))
|
|
90
|
+
continue;
|
|
91
|
+
const full = path.join(dir, entry);
|
|
92
|
+
let st;
|
|
93
|
+
try {
|
|
94
|
+
st = await stat(full);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!st.isDirectory())
|
|
100
|
+
continue;
|
|
101
|
+
if (entry.startsWith("@") && !scope) {
|
|
102
|
+
await visit(full, entry);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const name = scope ? `${scope}/${entry}` : entry;
|
|
106
|
+
try {
|
|
107
|
+
const raw = await readFile(path.join(full, "package.json"), "utf8");
|
|
108
|
+
const meta = JSON.parse(raw);
|
|
109
|
+
lock.packages[name] = {
|
|
110
|
+
version: meta.version || "0.0.0",
|
|
111
|
+
resolved: name,
|
|
112
|
+
integrity: "",
|
|
113
|
+
source: "dpm",
|
|
114
|
+
dependencies: meta.dependencies,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* skip broken packages */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
await visit(root);
|
|
123
|
+
await saveLockfile(lock, cwd);
|
|
124
|
+
return lock;
|
|
125
|
+
}
|
|
126
|
+
async function prepareRuntimeInstall(cwd) {
|
|
127
|
+
const config = await loadConfig();
|
|
128
|
+
await ensureInstallConfig(config, cwd);
|
|
129
|
+
return { config, env: runtimeInstallEnv(config) };
|
|
126
130
|
}
|
|
127
|
-
|
|
131
|
+
/** Installs package.json dependencies via the managed DPM runtime. */
|
|
132
|
+
export async function installAll(_api, cwd = process.cwd(), log = createUi(), options = {}) {
|
|
128
133
|
const ui = asUi(log);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return { lockfileVersion: 1, packages: {} };
|
|
136
|
-
}
|
|
137
|
-
ui.info(`${c.cyan("→")} resolving ${c.bold(String(entries.length))} top-level dependenc${entries.length === 1 ? "y" : "ies"}`);
|
|
138
|
-
const lock = { lockfileVersion: 1, packages: {} };
|
|
139
|
-
const queue = [...entries];
|
|
140
|
-
let installed = 0;
|
|
141
|
-
const started = Date.now();
|
|
142
|
-
const preferred = {
|
|
143
|
-
...readOverrides(pkg),
|
|
144
|
-
...(options.preferredVersions || {}),
|
|
145
|
-
};
|
|
146
|
-
while (queue.length) {
|
|
147
|
-
const [name, range] = queue.pop();
|
|
148
|
-
if (lock.packages[name])
|
|
149
|
-
continue;
|
|
150
|
-
const progress = () => {
|
|
151
|
-
const pending = queue.length + 1;
|
|
152
|
-
return `${c.dim(`${installed} done · ${pending} left`)}`;
|
|
134
|
+
await ensurePackageJson(cwd, ui);
|
|
135
|
+
if (options.preferredVersions && Object.keys(options.preferredVersions).length > 0) {
|
|
136
|
+
const pkg = await readPackageJson(cwd);
|
|
137
|
+
const overrides = {
|
|
138
|
+
...readOverrides(pkg),
|
|
139
|
+
...options.preferredVersions,
|
|
153
140
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!meta)
|
|
157
|
-
throw new Error(`Package not found: ${name}`);
|
|
158
|
-
const versions = meta.versions.map((v) => v.version);
|
|
159
|
-
const version = pickVersion(versions, range, meta["dist-tags"] || {}, preferred[name]);
|
|
160
|
-
if (!version)
|
|
161
|
-
throw new Error(`No version of ${name} matches ${range}`);
|
|
162
|
-
const ver = meta.versions.find((v) => v.version === version);
|
|
163
|
-
const source = meta.source || "dpm";
|
|
164
|
-
const sourceTag = source === "npm" ? c.magenta("[npm]") : c.blue("[dpm]");
|
|
165
|
-
setStatus(ui, `download ${c.bold(name)}@${c.green(version)} ${sourceTag} ${progress()}`);
|
|
166
|
-
const tarball = await api.downloadTarball(ver.dist.tarball);
|
|
167
|
-
const integrity = ver.dist.integrity || integrityOf(tarball);
|
|
168
|
-
setStatus(ui, `extract ${c.bold(name)}@${c.green(version)} ${progress()}`);
|
|
169
|
-
await extractToNodeModules(name, tarball, cwd);
|
|
170
|
-
installed += 1;
|
|
171
|
-
lock.packages[name] = {
|
|
172
|
-
version,
|
|
173
|
-
resolved: ver.dist.tarball,
|
|
174
|
-
integrity,
|
|
175
|
-
source,
|
|
176
|
-
dependencies: ver.dependencies,
|
|
177
|
-
};
|
|
178
|
-
for (const [dep, depRange] of Object.entries(ver.dependencies || {})) {
|
|
179
|
-
if (!lock.packages[dep])
|
|
180
|
-
queue.push([dep, depRange]);
|
|
181
|
-
}
|
|
141
|
+
pkg.overrides = overrides;
|
|
142
|
+
await writePackageJson(pkg, cwd);
|
|
182
143
|
}
|
|
144
|
+
const { env } = await prepareRuntimeInstall(cwd);
|
|
145
|
+
setStatus(ui, "installing dependencies…");
|
|
146
|
+
const started = Date.now();
|
|
147
|
+
const code = await spawnRuntime({
|
|
148
|
+
cwd,
|
|
149
|
+
args: ["install"],
|
|
150
|
+
env,
|
|
151
|
+
brandOutput: true,
|
|
152
|
+
});
|
|
183
153
|
ui.clearStatus();
|
|
184
|
-
|
|
154
|
+
if (code !== 0) {
|
|
155
|
+
throw new Error(`install failed (exit ${code})`);
|
|
156
|
+
}
|
|
157
|
+
const lock = await syncLockFromNodeModules(cwd);
|
|
185
158
|
const elapsed = ((Date.now() - started) / 1000).toFixed(1);
|
|
159
|
+
const count = Object.keys(lock.packages).length;
|
|
186
160
|
if (ui.success) {
|
|
187
|
-
ui.success(`added ${c.bold(String(
|
|
161
|
+
ui.success(`added ${c.bold(String(count))} package${count === 1 ? "" : "s"} in ${elapsed}s`);
|
|
188
162
|
}
|
|
189
163
|
else {
|
|
190
|
-
ui.info(`added ${
|
|
164
|
+
ui.info(`added ${count} package${count === 1 ? "" : "s"} in ${elapsed}s`);
|
|
191
165
|
}
|
|
192
166
|
if (!options.skipAudit) {
|
|
193
167
|
setStatus(ui, "checking vulnerabilities…");
|
|
194
168
|
const hits = await auditPackages(lock.packages);
|
|
169
|
+
const pkg = await readPackageJson(cwd);
|
|
170
|
+
const deps = topLevelDeps(pkg);
|
|
195
171
|
const fixes = hits.length
|
|
196
|
-
? await proposeFixes(
|
|
172
|
+
? await proposeFixes(_api, lock, hits, deps, true)
|
|
197
173
|
: [];
|
|
198
174
|
ui.clearStatus();
|
|
199
175
|
ui.info(formatAuditReport(hits, fixes));
|
|
200
176
|
}
|
|
201
177
|
return lock;
|
|
202
178
|
}
|
|
179
|
+
/** Adds packages then installs via the managed DPM runtime. */
|
|
203
180
|
export async function addPackages(api, specs, cwd = process.cwd(), log = createUi()) {
|
|
204
181
|
if (specs.length === 0)
|
|
205
182
|
return installAll(api, cwd, log);
|
|
206
183
|
const ui = asUi(log);
|
|
207
|
-
|
|
208
|
-
const
|
|
184
|
+
await ensurePackageJson(cwd, ui);
|
|
185
|
+
const { env } = await prepareRuntimeInstall(cwd);
|
|
186
|
+
setStatus(ui, `add ${specs.join(", ")}`);
|
|
187
|
+
const code = await spawnRuntime({
|
|
188
|
+
cwd,
|
|
189
|
+
args: ["add", ...specs],
|
|
190
|
+
env,
|
|
191
|
+
brandOutput: true,
|
|
192
|
+
});
|
|
193
|
+
ui.clearStatus();
|
|
194
|
+
if (code !== 0)
|
|
195
|
+
throw new Error(`add failed (exit ${code})`);
|
|
209
196
|
for (const spec of specs) {
|
|
210
|
-
const { name
|
|
211
|
-
|
|
212
|
-
const savedRange = await resolveRange(api, name, range);
|
|
213
|
-
deps[name] = savedRange;
|
|
214
|
-
ui.info(`${c.green("+")} ${c.bold(name)}@${c.cyan(savedRange)}`);
|
|
197
|
+
const { name } = parsePackageSpec(spec);
|
|
198
|
+
ui.info(`${c.green("+")} ${c.bold(name)}`);
|
|
215
199
|
}
|
|
216
|
-
|
|
217
|
-
await writePackageJson(pkg, cwd);
|
|
218
|
-
return installAll(api, cwd, ui);
|
|
200
|
+
return syncLockFromNodeModules(cwd);
|
|
219
201
|
}
|
|
220
202
|
/**
|
|
221
|
-
* Installs packages into the
|
|
222
|
-
* Downloads through the DPM registry so installs count as DPM downloads.
|
|
203
|
+
* Installs packages into the global prefix via the managed DPM runtime.
|
|
223
204
|
* Used for CLI self-updates: `dpm i -g dpm` / `dpm i -g @drops-it/dpm`.
|
|
224
205
|
*/
|
|
225
|
-
export async function installGlobalPackages(
|
|
206
|
+
export async function installGlobalPackages(_api, specs, log = createUi()) {
|
|
226
207
|
if (specs.length === 0) {
|
|
227
208
|
throw new Error("global install requires at least one package name");
|
|
228
209
|
}
|
|
229
210
|
const ui = asUi(log);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
await writeFile(tmp, tarball);
|
|
252
|
-
try {
|
|
253
|
-
setStatus(ui, `npm install -g ${c.bold(name)}@${c.green(version)}`);
|
|
254
|
-
await runNpm(["install", "-g", tmp]);
|
|
255
|
-
}
|
|
256
|
-
finally {
|
|
257
|
-
await rm(tmp, { force: true });
|
|
258
|
-
}
|
|
259
|
-
ui.clearStatus();
|
|
260
|
-
if (ui.success)
|
|
261
|
-
ui.success(`installed ${c.bold(name)}@${c.green(version)} globally`);
|
|
262
|
-
else
|
|
263
|
-
ui.info(`installed ${name}@${version} globally`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
async function npmGlobalPrefix() {
|
|
267
|
-
const { stdout } = await runNpm(["prefix", "-g"]);
|
|
268
|
-
return stdout.toString().trim();
|
|
211
|
+
const mapped = specs.map((spec) => {
|
|
212
|
+
const { name, range } = parsePackageSpec(spec);
|
|
213
|
+
const real = name === "@drops-it/dpm" ? "dpm" : name;
|
|
214
|
+
return range === "latest" ? real : `${real}@${range}`;
|
|
215
|
+
});
|
|
216
|
+
const config = await loadConfig();
|
|
217
|
+
await ensureInstallConfig(config, process.cwd());
|
|
218
|
+
const env = runtimeInstallEnv(config);
|
|
219
|
+
setStatus(ui, `install -g ${mapped.join(", ")}`);
|
|
220
|
+
const code = await spawnRuntime({
|
|
221
|
+
args: ["add", "-g", ...mapped],
|
|
222
|
+
env,
|
|
223
|
+
brandOutput: true,
|
|
224
|
+
});
|
|
225
|
+
ui.clearStatus();
|
|
226
|
+
if (code !== 0)
|
|
227
|
+
throw new Error(`global install failed (exit ${code})`);
|
|
228
|
+
if (ui.success)
|
|
229
|
+
ui.success(`installed ${mapped.join(", ")} globally`);
|
|
230
|
+
else
|
|
231
|
+
ui.info(`installed ${mapped.join(", ")} globally`);
|
|
269
232
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
await runNpm(["unlink", "-g", name]);
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
// Package may already be gone.
|
|
280
|
-
}
|
|
233
|
+
const CLI_SELF_NAMES = ["@drops-it/dpm", "dpm"];
|
|
234
|
+
async function runtimeGlobalPrefix() {
|
|
235
|
+
// Best-effort: common user global paths when removing the CLI.
|
|
236
|
+
if (process.platform === "win32") {
|
|
237
|
+
return path.join(homedir(), ".dpm", "global");
|
|
281
238
|
}
|
|
239
|
+
return path.join(homedir(), ".dpm", "global");
|
|
282
240
|
}
|
|
283
241
|
/** Removes leftover Windows/Unix bin shims for a command name. */
|
|
284
242
|
async function removeGlobalBinShims(binName, prefix) {
|
|
@@ -286,13 +244,13 @@ async function removeGlobalBinShims(binName, prefix) {
|
|
|
286
244
|
path.join(prefix, binName),
|
|
287
245
|
path.join(prefix, `${binName}.cmd`),
|
|
288
246
|
path.join(prefix, `${binName}.ps1`),
|
|
247
|
+
path.join(prefix, "bin", binName),
|
|
289
248
|
path.join(prefix, "node_modules", ".bin", binName),
|
|
290
249
|
path.join(prefix, "node_modules", ".bin", `${binName}.cmd`),
|
|
291
250
|
path.join(prefix, "node_modules", ".bin", `${binName}.ps1`),
|
|
292
251
|
];
|
|
293
252
|
await Promise.all(candidates.map((p) => rm(p, { force: true })));
|
|
294
253
|
}
|
|
295
|
-
const CLI_SELF_NAMES = ["@drops-it/dpm", "dpm"];
|
|
296
254
|
/**
|
|
297
255
|
* Uninstalls global packages (and cleans dpm bin shims when removing the CLI).
|
|
298
256
|
* `dpm rm -g` with no names removes the dpm CLI itself.
|
|
@@ -300,31 +258,29 @@ const CLI_SELF_NAMES = ["@drops-it/dpm", "dpm"];
|
|
|
300
258
|
export async function removeGlobalPackages(specs, log = createUi()) {
|
|
301
259
|
const ui = asUi(log);
|
|
302
260
|
const targets = specs.length > 0
|
|
303
|
-
? specs.map((s) => parsePackageSpec(s).name)
|
|
261
|
+
? specs.map((s) => parsePackageSpec(s).name).map((n) => (n === "@drops-it/dpm" ? "dpm" : n))
|
|
304
262
|
: [...CLI_SELF_NAMES];
|
|
305
|
-
const
|
|
306
|
-
const
|
|
263
|
+
const config = await loadConfig();
|
|
264
|
+
const env = runtimeInstallEnv(config);
|
|
265
|
+
const removingSelf = targets.some((n) => CLI_SELF_NAMES.includes(n) || n === "dpm");
|
|
307
266
|
for (const name of targets) {
|
|
308
267
|
setStatus(ui, `uninstall -g ${c.bold(name)}`);
|
|
309
|
-
await
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await tryNpmUninstallGlobal("@drops-it/dpm");
|
|
268
|
+
await spawnRuntime({
|
|
269
|
+
args: ["remove", "-g", name],
|
|
270
|
+
env,
|
|
271
|
+
brandOutput: true,
|
|
272
|
+
});
|
|
315
273
|
}
|
|
316
274
|
if (removingSelf) {
|
|
275
|
+
const prefix = await runtimeGlobalPrefix();
|
|
317
276
|
await removeGlobalBinShims("dpm", prefix);
|
|
318
|
-
// Also clear a linked/local leftover under the user npm folder on Windows.
|
|
319
277
|
const roaming = path.join(homedir(), "AppData", "Roaming", "npm");
|
|
320
|
-
|
|
321
|
-
await removeGlobalBinShims("dpm", roaming);
|
|
322
|
-
}
|
|
278
|
+
await removeGlobalBinShims("dpm", roaming);
|
|
323
279
|
await rm(path.join(prefix, "node_modules", "dpm"), { recursive: true, force: true });
|
|
324
|
-
await rm(path.join(
|
|
280
|
+
await rm(path.join(homedir(), ".bun", "install", "global", "node_modules", "dpm"), {
|
|
325
281
|
recursive: true,
|
|
326
282
|
force: true,
|
|
327
|
-
});
|
|
283
|
+
}).catch(() => undefined);
|
|
328
284
|
}
|
|
329
285
|
ui.clearStatus();
|
|
330
286
|
if (ui.success) {
|
|
@@ -340,26 +296,21 @@ export async function addPackage(api, name, range = "latest", cwd = process.cwd(
|
|
|
340
296
|
const spec = range === "latest" ? name : `${name}@${range}`;
|
|
341
297
|
return addPackages(api, [spec], cwd, log);
|
|
342
298
|
}
|
|
299
|
+
/** Removes a local dependency via the managed DPM runtime. */
|
|
343
300
|
export async function removePackage(name, cwd = process.cwd(), log = createUi()) {
|
|
344
301
|
const ui = asUi(log);
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
delete dev[name];
|
|
353
|
-
pkg.dependencies = deps;
|
|
354
|
-
pkg.devDependencies = dev;
|
|
355
|
-
await writePackageJson(pkg, cwd);
|
|
356
|
-
await rm(path.join(cwd, "node_modules", ...name.split("/")), {
|
|
357
|
-
recursive: true,
|
|
358
|
-
force: true,
|
|
302
|
+
const { env } = await prepareRuntimeInstall(cwd);
|
|
303
|
+
setStatus(ui, `remove ${c.bold(name)}`);
|
|
304
|
+
const code = await spawnRuntime({
|
|
305
|
+
cwd,
|
|
306
|
+
args: ["remove", name],
|
|
307
|
+
env,
|
|
308
|
+
brandOutput: true,
|
|
359
309
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
310
|
+
ui.clearStatus();
|
|
311
|
+
if (code !== 0)
|
|
312
|
+
throw new Error(`remove failed (exit ${code})`);
|
|
313
|
+
await syncLockFromNodeModules(cwd);
|
|
363
314
|
if (ui.success)
|
|
364
315
|
ui.success(`removed ${c.bold(name)}`);
|
|
365
316
|
else
|
|
@@ -384,7 +335,7 @@ export async function runAudit(api, cwd = process.cwd(), log = createUi()) {
|
|
|
384
335
|
return { hits, fixes: fixes.filter((f) => !f.breaking) };
|
|
385
336
|
}
|
|
386
337
|
/**
|
|
387
|
-
* Applies available advisory fixes
|
|
338
|
+
* Applies available advisory fixes, then reinstalls via the DPM runtime.
|
|
388
339
|
*/
|
|
389
340
|
export async function auditFix(api, cwd = process.cwd(), log = createUi(), force = false) {
|
|
390
341
|
const ui = asUi(log);
|
|
@@ -418,7 +369,6 @@ export async function auditFix(api, cwd = process.cwd(), log = createUi(), force
|
|
|
418
369
|
devDependencies[fix.name] = `^${fix.to}`;
|
|
419
370
|
}
|
|
420
371
|
else {
|
|
421
|
-
// Persist transitive fixes so the next plain install keeps them.
|
|
422
372
|
overrides[fix.name] = fix.to;
|
|
423
373
|
}
|
|
424
374
|
}
|
|
@@ -433,8 +383,26 @@ export async function auditFix(api, cwd = process.cwd(), log = createUi(), force
|
|
|
433
383
|
});
|
|
434
384
|
return { fixed: applicable, lock: next };
|
|
435
385
|
}
|
|
386
|
+
async function readLocalReadme(cwd) {
|
|
387
|
+
for (const name of ["README.md", "readme.md", "README", "readme.txt", "README.txt"]) {
|
|
388
|
+
try {
|
|
389
|
+
const text = await readFile(path.join(cwd, name), "utf8");
|
|
390
|
+
if (text.trim())
|
|
391
|
+
return text;
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
/* try next */
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return "";
|
|
398
|
+
}
|
|
399
|
+
/** Packs the current package into a gzipped tarball for publish. */
|
|
436
400
|
export async function packTarball(cwd = process.cwd()) {
|
|
437
401
|
const manifest = await readPackageJson(cwd);
|
|
402
|
+
const readme = await readLocalReadme(cwd);
|
|
403
|
+
if (readme && !String(manifest.readme ?? "").trim()) {
|
|
404
|
+
manifest.readme = readme;
|
|
405
|
+
}
|
|
438
406
|
const out = path.join(cwd, `.dpm-pack-${Date.now()}.tgz`);
|
|
439
407
|
await tar.c({
|
|
440
408
|
gzip: true,
|
|
@@ -444,7 +412,10 @@ export async function packTarball(cwd = process.cwd()) {
|
|
|
444
412
|
filter: (p) => !p.includes("node_modules") &&
|
|
445
413
|
!p.startsWith(".git") &&
|
|
446
414
|
!p.endsWith(".tgz") &&
|
|
447
|
-
p !== "dpm-lock.yaml"
|
|
415
|
+
p !== "dpm-lock.yaml" &&
|
|
416
|
+
p !== "bun.lock" &&
|
|
417
|
+
p !== "bun.lockb" &&
|
|
418
|
+
p !== "bunfig.toml",
|
|
448
419
|
}, ["."]);
|
|
449
420
|
const buf = await readFile(out);
|
|
450
421
|
await rm(out, { force: true });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { DpmConfig } from "./config.js";
|
|
2
|
+
/** Pinned managed runtime version (oven-sh/bun release tag without prefix). */
|
|
3
|
+
export declare const RUNTIME_VERSION = "1.2.19";
|
|
4
|
+
export declare function managedRuntimePath(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Ensures the managed DPM runtime binary exists at a stable path.
|
|
7
|
+
* Downloads a pinned release from GitHub when missing or version-mismatched.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ensureRuntime(): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a runnable runtime path: managed binary, else PATH fallback branded as DPM.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveRuntime(): Promise<string>;
|
|
14
|
+
export type SpawnRuntimeOptions = {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
/** Extra args after the runtime binary (e.g. ["install"], ["run", "start"]). */
|
|
18
|
+
args: string[];
|
|
19
|
+
/** When true, rewrite stderr banners that say bun → dpm. */
|
|
20
|
+
brandOutput?: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Spawns the managed runtime with inherited stdio and returns the exit code.
|
|
24
|
+
*/
|
|
25
|
+
export declare function spawnRuntime(opts: SpawnRuntimeOptions): Promise<number>;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the install registry URL.
|
|
28
|
+
* DPM hosts use the `/npm` facade; plain npmjs (or DPM_BOOTSTRAP=1) uses the URL as-is.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveInstallRegistryUrl(registry: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Writes project-local registry config so the runtime installer hits the DPM npm facade
|
|
33
|
+
* (or npmjs when bootstrapping / pointed at registry.npmjs.org).
|
|
34
|
+
*/
|
|
35
|
+
export declare function ensureInstallConfig(config: DpmConfig, cwd?: string): Promise<void>;
|
|
36
|
+
/** Builds npm_config_* env for a one-shot install without relying only on files. */
|
|
37
|
+
export declare function runtimeInstallEnv(config: DpmConfig): NodeJS.ProcessEnv;
|