@octonodes/cli 0.2.3 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octonodes/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Octonode API and plugin development CLI.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  "prepack": "tsc -b"
22
22
  },
23
23
  "dependencies": {
24
- "@octonodes/sdk": "0.2.3",
24
+ "@octonodes/sdk": "0.2.5",
25
25
  "@supabase/supabase-js": "2.107.0",
26
26
  "esbuild": "0.28.1",
27
27
  "tar": "7.5.22",
@@ -11541,7 +11541,7 @@ var require_plugin_publishing = __commonJS({
11541
11541
  "packages/schema/dist/plugin-publishing.js"(exports2) {
11542
11542
  "use strict";
11543
11543
  Object.defineProperty(exports2, "__esModule", { value: true });
11544
- exports2.PluginPublisherConnections = exports2.PluginPublisherConnection = exports2.PluginPublisherPreview = exports2.PluginPublisherConsent = exports2.PluginPublisherAutomatic = exports2.PluginPublisherRepository = exports2.PluginPublisherInput = exports2.pluginReleaseScope = exports2.PluginReleaseConfig = exports2.PluginConfigPath = void 0;
11544
+ exports2.PluginCloudBuild = exports2.PluginPublisherBuildDetail = exports2.PluginPublisherBuilds = exports2.PluginPublisherBuildQuery = exports2.PluginPublisherApplied = exports2.PluginPublisherApply = exports2.PluginPublisherReview = exports2.PluginPublisherReviewItem = exports2.PluginPublisherOperation = exports2.PluginPublisherConnections = exports2.PluginPublisherConnection = exports2.PluginPublisherPreview = exports2.PluginPublisherConsent = exports2.PluginPublisherAutomatic = exports2.PluginPublisherRepository = exports2.PluginPublisherInput = exports2.pluginReleaseScope = exports2.PluginReleaseConfig = exports2.PluginConfigPath = void 0;
11545
11545
  var zod_1 = require("zod");
11546
11546
  var plugin_version_1 = require_plugin_version();
11547
11547
  exports2.PluginConfigPath = zod_1.z.string().max(512).refine((path) => /^(?:[a-zA-Z0-9_.-]+\/)*(?:plugin\.octonode\.(?:json|ya?ml)|octonode\.plugin\.json)$/.test(path) && !path.split("/").some((part) => [".", "..", ".git", "node_modules"].includes(part)), "Select a repository-relative octonode.plugin.json file (older plugin.octonode release files also work)");
@@ -11585,6 +11585,88 @@ var require_plugin_publishing = __commonJS({
11585
11585
  lastPublishedAt: zod_1.z.number().nullable()
11586
11586
  });
11587
11587
  exports2.PluginPublisherConnections = zod_1.z.object({ items: zod_1.z.array(exports2.PluginPublisherConnection) });
11588
+ exports2.PluginPublisherOperation = zod_1.z.enum(["create", "update", "delete"]);
11589
+ exports2.PluginPublisherReviewItem = zod_1.z.object({
11590
+ configPath: exports2.PluginConfigPath,
11591
+ config: exports2.PluginReleaseConfig,
11592
+ sha256: zod_1.z.string(),
11593
+ connectionId: zod_1.z.string().nullable(),
11594
+ missing: zod_1.z.boolean()
11595
+ });
11596
+ exports2.PluginPublisherReview = zod_1.z.object({
11597
+ repositoryId: zod_1.z.number(),
11598
+ repository: zod_1.z.string(),
11599
+ branch: zod_1.z.string(),
11600
+ headSha: zod_1.z.string(),
11601
+ revision: zod_1.z.string(),
11602
+ items: zod_1.z.array(exports2.PluginPublisherReviewItem)
11603
+ });
11604
+ exports2.PluginPublisherApply = zod_1.z.object({
11605
+ repositoryId: zod_1.z.number().int().positive(),
11606
+ revision: zod_1.z.string().regex(/^[a-f0-9]{64}$/),
11607
+ operations: zod_1.z.array(zod_1.z.object({ configPath: exports2.PluginConfigPath, operation: exports2.PluginPublisherOperation }).strict()).min(1).max(100)
11608
+ }).strict();
11609
+ exports2.PluginPublisherApplied = zod_1.z.object({
11610
+ ok: zod_1.z.boolean(),
11611
+ sha: zod_1.z.string(),
11612
+ repositoryId: zod_1.z.number(),
11613
+ buildId: zod_1.z.number()
11614
+ });
11615
+ exports2.PluginPublisherBuildQuery = zod_1.z.object({
11616
+ repositoryId: zod_1.z.coerce.number().int().positive().optional(),
11617
+ page: zod_1.z.coerce.number().int().min(1).max(1e3).default(1),
11618
+ search: zod_1.z.string().max(200).default(""),
11619
+ status: zod_1.z.enum(["", "queued", "in_progress", "success", "failure"]).default(""),
11620
+ sort: zod_1.z.enum(["newest", "oldest", "name"]).default("newest")
11621
+ });
11622
+ exports2.PluginPublisherBuilds = zod_1.z.object({
11623
+ total: zod_1.z.number(),
11624
+ page: zod_1.z.number(),
11625
+ hasMore: zod_1.z.boolean(),
11626
+ items: zod_1.z.array(zod_1.z.object({
11627
+ id: zod_1.z.number(),
11628
+ repositoryId: zod_1.z.number(),
11629
+ number: zod_1.z.number(),
11630
+ attempt: zod_1.z.number(),
11631
+ title: zod_1.z.string(),
11632
+ sha: zod_1.z.string(),
11633
+ branch: zod_1.z.string(),
11634
+ status: zod_1.z.string(),
11635
+ conclusion: zod_1.z.string().nullable(),
11636
+ url: zod_1.z.string(),
11637
+ createdAt: zod_1.z.string(),
11638
+ updatedAt: zod_1.z.string()
11639
+ }))
11640
+ });
11641
+ exports2.PluginPublisherBuildDetail = zod_1.z.object({
11642
+ repository: zod_1.z.string(),
11643
+ sha: zod_1.z.string(),
11644
+ branch: zod_1.z.string(),
11645
+ status: zod_1.z.string(),
11646
+ createdAt: zod_1.z.string(),
11647
+ items: zod_1.z.array(zod_1.z.object({
11648
+ configPath: exports2.PluginConfigPath,
11649
+ name: zod_1.z.string(),
11650
+ version: zod_1.z.string(),
11651
+ scope: zod_1.z.enum(["user", "team", "organization", "public"]),
11652
+ operation: exports2.PluginPublisherOperation,
11653
+ status: zod_1.z.string(),
11654
+ url: zod_1.z.string().nullable(),
11655
+ error: zod_1.z.string().nullable()
11656
+ })),
11657
+ log: zod_1.z.string()
11658
+ });
11659
+ exports2.PluginCloudBuild = zod_1.z.object({
11660
+ id: zod_1.z.number().int().positive(),
11661
+ sha: zod_1.z.string().regex(/^[a-f0-9]{40}$/),
11662
+ active: zod_1.z.boolean(),
11663
+ items: zod_1.z.array(zod_1.z.object({
11664
+ configPath: exports2.PluginConfigPath,
11665
+ config: exports2.PluginReleaseConfig,
11666
+ operation: exports2.PluginPublisherOperation,
11667
+ status: zod_1.z.string()
11668
+ })).max(100)
11669
+ });
11588
11670
  }
11589
11671
  });
11590
11672
 
@@ -12634,6 +12716,7 @@ var PLUGIN_BUNDLE_EXCLUDES = [
12634
12716
  "*.pem",
12635
12717
  "*.key"
12636
12718
  ];
12719
+ var MAX_CATALOG_MANIFESTS = 512;
12637
12720
 
12638
12721
  // packages/plugin/src/integrity.ts
12639
12722
  function collectFiles(dir, base, files) {
@@ -12793,6 +12876,79 @@ function loadPlugin(dir, source = "user") {
12793
12876
  function isPluginDir(dir) {
12794
12877
  return import_schema2.PLUGIN_DEFINITION_FILENAMES.some((name) => (0, import_node_fs4.existsSync)((0, import_node_path4.join)(dir, name)));
12795
12878
  }
12879
+ function pluginDirsIn(root) {
12880
+ if (!(0, import_node_fs4.existsSync)(root)) return [];
12881
+ const out = [];
12882
+ for (const name of (0, import_node_fs4.readdirSync)(root)) {
12883
+ if (name.startsWith(".")) continue;
12884
+ const dir = (0, import_node_path4.join)(root, name);
12885
+ try {
12886
+ if ((0, import_node_fs4.statSync)(dir).isDirectory() && isPluginDir(dir)) out.push(dir);
12887
+ } catch {
12888
+ }
12889
+ }
12890
+ return out;
12891
+ }
12892
+ function discoverPluginCatalog(opts = {}) {
12893
+ return discover(opts, true);
12894
+ }
12895
+ var catalogManifests = /* @__PURE__ */ new Map();
12896
+ function catalogManifest(dir) {
12897
+ const path = (0, import_plugin_runtime2.pluginDefinitionPath)(dir);
12898
+ const stat = (0, import_node_fs4.statSync)(path);
12899
+ const revision = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
12900
+ const cached = catalogManifests.get(path);
12901
+ if (cached?.revision === revision) return cached.manifest;
12902
+ const manifest2 = loadPluginManifest(dir);
12903
+ if (catalogManifests.size >= MAX_CATALOG_MANIFESTS) catalogManifests.delete(catalogManifests.keys().next().value);
12904
+ catalogManifests.set(path, { revision, manifest: manifest2 });
12905
+ return manifest2;
12906
+ }
12907
+ function discover(opts, metadataOnly) {
12908
+ const plugins = [];
12909
+ const errors = [];
12910
+ const seen = /* @__PURE__ */ new Set();
12911
+ const scanRoot = (root, source) => {
12912
+ for (const dir of pluginDirsIn(root)) {
12913
+ try {
12914
+ const manifest2 = metadataOnly ? catalogManifest(dir) : loadPluginManifest(dir);
12915
+ if (seen.has(manifest2.id)) continue;
12916
+ seen.add(manifest2.id);
12917
+ plugins.push({ manifest: manifest2, dir, source });
12918
+ } catch (err) {
12919
+ errors.push({ dir, message: err.message });
12920
+ }
12921
+ }
12922
+ };
12923
+ scanRoot(projectPluginRoot(opts.cwd), "project");
12924
+ try {
12925
+ const lock = readLockfile(opts.cwd);
12926
+ for (const [id, entry] of Object.entries(lock.plugins)) {
12927
+ if (seen.has(id)) continue;
12928
+ const dir = storeEntryDir(entry.pluginId ?? id, entry.version, entry.sha256, opts.storeRoot);
12929
+ if (!isPluginDir(dir)) {
12930
+ errors.push({
12931
+ dir,
12932
+ message: `plugin "${id}"@${entry.version} is locked but missing from the store \u2014 run \`octonode install\``
12933
+ });
12934
+ continue;
12935
+ }
12936
+ try {
12937
+ const manifest2 = metadataOnly ? catalogManifest(dir) : loadPluginManifest(dir);
12938
+ if (manifest2.id !== (entry.pluginId ?? id) || manifest2.version !== entry.version || !metadataOnly && hashPluginDir(dir) !== entry.sha256)
12939
+ throw new Error(`Locked plugin ${id} failed identity or integrity validation; run octonode install`);
12940
+ seen.add(id);
12941
+ plugins.push({ manifest: { ...manifest2, id }, dir, source: "store" });
12942
+ } catch (err) {
12943
+ errors.push({ dir, message: err.message });
12944
+ }
12945
+ }
12946
+ } catch (err) {
12947
+ errors.push({ dir: opts.cwd ?? process.cwd(), message: err.message });
12948
+ }
12949
+ scanRoot(opts.userRoot ?? userPluginRoot(), "user");
12950
+ return { plugins, errors };
12951
+ }
12796
12952
 
12797
12953
  // packages/plugin/src/dependencies.ts
12798
12954
  var import_node_child_process = require("node:child_process");
@@ -13172,7 +13328,7 @@ async function run(project, command, cwd, cacheRoot) {
13172
13328
  maxBuffer: 4 * 1024 * 1024
13173
13329
  });
13174
13330
  }
13175
- async function verifyProjectDependencies(project, names) {
13331
+ async function verifyProjectDependencies(project, names, versions = {}) {
13176
13332
  if (!names.length) return;
13177
13333
  const pnp = (0, import_node_path6.join)(project.root, ".pnp.cjs");
13178
13334
  const loader = (0, import_node_path6.join)(project.root, ".pnp.loader.mjs");
@@ -13184,14 +13340,37 @@ async function verifyProjectDependencies(project, names) {
13184
13340
  "--input-type=module",
13185
13341
  "-e",
13186
13342
  `import { createRequire } from 'node:module';
13187
- import { existsSync } from 'node:fs';
13343
+ import { existsSync, readFileSync } from 'node:fs';
13344
+ import { dirname, join } from 'node:path';
13188
13345
  import { fileURLToPath } from 'node:url';
13189
13346
  const require = createRequire(${JSON.stringify((0, import_node_path6.join)(project.target, "package.json"))});
13347
+ const versions = ${JSON.stringify(versions)};
13190
13348
  for (const name of ${JSON.stringify(names)}) {
13191
- try { require.resolve(name); } catch {
13349
+ let resolved;
13350
+ try { resolved = require.resolve(name); } catch {
13192
13351
  const entry = import.meta.resolve(name);
13352
+ resolved = entry.startsWith('file:') ? fileURLToPath(entry) : undefined;
13193
13353
  if (entry.startsWith('file:') && !existsSync(fileURLToPath(entry))) throw new Error('missing package entry: ' + name);
13194
13354
  }
13355
+ const identity = name.startsWith('@') ? name.split('/').slice(0, 2).join('/') : name.split('/')[0];
13356
+ if (Object.hasOwn(versions, identity)) {
13357
+ let directory = resolved ? dirname(resolved) : '';
13358
+ let matched = false;
13359
+ while (directory) {
13360
+ const file = join(directory, 'package.json');
13361
+ if (existsSync(file)) {
13362
+ const pkg = JSON.parse(readFileSync(file, 'utf8'));
13363
+ if (pkg.name === identity) {
13364
+ if (pkg.version !== versions[identity]) throw new Error('installed dependency version mismatch: ' + name);
13365
+ matched = true; break;
13366
+ }
13367
+ }
13368
+ const parent = dirname(directory);
13369
+ if (parent === directory) break;
13370
+ directory = parent;
13371
+ }
13372
+ if (!matched) throw new Error('cannot verify installed package: ' + name);
13373
+ }
13195
13374
  }`
13196
13375
  ],
13197
13376
  { cwd: project.target, env: (0, import_common.childProcessEnvironment)(), timeout: 3e4, maxBuffer: 1024 * 1024 }
@@ -13241,7 +13420,40 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13241
13420
  throw new Error("invalid npm dependency subpath");
13242
13421
  }
13243
13422
  }
13423
+ const unique = /* @__PURE__ */ new Map();
13424
+ for (const dependency of dependencies) {
13425
+ const spec = dependency.spec.startsWith(`${dependency.name}@`) ? dependency.spec.slice(dependency.name.length + 1) : dependency.spec;
13426
+ const previous = unique.get(dependency.name);
13427
+ if (previous && previous.spec !== spec) throw new Error(`Conflicting dependency requests for ${dependency.name}`);
13428
+ unique.set(dependency.name, {
13429
+ ...dependency,
13430
+ spec,
13431
+ ...previous?.subpaths || dependency.subpaths ? { subpaths: [.../* @__PURE__ */ new Set([...previous?.subpaths ?? [], ...dependency.subpaths ?? []])] } : {}
13432
+ });
13433
+ }
13434
+ dependencies = [...unique.values()];
13244
13435
  const project = resolveDependencyProject(cwd);
13436
+ const declared = manifest(project.target);
13437
+ const existing = { ...declared.devDependencies, ...declared.optionalDependencies, ...declared.dependencies };
13438
+ if (options.preserveExisting) {
13439
+ for (const { name, spec } of dependencies)
13440
+ if (Object.hasOwn(existing, name) && existing[name] !== spec && !(existing[name].startsWith("file:") && spec.startsWith("file:") && (0, import_node_path6.resolve)(project.target, existing[name].slice(5)) === (0, import_node_path6.resolve)(project.target, spec.slice(5))))
13441
+ throw new Error(
13442
+ `Dependency conflict: ${name} is ${existing[name]}, plugin requires ${spec}; reconcile it explicitly first`
13443
+ );
13444
+ }
13445
+ const reusable = dependencies.length > 0 && !options.remove?.length && dependencies.every(
13446
+ ({ name, spec }) => existing[name] === spec && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(spec)
13447
+ );
13448
+ const reuse = reusable && await verifyProjectDependencies(
13449
+ project,
13450
+ dependencies.flatMap(({ name, subpaths }) => subpaths ? subpaths.map((path) => name + path.slice(1)) : [name]),
13451
+ Object.fromEntries(dependencies.map(({ name, spec }) => [name, spec]))
13452
+ ).then(
13453
+ () => true,
13454
+ () => false
13455
+ );
13456
+ const metadataOnly = options.metadataOnly || reuse;
13245
13457
  const packageJson = (0, import_node_path6.join)(project.target, "package.json");
13246
13458
  if (!dependencies.length && !(0, import_node_fs6.existsSync)(packageJson) && !options.commit) return;
13247
13459
  const hasLock = PACKAGE_MANAGER_LOCKS[project.manager].some((file) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(project.root, file)));
@@ -13277,13 +13489,12 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13277
13489
  else (0, import_node_fs6.writeFileSync)(path, content);
13278
13490
  }
13279
13491
  };
13280
- if (hadInstall && !hasLock && !options.metadataOnly)
13492
+ if (hadInstall && !hasLock && !metadataOnly)
13281
13493
  throw new Error(
13282
13494
  "create a native lockfile before updating existing dependencies so failed installations can be recovered"
13283
13495
  );
13284
13496
  try {
13285
- if (options.metadataOnly && (0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(journal)))
13286
- (0, import_node_fs6.writeFileSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(journal), "metadata-only"), "1");
13497
+ if (metadataOnly && (0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(journal))) (0, import_node_fs6.writeFileSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(journal), "metadata-only"), "1");
13287
13498
  if ((0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(journal)))
13288
13499
  (0, import_node_fs6.writeFileSync)(
13289
13500
  journal,
@@ -13294,7 +13505,7 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13294
13505
  }))
13295
13506
  )
13296
13507
  );
13297
- if (options.metadataOnly) {
13508
+ if (metadataOnly) {
13298
13509
  metadataTouched = true;
13299
13510
  options.commit?.();
13300
13511
  (0, import_node_fs6.rmSync)(journal, { force: true });
@@ -13386,14 +13597,14 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13386
13597
  }
13387
13598
  restoreBefore();
13388
13599
  let recovery = "";
13389
- if (!options.metadataOnly && hadInstall && hasLock) {
13600
+ if (!metadataOnly && hadInstall && hasLock) {
13390
13601
  try {
13391
13602
  await run(project, installCommand(project, true), project.root, options.cacheRoot);
13392
13603
  } catch {
13393
13604
  recovery = "; manifests restored, but dependency recovery failed \u2014 run a frozen install before executing workflows";
13394
13605
  }
13395
13606
  restoreBefore();
13396
- } else if (!options.metadataOnly && !hadInstall) {
13607
+ } else if (!metadataOnly && !hadInstall) {
13397
13608
  (0, import_node_fs6.rmSync)((0, import_node_path6.join)(project.root, "node_modules"), { recursive: true, force: true });
13398
13609
  }
13399
13610
  for (const [path, content] of pnpBefore) {
@@ -13698,6 +13909,19 @@ async function generatePluginPackage(cwd, selections, expectedPacker) {
13698
13909
  function pluginNpmDependencies(manifest2) {
13699
13910
  return manifest2.integration?.npmDependencies ?? (manifest2.integration?.npm ? [manifest2.integration.npm] : []);
13700
13911
  }
13912
+ function assertUniqueNpmPlugins(plugins) {
13913
+ const owners = /* @__PURE__ */ new Map();
13914
+ for (const { alias, manifest: manifest2 } of plugins) {
13915
+ const pkg = manifest2.integration?.npm?.package.trim().toLowerCase();
13916
+ if (!pkg) continue;
13917
+ const previous = owners.get(pkg);
13918
+ if (previous && previous !== alias)
13919
+ throw new Error(
13920
+ `Use existing npm integration "${previous}" for ${pkg}; duplicate plugin alias "${alias}" is not allowed`
13921
+ );
13922
+ owners.set(pkg, alias);
13923
+ }
13924
+ }
13701
13925
 
13702
13926
  // packages/plugin/src/prepared/invocation.ts
13703
13927
  var import_common3 = __toESM(require_dist2());
@@ -13746,6 +13970,10 @@ async function reconcilePluginLock(cwd, next, options = {}) {
13746
13970
  throw new Error(`Plugin identity or integrity mismatch: ${alias}`);
13747
13971
  return { alias, directory, sha256: pin.sha256, manifest: manifest2, remote: preparedPluginPin(directory, pin) };
13748
13972
  });
13973
+ assertUniqueNpmPlugins([
13974
+ ...discoverPluginCatalog({ cwd, storeRoot: options.storeRoot }).plugins.filter((item) => item.source === "project" && !loaded.some((pin) => pin.alias === item.manifest.id)).map((item) => ({ alias: item.manifest.id, manifest: item.manifest })),
13975
+ ...loaded
13976
+ ]);
13749
13977
  const libraries = loaded.filter((item) => item.manifest.library);
13750
13978
  const generated = libraries.length ? await generatePluginPackage(project.target, libraries) : void 0;
13751
13979
  const packageFile = (0, import_node_path10.join)(project.target, "package.json");
@@ -13842,6 +14070,9 @@ function installRoot(opts) {
13842
14070
  return opts.project ? { root: projectPluginRoot(opts.cwd), source: "project" } : { root: userPluginRoot(), source: "user" };
13843
14071
  }
13844
14072
  async function installNodeDeps(dir, opts = {}) {
14073
+ return installNodeDepsUnlocked(dir, opts, false);
14074
+ }
14075
+ async function installNodeDepsUnlocked(dir, opts, locked) {
13845
14076
  if (opts.noDeps) return {};
13846
14077
  const dependencies = pluginNpmDependencies(loadPluginManifest(dir));
13847
14078
  if (dependencies.length) {
@@ -13854,11 +14085,12 @@ async function installNodeDeps(dir, opts = {}) {
13854
14085
  });
13855
14086
  return {};
13856
14087
  }
13857
- await installProjectDependencies(
14088
+ await (locked ? reconcileDependencies : installProjectDependencies)(
13858
14089
  opts.cwd ?? process.cwd(),
13859
14090
  dependencies.map((npm) => ({ name: npm.package, spec: npm.spec })),
13860
14091
  {
13861
- cacheRoot: opts.cacheRoot
14092
+ cacheRoot: opts.cacheRoot,
14093
+ preserveExisting: true
13862
14094
  }
13863
14095
  );
13864
14096
  return { depsInstalled: true };
@@ -13894,6 +14126,10 @@ async function installNodeDeps(dir, opts = {}) {
13894
14126
  }
13895
14127
  }
13896
14128
  async function installPlugin(src, opts = {}) {
14129
+ if (opts.project) return withDependencyInstall(opts.cwd ?? process.cwd(), () => installPluginUnlocked(src, opts));
14130
+ return installPluginUnlocked(src, opts);
14131
+ }
14132
+ async function installPluginUnlocked(src, opts) {
13897
14133
  if (!(0, import_node_fs11.existsSync)(src) || !(0, import_node_fs11.statSync)(src).isDirectory()) {
13898
14134
  throw new Error(`source "${src}" is not a directory`);
13899
14135
  }
@@ -13902,13 +14138,20 @@ async function installPlugin(src, opts = {}) {
13902
14138
  }
13903
14139
  const { manifest: manifest2 } = loadPlugin(src);
13904
14140
  const { root, source } = installRoot(opts);
14141
+ const installed = discoverPluginCatalog({ cwd: opts.cwd }).plugins.filter(
14142
+ (item) => (opts.project ? item.source !== "user" : item.source === "user") && item.manifest.id !== manifest2.id
14143
+ );
14144
+ assertUniqueNpmPlugins([
14145
+ ...installed.map((item) => ({ alias: item.manifest.id, manifest: item.manifest })),
14146
+ { alias: manifest2.id, manifest: manifest2 }
14147
+ ]);
13905
14148
  const dest = (0, import_node_path11.join)(root, manifest2.id);
13906
14149
  if ((0, import_node_fs11.existsSync)(dest)) {
13907
14150
  if (!opts.force) {
13908
14151
  throw new Error(`plugin "${manifest2.id}" is already installed at ${dest} (use --force to overwrite)`);
13909
14152
  }
13910
14153
  }
13911
- const deps = pluginNpmDependencies(manifest2).length ? await installNodeDeps(src, { ...opts, cacheOnly: opts.cacheOnly ?? !opts.project }) : {};
14154
+ const deps = pluginNpmDependencies(manifest2).length ? await installNodeDepsUnlocked(src, { ...opts, cacheOnly: opts.cacheOnly ?? !opts.project }, !!opts.project) : {};
13912
14155
  if (deps.depsError) return { manifest: manifest2, dir: dest, source, ...deps };
13913
14156
  if ((0, import_node_fs11.existsSync)(dest)) (0, import_node_fs11.rmSync)(dest, { recursive: true, force: true });
13914
14157
  (0, import_node_fs11.mkdirSync)(root, { recursive: true });
@@ -13918,7 +14161,7 @@ async function installPlugin(src, opts = {}) {
13918
14161
  dir: dest,
13919
14162
  source,
13920
14163
  ...deps,
13921
- ...pluginNpmDependencies(manifest2).length ? {} : await installNodeDeps(dest, opts)
14164
+ ...pluginNpmDependencies(manifest2).length ? {} : await installNodeDepsUnlocked(dest, opts, !!opts.project)
13922
14165
  };
13923
14166
  }
13924
14167
 
@@ -14264,7 +14507,20 @@ var RemoteRegistry = class {
14264
14507
  }
14265
14508
  try {
14266
14509
  await withDependencyInstall(opts.cwd ?? process.cwd(), async () => {
14267
- const previous = readLockfile(opts.cwd).plugins[alias];
14510
+ const pins = readLockfile(opts.cwd).plugins;
14511
+ assertUniqueNpmPlugins([
14512
+ ...discoverPluginCatalog({ cwd: opts.cwd, storeRoot: opts.storeRoot }).plugins.filter(
14513
+ (item) => item.source === "project" && item.manifest.id !== alias && !pins[item.manifest.id]
14514
+ ).map((item) => ({ alias: item.manifest.id, manifest: item.manifest })),
14515
+ ...Object.entries(pins).filter(([key]) => key !== alias).map(([key, locked]) => ({
14516
+ alias: key,
14517
+ manifest: loadPluginManifest(
14518
+ storeEntryDir(locked.pluginId ?? key, locked.version, locked.sha256, opts.storeRoot)
14519
+ )
14520
+ })),
14521
+ { alias, manifest: entry.manifest }
14522
+ ]);
14523
+ const previous = pins[alias];
14268
14524
  if (previous && ((previous.pluginId ?? alias) !== entry.id || previous.registry !== this.baseUrl || previous.publisher !== pin.publisher))
14269
14525
  throw new Error("Plugin alias belongs to another publisher or registry");
14270
14526
  upsertLockEntry(opts.cwd ?? process.cwd(), alias, pin);