contribute-now 0.7.0 → 0.7.1-dev.42d9d46
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 +128 -1649
- package/package.json +1 -3
package/dist/index.js
CHANGED
|
@@ -1298,579 +1298,6 @@ 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
|
-
|
|
1874
1301
|
// node_modules/sisteransi/src/index.js
|
|
1875
1302
|
var require_src = __commonJS((exports, module) => {
|
|
1876
1303
|
var ESC = "\x1B";
|
|
@@ -5430,7 +4857,7 @@ var init_session = __esm(() => {
|
|
|
5430
4857
|
import { spawn } from "node:child_process";
|
|
5431
4858
|
import { existsSync as existsSync4 } from "node:fs";
|
|
5432
4859
|
import { Socket } from "node:net";
|
|
5433
|
-
import { dirname as
|
|
4860
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
5434
4861
|
import { fileURLToPath } from "node:url";
|
|
5435
4862
|
function isZodSchema(value) {
|
|
5436
4863
|
return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
|
|
@@ -5452,7 +4879,7 @@ function getNodeExecPath() {
|
|
|
5452
4879
|
function getBundledCliPath() {
|
|
5453
4880
|
const sdkUrl = import.meta.resolve("@github/copilot/sdk");
|
|
5454
4881
|
const sdkPath = fileURLToPath(sdkUrl);
|
|
5455
|
-
return
|
|
4882
|
+
return join4(dirname4(dirname4(sdkPath)), "index.js");
|
|
5456
4883
|
}
|
|
5457
4884
|
|
|
5458
4885
|
class CopilotClient {
|
|
@@ -6204,14 +5631,14 @@ var approveAll = () => ({ kind: "approved" });
|
|
|
6204
5631
|
var init_types2 = () => {};
|
|
6205
5632
|
|
|
6206
5633
|
// node_modules/@github/copilot-sdk/dist/index.js
|
|
6207
|
-
var
|
|
6208
|
-
__export(
|
|
5634
|
+
var exports_dist2 = {};
|
|
5635
|
+
__export(exports_dist2, {
|
|
6209
5636
|
defineTool: () => defineTool,
|
|
6210
5637
|
approveAll: () => approveAll,
|
|
6211
5638
|
CopilotSession: () => CopilotSession,
|
|
6212
5639
|
CopilotClient: () => CopilotClient
|
|
6213
5640
|
});
|
|
6214
|
-
var
|
|
5641
|
+
var init_dist = __esm(() => {
|
|
6215
5642
|
init_client();
|
|
6216
5643
|
init_session();
|
|
6217
5644
|
init_types2();
|
|
@@ -9399,991 +8826,21 @@ var LogEngine = {
|
|
|
9399
8826
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
9400
8827
|
|
|
9401
8828
|
// src/utils/state.ts
|
|
9402
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
9403
|
-
import { dirname as
|
|
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
|
|
8829
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8830
|
+
import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
|
|
10376
8831
|
var LOCAL_STATE_DIRNAME = "contribute-now";
|
|
10377
8832
|
var LOCAL_STATE_CONFIG_NAME = "state";
|
|
10378
8833
|
var LOCAL_STATE_LOCATION_LABEL = `.git/${LOCAL_STATE_DIRNAME}/${LOCAL_STATE_CONFIG_NAME}.db`;
|
|
10379
|
-
var
|
|
8834
|
+
var DEFAULT_LOCAL_STATE = {
|
|
8835
|
+
guideRotation: {}
|
|
8836
|
+
};
|
|
10380
8837
|
function findRepoRoot2(cwd = process.cwd()) {
|
|
10381
8838
|
let current = resolve3(cwd);
|
|
10382
8839
|
while (true) {
|
|
10383
|
-
if (existsSync3(
|
|
8840
|
+
if (existsSync3(join3(current, ".git"))) {
|
|
10384
8841
|
return current;
|
|
10385
8842
|
}
|
|
10386
|
-
const parent =
|
|
8843
|
+
const parent = dirname3(current);
|
|
10387
8844
|
if (parent === current) {
|
|
10388
8845
|
return null;
|
|
10389
8846
|
}
|
|
@@ -10395,13 +8852,13 @@ function resolveGitDir2(cwd = process.cwd()) {
|
|
|
10395
8852
|
if (!repoRoot) {
|
|
10396
8853
|
return null;
|
|
10397
8854
|
}
|
|
10398
|
-
const dotGitPath =
|
|
8855
|
+
const dotGitPath = join3(repoRoot, ".git");
|
|
10399
8856
|
try {
|
|
10400
|
-
const
|
|
10401
|
-
if (
|
|
8857
|
+
const stat = statSync3(dotGitPath);
|
|
8858
|
+
if (stat.isDirectory()) {
|
|
10402
8859
|
return dotGitPath;
|
|
10403
8860
|
}
|
|
10404
|
-
if (!
|
|
8861
|
+
if (!stat.isFile()) {
|
|
10405
8862
|
return null;
|
|
10406
8863
|
}
|
|
10407
8864
|
const content = readFileSync3(dotGitPath, "utf-8").trim();
|
|
@@ -10419,40 +8876,41 @@ function getLocalStateDir(cwd = process.cwd()) {
|
|
|
10419
8876
|
if (!gitDir) {
|
|
10420
8877
|
return null;
|
|
10421
8878
|
}
|
|
10422
|
-
return
|
|
8879
|
+
return join3(gitDir, LOCAL_STATE_DIRNAME);
|
|
10423
8880
|
}
|
|
10424
|
-
|
|
10425
|
-
const
|
|
10426
|
-
if (!
|
|
10427
|
-
return
|
|
8881
|
+
function readLocalState(cwd = process.cwd()) {
|
|
8882
|
+
const statePath = getLocalStatePath(cwd);
|
|
8883
|
+
if (!statePath || !existsSync3(statePath)) {
|
|
8884
|
+
return DEFAULT_LOCAL_STATE;
|
|
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;
|
|
10428
8897
|
}
|
|
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
|
-
});
|
|
10438
8898
|
}
|
|
10439
|
-
|
|
8899
|
+
function writeLocalState(state, cwd = process.cwd()) {
|
|
10440
8900
|
const statePath = getLocalStatePath(cwd);
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
if (existing) {
|
|
10444
|
-
return existing;
|
|
8901
|
+
if (!statePath) {
|
|
8902
|
+
return;
|
|
10445
8903
|
}
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
8904
|
+
mkdirSync3(dirname3(statePath), { recursive: true });
|
|
8905
|
+
writeFileSync3(statePath, `${JSON.stringify(state, null, 2)}
|
|
8906
|
+
`, "utf-8");
|
|
10449
8907
|
}
|
|
10450
8908
|
function getLocalStatePath(cwd = process.cwd()) {
|
|
10451
8909
|
const stateDir = getLocalStateDir(cwd);
|
|
10452
8910
|
if (!stateDir) {
|
|
10453
8911
|
return null;
|
|
10454
8912
|
}
|
|
10455
|
-
return
|
|
8913
|
+
return join3(stateDir, `${LOCAL_STATE_CONFIG_NAME}.db`);
|
|
10456
8914
|
}
|
|
10457
8915
|
function getLocalStateLocationLabel(cwd = process.cwd()) {
|
|
10458
8916
|
return getLocalStatePath(cwd) ? LOCAL_STATE_LOCATION_LABEL : null;
|
|
@@ -10462,25 +8920,23 @@ function hasLocalStateStore(cwd = process.cwd()) {
|
|
|
10462
8920
|
return !!statePath && existsSync3(statePath);
|
|
10463
8921
|
}
|
|
10464
8922
|
async function getGuideRotationIndex(commandKey, cwd = process.cwd()) {
|
|
10465
|
-
const
|
|
10466
|
-
if (!store) {
|
|
10467
|
-
return 0;
|
|
10468
|
-
}
|
|
10469
|
-
const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
|
|
8923
|
+
const rotationIndex = readLocalState(cwd).guideRotation[commandKey];
|
|
10470
8924
|
return typeof rotationIndex === "number" ? rotationIndex : 0;
|
|
10471
8925
|
}
|
|
10472
8926
|
async function advanceGuideRotation(commandKey, rotatableCount, cwd = process.cwd()) {
|
|
10473
8927
|
if (rotatableCount <= 0) {
|
|
10474
8928
|
return;
|
|
10475
8929
|
}
|
|
10476
|
-
const
|
|
10477
|
-
|
|
10478
|
-
return;
|
|
10479
|
-
}
|
|
10480
|
-
const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
|
|
8930
|
+
const state = readLocalState(cwd);
|
|
8931
|
+
const rotationIndex = state.guideRotation[commandKey];
|
|
10481
8932
|
const currentIndex = typeof rotationIndex === "number" ? rotationIndex : 0;
|
|
10482
8933
|
const nextIndex = (currentIndex + 1) % rotatableCount;
|
|
10483
|
-
|
|
8934
|
+
writeLocalState({
|
|
8935
|
+
guideRotation: {
|
|
8936
|
+
...state.guideRotation,
|
|
8937
|
+
[commandKey]: nextIndex
|
|
8938
|
+
}
|
|
8939
|
+
}, cwd);
|
|
10484
8940
|
}
|
|
10485
8941
|
|
|
10486
8942
|
// src/utils/tips.ts
|
|
@@ -12156,54 +10612,79 @@ async function multiSelectPrompt(message, choices) {
|
|
|
12156
10612
|
}
|
|
12157
10613
|
|
|
12158
10614
|
// src/utils/copilot.ts
|
|
12159
|
-
|
|
10615
|
+
init_dist();
|
|
12160
10616
|
|
|
12161
10617
|
// src/utils/secrets.ts
|
|
12162
|
-
|
|
12163
|
-
import {
|
|
12164
|
-
import {
|
|
12165
|
-
import { resolve as resolve4 } from "node:path";
|
|
10618
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
10619
|
+
import { homedir } from "node:os";
|
|
10620
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
12166
10621
|
var CONTRIBUTE_NOW_SECRETS_DIRNAME = ".contribute-now";
|
|
12167
10622
|
var CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME = "secrets";
|
|
12168
10623
|
var OLLAMA_CLOUD_API_KEY = "ollama.cloud.apiKey";
|
|
12169
|
-
|
|
12170
|
-
function getSecretsStorePath(baseDir = homedir3()) {
|
|
10624
|
+
function getSecretsStorePath(baseDir = homedir()) {
|
|
12171
10625
|
return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
|
|
12172
10626
|
}
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
10627
|
+
function getSecretsFilePath(baseDir = homedir()) {
|
|
10628
|
+
return join5(getSecretsStorePath(baseDir), "store.json");
|
|
10629
|
+
}
|
|
10630
|
+
function readSecretsStore(baseDir = homedir()) {
|
|
10631
|
+
const filePath = getSecretsFilePath(baseDir);
|
|
10632
|
+
if (!existsSync5(filePath)) {
|
|
10633
|
+
return null;
|
|
12178
10634
|
}
|
|
12179
|
-
|
|
10635
|
+
try {
|
|
10636
|
+
const parsed = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
10637
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
10638
|
+
} catch {
|
|
12180
10639
|
return null;
|
|
12181
10640
|
}
|
|
12182
|
-
const storePromise = SecretsEngine.open({ path: storePath });
|
|
12183
|
-
secretsStoreCache.set(storePath, storePromise);
|
|
12184
|
-
return storePromise;
|
|
12185
10641
|
}
|
|
12186
|
-
function
|
|
12187
|
-
|
|
10642
|
+
function writeSecretsStore(store, baseDir = homedir()) {
|
|
10643
|
+
const storePath = getSecretsStorePath(baseDir);
|
|
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 {}
|
|
12188
10655
|
}
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
return store ? store.has(OLLAMA_CLOUD_API_KEY) : false;
|
|
10656
|
+
function hasSecretsStore(baseDir = homedir()) {
|
|
10657
|
+
return existsSync5(getSecretsFilePath(baseDir));
|
|
12192
10658
|
}
|
|
12193
|
-
async function
|
|
12194
|
-
|
|
12195
|
-
return store ? store.get(OLLAMA_CLOUD_API_KEY) : null;
|
|
10659
|
+
async function hasOllamaCloudApiKey(baseDir = homedir()) {
|
|
10660
|
+
return typeof readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] === "string";
|
|
12196
10661
|
}
|
|
12197
|
-
async function
|
|
12198
|
-
|
|
12199
|
-
if (!store) {
|
|
12200
|
-
throw new Error("Secrets store could not be opened");
|
|
12201
|
-
}
|
|
12202
|
-
await store.set(OLLAMA_CLOUD_API_KEY, value);
|
|
10662
|
+
async function getOllamaCloudApiKey(baseDir = homedir()) {
|
|
10663
|
+
return readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] ?? null;
|
|
12203
10664
|
}
|
|
12204
|
-
async function
|
|
12205
|
-
const
|
|
12206
|
-
|
|
10665
|
+
async function setOllamaCloudApiKey(value, baseDir = homedir()) {
|
|
10666
|
+
const existingStore = readSecretsStore(baseDir) ?? {};
|
|
10667
|
+
writeSecretsStore({
|
|
10668
|
+
...existingStore,
|
|
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;
|
|
10676
|
+
}
|
|
10677
|
+
const nextStore = { ...existingStore };
|
|
10678
|
+
delete nextStore[OLLAMA_CLOUD_API_KEY];
|
|
10679
|
+
if (Object.keys(nextStore).length === 0) {
|
|
10680
|
+
try {
|
|
10681
|
+
rmSync(getSecretsFilePath(baseDir), { force: true });
|
|
10682
|
+
rmSync(getSecretsStorePath(baseDir), { recursive: true, force: true });
|
|
10683
|
+
} catch {}
|
|
10684
|
+
return true;
|
|
10685
|
+
}
|
|
10686
|
+
writeSecretsStore(nextStore, baseDir);
|
|
10687
|
+
return true;
|
|
12207
10688
|
}
|
|
12208
10689
|
|
|
12209
10690
|
// src/utils/copilot.ts
|
|
@@ -14189,13 +12670,13 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
14189
12670
|
const modelLookupApiKey = ollamaApiKeyAction === "set" ? ollamaApiKey ?? null : ollamaApiKeyAction === "keep" ? await getOllamaCloudApiKey() : null;
|
|
14190
12671
|
aiModel = await promptForOllamaCloudModelSelection(modelLookupApiKey, current.aiProvider === "ollama-cloud" ? current.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL : DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
14191
12672
|
} else if (hasExistingOllamaApiKey) {
|
|
14192
|
-
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from secrets
|
|
12673
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
14193
12674
|
if (shouldDeleteStoredKey) {
|
|
14194
12675
|
ollamaApiKeyAction = "delete";
|
|
14195
12676
|
}
|
|
14196
12677
|
}
|
|
14197
12678
|
} else if (hasExistingOllamaApiKey) {
|
|
14198
|
-
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from secrets
|
|
12679
|
+
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
14199
12680
|
if (shouldDeleteStoredKey) {
|
|
14200
12681
|
ollamaApiKeyAction = "delete";
|
|
14201
12682
|
}
|
|
@@ -14222,7 +12703,7 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
14222
12703
|
async function applyOllamaApiKeyEdit(result) {
|
|
14223
12704
|
if (result.ollamaApiKeyAction === "set" && result.ollamaApiKey) {
|
|
14224
12705
|
await setOllamaCloudApiKey(result.ollamaApiKey);
|
|
14225
|
-
success("Stored Ollama Cloud API key in secrets
|
|
12706
|
+
success("Stored Ollama Cloud API key in the local secrets store.");
|
|
14226
12707
|
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
14227
12708
|
return;
|
|
14228
12709
|
}
|
|
@@ -14345,7 +12826,7 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
|
14345
12826
|
// package.json
|
|
14346
12827
|
var package_default = {
|
|
14347
12828
|
name: "contribute-now",
|
|
14348
|
-
version: "0.7.
|
|
12829
|
+
version: "0.7.1-dev.42d9d46",
|
|
14349
12830
|
description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
|
|
14350
12831
|
type: "module",
|
|
14351
12832
|
bin: {
|
|
@@ -14393,9 +12874,7 @@ var package_default = {
|
|
|
14393
12874
|
dependencies: {
|
|
14394
12875
|
"@clack/prompts": "^1.0.1",
|
|
14395
12876
|
"@github/copilot-sdk": "^0.1.25",
|
|
14396
|
-
"@wgtechlabs/config-engine": "^0.1.0",
|
|
14397
12877
|
"@wgtechlabs/log-engine": "^2.3.1",
|
|
14398
|
-
"@wgtechlabs/secrets-engine": "^2.0.0",
|
|
14399
12878
|
citty: "^0.1.6",
|
|
14400
12879
|
figlet: "^1.10.0",
|
|
14401
12880
|
picocolors: "^1.1.1"
|
|
@@ -14512,7 +12991,7 @@ async function depsSection() {
|
|
|
14512
12991
|
});
|
|
14513
12992
|
}
|
|
14514
12993
|
try {
|
|
14515
|
-
await Promise.resolve().then(() => (
|
|
12994
|
+
await Promise.resolve().then(() => (init_dist(), exports_dist2));
|
|
14516
12995
|
checks.push({ label: "Copilot SDK importable", ok: true });
|
|
14517
12996
|
} catch {
|
|
14518
12997
|
checks.push({
|
|
@@ -14600,7 +13079,7 @@ async function configSection() {
|
|
|
14600
13079
|
label: hasApiKey ? "Ollama Cloud API key present" : "Ollama Cloud API key missing",
|
|
14601
13080
|
ok: true,
|
|
14602
13081
|
warning: !hasApiKey,
|
|
14603
|
-
detail: hasSecretsStore() ? "stored in secrets
|
|
13082
|
+
detail: hasSecretsStore() ? "stored in the local secrets store" : "run `contrib setup` to save it"
|
|
14604
13083
|
});
|
|
14605
13084
|
}
|
|
14606
13085
|
}
|
|
@@ -14794,15 +13273,15 @@ var doctor_default = defineCommand({
|
|
|
14794
13273
|
});
|
|
14795
13274
|
|
|
14796
13275
|
// src/commands/hook.ts
|
|
14797
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
14798
|
-
import { join as
|
|
13276
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13277
|
+
import { join as join6 } from "node:path";
|
|
14799
13278
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
14800
13279
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
14801
13280
|
function getHooksDir(cwd = process.cwd()) {
|
|
14802
|
-
return
|
|
13281
|
+
return join6(cwd, ".git", "hooks");
|
|
14803
13282
|
}
|
|
14804
13283
|
function getHookPath(cwd = process.cwd()) {
|
|
14805
|
-
return
|
|
13284
|
+
return join6(getHooksDir(cwd), "commit-msg");
|
|
14806
13285
|
}
|
|
14807
13286
|
function generateHookScript() {
|
|
14808
13287
|
return `#!/bin/sh
|
|
@@ -14878,7 +13357,7 @@ async function installHook() {
|
|
|
14878
13357
|
const hookPath = getHookPath();
|
|
14879
13358
|
const hooksDir = getHooksDir();
|
|
14880
13359
|
if (existsSync6(hookPath)) {
|
|
14881
|
-
const existing =
|
|
13360
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
14882
13361
|
if (!existing.includes(HOOK_MARKER)) {
|
|
14883
13362
|
error("A commit-msg hook already exists and was not installed by contribute-now.");
|
|
14884
13363
|
warn(`Path: ${hookPath}`);
|
|
@@ -14888,9 +13367,9 @@ async function installHook() {
|
|
|
14888
13367
|
info("Updating existing contribute-now hook...");
|
|
14889
13368
|
}
|
|
14890
13369
|
if (!existsSync6(hooksDir)) {
|
|
14891
|
-
|
|
13370
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
14892
13371
|
}
|
|
14893
|
-
|
|
13372
|
+
writeFileSync5(hookPath, generateHookScript(), { mode: 493 });
|
|
14894
13373
|
success(`commit-msg hook installed.`);
|
|
14895
13374
|
info(`Convention: ${import_picocolors12.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
14896
13375
|
info(`Path: ${import_picocolors12.default.dim(hookPath)}`, "");
|
|
@@ -14903,12 +13382,12 @@ async function uninstallHook() {
|
|
|
14903
13382
|
info("No commit-msg hook found. Nothing to uninstall.");
|
|
14904
13383
|
return;
|
|
14905
13384
|
}
|
|
14906
|
-
const content =
|
|
13385
|
+
const content = readFileSync5(hookPath, "utf-8");
|
|
14907
13386
|
if (!content.includes(HOOK_MARKER)) {
|
|
14908
13387
|
error("The commit-msg hook was not installed by contribute-now. Leaving it untouched.");
|
|
14909
13388
|
process.exit(1);
|
|
14910
13389
|
}
|
|
14911
|
-
|
|
13390
|
+
rmSync2(hookPath);
|
|
14912
13391
|
success("commit-msg hook removed.");
|
|
14913
13392
|
}
|
|
14914
13393
|
|
|
@@ -15589,7 +14068,7 @@ var setup_default = defineCommand({
|
|
|
15589
14068
|
if (enableAI) {
|
|
15590
14069
|
const providerChoice = await selectPrompt("Which AI provider should this clone use?", [
|
|
15591
14070
|
"GitHub Copilot — use your existing GitHub/Copilot auth",
|
|
15592
|
-
"Ollama Cloud — use an API key stored
|
|
14071
|
+
"Ollama Cloud — use an API key stored in the local secrets store"
|
|
15593
14072
|
]);
|
|
15594
14073
|
aiProvider = providerChoice.startsWith("Ollama Cloud") ? "ollama-cloud" : "copilot";
|
|
15595
14074
|
if (aiProvider === "ollama-cloud") {
|
|
@@ -15601,7 +14080,7 @@ var setup_default = defineCommand({
|
|
|
15601
14080
|
aiModel = await promptForOllamaCloudModel(apiKey);
|
|
15602
14081
|
try {
|
|
15603
14082
|
await setOllamaCloudApiKey(apiKey);
|
|
15604
|
-
success("Stored Ollama Cloud API key in secrets
|
|
14083
|
+
success("Stored Ollama Cloud API key in the local secrets store.");
|
|
15605
14084
|
info(`Secrets path: ${import_picocolors15.default.bold(getSecretsStorePath())}`);
|
|
15606
14085
|
} catch (err) {
|
|
15607
14086
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -16880,7 +15359,7 @@ var sync_default = defineCommand({
|
|
|
16880
15359
|
});
|
|
16881
15360
|
|
|
16882
15361
|
// src/commands/update.ts
|
|
16883
|
-
import { readFileSync as
|
|
15362
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
16884
15363
|
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
16885
15364
|
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
16886
15365
|
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
@@ -17084,7 +15563,7 @@ var update_default = defineCommand({
|
|
|
17084
15563
|
let conflictDiff = "";
|
|
17085
15564
|
for (const file of conflictFiles.slice(0, 3)) {
|
|
17086
15565
|
try {
|
|
17087
|
-
const content =
|
|
15566
|
+
const content = readFileSync6(file, "utf-8");
|
|
17088
15567
|
if (content.includes("<<<<<<<")) {
|
|
17089
15568
|
conflictDiff += `
|
|
17090
15569
|
--- ${file} ---
|
|
@@ -17125,7 +15604,7 @@ ${import_picocolors21.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
17125
15604
|
});
|
|
17126
15605
|
|
|
17127
15606
|
// src/commands/validate.ts
|
|
17128
|
-
import { readFileSync as
|
|
15607
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
17129
15608
|
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
17130
15609
|
var validate_default = defineCommand({
|
|
17131
15610
|
meta: {
|
|
@@ -17155,7 +15634,7 @@ var validate_default = defineCommand({
|
|
|
17155
15634
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
17156
15635
|
process.exit(0);
|
|
17157
15636
|
}
|
|
17158
|
-
const message = args.file ?
|
|
15637
|
+
const message = args.file ? readFileSync7(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
|
|
17159
15638
|
if (!message) {
|
|
17160
15639
|
error("No commit message provided. Pass a message or use --file <path>.");
|
|
17161
15640
|
process.exit(1);
|