sneakoscope 0.6.79 → 0.6.80
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.
- package/package.json +1 -1
- package/src/cli/main.mjs +95 -7
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +84 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.80",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
package/src/cli/main.mjs
CHANGED
|
@@ -440,10 +440,11 @@ async function wizard(args = []) {
|
|
|
440
440
|
const rl = readline.createInterface({ input, output });
|
|
441
441
|
try {
|
|
442
442
|
console.log('ㅅㅋㅅ Setup UI\n');
|
|
443
|
-
|
|
443
|
+
const currentPackage = await effectivePackageVersion();
|
|
444
|
+
console.log(`Current package: ${currentPackage}`);
|
|
444
445
|
const latest = await npmPackageVersion('sneakoscope');
|
|
445
446
|
if (latest.version) {
|
|
446
|
-
const needsUpdate = compareVersions(latest.version,
|
|
447
|
+
const needsUpdate = compareVersions(latest.version, currentPackage) > 0;
|
|
447
448
|
console.log(`Latest on npm: ${latest.version}${needsUpdate ? ' (update available)' : ''}`);
|
|
448
449
|
if (needsUpdate) {
|
|
449
450
|
const update = await askChoice(rl, 'Update SKS before setup?', ['yes', 'no'], 'yes');
|
|
@@ -496,11 +497,13 @@ async function askChoice(rl, question, choices, fallback) {
|
|
|
496
497
|
|
|
497
498
|
async function updateCheck(args = []) {
|
|
498
499
|
const latest = await npmPackageVersion('sneakoscope');
|
|
500
|
+
const currentPackage = await effectivePackageVersion();
|
|
499
501
|
const result = {
|
|
500
502
|
package: 'sneakoscope',
|
|
501
|
-
current:
|
|
503
|
+
current: currentPackage,
|
|
504
|
+
runtime_current: PACKAGE_VERSION,
|
|
502
505
|
latest: latest.version,
|
|
503
|
-
update_available: latest.version ? compareVersions(latest.version,
|
|
506
|
+
update_available: latest.version ? compareVersions(latest.version, currentPackage) > 0 : false,
|
|
504
507
|
error: latest.error || null
|
|
505
508
|
};
|
|
506
509
|
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
@@ -1124,14 +1127,15 @@ async function madHighCommand(args = []) {
|
|
|
1124
1127
|
async function maybePromptSksUpdateForMad(args = []) {
|
|
1125
1128
|
if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
|
|
1126
1129
|
const latest = await npmPackageVersion('sneakoscope');
|
|
1127
|
-
|
|
1130
|
+
const currentPackage = await effectivePackageVersion();
|
|
1131
|
+
if (!latest.version || compareVersions(latest.version, currentPackage) <= 0) return { status: 'current', latest: latest.version || null, error: latest.error || null };
|
|
1128
1132
|
const command = 'npm i -g sneakoscope@latest';
|
|
1129
1133
|
if (flag(args, '--yes') || flag(args, '-y')) return installSksLatest(command, latest.version);
|
|
1130
1134
|
if (!canAskYesNo()) {
|
|
1131
|
-
console.log(`SKS update available: ${
|
|
1135
|
+
console.log(`SKS update available: ${currentPackage} -> ${latest.version}. Run: ${command}`);
|
|
1132
1136
|
return { status: 'available', latest: latest.version, command };
|
|
1133
1137
|
}
|
|
1134
|
-
const answer = (await askPostinstallQuestion(`SKS ${
|
|
1138
|
+
const answer = (await askPostinstallQuestion(`SKS ${currentPackage} -> ${latest.version} update before MAD launch? [Y/n] `)).trim();
|
|
1135
1139
|
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
1136
1140
|
if (!yes) return { status: 'skipped_by_user', latest: latest.version, command };
|
|
1137
1141
|
return installSksLatest(command, latest.version);
|
|
@@ -1899,6 +1903,15 @@ async function npmPackageVersion(name) {
|
|
|
1899
1903
|
return { version: result.stdout.trim().split(/\s+/).pop() };
|
|
1900
1904
|
}
|
|
1901
1905
|
|
|
1906
|
+
async function effectivePackageVersion() {
|
|
1907
|
+
const pkg = await readJson(path.join(packageRoot(), 'package.json'), {}).catch(() => ({}));
|
|
1908
|
+
return highestVersion([PACKAGE_VERSION, pkg.version]);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
function highestVersion(versions = []) {
|
|
1912
|
+
return versions.filter(Boolean).reduce((best, candidate) => compareVersions(candidate, best) > 0 ? candidate : best, '0.0.0');
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1902
1915
|
function compareVersions(a, b) {
|
|
1903
1916
|
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
1904
1917
|
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
@@ -2263,6 +2276,81 @@ async function selftest() {
|
|
|
2263
2276
|
const hookState = await readJson(stateFile(hookGoalTmp), {});
|
|
2264
2277
|
if (hookState.phase !== 'GOAL_READY' || hookState.mode !== 'GOAL') throw new Error('selftest failed: $Goal hook did not set ready state');
|
|
2265
2278
|
if (!(await exists(path.join(missionDir(hookGoalTmp, hookState.mission_id), GOAL_WORKFLOW_ARTIFACT)))) throw new Error('selftest failed: $Goal hook did not write goal workflow artifact');
|
|
2279
|
+
const hookUpdateCurrentTmp = tmpdir();
|
|
2280
|
+
await initProject(hookUpdateCurrentTmp, {});
|
|
2281
|
+
const hookUpdateCurrentPayload = JSON.stringify({ cwd: hookUpdateCurrentTmp, prompt: '상태 확인해줘' });
|
|
2282
|
+
const hookUpdateCurrentResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
|
|
2283
|
+
cwd: hookUpdateCurrentTmp,
|
|
2284
|
+
input: hookUpdateCurrentPayload,
|
|
2285
|
+
env: { SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: '9.9.9', SKS_INSTALLED_SKS_VERSION: '9.9.9' },
|
|
2286
|
+
timeoutMs: 15000,
|
|
2287
|
+
maxOutputBytes: 256 * 1024
|
|
2288
|
+
});
|
|
2289
|
+
if (hookUpdateCurrentResult.code !== 0) throw new Error(`selftest failed: current update hook exited ${hookUpdateCurrentResult.code}: ${hookUpdateCurrentResult.stderr}`);
|
|
2290
|
+
const hookUpdateCurrentJson = JSON.parse(hookUpdateCurrentResult.stdout);
|
|
2291
|
+
const hookUpdateCurrentContext = hookUpdateCurrentJson.hookSpecificOutput?.additionalContext || '';
|
|
2292
|
+
if (String(hookUpdateCurrentContext).includes('Update SKS now') || String(hookUpdateCurrentContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook prompted for update even though installed SKS is current');
|
|
2293
|
+
const hookUpdateCurrentState = await readJson(path.join(hookUpdateCurrentTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2294
|
+
if (hookUpdateCurrentState.pending_offer) throw new Error('selftest failed: current installed SKS left a pending update offer');
|
|
2295
|
+
if (hookUpdateCurrentState.current !== '9.9.9' || hookUpdateCurrentState.runtime_current !== PACKAGE_VERSION || hookUpdateCurrentState.installed_current !== '9.9.9') throw new Error('selftest failed: hook did not record effective installed SKS version');
|
|
2296
|
+
const hookUpdatePendingTmp = tmpdir();
|
|
2297
|
+
await initProject(hookUpdatePendingTmp, {});
|
|
2298
|
+
await writeJsonAtomic(path.join(hookUpdatePendingTmp, '.sneakoscope', 'state', 'update-check.json'), {
|
|
2299
|
+
current: PACKAGE_VERSION,
|
|
2300
|
+
latest: '9.9.9',
|
|
2301
|
+
pending_offer: { conversation_id: hookUpdatePendingTmp, latest: '9.9.9', offered_at: nowIso() }
|
|
2302
|
+
});
|
|
2303
|
+
const hookUpdatePendingPayload = JSON.stringify({ cwd: hookUpdatePendingTmp, prompt: 'Update SKS now' });
|
|
2304
|
+
const hookUpdatePendingResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
|
|
2305
|
+
cwd: hookUpdatePendingTmp,
|
|
2306
|
+
input: hookUpdatePendingPayload,
|
|
2307
|
+
env: { SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: '9.9.9', SKS_INSTALLED_SKS_VERSION: '9.9.9' },
|
|
2308
|
+
timeoutMs: 15000,
|
|
2309
|
+
maxOutputBytes: 256 * 1024
|
|
2310
|
+
});
|
|
2311
|
+
if (hookUpdatePendingResult.code !== 0) throw new Error(`selftest failed: stale pending update hook exited ${hookUpdatePendingResult.code}: ${hookUpdatePendingResult.stderr}`);
|
|
2312
|
+
const hookUpdatePendingJson = JSON.parse(hookUpdatePendingResult.stdout);
|
|
2313
|
+
const hookUpdatePendingContext = hookUpdatePendingJson.hookSpecificOutput?.additionalContext || '';
|
|
2314
|
+
if (String(hookUpdatePendingContext).includes('user accepted update') || String(hookUpdatePendingContext).includes('Before doing other work')) throw new Error('selftest failed: current installed SKS accepted a stale pending update offer');
|
|
2315
|
+
const hookUpdatePendingState = await readJson(path.join(hookUpdatePendingTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2316
|
+
if (hookUpdatePendingState.pending_offer) throw new Error('selftest failed: stale pending update offer was not cleared after installed SKS became current');
|
|
2317
|
+
const hookUpdateSkippedTmp = tmpdir();
|
|
2318
|
+
await initProject(hookUpdateSkippedTmp, {});
|
|
2319
|
+
await writeJsonAtomic(path.join(hookUpdateSkippedTmp, '.sneakoscope', 'state', 'update-check.json'), {
|
|
2320
|
+
current: PACKAGE_VERSION,
|
|
2321
|
+
latest: '9.9.9',
|
|
2322
|
+
skipped: { conversation_id: hookUpdateSkippedTmp, latest: '9.9.9', skipped_at: nowIso() }
|
|
2323
|
+
});
|
|
2324
|
+
const hookUpdateSkippedPayload = JSON.stringify({ cwd: hookUpdateSkippedTmp, prompt: '상태 확인해줘' });
|
|
2325
|
+
const hookUpdateSkippedResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
|
|
2326
|
+
cwd: hookUpdateSkippedTmp,
|
|
2327
|
+
input: hookUpdateSkippedPayload,
|
|
2328
|
+
env: { SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: '9.9.9', SKS_INSTALLED_SKS_VERSION: '9.9.9' },
|
|
2329
|
+
timeoutMs: 15000,
|
|
2330
|
+
maxOutputBytes: 256 * 1024
|
|
2331
|
+
});
|
|
2332
|
+
if (hookUpdateSkippedResult.code !== 0) throw new Error(`selftest failed: stale skipped update hook exited ${hookUpdateSkippedResult.code}: ${hookUpdateSkippedResult.stderr}`);
|
|
2333
|
+
const hookUpdateSkippedJson = JSON.parse(hookUpdateSkippedResult.stdout);
|
|
2334
|
+
const hookUpdateSkippedContext = hookUpdateSkippedJson.hookSpecificOutput?.additionalContext || '';
|
|
2335
|
+
if (String(hookUpdateSkippedContext).includes('was skipped for this conversation')) throw new Error('selftest failed: current installed SKS kept stale skipped update context');
|
|
2336
|
+
const hookUpdateSkippedState = await readJson(path.join(hookUpdateSkippedTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2337
|
+
if (hookUpdateSkippedState.skipped) throw new Error('selftest failed: stale skipped update state was not cleared after installed SKS became current');
|
|
2338
|
+
const hookUpdateOldTmp = tmpdir();
|
|
2339
|
+
await initProject(hookUpdateOldTmp, {});
|
|
2340
|
+
const hookUpdateOldPayload = JSON.stringify({ cwd: hookUpdateOldTmp, prompt: '상태 확인해줘' });
|
|
2341
|
+
const hookUpdateOldResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
|
|
2342
|
+
cwd: hookUpdateOldTmp,
|
|
2343
|
+
input: hookUpdateOldPayload,
|
|
2344
|
+
env: { SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: '9.9.9', SKS_INSTALLED_SKS_VERSION: '0.0.0' },
|
|
2345
|
+
timeoutMs: 15000,
|
|
2346
|
+
maxOutputBytes: 256 * 1024
|
|
2347
|
+
});
|
|
2348
|
+
if (hookUpdateOldResult.code !== 0) throw new Error(`selftest failed: stale update hook exited ${hookUpdateOldResult.code}: ${hookUpdateOldResult.stderr}`);
|
|
2349
|
+
const hookUpdateOldJson = JSON.parse(hookUpdateOldResult.stdout);
|
|
2350
|
+
const hookUpdateOldContext = hookUpdateOldJson.hookSpecificOutput?.additionalContext || '';
|
|
2351
|
+
if (!String(hookUpdateOldContext).includes('Update SKS now') || !String(hookUpdateOldContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook did not prompt when installed SKS is stale');
|
|
2352
|
+
const hookUpdateOldState = await readJson(path.join(hookUpdateOldTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2353
|
+
if (hookUpdateOldState.pending_offer?.latest !== '9.9.9') throw new Error('selftest failed: stale installed SKS did not persist pending update offer');
|
|
2266
2354
|
const hookKoreanSksTmp = tmpdir();
|
|
2267
2355
|
await initProject(hookKoreanSksTmp, {});
|
|
2268
2356
|
const hookKoreanSksPayload = JSON.stringify({ cwd: hookKoreanSksTmp, prompt: koreanReadmeInstallPrompt });
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.6.
|
|
8
|
+
export const PACKAGE_VERSION = '0.6.80';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdin, nowIso, runProcess, which, PACKAGE_VERSION, sha256 } from './fsx.mjs';
|
|
2
|
+
import { projectRoot, readJson, readText, writeJsonAtomic, appendJsonl, readStdin, nowIso, runProcess, which, PACKAGE_VERSION, sha256, packageRoot } from './fsx.mjs';
|
|
3
3
|
import { looksInteractiveCommand, interactiveCommandReason } from './no-question-guard.mjs';
|
|
4
4
|
import { missionDir, setCurrent, stateFile } from './mission.mjs';
|
|
5
5
|
import { checkDbOperation, dbBlockReason, handleMadSksUserConfirmation } from './db-safety.mjs';
|
|
@@ -334,6 +334,50 @@ async function updateCheckContext(root, payload, prompt) {
|
|
|
334
334
|
const updateState = await readJson(statePath, {});
|
|
335
335
|
const conv = conversationId(payload);
|
|
336
336
|
const pending = updateState.pending_offer;
|
|
337
|
+
let effective = null;
|
|
338
|
+
async function effectiveVersion() {
|
|
339
|
+
if (!effective) {
|
|
340
|
+
const installed = await detectInstalledSksVersion();
|
|
341
|
+
effective = {
|
|
342
|
+
installed,
|
|
343
|
+
current: highestVersion([PACKAGE_VERSION, installed.version])
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return effective;
|
|
347
|
+
}
|
|
348
|
+
if (pending?.latest) {
|
|
349
|
+
const currentCheck = await effectiveVersion();
|
|
350
|
+
if (compareVersions(pending.latest, currentCheck.current) <= 0) {
|
|
351
|
+
await writeJsonAtomic(statePath, {
|
|
352
|
+
...updateState,
|
|
353
|
+
current: currentCheck.current,
|
|
354
|
+
runtime_current: PACKAGE_VERSION,
|
|
355
|
+
installed_current: currentCheck.installed.version || null,
|
|
356
|
+
latest: pending.latest,
|
|
357
|
+
checked_at: nowIso(),
|
|
358
|
+
pending_offer: null,
|
|
359
|
+
check_error: null
|
|
360
|
+
});
|
|
361
|
+
return '';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (updateState.skipped?.latest) {
|
|
365
|
+
const currentCheck = await effectiveVersion();
|
|
366
|
+
if (compareVersions(updateState.skipped.latest, currentCheck.current) <= 0) {
|
|
367
|
+
await writeJsonAtomic(statePath, {
|
|
368
|
+
...updateState,
|
|
369
|
+
current: currentCheck.current,
|
|
370
|
+
runtime_current: PACKAGE_VERSION,
|
|
371
|
+
installed_current: currentCheck.installed.version || null,
|
|
372
|
+
latest: updateState.skipped.latest,
|
|
373
|
+
checked_at: nowIso(),
|
|
374
|
+
pending_offer: null,
|
|
375
|
+
skipped: null,
|
|
376
|
+
check_error: null
|
|
377
|
+
});
|
|
378
|
+
return '';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
337
381
|
if (pending?.conversation_id === conv && pending?.latest && looksLikeUpdateDecline(prompt)) {
|
|
338
382
|
await writeJsonAtomic(statePath, {
|
|
339
383
|
...updateState,
|
|
@@ -354,26 +398,34 @@ async function updateCheckContext(root, payload, prompt) {
|
|
|
354
398
|
return `SKS update check: update ${updateState.skipped.latest} was skipped for this conversation only. Do not ask again in this conversation; check again next conversation.`;
|
|
355
399
|
}
|
|
356
400
|
const check = await checkLatestVersion();
|
|
401
|
+
const { installed, current } = await effectiveVersion();
|
|
402
|
+
const isCurrent = check.latest && compareVersions(check.latest, current) <= 0;
|
|
357
403
|
await writeJsonAtomic(statePath, {
|
|
358
404
|
...updateState,
|
|
359
|
-
current
|
|
405
|
+
current,
|
|
406
|
+
runtime_current: PACKAGE_VERSION,
|
|
407
|
+
installed_current: installed.version || null,
|
|
360
408
|
latest: check.latest || null,
|
|
361
409
|
checked_at: nowIso(),
|
|
410
|
+
pending_offer: isCurrent ? null : updateState.pending_offer || null,
|
|
362
411
|
check_error: check.error || null
|
|
363
412
|
});
|
|
364
|
-
if (!check.latest || check.error ||
|
|
413
|
+
if (!check.latest || check.error || isCurrent) return '';
|
|
365
414
|
await writeJsonAtomic(statePath, {
|
|
366
415
|
...updateState,
|
|
367
|
-
current
|
|
416
|
+
current,
|
|
417
|
+
runtime_current: PACKAGE_VERSION,
|
|
418
|
+
installed_current: installed.version || null,
|
|
368
419
|
latest: check.latest,
|
|
369
420
|
checked_at: nowIso(),
|
|
370
421
|
pending_offer: { conversation_id: conv, latest: check.latest, offered_at: nowIso() },
|
|
371
422
|
skipped: updateState.skipped?.conversation_id === conv ? null : updateState.skipped || null
|
|
372
423
|
});
|
|
373
|
-
return `SKS update check: installed ${
|
|
424
|
+
return `SKS update check: installed ${current}, latest ${check.latest}. Before any other work, ask the user to choose: "Update SKS now" or "Skip update for this conversation". If they choose update, run npm i -g sneakoscope for global installs, or npm i -D sneakoscope && npx sks setup --install-scope project for project installs, then run sks setup and sks doctor --fix. If they skip, do not ask again in this conversation, but check again next conversation.`;
|
|
374
425
|
}
|
|
375
426
|
|
|
376
427
|
async function checkLatestVersion() {
|
|
428
|
+
if (process.env.SKS_NPM_VIEW_SNEAKOSCOPE_VERSION) return { latest: process.env.SKS_NPM_VIEW_SNEAKOSCOPE_VERSION };
|
|
377
429
|
const npm = await which('npm').catch(() => null);
|
|
378
430
|
if (!npm) return { error: 'npm not found' };
|
|
379
431
|
const result = await runProcess(npm, ['view', 'sneakoscope', 'version'], { timeoutMs: 3500, maxOutputBytes: 4096 });
|
|
@@ -381,6 +433,33 @@ async function checkLatestVersion() {
|
|
|
381
433
|
return { latest: result.stdout.trim().split(/\s+/).pop() };
|
|
382
434
|
}
|
|
383
435
|
|
|
436
|
+
async function detectInstalledSksVersion() {
|
|
437
|
+
const override = parseVersionText(process.env.SKS_INSTALLED_SKS_VERSION || '');
|
|
438
|
+
if (override) return { version: override, source: 'env' };
|
|
439
|
+
const candidates = [];
|
|
440
|
+
const pkg = await readJson(path.join(packageRoot(), 'package.json'), {}).catch(() => ({}));
|
|
441
|
+
if (parseVersionText(pkg.version)) candidates.push({ version: parseVersionText(pkg.version), source: 'package.json' });
|
|
442
|
+
const sks = await which('sks').catch(() => null);
|
|
443
|
+
if (!sks) return candidates[0] || { version: null, source: null };
|
|
444
|
+
const result = await runProcess(sks, ['--version'], {
|
|
445
|
+
timeoutMs: 2000,
|
|
446
|
+
maxOutputBytes: 4096,
|
|
447
|
+
env: { SKS_DISABLE_UPDATE_CHECK: '1' }
|
|
448
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
449
|
+
if (result.code === 0 && parseVersionText(result.stdout)) candidates.push({ version: parseVersionText(result.stdout), source: sks });
|
|
450
|
+
if (candidates.length) return candidates.reduce((best, candidate) => compareVersions(candidate.version, best.version) > 0 ? candidate : best);
|
|
451
|
+
return { version: null, source: sks, error: `${result.stderr || result.stdout || 'sks --version failed'}`.trim() };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function parseVersionText(text) {
|
|
455
|
+
const match = String(text || '').match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
|
|
456
|
+
return match ? match[0] : null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function highestVersion(versions = []) {
|
|
460
|
+
return versions.filter(Boolean).reduce((best, candidate) => compareVersions(candidate, best) > 0 ? candidate : best, '0.0.0');
|
|
461
|
+
}
|
|
462
|
+
|
|
384
463
|
function compareVersions(a, b) {
|
|
385
464
|
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
386
465
|
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|