arcane-cli 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +146 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -262,7 +262,16 @@ async function resolveSecretsScanExcludePrefixes(targetDir) {
|
|
|
262
262
|
}
|
|
263
263
|
async function writeManifest(targetDir, manifest) {
|
|
264
264
|
const filePath = manifestPath(targetDir);
|
|
265
|
-
|
|
265
|
+
let existing = "";
|
|
266
|
+
try {
|
|
267
|
+
existing = await readFile2(filePath, "utf-8");
|
|
268
|
+
} catch {
|
|
269
|
+
existing = "";
|
|
270
|
+
}
|
|
271
|
+
const eol = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
272
|
+
const trailingNewline = /\r?\n$/.test(existing) ? eol : "";
|
|
273
|
+
const body = JSON.stringify(manifest, null, 2).replaceAll("\n", eol);
|
|
274
|
+
await writeFile2(filePath, body + trailingNewline, "utf-8");
|
|
266
275
|
}
|
|
267
276
|
function addComponent(manifest, component) {
|
|
268
277
|
return {
|
|
@@ -4353,6 +4362,9 @@ function defaultScopePatch(options, manifest) {
|
|
|
4353
4362
|
function migrateLegacyComponents(components) {
|
|
4354
4363
|
const out = [];
|
|
4355
4364
|
const seen = /* @__PURE__ */ new Set();
|
|
4365
|
+
const inheritedFiles = [];
|
|
4366
|
+
const inheritedHashes = {};
|
|
4367
|
+
let carrier = null;
|
|
4356
4368
|
for (const entry of components) {
|
|
4357
4369
|
const replacements = LEGACY_COMPONENT_MIGRATIONS[entry.name];
|
|
4358
4370
|
if (!replacements) {
|
|
@@ -4361,13 +4373,24 @@ function migrateLegacyComponents(components) {
|
|
|
4361
4373
|
out.push(entry);
|
|
4362
4374
|
continue;
|
|
4363
4375
|
}
|
|
4376
|
+
for (const file of entry.files) {
|
|
4377
|
+
if (!inheritedFiles.includes(file)) inheritedFiles.push(file);
|
|
4378
|
+
const hash = entry.fileHashes?.[file];
|
|
4379
|
+
if (hash !== void 0 && inheritedHashes[file] === void 0) {
|
|
4380
|
+
inheritedHashes[file] = hash;
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4364
4383
|
for (const name of replacements) {
|
|
4384
|
+
carrier ??= name;
|
|
4365
4385
|
if (seen.has(name)) continue;
|
|
4366
4386
|
seen.add(name);
|
|
4367
4387
|
out.push({ name, files: [], installedVersion: entry.installedVersion });
|
|
4368
4388
|
}
|
|
4369
4389
|
}
|
|
4370
|
-
return
|
|
4390
|
+
return {
|
|
4391
|
+
components: out,
|
|
4392
|
+
inherited: { files: inheritedFiles, fileHashes: inheritedHashes, carrier }
|
|
4393
|
+
};
|
|
4371
4394
|
}
|
|
4372
4395
|
async function resolveOrphan(targetDir, file, recordedHash, prune) {
|
|
4373
4396
|
try {
|
|
@@ -4392,6 +4415,19 @@ async function resolveOrphan(targetDir, file, recordedHash, prune) {
|
|
|
4392
4415
|
console.log(` Pruned orphaned file: ${file}`);
|
|
4393
4416
|
return "pruned";
|
|
4394
4417
|
}
|
|
4418
|
+
async function retainReportedOrphan(targetDir, file, recordedHash, into) {
|
|
4419
|
+
try {
|
|
4420
|
+
validateTargetPath(targetDir, file);
|
|
4421
|
+
} catch {
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4424
|
+
if (!await fileExists(join9(targetDir, file))) return;
|
|
4425
|
+
if (!into.files.includes(file)) into.files.push(file);
|
|
4426
|
+
if (recordedHash !== void 0) {
|
|
4427
|
+
into.fileHashes ??= {};
|
|
4428
|
+
into.fileHashes[file] = recordedHash;
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4395
4431
|
function findReadoptedSpellComponents(components, scope = "repo", spellScope = "repo") {
|
|
4396
4432
|
if (scope !== "repo" || spellScope !== "repo") return /* @__PURE__ */ new Set();
|
|
4397
4433
|
return new Set(
|
|
@@ -4539,7 +4575,17 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
4539
4575
|
const conflictedFiles = [];
|
|
4540
4576
|
const customizedShims = [];
|
|
4541
4577
|
const orphanReport = [];
|
|
4542
|
-
const
|
|
4578
|
+
const orphanCandidates = [];
|
|
4579
|
+
const { components: componentsToUpdate, inherited } = migrateLegacyComponents(manifest.components);
|
|
4580
|
+
if (inherited.carrier !== null) {
|
|
4581
|
+
for (const file of inherited.files) {
|
|
4582
|
+
orphanCandidates.push({
|
|
4583
|
+
owner: inherited.carrier,
|
|
4584
|
+
file,
|
|
4585
|
+
recordedHash: inherited.fileHashes[file]
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4543
4589
|
for (const installed of componentsToUpdate) {
|
|
4544
4590
|
let component;
|
|
4545
4591
|
try {
|
|
@@ -4658,24 +4704,11 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
4658
4704
|
}
|
|
4659
4705
|
for (const file of installed.files) {
|
|
4660
4706
|
if (updatedFiles.includes(file)) continue;
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
targetDir,
|
|
4707
|
+
orphanCandidates.push({
|
|
4708
|
+
owner: installed.name,
|
|
4664
4709
|
file,
|
|
4665
|
-
recordedHash
|
|
4666
|
-
|
|
4667
|
-
);
|
|
4668
|
-
orphanReport.push({ file, status });
|
|
4669
|
-
if (status === "reported") {
|
|
4670
|
-
try {
|
|
4671
|
-
validateTargetPath(targetDir, file);
|
|
4672
|
-
if (await fileExists(join9(targetDir, file))) {
|
|
4673
|
-
updatedFiles.push(file);
|
|
4674
|
-
if (recordedHash !== void 0) fileHashes[file] = recordedHash;
|
|
4675
|
-
}
|
|
4676
|
-
} catch {
|
|
4677
|
-
}
|
|
4678
|
-
}
|
|
4710
|
+
recordedHash: installed.fileHashes?.[file]
|
|
4711
|
+
});
|
|
4679
4712
|
}
|
|
4680
4713
|
updatedComponents.push({
|
|
4681
4714
|
...installed,
|
|
@@ -4684,6 +4717,26 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
4684
4717
|
fileHashes
|
|
4685
4718
|
});
|
|
4686
4719
|
}
|
|
4720
|
+
const claimed = new Set(updatedComponents.flatMap((c) => c.files));
|
|
4721
|
+
const resolvedOrphans = /* @__PURE__ */ new Set();
|
|
4722
|
+
for (const { owner, file, recordedHash } of orphanCandidates) {
|
|
4723
|
+
if (claimed.has(file) || resolvedOrphans.has(file)) continue;
|
|
4724
|
+
resolvedOrphans.add(file);
|
|
4725
|
+
const status = await resolveOrphan(
|
|
4726
|
+
targetDir,
|
|
4727
|
+
file,
|
|
4728
|
+
recordedHash,
|
|
4729
|
+
Boolean(options.prune) && !options.dryRun
|
|
4730
|
+
);
|
|
4731
|
+
orphanReport.push({ file, status });
|
|
4732
|
+
if (status !== "reported") continue;
|
|
4733
|
+
let into = updatedComponents.find((c) => c.name === owner);
|
|
4734
|
+
if (!into) {
|
|
4735
|
+
into = { name: owner, files: [], installedVersion: packageVersion, fileHashes: {} };
|
|
4736
|
+
updatedComponents.push(into);
|
|
4737
|
+
}
|
|
4738
|
+
await retainReportedOrphan(targetDir, file, recordedHash, into);
|
|
4739
|
+
}
|
|
4687
4740
|
const orphansToReport = orphanReport.filter((o) => o.status !== "not-found");
|
|
4688
4741
|
if (orphansToReport.length > 0) {
|
|
4689
4742
|
const verb = options.dryRun ? "[dry-run] Found" : "Found";
|
|
@@ -5189,8 +5242,6 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
5189
5242
|
// src/commands/doctor.ts
|
|
5190
5243
|
import { access as access4, readdir as readdir8, readFile as readFile10, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
|
|
5191
5244
|
import { join as join13, dirname as dirname9 } from "path";
|
|
5192
|
-
import { execFile as execFile3 } from "child_process";
|
|
5193
|
-
import { promisify as promisify2 } from "util";
|
|
5194
5245
|
|
|
5195
5246
|
// src/config/incidents.ts
|
|
5196
5247
|
var INCIDENT_QUEUE = [
|
|
@@ -5331,10 +5382,33 @@ async function scanRepository(repoRoot, rules, extensions, excludePrefixes = [])
|
|
|
5331
5382
|
return findings;
|
|
5332
5383
|
}
|
|
5333
5384
|
|
|
5334
|
-
// src/modules/
|
|
5385
|
+
// src/modules/exec.ts
|
|
5335
5386
|
import { execFile as execFile2 } from "child_process";
|
|
5336
|
-
|
|
5337
|
-
|
|
5387
|
+
var EXTERNAL_CLI_TIMEOUT_MS = 1e4;
|
|
5388
|
+
function execFileWithTimeout(file, args, timeoutMs) {
|
|
5389
|
+
return new Promise((resolve5, reject) => {
|
|
5390
|
+
const child = execFile2(
|
|
5391
|
+
file,
|
|
5392
|
+
args,
|
|
5393
|
+
{
|
|
5394
|
+
timeout: timeoutMs,
|
|
5395
|
+
killSignal: "SIGTERM",
|
|
5396
|
+
windowsHide: true,
|
|
5397
|
+
encoding: "utf8"
|
|
5398
|
+
},
|
|
5399
|
+
(error, stdout, stderr) => {
|
|
5400
|
+
if (error) {
|
|
5401
|
+
reject(error);
|
|
5402
|
+
return;
|
|
5403
|
+
}
|
|
5404
|
+
resolve5({ stdout, stderr });
|
|
5405
|
+
}
|
|
5406
|
+
);
|
|
5407
|
+
child.stdin?.end();
|
|
5408
|
+
});
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
// src/modules/platform-policy.ts
|
|
5338
5412
|
var SANCTIONED_MERGE_METHODS = ["merge", "rebase"];
|
|
5339
5413
|
function compareToLadder(effective, declaredButBlocked, interactionNote) {
|
|
5340
5414
|
const disallowedButPermitted = effective.filter(
|
|
@@ -5401,17 +5475,19 @@ function evaluateGitHubMergePolicy(rulesets) {
|
|
|
5401
5475
|
}
|
|
5402
5476
|
async function fetchGitHubRulesets(owner, repo) {
|
|
5403
5477
|
try {
|
|
5404
|
-
const { stdout: listOut } = await
|
|
5405
|
-
"
|
|
5406
|
-
`repos/${owner}/${repo}/rulesets`
|
|
5407
|
-
|
|
5478
|
+
const { stdout: listOut } = await execFileWithTimeout(
|
|
5479
|
+
"gh",
|
|
5480
|
+
["api", `repos/${owner}/${repo}/rulesets`],
|
|
5481
|
+
EXTERNAL_CLI_TIMEOUT_MS
|
|
5482
|
+
);
|
|
5408
5483
|
const summaries = JSON.parse(listOut);
|
|
5409
5484
|
const detailed = await Promise.all(
|
|
5410
5485
|
summaries.map(async (s) => {
|
|
5411
|
-
const { stdout } = await
|
|
5412
|
-
"
|
|
5413
|
-
`repos/${owner}/${repo}/rulesets/${s.id}`
|
|
5414
|
-
|
|
5486
|
+
const { stdout } = await execFileWithTimeout(
|
|
5487
|
+
"gh",
|
|
5488
|
+
["api", `repos/${owner}/${repo}/rulesets/${s.id}`],
|
|
5489
|
+
EXTERNAL_CLI_TIMEOUT_MS
|
|
5490
|
+
);
|
|
5415
5491
|
return JSON.parse(stdout);
|
|
5416
5492
|
})
|
|
5417
5493
|
);
|
|
@@ -5443,7 +5519,7 @@ function evaluateAdoMergePolicy(policies) {
|
|
|
5443
5519
|
async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
5444
5520
|
try {
|
|
5445
5521
|
const orgUrl = `https://dev.azure.com/${org}`;
|
|
5446
|
-
const { stdout: repoOut } = await
|
|
5522
|
+
const { stdout: repoOut } = await execFileWithTimeout("az", [
|
|
5447
5523
|
"repos",
|
|
5448
5524
|
"show",
|
|
5449
5525
|
"--repository",
|
|
@@ -5456,10 +5532,10 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
|
5456
5532
|
"id",
|
|
5457
5533
|
"--output",
|
|
5458
5534
|
"tsv"
|
|
5459
|
-
]);
|
|
5535
|
+
], EXTERNAL_CLI_TIMEOUT_MS);
|
|
5460
5536
|
const repositoryId = repoOut.trim();
|
|
5461
5537
|
if (!repositoryId) return null;
|
|
5462
|
-
const { stdout } = await
|
|
5538
|
+
const { stdout } = await execFileWithTimeout("az", [
|
|
5463
5539
|
"repos",
|
|
5464
5540
|
"policy",
|
|
5465
5541
|
"list",
|
|
@@ -5473,7 +5549,7 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch) {
|
|
|
5473
5549
|
project,
|
|
5474
5550
|
"--output",
|
|
5475
5551
|
"json"
|
|
5476
|
-
]);
|
|
5552
|
+
], EXTERNAL_CLI_TIMEOUT_MS);
|
|
5477
5553
|
return JSON.parse(stdout);
|
|
5478
5554
|
} catch {
|
|
5479
5555
|
return null;
|
|
@@ -5501,7 +5577,6 @@ function parseAdoRemote(url) {
|
|
|
5501
5577
|
}
|
|
5502
5578
|
|
|
5503
5579
|
// src/commands/doctor.ts
|
|
5504
|
-
var execFileAsync2 = promisify2(execFile3);
|
|
5505
5580
|
function isRecord3(value) {
|
|
5506
5581
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5507
5582
|
}
|
|
@@ -5518,40 +5593,33 @@ async function checkNodeVersion() {
|
|
|
5518
5593
|
message: `${raw} \u2014 Node.js 18 or later is required`
|
|
5519
5594
|
};
|
|
5520
5595
|
}
|
|
5521
|
-
async function checkVSCodeExtension(id, label) {
|
|
5596
|
+
async function checkVSCodeExtension(id, label, homeDir = resolveHomeDir()) {
|
|
5522
5597
|
const name = `VS Code extension: ${label}`;
|
|
5523
|
-
const
|
|
5524
|
-
|
|
5525
|
-
join13(
|
|
5526
|
-
join13(home, ".vscode-insiders", "extensions")
|
|
5598
|
+
const flavours = [
|
|
5599
|
+
{ dir: join13(homeDir, ".vscode", "extensions"), bin: "code" },
|
|
5600
|
+
{ dir: join13(homeDir, ".vscode-insiders", "extensions"), bin: "code-insiders" }
|
|
5527
5601
|
];
|
|
5528
|
-
|
|
5602
|
+
let installBin = null;
|
|
5603
|
+
for (const flavour of flavours) {
|
|
5604
|
+
let entries;
|
|
5529
5605
|
try {
|
|
5530
|
-
|
|
5531
|
-
const entries = await readdir11(dir);
|
|
5532
|
-
const prefix = id.toLowerCase() + "-";
|
|
5533
|
-
const found = entries.some(
|
|
5534
|
-
(e) => e.toLowerCase() === id.toLowerCase() || e.toLowerCase().startsWith(prefix)
|
|
5535
|
-
);
|
|
5536
|
-
if (found) {
|
|
5537
|
-
return { name, passed: true, message: `${id} is installed` };
|
|
5538
|
-
}
|
|
5606
|
+
entries = await readdir8(flavour.dir);
|
|
5539
5607
|
} catch {
|
|
5608
|
+
continue;
|
|
5540
5609
|
}
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
} catch {
|
|
5610
|
+
installBin ??= flavour.bin;
|
|
5611
|
+
const prefix = id.toLowerCase() + "-";
|
|
5612
|
+
const found = entries.some(
|
|
5613
|
+
(e) => e.toLowerCase() === id.toLowerCase() || e.toLowerCase().startsWith(prefix)
|
|
5614
|
+
);
|
|
5615
|
+
if (found) {
|
|
5616
|
+
return { name, passed: true, message: `${id} is installed` };
|
|
5549
5617
|
}
|
|
5550
5618
|
}
|
|
5551
5619
|
return {
|
|
5552
5620
|
name,
|
|
5553
5621
|
passed: false,
|
|
5554
|
-
message: `${id} not found. Install via: ${installBin} --install-extension ${id}`,
|
|
5622
|
+
message: `${id} not found. Install via: ${installBin ?? "code"} --install-extension ${id}`,
|
|
5555
5623
|
blocking: false
|
|
5556
5624
|
};
|
|
5557
5625
|
}
|
|
@@ -6037,7 +6105,7 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
6037
6105
|
}
|
|
6038
6106
|
const results = await Promise.all([
|
|
6039
6107
|
checkNodeVersion(),
|
|
6040
|
-
checkVSCodeExtension("GitHub.copilot-chat", "GitHub Copilot (Chat)"),
|
|
6108
|
+
checkVSCodeExtension("GitHub.copilot-chat", "GitHub Copilot (Chat)", resolveHomeDir()),
|
|
6041
6109
|
checkArcaneManifest(targetDir),
|
|
6042
6110
|
Promise.resolve(checkIncidentReleaseGate()),
|
|
6043
6111
|
checkPullRebase(targetDir),
|
|
@@ -6067,26 +6135,24 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
6067
6135
|
console.log(" All checks passed. Your environment is ready.\n");
|
|
6068
6136
|
} else {
|
|
6069
6137
|
console.log(" One or more checks failed. Fix the issues above before proceeding.\n");
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
console.log(`
|
|
6138
|
+
process.exitCode = 1;
|
|
6139
|
+
}
|
|
6140
|
+
const hasMissingSession = sessionResults.some((r) => !r.passed);
|
|
6141
|
+
if (options.fix && hasMissingSession && assetsDir) {
|
|
6142
|
+
console.log(" \u{1F527} Fixing missing session continuity files...\n");
|
|
6143
|
+
const created = await fixSessionContinuity(targetDir, assetsDir);
|
|
6144
|
+
if (created.length > 0) {
|
|
6145
|
+
for (const file of created) {
|
|
6146
|
+
console.log(` \u2713 Created: ${file}`);
|
|
6147
|
+
}
|
|
6148
|
+
console.log(`
|
|
6079
6149
|
${created.length} file(s) created. Repo is now close-session-ready.
|
|
6080
6150
|
`);
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
}
|
|
6084
|
-
} else if (hasMissingSession && !options.fix) {
|
|
6085
|
-
console.log(" \u{1F4A1} Run `spell doctor --fix` to create missing session files.\n");
|
|
6086
|
-
}
|
|
6087
|
-
if (!allPassed) {
|
|
6088
|
-
process.exitCode = 1;
|
|
6151
|
+
} else {
|
|
6152
|
+
console.log(" No files needed creation (all present).\n");
|
|
6089
6153
|
}
|
|
6154
|
+
} else if (hasMissingSession && !options.fix) {
|
|
6155
|
+
console.log(" \u{1F4A1} Run `spell doctor --fix` to create missing session files.\n");
|
|
6090
6156
|
}
|
|
6091
6157
|
}
|
|
6092
6158
|
|