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
92 changes: 46 additions & 46 deletions features/uv/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,90 @@
"components": {
"uv": {
"versions": {
"0.13.1": {
"0.13.7": {
"linux-x64": {
"url": "https://github.com/astral-sh/uv/releases/download/0.13.1/uv-x86_64-unknown-linux-musl.tar.gz",
"sha256": "6c86040a8f045ab9adc9e3ec4cdafa03fd00e9a988ed2bf28971c7fc2d0debd0"
"url": "https://github.com/astral-sh/uv/releases/download/0.13.7/uv-x86_64-unknown-linux-musl.tar.gz",
"sha256": "287491da03f2a785c70fa75b13e1f849918c2f2b5415fe84876b8214eeb792f9"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/uv/releases/download/0.13.1/uv-aarch64-unknown-linux-musl.tar.gz",
"sha256": "345a8308e4d1835cdfaadf692836fd3137a6d45864773f05ad7bf59442b1ba16"
"url": "https://github.com/astral-sh/uv/releases/download/0.13.7/uv-aarch64-unknown-linux-musl.tar.gz",
"sha256": "2d3ec076a0c4c0e8250bb6418c44080828b544f1a3be0b6e46adf9fb6923de45"
}
}
},
"aliases": {
"0.13.1": ["0.13", "latest"]
"0.13.7": ["0.13", "latest"]
}
},
"python": {
"versions": {
"3.11.17-20261013": {
"pythonVersion": "3.11.17",
"build": "20261013",
"3.11.18-20261026": {
"pythonVersion": "3.11.18",
"build": "20261026",
"linux-x64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.11.17%2B20261013-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "3b8e5a248bcc351265ffe68f5e457668a2e09c99a47f81f658988c7d3d107522"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.11.18%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "17c4978a36c496dc53c08899ef8dad1ad2734f6325f08d6033a1e66419e0af26"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.11.17%2B20261013-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "3d09fa150bebc6c4f888f8d601328b46fd17f1be5c62cd390a353b7ccff6ed51"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.11.18%2B20261026-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "ecf28575cd67fa7a53c7bf2355bae5cff2c496efeb51780d50a8b54441cdce74"
}
},
"3.12.15-20261013": {
"pythonVersion": "3.12.15",
"build": "20261013",
"3.12.16-20261026": {
"pythonVersion": "3.12.16",
"build": "20261026",
"linux-x64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.12.15%2B20261013-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "178d3d83b6e8e99285a65d3fce9c16b87952cc5efdb4d1a0e3ff3be18ff0fc9c"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.12.16%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "ce4f6d0a6cfdb3bed6e44c2f9affd85943c76a42e25ad3a259002f31d6fb8a20"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.12.15%2B20261013-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "9467a99bacc7d90b64b02de7fa9ecfc99b72905e64e74f7ff6b460ad95cfed5a"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.12.16%2B20261026-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "bb4b6392670c5d3f93876996fcaa19858a4a413a0c7720144c18b0d6f1f0db2d"
}
},
"3.13.16-20261013": {
"pythonVersion": "3.13.16",
"build": "20261013",
"3.13.18-20261026": {
"pythonVersion": "3.13.18",
"build": "20261026",
"linux-x64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.13.16%2B20261013-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "d0080766b6b74aeb81e1949ef0d9a20e6c17f7b160fb295a69b936ccb17660be"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "df15fe60d3ff55bf9dcf25824b5572756bb8816ff93865a273d3301d7066a8b7"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.13.16%2B20261013-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "69a30ed26198178732c37b8de0b6b5191479fbacfe526954da776befe38f18e7"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "578d9da1983463ffb0c8c98ab0da2f18177f44069328eb0af847860c1eb05c3e"
}
},
"3.14.8-20261013": {
"pythonVersion": "3.14.8",
"build": "20261013",
"3.14.10-20261026": {
"pythonVersion": "3.14.10",
"build": "20261026",
"linux-x64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.14.8%2B20261013-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "d8faa1b588506b9e3e5a3f57807bb056d534a1641a6b10d15b6ea3f0b413b960"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.14.10%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "4d3c16c59ed0802a3d1d34d8bce504b15ceeaff39d62dadecc6a9fa803412aed"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.14.8%2B20261013-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "d8ae14f303d3971431184cf423ae5467202de2bc19bf6491a490250b87647d4b"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.14.10%2B20261026-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "90f48337bc3dd4ae6eda81e001e4c981236d403b6b5a07778bbdf94ee423dc00"
}
},
"3.15.0-20261013": {
"pythonVersion": "3.15.0",
"build": "20261013",
"3.15.2-20261026": {
"pythonVersion": "3.15.2",
"build": "20261026",
"linux-x64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.15.0%2B20261013-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "69740dcd9b3480b3d53760f2d3b75582adb14fa54434628dea32480ddede542a"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.15.2%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "7b03163a980c607ba8194709d4a4086735a7a9c488cc475b098157306927b36b"
},
"linux-arm64": {
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261013/cpython-3.15.0%2B20261013-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "064778ca701efc4c8366d0b235a2a0f5dbe14d921aeb2ef4f5c7b93e5a75cbd0"
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.15.2%2B20261026-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz",
"sha256": "0f23351c03b39beb880de668976074fcc3c082d9c3b03d0b43185632b2b270a0"
}
}
},
"aliases": {
"3.11.17-20261013": ["3.11", "3.11.17"],
"3.12.15-20261013": ["3.12", "3.12.15"],
"3.13.16-20261013": ["3.13", "3.13.16"],
"3.14.8-20261013": ["3.14", "3.14.8"],
"3.15.0-20261013": ["3.15", "3.15.0", "latest"]
"3.11.18-20261026": ["3.11", "3.11.18"],
"3.12.16-20261026": ["3.12", "3.12.16"],
"3.13.18-20261026": ["3.13", "3.13.18"],
"3.14.10-20261026": ["3.14", "3.14.10"],
"3.15.2-20261026": ["3.15", "3.15.2", "latest"]
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions scripts/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { csharpierCheck } from "@/scripts/tasks/csharpier.js";
import { spellCheck } from "@/scripts/tasks/cspell.js";
import { dotnetFormatCheck } from "@/scripts/tasks/dotnetFormat.js";
import { eslint } from "@/scripts/tasks/eslint.js";
import { featureConfigs } from "@/scripts/tasks/featureConfigs.js";
import { imageFeatures } from "@/scripts/tasks/imageFeatures.js";
import { prettierCheck } from "@/scripts/tasks/prettier.js";
import { syncpackLint } from "@/scripts/tasks/syncpack.js";
Expand All @@ -13,5 +14,6 @@ await eslint();
await prettierCheck();
await dotnetFormatCheck();
await csharpierCheck();
await featureConfigs.check();
await imageFeatures.check();
await spellCheck();
2 changes: 2 additions & 0 deletions scripts/tasks/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import prettierOptions from "@/.config/prettier/.prettierrc.json" with { type: "
import { getRemoteInfo } from "@/scripts/git.js";
import { projectRoot } from "@/scripts/project.js";
import { shellOptions } from "@/scripts/shell.js";
import { featureConfigs } from "@/scripts/tasks/featureConfigs.js";
import { imageFeatures } from "@/scripts/tasks/imageFeatures.js";

const formatFile = async (path: string) => {
Expand Down Expand Up @@ -56,5 +57,6 @@ export const build = async () => {
await rm(tempPath, { recursive: true, force: true });
}

await featureConfigs.write();
await imageFeatures.write();
};
28 changes: 28 additions & 0 deletions scripts/tasks/featureConfigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { readFile, writeFile } from "node:fs/promises";

import { formatConfig } from "@/scripts/tasks/features/config.js";
import { features } from "@/scripts/tasks/features/features.js";

const formatted = async (configPath: string): Promise<{ actual: string; expected: string }> => {
const actual = await readFile(configPath, "utf-8");
return { actual, expected: await formatConfig(JSON.parse(actual), configPath) };
};

const write = async (): Promise<void> => {
for (const feature of Object.values(features)) {
const { expected } = await formatted(feature.configPath);
await writeFile(feature.configPath, expected);
console.log(`feature-configs: wrote ${feature.configPath}`);
}
};

const check = async (): Promise<void> => {
for (const feature of Object.values(features)) {
const { actual, expected } = await formatted(feature.configPath);
if (actual !== expected) {
throw new Error(`${feature.configPath} is not canonically formatted; run pnpm build.`);
}
}
};

export const featureConfigs = { write, check } as const;
7 changes: 7 additions & 0 deletions scripts/tasks/features/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as prettier from "prettier";

import prettierOptions from "@/.config/prettier/.prettierrc.json" with { type: "json" };

export const tagPattern = /^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$/u;

// The names a key derives before any channel suffix; tagNames takes the first as canonical.
Expand Down Expand Up @@ -96,3 +100,6 @@ export const checkTagNamespace = (namespace: TagNamespace): void => {
}
}
};

export const formatConfig = async (document: unknown, configPath: string): Promise<string> =>
prettier.format(JSON.stringify(document, null, 2), { ...prettierOptions, filepath: configPath });
9 changes: 2 additions & 7 deletions scripts/tasks/features/dotnet/generateConfig.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";

import * as prettier from "prettier";
import { z } from "zod";

import prettierOptions from "@/.config/prettier/.prettierrc.json" with { type: "json" };
import { projectRoot } from "@/scripts/project.js";
import { checkTagNamespace } from "@/scripts/tasks/features/config.js";
import { checkTagNamespace, formatConfig } from "@/scripts/tasks/features/config.js";

const releasesIndexUrl = "https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json";
export const configPath = path.resolve(projectRoot, "features/dotnet/config.json");
Expand Down Expand Up @@ -245,8 +243,5 @@ export const generateConfig = async (): Promise<void> => {
},
};
validateConfig(config, configPath);
await writeFile(
configPath,
await prettier.format(JSON.stringify(config), { ...prettierOptions, filepath: configPath }),
);
await writeFile(configPath, await formatConfig(config, configPath));
};
9 changes: 2 additions & 7 deletions scripts/tasks/features/ripgrep/generateConfig.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";

import * as prettier from "prettier";
import { z } from "zod";

import prettierOptions from "@/.config/prettier/.prettierrc.json" with { type: "json" };
import { projectRoot } from "@/scripts/project.js";
import { checkTagNamespace } from "@/scripts/tasks/features/config.js";
import { checkTagNamespace, formatConfig } from "@/scripts/tasks/features/config.js";

const releasesUrl = "https://api.github.com/repos/BurntSushi/ripgrep/releases";
export const configPath = path.resolve(projectRoot, "features/ripgrep/config.json");
Expand Down Expand Up @@ -178,8 +176,5 @@ export const generateConfig = async (): Promise<void> => {
aliases: { [candidate.tag]: versionAliases(candidate) },
};
validateConfig(config, configPath);
await writeFile(
configPath,
await prettier.format(JSON.stringify(config), { ...prettierOptions, filepath: configPath }),
);
await writeFile(configPath, await formatConfig(config, configPath));
};
9 changes: 2 additions & 7 deletions scripts/tasks/features/uv/generateConfig.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";

import * as prettier from "prettier";
import { z } from "zod";

import prettierOptions from "@/.config/prettier/.prettierrc.json" with { type: "json" };
import { projectRoot } from "@/scripts/project.js";
import { checkTagNamespace } from "@/scripts/tasks/features/config.js";
import { checkTagNamespace, formatConfig } from "@/scripts/tasks/features/config.js";

import type { DockerArch, Rid } from "../architecture.js";
import { dockerArchOptions, ridFor, rids } from "../architecture.js";
Expand Down Expand Up @@ -274,8 +272,5 @@ export const generateConfig = async (): Promise<void> => {
},
};
validateConfig(config, configPath);
await writeFile(
configPath,
await prettier.format(JSON.stringify(config), { ...prettierOptions, filepath: configPath }),
);
await writeFile(configPath, await formatConfig(config, configPath));
};
21 changes: 16 additions & 5 deletions scripts/verify/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,37 @@ import type { CommandResult } from "./lifecycle.js";
export interface Expectation {
stdout?: readonly string[];
sorted?: boolean;
fields?: boolean;
exitCode?: number;
}

const linesOf = (text: string): string[] => (text.trim() === "" ? [] : text.trimEnd().split("\n"));

const sameLines = (actual: readonly string[], expected: readonly string[]): boolean =>
actual.length === expected.length && actual.every((line, index) => line === expected[index]);
const fieldsOf = (line: string): string[] => (line.trim() === "" ? [] : line.trim().split(/\s+/u));

const lineOf = (line: string, fields: boolean): string => (fields ? fieldsOf(line).join(" ") : line);

const sameLines = (actual: readonly string[], expected: readonly string[], fields: boolean): boolean =>
actual.length === expected.length &&
actual.every((line, index) => lineOf(line, fields) === lineOf(expected[index], fields));

const describe = (lines: readonly string[], fields: boolean): string =>
JSON.stringify(lines.map((line) => lineOf(line, fields)));

export const assertExec = (result: CommandResult, expectation: Expectation): void => {
const stdout = linesOf(result.stdout);
const sorted = expectation.sorted === true;
const order = (lines: readonly string[]): string[] => (sorted ? [...lines].sort() : [...lines]);
const fields = expectation.fields === true;
const order = (lines: readonly string[]): string[] =>
sorted ? lines.map((line) => lineOf(line, fields)).sort() : [...lines];
const exitCode = expectation.exitCode ?? 0;

const problems: string[] = [];
if (result.exitCode !== exitCode) {
problems.push(`exit code ${result.exitCode ?? "none"}, expected ${exitCode}`);
}
if (expectation.stdout !== undefined && !sameLines(order(stdout), order(expectation.stdout))) {
problems.push(`stdout ${JSON.stringify(stdout)}, expected ${JSON.stringify(expectation.stdout)}`);
if (expectation.stdout !== undefined && !sameLines(order(stdout), order(expectation.stdout), fields)) {
problems.push(`stdout ${describe(stdout, fields)}, expected ${describe(expectation.stdout, fields)}`);
}
if (result.stderr.trim() !== "") {
problems.push(`stderr ${JSON.stringify(result.stderr)}`);
Expand Down
12 changes: 6 additions & 6 deletions scripts/verify/uv/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const pythonExecutable = (selection: Selection, pythonVersion: string): string =
`${pythonPatchDir(selection, pythonVersion)}/bin/python${minorOf(pythonVersion)}`;

const pythonListLine = (selection: Selection, pythonVersion: string): string =>
`${pythonKey(selection, pythonVersion)} ${pythonMinorDir(selection, pythonVersion)}/bin/python${minorOf(pythonVersion)}`;
`${pythonKey(selection, pythonVersion)} ${pythonMinorDir(selection, pythonVersion)}/bin/python${minorOf(pythonVersion)}`;

const linkTarget = (name: string, target: string): Exec => ({
command: ["sh", "-c", `readlink -f "$XDG_BIN_HOME/${name}"`],
Expand Down Expand Up @@ -126,7 +126,7 @@ const pythonExecs = (selection: Selection): Exec[] => {
{ command: ["cat", `${featureDir}/default-python`], expect: { stdout: [pythonVersion] } },
{
command: ["uv", "python", "list", "--only-installed"],
expect: { stdout: [pythonListLine(selection, pythonVersion)] },
expect: { stdout: [pythonListLine(selection, pythonVersion)], fields: true },
},
linkTarget("python", pythonExecutable(selection, pythonVersion)),
linkTarget("python3", pythonExecutable(selection, pythonVersion)),
Expand All @@ -147,7 +147,7 @@ const pythonRestartExecs = (selection: Selection): Exec[] => {
{ command: ["cat", `${featureDir}/default-python`], expect: { stdout: [pythonVersion] } },
{
command: ["uv", "python", "list", "--only-installed"],
expect: { stdout: [pythonListLine(selection, pythonVersion)] },
expect: { stdout: [pythonListLine(selection, pythonVersion)], fields: true },
},
linkTarget("python", pythonExecutable(selection, pythonVersion)),
linkTarget("python3", pythonExecutable(selection, pythonVersion)),
Expand All @@ -162,7 +162,7 @@ const multiExecs = (selection: Selection): Exec[] => {
return [
{
command: ["uv", "python", "list", "--only-installed"],
expect: { stdout: [pythonListLine(selection, latest), pythonListLine(selection, previous)] },
expect: { stdout: [pythonListLine(selection, latest), pythonListLine(selection, previous)], fields: true },
},
linkTarget(`python${minorOf(latest)}`, pythonExecutable(selection, latest)),
linkTarget(`python${minorOf(previous)}`, pythonExecutable(selection, previous)),
Expand All @@ -178,7 +178,7 @@ const downloadExecs = (selection: Selection): Exec[] => {
return [
{
command: ["uv", "python", "list", "--only-installed"],
expect: { stdout: [pythonListLine(selection, pythonVersion)] },
expect: { stdout: [pythonListLine(selection, pythonVersion)], fields: true },
},
linkTarget("python", pythonExecutable(selection, pythonVersion)),
linkTarget("python3", pythonExecutable(selection, pythonVersion)),
Expand All @@ -201,7 +201,7 @@ const smokeExecs = (selection: Selection, component: Component): Exec[] => {
{ command: ["sh", "-c", '"$XDG_BIN_HOME/python" -V'], expect: { stdout: [`Python ${pythonVersion}`] } },
{
command: ["uv", "python", "list", "--only-installed"],
expect: { stdout: [pythonListLine(selection, pythonVersion)] },
expect: { stdout: [pythonListLine(selection, pythonVersion)], fields: true },
},
);
}
Expand Down
Loading