contribute-now 0.7.0-dev.3b89c66 → 0.7.0-dev.45c1376
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1649 -128
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1298,6 +1298,579 @@ var init_formatter = __esm(() => {
|
|
|
1298
1298
|
init_message_formatter();
|
|
1299
1299
|
});
|
|
1300
1300
|
|
|
1301
|
+
// node_modules/@wgtechlabs/secrets-engine/dist/index.js
|
|
1302
|
+
var exports_dist = {};
|
|
1303
|
+
__export(exports_dist, {
|
|
1304
|
+
SecurityError: () => SecurityError,
|
|
1305
|
+
SecretsEngineError: () => SecretsEngineError,
|
|
1306
|
+
SecretsEngine: () => SecretsEngine,
|
|
1307
|
+
KeyNotFoundError: () => KeyNotFoundError,
|
|
1308
|
+
IntegrityError: () => IntegrityError,
|
|
1309
|
+
InitializationError: () => InitializationError,
|
|
1310
|
+
DecryptionError: () => DecryptionError
|
|
1311
|
+
});
|
|
1312
|
+
import { readdir, rm, unlink } from "fs/promises";
|
|
1313
|
+
import { join as join3 } from "path";
|
|
1314
|
+
import {
|
|
1315
|
+
createCipheriv,
|
|
1316
|
+
createDecipheriv,
|
|
1317
|
+
createHash,
|
|
1318
|
+
createHmac,
|
|
1319
|
+
randomBytes,
|
|
1320
|
+
scryptSync
|
|
1321
|
+
} from "crypto";
|
|
1322
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1323
|
+
import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
1324
|
+
import { homedir, hostname, networkInterfaces, userInfo } from "os";
|
|
1325
|
+
import { join as join4 } from "path";
|
|
1326
|
+
import { Database } from "bun:sqlite";
|
|
1327
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1328
|
+
import { join as join22 } from "path";
|
|
1329
|
+
function deriveMasterKey(machineId, keyfile, salt) {
|
|
1330
|
+
const password = Buffer.concat([Buffer.from(machineId, "utf-8"), keyfile]);
|
|
1331
|
+
return scryptSync(password, salt, CONSTANTS.KEY_LENGTH, {
|
|
1332
|
+
N: CONSTANTS.SCRYPT_N,
|
|
1333
|
+
r: CONSTANTS.SCRYPT_R,
|
|
1334
|
+
p: CONSTANTS.SCRYPT_P,
|
|
1335
|
+
maxmem: 256 * CONSTANTS.SCRYPT_N * CONSTANTS.SCRYPT_R
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
function encrypt(masterKey, plaintext) {
|
|
1339
|
+
const iv = randomBytes(CONSTANTS.IV_LENGTH);
|
|
1340
|
+
const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
|
|
1341
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
1342
|
+
const authTag = cipher.getAuthTag();
|
|
1343
|
+
return {
|
|
1344
|
+
iv,
|
|
1345
|
+
ciphertext: Buffer.concat([encrypted, authTag])
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function decrypt(masterKey, iv, ciphertext, keyHash) {
|
|
1349
|
+
if (ciphertext.length < CONSTANTS.AUTH_TAG_LENGTH) {
|
|
1350
|
+
throw new DecryptionError("Ciphertext too short to contain auth tag", keyHash);
|
|
1351
|
+
}
|
|
1352
|
+
const authTag = ciphertext.subarray(ciphertext.length - CONSTANTS.AUTH_TAG_LENGTH);
|
|
1353
|
+
const encrypted = ciphertext.subarray(0, ciphertext.length - CONSTANTS.AUTH_TAG_LENGTH);
|
|
1354
|
+
try {
|
|
1355
|
+
const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
|
|
1356
|
+
decipher.setAuthTag(authTag);
|
|
1357
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf-8");
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
throw new DecryptionError(error instanceof Error ? error.message : "Unknown decryption error", keyHash);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function hmac(masterKey, data) {
|
|
1363
|
+
return createHmac("sha256", masterKey).update(data).digest("hex");
|
|
1364
|
+
}
|
|
1365
|
+
function sha256(data) {
|
|
1366
|
+
return createHash("sha256").update(data).digest();
|
|
1367
|
+
}
|
|
1368
|
+
function generateSalt() {
|
|
1369
|
+
return randomBytes(CONSTANTS.SALT_LENGTH);
|
|
1370
|
+
}
|
|
1371
|
+
function matchGlob(pattern, key) {
|
|
1372
|
+
const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]*");
|
|
1373
|
+
const regex2 = new RegExp(`^${regexStr}$`);
|
|
1374
|
+
return regex2.test(key);
|
|
1375
|
+
}
|
|
1376
|
+
function filterKeys(keys, pattern) {
|
|
1377
|
+
return keys.filter((key) => matchGlob(pattern, key));
|
|
1378
|
+
}
|
|
1379
|
+
function resolveStoragePath(options) {
|
|
1380
|
+
if (options?.path) {
|
|
1381
|
+
return options.path;
|
|
1382
|
+
}
|
|
1383
|
+
if (options?.location === "xdg") {
|
|
1384
|
+
return resolveXdgPath();
|
|
1385
|
+
}
|
|
1386
|
+
if (process.platform !== "win32" && process.env.XDG_CONFIG_HOME) {
|
|
1387
|
+
return join4(process.env.XDG_CONFIG_HOME, "secrets-engine");
|
|
1388
|
+
}
|
|
1389
|
+
return join4(homedir(), CONSTANTS.DIR_NAME);
|
|
1390
|
+
}
|
|
1391
|
+
function resolveXdgPath() {
|
|
1392
|
+
if (process.platform === "win32") {
|
|
1393
|
+
const appData = process.env.APPDATA;
|
|
1394
|
+
if (!appData) {
|
|
1395
|
+
throw new InitializationError("APPDATA environment variable is not set");
|
|
1396
|
+
}
|
|
1397
|
+
return join4(appData, "secrets-engine");
|
|
1398
|
+
}
|
|
1399
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join4(homedir(), ".config");
|
|
1400
|
+
return join4(xdgConfig, "secrets-engine");
|
|
1401
|
+
}
|
|
1402
|
+
async function ensureDirectory(dirPath) {
|
|
1403
|
+
try {
|
|
1404
|
+
await mkdir(dirPath, { recursive: true, mode: EXPECTED_MODES.directory });
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
throw new InitializationError(`Cannot create storage directory at "${dirPath}"`, error);
|
|
1407
|
+
}
|
|
1408
|
+
if (process.platform !== "win32") {
|
|
1409
|
+
await verifyPermission(dirPath, EXPECTED_MODES.directory, "directory");
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async function verifyPermission(filePath, expectedMode, label) {
|
|
1413
|
+
if (process.platform === "win32") {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const fileStat = await stat(filePath);
|
|
1417
|
+
const actualMode = fileStat.mode & 511;
|
|
1418
|
+
if (actualMode !== expectedMode) {
|
|
1419
|
+
throw new SecurityError(`Insecure ${label} permissions`, formatOctal(expectedMode), formatOctal(actualMode), filePath);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function formatOctal(mode) {
|
|
1423
|
+
return `0o${mode.toString(8).padStart(3, "0")}`;
|
|
1424
|
+
}
|
|
1425
|
+
function getMachineIdentity() {
|
|
1426
|
+
const host = hostname();
|
|
1427
|
+
const mac = getPrimaryMac();
|
|
1428
|
+
const user = userInfo().username;
|
|
1429
|
+
return `${host}:${mac}:${user}`;
|
|
1430
|
+
}
|
|
1431
|
+
function getPrimaryMac() {
|
|
1432
|
+
const interfaces = networkInterfaces();
|
|
1433
|
+
for (const entries of Object.values(interfaces)) {
|
|
1434
|
+
if (!entries)
|
|
1435
|
+
continue;
|
|
1436
|
+
for (const entry of entries) {
|
|
1437
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
1438
|
+
return entry.mac;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return "no-mac-available";
|
|
1443
|
+
}
|
|
1444
|
+
async function ensureKeyfile(dirPath) {
|
|
1445
|
+
const keyfilePath = join4(dirPath, CONSTANTS.KEYFILE_NAME);
|
|
1446
|
+
try {
|
|
1447
|
+
if (process.platform !== "win32") {
|
|
1448
|
+
await verifyPermission(keyfilePath, EXPECTED_MODES.keyfile, "keyfile");
|
|
1449
|
+
}
|
|
1450
|
+
return await readFile(keyfilePath);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
if (isFileNotFoundError(error)) {
|
|
1453
|
+
return await createKeyfile(keyfilePath);
|
|
1454
|
+
}
|
|
1455
|
+
throw error;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
async function createKeyfile(keyfilePath) {
|
|
1459
|
+
const { randomBytes: randomBytes2 } = await import("crypto");
|
|
1460
|
+
const keyfileData = randomBytes2(CONSTANTS.KEYFILE_LENGTH);
|
|
1461
|
+
await writeFile(keyfilePath, keyfileData, { mode: EXPECTED_MODES.keyfile });
|
|
1462
|
+
if (process.platform !== "win32") {
|
|
1463
|
+
await chmod(keyfilePath, EXPECTED_MODES.keyfile);
|
|
1464
|
+
}
|
|
1465
|
+
return keyfileData;
|
|
1466
|
+
}
|
|
1467
|
+
async function readMetaFile(dirPath) {
|
|
1468
|
+
const metaPath = join4(dirPath, CONSTANTS.META_NAME);
|
|
1469
|
+
try {
|
|
1470
|
+
return await readFile(metaPath, "utf-8");
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
if (isFileNotFoundError(error)) {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
async function writeMetaFile(dirPath, content) {
|
|
1479
|
+
const metaPath = join4(dirPath, CONSTANTS.META_NAME);
|
|
1480
|
+
await writeFile(metaPath, content, { mode: EXPECTED_MODES.meta });
|
|
1481
|
+
if (process.platform !== "win32") {
|
|
1482
|
+
await chmod(metaPath, EXPECTED_MODES.meta);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function isFileNotFoundError(error) {
|
|
1486
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1487
|
+
}
|
|
1488
|
+
async function computeIntegrityHmac(masterKey, dbFilePath, checkpointFn) {
|
|
1489
|
+
if (checkpointFn) {
|
|
1490
|
+
try {
|
|
1491
|
+
checkpointFn();
|
|
1492
|
+
} catch (err) {
|
|
1493
|
+
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
1494
|
+
throw new IntegrityError(`Integrity checkpoint failed: ${originalMessage}`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
const dbBytes = Buffer.from(await readFile2(dbFilePath));
|
|
1498
|
+
const dbHash = sha256(dbBytes);
|
|
1499
|
+
return hmac(masterKey, dbHash);
|
|
1500
|
+
}
|
|
1501
|
+
async function verifyIntegrity(masterKey, dbFilePath, dirPath, checkpointFn) {
|
|
1502
|
+
const metaRaw = await readMetaFile(dirPath);
|
|
1503
|
+
if (!metaRaw) {
|
|
1504
|
+
throw new IntegrityError("Metadata file (meta.json) is missing");
|
|
1505
|
+
}
|
|
1506
|
+
let meta;
|
|
1507
|
+
try {
|
|
1508
|
+
meta = JSON.parse(metaRaw);
|
|
1509
|
+
} catch {
|
|
1510
|
+
throw new IntegrityError("Metadata file (meta.json) is corrupted");
|
|
1511
|
+
}
|
|
1512
|
+
if (meta.version !== CONSTANTS.STORE_VERSION) {
|
|
1513
|
+
throw new IntegrityError(`Unsupported store version: expected "${CONSTANTS.STORE_VERSION}", got "${meta.version}"`);
|
|
1514
|
+
}
|
|
1515
|
+
const computedHmac = await computeIntegrityHmac(masterKey, dbFilePath, checkpointFn);
|
|
1516
|
+
if (computedHmac !== meta.integrity) {
|
|
1517
|
+
throw new IntegrityError;
|
|
1518
|
+
}
|
|
1519
|
+
return meta;
|
|
1520
|
+
}
|
|
1521
|
+
async function updateIntegrity(masterKey, dbFilePath, dirPath, salt, checkpointFn) {
|
|
1522
|
+
const integrity = await computeIntegrityHmac(masterKey, dbFilePath, checkpointFn);
|
|
1523
|
+
const meta = {
|
|
1524
|
+
version: CONSTANTS.STORE_VERSION,
|
|
1525
|
+
salt,
|
|
1526
|
+
integrity
|
|
1527
|
+
};
|
|
1528
|
+
await writeMetaFile(dirPath, JSON.stringify(meta, null, 2));
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
class SecretStore {
|
|
1532
|
+
db;
|
|
1533
|
+
constructor(db) {
|
|
1534
|
+
this.db = db;
|
|
1535
|
+
}
|
|
1536
|
+
static open(dirPath) {
|
|
1537
|
+
const dbPath = join22(dirPath, CONSTANTS.DB_NAME);
|
|
1538
|
+
try {
|
|
1539
|
+
const db = new Database(dbPath, { create: true });
|
|
1540
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
1541
|
+
db.exec("PRAGMA foreign_keys = ON;");
|
|
1542
|
+
db.exec("PRAGMA busy_timeout = 5000;");
|
|
1543
|
+
db.exec(CREATE_SECRETS_TABLE);
|
|
1544
|
+
db.exec(CREATE_META_TABLE);
|
|
1545
|
+
return new SecretStore(db);
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
throw new InitializationError(`Cannot open database at "${dbPath}"`, error);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
upsert(entry) {
|
|
1551
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1552
|
+
const stmt = this.db.prepare(`
|
|
1553
|
+
INSERT INTO secrets (key_hash, key_enc, iv, cipher, created, updated)
|
|
1554
|
+
VALUES ($key_hash, $key_enc, $iv, $cipher, $created, $updated)
|
|
1555
|
+
ON CONFLICT(key_hash) DO UPDATE SET
|
|
1556
|
+
key_enc = excluded.key_enc,
|
|
1557
|
+
iv = excluded.iv,
|
|
1558
|
+
cipher = excluded.cipher,
|
|
1559
|
+
updated = excluded.updated
|
|
1560
|
+
`);
|
|
1561
|
+
stmt.run({
|
|
1562
|
+
$key_hash: entry.key_hash,
|
|
1563
|
+
$key_enc: entry.key_enc,
|
|
1564
|
+
$iv: entry.iv,
|
|
1565
|
+
$cipher: entry.cipher,
|
|
1566
|
+
$created: now,
|
|
1567
|
+
$updated: now
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
findByHash(keyHash) {
|
|
1571
|
+
const stmt = this.db.prepare("SELECT key_hash, key_enc, iv, cipher, created, updated FROM secrets WHERE key_hash = ?");
|
|
1572
|
+
const row = stmt.get(keyHash);
|
|
1573
|
+
return row ?? null;
|
|
1574
|
+
}
|
|
1575
|
+
findAll() {
|
|
1576
|
+
const stmt = this.db.prepare("SELECT key_hash, key_enc, iv, cipher, created, updated FROM secrets");
|
|
1577
|
+
return stmt.all();
|
|
1578
|
+
}
|
|
1579
|
+
deleteByHash(keyHash) {
|
|
1580
|
+
const stmt = this.db.prepare("DELETE FROM secrets WHERE key_hash = ?");
|
|
1581
|
+
const result = stmt.run(keyHash);
|
|
1582
|
+
return result.changes > 0;
|
|
1583
|
+
}
|
|
1584
|
+
deleteAll() {
|
|
1585
|
+
this.db.exec("DELETE FROM secrets");
|
|
1586
|
+
}
|
|
1587
|
+
count() {
|
|
1588
|
+
const stmt = this.db.prepare("SELECT COUNT(*) as count FROM secrets");
|
|
1589
|
+
const row = stmt.get();
|
|
1590
|
+
return row.count;
|
|
1591
|
+
}
|
|
1592
|
+
async readRawBytes() {
|
|
1593
|
+
return Buffer.from(await readFile3(this.db.filename));
|
|
1594
|
+
}
|
|
1595
|
+
get filePath() {
|
|
1596
|
+
return this.db.filename;
|
|
1597
|
+
}
|
|
1598
|
+
checkpoint() {
|
|
1599
|
+
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
1600
|
+
}
|
|
1601
|
+
close() {
|
|
1602
|
+
this.db.close();
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
class SecretsEngine {
|
|
1607
|
+
masterKey;
|
|
1608
|
+
store;
|
|
1609
|
+
dirPath;
|
|
1610
|
+
salt;
|
|
1611
|
+
keyIndex = new Map;
|
|
1612
|
+
closed = false;
|
|
1613
|
+
constructor(masterKey, store, dirPath, salt) {
|
|
1614
|
+
this.masterKey = masterKey;
|
|
1615
|
+
this.store = store;
|
|
1616
|
+
this.dirPath = dirPath;
|
|
1617
|
+
this.salt = salt;
|
|
1618
|
+
}
|
|
1619
|
+
static async open(options) {
|
|
1620
|
+
const dirPath = resolveStoragePath(options);
|
|
1621
|
+
await ensureDirectory(dirPath);
|
|
1622
|
+
const keyfile = await ensureKeyfile(dirPath);
|
|
1623
|
+
const { salt, isNewStore } = await resolveSalt(dirPath);
|
|
1624
|
+
const machineId = getMachineIdentity();
|
|
1625
|
+
const masterKey = deriveMasterKey(machineId, keyfile, Buffer.from(salt, "hex"));
|
|
1626
|
+
const store = SecretStore.open(dirPath);
|
|
1627
|
+
try {
|
|
1628
|
+
if (!isNewStore) {
|
|
1629
|
+
await verifyIntegrity(masterKey, store.filePath, dirPath, () => store.checkpoint());
|
|
1630
|
+
}
|
|
1631
|
+
const engine = new SecretsEngine(masterKey, store, dirPath, salt);
|
|
1632
|
+
engine.buildKeyIndex();
|
|
1633
|
+
if (isNewStore) {
|
|
1634
|
+
await updateIntegrity(masterKey, store.filePath, dirPath, salt, () => store.checkpoint());
|
|
1635
|
+
}
|
|
1636
|
+
return engine;
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
try {
|
|
1639
|
+
store.close();
|
|
1640
|
+
} catch {}
|
|
1641
|
+
throw error;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
async get(key) {
|
|
1645
|
+
this.ensureOpen();
|
|
1646
|
+
const keyHash = this.hashKey(key);
|
|
1647
|
+
const entry = this.store.findByHash(keyHash);
|
|
1648
|
+
if (!entry) {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
return decrypt(this.masterKey, Buffer.from(entry.iv), Buffer.from(entry.cipher), keyHash);
|
|
1652
|
+
}
|
|
1653
|
+
async getOrThrow(key) {
|
|
1654
|
+
const value = await this.get(key);
|
|
1655
|
+
if (value === null) {
|
|
1656
|
+
throw new KeyNotFoundError(key);
|
|
1657
|
+
}
|
|
1658
|
+
return value;
|
|
1659
|
+
}
|
|
1660
|
+
async set(key, value) {
|
|
1661
|
+
this.ensureOpen();
|
|
1662
|
+
const keyHash = this.hashKey(key);
|
|
1663
|
+
const encryptedKey = encrypt(this.masterKey, key);
|
|
1664
|
+
const encryptedValue = encrypt(this.masterKey, value);
|
|
1665
|
+
const keyEncPacked = Buffer.concat([encryptedKey.iv, encryptedKey.ciphertext]);
|
|
1666
|
+
this.store.upsert({
|
|
1667
|
+
key_hash: keyHash,
|
|
1668
|
+
key_enc: keyEncPacked,
|
|
1669
|
+
iv: encryptedValue.iv,
|
|
1670
|
+
cipher: encryptedValue.ciphertext
|
|
1671
|
+
});
|
|
1672
|
+
this.keyIndex.set(keyHash, key);
|
|
1673
|
+
await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt, () => this.store.checkpoint());
|
|
1674
|
+
}
|
|
1675
|
+
async has(key) {
|
|
1676
|
+
this.ensureOpen();
|
|
1677
|
+
return this.keyIndex.has(this.hashKey(key));
|
|
1678
|
+
}
|
|
1679
|
+
async delete(key) {
|
|
1680
|
+
this.ensureOpen();
|
|
1681
|
+
const keyHash = this.hashKey(key);
|
|
1682
|
+
const deleted = this.store.deleteByHash(keyHash);
|
|
1683
|
+
if (deleted) {
|
|
1684
|
+
this.keyIndex.delete(keyHash);
|
|
1685
|
+
await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt, () => this.store.checkpoint());
|
|
1686
|
+
}
|
|
1687
|
+
return deleted;
|
|
1688
|
+
}
|
|
1689
|
+
async keys(pattern) {
|
|
1690
|
+
this.ensureOpen();
|
|
1691
|
+
const allKeys = Array.from(this.keyIndex.values());
|
|
1692
|
+
if (!pattern) {
|
|
1693
|
+
return allKeys.sort();
|
|
1694
|
+
}
|
|
1695
|
+
return filterKeys(allKeys, pattern).sort();
|
|
1696
|
+
}
|
|
1697
|
+
async destroy() {
|
|
1698
|
+
this.ensureOpen();
|
|
1699
|
+
this.store.checkpoint();
|
|
1700
|
+
this.store.close();
|
|
1701
|
+
this.keyIndex.clear();
|
|
1702
|
+
this.closed = true;
|
|
1703
|
+
await new Promise((resolve3) => setTimeout(resolve3, 150));
|
|
1704
|
+
await removeDirectoryContents(this.dirPath);
|
|
1705
|
+
}
|
|
1706
|
+
async close() {
|
|
1707
|
+
if (!this.closed) {
|
|
1708
|
+
try {
|
|
1709
|
+
this.store.checkpoint();
|
|
1710
|
+
await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt);
|
|
1711
|
+
} finally {
|
|
1712
|
+
this.store.close();
|
|
1713
|
+
this.keyIndex.clear();
|
|
1714
|
+
this.closed = true;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
get size() {
|
|
1719
|
+
this.ensureOpen();
|
|
1720
|
+
return this.keyIndex.size;
|
|
1721
|
+
}
|
|
1722
|
+
get storagePath() {
|
|
1723
|
+
return this.dirPath;
|
|
1724
|
+
}
|
|
1725
|
+
buildKeyIndex() {
|
|
1726
|
+
const entries = this.store.findAll();
|
|
1727
|
+
for (const entry of entries) {
|
|
1728
|
+
try {
|
|
1729
|
+
const keyEncBuf = Buffer.from(entry.key_enc);
|
|
1730
|
+
const keyIv = keyEncBuf.subarray(0, 12);
|
|
1731
|
+
const keyCipher = keyEncBuf.subarray(12);
|
|
1732
|
+
const keyName = decrypt(this.masterKey, keyIv, keyCipher, entry.key_hash);
|
|
1733
|
+
this.keyIndex.set(entry.key_hash, keyName);
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
if (error instanceof DecryptionError) {
|
|
1736
|
+
console.warn(`[secrets-engine] Skipping corrupted entry: ${entry.key_hash.slice(0, 16)}…`);
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
hashKey(key) {
|
|
1744
|
+
return hmac(this.masterKey, key);
|
|
1745
|
+
}
|
|
1746
|
+
ensureOpen() {
|
|
1747
|
+
if (this.closed) {
|
|
1748
|
+
throw new Error("SecretsEngine instance is closed");
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function resolveSalt(dirPath) {
|
|
1753
|
+
const metaRaw = await readMetaFile(dirPath);
|
|
1754
|
+
if (metaRaw) {
|
|
1755
|
+
try {
|
|
1756
|
+
const meta = JSON.parse(metaRaw);
|
|
1757
|
+
if (meta.salt) {
|
|
1758
|
+
return { salt: meta.salt, isNewStore: false };
|
|
1759
|
+
}
|
|
1760
|
+
} catch {}
|
|
1761
|
+
}
|
|
1762
|
+
const salt = generateSalt().toString("hex");
|
|
1763
|
+
return { salt, isNewStore: true };
|
|
1764
|
+
}
|
|
1765
|
+
async function removeDirectoryContents(dirPath) {
|
|
1766
|
+
const maxRetries = 5;
|
|
1767
|
+
const retryDelay = 200;
|
|
1768
|
+
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1769
|
+
try {
|
|
1770
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
1771
|
+
for (const entry of entries) {
|
|
1772
|
+
const fullPath = join3(dirPath, entry.name);
|
|
1773
|
+
if (entry.isDirectory()) {
|
|
1774
|
+
await rm(fullPath, { recursive: true, force: true });
|
|
1775
|
+
} else {
|
|
1776
|
+
await unlink(fullPath);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
await rm(dirPath, { force: true, recursive: true });
|
|
1780
|
+
return;
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
const isRetryable = error instanceof Error && "code" in error && (error.code === "EBUSY" || error.code === "EPERM");
|
|
1783
|
+
if (!isRetryable || attempt === maxRetries - 1) {
|
|
1784
|
+
throw error;
|
|
1785
|
+
}
|
|
1786
|
+
await new Promise((resolve3) => setTimeout(resolve3, retryDelay * (attempt + 1)));
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
var SecretsEngineError, SecurityError, IntegrityError, KeyNotFoundError, DecryptionError, InitializationError, CONSTANTS, EXPECTED_MODES, CREATE_SECRETS_TABLE = `
|
|
1791
|
+
CREATE TABLE IF NOT EXISTS secrets (
|
|
1792
|
+
key_hash TEXT PRIMARY KEY,
|
|
1793
|
+
key_enc BLOB NOT NULL,
|
|
1794
|
+
iv BLOB NOT NULL,
|
|
1795
|
+
cipher BLOB NOT NULL,
|
|
1796
|
+
created INTEGER NOT NULL,
|
|
1797
|
+
updated INTEGER NOT NULL
|
|
1798
|
+
) STRICT;
|
|
1799
|
+
`, CREATE_META_TABLE = `
|
|
1800
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
1801
|
+
key TEXT PRIMARY KEY,
|
|
1802
|
+
value TEXT NOT NULL
|
|
1803
|
+
) STRICT;
|
|
1804
|
+
`;
|
|
1805
|
+
var init_dist = __esm(() => {
|
|
1806
|
+
SecretsEngineError = class SecretsEngineError extends Error {
|
|
1807
|
+
constructor(message, options) {
|
|
1808
|
+
super(message, options);
|
|
1809
|
+
this.name = this.constructor.name;
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
SecurityError = class SecurityError extends SecretsEngineError {
|
|
1813
|
+
expectedPermission;
|
|
1814
|
+
actualPermission;
|
|
1815
|
+
path;
|
|
1816
|
+
code = "SECURITY_ERROR";
|
|
1817
|
+
constructor(message, expectedPermission, actualPermission, path2) {
|
|
1818
|
+
super(`${message} — expected ${expectedPermission}, got ${actualPermission} on "${path2}"`);
|
|
1819
|
+
this.expectedPermission = expectedPermission;
|
|
1820
|
+
this.actualPermission = actualPermission;
|
|
1821
|
+
this.path = path2;
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
IntegrityError = class IntegrityError extends SecretsEngineError {
|
|
1825
|
+
code = "INTEGRITY_ERROR";
|
|
1826
|
+
constructor(message = "Database integrity check failed — possible tampering detected") {
|
|
1827
|
+
super(message);
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
KeyNotFoundError = class KeyNotFoundError extends SecretsEngineError {
|
|
1831
|
+
code = "KEY_NOT_FOUND";
|
|
1832
|
+
constructor(key) {
|
|
1833
|
+
super(`Key not found: "${key}"`);
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
DecryptionError = class DecryptionError extends SecretsEngineError {
|
|
1837
|
+
keyHash;
|
|
1838
|
+
code = "DECRYPTION_ERROR";
|
|
1839
|
+
constructor(message, keyHash) {
|
|
1840
|
+
const detail = keyHash ? ` (entry: ${keyHash.slice(0, 16)}…)` : "";
|
|
1841
|
+
super(`Decryption failed${detail}: ${message}`);
|
|
1842
|
+
this.keyHash = keyHash;
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
InitializationError = class InitializationError extends SecretsEngineError {
|
|
1846
|
+
code = "INITIALIZATION_ERROR";
|
|
1847
|
+
constructor(message, cause) {
|
|
1848
|
+
super(`Initialization failed: ${message}`, { cause });
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
CONSTANTS = {
|
|
1852
|
+
IV_LENGTH: 12,
|
|
1853
|
+
AUTH_TAG_LENGTH: 16,
|
|
1854
|
+
KEY_LENGTH: 32,
|
|
1855
|
+
SCRYPT_N: 131072,
|
|
1856
|
+
SCRYPT_R: 8,
|
|
1857
|
+
SCRYPT_P: 1,
|
|
1858
|
+
KEYFILE_LENGTH: 32,
|
|
1859
|
+
SALT_LENGTH: 32,
|
|
1860
|
+
STORE_VERSION: "1",
|
|
1861
|
+
DIR_NAME: ".secrets-engine",
|
|
1862
|
+
KEYFILE_NAME: ".keyfile",
|
|
1863
|
+
DB_NAME: "store.db",
|
|
1864
|
+
META_NAME: "meta.json"
|
|
1865
|
+
};
|
|
1866
|
+
EXPECTED_MODES = {
|
|
1867
|
+
directory: 448,
|
|
1868
|
+
database: 384,
|
|
1869
|
+
keyfile: 256,
|
|
1870
|
+
meta: 384
|
|
1871
|
+
};
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1301
1874
|
// node_modules/sisteransi/src/index.js
|
|
1302
1875
|
var require_src = __commonJS((exports, module) => {
|
|
1303
1876
|
var ESC = "\x1B";
|
|
@@ -4857,7 +5430,7 @@ var init_session = __esm(() => {
|
|
|
4857
5430
|
import { spawn } from "node:child_process";
|
|
4858
5431
|
import { existsSync as existsSync4 } from "node:fs";
|
|
4859
5432
|
import { Socket } from "node:net";
|
|
4860
|
-
import { dirname as
|
|
5433
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
4861
5434
|
import { fileURLToPath } from "node:url";
|
|
4862
5435
|
function isZodSchema(value) {
|
|
4863
5436
|
return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
|
|
@@ -4879,7 +5452,7 @@ function getNodeExecPath() {
|
|
|
4879
5452
|
function getBundledCliPath() {
|
|
4880
5453
|
const sdkUrl = import.meta.resolve("@github/copilot/sdk");
|
|
4881
5454
|
const sdkPath = fileURLToPath(sdkUrl);
|
|
4882
|
-
return
|
|
5455
|
+
return join8(dirname5(dirname5(sdkPath)), "index.js");
|
|
4883
5456
|
}
|
|
4884
5457
|
|
|
4885
5458
|
class CopilotClient {
|
|
@@ -5631,14 +6204,14 @@ var approveAll = () => ({ kind: "approved" });
|
|
|
5631
6204
|
var init_types2 = () => {};
|
|
5632
6205
|
|
|
5633
6206
|
// node_modules/@github/copilot-sdk/dist/index.js
|
|
5634
|
-
var
|
|
5635
|
-
__export(
|
|
6207
|
+
var exports_dist3 = {};
|
|
6208
|
+
__export(exports_dist3, {
|
|
5636
6209
|
defineTool: () => defineTool,
|
|
5637
6210
|
approveAll: () => approveAll,
|
|
5638
6211
|
CopilotSession: () => CopilotSession,
|
|
5639
6212
|
CopilotClient: () => CopilotClient
|
|
5640
6213
|
});
|
|
5641
|
-
var
|
|
6214
|
+
var init_dist2 = __esm(() => {
|
|
5642
6215
|
init_client();
|
|
5643
6216
|
init_session();
|
|
5644
6217
|
init_types2();
|
|
@@ -8826,21 +9399,991 @@ var LogEngine = {
|
|
|
8826
9399
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
8827
9400
|
|
|
8828
9401
|
// src/utils/state.ts
|
|
8829
|
-
import { existsSync as existsSync3,
|
|
8830
|
-
import { dirname as
|
|
9402
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
9403
|
+
import { dirname as dirname4, join as join7, resolve as resolve3 } from "node:path";
|
|
9404
|
+
|
|
9405
|
+
// node_modules/@wgtechlabs/config-engine/dist/chunk-gs6j6hrj.js
|
|
9406
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
9407
|
+
var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
|
|
9408
|
+
function createZodValidator(schema) {
|
|
9409
|
+
return {
|
|
9410
|
+
validate(data) {
|
|
9411
|
+
const result = schema.safeParse(data);
|
|
9412
|
+
if (result.success) {
|
|
9413
|
+
return { success: true, data: result.data };
|
|
9414
|
+
}
|
|
9415
|
+
return {
|
|
9416
|
+
success: false,
|
|
9417
|
+
errors: formatZodErrors(result.error)
|
|
9418
|
+
};
|
|
9419
|
+
}
|
|
9420
|
+
};
|
|
9421
|
+
}
|
|
9422
|
+
function formatZodErrors(error) {
|
|
9423
|
+
return error.issues.map((issue) => {
|
|
9424
|
+
const path2 = issue.path.length > 0 ? `\`${issue.path.join(".")}\` ` : "";
|
|
9425
|
+
return `${path2}${issue.message}`;
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
function resolveValidator(options) {
|
|
9429
|
+
if (options.validator)
|
|
9430
|
+
return options.validator;
|
|
9431
|
+
if (options.schema)
|
|
9432
|
+
return createZodValidator(options.schema);
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9435
|
+
|
|
9436
|
+
// node_modules/@wgtechlabs/config-engine/dist/index.js
|
|
9437
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
9438
|
+
import { dirname as dirname3 } from "node:path";
|
|
9439
|
+
import { watch } from "node:fs";
|
|
9440
|
+
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
9441
|
+
import { join as join6 } from "node:path";
|
|
9442
|
+
|
|
9443
|
+
class ConfigCache {
|
|
9444
|
+
#store;
|
|
9445
|
+
#strategy;
|
|
9446
|
+
#data = new Map;
|
|
9447
|
+
#dirty = new Set;
|
|
9448
|
+
#deleted = new Set;
|
|
9449
|
+
#flushScheduled = false;
|
|
9450
|
+
#flushPromise = null;
|
|
9451
|
+
constructor(store, strategy = "batched") {
|
|
9452
|
+
this.#store = store;
|
|
9453
|
+
this.#strategy = strategy;
|
|
9454
|
+
}
|
|
9455
|
+
load(initial) {
|
|
9456
|
+
this.#data.clear();
|
|
9457
|
+
this.#dirty.clear();
|
|
9458
|
+
this.#deleted.clear();
|
|
9459
|
+
const stored = initial ?? this.#store.getAll();
|
|
9460
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
9461
|
+
this.#data.set(key, value);
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
get(key) {
|
|
9465
|
+
return this.#data.get(key);
|
|
9466
|
+
}
|
|
9467
|
+
has(key) {
|
|
9468
|
+
return this.#data.has(key);
|
|
9469
|
+
}
|
|
9470
|
+
get size() {
|
|
9471
|
+
return this.#data.size;
|
|
9472
|
+
}
|
|
9473
|
+
getAll() {
|
|
9474
|
+
const obj = Object.create(null);
|
|
9475
|
+
for (const [key, value] of this.#data) {
|
|
9476
|
+
obj[key] = value;
|
|
9477
|
+
}
|
|
9478
|
+
return obj;
|
|
9479
|
+
}
|
|
9480
|
+
entries() {
|
|
9481
|
+
return this.#data.entries();
|
|
9482
|
+
}
|
|
9483
|
+
set(key, value) {
|
|
9484
|
+
this.#data.set(key, value);
|
|
9485
|
+
this.#dirty.add(key);
|
|
9486
|
+
this.#deleted.delete(key);
|
|
9487
|
+
this.#scheduleFlush();
|
|
9488
|
+
}
|
|
9489
|
+
setMany(entries) {
|
|
9490
|
+
for (const [key, value] of entries) {
|
|
9491
|
+
this.#data.set(key, value);
|
|
9492
|
+
this.#dirty.add(key);
|
|
9493
|
+
this.#deleted.delete(key);
|
|
9494
|
+
}
|
|
9495
|
+
this.#scheduleFlush();
|
|
9496
|
+
}
|
|
9497
|
+
delete(key) {
|
|
9498
|
+
const existed = this.#data.delete(key);
|
|
9499
|
+
if (existed) {
|
|
9500
|
+
this.#dirty.delete(key);
|
|
9501
|
+
this.#deleted.add(key);
|
|
9502
|
+
this.#scheduleFlush();
|
|
9503
|
+
}
|
|
9504
|
+
return existed;
|
|
9505
|
+
}
|
|
9506
|
+
clear() {
|
|
9507
|
+
for (const key of this.#data.keys()) {
|
|
9508
|
+
this.#deleted.add(key);
|
|
9509
|
+
}
|
|
9510
|
+
this.#data.clear();
|
|
9511
|
+
this.#dirty.clear();
|
|
9512
|
+
this.#scheduleFlush();
|
|
9513
|
+
}
|
|
9514
|
+
replaceAll(data) {
|
|
9515
|
+
for (const key of this.#data.keys()) {
|
|
9516
|
+
if (!(key in data)) {
|
|
9517
|
+
this.#deleted.add(key);
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
this.#data.clear();
|
|
9521
|
+
this.#dirty.clear();
|
|
9522
|
+
for (const [key, value] of Object.entries(data)) {
|
|
9523
|
+
this.#data.set(key, value);
|
|
9524
|
+
this.#dirty.add(key);
|
|
9525
|
+
this.#deleted.delete(key);
|
|
9526
|
+
}
|
|
9527
|
+
this.#scheduleFlush();
|
|
9528
|
+
}
|
|
9529
|
+
#scheduleFlush() {
|
|
9530
|
+
if (this.#strategy === "manual")
|
|
9531
|
+
return;
|
|
9532
|
+
if (this.#strategy === "immediate") {
|
|
9533
|
+
this.flushSync();
|
|
9534
|
+
return;
|
|
9535
|
+
}
|
|
9536
|
+
if (!this.#flushScheduled) {
|
|
9537
|
+
this.#flushScheduled = true;
|
|
9538
|
+
this.#flushPromise = Promise.resolve().then(() => {
|
|
9539
|
+
this.flushSync();
|
|
9540
|
+
this.#flushScheduled = false;
|
|
9541
|
+
this.#flushPromise = null;
|
|
9542
|
+
});
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
flushSync() {
|
|
9546
|
+
if (this.#dirty.size === 0 && this.#deleted.size === 0)
|
|
9547
|
+
return;
|
|
9548
|
+
this.#store.transaction(() => {
|
|
9549
|
+
if (this.#dirty.size > 0) {
|
|
9550
|
+
const entries = [];
|
|
9551
|
+
for (const key of this.#dirty) {
|
|
9552
|
+
entries.push([key, this.#data.get(key)]);
|
|
9553
|
+
}
|
|
9554
|
+
this.#store.setMany(entries);
|
|
9555
|
+
}
|
|
9556
|
+
for (const key of this.#deleted) {
|
|
9557
|
+
this.#store.deleteOne(key);
|
|
9558
|
+
}
|
|
9559
|
+
this.#store.touchLastWrite();
|
|
9560
|
+
});
|
|
9561
|
+
this.#dirty.clear();
|
|
9562
|
+
this.#deleted.clear();
|
|
9563
|
+
}
|
|
9564
|
+
async flush() {
|
|
9565
|
+
if (this.#flushPromise) {
|
|
9566
|
+
await this.#flushPromise;
|
|
9567
|
+
} else if (this.#dirty.size > 0 || this.#deleted.size > 0) {
|
|
9568
|
+
this.flushSync();
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9571
|
+
reload() {
|
|
9572
|
+
this.#dirty.clear();
|
|
9573
|
+
this.#deleted.clear();
|
|
9574
|
+
this.load();
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
function getByPath(obj, path2) {
|
|
9578
|
+
if (typeof obj !== "object" || obj === null)
|
|
9579
|
+
return;
|
|
9580
|
+
const keys = path2.split(".");
|
|
9581
|
+
let current = obj;
|
|
9582
|
+
for (const key of keys) {
|
|
9583
|
+
if (typeof current !== "object" || current === null)
|
|
9584
|
+
return;
|
|
9585
|
+
current = current[key];
|
|
9586
|
+
}
|
|
9587
|
+
return current;
|
|
9588
|
+
}
|
|
9589
|
+
function setByPath(obj, path2, value) {
|
|
9590
|
+
const keys = path2.split(".");
|
|
9591
|
+
if (keys.length === 0)
|
|
9592
|
+
return obj;
|
|
9593
|
+
const result = { ...obj };
|
|
9594
|
+
let current = result;
|
|
9595
|
+
for (let i2 = 0;i2 < keys.length - 1; i2++) {
|
|
9596
|
+
const key = keys[i2];
|
|
9597
|
+
if (typeof current[key] !== "object" || current[key] === null) {
|
|
9598
|
+
current[key] = {};
|
|
9599
|
+
} else {
|
|
9600
|
+
current[key] = { ...current[key] };
|
|
9601
|
+
}
|
|
9602
|
+
current = current[key];
|
|
9603
|
+
}
|
|
9604
|
+
const lastKey = keys[keys.length - 1];
|
|
9605
|
+
current[lastKey] = value;
|
|
9606
|
+
return result;
|
|
9607
|
+
}
|
|
9608
|
+
function hasByPath(obj, path2) {
|
|
9609
|
+
if (typeof obj !== "object" || obj === null)
|
|
9610
|
+
return false;
|
|
9611
|
+
const keys = path2.split(".");
|
|
9612
|
+
let current = obj;
|
|
9613
|
+
for (let i2 = 0;i2 < keys.length; i2++) {
|
|
9614
|
+
if (typeof current !== "object" || current === null)
|
|
9615
|
+
return false;
|
|
9616
|
+
const key = keys[i2];
|
|
9617
|
+
if (!Object.prototype.hasOwnProperty.call(current, key))
|
|
9618
|
+
return false;
|
|
9619
|
+
current = current[key];
|
|
9620
|
+
}
|
|
9621
|
+
return true;
|
|
9622
|
+
}
|
|
9623
|
+
function deleteByPath(obj, path2) {
|
|
9624
|
+
const keys = path2.split(".");
|
|
9625
|
+
if (keys.length === 0)
|
|
9626
|
+
return obj;
|
|
9627
|
+
const result = { ...obj };
|
|
9628
|
+
let current = result;
|
|
9629
|
+
for (let i2 = 0;i2 < keys.length - 1; i2++) {
|
|
9630
|
+
const key = keys[i2];
|
|
9631
|
+
if (typeof current[key] !== "object" || current[key] === null) {
|
|
9632
|
+
return obj;
|
|
9633
|
+
}
|
|
9634
|
+
current[key] = { ...current[key] };
|
|
9635
|
+
current = current[key];
|
|
9636
|
+
}
|
|
9637
|
+
const lastKey = keys[keys.length - 1];
|
|
9638
|
+
delete current[lastKey];
|
|
9639
|
+
return result;
|
|
9640
|
+
}
|
|
9641
|
+
|
|
9642
|
+
class SecretsEngineEncryptor {
|
|
9643
|
+
#keyName;
|
|
9644
|
+
#derivedKey = null;
|
|
9645
|
+
#secrets = null;
|
|
9646
|
+
constructor(keyName) {
|
|
9647
|
+
this.#keyName = keyName;
|
|
9648
|
+
}
|
|
9649
|
+
async init() {
|
|
9650
|
+
let SecretsEngine2;
|
|
9651
|
+
try {
|
|
9652
|
+
const mod = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
9653
|
+
SecretsEngine2 = mod.SecretsEngine ?? mod.default?.SecretsEngine ?? mod;
|
|
9654
|
+
} catch {
|
|
9655
|
+
throw new Error('Encryption requires "@wgtechlabs/secrets-engine" as a peer dependency. Install it with: bun add @wgtechlabs/secrets-engine');
|
|
9656
|
+
}
|
|
9657
|
+
this.#secrets = await SecretsEngine2.open();
|
|
9658
|
+
const hasKey = await this.#secrets.has(this.#keyName);
|
|
9659
|
+
if (!hasKey) {
|
|
9660
|
+
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
9661
|
+
const keyHex2 = Array.from(keyBytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
9662
|
+
await this.#secrets.set(this.#keyName, keyHex2);
|
|
9663
|
+
}
|
|
9664
|
+
const keyHex = await this.#secrets.get(this.#keyName);
|
|
9665
|
+
const keyBuffer = hexToUint8Array(keyHex);
|
|
9666
|
+
this.#derivedKey = await crypto.subtle.importKey("raw", keyBuffer.buffer, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
9667
|
+
}
|
|
9668
|
+
async encrypt(plaintext) {
|
|
9669
|
+
if (!this.#derivedKey)
|
|
9670
|
+
throw new Error("Encryptor not initialized. Call init() first.");
|
|
9671
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
9672
|
+
const encoded = new TextEncoder().encode(plaintext);
|
|
9673
|
+
const cipherBuffer = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv.buffer }, this.#derivedKey, encoded);
|
|
9674
|
+
const ivB64 = uint8ArrayToBase64(iv);
|
|
9675
|
+
const cipherB64 = uint8ArrayToBase64(new Uint8Array(cipherBuffer));
|
|
9676
|
+
return `${ivB64}:${cipherB64}`;
|
|
9677
|
+
}
|
|
9678
|
+
async decrypt(ciphertext) {
|
|
9679
|
+
if (!this.#derivedKey)
|
|
9680
|
+
throw new Error("Encryptor not initialized. Call init() first.");
|
|
9681
|
+
const [ivB64, cipherB64] = ciphertext.split(":");
|
|
9682
|
+
if (!ivB64 || !cipherB64)
|
|
9683
|
+
throw new Error("Invalid encrypted data format.");
|
|
9684
|
+
const iv = base64ToUint8Array(ivB64);
|
|
9685
|
+
const cipherBuffer = base64ToUint8Array(cipherB64);
|
|
9686
|
+
const plainBuffer = await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv.buffer }, this.#derivedKey, cipherBuffer.buffer);
|
|
9687
|
+
return new TextDecoder().decode(plainBuffer);
|
|
9688
|
+
}
|
|
9689
|
+
async close() {
|
|
9690
|
+
if (this.#secrets) {
|
|
9691
|
+
this.#secrets.close();
|
|
9692
|
+
this.#secrets = null;
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
function hexToUint8Array(hex) {
|
|
9697
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
9698
|
+
for (let i2 = 0;i2 < hex.length; i2 += 2) {
|
|
9699
|
+
bytes[i2 / 2] = Number.parseInt(hex.substring(i2, i2 + 2), 16);
|
|
9700
|
+
}
|
|
9701
|
+
return bytes;
|
|
9702
|
+
}
|
|
9703
|
+
function uint8ArrayToBase64(bytes) {
|
|
9704
|
+
if (typeof Buffer !== "undefined") {
|
|
9705
|
+
return Buffer.from(bytes).toString("base64");
|
|
9706
|
+
}
|
|
9707
|
+
let binary = "";
|
|
9708
|
+
for (const byte of bytes) {
|
|
9709
|
+
binary += String.fromCharCode(byte);
|
|
9710
|
+
}
|
|
9711
|
+
return btoa(binary);
|
|
9712
|
+
}
|
|
9713
|
+
function base64ToUint8Array(b64) {
|
|
9714
|
+
if (typeof Buffer !== "undefined") {
|
|
9715
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
9716
|
+
}
|
|
9717
|
+
const binary = atob(b64);
|
|
9718
|
+
const bytes = new Uint8Array(binary.length);
|
|
9719
|
+
for (let i2 = 0;i2 < binary.length; i2++) {
|
|
9720
|
+
bytes[i2] = binary.charCodeAt(i2);
|
|
9721
|
+
}
|
|
9722
|
+
return bytes;
|
|
9723
|
+
}
|
|
9724
|
+
async function resolveEncryptor(encryptionKey) {
|
|
9725
|
+
if (!encryptionKey)
|
|
9726
|
+
return;
|
|
9727
|
+
if (typeof encryptionKey === "string") {
|
|
9728
|
+
const enc = new SecretsEngineEncryptor(encryptionKey);
|
|
9729
|
+
await enc.init();
|
|
9730
|
+
return enc;
|
|
9731
|
+
}
|
|
9732
|
+
return encryptionKey;
|
|
9733
|
+
}
|
|
9734
|
+
var META_KEY = "schema_version";
|
|
9735
|
+
var INITIAL_VERSION = "0.0.0";
|
|
9736
|
+
async function runMigrations(options) {
|
|
9737
|
+
const { store, migrations, projectVersion, beforeEachMigration, afterEachMigration } = options;
|
|
9738
|
+
if (migrations.length === 0)
|
|
9739
|
+
return;
|
|
9740
|
+
const currentVersion = store.getMeta(META_KEY) ?? INITIAL_VERSION;
|
|
9741
|
+
const versions = migrations.map((m2) => m2.version);
|
|
9742
|
+
const finalVersion = projectVersion;
|
|
9743
|
+
const pending = migrations.filter((m2) => compareVersions(m2.version, currentVersion) > 0);
|
|
9744
|
+
if (pending.length === 0) {
|
|
9745
|
+
store.setMeta(META_KEY, projectVersion);
|
|
9746
|
+
return;
|
|
9747
|
+
}
|
|
9748
|
+
pending.sort((a2, b2) => compareVersions(a2.version, b2.version));
|
|
9749
|
+
const ctx = createMigrationContext(store);
|
|
9750
|
+
const snapshot = store.getAll();
|
|
9751
|
+
const snapshotVersion = currentVersion;
|
|
9752
|
+
try {
|
|
9753
|
+
for (const migration of pending) {
|
|
9754
|
+
const hookCtx = {
|
|
9755
|
+
fromVersion: currentVersion,
|
|
9756
|
+
toVersion: migration.version,
|
|
9757
|
+
finalVersion,
|
|
9758
|
+
versions
|
|
9759
|
+
};
|
|
9760
|
+
if (beforeEachMigration) {
|
|
9761
|
+
await beforeEachMigration(hookCtx);
|
|
9762
|
+
}
|
|
9763
|
+
await migration.up(ctx);
|
|
9764
|
+
if (afterEachMigration) {
|
|
9765
|
+
await afterEachMigration(hookCtx);
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
store.setMeta(META_KEY, projectVersion);
|
|
9769
|
+
store.touchLastWrite();
|
|
9770
|
+
} catch (error) {
|
|
9771
|
+
store.transaction(() => {
|
|
9772
|
+
store.deleteAll();
|
|
9773
|
+
const entries = Object.entries(snapshot);
|
|
9774
|
+
if (entries.length > 0) {
|
|
9775
|
+
store.setMany(entries);
|
|
9776
|
+
}
|
|
9777
|
+
store.setMeta(META_KEY, snapshotVersion);
|
|
9778
|
+
});
|
|
9779
|
+
throw new Error(`Migration to version "${pending.find(() => true)?.version}" failed. All changes have been rolled back. Original error: ${error instanceof Error ? error.message : String(error)}`);
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
function createMigrationContext(store) {
|
|
9783
|
+
return {
|
|
9784
|
+
get(key) {
|
|
9785
|
+
return store.getOne(key);
|
|
9786
|
+
},
|
|
9787
|
+
set(key, value) {
|
|
9788
|
+
store.setOne(key, value);
|
|
9789
|
+
},
|
|
9790
|
+
has(key) {
|
|
9791
|
+
return store.has(key);
|
|
9792
|
+
},
|
|
9793
|
+
delete(key) {
|
|
9794
|
+
return store.deleteOne(key);
|
|
9795
|
+
}
|
|
9796
|
+
};
|
|
9797
|
+
}
|
|
9798
|
+
function compareVersions(a2, b2) {
|
|
9799
|
+
const partsA = a2.split(".").map(Number);
|
|
9800
|
+
const partsB = b2.split(".").map(Number);
|
|
9801
|
+
for (let i2 = 0;i2 < 3; i2++) {
|
|
9802
|
+
const va = partsA[i2] ?? 0;
|
|
9803
|
+
const vb = partsB[i2] ?? 0;
|
|
9804
|
+
if (va !== vb)
|
|
9805
|
+
return va - vb;
|
|
9806
|
+
}
|
|
9807
|
+
return 0;
|
|
9808
|
+
}
|
|
9809
|
+
function resolveConfigDir(projectName) {
|
|
9810
|
+
const home = homedir2();
|
|
9811
|
+
const os2 = platform2();
|
|
9812
|
+
switch (os2) {
|
|
9813
|
+
case "darwin":
|
|
9814
|
+
return join6(home, "Library", "Preferences", projectName);
|
|
9815
|
+
case "win32":
|
|
9816
|
+
return join6(process.env.APPDATA ?? join6(home, "AppData", "Roaming"), projectName);
|
|
9817
|
+
default:
|
|
9818
|
+
return join6(process.env.XDG_CONFIG_HOME ?? join6(home, ".config"), projectName);
|
|
9819
|
+
}
|
|
9820
|
+
}
|
|
9821
|
+
function resolveConfigPath(options) {
|
|
9822
|
+
const dir = options.cwd ?? resolveConfigDir(options.projectName);
|
|
9823
|
+
const name = options.configName ?? "config";
|
|
9824
|
+
return join6(dir, `${name}.db`);
|
|
9825
|
+
}
|
|
9826
|
+
function isBun() {
|
|
9827
|
+
return typeof globalThis.Bun !== "undefined";
|
|
9828
|
+
}
|
|
9829
|
+
function openDatabase(filepath) {
|
|
9830
|
+
if (isBun()) {
|
|
9831
|
+
return openBunDatabase(filepath);
|
|
9832
|
+
}
|
|
9833
|
+
return openNodeDatabase(filepath);
|
|
9834
|
+
}
|
|
9835
|
+
function openBunDatabase(filepath) {
|
|
9836
|
+
const { Database: Database2 } = __require2("bun:sqlite");
|
|
9837
|
+
const db = new Database2(filepath);
|
|
9838
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
9839
|
+
db.exec("PRAGMA busy_timeout = 5000;");
|
|
9840
|
+
return {
|
|
9841
|
+
prepare(sql) {
|
|
9842
|
+
const stmt = db.prepare(sql);
|
|
9843
|
+
return {
|
|
9844
|
+
run(...params) {
|
|
9845
|
+
stmt.run(...params);
|
|
9846
|
+
},
|
|
9847
|
+
get(...params) {
|
|
9848
|
+
return stmt.get(...params);
|
|
9849
|
+
},
|
|
9850
|
+
all(...params) {
|
|
9851
|
+
return stmt.all(...params);
|
|
9852
|
+
}
|
|
9853
|
+
};
|
|
9854
|
+
},
|
|
9855
|
+
exec(sql) {
|
|
9856
|
+
db.exec(sql);
|
|
9857
|
+
},
|
|
9858
|
+
close() {
|
|
9859
|
+
db.close();
|
|
9860
|
+
},
|
|
9861
|
+
transaction(fn) {
|
|
9862
|
+
return db.transaction(fn);
|
|
9863
|
+
}
|
|
9864
|
+
};
|
|
9865
|
+
}
|
|
9866
|
+
function openNodeDatabase(filepath) {
|
|
9867
|
+
let BetterSqlite3;
|
|
9868
|
+
try {
|
|
9869
|
+
BetterSqlite3 = __require2("better-sqlite3");
|
|
9870
|
+
} catch {
|
|
9871
|
+
throw new Error('config-engine requires "better-sqlite3" as a peer dependency when running on Node.js. Install it with: npm install better-sqlite3');
|
|
9872
|
+
}
|
|
9873
|
+
const db = new BetterSqlite3(filepath);
|
|
9874
|
+
db.pragma("journal_mode = WAL");
|
|
9875
|
+
db.pragma("busy_timeout = 5000");
|
|
9876
|
+
return {
|
|
9877
|
+
prepare(sql) {
|
|
9878
|
+
const stmt = db.prepare(sql);
|
|
9879
|
+
return {
|
|
9880
|
+
run(...params) {
|
|
9881
|
+
stmt.run(...params);
|
|
9882
|
+
},
|
|
9883
|
+
get(...params) {
|
|
9884
|
+
return stmt.get(...params);
|
|
9885
|
+
},
|
|
9886
|
+
all(...params) {
|
|
9887
|
+
return stmt.all(...params);
|
|
9888
|
+
}
|
|
9889
|
+
};
|
|
9890
|
+
},
|
|
9891
|
+
exec(sql) {
|
|
9892
|
+
db.exec(sql);
|
|
9893
|
+
},
|
|
9894
|
+
close() {
|
|
9895
|
+
db.close();
|
|
9896
|
+
},
|
|
9897
|
+
transaction(fn) {
|
|
9898
|
+
return db.transaction(fn);
|
|
9899
|
+
}
|
|
9900
|
+
};
|
|
9901
|
+
}
|
|
9902
|
+
|
|
9903
|
+
class ConfigStore {
|
|
9904
|
+
#db;
|
|
9905
|
+
constructor(db) {
|
|
9906
|
+
this.#db = db;
|
|
9907
|
+
this.#initialize();
|
|
9908
|
+
}
|
|
9909
|
+
#initialize() {
|
|
9910
|
+
this.#db.exec(`
|
|
9911
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
9912
|
+
key TEXT PRIMARY KEY,
|
|
9913
|
+
value TEXT NOT NULL
|
|
9914
|
+
);
|
|
9915
|
+
`);
|
|
9916
|
+
this.#db.exec(`
|
|
9917
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
9918
|
+
key TEXT PRIMARY KEY,
|
|
9919
|
+
value TEXT NOT NULL
|
|
9920
|
+
);
|
|
9921
|
+
`);
|
|
9922
|
+
}
|
|
9923
|
+
getAll() {
|
|
9924
|
+
const rows = this.#db.prepare("SELECT key, value FROM config").all();
|
|
9925
|
+
const result = Object.create(null);
|
|
9926
|
+
for (const row of rows) {
|
|
9927
|
+
result[row.key] = JSON.parse(row.value);
|
|
9928
|
+
}
|
|
9929
|
+
return result;
|
|
9930
|
+
}
|
|
9931
|
+
getOne(key) {
|
|
9932
|
+
const row = this.#db.prepare("SELECT value FROM config WHERE key = ?").get(key);
|
|
9933
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
9934
|
+
}
|
|
9935
|
+
setOne(key, value) {
|
|
9936
|
+
this.#db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
9937
|
+
}
|
|
9938
|
+
setMany(entries) {
|
|
9939
|
+
const stmt = this.#db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)");
|
|
9940
|
+
const runTransaction = this.#db.transaction(() => {
|
|
9941
|
+
for (const [key, value] of entries) {
|
|
9942
|
+
stmt.run(key, JSON.stringify(value));
|
|
9943
|
+
}
|
|
9944
|
+
});
|
|
9945
|
+
runTransaction();
|
|
9946
|
+
}
|
|
9947
|
+
deleteOne(key) {
|
|
9948
|
+
const before = this.count();
|
|
9949
|
+
this.#db.prepare("DELETE FROM config WHERE key = ?").run(key);
|
|
9950
|
+
return this.count() < before;
|
|
9951
|
+
}
|
|
9952
|
+
deleteAll() {
|
|
9953
|
+
this.#db.exec("DELETE FROM config;");
|
|
9954
|
+
}
|
|
9955
|
+
has(key) {
|
|
9956
|
+
const row = this.#db.prepare("SELECT 1 FROM config WHERE key = ? LIMIT 1").get(key);
|
|
9957
|
+
return row !== undefined && row !== null;
|
|
9958
|
+
}
|
|
9959
|
+
count() {
|
|
9960
|
+
const row = this.#db.prepare("SELECT COUNT(*) as cnt FROM config").get();
|
|
9961
|
+
return row.cnt;
|
|
9962
|
+
}
|
|
9963
|
+
getMeta(key) {
|
|
9964
|
+
const row = this.#db.prepare("SELECT value FROM _meta WHERE key = ?").get(key);
|
|
9965
|
+
return row?.value;
|
|
9966
|
+
}
|
|
9967
|
+
setMeta(key, value) {
|
|
9968
|
+
this.#db.prepare("INSERT OR REPLACE INTO _meta (key, value) VALUES (?, ?)").run(key, value);
|
|
9969
|
+
}
|
|
9970
|
+
hasMeta(key) {
|
|
9971
|
+
const row = this.#db.prepare("SELECT 1 FROM _meta WHERE key = ? LIMIT 1").get(key);
|
|
9972
|
+
return row !== undefined && row !== null;
|
|
9973
|
+
}
|
|
9974
|
+
transaction(fn) {
|
|
9975
|
+
const wrapped = this.#db.transaction(fn);
|
|
9976
|
+
return wrapped();
|
|
9977
|
+
}
|
|
9978
|
+
touchLastWrite() {
|
|
9979
|
+
this.setMeta("last_write", Date.now().toString());
|
|
9980
|
+
}
|
|
9981
|
+
getLastWrite() {
|
|
9982
|
+
const val = this.getMeta("last_write");
|
|
9983
|
+
return val ? Number(val) : 0;
|
|
9984
|
+
}
|
|
9985
|
+
close() {
|
|
9986
|
+
this.#db.close();
|
|
9987
|
+
}
|
|
9988
|
+
}
|
|
9989
|
+
|
|
9990
|
+
class ConfigEngineError extends Error {
|
|
9991
|
+
name = "ConfigEngineError";
|
|
9992
|
+
}
|
|
9993
|
+
|
|
9994
|
+
class ValidationError extends ConfigEngineError {
|
|
9995
|
+
name = "ValidationError";
|
|
9996
|
+
errors;
|
|
9997
|
+
constructor(errors) {
|
|
9998
|
+
super(`Config validation failed:
|
|
9999
|
+
- ${errors.join(`
|
|
10000
|
+
- `)}`);
|
|
10001
|
+
this.errors = errors;
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
|
|
10005
|
+
class ConfigEngine {
|
|
10006
|
+
#store;
|
|
10007
|
+
#cache;
|
|
10008
|
+
#validator;
|
|
10009
|
+
#encryptor;
|
|
10010
|
+
#defaults;
|
|
10011
|
+
#dotNotation;
|
|
10012
|
+
#filePath;
|
|
10013
|
+
#listeners = new Map;
|
|
10014
|
+
#anyListeners = new Set;
|
|
10015
|
+
#watcher = null;
|
|
10016
|
+
#lastKnownWrite = 0;
|
|
10017
|
+
#closed = false;
|
|
10018
|
+
constructor(store, cache, options) {
|
|
10019
|
+
this.#store = store;
|
|
10020
|
+
this.#cache = cache;
|
|
10021
|
+
this.#validator = options.validator;
|
|
10022
|
+
this.#encryptor = options.encryptor;
|
|
10023
|
+
this.#defaults = options.defaults;
|
|
10024
|
+
this.#dotNotation = options.dotNotation;
|
|
10025
|
+
this.#filePath = options.filePath;
|
|
10026
|
+
this.#lastKnownWrite = store.getLastWrite();
|
|
10027
|
+
if (options.watch) {
|
|
10028
|
+
this.#startWatching();
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
static async open(options) {
|
|
10032
|
+
const {
|
|
10033
|
+
projectName,
|
|
10034
|
+
projectVersion,
|
|
10035
|
+
cwd,
|
|
10036
|
+
configName,
|
|
10037
|
+
defaults = {},
|
|
10038
|
+
encryptionKey,
|
|
10039
|
+
migrations,
|
|
10040
|
+
beforeEachMigration,
|
|
10041
|
+
afterEachMigration,
|
|
10042
|
+
flushStrategy = "batched",
|
|
10043
|
+
accessPropertiesByDotNotation = true,
|
|
10044
|
+
watch: enableWatch = false,
|
|
10045
|
+
clearInvalidConfig = false
|
|
10046
|
+
} = options;
|
|
10047
|
+
const filePath = resolveConfigPath({ projectName, cwd, configName });
|
|
10048
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
10049
|
+
const db = openDatabase(filePath);
|
|
10050
|
+
const store = new ConfigStore(db);
|
|
10051
|
+
const encryptor = await resolveEncryptor(encryptionKey);
|
|
10052
|
+
const validator = resolveValidator(options);
|
|
10053
|
+
let storedData;
|
|
10054
|
+
try {
|
|
10055
|
+
storedData = store.getAll();
|
|
10056
|
+
if (encryptor) {
|
|
10057
|
+
storedData = await decryptStore(storedData, encryptor);
|
|
10058
|
+
}
|
|
10059
|
+
} catch (error) {
|
|
10060
|
+
if (clearInvalidConfig) {
|
|
10061
|
+
store.deleteAll();
|
|
10062
|
+
storedData = {};
|
|
10063
|
+
} else {
|
|
10064
|
+
store.close();
|
|
10065
|
+
throw new ConfigEngineError(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
10066
|
+
}
|
|
10067
|
+
}
|
|
10068
|
+
const merged = { ...defaults, ...storedData };
|
|
10069
|
+
if (validator) {
|
|
10070
|
+
const result = validator.validate(merged);
|
|
10071
|
+
if (!result.success) {
|
|
10072
|
+
if (clearInvalidConfig) {
|
|
10073
|
+
store.deleteAll();
|
|
10074
|
+
const freshData = { ...defaults };
|
|
10075
|
+
const freshResult = validator.validate(freshData);
|
|
10076
|
+
if (!freshResult.success) {
|
|
10077
|
+
store.close();
|
|
10078
|
+
throw new ValidationError(freshResult.errors);
|
|
10079
|
+
}
|
|
10080
|
+
const entries = Object.entries(freshData);
|
|
10081
|
+
if (encryptor) {
|
|
10082
|
+
const encrypted = await encryptStore(freshData, encryptor);
|
|
10083
|
+
store.setMany(Object.entries(encrypted));
|
|
10084
|
+
} else {
|
|
10085
|
+
store.setMany(entries);
|
|
10086
|
+
}
|
|
10087
|
+
store.touchLastWrite();
|
|
10088
|
+
const cache2 = new ConfigCache(store, flushStrategy);
|
|
10089
|
+
cache2.load(freshData);
|
|
10090
|
+
return new ConfigEngine(store, cache2, {
|
|
10091
|
+
validator,
|
|
10092
|
+
encryptor,
|
|
10093
|
+
defaults,
|
|
10094
|
+
dotNotation: accessPropertiesByDotNotation,
|
|
10095
|
+
filePath,
|
|
10096
|
+
watch: enableWatch
|
|
10097
|
+
});
|
|
10098
|
+
}
|
|
10099
|
+
store.close();
|
|
10100
|
+
throw new ValidationError(result.errors);
|
|
10101
|
+
}
|
|
10102
|
+
}
|
|
10103
|
+
const hasNewDefaults = Object.keys(defaults).some((key) => !(key in storedData));
|
|
10104
|
+
if (hasNewDefaults) {
|
|
10105
|
+
if (encryptor) {
|
|
10106
|
+
const encrypted = await encryptStore(merged, encryptor);
|
|
10107
|
+
store.setMany(Object.entries(encrypted));
|
|
10108
|
+
} else {
|
|
10109
|
+
store.setMany(Object.entries(merged));
|
|
10110
|
+
}
|
|
10111
|
+
store.touchLastWrite();
|
|
10112
|
+
}
|
|
10113
|
+
if (migrations && migrations.length > 0) {
|
|
10114
|
+
if (!projectVersion) {
|
|
10115
|
+
store.close();
|
|
10116
|
+
throw new ConfigEngineError('"projectVersion" is required when "migrations" is provided.');
|
|
10117
|
+
}
|
|
10118
|
+
await runMigrations({
|
|
10119
|
+
store,
|
|
10120
|
+
migrations,
|
|
10121
|
+
projectVersion,
|
|
10122
|
+
beforeEachMigration,
|
|
10123
|
+
afterEachMigration
|
|
10124
|
+
});
|
|
10125
|
+
}
|
|
10126
|
+
const cache = new ConfigCache(store, flushStrategy);
|
|
10127
|
+
let finalData;
|
|
10128
|
+
if (encryptor) {
|
|
10129
|
+
finalData = await decryptStore(store.getAll(), encryptor);
|
|
10130
|
+
} else {
|
|
10131
|
+
finalData = store.getAll();
|
|
10132
|
+
}
|
|
10133
|
+
const cacheData = { ...defaults, ...finalData };
|
|
10134
|
+
cache.load(cacheData);
|
|
10135
|
+
return new ConfigEngine(store, cache, {
|
|
10136
|
+
validator,
|
|
10137
|
+
encryptor,
|
|
10138
|
+
defaults,
|
|
10139
|
+
dotNotation: accessPropertiesByDotNotation,
|
|
10140
|
+
filePath,
|
|
10141
|
+
watch: enableWatch
|
|
10142
|
+
});
|
|
10143
|
+
}
|
|
10144
|
+
get(key, defaultValue) {
|
|
10145
|
+
this.#ensureOpen();
|
|
10146
|
+
if (this.#dotNotation && key.includes(".")) {
|
|
10147
|
+
const full = this.#cache.getAll();
|
|
10148
|
+
const value2 = getByPath(full, key);
|
|
10149
|
+
return value2 !== undefined ? value2 : defaultValue;
|
|
10150
|
+
}
|
|
10151
|
+
const value = this.#cache.get(key);
|
|
10152
|
+
return value !== undefined ? value : defaultValue;
|
|
10153
|
+
}
|
|
10154
|
+
has(key) {
|
|
10155
|
+
this.#ensureOpen();
|
|
10156
|
+
if (this.#dotNotation && key.includes(".")) {
|
|
10157
|
+
return hasByPath(this.#cache.getAll(), key);
|
|
10158
|
+
}
|
|
10159
|
+
return this.#cache.has(key);
|
|
10160
|
+
}
|
|
10161
|
+
get store() {
|
|
10162
|
+
this.#ensureOpen();
|
|
10163
|
+
return this.#cache.getAll();
|
|
10164
|
+
}
|
|
10165
|
+
set store(value) {
|
|
10166
|
+
this.#ensureOpen();
|
|
10167
|
+
this.#validateFull(value);
|
|
10168
|
+
const oldStore = this.#cache.getAll();
|
|
10169
|
+
this.#cache.replaceAll(value);
|
|
10170
|
+
this.#notifyAnyChange(value, oldStore);
|
|
10171
|
+
}
|
|
10172
|
+
get size() {
|
|
10173
|
+
this.#ensureOpen();
|
|
10174
|
+
return this.#cache.size;
|
|
10175
|
+
}
|
|
10176
|
+
get path() {
|
|
10177
|
+
return this.#filePath;
|
|
10178
|
+
}
|
|
10179
|
+
set(keyOrObject, value) {
|
|
10180
|
+
this.#ensureOpen();
|
|
10181
|
+
if (typeof keyOrObject === "string") {
|
|
10182
|
+
this.#setKey(keyOrObject, value);
|
|
10183
|
+
} else {
|
|
10184
|
+
const entries = Object.entries(keyOrObject);
|
|
10185
|
+
const oldStore = this.#cache.getAll();
|
|
10186
|
+
for (const [key, val] of entries) {
|
|
10187
|
+
if (this.#dotNotation && key.includes(".")) {
|
|
10188
|
+
const full = this.#cache.getAll();
|
|
10189
|
+
const updated = setByPath(full, key, val);
|
|
10190
|
+
this.#validateFull(updated);
|
|
10191
|
+
this.#cache.replaceAll(updated);
|
|
10192
|
+
} else {
|
|
10193
|
+
this.#cache.set(key, val);
|
|
10194
|
+
}
|
|
10195
|
+
}
|
|
10196
|
+
this.#validateFull(this.#cache.getAll());
|
|
10197
|
+
this.#notifyAnyChange(this.#cache.getAll(), oldStore);
|
|
10198
|
+
}
|
|
10199
|
+
}
|
|
10200
|
+
#setKey(key, value) {
|
|
10201
|
+
const oldStore = this.#cache.getAll();
|
|
10202
|
+
const oldValue = this.get(key);
|
|
10203
|
+
if (this.#dotNotation && key.includes(".")) {
|
|
10204
|
+
const full = this.#cache.getAll();
|
|
10205
|
+
const updated = setByPath(full, key, value);
|
|
10206
|
+
this.#validateFull(updated);
|
|
10207
|
+
this.#cache.replaceAll(updated);
|
|
10208
|
+
} else {
|
|
10209
|
+
const testStore = { ...this.#cache.getAll(), [key]: value };
|
|
10210
|
+
this.#validateFull(testStore);
|
|
10211
|
+
this.#cache.set(key, value);
|
|
10212
|
+
}
|
|
10213
|
+
this.#notifyKeyChange(key, value, oldValue);
|
|
10214
|
+
this.#notifyAnyChange(this.#cache.getAll(), oldStore);
|
|
10215
|
+
}
|
|
10216
|
+
delete(key) {
|
|
10217
|
+
this.#ensureOpen();
|
|
10218
|
+
const oldStore = this.#cache.getAll();
|
|
10219
|
+
const oldValue = this.get(key);
|
|
10220
|
+
if (this.#dotNotation && key.includes(".")) {
|
|
10221
|
+
const full = this.#cache.getAll();
|
|
10222
|
+
const updated = deleteByPath(full, key);
|
|
10223
|
+
this.#cache.replaceAll(updated);
|
|
10224
|
+
} else {
|
|
10225
|
+
this.#cache.delete(key);
|
|
10226
|
+
}
|
|
10227
|
+
this.#notifyKeyChange(key, undefined, oldValue);
|
|
10228
|
+
this.#notifyAnyChange(this.#cache.getAll(), oldStore);
|
|
10229
|
+
}
|
|
10230
|
+
clear() {
|
|
10231
|
+
this.#ensureOpen();
|
|
10232
|
+
const oldStore = this.#cache.getAll();
|
|
10233
|
+
this.#cache.clear();
|
|
10234
|
+
if (this.#defaults && Object.keys(this.#defaults).length > 0) {
|
|
10235
|
+
this.#cache.setMany(Object.entries(this.#defaults));
|
|
10236
|
+
}
|
|
10237
|
+
this.#notifyAnyChange(this.#cache.getAll(), oldStore);
|
|
10238
|
+
}
|
|
10239
|
+
reset(...keys) {
|
|
10240
|
+
this.#ensureOpen();
|
|
10241
|
+
const oldStore = this.#cache.getAll();
|
|
10242
|
+
for (const key of keys) {
|
|
10243
|
+
if (key in this.#defaults) {
|
|
10244
|
+
this.#cache.set(key, this.#defaults[key]);
|
|
10245
|
+
} else {
|
|
10246
|
+
this.#cache.delete(key);
|
|
10247
|
+
}
|
|
10248
|
+
}
|
|
10249
|
+
this.#notifyAnyChange(this.#cache.getAll(), oldStore);
|
|
10250
|
+
}
|
|
10251
|
+
onDidChange(key, callback) {
|
|
10252
|
+
if (!this.#listeners.has(key)) {
|
|
10253
|
+
this.#listeners.set(key, new Set);
|
|
10254
|
+
}
|
|
10255
|
+
const set = this.#listeners.get(key);
|
|
10256
|
+
set.add(callback);
|
|
10257
|
+
return () => {
|
|
10258
|
+
set.delete(callback);
|
|
10259
|
+
if (set.size === 0)
|
|
10260
|
+
this.#listeners.delete(key);
|
|
10261
|
+
};
|
|
10262
|
+
}
|
|
10263
|
+
onDidAnyChange(callback) {
|
|
10264
|
+
this.#anyListeners.add(callback);
|
|
10265
|
+
return () => {
|
|
10266
|
+
this.#anyListeners.delete(callback);
|
|
10267
|
+
};
|
|
10268
|
+
}
|
|
10269
|
+
async flush() {
|
|
10270
|
+
this.#ensureOpen();
|
|
10271
|
+
await this.#cache.flush();
|
|
10272
|
+
}
|
|
10273
|
+
flushSync() {
|
|
10274
|
+
this.#ensureOpen();
|
|
10275
|
+
this.#cache.flushSync();
|
|
10276
|
+
}
|
|
10277
|
+
close() {
|
|
10278
|
+
if (this.#closed)
|
|
10279
|
+
return;
|
|
10280
|
+
this.#stopWatching();
|
|
10281
|
+
this.#cache.flushSync();
|
|
10282
|
+
this.#store.close();
|
|
10283
|
+
this.#closed = true;
|
|
10284
|
+
}
|
|
10285
|
+
*[Symbol.iterator]() {
|
|
10286
|
+
this.#ensureOpen();
|
|
10287
|
+
yield* this.#cache.entries();
|
|
10288
|
+
}
|
|
10289
|
+
#ensureOpen() {
|
|
10290
|
+
if (this.#closed) {
|
|
10291
|
+
throw new ConfigEngineError("This ConfigEngine instance has been closed. Create a new one with ConfigEngine.open().");
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
#validateFull(data) {
|
|
10295
|
+
if (!this.#validator)
|
|
10296
|
+
return;
|
|
10297
|
+
const result = this.#validator.validate(data);
|
|
10298
|
+
if (!result.success) {
|
|
10299
|
+
throw new ValidationError(result.errors);
|
|
10300
|
+
}
|
|
10301
|
+
}
|
|
10302
|
+
#notifyKeyChange(key, newValue, oldValue) {
|
|
10303
|
+
if (newValue === oldValue)
|
|
10304
|
+
return;
|
|
10305
|
+
if (typeof newValue === "object" && typeof oldValue === "object" && JSON.stringify(newValue) === JSON.stringify(oldValue)) {
|
|
10306
|
+
return;
|
|
10307
|
+
}
|
|
10308
|
+
const listeners = this.#listeners.get(key);
|
|
10309
|
+
if (listeners) {
|
|
10310
|
+
for (const cb of listeners) {
|
|
10311
|
+
cb(newValue, oldValue);
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
#notifyAnyChange(newStore, oldStore) {
|
|
10316
|
+
if (JSON.stringify(newStore) === JSON.stringify(oldStore))
|
|
10317
|
+
return;
|
|
10318
|
+
for (const cb of this.#anyListeners) {
|
|
10319
|
+
cb(newStore, oldStore);
|
|
10320
|
+
}
|
|
10321
|
+
}
|
|
10322
|
+
#startWatching() {
|
|
10323
|
+
try {
|
|
10324
|
+
this.#watcher = watch(dirname3(this.#filePath), (_event, filename) => {
|
|
10325
|
+
if (!filename)
|
|
10326
|
+
return;
|
|
10327
|
+
const expectedName = this.#filePath.split(/[\\/]/).pop();
|
|
10328
|
+
if (filename !== expectedName)
|
|
10329
|
+
return;
|
|
10330
|
+
const currentWrite = this.#store.getLastWrite();
|
|
10331
|
+
if (currentWrite > this.#lastKnownWrite) {
|
|
10332
|
+
this.#lastKnownWrite = currentWrite;
|
|
10333
|
+
const oldStore = this.#cache.getAll();
|
|
10334
|
+
this.#cache.reload();
|
|
10335
|
+
const newStore = this.#cache.getAll();
|
|
10336
|
+
this.#notifyAnyChange(newStore, oldStore);
|
|
10337
|
+
}
|
|
10338
|
+
});
|
|
10339
|
+
if (this.#watcher && "unref" in this.#watcher) {
|
|
10340
|
+
this.#watcher.unref();
|
|
10341
|
+
}
|
|
10342
|
+
} catch {}
|
|
10343
|
+
}
|
|
10344
|
+
#stopWatching() {
|
|
10345
|
+
if (this.#watcher) {
|
|
10346
|
+
this.#watcher.close();
|
|
10347
|
+
this.#watcher = null;
|
|
10348
|
+
}
|
|
10349
|
+
}
|
|
10350
|
+
}
|
|
10351
|
+
async function encryptStore(data, encryptor) {
|
|
10352
|
+
const result = {};
|
|
10353
|
+
for (const [key, value] of Object.entries(data)) {
|
|
10354
|
+
result[key] = await encryptor.encrypt(JSON.stringify(value));
|
|
10355
|
+
}
|
|
10356
|
+
return result;
|
|
10357
|
+
}
|
|
10358
|
+
async function decryptStore(data, encryptor) {
|
|
10359
|
+
const result = {};
|
|
10360
|
+
for (const [key, value] of Object.entries(data)) {
|
|
10361
|
+
if (typeof value === "string") {
|
|
10362
|
+
try {
|
|
10363
|
+
const decrypted = await encryptor.decrypt(value);
|
|
10364
|
+
result[key] = JSON.parse(decrypted);
|
|
10365
|
+
} catch {
|
|
10366
|
+
result[key] = value;
|
|
10367
|
+
}
|
|
10368
|
+
} else {
|
|
10369
|
+
result[key] = value;
|
|
10370
|
+
}
|
|
10371
|
+
}
|
|
10372
|
+
return result;
|
|
10373
|
+
}
|
|
10374
|
+
|
|
10375
|
+
// src/utils/state.ts
|
|
8831
10376
|
var LOCAL_STATE_DIRNAME = "contribute-now";
|
|
8832
10377
|
var LOCAL_STATE_CONFIG_NAME = "state";
|
|
8833
10378
|
var LOCAL_STATE_LOCATION_LABEL = `.git/${LOCAL_STATE_DIRNAME}/${LOCAL_STATE_CONFIG_NAME}.db`;
|
|
8834
|
-
var
|
|
8835
|
-
guideRotation: {}
|
|
8836
|
-
};
|
|
10379
|
+
var stateStoreCache = new Map;
|
|
8837
10380
|
function findRepoRoot2(cwd = process.cwd()) {
|
|
8838
10381
|
let current = resolve3(cwd);
|
|
8839
10382
|
while (true) {
|
|
8840
|
-
if (existsSync3(
|
|
10383
|
+
if (existsSync3(join7(current, ".git"))) {
|
|
8841
10384
|
return current;
|
|
8842
10385
|
}
|
|
8843
|
-
const parent =
|
|
10386
|
+
const parent = dirname4(current);
|
|
8844
10387
|
if (parent === current) {
|
|
8845
10388
|
return null;
|
|
8846
10389
|
}
|
|
@@ -8852,13 +10395,13 @@ function resolveGitDir2(cwd = process.cwd()) {
|
|
|
8852
10395
|
if (!repoRoot) {
|
|
8853
10396
|
return null;
|
|
8854
10397
|
}
|
|
8855
|
-
const dotGitPath =
|
|
10398
|
+
const dotGitPath = join7(repoRoot, ".git");
|
|
8856
10399
|
try {
|
|
8857
|
-
const
|
|
8858
|
-
if (
|
|
10400
|
+
const stat2 = statSync3(dotGitPath);
|
|
10401
|
+
if (stat2.isDirectory()) {
|
|
8859
10402
|
return dotGitPath;
|
|
8860
10403
|
}
|
|
8861
|
-
if (!
|
|
10404
|
+
if (!stat2.isFile()) {
|
|
8862
10405
|
return null;
|
|
8863
10406
|
}
|
|
8864
10407
|
const content = readFileSync3(dotGitPath, "utf-8").trim();
|
|
@@ -8876,41 +10419,40 @@ function getLocalStateDir(cwd = process.cwd()) {
|
|
|
8876
10419
|
if (!gitDir) {
|
|
8877
10420
|
return null;
|
|
8878
10421
|
}
|
|
8879
|
-
return
|
|
10422
|
+
return join7(gitDir, LOCAL_STATE_DIRNAME);
|
|
8880
10423
|
}
|
|
8881
|
-
function
|
|
8882
|
-
const
|
|
8883
|
-
if (!
|
|
8884
|
-
return
|
|
8885
|
-
}
|
|
8886
|
-
try {
|
|
8887
|
-
const raw = JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
8888
|
-
const guideRotation = raw.guideRotation && typeof raw.guideRotation === "object" ? raw.guideRotation : {};
|
|
8889
|
-
return {
|
|
8890
|
-
guideRotation: Object.fromEntries(Object.entries(guideRotation).filter((entry) => {
|
|
8891
|
-
const [key, value] = entry;
|
|
8892
|
-
return typeof key === "string" && typeof value === "number" && Number.isFinite(value);
|
|
8893
|
-
}))
|
|
8894
|
-
};
|
|
8895
|
-
} catch {
|
|
8896
|
-
return DEFAULT_LOCAL_STATE;
|
|
10424
|
+
async function openLocalStateStore(cwd = process.cwd()) {
|
|
10425
|
+
const stateDir = getLocalStateDir(cwd);
|
|
10426
|
+
if (!stateDir) {
|
|
10427
|
+
return null;
|
|
8897
10428
|
}
|
|
10429
|
+
return ConfigEngine.open({
|
|
10430
|
+
projectName: "contribute-now",
|
|
10431
|
+
cwd: stateDir,
|
|
10432
|
+
configName: LOCAL_STATE_CONFIG_NAME,
|
|
10433
|
+
defaults: {
|
|
10434
|
+
guideRotation: {}
|
|
10435
|
+
},
|
|
10436
|
+
flushStrategy: "immediate"
|
|
10437
|
+
});
|
|
8898
10438
|
}
|
|
8899
|
-
function
|
|
10439
|
+
async function getLocalStateStore(cwd = process.cwd()) {
|
|
8900
10440
|
const statePath = getLocalStatePath(cwd);
|
|
8901
|
-
|
|
8902
|
-
|
|
10441
|
+
const cacheKey = statePath ?? resolve3(cwd);
|
|
10442
|
+
const existing = stateStoreCache.get(cacheKey);
|
|
10443
|
+
if (existing) {
|
|
10444
|
+
return existing;
|
|
8903
10445
|
}
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
10446
|
+
const storePromise = openLocalStateStore(cwd).catch(() => null);
|
|
10447
|
+
stateStoreCache.set(cacheKey, storePromise);
|
|
10448
|
+
return storePromise;
|
|
8907
10449
|
}
|
|
8908
10450
|
function getLocalStatePath(cwd = process.cwd()) {
|
|
8909
10451
|
const stateDir = getLocalStateDir(cwd);
|
|
8910
10452
|
if (!stateDir) {
|
|
8911
10453
|
return null;
|
|
8912
10454
|
}
|
|
8913
|
-
return
|
|
10455
|
+
return join7(stateDir, `${LOCAL_STATE_CONFIG_NAME}.db`);
|
|
8914
10456
|
}
|
|
8915
10457
|
function getLocalStateLocationLabel(cwd = process.cwd()) {
|
|
8916
10458
|
return getLocalStatePath(cwd) ? LOCAL_STATE_LOCATION_LABEL : null;
|
|
@@ -8920,23 +10462,25 @@ function hasLocalStateStore(cwd = process.cwd()) {
|
|
|
8920
10462
|
return !!statePath && existsSync3(statePath);
|
|
8921
10463
|
}
|
|
8922
10464
|
async function getGuideRotationIndex(commandKey, cwd = process.cwd()) {
|
|
8923
|
-
const
|
|
10465
|
+
const store = await getLocalStateStore(cwd);
|
|
10466
|
+
if (!store) {
|
|
10467
|
+
return 0;
|
|
10468
|
+
}
|
|
10469
|
+
const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
|
|
8924
10470
|
return typeof rotationIndex === "number" ? rotationIndex : 0;
|
|
8925
10471
|
}
|
|
8926
10472
|
async function advanceGuideRotation(commandKey, rotatableCount, cwd = process.cwd()) {
|
|
8927
10473
|
if (rotatableCount <= 0) {
|
|
8928
10474
|
return;
|
|
8929
10475
|
}
|
|
8930
|
-
const
|
|
8931
|
-
|
|
10476
|
+
const store = await getLocalStateStore(cwd);
|
|
10477
|
+
if (!store) {
|
|
10478
|
+
return;
|
|
10479
|
+
}
|
|
10480
|
+
const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
|
|
8932
10481
|
const currentIndex = typeof rotationIndex === "number" ? rotationIndex : 0;
|
|
8933
10482
|
const nextIndex = (currentIndex + 1) % rotatableCount;
|
|
8934
|
-
|
|
8935
|
-
guideRotation: {
|
|
8936
|
-
...state.guideRotation,
|
|
8937
|
-
[commandKey]: nextIndex
|
|
8938
|
-
}
|
|
8939
|
-
}, cwd);
|
|
10483
|
+
store.set(`guideRotation.${commandKey}`, nextIndex);
|
|
8940
10484
|
}
|
|
8941
10485
|
|
|
8942
10486
|
// src/utils/tips.ts
|
|
@@ -10612,79 +12156,54 @@ async function multiSelectPrompt(message, choices) {
|
|
|
10612
12156
|
}
|
|
10613
12157
|
|
|
10614
12158
|
// src/utils/copilot.ts
|
|
10615
|
-
|
|
12159
|
+
init_dist2();
|
|
10616
12160
|
|
|
10617
12161
|
// src/utils/secrets.ts
|
|
10618
|
-
|
|
10619
|
-
import {
|
|
10620
|
-
import {
|
|
12162
|
+
init_dist();
|
|
12163
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
12164
|
+
import { homedir as homedir3 } from "node:os";
|
|
12165
|
+
import { resolve as resolve4 } from "node:path";
|
|
10621
12166
|
var CONTRIBUTE_NOW_SECRETS_DIRNAME = ".contribute-now";
|
|
10622
12167
|
var CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME = "secrets";
|
|
10623
12168
|
var OLLAMA_CLOUD_API_KEY = "ollama.cloud.apiKey";
|
|
10624
|
-
|
|
12169
|
+
var secretsStoreCache = new Map;
|
|
12170
|
+
function getSecretsStorePath(baseDir = homedir3()) {
|
|
10625
12171
|
return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
|
|
10626
12172
|
}
|
|
10627
|
-
function
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
if (!existsSync5(filePath)) {
|
|
10633
|
-
return null;
|
|
12173
|
+
async function getSecretsStore(baseDir = homedir3(), createIfMissing = true) {
|
|
12174
|
+
const storePath = getSecretsStorePath(baseDir);
|
|
12175
|
+
const existing = secretsStoreCache.get(storePath);
|
|
12176
|
+
if (existing) {
|
|
12177
|
+
return existing;
|
|
10634
12178
|
}
|
|
10635
|
-
|
|
10636
|
-
const parsed = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
10637
|
-
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
10638
|
-
} catch {
|
|
12179
|
+
if (!createIfMissing && !existsSync5(storePath)) {
|
|
10639
12180
|
return null;
|
|
10640
12181
|
}
|
|
12182
|
+
const storePromise = SecretsEngine.open({ path: storePath });
|
|
12183
|
+
secretsStoreCache.set(storePath, storePromise);
|
|
12184
|
+
return storePromise;
|
|
10641
12185
|
}
|
|
10642
|
-
function
|
|
10643
|
-
|
|
10644
|
-
const filePath = getSecretsFilePath(baseDir);
|
|
10645
|
-
mkdirSync4(storePath, { recursive: true, mode: 448 });
|
|
10646
|
-
writeFileSync4(filePath, `${JSON.stringify(store, null, 2)}
|
|
10647
|
-
`, {
|
|
10648
|
-
encoding: "utf-8",
|
|
10649
|
-
mode: 384
|
|
10650
|
-
});
|
|
10651
|
-
try {
|
|
10652
|
-
chmodSync(storePath, 448);
|
|
10653
|
-
chmodSync(filePath, 384);
|
|
10654
|
-
} catch {}
|
|
10655
|
-
}
|
|
10656
|
-
function hasSecretsStore(baseDir = homedir()) {
|
|
10657
|
-
return existsSync5(getSecretsFilePath(baseDir));
|
|
12186
|
+
function hasSecretsStore(baseDir = homedir3()) {
|
|
12187
|
+
return existsSync5(getSecretsStorePath(baseDir));
|
|
10658
12188
|
}
|
|
10659
|
-
async function hasOllamaCloudApiKey(baseDir =
|
|
10660
|
-
|
|
12189
|
+
async function hasOllamaCloudApiKey(baseDir = homedir3()) {
|
|
12190
|
+
const store = await getSecretsStore(baseDir, false);
|
|
12191
|
+
return store ? store.has(OLLAMA_CLOUD_API_KEY) : false;
|
|
10661
12192
|
}
|
|
10662
|
-
async function getOllamaCloudApiKey(baseDir =
|
|
10663
|
-
|
|
12193
|
+
async function getOllamaCloudApiKey(baseDir = homedir3()) {
|
|
12194
|
+
const store = await getSecretsStore(baseDir, false);
|
|
12195
|
+
return store ? store.get(OLLAMA_CLOUD_API_KEY) : null;
|
|
10664
12196
|
}
|
|
10665
|
-
async function setOllamaCloudApiKey(value, baseDir =
|
|
10666
|
-
const
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
[OLLAMA_CLOUD_API_KEY]: value
|
|
10670
|
-
}, baseDir);
|
|
10671
|
-
}
|
|
10672
|
-
async function deleteOllamaCloudApiKey(baseDir = homedir()) {
|
|
10673
|
-
const existingStore = readSecretsStore(baseDir);
|
|
10674
|
-
if (!existingStore || !(OLLAMA_CLOUD_API_KEY in existingStore)) {
|
|
10675
|
-
return false;
|
|
12197
|
+
async function setOllamaCloudApiKey(value, baseDir = homedir3()) {
|
|
12198
|
+
const store = await getSecretsStore(baseDir);
|
|
12199
|
+
if (!store) {
|
|
12200
|
+
throw new Error("Secrets store could not be opened");
|
|
10676
12201
|
}
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
rmSync(getSecretsStorePath(baseDir), { recursive: true, force: true });
|
|
10683
|
-
} catch {}
|
|
10684
|
-
return true;
|
|
10685
|
-
}
|
|
10686
|
-
writeSecretsStore(nextStore, baseDir);
|
|
10687
|
-
return true;
|
|
12202
|
+
await store.set(OLLAMA_CLOUD_API_KEY, value);
|
|
12203
|
+
}
|
|
12204
|
+
async function deleteOllamaCloudApiKey(baseDir = homedir3()) {
|
|
12205
|
+
const store = await getSecretsStore(baseDir, false);
|
|
12206
|
+
return store ? store.delete(OLLAMA_CLOUD_API_KEY) : false;
|
|
10688
12207
|
}
|
|
10689
12208
|
|
|
10690
12209
|
// src/utils/copilot.ts
|
|
@@ -12670,13 +14189,13 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12670
14189
|
const modelLookupApiKey = ollamaApiKeyAction === "set" ? ollamaApiKey ?? null : ollamaApiKeyAction === "keep" ? await getOllamaCloudApiKey() : null;
|
|
12671
14190
|
aiModel = await promptForOllamaCloudModelSelection(modelLookupApiKey, current.aiProvider === "ollama-cloud" ? current.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL : DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
12672
14191
|
} else if (hasExistingOllamaApiKey) {
|
|
12673
|
-
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from
|
|
14192
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from secrets-engine?");
|
|
12674
14193
|
if (shouldDeleteStoredKey) {
|
|
12675
14194
|
ollamaApiKeyAction = "delete";
|
|
12676
14195
|
}
|
|
12677
14196
|
}
|
|
12678
14197
|
} else if (hasExistingOllamaApiKey) {
|
|
12679
|
-
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from
|
|
14198
|
+
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from secrets-engine?");
|
|
12680
14199
|
if (shouldDeleteStoredKey) {
|
|
12681
14200
|
ollamaApiKeyAction = "delete";
|
|
12682
14201
|
}
|
|
@@ -12703,7 +14222,7 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12703
14222
|
async function applyOllamaApiKeyEdit(result) {
|
|
12704
14223
|
if (result.ollamaApiKeyAction === "set" && result.ollamaApiKey) {
|
|
12705
14224
|
await setOllamaCloudApiKey(result.ollamaApiKey);
|
|
12706
|
-
success("Stored Ollama Cloud API key in
|
|
14225
|
+
success("Stored Ollama Cloud API key in secrets-engine.");
|
|
12707
14226
|
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
12708
14227
|
return;
|
|
12709
14228
|
}
|
|
@@ -12826,7 +14345,7 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
|
12826
14345
|
// package.json
|
|
12827
14346
|
var package_default = {
|
|
12828
14347
|
name: "contribute-now",
|
|
12829
|
-
version: "0.7.0-dev.
|
|
14348
|
+
version: "0.7.0-dev.45c1376",
|
|
12830
14349
|
description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
|
|
12831
14350
|
type: "module",
|
|
12832
14351
|
bin: {
|
|
@@ -12874,7 +14393,9 @@ var package_default = {
|
|
|
12874
14393
|
dependencies: {
|
|
12875
14394
|
"@clack/prompts": "^1.0.1",
|
|
12876
14395
|
"@github/copilot-sdk": "^0.1.25",
|
|
14396
|
+
"@wgtechlabs/config-engine": "^0.1.0",
|
|
12877
14397
|
"@wgtechlabs/log-engine": "^2.3.1",
|
|
14398
|
+
"@wgtechlabs/secrets-engine": "^2.0.0",
|
|
12878
14399
|
citty: "^0.1.6",
|
|
12879
14400
|
figlet: "^1.10.0",
|
|
12880
14401
|
picocolors: "^1.1.1"
|
|
@@ -12991,7 +14512,7 @@ async function depsSection() {
|
|
|
12991
14512
|
});
|
|
12992
14513
|
}
|
|
12993
14514
|
try {
|
|
12994
|
-
await Promise.resolve().then(() => (
|
|
14515
|
+
await Promise.resolve().then(() => (init_dist2(), exports_dist3));
|
|
12995
14516
|
checks.push({ label: "Copilot SDK importable", ok: true });
|
|
12996
14517
|
} catch {
|
|
12997
14518
|
checks.push({
|
|
@@ -13079,7 +14600,7 @@ async function configSection() {
|
|
|
13079
14600
|
label: hasApiKey ? "Ollama Cloud API key present" : "Ollama Cloud API key missing",
|
|
13080
14601
|
ok: true,
|
|
13081
14602
|
warning: !hasApiKey,
|
|
13082
|
-
detail: hasSecretsStore() ? "stored in
|
|
14603
|
+
detail: hasSecretsStore() ? "stored in secrets-engine" : "run `contrib setup` to save it"
|
|
13083
14604
|
});
|
|
13084
14605
|
}
|
|
13085
14606
|
}
|
|
@@ -13273,15 +14794,15 @@ var doctor_default = defineCommand({
|
|
|
13273
14794
|
});
|
|
13274
14795
|
|
|
13275
14796
|
// src/commands/hook.ts
|
|
13276
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
13277
|
-
import { join as
|
|
14797
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
14798
|
+
import { join as join9 } from "node:path";
|
|
13278
14799
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
13279
14800
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
13280
14801
|
function getHooksDir(cwd = process.cwd()) {
|
|
13281
|
-
return
|
|
14802
|
+
return join9(cwd, ".git", "hooks");
|
|
13282
14803
|
}
|
|
13283
14804
|
function getHookPath(cwd = process.cwd()) {
|
|
13284
|
-
return
|
|
14805
|
+
return join9(getHooksDir(cwd), "commit-msg");
|
|
13285
14806
|
}
|
|
13286
14807
|
function generateHookScript() {
|
|
13287
14808
|
return `#!/bin/sh
|
|
@@ -13357,7 +14878,7 @@ async function installHook() {
|
|
|
13357
14878
|
const hookPath = getHookPath();
|
|
13358
14879
|
const hooksDir = getHooksDir();
|
|
13359
14880
|
if (existsSync6(hookPath)) {
|
|
13360
|
-
const existing =
|
|
14881
|
+
const existing = readFileSync4(hookPath, "utf-8");
|
|
13361
14882
|
if (!existing.includes(HOOK_MARKER)) {
|
|
13362
14883
|
error("A commit-msg hook already exists and was not installed by contribute-now.");
|
|
13363
14884
|
warn(`Path: ${hookPath}`);
|
|
@@ -13367,9 +14888,9 @@ async function installHook() {
|
|
|
13367
14888
|
info("Updating existing contribute-now hook...");
|
|
13368
14889
|
}
|
|
13369
14890
|
if (!existsSync6(hooksDir)) {
|
|
13370
|
-
|
|
14891
|
+
mkdirSync4(hooksDir, { recursive: true });
|
|
13371
14892
|
}
|
|
13372
|
-
|
|
14893
|
+
writeFileSync3(hookPath, generateHookScript(), { mode: 493 });
|
|
13373
14894
|
success(`commit-msg hook installed.`);
|
|
13374
14895
|
info(`Convention: ${import_picocolors12.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
13375
14896
|
info(`Path: ${import_picocolors12.default.dim(hookPath)}`, "");
|
|
@@ -13382,12 +14903,12 @@ async function uninstallHook() {
|
|
|
13382
14903
|
info("No commit-msg hook found. Nothing to uninstall.");
|
|
13383
14904
|
return;
|
|
13384
14905
|
}
|
|
13385
|
-
const content =
|
|
14906
|
+
const content = readFileSync4(hookPath, "utf-8");
|
|
13386
14907
|
if (!content.includes(HOOK_MARKER)) {
|
|
13387
14908
|
error("The commit-msg hook was not installed by contribute-now. Leaving it untouched.");
|
|
13388
14909
|
process.exit(1);
|
|
13389
14910
|
}
|
|
13390
|
-
|
|
14911
|
+
rmSync(hookPath);
|
|
13391
14912
|
success("commit-msg hook removed.");
|
|
13392
14913
|
}
|
|
13393
14914
|
|
|
@@ -14068,7 +15589,7 @@ var setup_default = defineCommand({
|
|
|
14068
15589
|
if (enableAI) {
|
|
14069
15590
|
const providerChoice = await selectPrompt("Which AI provider should this clone use?", [
|
|
14070
15591
|
"GitHub Copilot — use your existing GitHub/Copilot auth",
|
|
14071
|
-
"Ollama Cloud — use an API key stored
|
|
15592
|
+
"Ollama Cloud — use an API key stored with secrets-engine"
|
|
14072
15593
|
]);
|
|
14073
15594
|
aiProvider = providerChoice.startsWith("Ollama Cloud") ? "ollama-cloud" : "copilot";
|
|
14074
15595
|
if (aiProvider === "ollama-cloud") {
|
|
@@ -14080,7 +15601,7 @@ var setup_default = defineCommand({
|
|
|
14080
15601
|
aiModel = await promptForOllamaCloudModel(apiKey);
|
|
14081
15602
|
try {
|
|
14082
15603
|
await setOllamaCloudApiKey(apiKey);
|
|
14083
|
-
success("Stored Ollama Cloud API key in
|
|
15604
|
+
success("Stored Ollama Cloud API key in secrets-engine.");
|
|
14084
15605
|
info(`Secrets path: ${import_picocolors15.default.bold(getSecretsStorePath())}`);
|
|
14085
15606
|
} catch (err) {
|
|
14086
15607
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -15359,7 +16880,7 @@ var sync_default = defineCommand({
|
|
|
15359
16880
|
});
|
|
15360
16881
|
|
|
15361
16882
|
// src/commands/update.ts
|
|
15362
|
-
import { readFileSync as
|
|
16883
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
15363
16884
|
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
15364
16885
|
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
15365
16886
|
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
@@ -15563,7 +17084,7 @@ var update_default = defineCommand({
|
|
|
15563
17084
|
let conflictDiff = "";
|
|
15564
17085
|
for (const file of conflictFiles.slice(0, 3)) {
|
|
15565
17086
|
try {
|
|
15566
|
-
const content =
|
|
17087
|
+
const content = readFileSync5(file, "utf-8");
|
|
15567
17088
|
if (content.includes("<<<<<<<")) {
|
|
15568
17089
|
conflictDiff += `
|
|
15569
17090
|
--- ${file} ---
|
|
@@ -15604,7 +17125,7 @@ ${import_picocolors21.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
15604
17125
|
});
|
|
15605
17126
|
|
|
15606
17127
|
// src/commands/validate.ts
|
|
15607
|
-
import { readFileSync as
|
|
17128
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
15608
17129
|
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
15609
17130
|
var validate_default = defineCommand({
|
|
15610
17131
|
meta: {
|
|
@@ -15634,7 +17155,7 @@ var validate_default = defineCommand({
|
|
|
15634
17155
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
15635
17156
|
process.exit(0);
|
|
15636
17157
|
}
|
|
15637
|
-
const message = args.file ?
|
|
17158
|
+
const message = args.file ? readFileSync6(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
|
|
15638
17159
|
if (!message) {
|
|
15639
17160
|
error("No commit message provided. Pass a message or use --file <path>.");
|
|
15640
17161
|
process.exit(1);
|