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.
- package/README.md +126 -108
- package/dist/cli/index.d.ts +69 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +94 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/init.tpl.d.ts +70 -0
- package/dist/generators/init.tpl.d.ts.map +1 -1
- package/dist/generators/init.tpl.js +100 -8
- package/dist/generators/init.tpl.js.map +1 -1
- package/dist/generators/workflows.tpl.d.ts +86 -0
- package/dist/generators/workflows.tpl.d.ts.map +1 -1
- package/dist/generators/workflows.tpl.js +101 -9
- package/dist/generators/workflows.tpl.js.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +47 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.js +72 -3
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.js +22 -2
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-release.yml.tpl.d.ts +18 -0
- package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-release.yml.tpl.js +141 -0
- package/dist/templates/actions/create-release.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-tag.yml.tpl.js +22 -2
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +70 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.js +98 -2
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.js +22 -2
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.js +50 -7
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +114 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +479 -74
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -1
- package/dist/types/index.d.ts +231 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/ast-path-operations.d.ts.map +1 -1
- package/dist/utils/ast-path-operations.js +24 -11
- package/dist/utils/ast-path-operations.js.map +1 -1
- package/dist/utils/config.d.ts +63 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +71 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/github-setup.d.ts +323 -2
- package/dist/utils/github-setup.d.ts.map +1 -1
- package/dist/utils/github-setup.js +551 -23
- package/dist/utils/github-setup.js.map +1 -1
- package/dist/utils/idempotency.d.ts +147 -7
- package/dist/utils/idempotency.d.ts.map +1 -1
- package/dist/utils/idempotency.js +143 -15
- package/dist/utils/idempotency.js.map +1 -1
- package/dist/utils/logger.d.ts +134 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +153 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/preflight.d.ts +223 -12
- package/dist/utils/preflight.d.ts.map +1 -1
- package/dist/utils/preflight.js +216 -16
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/versioning.d.ts +160 -8
- package/dist/utils/versioning.d.ts.map +1 -1
- package/dist/utils/versioning.js +179 -15
- package/dist/utils/versioning.js.map +1 -1
- package/package.json +26 -5
- package/.claude/settings.local.json +0 -35
- package/.github/actions/calculate-version/action.yml +0 -106
- package/.github/actions/create-pr/action.yml +0 -122
- package/.github/actions/create-release/action.yml +0 -74
- package/.github/actions/create-tag/action.yml +0 -94
- package/.github/actions/detect-changes/action.yml +0 -62
- package/.github/actions/manage-branch/action.yml +0 -113
- package/.github/actions/promote-branch/action.yml +0 -352
- package/.github/example/workflows/job.analyze.code.yml +0 -26
- package/.github/example/workflows/job.analyze.docker.yml +0 -32
- package/.github/example/workflows/job.app.api.deploy.yml +0 -127
- package/.github/example/workflows/job.app.api.test.yml +0 -102
- package/.github/example/workflows/job.app.docs.deploy.yml +0 -40
- package/.github/example/workflows/job.app.docs.test.yml +0 -50
- package/.github/example/workflows/job.app.web.deploy.yml +0 -96
- package/.github/example/workflows/job.app.web.test.yml +0 -49
- package/.github/example/workflows/job.changes.yml +0 -82
- package/.github/example/workflows/job.create-pr.yml +0 -96
- package/.github/example/workflows/job.env-check.yml +0 -43
- package/.github/example/workflows/job.fast-forward.yml +0 -103
- package/.github/example/workflows/job.lint.yml +0 -64
- package/.github/example/workflows/job.pr-name.yml +0 -70
- package/.github/example/workflows/job.pr-squash-name.yml +0 -27
- package/.github/example/workflows/job.tag.yml +0 -38
- package/.github/example/workflows/job.version.yml +0 -99
- package/.github/example/workflows/lib.plugin-base.publish.yml +0 -59
- package/.github/example/workflows/pipe.yml +0 -178
- package/.github/example/workflows/scripts/fastforward.sh +0 -51
- package/.github/example/workflows/scripts/pipe-cleaner.sh +0 -70
- package/.github/scripts/fastforward.sh +0 -51
- package/.github/scripts/pipe-cleaner.sh +0 -70
- package/.github/workflows/pipeline.yml +0 -220
- package/.github/workflows/pr-title-check.yml +0 -70
- package/.github/workflows/publish.yml +0 -39
- package/.pipecraftrc.json +0 -78
- package/.release-it.cjs +0 -71
- package/PIPELINE_TESTING_PLAN.md +0 -499
- package/TRUNK_FLOW_PLAN.md +0 -401
- package/assets/logo_banner.png +0 -0
- package/assets/logo_banner.webp +0 -0
- package/docs/USER_JOURNEY_ERRORS.md +0 -352
- package/eslint.config.js +0 -77
- package/examples/basic-config.json +0 -41
- package/examples/monorepo-config.json +0 -49
- package/examples/usage.md +0 -289
- package/scripts/debug-workflows.sh +0 -339
- package/scripts/parse-pipeline.js +0 -29
- package/scripts/test-job-order.cjs +0 -140
- package/scripts/validate-pipeline.cjs +0 -379
- package/scripts/verify-job-order.sh +0 -30
- package/src/cli/index.ts +0 -414
- package/src/generators/init.tpl.ts +0 -126
- package/src/generators/workflows.tpl.ts +0 -80
- package/src/templates/actions/calculate-version.yml.tpl.ts +0 -124
- package/src/templates/actions/create-pr.yml.tpl.ts +0 -147
- package/src/templates/actions/create-tag.yml.tpl.ts +0 -112
- package/src/templates/actions/detect-changes.yml.tpl.ts +0 -87
- package/src/templates/actions/manage-branch.yml.tpl.ts +0 -132
- package/src/templates/actions/promote-branch.yml.tpl.ts +0 -364
- package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +0 -706
- package/src/types/index.ts +0 -64
- package/src/utils/README-ast-path-operations.md +0 -390
- package/src/utils/ast-path-operations.ts +0 -581
- package/src/utils/config.ts +0 -64
- package/src/utils/github-setup.ts +0 -558
- package/src/utils/idempotency.ts +0 -215
- package/src/utils/preflight.ts +0 -306
- package/src/utils/versioning.ts +0 -244
- package/tests/README.md +0 -229
- package/tests/TEST_STRUCTURE.md +0 -256
- package/tests/act/run-act-tests.sh +0 -345
- package/tests/debugging/debug-utils.ts +0 -538
- package/tests/debugging/debug-workflow.test.ts +0 -339
- package/tests/debugging/debug-workflows.sh +0 -339
- package/tests/debugging/iterative-debug.ts +0 -652
- package/tests/debugging/run-debug-tests.sh +0 -431
- package/tests/fixtures/basic-config.json +0 -51
- package/tests/fixtures/invalid-config.json +0 -9
- package/tests/fixtures/pipeline-generated.yml +0 -235
- package/tests/fixtures/pipeline-preserve-comments.yml +0 -245
- package/tests/fixtures/pipeline-user-modified.yml +0 -245
- package/tests/fixtures/test-config.json +0 -58
- package/tests/github-live/README.md +0 -250
- package/tests/github-local/JOB_WORKFLOW_TESTS.md +0 -296
- package/tests/github-local/README.md +0 -240
- package/tests/github-local/run-all-tests.sh +0 -422
- package/tests/github-local/test-job-workflows.sh +0 -631
- package/tests/github-local/test-pipeline-workflow.sh +0 -440
- package/tests/integration/generators.test.ts +0 -578
- package/tests/integration/path-based-template.test.ts +0 -510
- package/tests/integration/simple-path-based.test.ts +0 -415
- package/tests/setup.ts +0 -56
- package/tests/unit/ast-path-operations-extended.test.ts +0 -302
- package/tests/unit/cli.test.ts +0 -541
- package/tests/unit/config-extended.test.ts +0 -412
- package/tests/unit/config.test.ts +0 -152
- package/tests/unit/github-setup.test.ts +0 -189
- package/tests/unit/idempotency-isolated.test.ts +0 -297
- package/tests/unit/job-order.test.ts +0 -157
- package/tests/unit/pipeline-path-based.test.ts +0 -511
- package/tests/unit/validate-pipeline.test.ts +0 -384
- package/tests/unit/versioning-extended.test.ts +0 -279
- package/tests/unit/versioning.test.ts +0 -241
- package/tsconfig.debug.json +0 -17
- package/tsconfig.json +0 -28
- package/vitest.config.ts +0 -51
|
@@ -1,581 +0,0 @@
|
|
|
1
|
-
import { parseDocument, stringify, YAMLMap, YAMLSeq, Scalar, Node } from 'yaml'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* # AST Path Operations for YAML Manipulation
|
|
5
|
-
*
|
|
6
|
-
* This module provides a powerful and precise way to manipulate YAML documents using
|
|
7
|
-
* path-based operations. It allows you to target specific locations in the YAML
|
|
8
|
-
* structure and apply different types of operations (set, merge, overwrite, preserve).
|
|
9
|
-
*
|
|
10
|
-
* ## Key Features
|
|
11
|
-
*
|
|
12
|
-
* - **Path-based targeting**: Use dot notation to target specific YAML paths
|
|
13
|
-
* - **Multiple operation types**: Set, merge, overwrite, or preserve values
|
|
14
|
-
* - **Flexible value types**: Support objects, arrays, strings, YAML nodes, and parsed documents
|
|
15
|
-
* - **Type safety**: Full TypeScript support with proper Node types
|
|
16
|
-
* - **Context injection**: Dynamic values can be injected at build time
|
|
17
|
-
*
|
|
18
|
-
* ## Usage Examples
|
|
19
|
-
*
|
|
20
|
-
* ### Basic Path Operations
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { applyPathOperations, createValueFromString } from './ast-path-operations'
|
|
23
|
-
*
|
|
24
|
-
* const operations = [
|
|
25
|
-
* {
|
|
26
|
-
* path: 'on.workflow_call.inputs.version',
|
|
27
|
-
* operation: 'set',
|
|
28
|
-
* value: {
|
|
29
|
-
* description: 'The version to deploy',
|
|
30
|
-
* required: false,
|
|
31
|
-
* type: 'string'
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* ]
|
|
35
|
-
*
|
|
36
|
-
* applyPathOperations(doc, operations)
|
|
37
|
-
* ```
|
|
38
|
-
*
|
|
39
|
-
* ### Complex Job Definitions
|
|
40
|
-
* ```typescript
|
|
41
|
-
* const jobOperation = {
|
|
42
|
-
* path: 'jobs.changes',
|
|
43
|
-
* operation: 'overwrite',
|
|
44
|
-
* value: createValueFromString(`
|
|
45
|
-
* runs-on: ubuntu-latest
|
|
46
|
-
* steps:
|
|
47
|
-
* - uses: ./.github/actions/detect-changes
|
|
48
|
-
* with:
|
|
49
|
-
* baseRef: ${{ inputs.baseRef || 'main' }}
|
|
50
|
-
* `)
|
|
51
|
-
* }
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* ### Array Merging
|
|
55
|
-
* ```typescript
|
|
56
|
-
* const branchOperation = {
|
|
57
|
-
* path: 'on.pull_request.branches',
|
|
58
|
-
* operation: 'merge',
|
|
59
|
-
* value: ['develop', 'staging', 'main']
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*
|
|
63
|
-
* ## Operation Types
|
|
64
|
-
*
|
|
65
|
-
* - **`set`**: Set a value at the specified path (creates if doesn't exist)
|
|
66
|
-
* - **`merge`**: Merge with existing value (for objects/arrays)
|
|
67
|
-
* - **`overwrite`**: Replace existing value completely
|
|
68
|
-
* - **`preserve`**: Keep existing value, ignore template value
|
|
69
|
-
*
|
|
70
|
-
* ## Value Types Supported
|
|
71
|
-
*
|
|
72
|
-
* - **Objects**: `{ key: 'value' }` - Simple key-value pairs
|
|
73
|
-
* - **Arrays**: `['item1', 'item2']` - Simple arrays
|
|
74
|
-
* - **YAML Strings**: Multi-line YAML with proper formatting
|
|
75
|
-
* - **Parsed Documents**: Pre-parsed YAML nodes
|
|
76
|
-
* - **Primitives**: strings, numbers, booleans
|
|
77
|
-
*
|
|
78
|
-
* @fileoverview Path-based AST operations for precise YAML manipulation
|
|
79
|
-
* @author Pipecraft Team
|
|
80
|
-
* @version 1.0.0
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Available operation types for path-based AST manipulation
|
|
85
|
-
*
|
|
86
|
-
* @typedef {('set' | 'merge' | 'overwrite' | 'preserve')} PathOperation
|
|
87
|
-
*
|
|
88
|
-
* - `set`: Set a value at the specified path (creates if doesn't exist)
|
|
89
|
-
* - `merge`: Merge with existing value (for objects/arrays)
|
|
90
|
-
* - `overwrite`: Replace existing value completely
|
|
91
|
-
* - `preserve`: Keep existing value, ignore template value
|
|
92
|
-
*/
|
|
93
|
-
export type PathOperation = 'set' | 'merge' | 'overwrite' | 'preserve'
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Supported value types for path operations
|
|
97
|
-
*
|
|
98
|
-
* @typedef {(Node | object | string | number | boolean | any[])} PathValue
|
|
99
|
-
*
|
|
100
|
-
* Supports YAML nodes, JavaScript objects, primitives, and arrays
|
|
101
|
-
*/
|
|
102
|
-
export type PathValue = Node | object | string | number | boolean | any[]
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Configuration for a single path operation
|
|
106
|
-
*
|
|
107
|
-
* @interface PathOperationConfig
|
|
108
|
-
* @property {string} path - Dot-notation path to target (e.g., 'jobs.changes.steps')
|
|
109
|
-
* @property {PathOperation} operation - Type of operation to perform
|
|
110
|
-
* @property {PathValue} value - Value to set/merge/overwrite
|
|
111
|
-
* @property {boolean} [required=true] - Whether the path must exist
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* const config: PathOperationConfig = {
|
|
116
|
-
* path: 'on.workflow_call.inputs.version',
|
|
117
|
-
* operation: 'set',
|
|
118
|
-
* value: {
|
|
119
|
-
* description: 'The version to deploy',
|
|
120
|
-
* required: false,
|
|
121
|
-
* type: 'string'
|
|
122
|
-
* },
|
|
123
|
-
* required: true
|
|
124
|
-
* }
|
|
125
|
-
* ```
|
|
126
|
-
*/
|
|
127
|
-
export interface PathOperationConfig {
|
|
128
|
-
path: string
|
|
129
|
-
operation: PathOperation
|
|
130
|
-
value: PathValue
|
|
131
|
-
required?: boolean
|
|
132
|
-
commentBefore?: string
|
|
133
|
-
comment?: string
|
|
134
|
-
spaceBefore?: boolean
|
|
135
|
-
spaceBeforeComment?: boolean
|
|
136
|
-
tag?: string
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Set a value at a specific path in the YAML AST
|
|
141
|
-
*
|
|
142
|
-
* Creates intermediate nodes as needed and sets the final value at the specified path.
|
|
143
|
-
* This is the core function for setting values in the YAML structure.
|
|
144
|
-
*
|
|
145
|
-
* @param {YAMLMap} doc - The YAML document to modify
|
|
146
|
-
* @param {string} path - Dot-notation path (e.g., 'jobs.changes.steps')
|
|
147
|
-
* @param {PathValue} value - Value to set at the path
|
|
148
|
-
*
|
|
149
|
-
* @throws {Error} When path navigation fails or parent is not a map
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```typescript
|
|
153
|
-
* const doc = parseDocument('name: Pipeline')
|
|
154
|
-
* setPathValue(doc.contents, 'jobs.changes.runs-on', 'ubuntu-latest')
|
|
155
|
-
* // Results in: jobs: { changes: { 'runs-on': 'ubuntu-latest' } }
|
|
156
|
-
* ```
|
|
157
|
-
*/
|
|
158
|
-
export function setPathValue(doc: YAMLMap, path: string, value: PathValue, document?: any, commentBefore?: string, spaceBeforeComment?: boolean): void {
|
|
159
|
-
const pathParts = path.split('.')
|
|
160
|
-
let current: Node = doc
|
|
161
|
-
|
|
162
|
-
// Navigate to the parent of the target
|
|
163
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
164
|
-
const part = pathParts[i]
|
|
165
|
-
|
|
166
|
-
if (current instanceof YAMLMap) {
|
|
167
|
-
let next: any = current.get(part)
|
|
168
|
-
if (!next) {
|
|
169
|
-
// Create missing intermediate nodes
|
|
170
|
-
next = new YAMLMap()
|
|
171
|
-
current.set(part, next)
|
|
172
|
-
}
|
|
173
|
-
current = next
|
|
174
|
-
} else {
|
|
175
|
-
throw new Error(`Cannot navigate to ${part} - parent is not a map`)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Set the final value
|
|
180
|
-
const finalKey = pathParts[pathParts.length - 1]
|
|
181
|
-
if (current instanceof YAMLMap) {
|
|
182
|
-
let node: any
|
|
183
|
-
|
|
184
|
-
// Check if value is already a node (from createValueFromString, etc.)
|
|
185
|
-
if (value && typeof value === 'object' && ('items' in value || 'type' in value)) {
|
|
186
|
-
// Value is already a node, use it directly
|
|
187
|
-
node = value
|
|
188
|
-
} else {
|
|
189
|
-
// Value is not a node, create one
|
|
190
|
-
node = document && document.createNode ? document.createNode(value) : createNode(value)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// When commentBefore is provided, or when setting a job key (which may need comments added later),
|
|
194
|
-
// we need to create a Scalar key instead of a string key.
|
|
195
|
-
// This is because YAML's set() method creates a plain string key by default,
|
|
196
|
-
// but comments can only be attached to Scalar objects.
|
|
197
|
-
const isJobKey = pathParts.length === 2 && pathParts[0] === 'jobs'
|
|
198
|
-
|
|
199
|
-
if (commentBefore || isJobKey) {
|
|
200
|
-
// Create a Scalar key (with or without comment)
|
|
201
|
-
const scalarKey = new Scalar(finalKey)
|
|
202
|
-
|
|
203
|
-
if (commentBefore) {
|
|
204
|
-
;(scalarKey as any).commentBefore = commentBefore
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Add blank line before the comment if requested
|
|
208
|
-
if (spaceBeforeComment) {
|
|
209
|
-
;(scalarKey as any).spaceBefore = true
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Add the pair to the map with a Scalar key
|
|
213
|
-
current.add({ key: scalarKey, value: node })
|
|
214
|
-
} else {
|
|
215
|
-
// Normal case: use the simple set() method which creates a string key
|
|
216
|
-
current.set(finalKey, node)
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
throw new Error(`Cannot set ${finalKey} - parent is not a map`)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Get a value at a specific path in the YAML AST
|
|
225
|
-
*
|
|
226
|
-
* Navigates to the specified path and returns the node if found, null otherwise.
|
|
227
|
-
* This is useful for checking if a path exists before applying operations.
|
|
228
|
-
*
|
|
229
|
-
* @param {YAMLMap} doc - The YAML document to read from
|
|
230
|
-
* @param {string} path - Dot-notation path (e.g., 'jobs.changes.steps')
|
|
231
|
-
* @returns {Node | null} The node at the path, or null if not found
|
|
232
|
-
*
|
|
233
|
-
* @example
|
|
234
|
-
* ```typescript
|
|
235
|
-
* const doc = parseDocument('jobs: { changes: { runs-on: ubuntu-latest } }')
|
|
236
|
-
* const value = getPathValue(doc.contents, 'jobs.changes.runs-on')
|
|
237
|
-
* console.log(value) // Scalar('ubuntu-latest')
|
|
238
|
-
* ```
|
|
239
|
-
*/
|
|
240
|
-
export function getPathValue(doc: YAMLMap, path: string): Node | null {
|
|
241
|
-
const pathParts = path.split('.')
|
|
242
|
-
let current: Node = doc
|
|
243
|
-
|
|
244
|
-
for (const part of pathParts) {
|
|
245
|
-
if (current instanceof YAMLMap) {
|
|
246
|
-
current = current.get(part) as Node
|
|
247
|
-
if (!current) return null
|
|
248
|
-
} else {
|
|
249
|
-
return null
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return current
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Ensure a path exists and apply the specified operation
|
|
258
|
-
*
|
|
259
|
-
* This is the main orchestration function that handles all path operations.
|
|
260
|
-
* It checks if the path exists, applies the appropriate operation based on
|
|
261
|
-
* the configuration, and handles required vs optional paths.
|
|
262
|
-
*
|
|
263
|
-
* @param {YAMLMap} doc - The YAML document to modify
|
|
264
|
-
* @param {PathOperationConfig} config - Operation configuration
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* ```typescript
|
|
268
|
-
* const config: PathOperationConfig = {
|
|
269
|
-
* path: 'jobs.changes.runs-on',
|
|
270
|
-
* operation: 'set',
|
|
271
|
-
* value: 'ubuntu-latest',
|
|
272
|
-
* required: true
|
|
273
|
-
* }
|
|
274
|
-
*
|
|
275
|
-
* ensurePathAndApply(doc, config)
|
|
276
|
-
* ```
|
|
277
|
-
*/
|
|
278
|
-
export function ensurePathAndApply(
|
|
279
|
-
doc: YAMLMap,
|
|
280
|
-
config: PathOperationConfig,
|
|
281
|
-
document?: any
|
|
282
|
-
): void {
|
|
283
|
-
const { path, operation, value, required = true, commentBefore, spaceBeforeComment } = config
|
|
284
|
-
|
|
285
|
-
// Check if path exists
|
|
286
|
-
const existingValue = getPathValue(doc, path)
|
|
287
|
-
|
|
288
|
-
if (!existingValue && !required) {
|
|
289
|
-
// Path doesn't exist and not required - skip
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Apply operation based on whether path exists or not
|
|
294
|
-
// For required paths that don't exist, we still respect the operation type
|
|
295
|
-
switch (operation) {
|
|
296
|
-
case 'set':
|
|
297
|
-
setPathValue(doc, path, value, document, commentBefore, spaceBeforeComment)
|
|
298
|
-
break
|
|
299
|
-
|
|
300
|
-
case 'merge':
|
|
301
|
-
// Merge will handle non-existent paths by creating them
|
|
302
|
-
mergePathValue(doc, path, value, document)
|
|
303
|
-
break
|
|
304
|
-
|
|
305
|
-
case 'overwrite':
|
|
306
|
-
setPathValue(doc, path, value, document, commentBefore, spaceBeforeComment)
|
|
307
|
-
break
|
|
308
|
-
|
|
309
|
-
case 'preserve':
|
|
310
|
-
// Only preserve if path exists, otherwise create it
|
|
311
|
-
if (!existingValue) {
|
|
312
|
-
setPathValue(doc, path, value, document, commentBefore, spaceBeforeComment)
|
|
313
|
-
}
|
|
314
|
-
// If exists, do nothing - keep existing value
|
|
315
|
-
break
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Merge a value at a specific path (for objects/arrays)
|
|
321
|
-
*
|
|
322
|
-
* Intelligently merges values based on their type:
|
|
323
|
-
* - Objects: Merges key-value pairs, preserving existing keys
|
|
324
|
-
* - Arrays: Adds new items that don't already exist
|
|
325
|
-
* - Other types: Falls back to overwrite behavior
|
|
326
|
-
*
|
|
327
|
-
* @param {YAMLMap} doc - The YAML document to modify
|
|
328
|
-
* @param {string} path - Dot-notation path to merge at
|
|
329
|
-
* @param {PathValue} value - Value to merge
|
|
330
|
-
*
|
|
331
|
-
* @example
|
|
332
|
-
* ```typescript
|
|
333
|
-
* // Merge object properties
|
|
334
|
-
* mergePathValue(doc, 'on.workflow_call.inputs', {
|
|
335
|
-
* version: { description: 'Version to deploy' },
|
|
336
|
-
* environment: { description: 'Environment to deploy to' }
|
|
337
|
-
* })
|
|
338
|
-
*
|
|
339
|
-
* // Merge array items
|
|
340
|
-
* mergePathValue(doc, 'on.pull_request.branches', ['feature-branch'])
|
|
341
|
-
* ```
|
|
342
|
-
*/
|
|
343
|
-
function mergePathValue(doc: YAMLMap, path: string, value: PathValue, document?: any): void {
|
|
344
|
-
const existingValue = getPathValue(doc, path)
|
|
345
|
-
|
|
346
|
-
if (!existingValue) {
|
|
347
|
-
setPathValue(doc, path, value, document)
|
|
348
|
-
return
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Merge logic based on type
|
|
352
|
-
if (existingValue instanceof YAMLMap && typeof value === 'object') {
|
|
353
|
-
// Merge objects
|
|
354
|
-
const newMap = (document && document.createNode ? document.createNode(value) : createNode(value)) as YAMLMap
|
|
355
|
-
for (const pair of newMap.items) {
|
|
356
|
-
const key = pair.key
|
|
357
|
-
const val = pair.value
|
|
358
|
-
existingValue.set(key, val)
|
|
359
|
-
}
|
|
360
|
-
} else if (existingValue instanceof YAMLSeq && Array.isArray(value)) {
|
|
361
|
-
// Merge arrays - add new items that don't exist
|
|
362
|
-
const newSeq = (document && document.createNode ? document.createNode(value) : createNode(value)) as YAMLSeq
|
|
363
|
-
for (const item of newSeq.items) {
|
|
364
|
-
if (!existingValue.items.some(existing =>
|
|
365
|
-
stringify(existing) === stringify(item)
|
|
366
|
-
)) {
|
|
367
|
-
existingValue.items.push(item)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
// Fallback to overwrite
|
|
372
|
-
setPathValue(doc, path, value, document)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Create a YAML node from a JavaScript value, YAML node, or parsed document
|
|
378
|
-
*
|
|
379
|
-
* This is the core value conversion function that handles all supported value types.
|
|
380
|
-
* It intelligently converts JavaScript values to appropriate YAML nodes.
|
|
381
|
-
*
|
|
382
|
-
* @param {PathValue} value - Value to convert to YAML node
|
|
383
|
-
* @returns {Node} The converted YAML node
|
|
384
|
-
*
|
|
385
|
-
* @example
|
|
386
|
-
* ```typescript
|
|
387
|
-
* // Convert object
|
|
388
|
-
* const node = createNode({ key: 'value' })
|
|
389
|
-
*
|
|
390
|
-
* // Convert array
|
|
391
|
-
* const node = createNode(['item1', 'item2'])
|
|
392
|
-
*
|
|
393
|
-
* // Convert string
|
|
394
|
-
* const node = createNode('simple string')
|
|
395
|
-
*
|
|
396
|
-
* // Convert YAML string
|
|
397
|
-
* const node = createNode(createValueFromString(`
|
|
398
|
-
* runs-on: ubuntu-latest
|
|
399
|
-
* steps:
|
|
400
|
-
* - name: Example step
|
|
401
|
-
* run: echo "Hello"
|
|
402
|
-
* `))
|
|
403
|
-
* ```
|
|
404
|
-
*/
|
|
405
|
-
function createNode(value: PathValue): Node {
|
|
406
|
-
// If it's already a YAML node, return it
|
|
407
|
-
if (value && typeof value === 'object' && 'type' in value) {
|
|
408
|
-
return value as Node
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// If it's a parsed document, extract the contents
|
|
412
|
-
if (value && typeof value === 'object' && 'contents' in value) {
|
|
413
|
-
const contents = (value as any).contents
|
|
414
|
-
// If contents is a single item, return it directly
|
|
415
|
-
if (contents && typeof contents === 'object' && 'type' in contents) {
|
|
416
|
-
return contents
|
|
417
|
-
}
|
|
418
|
-
return contents
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Handle primitive types
|
|
422
|
-
if (typeof value === 'string') {
|
|
423
|
-
return new Scalar(value)
|
|
424
|
-
} else if (typeof value === 'number') {
|
|
425
|
-
return new Scalar(value)
|
|
426
|
-
} else if (typeof value === 'boolean') {
|
|
427
|
-
return new Scalar(value)
|
|
428
|
-
} else if (Array.isArray(value)) {
|
|
429
|
-
const seq = new YAMLSeq()
|
|
430
|
-
for (const item of value) {
|
|
431
|
-
seq.items.push(createNode(item))
|
|
432
|
-
}
|
|
433
|
-
return seq
|
|
434
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
435
|
-
const map = new YAMLMap()
|
|
436
|
-
for (const [key, val] of Object.entries(value)) {
|
|
437
|
-
map.set(key, createNode(val))
|
|
438
|
-
}
|
|
439
|
-
return map
|
|
440
|
-
} else {
|
|
441
|
-
return new Scalar(value)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Apply multiple path operations to a document
|
|
447
|
-
*
|
|
448
|
-
* This is the main entry point for applying multiple operations to a YAML document.
|
|
449
|
-
* It processes all operations in order and applies them to the document.
|
|
450
|
-
*
|
|
451
|
-
* @param {YAMLMap} doc - The YAML document to modify
|
|
452
|
-
* @param {PathOperationConfig[]} operations - Array of operations to apply
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```typescript
|
|
456
|
-
* const operations: PathOperationConfig[] = [
|
|
457
|
-
* {
|
|
458
|
-
* path: 'on.workflow_call.inputs.version',
|
|
459
|
-
* operation: 'set',
|
|
460
|
-
* value: { description: 'Version to deploy', required: false, type: 'string' }
|
|
461
|
-
* },
|
|
462
|
-
* {
|
|
463
|
-
* path: 'jobs.changes',
|
|
464
|
-
* operation: 'overwrite',
|
|
465
|
-
* value: createValueFromString(`
|
|
466
|
-
* runs-on: ubuntu-latest
|
|
467
|
-
* steps:
|
|
468
|
-
* - uses: ./.github/actions/detect-changes
|
|
469
|
-
* `)
|
|
470
|
-
* }
|
|
471
|
-
* ]
|
|
472
|
-
*
|
|
473
|
-
* applyPathOperations(doc, operations)
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
export function applyPathOperations(
|
|
477
|
-
doc: YAMLMap,
|
|
478
|
-
operations: PathOperationConfig[],
|
|
479
|
-
document?: any
|
|
480
|
-
): void {
|
|
481
|
-
for (const operation of operations) {
|
|
482
|
-
ensurePathAndApply(doc, operation, document)
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Helper functions for creating values
|
|
488
|
-
*
|
|
489
|
-
* These convenience functions make it easier to create YAML nodes from different
|
|
490
|
-
* value types. They handle the parsing and conversion automatically.
|
|
491
|
-
*/
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Create a YAML node from a YAML string
|
|
495
|
-
*
|
|
496
|
-
* Parses a YAML string and returns the root node. This is useful for complex
|
|
497
|
-
* multi-line YAML structures like job definitions.
|
|
498
|
-
*
|
|
499
|
-
* @param {string} yamlString - YAML string to parse
|
|
500
|
-
* @returns {Node} The parsed YAML node
|
|
501
|
-
*
|
|
502
|
-
* @example
|
|
503
|
-
* ```typescript
|
|
504
|
-
* const node = createValueFromString(`
|
|
505
|
-
* runs-on: ubuntu-latest
|
|
506
|
-
* steps:
|
|
507
|
-
* - name: Checkout code
|
|
508
|
-
* uses: actions/checkout@v3
|
|
509
|
-
* - name: Run tests
|
|
510
|
-
* run: npm test
|
|
511
|
-
* `)
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
export function createValueFromString(yamlString: string, context?: any, document?: any): Node {
|
|
515
|
-
// Evaluate JavaScript template literals in the string using the provided context
|
|
516
|
-
// Use a more sophisticated approach that handles nested braces
|
|
517
|
-
let processedString = yamlString
|
|
518
|
-
let match
|
|
519
|
-
const regex = /\$\{([^{}]+)\}/g
|
|
520
|
-
|
|
521
|
-
while ((match = regex.exec(processedString)) !== null) {
|
|
522
|
-
const [fullMatch, expression] = match
|
|
523
|
-
try {
|
|
524
|
-
// Create a function that evaluates the expression with the context
|
|
525
|
-
const func = new Function('ctx', `return ${expression}`)
|
|
526
|
-
const result = func(context || {})
|
|
527
|
-
processedString = processedString.replace(fullMatch, JSON.stringify(result))
|
|
528
|
-
// Reset regex lastIndex to avoid issues with string replacement
|
|
529
|
-
regex.lastIndex = 0
|
|
530
|
-
} catch (error) {
|
|
531
|
-
// If evaluation fails, return the original expression as a string
|
|
532
|
-
processedString = processedString.replace(fullMatch, `"${expression}"`)
|
|
533
|
-
regex.lastIndex = 0
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Parse the YAML string and return the root content as a proper Node
|
|
538
|
-
const doc = parseDocument(processedString)
|
|
539
|
-
return doc.contents as Node
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Create a YAML node from a JavaScript object
|
|
544
|
-
*
|
|
545
|
-
* Converts a plain JavaScript object to a YAML map node.
|
|
546
|
-
*
|
|
547
|
-
* @param {object} obj - JavaScript object to convert
|
|
548
|
-
* @returns {Node} The converted YAML map node
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```typescript
|
|
552
|
-
* const node = createValueFromObject({
|
|
553
|
-
* description: 'The version to deploy',
|
|
554
|
-
* required: false,
|
|
555
|
-
* type: 'string'
|
|
556
|
-
* })
|
|
557
|
-
* ```
|
|
558
|
-
*/
|
|
559
|
-
export function createValueFromObject(obj: object, doc?: any): Node {
|
|
560
|
-
if (doc && doc.createNode) {
|
|
561
|
-
return doc.createNode(obj)
|
|
562
|
-
}
|
|
563
|
-
return createNode(obj)
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Create a YAML node from a JavaScript array
|
|
568
|
-
*
|
|
569
|
-
* Converts a JavaScript array to a YAML sequence node.
|
|
570
|
-
*
|
|
571
|
-
* @param {any[]} arr - JavaScript array to convert
|
|
572
|
-
* @returns {Node} The converted YAML sequence node
|
|
573
|
-
*
|
|
574
|
-
* @example
|
|
575
|
-
* ```typescript
|
|
576
|
-
* const node = createValueFromArray(['develop', 'staging', 'main'])
|
|
577
|
-
* ```
|
|
578
|
-
*/
|
|
579
|
-
export function createValueFromArray(arr: any[]): Node {
|
|
580
|
-
return createNode(arr)
|
|
581
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { cosmiconfigSync } from 'cosmiconfig'
|
|
2
|
-
import { PipecraftConfig, DomainConfig } from '../types/index.js'
|
|
3
|
-
|
|
4
|
-
export const loadConfig = (configPath?: string) => {
|
|
5
|
-
const explorer = cosmiconfigSync('pipecraft')
|
|
6
|
-
const result = configPath ? explorer.load(configPath) : explorer.search()
|
|
7
|
-
|
|
8
|
-
if (!result) {
|
|
9
|
-
throw new Error(`No configuration file found. Expected: ${configPath || '.pipecraftrc.json'}`)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return result.config
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const validateConfig = (config: any) => {
|
|
16
|
-
const requiredFields = ['ciProvider', 'mergeStrategy', 'requireConventionalCommits', 'initialBranch', 'finalBranch', 'branchFlow', 'domains']
|
|
17
|
-
|
|
18
|
-
for (const field of requiredFields) {
|
|
19
|
-
if (!(field in config)) {
|
|
20
|
-
throw new Error(`Missing required field: ${field}`)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!['github', 'gitlab'].includes(config.ciProvider)) {
|
|
25
|
-
throw new Error('ciProvider must be either "github" or "gitlab"')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!['fast-forward', 'merge'].includes(config.mergeStrategy)) {
|
|
29
|
-
throw new Error('mergeStrategy must be either "fast-forward" or "merge"')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!Array.isArray(config.branchFlow) || config.branchFlow.length < 2) {
|
|
33
|
-
throw new Error('branchFlow must be an array with at least 2 branches')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (typeof config.domains !== 'object') {
|
|
37
|
-
throw new Error('domains must be an object')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const [domainName, domainConfig] of Object.entries(config.domains) as [string, DomainConfig][]) {
|
|
41
|
-
if (!domainConfig || typeof domainConfig !== 'object') {
|
|
42
|
-
throw new Error(`Domain "${domainName}" must be an object`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!domainConfig.paths || !Array.isArray(domainConfig.paths)) {
|
|
46
|
-
throw new Error(`Domain "${domainName}" must have a "paths" array`)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (domainConfig.paths.length === 0) {
|
|
50
|
-
throw new Error(`Domain "${domainName}" must have at least one path pattern`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Set defaults for optional properties
|
|
54
|
-
if (domainConfig.testable === undefined) {
|
|
55
|
-
domainConfig.testable = true
|
|
56
|
-
}
|
|
57
|
-
if (domainConfig.deployable === undefined) {
|
|
58
|
-
domainConfig.deployable = true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return true
|
|
64
|
-
}
|