@invarn/cibuild 1.3.16 → 1.3.18

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 +47 -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 +58 -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 +352 -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 +505 -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 +17 -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,405 @@
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 * as crypto from 'crypto';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { execSync } from 'child_process';
10
+ import { SecretsManager } from './secrets-manager.js';
11
+ /**
12
+ * Hints for common variables that require user configuration
13
+ */
14
+ const CIBUILD_VARIABLE_HINTS = {
15
+ CIBUILD_PUBLIC_INSTALL_PAGE_URL: 'Public URL where users can download/install the built app.\n\n' +
16
+ 'Purpose:\n' +
17
+ ' • Used in Slack notifications and other build artifacts\n' +
18
+ ' • Provides a download link for QA, stakeholders, or beta testers\n' +
19
+ ' • Commonly points to artifact hosting (Firebase App Distribution, TestFlight, etc.)\n\n' +
20
+ 'Expected format:\n' +
21
+ ' ┌────────────────────────────────────────────────────────┐\n' +
22
+ ' │ https://example.com/downloads/myapp-v1.0.0.apk │\n' +
23
+ ' │ https://appdistribution.firebase.dev/i/abc123 │\n' +
24
+ ' │ https://testflight.apple.com/join/xyz456 │\n' +
25
+ ' └────────────────────────────────────────────────────────┘\n\n' +
26
+ 'How to set:\n' +
27
+ ' • Add to your YAML under app.envs or workflows.[name].envs:\n' +
28
+ ' envs:\n' +
29
+ ' - CIBUILD_PUBLIC_INSTALL_PAGE_URL: "https://your-url-here"\n' +
30
+ ' • Or set dynamically in a previous step that uploads the build\n' +
31
+ ' • Or leave it as a placeholder if not using artifact distribution',
32
+ CIBUILD_PUBLIC_INSTALL_PAGE_QR_CODE_IMAGE_URL: 'URL to a QR code image for easy mobile download/install.\n\n' +
33
+ 'Purpose:\n' +
34
+ ' • Used in Slack notifications for quick mobile access\n' +
35
+ ' • QR code typically points to CIBUILD_PUBLIC_INSTALL_PAGE_URL\n\n' +
36
+ 'Expected format:\n' +
37
+ ' ┌────────────────────────────────────────────────────────┐\n' +
38
+ ' │ https://example.com/qr-codes/myapp-v1.0.0.png │\n' +
39
+ ' │ https://api.qrserver.com/v1/create-qr-code/?data=... │\n' +
40
+ ' └────────────────────────────────────────────────────────┘\n\n' +
41
+ 'How to set:\n' +
42
+ ' • Add to your YAML under app.envs or workflows.[name].envs\n' +
43
+ ' • Or generate dynamically in a previous step\n' +
44
+ ' • Or use a QR code generation service with the install URL',
45
+ CIBUILD_PULL_REQUEST: 'Pull request number or ID for PR builds.\n\n' +
46
+ 'Expected format:\n' +
47
+ ' ┌────────────────────────────────────────────────────────┐\n' +
48
+ ' │ 123 │\n' +
49
+ ' │ pr-456 │\n' +
50
+ ' └────────────────────────────────────────────────────────┘\n\n' +
51
+ 'How to set:\n' +
52
+ ' • Typically provided by CI system for PR builds\n' +
53
+ ' • Or set manually: export CIBUILD_PULL_REQUEST="123"',
54
+ CIBUILD_GIT_TAG: 'Git tag name for tag-triggered builds.\n\n' +
55
+ 'Expected format:\n' +
56
+ ' ┌────────────────────────────────────────────────────────┐\n' +
57
+ ' │ v1.0.0 │\n' +
58
+ ' │ release-2024-01-15 │\n' +
59
+ ' └────────────────────────────────────────────────────────┘\n\n' +
60
+ 'How to set:\n' +
61
+ ' • Typically provided by CI system for tag builds\n' +
62
+ ' • Or set manually: export CIBUILD_GIT_TAG="v1.0.0"',
63
+ };
64
+ export class MissingEnvironmentVariableError extends Error {
65
+ variableName;
66
+ stepName;
67
+ hint;
68
+ constructor(variableName, stepName, hint) {
69
+ const stepInfo = stepName ? ` required by step: '${stepName}'` : '';
70
+ const hintInfo = hint ? `\n\n${hint}` : '';
71
+ super(`Missing environment variable: '${variableName}'${stepInfo}\n` +
72
+ `Check your YAML file's 'app.envs' or 'workflows.[name].envs' sections.${hintInfo}`);
73
+ this.variableName = variableName;
74
+ this.stepName = stepName;
75
+ this.hint = hint;
76
+ this.name = 'MissingEnvironmentVariableError';
77
+ }
78
+ }
79
+ /**
80
+ * EnvResolver handles environment variable merging and interpolation
81
+ */
82
+ export class EnvResolver {
83
+ envVars;
84
+ referencedVars = new Set();
85
+ secretsManager;
86
+ constructor(pipeline, workflow, workflowName, platform, stack, yamlFilePath) {
87
+ this.envVars = new Map();
88
+ this.secretsManager = new SecretsManager();
89
+ // Step 1: Add built-in environment variables (auto-detected defaults)
90
+ this.addBuiltInVars(workflowName, platform, stack, yamlFilePath);
91
+ // Step 2: Load secrets — overrides auto-detected built-ins with user-stored values
92
+ // Workflow-specific secrets take precedence over global secrets
93
+ const allSecrets = this.secretsManager.getAll(workflowName);
94
+ for (const [key, value] of Object.entries(allSecrets)) {
95
+ this.envVars.set(key, value);
96
+ }
97
+ // Step 3: Add global environment variables (app.envs) — overrides secrets
98
+ if (pipeline.app?.envs) {
99
+ this.mergeEnvVars(pipeline.app.envs);
100
+ }
101
+ // Step 4: Add workflow-level environment variables — highest priority, overrides everything
102
+ if (workflow.envs) {
103
+ this.mergeEnvVars(workflow.envs);
104
+ }
105
+ }
106
+ /**
107
+ * Gets the value of an environment variable
108
+ * @param name Variable name
109
+ * @returns Variable value or undefined if not found
110
+ */
111
+ get(name) {
112
+ this.referencedVars.add(name);
113
+ return this.envVars.get(name);
114
+ }
115
+ /**
116
+ * Checks if an environment variable is defined
117
+ * @param name Variable name
118
+ * @returns True if variable exists
119
+ */
120
+ has(name) {
121
+ return this.envVars.has(name);
122
+ }
123
+ /**
124
+ * Registers a variable as a step output passthrough.
125
+ * When this variable appears in a later step's inputs it will be kept as
126
+ * the literal shell reference "$NAME" so the shell resolves it at runtime.
127
+ */
128
+ registerStepOutput(name) {
129
+ if (!this.envVars.has(name)) {
130
+ this.envVars.set(name, `$${name}`);
131
+ }
132
+ }
133
+ /**
134
+ * Gets all environment variables as a plain object
135
+ * @returns Object with all environment variables
136
+ */
137
+ getAll() {
138
+ const result = {};
139
+ for (const [key, value] of this.envVars.entries()) {
140
+ result[key] = value;
141
+ }
142
+ return result;
143
+ }
144
+ /**
145
+ * Interpolates environment variables in a string
146
+ * Supports four formats:
147
+ * - $VAR
148
+ * - ${VAR}
149
+ * - {{getenv "VAR"}}
150
+ * - {{checksum "path/to/file"}}
151
+ *
152
+ * @param str String to interpolate
153
+ * @param stepName Optional step name for error messages
154
+ * @returns Interpolated string
155
+ * @throws MissingEnvironmentVariableError if referenced variable not found
156
+ */
157
+ interpolate(str, stepName) {
158
+ let result = str;
159
+ // Format 1: {{checksum "file"}}
160
+ result = result.replace(/\{\{checksum "([^"]+)"\}\}/g, (match, filePath) => {
161
+ return this.computeChecksum(filePath);
162
+ });
163
+ // Format 2: {{getenv "VAR"}}
164
+ result = result.replace(/\{\{getenv "([^"]+)"\}\}/g, (match, varName) => {
165
+ return this.resolveVar(varName, stepName);
166
+ });
167
+ // Format 3: ${VAR}
168
+ result = result.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, (match, varName) => {
169
+ return this.resolveVar(varName, stepName);
170
+ });
171
+ // Format 4: $VAR (must not be followed by alphanumeric or underscore)
172
+ result = result.replace(/\$([A-Z_][A-Z0-9_]*)(?![A-Z0-9_])/g, (match, varName) => {
173
+ return this.resolveVar(varName, stepName);
174
+ });
175
+ return result;
176
+ }
177
+ /**
178
+ * Interpolates environment variables in an object recursively
179
+ * @param obj Object to interpolate
180
+ * @param stepName Optional step name for error messages
181
+ * @returns Interpolated object
182
+ */
183
+ interpolateObject(obj, stepName) {
184
+ if (typeof obj === 'string') {
185
+ return this.interpolate(obj, stepName);
186
+ }
187
+ if (Array.isArray(obj)) {
188
+ return obj.map((item) => this.interpolateObject(item, stepName));
189
+ }
190
+ if (obj && typeof obj === 'object') {
191
+ const result = {};
192
+ for (const [key, value] of Object.entries(obj)) {
193
+ result[key] = this.interpolateObject(value, stepName);
194
+ }
195
+ return result;
196
+ }
197
+ return obj;
198
+ }
199
+ /**
200
+ * Validates that all referenced variables are defined
201
+ * @throws MissingEnvironmentVariableError if any referenced variable is missing
202
+ */
203
+ validateReferencedVars() {
204
+ for (const varName of this.referencedVars) {
205
+ if (!this.envVars.has(varName)) {
206
+ throw new MissingEnvironmentVariableError(varName);
207
+ }
208
+ }
209
+ }
210
+ /**
211
+ * Resolves a single variable reference
212
+ * @param varName Variable name
213
+ * @param stepName Optional step name for error messages
214
+ * @returns Variable value
215
+ * @throws MissingEnvironmentVariableError if variable not found
216
+ */
217
+ resolveVar(varName, stepName) {
218
+ this.referencedVars.add(varName);
219
+ const value = this.envVars.get(varName);
220
+ if (value === undefined) {
221
+ // Check if we have a hint for this variable
222
+ const hint = CIBUILD_VARIABLE_HINTS[varName];
223
+ throw new MissingEnvironmentVariableError(varName, stepName, hint);
224
+ }
225
+ return value;
226
+ }
227
+ /**
228
+ * Computes MD5 checksum of a file
229
+ * @param filePath Path to file (relative to current working directory)
230
+ * @returns MD5 hash of file contents
231
+ * @throws Error if file does not exist or cannot be read
232
+ */
233
+ computeChecksum(filePath) {
234
+ try {
235
+ // Resolve relative path to absolute path
236
+ const absolutePath = path.resolve(process.cwd(), filePath);
237
+ // Check if file exists
238
+ if (!fs.existsSync(absolutePath)) {
239
+ throw new Error(`File not found: ${filePath}`);
240
+ }
241
+ // Read file and compute MD5 hash
242
+ const fileBuffer = fs.readFileSync(absolutePath);
243
+ const hash = crypto.createHash('md5');
244
+ hash.update(fileBuffer);
245
+ return hash.digest('hex');
246
+ }
247
+ catch (error) {
248
+ if (error instanceof Error) {
249
+ throw new Error(`Failed to compute checksum for "${filePath}": ${error.message}`);
250
+ }
251
+ throw error;
252
+ }
253
+ }
254
+ /**
255
+ * Adds built-in environment variables
256
+ * @param workflowName Name of the current workflow
257
+ * @param platform Detected platform
258
+ * @param stack Optional stack identifier
259
+ */
260
+ addBuiltInVars(workflowName, platform, stack, yamlFilePath) {
261
+ // CI Build native variables (FR-9.2)
262
+ const buildNumber = process.env.BUILD_NUMBER || '1';
263
+ const buildUrl = process.env.BUILD_URL || 'https://cibuild.io/builds/1';
264
+ const appTitle = process.env.APP_TITLE || 'CI Build Build';
265
+ // Extract YAML filename (without extension) for use in naming resources like SSH keys
266
+ if (yamlFilePath) {
267
+ const yamlFilename = path.basename(yamlFilePath, path.extname(yamlFilePath));
268
+ this.envVars.set('CIBUILD_YAML_FILENAME', yamlFilename);
269
+ }
270
+ this.envVars.set('CIBUILD_BUILD_NUMBER', buildNumber);
271
+ this.envVars.set('CIBUILD_BUILD_URL', buildUrl);
272
+ this.envVars.set('CIBUILD_APP_TITLE', appTitle);
273
+ // Writable scratch path on local APFS disk. The sandbox runner sets this
274
+ // in the guest shell; outside a sandbox we fall back to TMPDIR so
275
+ // pipelines that reference $CIBUILD_SCRATCH still interpolate to
276
+ // something writable.
277
+ this.envVars.set('CIBUILD_SCRATCH', process.env.CIBUILD_SCRATCH || process.env.TMPDIR || '/tmp');
278
+ // Auto-detect git metadata from local repo
279
+ const git = (cmd, fallback) => {
280
+ try {
281
+ return execSync(`git ${cmd}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() || fallback;
282
+ }
283
+ catch {
284
+ return fallback;
285
+ }
286
+ };
287
+ // Common built-in variables
288
+ const gitBranch = process.env.GIT_BRANCH || git('rev-parse --abbrev-ref HEAD', 'main');
289
+ const gitCommit = git('rev-parse HEAD', process.env.GIT_COMMIT || 'HEAD');
290
+ const gitRepoUrl = process.env.CIBUILD_GIT_REPOSITORY_URL || process.env.GIT_REPOSITORY_URL || git('remote get-url origin', '');
291
+ // Source directory
292
+ this.envVars.set('CIBUILD_SOURCE_DIR', process.cwd());
293
+ // Git variables
294
+ this.envVars.set('CIBUILD_GIT_BRANCH', gitBranch);
295
+ this.envVars.set('CIBUILD_GIT_COMMIT', gitCommit);
296
+ this.envVars.set('CIBUILD_GIT_REPOSITORY_URL', gitRepoUrl);
297
+ this.envVars.set('CIBUILD_TRIGGERED_WORKFLOW_ID', workflowName);
298
+ // Git metadata (auto-detected from local repo)
299
+ this.envVars.set('GIT_CLONE_COMMIT_HASH', gitCommit);
300
+ this.envVars.set('GIT_CLONE_COMMIT_AUTHOR_NAME', git('log -1 --format=%an', process.env.GIT_AUTHOR_NAME || ''));
301
+ this.envVars.set('GIT_CLONE_COMMIT_AUTHOR_EMAIL', git('log -1 --format=%ae', process.env.GIT_AUTHOR_EMAIL || ''));
302
+ this.envVars.set('GIT_CLONE_COMMIT_COMMITER_NAME', git('log -1 --format=%cn', process.env.GIT_COMMITTER_NAME || ''));
303
+ this.envVars.set('GIT_CLONE_COMMIT_COMMITER_EMAIL', git('log -1 --format=%ce', process.env.GIT_COMMITTER_EMAIL || ''));
304
+ this.envVars.set('GIT_CLONE_COMMIT_MESSAGE_SUBJECT', git('log -1 --format=%s', process.env.GIT_COMMIT_MESSAGE || ''));
305
+ this.envVars.set('GIT_CLONE_COMMIT_MESSAGE_BODY', git('log -1 --format=%b', ''));
306
+ // Platform-specific built-in variables
307
+ if (stack) {
308
+ this.envVars.set('CIBUILD_STACK', stack);
309
+ }
310
+ if (platform === 'macos') {
311
+ // Extract Xcode version from stack if available
312
+ if (stack) {
313
+ const xcodeMatch = stack.match(/xcode-(\d+\.\d+)/);
314
+ if (xcodeMatch) {
315
+ this.envVars.set('CIBUILD_XCODE_VERSION', xcodeMatch[1]);
316
+ }
317
+ }
318
+ // Default Xcode scheme (can be overridden by user)
319
+ if (!this.envVars.has('CIBUILD_SCHEME')) {
320
+ this.envVars.set('CIBUILD_SCHEME', process.env.XCODE_SCHEME || 'Release');
321
+ }
322
+ }
323
+ if (platform === 'linux') {
324
+ // JAVA_HOME (can be overridden by user)
325
+ this.envVars.set('JAVA_HOME', process.env.JAVA_HOME || '/usr/lib/jvm/java-17-openjdk');
326
+ this.envVars.set('CIBUILD_GRADLE_VERSION', process.env.GRADLE_VERSION || '8.0');
327
+ }
328
+ }
329
+ /**
330
+ * Merges environment variables from YAML array format
331
+ * @param envArray Array of environment variable objects
332
+ */
333
+ mergeEnvVars(envArray) {
334
+ for (const envObj of envArray) {
335
+ for (const [key, value] of Object.entries(envObj)) {
336
+ // First resolve any secret references
337
+ const secretResolved = this.resolveSecretReference(value);
338
+ // Then interpolate in case it references other env vars
339
+ const interpolated = this.interpolateEnvValue(secretResolved);
340
+ // Don't overwrite a non-empty value (e.g. a loaded secret) with an empty
341
+ // YAML placeholder string — secrets are loaded before YAML envs and should
342
+ // survive empty placeholder declarations like `KEYSTORE_BASE64: ""`
343
+ if (interpolated === '' && this.envVars.get(key)) {
344
+ continue;
345
+ }
346
+ this.envVars.set(key, interpolated);
347
+ }
348
+ }
349
+ }
350
+ /**
351
+ * Resolves $SECRET{secret_id} references to actual secret values
352
+ * @param value Value that may contain a secret reference
353
+ * @returns Resolved value with secret content, or empty string if secret not found
354
+ */
355
+ resolveSecretReference(value) {
356
+ // Convert non-strings to strings first
357
+ if (typeof value !== 'string') {
358
+ return String(value);
359
+ }
360
+ // Check for $SECRET{secret_id} pattern
361
+ const secretMatch = value.match(/^\$SECRET\{([^}]+)\}$/);
362
+ if (!secretMatch) {
363
+ return value;
364
+ }
365
+ const secretId = secretMatch[1];
366
+ const secretValue = this.secretsManager.getSecret(secretId);
367
+ if (!secretValue) {
368
+ // Return empty string silently — the validation step will report this
369
+ return '';
370
+ }
371
+ return secretValue;
372
+ }
373
+ /**
374
+ * Interpolates environment variable value during initial setup
375
+ * This allows env vars to reference other env vars
376
+ * @param value Raw value
377
+ * @returns Interpolated value or original if interpolation fails
378
+ */
379
+ interpolateEnvValue(value) {
380
+ try {
381
+ return this.interpolate(value);
382
+ }
383
+ catch (err) {
384
+ // If interpolation fails during setup, return the raw value
385
+ // It will be interpolated later when all vars are available
386
+ return value;
387
+ }
388
+ }
389
+ }
390
+ /**
391
+ * Helper function to create an EnvResolver for a workflow
392
+ * @param pipeline Parsed YAML pipeline
393
+ * @param workflowName Name of the workflow
394
+ * @param platform Detected platform
395
+ * @param stack Optional stack identifier
396
+ * @returns EnvResolver instance
397
+ */
398
+ export function createEnvResolver(pipeline, workflowName, platform, stack) {
399
+ const workflow = pipeline.workflows[workflowName];
400
+ if (!workflow) {
401
+ throw new Error(`Workflow '${workflowName}' not found`);
402
+ }
403
+ return new EnvResolver(pipeline, workflow, workflowName, platform, stack);
404
+ }
405
+ //# sourceMappingURL=env-resolver.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for environment variable resolution
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=env-resolver.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}