@wipcomputer/wip-ldm-os 0.4.72 → 0.4.73-alpha.10
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 +143 -26
- package/lib/deploy.mjs +26 -2
- package/package.json +1 -1
- package/shared/docs/how-install-works.md.tmpl +22 -2
- package/shared/docs/how-releases-work.md.tmpl +57 -43
package/bin/ldm.js
CHANGED
|
@@ -46,6 +46,53 @@ function installLog(msg) {
|
|
|
46
46
|
} catch {}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// ── Semver comparison (#XX) ──
|
|
50
|
+
// Proper semver comparison that handles prereleases correctly.
|
|
51
|
+
// 0.4.73-alpha.1 > 0.4.72 (higher base version, prerelease doesn't matter)
|
|
52
|
+
// 0.4.72-alpha.1 < 0.4.72 (same base version, prerelease is older than stable)
|
|
53
|
+
// 0.4.73 > 0.4.72 (straightforward)
|
|
54
|
+
// Returns true if version `a` is strictly newer than version `b`.
|
|
55
|
+
function semverNewer(a, b) {
|
|
56
|
+
if (!a || !b || a === b) return false;
|
|
57
|
+
// Split into base and prerelease: "0.4.73-alpha.1" -> ["0.4.73", "alpha.1"]
|
|
58
|
+
const [aBase, aPre] = a.split('-', 2);
|
|
59
|
+
const [bBase, bPre] = b.split('-', 2);
|
|
60
|
+
const aParts = aBase.split('.').map(Number);
|
|
61
|
+
const bParts = bBase.split('.').map(Number);
|
|
62
|
+
// Compare base version (major.minor.patch)
|
|
63
|
+
for (let i = 0; i < 3; i++) {
|
|
64
|
+
const av = aParts[i] || 0;
|
|
65
|
+
const bv = bParts[i] || 0;
|
|
66
|
+
if (av > bv) return true;
|
|
67
|
+
if (av < bv) return false;
|
|
68
|
+
}
|
|
69
|
+
// Base versions are equal. Stable > prerelease.
|
|
70
|
+
// If a has no prerelease and b does, a is newer (stable release of same base).
|
|
71
|
+
// If a has prerelease and b doesn't, a is older (prerelease of same base).
|
|
72
|
+
if (!aPre && bPre) return true; // a=0.4.72, b=0.4.72-alpha.1 -> a is newer
|
|
73
|
+
if (aPre && !bPre) return false; // a=0.4.72-alpha.1, b=0.4.72 -> a is older
|
|
74
|
+
// Both have prereleases with same base. Compare prerelease segments lexically.
|
|
75
|
+
if (aPre && bPre) {
|
|
76
|
+
const aSegs = aPre.split('.');
|
|
77
|
+
const bSegs = bPre.split('.');
|
|
78
|
+
for (let i = 0; i < Math.max(aSegs.length, bSegs.length); i++) {
|
|
79
|
+
const as = aSegs[i] || '';
|
|
80
|
+
const bs = bSegs[i] || '';
|
|
81
|
+
// Numeric segments compared numerically
|
|
82
|
+
const an = /^\d+$/.test(as) ? Number(as) : NaN;
|
|
83
|
+
const bn = /^\d+$/.test(bs) ? Number(bs) : NaN;
|
|
84
|
+
if (!isNaN(an) && !isNaN(bn)) {
|
|
85
|
+
if (an > bn) return true;
|
|
86
|
+
if (an < bn) return false;
|
|
87
|
+
} else {
|
|
88
|
+
if (as > bs) return true;
|
|
89
|
+
if (as < bs) return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
49
96
|
// Read our own version from package.json
|
|
50
97
|
const pkgPath = join(__dirname, '..', 'package.json');
|
|
51
98
|
let PKG_VERSION = '0.2.0';
|
|
@@ -148,6 +195,8 @@ const NONE_FLAG = args.includes('--none');
|
|
|
148
195
|
const FIX_FLAG = args.includes('--fix');
|
|
149
196
|
const CLEANUP_FLAG = args.includes('--cleanup');
|
|
150
197
|
const CHECK_FLAG = args.includes('--check');
|
|
198
|
+
const ALPHA_FLAG = args.includes('--alpha');
|
|
199
|
+
const BETA_FLAG = args.includes('--beta');
|
|
151
200
|
|
|
152
201
|
function readJSON(path) {
|
|
153
202
|
try {
|
|
@@ -170,7 +219,7 @@ function checkCliVersion() {
|
|
|
170
219
|
encoding: 'utf8',
|
|
171
220
|
timeout: 10000,
|
|
172
221
|
}).trim();
|
|
173
|
-
if (result && result
|
|
222
|
+
if (result && semverNewer(result, PKG_VERSION)) {
|
|
174
223
|
console.log('');
|
|
175
224
|
console.log(` CLI is outdated: v${PKG_VERSION} installed, v${result} available.`);
|
|
176
225
|
console.log(` Run: npm install -g @wipcomputer/wip-ldm-os@${result}`);
|
|
@@ -1191,6 +1240,8 @@ async function cmdInstall() {
|
|
|
1191
1240
|
--json JSON output
|
|
1192
1241
|
--yes Auto-accept catalog prompts
|
|
1193
1242
|
--none Skip catalog prompts
|
|
1243
|
+
--alpha Check @alpha npm tag for updates (prerelease track)
|
|
1244
|
+
--beta Check @beta npm tag for updates (prerelease track)
|
|
1194
1245
|
`);
|
|
1195
1246
|
process.exit(0);
|
|
1196
1247
|
}
|
|
@@ -1270,23 +1321,31 @@ async function cmdInstall() {
|
|
|
1270
1321
|
|
|
1271
1322
|
// Check if target looks like an npm package (starts with @ or is a plain name without /)
|
|
1272
1323
|
if (resolvedTarget.startsWith('@') || (!resolvedTarget.includes('/') && !existsSync(resolve(resolvedTarget)))) {
|
|
1273
|
-
// Try npm
|
|
1324
|
+
// Try npm pack + tar extract to temp dir
|
|
1325
|
+
// npm install --prefix silently fails for scoped packages in temp directories...
|
|
1326
|
+
// it creates the lock file but doesn't extract files. npm pack is reliable.
|
|
1274
1327
|
const npmName = resolvedTarget;
|
|
1275
1328
|
const tempDir = join(LDM_TMP, `npm-${Date.now()}`);
|
|
1276
1329
|
console.log('');
|
|
1277
1330
|
console.log(` Installing ${npmName} from npm...`);
|
|
1278
1331
|
try {
|
|
1279
1332
|
mkdirSync(tempDir, { recursive: true });
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1333
|
+
// Use npm pack + tar instead of npm install --prefix
|
|
1334
|
+
const tarball = execSync(`npm pack ${npmName} --pack-destination "${tempDir}" 2>/dev/null`, {
|
|
1335
|
+
encoding: 'utf8', timeout: 60000, cwd: tempDir,
|
|
1336
|
+
}).trim();
|
|
1337
|
+
const tarPath = join(tempDir, tarball);
|
|
1338
|
+
if (existsSync(tarPath)) {
|
|
1339
|
+
execSync(`tar xzf "${tarPath}" -C "${tempDir}"`, { stdio: 'pipe' });
|
|
1340
|
+
const extracted = join(tempDir, 'package');
|
|
1341
|
+
if (existsSync(extracted)) {
|
|
1342
|
+
console.log(` + Installed from npm`);
|
|
1343
|
+
repoPath = extracted;
|
|
1344
|
+
} else {
|
|
1345
|
+
console.error(` x npm pack succeeded but extraction failed`);
|
|
1346
|
+
}
|
|
1287
1347
|
} else {
|
|
1288
|
-
console.error(` x
|
|
1289
|
-
process.exit(1);
|
|
1348
|
+
console.error(` x npm pack failed: tarball not found`);
|
|
1290
1349
|
}
|
|
1291
1350
|
} catch (e) {
|
|
1292
1351
|
// npm failed, fall through to git clone or path resolution
|
|
@@ -1498,13 +1557,19 @@ async function cmdInstallCatalog() {
|
|
|
1498
1557
|
// Self-update: check if CLI itself is outdated. Update first, then re-exec.
|
|
1499
1558
|
// This breaks the chicken-and-egg: new features in ldm install are always
|
|
1500
1559
|
// available because the installer upgrades itself before doing anything else.
|
|
1560
|
+
// --alpha and --beta flags check the corresponding npm dist-tag instead of @latest.
|
|
1501
1561
|
if (!DRY_RUN && !process.env.LDM_SELF_UPDATED) {
|
|
1502
1562
|
try {
|
|
1503
|
-
const
|
|
1563
|
+
const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
|
|
1564
|
+
const trackLabel = npmTag === 'latest' ? '' : ` (${npmTag} track)`;
|
|
1565
|
+
const npmViewCmd = npmTag === 'latest'
|
|
1566
|
+
? 'npm view @wipcomputer/wip-ldm-os version 2>/dev/null'
|
|
1567
|
+
: `npm view @wipcomputer/wip-ldm-os dist-tags.${npmTag} 2>/dev/null`;
|
|
1568
|
+
const latest = execSync(npmViewCmd, {
|
|
1504
1569
|
encoding: 'utf8', timeout: 15000,
|
|
1505
1570
|
}).trim();
|
|
1506
|
-
if (latest && latest
|
|
1507
|
-
console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}. Updating first...`);
|
|
1571
|
+
if (latest && semverNewer(latest, PKG_VERSION)) {
|
|
1572
|
+
console.log(` LDM OS CLI v${PKG_VERSION} -> v${latest}${trackLabel}. Updating first...`);
|
|
1508
1573
|
try {
|
|
1509
1574
|
execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
|
|
1510
1575
|
console.log(` CLI updated to v${latest}. Re-running with new code...`);
|
|
@@ -1761,7 +1826,7 @@ async function cmdInstallCatalog() {
|
|
|
1761
1826
|
const cliLatest = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
|
|
1762
1827
|
encoding: 'utf8', timeout: 10000,
|
|
1763
1828
|
}).trim();
|
|
1764
|
-
if (cliLatest && cliLatest
|
|
1829
|
+
if (cliLatest && semverNewer(cliLatest, PKG_VERSION)) {
|
|
1765
1830
|
npmUpdates.push({
|
|
1766
1831
|
name: 'LDM OS CLI',
|
|
1767
1832
|
catalogNpm: '@wipcomputer/wip-ldm-os',
|
|
@@ -1819,13 +1884,18 @@ async function cmdInstallCatalog() {
|
|
|
1819
1884
|
}
|
|
1820
1885
|
|
|
1821
1886
|
// Check npm for updates (fast, one HTTP call)
|
|
1887
|
+
// --alpha and --beta flags check the corresponding npm dist-tag
|
|
1822
1888
|
if (npmPkg) {
|
|
1823
1889
|
try {
|
|
1824
|
-
const
|
|
1890
|
+
const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
|
|
1891
|
+
const npmViewCmd = npmTag === 'latest'
|
|
1892
|
+
? `npm view ${npmPkg} version 2>/dev/null`
|
|
1893
|
+
: `npm view ${npmPkg} dist-tags.${npmTag} 2>/dev/null`;
|
|
1894
|
+
const latestVersion = execSync(npmViewCmd, {
|
|
1825
1895
|
encoding: 'utf8', timeout: 10000,
|
|
1826
1896
|
}).trim();
|
|
1827
1897
|
|
|
1828
|
-
if (latestVersion && latestVersion
|
|
1898
|
+
if (latestVersion && semverNewer(latestVersion, currentVersion)) {
|
|
1829
1899
|
npmUpdates.push({
|
|
1830
1900
|
name,
|
|
1831
1901
|
catalogRepo: repoUrl,
|
|
@@ -1852,7 +1922,7 @@ async function cmdInstallCatalog() {
|
|
|
1852
1922
|
const tagMatch = tags.match(/refs\/tags\/v?(\d+\.\d+\.\d+)/);
|
|
1853
1923
|
if (tagMatch) {
|
|
1854
1924
|
const latestVersion = tagMatch[1];
|
|
1855
|
-
if (latestVersion
|
|
1925
|
+
if (semverNewer(latestVersion, currentVersion)) {
|
|
1856
1926
|
npmUpdates.push({
|
|
1857
1927
|
name,
|
|
1858
1928
|
catalogRepo: repoUrl,
|
|
@@ -1885,10 +1955,14 @@ async function cmdInstallCatalog() {
|
|
|
1885
1955
|
if (!currentVersion) continue;
|
|
1886
1956
|
|
|
1887
1957
|
try {
|
|
1888
|
-
const
|
|
1958
|
+
const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : 'latest';
|
|
1959
|
+
const npmViewCmd = npmTag === 'latest'
|
|
1960
|
+
? `npm view ${catalogComp.npm} version 2>/dev/null`
|
|
1961
|
+
: `npm view ${catalogComp.npm} dist-tags.${npmTag} 2>/dev/null`;
|
|
1962
|
+
const latestVersion = execSync(npmViewCmd, {
|
|
1889
1963
|
encoding: 'utf8', timeout: 10000,
|
|
1890
1964
|
}).trim();
|
|
1891
|
-
if (latestVersion && latestVersion
|
|
1965
|
+
if (latestVersion && semverNewer(latestVersion, currentVersion)) {
|
|
1892
1966
|
npmUpdates.push({
|
|
1893
1967
|
name: binName,
|
|
1894
1968
|
catalogRepo: catalogComp.repo,
|
|
@@ -1921,7 +1995,7 @@ async function cmdInstallCatalog() {
|
|
|
1921
1995
|
const latest = execSync(`npm view ${comp.npm} version 2>/dev/null`, {
|
|
1922
1996
|
encoding: 'utf8', timeout: 10000,
|
|
1923
1997
|
}).trim();
|
|
1924
|
-
if (latest && latest
|
|
1998
|
+
if (latest && semverNewer(latest, currentVersion)) {
|
|
1925
1999
|
// Remove any sub-tool entries that belong to this parent.
|
|
1926
2000
|
const parentMatches = new Set(comp.registryMatches || []);
|
|
1927
2001
|
for (let i = npmUpdates.length - 1; i >= 0; i--) {
|
|
@@ -2140,13 +2214,56 @@ async function cmdInstallCatalog() {
|
|
|
2140
2214
|
continue;
|
|
2141
2215
|
}
|
|
2142
2216
|
|
|
2143
|
-
if (!entry.catalogRepo) {
|
|
2217
|
+
if (!entry.catalogRepo && !entry.catalogNpm) {
|
|
2144
2218
|
console.log(` Skipping ${entry.name}: no catalog repo (install manually with ldm install <org/repo>)`);
|
|
2145
2219
|
continue;
|
|
2146
2220
|
}
|
|
2147
|
-
|
|
2221
|
+
|
|
2222
|
+
// Source resolution chain (#264):
|
|
2223
|
+
// 1. npm (when --alpha/--beta or npm package available) - works online, any machine
|
|
2224
|
+
// 2. Local private repo (offline, developer machine) - works without internet
|
|
2225
|
+
// 3. GitHub clone (fallback) - works online, any machine
|
|
2226
|
+
let installSource = null;
|
|
2227
|
+
const npmTag = ALPHA_FLAG ? 'alpha' : BETA_FLAG ? 'beta' : null;
|
|
2228
|
+
|
|
2229
|
+
// Try npm first when using alpha/beta tracks or when npm is available
|
|
2230
|
+
if (entry.catalogNpm && (npmTag || !entry.catalogRepo)) {
|
|
2231
|
+
const ver = npmTag ? `${entry.catalogNpm}@${npmTag}` : `${entry.catalogNpm}@${entry.latestVersion}`;
|
|
2232
|
+
installSource = ver;
|
|
2233
|
+
console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from npm ${npmTag || 'latest'})...`);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// Try local private repo (for offline/developer installs)
|
|
2237
|
+
if (!installSource && entry.catalogRepo) {
|
|
2238
|
+
const repoName = basename(entry.catalogRepo);
|
|
2239
|
+
const privateRepoName = repoName + '-private';
|
|
2240
|
+
const WORKSPACE = join(HOME, 'wipcomputerinc');
|
|
2241
|
+
// Search known repo locations
|
|
2242
|
+
const searchDirs = ['repos/ldm-os/devops', 'repos/ldm-os/components', 'repos/ldm-os/utilities', 'repos/ldm-os/apps', 'repos/ldm-os/apis', 'repos/ldm-os/identity'];
|
|
2243
|
+
for (const dir of searchDirs) {
|
|
2244
|
+
const localPrivate = join(WORKSPACE, dir, privateRepoName);
|
|
2245
|
+
const localPublic = join(WORKSPACE, dir, repoName);
|
|
2246
|
+
if (existsSync(localPrivate)) {
|
|
2247
|
+
installSource = localPrivate;
|
|
2248
|
+
console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from local ${privateRepoName})...`);
|
|
2249
|
+
break;
|
|
2250
|
+
}
|
|
2251
|
+
if (existsSync(localPublic)) {
|
|
2252
|
+
installSource = localPublic;
|
|
2253
|
+
console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from local ${repoName})...`);
|
|
2254
|
+
break;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// Fallback: GitHub clone
|
|
2260
|
+
if (!installSource) {
|
|
2261
|
+
installSource = entry.catalogRepo;
|
|
2262
|
+
console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from ${entry.catalogRepo})...`);
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2148
2265
|
try {
|
|
2149
|
-
execSync(`ldm install ${
|
|
2266
|
+
execSync(`ldm install ${installSource}`, { stdio: 'inherit' });
|
|
2150
2267
|
updated++;
|
|
2151
2268
|
|
|
2152
2269
|
// For parent packages, update registry version for all sub-tools (#139, #262)
|
|
@@ -2563,7 +2680,7 @@ function cmdStatus() {
|
|
|
2563
2680
|
const latest = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
|
|
2564
2681
|
encoding: 'utf8', timeout: 10000,
|
|
2565
2682
|
}).trim();
|
|
2566
|
-
if (latest && latest
|
|
2683
|
+
if (latest && semverNewer(latest, PKG_VERSION)) cliUpdate = latest;
|
|
2567
2684
|
} catch {}
|
|
2568
2685
|
|
|
2569
2686
|
// Check extensions against npm using registry source info (#262)
|
|
@@ -2583,7 +2700,7 @@ function cmdStatus() {
|
|
|
2583
2700
|
const latest = execSync(`npm view ${npmPkg} version 2>/dev/null`, {
|
|
2584
2701
|
encoding: 'utf8', timeout: 10000,
|
|
2585
2702
|
}).trim();
|
|
2586
|
-
if (latest && latest
|
|
2703
|
+
if (latest && semverNewer(latest, currentVersion)) {
|
|
2587
2704
|
updates.push({ name, current: currentVersion, latest, npm: npmPkg });
|
|
2588
2705
|
}
|
|
2589
2706
|
} catch {}
|
package/lib/deploy.mjs
CHANGED
|
@@ -394,14 +394,38 @@ function runBuildIfNeeded(repoPath) {
|
|
|
394
394
|
|
|
395
395
|
function compareSemver(a, b) {
|
|
396
396
|
if (!a || !b) return 0;
|
|
397
|
-
|
|
398
|
-
const
|
|
397
|
+
if (a === b) return 0;
|
|
398
|
+
const [aBase, aPre] = a.split('-', 2);
|
|
399
|
+
const [bBase, bPre] = b.split('-', 2);
|
|
400
|
+
const pa = aBase.split('.').map(Number);
|
|
401
|
+
const pb = bBase.split('.').map(Number);
|
|
399
402
|
for (let i = 0; i < 3; i++) {
|
|
400
403
|
const na = pa[i] || 0;
|
|
401
404
|
const nb = pb[i] || 0;
|
|
402
405
|
if (na > nb) return 1;
|
|
403
406
|
if (na < nb) return -1;
|
|
404
407
|
}
|
|
408
|
+
// Base versions equal. Stable > prerelease.
|
|
409
|
+
if (!aPre && bPre) return 1; // a is stable, b is prerelease -> a is newer
|
|
410
|
+
if (aPre && !bPre) return -1; // a is prerelease, b is stable -> b is newer
|
|
411
|
+
// Both have prereleases. Compare segments.
|
|
412
|
+
if (aPre && bPre) {
|
|
413
|
+
const aSegs = aPre.split('.');
|
|
414
|
+
const bSegs = bPre.split('.');
|
|
415
|
+
for (let i = 0; i < Math.max(aSegs.length, bSegs.length); i++) {
|
|
416
|
+
const as = aSegs[i] || '';
|
|
417
|
+
const bs = bSegs[i] || '';
|
|
418
|
+
const an = Number(as);
|
|
419
|
+
const bn = Number(bs);
|
|
420
|
+
if (!isNaN(an) && !isNaN(bn)) {
|
|
421
|
+
if (an > bn) return 1;
|
|
422
|
+
if (an < bn) return -1;
|
|
423
|
+
} else {
|
|
424
|
+
if (as > bs) return 1;
|
|
425
|
+
if (as < bs) return -1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
405
429
|
return 0;
|
|
406
430
|
}
|
|
407
431
|
|
package/package.json
CHANGED
|
@@ -16,11 +16,31 @@ Your AI reads the spec, explains what LDM OS is, checks if it's already installe
|
|
|
16
16
|
1. **Self-update.** Checks npm for a newer version of itself. Updates first, then re-runs with new code.
|
|
17
17
|
2. **System scan.** Reads `~/.ldm/extensions/`, `~/.openclaw/extensions/`, Claude Code MCP config, CLI binaries on PATH.
|
|
18
18
|
3. **Catalog check.** Matches installed extensions against the catalog. Shows what's available, what's behind.
|
|
19
|
-
4. **npm version check.** Checks every installed extension against npm for newer versions.
|
|
20
|
-
5. **Update.**
|
|
19
|
+
4. **npm version check.** Checks every installed extension against npm for newer versions (uses dist-tags for alpha/beta).
|
|
20
|
+
5. **Update.** Resolves the install source (see Source Resolution below), deploys to extensions dirs, registers MCP/hooks/skills.
|
|
21
21
|
6. **Health check.** Verifies CLIs exist, no broken symlinks, no stale configs.
|
|
22
22
|
7. **Cleanup.** Removes staging dirs, prunes ghost entries from registry.
|
|
23
23
|
|
|
24
|
+
## Release Tracks
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ldm install # stable (npm @latest)
|
|
28
|
+
ldm install --alpha # alpha track (npm @alpha)
|
|
29
|
+
ldm install --beta # beta track (npm @beta)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each track overwrites whatever is installed. Alpha, beta, and stable all use the same install path. The flag only changes which npm dist-tag is checked for updates.
|
|
33
|
+
|
|
34
|
+
## Source Resolution
|
|
35
|
+
|
|
36
|
+
When updating, the installer finds the package in priority order:
|
|
37
|
+
|
|
38
|
+
1. **npm** (when `--alpha` or `--beta`): downloads from npm dist-tag. Works on any machine.
|
|
39
|
+
2. **Local private repo** (developer machine): finds the repo at `~/wipcomputerinc/repos/ldm-os/`. Works offline, no npm or internet needed.
|
|
40
|
+
3. **GitHub clone** (fallback): clones the public repo. Existing behavior.
|
|
41
|
+
|
|
42
|
+
Alpha/beta installs never require `deploy-public`. Merge to private main, `wip-release alpha`, `ldm install --alpha`, done.
|
|
43
|
+
|
|
24
44
|
## Dry Run
|
|
25
45
|
|
|
26
46
|
Always preview first:
|
|
@@ -1,77 +1,91 @@
|
|
|
1
1
|
# How Releases Work
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Four Release Tracks
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
| Track | npm tag | Who | Purpose |
|
|
6
|
+
|-------|---------|-----|---------|
|
|
7
|
+
| **Alpha** | `@alpha` | Developer | Moving fast. Fix, test, iterate. No deploy-public needed. |
|
|
8
|
+
| **Beta** | `@beta` | Testers | Testing the distribution pipeline. npm + installer verified. |
|
|
9
|
+
| **Hotfix** | `@latest` | Everyone | Emergency fix. Goes straight to stable. No deploy-public. |
|
|
10
|
+
| **Stable** | `@latest` | Everyone | Full release. deploy-public syncs the public repo. |
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
## The Alpha Flow (Development)
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
ldm worktree add my-prefix/feature-name
|
|
12
|
-
cd _worktrees/repo--my-prefix--feature-name/
|
|
13
|
-
# edit files
|
|
14
|
-
git add <files>
|
|
15
|
-
git commit -m "description"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### 2. Write Release Notes
|
|
19
|
-
|
|
20
|
-
Create `RELEASE-NOTES-v{version}.md` (dashes, not dots) in the repo root. Commit it on the branch with the code. It gets reviewed in the PR.
|
|
21
|
-
|
|
22
|
-
### 3. Push and PR
|
|
14
|
+
This is the fast path. Most work happens here.
|
|
23
15
|
|
|
24
16
|
```bash
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
# 1. Branch and code
|
|
18
|
+
git worktree add .worktrees/repo--my-prefix--feature -b my-prefix/feature
|
|
19
|
+
cd .worktrees/repo--my-prefix--feature/
|
|
20
|
+
# edit, commit
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
# 2. Push, PR, merge
|
|
23
|
+
git push -u origin my-prefix/feature
|
|
24
|
+
gh pr create && gh pr merge --merge
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
# 3. Alpha release
|
|
27
|
+
cd /path/to/repo && git checkout main && git pull
|
|
28
|
+
wip-release alpha --notes="what changed"
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
git checkout main && git pull
|
|
37
|
-
wip-release patch # auto-detects the release notes file
|
|
30
|
+
# 4. Install and test
|
|
31
|
+
ldm install --alpha
|
|
38
32
|
```
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
Done. No deploy-public. No public repo sync. The installer pulls from npm @alpha (or finds the local private repo if offline).
|
|
41
35
|
|
|
42
|
-
|
|
36
|
+
**Sub-tool versions:** If you changed a sub-tool's code, bump its `package.json` version in the PR. Same version = same code. `wip-release` warns if files changed without a version bump.
|
|
37
|
+
|
|
38
|
+
## The Stable Flow (Public Release)
|
|
43
39
|
|
|
44
40
|
```bash
|
|
45
|
-
|
|
46
|
-
```
|
|
41
|
+
# 1-3. Same as alpha: branch, PR, merge
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
# 4. Write release notes on the branch
|
|
44
|
+
# RELEASE-NOTES-v{version}.md in repo root, committed with the code
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
# 5. Stable release
|
|
47
|
+
git checkout main && git pull
|
|
48
|
+
wip-release patch # auto-detects release notes
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
# 6. Deploy to public
|
|
51
|
+
deploy-public /path/to/private-repo org/public-repo
|
|
52
|
+
|
|
53
|
+
# 7. Dogfood
|
|
54
|
+
ldm install
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
`wip-release` handles: version bump, CHANGELOG.md, SKILL.md sync, npm publish, GitHub release.
|
|
58
|
+
|
|
59
|
+
`deploy-public` syncs everything except `ai/` to the public repo. Creates a matching release.
|
|
58
60
|
|
|
59
61
|
## Quality Gates
|
|
60
62
|
|
|
61
|
-
`wip-release` enforces before publishing:
|
|
63
|
+
`wip-release` enforces before publishing (stable only):
|
|
62
64
|
- Release notes must be a file (not a flag)
|
|
63
|
-
- Must reference a GitHub issue
|
|
64
65
|
- Product docs must be updated
|
|
65
|
-
- Technical docs must be updated if source changed
|
|
66
66
|
- No stale merged branches
|
|
67
67
|
- Must run from main working tree (not worktree)
|
|
68
68
|
|
|
69
|
+
Alpha/beta skip most gates for speed.
|
|
70
|
+
|
|
71
|
+
## Source Resolution
|
|
72
|
+
|
|
73
|
+
The installer resolves sources in priority order:
|
|
74
|
+
|
|
75
|
+
1. **npm** (with dist-tag for alpha/beta): works on any machine with internet
|
|
76
|
+
2. **Local private repo**: works offline on developer machines
|
|
77
|
+
3. **GitHub clone**: fallback for stable installs and third-party tools
|
|
78
|
+
|
|
79
|
+
## Version Immutability
|
|
80
|
+
|
|
81
|
+
Same version = same code. Always. If code changed, the version must change. The installer uses version comparison to decide whether to deploy. `wip-release` validates that sub-tools with changed files have bumped versions.
|
|
82
|
+
|
|
69
83
|
## Three Separate Steps
|
|
70
84
|
|
|
71
85
|
| Step | What happens | What it means |
|
|
72
86
|
|------|-------------|---------------|
|
|
73
87
|
| Merge | PR merged to main | Code lands. Nothing else changes. |
|
|
74
|
-
|
|
|
75
|
-
| Install |
|
|
88
|
+
| Release | `wip-release alpha/beta/patch` | Published to npm. Available for install. |
|
|
89
|
+
| Install | `ldm install [--alpha/--beta]` | Extensions updated on your machine. |
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
For stable releases, add a fourth step: `deploy-public` to sync the public GitHub repo.
|