icoa-cli 2.19.616 → 2.19.617

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import chalk from"chalk";import{execSync as o}from"node:child_process";import{createWriteStream as t,existsSync as e,mkdirSync as n,mkdtempSync as r,renameSync as s,rmSync as i}from"node:fs";import{homedir as a}from"node:os";import{join as c}from"node:path";import{Readable as l}from"node:stream";import{pipeline as h}from"node:stream/promises";import{icoaStandalonePython as d,parsePyVersion as p,REQUIRED_PY as y,resolveHostPython as u,resolveRunPython as m}from"./aienv.js";import{httpFetch as f}from"./http.js";function g(t){try{return o(`${t} --version 2>&1`,{encoding:"utf-8",timeout:4e3}).trim()}catch{return null}}let $,w;export function probePython(o=!1,t={}){const e=t.hostOnly?w:$;if(e&&!o)return e;const n=a(),r=t.hostOnly?(()=>{const o=u(g,process.platform,n);return o?{...o,source:"host"}:null})():m(g,process.platform,n);if(r){const o=(g(r.cmd.includes(" ")?r.cmd:`"${r.cmd}"`)??"").replace(/^Python\s+/i,"")||`${r.version.major}.${r.version.minor}`,e=d(n,process.platform),s="aienv"===r.source?"aienv":r.cmd===e?"icoa":"system",i={ok:!0,version:o,cmd:r.cmd,source:s};return t.hostOnly?w=i:$=i,i}let s;for(const o of["python3","python"]){const t=g(o);if(p(t)){s=(t??"").replace(/^Python\s+/i,"").trim();break}}const i={ok:!1,otherVersion:s};return t.hostOnly?w=i:$=i,i}export function resetPythonProbe(){$=void 0,w=void 0}const P=`${y.major}.${y.minor}`;export function pythonDoorLine(o,t=!1,e){const n=e??probePython(),r=t?"":"🐍 ";if(n.ok){const t="aienv"===n.source?o?"aienv 环境":"aienv venv":"icoa"===n.source?o?"icoa 装的":"installed by icoa":o?"系统自带":"system";return chalk.gray(` ${r}Python ${n.version} · ${t} · `)+chalk.gray(o?"本机可跑代码":"code runs locally")}return n.otherVersion?chalk.gray(` ${r}Python ${n.otherVersion} — `)+chalk.gray(o?`课程按 ${P} 校准 · `:`courses are calibrated on ${P} · `)+chalk.bold.green("python setup")+chalk.gray(o?` 装并存的 ${P}(免 sudo,不动系统 python3)`:" adds one side-by-side (no sudo, system python3 untouched)"):chalk.gray(` ${r}`+(o?"没有 Python — 读卡答题不需要它 · ":"No Python — reading and answering cards works without it · "))+chalk.bold.green("python setup")+chalk.gray(o?" 让 /py 里能跑代码":" when you want to run code in /py")}export function selectionSetupLine(o,t=!1,e){const n=e??probePython(!1,{hostOnly:!0}),r=t?"":"🐍 ",s=chalk.gray(" (Paper C · SIMOC are MCQ-only — neither is needed)");return n.ok?o?chalk.gray(` ${r}Python ${n.version} · `)+chalk.green("exam toolkit ready"):chalk.gray(` ${r}Python ${n.version} · toolkit not installed · `)+chalk.bold.green("exam setup")+chalk.gray(" (13 packages, ~150 MB)")+s:chalk.gray(` ${r}`+(n.otherVersion?`Python ${n.otherVersion} — the exam runs on ${P} · `:`No Python ${P} yet · `))+chalk.bold.green("exam setup")+chalk.gray(" installs it (~20 s) plus the toolkit (~150 MB)")+s}export function printNoPython312(o,t={}){const e=probePython();console.log(chalk.yellow(o?e.otherVersion?` 这台机器只有 Python ${e.otherVersion} — 课程和判分机都在 ${P} 线上。`:` 找不到 Python ${P} — 不是你的代码有问题。`:e.otherVersion?` This machine has Python ${e.otherVersion} only — courses and the judge both run on the ${P} line.`:` Python ${P} not found — this is not your code.`)),console.log(chalk.gray(o?" 装一份并存的(免 sudo,二十秒,不动系统 python3): ":" Add one side-by-side (no sudo, ~20 s, system python3 untouched): ")+chalk.bold.cyan("python setup")),t.judgeIsServerSide&&console.log(chalk.gray(o?" 判分在服务器上跑,所以 /submit 照样能用 — 只是没法先本地自测。":" Judging is server-side — /submit still works, only self-check is off.")),console.log()}export function standaloneTriple(o=process.platform,t=""){const e=t.toLowerCase(),n="aarch64"===e||"arm64"===e,r="x86_64"===e||"amd64"===e||"x64"===e;return"darwin"===o?n?"aarch64-apple-darwin":r?"x86_64-apple-darwin":"":"win32"===o?n?"aarch64-pc-windows-msvc":r?"x86_64-pc-windows-msvc":"":n?"aarch64-unknown-linux-gnu":r?"x86_64-unknown-linux-gnu":""}export function pickStandaloneAsset(o,t){const e=`${y.major}\\.${y.minor}`,n=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only_stripped\\.tar\\.gz$`),r=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only\\.tar\\.gz$`);return o.find(o=>n.test(o.name))??o.find(o=>r.test(o.name))}export function isAllowedAssetUrl(o){return/^https:\/\/github\.com\/astral-sh\/python-build-standalone\/releases\/download\/[\w.%+-]+\/cpython-3\.12\.[\w.%+-]+\.tar\.gz$/.test(o)}const b="win32"===process.platform?"tar.exe":"tar";export async function provisionPython312(p={}){const u=a(),m=c(u,".icoa","python312"),$=d(u,process.platform),w=o=>{p.quiet||console.log(o)};if(!p.force&&e($)){const o=g(`"${$}"`);if(o&&new RegExp(`^Python ${y.major}\\.${y.minor}\\.`).test(o))return w(chalk.green(` ✓ ${o}`)+chalk.gray(` → ${$}`)),resetPythonProbe(),!0}const v=function(){if("win32"===process.platform)return"arm64"===process.arch?"aarch64":"x86_64";try{return o("uname -m",{encoding:"utf-8"}).trim()}catch{return"arm64"===process.arch?"aarch64":"x86_64"}}(),x=standaloneTriple(process.platform,v);if(!x)return w(chalk.yellow(` ~ No prebuilt Python ${P} for '${v||"unknown"}' on this platform.`)),!1;if(!function(){try{return o(`${b} --version`,{stdio:"ignore",timeout:4e3}),!0}catch{return!1}}())return w(chalk.red(" ✗ `tar` is not installed")+chalk.gray(" — needed to unpack the download.")),"linux"===process.platform&&w(chalk.gray(" Install it: ")+chalk.cyan("sudo apt install -y tar")),!1;let k="",_="";try{const o=await f("https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest",{headers:{"User-Agent":"icoa-cli",Accept:"application/vnd.github+json"}});if(!o.ok)throw new Error(`GitHub API ${o.status}`);const t=pickStandaloneAsset((await o.json()).assets??[],x);if(!t)throw new Error(`no CPython ${P} build for ${x} in the latest release`);if(k=t.browser_download_url,_=t.name,!isAllowedAssetUrl(k))throw new Error("asset URL failed the allowlist check")}catch(o){return w(chalk.red(` ✗ Could not resolve a standalone Python ${P}`)+chalk.gray(` (${o.message})`)),!1}const j=_.match(/^cpython-(\d+\.\d+\.\d+)/)?.[1]??P;w(chalk.gray(` Fetching CPython ${j} for ${x} …`));let A="";try{n(c(u,".icoa"),{recursive:!0}),A=r(c(u,".icoa",".py312-"));const e=c(A,"py.tgz");await async function(o,e){const n=await f(o,{timeoutMs:6e4,headers:{"User-Agent":"icoa-cli"}});if(!n.ok)throw new Error(`download HTTP ${n.status}`);if(!n.body)throw new Error("download returned an empty body");await h(l.fromWeb(n.body),t(e))}(k,e),o(`${b} -xzf "${e}" -C "${A}"`,{stdio:["ignore","ignore","pipe"]}),i(m,{recursive:!0,force:!0}),s(c(A,"python"),m);const a=o(`"${$}" --version`,{encoding:"utf-8"}).trim();if(!new RegExp(`^Python ${y.major}\\.${y.minor}\\.`).test(a))throw new Error(`unexpected version: ${a}`);return resetPythonProbe(),w(chalk.green(` ✓ ${a} installed`)+chalk.gray(` → ${$} (system python3 untouched)`)),!0}catch(o){const t=String(o.stderr??o.message).trim().split("\n").pop();return w(chalk.red(` ✗ Python ${P} install failed`)+chalk.gray(t?` (${t})`:"")),!1}finally{A&&i(A,{recursive:!0,force:!0})}}
1
+ import chalk from"chalk";import{execSync as o}from"node:child_process";import{createHash as t}from"node:crypto";import{createWriteStream as e,existsSync as r,mkdirSync as n,mkdtempSync as s,renameSync as i,rmSync as a}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{Readable as h,Transform as p}from"node:stream";import{pipeline as u}from"node:stream/promises";import{icoaStandalonePython as d,parsePyVersion as y,REQUIRED_PY as m,resolveHostPython as f,resolveRunPython as g}from"./aienv.js";import{httpFetch as $}from"./http.js";import{lazyJson as w}from"./lazy-assets.js";function b(t){try{return o(`${t} --version 2>&1`,{encoding:"utf-8",timeout:4e3}).trim()}catch{return null}}let v,P;export function probePython(o=!1,t={}){const e=t.hostOnly?P:v;if(e&&!o)return e;const r=c(),n=t.hostOnly?(()=>{const o=f(b,process.platform,r);return o?{...o,source:"host"}:null})():g(b,process.platform,r);if(n){const o=(b(n.cmd.includes(" ")?n.cmd:`"${n.cmd}"`)??"").replace(/^Python\s+/i,"")||`${n.version.major}.${n.version.minor}`,e=d(r,process.platform),s="aienv"===n.source?"aienv":n.cmd===e?"icoa":"system",i={ok:!0,version:o,cmd:n.cmd,source:s};return t.hostOnly?P=i:v=i,i}let s;for(const o of["python3","python"]){const t=b(o);if(y(t)){s=(t??"").replace(/^Python\s+/i,"").trim();break}}const i={ok:!1,otherVersion:s};return t.hostOnly?P=i:v=i,i}export function resetPythonProbe(){v=void 0,P=void 0}const x=`${m.major}.${m.minor}`;export function pythonDoorLine(o,t=!1,e){const r=e??probePython(),n=t?"":"🐍 ";if(r.ok){const t="aienv"===r.source?o?"aienv 环境":"aienv venv":"icoa"===r.source?o?"icoa 装的":"installed by icoa":o?"系统自带":"system";return chalk.gray(` ${n}Python ${r.version} · ${t} · `)+chalk.gray(o?"本机可跑代码":"code runs locally")}return r.otherVersion?chalk.gray(` ${n}Python ${r.otherVersion} — `)+chalk.gray(o?`课程按 ${x} 校准 · `:`courses are calibrated on ${x} · `)+chalk.bold.green("python setup")+chalk.gray(o?` 装并存的 ${x}(免 sudo,不动系统 python3)`:" adds one side-by-side (no sudo, system python3 untouched)"):chalk.gray(` ${n}`+(o?"没有 Python — 读卡答题不需要它 · ":"No Python — reading and answering cards works without it · "))+chalk.bold.green("python setup")+chalk.gray(o?" 让 /py 里能跑代码":" when you want to run code in /py")}export function selectionSetupLine(o,t=!1,e){const r=e??probePython(!1,{hostOnly:!0}),n=t?"":"🐍 ",s=chalk.gray(" (Paper C · SIMOC are MCQ-only — neither is needed)");return r.ok?o?chalk.gray(` ${n}Python ${r.version} · `)+chalk.green("exam toolkit ready"):chalk.gray(` ${n}Python ${r.version} · toolkit not installed · `)+chalk.bold.green("exam setup")+chalk.gray(" (13 packages, ~150 MB)")+s:chalk.gray(` ${n}`+(r.otherVersion?`Python ${r.otherVersion} — the exam runs on ${x} · `:`No Python ${x} yet · `))+chalk.bold.green("exam setup")+chalk.gray(" installs it (~20 s) plus the toolkit (~150 MB)")+s}export function printNoPython312(o,t={}){const e=probePython();console.log(chalk.yellow(o?e.otherVersion?` 这台机器只有 Python ${e.otherVersion} — 课程和判分机都在 ${x} 线上。`:` 找不到 Python ${x} — 不是你的代码有问题。`:e.otherVersion?` This machine has Python ${e.otherVersion} only — courses and the judge both run on the ${x} line.`:` Python ${x} not found — this is not your code.`)),console.log(chalk.gray(o?" 装一份并存的(免 sudo,二十秒,不动系统 python3): ":" Add one side-by-side (no sudo, ~20 s, system python3 untouched): ")+chalk.bold.cyan("python setup")),t.judgeIsServerSide&&console.log(chalk.gray(o?" 判分在服务器上跑,所以 /submit 照样能用 — 只是没法先本地自测。":" Judging is server-side — /submit still works, only self-check is off.")),console.log()}export function standaloneTriple(o=process.platform,t=""){const e=t.toLowerCase(),r="aarch64"===e||"arm64"===e,n="x86_64"===e||"amd64"===e||"x64"===e;return"darwin"===o?r?"aarch64-apple-darwin":n?"x86_64-apple-darwin":"":"win32"===o?r?"aarch64-pc-windows-msvc":n?"x86_64-pc-windows-msvc":"":r?"aarch64-unknown-linux-gnu":n?"x86_64-unknown-linux-gnu":""}export function pickStandaloneAsset(o,t){const e=`${m.major}\\.${m.minor}`,r=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only_stripped\\.tar\\.gz$`),n=new RegExp(`^cpython-${e}\\.\\d+\\+\\d+-${t}-install_only\\.tar\\.gz$`);return o.find(o=>r.test(o.name))??o.find(o=>n.test(o.name))}export function isAllowedAssetUrl(o){return/^https:\/\/github\.com\/astral-sh\/python-build-standalone\/releases\/download\/[\w.%+-]+\/cpython-3\.12\.[\w.%+-]+\.tar\.gz$/.test(o)}const k=/^[0-9a-f]{64}$/;export function candidatesFromManifest(o,t){const e=o?.assets?.[t];if(!e||"string"!=typeof e.path||!function(o,t){const e=`${m.major}\\.${m.minor}`;return new RegExp(`^\\d{8}/cpython-${e}\\.\\d+\\+\\d+-${t}-install_only(_stripped)?\\.tar\\.gz$`).test(o)}(e.path,t))return[];const r="string"==typeof e.sha256&&k.test(e.sha256)?e.sha256:void 0,n=e.path.slice(e.path.indexOf("/")+1),s=[];for(const t of Array.isArray(o.mirrors)?o.mirrors:[]){if("string"!=typeof t||!/^https:\/\/[\w.-]+(\/[\w./%-]*)?$/.test(t))continue;const o=`${t.replace(/\/+$/,"")}/${e.path.replace(/\+/g,"%2B")}`;s.push({url:o,name:n,via:new URL(o).host,sha256:r})}return"string"==typeof e.github&&isAllowedAssetUrl(e.github)&&s.push({url:e.github,name:n,via:"github.com",sha256:r}),s}const _="win32"===process.platform?"tar.exe":"tar";async function j(o,r,n){const s=await $(o,{timeoutMs:3e4,retries:0,headers:{"User-Agent":"icoa-cli"}});if(!s.ok)throw new Error(`HTTP ${s.status}`);if(!s.body)throw new Error("empty body");const i=t("sha256"),a=new p({transform(o,t,e){i.update(o),e(null,o)}});if(await u(h.fromWeb(s.body),a,e(r)),n&&i.digest("hex")!==n)throw new Error("sha256 mismatch")}export async function provisionPython312(t={}){const e=c(),h=l(e,".icoa","python312"),p=d(e,process.platform),u=o=>{t.quiet||console.log(o)};if(!t.force&&r(p)){const o=b(`"${p}"`);if(o&&new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(o))return u(chalk.green(` ✓ ${o}`)+chalk.gray(` → ${p}`)),resetPythonProbe(),!0}const y=function(){if("win32"===process.platform)return"arm64"===process.arch?"aarch64":"x86_64";try{return o("uname -m",{encoding:"utf-8"}).trim()}catch{return"arm64"===process.arch?"aarch64":"x86_64"}}(),f=standaloneTriple(process.platform,y);if(!f)return u(chalk.yellow(` ~ No prebuilt Python ${x} for '${y||"unknown"}' on this platform.`)),!1;if(!function(){try{return o(`${_} --version`,{stdio:"ignore",timeout:4e3}),!0}catch{return!1}}())return u(chalk.red(" ✗ `tar` is not installed")+chalk.gray(" — needed to unpack the download.")),"linux"===process.platform&&u(chalk.gray(" Install it: ")+chalk.cyan("sudo apt install -y tar")),!1;let g=candidatesFromManifest(await w("python312/manifest.json",8e3,{force:!0}),f);if(0===g.length)try{const o=await $("https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest",{headers:{"User-Agent":"icoa-cli",Accept:"application/vnd.github+json"}});if(!o.ok)throw new Error(`GitHub API ${o.status}`);const t=pickStandaloneAsset((await o.json()).assets??[],f);if(!t)throw new Error(`no CPython ${x} build for ${f} in the latest release`);if(!isAllowedAssetUrl(t.browser_download_url))throw new Error("asset URL failed the allowlist check");g=[{url:t.browser_download_url,name:t.name,via:"github.com"}]}catch(o){return u(chalk.red(` ✗ Could not resolve a standalone Python ${x}`)+chalk.gray(` (mirror list unreachable · ${o.message})`)),!1}const v=g[0].name.match(/^cpython-(\d+\.\d+\.\d+)/)?.[1]??x;let P="";try{n(l(e,".icoa"),{recursive:!0}),P=s(l(e,".icoa",".py312-"));const t=l(P,"py.tgz"),r=[];let c=!1;for(const o of g){u(chalk.gray(` Fetching CPython ${v} for ${f} via ${o.via} …`));try{await j(o.url,t,o.sha256),c=!0;break}catch(t){const e=t.message.split("\n")[0].slice(0,80);r.push(`${o.via}: ${e}`),u(chalk.gray(` ~ ${o.via} did not work (${e}) — trying the next source`))}}if(!c)throw new Error(`no source reachable ${r.join(" · ")}`);o(`${_} -xzf "${t}" -C "${P}"`,{stdio:["ignore","ignore","pipe"]}),a(h,{recursive:!0,force:!0}),i(l(P,"python"),h);const d=o(`"${p}" --version`,{encoding:"utf-8"}).trim();if(!new RegExp(`^Python ${m.major}\\.${m.minor}\\.`).test(d))throw new Error(`unexpected version: ${d}`);return resetPythonProbe(),u(chalk.green(` ✓ ${d} installed`)+chalk.gray(` → ${p} (system python3 untouched)`)),!0}catch(o){const t=String(o.stderr??o.message).trim().split("\n").pop();return u(chalk.red(` ✗ Python ${x} install failed`)+chalk.gray(t?` (${t})`:"")),!1}finally{P&&a(P,{recursive:!0,force:!0})}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.616",
3
+ "version": "2.19.617",
4
4
  "description": "ICOA CLI — AI-native competition runtime of the International Cyber Olympiad in AI: learn, practise, and compete in AI & cybersecurity from the terminal (AI4CTF · CTF4AI · CTF4EAI)",
5
5
  "author": "Charlie Zhu",
6
6
  "type": "module",