@brainervirus/workit-core 0.6.1 → 0.7.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/package.json +3 -7
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +19 -21
- package/scripts/rewrite-workspace-deps.ts +15 -9
- package/scripts/sync-runtime.sh +71 -19
- package/scripts/vendor-assets.ts +37 -0
- package/skills/wk-implement/SKILL.md +2 -2
- package/skills/wk-pr/SKILL.md +1 -1
- package/src/core/boundary.ts +27 -0
- package/src/core/branch-policy.ts +63 -0
- package/src/core/branch.ts +30 -16
- package/src/core/config.ts +193 -31
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +11 -9
- package/src/core/docs-validate.ts +18 -6
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -141
- package/src/core/git.ts +22 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +5 -57
- package/src/core/hygiene.ts +26 -12
- package/src/core/init.ts +43 -11
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/ports/init-toolkit-status.ts +1 -1
- package/src/core/ports/vcs-verify-token.ts +1 -1
- package/src/core/ports/youtrack-api.ts +1 -1
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +116 -21
- package/src/core/registration.ts +215 -0
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tools.ts +23 -0
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -44
- package/src/core/sdd.ts +45 -28
- package/src/core/setup-state.ts +54 -0
- package/src/core/setup.ts +1216 -0
- package/src/core/skill-manifests.ts +95 -0
- package/src/core/support-matrix.ts +12 -0
- package/src/core/sync-runtime.ts +348 -0
- package/src/core/templates.ts +2 -2
- package/src/core/vcs-config.ts +107 -37
- package/src/core/verify-project.ts +181 -0
- package/src/core/workspaces.ts +136 -17
- package/src/core/youtrack-tools.ts +228 -0
- package/src/core/youtrack.ts +125 -67
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +4 -3
- package/scripts/_shared/common.sh +0 -173
- package/scripts/changelog-context.sh +0 -42
- package/scripts/docs-refresh-context.sh +0 -40
- package/scripts/init/apply.sh +0 -5
- package/scripts/init/status.sh +0 -5
- package/scripts/init/toolkit-status.sh +0 -5
- package/scripts/pr-create.sh +0 -5
- package/scripts/pr-ready-context.sh +0 -88
- package/scripts/present/ascii-wireframe.sh +0 -5
- package/scripts/present/flow-diagram.sh +0 -5
- package/scripts/release-notes-context.sh +0 -40
- package/scripts/vcs/config.sh +0 -5
- package/scripts/vcs/merged-style.sh +0 -5
- package/scripts/vcs/token-create-urls.sh +0 -5
- package/scripts/vcs/verify-token.sh +0 -5
- package/scripts/verify-project.sh +0 -140
- package/scripts/youtrack/api.sh +0 -5
- package/scripts/youtrack/config.sh +0 -5
- package/scripts/youtrack/greeting.sh +0 -5
- package/scripts/youtrack/parse-duration.sh +0 -5
- package/scripts/youtrack/token-create-url.sh +0 -5
- package/scripts/youtrack/verify-token.sh +0 -5
- package/scripts/youtrack/work-date-ms.sh +0 -5
- package/src/tools/docs-repo.ts +0 -51
- package/src/tools/flow.ts +0 -99
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -49
- package/src/tools/repo.ts +0 -490
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -216
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -423
package/src/core/git.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
|
|
3
3
|
const run = (cwd: string, args: string[]): { stdout: string; stderr: string; exitCode: number } => {
|
|
4
4
|
try {
|
|
5
|
-
const stdout = execFileSync("git", args, {
|
|
5
|
+
const stdout = execFileSync("git", args, {
|
|
6
|
+
cwd,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
// AR-14: expected negative fixtures (non-repo dirs, unborn HEADs, bogus
|
|
9
|
+
// shas) must not inherit raw git usage/fatal stderr into the suite output.
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
+
});
|
|
6
12
|
return { stdout: stdout.trimEnd(), stderr: "", exitCode: 0 };
|
|
7
13
|
} catch (error) {
|
|
8
14
|
const e = error as { stdout?: string; stderr?: string; status?: number };
|
|
@@ -16,8 +22,18 @@ const run = (cwd: string, args: string[]): { stdout: string; stderr: string; exi
|
|
|
16
22
|
|
|
17
23
|
export const gitContext = (workspaceRoot: string, paths: string[] = []) => {
|
|
18
24
|
const cwd = workspaceRoot;
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
let failure: { stderr: string; exitCode: number } | undefined;
|
|
26
|
+
|
|
27
|
+
const runHere = (args: string[]) => {
|
|
28
|
+
const result = run(cwd, args);
|
|
29
|
+
if (!failure && result.exitCode !== 0) {
|
|
30
|
+
failure = { stderr: result.stderr, exitCode: result.exitCode };
|
|
31
|
+
}
|
|
32
|
+
return result.stdout;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const branch = runHere(["rev-parse", "--abbrev-ref", "HEAD"]) || "unknown";
|
|
36
|
+
const status_short = runHere(["status", "--porcelain"]);
|
|
21
37
|
|
|
22
38
|
const staged: string[] = [];
|
|
23
39
|
const unstaged: string[] = [];
|
|
@@ -35,8 +51,8 @@ export const gitContext = (workspaceRoot: string, paths: string[] = []) => {
|
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
const pathArgs = paths.length ? ["--", ...paths] : [];
|
|
38
|
-
const diff_stat =
|
|
39
|
-
const cached_stat =
|
|
54
|
+
const diff_stat = runHere(["diff", "--stat", ...pathArgs]);
|
|
55
|
+
const cached_stat = runHere(["diff", "--cached", "--stat", ...pathArgs]);
|
|
40
56
|
|
|
41
57
|
const stagedSet = new Set(staged);
|
|
42
58
|
const unstagedSet = new Set(unstaged);
|
|
@@ -52,5 +68,6 @@ export const gitContext = (workspaceRoot: string, paths: string[] = []) => {
|
|
|
52
68
|
diff_stat: [diff_stat, cached_stat].filter(Boolean).join("\n"),
|
|
53
69
|
partial_staged: partial_staged.length > 0,
|
|
54
70
|
partial_staged_files: partial_staged,
|
|
71
|
+
...(failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}),
|
|
55
72
|
};
|
|
56
73
|
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { tool, type PluginInput } from "@opencode-ai/plugin";
|
|
4
2
|
import { fail, ok, type Result } from "../core";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { WorkflowStateStore } from "../state";
|
|
3
|
+
import { resolveWorkflowPaths, buildHandoffContract } from "./handoff-context";
|
|
4
|
+
import { assetRoot } from "./package-root";
|
|
8
5
|
|
|
9
6
|
type ApiResponse<T> = { data?: T; error?: unknown };
|
|
10
7
|
type ApiResult<T> = Promise<ApiResponse<T>>;
|
|
@@ -31,20 +28,6 @@ export type HandoffClient = {
|
|
|
31
28
|
};
|
|
32
29
|
};
|
|
33
30
|
|
|
34
|
-
export const adaptPluginHandoffClient = (client: PluginInput["client"]): HandoffClient => ({
|
|
35
|
-
session: {
|
|
36
|
-
create: (input) => client.session.create(input),
|
|
37
|
-
promptAsync: (input) => client.session.promptAsync(input),
|
|
38
|
-
},
|
|
39
|
-
tui: {
|
|
40
|
-
selectSession: ({ body, query }) =>
|
|
41
|
-
client.tui.publish({
|
|
42
|
-
body: { type: "tui.session.select", properties: { sessionID: body.sessionID } } as never,
|
|
43
|
-
query,
|
|
44
|
-
}),
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
31
|
export type HandoffRequest = {
|
|
49
32
|
directory: string;
|
|
50
33
|
title: string;
|
|
@@ -59,13 +42,13 @@ type HandoffData = {
|
|
|
59
42
|
stage?: "create" | "seed" | "select";
|
|
60
43
|
};
|
|
61
44
|
|
|
62
|
-
const message = (error: unknown) =>
|
|
45
|
+
export const message = (error: unknown) =>
|
|
63
46
|
error instanceof Error
|
|
64
47
|
? error.message
|
|
65
48
|
: typeof error === "object" && error !== null && "message" in error
|
|
66
49
|
? String(error.message)
|
|
67
50
|
: String(error);
|
|
68
|
-
const apiError = (response: ApiResponse<unknown> | void) => response?.error;
|
|
51
|
+
export const apiError = (response: ApiResponse<unknown> | void) => response?.error;
|
|
69
52
|
|
|
70
53
|
export async function handoffSession(
|
|
71
54
|
client: HandoffClient,
|
|
@@ -110,8 +93,6 @@ export async function handoffSession(
|
|
|
110
93
|
}
|
|
111
94
|
}
|
|
112
95
|
|
|
113
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
114
|
-
|
|
115
96
|
export type HandoffContextResult =
|
|
116
97
|
| { prompt: string; spec: string; plan: string; sdd: string }
|
|
117
98
|
| { error: string };
|
|
@@ -119,10 +100,7 @@ export type HandoffContextResult =
|
|
|
119
100
|
export const buildHandoffPrompt = (root: string, message: string): HandoffContextResult => {
|
|
120
101
|
const resolved = resolveWorkflowPaths(root, message);
|
|
121
102
|
if ("error" in resolved) return { error: resolved.error };
|
|
122
|
-
const templatePath = path.
|
|
123
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
124
|
-
"../../templates/execution-contract.md",
|
|
125
|
-
);
|
|
103
|
+
const templatePath = path.join(assetRoot(), "templates", "execution-contract.md");
|
|
126
104
|
const contract = buildHandoffContract({
|
|
127
105
|
root,
|
|
128
106
|
spec: resolved.spec,
|
|
@@ -133,33 +111,3 @@ export const buildHandoffPrompt = (root: string, message: string): HandoffContex
|
|
|
133
111
|
const sdd = `docs/${path.basename(path.dirname(resolved.plan))}/sdd`;
|
|
134
112
|
return { prompt: contract.prompt, spec: resolved.spec, plan: resolved.plan, sdd };
|
|
135
113
|
};
|
|
136
|
-
|
|
137
|
-
export function createHandoffTools(client: HandoffClient, state: WorkflowStateStore) {
|
|
138
|
-
return {
|
|
139
|
-
workflow_handoff_session: tool({
|
|
140
|
-
description:
|
|
141
|
-
"Create, seed, and select a continuation session; --stay in the message skips selection",
|
|
142
|
-
args: { message: tool.schema.string() },
|
|
143
|
-
execute: async ({ message: userMessage }, context) => {
|
|
144
|
-
const built = buildHandoffPrompt(context.directory, userMessage);
|
|
145
|
-
if ("error" in built) return output(fail(built.error));
|
|
146
|
-
const active = built;
|
|
147
|
-
try {
|
|
148
|
-
const gate = assertFlowGates(context.directory, active.plan);
|
|
149
|
-
if (!gate.ok) return output(fail(gate.error));
|
|
150
|
-
state.set(context.sessionID, { spec: active.spec, plan: active.plan, sdd: active.sdd });
|
|
151
|
-
return output(
|
|
152
|
-
await handoffSession(client, {
|
|
153
|
-
directory: context.directory,
|
|
154
|
-
title: `Continue ${path.basename(path.dirname(active.plan))}`,
|
|
155
|
-
prompt: active.prompt,
|
|
156
|
-
stay: /(?:^|\s)--stay(?:\s|$)/.test(userMessage),
|
|
157
|
-
}),
|
|
158
|
-
);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
return output(fail(message(error)));
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
}),
|
|
164
|
-
};
|
|
165
|
-
}
|
package/src/core/hygiene.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { changelogUnreleasedStats } from "./changelog";
|
|
4
|
+
import { assetRoot } from "./package-root";
|
|
5
5
|
|
|
6
6
|
export type HygieneFile =
|
|
7
7
|
| "CHANGELOG.md"
|
|
@@ -12,7 +12,7 @@ export type HygieneFile =
|
|
|
12
12
|
| "CONTRIBUTING.md";
|
|
13
13
|
type State = "missing" | "invalid" | "ok" | "skip";
|
|
14
14
|
|
|
15
|
-
const repoRoot =
|
|
15
|
+
const repoRoot = assetRoot();
|
|
16
16
|
const templatesDir = () => path.join(repoRoot, "templates", "hygiene");
|
|
17
17
|
|
|
18
18
|
const packageJson = (root: string): Record<string, unknown> | null => {
|
|
@@ -75,25 +75,39 @@ export const hygieneFiles = (
|
|
|
75
75
|
return { state, openSource };
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// Pure planner: which hygiene files would be created and with what content,
|
|
79
|
+
// without touching the filesystem. Drives the wizard preview (WZ-08) and the
|
|
80
|
+
// apply path; ensureHygieneFiles is just "plan then write".
|
|
81
|
+
export const planHygieneFiles = (
|
|
79
82
|
root: string,
|
|
80
|
-
opts: {
|
|
81
|
-
): {
|
|
82
|
-
|
|
83
|
+
opts: { includeOpenSource?: boolean } = {},
|
|
84
|
+
): Array<{ path: string; content: string }> => {
|
|
85
|
+
const openSource = opts.includeOpenSource ?? isOpenSource(root);
|
|
83
86
|
const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"] as HygieneFile[];
|
|
84
|
-
if (
|
|
85
|
-
const
|
|
86
|
-
const tplDir = templatesDir();
|
|
87
|
+
if (openSource) files.push("LICENSE", "CONTRIBUTING.md");
|
|
88
|
+
const planned: Array<{ path: string; content: string }> = [];
|
|
87
89
|
for (const file of files) {
|
|
88
90
|
if (existsSync(path.join(root, file))) continue;
|
|
89
|
-
const tpl = path.join(
|
|
91
|
+
const tpl = path.join(templatesDir(), file);
|
|
90
92
|
if (!existsSync(tpl)) continue; // skip missing template, never fail
|
|
91
93
|
const content = readFileSync(tpl, "utf8")
|
|
92
94
|
.replace(/<PROJECT>/g, path.basename(root))
|
|
93
95
|
.replace(/<YEAR>/g, String(new Date().getFullYear()))
|
|
94
96
|
.replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
planned.push({ path: path.join(root, file), content });
|
|
98
|
+
}
|
|
99
|
+
return planned;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const ensureHygieneFiles = (
|
|
103
|
+
root: string,
|
|
104
|
+
opts: { confirmed: boolean; includeOpenSource?: boolean },
|
|
105
|
+
): { ok: true; created: string[] } | { ok: false; error: string } => {
|
|
106
|
+
if (!opts.confirmed) return { ok: false, error: "confirmed: true required" };
|
|
107
|
+
const created: string[] = [];
|
|
108
|
+
for (const planned of planHygieneFiles(root, opts)) {
|
|
109
|
+
writeFileSync(planned.path, planned.content, "utf8");
|
|
110
|
+
created.push(path.basename(planned.path));
|
|
97
111
|
}
|
|
98
112
|
return { ok: true, created };
|
|
99
113
|
};
|
package/src/core/init.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { configDir } from "./config";
|
|
3
|
+
import { configDir, isConfigObject } from "./config";
|
|
4
4
|
import { PLUGIN_ROOT } from "./scripts";
|
|
5
|
+
import { writeFileExclusive } from "./safe-write";
|
|
5
6
|
import { resolveWorkspace, workspacesPath } from "./workspaces";
|
|
7
|
+
import { applyWorkspaceBranchPolicy } from "./setup";
|
|
6
8
|
import { vcsTokenCreateUrls, vcsVerifyToken } from "./vcs-config";
|
|
7
9
|
import { youTrackTokenCreateUrl, youTrackVerifyToken } from "./youtrack";
|
|
8
10
|
|
|
9
11
|
const TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
10
12
|
|
|
13
|
+
// AR-07/CA-37: a parseable non-object (null, scalar, array) is not a config
|
|
14
|
+
// file — never display it as configured (fail-open) nor as unconfigured.
|
|
11
15
|
const readJson = (p: string): Record<string, any> | null => {
|
|
12
16
|
try {
|
|
13
|
-
|
|
17
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
18
|
+
return isConfigObject(parsed) ? (parsed as Record<string, any>) : null;
|
|
14
19
|
} catch {
|
|
15
20
|
return null;
|
|
16
21
|
}
|
|
@@ -97,7 +102,18 @@ export function initStatusData(configDirPath = configDir()): Record<string, any>
|
|
|
97
102
|
youtrackConfig.tokenCreate = youtrackTokenCreate;
|
|
98
103
|
}
|
|
99
104
|
} else if (fs.existsSync(ytJson)) {
|
|
100
|
-
|
|
105
|
+
// Distinguish parse failure (legacy message, path in config_edit_path) from a
|
|
106
|
+
// parseable non-object, which gets the shared shape diagnostic with the path.
|
|
107
|
+
let parseFailed = false;
|
|
108
|
+
try {
|
|
109
|
+
JSON.parse(fs.readFileSync(ytJson, "utf8"));
|
|
110
|
+
} catch {
|
|
111
|
+
parseFailed = true;
|
|
112
|
+
}
|
|
113
|
+
youtrackConfig = {
|
|
114
|
+
config_edit_path: resolvePath(ytJson),
|
|
115
|
+
error: parseFailed ? "invalid youtrack.json" : `${resolvePath(ytJson)} is not a JSON object`,
|
|
116
|
+
};
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
items.push({
|
|
@@ -239,7 +255,7 @@ export function initStatusData(configDirPath = configDir()): Record<string, any>
|
|
|
239
255
|
}
|
|
240
256
|
|
|
241
257
|
/** Port of scripts/init/toolkit-status.sh — filesystem + API health check. */
|
|
242
|
-
export function toolkitStatusData(configDirPath = configDir()): Record<string, any
|
|
258
|
+
export async function toolkitStatusData(configDirPath = configDir()): Promise<Record<string, any>> {
|
|
243
259
|
const status = initStatusData(configDirPath);
|
|
244
260
|
const tokenItem = status.items.find((i: Record<string, any>) => i.id === "youtrack_token") ?? {};
|
|
245
261
|
const placeholder = Boolean(tokenItem.placeholder);
|
|
@@ -253,13 +269,14 @@ export function toolkitStatusData(configDirPath = configDir()): Record<string, a
|
|
|
253
269
|
|
|
254
270
|
const verify = placeholder
|
|
255
271
|
? { ok: false, error: "token still placeholder YOUR_TOKEN_HERE" }
|
|
256
|
-
: youTrackVerifyToken();
|
|
272
|
+
: await youTrackVerifyToken();
|
|
257
273
|
const vcsVerify = vcsPlaceholder
|
|
258
274
|
? { ok: false, error: "vcs token still placeholder YOUR_TOKEN_HERE" }
|
|
259
|
-
: vcsVerifyToken();
|
|
275
|
+
: await vcsVerifyToken();
|
|
276
|
+
const youTrackHealth = ("data" in verify ? verify.data : verify) as Record<string, any>;
|
|
260
277
|
|
|
261
278
|
status.youtrack_verify = verify;
|
|
262
|
-
status.youtrack_ok = placeholder ? false : Boolean(
|
|
279
|
+
status.youtrack_ok = placeholder ? false : Boolean(youTrackHealth.ok);
|
|
263
280
|
status.vcs_verify = vcsVerify;
|
|
264
281
|
status.vcs_ok =
|
|
265
282
|
vcsJsonOk && !vcsPlaceholder ? Boolean((vcsVerify as Record<string, any>).ok) : false;
|
|
@@ -361,7 +378,9 @@ export function initApplyData(
|
|
|
361
378
|
}
|
|
362
379
|
case "youtrack_token_placeholder": {
|
|
363
380
|
const p = path.join(dir, "youtrack.token");
|
|
364
|
-
|
|
381
|
+
// wx + EEXIST-as-preserved (CA-13): an existing real token is never
|
|
382
|
+
// clobbered — shared with the CLI wizard's ensureToken via safe-write.
|
|
383
|
+
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER + "\n", 0o600) === "preserved";
|
|
365
384
|
const abs = path.resolve(p);
|
|
366
385
|
return {
|
|
367
386
|
action,
|
|
@@ -369,6 +388,7 @@ export function initApplyData(
|
|
|
369
388
|
path: abs,
|
|
370
389
|
token_edit_path: abs,
|
|
371
390
|
placeholder: TOKEN_PLACEHOLDER,
|
|
391
|
+
preserved,
|
|
372
392
|
instruction: `Open ${abs} in your editor, replace YOUR_TOKEN_HERE with your YouTrack permanent token, save, then run /wk-status`,
|
|
373
393
|
};
|
|
374
394
|
}
|
|
@@ -376,7 +396,8 @@ export function initApplyData(
|
|
|
376
396
|
const jsonOut = path.join(dir, "youtrack.json");
|
|
377
397
|
const tokenOut = path.join(dir, "youtrack.token");
|
|
378
398
|
fs.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + "\n", "utf8");
|
|
379
|
-
|
|
399
|
+
const preserved =
|
|
400
|
+
writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER + "\n", 0o600) === "preserved";
|
|
380
401
|
const configPath = path.resolve(jsonOut);
|
|
381
402
|
const tokenPath = path.resolve(tokenOut);
|
|
382
403
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
@@ -397,6 +418,7 @@ export function initApplyData(
|
|
|
397
418
|
token_create: tokenCreate,
|
|
398
419
|
config_edit_path: configPath,
|
|
399
420
|
placeholder: TOKEN_PLACEHOLDER,
|
|
421
|
+
preserved,
|
|
400
422
|
youtrack_config: {
|
|
401
423
|
config_edit_path: configPath,
|
|
402
424
|
baseUrl: base,
|
|
@@ -433,8 +455,11 @@ export function initApplyData(
|
|
|
433
455
|
fs.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(dir), null, 2) + "\n", "utf8");
|
|
434
456
|
const glPath = path.join(dir, "gitlab.token");
|
|
435
457
|
const ghPath = path.join(dir, "github.token");
|
|
458
|
+
const preservedTokens: string[] = [];
|
|
436
459
|
for (const p of [glPath, ghPath]) {
|
|
437
|
-
|
|
460
|
+
if (writeFileExclusive(p, TOKEN_PLACEHOLDER + "\n", 0o600) === "preserved") {
|
|
461
|
+
preservedTokens.push(path.resolve(p));
|
|
462
|
+
}
|
|
438
463
|
}
|
|
439
464
|
const configPath = path.resolve(jsonOut);
|
|
440
465
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
@@ -455,6 +480,7 @@ export function initApplyData(
|
|
|
455
480
|
token_edit_path: activePath,
|
|
456
481
|
token_create_url: active.createUrl,
|
|
457
482
|
token_create_urls: tokenUrls,
|
|
483
|
+
preserved_tokens: preservedTokens,
|
|
458
484
|
vcs_config: {
|
|
459
485
|
config_edit_path: configPath,
|
|
460
486
|
provider,
|
|
@@ -472,6 +498,12 @@ export function initApplyData(
|
|
|
472
498
|
else process.env.WORKFLOW_VCS_CONFIG = prev;
|
|
473
499
|
}
|
|
474
500
|
}
|
|
501
|
+
case "branch_policy": {
|
|
502
|
+
const root = env.WORKFLOW_WORKSPACE_ROOT?.trim()
|
|
503
|
+
? env.WORKFLOW_WORKSPACE_ROOT
|
|
504
|
+
: process.cwd();
|
|
505
|
+
return applyWorkspaceBranchPolicy({ workspace_root: root, env });
|
|
506
|
+
}
|
|
475
507
|
default: {
|
|
476
508
|
return {
|
|
477
509
|
error: `unknown action ${action} (youtrack_scaffold|youtrack_json|youtrack_token_placeholder|vcs_scaffold)`,
|
|
@@ -488,7 +520,7 @@ export function initStatus(): Record<string, any> {
|
|
|
488
520
|
};
|
|
489
521
|
}
|
|
490
522
|
|
|
491
|
-
export function toolkitStatus(): Record<string, any
|
|
523
|
+
export async function toolkitStatus(): Promise<Record<string, any>> {
|
|
492
524
|
return toolkitStatusData();
|
|
493
525
|
}
|
|
494
526
|
|