mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
* feat(cli): lambda deploy works from the npm install, no repo checkout Each release builds the Lambda handler zip on Linux, attaches it to the GitHub release, and pins its URL and sha256 (plus the SAM template) in @hyperframes/aws-lambda before npm publish. Outside a checkout, lambda deploy downloads that zip, refuses it on a sha256 mismatch, and deploys it with the packaged template. With neither a checkout nor a pinned zip it fails up front with one message naming every way out. * fix(cli): lambda deploy test mocks the pinned-handler reader, comments fit the checks * test(cli): sam tests deploy from a SAM directory, not the repo root * chore(aws-lambda): raise the package size budget for the pinned handler template Measured with pnpm@10.17.1 pack on Linux. Release state (prebuilt/ written by pin-handler-zip): 77,307 packed / 285,654 unpacked. CI state (no prebuilt/): 70,104 / 253,390. Packed is the check's 5% headroom over the release size. Unpacked is 292,000 (2.2% over release) because a 5% budget would put the CI pack under the 85% ratchet. * test(cli): anchor the docs link check in the lambda deploy fallback test * fix(cli): lambda deploy logs the verified handler sha256 and keeps a presigned query out of errors * fix(cli): a redirect-limit download error keeps a signed query out of its message * test(cli): the redirect-limit leak test types its rejection * docs(aws-lambda): header comments cut to what the code cannot say * chore(aws-lambda): size budget fits both the CI and the release package
199 lines
7.8 KiB
JavaScript
199 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
import { parse } from "yaml";
|
|
|
|
const workflow = readFileSync(new URL("../.github/workflows/publish.yml", import.meta.url), "utf8");
|
|
const config = parse(workflow);
|
|
const publish = config.jobs.publish;
|
|
const checkout = publish.steps.find((step) => step.uses?.startsWith("actions/checkout@"));
|
|
const checkoutGuard = publish.steps.find(
|
|
(step) => step.name === "Verify immutable release checkout",
|
|
);
|
|
const createReleaseTag = publish.steps.find((step) => step.name === "Create release tag");
|
|
|
|
const normalizeExpression = (expression) => expression.replace(/\s+/g, " ").trim();
|
|
|
|
function git(cwd, ...args) {
|
|
return execFileSync("git", args, {
|
|
cwd,
|
|
encoding: "utf8",
|
|
stdio: "pipe",
|
|
timeout: 5_000,
|
|
}).trim();
|
|
}
|
|
|
|
function runCreateReleaseTag(cwd, version) {
|
|
return spawnSync("bash", ["-euo", "pipefail", "-c", createReleaseTag.run], {
|
|
cwd,
|
|
encoding: "utf8",
|
|
env: { ...process.env, VERSION: version },
|
|
timeout: 5_000,
|
|
});
|
|
}
|
|
|
|
test("stable publishing has one reviewed immutable event path", () => {
|
|
assert.deepEqual(config.on.push.tags, ["v*-*"]);
|
|
assert.equal(config.on.workflow_dispatch, undefined);
|
|
assert.equal(
|
|
normalizeExpression(publish.if),
|
|
"github.event_name == 'push' || (github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v'))",
|
|
);
|
|
assert.equal(
|
|
normalizeExpression(publish.env.EXPECTED_RELEASE_SHA),
|
|
"${{ github.event_name == 'pull_request' && github.event.pull_request.merge_commit_sha || github.sha }}",
|
|
);
|
|
assert.equal(checkout.with.ref, "${{ env.EXPECTED_RELEASE_SHA }}");
|
|
});
|
|
|
|
test("the executable checkout guard cannot be conditionally disabled", () => {
|
|
assert.ok(checkoutGuard);
|
|
assert.equal(checkoutGuard.if, undefined);
|
|
assert.equal(checkoutGuard["continue-on-error"], undefined);
|
|
assert.equal(
|
|
checkoutGuard.run.trim(),
|
|
[
|
|
'ACTUAL_SHA="$(git rev-parse HEAD)"',
|
|
'EXPECTED_COMMIT_SHA="$(git rev-parse "${EXPECTED_RELEASE_SHA}^{commit}")"',
|
|
'if [ "$ACTUAL_SHA" != "$EXPECTED_COMMIT_SHA" ]; then',
|
|
' echo "::error::Expected release commit $EXPECTED_COMMIT_SHA, checked out $ACTUAL_SHA"',
|
|
" exit 1",
|
|
"fi",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("stable release tag recovery is idempotent and immutable", () => {
|
|
assert.ok(createReleaseTag);
|
|
assert.equal(createReleaseTag.if, "github.event_name == 'pull_request'");
|
|
assert.equal(
|
|
createReleaseTag.run.trim(),
|
|
[
|
|
'TAG="v$VERSION"',
|
|
'EXPECTED_TAG_SHA="$(git rev-parse HEAD)"',
|
|
"",
|
|
"verify_remote_tag() {",
|
|
' git fetch --force --no-tags origin "+refs/tags/$TAG:refs/tags/$TAG"',
|
|
' ACTUAL_TAG_SHA="$(git rev-parse "refs/tags/$TAG^{commit}")"',
|
|
' if [ "$ACTUAL_TAG_SHA" != "$EXPECTED_TAG_SHA" ]; then',
|
|
' echo "::error::Release tag $TAG points to $ACTUAL_TAG_SHA, expected $EXPECTED_TAG_SHA"',
|
|
" exit 1",
|
|
" fi",
|
|
' echo "Release tag $TAG already exists at the expected commit — skipping"',
|
|
"}",
|
|
"",
|
|
'if [ -n "$(git ls-remote --refs origin "refs/tags/$TAG")" ]; then',
|
|
" verify_remote_tag",
|
|
"else",
|
|
' git tag --no-sign "$TAG" "$EXPECTED_TAG_SHA"',
|
|
' if ! git push origin "refs/tags/$TAG"; then',
|
|
" # A concurrent retry may have created the tag after ls-remote.",
|
|
' git tag -d "$TAG"',
|
|
" verify_remote_tag",
|
|
" fi",
|
|
"fi",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("stable release tag creation survives retries and rejects a mismatched commit", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "hyperframes-release-tag-test-"));
|
|
const origin = join(root, "origin.git");
|
|
const checkout = join(root, "checkout");
|
|
|
|
try {
|
|
execFileSync("git", ["init", "--bare", origin], { stdio: "pipe", timeout: 5_000 });
|
|
execFileSync("git", ["init", checkout], { stdio: "pipe", timeout: 5_000 });
|
|
git(checkout, "config", "user.name", "HyperFrames Test");
|
|
git(checkout, "config", "user.email", "test@hyperframes.invalid");
|
|
git(checkout, "commit", "--allow-empty", "-m", "release commit");
|
|
git(checkout, "branch", "-M", "main");
|
|
git(checkout, "remote", "add", "origin", origin);
|
|
git(checkout, "push", "-u", "origin", "main");
|
|
|
|
const releaseSha = git(checkout, "rev-parse", "HEAD");
|
|
const firstRun = runCreateReleaseTag(checkout, "9.8.7");
|
|
assert.equal(firstRun.status, 0, `${firstRun.stdout}\n${firstRun.stderr}`);
|
|
assert.equal(git(checkout, "rev-parse", "refs/tags/v9.8.7^{commit}"), releaseSha);
|
|
|
|
const retry = runCreateReleaseTag(checkout, "9.8.7");
|
|
assert.equal(retry.status, 0, `${retry.stdout}\n${retry.stderr}`);
|
|
assert.match(retry.stdout, /already exists at the expected commit/);
|
|
|
|
git(checkout, "commit", "--allow-empty", "-m", "different commit");
|
|
const mismatch = runCreateReleaseTag(checkout, "9.8.7");
|
|
assert.equal(mismatch.status, 1, `${mismatch.stdout}\n${mismatch.stderr}`);
|
|
assert.match(mismatch.stdout, /points to .* expected/);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("the Lambda handler zip is pinned and attached to the release before npm publish", () => {
|
|
const names = publish.steps.map((step) => step.name);
|
|
const at = (name) => {
|
|
assert.ok(names.includes(name), `missing step ${name}`);
|
|
return names.indexOf(name);
|
|
};
|
|
assert.ok(at("Build Lambda handler zip") < at("Create GitHub Release"));
|
|
assert.ok(at("Create GitHub Release") < at("Attach Lambda handler zip to release"));
|
|
assert.ok(at("Attach Lambda handler zip to release") < at("Publish packages"));
|
|
const attach = publish.steps[at("Attach Lambda handler zip to release")];
|
|
assert.equal(attach.if, "steps.lambda_zip.outputs.attached != 'true'");
|
|
assert.doesNotMatch(attach.run, /--clobber/);
|
|
});
|
|
|
|
test("a retried release reuses the attached handler zip instead of building a new one", () => {
|
|
const step = publish.steps.find((s) => s.name === "Build Lambda handler zip");
|
|
const run = (ghExit) => {
|
|
const root = mkdtempSync(join(tmpdir(), "hyperframes-lambda-zip-step-"));
|
|
try {
|
|
const bin = join(root, "bin");
|
|
mkdirSync(bin);
|
|
const log = join(root, "calls.log");
|
|
for (const [name, exit] of [
|
|
["gh", ghExit],
|
|
["bun", 0],
|
|
]) {
|
|
writeFileSync(join(bin, name), `#!/bin/sh\necho "${name} $*" >> "${log}"\nexit ${exit}\n`);
|
|
chmodSync(join(bin, name), 0o755);
|
|
}
|
|
const output = join(root, "github-output");
|
|
writeFileSync(output, "");
|
|
const result = spawnSync("bash", ["-euo", "pipefail", "-c", step.run], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
PATH: `${bin}:${process.env.PATH}`,
|
|
GITHUB_OUTPUT: output,
|
|
VERSION: "9.8.7",
|
|
REPO: "o/r",
|
|
},
|
|
timeout: 5_000,
|
|
});
|
|
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
|
return { calls: readFileSync(log, "utf8"), output: readFileSync(output, "utf8") };
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
};
|
|
|
|
const retried = run(0);
|
|
assert.match(
|
|
retried.calls,
|
|
/^gh release download v9\.8\.7 --repo o\/r --pattern hyperframes-lambda-handler\.zip --dir dist$/m,
|
|
);
|
|
assert.doesNotMatch(retried.calls, /build:zip/);
|
|
assert.match(retried.calls, /^bun packages\/aws-lambda\/scripts\/pin-handler-zip\.ts$/m);
|
|
assert.equal(retried.output, "attached=true\n");
|
|
|
|
const fresh = run(1);
|
|
assert.match(fresh.calls, /^bun run --cwd packages\/aws-lambda build:zip$/m);
|
|
assert.match(fresh.calls, /^bun packages\/aws-lambda\/scripts\/pin-handler-zip\.ts$/m);
|
|
assert.equal(fresh.output, "");
|
|
});
|