@invarn/cibuild 1.3.15 → 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,206 @@
1
+ /**
2
+ * Bitrise YAML Compatibility Layer
3
+ *
4
+ * This module provides compatibility functions to convert Bitrise YAML pipelines
5
+ * to CI Build format, including metadata conversion and environment variable mapping.
6
+ */
7
+ /**
8
+ * Map of Bitrise environment variables to CI Build equivalents
9
+ * Used for automatic variable substitution (FR-9.2)
10
+ */
11
+ export const BITRISE_ENV_MAP = {
12
+ // Build information
13
+ BITRISE_BUILD_NUMBER: 'CIBUILD_BUILD_NUMBER',
14
+ BITRISE_BUILD_URL: 'CIBUILD_BUILD_URL',
15
+ BITRISE_APP_TITLE: 'CIBUILD_APP_TITLE',
16
+ // Git information
17
+ BITRISE_GIT_BRANCH: 'CIBUILD_GIT_BRANCH',
18
+ BITRISE_GIT_REPOSITORY_URL: 'CIBUILD_GIT_REPOSITORY_URL',
19
+ BITRISE_GIT_COMMIT: 'CIBUILD_GIT_COMMIT',
20
+ BITRISE_SOURCE_DIR: 'CIBUILD_SOURCE_DIR',
21
+ BITRISE_TRIGGERED_WORKFLOW_ID: 'CIBUILD_TRIGGERED_WORKFLOW_ID',
22
+ // Xcode-specific
23
+ BITRISE_XCODE_VERSION: 'CIBUILD_XCODE_VERSION',
24
+ BITRISE_SCHEME: 'CIBUILD_SCHEME',
25
+ // Gradle-specific
26
+ BITRISE_GRADLE_VERSION: 'CIBUILD_GRADLE_VERSION',
27
+ };
28
+ /**
29
+ * Bitrise environment variables that require user input
30
+ * These cannot be automatically mapped and must be provided by the user (FR-9.3)
31
+ */
32
+ export const BITRISE_USER_REQUIRED_VARS = [
33
+ 'CIBUILD_PUBLIC_INSTALL_PAGE_URL',
34
+ 'CIBUILD_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL',
35
+ 'CIBUILD_PULL_REQUEST',
36
+ 'CIBUILD_GIT_TAG',
37
+ ];
38
+ /**
39
+ * Error class for Bitrise-specific environment variable issues
40
+ * Provides detailed information about missing variables (FR-9.3, FR-9.4, FR-9.5)
41
+ */
42
+ export class BitriseEnvironmentError extends Error {
43
+ missingVariables;
44
+ constructor(missingVariables) {
45
+ const message = formatBitriseEnvironmentError(missingVariables);
46
+ super(message);
47
+ this.name = 'BitriseEnvironmentError';
48
+ this.missingVariables = missingVariables;
49
+ }
50
+ }
51
+ /**
52
+ * Converts Bitrise metadata to CI Build format
53
+ * Transforms meta.bitrise.io to meta.cibuild.io (FR-1.1, FR-1.2, FR-1.3)
54
+ *
55
+ * @param pipeline YAML pipeline object (mutated in place)
56
+ */
57
+ export function convertBitriseMetadata(pipeline) {
58
+ // Check if Bitrise metadata exists
59
+ if (!pipeline.meta || !pipeline.meta['bitrise.io']) {
60
+ return;
61
+ }
62
+ const bitriseMeta = pipeline.meta['bitrise.io'];
63
+ // If cibuild.io metadata already exists, prefer it (FR-11.3)
64
+ if (pipeline.meta['cibuild.io']) {
65
+ // Keep both for reference, but CI Build metadata takes precedence
66
+ return;
67
+ }
68
+ // Convert Bitrise metadata to CI Build format
69
+ pipeline.meta['cibuild.io'] = {
70
+ stack: bitriseMeta.stack || 'linux-ubuntu-22.04',
71
+ machine_type: bitriseMeta.machine_type || 'standard',
72
+ };
73
+ // Optionally preserve original Bitrise metadata for reference
74
+ // (can be removed in production if not needed)
75
+ }
76
+ /**
77
+ * Detects Bitrise-specific environment variables in step inputs
78
+ * Scans recursively through objects and arrays (FR-9.1)
79
+ *
80
+ * @param inputs Step inputs object
81
+ * @param stepName Name of the step (for error reporting)
82
+ * @returns Array of detected Bitrise variable information
83
+ */
84
+ export function detectBitriseEnvironmentVariables(inputs, stepName) {
85
+ const detected = [];
86
+ // Recursively scan inputs for Bitrise variables
87
+ scanForBitriseVars(inputs, stepName, '', detected);
88
+ return detected;
89
+ }
90
+ /**
91
+ * Recursively scans an object/array for Bitrise environment variables
92
+ *
93
+ * @param obj Object to scan
94
+ * @param stepName Step name for error reporting
95
+ * @param path Current path in object (for input field tracking)
96
+ * @param detected Array to accumulate detected variables
97
+ */
98
+ function scanForBitriseVars(obj, stepName, path, detected) {
99
+ if (typeof obj === 'string') {
100
+ // Check for Bitrise variables in string
101
+ const bitriseVars = extractBitriseVariables(obj);
102
+ for (const varName of bitriseVars) {
103
+ // Check if this is a user-required variable (needs explicit configuration)
104
+ if (BITRISE_USER_REQUIRED_VARS.includes(varName)) {
105
+ detected.push({
106
+ name: varName,
107
+ stepName,
108
+ inputField: path || 'unknown',
109
+ suggestedAction: `Set this environment variable with your value:\nexport ${varName}="your-value-here"`,
110
+ });
111
+ }
112
+ // Check if this variable has a CI Build equivalent
113
+ else if (varName in BITRISE_ENV_MAP && BITRISE_ENV_MAP[varName] !== varName) {
114
+ const cibuildVar = BITRISE_ENV_MAP[varName];
115
+ detected.push({
116
+ name: varName,
117
+ stepName,
118
+ inputField: path || 'unknown',
119
+ cibuildEquivalent: cibuildVar,
120
+ suggestedAction: `Auto-mapped to ${cibuildVar}. You can also set:\nexport ${varName}="$${cibuildVar}"`,
121
+ });
122
+ }
123
+ }
124
+ }
125
+ else if (Array.isArray(obj)) {
126
+ // Scan array elements
127
+ obj.forEach((item, index) => {
128
+ scanForBitriseVars(item, stepName, `${path}[${index}]`, detected);
129
+ });
130
+ }
131
+ else if (obj && typeof obj === 'object') {
132
+ // Scan object properties
133
+ for (const [key, value] of Object.entries(obj)) {
134
+ const newPath = path ? `${path}.${key}` : key;
135
+ scanForBitriseVars(value, stepName, newPath, detected);
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Extracts Bitrise variable names from a string
141
+ * Supports formats: $VAR, ${VAR}, {{getenv "VAR"}}
142
+ *
143
+ * @param str String to search
144
+ * @returns Array of unique Bitrise variable names
145
+ */
146
+ function extractBitriseVariables(str) {
147
+ const vars = new Set();
148
+ // Pattern 1: {{getenv "BITRISE_*"}}
149
+ const getenvPattern = /\{\{getenv "(BITRISE_[A-Z0-9_]+)"\}\}/g;
150
+ let match;
151
+ while ((match = getenvPattern.exec(str)) !== null) {
152
+ vars.add(match[1]);
153
+ }
154
+ // Pattern 2: ${BITRISE_*}
155
+ const bracesPattern = /\$\{(BITRISE_[A-Z0-9_]+)\}/g;
156
+ while ((match = bracesPattern.exec(str)) !== null) {
157
+ vars.add(match[1]);
158
+ }
159
+ // Pattern 3: $BITRISE_* (not followed by alphanumeric or underscore)
160
+ const dollarPattern = /\$(BITRISE_[A-Z0-9_]+)(?![A-Z0-9_])/g;
161
+ while ((match = dollarPattern.exec(str)) !== null) {
162
+ vars.add(match[1]);
163
+ }
164
+ return Array.from(vars);
165
+ }
166
+ /**
167
+ * Formats a detailed error message for missing Bitrise environment variables
168
+ * (FR-9.3, FR-9.4, FR-9.5)
169
+ *
170
+ * @param missingVars Array of missing variable information
171
+ * @returns Formatted error message
172
+ */
173
+ function formatBitriseEnvironmentError(missingVars) {
174
+ const lines = [
175
+ '❌ Bitrise-specific environment variables detected:',
176
+ '',
177
+ ];
178
+ for (const varInfo of missingVars) {
179
+ lines.push('┌─────────────────────────────────────────────────────────────────────────────');
180
+ lines.push(`│ Variable: $${varInfo.name}`);
181
+ lines.push(`│ Used in: ${varInfo.stepName}`);
182
+ lines.push(`│ Input: ${varInfo.inputField}`);
183
+ if (varInfo.cibuildEquivalent) {
184
+ lines.push(`│ CI Build: Use $${varInfo.cibuildEquivalent} instead, or set:`);
185
+ lines.push(`│ export ${varInfo.name}="$${varInfo.cibuildEquivalent}"`);
186
+ }
187
+ else {
188
+ lines.push(`│ Action: ${varInfo.suggestedAction.replace(/\n/g, '\n│ ')}`);
189
+ }
190
+ lines.push('└─────────────────────────────────────────────────────────────────────────────');
191
+ lines.push('');
192
+ }
193
+ lines.push(`Please configure these ${missingVars.length} variable${missingVars.length > 1 ? 's' : ''} and try again.`);
194
+ return lines.join('\n');
195
+ }
196
+ /**
197
+ * Checks if a variable should trigger a warning vs error
198
+ * Mapped variables generate info logs, unmapped variables generate errors
199
+ *
200
+ * @param varName Bitrise variable name
201
+ * @returns True if variable needs user configuration (error), false if auto-mapped (info)
202
+ */
203
+ export function requiresUserConfiguration(varName) {
204
+ return BITRISE_USER_REQUIRED_VARS.includes(varName);
205
+ }
206
+ //# sourceMappingURL=bitrise-compat.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for Bitrise YAML Compatibility Layer
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=bitrise-compat.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitrise-compat.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/bitrise-compat.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Unit tests for Bitrise YAML Compatibility Layer
3
+ */
4
+ import { describe, test, expect } from '@jest/globals';
5
+ import { BITRISE_ENV_MAP, BITRISE_USER_REQUIRED_VARS, BitriseEnvironmentError, convertBitriseMetadata, detectBitriseEnvironmentVariables, requiresUserConfiguration, } from './bitrise-compat.js';
6
+ describe('Bitrise Compatibility Layer', () => {
7
+ describe('BITRISE_ENV_MAP', () => {
8
+ test('should map BITRISE_BUILD_NUMBER to CIBUILD_BUILD_NUMBER', () => {
9
+ expect(BITRISE_ENV_MAP.BITRISE_BUILD_NUMBER).toBe('CIBUILD_BUILD_NUMBER');
10
+ });
11
+ test('should map BITRISE_BUILD_URL to CIBUILD_BUILD_URL', () => {
12
+ expect(BITRISE_ENV_MAP.BITRISE_BUILD_URL).toBe('CIBUILD_BUILD_URL');
13
+ });
14
+ test('should map BITRISE_APP_TITLE to CIBUILD_APP_TITLE', () => {
15
+ expect(BITRISE_ENV_MAP.BITRISE_APP_TITLE).toBe('CIBUILD_APP_TITLE');
16
+ });
17
+ test('should map git variables to CIBUILD equivalents', () => {
18
+ expect(BITRISE_ENV_MAP.BITRISE_GIT_BRANCH).toBe('CIBUILD_GIT_BRANCH');
19
+ expect(BITRISE_ENV_MAP.BITRISE_GIT_REPOSITORY_URL).toBe('CIBUILD_GIT_REPOSITORY_URL');
20
+ expect(BITRISE_ENV_MAP.BITRISE_GIT_COMMIT).toBe('CIBUILD_GIT_COMMIT');
21
+ expect(BITRISE_ENV_MAP.BITRISE_SOURCE_DIR).toBe('CIBUILD_SOURCE_DIR');
22
+ });
23
+ });
24
+ describe('BITRISE_USER_REQUIRED_VARS', () => {
25
+ test('should include CIBUILD_PUBLIC_INSTALL_PAGE_URL', () => {
26
+ expect(BITRISE_USER_REQUIRED_VARS).toContain('CIBUILD_PUBLIC_INSTALL_PAGE_URL');
27
+ });
28
+ test('should include CIBUILD_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL', () => {
29
+ expect(BITRISE_USER_REQUIRED_VARS).toContain('CIBUILD_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL');
30
+ });
31
+ test('should include CIBUILD_PULL_REQUEST', () => {
32
+ expect(BITRISE_USER_REQUIRED_VARS).toContain('CIBUILD_PULL_REQUEST');
33
+ });
34
+ });
35
+ describe('convertBitriseMetadata', () => {
36
+ test('should convert meta.bitrise.io to meta.cibuild.io', () => {
37
+ const pipeline = {
38
+ format_version: '4',
39
+ meta: {
40
+ 'bitrise.io': {
41
+ stack: 'linux-docker-android-22.04',
42
+ machine_type: 'performance',
43
+ },
44
+ },
45
+ workflows: {},
46
+ };
47
+ convertBitriseMetadata(pipeline);
48
+ expect(pipeline.meta?.['cibuild.io']).toBeDefined();
49
+ expect(pipeline.meta?.['cibuild.io']?.stack).toBe('linux-docker-android-22.04');
50
+ expect(pipeline.meta?.['cibuild.io']?.machine_type).toBe('performance');
51
+ });
52
+ test('should use default values if Bitrise metadata is incomplete', () => {
53
+ const pipeline = {
54
+ format_version: '4',
55
+ meta: {
56
+ 'bitrise.io': {},
57
+ },
58
+ workflows: {},
59
+ };
60
+ convertBitriseMetadata(pipeline);
61
+ expect(pipeline.meta?.['cibuild.io']).toBeDefined();
62
+ expect(pipeline.meta?.['cibuild.io']?.stack).toBe('linux-ubuntu-22.04');
63
+ expect(pipeline.meta?.['cibuild.io']?.machine_type).toBe('standard');
64
+ });
65
+ test('should not modify pipeline if no Bitrise metadata exists', () => {
66
+ const pipeline = {
67
+ format_version: '4',
68
+ meta: {},
69
+ workflows: {},
70
+ };
71
+ convertBitriseMetadata(pipeline);
72
+ expect(pipeline.meta?.['cibuild.io']).toBeUndefined();
73
+ });
74
+ test('should not override existing cibuild.io metadata (FR-11.3)', () => {
75
+ const pipeline = {
76
+ format_version: '4',
77
+ meta: {
78
+ 'bitrise.io': {
79
+ stack: 'linux-docker-android-22.04',
80
+ machine_type: 'performance',
81
+ },
82
+ 'cibuild.io': {
83
+ stack: 'macos-ventura-xcode-15.1',
84
+ machine_type: 'standard',
85
+ },
86
+ },
87
+ workflows: {},
88
+ };
89
+ convertBitriseMetadata(pipeline);
90
+ // CI Build metadata should remain unchanged
91
+ expect(pipeline.meta?.['cibuild.io']?.stack).toBe('macos-ventura-xcode-15.1');
92
+ expect(pipeline.meta?.['cibuild.io']?.machine_type).toBe('standard');
93
+ });
94
+ test('should preserve all other YAML structure (FR-1.3)', () => {
95
+ const pipeline = {
96
+ format_version: '4',
97
+ meta: {
98
+ 'bitrise.io': {
99
+ stack: 'linux-docker-android-22.04',
100
+ },
101
+ },
102
+ app: {
103
+ envs: [{ PROJECT_NAME: 'MyApp' }],
104
+ },
105
+ workflows: {
106
+ primary: {
107
+ steps: [],
108
+ },
109
+ },
110
+ };
111
+ convertBitriseMetadata(pipeline);
112
+ // All original structure should be preserved
113
+ expect(pipeline.format_version).toBe('4');
114
+ expect(pipeline.app).toBeDefined();
115
+ expect(pipeline.app?.envs).toHaveLength(1);
116
+ expect(pipeline.workflows.primary).toBeDefined();
117
+ });
118
+ });
119
+ describe('detectBitriseEnvironmentVariables', () => {
120
+ test('should detect $VAR format', () => {
121
+ const inputs = {
122
+ message: 'Build $BITRISE_BUILD_NUMBER completed',
123
+ };
124
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
125
+ expect(detected).toHaveLength(1);
126
+ expect(detected[0].name).toBe('BITRISE_BUILD_NUMBER');
127
+ expect(detected[0].stepName).toBe('test-step');
128
+ expect(detected[0].cibuildEquivalent).toBe('CIBUILD_BUILD_NUMBER');
129
+ });
130
+ test('should detect ${VAR} format', () => {
131
+ const inputs = {
132
+ url: 'https://example.com/build/${BITRISE_BUILD_URL}',
133
+ };
134
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
135
+ expect(detected).toHaveLength(1);
136
+ expect(detected[0].name).toBe('BITRISE_BUILD_URL');
137
+ expect(detected[0].cibuildEquivalent).toBe('CIBUILD_BUILD_URL');
138
+ });
139
+ test('should detect {{getenv "VAR"}} format', () => {
140
+ const inputs = {
141
+ content: 'Build: {{getenv "BITRISE_BUILD_NUMBER"}}',
142
+ };
143
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
144
+ expect(detected).toHaveLength(1);
145
+ expect(detected[0].name).toBe('BITRISE_BUILD_NUMBER');
146
+ expect(detected[0].cibuildEquivalent).toBe('CIBUILD_BUILD_NUMBER');
147
+ });
148
+ test('should detect multiple variables in same string', () => {
149
+ const inputs = {
150
+ message: 'Build $BITRISE_BUILD_NUMBER and URL: ${BITRISE_PUBLIC_INSTALL_PAGE_URL}',
151
+ };
152
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
153
+ expect(detected.length).toBeGreaterThanOrEqual(2);
154
+ const varNames = detected.map((v) => v.name);
155
+ expect(varNames).toContain('BITRISE_BUILD_NUMBER');
156
+ expect(varNames).toContain('BITRISE_PUBLIC_INSTALL_PAGE_URL');
157
+ });
158
+ test('should detect variables in nested objects', () => {
159
+ const inputs = {
160
+ slack: {
161
+ webhook: 'https://hooks.slack.com',
162
+ message: {
163
+ text: 'Build $BITRISE_BUILD_URL',
164
+ fields: ['QR Code: $BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL'],
165
+ },
166
+ },
167
+ };
168
+ const detected = detectBitriseEnvironmentVariables(inputs, 'slack-step');
169
+ expect(detected.length).toBeGreaterThanOrEqual(1);
170
+ const varNames = detected.map((v) => v.name);
171
+ // BITRISE_BUILD_URL has CI Build equivalent, BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL needs user input
172
+ expect(varNames.length).toBeGreaterThan(0);
173
+ });
174
+ test('should detect variables in arrays', () => {
175
+ const inputs = {
176
+ paths: ['Install: $BITRISE_PUBLIC_INSTALL_PAGE_URL', 'Build: $BITRISE_BUILD_NUMBER'],
177
+ };
178
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
179
+ expect(detected.length).toBeGreaterThanOrEqual(2);
180
+ const varNames = detected.map((v) => v.name);
181
+ expect(varNames).toContain('BITRISE_PUBLIC_INSTALL_PAGE_URL');
182
+ expect(varNames).toContain('BITRISE_BUILD_NUMBER');
183
+ });
184
+ test('should identify user-required variables', () => {
185
+ const inputs = {
186
+ url: '$BITRISE_PUBLIC_INSTALL_PAGE_URL',
187
+ };
188
+ const detected = detectBitriseEnvironmentVariables(inputs, 'deploy-step');
189
+ expect(detected).toHaveLength(1);
190
+ expect(detected[0].name).toBe('BITRISE_PUBLIC_INSTALL_PAGE_URL');
191
+ expect(detected[0].cibuildEquivalent).toBeUndefined();
192
+ expect(detected[0].suggestedAction).toContain('Set this environment variable');
193
+ });
194
+ test('should include input field path in detection', () => {
195
+ const inputs = {
196
+ slack: {
197
+ buttons: 'Install|$BITRISE_PUBLIC_INSTALL_PAGE_URL',
198
+ },
199
+ };
200
+ const detected = detectBitriseEnvironmentVariables(inputs, 'slack@3');
201
+ expect(detected).toHaveLength(1);
202
+ expect(detected[0].inputField).toBe('slack.buttons');
203
+ });
204
+ test('should handle empty inputs', () => {
205
+ const detected = detectBitriseEnvironmentVariables({}, 'test-step');
206
+ expect(detected).toHaveLength(0);
207
+ });
208
+ test('should handle inputs with no Bitrise variables', () => {
209
+ const inputs = {
210
+ message: 'Hello world',
211
+ count: 42,
212
+ enabled: true,
213
+ };
214
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
215
+ expect(detected).toHaveLength(0);
216
+ });
217
+ });
218
+ describe('BitriseEnvironmentError', () => {
219
+ test('should format error message for single variable', () => {
220
+ const missingVars = [
221
+ {
222
+ name: 'BITRISE_PUBLIC_INSTALL_PAGE_URL',
223
+ stepName: 'slack@3',
224
+ inputField: 'buttons',
225
+ suggestedAction: 'Set this environment variable with your install page URL',
226
+ },
227
+ ];
228
+ const error = new BitriseEnvironmentError(missingVars);
229
+ expect(error.message).toContain('❌ Bitrise-specific environment variables detected');
230
+ expect(error.message).toContain('Variable: $BITRISE_PUBLIC_INSTALL_PAGE_URL');
231
+ expect(error.message).toContain('Used in: slack@3');
232
+ expect(error.message).toContain('Input: buttons');
233
+ expect(error.message).toContain('configure these 1 variable');
234
+ });
235
+ test('should format error message for multiple variables', () => {
236
+ const missingVars = [
237
+ {
238
+ name: 'BITRISE_PUBLIC_INSTALL_PAGE_URL',
239
+ stepName: 'slack@3',
240
+ inputField: 'buttons',
241
+ suggestedAction: 'Set this variable',
242
+ },
243
+ {
244
+ name: 'BITRISE_BUILD_URL',
245
+ stepName: 'slack@3',
246
+ inputField: 'buttons',
247
+ cibuildEquivalent: 'CIBUILD_BUILD_URL',
248
+ suggestedAction: 'Use CI Build equivalent',
249
+ },
250
+ ];
251
+ const error = new BitriseEnvironmentError(missingVars);
252
+ expect(error.message).toContain('BITRISE_PUBLIC_INSTALL_PAGE_URL');
253
+ expect(error.message).toContain('BITRISE_BUILD_URL');
254
+ expect(error.message).toContain('configure these 2 variables');
255
+ });
256
+ test('should show CI Build equivalent when available', () => {
257
+ const missingVars = [
258
+ {
259
+ name: 'BITRISE_BUILD_NUMBER',
260
+ stepName: 'script@1',
261
+ inputField: 'content',
262
+ cibuildEquivalent: 'CIBUILD_BUILD_NUMBER',
263
+ suggestedAction: 'Use CI Build equivalent',
264
+ },
265
+ ];
266
+ const error = new BitriseEnvironmentError(missingVars);
267
+ expect(error.message).toContain('CI Build: Use $CIBUILD_BUILD_NUMBER instead');
268
+ expect(error.message).toContain('export BITRISE_BUILD_NUMBER="$CIBUILD_BUILD_NUMBER"');
269
+ });
270
+ test('should include box drawing characters for formatting', () => {
271
+ const missingVars = [
272
+ {
273
+ name: 'BITRISE_PUBLIC_INSTALL_PAGE_URL',
274
+ stepName: 'test',
275
+ inputField: 'url',
276
+ suggestedAction: 'Set this',
277
+ },
278
+ ];
279
+ const error = new BitriseEnvironmentError(missingVars);
280
+ expect(error.message).toContain('┌─────');
281
+ expect(error.message).toContain('│ Variable:');
282
+ expect(error.message).toContain('└─────');
283
+ });
284
+ test('should store missing variables in error object', () => {
285
+ const missingVars = [
286
+ {
287
+ name: 'BITRISE_PUBLIC_INSTALL_PAGE_URL',
288
+ stepName: 'slack@3',
289
+ inputField: 'buttons',
290
+ suggestedAction: 'Set this variable',
291
+ },
292
+ ];
293
+ const error = new BitriseEnvironmentError(missingVars);
294
+ expect(error.missingVariables).toEqual(missingVars);
295
+ expect(error.name).toBe('BitriseEnvironmentError');
296
+ });
297
+ });
298
+ describe('requiresUserConfiguration', () => {
299
+ test('should return true for user-required variables', () => {
300
+ expect(requiresUserConfiguration('BITRISE_PUBLIC_INSTALL_PAGE_URL')).toBe(true);
301
+ expect(requiresUserConfiguration('BITRISE_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL')).toBe(true);
302
+ expect(requiresUserConfiguration('BITRISE_PULL_REQUEST')).toBe(true);
303
+ });
304
+ test('should return false for auto-mapped variables', () => {
305
+ expect(requiresUserConfiguration('BITRISE_BUILD_NUMBER')).toBe(false);
306
+ expect(requiresUserConfiguration('BITRISE_BUILD_URL')).toBe(false);
307
+ expect(requiresUserConfiguration('BITRISE_GIT_BRANCH')).toBe(false);
308
+ });
309
+ test('should return false for unknown variables', () => {
310
+ expect(requiresUserConfiguration('UNKNOWN_VARIABLE')).toBe(false);
311
+ });
312
+ });
313
+ describe('Edge cases', () => {
314
+ test('should not detect non-BITRISE variables', () => {
315
+ const inputs = {
316
+ message: 'Build $BUILD_NUMBER on ${GIT_BRANCH}',
317
+ };
318
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
319
+ expect(detected).toHaveLength(0);
320
+ });
321
+ test('should handle variables at string boundaries', () => {
322
+ const inputs = {
323
+ message: '$BITRISE_BUILD_NUMBER',
324
+ };
325
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
326
+ expect(detected).toHaveLength(1);
327
+ expect(detected[0].name).toBe('BITRISE_BUILD_NUMBER');
328
+ });
329
+ test('should not detect variables in the middle of words', () => {
330
+ const inputs = {
331
+ message: 'NOTBITRISE_BUILD_NUMBER',
332
+ };
333
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
334
+ expect(detected).toHaveLength(0);
335
+ });
336
+ test('should handle null and undefined values gracefully', () => {
337
+ const inputs = {
338
+ nullValue: null,
339
+ undefinedValue: undefined,
340
+ message: 'test',
341
+ };
342
+ const detected = detectBitriseEnvironmentVariables(inputs, 'test-step');
343
+ expect(detected).toHaveLength(0);
344
+ });
345
+ });
346
+ });
347
+ //# sourceMappingURL=bitrise-compat.test.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Converts YAML pipeline workflows to PipelineDef format
3
+ * This bridges the YAML configuration with the existing runner engine
4
+ */
5
+ import type { PipelineDef, CIConfig } from '../types.js';
6
+ import type { YAMLPipeline } from './types.js';
7
+ export declare class YAMLConversionError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Result of YAML to PipelineDef conversion
12
+ */
13
+ export interface ConversionResult {
14
+ pipeline: PipelineDef;
15
+ warnings: string[];
16
+ skippedSteps: number;
17
+ }
18
+ /**
19
+ * Converts a YAML pipeline workflow to PipelineDef format
20
+ * @param pipeline Parsed YAML pipeline
21
+ * @param config CI configuration with path mappings
22
+ * @param workflowName Name of the workflow to convert (if not specified, uses first workflow)
23
+ * @param yamlFilePath Optional path to the YAML file (used for naming SSH keys, etc.)
24
+ * @returns ConversionResult with PipelineDef and warnings about unsupported steps
25
+ */
26
+ export declare function convertYAMLToPipelineDef(pipeline: YAMLPipeline, config: CIConfig, workflowName?: string, yamlFilePath?: string): Promise<ConversionResult>;
27
+ /**
28
+ * Helper function to get available workflow names from a pipeline
29
+ * @param pipeline Parsed YAML pipeline
30
+ * @returns Array of workflow names
31
+ */
32
+ export declare function getAvailableWorkflows(pipeline: YAMLPipeline): string[];
33
+ //# sourceMappingURL=converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../../src/yaml/converter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AAKzE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAgH3B;AAwGD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtE"}