@apilift/cli 1.4.33 → 1.4.35

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.
@@ -19866,6 +19866,55 @@ var extensionPageMessageSchema = external_exports.discriminatedUnion("operation"
19866
19866
  }).strict()
19867
19867
  ]);
19868
19868
 
19869
+ // src/capture-server/windows-owner.ts
19870
+ import { Worker } from "node:worker_threads";
19871
+ var script = `$ErrorActionPreference='Stop'; while ($null -ne ($line=[Console]::ReadLine())) { $parts=$line.Split('|'); try { $p=[System.Diagnostics.Process]::GetProcessById([int]$parts[1]); $stamp=([DateTimeOffset]$p.StartTime.ToUniversalTime()).ToUnixTimeMilliseconds(); $p.Dispose() } catch { $stamp=0 }; [Console]::WriteLine($parts[0]+'|'+$stamp); [Console]::Out.Flush() }`;
19872
+ var workerSource = `
19873
+ const {workerData}=require('node:worker_threads');
19874
+ const {spawn}=require('node:child_process');
19875
+ const state=new Int32Array(workerData.memory,0,4), value=new Float64Array(workerData.memory,16,1);
19876
+ const child=spawn('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-Command',workerData.script],{windowsHide:true,stdio:['pipe','pipe','ignore']});
19877
+ const fail=()=>{Atomics.store(state,0,3);Atomics.notify(state,0);};
19878
+ child.on('error',fail);child.on('exit',fail);
19879
+ let text='';
19880
+ child.stdout.on('data',chunk=>{
19881
+ text+=chunk.toString();
19882
+ let end;
19883
+ while((end=text.indexOf('\\n'))>=0){
19884
+ const [id,stamp]=text.slice(0,end).trim().split('|').map(Number);text=text.slice(end+1);
19885
+ if(id!==Atomics.load(state,1)||Atomics.load(state,0)!==1)continue;
19886
+ value[0]=Number.isFinite(stamp)?stamp:0;Atomics.store(state,0,2);Atomics.notify(state,0);
19887
+ }
19888
+ if(text.length>1024){child.kill();fail();}
19889
+ });
19890
+ const {parentPort}=require('node:worker_threads');
19891
+ parentPort.on('message',({id,pid})=>{try{child.stdin.write(id+'|'+pid+'\\n');}catch{fail();}});
19892
+ `;
19893
+ var worker;
19894
+ var memory;
19895
+ var sequence = 0;
19896
+ function windowsProcessStart(pid) {
19897
+ if (!worker) {
19898
+ memory = new SharedArrayBuffer(24);
19899
+ worker = new Worker(workerSource, { eval: true, workerData: { memory, script } });
19900
+ worker.on("error", () => {
19901
+ });
19902
+ worker.unref();
19903
+ }
19904
+ const state = new Int32Array(memory, 0, 4), value = new Float64Array(memory, 16, 1);
19905
+ const id2 = ++sequence;
19906
+ Atomics.store(state, 1, id2);
19907
+ Atomics.store(state, 0, 1);
19908
+ worker.postMessage({ id: id2, pid });
19909
+ Atomics.wait(state, 0, 1, 4e3);
19910
+ if (Atomics.load(state, 0) === 2 && value[0] > 0) return value[0];
19911
+ if (Atomics.load(state, 0) !== 2) {
19912
+ void worker.terminate();
19913
+ worker = void 0;
19914
+ }
19915
+ return null;
19916
+ }
19917
+
19869
19918
  // src/capture-server/store.ts
19870
19919
  import { randomUUID, createHash } from "node:crypto";
19871
19920
  import { mkdirSync, chmodSync, readFileSync as readFileSync2 } from "node:fs";
@@ -19934,6 +19983,10 @@ function inspectOwner(pid, expectedStartedAt, probe) {
19934
19983
  return false;
19935
19984
  }
19936
19985
  }
19986
+ if (windows && !probe.inspect) {
19987
+ const observed = windowsProcessStart(pid);
19988
+ return observed !== null && Math.abs(observed - expectedStartedAt) <= 5e3;
19989
+ }
19937
19990
  for (let attempt = 0; attempt < (windows ? 2 : 1); attempt++) {
19938
19991
  if (!alive(pid)) return false;
19939
19992
  const remaining = windows ? 8e3 - (now() - started) : 2e3;
@@ -19974,6 +20027,7 @@ var CaptureStore = class {
19974
20027
  if (!hadCandidates) this.db.exec(`INSERT INTO capture_candidates (scope_id,seq,capture_id,entry_json) SELECT e.scope_id,g.first_seq,g.capture_id,e.entry_json FROM capture_entries e JOIN (SELECT scope_id,COALESCE(json_extract(entry_json,'$.captureId'),CAST(seq AS TEXT)) AS capture_id,MIN(seq) AS first_seq,MAX(seq) AS last_seq FROM capture_entries GROUP BY scope_id,capture_id) g ON e.scope_id=g.scope_id AND e.seq=g.last_seq`);
19975
20028
  const columns = this.db.prepare("PRAGMA table_info(scopes)").all();
19976
20029
  if (!columns.some((column) => column.name === "owner_session")) this.db.exec("ALTER TABLE scopes ADD COLUMN owner_session TEXT");
20030
+ if (!this.db.prepare("PRAGMA table_info(bridge_status)").all().some((column) => column.name === "sequence")) this.db.exec("ALTER TABLE bridge_status ADD COLUMN sequence INTEGER NOT NULL DEFAULT 0");
19977
20031
  if (!this.db.prepare("PRAGMA table_info(bridge_status)").all().some((column) => column.name === "capabilities_json")) this.db.exec("ALTER TABLE bridge_status ADD COLUMN capabilities_json TEXT NOT NULL DEFAULT '[]'");
19978
20032
  if (!columns.some((column) => column.name === "start_url")) this.db.exec("ALTER TABLE scopes ADD COLUMN start_url TEXT");
19979
20033
  if (!columns.some((column) => column.name === "binding_mode")) this.db.exec("ALTER TABLE scopes ADD COLUMN binding_mode TEXT");
@@ -20136,8 +20190,11 @@ var CaptureStore = class {
20136
20190
  this.prune();
20137
20191
  return Boolean(this.db.prepare("SELECT 1 FROM scopes WHERE retained_at IS NULL LIMIT 1").get() || this.db.prepare("SELECT 1 FROM jobs j JOIN scopes s ON s.id=j.scope_id WHERE s.retained_at IS NULL LIMIT 1").get() || this.db.prepare("SELECT 1 FROM scopes s WHERE s.retained_at>? AND (EXISTS (SELECT 1 FROM capture_candidates c WHERE c.scope_id=s.id AND json_extract(c.entry_json,'$.pending')=1) OR EXISTS (SELECT 1 FROM capture_body_refs r LEFT JOIN blobs b ON b.scope_id=r.scope_id AND b.digest=r.digest WHERE r.scope_id=s.id AND COALESCE(b.complete,0)=0)) LIMIT 1").get(this.clock() - 2e4));
20138
20192
  }
20193
+ wakeBridge() {
20194
+ this.emit();
20195
+ }
20139
20196
  bridgeReady(version2, capabilities = []) {
20140
- this.db.prepare("INSERT INTO bridge_status(id,version,updated_at,capabilities_json) VALUES(1,?,?,?) ON CONFLICT(id) DO UPDATE SET version=excluded.version,updated_at=excluded.updated_at,capabilities_json=excluded.capabilities_json").run(version2, this.clock(), json3(capabilities));
20197
+ this.db.prepare("INSERT INTO bridge_status(id,version,updated_at,capabilities_json) VALUES(1,?,?,?) ON CONFLICT(id) DO UPDATE SET version=excluded.version,updated_at=excluded.updated_at,capabilities_json=excluded.capabilities_json,sequence=bridge_status.sequence+1").run(version2, this.clock(), json3(capabilities));
20141
20198
  }
20142
20199
  bridgeCapabilities() {
20143
20200
  const row = this.db.prepare("SELECT capabilities_json FROM bridge_status WHERE id=1 AND updated_at>?").get(this.clock() - 6e4);
@@ -20312,6 +20369,15 @@ async function startCaptureServer(options) {
20312
20369
  const store = new CaptureStore(root, options.clock, options.alive);
20313
20370
  const token = randomBytes(32).toString("base64url");
20314
20371
  let lastActiveAt = Date.now();
20372
+ async function untilChanged(read, ready, waitMs) {
20373
+ const deadline = Date.now() + Math.max(0, Math.min(25e3, Number.isFinite(waitMs) ? waitMs : 0));
20374
+ for (; ; ) {
20375
+ const revision = (await store.changes(-1, 0)).revision;
20376
+ const value = read();
20377
+ if (ready(value) || Date.now() >= deadline) return value;
20378
+ await store.changes(revision, deadline - Date.now());
20379
+ }
20380
+ }
20315
20381
  const server = createServer(async (request2, response) => {
20316
20382
  try {
20317
20383
  if (!equalToken(request2.headers.authorization?.replace(/^Bearer /, "") ?? "", token)) {
@@ -20333,6 +20399,12 @@ async function startCaptureServer(options) {
20333
20399
  send(response, 200, { scopes: store.scopes(), extensionCapabilities: store.bridgeCapabilities() });
20334
20400
  return;
20335
20401
  }
20402
+ if (path === "/bridge/demand" && request2.method === "POST") {
20403
+ await readBody(request2);
20404
+ store.wakeBridge();
20405
+ send(response, 200, { ok: true });
20406
+ return;
20407
+ }
20336
20408
  if (path === "/bridge/ready" && request2.method === "POST") {
20337
20409
  const body2 = object2(await readBody(request2));
20338
20410
  if (typeof body2.version !== "string" || body2.capabilities !== void 0 && (!Array.isArray(body2.capabilities) || body2.capabilities.some((value) => typeof value !== "string"))) throw new CaptureError("invalid-bridge-version", "Native host version and supported capabilities are required");
@@ -20341,7 +20413,8 @@ async function startCaptureServer(options) {
20341
20413
  return;
20342
20414
  }
20343
20415
  if (path === "/scope/candidates" && request2.method === "GET") {
20344
- send(response, 200, { candidates: store.candidates(String(url3.searchParams.get("id") ?? "")) });
20416
+ const candidates = await untilChanged(() => store.candidates(String(url3.searchParams.get("id") ?? "")), (value) => value.length > 0, Number(url3.searchParams.get("waitMs") ?? 0));
20417
+ send(response, 200, { candidates });
20345
20418
  return;
20346
20419
  }
20347
20420
  if (path === "/scope/entry" && request2.method === "GET") {
@@ -20363,7 +20436,7 @@ async function startCaptureServer(options) {
20363
20436
  return;
20364
20437
  }
20365
20438
  if (path === "/job/result" && request2.method === "GET") {
20366
- const result = store.jobResult(String(url3.searchParams.get("id") ?? ""));
20439
+ const result = await untilChanged(() => store.jobResult(String(url3.searchParams.get("id") ?? "")), (value) => !value || ["delivered", "uncertain"].includes(value.status), Number(url3.searchParams.get("waitMs") ?? 0));
20367
20440
  send(response, result ? 200 : 404, result ?? { error: { code: "job-missing", message: "Job not found" } });
20368
20441
  return;
20369
20442
  }