omni-dsh-plugins 1.0.1 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/bin.js +258 -94
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -147,3 +147,18 @@ npx omni-dsh-plugins search tui \
147
147
  --catalog https://dsh-plugins.omniroute.online/catalog.snapshot.json \
148
148
  --revision <published-catalog-revision>
149
149
  ```
150
+
151
+ ## Releasing (maintainers)
152
+
153
+ Releases are tag-driven and fail closed. The npm artifact is built from the tagged commit —
154
+ never from a branch tip:
155
+
156
+ 1. Land the release commit (with the final `cli/package.json` version) on `main` through a
157
+ reviewed pull request.
158
+ 2. Tag that commit `cli-v<version>` (the version must equal `cli/package.json` of the tagged
159
+ commit) and push the tag. The push triggers `.github/workflows/release-npm.yml`, which
160
+ verifies the tag/version match, verifies the commit is an ancestor of `main`, runs tests,
161
+ checks the exact tarball manifest, smoke-runs the packed binary, and publishes with npm
162
+ provenance.
163
+ 3. `workflow_dispatch` exists only as a re-run fallback: it refuses to publish unless the
164
+ `cli-v<version>` tag already exists and points at exactly the commit checked out by the run.
package/dist/bin.js CHANGED
@@ -22989,6 +22989,70 @@ var import_semver = __toESM(require_semver2(), 1);
22989
22989
  var import_spdx_expression_parse = __toESM(require_spdx_expression_parse(), 1);
22990
22990
  var import_ssri = __toESM(require_lib(), 1);
22991
22991
  var import_yaml = __toESM(require_dist2(), 1);
22992
+ var MAX_MEDIA_ITEMS = 6;
22993
+ var MAX_MEDIA_ALT_LENGTH = 120;
22994
+ var REPOSITORY_URL = /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/u;
22995
+ var RAW_URL = /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)(\/.*)$/u;
22996
+ var ASSET_URL = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/assets(\/.*)$/u;
22997
+ var MEDIA_PATH = /^(?:\/(?!\.{1,2}(?:\/|$))[A-Za-z0-9._@+-]+)+$/u;
22998
+ function repositorySlug(repository) {
22999
+ const match = REPOSITORY_URL.exec(String(repository ?? ""));
23000
+ return match === null ? null : { owner: match[1], repo: match[2] };
23001
+ }
23002
+ function classifyMediaUrl(url, kind, slug, commit) {
23003
+ const raw = RAW_URL.exec(url);
23004
+ if (raw !== null) {
23005
+ if (!MEDIA_PATH.test(raw[4])) return "unusable";
23006
+ if (commit === "" || raw[3] !== commit) return "not-pinned";
23007
+ return slug !== null && raw[1] === slug.owner && raw[2] === slug.repo ? "ok" : "wrong-repository";
23008
+ }
23009
+ const asset = kind === "video" ? ASSET_URL.exec(url) : null;
23010
+ if (asset !== null) {
23011
+ if (!MEDIA_PATH.test(asset[3])) return "unusable";
23012
+ return slug !== null && asset[1] === slug.owner && asset[2] === slug.repo ? "ok" : "wrong-repository";
23013
+ }
23014
+ return "unusable";
23015
+ }
23016
+ function validateMediaField(media, source) {
23017
+ if (media === void 0) {
23018
+ return [];
23019
+ }
23020
+ if (!Array.isArray(media)) {
23021
+ return ["media must be an array"];
23022
+ }
23023
+ if (media.length === 0) {
23024
+ return ["media must not be an empty list"];
23025
+ }
23026
+ const errors = [];
23027
+ if (media.length > MAX_MEDIA_ITEMS) {
23028
+ errors.push(`media has more than ${MAX_MEDIA_ITEMS} items`);
23029
+ }
23030
+ const slug = repositorySlug(source.repository);
23031
+ const commit = String(source.commit ?? "");
23032
+ media.forEach((item, index) => {
23033
+ const value = typeof item === "object" && item !== null ? item : {};
23034
+ if (value.kind !== "screenshot" && value.kind !== "video") {
23035
+ errors.push(`media[${index}].kind must be "screenshot" or "video"`);
23036
+ }
23037
+ if (typeof value.alt !== "string" || value.alt.trim() === "" || value.alt.length > MAX_MEDIA_ALT_LENGTH) {
23038
+ errors.push(`media[${index}].alt must be 1-${MAX_MEDIA_ALT_LENGTH} characters`);
23039
+ }
23040
+ const url = typeof value.url === "string" ? value.url : "";
23041
+ switch (classifyMediaUrl(url, value.kind, slug, commit)) {
23042
+ case "ok":
23043
+ return;
23044
+ case "not-pinned":
23045
+ errors.push(`media[${index}].url must pin the entry commit, not a branch`);
23046
+ return;
23047
+ case "wrong-repository":
23048
+ errors.push(`media[${index}].url must reference the entry's own repository`);
23049
+ return;
23050
+ default:
23051
+ errors.push(`media[${index}].url must be a GitHub URL pinned to the entry commit`);
23052
+ }
23053
+ });
23054
+ return errors;
23055
+ }
22992
23056
  function parseSpdx(value) {
22993
23057
  try {
22994
23058
  return (0, import_spdx_expression_parse.default)(value);
@@ -23170,6 +23234,9 @@ function semanticIssues(entry) {
23170
23234
  }
23171
23235
  }
23172
23236
  }
23237
+ for (const message of validateMediaField(entry.media, entry.source)) {
23238
+ issues.push({ code: "invalid-media", message });
23239
+ }
23173
23240
  if (entry.verification.smokeTest !== null) {
23174
23241
  try {
23175
23242
  parseExactSemver(entry.verification.smokeTest.check.version);
@@ -23251,6 +23318,9 @@ function isContained2(root, candidate) {
23251
23318
  function hasErrorCode(error, code) {
23252
23319
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
23253
23320
  }
23321
+ function creatorSlug(creatorGithub) {
23322
+ return creatorGithub.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-");
23323
+ }
23254
23324
  function safeEntryLabel(fileName) {
23255
23325
  const safeName = fileName.replaceAll(/[^A-Za-z0-9._-]/gu, "_");
23256
23326
  return `${ENTRY_DIRECTORY}/${safeName || "invalid-entry"}`;
@@ -23347,6 +23417,9 @@ async function loadCatalog(input) {
23347
23417
  entryDirectory = await containedRealPath(root, requestedEntryDirectory) ?? "";
23348
23418
  } catch (error) {
23349
23419
  if (hasErrorCode(error, "ENOENT")) {
23420
+ if (normalized.source.kind === "snapshot") {
23421
+ diagnostics.push(degenerateSnapshotDiagnostic());
23422
+ }
23350
23423
  return snapshotResult(normalized.source, [], diagnostics);
23351
23424
  }
23352
23425
  diagnostics.push({
@@ -23430,6 +23503,28 @@ async function loadCatalog(input) {
23430
23503
  continue;
23431
23504
  }
23432
23505
  const entry = parsed;
23506
+ const baseName = directoryEntry.name.slice(0, -".yaml".length);
23507
+ let violatesIdConvention = false;
23508
+ if (entry.id !== baseName) {
23509
+ violatesIdConvention = true;
23510
+ diagnostics.push({
23511
+ file,
23512
+ code: "id-filename-mismatch",
23513
+ message: "public ID must equal the catalog file's basename"
23514
+ });
23515
+ }
23516
+ const expectedPrefix = `${creatorSlug(entry.creator.github)}-`;
23517
+ if (!entry.id.startsWith(expectedPrefix)) {
23518
+ violatesIdConvention = true;
23519
+ diagnostics.push({
23520
+ file,
23521
+ code: "id-creator-prefix",
23522
+ message: `public ID must start with the creator slug prefix "${expectedPrefix}"`
23523
+ });
23524
+ }
23525
+ if (violatesIdConvention) {
23526
+ continue;
23527
+ }
23433
23528
  try {
23434
23529
  candidates.push({
23435
23530
  canonicalKey: canonicalPluginKey(entry.source.repositoryNodeId, entry.source.subpath),
@@ -23462,8 +23557,18 @@ async function loadCatalog(input) {
23462
23557
  ...duplicateKeys.rejectedFiles
23463
23558
  ]);
23464
23559
  const entries = candidates.filter((candidate) => !rejectedFiles.has(candidate.file)).map((candidate) => candidate.entry).sort((left, right) => compareText2(left.id, right.id));
23560
+ if (normalized.source.kind === "snapshot" && entries.length === 0 && diagnostics.length === 0) {
23561
+ diagnostics.push(degenerateSnapshotDiagnostic());
23562
+ }
23465
23563
  return snapshotResult(normalized.source, entries, diagnostics);
23466
23564
  }
23565
+ function degenerateSnapshotDiagnostic() {
23566
+ return {
23567
+ file: ENTRY_DIRECTORY,
23568
+ code: "degenerate-snapshot",
23569
+ message: "materialized snapshot contains no plugin entries; the published catalog is never empty, so this snapshot is broken or truncated"
23570
+ };
23571
+ }
23467
23572
 
23468
23573
  // src/catalogSnapshot.ts
23469
23574
  import { constants } from "node:fs";
@@ -23671,15 +23776,15 @@ var CATALOG_SNAPSHOT_FORMAT_V1 = "omni-dsh-catalog-snapshot-v1";
23671
23776
  var PUBLIC_CATALOG_RAW_ORIGIN = "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-dsh-plugins";
23672
23777
  var PUBLIC_SNAPSHOT_SITE_URL = "https://dsh-plugins.omniroute.online/catalog.snapshot.json";
23673
23778
  var CATALOG_SNAPSHOT_LIMITS = Object.freeze({
23674
- snapshotBytes: 10 * 1024 * 1024,
23675
- totalFileBytes: 8 * 1024 * 1024,
23779
+ snapshotBytes: 128 * 1024 * 1024,
23780
+ totalFileBytes: 128 * 1024 * 1024,
23676
23781
  fileBytes: 1024 * 1024,
23677
- files: 2048,
23678
- directoryEntries: 4096,
23782
+ files: 32768,
23783
+ directoryEntries: 32768,
23679
23784
  pathBytes: 512,
23680
23785
  redirects: 3,
23681
23786
  gitOutputBytes: 64 * 1024,
23682
- gitTreeBytes: 2 * 1024 * 1024,
23787
+ gitTreeBytes: 32 * 1024 * 1024,
23683
23788
  pathDepth: 16
23684
23789
  });
23685
23790
  var CATALOG_DEADLINE_DEFAULTS = Object.freeze({
@@ -24465,8 +24570,8 @@ async function materializeSelection(selection2, dependencies) {
24465
24570
 
24466
24571
  // src/commands/catalog.ts
24467
24572
  var import_yaml3 = __toESM(require_dist2(), 1);
24468
- import { readFile as readFile3, realpath as realpath4 } from "node:fs/promises";
24469
- import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4, sep as sep4 } from "node:path";
24573
+ import { readdir as readdir2, readFile as readFile3, realpath as realpath4 } from "node:fs/promises";
24574
+ import { isAbsolute as isAbsolute4, join as join2, relative as relative4, resolve as resolve4, sep as sep4 } from "node:path";
24470
24575
  function sortedDiagnostics(diagnostics) {
24471
24576
  return [...diagnostics].sort(
24472
24577
  (left, right) => left.file.localeCompare(right.file) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message)
@@ -24560,6 +24665,64 @@ function hasBalancedMarkdownFences(content) {
24560
24665
  }
24561
24666
  return open4 === null;
24562
24667
  }
24668
+ var TRANSLATED_DOCUMENTS = [
24669
+ "README.md",
24670
+ "CONTRIBUTING.md",
24671
+ "SECURITY.md",
24672
+ "docs/SCHEMA.md",
24673
+ "docs/CLI.md",
24674
+ "docs/GOVERNANCE.md",
24675
+ "docs/CATEGORIES.md",
24676
+ "docs/CREDIT.md",
24677
+ "docs/RANKING.md",
24678
+ "docs/UNOFFICIAL.md"
24679
+ ];
24680
+ var LOCALE_NAME = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$/u;
24681
+ function documentShape(content) {
24682
+ const lines = content.split(/\r?\n/u);
24683
+ return {
24684
+ sections: lines.filter((line) => line.startsWith("## ")).length,
24685
+ subsections: lines.filter((line) => line.startsWith("### ")).length,
24686
+ rows: lines.filter((line) => line.startsWith("| ")).length
24687
+ };
24688
+ }
24689
+ async function translationShapeDiagnostics(root) {
24690
+ let locales;
24691
+ try {
24692
+ locales = (await readdir2(join2(root, "docs", "i18n"), { withFileTypes: true })).filter((entry) => entry.isDirectory() && LOCALE_NAME.test(entry.name)).map((entry) => entry.name).sort();
24693
+ } catch {
24694
+ return [];
24695
+ }
24696
+ const diagnostics = [];
24697
+ for (const document of TRANSLATED_DOCUMENTS) {
24698
+ let english;
24699
+ try {
24700
+ english = documentShape(await readContained(root, document));
24701
+ } catch {
24702
+ continue;
24703
+ }
24704
+ for (const locale of locales) {
24705
+ const file = `docs/i18n/${locale}/${document.split("/").at(-1)}`;
24706
+ let translated;
24707
+ try {
24708
+ translated = documentShape(await readContained(root, file));
24709
+ } catch {
24710
+ continue;
24711
+ }
24712
+ const drift = ["sections", "subsections", "rows"].filter(
24713
+ (part) => translated[part] !== english[part]
24714
+ );
24715
+ if (drift.length > 0) {
24716
+ diagnostics.push({
24717
+ file,
24718
+ code: "docs",
24719
+ message: `translation does not match the shape of ${document}: ` + drift.map((part) => `${part} ${translated[part]} != ${english[part]}`).join(", ")
24720
+ });
24721
+ }
24722
+ }
24723
+ }
24724
+ return diagnostics;
24725
+ }
24563
24726
  function tableValues(content, heading) {
24564
24727
  const start = content.indexOf(`## ${heading}`);
24565
24728
  if (start < 0) {
@@ -24585,7 +24748,7 @@ function schemaEnum(document, property) {
24585
24748
  function sameValues(left, right) {
24586
24749
  return [...left].sort().join("\0") === [...right].sort().join("\0");
24587
24750
  }
24588
- async function docsCheckCommand(context, rootInput) {
24751
+ async function docsCheckCommand(context, rootInput, options = {}) {
24589
24752
  let root;
24590
24753
  try {
24591
24754
  root = await canonicalRoot(rootInput);
@@ -24624,6 +24787,7 @@ async function docsCheckCommand(context, rootInput) {
24624
24787
  diagnostics.push({ file, code: "docs", message: "unbalanced Markdown fence" });
24625
24788
  }
24626
24789
  }
24790
+ diagnostics.push(...await translationShapeDiagnostics(root));
24627
24791
  try {
24628
24792
  const categories = contents.get("docs/CATEGORIES.md");
24629
24793
  const schema = (0, import_yaml3.parse)(await readContained(root, "schemas/plugin.schema.yaml"), {
@@ -24658,7 +24822,7 @@ async function docsCheckCommand(context, rootInput) {
24658
24822
  const snapshot = await context.loadCatalog({ root: rootInput });
24659
24823
  diagnostics.push(...snapshot.diagnostics);
24660
24824
  const readme = contents.get("README.md");
24661
- if (readme !== void 0 && !readme.includes(`**${snapshot.entries.length} plugins merged.**`)) {
24825
+ if (options.skipCount !== true && readme !== void 0 && !readme.includes(`**${snapshot.entries.length} plugins merged.**`)) {
24662
24826
  diagnostics.push({
24663
24827
  file: "README.md",
24664
24828
  code: "docs",
@@ -24882,12 +25046,12 @@ async function openArtifactDeliveryChannel(lease, options) {
24882
25046
  // src/dsh/installState.ts
24883
25047
  import { randomUUID } from "node:crypto";
24884
25048
  import { homedir, hostname as localHostname } from "node:os";
24885
- import { join as join3, resolve as resolve6 } from "node:path";
24886
- import { lstat as lstat4, mkdir as mkdir3, readFile as readFile4, readdir as readdir2, realpath as realpath6, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
25049
+ import { join as join4, resolve as resolve6 } from "node:path";
25050
+ import { lstat as lstat4, mkdir as mkdir3, readFile as readFile4, readdir as readdir3, realpath as realpath6, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
24887
25051
 
24888
25052
  // src/dsh/paths.ts
24889
25053
  import { lstat as lstat3, mkdir as mkdir2, realpath as realpath5 } from "node:fs/promises";
24890
- import { isAbsolute as isAbsolute5, join as join2, relative as relative5, resolve as resolve5, sep as sep5 } from "node:path";
25054
+ import { isAbsolute as isAbsolute5, join as join3, relative as relative5, resolve as resolve5, sep as sep5 } from "node:path";
24891
25055
  var SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
24892
25056
  function isPathWithin(root, candidate) {
24893
25057
  const child = relative5(root, candidate);
@@ -24916,7 +25080,7 @@ async function ensureContainedDirectory(canonicalRoot2, ...segments) {
24916
25080
  let current = canonicalRoot2;
24917
25081
  for (const segment of segments) {
24918
25082
  assertSafeCacheSegment(segment, "managed path segment");
24919
- const candidate = join2(current, segment);
25083
+ const candidate = join3(current, segment);
24920
25084
  if (!isPathWithin(canonicalRoot2, candidate)) {
24921
25085
  throw new CliSafetyError("managed path escapes its root");
24922
25086
  }
@@ -25060,7 +25224,7 @@ function parseRecoveryMarker(value) {
25060
25224
  }
25061
25225
  function resolveDshHome(env = process.env) {
25062
25226
  const configured = env.DSH_HOME?.trim();
25063
- return resolve6(configured === void 0 || configured === "" ? join3(homedir(), ".dsh") : configured);
25227
+ return resolve6(configured === void 0 || configured === "" ? join4(homedir(), ".dsh") : configured);
25064
25228
  }
25065
25229
  function safeRelativeCachePath(value) {
25066
25230
  const segments = value.split("/");
@@ -25138,7 +25302,7 @@ async function readInstallState(home) {
25138
25302
  }
25139
25303
  throw error;
25140
25304
  }
25141
- const statePath = join3(canonicalHome, ".dsh-plugins", "state.json");
25305
+ const statePath = join4(canonicalHome, ".dsh-plugins", "state.json");
25142
25306
  try {
25143
25307
  const info = await lstat4(statePath);
25144
25308
  if (info.isSymbolicLink() || !info.isFile()) {
@@ -25207,7 +25371,7 @@ async function readStateWriterOwner(lockPath) {
25207
25371
  if (info.isSymbolicLink() || !info.isDirectory()) {
25208
25372
  throw new CliSafetyError("install state publication lock path is unsafe");
25209
25373
  }
25210
- const source = await readFile4(join3(lockPath, "owner.json"), "utf8");
25374
+ const source = await readFile4(join4(lockPath, "owner.json"), "utf8");
25211
25375
  if (source.length > 4096) throw new Error("oversized-owner");
25212
25376
  const value = JSON.parse(source);
25213
25377
  if (value.version !== 1 || typeof value.ownerToken !== "string" || !SAFE_WRITER_TOKEN.test(value.ownerToken) || !Number.isSafeInteger(value.fencingToken) || (value.fencingToken ?? 0) < 0 || !Number.isFinite(value.acquiredAt) || !Number.isFinite(value.leaseMs) || (value.leaseMs ?? 0) <= 0 || !Number.isSafeInteger(value.pid) || (value.pid ?? 0) <= 0 || typeof value.processStartIdentity !== "string" || !SAFE_PROCESS_IDENTITY.test(value.processStartIdentity) || typeof value.hostname !== "string" || value.hostname.length === 0 || value.hostname.length > 255) {
@@ -25248,7 +25412,7 @@ async function writeStateWriterFence(path, value) {
25248
25412
  }
25249
25413
  }
25250
25414
  async function writeStateWriterOwner(lockPath, owner) {
25251
- const temporary = join3(lockPath, `.owner-${randomUUID()}.tmp`);
25415
+ const temporary = join4(lockPath, `.owner-${randomUUID()}.tmp`);
25252
25416
  try {
25253
25417
  await writeFile2(temporary, `${JSON.stringify(owner)}
25254
25418
  `, {
@@ -25256,7 +25420,7 @@ async function writeStateWriterOwner(lockPath, owner) {
25256
25420
  mode: 384,
25257
25421
  flag: "wx"
25258
25422
  });
25259
- await rename(temporary, join3(lockPath, "owner.json"));
25423
+ await rename(temporary, join4(lockPath, "owner.json"));
25260
25424
  } catch {
25261
25425
  await rm2(temporary, { force: true }).catch(() => void 0);
25262
25426
  throw new CliSafetyError("install state publication lock metadata could not be published");
@@ -25266,7 +25430,7 @@ async function latestStateWriterHeartbeat(lockPath, owner) {
25266
25430
  let latest = owner.acquiredAt;
25267
25431
  const prefix = `heartbeat-${owner.ownerToken}-`;
25268
25432
  try {
25269
- for (const entry of await readdir2(lockPath, { withFileTypes: true })) {
25433
+ for (const entry of await readdir3(lockPath, { withFileTypes: true })) {
25270
25434
  if (!entry.isDirectory() || !entry.name.startsWith(prefix)) continue;
25271
25435
  const timestamp = Number(entry.name.slice(prefix.length));
25272
25436
  if (Number.isFinite(timestamp)) latest = Math.max(latest, timestamp);
@@ -25304,15 +25468,15 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25304
25468
  if (!Number.isSafeInteger(pid) || pid <= 0 || !SAFE_PROCESS_IDENTITY.test(processStartIdentity) || hostname.length === 0 || hostname.length > 255) {
25305
25469
  throw new CliSafetyError("install state writer identity is invalid");
25306
25470
  }
25307
- const lockPath = join3(stateDirectory, ".state-write.lock");
25308
- const fencePath = join3(stateDirectory, ".state-write.fence.json");
25471
+ const lockPath = join4(stateDirectory, ".state-write.lock");
25472
+ const fencePath = join4(stateDirectory, ".state-write.fence.json");
25309
25473
  const startedAt = now();
25310
25474
  while (true) {
25311
25475
  const ownerToken = makeOwnerToken();
25312
25476
  if (!SAFE_WRITER_TOKEN.test(ownerToken)) {
25313
25477
  throw new CliSafetyError("install state writer owner token is invalid");
25314
25478
  }
25315
- const candidate = join3(stateDirectory, `.state-write.claim-${randomUUID()}`);
25479
+ const candidate = join4(stateDirectory, `.state-write.claim-${randomUUID()}`);
25316
25480
  const provisionalOwner = {
25317
25481
  version: 1,
25318
25482
  ownerToken,
@@ -25327,7 +25491,7 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25327
25491
  let fencingToken = 0;
25328
25492
  try {
25329
25493
  await mkdir3(candidate, { mode: 448 });
25330
- await writeFile2(join3(candidate, "owner.json"), `${JSON.stringify(provisionalOwner)}
25494
+ await writeFile2(join4(candidate, "owner.json"), `${JSON.stringify(provisionalOwner)}
25331
25495
  `, {
25332
25496
  encoding: "utf8",
25333
25497
  mode: 384,
@@ -25341,7 +25505,7 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25341
25505
  } catch (error) {
25342
25506
  await rm2(candidate, { recursive: true, force: true }).catch(() => void 0);
25343
25507
  if (claimed) {
25344
- const failedPath = join3(stateDirectory, `.state-write.failed-${randomUUID()}`);
25508
+ const failedPath = join4(stateDirectory, `.state-write.failed-${randomUUID()}`);
25345
25509
  try {
25346
25510
  const current2 = await readStateWriterOwner(lockPath);
25347
25511
  if (current2?.ownerToken === ownerToken) {
@@ -25373,7 +25537,7 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25373
25537
  if (leaseExpired && ownerProvenDead) {
25374
25538
  const confirmed = await readStateWriterOwner(lockPath);
25375
25539
  if (confirmed?.ownerToken === current.ownerToken && confirmed.fencingToken === current.fencingToken && now() - await latestStateWriterHeartbeat(lockPath, confirmed) > confirmed.leaseMs) {
25376
- const stalePath = join3(stateDirectory, `.state-write.stale-${randomUUID()}`);
25540
+ const stalePath = join4(stateDirectory, `.state-write.stale-${randomUUID()}`);
25377
25541
  try {
25378
25542
  await rename(lockPath, stalePath);
25379
25543
  const detached = await readStateWriterOwner(stalePath);
@@ -25410,7 +25574,7 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25410
25574
  const heartbeat = async () => {
25411
25575
  await assertOwned();
25412
25576
  try {
25413
- await mkdir3(join3(lockPath, `heartbeat-${ownerToken}-${now()}`), { mode: 448 });
25577
+ await mkdir3(join4(lockPath, `heartbeat-${ownerToken}-${now()}`), { mode: 448 });
25414
25578
  } catch (error) {
25415
25579
  if (stateWriterErrorCode(error) !== "EEXIST") {
25416
25580
  lost = true;
@@ -25432,7 +25596,7 @@ async function acquireStateWriter(stateDirectory, options = {}) {
25432
25596
  if (current === null || current.ownerToken !== ownerToken || current.fencingToken !== fencingToken || current.processStartIdentity !== processStartIdentity) {
25433
25597
  return;
25434
25598
  }
25435
- const releasePath = join3(stateDirectory, `.state-write.release-${ownerToken}`);
25599
+ const releasePath = join4(stateDirectory, `.state-write.release-${ownerToken}`);
25436
25600
  try {
25437
25601
  await rename(lockPath, releasePath);
25438
25602
  const detached = await readStateWriterOwner(releasePath);
@@ -25459,8 +25623,8 @@ async function writeInstallState(home, state, options) {
25459
25623
  const canonicalHome = await ensureCanonicalHome(home);
25460
25624
  const stateDirectory = await ensureContainedDirectory(canonicalHome, ".dsh-plugins");
25461
25625
  const writer = await acquireStateWriter(stateDirectory, options?.writer);
25462
- const target = join3(stateDirectory, "state.json");
25463
- const temporary = join3(stateDirectory, `.state-${randomUUID()}.tmp`);
25626
+ const target = join4(stateDirectory, "state.json");
25627
+ const temporary = join4(stateDirectory, `.state-${randomUUID()}.tmp`);
25464
25628
  try {
25465
25629
  const current = await readInstallState(canonicalHome);
25466
25630
  const currentGeneration = current.generation ?? 0;
@@ -25517,8 +25681,8 @@ async function writeInstallState(home, state, options) {
25517
25681
  // src/dsh/profileLock.ts
25518
25682
  import { randomUUID as randomUUID2 } from "node:crypto";
25519
25683
  import { hostname as localHostname2 } from "node:os";
25520
- import { join as join4 } from "node:path";
25521
- import { lstat as lstat5, mkdir as mkdir4, readFile as readFile5, readdir as readdir3, rename as rename2, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
25684
+ import { join as join5 } from "node:path";
25685
+ import { lstat as lstat5, mkdir as mkdir4, readFile as readFile5, readdir as readdir4, rename as rename2, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
25522
25686
  var DEFAULT_LEASE_MS = 3e4;
25523
25687
  var DEFAULT_HEARTBEAT_MS = 1e4;
25524
25688
  var DEFAULT_ACQUIRE_TIMEOUT_MS = 3e4;
@@ -25560,7 +25724,7 @@ async function readOwner(lockPath) {
25560
25724
  if (info.isSymbolicLink() || !info.isDirectory()) {
25561
25725
  throw new CliSafetyError("profile mutation lock path is unsafe");
25562
25726
  }
25563
- const source = await readFile5(join4(lockPath, "owner.json"), "utf8");
25727
+ const source = await readFile5(join5(lockPath, "owner.json"), "utf8");
25564
25728
  if (source.length > 4096) throw new Error("oversized-owner");
25565
25729
  const value = JSON.parse(source);
25566
25730
  if (value.version !== 1 || typeof value.ownerToken !== "string" || !SAFE_TOKEN.test(value.ownerToken) || !Number.isSafeInteger(value.fencingToken) || (value.fencingToken ?? 0) < 0 || typeof value.profile !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value.profile) || !Number.isFinite(value.acquiredAt) || !Number.isFinite(value.leaseMs) || !Number.isSafeInteger(value.pid) || (value.pid ?? 0) <= 0 || typeof value.hostname !== "string" || value.hostname.length === 0 || value.hostname.length > 255) {
@@ -25574,7 +25738,7 @@ async function readOwner(lockPath) {
25574
25738
  }
25575
25739
  }
25576
25740
  async function writeOwner(lockPath, owner) {
25577
- const temporary = join4(lockPath, `.owner-${randomUUID2()}.tmp`);
25741
+ const temporary = join5(lockPath, `.owner-${randomUUID2()}.tmp`);
25578
25742
  try {
25579
25743
  await writeFile3(temporary, `${JSON.stringify(owner)}
25580
25744
  `, {
@@ -25582,7 +25746,7 @@ async function writeOwner(lockPath, owner) {
25582
25746
  mode: 384,
25583
25747
  flag: "wx"
25584
25748
  });
25585
- await rename2(temporary, join4(lockPath, "owner.json"));
25749
+ await rename2(temporary, join5(lockPath, "owner.json"));
25586
25750
  } catch {
25587
25751
  await rm3(temporary, { force: true }).catch(() => void 0);
25588
25752
  throw new CliSafetyError("profile mutation lock metadata could not be published");
@@ -25618,11 +25782,11 @@ async function writeFence(path, value) {
25618
25782
  async function readLegacyFenceMaximum(lockDirectory) {
25619
25783
  let maximum = 0;
25620
25784
  try {
25621
- for (const entry of await readdir3(lockDirectory, { withFileTypes: true })) {
25785
+ for (const entry of await readdir4(lockDirectory, { withFileTypes: true })) {
25622
25786
  if (!entry.isFile() || !/^[a-z0-9]+(?:-[a-z0-9]+)*\.fence\.json$/u.test(entry.name)) {
25623
25787
  continue;
25624
25788
  }
25625
- maximum = Math.max(maximum, await readFence(join4(lockDirectory, entry.name)));
25789
+ maximum = Math.max(maximum, await readFence(join5(lockDirectory, entry.name)));
25626
25790
  }
25627
25791
  } catch (error) {
25628
25792
  if (errorCode(error) !== "ENOENT") {
@@ -25635,7 +25799,7 @@ async function latestHeartbeat(lockPath, owner) {
25635
25799
  let latest = owner.acquiredAt;
25636
25800
  const prefix = `heartbeat-${owner.ownerToken}-`;
25637
25801
  try {
25638
- for (const entry of await readdir3(lockPath, { withFileTypes: true })) {
25802
+ for (const entry of await readdir4(lockPath, { withFileTypes: true })) {
25639
25803
  if (!entry.isDirectory() || !entry.name.startsWith(prefix)) continue;
25640
25804
  const timestamp = Number(entry.name.slice(prefix.length));
25641
25805
  if (Number.isFinite(timestamp)) latest = Math.max(latest, timestamp);
@@ -25668,8 +25832,8 @@ async function acquireProfileLock(home, profile, options = {}) {
25668
25832
  const isProcessAlive = options.isProcessAlive ?? processAlive;
25669
25833
  const canonicalHome = await ensureCanonicalHome(home);
25670
25834
  const lockDirectory = await ensureContainedDirectory(canonicalHome, ".dsh-plugins", "locks");
25671
- const lockPath = join4(lockDirectory, "mutation.lock");
25672
- const fencePath = join4(lockDirectory, "mutation.fence.json");
25835
+ const lockPath = join5(lockDirectory, "mutation.lock");
25836
+ const fencePath = join5(lockDirectory, "mutation.fence.json");
25673
25837
  const startedAt = now();
25674
25838
  while (true) {
25675
25839
  const ownerToken = makeOwnerToken();
@@ -25677,7 +25841,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25677
25841
  throw new CliSafetyError("profile mutation owner token is invalid");
25678
25842
  }
25679
25843
  let fencingToken = 0;
25680
- const candidate = join4(lockDirectory, `.mutation.claim-${randomUUID2()}`);
25844
+ const candidate = join5(lockDirectory, `.mutation.claim-${randomUUID2()}`);
25681
25845
  const provisionalRecord = {
25682
25846
  version: 1,
25683
25847
  ownerToken,
@@ -25691,7 +25855,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25691
25855
  let claimed = false;
25692
25856
  try {
25693
25857
  await mkdir4(candidate, { mode: 448 });
25694
- await writeFile3(join4(candidate, "owner.json"), `${JSON.stringify(provisionalRecord)}
25858
+ await writeFile3(join5(candidate, "owner.json"), `${JSON.stringify(provisionalRecord)}
25695
25859
  `, {
25696
25860
  encoding: "utf8",
25697
25861
  mode: 384,
@@ -25708,7 +25872,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25708
25872
  } catch (error) {
25709
25873
  await rm3(candidate, { recursive: true, force: true }).catch(() => void 0);
25710
25874
  if (claimed) {
25711
- const failedPath = join4(lockDirectory, `.mutation.failed-${randomUUID2()}`);
25875
+ const failedPath = join5(lockDirectory, `.mutation.failed-${randomUUID2()}`);
25712
25876
  try {
25713
25877
  const current2 = await readOwner(lockPath);
25714
25878
  if (current2?.ownerToken === ownerToken) {
@@ -25727,7 +25891,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25727
25891
  const leaseExpired = now() - await latestHeartbeat(lockPath, current) > current.leaseMs;
25728
25892
  const localOwnerAlive = current.hostname === hostname && isProcessAlive(current.pid);
25729
25893
  if (leaseExpired && !localOwnerAlive) {
25730
- const stalePath = join4(lockDirectory, `.mutation.stale-${randomUUID2()}`);
25894
+ const stalePath = join5(lockDirectory, `.mutation.stale-${randomUUID2()}`);
25731
25895
  try {
25732
25896
  await rename2(lockPath, stalePath);
25733
25897
  await rm3(stalePath, { recursive: true, force: true });
@@ -25756,7 +25920,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25756
25920
  const heartbeat = async () => {
25757
25921
  await assertOwned();
25758
25922
  try {
25759
- await mkdir4(join4(lockPath, `heartbeat-${ownerToken}-${now()}`), { mode: 448 });
25923
+ await mkdir4(join5(lockPath, `heartbeat-${ownerToken}-${now()}`), { mode: 448 });
25760
25924
  } catch (error) {
25761
25925
  if (errorCode(error) !== "EEXIST") {
25762
25926
  lost = true;
@@ -25778,7 +25942,7 @@ async function acquireProfileLock(home, profile, options = {}) {
25778
25942
  if (current === null || current.ownerToken !== ownerToken || current.fencingToken !== fencingToken || current.profile !== profile) {
25779
25943
  return;
25780
25944
  }
25781
- const releasePath = join4(lockDirectory, `.mutation.release-${ownerToken}`);
25945
+ const releasePath = join5(lockDirectory, `.mutation.release-${ownerToken}`);
25782
25946
  try {
25783
25947
  await rename2(lockPath, releasePath);
25784
25948
  const detached = await readOwner(releasePath);
@@ -25807,17 +25971,17 @@ async function acquireProfileLock(home, profile, options = {}) {
25807
25971
 
25808
25972
  // src/dsh/profileTransaction.ts
25809
25973
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
25810
- import { join as join5 } from "node:path";
25811
- import { cp, lstat as lstat6, readFile as readFile6, readdir as readdir4, rename as rename3, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
25974
+ import { join as join6 } from "node:path";
25975
+ import { cp, lstat as lstat6, readFile as readFile6, readdir as readdir5, rename as rename3, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
25812
25976
  async function profileFingerprint(root) {
25813
25977
  const hash = createHash2("sha512");
25814
25978
  let files = 0;
25815
25979
  let bytes = 0;
25816
25980
  const visit = async (directory, prefix) => {
25817
- const entries = await readdir4(directory, { withFileTypes: true });
25981
+ const entries = await readdir5(directory, { withFileTypes: true });
25818
25982
  entries.sort((left, right) => Buffer.from(left.name).compare(Buffer.from(right.name)));
25819
25983
  for (const entry of entries) {
25820
- const path = join5(directory, entry.name);
25984
+ const path = join6(directory, entry.name);
25821
25985
  const relativePath = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
25822
25986
  const info = await lstat6(path);
25823
25987
  if (info.isSymbolicLink()) throw new CliSafetyError("profile backup contains a symlink");
@@ -25855,21 +26019,21 @@ async function activeExists(active) {
25855
26019
  async function recoverTransactions(profiles, active, profile, committedFencingToken, assertLockOwned) {
25856
26020
  const prefix = `.dsh-plugins-transaction-${profile}-`;
25857
26021
  const journals = [];
25858
- for (const entry of await readdir4(profiles, { withFileTypes: true })) {
26022
+ for (const entry of await readdir5(profiles, { withFileTypes: true })) {
25859
26023
  if (!entry.isFile() || !entry.name.startsWith(prefix) || !entry.name.endsWith(".json")) continue;
25860
26024
  try {
25861
- const value = JSON.parse(await readFile6(join5(profiles, entry.name), "utf8"));
26025
+ const value = JSON.parse(await readFile6(join6(profiles, entry.name), "utf8"));
25862
26026
  if (value.version !== 1 || value.profile !== profile || !Number.isSafeInteger(value.fencingToken) || typeof value.existed !== "boolean" || !value.backupName.startsWith(`.dsh-plugins-backup-${profile}-`) || value.backupName.includes("/") || value.backupName.includes("\\")) {
25863
26027
  throw new Error("invalid-journal");
25864
26028
  }
25865
- journals.push({ path: join5(profiles, entry.name), value });
26029
+ journals.push({ path: join6(profiles, entry.name), value });
25866
26030
  } catch {
25867
26031
  throw new CliSafetyError("profile transaction journal requires manual recovery");
25868
26032
  }
25869
26033
  }
25870
26034
  journals.sort((left, right) => right.value.fencingToken - left.value.fencingToken);
25871
26035
  for (const journal of journals) {
25872
- const backup = join5(profiles, journal.value.backupName);
26036
+ const backup = join6(profiles, journal.value.backupName);
25873
26037
  if (journal.value.fencingToken <= committedFencingToken) {
25874
26038
  await rm4(backup, { recursive: true, force: true }).catch(() => void 0);
25875
26039
  await rm4(journal.path, { force: true }).catch(() => void 0);
@@ -25894,7 +26058,7 @@ async function beginProfileTransaction(home, profile, options = {}) {
25894
26058
  await assertLockOwned();
25895
26059
  const canonicalHome = await ensureCanonicalHome(home);
25896
26060
  const profiles = await ensureContainedDirectory(canonicalHome, "profiles");
25897
- const active = join5(profiles, profile);
26061
+ const active = join6(profiles, profile);
25898
26062
  await recoverTransactions(
25899
26063
  profiles,
25900
26064
  active,
@@ -25904,8 +26068,8 @@ async function beginProfileTransaction(home, profile, options = {}) {
25904
26068
  );
25905
26069
  const suffix = randomUUID3();
25906
26070
  const backupName = `.dsh-plugins-backup-${profile}-${suffix}`;
25907
- const backup = join5(profiles, backupName);
25908
- const journal = join5(profiles, `.dsh-plugins-transaction-${profile}-${suffix}.json`);
26071
+ const backup = join6(profiles, backupName);
26072
+ const journal = join6(profiles, `.dsh-plugins-transaction-${profile}-${suffix}.json`);
25909
26073
  const journalTemporary = `${journal}.tmp`;
25910
26074
  const existed = await activeExists(active);
25911
26075
  let backupFingerprint = null;
@@ -25983,9 +26147,9 @@ async function recoverProfileTransaction(home, reference, assertLockOwned) {
25983
26147
  }
25984
26148
  const canonicalHome = await ensureCanonicalHome(home);
25985
26149
  const profiles = await ensureContainedDirectory(canonicalHome, "profiles");
25986
- const journal = join5(canonicalHome, reference.journalRelativePath);
25987
- const backup = join5(canonicalHome, reference.backupRelativePath);
25988
- const active = join5(profiles, reference.profile);
26150
+ const journal = join6(canonicalHome, reference.journalRelativePath);
26151
+ const backup = join6(canonicalHome, reference.backupRelativePath);
26152
+ const active = join6(profiles, reference.profile);
25989
26153
  const displaced = `${backup}.displaced`;
25990
26154
  let value;
25991
26155
  try {
@@ -26037,8 +26201,8 @@ async function finalizeProfileTransactionRecovery(home, reference, assertLockOwn
26037
26201
  assertSafeProfileName(reference.profile);
26038
26202
  const canonicalHome = await ensureCanonicalHome(home);
26039
26203
  await ensureContainedDirectory(canonicalHome, "profiles");
26040
- const journal = join5(canonicalHome, reference.journalRelativePath);
26041
- const backup = join5(canonicalHome, reference.backupRelativePath);
26204
+ const journal = join6(canonicalHome, reference.journalRelativePath);
26205
+ const backup = join6(canonicalHome, reference.backupRelativePath);
26042
26206
  await assertLockOwned();
26043
26207
  await rm4(backup, { recursive: true, force: true });
26044
26208
  await rm4(`${backup}.displaced`, { recursive: true, force: true });
@@ -26064,7 +26228,7 @@ import {
26064
26228
  rename as rename4,
26065
26229
  rm as rm5
26066
26230
  } from "node:fs/promises";
26067
- import { join as join6 } from "node:path";
26231
+ import { join as join7 } from "node:path";
26068
26232
  var MutationRecoveryJournalDurabilityAmbiguousError = class extends CliSafetyError {
26069
26233
  possiblyPublished = true;
26070
26234
  constructor(operation) {
@@ -26131,8 +26295,8 @@ async function createPaths(home) {
26131
26295
  const recoveryRoot = await ensureContainedDirectory(canonicalHome, ".dsh-plugins", "recovery");
26132
26296
  return {
26133
26297
  recoveryRoot,
26134
- active: join6(recoveryRoot, "active"),
26135
- marker: join6(recoveryRoot, "active", "marker.json")
26298
+ active: join7(recoveryRoot, "active"),
26299
+ marker: join7(recoveryRoot, "active", "marker.json")
26136
26300
  };
26137
26301
  }
26138
26302
  async function existingPaths(home) {
@@ -26143,9 +26307,9 @@ async function existingPaths(home) {
26143
26307
  if (error.code === "ENOENT") return null;
26144
26308
  throw new CliSafetyError("mutation recovery journal could not be inspected safely");
26145
26309
  }
26146
- const dshPlugins = join6(canonicalHome, ".dsh-plugins");
26147
- const recoveryRoot = join6(dshPlugins, "recovery");
26148
- const active = join6(recoveryRoot, "active");
26310
+ const dshPlugins = join7(canonicalHome, ".dsh-plugins");
26311
+ const recoveryRoot = join7(dshPlugins, "recovery");
26312
+ const active = join7(recoveryRoot, "active");
26149
26313
  try {
26150
26314
  for (const path of [dshPlugins, recoveryRoot, active]) {
26151
26315
  const info = await lstat7(path);
@@ -26158,7 +26322,7 @@ async function existingPaths(home) {
26158
26322
  if (error instanceof CliSafetyError) throw error;
26159
26323
  throw new CliSafetyError("mutation recovery journal could not be inspected safely");
26160
26324
  }
26161
- return { recoveryRoot, active, marker: join6(active, "marker.json") };
26325
+ return { recoveryRoot, active, marker: join7(active, "marker.json") };
26162
26326
  }
26163
26327
  function safeRelative(value, prefix) {
26164
26328
  return typeof value === "string" && value.startsWith(prefix) && !value.includes("\\") && !value.includes("..") && !value.startsWith("/");
@@ -26198,7 +26362,7 @@ function parseJournal(value) {
26198
26362
  return record;
26199
26363
  }
26200
26364
  async function writeMarker(paths, value, dependencies = {}) {
26201
- const temporary = join6(paths.active, `.marker-${randomUUID4()}.tmp`);
26365
+ const temporary = join7(paths.active, `.marker-${randomUUID4()}.tmp`);
26202
26366
  const handle = await open2(temporary, "wx", 384);
26203
26367
  try {
26204
26368
  await handle.writeFile(`${JSON.stringify(value)}
@@ -26270,7 +26434,7 @@ async function removeCompletedMutationRecoveryJournal(home, expected, dependenci
26270
26434
  if (current === null || current.revision !== expected.revision || current.ownerToken !== expected.ownerToken || current.fencingToken !== expected.fencingToken || current.phase !== "completed") {
26271
26435
  throw new CliSafetyError("durable recovery intent ownership changed; recovery required");
26272
26436
  }
26273
- const completed = join6(paths.recoveryRoot, `.completed-${randomUUID4()}`);
26437
+ const completed = join7(paths.recoveryRoot, `.completed-${randomUUID4()}`);
26274
26438
  await dependencies.beforeRename?.();
26275
26439
  await rename4(paths.active, completed);
26276
26440
  try {
@@ -26707,14 +26871,14 @@ import {
26707
26871
  mkdtemp as mkdtemp2,
26708
26872
  open as open3,
26709
26873
  readlink,
26710
- readdir as readdir5,
26874
+ readdir as readdir6,
26711
26875
  realpath as realpath8,
26712
26876
  rename as rename5,
26713
26877
  rm as rm6,
26714
26878
  writeFile as writeFile5
26715
26879
  } from "node:fs/promises";
26716
26880
  import { devNull } from "node:os";
26717
- import { basename, dirname as dirname3, join as join7, relative as relative6, resolve as resolve7, sep as sep6 } from "node:path";
26881
+ import { basename, dirname as dirname3, join as join8, relative as relative6, resolve as resolve7, sep as sep6 } from "node:path";
26718
26882
  import { pipeline } from "node:stream/promises";
26719
26883
  import { createGzip } from "node:zlib";
26720
26884
  var PACKAGE_NAME2 = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u;
@@ -27101,10 +27265,10 @@ async function createDeterministicSourceArchive(sourceRoot, pluginRoot, destinat
27101
27265
  try {
27102
27266
  await emit(tarHeader("package/", 0, 493, "directory"));
27103
27267
  const visit = async (directory, relativeDirectory) => {
27104
- const entries = await readdir5(directory, { withFileTypes: true });
27268
+ const entries = await readdir6(directory, { withFileTypes: true });
27105
27269
  entries.sort((left, right) => Buffer.from(left.name).compare(Buffer.from(right.name)));
27106
27270
  for (const entry of entries) {
27107
- const candidate = join7(directory, entry.name);
27271
+ const candidate = join8(directory, entry.name);
27108
27272
  const relativePath = relativeDirectory === "" ? entry.name : `${relativeDirectory}/${entry.name}`;
27109
27273
  const archivePath = `package/${relativePath}`;
27110
27274
  const info = await lstat8(candidate);
@@ -27160,8 +27324,8 @@ async function createDeterministicSourceArchive(sourceRoot, pluginRoot, destinat
27160
27324
  }
27161
27325
  async function createArtifactLease(cached, home, budgets) {
27162
27326
  const leasesRoot = await ensureContainedDirectory(home, ".dsh-plugins", "artifact-leases");
27163
- const leaseRoot = await mkdtemp2(join7(leasesRoot, ".lease-"));
27164
- const destination = join7(leaseRoot, "artifact.tgz");
27327
+ const leaseRoot = await mkdtemp2(join8(leasesRoot, ".lease-"));
27328
+ const destination = join8(leaseRoot, "artifact.tgz");
27165
27329
  let heldFd;
27166
27330
  try {
27167
27331
  const cache = cached[VERIFIED_CACHE];
@@ -27298,10 +27462,10 @@ async function inspectSourceTree(root, budgets) {
27298
27462
  if (before.isSymbolicLink() || !before.isDirectory()) {
27299
27463
  throw new CliSafetyError("source tree changed during verification");
27300
27464
  }
27301
- const entries = await readdir5(directory, { withFileTypes: true });
27465
+ const entries = await readdir6(directory, { withFileTypes: true });
27302
27466
  entries.sort((left, right) => Buffer.from(left.name).compare(Buffer.from(right.name)));
27303
27467
  for (const entry of entries) {
27304
- const candidate = join7(directory, entry.name);
27468
+ const candidate = join8(directory, entry.name);
27305
27469
  const relativePath = relative6(root, candidate).split(sep6).join("/");
27306
27470
  const candidateDepth = relativePath.split("/").length;
27307
27471
  assertTreeBudget(bytes, files, candidateDepth, budgets);
@@ -27359,9 +27523,9 @@ async function measureCheckout(root, budgets) {
27359
27523
  let files = 0;
27360
27524
  const visit = async (directory, depth) => {
27361
27525
  assertTreeBudget(bytes, files, depth, budgets);
27362
- const entries = await readdir5(directory, { withFileTypes: true });
27526
+ const entries = await readdir6(directory, { withFileTypes: true });
27363
27527
  for (const entry of entries) {
27364
- const candidate = join7(directory, entry.name);
27528
+ const candidate = join8(directory, entry.name);
27365
27529
  const candidateDepth = depth + 1;
27366
27530
  const info = await lstat8(candidate);
27367
27531
  if (info.isDirectory() && !info.isSymbolicLink()) {
@@ -27532,7 +27696,7 @@ async function stageNpm(entry, temporary, fetchImpl, budgets, signal) {
27532
27696
  throw new CliSafetyError("checksum verification failed");
27533
27697
  }
27534
27698
  const artifact = "artifact.tgz";
27535
- await writeFile5(join7(temporary, artifact), bytes, { mode: 384, flag: "wx" });
27699
+ await writeFile5(join8(temporary, artifact), bytes, { mode: 384, flag: "wx" });
27536
27700
  return {
27537
27701
  packageName: entry.package.name,
27538
27702
  installRelativePath: artifact,
@@ -27545,7 +27709,7 @@ async function stageSource(entry, temporary, runProcess, budgets, signal) {
27545
27709
  }
27546
27710
  if (!SHA402.test(entry.source.commit)) throw new CliSafetyError("source commit pin is invalid");
27547
27711
  const subpath = safeSourceSubpath(entry.source.subpath);
27548
- const source = join7(temporary, "source");
27712
+ const source = join8(temporary, "source");
27549
27713
  await mkdir6(source, { mode: 448 });
27550
27714
  await runGit2(runProcess, { args: ["init", "--quiet"], cwd: source }, budgets, signal);
27551
27715
  await runGit2(
@@ -27592,7 +27756,7 @@ async function stageSource(entry, temporary, runProcess, budgets, signal) {
27592
27756
  if (checkedCommit.toLowerCase() !== entry.source.commit.toLowerCase()) {
27593
27757
  throw new CliSafetyError("source checkout does not match the commit pin");
27594
27758
  }
27595
- await rm6(join7(source, ".git"), { recursive: true, force: true });
27759
+ await rm6(join8(source, ".git"), { recursive: true, force: true });
27596
27760
  const tree = await inspectSourceTree(source, budgets);
27597
27761
  const pluginRoot = resolve7(source, subpath);
27598
27762
  const expectedRelativePath = subpath === "." ? "source" : `source/${subpath}`;
@@ -27615,7 +27779,7 @@ async function stageSource(entry, temporary, runProcess, budgets, signal) {
27615
27779
  let packageName;
27616
27780
  try {
27617
27781
  packageName = JSON.parse((await readRegularFile(
27618
- join7(canonicalPluginRoot, "package.json"),
27782
+ join8(canonicalPluginRoot, "package.json"),
27619
27783
  budgets.metadataBytes,
27620
27784
  "source package manifest is unsafe"
27621
27785
  )).toString("utf8")).name;
@@ -27649,7 +27813,7 @@ async function readCached(finalRoot, id, hash, entry, budgets) {
27649
27813
  throw new CliSafetyError("staging cache key is invalid");
27650
27814
  }
27651
27815
  const metadata = JSON.parse((await readRegularFile(
27652
- join7(canonicalRoot2, "metadata.json"),
27816
+ join8(canonicalRoot2, "metadata.json"),
27653
27817
  budgets.metadataBytes,
27654
27818
  "staging cache metadata is unsafe"
27655
27819
  )).toString("utf8"));
@@ -27690,7 +27854,7 @@ async function readCached(finalRoot, id, hash, entry, budgets) {
27690
27854
  if (metadata.artifact.kind !== "source" || metadata.artifact.commit !== entry.source.commit.toLowerCase() || metadata.installRelativePath !== expectedRelativePath) {
27691
27855
  throw new CliSafetyError("staging cache metadata is invalid");
27692
27856
  }
27693
- const sourceRoot = join7(canonicalRoot2, "source");
27857
+ const sourceRoot = join8(canonicalRoot2, "source");
27694
27858
  const tree = await inspectSourceTree(sourceRoot, budgets);
27695
27859
  if (tree.digest !== metadata.artifact.treeSha512 || tree.bytes !== metadata.artifact.bytes || tree.files !== metadata.artifact.files) {
27696
27860
  throw new CliSafetyError("staging cache tree verification failed");
@@ -27700,7 +27864,7 @@ async function readCached(finalRoot, id, hash, entry, budgets) {
27700
27864
  throw new CliSafetyError("staging cache target is unsafe");
27701
27865
  }
27702
27866
  const packageInfo = JSON.parse((await readRegularFile(
27703
- join7(canonicalTarget, "package.json"),
27867
+ join8(canonicalTarget, "package.json"),
27704
27868
  budgets.metadataBytes,
27705
27869
  "staging cache package manifest is unsafe"
27706
27870
  )).toString("utf8"));
@@ -27736,7 +27900,7 @@ async function readCached(finalRoot, id, hash, entry, budgets) {
27736
27900
  }
27737
27901
  }
27738
27902
  async function quarantineCache(finalRoot, idRoot, hash) {
27739
- const quarantine = join7(idRoot, `.quarantine-${hash}-${randomUUID5()}`);
27903
+ const quarantine = join8(idRoot, `.quarantine-${hash}-${randomUUID5()}`);
27740
27904
  try {
27741
27905
  await rename5(finalRoot, quarantine);
27742
27906
  } catch (error) {
@@ -27752,7 +27916,7 @@ async function stageCatalogEntry(entry, options) {
27752
27916
  const budgets = stageBudgets(options.budgets);
27753
27917
  const home = await ensureCanonicalHome(options.dshHome);
27754
27918
  const idRoot = await ensureContainedDirectory(home, ".dsh-plugins", "cache", id);
27755
- const finalRoot = join7(idRoot, hash);
27919
+ const finalRoot = join8(idRoot, hash);
27756
27920
  try {
27757
27921
  await lstat8(finalRoot);
27758
27922
  try {
@@ -27774,7 +27938,7 @@ async function stageCatalogEntry(entry, options) {
27774
27938
  if (options.offline === true) {
27775
27939
  throw new CliSafetyError("pinned artifact is not cached and offline mode forbids retrieval");
27776
27940
  }
27777
- const temporary = await mkdtemp2(join7(idRoot, ".stage-"));
27941
+ const temporary = await mkdtemp2(join8(idRoot, ".stage-"));
27778
27942
  try {
27779
27943
  const staged = entry.package.ecosystem === "npm" ? await stageNpm(
27780
27944
  entry,
@@ -27795,7 +27959,7 @@ async function stageCatalogEntry(entry, options) {
27795
27959
  descriptorHash: hash,
27796
27960
  ...staged
27797
27961
  };
27798
- await writeFile5(join7(temporary, "metadata.json"), `${JSON.stringify(metadata)}
27962
+ await writeFile5(join8(temporary, "metadata.json"), `${JSON.stringify(metadata)}
27799
27963
  `, {
27800
27964
  encoding: "utf8",
27801
27965
  mode: 384,
@@ -27823,7 +27987,7 @@ async function stageCatalogEntry(entry, options) {
27823
27987
  );
27824
27988
  } catch (error) {
27825
27989
  if (error instanceof GitProcessUnreapedError) {
27826
- const quarantine = join7(idRoot, `.unreaped-${hash}-${randomUUID5()}`);
27990
+ const quarantine = join8(idRoot, `.unreaped-${hash}-${randomUUID5()}`);
27827
27991
  await rename5(temporary, quarantine).catch(() => void 0);
27828
27992
  throw error;
27829
27993
  }
@@ -29371,8 +29535,8 @@ async function runCli(argv, dependencies = defaultDependencies3) {
29371
29535
  addCatalogOptions(catalog.command("validate").description("validate catalog YAML and semantics")).action(async (options) => {
29372
29536
  result = await validateCatalogCommand(dependencies, selection(options), options.json === true);
29373
29537
  });
29374
- catalog.command("docs-check").description("check required public catalog documentation").argument("[root]", "public repository root", ".").action(async (root) => {
29375
- result = await docsCheckCommand(dependencies, root);
29538
+ catalog.command("docs-check").description("check required public catalog documentation").argument("[root]", "public repository root", ".").option("--skip-count", "skip the exact README entry-count assertion (pull-request mode)").action(async (root, options) => {
29539
+ result = await docsCheckCommand(dependencies, root, options);
29376
29540
  });
29377
29541
  catalog.command("github-forms-check").description("check structured public GitHub issue forms").argument("[root]", "public repository root", ".").action(async (root) => {
29378
29542
  result = await githubFormsCheckCommand(dependencies, root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-dsh-plugins",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Unofficial catalog and safe installer for DeepSeek Harness plugins",
5
5
  "type": "module",
6
6
  "license": "MIT",