claudeup 4.19.0 → 4.22.0
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/claudeup.js +43 -19
- package/package.json +14 -19
- package/scripts/build-binaries.ts +77 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +43 -0
- package/src/__tests__/cli-router.test.ts +65 -0
- package/src/__tests__/conventions-integration.test.ts +745 -0
- package/src/__tests__/conventions-manager.test.ts +1172 -0
- package/src/__tests__/doctor-bins.test.ts +35 -0
- package/src/__tests__/doctor.test.ts +450 -0
- package/src/__tests__/dual-write-prevention.test.ts +14 -4
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/gitignore-prerun.test.ts +1 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +98 -0
- package/src/__tests__/manifest.test.ts +136 -0
- package/src/__tests__/marketplace-refresh.test.ts +267 -0
- package/src/__tests__/plugin-requires.test.ts +133 -0
- package/src/__tests__/plugin-setup.test.ts +7 -0
- package/src/__tests__/profile-command.test.ts +116 -0
- package/src/__tests__/profile-materializer.test.ts +82 -0
- package/src/__tests__/profile-sync.test.ts +136 -0
- package/src/__tests__/registry-version-resolution.test.ts +74 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +218 -0
- package/src/__tests__/symlink-manager.test.ts +99 -0
- package/src/__tests__/toolchain.test.ts +56 -0
- package/src/cli/claude.ts +21 -0
- package/src/cli/doctor.ts +132 -0
- package/src/cli/install.ts +360 -0
- package/src/cli/profile.ts +166 -0
- package/src/cli/router.ts +107 -0
- package/src/cli/update.ts +85 -0
- package/src/data/cli-tools.ts +7 -7
- package/src/data/gitignore-defaults.ts +4 -0
- package/src/data/marketplaces.ts +12 -0
- package/src/data/predefined-profiles.ts +3 -4
- package/src/main.tsx +11 -154
- package/src/prerunner/index.ts +62 -27
- package/src/services/claude-cli.ts +19 -22
- package/src/services/claude-settings.ts +99 -26
- package/src/services/conventions-manager.ts +865 -0
- package/src/services/doctor-bins.ts +49 -0
- package/src/services/doctor.ts +509 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/manifest.ts +166 -0
- package/src/services/marketplace-refresh.ts +173 -0
- package/src/services/plugin-manager.ts +7 -1
- package/src/services/plugin-requires.ts +215 -0
- package/src/services/plugin-version-check.ts +9 -4
- package/src/services/profile-materializer.ts +61 -0
- package/src/services/profile-sync.ts +150 -0
- package/src/services/resolver.ts +293 -0
- package/src/services/symlink-manager.ts +146 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/version-check.ts +10 -11
- package/src/types/index.ts +138 -31
- package/src/ui/App.tsx +0 -4
- package/src/ui/screens/CliToolsScreen.tsx +11 -3
- package/src/ui/screens/index.ts +0 -1
- package/src/ui/state/reducer.ts +0 -101
- package/src/ui/state/types.ts +0 -35
- package/src/utils/command-utils.ts +23 -0
- package/src/utils/file-locking.ts +144 -0
- package/src/data/alias-flags.js +0 -205
- package/src/data/cli-tools.js +0 -123
- package/src/data/gitignore-defaults.js +0 -24
- package/src/data/gitignore-reasons.js +0 -97
- package/src/data/gitignore-templates.js +0 -21
- package/src/data/marketplaces.js +0 -138
- package/src/data/mcp-servers.js +0 -509
- package/src/data/predefined-profiles.js +0 -248
- package/src/data/settings-catalog.js +0 -625
- package/src/data/skill-repos.js +0 -160
- package/src/data/statuslines.js +0 -159
- package/src/data/statuslines.ts +0 -188
- package/src/index.js +0 -4
- package/src/index.ts +0 -5
- package/src/main.js +0 -170
- package/src/prerunner/index.js +0 -252
- package/src/services/alias-settings.js +0 -51
- package/src/services/alias-shell-writer.js +0 -1018
- package/src/services/alias-store.js +0 -129
- package/src/services/claude-cli.js +0 -189
- package/src/services/claude-runner.js +0 -28
- package/src/services/claude-settings.js +0 -1223
- package/src/services/gitignore-detector.js +0 -155
- package/src/services/gitignore-fixer.js +0 -231
- package/src/services/gitignore-prerun.js +0 -46
- package/src/services/gitignore-resolver.js +0 -143
- package/src/services/gitignore-service.js +0 -117
- package/src/services/local-marketplace.js +0 -339
- package/src/services/marketplace-fetcher.js +0 -96
- package/src/services/marketplace-sync.js +0 -94
- package/src/services/mcp-registry.js +0 -87
- package/src/services/plugin-manager.js +0 -473
- package/src/services/plugin-mcp-config.js +0 -177
- package/src/services/plugin-setup.js +0 -499
- package/src/services/plugin-version-check.js +0 -262
- package/src/services/profiles.js +0 -161
- package/src/services/settings-manager.js +0 -243
- package/src/services/skills-manager.js +0 -393
- package/src/services/skillsmp-client.js +0 -87
- package/src/services/update-cache.js +0 -52
- package/src/services/version-check.js +0 -99
- package/src/types/gitignore.js +0 -6
- package/src/types/index.js +0 -1
- package/src/ui/App.js +0 -355
- package/src/ui/adapters/pluginsAdapter.js +0 -139
- package/src/ui/adapters/settingsAdapter.js +0 -111
- package/src/ui/adapters/skillsAdapter.js +0 -154
- package/src/ui/components/CategoryHeader.js +0 -9
- package/src/ui/components/EmptyFilterState.js +0 -4
- package/src/ui/components/FlagDetailEditor.js +0 -0
- package/src/ui/components/ScopeIndicator.js +0 -30
- package/src/ui/components/ScrollableList.js +0 -36
- package/src/ui/components/SearchInput.js +0 -19
- package/src/ui/components/StyledText.js +0 -39
- package/src/ui/components/TabBar.js +0 -19
- package/src/ui/components/layout/FooterHints.js +0 -29
- package/src/ui/components/layout/Panel.js +0 -6
- package/src/ui/components/layout/ProgressBar.js +0 -14
- package/src/ui/components/layout/ScopeTabs.js +0 -6
- package/src/ui/components/layout/ScreenLayout.js +0 -16
- package/src/ui/components/layout/index.js +0 -5
- package/src/ui/components/modals/ConfirmModal.js +0 -14
- package/src/ui/components/modals/InputModal.js +0 -5
- package/src/ui/components/modals/LoadingModal.js +0 -17
- package/src/ui/components/modals/MessageModal.js +0 -16
- package/src/ui/components/modals/ModalContainer.js +0 -137
- package/src/ui/components/modals/SelectModal.js +0 -18
- package/src/ui/components/modals/VersionMismatchModal.js +0 -36
- package/src/ui/components/modals/index.js +0 -6
- package/src/ui/components/primitives/ActionHints.js +0 -13
- package/src/ui/components/primitives/DetailSection.js +0 -7
- package/src/ui/components/primitives/KeyValueLine.js +0 -8
- package/src/ui/components/primitives/ListCategoryRow.js +0 -8
- package/src/ui/components/primitives/MetaText.js +0 -8
- package/src/ui/components/primitives/ScopeDetail.js +0 -32
- package/src/ui/components/primitives/ScopeSquares.js +0 -11
- package/src/ui/components/primitives/SelectableRow.js +0 -5
- package/src/ui/components/primitives/index.js +0 -8
- package/src/ui/hooks/index.js +0 -4
- package/src/ui/hooks/useAsyncData.js +0 -77
- package/src/ui/hooks/useGitignoreModal.js +0 -74
- package/src/ui/hooks/useKeyboard.js +0 -13
- package/src/ui/hooks/useKeyboardHandler.js +0 -39
- package/src/ui/hooks/useMismatchModal.js +0 -77
- package/src/ui/registry.js +0 -1
- package/src/ui/renderers/cliToolRenderers.js +0 -54
- package/src/ui/renderers/gitignoreRenderers.js +0 -46
- package/src/ui/renderers/mcpRenderers.js +0 -26
- package/src/ui/renderers/pluginRenderers.js +0 -124
- package/src/ui/renderers/profileRenderers.js +0 -177
- package/src/ui/renderers/settingsRenderers.js +0 -73
- package/src/ui/renderers/skillRenderers.js +0 -138
- package/src/ui/screens/AliasScreen.js +0 -1111
- package/src/ui/screens/CliToolsScreen.js +0 -338
- package/src/ui/screens/EnvVarsScreen.js +0 -152
- package/src/ui/screens/GitignoreScreen.js +0 -328
- package/src/ui/screens/McpRegistryScreen.js +0 -238
- package/src/ui/screens/McpScreen.js +0 -176
- package/src/ui/screens/ModelSelectorScreen.js +0 -296
- package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
- package/src/ui/screens/PluginsScreen.js +0 -769
- package/src/ui/screens/ProfilesScreen.js +0 -298
- package/src/ui/screens/SkillsScreen.js +0 -549
- package/src/ui/screens/StatusLineScreen.js +0 -206
- package/src/ui/screens/StatusLineScreen.tsx +0 -416
- package/src/ui/screens/index.js +0 -10
- package/src/ui/state/AnimationContext.js +0 -34
- package/src/ui/state/AppContext.js +0 -162
- package/src/ui/state/DimensionsContext.js +0 -71
- package/src/ui/state/reducer.js +0 -547
- package/src/ui/state/types.js +0 -1
- package/src/ui/theme.js +0 -47
- package/src/utils/clipboard.js +0 -56
- package/src/utils/command-utils.js +0 -19
- package/src/utils/fuzzy-search.js +0 -101
- package/src/utils/string-utils.js +0 -62
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { execFile } from "node:child_process";
|
|
13
13
|
import { promisify } from "node:util";
|
|
14
|
+
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
14
15
|
import { which } from "../utils/command-utils.js";
|
|
15
16
|
import {
|
|
16
17
|
isMarketplaceRegistered,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
removeLocalInstalledPluginVersion,
|
|
19
20
|
} from "./claude-settings.js";
|
|
20
21
|
import { removeInstalledPluginVersion } from "./plugin-manager.js";
|
|
21
|
-
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
22
22
|
|
|
23
23
|
const execFileAsync = promisify(execFile);
|
|
24
24
|
|
|
@@ -84,13 +84,21 @@ async function recoverMissingMarketplace(
|
|
|
84
84
|
marketplace: string,
|
|
85
85
|
): Promise<boolean> {
|
|
86
86
|
if (await isMarketplaceRegistered(marketplace)) {
|
|
87
|
-
// Registered but
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
// Registered but the plugin wasn't found → the registry may use a stale or
|
|
88
|
+
// "directory" source. Fix the registry, then refresh the clone
|
|
89
|
+
// NON-DESTRUCTIVELY (git pull --ff-only) — never `marketplace update`, which
|
|
90
|
+
// deletes the directory before re-cloning and can leave it permanently gone
|
|
91
|
+
// on failure (see marketplace-refresh.ts). If the clone is absent, fall
|
|
92
|
+
// through to the safe `marketplace add` below to create it.
|
|
93
|
+
const { recoverMarketplaceSettings } = await import("./claude-settings.js");
|
|
91
94
|
await recoverMarketplaceSettings();
|
|
92
|
-
await
|
|
93
|
-
|
|
95
|
+
const { fastForwardCloneIfPresent } = await import(
|
|
96
|
+
"./marketplace-refresh.js"
|
|
97
|
+
);
|
|
98
|
+
if ((await fastForwardCloneIfPresent(marketplace)) !== "absent") {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Clone missing → fall through to `marketplace add`.
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
const defaultMp = defaultMarketplaces.find((m) => m.name === marketplace);
|
|
@@ -115,18 +123,11 @@ export async function installPlugin(
|
|
|
115
123
|
try {
|
|
116
124
|
await execClaude(["plugin", "install", pluginId, "--scope", scope]);
|
|
117
125
|
} catch (error) {
|
|
118
|
-
const msg =
|
|
119
|
-
error instanceof Error ? error.message : String(error);
|
|
126
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
120
127
|
if (msg.includes("not found in marketplace")) {
|
|
121
128
|
const marketplace = pluginId.split("@")[1];
|
|
122
129
|
if (marketplace && (await recoverMissingMarketplace(marketplace))) {
|
|
123
|
-
await execClaude([
|
|
124
|
-
"plugin",
|
|
125
|
-
"install",
|
|
126
|
-
pluginId,
|
|
127
|
-
"--scope",
|
|
128
|
-
scope,
|
|
129
|
-
]);
|
|
130
|
+
await execClaude(["plugin", "install", pluginId, "--scope", scope]);
|
|
130
131
|
return;
|
|
131
132
|
}
|
|
132
133
|
}
|
|
@@ -193,13 +194,9 @@ export async function updatePlugin(
|
|
|
193
194
|
scope: PluginScope = "user",
|
|
194
195
|
): Promise<void> {
|
|
195
196
|
try {
|
|
196
|
-
await execClaude(
|
|
197
|
-
["plugin", "install", pluginId, "--scope", scope],
|
|
198
|
-
60000,
|
|
199
|
-
);
|
|
197
|
+
await execClaude(["plugin", "install", pluginId, "--scope", scope], 60000);
|
|
200
198
|
} catch (error) {
|
|
201
|
-
const msg =
|
|
202
|
-
error instanceof Error ? error.message : String(error);
|
|
199
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
203
200
|
if (msg.includes("not found in marketplace")) {
|
|
204
201
|
const marketplace = pluginId.split("@")[1];
|
|
205
202
|
if (marketplace && (await recoverMissingMarketplace(marketplace))) {
|
|
@@ -248,11 +248,85 @@ export async function getLocalEnabledPlugins(
|
|
|
248
248
|
return settings.enabledPlugins || {};
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Overlay the installed_plugins.json registry onto a settings-derived version map.
|
|
253
|
+
*
|
|
254
|
+
* `settings.installedPluginVersions` is claudeup's own bookkeeping and is routinely
|
|
255
|
+
* incomplete: Claude Code's own `plugin install` writes the registry but not our
|
|
256
|
+
* settings key, so a plugin can be genuinely installed and still be absent here.
|
|
257
|
+
* Callers that only read settings then see `undefined` and — because the update
|
|
258
|
+
* check is `installedVersion && compare(...)` — silently treat the plugin as
|
|
259
|
+
* up to date instead of unknown. That hides real updates.
|
|
260
|
+
*
|
|
261
|
+
* The registry is what Claude Code actually loaded, so it wins on conflict.
|
|
262
|
+
*
|
|
263
|
+
* Scope resolution mirrors Claude Code: for a project scope, prefer an entry
|
|
264
|
+
* pinned to that exact projectPath (project or local), then fall back to the
|
|
265
|
+
* user-scope entry, which is what a project without its own install resolves to.
|
|
266
|
+
*/
|
|
267
|
+
export function pickRegistryEntry(
|
|
268
|
+
entries: InstalledPluginEntry[] | undefined,
|
|
269
|
+
scope: "user" | "project" | "local",
|
|
270
|
+
projectPath?: string,
|
|
271
|
+
): InstalledPluginEntry | undefined {
|
|
272
|
+
if (!Array.isArray(entries) || entries.length === 0) return undefined;
|
|
273
|
+
|
|
274
|
+
if (scope === "user") return entries.find((e) => e.scope === "user");
|
|
275
|
+
|
|
276
|
+
const target = projectPath ? path.resolve(projectPath) : undefined;
|
|
277
|
+
if (target) {
|
|
278
|
+
const pinned = entries.find(
|
|
279
|
+
(e) =>
|
|
280
|
+
(e.scope === scope || e.scope === "local") &&
|
|
281
|
+
e.projectPath !== undefined &&
|
|
282
|
+
path.resolve(e.projectPath) === target,
|
|
283
|
+
);
|
|
284
|
+
if (pinned) return pinned;
|
|
285
|
+
}
|
|
286
|
+
// A project with no install of its own resolves to the user-scope entry.
|
|
287
|
+
return entries.find((e) => e.scope === "user");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function overlayRegistryVersions(
|
|
291
|
+
base: Record<string, string>,
|
|
292
|
+
scope: "user" | "project" | "local",
|
|
293
|
+
projectPath?: string,
|
|
294
|
+
): Promise<Record<string, string>> {
|
|
295
|
+
let registry: InstalledPluginsRegistry;
|
|
296
|
+
try {
|
|
297
|
+
registry = await readInstalledPluginsRegistry();
|
|
298
|
+
} catch {
|
|
299
|
+
return base; // registry unreadable — settings-only is still better than nothing
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const merged = { ...base };
|
|
303
|
+
for (const [pluginId, entries] of Object.entries(registry.plugins ?? {})) {
|
|
304
|
+
const pick = pickRegistryEntry(entries, scope, projectPath);
|
|
305
|
+
if (pick?.version) merged[pluginId] = pick.version;
|
|
306
|
+
}
|
|
307
|
+
return merged;
|
|
308
|
+
}
|
|
309
|
+
|
|
251
310
|
export async function getLocalInstalledPluginVersions(
|
|
252
311
|
projectPath?: string,
|
|
253
312
|
): Promise<Record<string, string>> {
|
|
254
313
|
const settings = await readLocalSettings(projectPath);
|
|
255
|
-
return
|
|
314
|
+
return overlayRegistryVersions(
|
|
315
|
+
settings.installedPluginVersions || {},
|
|
316
|
+
"local",
|
|
317
|
+
projectPath,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Overlay the registry onto a project-scope settings map.
|
|
323
|
+
* Exported for plugin-manager, which reads project settings itself.
|
|
324
|
+
*/
|
|
325
|
+
export async function getProjectInstalledPluginVersions(
|
|
326
|
+
base: Record<string, string>,
|
|
327
|
+
projectPath?: string,
|
|
328
|
+
): Promise<Record<string, string>> {
|
|
329
|
+
return overlayRegistryVersions(base, "project", projectPath);
|
|
256
330
|
}
|
|
257
331
|
|
|
258
332
|
// Local plugin management (writes to settings.local.json)
|
|
@@ -529,7 +603,10 @@ export async function getGlobalInstalledPluginVersions(): Promise<
|
|
|
529
603
|
Record<string, string>
|
|
530
604
|
> {
|
|
531
605
|
const settings = await readGlobalSettings();
|
|
532
|
-
return
|
|
606
|
+
return overlayRegistryVersions(
|
|
607
|
+
settings.installedPluginVersions || {},
|
|
608
|
+
"user",
|
|
609
|
+
);
|
|
533
610
|
}
|
|
534
611
|
|
|
535
612
|
export async function saveGlobalInstalledPluginVersion(
|
|
@@ -744,20 +821,16 @@ export async function discoverAllGlobalMarketplaces(): Promise<
|
|
|
744
821
|
}
|
|
745
822
|
}
|
|
746
823
|
|
|
747
|
-
// installed_plugins.json registry management
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
);
|
|
824
|
+
// installed_plugins.json registry management.
|
|
825
|
+
// Resolved lazily (per-call) rather than cached at module load: caching
|
|
826
|
+
// os.homedir() in a module-level constant makes the path capture whatever
|
|
827
|
+
// HOME was set to when this module was first imported, which is fragile
|
|
828
|
+
// (import-order-dependent) and breaks any caller that changes HOME afterward.
|
|
829
|
+
const installedPluginsFile = (): string =>
|
|
830
|
+
path.join(os.homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
754
831
|
|
|
755
|
-
const
|
|
756
|
-
os.homedir(),
|
|
757
|
-
".claude",
|
|
758
|
-
"plugins",
|
|
759
|
-
"known_marketplaces.json",
|
|
760
|
-
);
|
|
832
|
+
const knownMarketplacesFile = (): string =>
|
|
833
|
+
path.join(os.homedir(), ".claude", "plugins", "known_marketplaces.json");
|
|
761
834
|
|
|
762
835
|
interface KnownMarketplaceEntry {
|
|
763
836
|
source: { source: string; path?: string; repo?: string };
|
|
@@ -774,8 +847,8 @@ type KnownMarketplaces = Record<string, KnownMarketplaceEntry>;
|
|
|
774
847
|
async function writeKnownMarketplaces(
|
|
775
848
|
marketplaces: KnownMarketplaces,
|
|
776
849
|
): Promise<void> {
|
|
777
|
-
await fs.ensureDir(path.dirname(
|
|
778
|
-
await fs.writeJson(
|
|
850
|
+
await fs.ensureDir(path.dirname(knownMarketplacesFile()));
|
|
851
|
+
await fs.writeJson(knownMarketplacesFile(), marketplaces, { spaces: 2 });
|
|
779
852
|
}
|
|
780
853
|
|
|
781
854
|
/**
|
|
@@ -1316,8 +1389,8 @@ export async function cleanupExtraKnownMarketplaces(
|
|
|
1316
1389
|
*/
|
|
1317
1390
|
async function readKnownMarketplaces(): Promise<KnownMarketplaces> {
|
|
1318
1391
|
try {
|
|
1319
|
-
if (await fs.pathExists(
|
|
1320
|
-
return await fs.readJson(
|
|
1392
|
+
if (await fs.pathExists(knownMarketplacesFile())) {
|
|
1393
|
+
return await fs.readJson(knownMarketplacesFile());
|
|
1321
1394
|
}
|
|
1322
1395
|
} catch {
|
|
1323
1396
|
// Return empty if can't read
|
|
@@ -1434,8 +1507,8 @@ async function copyPluginToCache(
|
|
|
1434
1507
|
*/
|
|
1435
1508
|
export async function readInstalledPluginsRegistry(): Promise<InstalledPluginsRegistry> {
|
|
1436
1509
|
try {
|
|
1437
|
-
if (await fs.pathExists(
|
|
1438
|
-
const content = await fs.readJson(
|
|
1510
|
+
if (await fs.pathExists(installedPluginsFile())) {
|
|
1511
|
+
const content = await fs.readJson(installedPluginsFile());
|
|
1439
1512
|
// Validate structure
|
|
1440
1513
|
if (!content.version || !content.plugins) {
|
|
1441
1514
|
throw new Error("Invalid registry structure");
|
|
@@ -1444,10 +1517,10 @@ export async function readInstalledPluginsRegistry(): Promise<InstalledPluginsRe
|
|
|
1444
1517
|
}
|
|
1445
1518
|
} catch (error) {
|
|
1446
1519
|
// Backup corrupted file
|
|
1447
|
-
if (await fs.pathExists(
|
|
1520
|
+
if (await fs.pathExists(installedPluginsFile())) {
|
|
1448
1521
|
try {
|
|
1449
|
-
const backup = `${
|
|
1450
|
-
await fs.copy(
|
|
1522
|
+
const backup = `${installedPluginsFile()}.backup.${Date.now()}`;
|
|
1523
|
+
await fs.copy(installedPluginsFile(), backup);
|
|
1451
1524
|
console.warn(`Corrupted registry backed up to: ${backup}`);
|
|
1452
1525
|
} catch {
|
|
1453
1526
|
// Ignore backup errors
|
|
@@ -1463,8 +1536,8 @@ export async function readInstalledPluginsRegistry(): Promise<InstalledPluginsRe
|
|
|
1463
1536
|
export async function writeInstalledPluginsRegistry(
|
|
1464
1537
|
registry: InstalledPluginsRegistry,
|
|
1465
1538
|
): Promise<void> {
|
|
1466
|
-
await fs.ensureDir(path.dirname(
|
|
1467
|
-
await fs.writeJson(
|
|
1539
|
+
await fs.ensureDir(path.dirname(installedPluginsFile()));
|
|
1540
|
+
await fs.writeJson(installedPluginsFile(), registry, { spaces: 2 });
|
|
1468
1541
|
}
|
|
1469
1542
|
|
|
1470
1543
|
/**
|