pipecraft 0.0.0-releaseit
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/.claude/settings.local.json +35 -0
- package/.github/actions/calculate-version/action.yml +106 -0
- package/.github/actions/create-pr/action.yml +122 -0
- package/.github/actions/create-release/action.yml +74 -0
- package/.github/actions/create-tag/action.yml +94 -0
- package/.github/actions/detect-changes/action.yml +62 -0
- package/.github/actions/manage-branch/action.yml +113 -0
- package/.github/actions/promote-branch/action.yml +352 -0
- package/.github/example/workflows/job.analyze.code.yml +26 -0
- package/.github/example/workflows/job.analyze.docker.yml +32 -0
- package/.github/example/workflows/job.app.api.deploy.yml +127 -0
- package/.github/example/workflows/job.app.api.test.yml +102 -0
- package/.github/example/workflows/job.app.docs.deploy.yml +40 -0
- package/.github/example/workflows/job.app.docs.test.yml +50 -0
- package/.github/example/workflows/job.app.web.deploy.yml +96 -0
- package/.github/example/workflows/job.app.web.test.yml +49 -0
- package/.github/example/workflows/job.changes.yml +82 -0
- package/.github/example/workflows/job.create-pr.yml +96 -0
- package/.github/example/workflows/job.env-check.yml +43 -0
- package/.github/example/workflows/job.fast-forward.yml +103 -0
- package/.github/example/workflows/job.lint.yml +64 -0
- package/.github/example/workflows/job.pr-name.yml +70 -0
- package/.github/example/workflows/job.pr-squash-name.yml +27 -0
- package/.github/example/workflows/job.tag.yml +38 -0
- package/.github/example/workflows/job.version.yml +99 -0
- package/.github/example/workflows/lib.plugin-base.publish.yml +59 -0
- package/.github/example/workflows/pipe.yml +178 -0
- package/.github/example/workflows/scripts/fastforward.sh +51 -0
- package/.github/example/workflows/scripts/pipe-cleaner.sh +70 -0
- package/.github/scripts/fastforward.sh +51 -0
- package/.github/scripts/pipe-cleaner.sh +70 -0
- package/.github/workflows/pipeline.yml +220 -0
- package/.github/workflows/pr-title-check.yml +70 -0
- package/.github/workflows/publish.yml +39 -0
- package/.pipecraftrc.json +78 -0
- package/.release-it.cjs +71 -0
- package/PIPELINE_TESTING_PLAN.md +499 -0
- package/README.md +1101 -0
- package/TRUNK_FLOW_PLAN.md +401 -0
- package/assets/logo_banner.png +0 -0
- package/assets/logo_banner.webp +0 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +364 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/generators/init.tpl.d.ts +3 -0
- package/dist/generators/init.tpl.d.ts.map +1 -0
- package/dist/generators/init.tpl.js +117 -0
- package/dist/generators/init.tpl.js.map +1 -0
- package/dist/generators/workflows.tpl.d.ts +7 -0
- package/dist/generators/workflows.tpl.d.ts.map +1 -0
- package/dist/generators/workflows.tpl.js +73 -0
- package/dist/generators/workflows.tpl.js.map +1 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/calculate-version.yml.tpl.js +122 -0
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-pr.yml.tpl.js +145 -0
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.js +111 -0
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/detect-changes.yml.tpl.js +79 -0
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/manage-branch.yml.tpl.js +130 -0
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +3 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/promote-branch.yml.tpl.js +362 -0
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +16 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +652 -0
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ast-path-operations.d.ts +287 -0
- package/dist/utils/ast-path-operations.d.ts.map +1 -0
- package/dist/utils/ast-path-operations.js +419 -0
- package/dist/utils/ast-path-operations.js.map +1 -0
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +49 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/github-setup.d.ts +74 -0
- package/dist/utils/github-setup.d.ts.map +1 -0
- package/dist/utils/github-setup.js +418 -0
- package/dist/utils/github-setup.js.map +1 -0
- package/dist/utils/idempotency.d.ts +47 -0
- package/dist/utils/idempotency.d.ts.map +1 -0
- package/dist/utils/idempotency.js +176 -0
- package/dist/utils/idempotency.js.map +1 -0
- package/dist/utils/preflight.d.ts +55 -0
- package/dist/utils/preflight.d.ts.map +1 -0
- package/dist/utils/preflight.js +261 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/versioning.d.ts +41 -0
- package/dist/utils/versioning.d.ts.map +1 -0
- package/dist/utils/versioning.js +219 -0
- package/dist/utils/versioning.js.map +1 -0
- package/docs/USER_JOURNEY_ERRORS.md +352 -0
- package/eslint.config.js +77 -0
- package/examples/basic-config.json +41 -0
- package/examples/monorepo-config.json +49 -0
- package/examples/usage.md +289 -0
- package/package.json +58 -0
- package/scripts/debug-workflows.sh +339 -0
- package/scripts/parse-pipeline.js +29 -0
- package/scripts/test-job-order.cjs +140 -0
- package/scripts/validate-pipeline.cjs +379 -0
- package/scripts/verify-job-order.sh +30 -0
- package/src/cli/index.ts +414 -0
- package/src/generators/init.tpl.ts +126 -0
- package/src/generators/workflows.tpl.ts +80 -0
- package/src/templates/actions/calculate-version.yml.tpl.ts +124 -0
- package/src/templates/actions/create-pr.yml.tpl.ts +147 -0
- package/src/templates/actions/create-tag.yml.tpl.ts +112 -0
- package/src/templates/actions/detect-changes.yml.tpl.ts +87 -0
- package/src/templates/actions/manage-branch.yml.tpl.ts +132 -0
- package/src/templates/actions/promote-branch.yml.tpl.ts +364 -0
- package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +706 -0
- package/src/types/index.ts +64 -0
- package/src/utils/README-ast-path-operations.md +390 -0
- package/src/utils/ast-path-operations.ts +581 -0
- package/src/utils/config.ts +64 -0
- package/src/utils/github-setup.ts +558 -0
- package/src/utils/idempotency.ts +215 -0
- package/src/utils/preflight.ts +306 -0
- package/src/utils/versioning.ts +244 -0
- package/tests/README.md +229 -0
- package/tests/TEST_STRUCTURE.md +256 -0
- package/tests/act/run-act-tests.sh +345 -0
- package/tests/debugging/debug-utils.ts +538 -0
- package/tests/debugging/debug-workflow.test.ts +339 -0
- package/tests/debugging/debug-workflows.sh +339 -0
- package/tests/debugging/iterative-debug.ts +652 -0
- package/tests/debugging/run-debug-tests.sh +431 -0
- package/tests/fixtures/basic-config.json +51 -0
- package/tests/fixtures/invalid-config.json +9 -0
- package/tests/fixtures/pipeline-generated.yml +235 -0
- package/tests/fixtures/pipeline-preserve-comments.yml +245 -0
- package/tests/fixtures/pipeline-user-modified.yml +245 -0
- package/tests/fixtures/test-config.json +58 -0
- package/tests/github-live/README.md +250 -0
- package/tests/github-local/JOB_WORKFLOW_TESTS.md +296 -0
- package/tests/github-local/README.md +240 -0
- package/tests/github-local/run-all-tests.sh +422 -0
- package/tests/github-local/test-job-workflows.sh +631 -0
- package/tests/github-local/test-pipeline-workflow.sh +440 -0
- package/tests/integration/generators.test.ts +578 -0
- package/tests/integration/path-based-template.test.ts +510 -0
- package/tests/integration/simple-path-based.test.ts +415 -0
- package/tests/setup.ts +56 -0
- package/tests/unit/ast-path-operations-extended.test.ts +302 -0
- package/tests/unit/cli.test.ts +541 -0
- package/tests/unit/config-extended.test.ts +412 -0
- package/tests/unit/config.test.ts +152 -0
- package/tests/unit/github-setup.test.ts +189 -0
- package/tests/unit/idempotency-isolated.test.ts +297 -0
- package/tests/unit/job-order.test.ts +157 -0
- package/tests/unit/pipeline-path-based.test.ts +511 -0
- package/tests/unit/validate-pipeline.test.ts +384 -0
- package/tests/unit/versioning-extended.test.ts +279 -0
- package/tests/unit/versioning.test.ts +241 -0
- package/tsconfig.debug.json +17 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +51 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createHash } from 'crypto'
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync, statSync, rmSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { PipecraftConfig } from '../types/index.js'
|
|
5
|
+
|
|
6
|
+
export interface FileHash {
|
|
7
|
+
path: string
|
|
8
|
+
hash: string
|
|
9
|
+
mtime: number
|
|
10
|
+
size: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RebuildCache {
|
|
14
|
+
files: Record<string, FileHash>
|
|
15
|
+
configHash: string
|
|
16
|
+
lastGenerated: number
|
|
17
|
+
version: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class IdempotencyManager {
|
|
21
|
+
private cacheFile: string
|
|
22
|
+
private config: PipecraftConfig
|
|
23
|
+
|
|
24
|
+
constructor(config: PipecraftConfig, cacheFile = '.pipecraft-cache.json') {
|
|
25
|
+
this.config = config
|
|
26
|
+
this.cacheFile = cacheFile
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculate hash for a file or directory
|
|
31
|
+
*/
|
|
32
|
+
async calculateHash(filePath: string, algorithm: string = 'sha256'): Promise<string> {
|
|
33
|
+
if (!existsSync(filePath)) {
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stats = statSync(filePath)
|
|
38
|
+
if (stats.isDirectory()) {
|
|
39
|
+
// For directories, hash all files recursively
|
|
40
|
+
return await this.hashDirectory(filePath, algorithm)
|
|
41
|
+
} else {
|
|
42
|
+
// For files, hash content + metadata
|
|
43
|
+
const content = readFileSync(filePath)
|
|
44
|
+
const hash = createHash(algorithm)
|
|
45
|
+
hash.update(content)
|
|
46
|
+
hash.update(stats.mtime.toString())
|
|
47
|
+
hash.update(stats.size.toString())
|
|
48
|
+
return hash.digest('hex')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hash directory contents recursively
|
|
54
|
+
*/
|
|
55
|
+
private async hashDirectory(dirPath: string, algorithm: string): Promise<string> {
|
|
56
|
+
const fs = await import('fs')
|
|
57
|
+
const path = await import('path')
|
|
58
|
+
const hash = createHash(algorithm)
|
|
59
|
+
|
|
60
|
+
const hashDir = (dir: string) => {
|
|
61
|
+
const items = fs.readdirSync(dir)
|
|
62
|
+
for (const item of items) {
|
|
63
|
+
const itemPath = path.join(dir, item)
|
|
64
|
+
const stats = fs.statSync(itemPath)
|
|
65
|
+
|
|
66
|
+
if (stats.isDirectory()) {
|
|
67
|
+
hashDir(itemPath)
|
|
68
|
+
} else {
|
|
69
|
+
const content = fs.readFileSync(itemPath)
|
|
70
|
+
hash.update(content)
|
|
71
|
+
hash.update(stats.mtime.toString())
|
|
72
|
+
hash.update(stats.size.toString())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hashDir(dirPath)
|
|
78
|
+
return hash.digest('hex')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load rebuild cache from file
|
|
83
|
+
*/
|
|
84
|
+
loadCache(): RebuildCache | null {
|
|
85
|
+
if (!existsSync(this.cacheFile)) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const content = readFileSync(this.cacheFile, 'utf8')
|
|
91
|
+
return JSON.parse(content)
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
console.warn(`Failed to load cache file: ${error.message}`)
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Save rebuild cache to file
|
|
100
|
+
*/
|
|
101
|
+
saveCache(cache: RebuildCache): void {
|
|
102
|
+
try {
|
|
103
|
+
writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2))
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
console.warn(`Failed to save cache file: ${error.message}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if files have changed since last generation
|
|
111
|
+
*/
|
|
112
|
+
async hasChanges(): Promise<boolean> {
|
|
113
|
+
if (!this.config.rebuild?.enabled) {
|
|
114
|
+
return true // Always regenerate if rebuild is disabled
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this.config.rebuild?.forceRegenerate) {
|
|
118
|
+
return true // Force regeneration
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const cache = this.loadCache()
|
|
122
|
+
if (!cache) {
|
|
123
|
+
return true // No cache, need to regenerate
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check if config has changed
|
|
127
|
+
const configHash = await this.calculateHash('.pipecraftrc.json')
|
|
128
|
+
if (cache.configHash !== configHash) {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check if template files have changed
|
|
133
|
+
const templateDir = 'src/templates'
|
|
134
|
+
if (existsSync(templateDir)) {
|
|
135
|
+
const templateHash = await this.calculateHash(templateDir)
|
|
136
|
+
if (cache.files[templateDir]?.hash !== templateHash) {
|
|
137
|
+
return true
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check if source files have changed
|
|
142
|
+
const sourceDir = 'src/generators'
|
|
143
|
+
if (existsSync(sourceDir)) {
|
|
144
|
+
const sourceHash = await this.calculateHash(sourceDir)
|
|
145
|
+
if (cache.files[sourceDir]?.hash !== sourceHash) {
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Update cache with current file states
|
|
155
|
+
*/
|
|
156
|
+
async updateCache(): Promise<void> {
|
|
157
|
+
const cache: RebuildCache = {
|
|
158
|
+
files: {},
|
|
159
|
+
configHash: await this.calculateHash('.pipecraftrc.json'),
|
|
160
|
+
lastGenerated: Date.now(),
|
|
161
|
+
version: '1.0.0'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Cache template files
|
|
165
|
+
const templateDir = 'src/templates'
|
|
166
|
+
if (existsSync(templateDir)) {
|
|
167
|
+
cache.files[templateDir] = {
|
|
168
|
+
path: templateDir,
|
|
169
|
+
hash: await this.calculateHash(templateDir),
|
|
170
|
+
mtime: statSync(templateDir).mtime.getTime(),
|
|
171
|
+
size: 0 // Directory size not meaningful
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Cache source files
|
|
176
|
+
const sourceDir = 'src/generators'
|
|
177
|
+
if (existsSync(sourceDir)) {
|
|
178
|
+
cache.files[sourceDir] = {
|
|
179
|
+
path: sourceDir,
|
|
180
|
+
hash: await this.calculateHash(sourceDir),
|
|
181
|
+
mtime: statSync(sourceDir).mtime.getTime(),
|
|
182
|
+
size: 0
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.saveCache(cache)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check if specific file should be regenerated
|
|
191
|
+
*/
|
|
192
|
+
async shouldRegenerateFile(filePath: string): Promise<boolean> {
|
|
193
|
+
if (!this.config.rebuild?.enabled) {
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.config.rebuild?.forceRegenerate) {
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cache = this.loadCache()
|
|
202
|
+
if (!cache) {
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const currentHash = await this.calculateHash(filePath)
|
|
207
|
+
const cachedFile = cache.files[filePath]
|
|
208
|
+
|
|
209
|
+
if (!cachedFile) {
|
|
210
|
+
return true // File not in cache, needs generation
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return cachedFile.hash !== currentHash
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight checks for PipeCraft commands
|
|
3
|
+
* Validates environment and prerequisites before executing commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs'
|
|
7
|
+
import { execSync } from 'child_process'
|
|
8
|
+
import { join } from 'path'
|
|
9
|
+
import { cosmiconfigSync } from 'cosmiconfig'
|
|
10
|
+
|
|
11
|
+
export interface PreflightResult {
|
|
12
|
+
passed: boolean
|
|
13
|
+
message: string
|
|
14
|
+
suggestion?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PreflightChecks {
|
|
18
|
+
configExists: PreflightResult
|
|
19
|
+
configValid: PreflightResult
|
|
20
|
+
inGitRepo: PreflightResult
|
|
21
|
+
hasGitRemote: PreflightResult
|
|
22
|
+
canWriteGithubDir: PreflightResult
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if config file exists using cosmiconfig
|
|
27
|
+
* Searches for .pipecraftrc.json, .pipecraftrc, or package.json pipecraft key
|
|
28
|
+
*/
|
|
29
|
+
export function checkConfigExists(): PreflightResult {
|
|
30
|
+
const explorer = cosmiconfigSync('pipecraft')
|
|
31
|
+
const result = explorer.search()
|
|
32
|
+
|
|
33
|
+
if (!result) {
|
|
34
|
+
return {
|
|
35
|
+
passed: false,
|
|
36
|
+
message: 'No PipeCraft configuration found',
|
|
37
|
+
suggestion: "Run 'pipecraft init' to create a configuration file"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
passed: true,
|
|
43
|
+
message: `Configuration found: ${result.filepath}`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if config file is valid and has required fields
|
|
49
|
+
*/
|
|
50
|
+
export function checkConfigValid(): PreflightResult {
|
|
51
|
+
const explorer = cosmiconfigSync('pipecraft')
|
|
52
|
+
const result = explorer.search()
|
|
53
|
+
|
|
54
|
+
if (!result) {
|
|
55
|
+
return {
|
|
56
|
+
passed: false,
|
|
57
|
+
message: 'No configuration file found',
|
|
58
|
+
suggestion: "Run 'pipecraft init' first"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const config = result.config
|
|
64
|
+
|
|
65
|
+
// Check for required fields
|
|
66
|
+
const requiredFields = ['ciProvider', 'branchFlow', 'domains']
|
|
67
|
+
const missingFields = requiredFields.filter(field => !(field in config))
|
|
68
|
+
|
|
69
|
+
if (missingFields.length > 0) {
|
|
70
|
+
return {
|
|
71
|
+
passed: false,
|
|
72
|
+
message: `Config is missing required fields: ${missingFields.join(', ')}`,
|
|
73
|
+
suggestion: "Run 'pipecraft init --force' to recreate config"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check domains is not empty
|
|
78
|
+
if (!config.domains || Object.keys(config.domains).length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
passed: false,
|
|
81
|
+
message: 'Config has no domains configured',
|
|
82
|
+
suggestion: "Add at least one domain to your config file"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
passed: true,
|
|
88
|
+
message: 'Configuration is valid'
|
|
89
|
+
}
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
if (error instanceof SyntaxError) {
|
|
92
|
+
return {
|
|
93
|
+
passed: false,
|
|
94
|
+
message: `Invalid JSON in config file: ${error.message}`,
|
|
95
|
+
suggestion: 'Fix JSON syntax in your config file'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
passed: false,
|
|
101
|
+
message: `Error reading config: ${error.message}`,
|
|
102
|
+
suggestion: 'Check file permissions for your config file'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if current directory is a git repository
|
|
109
|
+
*/
|
|
110
|
+
export function checkInGitRepo(): PreflightResult {
|
|
111
|
+
try {
|
|
112
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
113
|
+
stdio: 'pipe',
|
|
114
|
+
encoding: 'utf8'
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
passed: true,
|
|
119
|
+
message: 'Current directory is a git repository'
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
passed: false,
|
|
124
|
+
message: 'Not in a git repository',
|
|
125
|
+
suggestion: "Initialize git: 'git init' or clone an existing repository"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if git remote is configured
|
|
132
|
+
*/
|
|
133
|
+
export function checkHasGitRemote(): PreflightResult {
|
|
134
|
+
try {
|
|
135
|
+
const remote = execSync('git remote get-url origin', {
|
|
136
|
+
stdio: 'pipe',
|
|
137
|
+
encoding: 'utf8'
|
|
138
|
+
}).trim()
|
|
139
|
+
|
|
140
|
+
if (!remote) {
|
|
141
|
+
return {
|
|
142
|
+
passed: false,
|
|
143
|
+
message: 'No git remote configured',
|
|
144
|
+
suggestion: "Add remote: 'git remote add origin <url>'"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if it's a GitHub remote
|
|
149
|
+
const isGitHub = remote.includes('github.com')
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
passed: true,
|
|
153
|
+
message: `Git remote configured: ${remote}`,
|
|
154
|
+
suggestion: !isGitHub
|
|
155
|
+
? 'Note: PipeCraft is optimized for GitHub. GitLab support is experimental.'
|
|
156
|
+
: undefined
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
passed: false,
|
|
161
|
+
message: 'No git remote named "origin"',
|
|
162
|
+
suggestion: "Add remote: 'git remote add origin <url>'"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if .github directory is writable
|
|
169
|
+
*/
|
|
170
|
+
export function checkCanWriteGithubDir(): PreflightResult {
|
|
171
|
+
const githubDir = '.github'
|
|
172
|
+
const workflowsDir = join(githubDir, 'workflows')
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (!existsSync(githubDir)) {
|
|
176
|
+
// Try to create it
|
|
177
|
+
mkdirSync(githubDir, { recursive: true })
|
|
178
|
+
mkdirSync(workflowsDir, { recursive: true })
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
passed: true,
|
|
182
|
+
message: 'Created .github/workflows directory'
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Directory exists, check if writable
|
|
187
|
+
if (existsSync(workflowsDir)) {
|
|
188
|
+
// Try to write a test file
|
|
189
|
+
const testFile = join(workflowsDir, '.pipecraft-test')
|
|
190
|
+
writeFileSync(testFile, 'test')
|
|
191
|
+
unlinkSync(testFile)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
passed: true,
|
|
195
|
+
message: '.github/workflows directory is writable'
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
mkdirSync(workflowsDir, { recursive: true })
|
|
199
|
+
return {
|
|
200
|
+
passed: true,
|
|
201
|
+
message: 'Created .github/workflows directory'
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error: any) {
|
|
205
|
+
return {
|
|
206
|
+
passed: false,
|
|
207
|
+
message: `Cannot write to .github directory: ${error.message}`,
|
|
208
|
+
suggestion: 'Check directory permissions'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check Node.js version
|
|
215
|
+
*/
|
|
216
|
+
export function checkNodeVersion(minVersion: string = '18.0.0'): PreflightResult {
|
|
217
|
+
const currentVersion = process.version.slice(1) // Remove 'v' prefix
|
|
218
|
+
|
|
219
|
+
const [currentMajor] = currentVersion.split('.').map(Number)
|
|
220
|
+
const [minMajor] = minVersion.split('.').map(Number)
|
|
221
|
+
|
|
222
|
+
if (currentMajor < minMajor) {
|
|
223
|
+
return {
|
|
224
|
+
passed: false,
|
|
225
|
+
message: `Node.js ${currentVersion} is too old (minimum: ${minVersion})`,
|
|
226
|
+
suggestion: 'Update Node.js: https://nodejs.org'
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
passed: true,
|
|
232
|
+
message: `Node.js ${currentVersion} (>= ${minVersion})`
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Run all pre-flight checks for generate command
|
|
238
|
+
* Note: No longer needs configPath - uses cosmiconfig to search automatically
|
|
239
|
+
*/
|
|
240
|
+
export function runPreflightChecks(): PreflightChecks {
|
|
241
|
+
return {
|
|
242
|
+
configExists: checkConfigExists(),
|
|
243
|
+
configValid: checkConfigValid(),
|
|
244
|
+
inGitRepo: checkInGitRepo(),
|
|
245
|
+
hasGitRemote: checkHasGitRemote(),
|
|
246
|
+
canWriteGithubDir: checkCanWriteGithubDir()
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Format preflight results for display
|
|
252
|
+
*/
|
|
253
|
+
export function formatPreflightResults(checks: PreflightChecks): {
|
|
254
|
+
allPassed: boolean
|
|
255
|
+
output: string
|
|
256
|
+
nextSteps?: string[]
|
|
257
|
+
} {
|
|
258
|
+
const results: string[] = []
|
|
259
|
+
let allPassed = true
|
|
260
|
+
|
|
261
|
+
for (const [key, result] of Object.entries(checks)) {
|
|
262
|
+
const icon = result.passed ? '✅' : '❌'
|
|
263
|
+
results.push(`${icon} ${result.message}`)
|
|
264
|
+
|
|
265
|
+
if (!result.passed) {
|
|
266
|
+
allPassed = false
|
|
267
|
+
if (result.suggestion) {
|
|
268
|
+
results.push(` 💡 ${result.suggestion}`)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Provide next steps if all checks passed
|
|
274
|
+
const nextSteps: string[] | undefined = allPassed ? [
|
|
275
|
+
'Your environment is ready to generate workflows!',
|
|
276
|
+
'',
|
|
277
|
+
'Next steps:',
|
|
278
|
+
' 1. Validate the pipeline: pipecraft validate:pipeline',
|
|
279
|
+
' 2. Set up GitHub tokens: pipecraft setup-github --verify',
|
|
280
|
+
' 3. Commit the generated workflows: git add .github && git commit -m "chore: add workflows"',
|
|
281
|
+
' 4. Push to remote: git push origin ' + getCurrentBranch(),
|
|
282
|
+
'',
|
|
283
|
+
'⚠️ Important: Set up GitHub tokens (step 2) BEFORE pushing to ensure workflows run correctly!'
|
|
284
|
+
] : undefined
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
allPassed,
|
|
288
|
+
output: results.join('\n'),
|
|
289
|
+
nextSteps
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get current git branch name
|
|
295
|
+
*/
|
|
296
|
+
function getCurrentBranch(): string {
|
|
297
|
+
try {
|
|
298
|
+
const branch = execSync('git branch --show-current', {
|
|
299
|
+
stdio: 'pipe',
|
|
300
|
+
encoding: 'utf8'
|
|
301
|
+
}).trim()
|
|
302
|
+
return branch || 'main'
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return 'main'
|
|
305
|
+
}
|
|
306
|
+
}
|