@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.
- package/LICENSE +201 -0
- package/README.md +340 -0
- package/agents/ai_engineer.md +113 -0
- package/agents/developer.md +236 -0
- package/agents/scrum-master.md +349 -0
- package/agents/scrutiny-agent.md +137 -0
- package/agents/solution-assessor.md +185 -0
- package/agents/tester.md +339 -0
- package/bin/jenga.js +70 -0
- package/hooks/copilot_session_end.sh +29 -0
- package/hooks/on_session_end.sh +238 -0
- package/hooks/prompt_router.sh +11 -0
- package/hooks/prompt_router_helper.js +52 -0
- package/hooks/session_end_helper.js +29 -0
- package/hooks/session_end_watcher.sh +24 -0
- package/lib/commands/attach.js +47 -0
- package/lib/commands/init.js +207 -0
- package/lib/commands/start.js +16 -0
- package/lib/commands/status.js +53 -0
- package/lib/config-schema.js +72 -0
- package/lib/inject-settings.js +61 -0
- package/lib/mirror.js +244 -0
- package/lib/resolve-project-dir.sh +47 -0
- package/mcp/execute-ticket/index.js +10 -0
- package/mcp/execute-ticket/package.json +5 -0
- package/mcp/help/index.js +79 -0
- package/mcp/help/package.json +14 -0
- package/mcp/router/README.md +19 -0
- package/mcp/router/embedder.js +23 -0
- package/mcp/router/index.js +204 -0
- package/mcp/router/matcher.js +87 -0
- package/mcp/router/package-lock.json +1048 -0
- package/mcp/router/package.json +11 -0
- package/mcp/router/skill-index.js +104 -0
- package/package.json +47 -0
- package/scripts/board_resolver.sh +46 -0
- package/scripts/e25_s01_extract_board_graph.py +292 -0
- package/scripts/e25_s01_generate_synthetic_board.py +90 -0
- package/scripts/measurement-10x.json +50 -0
- package/scripts/measurement-10x.txt +4 -0
- package/scripts/measurement-real.json +50 -0
- package/scripts/measurement-real.txt +4 -0
- package/scripts/postinstall.js +165 -0
- package/scripts/todo_cleanup.sh +22 -0
- package/scripts/todo_manager.sh +86 -0
- package/scripts/validate-board.sh +190 -0
- package/scripts/validate-story-format.sh +53 -0
- package/skills/brainstorm/SKILL.md +47 -0
- package/skills/btw/SKILL.md +42 -0
- package/skills/commit/SKILL.md +29 -0
- package/skills/commit/assets/user_instructions_template.md +22 -0
- package/skills/continue/SKILL.md +29 -0
- package/skills/convert/SKILL.md +124 -0
- package/skills/convert/convert_cli.py +235 -0
- package/skills/convert/tests/sample.csv +4 -0
- package/skills/convert/tests/sample.json +5 -0
- package/skills/convert/tests/sample.jsonl +3 -0
- package/skills/convert/tests/sample.yaml +18 -0
- package/skills/convert/tests/sample_obj.csv +2 -0
- package/skills/convert/tests/sample_obj.json +9 -0
- package/skills/deep-dive/SKILL.md +167 -0
- package/skills/do/SKILL.md +88 -0
- package/skills/do/assets/sender_template.json +12 -0
- package/skills/doc/SKILL.md +314 -0
- package/skills/doc/assets/path-objectives.yaml +38 -0
- package/skills/doc-sync/SKILL.md +167 -0
- package/skills/doc-sync/assets/default_excludes.txt +21 -0
- package/skills/doc-sync/assets/doc_targets.md +14 -0
- package/skills/dooo/SKILL.md +60 -0
- package/skills/error/SKILL.md +29 -0
- package/skills/evaluate/SKILL.md +45 -0
- package/skills/evaluate/assets/evaluation_invokation_template.yml +3 -0
- package/skills/evaluate/assets/evaluation_rapport_template.md +24 -0
- package/skills/examplify/SKILL.md +42 -0
- package/skills/help/SKILL.md +36 -0
- package/skills/improve/SKILL.md +55 -0
- package/skills/index/scripts/board-index +4 -0
- package/skills/index/scripts/board_index.py +615 -0
- package/skills/index/scripts/smoke_test.sh +86 -0
- package/skills/init/SKILL.md +44 -0
- package/skills/init/assets/.gitignore_template +15 -0
- package/skills/init/assets/PROJECT_SUMMARY_template.md +13 -0
- package/skills/init/assets/directory_structure.txt +13 -0
- package/skills/init/assets/test-config_template.json +4 -0
- package/skills/init/assets/workflow_template.json +30 -0
- package/skills/init/scripts/init.sh +48 -0
- package/skills/jbp/SKILL.md +25 -0
- package/skills/jenga/SKILL.md +68 -0
- package/skills/lgtm/SKILL.md +21 -0
- package/skills/mirror-public/SKILL.md +237 -0
- package/skills/mirror-public/assets/config.json +5 -0
- package/skills/mirror-public/scripts/mirror.sh +374 -0
- package/skills/pi-plan/SKILL.md +62 -0
- package/skills/pi-plan/assets/epic.json +7 -0
- package/skills/pi-plan/assets/story_template.md +18 -0
- package/skills/proceed/SKILL.md +29 -0
- package/skills/publish/SKILL.md +351 -0
- package/skills/publish/adapters/droplet.md +200 -0
- package/skills/publish/adapters/mobile-ios.md +114 -0
- package/skills/publish/adapters/npm-ci.md +223 -0
- package/skills/publish/adapters/npm.md +121 -0
- package/skills/publish/assets/ExportOptions.plist.template +19 -0
- package/skills/publish/assets/ci-contract.md +111 -0
- package/skills/publish/assets/ownership-matrix.md +17 -0
- package/skills/publish/assets/publish.example.json +85 -0
- package/skills/publish/assets/publish.example.npm-ci.json +40 -0
- package/skills/publish/assets/publish.example.npm.json +41 -0
- package/skills/publish/assets/secrets-guide.md +104 -0
- package/skills/publish/schemas/fixtures/npm-ci-minimal.json +17 -0
- package/skills/publish/schemas/fixtures/npm-ci-with-empty-secrets.json +18 -0
- package/skills/publish/schemas/fixtures/npm-ci-with-workflow-path.json +18 -0
- package/skills/publish/schemas/publish.schema.json +428 -0
- package/skills/publish/scripts/check_target_config.sh +96 -0
- package/skills/publish/scripts/droplet_pipeline.sh +208 -0
- package/skills/publish/scripts/generate_release_notes.sh +200 -0
- package/skills/publish/scripts/ios_pipeline.sh +486 -0
- package/skills/publish/scripts/npm_ci_pipeline.sh +225 -0
- package/skills/publish/scripts/npm_pipeline.sh +249 -0
- package/skills/publish/scripts/publish_common.sh +253 -0
- package/skills/publish/scripts/publish_deploy.sh +538 -0
- package/skills/publish/scripts/reconcile_tags.sh +135 -0
- package/skills/publish/scripts/run_gates.sh +616 -0
- package/skills/publish/scripts/setup_wizard.sh +394 -0
- package/skills/publish/scripts/show_history.sh +95 -0
- package/skills/publish/scripts/suggest_semver_bump.sh +105 -0
- package/skills/publish/scripts/validate_config.sh +163 -0
- package/skills/publish/scripts/validate_droplet_env.sh +45 -0
- package/skills/publish/scripts/validate_ios_env.sh +68 -0
- package/skills/publish/scripts/validate_npm_ci_env.sh +71 -0
- package/skills/publish/scripts/validate_npm_env.sh +22 -0
- package/skills/publish/scripts/write_ledger_entry.sh +126 -0
- package/skills/publish/wizards/droplet.md +275 -0
- package/skills/publish/wizards/mobile-ios.md +157 -0
- package/skills/publish/wizards/npm-ci.md +240 -0
- package/skills/publish/wizards/npm.md +224 -0
- package/skills/reconcile/SKILL.md +93 -0
- package/skills/reconcile/assets/report_format.md +44 -0
- package/skills/reconcile-origin/SKILL.md +75 -0
- package/skills/reconcile-origin/scripts/reconcile-origin.sh +372 -0
- package/skills/redo/SKILL.md +70 -0
- package/skills/route/SKILL.md +180 -0
- package/skills/self-sync/SKILL.md +73 -0
- package/skills/self-sync/scripts/run.js +136 -0
- package/skills/skillify/SKILL.md +68 -0
- package/skills/skillify/assets/init-new/SKILL.md +35 -0
- package/skills/skillify/assets/init-new/assets/.gitignore_template +15 -0
- package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +13 -0
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +10 -0
- package/skills/skillify/assets/init-new/assets/test-config_template.json +4 -0
- package/skills/skillify/assets/init-new/assets/workflow_template.json +17 -0
- package/skills/skillify/assets/init-new/scripts/init.sh +48 -0
- package/skills/skillify/assets/init-old/SKILL.md +124 -0
- package/skills/spinoff/SKILL.md +48 -0
- package/skills/status/SKILL.md +33 -0
- package/skills/status/assets/output_format.md +41 -0
- package/skills/todo/SKILL.md +46 -0
- package/skills/todo/assets/todo_handoff_template.md +22 -0
- package/skills/todo/assets/todo_template.md +3 -0
- package/skills/train/SKILL.md +116 -0
- package/skills/train/assets/dashboard-templates/classifiers.html +106 -0
- package/skills/train/assets/dashboard-templates/nlp.html +102 -0
- package/skills/train/assets/dashboard-templates/transformers.html +98 -0
- package/skills/train/assets/results-parsers/__init__.py +9 -0
- package/skills/train/assets/results-parsers/classifiers.py +84 -0
- package/skills/train/assets/results-parsers/nlp.py +88 -0
- package/skills/train/assets/results-parsers/reporter.py +154 -0
- package/skills/train/assets/results-parsers/transformers.py +120 -0
- package/skills/train/train_cli.py +786 -0
- package/templates/EXECUTION_PLAN_TEMPLATE.md +43 -0
- package/templates/EXECUTION_SUMMARY_TEMPLATE.md +50 -0
- package/templates/JENGA_CONFIG_TEMPLATE.json +23 -0
- package/templates/PROBLEM_RAPPORT_TEMPLATE.md +88 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +311 -0
- package/templates/SKILL.md +16 -0
- package/templates/SKILL_TEMPLATE.md +28 -0
- package/templates/USER_INSTRUCTIONS_TEMPLATE.md +22 -0
- 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}"
|