@octonodes/cli 0.2.4 → 0.2.6

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.4",
3
+ "version": "0.2.6",
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.4",
24
+ "@octonodes/sdk": "0.2.6",
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.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;
11544
+ exports2.PluginCloudBuild = exports2.PluginPublisherBuildDetail = exports2.PluginPublisherBuilds = exports2.PluginPublisherBuildQuery = exports2.PluginPublisherListQuery = 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)");
@@ -11612,17 +11612,32 @@ var require_plugin_publishing = __commonJS({
11612
11612
  repositoryId: zod_1.z.number(),
11613
11613
  buildId: zod_1.z.number()
11614
11614
  });
11615
+ exports2.PluginPublisherListQuery = zod_1.z.object({
11616
+ q: zod_1.z.string().trim().max(200).default(""),
11617
+ offset: zod_1.z.coerce.number().int().min(0).max(1e6).default(0),
11618
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(25),
11619
+ days: zod_1.z.enum(["7", "30", "90"]).default("30"),
11620
+ scope: zod_1.z.enum(["", "user", "group", "org", "public"]).default(""),
11621
+ source: zod_1.z.enum(["", "sdk", "npm"]).default(""),
11622
+ sort: zod_1.z.enum(["updated", "name", "version", "scope", "source", "installs", "score", "versions"]).default("updated"),
11623
+ direction: zod_1.z.enum(["asc", "desc"]).default("desc")
11624
+ });
11615
11625
  exports2.PluginPublisherBuildQuery = zod_1.z.object({
11616
11626
  repositoryId: zod_1.z.coerce.number().int().positive().optional(),
11617
- page: zod_1.z.coerce.number().int().min(1).max(1e3).default(1),
11627
+ page: zod_1.z.coerce.number().int().min(1).max(1e6).default(1),
11618
11628
  search: zod_1.z.string().max(200).default(""),
11619
11629
  status: zod_1.z.enum(["", "queued", "in_progress", "success", "failure"]).default(""),
11620
- sort: zod_1.z.enum(["newest", "oldest", "name"]).default("newest")
11630
+ sort: zod_1.z.enum(["newest", "oldest", "name", "status", "branch", "sha"]).default("newest"),
11631
+ direction: zod_1.z.enum(["asc", "desc"]).default("asc"),
11632
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(30),
11633
+ branch: zod_1.z.string().trim().max(200).default(""),
11634
+ sha: zod_1.z.string().trim().max(200).default("")
11621
11635
  });
11622
11636
  exports2.PluginPublisherBuilds = zod_1.z.object({
11623
11637
  total: zod_1.z.number(),
11624
11638
  page: zod_1.z.number(),
11625
11639
  hasMore: zod_1.z.boolean(),
11640
+ limit: zod_1.z.number(),
11626
11641
  items: zod_1.z.array(zod_1.z.object({
11627
11642
  id: zod_1.z.number(),
11628
11643
  repositoryId: zod_1.z.number(),
@@ -11670,6 +11685,43 @@ var require_plugin_publishing = __commonJS({
11670
11685
  }
11671
11686
  });
11672
11687
 
11688
+ // packages/schema/dist/plugin-allowlist.js
11689
+ var require_plugin_allowlist = __commonJS({
11690
+ "packages/schema/dist/plugin-allowlist.js"(exports2) {
11691
+ "use strict";
11692
+ Object.defineProperty(exports2, "__esModule", { value: true });
11693
+ exports2.PluginAllowlistPage = exports2.PluginAllowlistInput = exports2.PluginAllowlistQuery = exports2.PluginAllowlistKind = void 0;
11694
+ var zod_1 = require("zod");
11695
+ exports2.PluginAllowlistKind = zod_1.z.enum(["org", "team", "user"]);
11696
+ exports2.PluginAllowlistQuery = zod_1.z.object({
11697
+ kind: exports2.PluginAllowlistKind,
11698
+ q: zod_1.z.string().trim().max(200).default(""),
11699
+ status: zod_1.z.enum(["allowed", "available", "all"]).default("allowed"),
11700
+ sort: zod_1.z.enum(["name", "id"]).default("name"),
11701
+ direction: zod_1.z.enum(["asc", "desc"]).default("asc"),
11702
+ offset: zod_1.z.coerce.number().int().min(0).max(1e6).default(0),
11703
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(25)
11704
+ });
11705
+ exports2.PluginAllowlistInput = zod_1.z.object({
11706
+ kind: exports2.PluginAllowlistKind,
11707
+ id: zod_1.z.string().min(1).max(200)
11708
+ }).strict();
11709
+ exports2.PluginAllowlistPage = zod_1.z.object({
11710
+ items: zod_1.z.array(zod_1.z.object({
11711
+ id: zod_1.z.string(),
11712
+ name: zod_1.z.string(),
11713
+ allowed: zod_1.z.boolean(),
11714
+ inherited: zod_1.z.boolean(),
11715
+ eligible: zod_1.z.boolean()
11716
+ })),
11717
+ total: zod_1.z.number(),
11718
+ offset: zod_1.z.number(),
11719
+ limit: zod_1.z.number(),
11720
+ canAdd: zod_1.z.boolean()
11721
+ });
11722
+ }
11723
+ });
11724
+
11673
11725
  // packages/schema/dist/index.js
11674
11726
  var require_dist = __commonJS({
11675
11727
  "packages/schema/dist/index.js"(exports2) {
@@ -11735,6 +11787,7 @@ var require_dist = __commonJS({
11735
11787
  __exportStar(require_execution(), exports2);
11736
11788
  __exportStar(require_execution_constants(), exports2);
11737
11789
  __exportStar(require_plugin_publishing(), exports2);
11790
+ __exportStar(require_plugin_allowlist(), exports2);
11738
11791
  }
11739
11792
  });
11740
11793
 
@@ -12201,8 +12254,8 @@ var require_constants2 = __commonJS({
12201
12254
  exports2.OCTONODE_STORE_DIR = OCTONODE_STORE_DIR;
12202
12255
  var OCTONODE_STUDIO_URL = () => exports2.PROCESS_ENV.OCTONODE_STUDIO_URL ?? exports2.PROCESS_ENV.VITE_OCTONODE_STUDIO_URL ?? exports2.PROCESS_ENV.VITE_PUBLIC_OCTONODE_STUDIO_URL;
12203
12256
  exports2.OCTONODE_STUDIO_URL = OCTONODE_STUDIO_URL;
12204
- var OCTONODE_USER = () => exports2.PROCESS_ENV.OCTONODE_USER;
12205
- exports2.OCTONODE_USER = OCTONODE_USER;
12257
+ var OCTONODE_USER2 = () => exports2.PROCESS_ENV.OCTONODE_USER;
12258
+ exports2.OCTONODE_USER = OCTONODE_USER2;
12206
12259
  var OCTONODE_USER_EMAIL = () => exports2.PROCESS_ENV.OCTONODE_USER_EMAIL;
12207
12260
  exports2.OCTONODE_USER_EMAIL = OCTONODE_USER_EMAIL;
12208
12261
  var OCTONODE_VISUAL_PORT = () => exports2.PROCESS_ENV.OCTONODE_VISUAL_PORT;
@@ -12716,6 +12769,7 @@ var PLUGIN_BUNDLE_EXCLUDES = [
12716
12769
  "*.pem",
12717
12770
  "*.key"
12718
12771
  ];
12772
+ var MAX_CATALOG_MANIFESTS = 512;
12719
12773
 
12720
12774
  // packages/plugin/src/integrity.ts
12721
12775
  function collectFiles(dir, base, files) {
@@ -12875,6 +12929,79 @@ function loadPlugin(dir, source = "user") {
12875
12929
  function isPluginDir(dir) {
12876
12930
  return import_schema2.PLUGIN_DEFINITION_FILENAMES.some((name) => (0, import_node_fs4.existsSync)((0, import_node_path4.join)(dir, name)));
12877
12931
  }
12932
+ function pluginDirsIn(root) {
12933
+ if (!(0, import_node_fs4.existsSync)(root)) return [];
12934
+ const out = [];
12935
+ for (const name of (0, import_node_fs4.readdirSync)(root)) {
12936
+ if (name.startsWith(".")) continue;
12937
+ const dir = (0, import_node_path4.join)(root, name);
12938
+ try {
12939
+ if ((0, import_node_fs4.statSync)(dir).isDirectory() && isPluginDir(dir)) out.push(dir);
12940
+ } catch {
12941
+ }
12942
+ }
12943
+ return out;
12944
+ }
12945
+ function discoverPluginCatalog(opts = {}) {
12946
+ return discover(opts, true);
12947
+ }
12948
+ var catalogManifests = /* @__PURE__ */ new Map();
12949
+ function catalogManifest(dir) {
12950
+ const path = (0, import_plugin_runtime2.pluginDefinitionPath)(dir);
12951
+ const stat = (0, import_node_fs4.statSync)(path);
12952
+ const revision = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
12953
+ const cached = catalogManifests.get(path);
12954
+ if (cached?.revision === revision) return cached.manifest;
12955
+ const manifest2 = loadPluginManifest(dir);
12956
+ if (catalogManifests.size >= MAX_CATALOG_MANIFESTS) catalogManifests.delete(catalogManifests.keys().next().value);
12957
+ catalogManifests.set(path, { revision, manifest: manifest2 });
12958
+ return manifest2;
12959
+ }
12960
+ function discover(opts, metadataOnly) {
12961
+ const plugins = [];
12962
+ const errors = [];
12963
+ const seen = /* @__PURE__ */ new Set();
12964
+ const scanRoot = (root, source) => {
12965
+ for (const dir of pluginDirsIn(root)) {
12966
+ try {
12967
+ const manifest2 = metadataOnly ? catalogManifest(dir) : loadPluginManifest(dir);
12968
+ if (seen.has(manifest2.id)) continue;
12969
+ seen.add(manifest2.id);
12970
+ plugins.push({ manifest: manifest2, dir, source });
12971
+ } catch (err) {
12972
+ errors.push({ dir, message: err.message });
12973
+ }
12974
+ }
12975
+ };
12976
+ scanRoot(projectPluginRoot(opts.cwd), "project");
12977
+ try {
12978
+ const lock = readLockfile(opts.cwd);
12979
+ for (const [id, entry] of Object.entries(lock.plugins)) {
12980
+ if (seen.has(id)) continue;
12981
+ const dir = storeEntryDir(entry.pluginId ?? id, entry.version, entry.sha256, opts.storeRoot);
12982
+ if (!isPluginDir(dir)) {
12983
+ errors.push({
12984
+ dir,
12985
+ message: `plugin "${id}"@${entry.version} is locked but missing from the store \u2014 run \`octonode install\``
12986
+ });
12987
+ continue;
12988
+ }
12989
+ try {
12990
+ const manifest2 = metadataOnly ? catalogManifest(dir) : loadPluginManifest(dir);
12991
+ if (manifest2.id !== (entry.pluginId ?? id) || manifest2.version !== entry.version || !metadataOnly && hashPluginDir(dir) !== entry.sha256)
12992
+ throw new Error(`Locked plugin ${id} failed identity or integrity validation; run octonode install`);
12993
+ seen.add(id);
12994
+ plugins.push({ manifest: { ...manifest2, id }, dir, source: "store" });
12995
+ } catch (err) {
12996
+ errors.push({ dir, message: err.message });
12997
+ }
12998
+ }
12999
+ } catch (err) {
13000
+ errors.push({ dir: opts.cwd ?? process.cwd(), message: err.message });
13001
+ }
13002
+ scanRoot(opts.userRoot ?? userPluginRoot(), "user");
13003
+ return { plugins, errors };
13004
+ }
12878
13005
 
12879
13006
  // packages/plugin/src/dependencies.ts
12880
13007
  var import_node_child_process = require("node:child_process");
@@ -13254,7 +13381,7 @@ async function run(project, command, cwd, cacheRoot) {
13254
13381
  maxBuffer: 4 * 1024 * 1024
13255
13382
  });
13256
13383
  }
13257
- async function verifyProjectDependencies(project, names) {
13384
+ async function verifyProjectDependencies(project, names, versions = {}) {
13258
13385
  if (!names.length) return;
13259
13386
  const pnp = (0, import_node_path6.join)(project.root, ".pnp.cjs");
13260
13387
  const loader = (0, import_node_path6.join)(project.root, ".pnp.loader.mjs");
@@ -13266,14 +13393,37 @@ async function verifyProjectDependencies(project, names) {
13266
13393
  "--input-type=module",
13267
13394
  "-e",
13268
13395
  `import { createRequire } from 'node:module';
13269
- import { existsSync } from 'node:fs';
13396
+ import { existsSync, readFileSync } from 'node:fs';
13397
+ import { dirname, join } from 'node:path';
13270
13398
  import { fileURLToPath } from 'node:url';
13271
13399
  const require = createRequire(${JSON.stringify((0, import_node_path6.join)(project.target, "package.json"))});
13400
+ const versions = ${JSON.stringify(versions)};
13272
13401
  for (const name of ${JSON.stringify(names)}) {
13273
- try { require.resolve(name); } catch {
13402
+ let resolved;
13403
+ try { resolved = require.resolve(name); } catch {
13274
13404
  const entry = import.meta.resolve(name);
13405
+ resolved = entry.startsWith('file:') ? fileURLToPath(entry) : undefined;
13275
13406
  if (entry.startsWith('file:') && !existsSync(fileURLToPath(entry))) throw new Error('missing package entry: ' + name);
13276
13407
  }
13408
+ const identity = name.startsWith('@') ? name.split('/').slice(0, 2).join('/') : name.split('/')[0];
13409
+ if (Object.hasOwn(versions, identity)) {
13410
+ let directory = resolved ? dirname(resolved) : '';
13411
+ let matched = false;
13412
+ while (directory) {
13413
+ const file = join(directory, 'package.json');
13414
+ if (existsSync(file)) {
13415
+ const pkg = JSON.parse(readFileSync(file, 'utf8'));
13416
+ if (pkg.name === identity) {
13417
+ if (pkg.version !== versions[identity]) throw new Error('installed dependency version mismatch: ' + name);
13418
+ matched = true; break;
13419
+ }
13420
+ }
13421
+ const parent = dirname(directory);
13422
+ if (parent === directory) break;
13423
+ directory = parent;
13424
+ }
13425
+ if (!matched) throw new Error('cannot verify installed package: ' + name);
13426
+ }
13277
13427
  }`
13278
13428
  ],
13279
13429
  { cwd: project.target, env: (0, import_common.childProcessEnvironment)(), timeout: 3e4, maxBuffer: 1024 * 1024 }
@@ -13323,7 +13473,40 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13323
13473
  throw new Error("invalid npm dependency subpath");
13324
13474
  }
13325
13475
  }
13476
+ const unique = /* @__PURE__ */ new Map();
13477
+ for (const dependency of dependencies) {
13478
+ const spec = dependency.spec.startsWith(`${dependency.name}@`) ? dependency.spec.slice(dependency.name.length + 1) : dependency.spec;
13479
+ const previous = unique.get(dependency.name);
13480
+ if (previous && previous.spec !== spec) throw new Error(`Conflicting dependency requests for ${dependency.name}`);
13481
+ unique.set(dependency.name, {
13482
+ ...dependency,
13483
+ spec,
13484
+ ...previous?.subpaths || dependency.subpaths ? { subpaths: [.../* @__PURE__ */ new Set([...previous?.subpaths ?? [], ...dependency.subpaths ?? []])] } : {}
13485
+ });
13486
+ }
13487
+ dependencies = [...unique.values()];
13326
13488
  const project = resolveDependencyProject(cwd);
13489
+ const declared = manifest(project.target);
13490
+ const existing = { ...declared.devDependencies, ...declared.optionalDependencies, ...declared.dependencies };
13491
+ if (options.preserveExisting) {
13492
+ for (const { name, spec } of dependencies)
13493
+ 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))))
13494
+ throw new Error(
13495
+ `Dependency conflict: ${name} is ${existing[name]}, plugin requires ${spec}; reconcile it explicitly first`
13496
+ );
13497
+ }
13498
+ const reusable = dependencies.length > 0 && !options.remove?.length && dependencies.every(
13499
+ ({ name, spec }) => existing[name] === spec && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(spec)
13500
+ );
13501
+ const reuse = reusable && await verifyProjectDependencies(
13502
+ project,
13503
+ dependencies.flatMap(({ name, subpaths }) => subpaths ? subpaths.map((path) => name + path.slice(1)) : [name]),
13504
+ Object.fromEntries(dependencies.map(({ name, spec }) => [name, spec]))
13505
+ ).then(
13506
+ () => true,
13507
+ () => false
13508
+ );
13509
+ const metadataOnly = options.metadataOnly || reuse;
13327
13510
  const packageJson = (0, import_node_path6.join)(project.target, "package.json");
13328
13511
  if (!dependencies.length && !(0, import_node_fs6.existsSync)(packageJson) && !options.commit) return;
13329
13512
  const hasLock = PACKAGE_MANAGER_LOCKS[project.manager].some((file) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(project.root, file)));
@@ -13359,13 +13542,12 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13359
13542
  else (0, import_node_fs6.writeFileSync)(path, content);
13360
13543
  }
13361
13544
  };
13362
- if (hadInstall && !hasLock && !options.metadataOnly)
13545
+ if (hadInstall && !hasLock && !metadataOnly)
13363
13546
  throw new Error(
13364
13547
  "create a native lockfile before updating existing dependencies so failed installations can be recovered"
13365
13548
  );
13366
13549
  try {
13367
- if (options.metadataOnly && (0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(journal)))
13368
- (0, import_node_fs6.writeFileSync)((0, import_node_path6.join)((0, import_node_path6.dirname)(journal), "metadata-only"), "1");
13550
+ 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");
13369
13551
  if ((0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(journal)))
13370
13552
  (0, import_node_fs6.writeFileSync)(
13371
13553
  journal,
@@ -13376,7 +13558,7 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13376
13558
  }))
13377
13559
  )
13378
13560
  );
13379
- if (options.metadataOnly) {
13561
+ if (metadataOnly) {
13380
13562
  metadataTouched = true;
13381
13563
  options.commit?.();
13382
13564
  (0, import_node_fs6.rmSync)(journal, { force: true });
@@ -13468,14 +13650,14 @@ async function reconcileDependencies(cwd, dependencies, options = {}) {
13468
13650
  }
13469
13651
  restoreBefore();
13470
13652
  let recovery = "";
13471
- if (!options.metadataOnly && hadInstall && hasLock) {
13653
+ if (!metadataOnly && hadInstall && hasLock) {
13472
13654
  try {
13473
13655
  await run(project, installCommand(project, true), project.root, options.cacheRoot);
13474
13656
  } catch {
13475
13657
  recovery = "; manifests restored, but dependency recovery failed \u2014 run a frozen install before executing workflows";
13476
13658
  }
13477
13659
  restoreBefore();
13478
- } else if (!options.metadataOnly && !hadInstall) {
13660
+ } else if (!metadataOnly && !hadInstall) {
13479
13661
  (0, import_node_fs6.rmSync)((0, import_node_path6.join)(project.root, "node_modules"), { recursive: true, force: true });
13480
13662
  }
13481
13663
  for (const [path, content] of pnpBefore) {
@@ -13780,6 +13962,19 @@ async function generatePluginPackage(cwd, selections, expectedPacker) {
13780
13962
  function pluginNpmDependencies(manifest2) {
13781
13963
  return manifest2.integration?.npmDependencies ?? (manifest2.integration?.npm ? [manifest2.integration.npm] : []);
13782
13964
  }
13965
+ function assertUniqueNpmPlugins(plugins) {
13966
+ const owners = /* @__PURE__ */ new Map();
13967
+ for (const { alias, manifest: manifest2 } of plugins) {
13968
+ const pkg = manifest2.integration?.npm?.package.trim().toLowerCase();
13969
+ if (!pkg) continue;
13970
+ const previous = owners.get(pkg);
13971
+ if (previous && previous !== alias)
13972
+ throw new Error(
13973
+ `Use existing npm integration "${previous}" for ${pkg}; duplicate plugin alias "${alias}" is not allowed`
13974
+ );
13975
+ owners.set(pkg, alias);
13976
+ }
13977
+ }
13783
13978
 
13784
13979
  // packages/plugin/src/prepared/invocation.ts
13785
13980
  var import_common3 = __toESM(require_dist2());
@@ -13828,6 +14023,10 @@ async function reconcilePluginLock(cwd, next, options = {}) {
13828
14023
  throw new Error(`Plugin identity or integrity mismatch: ${alias}`);
13829
14024
  return { alias, directory, sha256: pin.sha256, manifest: manifest2, remote: preparedPluginPin(directory, pin) };
13830
14025
  });
14026
+ assertUniqueNpmPlugins([
14027
+ ...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 })),
14028
+ ...loaded
14029
+ ]);
13831
14030
  const libraries = loaded.filter((item) => item.manifest.library);
13832
14031
  const generated = libraries.length ? await generatePluginPackage(project.target, libraries) : void 0;
13833
14032
  const packageFile = (0, import_node_path10.join)(project.target, "package.json");
@@ -13924,6 +14123,9 @@ function installRoot(opts) {
13924
14123
  return opts.project ? { root: projectPluginRoot(opts.cwd), source: "project" } : { root: userPluginRoot(), source: "user" };
13925
14124
  }
13926
14125
  async function installNodeDeps(dir, opts = {}) {
14126
+ return installNodeDepsUnlocked(dir, opts, false);
14127
+ }
14128
+ async function installNodeDepsUnlocked(dir, opts, locked) {
13927
14129
  if (opts.noDeps) return {};
13928
14130
  const dependencies = pluginNpmDependencies(loadPluginManifest(dir));
13929
14131
  if (dependencies.length) {
@@ -13936,11 +14138,12 @@ async function installNodeDeps(dir, opts = {}) {
13936
14138
  });
13937
14139
  return {};
13938
14140
  }
13939
- await installProjectDependencies(
14141
+ await (locked ? reconcileDependencies : installProjectDependencies)(
13940
14142
  opts.cwd ?? process.cwd(),
13941
14143
  dependencies.map((npm) => ({ name: npm.package, spec: npm.spec })),
13942
14144
  {
13943
- cacheRoot: opts.cacheRoot
14145
+ cacheRoot: opts.cacheRoot,
14146
+ preserveExisting: true
13944
14147
  }
13945
14148
  );
13946
14149
  return { depsInstalled: true };
@@ -13976,6 +14179,10 @@ async function installNodeDeps(dir, opts = {}) {
13976
14179
  }
13977
14180
  }
13978
14181
  async function installPlugin(src, opts = {}) {
14182
+ if (opts.project) return withDependencyInstall(opts.cwd ?? process.cwd(), () => installPluginUnlocked(src, opts));
14183
+ return installPluginUnlocked(src, opts);
14184
+ }
14185
+ async function installPluginUnlocked(src, opts) {
13979
14186
  if (!(0, import_node_fs11.existsSync)(src) || !(0, import_node_fs11.statSync)(src).isDirectory()) {
13980
14187
  throw new Error(`source "${src}" is not a directory`);
13981
14188
  }
@@ -13984,13 +14191,20 @@ async function installPlugin(src, opts = {}) {
13984
14191
  }
13985
14192
  const { manifest: manifest2 } = loadPlugin(src);
13986
14193
  const { root, source } = installRoot(opts);
14194
+ const installed = discoverPluginCatalog({ cwd: opts.cwd }).plugins.filter(
14195
+ (item) => (opts.project ? item.source !== "user" : item.source === "user") && item.manifest.id !== manifest2.id
14196
+ );
14197
+ assertUniqueNpmPlugins([
14198
+ ...installed.map((item) => ({ alias: item.manifest.id, manifest: item.manifest })),
14199
+ { alias: manifest2.id, manifest: manifest2 }
14200
+ ]);
13987
14201
  const dest = (0, import_node_path11.join)(root, manifest2.id);
13988
14202
  if ((0, import_node_fs11.existsSync)(dest)) {
13989
14203
  if (!opts.force) {
13990
14204
  throw new Error(`plugin "${manifest2.id}" is already installed at ${dest} (use --force to overwrite)`);
13991
14205
  }
13992
14206
  }
13993
- const deps = pluginNpmDependencies(manifest2).length ? await installNodeDeps(src, { ...opts, cacheOnly: opts.cacheOnly ?? !opts.project }) : {};
14207
+ const deps = pluginNpmDependencies(manifest2).length ? await installNodeDepsUnlocked(src, { ...opts, cacheOnly: opts.cacheOnly ?? !opts.project }, !!opts.project) : {};
13994
14208
  if (deps.depsError) return { manifest: manifest2, dir: dest, source, ...deps };
13995
14209
  if ((0, import_node_fs11.existsSync)(dest)) (0, import_node_fs11.rmSync)(dest, { recursive: true, force: true });
13996
14210
  (0, import_node_fs11.mkdirSync)(root, { recursive: true });
@@ -14000,7 +14214,7 @@ async function installPlugin(src, opts = {}) {
14000
14214
  dir: dest,
14001
14215
  source,
14002
14216
  ...deps,
14003
- ...pluginNpmDependencies(manifest2).length ? {} : await installNodeDeps(dest, opts)
14217
+ ...pluginNpmDependencies(manifest2).length ? {} : await installNodeDepsUnlocked(dest, opts, !!opts.project)
14004
14218
  };
14005
14219
  }
14006
14220
 
@@ -14073,11 +14287,15 @@ var RemoteRegistry = class {
14073
14287
  }
14074
14288
  this.baseUrl = baseUrl.replace(/\/$/, "");
14075
14289
  this.token = opts.token ?? (0, import_common5.OCTONODE_MARKETPLACE_TOKEN)();
14290
+ this.workspace = opts.workspace ?? (0, import_common5.OCTONODE_USER)()?.match(/^workspace:((?:user|org|team):.+)$/)?.[1];
14076
14291
  }
14077
14292
  async request(path, init = {}) {
14078
14293
  const headers = new Headers(init.headers);
14079
14294
  if (this.token) headers.set("authorization", `Bearer ${this.token}`);
14080
- const res = await fetch(`${this.baseUrl}${path}`, {
14295
+ const url = new URL(`${this.baseUrl}${path}`);
14296
+ if (this.workspace && (init.method === void 0 || init.method === "GET" || url.pathname.endsWith("/install")))
14297
+ url.searchParams.set("workspace", this.workspace);
14298
+ const res = await fetch(url.toString(), {
14081
14299
  ...init,
14082
14300
  headers,
14083
14301
  redirect: "error",
@@ -14217,13 +14435,15 @@ var RemoteRegistry = class {
14217
14435
  */
14218
14436
  async install(id, opts = {}) {
14219
14437
  const detail = await this.detail(id, opts.scope);
14438
+ if (detail.manifestId && detail.manifestId !== id)
14439
+ throw new Error("Publisher-qualified plugins require installToStore so their registry identity is preserved");
14220
14440
  const version = opts.version ?? detail.version;
14221
14441
  const release2 = detail.versions?.find((entry) => entry.version === version);
14222
14442
  const dir = await this.downloadBundle(
14223
14443
  id,
14224
14444
  version,
14225
14445
  opts.scope,
14226
- id,
14446
+ detail.manifestId ?? id,
14227
14447
  release2?.archiveSha256 ?? release2?.sha256
14228
14448
  );
14229
14449
  const plugin = await installPlugin(dir, opts);
@@ -14256,7 +14476,7 @@ var RemoteRegistry = class {
14256
14476
  throw new Error("Marketplace release is missing its immutable checksum");
14257
14477
  const availableScopes = versionMeta?.scope ?? detail.scope;
14258
14478
  const scope = opts.scope && availableScopes.includes(opts.scope) ? opts.scope : availableScopes[0];
14259
- const dir = await this.downloadBundle(id, version, scope, id, releaseChecksum);
14479
+ const dir = await this.downloadBundle(id, version, scope, detail.manifestId ?? id, releaseChecksum);
14260
14480
  const entry = (() => {
14261
14481
  try {
14262
14482
  return addToStore(dir, { storeRoot: opts.storeRoot });
@@ -14271,7 +14491,7 @@ var RemoteRegistry = class {
14271
14491
  "Library installation requires native dependency activation; use frozen --artifacts-only for CI restore"
14272
14492
  );
14273
14493
  if (!opts.cacheOnly && !opts.noDeps) {
14274
- const alias2 = opts.alias ?? entry.id;
14494
+ const alias2 = opts.alias ?? (detail.manifestId && id !== detail.manifestId ? id : entry.id);
14275
14495
  const pin2 = {
14276
14496
  pluginId: entry.id,
14277
14497
  remoteId: id,
@@ -14323,7 +14543,7 @@ var RemoteRegistry = class {
14323
14543
  cacheOnly: opts.cacheOnly
14324
14544
  }) : {};
14325
14545
  if (deps.depsError) throw new Error(deps.depsError);
14326
- const alias = opts.alias ?? entry.id;
14546
+ const alias = opts.alias ?? (detail.manifestId && id !== detail.manifestId ? id : entry.id);
14327
14547
  const pin = {
14328
14548
  pluginId: entry.id,
14329
14549
  remoteId: id,
@@ -14346,7 +14566,20 @@ var RemoteRegistry = class {
14346
14566
  }
14347
14567
  try {
14348
14568
  await withDependencyInstall(opts.cwd ?? process.cwd(), async () => {
14349
- const previous = readLockfile(opts.cwd).plugins[alias];
14569
+ const pins = readLockfile(opts.cwd).plugins;
14570
+ assertUniqueNpmPlugins([
14571
+ ...discoverPluginCatalog({ cwd: opts.cwd, storeRoot: opts.storeRoot }).plugins.filter(
14572
+ (item) => item.source === "project" && item.manifest.id !== alias && !pins[item.manifest.id]
14573
+ ).map((item) => ({ alias: item.manifest.id, manifest: item.manifest })),
14574
+ ...Object.entries(pins).filter(([key]) => key !== alias).map(([key, locked]) => ({
14575
+ alias: key,
14576
+ manifest: loadPluginManifest(
14577
+ storeEntryDir(locked.pluginId ?? key, locked.version, locked.sha256, opts.storeRoot)
14578
+ )
14579
+ })),
14580
+ { alias, manifest: entry.manifest }
14581
+ ]);
14582
+ const previous = pins[alias];
14350
14583
  if (previous && ((previous.pluginId ?? alias) !== entry.id || previous.registry !== this.baseUrl || previous.publisher !== pin.publisher))
14351
14584
  throw new Error("Plugin alias belongs to another publisher or registry");
14352
14585
  upsertLockEntry(opts.cwd ?? process.cwd(), alias, pin);