dsh-plugin-update 0.5.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/store.d.ts CHANGED
@@ -131,6 +131,48 @@ export declare function createBatchDiskPorts(homeDir: string, profileDir: string
131
131
  writeBatch: (session: BatchSession) => Promise<void>;
132
132
  clearBatch: () => Promise<void>;
133
133
  };
134
+ /** 批量属主隔离与知识/偏好落盘(#59 D1:知识、一轮、属主各回各家)。 */
135
+ export declare const INVENTORY_FILE = "inventory.json";
136
+ export declare const PREFS_FILE = "prefs.json";
137
+ export declare const INVENTORY_FILE_MAX_BYTES: number;
138
+ export declare const PREFS_FILE_MAX_BYTES: number;
139
+ export declare function sanitizeBatchOwner(owner: unknown): string | null;
140
+ export interface BatchOwnerPaths {
141
+ directory: string;
142
+ batchFile: string;
143
+ inventoryFile: string;
144
+ prefsFile: string;
145
+ }
146
+ export declare function batchPathsForOwner(homeDir: string, profileDir: string, owner: unknown): BatchOwnerPaths | null;
147
+ export interface BatchInventoryEntry {
148
+ lastCheckedAt: number;
149
+ installedVersion: string | null;
150
+ latestVersion: string | null;
151
+ canInstall: boolean | null;
152
+ error: string | null;
153
+ }
154
+ export interface BatchInventory {
155
+ version: 1;
156
+ updatedAt: number;
157
+ entries: Record<string, BatchInventoryEntry>;
158
+ }
159
+ export declare function emptyBatchInventory(nowMs?: number): BatchInventory;
160
+ export declare function normalizeBatchInventory(raw: unknown, nowMs?: number): BatchInventory;
161
+ export interface BatchPrefs {
162
+ checkOnOpen?: boolean;
163
+ }
164
+ export declare function normalizeBatchPrefs(raw: unknown): BatchPrefs;
165
+ export interface BatchOwnerPorts {
166
+ paths: BatchOwnerPaths | null;
167
+ readBatch: () => Promise<BatchSession>;
168
+ writeBatch: (session: BatchSession) => Promise<void>;
169
+ clearBatch: () => Promise<void>;
170
+ readInventory: () => Promise<BatchInventory>;
171
+ writeInventory: (inventory: BatchInventory) => Promise<void>;
172
+ readPrefs: () => Promise<BatchPrefs>;
173
+ writePrefs: (prefs: BatchPrefs) => Promise<void>;
174
+ }
175
+ export declare function createBatchOwnerPorts(homeDir: string, profileDir: string, owner: unknown, deps?: BatchPortsDeps): BatchOwnerPorts;
134
176
  /** 按插件 + 版本持久化的跳过记录(#16,与三冻结落盘名并存的第四文件,不碰旧树)。
135
177
  *
136
178
  * 键:跳过只按「插件标识 + 版本」记——目录已按插件标识隔离(pathsForUpdate),
package/dist/store.js CHANGED
@@ -328,6 +328,135 @@ function createBatchDiskPorts(homeDir, profileDir, deps = {}) {
328
328
  }
329
329
  return { paths, readBatch, writeBatch, clearBatch };
330
330
  }
331
+ const INVENTORY_FILE = "inventory.json";
332
+ const PREFS_FILE = "prefs.json";
333
+ const INVENTORY_FILE_MAX_BYTES = 1024 * 1024;
334
+ const PREFS_FILE_MAX_BYTES = 64 * 1024;
335
+ function sanitizeBatchOwner(owner) {
336
+ if (typeof owner !== "string" || !owner) return null;
337
+ if (owner.length < 1 || owner.length > 64) return null;
338
+ if (!/^[A-Za-z0-9_-]+$/.test(owner)) return null;
339
+ return owner;
340
+ }
341
+ function batchPathsForOwner(homeDir, profileDir, owner) {
342
+ const clean = sanitizeBatchOwner(owner);
343
+ if (!clean) return null;
344
+ const queue = queuePathsForUpdate(homeDir, profileDir);
345
+ if (!queue) return null;
346
+ const directory = join(queue.directory, clean);
347
+ return { directory, batchFile: join(directory, BATCH_FILE), inventoryFile: join(directory, INVENTORY_FILE), prefsFile: join(directory, PREFS_FILE) };
348
+ }
349
+ function emptyBatchInventory(nowMs) {
350
+ const at = typeof nowMs === "number" && Number.isFinite(nowMs) && nowMs >= 0 ? nowMs : 0;
351
+ return { version: 1, updatedAt: at, entries: {} };
352
+ }
353
+ function asMillisOrZero(value) {
354
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
355
+ }
356
+ function asNullableText(value) {
357
+ return typeof value === "string" && value ? value : null;
358
+ }
359
+ function normalizeBatchInventory(raw, nowMs) {
360
+ const fallback = emptyBatchInventory(nowMs);
361
+ try {
362
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fallback;
363
+ const input = raw;
364
+ if (input["version"] !== 1) return fallback;
365
+ const entries = {};
366
+ const rawEntries = input["entries"];
367
+ if (rawEntries && typeof rawEntries === "object" && !Array.isArray(rawEntries)) {
368
+ for (const [key, item] of Object.entries(rawEntries)) {
369
+ if (!key || typeof item !== "object" || !item || Array.isArray(item)) continue;
370
+ const e = item;
371
+ const canInstallRaw = e["canInstall"];
372
+ entries[key] = {
373
+ lastCheckedAt: asMillisOrZero(e["lastCheckedAt"]),
374
+ installedVersion: asNullableText(e["installedVersion"]),
375
+ latestVersion: asNullableText(e["latestVersion"]),
376
+ canInstall: typeof canInstallRaw === "boolean" ? canInstallRaw : null,
377
+ error: asNullableText(e["error"])
378
+ };
379
+ }
380
+ }
381
+ return { version: 1, updatedAt: asMillisOrZero(input["updatedAt"]), entries };
382
+ } catch {
383
+ return fallback;
384
+ }
385
+ }
386
+ function normalizeBatchPrefs(raw) {
387
+ try {
388
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
389
+ const v = raw["checkOnOpen"];
390
+ if (typeof v === "boolean") return { checkOnOpen: v };
391
+ return {};
392
+ } catch {
393
+ return {};
394
+ }
395
+ }
396
+ async function readJsonFile(filename, maxBytes, readText, statFile) {
397
+ try {
398
+ if ((await statFile(filename)).size > maxBytes) return null;
399
+ return JSON.parse(await readText(filename, "utf8"));
400
+ } catch (error) {
401
+ if (error && error.code === "ENOENT") return null;
402
+ return null;
403
+ }
404
+ }
405
+ async function writeJsonFileAtomic(filename, value, directory) {
406
+ await mkdir(directory, { recursive: true, mode: 448 });
407
+ const temporary = filename + "." + randomUUID() + ".tmp";
408
+ try {
409
+ await writeFile(temporary, JSON.stringify(value, null, 2) + "\n", { mode: 384, flag: "wx" });
410
+ await rename(temporary, filename);
411
+ } catch {
412
+ throw batchFail();
413
+ } finally {
414
+ await unlink(temporary).catch(() => {
415
+ });
416
+ }
417
+ }
418
+ function createBatchOwnerPorts(homeDir, profileDir, owner, deps = {}) {
419
+ const paths = batchPathsForOwner(homeDir, profileDir, owner);
420
+ const readText = deps.readFileImpl ?? ((filename, encoding) => readFile(filename, encoding));
421
+ const statFile = deps.statImpl ?? ((filename) => stat(filename));
422
+ async function readBatch() {
423
+ if (!paths) return emptyBatchSession();
424
+ const raw = await readJsonFile(paths.batchFile, BATCH_FILE_MAX_BYTES, readText, statFile);
425
+ if (raw === null) return emptyBatchSession();
426
+ return normalizeBatchSession(raw);
427
+ }
428
+ async function writeBatch(session) {
429
+ if (!paths) throw batchFail();
430
+ await writeJsonFileAtomic(paths.batchFile, normalizeBatchSession(session), paths.directory);
431
+ }
432
+ async function clearBatch() {
433
+ if (!paths) return;
434
+ await writeBatch(emptyBatchSession());
435
+ }
436
+ async function readInventory() {
437
+ if (!paths) return emptyBatchInventory();
438
+ const raw = await readJsonFile(paths.inventoryFile, INVENTORY_FILE_MAX_BYTES, readText, statFile);
439
+ if (raw === null) return emptyBatchInventory();
440
+ return normalizeBatchInventory(raw);
441
+ }
442
+ async function writeInventory(inventory) {
443
+ if (!paths) throw batchFail();
444
+ const normalized = normalizeBatchInventory(inventory, inventory.updatedAt);
445
+ normalized.updatedAt = asMillisOrZero(inventory.updatedAt);
446
+ await writeJsonFileAtomic(paths.inventoryFile, normalized, paths.directory);
447
+ }
448
+ async function readPrefs() {
449
+ if (!paths) return {};
450
+ const raw = await readJsonFile(paths.prefsFile, PREFS_FILE_MAX_BYTES, readText, statFile);
451
+ if (raw === null) return {};
452
+ return normalizeBatchPrefs(raw);
453
+ }
454
+ async function writePrefs(prefs) {
455
+ if (!paths) throw batchFail();
456
+ await writeJsonFileAtomic(paths.prefsFile, normalizeBatchPrefs(prefs), paths.directory);
457
+ }
458
+ return { paths, readBatch, writeBatch, clearBatch, readInventory, writeInventory, readPrefs, writePrefs };
459
+ }
331
460
  const MAX_SKIPPED_ENTRIES = 50;
332
461
  function skipKey(pluginId, version) {
333
462
  return `${String(pluginId)}@${String(version)}`;
@@ -693,25 +822,35 @@ const __executorInternals = { INSTALL_STDIO, TERMINATION_GRACE_MS, CLI_PACKAGE_N
693
822
  export {
694
823
  BATCH_FILE,
695
824
  BATCH_FILE_MAX_BYTES,
825
+ INVENTORY_FILE,
826
+ INVENTORY_FILE_MAX_BYTES,
696
827
  MAX_SKIPPED_ENTRIES,
828
+ PREFS_FILE,
829
+ PREFS_FILE_MAX_BYTES,
697
830
  QUEUE_DIR_SEGMENT,
698
831
  QUEUE_FILE,
699
832
  QUEUE_LOCK_FILE,
700
833
  QUEUE_LOCK_STALE_MS,
701
834
  __executorInternals,
702
835
  addSkipped,
836
+ batchPathsForOwner,
703
837
  batchPathsForUpdate,
704
838
  clearSkipped,
705
839
  createBatchDiskPorts,
840
+ createBatchOwnerPorts,
706
841
  createSkipDiskPorts,
707
842
  createUpdateDiskPorts,
708
843
  createUpdateExecutor,
709
844
  createUpdateQueuePorts,
845
+ emptyBatchInventory,
710
846
  isVersionSkipped,
711
847
  legacyPathsForUpdate,
848
+ normalizeBatchInventory,
849
+ normalizeBatchPrefs,
712
850
  normalizeSkipped,
713
851
  pathsForUpdate,
714
852
  queuePathsForUpdate,
715
853
  resolveCliEntry,
854
+ sanitizeBatchOwner,
716
855
  skipKey
717
856
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-update",
3
- "version": "0.5.8",
3
+ "version": "0.7.0",
4
4
  "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
5
  "license": "MIT",
6
6
  "author": "王辰浩",