workflow-agent-cli 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-VFN3BY56.js +120 -0
- package/dist/chunk-VFN3BY56.js.map +1 -0
- package/dist/chunk-X2NQJ2ZY.js +170 -0
- package/dist/chunk-X2NQJ2ZY.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1206 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +11 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/schema-CiJ4W7in.d.ts +97 -0
- package/dist/scripts/postinstall.d.ts +1 -0
- package/dist/scripts/postinstall.js +73 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/validators/index.d.ts +16 -0
- package/dist/validators/index.js +17 -0
- package/dist/validators/index.js.map +1 -0
- package/package.json +80 -0
- package/templates/AGENT_EDITING_INSTRUCTIONS.md +887 -0
- package/templates/BRANCHING_STRATEGY.md +442 -0
- package/templates/COMPONENT_LIBRARY.md +611 -0
- package/templates/CUSTOM_SCOPE_TEMPLATE.md +228 -0
- package/templates/DEPLOYMENT_STRATEGY.md +509 -0
- package/templates/Guidelines.md +62 -0
- package/templates/LIBRARY_INVENTORY.md +615 -0
- package/templates/PROJECT_TEMPLATE_README.md +347 -0
- package/templates/SCOPE_CREATION_WORKFLOW.md +286 -0
- package/templates/SELF_IMPROVEMENT_MANDATE.md +298 -0
- package/templates/SINGLE_SOURCE_OF_TRUTH.md +492 -0
- package/templates/TESTING_STRATEGY.md +801 -0
- package/templates/_TEMPLATE_EXAMPLE.md +28 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/config/index.ts
|
|
2
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
3
|
+
|
|
4
|
+
// src/config/schema.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var RESERVED_SCOPE_NAMES = ["init", "create", "build", "test", "config", "docs", "ci", "deps"];
|
|
7
|
+
var ScopeSchema = z.object({
|
|
8
|
+
name: z.string().min(1).max(32, "Scope name must be 32 characters or less").regex(/^[a-z0-9-]+$/, "Scope name must be lowercase alphanumeric with hyphens").refine((name) => !RESERVED_SCOPE_NAMES.includes(name), {
|
|
9
|
+
message: `Scope name cannot be a reserved word: ${RESERVED_SCOPE_NAMES.join(", ")}`
|
|
10
|
+
}),
|
|
11
|
+
description: z.string().min(10, "Scope description must be at least 10 characters"),
|
|
12
|
+
emoji: z.string().optional(),
|
|
13
|
+
category: z.enum(["auth", "features", "infrastructure", "documentation", "testing", "performance", "other"]).optional()
|
|
14
|
+
});
|
|
15
|
+
var BranchTypeSchema = z.enum([
|
|
16
|
+
"feature",
|
|
17
|
+
"bugfix",
|
|
18
|
+
"hotfix",
|
|
19
|
+
"chore",
|
|
20
|
+
"refactor",
|
|
21
|
+
"docs",
|
|
22
|
+
"test",
|
|
23
|
+
"release"
|
|
24
|
+
]);
|
|
25
|
+
var ConventionalTypeSchema = z.enum([
|
|
26
|
+
"feat",
|
|
27
|
+
"fix",
|
|
28
|
+
"refactor",
|
|
29
|
+
"chore",
|
|
30
|
+
"docs",
|
|
31
|
+
"test",
|
|
32
|
+
"perf",
|
|
33
|
+
"style",
|
|
34
|
+
"ci",
|
|
35
|
+
"build",
|
|
36
|
+
"revert"
|
|
37
|
+
]);
|
|
38
|
+
var EnforcementLevelSchema = z.enum(["strict", "advisory", "learning"]);
|
|
39
|
+
var AnalyticsConfigSchema = z.object({
|
|
40
|
+
enabled: z.boolean().default(false),
|
|
41
|
+
shareAnonymous: z.boolean().default(false)
|
|
42
|
+
});
|
|
43
|
+
var WorkflowConfigSchema = z.object({
|
|
44
|
+
projectName: z.string().min(1),
|
|
45
|
+
scopes: z.array(ScopeSchema).min(1),
|
|
46
|
+
branchTypes: z.array(BranchTypeSchema).optional(),
|
|
47
|
+
conventionalTypes: z.array(ConventionalTypeSchema).optional(),
|
|
48
|
+
enforcement: EnforcementLevelSchema.default("strict"),
|
|
49
|
+
language: z.string().default("en"),
|
|
50
|
+
analytics: AnalyticsConfigSchema.optional(),
|
|
51
|
+
adapter: z.string().optional(),
|
|
52
|
+
syncRemote: z.string().optional()
|
|
53
|
+
});
|
|
54
|
+
function validateScopeDefinitions(scopes) {
|
|
55
|
+
const errors = [];
|
|
56
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const scope of scopes) {
|
|
58
|
+
if (seenNames.has(scope.name)) {
|
|
59
|
+
errors.push(`Duplicate scope name: "${scope.name}"`);
|
|
60
|
+
}
|
|
61
|
+
seenNames.add(scope.name);
|
|
62
|
+
const result = ScopeSchema.safeParse(scope);
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
result.error.errors.forEach((err) => {
|
|
65
|
+
errors.push(`Scope "${scope.name}": ${err.message}`);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
valid: errors.length === 0,
|
|
71
|
+
errors
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/config/index.ts
|
|
76
|
+
import { join } from "path";
|
|
77
|
+
import { existsSync } from "fs";
|
|
78
|
+
var explorer = cosmiconfig("workflow", {
|
|
79
|
+
searchPlaces: [
|
|
80
|
+
"workflow.config.ts",
|
|
81
|
+
"workflow.config.js",
|
|
82
|
+
"workflow.config.json",
|
|
83
|
+
".workflowrc",
|
|
84
|
+
".workflowrc.json",
|
|
85
|
+
"package.json"
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
89
|
+
try {
|
|
90
|
+
const result = await explorer.search(cwd);
|
|
91
|
+
if (!result || !result.config) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const validated = WorkflowConfigSchema.parse(result.config);
|
|
95
|
+
return validated;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
throw new Error(`Failed to load workflow config: ${error.message}`);
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function hasConfig(cwd = process.cwd()) {
|
|
104
|
+
const configPaths = [
|
|
105
|
+
"workflow.config.ts",
|
|
106
|
+
"workflow.config.js",
|
|
107
|
+
"workflow.config.json",
|
|
108
|
+
".workflowrc",
|
|
109
|
+
".workflowrc.json"
|
|
110
|
+
];
|
|
111
|
+
return configPaths.some((path) => existsSync(join(cwd, path)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
WorkflowConfigSchema,
|
|
116
|
+
validateScopeDefinitions,
|
|
117
|
+
loadConfig,
|
|
118
|
+
hasConfig
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=chunk-VFN3BY56.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/index.ts","../src/config/schema.ts"],"sourcesContent":["import { cosmiconfig } from 'cosmiconfig';\nimport { WorkflowConfig, WorkflowConfigSchema } from './schema.js';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\n\nconst explorer = cosmiconfig('workflow', {\n searchPlaces: [\n 'workflow.config.ts',\n 'workflow.config.js',\n 'workflow.config.json',\n '.workflowrc',\n '.workflowrc.json',\n 'package.json',\n ],\n});\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<WorkflowConfig | null> {\n try {\n const result = await explorer.search(cwd);\n \n if (!result || !result.config) {\n return null;\n }\n\n // Validate config against schema\n const validated = WorkflowConfigSchema.parse(result.config);\n return validated;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to load workflow config: ${error.message}`);\n }\n throw error;\n }\n}\n\nexport function hasConfig(cwd: string = process.cwd()): boolean {\n const configPaths = [\n 'workflow.config.ts',\n 'workflow.config.js',\n 'workflow.config.json',\n '.workflowrc',\n '.workflowrc.json',\n ];\n\n return configPaths.some((path) => existsSync(join(cwd, path)));\n}\n\nexport { WorkflowConfig, WorkflowConfigSchema, Scope, BranchType, ConventionalType } from './schema.js';\n","import { z } from 'zod';\n\n// Reserved scope names that cannot be used\nconst RESERVED_SCOPE_NAMES = ['init', 'create', 'build', 'test', 'config', 'docs', 'ci', 'deps'];\n\nexport const ScopeSchema = z.object({\n name: z.string()\n .min(1)\n .max(32, 'Scope name must be 32 characters or less')\n .regex(/^[a-z0-9-]+$/, 'Scope name must be lowercase alphanumeric with hyphens')\n .refine((name) => !RESERVED_SCOPE_NAMES.includes(name), {\n message: `Scope name cannot be a reserved word: ${RESERVED_SCOPE_NAMES.join(', ')}`,\n }),\n description: z.string().min(10, 'Scope description must be at least 10 characters'),\n emoji: z.string().optional(),\n category: z.enum(['auth', 'features', 'infrastructure', 'documentation', 'testing', 'performance', 'other']).optional(),\n});\n\nexport const BranchTypeSchema = z.enum([\n 'feature',\n 'bugfix',\n 'hotfix',\n 'chore',\n 'refactor',\n 'docs',\n 'test',\n 'release',\n]);\n\nexport const ConventionalTypeSchema = z.enum([\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n 'ci',\n 'build',\n 'revert',\n]);\n\nexport const EnforcementLevelSchema = z.enum(['strict', 'advisory', 'learning']);\n\nexport const AnalyticsConfigSchema = z.object({\n enabled: z.boolean().default(false),\n shareAnonymous: z.boolean().default(false),\n});\n\nexport const WorkflowConfigSchema = z.object({\n projectName: z.string().min(1),\n scopes: z.array(ScopeSchema).min(1),\n branchTypes: z.array(BranchTypeSchema).optional(),\n conventionalTypes: z.array(ConventionalTypeSchema).optional(),\n enforcement: EnforcementLevelSchema.default('strict'),\n language: z.string().default('en'),\n analytics: AnalyticsConfigSchema.optional(),\n adapter: z.string().optional(),\n syncRemote: z.string().optional(),\n});\n\nexport type Scope = z.infer<typeof ScopeSchema>;\nexport type BranchType = z.infer<typeof BranchTypeSchema>;\nexport type ConventionalType = z.infer<typeof ConventionalTypeSchema>;\nexport type EnforcementLevel = z.infer<typeof EnforcementLevelSchema>;\nexport type AnalyticsConfig = z.infer<typeof AnalyticsConfigSchema>;\nexport type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;\n\nexport const defaultBranchTypes: BranchType[] = [\n 'feature',\n 'bugfix',\n 'hotfix',\n 'chore',\n 'refactor',\n 'docs',\n 'test',\n];\n\nexport const defaultConventionalTypes: ConventionalType[] = [\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n];\n\n/**\n * Validates scope definitions for duplicates, description quality, and category values\n * @param scopes Array of scope definitions to validate\n * @returns Object with validation result and error messages\n */\nexport function validateScopeDefinitions(scopes: Scope[]): { \n valid: boolean; \n errors: string[];\n} {\n const errors: string[] = [];\n const seenNames = new Set<string>();\n\n for (const scope of scopes) {\n // Check for duplicate names\n if (seenNames.has(scope.name)) {\n errors.push(`Duplicate scope name: \"${scope.name}\"`);\n }\n seenNames.add(scope.name);\n\n // Validate using schema (this will catch min length, reserved names, etc.)\n const result = ScopeSchema.safeParse(scope);\n if (!result.success) {\n result.error.errors.forEach(err => {\n errors.push(`Scope \"${scope.name}\": ${err.message}`);\n });\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;;;ACA5B,SAAS,SAAS;AAGlB,IAAM,uBAAuB,CAAC,QAAQ,UAAU,SAAS,QAAQ,UAAU,QAAQ,MAAM,MAAM;AAExF,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,MAAM,EAAE,OAAO,EACZ,IAAI,CAAC,EACL,IAAI,IAAI,0CAA0C,EAClD,MAAM,gBAAgB,wDAAwD,EAC9E,OAAO,CAAC,SAAS,CAAC,qBAAqB,SAAS,IAAI,GAAG;AAAA,IACtD,SAAS,yCAAyC,qBAAqB,KAAK,IAAI,CAAC;AAAA,EACnF,CAAC;AAAA,EACH,aAAa,EAAE,OAAO,EAAE,IAAI,IAAI,kDAAkD;AAAA,EAClF,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,UAAU,EAAE,KAAK,CAAC,QAAQ,YAAY,kBAAkB,iBAAiB,WAAW,eAAe,OAAO,CAAC,EAAE,SAAS;AACxH,CAAC;AAEM,IAAM,mBAAmB,EAAE,KAAK;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,yBAAyB,EAAE,KAAK;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,yBAAyB,EAAE,KAAK,CAAC,UAAU,YAAY,UAAU,CAAC;AAExE,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,SAAS,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EAClC,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAC3C,CAAC;AAEM,IAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,QAAQ,EAAE,MAAM,WAAW,EAAE,IAAI,CAAC;AAAA,EAClC,aAAa,EAAE,MAAM,gBAAgB,EAAE,SAAS;AAAA,EAChD,mBAAmB,EAAE,MAAM,sBAAsB,EAAE,SAAS;AAAA,EAC5D,aAAa,uBAAuB,QAAQ,QAAQ;AAAA,EACpD,UAAU,EAAE,OAAO,EAAE,QAAQ,IAAI;AAAA,EACjC,WAAW,sBAAsB,SAAS;AAAA,EAC1C,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAmCM,SAAS,yBAAyB,QAGvC;AACA,QAAM,SAAmB,CAAC;AAC1B,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,SAAS,QAAQ;AAE1B,QAAI,UAAU,IAAI,MAAM,IAAI,GAAG;AAC7B,aAAO,KAAK,0BAA0B,MAAM,IAAI,GAAG;AAAA,IACrD;AACA,cAAU,IAAI,MAAM,IAAI;AAGxB,UAAM,SAAS,YAAY,UAAU,KAAK;AAC1C,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,MAAM,OAAO,QAAQ,SAAO;AACjC,eAAO,KAAK,UAAU,MAAM,IAAI,MAAM,IAAI,OAAO,EAAE;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB;AAAA,EACF;AACF;;;ADxHA,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAE3B,IAAM,WAAW,YAAY,YAAY;AAAA,EACvC,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAED,eAAsB,WAAW,MAAc,QAAQ,IAAI,GAAmC;AAC5F,MAAI;AACF,UAAM,SAAS,MAAM,SAAS,OAAO,GAAG;AAExC,QAAI,CAAC,UAAU,CAAC,OAAO,QAAQ;AAC7B,aAAO;AAAA,IACT;AAGA,UAAM,YAAY,qBAAqB,MAAM,OAAO,MAAM;AAC1D,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI,MAAM,mCAAmC,MAAM,OAAO,EAAE;AAAA,IACpE;AACA,UAAM;AAAA,EACR;AACF;AAEO,SAAS,UAAU,MAAc,QAAQ,IAAI,GAAY;AAC9D,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,YAAY,KAAK,CAAC,SAAS,WAAW,KAAK,KAAK,IAAI,CAAC,CAAC;AAC/D;","names":[]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/validators/index.ts
|
|
2
|
+
import didYouMean from "didyoumean2";
|
|
3
|
+
import { readdir } from "fs/promises";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var customScopesCache = null;
|
|
6
|
+
var cacheTimestamp = 0;
|
|
7
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
8
|
+
async function discoverCustomScopes(workspacePath = process.cwd()) {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
if (customScopesCache && now - cacheTimestamp < CACHE_TTL) {
|
|
11
|
+
return customScopesCache;
|
|
12
|
+
}
|
|
13
|
+
const discoveredScopes = [];
|
|
14
|
+
try {
|
|
15
|
+
const workspaceLocations = [
|
|
16
|
+
join(workspacePath, "packages"),
|
|
17
|
+
workspacePath
|
|
18
|
+
];
|
|
19
|
+
for (const location of workspaceLocations) {
|
|
20
|
+
try {
|
|
21
|
+
const entries = await readdir(location, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.isDirectory() && entry.name.startsWith("scopes-")) {
|
|
24
|
+
const indexPath = join(location, entry.name, "src", "index.ts");
|
|
25
|
+
try {
|
|
26
|
+
const module = await import(indexPath);
|
|
27
|
+
const scopes = module.scopes || module.default?.scopes;
|
|
28
|
+
if (Array.isArray(scopes)) {
|
|
29
|
+
discoveredScopes.push(...scopes);
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
customScopesCache = discoveredScopes;
|
|
39
|
+
cacheTimestamp = now;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn("Warning: Error discovering custom scopes:", error);
|
|
42
|
+
}
|
|
43
|
+
return discoveredScopes;
|
|
44
|
+
}
|
|
45
|
+
function invalidateCustomScopesCache() {
|
|
46
|
+
customScopesCache = null;
|
|
47
|
+
cacheTimestamp = 0;
|
|
48
|
+
}
|
|
49
|
+
async function getAllScopes(config, workspacePath) {
|
|
50
|
+
const configScopes = config.scopes;
|
|
51
|
+
const customScopes = await discoverCustomScopes(workspacePath);
|
|
52
|
+
const scopeMap = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const scope of configScopes) {
|
|
54
|
+
scopeMap.set(scope.name, scope);
|
|
55
|
+
}
|
|
56
|
+
for (const scope of customScopes) {
|
|
57
|
+
if (!scopeMap.has(scope.name)) {
|
|
58
|
+
scopeMap.set(scope.name, scope);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Array.from(scopeMap.values());
|
|
62
|
+
}
|
|
63
|
+
async function validateBranchName(branchName, config, workspacePath) {
|
|
64
|
+
const branchTypes = config.branchTypes || ["feature", "bugfix", "hotfix", "chore", "refactor", "docs", "test"];
|
|
65
|
+
const allScopes = await getAllScopes(config, workspacePath);
|
|
66
|
+
const scopes = allScopes.map((s) => s.name);
|
|
67
|
+
const branchPattern = /^([a-z]+)\/([a-z0-9-]+)\/([a-z0-9-]+)$/;
|
|
68
|
+
const match = branchName.match(branchPattern);
|
|
69
|
+
if (!match) {
|
|
70
|
+
return {
|
|
71
|
+
valid: false,
|
|
72
|
+
error: `Branch name must follow format: <type>/<scope>/<description> (e.g., feature/auth/add-login)`,
|
|
73
|
+
suggestion: `Current: ${branchName}. All parts must be lowercase alphanumeric with hyphens.`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const [, type, scope, description] = match;
|
|
77
|
+
if (!branchTypes.includes(type)) {
|
|
78
|
+
const suggestion = didYouMean(type, branchTypes);
|
|
79
|
+
return {
|
|
80
|
+
valid: false,
|
|
81
|
+
error: `Invalid branch type '${type}'. Must be one of: ${branchTypes.join(", ")}`,
|
|
82
|
+
suggestion: suggestion ? `Did you mean '${suggestion}'?` : void 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (!scopes.includes(scope)) {
|
|
86
|
+
const suggestion = didYouMean(scope, scopes);
|
|
87
|
+
const scopeList = scopes.slice(0, 5).join(", ") + (scopes.length > 5 ? "..." : "");
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,
|
|
91
|
+
suggestion: suggestion ? `Did you mean '${suggestion}'?` : void 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (description.length < 3) {
|
|
95
|
+
return {
|
|
96
|
+
valid: false,
|
|
97
|
+
error: `Branch description '${description}' is too short (minimum 3 characters)`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { valid: true };
|
|
101
|
+
}
|
|
102
|
+
async function validateCommitMessage(message, config, workspacePath) {
|
|
103
|
+
const conventionalTypes = config.conventionalTypes || [
|
|
104
|
+
"feat",
|
|
105
|
+
"fix",
|
|
106
|
+
"refactor",
|
|
107
|
+
"chore",
|
|
108
|
+
"docs",
|
|
109
|
+
"test",
|
|
110
|
+
"perf",
|
|
111
|
+
"style"
|
|
112
|
+
];
|
|
113
|
+
const allScopes = await getAllScopes(config, workspacePath);
|
|
114
|
+
const scopes = allScopes.map((s) => s.name);
|
|
115
|
+
const commitPattern = /^([a-z]+)(?:\(([a-z0-9-]+)\))?: (.+)$/;
|
|
116
|
+
const match = message.match(commitPattern);
|
|
117
|
+
if (!match) {
|
|
118
|
+
return {
|
|
119
|
+
valid: false,
|
|
120
|
+
error: `Commit message must follow conventional commits format: <type>(<scope>): <description>`,
|
|
121
|
+
suggestion: `Example: feat(auth): add login validation`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const [, type, scope, description] = match;
|
|
125
|
+
if (!conventionalTypes.includes(type)) {
|
|
126
|
+
const suggestion = didYouMean(type, conventionalTypes);
|
|
127
|
+
return {
|
|
128
|
+
valid: false,
|
|
129
|
+
error: `Invalid commit type '${type}'. Must be one of: ${conventionalTypes.join(", ")}`,
|
|
130
|
+
suggestion: suggestion ? `Did you mean '${suggestion}'?` : void 0
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (scope && !scopes.includes(scope)) {
|
|
134
|
+
const suggestion = didYouMean(scope, scopes);
|
|
135
|
+
const scopeList = scopes.slice(0, 5).join(", ") + (scopes.length > 5 ? "..." : "");
|
|
136
|
+
return {
|
|
137
|
+
valid: false,
|
|
138
|
+
error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,
|
|
139
|
+
suggestion: suggestion ? `Did you mean '${suggestion}'?` : void 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (description.length < 10) {
|
|
143
|
+
return {
|
|
144
|
+
valid: false,
|
|
145
|
+
error: `Commit description is too short (minimum 10 characters)`,
|
|
146
|
+
suggestion: `Be more descriptive about what changed`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (description[0] !== description[0].toLowerCase()) {
|
|
150
|
+
return {
|
|
151
|
+
valid: false,
|
|
152
|
+
error: `Commit description must start with lowercase letter`,
|
|
153
|
+
suggestion: `Change '${description}' to '${description[0].toLowerCase()}${description.slice(1)}'`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return { valid: true };
|
|
157
|
+
}
|
|
158
|
+
async function validatePRTitle(title, config, workspacePath) {
|
|
159
|
+
return validateCommitMessage(title, config, workspacePath);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
discoverCustomScopes,
|
|
164
|
+
invalidateCustomScopesCache,
|
|
165
|
+
getAllScopes,
|
|
166
|
+
validateBranchName,
|
|
167
|
+
validateCommitMessage,
|
|
168
|
+
validatePRTitle
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=chunk-X2NQJ2ZY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validators/index.ts"],"sourcesContent":["import didYouMean from 'didyoumean2';\nimport type { WorkflowConfig, BranchType, Scope } from '../config/index.js';\nimport { readdir } from 'fs/promises';\nimport { join } from 'path';\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n suggestion?: string;\n}\n\n// Cache for discovered custom scopes\nlet customScopesCache: Scope[] | null = null;\nlet cacheTimestamp: number = 0;\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Discovers custom scope packages in the workspace and node_modules\n * @param workspacePath Path to workspace root\n * @returns Array of discovered scopes\n */\nexport async function discoverCustomScopes(workspacePath: string = process.cwd()): Promise<Scope[]> {\n // Check cache validity\n const now = Date.now();\n if (customScopesCache && (now - cacheTimestamp) < CACHE_TTL) {\n return customScopesCache;\n }\n\n const discoveredScopes: Scope[] = [];\n\n try {\n // Search for custom scope packages in workspace\n const workspaceLocations = [\n join(workspacePath, 'packages'),\n workspacePath,\n ];\n\n for (const location of workspaceLocations) {\n try {\n const entries = await readdir(location, { withFileTypes: true });\n \n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.startsWith('scopes-')) {\n const indexPath = join(location, entry.name, 'src', 'index.ts');\n try {\n const module = await import(indexPath);\n const scopes = module.scopes || module.default?.scopes;\n \n if (Array.isArray(scopes)) {\n discoveredScopes.push(...scopes);\n }\n } catch {\n // Silently skip packages that can't be loaded\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n\n // Update cache\n customScopesCache = discoveredScopes;\n cacheTimestamp = now;\n\n } catch (error) {\n // Return empty array on error\n console.warn('Warning: Error discovering custom scopes:', error);\n }\n\n return discoveredScopes;\n}\n\n/**\n * Invalidates the custom scopes cache (useful after config changes)\n */\nexport function invalidateCustomScopesCache(): void {\n customScopesCache = null;\n cacheTimestamp = 0;\n}\n\n/**\n * Gets all available scopes including custom discovered ones\n * @param config Workflow configuration\n * @param workspacePath Optional workspace path\n * @returns Combined array of scopes\n */\nexport async function getAllScopes(config: WorkflowConfig, workspacePath?: string): Promise<Scope[]> {\n const configScopes = config.scopes;\n const customScopes = await discoverCustomScopes(workspacePath);\n \n // Merge and deduplicate by name\n const scopeMap = new Map<string, Scope>();\n \n // Config scopes take precedence\n for (const scope of configScopes) {\n scopeMap.set(scope.name, scope);\n }\n \n // Add custom scopes that don't conflict\n for (const scope of customScopes) {\n if (!scopeMap.has(scope.name)) {\n scopeMap.set(scope.name, scope);\n }\n }\n \n return Array.from(scopeMap.values());\n}\n\nexport async function validateBranchName(\n branchName: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n const branchTypes = config.branchTypes || ['feature', 'bugfix', 'hotfix', 'chore', 'refactor', 'docs', 'test'];\n const allScopes = await getAllScopes(config, workspacePath);\n const scopes = allScopes.map((s) => s.name);\n\n // Expected format: <type>/<scope>/<description>\n const branchPattern = /^([a-z]+)\\/([a-z0-9-]+)\\/([a-z0-9-]+)$/;\n const match = branchName.match(branchPattern);\n\n if (!match) {\n return {\n valid: false,\n error: `Branch name must follow format: <type>/<scope>/<description> (e.g., feature/auth/add-login)`,\n suggestion: `Current: ${branchName}. All parts must be lowercase alphanumeric with hyphens.`,\n };\n }\n\n const [, type, scope, description] = match;\n\n // Validate type\n if (!branchTypes.includes(type as BranchType)) {\n const suggestion = didYouMean(type, branchTypes);\n return {\n valid: false,\n error: `Invalid branch type '${type}'. Must be one of: ${branchTypes.join(', ')}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate scope\n if (!scopes.includes(scope)) {\n const suggestion = didYouMean(scope, scopes);\n const scopeList = scopes.slice(0, 5).join(', ') + (scopes.length > 5 ? '...' : '');\n return {\n valid: false,\n error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate description (not empty, meaningful)\n if (description.length < 3) {\n return {\n valid: false,\n error: `Branch description '${description}' is too short (minimum 3 characters)`,\n };\n }\n\n return { valid: true };\n}\n\nexport async function validateCommitMessage(\n message: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n const conventionalTypes = config.conventionalTypes || [\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n ];\n const allScopes = await getAllScopes(config, workspacePath);\n const scopes = allScopes.map((s) => s.name);\n\n // Expected format: <type>(<scope>): <description>\n const commitPattern = /^([a-z]+)(?:\\(([a-z0-9-]+)\\))?: (.+)$/;\n const match = message.match(commitPattern);\n\n if (!match) {\n return {\n valid: false,\n error: `Commit message must follow conventional commits format: <type>(<scope>): <description>`,\n suggestion: `Example: feat(auth): add login validation`,\n };\n }\n\n const [, type, scope, description] = match;\n\n // Validate type\n if (!conventionalTypes.includes(type as any)) {\n const suggestion = didYouMean(type, conventionalTypes);\n return {\n valid: false,\n error: `Invalid commit type '${type}'. Must be one of: ${conventionalTypes.join(', ')}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate scope (optional but recommended)\n if (scope && !scopes.includes(scope)) {\n const suggestion = didYouMean(scope, scopes);\n const scopeList = scopes.slice(0, 5).join(', ') + (scopes.length > 5 ? '...' : '');\n return {\n valid: false,\n error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate description\n if (description.length < 10) {\n return {\n valid: false,\n error: `Commit description is too short (minimum 10 characters)`,\n suggestion: `Be more descriptive about what changed`,\n };\n }\n\n if (description[0] !== description[0].toLowerCase()) {\n return {\n valid: false,\n error: `Commit description must start with lowercase letter`,\n suggestion: `Change '${description}' to '${description[0].toLowerCase()}${description.slice(1)}'`,\n };\n }\n\n return { valid: true };\n}\n\nexport async function validatePRTitle(\n title: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n // PR titles follow same format as commit messages\n return validateCommitMessage(title, config, workspacePath);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|