@drops-it/dpm 1.0.6 → 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 +2 -6
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { access, chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { homedir, platform, tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
/** Pinned managed runtime version (oven-sh/bun release tag without prefix). */
|
|
7
|
+
export const RUNTIME_VERSION = "1.2.19";
|
|
8
|
+
const RUNTIME_NAME = process.platform === "win32" ? "dpm-runtime.exe" : "dpm-runtime";
|
|
9
|
+
function runtimeRoot() {
|
|
10
|
+
if (process.env.DPM_RUNTIME_DIR)
|
|
11
|
+
return process.env.DPM_RUNTIME_DIR;
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
const base = process.env.LOCALAPPDATA || path.join(homedir(), "AppData", "Local");
|
|
14
|
+
return path.join(base, "Programs", "DPM", "runtime");
|
|
15
|
+
}
|
|
16
|
+
return path.join(homedir(), ".dpm", "runtime");
|
|
17
|
+
}
|
|
18
|
+
export function managedRuntimePath() {
|
|
19
|
+
return path.join(runtimeRoot(), RUNTIME_NAME);
|
|
20
|
+
}
|
|
21
|
+
async function exists(p) {
|
|
22
|
+
try {
|
|
23
|
+
await access(p);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assetForPlatform() {
|
|
31
|
+
const os = platform();
|
|
32
|
+
const arch = process.arch;
|
|
33
|
+
if (os === "win32") {
|
|
34
|
+
return {
|
|
35
|
+
asset: arch === "arm64" ? "bun-windows-x64.zip" : "bun-windows-x64.zip",
|
|
36
|
+
isZip: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (os === "darwin") {
|
|
40
|
+
return {
|
|
41
|
+
asset: arch === "arm64" ? "bun-darwin-aarch64.zip" : "bun-darwin-x64.zip",
|
|
42
|
+
isZip: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// linux
|
|
46
|
+
if (arch === "arm64") {
|
|
47
|
+
return { asset: "bun-linux-aarch64.zip", isZip: true };
|
|
48
|
+
}
|
|
49
|
+
return { asset: "bun-linux-x64.zip", isZip: true };
|
|
50
|
+
}
|
|
51
|
+
function releaseUrl(asset) {
|
|
52
|
+
return `https://github.com/oven-sh/bun/releases/download/bun-v${RUNTIME_VERSION}/${asset}`;
|
|
53
|
+
}
|
|
54
|
+
async function downloadFile(url, dest) {
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
headers: { "user-agent": "dpm-runtime-installer" },
|
|
57
|
+
redirect: "follow",
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
throw new Error(`DPM runtime download failed (${res.status}).`);
|
|
61
|
+
}
|
|
62
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
63
|
+
await writeFile(dest, Buffer.from(await res.arrayBuffer()));
|
|
64
|
+
}
|
|
65
|
+
async function extractZip(zipPath, destDir) {
|
|
66
|
+
await mkdir(destDir, { recursive: true });
|
|
67
|
+
if (process.platform === "win32") {
|
|
68
|
+
const ps = `
|
|
69
|
+
$ErrorActionPreference = 'Stop'
|
|
70
|
+
Expand-Archive -LiteralPath '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force
|
|
71
|
+
`;
|
|
72
|
+
execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { windowsHide: true, stdio: "ignore" });
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
execFileSync("unzip", ["-o", zipPath, "-d", destDir], { stdio: "ignore" });
|
|
76
|
+
}
|
|
77
|
+
const walk = (dir) => {
|
|
78
|
+
for (const name of readdirSync(dir)) {
|
|
79
|
+
const full = path.join(dir, name);
|
|
80
|
+
const st = statSync(full);
|
|
81
|
+
if (st.isDirectory()) {
|
|
82
|
+
const found = walk(full);
|
|
83
|
+
if (found)
|
|
84
|
+
return found;
|
|
85
|
+
}
|
|
86
|
+
else if (/^bun(\.exe)?$/i.test(name)) {
|
|
87
|
+
return full;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
const binary = walk(destDir);
|
|
93
|
+
if (!binary)
|
|
94
|
+
throw new Error("DPM runtime archive did not contain a binary.");
|
|
95
|
+
return binary;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ensures the managed DPM runtime binary exists at a stable path.
|
|
99
|
+
* Downloads a pinned release from GitHub when missing or version-mismatched.
|
|
100
|
+
*/
|
|
101
|
+
export async function ensureRuntime() {
|
|
102
|
+
if (process.env.DPM_RUNTIME && (await exists(process.env.DPM_RUNTIME))) {
|
|
103
|
+
return process.env.DPM_RUNTIME;
|
|
104
|
+
}
|
|
105
|
+
const target = managedRuntimePath();
|
|
106
|
+
const versionFile = path.join(runtimeRoot(), "version");
|
|
107
|
+
if (await exists(target) && (await exists(versionFile))) {
|
|
108
|
+
const v = (await readFile(versionFile, "utf8")).trim();
|
|
109
|
+
if (v === RUNTIME_VERSION)
|
|
110
|
+
return target;
|
|
111
|
+
}
|
|
112
|
+
const { asset } = assetForPlatform();
|
|
113
|
+
const url = releaseUrl(asset);
|
|
114
|
+
const tmpDir = path.join(tmpdir(), `dpm-runtime-${Date.now()}`);
|
|
115
|
+
await mkdir(tmpDir, { recursive: true });
|
|
116
|
+
const zipPath = path.join(tmpDir, asset);
|
|
117
|
+
try {
|
|
118
|
+
process.stderr.write("Installing DPM runtime…\n");
|
|
119
|
+
await downloadFile(url, zipPath);
|
|
120
|
+
const extracted = await extractZip(zipPath, path.join(tmpDir, "out"));
|
|
121
|
+
await mkdir(runtimeRoot(), { recursive: true });
|
|
122
|
+
const tmpTarget = `${target}.tmp`;
|
|
123
|
+
await rm(tmpTarget, { force: true });
|
|
124
|
+
// copy via read/write for cross-device moves
|
|
125
|
+
const data = await readFile(extracted);
|
|
126
|
+
await writeFile(tmpTarget, data);
|
|
127
|
+
if (process.platform !== "win32")
|
|
128
|
+
await chmod(tmpTarget, 0o755);
|
|
129
|
+
await rm(target, { force: true });
|
|
130
|
+
await rename(tmpTarget, target);
|
|
131
|
+
await writeFile(versionFile, `${RUNTIME_VERSION}\n`);
|
|
132
|
+
return target;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolves a runnable runtime path: managed binary, else PATH fallback branded as DPM.
|
|
140
|
+
*/
|
|
141
|
+
export async function resolveRuntime() {
|
|
142
|
+
try {
|
|
143
|
+
return await ensureRuntime();
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const fallback = process.platform === "win32" ? "bun.exe" : "bun";
|
|
147
|
+
const which = spawnSync(process.platform === "win32" ? "where" : "which", [fallback.replace(/\.exe$/, "")], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
windowsHide: true,
|
|
150
|
+
});
|
|
151
|
+
if (which.status === 0 && which.stdout?.trim()) {
|
|
152
|
+
return which.stdout.trim().split(/\r?\n/)[0];
|
|
153
|
+
}
|
|
154
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
155
|
+
throw new Error(`DPM runtime is not available (${msg}). Re-run the DPM installer or set DPM_RUNTIME.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function brandStderr(chunk) {
|
|
159
|
+
return Buffer.from(chunk
|
|
160
|
+
.toString("utf8")
|
|
161
|
+
.replace(/\bBun\b/g, "DPM")
|
|
162
|
+
.replace(/\bbun\b/g, "dpm"));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Spawns the managed runtime with inherited stdio and returns the exit code.
|
|
166
|
+
*/
|
|
167
|
+
export async function spawnRuntime(opts) {
|
|
168
|
+
const bin = await resolveRuntime();
|
|
169
|
+
const env = {
|
|
170
|
+
...process.env,
|
|
171
|
+
...opts.env,
|
|
172
|
+
// Keep npm-compatible clients pointed at DPM when nested tools spawn.
|
|
173
|
+
npm_config_registry: opts.env?.npm_config_registry ?? process.env.npm_config_registry,
|
|
174
|
+
};
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const child = spawn(bin, opts.args, {
|
|
177
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
178
|
+
env,
|
|
179
|
+
stdio: opts.brandOutput ? ["inherit", "inherit", "pipe"] : "inherit",
|
|
180
|
+
windowsHide: true,
|
|
181
|
+
});
|
|
182
|
+
if (opts.brandOutput && child.stderr) {
|
|
183
|
+
child.stderr.on("data", (chunk) => {
|
|
184
|
+
process.stderr.write(brandStderr(chunk));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
child.on("error", reject);
|
|
188
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resolves the install registry URL.
|
|
193
|
+
* DPM hosts use the `/npm` facade; plain npmjs (or DPM_BOOTSTRAP=1) uses the URL as-is.
|
|
194
|
+
*/
|
|
195
|
+
export function resolveInstallRegistryUrl(registry) {
|
|
196
|
+
const base = registry.replace(/\/$/, "").replace(/\/npm$/i, "");
|
|
197
|
+
const bootstrap = process.env.DPM_BOOTSTRAP === "1" ||
|
|
198
|
+
/registry\.npmjs\.org/i.test(base) ||
|
|
199
|
+
process.env.DPM_USE_NPM_UPSTREAM === "1";
|
|
200
|
+
if (bootstrap)
|
|
201
|
+
return `${base}/`;
|
|
202
|
+
return `${base}/npm/`;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Writes project-local registry config so the runtime installer hits the DPM npm facade
|
|
206
|
+
* (or npmjs when bootstrapping / pointed at registry.npmjs.org).
|
|
207
|
+
*/
|
|
208
|
+
export async function ensureInstallConfig(config, cwd = process.cwd()) {
|
|
209
|
+
const registryUrl = resolveInstallRegistryUrl(config.registry);
|
|
210
|
+
const host = new URL(registryUrl).host;
|
|
211
|
+
const pathPrefix = registryUrl.includes("/npm") ? "/npm" : "";
|
|
212
|
+
const npmrcLines = [
|
|
213
|
+
`registry=${registryUrl}`,
|
|
214
|
+
`//${host}${pathPrefix}/:_authToken=${config.token ?? ""}`,
|
|
215
|
+
];
|
|
216
|
+
await writeFile(path.join(cwd, ".npmrc"), `${npmrcLines.join("\n")}\n`, { mode: 0o600 });
|
|
217
|
+
const bunfig = `[install]
|
|
218
|
+
registry = "${registryUrl}"
|
|
219
|
+
`;
|
|
220
|
+
await writeFile(path.join(cwd, "bunfig.toml"), bunfig);
|
|
221
|
+
}
|
|
222
|
+
/** Builds npm_config_* env for a one-shot install without relying only on files. */
|
|
223
|
+
export function runtimeInstallEnv(config) {
|
|
224
|
+
const registryUrl = resolveInstallRegistryUrl(config.registry);
|
|
225
|
+
return {
|
|
226
|
+
npm_config_registry: registryUrl,
|
|
227
|
+
...(config.token
|
|
228
|
+
? {
|
|
229
|
+
npm_config__authToken: config.token,
|
|
230
|
+
BUN_CONFIG_TOKEN: config.token,
|
|
231
|
+
}
|
|
232
|
+
: {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drops-it/dpm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Drops Package Manager CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,9 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"engines": {
|
|
13
|
-
"node": ">=22"
|
|
14
|
-
},
|
|
15
12
|
"scripts": {
|
|
16
13
|
"dev": "tsx src/index.ts",
|
|
17
14
|
"build": "tsc -p tsconfig.json",
|
|
@@ -26,8 +23,7 @@
|
|
|
26
23
|
"commander": "^12.1.0",
|
|
27
24
|
"semver": "^7.6.3",
|
|
28
25
|
"tar": "^7.4.3",
|
|
29
|
-
"yaml": "^2.7.0"
|
|
30
|
-
"magmastream": "^2.10.2"
|
|
26
|
+
"yaml": "^2.7.0"
|
|
31
27
|
},
|
|
32
28
|
"devDependencies": {
|
|
33
29
|
"@types/node": "^22.10.5",
|