monoship 1.8.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/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +597 -0
- package/dist/index.d.mts +310 -0
- package/dist/index.mjs +616 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +86 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import libnpmpack from "libnpmpack";
|
|
6
|
+
import { publish as publish$1 } from "libnpmpublish";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import semver from "semver";
|
|
11
|
+
import hapic, { hasOwnProperty, isClientError } from "hapic";
|
|
12
|
+
//#region src/core/error.ts
|
|
13
|
+
var BaseError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
statusCode;
|
|
16
|
+
constructor(message, options) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = options.code;
|
|
19
|
+
this.statusCode = options.statusCode;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/core/filesystem/memory.ts
|
|
24
|
+
var MemoryFileSystem = class {
|
|
25
|
+
files;
|
|
26
|
+
constructor(files = {}) {
|
|
27
|
+
this.files = new Map(Object.entries(files || {}).map(([k, v]) => [this.normalize(k), v]));
|
|
28
|
+
}
|
|
29
|
+
async readFile(filePath) {
|
|
30
|
+
const normalized = this.normalize(filePath);
|
|
31
|
+
const content = this.files.get(normalized);
|
|
32
|
+
if (content === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
|
|
33
|
+
return content;
|
|
34
|
+
}
|
|
35
|
+
async writeFile(filePath, content) {
|
|
36
|
+
this.files.set(this.normalize(filePath), content);
|
|
37
|
+
}
|
|
38
|
+
async glob(patterns, options = {}) {
|
|
39
|
+
const cwd = options.cwd ? this.normalize(options.cwd) : "";
|
|
40
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
41
|
+
const keys = Array.from(this.files.keys());
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
if (cwd && !key.startsWith(`${cwd}/`) && key !== cwd) continue;
|
|
44
|
+
const parts = (cwd ? key.slice(cwd.length + 1) : key).split("/");
|
|
45
|
+
for (const pattern of patterns) if (this.matchGlobPattern(parts, pattern)) {
|
|
46
|
+
const dir = parts.slice(0, pattern.split("/").length).join("/");
|
|
47
|
+
const absolute = cwd ? `${cwd}/${dir}` : dir;
|
|
48
|
+
dirs.add(absolute);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return Array.from(dirs);
|
|
52
|
+
}
|
|
53
|
+
getFile(filePath) {
|
|
54
|
+
return this.files.get(this.normalize(filePath));
|
|
55
|
+
}
|
|
56
|
+
normalize(filePath) {
|
|
57
|
+
return path.posix.normalize(filePath.replace(/\\/g, "/"));
|
|
58
|
+
}
|
|
59
|
+
matchGlobPattern(parts, pattern) {
|
|
60
|
+
const patternParts = pattern.split("/");
|
|
61
|
+
for (const [i, pp] of patternParts.entries()) {
|
|
62
|
+
if (i >= parts.length) return false;
|
|
63
|
+
if (pp === "*" || pp === "**") continue;
|
|
64
|
+
if (pp !== parts[i]) return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/core/filesystem/node.ts
|
|
71
|
+
var NodeFileSystem = class {
|
|
72
|
+
async readFile(filePath) {
|
|
73
|
+
return fs.promises.readFile(filePath, { encoding: "utf-8" });
|
|
74
|
+
}
|
|
75
|
+
async writeFile(filePath, content) {
|
|
76
|
+
await fs.promises.writeFile(filePath, content, { encoding: "utf-8" });
|
|
77
|
+
}
|
|
78
|
+
async glob(patterns, options = {}) {
|
|
79
|
+
return fg(patterns, {
|
|
80
|
+
ignore: options.ignore || ["node_modules/**"],
|
|
81
|
+
cwd: options.cwd,
|
|
82
|
+
absolute: true,
|
|
83
|
+
onlyDirectories: true
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/core/logger/consola.ts
|
|
89
|
+
var ConsolaLogger = class {
|
|
90
|
+
info(message) {
|
|
91
|
+
consola.info(message);
|
|
92
|
+
}
|
|
93
|
+
success(message) {
|
|
94
|
+
consola.success(message);
|
|
95
|
+
}
|
|
96
|
+
warn(message) {
|
|
97
|
+
consola.warn(message);
|
|
98
|
+
}
|
|
99
|
+
error(message) {
|
|
100
|
+
consola.error(message);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/core/logger/noop.ts
|
|
105
|
+
var NoopLogger = class {
|
|
106
|
+
info(_message) {}
|
|
107
|
+
success(_message) {}
|
|
108
|
+
warn(_message) {}
|
|
109
|
+
error(_message) {}
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/core/publisher/error.ts
|
|
113
|
+
var PublishError = class extends BaseError {
|
|
114
|
+
constructor(message, options) {
|
|
115
|
+
super(message, {
|
|
116
|
+
code: "EPUBLISH",
|
|
117
|
+
statusCode: 500
|
|
118
|
+
});
|
|
119
|
+
if (options?.cause) this.cause = options.cause;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/core/publisher/memory.ts
|
|
124
|
+
var MemoryPublisher = class {
|
|
125
|
+
published;
|
|
126
|
+
constructor() {
|
|
127
|
+
this.published = [];
|
|
128
|
+
}
|
|
129
|
+
async publish(packagePath, manifest, options) {
|
|
130
|
+
this.published.push({
|
|
131
|
+
packagePath,
|
|
132
|
+
manifest,
|
|
133
|
+
options
|
|
134
|
+
});
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/utils/object.ts
|
|
140
|
+
function isObject(input) {
|
|
141
|
+
return typeof input === "object" && input !== null && !Array.isArray(input);
|
|
142
|
+
}
|
|
143
|
+
function isError(input) {
|
|
144
|
+
return isObject(input) && typeof input.message === "string";
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/core/publisher/npm.ts
|
|
148
|
+
var NpmPublisher = class {
|
|
149
|
+
/**
|
|
150
|
+
* Publish a package using libnpmpack + libnpmpublish.
|
|
151
|
+
*
|
|
152
|
+
* @returns `true` if published, `false` if the version already exists.
|
|
153
|
+
* @throws {PublishError} On non-conflict failures (network errors, auth failures, etc.).
|
|
154
|
+
*/
|
|
155
|
+
async publish(packagePath, manifest, options) {
|
|
156
|
+
try {
|
|
157
|
+
await publish$1(manifest, await libnpmpack(packagePath), options);
|
|
158
|
+
return true;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (this.isNpmJsVersionConflict(e) || this.isNpmPkgGitHubVersionConflict(e)) return false;
|
|
161
|
+
const cause = isError(e) ? e : void 0;
|
|
162
|
+
throw new PublishError(cause?.message || "libnpmpublish failed with an unknown error", { cause });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Determines whether an exception represents a version conflict
|
|
167
|
+
* when publishing to the npmjs.org registry.
|
|
168
|
+
*/
|
|
169
|
+
isNpmJsVersionConflict(ex) {
|
|
170
|
+
if (!isObject(ex)) return false;
|
|
171
|
+
if ("code" in ex && ex.code === "EPUBLISHCONFLICT") return true;
|
|
172
|
+
return "code" in ex && ex.code === "E403" && typeof ex.message === "string" && ex.message.includes("You cannot publish over the previously published versions");
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Determines whether an exception represents a version conflict
|
|
176
|
+
* when publishing to GitHub Packages (npm.pkg.github.com).
|
|
177
|
+
*/
|
|
178
|
+
isNpmPkgGitHubVersionConflict(ex) {
|
|
179
|
+
if (!isObject(ex)) return false;
|
|
180
|
+
if ("code" in ex && ex.code === "E409") return true;
|
|
181
|
+
if ("body" in ex && isObject(ex.body) && ex.body.error === "Cannot publish over existing version") return true;
|
|
182
|
+
return typeof ex.message === "string" && ex.message.startsWith("409 Conflict - PUT https://npm.pkg.github.com");
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/core/publisher/npm-cli.ts
|
|
187
|
+
const execFileAsync$1 = promisify(execFile);
|
|
188
|
+
const AUTH_TOKEN_PATTERN = /^(\/\/.+)\/:_authToken$/;
|
|
189
|
+
/**
|
|
190
|
+
* Extract the auth token entry from publish options.
|
|
191
|
+
*
|
|
192
|
+
* When `registry` is provided, prefers the entry whose scope matches
|
|
193
|
+
* the target registry (host + path). Falls back to the first match
|
|
194
|
+
* if no registry-specific entry is found.
|
|
195
|
+
*/
|
|
196
|
+
function parseAuthTokenEntry(options, registry) {
|
|
197
|
+
let registryScope;
|
|
198
|
+
if (registry) try {
|
|
199
|
+
const url = new URL(registry);
|
|
200
|
+
registryScope = `//${url.host}${url.pathname.replace(/\/$/, "")}`;
|
|
201
|
+
} catch {}
|
|
202
|
+
let fallback;
|
|
203
|
+
const keys = Object.keys(options);
|
|
204
|
+
for (const key of keys) {
|
|
205
|
+
const match = AUTH_TOKEN_PATTERN.exec(key);
|
|
206
|
+
const registryPath = match?.[1];
|
|
207
|
+
if (match && registryPath && options[key]) {
|
|
208
|
+
const entry = {
|
|
209
|
+
key,
|
|
210
|
+
token: options[key],
|
|
211
|
+
registryPath
|
|
212
|
+
};
|
|
213
|
+
if (registryScope && registryPath === registryScope) return entry;
|
|
214
|
+
if (!fallback) fallback = entry;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return fallback;
|
|
218
|
+
}
|
|
219
|
+
var NpmCliPublisher = class {
|
|
220
|
+
execFn;
|
|
221
|
+
readFileFn;
|
|
222
|
+
writeFileFn;
|
|
223
|
+
unlinkFn;
|
|
224
|
+
constructor(options = {}) {
|
|
225
|
+
this.execFn = options.execFn || execFileAsync$1;
|
|
226
|
+
this.readFileFn = options.readFileFn || ((fp, enc) => readFile(fp, enc));
|
|
227
|
+
this.writeFileFn = options.writeFileFn || ((fp, content, enc) => writeFile(fp, content, enc));
|
|
228
|
+
this.unlinkFn = options.unlinkFn || ((fp) => unlink(fp));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Publish a package by shelling out to `npm publish`.
|
|
232
|
+
*
|
|
233
|
+
* Writes a temporary `.npmrc` for auth when a token is present and
|
|
234
|
+
* restores/removes it after the command completes (even on failure).
|
|
235
|
+
*
|
|
236
|
+
* @returns `true` if published, `false` if the version already exists.
|
|
237
|
+
* @throws {PublishError} On non-conflict failures (network errors, auth failures, etc.).
|
|
238
|
+
*/
|
|
239
|
+
async publish(packagePath, _manifest, options) {
|
|
240
|
+
const args = ["publish"];
|
|
241
|
+
const authEntry = parseAuthTokenEntry(options, options.registry);
|
|
242
|
+
if (options.registry) args.push("--registry", options.registry);
|
|
243
|
+
else if (authEntry) args.push("--registry", `https:${authEntry.registryPath}`);
|
|
244
|
+
if (options.access) args.push("--access", options.access);
|
|
245
|
+
if (options.tag) args.push("--tag", options.tag);
|
|
246
|
+
const env = { ...process.env };
|
|
247
|
+
let npmrcPath;
|
|
248
|
+
let existingNpmrc;
|
|
249
|
+
if (authEntry) {
|
|
250
|
+
env.NODE_AUTH_TOKEN = authEntry.token;
|
|
251
|
+
const registryUrl = options.registry || `https:${authEntry.registryPath}`;
|
|
252
|
+
let npmrcContent;
|
|
253
|
+
try {
|
|
254
|
+
const url = new URL(registryUrl);
|
|
255
|
+
const registryPath = url.pathname.replace(/\/$/, "");
|
|
256
|
+
npmrcContent = `//${url.host}${registryPath}/:_authToken=\${NODE_AUTH_TOKEN}\n`;
|
|
257
|
+
} catch {
|
|
258
|
+
throw new PublishError(`Invalid registry URL: ${registryUrl}`);
|
|
259
|
+
}
|
|
260
|
+
npmrcPath = path.join(packagePath, ".npmrc");
|
|
261
|
+
try {
|
|
262
|
+
existingNpmrc = await this.readFileFn(npmrcPath, "utf-8");
|
|
263
|
+
} catch {}
|
|
264
|
+
const finalContent = existingNpmrc ? `${existingNpmrc.trimEnd()}\n${npmrcContent}` : npmrcContent;
|
|
265
|
+
await this.writeFileFn(npmrcPath, finalContent, "utf-8");
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await this.execFn("npm", args, {
|
|
269
|
+
cwd: packagePath,
|
|
270
|
+
env
|
|
271
|
+
});
|
|
272
|
+
return true;
|
|
273
|
+
} catch (e) {
|
|
274
|
+
if (this.isVersionConflict(e)) return false;
|
|
275
|
+
const cause = isError(e) ? e : void 0;
|
|
276
|
+
throw new PublishError(cause?.message || "npm publish failed with an unknown error", { cause });
|
|
277
|
+
} finally {
|
|
278
|
+
if (npmrcPath) if (typeof existingNpmrc === "string") await this.writeFileFn(npmrcPath, existingNpmrc, "utf-8");
|
|
279
|
+
else try {
|
|
280
|
+
await this.unlinkFn(npmrcPath);
|
|
281
|
+
} catch {}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
isVersionConflict(e) {
|
|
285
|
+
if (!isObject(e)) return false;
|
|
286
|
+
let stderr = "";
|
|
287
|
+
if (typeof e.stderr === "string") stderr = e.stderr;
|
|
288
|
+
const message = isError(e) ? e.message : "";
|
|
289
|
+
const combined = `${stderr} ${message}`;
|
|
290
|
+
return combined.includes("EPUBLISHCONFLICT") || combined.includes("You cannot publish over the previously published versions") || combined.includes("Cannot publish over existing version") || combined.includes("409 Conflict");
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/core/publisher/resolve.ts
|
|
295
|
+
const execFileAsync = promisify(execFile);
|
|
296
|
+
const NPM_MIN_VERSION = "10.0.0";
|
|
297
|
+
async function resolvePublisher(options = {}) {
|
|
298
|
+
const execFn = options.execFn || execFileAsync;
|
|
299
|
+
try {
|
|
300
|
+
const { stdout } = await execFn("npm", ["--version"], {
|
|
301
|
+
cwd: process.cwd(),
|
|
302
|
+
env: process.env
|
|
303
|
+
});
|
|
304
|
+
const version = stdout.trim();
|
|
305
|
+
if (semver.gte(version, NPM_MIN_VERSION)) return new NpmCliPublisher({ execFn });
|
|
306
|
+
} catch {}
|
|
307
|
+
return new NpmPublisher();
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/core/registry-client/error.ts
|
|
311
|
+
var RegistryError = class extends BaseError {
|
|
312
|
+
constructor(message, statusCode) {
|
|
313
|
+
super(message, {
|
|
314
|
+
code: `E${statusCode}`,
|
|
315
|
+
statusCode
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Duck-type check for RegistryError shape.
|
|
321
|
+
*
|
|
322
|
+
* Validates the error has `message` (string), `statusCode` (number),
|
|
323
|
+
* and `code` (string) — matching the BaseError contract.
|
|
324
|
+
*
|
|
325
|
+
* @param input The value to check.
|
|
326
|
+
* @returns `true` if the value has the RegistryError shape.
|
|
327
|
+
*/
|
|
328
|
+
function isRegistryError(input) {
|
|
329
|
+
return isObject(input) && typeof input.message === "string" && typeof input.statusCode === "number" && typeof input.code === "string";
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/core/registry-client/hapic.ts
|
|
333
|
+
var HapicRegistryClient = class {
|
|
334
|
+
async getPackument(name, options) {
|
|
335
|
+
const path = encodeURIComponent(name).replace(/^%40/, "@");
|
|
336
|
+
const headers = { ACCEPT: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" };
|
|
337
|
+
if (options.token) headers.AUTHORIZATION = `Bearer ${options.token}`;
|
|
338
|
+
try {
|
|
339
|
+
return (await hapic.get(new URL(path, options.registry || "https://registry.npmjs.org/").toString(), { headers })).data;
|
|
340
|
+
} catch (e) {
|
|
341
|
+
if (isClientError(e)) throw new RegistryError(e.message, e.statusCode || 500);
|
|
342
|
+
throw new RegistryError(`Registry request failed for ${name}`, 500);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/core/registry-client/memory.ts
|
|
348
|
+
var MemoryRegistryClient = class {
|
|
349
|
+
items;
|
|
350
|
+
constructor(items = {}) {
|
|
351
|
+
this.items = new Map(Object.entries(items));
|
|
352
|
+
}
|
|
353
|
+
async getPackument(name) {
|
|
354
|
+
const packument = this.items.get(name);
|
|
355
|
+
if (!packument) throw new RegistryError(`Package not found: ${name}`, 404);
|
|
356
|
+
return packument;
|
|
357
|
+
}
|
|
358
|
+
addPackument(name, packument) {
|
|
359
|
+
this.items.set(name, packument);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/core/token-provider/chain.ts
|
|
364
|
+
var ChainTokenProvider = class {
|
|
365
|
+
providers;
|
|
366
|
+
constructor(providers) {
|
|
367
|
+
this.providers = providers;
|
|
368
|
+
}
|
|
369
|
+
async getToken(packageName, registry) {
|
|
370
|
+
for (const provider of this.providers) {
|
|
371
|
+
const token = await provider.getToken(packageName, registry);
|
|
372
|
+
if (token) return token;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/core/token-provider/env.ts
|
|
378
|
+
var EnvTokenProvider = class {
|
|
379
|
+
async getToken(_packageName, _registry) {
|
|
380
|
+
return process.env.NODE_AUTH_TOKEN || void 0;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/core/token-provider/memory.ts
|
|
385
|
+
var MemoryTokenProvider = class {
|
|
386
|
+
token;
|
|
387
|
+
constructor(token) {
|
|
388
|
+
this.token = token;
|
|
389
|
+
}
|
|
390
|
+
async getToken(_packageName, _registry) {
|
|
391
|
+
return this.token;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/core/token-provider/oidc.ts
|
|
396
|
+
var OidcTokenProvider = class {
|
|
397
|
+
requestUrl;
|
|
398
|
+
requestToken;
|
|
399
|
+
fetchFn;
|
|
400
|
+
maxRetries;
|
|
401
|
+
retryDelayMs;
|
|
402
|
+
tokenCache;
|
|
403
|
+
constructor(options) {
|
|
404
|
+
this.requestUrl = options.requestUrl;
|
|
405
|
+
this.requestToken = options.requestToken;
|
|
406
|
+
this.fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
407
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
408
|
+
this.retryDelayMs = options.retryDelayMs ?? 1e3;
|
|
409
|
+
this.tokenCache = /* @__PURE__ */ new Map();
|
|
410
|
+
}
|
|
411
|
+
async getToken(packageName, registry) {
|
|
412
|
+
const cacheKey = `${packageName}@${registry}`;
|
|
413
|
+
const cached = this.tokenCache.get(cacheKey);
|
|
414
|
+
if (cached) return cached;
|
|
415
|
+
const audience = `npm:${new URL(registry).host}`;
|
|
416
|
+
const separator = this.requestUrl.includes("?") ? "&" : "?";
|
|
417
|
+
const oidcUrl = `${this.requestUrl}${separator}audience=${encodeURIComponent(audience)}`;
|
|
418
|
+
const oidcResponse = await this.fetchWithRetry(oidcUrl, { headers: { Authorization: `Bearer ${this.requestToken}` } });
|
|
419
|
+
if (!oidcResponse.ok) throw new Error(`Failed to fetch OIDC token from GitHub: ${oidcResponse.status} ${oidcResponse.statusText}`);
|
|
420
|
+
const idToken = (await oidcResponse.json()).value;
|
|
421
|
+
if (!idToken) throw new Error("OIDC response did not contain a valid token");
|
|
422
|
+
const encodedName = encodeURIComponent(packageName).replace(/^%40/, "@");
|
|
423
|
+
const exchangeUrl = new URL(`/-/npm/v1/oidc/token/exchange/package/${encodedName}`, registry).toString();
|
|
424
|
+
const exchangeResponse = await this.fetchWithRetry(exchangeUrl, {
|
|
425
|
+
method: "POST",
|
|
426
|
+
headers: { Authorization: `Bearer ${idToken}` }
|
|
427
|
+
});
|
|
428
|
+
if (!exchangeResponse.ok) throw new Error(`Failed to exchange OIDC token with npm registry: ${exchangeResponse.status} ${exchangeResponse.statusText}`);
|
|
429
|
+
const { token } = await exchangeResponse.json();
|
|
430
|
+
if (!token) throw new Error("Token exchange response did not contain a valid token");
|
|
431
|
+
this.tokenCache.set(cacheKey, token);
|
|
432
|
+
return token;
|
|
433
|
+
}
|
|
434
|
+
async fetchWithRetry(url, init) {
|
|
435
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) try {
|
|
436
|
+
const response = await this.fetchFn(url, init);
|
|
437
|
+
if (response.ok || response.status < 500) return response;
|
|
438
|
+
if (attempt < this.maxRetries) {
|
|
439
|
+
await this.delay(this.retryDelayMs * (attempt + 1));
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
return response;
|
|
443
|
+
} catch (e) {
|
|
444
|
+
if (attempt >= this.maxRetries) throw e;
|
|
445
|
+
await this.delay(this.retryDelayMs * (attempt + 1));
|
|
446
|
+
}
|
|
447
|
+
throw new Error("Unreachable");
|
|
448
|
+
}
|
|
449
|
+
delay(ms) {
|
|
450
|
+
return new Promise((resolve) => {
|
|
451
|
+
setTimeout(resolve, ms);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/package.ts
|
|
457
|
+
async function isPackagePublished(pkg, registryClient, options) {
|
|
458
|
+
const { name, version } = pkg.content;
|
|
459
|
+
if (!name || !version) throw new Error(`Name or version attribute is missing in ${pkg.path}`);
|
|
460
|
+
try {
|
|
461
|
+
const { versions } = await registryClient.getPackument(name, options);
|
|
462
|
+
if (typeof versions === "undefined" || typeof versions[version] === "undefined") return false;
|
|
463
|
+
} catch (e) {
|
|
464
|
+
if (isRegistryError(e) && e.statusCode === 404) return false;
|
|
465
|
+
throw e;
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
async function publishPackage(pkg, publisher, options) {
|
|
470
|
+
let pkgPath;
|
|
471
|
+
if (path.isAbsolute(pkg.path)) pkgPath = pkg.path;
|
|
472
|
+
else pkgPath = path.resolve(pkg.path);
|
|
473
|
+
const publishOptions = { ...pkg.content.publishConfig || {} };
|
|
474
|
+
if (options.token && options.token.length > 0) {
|
|
475
|
+
const registry = options.registry || "https://registry.npmjs.org/";
|
|
476
|
+
const url = new URL(registry);
|
|
477
|
+
const registryPath = url.pathname.replace(/\/$/, "");
|
|
478
|
+
publishOptions[`//${url.host}${registryPath}/:_authToken`] = options.token;
|
|
479
|
+
}
|
|
480
|
+
return publisher.publish(pkgPath, pkg.content, publishOptions);
|
|
481
|
+
}
|
|
482
|
+
function isPackagePublishable(pkg) {
|
|
483
|
+
return !!pkg.content.name && !pkg.content.private && !!pkg.content.version;
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/package-dependency.ts
|
|
487
|
+
function updatePackagesDependencies(packages) {
|
|
488
|
+
const pkgDir = {};
|
|
489
|
+
for (const package_ of packages) pkgDir[package_.content.name] = package_;
|
|
490
|
+
for (const pkg of packages) {
|
|
491
|
+
if (pkg.content.dependencies) updatePackageDependenciesByType(pkg, "dependencies", pkgDir);
|
|
492
|
+
if (pkg.content.devDependencies) updatePackageDependenciesByType(pkg, "devDependencies", pkgDir);
|
|
493
|
+
if (pkg.content.peerDependencies) updatePackageDependenciesByType(pkg, "peerDependencies", pkgDir);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function isDependencyWorkspaceProtocolValue(value) {
|
|
497
|
+
return value.substring(0, 10) === "workspace:";
|
|
498
|
+
}
|
|
499
|
+
function normalizeDependencyVersionValue(input, pkgVersion) {
|
|
500
|
+
if (input.length === 1) {
|
|
501
|
+
if (input === "~" || input === "^") return input + pkgVersion;
|
|
502
|
+
return pkgVersion;
|
|
503
|
+
}
|
|
504
|
+
const firstCharacter = input.substring(0, 1);
|
|
505
|
+
if (firstCharacter === "*" || firstCharacter === "~" || firstCharacter === "^") {
|
|
506
|
+
if (semver.valid(input.substring(1))) {
|
|
507
|
+
if (firstCharacter === "~" || firstCharacter === "^") return firstCharacter + pkgVersion;
|
|
508
|
+
return pkgVersion;
|
|
509
|
+
}
|
|
510
|
+
return pkgVersion;
|
|
511
|
+
}
|
|
512
|
+
return pkgVersion;
|
|
513
|
+
}
|
|
514
|
+
function updatePackageDependenciesByType(pkg, depType, pkgDir) {
|
|
515
|
+
const dependencies = pkg.content[depType];
|
|
516
|
+
if (!dependencies) return;
|
|
517
|
+
const keys = Object.keys(dependencies);
|
|
518
|
+
for (const key of keys) {
|
|
519
|
+
const value = dependencies[key];
|
|
520
|
+
if (!value || !isDependencyWorkspaceProtocolValue(value)) continue;
|
|
521
|
+
if (!hasOwnProperty(pkgDir, key)) {
|
|
522
|
+
pkg.valid = false;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const depPkg = pkgDir[key];
|
|
526
|
+
if (!depPkg) {
|
|
527
|
+
pkg.valid = false;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
dependencies[key] = normalizeDependencyVersionValue(value.substring(10), depPkg.content.version);
|
|
531
|
+
pkg.modified = true;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/module.ts
|
|
536
|
+
function resolveTokenProvider(options) {
|
|
537
|
+
if (options.tokenProvider) return options.tokenProvider;
|
|
538
|
+
if (options.token) return new MemoryTokenProvider(options.token);
|
|
539
|
+
return new EnvTokenProvider();
|
|
540
|
+
}
|
|
541
|
+
async function readWorkspacePackages(workspace, cwd, fileSystem) {
|
|
542
|
+
const directories = await fileSystem.glob(workspace, {
|
|
543
|
+
cwd,
|
|
544
|
+
ignore: ["node_modules/**"]
|
|
545
|
+
});
|
|
546
|
+
const pkgs = [];
|
|
547
|
+
for (const directory of directories) try {
|
|
548
|
+
const raw = await fileSystem.readFile(path.posix.join(directory, "package.json"));
|
|
549
|
+
const content = JSON.parse(raw);
|
|
550
|
+
pkgs.push({
|
|
551
|
+
path: directory,
|
|
552
|
+
content
|
|
553
|
+
});
|
|
554
|
+
} catch {}
|
|
555
|
+
return pkgs;
|
|
556
|
+
}
|
|
557
|
+
async function publish(options = {}) {
|
|
558
|
+
const cwd = options.cwd || process.cwd();
|
|
559
|
+
const registry = options.registry || "https://registry.npmjs.org/";
|
|
560
|
+
const rootPackage = options.rootPackage ?? true;
|
|
561
|
+
const fileSystem = options.fileSystem ?? new NodeFileSystem();
|
|
562
|
+
const registryClient = options.registryClient ?? new HapicRegistryClient();
|
|
563
|
+
const publisher = options.publisher ?? await resolvePublisher();
|
|
564
|
+
const tokenProvider = resolveTokenProvider(options);
|
|
565
|
+
const logger = options.logger ?? new NoopLogger();
|
|
566
|
+
const raw = await fileSystem.readFile(path.posix.join(cwd, "package.json"));
|
|
567
|
+
let pkg;
|
|
568
|
+
try {
|
|
569
|
+
pkg = JSON.parse(raw);
|
|
570
|
+
} catch {
|
|
571
|
+
throw new Error(`Invalid JSON in package.json at ${path.posix.join(cwd, "package.json")}`);
|
|
572
|
+
}
|
|
573
|
+
const packages = [];
|
|
574
|
+
if (!Array.isArray(pkg.workspaces) && !rootPackage) return [];
|
|
575
|
+
if (rootPackage) packages.push({
|
|
576
|
+
path: cwd,
|
|
577
|
+
content: pkg
|
|
578
|
+
});
|
|
579
|
+
if (Array.isArray(pkg.workspaces)) packages.push(...await readWorkspacePackages(pkg.workspaces, cwd, fileSystem));
|
|
580
|
+
updatePackagesDependencies(packages);
|
|
581
|
+
const unpublishedPackages = [];
|
|
582
|
+
for (const p of packages) {
|
|
583
|
+
if (!isPackagePublishable(p)) continue;
|
|
584
|
+
if (p.valid === false) {
|
|
585
|
+
logger.warn(`Skipping ${p.content.name}: unresolved workspace dependencies`);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const token = await tokenProvider.getToken(p.content.name, registry);
|
|
589
|
+
if (await isPackagePublished(p, registryClient, {
|
|
590
|
+
registry,
|
|
591
|
+
token
|
|
592
|
+
})) continue;
|
|
593
|
+
if (p.modified && !options.dryRun) try {
|
|
594
|
+
await fileSystem.writeFile(path.posix.join(p.path, "package.json"), JSON.stringify(p.content));
|
|
595
|
+
} catch (e) {
|
|
596
|
+
const message = isError(e) ? e.message : String(e);
|
|
597
|
+
logger.warn(`Failed to write package.json for ${p.content.name}: ${message}`);
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
unpublishedPackages.push({
|
|
601
|
+
pkg: p,
|
|
602
|
+
token
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (unpublishedPackages.length === 0) return [];
|
|
606
|
+
if (options.dryRun) return unpublishedPackages.map((item) => item.pkg);
|
|
607
|
+
for (const { pkg: p, token } of unpublishedPackages) p.published = await publishPackage(p, publisher, {
|
|
608
|
+
token,
|
|
609
|
+
registry
|
|
610
|
+
});
|
|
611
|
+
return unpublishedPackages.map((item) => item.pkg).filter((p) => !!p.published);
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
export { BaseError, ChainTokenProvider, ConsolaLogger, EnvTokenProvider, HapicRegistryClient, MemoryFileSystem, MemoryPublisher, MemoryRegistryClient, MemoryTokenProvider, NodeFileSystem, NoopLogger, NpmCliPublisher, NpmPublisher, OidcTokenProvider, PublishError, RegistryError, isPackagePublishable, isPackagePublished, isRegistryError, publish, publishPackage, resolvePublisher, updatePackagesDependencies };
|
|
615
|
+
|
|
616
|
+
//# sourceMappingURL=index.mjs.map
|