@skillsmith/core 0.11.1 → 0.11.3
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/CHANGELOG.md +20 -0
- package/README.md +6 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/McpReferenceExtractor.d.ts +59 -5
- package/dist/src/analysis/McpReferenceExtractor.d.ts.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts +2 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js +278 -0
- package/dist/src/analysis/McpReferenceExtractor.hardening.test.js.map +1 -0
- package/dist/src/analysis/McpReferenceExtractor.js +276 -20
- package/dist/src/analysis/McpReferenceExtractor.js.map +1 -1
- package/dist/src/analysis/McpReferenceExtractor.test.js +4 -0
- package/dist/src/analysis/McpReferenceExtractor.test.js.map +1 -1
- package/dist/src/config/config-atomic-write.d.ts +58 -0
- package/dist/src/config/config-atomic-write.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.js +151 -0
- package/dist/src/config/config-atomic-write.js.map +1 -0
- package/dist/src/config/config-atomic-write.test.d.ts +9 -0
- package/dist/src/config/config-atomic-write.test.d.ts.map +1 -0
- package/dist/src/config/config-atomic-write.test.js +92 -0
- package/dist/src/config/config-atomic-write.test.js.map +1 -0
- package/dist/src/config/device-identity.d.ts +16 -0
- package/dist/src/config/device-identity.d.ts.map +1 -1
- package/dist/src/config/device-identity.js +82 -14
- package/dist/src/config/device-identity.js.map +1 -1
- package/dist/src/config/device-identity.test.js +158 -1
- package/dist/src/config/device-identity.test.js.map +1 -1
- package/dist/src/config/index.d.ts +14 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +31 -15
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/exports/services.d.ts +2 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +1 -0
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SwarmIndexer.d.ts +1 -1
- package/dist/src/indexer/SwarmIndexer.js +2 -2
- package/dist/src/indexer/SwarmIndexer.js.map +1 -1
- package/dist/src/install/paths.d.ts +1 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +2 -0
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.js +10 -1
- package/dist/src/install/paths.test.js.map +1 -1
- package/dist/src/journal/reader.d.ts.map +1 -1
- package/dist/src/journal/reader.js +4 -1
- package/dist/src/journal/reader.js.map +1 -1
- package/dist/src/journal/types.d.ts +13 -3
- package/dist/src/journal/types.d.ts.map +1 -1
- package/dist/src/journal/types.js +8 -2
- package/dist/src/journal/types.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +36 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/security/audit-types.d.ts +1 -1
- package/dist/src/security/audit-types.d.ts.map +1 -1
- package/dist/src/security/audit-types.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +7 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.js +10 -74
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts +14 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.js +33 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts +9 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js +65 -0
- package/dist/src/security/scanner/SecurityScanner.formatters.test.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +15 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/confusables.d.ts +28 -0
- package/dist/src/security/scanner/confusables.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.js +87 -0
- package/dist/src/security/scanner/confusables.js.map +1 -0
- package/dist/src/security/scanner/confusables.test.d.ts +10 -0
- package/dist/src/security/scanner/confusables.test.d.ts.map +1 -0
- package/dist/src/security/scanner/confusables.test.js +44 -0
- package/dist/src/security/scanner/confusables.test.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +7 -1
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +13 -0
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +14 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/typosquat-reference-list.d.ts +54 -0
- package/dist/src/security/scanner/typosquat-reference-list.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.js +55 -0
- package/dist/src/security/scanner/typosquat-reference-list.js.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts +5 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js +52 -0
- package/dist/src/security/scanner/typosquat-reference-list.test.js.map +1 -0
- package/dist/src/security/scanner/typosquat.d.ts +100 -0
- package/dist/src/security/scanner/typosquat.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.js +256 -0
- package/dist/src/security/scanner/typosquat.js.map +1 -0
- package/dist/src/security/scanner/typosquat.test.d.ts +12 -0
- package/dist/src/security/scanner/typosquat.test.d.ts.map +1 -0
- package/dist/src/security/scanner/typosquat.test.js +140 -0
- package/dist/src/security/scanner/typosquat.test.js.map +1 -0
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +10 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +21 -0
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +46 -2
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.helpers.test.d.ts +2 -0
- package/dist/src/services/skill-installation.helpers.test.d.ts.map +1 -0
- package/dist/src/services/skill-installation.helpers.test.js +80 -0
- package/dist/src/services/skill-installation.helpers.test.js.map +1 -0
- package/dist/src/sync/inventory-collector.d.ts +15 -4
- package/dist/src/sync/inventory-collector.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.js +73 -24
- package/dist/src/sync/inventory-collector.js.map +1 -1
- package/dist/src/sync/inventory-collector.test.d.ts +15 -1
- package/dist/src/sync/inventory-collector.test.d.ts.map +1 -1
- package/dist/src/sync/inventory-collector.test.js +75 -5
- package/dist/src/sync/inventory-collector.test.js.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +1 -1
- package/dist/src/telemetry/tracer-imports.js +1 -1
- package/dist/src/telemetry/tracer.js +1 -1
- package/dist/tests/SecurityScanner.scoring.test.js +60 -0
- package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
- package/dist/tests/SwarmIndexer.test.js +4 -5
- package/dist/tests/SwarmIndexer.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +1 -0
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +1 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +8 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +13 -8
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5531: Atomic config-write primitive tests.
|
|
3
|
+
*
|
|
4
|
+
* Uses the same tmpdir-per-test isolation harness as device-identity.test.ts
|
|
5
|
+
* / index.test.ts (no shared HOME mutation needed here — these primitives
|
|
6
|
+
* take an explicit path, not the global config path).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=config-atomic-write.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-atomic-write.test.d.ts","sourceRoot":"","sources":["../../../src/config/config-atomic-write.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5531: Atomic config-write primitive tests.
|
|
3
|
+
*
|
|
4
|
+
* Uses the same tmpdir-per-test isolation harness as device-identity.test.ts
|
|
5
|
+
* / index.test.ts (no shared HOME mutation needed here — these primitives
|
|
6
|
+
* take an explicit path, not the global config path).
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, utimesSync, writeFileSync } from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
|
|
13
|
+
function makeTempDir() {
|
|
14
|
+
const dir = path.join(os.tmpdir(), `skillsmith-atomic-write-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
describe('acquireConfigLock — mutual exclusion', () => {
|
|
19
|
+
it('a second acquire attempt while the lock is held times out', () => {
|
|
20
|
+
const dir = makeTempDir();
|
|
21
|
+
const configPath = path.join(dir, 'config.json');
|
|
22
|
+
const release = acquireConfigLock(configPath);
|
|
23
|
+
try {
|
|
24
|
+
expect(() => acquireConfigLock(configPath, 200)).toThrow(/Timed out waiting for config lock/);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
release();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('a subsequent acquire succeeds immediately after release', () => {
|
|
31
|
+
const dir = makeTempDir();
|
|
32
|
+
const configPath = path.join(dir, 'config.json');
|
|
33
|
+
const release = acquireConfigLock(configPath);
|
|
34
|
+
release();
|
|
35
|
+
// Should not throw / should not need to wait out any timeout.
|
|
36
|
+
const secondRelease = acquireConfigLock(configPath, 200);
|
|
37
|
+
secondRelease();
|
|
38
|
+
});
|
|
39
|
+
it('force-clears a stale lock (older than the staleness threshold) instead of waiting out the full timeout', () => {
|
|
40
|
+
const dir = makeTempDir();
|
|
41
|
+
const configPath = path.join(dir, 'config.json');
|
|
42
|
+
const lockPath = `${configPath}.lock`;
|
|
43
|
+
// Simulate a lock abandoned by a crashed process: create it, then
|
|
44
|
+
// backdate its mtime well past the staleness threshold.
|
|
45
|
+
writeFileSync(lockPath, '99999');
|
|
46
|
+
const longAgo = new Date(Date.now() - 60_000);
|
|
47
|
+
utimesSync(lockPath, longAgo, longAgo);
|
|
48
|
+
const start = Date.now();
|
|
49
|
+
// Even with a tight timeout budget, stale-lock recovery should let this
|
|
50
|
+
// succeed almost immediately rather than burning the whole window.
|
|
51
|
+
const release = acquireConfigLock(configPath, 500);
|
|
52
|
+
const elapsedMs = Date.now() - start;
|
|
53
|
+
release();
|
|
54
|
+
expect(elapsedMs).toBeLessThan(500);
|
|
55
|
+
});
|
|
56
|
+
it('release() is idempotent — calling it twice does not throw', () => {
|
|
57
|
+
const dir = makeTempDir();
|
|
58
|
+
const configPath = path.join(dir, 'config.json');
|
|
59
|
+
const release = acquireConfigLock(configPath);
|
|
60
|
+
release();
|
|
61
|
+
expect(() => release()).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('atomicWriteFile', () => {
|
|
65
|
+
it('writes the given content to the target path', () => {
|
|
66
|
+
const dir = makeTempDir();
|
|
67
|
+
const filePath = path.join(dir, 'config.json');
|
|
68
|
+
atomicWriteFile(filePath, JSON.stringify({ a: 1 }), 0o600);
|
|
69
|
+
expect(JSON.parse(readFileSync(filePath, 'utf-8'))).toEqual({ a: 1 });
|
|
70
|
+
});
|
|
71
|
+
it('leaves no stray temp file behind after a successful write', () => {
|
|
72
|
+
const dir = makeTempDir();
|
|
73
|
+
const filePath = path.join(dir, 'config.json');
|
|
74
|
+
atomicWriteFile(filePath, '{}', 0o600);
|
|
75
|
+
const entries = readdirSync(dir);
|
|
76
|
+
expect(entries.some((f) => f.endsWith('.tmp'))).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it('overwrites existing content atomically (rename-in-place, not append)', () => {
|
|
79
|
+
const dir = makeTempDir();
|
|
80
|
+
const filePath = path.join(dir, 'config.json');
|
|
81
|
+
atomicWriteFile(filePath, JSON.stringify({ a: 1 }), 0o600);
|
|
82
|
+
atomicWriteFile(filePath, JSON.stringify({ b: 2 }), 0o600);
|
|
83
|
+
expect(JSON.parse(readFileSync(filePath, 'utf-8'))).toEqual({ b: 2 });
|
|
84
|
+
});
|
|
85
|
+
it('sets the requested file mode', () => {
|
|
86
|
+
const dir = makeTempDir();
|
|
87
|
+
const filePath = path.join(dir, 'config.json');
|
|
88
|
+
atomicWriteFile(filePath, '{}', 0o600);
|
|
89
|
+
expect(existsSync(filePath)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=config-atomic-write.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-atomic-write.test.js","sourceRoot":"","sources":["../../../src/config/config-atomic-write.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,10 +43,26 @@ export declare function getDeviceId(): string | undefined;
|
|
|
43
43
|
* `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i`
|
|
44
44
|
*
|
|
45
45
|
* Preserves any existing `deviceLabel` and `lastPushAt` during the write.
|
|
46
|
+
* Concurrency-safe across processes (SMI-5531) — see
|
|
47
|
+
* {@link getOrCreatePersistedId}.
|
|
46
48
|
*
|
|
47
49
|
* SMI-5391
|
|
48
50
|
*/
|
|
49
51
|
export declare function getOrCreateDeviceId(): string;
|
|
52
|
+
/**
|
|
53
|
+
* Return the persisted per-install telemetry identifier, creating and
|
|
54
|
+
* persisting a new one (`sha256(randomUUID())`) if none exists yet.
|
|
55
|
+
*
|
|
56
|
+
* SMI-5531: generated and persisted UNCONDITIONALLY — this call must
|
|
57
|
+
* succeed and persist an id regardless of `SKILLSMITH_TELEMETRY_ENABLED` /
|
|
58
|
+
* `POSTHOG_API_KEY`, which is exactly the env-gated condition that made the
|
|
59
|
+
* legacy `context.distinctId` inert for virtually every real user. If a
|
|
60
|
+
* future refactor re-couples this to that legacy gate, this fix is inert
|
|
61
|
+
* again under a new field name — do not add any env/flag check here.
|
|
62
|
+
*
|
|
63
|
+
* Concurrency-safe across processes — see {@link getOrCreatePersistedId}.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getOrCreateInstallId(): string;
|
|
50
66
|
/**
|
|
51
67
|
* Set (or clear) the optional user-facing label for this device.
|
|
52
68
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-identity.d.ts","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"device-identity.d.ts","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAqEH;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAEhD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAS7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAMD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,IAAI,OAAO,CAIxD;AAMD;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAQ9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,SAAsB,GAC/B,OAAO,CAKT"}
|
|
@@ -28,8 +28,63 @@
|
|
|
28
28
|
* }
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
import { randomUUID } from 'node:crypto';
|
|
32
|
-
import {
|
|
31
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
32
|
+
import { chmodSync } from 'node:fs';
|
|
33
|
+
import { loadConfig, saveConfig, getConfigPath, ensureConfigDir } from './index.js';
|
|
34
|
+
import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Shared create-if-absent primitive (SMI-5531)
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Create-if-absent primitive shared by `getOrCreateDeviceId` and
|
|
40
|
+
* `getOrCreateInstallId`.
|
|
41
|
+
*
|
|
42
|
+
* An unguarded fast-path read (`getExisting(loadConfig())`) covers the
|
|
43
|
+
* common case cheaply. Only on a miss do we pay for
|
|
44
|
+
* {@link acquireConfigLock} — and, critically, RE-READ config state after
|
|
45
|
+
* acquiring it: another process may have created the value in the window
|
|
46
|
+
* between our fast-path read and lock acquisition. Skipping that re-check
|
|
47
|
+
* (e.g. by calling the plain `saveConfig()` with a value already decided
|
|
48
|
+
* before the lock was held) is exactly the TOCTOU shape that let two
|
|
49
|
+
* concurrent callers generate and persist two DIFFERENT ids — whichever
|
|
50
|
+
* wrote last would win on disk, but the loser's caller would still return
|
|
51
|
+
* its own (now-orphaned) generated value, so the two callers would never
|
|
52
|
+
* converge. `saveConfig()` itself is deliberately NOT used inside the
|
|
53
|
+
* locked section below: it would try to (re-)acquire this same
|
|
54
|
+
* non-reentrant lock and deadlock.
|
|
55
|
+
*
|
|
56
|
+
* @param getExisting - Reads the target id out of a config snapshot.
|
|
57
|
+
* @param generate - Produces a new id when none exists.
|
|
58
|
+
* @param merge - Returns a new full config with the id applied on top of `latest`.
|
|
59
|
+
*/
|
|
60
|
+
function getOrCreatePersistedId(getExisting, generate, merge) {
|
|
61
|
+
const fastPathExisting = getExisting(loadConfig());
|
|
62
|
+
if (fastPathExisting)
|
|
63
|
+
return fastPathExisting;
|
|
64
|
+
// The lock file lives alongside config.json — the directory must exist
|
|
65
|
+
// BEFORE we try to create it, not after acquiring the lock.
|
|
66
|
+
ensureConfigDir();
|
|
67
|
+
const configPath = getConfigPath();
|
|
68
|
+
const release = acquireConfigLock(configPath);
|
|
69
|
+
try {
|
|
70
|
+
const latest = loadConfig();
|
|
71
|
+
const alreadyCreated = getExisting(latest);
|
|
72
|
+
if (alreadyCreated)
|
|
73
|
+
return alreadyCreated;
|
|
74
|
+
const id = generate();
|
|
75
|
+
atomicWriteFile(configPath, JSON.stringify(merge(latest, id), null, 2), 0o600);
|
|
76
|
+
try {
|
|
77
|
+
chmodSync(configPath, 0o600);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore chmod errors on Windows.
|
|
81
|
+
}
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
33
88
|
// ============================================================================
|
|
34
89
|
// Device identity
|
|
35
90
|
// ============================================================================
|
|
@@ -50,22 +105,35 @@ export function getDeviceId() {
|
|
|
50
105
|
* `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i`
|
|
51
106
|
*
|
|
52
107
|
* Preserves any existing `deviceLabel` and `lastPushAt` during the write.
|
|
108
|
+
* Concurrency-safe across processes (SMI-5531) — see
|
|
109
|
+
* {@link getOrCreatePersistedId}.
|
|
53
110
|
*
|
|
54
111
|
* SMI-5391
|
|
55
112
|
*/
|
|
56
113
|
export function getOrCreateDeviceId() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
114
|
+
return getOrCreatePersistedId((config) => config.inventory?.deviceId, () => randomUUID(), (latest, deviceId) => ({
|
|
115
|
+
...latest,
|
|
116
|
+
inventory: { ...latest.inventory, deviceId },
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Return the persisted per-install telemetry identifier, creating and
|
|
121
|
+
* persisting a new one (`sha256(randomUUID())`) if none exists yet.
|
|
122
|
+
*
|
|
123
|
+
* SMI-5531: generated and persisted UNCONDITIONALLY — this call must
|
|
124
|
+
* succeed and persist an id regardless of `SKILLSMITH_TELEMETRY_ENABLED` /
|
|
125
|
+
* `POSTHOG_API_KEY`, which is exactly the env-gated condition that made the
|
|
126
|
+
* legacy `context.distinctId` inert for virtually every real user. If a
|
|
127
|
+
* future refactor re-couples this to that legacy gate, this fix is inert
|
|
128
|
+
* again under a new field name — do not add any env/flag check here.
|
|
129
|
+
*
|
|
130
|
+
* Concurrency-safe across processes — see {@link getOrCreatePersistedId}.
|
|
131
|
+
*/
|
|
132
|
+
export function getOrCreateInstallId() {
|
|
133
|
+
return getOrCreatePersistedId((config) => config.telemetry?.installId, () => createHash('sha256').update(randomUUID()).digest('hex'), (latest, installId) => ({
|
|
134
|
+
...latest,
|
|
135
|
+
telemetry: { ...latest.telemetry, installId },
|
|
136
|
+
}));
|
|
69
137
|
}
|
|
70
138
|
/**
|
|
71
139
|
* Set (or clear) the optional user-facing label for this device.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-identity.js","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"device-identity.js","sourceRoot":"","sources":["../../../src/config/device-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,22 +9,61 @@
|
|
|
9
9
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as os from 'os';
|
|
12
|
-
import {
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { promisify } from 'node:util';
|
|
14
|
+
import { mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { getDeviceId, getOrCreateDeviceId, getOrCreateInstallId, setDeviceLabel, forgetDevice, isInventorySyncDisabledLocally, getLastInventoryPushAt, recordInventoryPush, shouldAutoPush, } from './device-identity.js';
|
|
17
|
+
import { loadConfig } from './index.js';
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
13
19
|
// ---------------------------------------------------------------------------
|
|
14
20
|
// Helpers
|
|
15
21
|
// ---------------------------------------------------------------------------
|
|
16
22
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
|
+
const SHA256_HEX_RE = /^[0-9a-f]{64}$/i;
|
|
17
24
|
function makeTempConfigDir() {
|
|
18
25
|
return path.join(os.tmpdir(), `skillsmith-device-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
19
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Absolute path to this module's source, so a spawned subprocess can import
|
|
29
|
+
* the SAME implementation under test (not a copy/reimplementation) via `tsx`
|
|
30
|
+
* (already a repo devDependency — see CLAUDE.md's `npx tsx` usage).
|
|
31
|
+
*/
|
|
32
|
+
const DEVICE_IDENTITY_SRC = fileURLToPath(new URL('./device-identity.ts', import.meta.url));
|
|
33
|
+
/**
|
|
34
|
+
* Run `exportName()` (a zero-arg, string-returning export of
|
|
35
|
+
* device-identity.ts) in a real, separate OS process with `HOME` pointed at
|
|
36
|
+
* `homeDir`. Used to exercise genuine CROSS-PROCESS races that a single
|
|
37
|
+
* Node process's single-threaded event loop cannot produce on its own —
|
|
38
|
+
* `getOrCreateDeviceId`/`getOrCreateInstallId` are fully synchronous, so two
|
|
39
|
+
* "concurrent" calls within one process never actually interleave.
|
|
40
|
+
*/
|
|
41
|
+
async function runInSubprocess(exportName, homeDir) {
|
|
42
|
+
const scriptDir = mkdtempSync(path.join(os.tmpdir(), 'skillsmith-concurrency-'));
|
|
43
|
+
const scriptPath = path.join(scriptDir, 'run.mts');
|
|
44
|
+
writeFileSync(scriptPath, `import { ${exportName} } from ${JSON.stringify(DEVICE_IDENTITY_SRC)}\n` +
|
|
45
|
+
`process.stdout.write(${exportName}())\n`);
|
|
46
|
+
const { stdout } = await execFileAsync('npx', ['tsx', scriptPath], {
|
|
47
|
+
env: { ...process.env, HOME: homeDir },
|
|
48
|
+
timeout: 15_000,
|
|
49
|
+
});
|
|
50
|
+
return stdout.trim();
|
|
51
|
+
}
|
|
20
52
|
// ---------------------------------------------------------------------------
|
|
21
53
|
// Setup / teardown
|
|
22
54
|
// ---------------------------------------------------------------------------
|
|
23
55
|
let savedHome;
|
|
24
56
|
let savedInventoryDisable;
|
|
57
|
+
// SMI-5531: saved/restored so getOrCreateInstallId tests can assert
|
|
58
|
+
// unconditional generation under the EXACT conditions of a real end user's
|
|
59
|
+
// environment (both unset) without leaking into other tests/files.
|
|
60
|
+
let savedTelemetryEnabled;
|
|
61
|
+
let savedPostHogApiKey;
|
|
25
62
|
beforeEach(() => {
|
|
26
63
|
savedHome = process.env.HOME;
|
|
27
64
|
savedInventoryDisable = process.env.SKILLSMITH_INVENTORY_DISABLE;
|
|
65
|
+
savedTelemetryEnabled = process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
66
|
+
savedPostHogApiKey = process.env.POSTHOG_API_KEY;
|
|
28
67
|
// Point HOME at a fresh tmpdir so each test begins with a blank config
|
|
29
68
|
process.env.HOME = makeTempConfigDir();
|
|
30
69
|
delete process.env.SKILLSMITH_INVENTORY_DISABLE;
|
|
@@ -42,6 +81,18 @@ afterEach(() => {
|
|
|
42
81
|
else {
|
|
43
82
|
delete process.env.SKILLSMITH_INVENTORY_DISABLE;
|
|
44
83
|
}
|
|
84
|
+
if (savedTelemetryEnabled !== undefined) {
|
|
85
|
+
process.env.SKILLSMITH_TELEMETRY_ENABLED = savedTelemetryEnabled;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
89
|
+
}
|
|
90
|
+
if (savedPostHogApiKey !== undefined) {
|
|
91
|
+
process.env.POSTHOG_API_KEY = savedPostHogApiKey;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
delete process.env.POSTHOG_API_KEY;
|
|
95
|
+
}
|
|
45
96
|
});
|
|
46
97
|
// ---------------------------------------------------------------------------
|
|
47
98
|
// getOrCreateDeviceId
|
|
@@ -62,6 +113,112 @@ describe('getOrCreateDeviceId', () => {
|
|
|
62
113
|
});
|
|
63
114
|
});
|
|
64
115
|
// ---------------------------------------------------------------------------
|
|
116
|
+
// getOrCreateInstallId (SMI-5531)
|
|
117
|
+
//
|
|
118
|
+
// THE MOST IMPORTANT TEST SUITE IN THIS FILE: SMI-5531's root cause was that
|
|
119
|
+
// `context.distinctId` was only ever populated when BOTH
|
|
120
|
+
// SKILLSMITH_TELEMETRY_ENABLED=true AND a POSTHOG_API_KEY were present —
|
|
121
|
+
// conditions never true for a real end-user install. `getOrCreateInstallId`
|
|
122
|
+
// exists specifically to be immune to that. If these tests pass while a
|
|
123
|
+
// future refactor re-couples generation to that legacy gate, the fix is
|
|
124
|
+
// silently inert again under a new field name — the source-text guard test
|
|
125
|
+
// below is a second, independent line of defense against exactly that.
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
describe('getOrCreateInstallId — unconditional generation (the critical regression test)', () => {
|
|
128
|
+
it('generates and persists an id with SKILLSMITH_TELEMETRY_ENABLED unset and no POSTHOG_API_KEY present', () => {
|
|
129
|
+
delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
130
|
+
delete process.env.POSTHOG_API_KEY;
|
|
131
|
+
// Sanity-check the test itself actually reflects a real end user's
|
|
132
|
+
// environment before asserting anything about the function under test.
|
|
133
|
+
expect(process.env.SKILLSMITH_TELEMETRY_ENABLED).toBeUndefined();
|
|
134
|
+
expect(process.env.POSTHOG_API_KEY).toBeUndefined();
|
|
135
|
+
const id = getOrCreateInstallId();
|
|
136
|
+
expect(SHA256_HEX_RE.test(id)).toBe(true);
|
|
137
|
+
// Persisted, not just returned in memory — a fresh read must see it too.
|
|
138
|
+
expect(loadConfig().telemetry?.installId).toBe(id);
|
|
139
|
+
});
|
|
140
|
+
it('still generates unconditionally when SKILLSMITH_TELEMETRY_ENABLED is explicitly "false"', () => {
|
|
141
|
+
process.env.SKILLSMITH_TELEMETRY_ENABLED = 'false';
|
|
142
|
+
delete process.env.POSTHOG_API_KEY;
|
|
143
|
+
const id = getOrCreateInstallId();
|
|
144
|
+
expect(SHA256_HEX_RE.test(id)).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
it('is idempotent — second call returns the same id (no regeneration) with no env vars set', () => {
|
|
147
|
+
delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
148
|
+
delete process.env.POSTHOG_API_KEY;
|
|
149
|
+
const first = getOrCreateInstallId();
|
|
150
|
+
const second = getOrCreateInstallId();
|
|
151
|
+
expect(second).toBe(first);
|
|
152
|
+
});
|
|
153
|
+
it('generates a DIFFERENT id than getOrCreateDeviceId — distinct namespaces, not aliases', () => {
|
|
154
|
+
delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
155
|
+
delete process.env.POSTHOG_API_KEY;
|
|
156
|
+
const installId = getOrCreateInstallId();
|
|
157
|
+
const deviceId = getOrCreateDeviceId();
|
|
158
|
+
expect(installId).not.toBe(deviceId);
|
|
159
|
+
});
|
|
160
|
+
it('does not clobber an existing deviceId when creating an installId, and vice versa', () => {
|
|
161
|
+
delete process.env.SKILLSMITH_TELEMETRY_ENABLED;
|
|
162
|
+
delete process.env.POSTHOG_API_KEY;
|
|
163
|
+
const deviceId = getOrCreateDeviceId();
|
|
164
|
+
const installId = getOrCreateInstallId();
|
|
165
|
+
expect(getDeviceId()).toBe(deviceId);
|
|
166
|
+
expect(loadConfig().telemetry?.installId).toBe(installId);
|
|
167
|
+
});
|
|
168
|
+
it('the implementation never READS SKILLSMITH_TELEMETRY_ENABLED or POSTHOG_API_KEY (must stay decoupled from the legacy env-gated distinctId path)', () => {
|
|
169
|
+
// Checks for the actual `process.env.X` access pattern, not any textual
|
|
170
|
+
// mention — this module's own doc comments legitimately name these env
|
|
171
|
+
// vars in prose to explain why they're deliberately never read here.
|
|
172
|
+
const source = readFileSync(DEVICE_IDENTITY_SRC, 'utf-8');
|
|
173
|
+
expect(source).not.toMatch(/process\.env(\.|\[['"])SKILLSMITH_TELEMETRY_ENABLED/);
|
|
174
|
+
expect(source).not.toMatch(/process\.env(\.|\[['"])POSTHOG_API_KEY/);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// getOrCreateInstallId — cross-process concurrency (SMI-5531)
|
|
179
|
+
//
|
|
180
|
+
// getOrCreateDeviceId/getOrCreateInstallId are fully synchronous, so two
|
|
181
|
+
// "concurrent" calls WITHIN one Node process never truly interleave — real
|
|
182
|
+
// concurrency requires a real second OS process. These tests spawn genuine
|
|
183
|
+
// subprocesses (via tsx) pointed at the SAME HOME, so the underlying
|
|
184
|
+
// cross-process lock (config-atomic-write.ts) is actually exercised.
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
describe('getOrCreateInstallId — cross-process concurrency', () => {
|
|
187
|
+
it('two simultaneous calls from separate processes converge on the same id, with a valid (non-corrupted) config.json', async () => {
|
|
188
|
+
const sharedHome = makeTempConfigDir();
|
|
189
|
+
// No config.json exists yet — both processes race the "create" path.
|
|
190
|
+
const [idA, idB] = await Promise.all([
|
|
191
|
+
runInSubprocess('getOrCreateInstallId', sharedHome),
|
|
192
|
+
runInSubprocess('getOrCreateInstallId', sharedHome),
|
|
193
|
+
]);
|
|
194
|
+
expect(idA).toBe(idB);
|
|
195
|
+
expect(SHA256_HEX_RE.test(idA)).toBe(true);
|
|
196
|
+
const skillsmithDir = path.join(sharedHome, '.skillsmith');
|
|
197
|
+
const configPath = path.join(skillsmithDir, 'config.json');
|
|
198
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
199
|
+
const parsed = JSON.parse(raw); // throws if corrupted
|
|
200
|
+
expect(parsed.telemetry?.installId).toBe(idA);
|
|
201
|
+
// No stray lock/temp files left behind after both processes finished.
|
|
202
|
+
const entries = readdirSync(skillsmithDir);
|
|
203
|
+
expect(entries.some((f) => f.endsWith('.lock'))).toBe(false);
|
|
204
|
+
expect(entries.some((f) => f.endsWith('.tmp'))).toBe(false);
|
|
205
|
+
}, 20_000);
|
|
206
|
+
it('a concurrent saveConfig write for a sibling key is never dropped (no lost update)', async () => {
|
|
207
|
+
const sharedHome = makeTempConfigDir();
|
|
208
|
+
const [installId] = await Promise.all([
|
|
209
|
+
runInSubprocess('getOrCreateInstallId', sharedHome),
|
|
210
|
+
runInSubprocess('getOrCreateDeviceId', sharedHome),
|
|
211
|
+
]);
|
|
212
|
+
const configPath = path.join(sharedHome, '.skillsmith', 'config.json');
|
|
213
|
+
const parsed = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
214
|
+
// BOTH concurrently-written keys must be present — neither write may
|
|
215
|
+
// have silently clobbered the other's.
|
|
216
|
+
expect(parsed.telemetry?.installId).toBe(installId);
|
|
217
|
+
expect(SHA256_HEX_RE.test(parsed.telemetry?.installId ?? '')).toBe(true);
|
|
218
|
+
expect(UUID_RE.test(parsed.inventory?.deviceId ?? '')).toBe(true);
|
|
219
|
+
}, 20_000);
|
|
220
|
+
});
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
65
222
|
// getDeviceId (pure read)
|
|
66
223
|
// ---------------------------------------------------------------------------
|
|
67
224
|
describe('getDeviceId', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-identity.test.js","sourceRoot":"","sources":["../../../src/config/device-identity.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"device-identity.test.js","sourceRoot":"","sources":["../../../src/config/device-identity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACpE,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,8BAA8B,EAC9B,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,GACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;AAEzC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,MAAM,OAAO,GAAG,iEAAiE,CAAA;AACjF,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAEvC,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC,IAAI,CACd,EAAE,CAAC,MAAM,EAAE,EACX,0BAA0B,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAC9E,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAE3F;;;;;;;GAOG;AACH,KAAK,UAAU,eAAe,CAAC,UAAkB,EAAE,OAAe;IAChE,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAA;IAChF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAClD,aAAa,CACX,UAAU,EACV,YAAY,UAAU,WAAW,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI;QACtE,wBAAwB,UAAU,OAAO,CAC5C,CAAA;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE;QACjE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE;QACtC,OAAO,EAAE,MAAM;KAChB,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;AACtB,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,IAAI,SAA6B,CAAA;AACjC,IAAI,qBAAyC,CAAA;AAC7C,oEAAoE;AACpE,2EAA2E;AAC3E,mEAAmE;AACnE,IAAI,qBAAyC,CAAA;AAC7C,IAAI,kBAAsC,CAAA;AAE1C,UAAU,CAAC,GAAG,EAAE;IACd,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAA;IAC5B,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;IAChE,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;IAChE,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAChD,uEAAuE;IACvE,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,iBAAiB,EAAE,CAAA;IACtC,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;AACjD,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,SAAS,CAAA;IAC9B,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAA;IACzB,CAAC;IACD,IAAI,qBAAqB,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,qBAAqB,CAAA;IAClE,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;IACjD,CAAC;IACD,IAAI,qBAAqB,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,qBAAqB,CAAA;IAClE,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;IACjD,CAAC;IACD,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,kBAAkB,CAAA;IAClD,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACpC,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAA;QACnC,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAA;QACpC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,kCAAkC;AAClC,EAAE;AACF,6EAA6E;AAC7E,yDAAyD;AACzD,yEAAyE;AACzE,4EAA4E;AAC5E,wEAAwE;AACxE,wEAAwE;AACxE,2EAA2E;AAC3E,uEAAuE;AACvE,8EAA8E;AAE9E,QAAQ,CAAC,gFAAgF,EAAE,GAAG,EAAE;IAC9F,EAAE,CAAC,qGAAqG,EAAE,GAAG,EAAE;QAC7G,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;QAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;QAClC,mEAAmE;QACnE,uEAAuE;QACvE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,aAAa,EAAE,CAAA;QAChE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,CAAA;QAEnD,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAA;QAEjC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACzC,yEAAyE;QACzE,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yFAAyF,EAAE,GAAG,EAAE;QACjG,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,OAAO,CAAA;QAClD,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;QAElC,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAA;QAEjC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;QAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;QAElC,MAAM,KAAK,GAAG,oBAAoB,EAAE,CAAA;QACpC,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;QAErC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC9F,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;QAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;QAElC,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAA;QACxC,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAA;QAEtC,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;QAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;QAElC,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAA;QACtC,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAA;QAExC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACpC,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gJAAgJ,EAAE,GAAG,EAAE;QACxJ,wEAAwE;QACxE,uEAAuE;QACvE,qEAAqE;QACrE,MAAM,MAAM,GAAG,YAAY,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAA;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,qDAAqD,CAAC,CAAA;QACjF,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,8DAA8D;AAC9D,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,qEAAqE;AACrE,qEAAqE;AACrE,8EAA8E;AAE9E,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAChE,EAAE,CAAC,kHAAkH,EAAE,KAAK,IAAI,EAAE;QAChI,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAA;QACtC,qEAAqE;QAErE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACnC,eAAe,CAAC,sBAAsB,EAAE,UAAU,CAAC;YACnD,eAAe,CAAC,sBAAsB,EAAE,UAAU,CAAC;SACpD,CAAC,CAAA;QAEF,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACrB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE1C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;QAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;QAC1D,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2C,CAAA,CAAC,sBAAsB;QAC/F,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAE7C,sEAAsE;QACtE,MAAM,OAAO,GAAG,WAAW,CAAC,aAAa,CAAC,CAAA;QAC1C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5D,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7D,CAAC,EAAE,MAAM,CAAC,CAAA;IAEV,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;QACjG,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAA;QAEtC,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpC,eAAe,CAAC,sBAAsB,EAAE,UAAU,CAAC;YACnD,eAAe,CAAC,qBAAqB,EAAE,UAAU,CAAC;SACnD,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,aAAa,CAAC,CAAA;QACtE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAG1D,CAAA;QAED,qEAAqE;QACrE,uCAAuC;QACvC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACnD,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnE,CAAC,EAAE,MAAM,CAAC,CAAA;AACZ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,aAAa,EAAE,CAAA;IACvC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,mBAAmB,EAAE,CAAA;QACrB,YAAY,EAAE,CAAA;QACd,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,aAAa,EAAE,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAA;QACpC,YAAY,EAAE,CAAA;QACd,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAA;QACnC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,cAAc,CAAC,WAAW,CAAC,CAAA;QAC3B,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,cAAc,CAAC,WAAW,CAAC,CAAA;QAC3B,cAAc,CAAC,SAAS,CAAC,CAAA;QACzB,iCAAiC;QACjC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACnC,mBAAmB,CAAC,EAAE,CAAC,CAAA;QACvB,cAAc,CAAC,cAAc,CAAC,CAAA;QAC9B,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,iCAAiC;AACjC,8EAA8E;AAE9E,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,OAAO,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAA;QAC/C,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,GAAG,CAAA;QAC9C,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,MAAM,CAAA;QACjD,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,MAAM,CAAA;QACjD,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,OAAO,CAAA;QAClD,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,4BAA4B,GAAG,GAAG,CAAA;QAC9C,MAAM,CAAC,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IAClC,MAAM,mBAAmB,GAAG,EAAE,GAAG,WAAW,CAAA;IAE5C,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC,WAAW,EAAE,CAAA;QAC5D,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,kBAAkB,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,CAAC,CAAC,WAAW,EAAE,CAAA;QACzE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,mEAAmE;QACnE,MAAM,QAAQ,GAAG,iBAAiB,CAAA;QAClC,MAAM,cAAc,GAAG,UAAU,CAAA,CAAC,aAAa;QAC/C,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC,CAAC,WAAW,EAAE,CAAA;QACvE,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;QAClE,+DAA+D;QAC/D,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACrE,2DAA2D;QAC3D,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,sCAAsC;QACtC,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,mBAAmB,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QAC/E,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnD,uCAAuC;QACvC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,mBAAmB,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QAChF,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC5D,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,aAAa,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACnC,mBAAmB,CAAC,EAAE,CAAC,CAAA;QACvB,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAA;QAChC,mBAAmB,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA;QAC7C,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;QACvD,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACvC,mBAAmB,CAAC,KAAK,CAAC,CAAA;QAC1B,mBAAmB,CAAC,MAAM,CAAC,CAAA;QAC3B,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -44,6 +44,12 @@ export interface SkillsmithConfig {
|
|
|
44
44
|
telemetry?: {
|
|
45
45
|
/** Enable telemetry (default: false, opt-in) */
|
|
46
46
|
enabled?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Stable per-install identifier (SMI-5531): `sha256(randomUUID())`,
|
|
49
|
+
* generated unconditionally, decoupled from `SKILLSMITH_TELEMETRY_ENABLED`
|
|
50
|
+
* / `POSTHOG_API_KEY`. See `getOrCreateInstallId()` in `./device-identity.ts`.
|
|
51
|
+
*/
|
|
52
|
+
installId?: string;
|
|
47
53
|
};
|
|
48
54
|
/** Sync settings */
|
|
49
55
|
sync?: {
|
|
@@ -113,6 +119,14 @@ export declare function loadConfig(): SkillsmithConfig;
|
|
|
113
119
|
* Save configuration to ~/.skillsmith/config.json
|
|
114
120
|
* Creates the file with 0600 permissions (owner read/write only)
|
|
115
121
|
*
|
|
122
|
+
* SMI-5531: serialized under a cross-process lock ({@link acquireConfigLock})
|
|
123
|
+
* and written atomically (temp-file + rename, {@link atomicWriteFile}) —
|
|
124
|
+
* closes a lost-update race (two concurrent writers each dropping the
|
|
125
|
+
* other's key) and a torn-write race under a bare `writeFileSync`.
|
|
126
|
+
* `existingConfig` is (re-)read AFTER the lock is acquired, not before, or a
|
|
127
|
+
* writer that read stale state while waiting would reintroduce the same
|
|
128
|
+
* lost-update bug.
|
|
129
|
+
*
|
|
116
130
|
* @param config - Configuration to save (merged with existing)
|
|
117
131
|
* @param options - Save options
|
|
118
132
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,yBAAyB;IACzB,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;;WAIG;QACH,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,oBAAoB;IACpB,IAAI,CAAC,EAAE;QACL,6BAA6B;QAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,oCAAoC;QACpC,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,qDAAqD;IACrD,SAAS,CAAC,EAAE;QACV,gDAAgD;QAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,yCAAyC;QACzC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gFAAgF;QAChF,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,sDAAsD;IACtD,KAAK,CAAC,EAAE;QACN,kFAAkF;QAClF,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AA0DD;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAUpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAc7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAoB,GAC7C,IAAI,CAyCN;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAU9C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,SAA+B,GAAG,MAAM,CAQ/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAOxC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAU5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGxD;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/D;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAgCD;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAC7C,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;CAC9C,CAAC,CA2CD"}
|
package/dist/src/config/index.js
CHANGED
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { homedir } from 'os';
|
|
34
34
|
import { join } from 'path';
|
|
35
|
-
import { existsSync, readFileSync,
|
|
35
|
+
import { existsSync, readFileSync, mkdirSync, chmodSync } from 'fs';
|
|
36
|
+
import { acquireConfigLock, atomicWriteFile } from './config-atomic-write.js';
|
|
36
37
|
/** Default config directory name */
|
|
37
38
|
const CONFIG_DIR = '.skillsmith';
|
|
38
39
|
/** Default config file name */
|
|
@@ -139,28 +140,43 @@ export function loadConfig() {
|
|
|
139
140
|
* Save configuration to ~/.skillsmith/config.json
|
|
140
141
|
* Creates the file with 0600 permissions (owner read/write only)
|
|
141
142
|
*
|
|
143
|
+
* SMI-5531: serialized under a cross-process lock ({@link acquireConfigLock})
|
|
144
|
+
* and written atomically (temp-file + rename, {@link atomicWriteFile}) —
|
|
145
|
+
* closes a lost-update race (two concurrent writers each dropping the
|
|
146
|
+
* other's key) and a torn-write race under a bare `writeFileSync`.
|
|
147
|
+
* `existingConfig` is (re-)read AFTER the lock is acquired, not before, or a
|
|
148
|
+
* writer that read stale state while waiting would reintroduce the same
|
|
149
|
+
* lost-update bug.
|
|
150
|
+
*
|
|
142
151
|
* @param config - Configuration to save (merged with existing)
|
|
143
152
|
* @param options - Save options
|
|
144
153
|
*/
|
|
145
154
|
export function saveConfig(config, options = { merge: true }) {
|
|
146
155
|
ensureConfigDir();
|
|
147
156
|
const configPath = getConfigPath();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
existingConfig =
|
|
157
|
+
const release = acquireConfigLock(configPath);
|
|
158
|
+
try {
|
|
159
|
+
let existingConfig = {};
|
|
160
|
+
if (options.merge && existsSync(configPath)) {
|
|
161
|
+
existingConfig = loadConfig();
|
|
162
|
+
}
|
|
163
|
+
// Remove undefined values so they are omitted from JSON output
|
|
164
|
+
const updates = Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined));
|
|
165
|
+
// Explicit undefined fields are deletions — remove them from existing config
|
|
166
|
+
const deletions = Object.keys(config).filter((k) => config[k] === undefined);
|
|
167
|
+
const cleaned = { ...existingConfig };
|
|
168
|
+
for (const key of deletions) {
|
|
169
|
+
delete cleaned[key];
|
|
170
|
+
}
|
|
171
|
+
const mergedConfig = { ...cleaned, ...updates };
|
|
172
|
+
const configJson = JSON.stringify(mergedConfig, null, 2);
|
|
173
|
+
atomicWriteFile(configPath, configJson, 0o600);
|
|
151
174
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Explicit undefined fields are deletions — remove them from existing config
|
|
155
|
-
const deletions = Object.keys(config).filter((k) => config[k] === undefined);
|
|
156
|
-
const cleaned = { ...existingConfig };
|
|
157
|
-
for (const key of deletions) {
|
|
158
|
-
delete cleaned[key];
|
|
175
|
+
finally {
|
|
176
|
+
release();
|
|
159
177
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
writeFileSync(configPath, configJson, { encoding: 'utf-8', mode: 0o600 });
|
|
163
|
-
// Ensure permissions are set correctly (in case file existed)
|
|
178
|
+
// Ensure permissions are set correctly (belt-and-braces — atomicWriteFile
|
|
179
|
+
// already chmods the file itself; in case the file existed pre-migration).
|
|
164
180
|
try {
|
|
165
181
|
chmodSync(configPath, 0o600);
|
|
166
182
|
}
|