@jenga-ai/agent 1.0.0

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 (177) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +340 -0
  3. package/agents/ai_engineer.md +113 -0
  4. package/agents/developer.md +236 -0
  5. package/agents/scrum-master.md +349 -0
  6. package/agents/scrutiny-agent.md +137 -0
  7. package/agents/solution-assessor.md +185 -0
  8. package/agents/tester.md +339 -0
  9. package/bin/jenga.js +70 -0
  10. package/hooks/copilot_session_end.sh +29 -0
  11. package/hooks/on_session_end.sh +238 -0
  12. package/hooks/prompt_router.sh +11 -0
  13. package/hooks/prompt_router_helper.js +52 -0
  14. package/hooks/session_end_helper.js +29 -0
  15. package/hooks/session_end_watcher.sh +24 -0
  16. package/lib/commands/attach.js +47 -0
  17. package/lib/commands/init.js +207 -0
  18. package/lib/commands/start.js +16 -0
  19. package/lib/commands/status.js +53 -0
  20. package/lib/config-schema.js +72 -0
  21. package/lib/inject-settings.js +61 -0
  22. package/lib/mirror.js +244 -0
  23. package/lib/resolve-project-dir.sh +47 -0
  24. package/mcp/execute-ticket/index.js +10 -0
  25. package/mcp/execute-ticket/package.json +5 -0
  26. package/mcp/help/index.js +79 -0
  27. package/mcp/help/package.json +14 -0
  28. package/mcp/router/README.md +19 -0
  29. package/mcp/router/embedder.js +23 -0
  30. package/mcp/router/index.js +204 -0
  31. package/mcp/router/matcher.js +87 -0
  32. package/mcp/router/package-lock.json +1048 -0
  33. package/mcp/router/package.json +11 -0
  34. package/mcp/router/skill-index.js +104 -0
  35. package/package.json +47 -0
  36. package/scripts/board_resolver.sh +46 -0
  37. package/scripts/e25_s01_extract_board_graph.py +292 -0
  38. package/scripts/e25_s01_generate_synthetic_board.py +90 -0
  39. package/scripts/measurement-10x.json +50 -0
  40. package/scripts/measurement-10x.txt +4 -0
  41. package/scripts/measurement-real.json +50 -0
  42. package/scripts/measurement-real.txt +4 -0
  43. package/scripts/postinstall.js +165 -0
  44. package/scripts/todo_cleanup.sh +22 -0
  45. package/scripts/todo_manager.sh +86 -0
  46. package/scripts/validate-board.sh +190 -0
  47. package/scripts/validate-story-format.sh +53 -0
  48. package/skills/brainstorm/SKILL.md +47 -0
  49. package/skills/btw/SKILL.md +42 -0
  50. package/skills/commit/SKILL.md +29 -0
  51. package/skills/commit/assets/user_instructions_template.md +22 -0
  52. package/skills/continue/SKILL.md +29 -0
  53. package/skills/convert/SKILL.md +124 -0
  54. package/skills/convert/convert_cli.py +235 -0
  55. package/skills/convert/tests/sample.csv +4 -0
  56. package/skills/convert/tests/sample.json +5 -0
  57. package/skills/convert/tests/sample.jsonl +3 -0
  58. package/skills/convert/tests/sample.yaml +18 -0
  59. package/skills/convert/tests/sample_obj.csv +2 -0
  60. package/skills/convert/tests/sample_obj.json +9 -0
  61. package/skills/deep-dive/SKILL.md +167 -0
  62. package/skills/do/SKILL.md +88 -0
  63. package/skills/do/assets/sender_template.json +12 -0
  64. package/skills/doc/SKILL.md +314 -0
  65. package/skills/doc/assets/path-objectives.yaml +38 -0
  66. package/skills/doc-sync/SKILL.md +167 -0
  67. package/skills/doc-sync/assets/default_excludes.txt +21 -0
  68. package/skills/doc-sync/assets/doc_targets.md +14 -0
  69. package/skills/dooo/SKILL.md +60 -0
  70. package/skills/error/SKILL.md +29 -0
  71. package/skills/evaluate/SKILL.md +45 -0
  72. package/skills/evaluate/assets/evaluation_invokation_template.yml +3 -0
  73. package/skills/evaluate/assets/evaluation_rapport_template.md +24 -0
  74. package/skills/examplify/SKILL.md +42 -0
  75. package/skills/help/SKILL.md +36 -0
  76. package/skills/improve/SKILL.md +55 -0
  77. package/skills/index/scripts/board-index +4 -0
  78. package/skills/index/scripts/board_index.py +615 -0
  79. package/skills/index/scripts/smoke_test.sh +86 -0
  80. package/skills/init/SKILL.md +44 -0
  81. package/skills/init/assets/.gitignore_template +15 -0
  82. package/skills/init/assets/PROJECT_SUMMARY_template.md +13 -0
  83. package/skills/init/assets/directory_structure.txt +13 -0
  84. package/skills/init/assets/test-config_template.json +4 -0
  85. package/skills/init/assets/workflow_template.json +30 -0
  86. package/skills/init/scripts/init.sh +48 -0
  87. package/skills/jbp/SKILL.md +25 -0
  88. package/skills/jenga/SKILL.md +68 -0
  89. package/skills/lgtm/SKILL.md +21 -0
  90. package/skills/mirror-public/SKILL.md +237 -0
  91. package/skills/mirror-public/assets/config.json +5 -0
  92. package/skills/mirror-public/scripts/mirror.sh +374 -0
  93. package/skills/pi-plan/SKILL.md +62 -0
  94. package/skills/pi-plan/assets/epic.json +7 -0
  95. package/skills/pi-plan/assets/story_template.md +18 -0
  96. package/skills/proceed/SKILL.md +29 -0
  97. package/skills/publish/SKILL.md +351 -0
  98. package/skills/publish/adapters/droplet.md +200 -0
  99. package/skills/publish/adapters/mobile-ios.md +114 -0
  100. package/skills/publish/adapters/npm-ci.md +223 -0
  101. package/skills/publish/adapters/npm.md +121 -0
  102. package/skills/publish/assets/ExportOptions.plist.template +19 -0
  103. package/skills/publish/assets/ci-contract.md +111 -0
  104. package/skills/publish/assets/ownership-matrix.md +17 -0
  105. package/skills/publish/assets/publish.example.json +85 -0
  106. package/skills/publish/assets/publish.example.npm-ci.json +40 -0
  107. package/skills/publish/assets/publish.example.npm.json +41 -0
  108. package/skills/publish/assets/secrets-guide.md +104 -0
  109. package/skills/publish/schemas/fixtures/npm-ci-minimal.json +17 -0
  110. package/skills/publish/schemas/fixtures/npm-ci-with-empty-secrets.json +18 -0
  111. package/skills/publish/schemas/fixtures/npm-ci-with-workflow-path.json +18 -0
  112. package/skills/publish/schemas/publish.schema.json +428 -0
  113. package/skills/publish/scripts/check_target_config.sh +96 -0
  114. package/skills/publish/scripts/droplet_pipeline.sh +208 -0
  115. package/skills/publish/scripts/generate_release_notes.sh +200 -0
  116. package/skills/publish/scripts/ios_pipeline.sh +486 -0
  117. package/skills/publish/scripts/npm_ci_pipeline.sh +225 -0
  118. package/skills/publish/scripts/npm_pipeline.sh +249 -0
  119. package/skills/publish/scripts/publish_common.sh +253 -0
  120. package/skills/publish/scripts/publish_deploy.sh +538 -0
  121. package/skills/publish/scripts/reconcile_tags.sh +135 -0
  122. package/skills/publish/scripts/run_gates.sh +616 -0
  123. package/skills/publish/scripts/setup_wizard.sh +394 -0
  124. package/skills/publish/scripts/show_history.sh +95 -0
  125. package/skills/publish/scripts/suggest_semver_bump.sh +105 -0
  126. package/skills/publish/scripts/validate_config.sh +163 -0
  127. package/skills/publish/scripts/validate_droplet_env.sh +45 -0
  128. package/skills/publish/scripts/validate_ios_env.sh +68 -0
  129. package/skills/publish/scripts/validate_npm_ci_env.sh +71 -0
  130. package/skills/publish/scripts/validate_npm_env.sh +22 -0
  131. package/skills/publish/scripts/write_ledger_entry.sh +126 -0
  132. package/skills/publish/wizards/droplet.md +275 -0
  133. package/skills/publish/wizards/mobile-ios.md +157 -0
  134. package/skills/publish/wizards/npm-ci.md +240 -0
  135. package/skills/publish/wizards/npm.md +224 -0
  136. package/skills/reconcile/SKILL.md +93 -0
  137. package/skills/reconcile/assets/report_format.md +44 -0
  138. package/skills/reconcile-origin/SKILL.md +75 -0
  139. package/skills/reconcile-origin/scripts/reconcile-origin.sh +372 -0
  140. package/skills/redo/SKILL.md +70 -0
  141. package/skills/route/SKILL.md +180 -0
  142. package/skills/self-sync/SKILL.md +73 -0
  143. package/skills/self-sync/scripts/run.js +136 -0
  144. package/skills/skillify/SKILL.md +68 -0
  145. package/skills/skillify/assets/init-new/SKILL.md +35 -0
  146. package/skills/skillify/assets/init-new/assets/.gitignore_template +15 -0
  147. package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +13 -0
  148. package/skills/skillify/assets/init-new/assets/directory_structure.txt +10 -0
  149. package/skills/skillify/assets/init-new/assets/test-config_template.json +4 -0
  150. package/skills/skillify/assets/init-new/assets/workflow_template.json +17 -0
  151. package/skills/skillify/assets/init-new/scripts/init.sh +48 -0
  152. package/skills/skillify/assets/init-old/SKILL.md +124 -0
  153. package/skills/spinoff/SKILL.md +48 -0
  154. package/skills/status/SKILL.md +33 -0
  155. package/skills/status/assets/output_format.md +41 -0
  156. package/skills/todo/SKILL.md +46 -0
  157. package/skills/todo/assets/todo_handoff_template.md +22 -0
  158. package/skills/todo/assets/todo_template.md +3 -0
  159. package/skills/train/SKILL.md +116 -0
  160. package/skills/train/assets/dashboard-templates/classifiers.html +106 -0
  161. package/skills/train/assets/dashboard-templates/nlp.html +102 -0
  162. package/skills/train/assets/dashboard-templates/transformers.html +98 -0
  163. package/skills/train/assets/results-parsers/__init__.py +9 -0
  164. package/skills/train/assets/results-parsers/classifiers.py +84 -0
  165. package/skills/train/assets/results-parsers/nlp.py +88 -0
  166. package/skills/train/assets/results-parsers/reporter.py +154 -0
  167. package/skills/train/assets/results-parsers/transformers.py +120 -0
  168. package/skills/train/train_cli.py +786 -0
  169. package/templates/EXECUTION_PLAN_TEMPLATE.md +43 -0
  170. package/templates/EXECUTION_SUMMARY_TEMPLATE.md +50 -0
  171. package/templates/JENGA_CONFIG_TEMPLATE.json +23 -0
  172. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +88 -0
  173. package/templates/SCRUM_BOARD_SCHEMA.md +311 -0
  174. package/templates/SKILL.md +16 -0
  175. package/templates/SKILL_TEMPLATE.md +28 -0
  176. package/templates/USER_INSTRUCTIONS_TEMPLATE.md +22 -0
  177. package/templates/copilot-instructions.md.tpl +55 -0
@@ -0,0 +1,486 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXIT_DEPLOY_FAILURE=3
5
+ EXIT_VALIDATION_FAILURE=4
6
+
7
+ usage() {
8
+ cat <<'USAGE'
9
+ Usage: ios_pipeline.sh <target> <publish_json_path> [--dry-run] [--non-interactive]
10
+ USAGE
11
+ }
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ if REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null)"; then
15
+ :
16
+ else
17
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
18
+ fi
19
+ VALIDATE_CONFIG_SCRIPT="$SCRIPT_DIR/validate_config.sh"
20
+ VALIDATE_ENV_SCRIPT="$SCRIPT_DIR/validate_ios_env.sh"
21
+ EXPORT_TEMPLATE="$SCRIPT_DIR/../assets/ExportOptions.plist.template"
22
+ HISTORY_FILE=""
23
+ HISTORY_LOCK=""
24
+ RUN_ID=""
25
+ RUN_STARTED_AT=""
26
+ SCRATCH_DIR=""
27
+ EXPORT_OPTIONS_PATH=""
28
+ RUN_OUTPUT=""
29
+ RUN_STATUS=0
30
+ LAST_COMMAND=""
31
+ APP_DISPLAY_NAME=""
32
+ TARGET_JSON=""
33
+ EXPORT_METHOD=""
34
+ BUNDLE_ID=""
35
+ TEAM_ID=""
36
+ ARCHIVE_PATH=""
37
+ EXPORT_PATH=""
38
+ SCHEME=""
39
+ CONFIGURATION=""
40
+ PROJECT_PATH=""
41
+ WORKSPACE_PATH=""
42
+ DESTINATION=""
43
+ SDK=""
44
+ APP_STORE_APP_ID=""
45
+ PROVIDER_SHORT_NAME=""
46
+ API_KEY_ID=""
47
+ ISSUER_ID=""
48
+ PRIVATE_KEY_PATH=""
49
+ CODE_SIGN_IDENTITY_VALUE=""
50
+ PROVISIONING_PROFILE_UUID_VALUE=""
51
+ SELECTED_UPLOAD_TOOL=""
52
+ IPA_PATH=""
53
+
54
+ cleanup() {
55
+ if [[ -n "${SCRATCH_DIR:-}" && -d "$SCRATCH_DIR" ]]; then
56
+ rm -rf "$SCRATCH_DIR"
57
+ fi
58
+ }
59
+ trap cleanup EXIT
60
+
61
+ if [[ $# -lt 2 || $# -gt 4 ]]; then
62
+ usage >&2
63
+ exit 1
64
+ fi
65
+
66
+ TARGET_NAME="$1"
67
+ PUBLISH_JSON="$2"
68
+ DRY_RUN=0
69
+ NON_INTERACTIVE=0
70
+ shift 2
71
+
72
+ for arg in "$@"; do
73
+ case "$arg" in
74
+ --dry-run) DRY_RUN=1 ;;
75
+ --non-interactive) NON_INTERACTIVE=1 ;;
76
+ *) usage >&2; exit 1 ;;
77
+ esac
78
+ done
79
+
80
+ if [[ ! -t 0 ]]; then
81
+ NON_INTERACTIVE=1
82
+ fi
83
+
84
+ fail_validation() {
85
+ printf 'iOS pipeline validation failed: %s\n' "$1" >&2
86
+ exit "$EXIT_VALIDATION_FAILURE"
87
+ }
88
+
89
+ fail_deploy() {
90
+ printf '%s\n' "$1" >&2
91
+ exit "$EXIT_DEPLOY_FAILURE"
92
+ }
93
+
94
+ log_info() {
95
+ printf '→ %s\n' "$1"
96
+ }
97
+
98
+ log_warn() {
99
+ printf '⚠ %s\n' "$1" >&2
100
+ }
101
+
102
+ print_manual_steps() {
103
+ cat <<EOF2
104
+ ✅ Upload complete. Next manual steps:
105
+ 1. Go to App Store Connect → Apps → [$APP_DISPLAY_NAME] → TestFlight (or App Store)
106
+ 2. Find the new build and submit for review / release
107
+ 3. Set release notes if required
108
+ EOF2
109
+ }
110
+
111
+ stat_mtime() {
112
+ if [[ -e "$1" ]]; then
113
+ stat -f %m "$1" 2>/dev/null || echo 0
114
+ else
115
+ echo 0
116
+ fi
117
+ }
118
+
119
+ acquire_lock() {
120
+ local lock_path="$1"
121
+ local now mtime
122
+ now="$(date +%s)"
123
+
124
+ if [[ -e "$lock_path" ]]; then
125
+ mtime="$(stat_mtime "$lock_path")"
126
+ if (( now - mtime < 60 )); then
127
+ sleep 10
128
+ now="$(date +%s)"
129
+ mtime="$(stat_mtime "$lock_path")"
130
+ if [[ -e "$lock_path" ]] && (( now - mtime < 60 )); then
131
+ fail_deploy "Could not acquire lock for $lock_path"
132
+ fi
133
+ fi
134
+ rm -f "$lock_path"
135
+ fi
136
+
137
+ if ! ( set -o noclobber; : > "$lock_path" ) 2>/dev/null; then
138
+ fail_deploy "Could not create lock file $lock_path"
139
+ fi
140
+ }
141
+
142
+ release_lock() {
143
+ rm -f "$1"
144
+ }
145
+
146
+ extract_env_name() {
147
+ local ref="$1"
148
+ ref="${ref#\$\{}"
149
+ ref="${ref#\$}"
150
+ ref="${ref%\}}"
151
+ printf '%s' "$ref"
152
+ }
153
+
154
+ resolve_env_value_from_ref() {
155
+ local ref="$1"
156
+ local var_name
157
+ var_name="$(extract_env_name "$ref")"
158
+ printenv "$var_name" 2>/dev/null || true
159
+ }
160
+
161
+ ensure_history_file() {
162
+ mkdir -p "$(dirname "$HISTORY_FILE")"
163
+ acquire_lock "$HISTORY_LOCK"
164
+ if [[ ! -f "$HISTORY_FILE" ]]; then
165
+ echo '[]' > "$HISTORY_FILE"
166
+ fi
167
+ release_lock "$HISTORY_LOCK"
168
+ }
169
+
170
+ write_history_jq() {
171
+ local filter="$1"
172
+ shift
173
+ local tmp_file="$HISTORY_FILE.tmp.$$"
174
+ acquire_lock "$HISTORY_LOCK"
175
+ if jq "$@" "$filter" "$HISTORY_FILE" > "$tmp_file"; then
176
+ mv "$tmp_file" "$HISTORY_FILE"
177
+ release_lock "$HISTORY_LOCK"
178
+ return 0
179
+ fi
180
+ rm -f "$tmp_file"
181
+ release_lock "$HISTORY_LOCK"
182
+ fail_deploy "Failed to update publish history at $HISTORY_FILE"
183
+ }
184
+
185
+ append_history_entry() {
186
+ local entry
187
+ entry="$(jq -cn \
188
+ --arg id "$RUN_ID" \
189
+ --arg target "$TARGET_NAME" \
190
+ --arg started_at "$RUN_STARTED_AT" \
191
+ --arg export_method "$EXPORT_METHOD" \
192
+ '{id:$id,target:$target,adapter:"mobile-ios",state:"building",started_at:$started_at,completed_at:null,export_method:$export_method,steps:[]}')"
193
+ write_history_jq '. += [$entry]' --argjson entry "$entry"
194
+ }
195
+
196
+ append_step() {
197
+ local step_name="$1"
198
+ local step_status="$2"
199
+ local step_timestamp
200
+ step_timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
201
+ write_history_jq 'map(if .id == $id then .steps += [{step:$step,status:$status,timestamp:$timestamp}] else . end)' \
202
+ --arg id "$RUN_ID" \
203
+ --arg step "$step_name" \
204
+ --arg status "$step_status" \
205
+ --arg timestamp "$step_timestamp"
206
+ }
207
+
208
+ update_state() {
209
+ local new_state="$1"
210
+ write_history_jq 'map(if .id == $id then .state = $state else . end)' \
211
+ --arg id "$RUN_ID" \
212
+ --arg state "$new_state"
213
+ }
214
+
215
+ complete_history() {
216
+ local final_state="$1"
217
+ local completed_at
218
+ completed_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
219
+ write_history_jq 'map(if .id == $id then .state = $state | .completed_at = $completed_at else . end)' \
220
+ --arg id "$RUN_ID" \
221
+ --arg state "$final_state" \
222
+ --arg completed_at "$completed_at"
223
+ }
224
+
225
+ print_command() {
226
+ printf '[DRY RUN]'
227
+ while (($#)); do
228
+ printf ' %q' "$1"
229
+ shift
230
+ done
231
+ printf '\n'
232
+ }
233
+
234
+ run_command() {
235
+ LAST_COMMAND=""
236
+ local cmd=("$@")
237
+ local rendered=""
238
+ local arg
239
+ for arg in "${cmd[@]}"; do
240
+ rendered+=" $(printf '%q' "$arg")"
241
+ done
242
+ LAST_COMMAND="${rendered# }"
243
+
244
+ if (( DRY_RUN )); then
245
+ print_command "${cmd[@]}"
246
+ RUN_OUTPUT=""
247
+ RUN_STATUS=0
248
+ return 0
249
+ fi
250
+
251
+ if RUN_OUTPUT="$("${cmd[@]}" 2>&1)"; then
252
+ RUN_STATUS=0
253
+ return 0
254
+ fi
255
+
256
+ RUN_STATUS=$?
257
+ return "$RUN_STATUS"
258
+ }
259
+
260
+ print_failure_block() {
261
+ local phase_name="$1"
262
+ echo "========== IOS PIPELINE FAILURE ==========" >&2
263
+ echo "Phase: $phase_name" >&2
264
+ echo "Target: $TARGET_NAME" >&2
265
+ echo "Command: ${LAST_COMMAND:-n/a}" >&2
266
+ echo "Exit code: $RUN_STATUS" >&2
267
+ echo "----------- output -----------" >&2
268
+ if [[ -n "$RUN_OUTPUT" ]]; then
269
+ printf '%s\n' "$RUN_OUTPUT" >&2
270
+ else
271
+ echo "(no output captured)" >&2
272
+ fi
273
+ echo "=========================================" >&2
274
+ }
275
+
276
+ load_target_config() {
277
+ TARGET_JSON="$(jq -c --arg target "$TARGET_NAME" '.targets[]? | select(.name == $target)' "$PUBLISH_JSON")"
278
+ [[ -n "$TARGET_JSON" ]] || fail_validation "target '$TARGET_NAME' was not found in '$PUBLISH_JSON'"
279
+
280
+ local target_type target_platform
281
+ target_type="$(printf '%s' "$TARGET_JSON" | jq -r '.type // empty')"
282
+ target_platform="$(printf '%s' "$TARGET_JSON" | jq -r '.platform // empty')"
283
+ [[ "$target_type" == "mobile-ios" ]] || fail_validation "target '$TARGET_NAME' is not a mobile-ios target"
284
+ [[ "$target_platform" == "ios-app-store" ]] || fail_validation "target '$TARGET_NAME' is not an ios-app-store target"
285
+
286
+ SCHEME="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.scheme // empty')"
287
+ CONFIGURATION="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.configuration // empty')"
288
+ ARCHIVE_PATH="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.archive_path // empty')"
289
+ EXPORT_PATH="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.export_path // empty')"
290
+ EXPORT_METHOD="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.export_method // empty')"
291
+ BUNDLE_ID="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.bundle_id // empty')"
292
+ TEAM_ID="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.team_id // empty')"
293
+ APP_STORE_APP_ID="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.app_store_app_id // empty')"
294
+ PROVIDER_SHORT_NAME="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.provider_short_name // empty')"
295
+ PROJECT_PATH="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.project_path // empty')"
296
+ WORKSPACE_PATH="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.workspace_path // empty')"
297
+ DESTINATION="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.destination // empty')"
298
+ SDK="$(printf '%s' "$TARGET_JSON" | jq -r '.ios.sdk // empty')"
299
+
300
+ API_KEY_ID="$(resolve_env_value_from_ref "$(printf '%s' "$TARGET_JSON" | jq -r '.secrets.app_store_connect_api_key_id // empty')")"
301
+ ISSUER_ID="$(resolve_env_value_from_ref "$(printf '%s' "$TARGET_JSON" | jq -r '.secrets.app_store_connect_issuer_id // empty')")"
302
+ PRIVATE_KEY_PATH="$(resolve_env_value_from_ref "$(printf '%s' "$TARGET_JSON" | jq -r '.secrets.app_store_connect_private_key_path // empty')")"
303
+ CODE_SIGN_IDENTITY_VALUE="$(resolve_env_value_from_ref "$(printf '%s' "$TARGET_JSON" | jq -r '.secrets.code_sign_identity // empty')")"
304
+ PROVISIONING_PROFILE_UUID_VALUE="$(resolve_env_value_from_ref "$(printf '%s' "$TARGET_JSON" | jq -r '.secrets.provisioning_profile_uuid // empty')")"
305
+
306
+ [[ -n "$SCHEME" && -n "$CONFIGURATION" && -n "$ARCHIVE_PATH" && -n "$EXPORT_PATH" && -n "$EXPORT_METHOD" && -n "$BUNDLE_ID" && -n "$TEAM_ID" && -n "$APP_STORE_APP_ID" ]] || fail_validation "target '$TARGET_NAME' is missing required ios fields"
307
+ [[ -n "$PROJECT_PATH" || -n "$WORKSPACE_PATH" ]] || fail_validation "target '$TARGET_NAME' must define project_path or workspace_path"
308
+ APP_DISPLAY_NAME="$BUNDLE_ID"
309
+ }
310
+
311
+ build_export_options() {
312
+ mkdir -p "$SCRATCH_DIR"
313
+ [[ -f "$EXPORT_TEMPLATE" ]] || fail_deploy "ExportOptions template not found at $EXPORT_TEMPLATE"
314
+
315
+ sed \
316
+ -e "s|{{EXPORT_METHOD}}|$EXPORT_METHOD|g" \
317
+ -e "s|{{TEAM_ID}}|$TEAM_ID|g" \
318
+ -e "s|{{SIGNING_IDENTITY}}|$CODE_SIGN_IDENTITY_VALUE|g" \
319
+ -e "s|{{BUNDLE_ID}}|$BUNDLE_ID|g" \
320
+ -e "s|{{PROVISIONING_PROFILE_UUID}}|$PROVISIONING_PROFILE_UUID_VALUE|g" \
321
+ "$EXPORT_TEMPLATE" > "$EXPORT_OPTIONS_PATH"
322
+ }
323
+
324
+ run_build() {
325
+ local cmd=(xcodebuild archive)
326
+ if [[ -n "$WORKSPACE_PATH" ]]; then
327
+ cmd+=( -workspace "$WORKSPACE_PATH" )
328
+ else
329
+ cmd+=( -project "$PROJECT_PATH" )
330
+ fi
331
+ cmd+=( -scheme "$SCHEME" -configuration "$CONFIGURATION" -archivePath "$ARCHIVE_PATH" )
332
+ if [[ -n "$DESTINATION" ]]; then
333
+ cmd+=( -destination "$DESTINATION" )
334
+ fi
335
+ if [[ -n "$SDK" ]]; then
336
+ cmd+=( -sdk "$SDK" )
337
+ fi
338
+
339
+ if run_command "${cmd[@]}"; then
340
+ append_step build passed
341
+ update_state signing
342
+ else
343
+ append_step build failed
344
+ complete_history failed
345
+ print_failure_block build
346
+ exit "$EXIT_DEPLOY_FAILURE"
347
+ fi
348
+ }
349
+
350
+ run_sign() {
351
+ if build_export_options; then
352
+ append_step sign passed
353
+ update_state exporting
354
+ else
355
+ RUN_STATUS=1
356
+ RUN_OUTPUT="Failed to generate ExportOptions.plist"
357
+ LAST_COMMAND="generate ExportOptions.plist"
358
+ append_step sign failed
359
+ complete_history failed
360
+ print_failure_block sign
361
+ exit "$EXIT_DEPLOY_FAILURE"
362
+ fi
363
+ }
364
+
365
+ run_export() {
366
+ mkdir -p "$EXPORT_PATH"
367
+ local cmd=(xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" -exportPath "$EXPORT_PATH" -exportOptionsPlist "$EXPORT_OPTIONS_PATH")
368
+ if run_command "${cmd[@]}"; then
369
+ append_step export passed
370
+ update_state uploading
371
+ else
372
+ append_step export failed
373
+ complete_history failed
374
+ print_failure_block export
375
+ exit "$EXIT_DEPLOY_FAILURE"
376
+ fi
377
+ }
378
+
379
+ resolve_ipa_path() {
380
+ if (( DRY_RUN )); then
381
+ IPA_PATH="$EXPORT_PATH/$SCHEME.ipa"
382
+ return 0
383
+ fi
384
+
385
+ IPA_PATH="$(find "$EXPORT_PATH" -type f -name '*.ipa' | head -n 1)"
386
+ [[ -n "$IPA_PATH" ]] || fail_deploy "No IPA was found under $EXPORT_PATH after export"
387
+ }
388
+
389
+ choose_upload_tool() {
390
+ if (( DRY_RUN )); then
391
+ SELECTED_UPLOAD_TOOL="notarytool"
392
+ return 0
393
+ fi
394
+
395
+ if xcrun notarytool --help >/dev/null 2>&1; then
396
+ SELECTED_UPLOAD_TOOL="notarytool"
397
+ else
398
+ SELECTED_UPLOAD_TOOL="altool"
399
+ log_warn "xcrun notarytool is unavailable; falling back to xcrun altool."
400
+ fi
401
+ }
402
+
403
+ run_upload() {
404
+ resolve_ipa_path
405
+ choose_upload_tool
406
+
407
+ local cmd=(xcrun)
408
+ if [[ "$SELECTED_UPLOAD_TOOL" == "notarytool" ]]; then
409
+ cmd+=( notarytool submit "$IPA_PATH" --key-id "$API_KEY_ID" --issuer "$ISSUER_ID" --key "$PRIVATE_KEY_PATH" --wait )
410
+ else
411
+ cmd+=( altool --upload-app --type ios --file "$IPA_PATH" --apiKey "$API_KEY_ID" --apiIssuer "$ISSUER_ID" )
412
+ if [[ -n "$PROVIDER_SHORT_NAME" ]]; then
413
+ cmd+=( --asc-provider "$PROVIDER_SHORT_NAME" )
414
+ fi
415
+ fi
416
+
417
+ if run_command "${cmd[@]}"; then
418
+ append_step upload passed
419
+ complete_history uploaded
420
+ else
421
+ append_step upload failed
422
+ complete_history failed
423
+ print_failure_block upload
424
+ exit "$EXIT_DEPLOY_FAILURE"
425
+ fi
426
+ }
427
+
428
+ main() {
429
+ [[ -f "$PUBLISH_JSON" ]] || fail_validation "publish config not found at '$PUBLISH_JSON'"
430
+ [[ -f "$VALIDATE_CONFIG_SCRIPT" ]] || fail_validation "validate_config.sh not found"
431
+ [[ -f "$VALIDATE_ENV_SCRIPT" ]] || fail_validation "validate_ios_env.sh not found"
432
+ command -v jq >/dev/null 2>&1 || fail_validation "jq is required"
433
+
434
+ bash "$VALIDATE_CONFIG_SCRIPT" "$PUBLISH_JSON"
435
+ bash "$VALIDATE_ENV_SCRIPT" "$PUBLISH_JSON" "$TARGET_NAME"
436
+
437
+ HISTORY_FILE="$REPO_ROOT/$(jq -r '.defaults.history_file // "project/logs/publish-history.json"' "$PUBLISH_JSON")"
438
+ HISTORY_LOCK="${HISTORY_FILE}.lock"
439
+ RUN_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
440
+ RUN_STARTED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
441
+ SCRATCH_DIR="$REPO_ROOT/.claude/publish-tmp/$RUN_ID"
442
+ EXPORT_OPTIONS_PATH="$SCRATCH_DIR/ExportOptions.plist"
443
+ RUN_OUTPUT=""
444
+ RUN_STATUS=0
445
+ LAST_COMMAND=""
446
+ APP_DISPLAY_NAME="$TARGET_NAME"
447
+ TARGET_JSON=""
448
+ EXPORT_METHOD=""
449
+ BUNDLE_ID=""
450
+ TEAM_ID=""
451
+ ARCHIVE_PATH=""
452
+ EXPORT_PATH=""
453
+ SCHEME=""
454
+ CONFIGURATION=""
455
+ PROJECT_PATH=""
456
+ WORKSPACE_PATH=""
457
+ DESTINATION=""
458
+ SDK=""
459
+ APP_STORE_APP_ID=""
460
+ PROVIDER_SHORT_NAME=""
461
+ API_KEY_ID=""
462
+ ISSUER_ID=""
463
+ PRIVATE_KEY_PATH=""
464
+ CODE_SIGN_IDENTITY_VALUE=""
465
+ PROVISIONING_PROFILE_UUID_VALUE=""
466
+ SELECTED_UPLOAD_TOOL=""
467
+ IPA_PATH=""
468
+
469
+ load_target_config
470
+
471
+ if [[ ! -f "$PRIVATE_KEY_PATH" && $DRY_RUN -eq 0 ]]; then
472
+ fail_validation "private key path '$PRIVATE_KEY_PATH' does not exist"
473
+ fi
474
+
475
+ ensure_history_file
476
+ append_history_entry
477
+ log_info "Starting iOS publish pipeline for '$TARGET_NAME'..."
478
+
479
+ run_build
480
+ run_sign
481
+ run_export
482
+ run_upload
483
+ print_manual_steps
484
+ }
485
+
486
+ main "$@"
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env bash
2
+ # npm_ci_pipeline.sh — Generate (or update) a GitHub Actions workflow for npm
3
+ # publishing via OIDC Trusted Publishers, commit it, and trigger it with gh.
4
+ #
5
+ # Usage:
6
+ # npm_ci_pipeline.sh --target <name> --config <path> [--dry-run]
7
+ #
8
+ # Exit codes:
9
+ # 0 success (or --dry-run completed cleanly)
10
+ # 3 workflow trigger failure (gh workflow run exited non-zero)
11
+ # 4 config or environment invalid
12
+
13
+ set -euo pipefail
14
+
15
+ EXIT_ENV_INVALID=4
16
+ EXIT_TRIGGER_FAILED=3
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Resolve repo root
22
+ # ---------------------------------------------------------------------------
23
+ if REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel 2>/dev/null)"; then
24
+ :
25
+ else
26
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
27
+ fi
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Source shared helpers
31
+ # ---------------------------------------------------------------------------
32
+ # shellcheck source=publish_common.sh
33
+ source "${SCRIPT_DIR}/publish_common.sh"
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Arg parsing
37
+ # ---------------------------------------------------------------------------
38
+ TARGET_NAME=""
39
+ CONFIG_PATH=""
40
+ DRY_RUN=0
41
+
42
+ usage() {
43
+ cat <<'USAGE'
44
+ Usage: npm_ci_pipeline.sh --target <name> --config <path> [--dry-run]
45
+ USAGE
46
+ }
47
+
48
+ fail_usage() {
49
+ usage >&2
50
+ printf 'npm-ci pipeline error: %s\n' "$1" >&2
51
+ exit "${EXIT_ENV_INVALID}"
52
+ }
53
+
54
+ while [[ $# -gt 0 ]]; do
55
+ case "$1" in
56
+ --target)
57
+ [[ $# -ge 2 ]] || fail_usage "--target requires a value"
58
+ TARGET_NAME="$2"
59
+ shift 2
60
+ ;;
61
+ --target=*)
62
+ TARGET_NAME="${1#*=}"
63
+ shift
64
+ ;;
65
+ --config)
66
+ [[ $# -ge 2 ]] || fail_usage "--config requires a value"
67
+ CONFIG_PATH="$2"
68
+ shift 2
69
+ ;;
70
+ --config=*)
71
+ CONFIG_PATH="${1#*=}"
72
+ shift
73
+ ;;
74
+ --dry-run)
75
+ DRY_RUN=1
76
+ shift
77
+ ;;
78
+ -h|--help)
79
+ usage
80
+ exit 0
81
+ ;;
82
+ *)
83
+ fail_usage "unknown option: $1"
84
+ ;;
85
+ esac
86
+ done
87
+
88
+ [[ -n "${TARGET_NAME}" ]] || fail_usage "--target <name> is required"
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # Resolve config path
92
+ # ---------------------------------------------------------------------------
93
+ if [[ -z "${CONFIG_PATH}" ]]; then
94
+ if [[ -f "${REPO_ROOT}/publish.json" ]]; then
95
+ CONFIG_PATH="${REPO_ROOT}/publish.json"
96
+ elif [[ -f "${REPO_ROOT}/project/configs/publish.json" ]]; then
97
+ CONFIG_PATH="${REPO_ROOT}/project/configs/publish.json"
98
+ else
99
+ fail_usage "publish.json not found at '${REPO_ROOT}/publish.json' or '${REPO_ROOT}/project/configs/publish.json'"
100
+ fi
101
+ fi
102
+
103
+ [[ -f "${CONFIG_PATH}" ]] || fail_usage "publish config not found at '${CONFIG_PATH}'"
104
+ command -v jq >/dev/null 2>&1 || fail_usage "jq is required but not found on PATH"
105
+
106
+ # ---------------------------------------------------------------------------
107
+ # Phase: validate
108
+ # ---------------------------------------------------------------------------
109
+ printf '[validate] checking environment and config...\n'
110
+ if ! bash "${SCRIPT_DIR}/validate_npm_ci_env.sh" "${CONFIG_PATH}" "${TARGET_NAME}"; then
111
+ exit "${EXIT_ENV_INVALID}"
112
+ fi
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Extract config values
116
+ # ---------------------------------------------------------------------------
117
+ TARGET_JSON="$(jq -c --arg t "${TARGET_NAME}" \
118
+ '.targets[]? | select(.name == $t and .type == "npm-ci")' \
119
+ "${CONFIG_PATH}")"
120
+
121
+ GITHUB_REPO="$(printf '%s' "${TARGET_JSON}" | jq -r '.github_repo')"
122
+ WORKFLOW_PATH="$(printf '%s' "${TARGET_JSON}" | jq -r '.workflow_path // ".github/workflows/npm-publish.yml"')"
123
+ PACKAGE_NAME="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.package_name')"
124
+ NPM_ACCESS="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.access')"
125
+ DIST_TAG="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.dist_tag // "latest"')"
126
+
127
+ WORKFLOW_FILENAME="$(basename "${WORKFLOW_PATH}")"
128
+
129
+ # Resolve workflow file to absolute path
130
+ if [[ "${WORKFLOW_PATH}" = /* ]]; then
131
+ WORKFLOW_FILE="${WORKFLOW_PATH}"
132
+ else
133
+ WORKFLOW_FILE="${REPO_ROOT}/${WORKFLOW_PATH}"
134
+ fi
135
+ WORKFLOW_DIR="$(dirname "${WORKFLOW_FILE}")"
136
+
137
+ # Relative path for git operations (relative to REPO_ROOT)
138
+ WORKFLOW_FILE_REL="${WORKFLOW_PATH#./}"
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Phase: generate-workflow
142
+ # ---------------------------------------------------------------------------
143
+ printf '[generate-workflow] rendering workflow YAML...\n'
144
+
145
+ WORKFLOW_YAML="$(cat <<EOF
146
+ # Generated by /publish npm-ci pipeline — do not edit manually
147
+ name: "Publish — ${PACKAGE_NAME}"
148
+ on:
149
+ workflow_dispatch:
150
+
151
+ permissions:
152
+ id-token: write
153
+ contents: read
154
+
155
+ jobs:
156
+ publish:
157
+ runs-on: ubuntu-latest
158
+ steps:
159
+ - name: Checkout
160
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
161
+
162
+ - name: Setup Node.js
163
+ uses: actions/setup-node@v4
164
+ with:
165
+ node-version: lts/*
166
+ registry-url: https://registry.npmjs.org
167
+
168
+ - name: Install dependencies
169
+ run: npm ci
170
+
171
+ - name: Publish to npm
172
+ run: npm publish --provenance --access ${NPM_ACCESS} --tag ${DIST_TAG}
173
+ EOF
174
+ )"
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # Dry-run: print YAML and exit
178
+ # ---------------------------------------------------------------------------
179
+ if (( DRY_RUN )); then
180
+ printf '[generate-workflow] dry-run — printing YAML only (no file written, no commit, no trigger)\n'
181
+ printf '%s\n' "${WORKFLOW_YAML}"
182
+ exit 0
183
+ fi
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Phase: commit-workflow
187
+ # ---------------------------------------------------------------------------
188
+ printf '[commit-workflow] writing workflow file...\n'
189
+
190
+ mkdir -p "${WORKFLOW_DIR}"
191
+ printf '%s\n' "${WORKFLOW_YAML}" > "${WORKFLOW_FILE}"
192
+
193
+ if git -C "${REPO_ROOT}" diff --quiet -- "${WORKFLOW_FILE_REL}" 2>/dev/null && \
194
+ git -C "${REPO_ROOT}" diff --cached --quiet -- "${WORKFLOW_FILE_REL}" 2>/dev/null && \
195
+ git -C "${REPO_ROOT}" ls-files --error-unmatch "${WORKFLOW_FILE_REL}" >/dev/null 2>&1; then
196
+ printf '[commit-workflow] no changes — skipping commit\n'
197
+ else
198
+ git -C "${REPO_ROOT}" add "${WORKFLOW_FILE_REL}"
199
+ git -C "${REPO_ROOT}" commit -m "chore(publish): update npm CI workflow for target ${TARGET_NAME}"
200
+ printf '[commit-workflow] committed: %s\n' "${WORKFLOW_FILE}"
201
+ fi
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Phase: trigger
205
+ # ---------------------------------------------------------------------------
206
+ printf '[trigger] dispatching workflow run...\n'
207
+
208
+ if ! gh workflow run "${WORKFLOW_FILENAME}" --repo "${GITHUB_REPO}"; then
209
+ printf '[trigger] ERROR: gh workflow run failed\n' >&2
210
+ exit "${EXIT_TRIGGER_FAILED}"
211
+ fi
212
+
213
+ RUN_URL="$(gh run list \
214
+ --workflow "${WORKFLOW_FILENAME}" \
215
+ --repo "${GITHUB_REPO}" \
216
+ --limit 1 \
217
+ --json url \
218
+ --jq '.[0].url')"
219
+
220
+ printf '%s\n' "${RUN_URL}"
221
+
222
+ # ---------------------------------------------------------------------------
223
+ # Phase: triggered
224
+ # ---------------------------------------------------------------------------
225
+ printf '[triggered] workflow run dispatched: %s\n' "${RUN_URL}"