@microwiseai/snapshot 0.8.0 → 0.9.1

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 (116) hide show
  1. package/dist/build-info.json +1 -0
  2. package/dist/commands/access.d.ts +25 -9
  3. package/dist/commands/access.d.ts.map +1 -1
  4. package/dist/commands/access.js +101 -47
  5. package/dist/commands/access.js.map +1 -1
  6. package/dist/commands/config.d.ts +3 -0
  7. package/dist/commands/config.d.ts.map +1 -0
  8. package/dist/commands/config.js +37 -0
  9. package/dist/commands/config.js.map +1 -0
  10. package/dist/commands/install.d.ts +54 -0
  11. package/dist/commands/install.d.ts.map +1 -1
  12. package/dist/commands/install.js +494 -145
  13. package/dist/commands/install.js.map +1 -1
  14. package/dist/commands/publish.d.ts +10 -0
  15. package/dist/commands/publish.d.ts.map +1 -1
  16. package/dist/commands/publish.js +94 -12
  17. package/dist/commands/publish.js.map +1 -1
  18. package/dist/index.js +45 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/access-index.d.ts +81 -0
  21. package/dist/lib/access-index.d.ts.map +1 -0
  22. package/dist/lib/access-index.js +175 -0
  23. package/dist/lib/access-index.js.map +1 -0
  24. package/dist/lib/access-sync.d.ts +66 -17
  25. package/dist/lib/access-sync.d.ts.map +1 -1
  26. package/dist/lib/access-sync.js +391 -217
  27. package/dist/lib/access-sync.js.map +1 -1
  28. package/dist/lib/extends-access.d.ts +34 -0
  29. package/dist/lib/extends-access.d.ts.map +1 -0
  30. package/dist/lib/extends-access.js +82 -0
  31. package/dist/lib/extends-access.js.map +1 -0
  32. package/dist/lib/gitlab-publisher.d.ts +70 -0
  33. package/dist/lib/gitlab-publisher.d.ts.map +1 -0
  34. package/dist/lib/gitlab-publisher.js +165 -0
  35. package/dist/lib/gitlab-publisher.js.map +1 -0
  36. package/dist/lib/gitlab.d.ts +4 -0
  37. package/dist/lib/gitlab.d.ts.map +1 -1
  38. package/dist/lib/gitlab.js +1 -1
  39. package/dist/lib/gitlab.js.map +1 -1
  40. package/dist/lib/grant.d.ts +157 -0
  41. package/dist/lib/grant.d.ts.map +1 -0
  42. package/dist/lib/grant.js +352 -0
  43. package/dist/lib/grant.js.map +1 -0
  44. package/dist/lib/install-verify.d.ts +16 -2
  45. package/dist/lib/install-verify.d.ts.map +1 -1
  46. package/dist/lib/install-verify.js +24 -3
  47. package/dist/lib/install-verify.js.map +1 -1
  48. package/dist/lib/ledger.d.ts +13 -0
  49. package/dist/lib/ledger.d.ts.map +1 -1
  50. package/dist/lib/ledger.js.map +1 -1
  51. package/dist/lib/prerequisites.d.ts.map +1 -1
  52. package/dist/lib/prerequisites.js +4 -1
  53. package/dist/lib/prerequisites.js.map +1 -1
  54. package/dist/lib/proxy-auth-globalconfig.d.ts +55 -0
  55. package/dist/lib/proxy-auth-globalconfig.d.ts.map +1 -0
  56. package/dist/lib/proxy-auth-globalconfig.js +110 -0
  57. package/dist/lib/proxy-auth-globalconfig.js.map +1 -0
  58. package/dist/lib/proxy-userconfig.d.ts +45 -0
  59. package/dist/lib/proxy-userconfig.d.ts.map +1 -0
  60. package/dist/lib/proxy-userconfig.js +94 -0
  61. package/dist/lib/proxy-userconfig.js.map +1 -0
  62. package/dist/lib/semver-range.d.ts +32 -0
  63. package/dist/lib/semver-range.d.ts.map +1 -0
  64. package/dist/lib/semver-range.js +243 -0
  65. package/dist/lib/semver-range.js.map +1 -0
  66. package/dist/lib/session.d.ts +5 -0
  67. package/dist/lib/session.d.ts.map +1 -1
  68. package/dist/lib/session.js +9 -0
  69. package/dist/lib/session.js.map +1 -1
  70. package/dist/lib/skit-adapter.d.ts.map +1 -1
  71. package/dist/lib/skit-adapter.js +26 -3
  72. package/dist/lib/skit-adapter.js.map +1 -1
  73. package/dist/lib/timing.d.ts +7 -0
  74. package/dist/lib/timing.d.ts.map +1 -0
  75. package/dist/lib/timing.js +14 -0
  76. package/dist/lib/timing.js.map +1 -0
  77. package/dist/lib/types.d.ts +3 -0
  78. package/dist/lib/types.d.ts.map +1 -1
  79. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/command-specs.d.ts +25 -0
  80. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/command-specs.d.ts.map +1 -0
  81. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/command-specs.js +55 -0
  82. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/command-specs.js.map +1 -0
  83. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/config.d.ts +52 -0
  84. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/config.d.ts.map +1 -0
  85. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/config.js +62 -0
  86. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/config.js.map +1 -0
  87. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/detect.d.ts +30 -0
  88. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/detect.d.ts.map +1 -0
  89. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/detect.js +39 -0
  90. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/detect.js.map +1 -0
  91. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/index.d.ts +35 -0
  92. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/index.d.ts.map +1 -0
  93. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/index.js +31 -0
  94. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/index.js.map +1 -0
  95. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/policy.d.ts +42 -0
  96. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/policy.d.ts.map +1 -0
  97. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/policy.js +36 -0
  98. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/policy.js.map +1 -0
  99. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/report.d.ts +11 -0
  100. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/report.d.ts.map +1 -0
  101. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/report.js +46 -0
  102. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/report.js.map +1 -0
  103. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/semver.d.ts +11 -0
  104. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/semver.d.ts.map +1 -0
  105. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/semver.js +81 -0
  106. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/semver.js.map +1 -0
  107. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/types.d.ts +29 -0
  108. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/types.d.ts.map +1 -0
  109. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/types.js +6 -0
  110. package/node_modules/@microwiseai/snapshot-downgrade-guard/dist/types.js.map +1 -0
  111. package/node_modules/@microwiseai/snapshot-downgrade-guard/package.json +55 -0
  112. package/package.json +5 -2
  113. package/dist/lib/transitive-resolver.d.ts +0 -89
  114. package/dist/lib/transitive-resolver.d.ts.map +0 -1
  115. package/dist/lib/transitive-resolver.js +0 -447
  116. package/dist/lib/transitive-resolver.js.map +0 -1
@@ -0,0 +1,62 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { asDowngradeMode } from './policy.js';
5
+ const DEFAULT_FS = { existsSync, readFileSync, writeFileSync, mkdirSync };
6
+ /** Default config directory (~/.config/ist-snapshot). */
7
+ export function getConfigDir(opts = {}) {
8
+ return opts.configDir ?? join(homedir(), '.config', 'ist-snapshot');
9
+ }
10
+ /** Absolute path to config.json. */
11
+ export function getConfigPath(opts = {}) {
12
+ return join(getConfigDir(opts), 'config.json');
13
+ }
14
+ /**
15
+ * Read the machine config. Returns {} when the file is absent or unparseable
16
+ * (tolerant — a broken/foreign file must not crash an install decision).
17
+ */
18
+ export function readMachineConfig(opts = {}) {
19
+ const fs = opts.fs ?? DEFAULT_FS;
20
+ const path = getConfigPath(opts);
21
+ try {
22
+ if (!fs.existsSync(path))
23
+ return {};
24
+ const parsed = JSON.parse(fs.readFileSync(path, 'utf-8'));
25
+ return parsed && typeof parsed === 'object' ? parsed : {};
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ /**
32
+ * Write the machine config (pretty JSON), creating the config dir if needed.
33
+ */
34
+ export function writeMachineConfig(config, opts = {}) {
35
+ const fs = opts.fs ?? DEFAULT_FS;
36
+ const dir = getConfigDir(opts);
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ fs.writeFileSync(getConfigPath(opts), JSON.stringify(config, null, 2) + '\n');
41
+ }
42
+ /**
43
+ * Read `install.onDowngrade` from machine config, validated.
44
+ * Returns null when unset or invalid.
45
+ */
46
+ export function getMachineDowngradeMode(opts = {}) {
47
+ return asDowngradeMode(readMachineConfig(opts).install?.onDowngrade);
48
+ }
49
+ /**
50
+ * Set `install.onDowngrade` in machine config (read-modify-write), preserving
51
+ * every other key. Returns the written config.
52
+ */
53
+ export function setMachineDowngradeMode(mode, opts = {}) {
54
+ const current = readMachineConfig(opts);
55
+ const next = {
56
+ ...current,
57
+ install: { ...(current.install ?? {}), onDowngrade: mode },
58
+ };
59
+ writeMachineConfig(next, opts);
60
+ return next;
61
+ }
62
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA+B9C,MAAM,UAAU,GAAa,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AAEpF,yDAAyD;AACzD,MAAM,UAAU,YAAY,CAAC,OAAwB,EAAE;IACrD,OAAO,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AACtE,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,aAAa,CAAC,OAAwB,EAAE;IACtD,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAwB,EAAE;IAC1D,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,UAAU,CAAC;IACjC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAwB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqB,EAAE,OAAwB,EAAE;IAClF,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,UAAU,CAAC;IACjC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAwB,EAAE;IAChE,OAAO,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAmB,EACnB,OAAwB,EAAE;IAE1B,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAkB;QAC1B,GAAG,OAAO;QACV,OAAO,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;KAC3D,CAAC;IACF,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { DetectResult } from './types.js';
2
+ /**
3
+ * Reads the currently-installed (global) version of a package, or null/undefined
4
+ * when it is not installed. MUST be injected — this package never shells out to
5
+ * npm, keeping it pure and side-effect-free. Core supplies a real implementation
6
+ * (e.g. `npm ls -g`); tests inject a stub.
7
+ */
8
+ export type ReadVersionFn = (pkg: string) => string | null | undefined;
9
+ export interface DetectOptions {
10
+ /** Injected version reader. Required — no default npm execution. */
11
+ readVersion: ReadVersionFn;
12
+ }
13
+ /**
14
+ * Detect which pinned packages would be DOWNGRADED relative to what is already
15
+ * installed globally.
16
+ *
17
+ * For each `pkg -> pinned` entry:
18
+ * - not installed (readVersion returns null/undefined/empty) → OK (skip)
19
+ * - installed === pinned → OK (skip)
20
+ * - installed < pinned (an upgrade) → OK (skip)
21
+ * - installed > pinned (a downgrade, strict) → OFFENDER
22
+ * - either side not an exact semver (incomparable) → OK (skip, tolerant)
23
+ *
24
+ * A clean Docker sandbox has nothing installed globally, so every entry skips
25
+ * and `wouldDowngrade` is false — the guard auto-passes with no config.
26
+ *
27
+ * Pure: no I/O, no mutation of `installMap`.
28
+ */
29
+ export declare function detectDowngrades(installMap: Record<string, string>, opts?: DetectOptions): DetectResult;
30
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,YAAY,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEvE,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,WAAW,EAAE,aAAa,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,IAAI,CAAC,EAAE,aAAa,GACnB,YAAY,CAwBd"}
@@ -0,0 +1,39 @@
1
+ import { compareExact } from './semver.js';
2
+ /**
3
+ * Detect which pinned packages would be DOWNGRADED relative to what is already
4
+ * installed globally.
5
+ *
6
+ * For each `pkg -> pinned` entry:
7
+ * - not installed (readVersion returns null/undefined/empty) → OK (skip)
8
+ * - installed === pinned → OK (skip)
9
+ * - installed < pinned (an upgrade) → OK (skip)
10
+ * - installed > pinned (a downgrade, strict) → OFFENDER
11
+ * - either side not an exact semver (incomparable) → OK (skip, tolerant)
12
+ *
13
+ * A clean Docker sandbox has nothing installed globally, so every entry skips
14
+ * and `wouldDowngrade` is false — the guard auto-passes with no config.
15
+ *
16
+ * Pure: no I/O, no mutation of `installMap`.
17
+ */
18
+ export function detectDowngrades(installMap, opts) {
19
+ const readVersion = opts?.readVersion;
20
+ if (typeof readVersion !== 'function') {
21
+ throw new Error('detectDowngrades: opts.readVersion injection is required (this package never runs npm itself).');
22
+ }
23
+ const offenders = [];
24
+ for (const [pkg, pinned] of Object.entries(installMap ?? {})) {
25
+ const installed = readVersion(pkg);
26
+ if (installed === null || installed === undefined || installed === '') {
27
+ continue; // not installed → no downgrade
28
+ }
29
+ const cmp = compareExact(installed, pinned);
30
+ if (cmp === null) {
31
+ continue; // incomparable (non-exact semver) → tolerant skip
32
+ }
33
+ if (cmp > 0) {
34
+ offenders.push({ pkg, installed, pinned });
35
+ }
36
+ }
37
+ return { wouldDowngrade: offenders.length > 0, offenders };
38
+ }
39
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAkC,EAClC,IAAoB;IAEpB,MAAM,WAAW,GAAG,IAAI,EAAE,WAAW,CAAC;IACtC,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;YACtE,SAAS,CAAC,+BAA+B;QAC3C,CAAC;QACD,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,SAAS,CAAC,kDAAkD;QAC9D,CAAC;QACD,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,cAAc,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @microwiseai/snapshot-downgrade-guard
3
+ *
4
+ * Auto-detects when a snapshot install would DOWNGRADE already-installed global
5
+ * tooling, and resolves the layered refuse/warn/allow policy. Pure and
6
+ * dependency-injected: it never runs npm, touches the network, or mutates state
7
+ * except through the explicit machine-config writers. A clean Docker sandbox
8
+ * (nothing installed globally) produces zero offenders and auto-passes.
9
+ *
10
+ * Usage (in core, at the npm-exec boundary):
11
+ * import {
12
+ * detectDowngrades, resolveDowngradePolicy, readOneShotOverride,
13
+ * readMachineConfig, formatDowngradeReport,
14
+ * } from '@microwiseai/snapshot-downgrade-guard';
15
+ *
16
+ * const { wouldDowngrade, offenders } = detectDowngrades(installMap, { readVersion });
17
+ * const mode = resolveDowngradePolicy({
18
+ * override: readOneShotOverride({ env: process.env, allowDowngradeFlag }),
19
+ * machine: readMachineConfig().install?.onDowngrade,
20
+ * snapshot: snapshot.policy?.onDowngrade,
21
+ * });
22
+ * if (wouldDowngrade && mode === 'refuse') throw new Error(formatDowngradeReport(offenders, mode));
23
+ */
24
+ export { detectDowngrades } from './detect.js';
25
+ export type { DetectOptions, ReadVersionFn } from './detect.js';
26
+ export { resolveDowngradePolicy, readOneShotOverride, asDowngradeMode, DEFAULT_DOWNGRADE_MODE, } from './policy.js';
27
+ export type { PolicySources, OneShotOverrideInput } from './policy.js';
28
+ export { getConfigDir, getConfigPath, readMachineConfig, writeMachineConfig, getMachineDowngradeMode, setMachineDowngradeMode, } from './config.js';
29
+ export type { MachineConfig, ConfigFs, ConfigIoOptions } from './config.js';
30
+ export { formatDowngradeReport } from './report.js';
31
+ export { extractGlobalInstallSpecs } from './command-specs.js';
32
+ export { isExactSemver, compareExact } from './semver.js';
33
+ export type { DowngradeMode, Offender, DetectResult } from './types.js';
34
+ export { DOWNGRADE_MODES } from './types.js';
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE1D,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @microwiseai/snapshot-downgrade-guard
3
+ *
4
+ * Auto-detects when a snapshot install would DOWNGRADE already-installed global
5
+ * tooling, and resolves the layered refuse/warn/allow policy. Pure and
6
+ * dependency-injected: it never runs npm, touches the network, or mutates state
7
+ * except through the explicit machine-config writers. A clean Docker sandbox
8
+ * (nothing installed globally) produces zero offenders and auto-passes.
9
+ *
10
+ * Usage (in core, at the npm-exec boundary):
11
+ * import {
12
+ * detectDowngrades, resolveDowngradePolicy, readOneShotOverride,
13
+ * readMachineConfig, formatDowngradeReport,
14
+ * } from '@microwiseai/snapshot-downgrade-guard';
15
+ *
16
+ * const { wouldDowngrade, offenders } = detectDowngrades(installMap, { readVersion });
17
+ * const mode = resolveDowngradePolicy({
18
+ * override: readOneShotOverride({ env: process.env, allowDowngradeFlag }),
19
+ * machine: readMachineConfig().install?.onDowngrade,
20
+ * snapshot: snapshot.policy?.onDowngrade,
21
+ * });
22
+ * if (wouldDowngrade && mode === 'refuse') throw new Error(formatDowngradeReport(offenders, mode));
23
+ */
24
+ export { detectDowngrades } from './detect.js';
25
+ export { resolveDowngradePolicy, readOneShotOverride, asDowngradeMode, DEFAULT_DOWNGRADE_MODE, } from './policy.js';
26
+ export { getConfigDir, getConfigPath, readMachineConfig, writeMachineConfig, getMachineDowngradeMode, setMachineDowngradeMode, } from './config.js';
27
+ export { formatDowngradeReport } from './report.js';
28
+ export { extractGlobalInstallSpecs } from './command-specs.js';
29
+ export { isExactSemver, compareExact } from './semver.js';
30
+ export { DOWNGRADE_MODES } from './types.js';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type DowngradeMode } from './types.js';
2
+ /**
3
+ * Policy values read from each layer. Any field may be absent (undefined/null)
4
+ * or invalid; invalid values are ignored (treated as absent) so a typo never
5
+ * silently weakens the guard.
6
+ *
7
+ * Precedence (highest wins):
8
+ * (a) override — CLI flag / one-shot env (already mapped to a mode)
9
+ * (b) machine — machine config (~/.config/ist-snapshot/config.json)
10
+ * (c) snapshot — snapshot.json `policy.onDowngrade`
11
+ * (d) default — 'refuse'
12
+ */
13
+ export interface PolicySources {
14
+ override?: DowngradeMode | string | null;
15
+ machine?: DowngradeMode | string | null;
16
+ snapshot?: DowngradeMode | string | null;
17
+ }
18
+ /** The default when no layer specifies a valid mode. */
19
+ export declare const DEFAULT_DOWNGRADE_MODE: DowngradeMode;
20
+ /** Narrow an arbitrary value to a valid DowngradeMode, or null if invalid. */
21
+ export declare function asDowngradeMode(v: unknown): DowngradeMode | null;
22
+ /**
23
+ * Resolve the effective downgrade policy from the layered sources.
24
+ * Pure: first valid value in precedence order (a > b > c), else the default.
25
+ */
26
+ export declare function resolveDowngradePolicy(sources?: PolicySources): DowngradeMode;
27
+ export interface OneShotOverrideInput {
28
+ /** Environment map (e.g. process.env). SNAPSHOT_ALLOW_LOCAL=1 → allow. */
29
+ env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
30
+ /** CLI flag, e.g. --allow-downgrade → allow. */
31
+ allowDowngradeFlag?: boolean;
32
+ }
33
+ /**
34
+ * Map the one-shot escape hatches (CLI `--allow-downgrade` or env
35
+ * `SNAPSHOT_ALLOW_LOCAL=1`) to an override mode.
36
+ *
37
+ * Returns 'allow' when either hatch is engaged, otherwise null (no override).
38
+ * Pure — reads only the injected inputs. Core passes the result as
39
+ * `PolicySources.override`.
40
+ */
41
+ export declare function readOneShotOverride(input?: OneShotOverrideInput): DowngradeMode | null;
42
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,wDAAwD;AACxD,eAAO,MAAM,sBAAsB,EAAE,aAAwB,CAAC;AAE9D,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAIhE;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAOjF;AAED,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7D,gDAAgD;IAChD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,oBAAyB,GAAG,aAAa,GAAG,IAAI,CAK1F"}
@@ -0,0 +1,36 @@
1
+ import { DOWNGRADE_MODES } from './types.js';
2
+ /** The default when no layer specifies a valid mode. */
3
+ export const DEFAULT_DOWNGRADE_MODE = 'refuse';
4
+ /** Narrow an arbitrary value to a valid DowngradeMode, or null if invalid. */
5
+ export function asDowngradeMode(v) {
6
+ return typeof v === 'string' && DOWNGRADE_MODES.includes(v)
7
+ ? v
8
+ : null;
9
+ }
10
+ /**
11
+ * Resolve the effective downgrade policy from the layered sources.
12
+ * Pure: first valid value in precedence order (a > b > c), else the default.
13
+ */
14
+ export function resolveDowngradePolicy(sources = {}) {
15
+ return (asDowngradeMode(sources.override) ??
16
+ asDowngradeMode(sources.machine) ??
17
+ asDowngradeMode(sources.snapshot) ??
18
+ DEFAULT_DOWNGRADE_MODE);
19
+ }
20
+ /**
21
+ * Map the one-shot escape hatches (CLI `--allow-downgrade` or env
22
+ * `SNAPSHOT_ALLOW_LOCAL=1`) to an override mode.
23
+ *
24
+ * Returns 'allow' when either hatch is engaged, otherwise null (no override).
25
+ * Pure — reads only the injected inputs. Core passes the result as
26
+ * `PolicySources.override`.
27
+ */
28
+ export function readOneShotOverride(input = {}) {
29
+ const env = input.env ?? {};
30
+ if (input.allowDowngradeFlag === true)
31
+ return 'allow';
32
+ if (env.SNAPSHOT_ALLOW_LOCAL === '1')
33
+ return 'allow';
34
+ return null;
35
+ }
36
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAC;AAmBjE,wDAAwD;AACxD,MAAM,CAAC,MAAM,sBAAsB,GAAkB,QAAQ,CAAC;AAE9D,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAK,eAAqC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChF,CAAC,CAAE,CAAmB;QACtB,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAyB,EAAE;IAChE,OAAO,CACL,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC;QACjC,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC;QAChC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC;QACjC,sBAAsB,CACvB,CAAC;AACJ,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAA8B,EAAE;IAClE,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,kBAAkB,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACtD,IAAI,GAAG,CAAC,oBAAoB,KAAK,GAAG;QAAE,OAAO,OAAO,CAAC;IACrD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { DowngradeMode, Offender } from './types.js';
2
+ /**
3
+ * Build a user-facing message describing the detected downgrades for the given
4
+ * policy mode. Korean + short English.
5
+ *
6
+ * - refuse: block message with the offender list and the three bypass routes.
7
+ * - warn: warning header + offender list, install continues.
8
+ * - allow: minimal note (usually not shown by callers).
9
+ */
10
+ export declare function formatDowngradeReport(offenders: Offender[], mode: DowngradeMode): string;
11
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAqCxF"}
@@ -0,0 +1,46 @@
1
+ function offenderLines(offenders) {
2
+ return offenders.map((o) => ` • ${o.pkg}: ${o.installed} → ${o.pinned}`).join('\n');
3
+ }
4
+ /**
5
+ * Build a user-facing message describing the detected downgrades for the given
6
+ * policy mode. Korean + short English.
7
+ *
8
+ * - refuse: block message with the offender list and the three bypass routes.
9
+ * - warn: warning header + offender list, install continues.
10
+ * - allow: minimal note (usually not shown by callers).
11
+ */
12
+ export function formatDowngradeReport(offenders, mode) {
13
+ if (offenders.length === 0) {
14
+ return '다운그레이드 없음 / No downgrades detected.';
15
+ }
16
+ const list = offenderLines(offenders);
17
+ if (mode === 'refuse') {
18
+ return [
19
+ '스냅샷 설치가 현재 전역 패키지를 다운그레이드하므로 거부되었습니다.',
20
+ 'Refused: this snapshot would downgrade already-installed global packages.',
21
+ '',
22
+ '다운그레이드 대상 / Would downgrade:',
23
+ list,
24
+ '',
25
+ '해결 / How to proceed:',
26
+ ' 1) 깨끗한 Docker 샌드박스에서 실행 (권장) / run in a clean Docker sandbox (recommended)',
27
+ ' 2) 이번 한 번만 강제 / force just this once: --allow-downgrade 또는/or SNAPSHOT_ALLOW_LOCAL=1',
28
+ ' 3) 이 머신에서 항상 허용 / always allow on this machine: snapshot config set install.onDowngrade allow',
29
+ ].join('\n');
30
+ }
31
+ if (mode === 'warn') {
32
+ return [
33
+ '⚠ 경고: 스냅샷 설치가 아래 전역 패키지를 다운그레이드합니다 (계속 진행).',
34
+ '⚠ Warning: this snapshot will downgrade the following global packages (continuing).',
35
+ '',
36
+ list,
37
+ ].join('\n');
38
+ }
39
+ // allow
40
+ return [
41
+ '스냅샷 설치가 아래 패키지를 다운그레이드합니다 (allow 정책으로 진행).',
42
+ 'Proceeding despite downgrades (policy=allow):',
43
+ list,
44
+ ].join('\n');
45
+ }
46
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAEA,SAAS,aAAa,CAAC,SAAqB;IAC1C,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAqB,EAAE,IAAmB;IAC9E,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,qCAAqC,CAAC;IAC/C,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAEtC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,uCAAuC;YACvC,2EAA2E;YAC3E,EAAE;YACF,8BAA8B;YAC9B,IAAI;YACJ,EAAE;YACF,sBAAsB;YACtB,8EAA8E;YAC9E,2FAA2F;YAC3F,kGAAkG;SACnG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO;YACL,6CAA6C;YAC7C,qFAAqF;YACrF,EAAE;YACF,IAAI;SACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED,QAAQ;IACR,OAAO;QACL,4CAA4C;QAC5C,+CAA+C;QAC/C,IAAI;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** True iff `v` is a single, exact semver (prerelease/build metadata allowed). */
2
+ export declare function isExactSemver(v: unknown): boolean;
3
+ /**
4
+ * Compare two exact semvers by SemVer-spec precedence (build metadata ignored).
5
+ *
6
+ * Returns -1 if a < b, 0 if equal precedence, 1 if a > b, or `null` when EITHER
7
+ * side is not an exact semver (incomparable — caller should skip). Never throws:
8
+ * tolerance is a design requirement.
9
+ */
10
+ export declare function compareExact(a: unknown, b: unknown): -1 | 0 | 1 | null;
11
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../src/semver.ts"],"names":[],"mappings":"AAiBA,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEjD;AAuBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAgCtE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Minimal, self-contained exact-semver comparison.
3
+ *
4
+ * This package stays zero-runtime-dependency (no `semver`) on purpose so it is
5
+ * self-sufficient and side-effect-free. We only compare EXACT semvers; anything
6
+ * that is not a single exact version is reported as "incomparable" and the
7
+ * caller skips it (tolerant, same philosophy as the core-floor check).
8
+ *
9
+ * The pattern is the canonical semver.org regex, anchored — it accepts:
10
+ * 0.3.59 (plain)
11
+ * 0.3.70-dev.0 (prerelease)
12
+ * 1.2.3-rc.1+build (prerelease + build metadata)
13
+ * and rejects ranges, `^`/`~`, `v` prefixes, dist-tags, partial versions, etc.
14
+ */
15
+ const EXACT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
16
+ /** True iff `v` is a single, exact semver (prerelease/build metadata allowed). */
17
+ export function isExactSemver(v) {
18
+ return typeof v === 'string' && EXACT_SEMVER.test(v.trim());
19
+ }
20
+ function parseSemver(v) {
21
+ if (typeof v !== 'string')
22
+ return null;
23
+ const m = EXACT_SEMVER.exec(v.trim());
24
+ if (!m)
25
+ return null;
26
+ const prerelease = [];
27
+ if (m[4]) {
28
+ for (const id of m[4].split('.')) {
29
+ prerelease.push(/^\d+$/.test(id) ? Number(id) : id);
30
+ }
31
+ }
32
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), prerelease };
33
+ }
34
+ /**
35
+ * Compare two exact semvers by SemVer-spec precedence (build metadata ignored).
36
+ *
37
+ * Returns -1 if a < b, 0 if equal precedence, 1 if a > b, or `null` when EITHER
38
+ * side is not an exact semver (incomparable — caller should skip). Never throws:
39
+ * tolerance is a design requirement.
40
+ */
41
+ export function compareExact(a, b) {
42
+ const pa = parseSemver(a);
43
+ const pb = parseSemver(b);
44
+ if (!pa || !pb)
45
+ return null;
46
+ for (const k of ['major', 'minor', 'patch']) {
47
+ if (pa[k] !== pb[k])
48
+ return pa[k] < pb[k] ? -1 : 1;
49
+ }
50
+ // A version WITHOUT prerelease outranks one WITH (1.0.0 > 1.0.0-rc.1).
51
+ const aPre = pa.prerelease;
52
+ const bPre = pb.prerelease;
53
+ if (aPre.length === 0 && bPre.length === 0)
54
+ return 0;
55
+ if (aPre.length === 0)
56
+ return 1;
57
+ if (bPre.length === 0)
58
+ return -1;
59
+ const len = Math.min(aPre.length, bPre.length);
60
+ for (let i = 0; i < len; i++) {
61
+ const x = aPre[i];
62
+ const y = bPre[i];
63
+ if (x === y)
64
+ continue;
65
+ const xNum = typeof x === 'number';
66
+ const yNum = typeof y === 'number';
67
+ // Numeric identifiers always have lower precedence than non-numeric.
68
+ if (xNum && !yNum)
69
+ return -1;
70
+ if (!xNum && yNum)
71
+ return 1;
72
+ if (xNum && yNum)
73
+ return x < y ? -1 : 1;
74
+ return x < y ? -1 : 1;
75
+ }
76
+ // All shared identifiers equal — the longer prerelease list has higher precedence.
77
+ if (aPre.length === bPre.length)
78
+ return 0;
79
+ return aPre.length < bPre.length ? -1 : 1;
80
+ }
81
+ //# sourceMappingURL=semver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.js","sourceRoot":"","sources":["../src/semver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared types for @microwiseai/snapshot-downgrade-guard.
3
+ */
4
+ /**
5
+ * Downgrade policy mode.
6
+ * - refuse (default): block the install when a downgrade is detected.
7
+ * - warn: print a warning, then proceed.
8
+ * - allow: proceed silently.
9
+ */
10
+ export type DowngradeMode = 'refuse' | 'warn' | 'allow';
11
+ /** The set of valid policy modes (runtime guard). */
12
+ export declare const DOWNGRADE_MODES: readonly DowngradeMode[];
13
+ /** A single package whose currently-installed version is newer than the pin. */
14
+ export interface Offender {
15
+ /** Package name. */
16
+ pkg: string;
17
+ /** Currently-installed (global) version. */
18
+ installed: string;
19
+ /** Version the snapshot would pin/install (a downgrade relative to `installed`). */
20
+ pinned: string;
21
+ }
22
+ /** Result of {@link detectDowngrades}. */
23
+ export interface DetectResult {
24
+ /** True iff at least one offender was found. */
25
+ wouldDowngrade: boolean;
26
+ /** Packages that would be downgraded (installed > pinned, strict). */
27
+ offenders: Offender[];
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD,qDAAqD;AACrD,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAAgC,CAAC;AAErF,gFAAgF;AAChF,MAAM,WAAW,QAAQ;IACvB,oBAAoB;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,cAAc,EAAE,OAAO,CAAC;IACxB,sEAAsE;IACtE,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared types for @microwiseai/snapshot-downgrade-guard.
3
+ */
4
+ /** The set of valid policy modes (runtime guard). */
5
+ export const DOWNGRADE_MODES = ['refuse', 'warn', 'allow'];
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,qDAAqD;AACrD,MAAM,CAAC,MAAM,eAAe,GAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@microwiseai/snapshot-downgrade-guard",
3
+ "version": "0.1.0",
4
+ "description": "Auto-detects when a snapshot install would downgrade already-installed global tooling, and resolves the layered refuse/warn/allow policy. Pure, dependency-injected, no side effects.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "dev": "tsc --watch",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "vitest",
20
+ "test:run": "vitest run",
21
+ "lint": "eslint src",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "keywords": [
28
+ "snapshot",
29
+ "downgrade",
30
+ "guard",
31
+ "semver",
32
+ "install"
33
+ ],
34
+ "author": "IST Team",
35
+ "license": "MIT",
36
+ "dependencies": {},
37
+ "devDependencies": {
38
+ "@types/node": "^20.11.0",
39
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
40
+ "@typescript-eslint/parser": "^8.57.0",
41
+ "eslint": "^10.0.3",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^4.0.18"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "glpkg": {
49
+ "group": "microwiseai"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://gitlab.com/microwiseai/snapshot.downgrade-guard.git"
54
+ }
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microwiseai/snapshot",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "CLI for installing IST Snapshot packages",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -25,6 +25,7 @@
25
25
  "scripts": {
26
26
  "prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
27
27
  "build": "tsc -p tsconfig.build.json",
28
+ "postbuild": "node scripts/write-build-info.mjs",
28
29
  "dev": "tsc --watch",
29
30
  "typecheck": "tsc --noEmit",
30
31
  "test": "vitest",
@@ -32,7 +33,7 @@
32
33
  "lint": "eslint src",
33
34
  "prepublishOnly": "npm run build",
34
35
  "publish:npm": "set -a; . ./.env; set +a; npm publish --access public",
35
- "publish:glpkg": "glpkg publish --git-tag false --push false",
36
+ "publish:glpkg": "glpkg publish",
36
37
  "publish:all": "npm run publish:npm && npm run publish:glpkg"
37
38
  },
38
39
  "files": [
@@ -53,6 +54,7 @@
53
54
  "@microwiseai/license": "^0.1.4",
54
55
  "@microwiseai/skit": "^0.1.19",
55
56
  "@microwiseai/snapshot-dedup": "0.2.0",
57
+ "@microwiseai/snapshot-downgrade-guard": "0.1.0",
56
58
  "@microwiseai/snapshot-parallel": "^0.1.0",
57
59
  "chalk": "^5.3.0",
58
60
  "commander": "^12.1.0",
@@ -75,6 +77,7 @@
75
77
  "@microwiseai/license",
76
78
  "@microwiseai/skit",
77
79
  "@microwiseai/snapshot-dedup",
80
+ "@microwiseai/snapshot-downgrade-guard",
78
81
  "@microwiseai/snapshot-parallel"
79
82
  ],
80
83
  "glpkg": {