VS Code extension that previews Archify diagrams from their JSON sources (live, as you type) and opens rendered Archify HTML in a viewer tab. Bundles the Archify 3.0.1 renderer and runs it on VS Code's Node runtime. Adds validation diagnostics, JSON schema help, source-link navigation, export saving, render-to-file and open-in-browser commands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
92 lines
3.6 KiB
JavaScript
92 lines
3.6 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const childPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'scripts', 'delivery-update-child.mjs');
|
|
const DEADLINE_MS = 1_000;
|
|
const MAX_OUTPUT_BYTES = 16 * 1_024;
|
|
|
|
function unavailable(reason) {
|
|
return { status: 'unavailable', installedVersion: null, availableVersion: null,
|
|
releaseNotes: null, checkedAt: null, source: null, noticeRequired: false,
|
|
noticeText: null, reason };
|
|
}
|
|
|
|
function normalize(result) {
|
|
if (result?.status === 'current') return {
|
|
status: 'current', installedVersion: result.installedVersion,
|
|
availableVersion: result.availableVersion, releaseNotes: null,
|
|
checkedAt: result.checkedAt, source: result.source,
|
|
noticeRequired: false, noticeText: null,
|
|
};
|
|
if (result?.status !== 'update_available') {
|
|
return unavailable(result?.reason || (result?.status === 'silent' ? 'no-update' : 'invalid-result'));
|
|
}
|
|
const noticeRequired = result.noticeRequired !== false;
|
|
const cached = result.source === 'cache';
|
|
const noticeText = noticeRequired
|
|
? `Archify ${result.severity === 'security' ? 'security update' : 'update'}: ${result.installedVersion} → ${result.latestVersion}. ${cached
|
|
? `A previous check at ${result.checkedAt} found a newer release.`
|
|
: 'A newer release is available.'} Release notes: ${result.releaseNotes}. The installed Skill has not changed; ask to snooze or ignore this reminder.`
|
|
: null;
|
|
return {
|
|
status: 'update_available',
|
|
installedVersion: result.installedVersion,
|
|
availableVersion: result.latestVersion,
|
|
releaseNotes: result.releaseNotes,
|
|
severity: result.severity,
|
|
checkedAt: result.checkedAt,
|
|
source: result.source,
|
|
noticeRequired,
|
|
noticeText,
|
|
...(result.eventKey ? { eventKey: result.eventKey } : {}),
|
|
...(result.reason ? { reason: result.reason } : {}),
|
|
...(result.suppressedUntil ? { suppressedUntil: result.suppressedUntil } : {}),
|
|
};
|
|
}
|
|
|
|
export function startDeliveryUpdateCheck({
|
|
env = process.env, deadlineMs = DEADLINE_MS, checkerPath = childPath,
|
|
} = {}) {
|
|
if (env.ARCHIFY_UPDATE_CHECK_DISABLED === '1') return Promise.resolve(unavailable('disabled'));
|
|
return new Promise((resolve) => {
|
|
let child;
|
|
try {
|
|
const deadlineNs = process.hrtime.bigint() + BigInt(Math.floor(deadlineMs * 1_000_000));
|
|
child = spawn(process.execPath, [checkerPath, String(deadlineNs)], {
|
|
env,
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
windowsHide: true,
|
|
});
|
|
} catch {
|
|
resolve(unavailable('runtime-unavailable'));
|
|
return;
|
|
}
|
|
let output = '';
|
|
let timedOut = false;
|
|
let overflow = false;
|
|
const timer = setTimeout(() => {
|
|
timedOut = true;
|
|
child.kill('SIGKILL');
|
|
}, deadlineMs);
|
|
child.stdout.on('data', (chunk) => {
|
|
if (Buffer.byteLength(output) + chunk.length > MAX_OUTPUT_BYTES) {
|
|
overflow = true;
|
|
child.kill('SIGKILL');
|
|
} else output += chunk.toString('utf8');
|
|
});
|
|
child.on('error', () => {});
|
|
child.on('close', (code) => {
|
|
clearTimeout(timer);
|
|
if (overflow) return resolve(unavailable('invalid-result'));
|
|
// A synchronous renderer can delay both this callback and the parent's
|
|
// timer. A complete child result wins even if the timer fired meanwhile.
|
|
if (code === 0 && output.endsWith('\n')) {
|
|
try { return resolve(normalize(JSON.parse(output))); }
|
|
catch { return resolve(unavailable('invalid-result')); }
|
|
}
|
|
resolve(unavailable(timedOut || child.signalCode === 'SIGKILL' ? 'timeout' : 'check-failed'));
|
|
});
|
|
});
|
|
}
|