fix: ship ts-apt's apt-fast script with the bundle

On runners with aria2 installed, ts-apt uses apt-fast and spawns
scripts/dist/apt-fast.sh relative to its own module. Bundling moved that
module, so the action failed with "spawn .../scripts/dist/apt-fast.sh
ENOENT".

Write the bundle to dist/action/index.js and copy ts-apt's scripts/dist
to dist/scripts/dist, which is where ts-apt looks from there. Point
action.yml at the new entrypoint and include both in the action bundle
artifact. Workflow artifacts drop file modes, so the action marks the
script executable at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
awalsh128 2026-11-05 18:30:20 -08:00
parent c24c2b9e97
commit 8aa8f46244
5 changed files with 52 additions and 7 deletions

View file

@ -35,7 +35,7 @@ runs:
- name: Build
if: steps.try-download-artifact.outputs.found_artifact != 'true'
shell: bash
run: npm run build # Generates dist/ folder, including the dist/index.js bundle
run: npm run build # Generates dist/ folder, including the dist/action/index.js bundle
- name: Upload Build Artifacts
if: steps.try-download-artifact.outputs.found_artifact != 'true'

View file

@ -48,8 +48,8 @@ jobs:
name: action-bundle-${{ github.event.pull_request.head.sha || github.sha }}
path: |
action.yml
dist/index.js
dist/index.js.map
dist/action/
dist/scripts/
if-no-files-found: error
retention-days: 1

View file

@ -43,4 +43,4 @@ outputs:
runs:
using: "node24"
main: "dist/index.js"
main: "dist/action/index.js"

View file

@ -1,10 +1,27 @@
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import fs from "node:fs";
// ts-apt runs scripts/dist/apt-fast.sh, which it finds relative to its own
// module (i.e. ../scripts/dist from the bundle). Bundling moves that module
// into dist/action/index.js, so ship the scripts at dist/scripts/dist.
const TS_APT_SCRIPTS_SRC = "node_modules/ts-apt/scripts/dist";
const TS_APT_SCRIPTS_DEST = "dist/scripts/dist";
/** Copies the ts-apt shell scripts next to the bundle after it's written. */
function copyTsAptScripts() {
return {
name: "copy-ts-apt-scripts",
writeBundle() {
fs.cpSync(TS_APT_SCRIPTS_SRC, TS_APT_SCRIPTS_DEST, { recursive: true });
},
};
}
/**
* Bundles the action and all of its dependencies into dist/index.js, the
* entrypoint action.yml runs. GitHub Actions doesn't install node_modules, so
* Bundles the action and all of its dependencies into dist/action/index.js,
* the entrypoint action.yml runs. GitHub Actions doesn't install node_modules, so
* the bundle has to be self-contained.
*
* The input is the tsc output from `npm run build:lib`, so TypeScript is only
@ -15,7 +32,7 @@ import { nodeResolve } from "@rollup/plugin-node-resolve";
export default {
input: "dist/src/index.js",
output: {
file: "dist/index.js",
file: "dist/action/index.js",
format: "es",
sourcemap: true,
// Some CommonJS dependencies call require() at runtime.
@ -27,6 +44,7 @@ export default {
nodeResolve({ preferBuiltins: true, exportConditions: ["node"] }),
commonjs(),
json(),
copyTsAptScripts(),
],
onwarn(warning, warn) {
// Dependencies trigger these and they are harmless and very noisy:

View file

@ -1,9 +1,35 @@
import * as core from "@actions/core";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { runAction, type ActionInputs } from "./action.js";
import { DefaultCommandRunner } from "ts-apt";
import { Cache, CACHE_DEFAULT_DIRNAME } from "./cache.js";
import { Instruments } from "./instrumentation.js";
/**
* Marks the bundled ts-apt apt-fast script executable when it's present.
*
* ts-apt spawns the script directly, and some ways of shipping the bundle
* (e.g. workflow artifacts) drop file modes.
*
* @param bundleDir Directory of the running bundle (i.e. dist/action).
*/
function ensureBundledScriptsExecutable(
bundleDir: string = path.dirname(fileURLToPath(import.meta.url)),
): void {
const aptFastPath = path.join(
bundleDir,
"..",
"scripts",
"dist",
"apt-fast.sh",
);
if (fs.existsSync(aptFastPath)) {
fs.chmodSync(aptFastPath, 0o755);
}
}
/**
* Parses empty package behavior input from workflow configuration.
*
@ -51,6 +77,7 @@ async function main(): Promise<void> {
const inputs = getInputs();
const instruments = new Instruments(cacheDir, inputs.debug || core.isDebug());
try {
ensureBundledScriptsExecutable();
const commandRunner = new DefaultCommandRunner(
instruments.execLogger,
instruments.execLogger,