shennian 0.3.6 → 0.3.7
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,3 +1,3 @@
|
|
|
1
|
-
import i from"node:fs";import p from"node:path";import{execSync as
|
|
1
|
+
import i from"node:fs";import p from"node:path";import{execSync as F,execFile as D}from"node:child_process";import{promisify as O}from"node:util";import{fileURLToPath as L}from"node:url";import{getShennianDir as $,resolveShennianPath as d}from"../config/index.js";const f=O(D),m=d("upgrade-attempt.json"),J=3,I="https://registry.npmjs.org",H=10*6e4,U=[5*6e4,30*6e4,120*6e4,360*6e4];function y(){try{const r=B();if(r)return JSON.parse(i.readFileSync(r,"utf-8")).version}catch{}return"0.0.0"}function B(r=L(import.meta.url)){let e=p.dirname(r);for(let n=0;n<6;n++){const t=p.join(e,"package.json");if(i.existsSync(t)&&JSON.parse(i.readFileSync(t,"utf-8")).name==="shennian")return t;e=p.dirname(e)}return null}async function G(){const r=await fetch("https://registry.npmjs.org/shennian/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!r.ok)throw new Error(`npm registry returned ${r.status}`);return(await r.json()).version}function P(r,e){const n=u=>u.replace(/^v/,"").split(".").map(Number),[t,s,o]=n(r),[a,l,c]=n(e);return a>t?"major":a===t&&l>s?"minor":a===t&&l===s&&c>o?"patch":"none"}function Y(){return F("npm root -g",{encoding:"utf-8",stdio:"pipe",windowsHide:!0}).trim()}function z(){const r=Y();return p.join(r,"shennian","dist","bin","shennian.js")}function K(){try{return JSON.parse(i.readFileSync(m,"utf-8"))}catch{return null}}function g(r){i.mkdirSync($(),{recursive:!0}),i.writeFileSync(m,JSON.stringify(r,null,2))}function h(){try{i.unlinkSync(m)}catch{}}function _(){return d("upgrade-failures.json")}function X(){return d("upgrade.lock")}function N(r=X(),e={}){const n=e.now??Date.now(),t=e.pid??process.pid,s=e.isProcessAlive??(c=>{try{return process.kill(c,0),!0}catch{return!1}});i.mkdirSync(p.dirname(r),{recursive:!0});const o=()=>i.openSync(r,"wx",384);let a;try{a=o()}catch(c){if(c.code!=="EEXIST")throw c;let u=!1;try{const v=JSON.parse(i.readFileSync(r,"utf-8")),E=Number(v.pid),b=Number(v.createdAt);u=!Number.isInteger(E)||!Number.isFinite(b)||n-b>H||!s(E)}catch{u=!0}if(!u)throw new Error("Another Shennian upgrade is already running");i.rmSync(r,{force:!0}),a=o()}i.writeFileSync(a,JSON.stringify({pid:t,createdAt:n})),i.closeSync(a);let l=!1;return{release(){if(!l){l=!0;try{i.unlinkSync(r)}catch{}}}}}function k(r=_()){try{const e=JSON.parse(i.readFileSync(r,"utf-8"));return!e||typeof e!="object"||!e.versions?{versions:{}}:{versions:e.versions}}catch{return{versions:{}}}}function R(r){i.mkdirSync($(),{recursive:!0}),i.writeFileSync(_(),JSON.stringify(r,null,2))}function Z(r){return U[Math.min(Math.max(r-1,0),U.length-1)]}function nr(r,e=Date.now(),n=k()){const t=n.versions[r];return!!(t&&t.nextRetryAt>e)}function or(r,e,n=Date.now()){const t=k(),s=t.versions[r],o=(s?.failureCount??0)+1,a={version:r,failureCount:o,firstFailedAt:s?.firstFailedAt??n,lastFailedAt:n,nextRetryAt:n+Z(o),error:e};return t.versions[r]=a,R(t),a}function w(r){const e=k();e.versions[r]&&(delete e.versions[r],R(e))}async function ar(){const r=K();if(!r)return!1;if(Date.now()-r.attemptAt>600*1e3)return h(),!1;if(r.attemptCount++,r.attemptCount>=J){console.error(`[upgrade] New version crashed ${r.attemptCount} times, rolling back to ${r.from}...`);let n;try{n=N()}catch(t){return console.error(`[upgrade] Rollback deferred: ${t instanceof Error?t.message:String(t)}`),!1}try{return await M(r.from),h(),console.error(`[upgrade] Rolled back to ${r.from}. Restarting...`),!0}catch(t){return console.error(`[upgrade] Rollback failed: ${t instanceof Error?t.message:String(t)}`),h(),!1}finally{n.release()}}return g(r),!1}async function ir(r,e,n={}){let t;try{t=N()}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}try{return await q(r,e,n)}finally{t.release()}}async function q(r,e,n={}){const t=n.currentVersion??y(),s=y();if(t===r)return w(r),{ok:!1,error:`Already on version ${r}`};e({step:"checking"});try{F("npm --version",{stdio:"pipe",windowsHide:!0})}catch{return{ok:!1,error:"npm is not available in PATH"}}if(s===r){e({step:"verifying",version:r});try{await A(r)}catch(o){return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}`}}return g({from:t,to:r,attemptCount:0,attemptAt:Date.now()}),w(r),e({step:"restarting",from:t,to:r}),{ok:!0,from:t,to:r}}e({step:"backing-up"}),e({step:"installing",version:r});try{await j(r)}catch(o){const a=await T(t);return{ok:!1,error:`npm install failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}e({step:"verifying",version:r});try{await A(r)}catch(o){const a=await T(t);return{ok:!1,error:`Smoke test failed: ${o instanceof Error?o.message:String(o)}${a.ok?"":`; rollback failed: ${a.error}`}`,rolledBack:a.ok}}return g({from:t,to:r,attemptCount:0,attemptAt:Date.now()}),w(r),e({step:"restarting",from:t,to:r}),{ok:!0,from:t,to:r}}function C(r){if(!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(r))throw new Error(`Invalid npm version: ${r}`)}function S(){return process.platform==="win32"?"npm.cmd":"npm"}function x(r){return{timeout:r,windowsHide:!0,shell:process.platform==="win32"}}async function j(r){C(r);try{await f(S(),["install","-g",`shennian@${r}`],x(12e4))}catch(e){const n=e instanceof Error?e.message:String(e);try{await f(S(),["install","-g",`shennian@${r}`,"--registry",I],{...x(12e4)})}catch(t){const s=t instanceof Error?t.message:String(t);throw new Error(`${n}
|
|
2
2
|
--- npmjs fallback ---
|
|
3
|
-
${s}`)}}}async function
|
|
3
|
+
${s}`)}}}async function A(r){C(r);const e=z(),{stdout:n}=await f(process.execPath,[e,"--version"],{timeout:1e4,windowsHide:!0});if(n.trim()!==r)throw new Error(`Version verification failed: expected ${r}, got ${n.trim()||"empty output"}`);await f(S(),["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0"],{...x(3e4)})}async function M(r,e={}){await(e.install??j)(r),await(e.verify??A)(r)}async function T(r){try{return await M(r),{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function sr(r){const e=r??y(),n=await G(),t=P(e,n);return t==="none"?{hasUpdate:!1}:{hasUpdate:!0,current:e,latest:n,changeType:t}}export{N as acquireUpgradeLock,sr as checkForUpdate,h as clearUpgradeAttempt,w as clearUpgradeFailure,P as compareVersions,G as fetchLatestVersion,B as findPackageJson,y as getCurrentVersion,Z as getUpgradeRetryDelayMs,ar as handleStartupCrashCheck,j as installShennianVersion,nr as isUpgradeVersionInCooldown,ir as performUpgrade,K as readUpgradeAttempt,k as readUpgradeFailures,or as recordUpgradeFailure,M as rollbackToExactVersion,A as verifyInstalledVersion,g as writeUpgradeAttempt};
|