flowcat 1.7.0 → 1.8.0
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/core/addTask.ts +1 -11
- package/core/config.ts +2 -64
- package/core/initFlow.ts +0 -5
- package/core/pr.ts +25 -27
- package/core/prWorkflow.ts +8 -15
- package/dist/fcat.mjs +16950 -30286
- package/dist/fweb +0 -0
- package/dist/index.mjs +2330 -16481
- package/package.json +1 -1
package/core/addTask.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
import { resolveGithubToken } from "./config";
|
|
4
3
|
import { withWorkspaceLock } from "./lock";
|
|
5
4
|
import { emitHook } from "./pluginManager";
|
|
6
5
|
import { buildPrAttachment, fetchGitHubPr, parseGitHubPrUrl } from "./pr";
|
|
7
|
-
import type { PrFetched } from "./schemas/pr";
|
|
8
6
|
import type { Task } from "./schemas/task";
|
|
9
7
|
import { taskSchema } from "./schemas/task";
|
|
10
8
|
import { buildTask } from "./taskFactory";
|
|
@@ -59,15 +57,7 @@ export const addTask = async (input: AddTaskInput): Promise<AddTaskResult> => {
|
|
|
59
57
|
if (parsedInput.url) {
|
|
60
58
|
const parsed = parseGitHubPrUrl(parsedInput.url);
|
|
61
59
|
if (parsed) {
|
|
62
|
-
const
|
|
63
|
-
let fetched: PrFetched | null = null;
|
|
64
|
-
if (token) {
|
|
65
|
-
try {
|
|
66
|
-
fetched = await fetchGitHubPr(parsed, token);
|
|
67
|
-
} catch {
|
|
68
|
-
fetched = null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
60
|
+
const fetched = await fetchGitHubPr(parsed);
|
|
71
61
|
prAttachment = buildPrAttachment(parsed, fetched ?? undefined);
|
|
72
62
|
prTitle = fetched?.title ?? `PR #${parsed.number} ${parsed.repo.owner}/${parsed.repo.name}`;
|
|
73
63
|
} else {
|
package/core/config.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { access, mkdir
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { APP_NAME, ENV_FILE } from "./constants";
|
|
5
|
+
import { APP_NAME } from "./constants";
|
|
8
6
|
import { readJsonFile, writeJsonAtomic } from "./json";
|
|
9
7
|
|
|
10
8
|
export type PluginEntry = {
|
|
@@ -36,9 +34,6 @@ export type PluginsConfig = {
|
|
|
36
34
|
|
|
37
35
|
export type AppConfig = {
|
|
38
36
|
autoCommit?: boolean;
|
|
39
|
-
github?: {
|
|
40
|
-
token?: string;
|
|
41
|
-
};
|
|
42
37
|
plugins?: PluginsConfig;
|
|
43
38
|
};
|
|
44
39
|
|
|
@@ -92,60 +87,3 @@ export const resolveAutoCommitEnabled = async (workspaceRoot: string): Promise<b
|
|
|
92
87
|
const globalConfig = await readConfigFile(resolveGlobalConfigPath());
|
|
93
88
|
return globalConfig.autoCommit ?? false;
|
|
94
89
|
};
|
|
95
|
-
|
|
96
|
-
export const resolveWorkspaceEnvPath = (workspaceRoot: string): string => {
|
|
97
|
-
return path.join(workspaceRoot, ENV_FILE);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export const resolveGlobalEnvPath = (): string => {
|
|
101
|
-
const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homedir(), ".config");
|
|
102
|
-
return path.join(configHome, APP_NAME, ENV_FILE);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Read and parse an env file, returning empty object if file doesn't exist
|
|
107
|
-
*/
|
|
108
|
-
export const readEnvFile = async (filePath: string): Promise<Record<string, string>> => {
|
|
109
|
-
if (!(await configFileExists(filePath))) {
|
|
110
|
-
return {};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const content = await readFile(filePath, "utf8");
|
|
115
|
-
return parseEnv(content);
|
|
116
|
-
} catch {
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const resolveGithubToken = async (workspaceRoot: string): Promise<string | null> => {
|
|
122
|
-
// 1. Check environment variables first
|
|
123
|
-
if (process.env.FLOWCAT_GITHUB_TOKEN) {
|
|
124
|
-
return process.env.FLOWCAT_GITHUB_TOKEN;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (process.env.IL_GITHUB_TOKEN) {
|
|
128
|
-
return process.env.IL_GITHUB_TOKEN;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 2. Check workspace .env file
|
|
132
|
-
const workspaceEnv = await readEnvFile(resolveWorkspaceEnvPath(workspaceRoot));
|
|
133
|
-
if (workspaceEnv.GITHUB_TOKEN) {
|
|
134
|
-
return workspaceEnv.GITHUB_TOKEN;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 3. Check global .env file
|
|
138
|
-
const globalEnv = await readEnvFile(resolveGlobalEnvPath());
|
|
139
|
-
if (globalEnv.GITHUB_TOKEN) {
|
|
140
|
-
return globalEnv.GITHUB_TOKEN;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 4. Fallback to config.json (for backwards compatibility)
|
|
144
|
-
const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
|
|
145
|
-
if (workspaceConfig.github?.token) {
|
|
146
|
-
return workspaceConfig.github.token;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const globalConfig = await readConfigFile(resolveGlobalConfigPath());
|
|
150
|
-
return globalConfig.github?.token ?? null;
|
|
151
|
-
};
|
package/core/initFlow.ts
CHANGED
|
@@ -101,17 +101,12 @@ export const runInitFlow = async (options: InitFlowOptions): Promise<InitChoice>
|
|
|
101
101
|
const currentConfig = await readConfigFile(configPath);
|
|
102
102
|
|
|
103
103
|
const autoCommit = currentConfig.autoCommit ?? false;
|
|
104
|
-
const githubToken = currentConfig.github?.token ?? null;
|
|
105
104
|
|
|
106
105
|
await ensureWorkspaceLayout(choice.workspaceRoot);
|
|
107
106
|
|
|
108
107
|
await updateConfigFile(configPath, (current) => ({
|
|
109
108
|
...current,
|
|
110
109
|
autoCommit,
|
|
111
|
-
github: {
|
|
112
|
-
...current.github,
|
|
113
|
-
...(githubToken ? { token: githubToken } : {}),
|
|
114
|
-
},
|
|
115
110
|
}));
|
|
116
111
|
|
|
117
112
|
if (choice.kind === "repo" && choice.repoRoot) {
|
package/core/pr.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
2
3
|
|
|
3
4
|
import type { PrAttachment, PrFetched } from "./schemas/pr";
|
|
4
5
|
import { nowIso } from "./time";
|
|
5
6
|
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
6
9
|
export type ParsedPr = {
|
|
7
10
|
url: string;
|
|
8
11
|
provider: "github";
|
|
@@ -54,36 +57,31 @@ export const buildPrAttachment = (parsed: ParsedPr, fetched?: PrFetched): PrAtta
|
|
|
54
57
|
};
|
|
55
58
|
};
|
|
56
59
|
|
|
57
|
-
export const fetchGitHubPr = async (
|
|
58
|
-
parsed: ParsedPr,
|
|
59
|
-
token?: string | null,
|
|
60
|
-
): Promise<PrFetched | null> => {
|
|
60
|
+
export const fetchGitHubPr = async (parsed: ParsedPr): Promise<PrFetched | null> => {
|
|
61
61
|
if (!parsed.repo || !parsed.number) {
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const response = await octokit.rest.pulls.get({
|
|
71
|
-
owner: parsed.repo.owner,
|
|
72
|
-
repo: parsed.repo.name,
|
|
73
|
-
pull_number: parsed.number,
|
|
74
|
-
});
|
|
65
|
+
try {
|
|
66
|
+
const { stdout } = await execAsync(
|
|
67
|
+
`gh pr view ${parsed.url} --json title,author,state,isDraft,updatedAt`,
|
|
68
|
+
);
|
|
69
|
+
const data = JSON.parse(stdout);
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
// gh returns state as uppercase: "MERGED", "OPEN", "CLOSED"
|
|
72
|
+
const state = data.state.toLowerCase() as PrFetched["state"];
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
return {
|
|
75
|
+
at: nowIso(),
|
|
76
|
+
title: data.title,
|
|
77
|
+
author: {
|
|
78
|
+
login: data.author.login,
|
|
79
|
+
},
|
|
80
|
+
state,
|
|
81
|
+
draft: data.isDraft,
|
|
82
|
+
updated_at: data.updatedAt,
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
89
87
|
};
|
package/core/prWorkflow.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveGithubToken } from "./config";
|
|
2
1
|
import { withWorkspaceLock } from "./lock";
|
|
3
2
|
import { emitHook } from "./pluginManager";
|
|
4
3
|
import { buildPrAttachment, fetchGitHubPr, parseGitHubPrUrl } from "./pr";
|
|
@@ -12,9 +11,9 @@ import { nowIso } from "./time";
|
|
|
12
11
|
import type { TaskAction, TaskStatus } from "./types";
|
|
13
12
|
|
|
14
13
|
export class PrWorkflowError extends Error {
|
|
15
|
-
code: "INVALID_PR_URL" | "
|
|
14
|
+
code: "INVALID_PR_URL" | "FETCH_FAILED";
|
|
16
15
|
|
|
17
|
-
constructor(code: "INVALID_PR_URL" | "
|
|
16
|
+
constructor(code: "INVALID_PR_URL" | "FETCH_FAILED", message: string) {
|
|
18
17
|
super(message);
|
|
19
18
|
this.code = code;
|
|
20
19
|
this.name = "PrWorkflowError";
|
|
@@ -69,15 +68,7 @@ export const attachPrToTask = async (input: {
|
|
|
69
68
|
throw new PrWorkflowError("INVALID_PR_URL", "Invalid PR URL");
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
const
|
|
73
|
-
let fetched: PrFetched | null = null;
|
|
74
|
-
if (token) {
|
|
75
|
-
try {
|
|
76
|
-
fetched = await fetchGitHubPr(parsed, token);
|
|
77
|
-
} catch {
|
|
78
|
-
fetched = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
71
|
+
const fetched = await fetchGitHubPr(parsed);
|
|
81
72
|
const stored = await resolveTask(input.workspaceRoot, input.identifier);
|
|
82
73
|
|
|
83
74
|
const updated: Task = {
|
|
@@ -126,10 +117,12 @@ export const refreshPrForTask = async (input: {
|
|
|
126
117
|
throw new Error("Invalid PR URL");
|
|
127
118
|
}
|
|
128
119
|
|
|
129
|
-
const
|
|
130
|
-
const fetched = await fetchGitHubPr(parsed, token);
|
|
120
|
+
const fetched = await fetchGitHubPr(parsed);
|
|
131
121
|
if (!fetched) {
|
|
132
|
-
throw new PrWorkflowError(
|
|
122
|
+
throw new PrWorkflowError(
|
|
123
|
+
"FETCH_FAILED",
|
|
124
|
+
"Failed to fetch PR. Make sure gh CLI is installed and authenticated.",
|
|
125
|
+
);
|
|
133
126
|
}
|
|
134
127
|
|
|
135
128
|
const baseUpdated: Task = {
|