workflow-agent-cli 2.1.0 → 2.2.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/README.md +33 -17
- package/dist/{chunk-B27W7GWP.js → chunk-JCDT4363.js} +102 -22
- package/dist/chunk-JCDT4363.js.map +1 -0
- package/dist/{chunk-X2NQJ2ZY.js → chunk-NMHWD2GA.js} +11 -6
- package/dist/chunk-NMHWD2GA.js.map +1 -0
- package/dist/cli/index.js +913 -137
- 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 +2 -2
- package/dist/schema-Bdv6BQqI.d.ts +437 -0
- package/dist/scripts/postinstall.js +6 -2
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/validators/index.d.ts +1 -1
- package/dist/validators/index.js +1 -1
- package/package.json +2 -2
- package/templates/AGENT_EDITING_INSTRUCTIONS.md +226 -39
- package/templates/COMPONENT_LIBRARY.md +23 -17
- package/templates/CUSTOM_SCOPE_TEMPLATE.md +5 -4
- package/templates/LIBRARY_INVENTORY.md +20 -20
- package/templates/SCOPE_CREATION_WORKFLOW.md +39 -11
- package/templates/SELF_IMPROVEMENT_MANDATE.md +24 -18
- package/templates/SINGLE_SOURCE_OF_TRUTH.md +59 -42
- package/templates/TESTING_STRATEGY.md +79 -69
- package/templates/_TEMPLATE_EXAMPLE.md +2 -1
- package/dist/chunk-B27W7GWP.js.map +0 -1
- package/dist/chunk-X2NQJ2ZY.js.map +0 -1
- package/dist/schema-C1lmnd7L.d.ts +0 -256
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
validateBranchName,
|
|
4
4
|
validateCommitMessage,
|
|
5
5
|
validatePRTitle
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-NMHWD2GA.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_RESERVED_SCOPE_NAMES,
|
|
9
9
|
hasConfig,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
validateConfig,
|
|
12
12
|
validateScopeDefinitions,
|
|
13
13
|
validateScopeName
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-JCDT4363.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/index.ts
|
|
17
17
|
import { Command as Command2 } from "commander";
|
|
@@ -79,7 +79,7 @@ var adapters = {
|
|
|
79
79
|
config: "src"
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
-
|
|
82
|
+
remix: {
|
|
83
83
|
name: "Remix",
|
|
84
84
|
description: "Remix full-stack framework",
|
|
85
85
|
detect: async () => {
|
|
@@ -94,7 +94,7 @@ var adapters = {
|
|
|
94
94
|
config: "app/root.tsx"
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
|
-
|
|
97
|
+
astro: {
|
|
98
98
|
name: "Astro",
|
|
99
99
|
description: "Astro static site framework",
|
|
100
100
|
detect: async () => {
|
|
@@ -109,7 +109,7 @@ var adapters = {
|
|
|
109
109
|
config: "src/pages"
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
|
-
|
|
112
|
+
sveltekit: {
|
|
113
113
|
name: "SvelteKit",
|
|
114
114
|
description: "SvelteKit full-stack framework",
|
|
115
115
|
detect: async () => {
|
|
@@ -124,7 +124,7 @@ var adapters = {
|
|
|
124
124
|
config: "src/routes"
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
|
-
|
|
127
|
+
generic: {
|
|
128
128
|
name: "Generic Project",
|
|
129
129
|
description: "Standard project structure",
|
|
130
130
|
detect: async () => true,
|
|
@@ -210,7 +210,9 @@ async function validateTemplateDirectory(templateDir) {
|
|
|
210
210
|
const files = await fs.readdir(templateDir);
|
|
211
211
|
const templateFiles = files.filter((f) => f.match(/\.(md|ts|json)$/));
|
|
212
212
|
if (templateFiles.length === 0) {
|
|
213
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`No template files found in template directory: ${templateDir}`
|
|
215
|
+
);
|
|
214
216
|
}
|
|
215
217
|
} catch (error) {
|
|
216
218
|
if (error.code === "ENOENT") {
|
|
@@ -470,12 +472,14 @@ async function installSingleHook(hookType, config, projectPath = process.cwd())
|
|
|
470
472
|
}
|
|
471
473
|
async function installHooks(config, projectPath = process.cwd()) {
|
|
472
474
|
if (!hasGitRepo(projectPath)) {
|
|
473
|
-
return [
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
475
|
+
return [
|
|
476
|
+
{
|
|
477
|
+
success: false,
|
|
478
|
+
hookType: "pre-commit",
|
|
479
|
+
wrappedExisting: false,
|
|
480
|
+
error: "No git repository found. Run git init first."
|
|
481
|
+
}
|
|
482
|
+
];
|
|
479
483
|
}
|
|
480
484
|
const results = await Promise.all([
|
|
481
485
|
installSingleHook("pre-commit", config, projectPath),
|
|
@@ -535,7 +539,9 @@ async function isGitRepo(projectPath = process.cwd()) {
|
|
|
535
539
|
}
|
|
536
540
|
async function getGitRemoteUrl(projectPath = process.cwd()) {
|
|
537
541
|
try {
|
|
538
|
-
const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
|
|
542
|
+
const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
|
|
543
|
+
cwd: projectPath
|
|
544
|
+
});
|
|
539
545
|
return stdout.trim() || null;
|
|
540
546
|
} catch {
|
|
541
547
|
return null;
|
|
@@ -558,15 +564,23 @@ function parseGitHubUrl(remoteUrl) {
|
|
|
558
564
|
}
|
|
559
565
|
async function getDefaultBranch(projectPath = process.cwd()) {
|
|
560
566
|
try {
|
|
561
|
-
const { stdout } = await execa(
|
|
562
|
-
|
|
563
|
-
|
|
567
|
+
const { stdout } = await execa(
|
|
568
|
+
"git",
|
|
569
|
+
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
570
|
+
{
|
|
571
|
+
cwd: projectPath
|
|
572
|
+
}
|
|
573
|
+
);
|
|
564
574
|
return stdout.trim().replace("refs/remotes/origin/", "") || "main";
|
|
565
575
|
} catch {
|
|
566
576
|
try {
|
|
567
|
-
const { stdout } = await execa(
|
|
568
|
-
|
|
569
|
-
|
|
577
|
+
const { stdout } = await execa(
|
|
578
|
+
"git",
|
|
579
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
580
|
+
{
|
|
581
|
+
cwd: projectPath
|
|
582
|
+
}
|
|
583
|
+
);
|
|
570
584
|
return stdout.trim() || "main";
|
|
571
585
|
} catch {
|
|
572
586
|
return "main";
|
|
@@ -758,7 +772,11 @@ function generateCIWorkflowContent(options) {
|
|
|
758
772
|
}
|
|
759
773
|
if (checks.includes("typecheck")) {
|
|
760
774
|
if (hasTypecheckScript) {
|
|
761
|
-
const typecheckCmd = getRunCommand(
|
|
775
|
+
const typecheckCmd = getRunCommand(
|
|
776
|
+
packageManager,
|
|
777
|
+
"typecheck",
|
|
778
|
+
isMonorepo2
|
|
779
|
+
);
|
|
762
780
|
steps.push(`
|
|
763
781
|
- name: Type check
|
|
764
782
|
run: ${typecheckCmd}`);
|
|
@@ -770,7 +788,11 @@ function generateCIWorkflowContent(options) {
|
|
|
770
788
|
}
|
|
771
789
|
if (checks.includes("format")) {
|
|
772
790
|
if (hasFormatScript) {
|
|
773
|
-
const formatCmd = getRunCommand(
|
|
791
|
+
const formatCmd = getRunCommand(
|
|
792
|
+
packageManager,
|
|
793
|
+
"format:check",
|
|
794
|
+
isMonorepo2
|
|
795
|
+
);
|
|
774
796
|
steps.push(`
|
|
775
797
|
- name: Format check
|
|
776
798
|
run: ${formatCmd} || npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
@@ -844,7 +866,13 @@ async function createCIWorkflow(options = {}) {
|
|
|
844
866
|
const projectInfo = await getProjectInfo(projectPath);
|
|
845
867
|
const packageManager = options.packageManager || projectInfo.packageManager;
|
|
846
868
|
const isMonorepo2 = options.isMonorepo ?? projectInfo.isMonorepo;
|
|
847
|
-
const checks = options.ciConfig?.checks || [
|
|
869
|
+
const checks = options.ciConfig?.checks || [
|
|
870
|
+
"lint",
|
|
871
|
+
"typecheck",
|
|
872
|
+
"format",
|
|
873
|
+
"build",
|
|
874
|
+
"test"
|
|
875
|
+
];
|
|
848
876
|
const nodeVersions = options.nodeVersions || ["20"];
|
|
849
877
|
const defaultBranch = options.defaultBranch || "main";
|
|
850
878
|
if (!existsSync3(workflowsDir)) {
|
|
@@ -904,12 +932,30 @@ async function initCommand(options) {
|
|
|
904
932
|
const preset = isNonInteractive ? options.preset : await p.select({
|
|
905
933
|
message: "Choose a scope preset for your project:",
|
|
906
934
|
options: [
|
|
907
|
-
{
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
{
|
|
912
|
-
|
|
935
|
+
{
|
|
936
|
+
value: "saas",
|
|
937
|
+
label: "\u{1F4E6} SaaS Application - 17 scopes (auth, tasks, boards, sprints, etc.)"
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
value: "library",
|
|
941
|
+
label: "\u{1F4DA} Library/Package - 10 scopes (types, build, docs, examples, etc.)"
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
value: "api",
|
|
945
|
+
label: "\u{1F50C} API/Backend - 13 scopes (auth, endpoints, models, services, etc.)"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
value: "ecommerce",
|
|
949
|
+
label: "\u{1F6D2} E-commerce - 12 scopes (cart, products, payments, orders, etc.)"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
value: "cms",
|
|
953
|
+
label: "\u{1F4DD} CMS - 13 scopes (content, pages, media, editor, etc.)"
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
value: "custom",
|
|
957
|
+
label: "\u2728 Custom (define your own scopes manually)"
|
|
958
|
+
}
|
|
913
959
|
]
|
|
914
960
|
});
|
|
915
961
|
if (!isNonInteractive && p.isCancel(preset)) {
|
|
@@ -926,27 +972,62 @@ async function initCommand(options) {
|
|
|
926
972
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
927
973
|
spinner5.stop(`\u2713 Loaded ${scopes.length} scopes from preset`);
|
|
928
974
|
} catch (error) {
|
|
929
|
-
console.log(
|
|
930
|
-
|
|
975
|
+
console.log(
|
|
976
|
+
chalk.yellow(
|
|
977
|
+
`
|
|
978
|
+
\u26A0\uFE0F Could not load preset package. Using basic scopes.`
|
|
979
|
+
)
|
|
980
|
+
);
|
|
931
981
|
scopes = [
|
|
932
|
-
{
|
|
982
|
+
{
|
|
983
|
+
name: "feat",
|
|
984
|
+
description: "New features and enhancements",
|
|
985
|
+
emoji: "\u2728"
|
|
986
|
+
},
|
|
933
987
|
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
934
|
-
{
|
|
988
|
+
{
|
|
989
|
+
name: "documentation",
|
|
990
|
+
description: "Documentation updates and improvements",
|
|
991
|
+
emoji: "\u{1F4DA}"
|
|
992
|
+
}
|
|
935
993
|
];
|
|
936
994
|
}
|
|
937
995
|
} else {
|
|
938
996
|
scopes = [
|
|
939
|
-
{
|
|
997
|
+
{
|
|
998
|
+
name: "feat",
|
|
999
|
+
description: "New features and enhancements",
|
|
1000
|
+
emoji: "\u2728"
|
|
1001
|
+
},
|
|
940
1002
|
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
941
|
-
{
|
|
1003
|
+
{
|
|
1004
|
+
name: "documentation",
|
|
1005
|
+
description: "Documentation updates and improvements",
|
|
1006
|
+
emoji: "\u{1F4DA}"
|
|
1007
|
+
}
|
|
942
1008
|
];
|
|
943
|
-
console.log(
|
|
1009
|
+
console.log(
|
|
1010
|
+
chalk.dim(
|
|
1011
|
+
"\n\u{1F4A1} Tip: Edit workflow.config.json to add your custom scopes"
|
|
1012
|
+
)
|
|
1013
|
+
);
|
|
944
1014
|
}
|
|
945
1015
|
const config = {
|
|
946
1016
|
projectName,
|
|
947
1017
|
scopes,
|
|
948
1018
|
enforcement: "strict",
|
|
949
|
-
language: "en"
|
|
1019
|
+
language: "en",
|
|
1020
|
+
hooks: {
|
|
1021
|
+
enabled: true,
|
|
1022
|
+
preCommit: ["validate-branch", "check-guidelines"],
|
|
1023
|
+
commitMsg: ["validate-commit"]
|
|
1024
|
+
},
|
|
1025
|
+
ci: {
|
|
1026
|
+
enabled: true,
|
|
1027
|
+
provider: "github",
|
|
1028
|
+
checks: ["lint", "typecheck", "format", "build", "test"]
|
|
1029
|
+
},
|
|
1030
|
+
reservedScopeNames: DEFAULT_RESERVED_SCOPE_NAMES
|
|
950
1031
|
};
|
|
951
1032
|
const configPath = join4(cwd, "workflow.config.json");
|
|
952
1033
|
await writeFile3(configPath, JSON.stringify(config, null, 2));
|
|
@@ -958,8 +1039,12 @@ async function initCommand(options) {
|
|
|
958
1039
|
const projectInfo = await getProjectInfo(cwd);
|
|
959
1040
|
const mandatoryTemplates = getMandatoryTemplates();
|
|
960
1041
|
const optionalTemplates = getOptionalTemplates();
|
|
961
|
-
console.log(
|
|
962
|
-
|
|
1042
|
+
console.log(
|
|
1043
|
+
chalk.dim(
|
|
1044
|
+
`
|
|
1045
|
+
\u{1F4CB} Generating ${mandatoryTemplates.length} mandatory guidelines...`
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
963
1048
|
const guidelinesDir = join4(cwd, "guidelines");
|
|
964
1049
|
const templatesDir = join4(__dirname, "../../templates");
|
|
965
1050
|
let mandatoryGenerated = 0;
|
|
@@ -972,13 +1057,20 @@ async function initCommand(options) {
|
|
|
972
1057
|
try {
|
|
973
1058
|
const templatePath = join4(templatesDir, template.filename);
|
|
974
1059
|
const outputPath = join4(guidelinesDir, template.filename);
|
|
975
|
-
await renderTemplateFile(templatePath, outputPath,
|
|
1060
|
+
await renderTemplateFile(templatePath, outputPath, {
|
|
1061
|
+
...context,
|
|
1062
|
+
reservedScopeNames: (config.reservedScopeNames || []).join(", ")
|
|
1063
|
+
});
|
|
976
1064
|
mandatoryGenerated++;
|
|
977
1065
|
} catch (error) {
|
|
978
|
-
console.log(
|
|
1066
|
+
console.log(
|
|
1067
|
+
chalk.yellow(` \u26A0\uFE0F Could not generate ${template.filename}`)
|
|
1068
|
+
);
|
|
979
1069
|
}
|
|
980
1070
|
}
|
|
981
|
-
console.log(
|
|
1071
|
+
console.log(
|
|
1072
|
+
chalk.green(`\u2713 Generated ${mandatoryGenerated} mandatory guidelines`)
|
|
1073
|
+
);
|
|
982
1074
|
let shouldGenerateOptional = isNonInteractive;
|
|
983
1075
|
if (!isNonInteractive) {
|
|
984
1076
|
const response = await p.confirm({
|
|
@@ -997,11 +1089,17 @@ async function initCommand(options) {
|
|
|
997
1089
|
} catch {
|
|
998
1090
|
}
|
|
999
1091
|
}
|
|
1000
|
-
console.log(
|
|
1092
|
+
console.log(
|
|
1093
|
+
chalk.green(`\u2713 Generated ${optionalGenerated} optional guidelines`)
|
|
1094
|
+
);
|
|
1001
1095
|
}
|
|
1002
1096
|
} catch (error) {
|
|
1003
|
-
console.log(
|
|
1004
|
-
|
|
1097
|
+
console.log(
|
|
1098
|
+
chalk.yellow(
|
|
1099
|
+
`
|
|
1100
|
+
\u26A0\uFE0F Could not generate guidelines: ${error instanceof Error ? error.message : String(error)}`
|
|
1101
|
+
)
|
|
1102
|
+
);
|
|
1005
1103
|
console.log(chalk.dim("You can manually copy guidelines later if needed."));
|
|
1006
1104
|
}
|
|
1007
1105
|
if (hasGitRepo(cwd)) {
|
|
@@ -1016,7 +1114,14 @@ async function initCommand(options) {
|
|
|
1016
1114
|
if (shouldInstallHooks) {
|
|
1017
1115
|
const hookSpinner = p.spinner();
|
|
1018
1116
|
hookSpinner.start("Installing git hooks...");
|
|
1019
|
-
const hookResults = await installHooks(
|
|
1117
|
+
const hookResults = await installHooks(
|
|
1118
|
+
config.hooks || {
|
|
1119
|
+
enabled: true,
|
|
1120
|
+
preCommit: ["validate-branch", "check-guidelines"],
|
|
1121
|
+
commitMsg: ["validate-commit"]
|
|
1122
|
+
},
|
|
1123
|
+
cwd
|
|
1124
|
+
);
|
|
1020
1125
|
const allSuccess = hookResults.every((r) => r.success);
|
|
1021
1126
|
if (allSuccess) {
|
|
1022
1127
|
hookSpinner.stop("\u2713 Installed git hooks");
|
|
@@ -1028,19 +1133,29 @@ async function initCommand(options) {
|
|
|
1028
1133
|
if (repoInfo.isGitHub) {
|
|
1029
1134
|
const existingCI = hasCIWorkflow(cwd);
|
|
1030
1135
|
if (!existingCI) {
|
|
1031
|
-
console.log(
|
|
1136
|
+
console.log(
|
|
1137
|
+
chalk.dim(
|
|
1138
|
+
"\n\u{1F527} Setting up GitHub Actions CI (mandatory for GitHub repos)..."
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1032
1141
|
const ciResult = await createCIWorkflow({
|
|
1033
1142
|
projectPath: cwd,
|
|
1034
1143
|
packageManager: projectInfo.packageManager,
|
|
1035
1144
|
isMonorepo: projectInfo.isMonorepo,
|
|
1036
|
-
ciConfig: config.ci
|
|
1145
|
+
ciConfig: config.ci || {
|
|
1146
|
+
enabled: true,
|
|
1147
|
+
provider: "github",
|
|
1148
|
+
checks: ["lint", "typecheck", "format", "build", "test"]
|
|
1149
|
+
},
|
|
1037
1150
|
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1038
1151
|
});
|
|
1039
1152
|
if (ciResult.success) {
|
|
1040
1153
|
console.log(chalk.green("\u2713 Created GitHub Actions CI workflow"));
|
|
1041
1154
|
console.log(chalk.dim(` File: .github/workflows/ci.yml`));
|
|
1042
1155
|
} else {
|
|
1043
|
-
console.log(
|
|
1156
|
+
console.log(
|
|
1157
|
+
chalk.yellow(`\u26A0\uFE0F Could not create CI workflow: ${ciResult.error}`)
|
|
1158
|
+
);
|
|
1044
1159
|
}
|
|
1045
1160
|
} else {
|
|
1046
1161
|
console.log(chalk.dim("\n\u2713 GitHub Actions CI workflow already exists"));
|
|
@@ -1068,13 +1183,21 @@ async function initCommand(options) {
|
|
|
1068
1183
|
}
|
|
1069
1184
|
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
1070
1185
|
console.log(chalk.dim("\nNext steps:"));
|
|
1071
|
-
console.log(
|
|
1072
|
-
|
|
1186
|
+
console.log(
|
|
1187
|
+
chalk.dim(" 1. Review your configuration in workflow.config.json")
|
|
1188
|
+
);
|
|
1189
|
+
console.log(
|
|
1190
|
+
chalk.dim(" 2. Review generated guidelines in guidelines/ directory")
|
|
1191
|
+
);
|
|
1073
1192
|
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
1074
1193
|
console.log(chalk.dim(" 4. Run: workflow doctor (for health check)\n"));
|
|
1075
1194
|
if (repoInfo.isGitHub) {
|
|
1076
|
-
console.log(
|
|
1077
|
-
|
|
1195
|
+
console.log(
|
|
1196
|
+
chalk.cyan("\u{1F4A1} Recommended: Enable branch protection on GitHub")
|
|
1197
|
+
);
|
|
1198
|
+
console.log(
|
|
1199
|
+
chalk.dim(" Settings \u2192 Branches \u2192 Add rule \u2192 Require status checks\n")
|
|
1200
|
+
);
|
|
1078
1201
|
}
|
|
1079
1202
|
}
|
|
1080
1203
|
|
|
@@ -1084,7 +1207,9 @@ import { execa as execa2 } from "execa";
|
|
|
1084
1207
|
async function validateCommand(type, value, _options = {}) {
|
|
1085
1208
|
const config = await loadConfig();
|
|
1086
1209
|
if (!config) {
|
|
1087
|
-
console.error(
|
|
1210
|
+
console.error(
|
|
1211
|
+
chalk2.red("\u2717 No workflow configuration found. Run: workflow init")
|
|
1212
|
+
);
|
|
1088
1213
|
process.exit(1);
|
|
1089
1214
|
}
|
|
1090
1215
|
let targetValue = value;
|
|
@@ -1134,8 +1259,12 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1134
1259
|
if (enforcementLevel === "strict") {
|
|
1135
1260
|
process.exit(1);
|
|
1136
1261
|
} else {
|
|
1137
|
-
console.log(
|
|
1138
|
-
|
|
1262
|
+
console.log(
|
|
1263
|
+
chalk2.yellow(
|
|
1264
|
+
`
|
|
1265
|
+
\u26A0\uFE0F Advisory mode: validation failed but not blocking`
|
|
1266
|
+
)
|
|
1267
|
+
);
|
|
1139
1268
|
process.exit(0);
|
|
1140
1269
|
}
|
|
1141
1270
|
}
|
|
@@ -1153,37 +1282,45 @@ import prompts from "prompts";
|
|
|
1153
1282
|
import chalk3 from "chalk";
|
|
1154
1283
|
function createConfigCommand() {
|
|
1155
1284
|
const command = new Command("config").description("Manage workflow configuration").option("-f, --force", "Skip validation checks").option("--cwd <path>", "Working directory", process.cwd());
|
|
1156
|
-
command.command("validate").description("Validate workflow configuration").action(async (
|
|
1285
|
+
command.command("validate").description("Validate workflow configuration").action(async () => {
|
|
1157
1286
|
const opts = command.opts();
|
|
1158
1287
|
await validateConfigAction(opts);
|
|
1159
1288
|
});
|
|
1160
|
-
command.command("add").description("Add configuration items").argument("<type>", "Type to add (scope)").action(async (type
|
|
1289
|
+
command.command("add").description("Add configuration items").argument("<type>", "Type to add (scope)").action(async (type) => {
|
|
1161
1290
|
const opts = command.opts();
|
|
1162
1291
|
if (type === "scope") {
|
|
1163
1292
|
await addScopeAction(opts);
|
|
1164
1293
|
} else {
|
|
1165
|
-
console.error(
|
|
1294
|
+
console.error(
|
|
1295
|
+
chalk3.red(
|
|
1296
|
+
`Unknown type: ${type}. Currently only "scope" is supported.`
|
|
1297
|
+
)
|
|
1298
|
+
);
|
|
1166
1299
|
process.exit(1);
|
|
1167
1300
|
}
|
|
1168
1301
|
});
|
|
1169
|
-
command.command("remove").description("Remove configuration items").argument("<type>", "Type to remove (scope)").argument("<name>", "Name of the item to remove").action(async (type, name
|
|
1302
|
+
command.command("remove").description("Remove configuration items").argument("<type>", "Type to remove (scope)").argument("<name>", "Name of the item to remove").action(async (type, name) => {
|
|
1170
1303
|
const opts = command.opts();
|
|
1171
1304
|
if (type === "scope") {
|
|
1172
1305
|
await removeScopeAction(name, opts);
|
|
1173
1306
|
} else {
|
|
1174
|
-
console.error(
|
|
1307
|
+
console.error(
|
|
1308
|
+
chalk3.red(
|
|
1309
|
+
`Unknown type: ${type}. Currently only "scope" is supported.`
|
|
1310
|
+
)
|
|
1311
|
+
);
|
|
1175
1312
|
process.exit(1);
|
|
1176
1313
|
}
|
|
1177
1314
|
});
|
|
1178
|
-
command.command("list").description("List configuration items").argument("[type]", "Type to list (scopes, reserved, all)", "all").action(async (type
|
|
1315
|
+
command.command("list").description("List configuration items").argument("[type]", "Type to list (scopes, reserved, all)", "all").action(async (type) => {
|
|
1179
1316
|
const opts = command.opts();
|
|
1180
1317
|
await listConfigAction(type, opts);
|
|
1181
1318
|
});
|
|
1182
|
-
command.command("get").description("Get a configuration value").argument("<path>", "Configuration path (e.g., scopes[0].name)").action(async (path2
|
|
1319
|
+
command.command("get").description("Get a configuration value").argument("<path>", "Configuration path (e.g., scopes[0].name)").action(async (path2) => {
|
|
1183
1320
|
const opts = command.opts();
|
|
1184
1321
|
await getConfigValue(path2, opts);
|
|
1185
1322
|
});
|
|
1186
|
-
command.command("set").description("Set a configuration value").argument("<path>", "Configuration path (e.g., reservedScopeNames)").argument("<value>", "Value to set").action(async (path2, value
|
|
1323
|
+
command.command("set").description("Set a configuration value").argument("<path>", "Configuration path (e.g., reservedScopeNames)").argument("<value>", "Value to set").action(async (path2, value) => {
|
|
1187
1324
|
const opts = command.opts();
|
|
1188
1325
|
await setConfigValue(path2, value, opts);
|
|
1189
1326
|
});
|
|
@@ -1214,7 +1351,9 @@ async function addScopeAction(opts) {
|
|
|
1214
1351
|
const cwd = opts.cwd || process.cwd();
|
|
1215
1352
|
const configPath = join5(cwd, "workflow.config.json");
|
|
1216
1353
|
if (!existsSync5(configPath)) {
|
|
1217
|
-
console.error(
|
|
1354
|
+
console.error(
|
|
1355
|
+
chalk3.red("No workflow.config.json found. Run: workflow init")
|
|
1356
|
+
);
|
|
1218
1357
|
process.exit(1);
|
|
1219
1358
|
}
|
|
1220
1359
|
const config = await loadConfig(cwd);
|
|
@@ -1231,7 +1370,8 @@ async function addScopeAction(opts) {
|
|
|
1231
1370
|
message: "Scope name:",
|
|
1232
1371
|
validate: (value) => {
|
|
1233
1372
|
if (!value) return "Name is required";
|
|
1234
|
-
if (existingNames.includes(value))
|
|
1373
|
+
if (existingNames.includes(value))
|
|
1374
|
+
return `Scope "${value}" already exists`;
|
|
1235
1375
|
const validation = validateScopeName(value, reservedNames);
|
|
1236
1376
|
if (!validation.valid) {
|
|
1237
1377
|
return validation.error + (validation.suggestion ? ` Try: ${validation.suggestion}` : "");
|
|
@@ -1306,7 +1446,9 @@ async function addScopeAction(opts) {
|
|
|
1306
1446
|
console.log(chalk3.gray(` Description: ${response.description}`));
|
|
1307
1447
|
console.log(chalk3.gray(` Types: ${response.allowedTypes.join(", ")}`));
|
|
1308
1448
|
if (newScope.mandatoryGuidelines) {
|
|
1309
|
-
console.log(
|
|
1449
|
+
console.log(
|
|
1450
|
+
chalk3.gray(` Guidelines: ${newScope.mandatoryGuidelines.join(", ")}`)
|
|
1451
|
+
);
|
|
1310
1452
|
}
|
|
1311
1453
|
}
|
|
1312
1454
|
async function removeScopeAction(name, opts) {
|
|
@@ -1357,9 +1499,15 @@ async function listConfigAction(type, opts) {
|
|
|
1357
1499
|
console.log(chalk3.green(`
|
|
1358
1500
|
${index + 1}. ${scope.name}`));
|
|
1359
1501
|
console.log(chalk3.gray(` ${scope.description}`));
|
|
1360
|
-
console.log(
|
|
1502
|
+
console.log(
|
|
1503
|
+
chalk3.gray(` Types: ${scope.allowedTypes?.join(", ") || "all"}`)
|
|
1504
|
+
);
|
|
1361
1505
|
if (scope.mandatoryGuidelines && scope.mandatoryGuidelines.length > 0) {
|
|
1362
|
-
console.log(
|
|
1506
|
+
console.log(
|
|
1507
|
+
chalk3.gray(
|
|
1508
|
+
` Guidelines: ${scope.mandatoryGuidelines.join(", ")}`
|
|
1509
|
+
)
|
|
1510
|
+
);
|
|
1363
1511
|
}
|
|
1364
1512
|
});
|
|
1365
1513
|
}
|
|
@@ -1368,7 +1516,11 @@ async function listConfigAction(type, opts) {
|
|
|
1368
1516
|
const reserved = config.reservedScopeNames || DEFAULT_RESERVED_SCOPE_NAMES;
|
|
1369
1517
|
console.log(chalk3.blue("\n\u{1F6AB} Reserved Scope Names:"));
|
|
1370
1518
|
console.log(chalk3.gray(` ${reserved.join(", ")}`));
|
|
1371
|
-
console.log(
|
|
1519
|
+
console.log(
|
|
1520
|
+
chalk3.gray(
|
|
1521
|
+
'\n \u{1F4A1} Configure in workflow.config.json: "reservedScopeNames"'
|
|
1522
|
+
)
|
|
1523
|
+
);
|
|
1372
1524
|
}
|
|
1373
1525
|
}
|
|
1374
1526
|
async function getConfigValue(path2, opts) {
|
|
@@ -1408,7 +1560,9 @@ async function setConfigValue(path2, value, opts) {
|
|
|
1408
1560
|
const validation = await validateConfig(cwd);
|
|
1409
1561
|
if (!validation.valid) {
|
|
1410
1562
|
console.error(chalk3.red("\u2717 Invalid configuration after change:"));
|
|
1411
|
-
validation.errors.forEach(
|
|
1563
|
+
validation.errors.forEach(
|
|
1564
|
+
(err) => console.error(chalk3.red(` \u2022 ${err}`))
|
|
1565
|
+
);
|
|
1412
1566
|
console.log(chalk3.gray("\nUse --force to skip validation"));
|
|
1413
1567
|
process.exit(1);
|
|
1414
1568
|
}
|
|
@@ -1479,7 +1633,9 @@ async function suggestCommand(feedback, options = {}) {
|
|
|
1479
1633
|
console.log(chalk4.dim("\nYour suggestion will be:"));
|
|
1480
1634
|
console.log(chalk4.dim(" 1. Reviewed by the community"));
|
|
1481
1635
|
console.log(chalk4.dim(" 2. Prioritized based on impact"));
|
|
1482
|
-
console.log(
|
|
1636
|
+
console.log(
|
|
1637
|
+
chalk4.dim(" 3. Incorporated into future releases if approved\n")
|
|
1638
|
+
);
|
|
1483
1639
|
}
|
|
1484
1640
|
|
|
1485
1641
|
// src/cli/commands/doctor.ts
|
|
@@ -1503,7 +1659,9 @@ function getEffectiveMandatoryTemplates(guidelinesConfig) {
|
|
|
1503
1659
|
}
|
|
1504
1660
|
}
|
|
1505
1661
|
if (guidelinesConfig.optionalOverrides) {
|
|
1506
|
-
mandatory = mandatory.filter(
|
|
1662
|
+
mandatory = mandatory.filter(
|
|
1663
|
+
(t) => !guidelinesConfig.optionalOverrides.includes(t)
|
|
1664
|
+
);
|
|
1507
1665
|
}
|
|
1508
1666
|
return mandatory;
|
|
1509
1667
|
}
|
|
@@ -1520,7 +1678,9 @@ async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
|
1520
1678
|
const mandatory2 = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1521
1679
|
result.valid = false;
|
|
1522
1680
|
result.missingMandatory = mandatory2;
|
|
1523
|
-
result.errors.push(
|
|
1681
|
+
result.errors.push(
|
|
1682
|
+
"Guidelines directory does not exist. Run: workflow init"
|
|
1683
|
+
);
|
|
1524
1684
|
return result;
|
|
1525
1685
|
}
|
|
1526
1686
|
let existingFiles = [];
|
|
@@ -1528,7 +1688,9 @@ async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
|
1528
1688
|
existingFiles = await readdir(guidelinesDir);
|
|
1529
1689
|
} catch (error) {
|
|
1530
1690
|
result.valid = false;
|
|
1531
|
-
result.errors.push(
|
|
1691
|
+
result.errors.push(
|
|
1692
|
+
`Cannot read guidelines directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1693
|
+
);
|
|
1532
1694
|
return result;
|
|
1533
1695
|
}
|
|
1534
1696
|
const mandatory = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
@@ -1568,7 +1730,9 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1568
1730
|
};
|
|
1569
1731
|
if (!existsSync6(workflowsDir)) {
|
|
1570
1732
|
result.valid = false;
|
|
1571
|
-
result.errors.push(
|
|
1733
|
+
result.errors.push(
|
|
1734
|
+
"GitHub Actions workflows directory does not exist. Run: workflow github:setup"
|
|
1735
|
+
);
|
|
1572
1736
|
return result;
|
|
1573
1737
|
}
|
|
1574
1738
|
let workflowFiles = [];
|
|
@@ -1576,11 +1740,24 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1576
1740
|
workflowFiles = await readdir(workflowsDir);
|
|
1577
1741
|
} catch (error) {
|
|
1578
1742
|
result.valid = false;
|
|
1579
|
-
result.errors.push(
|
|
1743
|
+
result.errors.push(
|
|
1744
|
+
`Cannot read workflows directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1745
|
+
);
|
|
1580
1746
|
return result;
|
|
1581
1747
|
}
|
|
1582
|
-
const ciWorkflowNames = [
|
|
1583
|
-
|
|
1748
|
+
const ciWorkflowNames = [
|
|
1749
|
+
"ci.yml",
|
|
1750
|
+
"ci.yaml",
|
|
1751
|
+
"main.yml",
|
|
1752
|
+
"main.yaml",
|
|
1753
|
+
"build.yml",
|
|
1754
|
+
"build.yaml",
|
|
1755
|
+
"test.yml",
|
|
1756
|
+
"test.yaml"
|
|
1757
|
+
];
|
|
1758
|
+
const ciWorkflows = workflowFiles.filter(
|
|
1759
|
+
(f) => ciWorkflowNames.includes(f.toLowerCase())
|
|
1760
|
+
);
|
|
1584
1761
|
if (ciWorkflows.length === 0) {
|
|
1585
1762
|
result.valid = false;
|
|
1586
1763
|
result.errors.push("No CI workflow file found. Run: workflow github:setup");
|
|
@@ -1592,7 +1769,9 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1592
1769
|
try {
|
|
1593
1770
|
workflowContent = await readFile3(workflowPath, "utf-8");
|
|
1594
1771
|
} catch (error) {
|
|
1595
|
-
result.warnings.push(
|
|
1772
|
+
result.warnings.push(
|
|
1773
|
+
`Cannot read workflow file: ${error instanceof Error ? error.message : String(error)}`
|
|
1774
|
+
);
|
|
1596
1775
|
return result;
|
|
1597
1776
|
}
|
|
1598
1777
|
const contentLower = workflowContent.toLowerCase();
|
|
@@ -1621,10 +1800,16 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1621
1800
|
} else {
|
|
1622
1801
|
result.warnings.push("CI workflow may be missing test step");
|
|
1623
1802
|
}
|
|
1624
|
-
const mandatoryChecks = [
|
|
1803
|
+
const mandatoryChecks = [
|
|
1804
|
+
result.hasLintCheck,
|
|
1805
|
+
result.hasTypecheckCheck,
|
|
1806
|
+
result.hasFormatCheck
|
|
1807
|
+
];
|
|
1625
1808
|
if (!mandatoryChecks.every(Boolean)) {
|
|
1626
1809
|
result.valid = false;
|
|
1627
|
-
result.errors.push(
|
|
1810
|
+
result.errors.push(
|
|
1811
|
+
"CI workflow is missing mandatory checks (lint, typecheck, format)"
|
|
1812
|
+
);
|
|
1628
1813
|
}
|
|
1629
1814
|
return result;
|
|
1630
1815
|
}
|
|
@@ -1661,13 +1846,25 @@ async function doctorCommand(options = {}) {
|
|
|
1661
1846
|
console.log(chalk5.bold("\n\u{1F4DA} Guidelines"));
|
|
1662
1847
|
const guidelinesResult = await validateGuidelinesExist(cwd, config);
|
|
1663
1848
|
if (guidelinesResult.valid) {
|
|
1664
|
-
console.log(
|
|
1849
|
+
console.log(
|
|
1850
|
+
chalk5.green(
|
|
1851
|
+
` \u2713 All ${guidelinesResult.presentMandatory.length} mandatory guidelines present`
|
|
1852
|
+
)
|
|
1853
|
+
);
|
|
1665
1854
|
if (guidelinesResult.presentOptional.length > 0) {
|
|
1666
|
-
console.log(
|
|
1855
|
+
console.log(
|
|
1856
|
+
chalk5.dim(
|
|
1857
|
+
` + ${guidelinesResult.presentOptional.length} optional guidelines`
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1667
1860
|
}
|
|
1668
1861
|
} else {
|
|
1669
1862
|
hasErrors = true;
|
|
1670
|
-
console.log(
|
|
1863
|
+
console.log(
|
|
1864
|
+
chalk5.red(
|
|
1865
|
+
` \u2717 Missing ${guidelinesResult.missingMandatory.length} mandatory guidelines:`
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1671
1868
|
for (const missing of guidelinesResult.missingMandatory) {
|
|
1672
1869
|
console.log(chalk5.red(` \u2022 ${missing}`));
|
|
1673
1870
|
}
|
|
@@ -1681,14 +1878,20 @@ async function doctorCommand(options = {}) {
|
|
|
1681
1878
|
const hookStatuses = await getAllHooksStatus(cwd);
|
|
1682
1879
|
const installedHooks = hookStatuses.filter((h) => h.installed);
|
|
1683
1880
|
if (installedHooks.length === hookStatuses.length) {
|
|
1684
|
-
console.log(
|
|
1881
|
+
console.log(
|
|
1882
|
+
chalk5.green(` \u2713 All ${installedHooks.length} hooks installed`)
|
|
1883
|
+
);
|
|
1685
1884
|
for (const hook of hookStatuses) {
|
|
1686
1885
|
const extra = hook.wrappedOriginal ? " (wrapping original)" : "";
|
|
1687
1886
|
console.log(chalk5.dim(` \u2022 ${hook.hookType}${extra}`));
|
|
1688
1887
|
}
|
|
1689
1888
|
} else if (installedHooks.length > 0) {
|
|
1690
1889
|
hasWarnings = true;
|
|
1691
|
-
console.log(
|
|
1890
|
+
console.log(
|
|
1891
|
+
chalk5.yellow(
|
|
1892
|
+
` \u26A0 ${installedHooks.length}/${hookStatuses.length} hooks installed`
|
|
1893
|
+
)
|
|
1894
|
+
);
|
|
1692
1895
|
for (const hook of hookStatuses) {
|
|
1693
1896
|
if (hook.installed) {
|
|
1694
1897
|
console.log(chalk5.green(` \u2713 ${hook.hookType}`));
|
|
@@ -1711,7 +1914,11 @@ async function doctorCommand(options = {}) {
|
|
|
1711
1914
|
console.log(chalk5.dim(" \u25CB Not a GitHub repository (CI check skipped)"));
|
|
1712
1915
|
console.log(chalk5.dim(` Remote: ${repoInfo.remoteUrl || "none"}`));
|
|
1713
1916
|
} else {
|
|
1714
|
-
console.log(
|
|
1917
|
+
console.log(
|
|
1918
|
+
chalk5.dim(
|
|
1919
|
+
` Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
1920
|
+
)
|
|
1921
|
+
);
|
|
1715
1922
|
const ciResult = await validateGitHubActionsSetup(cwd);
|
|
1716
1923
|
if (ciResult.valid) {
|
|
1717
1924
|
console.log(chalk5.green(" \u2713 GitHub Actions CI configured correctly"));
|
|
@@ -1740,15 +1947,21 @@ async function doctorCommand(options = {}) {
|
|
|
1740
1947
|
if (repoInfo.isGitHub && hasCIWorkflow(cwd)) {
|
|
1741
1948
|
console.log(chalk5.cyan(" \u2192 Enable branch protection on GitHub"));
|
|
1742
1949
|
console.log(chalk5.dim(" Settings \u2192 Branches \u2192 Add rule"));
|
|
1743
|
-
console.log(
|
|
1744
|
-
|
|
1950
|
+
console.log(
|
|
1951
|
+
chalk5.dim(" \u2611 Require status checks to pass before merging")
|
|
1952
|
+
);
|
|
1953
|
+
console.log(
|
|
1954
|
+
chalk5.dim(" \u2611 Require branches to be up to date before merging")
|
|
1955
|
+
);
|
|
1745
1956
|
}
|
|
1746
1957
|
if (config.enforcement !== "strict") {
|
|
1747
1958
|
console.log(chalk5.cyan(` \u2192 Consider switching to 'strict' enforcement`));
|
|
1748
1959
|
console.log(chalk5.dim(` Current: ${config.enforcement}`));
|
|
1749
1960
|
}
|
|
1750
1961
|
if (config.scopes.length < 5) {
|
|
1751
|
-
console.log(
|
|
1962
|
+
console.log(
|
|
1963
|
+
chalk5.cyan(" \u2192 Consider adding more scopes for better organization")
|
|
1964
|
+
);
|
|
1752
1965
|
console.log(chalk5.dim(" Run: workflow config add scope <name>"));
|
|
1753
1966
|
}
|
|
1754
1967
|
console.log(chalk5.bold("\n\u{1F4CA} Summary"));
|
|
@@ -1766,7 +1979,7 @@ async function doctorCommand(options = {}) {
|
|
|
1766
1979
|
// src/cli/commands/setup.ts
|
|
1767
1980
|
import * as p3 from "@clack/prompts";
|
|
1768
1981
|
import chalk6 from "chalk";
|
|
1769
|
-
import { readFileSync
|
|
1982
|
+
import { readFileSync, writeFileSync as writeFileSync2, existsSync as existsSync7 } from "fs";
|
|
1770
1983
|
import { join as join7 } from "path";
|
|
1771
1984
|
var WORKFLOW_SCRIPTS = {
|
|
1772
1985
|
"workflow:init": "workflow-agent init",
|
|
@@ -1782,7 +1995,7 @@ async function setupCommand() {
|
|
|
1782
1995
|
p3.cancel("No package.json found in current directory");
|
|
1783
1996
|
process.exit(1);
|
|
1784
1997
|
}
|
|
1785
|
-
const packageJsonContent =
|
|
1998
|
+
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
|
|
1786
1999
|
const packageJson = JSON.parse(packageJsonContent);
|
|
1787
2000
|
if (!packageJson.scripts) {
|
|
1788
2001
|
packageJson.scripts = {};
|
|
@@ -1826,7 +2039,11 @@ async function setupCommand() {
|
|
|
1826
2039
|
JSON.stringify(packageJson, null, 2) + "\n",
|
|
1827
2040
|
"utf-8"
|
|
1828
2041
|
);
|
|
1829
|
-
p3.outro(
|
|
2042
|
+
p3.outro(
|
|
2043
|
+
chalk6.green(
|
|
2044
|
+
`\u2713 Added ${Object.keys(scriptsToAdd).length} workflow scripts to package.json!`
|
|
2045
|
+
)
|
|
2046
|
+
);
|
|
1830
2047
|
console.log(chalk6.dim("\nRun them with:"));
|
|
1831
2048
|
console.log(chalk6.dim(" pnpm run workflow:init"));
|
|
1832
2049
|
console.log(chalk6.dim(" npm run workflow:init\n"));
|
|
@@ -1851,8 +2068,10 @@ async function scopeCreateCommand(options) {
|
|
|
1851
2068
|
placeholder: "my-custom-scope",
|
|
1852
2069
|
validate: (value) => {
|
|
1853
2070
|
if (!value || value.length === 0) return "Package name is required";
|
|
1854
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
1855
|
-
|
|
2071
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2072
|
+
return "Package name must be lowercase alphanumeric with hyphens";
|
|
2073
|
+
if (value.length > 32)
|
|
2074
|
+
return "Package name must be 32 characters or less";
|
|
1856
2075
|
return void 0;
|
|
1857
2076
|
}
|
|
1858
2077
|
});
|
|
@@ -1887,7 +2106,9 @@ async function scopeCreateCommand(options) {
|
|
|
1887
2106
|
});
|
|
1888
2107
|
}
|
|
1889
2108
|
} else {
|
|
1890
|
-
console.log(
|
|
2109
|
+
console.log(
|
|
2110
|
+
chalk7.dim("\nAdd scopes to your preset (aim for 8-15 scopes):\n")
|
|
2111
|
+
);
|
|
1891
2112
|
let addMore = true;
|
|
1892
2113
|
while (addMore) {
|
|
1893
2114
|
const scopeName = await p4.text({
|
|
@@ -1895,9 +2116,11 @@ async function scopeCreateCommand(options) {
|
|
|
1895
2116
|
placeholder: "auth",
|
|
1896
2117
|
validate: (value) => {
|
|
1897
2118
|
if (!value || value.length === 0) return "Scope name is required";
|
|
1898
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
2119
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2120
|
+
return "Must be lowercase alphanumeric with hyphens";
|
|
1899
2121
|
if (value.length > 32) return "Must be 32 characters or less";
|
|
1900
|
-
if (scopes.some((s) => s.name === value))
|
|
2122
|
+
if (scopes.some((s) => s.name === value))
|
|
2123
|
+
return "Scope name already exists";
|
|
1901
2124
|
return void 0;
|
|
1902
2125
|
}
|
|
1903
2126
|
});
|
|
@@ -1908,7 +2131,8 @@ async function scopeCreateCommand(options) {
|
|
|
1908
2131
|
message: "Description:",
|
|
1909
2132
|
placeholder: "Authentication and authorization",
|
|
1910
2133
|
validate: (value) => {
|
|
1911
|
-
if (!value || value.length < 10)
|
|
2134
|
+
if (!value || value.length < 10)
|
|
2135
|
+
return "Description must be at least 10 characters";
|
|
1912
2136
|
return void 0;
|
|
1913
2137
|
}
|
|
1914
2138
|
});
|
|
@@ -1966,13 +2190,17 @@ async function scopeCreateCommand(options) {
|
|
|
1966
2190
|
const validation = validateScopeDefinitions(scopes);
|
|
1967
2191
|
if (!validation.valid) {
|
|
1968
2192
|
console.log(chalk7.red("\n\u2717 Scope validation failed:\n"));
|
|
1969
|
-
validation.errors.forEach(
|
|
2193
|
+
validation.errors.forEach(
|
|
2194
|
+
(error) => console.log(chalk7.red(` \u2022 ${error}`))
|
|
2195
|
+
);
|
|
1970
2196
|
p4.cancel("Operation cancelled");
|
|
1971
2197
|
process.exit(1);
|
|
1972
2198
|
}
|
|
1973
|
-
console.log(
|
|
2199
|
+
console.log(
|
|
2200
|
+
chalk7.green(`
|
|
1974
2201
|
\u2713 ${scopes.length} scopes validated successfully
|
|
1975
|
-
`)
|
|
2202
|
+
`)
|
|
2203
|
+
);
|
|
1976
2204
|
let outputDir;
|
|
1977
2205
|
if (options.outputDir) {
|
|
1978
2206
|
outputDir = options.outputDir;
|
|
@@ -2126,7 +2354,11 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2126
2354
|
});
|
|
2127
2355
|
});
|
|
2128
2356
|
`;
|
|
2129
|
-
await writeFile4(
|
|
2357
|
+
await writeFile4(
|
|
2358
|
+
join8(outputDir, "src", "index.test.ts"),
|
|
2359
|
+
testFile,
|
|
2360
|
+
"utf-8"
|
|
2361
|
+
);
|
|
2130
2362
|
}
|
|
2131
2363
|
spinner5.stop("\u2713 Package structure created");
|
|
2132
2364
|
if (isMonorepo2) {
|
|
@@ -2134,13 +2366,17 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2134
2366
|
const workspaceContent = await readFile4(workspaceFile, "utf-8");
|
|
2135
2367
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2136
2368
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2137
|
-
console.log(
|
|
2369
|
+
console.log(
|
|
2370
|
+
chalk7.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:")
|
|
2371
|
+
);
|
|
2138
2372
|
console.log(chalk7.dim(` - '${packagePath}'`));
|
|
2139
2373
|
} else {
|
|
2140
2374
|
console.log(chalk7.green("\n\u2713 Package will be included in workspace"));
|
|
2141
2375
|
}
|
|
2142
2376
|
}
|
|
2143
|
-
console.log(
|
|
2377
|
+
console.log(
|
|
2378
|
+
chalk7.green.bold("\n\u2728 Custom scope package created successfully!\n")
|
|
2379
|
+
);
|
|
2144
2380
|
console.log(chalk7.bold("Package details:"));
|
|
2145
2381
|
console.log(chalk7.dim(` Location: ${outputDir}`));
|
|
2146
2382
|
console.log(chalk7.dim(` Package: @workflow/scopes-${packageName}`));
|
|
@@ -2153,17 +2389,27 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2153
2389
|
if (!options.noTest) {
|
|
2154
2390
|
console.log(chalk7.dim(` 4. pnpm test`));
|
|
2155
2391
|
}
|
|
2156
|
-
console.log(
|
|
2392
|
+
console.log(
|
|
2393
|
+
chalk7.dim(
|
|
2394
|
+
` ${!options.noTest ? "5" : "4"}. Update repository URL in package.json`
|
|
2395
|
+
)
|
|
2396
|
+
);
|
|
2157
2397
|
const shouldPublish = isNonInteractive ? false : await p4.confirm({
|
|
2158
2398
|
message: "\nWould you like instructions for publishing to npm?",
|
|
2159
2399
|
initialValue: false
|
|
2160
2400
|
});
|
|
2161
2401
|
if (shouldPublish && !p4.isCancel(shouldPublish)) {
|
|
2162
2402
|
console.log(chalk7.bold("\n\u{1F4E6} Publishing instructions:\n"));
|
|
2163
|
-
console.log(
|
|
2403
|
+
console.log(
|
|
2404
|
+
chalk7.dim(" 1. npm login (or configure .npmrc with your registry)")
|
|
2405
|
+
);
|
|
2164
2406
|
console.log(chalk7.dim(" 2. Update version in package.json as needed"));
|
|
2165
2407
|
console.log(chalk7.dim(" 3. pnpm publish --access public"));
|
|
2166
|
-
console.log(
|
|
2408
|
+
console.log(
|
|
2409
|
+
chalk7.dim(
|
|
2410
|
+
" 4. Use in other projects: pnpm add @workflow/scopes-" + packageName + "\n"
|
|
2411
|
+
)
|
|
2412
|
+
);
|
|
2167
2413
|
}
|
|
2168
2414
|
} catch (error) {
|
|
2169
2415
|
spinner5.stop("\u2717 Failed to create package");
|
|
@@ -2200,11 +2446,17 @@ async function scopeMigrateCommand(options) {
|
|
|
2200
2446
|
p5.cancel("No scopes found in workflow.config.json");
|
|
2201
2447
|
process.exit(1);
|
|
2202
2448
|
}
|
|
2203
|
-
console.log(
|
|
2204
|
-
`
|
|
2449
|
+
console.log(
|
|
2450
|
+
chalk8.dim(`Found ${config.scopes.length} scopes in workflow.config.json
|
|
2451
|
+
`)
|
|
2452
|
+
);
|
|
2205
2453
|
console.log(chalk8.bold("Current scopes:"));
|
|
2206
2454
|
config.scopes.forEach((scope, i) => {
|
|
2207
|
-
console.log(
|
|
2455
|
+
console.log(
|
|
2456
|
+
chalk8.dim(
|
|
2457
|
+
` ${i + 1}. ${scope.emoji || "\u2022"} ${scope.name} - ${scope.description}`
|
|
2458
|
+
)
|
|
2459
|
+
);
|
|
2208
2460
|
});
|
|
2209
2461
|
console.log();
|
|
2210
2462
|
const shouldContinue = await p5.confirm({
|
|
@@ -2224,8 +2476,10 @@ async function scopeMigrateCommand(options) {
|
|
|
2224
2476
|
placeholder: config.projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
|
|
2225
2477
|
validate: (value) => {
|
|
2226
2478
|
if (!value || value.length === 0) return "Package name is required";
|
|
2227
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
2228
|
-
|
|
2479
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2480
|
+
return "Package name must be lowercase alphanumeric with hyphens";
|
|
2481
|
+
if (value.length > 32)
|
|
2482
|
+
return "Package name must be 32 characters or less";
|
|
2229
2483
|
return void 0;
|
|
2230
2484
|
}
|
|
2231
2485
|
});
|
|
@@ -2247,7 +2501,9 @@ async function scopeMigrateCommand(options) {
|
|
|
2247
2501
|
const validation = validateScopeDefinitions(config.scopes);
|
|
2248
2502
|
if (!validation.valid) {
|
|
2249
2503
|
console.log(chalk8.yellow("\n\u26A0\uFE0F Scope validation warnings:\n"));
|
|
2250
|
-
validation.errors.forEach(
|
|
2504
|
+
validation.errors.forEach(
|
|
2505
|
+
(error) => console.log(chalk8.yellow(` \u2022 ${error}`))
|
|
2506
|
+
);
|
|
2251
2507
|
const shouldFix = await p5.confirm({
|
|
2252
2508
|
message: "Some scopes have validation issues. Continue anyway?",
|
|
2253
2509
|
initialValue: false
|
|
@@ -2419,7 +2675,9 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2419
2675
|
const workspaceContent = await readFile5(workspaceFile, "utf-8");
|
|
2420
2676
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2421
2677
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2422
|
-
console.log(
|
|
2678
|
+
console.log(
|
|
2679
|
+
chalk8.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:")
|
|
2680
|
+
);
|
|
2423
2681
|
console.log(chalk8.dim(` - '${packagePath}'`));
|
|
2424
2682
|
} else {
|
|
2425
2683
|
console.log(chalk8.green("\n\u2713 Package will be included in workspace"));
|
|
@@ -2438,11 +2696,17 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2438
2696
|
preset: `scopes-${packageName}`
|
|
2439
2697
|
// Reference the new package
|
|
2440
2698
|
};
|
|
2441
|
-
await writeFile5(
|
|
2699
|
+
await writeFile5(
|
|
2700
|
+
configPath,
|
|
2701
|
+
JSON.stringify(updatedConfig, null, 2),
|
|
2702
|
+
"utf-8"
|
|
2703
|
+
);
|
|
2442
2704
|
console.log(chalk8.green("\u2713 Updated workflow.config.json"));
|
|
2443
2705
|
console.log(chalk8.dim(" \u2022 Cleared inline scopes"));
|
|
2444
|
-
console.log(
|
|
2445
|
-
`
|
|
2706
|
+
console.log(
|
|
2707
|
+
chalk8.dim(` \u2022 Added preset reference: scopes-${packageName}
|
|
2708
|
+
`)
|
|
2709
|
+
);
|
|
2446
2710
|
}
|
|
2447
2711
|
console.log(chalk8.green.bold("\n\u2728 Migration completed successfully!\n"));
|
|
2448
2712
|
console.log(chalk8.bold("Package details:"));
|
|
@@ -2459,11 +2723,23 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2459
2723
|
`));
|
|
2460
2724
|
if (!keepConfig) {
|
|
2461
2725
|
console.log(chalk8.bold("To use the migrated scopes:\n"));
|
|
2462
|
-
console.log(
|
|
2463
|
-
|
|
2464
|
-
`
|
|
2465
|
-
|
|
2466
|
-
|
|
2726
|
+
console.log(
|
|
2727
|
+
chalk8.dim(
|
|
2728
|
+
` 1. Install the package: pnpm add -w @workflow/scopes-${packageName}`
|
|
2729
|
+
)
|
|
2730
|
+
);
|
|
2731
|
+
console.log(
|
|
2732
|
+
chalk8.dim(
|
|
2733
|
+
` 2. The preset is already referenced in workflow.config.json
|
|
2734
|
+
`
|
|
2735
|
+
)
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
console.log(
|
|
2739
|
+
chalk8.dim(
|
|
2740
|
+
"\u{1F4A1} Tip: You can now reuse this scope package across multiple projects!\n"
|
|
2741
|
+
)
|
|
2742
|
+
);
|
|
2467
2743
|
} catch (error) {
|
|
2468
2744
|
spinner5.stop("\u2717 Migration failed");
|
|
2469
2745
|
console.error(chalk8.red("\nError:"), error);
|
|
@@ -2505,12 +2781,18 @@ async function installHooksAction(cwd) {
|
|
|
2505
2781
|
for (const result of results) {
|
|
2506
2782
|
if (result.success) {
|
|
2507
2783
|
if (result.wrappedExisting) {
|
|
2508
|
-
console.log(
|
|
2784
|
+
console.log(
|
|
2785
|
+
chalk9.green(
|
|
2786
|
+
`\u2713 Installed ${result.hookType} hook (wrapped existing hook)`
|
|
2787
|
+
)
|
|
2788
|
+
);
|
|
2509
2789
|
} else {
|
|
2510
2790
|
console.log(chalk9.green(`\u2713 Installed ${result.hookType} hook`));
|
|
2511
2791
|
}
|
|
2512
2792
|
} else {
|
|
2513
|
-
console.error(
|
|
2793
|
+
console.error(
|
|
2794
|
+
chalk9.red(`\u2717 Failed to install ${result.hookType}: ${result.error}`)
|
|
2795
|
+
);
|
|
2514
2796
|
hasErrors = true;
|
|
2515
2797
|
}
|
|
2516
2798
|
}
|
|
@@ -2533,12 +2815,16 @@ async function uninstallHooksAction(cwd) {
|
|
|
2533
2815
|
for (const result of results) {
|
|
2534
2816
|
if (result.success) {
|
|
2535
2817
|
if (result.wrappedExisting) {
|
|
2536
|
-
console.log(
|
|
2818
|
+
console.log(
|
|
2819
|
+
chalk9.green(`\u2713 Removed ${result.hookType} hook (restored original)`)
|
|
2820
|
+
);
|
|
2537
2821
|
} else {
|
|
2538
2822
|
console.log(chalk9.green(`\u2713 Removed ${result.hookType} hook`));
|
|
2539
2823
|
}
|
|
2540
2824
|
} else if (result.error) {
|
|
2541
|
-
console.error(
|
|
2825
|
+
console.error(
|
|
2826
|
+
chalk9.red(`\u2717 Failed to remove ${result.hookType}: ${result.error}`)
|
|
2827
|
+
);
|
|
2542
2828
|
hasErrors = true;
|
|
2543
2829
|
}
|
|
2544
2830
|
}
|
|
@@ -2614,7 +2900,11 @@ async function setupAction(cwd) {
|
|
|
2614
2900
|
process.exit(0);
|
|
2615
2901
|
}
|
|
2616
2902
|
} else {
|
|
2617
|
-
console.log(
|
|
2903
|
+
console.log(
|
|
2904
|
+
chalk10.dim(
|
|
2905
|
+
`Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
2906
|
+
)
|
|
2907
|
+
);
|
|
2618
2908
|
}
|
|
2619
2909
|
if (hasCIWorkflow(cwd)) {
|
|
2620
2910
|
const shouldOverwrite = await p6.confirm({
|
|
@@ -2648,7 +2938,13 @@ async function setupAction(cwd) {
|
|
|
2648
2938
|
console.log(chalk10.dim(" \u2022 Push to main/develop branches"));
|
|
2649
2939
|
console.log(chalk10.dim(" \u2022 Pull requests to main/develop branches"));
|
|
2650
2940
|
console.log(chalk10.dim("\nChecks included:"));
|
|
2651
|
-
const checks = ciConfig?.checks || [
|
|
2941
|
+
const checks = ciConfig?.checks || [
|
|
2942
|
+
"lint",
|
|
2943
|
+
"typecheck",
|
|
2944
|
+
"format",
|
|
2945
|
+
"build",
|
|
2946
|
+
"test"
|
|
2947
|
+
];
|
|
2652
2948
|
for (const check of checks) {
|
|
2653
2949
|
const hasScript = check === "lint" ? projectInfo.hasLintScript : check === "typecheck" ? projectInfo.hasTypecheckScript : check === "format" ? projectInfo.hasFormatScript : check === "test" ? projectInfo.hasTestScript : check === "build" ? projectInfo.hasBuildScript : false;
|
|
2654
2950
|
const status = hasScript ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
@@ -2657,7 +2953,9 @@ async function setupAction(cwd) {
|
|
|
2657
2953
|
}
|
|
2658
2954
|
console.log(chalk10.cyan("\n\u{1F4A1} Recommended: Enable branch protection"));
|
|
2659
2955
|
console.log(chalk10.dim(" Go to GitHub \u2192 Settings \u2192 Branches \u2192 Add rule"));
|
|
2660
|
-
console.log(
|
|
2956
|
+
console.log(
|
|
2957
|
+
chalk10.dim(' Enable "Require status checks to pass before merging"')
|
|
2958
|
+
);
|
|
2661
2959
|
console.log(chalk10.dim(' Select the "ci" status check'));
|
|
2662
2960
|
} else {
|
|
2663
2961
|
spinner5.stop(chalk10.red("\u2717 Failed to create CI workflow"));
|
|
@@ -2708,18 +3006,496 @@ async function checkAction(cwd) {
|
|
|
2708
3006
|
}
|
|
2709
3007
|
}
|
|
2710
3008
|
|
|
3009
|
+
// src/cli/commands/fix.ts
|
|
3010
|
+
import { existsSync as existsSync10, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync } from "fs";
|
|
3011
|
+
import { dirname as dirname2, join as join10, relative } from "path";
|
|
3012
|
+
import { execa as execa3 } from "execa";
|
|
3013
|
+
import * as p7 from "@clack/prompts";
|
|
3014
|
+
import pc from "picocolors";
|
|
3015
|
+
function extractFilePaths(errorMessage, cwd) {
|
|
3016
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3017
|
+
const patterns = [
|
|
3018
|
+
/(?:at\s+)?([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))(?::\d+(?::\d+)?)?/g,
|
|
3019
|
+
/(?:Error in |from )\.?([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))/g,
|
|
3020
|
+
/['"]([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))['"]/g
|
|
3021
|
+
];
|
|
3022
|
+
for (const pattern of patterns) {
|
|
3023
|
+
let match;
|
|
3024
|
+
while ((match = pattern.exec(errorMessage)) !== null) {
|
|
3025
|
+
let filePath = match[1];
|
|
3026
|
+
if (!filePath.startsWith("/")) {
|
|
3027
|
+
filePath = join10(cwd, filePath);
|
|
3028
|
+
}
|
|
3029
|
+
if (existsSync10(filePath)) {
|
|
3030
|
+
paths.add(filePath);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return Array.from(paths);
|
|
3035
|
+
}
|
|
3036
|
+
function readFileContents(paths) {
|
|
3037
|
+
const contents = {};
|
|
3038
|
+
for (const filePath of paths) {
|
|
3039
|
+
try {
|
|
3040
|
+
if (existsSync10(filePath)) {
|
|
3041
|
+
contents[filePath] = readFileSync2(filePath, "utf-8");
|
|
3042
|
+
}
|
|
3043
|
+
} catch {
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
return contents;
|
|
3047
|
+
}
|
|
3048
|
+
async function generateFixWithLLM(errorMessage, _fileContents, _context) {
|
|
3049
|
+
console.log(pc.dim(" Analyzing error with LLM..."));
|
|
3050
|
+
return {
|
|
3051
|
+
analysis: `Error analysis for: ${errorMessage.slice(0, 100)}...`,
|
|
3052
|
+
rootCause: "Unable to determine root cause without LLM API access",
|
|
3053
|
+
suggestedFix: {
|
|
3054
|
+
description: "Manual intervention required - LLM API not configured",
|
|
3055
|
+
files: []
|
|
3056
|
+
},
|
|
3057
|
+
confidence: 0
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
async function applyChanges(changes, dryRun) {
|
|
3061
|
+
for (const change of changes) {
|
|
3062
|
+
const actionColor = change.action === "create" ? pc.green : change.action === "delete" ? pc.red : pc.yellow;
|
|
3063
|
+
console.log(` ${actionColor(change.action.toUpperCase())} ${change.path}`);
|
|
3064
|
+
if (dryRun) {
|
|
3065
|
+
console.log(pc.dim(" (dry run - no changes made)"));
|
|
3066
|
+
continue;
|
|
3067
|
+
}
|
|
3068
|
+
switch (change.action) {
|
|
3069
|
+
case "create":
|
|
3070
|
+
case "modify":
|
|
3071
|
+
if (change.content) {
|
|
3072
|
+
const dir = dirname2(change.path);
|
|
3073
|
+
if (!existsSync10(dir)) {
|
|
3074
|
+
mkdirSync(dir, { recursive: true });
|
|
3075
|
+
}
|
|
3076
|
+
writeFileSync3(change.path, change.content);
|
|
3077
|
+
}
|
|
3078
|
+
break;
|
|
3079
|
+
case "delete":
|
|
3080
|
+
console.log(pc.dim(" (delete skipped for safety)"));
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
async function commitAndPush(message, cwd) {
|
|
3086
|
+
try {
|
|
3087
|
+
await execa3("git", ["add", "-A"], { cwd });
|
|
3088
|
+
const { stdout: status } = await execa3(
|
|
3089
|
+
"git",
|
|
3090
|
+
["status", "--porcelain"],
|
|
3091
|
+
{ cwd }
|
|
3092
|
+
);
|
|
3093
|
+
if (!status.trim()) {
|
|
3094
|
+
return { success: true };
|
|
3095
|
+
}
|
|
3096
|
+
await execa3("git", ["commit", "-m", message], { cwd });
|
|
3097
|
+
await execa3("git", ["push"], { cwd });
|
|
3098
|
+
return { success: true };
|
|
3099
|
+
} catch (error) {
|
|
3100
|
+
return {
|
|
3101
|
+
success: false,
|
|
3102
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
async function fixCommand(options) {
|
|
3107
|
+
const cwd = process.cwd();
|
|
3108
|
+
console.log(pc.cyan("\n\u{1F527} Auto-Heal: Fixing Pipeline Error\n"));
|
|
3109
|
+
if (!options.error) {
|
|
3110
|
+
console.error(pc.red("Error: --error flag is required"));
|
|
3111
|
+
process.exit(1);
|
|
3112
|
+
}
|
|
3113
|
+
console.log(pc.bold("Error Message:"));
|
|
3114
|
+
console.log(pc.dim(options.error.slice(0, 500)));
|
|
3115
|
+
if (options.error.length > 500) {
|
|
3116
|
+
console.log(pc.dim(`... (${options.error.length - 500} more characters)`));
|
|
3117
|
+
}
|
|
3118
|
+
console.log("");
|
|
3119
|
+
let filePaths = options.files || [];
|
|
3120
|
+
if (filePaths.length === 0) {
|
|
3121
|
+
console.log(pc.dim("Extracting file paths from error..."));
|
|
3122
|
+
filePaths = extractFilePaths(options.error, cwd);
|
|
3123
|
+
}
|
|
3124
|
+
if (filePaths.length > 0) {
|
|
3125
|
+
console.log(pc.bold("\nRelevant Files:"));
|
|
3126
|
+
for (const path2 of filePaths) {
|
|
3127
|
+
console.log(` \u{1F4C4} ${relative(cwd, path2)}`);
|
|
3128
|
+
}
|
|
3129
|
+
console.log("");
|
|
3130
|
+
}
|
|
3131
|
+
const fileContents = readFileContents(filePaths);
|
|
3132
|
+
let context;
|
|
3133
|
+
if (options.context) {
|
|
3134
|
+
try {
|
|
3135
|
+
context = JSON.parse(options.context);
|
|
3136
|
+
} catch {
|
|
3137
|
+
context = options.context;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
console.log(pc.bold("Generating Fix...\n"));
|
|
3141
|
+
const fix = await generateFixWithLLM(options.error, fileContents, context);
|
|
3142
|
+
console.log(pc.bold("Analysis:"));
|
|
3143
|
+
console.log(` ${fix.analysis}`);
|
|
3144
|
+
console.log("");
|
|
3145
|
+
console.log(pc.bold("Root Cause:"));
|
|
3146
|
+
console.log(` ${fix.rootCause}`);
|
|
3147
|
+
console.log("");
|
|
3148
|
+
console.log(pc.bold("Suggested Fix:"));
|
|
3149
|
+
console.log(` ${fix.suggestedFix.description}`);
|
|
3150
|
+
console.log(` Confidence: ${Math.round(fix.confidence * 100)}%`);
|
|
3151
|
+
console.log("");
|
|
3152
|
+
if (fix.suggestedFix.files.length === 0) {
|
|
3153
|
+
console.log(
|
|
3154
|
+
pc.yellow("\u26A0\uFE0F No automatic fix available. Manual intervention required.")
|
|
3155
|
+
);
|
|
3156
|
+
process.exit(1);
|
|
3157
|
+
}
|
|
3158
|
+
if (fix.confidence < 0.5) {
|
|
3159
|
+
console.log(
|
|
3160
|
+
pc.yellow(
|
|
3161
|
+
`\u26A0\uFE0F Low confidence (${Math.round(fix.confidence * 100)}%). Skipping automatic fix.`
|
|
3162
|
+
)
|
|
3163
|
+
);
|
|
3164
|
+
process.exit(1);
|
|
3165
|
+
}
|
|
3166
|
+
if (!options.auto) {
|
|
3167
|
+
const shouldApply = await p7.confirm({
|
|
3168
|
+
message: "Apply suggested changes?",
|
|
3169
|
+
initialValue: false
|
|
3170
|
+
});
|
|
3171
|
+
if (p7.isCancel(shouldApply) || !shouldApply) {
|
|
3172
|
+
console.log(pc.dim("Fix cancelled."));
|
|
3173
|
+
process.exit(0);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
console.log(pc.bold("\nApplying Changes...\n"));
|
|
3177
|
+
await applyChanges(fix.suggestedFix.files, options.dryRun || false);
|
|
3178
|
+
if (options.dryRun) {
|
|
3179
|
+
console.log(pc.yellow("\n\u26A0\uFE0F Dry run complete. No changes were made."));
|
|
3180
|
+
process.exit(0);
|
|
3181
|
+
}
|
|
3182
|
+
if (options.auto) {
|
|
3183
|
+
console.log(pc.bold("\nCommitting and Pushing...\n"));
|
|
3184
|
+
const scope = filePaths.length > 0 ? dirname2(relative(cwd, filePaths[0])).split("/")[0] || "core" : "core";
|
|
3185
|
+
const commitMessage = `fix(${scope}): auto-heal pipeline failure
|
|
3186
|
+
|
|
3187
|
+
${fix.rootCause}
|
|
3188
|
+
|
|
3189
|
+
Auto-generated fix with ${Math.round(fix.confidence * 100)}% confidence.
|
|
3190
|
+
`;
|
|
3191
|
+
const result = await commitAndPush(commitMessage, cwd);
|
|
3192
|
+
if (result.success) {
|
|
3193
|
+
console.log(pc.green("\u2705 Fix applied, committed, and pushed!"));
|
|
3194
|
+
} else {
|
|
3195
|
+
console.log(pc.red(`\u274C Failed to commit/push: ${result.error}`));
|
|
3196
|
+
process.exit(1);
|
|
3197
|
+
}
|
|
3198
|
+
} else {
|
|
3199
|
+
console.log(pc.green("\n\u2705 Fix applied! Don't forget to commit and push."));
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// src/cli/commands/visual.ts
|
|
3204
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3205
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
3206
|
+
import * as p8 from "@clack/prompts";
|
|
3207
|
+
import pc2 from "picocolors";
|
|
3208
|
+
function getBaselinesDir() {
|
|
3209
|
+
const cwd = process.cwd();
|
|
3210
|
+
return join11(cwd, ".visual-baselines");
|
|
3211
|
+
}
|
|
3212
|
+
function getMetadataPath() {
|
|
3213
|
+
return join11(getBaselinesDir(), "baselines.json");
|
|
3214
|
+
}
|
|
3215
|
+
function loadMetadata() {
|
|
3216
|
+
const metadataPath = getMetadataPath();
|
|
3217
|
+
if (!existsSync11(metadataPath)) {
|
|
3218
|
+
return {};
|
|
3219
|
+
}
|
|
3220
|
+
try {
|
|
3221
|
+
return JSON.parse(readFileSync3(metadataPath, "utf-8"));
|
|
3222
|
+
} catch {
|
|
3223
|
+
return {};
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
function saveMetadata(metadata) {
|
|
3227
|
+
const dir = getBaselinesDir();
|
|
3228
|
+
if (!existsSync11(dir)) {
|
|
3229
|
+
mkdirSync2(dir, { recursive: true });
|
|
3230
|
+
}
|
|
3231
|
+
writeFileSync4(getMetadataPath(), JSON.stringify(metadata, null, 2));
|
|
3232
|
+
}
|
|
3233
|
+
async function checkPlaywright() {
|
|
3234
|
+
try {
|
|
3235
|
+
await import("playwright");
|
|
3236
|
+
return true;
|
|
3237
|
+
} catch {
|
|
3238
|
+
return false;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
async function visualCaptureCommand(name, url, options) {
|
|
3242
|
+
console.log(pc2.cyan("\n\u{1F4F8} Capturing Visual Baseline\n"));
|
|
3243
|
+
const hasPlaywright = await checkPlaywright();
|
|
3244
|
+
if (!hasPlaywright) {
|
|
3245
|
+
console.log(pc2.yellow("\u26A0\uFE0F Playwright is not installed."));
|
|
3246
|
+
console.log(pc2.dim("Install it with: npm install playwright"));
|
|
3247
|
+
console.log(
|
|
3248
|
+
pc2.dim("Then install browsers: npx playwright install chromium")
|
|
3249
|
+
);
|
|
3250
|
+
process.exit(1);
|
|
3251
|
+
}
|
|
3252
|
+
const width = parseInt(options.width || "1280");
|
|
3253
|
+
const height = parseInt(options.height || "720");
|
|
3254
|
+
const delay = parseInt(options.delay || "0");
|
|
3255
|
+
console.log(` Name: ${pc2.bold(name)}`);
|
|
3256
|
+
console.log(` URL: ${url}`);
|
|
3257
|
+
console.log(` Viewport: ${width}x${height}`);
|
|
3258
|
+
console.log("");
|
|
3259
|
+
try {
|
|
3260
|
+
const { chromium } = await import("playwright");
|
|
3261
|
+
const browser = await chromium.launch({ headless: true });
|
|
3262
|
+
const context = await browser.newContext({
|
|
3263
|
+
viewport: { width, height }
|
|
3264
|
+
});
|
|
3265
|
+
const page = await context.newPage();
|
|
3266
|
+
console.log(pc2.dim(" Loading page..."));
|
|
3267
|
+
await page.goto(url, { waitUntil: "networkidle" });
|
|
3268
|
+
if (options.waitFor) {
|
|
3269
|
+
console.log(pc2.dim(` Waiting for selector: ${options.waitFor}`));
|
|
3270
|
+
await page.waitForSelector(options.waitFor, { timeout: 1e4 });
|
|
3271
|
+
}
|
|
3272
|
+
if (delay > 0) {
|
|
3273
|
+
console.log(pc2.dim(` Waiting ${delay}ms...`));
|
|
3274
|
+
await page.waitForTimeout(delay);
|
|
3275
|
+
}
|
|
3276
|
+
const baselinesDir = getBaselinesDir();
|
|
3277
|
+
const outputPath = options.output || join11(baselinesDir, "screenshots", `${name}.png`);
|
|
3278
|
+
const outputDir = dirname3(outputPath);
|
|
3279
|
+
if (!existsSync11(outputDir)) {
|
|
3280
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
3281
|
+
}
|
|
3282
|
+
console.log(pc2.dim(" Capturing screenshot..."));
|
|
3283
|
+
await page.screenshot({
|
|
3284
|
+
path: outputPath,
|
|
3285
|
+
fullPage: options.fullPage
|
|
3286
|
+
});
|
|
3287
|
+
await browser.close();
|
|
3288
|
+
const metadata = loadMetadata();
|
|
3289
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3290
|
+
metadata[name] = {
|
|
3291
|
+
name,
|
|
3292
|
+
url,
|
|
3293
|
+
path: outputPath,
|
|
3294
|
+
width,
|
|
3295
|
+
height,
|
|
3296
|
+
createdAt: metadata[name]?.createdAt || now,
|
|
3297
|
+
updatedAt: now
|
|
3298
|
+
};
|
|
3299
|
+
saveMetadata(metadata);
|
|
3300
|
+
console.log(pc2.green(`
|
|
3301
|
+
\u2705 Baseline "${name}" saved to ${outputPath}`));
|
|
3302
|
+
} catch (error) {
|
|
3303
|
+
console.error(pc2.red("\n\u274C Failed to capture screenshot:"), error);
|
|
3304
|
+
process.exit(1);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
async function visualCompareCommand(url, options) {
|
|
3308
|
+
console.log(pc2.cyan("\n\u{1F50D} Comparing Against Baseline\n"));
|
|
3309
|
+
const metadata = loadMetadata();
|
|
3310
|
+
const baseline = metadata[options.baseline];
|
|
3311
|
+
if (!baseline) {
|
|
3312
|
+
console.log(pc2.red(`\u274C Baseline "${options.baseline}" not found`));
|
|
3313
|
+
console.log(pc2.dim("Available baselines:"));
|
|
3314
|
+
Object.keys(metadata).forEach((name) => {
|
|
3315
|
+
console.log(pc2.dim(` - ${name}`));
|
|
3316
|
+
});
|
|
3317
|
+
process.exit(1);
|
|
3318
|
+
}
|
|
3319
|
+
if (!existsSync11(baseline.path)) {
|
|
3320
|
+
console.log(pc2.red(`\u274C Baseline screenshot not found: ${baseline.path}`));
|
|
3321
|
+
process.exit(1);
|
|
3322
|
+
}
|
|
3323
|
+
console.log(` Baseline: ${pc2.bold(options.baseline)}`);
|
|
3324
|
+
console.log(` URL: ${url}`);
|
|
3325
|
+
console.log(` Viewport: ${baseline.width}x${baseline.height}`);
|
|
3326
|
+
console.log("");
|
|
3327
|
+
const hasPlaywright = await checkPlaywright();
|
|
3328
|
+
if (!hasPlaywright) {
|
|
3329
|
+
console.log(pc2.yellow("\u26A0\uFE0F Playwright is not installed."));
|
|
3330
|
+
process.exit(1);
|
|
3331
|
+
}
|
|
3332
|
+
try {
|
|
3333
|
+
const { chromium } = await import("playwright");
|
|
3334
|
+
const browser = await chromium.launch({ headless: true });
|
|
3335
|
+
const context = await browser.newContext({
|
|
3336
|
+
viewport: { width: baseline.width, height: baseline.height }
|
|
3337
|
+
});
|
|
3338
|
+
const page = await context.newPage();
|
|
3339
|
+
console.log(pc2.dim(" Loading page..."));
|
|
3340
|
+
await page.goto(url, { waitUntil: "networkidle" });
|
|
3341
|
+
const baselinesDir = getBaselinesDir();
|
|
3342
|
+
const comparePath = options.output || join11(
|
|
3343
|
+
baselinesDir,
|
|
3344
|
+
"comparisons",
|
|
3345
|
+
`${options.baseline}-${Date.now()}.png`
|
|
3346
|
+
);
|
|
3347
|
+
const compareDir = dirname3(comparePath);
|
|
3348
|
+
if (!existsSync11(compareDir)) {
|
|
3349
|
+
mkdirSync2(compareDir, { recursive: true });
|
|
3350
|
+
}
|
|
3351
|
+
console.log(pc2.dim(" Capturing screenshot..."));
|
|
3352
|
+
await page.screenshot({ path: comparePath });
|
|
3353
|
+
await browser.close();
|
|
3354
|
+
const baselineBuffer = readFileSync3(baseline.path);
|
|
3355
|
+
const compareBuffer = readFileSync3(comparePath);
|
|
3356
|
+
const areSameSize = baselineBuffer.length === compareBuffer.length;
|
|
3357
|
+
const areIdentical = areSameSize && baselineBuffer.equals(compareBuffer);
|
|
3358
|
+
console.log("");
|
|
3359
|
+
if (areIdentical) {
|
|
3360
|
+
console.log(pc2.green("\u2705 Screenshots are identical"));
|
|
3361
|
+
} else {
|
|
3362
|
+
console.log(pc2.yellow("\u26A0\uFE0F Screenshots differ"));
|
|
3363
|
+
console.log(pc2.dim(` Baseline size: ${baselineBuffer.length} bytes`));
|
|
3364
|
+
console.log(pc2.dim(` Current size: ${compareBuffer.length} bytes`));
|
|
3365
|
+
console.log("");
|
|
3366
|
+
console.log(
|
|
3367
|
+
pc2.dim(
|
|
3368
|
+
"For detailed LLM-based comparison, use the GitHub App's visual testing."
|
|
3369
|
+
)
|
|
3370
|
+
);
|
|
3371
|
+
console.log(pc2.dim(`Comparison saved to: ${comparePath}`));
|
|
3372
|
+
process.exit(1);
|
|
3373
|
+
}
|
|
3374
|
+
} catch (error) {
|
|
3375
|
+
console.error(pc2.red("\n\u274C Failed to compare:"), error);
|
|
3376
|
+
process.exit(1);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
async function visualListCommand(options) {
|
|
3380
|
+
const metadata = loadMetadata();
|
|
3381
|
+
const baselines = Object.values(metadata);
|
|
3382
|
+
if (options.json) {
|
|
3383
|
+
console.log(JSON.stringify(baselines, null, 2));
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
if (baselines.length === 0) {
|
|
3387
|
+
console.log(pc2.dim("No baselines found."));
|
|
3388
|
+
console.log(pc2.dim("Create one with: workflow visual capture <name> <url>"));
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
console.log(pc2.cyan("\n\u{1F4F8} Visual Baselines\n"));
|
|
3392
|
+
for (const baseline of baselines) {
|
|
3393
|
+
console.log(` ${pc2.bold(baseline.name)}`);
|
|
3394
|
+
console.log(` URL: ${baseline.url}`);
|
|
3395
|
+
console.log(` Viewport: ${baseline.width}x${baseline.height}`);
|
|
3396
|
+
console.log(` Path: ${baseline.path}`);
|
|
3397
|
+
console.log(` Updated: ${baseline.updatedAt}`);
|
|
3398
|
+
console.log("");
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
async function visualUpdateCommand(name, options) {
|
|
3402
|
+
const metadata = loadMetadata();
|
|
3403
|
+
const baseline = metadata[name];
|
|
3404
|
+
if (!baseline) {
|
|
3405
|
+
console.log(pc2.red(`\u274C Baseline "${name}" not found`));
|
|
3406
|
+
process.exit(1);
|
|
3407
|
+
}
|
|
3408
|
+
console.log(pc2.cyan(`
|
|
3409
|
+
\u{1F504} Updating baseline "${name}"...
|
|
3410
|
+
`));
|
|
3411
|
+
await visualCaptureCommand(name, baseline.url, {
|
|
3412
|
+
width: options.width || String(baseline.width),
|
|
3413
|
+
height: options.height || String(baseline.height),
|
|
3414
|
+
fullPage: options.fullPage,
|
|
3415
|
+
output: baseline.path
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
async function visualApproveCommand(name) {
|
|
3419
|
+
const metadata = loadMetadata();
|
|
3420
|
+
const baseline = metadata[name];
|
|
3421
|
+
if (!baseline) {
|
|
3422
|
+
console.log(pc2.red(`\u274C Baseline "${name}" not found`));
|
|
3423
|
+
process.exit(1);
|
|
3424
|
+
}
|
|
3425
|
+
const confirm8 = await p8.confirm({
|
|
3426
|
+
message: `Update baseline "${name}" with the latest comparison?`,
|
|
3427
|
+
initialValue: false
|
|
3428
|
+
});
|
|
3429
|
+
if (p8.isCancel(confirm8) || !confirm8) {
|
|
3430
|
+
console.log(pc2.dim("Cancelled."));
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
const baselinesDir = getBaselinesDir();
|
|
3434
|
+
const comparisonsDir = join11(baselinesDir, "comparisons");
|
|
3435
|
+
if (!existsSync11(comparisonsDir)) {
|
|
3436
|
+
console.log(pc2.yellow("No comparisons found to approve."));
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
console.log(
|
|
3440
|
+
pc2.yellow(
|
|
3441
|
+
"To approve, re-capture the baseline with: workflow visual update " + name
|
|
3442
|
+
)
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
|
|
2711
3446
|
// src/cli/index.ts
|
|
2712
3447
|
var program = new Command2();
|
|
2713
|
-
program.name("workflow").description(
|
|
2714
|
-
|
|
2715
|
-
|
|
3448
|
+
program.name("workflow").description(
|
|
3449
|
+
"A self-evolving workflow management system for AI agent development"
|
|
3450
|
+
).version("1.0.0");
|
|
3451
|
+
program.command("init").description("Initialize workflow in current project").option("--migrate", "Auto-detect existing patterns and migrate").option("--workspace", "Initialize for multiple repositories").option(
|
|
3452
|
+
"--preset <preset>",
|
|
3453
|
+
"Preset to use (saas, library, api, ecommerce, cms, custom)"
|
|
3454
|
+
).option("--name <name>", "Project name").option("-y, --yes", "Skip confirmation prompts").action(initCommand);
|
|
3455
|
+
program.command("validate <type>").description("Validate branch name, commit message, or PR title").argument("<type>", "What to validate: branch, commit, or pr").argument(
|
|
3456
|
+
"[value]",
|
|
3457
|
+
"Value to validate (defaults to current branch/HEAD commit)"
|
|
3458
|
+
).option(
|
|
3459
|
+
"--suggest-on-error",
|
|
3460
|
+
"Offer improvement suggestions on validation errors"
|
|
3461
|
+
).action(validateCommand);
|
|
2716
3462
|
program.addCommand(createConfigCommand());
|
|
2717
|
-
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option(
|
|
3463
|
+
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option(
|
|
3464
|
+
"--category <category>",
|
|
3465
|
+
"Category: feature, bug, documentation, performance, other"
|
|
3466
|
+
).action(suggestCommand);
|
|
2718
3467
|
program.command("setup").description("Add workflow scripts to package.json").action(setupCommand);
|
|
2719
|
-
program.command("doctor").description("Run health check and get optimization suggestions").option(
|
|
3468
|
+
program.command("doctor").description("Run health check and get optimization suggestions").option(
|
|
3469
|
+
"--check-guidelines-only",
|
|
3470
|
+
"Only check mandatory guidelines exist (exits 0 or 1)"
|
|
3471
|
+
).action(doctorCommand);
|
|
2720
3472
|
program.command("hooks").description("Manage git hooks (install, uninstall, status)").argument("<action>", "Action: install, uninstall, status").action(hooksCommand);
|
|
2721
3473
|
program.command("github").description("Manage GitHub Actions CI (setup, check)").argument("<action>", "Action: setup, check").action(githubCommand);
|
|
2722
|
-
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3474
|
+
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3475
|
+
"--scopes <scopes>",
|
|
3476
|
+
"Comma-separated scopes (format: name:description:emoji:category)"
|
|
3477
|
+
).option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
2723
3478
|
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);
|
|
3479
|
+
program.command("fix").description("Auto-heal pipeline failures using LLM").option("--error <error>", "Error message to fix (required)").option("--context <context>", "Additional context JSON").option("--files <files>", "Comma-separated file paths to analyze").option("--auto", "Apply fix automatically without confirmation").option("--dry-run", "Show what would be changed without applying").action((options) => {
|
|
3480
|
+
fixCommand({
|
|
3481
|
+
error: options.error,
|
|
3482
|
+
context: options.context,
|
|
3483
|
+
files: options.files?.split(","),
|
|
3484
|
+
auto: options.auto,
|
|
3485
|
+
dryRun: options.dryRun
|
|
3486
|
+
});
|
|
3487
|
+
});
|
|
3488
|
+
var visual = program.command("visual").description("Visual testing commands");
|
|
3489
|
+
visual.command("capture").description("Capture a new baseline screenshot").argument("<name>", "Name for the baseline").argument("<url>", "URL to capture").option("-w, --width <width>", "Viewport width", "1280").option("-h, --height <height>", "Viewport height", "720").option("--full-page", "Capture full page").option("-o, --output <path>", "Output path").option("--wait-for <selector>", "Wait for selector before capture").option("--delay <ms>", "Additional delay in ms").action(visualCaptureCommand);
|
|
3490
|
+
visual.command("compare").description("Compare a URL against a baseline").argument("<url>", "URL to compare").option("-b, --baseline <name>", "Baseline name to compare against (required)").option("-o, --output <path>", "Output path for comparison screenshot").option("-t, --threshold <percent>", "Difference threshold percentage").action((url, options) => {
|
|
3491
|
+
if (!options.baseline) {
|
|
3492
|
+
console.error("Error: --baseline is required");
|
|
3493
|
+
process.exit(1);
|
|
3494
|
+
}
|
|
3495
|
+
visualCompareCommand(url, options);
|
|
3496
|
+
});
|
|
3497
|
+
visual.command("list").description("List all baselines").option("--json", "Output as JSON").action(visualListCommand);
|
|
3498
|
+
visual.command("update").description("Update an existing baseline").argument("<name>", "Baseline name to update").option("-w, --width <width>", "Override viewport width").option("-h, --height <height>", "Override viewport height").option("--full-page", "Capture full page").action(visualUpdateCommand);
|
|
3499
|
+
visual.command("approve").description("Approve a comparison as the new baseline").argument("<name>", "Baseline name to approve").action(visualApproveCommand);
|
|
2724
3500
|
program.parse();
|
|
2725
3501
|
//# sourceMappingURL=index.js.map
|