@invarn/cibuild 1.3.16 → 1.3.17

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 (242) hide show
  1. package/dist/cli.cjs +1 -1
  2. package/dist/src/cli.d.ts +3 -0
  3. package/dist/src/cli.d.ts.map +1 -0
  4. package/dist/src/cli.js +987 -0
  5. package/dist/src/commands/android-scanner.d.ts +32 -0
  6. package/dist/src/commands/android-scanner.d.ts.map +1 -0
  7. package/dist/src/commands/android-scanner.js +667 -0
  8. package/dist/src/commands/build.d.ts +5 -0
  9. package/dist/src/commands/build.d.ts.map +1 -0
  10. package/dist/src/commands/build.js +1096 -0
  11. package/dist/src/commands/edit.d.ts +3 -0
  12. package/dist/src/commands/edit.d.ts.map +1 -0
  13. package/dist/src/commands/edit.js +651 -0
  14. package/dist/src/commands/file-secret-collector.d.ts +37 -0
  15. package/dist/src/commands/file-secret-collector.d.ts.map +1 -0
  16. package/dist/src/commands/file-secret-collector.js +199 -0
  17. package/dist/src/commands/github-workflow.d.ts +5 -0
  18. package/dist/src/commands/github-workflow.d.ts.map +1 -0
  19. package/dist/src/commands/github-workflow.js +45 -0
  20. package/dist/src/commands/ios-scanner.d.ts +27 -0
  21. package/dist/src/commands/ios-scanner.d.ts.map +1 -0
  22. package/dist/src/commands/ios-scanner.js +337 -0
  23. package/dist/src/commands/reset.d.ts +7 -0
  24. package/dist/src/commands/reset.d.ts.map +1 -0
  25. package/dist/src/commands/reset.js +81 -0
  26. package/dist/src/commands/secrets-sync-workflow.d.ts +15 -0
  27. package/dist/src/commands/secrets-sync-workflow.d.ts.map +1 -0
  28. package/dist/src/commands/secrets-sync-workflow.js +255 -0
  29. package/dist/src/commands/secrets-upload.d.ts +21 -0
  30. package/dist/src/commands/secrets-upload.d.ts.map +1 -0
  31. package/dist/src/commands/secrets-upload.js +177 -0
  32. package/dist/src/commands/secrets-upload.test.d.ts +5 -0
  33. package/dist/src/commands/secrets-upload.test.d.ts.map +1 -0
  34. package/dist/src/commands/secrets-upload.test.js +60 -0
  35. package/dist/src/config.d.ts +3 -0
  36. package/dist/src/config.d.ts.map +1 -0
  37. package/dist/src/config.js +46 -0
  38. package/dist/src/envman/cli.d.ts +21 -0
  39. package/dist/src/envman/cli.d.ts.map +1 -0
  40. package/dist/src/envman/cli.js +240 -0
  41. package/dist/src/envman/envman.d.ts +83 -0
  42. package/dist/src/envman/envman.d.ts.map +1 -0
  43. package/dist/src/envman/envman.js +361 -0
  44. package/dist/src/envman/envman.test.d.ts +5 -0
  45. package/dist/src/envman/envman.test.d.ts.map +1 -0
  46. package/dist/src/envman/envman.test.js +236 -0
  47. package/dist/src/envman/index.d.ts +23 -0
  48. package/dist/src/envman/index.d.ts.map +1 -0
  49. package/dist/src/envman/index.js +23 -0
  50. package/dist/src/envman/types.d.ts +55 -0
  51. package/dist/src/envman/types.d.ts.map +1 -0
  52. package/dist/src/envman/types.js +12 -0
  53. package/dist/src/lib.d.ts +27 -0
  54. package/dist/src/lib.d.ts.map +1 -0
  55. package/dist/src/lib.js +32 -0
  56. package/dist/src/pipeline.d.ts +3 -0
  57. package/dist/src/pipeline.d.ts.map +1 -0
  58. package/dist/src/pipeline.js +57 -0
  59. package/dist/src/runner.d.ts +17 -0
  60. package/dist/src/runner.d.ts.map +1 -0
  61. package/dist/src/runner.js +234 -0
  62. package/dist/src/types.d.ts +57 -0
  63. package/dist/src/types.d.ts.map +1 -0
  64. package/dist/src/types.js +2 -0
  65. package/dist/src/yaml/bitrise-compat.d.ts +65 -0
  66. package/dist/src/yaml/bitrise-compat.d.ts.map +1 -0
  67. package/dist/src/yaml/bitrise-compat.js +206 -0
  68. package/dist/src/yaml/bitrise-compat.test.d.ts +5 -0
  69. package/dist/src/yaml/bitrise-compat.test.d.ts.map +1 -0
  70. package/dist/src/yaml/bitrise-compat.test.js +347 -0
  71. package/dist/src/yaml/converter.d.ts +33 -0
  72. package/dist/src/yaml/converter.d.ts.map +1 -0
  73. package/dist/src/yaml/converter.js +222 -0
  74. package/dist/src/yaml/converter.test.d.ts +5 -0
  75. package/dist/src/yaml/converter.test.d.ts.map +1 -0
  76. package/dist/src/yaml/converter.test.js +348 -0
  77. package/dist/src/yaml/e2e.test.d.ts +6 -0
  78. package/dist/src/yaml/e2e.test.d.ts.map +1 -0
  79. package/dist/src/yaml/e2e.test.js +446 -0
  80. package/dist/src/yaml/env-resolver.d.ts +120 -0
  81. package/dist/src/yaml/env-resolver.d.ts.map +1 -0
  82. package/dist/src/yaml/env-resolver.js +405 -0
  83. package/dist/src/yaml/env-resolver.test.d.ts +5 -0
  84. package/dist/src/yaml/env-resolver.test.d.ts.map +1 -0
  85. package/dist/src/yaml/env-resolver.test.js +502 -0
  86. package/dist/src/yaml/interactive-prompts.d.ts +71 -0
  87. package/dist/src/yaml/interactive-prompts.d.ts.map +1 -0
  88. package/dist/src/yaml/interactive-prompts.js +258 -0
  89. package/dist/src/yaml/missing-env-handler.d.ts +45 -0
  90. package/dist/src/yaml/missing-env-handler.d.ts.map +1 -0
  91. package/dist/src/yaml/missing-env-handler.js +64 -0
  92. package/dist/src/yaml/parser.d.ts +33 -0
  93. package/dist/src/yaml/parser.d.ts.map +1 -0
  94. package/dist/src/yaml/parser.js +145 -0
  95. package/dist/src/yaml/pipeline-with-secrets.d.ts +25 -0
  96. package/dist/src/yaml/pipeline-with-secrets.d.ts.map +1 -0
  97. package/dist/src/yaml/pipeline-with-secrets.js +76 -0
  98. package/dist/src/yaml/platform-detector.d.ts +83 -0
  99. package/dist/src/yaml/platform-detector.d.ts.map +1 -0
  100. package/dist/src/yaml/platform-detector.js +188 -0
  101. package/dist/src/yaml/platform-detector.test.d.ts +5 -0
  102. package/dist/src/yaml/platform-detector.test.d.ts.map +1 -0
  103. package/dist/src/yaml/platform-detector.test.js +414 -0
  104. package/dist/src/yaml/preflight-validation.d.ts +40 -0
  105. package/dist/src/yaml/preflight-validation.d.ts.map +1 -0
  106. package/dist/src/yaml/preflight-validation.js +152 -0
  107. package/dist/src/yaml/secrets-manager.d.ts +77 -0
  108. package/dist/src/yaml/secrets-manager.d.ts.map +1 -0
  109. package/dist/src/yaml/secrets-manager.js +219 -0
  110. package/dist/src/yaml/step-validator.d.ts +54 -0
  111. package/dist/src/yaml/step-validator.d.ts.map +1 -0
  112. package/dist/src/yaml/step-validator.js +403 -0
  113. package/dist/src/yaml/steps/android-sign.d.ts +35 -0
  114. package/dist/src/yaml/steps/android-sign.d.ts.map +1 -0
  115. package/dist/src/yaml/steps/android-sign.js +147 -0
  116. package/dist/src/yaml/steps/android-version.d.ts +26 -0
  117. package/dist/src/yaml/steps/android-version.d.ts.map +1 -0
  118. package/dist/src/yaml/steps/android-version.js +128 -0
  119. package/dist/src/yaml/steps/android-version.test.d.ts +5 -0
  120. package/dist/src/yaml/steps/android-version.test.d.ts.map +1 -0
  121. package/dist/src/yaml/steps/android-version.test.js +196 -0
  122. package/dist/src/yaml/steps/android.d.ts +95 -0
  123. package/dist/src/yaml/steps/android.d.ts.map +1 -0
  124. package/dist/src/yaml/steps/android.js +916 -0
  125. package/dist/src/yaml/steps/app-store-deploy.d.ts +48 -0
  126. package/dist/src/yaml/steps/app-store-deploy.d.ts.map +1 -0
  127. package/dist/src/yaml/steps/app-store-deploy.js +162 -0
  128. package/dist/src/yaml/steps/base.d.ts +238 -0
  129. package/dist/src/yaml/steps/base.d.ts.map +1 -0
  130. package/dist/src/yaml/steps/base.js +345 -0
  131. package/dist/src/yaml/steps/bitrise-android-tools.d.ts +26 -0
  132. package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +1 -0
  133. package/dist/src/yaml/steps/bitrise-android-tools.js +198 -0
  134. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +5 -0
  135. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +1 -0
  136. package/dist/src/yaml/steps/bitrise-android-tools.test.js +280 -0
  137. package/dist/src/yaml/steps/bitrise-apk-info.d.ts +22 -0
  138. package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +1 -0
  139. package/dist/src/yaml/steps/bitrise-apk-info.js +144 -0
  140. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +5 -0
  141. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +1 -0
  142. package/dist/src/yaml/steps/bitrise-apk-info.test.js +331 -0
  143. package/dist/src/yaml/steps/bitrise-slack.d.ts +49 -0
  144. package/dist/src/yaml/steps/bitrise-slack.d.ts.map +1 -0
  145. package/dist/src/yaml/steps/bitrise-slack.js +280 -0
  146. package/dist/src/yaml/steps/bitrise-slack.test.d.ts +5 -0
  147. package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +1 -0
  148. package/dist/src/yaml/steps/bitrise-slack.test.js +484 -0
  149. package/dist/src/yaml/steps/bitrise-ssh.d.ts +27 -0
  150. package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +1 -0
  151. package/dist/src/yaml/steps/bitrise-ssh.js +134 -0
  152. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +5 -0
  153. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +1 -0
  154. package/dist/src/yaml/steps/bitrise-ssh.test.js +205 -0
  155. package/dist/src/yaml/steps/cache.d.ts +52 -0
  156. package/dist/src/yaml/steps/cache.d.ts.map +1 -0
  157. package/dist/src/yaml/steps/cache.js +351 -0
  158. package/dist/src/yaml/steps/fastlane.d.ts +27 -0
  159. package/dist/src/yaml/steps/fastlane.d.ts.map +1 -0
  160. package/dist/src/yaml/steps/fastlane.js +79 -0
  161. package/dist/src/yaml/steps/file.d.ts +27 -0
  162. package/dist/src/yaml/steps/file.d.ts.map +1 -0
  163. package/dist/src/yaml/steps/file.js +35 -0
  164. package/dist/src/yaml/steps/flutter.d.ts +63 -0
  165. package/dist/src/yaml/steps/flutter.d.ts.map +1 -0
  166. package/dist/src/yaml/steps/flutter.js +215 -0
  167. package/dist/src/yaml/steps/git-clone.d.ts +26 -0
  168. package/dist/src/yaml/steps/git-clone.d.ts.map +1 -0
  169. package/dist/src/yaml/steps/git-clone.js +111 -0
  170. package/dist/src/yaml/steps/google-play-deploy.d.ts +37 -0
  171. package/dist/src/yaml/steps/google-play-deploy.d.ts.map +1 -0
  172. package/dist/src/yaml/steps/google-play-deploy.js +193 -0
  173. package/dist/src/yaml/steps/google-play-deploy.test.d.ts +5 -0
  174. package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +1 -0
  175. package/dist/src/yaml/steps/google-play-deploy.test.js +310 -0
  176. package/dist/src/yaml/steps/index.d.ts +10 -0
  177. package/dist/src/yaml/steps/index.d.ts.map +1 -0
  178. package/dist/src/yaml/steps/index.js +1361 -0
  179. package/dist/src/yaml/steps/ios-deps.d.ts +43 -0
  180. package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -0
  181. package/dist/src/yaml/steps/ios-deps.js +141 -0
  182. package/dist/src/yaml/steps/ios-deps.test.d.ts +5 -0
  183. package/dist/src/yaml/steps/ios-deps.test.d.ts.map +1 -0
  184. package/dist/src/yaml/steps/ios-deps.test.js +90 -0
  185. package/dist/src/yaml/steps/ios-signing.d.ts +31 -0
  186. package/dist/src/yaml/steps/ios-signing.d.ts.map +1 -0
  187. package/dist/src/yaml/steps/ios-signing.js +144 -0
  188. package/dist/src/yaml/steps/ios-version.d.ts +47 -0
  189. package/dist/src/yaml/steps/ios-version.d.ts.map +1 -0
  190. package/dist/src/yaml/steps/ios-version.js +151 -0
  191. package/dist/src/yaml/steps/linting.d.ts +47 -0
  192. package/dist/src/yaml/steps/linting.d.ts.map +1 -0
  193. package/dist/src/yaml/steps/linting.js +148 -0
  194. package/dist/src/yaml/steps/phase2.test.d.ts +6 -0
  195. package/dist/src/yaml/steps/phase2.test.d.ts.map +1 -0
  196. package/dist/src/yaml/steps/phase2.test.js +197 -0
  197. package/dist/src/yaml/steps/phase3.test.d.ts +5 -0
  198. package/dist/src/yaml/steps/phase3.test.d.ts.map +1 -0
  199. package/dist/src/yaml/steps/phase3.test.js +144 -0
  200. package/dist/src/yaml/steps/phase4.test.d.ts +5 -0
  201. package/dist/src/yaml/steps/phase4.test.d.ts.map +1 -0
  202. package/dist/src/yaml/steps/phase4.test.js +166 -0
  203. package/dist/src/yaml/steps/phase5.test.d.ts +6 -0
  204. package/dist/src/yaml/steps/phase5.test.d.ts.map +1 -0
  205. package/dist/src/yaml/steps/phase5.test.js +263 -0
  206. package/dist/src/yaml/steps/registry.d.ts +88 -0
  207. package/dist/src/yaml/steps/registry.d.ts.map +1 -0
  208. package/dist/src/yaml/steps/registry.js +125 -0
  209. package/dist/src/yaml/steps/registry.test.d.ts +5 -0
  210. package/dist/src/yaml/steps/registry.test.d.ts.map +1 -0
  211. package/dist/src/yaml/steps/registry.test.js +235 -0
  212. package/dist/src/yaml/steps/release.d.ts +50 -0
  213. package/dist/src/yaml/steps/release.d.ts.map +1 -0
  214. package/dist/src/yaml/steps/release.js +154 -0
  215. package/dist/src/yaml/steps/script.d.ts +23 -0
  216. package/dist/src/yaml/steps/script.d.ts.map +1 -0
  217. package/dist/src/yaml/steps/script.js +63 -0
  218. package/dist/src/yaml/steps/spec-validation.test.d.ts +6 -0
  219. package/dist/src/yaml/steps/spec-validation.test.d.ts.map +1 -0
  220. package/dist/src/yaml/steps/spec-validation.test.js +130 -0
  221. package/dist/src/yaml/steps/steps.test.d.ts +6 -0
  222. package/dist/src/yaml/steps/steps.test.d.ts.map +1 -0
  223. package/dist/src/yaml/steps/steps.test.js +474 -0
  224. package/dist/src/yaml/steps/test-config.d.ts +3 -0
  225. package/dist/src/yaml/steps/test-config.d.ts.map +1 -0
  226. package/dist/src/yaml/steps/test-config.js +16 -0
  227. package/dist/src/yaml/steps/xcode-new.test.d.ts +5 -0
  228. package/dist/src/yaml/steps/xcode-new.test.d.ts.map +1 -0
  229. package/dist/src/yaml/steps/xcode-new.test.js +211 -0
  230. package/dist/src/yaml/steps/xcode.d.ts +222 -0
  231. package/dist/src/yaml/steps/xcode.d.ts.map +1 -0
  232. package/dist/src/yaml/steps/xcode.js +999 -0
  233. package/dist/src/yaml/types.d.ts +68 -0
  234. package/dist/src/yaml/types.d.ts.map +1 -0
  235. package/dist/src/yaml/types.js +5 -0
  236. package/dist/src/yaml/validation-types.d.ts +96 -0
  237. package/dist/src/yaml/validation-types.d.ts.map +1 -0
  238. package/dist/src/yaml/validation-types.js +8 -0
  239. package/dist/src/yaml/yaml-updater.d.ts +24 -0
  240. package/dist/src/yaml/yaml-updater.d.ts.map +1 -0
  241. package/dist/src/yaml/yaml-updater.js +128 -0
  242. package/package.json +16 -4
@@ -0,0 +1,446 @@
1
+ /**
2
+ * End-to-end integration tests for YAML pipelines
3
+ * Tests complete YAML pipeline loading and execution through CLI components
4
+ */
5
+ import { describe, test, expect } from '@jest/globals';
6
+ import { loadYAMLPipeline } from './parser.js';
7
+ import { convertYAMLToPipelineDef } from './converter.js';
8
+ import { detectPlatformInfo } from './platform-detector.js';
9
+ import { resolve } from 'node:path';
10
+ import { existsSync } from 'node:fs';
11
+ import * as jsYaml from 'js-yaml';
12
+ import './steps/index.js'; // Initialize step registry
13
+ // Test helper for default config
14
+ const testConfig = {
15
+ artifactsDir: './artifacts',
16
+ interpreters: {
17
+ python: 'python3',
18
+ ruby: 'ruby',
19
+ node: 'node',
20
+ bash: '/bin/bash',
21
+ },
22
+ maxConcurrentJobs: 1,
23
+ paths: {
24
+ buildsDir: '.ci-builds',
25
+ cacheDir: '.ci-cache',
26
+ derivedDataDir: '~/Library/Developer/Xcode/DerivedData',
27
+ },
28
+ };
29
+ describe('YAML Pipeline End-to-End Tests', () => {
30
+ const examplesDir = resolve(process.cwd(), 'examples');
31
+ describe('iOS Build Pipeline', () => {
32
+ const pipelinePath = resolve(examplesDir, 'ios-build.yml');
33
+ test('should load and parse iOS pipeline file', () => {
34
+ expect(existsSync(pipelinePath)).toBe(true);
35
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
36
+ expect(yamlPipeline.format_version).toBe('4');
37
+ expect(yamlPipeline.workflows).toBeDefined();
38
+ expect(yamlPipeline.workflows.primary).toBeDefined();
39
+ expect(yamlPipeline.workflows.debug).toBeDefined();
40
+ });
41
+ test('should convert primary workflow to PipelineDef', async () => {
42
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
43
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
44
+ expect(result.pipeline.name).toBe('primary (YAML)');
45
+ expect(result.pipeline.steps).toBeDefined();
46
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
47
+ // Verify step IDs are generated
48
+ const stepIds = result.pipeline.steps.map(s => s.id);
49
+ expect(stepIds).toContain('step_1');
50
+ expect(stepIds).toContain('step_2');
51
+ });
52
+ test('should detect macOS platform for iOS workflow', () => {
53
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
54
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'primary');
55
+ expect(platformInfo.platform).toBe('macos');
56
+ expect(platformInfo.stack).toBe('macos-ventura-xcode-15.1');
57
+ expect(platformInfo.machineType).toBe('performance');
58
+ });
59
+ test('should convert debug workflow to PipelineDef', async () => {
60
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
61
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'debug');
62
+ expect(result.pipeline.name).toBe('debug (YAML)');
63
+ expect(result.pipeline.steps).toBeDefined();
64
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
65
+ });
66
+ test('should include all expected iOS steps', async () => {
67
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
68
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
69
+ const stepNames = result.pipeline.steps.map(s => s.name);
70
+ expect(stepNames).toContain('git-clone');
71
+ expect(stepNames).toContain('cache-pull');
72
+ expect(stepNames).toContain('cache-push');
73
+ // xcodebuild and xcode-test steps should exist (titles may vary)
74
+ expect(result.pipeline.steps.length).toBeGreaterThanOrEqual(5);
75
+ });
76
+ test('should include environment-dependent step scripts', async () => {
77
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
78
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
79
+ // Check that steps have generated scripts (environment variables resolved during conversion)
80
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
81
+ // Check that all steps have valid scripts
82
+ for (const step of result.pipeline.steps) {
83
+ expect(step.script).toBeDefined();
84
+ expect(typeof step.script).toBe('string');
85
+ expect(step.script.length).toBeGreaterThan(0);
86
+ }
87
+ });
88
+ });
89
+ describe('Android Build Pipeline', () => {
90
+ const pipelinePath = resolve(examplesDir, 'android-build.yml');
91
+ test('should load and parse Android pipeline file', () => {
92
+ expect(existsSync(pipelinePath)).toBe(true);
93
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
94
+ expect(yamlPipeline.format_version).toBe('4');
95
+ expect(yamlPipeline.workflows).toBeDefined();
96
+ expect(yamlPipeline.workflows.primary).toBeDefined();
97
+ expect(yamlPipeline.workflows.debug).toBeDefined();
98
+ expect(yamlPipeline.workflows.qa).toBeDefined();
99
+ });
100
+ test('should convert primary workflow to PipelineDef', async () => {
101
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
102
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
103
+ expect(result.pipeline.name).toBe('primary (YAML)');
104
+ expect(result.pipeline.steps).toBeDefined();
105
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
106
+ });
107
+ test('should detect Linux platform for Android workflow', () => {
108
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
109
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'primary');
110
+ expect(platformInfo.platform).toBe('linux');
111
+ expect(platformInfo.stack).toBe('linux-docker-android-22.04');
112
+ expect(platformInfo.machineType).toBe('standard');
113
+ });
114
+ test('should include all expected Android steps', async () => {
115
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
116
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
117
+ const stepNames = result.pipeline.steps.map(s => s.name);
118
+ expect(stepNames).toContain('git-clone');
119
+ expect(stepNames).toContain('cache-pull');
120
+ expect(stepNames).toContain('cache-push');
121
+ // Android build steps should exist (8 total in primary workflow)
122
+ expect(result.pipeline.steps.length).toBe(8);
123
+ });
124
+ test('should convert qa workflow with test filters', async () => {
125
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
126
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'qa');
127
+ expect(result.pipeline.name).toBe('qa (YAML)');
128
+ expect(result.pipeline.steps).toBeDefined();
129
+ // QA workflow should have test steps with generated scripts
130
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
131
+ for (const step of result.pipeline.steps) {
132
+ expect(step.kind).toBe('script');
133
+ expect(step.script).toBeDefined();
134
+ }
135
+ });
136
+ test('should generate scripts with environment variables', async () => {
137
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
138
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
139
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
140
+ // Check that all steps have generated scripts
141
+ for (const step of result.pipeline.steps) {
142
+ expect(step.script).toBeDefined();
143
+ expect(step.script.length).toBeGreaterThan(0);
144
+ }
145
+ });
146
+ });
147
+ describe('Multi-Platform Pipeline', () => {
148
+ const pipelinePath = resolve(examplesDir, 'multi-platform.yml');
149
+ test('should load and parse multi-platform pipeline file', () => {
150
+ expect(existsSync(pipelinePath)).toBe(true);
151
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
152
+ expect(yamlPipeline.format_version).toBe('4');
153
+ expect(yamlPipeline.workflows).toBeDefined();
154
+ expect(Object.keys(yamlPipeline.workflows)).toHaveLength(4);
155
+ });
156
+ test('should have iOS and Android workflows', () => {
157
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
158
+ expect(yamlPipeline.workflows['ios-build']).toBeDefined();
159
+ expect(yamlPipeline.workflows['android-build']).toBeDefined();
160
+ expect(yamlPipeline.workflows['ios-debug']).toBeDefined();
161
+ expect(yamlPipeline.workflows['android-debug']).toBeDefined();
162
+ });
163
+ test('should detect macOS platform for ios-build workflow', () => {
164
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
165
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'ios-build');
166
+ expect(platformInfo.platform).toBe('macos');
167
+ expect(platformInfo.machineType).toBe('standard');
168
+ });
169
+ test('should detect Linux platform for android-build workflow', () => {
170
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
171
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'android-build');
172
+ expect(platformInfo.platform).toBe('linux');
173
+ expect(platformInfo.machineType).toBe('standard');
174
+ });
175
+ test('should convert ios-build workflow to PipelineDef', async () => {
176
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
177
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'ios-build');
178
+ expect(result.pipeline.name).toBe('ios-build (YAML)');
179
+ expect(result.pipeline.steps).toBeDefined();
180
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
181
+ // Verify all steps have scripts
182
+ for (const step of result.pipeline.steps) {
183
+ expect(step.script).toBeDefined();
184
+ }
185
+ });
186
+ test('should convert android-build workflow to PipelineDef', async () => {
187
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
188
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'android-build');
189
+ expect(result.pipeline.name).toBe('android-build (YAML)');
190
+ expect(result.pipeline.steps).toBeDefined();
191
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
192
+ // Verify all steps have scripts
193
+ for (const step of result.pipeline.steps) {
194
+ expect(step.script).toBeDefined();
195
+ }
196
+ });
197
+ test('should process global and workflow-specific environment variables into scripts', async () => {
198
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
199
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'ios-build');
200
+ // Verify steps were generated
201
+ expect(result.pipeline.steps).toBeDefined();
202
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
203
+ // All steps should have generated scripts
204
+ for (const step of result.pipeline.steps) {
205
+ expect(step.script).toBeDefined();
206
+ expect(step.script.length).toBeGreaterThan(0);
207
+ }
208
+ });
209
+ test('should use first workflow when no workflow specified', async () => {
210
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
211
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig);
212
+ // Should default to first workflow in the object
213
+ expect(result.pipeline.name).toContain('(YAML)');
214
+ expect(result.pipeline.steps).toBeDefined();
215
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
216
+ });
217
+ });
218
+ describe('Workflow Selection', () => {
219
+ const pipelinePath = resolve(examplesDir, 'multi-platform.yml');
220
+ test('should throw error for non-existent workflow', async () => {
221
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
222
+ await expect(async () => {
223
+ await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'non-existent-workflow');
224
+ }).rejects.toThrow();
225
+ });
226
+ test('should successfully select each workflow by name', async () => {
227
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
228
+ const workflowNames = ['ios-build', 'android-build', 'ios-debug', 'android-debug'];
229
+ for (const workflowName of workflowNames) {
230
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, workflowName);
231
+ expect(result.pipeline.name).toBe(`${workflowName} (YAML)`);
232
+ }
233
+ });
234
+ });
235
+ describe('Step Script Generation', () => {
236
+ test('should generate valid bash scripts for all steps', async () => {
237
+ const pipelinePath = resolve(examplesDir, 'ios-build.yml');
238
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
239
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
240
+ for (const step of result.pipeline.steps) {
241
+ expect(step.kind).toBe('script');
242
+ expect(step.script).toBeDefined();
243
+ expect(typeof step.script).toBe('string');
244
+ expect(step.script.length).toBeGreaterThan(0);
245
+ // Scripts should be valid bash (start with shebang)
246
+ expect(step.script).toMatch(/^#!/);
247
+ }
248
+ });
249
+ test('should generate unique step IDs', async () => {
250
+ const pipelinePath = resolve(examplesDir, 'ios-build.yml');
251
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
252
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
253
+ const stepIds = result.pipeline.steps.map(s => s.id);
254
+ const uniqueIds = new Set(stepIds);
255
+ // All step IDs should be unique
256
+ expect(stepIds.length).toBe(uniqueIds.size);
257
+ });
258
+ });
259
+ describe('Environment Variable Resolution', () => {
260
+ test('should generate scripts with resolved variables', async () => {
261
+ const pipelinePath = resolve(examplesDir, 'android-build.yml');
262
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
263
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'primary');
264
+ // Scripts should be generated with resolved variable values
265
+ expect(result.pipeline.steps.length).toBe(8);
266
+ for (const step of result.pipeline.steps) {
267
+ expect(step.kind).toBe('script');
268
+ expect(step.script).toBeDefined();
269
+ expect(step.script.length).toBeGreaterThan(0);
270
+ }
271
+ });
272
+ test('should process app-level and workflow-level environment variables', async () => {
273
+ const pipelinePath = resolve(examplesDir, 'multi-platform.yml');
274
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
275
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'android-build');
276
+ // Verify all steps have valid scripts
277
+ expect(result.pipeline.steps.length).toBeGreaterThan(0);
278
+ for (const step of result.pipeline.steps) {
279
+ expect(step.script).toBeDefined();
280
+ }
281
+ });
282
+ });
283
+ describe('Platform Detection', () => {
284
+ test('should auto-detect platform from xcodebuild step', () => {
285
+ const pipelinePath = resolve(examplesDir, 'multi-platform.yml');
286
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
287
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'ios-build');
288
+ expect(platformInfo.platform).toBe('macos');
289
+ });
290
+ test('should auto-detect platform from gradle-build step', () => {
291
+ const pipelinePath = resolve(examplesDir, 'multi-platform.yml');
292
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
293
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'android-build');
294
+ expect(platformInfo.platform).toBe('linux');
295
+ });
296
+ test('should use stack metadata when available', () => {
297
+ const pipelinePath = resolve(examplesDir, 'ios-build.yml');
298
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
299
+ const platformInfo = detectPlatformInfo(yamlPipeline, 'primary');
300
+ expect(platformInfo.stack).toBe('macos-ventura-xcode-15.1');
301
+ expect(platformInfo.platform).toBe('macos');
302
+ });
303
+ });
304
+ describe('Unsupported Steps Integration', () => {
305
+ test('should handle YAML with mixed supported and unsupported steps', async () => {
306
+ // Create a temporary YAML in memory with mixed steps
307
+ const yamlContent = `format_version: '4'
308
+
309
+ workflows:
310
+ test-warnings:
311
+ steps:
312
+ - script@1.0.0:
313
+ title: Valid script step
314
+ inputs:
315
+ content: |
316
+ echo "This step is supported"
317
+ - unsupported-bitrise-step@1.0.0:
318
+ title: This step does not exist
319
+ inputs:
320
+ some_input: value
321
+ - npm@1.0.0:
322
+ title: NPM step (should suggest script alternative)
323
+ inputs:
324
+ command: install
325
+ - script@1.0.0:
326
+ title: Another valid step
327
+ inputs:
328
+ content: |
329
+ echo "This step is also supported"
330
+ - cocoapods-install@2.0.0:
331
+ title: CocoaPods (should suggest script alternative)
332
+ - completely-unknown-step@3.0.0:
333
+ title: Unknown step (no suggestion)
334
+ `;
335
+ // Parse YAML manually
336
+ // Use js-yaml for parsing
337
+ const yamlPipeline = jsYaml.load(yamlContent);
338
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'test-warnings');
339
+ // Verify warnings were collected
340
+ expect(result.warnings).toBeDefined();
341
+ expect(result.warnings.length).toBe(3); // 3 unsupported steps (cocoapods-install is now supported)
342
+ // Verify skipped steps count
343
+ expect(result.skippedSteps).toBe(3);
344
+ // Verify supported steps were processed (script x2 + cocoapods-install)
345
+ expect(result.pipeline.steps.length).toBe(3);
346
+ expect(result.pipeline.steps[0].name).toBe('Valid script step');
347
+ expect(result.pipeline.steps[1].name).toBe('Another valid step');
348
+ expect(result.pipeline.steps[2].name).toBe('CocoaPods (should suggest script alternative)');
349
+ // Verify warning messages contain step names
350
+ expect(result.warnings[0]).toContain('unsupported-bitrise-step@1.0.0');
351
+ expect(result.warnings[1]).toContain('npm@1.0.0');
352
+ expect(result.warnings[2]).toContain('completely-unknown-step@3.0.0');
353
+ // Verify suggestions are included for known steps
354
+ expect(result.warnings[1]).toContain('💡 Suggestion');
355
+ expect(result.warnings[1]).toContain('script step with npm commands');
356
+ // Verify no suggestion for unknown step
357
+ expect(result.warnings[2]).not.toContain('💡 Suggestion');
358
+ });
359
+ test('should handle YAML with all unsupported steps', async () => {
360
+ const yamlContent = `format_version: '4'
361
+
362
+ workflows:
363
+ all-unsupported:
364
+ steps:
365
+ - unsupported-1@1.0.0:
366
+ inputs: {}
367
+ - unsupported-2@2.0.0:
368
+ inputs: {}
369
+ - unsupported-3@3.0.0:
370
+ inputs: {}
371
+ `;
372
+ // Use js-yaml for parsing
373
+ const yamlPipeline = jsYaml.load(yamlContent);
374
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'all-unsupported');
375
+ // All steps should be skipped
376
+ expect(result.warnings.length).toBe(3);
377
+ expect(result.skippedSteps).toBe(3);
378
+ expect(result.pipeline.steps.length).toBe(0);
379
+ // Pipeline should still be valid
380
+ expect(result.pipeline.name).toBe('all-unsupported (YAML)');
381
+ expect(result.pipeline.stepsOrder.length).toBe(0);
382
+ });
383
+ test('should preserve step order after filtering unsupported steps', async () => {
384
+ const yamlContent = `format_version: '4'
385
+
386
+ workflows:
387
+ order-test:
388
+ steps:
389
+ - script@1.0.0:
390
+ title: Step 1
391
+ inputs:
392
+ content: echo "1"
393
+ - unsupported-step@1.0.0:
394
+ inputs: {}
395
+ - script@1.0.0:
396
+ title: Step 2
397
+ inputs:
398
+ content: echo "2"
399
+ - another-unsupported@1.0.0:
400
+ inputs: {}
401
+ - script@1.0.0:
402
+ title: Step 3
403
+ inputs:
404
+ content: echo "3"
405
+ `;
406
+ // Use js-yaml for parsing
407
+ const yamlPipeline = jsYaml.load(yamlContent);
408
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'order-test');
409
+ // Verify order is preserved
410
+ expect(result.pipeline.steps.length).toBe(3);
411
+ expect(result.pipeline.steps[0].name).toBe('Step 1');
412
+ expect(result.pipeline.steps[1].name).toBe('Step 2');
413
+ expect(result.pipeline.steps[2].name).toBe('Step 3');
414
+ // Verify stepsOrder matches
415
+ expect(result.pipeline.stepsOrder.length).toBe(3);
416
+ });
417
+ test('should handle multiple versions of same unsupported step', async () => {
418
+ const yamlContent = `format_version: '4'
419
+
420
+ workflows:
421
+ version-test:
422
+ steps:
423
+ - npm@1.0.0:
424
+ inputs: {}
425
+ - npm@2.0.0:
426
+ inputs: {}
427
+ - npm@3.5.0:
428
+ inputs: {}
429
+ `;
430
+ // Use js-yaml for parsing
431
+ const yamlPipeline = jsYaml.load(yamlContent);
432
+ const result = await convertYAMLToPipelineDef(yamlPipeline, testConfig, 'version-test');
433
+ // Should warn about each version separately
434
+ expect(result.warnings.length).toBe(3);
435
+ expect(result.warnings[0]).toContain('npm@1.0.0');
436
+ expect(result.warnings[1]).toContain('npm@2.0.0');
437
+ expect(result.warnings[2]).toContain('npm@3.5.0');
438
+ // All should have the same suggestion
439
+ result.warnings.forEach(warning => {
440
+ expect(warning).toContain('💡 Suggestion');
441
+ expect(warning).toContain('script step with npm commands');
442
+ });
443
+ });
444
+ });
445
+ });
446
+ //# sourceMappingURL=e2e.test.js.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Environment variable resolution and interpolation
3
+ * Merges global and workflow-level environment variables
4
+ * Supports variable interpolation in multiple formats
5
+ */
6
+ import type { YAMLPipeline, YAMLWorkflow, Platform } from './types.js';
7
+ export declare class MissingEnvironmentVariableError extends Error {
8
+ readonly variableName: string;
9
+ readonly stepName?: string | undefined;
10
+ readonly hint?: string | undefined;
11
+ constructor(variableName: string, stepName?: string | undefined, hint?: string | undefined);
12
+ }
13
+ /**
14
+ * EnvResolver handles environment variable merging and interpolation
15
+ */
16
+ export declare class EnvResolver {
17
+ private envVars;
18
+ private referencedVars;
19
+ private secretsManager;
20
+ constructor(pipeline: YAMLPipeline, workflow: YAMLWorkflow, workflowName: string, platform: Platform, stack?: string, yamlFilePath?: string);
21
+ /**
22
+ * Gets the value of an environment variable
23
+ * @param name Variable name
24
+ * @returns Variable value or undefined if not found
25
+ */
26
+ get(name: string): string | undefined;
27
+ /**
28
+ * Checks if an environment variable is defined
29
+ * @param name Variable name
30
+ * @returns True if variable exists
31
+ */
32
+ has(name: string): boolean;
33
+ /**
34
+ * Registers a variable as a step output passthrough.
35
+ * When this variable appears in a later step's inputs it will be kept as
36
+ * the literal shell reference "$NAME" so the shell resolves it at runtime.
37
+ */
38
+ registerStepOutput(name: string): void;
39
+ /**
40
+ * Gets all environment variables as a plain object
41
+ * @returns Object with all environment variables
42
+ */
43
+ getAll(): Record<string, string>;
44
+ /**
45
+ * Interpolates environment variables in a string
46
+ * Supports four formats:
47
+ * - $VAR
48
+ * - ${VAR}
49
+ * - {{getenv "VAR"}}
50
+ * - {{checksum "path/to/file"}}
51
+ *
52
+ * @param str String to interpolate
53
+ * @param stepName Optional step name for error messages
54
+ * @returns Interpolated string
55
+ * @throws MissingEnvironmentVariableError if referenced variable not found
56
+ */
57
+ interpolate(str: string, stepName?: string): string;
58
+ /**
59
+ * Interpolates environment variables in an object recursively
60
+ * @param obj Object to interpolate
61
+ * @param stepName Optional step name for error messages
62
+ * @returns Interpolated object
63
+ */
64
+ interpolateObject(obj: any, stepName?: string): any;
65
+ /**
66
+ * Validates that all referenced variables are defined
67
+ * @throws MissingEnvironmentVariableError if any referenced variable is missing
68
+ */
69
+ validateReferencedVars(): void;
70
+ /**
71
+ * Resolves a single variable reference
72
+ * @param varName Variable name
73
+ * @param stepName Optional step name for error messages
74
+ * @returns Variable value
75
+ * @throws MissingEnvironmentVariableError if variable not found
76
+ */
77
+ private resolveVar;
78
+ /**
79
+ * Computes MD5 checksum of a file
80
+ * @param filePath Path to file (relative to current working directory)
81
+ * @returns MD5 hash of file contents
82
+ * @throws Error if file does not exist or cannot be read
83
+ */
84
+ private computeChecksum;
85
+ /**
86
+ * Adds built-in environment variables
87
+ * @param workflowName Name of the current workflow
88
+ * @param platform Detected platform
89
+ * @param stack Optional stack identifier
90
+ */
91
+ private addBuiltInVars;
92
+ /**
93
+ * Merges environment variables from YAML array format
94
+ * @param envArray Array of environment variable objects
95
+ */
96
+ private mergeEnvVars;
97
+ /**
98
+ * Resolves $SECRET{secret_id} references to actual secret values
99
+ * @param value Value that may contain a secret reference
100
+ * @returns Resolved value with secret content, or empty string if secret not found
101
+ */
102
+ private resolveSecretReference;
103
+ /**
104
+ * Interpolates environment variable value during initial setup
105
+ * This allows env vars to reference other env vars
106
+ * @param value Raw value
107
+ * @returns Interpolated value or original if interpolation fails
108
+ */
109
+ private interpolateEnvValue;
110
+ }
111
+ /**
112
+ * Helper function to create an EnvResolver for a workflow
113
+ * @param pipeline Parsed YAML pipeline
114
+ * @param workflowName Name of the workflow
115
+ * @param platform Detected platform
116
+ * @param stack Optional stack identifier
117
+ * @returns EnvResolver instance
118
+ */
119
+ export declare function createEnvResolver(pipeline: YAMLPipeline, workflowName: string, platform: Platform, stack?: string): EnvResolver;
120
+ //# sourceMappingURL=env-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgEnF,qBAAa,+BAAgC,SAAQ,KAAK;aAEtC,YAAY,EAAE,MAAM;aACpB,QAAQ,CAAC,EAAE,MAAM;aACjB,IAAI,CAAC,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,YAAA,EACjB,IAAI,CAAC,EAAE,MAAM,YAAA;CAUhC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,cAAc,CAAiB;gBAGrC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrC;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC;;;OAGG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IA0BnD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG;IAoBnD;;;OAGG;IACH,sBAAsB,IAAI,IAAI;IAQ9B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAalB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAmFtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAS5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,WAAW,CAOb"}