@batadata/cli 0.1.7 → 0.1.9

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/link.d.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Project linking — Vercel/Neon-style. A linked directory carries its project
3
+ * (and optionally its branch) in `.batadata/project.json`, so an agent no longer
4
+ * has to thread `--project <id>` through every invocation.
5
+ *
6
+ * ── Resolution precedence (the ONE rule, used everywhere) ─────────────────
7
+ * 1. an explicit `--project` / `--branch` flag (per-invocation intent)
8
+ * 2. `.batadata/project.json`, discovered by walking UP from CWD like git (per-directory intent)
9
+ * 3. `defaultProject` in `~/.batarc` (per-machine fallback)
10
+ *
11
+ * Flags always win, so linking never silently overrides an explicit request.
12
+ * The precedence itself lives in the pure `pickProjectId` / `pickBranchId`
13
+ * helpers (unit-testable without fs), and `resolveProjectId` / `resolveBranchId`
14
+ * compose them with the on-disk sources.
15
+ */
16
+ export declare const LINK_DIR = ".batadata";
17
+ export declare const LINK_FILE = "project.json";
18
+ export interface LinkFile {
19
+ projectId: string;
20
+ /** Optional pinned branch (set by `bata db branch checkout`). */
21
+ branchId?: string | null;
22
+ }
23
+ export type ProjectSource = "flag" | "link" | "config" | "none";
24
+ export type BranchSource = "flag" | "link" | "none";
25
+ /**
26
+ * The ordered list of directories to search for a link file, from `startDir`
27
+ * up to (and including) the ceiling — the user's HOME dir or the filesystem
28
+ * root, whichever comes first. Exactly how git bounds its repo discovery.
29
+ *
30
+ * Pure: no fs, no env — just path math — so the walk-up order is unit-testable
31
+ * without ever writing to a real HOME.
32
+ */
33
+ export declare function linkSearchDirs(startDir: string, homeDir: string): string[];
34
+ /**
35
+ * Absolute path to the nearest `.batadata/project.json` at or above `startDir`,
36
+ * or null if none exists within the ceiling. Walks up like git.
37
+ */
38
+ export declare function findLinkFile(startDir?: string, homeDir?: string): string | null;
39
+ /**
40
+ * Read and parse the nearest link file. Returns its absolute path alongside the
41
+ * parsed contents, or null if there's no (valid) link file. A malformed file is
42
+ * treated as absent rather than throwing, so a stray/corrupt file can't wedge
43
+ * every command.
44
+ */
45
+ export declare function readLinkFile(startDir?: string, homeDir?: string): {
46
+ path: string;
47
+ link: LinkFile;
48
+ } | null;
49
+ /**
50
+ * Write `.batadata/project.json` inside `dir` (creating `.batadata/` if needed)
51
+ * and return the absolute path written. `branchId` is normalized to null when
52
+ * absent so the file shape is stable.
53
+ */
54
+ export declare function writeLinkFile(dir: string, link: LinkFile): string;
55
+ /**
56
+ * Delete the nearest link file (and its now-empty `.batadata/` dir, if empty).
57
+ * Returns the path removed, or null if there was nothing to remove. Idempotent.
58
+ */
59
+ export declare function removeLinkFile(startDir?: string, homeDir?: string): string | null;
60
+ /**
61
+ * Pure precedence policy for the project id: flag > linked > config > none.
62
+ * No fs/env access, so the ordering is unit-testable in isolation (mirrors the
63
+ * `pickTeamId` pattern in api.ts).
64
+ */
65
+ export declare function pickProjectId(params: {
66
+ flag?: string;
67
+ linked?: string | null;
68
+ config?: string;
69
+ }): {
70
+ projectId?: string;
71
+ source: ProjectSource;
72
+ };
73
+ /**
74
+ * Pure precedence policy for the branch id: flag > linked > none. There is no
75
+ * config-level default branch, so config isn't a source here.
76
+ */
77
+ export declare function pickBranchId(params: {
78
+ flag?: string;
79
+ linked?: string | null;
80
+ }): {
81
+ branchId?: string;
82
+ source: BranchSource;
83
+ };
84
+ /**
85
+ * Resolve the active project id from all sources in precedence order. `explicit`
86
+ * is an already-parsed `--project` value (or a positional id) — pass it through
87
+ * so a flag always wins over a link file or config default.
88
+ */
89
+ export declare function resolveProjectId(explicit?: string, opts?: {
90
+ startDir?: string;
91
+ homeDir?: string;
92
+ }): {
93
+ projectId?: string;
94
+ source: ProjectSource;
95
+ };
96
+ /**
97
+ * Resolve the active branch id from all sources in precedence order. `explicit`
98
+ * is an already-parsed `--branch` value (id or name); the link file's pinned
99
+ * branch is the fallback.
100
+ */
101
+ export declare function resolveBranchId(explicit?: string, opts?: {
102
+ startDir?: string;
103
+ homeDir?: string;
104
+ }): {
105
+ branchId?: string;
106
+ source: BranchSource;
107
+ };
package/dist/link.js ADDED
@@ -0,0 +1,170 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import { loadConfig } from "./config.js";
5
+ /**
6
+ * Project linking — Vercel/Neon-style. A linked directory carries its project
7
+ * (and optionally its branch) in `.batadata/project.json`, so an agent no longer
8
+ * has to thread `--project <id>` through every invocation.
9
+ *
10
+ * ── Resolution precedence (the ONE rule, used everywhere) ─────────────────
11
+ * 1. an explicit `--project` / `--branch` flag (per-invocation intent)
12
+ * 2. `.batadata/project.json`, discovered by walking UP from CWD like git (per-directory intent)
13
+ * 3. `defaultProject` in `~/.batarc` (per-machine fallback)
14
+ *
15
+ * Flags always win, so linking never silently overrides an explicit request.
16
+ * The precedence itself lives in the pure `pickProjectId` / `pickBranchId`
17
+ * helpers (unit-testable without fs), and `resolveProjectId` / `resolveBranchId`
18
+ * compose them with the on-disk sources.
19
+ */
20
+ export const LINK_DIR = ".batadata";
21
+ export const LINK_FILE = "project.json";
22
+ /** Resolve HOME at call time (env-first) so a redirected HOME — every test does
23
+ * this to avoid touching the developer's real home — is honored. Mirrors the
24
+ * same rule in config.ts. */
25
+ function defaultHome() {
26
+ return process.env.HOME || process.env.USERPROFILE || os.homedir();
27
+ }
28
+ /**
29
+ * The ordered list of directories to search for a link file, from `startDir`
30
+ * up to (and including) the ceiling — the user's HOME dir or the filesystem
31
+ * root, whichever comes first. Exactly how git bounds its repo discovery.
32
+ *
33
+ * Pure: no fs, no env — just path math — so the walk-up order is unit-testable
34
+ * without ever writing to a real HOME.
35
+ */
36
+ export function linkSearchDirs(startDir, homeDir) {
37
+ const dirs = [];
38
+ let dir = path.resolve(startDir);
39
+ const home = path.resolve(homeDir);
40
+ // Guard against a pathological loop; the parent === dir root check is the
41
+ // real terminator.
42
+ for (let i = 0; i < 4096; i++) {
43
+ dirs.push(dir);
44
+ if (dir === home)
45
+ break; // don't search above HOME
46
+ const parent = path.dirname(dir);
47
+ if (parent === dir)
48
+ break; // filesystem root
49
+ dir = parent;
50
+ }
51
+ return dirs;
52
+ }
53
+ /**
54
+ * Absolute path to the nearest `.batadata/project.json` at or above `startDir`,
55
+ * or null if none exists within the ceiling. Walks up like git.
56
+ */
57
+ export function findLinkFile(startDir = process.cwd(), homeDir = defaultHome()) {
58
+ for (const dir of linkSearchDirs(startDir, homeDir)) {
59
+ const candidate = path.join(dir, LINK_DIR, LINK_FILE);
60
+ if (fs.existsSync(candidate))
61
+ return candidate;
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Read and parse the nearest link file. Returns its absolute path alongside the
67
+ * parsed contents, or null if there's no (valid) link file. A malformed file is
68
+ * treated as absent rather than throwing, so a stray/corrupt file can't wedge
69
+ * every command.
70
+ */
71
+ export function readLinkFile(startDir = process.cwd(), homeDir = defaultHome()) {
72
+ const filePath = findLinkFile(startDir, homeDir);
73
+ if (!filePath)
74
+ return null;
75
+ try {
76
+ const raw = fs.readFileSync(filePath, "utf-8");
77
+ const parsed = JSON.parse(raw);
78
+ if (!parsed || typeof parsed.projectId !== "string" || !parsed.projectId) {
79
+ return null;
80
+ }
81
+ return { path: filePath, link: parsed };
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ /**
88
+ * Write `.batadata/project.json` inside `dir` (creating `.batadata/` if needed)
89
+ * and return the absolute path written. `branchId` is normalized to null when
90
+ * absent so the file shape is stable.
91
+ */
92
+ export function writeLinkFile(dir, link) {
93
+ const linkDir = path.join(path.resolve(dir), LINK_DIR);
94
+ fs.mkdirSync(linkDir, { recursive: true });
95
+ const filePath = path.join(linkDir, LINK_FILE);
96
+ const body = {
97
+ projectId: link.projectId,
98
+ branchId: link.branchId ?? null,
99
+ };
100
+ fs.writeFileSync(filePath, JSON.stringify(body, null, 2) + "\n", "utf-8");
101
+ return filePath;
102
+ }
103
+ /**
104
+ * Delete the nearest link file (and its now-empty `.batadata/` dir, if empty).
105
+ * Returns the path removed, or null if there was nothing to remove. Idempotent.
106
+ */
107
+ export function removeLinkFile(startDir = process.cwd(), homeDir = defaultHome()) {
108
+ const filePath = findLinkFile(startDir, homeDir);
109
+ if (!filePath)
110
+ return null;
111
+ try {
112
+ fs.unlinkSync(filePath);
113
+ // Best-effort cleanup of an empty .batadata dir.
114
+ const linkDir = path.dirname(filePath);
115
+ if (path.basename(linkDir) === LINK_DIR && fs.readdirSync(linkDir).length === 0) {
116
+ fs.rmdirSync(linkDir);
117
+ }
118
+ }
119
+ catch {
120
+ // Already gone / racing removal — unlink is best-effort.
121
+ }
122
+ return filePath;
123
+ }
124
+ /**
125
+ * Pure precedence policy for the project id: flag > linked > config > none.
126
+ * No fs/env access, so the ordering is unit-testable in isolation (mirrors the
127
+ * `pickTeamId` pattern in api.ts).
128
+ */
129
+ export function pickProjectId(params) {
130
+ if (params.flag)
131
+ return { projectId: params.flag, source: "flag" };
132
+ if (params.linked)
133
+ return { projectId: params.linked, source: "link" };
134
+ if (params.config)
135
+ return { projectId: params.config, source: "config" };
136
+ return { source: "none" };
137
+ }
138
+ /**
139
+ * Pure precedence policy for the branch id: flag > linked > none. There is no
140
+ * config-level default branch, so config isn't a source here.
141
+ */
142
+ export function pickBranchId(params) {
143
+ if (params.flag)
144
+ return { branchId: params.flag, source: "flag" };
145
+ if (params.linked)
146
+ return { branchId: params.linked, source: "link" };
147
+ return { source: "none" };
148
+ }
149
+ /**
150
+ * Resolve the active project id from all sources in precedence order. `explicit`
151
+ * is an already-parsed `--project` value (or a positional id) — pass it through
152
+ * so a flag always wins over a link file or config default.
153
+ */
154
+ export function resolveProjectId(explicit, opts = {}) {
155
+ const linked = readLinkFile(opts.startDir, opts.homeDir)?.link.projectId ?? null;
156
+ return pickProjectId({
157
+ flag: explicit,
158
+ linked,
159
+ config: loadConfig().defaultProject,
160
+ });
161
+ }
162
+ /**
163
+ * Resolve the active branch id from all sources in precedence order. `explicit`
164
+ * is an already-parsed `--branch` value (id or name); the link file's pinned
165
+ * branch is the fallback.
166
+ */
167
+ export function resolveBranchId(explicit, opts = {}) {
168
+ const linked = readLinkFile(opts.startDir, opts.homeDir)?.link.branchId ?? null;
169
+ return pickBranchId({ flag: explicit, linked });
170
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@batadata/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "CLI for BataDB — serverless Postgres platform",
5
5
  "bin": {
6
6
  "bata": "./dist/index.js"