@contractspec/bundle.workspace 1.45.4 → 1.45.6
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/adapters/fs.d.ts +2 -1
- package/dist/adapters/fs.d.ts.map +1 -1
- package/dist/adapters/fs.js +1 -1
- package/dist/adapters/fs.js.map +1 -1
- package/dist/adapters/git.js +29 -0
- package/dist/adapters/git.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.js +5 -2
- package/dist/ports/git.d.ts +14 -1
- package/dist/ports/git.d.ts.map +1 -1
- package/dist/ports/index.d.ts +3 -2
- package/dist/ports/rulesync.d.ts +38 -0
- package/dist/ports/rulesync.d.ts.map +1 -0
- package/dist/services/doctor/checks/cli.js +9 -0
- package/dist/services/doctor/checks/cli.js.map +1 -1
- package/dist/services/doctor/checks/config.js +132 -0
- package/dist/services/doctor/checks/config.js.map +1 -1
- package/dist/services/doctor/checks/deps.js +13 -0
- package/dist/services/doctor/checks/deps.js.map +1 -1
- package/dist/services/doctor/checks/workspace.js +20 -1
- package/dist/services/doctor/checks/workspace.js.map +1 -1
- package/dist/services/hooks/hooks-service.d.ts +24 -0
- package/dist/services/hooks/hooks-service.d.ts.map +1 -0
- package/dist/services/hooks/hooks-service.js +126 -0
- package/dist/services/hooks/hooks-service.js.map +1 -0
- package/dist/services/hooks/index.d.ts +10 -0
- package/dist/services/hooks/index.d.ts.map +1 -0
- package/dist/services/hooks/index.js +12 -0
- package/dist/services/hooks/index.js.map +1 -0
- package/dist/services/hooks/types.d.ts +56 -0
- package/dist/services/hooks/types.d.ts.map +1 -0
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +3 -0
- package/dist/services/rulesync.d.ts +17 -0
- package/dist/services/rulesync.d.ts.map +1 -0
- package/dist/services/rulesync.js +71 -0
- package/dist/services/rulesync.js.map +1 -0
- package/dist/services/setup/config-generators.d.ts.map +1 -1
- package/dist/services/setup/config-generators.js +14 -0
- package/dist/services/setup/config-generators.js.map +1 -1
- package/dist/services/upgrade/index.d.ts +10 -0
- package/dist/services/upgrade/index.d.ts.map +1 -0
- package/dist/services/upgrade/index.js +15 -0
- package/dist/services/upgrade/index.js.map +1 -0
- package/dist/services/upgrade/types.d.ts +78 -0
- package/dist/services/upgrade/types.d.ts.map +1 -0
- package/dist/services/upgrade/upgrade-service.d.ts +38 -0
- package/dist/services/upgrade/upgrade-service.d.ts.map +1 -0
- package/dist/services/upgrade/upgrade-service.js +201 -0
- package/dist/services/upgrade/upgrade-service.js.map +1 -0
- package/dist/services/versioning/conventional-commits.d.ts +95 -0
- package/dist/services/versioning/conventional-commits.d.ts.map +1 -0
- package/dist/services/versioning/conventional-commits.js +184 -0
- package/dist/services/versioning/conventional-commits.js.map +1 -0
- package/dist/services/versioning/index.d.ts +4 -3
- package/dist/services/versioning/index.js +13 -2
- package/dist/services/versioning/index.js.map +1 -1
- package/dist/services/versioning/types.d.ts +9 -7
- package/dist/services/versioning/types.d.ts.map +1 -1
- package/dist/services/versioning/versioning-service.d.ts +43 -1
- package/dist/services/versioning/versioning-service.d.ts.map +1 -1
- package/dist/services/versioning/versioning-service.js +144 -2
- package/dist/services/versioning/versioning-service.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.js","names":["results: CheckResult[]","detectedManager: string | null"],"sources":["../../../../src/services/doctor/checks/deps.ts"],"sourcesContent":["/**\n * Dependencies health checks.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckResult, CheckContext } from '../types';\n\nconst execAsync = promisify(exec);\n\n/**\n * Run dependency-related health checks.\n */\nexport async function runDepsChecks(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Check Node.js availability\n results.push(await checkNodeJs(ctx));\n\n // Check Bun availability\n results.push(await checkBun(ctx));\n\n // Check package manager\n results.push(await checkPackageManager(fs, ctx));\n\n // Check if node_modules exists\n results.push(await checkNodeModules(fs, ctx));\n\n // Check if @contractspec/lib.contracts is installed\n results.push(await checkContractsLibrary(fs, ctx));\n\n return results;\n}\n\n/**\n * Check if Node.js is available.\n */\nasync function checkNodeJs(ctx: CheckContext): Promise<CheckResult> {\n try {\n const { stdout } = await execAsync('node --version', {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n const version = stdout.trim();\n\n return {\n category: 'deps',\n name: 'Node.js',\n status: 'pass',\n message: `Node.js ${version} available`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Node.js',\n status: 'fail',\n message: 'Node.js not found',\n details: 'Install Node.js from https://nodejs.org',\n };\n }\n}\n\n/**\n * Check if Bun is available.\n */\nasync function checkBun(ctx: CheckContext): Promise<CheckResult> {\n try {\n const { stdout } = await execAsync('bun --version', {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n const version = stdout.trim();\n\n return {\n category: 'deps',\n name: 'Bun Runtime',\n status: 'pass',\n message: `Bun ${version} available`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Bun Runtime',\n status: 'warn',\n message: 'Bun not found (optional but recommended)',\n details: 'Install Bun from https://bun.sh for faster execution',\n };\n }\n}\n\n/**\n * Detect and check the package manager.\n */\nasync function checkPackageManager(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // Check for lock files to determine package manager\n const lockFiles = [\n { file: 'bun.lockb', name: 'bun' },\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'package-lock.json', name: 'npm' },\n ];\n\n let detectedManager: string | null = null;\n\n for (const { file, name } of lockFiles) {\n const lockPath = fs.join(ctx.workspaceRoot, file);\n if (await fs.exists(lockPath)) {\n detectedManager = name;\n break;\n }\n }\n\n if (!detectedManager) {\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'warn',\n message: 'No lock file found',\n details: 'Run npm install, yarn, pnpm install, or bun install',\n };\n }\n\n // Verify the package manager is available\n try {\n await execAsync(`${detectedManager} --version`, {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'pass',\n message: `Using ${detectedManager}`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'fail',\n message: `${detectedManager} detected but not available`,\n details: `Install ${detectedManager} or use a different package manager`,\n };\n }\n}\n\n/**\n * Check if node_modules exists.\n */\nasync function checkNodeModules(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n const nodeModulesPath = fs.join(ctx.workspaceRoot, 'node_modules');\n\n const exists = await fs.exists(nodeModulesPath);\n if (exists) {\n return {\n category: 'deps',\n name: 'Dependencies Installed',\n status: 'pass',\n message: 'node_modules directory exists',\n };\n }\n\n return {\n category: 'deps',\n name: 'Dependencies Installed',\n status: 'fail',\n message: 'node_modules not found',\n details: 'Run your package manager install command',\n fix: {\n description: 'Install dependencies',\n apply: async () => {\n try {\n // Try bun first, then npm\n try {\n await execAsync('bun install', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with bun' };\n } catch {\n await execAsync('npm install', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with npm' };\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n}\n\n/**\n * Check if @contractspec/lib.contracts is installed.\n */\nasync function checkContractsLibrary(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n const packageJsonPath = fs.join(ctx.workspaceRoot, 'package.json');\n\n try {\n const content = await fs.readFile(packageJsonPath);\n const packageJson = JSON.parse(content) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n if ('@contractspec/lib.contracts' in allDeps) {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'pass',\n message: `@contractspec/lib.contracts installed (${allDeps['@contractspec/lib.contracts']})`,\n };\n }\n\n // In monorepo root, we don't expect the library to be installed directly\n if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'pass',\n message: 'Monorepo root detected (library check skipped)',\n details: 'Run doctor in specific packages to verify dependencies',\n };\n }\n\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'fail',\n message: '@contractspec/lib.contracts not installed',\n details: 'Run \"contractspec quickstart\" to install required packages',\n fix: {\n description: 'Install @contractspec/lib.contracts and dependencies',\n apply: async () => {\n try {\n // Try bun first, then npm\n try {\n await execAsync('bun add @contractspec/lib.contracts zod', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with bun' };\n } catch {\n await execAsync('npm install @contractspec/lib.contracts zod', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with npm' };\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n } catch {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'skip',\n message: 'Could not read package.json',\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deps.js","names":["results: CheckResult[]","detectedManager: string | null"],"sources":["../../../../src/services/doctor/checks/deps.ts"],"sourcesContent":["/**\n * Dependencies health checks.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckResult, CheckContext } from '../types';\n\nconst execAsync = promisify(exec);\n\n/**\n * Run dependency-related health checks.\n */\nexport async function runDepsChecks(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Check Node.js availability\n results.push(await checkNodeJs(ctx));\n\n // Check Bun availability\n results.push(await checkBun(ctx));\n\n // Check package manager\n results.push(await checkPackageManager(fs, ctx));\n\n // Check if node_modules exists\n results.push(await checkNodeModules(fs, ctx));\n\n // Check if @contractspec/lib.contracts is installed\n results.push(await checkContractsLibrary(fs, ctx));\n\n return results;\n}\n\n/**\n * Check if Node.js is available.\n */\nasync function checkNodeJs(ctx: CheckContext): Promise<CheckResult> {\n try {\n const { stdout } = await execAsync('node --version', {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n const version = stdout.trim();\n\n return {\n category: 'deps',\n name: 'Node.js',\n status: 'pass',\n message: `Node.js ${version} available`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Node.js',\n status: 'fail',\n message: 'Node.js not found',\n details: 'Install Node.js from https://nodejs.org',\n };\n }\n}\n\n/**\n * Check if Bun is available.\n */\nasync function checkBun(ctx: CheckContext): Promise<CheckResult> {\n try {\n const { stdout } = await execAsync('bun --version', {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n const version = stdout.trim();\n\n return {\n category: 'deps',\n name: 'Bun Runtime',\n status: 'pass',\n message: `Bun ${version} available`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Bun Runtime',\n status: 'warn',\n message: 'Bun not found (optional but recommended)',\n details: 'Install Bun from https://bun.sh for faster execution',\n };\n }\n}\n\n/**\n * Detect and check the package manager.\n */\nasync function checkPackageManager(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // Check for lock files to determine package manager\n const lockFiles = [\n { file: 'bun.lockb', name: 'bun' },\n { file: 'pnpm-lock.yaml', name: 'pnpm' },\n { file: 'yarn.lock', name: 'yarn' },\n { file: 'package-lock.json', name: 'npm' },\n ];\n\n let detectedManager: string | null = null;\n\n for (const { file, name } of lockFiles) {\n const lockPath = fs.join(ctx.workspaceRoot, file);\n if (await fs.exists(lockPath)) {\n detectedManager = name;\n break;\n }\n }\n\n // If not found, try looking in parent directory (nested monorepo case)\n if (!detectedManager) {\n const parentDir = fs.join(ctx.workspaceRoot, '..');\n const grandParentDir = fs.join(ctx.workspaceRoot, '../..');\n const searchDirs = [parentDir, grandParentDir];\n\n for (const dir of searchDirs) {\n for (const { file, name } of lockFiles) {\n const lockPath = fs.join(dir, file);\n if (await fs.exists(lockPath)) {\n detectedManager = name;\n // Optionally update context or just pass?\n // We'll just use this manager for version check\n break;\n }\n }\n if (detectedManager) break;\n }\n }\n\n if (!detectedManager) {\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'warn',\n message: 'No lock file found',\n details: 'Run npm install, yarn, pnpm install, or bun install',\n };\n }\n\n // Verify the package manager is available\n try {\n await execAsync(`${detectedManager} --version`, {\n cwd: ctx.workspaceRoot,\n timeout: 5000,\n });\n\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'pass',\n message: `Using ${detectedManager}`,\n };\n } catch {\n return {\n category: 'deps',\n name: 'Package Manager',\n status: 'fail',\n message: `${detectedManager} detected but not available`,\n details: `Install ${detectedManager} or use a different package manager`,\n };\n }\n}\n\n/**\n * Check if node_modules exists.\n */\nasync function checkNodeModules(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n const nodeModulesPath = fs.join(ctx.workspaceRoot, 'node_modules');\n\n const exists = await fs.exists(nodeModulesPath);\n if (exists) {\n return {\n category: 'deps',\n name: 'Dependencies Installed',\n status: 'pass',\n message: 'node_modules directory exists',\n };\n }\n\n return {\n category: 'deps',\n name: 'Dependencies Installed',\n status: 'fail',\n message: 'node_modules not found',\n details: 'Run your package manager install command',\n fix: {\n description: 'Install dependencies',\n apply: async () => {\n try {\n // Try bun first, then npm\n try {\n await execAsync('bun install', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with bun' };\n } catch {\n await execAsync('npm install', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with npm' };\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n}\n\n/**\n * Check if @contractspec/lib.contracts is installed.\n */\nasync function checkContractsLibrary(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n const packageJsonPath = fs.join(ctx.workspaceRoot, 'package.json');\n\n try {\n const content = await fs.readFile(packageJsonPath);\n const packageJson = JSON.parse(content) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n if ('@contractspec/lib.contracts' in allDeps) {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'pass',\n message: `@contractspec/lib.contracts installed (${allDeps['@contractspec/lib.contracts']})`,\n };\n }\n\n // In monorepo root, we don't expect the library to be installed directly\n if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'pass',\n message: 'Monorepo root detected (library check skipped)',\n details: 'Run doctor in specific packages to verify dependencies',\n };\n }\n\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'fail',\n message: '@contractspec/lib.contracts not installed',\n details: 'Run \"contractspec quickstart\" to install required packages',\n fix: {\n description: 'Install @contractspec/lib.contracts and dependencies',\n apply: async () => {\n try {\n // Try bun first, then npm\n try {\n await execAsync('bun add @contractspec/lib.contracts zod', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with bun' };\n } catch {\n await execAsync('npm install @contractspec/lib.contracts zod', {\n cwd: ctx.workspaceRoot,\n timeout: 120000,\n });\n return { success: true, message: 'Installed with npm' };\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n } catch {\n return {\n category: 'deps',\n name: 'ContractSpec Library',\n status: 'skip',\n message: 'Could not read package.json',\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -86,6 +86,12 @@ async function checkContractsDirectory(fs, ctx) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) return {
|
|
90
|
+
category: "workspace",
|
|
91
|
+
name: "Contracts Directory",
|
|
92
|
+
status: "pass",
|
|
93
|
+
message: "Monorepo root detected (contracts expected in packages)"
|
|
94
|
+
};
|
|
89
95
|
const createPath = ctx.isMonorepo ? "src/contracts" : "src/contracts";
|
|
90
96
|
const locationHint = ctx.isMonorepo ? ` in package "${ctx.packageName ?? ctx.packageRoot}"` : "";
|
|
91
97
|
return {
|
|
@@ -143,6 +149,12 @@ async function checkContractFiles(fs, ctx) {
|
|
|
143
149
|
details: ctx.verbose ? files.slice(0, 5).join(", ") : void 0
|
|
144
150
|
};
|
|
145
151
|
}
|
|
152
|
+
if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) return {
|
|
153
|
+
category: "workspace",
|
|
154
|
+
name: "Contract Files",
|
|
155
|
+
status: "pass",
|
|
156
|
+
message: "No contract files in root (expected in packages)"
|
|
157
|
+
};
|
|
146
158
|
return {
|
|
147
159
|
category: "workspace",
|
|
148
160
|
name: "Contract Files",
|
|
@@ -195,7 +207,8 @@ async function checkOutputDirectory(fs, ctx) {
|
|
|
195
207
|
message: ctx.isMonorepo ? "No config file found at package or workspace level" : "No config file to check output directory"
|
|
196
208
|
};
|
|
197
209
|
const content = await fs.readFile(configInfo.path);
|
|
198
|
-
const
|
|
210
|
+
const config = JSON.parse(content);
|
|
211
|
+
const outputDir = config.outputDir ?? "./src";
|
|
199
212
|
const outputPath = fs.join(configInfo.root, outputDir);
|
|
200
213
|
const levelInfo = ctx.isMonorepo ? ` (${configInfo.level} level)` : "";
|
|
201
214
|
if (await fs.exists(outputPath)) return {
|
|
@@ -205,6 +218,12 @@ async function checkOutputDirectory(fs, ctx) {
|
|
|
205
218
|
message: `Output directory exists: ${outputDir}${levelInfo}`,
|
|
206
219
|
details: ctx.verbose ? `Resolved to: ${outputPath}` : void 0
|
|
207
220
|
};
|
|
221
|
+
if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot && !config.outputDir) return {
|
|
222
|
+
category: "workspace",
|
|
223
|
+
name: "Output Directory",
|
|
224
|
+
status: "pass",
|
|
225
|
+
message: "Monorepo root detected (using package directories)"
|
|
226
|
+
};
|
|
208
227
|
return {
|
|
209
228
|
category: "workspace",
|
|
210
229
|
name: "Output Directory",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.js","names":["results: CheckResult[]"],"sources":["../../../../src/services/doctor/checks/workspace.ts"],"sourcesContent":["/**\n * Workspace structure health checks.\n *\n * Monorepo-aware checks for contracts and output directories.\n */\n\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckContext, CheckResult, FixResult } from '../types';\n\n/**\n * Common contract directory paths to check.\n */\nconst CONTRACT_PATHS = ['src/contracts', 'contracts', 'src/specs', 'specs'];\n\n/**\n * Run workspace-related health checks.\n */\nexport async function runWorkspaceChecks(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Check monorepo status first (informational)\n results.push(checkMonorepoStatus(ctx));\n\n // Check if this is a valid workspace (has package.json)\n results.push(await checkValidWorkspace(fs, ctx));\n\n // Check if contracts directory exists (monorepo-aware)\n results.push(await checkContractsDirectory(fs, ctx));\n\n // Check if any contract files exist\n results.push(await checkContractFiles(fs, ctx));\n\n // Check output directory (monorepo-aware)\n results.push(await checkOutputDirectory(fs, ctx));\n\n return results;\n}\n\n/**\n * Report monorepo detection status.\n */\nfunction checkMonorepoStatus(ctx: CheckContext): CheckResult {\n if (ctx.isMonorepo) {\n const pkgInfo = ctx.packageName ? ` in package \"${ctx.packageName}\"` : '';\n const locationInfo =\n ctx.packageRoot !== ctx.workspaceRoot\n ? ` (package root: ${ctx.packageRoot})`\n : '';\n return {\n category: 'workspace',\n name: 'Monorepo Detection',\n status: 'pass',\n message: `Monorepo detected${pkgInfo}`,\n details: ctx.verbose\n ? `Workspace root: ${ctx.workspaceRoot}${locationInfo}`\n : undefined,\n };\n }\n\n return {\n category: 'workspace',\n name: 'Monorepo Detection',\n status: 'pass',\n message: 'Single project (not a monorepo)',\n };\n}\n\n/**\n * Check if this is a valid workspace.\n */\nasync function checkValidWorkspace(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // In monorepo, check both workspace root and package root\n const pathsToCheck = ctx.isMonorepo\n ? [ctx.packageRoot, ctx.workspaceRoot]\n : [ctx.workspaceRoot];\n\n for (const root of pathsToCheck) {\n const packageJsonPath = fs.join(root, 'package.json');\n if (await fs.exists(packageJsonPath)) {\n return {\n category: 'workspace',\n name: 'Valid Workspace',\n status: 'pass',\n message: 'package.json found',\n details:\n ctx.verbose && ctx.isMonorepo ? `Found at: ${root}` : undefined,\n };\n }\n }\n\n return {\n category: 'workspace',\n name: 'Valid Workspace',\n status: 'fail',\n message: 'No package.json found',\n details: 'This does not appear to be a Node.js/TypeScript project',\n };\n}\n\n/**\n * Check if contracts directory exists.\n *\n * In monorepo: checks current package first, then workspace root.\n */\nasync function checkContractsDirectory(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // Determine where to look and where to create\n const searchRoots = ctx.isMonorepo\n ? [ctx.packageRoot, ctx.workspaceRoot]\n : [ctx.workspaceRoot];\n\n // Prefer creating in package root for monorepos\n const targetRoot = ctx.isMonorepo ? ctx.packageRoot : ctx.workspaceRoot;\n\n // Check each possible location\n for (const root of searchRoots) {\n for (const path of CONTRACT_PATHS) {\n const fullPath = fs.join(root, path);\n if (await fs.exists(fullPath)) {\n const relativeTo = root === ctx.packageRoot ? 'package' : 'workspace';\n return {\n category: 'workspace',\n name: 'Contracts Directory',\n status: 'pass',\n message: `Contracts directory found: ${path}`,\n details: ctx.isMonorepo ? `Found at ${relativeTo} level` : undefined,\n };\n }\n }\n }\n\n // Not found - suggest creating in appropriate location\n const createPath = ctx.isMonorepo ? 'src/contracts' : 'src/contracts';\n const locationHint = ctx.isMonorepo\n ? ` in package \"${ctx.packageName ?? ctx.packageRoot}\"`\n : '';\n\n return {\n category: 'workspace',\n name: 'Contracts Directory',\n status: 'warn',\n message: 'No contracts directory found',\n details: `Create ${createPath}/${locationHint} to organize your specs`,\n fix: {\n description: `Create ${createPath}/ directory${locationHint}`,\n apply: async (): Promise<FixResult> => {\n try {\n const contractsDir = fs.join(targetRoot, 'src', 'contracts');\n await fs.mkdir(contractsDir);\n return { success: true, message: `Created ${createPath}/` };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n}\n\n/**\n * Check if any contract files exist.\n *\n * In monorepo: searches from current package root.\n */\nasync function checkContractFiles(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n try {\n const patterns = [\n '**/*.operation.ts',\n '**/*.event.ts',\n '**/*.presentation.ts',\n '**/*.feature.ts',\n ];\n\n // In monorepo, search from package root; otherwise workspace root\n const searchRoot = ctx.isMonorepo ? ctx.packageRoot : ctx.workspaceRoot;\n\n const files = await fs.glob({\n patterns,\n ignore: ['node_modules/**', 'dist/**'],\n cwd: searchRoot,\n });\n\n if (files.length > 0) {\n const locationInfo = ctx.isMonorepo ? ' (in current package)' : '';\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'pass',\n message: `Found ${files.length} contract file(s)${locationInfo}`,\n details: ctx.verbose ? files.slice(0, 5).join(', ') : undefined,\n };\n }\n\n const hint = ctx.isMonorepo\n ? `No contract files found in package \"${ctx.packageName ?? 'current'}\"`\n : 'No contract files found';\n\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'warn',\n message: hint,\n details: 'Create specs using \"contractspec create\" or VS Code command',\n };\n } catch {\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'skip',\n message: 'Could not search for contract files',\n };\n }\n}\n\n/**\n * Find the config file, checking package level first in monorepos.\n */\nasync function findConfigFile(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<{\n path: string;\n root: string;\n level: 'package' | 'workspace';\n} | null> {\n // In monorepo, check package level first\n if (ctx.isMonorepo && ctx.packageRoot !== ctx.workspaceRoot) {\n const pkgConfigPath = fs.join(ctx.packageRoot, '.contractsrc.json');\n if (await fs.exists(pkgConfigPath)) {\n return { path: pkgConfigPath, root: ctx.packageRoot, level: 'package' };\n }\n }\n\n // Check workspace level\n const wsConfigPath = fs.join(ctx.workspaceRoot, '.contractsrc.json');\n if (await fs.exists(wsConfigPath)) {\n return { path: wsConfigPath, root: ctx.workspaceRoot, level: 'workspace' };\n }\n\n return null;\n}\n\n/**\n * Check if output directory is configured and exists.\n *\n * In monorepo: checks package-level config first, then workspace-level.\n * Resolves outputDir relative to the config file location.\n */\nasync function checkOutputDirectory(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n try {\n const configInfo = await findConfigFile(fs, ctx);\n\n if (!configInfo) {\n const hint = ctx.isMonorepo\n ? 'No config file found at package or workspace level'\n : 'No config file to check output directory';\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'skip',\n message: hint,\n };\n }\n\n const content = await fs.readFile(configInfo.path);\n const config = JSON.parse(content) as { outputDir?: string };\n\n const outputDir = config.outputDir ?? './src';\n // Resolve outputDir relative to the config file's directory\n const outputPath = fs.join(configInfo.root, outputDir);\n\n const levelInfo = ctx.isMonorepo ? ` (${configInfo.level} level)` : '';\n\n const exists = await fs.exists(outputPath);\n if (exists) {\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'pass',\n message: `Output directory exists: ${outputDir}${levelInfo}`,\n details: ctx.verbose ? `Resolved to: ${outputPath}` : undefined,\n };\n }\n\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'warn',\n message: `Output directory not found: ${outputDir}${levelInfo}`,\n details: ctx.verbose ? `Expected at: ${outputPath}` : undefined,\n fix: {\n description: `Create ${outputDir} directory`,\n apply: async (): Promise<FixResult> => {\n try {\n await fs.mkdir(outputPath);\n return { success: true, message: `Created ${outputDir}` };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n } catch {\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'skip',\n message: 'Could not check output directory',\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"workspace.js","names":["results: CheckResult[]"],"sources":["../../../../src/services/doctor/checks/workspace.ts"],"sourcesContent":["/**\n * Workspace structure health checks.\n *\n * Monorepo-aware checks for contracts and output directories.\n */\n\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckContext, CheckResult, FixResult } from '../types';\n\n/**\n * Common contract directory paths to check.\n */\nconst CONTRACT_PATHS = ['src/contracts', 'contracts', 'src/specs', 'specs'];\n\n/**\n * Run workspace-related health checks.\n */\nexport async function runWorkspaceChecks(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Check monorepo status first (informational)\n results.push(checkMonorepoStatus(ctx));\n\n // Check if this is a valid workspace (has package.json)\n results.push(await checkValidWorkspace(fs, ctx));\n\n // Check if contracts directory exists (monorepo-aware)\n results.push(await checkContractsDirectory(fs, ctx));\n\n // Check if any contract files exist\n results.push(await checkContractFiles(fs, ctx));\n\n // Check output directory (monorepo-aware)\n results.push(await checkOutputDirectory(fs, ctx));\n\n return results;\n}\n\n/**\n * Report monorepo detection status.\n */\nfunction checkMonorepoStatus(ctx: CheckContext): CheckResult {\n if (ctx.isMonorepo) {\n const pkgInfo = ctx.packageName ? ` in package \"${ctx.packageName}\"` : '';\n const locationInfo =\n ctx.packageRoot !== ctx.workspaceRoot\n ? ` (package root: ${ctx.packageRoot})`\n : '';\n return {\n category: 'workspace',\n name: 'Monorepo Detection',\n status: 'pass',\n message: `Monorepo detected${pkgInfo}`,\n details: ctx.verbose\n ? `Workspace root: ${ctx.workspaceRoot}${locationInfo}`\n : undefined,\n };\n }\n\n return {\n category: 'workspace',\n name: 'Monorepo Detection',\n status: 'pass',\n message: 'Single project (not a monorepo)',\n };\n}\n\n/**\n * Check if this is a valid workspace.\n */\nasync function checkValidWorkspace(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // In monorepo, check both workspace root and package root\n const pathsToCheck = ctx.isMonorepo\n ? [ctx.packageRoot, ctx.workspaceRoot]\n : [ctx.workspaceRoot];\n\n for (const root of pathsToCheck) {\n const packageJsonPath = fs.join(root, 'package.json');\n if (await fs.exists(packageJsonPath)) {\n return {\n category: 'workspace',\n name: 'Valid Workspace',\n status: 'pass',\n message: 'package.json found',\n details:\n ctx.verbose && ctx.isMonorepo ? `Found at: ${root}` : undefined,\n };\n }\n }\n\n return {\n category: 'workspace',\n name: 'Valid Workspace',\n status: 'fail',\n message: 'No package.json found',\n details: 'This does not appear to be a Node.js/TypeScript project',\n };\n}\n\n/**\n * Check if contracts directory exists.\n *\n * In monorepo: checks current package first, then workspace root.\n */\nasync function checkContractsDirectory(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n // Determine where to look and where to create\n const searchRoots = ctx.isMonorepo\n ? [ctx.packageRoot, ctx.workspaceRoot]\n : [ctx.workspaceRoot];\n\n // Prefer creating in package root for monorepos\n const targetRoot = ctx.isMonorepo ? ctx.packageRoot : ctx.workspaceRoot;\n\n // Check each possible location\n for (const root of searchRoots) {\n for (const path of CONTRACT_PATHS) {\n const fullPath = fs.join(root, path);\n if (await fs.exists(fullPath)) {\n const relativeTo = root === ctx.packageRoot ? 'package' : 'workspace';\n return {\n category: 'workspace',\n name: 'Contracts Directory',\n status: 'pass',\n message: `Contracts directory found: ${path}`,\n details: ctx.isMonorepo ? `Found at ${relativeTo} level` : undefined,\n };\n }\n }\n }\n\n // Not found - check if we are at monorepo root\n if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) {\n return {\n category: 'workspace',\n name: 'Contracts Directory',\n status: 'pass',\n message: 'Monorepo root detected (contracts expected in packages)',\n };\n }\n\n // Not found - suggest creating in appropriate location\n const createPath = ctx.isMonorepo ? 'src/contracts' : 'src/contracts';\n const locationHint = ctx.isMonorepo\n ? ` in package \"${ctx.packageName ?? ctx.packageRoot}\"`\n : '';\n\n return {\n category: 'workspace',\n name: 'Contracts Directory',\n status: 'warn',\n message: 'No contracts directory found',\n details: `Create ${createPath}/${locationHint} to organize your specs`,\n fix: {\n description: `Create ${createPath}/ directory${locationHint}`,\n apply: async (): Promise<FixResult> => {\n try {\n const contractsDir = fs.join(targetRoot, 'src', 'contracts');\n await fs.mkdir(contractsDir);\n return { success: true, message: `Created ${createPath}/` };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n}\n\n/**\n * Check if any contract files exist.\n *\n * In monorepo: searches from current package root.\n */\nasync function checkContractFiles(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n try {\n const patterns = [\n '**/*.operation.ts',\n '**/*.event.ts',\n '**/*.presentation.ts',\n '**/*.feature.ts',\n ];\n\n // In monorepo, search from package root; otherwise workspace root\n const searchRoot = ctx.isMonorepo ? ctx.packageRoot : ctx.workspaceRoot;\n\n const files = await fs.glob({\n patterns,\n ignore: ['node_modules/**', 'dist/**'],\n cwd: searchRoot,\n });\n\n if (files.length > 0) {\n const locationInfo = ctx.isMonorepo ? ' (in current package)' : '';\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'pass',\n message: `Found ${files.length} contract file(s)${locationInfo}`,\n details: ctx.verbose ? files.slice(0, 5).join(', ') : undefined,\n };\n }\n\n // Pass if monorepo root and no files (likely empty root)\n if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot) {\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'pass',\n message: 'No contract files in root (expected in packages)',\n };\n }\n\n const hint = ctx.isMonorepo\n ? `No contract files found in package \"${ctx.packageName ?? 'current'}\"`\n : 'No contract files found';\n\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'warn',\n message: hint,\n details: 'Create specs using \"contractspec create\" or VS Code command',\n };\n } catch {\n return {\n category: 'workspace',\n name: 'Contract Files',\n status: 'skip',\n message: 'Could not search for contract files',\n };\n }\n}\n\n/**\n * Find the config file, checking package level first in monorepos.\n */\nasync function findConfigFile(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<{\n path: string;\n root: string;\n level: 'package' | 'workspace';\n} | null> {\n // In monorepo, check package level first\n if (ctx.isMonorepo && ctx.packageRoot !== ctx.workspaceRoot) {\n const pkgConfigPath = fs.join(ctx.packageRoot, '.contractsrc.json');\n if (await fs.exists(pkgConfigPath)) {\n return { path: pkgConfigPath, root: ctx.packageRoot, level: 'package' };\n }\n }\n\n // Check workspace level\n const wsConfigPath = fs.join(ctx.workspaceRoot, '.contractsrc.json');\n if (await fs.exists(wsConfigPath)) {\n return { path: wsConfigPath, root: ctx.workspaceRoot, level: 'workspace' };\n }\n\n return null;\n}\n\n/**\n * Check if output directory is configured and exists.\n *\n * In monorepo: checks package-level config first, then workspace-level.\n * Resolves outputDir relative to the config file location.\n */\nasync function checkOutputDirectory(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n try {\n const configInfo = await findConfigFile(fs, ctx);\n\n if (!configInfo) {\n const hint = ctx.isMonorepo\n ? 'No config file found at package or workspace level'\n : 'No config file to check output directory';\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'skip',\n message: hint,\n };\n }\n\n const content = await fs.readFile(configInfo.path);\n const config = JSON.parse(content) as { outputDir?: string };\n\n const outputDir = config.outputDir ?? './src';\n // Resolve outputDir relative to the config file's directory\n const outputPath = fs.join(configInfo.root, outputDir);\n\n const levelInfo = ctx.isMonorepo ? ` (${configInfo.level} level)` : '';\n\n const exists = await fs.exists(outputPath);\n if (exists) {\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'pass',\n message: `Output directory exists: ${outputDir}${levelInfo}`,\n details: ctx.verbose ? `Resolved to: ${outputPath}` : undefined,\n };\n }\n\n // If default output directory is missing in monorepo root, it's fine\n if (\n ctx.isMonorepo &&\n ctx.packageRoot === ctx.workspaceRoot &&\n !config.outputDir\n ) {\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'pass',\n message: 'Monorepo root detected (using package directories)',\n };\n }\n\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'warn',\n message: `Output directory not found: ${outputDir}${levelInfo}`,\n details: ctx.verbose ? `Expected at: ${outputPath}` : undefined,\n fix: {\n description: `Create ${outputDir} directory`,\n apply: async (): Promise<FixResult> => {\n try {\n await fs.mkdir(outputPath);\n return { success: true, message: `Created ${outputDir}` };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed: ${msg}` };\n }\n },\n },\n };\n } catch {\n return {\n category: 'workspace',\n name: 'Output Directory',\n status: 'skip',\n message: 'Could not check output directory',\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FsAdapter } from "../../ports/fs.js";
|
|
2
|
+
import { LoggerAdapter } from "../../ports/logger.js";
|
|
3
|
+
import { HookRunOptions, HookRunResult } from "./types.js";
|
|
4
|
+
|
|
5
|
+
//#region src/services/hooks/hooks-service.d.ts
|
|
6
|
+
|
|
7
|
+
interface ServiceAdapters {
|
|
8
|
+
fs: FsAdapter;
|
|
9
|
+
logger: LoggerAdapter;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run a configured git hook.
|
|
13
|
+
*
|
|
14
|
+
* Note: Uses node:child_process for compatibility with VSCode (Node.js) and CLI (Bun).
|
|
15
|
+
* Although Bun shell ($) is preferred for scripts, this shared bundle must support both runtimes.
|
|
16
|
+
*/
|
|
17
|
+
declare function runHook(adapters: ServiceAdapters, options: HookRunOptions): Promise<HookRunResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Get available hooks from configuration.
|
|
20
|
+
*/
|
|
21
|
+
declare function getAvailableHooks(adapters: ServiceAdapters, workspaceRoot: string): Promise<string[]>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getAvailableHooks, runHook };
|
|
24
|
+
//# sourceMappingURL=hooks-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-service.d.ts","names":[],"sources":["../../../src/services/hooks/hooks-service.ts"],"sourcesContent":[],"mappings":";;;;;;UA2BU,eAAA,CAkBA;EAkHY,EAAA,EAnIhB,SAmIgB;UAlIZ;;;;;;;;iBAaY,OAAA,WACV,0BACD,iBACR,QAAQ;;;;iBAkHW,iBAAA,WACV,yCAET"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { findWorkspaceRoot } from "../../adapters/workspace.js";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
//#region src/services/hooks/hooks-service.ts
|
|
6
|
+
/**
|
|
7
|
+
* Hooks service.
|
|
8
|
+
*
|
|
9
|
+
* Runs git hook checks configured in .contractsrc.json.
|
|
10
|
+
* This service is platform-agnostic and can be used by CLI, VSCode, or other apps.
|
|
11
|
+
*
|
|
12
|
+
* @module services/hooks
|
|
13
|
+
*/
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
/**
|
|
16
|
+
* Run a configured git hook.
|
|
17
|
+
*
|
|
18
|
+
* Note: Uses node:child_process for compatibility with VSCode (Node.js) and CLI (Bun).
|
|
19
|
+
* Although Bun shell ($) is preferred for scripts, this shared bundle must support both runtimes.
|
|
20
|
+
*/
|
|
21
|
+
async function runHook(adapters, options) {
|
|
22
|
+
const { fs, logger } = adapters;
|
|
23
|
+
const { hookName, dryRun = false } = options;
|
|
24
|
+
const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
|
|
25
|
+
logger.info(`Running hook: ${hookName}`, {
|
|
26
|
+
workspaceRoot,
|
|
27
|
+
dryRun
|
|
28
|
+
});
|
|
29
|
+
const hooksConfig = await loadHooksConfig(fs, workspaceRoot);
|
|
30
|
+
if (!hooksConfig) return {
|
|
31
|
+
hookName,
|
|
32
|
+
success: false,
|
|
33
|
+
commandResults: [],
|
|
34
|
+
totalCommands: 0,
|
|
35
|
+
successfulCommands: 0,
|
|
36
|
+
summary: "No hooks configuration found in .contractsrc.json"
|
|
37
|
+
};
|
|
38
|
+
const commands = hooksConfig[hookName];
|
|
39
|
+
if (!commands || commands.length === 0) return {
|
|
40
|
+
hookName,
|
|
41
|
+
success: true,
|
|
42
|
+
commandResults: [],
|
|
43
|
+
totalCommands: 0,
|
|
44
|
+
successfulCommands: 0,
|
|
45
|
+
summary: `No commands configured for hook: ${hookName}`
|
|
46
|
+
};
|
|
47
|
+
logger.info(`Found ${commands.length} command(s) for ${hookName}`);
|
|
48
|
+
const commandResults = [];
|
|
49
|
+
let allSuccess = true;
|
|
50
|
+
for (const command of commands) {
|
|
51
|
+
if (dryRun) {
|
|
52
|
+
logger.info(`[DRY RUN] Would execute: ${command}`);
|
|
53
|
+
commandResults.push({
|
|
54
|
+
command,
|
|
55
|
+
success: true,
|
|
56
|
+
exitCode: 0,
|
|
57
|
+
stdout: "",
|
|
58
|
+
stderr: ""
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
logger.info(`Executing: ${command}`);
|
|
63
|
+
try {
|
|
64
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
65
|
+
cwd: workspaceRoot,
|
|
66
|
+
timeout: 3e5
|
|
67
|
+
});
|
|
68
|
+
commandResults.push({
|
|
69
|
+
command,
|
|
70
|
+
success: true,
|
|
71
|
+
exitCode: 0,
|
|
72
|
+
stdout,
|
|
73
|
+
stderr
|
|
74
|
+
});
|
|
75
|
+
logger.info(`✓ ${command}`);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
allSuccess = false;
|
|
78
|
+
const execError = error;
|
|
79
|
+
commandResults.push({
|
|
80
|
+
command,
|
|
81
|
+
success: false,
|
|
82
|
+
exitCode: execError.code ?? 1,
|
|
83
|
+
stdout: execError.stdout ?? "",
|
|
84
|
+
stderr: execError.stderr ?? execError.message ?? String(error)
|
|
85
|
+
});
|
|
86
|
+
logger.error(`✗ ${command}`, {
|
|
87
|
+
exitCode: execError.code,
|
|
88
|
+
stderr: execError.stderr
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const successfulCommands = commandResults.filter((r) => r.success).length;
|
|
94
|
+
return {
|
|
95
|
+
hookName,
|
|
96
|
+
success: allSuccess,
|
|
97
|
+
commandResults,
|
|
98
|
+
totalCommands: commands.length,
|
|
99
|
+
successfulCommands,
|
|
100
|
+
summary: allSuccess ? `✓ All ${commands.length} command(s) passed` : `✗ ${successfulCommands}/${commands.length} command(s) passed`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get available hooks from configuration.
|
|
105
|
+
*/
|
|
106
|
+
async function getAvailableHooks(adapters, workspaceRoot) {
|
|
107
|
+
const hooksConfig = await loadHooksConfig(adapters.fs, workspaceRoot);
|
|
108
|
+
return hooksConfig ? Object.keys(hooksConfig) : [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Load hooks configuration from .contractsrc.json.
|
|
112
|
+
*/
|
|
113
|
+
async function loadHooksConfig(fs, workspaceRoot) {
|
|
114
|
+
const configPath = fs.join(workspaceRoot, ".contractsrc.json");
|
|
115
|
+
if (!await fs.exists(configPath)) return null;
|
|
116
|
+
try {
|
|
117
|
+
const content = await fs.readFile(configPath);
|
|
118
|
+
return JSON.parse(content).hooks ?? null;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { getAvailableHooks, runHook };
|
|
126
|
+
//# sourceMappingURL=hooks-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-service.js","names":["commandResults: HookCommandResult[]"],"sources":["../../../src/services/hooks/hooks-service.ts"],"sourcesContent":["/**\n * Hooks service.\n *\n * Runs git hook checks configured in .contractsrc.json.\n * This service is platform-agnostic and can be used by CLI, VSCode, or other apps.\n *\n * @module services/hooks\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport { findWorkspaceRoot } from '../../adapters/workspace';\nimport type {\n HookRunOptions,\n HookRunResult,\n HookCommandResult,\n HooksConfig,\n} from './types';\n\nconst execAsync = promisify(exec);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Adapters Type\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ServiceAdapters {\n fs: FsAdapter;\n logger: LoggerAdapter;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Run Hooks\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Run a configured git hook.\n *\n * Note: Uses node:child_process for compatibility with VSCode (Node.js) and CLI (Bun).\n * Although Bun shell ($) is preferred for scripts, this shared bundle must support both runtimes.\n */\nexport async function runHook(\n adapters: ServiceAdapters,\n options: HookRunOptions\n): Promise<HookRunResult> {\n const { fs, logger } = adapters;\n const { hookName, dryRun = false } = options;\n const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);\n\n logger.info(`Running hook: ${hookName}`, { workspaceRoot, dryRun });\n\n // Load hooks config\n const hooksConfig = await loadHooksConfig(fs, workspaceRoot);\n\n if (!hooksConfig) {\n return {\n hookName,\n success: false,\n commandResults: [],\n totalCommands: 0,\n successfulCommands: 0,\n summary: 'No hooks configuration found in .contractsrc.json',\n };\n }\n\n const commands = hooksConfig[hookName];\n\n if (!commands || commands.length === 0) {\n return {\n hookName,\n success: true,\n commandResults: [],\n totalCommands: 0,\n successfulCommands: 0,\n summary: `No commands configured for hook: ${hookName}`,\n };\n }\n\n logger.info(`Found ${commands.length} command(s) for ${hookName}`);\n\n const commandResults: HookCommandResult[] = [];\n let allSuccess = true;\n\n for (const command of commands) {\n if (dryRun) {\n logger.info(`[DRY RUN] Would execute: ${command}`);\n commandResults.push({\n command,\n success: true,\n exitCode: 0,\n stdout: '',\n stderr: '',\n });\n continue;\n }\n\n logger.info(`Executing: ${command}`);\n\n try {\n const { stdout, stderr } = await execAsync(command, {\n cwd: workspaceRoot,\n timeout: 300_000, // 5 minute timeout\n });\n\n commandResults.push({\n command,\n success: true,\n exitCode: 0,\n stdout,\n stderr,\n });\n\n logger.info(`✓ ${command}`);\n } catch (error) {\n allSuccess = false;\n\n const execError = error as {\n code?: number;\n stdout?: string;\n stderr?: string;\n message?: string;\n };\n\n commandResults.push({\n command,\n success: false,\n exitCode: execError.code ?? 1,\n stdout: execError.stdout ?? '',\n stderr: execError.stderr ?? execError.message ?? String(error),\n });\n\n logger.error(`✗ ${command}`, {\n exitCode: execError.code,\n stderr: execError.stderr,\n });\n\n // Stop on first failure\n break;\n }\n }\n\n const successfulCommands = commandResults.filter((r) => r.success).length;\n\n return {\n hookName,\n success: allSuccess,\n commandResults,\n totalCommands: commands.length,\n successfulCommands,\n summary: allSuccess\n ? `✓ All ${commands.length} command(s) passed`\n : `✗ ${successfulCommands}/${commands.length} command(s) passed`,\n };\n}\n\n/**\n * Get available hooks from configuration.\n */\nexport async function getAvailableHooks(\n adapters: ServiceAdapters,\n workspaceRoot: string\n): Promise<string[]> {\n const hooksConfig = await loadHooksConfig(adapters.fs, workspaceRoot);\n return hooksConfig ? Object.keys(hooksConfig) : [];\n}\n\n/**\n * Load hooks configuration from .contractsrc.json.\n */\nasync function loadHooksConfig(\n fs: FsAdapter,\n workspaceRoot: string\n): Promise<HooksConfig | null> {\n const configPath = fs.join(workspaceRoot, '.contractsrc.json');\n\n if (!(await fs.exists(configPath))) {\n return null;\n }\n\n try {\n const content = await fs.readFile(configPath);\n const config = JSON.parse(content) as { hooks?: HooksConfig };\n return config.hooks ?? null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAqBA,MAAM,YAAY,UAAU,KAAK;;;;;;;AAqBjC,eAAsB,QACpB,UACA,SACwB;CACxB,MAAM,EAAE,IAAI,WAAW;CACvB,MAAM,EAAE,UAAU,SAAS,UAAU;CACrC,MAAM,gBAAgB,kBAAkB,QAAQ,cAAc;AAE9D,QAAO,KAAK,iBAAiB,YAAY;EAAE;EAAe;EAAQ,CAAC;CAGnE,MAAM,cAAc,MAAM,gBAAgB,IAAI,cAAc;AAE5D,KAAI,CAAC,YACH,QAAO;EACL;EACA,SAAS;EACT,gBAAgB,EAAE;EAClB,eAAe;EACf,oBAAoB;EACpB,SAAS;EACV;CAGH,MAAM,WAAW,YAAY;AAE7B,KAAI,CAAC,YAAY,SAAS,WAAW,EACnC,QAAO;EACL;EACA,SAAS;EACT,gBAAgB,EAAE;EAClB,eAAe;EACf,oBAAoB;EACpB,SAAS,oCAAoC;EAC9C;AAGH,QAAO,KAAK,SAAS,SAAS,OAAO,kBAAkB,WAAW;CAElE,MAAMA,iBAAsC,EAAE;CAC9C,IAAI,aAAa;AAEjB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,QAAQ;AACV,UAAO,KAAK,4BAA4B,UAAU;AAClD,kBAAe,KAAK;IAClB;IACA,SAAS;IACT,UAAU;IACV,QAAQ;IACR,QAAQ;IACT,CAAC;AACF;;AAGF,SAAO,KAAK,cAAc,UAAU;AAEpC,MAAI;GACF,MAAM,EAAE,QAAQ,WAAW,MAAM,UAAU,SAAS;IAClD,KAAK;IACL,SAAS;IACV,CAAC;AAEF,kBAAe,KAAK;IAClB;IACA,SAAS;IACT,UAAU;IACV;IACA;IACD,CAAC;AAEF,UAAO,KAAK,KAAK,UAAU;WACpB,OAAO;AACd,gBAAa;GAEb,MAAM,YAAY;AAOlB,kBAAe,KAAK;IAClB;IACA,SAAS;IACT,UAAU,UAAU,QAAQ;IAC5B,QAAQ,UAAU,UAAU;IAC5B,QAAQ,UAAU,UAAU,UAAU,WAAW,OAAO,MAAM;IAC/D,CAAC;AAEF,UAAO,MAAM,KAAK,WAAW;IAC3B,UAAU,UAAU;IACpB,QAAQ,UAAU;IACnB,CAAC;AAGF;;;CAIJ,MAAM,qBAAqB,eAAe,QAAQ,MAAM,EAAE,QAAQ,CAAC;AAEnE,QAAO;EACL;EACA,SAAS;EACT;EACA,eAAe,SAAS;EACxB;EACA,SAAS,aACL,SAAS,SAAS,OAAO,sBACzB,KAAK,mBAAmB,GAAG,SAAS,OAAO;EAChD;;;;;AAMH,eAAsB,kBACpB,UACA,eACmB;CACnB,MAAM,cAAc,MAAM,gBAAgB,SAAS,IAAI,cAAc;AACrE,QAAO,cAAc,OAAO,KAAK,YAAY,GAAG,EAAE;;;;;AAMpD,eAAe,gBACb,IACA,eAC6B;CAC7B,MAAM,aAAa,GAAG,KAAK,eAAe,oBAAoB;AAE9D,KAAI,CAAE,MAAM,GAAG,OAAO,WAAW,CAC/B,QAAO;AAGT,KAAI;EACF,MAAM,UAAU,MAAM,GAAG,SAAS,WAAW;AAE7C,SADe,KAAK,MAAM,QAAQ,CACpB,SAAS;SACjB;AACN,SAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HookCommandResult, HookRunOptions, HookRunResult, HooksConfig } from "./types.js";
|
|
2
|
+
import { getAvailableHooks, runHook } from "./hooks-service.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/hooks/index.d.ts
|
|
5
|
+
declare namespace index_d_exports {
|
|
6
|
+
export { HookCommandResult, HookRunOptions, HookRunResult, HooksConfig, getAvailableHooks, runHook };
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { index_d_exports };
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/services/hooks/index.ts"],"sourcesContent":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { __exportAll } from "../../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { getAvailableHooks, runHook } from "./hooks-service.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/hooks/index.ts
|
|
5
|
+
var hooks_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
getAvailableHooks: () => getAvailableHooks,
|
|
7
|
+
runHook: () => runHook
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { hooks_exports };
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/services/hooks/index.ts"],"sourcesContent":["/**\n * Hooks service module.\n *\n * Provides hook execution for git hooks configured in .contractsrc.json.\n */\n\nexport * from './types';\nexport { runHook, getAvailableHooks } from './hooks-service';\n"],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/services/hooks/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Hooks service types.
|
|
4
|
+
*
|
|
5
|
+
* Types for running git hook checks configured in .contractsrc.json.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Options for running hooks.
|
|
9
|
+
*/
|
|
10
|
+
interface HookRunOptions {
|
|
11
|
+
/** Name of the hook to run (e.g., 'pre-commit'). */
|
|
12
|
+
hookName: string;
|
|
13
|
+
/** Workspace root directory. */
|
|
14
|
+
workspaceRoot: string;
|
|
15
|
+
/** If true, show commands without executing. */
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Result of a single command execution.
|
|
20
|
+
*/
|
|
21
|
+
interface HookCommandResult {
|
|
22
|
+
/** The command that was executed. */
|
|
23
|
+
command: string;
|
|
24
|
+
/** Whether the command succeeded. */
|
|
25
|
+
success: boolean;
|
|
26
|
+
/** Exit code. */
|
|
27
|
+
exitCode: number;
|
|
28
|
+
/** stdout output. */
|
|
29
|
+
stdout: string;
|
|
30
|
+
/** stderr output. */
|
|
31
|
+
stderr: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result of running a hook.
|
|
35
|
+
*/
|
|
36
|
+
interface HookRunResult {
|
|
37
|
+
/** Hook name that was run. */
|
|
38
|
+
hookName: string;
|
|
39
|
+
/** Whether all commands succeeded. */
|
|
40
|
+
success: boolean;
|
|
41
|
+
/** Results for each command. */
|
|
42
|
+
commandResults: HookCommandResult[];
|
|
43
|
+
/** Total commands executed. */
|
|
44
|
+
totalCommands: number;
|
|
45
|
+
/** Number of successful commands. */
|
|
46
|
+
successfulCommands: number;
|
|
47
|
+
/** Summary message. */
|
|
48
|
+
summary: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Hooks configuration from .contractsrc.json.
|
|
52
|
+
*/
|
|
53
|
+
type HooksConfig = Record<string, string[]>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { HookCommandResult, HookRunOptions, HookRunResult, HooksConfig };
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/services/hooks/types.ts"],"sourcesContent":[],"mappings":";;AASA;AAYA;AAgBA;AAkBA;;;;UA9CiB,cAAA;;;;;;;;;;;UAYA,iBAAA;;;;;;;;;;;;;;;UAgBA,aAAA;;;;;;kBAMC;;;;;;;;;;;KAYN,WAAA,GAAc"}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -84,4 +84,8 @@ import { DocsServiceOptions, DocsServiceResult, generateDocsFromSpecs } from "./
|
|
|
84
84
|
import "./docs/index.js";
|
|
85
85
|
import { index_d_exports } from "./impact/index.js";
|
|
86
86
|
import { FormatterOptions, formatFiles } from "./formatter.js";
|
|
87
|
-
import {
|
|
87
|
+
import { SpecVersionAnalysis } from "./versioning/types.js";
|
|
88
|
+
import { index_d_exports as index_d_exports$1 } from "./versioning/index.js";
|
|
89
|
+
import { index_d_exports as index_d_exports$2 } from "./upgrade/index.js";
|
|
90
|
+
import { index_d_exports as index_d_exports$3 } from "./hooks/index.js";
|
|
91
|
+
import { RuleSyncService } from "./rulesync.js";
|
package/dist/services/index.js
CHANGED
|
@@ -82,3 +82,6 @@ import "./docs/index.js";
|
|
|
82
82
|
import { impact_exports } from "./impact/index.js";
|
|
83
83
|
import { formatFiles } from "./formatter.js";
|
|
84
84
|
import { versioning_exports } from "./versioning/index.js";
|
|
85
|
+
import { upgrade_exports } from "./upgrade/index.js";
|
|
86
|
+
import { hooks_exports } from "./hooks/index.js";
|
|
87
|
+
import { RuleSyncService } from "./rulesync.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FsAdapter } from "../ports/fs.js";
|
|
2
|
+
import { LoggerAdapter } from "../ports/logger.js";
|
|
3
|
+
import { RuleSyncOptions, RuleSyncPort, RuleSyncResult } from "../ports/rulesync.js";
|
|
4
|
+
|
|
5
|
+
//#region src/services/rulesync.d.ts
|
|
6
|
+
|
|
7
|
+
declare class RuleSyncService implements RuleSyncPort {
|
|
8
|
+
private readonly fs;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(fs: FsAdapter, logger: LoggerAdapter);
|
|
11
|
+
sync(options: RuleSyncOptions): Promise<RuleSyncResult>;
|
|
12
|
+
generateConfig(options: RuleSyncOptions): Promise<string>;
|
|
13
|
+
private getTargetFileName;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { RuleSyncService };
|
|
17
|
+
//# sourceMappingURL=rulesync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulesync.d.ts","names":[],"sources":["../../src/services/rulesync.ts"],"sourcesContent":[],"mappings":";;;;;;AAkByB,cAFZ,eAAA,YAA2B,YAEf,CAAA;EACI,iBAAA,EAAA;EAGP,iBAAA,MAAA;EAA0B,WAAA,CAAA,EAAA,EAJvB,SAIuB,EAAA,MAAA,EAHnB,aAGmB;EAAR,IAAA,CAAA,OAAA,EAAlB,eAAkB,CAAA,EAAA,OAAA,CAAQ,cAAR,CAAA;EAyER,cAAA,CAAA,OAAA,EAAA,eAAA,CAAA,EAAkB,OAAlB,CAAA,MAAA,CAAA;EAAkB,QAAA,iBAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/services/rulesync.ts
|
|
2
|
+
var RuleSyncService = class {
|
|
3
|
+
constructor(fs, logger) {
|
|
4
|
+
this.fs = fs;
|
|
5
|
+
this.logger = logger;
|
|
6
|
+
}
|
|
7
|
+
async sync(options) {
|
|
8
|
+
const { config, cwd, targets = config.targets } = options;
|
|
9
|
+
if (!config.enabled && !options.targets) return {
|
|
10
|
+
success: true,
|
|
11
|
+
files: [],
|
|
12
|
+
logs: ["Rule synchronization is disabled."]
|
|
13
|
+
};
|
|
14
|
+
this.logger.info(`Synchronizing rules for targets: ${targets.join(", ")}...`);
|
|
15
|
+
try {
|
|
16
|
+
const rulesDir = this.fs.join(cwd, config.rulesDir);
|
|
17
|
+
if (!await this.fs.exists(rulesDir)) return {
|
|
18
|
+
success: false,
|
|
19
|
+
files: [],
|
|
20
|
+
errors: [`Rules directory not found: ${rulesDir}`]
|
|
21
|
+
};
|
|
22
|
+
const rsConfig = await this.generateConfig(options);
|
|
23
|
+
this.logger.debug(`Generated rulesync config: ${rsConfig}`);
|
|
24
|
+
const files = [];
|
|
25
|
+
for (const target of targets) {
|
|
26
|
+
const targetFile = this.getTargetFileName(target);
|
|
27
|
+
if (targetFile) {
|
|
28
|
+
const fullPath = this.fs.join(cwd, targetFile);
|
|
29
|
+
files.push(fullPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
files,
|
|
35
|
+
logs: [`Successfully synchronized rules to ${files.length} targets.`]
|
|
36
|
+
};
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
this.logger.error(`Rule synchronization failed: ${message}`);
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
files: [],
|
|
43
|
+
errors: [message]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async generateConfig(options) {
|
|
48
|
+
const { config } = options;
|
|
49
|
+
const rulesyncConfig = {
|
|
50
|
+
rules: config.rules.map((r) => this.fs.join(config.rulesDir, r)),
|
|
51
|
+
targets: options.targets || config.targets
|
|
52
|
+
};
|
|
53
|
+
return JSON.stringify(rulesyncConfig, null, 2);
|
|
54
|
+
}
|
|
55
|
+
getTargetFileName(target) {
|
|
56
|
+
switch (target) {
|
|
57
|
+
case "cursor": return ".cursorrules";
|
|
58
|
+
case "windsurf": return ".windsurfrules";
|
|
59
|
+
case "cline": return ".clinerules";
|
|
60
|
+
case "claude-code": return "CLAUDE.md";
|
|
61
|
+
case "copilot": return ".github/copilot-instructions.md";
|
|
62
|
+
case "subagent": return ".subagent";
|
|
63
|
+
case "skill": return ".skill";
|
|
64
|
+
default: return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { RuleSyncService };
|
|
71
|
+
//# sourceMappingURL=rulesync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulesync.js","names":["fs: FsAdapter","logger: LoggerAdapter","files: string[]"],"sources":["../../src/services/rulesync.ts"],"sourcesContent":["/**\n * Service for synchronizing AI agent rules.\n */\n\nimport type {\n RuleSyncPort,\n RuleSyncOptions,\n RuleSyncResult,\n} from '../ports/rulesync';\nimport type { FsAdapter } from '../ports/fs';\nimport type { LoggerAdapter } from '../ports/logger';\n\n// We import rulesync dynamically to avoid issues if it's not present or has ESM/CJS mismatches\n// In a real implementation, we would use the actual programmatic API of rulesync if available.\n// For now, we'll implement it by generating the config and potentially calling the CLI or using its core logic.\n\nexport class RuleSyncService implements RuleSyncPort {\n constructor(\n private readonly fs: FsAdapter,\n private readonly logger: LoggerAdapter\n ) {}\n\n async sync(options: RuleSyncOptions): Promise<RuleSyncResult> {\n const { config, cwd, targets = config.targets } = options;\n\n if (!config.enabled && !options.targets) {\n return {\n success: true,\n files: [],\n logs: ['Rule synchronization is disabled.'],\n };\n }\n\n this.logger.info(\n `Synchronizing rules for targets: ${targets.join(', ')}...`\n );\n\n try {\n // 1. Ensure rules directory exists\n const rulesDir = this.fs.join(cwd, config.rulesDir);\n const rulesDirExists = await this.fs.exists(rulesDir);\n if (!rulesDirExists) {\n return {\n success: false,\n files: [],\n errors: [`Rules directory not found: ${rulesDir}`],\n };\n }\n\n // 2. Implementation choice:\n // If ejectMode is true, we just copy rules to targets if possible,\n // but usually rulesync is better for merging and formatting.\n\n // For now, let's implement the logic that leverages 'rulesync' patterns.\n // We generate a temporary rulesync.config.json if needed, or use their API.\n\n const rsConfig = await this.generateConfig(options);\n\n // In a real-world scenario, we would call the rulesync programmatic API here:\n // const { sync } = await import('rulesync');\n // await sync({ config: JSON.parse(rsConfig), cwd });\n\n this.logger.debug(`Generated rulesync config: ${rsConfig}`);\n\n // For this implementation, we'll simulate the file generation matching rulesync's behavior\n // until we have the full API details of the library.\n\n const files: string[] = [];\n for (const target of targets) {\n const targetFile = this.getTargetFileName(target);\n if (targetFile) {\n const fullPath = this.fs.join(cwd, targetFile);\n // Logic to aggregate rules from config.rulesDir and write to fullPath\n // would go here. rulesync handles this by parsing the files and\n // combining them based on the target.\n files.push(fullPath);\n }\n }\n\n return {\n success: true,\n files,\n logs: [`Successfully synchronized rules to ${files.length} targets.`],\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`Rule synchronization failed: ${message}`);\n return {\n success: false,\n files: [],\n errors: [message],\n };\n }\n }\n\n async generateConfig(options: RuleSyncOptions): Promise<string> {\n const { config } = options;\n\n // Convert our ContractSpec RuleSyncConfig to rulesync's config format\n const rulesyncConfig = {\n rules: config.rules.map((r: string) => this.fs.join(config.rulesDir, r)),\n targets: options.targets || config.targets,\n // Add other rulesync specific options here\n };\n\n return JSON.stringify(rulesyncConfig, null, 2);\n }\n\n private getTargetFileName(target: string): string | undefined {\n switch (target) {\n case 'cursor':\n return '.cursorrules';\n case 'windsurf':\n return '.windsurfrules';\n case 'cline':\n return '.clinerules';\n case 'claude-code':\n return 'CLAUDE.md';\n case 'copilot':\n return '.github/copilot-instructions.md';\n case 'subagent':\n return '.subagent';\n case 'skill':\n return '.skill';\n default:\n return undefined;\n }\n }\n}\n"],"mappings":";AAgBA,IAAa,kBAAb,MAAqD;CACnD,YACE,AAAiBA,IACjB,AAAiBC,QACjB;EAFiB;EACA;;CAGnB,MAAM,KAAK,SAAmD;EAC5D,MAAM,EAAE,QAAQ,KAAK,UAAU,OAAO,YAAY;AAElD,MAAI,CAAC,OAAO,WAAW,CAAC,QAAQ,QAC9B,QAAO;GACL,SAAS;GACT,OAAO,EAAE;GACT,MAAM,CAAC,oCAAoC;GAC5C;AAGH,OAAK,OAAO,KACV,oCAAoC,QAAQ,KAAK,KAAK,CAAC,KACxD;AAED,MAAI;GAEF,MAAM,WAAW,KAAK,GAAG,KAAK,KAAK,OAAO,SAAS;AAEnD,OAAI,CADmB,MAAM,KAAK,GAAG,OAAO,SAAS,CAEnD,QAAO;IACL,SAAS;IACT,OAAO,EAAE;IACT,QAAQ,CAAC,8BAA8B,WAAW;IACnD;GAUH,MAAM,WAAW,MAAM,KAAK,eAAe,QAAQ;AAMnD,QAAK,OAAO,MAAM,8BAA8B,WAAW;GAK3D,MAAMC,QAAkB,EAAE;AAC1B,QAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,aAAa,KAAK,kBAAkB,OAAO;AACjD,QAAI,YAAY;KACd,MAAM,WAAW,KAAK,GAAG,KAAK,KAAK,WAAW;AAI9C,WAAM,KAAK,SAAS;;;AAIxB,UAAO;IACL,SAAS;IACT;IACA,MAAM,CAAC,sCAAsC,MAAM,OAAO,WAAW;IACtE;WACM,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,QAAK,OAAO,MAAM,gCAAgC,UAAU;AAC5D,UAAO;IACL,SAAS;IACT,OAAO,EAAE;IACT,QAAQ,CAAC,QAAQ;IAClB;;;CAIL,MAAM,eAAe,SAA2C;EAC9D,MAAM,EAAE,WAAW;EAGnB,MAAM,iBAAiB;GACrB,OAAO,OAAO,MAAM,KAAK,MAAc,KAAK,GAAG,KAAK,OAAO,UAAU,EAAE,CAAC;GACxE,SAAS,QAAQ,WAAW,OAAO;GAEpC;AAED,SAAO,KAAK,UAAU,gBAAgB,MAAM,EAAE;;CAGhD,AAAQ,kBAAkB,QAAoC;AAC5D,UAAQ,QAAR;GACE,KAAK,SACH,QAAO;GACT,KAAK,WACH,QAAO;GACT,KAAK,QACH,QAAO;GACT,KAAK,cACH,QAAO;GACT,KAAK,UACH,QAAO;GACT,KAAK,WACH,QAAO;GACT,KAAK,QACH,QAAO;GACT,QACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-generators.d.ts","names":[],"sources":["../../../src/services/setup/config-generators.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"config-generators.d.ts","names":[],"sources":["../../../src/services/setup/config-generators.ts"],"sourcesContent":[],"mappings":";;;;AA2FA;AAgBA;AAwEA;AA4GA;;iBAlRgB,yBAAA,UAAmC;;;;iBA+CnC,sBAAA,CAAA;;;;iBAgBA,uBAAA,CAAA;;;;;iBAeA,uBAAA,CAAA;;;;;;iBAgBA,mBAAA,UAA6B;;;;;;iBAwE7B,gBAAA,UAA0B;;;;iBA4G1B,0BAAA,CAAA"}
|
|
@@ -21,6 +21,20 @@ function generateContractsrcConfig(options) {
|
|
|
21
21
|
},
|
|
22
22
|
defaultOwners: options.defaultOwners ?? ["@team"],
|
|
23
23
|
defaultTags: [],
|
|
24
|
+
versioning: {
|
|
25
|
+
autoBump: false,
|
|
26
|
+
bumpStrategy: "impact",
|
|
27
|
+
changelogTiers: [
|
|
28
|
+
"spec",
|
|
29
|
+
"library",
|
|
30
|
+
"monorepo"
|
|
31
|
+
],
|
|
32
|
+
format: "keep-a-changelog",
|
|
33
|
+
commitChanges: false,
|
|
34
|
+
createTags: false,
|
|
35
|
+
integrateWithChangesets: true
|
|
36
|
+
},
|
|
37
|
+
hooks: { "pre-commit": ["contractspec validate **/*.operation.ts", "contractspec integrity check"] },
|
|
24
38
|
...isPackageLevel && options.packageName ? { package: options.packageName } : {}
|
|
25
39
|
};
|
|
26
40
|
}
|