@blueprintit/shop-os-install 0.5.1 → 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.
@@ -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.1" } });
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,14 +174,15 @@ function writeJSON(path, obj) {
173
174
  }
174
175
 
175
176
  function ensureMarketplaces(claudeRoot) {
176
- // Always (re-)register marketplaces and force-delete any stale clone on
177
- // disk. A clone pinned to an old commit is the #1 reason re-installs keep
178
- // serving old plugin versions (e.g. obsidian 3.8.0 skills appearing months
179
- // after the marketplace shipped 3.12.0). Deleting the directory forces
180
- // Claude Code to re-clone from origin on its next launch.
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.
181
183
  //
182
184
  // We still report `added` vs already-known to keep the customer-facing
183
- // install output consistent across runs — the cache-wipe is intentionally
185
+ // install output consistent across runs — the refresh is intentionally
184
186
  // invisible.
185
187
  const path = join(claudeRoot, "plugins", "known_marketplaces.json");
186
188
  const known = readJSON(path, {});
@@ -193,20 +195,47 @@ function ensureMarketplaces(claudeRoot) {
193
195
  installLocation,
194
196
  lastUpdated: new Date().toISOString(),
195
197
  };
196
- if (existsSync(installLocation)) {
197
- try {
198
- rmSync(installLocation, { recursive: true, force: true });
199
- } catch {
200
- // Best-effort. Locked files on Windows can block removal; in that case
201
- // Claude Code will keep using the stale clone. Customer can quit Claude
202
- // Code and re-run to recover.
203
- }
204
- }
198
+ refreshMarketplaceClone(mp, installLocation);
205
199
  }
206
200
  writeJSON(path, known);
207
201
  return { added, total: MARKETPLACES.length };
208
202
  }
209
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" });
237
+ }
238
+
210
239
  function ensurePluginsInstalled(claudeRoot) {
211
240
  // Always reset the Shop OS-required plugin entries to a pending user-scope
212
241
  // record. A previously-installed pinned entry (e.g. obsidian 3.8.0 from an
@@ -229,6 +258,30 @@ function ensurePluginsInstalled(claudeRoot) {
229
258
  const queued = [];
230
259
  for (const id of PLUGINS_TO_ENABLE) {
231
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
+
232
285
  existing.plugins[id] = [
233
286
  {
234
287
  scope: "user",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueprintit/shop-os-install",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "One-command installer for Shop OS — Blueprint IT's AI Operating System for small businesses.",
5
5
  "type": "module",
6
6
  "bin": {