@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,81 @@
1
+ /**
2
+ * Reset command — removes all files and folders created by cibuild.
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
5
+ import { resolve } from 'node:path';
6
+ import prompts from 'prompts';
7
+ /** Paths that cibuild creates, relative to project root. */
8
+ const CIBUILD_PATHS = [
9
+ '.ci',
10
+ '.cibuild-secrets.json',
11
+ '.ci-builds',
12
+ '.ci-cache',
13
+ 'ci.config.json',
14
+ ];
15
+ /** Lines that `ci init` appends to .gitignore. */
16
+ const GITIGNORE_ENTRIES = [
17
+ '.cibuild-secrets.json',
18
+ '.ci/.envstore.json',
19
+ '.ci/keys/',
20
+ ];
21
+ export async function handleResetCommand(options = {}) {
22
+ const cwd = process.cwd();
23
+ // Discover what actually exists
24
+ const existing = CIBUILD_PATHS
25
+ .map((p) => ({ rel: p, abs: resolve(cwd, p) }))
26
+ .filter(({ abs }) => existsSync(abs));
27
+ const gitignorePath = resolve(cwd, '.gitignore');
28
+ const hasGitignoreEntries = existsSync(gitignorePath) && hasAnyEntry(gitignorePath);
29
+ if (existing.length === 0 && !hasGitignoreEntries) {
30
+ console.log('Nothing to reset — no cibuild files found.');
31
+ return;
32
+ }
33
+ // Show what will be removed
34
+ console.log('\nThe following will be removed:\n');
35
+ for (const { rel } of existing) {
36
+ console.log(` • ${rel}`);
37
+ }
38
+ if (hasGitignoreEntries) {
39
+ console.log(' • cibuild entries from .gitignore');
40
+ }
41
+ console.log('');
42
+ // Confirm unless --force
43
+ if (!options.force) {
44
+ const { confirmed } = await prompts({
45
+ type: 'confirm',
46
+ name: 'confirmed',
47
+ message: 'Are you sure? This cannot be undone.',
48
+ initial: false,
49
+ });
50
+ if (!confirmed) {
51
+ console.log('\n❌ Cancelled');
52
+ return;
53
+ }
54
+ }
55
+ // Remove paths
56
+ for (const { rel, abs } of existing) {
57
+ rmSync(abs, { recursive: true, force: true });
58
+ console.log(` Removed ${rel}`);
59
+ }
60
+ // Clean .gitignore
61
+ if (hasGitignoreEntries) {
62
+ cleanGitignore(gitignorePath);
63
+ console.log(' Cleaned .gitignore');
64
+ }
65
+ console.log('\n✅ Reset complete\n');
66
+ }
67
+ function hasAnyEntry(gitignorePath) {
68
+ const content = readFileSync(gitignorePath, 'utf-8');
69
+ return GITIGNORE_ENTRIES.some((entry) => content.split('\n').includes(entry));
70
+ }
71
+ function cleanGitignore(gitignorePath) {
72
+ const lines = readFileSync(gitignorePath, 'utf-8').split('\n');
73
+ const filtered = lines.filter((line) => !GITIGNORE_ENTRIES.includes(line));
74
+ // Remove trailing blank lines left over from removal
75
+ while (filtered.length > 0 && filtered[filtered.length - 1] === '') {
76
+ filtered.pop();
77
+ }
78
+ const result = filtered.length > 0 ? filtered.join('\n') + '\n' : '';
79
+ writeFileSync(gitignorePath, result);
80
+ }
81
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Sync local .cibuild-secrets.json keys into a GitHub Actions workflow YAML.
3
+ *
4
+ * Updates the job `env:` block so the encoded secret names match exactly
5
+ * what `ci secrets upload` pushes to the GitHub environment.
6
+ *
7
+ * Also regenerates the node -e script that reconstructs .cibuild-secrets.json
8
+ * from the encoded environment variables.
9
+ */
10
+ export interface SyncWorkflowOptions {
11
+ workflowPath: string;
12
+ dryRun: boolean;
13
+ }
14
+ export declare function handleSecretsSyncWorkflowCommand(options: SyncWorkflowOptions): Promise<void>;
15
+ //# sourceMappingURL=secrets-sync-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-sync-workflow.d.ts","sourceRoot":"","sources":["../../../src/commands/secrets-sync-workflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAyFD,wBAAsB,gCAAgC,CACpD,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAsNf"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Sync local .cibuild-secrets.json keys into a GitHub Actions workflow YAML.
3
+ *
4
+ * Updates the job `env:` block so the encoded secret names match exactly
5
+ * what `ci secrets upload` pushes to the GitHub environment.
6
+ *
7
+ * Also regenerates the node -e script that reconstructs .cibuild-secrets.json
8
+ * from the encoded environment variables.
9
+ */
10
+ import * as fs from 'fs';
11
+ import { SecretsManager } from '../yaml/secrets-manager.js';
12
+ import { encodeSecretName } from './secrets-upload.js';
13
+ function collectEncodedSecrets(sm) {
14
+ const secrets = [];
15
+ // Global secrets
16
+ const globalSecrets = sm.getAll(); // no workflow → global only
17
+ for (const name of Object.keys(globalSecrets)) {
18
+ secrets.push({
19
+ encoded: encodeSecretName(name),
20
+ original: name,
21
+ });
22
+ }
23
+ // Workflow-specific secrets
24
+ const data = sm.data;
25
+ for (const [workflow, vars] of Object.entries(data.workflows)) {
26
+ for (const name of Object.keys(vars)) {
27
+ secrets.push({
28
+ encoded: encodeSecretName(name, workflow),
29
+ original: name,
30
+ workflow,
31
+ });
32
+ }
33
+ }
34
+ return secrets;
35
+ }
36
+ /**
37
+ * Build the node -e script that reconstructs .cibuild-secrets.json from env vars.
38
+ */
39
+ function buildSecretsScript(secrets) {
40
+ // Group secrets by workflow
41
+ const global = [];
42
+ const byWorkflow = new Map();
43
+ for (const s of secrets) {
44
+ if (s.workflow) {
45
+ if (!byWorkflow.has(s.workflow))
46
+ byWorkflow.set(s.workflow, []);
47
+ byWorkflow.get(s.workflow).push(s);
48
+ }
49
+ else {
50
+ global.push(s);
51
+ }
52
+ }
53
+ // Build the JSON object literal
54
+ const globalEntries = global
55
+ .map((s) => ` ...(process.env.${s.encoded} && { ${s.original}: process.env.${s.encoded} }),`)
56
+ .join('\n');
57
+ const workflowBlocks = [];
58
+ for (const [wf, wfSecrets] of byWorkflow) {
59
+ const entries = wfSecrets
60
+ .map((s) => ` ...(process.env.${s.encoded} && { ${s.original}: process.env.${s.encoded} }),`)
61
+ .join('\n');
62
+ workflowBlocks.push(` "${wf}": {\n${entries}\n }`);
63
+ }
64
+ const workflowsObj = workflowBlocks.length > 0
65
+ ? `{\n${workflowBlocks.join(',\n')}\n }`
66
+ : '{}';
67
+ const globalObj = globalEntries
68
+ ? `{\n${globalEntries}\n }`
69
+ : '{}';
70
+ return [
71
+ `node -e '`,
72
+ ` const s = JSON.stringify({`,
73
+ ` global: ${globalObj},`,
74
+ ` workflows: ${workflowsObj}`,
75
+ ` }, null, 2);`,
76
+ ` require("fs").writeFileSync(process.argv[1], s);`,
77
+ ` ' "$SECRETS_TMP"`,
78
+ ].join('\n');
79
+ }
80
+ export async function handleSecretsSyncWorkflowCommand(options) {
81
+ const { workflowPath, dryRun } = options;
82
+ if (!fs.existsSync(workflowPath)) {
83
+ console.error(`Error: Workflow file not found: ${workflowPath}`);
84
+ process.exit(1);
85
+ }
86
+ const sm = new SecretsManager();
87
+ const allNames = sm.getAllSecretNames();
88
+ if (allNames.length === 0) {
89
+ console.error('Error: No secrets found. Use "ci secrets add" to add secrets first.');
90
+ process.exit(1);
91
+ }
92
+ const encodedSecrets = collectEncodedSecrets(sm);
93
+ if (encodedSecrets.length === 0) {
94
+ console.log('No secrets to sync (all values are empty).');
95
+ return;
96
+ }
97
+ const content = fs.readFileSync(workflowPath, 'utf-8');
98
+ const lines = content.split('\n');
99
+ // --- Update env: block ---
100
+ // Find the env: block under the first job
101
+ let envBlockStart = -1;
102
+ let envBlockIndent = 0;
103
+ for (let i = 0; i < lines.length; i++) {
104
+ const match = lines[i].match(/^(\s+)env:\s*$/);
105
+ if (match) {
106
+ envBlockStart = i;
107
+ envBlockIndent = match[1].length;
108
+ break;
109
+ }
110
+ }
111
+ if (envBlockStart === -1) {
112
+ // No env: block found — insert one before the first job's `steps:` line
113
+ for (let i = 0; i < lines.length; i++) {
114
+ const stepsMatch = lines[i].match(/^(\s+)steps:\s*$/);
115
+ if (stepsMatch) {
116
+ envBlockIndent = stepsMatch[1].length;
117
+ const pad = ' '.repeat(envBlockIndent);
118
+ const entryPad = ' '.repeat(envBlockIndent + 2);
119
+ const envLine = `${pad}env:`;
120
+ const secretLines = encodedSecrets.map((s) => `${entryPad}${s.encoded}: \${{ secrets.${s.encoded} }}`);
121
+ lines.splice(i, 0, envLine, ...secretLines);
122
+ break;
123
+ }
124
+ }
125
+ const newContent = lines.join('\n');
126
+ if (dryRun) {
127
+ console.log(`\nDry run — would add env: block with ${encodedSecrets.length} secret(s) to ${workflowPath}\n`);
128
+ console.log('Run without --dry-run to apply changes.');
129
+ return;
130
+ }
131
+ fs.writeFileSync(workflowPath, newContent, 'utf-8');
132
+ console.log(`\n✅ Added env: block with ${encodedSecrets.length} secret(s) to ${workflowPath}`);
133
+ return;
134
+ }
135
+ // Find the extent of the env block (lines with indentation > envBlockIndent)
136
+ const entryIndent = envBlockIndent + 2;
137
+ let envBlockEnd = envBlockStart + 1;
138
+ while (envBlockEnd < lines.length) {
139
+ const line = lines[envBlockEnd];
140
+ // Empty lines could be inside the block or trailing — peek ahead
141
+ if (line.trim() === '') {
142
+ // Check if there's a non-empty line after this that's still in the block
143
+ let nextNonEmpty = envBlockEnd + 1;
144
+ while (nextNonEmpty < lines.length && lines[nextNonEmpty].trim() === '') {
145
+ nextNonEmpty++;
146
+ }
147
+ if (nextNonEmpty < lines.length) {
148
+ const nextIndent = lines[nextNonEmpty].match(/^(\s*)/)?.[1].length ?? 0;
149
+ if (nextIndent > envBlockIndent) {
150
+ // Still inside the block — include this blank line
151
+ envBlockEnd++;
152
+ continue;
153
+ }
154
+ }
155
+ // Trailing blank line(s) — don't consume them
156
+ break;
157
+ }
158
+ // Check indentation — if it's at or below the env: level, we're past the block
159
+ const lineIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
160
+ if (lineIndent <= envBlockIndent)
161
+ break;
162
+ envBlockEnd++;
163
+ }
164
+ // Build set of original secret names to remove old non-encoded entries
165
+ const originalSecretNames = new Set(encodedSecrets.map((s) => s.original));
166
+ // Collect existing non-secret env entries (keep them, including blank lines)
167
+ const existingEntries = [];
168
+ for (let i = envBlockStart + 1; i < envBlockEnd; i++) {
169
+ const line = lines[i];
170
+ // Preserve blank lines between entries
171
+ if (line.trim() === '') {
172
+ existingEntries.push(line);
173
+ continue;
174
+ }
175
+ // Remove old CIBUILD_S__ / CIBUILD_SW__ entries (we'll regenerate them)
176
+ if (line.match(/^\s+CIBUILD_S(W)?__/))
177
+ continue;
178
+ // Remove old non-encoded entries that match a known secret name
179
+ const keyMatch = line.match(/^\s+(\w+):\s/);
180
+ if (keyMatch && originalSecretNames.has(keyMatch[1]))
181
+ continue;
182
+ existingEntries.push(line);
183
+ }
184
+ // Trim trailing blank lines from existing entries (we add a clean separator)
185
+ while (existingEntries.length > 0 && existingEntries[existingEntries.length - 1].trim() === '') {
186
+ existingEntries.pop();
187
+ }
188
+ // Build new secret entries
189
+ const pad = ' '.repeat(entryIndent);
190
+ const newSecretLines = encodedSecrets.map((s) => `${pad}${s.encoded}: \${{ secrets.${s.encoded} }}`);
191
+ // Reassemble
192
+ const newEnvBlock = [
193
+ lines[envBlockStart], // " env:"
194
+ ...existingEntries,
195
+ ...newSecretLines,
196
+ ];
197
+ // --- Update node -e script ---
198
+ // Find the node -e block that builds .cibuild-secrets.json
199
+ let nodeScriptStart = -1;
200
+ let nodeScriptEnd = -1;
201
+ for (let i = 0; i < lines.length; i++) {
202
+ if (lines[i].match(/node\s+-e\s+'/)) {
203
+ nodeScriptStart = i;
204
+ }
205
+ if (nodeScriptStart !== -1 && nodeScriptEnd === -1 && lines[i].match(/'\s+"\$SECRETS_TMP"/)) {
206
+ nodeScriptEnd = i + 1;
207
+ break;
208
+ }
209
+ }
210
+ // Splice the file back together
211
+ const result = [];
212
+ for (let i = 0; i < lines.length; i++) {
213
+ if (i >= envBlockStart && i < envBlockEnd) {
214
+ if (i === envBlockStart) {
215
+ result.push(...newEnvBlock);
216
+ }
217
+ // skip old lines
218
+ continue;
219
+ }
220
+ if (nodeScriptStart !== -1 && i >= nodeScriptStart && i < nodeScriptEnd) {
221
+ if (i === nodeScriptStart) {
222
+ // Rebuild node script with encoded env var names
223
+ result.push(`${' '.repeat(10)}${buildSecretsScript(encodedSecrets)}`);
224
+ }
225
+ continue;
226
+ }
227
+ result.push(lines[i]);
228
+ }
229
+ const newContent = result.join('\n');
230
+ if (dryRun) {
231
+ console.log(`\nDry run — would sync ${encodedSecrets.length} secret(s) to ${workflowPath}:\n`);
232
+ for (const s of encodedSecrets) {
233
+ const label = s.workflow
234
+ ? `workflow:${s.workflow} → ${s.original}`
235
+ : `global → ${s.original}`;
236
+ console.log(` ${label} → ${s.encoded}`);
237
+ }
238
+ console.log(`\nEnv block preview:\n`);
239
+ for (const line of newEnvBlock) {
240
+ console.log(line);
241
+ }
242
+ console.log('\nRun without --dry-run to apply changes.');
243
+ return;
244
+ }
245
+ fs.writeFileSync(workflowPath, newContent, 'utf-8');
246
+ console.log(`\n✅ Synced ${encodedSecrets.length} secret(s) to ${workflowPath}\n`);
247
+ for (const s of encodedSecrets) {
248
+ const label = s.workflow
249
+ ? `workflow:${s.workflow} → ${s.original}`
250
+ : `global → ${s.original}`;
251
+ console.log(` ${label} → ${s.encoded}`);
252
+ }
253
+ console.log('');
254
+ }
255
+ //# sourceMappingURL=secrets-sync-workflow.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Upload local .cibuild-secrets.json to GitHub Secrets (in a GitHub environment).
3
+ *
4
+ * Encoding scheme:
5
+ * global secret "VAR" → CIBUILD_S__VAR
6
+ * workflow "my-wf" secret "VAR" → CIBUILD_SW__MY_WF__VAR
7
+ *
8
+ * Uses `gh` CLI for all GitHub API interactions.
9
+ */
10
+ export declare function encodeSecretName(varName: string, workflow?: string): string;
11
+ export declare function decodeSecretName(encoded: string): {
12
+ varName: string;
13
+ workflow?: string;
14
+ } | null;
15
+ export interface SecretsUploadOptions {
16
+ envName: string;
17
+ repo?: string;
18
+ dryRun: boolean;
19
+ }
20
+ export declare function handleSecretsUploadCommand(options: SecretsUploadOptions): Promise<void>;
21
+ //# sourceMappingURL=secrets-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-upload.d.ts","sourceRoot":"","sources":["../../../src/commands/secrets-upload.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiB/F;AAwDD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqF7F"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Upload local .cibuild-secrets.json to GitHub Secrets (in a GitHub environment).
3
+ *
4
+ * Encoding scheme:
5
+ * global secret "VAR" → CIBUILD_S__VAR
6
+ * workflow "my-wf" secret "VAR" → CIBUILD_SW__MY_WF__VAR
7
+ *
8
+ * Uses `gh` CLI for all GitHub API interactions.
9
+ */
10
+ import { execSync } from 'child_process';
11
+ import { existsSync } from 'fs';
12
+ import { resolve } from 'path';
13
+ import { SecretsManager } from '../yaml/secrets-manager.js';
14
+ import { handleSecretsSyncWorkflowCommand } from './secrets-sync-workflow.js';
15
+ const GLOBAL_PREFIX = 'CIBUILD_S__';
16
+ const WORKFLOW_PREFIX = 'CIBUILD_SW__';
17
+ export function encodeSecretName(varName, workflow) {
18
+ if (workflow) {
19
+ const encodedWorkflow = workflow.toUpperCase().replace(/-/g, '_');
20
+ return `${WORKFLOW_PREFIX}${encodedWorkflow}__${varName}`;
21
+ }
22
+ return `${GLOBAL_PREFIX}${varName}`;
23
+ }
24
+ export function decodeSecretName(encoded) {
25
+ if (encoded.startsWith(WORKFLOW_PREFIX)) {
26
+ const rest = encoded.slice(WORKFLOW_PREFIX.length);
27
+ const sepIdx = rest.indexOf('__');
28
+ if (sepIdx === -1)
29
+ return null;
30
+ const encodedWorkflow = rest.slice(0, sepIdx);
31
+ const varName = rest.slice(sepIdx + 2);
32
+ if (!varName)
33
+ return null;
34
+ const workflow = encodedWorkflow.toLowerCase().replace(/_/g, '-');
35
+ return { varName, workflow };
36
+ }
37
+ if (encoded.startsWith(GLOBAL_PREFIX)) {
38
+ const varName = encoded.slice(GLOBAL_PREFIX.length);
39
+ if (!varName)
40
+ return null;
41
+ return { varName };
42
+ }
43
+ return null;
44
+ }
45
+ function ensureGhCli() {
46
+ try {
47
+ execSync('gh --version', { stdio: 'pipe' });
48
+ }
49
+ catch {
50
+ console.error('Error: GitHub CLI (gh) is required.');
51
+ console.error(' macOS: brew install gh');
52
+ console.error(' Other: https://cli.github.com/');
53
+ process.exit(1);
54
+ }
55
+ try {
56
+ execSync('gh auth status', { stdio: 'pipe' });
57
+ }
58
+ catch {
59
+ console.error('Error: Not authenticated with GitHub CLI. Run "gh auth login" first.');
60
+ process.exit(1);
61
+ }
62
+ }
63
+ function getRepo(repoArg) {
64
+ if (repoArg)
65
+ return repoArg;
66
+ try {
67
+ const result = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {
68
+ stdio: ['pipe', 'pipe', 'pipe'],
69
+ encoding: 'utf-8',
70
+ });
71
+ return result.trim();
72
+ }
73
+ catch {
74
+ console.error('Error: Could not detect repository. Use --repo <owner/repo> or run from a git repo.');
75
+ process.exit(1);
76
+ }
77
+ }
78
+ function ensureEnvironment(repo, envName) {
79
+ try {
80
+ execSync(`gh api -X PUT repos/${repo}/environments/${envName}`, { stdio: 'pipe' });
81
+ }
82
+ catch (err) {
83
+ console.error(`Error: Could not create environment "${envName}". Check that you have admin access to ${repo}.`);
84
+ if (err instanceof Error)
85
+ console.error(err.message);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ function uploadSecret(repo, envName, name, value) {
90
+ try {
91
+ execSync(`gh secret set ${name} --env ${envName} --repo ${repo}`, {
92
+ input: value,
93
+ stdio: ['pipe', 'pipe', 'pipe'],
94
+ });
95
+ return true;
96
+ }
97
+ catch (err) {
98
+ console.error(` Failed to upload "${name}": ${err instanceof Error ? err.message : String(err)}`);
99
+ return false;
100
+ }
101
+ }
102
+ export async function handleSecretsUploadCommand(options) {
103
+ const { envName, dryRun } = options;
104
+ if (!dryRun) {
105
+ ensureGhCli();
106
+ }
107
+ const sm = new SecretsManager();
108
+ const allNames = sm.getAllSecretNames();
109
+ if (allNames.length === 0) {
110
+ console.error('Error: No secrets found. Use "ci secrets add" to add secrets first.');
111
+ process.exit(1);
112
+ }
113
+ // Build list of encoded secrets to upload
114
+ const toUpload = [];
115
+ // Global secrets
116
+ const globalSecrets = sm.getAll(); // no workflow → global only
117
+ for (const [name, value] of Object.entries(globalSecrets)) {
118
+ if (!value)
119
+ continue;
120
+ toUpload.push({
121
+ encoded: encodeSecretName(name),
122
+ value,
123
+ label: `global → ${name}`,
124
+ });
125
+ }
126
+ // Workflow-specific secrets (only those that differ from global)
127
+ const data = sm.data;
128
+ for (const [workflow, vars] of Object.entries(data.workflows)) {
129
+ if (workflow.includes('_')) {
130
+ console.warn(` Warning: Workflow "${workflow}" contains underscores, which may cause ambiguity when decoding.`);
131
+ }
132
+ for (const [name, value] of Object.entries(vars)) {
133
+ if (!value)
134
+ continue;
135
+ toUpload.push({
136
+ encoded: encodeSecretName(name, workflow),
137
+ value,
138
+ label: `workflow:${workflow} → ${name}`,
139
+ });
140
+ }
141
+ }
142
+ if (toUpload.length === 0) {
143
+ console.log('No secrets to upload (all values are empty).');
144
+ return;
145
+ }
146
+ const repo = dryRun ? (options.repo || '<auto-detected>') : getRepo(options.repo);
147
+ if (dryRun) {
148
+ console.log(`\nDry run — would upload ${toUpload.length} secret(s) to environment "${envName}" in ${repo}:\n`);
149
+ for (const s of toUpload) {
150
+ console.log(` ${s.label} → ${s.encoded}`);
151
+ }
152
+ console.log('\nRun without --dry-run to upload.');
153
+ return;
154
+ }
155
+ console.log(`\nUploading ${toUpload.length} secret(s) to environment "${envName}" in ${repo}...\n`);
156
+ ensureEnvironment(repo, envName);
157
+ let failed = 0;
158
+ for (const s of toUpload) {
159
+ process.stdout.write(` ${s.label} → ${s.encoded} ... `);
160
+ if (uploadSecret(repo, envName, s.encoded, s.value)) {
161
+ console.log('✓');
162
+ }
163
+ else {
164
+ failed++;
165
+ }
166
+ }
167
+ console.log(`\nDone. ${toUpload.length - failed}/${toUpload.length} secrets uploaded.`);
168
+ if (failed > 0) {
169
+ process.exit(1);
170
+ }
171
+ // Auto-sync workflow if .github/workflows/ci.yml exists
172
+ const workflowPath = resolve(process.cwd(), '.github', 'workflows', 'ci.yml');
173
+ if (existsSync(workflowPath)) {
174
+ await handleSecretsSyncWorkflowCommand({ workflowPath, dryRun: false });
175
+ }
176
+ }
177
+ //# sourceMappingURL=secrets-upload.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for secrets-upload encode/decode and SecretsManager structured env var reading
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=secrets-upload.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-upload.test.d.ts","sourceRoot":"","sources":["../../../src/commands/secrets-upload.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Tests for secrets-upload encode/decode and SecretsManager structured env var reading
3
+ */
4
+ import { describe, test, expect } from '@jest/globals';
5
+ import { encodeSecretName, decodeSecretName } from './secrets-upload.js';
6
+ describe('secrets-upload encoding', () => {
7
+ describe('encodeSecretName', () => {
8
+ test('should encode global secret', () => {
9
+ expect(encodeSecretName('SLACK_WEBHOOK')).toBe('CIBUILD_S__SLACK_WEBHOOK');
10
+ });
11
+ test('should encode workflow secret', () => {
12
+ expect(encodeSecretName('KEYSTORE_PASS', 'release')).toBe('CIBUILD_SW__RELEASE__KEYSTORE_PASS');
13
+ });
14
+ test('should encode workflow with hyphens', () => {
15
+ expect(encodeSecretName('API_KEY', 'staging-java-17')).toBe('CIBUILD_SW__STAGING_JAVA_17__API_KEY');
16
+ });
17
+ });
18
+ describe('decodeSecretName', () => {
19
+ test('should decode global secret', () => {
20
+ const result = decodeSecretName('CIBUILD_S__SLACK_WEBHOOK');
21
+ expect(result).toEqual({ varName: 'SLACK_WEBHOOK' });
22
+ });
23
+ test('should decode workflow secret', () => {
24
+ const result = decodeSecretName('CIBUILD_SW__RELEASE__KEYSTORE_PASS');
25
+ expect(result).toEqual({ varName: 'KEYSTORE_PASS', workflow: 'release' });
26
+ });
27
+ test('should decode workflow with hyphens (converted from underscores)', () => {
28
+ const result = decodeSecretName('CIBUILD_SW__STAGING_JAVA_17__API_KEY');
29
+ expect(result).toEqual({ varName: 'API_KEY', workflow: 'staging-java-17' });
30
+ });
31
+ test('should return null for unrelated env var', () => {
32
+ expect(decodeSecretName('HOME')).toBeNull();
33
+ expect(decodeSecretName('PATH')).toBeNull();
34
+ expect(decodeSecretName('CIBUILD_GIT_BRANCH')).toBeNull();
35
+ });
36
+ test('should return null for malformed prefix-only keys', () => {
37
+ expect(decodeSecretName('CIBUILD_S__')).toBeNull();
38
+ expect(decodeSecretName('CIBUILD_SW__')).toBeNull();
39
+ expect(decodeSecretName('CIBUILD_SW__RELEASE')).toBeNull();
40
+ });
41
+ });
42
+ describe('roundtrip encode/decode', () => {
43
+ test('global secret roundtrips correctly', () => {
44
+ const encoded = encodeSecretName('MY_SECRET');
45
+ const decoded = decodeSecretName(encoded);
46
+ expect(decoded).toEqual({ varName: 'MY_SECRET' });
47
+ });
48
+ test('workflow secret roundtrips correctly', () => {
49
+ const encoded = encodeSecretName('MY_SECRET', 'my-workflow');
50
+ const decoded = decodeSecretName(encoded);
51
+ expect(decoded).toEqual({ varName: 'MY_SECRET', workflow: 'my-workflow' });
52
+ });
53
+ test('workflow with multiple hyphens roundtrips correctly', () => {
54
+ const encoded = encodeSecretName('DB_PASS', 'staging-java-17');
55
+ const decoded = decodeSecretName(encoded);
56
+ expect(decoded).toEqual({ varName: 'DB_PASS', workflow: 'staging-java-17' });
57
+ });
58
+ });
59
+ });
60
+ //# sourceMappingURL=secrets-upload.test.js.map
@@ -0,0 +1,3 @@
1
+ import type { CIConfig } from "./types.js";
2
+ export declare function loadConfig(configPath?: string): CIConfig;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAkB3C,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CA6BxD"}