@pratik7368patil/anchor-core 0.1.40 → 0.1.41
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.d.ts +126 -1
- package/dist/index.js +920 -185
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7496,17 +7496,849 @@ function watchCodebase(db, input) {
|
|
|
7496
7496
|
return () => clearInterval(timer);
|
|
7497
7497
|
}
|
|
7498
7498
|
|
|
7499
|
-
// src/
|
|
7499
|
+
// src/autosync.ts
|
|
7500
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
7501
|
+
import fs10 from "fs";
|
|
7502
|
+
import os3 from "os";
|
|
7503
|
+
import path21 from "path";
|
|
7504
|
+
|
|
7505
|
+
// src/org/config.ts
|
|
7500
7506
|
import fs9 from "fs";
|
|
7507
|
+
import os2 from "os";
|
|
7501
7508
|
import path20 from "path";
|
|
7509
|
+
import { z as z2 } from "zod";
|
|
7510
|
+
var ORG_REPO_GROUPS = ["backend", "frontend", "shared", "infra", "docs", "unknown"];
|
|
7511
|
+
var OrgRepoSchema = z2.object({
|
|
7512
|
+
fullName: z2.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/),
|
|
7513
|
+
alias: z2.string().min(1),
|
|
7514
|
+
group: z2.enum(ORG_REPO_GROUPS),
|
|
7515
|
+
cloneUrl: z2.string().min(1),
|
|
7516
|
+
defaultBranch: z2.string().min(1),
|
|
7517
|
+
enabled: z2.boolean()
|
|
7518
|
+
});
|
|
7519
|
+
var OrgConfigSchema = z2.object({
|
|
7520
|
+
version: z2.literal(1),
|
|
7521
|
+
org: z2.string().regex(/^[A-Za-z0-9_.-]+$/),
|
|
7522
|
+
repos: z2.array(OrgRepoSchema)
|
|
7523
|
+
});
|
|
7524
|
+
function validateOrgName(org) {
|
|
7525
|
+
const trimmed = org.trim();
|
|
7526
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(trimmed)) {
|
|
7527
|
+
throw new Error("Invalid org name. Use only letters, numbers, dot, underscore, and hyphen.");
|
|
7528
|
+
}
|
|
7529
|
+
return trimmed;
|
|
7530
|
+
}
|
|
7531
|
+
function validateOrgRepoFullName(fullName) {
|
|
7532
|
+
const trimmed = fullName.trim();
|
|
7533
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(trimmed)) {
|
|
7534
|
+
throw new Error("Invalid repo name. Use owner/name.");
|
|
7535
|
+
}
|
|
7536
|
+
return trimmed;
|
|
7537
|
+
}
|
|
7538
|
+
function validateOrgRepoGroup(group) {
|
|
7539
|
+
if (!group) return "unknown";
|
|
7540
|
+
if (ORG_REPO_GROUPS.includes(group)) return group;
|
|
7541
|
+
throw new Error(`Invalid repo group: ${group}`);
|
|
7542
|
+
}
|
|
7543
|
+
function defaultOrgBaseDir() {
|
|
7544
|
+
if (process.env.ANCHOR_ORG_HOME) return process.env.ANCHOR_ORG_HOME;
|
|
7545
|
+
return path20.join(os2.homedir(), ".anchor", "orgs");
|
|
7546
|
+
}
|
|
7547
|
+
function orgRoot(org, baseDir = defaultOrgBaseDir()) {
|
|
7548
|
+
return path20.join(baseDir, validateOrgName(org));
|
|
7549
|
+
}
|
|
7550
|
+
function orgConfigPath(org, baseDir = defaultOrgBaseDir()) {
|
|
7551
|
+
return path20.join(orgRoot(org, baseDir), "org.json");
|
|
7552
|
+
}
|
|
7553
|
+
function orgDatabasePath(org, baseDir = defaultOrgBaseDir()) {
|
|
7554
|
+
return path20.join(orgRoot(org, baseDir), "org.sqlite");
|
|
7555
|
+
}
|
|
7556
|
+
function orgReposRoot(org, baseDir = defaultOrgBaseDir()) {
|
|
7557
|
+
return path20.join(orgRoot(org, baseDir), "repos");
|
|
7558
|
+
}
|
|
7559
|
+
function repoAliasFromFullName(fullName) {
|
|
7560
|
+
return validateOrgRepoFullName(fullName).split("/")[1] ?? fullName.replace(/\W+/g, "-");
|
|
7561
|
+
}
|
|
7562
|
+
function defaultOrgCloneUrl(fullName) {
|
|
7563
|
+
return `https://github.com/${validateOrgRepoFullName(fullName)}.git`;
|
|
7564
|
+
}
|
|
7565
|
+
function orgRepoLocalPath(org, repo, baseDir = defaultOrgBaseDir()) {
|
|
7566
|
+
const safeAlias = repo.alias.replace(/[^A-Za-z0-9_.-]/g, "-") || repoAliasFromFullName(repo.fullName);
|
|
7567
|
+
return path20.join(orgReposRoot(org, baseDir), safeAlias);
|
|
7568
|
+
}
|
|
7569
|
+
function parseOrgConfig(text) {
|
|
7570
|
+
const parsed = OrgConfigSchema.parse(JSON.parse(text));
|
|
7571
|
+
return parsed;
|
|
7572
|
+
}
|
|
7573
|
+
function atomicWriteJson(filePath, value) {
|
|
7574
|
+
fs9.mkdirSync(path20.dirname(filePath), { recursive: true });
|
|
7575
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
7576
|
+
fs9.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
7577
|
+
`, { mode: 384 });
|
|
7578
|
+
fs9.renameSync(tmp, filePath);
|
|
7579
|
+
}
|
|
7580
|
+
function loadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
7581
|
+
const filePath = orgConfigPath(org, baseDir);
|
|
7582
|
+
if (!fs9.existsSync(filePath)) {
|
|
7583
|
+
throw new Error(
|
|
7584
|
+
`Anchor org config not found at ${filePath}. Run anchor org init --org ${org}.`
|
|
7585
|
+
);
|
|
7586
|
+
}
|
|
7587
|
+
return parseOrgConfig(fs9.readFileSync(filePath, "utf8"));
|
|
7588
|
+
}
|
|
7589
|
+
function maybeLoadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
7590
|
+
const filePath = orgConfigPath(org, baseDir);
|
|
7591
|
+
if (!fs9.existsSync(filePath)) return void 0;
|
|
7592
|
+
return loadOrgConfig(org, baseDir);
|
|
7593
|
+
}
|
|
7594
|
+
function saveOrgConfig(config, baseDir = defaultOrgBaseDir()) {
|
|
7595
|
+
const parsed = OrgConfigSchema.parse(config);
|
|
7596
|
+
atomicWriteJson(orgConfigPath(parsed.org, baseDir), parsed);
|
|
7597
|
+
return parsed;
|
|
7598
|
+
}
|
|
7599
|
+
function initOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
7600
|
+
const normalizedOrg = validateOrgName(org);
|
|
7601
|
+
fs9.mkdirSync(orgReposRoot(normalizedOrg, baseDir), { recursive: true });
|
|
7602
|
+
const existing = maybeLoadOrgConfig(normalizedOrg, baseDir);
|
|
7603
|
+
if (existing) return existing;
|
|
7604
|
+
return saveOrgConfig({ version: 1, org: normalizedOrg, repos: [] }, baseDir);
|
|
7605
|
+
}
|
|
7606
|
+
function addOrgRepoConfig(org, repoFullName, input = {}, baseDir = defaultOrgBaseDir()) {
|
|
7607
|
+
const config = initOrgConfig(org, baseDir);
|
|
7608
|
+
const fullName = validateOrgRepoFullName(repoFullName);
|
|
7609
|
+
const existing = config.repos.find((repo) => repo.fullName === fullName);
|
|
7610
|
+
const candidate = {
|
|
7611
|
+
fullName,
|
|
7612
|
+
alias: input.alias?.trim() || existing?.alias || repoAliasFromFullName(fullName),
|
|
7613
|
+
group: validateOrgRepoGroup(input.group ?? existing?.group),
|
|
7614
|
+
cloneUrl: input.cloneUrl?.trim() || existing?.cloneUrl || defaultOrgCloneUrl(fullName),
|
|
7615
|
+
defaultBranch: input.defaultBranch?.trim() || existing?.defaultBranch || "main",
|
|
7616
|
+
enabled: true
|
|
7617
|
+
};
|
|
7618
|
+
const repos = existing ? config.repos.map((repo) => repo.fullName === fullName ? candidate : repo) : [...config.repos, candidate];
|
|
7619
|
+
return saveOrgConfig(
|
|
7620
|
+
{ ...config, repos: repos.sort((a, b) => a.fullName.localeCompare(b.fullName)) },
|
|
7621
|
+
baseDir
|
|
7622
|
+
);
|
|
7623
|
+
}
|
|
7624
|
+
function removeOrgRepoConfig(org, repoFullName, baseDir = defaultOrgBaseDir()) {
|
|
7625
|
+
const config = loadOrgConfig(org, baseDir);
|
|
7626
|
+
const fullName = validateOrgRepoFullName(repoFullName);
|
|
7627
|
+
return saveOrgConfig(
|
|
7628
|
+
{
|
|
7629
|
+
...config,
|
|
7630
|
+
repos: config.repos.map(
|
|
7631
|
+
(repo) => repo.fullName === fullName ? { ...repo, enabled: false } : repo
|
|
7632
|
+
)
|
|
7633
|
+
},
|
|
7634
|
+
baseDir
|
|
7635
|
+
);
|
|
7636
|
+
}
|
|
7637
|
+
function listOrgNames(baseDir = defaultOrgBaseDir()) {
|
|
7638
|
+
if (!fs9.existsSync(baseDir)) return [];
|
|
7639
|
+
return fs9.readdirSync(baseDir, { withFileTypes: true }).filter(
|
|
7640
|
+
(entry) => entry.isDirectory() && fs9.existsSync(path20.join(baseDir, entry.name, "org.json"))
|
|
7641
|
+
).map((entry) => entry.name).sort();
|
|
7642
|
+
}
|
|
7643
|
+
function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
|
|
7644
|
+
if (org) return validateOrgName(org);
|
|
7645
|
+
const names = listOrgNames(baseDir);
|
|
7646
|
+
if (names.length === 1) return names[0] ?? "";
|
|
7647
|
+
if (names.length === 0) {
|
|
7648
|
+
throw new Error("No Anchor org configured. Run anchor org init --org <org>.");
|
|
7649
|
+
}
|
|
7650
|
+
throw new Error(`Multiple Anchor orgs configured (${names.join(", ")}). Pass org explicitly.`);
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
// src/autosync.ts
|
|
7654
|
+
var CONFIG_VERSION = 1;
|
|
7655
|
+
var REPO_TIMEOUT_MS = 45 * 60 * 1e3;
|
|
7656
|
+
var ORG_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
|
|
7657
|
+
var GRAPH_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
|
|
7658
|
+
var STALE_RUN_MS = 36 * 60 * 60 * 1e3;
|
|
7659
|
+
function autosyncRoot(homeDir = os3.homedir()) {
|
|
7660
|
+
return path21.join(homeDir, ".anchor");
|
|
7661
|
+
}
|
|
7662
|
+
function autosyncConfigPath(homeDir = os3.homedir()) {
|
|
7663
|
+
return path21.join(autosyncRoot(homeDir), "autosync", "config.json");
|
|
7664
|
+
}
|
|
7665
|
+
function autosyncLogsRoot(homeDir = os3.homedir()) {
|
|
7666
|
+
return path21.join(autosyncRoot(homeDir), "logs", "autosync");
|
|
7667
|
+
}
|
|
7668
|
+
function autosyncLocksRoot(homeDir = os3.homedir()) {
|
|
7669
|
+
return path21.join(autosyncRoot(homeDir), "locks", "autosync");
|
|
7670
|
+
}
|
|
7671
|
+
function autosyncJobIdForRepo(repoRoot) {
|
|
7672
|
+
return `repo-${stableHashText(path21.resolve(repoRoot))}`;
|
|
7673
|
+
}
|
|
7674
|
+
function autosyncJobIdForOrg(org) {
|
|
7675
|
+
return `org-${safeName(org)}`;
|
|
7676
|
+
}
|
|
7677
|
+
function autosyncJobIdForOrgGraph(org) {
|
|
7678
|
+
return `org-graph-${safeName(org)}`;
|
|
7679
|
+
}
|
|
7680
|
+
function autosyncLockPath(jobId, homeDir = os3.homedir()) {
|
|
7681
|
+
return path21.join(autosyncLocksRoot(homeDir), `${safeName(jobId)}.lock`);
|
|
7682
|
+
}
|
|
7683
|
+
function defaultCommandRunner(command, args, options = {}) {
|
|
7684
|
+
return execFileSync4(command, args, {
|
|
7685
|
+
cwd: options.cwd,
|
|
7686
|
+
input: options.input,
|
|
7687
|
+
encoding: "utf8",
|
|
7688
|
+
stdio: options.input === void 0 ? ["ignore", "pipe", "pipe"] : ["pipe", "pipe", "pipe"]
|
|
7689
|
+
}).trim();
|
|
7690
|
+
}
|
|
7691
|
+
function readAutosyncConfig(homeDir = os3.homedir()) {
|
|
7692
|
+
const filePath = autosyncConfigPath(homeDir);
|
|
7693
|
+
if (!fs10.existsSync(filePath)) return void 0;
|
|
7694
|
+
return normalizeAutosyncConfig(JSON.parse(fs10.readFileSync(filePath, "utf8")));
|
|
7695
|
+
}
|
|
7696
|
+
function writeAutosyncConfig(config, homeDir = os3.homedir()) {
|
|
7697
|
+
const filePath = autosyncConfigPath(homeDir);
|
|
7698
|
+
fs10.mkdirSync(path21.dirname(filePath), { recursive: true });
|
|
7699
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
7700
|
+
fs10.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
|
|
7701
|
+
`, { mode: 384 });
|
|
7702
|
+
fs10.renameSync(tmp, filePath);
|
|
7703
|
+
}
|
|
7704
|
+
function installDefaultAutosync(options) {
|
|
7705
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
7706
|
+
if (options.mode === "off") return disableAutosync({ ...options, homeDir });
|
|
7707
|
+
const gitRoot = detectGitRoot(options.cwd);
|
|
7708
|
+
if (!gitRoot) {
|
|
7709
|
+
return {
|
|
7710
|
+
enabled: false,
|
|
7711
|
+
configPath: autosyncConfigPath(homeDir),
|
|
7712
|
+
jobs: [],
|
|
7713
|
+
warnings: ["Autosync was not installed because no git repository was detected."]
|
|
7714
|
+
};
|
|
7715
|
+
}
|
|
7716
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7717
|
+
const existing = readAutosyncConfig(homeDir) ?? {
|
|
7718
|
+
version: CONFIG_VERSION,
|
|
7719
|
+
enabled: true,
|
|
7720
|
+
updatedAt: now,
|
|
7721
|
+
jobs: []
|
|
7722
|
+
};
|
|
7723
|
+
const repo = detectGitHubRepo(gitRoot)?.fullName;
|
|
7724
|
+
const nodePath = path21.resolve(options.nodePath ?? process.execPath);
|
|
7725
|
+
const anchorScriptPath = path21.resolve(options.anchorScriptPath);
|
|
7726
|
+
const jobs = new Map(existing.jobs.map((job) => [job.id, job]));
|
|
7727
|
+
const installJobs = [];
|
|
7728
|
+
const repoJob = buildRepoJob({
|
|
7729
|
+
repoRoot: gitRoot,
|
|
7730
|
+
repo,
|
|
7731
|
+
nodePath,
|
|
7732
|
+
anchorScriptPath,
|
|
7733
|
+
homeDir,
|
|
7734
|
+
now,
|
|
7735
|
+
previous: jobs.get(autosyncJobIdForRepo(gitRoot))
|
|
7736
|
+
});
|
|
7737
|
+
jobs.set(repoJob.id, repoJob);
|
|
7738
|
+
installJobs.push(repoJob);
|
|
7739
|
+
for (const org of discoverConfiguredOrgs(options.orgBaseDir)) {
|
|
7740
|
+
const orgJob = buildOrgJob({
|
|
7741
|
+
org,
|
|
7742
|
+
nodePath,
|
|
7743
|
+
anchorScriptPath,
|
|
7744
|
+
homeDir,
|
|
7745
|
+
now,
|
|
7746
|
+
previous: jobs.get(autosyncJobIdForOrg(org))
|
|
7747
|
+
});
|
|
7748
|
+
const graphJob = buildOrgGraphJob({
|
|
7749
|
+
org,
|
|
7750
|
+
nodePath,
|
|
7751
|
+
anchorScriptPath,
|
|
7752
|
+
homeDir,
|
|
7753
|
+
now,
|
|
7754
|
+
previous: jobs.get(autosyncJobIdForOrgGraph(org))
|
|
7755
|
+
});
|
|
7756
|
+
jobs.set(orgJob.id, orgJob);
|
|
7757
|
+
jobs.set(graphJob.id, graphJob);
|
|
7758
|
+
installJobs.push(orgJob, graphJob);
|
|
7759
|
+
}
|
|
7760
|
+
const config = {
|
|
7761
|
+
version: CONFIG_VERSION,
|
|
7762
|
+
enabled: true,
|
|
7763
|
+
updatedAt: now,
|
|
7764
|
+
jobs: [...jobs.values()].sort((a, b) => a.id.localeCompare(b.id))
|
|
7765
|
+
};
|
|
7766
|
+
writeAutosyncConfig(config, homeDir);
|
|
7767
|
+
const warnings = [];
|
|
7768
|
+
const results = installJobs.map((job) => {
|
|
7769
|
+
try {
|
|
7770
|
+
return installSchedulerJob(job, {
|
|
7771
|
+
platform: options.platform ?? process.platform,
|
|
7772
|
+
homeDir,
|
|
7773
|
+
runner: options.runner ?? defaultCommandRunner
|
|
7774
|
+
});
|
|
7775
|
+
} catch (error) {
|
|
7776
|
+
warnings.push(`${job.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7777
|
+
const artifact = schedulerArtifactForJob(job, options.platform ?? process.platform, homeDir);
|
|
7778
|
+
return {
|
|
7779
|
+
id: job.id,
|
|
7780
|
+
label: job.label,
|
|
7781
|
+
kind: job.kind,
|
|
7782
|
+
schedule: job.schedule,
|
|
7783
|
+
scheduler: artifact.scheduler,
|
|
7784
|
+
installed: false,
|
|
7785
|
+
nextRunHint: artifact.nextRunHint,
|
|
7786
|
+
logPath: job.logPath,
|
|
7787
|
+
message: "Scheduler install failed; rerun anchor init after fixing local scheduler access."
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
});
|
|
7791
|
+
return {
|
|
7792
|
+
enabled: true,
|
|
7793
|
+
configPath: autosyncConfigPath(homeDir),
|
|
7794
|
+
jobs: results,
|
|
7795
|
+
warnings
|
|
7796
|
+
};
|
|
7797
|
+
}
|
|
7798
|
+
function disableAutosync(options = {}) {
|
|
7799
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
7800
|
+
const config = readAutosyncConfig(homeDir);
|
|
7801
|
+
const warnings = [];
|
|
7802
|
+
if (config) {
|
|
7803
|
+
const disabled = {
|
|
7804
|
+
...config,
|
|
7805
|
+
enabled: false,
|
|
7806
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7807
|
+
jobs: config.jobs.map((job) => ({ ...job, enabled: false, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }))
|
|
7808
|
+
};
|
|
7809
|
+
writeAutosyncConfig(disabled, homeDir);
|
|
7810
|
+
for (const job of config.jobs) {
|
|
7811
|
+
try {
|
|
7812
|
+
removeSchedulerJob(job, {
|
|
7813
|
+
platform: options.platform ?? process.platform,
|
|
7814
|
+
homeDir,
|
|
7815
|
+
runner: options.runner ?? defaultCommandRunner
|
|
7816
|
+
});
|
|
7817
|
+
} catch (error) {
|
|
7818
|
+
warnings.push(`${job.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
return {
|
|
7823
|
+
enabled: false,
|
|
7824
|
+
configPath: autosyncConfigPath(homeDir),
|
|
7825
|
+
jobs: [],
|
|
7826
|
+
warnings
|
|
7827
|
+
};
|
|
7828
|
+
}
|
|
7829
|
+
function getAutosyncStatus(options = {}) {
|
|
7830
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
7831
|
+
const platform = options.platform ?? process.platform;
|
|
7832
|
+
const config = readAutosyncConfig(homeDir);
|
|
7833
|
+
const scheduler = schedulerName(platform);
|
|
7834
|
+
if (!config) {
|
|
7835
|
+
return {
|
|
7836
|
+
enabled: false,
|
|
7837
|
+
configured: false,
|
|
7838
|
+
configPath: autosyncConfigPath(homeDir),
|
|
7839
|
+
scheduler,
|
|
7840
|
+
jobs: [],
|
|
7841
|
+
warnings: ["Autosync is not configured. Run anchor init to install local autosync."]
|
|
7842
|
+
};
|
|
7843
|
+
}
|
|
7844
|
+
const gitRoot = options.cwd ? detectGitRoot(options.cwd) : void 0;
|
|
7845
|
+
const matchingRepoJobId = gitRoot ? autosyncJobIdForRepo(gitRoot) : void 0;
|
|
7846
|
+
const jobs = config.jobs.map((job) => {
|
|
7847
|
+
const detected = schedulerArtifactExists(job, platform, homeDir);
|
|
7848
|
+
const stale = isAutosyncJobStale(job);
|
|
7849
|
+
const failing = job.lastRun?.status === "failed";
|
|
7850
|
+
return {
|
|
7851
|
+
id: job.id,
|
|
7852
|
+
label: job.label,
|
|
7853
|
+
kind: job.kind,
|
|
7854
|
+
schedule: job.schedule,
|
|
7855
|
+
enabled: job.enabled,
|
|
7856
|
+
scheduler,
|
|
7857
|
+
schedulerDetected: detected,
|
|
7858
|
+
logPath: job.logPath,
|
|
7859
|
+
lastRun: job.lastRun,
|
|
7860
|
+
stale,
|
|
7861
|
+
failing
|
|
7862
|
+
};
|
|
7863
|
+
});
|
|
7864
|
+
const relevantJobs = matchingRepoJobId ? jobs.filter((job) => job.id === matchingRepoJobId || job.kind !== "repo") : jobs;
|
|
7865
|
+
const warnings = [];
|
|
7866
|
+
if (!config.enabled) warnings.push("Autosync is disabled.");
|
|
7867
|
+
if (matchingRepoJobId && !jobs.some((job) => job.id === matchingRepoJobId)) {
|
|
7868
|
+
warnings.push("Autosync is not installed for this repository. Run anchor init.");
|
|
7869
|
+
}
|
|
7870
|
+
for (const job of relevantJobs) {
|
|
7871
|
+
if (job.enabled && !job.schedulerDetected) {
|
|
7872
|
+
warnings.push(`${job.label} scheduler file is missing. Run anchor init.`);
|
|
7873
|
+
}
|
|
7874
|
+
if (job.failing) warnings.push(`${job.label} last run failed.`);
|
|
7875
|
+
if (job.stale) warnings.push(`${job.label} has not completed recently.`);
|
|
7876
|
+
}
|
|
7877
|
+
return {
|
|
7878
|
+
enabled: config.enabled,
|
|
7879
|
+
configured: true,
|
|
7880
|
+
configPath: autosyncConfigPath(homeDir),
|
|
7881
|
+
scheduler,
|
|
7882
|
+
jobs: relevantJobs,
|
|
7883
|
+
lastRun: latestAutosyncRun(
|
|
7884
|
+
relevantJobs.map((job) => job.lastRun).filter((run) => Boolean(run))
|
|
7885
|
+
),
|
|
7886
|
+
warnings
|
|
7887
|
+
};
|
|
7888
|
+
}
|
|
7889
|
+
function acquireAutosyncLock(jobId, homeDir = os3.homedir()) {
|
|
7890
|
+
const lockPath = autosyncLockPath(jobId, homeDir);
|
|
7891
|
+
fs10.mkdirSync(path21.dirname(lockPath), { recursive: true });
|
|
7892
|
+
try {
|
|
7893
|
+
fs10.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), {
|
|
7894
|
+
flag: "wx",
|
|
7895
|
+
mode: 384
|
|
7896
|
+
});
|
|
7897
|
+
} catch {
|
|
7898
|
+
const existing = readLock(lockPath);
|
|
7899
|
+
if (existing?.pid && processIsRunning(existing.pid)) {
|
|
7900
|
+
return {
|
|
7901
|
+
acquired: false,
|
|
7902
|
+
lockPath,
|
|
7903
|
+
release: () => void 0,
|
|
7904
|
+
message: `Autosync job ${jobId} is already running as pid ${existing.pid}.`
|
|
7905
|
+
};
|
|
7906
|
+
}
|
|
7907
|
+
fs10.rmSync(lockPath, { force: true });
|
|
7908
|
+
fs10.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), {
|
|
7909
|
+
flag: "wx",
|
|
7910
|
+
mode: 384
|
|
7911
|
+
});
|
|
7912
|
+
}
|
|
7913
|
+
return {
|
|
7914
|
+
acquired: true,
|
|
7915
|
+
lockPath,
|
|
7916
|
+
release: () => fs10.rmSync(lockPath, { force: true })
|
|
7917
|
+
};
|
|
7918
|
+
}
|
|
7919
|
+
function recordAutosyncRun(jobId, run, homeDir = os3.homedir()) {
|
|
7920
|
+
const config = readAutosyncConfig(homeDir);
|
|
7921
|
+
if (!config) return;
|
|
7922
|
+
writeAutosyncConfig(
|
|
7923
|
+
{
|
|
7924
|
+
...config,
|
|
7925
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7926
|
+
jobs: config.jobs.map(
|
|
7927
|
+
(job) => job.id === jobId ? { ...job, lastRun: run, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : job
|
|
7928
|
+
)
|
|
7929
|
+
},
|
|
7930
|
+
homeDir
|
|
7931
|
+
);
|
|
7932
|
+
}
|
|
7933
|
+
function resolveAutosyncJob(kind, input) {
|
|
7934
|
+
const config = readAutosyncConfig();
|
|
7935
|
+
if (!config) throw new Error("Autosync is not configured. Run anchor init.");
|
|
7936
|
+
const jobId = kind === "repo" ? autosyncJobIdForRepo(input.cwd ?? process.cwd()) : kind === "org" ? autosyncJobIdForOrg(requiredOrg(input.org)) : autosyncJobIdForOrgGraph(requiredOrg(input.org));
|
|
7937
|
+
const job = config.jobs.find((item) => item.id === jobId);
|
|
7938
|
+
if (!job) throw new Error(`Autosync job ${jobId} is not configured. Run anchor init.`);
|
|
7939
|
+
return job;
|
|
7940
|
+
}
|
|
7941
|
+
function buildRepoJob(input) {
|
|
7942
|
+
const id = autosyncJobIdForRepo(input.repoRoot);
|
|
7943
|
+
return {
|
|
7944
|
+
id,
|
|
7945
|
+
kind: "repo",
|
|
7946
|
+
label: `Repo autosync${input.repo ? `: ${input.repo}` : ""}`,
|
|
7947
|
+
schedule: "daily",
|
|
7948
|
+
enabled: true,
|
|
7949
|
+
repoRoot: input.repoRoot,
|
|
7950
|
+
repo: input.repo,
|
|
7951
|
+
nodePath: input.nodePath,
|
|
7952
|
+
anchorScriptPath: input.anchorScriptPath,
|
|
7953
|
+
args: ["internal", "autosync-run", "--kind", "repo", "--cwd", input.repoRoot],
|
|
7954
|
+
logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
|
|
7955
|
+
lockPath: autosyncLockPath(id, input.homeDir),
|
|
7956
|
+
timeoutMs: REPO_TIMEOUT_MS,
|
|
7957
|
+
createdAt: input.previous?.createdAt ?? input.now,
|
|
7958
|
+
updatedAt: input.now,
|
|
7959
|
+
lastRun: input.previous?.lastRun
|
|
7960
|
+
};
|
|
7961
|
+
}
|
|
7962
|
+
function buildOrgJob(input) {
|
|
7963
|
+
const id = autosyncJobIdForOrg(input.org);
|
|
7964
|
+
return {
|
|
7965
|
+
id,
|
|
7966
|
+
kind: "org",
|
|
7967
|
+
label: `Org autosync: ${input.org}`,
|
|
7968
|
+
schedule: "daily",
|
|
7969
|
+
enabled: true,
|
|
7970
|
+
org: input.org,
|
|
7971
|
+
nodePath: input.nodePath,
|
|
7972
|
+
anchorScriptPath: input.anchorScriptPath,
|
|
7973
|
+
args: ["internal", "autosync-run", "--kind", "org", "--org", input.org, "--no-graph"],
|
|
7974
|
+
logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
|
|
7975
|
+
lockPath: autosyncLockPath(id, input.homeDir),
|
|
7976
|
+
timeoutMs: ORG_TIMEOUT_MS,
|
|
7977
|
+
createdAt: input.previous?.createdAt ?? input.now,
|
|
7978
|
+
updatedAt: input.now,
|
|
7979
|
+
lastRun: input.previous?.lastRun
|
|
7980
|
+
};
|
|
7981
|
+
}
|
|
7982
|
+
function buildOrgGraphJob(input) {
|
|
7983
|
+
const id = autosyncJobIdForOrgGraph(input.org);
|
|
7984
|
+
return {
|
|
7985
|
+
id,
|
|
7986
|
+
kind: "org-graph",
|
|
7987
|
+
label: `Org graph autosync: ${input.org}`,
|
|
7988
|
+
schedule: "weekly",
|
|
7989
|
+
enabled: true,
|
|
7990
|
+
org: input.org,
|
|
7991
|
+
nodePath: input.nodePath,
|
|
7992
|
+
anchorScriptPath: input.anchorScriptPath,
|
|
7993
|
+
args: ["internal", "autosync-run", "--kind", "org-graph", "--org", input.org],
|
|
7994
|
+
logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
|
|
7995
|
+
lockPath: autosyncLockPath(id, input.homeDir),
|
|
7996
|
+
timeoutMs: GRAPH_TIMEOUT_MS,
|
|
7997
|
+
createdAt: input.previous?.createdAt ?? input.now,
|
|
7998
|
+
updatedAt: input.now,
|
|
7999
|
+
lastRun: input.previous?.lastRun
|
|
8000
|
+
};
|
|
8001
|
+
}
|
|
8002
|
+
function discoverConfiguredOrgs(baseDir) {
|
|
8003
|
+
return listOrgNames(baseDir).filter((org) => {
|
|
8004
|
+
try {
|
|
8005
|
+
return loadOrgConfig(org, baseDir).repos.some((repo) => repo.enabled);
|
|
8006
|
+
} catch {
|
|
8007
|
+
return false;
|
|
8008
|
+
}
|
|
8009
|
+
});
|
|
8010
|
+
}
|
|
8011
|
+
function installSchedulerJob(job, options) {
|
|
8012
|
+
fs10.mkdirSync(path21.dirname(job.logPath), { recursive: true });
|
|
8013
|
+
const artifact = schedulerArtifactForJob(job, options.platform, options.homeDir);
|
|
8014
|
+
if (options.platform === "darwin") installLaunchdJob(job, artifact, options.runner);
|
|
8015
|
+
else if (options.platform === "linux") installLinuxJob(job, artifact, options);
|
|
8016
|
+
else if (options.platform === "win32") installWindowsJob(job, artifact, options.runner);
|
|
8017
|
+
else throw new Error(`Unsupported autosync scheduler platform: ${options.platform}`);
|
|
8018
|
+
return {
|
|
8019
|
+
id: job.id,
|
|
8020
|
+
label: job.label,
|
|
8021
|
+
kind: job.kind,
|
|
8022
|
+
schedule: job.schedule,
|
|
8023
|
+
scheduler: artifact.scheduler,
|
|
8024
|
+
installed: true,
|
|
8025
|
+
nextRunHint: artifact.nextRunHint,
|
|
8026
|
+
logPath: job.logPath,
|
|
8027
|
+
message: "Installed local scheduler job."
|
|
8028
|
+
};
|
|
8029
|
+
}
|
|
8030
|
+
function removeSchedulerJob(job, options) {
|
|
8031
|
+
const artifact = schedulerArtifactForJob(job, options.platform, options.homeDir);
|
|
8032
|
+
if (options.platform === "darwin") {
|
|
8033
|
+
const uid = process.getuid?.();
|
|
8034
|
+
if (artifact.filePath && uid !== void 0) {
|
|
8035
|
+
try {
|
|
8036
|
+
options.runner("launchctl", ["bootout", `gui/${uid}`, artifact.filePath]);
|
|
8037
|
+
} catch {
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
if (artifact.filePath) fs10.rmSync(artifact.filePath, { force: true });
|
|
8041
|
+
return;
|
|
8042
|
+
}
|
|
8043
|
+
if (options.platform === "linux") {
|
|
8044
|
+
if (artifact.filePath) {
|
|
8045
|
+
const timerPath = artifact.filePath.replace(/\.service$/, ".timer");
|
|
8046
|
+
try {
|
|
8047
|
+
options.runner("systemctl", ["--user", "disable", "--now", path21.basename(timerPath)]);
|
|
8048
|
+
} catch {
|
|
8049
|
+
}
|
|
8050
|
+
fs10.rmSync(artifact.filePath, { force: true });
|
|
8051
|
+
fs10.rmSync(timerPath, { force: true });
|
|
8052
|
+
}
|
|
8053
|
+
removeCronBlock(job.id, options.runner);
|
|
8054
|
+
return;
|
|
8055
|
+
}
|
|
8056
|
+
if (options.platform === "win32") {
|
|
8057
|
+
try {
|
|
8058
|
+
options.runner("schtasks", ["/Delete", "/F", "/TN", windowsTaskName(job)]);
|
|
8059
|
+
} catch {
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
8062
|
+
}
|
|
8063
|
+
function schedulerArtifactForJob(job, platform, homeDir) {
|
|
8064
|
+
const time = scheduleTime(job);
|
|
8065
|
+
if (platform === "darwin") {
|
|
8066
|
+
const label = `com.anchor.autosync.${safeName(job.id)}`;
|
|
8067
|
+
return {
|
|
8068
|
+
scheduler: "launchd",
|
|
8069
|
+
label,
|
|
8070
|
+
filePath: path21.join(homeDir, "Library", "LaunchAgents", `${label}.plist`),
|
|
8071
|
+
nextRunHint: `${job.schedule} around ${time.hourLabel}`
|
|
8072
|
+
};
|
|
8073
|
+
}
|
|
8074
|
+
if (platform === "linux") {
|
|
8075
|
+
const unit = `anchor-autosync-${safeName(job.id)}`;
|
|
8076
|
+
return {
|
|
8077
|
+
scheduler: "systemd-user",
|
|
8078
|
+
label: unit,
|
|
8079
|
+
filePath: path21.join(homeDir, ".config", "systemd", "user", `${unit}.service`),
|
|
8080
|
+
nextRunHint: `${job.schedule} around ${time.hourLabel}`
|
|
8081
|
+
};
|
|
8082
|
+
}
|
|
8083
|
+
if (platform === "win32") {
|
|
8084
|
+
return {
|
|
8085
|
+
scheduler: "windows-task-scheduler",
|
|
8086
|
+
label: windowsTaskName(job),
|
|
8087
|
+
nextRunHint: `${job.schedule} around ${time.hourLabel}`
|
|
8088
|
+
};
|
|
8089
|
+
}
|
|
8090
|
+
return { scheduler: "unsupported", nextRunHint: "n/a" };
|
|
8091
|
+
}
|
|
8092
|
+
function schedulerArtifactExists(job, platform, homeDir) {
|
|
8093
|
+
const artifact = schedulerArtifactForJob(job, platform, homeDir);
|
|
8094
|
+
if (platform === "win32") return true;
|
|
8095
|
+
if (platform === "linux") {
|
|
8096
|
+
return Boolean(
|
|
8097
|
+
artifact.filePath && (fs10.existsSync(artifact.filePath) || fs10.existsSync(path21.join(homeDir, ".config", "systemd", "user", `${artifact.label}.timer`)) || cronHasBlock(job.id))
|
|
8098
|
+
);
|
|
8099
|
+
}
|
|
8100
|
+
return Boolean(artifact.filePath && fs10.existsSync(artifact.filePath));
|
|
8101
|
+
}
|
|
8102
|
+
function installLaunchdJob(job, artifact, runner) {
|
|
8103
|
+
if (!artifact.filePath || !artifact.label) throw new Error("Missing launchd artifact path.");
|
|
8104
|
+
fs10.mkdirSync(path21.dirname(artifact.filePath), { recursive: true });
|
|
8105
|
+
fs10.writeFileSync(artifact.filePath, launchdPlist(job, artifact.label), { mode: 420 });
|
|
8106
|
+
const uid = process.getuid?.();
|
|
8107
|
+
if (uid === void 0) return;
|
|
8108
|
+
try {
|
|
8109
|
+
runner("launchctl", ["bootout", `gui/${uid}`, artifact.filePath]);
|
|
8110
|
+
} catch {
|
|
8111
|
+
}
|
|
8112
|
+
runner("launchctl", ["bootstrap", `gui/${uid}`, artifact.filePath]);
|
|
8113
|
+
runner("launchctl", ["enable", `gui/${uid}/${artifact.label}`]);
|
|
8114
|
+
}
|
|
8115
|
+
function installLinuxJob(job, artifact, options) {
|
|
8116
|
+
if (!artifact.filePath || !artifact.label) throw new Error("Missing systemd artifact path.");
|
|
8117
|
+
const timerPath = artifact.filePath.replace(/\.service$/, ".timer");
|
|
8118
|
+
fs10.mkdirSync(path21.dirname(artifact.filePath), { recursive: true });
|
|
8119
|
+
fs10.writeFileSync(artifact.filePath, systemdService(job), { mode: 420 });
|
|
8120
|
+
fs10.writeFileSync(timerPath, systemdTimer(job), { mode: 420 });
|
|
8121
|
+
try {
|
|
8122
|
+
options.runner("systemctl", ["--user", "daemon-reload"]);
|
|
8123
|
+
options.runner("systemctl", ["--user", "enable", "--now", path21.basename(timerPath)]);
|
|
8124
|
+
} catch {
|
|
8125
|
+
installCronFallback(job, options.runner);
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
function installWindowsJob(job, artifact, runner) {
|
|
8129
|
+
const time = scheduleTime(job);
|
|
8130
|
+
const args = [
|
|
8131
|
+
"/Create",
|
|
8132
|
+
"/F",
|
|
8133
|
+
"/TN",
|
|
8134
|
+
artifact.label ?? windowsTaskName(job),
|
|
8135
|
+
"/TR",
|
|
8136
|
+
`${quoteWindows(job.nodePath)} ${quoteWindows(job.anchorScriptPath)} ${job.args.map(quoteWindows).join(" ")}`,
|
|
8137
|
+
"/SC",
|
|
8138
|
+
job.schedule === "weekly" ? "WEEKLY" : "DAILY",
|
|
8139
|
+
"/ST",
|
|
8140
|
+
`${pad2(time.hour)}:${pad2(time.minute)}`
|
|
8141
|
+
];
|
|
8142
|
+
if (job.schedule === "weekly") args.push("/D", "MON");
|
|
8143
|
+
runner("schtasks", args);
|
|
8144
|
+
}
|
|
8145
|
+
function launchdPlist(job, label) {
|
|
8146
|
+
const time = scheduleTime(job);
|
|
8147
|
+
const calendar = job.schedule === "weekly" ? `<dict><key>Weekday</key><integer>1</integer><key>Hour</key><integer>${time.hour}</integer><key>Minute</key><integer>${time.minute}</integer></dict>` : `<dict><key>Hour</key><integer>${time.hour}</integer><key>Minute</key><integer>${time.minute}</integer></dict>`;
|
|
8148
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
8149
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
8150
|
+
<plist version="1.0">
|
|
8151
|
+
<dict>
|
|
8152
|
+
<key>Label</key><string>${xmlEscape(label)}</string>
|
|
8153
|
+
<key>ProgramArguments</key>
|
|
8154
|
+
<array>
|
|
8155
|
+
${[job.nodePath, job.anchorScriptPath, ...job.args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n")}
|
|
8156
|
+
</array>
|
|
8157
|
+
<key>StartCalendarInterval</key>${calendar}
|
|
8158
|
+
<key>StandardOutPath</key><string>${xmlEscape(job.logPath)}</string>
|
|
8159
|
+
<key>StandardErrorPath</key><string>${xmlEscape(job.logPath)}</string>
|
|
8160
|
+
<key>EnvironmentVariables</key>
|
|
8161
|
+
<dict>
|
|
8162
|
+
<key>HOME</key><string>${xmlEscape(os3.homedir())}</string>
|
|
8163
|
+
<key>PATH</key><string>${xmlEscape(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")}</string>
|
|
8164
|
+
<key>SHELL</key><string>${xmlEscape(process.env.SHELL ?? "/bin/sh")}</string>
|
|
8165
|
+
<key>ANCHOR_PROGRESS</key><string>plain</string>
|
|
8166
|
+
</dict>
|
|
8167
|
+
</dict>
|
|
8168
|
+
</plist>
|
|
8169
|
+
`;
|
|
8170
|
+
}
|
|
8171
|
+
function systemdService(job) {
|
|
8172
|
+
return `[Unit]
|
|
8173
|
+
Description=${job.label}
|
|
8174
|
+
|
|
8175
|
+
[Service]
|
|
8176
|
+
Type=oneshot
|
|
8177
|
+
Environment=ANCHOR_PROGRESS=plain
|
|
8178
|
+
Environment=HOME=${systemdEscape(os3.homedir())}
|
|
8179
|
+
Environment=PATH=${systemdEscape(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")}
|
|
8180
|
+
Environment=SHELL=${systemdEscape(process.env.SHELL ?? "/bin/sh")}
|
|
8181
|
+
ExecStart=${systemdEscape(job.nodePath)} ${[job.anchorScriptPath, ...job.args].map(systemdEscape).join(" ")}
|
|
8182
|
+
StandardOutput=append:${job.logPath}
|
|
8183
|
+
StandardError=append:${job.logPath}
|
|
8184
|
+
`;
|
|
8185
|
+
}
|
|
8186
|
+
function systemdTimer(job) {
|
|
8187
|
+
const time = scheduleTime(job);
|
|
8188
|
+
const calendar = job.schedule === "weekly" ? `Mon *-*-* ${pad2(time.hour)}:${pad2(time.minute)}:00` : `*-*-* ${pad2(time.hour)}:${pad2(time.minute)}:00`;
|
|
8189
|
+
return `[Unit]
|
|
8190
|
+
Description=${job.label} timer
|
|
8191
|
+
|
|
8192
|
+
[Timer]
|
|
8193
|
+
OnCalendar=${calendar}
|
|
8194
|
+
Persistent=true
|
|
8195
|
+
|
|
8196
|
+
[Install]
|
|
8197
|
+
WantedBy=timers.target
|
|
8198
|
+
`;
|
|
8199
|
+
}
|
|
8200
|
+
function installCronFallback(job, runner) {
|
|
8201
|
+
const existing = readCrontab(runner);
|
|
8202
|
+
const withoutBlock = removeCronBlockFromText(existing, job.id);
|
|
8203
|
+
const time = scheduleTime(job);
|
|
8204
|
+
const weekday = job.schedule === "weekly" ? "1" : "*";
|
|
8205
|
+
const command = `HOME=${shellQuote(os3.homedir())} PATH=${shellQuote(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")} SHELL=${shellQuote(process.env.SHELL ?? "/bin/sh")} ANCHOR_PROGRESS=plain ${shellQuote(job.nodePath)} ${shellQuote(job.anchorScriptPath)} ${job.args.map(shellQuote).join(" ")} >> ${shellQuote(job.logPath)} 2>&1`;
|
|
8206
|
+
const block = [
|
|
8207
|
+
`# anchor autosync start ${job.id}`,
|
|
8208
|
+
`${time.minute} ${time.hour} * * ${weekday} ${command}`,
|
|
8209
|
+
`# anchor autosync end ${job.id}`
|
|
8210
|
+
].join("\n");
|
|
8211
|
+
runner("crontab", ["-"], { input: `${withoutBlock.trim()}
|
|
8212
|
+
${block}
|
|
8213
|
+
` });
|
|
8214
|
+
}
|
|
8215
|
+
function removeCronBlock(jobId, runner) {
|
|
8216
|
+
const existing = readCrontab(runner);
|
|
8217
|
+
const next = removeCronBlockFromText(existing, jobId);
|
|
8218
|
+
if (next !== existing) runner("crontab", ["-"], { input: next.trim() ? `${next.trim()}
|
|
8219
|
+
` : "" });
|
|
8220
|
+
}
|
|
8221
|
+
function readCrontab(runner) {
|
|
8222
|
+
try {
|
|
8223
|
+
return runner("crontab", ["-l"]);
|
|
8224
|
+
} catch {
|
|
8225
|
+
return "";
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
function removeCronBlockFromText(text, jobId) {
|
|
8229
|
+
const start = `# anchor autosync start ${jobId}`;
|
|
8230
|
+
const end = `# anchor autosync end ${jobId}`;
|
|
8231
|
+
const lines = text.split("\n");
|
|
8232
|
+
const kept = [];
|
|
8233
|
+
let skipping = false;
|
|
8234
|
+
for (const line of lines) {
|
|
8235
|
+
if (line.trim() === start) {
|
|
8236
|
+
skipping = true;
|
|
8237
|
+
continue;
|
|
8238
|
+
}
|
|
8239
|
+
if (line.trim() === end) {
|
|
8240
|
+
skipping = false;
|
|
8241
|
+
continue;
|
|
8242
|
+
}
|
|
8243
|
+
if (!skipping) kept.push(line);
|
|
8244
|
+
}
|
|
8245
|
+
return kept.join("\n").trim();
|
|
8246
|
+
}
|
|
8247
|
+
function cronHasBlock(_jobId) {
|
|
8248
|
+
return false;
|
|
8249
|
+
}
|
|
8250
|
+
function scheduleTime(job) {
|
|
8251
|
+
const offset = stableHashNumber(job.id) % 180;
|
|
8252
|
+
const baseHour = job.schedule === "weekly" ? 3 : 9;
|
|
8253
|
+
const hour = (baseHour + Math.floor(offset / 60)) % 24;
|
|
8254
|
+
const minute = offset % 60;
|
|
8255
|
+
return { hour, minute, hourLabel: `${pad2(hour)}:${pad2(minute)}` };
|
|
8256
|
+
}
|
|
8257
|
+
function isAutosyncJobStale(job) {
|
|
8258
|
+
if (!job.enabled) return false;
|
|
8259
|
+
if (!job.lastRun?.finishedAt) return false;
|
|
8260
|
+
if (job.lastRun.status === "failed") return true;
|
|
8261
|
+
return Date.now() - Date.parse(job.lastRun.finishedAt) > STALE_RUN_MS;
|
|
8262
|
+
}
|
|
8263
|
+
function latestAutosyncRun(runs) {
|
|
8264
|
+
return runs.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))[0];
|
|
8265
|
+
}
|
|
8266
|
+
function normalizeAutosyncConfig(value) {
|
|
8267
|
+
const candidate = value;
|
|
8268
|
+
return {
|
|
8269
|
+
version: CONFIG_VERSION,
|
|
8270
|
+
enabled: Boolean(candidate.enabled),
|
|
8271
|
+
updatedAt: candidate.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8272
|
+
jobs: Array.isArray(candidate.jobs) ? candidate.jobs : []
|
|
8273
|
+
};
|
|
8274
|
+
}
|
|
8275
|
+
function readLock(lockPath) {
|
|
8276
|
+
try {
|
|
8277
|
+
return JSON.parse(fs10.readFileSync(lockPath, "utf8"));
|
|
8278
|
+
} catch {
|
|
8279
|
+
return void 0;
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
function processIsRunning(pid) {
|
|
8283
|
+
try {
|
|
8284
|
+
process.kill(pid, 0);
|
|
8285
|
+
return true;
|
|
8286
|
+
} catch {
|
|
8287
|
+
return false;
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
function requiredOrg(org) {
|
|
8291
|
+
if (!org) throw new Error("Pass --org <org>.");
|
|
8292
|
+
return org;
|
|
8293
|
+
}
|
|
8294
|
+
function schedulerName(platform) {
|
|
8295
|
+
if (platform === "darwin") return "launchd";
|
|
8296
|
+
if (platform === "linux") return "systemd-user";
|
|
8297
|
+
if (platform === "win32") return "windows-task-scheduler";
|
|
8298
|
+
return "unsupported";
|
|
8299
|
+
}
|
|
8300
|
+
function stableHashNumber(value) {
|
|
8301
|
+
let hash = 5381;
|
|
8302
|
+
for (const char of value) hash = (hash << 5) + hash + char.charCodeAt(0) >>> 0;
|
|
8303
|
+
return hash;
|
|
8304
|
+
}
|
|
8305
|
+
function stableHashText(value) {
|
|
8306
|
+
const hash = stableHashNumber(value);
|
|
8307
|
+
return hash.toString(36);
|
|
8308
|
+
}
|
|
8309
|
+
function safeName(value) {
|
|
8310
|
+
return value.replace(/[^A-Za-z0-9_.-]/g, "-");
|
|
8311
|
+
}
|
|
8312
|
+
function pad2(value) {
|
|
8313
|
+
return String(value).padStart(2, "0");
|
|
8314
|
+
}
|
|
8315
|
+
function xmlEscape(value) {
|
|
8316
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
8317
|
+
}
|
|
8318
|
+
function systemdEscape(value) {
|
|
8319
|
+
return value.includes(" ") ? `"${value.replaceAll('"', '\\"')}"` : value;
|
|
8320
|
+
}
|
|
8321
|
+
function shellQuote(value) {
|
|
8322
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
8323
|
+
}
|
|
8324
|
+
function quoteWindows(value) {
|
|
8325
|
+
return `"${value.replaceAll('"', '\\"')}"`;
|
|
8326
|
+
}
|
|
8327
|
+
function windowsTaskName(job) {
|
|
8328
|
+
return `AnchorAutosync-${safeName(job.id)}`;
|
|
8329
|
+
}
|
|
8330
|
+
|
|
8331
|
+
// src/ci.ts
|
|
8332
|
+
import fs11 from "fs";
|
|
8333
|
+
import path22 from "path";
|
|
7502
8334
|
function runAnchorCi(db, cwd, input = {}) {
|
|
7503
8335
|
initializeSchema(db);
|
|
7504
8336
|
const status = getIndexStatus(cwd, false);
|
|
7505
8337
|
const minCoverage = input.minCoverage ?? 70;
|
|
7506
8338
|
const rules = validateTeamRulesFile(cwd);
|
|
7507
8339
|
const evidence = rules.ok ? checkTeamRuleEvidence(cwd) : void 0;
|
|
7508
|
-
const evalsPath2 =
|
|
7509
|
-
const evals =
|
|
8340
|
+
const evalsPath2 = path22.join(cwd, ANCHOR_EVALS_FILE);
|
|
8341
|
+
const evals = fs11.existsSync(evalsPath2) ? runRetrievalEvals(db, cwd) : void 0;
|
|
7510
8342
|
const checks = [
|
|
7511
8343
|
{
|
|
7512
8344
|
name: "coverage",
|
|
@@ -9018,156 +9850,8 @@ async function fetchMergedPullRequests(options) {
|
|
|
9018
9850
|
}
|
|
9019
9851
|
}
|
|
9020
9852
|
|
|
9021
|
-
// src/org/config.ts
|
|
9022
|
-
import fs10 from "fs";
|
|
9023
|
-
import os2 from "os";
|
|
9024
|
-
import path21 from "path";
|
|
9025
|
-
import { z as z2 } from "zod";
|
|
9026
|
-
var ORG_REPO_GROUPS = ["backend", "frontend", "shared", "infra", "docs", "unknown"];
|
|
9027
|
-
var OrgRepoSchema = z2.object({
|
|
9028
|
-
fullName: z2.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/),
|
|
9029
|
-
alias: z2.string().min(1),
|
|
9030
|
-
group: z2.enum(ORG_REPO_GROUPS),
|
|
9031
|
-
cloneUrl: z2.string().min(1),
|
|
9032
|
-
defaultBranch: z2.string().min(1),
|
|
9033
|
-
enabled: z2.boolean()
|
|
9034
|
-
});
|
|
9035
|
-
var OrgConfigSchema = z2.object({
|
|
9036
|
-
version: z2.literal(1),
|
|
9037
|
-
org: z2.string().regex(/^[A-Za-z0-9_.-]+$/),
|
|
9038
|
-
repos: z2.array(OrgRepoSchema)
|
|
9039
|
-
});
|
|
9040
|
-
function validateOrgName(org) {
|
|
9041
|
-
const trimmed = org.trim();
|
|
9042
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(trimmed)) {
|
|
9043
|
-
throw new Error("Invalid org name. Use only letters, numbers, dot, underscore, and hyphen.");
|
|
9044
|
-
}
|
|
9045
|
-
return trimmed;
|
|
9046
|
-
}
|
|
9047
|
-
function validateOrgRepoFullName(fullName) {
|
|
9048
|
-
const trimmed = fullName.trim();
|
|
9049
|
-
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(trimmed)) {
|
|
9050
|
-
throw new Error("Invalid repo name. Use owner/name.");
|
|
9051
|
-
}
|
|
9052
|
-
return trimmed;
|
|
9053
|
-
}
|
|
9054
|
-
function validateOrgRepoGroup(group) {
|
|
9055
|
-
if (!group) return "unknown";
|
|
9056
|
-
if (ORG_REPO_GROUPS.includes(group)) return group;
|
|
9057
|
-
throw new Error(`Invalid repo group: ${group}`);
|
|
9058
|
-
}
|
|
9059
|
-
function defaultOrgBaseDir() {
|
|
9060
|
-
if (process.env.ANCHOR_ORG_HOME) return process.env.ANCHOR_ORG_HOME;
|
|
9061
|
-
return path21.join(os2.homedir(), ".anchor", "orgs");
|
|
9062
|
-
}
|
|
9063
|
-
function orgRoot(org, baseDir = defaultOrgBaseDir()) {
|
|
9064
|
-
return path21.join(baseDir, validateOrgName(org));
|
|
9065
|
-
}
|
|
9066
|
-
function orgConfigPath(org, baseDir = defaultOrgBaseDir()) {
|
|
9067
|
-
return path21.join(orgRoot(org, baseDir), "org.json");
|
|
9068
|
-
}
|
|
9069
|
-
function orgDatabasePath(org, baseDir = defaultOrgBaseDir()) {
|
|
9070
|
-
return path21.join(orgRoot(org, baseDir), "org.sqlite");
|
|
9071
|
-
}
|
|
9072
|
-
function orgReposRoot(org, baseDir = defaultOrgBaseDir()) {
|
|
9073
|
-
return path21.join(orgRoot(org, baseDir), "repos");
|
|
9074
|
-
}
|
|
9075
|
-
function repoAliasFromFullName(fullName) {
|
|
9076
|
-
return validateOrgRepoFullName(fullName).split("/")[1] ?? fullName.replace(/\W+/g, "-");
|
|
9077
|
-
}
|
|
9078
|
-
function defaultOrgCloneUrl(fullName) {
|
|
9079
|
-
return `https://github.com/${validateOrgRepoFullName(fullName)}.git`;
|
|
9080
|
-
}
|
|
9081
|
-
function orgRepoLocalPath(org, repo, baseDir = defaultOrgBaseDir()) {
|
|
9082
|
-
const safeAlias = repo.alias.replace(/[^A-Za-z0-9_.-]/g, "-") || repoAliasFromFullName(repo.fullName);
|
|
9083
|
-
return path21.join(orgReposRoot(org, baseDir), safeAlias);
|
|
9084
|
-
}
|
|
9085
|
-
function parseOrgConfig(text) {
|
|
9086
|
-
const parsed = OrgConfigSchema.parse(JSON.parse(text));
|
|
9087
|
-
return parsed;
|
|
9088
|
-
}
|
|
9089
|
-
function atomicWriteJson(filePath, value) {
|
|
9090
|
-
fs10.mkdirSync(path21.dirname(filePath), { recursive: true });
|
|
9091
|
-
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9092
|
-
fs10.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
9093
|
-
`, { mode: 384 });
|
|
9094
|
-
fs10.renameSync(tmp, filePath);
|
|
9095
|
-
}
|
|
9096
|
-
function loadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
9097
|
-
const filePath = orgConfigPath(org, baseDir);
|
|
9098
|
-
if (!fs10.existsSync(filePath)) {
|
|
9099
|
-
throw new Error(
|
|
9100
|
-
`Anchor org config not found at ${filePath}. Run anchor org init --org ${org}.`
|
|
9101
|
-
);
|
|
9102
|
-
}
|
|
9103
|
-
return parseOrgConfig(fs10.readFileSync(filePath, "utf8"));
|
|
9104
|
-
}
|
|
9105
|
-
function maybeLoadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
9106
|
-
const filePath = orgConfigPath(org, baseDir);
|
|
9107
|
-
if (!fs10.existsSync(filePath)) return void 0;
|
|
9108
|
-
return loadOrgConfig(org, baseDir);
|
|
9109
|
-
}
|
|
9110
|
-
function saveOrgConfig(config, baseDir = defaultOrgBaseDir()) {
|
|
9111
|
-
const parsed = OrgConfigSchema.parse(config);
|
|
9112
|
-
atomicWriteJson(orgConfigPath(parsed.org, baseDir), parsed);
|
|
9113
|
-
return parsed;
|
|
9114
|
-
}
|
|
9115
|
-
function initOrgConfig(org, baseDir = defaultOrgBaseDir()) {
|
|
9116
|
-
const normalizedOrg = validateOrgName(org);
|
|
9117
|
-
fs10.mkdirSync(orgReposRoot(normalizedOrg, baseDir), { recursive: true });
|
|
9118
|
-
const existing = maybeLoadOrgConfig(normalizedOrg, baseDir);
|
|
9119
|
-
if (existing) return existing;
|
|
9120
|
-
return saveOrgConfig({ version: 1, org: normalizedOrg, repos: [] }, baseDir);
|
|
9121
|
-
}
|
|
9122
|
-
function addOrgRepoConfig(org, repoFullName, input = {}, baseDir = defaultOrgBaseDir()) {
|
|
9123
|
-
const config = initOrgConfig(org, baseDir);
|
|
9124
|
-
const fullName = validateOrgRepoFullName(repoFullName);
|
|
9125
|
-
const existing = config.repos.find((repo) => repo.fullName === fullName);
|
|
9126
|
-
const candidate = {
|
|
9127
|
-
fullName,
|
|
9128
|
-
alias: input.alias?.trim() || existing?.alias || repoAliasFromFullName(fullName),
|
|
9129
|
-
group: validateOrgRepoGroup(input.group ?? existing?.group),
|
|
9130
|
-
cloneUrl: input.cloneUrl?.trim() || existing?.cloneUrl || defaultOrgCloneUrl(fullName),
|
|
9131
|
-
defaultBranch: input.defaultBranch?.trim() || existing?.defaultBranch || "main",
|
|
9132
|
-
enabled: true
|
|
9133
|
-
};
|
|
9134
|
-
const repos = existing ? config.repos.map((repo) => repo.fullName === fullName ? candidate : repo) : [...config.repos, candidate];
|
|
9135
|
-
return saveOrgConfig(
|
|
9136
|
-
{ ...config, repos: repos.sort((a, b) => a.fullName.localeCompare(b.fullName)) },
|
|
9137
|
-
baseDir
|
|
9138
|
-
);
|
|
9139
|
-
}
|
|
9140
|
-
function removeOrgRepoConfig(org, repoFullName, baseDir = defaultOrgBaseDir()) {
|
|
9141
|
-
const config = loadOrgConfig(org, baseDir);
|
|
9142
|
-
const fullName = validateOrgRepoFullName(repoFullName);
|
|
9143
|
-
return saveOrgConfig(
|
|
9144
|
-
{
|
|
9145
|
-
...config,
|
|
9146
|
-
repos: config.repos.map(
|
|
9147
|
-
(repo) => repo.fullName === fullName ? { ...repo, enabled: false } : repo
|
|
9148
|
-
)
|
|
9149
|
-
},
|
|
9150
|
-
baseDir
|
|
9151
|
-
);
|
|
9152
|
-
}
|
|
9153
|
-
function listOrgNames(baseDir = defaultOrgBaseDir()) {
|
|
9154
|
-
if (!fs10.existsSync(baseDir)) return [];
|
|
9155
|
-
return fs10.readdirSync(baseDir, { withFileTypes: true }).filter(
|
|
9156
|
-
(entry) => entry.isDirectory() && fs10.existsSync(path21.join(baseDir, entry.name, "org.json"))
|
|
9157
|
-
).map((entry) => entry.name).sort();
|
|
9158
|
-
}
|
|
9159
|
-
function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
|
|
9160
|
-
if (org) return validateOrgName(org);
|
|
9161
|
-
const names = listOrgNames(baseDir);
|
|
9162
|
-
if (names.length === 1) return names[0] ?? "";
|
|
9163
|
-
if (names.length === 0) {
|
|
9164
|
-
throw new Error("No Anchor org configured. Run anchor org init --org <org>.");
|
|
9165
|
-
}
|
|
9166
|
-
throw new Error(`Multiple Anchor orgs configured (${names.join(", ")}). Pass org explicitly.`);
|
|
9167
|
-
}
|
|
9168
|
-
|
|
9169
9853
|
// src/org/database.ts
|
|
9170
|
-
import
|
|
9854
|
+
import fs12 from "fs";
|
|
9171
9855
|
var DEFAULT_EDGE_DISTRIBUTION = {
|
|
9172
9856
|
strong: 0,
|
|
9173
9857
|
moderate: 0,
|
|
@@ -9429,7 +10113,7 @@ function getOrgStatus(db, config, baseDir, options = {}) {
|
|
|
9429
10113
|
db.prepare("SELECT * FROM org_repo_state WHERE org = ?").all(config.org).map((row) => [row.repo, row])
|
|
9430
10114
|
);
|
|
9431
10115
|
const clonedRepoCount = enabledRepos.filter(
|
|
9432
|
-
(repo) =>
|
|
10116
|
+
(repo) => fs12.existsSync(orgRepoLocalPath(config.org, repo, baseDir))
|
|
9433
10117
|
).length;
|
|
9434
10118
|
const codeFileCount = count(db, "code_files");
|
|
9435
10119
|
const codeChunkCount = count(db, "code_chunks");
|
|
@@ -9512,7 +10196,7 @@ function getOrgStatus(db, config, baseDir, options = {}) {
|
|
|
9512
10196
|
return {
|
|
9513
10197
|
...repo,
|
|
9514
10198
|
localPath,
|
|
9515
|
-
cloned:
|
|
10199
|
+
cloned: fs12.existsSync(localPath),
|
|
9516
10200
|
currentCommit: state?.current_commit ?? void 0,
|
|
9517
10201
|
lastPulledAt: state?.last_pulled_at ?? void 0,
|
|
9518
10202
|
lastCodeIndexedAt: state?.last_code_indexed_at ?? void 0,
|
|
@@ -9524,20 +10208,20 @@ function getOrgStatus(db, config, baseDir, options = {}) {
|
|
|
9524
10208
|
}
|
|
9525
10209
|
|
|
9526
10210
|
// src/org/heartbeat.ts
|
|
9527
|
-
import
|
|
9528
|
-
import
|
|
10211
|
+
import fs13 from "fs";
|
|
10212
|
+
import path23 from "path";
|
|
9529
10213
|
var HEARTBEAT_STALE_AFTER_MS = 2 * 60 * 1e3;
|
|
9530
10214
|
function orgHeartbeatPath(org, baseDir) {
|
|
9531
|
-
return
|
|
10215
|
+
return path23.join(orgRoot(validateOrgName(org), baseDir), "sync-heartbeat.json");
|
|
9532
10216
|
}
|
|
9533
10217
|
function atomicWriteJson2(filePath, value) {
|
|
9534
|
-
|
|
10218
|
+
fs13.mkdirSync(path23.dirname(filePath), { recursive: true });
|
|
9535
10219
|
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
9536
|
-
|
|
10220
|
+
fs13.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
9537
10221
|
`, { mode: 384 });
|
|
9538
|
-
|
|
10222
|
+
fs13.renameSync(tmp, filePath);
|
|
9539
10223
|
}
|
|
9540
|
-
function
|
|
10224
|
+
function processIsRunning2(pid) {
|
|
9541
10225
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
9542
10226
|
try {
|
|
9543
10227
|
process.kill(pid, 0);
|
|
@@ -9627,20 +10311,20 @@ function writeOrgHeartbeat(heartbeat, baseDir) {
|
|
|
9627
10311
|
function clearOrgHeartbeat(org, baseDir) {
|
|
9628
10312
|
try {
|
|
9629
10313
|
const filePath = orgHeartbeatPath(org, baseDir);
|
|
9630
|
-
if (
|
|
10314
|
+
if (fs13.existsSync(filePath)) fs13.unlinkSync(filePath);
|
|
9631
10315
|
} catch {
|
|
9632
10316
|
}
|
|
9633
10317
|
}
|
|
9634
10318
|
function readOrgHeartbeat(org, baseDir) {
|
|
9635
10319
|
const filePath = orgHeartbeatPath(org, baseDir);
|
|
9636
|
-
if (!
|
|
10320
|
+
if (!fs13.existsSync(filePath)) return void 0;
|
|
9637
10321
|
try {
|
|
9638
|
-
const heartbeat = parseHeartbeat(JSON.parse(
|
|
10322
|
+
const heartbeat = parseHeartbeat(JSON.parse(fs13.readFileSync(filePath, "utf8")));
|
|
9639
10323
|
if (!heartbeat) return void 0;
|
|
9640
10324
|
const now = Date.now();
|
|
9641
10325
|
const startedAtMs = Date.parse(heartbeat.startedAt);
|
|
9642
10326
|
const updatedAtMs = Date.parse(heartbeat.updatedAt);
|
|
9643
|
-
const pidRunning =
|
|
10327
|
+
const pidRunning = processIsRunning2(heartbeat.pid);
|
|
9644
10328
|
const lastUpdateAgeSeconds = Number.isFinite(updatedAtMs) ? Math.max(0, Math.floor((now - updatedAtMs) / 1e3)) : 0;
|
|
9645
10329
|
return {
|
|
9646
10330
|
...heartbeat,
|
|
@@ -9655,11 +10339,11 @@ function readOrgHeartbeat(org, baseDir) {
|
|
|
9655
10339
|
}
|
|
9656
10340
|
|
|
9657
10341
|
// src/org/clone.ts
|
|
9658
|
-
import { execFileSync as
|
|
9659
|
-
import
|
|
9660
|
-
import
|
|
10342
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
10343
|
+
import fs14 from "fs";
|
|
10344
|
+
import path24 from "path";
|
|
9661
10345
|
function defaultGitCommandRunner(command, args, options = {}) {
|
|
9662
|
-
return
|
|
10346
|
+
return execFileSync5(command, args, {
|
|
9663
10347
|
cwd: options.cwd,
|
|
9664
10348
|
encoding: "utf8",
|
|
9665
10349
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9673,7 +10357,7 @@ function currentCommit(runner, localPath) {
|
|
|
9673
10357
|
}
|
|
9674
10358
|
}
|
|
9675
10359
|
function plannedOrgCloneCommands(repo, localPath) {
|
|
9676
|
-
if (!
|
|
10360
|
+
if (!fs14.existsSync(path24.join(localPath, ".git"))) {
|
|
9677
10361
|
return [
|
|
9678
10362
|
{
|
|
9679
10363
|
command: "git",
|
|
@@ -9702,8 +10386,8 @@ function plannedOrgCloneCommands(repo, localPath) {
|
|
|
9702
10386
|
function cloneOrPullOrgRepo(input) {
|
|
9703
10387
|
const runner = input.runner ?? defaultGitCommandRunner;
|
|
9704
10388
|
const localPath = orgRepoLocalPath(input.org, input.repo, input.baseDir);
|
|
9705
|
-
const existed =
|
|
9706
|
-
|
|
10389
|
+
const existed = fs14.existsSync(path24.join(localPath, ".git"));
|
|
10390
|
+
fs14.mkdirSync(path24.dirname(localPath), { recursive: true });
|
|
9707
10391
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9708
10392
|
try {
|
|
9709
10393
|
const commands = plannedOrgCloneCommands(input.repo, localPath);
|
|
@@ -9804,8 +10488,8 @@ function orgCloneStateFromResult(org, repo, result) {
|
|
|
9804
10488
|
|
|
9805
10489
|
// src/org/graph.ts
|
|
9806
10490
|
import crypto9 from "crypto";
|
|
9807
|
-
import
|
|
9808
|
-
import
|
|
10491
|
+
import fs15 from "fs";
|
|
10492
|
+
import path25 from "path";
|
|
9809
10493
|
var MIN_FILE_EDGE_CONFIDENCE = 0.62;
|
|
9810
10494
|
var MIN_REPO_EDGE_CONFIDENCE = 0.7;
|
|
9811
10495
|
var MIN_VISIBLE_EVIDENCE = 2;
|
|
@@ -9850,10 +10534,10 @@ function evidenceJson(evidence) {
|
|
|
9850
10534
|
return JSON.stringify(evidence);
|
|
9851
10535
|
}
|
|
9852
10536
|
function readPackageManifest(repoPath) {
|
|
9853
|
-
const packagePath =
|
|
9854
|
-
if (!
|
|
10537
|
+
const packagePath = path25.join(repoPath, "package.json");
|
|
10538
|
+
if (!fs15.existsSync(packagePath)) return void 0;
|
|
9855
10539
|
try {
|
|
9856
|
-
return JSON.parse(
|
|
10540
|
+
return JSON.parse(fs15.readFileSync(packagePath, "utf8"));
|
|
9857
10541
|
} catch {
|
|
9858
10542
|
return void 0;
|
|
9859
10543
|
}
|
|
@@ -10596,7 +11280,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
10596
11280
|
}
|
|
10597
11281
|
|
|
10598
11282
|
// src/org/index.ts
|
|
10599
|
-
import
|
|
11283
|
+
import fs16 from "fs";
|
|
10600
11284
|
var ORG_SYNC_RESUME_WINDOW_MS = 12 * 60 * 60 * 1e3;
|
|
10601
11285
|
function readCommit(runner, cwd) {
|
|
10602
11286
|
try {
|
|
@@ -10670,7 +11354,7 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
10670
11354
|
current: repoPosition,
|
|
10671
11355
|
total: repos.length
|
|
10672
11356
|
});
|
|
10673
|
-
if (!
|
|
11357
|
+
if (!fs16.existsSync(localPath)) throw new Error(missingCloneError(repo.fullName, localPath));
|
|
10674
11358
|
emit({
|
|
10675
11359
|
stage: "org_repo_phase",
|
|
10676
11360
|
org: config.org,
|
|
@@ -10730,7 +11414,8 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
10730
11414
|
const pullRequests = await fetchPullRequests({
|
|
10731
11415
|
token: auth.token,
|
|
10732
11416
|
repo: repo.fullName,
|
|
10733
|
-
limit: 200,
|
|
11417
|
+
limit: options.all ? void 0 : 200,
|
|
11418
|
+
all: options.all,
|
|
10734
11419
|
since,
|
|
10735
11420
|
detailConcurrency: options.concurrency,
|
|
10736
11421
|
onProgress: options.onFetchProgress
|
|
@@ -10748,8 +11433,8 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
10748
11433
|
history = indexPullRequests(db, pullRequests, {
|
|
10749
11434
|
cwd: localPath,
|
|
10750
11435
|
repo: repo.fullName,
|
|
10751
|
-
historyCoverage: "limited",
|
|
10752
|
-
historyLimit: 200,
|
|
11436
|
+
historyCoverage: options.all ? "all" : "limited",
|
|
11437
|
+
historyLimit: options.all ? void 0 : 200,
|
|
10753
11438
|
historySince: since,
|
|
10754
11439
|
onProgress: options.onPrIndexProgress
|
|
10755
11440
|
});
|
|
@@ -11639,7 +12324,7 @@ function isTestPath2(filePath) {
|
|
|
11639
12324
|
}
|
|
11640
12325
|
|
|
11641
12326
|
// src/doctor.ts
|
|
11642
|
-
import
|
|
12327
|
+
import fs17 from "fs";
|
|
11643
12328
|
function check(name, ok, message, fix) {
|
|
11644
12329
|
return { name, ok, message, fix: ok ? void 0 : fix };
|
|
11645
12330
|
}
|
|
@@ -11744,7 +12429,7 @@ async function runDoctor(options) {
|
|
|
11744
12429
|
);
|
|
11745
12430
|
}
|
|
11746
12431
|
const dbPath = defaultDatabasePath(gitRoot ?? cwd);
|
|
11747
|
-
const dbExists =
|
|
12432
|
+
const dbExists = fs17.existsSync(dbPath);
|
|
11748
12433
|
checks.push(
|
|
11749
12434
|
check(
|
|
11750
12435
|
".anchor/index.sqlite exists",
|
|
@@ -11811,6 +12496,34 @@ async function runDoctor(options) {
|
|
|
11811
12496
|
);
|
|
11812
12497
|
}
|
|
11813
12498
|
}
|
|
12499
|
+
const autosyncStatus = getAutosyncStatus({ cwd: targetCwd });
|
|
12500
|
+
checks.push(
|
|
12501
|
+
check(
|
|
12502
|
+
"autosync configured",
|
|
12503
|
+
true,
|
|
12504
|
+
autosyncStatus.enabled ? `${autosyncStatus.jobs.length} autosync job(s) configured using ${autosyncStatus.scheduler}.` : "Autosync is not enabled."
|
|
12505
|
+
)
|
|
12506
|
+
);
|
|
12507
|
+
const missingSchedulers = autosyncStatus.jobs.filter(
|
|
12508
|
+
(job) => job.enabled && !job.schedulerDetected
|
|
12509
|
+
);
|
|
12510
|
+
checks.push(
|
|
12511
|
+
check(
|
|
12512
|
+
"autosync scheduler installed",
|
|
12513
|
+
!autosyncStatus.enabled || missingSchedulers.length === 0,
|
|
12514
|
+
missingSchedulers.length === 0 ? "Autosync scheduler files are present." : `${missingSchedulers.length} autosync scheduler job(s) are missing.`,
|
|
12515
|
+
"Run anchor init to reinstall local scheduler jobs."
|
|
12516
|
+
)
|
|
12517
|
+
);
|
|
12518
|
+
const failedJobs = autosyncStatus.jobs.filter((job) => job.failing);
|
|
12519
|
+
checks.push(
|
|
12520
|
+
check(
|
|
12521
|
+
"autosync last run healthy",
|
|
12522
|
+
!autosyncStatus.enabled || failedJobs.length === 0,
|
|
12523
|
+
failedJobs.length === 0 ? `Last autosync result: ${autosyncStatus.lastRun?.status ?? "not run yet"}.` : `${failedJobs.length} autosync job(s) failed recently.`,
|
|
12524
|
+
"Run anchor health and inspect ~/.anchor/logs/autosync/ for the failing job log."
|
|
12525
|
+
)
|
|
12526
|
+
);
|
|
11814
12527
|
return { ok: checks.every((item) => item.ok), checks };
|
|
11815
12528
|
}
|
|
11816
12529
|
|
|
@@ -11824,6 +12537,9 @@ function evaluateIndexHealth(status, rulesOk) {
|
|
|
11824
12537
|
if (status.staleCodeIndex) warnings.push("Code index is older than 7 days or has never run.");
|
|
11825
12538
|
if (!rulesOk) warnings.push("Team rules file is missing or invalid.");
|
|
11826
12539
|
if (status.lastFailedRun) warnings.push(`Last failed index run: ${status.lastFailedRun}.`);
|
|
12540
|
+
if (status.autosync) {
|
|
12541
|
+
warnings.push(...status.autosync.warnings.map((warning) => `Autosync: ${warning}`));
|
|
12542
|
+
}
|
|
11827
12543
|
const hasError = status.health === "missing_database" || status.health === "schema_invalid";
|
|
11828
12544
|
const healthStatus = hasError ? "error" : warnings.length > 0 ? "warning" : "ok";
|
|
11829
12545
|
return {
|
|
@@ -11837,11 +12553,13 @@ function evaluateIndexHealth(status, rulesOk) {
|
|
|
11837
12553
|
coverageScore: status.coverageScore,
|
|
11838
12554
|
coverageGrade: status.coverageGrade,
|
|
11839
12555
|
coverageReasons: status.coverageReasons,
|
|
11840
|
-
suggestedPrompts: status.suggestedPrompts
|
|
12556
|
+
suggestedPrompts: status.suggestedPrompts,
|
|
12557
|
+
autosync: status.autosync
|
|
11841
12558
|
};
|
|
11842
12559
|
}
|
|
11843
12560
|
function getAnchorIndexHealth(cwd) {
|
|
11844
12561
|
const indexStatus = getIndexStatus(cwd);
|
|
12562
|
+
indexStatus.autosync = getAutosyncStatus({ cwd });
|
|
11845
12563
|
const rulesValidation = validateTeamRulesFile(cwd);
|
|
11846
12564
|
return {
|
|
11847
12565
|
...evaluateIndexHealth(indexStatus, rulesValidation.ok),
|
|
@@ -11860,12 +12578,21 @@ export {
|
|
|
11860
12578
|
GitHubGraphQLError,
|
|
11861
12579
|
SCHEMA_SQL,
|
|
11862
12580
|
TEAM_RULES_FILE,
|
|
12581
|
+
acquireAutosyncLock,
|
|
11863
12582
|
addOrgRepoConfig,
|
|
11864
12583
|
addRetrievalEval,
|
|
11865
12584
|
addTeamRule,
|
|
11866
12585
|
agentTargetLabel,
|
|
11867
12586
|
anchorMcpEntry,
|
|
11868
12587
|
architectureFilesFromDiff,
|
|
12588
|
+
autosyncConfigPath,
|
|
12589
|
+
autosyncJobIdForOrg,
|
|
12590
|
+
autosyncJobIdForOrgGraph,
|
|
12591
|
+
autosyncJobIdForRepo,
|
|
12592
|
+
autosyncLockPath,
|
|
12593
|
+
autosyncLocksRoot,
|
|
12594
|
+
autosyncLogsRoot,
|
|
12595
|
+
autosyncRoot,
|
|
11869
12596
|
buildAnchorContextResult,
|
|
11870
12597
|
buildArchitectureFromIndexedData,
|
|
11871
12598
|
buildArchitectureIndex,
|
|
@@ -11900,6 +12627,7 @@ export {
|
|
|
11900
12627
|
countValidTeamRules,
|
|
11901
12628
|
createGitHubClient,
|
|
11902
12629
|
createGitHubGraphQLRequester,
|
|
12630
|
+
defaultCommandRunner,
|
|
11903
12631
|
defaultDatabasePath,
|
|
11904
12632
|
defaultGitCommandRunner,
|
|
11905
12633
|
defaultOrgBaseDir,
|
|
@@ -11909,6 +12637,7 @@ export {
|
|
|
11909
12637
|
detectGitRoot,
|
|
11910
12638
|
detectTestCommands,
|
|
11911
12639
|
detectTestCommandsForFile,
|
|
12640
|
+
disableAutosync,
|
|
11912
12641
|
discoverCodeFiles,
|
|
11913
12642
|
discoverCodeFilesByPaths,
|
|
11914
12643
|
emptyCodeIndexSummary,
|
|
@@ -11939,6 +12668,7 @@ export {
|
|
|
11939
12668
|
getAnchorIndexHealth,
|
|
11940
12669
|
getArchitectureContext,
|
|
11941
12670
|
getArchitectureMapContext,
|
|
12671
|
+
getAutosyncStatus,
|
|
11942
12672
|
getCodeIndexStateForRepo,
|
|
11943
12673
|
getGitHubRateLimitDelayMs,
|
|
11944
12674
|
getGraphQLFetchCheckpoint,
|
|
@@ -11972,6 +12702,7 @@ export {
|
|
|
11972
12702
|
initPlaybooks,
|
|
11973
12703
|
initRetrievalEvals,
|
|
11974
12704
|
initializeSchema,
|
|
12705
|
+
installDefaultAutosync,
|
|
11975
12706
|
isAnchorAgentTarget,
|
|
11976
12707
|
isGitHubGraphQLResourceLimitError,
|
|
11977
12708
|
isGitHubRateLimitError,
|
|
@@ -12009,10 +12740,12 @@ export {
|
|
|
12009
12740
|
rankRelevantTests,
|
|
12010
12741
|
rankTeamRules,
|
|
12011
12742
|
rankWisdomUnits,
|
|
12743
|
+
readAutosyncConfig,
|
|
12012
12744
|
readDiscoveredCodeFileContent,
|
|
12013
12745
|
readGitHeadCommit,
|
|
12014
12746
|
readOrgHeartbeat,
|
|
12015
12747
|
rebuildOrgGraph,
|
|
12748
|
+
recordAutosyncRun,
|
|
12016
12749
|
recordFeedback,
|
|
12017
12750
|
recordIndexRun,
|
|
12018
12751
|
recordOrgGraphState,
|
|
@@ -12027,6 +12760,7 @@ export {
|
|
|
12027
12760
|
replaceCodeIndex,
|
|
12028
12761
|
repoAliasFromFullName,
|
|
12029
12762
|
requestWithGitHubRateLimit,
|
|
12763
|
+
resolveAutosyncJob,
|
|
12030
12764
|
resolveGitHubToken,
|
|
12031
12765
|
resolveOrgForTool,
|
|
12032
12766
|
resolvePullRequestDetailConcurrency,
|
|
@@ -12060,6 +12794,7 @@ export {
|
|
|
12060
12794
|
validateOrgRepoGroup,
|
|
12061
12795
|
validateTeamRulesFile,
|
|
12062
12796
|
watchCodebase,
|
|
12797
|
+
writeAutosyncConfig,
|
|
12063
12798
|
writeOrgHeartbeat
|
|
12064
12799
|
};
|
|
12065
12800
|
//# sourceMappingURL=index.js.map
|