pipecraft 0.29.2 → 0.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -34
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +66 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/init.tpl.d.ts +4 -4
- package/dist/generators/init.tpl.d.ts.map +1 -1
- package/dist/generators/init.tpl.js +369 -79
- package/dist/generators/init.tpl.js.map +1 -1
- package/dist/generators/workflows.tpl.d.ts +9 -9
- package/dist/generators/workflows.tpl.d.ts.map +1 -1
- package/dist/generators/workflows.tpl.js +33 -24
- package/dist/generators/workflows.tpl.js.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +7 -4
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.js +16 -9
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +5 -2
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.js +9 -6
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-release.yml.tpl.d.ts +5 -2
- package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-release.yml.tpl.js +9 -6
- package/dist/templates/actions/create-release.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +5 -2
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-tag.yml.tpl.js +9 -6
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +24 -52
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.js +281 -108
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +5 -2
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.js +9 -6
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +5 -2
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.js +27 -76
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
- package/dist/templates/actions/run-nx-affected.yml.tpl.d.ts +33 -0
- package/dist/templates/actions/run-nx-affected.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/run-nx-affected.yml.tpl.js +346 -0
- package/dist/templates/actions/run-nx-affected.yml.tpl.js.map +1 -0
- package/dist/templates/release-it.cjs.tpl.d.ts +1 -1
- package/dist/templates/release-it.cjs.tpl.d.ts.map +1 -1
- package/dist/templates/release-it.cjs.tpl.js +24 -26
- package/dist/templates/release-it.cjs.tpl.js.map +1 -1
- package/dist/templates/workflows/enforce-pr-target.yml.tpl.d.ts +1 -1
- package/dist/templates/workflows/enforce-pr-target.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/enforce-pr-target.yml.tpl.js +2 -3
- package/dist/templates/workflows/enforce-pr-target.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pipeline-nx.yml.tpl.d.ts +2 -2
- package/dist/templates/workflows/pipeline-nx.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pipeline-nx.yml.tpl.js +300 -93
- package/dist/templates/workflows/pipeline-nx.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pipeline.yml.tpl.d.ts +2 -2
- package/dist/templates/workflows/pipeline.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pipeline.yml.tpl.js +347 -74
- package/dist/templates/workflows/pipeline.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pr-title-check.yml.tpl.d.ts +1 -1
- package/dist/templates/workflows/pr-title-check.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pr-title-check.yml.tpl.js +101 -22
- package/dist/templates/workflows/pr-title-check.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/shared/index.d.ts +3 -2
- package/dist/templates/workflows/shared/index.d.ts.map +1 -1
- package/dist/templates/workflows/shared/index.js +3 -2
- package/dist/templates/workflows/shared/index.js.map +1 -1
- package/dist/templates/workflows/shared/operations-changes.d.ts +13 -2
- package/dist/templates/workflows/shared/operations-changes.d.ts.map +1 -1
- package/dist/templates/workflows/shared/operations-changes.js +47 -16
- package/dist/templates/workflows/shared/operations-changes.js.map +1 -1
- package/dist/templates/workflows/shared/operations-domain-jobs.d.ts +18 -1
- package/dist/templates/workflows/shared/operations-domain-jobs.d.ts.map +1 -1
- package/dist/templates/workflows/shared/operations-domain-jobs.js +143 -19
- package/dist/templates/workflows/shared/operations-domain-jobs.js.map +1 -1
- package/dist/templates/workflows/shared/operations-gate.d.ts +32 -0
- package/dist/templates/workflows/shared/operations-gate.d.ts.map +1 -0
- package/dist/templates/workflows/shared/operations-gate.js +77 -0
- package/dist/templates/workflows/shared/operations-gate.js.map +1 -0
- package/dist/templates/workflows/shared/operations-header.d.ts +1 -1
- package/dist/templates/workflows/shared/operations-header.d.ts.map +1 -1
- package/dist/templates/workflows/shared/operations-header.js +46 -6
- package/dist/templates/workflows/shared/operations-header.js.map +1 -1
- package/dist/templates/workflows/shared/operations-tag-promote.d.ts +5 -1
- package/dist/templates/workflows/shared/operations-tag-promote.d.ts.map +1 -1
- package/dist/templates/workflows/shared/operations-tag-promote.js +138 -31
- package/dist/templates/workflows/shared/operations-tag-promote.js.map +1 -1
- package/dist/templates/workflows/shared/operations-version.d.ts +3 -1
- package/dist/templates/workflows/shared/operations-version.d.ts.map +1 -1
- package/dist/templates/workflows/shared/operations-version.js +18 -10
- package/dist/templates/workflows/shared/operations-version.js.map +1 -1
- package/dist/templates/yaml-format-utils.d.ts.map +1 -1
- package/dist/templates/yaml-format-utils.js.map +1 -1
- package/dist/types/index.d.ts +57 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/action-reference.d.ts +73 -0
- package/dist/utils/action-reference.d.ts.map +1 -0
- package/dist/utils/action-reference.js +120 -0
- package/dist/utils/action-reference.js.map +1 -0
- package/dist/utils/ast-path-operations.d.ts +3 -3
- package/dist/utils/ast-path-operations.d.ts.map +1 -1
- package/dist/utils/ast-path-operations.js +58 -17
- package/dist/utils/ast-path-operations.js.map +1 -1
- package/dist/utils/config.d.ts +11 -5
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +33 -8
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/github-setup.d.ts +4 -4
- package/dist/utils/github-setup.d.ts.map +1 -1
- package/dist/utils/github-setup.js +57 -36
- package/dist/utils/github-setup.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/messaging.d.ts.map +1 -1
- package/dist/utils/messaging.js +7 -3
- package/dist/utils/messaging.js.map +1 -1
- package/dist/utils/preflight.d.ts +2 -2
- package/dist/utils/preflight.d.ts.map +1 -1
- package/dist/utils/preflight.js +43 -41
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/versioning.d.ts +1 -1
- package/dist/utils/versioning.d.ts.map +1 -1
- package/dist/utils/versioning.js +10 -10
- package/dist/utils/versioning.js.map +1 -1
- package/package.json +11 -4
- package/src/generators/init.tpl.ts +434 -96
- package/src/generators/workflows.tpl.ts +54 -45
- package/dist/templates/actions/detect-changes-nx.yml.tpl.d.ts +0 -32
- package/dist/templates/actions/detect-changes-nx.yml.tpl.d.ts.map +0 -1
- package/dist/templates/actions/detect-changes-nx.yml.tpl.js +0 -227
- package/dist/templates/actions/detect-changes-nx.yml.tpl.js.map +0 -1
- package/dist/utils/idempotency.d.ts +0 -187
- package/dist/utils/idempotency.d.ts.map +0 -1
- package/dist/utils/idempotency.js +0 -304
- package/dist/utils/idempotency.js.map +0 -1
|
@@ -1,100 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Detect Changes Action Template
|
|
3
3
|
*
|
|
4
|
-
* Generates a GitHub composite action that detects which domains
|
|
5
|
-
*
|
|
6
|
-
* conditional workflow execution.
|
|
4
|
+
* Generates a REUSABLE GitHub composite action that detects which domains have changed.
|
|
5
|
+
* Supports both Nx dependency graph analysis and path-based change detection.
|
|
7
6
|
*
|
|
8
7
|
* ## Purpose
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* This action is **configuration-driven** and accepts domain definitions as JSON input,
|
|
10
|
+
* making it truly reusable across any project without regeneration:
|
|
11
|
+
* - For Nx monorepos: Uses `nx show projects --affected` to leverage the dependency graph
|
|
12
|
+
* - For path-based projects: Uses path filters to detect which domains changed
|
|
13
|
+
* - Automatically falls back to path-based detection if Nx is unavailable
|
|
13
14
|
*
|
|
14
|
-
* ##
|
|
15
|
+
* ## Key Design Principle
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 3. Outputs a boolean for each domain (true if changed, false if not)
|
|
19
|
-
* 4. Logs results for debugging
|
|
17
|
+
* **Domains are passed as input, not hardcoded in the action.**
|
|
18
|
+
* This allows the same action to work with any domain configuration without regeneration.
|
|
20
19
|
*
|
|
21
20
|
* ## Generated Action Location
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* ## Usage in Workflows
|
|
26
|
-
*
|
|
27
|
-
* ```yaml
|
|
28
|
-
* jobs:
|
|
29
|
-
* changes:
|
|
30
|
-
* runs-on: ubuntu-latest
|
|
31
|
-
* outputs:
|
|
32
|
-
* api: ${{ steps.changes.outputs.api }}
|
|
33
|
-
* web: ${{ steps.changes.outputs.web }}
|
|
34
|
-
* steps:
|
|
35
|
-
* - uses: ./.github/actions/detect-changes
|
|
36
|
-
* id: changes
|
|
37
|
-
* with:
|
|
38
|
-
* baseRef: main
|
|
39
|
-
*
|
|
40
|
-
* test-api:
|
|
41
|
-
* needs: changes
|
|
42
|
-
* if: needs.changes.outputs.api == 'true'
|
|
43
|
-
* # Only runs if API domain changed
|
|
44
|
-
* ```
|
|
22
|
+
* `actions/detect-changes/action.yml`
|
|
45
23
|
*
|
|
46
24
|
* @module templates/actions/detect-changes.yml.tpl
|
|
47
25
|
*/
|
|
48
|
-
import {
|
|
26
|
+
import { renderTemplate, toFile } from '@featherscloud/pinion';
|
|
49
27
|
import fs from 'fs';
|
|
50
28
|
import { logger } from '../../utils/logger.js';
|
|
29
|
+
import { getActionOutputDir } from '../../utils/action-reference.js';
|
|
51
30
|
/**
|
|
52
31
|
* Generates the detect-changes composite action YAML content.
|
|
53
32
|
*
|
|
54
33
|
* Creates a GitHub Actions composite action that:
|
|
55
|
-
* - Accepts
|
|
56
|
-
* -
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
59
|
-
* -
|
|
34
|
+
* - Accepts domain configuration as JSON input (not hardcoded)
|
|
35
|
+
* - Dynamically parses domains using jq
|
|
36
|
+
* - Checks for Nx availability in the repository
|
|
37
|
+
* - Uses `nx show projects --affected` for dependency graph analysis
|
|
38
|
+
* - Maps affected projects to domains dynamically
|
|
39
|
+
* - Falls back to path-based detection if Nx isn't available
|
|
40
|
+
* - Outputs individual domain results AND a JSON summary
|
|
60
41
|
*
|
|
61
|
-
* @param {any} ctx - Context
|
|
62
|
-
* @param {Record<string, DomainConfig>} ctx.domains - Domain configurations with path patterns
|
|
42
|
+
* @param {any} ctx - Context (not used - action is fully dynamic now)
|
|
63
43
|
* @returns {string} YAML content for the composite action
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* const yaml = changesActionTemplate({
|
|
68
|
-
* domains: {
|
|
69
|
-
* api: { paths: ['src/api/**'] },
|
|
70
|
-
* web: { paths: ['src/web/**'] }
|
|
71
|
-
* }
|
|
72
|
-
* })
|
|
73
|
-
* // Generates action with 'api' and 'web' outputs
|
|
74
|
-
* ```
|
|
75
44
|
*/
|
|
76
45
|
const changesActionTemplate = (ctx) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
description: 'Whether ${domainName} domain has changes'
|
|
81
|
-
value: \${{ steps.merge.outputs.${domainName} }}`;
|
|
82
|
-
}).join('\n');
|
|
83
|
-
const domainFilters = Object.entries(ctx.domains).map((entry) => {
|
|
84
|
-
const [domainName, domainConfig] = entry;
|
|
85
|
-
return ` ${domainName}:\n - '${domainConfig.paths.join("'\\n - '")}'`;
|
|
86
|
-
}).join('\n');
|
|
87
|
-
const domainEchoes = Object.entries(ctx.domains).map((entry) => {
|
|
88
|
-
const [domainName, domainConfig] = entry;
|
|
89
|
-
return ` echo "${domainName}=\${{ contains(steps.filter.outputs.changes, '${domainName}') }}" >> $GITHUB_OUTPUT`;
|
|
90
|
-
}).join('\n');
|
|
91
|
-
const domainResults = Object.entries(ctx.domains).map((entry) => {
|
|
92
|
-
const [domainName, domainConfig] = entry;
|
|
93
|
-
return ` echo " ${domainName}: \${{ contains(steps.filter.outputs.changes, '${domainName}') }}"`;
|
|
94
|
-
}).join('\n');
|
|
95
|
-
// Generate the composite action YAML
|
|
96
|
-
return `name: 'Detect Changes'
|
|
97
|
-
description: 'Detect changes in different application domains based on file paths'
|
|
46
|
+
// Generate the composite action YAML - now fully configuration-driven
|
|
47
|
+
return `name: 'Detect Changes (Configuration-Driven)'
|
|
48
|
+
description: 'Enhanced change detection using Nx dependency graph with path-based fallback. Accepts domain configuration as JSON input.'
|
|
98
49
|
author: 'Pipecraft'
|
|
99
50
|
|
|
100
51
|
inputs:
|
|
@@ -102,18 +53,39 @@ inputs:
|
|
|
102
53
|
description: 'Base reference to compare against'
|
|
103
54
|
required: false
|
|
104
55
|
default: 'main'
|
|
56
|
+
domains-config:
|
|
57
|
+
description: 'YAML string of domain configurations (embedded in pipeline at generation time)'
|
|
58
|
+
required: true
|
|
59
|
+
useNx:
|
|
60
|
+
description: 'Whether to use Nx dependency graph for change detection'
|
|
61
|
+
required: false
|
|
62
|
+
default: 'true'
|
|
63
|
+
node-version:
|
|
64
|
+
description: 'Node.js version to use'
|
|
65
|
+
required: false
|
|
66
|
+
default: '20'
|
|
67
|
+
pnpm-version:
|
|
68
|
+
description: 'pnpm version to use (only used if pnpm detected)'
|
|
69
|
+
required: false
|
|
70
|
+
default: '9'
|
|
105
71
|
|
|
106
72
|
outputs:
|
|
107
|
-
|
|
73
|
+
changes:
|
|
74
|
+
description: 'JSON object with domain change results (e.g., {"core": true, "docs": false})'
|
|
75
|
+
value: \${{ steps.output.outputs.changes }}
|
|
76
|
+
affectedDomains:
|
|
77
|
+
description: 'Comma-separated list of domains with changes'
|
|
78
|
+
value: \${{ steps.output.outputs.affectedDomains }}
|
|
79
|
+
nxAvailable:
|
|
80
|
+
description: 'Whether Nx is available in the repository'
|
|
81
|
+
value: \${{ steps.nx-check.outputs.available }}
|
|
82
|
+
affectedProjects:
|
|
83
|
+
description: 'Comma-separated list of affected Nx projects'
|
|
84
|
+
value: \${{ steps.nx-filter.outputs.affectedProjects }}
|
|
108
85
|
|
|
109
86
|
runs:
|
|
110
87
|
using: 'composite'
|
|
111
88
|
steps:
|
|
112
|
-
- name: Checkout Code
|
|
113
|
-
uses: actions/checkout@v4
|
|
114
|
-
with:
|
|
115
|
-
fetch-depth: 0
|
|
116
|
-
|
|
117
89
|
- name: Set Base Branch
|
|
118
90
|
id: set-base
|
|
119
91
|
shell: bash
|
|
@@ -122,54 +94,255 @@ runs:
|
|
|
122
94
|
echo "base_branch=$base_branch" >> $GITHUB_OUTPUT
|
|
123
95
|
echo "base_branch=$base_branch" >> $GITHUB_ENV
|
|
124
96
|
|
|
125
|
-
- name:
|
|
97
|
+
- name: Parse Domain Configuration
|
|
98
|
+
id: parse-domains
|
|
99
|
+
shell: bash
|
|
100
|
+
run: |
|
|
101
|
+
# Parse the domains-config YAML input (embedded in pipeline at generation time)
|
|
102
|
+
echo "\${{ inputs.domains-config }}" > /tmp/domains-config.yml
|
|
103
|
+
|
|
104
|
+
# Extract domain names using yq (or fallback to grep/awk)
|
|
105
|
+
if command -v yq >/dev/null 2>&1; then
|
|
106
|
+
DOMAIN_NAMES=$(yq eval 'keys | join(",")' /tmp/domains-config.yml)
|
|
107
|
+
else
|
|
108
|
+
# Fallback: extract domain names without yq
|
|
109
|
+
DOMAIN_NAMES=$(grep -E '^[[:space:]]*[a-zA-Z0-9_-]+:' /tmp/domains-config.yml | sed 's/[[:space:]]*\\(.*\\):.*/\\1/' | tr '\\n' ',' | sed 's/,$//')
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
echo "domains=$DOMAIN_NAMES" >> $GITHUB_OUTPUT
|
|
113
|
+
echo "📋 Configured domains: $DOMAIN_NAMES"
|
|
114
|
+
|
|
115
|
+
# Create filters file for paths-filter action
|
|
116
|
+
echo "filters:" > /tmp/path-filters.yml
|
|
117
|
+
cat /tmp/domains-config.yml >> /tmp/path-filters.yml
|
|
118
|
+
|
|
119
|
+
cat /tmp/path-filters.yml
|
|
120
|
+
echo ""
|
|
121
|
+
|
|
122
|
+
- name: Check for Nx
|
|
123
|
+
id: nx-check
|
|
124
|
+
shell: bash
|
|
125
|
+
run: |
|
|
126
|
+
if [ -f "nx.json" ] || ([ -f "package.json" ] && grep -q '"nx"' package.json); then
|
|
127
|
+
echo "available=true" >> $GITHUB_OUTPUT
|
|
128
|
+
echo "🔍 Nx detected in repository"
|
|
129
|
+
else
|
|
130
|
+
echo "available=false" >> $GITHUB_OUTPUT
|
|
131
|
+
echo "⚠️ Nx not detected, falling back to path-based detection"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
- name: Setup Node.js
|
|
135
|
+
if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
|
|
136
|
+
uses: actions/setup-node@v4
|
|
137
|
+
with:
|
|
138
|
+
node-version: \${{ inputs.node-version }}
|
|
139
|
+
|
|
140
|
+
- name: Install Dependencies
|
|
141
|
+
if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
|
|
142
|
+
shell: bash
|
|
143
|
+
run: |
|
|
144
|
+
echo "📦 Installing dependencies for Nx..."
|
|
145
|
+
if [ -f "pnpm-lock.yaml" ]; then
|
|
146
|
+
corepack enable
|
|
147
|
+
pnpm install --frozen-lockfile || pnpm install
|
|
148
|
+
elif [ -f "yarn.lock" ]; then
|
|
149
|
+
yarn install --frozen-lockfile || yarn install
|
|
150
|
+
elif [ -f "package-lock.json" ]; then
|
|
151
|
+
npm ci || npm install
|
|
152
|
+
else
|
|
153
|
+
npm install
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
- name: Detect Changes with Nx (if available)
|
|
157
|
+
id: nx-filter
|
|
158
|
+
if: steps.nx-check.outputs.available == 'true' && inputs.useNx == 'true'
|
|
159
|
+
shell: bash
|
|
160
|
+
run: |
|
|
161
|
+
echo "🚀 Using Nx dependency graph for change detection"
|
|
162
|
+
|
|
163
|
+
# Get affected projects using Nx
|
|
164
|
+
if command -v npx >/dev/null 2>&1; then
|
|
165
|
+
# Get list of affected projects (newline-separated)
|
|
166
|
+
AFFECTED_PROJECTS_RAW=$(npx nx show projects --affected --base=\${{ steps.set-base.outputs.base_branch }} 2>/dev/null || echo "")
|
|
167
|
+
|
|
168
|
+
# Convert newlines to commas for storage
|
|
169
|
+
AFFECTED_PROJECTS=$(echo "$AFFECTED_PROJECTS_RAW" | tr '\\n' ',' | sed 's/,$//')
|
|
170
|
+
echo "affectedProjects=$AFFECTED_PROJECTS" >> $GITHUB_OUTPUT
|
|
171
|
+
|
|
172
|
+
if [ -n "$AFFECTED_PROJECTS" ]; then
|
|
173
|
+
echo "📦 Affected Nx projects: $AFFECTED_PROJECTS"
|
|
174
|
+
|
|
175
|
+
# Parse domains dynamically from config
|
|
176
|
+
IFS=',' read -ra DOMAIN_NAMES <<< "\${{ steps.parse-domains.outputs.domains }}"
|
|
177
|
+
|
|
178
|
+
# Initialize results JSON
|
|
179
|
+
echo "{" > /tmp/nx-results.json
|
|
180
|
+
FIRST=true
|
|
181
|
+
|
|
182
|
+
# Check each domain dynamically
|
|
183
|
+
for domain in "\${DOMAIN_NAMES[@]}"; do
|
|
184
|
+
domain=$(echo "$domain" | xargs) # trim whitespace
|
|
185
|
+
DOMAIN_AFFECTED=false
|
|
186
|
+
|
|
187
|
+
# Check if any affected project matches this domain name
|
|
188
|
+
IFS=',' read -ra PROJECTS <<< "$AFFECTED_PROJECTS"
|
|
189
|
+
for project in "\${PROJECTS[@]}"; do
|
|
190
|
+
project=$(echo "$project" | xargs)
|
|
191
|
+
# Match if project name contains domain name (with flexible - vs _ matching)
|
|
192
|
+
domain_pattern=$(echo "$domain" | sed 's/-/[-_]/g')
|
|
193
|
+
if echo "$project" | grep -qiE "$domain_pattern"; then
|
|
194
|
+
DOMAIN_AFFECTED=true
|
|
195
|
+
echo " ✅ $project matches $domain domain"
|
|
196
|
+
break
|
|
197
|
+
fi
|
|
198
|
+
done
|
|
199
|
+
|
|
200
|
+
# Add to JSON
|
|
201
|
+
if [ "$FIRST" = true ]; then
|
|
202
|
+
FIRST=false
|
|
203
|
+
else
|
|
204
|
+
echo "," >> /tmp/nx-results.json
|
|
205
|
+
fi
|
|
206
|
+
echo " \\"$domain\\": $DOMAIN_AFFECTED" >> /tmp/nx-results.json
|
|
207
|
+
done
|
|
208
|
+
|
|
209
|
+
echo "}" >> /tmp/nx-results.json
|
|
210
|
+
|
|
211
|
+
else
|
|
212
|
+
echo "No affected projects detected"
|
|
213
|
+
# Create empty results
|
|
214
|
+
echo "{}" > /tmp/nx-results.json
|
|
215
|
+
IFS=',' read -ra DOMAIN_NAMES <<< "\${{ steps.parse-domains.outputs.domains }}"
|
|
216
|
+
FIRST=true
|
|
217
|
+
for domain in "\${DOMAIN_NAMES[@]}"; do
|
|
218
|
+
if [ "$FIRST" = true ]; then
|
|
219
|
+
echo "{" > /tmp/nx-results.json
|
|
220
|
+
FIRST=false
|
|
221
|
+
else
|
|
222
|
+
echo "," >> /tmp/nx-results.json
|
|
223
|
+
fi
|
|
224
|
+
echo " \\"$domain\\": false" >> /tmp/nx-results.json
|
|
225
|
+
done
|
|
226
|
+
echo "}" >> /tmp/nx-results.json
|
|
227
|
+
fi
|
|
228
|
+
else
|
|
229
|
+
echo "⚠️ npx not available, falling back to path-based detection"
|
|
230
|
+
echo "{}" > /tmp/nx-results.json
|
|
231
|
+
echo "affectedProjects=" >> $GITHUB_OUTPUT
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
cat /tmp/nx-results.json
|
|
235
|
+
|
|
236
|
+
- name: Transform Domain Config for Paths Filter
|
|
237
|
+
id: transform-config
|
|
238
|
+
if: steps.nx-check.outputs.available != 'true' || inputs.useNx != 'true'
|
|
239
|
+
shell: bash
|
|
240
|
+
run: |
|
|
241
|
+
# Transform domains-config from PipeCraft format to paths-filter format
|
|
242
|
+
# PipeCraft format: domain: { paths: [...] }
|
|
243
|
+
# paths-filter format: domain: [...]
|
|
244
|
+
|
|
245
|
+
# Read the domains config
|
|
246
|
+
cat /tmp/domains-config.yml
|
|
247
|
+
|
|
248
|
+
# Transform: remove 'paths:' lines and unindent the glob patterns
|
|
249
|
+
sed '/paths:/d' /tmp/domains-config.yml | sed 's/^ -/-/' > /tmp/paths-filter-config.yml
|
|
250
|
+
|
|
251
|
+
echo ""
|
|
252
|
+
echo "Transformed for paths-filter:"
|
|
253
|
+
cat /tmp/paths-filter-config.yml
|
|
254
|
+
|
|
255
|
+
# Store as output for use in paths-filter
|
|
256
|
+
{
|
|
257
|
+
echo "filters<<EOF"
|
|
258
|
+
cat /tmp/paths-filter-config.yml
|
|
259
|
+
echo "EOF"
|
|
260
|
+
} >> $GITHUB_OUTPUT
|
|
261
|
+
|
|
262
|
+
- name: Detect Changes with Paths Filter (fallback)
|
|
126
263
|
uses: dorny/paths-filter@v3
|
|
127
264
|
id: filter
|
|
265
|
+
if: steps.nx-check.outputs.available != 'true' || inputs.useNx != 'true'
|
|
128
266
|
with:
|
|
129
267
|
base: \${{ steps.set-base.outputs.base_branch }}
|
|
130
|
-
filters:
|
|
131
|
-
${domainFilters}
|
|
268
|
+
filters: \${{ steps.transform-config.outputs.filters }}
|
|
132
269
|
|
|
133
|
-
- name:
|
|
134
|
-
id:
|
|
270
|
+
- name: Generate Outputs
|
|
271
|
+
id: output
|
|
135
272
|
shell: bash
|
|
136
273
|
run: |
|
|
137
|
-
|
|
274
|
+
# Determine which detection method was used and build results
|
|
275
|
+
if [ "\${{ steps.nx-check.outputs.available }}" == "true" ] && [ "\${{ inputs.useNx }}" == "true" ]; then
|
|
276
|
+
# Use Nx results
|
|
277
|
+
CHANGES_JSON=$(cat /tmp/nx-results.json)
|
|
278
|
+
echo "🔍 Using Nx dependency analysis results"
|
|
279
|
+
echo "📦 Affected projects: \${{ steps.nx-filter.outputs.affectedProjects }}"
|
|
280
|
+
else
|
|
281
|
+
# Use path filter results - convert to JSON
|
|
282
|
+
echo "📁 Using path-based change detection"
|
|
283
|
+
echo "{" > /tmp/path-results.json
|
|
284
|
+
|
|
285
|
+
IFS=',' read -ra DOMAIN_NAMES <<< "\${{ steps.parse-domains.outputs.domains }}"
|
|
286
|
+
FIRST=true
|
|
287
|
+
for domain in "\${DOMAIN_NAMES[@]}"; do
|
|
288
|
+
domain=$(echo "$domain" | xargs)
|
|
289
|
+
if [ "$FIRST" = true ]; then
|
|
290
|
+
FIRST=false
|
|
291
|
+
else
|
|
292
|
+
echo "," >> /tmp/path-results.json
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
# Check if domain appears in filter changes
|
|
296
|
+
if echo "\${{ steps.filter.outputs.changes }}" | grep -q "$domain"; then
|
|
297
|
+
echo " \\"$domain\\": true" >> /tmp/path-results.json
|
|
298
|
+
else
|
|
299
|
+
echo " \\"$domain\\": false" >> /tmp/path-results.json
|
|
300
|
+
fi
|
|
301
|
+
done
|
|
302
|
+
echo "}" >> /tmp/path-results.json
|
|
303
|
+
|
|
304
|
+
CHANGES_JSON=$(cat /tmp/path-results.json)
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Output the JSON
|
|
308
|
+
echo "changes<<EOF" >> $GITHUB_OUTPUT
|
|
309
|
+
echo "$CHANGES_JSON" >> $GITHUB_OUTPUT
|
|
310
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
311
|
+
|
|
312
|
+
# Build comma-separated list of affected domains
|
|
313
|
+
AFFECTED_DOMAINS=$(echo "$CHANGES_JSON" | jq -r 'to_entries | map(select(.value == true) | .key) | join(",")')
|
|
314
|
+
echo "affectedDomains=$AFFECTED_DOMAINS" >> $GITHUB_OUTPUT
|
|
138
315
|
|
|
139
316
|
echo "📋 Change Detection Results:"
|
|
140
|
-
$
|
|
317
|
+
echo "$CHANGES_JSON" | jq '.'
|
|
318
|
+
echo "🎯 Affected domains: $AFFECTED_DOMAINS"
|
|
319
|
+
echo " nx-available: \${{ steps.nx-check.outputs.available }}"
|
|
320
|
+
`;
|
|
141
321
|
};
|
|
142
322
|
/**
|
|
143
323
|
* Generator entry point for detect-changes composite action.
|
|
144
324
|
*
|
|
145
|
-
* Generates the
|
|
146
|
-
* change detection logic. This action
|
|
147
|
-
*
|
|
325
|
+
* Generates the `actions/detect-changes/action.yml` file with configuration-driven
|
|
326
|
+
* change detection logic. This action accepts domain configuration as input at runtime,
|
|
327
|
+
* making it truly reusable without regeneration.
|
|
148
328
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
329
|
+
* **Important:** This action no longer embeds domain configurations. Instead, it receives
|
|
330
|
+
* them as JSON input from the workflow, allowing the same action to work with any
|
|
331
|
+
* domain configuration.
|
|
152
332
|
*
|
|
153
|
-
* @
|
|
154
|
-
*
|
|
155
|
-
* await generate({
|
|
156
|
-
* cwd: '/path/to/project',
|
|
157
|
-
* domains: {
|
|
158
|
-
* api: { paths: ['src/api/**'], description: 'API' },
|
|
159
|
-
* web: { paths: ['src/web/**'], description: 'Web' }
|
|
160
|
-
* }
|
|
161
|
-
* })
|
|
162
|
-
* // Creates: .github/actions/detect-changes/action.yml
|
|
163
|
-
* ```
|
|
333
|
+
* @param {PinionContext} ctx - Pinion generator context (domains no longer needed in template)
|
|
334
|
+
* @returns {Promise<PinionContext>} Updated context after file generation
|
|
164
335
|
*/
|
|
165
336
|
export const generate = (ctx) => Promise.resolve(ctx)
|
|
166
|
-
.then(
|
|
167
|
-
//
|
|
168
|
-
const
|
|
337
|
+
.then(ctx => {
|
|
338
|
+
// Determine output directory based on actionSourceMode
|
|
339
|
+
const config = ctx.config || {};
|
|
340
|
+
const outputDir = getActionOutputDir(config);
|
|
341
|
+
const filePath = `${outputDir}/detect-changes/action.yml`;
|
|
169
342
|
const exists = fs.existsSync(filePath);
|
|
170
343
|
const status = exists ? '🔄 Merged with existing' : '📝 Created new';
|
|
171
344
|
logger.verbose(`${status} ${filePath}`);
|
|
172
|
-
return ctx;
|
|
345
|
+
return { ...ctx, actionOutputPath: filePath };
|
|
173
346
|
})
|
|
174
|
-
.then(renderTemplate(changesActionTemplate, toFile('
|
|
347
|
+
.then(ctx => renderTemplate(changesActionTemplate, toFile(ctx.actionOutputPath || 'actions/detect-changes/action.yml'))(ctx));
|
|
175
348
|
//# sourceMappingURL=detect-changes.yml.tpl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-changes.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/actions/detect-changes.yml.tpl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"detect-changes.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/actions/detect-changes.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAsB,cAAc,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAGpE;;;;;;;;;;;;;;GAcG;AACH,MAAM,qBAAqB,GAAG,CAAC,GAAQ,EAAE,EAAE;IACzC,sEAAsE;IACtE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiRR,CAAA;AACD,CAAC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAA0D,EAAE,EAAE,CACrF,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;KACjB,IAAI,CAAC,GAAG,CAAC,EAAE;IACV,uDAAuD;IACvD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAA;IAC/B,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,GAAG,SAAS,4BAA4B,CAAA;IACzD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,gBAAgB,CAAA;IACpE,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAA;IACvC,OAAO,EAAE,GAAG,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAA;AAC/C,CAAC,CAAC;KACD,IAAI,CAAC,GAAG,CAAC,EAAE,CACV,cAAc,CACZ,qBAAqB,EACrB,MAAM,CAAC,GAAG,CAAC,gBAAgB,IAAI,mCAAmC,CAAC,CACpE,CAAC,GAAG,CAAC,CACP,CAAA"}
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module templates/actions/manage-branch.yml.tpl
|
|
8
8
|
*/
|
|
9
|
-
import { PinionContext } from '@featherscloud/pinion';
|
|
9
|
+
import { type PinionContext } from '@featherscloud/pinion';
|
|
10
|
+
import type { PipecraftConfig } from '../../types/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* Generator entry point for manage-branch composite action.
|
|
12
13
|
*
|
|
13
14
|
* @param {PinionContext} ctx - Pinion generator context
|
|
14
15
|
* @returns {Promise<PinionContext>} Updated context after file generation
|
|
15
16
|
*/
|
|
16
|
-
export declare const generate: (ctx: PinionContext
|
|
17
|
+
export declare const generate: (ctx: PinionContext & {
|
|
18
|
+
config?: Partial<PipecraftConfig>;
|
|
19
|
+
}) => Promise<any>;
|
|
17
20
|
//# sourceMappingURL=manage-branch.yml.tpl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manage-branch.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/actions/manage-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"manage-branch.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/actions/manage-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAKlF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AA4H3D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;CAAE,iBAiB9E,CAAA"}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module templates/actions/manage-branch.yml.tpl
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import fs from 'fs';
|
|
9
|
+
import { renderTemplate, toFile } from '@featherscloud/pinion';
|
|
11
10
|
import dedent from 'dedent';
|
|
11
|
+
import fs from 'fs';
|
|
12
12
|
import { logger } from '../../utils/logger.js';
|
|
13
|
+
import { getActionOutputDir } from '../../utils/action-reference.js';
|
|
13
14
|
/**
|
|
14
15
|
* Generates the manage-branch composite action YAML content.
|
|
15
16
|
*
|
|
@@ -138,13 +139,15 @@ const branchActionTemplate = (ctx) => {
|
|
|
138
139
|
* @returns {Promise<PinionContext>} Updated context after file generation
|
|
139
140
|
*/
|
|
140
141
|
export const generate = (ctx) => Promise.resolve(ctx)
|
|
141
|
-
.then(
|
|
142
|
+
.then(ctx => {
|
|
142
143
|
// Check if file exists to determine merge status
|
|
143
|
-
const
|
|
144
|
+
const config = ctx.config || {};
|
|
145
|
+
const outputDir = getActionOutputDir(config);
|
|
146
|
+
const filePath = `${outputDir}/manage-branch/action.yml`;
|
|
144
147
|
const exists = fs.existsSync(filePath);
|
|
145
148
|
const status = exists ? '🔄 Merged with existing' : '📝 Created new';
|
|
146
149
|
logger.verbose(`${status} ${filePath}`);
|
|
147
|
-
return ctx;
|
|
150
|
+
return { ...ctx, actionOutputPath: filePath };
|
|
148
151
|
})
|
|
149
|
-
.then(renderTemplate(branchActionTemplate, toFile('
|
|
152
|
+
.then(ctx => renderTemplate(branchActionTemplate, toFile(ctx.actionOutputPath || 'actions/manage-branch/action.yml'))(ctx));
|
|
150
153
|
//# sourceMappingURL=manage-branch.yml.tpl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manage-branch.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/actions/manage-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"manage-branch.yml.tpl.js","sourceRoot":"","sources":["../../../src/templates/actions/manage-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,cAAc,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAGpE;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,CAAC,GAAQ,EAAE,EAAE;IACxC,OAAO,MAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAgHA,CAAA;AACf,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAA0D,EAAE,EAAE,CACrF,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;KACjB,IAAI,CAAC,GAAG,CAAC,EAAE;IACV,iDAAiD;IACjD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAA;IAC/B,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,GAAG,SAAS,2BAA2B,CAAA;IACxD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,gBAAgB,CAAA;IACpE,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAA;IACvC,OAAO,EAAE,GAAG,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAA;AAC/C,CAAC,CAAC;KACD,IAAI,CAAC,GAAG,CAAC,EAAE,CACV,cAAc,CACZ,oBAAoB,EACpB,MAAM,CAAC,GAAG,CAAC,gBAAgB,IAAI,kCAAkC,CAAC,CACnE,CAAC,GAAG,CAAC,CACP,CAAA"}
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module templates/actions/promote-branch.yml.tpl
|
|
8
8
|
*/
|
|
9
|
-
import { PinionContext } from '@featherscloud/pinion';
|
|
9
|
+
import { type PinionContext } from '@featherscloud/pinion';
|
|
10
|
+
import type { PipecraftConfig } from '../../types/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* Generator entry point for promote-branch composite action.
|
|
12
13
|
*
|
|
13
14
|
* @param {PinionContext} ctx - Pinion generator context
|
|
14
15
|
* @returns {Promise<PinionContext>} Updated context after file generation
|
|
15
16
|
*/
|
|
16
|
-
export declare const generate: (ctx: PinionContext
|
|
17
|
+
export declare const generate: (ctx: PinionContext & {
|
|
18
|
+
config?: Partial<PipecraftConfig>;
|
|
19
|
+
}) => Promise<any>;
|
|
17
20
|
//# sourceMappingURL=promote-branch.yml.tpl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promote-branch.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/actions/promote-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"promote-branch.yml.tpl.d.ts","sourceRoot":"","sources":["../../../src/templates/actions/promote-branch.yml.tpl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA0B,MAAM,uBAAuB,CAAA;AAKlF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAuU3D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;CAAE,iBAiB9E,CAAA"}
|