@arii/boomtick-mcp 0.4.0 → 0.4.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/CHANGELOG.md +7 -0
- package/dist/config.js +43 -17
- package/dist/config.test.js +18 -3
- package/package.json +1 -1
- package/src/config.test.ts +22 -3
- package/src/config.ts +47 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1](https://github.com/arii/boomtick/compare/mcp-v0.4.0...mcp-v0.4.1) (2026-09-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **cli,mcp:** improve workspace config resolution and github repo detection ([#787](https://github.com/arii/boomtick/issues/787)) ([0103e90](https://github.com/arii/boomtick/commit/0103e904d30213cc336a1ce9567ad59257494236))
|
|
9
|
+
|
|
3
10
|
## [0.4.0](https://github.com/arii/boomtick/compare/mcp-v0.3.7...mcp-v0.4.0) (2026-09-04)
|
|
4
11
|
|
|
5
12
|
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import dotenv from "dotenv";
|
|
2
1
|
import path from "path";
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
5
4
|
import { execSync } from "child_process";
|
|
6
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const envPath = path.resolve(__dirname, "../.env");
|
|
7
|
+
if (fs.existsSync(envPath) && typeof process.loadEnvFile === "function") {
|
|
8
|
+
try {
|
|
9
|
+
process.loadEnvFile(envPath);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Ignore if already loaded or invalid
|
|
13
|
+
}
|
|
14
|
+
}
|
|
11
15
|
function getGithubToken() {
|
|
12
16
|
if (process.env.GITHUB_TOKEN) {
|
|
13
17
|
return process.env.GITHUB_TOKEN;
|
|
@@ -31,9 +35,11 @@ export function initializeConfig() {
|
|
|
31
35
|
return cachedDynamicConfig;
|
|
32
36
|
}
|
|
33
37
|
try {
|
|
34
|
-
// Attempt to load core properties from the Python CLI
|
|
38
|
+
// Attempt to load core properties from the Python CLI in the active workspace
|
|
39
|
+
const cwd = process.env.BOOMTICK_REPO_PATH || process.cwd();
|
|
35
40
|
const cmd = `td-cli config view`;
|
|
36
41
|
const output = execSync(cmd, {
|
|
42
|
+
cwd,
|
|
37
43
|
encoding: "utf-8",
|
|
38
44
|
stdio: ["ignore", "pipe", "ignore"]
|
|
39
45
|
});
|
|
@@ -58,10 +64,22 @@ function findRepoRoot() {
|
|
|
58
64
|
if (process.env.BOOMTICK_REPO_PATH) {
|
|
59
65
|
return process.env.BOOMTICK_REPO_PATH;
|
|
60
66
|
}
|
|
61
|
-
// Traverse up to find workspace.json or .git
|
|
62
|
-
let current =
|
|
67
|
+
// Traverse up from process.cwd() to find project_config.json, workspace.json, or .git
|
|
68
|
+
let current = process.cwd();
|
|
63
69
|
while (current !== path.parse(current).root) {
|
|
64
|
-
if (fs.existsSync(path.join(current, "
|
|
70
|
+
if (fs.existsSync(path.join(current, "project_config.json")) ||
|
|
71
|
+
fs.existsSync(path.join(current, "workspace.json")) ||
|
|
72
|
+
fs.existsSync(path.join(current, ".git"))) {
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
current = path.dirname(current);
|
|
76
|
+
}
|
|
77
|
+
// Fallback to __dirname search if discovery from cwd fails
|
|
78
|
+
current = __dirname;
|
|
79
|
+
while (current !== path.parse(current).root) {
|
|
80
|
+
if (fs.existsSync(path.join(current, "project_config.json")) ||
|
|
81
|
+
fs.existsSync(path.join(current, "workspace.json")) ||
|
|
82
|
+
fs.existsSync(path.join(current, ".git"))) {
|
|
65
83
|
return current;
|
|
66
84
|
}
|
|
67
85
|
current = path.dirname(current);
|
|
@@ -72,22 +90,30 @@ function findRepoRoot() {
|
|
|
72
90
|
export const config = {
|
|
73
91
|
get githubToken() { return getGithubToken(); },
|
|
74
92
|
get githubOwner() {
|
|
93
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
94
|
+
if (typeof repoString === "string" && repoString.includes("/")) {
|
|
95
|
+
return repoString.split("/")[0];
|
|
96
|
+
}
|
|
75
97
|
if (process.env.GITHUB_OWNER)
|
|
76
98
|
return process.env.GITHUB_OWNER;
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
99
|
+
const envRepo = process.env.GITHUB_REPOSITORY || process.env.GH_REPO;
|
|
100
|
+
if (envRepo && envRepo.includes("/")) {
|
|
101
|
+
return envRepo.split("/")[0];
|
|
80
102
|
}
|
|
81
|
-
|
|
103
|
+
throw new Error("GITHUB_OWNER must be set via environment variable or project_config.json");
|
|
82
104
|
},
|
|
83
105
|
get githubRepo() {
|
|
106
|
+
const repoString = cachedDynamicConfig?.github_repo;
|
|
107
|
+
if (typeof repoString === "string" && repoString.includes("/")) {
|
|
108
|
+
return repoString.split("/")[1];
|
|
109
|
+
}
|
|
84
110
|
if (process.env.GITHUB_REPO)
|
|
85
111
|
return process.env.GITHUB_REPO;
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
112
|
+
const envRepo = process.env.GITHUB_REPOSITORY || process.env.GH_REPO;
|
|
113
|
+
if (envRepo && envRepo.includes("/")) {
|
|
114
|
+
return envRepo.split("/")[1];
|
|
89
115
|
}
|
|
90
|
-
|
|
116
|
+
throw new Error("GITHUB_REPO must be set via environment variable or project_config.json");
|
|
91
117
|
},
|
|
92
118
|
get repoPath() {
|
|
93
119
|
return findRepoRoot();
|
package/dist/config.test.js
CHANGED
|
@@ -15,12 +15,14 @@ describe("config", () => {
|
|
|
15
15
|
process.env = originalEnv;
|
|
16
16
|
});
|
|
17
17
|
describe("initializeConfig", () => {
|
|
18
|
-
it("should load dynamic config from td-cli config view", async () => {
|
|
18
|
+
it("should load dynamic config from td-cli config view using active cwd", async () => {
|
|
19
19
|
const mockConfig = { github_repo: "owner/repo", base_branch: "origin/main", vite_base_path: "/app/" };
|
|
20
20
|
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
21
21
|
const { initializeConfig } = await import("./config.js");
|
|
22
22
|
const result = initializeConfig();
|
|
23
|
-
expect(execSync).toHaveBeenCalledWith("td-cli config view", expect.
|
|
23
|
+
expect(execSync).toHaveBeenCalledWith("td-cli config view", expect.objectContaining({
|
|
24
|
+
cwd: process.cwd()
|
|
25
|
+
}));
|
|
24
26
|
expect(result).toEqual(mockConfig);
|
|
25
27
|
});
|
|
26
28
|
it("should return cached dynamic config on subsequent calls", async () => {
|
|
@@ -67,7 +69,18 @@ describe("config", () => {
|
|
|
67
69
|
});
|
|
68
70
|
expect(config.githubToken).toBeUndefined();
|
|
69
71
|
});
|
|
70
|
-
it("should
|
|
72
|
+
it("should prioritize cachedDynamicConfig over environment variables for githubOwner and githubRepo", async () => {
|
|
73
|
+
process.env.GITHUB_OWNER = "env-owner";
|
|
74
|
+
process.env.GITHUB_REPO = "env-repo";
|
|
75
|
+
process.env.GITHUB_REPOSITORY = "env-owner/env-repo";
|
|
76
|
+
const mockConfig = { github_repo: "workspace-owner/workspace-repo" };
|
|
77
|
+
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
78
|
+
const { config, initializeConfig } = await import("./config.js");
|
|
79
|
+
initializeConfig();
|
|
80
|
+
expect(config.githubOwner).toBe("workspace-owner");
|
|
81
|
+
expect(config.githubRepo).toBe("workspace-repo");
|
|
82
|
+
});
|
|
83
|
+
it("should fallback to environment variables when cachedDynamicConfig is empty", async () => {
|
|
71
84
|
process.env.GITHUB_OWNER = "custom-owner";
|
|
72
85
|
process.env.GITHUB_REPO = "custom-repo";
|
|
73
86
|
const { config } = await import("./config.js");
|
|
@@ -77,6 +90,8 @@ describe("config", () => {
|
|
|
77
90
|
it("should throw when githubOwner / githubRepo cannot be determined", async () => {
|
|
78
91
|
delete process.env.GITHUB_OWNER;
|
|
79
92
|
delete process.env.GITHUB_REPO;
|
|
93
|
+
delete process.env.GITHUB_REPOSITORY;
|
|
94
|
+
delete process.env.GH_REPO;
|
|
80
95
|
vi.mocked(execSync).mockImplementation(() => {
|
|
81
96
|
throw new Error("td-cli failed");
|
|
82
97
|
});
|
package/package.json
CHANGED
package/src/config.test.ts
CHANGED
|
@@ -20,14 +20,16 @@ describe("config", () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
describe("initializeConfig", () => {
|
|
23
|
-
it("should load dynamic config from td-cli config view", async () => {
|
|
23
|
+
it("should load dynamic config from td-cli config view using active cwd", async () => {
|
|
24
24
|
const mockConfig = { github_repo: "owner/repo", base_branch: "origin/main", vite_base_path: "/app/" };
|
|
25
25
|
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
26
26
|
|
|
27
27
|
const { initializeConfig } = await import("./config.js");
|
|
28
28
|
const result = initializeConfig();
|
|
29
29
|
|
|
30
|
-
expect(execSync).toHaveBeenCalledWith("td-cli config view", expect.
|
|
30
|
+
expect(execSync).toHaveBeenCalledWith("td-cli config view", expect.objectContaining({
|
|
31
|
+
cwd: process.cwd()
|
|
32
|
+
}));
|
|
31
33
|
expect(result).toEqual(mockConfig);
|
|
32
34
|
});
|
|
33
35
|
|
|
@@ -87,7 +89,22 @@ describe("config", () => {
|
|
|
87
89
|
expect(config.githubToken).toBeUndefined();
|
|
88
90
|
});
|
|
89
91
|
|
|
90
|
-
it("should
|
|
92
|
+
it("should prioritize cachedDynamicConfig over environment variables for githubOwner and githubRepo", async () => {
|
|
93
|
+
process.env.GITHUB_OWNER = "env-owner";
|
|
94
|
+
process.env.GITHUB_REPO = "env-repo";
|
|
95
|
+
process.env.GITHUB_REPOSITORY = "env-owner/env-repo";
|
|
96
|
+
|
|
97
|
+
const mockConfig = { github_repo: "workspace-owner/workspace-repo" };
|
|
98
|
+
vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
99
|
+
|
|
100
|
+
const { config, initializeConfig } = await import("./config.js");
|
|
101
|
+
initializeConfig();
|
|
102
|
+
|
|
103
|
+
expect(config.githubOwner).toBe("workspace-owner");
|
|
104
|
+
expect(config.githubRepo).toBe("workspace-repo");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should fallback to environment variables when cachedDynamicConfig is empty", async () => {
|
|
91
108
|
process.env.GITHUB_OWNER = "custom-owner";
|
|
92
109
|
process.env.GITHUB_REPO = "custom-repo";
|
|
93
110
|
const { config } = await import("./config.js");
|
|
@@ -99,6 +116,8 @@ describe("config", () => {
|
|
|
99
116
|
it("should throw when githubOwner / githubRepo cannot be determined", async () => {
|
|
100
117
|
delete process.env.GITHUB_OWNER;
|
|
101
118
|
delete process.env.GITHUB_REPO;
|
|
119
|
+
delete process.env.GITHUB_REPOSITORY;
|
|
120
|
+
delete process.env.GH_REPO;
|
|
102
121
|
vi.mocked(execSync).mockImplementation(() => {
|
|
103
122
|
throw new Error("td-cli failed");
|
|
104
123
|
});
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import dotenv from "dotenv";
|
|
2
1
|
import path from "path";
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
@@ -6,10 +5,14 @@ import { execSync } from "child_process";
|
|
|
6
5
|
|
|
7
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const envPath = path.resolve(__dirname, "../.env");
|
|
9
|
+
if (fs.existsSync(envPath) && typeof process.loadEnvFile === "function") {
|
|
10
|
+
try {
|
|
11
|
+
process.loadEnvFile(envPath);
|
|
12
|
+
} catch {
|
|
13
|
+
// Ignore if already loaded or invalid
|
|
14
|
+
}
|
|
15
|
+
}
|
|
13
16
|
|
|
14
17
|
function getGithubToken(): string | undefined {
|
|
15
18
|
if (process.env.GITHUB_TOKEN) {
|
|
@@ -36,9 +39,11 @@ export function initializeConfig() {
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
try {
|
|
39
|
-
// Attempt to load core properties from the Python CLI
|
|
42
|
+
// Attempt to load core properties from the Python CLI in the active workspace
|
|
43
|
+
const cwd = process.env.BOOMTICK_REPO_PATH || process.cwd();
|
|
40
44
|
const cmd = `td-cli config view`;
|
|
41
45
|
const output = execSync(cmd, {
|
|
46
|
+
cwd,
|
|
42
47
|
encoding: "utf-8",
|
|
43
48
|
stdio: ["ignore", "pipe", "ignore"]
|
|
44
49
|
});
|
|
@@ -64,10 +69,27 @@ function findRepoRoot() {
|
|
|
64
69
|
return process.env.BOOMTICK_REPO_PATH;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
// Traverse up to find workspace.json or .git
|
|
68
|
-
let current =
|
|
72
|
+
// Traverse up from process.cwd() to find project_config.json, workspace.json, or .git
|
|
73
|
+
let current = process.cwd();
|
|
69
74
|
while (current !== path.parse(current).root) {
|
|
70
|
-
if (
|
|
75
|
+
if (
|
|
76
|
+
fs.existsSync(path.join(current, "project_config.json")) ||
|
|
77
|
+
fs.existsSync(path.join(current, "workspace.json")) ||
|
|
78
|
+
fs.existsSync(path.join(current, ".git"))
|
|
79
|
+
) {
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
current = path.dirname(current);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback to __dirname search if discovery from cwd fails
|
|
86
|
+
current = __dirname;
|
|
87
|
+
while (current !== path.parse(current).root) {
|
|
88
|
+
if (
|
|
89
|
+
fs.existsSync(path.join(current, "project_config.json")) ||
|
|
90
|
+
fs.existsSync(path.join(current, "workspace.json")) ||
|
|
91
|
+
fs.existsSync(path.join(current, ".git"))
|
|
92
|
+
) {
|
|
71
93
|
return current;
|
|
72
94
|
}
|
|
73
95
|
current = path.dirname(current);
|
|
@@ -79,20 +101,28 @@ function findRepoRoot() {
|
|
|
79
101
|
export const config = {
|
|
80
102
|
get githubToken() { return getGithubToken(); },
|
|
81
103
|
get githubOwner() {
|
|
82
|
-
if (process.env.GITHUB_OWNER) return process.env.GITHUB_OWNER;
|
|
83
104
|
const repoString = cachedDynamicConfig?.github_repo;
|
|
84
|
-
if (typeof repoString
|
|
85
|
-
|
|
105
|
+
if (typeof repoString === "string" && repoString.includes("/")) {
|
|
106
|
+
return repoString.split("/")[0];
|
|
107
|
+
}
|
|
108
|
+
if (process.env.GITHUB_OWNER) return process.env.GITHUB_OWNER;
|
|
109
|
+
const envRepo = process.env.GITHUB_REPOSITORY || process.env.GH_REPO;
|
|
110
|
+
if (envRepo && envRepo.includes("/")) {
|
|
111
|
+
return envRepo.split("/")[0];
|
|
86
112
|
}
|
|
87
|
-
|
|
113
|
+
throw new Error("GITHUB_OWNER must be set via environment variable or project_config.json");
|
|
88
114
|
},
|
|
89
115
|
get githubRepo() {
|
|
90
|
-
if (process.env.GITHUB_REPO) return process.env.GITHUB_REPO;
|
|
91
116
|
const repoString = cachedDynamicConfig?.github_repo;
|
|
92
|
-
if (typeof repoString
|
|
93
|
-
|
|
117
|
+
if (typeof repoString === "string" && repoString.includes("/")) {
|
|
118
|
+
return repoString.split("/")[1];
|
|
119
|
+
}
|
|
120
|
+
if (process.env.GITHUB_REPO) return process.env.GITHUB_REPO;
|
|
121
|
+
const envRepo = process.env.GITHUB_REPOSITORY || process.env.GH_REPO;
|
|
122
|
+
if (envRepo && envRepo.includes("/")) {
|
|
123
|
+
return envRepo.split("/")[1];
|
|
94
124
|
}
|
|
95
|
-
|
|
125
|
+
throw new Error("GITHUB_REPO must be set via environment variable or project_config.json");
|
|
96
126
|
},
|
|
97
127
|
get repoPath() {
|
|
98
128
|
return findRepoRoot();
|