@gh-symphony/cli 0.0.21 → 0.1.2
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 +100 -69
- package/dist/chunk-6I753NYO.js +18 -0
- package/dist/{workflow-BLJH2HC3.js → chunk-B4ZJMAZL.js} +27 -19
- package/dist/{chunk-SXGT7LOF.js → chunk-DLZAJXZL.js} +600 -12
- package/dist/chunk-GHVDABFO.js +235 -0
- package/dist/{chunk-QEONJ5DZ.js → chunk-GPRCOJDJ.js} +1314 -35
- package/dist/{chunk-A67CMOYE.js → chunk-VFHMHHZW.js} +1 -1
- package/dist/{chunk-JN3TQVFV.js → chunk-WM2B6BJ7.js} +16 -62
- package/dist/{chunk-ROGRTUFI.js → chunk-WOVNN5NW.js} +16 -6
- package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
- package/dist/{config-cmd-DNXNL26Z.js → config-cmd-2ADPUYWA.js} +1 -1
- package/dist/{doctor-4HBRICHP.js → doctor-EEPNFCGF.js} +464 -40
- package/dist/index.js +357 -244
- package/dist/repo-RX4OK7XH.js +6783 -0
- package/dist/{setup-B2SVLW2R.js → setup-XNHHRBGU.js} +57 -91
- package/dist/{upgrade-OJXPZRYE.js → upgrade-NS53EO2B.js} +2 -2
- package/dist/{version-TBDCTKDO.js → version-2RHFZ5CI.js} +1 -1
- package/dist/worker-entry.js +376 -15
- package/dist/workflow-26QNZZWH.js +22 -0
- package/package.json +5 -5
- package/dist/chunk-5NV3LSAJ.js +0 -11
- package/dist/chunk-C7G7RJ4G.js +0 -146
- package/dist/chunk-KY6WKH66.js +0 -1300
- package/dist/chunk-MYVJ6HK4.js +0 -3510
- package/dist/chunk-S6VIK4FF.js +0 -723
- package/dist/chunk-XN5ABWZ6.js +0 -486
- package/dist/chunk-Y6TYJMNT.js +0 -109
- package/dist/init-HZ3JEDGQ.js +0 -38
- package/dist/logs-6JKKYDGJ.js +0 -188
- package/dist/project-25NQ4J4Y.js +0 -24
- package/dist/recover-L3MJHHDA.js +0 -133
- package/dist/repo-TDCWQR6P.js +0 -379
- package/dist/run-XJQ6BF7U.js +0 -110
- package/dist/start-I2CC7BLW.js +0 -18
- package/dist/status-QSCFVGRQ.js +0 -11
- package/dist/stop-7MFCBQVW.js +0 -9
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveRepoRuntimeRoot
|
|
4
|
+
} from "./chunk-6I753NYO.js";
|
|
5
|
+
import {
|
|
6
|
+
parseWorkflowMarkdown
|
|
7
|
+
} from "./chunk-GPRCOJDJ.js";
|
|
8
|
+
import {
|
|
9
|
+
saveGlobalConfig,
|
|
10
|
+
saveProjectConfig
|
|
11
|
+
} from "./chunk-WOVNN5NW.js";
|
|
12
|
+
|
|
13
|
+
// src/repo-runtime.ts
|
|
14
|
+
import { execFileSync } from "child_process";
|
|
15
|
+
import {
|
|
16
|
+
mkdir,
|
|
17
|
+
readdir,
|
|
18
|
+
readFile,
|
|
19
|
+
rename,
|
|
20
|
+
rm,
|
|
21
|
+
stat,
|
|
22
|
+
writeFile
|
|
23
|
+
} from "fs/promises";
|
|
24
|
+
import { basename, dirname, join, resolve } from "path";
|
|
25
|
+
var INTERNAL_PROJECT_ID = "repository";
|
|
26
|
+
var RepoRuntimeMigrationError = class extends Error {
|
|
27
|
+
};
|
|
28
|
+
function parseRepoRuntimeFlags(args) {
|
|
29
|
+
const flags = { repoDir: process.cwd() };
|
|
30
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
31
|
+
const arg = args[i];
|
|
32
|
+
const value = args[i + 1];
|
|
33
|
+
if (arg === "--repo-dir") {
|
|
34
|
+
if (!value || value.startsWith("-")) {
|
|
35
|
+
throw new Error("Option '--repo-dir' argument missing");
|
|
36
|
+
}
|
|
37
|
+
flags.repoDir = resolve(value);
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (arg === "--workflow-file") {
|
|
42
|
+
if (!value || value.startsWith("-")) {
|
|
43
|
+
throw new Error("Option '--workflow-file' argument missing");
|
|
44
|
+
}
|
|
45
|
+
flags.workflowFile = value;
|
|
46
|
+
i += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg?.startsWith("-")) {
|
|
50
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return flags;
|
|
54
|
+
}
|
|
55
|
+
async function initRepoRuntime(flags) {
|
|
56
|
+
const repoDir = resolve(flags.repoDir);
|
|
57
|
+
const runtimeRoot = resolveRepoRuntimeRoot(repoDir);
|
|
58
|
+
await migrateLegacyRuntime(runtimeRoot);
|
|
59
|
+
const workflowPath = resolve(repoDir, flags.workflowFile ?? "WORKFLOW.md");
|
|
60
|
+
const workflow = parseWorkflowMarkdown(await readFile(workflowPath, "utf8"));
|
|
61
|
+
const repository = resolveRepository(repoDir);
|
|
62
|
+
const trackerAdapter = workflow.tracker.kind ?? "github-project";
|
|
63
|
+
const trackerBindingId = workflow.tracker.projectId ?? workflow.tracker.projectSlug ?? "";
|
|
64
|
+
const trackerSettings = {
|
|
65
|
+
...workflow.tracker.projectId ? { projectId: workflow.tracker.projectId } : {},
|
|
66
|
+
repository: `${repository.owner}/${repository.name}`
|
|
67
|
+
};
|
|
68
|
+
if (flags.assignedOnly) {
|
|
69
|
+
trackerSettings.assignedOnly = true;
|
|
70
|
+
}
|
|
71
|
+
if (trackerAdapter === "file") {
|
|
72
|
+
if (!process.env.GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"File tracker repo init requires GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH to point to the issues fixture."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
trackerSettings.issuesPath = process.env.GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH;
|
|
78
|
+
}
|
|
79
|
+
const projectConfig = {
|
|
80
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
81
|
+
slug: basename(repoDir) || INTERNAL_PROJECT_ID,
|
|
82
|
+
displayName: `${repository.owner}/${repository.name}`,
|
|
83
|
+
workspaceDir: repoDir,
|
|
84
|
+
repository,
|
|
85
|
+
tracker: {
|
|
86
|
+
adapter: trackerAdapter,
|
|
87
|
+
bindingId: trackerBindingId,
|
|
88
|
+
settings: trackerSettings
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
92
|
+
await saveProjectConfig(runtimeRoot, INTERNAL_PROJECT_ID, projectConfig);
|
|
93
|
+
await saveGlobalConfig(runtimeRoot, {
|
|
94
|
+
activeProject: INTERNAL_PROJECT_ID,
|
|
95
|
+
projects: [INTERNAL_PROJECT_ID]
|
|
96
|
+
});
|
|
97
|
+
const orchestratorConfig = {
|
|
98
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
99
|
+
slug: projectConfig.slug,
|
|
100
|
+
workspaceDir: repoDir,
|
|
101
|
+
repository,
|
|
102
|
+
tracker: projectConfig.tracker
|
|
103
|
+
};
|
|
104
|
+
await writeJsonFile(join(runtimeRoot, "project.json"), orchestratorConfig);
|
|
105
|
+
return {
|
|
106
|
+
configDir: runtimeRoot,
|
|
107
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
108
|
+
workflowPath,
|
|
109
|
+
repository
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function migrateLegacyRuntime(runtimeRoot) {
|
|
113
|
+
const projectsDir = join(runtimeRoot, "projects");
|
|
114
|
+
const projectIds = await readDirectoryNames(projectsDir);
|
|
115
|
+
if (projectIds.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (projectIds.length === 1 && projectIds[0] === INTERNAL_PROJECT_ID && await pathExists(join(runtimeRoot, "project.json"))) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (projectIds.length > 1) {
|
|
122
|
+
throw new RepoRuntimeMigrationError(
|
|
123
|
+
[
|
|
124
|
+
"Multiple legacy project runtime directories were found under .runtime/orchestrator/projects.",
|
|
125
|
+
`Found: ${projectIds.join(", ")}`,
|
|
126
|
+
"Automatic migration is only supported when exactly one project directory exists.",
|
|
127
|
+
"Manually keep the project directory you want to promote, archive or remove the others, then re-run 'gh-symphony repo init'."
|
|
128
|
+
].join("\n")
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const sourceDir = join(projectsDir, projectIds[0]);
|
|
132
|
+
const entries = await readdir(sourceDir);
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const target = join(runtimeRoot, entry);
|
|
135
|
+
if (await pathExists(target)) {
|
|
136
|
+
throw new RepoRuntimeMigrationError(
|
|
137
|
+
`Cannot promote legacy runtime data because '${entry}' already exists in .runtime/orchestrator. Move or remove it, then re-run 'gh-symphony repo init'.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
await rename(join(sourceDir, entry), target);
|
|
141
|
+
}
|
|
142
|
+
await stripProjectIdFromRunRecords(join(runtimeRoot, "runs"));
|
|
143
|
+
await rm(projectsDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
async function stripProjectIdFromRunRecords(runsDir) {
|
|
146
|
+
for (const runId of await readDirectoryNames(runsDir)) {
|
|
147
|
+
const runPath = join(runsDir, runId, "run.json");
|
|
148
|
+
const run = await readJsonFile(runPath);
|
|
149
|
+
if (!run || !("projectId" in run)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
delete run.projectId;
|
|
153
|
+
await writeJsonFile(runPath, run);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function readDirectoryNames(path) {
|
|
157
|
+
try {
|
|
158
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
159
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (isMissing(error)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function resolveRepository(repoDir) {
|
|
168
|
+
const remote = readGitOrigin(repoDir);
|
|
169
|
+
const cleanedRemote = remote.replace(/\.git$/, "");
|
|
170
|
+
const match = cleanedRemote.match(/github\.com[:/]([^/]+)\/([^/]+)$/) ?? cleanedRemote.match(/^([^/]+)\/([^/]+)$/);
|
|
171
|
+
if (!match) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Unable to infer GitHub repository from git remote 'origin'. Run from a cloned GitHub repository or set origin to owner/name."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
owner: match[1],
|
|
178
|
+
name: match[2],
|
|
179
|
+
cloneUrl: remote.startsWith("http") ? remote : `https://github.com/${match[1]}/${match[2]}.git`,
|
|
180
|
+
path: repoDir
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function readGitOrigin(repoDir) {
|
|
184
|
+
try {
|
|
185
|
+
return execFileSync(
|
|
186
|
+
"git",
|
|
187
|
+
["-C", repoDir, "config", "--get", "remote.origin.url"],
|
|
188
|
+
{
|
|
189
|
+
encoding: "utf8",
|
|
190
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
191
|
+
}
|
|
192
|
+
).trim();
|
|
193
|
+
} catch {
|
|
194
|
+
throw new Error(
|
|
195
|
+
"Unable to read git remote 'origin'. Run 'gh-symphony repo init' inside a cloned repository."
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function readJsonFile(path) {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (isMissing(error)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function writeJsonFile(path, value) {
|
|
210
|
+
await mkdir(dirname(path), { recursive: true });
|
|
211
|
+
const temporaryPath = `${path}.tmp`;
|
|
212
|
+
await writeFile(temporaryPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
213
|
+
await rename(temporaryPath, path);
|
|
214
|
+
}
|
|
215
|
+
async function pathExists(path) {
|
|
216
|
+
try {
|
|
217
|
+
await stat(path);
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (isMissing(error)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isMissing(error) {
|
|
227
|
+
return Boolean(
|
|
228
|
+
error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export {
|
|
233
|
+
parseRepoRuntimeFlags,
|
|
234
|
+
initRepoRuntime
|
|
235
|
+
};
|