shennian 0.4.40 → 0.4.42

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.
@@ -366,8 +366,8 @@
366
366
  },
367
367
  {
368
368
  "file": "src/commands/upgrade.js",
369
- "beforeBytes": 12365,
370
- "afterBytes": 6391
369
+ "beforeBytes": 12351,
370
+ "afterBytes": 6348
371
371
  },
372
372
  {
373
373
  "file": "src/config/index.js",
@@ -896,18 +896,18 @@
896
896
  },
897
897
  {
898
898
  "file": "src/upgrade/updater-bootstrap.js",
899
- "beforeBytes": 3600,
900
- "afterBytes": 1845
899
+ "beforeBytes": 3701,
900
+ "afterBytes": 1916
901
901
  },
902
902
  {
903
903
  "file": "src/upgrade/updater-launcher.js",
904
- "beforeBytes": 9346,
905
- "afterBytes": 4701
904
+ "beforeBytes": 10317,
905
+ "afterBytes": 5253
906
906
  },
907
907
  {
908
908
  "file": "src/upgrade/updater-protocol.js",
909
- "beforeBytes": 2968,
910
- "afterBytes": 1796
909
+ "beforeBytes": 3029,
910
+ "afterBytes": 1839
911
911
  },
912
912
  {
913
913
  "file": "src/upgrade/updater-service-entry.js",
@@ -916,8 +916,8 @@
916
916
  },
917
917
  {
918
918
  "file": "src/upgrade/updater-worker.js",
919
- "beforeBytes": 30329,
920
- "afterBytes": 14939
919
+ "beforeBytes": 30817,
920
+ "afterBytes": 15224
921
921
  },
922
922
  {
923
923
  "file": "src/windows-shell.js",
@@ -1 +1 @@
1
- import c from"chalk";import{execSync as I,spawn as b}from"node:child_process";import{randomUUID as E}from"node:crypto";import P from"node:os";import{createInterface as V}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as v,compareVersions as k,checkForUpdate as $,acquireUpgradeLock as M,recordUpgradeFailure as A}from"../upgrade/engine.js";import{callDaemonIpc as F}from"../daemon-ipc/client.js";import{getDaemonStatus as _}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as D}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as H}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as L}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as q}from"../upgrade/updater-worker.js";import{getUpgradeChannel as R,setUpgradeChannel as G}from"../upgrade/channel.js";const j=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(n,r){return n!=="major"||r}function ae(n={}){const r=n.platform??P.platform(),i=n.kill??process.kill,e=n.pid??process.pid,d=n.env??process.env,o=n.spawn??b,t=n.execSync??I;if(r==="win32"){const a=d.ComSpec||"cmd.exe";try{o(a,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{t('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{o("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:d}).unref()}catch{}i(e,"SIGTERM")}function se(n){n.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(G(r.channel),console.log(`Upgrade channel: ${R()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${R()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const d=setInterval(()=>{process.stdout.write(`\r ${j[e%j.length]} Checking for updates...`),e++},80);let o;try{o=r.to??await v(),clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const t=k(i,o);if(t==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(o)} ${t==="major"?c.red("(major)"):t==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(t==="major"&&(console.log(c.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const a=V({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${o} to confirm this major upgrade: `);if(a.close(),p.trim()!==o){console.log(c.gray(" Upgrade cancelled."));return}}try{await F({method:"upgrade.start",params:{targetVersion:o,confirmedMajor:t==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(_({cleanupStale:!0}).running)throw a;const f=U();S({fromVersion:i,targetVersion:o,oldDaemonPid:null},{readiness:f})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${o}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function ie(n,r,i,e={}){const d=s=>{n.sendEvent({type:"event",event:"upgrade",payload:s})},o=e.currentVersion??y();let t;try{d({state:"checking",machineId:"self"}),t=i??await v()}catch(s){const l=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const a=k(o,t);if(a==="none")return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:o}}),{ok:!0,alreadyLatest:!0,version:o};if(!Y(a,e.confirmedMajor===!0))return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const p=E();let f;try{f=M();try{N(L("runtime","updater"))}catch(s){throw f.release(),s}}catch(s){const l=s instanceof Error?s.message:String(s);return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const m={from:o,to:t,jobId:p},w=async()=>{let s=!1;try{const l=U(),u={fromVersion:o,targetVersion:t,oldDaemonPid:process.pid,jobId:p},g=D(u);if(d({state:"downloading",machineId:"self",version:t,jobId:p}),await q(g),H(g,l.workerPath),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const x=e.prepareForUpgrade?.(p)??{activeRunCount:0};s=!0;const h=S(u,{readiness:l});return h.process?.once&&await new Promise((C,T)=>{h.process.once("spawn",C),h.process.once("error",T)}),d({state:"restarting",machineId:"self",...m,activeRunCount:x.activeRunCount}),e.shutdownAfterHandoff?.(),{ok:!0,...m,resultPath:g.resultPath}}catch(l){s&&e.cancelUpgradePreparation?.();const u=l instanceof Error?l.message:String(l);return u!=="upgrade_waiting_for_idle"&&A(t,u),d({state:"failed",machineId:"self",error:u,jobId:p}),{ok:!1,error:u}}finally{f.release()}};return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:m}),e.waitForCompletion?w():(w(),{ok:!0,...m})}async function ce(n,r,i={}){const e=await $(i.currentVersion);n.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{ie as handleUpgradeStart,ce as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,se as registerUpgradeCommand,ae as restartCurrentDaemonAfterUpgrade};
1
+ import c from"chalk";import{execSync as x,spawn as C}from"node:child_process";import{randomUUID as T}from"node:crypto";import I from"node:os";import{createInterface as b}from"node:readline/promises";import{getCurrentVersion as h,fetchLatestVersion as w,compareVersions as k,checkForUpdate as E,acquireUpgradeLock as V,recordUpgradeFailure as $}from"../upgrade/engine.js";import{callDaemonIpc as P}from"../daemon-ipc/client.js";import{getDaemonStatus as M}from"./daemon.js";import{ensureUpdaterInstalled as v}from"../upgrade/updater-bootstrap.js";import{launchUpdater as U,createUpdaterJob as F,waitForUpdaterReady as A,confirmUpdaterHandoff as _}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as D}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as H}from"../config/index.js";import{assertUpdaterAvailable as L,prepareUpdaterCandidate as q}from"../upgrade/updater-worker.js";import{getUpgradeChannel as R,setUpgradeChannel as N}from"../upgrade/channel.js";const S=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function G(n,r){return n!=="major"||r}function oe(n={}){const r=n.platform??I.platform(),i=n.kill??process.kill,e=n.pid??process.pid,d=n.env??process.env,t=n.spawn??C,o=n.execSync??x;if(r==="win32"){const a=d.ComSpec||"cmd.exe";try{t(a,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{o('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{t("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:d}).unref()}catch{}i(e,"SIGTERM")}function ae(n){n.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(N(r.channel),console.log(`Upgrade channel: ${R()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${R()}`);const i=h();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const d=setInterval(()=>{process.stdout.write(`\r ${S[e%S.length]} Checking for updates...`),e++},80);let t;try{t=r.to??await w(),clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const o=k(i,t);if(o==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(t)} ${o==="major"?c.red("(major)"):o==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(o==="major"&&(console.log(c.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const a=b({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${t} to confirm this major upgrade: `);if(a.close(),p.trim()!==t){console.log(c.gray(" Upgrade cancelled."));return}}try{await P({method:"upgrade.start",params:{targetVersion:t,confirmedMajor:o==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(M({cleanupStale:!0}).running)throw a;const f=v();U({fromVersion:i,targetVersion:t,oldDaemonPid:null},{readiness:f})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${t}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function se(n,r,i,e={}){const d=s=>{n.sendEvent({type:"event",event:"upgrade",payload:s})},t=e.currentVersion??h();let o;try{d({state:"checking",machineId:"self"}),o=i??await w()}catch(s){const l=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const a=k(t,o);if(a==="none")return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:t}}),{ok:!0,alreadyLatest:!0,version:t};if(!G(a,e.confirmedMajor===!0))return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const p=T();let f;try{f=V();try{L(H("runtime","updater"))}catch(s){throw f.release(),s}}catch(s){const l=s instanceof Error?s.message:String(s);return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const g={from:t,to:o,jobId:p},y=async()=>{let s=!1;try{const l=v(),u={fromVersion:t,targetVersion:o,oldDaemonPid:process.pid,jobId:p,requireHandoffAck:!0},m=F(u);if(d({state:"downloading",machineId:"self",version:o,jobId:p}),await q(m),D(m,l.workerPath),U(u,{readiness:l}),await A(m),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const j=e.prepareForUpgrade?.(p)??{activeRunCount:0};return s=!0,_(m),d({state:"restarting",machineId:"self",...g,activeRunCount:j.activeRunCount}),{ok:!0,...g,resultPath:m.resultPath}}catch(l){s&&e.cancelUpgradePreparation?.();const u=l instanceof Error?l.message:String(l);return u!=="upgrade_waiting_for_idle"&&$(o,u),d({state:"failed",machineId:"self",error:u,jobId:p}),{ok:!1,error:u}}finally{f.release()}};return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:g}),e.waitForCompletion?y():(y(),{ok:!0,...g})}async function ie(n,r,i={}){const e=await E(i.currentVersion);n.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??h()}})}export{se as handleUpgradeStart,ie as handleUpgradeStatus,G as hasMajorUpgradeConfirmation,ae as registerUpgradeCommand,oe as restartCurrentDaemonAfterUpgrade};
@@ -1,2 +1,2 @@
1
- import r from"node:fs";import t from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as d}from"node:crypto";import{resolveShennianPath as f}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function m(e,o){r.mkdirSync(t.dirname(e),{recursive:!0,mode:448});const n=`${e}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(n,`${JSON.stringify(o,null,2)}
2
- `,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,e)}finally{try{r.unlinkSync(n)}catch{}}}function P(e={}){const o=e.root??f("runtime","updater"),n=t.join(o,"versions",`${l}-offline`);r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448),m(t.join(n,"package.json"),{type:"module"});const i=e.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=t.join(n,"updater-worker.js"),p=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(p)){const c=`${s}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(c,p,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(e.now??new Date).toISOString()};return m(t.join(o,"current.json"),a),m(t.join(o,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function U(e=f("runtime","updater")){try{const o=JSON.parse(r.readFileSync(t.join(e,"current.json"),"utf8"));if(o.protocolVersion!==l||!o.workerPath)return null;const n=r.lstatSync(o.workerPath);return n.isSymbolicLink()||!n.isFile()?null:o}catch{return null}}export{P as ensureUpdaterInstalled,U as readUpdaterReadiness};
1
+ import r from"node:fs";import t from"node:path";import{fileURLToPath as h}from"node:url";import{createHash as w,randomUUID as d}from"node:crypto";import{resolveShennianPath as f}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as p}from"./updater-protocol.js";function m(e,n){r.mkdirSync(t.dirname(e),{recursive:!0,mode:448});const o=`${e}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(o,`${JSON.stringify(n,null,2)}
2
+ `,{mode:384,flag:"wx"});const s=r.openSync(o,process.platform==="win32"?"r+":"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}r.renameSync(o,e)}finally{try{r.unlinkSync(o)}catch{}}}function g(e={}){const n=e.root??f("runtime","updater"),o=e.assetPath??h(new URL("./updater-worker.js",import.meta.url)),s=r.readFileSync(o),S=w("sha256").update(s).digest("hex").slice(0,16),i=t.join(n,"versions",`${p}-offline-${S}`);r.mkdirSync(i,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(i,448),m(t.join(i,"package.json"),{type:"module"});const a=t.join(i,"updater-worker.js"),u=(()=>{try{return r.readFileSync(a)}catch{return null}})();if(!u||!u.equals(s)){const c=`${a}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(c,s,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,a)}finally{try{r.unlinkSync(c)}catch{}}}const l={protocolVersion:p,workerPath:a,installedAt:(e.now??new Date).toISOString()};return m(t.join(n,"current.json"),l),m(t.join(n,"readiness.json"),{protocolVersion:l.protocolVersion,installedAt:l.installedAt}),l}function v(e=f("runtime","updater")){try{const n=JSON.parse(r.readFileSync(t.join(e,"current.json"),"utf8"));if(n.protocolVersion!==p||!n.workerPath)return null;const o=r.lstatSync(n.workerPath);return o.isSymbolicLink()||!o.isFile()?null:n}catch{return null}}export{g as ensureUpdaterInstalled,v as readUpdaterReadiness};
@@ -14,6 +14,7 @@ export declare function createUpdaterJob(input: {
14
14
  operation?: 'upgrade' | 'rollback';
15
15
  isolatedDaemonStart?: boolean;
16
16
  rollbackJobId?: string;
17
+ requireHandoffAck?: boolean;
17
18
  }): UpdaterJob;
18
19
  export declare function writeUpdaterJob(job: UpdaterJob, root?: string): string;
19
20
  export declare function needsSystemdUpdater(platform: string, cgroup: string): boolean;
@@ -30,4 +31,7 @@ export declare function launchUpdater(input: Parameters<typeof createUpdaterJob>
30
31
  job: UpdaterJob;
31
32
  process: ChildProcess | null;
32
33
  };
34
+ /** Do not freeze/exit just because spawn succeeded: require worker readiness. */
35
+ export declare function waitForUpdaterReady(job: UpdaterJob, timeoutMs?: number): Promise<void>;
36
+ export declare function confirmUpdaterHandoff(job: UpdaterJob): void;
33
37
  export declare function markUpdaterHealthy(jobId: string, effectiveVersion: string, root?: string): boolean;
@@ -1,3 +1,3 @@
1
- import r from"node:fs";import d from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as j,spawnSync as I}from"node:child_process";import{resolveShennianPath as u}from"../config/index.js";import{findPackageJson as P,resolveNpmGlobalPrefixFromPackageJson as E}from"./engine.js";import{ensureUpdaterInstalled as J}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(e,o){r.mkdirSync(d.dirname(e),{recursive:!0,mode:448});const n=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(n,`${JSON.stringify(o,null,2)}
2
- `,{mode:384,flag:"wx"});const c=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(n,e),process.platform!=="win32"){const t=r.openSync(d.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(n)}catch{}}}function $(e){const o=e.root??u("runtime","updater"),n=P(),c=e.npmPrefix??(n?E(n):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=e.jobId??y();return{protocolVersion:S,jobId:t,fromVersion:e.fromVersion,targetVersion:e.targetVersion,npmPrefix:c,npmExecutable:e.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:e.nodeExecutable??process.execPath,packageName:"shennian",operation:e.operation??"upgrade",oldDaemonPid:e.oldDaemonPid,platform:process.platform,createdAt:(e.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:d.join(o,"results",`${t}.json`),attemptPath:u("upgrade-attempt.json"),...e.rollbackJobId?{rollbackJobId:e.rollbackJobId}:{},...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function O(e,o=u("runtime","updater")){const n=d.join(o,"jobs");r.mkdirSync(n,{recursive:!0,mode:448});const c=d.join(n,`${e.jobId}.json`),t=`${c}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
- `,{mode:384,flag:"wx"});const s=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}if(r.renameSync(t,c),process.platform!=="win32"){const a=r.openSync(n,"r");try{r.fsyncSync(a)}finally{r.closeSync(a)}}return c}finally{try{r.unlinkSync(t)}catch{}}}function _(e,o){return e==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function C(e,o={}){const n=o.root??e.root??u("runtime","updater"),c=o.readiness??J({root:n}),t=$({...e,root:n}),s=O(t,n),a=d.join(n,"updater.lock");r.mkdirSync(n,{recursive:!0,mode:448});let l=null,m=!1;const w=()=>{if(m){try{JSON.parse(r.readFileSync(a,"utf8")).jobId===t.jobId&&r.unlinkSync(a)}catch{}m=!1}};try{try{l=r.openSync(a,"wx",384),m=!0}catch(i){if(i.code!=="EEXIST")throw i;let h=!1;try{const b=JSON.parse(r.readFileSync(a,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});h=!!(b.pid&&k(b.pid))}catch{}if(h)throw new Error("upgrade_locked");r.rmSync(a,{force:!0}),l=r.openSync(a,"wx",384),m=!0}r.writeFileSync(l,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(o.now??Date.now)()})),r.closeSync(l),l=null;let p=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{p=r.readFileSync("/proc/self/cgroup","utf8")}catch{}if(_(o.platform??process.platform,p)){const i=(o.systemdRun??I)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,c.workerPath,s],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const f=(o.spawn??j)(t.nodeExecutable,[c.workerPath,s],{detached:!0,stdio:"ignore",windowsHide:!0});return f.once?.("error",i=>{g(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),w()}),f.unref(),{job:t,process:f}}catch(p){l!==null&&r.closeSync(l),w();try{r.unlinkSync(s)}catch{}throw p}}function H(e,o,n=u("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const c=d.join(n,"results",`${e}.json`);try{const t=r.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const s=JSON.parse(r.readFileSync(c,"utf8"));return s.protocolVersion!==S||s.jobId!==e||s.state!=="starting"||s.targetVersion!==o?!1:(g(c,{...s,state:s.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{$ as createUpdaterJob,C as launchUpdater,H as markUpdaterHealthy,_ as needsSystemdUpdater,O as writeUpdaterJob};
1
+ import r from"node:fs";import d from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as I,spawnSync as j}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as P,resolveNpmGlobalPrefixFromPackageJson as _}from"./engine.js";import{ensureUpdaterInstalled as E}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function w(e,o){r.mkdirSync(d.dirname(e),{recursive:!0,mode:448});const a=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(a,`${JSON.stringify(o,null,2)}
2
+ `,{mode:384,flag:"wx"});const n=r.openSync(a,process.platform==="win32"?"r+":"r");try{r.fsyncSync(n)}finally{r.closeSync(n)}if(r.renameSync(a,e),process.platform!=="win32"){const t=r.openSync(d.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(a)}catch{}}}function J(e){const o=e.root??p("runtime","updater"),a=P(),n=e.npmPrefix??(a?_(a):null);if(!n)throw new Error("Cannot determine the active npm prefix");const t=e.jobId??y();return{protocolVersion:S,jobId:t,fromVersion:e.fromVersion,targetVersion:e.targetVersion,npmPrefix:n,npmExecutable:e.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:e.nodeExecutable??process.execPath,packageName:"shennian",operation:e.operation??"upgrade",oldDaemonPid:e.oldDaemonPid,platform:process.platform,createdAt:(e.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:d.join(o,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...e.requireHandoffAck?{requireHandoffAck:!0}:{},...e.rollbackJobId?{rollbackJobId:e.rollbackJobId}:{},...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function $(e,o=p("runtime","updater")){const a=d.join(o,"jobs");r.mkdirSync(a,{recursive:!0,mode:448});const n=d.join(a,`${e.jobId}.json`),t=`${n}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384,flag:"wx"});const c=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(t,n),process.platform!=="win32"){const s=r.openSync(a,"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}}return n}finally{try{r.unlinkSync(t)}catch{}}}function O(e,o){return e==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function H(e,o={}){const a=o.root??e.root??p("runtime","updater"),n=o.readiness??E({root:a}),t=J({...e,root:a}),c=$(t,a),s=d.join(a,"updater.lock");r.mkdirSync(a,{recursive:!0,mode:448});let l=null,f=!1;const h=()=>{if(f){try{JSON.parse(r.readFileSync(s,"utf8")).jobId===t.jobId&&r.unlinkSync(s)}catch{}f=!1}};try{try{l=r.openSync(s,"wx",384),f=!0}catch(i){if(i.code!=="EEXIST")throw i;let b=!1;try{const g=JSON.parse(r.readFileSync(s,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});b=!!(g.pid&&k(g.pid))}catch{}if(b)throw new Error("upgrade_locked");r.rmSync(s,{force:!0}),l=r.openSync(s,"wx",384),f=!0}r.writeFileSync(l,JSON.stringify({jobId:t.jobId,phase:"handoff",pid:process.pid,createdAt:(o.now??Date.now)()})),r.closeSync(l),l=null;let u=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{u=r.readFileSync("/proc/self/cgroup","utf8")}catch{}if(O(o.platform??process.platform,u)){const i=(o.systemdRun??j)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,n.workerPath,c],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const m=(o.spawn??I)(t.nodeExecutable,[n.workerPath,c],{detached:!0,stdio:"ignore",windowsHide:!0});return m.once?.("error",i=>{w(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),h()}),m.unref(),{job:t,process:m}}catch(u){l!==null&&r.closeSync(l),h();try{r.unlinkSync(c)}catch{}throw u}}async function C(e,o=8e3){const a=Date.now()+o;for(;Date.now()<a;){let n=null;try{n=JSON.parse(r.readFileSync(e.resultPath,"utf8"))}catch{}if(n?.jobId===e.jobId&&n.state==="waiting_for_daemon")return;if(n?.jobId===e.jobId&&n.state==="failed")throw new Error(n.errorCode??"updater_not_ready");await new Promise(t=>setTimeout(t,50))}throw new Error("updater_readiness_timeout")}function R(e){w(`${e.resultPath}.handoff`,{jobId:e.jobId})}function L(e,o,a=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const n=d.join(a,"results",`${e}.json`);try{const t=r.lstatSync(n);if(t.isSymbolicLink()||!t.isFile())return!1;const c=JSON.parse(r.readFileSync(n,"utf8"));return c.protocolVersion!==S||c.jobId!==e||c.state!=="starting"||c.targetVersion!==o?!1:(w(n,{...c,state:c.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{R as confirmUpdaterHandoff,J as createUpdaterJob,H as launchUpdater,L as markUpdaterHealthy,O as needsSystemdUpdater,C as waitForUpdaterReady,$ as writeUpdaterJob};
@@ -20,6 +20,7 @@ export type UpdaterJob = {
20
20
  isolatedDaemonStart?: boolean;
21
21
  /** Original local installation transaction used by crash-loop recovery. */
22
22
  rollbackJobId?: string;
23
+ requireHandoffAck?: boolean;
23
24
  };
24
25
  export type UpdaterResultState = 'waiting_for_daemon' | 'installing' | 'verifying' | 'starting' | 'healthy' | 'rolled_back' | 'failed';
25
26
  export type UpdaterErrorCode = 'invalid_job' | 'job_expired' | 'upgrade_locked' | 'daemon_exit_timeout' | 'npm_install_failed' | 'verification_failed' | 'rollback_failed' | 'daemon_start_failed' | 'internal_error';
@@ -1 +1 @@
1
- const b=1,d=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;function p(o){if(!o||typeof o!="object"||Array.isArray(o))throw new Error("invalid_job");const r=o,e=m=>{const i=r[m];if(typeof i!="string"||!i.trim())throw new Error("invalid_job");return i};if(r.protocolVersion!==1)throw new Error("invalid_job");const s=e("fromVersion"),l=e("targetVersion");if(!d.test(s)||!d.test(l))throw new Error("invalid_job");const c=e("createdAt");if(!Number.isFinite(Date.parse(c)))throw new Error("invalid_job");if(r.packageName!=="shennian")throw new Error("invalid_job");const t=r.oldDaemonPid==null?null:Number(r.oldDaemonPid);if(t!=null&&(!Number.isSafeInteger(t)||t<=0))throw new Error("invalid_job");const n=Number(r.daemonExitTimeoutMs),a=Number(r.commandTimeoutMs);if(!Number.isSafeInteger(n)||n<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:e("jobId"),fromVersion:s,targetVersion:l,npmPrefix:e("npmPrefix"),npmExecutable:e("npmExecutable"),nodeExecutable:e("nodeExecutable"),packageName:"shennian",operation:r.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:t,platform:e("platform"),createdAt:c,daemonExitTimeoutMs:n,commandTimeoutMs:a,resultPath:e("resultPath"),attemptPath:e("attemptPath"),isolatedDaemonStart:r.isolatedDaemonStart===!0,...typeof r.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(r.rollbackJobId)?{rollbackJobId:r.rollbackJobId}:{}}}function u(o){return(o instanceof Error?o.message:String(o)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}export{b as UPDATER_PROTOCOL_VERSION,p as parseUpdaterJob,u as sanitizeUpdaterError};
1
+ const b=1,d=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;function p(o){if(!o||typeof o!="object"||Array.isArray(o))throw new Error("invalid_job");const r=o,e=m=>{const i=r[m];if(typeof i!="string"||!i.trim())throw new Error("invalid_job");return i};if(r.protocolVersion!==1)throw new Error("invalid_job");const s=e("fromVersion"),c=e("targetVersion");if(!d.test(s)||!d.test(c))throw new Error("invalid_job");const l=e("createdAt");if(!Number.isFinite(Date.parse(l)))throw new Error("invalid_job");if(r.packageName!=="shennian")throw new Error("invalid_job");const t=r.oldDaemonPid==null?null:Number(r.oldDaemonPid);if(t!=null&&(!Number.isSafeInteger(t)||t<=0))throw new Error("invalid_job");const n=Number(r.daemonExitTimeoutMs),a=Number(r.commandTimeoutMs);if(!Number.isSafeInteger(n)||n<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:e("jobId"),fromVersion:s,targetVersion:c,npmPrefix:e("npmPrefix"),npmExecutable:e("npmExecutable"),nodeExecutable:e("nodeExecutable"),packageName:"shennian",operation:r.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:t,platform:e("platform"),createdAt:l,daemonExitTimeoutMs:n,commandTimeoutMs:a,resultPath:e("resultPath"),attemptPath:e("attemptPath"),isolatedDaemonStart:r.isolatedDaemonStart===!0,requireHandoffAck:r.requireHandoffAck===!0,...typeof r.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(r.rollbackJobId)?{rollbackJobId:r.rollbackJobId}:{}}}function u(o){return(o instanceof Error?o.message:String(o)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}export{b as UPDATER_PROTOCOL_VERSION,p as parseUpdaterJob,u as sanitizeUpdaterError};
@@ -19,6 +19,7 @@ type Job = {
19
19
  attemptPath: string;
20
20
  isolatedDaemonStart?: boolean;
21
21
  rollbackJobId?: string;
22
+ requireHandoffAck?: boolean;
22
23
  };
23
24
  type WorkerDeps = {
24
25
  now?: () => number;
@@ -1,2 +1,2 @@
1
- import o from"node:fs";import D from"node:os";import i from"node:path";import{execFile as U}from"node:child_process";import{promisify as Z}from"node:util";import{fileURLToPath as H,pathToFileURL as K}from"node:url";const j=Z(U),L=1,B="https://registry.npmjs.org",W=30*6e4;function A(t){return(t instanceof Error?t.message:String(t)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}function k(t,r){o.mkdirSync(i.dirname(t),{recursive:!0,mode:448});const e=`${t}.tmp-${process.pid}-${Date.now()}`;try{o.writeFileSync(e,`${JSON.stringify(r,null,2)}
2
- `,{mode:384,flag:"wx"});const n=o.openSync(e,process.platform==="win32"?"r+":"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}if(o.renameSync(e,t),process.platform!=="win32"){const s=o.openSync(i.dirname(t),"r");try{o.fsyncSync(s)}finally{o.closeSync(s)}}}finally{try{o.unlinkSync(e)}catch{}}}function O(t){const r=o.lstatSync(t);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const e=JSON.parse(o.readFileSync(t,"utf8")),n=w=>{if(typeof e[w]!="string"||!String(e[w]).trim())throw new Error("invalid_job");return String(e[w])},s=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),c=n("targetVersion");if(e.protocolVersion!==L||e.packageName!=="shennian")throw new Error("invalid_job");if(!s.test(l)||!s.test(c))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const f=e.oldDaemonPid==null?null:Number(e.oldDaemonPid),a=Number(e.daemonExitTimeoutMs),u=Number(e.commandTimeoutMs);if(f!=null&&(!Number.isSafeInteger(f)||f<1))throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(u)||u<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:c,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:e.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:f,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:a,commandTimeoutMs:u,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:e.isolatedDaemonStart===!0,...typeof e.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(e.rollbackJobId)?{rollbackJobId:e.rollbackJobId}:{}}}function T(t,r){return r==="win32"?i.win32.join(t,"node_modules","shennian"):i.posix.join(t,"lib","node_modules","shennian")}function x(t){const r=T(t.npmPrefix,t.platform),e=i.join(i.dirname(r),`.shennian-upgrade-${t.rollbackJobId??t.jobId}`),n=i.join(e,"candidate");return{active:r,transaction:e,prefix:n,candidate:T(n,t.platform),previous:i.join(e,"previous"),rejected:i.join(e,"rejected"),marker:i.join(e,"transaction.json")}}function p(t){try{return JSON.parse(o.readFileSync(t,"utf8"))}catch{return null}}function N(t){const r=o.lstatSync(t);if(!r.isDirectory()||r.isSymbolicLink())throw new Error("unsafe_installation")}function y(t,r){N(t);const e=o.realpathSync(t),n=a=>{const u=i.relative(e,o.realpathSync(a));if(u.startsWith(".."+i.sep)||u===".."||i.isAbsolute(u))throw new Error("external_package_dependency")},s=a=>{for(const u of o.readdirSync(a,{withFileTypes:!0})){const w=i.join(a,u.name);u.isSymbolicLink()?n(w):u.isDirectory()&&s(w)}};s(t);const l=p(i.join(t,"package.json"));if(l?.name!=="shennian"||l.version!==r||l.bin?.shennian!=="dist/bin/shennian.js")throw new Error("verification_failed");n(i.join(t,"dist","bin","shennian.js"));const c=new Set,f=a=>{const u=o.realpathSync(a);if(c.has(u))return;c.add(u);const w=p(i.join(a,"package.json"));if(!w)throw new Error("incomplete_package");for(const v of Object.keys(w.dependencies??{})){if(Object.hasOwn(w.optionalDependencies??{},v))continue;if(!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(v))throw new Error("invalid_dependency");let g=a,_=!1;for(;g===t||g.startsWith(t+i.sep);){const S=i.join(g,"node_modules",v);if(o.existsSync(i.join(S,"package.json"))){n(S),f(S),_=!0;break}g=i.dirname(g)}if(!_)throw new Error("incomplete_package")}};f(t)}function ue(t){const r=p(i.join(t,"updater.lock"));if(J(r))throw new Error("upgrade_locked")}async function X(t,r={}){const e=x(t);N(i.dirname(e.active)),y(e.active,t.fromVersion),o.mkdirSync(e.transaction,{mode:448});const n=r.execFile??j;try{await ee({...t,npmPrefix:e.prefix},t.targetVersion,n),await q(t,r)}catch(s){throw o.rmSync(e.transaction,{recursive:!0,force:!0}),Object.assign(new Error(A(s)),{code:"candidate_preparation_failed"})}}async function q(t,r={}){const e=x(t);N(e.transaction);const n=p(e.marker);if(n&&n.phase!=="prepared")throw new Error("transaction_already_started");y(e.active,t.fromVersion),y(e.candidate,t.targetVersion);const s=r.execFile??j;await E({...t,npmPrefix:e.prefix},t.targetVersion,s),await s(t.npmExecutable,["ls","-g",`shennian@${t.targetVersion}`,"--all","--omit=dev","--offline","--depth=100","--prefix",e.prefix],h(t,3e4)),k(e.marker,{jobId:t.jobId,fromVersion:t.fromVersion,targetVersion:t.targetVersion,phase:"prepared"})}function F(t,r){if(o.renameSync(t,r),process.platform!=="win32")for(const e of new Set([i.dirname(t),i.dirname(r)])){const n=o.openSync(e,"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}}}function Y(t){const r=x(t),e=i.dirname(t.resultPath);for(const n of o.readdirSync(i.dirname(r.active),{withFileTypes:!0})){if(!n.isDirectory()||!/^\.shennian-upgrade-[A-Za-z0-9_-]+$/.test(n.name))continue;const s=i.join(i.dirname(r.active),n.name);if(s===r.transaction)continue;const l=p(i.join(s,"transaction.json"));if(!l?.jobId||n.name!==`.shennian-upgrade-${l.jobId}`)continue;const c=p(i.join(e,`${l.jobId}.json`));c?.jobId===l.jobId&&["healthy","rolled_back"].includes(c.state??"")&&o.rmSync(s,{recursive:!0,force:!0})}}function Q(t){const r=i.join(i.dirname(t.attemptPath),"upgrade-failures.json");let e={versions:{}};try{e=JSON.parse(o.readFileSync(r,"utf8"))}catch{}const n=e.versions?.[t.targetVersion],s=Math.max(0,Number(n?.failureCount)||0)+1,l=Date.now(),c=[5*6e4,30*6e4,120*6e4,360*6e4];k(r,{versions:{...e.versions,[t.targetVersion]:{version:t.targetVersion,failureCount:s,firstFailedAt:n?.firstFailedAt??l,lastFailedAt:l,nextRetryAt:l+c[Math.min(s-1,c.length-1)],error:"upgrade_rolled_back"}}})}function b(t,r){const e=x(t),n=p(e.marker);if(!n||n.jobId!==(t.rollbackJobId??t.jobId))throw new Error("invalid_transaction");k(e.marker,{...n,phase:r})}function J(t){const r=Date.now()-D.uptime()*1e3;return!!(t?.pid&&(t.createdAt===void 0||t.createdAt>=r-2e3)&&I(t.pid))}function I(t){try{return process.kill(t,0),!0}catch{return!1}}async function $(t,r){if(r.serviceRecovery||!t.oldDaemonPid||!(r.isAlive??I)(t.oldDaemonPid))return;const e=r.kill??((l,c)=>process.kill(l,c)),n=r.sleep??(l=>new Promise(c=>setTimeout(c,l)));e(t.oldDaemonPid,"SIGTERM");const s=(r.now??Date.now)()+t.daemonExitTimeoutMs;for(;(r.now??Date.now)()<s;){if(!(r.isAlive??I)(t.oldDaemonPid))return;await n(100)}if(r.kill)e(t.oldDaemonPid,"SIGKILL");else if(t.platform==="win32")try{await(r.execFile??j)("taskkill",["/PID",String(t.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-t.oldDaemonPid,"SIGKILL")}catch{try{process.kill(t.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??I)(t.oldDaemonPid))throw new Error("daemon_exit_timeout")}function h(t,r=t.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:t.platform==="win32"}}async function ee(t,r,e){const n=["install","-g",`shennian@${r}`,"--prefix",t.npmPrefix];try{await e(t.npmExecutable,n,h(t))}catch{await e(t.npmExecutable,[...n,"--registry",B],h(t))}}async function E(t,r,e){const n=T(t.npmPrefix,t.platform),s=i.join(n,"dist","bin","shennian.js"),{stdout:l}=await e(t.nodeExecutable,[s,"--version"],h(t,1e4));if(l.trim()!==r)throw new Error("verification_failed");return s}async function P(t,r,e){if(t.isolatedDaemonStart){await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4));return}try{if(t.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():D.userInfo().uid;try{await e("launchctl",["bootstrap",`gui/${n}`,i.join(D.homedir(),"Library/LaunchAgents/com.shennian.agent.plist")],h(t,15e3))}catch{}await e("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],h(t,15e3));return}if(t.platform==="linux"){await e("systemctl",["--user","restart","shennian"],h(t,15e3));return}if(t.platform==="win32"){await e("schtasks",["/Run","/TN","ShennianAgent"],h(t,15e3));return}}catch{}await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4))}async function C(t,r){if(!t.isolatedDaemonStart)try{t.platform==="darwin"?await r("launchctl",["bootout",`gui/${process.getuid?.()??D.userInfo().uid}/com.shennian.agent`],h(t,15e3)):t.platform==="linux"?await r("systemctl",["--user","stop","shennian"],h(t,15e3)):t.platform==="win32"&&await r("schtasks",["/End","/TN","ShennianAgent"],h(t,15e3))}catch{}}function te(t){return(r,e={})=>k(t.resultPath,{protocolVersion:L,jobId:t.jobId,state:r,fromVersion:t.fromVersion,targetVersion:t.targetVersion,operation:t.operation,...e,updatedAt:new Date().toISOString()})}function re(t,r){const e=i.dirname(i.dirname(t)),n=i.join(e,"updater.lock");o.mkdirSync(e,{recursive:!0,mode:448});try{const s=o.openSync(n,"wx",384);o.writeFileSync(s,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),o.closeSync(s)}catch(s){if(s.code!=="EEXIST")throw s;try{const c=JSON.parse(o.readFileSync(n,"utf8"));if((c.jobId!==r.jobId||c.pid!==r.oldDaemonPid)&&J(c))throw new Error("upgrade_locked")}catch(c){if(c instanceof Error&&c.message==="upgrade_locked")throw c}o.rmSync(n,{force:!0});const l=o.openSync(n,"wx",384);o.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),o.closeSync(l)}return()=>{try{o.unlinkSync(n)}catch{}}}async function M(t,r={}){let e;try{e=O(t)}catch{throw new Error("invalid_job")}if(!r.serviceRecovery&&(r.now??Date.now)()-Date.parse(e.createdAt)>W)throw new Error("job_expired");const n=i.dirname(i.dirname(i.resolve(t)));if(i.basename(i.dirname(i.resolve(t)))!=="jobs"||i.basename(i.resolve(t))!==`${e.jobId}.json`)throw new Error("invalid_job");if(i.dirname(i.resolve(e.resultPath))!==i.join(n,"results"))throw new Error("invalid_job");if(i.basename(i.resolve(e.resultPath))!==`${e.jobId}.json`)throw new Error("invalid_job");const s=i.dirname(i.dirname(n));if(i.resolve(e.attemptPath)!==i.join(s,"upgrade-attempt.json"))throw new Error("invalid_job");const l=re(t,e),c=te(e),f=r.execFile??j,a=x(e),u=i.join(n,"active-job.json"),w=r.sleep??(d=>new Promise(m=>setTimeout(m,d)));let v=!1,g={};const _=d=>{d&&Q(e),k(e.attemptPath,{from:d?e.targetVersion:e.fromVersion,to:d?e.fromVersion:e.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:e.jobId,operation:d?"rollback":"upgrade"}),c("starting",d?{operation:"rollback",...g,effectiveVersion:e.fromVersion,targetVersion:e.fromVersion,fromVersion:e.targetVersion}:{})},S=async d=>{if(r.serviceRecovery)return;const m=(r.now??Date.now)()+(r.healthTimeoutMs??9e4);for(;(r.now??Date.now)()<m;){if(p(e.resultPath)?.state===(d?"rolled_back":"healthy"))return;await w(250)}throw new Error("daemon_health_timeout")},G=async()=>{const d=p(i.join(s,"daemon.pid"));d?.version===e.targetVersion&&Number.isSafeInteger(d.pid)&&typeof d.pid=="number"&&d.pid!==process.pid&&await $({...e,oldDaemonPid:d.pid},r)},R=async()=>{if(b(e,"rolling_back"),r.serviceRecovery||await C(e,f),await G(),!o.existsSync(a.previous))y(a.active,e.fromVersion);else{if(y(a.previous,e.fromVersion),o.existsSync(a.active)){if(o.existsSync(a.rejected))throw new Error("rollback_destination_exists");F(a.active,a.rejected)}F(a.previous,a.active)}b(e,"restored"),y(a.active,e.fromVersion);const d=await E(e,e.fromVersion,f);_(!0),r.serviceRecovery||await P(e,d,f),await S(!0)};try{const d=p(e.resultPath);if(d?.jobId===e.jobId&&["healthy","rolled_back","failed"].includes(d.state??""))return;const m=p(a.marker);if(m&&(m.jobId!==(e.rollbackJobId??e.jobId)||m.fromVersion!==(e.operation==="rollback"?e.targetVersion:e.fromVersion)))throw new Error("invalid_transaction");if(e.operation==="rollback"){e={...e,fromVersion:e.targetVersion,targetVersion:e.fromVersion},v=!0,k(u,{jobPath:t}),await $(e,r),await R();return}if(m&&m.phase!=="prepared"){v=!0,m.phase==="restored"||m.phase==="rolling_back"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),_(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await S(!0)):m.phase==="switching"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),_(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await S(!0)):await R();return}m||(c("installing"),await X(e,r)),y(a.candidate,e.targetVersion),y(a.active,e.fromVersion),c("waiting_for_daemon"),await w(250),k(u,{jobPath:t}),r.serviceRecovery||await C(e,f),await $(e,r),b(e,"switching"),v=!0,F(a.active,a.previous),F(a.candidate,a.active),c("verifying"),y(a.active,e.targetVersion);const V=await E(e,e.targetVersion,f);b(e,"starting"),_(!1),r.serviceRecovery||await P(e,V,f),await S(!1)}catch(d){const m=d.code??(d instanceof Error?d.message:"internal_error");if(!v){c("failed",{errorCode:m,errorSummary:A(d)}),(!e.oldDaemonPid||!(r.isAlive??I)(e.oldDaemonPid))&&(y(a.active,e.fromVersion),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f));return}g={errorCode:m,errorSummary:A(d)};try{await R()}catch(V){throw c("failed",{errorCode:"rollback_failed",errorSummary:A(V)}),V}}finally{const d=p(e.resultPath);if(["healthy","rolled_back"].includes(d?.state??""))try{Y(e)}catch{}l()}}async function ne(t){const r=p(i.join(t,"active-job.json"));if(!r?.jobPath)return;const e=i.resolve(r.jobPath);if(i.dirname(e)!==i.join(i.resolve(t),"jobs"))throw new Error("invalid_job");const n=O(e);for(;;){const s=p(n.resultPath);if(["healthy","rolled_back","failed"].includes(s?.state??""))return;const l=p(i.join(t,"updater.lock")),c=l?.pid!==process.pid&&J(l),f=p(x(n).marker);if(c){if(s?.state==="starting"&&["starting","restored"].includes(f?.phase??""))return;await new Promise(a=>setTimeout(a,250));continue}await M(e,{serviceRecovery:!0});return}}function z(t){try{return o.realpathSync.native(t)}catch{return i.resolve(t)}}const ie=process.argv[1]?z(process.argv[1]):"",oe=z(H(import.meta.url));if(ie===oe)if(process.argv[2]==="--service"){const[,,,t,r,...e]=process.argv;ne(t).then(async()=>{process.argv=[process.execPath,r,...e],await import(K(r).href)}).catch(()=>{process.exitCode=1})}else{const t=process.argv[2];t?M(t).catch(()=>{process.exitCode=1}):process.exitCode=2}export{ue as assertUpdaterAvailable,q as finalizeUpdaterCandidate,X as prepareUpdaterCandidate,ne as recoverServiceEntry,M as runUpdaterJob,x as updaterPaths,y as verifyLocalPackage};
1
+ import o from"node:fs";import D from"node:os";import i from"node:path";import{execFile as U}from"node:child_process";import{promisify as Z}from"node:util";import{fileURLToPath as q,pathToFileURL as K}from"node:url";const j=Z(U),L=1,B="https://registry.npmjs.org",W=30*6e4;function A(t){return(t instanceof Error?t.message:String(t)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}function k(t,r){o.mkdirSync(i.dirname(t),{recursive:!0,mode:448});const e=`${t}.tmp-${process.pid}-${Date.now()}`;try{o.writeFileSync(e,`${JSON.stringify(r,null,2)}
2
+ `,{mode:384,flag:"wx"});const n=o.openSync(e,process.platform==="win32"?"r+":"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}if(o.renameSync(e,t),process.platform!=="win32"){const s=o.openSync(i.dirname(t),"r");try{o.fsyncSync(s)}finally{o.closeSync(s)}}}finally{try{o.unlinkSync(e)}catch{}}}function O(t){const r=o.lstatSync(t);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const e=JSON.parse(o.readFileSync(t,"utf8")),n=w=>{if(typeof e[w]!="string"||!String(e[w]).trim())throw new Error("invalid_job");return String(e[w])},s=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),c=n("targetVersion");if(e.protocolVersion!==L||e.packageName!=="shennian")throw new Error("invalid_job");if(!s.test(l)||!s.test(c))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const f=e.oldDaemonPid==null?null:Number(e.oldDaemonPid),a=Number(e.daemonExitTimeoutMs),u=Number(e.commandTimeoutMs);if(f!=null&&(!Number.isSafeInteger(f)||f<1))throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(u)||u<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:c,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:e.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:f,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:a,commandTimeoutMs:u,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:e.isolatedDaemonStart===!0,requireHandoffAck:e.requireHandoffAck===!0,...typeof e.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(e.rollbackJobId)?{rollbackJobId:e.rollbackJobId}:{}}}function T(t,r){return r==="win32"?i.win32.join(t,"node_modules","shennian"):i.posix.join(t,"lib","node_modules","shennian")}function x(t){const r=T(t.npmPrefix,t.platform),e=i.join(i.dirname(r),`.shennian-upgrade-${t.rollbackJobId??t.jobId}`),n=i.join(e,"candidate");return{active:r,transaction:e,prefix:n,candidate:T(n,t.platform),previous:i.join(e,"previous"),rejected:i.join(e,"rejected"),marker:i.join(e,"transaction.json")}}function m(t){try{return JSON.parse(o.readFileSync(t,"utf8"))}catch{return null}}function N(t){const r=o.lstatSync(t);if(!r.isDirectory()||r.isSymbolicLink())throw new Error("unsafe_installation")}function y(t,r){N(t);const e=o.realpathSync(t),n=a=>{const u=i.relative(e,o.realpathSync(a));if(u.startsWith(".."+i.sep)||u===".."||i.isAbsolute(u))throw new Error("external_package_dependency")},s=a=>{for(const u of o.readdirSync(a,{withFileTypes:!0})){const w=i.join(a,u.name);u.isSymbolicLink()?n(w):u.isDirectory()&&s(w)}};s(t);const l=m(i.join(t,"package.json"));if(l?.name!=="shennian"||l.version!==r||l.bin?.shennian!=="dist/bin/shennian.js")throw new Error("verification_failed");n(i.join(t,"dist","bin","shennian.js"));const c=new Set,f=a=>{const u=o.realpathSync(a);if(c.has(u))return;c.add(u);const w=m(i.join(a,"package.json"));if(!w)throw new Error("incomplete_package");for(const v of Object.keys(w.dependencies??{})){if(Object.hasOwn(w.optionalDependencies??{},v))continue;if(!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(v))throw new Error("invalid_dependency");let g=a,S=!1;for(;g===t||g.startsWith(t+i.sep);){const _=i.join(g,"node_modules",v);if(o.existsSync(i.join(_,"package.json"))){n(_),f(_),S=!0;break}g=i.dirname(g)}if(!S)throw new Error("incomplete_package")}};f(t)}function me(t){const r=m(i.join(t,"updater.lock"));if($(r))throw new Error("upgrade_locked")}async function X(t,r={}){const e=x(t);N(i.dirname(e.active)),y(e.active,t.fromVersion),o.mkdirSync(e.transaction,{mode:448});const n=r.execFile??j;try{await te({...t,npmPrefix:e.prefix},t.targetVersion,n),await Y(t,r)}catch(s){throw o.rmSync(e.transaction,{recursive:!0,force:!0}),Object.assign(new Error(A(s)),{code:"candidate_preparation_failed"})}}async function Y(t,r={}){const e=x(t);N(e.transaction);const n=m(e.marker);if(n&&n.phase!=="prepared")throw new Error("transaction_already_started");y(e.active,t.fromVersion),y(e.candidate,t.targetVersion);const s=r.execFile??j;await E({...t,npmPrefix:e.prefix},t.targetVersion,s),await s(t.npmExecutable,["ls","-g",`shennian@${t.targetVersion}`,"--all","--omit=dev","--offline","--depth=100","--prefix",e.prefix],h(t,3e4)),k(e.marker,{jobId:t.jobId,fromVersion:t.fromVersion,targetVersion:t.targetVersion,phase:"prepared"})}function F(t,r){if(o.renameSync(t,r),process.platform!=="win32")for(const e of new Set([i.dirname(t),i.dirname(r)])){const n=o.openSync(e,"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}}}function Q(t){const r=x(t),e=i.dirname(t.resultPath);for(const n of o.readdirSync(i.dirname(r.active),{withFileTypes:!0})){if(!n.isDirectory()||!/^\.shennian-upgrade-[A-Za-z0-9_-]+$/.test(n.name))continue;const s=i.join(i.dirname(r.active),n.name);if(s===r.transaction)continue;const l=m(i.join(s,"transaction.json"));if(!l?.jobId||n.name!==`.shennian-upgrade-${l.jobId}`)continue;const c=m(i.join(e,`${l.jobId}.json`));c?.jobId===l.jobId&&["healthy","rolled_back"].includes(c.state??"")&&o.rmSync(s,{recursive:!0,force:!0})}}function ee(t){const r=i.join(i.dirname(t.attemptPath),"upgrade-failures.json");let e={versions:{}};try{e=JSON.parse(o.readFileSync(r,"utf8"))}catch{}const n=e.versions?.[t.targetVersion],s=Math.max(0,Number(n?.failureCount)||0)+1,l=Date.now(),c=[5*6e4,30*6e4,120*6e4,360*6e4];k(r,{versions:{...e.versions,[t.targetVersion]:{version:t.targetVersion,failureCount:s,firstFailedAt:n?.firstFailedAt??l,lastFailedAt:l,nextRetryAt:l+c[Math.min(s-1,c.length-1)],error:"upgrade_rolled_back"}}})}function b(t,r){const e=x(t),n=m(e.marker);if(!n||n.jobId!==(t.rollbackJobId??t.jobId))throw new Error("invalid_transaction");k(e.marker,{...n,phase:r})}function $(t){const r=Date.now()-D.uptime()*1e3;return!!(t?.pid&&(t.createdAt===void 0||t.createdAt>=r-2e3)&&I(t.pid))}function I(t){try{return process.kill(t,0),!0}catch{return!1}}async function J(t,r){if(r.serviceRecovery||!t.oldDaemonPid||!(r.isAlive??I)(t.oldDaemonPid))return;const e=r.kill??((l,c)=>process.kill(l,c)),n=r.sleep??(l=>new Promise(c=>setTimeout(c,l)));e(t.oldDaemonPid,"SIGTERM");const s=(r.now??Date.now)()+t.daemonExitTimeoutMs;for(;(r.now??Date.now)()<s;){if(!(r.isAlive??I)(t.oldDaemonPid))return;await n(100)}if(r.kill)e(t.oldDaemonPid,"SIGKILL");else if(t.platform==="win32")try{await(r.execFile??j)("taskkill",["/PID",String(t.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-t.oldDaemonPid,"SIGKILL")}catch{try{process.kill(t.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??I)(t.oldDaemonPid))throw new Error("daemon_exit_timeout")}function h(t,r=t.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:t.platform==="win32"}}async function te(t,r,e){const n=["install","-g",`shennian@${r}`,"--prefix",t.npmPrefix];try{await e(t.npmExecutable,n,h(t))}catch{await e(t.npmExecutable,[...n,"--registry",B],h(t))}}async function E(t,r,e){const n=T(t.npmPrefix,t.platform),s=i.join(n,"dist","bin","shennian.js"),{stdout:l}=await e(t.nodeExecutable,[s,"--version"],h(t,1e4));if(l.trim()!==r)throw new Error("verification_failed");return s}async function P(t,r,e){if(t.isolatedDaemonStart){await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4));return}try{if(t.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():D.userInfo().uid;try{await e("launchctl",["bootstrap",`gui/${n}`,i.join(D.homedir(),"Library/LaunchAgents/com.shennian.agent.plist")],h(t,15e3))}catch{}await e("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],h(t,15e3));return}if(t.platform==="linux"){await e("systemctl",["--user","restart","shennian"],h(t,15e3));return}if(t.platform==="win32"){await e("schtasks",["/Run","/TN","ShennianAgent"],h(t,15e3));return}}catch{}await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4))}async function C(t,r){if(!t.isolatedDaemonStart)try{t.platform==="darwin"?await r("launchctl",["bootout",`gui/${process.getuid?.()??D.userInfo().uid}/com.shennian.agent`],h(t,15e3)):t.platform==="linux"?await r("systemctl",["--user","stop","shennian"],h(t,15e3)):t.platform==="win32"&&await r("schtasks",["/End","/TN","ShennianAgent"],h(t,15e3))}catch{}}function re(t){return(r,e={})=>k(t.resultPath,{protocolVersion:L,jobId:t.jobId,state:r,fromVersion:t.fromVersion,targetVersion:t.targetVersion,operation:t.operation,...e,updatedAt:new Date().toISOString()})}function ne(t,r){const e=i.dirname(i.dirname(t)),n=i.join(e,"updater.lock");o.mkdirSync(e,{recursive:!0,mode:448});try{const s=o.openSync(n,"wx",384);o.writeFileSync(s,JSON.stringify({jobId:r.jobId,phase:"worker",pid:process.pid,createdAt:Date.now()})),o.closeSync(s)}catch(s){if(s.code!=="EEXIST")throw s;try{const c=JSON.parse(o.readFileSync(n,"utf8"));if((c.jobId!==r.jobId||c.phase!=="handoff"&&c.pid!==r.oldDaemonPid)&&$(c))throw new Error("upgrade_locked")}catch(c){if(c instanceof Error&&c.message==="upgrade_locked")throw c}o.rmSync(n,{force:!0});const l=o.openSync(n,"wx",384);o.writeFileSync(l,JSON.stringify({jobId:r.jobId,phase:"worker",pid:process.pid,createdAt:Date.now()})),o.closeSync(l)}return()=>{try{o.unlinkSync(n)}catch{}}}async function M(t,r={}){let e;try{e=O(t)}catch{throw new Error("invalid_job")}if(!r.serviceRecovery&&(r.now??Date.now)()-Date.parse(e.createdAt)>W)throw new Error("job_expired");const n=i.dirname(i.dirname(i.resolve(t)));if(i.basename(i.dirname(i.resolve(t)))!=="jobs"||i.basename(i.resolve(t))!==`${e.jobId}.json`)throw new Error("invalid_job");if(i.dirname(i.resolve(e.resultPath))!==i.join(n,"results"))throw new Error("invalid_job");if(i.basename(i.resolve(e.resultPath))!==`${e.jobId}.json`)throw new Error("invalid_job");const s=i.dirname(i.dirname(n));if(i.resolve(e.attemptPath)!==i.join(s,"upgrade-attempt.json"))throw new Error("invalid_job");const l=ne(t,e),c=re(e),f=r.execFile??j,a=x(e),u=i.join(n,"active-job.json"),w=r.sleep??(d=>new Promise(p=>setTimeout(p,d)));let v=!1,g={};const S=d=>{d&&ee(e),k(e.attemptPath,{from:d?e.targetVersion:e.fromVersion,to:d?e.fromVersion:e.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:e.jobId,operation:d?"rollback":"upgrade"}),c("starting",d?{operation:"rollback",...g,effectiveVersion:e.fromVersion,targetVersion:e.fromVersion,fromVersion:e.targetVersion}:{})},_=async d=>{if(r.serviceRecovery)return;const p=(r.now??Date.now)()+(r.healthTimeoutMs??9e4);for(;(r.now??Date.now)()<p;){if(m(e.resultPath)?.state===(d?"rolled_back":"healthy"))return;await w(250)}throw new Error("daemon_health_timeout")},H=async()=>{const d=m(i.join(s,"daemon.pid"));d?.version===e.targetVersion&&Number.isSafeInteger(d.pid)&&typeof d.pid=="number"&&d.pid!==process.pid&&await J({...e,oldDaemonPid:d.pid},r)},R=async()=>{if(b(e,"rolling_back"),r.serviceRecovery||await C(e,f),await H(),!o.existsSync(a.previous))y(a.active,e.fromVersion);else{if(y(a.previous,e.fromVersion),o.existsSync(a.active)){if(o.existsSync(a.rejected))throw new Error("rollback_destination_exists");F(a.active,a.rejected)}F(a.previous,a.active)}b(e,"restored"),y(a.active,e.fromVersion);const d=await E(e,e.fromVersion,f);S(!0),r.serviceRecovery||await P(e,d,f),await _(!0)};try{const d=m(e.resultPath);if(d?.jobId===e.jobId&&["healthy","rolled_back","failed"].includes(d.state??""))return;const p=m(a.marker);if(p&&(p.jobId!==(e.rollbackJobId??e.jobId)||p.fromVersion!==(e.operation==="rollback"?e.targetVersion:e.fromVersion)))throw new Error("invalid_transaction");if(e.operation==="rollback"){e={...e,fromVersion:e.targetVersion,targetVersion:e.fromVersion},v=!0,k(u,{jobPath:t}),await J(e,r),await R();return}if(p&&p.phase!=="prepared"){v=!0,p.phase==="restored"||p.phase==="rolling_back"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),S(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await _(!0)):p.phase==="switching"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),S(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await _(!0)):await R();return}if(p||(c("installing"),await X(e,r)),y(a.candidate,e.targetVersion),y(a.active,e.fromVersion),c("waiting_for_daemon"),e.requireHandoffAck){const G=(r.now??Date.now)()+1e4;for(;m(`${e.resultPath}.handoff`)?.jobId!==e.jobId;){if((r.now??Date.now)()>=G)throw new Error("handoff_not_confirmed");await w(50)}}await w(250),k(u,{jobPath:t}),r.serviceRecovery||await C(e,f),await J(e,r),b(e,"switching"),v=!0,F(a.active,a.previous),F(a.candidate,a.active),c("verifying"),y(a.active,e.targetVersion);const V=await E(e,e.targetVersion,f);b(e,"starting"),S(!1),r.serviceRecovery||await P(e,V,f),await _(!1)}catch(d){const p=d.code??(d instanceof Error?d.message:"internal_error");if(!v){c("failed",{errorCode:p,errorSummary:A(d)}),(!e.oldDaemonPid||!(r.isAlive??I)(e.oldDaemonPid))&&(y(a.active,e.fromVersion),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f));return}g={errorCode:p,errorSummary:A(d)};try{await R()}catch(V){throw c("failed",{errorCode:"rollback_failed",errorSummary:A(V)}),V}}finally{const d=m(e.resultPath);if(["healthy","rolled_back"].includes(d?.state??""))try{Q(e)}catch{}l()}}async function ie(t){const r=m(i.join(t,"active-job.json"));if(!r?.jobPath)return;const e=i.resolve(r.jobPath);if(i.dirname(e)!==i.join(i.resolve(t),"jobs"))throw new Error("invalid_job");const n=O(e);for(;;){const s=m(n.resultPath);if(["healthy","rolled_back","failed"].includes(s?.state??""))return;const l=m(i.join(t,"updater.lock")),c=l?.pid!==process.pid&&$(l),f=m(x(n).marker);if(c){if(s?.state==="starting"&&["starting","restored"].includes(f?.phase??""))return;await new Promise(a=>setTimeout(a,250));continue}await M(e,{serviceRecovery:!0});return}}function z(t){try{return o.realpathSync.native(t)}catch{return i.resolve(t)}}const oe=process.argv[1]?z(process.argv[1]):"",ae=z(q(import.meta.url));if(oe===ae)if(process.argv[2]==="--service"){const[,,,t,r,...e]=process.argv;ie(t).then(async()=>{process.argv=[process.execPath,r,...e],await import(K(r).href)}).catch(()=>{process.exitCode=1})}else{const t=process.argv[2];t?M(t).catch(()=>{process.exitCode=1}):process.exitCode=2}export{me as assertUpdaterAvailable,Y as finalizeUpdaterCandidate,X as prepareUpdaterCandidate,ie as recoverServiceEntry,M as runUpdaterJob,x as updaterPaths,y as verifyLocalPackage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.40",
3
+ "version": "0.4.42",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {