flowcat 1.6.3 → 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 +0 -21
- package/core/constants.ts +1 -0
- package/core/gitignore.ts +1 -1
- package/core/initFlow.ts +0 -5
- package/core/pr.ts +25 -27
- package/core/prWorkflow.ts +8 -15
- package/core/workspace.ts +28 -8
- package/dist/fcat.mjs +8230 -21131
- package/dist/fweb +0 -0
- package/dist/index.mjs +5109 -18825
- package/package.json +3 -2
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
|
@@ -34,9 +34,6 @@ export type PluginsConfig = {
|
|
|
34
34
|
|
|
35
35
|
export type AppConfig = {
|
|
36
36
|
autoCommit?: boolean;
|
|
37
|
-
github?: {
|
|
38
|
-
token?: string;
|
|
39
|
-
};
|
|
40
37
|
plugins?: PluginsConfig;
|
|
41
38
|
};
|
|
42
39
|
|
|
@@ -90,21 +87,3 @@ export const resolveAutoCommitEnabled = async (workspaceRoot: string): Promise<b
|
|
|
90
87
|
const globalConfig = await readConfigFile(resolveGlobalConfigPath());
|
|
91
88
|
return globalConfig.autoCommit ?? false;
|
|
92
89
|
};
|
|
93
|
-
|
|
94
|
-
export const resolveGithubToken = async (workspaceRoot: string): Promise<string | null> => {
|
|
95
|
-
if (process.env.FLOWCAT_GITHUB_TOKEN) {
|
|
96
|
-
return process.env.FLOWCAT_GITHUB_TOKEN;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (process.env.IL_GITHUB_TOKEN) {
|
|
100
|
-
return process.env.IL_GITHUB_TOKEN;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
|
|
104
|
-
if (workspaceConfig.github?.token) {
|
|
105
|
-
return workspaceConfig.github.token;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const globalConfig = await readConfigFile(resolveGlobalConfigPath());
|
|
109
|
-
return globalConfig.github?.token ?? null;
|
|
110
|
-
};
|
package/core/constants.ts
CHANGED
|
@@ -4,5 +4,6 @@ export const LOCK_DIR = ".lock";
|
|
|
4
4
|
export const LOCK_FILE = "store.lock";
|
|
5
5
|
export const TASKS_DIR = "tasks";
|
|
6
6
|
export const PLUGINS_DIR = "plugins";
|
|
7
|
+
export const ENV_FILE = ".env";
|
|
7
8
|
|
|
8
9
|
export const STATUS_ORDER = ["backlog", "active", "paused", "completed", "cancelled"] as const;
|
package/core/gitignore.ts
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { APP_DIR } from "./constants";
|
|
5
5
|
|
|
6
|
-
const ignoredEntries = [`${APP_DIR}/.lock
|
|
6
|
+
const ignoredEntries = [`${APP_DIR}/.lock/`];
|
|
7
7
|
|
|
8
8
|
export const ensureWorkspaceIgnored = async (repoRoot: string): Promise<boolean> => {
|
|
9
9
|
const gitignorePath = path.join(repoRoot, ".gitignore");
|
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 = {
|
package/core/workspace.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { access, mkdir } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { configFileExists, resolveGlobalConfigPath, resolveWorkspaceConfigPath } from "./config";
|
|
6
|
-
import { APP_DIR, APP_NAME, LOCK_DIR, PLUGINS_DIR, STATUS_ORDER, TASKS_DIR } from "./constants";
|
|
6
|
+
import { APP_DIR, APP_NAME, ENV_FILE, LOCK_DIR, PLUGINS_DIR, STATUS_ORDER, TASKS_DIR } from "./constants";
|
|
7
7
|
import { writeJsonAtomic } from "./json";
|
|
8
8
|
|
|
9
9
|
export type WorkspaceKind = "explicit" | "global" | "repo";
|
|
@@ -106,22 +106,24 @@ export const ensureWorkspaceLayout = async (workspaceRoot: string): Promise<void
|
|
|
106
106
|
),
|
|
107
107
|
);
|
|
108
108
|
|
|
109
|
-
// Create
|
|
110
|
-
await
|
|
109
|
+
// Create package.json for TypeScript plugin development
|
|
110
|
+
await ensureWorkspacePackageJson(workspaceRoot);
|
|
111
|
+
// Create .gitignore for workspace-local files
|
|
112
|
+
await ensureWorkspaceGitignore(workspaceRoot);
|
|
111
113
|
};
|
|
112
114
|
|
|
113
115
|
/**
|
|
114
|
-
* Ensure
|
|
116
|
+
* Ensure workspace has a package.json for TypeScript plugin development
|
|
115
117
|
*/
|
|
116
|
-
export const
|
|
117
|
-
const packageJsonPath = path.join(workspaceRoot,
|
|
118
|
+
export const ensureWorkspacePackageJson = async (workspaceRoot: string): Promise<void> => {
|
|
119
|
+
const packageJsonPath = path.join(workspaceRoot, "package.json");
|
|
118
120
|
|
|
119
121
|
if (await exists(packageJsonPath)) {
|
|
120
122
|
return;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
const packageJson = {
|
|
124
|
-
name: "flowcat-
|
|
126
|
+
name: "flowcat-workspace",
|
|
125
127
|
type: "module",
|
|
126
128
|
private: true,
|
|
127
129
|
dependencies: {
|
|
@@ -131,3 +133,21 @@ export const ensurePluginsPackageJson = async (workspaceRoot: string): Promise<v
|
|
|
131
133
|
|
|
132
134
|
await writeJsonAtomic(packageJsonPath, packageJson);
|
|
133
135
|
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Ensure workspace has a .gitignore for local files that shouldn't be committed
|
|
139
|
+
*/
|
|
140
|
+
export const ensureWorkspaceGitignore = async (workspaceRoot: string): Promise<void> => {
|
|
141
|
+
const gitignorePath = path.join(workspaceRoot, ".gitignore");
|
|
142
|
+
|
|
143
|
+
if (await exists(gitignorePath)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const gitignoreContent = `# Flowcat workspace local files
|
|
148
|
+
${ENV_FILE}
|
|
149
|
+
${LOCK_DIR}/
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
await writeFile(gitignorePath, gitignoreContent, "utf8");
|
|
153
|
+
};
|