@wipcomputer/wip-ldm-os 0.4.85-alpha.13 → 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 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);
@@ -272,9 +272,13 @@ function maybeSelfUpdateLdmCliBeforeInstall() {
272
272
  execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
273
273
  console.log(` CLI updated to v${latest}. Re-running with new code...`);
274
274
  console.log('');
275
- const reArgs = process.argv.slice(2).join(' ') || 'install';
276
- execSync(`LDM_SELF_UPDATED=1 ldm ${reArgs}`, { stdio: 'inherit' });
277
- process.exit(0);
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);
278
282
  } catch (e) {
279
283
  console.log(` ! Self-update failed: ${e.message}. Continuing with v${PKG_VERSION}.`);
280
284
  }
@@ -1492,23 +1496,6 @@ async function showCatalogPicker() {
1492
1496
  // ── ldm install ──
1493
1497
 
1494
1498
  async function cmdInstall() {
1495
- if (!DRY_RUN && !acquireInstallLock()) return;
1496
-
1497
- // Ensure LDM is initialized
1498
- if (!existsSync(VERSION_PATH)) {
1499
- console.log(' LDM OS not initialized. Running init first...');
1500
- console.log('');
1501
- cmdInit();
1502
- }
1503
-
1504
- const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
1505
- const { detectInterfacesJSON } = await import('../lib/detect.mjs');
1506
-
1507
- // Refresh harness detection (catches newly installed harnesses)
1508
- detectHarnesses();
1509
-
1510
- setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT, origin: 'manual' });
1511
-
1512
1499
  // --help flag (#81)
1513
1500
  if (args.includes('--help') || args.includes('-h')) {
1514
1501
  console.log(`
@@ -1530,6 +1517,23 @@ async function cmdInstall() {
1530
1517
 
1531
1518
  maybeSelfUpdateLdmCliBeforeInstall();
1532
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
+
1533
1537
  // Find the target (skip flags)
1534
1538
  const target = args.slice(1).find(a => !a.startsWith('--'));
1535
1539
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.85-alpha.13",
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": {
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from 'node:fs';
2
+ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
3
4
  import { dirname, join } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
4
6
  import { fileURLToPath } from 'node:url';
5
7
 
6
8
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -21,17 +23,27 @@ if (!helperBlock.includes("execSync(`npm install -g @wipcomputer/wip-ldm-os@${la
21
23
  throw new Error('Self-update helper must update LDM OS before real installs');
22
24
  }
23
25
 
24
- if (!helperBlock.includes('execSync(`LDM_SELF_UPDATED=1 ldm ${reArgs}`')) {
25
- throw new Error('Self-update helper must re-run the original install command');
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');
26
32
  }
27
33
 
28
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);
29
37
  const targetIdx = cli.indexOf('// Find the target (skip flags)', cmdInstallIdx);
30
38
  const preflightCallIdx = cli.indexOf('maybeSelfUpdateLdmCliBeforeInstall();', cmdInstallIdx);
31
39
  if (cmdInstallIdx === -1 || targetIdx === -1 || preflightCallIdx === -1) {
32
40
  throw new Error('Could not find cmdInstall self-update placement');
33
41
  }
34
42
 
43
+ if (preflightCallIdx > lockIdx || preflightCallIdx > initIdx) {
44
+ throw new Error('Self-update preflight must run before lock acquisition and init work');
45
+ }
46
+
35
47
  if (preflightCallIdx > targetIdx) {
36
48
  throw new Error('Self-update preflight must run before target resolution so app installs are covered');
37
49
  }
@@ -43,4 +55,77 @@ if (oldCatalogBlock !== -1 && oldCatalogBlock < autoDetectIdx) {
43
55
  throw new Error('Catalog install should not own the only self-update block');
44
56
  }
45
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
+
46
131
  console.log('targeted install self-update regression checks passed');