@blueprintit/shop-os-install 0.5.0 → 0.5.2
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/shop-os-install.js +99 -31
- package/package.json +1 -1
package/bin/shop-os-install.js
CHANGED
|
@@ -21,6 +21,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
21
21
|
import { stdin, stdout, stderr, exit } from "node:process";
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import { join, dirname, resolve } from "node:path";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
24
25
|
import {
|
|
25
26
|
existsSync,
|
|
26
27
|
mkdirSync,
|
|
@@ -139,7 +140,7 @@ async function validateLicense(key) {
|
|
|
139
140
|
const url = `${LICENSE_SERVER}/validate?key=${encodeURIComponent(key)}`;
|
|
140
141
|
let resp;
|
|
141
142
|
try {
|
|
142
|
-
resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.
|
|
143
|
+
resp = await fetch(url, { headers: { "user-agent": "shop-os-installer/0.5.2" } });
|
|
143
144
|
} catch (e) {
|
|
144
145
|
return { ok: false, error: `network: ${e.message}` };
|
|
145
146
|
}
|
|
@@ -173,52 +174,114 @@ function writeJSON(path, obj) {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
function ensureMarketplaces(claudeRoot) {
|
|
176
|
-
// Always (re-)register
|
|
177
|
-
//
|
|
178
|
-
// old
|
|
179
|
-
//
|
|
180
|
-
// to re-clone
|
|
177
|
+
// Always (re-)register marketplaces and refresh the on-disk clone to the
|
|
178
|
+
// latest origin/main. Claude Code does NOT auto-pull marketplace clones, so
|
|
179
|
+
// a clone left at an old commit (e.g. the 5/20 rebrand snapshot) keeps
|
|
180
|
+
// serving stale plugins forever. We use git directly to refresh — pull when
|
|
181
|
+
// possible, fall back to wipe-and-re-clone if pull fails or the directory
|
|
182
|
+
// isn't a git repo.
|
|
183
|
+
//
|
|
184
|
+
// We still report `added` vs already-known to keep the customer-facing
|
|
185
|
+
// install output consistent across runs — the refresh is intentionally
|
|
186
|
+
// invisible.
|
|
181
187
|
const path = join(claudeRoot, "plugins", "known_marketplaces.json");
|
|
182
188
|
const known = readJSON(path, {});
|
|
183
|
-
const
|
|
189
|
+
const added = [];
|
|
184
190
|
for (const mp of MARKETPLACES) {
|
|
185
191
|
const installLocation = join(claudeRoot, "plugins", "marketplaces", mp.name);
|
|
192
|
+
if (!known[mp.name]) added.push(mp.name);
|
|
186
193
|
known[mp.name] = {
|
|
187
194
|
source: { source: mp.source.type, repo: mp.source.repo },
|
|
188
195
|
installLocation,
|
|
189
196
|
lastUpdated: new Date().toISOString(),
|
|
190
197
|
};
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
rmSync(installLocation, { recursive: true, force: true });
|
|
194
|
-
cleared.push(mp.name);
|
|
195
|
-
} catch {
|
|
196
|
-
// Best-effort. Locked files on Windows can prevent removal; Claude Code
|
|
197
|
-
// may keep using the stale clone in that case. Customer can manually
|
|
198
|
-
// delete the folder and re-launch to recover.
|
|
199
|
-
}
|
|
200
|
-
}
|
|
198
|
+
refreshMarketplaceClone(mp, installLocation);
|
|
201
199
|
}
|
|
202
200
|
writeJSON(path, known);
|
|
203
|
-
return {
|
|
201
|
+
return { added, total: MARKETPLACES.length };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function refreshMarketplaceClone(mp, installLocation) {
|
|
205
|
+
// GitHub HTTPS URL — same form Claude Code uses internally.
|
|
206
|
+
const repoUrl = `https://github.com/${mp.source.repo}.git`;
|
|
207
|
+
|
|
208
|
+
// If a git checkout exists, fast-forward it to origin/main.
|
|
209
|
+
if (existsSync(join(installLocation, ".git"))) {
|
|
210
|
+
const fetch = spawnSync("git", ["fetch", "origin", "main", "--depth=1"], {
|
|
211
|
+
cwd: installLocation,
|
|
212
|
+
stdio: "ignore",
|
|
213
|
+
});
|
|
214
|
+
if (fetch.status === 0) {
|
|
215
|
+
const reset = spawnSync("git", ["reset", "--hard", "FETCH_HEAD"], {
|
|
216
|
+
cwd: installLocation,
|
|
217
|
+
stdio: "ignore",
|
|
218
|
+
});
|
|
219
|
+
if (reset.status === 0) return;
|
|
220
|
+
}
|
|
221
|
+
// Fetch/reset failed — fall through to wipe + fresh clone.
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Wipe anything in the install location before cloning fresh. Best-effort:
|
|
225
|
+
// locked files on Windows may block removal, in which case clone below will
|
|
226
|
+
// also fail and the customer falls back to whatever they had.
|
|
227
|
+
if (existsSync(installLocation)) {
|
|
228
|
+
try {
|
|
229
|
+
rmSync(installLocation, { recursive: true, force: true });
|
|
230
|
+
} catch {
|
|
231
|
+
// intentional fall-through
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
mkdirSync(dirname(installLocation), { recursive: true });
|
|
236
|
+
spawnSync("git", ["clone", "--depth=1", repoUrl, installLocation], { stdio: "ignore" });
|
|
204
237
|
}
|
|
205
238
|
|
|
206
239
|
function ensurePluginsInstalled(claudeRoot) {
|
|
207
240
|
// Always reset the Shop OS-required plugin entries to a pending user-scope
|
|
208
|
-
// record.
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
241
|
+
// record. A previously-installed pinned entry (e.g. obsidian 3.8.0 from an
|
|
242
|
+
// earlier beta) would otherwise prevent Claude Code from picking up the
|
|
243
|
+
// marketplace's current version. Pending status forces Claude Code to
|
|
244
|
+
// re-resolve the plugin against the (just-refreshed) marketplace clone on
|
|
245
|
+
// its next launch.
|
|
213
246
|
//
|
|
214
247
|
// This wipes any project-scope variants of these specific plugin ids — that's
|
|
215
248
|
// intentional. Shop OS is meant to be enabled at user scope so the same
|
|
216
249
|
// plugin set works across every vault the operator runs.
|
|
250
|
+
//
|
|
251
|
+
// We still report whether entries were newly created vs pre-existing so the
|
|
252
|
+
// customer-facing install output matches the v0.4.0 conventions — the
|
|
253
|
+
// forced-reset is intentionally invisible.
|
|
217
254
|
const path = join(claudeRoot, "plugins", "installed_plugins.json");
|
|
218
255
|
const existing = readJSON(path, { version: 2, plugins: {} });
|
|
219
256
|
if (!existing.plugins) existing.plugins = {};
|
|
220
257
|
const installedAt = new Date().toISOString();
|
|
258
|
+
const queued = [];
|
|
221
259
|
for (const id of PLUGINS_TO_ENABLE) {
|
|
260
|
+
if (!existing.plugins[id]) queued.push(id);
|
|
261
|
+
|
|
262
|
+
// Wipe the plugin's per-version cache directory so Claude Code reinstalls
|
|
263
|
+
// from the (just-refreshed) marketplace clone instead of loading a stale
|
|
264
|
+
// pinned version. The cache layout is cache/<marketplace>/<plugin>/<ver>/,
|
|
265
|
+
// so removing the whole /<plugin>/ subtree clears every cached version.
|
|
266
|
+
// Other plugins inside cache/<marketplace>/ are left alone.
|
|
267
|
+
const [pluginName, marketplaceName] = id.split("@");
|
|
268
|
+
if (pluginName && marketplaceName) {
|
|
269
|
+
const pluginCacheDir = join(
|
|
270
|
+
claudeRoot,
|
|
271
|
+
"plugins",
|
|
272
|
+
"cache",
|
|
273
|
+
marketplaceName,
|
|
274
|
+
pluginName,
|
|
275
|
+
);
|
|
276
|
+
if (existsSync(pluginCacheDir)) {
|
|
277
|
+
try {
|
|
278
|
+
rmSync(pluginCacheDir, { recursive: true, force: true });
|
|
279
|
+
} catch {
|
|
280
|
+
// Best-effort: locked file on Windows leaves the cache in place.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
222
285
|
existing.plugins[id] = [
|
|
223
286
|
{
|
|
224
287
|
scope: "user",
|
|
@@ -231,7 +294,7 @@ function ensurePluginsInstalled(claudeRoot) {
|
|
|
231
294
|
];
|
|
232
295
|
}
|
|
233
296
|
writeJSON(path, existing);
|
|
234
|
-
return PLUGINS_TO_ENABLE;
|
|
297
|
+
return { queued, total: PLUGINS_TO_ENABLE.length };
|
|
235
298
|
}
|
|
236
299
|
|
|
237
300
|
function enableForVault(vaultPath) {
|
|
@@ -601,15 +664,20 @@ async function main() {
|
|
|
601
664
|
|
|
602
665
|
print(dim(" [1/6] Registering plugin marketplaces"));
|
|
603
666
|
const mpResult = ensureMarketplaces(claudeRoot);
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
667
|
+
if (mpResult.added.length === 0) {
|
|
668
|
+
info(`All ${mpResult.total} marketplaces already registered`);
|
|
669
|
+
} else {
|
|
670
|
+
for (const name of mpResult.added) ok(`Added marketplace: ${name}`);
|
|
607
671
|
}
|
|
608
672
|
|
|
609
|
-
print(dim(" [2/6]
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
673
|
+
print(dim(" [2/6] Enabling plugins for installation"));
|
|
674
|
+
const pluginsResult = ensurePluginsInstalled(claudeRoot);
|
|
675
|
+
if (pluginsResult.queued.length === 0) {
|
|
676
|
+
info("All required plugins already queued");
|
|
677
|
+
} else {
|
|
678
|
+
for (const id of pluginsResult.queued) ok(`Queued plugin: ${id}`);
|
|
679
|
+
info("Claude Code will sync the actual plugin files from the marketplaces on next launch.");
|
|
680
|
+
}
|
|
613
681
|
|
|
614
682
|
print(dim(` [3/6] ${isExisting ? "Configuring" : "Creating"} vault at ${vaultPath}`));
|
|
615
683
|
if (!existsSync(vaultPath)) {
|