@wipcomputer/wip-ldm-os 0.4.85-alpha.12 → 0.4.85-alpha.14
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/bin/ldm.js +76 -50
- package/package.json +2 -1
- package/scripts/test-installer-target-self-update.mjs +131 -0
package/bin/ldm.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, cpSync, chmodSync, unlinkSync, readlinkSync, renameSync, statSync, lstatSync, symlinkSync } from 'node:fs';
|
|
24
24
|
import { join, basename, resolve, dirname } from 'node:path';
|
|
25
|
-
import { execSync } from 'node:child_process';
|
|
25
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
|
|
28
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -229,6 +229,62 @@ function checkCliVersion() {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
function selectedLdmNpmTrack() {
|
|
233
|
+
return ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function selectedLdmTrackLabel(npmTag) {
|
|
237
|
+
return npmTag === 'latest' ? '' : ` (${npmTag} track)`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function latestLdmCliForSelectedTrack() {
|
|
241
|
+
const npmTag = selectedLdmNpmTrack();
|
|
242
|
+
const npmViewCmd = npmTag === 'latest'
|
|
243
|
+
? 'npm view @wipcomputer/wip-ldm-os version 2>/dev/null'
|
|
244
|
+
: `npm view @wipcomputer/wip-ldm-os dist-tags.${npmTag} 2>/dev/null`;
|
|
245
|
+
const latest = execSync(npmViewCmd, {
|
|
246
|
+
encoding: 'utf8',
|
|
247
|
+
timeout: 15000,
|
|
248
|
+
}).trim();
|
|
249
|
+
return { latest, npmTag };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function maybeSelfUpdateLdmCliBeforeInstall() {
|
|
253
|
+
// This shared preflight covers both bare `ldm install` and targeted
|
|
254
|
+
// `ldm install <app>`. Dry runs never update, but still disclose skew.
|
|
255
|
+
if (process.env.LDM_SELF_UPDATED) return;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const { latest, npmTag } = latestLdmCliForSelectedTrack();
|
|
259
|
+
if (!latest || !semverNewer(latest, PKG_VERSION)) return;
|
|
260
|
+
|
|
261
|
+
const trackLabel = selectedLdmTrackLabel(npmTag);
|
|
262
|
+
|
|
263
|
+
if (DRY_RUN) {
|
|
264
|
+
console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}${trackLabel} is available.`);
|
|
265
|
+
console.log(` Dry run only: continuing with v${PKG_VERSION}.`);
|
|
266
|
+
console.log('');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}${trackLabel}. Updating first...`);
|
|
271
|
+
try {
|
|
272
|
+
execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
|
|
273
|
+
console.log(` CLI updated to v${latest}. Re-running with new code...`);
|
|
274
|
+
console.log('');
|
|
275
|
+
const reArgs = process.argv.slice(2);
|
|
276
|
+
const child = spawnSync('ldm', reArgs.length > 0 ? reArgs : ['install'], {
|
|
277
|
+
stdio: 'inherit',
|
|
278
|
+
env: { ...process.env, LDM_SELF_UPDATED: '1' },
|
|
279
|
+
});
|
|
280
|
+
if (child.error) throw child.error;
|
|
281
|
+
process.exit(child.status ?? 1);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.log(` ! Self-update failed: ${e.message}. Continuing with v${PKG_VERSION}.`);
|
|
284
|
+
}
|
|
285
|
+
} catch {}
|
|
286
|
+
}
|
|
287
|
+
|
|
232
288
|
// ── Dead backup trigger cleanup (#207) ──
|
|
233
289
|
// Three backup systems were competing. Only ai.openclaw.ldm-backup (3am) works.
|
|
234
290
|
// This removes: broken cron entry (LDMDevTools.app), old com.wipcomputer.daily-backup.
|
|
@@ -1440,23 +1496,6 @@ async function showCatalogPicker() {
|
|
|
1440
1496
|
// ── ldm install ──
|
|
1441
1497
|
|
|
1442
1498
|
async function cmdInstall() {
|
|
1443
|
-
if (!DRY_RUN && !acquireInstallLock()) return;
|
|
1444
|
-
|
|
1445
|
-
// Ensure LDM is initialized
|
|
1446
|
-
if (!existsSync(VERSION_PATH)) {
|
|
1447
|
-
console.log(' LDM OS not initialized. Running init first...');
|
|
1448
|
-
console.log('');
|
|
1449
|
-
cmdInit();
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
|
|
1453
|
-
const { detectInterfacesJSON } = await import('../lib/detect.mjs');
|
|
1454
|
-
|
|
1455
|
-
// Refresh harness detection (catches newly installed harnesses)
|
|
1456
|
-
detectHarnesses();
|
|
1457
|
-
|
|
1458
|
-
setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT, origin: 'manual' });
|
|
1459
|
-
|
|
1460
1499
|
// --help flag (#81)
|
|
1461
1500
|
if (args.includes('--help') || args.includes('-h')) {
|
|
1462
1501
|
console.log(`
|
|
@@ -1476,6 +1515,25 @@ async function cmdInstall() {
|
|
|
1476
1515
|
process.exit(0);
|
|
1477
1516
|
}
|
|
1478
1517
|
|
|
1518
|
+
maybeSelfUpdateLdmCliBeforeInstall();
|
|
1519
|
+
|
|
1520
|
+
if (!DRY_RUN && !acquireInstallLock()) return;
|
|
1521
|
+
|
|
1522
|
+
// Ensure LDM is initialized
|
|
1523
|
+
if (!existsSync(VERSION_PATH)) {
|
|
1524
|
+
console.log(' LDM OS not initialized. Running init first...');
|
|
1525
|
+
console.log('');
|
|
1526
|
+
cmdInit();
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
|
|
1530
|
+
const { detectInterfacesJSON } = await import('../lib/detect.mjs');
|
|
1531
|
+
|
|
1532
|
+
// Refresh harness detection (catches newly installed harnesses)
|
|
1533
|
+
detectHarnesses();
|
|
1534
|
+
|
|
1535
|
+
setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT, origin: 'manual' });
|
|
1536
|
+
|
|
1479
1537
|
// Find the target (skip flags)
|
|
1480
1538
|
const target = args.slice(1).find(a => !a.startsWith('--'));
|
|
1481
1539
|
|
|
@@ -1921,38 +1979,6 @@ async function cmdInstallCatalog() {
|
|
|
1921
1979
|
// No lock here. cmdInstall() already holds it when calling this.
|
|
1922
1980
|
installLog(`ldm install started (v${PKG_VERSION}, DRY_RUN=${DRY_RUN})`);
|
|
1923
1981
|
|
|
1924
|
-
// Self-update: check if CLI itself is outdated. Update first, then re-exec.
|
|
1925
|
-
// This breaks the chicken-and-egg: new features in ldm install are always
|
|
1926
|
-
// available because the installer upgrades itself before doing anything else.
|
|
1927
|
-
// --alpha and --beta flags check the corresponding npm dist-tag instead of @latest.
|
|
1928
|
-
if (!DRY_RUN && !process.env.LDM_SELF_UPDATED) {
|
|
1929
|
-
try {
|
|
1930
|
-
const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
|
|
1931
|
-
const trackLabel = npmTag === 'latest' ? '' : ` (${npmTag} track)`;
|
|
1932
|
-
const npmViewCmd = npmTag === 'latest'
|
|
1933
|
-
? 'npm view @wipcomputer/wip-ldm-os version 2>/dev/null'
|
|
1934
|
-
: `npm view @wipcomputer/wip-ldm-os dist-tags.${npmTag} 2>/dev/null`;
|
|
1935
|
-
const latest = execSync(npmViewCmd, {
|
|
1936
|
-
encoding: 'utf8', timeout: 15000,
|
|
1937
|
-
}).trim();
|
|
1938
|
-
if (latest && semverNewer(latest, PKG_VERSION)) {
|
|
1939
|
-
console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}${trackLabel}. Updating first...`);
|
|
1940
|
-
try {
|
|
1941
|
-
execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
|
|
1942
|
-
console.log(` CLI updated to v${latest}. Re-running with new code...`);
|
|
1943
|
-
console.log('');
|
|
1944
|
-
// Re-exec with the new binary. LDM_SELF_UPDATED prevents infinite loop.
|
|
1945
|
-
// process.argv.slice(2) skips 'node' and the script path, keeps just 'install' + flags
|
|
1946
|
-
const reArgs = process.argv.slice(2).join(' ') || 'install';
|
|
1947
|
-
execSync(`LDM_SELF_UPDATED=1 ldm ${reArgs}`, { stdio: 'inherit' });
|
|
1948
|
-
process.exit(0);
|
|
1949
|
-
} catch (e) {
|
|
1950
|
-
console.log(` ! Self-update failed: ${e.message}. Continuing with v${PKG_VERSION}.`);
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
} catch {}
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
1982
|
autoDetectExtensions();
|
|
1957
1983
|
|
|
1958
1984
|
// Migrate old registry entries to v2 format (#262)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.4.85-alpha.
|
|
3
|
+
"version": "0.4.85-alpha.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"engines": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"test:skill-frontmatter": "node scripts/test-skill-frontmatter.mjs",
|
|
24
24
|
"test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
|
|
25
25
|
"test:installer-hook-toolname": "node scripts/test-installer-hook-toolname.mjs",
|
|
26
|
+
"test:installer-target-self-update": "node scripts/test-installer-target-self-update.mjs",
|
|
26
27
|
"test:installer-skill-directory": "node scripts/test-installer-skill-directory.mjs",
|
|
27
28
|
"test:installer-skill-dry-run-destinations": "node scripts/test-installer-skill-dry-run-destinations.mjs",
|
|
28
29
|
"test:ldm-install-bin-shim": "node scripts/test-ldm-install-preserves-foreign-bin.mjs",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const cli = readFileSync(join(root, 'bin', 'ldm.js'), 'utf8');
|
|
10
|
+
|
|
11
|
+
const helperName = 'function maybeSelfUpdateLdmCliBeforeInstall()';
|
|
12
|
+
const helperIdx = cli.indexOf(helperName);
|
|
13
|
+
if (helperIdx === -1) {
|
|
14
|
+
throw new Error('Missing shared LDM OS self-update preflight helper');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const helperBlock = cli.slice(helperIdx, cli.indexOf('// ── Dead backup trigger cleanup', helperIdx));
|
|
18
|
+
if (!helperBlock.includes('Dry run only: continuing with v${PKG_VERSION}.')) {
|
|
19
|
+
throw new Error('Self-update helper must warn on dry run without updating');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!helperBlock.includes("execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`")) {
|
|
23
|
+
throw new Error('Self-update helper must update LDM OS before real installs');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!helperBlock.includes("spawnSync('ldm'")) {
|
|
27
|
+
throw new Error('Self-update helper must re-run the original install command without shell joining args');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (helperBlock.includes('process.argv.slice(2).join')) {
|
|
31
|
+
throw new Error('Self-update helper must preserve argv boundaries when re-running install');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cmdInstallIdx = cli.indexOf('async function cmdInstall()');
|
|
35
|
+
const lockIdx = cli.indexOf('acquireInstallLock()', cmdInstallIdx);
|
|
36
|
+
const initIdx = cli.indexOf('LDM OS not initialized. Running init first', cmdInstallIdx);
|
|
37
|
+
const targetIdx = cli.indexOf('// Find the target (skip flags)', cmdInstallIdx);
|
|
38
|
+
const preflightCallIdx = cli.indexOf('maybeSelfUpdateLdmCliBeforeInstall();', cmdInstallIdx);
|
|
39
|
+
if (cmdInstallIdx === -1 || targetIdx === -1 || preflightCallIdx === -1) {
|
|
40
|
+
throw new Error('Could not find cmdInstall self-update placement');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (preflightCallIdx > lockIdx || preflightCallIdx > initIdx) {
|
|
44
|
+
throw new Error('Self-update preflight must run before lock acquisition and init work');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (preflightCallIdx > targetIdx) {
|
|
48
|
+
throw new Error('Self-update preflight must run before target resolution so app installs are covered');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const catalogIdx = cli.indexOf('async function cmdInstallCatalog()');
|
|
52
|
+
const oldCatalogBlock = cli.indexOf('Self-update: check if CLI itself is outdated', catalogIdx);
|
|
53
|
+
const autoDetectIdx = cli.indexOf('autoDetectExtensions();', catalogIdx);
|
|
54
|
+
if (oldCatalogBlock !== -1 && oldCatalogBlock < autoDetectIdx) {
|
|
55
|
+
throw new Error('Catalog install should not own the only self-update block');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-target-self-update-'));
|
|
59
|
+
try {
|
|
60
|
+
const home = join(tempRoot, 'home');
|
|
61
|
+
const fakeBin = join(tempRoot, 'bin');
|
|
62
|
+
const target = join(tempRoot, 'target skill with spaces');
|
|
63
|
+
|
|
64
|
+
mkdirSync(join(home, '.ldm'), { recursive: true });
|
|
65
|
+
writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
|
|
66
|
+
version: '0.0.0',
|
|
67
|
+
installed: new Date().toISOString(),
|
|
68
|
+
updated: new Date().toISOString(),
|
|
69
|
+
}, null, 2) + '\n');
|
|
70
|
+
|
|
71
|
+
mkdirSync(fakeBin, { recursive: true });
|
|
72
|
+
const fakeNpm = join(fakeBin, 'npm');
|
|
73
|
+
writeFileSync(fakeNpm, `#!/usr/bin/env bash
|
|
74
|
+
if [ "$1" = "view" ] && [ "$2" = "@wipcomputer/wip-ldm-os" ] && [ "$3" = "dist-tags.alpha" ]; then
|
|
75
|
+
echo "99.0.0-alpha.1"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
echo "unexpected npm command: $*" >&2
|
|
79
|
+
exit 64
|
|
80
|
+
`);
|
|
81
|
+
chmodSync(fakeNpm, 0o755);
|
|
82
|
+
|
|
83
|
+
mkdirSync(target, { recursive: true });
|
|
84
|
+
writeFileSync(join(target, 'SKILL.md'), `---
|
|
85
|
+
name: test-target-skill
|
|
86
|
+
description: Test target skill for installer self-update dry-run checks.
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Test Target Skill
|
|
90
|
+
`);
|
|
91
|
+
|
|
92
|
+
const result = spawnSync(process.execPath, [
|
|
93
|
+
join(root, 'bin', 'ldm.js'),
|
|
94
|
+
'install',
|
|
95
|
+
'--alpha',
|
|
96
|
+
'--dry-run',
|
|
97
|
+
target,
|
|
98
|
+
], {
|
|
99
|
+
cwd: root,
|
|
100
|
+
encoding: 'utf8',
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
HOME: home,
|
|
104
|
+
PATH: `${fakeBin}:${process.env.PATH || ''}`,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (result.status !== 0) {
|
|
109
|
+
throw new Error(`Runtime dry-run exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!result.stdout.includes('LDM OS CLI v')) {
|
|
113
|
+
throw new Error(`Runtime dry-run did not print the LDM OS skew warning\nstdout:\n${result.stdout}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!result.stdout.includes('-> v99.0.0-alpha.1 (alpha track) is available.')) {
|
|
117
|
+
throw new Error(`Runtime dry-run did not include the selected alpha track version\nstdout:\n${result.stdout}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!result.stdout.includes('Dry run only: continuing with v')) {
|
|
121
|
+
throw new Error(`Runtime dry-run did not say it would continue without updating\nstdout:\n${result.stdout}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!result.stdout.includes('Installing: target skill with spaces (dry run)')) {
|
|
125
|
+
throw new Error(`Runtime dry-run did not continue to the targeted install preview\nstdout:\n${result.stdout}`);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log('targeted install self-update regression checks passed');
|