workflow-agent-cli 1.1.6 → 2.0.1
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/LICENSE +21 -0
- package/dist/{chunk-VFN3BY56.js → chunk-4BIDFDSR.js} +34 -2
- package/dist/chunk-4BIDFDSR.js.map +1 -0
- package/dist/cli/index.js +1357 -132
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{schema-CiJ4W7in.d.ts → schema-RkQ91pZW.d.ts} +64 -0
- package/dist/scripts/postinstall.js +0 -0
- package/dist/validators/index.d.ts +1 -1
- package/package.json +15 -12
- package/dist/chunk-VFN3BY56.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
hasConfig,
|
|
9
9
|
loadConfig,
|
|
10
10
|
validateScopeDefinitions
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-4BIDFDSR.js";
|
|
12
12
|
|
|
13
13
|
// src/cli/index.ts
|
|
14
14
|
import { Command } from "commander";
|
|
@@ -16,9 +16,9 @@ import { Command } from "commander";
|
|
|
16
16
|
// src/cli/commands/init.ts
|
|
17
17
|
import * as p from "@clack/prompts";
|
|
18
18
|
import chalk from "chalk";
|
|
19
|
-
import { existsSync } from "fs";
|
|
20
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
21
|
-
import { join, dirname } from "path";
|
|
19
|
+
import { existsSync as existsSync4 } from "fs";
|
|
20
|
+
import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
21
|
+
import { join as join4, dirname } from "path";
|
|
22
22
|
import { fileURLToPath } from "url";
|
|
23
23
|
|
|
24
24
|
// src/templates/renderer.ts
|
|
@@ -189,18 +189,6 @@ async function renderTemplateFile(templatePath, outputPath, context) {
|
|
|
189
189
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
190
190
|
await fs.writeFile(outputPath, rendered, "utf-8");
|
|
191
191
|
}
|
|
192
|
-
async function renderTemplateDirectory(templateDir, outputDir, context) {
|
|
193
|
-
const files = await fs.readdir(templateDir);
|
|
194
|
-
const rendered = [];
|
|
195
|
-
for (const file of files) {
|
|
196
|
-
if (!file.match(/\.(md|ts|json)$/)) continue;
|
|
197
|
-
const templatePath = path.join(templateDir, file);
|
|
198
|
-
const outputPath = path.join(outputDir, file);
|
|
199
|
-
await renderTemplateFile(templatePath, outputPath, context);
|
|
200
|
-
rendered.push(file);
|
|
201
|
-
}
|
|
202
|
-
return rendered;
|
|
203
|
-
}
|
|
204
192
|
async function getProjectName(projectPath) {
|
|
205
193
|
try {
|
|
206
194
|
const pkgPath = path.join(projectPath, "package.json");
|
|
@@ -229,13 +217,666 @@ async function validateTemplateDirectory(templateDir) {
|
|
|
229
217
|
}
|
|
230
218
|
}
|
|
231
219
|
|
|
220
|
+
// src/templates/metadata.ts
|
|
221
|
+
var templateMetadata = {
|
|
222
|
+
"AGENT_EDITING_INSTRUCTIONS.md": {
|
|
223
|
+
filename: "AGENT_EDITING_INSTRUCTIONS.md",
|
|
224
|
+
displayName: "Agent Editing Instructions",
|
|
225
|
+
mandatory: true,
|
|
226
|
+
category: "workflow",
|
|
227
|
+
validators: ["implementation-plan"],
|
|
228
|
+
description: "Core rules for AI agents: implementation plans, coding standards, architecture"
|
|
229
|
+
},
|
|
230
|
+
"BRANCHING_STRATEGY.md": {
|
|
231
|
+
filename: "BRANCHING_STRATEGY.md",
|
|
232
|
+
displayName: "Branching Strategy",
|
|
233
|
+
mandatory: true,
|
|
234
|
+
category: "workflow",
|
|
235
|
+
validators: ["branch-name", "pr-title"],
|
|
236
|
+
description: "Git branch naming conventions, PR requirements, merge policies"
|
|
237
|
+
},
|
|
238
|
+
"TESTING_STRATEGY.md": {
|
|
239
|
+
filename: "TESTING_STRATEGY.md",
|
|
240
|
+
displayName: "Testing Strategy",
|
|
241
|
+
mandatory: true,
|
|
242
|
+
category: "development",
|
|
243
|
+
validators: ["test-coverage"],
|
|
244
|
+
description: "Testing pyramid, Vitest/Playwright patterns, when tests are required"
|
|
245
|
+
},
|
|
246
|
+
"SELF_IMPROVEMENT_MANDATE.md": {
|
|
247
|
+
filename: "SELF_IMPROVEMENT_MANDATE.md",
|
|
248
|
+
displayName: "Self-Improvement Mandate",
|
|
249
|
+
mandatory: true,
|
|
250
|
+
category: "workflow",
|
|
251
|
+
validators: [],
|
|
252
|
+
description: "Continuous improvement tracking, changelog requirements"
|
|
253
|
+
},
|
|
254
|
+
"SINGLE_SOURCE_OF_TRUTH.md": {
|
|
255
|
+
filename: "SINGLE_SOURCE_OF_TRUTH.md",
|
|
256
|
+
displayName: "Single Source of Truth",
|
|
257
|
+
mandatory: true,
|
|
258
|
+
category: "workflow",
|
|
259
|
+
validators: [],
|
|
260
|
+
description: "Canonical code locations, service patterns, avoiding duplication"
|
|
261
|
+
},
|
|
262
|
+
"COMPONENT_LIBRARY.md": {
|
|
263
|
+
filename: "COMPONENT_LIBRARY.md",
|
|
264
|
+
displayName: "Component Library",
|
|
265
|
+
mandatory: false,
|
|
266
|
+
category: "development",
|
|
267
|
+
validators: [],
|
|
268
|
+
description: "UI component patterns, design tokens, decision tree"
|
|
269
|
+
},
|
|
270
|
+
"DEPLOYMENT_STRATEGY.md": {
|
|
271
|
+
filename: "DEPLOYMENT_STRATEGY.md",
|
|
272
|
+
displayName: "Deployment Strategy",
|
|
273
|
+
mandatory: false,
|
|
274
|
+
category: "development",
|
|
275
|
+
validators: [],
|
|
276
|
+
description: "Deployment workflow, environments, migrations, rollback"
|
|
277
|
+
},
|
|
278
|
+
"LIBRARY_INVENTORY.md": {
|
|
279
|
+
filename: "LIBRARY_INVENTORY.md",
|
|
280
|
+
displayName: "Library Inventory",
|
|
281
|
+
mandatory: false,
|
|
282
|
+
category: "development",
|
|
283
|
+
validators: [],
|
|
284
|
+
description: "Dependency catalog, approved libraries, new library process"
|
|
285
|
+
},
|
|
286
|
+
"SCOPE_CREATION_WORKFLOW.md": {
|
|
287
|
+
filename: "SCOPE_CREATION_WORKFLOW.md",
|
|
288
|
+
displayName: "Scope Creation Workflow",
|
|
289
|
+
mandatory: false,
|
|
290
|
+
category: "workflow",
|
|
291
|
+
validators: [],
|
|
292
|
+
description: "Workflow for AI agents creating custom scopes"
|
|
293
|
+
},
|
|
294
|
+
"CUSTOM_SCOPE_TEMPLATE.md": {
|
|
295
|
+
filename: "CUSTOM_SCOPE_TEMPLATE.md",
|
|
296
|
+
displayName: "Custom Scope Template",
|
|
297
|
+
mandatory: false,
|
|
298
|
+
category: "workflow",
|
|
299
|
+
validators: [],
|
|
300
|
+
description: "Template for defining custom scope packages"
|
|
301
|
+
},
|
|
302
|
+
"PROJECT_TEMPLATE_README.md": {
|
|
303
|
+
filename: "PROJECT_TEMPLATE_README.md",
|
|
304
|
+
displayName: "Project Template README",
|
|
305
|
+
mandatory: false,
|
|
306
|
+
category: "documentation",
|
|
307
|
+
validators: [],
|
|
308
|
+
description: "Meta-document describing project structure"
|
|
309
|
+
},
|
|
310
|
+
"Guidelines.md": {
|
|
311
|
+
filename: "Guidelines.md",
|
|
312
|
+
displayName: "Custom Guidelines",
|
|
313
|
+
mandatory: false,
|
|
314
|
+
category: "documentation",
|
|
315
|
+
validators: [],
|
|
316
|
+
description: "Placeholder for custom user guidelines"
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
function getMandatoryTemplates() {
|
|
320
|
+
return Object.values(templateMetadata).filter((t) => t.mandatory);
|
|
321
|
+
}
|
|
322
|
+
function getOptionalTemplates() {
|
|
323
|
+
return Object.values(templateMetadata).filter((t) => !t.mandatory);
|
|
324
|
+
}
|
|
325
|
+
function getMandatoryTemplateFilenames() {
|
|
326
|
+
return getMandatoryTemplates().map((t) => t.filename);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/utils/hooks.ts
|
|
330
|
+
import { existsSync } from "fs";
|
|
331
|
+
import { readFile, writeFile, unlink, chmod, rename, mkdir } from "fs/promises";
|
|
332
|
+
import { join } from "path";
|
|
333
|
+
function getGitHooksDir(projectPath = process.cwd()) {
|
|
334
|
+
return join(projectPath, ".git", "hooks");
|
|
335
|
+
}
|
|
336
|
+
function hasGitRepo(projectPath = process.cwd()) {
|
|
337
|
+
return existsSync(join(projectPath, ".git"));
|
|
338
|
+
}
|
|
339
|
+
function generatePreCommitHook(config) {
|
|
340
|
+
const checks = config?.preCommit || ["validate-branch", "check-guidelines"];
|
|
341
|
+
const checkCommands = checks.map((check) => {
|
|
342
|
+
switch (check) {
|
|
343
|
+
case "validate-branch":
|
|
344
|
+
return " workflow validate branch";
|
|
345
|
+
case "validate-commit":
|
|
346
|
+
return " workflow validate commit";
|
|
347
|
+
case "check-guidelines":
|
|
348
|
+
return " workflow doctor --check-guidelines-only 2>/dev/null || true";
|
|
349
|
+
default:
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
}).filter(Boolean).join("\n");
|
|
353
|
+
return `#!/bin/sh
|
|
354
|
+
# Workflow Agent pre-commit hook
|
|
355
|
+
# Auto-generated - do not edit manually
|
|
356
|
+
# To reinstall: workflow hooks install
|
|
357
|
+
# To uninstall: workflow hooks uninstall
|
|
358
|
+
|
|
359
|
+
# Skip in CI environment
|
|
360
|
+
if [ -n "\${CI:-}" ] || [ -n "\${GITHUB_ACTIONS:-}" ] || [ -n "\${GITLAB_CI:-}" ]; then
|
|
361
|
+
exit 0
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Run workflow checks
|
|
365
|
+
${checkCommands}
|
|
366
|
+
|
|
367
|
+
# Run original hook if it exists
|
|
368
|
+
if [ -f ".git/hooks/pre-commit.original" ]; then
|
|
369
|
+
.git/hooks/pre-commit.original "$@"
|
|
370
|
+
fi
|
|
371
|
+
`;
|
|
372
|
+
}
|
|
373
|
+
function generateCommitMsgHook(config) {
|
|
374
|
+
const checks = config?.commitMsg || ["validate-commit"];
|
|
375
|
+
const checkCommands = checks.map((check) => {
|
|
376
|
+
switch (check) {
|
|
377
|
+
case "validate-commit":
|
|
378
|
+
return ' workflow validate commit "$(cat "$1")"';
|
|
379
|
+
default:
|
|
380
|
+
return "";
|
|
381
|
+
}
|
|
382
|
+
}).filter(Boolean).join("\n");
|
|
383
|
+
return `#!/bin/sh
|
|
384
|
+
# Workflow Agent commit-msg hook
|
|
385
|
+
# Auto-generated - do not edit manually
|
|
386
|
+
# To reinstall: workflow hooks install
|
|
387
|
+
# To uninstall: workflow hooks uninstall
|
|
388
|
+
|
|
389
|
+
# Skip in CI environment
|
|
390
|
+
if [ -n "\${CI:-}" ] || [ -n "\${GITHUB_ACTIONS:-}" ] || [ -n "\${GITLAB_CI:-}" ]; then
|
|
391
|
+
exit 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
# Run workflow checks
|
|
395
|
+
${checkCommands}
|
|
396
|
+
|
|
397
|
+
# Run original hook if it exists
|
|
398
|
+
if [ -f ".git/hooks/commit-msg.original" ]; then
|
|
399
|
+
.git/hooks/commit-msg.original "$@"
|
|
400
|
+
fi
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
async function isWorkflowHook(hookPath) {
|
|
404
|
+
try {
|
|
405
|
+
const content = await readFile(hookPath, "utf-8");
|
|
406
|
+
return content.includes("Workflow Agent") && content.includes("Auto-generated");
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function getHookStatus(hookType, projectPath = process.cwd()) {
|
|
412
|
+
const hooksDir = getGitHooksDir(projectPath);
|
|
413
|
+
const hookPath = join(hooksDir, hookType);
|
|
414
|
+
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
415
|
+
const status = {
|
|
416
|
+
installed: false,
|
|
417
|
+
hookType,
|
|
418
|
+
hasExistingHook: false,
|
|
419
|
+
wrappedOriginal: false
|
|
420
|
+
};
|
|
421
|
+
if (!existsSync(hookPath)) {
|
|
422
|
+
return status;
|
|
423
|
+
}
|
|
424
|
+
status.hasExistingHook = true;
|
|
425
|
+
if (await isWorkflowHook(hookPath)) {
|
|
426
|
+
status.installed = true;
|
|
427
|
+
status.wrappedOriginal = existsSync(originalPath);
|
|
428
|
+
}
|
|
429
|
+
return status;
|
|
430
|
+
}
|
|
431
|
+
async function getAllHooksStatus(projectPath = process.cwd()) {
|
|
432
|
+
return Promise.all([
|
|
433
|
+
getHookStatus("pre-commit", projectPath),
|
|
434
|
+
getHookStatus("commit-msg", projectPath)
|
|
435
|
+
]);
|
|
436
|
+
}
|
|
437
|
+
async function installSingleHook(hookType, config, projectPath = process.cwd()) {
|
|
438
|
+
const hooksDir = getGitHooksDir(projectPath);
|
|
439
|
+
const hookPath = join(hooksDir, hookType);
|
|
440
|
+
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
441
|
+
const result = {
|
|
442
|
+
success: false,
|
|
443
|
+
hookType,
|
|
444
|
+
wrappedExisting: false
|
|
445
|
+
};
|
|
446
|
+
try {
|
|
447
|
+
if (!existsSync(hooksDir)) {
|
|
448
|
+
await mkdir(hooksDir, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
if (existsSync(hookPath)) {
|
|
451
|
+
const isOurs = await isWorkflowHook(hookPath);
|
|
452
|
+
if (!isOurs) {
|
|
453
|
+
await rename(hookPath, originalPath);
|
|
454
|
+
result.wrappedExisting = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const hookContent = hookType === "pre-commit" ? generatePreCommitHook(config) : generateCommitMsgHook(config);
|
|
458
|
+
await writeFile(hookPath, hookContent, "utf-8");
|
|
459
|
+
await chmod(hookPath, 493);
|
|
460
|
+
result.success = true;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
463
|
+
}
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
async function installHooks(config, projectPath = process.cwd()) {
|
|
467
|
+
if (!hasGitRepo(projectPath)) {
|
|
468
|
+
return [{
|
|
469
|
+
success: false,
|
|
470
|
+
hookType: "pre-commit",
|
|
471
|
+
wrappedExisting: false,
|
|
472
|
+
error: "No git repository found. Run git init first."
|
|
473
|
+
}];
|
|
474
|
+
}
|
|
475
|
+
const results = await Promise.all([
|
|
476
|
+
installSingleHook("pre-commit", config, projectPath),
|
|
477
|
+
installSingleHook("commit-msg", config, projectPath)
|
|
478
|
+
]);
|
|
479
|
+
return results;
|
|
480
|
+
}
|
|
481
|
+
async function uninstallSingleHook(hookType, projectPath = process.cwd()) {
|
|
482
|
+
const hooksDir = getGitHooksDir(projectPath);
|
|
483
|
+
const hookPath = join(hooksDir, hookType);
|
|
484
|
+
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
485
|
+
const result = {
|
|
486
|
+
success: false,
|
|
487
|
+
hookType,
|
|
488
|
+
wrappedExisting: false
|
|
489
|
+
};
|
|
490
|
+
try {
|
|
491
|
+
if (!existsSync(hookPath)) {
|
|
492
|
+
result.success = true;
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
const isOurs = await isWorkflowHook(hookPath);
|
|
496
|
+
if (!isOurs) {
|
|
497
|
+
result.error = "Hook is not managed by Workflow Agent";
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
await unlink(hookPath);
|
|
501
|
+
if (existsSync(originalPath)) {
|
|
502
|
+
await rename(originalPath, hookPath);
|
|
503
|
+
result.wrappedExisting = true;
|
|
504
|
+
}
|
|
505
|
+
result.success = true;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
508
|
+
}
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
async function uninstallHooks(projectPath = process.cwd()) {
|
|
512
|
+
return Promise.all([
|
|
513
|
+
uninstallSingleHook("pre-commit", projectPath),
|
|
514
|
+
uninstallSingleHook("commit-msg", projectPath)
|
|
515
|
+
]);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/utils/git-repo.ts
|
|
519
|
+
import { execa } from "execa";
|
|
520
|
+
import { existsSync as existsSync2 } from "fs";
|
|
521
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
522
|
+
import { join as join2 } from "path";
|
|
523
|
+
async function isGitRepo(projectPath = process.cwd()) {
|
|
524
|
+
try {
|
|
525
|
+
await execa("git", ["rev-parse", "--git-dir"], { cwd: projectPath });
|
|
526
|
+
return true;
|
|
527
|
+
} catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async function getGitRemoteUrl(projectPath = process.cwd()) {
|
|
532
|
+
try {
|
|
533
|
+
const { stdout } = await execa("git", ["remote", "get-url", "origin"], { cwd: projectPath });
|
|
534
|
+
return stdout.trim() || null;
|
|
535
|
+
} catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function isGitHubRemote(remoteUrl) {
|
|
540
|
+
if (!remoteUrl) return false;
|
|
541
|
+
return remoteUrl.includes("github.com");
|
|
542
|
+
}
|
|
543
|
+
function parseGitHubUrl(remoteUrl) {
|
|
544
|
+
if (!remoteUrl || !isGitHubRemote(remoteUrl)) return null;
|
|
545
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
546
|
+
if (match) {
|
|
547
|
+
return {
|
|
548
|
+
owner: match[1],
|
|
549
|
+
repo: match[2].replace(/\.git$/, "")
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
async function getDefaultBranch(projectPath = process.cwd()) {
|
|
555
|
+
try {
|
|
556
|
+
const { stdout } = await execa("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
557
|
+
cwd: projectPath
|
|
558
|
+
});
|
|
559
|
+
return stdout.trim().replace("refs/remotes/origin/", "") || "main";
|
|
560
|
+
} catch {
|
|
561
|
+
try {
|
|
562
|
+
const { stdout } = await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
563
|
+
cwd: projectPath
|
|
564
|
+
});
|
|
565
|
+
return stdout.trim() || "main";
|
|
566
|
+
} catch {
|
|
567
|
+
return "main";
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async function getRepoInfo(projectPath = process.cwd()) {
|
|
572
|
+
const isRepo = await isGitRepo(projectPath);
|
|
573
|
+
if (!isRepo) {
|
|
574
|
+
return {
|
|
575
|
+
isGitRepo: false,
|
|
576
|
+
remoteUrl: null,
|
|
577
|
+
isGitHub: false,
|
|
578
|
+
github: null,
|
|
579
|
+
defaultBranch: null
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const remoteUrl = await getGitRemoteUrl(projectPath);
|
|
583
|
+
const isGitHub = isGitHubRemote(remoteUrl);
|
|
584
|
+
const github = parseGitHubUrl(remoteUrl);
|
|
585
|
+
const defaultBranch = await getDefaultBranch(projectPath);
|
|
586
|
+
return {
|
|
587
|
+
isGitRepo: true,
|
|
588
|
+
remoteUrl,
|
|
589
|
+
isGitHub,
|
|
590
|
+
github,
|
|
591
|
+
defaultBranch
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function detectPackageManager(projectPath = process.cwd()) {
|
|
595
|
+
if (existsSync2(join2(projectPath, "pnpm-lock.yaml"))) {
|
|
596
|
+
return "pnpm";
|
|
597
|
+
}
|
|
598
|
+
if (existsSync2(join2(projectPath, "yarn.lock"))) {
|
|
599
|
+
return "yarn";
|
|
600
|
+
}
|
|
601
|
+
if (existsSync2(join2(projectPath, "bun.lockb"))) {
|
|
602
|
+
return "bun";
|
|
603
|
+
}
|
|
604
|
+
if (existsSync2(join2(projectPath, "package-lock.json"))) {
|
|
605
|
+
return "npm";
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
const pkgPath = join2(projectPath, "package.json");
|
|
609
|
+
if (existsSync2(pkgPath)) {
|
|
610
|
+
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
611
|
+
if (pkg.packageManager) {
|
|
612
|
+
if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
|
|
613
|
+
if (pkg.packageManager.startsWith("yarn")) return "yarn";
|
|
614
|
+
if (pkg.packageManager.startsWith("bun")) return "bun";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
} catch {
|
|
618
|
+
}
|
|
619
|
+
return "npm";
|
|
620
|
+
}
|
|
621
|
+
async function isMonorepo(projectPath = process.cwd()) {
|
|
622
|
+
if (existsSync2(join2(projectPath, "pnpm-workspace.yaml"))) {
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const pkgPath = join2(projectPath, "package.json");
|
|
627
|
+
if (existsSync2(pkgPath)) {
|
|
628
|
+
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
629
|
+
if (pkg.workspaces) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
if (existsSync2(join2(projectPath, "lerna.json"))) {
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
if (existsSync2(join2(projectPath, "nx.json"))) {
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (existsSync2(join2(projectPath, "turbo.json"))) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
async function getPackageScripts(projectPath = process.cwd()) {
|
|
647
|
+
try {
|
|
648
|
+
const pkgPath = join2(projectPath, "package.json");
|
|
649
|
+
if (!existsSync2(pkgPath)) {
|
|
650
|
+
return {};
|
|
651
|
+
}
|
|
652
|
+
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
653
|
+
return pkg.scripts || {};
|
|
654
|
+
} catch {
|
|
655
|
+
return {};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function getProjectInfo(projectPath = process.cwd()) {
|
|
659
|
+
const packageManager = await detectPackageManager(projectPath);
|
|
660
|
+
const monorepo = await isMonorepo(projectPath);
|
|
661
|
+
const scripts = await getPackageScripts(projectPath);
|
|
662
|
+
return {
|
|
663
|
+
packageManager,
|
|
664
|
+
isMonorepo: monorepo,
|
|
665
|
+
hasLintScript: "lint" in scripts,
|
|
666
|
+
hasTypecheckScript: "typecheck" in scripts || "type-check" in scripts,
|
|
667
|
+
hasFormatScript: "format" in scripts || "format:check" in scripts,
|
|
668
|
+
hasTestScript: "test" in scripts,
|
|
669
|
+
hasBuildScript: "build" in scripts
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function getInstallCommand(packageManager) {
|
|
673
|
+
switch (packageManager) {
|
|
674
|
+
case "pnpm":
|
|
675
|
+
return "pnpm install --frozen-lockfile";
|
|
676
|
+
case "yarn":
|
|
677
|
+
return "yarn install --frozen-lockfile";
|
|
678
|
+
case "bun":
|
|
679
|
+
return "bun install --frozen-lockfile";
|
|
680
|
+
case "npm":
|
|
681
|
+
default:
|
|
682
|
+
return "npm ci";
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function getRunCommand(packageManager, script, isMonorepo2 = false) {
|
|
686
|
+
switch (packageManager) {
|
|
687
|
+
case "pnpm":
|
|
688
|
+
return isMonorepo2 ? `pnpm -r run ${script}` : `pnpm run ${script}`;
|
|
689
|
+
case "yarn":
|
|
690
|
+
return isMonorepo2 ? `yarn workspaces run ${script}` : `yarn run ${script}`;
|
|
691
|
+
case "bun":
|
|
692
|
+
return `bun run ${script}`;
|
|
693
|
+
case "npm":
|
|
694
|
+
default:
|
|
695
|
+
return isMonorepo2 ? `npm run ${script} --workspaces --if-present` : `npm run ${script}`;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/utils/github-actions.ts
|
|
700
|
+
import { existsSync as existsSync3 } from "fs";
|
|
701
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
702
|
+
import { join as join3 } from "path";
|
|
703
|
+
function generateCIWorkflowContent(options) {
|
|
704
|
+
const {
|
|
705
|
+
packageManager,
|
|
706
|
+
isMonorepo: isMonorepo2,
|
|
707
|
+
checks,
|
|
708
|
+
nodeVersions,
|
|
709
|
+
defaultBranch,
|
|
710
|
+
hasLintScript,
|
|
711
|
+
hasTypecheckScript,
|
|
712
|
+
hasFormatScript,
|
|
713
|
+
hasTestScript,
|
|
714
|
+
hasBuildScript
|
|
715
|
+
} = options;
|
|
716
|
+
const installCmd = getInstallCommand(packageManager);
|
|
717
|
+
const steps = [];
|
|
718
|
+
steps.push(` - name: Checkout
|
|
719
|
+
uses: actions/checkout@v4`);
|
|
720
|
+
let cacheType = packageManager;
|
|
721
|
+
if (packageManager === "bun") {
|
|
722
|
+
cacheType = "npm";
|
|
723
|
+
}
|
|
724
|
+
const useMatrix = nodeVersions.length > 1;
|
|
725
|
+
const nodeVersionValue = useMatrix ? `\${{ matrix.node-version }}` : nodeVersions[0] || "20";
|
|
726
|
+
steps.push(`
|
|
727
|
+
- name: Setup Node.js
|
|
728
|
+
uses: actions/setup-node@v4
|
|
729
|
+
with:
|
|
730
|
+
node-version: '${nodeVersionValue}'
|
|
731
|
+
cache: '${cacheType}'`);
|
|
732
|
+
if (packageManager === "pnpm") {
|
|
733
|
+
steps.push(`
|
|
734
|
+
- name: Setup pnpm
|
|
735
|
+
uses: pnpm/action-setup@v4
|
|
736
|
+
with:
|
|
737
|
+
version: 9`);
|
|
738
|
+
}
|
|
739
|
+
steps.push(`
|
|
740
|
+
- name: Install dependencies
|
|
741
|
+
run: ${installCmd}`);
|
|
742
|
+
if (checks.includes("lint")) {
|
|
743
|
+
if (hasLintScript) {
|
|
744
|
+
const lintCmd = getRunCommand(packageManager, "lint", isMonorepo2);
|
|
745
|
+
steps.push(`
|
|
746
|
+
- name: Lint
|
|
747
|
+
run: ${lintCmd}`);
|
|
748
|
+
} else {
|
|
749
|
+
steps.push(`
|
|
750
|
+
- name: Lint
|
|
751
|
+
run: echo "No lint script configured - add 'lint' to package.json scripts"`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (checks.includes("typecheck")) {
|
|
755
|
+
if (hasTypecheckScript) {
|
|
756
|
+
const typecheckCmd = getRunCommand(packageManager, "typecheck", isMonorepo2);
|
|
757
|
+
steps.push(`
|
|
758
|
+
- name: Type check
|
|
759
|
+
run: ${typecheckCmd}`);
|
|
760
|
+
} else {
|
|
761
|
+
steps.push(`
|
|
762
|
+
- name: Type check
|
|
763
|
+
run: npx tsc --noEmit`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (checks.includes("format")) {
|
|
767
|
+
if (hasFormatScript) {
|
|
768
|
+
const formatCmd = getRunCommand(packageManager, "format:check", isMonorepo2);
|
|
769
|
+
steps.push(`
|
|
770
|
+
- name: Format check
|
|
771
|
+
run: ${formatCmd} || npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
772
|
+
} else {
|
|
773
|
+
steps.push(`
|
|
774
|
+
- name: Format check
|
|
775
|
+
run: npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (checks.includes("build")) {
|
|
779
|
+
if (hasBuildScript) {
|
|
780
|
+
const buildCmd = getRunCommand(packageManager, "build", isMonorepo2);
|
|
781
|
+
steps.push(`
|
|
782
|
+
- name: Build
|
|
783
|
+
run: ${buildCmd}`);
|
|
784
|
+
} else {
|
|
785
|
+
steps.push(`
|
|
786
|
+
- name: Build
|
|
787
|
+
run: echo "No build script configured - add 'build' to package.json scripts"`);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (checks.includes("test")) {
|
|
791
|
+
if (hasTestScript) {
|
|
792
|
+
const testCmd = getRunCommand(packageManager, "test", isMonorepo2);
|
|
793
|
+
steps.push(`
|
|
794
|
+
- name: Test
|
|
795
|
+
run: ${testCmd}`);
|
|
796
|
+
} else {
|
|
797
|
+
steps.push(`
|
|
798
|
+
- name: Test
|
|
799
|
+
run: echo "No test script configured - add 'test' to package.json scripts"`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
let matrixStrategy = "";
|
|
803
|
+
if (useMatrix) {
|
|
804
|
+
matrixStrategy = `
|
|
805
|
+
strategy:
|
|
806
|
+
matrix:
|
|
807
|
+
node-version: [${nodeVersions.map((v) => `'${v}'`).join(", ")}]`;
|
|
808
|
+
}
|
|
809
|
+
const workflow = `# Workflow Agent CI Pipeline
|
|
810
|
+
# Auto-generated - modifications will be preserved on regeneration
|
|
811
|
+
# To regenerate: workflow github:setup
|
|
812
|
+
|
|
813
|
+
name: CI
|
|
814
|
+
|
|
815
|
+
on:
|
|
816
|
+
push:
|
|
817
|
+
branches: [${defaultBranch}, develop]
|
|
818
|
+
pull_request:
|
|
819
|
+
branches: [${defaultBranch}, develop]
|
|
820
|
+
|
|
821
|
+
jobs:
|
|
822
|
+
ci:
|
|
823
|
+
runs-on: ubuntu-latest${matrixStrategy}
|
|
824
|
+
|
|
825
|
+
steps:
|
|
826
|
+
${steps.join("\n")}
|
|
827
|
+
`;
|
|
828
|
+
return workflow;
|
|
829
|
+
}
|
|
830
|
+
async function createCIWorkflow(options = {}) {
|
|
831
|
+
const projectPath = options.projectPath || process.cwd();
|
|
832
|
+
const workflowsDir = join3(projectPath, ".github", "workflows");
|
|
833
|
+
const workflowPath = join3(workflowsDir, "ci.yml");
|
|
834
|
+
const result = {
|
|
835
|
+
success: false,
|
|
836
|
+
filePath: workflowPath
|
|
837
|
+
};
|
|
838
|
+
try {
|
|
839
|
+
const projectInfo = await getProjectInfo(projectPath);
|
|
840
|
+
const packageManager = options.packageManager || projectInfo.packageManager;
|
|
841
|
+
const isMonorepo2 = options.isMonorepo ?? projectInfo.isMonorepo;
|
|
842
|
+
const checks = options.ciConfig?.checks || ["lint", "typecheck", "format", "build", "test"];
|
|
843
|
+
const nodeVersions = options.nodeVersions || ["20"];
|
|
844
|
+
const defaultBranch = options.defaultBranch || "main";
|
|
845
|
+
if (!existsSync3(workflowsDir)) {
|
|
846
|
+
await mkdir2(workflowsDir, { recursive: true });
|
|
847
|
+
}
|
|
848
|
+
const content = generateCIWorkflowContent({
|
|
849
|
+
packageManager,
|
|
850
|
+
isMonorepo: isMonorepo2,
|
|
851
|
+
checks,
|
|
852
|
+
nodeVersions,
|
|
853
|
+
defaultBranch,
|
|
854
|
+
hasLintScript: projectInfo.hasLintScript,
|
|
855
|
+
hasTypecheckScript: projectInfo.hasTypecheckScript,
|
|
856
|
+
hasFormatScript: projectInfo.hasFormatScript,
|
|
857
|
+
hasTestScript: projectInfo.hasTestScript,
|
|
858
|
+
hasBuildScript: projectInfo.hasBuildScript
|
|
859
|
+
});
|
|
860
|
+
await writeFile2(workflowPath, content, "utf-8");
|
|
861
|
+
result.success = true;
|
|
862
|
+
} catch (error) {
|
|
863
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
function hasCIWorkflow(projectPath = process.cwd()) {
|
|
868
|
+
const workflowsDir = join3(projectPath, ".github", "workflows");
|
|
869
|
+
const possibleNames = ["ci.yml", "ci.yaml", "main.yml", "main.yaml"];
|
|
870
|
+
return possibleNames.some((name) => existsSync3(join3(workflowsDir, name)));
|
|
871
|
+
}
|
|
872
|
+
|
|
232
873
|
// src/cli/commands/init.ts
|
|
233
874
|
var __filename = fileURLToPath(import.meta.url);
|
|
234
875
|
var __dirname = dirname(__filename);
|
|
235
876
|
async function initCommand(options) {
|
|
236
877
|
console.log(chalk.bold.cyan("\n\u{1F680} Workflow Agent Initialization\n"));
|
|
237
878
|
const cwd = process.cwd();
|
|
238
|
-
const isNonInteractive = !!(options.preset && options.name);
|
|
879
|
+
const isNonInteractive = !!(options.preset && options.name) || !!options.yes;
|
|
239
880
|
if (hasConfig(cwd) && !options.yes && !isNonInteractive) {
|
|
240
881
|
const shouldContinue = await p.confirm({
|
|
241
882
|
message: "Workflow Agent is already configured. Continue and overwrite?",
|
|
@@ -275,24 +916,24 @@ async function initCommand(options) {
|
|
|
275
916
|
try {
|
|
276
917
|
const presetModule = await import(`@workflow/scopes-${preset}`);
|
|
277
918
|
scopes = presetModule.scopes || presetModule.default.scopes;
|
|
278
|
-
const
|
|
279
|
-
|
|
919
|
+
const spinner5 = p.spinner();
|
|
920
|
+
spinner5.start(`Loading ${presetModule.default?.name || preset} preset`);
|
|
280
921
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
281
|
-
|
|
922
|
+
spinner5.stop(`\u2713 Loaded ${scopes.length} scopes from preset`);
|
|
282
923
|
} catch (error) {
|
|
283
924
|
console.log(chalk.yellow(`
|
|
284
925
|
\u26A0\uFE0F Could not load preset package. Using basic scopes.`));
|
|
285
926
|
scopes = [
|
|
286
|
-
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
287
|
-
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
288
|
-
{ name: "
|
|
927
|
+
{ name: "feat", description: "New features and enhancements", emoji: "\u2728" },
|
|
928
|
+
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
929
|
+
{ name: "documentation", description: "Documentation updates and improvements", emoji: "\u{1F4DA}" }
|
|
289
930
|
];
|
|
290
931
|
}
|
|
291
932
|
} else {
|
|
292
933
|
scopes = [
|
|
293
|
-
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
294
|
-
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
295
|
-
{ name: "
|
|
934
|
+
{ name: "feat", description: "New features and enhancements", emoji: "\u2728" },
|
|
935
|
+
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
936
|
+
{ name: "documentation", description: "Documentation updates and improvements", emoji: "\u{1F4DA}" }
|
|
296
937
|
];
|
|
297
938
|
console.log(chalk.dim("\n\u{1F4A1} Tip: Edit workflow.config.json to add your custom scopes"));
|
|
298
939
|
}
|
|
@@ -302,54 +943,139 @@ async function initCommand(options) {
|
|
|
302
943
|
enforcement: "strict",
|
|
303
944
|
language: "en"
|
|
304
945
|
};
|
|
305
|
-
const configPath =
|
|
306
|
-
await
|
|
307
|
-
const workflowDir =
|
|
308
|
-
if (!
|
|
309
|
-
await
|
|
310
|
-
}
|
|
311
|
-
const shouldGenerateGuidelines = await p.confirm({
|
|
312
|
-
message: "Generate workflow guidelines from templates?",
|
|
313
|
-
initialValue: true
|
|
314
|
-
});
|
|
315
|
-
if (p.isCancel(shouldGenerateGuidelines)) {
|
|
316
|
-
p.cancel("Initialization cancelled");
|
|
317
|
-
process.exit(0);
|
|
946
|
+
const configPath = join4(cwd, "workflow.config.json");
|
|
947
|
+
await writeFile3(configPath, JSON.stringify(config, null, 2));
|
|
948
|
+
const workflowDir = join4(cwd, ".workflow");
|
|
949
|
+
if (!existsSync4(workflowDir)) {
|
|
950
|
+
await mkdir3(workflowDir, { recursive: true });
|
|
318
951
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
952
|
+
const repoInfo = await getRepoInfo(cwd);
|
|
953
|
+
const projectInfo = await getProjectInfo(cwd);
|
|
954
|
+
const mandatoryTemplates = getMandatoryTemplates();
|
|
955
|
+
const optionalTemplates = getOptionalTemplates();
|
|
956
|
+
console.log(chalk.dim(`
|
|
957
|
+
\u{1F4CB} Generating ${mandatoryTemplates.length} mandatory guidelines...`));
|
|
958
|
+
const guidelinesDir = join4(cwd, "guidelines");
|
|
959
|
+
const templatesDir = join4(__dirname, "../../templates");
|
|
960
|
+
let mandatoryGenerated = 0;
|
|
961
|
+
let optionalGenerated = 0;
|
|
962
|
+
try {
|
|
963
|
+
await validateTemplateDirectory(templatesDir);
|
|
964
|
+
const context = await buildTemplateContext(config, cwd);
|
|
965
|
+
await mkdir3(guidelinesDir, { recursive: true });
|
|
966
|
+
for (const template of mandatoryTemplates) {
|
|
967
|
+
try {
|
|
968
|
+
const templatePath = join4(templatesDir, template.filename);
|
|
969
|
+
const outputPath = join4(guidelinesDir, template.filename);
|
|
970
|
+
await renderTemplateFile(templatePath, outputPath, context);
|
|
971
|
+
mandatoryGenerated++;
|
|
972
|
+
} catch (error) {
|
|
973
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Could not generate ${template.filename}`));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
console.log(chalk.green(`\u2713 Generated ${mandatoryGenerated} mandatory guidelines`));
|
|
977
|
+
let shouldGenerateOptional = isNonInteractive;
|
|
978
|
+
if (!isNonInteractive) {
|
|
979
|
+
const response = await p.confirm({
|
|
980
|
+
message: `Generate ${optionalTemplates.length} optional guidelines (deployment, library inventory, etc.)?`,
|
|
981
|
+
initialValue: true
|
|
982
|
+
});
|
|
983
|
+
shouldGenerateOptional = !p.isCancel(response) && response;
|
|
984
|
+
}
|
|
985
|
+
if (shouldGenerateOptional) {
|
|
986
|
+
for (const template of optionalTemplates) {
|
|
987
|
+
try {
|
|
988
|
+
const templatePath = join4(templatesDir, template.filename);
|
|
989
|
+
const outputPath = join4(guidelinesDir, template.filename);
|
|
990
|
+
await renderTemplateFile(templatePath, outputPath, context);
|
|
991
|
+
optionalGenerated++;
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
console.log(chalk.green(`\u2713 Generated ${optionalGenerated} optional guidelines`));
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
console.log(chalk.yellow(`
|
|
999
|
+
\u26A0\uFE0F Could not generate guidelines: ${error instanceof Error ? error.message : String(error)}`));
|
|
1000
|
+
console.log(chalk.dim("You can manually copy guidelines later if needed."));
|
|
1001
|
+
}
|
|
1002
|
+
if (hasGitRepo(cwd)) {
|
|
1003
|
+
let shouldInstallHooks = isNonInteractive;
|
|
1004
|
+
if (!isNonInteractive) {
|
|
1005
|
+
const response = await p.confirm({
|
|
1006
|
+
message: "Install git hooks for pre-commit validation?",
|
|
1007
|
+
initialValue: true
|
|
1008
|
+
});
|
|
1009
|
+
shouldInstallHooks = !p.isCancel(response) && response;
|
|
1010
|
+
}
|
|
1011
|
+
if (shouldInstallHooks) {
|
|
1012
|
+
const hookSpinner = p.spinner();
|
|
1013
|
+
hookSpinner.start("Installing git hooks...");
|
|
1014
|
+
const hookResults = await installHooks(config.hooks, cwd);
|
|
1015
|
+
const allSuccess = hookResults.every((r) => r.success);
|
|
1016
|
+
if (allSuccess) {
|
|
1017
|
+
hookSpinner.stop("\u2713 Installed git hooks");
|
|
1018
|
+
} else {
|
|
1019
|
+
hookSpinner.stop("\u26A0\uFE0F Some hooks could not be installed");
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (repoInfo.isGitHub) {
|
|
1024
|
+
const existingCI = hasCIWorkflow(cwd);
|
|
1025
|
+
if (!existingCI) {
|
|
1026
|
+
console.log(chalk.dim("\n\u{1F527} Setting up GitHub Actions CI (mandatory for GitHub repos)..."));
|
|
1027
|
+
const ciResult = await createCIWorkflow({
|
|
1028
|
+
projectPath: cwd,
|
|
1029
|
+
packageManager: projectInfo.packageManager,
|
|
1030
|
+
isMonorepo: projectInfo.isMonorepo,
|
|
1031
|
+
ciConfig: config.ci,
|
|
1032
|
+
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1033
|
+
});
|
|
1034
|
+
if (ciResult.success) {
|
|
1035
|
+
console.log(chalk.green("\u2713 Created GitHub Actions CI workflow"));
|
|
1036
|
+
console.log(chalk.dim(` File: .github/workflows/ci.yml`));
|
|
1037
|
+
} else {
|
|
1038
|
+
console.log(chalk.yellow(`\u26A0\uFE0F Could not create CI workflow: ${ciResult.error}`));
|
|
1039
|
+
}
|
|
1040
|
+
} else {
|
|
1041
|
+
console.log(chalk.dim("\n\u2713 GitHub Actions CI workflow already exists"));
|
|
1042
|
+
}
|
|
1043
|
+
} else if (repoInfo.isGitRepo) {
|
|
1044
|
+
let shouldSetupCI = false;
|
|
1045
|
+
if (!isNonInteractive) {
|
|
1046
|
+
const response = await p.confirm({
|
|
1047
|
+
message: "No GitHub remote detected. Create CI workflow anyway?",
|
|
1048
|
+
initialValue: false
|
|
1049
|
+
});
|
|
1050
|
+
shouldSetupCI = !p.isCancel(response) && response;
|
|
1051
|
+
}
|
|
1052
|
+
if (shouldSetupCI) {
|
|
1053
|
+
const ciResult = await createCIWorkflow({
|
|
1054
|
+
projectPath: cwd,
|
|
1055
|
+
packageManager: projectInfo.packageManager,
|
|
1056
|
+
isMonorepo: projectInfo.isMonorepo,
|
|
1057
|
+
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1058
|
+
});
|
|
1059
|
+
if (ciResult.success) {
|
|
1060
|
+
console.log(chalk.green("\u2713 Created CI workflow"));
|
|
1061
|
+
}
|
|
335
1062
|
}
|
|
336
1063
|
}
|
|
337
1064
|
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
338
1065
|
console.log(chalk.dim("\nNext steps:"));
|
|
339
1066
|
console.log(chalk.dim(" 1. Review your configuration in workflow.config.json"));
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
console.log(chalk.dim("
|
|
346
|
-
console.log(chalk.dim(" 3. Run: workflow doctor (for optimization suggestions)\n"));
|
|
1067
|
+
console.log(chalk.dim(" 2. Review generated guidelines in guidelines/ directory"));
|
|
1068
|
+
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
1069
|
+
console.log(chalk.dim(" 4. Run: workflow doctor (for health check)\n"));
|
|
1070
|
+
if (repoInfo.isGitHub) {
|
|
1071
|
+
console.log(chalk.cyan("\u{1F4A1} Recommended: Enable branch protection on GitHub"));
|
|
1072
|
+
console.log(chalk.dim(" Settings \u2192 Branches \u2192 Add rule \u2192 Require status checks\n"));
|
|
347
1073
|
}
|
|
348
1074
|
}
|
|
349
1075
|
|
|
350
1076
|
// src/cli/commands/validate.ts
|
|
351
1077
|
import chalk2 from "chalk";
|
|
352
|
-
import { execa } from "execa";
|
|
1078
|
+
import { execa as execa2 } from "execa";
|
|
353
1079
|
async function validateCommand(type, value, _options = {}) {
|
|
354
1080
|
const config = await loadConfig();
|
|
355
1081
|
if (!config) {
|
|
@@ -362,7 +1088,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
362
1088
|
switch (type) {
|
|
363
1089
|
case "branch": {
|
|
364
1090
|
if (!targetValue) {
|
|
365
|
-
const { stdout } = await
|
|
1091
|
+
const { stdout } = await execa2("git", ["branch", "--show-current"]);
|
|
366
1092
|
targetValue = stdout.trim();
|
|
367
1093
|
}
|
|
368
1094
|
result = await validateBranchName(targetValue, config);
|
|
@@ -370,7 +1096,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
370
1096
|
}
|
|
371
1097
|
case "commit": {
|
|
372
1098
|
if (!targetValue) {
|
|
373
|
-
const { stdout } = await
|
|
1099
|
+
const { stdout } = await execa2("git", ["log", "-1", "--pretty=%s"]);
|
|
374
1100
|
targetValue = stdout.trim();
|
|
375
1101
|
}
|
|
376
1102
|
result = await validateCommitMessage(targetValue, config);
|
|
@@ -464,30 +1190,290 @@ async function suggestCommand(feedback, options = {}) {
|
|
|
464
1190
|
|
|
465
1191
|
// src/cli/commands/doctor.ts
|
|
466
1192
|
import chalk5 from "chalk";
|
|
467
|
-
|
|
1193
|
+
|
|
1194
|
+
// src/validators/guidelines.ts
|
|
1195
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1196
|
+
import { readFile as readFile3, readdir } from "fs/promises";
|
|
1197
|
+
import { join as join5 } from "path";
|
|
1198
|
+
function getEffectiveMandatoryTemplates(guidelinesConfig) {
|
|
1199
|
+
const coreMandatory = getMandatoryTemplateFilenames();
|
|
1200
|
+
if (!guidelinesConfig) {
|
|
1201
|
+
return coreMandatory;
|
|
1202
|
+
}
|
|
1203
|
+
let mandatory = [...coreMandatory];
|
|
1204
|
+
if (guidelinesConfig.additionalMandatory) {
|
|
1205
|
+
for (const template of guidelinesConfig.additionalMandatory) {
|
|
1206
|
+
if (!mandatory.includes(template) && templateMetadata[template]) {
|
|
1207
|
+
mandatory.push(template);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (guidelinesConfig.optionalOverrides) {
|
|
1212
|
+
mandatory = mandatory.filter((t) => !guidelinesConfig.optionalOverrides.includes(t));
|
|
1213
|
+
}
|
|
1214
|
+
return mandatory;
|
|
1215
|
+
}
|
|
1216
|
+
async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
1217
|
+
const guidelinesDir = join5(projectPath, "guidelines");
|
|
1218
|
+
const result = {
|
|
1219
|
+
valid: true,
|
|
1220
|
+
missingMandatory: [],
|
|
1221
|
+
presentMandatory: [],
|
|
1222
|
+
presentOptional: [],
|
|
1223
|
+
errors: []
|
|
1224
|
+
};
|
|
1225
|
+
if (!existsSync5(guidelinesDir)) {
|
|
1226
|
+
const mandatory2 = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1227
|
+
result.valid = false;
|
|
1228
|
+
result.missingMandatory = mandatory2;
|
|
1229
|
+
result.errors.push("Guidelines directory does not exist. Run: workflow init");
|
|
1230
|
+
return result;
|
|
1231
|
+
}
|
|
1232
|
+
let existingFiles = [];
|
|
1233
|
+
try {
|
|
1234
|
+
existingFiles = await readdir(guidelinesDir);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
result.valid = false;
|
|
1237
|
+
result.errors.push(`Cannot read guidelines directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
1238
|
+
return result;
|
|
1239
|
+
}
|
|
1240
|
+
const mandatory = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1241
|
+
for (const template of mandatory) {
|
|
1242
|
+
if (existingFiles.includes(template)) {
|
|
1243
|
+
result.presentMandatory.push(template);
|
|
1244
|
+
} else {
|
|
1245
|
+
result.missingMandatory.push(template);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const allTemplateNames = Object.keys(templateMetadata);
|
|
1249
|
+
for (const file of existingFiles) {
|
|
1250
|
+
if (allTemplateNames.includes(file) && !mandatory.includes(file)) {
|
|
1251
|
+
result.presentOptional.push(file);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (result.missingMandatory.length > 0) {
|
|
1255
|
+
result.valid = false;
|
|
1256
|
+
result.errors.push(
|
|
1257
|
+
`Missing mandatory guidelines: ${result.missingMandatory.join(", ")}. Run: workflow init`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
1263
|
+
const workflowsDir = join5(projectPath, ".github", "workflows");
|
|
1264
|
+
const result = {
|
|
1265
|
+
valid: true,
|
|
1266
|
+
hasWorkflowFile: false,
|
|
1267
|
+
hasLintCheck: false,
|
|
1268
|
+
hasTypecheckCheck: false,
|
|
1269
|
+
hasFormatCheck: false,
|
|
1270
|
+
hasBuildCheck: false,
|
|
1271
|
+
hasTestCheck: false,
|
|
1272
|
+
errors: [],
|
|
1273
|
+
warnings: []
|
|
1274
|
+
};
|
|
1275
|
+
if (!existsSync5(workflowsDir)) {
|
|
1276
|
+
result.valid = false;
|
|
1277
|
+
result.errors.push("GitHub Actions workflows directory does not exist. Run: workflow github:setup");
|
|
1278
|
+
return result;
|
|
1279
|
+
}
|
|
1280
|
+
let workflowFiles = [];
|
|
1281
|
+
try {
|
|
1282
|
+
workflowFiles = await readdir(workflowsDir);
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
result.valid = false;
|
|
1285
|
+
result.errors.push(`Cannot read workflows directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
1286
|
+
return result;
|
|
1287
|
+
}
|
|
1288
|
+
const ciWorkflowNames = ["ci.yml", "ci.yaml", "main.yml", "main.yaml", "build.yml", "build.yaml", "test.yml", "test.yaml"];
|
|
1289
|
+
const ciWorkflows = workflowFiles.filter((f) => ciWorkflowNames.includes(f.toLowerCase()));
|
|
1290
|
+
if (ciWorkflows.length === 0) {
|
|
1291
|
+
result.valid = false;
|
|
1292
|
+
result.errors.push("No CI workflow file found. Run: workflow github:setup");
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1295
|
+
result.hasWorkflowFile = true;
|
|
1296
|
+
const workflowPath = join5(workflowsDir, ciWorkflows[0]);
|
|
1297
|
+
let workflowContent = "";
|
|
1298
|
+
try {
|
|
1299
|
+
workflowContent = await readFile3(workflowPath, "utf-8");
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
result.warnings.push(`Cannot read workflow file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1302
|
+
return result;
|
|
1303
|
+
}
|
|
1304
|
+
const contentLower = workflowContent.toLowerCase();
|
|
1305
|
+
if (contentLower.includes("lint") || contentLower.includes("eslint")) {
|
|
1306
|
+
result.hasLintCheck = true;
|
|
1307
|
+
} else {
|
|
1308
|
+
result.warnings.push("CI workflow may be missing lint check");
|
|
1309
|
+
}
|
|
1310
|
+
if (contentLower.includes("typecheck") || contentLower.includes("type-check") || contentLower.includes("tsc")) {
|
|
1311
|
+
result.hasTypecheckCheck = true;
|
|
1312
|
+
} else {
|
|
1313
|
+
result.warnings.push("CI workflow may be missing typecheck");
|
|
1314
|
+
}
|
|
1315
|
+
if (contentLower.includes("format") || contentLower.includes("prettier")) {
|
|
1316
|
+
result.hasFormatCheck = true;
|
|
1317
|
+
} else {
|
|
1318
|
+
result.warnings.push("CI workflow may be missing format check");
|
|
1319
|
+
}
|
|
1320
|
+
if (contentLower.includes("build")) {
|
|
1321
|
+
result.hasBuildCheck = true;
|
|
1322
|
+
} else {
|
|
1323
|
+
result.warnings.push("CI workflow may be missing build step");
|
|
1324
|
+
}
|
|
1325
|
+
if (contentLower.includes("test") && !contentLower.includes("typecheck")) {
|
|
1326
|
+
result.hasTestCheck = true;
|
|
1327
|
+
} else {
|
|
1328
|
+
result.warnings.push("CI workflow may be missing test step");
|
|
1329
|
+
}
|
|
1330
|
+
const mandatoryChecks = [result.hasLintCheck, result.hasTypecheckCheck, result.hasFormatCheck];
|
|
1331
|
+
if (!mandatoryChecks.every(Boolean)) {
|
|
1332
|
+
result.valid = false;
|
|
1333
|
+
result.errors.push("CI workflow is missing mandatory checks (lint, typecheck, format)");
|
|
1334
|
+
}
|
|
1335
|
+
return result;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/cli/commands/doctor.ts
|
|
1339
|
+
async function doctorCommand(options = {}) {
|
|
1340
|
+
const cwd = process.cwd();
|
|
1341
|
+
if (options.checkGuidelinesOnly) {
|
|
1342
|
+
const result = await validateGuidelinesExist(cwd);
|
|
1343
|
+
if (!result.valid) {
|
|
1344
|
+
console.error(chalk5.red("\u2717 Missing mandatory guidelines"));
|
|
1345
|
+
for (const missing of result.missingMandatory) {
|
|
1346
|
+
console.error(chalk5.red(` \u2022 ${missing}`));
|
|
1347
|
+
}
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
process.exit(0);
|
|
1351
|
+
}
|
|
468
1352
|
console.log(chalk5.bold.cyan("\n\u{1F3E5} Workflow Agent Health Check\n"));
|
|
469
1353
|
const config = await loadConfig();
|
|
1354
|
+
let hasErrors = false;
|
|
1355
|
+
let hasWarnings = false;
|
|
1356
|
+
console.log(chalk5.bold("\u{1F4CB} Configuration"));
|
|
470
1357
|
if (!config) {
|
|
471
|
-
console.error(chalk5.red("\u2717 No workflow configuration found"));
|
|
472
|
-
console.log(chalk5.yellow("
|
|
1358
|
+
console.error(chalk5.red(" \u2717 No workflow configuration found"));
|
|
1359
|
+
console.log(chalk5.yellow(" Run: workflow init"));
|
|
473
1360
|
process.exit(1);
|
|
474
1361
|
}
|
|
475
|
-
console.log(chalk5.green("\u2713 Configuration loaded successfully"));
|
|
476
|
-
console.log(chalk5.dim(`
|
|
477
|
-
console.log(chalk5.dim(`
|
|
478
|
-
console.log(chalk5.dim(`
|
|
479
|
-
console.log(chalk5.dim(`
|
|
480
|
-
console.log(chalk5.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1362
|
+
console.log(chalk5.green(" \u2713 Configuration loaded successfully"));
|
|
1363
|
+
console.log(chalk5.dim(` Project: ${config.projectName}`));
|
|
1364
|
+
console.log(chalk5.dim(` Scopes: ${config.scopes.length} configured`));
|
|
1365
|
+
console.log(chalk5.dim(` Enforcement: ${config.enforcement}`));
|
|
1366
|
+
console.log(chalk5.dim(` Language: ${config.language}`));
|
|
1367
|
+
console.log(chalk5.bold("\n\u{1F4DA} Guidelines"));
|
|
1368
|
+
const guidelinesResult = await validateGuidelinesExist(cwd, config);
|
|
1369
|
+
if (guidelinesResult.valid) {
|
|
1370
|
+
console.log(chalk5.green(` \u2713 All ${guidelinesResult.presentMandatory.length} mandatory guidelines present`));
|
|
1371
|
+
if (guidelinesResult.presentOptional.length > 0) {
|
|
1372
|
+
console.log(chalk5.dim(` + ${guidelinesResult.presentOptional.length} optional guidelines`));
|
|
1373
|
+
}
|
|
1374
|
+
} else {
|
|
1375
|
+
hasErrors = true;
|
|
1376
|
+
console.log(chalk5.red(` \u2717 Missing ${guidelinesResult.missingMandatory.length} mandatory guidelines:`));
|
|
1377
|
+
for (const missing of guidelinesResult.missingMandatory) {
|
|
1378
|
+
console.log(chalk5.red(` \u2022 ${missing}`));
|
|
1379
|
+
}
|
|
1380
|
+
console.log(chalk5.yellow(" Run: workflow init"));
|
|
1381
|
+
}
|
|
1382
|
+
console.log(chalk5.bold("\n\u{1F517} Git Hooks"));
|
|
1383
|
+
if (!hasGitRepo(cwd)) {
|
|
1384
|
+
console.log(chalk5.yellow(" \u26A0 No git repository found"));
|
|
1385
|
+
hasWarnings = true;
|
|
1386
|
+
} else {
|
|
1387
|
+
const hookStatuses = await getAllHooksStatus(cwd);
|
|
1388
|
+
const installedHooks = hookStatuses.filter((h) => h.installed);
|
|
1389
|
+
if (installedHooks.length === hookStatuses.length) {
|
|
1390
|
+
console.log(chalk5.green(` \u2713 All ${installedHooks.length} hooks installed`));
|
|
1391
|
+
for (const hook of hookStatuses) {
|
|
1392
|
+
const extra = hook.wrappedOriginal ? " (wrapping original)" : "";
|
|
1393
|
+
console.log(chalk5.dim(` \u2022 ${hook.hookType}${extra}`));
|
|
1394
|
+
}
|
|
1395
|
+
} else if (installedHooks.length > 0) {
|
|
1396
|
+
hasWarnings = true;
|
|
1397
|
+
console.log(chalk5.yellow(` \u26A0 ${installedHooks.length}/${hookStatuses.length} hooks installed`));
|
|
1398
|
+
for (const hook of hookStatuses) {
|
|
1399
|
+
if (hook.installed) {
|
|
1400
|
+
console.log(chalk5.green(` \u2713 ${hook.hookType}`));
|
|
1401
|
+
} else {
|
|
1402
|
+
console.log(chalk5.yellow(` \u2717 ${hook.hookType}`));
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
console.log(chalk5.yellow(" Run: workflow hooks install"));
|
|
1406
|
+
} else {
|
|
1407
|
+
hasWarnings = true;
|
|
1408
|
+
console.log(chalk5.yellow(" \u26A0 No hooks installed"));
|
|
1409
|
+
console.log(chalk5.yellow(" Run: workflow hooks install"));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
console.log(chalk5.bold("\n\u{1F680} CI/CD Pipeline"));
|
|
1413
|
+
const repoInfo = await getRepoInfo(cwd);
|
|
1414
|
+
if (!repoInfo.isGitRepo) {
|
|
1415
|
+
console.log(chalk5.dim(" \u25CB No git repository (CI check skipped)"));
|
|
1416
|
+
} else if (!repoInfo.isGitHub) {
|
|
1417
|
+
console.log(chalk5.dim(" \u25CB Not a GitHub repository (CI check skipped)"));
|
|
1418
|
+
console.log(chalk5.dim(` Remote: ${repoInfo.remoteUrl || "none"}`));
|
|
1419
|
+
} else {
|
|
1420
|
+
console.log(chalk5.dim(` Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`));
|
|
1421
|
+
const ciResult = await validateGitHubActionsSetup(cwd);
|
|
1422
|
+
if (ciResult.valid) {
|
|
1423
|
+
console.log(chalk5.green(" \u2713 GitHub Actions CI configured correctly"));
|
|
1424
|
+
const checks = [
|
|
1425
|
+
ciResult.hasLintCheck && "lint",
|
|
1426
|
+
ciResult.hasTypecheckCheck && "typecheck",
|
|
1427
|
+
ciResult.hasFormatCheck && "format",
|
|
1428
|
+
ciResult.hasBuildCheck && "build",
|
|
1429
|
+
ciResult.hasTestCheck && "test"
|
|
1430
|
+
].filter(Boolean);
|
|
1431
|
+
console.log(chalk5.dim(` Checks: ${checks.join(", ")}`));
|
|
1432
|
+
} else if (!ciResult.hasWorkflowFile) {
|
|
1433
|
+
hasErrors = true;
|
|
1434
|
+
console.log(chalk5.red(" \u2717 No CI workflow found"));
|
|
1435
|
+
console.log(chalk5.yellow(" Run: workflow github:setup"));
|
|
1436
|
+
} else {
|
|
1437
|
+
hasWarnings = true;
|
|
1438
|
+
console.log(chalk5.yellow(" \u26A0 CI workflow may be incomplete"));
|
|
1439
|
+
for (const warning of ciResult.warnings) {
|
|
1440
|
+
console.log(chalk5.yellow(` \u2022 ${warning}`));
|
|
1441
|
+
}
|
|
1442
|
+
console.log(chalk5.yellow(" Run: workflow github:setup to regenerate"));
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
console.log(chalk5.bold("\n\u{1F4A1} Suggestions"));
|
|
1446
|
+
if (repoInfo.isGitHub && hasCIWorkflow(cwd)) {
|
|
1447
|
+
console.log(chalk5.cyan(" \u2192 Enable branch protection on GitHub"));
|
|
1448
|
+
console.log(chalk5.dim(" Settings \u2192 Branches \u2192 Add rule"));
|
|
1449
|
+
console.log(chalk5.dim(" \u2611 Require status checks to pass before merging"));
|
|
1450
|
+
console.log(chalk5.dim(" \u2611 Require branches to be up to date before merging"));
|
|
1451
|
+
}
|
|
1452
|
+
if (config.enforcement !== "strict") {
|
|
1453
|
+
console.log(chalk5.cyan(` \u2192 Consider switching to 'strict' enforcement`));
|
|
1454
|
+
console.log(chalk5.dim(` Current: ${config.enforcement}`));
|
|
1455
|
+
}
|
|
1456
|
+
if (config.scopes.length < 5) {
|
|
1457
|
+
console.log(chalk5.cyan(" \u2192 Consider adding more scopes for better organization"));
|
|
1458
|
+
console.log(chalk5.dim(" Run: workflow config add scope <name>"));
|
|
1459
|
+
}
|
|
1460
|
+
console.log(chalk5.bold("\n\u{1F4CA} Summary"));
|
|
1461
|
+
if (hasErrors) {
|
|
1462
|
+
console.log(chalk5.red(" \u2717 Health check failed - issues found"));
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
} else if (hasWarnings) {
|
|
1465
|
+
console.log(chalk5.yellow(" \u26A0 Health check passed with warnings"));
|
|
1466
|
+
} else {
|
|
1467
|
+
console.log(chalk5.green(" \u2713 All checks passed"));
|
|
1468
|
+
}
|
|
1469
|
+
console.log("");
|
|
484
1470
|
}
|
|
485
1471
|
|
|
486
1472
|
// src/cli/commands/setup.ts
|
|
487
1473
|
import * as p3 from "@clack/prompts";
|
|
488
1474
|
import chalk6 from "chalk";
|
|
489
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
490
|
-
import { join as
|
|
1475
|
+
import { readFileSync, writeFileSync, existsSync as existsSync6 } from "fs";
|
|
1476
|
+
import { join as join6 } from "path";
|
|
491
1477
|
var WORKFLOW_SCRIPTS = {
|
|
492
1478
|
"workflow:init": "workflow-agent init",
|
|
493
1479
|
"workflow:validate": "workflow-agent validate",
|
|
@@ -497,8 +1483,8 @@ var WORKFLOW_SCRIPTS = {
|
|
|
497
1483
|
async function setupCommand() {
|
|
498
1484
|
p3.intro(chalk6.bgBlue(" workflow-agent setup "));
|
|
499
1485
|
const cwd = process.cwd();
|
|
500
|
-
const packageJsonPath =
|
|
501
|
-
if (!
|
|
1486
|
+
const packageJsonPath = join6(cwd, "package.json");
|
|
1487
|
+
if (!existsSync6(packageJsonPath)) {
|
|
502
1488
|
p3.cancel("No package.json found in current directory");
|
|
503
1489
|
process.exit(1);
|
|
504
1490
|
}
|
|
@@ -555,15 +1541,15 @@ async function setupCommand() {
|
|
|
555
1541
|
// src/cli/commands/scope-create.ts
|
|
556
1542
|
import * as p4 from "@clack/prompts";
|
|
557
1543
|
import chalk7 from "chalk";
|
|
558
|
-
import { existsSync as
|
|
559
|
-
import { writeFile as
|
|
560
|
-
import { join as
|
|
1544
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1545
|
+
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
|
|
1546
|
+
import { join as join7 } from "path";
|
|
561
1547
|
async function scopeCreateCommand(options) {
|
|
562
1548
|
console.log(chalk7.bold.cyan("\n\u{1F3A8} Create Custom Scope Package\n"));
|
|
563
1549
|
const cwd = process.cwd();
|
|
564
1550
|
const isNonInteractive = !!(options.name && options.scopes && options.presetName);
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
1551
|
+
const isMonorepo2 = existsSync7(join7(cwd, "pnpm-workspace.yaml"));
|
|
1552
|
+
if (isMonorepo2) {
|
|
567
1553
|
console.log(chalk7.dim("\u2713 Detected monorepo workspace\n"));
|
|
568
1554
|
}
|
|
569
1555
|
const packageNameInput = isNonInteractive ? options.name : await p4.text({
|
|
@@ -696,8 +1682,8 @@ async function scopeCreateCommand(options) {
|
|
|
696
1682
|
let outputDir;
|
|
697
1683
|
if (options.outputDir) {
|
|
698
1684
|
outputDir = options.outputDir;
|
|
699
|
-
} else if (
|
|
700
|
-
outputDir =
|
|
1685
|
+
} else if (isMonorepo2) {
|
|
1686
|
+
outputDir = join7(cwd, "packages", `scopes-${packageName}`);
|
|
701
1687
|
} else {
|
|
702
1688
|
const customDir = await p4.text({
|
|
703
1689
|
message: "Output directory:",
|
|
@@ -708,9 +1694,9 @@ async function scopeCreateCommand(options) {
|
|
|
708
1694
|
p4.cancel("Operation cancelled");
|
|
709
1695
|
process.exit(0);
|
|
710
1696
|
}
|
|
711
|
-
outputDir =
|
|
1697
|
+
outputDir = join7(cwd, customDir);
|
|
712
1698
|
}
|
|
713
|
-
if (
|
|
1699
|
+
if (existsSync7(outputDir)) {
|
|
714
1700
|
const shouldOverwrite = await p4.confirm({
|
|
715
1701
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
716
1702
|
initialValue: false
|
|
@@ -720,10 +1706,10 @@ async function scopeCreateCommand(options) {
|
|
|
720
1706
|
process.exit(0);
|
|
721
1707
|
}
|
|
722
1708
|
}
|
|
723
|
-
const
|
|
724
|
-
|
|
1709
|
+
const spinner5 = p4.spinner();
|
|
1710
|
+
spinner5.start("Creating package structure...");
|
|
725
1711
|
try {
|
|
726
|
-
await
|
|
1712
|
+
await mkdir4(join7(outputDir, "src"), { recursive: true });
|
|
727
1713
|
const packageJson = {
|
|
728
1714
|
name: `@workflow/scopes-${packageName}`,
|
|
729
1715
|
version: "1.0.0",
|
|
@@ -763,8 +1749,8 @@ async function scopeCreateCommand(options) {
|
|
|
763
1749
|
access: "public"
|
|
764
1750
|
}
|
|
765
1751
|
};
|
|
766
|
-
await
|
|
767
|
-
|
|
1752
|
+
await writeFile4(
|
|
1753
|
+
join7(outputDir, "package.json"),
|
|
768
1754
|
JSON.stringify(packageJson, null, 2),
|
|
769
1755
|
"utf-8"
|
|
770
1756
|
);
|
|
@@ -776,8 +1762,8 @@ async function scopeCreateCommand(options) {
|
|
|
776
1762
|
},
|
|
777
1763
|
include: ["src/**/*"]
|
|
778
1764
|
};
|
|
779
|
-
await
|
|
780
|
-
|
|
1765
|
+
await writeFile4(
|
|
1766
|
+
join7(outputDir, "tsconfig.json"),
|
|
781
1767
|
JSON.stringify(tsconfig, null, 2),
|
|
782
1768
|
"utf-8"
|
|
783
1769
|
);
|
|
@@ -791,7 +1777,7 @@ export default defineConfig({
|
|
|
791
1777
|
sourcemap: true,
|
|
792
1778
|
});
|
|
793
1779
|
`;
|
|
794
|
-
await
|
|
1780
|
+
await writeFile4(join7(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
795
1781
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
796
1782
|
|
|
797
1783
|
export const scopes: Scope[] = ${JSON.stringify(scopes, null, 2)};
|
|
@@ -805,7 +1791,7 @@ export const preset = {
|
|
|
805
1791
|
|
|
806
1792
|
export default preset;
|
|
807
1793
|
`;
|
|
808
|
-
await
|
|
1794
|
+
await writeFile4(join7(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
809
1795
|
if (!options.noTest) {
|
|
810
1796
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
811
1797
|
import { scopes, preset } from './index.js';
|
|
@@ -846,12 +1832,12 @@ describe('${presetName} Scope Preset', () => {
|
|
|
846
1832
|
});
|
|
847
1833
|
});
|
|
848
1834
|
`;
|
|
849
|
-
await
|
|
1835
|
+
await writeFile4(join7(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
850
1836
|
}
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
const workspaceFile =
|
|
854
|
-
const workspaceContent = await
|
|
1837
|
+
spinner5.stop("\u2713 Package structure created");
|
|
1838
|
+
if (isMonorepo2) {
|
|
1839
|
+
const workspaceFile = join7(cwd, "pnpm-workspace.yaml");
|
|
1840
|
+
const workspaceContent = await readFile4(workspaceFile, "utf-8");
|
|
855
1841
|
const packagePath = `packages/scopes-${packageName}`;
|
|
856
1842
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
857
1843
|
console.log(chalk7.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:"));
|
|
@@ -886,7 +1872,7 @@ describe('${presetName} Scope Preset', () => {
|
|
|
886
1872
|
console.log(chalk7.dim(" 4. Use in other projects: pnpm add @workflow/scopes-" + packageName + "\n"));
|
|
887
1873
|
}
|
|
888
1874
|
} catch (error) {
|
|
889
|
-
|
|
1875
|
+
spinner5.stop("\u2717 Failed to create package");
|
|
890
1876
|
console.error(chalk7.red("\nError:"), error);
|
|
891
1877
|
process.exit(1);
|
|
892
1878
|
}
|
|
@@ -895,9 +1881,9 @@ describe('${presetName} Scope Preset', () => {
|
|
|
895
1881
|
// src/cli/commands/scope-migrate.ts
|
|
896
1882
|
import * as p5 from "@clack/prompts";
|
|
897
1883
|
import chalk8 from "chalk";
|
|
898
|
-
import { existsSync as
|
|
899
|
-
import { writeFile as
|
|
900
|
-
import { join as
|
|
1884
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1885
|
+
import { writeFile as writeFile5, mkdir as mkdir5, readFile as readFile5 } from "fs/promises";
|
|
1886
|
+
import { join as join8 } from "path";
|
|
901
1887
|
async function scopeMigrateCommand(options) {
|
|
902
1888
|
console.log(chalk8.bold.cyan("\n\u{1F504} Migrate Scopes to Custom Package\n"));
|
|
903
1889
|
const cwd = process.cwd();
|
|
@@ -935,8 +1921,8 @@ async function scopeMigrateCommand(options) {
|
|
|
935
1921
|
p5.cancel("Migration cancelled");
|
|
936
1922
|
process.exit(0);
|
|
937
1923
|
}
|
|
938
|
-
const
|
|
939
|
-
if (
|
|
1924
|
+
const isMonorepo2 = existsSync8(join8(cwd, "pnpm-workspace.yaml"));
|
|
1925
|
+
if (isMonorepo2) {
|
|
940
1926
|
console.log(chalk8.dim("\n\u2713 Detected monorepo workspace\n"));
|
|
941
1927
|
}
|
|
942
1928
|
const packageNameInput = options.name || await p5.text({
|
|
@@ -980,8 +1966,8 @@ async function scopeMigrateCommand(options) {
|
|
|
980
1966
|
let outputDir;
|
|
981
1967
|
if (options.outputDir) {
|
|
982
1968
|
outputDir = options.outputDir;
|
|
983
|
-
} else if (
|
|
984
|
-
outputDir =
|
|
1969
|
+
} else if (isMonorepo2) {
|
|
1970
|
+
outputDir = join8(cwd, "packages", `scopes-${packageName}`);
|
|
985
1971
|
} else {
|
|
986
1972
|
const customDir = await p5.text({
|
|
987
1973
|
message: "Output directory:",
|
|
@@ -992,9 +1978,9 @@ async function scopeMigrateCommand(options) {
|
|
|
992
1978
|
p5.cancel("Migration cancelled");
|
|
993
1979
|
process.exit(0);
|
|
994
1980
|
}
|
|
995
|
-
outputDir =
|
|
1981
|
+
outputDir = join8(cwd, customDir);
|
|
996
1982
|
}
|
|
997
|
-
if (
|
|
1983
|
+
if (existsSync8(outputDir)) {
|
|
998
1984
|
const shouldOverwrite = await p5.confirm({
|
|
999
1985
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
1000
1986
|
initialValue: false
|
|
@@ -1004,10 +1990,10 @@ async function scopeMigrateCommand(options) {
|
|
|
1004
1990
|
process.exit(0);
|
|
1005
1991
|
}
|
|
1006
1992
|
}
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1993
|
+
const spinner5 = p5.spinner();
|
|
1994
|
+
spinner5.start("Migrating scopes to package...");
|
|
1009
1995
|
try {
|
|
1010
|
-
await
|
|
1996
|
+
await mkdir5(join8(outputDir, "src"), { recursive: true });
|
|
1011
1997
|
const packageJson = {
|
|
1012
1998
|
name: `@workflow/scopes-${packageName}`,
|
|
1013
1999
|
version: "1.0.0",
|
|
@@ -1047,8 +2033,8 @@ async function scopeMigrateCommand(options) {
|
|
|
1047
2033
|
access: "public"
|
|
1048
2034
|
}
|
|
1049
2035
|
};
|
|
1050
|
-
await
|
|
1051
|
-
|
|
2036
|
+
await writeFile5(
|
|
2037
|
+
join8(outputDir, "package.json"),
|
|
1052
2038
|
JSON.stringify(packageJson, null, 2),
|
|
1053
2039
|
"utf-8"
|
|
1054
2040
|
);
|
|
@@ -1060,8 +2046,8 @@ async function scopeMigrateCommand(options) {
|
|
|
1060
2046
|
},
|
|
1061
2047
|
include: ["src/**/*"]
|
|
1062
2048
|
};
|
|
1063
|
-
await
|
|
1064
|
-
|
|
2049
|
+
await writeFile5(
|
|
2050
|
+
join8(outputDir, "tsconfig.json"),
|
|
1065
2051
|
JSON.stringify(tsconfig, null, 2),
|
|
1066
2052
|
"utf-8"
|
|
1067
2053
|
);
|
|
@@ -1075,7 +2061,7 @@ export default defineConfig({
|
|
|
1075
2061
|
sourcemap: true,
|
|
1076
2062
|
});
|
|
1077
2063
|
`;
|
|
1078
|
-
await
|
|
2064
|
+
await writeFile5(join8(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
1079
2065
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
1080
2066
|
|
|
1081
2067
|
export const scopes: Scope[] = ${JSON.stringify(config.scopes, null, 2)};
|
|
@@ -1089,7 +2075,7 @@ export const preset = {
|
|
|
1089
2075
|
|
|
1090
2076
|
export default preset;
|
|
1091
2077
|
`;
|
|
1092
|
-
await
|
|
2078
|
+
await writeFile5(join8(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
1093
2079
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
1094
2080
|
import { scopes, preset } from './index.js';
|
|
1095
2081
|
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
@@ -1132,11 +2118,11 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
1132
2118
|
});
|
|
1133
2119
|
});
|
|
1134
2120
|
`;
|
|
1135
|
-
await
|
|
1136
|
-
|
|
1137
|
-
if (
|
|
1138
|
-
const workspaceFile =
|
|
1139
|
-
const workspaceContent = await
|
|
2121
|
+
await writeFile5(join8(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
2122
|
+
spinner5.stop("\u2713 Package created from migrated scopes");
|
|
2123
|
+
if (isMonorepo2) {
|
|
2124
|
+
const workspaceFile = join8(cwd, "pnpm-workspace.yaml");
|
|
2125
|
+
const workspaceContent = await readFile5(workspaceFile, "utf-8");
|
|
1140
2126
|
const packagePath = `packages/scopes-${packageName}`;
|
|
1141
2127
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
1142
2128
|
console.log(chalk8.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:"));
|
|
@@ -1150,7 +2136,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
1150
2136
|
initialValue: false
|
|
1151
2137
|
});
|
|
1152
2138
|
if (!p5.isCancel(keepConfig) && !keepConfig) {
|
|
1153
|
-
const configPath =
|
|
2139
|
+
const configPath = join8(cwd, "workflow.config.json");
|
|
1154
2140
|
const updatedConfig = {
|
|
1155
2141
|
...config,
|
|
1156
2142
|
scopes: [],
|
|
@@ -1158,7 +2144,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
1158
2144
|
preset: `scopes-${packageName}`
|
|
1159
2145
|
// Reference the new package
|
|
1160
2146
|
};
|
|
1161
|
-
await
|
|
2147
|
+
await writeFile5(configPath, JSON.stringify(updatedConfig, null, 2), "utf-8");
|
|
1162
2148
|
console.log(chalk8.green("\u2713 Updated workflow.config.json"));
|
|
1163
2149
|
console.log(chalk8.dim(" \u2022 Cleared inline scopes"));
|
|
1164
2150
|
console.log(chalk8.dim(` \u2022 Added preset reference: scopes-${packageName}
|
|
@@ -1185,12 +2171,249 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
1185
2171
|
}
|
|
1186
2172
|
console.log(chalk8.dim("\u{1F4A1} Tip: You can now reuse this scope package across multiple projects!\n"));
|
|
1187
2173
|
} catch (error) {
|
|
1188
|
-
|
|
2174
|
+
spinner5.stop("\u2717 Migration failed");
|
|
1189
2175
|
console.error(chalk8.red("\nError:"), error);
|
|
1190
2176
|
process.exit(1);
|
|
1191
2177
|
}
|
|
1192
2178
|
}
|
|
1193
2179
|
|
|
2180
|
+
// src/cli/commands/hooks.ts
|
|
2181
|
+
import chalk9 from "chalk";
|
|
2182
|
+
async function hooksCommand(action) {
|
|
2183
|
+
const cwd = process.cwd();
|
|
2184
|
+
switch (action) {
|
|
2185
|
+
case "install":
|
|
2186
|
+
await installHooksAction(cwd);
|
|
2187
|
+
break;
|
|
2188
|
+
case "uninstall":
|
|
2189
|
+
await uninstallHooksAction(cwd);
|
|
2190
|
+
break;
|
|
2191
|
+
case "status":
|
|
2192
|
+
await statusHooksAction(cwd);
|
|
2193
|
+
break;
|
|
2194
|
+
default:
|
|
2195
|
+
console.error(chalk9.red(`Unknown action: ${action}`));
|
|
2196
|
+
console.log(chalk9.dim("Available actions: install, uninstall, status"));
|
|
2197
|
+
process.exit(1);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
async function installHooksAction(cwd) {
|
|
2201
|
+
console.log(chalk9.bold.cyan("\n\u{1F517} Installing Workflow Agent Git Hooks\n"));
|
|
2202
|
+
if (!hasGitRepo(cwd)) {
|
|
2203
|
+
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2204
|
+
console.log(chalk9.yellow(" Run: git init"));
|
|
2205
|
+
process.exit(1);
|
|
2206
|
+
}
|
|
2207
|
+
const config = await loadConfig();
|
|
2208
|
+
const hooksConfig = config?.hooks;
|
|
2209
|
+
const results = await installHooks(hooksConfig, cwd);
|
|
2210
|
+
let hasErrors = false;
|
|
2211
|
+
for (const result of results) {
|
|
2212
|
+
if (result.success) {
|
|
2213
|
+
if (result.wrappedExisting) {
|
|
2214
|
+
console.log(chalk9.green(`\u2713 Installed ${result.hookType} hook (wrapped existing hook)`));
|
|
2215
|
+
} else {
|
|
2216
|
+
console.log(chalk9.green(`\u2713 Installed ${result.hookType} hook`));
|
|
2217
|
+
}
|
|
2218
|
+
} else {
|
|
2219
|
+
console.error(chalk9.red(`\u2717 Failed to install ${result.hookType}: ${result.error}`));
|
|
2220
|
+
hasErrors = true;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
if (!hasErrors) {
|
|
2224
|
+
console.log(chalk9.green("\n\u2713 Git hooks installed successfully"));
|
|
2225
|
+
console.log(chalk9.dim("\nHooks will run automatically on commit."));
|
|
2226
|
+
console.log(chalk9.dim("They will be skipped in CI environments."));
|
|
2227
|
+
} else {
|
|
2228
|
+
process.exit(1);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
async function uninstallHooksAction(cwd) {
|
|
2232
|
+
console.log(chalk9.bold.cyan("\n\u{1F513} Uninstalling Workflow Agent Git Hooks\n"));
|
|
2233
|
+
if (!hasGitRepo(cwd)) {
|
|
2234
|
+
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2235
|
+
process.exit(1);
|
|
2236
|
+
}
|
|
2237
|
+
const results = await uninstallHooks(cwd);
|
|
2238
|
+
let hasErrors = false;
|
|
2239
|
+
for (const result of results) {
|
|
2240
|
+
if (result.success) {
|
|
2241
|
+
if (result.wrappedExisting) {
|
|
2242
|
+
console.log(chalk9.green(`\u2713 Removed ${result.hookType} hook (restored original)`));
|
|
2243
|
+
} else {
|
|
2244
|
+
console.log(chalk9.green(`\u2713 Removed ${result.hookType} hook`));
|
|
2245
|
+
}
|
|
2246
|
+
} else if (result.error) {
|
|
2247
|
+
console.error(chalk9.red(`\u2717 Failed to remove ${result.hookType}: ${result.error}`));
|
|
2248
|
+
hasErrors = true;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
if (!hasErrors) {
|
|
2252
|
+
console.log(chalk9.green("\n\u2713 Git hooks uninstalled successfully"));
|
|
2253
|
+
} else {
|
|
2254
|
+
process.exit(1);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
async function statusHooksAction(cwd) {
|
|
2258
|
+
console.log(chalk9.bold.cyan("\n\u{1F4CA} Workflow Agent Git Hooks Status\n"));
|
|
2259
|
+
if (!hasGitRepo(cwd)) {
|
|
2260
|
+
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
const statuses = await getAllHooksStatus(cwd);
|
|
2264
|
+
for (const status of statuses) {
|
|
2265
|
+
const icon = status.installed ? "\u2713" : "\u2717";
|
|
2266
|
+
const color = status.installed ? chalk9.green : chalk9.yellow;
|
|
2267
|
+
let message = `${icon} ${status.hookType}`;
|
|
2268
|
+
if (status.installed) {
|
|
2269
|
+
message += " - installed";
|
|
2270
|
+
if (status.wrappedOriginal) {
|
|
2271
|
+
message += " (wrapping original hook)";
|
|
2272
|
+
}
|
|
2273
|
+
} else if (status.hasExistingHook) {
|
|
2274
|
+
message += " - existing hook (not managed by Workflow Agent)";
|
|
2275
|
+
} else {
|
|
2276
|
+
message += " - not installed";
|
|
2277
|
+
}
|
|
2278
|
+
console.log(color(message));
|
|
2279
|
+
}
|
|
2280
|
+
const allInstalled = statuses.every((s) => s.installed);
|
|
2281
|
+
if (!allInstalled) {
|
|
2282
|
+
console.log(chalk9.dim("\nTo install hooks: workflow hooks install"));
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/cli/commands/github-actions.ts
|
|
2287
|
+
import chalk10 from "chalk";
|
|
2288
|
+
import * as p6 from "@clack/prompts";
|
|
2289
|
+
async function githubCommand(action) {
|
|
2290
|
+
const cwd = process.cwd();
|
|
2291
|
+
switch (action) {
|
|
2292
|
+
case "setup":
|
|
2293
|
+
await setupAction(cwd);
|
|
2294
|
+
break;
|
|
2295
|
+
case "check":
|
|
2296
|
+
await checkAction(cwd);
|
|
2297
|
+
break;
|
|
2298
|
+
default:
|
|
2299
|
+
console.error(chalk10.red(`Unknown action: ${action}`));
|
|
2300
|
+
console.log(chalk10.dim("Available actions: setup, check"));
|
|
2301
|
+
process.exit(1);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
async function setupAction(cwd) {
|
|
2305
|
+
console.log(chalk10.bold.cyan("\n\u{1F527} Setting Up GitHub Actions CI\n"));
|
|
2306
|
+
const repoInfo = await getRepoInfo(cwd);
|
|
2307
|
+
if (!repoInfo.isGitRepo) {
|
|
2308
|
+
console.error(chalk10.red("\u2717 No git repository found"));
|
|
2309
|
+
console.log(chalk10.yellow(" Run: git init"));
|
|
2310
|
+
process.exit(1);
|
|
2311
|
+
}
|
|
2312
|
+
if (!repoInfo.isGitHub) {
|
|
2313
|
+
console.log(chalk10.yellow("\u26A0\uFE0F No GitHub remote detected"));
|
|
2314
|
+
const shouldContinue = await p6.confirm({
|
|
2315
|
+
message: "Create GitHub Actions workflow anyway?",
|
|
2316
|
+
initialValue: true
|
|
2317
|
+
});
|
|
2318
|
+
if (p6.isCancel(shouldContinue) || !shouldContinue) {
|
|
2319
|
+
p6.cancel("Setup cancelled");
|
|
2320
|
+
process.exit(0);
|
|
2321
|
+
}
|
|
2322
|
+
} else {
|
|
2323
|
+
console.log(chalk10.dim(`Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`));
|
|
2324
|
+
}
|
|
2325
|
+
if (hasCIWorkflow(cwd)) {
|
|
2326
|
+
const shouldOverwrite = await p6.confirm({
|
|
2327
|
+
message: "CI workflow already exists. Overwrite?",
|
|
2328
|
+
initialValue: false
|
|
2329
|
+
});
|
|
2330
|
+
if (p6.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
2331
|
+
p6.cancel("Setup cancelled");
|
|
2332
|
+
process.exit(0);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
const projectInfo = await getProjectInfo(cwd);
|
|
2336
|
+
console.log(chalk10.dim(`Package manager: ${projectInfo.packageManager}`));
|
|
2337
|
+
console.log(chalk10.dim(`Monorepo: ${projectInfo.isMonorepo ? "yes" : "no"}`));
|
|
2338
|
+
const config = await loadConfig();
|
|
2339
|
+
const ciConfig = config?.ci;
|
|
2340
|
+
const spinner5 = p6.spinner();
|
|
2341
|
+
spinner5.start("Creating CI workflow...");
|
|
2342
|
+
const result = await createCIWorkflow({
|
|
2343
|
+
projectPath: cwd,
|
|
2344
|
+
packageManager: projectInfo.packageManager,
|
|
2345
|
+
isMonorepo: projectInfo.isMonorepo,
|
|
2346
|
+
ciConfig,
|
|
2347
|
+
defaultBranch: repoInfo.defaultBranch || "main"
|
|
2348
|
+
});
|
|
2349
|
+
if (result.success) {
|
|
2350
|
+
spinner5.stop(chalk10.green("\u2713 Created CI workflow"));
|
|
2351
|
+
console.log(chalk10.dim(` File: ${result.filePath}`));
|
|
2352
|
+
console.log(chalk10.green("\n\u2713 GitHub Actions CI setup complete"));
|
|
2353
|
+
console.log(chalk10.dim("\nThe workflow will run on:"));
|
|
2354
|
+
console.log(chalk10.dim(" \u2022 Push to main/develop branches"));
|
|
2355
|
+
console.log(chalk10.dim(" \u2022 Pull requests to main/develop branches"));
|
|
2356
|
+
console.log(chalk10.dim("\nChecks included:"));
|
|
2357
|
+
const checks = ciConfig?.checks || ["lint", "typecheck", "format", "build", "test"];
|
|
2358
|
+
for (const check of checks) {
|
|
2359
|
+
const hasScript = check === "lint" ? projectInfo.hasLintScript : check === "typecheck" ? projectInfo.hasTypecheckScript : check === "format" ? projectInfo.hasFormatScript : check === "test" ? projectInfo.hasTestScript : check === "build" ? projectInfo.hasBuildScript : false;
|
|
2360
|
+
const status = hasScript ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
2361
|
+
const note = hasScript ? "" : " (add script to package.json)";
|
|
2362
|
+
console.log(chalk10.dim(` ${status} ${check}${note}`));
|
|
2363
|
+
}
|
|
2364
|
+
console.log(chalk10.cyan("\n\u{1F4A1} Recommended: Enable branch protection"));
|
|
2365
|
+
console.log(chalk10.dim(" Go to GitHub \u2192 Settings \u2192 Branches \u2192 Add rule"));
|
|
2366
|
+
console.log(chalk10.dim(' Enable "Require status checks to pass before merging"'));
|
|
2367
|
+
console.log(chalk10.dim(' Select the "ci" status check'));
|
|
2368
|
+
} else {
|
|
2369
|
+
spinner5.stop(chalk10.red("\u2717 Failed to create CI workflow"));
|
|
2370
|
+
console.error(chalk10.red(` Error: ${result.error}`));
|
|
2371
|
+
process.exit(1);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
async function checkAction(cwd) {
|
|
2375
|
+
console.log(chalk10.bold.cyan("\n\u{1F50D} Checking GitHub Actions CI Setup\n"));
|
|
2376
|
+
const result = await validateGitHubActionsSetup(cwd);
|
|
2377
|
+
if (result.hasWorkflowFile) {
|
|
2378
|
+
console.log(chalk10.green("\u2713 CI workflow file found"));
|
|
2379
|
+
} else {
|
|
2380
|
+
console.log(chalk10.red("\u2717 No CI workflow file found"));
|
|
2381
|
+
console.log(chalk10.yellow(" Run: workflow github:setup"));
|
|
2382
|
+
process.exit(1);
|
|
2383
|
+
}
|
|
2384
|
+
const checks = [
|
|
2385
|
+
{ name: "Lint", present: result.hasLintCheck },
|
|
2386
|
+
{ name: "Type check", present: result.hasTypecheckCheck },
|
|
2387
|
+
{ name: "Format", present: result.hasFormatCheck },
|
|
2388
|
+
{ name: "Build", present: result.hasBuildCheck },
|
|
2389
|
+
{ name: "Test", present: result.hasTestCheck }
|
|
2390
|
+
];
|
|
2391
|
+
console.log(chalk10.dim("\nCI checks:"));
|
|
2392
|
+
for (const check of checks) {
|
|
2393
|
+
const icon = check.present ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
2394
|
+
console.log(` ${icon} ${check.name}`);
|
|
2395
|
+
}
|
|
2396
|
+
if (result.errors.length > 0) {
|
|
2397
|
+
console.log(chalk10.red("\nErrors:"));
|
|
2398
|
+
for (const error of result.errors) {
|
|
2399
|
+
console.log(chalk10.red(` \u2022 ${error}`));
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
if (result.warnings.length > 0) {
|
|
2403
|
+
console.log(chalk10.yellow("\nWarnings:"));
|
|
2404
|
+
for (const warning of result.warnings) {
|
|
2405
|
+
console.log(chalk10.yellow(` \u2022 ${warning}`));
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (result.valid) {
|
|
2409
|
+
console.log(chalk10.green("\n\u2713 CI setup is valid"));
|
|
2410
|
+
} else {
|
|
2411
|
+
console.log(chalk10.red("\n\u2717 CI setup has issues"));
|
|
2412
|
+
console.log(chalk10.yellow(" Run: workflow github:setup"));
|
|
2413
|
+
process.exit(1);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
1194
2417
|
// src/cli/index.ts
|
|
1195
2418
|
var program = new Command();
|
|
1196
2419
|
program.name("workflow").description("A self-evolving workflow management system for AI agent development").version("1.0.0");
|
|
@@ -1199,7 +2422,9 @@ program.command("validate <type>").description("Validate branch name, commit mes
|
|
|
1199
2422
|
program.command("config <action>").description("Manage workflow configuration").argument("<action>", "Action: get, set, add, remove").argument("[key]", "Config key").argument("[value]", "Config value").action(configCommand);
|
|
1200
2423
|
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option("--category <category>", "Category: feature, bug, documentation, performance, other").action(suggestCommand);
|
|
1201
2424
|
program.command("setup").description("Add workflow scripts to package.json").action(setupCommand);
|
|
1202
|
-
program.command("doctor").description("Run health check and get optimization suggestions").action(doctorCommand);
|
|
2425
|
+
program.command("doctor").description("Run health check and get optimization suggestions").option("--check-guidelines-only", "Only check mandatory guidelines exist (exits 0 or 1)").action(doctorCommand);
|
|
2426
|
+
program.command("hooks").description("Manage git hooks (install, uninstall, status)").argument("<action>", "Action: install, uninstall, status").action(hooksCommand);
|
|
2427
|
+
program.command("github").description("Manage GitHub Actions CI (setup, check)").argument("<action>", "Action: setup, check").action(githubCommand);
|
|
1203
2428
|
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option("--scopes <scopes>", "Comma-separated scopes (format: name:description:emoji:category)").option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
1204
2429
|
program.command("scope:migrate").description("Migrate inline scopes to a custom package").option("--name <name>", "Package name for the preset").option("--output-dir <dir>", "Output directory").option("--keep-config", "Keep inline scopes in config after migration").action(scopeMigrateCommand);
|
|
1205
2430
|
program.parse();
|