first-tree-staging 0.0.0-alpha → 0.5.2-staging.23.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.
@@ -0,0 +1,118 @@
1
+ //#region ../../packages/github-scan/src/github-scan/engine/runtime/repo-filter.ts
2
+ /**
3
+ * TS port of `RepoFilter` from
4
+ * `config.rs:39-114`.
5
+ *
6
+ * Allow-list with two shapes:
7
+ * - `owner/repo` → exact match
8
+ * - `owner/*` → all repos under `owner`
9
+ *
10
+ * Empty filter = "match everything". Used to scope notification polls,
11
+ * search queries, and snapshot hydration.
12
+ */
13
+ var RepoFilter = class RepoFilter {
14
+ allowedOwners;
15
+ allowedRepos;
16
+ constructor(owners, repos) {
17
+ this.allowedOwners = owners;
18
+ this.allowedRepos = repos;
19
+ }
20
+ static empty() {
21
+ return new RepoFilter([], []);
22
+ }
23
+ /**
24
+ * Parse a comma-separated list. Throws on an invalid pattern (not
25
+ * `owner/repo` and not `owner/*`).
26
+ */
27
+ static parseCsv(value) {
28
+ const owners = [];
29
+ const repos = [];
30
+ for (const raw of value.split(",")) {
31
+ const trimmed = raw.trim();
32
+ if (trimmed.length === 0) continue;
33
+ if (trimmed.endsWith("/*")) {
34
+ const owner = trimmed.slice(0, -2);
35
+ if (owner.length === 0) throw new Error(`invalid repo allow pattern \`${trimmed}\``);
36
+ if (!owners.includes(owner)) owners.push(owner);
37
+ continue;
38
+ }
39
+ if (trimmed.split("/").length === 2) {
40
+ if (!repos.includes(trimmed)) repos.push(trimmed);
41
+ continue;
42
+ }
43
+ throw new Error(`invalid repo allow pattern \`${trimmed}\`; use owner/repo or owner/*`);
44
+ }
45
+ return new RepoFilter(owners, repos);
46
+ }
47
+ merge(other) {
48
+ const owners = [...this.allowedOwners];
49
+ const repos = [...this.allowedRepos];
50
+ for (const o of other.allowedOwners) if (!owners.includes(o)) owners.push(o);
51
+ for (const r of other.allowedRepos) if (!repos.includes(r)) repos.push(r);
52
+ return new RepoFilter(owners, repos);
53
+ }
54
+ isEmpty() {
55
+ return this.allowedOwners.length === 0 && this.allowedRepos.length === 0;
56
+ }
57
+ matchesRepo(repo) {
58
+ if (this.isEmpty()) return true;
59
+ if (this.allowedRepos.includes(repo)) return true;
60
+ const slash = repo.indexOf("/");
61
+ if (slash <= 0) return false;
62
+ const owner = repo.slice(0, slash);
63
+ return this.allowedOwners.includes(owner);
64
+ }
65
+ owners() {
66
+ return this.allowedOwners;
67
+ }
68
+ repos() {
69
+ return this.allowedRepos;
70
+ }
71
+ /** Human-readable patterns joined by `, `. */
72
+ displayPatterns() {
73
+ return [...this.allowedRepos, ...this.allowedOwners.map((o) => `${o}/*`)].join(", ");
74
+ }
75
+ /** CLI-shaped value (comma-separated). */
76
+ cliValue() {
77
+ return [...this.allowedRepos, ...this.allowedOwners.map((o) => `${o}/*`)].join(",");
78
+ }
79
+ };
80
+ /**
81
+ * Compute the list of `gh search` scopes implied by a filter. Matches
82
+ * `GhClient::search_scopes` in Rust: empty filter → `[All]`; owners
83
+ * + explicit repos each get their own scope.
84
+ */
85
+ function searchScopesFor(filter) {
86
+ if (filter.isEmpty()) return [{ kind: "all" }];
87
+ const scopes = [];
88
+ for (const owner of filter.owners()) scopes.push({
89
+ kind: "owner",
90
+ owner
91
+ });
92
+ for (const repo of filter.repos()) scopes.push({
93
+ kind: "repo",
94
+ repo
95
+ });
96
+ if (scopes.length === 0) return [{ kind: "all" }];
97
+ return scopes;
98
+ }
99
+ //#endregion
100
+ //#region ../../packages/github-scan/src/github-scan/engine/runtime/allow-repo.ts
101
+ const REQUIRED_ALLOW_REPO_USAGE = "--allow-repo <owner/repo[,owner/*,...]>";
102
+ const REQUIRED_ALLOW_REPO_MESSAGE = "missing required --allow-repo <owner/repo[,owner/*,...]>; first-tree github scan startup now requires an explicit repo scope to avoid scanning all notifications";
103
+ function parseAllowRepoArg(argv) {
104
+ for (let i = 0; i < argv.length; i += 1) {
105
+ const arg = argv[i];
106
+ if (arg === "--allow-repo") return argv[i + 1];
107
+ if (arg?.startsWith("--allow-repo=")) return arg.slice(13);
108
+ }
109
+ }
110
+ function requireExplicitRepoFilter(allowRepo) {
111
+ const trimmed = allowRepo?.trim();
112
+ if (!trimmed) throw new Error(REQUIRED_ALLOW_REPO_MESSAGE);
113
+ const filter = RepoFilter.parseCsv(trimmed);
114
+ if (filter.isEmpty()) throw new Error(REQUIRED_ALLOW_REPO_MESSAGE);
115
+ return filter;
116
+ }
117
+ //#endregion
118
+ export { searchScopesFor as a, RepoFilter as i, parseAllowRepoArg as n, requireExplicitRepoFilter as r, REQUIRED_ALLOW_REPO_USAGE as t };
@@ -0,0 +1,63 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region ../../packages/github-scan/src/github-scan/engine/bridge.ts
7
+ /**
8
+ * Path-resolution + spawn helpers shared between the GitHub Scan CLI
9
+ * dispatcher and the daemon's HTTP server. The implementation package is
10
+ * `private: true`, so resolution must work both when running from the
11
+ * workspace package and after tsdown inlines it into `first-tree/dist/index.js`.
12
+ */
13
+ function resolvePackageRootViaRequire(startUrl) {
14
+ try {
15
+ return dirname(createRequire(startUrl).resolve("@first-tree/github-scan/package.json"));
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function walkUpFor(startUrl, predicate) {
21
+ let dir = dirname(fileURLToPath(startUrl));
22
+ while (true) {
23
+ if (predicate(dir)) return dir;
24
+ const parent = dirname(dir);
25
+ if (parent === dir) return null;
26
+ dir = parent;
27
+ }
28
+ }
29
+ function resolveFirstTreePackageRoot(startUrl = import.meta.url) {
30
+ const dev = resolvePackageRootViaRequire(startUrl);
31
+ if (dev !== null) return dev;
32
+ const prod = walkUpFor(startUrl, (dir) => existsSync(join(dir, "assets", "dashboard.html")));
33
+ if (prod !== null) return prod;
34
+ throw new Error("Could not locate the @first-tree/github-scan package root; neither workspace resolution nor bundled-asset lookup succeeded.");
35
+ }
36
+ function resolveStatuslineBundlePath(startUrl = import.meta.url) {
37
+ const dev = resolvePackageRootViaRequire(startUrl);
38
+ if (dev !== null) {
39
+ const candidate = join(dev, "dist", "github-scan-statusline.js");
40
+ if (existsSync(candidate)) return candidate;
41
+ }
42
+ const prod = walkUpFor(startUrl, (dir) => existsSync(join(dir, "github-scan-statusline.js")));
43
+ if (prod !== null) return join(prod, "github-scan-statusline.js");
44
+ throw new Error("Could not locate github-scan-statusline.js; run `pnpm build` first.");
45
+ }
46
+ /**
47
+ * Spawn `command args` synchronously with inherited stdio, propagating
48
+ * the child's exit code (or remapping a signal termination to 1 as a
49
+ * safe fallback).
50
+ */
51
+ function spawnInherit(command, args, deps = {}) {
52
+ const result = (deps.spawn ?? spawnSync)(command, args, { stdio: "inherit" });
53
+ if (result.error) {
54
+ const err = result.error;
55
+ process.stderr.write(`first-tree github scan: failed to spawn \`${command}\`: ${err.message}\n`);
56
+ return 1;
57
+ }
58
+ if (typeof result.status === "number") return result.status;
59
+ if (result.signal) return 1;
60
+ return 0;
61
+ }
62
+ //#endregion
63
+ export { resolveStatuslineBundlePath as n, spawnInherit as r, resolveFirstTreePackageRoot as t };
@@ -0,0 +1,2 @@
1
+ import { n as resolveStatuslineBundlePath, r as spawnInherit } from "./bridge-CTeNSTnc.mjs";
2
+ export { resolveStatuslineBundlePath, spawnInherit };
@@ -0,0 +1,26 @@
1
+ import { createRequire } from "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
21
+ value: mod,
22
+ enumerable: true
23
+ }) : target, mod));
24
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
25
+ //#endregion
26
+ export { __require as n, __toESM as r, __commonJSMin as t };
@@ -0,0 +1,46 @@
1
+ import "./store-Cejh0XL8.mjs";
2
+ import { a as resolveGitHubScanPaths } from "./types-DlQkfkMp.mjs";
3
+ import { i as resolveRunnerHome, l as cleanupExpiredClaims, s as ThreadStore } from "./runner-skeleton-Dng8ydwE.mjs";
4
+ import "./poll-CMlW52Bg.mjs";
5
+ //#region ../../packages/github-scan/src/github-scan/engine/commands/cleanup.ts
6
+ /**
7
+ * TS port of `Service::cleanup` in `service.rs:197-207`.
8
+ *
9
+ * Runs `ThreadStore.cleanupOldWorkspaces` + `cleanupExpiredClaims` and
10
+ * prints the removed paths.
11
+ */
12
+ async function runCleanup(argv = [], options = {}) {
13
+ const write = options.write ?? ((line) => process.stdout.write(`${line}\n`));
14
+ const home = options.runnerHome ?? parseHome(argv) ?? resolveRunnerHome();
15
+ const ttl = options.workspaceTtlSec ?? parseTtl(argv) ?? 48 * 3600;
16
+ const store = new ThreadStore({ runnerHome: home });
17
+ const paths = resolveGitHubScanPaths();
18
+ const removed = store.cleanupOldWorkspaces(ttl, []);
19
+ const clearedClaims = cleanupExpiredClaims(paths.claimsDir);
20
+ write(`removed ${removed.length} stale workspaces`);
21
+ for (const path of removed) write(`- ${path}`);
22
+ write(`cleared ${clearedClaims} expired claim(s)`);
23
+ return 0;
24
+ }
25
+ function parseHome(argv) {
26
+ for (let i = 0; i < argv.length; i += 1) {
27
+ const a = argv[i];
28
+ if (a === "--home") return argv[i + 1];
29
+ if (a?.startsWith("--home=")) return a.slice(7);
30
+ }
31
+ }
32
+ function parseTtl(argv) {
33
+ for (let i = 0; i < argv.length; i += 1) {
34
+ const a = argv[i];
35
+ if (a === "--workspace-ttl-secs") {
36
+ const n = Number.parseInt(argv[i + 1] ?? "", 10);
37
+ return Number.isFinite(n) && n > 0 ? n : void 0;
38
+ }
39
+ if (a?.startsWith("--workspace-ttl-secs=")) {
40
+ const n = Number.parseInt(a.slice(21), 10);
41
+ return Number.isFinite(n) && n > 0 ? n : void 0;
42
+ }
43
+ }
44
+ }
45
+ //#endregion
46
+ export { runCleanup };