@effected/github-actions 0.13.1 → 0.13.3
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/PackageManagerInstaller.js +174 -23
- package/index.d.ts +21 -11
- package/internal/pnpmExe.js +124 -0
- package/internal/runnerFile.js +21 -2
- package/package.json +9 -8
|
@@ -3,6 +3,7 @@ import { unstubbed } from "./internal/unstubbed.js";
|
|
|
3
3
|
import { digestFileHex } from "./internal/digest.js";
|
|
4
4
|
import { isWindowsRunner, toolCacheRoot } from "./internal/runner.js";
|
|
5
5
|
import { typeAt } from "./internal/fsProbe.js";
|
|
6
|
+
import { PNPM_EXE_PREFIX, detectMusl, isNodeScript, pnpmExeTarget, strongestSri } from "./internal/pnpmExe.js";
|
|
6
7
|
import { ToolInstaller } from "./ToolInstaller.js";
|
|
7
8
|
import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
8
9
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
@@ -22,9 +23,11 @@ var PackageManagerInstallerError = class extends Schema.TaggedError()("PackageMa
|
|
|
22
23
|
* entry). `integrityMismatch` — the downloaded artifact does not hash to
|
|
23
24
|
* what the pin declares; nothing was cached. `integrityMissing` — the pin
|
|
24
25
|
* carries no integrity and the caller asked for `requireIntegrity`.
|
|
25
|
-
* `unsupportedPlatform` —
|
|
26
|
-
* OS/architecture pair
|
|
27
|
-
*
|
|
26
|
+
* `unsupportedPlatform` — no build exists for this runner's
|
|
27
|
+
* OS/architecture pair: bun publishes none, or pnpm 12 ships no
|
|
28
|
+
* `@pnpm/exe.*` native binary for it. `layoutUnexpected` — the artifact
|
|
29
|
+
* extracted, but its contents are not shaped like the package manager it
|
|
30
|
+
* claims to be.
|
|
28
31
|
*/
|
|
29
32
|
reason: Schema.Literals([
|
|
30
33
|
"downloadFailed",
|
|
@@ -93,10 +96,14 @@ var AmbientPackageManager = class extends Schema.Class("AmbientPackageManager")(
|
|
|
93
96
|
* on Windows); for bun it is the entry directory itself, because the cached
|
|
94
97
|
* `bun` binary is directly executable and a shim would add nothing but a
|
|
95
98
|
* fork. `bins` still maps each published bin name to the underlying entry —
|
|
96
|
-
* a Node script to run with `node` for npm
|
|
97
|
-
* bun
|
|
98
|
-
*
|
|
99
|
-
*
|
|
99
|
+
* a Node script to run with `node` for npm, yarn and pnpm 11 and earlier; an
|
|
100
|
+
* executable to run directly for bun and for pnpm 12+, whose `pnpm` is the
|
|
101
|
+
* host's native binary (overlaid onto the cached wrapper the way pnpm's own
|
|
102
|
+
* install script does) and whose `pn`/`pnpx`/`pnx` are shell aliases of it.
|
|
103
|
+
* Each shim already applies that distinction. Calling `addPath` remains
|
|
104
|
+
* deliberately the consumer's move, and note that `addPath` targets
|
|
105
|
+
* *subsequent* steps: a same-process probe must use the absolute paths in
|
|
106
|
+
* `bins` or `binDir`.
|
|
100
107
|
*
|
|
101
108
|
* @public
|
|
102
109
|
*/
|
|
@@ -136,12 +143,23 @@ const InstalledPackageManager = Schema.Union([AmbientPackageManager, CachedPacka
|
|
|
136
143
|
/** The shim directory name inside a cached npm-registry-manager entry. */
|
|
137
144
|
const SHIM_DIR = ".bin";
|
|
138
145
|
/**
|
|
139
|
-
* The POSIX shim body: an `exec` wrapper so the shim's process *becomes*
|
|
140
|
-
*
|
|
146
|
+
* The POSIX shim body: an `exec` wrapper so the shim's process *becomes* the
|
|
147
|
+
* target, and `"$@"` so arguments survive quoting intact. A Node script
|
|
148
|
+
* (`.js`/`.mjs`/`.cjs`) runs under `node`; anything else — pnpm 12's native
|
|
149
|
+
* binary, its `#!/bin/sh` alias scripts — is exec'd directly, because handing
|
|
150
|
+
* a shell script or a Mach-O to node is exactly the "Invalid or unexpected
|
|
151
|
+
* token" failure this rule exists to prevent.
|
|
141
152
|
*/
|
|
142
|
-
const posixShim = (target) => `#!/bin/sh\nexec node "${target}" "$@"\n`;
|
|
143
|
-
/** The Windows shim body, CRLF-terminated as cmd expects. */
|
|
144
|
-
const cmdShim = (target) => `@echo off\r\nnode "${target}" %*\r\n`;
|
|
153
|
+
const posixShim = (target) => isNodeScript(target) ? `#!/bin/sh\nexec node "${target}" "$@"\n` : `#!/bin/sh\nexec "${target}" "$@"\n`;
|
|
154
|
+
/** The Windows shim body, CRLF-terminated as cmd expects; same node-vs-direct rule. */
|
|
155
|
+
const cmdShim = (target) => isNodeScript(target) ? `@echo off\r\nnode "${target}" %*\r\n` : `@echo off\r\n"${target}" %*\r\n`;
|
|
156
|
+
/** The bin names pnpm's wrapper publishes, every one of which becomes the native binary on Windows. */
|
|
157
|
+
const PNPM_NATIVE_BIN_NAMES = [
|
|
158
|
+
"pnpm",
|
|
159
|
+
"pn",
|
|
160
|
+
"pnpx",
|
|
161
|
+
"pnx"
|
|
162
|
+
];
|
|
145
163
|
/**
|
|
146
164
|
* The bun release asset name for a runner platform, or `None` when bun
|
|
147
165
|
* publishes no build for it.
|
|
@@ -201,6 +219,7 @@ const make = Effect.gen(function* () {
|
|
|
201
219
|
onSome: archFromRunner
|
|
202
220
|
}));
|
|
203
221
|
const windows = yield* isWindowsRunner(env);
|
|
222
|
+
const musl = detectMusl();
|
|
204
223
|
const bunBinaryName = windows ? "bun.exe" : "bun";
|
|
205
224
|
const shimFileName = (name) => windows ? `${name}.cmd` : name;
|
|
206
225
|
const shimBody = windows ? cmdShim : posixShim;
|
|
@@ -265,13 +284,18 @@ const make = Effect.gen(function* () {
|
|
|
265
284
|
}));
|
|
266
285
|
});
|
|
267
286
|
/**
|
|
268
|
-
*
|
|
287
|
+
* What a package directory's own manifest says about its entry points: the
|
|
288
|
+
* `bin` map, plus the `@pnpm/exe.*` optional dependencies that mark the
|
|
289
|
+
* pnpm 12 native-binary layout (empty for every other manager and version).
|
|
269
290
|
*
|
|
270
291
|
* Read from the artifact rather than hardcoded per manager, because the
|
|
271
292
|
* layouts genuinely move — corepack's own table has pnpm's entry at
|
|
272
|
-
* `bin/pnpm.cjs` through v10 and `bin/pnpm.mjs` from v11
|
|
293
|
+
* `bin/pnpm.cjs` through v10 and `bin/pnpm.mjs` from v11, and from v12 the
|
|
294
|
+
* `pnpm` bin is a placeholder for a native binary shipped as a separate
|
|
295
|
+
* package. Both maps are attacker-supplied bytes and are normalized to
|
|
296
|
+
* string → string before anything downstream reads them.
|
|
273
297
|
*/
|
|
274
|
-
const
|
|
298
|
+
const readPackageManifest = (pin, packageDir) => Effect.gen(function* () {
|
|
275
299
|
const manifestPath = path.join(packageDir, "package.json");
|
|
276
300
|
const raw = yield* fs.readFileString(manifestPath).pipe(Effect.mapError((cause) => errorFor(pin)({
|
|
277
301
|
reason: "layoutUnexpected",
|
|
@@ -300,8 +324,41 @@ const make = Effect.gen(function* () {
|
|
|
300
324
|
}));
|
|
301
325
|
yield* assertFile(pin, target, `bin ${name} (${relative}) is missing`);
|
|
302
326
|
}
|
|
303
|
-
return
|
|
327
|
+
return {
|
|
328
|
+
bins,
|
|
329
|
+
nativePackages: nativePackagesOf(manifest.optionalDependencies)
|
|
330
|
+
};
|
|
304
331
|
});
|
|
332
|
+
/**
|
|
333
|
+
* The `@pnpm/exe.<target>` → version entries of an `optionalDependencies`
|
|
334
|
+
* value, and nothing else: only well-formed string pairs under that prefix
|
|
335
|
+
* survive, so a hostile or malformed map cannot smuggle a package name
|
|
336
|
+
* into a registry url.
|
|
337
|
+
*/
|
|
338
|
+
const nativePackagesOf = (optionalDependencies) => {
|
|
339
|
+
if (typeof optionalDependencies !== "object" || optionalDependencies === null) return {};
|
|
340
|
+
const entries = {};
|
|
341
|
+
for (const [name, version] of Object.entries(optionalDependencies)) if (name.startsWith("@pnpm/exe.") && typeof version === "string") entries[name] = version;
|
|
342
|
+
return entries;
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* pnpm's Windows overlay places the native binary as `<name>.exe` for every
|
|
346
|
+
* wrapper bin and repoints `bin` at those; this is that repoint, applied to
|
|
347
|
+
* the in-memory map (the shims and the record) rather than the manifest.
|
|
348
|
+
*/
|
|
349
|
+
const nativeWindowsBins = (bins) => Object.fromEntries(Object.entries(bins).map(([name, relative]) => PNPM_NATIVE_BIN_NAMES.includes(name) ? [name, `${relative}.exe`] : [name, relative]));
|
|
350
|
+
/**
|
|
351
|
+
* Whether a file is still pnpm's shebang-less placeholder (a `#`-led text
|
|
352
|
+
* file that is not a `#!` script) rather than the native binary meant to
|
|
353
|
+
* overlay it — the mark of an entry written by a version of this module
|
|
354
|
+
* that predates the overlay, or by a foreign writer that ran no lifecycle
|
|
355
|
+
* scripts. Anything else (a shebang, an executable's magic, an unreadable
|
|
356
|
+
* or empty file) is left to the ordinary layout checks.
|
|
357
|
+
*/
|
|
358
|
+
const isPlaceholder = (file) => Effect.scoped(Effect.flatMap(fs.open(file), (handle) => handle.readAlloc(2))).pipe(Effect.map((head) => Option.match(head, {
|
|
359
|
+
onNone: () => false,
|
|
360
|
+
onSome: (bytes) => bytes.length >= 1 && bytes[0] === 35 && bytes[1] !== 33
|
|
361
|
+
})), Effect.orElseSucceed(() => false));
|
|
305
362
|
const rootBins = (bins, directory) => Object.fromEntries(Object.entries(bins).map(([name, relative]) => [name, path.join(directory, relative)]));
|
|
306
363
|
/**
|
|
307
364
|
* Write one executable shim per bin into `<into>/.bin`, each invoking
|
|
@@ -333,28 +390,118 @@ const make = Effect.gen(function* () {
|
|
|
333
390
|
if (!windows) yield* fs.chmod(shim, 493).pipe(Effect.mapError(cacheError(shim)));
|
|
334
391
|
}
|
|
335
392
|
});
|
|
336
|
-
/**
|
|
393
|
+
/**
|
|
394
|
+
* The record for a directory already in the cache, regenerating absent
|
|
395
|
+
* shims — or `None` when the entry is a pnpm 12 wrapper whose `pnpm` bin is
|
|
396
|
+
* still the placeholder, which no shim can make runnable: the caller then
|
|
397
|
+
* reinstalls over it through the ordinary install path (ToolInstaller's
|
|
398
|
+
* swap removes the old entry and renames the complete new one into place).
|
|
399
|
+
*/
|
|
337
400
|
const cachedRecord = (pin, directory) => Effect.gen(function* () {
|
|
338
401
|
if (pin.name === "bun") {
|
|
339
402
|
const binary = path.join(directory, bunBinaryName);
|
|
340
403
|
yield* assertFile(pin, binary, `cached bun binary (${bunBinaryName}) is missing`);
|
|
341
|
-
return CachedPackageManager.make({
|
|
404
|
+
return Option.some(CachedPackageManager.make({
|
|
342
405
|
name: pin.name,
|
|
343
406
|
version: pin.version.toString(),
|
|
344
407
|
directory,
|
|
345
408
|
binDir: directory,
|
|
346
409
|
bins: { bun: binary }
|
|
347
|
-
});
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
const manifest = yield* readPackageManifest(pin, directory);
|
|
413
|
+
let bins = manifest.bins;
|
|
414
|
+
if (Object.keys(manifest.nativePackages).length > 0) {
|
|
415
|
+
const entry = bins[pin.name];
|
|
416
|
+
if (entry !== void 0 && (yield* isPlaceholder(path.join(directory, entry)))) {
|
|
417
|
+
yield* Effect.logInfo(`The cached ${pin.name}@${pin.version.toString()} still holds pnpm's placeholder bin; reinstalling it over.`);
|
|
418
|
+
return Option.none();
|
|
419
|
+
}
|
|
420
|
+
if (windows) bins = nativeWindowsBins(bins);
|
|
348
421
|
}
|
|
349
|
-
const bins = yield* readPackageBins(pin, directory);
|
|
350
422
|
yield* writeShims(pin, directory, directory, bins, { skipExisting: true });
|
|
351
|
-
return CachedPackageManager.make({
|
|
423
|
+
return Option.some(CachedPackageManager.make({
|
|
352
424
|
name: pin.name,
|
|
353
425
|
version: pin.version.toString(),
|
|
354
426
|
directory,
|
|
355
427
|
binDir: path.join(directory, SHIM_DIR),
|
|
356
428
|
bins: rootBins(bins, directory)
|
|
429
|
+
}));
|
|
430
|
+
});
|
|
431
|
+
/**
|
|
432
|
+
* pnpm 12's native overlay, done here because the installer never runs the
|
|
433
|
+
* lifecycle script that does it upstream: pick the host's `@pnpm/exe.*`
|
|
434
|
+
* package from the wrapper's optional dependencies, fetch its tarball from
|
|
435
|
+
* the SAME registry the wrapper came from, verify it against the registry's
|
|
436
|
+
* own `dist.integrity` (fail-closed — this is a second artifact the pin
|
|
437
|
+
* never named, so there is no integrity-less posture to honor), and copy
|
|
438
|
+
* the executable over the placeholder in the STAGED wrapper, where `dist/`
|
|
439
|
+
* sits beside it as the binary expects. Answers the bins as they should be
|
|
440
|
+
* shimmed and recorded — retargeted to `.exe` on Windows, as upstream does.
|
|
441
|
+
*/
|
|
442
|
+
const overlayNativeBinary = (pin, packageDir, bins, nativePackages, registry) => Effect.gen(function* () {
|
|
443
|
+
const target = Option.getOrUndefined(pnpmExeTarget(runnerOs, arch, musl));
|
|
444
|
+
const packageName = target === void 0 ? void 0 : `${PNPM_EXE_PREFIX}${target}`;
|
|
445
|
+
const nativeVersion = packageName === void 0 ? void 0 : nativePackages[packageName];
|
|
446
|
+
if (target === void 0 || packageName === void 0 || nativeVersion === void 0) return yield* Effect.fail(errorFor(pin)({
|
|
447
|
+
reason: "unsupportedPlatform",
|
|
448
|
+
subject: `${runnerOs || "unknown"}/${arch}`
|
|
449
|
+
}));
|
|
450
|
+
if (nativeVersion !== pin.version.toString()) return yield* Effect.fail(errorFor(pin)({
|
|
451
|
+
reason: "layoutUnexpected",
|
|
452
|
+
subject: `${packageName} is pinned at ${nativeVersion}, not the wrapper's ${pin.version.toString()}`
|
|
453
|
+
}));
|
|
454
|
+
const placeholder = bins[pin.name];
|
|
455
|
+
if (placeholder === void 0) return yield* Effect.fail(errorFor(pin)({
|
|
456
|
+
reason: "layoutUnexpected",
|
|
457
|
+
subject: `package.json names no ${pin.name} bin to overlay`
|
|
458
|
+
}));
|
|
459
|
+
const packumentUrl = `${registry}/${packageName}/${nativeVersion}`;
|
|
460
|
+
const packumentFile = yield* installer.download(packumentUrl).pipe(Effect.mapError(fromInstaller(pin)));
|
|
461
|
+
const unverifiable = (cause) => errorFor(pin)({
|
|
462
|
+
reason: "integrityMismatch",
|
|
463
|
+
subject: packumentUrl,
|
|
464
|
+
...cause === void 0 ? {} : { cause }
|
|
465
|
+
});
|
|
466
|
+
const declared = (yield* fs.readFileString(packumentFile).pipe(Effect.mapError((cause) => unverifiable(cause)), Effect.flatMap((raw) => Effect.try({
|
|
467
|
+
try: () => JSON.parse(raw),
|
|
468
|
+
catch: (cause) => unverifiable(cause)
|
|
469
|
+
})))).dist?.integrity;
|
|
470
|
+
const expected = typeof declared === "string" ? Option.getOrUndefined(strongestSri(declared)) : void 0;
|
|
471
|
+
if (expected === void 0) return yield* Effect.fail(unverifiable());
|
|
472
|
+
const tarballUrl = `${registry}/${packageName}/-/exe.${target}-${nativeVersion}.tgz`;
|
|
473
|
+
const archive = yield* installer.download(tarballUrl).pipe(Effect.mapError(fromInstaller(pin)));
|
|
474
|
+
const actualHex = yield* hashFile(pin, archive, expected.algorithm);
|
|
475
|
+
if (actualHex !== expected.hex) return yield* Effect.fail(errorFor(pin)({
|
|
476
|
+
reason: "integrityMismatch",
|
|
477
|
+
subject: tarballUrl,
|
|
478
|
+
expected: `${expected.algorithm}.${expected.hex}`,
|
|
479
|
+
actual: `${expected.algorithm}.${actualHex}`
|
|
480
|
+
}));
|
|
481
|
+
const extracted = yield* installer.extractTar(archive).pipe(Effect.mapError(fromInstaller(pin)));
|
|
482
|
+
const nativeName = windows ? "pnpm.exe" : "pnpm";
|
|
483
|
+
const native = path.join(extracted, "package", nativeName);
|
|
484
|
+
yield* assertFile(pin, native, `package/${nativeName} is missing from the ${packageName} tarball`);
|
|
485
|
+
const cacheError = (subject) => (cause) => errorFor(pin)({
|
|
486
|
+
reason: "cacheFailed",
|
|
487
|
+
subject,
|
|
488
|
+
cause
|
|
357
489
|
});
|
|
490
|
+
if (windows) {
|
|
491
|
+
for (const name of PNPM_NATIVE_BIN_NAMES) {
|
|
492
|
+
const relative = bins[name];
|
|
493
|
+
if (relative === void 0) continue;
|
|
494
|
+
for (const destination of [path.join(packageDir, relative), path.join(packageDir, `${relative}.exe`)]) yield* fs.copyFile(native, destination).pipe(Effect.mapError(cacheError(destination)));
|
|
495
|
+
}
|
|
496
|
+
return nativeWindowsBins(bins);
|
|
497
|
+
}
|
|
498
|
+
const destination = path.join(packageDir, placeholder);
|
|
499
|
+
yield* fs.copyFile(native, destination).pipe(Effect.mapError(cacheError(destination)));
|
|
500
|
+
for (const relative of Object.values(bins)) if (!isNodeScript(relative)) {
|
|
501
|
+
const file = path.join(packageDir, relative);
|
|
502
|
+
yield* fs.chmod(file, 493).pipe(Effect.mapError(cacheError(file)));
|
|
503
|
+
}
|
|
504
|
+
return bins;
|
|
358
505
|
});
|
|
359
506
|
/** Download bun's per-platform zip from GitHub releases and cache the binary. */
|
|
360
507
|
const installBun = (pin) => Effect.gen(function* () {
|
|
@@ -397,7 +544,8 @@ const make = Effect.gen(function* () {
|
|
|
397
544
|
reason: "layoutUnexpected",
|
|
398
545
|
subject: "tarball has no package/ root"
|
|
399
546
|
}));
|
|
400
|
-
const
|
|
547
|
+
const manifest = yield* readPackageManifest(pin, packageDir);
|
|
548
|
+
const bins = Object.keys(manifest.nativePackages).length > 0 ? yield* overlayNativeBinary(pin, packageDir, manifest.bins, manifest.nativePackages, registry) : manifest.bins;
|
|
401
549
|
if (berry) {
|
|
402
550
|
const cli = path.join(packageDir, "bin/yarn.js");
|
|
403
551
|
yield* assertFile(pin, cli, "bin/yarn.js is missing from the cli-dist tarball");
|
|
@@ -426,7 +574,10 @@ const make = Effect.gen(function* () {
|
|
|
426
574
|
});
|
|
427
575
|
if (options?.requireIntegrity === true && pin.integrity === void 0) return yield* Effect.fail(errorFor(pin)({ reason: "integrityMissing" }));
|
|
428
576
|
const cached = yield* installer.find(pin.name, version);
|
|
429
|
-
if (Option.isSome(cached))
|
|
577
|
+
if (Option.isSome(cached)) {
|
|
578
|
+
const record = yield* cachedRecord(pin, cached.value);
|
|
579
|
+
if (Option.isSome(record)) return record.value;
|
|
580
|
+
}
|
|
430
581
|
if (pin.name === "npm" && options?.allowAmbient !== false) {
|
|
431
582
|
const probed = yield* ambientNpmVersion;
|
|
432
583
|
if (Option.isSome(probed) && probed.value === version) return AmbientPackageManager.make({
|
package/index.d.ts
CHANGED
|
@@ -4490,9 +4490,11 @@ declare const PackageManagerInstallerError_base: Schema.Class<PackageManagerInst
|
|
|
4490
4490
|
* entry). `integrityMismatch` — the downloaded artifact does not hash to
|
|
4491
4491
|
* what the pin declares; nothing was cached. `integrityMissing` — the pin
|
|
4492
4492
|
* carries no integrity and the caller asked for `requireIntegrity`.
|
|
4493
|
-
* `unsupportedPlatform` —
|
|
4494
|
-
* OS/architecture pair
|
|
4495
|
-
*
|
|
4493
|
+
* `unsupportedPlatform` — no build exists for this runner's
|
|
4494
|
+
* OS/architecture pair: bun publishes none, or pnpm 12 ships no
|
|
4495
|
+
* `@pnpm/exe.*` native binary for it. `layoutUnexpected` — the artifact
|
|
4496
|
+
* extracted, but its contents are not shaped like the package manager it
|
|
4497
|
+
* claims to be.
|
|
4496
4498
|
*/
|
|
4497
4499
|
readonly reason: Schema.Literals<readonly ["downloadFailed", "extractFailed", "integrityMismatch", "integrityMissing", "unsupportedPlatform", "layoutUnexpected", "cacheFailed"]>;
|
|
4498
4500
|
/** The package manager being installed. */
|
|
@@ -4530,10 +4532,14 @@ interface PackageManagerInstallOptions {
|
|
|
4530
4532
|
readonly requireIntegrity?: boolean | undefined;
|
|
4531
4533
|
/**
|
|
4532
4534
|
* The npm registry host the npm, pnpm and yarn tarballs download from.
|
|
4533
|
-
* Defaults to `https://registry.npmjs.org
|
|
4534
|
-
* the standard `/<name>/-/<basename>-<version>.tgz` tarball
|
|
4535
|
-
*
|
|
4536
|
-
*
|
|
4535
|
+
* Defaults to `https://registry.npmjs.org`. A corporate mirror qualifies
|
|
4536
|
+
* when it serves the standard `/<name>/-/<basename>-<version>.tgz` tarball
|
|
4537
|
+
* route and — for pnpm 12 and later, whose native binary is a second
|
|
4538
|
+
* artifact verified against the registry's own integrity — the
|
|
4539
|
+
* `/<name>/<version>` packument route as JSON carrying `dist.integrity`.
|
|
4540
|
+
* A tarball-only proxy fails a pnpm 12 pin with `downloadFailed` naming
|
|
4541
|
+
* that packument url. bun does not ship through a registry — its
|
|
4542
|
+
* per-platform zip always comes from GitHub releases.
|
|
4537
4543
|
*/
|
|
4538
4544
|
readonly registry?: string | undefined;
|
|
4539
4545
|
/**
|
|
@@ -4600,10 +4606,14 @@ declare const CachedPackageManager_base: Schema.Class<CachedPackageManager, Sche
|
|
|
4600
4606
|
* on Windows); for bun it is the entry directory itself, because the cached
|
|
4601
4607
|
* `bun` binary is directly executable and a shim would add nothing but a
|
|
4602
4608
|
* fork. `bins` still maps each published bin name to the underlying entry —
|
|
4603
|
-
* a Node script to run with `node` for npm
|
|
4604
|
-
* bun
|
|
4605
|
-
*
|
|
4606
|
-
*
|
|
4609
|
+
* a Node script to run with `node` for npm, yarn and pnpm 11 and earlier; an
|
|
4610
|
+
* executable to run directly for bun and for pnpm 12+, whose `pnpm` is the
|
|
4611
|
+
* host's native binary (overlaid onto the cached wrapper the way pnpm's own
|
|
4612
|
+
* install script does) and whose `pn`/`pnpx`/`pnx` are shell aliases of it.
|
|
4613
|
+
* Each shim already applies that distinction. Calling `addPath` remains
|
|
4614
|
+
* deliberately the consumer's move, and note that `addPath` targets
|
|
4615
|
+
* *subsequent* steps: a same-process probe must use the absolute paths in
|
|
4616
|
+
* `bins` or `binDir`.
|
|
4607
4617
|
*
|
|
4608
4618
|
* @public
|
|
4609
4619
|
*/
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Encoding, Option, Result } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/pnpmExe.ts
|
|
4
|
+
/**
|
|
5
|
+
* The pnpm native-binary layout: from pnpm 12 the `pnpm` registry package is a
|
|
6
|
+
* thin wrapper whose `pnpm` bin is a shebang-less placeholder that the
|
|
7
|
+
* package's own install script overwrites with the host's native executable,
|
|
8
|
+
* fetched as an optional dependency named `@pnpm/exe.<os>-<arch>[-musl]`.
|
|
9
|
+
* The installer never runs lifecycle scripts, so it performs that overlay
|
|
10
|
+
* itself; the pure pieces of that live here, where they are testable without
|
|
11
|
+
* a tarball.
|
|
12
|
+
*
|
|
13
|
+
* Detection is by LAYOUT (the manifest's `optionalDependencies` naming an
|
|
14
|
+
* `@pnpm/exe.*` package), never by major version — pnpm 11 and earlier ship
|
|
15
|
+
* no such dependency and keep their Node entry points.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
/** The prefix every native-binary package name carries. @internal */
|
|
20
|
+
const PNPM_EXE_PREFIX = "@pnpm/exe.";
|
|
21
|
+
/**
|
|
22
|
+
* The targets pnpm publishes a native binary for — the `PLATFORMS` table of
|
|
23
|
+
* the wrapper's own `native-binary.mjs`, flattened to the spellings that
|
|
24
|
+
* follow the prefix. Only linux x64/arm64 have a musl twin.
|
|
25
|
+
*/
|
|
26
|
+
const PNPM_EXE_TARGETS = /* @__PURE__ */ new Set([
|
|
27
|
+
"linux-x64",
|
|
28
|
+
"linux-arm64",
|
|
29
|
+
"linux-x64-musl",
|
|
30
|
+
"linux-arm64-musl",
|
|
31
|
+
"linux-riscv64",
|
|
32
|
+
"linux-ppc64",
|
|
33
|
+
"linux-s390x",
|
|
34
|
+
"darwin-x64",
|
|
35
|
+
"darwin-arm64",
|
|
36
|
+
"win32-x64",
|
|
37
|
+
"win32-arm64",
|
|
38
|
+
"freebsd-x64",
|
|
39
|
+
"android-x64",
|
|
40
|
+
"android-arm64"
|
|
41
|
+
]);
|
|
42
|
+
/** `RUNNER_OS` spellings (lower-cased) onto Node's `process.platform` spellings, which the targets use. */
|
|
43
|
+
const RUNNER_OS_TO_PLATFORM = {
|
|
44
|
+
linux: "linux",
|
|
45
|
+
macos: "darwin",
|
|
46
|
+
windows: "win32"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The `@pnpm/exe.*` target for a runner platform, or `None` when pnpm
|
|
50
|
+
* publishes no native binary for it.
|
|
51
|
+
*
|
|
52
|
+
* `musl` only distinguishes on linux; an arch released for glibc alone
|
|
53
|
+
* (riscv64, ppc64, s390x) answers `None` on a musl host, as upstream does —
|
|
54
|
+
* the glibc binary cannot run there.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
const pnpmExeTarget = (runnerOs, arch, musl) => {
|
|
59
|
+
const platform = RUNNER_OS_TO_PLATFORM[runnerOs.toLowerCase()];
|
|
60
|
+
if (platform === void 0) return Option.none();
|
|
61
|
+
const target = `${platform}-${arch}${platform === "linux" && musl ? "-musl" : ""}`;
|
|
62
|
+
return PNPM_EXE_TARGETS.has(target) ? Option.some(target) : Option.none();
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Whether the HOST libc is musl, the way the wrapper's `native-binary.mjs`
|
|
66
|
+
* decides it: a glibc build of node reports `glibcVersionRuntime` in its
|
|
67
|
+
* diagnostic report header, a musl build does not. Only meaningful on a
|
|
68
|
+
* linux host; anything unprobeable counts as glibc, which is what every
|
|
69
|
+
* hosted runner is.
|
|
70
|
+
*
|
|
71
|
+
* `process.report` is a global, not a `node:` import, so this stays inside
|
|
72
|
+
* the package's closed `node:` list.
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
const detectMusl = () => {
|
|
77
|
+
if (process.platform !== "linux") return false;
|
|
78
|
+
try {
|
|
79
|
+
const report = process.report?.getReport();
|
|
80
|
+
if (report === void 0 || report.header === void 0) return false;
|
|
81
|
+
return report.header.glibcVersionRuntime === void 0 || report.header.glibcVersionRuntime === "";
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** The SRI algorithms a registry may list, strongest first. */
|
|
87
|
+
const SRI_ALGORITHMS = [
|
|
88
|
+
"sha512",
|
|
89
|
+
"sha384",
|
|
90
|
+
"sha256",
|
|
91
|
+
"sha1"
|
|
92
|
+
];
|
|
93
|
+
/**
|
|
94
|
+
* The strongest entry of a Subresource Integrity string (`<algo>-<base64>`,
|
|
95
|
+
* several separated by whitespace), as `{ algorithm, hex }` — the spelling a
|
|
96
|
+
* corepack pin uses, so a mismatch reports both sides the same way. `None`
|
|
97
|
+
* when nothing parseable is there.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
const strongestSri = (integrity) => {
|
|
102
|
+
const entries = integrity.split(/\s+/).filter((entry) => entry.length > 0).flatMap((entry) => {
|
|
103
|
+
const dash = entry.indexOf("-");
|
|
104
|
+
if (dash <= 0) return [];
|
|
105
|
+
const algorithm = entry.slice(0, dash).toLowerCase();
|
|
106
|
+
if (!SRI_ALGORITHMS.includes(algorithm)) return [];
|
|
107
|
+
const digest = entry.slice(dash + 1).split("?")[0] ?? "";
|
|
108
|
+
const decoded = Encoding.decodeBase64(digest);
|
|
109
|
+
return Result.isSuccess(decoded) ? [{
|
|
110
|
+
algorithm,
|
|
111
|
+
hex: Encoding.encodeHex(decoded.success)
|
|
112
|
+
}] : [];
|
|
113
|
+
});
|
|
114
|
+
for (const algorithm of SRI_ALGORITHMS) {
|
|
115
|
+
const found = entries.find((entry) => entry.algorithm === algorithm);
|
|
116
|
+
if (found !== void 0) return Option.some(found);
|
|
117
|
+
}
|
|
118
|
+
return Option.none();
|
|
119
|
+
};
|
|
120
|
+
/** Whether a bin target is a Node script (run with `node`) rather than an executable run directly. @internal */
|
|
121
|
+
const isNodeScript = (target) => /\.[cm]?js$/i.test(target);
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
export { PNPM_EXE_PREFIX, detectMusl, isNodeScript, pnpmExeTarget, strongestSri };
|
package/internal/runnerFile.js
CHANGED
|
@@ -25,11 +25,30 @@ const delimiterFor = (value) => {
|
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Whether `name` can head a block without breaking the structure it lives
|
|
28
|
-
* in: non-empty,
|
|
28
|
+
* in: non-empty, free of the line breaks that would end it early, and free
|
|
29
|
+
* of the separators the runner's file-command parser would re-split the
|
|
30
|
+
* line on.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* The runner (`FileCommandManager`) locates the first `=` and the first
|
|
34
|
+
* `<<` on each line and whichever comes first decides the shape: a
|
|
35
|
+
* `key=value` property assignment, or a `name<<delimiter` heredoc block.
|
|
36
|
+
* Because a block line is always `name<<DELIMITER`, an `=` anywhere in the
|
|
37
|
+
* name precedes the marker and the line parses as a property assignment cut
|
|
38
|
+
* at that `=`, leaving every line of the block to be read as stray entries;
|
|
39
|
+
* a `<<` inside the name splits there instead and yields a delimiter the
|
|
40
|
+
* terminating line can never match. Either way the entries after the
|
|
41
|
+
* malformed block are corrupted, so both separators are refused. A name
|
|
42
|
+
* ending in a single `<` corrupts the block the same way: the composed
|
|
43
|
+
* header `name<<<DELIMITER` matches the runner's first `<<` one character
|
|
44
|
+
* early, so the delimiter it waits for is `<DELIMITER` and the terminating
|
|
45
|
+
* line never matches — the whole file is lost. An interior `<` is safe
|
|
46
|
+
* (`a<b` still composes a header that splits exactly at the marker), so
|
|
47
|
+
* only the trailing position is refused.
|
|
29
48
|
*
|
|
30
49
|
* @internal
|
|
31
50
|
*/
|
|
32
|
-
const isUsableName = (name) => name !== "" && !/[\r\n]/.test(name);
|
|
51
|
+
const isUsableName = (name) => name !== "" && !/[\r\n]/.test(name) && !name.includes("=") && !name.includes("<<") && !name.endsWith("<");
|
|
33
52
|
/**
|
|
34
53
|
* One heredoc block, ready to append. The caller has checked the name with
|
|
35
54
|
* {@link isUsableName}.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/github-actions",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The GitHub Actions runtime: env, inputs, outputs, state, logging, cache, artifacts, blob storage, OIDC and the token bridge.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,13 +39,14 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@azure/storage-blob": "^12.33.0",
|
|
42
|
-
"@effected/github": "^0.10.
|
|
43
|
-
"@effected/glob": "^0.6.
|
|
44
|
-
"@effected/markdown": "^0.10.
|
|
45
|
-
"@effected/npm": "^0.14.
|
|
46
|
-
"@effected/sbom": "^0.6.
|
|
47
|
-
"@effected/
|
|
48
|
-
"@effected/
|
|
42
|
+
"@effected/github": "^0.10.2",
|
|
43
|
+
"@effected/glob": "^0.6.1",
|
|
44
|
+
"@effected/markdown": "^0.10.1",
|
|
45
|
+
"@effected/npm": "^0.14.1",
|
|
46
|
+
"@effected/sbom": "^0.6.2",
|
|
47
|
+
"@effected/semver": "^0.7.1",
|
|
48
|
+
"@effected/templates": "^0.6.1",
|
|
49
|
+
"@effected/walker": "^0.9.1"
|
|
49
50
|
},
|
|
50
51
|
"peerDependencies": {
|
|
51
52
|
"@effect/platform-node": "4.0.0-rc.115",
|