pipecraft 0.0.0-releaseit → 0.26.0

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.
Files changed (183) hide show
  1. package/README.md +126 -108
  2. package/dist/cli/index.d.ts +69 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +94 -22
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/generators/init.tpl.d.ts +70 -0
  7. package/dist/generators/init.tpl.d.ts.map +1 -1
  8. package/dist/generators/init.tpl.js +100 -8
  9. package/dist/generators/init.tpl.js.map +1 -1
  10. package/dist/generators/workflows.tpl.d.ts +86 -0
  11. package/dist/generators/workflows.tpl.d.ts.map +1 -1
  12. package/dist/generators/workflows.tpl.js +101 -9
  13. package/dist/generators/workflows.tpl.js.map +1 -1
  14. package/dist/templates/actions/calculate-version.yml.tpl.d.ts +47 -0
  15. package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
  16. package/dist/templates/actions/calculate-version.yml.tpl.js +72 -3
  17. package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
  18. package/dist/templates/actions/create-pr.yml.tpl.d.ts +14 -0
  19. package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
  20. package/dist/templates/actions/create-pr.yml.tpl.js +22 -2
  21. package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
  22. package/dist/templates/actions/create-release.yml.tpl.d.ts +18 -0
  23. package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -0
  24. package/dist/templates/actions/create-release.yml.tpl.js +141 -0
  25. package/dist/templates/actions/create-release.yml.tpl.js.map +1 -0
  26. package/dist/templates/actions/create-tag.yml.tpl.d.ts +14 -0
  27. package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
  28. package/dist/templates/actions/create-tag.yml.tpl.js +22 -2
  29. package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
  30. package/dist/templates/actions/detect-changes.yml.tpl.d.ts +70 -0
  31. package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
  32. package/dist/templates/actions/detect-changes.yml.tpl.js +98 -2
  33. package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
  34. package/dist/templates/actions/manage-branch.yml.tpl.d.ts +14 -0
  35. package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
  36. package/dist/templates/actions/manage-branch.yml.tpl.js +22 -2
  37. package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
  38. package/dist/templates/actions/promote-branch.yml.tpl.d.ts +14 -0
  39. package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
  40. package/dist/templates/actions/promote-branch.yml.tpl.js +50 -7
  41. package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
  42. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +114 -1
  43. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -1
  44. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +479 -74
  45. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -1
  46. package/dist/types/index.d.ts +231 -8
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/index.js +9 -0
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/ast-path-operations.d.ts.map +1 -1
  51. package/dist/utils/ast-path-operations.js +24 -11
  52. package/dist/utils/ast-path-operations.js.map +1 -1
  53. package/dist/utils/config.d.ts +63 -0
  54. package/dist/utils/config.d.ts.map +1 -1
  55. package/dist/utils/config.js +71 -0
  56. package/dist/utils/config.js.map +1 -1
  57. package/dist/utils/github-setup.d.ts +323 -2
  58. package/dist/utils/github-setup.d.ts.map +1 -1
  59. package/dist/utils/github-setup.js +551 -23
  60. package/dist/utils/github-setup.js.map +1 -1
  61. package/dist/utils/idempotency.d.ts +147 -7
  62. package/dist/utils/idempotency.d.ts.map +1 -1
  63. package/dist/utils/idempotency.js +143 -15
  64. package/dist/utils/idempotency.js.map +1 -1
  65. package/dist/utils/logger.d.ts +134 -0
  66. package/dist/utils/logger.d.ts.map +1 -0
  67. package/dist/utils/logger.js +153 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/dist/utils/preflight.d.ts +223 -12
  70. package/dist/utils/preflight.d.ts.map +1 -1
  71. package/dist/utils/preflight.js +216 -16
  72. package/dist/utils/preflight.js.map +1 -1
  73. package/dist/utils/versioning.d.ts +160 -8
  74. package/dist/utils/versioning.d.ts.map +1 -1
  75. package/dist/utils/versioning.js +179 -15
  76. package/dist/utils/versioning.js.map +1 -1
  77. package/package.json +26 -5
  78. package/.claude/settings.local.json +0 -35
  79. package/.github/actions/calculate-version/action.yml +0 -106
  80. package/.github/actions/create-pr/action.yml +0 -122
  81. package/.github/actions/create-release/action.yml +0 -74
  82. package/.github/actions/create-tag/action.yml +0 -94
  83. package/.github/actions/detect-changes/action.yml +0 -62
  84. package/.github/actions/manage-branch/action.yml +0 -113
  85. package/.github/actions/promote-branch/action.yml +0 -352
  86. package/.github/example/workflows/job.analyze.code.yml +0 -26
  87. package/.github/example/workflows/job.analyze.docker.yml +0 -32
  88. package/.github/example/workflows/job.app.api.deploy.yml +0 -127
  89. package/.github/example/workflows/job.app.api.test.yml +0 -102
  90. package/.github/example/workflows/job.app.docs.deploy.yml +0 -40
  91. package/.github/example/workflows/job.app.docs.test.yml +0 -50
  92. package/.github/example/workflows/job.app.web.deploy.yml +0 -96
  93. package/.github/example/workflows/job.app.web.test.yml +0 -49
  94. package/.github/example/workflows/job.changes.yml +0 -82
  95. package/.github/example/workflows/job.create-pr.yml +0 -96
  96. package/.github/example/workflows/job.env-check.yml +0 -43
  97. package/.github/example/workflows/job.fast-forward.yml +0 -103
  98. package/.github/example/workflows/job.lint.yml +0 -64
  99. package/.github/example/workflows/job.pr-name.yml +0 -70
  100. package/.github/example/workflows/job.pr-squash-name.yml +0 -27
  101. package/.github/example/workflows/job.tag.yml +0 -38
  102. package/.github/example/workflows/job.version.yml +0 -99
  103. package/.github/example/workflows/lib.plugin-base.publish.yml +0 -59
  104. package/.github/example/workflows/pipe.yml +0 -178
  105. package/.github/example/workflows/scripts/fastforward.sh +0 -51
  106. package/.github/example/workflows/scripts/pipe-cleaner.sh +0 -70
  107. package/.github/scripts/fastforward.sh +0 -51
  108. package/.github/scripts/pipe-cleaner.sh +0 -70
  109. package/.github/workflows/pipeline.yml +0 -220
  110. package/.github/workflows/pr-title-check.yml +0 -70
  111. package/.github/workflows/publish.yml +0 -39
  112. package/.pipecraftrc.json +0 -78
  113. package/.release-it.cjs +0 -71
  114. package/PIPELINE_TESTING_PLAN.md +0 -499
  115. package/TRUNK_FLOW_PLAN.md +0 -401
  116. package/assets/logo_banner.png +0 -0
  117. package/assets/logo_banner.webp +0 -0
  118. package/docs/USER_JOURNEY_ERRORS.md +0 -352
  119. package/eslint.config.js +0 -77
  120. package/examples/basic-config.json +0 -41
  121. package/examples/monorepo-config.json +0 -49
  122. package/examples/usage.md +0 -289
  123. package/scripts/debug-workflows.sh +0 -339
  124. package/scripts/parse-pipeline.js +0 -29
  125. package/scripts/test-job-order.cjs +0 -140
  126. package/scripts/validate-pipeline.cjs +0 -379
  127. package/scripts/verify-job-order.sh +0 -30
  128. package/src/cli/index.ts +0 -414
  129. package/src/generators/init.tpl.ts +0 -126
  130. package/src/generators/workflows.tpl.ts +0 -80
  131. package/src/templates/actions/calculate-version.yml.tpl.ts +0 -124
  132. package/src/templates/actions/create-pr.yml.tpl.ts +0 -147
  133. package/src/templates/actions/create-tag.yml.tpl.ts +0 -112
  134. package/src/templates/actions/detect-changes.yml.tpl.ts +0 -87
  135. package/src/templates/actions/manage-branch.yml.tpl.ts +0 -132
  136. package/src/templates/actions/promote-branch.yml.tpl.ts +0 -364
  137. package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +0 -706
  138. package/src/types/index.ts +0 -64
  139. package/src/utils/README-ast-path-operations.md +0 -390
  140. package/src/utils/ast-path-operations.ts +0 -581
  141. package/src/utils/config.ts +0 -64
  142. package/src/utils/github-setup.ts +0 -558
  143. package/src/utils/idempotency.ts +0 -215
  144. package/src/utils/preflight.ts +0 -306
  145. package/src/utils/versioning.ts +0 -244
  146. package/tests/README.md +0 -229
  147. package/tests/TEST_STRUCTURE.md +0 -256
  148. package/tests/act/run-act-tests.sh +0 -345
  149. package/tests/debugging/debug-utils.ts +0 -538
  150. package/tests/debugging/debug-workflow.test.ts +0 -339
  151. package/tests/debugging/debug-workflows.sh +0 -339
  152. package/tests/debugging/iterative-debug.ts +0 -652
  153. package/tests/debugging/run-debug-tests.sh +0 -431
  154. package/tests/fixtures/basic-config.json +0 -51
  155. package/tests/fixtures/invalid-config.json +0 -9
  156. package/tests/fixtures/pipeline-generated.yml +0 -235
  157. package/tests/fixtures/pipeline-preserve-comments.yml +0 -245
  158. package/tests/fixtures/pipeline-user-modified.yml +0 -245
  159. package/tests/fixtures/test-config.json +0 -58
  160. package/tests/github-live/README.md +0 -250
  161. package/tests/github-local/JOB_WORKFLOW_TESTS.md +0 -296
  162. package/tests/github-local/README.md +0 -240
  163. package/tests/github-local/run-all-tests.sh +0 -422
  164. package/tests/github-local/test-job-workflows.sh +0 -631
  165. package/tests/github-local/test-pipeline-workflow.sh +0 -440
  166. package/tests/integration/generators.test.ts +0 -578
  167. package/tests/integration/path-based-template.test.ts +0 -510
  168. package/tests/integration/simple-path-based.test.ts +0 -415
  169. package/tests/setup.ts +0 -56
  170. package/tests/unit/ast-path-operations-extended.test.ts +0 -302
  171. package/tests/unit/cli.test.ts +0 -541
  172. package/tests/unit/config-extended.test.ts +0 -412
  173. package/tests/unit/config.test.ts +0 -152
  174. package/tests/unit/github-setup.test.ts +0 -189
  175. package/tests/unit/idempotency-isolated.test.ts +0 -297
  176. package/tests/unit/job-order.test.ts +0 -157
  177. package/tests/unit/pipeline-path-based.test.ts +0 -511
  178. package/tests/unit/validate-pipeline.test.ts +0 -384
  179. package/tests/unit/versioning-extended.test.ts +0 -279
  180. package/tests/unit/versioning.test.ts +0 -241
  181. package/tsconfig.debug.json +0 -17
  182. package/tsconfig.json +0 -28
  183. package/vitest.config.ts +0 -51
@@ -1,215 +0,0 @@
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
- }
@@ -1,306 +0,0 @@
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
- }