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
140 changes: 138 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -680,14 +680,33 @@ jobs:
XGENY_INSTALL_DIR="$test_root/install" \
XGENY_STATE_HOME="$test_root/state" \
sh "$installer" --version "$RELEASE_TAG" >/dev/null
test "$("$test_root/install/${{ matrix.executable }}" --version)" = "xgeny ${RELEASE_TAG#v}"
"$test_root/install/${{ matrix.executable }}" licenses > "$test_root/licenses.txt"
installed="$test_root/install/${{ matrix.executable }}"
test "$("$installed" --version)" = "xgeny ${RELEASE_TAG#v}"
"$installed" licenses > "$test_root/licenses.txt"
grep -Fq 'XGENy CLI Third-Party License Notices' "$test_root/licenses.txt"
grep -Fq 'Copyright notices for The Rust Standard Library' "$test_root/licenses.txt"
grep -Fq '===== musl C runtime notices =====' "$test_root/licenses.txt"
grep -Fq '===== LLVM libunwind notices =====' "$test_root/licenses.txt"
test ! -e "$test_root/state"

printf '/status\n/exit\n' \
| HOME="$test_root/home" \
XGENY_STATE_HOME="$test_root/state" \
"$installed" > "$test_root/repl.txt"
grep -Fq 'XGENy Developer Preview' "$test_root/repl.txt"
grep -Fq 'status: idle' "$test_root/repl.txt"
grep -Fq 'bye' "$test_root/repl.txt"
test ! -e "$test_root/state"

HOME="$test_root/home" \
XGENY_INSTALL_DIR="$test_root/install" \
XGENY_STATE_HOME="$test_root/state" \
sh "$installer" --version "$RELEASE_TAG" >/dev/null
test -f "$installed" && test ! -L "$installed"
test "$("$installed" --version)" = "xgeny ${RELEASE_TAG#v}"
test "$(find "$test_root/install" -mindepth 1 -maxdepth 1 | wc -l)" -eq 1
test ! -e "$test_root/state"

if [[ "$VERIFY_LATEST" == "true" && "$RELEASE_TAG" != *-* ]]; then
latest_ok=false
latest_installer="$test_root/xgeny-installer-latest.sh"
Expand All @@ -710,6 +729,10 @@ jobs:
test ! -e "$test_root/state"
fi

rm -f "$installed"
test ! -e "$installed"
test "$(find "$test_root/install" -mindepth 1 -maxdepth 1 | wc -l)" -eq 0

- name: Install exact published release on Windows
if: runner.os == 'Windows'
env:
Expand Down Expand Up @@ -750,6 +773,36 @@ jobs:
if (Test-Path -LiteralPath $StateRoot) {
throw "published install created runtime state"
}
$ReplOutput = (@("/status", "/exit") | & $Installed | Out-String)
$ReplExitCode = $LASTEXITCODE
if (
$ReplExitCode -ne 0 -or
-not $ReplOutput.Contains("XGENy Developer Preview") -or
-not $ReplOutput.Contains("status: idle") -or
-not $ReplOutput.Contains("bye")
) {
throw "published interactive lifecycle smoke failed"
}
if (Test-Path -LiteralPath $StateRoot) {
throw "published interactive smoke created runtime state"
}
& $Installer -Version $Tag -InstallDir $InstallRoot | Out-Null
$InstallEntries = @(Get-ChildItem -Force -LiteralPath $InstallRoot)
if (
$InstallEntries.Count -ne 1 -or
$InstallEntries[0].Name -ne "${{ matrix.executable }}" -or
$InstallEntries[0].PSIsContainer -or
($InstallEntries[0].Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0
) {
throw "published reinstall left an unexpected install layout"
}
$ReinstalledVersion = (& $Installed --version | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or $ReinstalledVersion -ne "xgeny $($Tag.Substring(1))") {
throw "published reinstall produced the wrong version"
}
if (Test-Path -LiteralPath $StateRoot) {
throw "published reinstall created runtime state"
}
$LicenseOutput = (& $Installed licenses | Out-String)
if (
$LASTEXITCODE -ne 0 -or
Expand Down Expand Up @@ -785,6 +838,13 @@ jobs:
throw "latest install created runtime state"
}
}
Remove-Item -Force -LiteralPath $Installed
if (Test-Path -LiteralPath $Installed) {
throw "published binary removal failed"
}
if (@(Get-ChildItem -Force -LiteralPath $InstallRoot).Count -ne 0) {
throw "published binary removal left install artifacts"
}
} finally {
$env:XGENY_STATE_HOME = $PreviousState
$env:XGENY_DOWNLOAD_BASE_URL = $PreviousBase
Expand Down Expand Up @@ -970,6 +1030,35 @@ jobs:
grep -Fq 'XGENy CLI Third-Party License Notices' "$test_root/licenses.txt"
test ! -e "$test_root/state"

printf '/status\n/exit\n' \
| HOME="$test_root/home" \
XGENY_STATE_HOME="$test_root/state" \
"$test_root/install/bin/xgeny" > "$test_root/repl.txt"
grep -Fq 'XGENy Developer Preview' "$test_root/repl.txt"
grep -Fq 'status: idle' "$test_root/repl.txt"
grep -Fq 'bye' "$test_root/repl.txt"
test ! -e "$test_root/state"

HOME="$test_root/home" \
npm_config_cache="$test_root/cache" \
npm install --global --prefix "$test_root/install" \
--include=optional --ignore-scripts --no-audit --no-fund \
--registry=https://registry.npmjs.org/ \
"@xgen/cli@$version"
test -f "$global_root/@xgen/cli/package.json"
test -f "$global_root/@xgen/$PLATFORM_PACKAGE/package.json"
test "$(HOME="$test_root/home" XGENY_STATE_HOME="$test_root/state" \
"$test_root/install/bin/xgeny" --version)" = "xgeny $version"
test ! -e "$test_root/state"

HOME="$test_root/home" \
npm_config_cache="$test_root/cache" \
npm uninstall --global --prefix "$test_root/install" \
--ignore-scripts --no-audit --no-fund '@xgen/cli'
test ! -e "$global_root/@xgen/cli"
test ! -e "$global_root/@xgen/$PLATFORM_PACKAGE"
test ! -e "$test_root/install/bin/xgeny"

- name: Install and verify exact npm package on Windows
if: runner.os == 'Windows'
env:
Expand Down Expand Up @@ -1024,6 +1113,53 @@ jobs:
if (Test-Path -LiteralPath $StateRoot) {
throw "npm install or verification created runtime state"
}
$ReplOutput = (@("/status", "/exit") | & $Shim | Out-String)
$ReplExitCode = $LASTEXITCODE
if (
$ReplExitCode -ne 0 -or
-not $ReplOutput.Contains("XGENy Developer Preview") -or
-not $ReplOutput.Contains("status: idle") -or
-not $ReplOutput.Contains("bye")
) {
throw "npm-installed interactive lifecycle smoke failed"
}
if (Test-Path -LiteralPath $StateRoot) {
throw "npm-installed interactive smoke created runtime state"
}

& npm.cmd install --global --prefix $InstallRoot `
--include=optional --ignore-scripts --no-audit --no-fund `
--registry=https://registry.npmjs.org/ `
"@xgen/cli@$Version"
if ($LASTEXITCODE -ne 0) { throw "npm reinstall failed" }
$Reinstalled = (& $Shim --version | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or $Reinstalled -ne "xgeny $Version") {
throw "npm reinstall produced the wrong version"
}
if (
-not (Test-Path -LiteralPath (Join-Path $GlobalRoot "@xgen/cli/package.json")) -or
-not (Test-Path -LiteralPath (Join-Path $GlobalRoot "@xgen/$env:PLATFORM_PACKAGE/package.json"))
) {
throw "npm reinstall is missing an exact package"
}
if (Test-Path -LiteralPath $StateRoot) {
throw "npm reinstall created runtime state"
}

& npm.cmd uninstall --global --prefix $InstallRoot `
--ignore-scripts --no-audit --no-fund "@xgen/cli"
if ($LASTEXITCODE -ne 0) { throw "npm uninstall failed" }
if (
(Test-Path -LiteralPath (Join-Path $GlobalRoot "@xgen/cli")) -or
(Test-Path -LiteralPath (Join-Path $GlobalRoot "@xgen/$env:PLATFORM_PACKAGE"))
) {
throw "npm uninstall left package artifacts"
}
foreach ($ShimName in @("xgeny", "xgeny.cmd", "xgeny.ps1")) {
if (Test-Path -LiteralPath (Join-Path $InstallRoot $ShimName)) {
throw "npm uninstall left an executable shim"
}
}
} finally {
$env:HOME = $PreviousHome
$env:USERPROFILE = $PreviousUserProfile
Expand Down
34 changes: 30 additions & 4 deletions npm/scripts/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,6 @@ async function main() {
`${launcher.name}@${version}`,
]);
await run(install.command, install.args, { env: npmEnvironment });
server.close();
await once(server, 'close');
server = null;

assert.deepEqual(new Set(requestedTarballs), new Set([launcher.name, specification.packageName]));
const root = npmInvocation(['root', '--global', '--prefix', installRoot]);
Expand Down Expand Up @@ -251,8 +248,37 @@ async function main() {
assert.match(interactiveResult.stdout, /status: idle/);
assert.match(interactiveResult.stdout, /bye/);
await assert.rejects(lstat(state), (error) => error.code === 'ENOENT');

await run(install.command, install.args, { env: npmEnvironment });
assert.equal(JSON.parse(await readFile(installedLauncher, 'utf8')).version, version);
assert.equal(JSON.parse(await readFile(installedPlatform, 'utf8')).version, version);
await assert.rejects(lstat(state), (error) => error.code === 'ENOENT');

server.close();
await once(server, 'close');
server = null;

const uninstall = npmInvocation([
'uninstall',
'--global',
'--prefix',
installRoot,
'--ignore-scripts',
'--no-audit',
'--no-fund',
launcher.name,
]);
await run(uninstall.command, uninstall.args, { env: npmEnvironment });
await assert.rejects(lstat(installedLauncher), (error) => error.code === 'ENOENT');
await assert.rejects(lstat(installedPlatform), (error) => error.code === 'ENOENT');
const installedShims = process.platform === 'win32'
? ['xgeny', 'xgeny.cmd', 'xgeny.ps1'].map((name) => path.join(installRoot, name))
: [path.join(installRoot, 'bin', 'xgeny')];
for (const installedShim of installedShims) {
await assert.rejects(lstat(installedShim), (error) => error.code === 'ENOENT');
}
console.log(
`npm global install smoke: PASS (${launcher.name} -> ${specification.packageName} ${version})`,
`npm install/reinstall/remove smoke: PASS (${launcher.name} -> ${specification.packageName} ${version})`,
);
} finally {
if (server) {
Expand Down
25 changes: 24 additions & 1 deletion scripts/check-npm-distribution-workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ if [ "$#" -gt 1 ]; then
fi
workflow=${1:-"$repo_root/.github/workflows/release.yml"}

python3 - "$workflow" "$repo_root/npm/scripts/publish.mjs" <<'PY'
python3 - \
"$workflow" \
"$repo_root/npm/scripts/publish.mjs" \
"$repo_root/npm/scripts/smoke.mjs" <<'PY'
from pathlib import Path
import re
import sys

workflow = Path(sys.argv[1])
publisher = Path(sys.argv[2])
smoke = Path(sys.argv[3])
lines = workflow.read_text(encoding="utf-8").splitlines()
text = "\n".join(lines)
publisher_text = publisher.read_text(encoding="utf-8")
smoke_text = smoke.read_text(encoding="utf-8")


def fail(message: str) -> None:
Expand Down Expand Up @@ -86,6 +91,11 @@ for fragment in (
"--include=optional --ignore-scripts",
'"@xgen/cli@$version"',
'"@xgen/cli@$Version"',
"printf '/status\\n/exit\\n'",
"status: idle",
'npm uninstall --global --prefix "$test_root/install"',
"& npm.cmd uninstall --global --prefix $InstallRoot",
"npm uninstall left package artifacts",
):
if fragment not in verify_job:
fail(f"published npm verification is missing: {fragment}")
Expand All @@ -103,5 +113,18 @@ for fragment in (
if fragment not in publisher_text:
fail(f"npm publisher is missing required safety contract: {fragment}")

for fragment in (
"const uninstall = npmInvocation([",
"await assert.rejects(lstat(installedLauncher)",
"await assert.rejects(lstat(installedPlatform)",
"npm install/reinstall/remove smoke: PASS",
):
if fragment not in smoke_text:
fail(f"npm package smoke is missing lifecycle verification: {fragment}")

install_invocation = "await run(install.command, install.args, { env: npmEnvironment });"
if smoke_text.count(install_invocation) != 2:
fail("npm package smoke must install the exact package twice")

print("npm release workflow contract: PASS")
PY
29 changes: 29 additions & 0 deletions scripts/check-release-workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,34 @@ if actual_invocation != expected_invocation:
"the classified release flags"
)

verify_starts = [index for index, line in enumerate(lines) if line == " verify-published:"]
if len(verify_starts) != 1:
raise SystemExit(
f"{workflow}: expected one verify-published job, found {len(verify_starts)}"
)
verify_start = verify_starts[0]
verify_end = next(
(
index
for index in range(verify_start + 1, len(lines))
if lines[index] == " publish-npm:"
),
len(lines),
)
verify_job = "\n".join(lines[verify_start:verify_end])
for fragment in (
"printf '/status\\n/exit\\n'",
"status: idle",
'sh "$installer" --version "$RELEASE_TAG"',
'rm -f "$installed"',
'$ReplOutput = (@("/status", "/exit")',
"published reinstall produced the wrong version",
"Remove-Item -Force -LiteralPath $Installed",
):
if fragment not in verify_job:
raise SystemExit(
f"{workflow}: published native lifecycle verification is missing: {fragment}"
)

print("release publisher context contract: PASS")
PY