@cliangdev/flux-plugin 0.0.0-dev.cbdf207 → 0.0.0-dev.df3e9bb
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/README.md +8 -4
- package/bin/install.cjs +150 -16
- package/package.json +7 -11
- package/src/__tests__/version.test.ts +37 -0
- package/src/adapters/local/.gitkeep +0 -0
- package/src/server/__tests__/config.test.ts +163 -0
- package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
- package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
- package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
- package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
- package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
- package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
- package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
- package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
- package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
- package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
- package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
- package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
- package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
- package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
- package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
- package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
- package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
- package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
- package/src/server/adapters/factory.ts +90 -0
- package/src/server/adapters/index.ts +9 -0
- package/src/server/adapters/linear/adapter.ts +1136 -0
- package/src/server/adapters/linear/client.ts +169 -0
- package/src/server/adapters/linear/config.ts +152 -0
- package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
- package/src/server/adapters/linear/helpers/index.ts +7 -0
- package/src/server/adapters/linear/index.ts +16 -0
- package/src/server/adapters/linear/mappers/description.ts +136 -0
- package/src/server/adapters/linear/mappers/epic.ts +81 -0
- package/src/server/adapters/linear/mappers/index.ts +27 -0
- package/src/server/adapters/linear/mappers/prd.ts +178 -0
- package/src/server/adapters/linear/mappers/task.ts +82 -0
- package/src/server/adapters/linear/types.ts +264 -0
- package/src/server/adapters/local-adapter.ts +968 -0
- package/src/server/adapters/types.ts +293 -0
- package/src/server/config.ts +73 -0
- package/src/server/db/__tests__/queries.test.ts +472 -0
- package/src/server/db/ids.ts +17 -0
- package/src/server/db/index.ts +69 -0
- package/src/server/db/queries.ts +142 -0
- package/src/server/db/refs.ts +60 -0
- package/src/server/db/schema.ts +88 -0
- package/src/server/db/sqlite.ts +10 -0
- package/src/server/index.ts +83 -0
- package/src/server/tools/__tests__/crud.test.ts +301 -0
- package/src/server/tools/__tests__/get-version.test.ts +27 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
- package/src/server/tools/__tests__/query.test.ts +353 -0
- package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
- package/src/server/tools/configure-linear.ts +373 -0
- package/src/server/tools/create-epic.ts +35 -0
- package/src/server/tools/create-prd.ts +31 -0
- package/src/server/tools/create-task.ts +38 -0
- package/src/server/tools/criteria.ts +50 -0
- package/src/server/tools/delete-entity.ts +76 -0
- package/src/server/tools/dependencies.ts +55 -0
- package/src/server/tools/get-entity.ts +238 -0
- package/src/server/tools/get-linear-url.ts +28 -0
- package/src/server/tools/get-project-context.ts +33 -0
- package/src/server/tools/get-stats.ts +52 -0
- package/src/server/tools/get-version.ts +20 -0
- package/src/server/tools/index.ts +114 -0
- package/src/server/tools/init-project.ts +108 -0
- package/src/server/tools/query-entities.ts +167 -0
- package/src/server/tools/render-status.ts +201 -0
- package/src/server/tools/update-entity.ts +140 -0
- package/src/server/tools/update-status.ts +166 -0
- package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
- package/src/server/utils/logger.ts +9 -0
- package/src/server/utils/mcp-response.ts +254 -0
- package/src/server/utils/status-transitions.ts +160 -0
- package/src/status-line/__tests__/status-line.test.ts +215 -0
- package/src/status-line/index.ts +147 -0
- package/src/utils/__tests__/chalk-import.test.ts +32 -0
- package/src/utils/__tests__/display.test.ts +97 -0
- package/src/utils/__tests__/status-renderer.test.ts +310 -0
- package/src/utils/display.ts +62 -0
- package/src/utils/status-renderer.ts +188 -0
- package/src/version.ts +5 -0
- package/dist/server/index.js +0 -87063
package/README.md
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Agent-orchestrated, spec-driven workflow for Claude Code.
|
|
4
4
|
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
This plugin requires [Bun](https://bun.sh). If you don't have Bun installed, the installer will offer to install it for you.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
|
|
12
|
+
bunx @cliangdev/flux-plugin
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
This installs:
|
|
@@ -16,8 +20,8 @@ This installs:
|
|
|
16
20
|
### Options
|
|
17
21
|
|
|
18
22
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
bunx @cliangdev/flux-plugin --global # Install to ~/.claude (all projects)
|
|
24
|
+
bunx @cliangdev/flux-plugin --local # Install to ./.claude (current project)
|
|
21
25
|
```
|
|
22
26
|
|
|
23
27
|
### What Gets Configured
|
|
@@ -121,7 +125,7 @@ your-project/
|
|
|
121
125
|
To update to the latest version, simply re-run the installer:
|
|
122
126
|
|
|
123
127
|
```bash
|
|
124
|
-
|
|
128
|
+
bunx @cliangdev/flux-plugin@latest --global
|
|
125
129
|
```
|
|
126
130
|
|
|
127
131
|
This will:
|
package/bin/install.cjs
CHANGED
|
@@ -4,22 +4,45 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const readline = require("readline");
|
|
7
|
+
const { execSync, spawn } = require("child_process");
|
|
7
8
|
|
|
8
9
|
const args = process.argv.slice(2);
|
|
9
10
|
|
|
10
11
|
if (args[0] === "serve") {
|
|
11
|
-
|
|
12
|
+
const serverSrc = path.join(__dirname, "..", "src", "server", "index.ts");
|
|
13
|
+
const bunPath = getBunPath();
|
|
14
|
+
if (!bunPath) {
|
|
15
|
+
console.error("Failed to start Flux MCP server: Bun is required but not found");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const child = spawn(bunPath, ["run", serverSrc], { stdio: "inherit" });
|
|
19
|
+
child.on("error", (err) => {
|
|
12
20
|
console.error("Failed to start Flux MCP server:", err.message);
|
|
13
21
|
process.exit(1);
|
|
14
22
|
});
|
|
23
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
15
24
|
} else {
|
|
16
25
|
runInstaller();
|
|
17
26
|
}
|
|
18
27
|
|
|
28
|
+
function getBunPath() {
|
|
29
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
30
|
+
const bunBinary = process.platform === "win32" ? "bun.exe" : "bun";
|
|
31
|
+
const localBunPath = path.join(bunDir, bunBinary);
|
|
32
|
+
if (fs.existsSync(localBunPath)) return localBunPath;
|
|
33
|
+
try {
|
|
34
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
35
|
+
return "bun";
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
function runInstaller() {
|
|
20
42
|
const cyan = "\x1b[36m";
|
|
21
43
|
const green = "\x1b[32m";
|
|
22
44
|
const yellow = "\x1b[33m";
|
|
45
|
+
const red = "\x1b[31m";
|
|
23
46
|
const dim = "\x1b[2m";
|
|
24
47
|
const reset = "\x1b[0m";
|
|
25
48
|
const pkg = require("../package.json");
|
|
@@ -43,7 +66,7 @@ ${cyan} ███████╗██╗ ██╗ ██╗██╗
|
|
|
43
66
|
console.log(banner);
|
|
44
67
|
|
|
45
68
|
if (hasHelp) {
|
|
46
|
-
console.log(` ${yellow}Usage:${reset}
|
|
69
|
+
console.log(` ${yellow}Usage:${reset} bunx @cliangdev/flux-plugin [options]
|
|
47
70
|
|
|
48
71
|
${yellow}Options:${reset}
|
|
49
72
|
${cyan}-g, --global${reset} Install globally (to ~/.claude)
|
|
@@ -52,17 +75,123 @@ ${cyan} ███████╗██╗ ██╗ ██╗██╗
|
|
|
52
75
|
|
|
53
76
|
${yellow}Examples:${reset}
|
|
54
77
|
${dim}# Interactive installation${reset}
|
|
55
|
-
|
|
78
|
+
bunx @cliangdev/flux-plugin
|
|
56
79
|
|
|
57
80
|
${dim}# Install globally (all projects)${reset}
|
|
58
|
-
|
|
81
|
+
bunx @cliangdev/flux-plugin --global
|
|
59
82
|
|
|
60
83
|
${dim}# Install locally (current project only)${reset}
|
|
61
|
-
|
|
84
|
+
bunx @cliangdev/flux-plugin --local
|
|
85
|
+
|
|
86
|
+
${yellow}Note:${reset} This plugin requires Bun. Install from https://bun.sh
|
|
62
87
|
`);
|
|
63
88
|
process.exit(0);
|
|
64
89
|
}
|
|
65
90
|
|
|
91
|
+
function isBunInstalled() {
|
|
92
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
93
|
+
const envPath = process.env.PATH || "";
|
|
94
|
+
const pathWithBun = envPath.includes(bunDir)
|
|
95
|
+
? envPath
|
|
96
|
+
: `${bunDir}${path.delimiter}${envPath}`;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
execSync("bun --version", {
|
|
100
|
+
stdio: "ignore",
|
|
101
|
+
env: { ...process.env, PATH: pathWithBun },
|
|
102
|
+
});
|
|
103
|
+
return true;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function installBun() {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const platform = os.platform();
|
|
112
|
+
const installCmd = platform === "win32" ? "powershell" : "/bin/sh";
|
|
113
|
+
const installArgs = platform === "win32"
|
|
114
|
+
? ["-c", "irm bun.sh/install.ps1 | iex"]
|
|
115
|
+
: ["-c", "curl -fsSL https://bun.sh/install | bash"];
|
|
116
|
+
|
|
117
|
+
console.log(`\n ${cyan}Installing Bun...${reset}\n`);
|
|
118
|
+
|
|
119
|
+
const child = spawn(installCmd, installArgs, {
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
shell: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
child.on("close", (code) => {
|
|
125
|
+
if (code === 0) {
|
|
126
|
+
console.log(`\n ${green}✓${reset} Bun installed successfully\n`);
|
|
127
|
+
resolve(true);
|
|
128
|
+
} else {
|
|
129
|
+
reject(new Error(`Installation exited with code ${code}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
child.on("error", (err) => {
|
|
134
|
+
reject(err);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function showBunInstallInstructions() {
|
|
140
|
+
console.log(`
|
|
141
|
+
${yellow}Bun is required but not installed.${reset}
|
|
142
|
+
|
|
143
|
+
Install Bun manually:
|
|
144
|
+
|
|
145
|
+
${cyan}macOS/Linux:${reset}
|
|
146
|
+
curl -fsSL https://bun.sh/install | bash
|
|
147
|
+
|
|
148
|
+
${cyan}Windows:${reset}
|
|
149
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
150
|
+
|
|
151
|
+
Then restart your terminal and run this installer again.
|
|
152
|
+
|
|
153
|
+
${dim}Learn more: https://bun.sh${reset}
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function checkBunAndContinue(callback) {
|
|
158
|
+
if (isBunInstalled()) {
|
|
159
|
+
callback();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rl = readline.createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log(` ${yellow}Bun is required but not installed.${reset}\n`);
|
|
169
|
+
|
|
170
|
+
rl.question(` Install Bun now? ${dim}[Y/n]${reset}: `, async (answer) => {
|
|
171
|
+
rl.close();
|
|
172
|
+
const shouldInstall = answer.trim().toLowerCase() !== "n";
|
|
173
|
+
|
|
174
|
+
if (shouldInstall) {
|
|
175
|
+
try {
|
|
176
|
+
await installBun();
|
|
177
|
+
if (isBunInstalled()) {
|
|
178
|
+
callback();
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` ${yellow}Please restart your terminal to use Bun, then run the installer again.${reset}\n`);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(`\n ${red}Failed to install Bun:${reset} ${err.message}\n`);
|
|
185
|
+
showBunInstallInstructions();
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
showBunInstallInstructions();
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
66
195
|
function copyDir(src, dest) {
|
|
67
196
|
fs.mkdirSync(dest, { recursive: true });
|
|
68
197
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -177,9 +306,10 @@ ${cyan} ███████╗██╗ ██╗ ██╗██╗
|
|
|
177
306
|
mcpConfig.mcpServers = {};
|
|
178
307
|
}
|
|
179
308
|
|
|
309
|
+
const versionTag = pkg.version.includes("-dev.") ? "latest" : pkg.version;
|
|
180
310
|
mcpConfig.mcpServers.flux = {
|
|
181
|
-
command: "
|
|
182
|
-
args: [
|
|
311
|
+
command: "bunx",
|
|
312
|
+
args: [`@cliangdev/flux-plugin@${versionTag}`, "serve"],
|
|
183
313
|
};
|
|
184
314
|
|
|
185
315
|
writeJson(mcpConfigPath, mcpConfig);
|
|
@@ -222,14 +352,18 @@ ${cyan} ███████╗██╗ ██╗ ██╗██╗
|
|
|
222
352
|
});
|
|
223
353
|
}
|
|
224
354
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
355
|
+
function startInstallation() {
|
|
356
|
+
if (hasGlobal && hasLocal) {
|
|
357
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
} else if (hasGlobal) {
|
|
360
|
+
install(true);
|
|
361
|
+
} else if (hasLocal) {
|
|
362
|
+
install(false);
|
|
363
|
+
} else {
|
|
364
|
+
promptLocation();
|
|
365
|
+
}
|
|
234
366
|
}
|
|
367
|
+
|
|
368
|
+
checkBunAndContinue(startInstallation);
|
|
235
369
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cliangdev/flux-plugin",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.df3e9bb",
|
|
4
4
|
"description": "Claude Code plugin for AI-first workflow orchestration with MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"flux-plugin": "./bin/install.cjs"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"bin/",
|
|
12
|
-
"
|
|
11
|
+
"bin/install.cjs",
|
|
12
|
+
"src/",
|
|
13
13
|
"skills/",
|
|
14
14
|
"commands/",
|
|
15
15
|
"agents/",
|
|
@@ -17,14 +17,12 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"dev": "bun run src/server/index.ts",
|
|
20
|
-
"build": "bun build src/server/index.ts
|
|
21
|
-
"
|
|
22
|
-
"build:
|
|
23
|
-
"build:compile:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
|
|
24
|
-
"build:compile:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
|
|
20
|
+
"build": "bun build --compile --outfile bin/flux-server src/server/index.ts && bun build --compile --outfile bin/flux-status src/status-line/index.ts",
|
|
21
|
+
"build:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
|
|
22
|
+
"build:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
|
|
25
23
|
"validate": "node scripts/validate-structure.cjs",
|
|
26
24
|
"test:integration": "bun test scripts/__tests__/integration.test.ts --timeout 120000",
|
|
27
|
-
"prepublishOnly": "bun run validate && bun run
|
|
25
|
+
"prepublishOnly": "bun run validate && bun run test:integration",
|
|
28
26
|
"test": "bun test",
|
|
29
27
|
"test:linear-description": "bun run src/server/adapters/__tests__/linear-description-test.ts",
|
|
30
28
|
"typecheck": "tsc --noEmit",
|
|
@@ -53,14 +51,12 @@
|
|
|
53
51
|
"license": "MIT",
|
|
54
52
|
"devDependencies": {
|
|
55
53
|
"@biomejs/biome": "^2.3.11",
|
|
56
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
57
54
|
"@types/bun": "^1.3.6",
|
|
58
55
|
"typescript": "^5.0.0"
|
|
59
56
|
},
|
|
60
57
|
"dependencies": {
|
|
61
58
|
"@linear/sdk": "^70.0.0",
|
|
62
59
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
63
|
-
"better-sqlite3": "^12.6.2",
|
|
64
60
|
"chalk": "^5.4.1",
|
|
65
61
|
"zod": "^4.3.5"
|
|
66
62
|
},
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
describe("version module", () => {
|
|
6
|
+
test("VERSION is exported from src/version.ts", async () => {
|
|
7
|
+
// Dynamic import to get the VERSION constant
|
|
8
|
+
const versionModule = await import("../version.js");
|
|
9
|
+
|
|
10
|
+
expect(versionModule.VERSION).toBeDefined();
|
|
11
|
+
expect(typeof versionModule.VERSION).toBe("string");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("VERSION equals package.json version", async () => {
|
|
15
|
+
// Read package.json version
|
|
16
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
17
|
+
expect(existsSync(packageJsonPath)).toBe(true);
|
|
18
|
+
|
|
19
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
20
|
+
const packageVersion = packageJson.version;
|
|
21
|
+
|
|
22
|
+
expect(packageVersion).toBeDefined();
|
|
23
|
+
expect(typeof packageVersion).toBe("string");
|
|
24
|
+
|
|
25
|
+
// Import VERSION and compare
|
|
26
|
+
const versionModule = await import("../version.js");
|
|
27
|
+
expect(versionModule.VERSION).toBe(packageVersion);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("VERSION is a valid semver format", async () => {
|
|
31
|
+
const versionModule = await import("../version.js");
|
|
32
|
+
|
|
33
|
+
// Basic semver format check (e.g., "0.1.0", "1.2.3-beta.1")
|
|
34
|
+
const semverPattern = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
35
|
+
expect(semverPattern.test(versionModule.VERSION)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
|
|
11
|
+
describe("config", () => {
|
|
12
|
+
const originalEnv = process.env.FLUX_PROJECT_ROOT;
|
|
13
|
+
const originalCwd = process.cwd();
|
|
14
|
+
// Use os.tmpdir() to get the real temp path (handles /tmp -> /private/tmp on macOS)
|
|
15
|
+
const TEST_DIR = `${realpathSync(tmpdir())}/flux-config-test-${Date.now()}`;
|
|
16
|
+
const NESTED_DIR = `${TEST_DIR}/subdir/nested`;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
// Clean up any previous test directory
|
|
20
|
+
if (existsSync(TEST_DIR)) {
|
|
21
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Create test directory structure
|
|
25
|
+
mkdirSync(NESTED_DIR, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Clear the config cache before each test
|
|
28
|
+
const { config } = await import("../config.js");
|
|
29
|
+
config.clearCache();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
// Restore original env
|
|
34
|
+
if (originalEnv !== undefined) {
|
|
35
|
+
process.env.FLUX_PROJECT_ROOT = originalEnv;
|
|
36
|
+
} else {
|
|
37
|
+
delete process.env.FLUX_PROJECT_ROOT;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Restore original cwd
|
|
41
|
+
process.chdir(originalCwd);
|
|
42
|
+
|
|
43
|
+
// Clear the config cache
|
|
44
|
+
const { config } = await import("../config.js");
|
|
45
|
+
config.clearCache();
|
|
46
|
+
|
|
47
|
+
// Clean up test directory
|
|
48
|
+
if (existsSync(TEST_DIR)) {
|
|
49
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("uses env var when properly set", async () => {
|
|
54
|
+
process.env.FLUX_PROJECT_ROOT = "/some/valid/path";
|
|
55
|
+
|
|
56
|
+
const { config } = await import("../config.js");
|
|
57
|
+
config.clearCache();
|
|
58
|
+
|
|
59
|
+
expect(config.projectRoot).toBe("/some/valid/path");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("ignores unresolved template variable and walks up directories", async () => {
|
|
63
|
+
// Create a .flux folder at TEST_DIR
|
|
64
|
+
mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
|
|
65
|
+
writeFileSync(
|
|
66
|
+
`${TEST_DIR}/.flux/project.json`,
|
|
67
|
+
JSON.stringify({ name: "test" }),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Simulate Claude Code passing unresolved template variable
|
|
71
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentionally testing literal template string
|
|
72
|
+
process.env.FLUX_PROJECT_ROOT = "${CLAUDE_PROJECT_DIR}";
|
|
73
|
+
|
|
74
|
+
// Change to nested directory
|
|
75
|
+
process.chdir(NESTED_DIR);
|
|
76
|
+
|
|
77
|
+
const { config } = await import("../config.js");
|
|
78
|
+
config.clearCache();
|
|
79
|
+
|
|
80
|
+
// Should walk up and find TEST_DIR, not use the literal "${CLAUDE_PROJECT_DIR}"
|
|
81
|
+
expect(config.projectRoot).not.toContain("${");
|
|
82
|
+
expect(config.projectRoot).toBe(TEST_DIR);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("walks up directories to find .flux folder", async () => {
|
|
86
|
+
// Create a .flux folder at TEST_DIR (parent of NESTED_DIR)
|
|
87
|
+
mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
|
|
88
|
+
writeFileSync(
|
|
89
|
+
`${TEST_DIR}/.flux/project.json`,
|
|
90
|
+
JSON.stringify({ name: "test" }),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// No env var set
|
|
94
|
+
delete process.env.FLUX_PROJECT_ROOT;
|
|
95
|
+
|
|
96
|
+
// Change to nested directory
|
|
97
|
+
process.chdir(NESTED_DIR);
|
|
98
|
+
|
|
99
|
+
const { config } = await import("../config.js");
|
|
100
|
+
config.clearCache();
|
|
101
|
+
|
|
102
|
+
// Should walk up and find TEST_DIR
|
|
103
|
+
expect(config.projectRoot).toBe(TEST_DIR);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("falls back to cwd when no .flux folder found", async () => {
|
|
107
|
+
// No .flux folder anywhere in TEST_DIR hierarchy
|
|
108
|
+
// No env var set
|
|
109
|
+
delete process.env.FLUX_PROJECT_ROOT;
|
|
110
|
+
|
|
111
|
+
// Change to test directory (which has no .flux)
|
|
112
|
+
process.chdir(TEST_DIR);
|
|
113
|
+
|
|
114
|
+
const { config } = await import("../config.js");
|
|
115
|
+
config.clearCache();
|
|
116
|
+
|
|
117
|
+
// Should fall back to cwd
|
|
118
|
+
expect(config.projectRoot).toBe(TEST_DIR);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("projectExists returns true when project.json exists", async () => {
|
|
122
|
+
mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
|
|
123
|
+
writeFileSync(
|
|
124
|
+
`${TEST_DIR}/.flux/project.json`,
|
|
125
|
+
JSON.stringify({ name: "test" }),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
129
|
+
|
|
130
|
+
const { config } = await import("../config.js");
|
|
131
|
+
config.clearCache();
|
|
132
|
+
|
|
133
|
+
expect(config.projectExists).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("projectExists returns false when project.json does not exist", async () => {
|
|
137
|
+
// No .flux folder
|
|
138
|
+
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
139
|
+
|
|
140
|
+
const { config } = await import("../config.js");
|
|
141
|
+
config.clearCache();
|
|
142
|
+
|
|
143
|
+
expect(config.projectExists).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("caches project root for performance", async () => {
|
|
147
|
+
process.env.FLUX_PROJECT_ROOT = "/first/path";
|
|
148
|
+
|
|
149
|
+
const { config } = await import("../config.js");
|
|
150
|
+
config.clearCache();
|
|
151
|
+
|
|
152
|
+
// First call caches the value
|
|
153
|
+
expect(config.projectRoot).toBe("/first/path");
|
|
154
|
+
|
|
155
|
+
// Changing env var should not affect cached value
|
|
156
|
+
process.env.FLUX_PROJECT_ROOT = "/second/path";
|
|
157
|
+
expect(config.projectRoot).toBe("/first/path");
|
|
158
|
+
|
|
159
|
+
// After clearing cache, should use new env var
|
|
160
|
+
config.clearCache();
|
|
161
|
+
expect(config.projectRoot).toBe("/second/path");
|
|
162
|
+
});
|
|
163
|
+
});
|