Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions scripts/deno_canary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# CANARY-BRIDGE pin — temporary opt-in to a Deno canary commit.
#
# WHY THIS EXISTS
# Deno v2.7.12–v2.7.14 panic on `Option::unwrap()` of a None ArrayBuffer
# in ext/node/ops/tls_wrap.rs (denoland/deno#33713). The fix landed on
# main in denoland/deno#33737 (commit df8d21c2) and will ship in v2.8.0
# in ~2 weeks. This pin bridges to that release without forcing every
# user onto a broken stable.
#
# HOW IT WORKS
# The first non-blank, non-`#`-comment line below is treated as the
# Deno canary commit SHA. scripts/download_deno.ts reads it (via
# readCanarySha()), downloads from dl.deno.land/canary/<sha>/, and
# stamps version.txt as `canary-<short-sha>`. Setting the env var
# DENO_CANARY_SHA overrides this file (CI ad-hoc testing).
#
# BACK-OUT CHECKLIST (when v2.8.0 ships)
# 1. Delete this file (scripts/deno_canary.txt).
# 2. In scripts/download_deno.ts, delete the two `CANARY-BRIDGE`
# blocks (the helpers near the top and the branch in main()).
# 3. Delete scripts/download_deno_test.ts (or its canary cases).
# 4. `grep -rn CANARY-BRIDGE` should return zero matches.
# 5. Bump the system Deno used by CI / contributors to v2.8.0.
# 6. Rebuild and republish swamp via the normal release flow.
#
# Pinned commit (verified to include the tls_wrap fix df8d21c2):
19bd3d8b99d92f15d20692aca02ac059bbc9ada7
90 changes: 83 additions & 7 deletions scripts/download_deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,77 @@ const TARGET_ARTIFACT_MAP: Record<string, string> = {
"x86_64-pc-windows-msvc": "deno-x86_64-pc-windows-msvc.zip",
};

// CANARY-BRIDGE: temporary opt-in for shipping a pinned Deno canary commit
// while waiting for an upstream stable release. Remove this entire block
// (and the call site in main(), and scripts/deno_canary.txt) when the
// targeted stable release ships. See scripts/deno_canary.txt for the
// full back-out checklist.
const CANARY_PIN_FILE = "deno_canary.txt";

/**
* Returns the canary commit SHA to download, or null for stable mode.
*
* Priority:
* 1. `DENO_CANARY_SHA` env var (CI ad-hoc override).
* 2. `scripts/deno_canary.txt` (committed pin — first non-blank,
* non-`#`-comment line).
* 3. null — fall through to the stable GitHub-releases path.
*/
async function readCanarySha(): Promise<string | null> {
const fromEnv = Deno.env.get("DENO_CANARY_SHA")?.trim();
if (fromEnv) return fromEnv;

const pinPath = join(import.meta.dirname ?? ".", CANARY_PIN_FILE);
let content: string;
try {
content = await Deno.readTextFile(pinPath);
} catch (err) {
if (err instanceof Deno.errors.NotFound) return null;
throw err;
}
for (const raw of content.split("\n")) {
const line = raw.trim();
if (!line || line.startsWith("#")) continue;
return line;
}
return null;
}

export interface DownloadPlan {
url: string;
versionLabel: string;
channel: "stable" | "canary";
}

/**
* Builds the download URL and the string written to `version.txt`.
*
* Stable channel uses the GitHub releases artifact path; canary uses
* `dl.deno.land/canary/<sha>/`. The `versionLabel` distinguishes canary
* builds (`canary-<short-sha>`) so the runtime's version-marker check
* forces a fresh extraction on every SHA bump.
*/
export function buildDownloadPlan(
channel: "stable" | "canary",
versionOrSha: string,
artifact: string,
): DownloadPlan {
if (channel === "canary") {
return {
url: `https://dl.deno.land/canary/${versionOrSha}/${artifact}`,
versionLabel: `canary-${versionOrSha.slice(0, 8)}`,
channel,
};
}
return {
url:
`https://github.com/denoland/deno/releases/download/v${versionOrSha}/${artifact}`,
versionLabel: versionOrSha,
channel,
};
}
// END CANARY-BRIDGE

/** Maps Deno.build.os + Deno.build.arch to a target triple. */
function detectCurrentTarget(): string {
const os = Deno.build.os;
Expand Down Expand Up @@ -129,17 +200,22 @@ async function main() {
Deno.exit(1);
}

const version = await getDenoVersion();
const isWindows = target.includes("windows");
const binaryName = isWindows ? "deno.exe" : "deno";

console.log(`Deno version: ${version}`);
// CANARY-BRIDGE: pick channel from deno_canary.txt / DENO_CANARY_SHA
// (returns null in stable mode). Remove this branch when the bridge ends.
const canarySha = await readCanarySha();
const plan = canarySha
? buildDownloadPlan("canary", canarySha, artifact)
: buildDownloadPlan("stable", await getDenoVersion(), artifact);

console.log(`Channel: ${plan.channel}`);
console.log(`Target: ${target}`);
console.log(`Artifact: ${artifact}`);
console.log(`Source: ${plan.url}`);

const url =
`https://github.com/denoland/deno/releases/download/v${version}/${artifact}`;
const zipBytes = await downloadFile(url);
const zipBytes = await downloadFile(plan.url);

console.log(`Downloaded ${zipBytes.length} bytes, extracting...`);
const binaryBytes = await extractDenoFromZip(zipBytes, binaryName);
Expand All @@ -163,10 +239,10 @@ async function main() {

// Write version file
const versionPath = join(outputDir, "version.txt");
await Deno.writeTextFile(versionPath, version);
await Deno.writeTextFile(versionPath, plan.versionLabel);

const sizeMB = (binaryBytes.length / 1024 / 1024).toFixed(1);
console.log(`Wrote deno ${version} (${sizeMB} MB) to ${outputPath}`);
console.log(`Wrote deno ${plan.versionLabel} (${sizeMB} MB) to ${outputPath}`);
console.log(`Wrote version to ${versionPath}`);
}

Expand Down
88 changes: 88 additions & 0 deletions scripts/download_deno_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Swamp, an Automation Framework
// Copyright (C) 2026 System Initiative, Inc.
//
// This file is part of Swamp.
//
// Swamp is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3
// as published by the Free Software Foundation, with the Swamp
// Extension and Definition Exception (found in the "COPYING-EXCEPTION"
// file).
//
// Swamp is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Swamp. If not, see <https://www.gnu.org/licenses/>.

import { assertEquals } from "@std/assert";
import { buildDownloadPlan } from "./download_deno.ts";

// CANARY-BRIDGE: these tests cover the canary opt-in. When the bridge
// ends, delete this file along with the helpers in download_deno.ts.
//
// `scripts/` is excluded from `deno test` in deno.json, so this file
// does not run in CI by default. To run it manually:
//
// cp scripts/download_deno*.ts /tmp/ \
// && deno test --no-check --config deno.json /tmp/download_deno_test.ts
//
// The smoke test (compile + run TLS probe against the bundled deno) is
// the load-bearing validation; this file documents the URL-builder
// contract and acts as a fast pre-build sanity check.

Deno.test("buildDownloadPlan: stable channel uses GitHub releases URL", () => {
const plan = buildDownloadPlan(
"stable",
"2.7.14",
"deno-x86_64-apple-darwin.zip",
);
assertEquals(plan.channel, "stable");
assertEquals(
plan.url,
"https://github.com/denoland/deno/releases/download/v2.7.14/deno-x86_64-apple-darwin.zip",
);
assertEquals(plan.versionLabel, "2.7.14");
});

Deno.test("buildDownloadPlan: canary channel uses dl.deno.land URL", () => {
const sha = "19bd3d8b99d92f15d20692aca02ac059bbc9ada7";
const plan = buildDownloadPlan(
"canary",
sha,
"deno-aarch64-apple-darwin.zip",
);
assertEquals(plan.channel, "canary");
assertEquals(
plan.url,
`https://dl.deno.land/canary/${sha}/deno-aarch64-apple-darwin.zip`,
);
});

Deno.test("buildDownloadPlan: canary versionLabel uses 8-char short sha", () => {
const plan = buildDownloadPlan(
"canary",
"19bd3d8b99d92f15d20692aca02ac059bbc9ada7",
"deno-x86_64-unknown-linux-gnu.zip",
);
assertEquals(plan.versionLabel, "canary-19bd3d8b");
});

Deno.test("buildDownloadPlan: canary label distinct from any stable semver", () => {
// The runtime compares version markers as exact strings, so canary labels
// must not collide with semver — guards against stale-cache issues when
// bridging back to stable.
const canary = buildDownloadPlan(
"canary",
"19bd3d8b99d92f15d20692aca02ac059bbc9ada7",
"deno-x86_64-apple-darwin.zip",
);
const stable = buildDownloadPlan(
"stable",
"2.8.0",
"deno-x86_64-apple-darwin.zip",
);
assertEquals(canary.versionLabel === stable.versionLabel, false);
});
Loading