@profoundlogic/coderflow-server 0.2.1
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.txt +322 -0
- package/README.md +158 -0
- package/dist/LICENSE.txt +322 -0
- package/dist/README.md +158 -0
- package/dist/base-image/Dockerfile +184 -0
- package/dist/base-image/agent-wrapper.sh +143 -0
- package/dist/base-image/apply-local-state.sh +357 -0
- package/dist/base-image/coder-git-credential-helper +307 -0
- package/dist/base-image/entrypoint.sh +942 -0
- package/dist/base-image/ssh_config_template +41 -0
- package/dist/base-image/start-code-server.sh +76 -0
- package/dist/base-image/sync-repos.sh +170 -0
- package/dist/base-image/vscode-extensions.txt +10 -0
- package/dist/base-image/vscode-settings.json +41 -0
- package/dist/coder-server.js +2 -0
- package/dist/config/cli-models.json +45 -0
- package/dist/config/imported-skills.schema.json +83 -0
- package/dist/config/skill-catalog.json +18 -0
- package/dist/config/skill-catalog.schema.json +140 -0
- package/dist/config.js +1 -0
- package/dist/examples/oidc.json.example +11 -0
- package/dist/lib/agent-keepalive.js +1 -0
- package/dist/lib/api-keys.js +1 -0
- package/dist/lib/apiKeys.js +1 -0
- package/dist/lib/auto-judge.js +1 -0
- package/dist/lib/basic-auth.js +1 -0
- package/dist/lib/build-history.js +1 -0
- package/dist/lib/build-output-service.js +1 -0
- package/dist/lib/build-scheduler.js +1 -0
- package/dist/lib/build-service.js +1 -0
- package/dist/lib/claude-oauth-refresh.js +1 -0
- package/dist/lib/cli/build.js +1 -0
- package/dist/lib/cli/config-command.js +1 -0
- package/dist/lib/cli/config.js +1 -0
- package/dist/lib/cli/create-user.js +1 -0
- package/dist/lib/cli/init.js +1 -0
- package/dist/lib/cli/jira.js +1 -0
- package/dist/lib/cli/license.js +1 -0
- package/dist/lib/cli/server-manager.js +1 -0
- package/dist/lib/container-tokens.js +1 -0
- package/dist/lib/data-dir.js +1 -0
- package/dist/lib/deployment-history.js +1 -0
- package/dist/lib/deployment-service.js +1 -0
- package/dist/lib/docker-utils.js +1 -0
- package/dist/lib/email.js +1 -0
- package/dist/lib/emailTemplates.js +1 -0
- package/dist/lib/entitlement.js +1 -0
- package/dist/lib/fetch-utils.js +1 -0
- package/dist/lib/git-provider-service.js +1 -0
- package/dist/lib/git-provider-setup/assets/coderflow_github_app.png +0 -0
- package/dist/lib/git-provider-setup/github-setup-handler.js +1 -0
- package/dist/lib/git-provider-setup/index.js +1 -0
- package/dist/lib/git-provider-setup/setup-factory.js +1 -0
- package/dist/lib/git-provider-setup/setup-interface.js +1 -0
- package/dist/lib/git-providers/azure-devops-provider.js +1 -0
- package/dist/lib/git-providers/github-app-provider.js +1 -0
- package/dist/lib/git-providers/index.js +1 -0
- package/dist/lib/git-providers/provider-factory.js +1 -0
- package/dist/lib/git-providers/provider-interface.js +1 -0
- package/dist/lib/jira-client.js +1 -0
- package/dist/lib/logger.js +1 -0
- package/dist/lib/model-fetcher.js +1 -0
- package/dist/lib/notifications.js +1 -0
- package/dist/lib/oidc-auth.js +1 -0
- package/dist/lib/oidc-device-flow.js +1 -0
- package/dist/lib/passwordTokens.js +1 -0
- package/dist/lib/pin-cascade.js +1 -0
- package/dist/lib/provider-accounts.js +1 -0
- package/dist/lib/provider-oauth.js +1 -0
- package/dist/lib/provider-profile.js +1 -0
- package/dist/lib/provider-token-refresh.js +1 -0
- package/dist/lib/roles.js +1 -0
- package/dist/lib/secrets.js +1 -0
- package/dist/lib/state-capture.js +1 -0
- package/dist/lib/static-files.js +1 -0
- package/dist/lib/task-name-generator.js +1 -0
- package/dist/lib/users.js +1 -0
- package/dist/middleware/requireAuth.js +1 -0
- package/dist/middleware/requireInit.js +1 -0
- package/dist/middleware/requirePermission.js +1 -0
- package/dist/package-lock.json +4151 -0
- package/dist/package.json +50 -0
- package/dist/routes/apiKeys.js +1 -0
- package/dist/routes/auth-oidc.js +1 -0
- package/dist/routes/auth.js +1 -0
- package/dist/routes/build.js +1 -0
- package/dist/routes/containers.js +1 -0
- package/dist/routes/deploy-task.js +1 -0
- package/dist/routes/environment-management.js +1 -0
- package/dist/routes/environments.js +1 -0
- package/dist/routes/external-skills.js +1 -0
- package/dist/routes/git-credentials.js +1 -0
- package/dist/routes/git-provider-setup.js +1 -0
- package/dist/routes/health.js +1 -0
- package/dist/routes/jira.js +1 -0
- package/dist/routes/objective-management.js +1 -0
- package/dist/routes/password.js +1 -0
- package/dist/routes/prompt.js +1 -0
- package/dist/routes/provider-auth.js +1 -0
- package/dist/routes/qa.js +1 -0
- package/dist/routes/settings.js +1 -0
- package/dist/routes/skill-management.js +1 -0
- package/dist/routes/skills.js +1 -0
- package/dist/routes/tasks.js +2 -0
- package/dist/routes/templates.js +1 -0
- package/dist/routes/test-task.js +1 -0
- package/dist/routes/test.js +1 -0
- package/dist/routes/users.js +1 -0
- package/dist/routes/visualizations.js +1 -0
- package/dist/schemas/template-metadata.schema.json +178 -0
- package/dist/scripts/create-user.js +2 -0
- package/dist/shipped-skills/environment-instructions/SKILL.md +154 -0
- package/dist/shipped-skills/environment-templates/SKILL.md +282 -0
- package/dist/shipped-skills/objective-management/SKILL.md +238 -0
- package/dist/shipped-skills/skill-editor/SKILL.md +326 -0
- package/dist/start.js +2 -0
- package/dist/web-ui/public/activity-detail-modal.js +1 -0
- package/dist/web-ui/public/activity-feed.js +1 -0
- package/dist/web-ui/public/activity-formatters.js +1 -0
- package/dist/web-ui/public/agent-event-parser.js +1 -0
- package/dist/web-ui/public/app.js +1 -0
- package/dist/web-ui/public/approve-dialog.js +1 -0
- package/dist/web-ui/public/coderflow-logo-reversed.svg +46 -0
- package/dist/web-ui/public/coderflow-logo.svg +46 -0
- package/dist/web-ui/public/comments-widget.js +1 -0
- package/dist/web-ui/public/docs/.nojekyll +0 -0
- package/dist/web-ui/public/docs/README.md +26 -0
- package/dist/web-ui/public/docs/_sidebar.md +47 -0
- package/dist/web-ui/public/docs/admin/ai-providers.md +132 -0
- package/dist/web-ui/public/docs/admin/email-notifications.md +69 -0
- package/dist/web-ui/public/docs/admin/environments.md +215 -0
- package/dist/web-ui/public/docs/admin/git-providers.md +147 -0
- package/dist/web-ui/public/docs/admin/installation.md +313 -0
- package/dist/web-ui/public/docs/admin/skills.md +35 -0
- package/dist/web-ui/public/docs/admin/sso.md +241 -0
- package/dist/web-ui/public/docs/admin/users-and-roles.md +57 -0
- package/dist/web-ui/public/docs/code/cli.md +102 -0
- package/dist/web-ui/public/docs/code/files-and-editing.md +86 -0
- package/dist/web-ui/public/docs/code/terminal-access.md +110 -0
- package/dist/web-ui/public/docs/code/vscode-extension.md +58 -0
- package/dist/web-ui/public/docs/getting-started/core-concepts.md +129 -0
- package/dist/web-ui/public/docs/getting-started/overview.md +46 -0
- package/dist/web-ui/public/docs/index.html +151 -0
- package/dist/web-ui/public/docs/integrations/custom.md +58 -0
- package/dist/web-ui/public/docs/integrations/ibmi/overview.md +58 -0
- package/dist/web-ui/public/docs/integrations/overview.md +48 -0
- package/dist/web-ui/public/docs/objectives/qa-mode.md +90 -0
- package/dist/web-ui/public/docs/objectives/staged-tasks.md +60 -0
- package/dist/web-ui/public/docs/objectives/working-with-objectives.md +102 -0
- package/dist/web-ui/public/docs/tasks/approval-and-deployment.md +83 -0
- package/dist/web-ui/public/docs/tasks/creating-tasks.md +111 -0
- package/dist/web-ui/public/docs/tasks/judging.md +114 -0
- package/dist/web-ui/public/docs/tasks/providing-feedback.md +41 -0
- package/dist/web-ui/public/docs/tasks/task-groups.md +73 -0
- package/dist/web-ui/public/docs/tasks/winner-selection.md +75 -0
- package/dist/web-ui/public/docs/templates/batch-processing.md +152 -0
- package/dist/web-ui/public/docs/templates/task-templates.md +44 -0
- package/dist/web-ui/public/docs/templates/template-examples.md +93 -0
- package/dist/web-ui/public/docs/testing/profound-automated-testing.md +77 -0
- package/dist/web-ui/public/docs/testing/task-visualizations.md +42 -0
- package/dist/web-ui/public/docs/testing/testing-menu.md +118 -0
- package/dist/web-ui/public/environments.css +3942 -0
- package/dist/web-ui/public/environments.html +1791 -0
- package/dist/web-ui/public/environments.js +1 -0
- package/dist/web-ui/public/favicon-16.png +0 -0
- package/dist/web-ui/public/favicon-32.png +0 -0
- package/dist/web-ui/public/favicon.ico +0 -0
- package/dist/web-ui/public/feedback-widget.css +3133 -0
- package/dist/web-ui/public/feedback-widget.js +1 -0
- package/dist/web-ui/public/git-history.css +2663 -0
- package/dist/web-ui/public/git-history.html +272 -0
- package/dist/web-ui/public/git-history.js +1 -0
- package/dist/web-ui/public/git-status.js +1 -0
- package/dist/web-ui/public/index.html +1459 -0
- package/dist/web-ui/public/index.js +1 -0
- package/dist/web-ui/public/login.html +346 -0
- package/dist/web-ui/public/login.js +1 -0
- package/dist/web-ui/public/markdown-editor.js +1 -0
- package/dist/web-ui/public/markdown-file-editor.js +1 -0
- package/dist/web-ui/public/modal-maximize.js +1 -0
- package/dist/web-ui/public/notifications.js +1 -0
- package/dist/web-ui/public/server-health.js +1 -0
- package/dist/web-ui/public/settings.css +761 -0
- package/dist/web-ui/public/settings.html +1044 -0
- package/dist/web-ui/public/settings.js +1 -0
- package/dist/web-ui/public/setup-password.html +355 -0
- package/dist/web-ui/public/setup-password.js +1 -0
- package/dist/web-ui/public/skills.css +1949 -0
- package/dist/web-ui/public/skills.html +820 -0
- package/dist/web-ui/public/skills.js +1 -0
- package/dist/web-ui/public/sse-client.js +1 -0
- package/dist/web-ui/public/sse-shared-worker.js +1 -0
- package/dist/web-ui/public/styles.css +18614 -0
- package/dist/web-ui/public/task.html +1779 -0
- package/dist/web-ui/public/task.js +1 -0
- package/dist/web-ui/public/terminal.html +45 -0
- package/dist/web-ui/public/terminal.js +1 -0
- package/dist/web-ui/public/theme.js +1 -0
- package/dist/web-ui/public/users.html +298 -0
- package/dist/web-ui/public/users.js +1 -0
- package/dist/web-ui/public/variant-grouping.js +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CoderFlow Base Image Entrypoint
|
|
3
|
+
# Handles task execution with logging and exit code management
|
|
4
|
+
|
|
5
|
+
set -o pipefail # Catch errors in pipes
|
|
6
|
+
# Note: We don't use 'set -e' because we want to capture exit codes
|
|
7
|
+
|
|
8
|
+
# Task metadata
|
|
9
|
+
TASK_ID="${TASK_ID:-$(date +%s)-$$}"
|
|
10
|
+
TASK_TYPE="${TASK_TYPE:-manual}"
|
|
11
|
+
ENVIRONMENT="${ENVIRONMENT:-base}"
|
|
12
|
+
TASK_OUTPUT_DIR="${TASK_OUTPUT_DIR:-/task-output}"
|
|
13
|
+
START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
14
|
+
LOG_FILE="$TASK_OUTPUT_DIR/log.txt"
|
|
15
|
+
|
|
16
|
+
# Ensure output directory and log file exist
|
|
17
|
+
mkdir -p "$TASK_OUTPUT_DIR"
|
|
18
|
+
touch "$LOG_FILE"
|
|
19
|
+
|
|
20
|
+
# Decode parameters and env vars provided by server
|
|
21
|
+
PARAMETERS_JSON='{}'
|
|
22
|
+
if [ -n "$TASK_PARAMETERS_B64" ]; then
|
|
23
|
+
decoded_parameters=$(printf '%s' "$TASK_PARAMETERS_B64" | base64 --decode 2>/dev/null)
|
|
24
|
+
if [ $? -eq 0 ] && [ -n "$decoded_parameters" ]; then
|
|
25
|
+
PARAMETERS_JSON="$decoded_parameters"
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
ENV_VARS_JSON='{}'
|
|
30
|
+
if [ -n "$TASK_ENV_VARS_B64" ]; then
|
|
31
|
+
decoded_env=$(printf '%s' "$TASK_ENV_VARS_B64" | base64 --decode 2>/dev/null)
|
|
32
|
+
if [ $? -eq 0 ] && [ -n "$decoded_env" ]; then
|
|
33
|
+
ENV_VARS_JSON="$decoded_env"
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Logging functions
|
|
38
|
+
log() {
|
|
39
|
+
local message="[$(date +'%Y-%m-%d %H:%M:%S')] $*"
|
|
40
|
+
echo "$message"
|
|
41
|
+
if [ -n "$LOG_FILE" ]; then
|
|
42
|
+
printf '%s\n' "$message" >> "$LOG_FILE"
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
log_error() {
|
|
47
|
+
log "ERROR: $*"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log_success() {
|
|
51
|
+
log "SUCCESS: $*"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Enhanced timing function with millisecond precision (only when DEBUG_TIMING is enabled)
|
|
55
|
+
log_timing() {
|
|
56
|
+
if [ "${DEBUG_TIMING:-false}" = "true" ]; then
|
|
57
|
+
local message="[$(date +'%Y-%m-%d %H:%M:%S.%3N')] [TIMING] $*"
|
|
58
|
+
echo "$message"
|
|
59
|
+
if [ -n "$LOG_FILE" ]; then
|
|
60
|
+
printf '%s\n' "$message" >> "$LOG_FILE"
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Mark timing checkpoint
|
|
66
|
+
TIMING_START=$(date +%s%3N)
|
|
67
|
+
timing_checkpoint() {
|
|
68
|
+
if [ "${DEBUG_TIMING:-false}" = "true" ]; then
|
|
69
|
+
local label="$1"
|
|
70
|
+
local now=$(date +%s%3N)
|
|
71
|
+
local elapsed=$((now - TIMING_START))
|
|
72
|
+
log_timing "$label (elapsed: ${elapsed}ms since start)"
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Setup git credentials early if available (needed for git operations)
|
|
77
|
+
# Git credential.helper store is pre-configured in the Dockerfile.
|
|
78
|
+
# Server passes credentials via CODER_GIT_CREDS environment variable (JSON array format).
|
|
79
|
+
setup_git_credentials() {
|
|
80
|
+
# Check for git credentials passed via environment variable (JSON array)
|
|
81
|
+
if [ -n "$CODER_GIT_CREDS" ]; then
|
|
82
|
+
# Parse JSON array and write each credential line to file
|
|
83
|
+
echo "$CODER_GIT_CREDS" | jq -r '.[]' > /home/coder/.git-credentials 2>/dev/null
|
|
84
|
+
if [ $? -eq 0 ]; then
|
|
85
|
+
chown coder:coder /home/coder/.git-credentials
|
|
86
|
+
chmod 600 /home/coder/.git-credentials
|
|
87
|
+
log "Static Git credentials configured"
|
|
88
|
+
else
|
|
89
|
+
log "Failed to parse static Git credentials from environment variable"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Setup git user identity if provided via environment variables
|
|
95
|
+
# CODER_USER_NAME and CODER_USER_EMAIL are passed from the server based on the authenticated user
|
|
96
|
+
setup_git_user_identity() {
|
|
97
|
+
if [ -n "$CODER_USER_NAME" ] || [ -n "$CODER_USER_EMAIL" ]; then
|
|
98
|
+
if [ -n "$CODER_USER_NAME" ]; then
|
|
99
|
+
git config --file /home/coder/.gitconfig user.name "$CODER_USER_NAME"
|
|
100
|
+
fi
|
|
101
|
+
if [ -n "$CODER_USER_EMAIL" ]; then
|
|
102
|
+
git config --file /home/coder/.gitconfig user.email "$CODER_USER_EMAIL"
|
|
103
|
+
fi
|
|
104
|
+
chown coder:coder /home/coder/.gitconfig
|
|
105
|
+
chmod 644 /home/coder/.gitconfig
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Setup cache directory for dynamic git credential helper
|
|
110
|
+
# This helper fetches fresh tokens from the server on-demand, solving the
|
|
111
|
+
# GitHub App token 1-hour expiration issue for long-running containers.
|
|
112
|
+
# Cache is stored at ~/.cache/coder-git-credentials/ with 700 permissions.
|
|
113
|
+
setup_credential_helper_cache() {
|
|
114
|
+
local cache_dir="/home/coder/.cache/coder-git-credentials"
|
|
115
|
+
|
|
116
|
+
# Create cache directory with secure permissions
|
|
117
|
+
mkdir -p "$cache_dir"
|
|
118
|
+
chmod 700 "$cache_dir"
|
|
119
|
+
chown coder:coder "$cache_dir"
|
|
120
|
+
|
|
121
|
+
# Also ensure parent .cache directory has correct ownership
|
|
122
|
+
chown coder:coder /home/coder/.cache 2>/dev/null || true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Update or clone repositories from configuration
|
|
126
|
+
update_repositories() {
|
|
127
|
+
timing_checkpoint "update_repositories: START"
|
|
128
|
+
local workspace_dir="${WORKSPACE_DIR:-/workspace}"
|
|
129
|
+
|
|
130
|
+
# Create workspace directory
|
|
131
|
+
mkdir -p "$workspace_dir"
|
|
132
|
+
|
|
133
|
+
# Check if REPOS_CONFIG is set
|
|
134
|
+
if [ -z "$REPOS_CONFIG" ]; then
|
|
135
|
+
log "No REPOS_CONFIG provided, skipping repository update" >&2
|
|
136
|
+
timing_checkpoint "update_repositories: END (no config)"
|
|
137
|
+
return 0
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Configure Git to use SSH without strict host key checking
|
|
141
|
+
export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
142
|
+
|
|
143
|
+
log "Updating repositories in $workspace_dir..." >&2
|
|
144
|
+
timing_checkpoint "update_repositories: Config loaded"
|
|
145
|
+
|
|
146
|
+
# Parse JSON array using jq
|
|
147
|
+
# Expected format: [{"name":"repo1","url":"https://...","branch":"main"},...]
|
|
148
|
+
local repo_count=$(echo "$REPOS_CONFIG" | jq 'length' 2>/dev/null)
|
|
149
|
+
|
|
150
|
+
if [ -z "$repo_count" ] || [ "$repo_count" -eq 0 ]; then
|
|
151
|
+
log "No repositories configured" >&2
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
log "Found $repo_count repositories" >&2
|
|
156
|
+
|
|
157
|
+
# Feature flag for parallel updates (can be disabled via env var if issues arise)
|
|
158
|
+
# Skip parallel mode if only 1 repo
|
|
159
|
+
local parallel_updates="${PARALLEL_REPO_UPDATES:-true}"
|
|
160
|
+
if [ "$repo_count" -eq 1 ]; then
|
|
161
|
+
parallel_updates="false"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [ "$parallel_updates" = "true" ]; then
|
|
165
|
+
log "Using parallel repository updates" >&2
|
|
166
|
+
timing_checkpoint "update_repositories: Starting parallel updates"
|
|
167
|
+
else
|
|
168
|
+
log "Using sequential repository updates" >&2
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Array to store background job PIDs
|
|
172
|
+
declare -a update_pids=()
|
|
173
|
+
|
|
174
|
+
# Process each repository using jq
|
|
175
|
+
for index in $(seq 0 $((repo_count - 1))); do
|
|
176
|
+
# Extract repository details using jq, supporting both object and string entries
|
|
177
|
+
local repo_json=$(echo "$REPOS_CONFIG" | jq -c ".[$index]" 2>/dev/null)
|
|
178
|
+
|
|
179
|
+
if [ -z "$repo_json" ] || [ "$repo_json" = "null" ]; then
|
|
180
|
+
log_error "Invalid repository configuration at index $index" >&2
|
|
181
|
+
continue
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
local repo_name=$(echo "$repo_json" | jq -r 'if type == "object" then (.name // empty) else . end' 2>/dev/null)
|
|
185
|
+
local repo_url=$(echo "$repo_json" | jq -r 'if type == "object" then (.url // empty) else empty end' 2>/dev/null)
|
|
186
|
+
local repo_branch=$(echo "$repo_json" | jq -r 'if type == "object" then (.branch // "main") else "main" end' 2>/dev/null)
|
|
187
|
+
local repo_path_from_json=$(echo "$repo_json" | jq -r 'if type == "object" then (.path // empty) else empty end' 2>/dev/null)
|
|
188
|
+
|
|
189
|
+
if [ -z "$repo_name" ] || [ "$repo_name" = "null" ]; then
|
|
190
|
+
log_error "Invalid repository configuration at index $index" >&2
|
|
191
|
+
continue
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [ -z "$repo_url" ] || [ "$repo_url" = "null" ]; then
|
|
195
|
+
log "Skipping automated update for $repo_name (no repository URL provided)" >&2
|
|
196
|
+
continue
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# Use path field from JSON if available, otherwise default to repo_name
|
|
200
|
+
local repo_path
|
|
201
|
+
if [ -n "$repo_path_from_json" ] && [ "$repo_path_from_json" != "null" ]; then
|
|
202
|
+
repo_path="$workspace_dir/$repo_path_from_json"
|
|
203
|
+
else
|
|
204
|
+
repo_path="$workspace_dir/$repo_name"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Define update function (will be run either inline or in background)
|
|
208
|
+
update_single_repo() {
|
|
209
|
+
local repo_name="$1"
|
|
210
|
+
local repo_url="$2"
|
|
211
|
+
local repo_branch="$3"
|
|
212
|
+
local repo_path="$4"
|
|
213
|
+
|
|
214
|
+
# Check for repo-specific branch override (e.g., PROFOUNDJS_BRANCH)
|
|
215
|
+
local env_var_name=$(echo "$repo_name" | tr '[:lower:]' '[:upper:]' | tr '-' '_')_BRANCH
|
|
216
|
+
local branch_override=$(eval echo \$$env_var_name)
|
|
217
|
+
|
|
218
|
+
# Strip origin/ prefix if present (handles case where UI sends full remote ref)
|
|
219
|
+
if [[ "$branch_override" == origin/* ]]; then
|
|
220
|
+
branch_override="${branch_override#origin/}"
|
|
221
|
+
log "Stripped origin/ prefix from branch override: $branch_override" >&2
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Determine final branch: repo-specific override > CODER_BRANCH > CHECKOUT_BRANCH > config branch
|
|
225
|
+
local branch="${branch_override:-${CODER_BRANCH:-${CHECKOUT_BRANCH:-$repo_branch}}}"
|
|
226
|
+
|
|
227
|
+
# Check if repo already exists (cloned during image build)
|
|
228
|
+
if [ -d "$repo_path/.git" ]; then
|
|
229
|
+
timing_checkpoint "update_repositories: Updating $repo_name START"
|
|
230
|
+
log "Updating $repo_name..." >&2
|
|
231
|
+
|
|
232
|
+
# Log branch override if used
|
|
233
|
+
if [ -n "$branch_override" ]; then
|
|
234
|
+
log "Using branch override $env_var_name=$branch (config: $repo_branch)" >&2
|
|
235
|
+
elif [ "$branch" != "$repo_branch" ]; then
|
|
236
|
+
log "Using branch override: $branch (config: $repo_branch)" >&2
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# Run git operations as coder user - output to both stdout and log file
|
|
240
|
+
log "Running git operations for $repo_name..."
|
|
241
|
+
|
|
242
|
+
# Check if we're running as root or coder user
|
|
243
|
+
if [ "$(id -u)" -eq 0 ]; then
|
|
244
|
+
# Running as root, use runuser to switch to coder
|
|
245
|
+
GIT_SSH_COMMAND="$GIT_SSH_COMMAND" runuser -u coder -- bash -c "
|
|
246
|
+
cd '$repo_path' || exit 1
|
|
247
|
+
|
|
248
|
+
# Temporarily disable post-checkout hook during startup
|
|
249
|
+
if [ -f '.git/hooks/post-checkout' ] && [ -x '.git/hooks/post-checkout' ]; then
|
|
250
|
+
mv '.git/hooks/post-checkout' '.git/hooks/post-checkout.disabled' 2>/dev/null || true
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Optimize git performance for startup operations
|
|
254
|
+
git config --local fetch.prune true 2>/dev/null || true
|
|
255
|
+
git config --local diff.autoRefreshIndex false 2>/dev/null || true
|
|
256
|
+
|
|
257
|
+
# Fetch latest refs from remote (required for new branches not known locally)
|
|
258
|
+
git fetch --depth=50 origin '$branch' 2>&1 || true
|
|
259
|
+
|
|
260
|
+
# Checkout branch
|
|
261
|
+
git checkout '$branch' 2>&1 || exit \$?
|
|
262
|
+
|
|
263
|
+
# Reset package-lock.json to avoid merge conflicts from npm version differences
|
|
264
|
+
git checkout -- package-lock.json '**/package-lock.json' 2>/dev/null || true
|
|
265
|
+
|
|
266
|
+
# Pull latest changes
|
|
267
|
+
git pull --depth=50 --no-stat 2>&1
|
|
268
|
+
pull_exit=\$?
|
|
269
|
+
|
|
270
|
+
# Re-enable post-checkout hook
|
|
271
|
+
if [ -f '.git/hooks/post-checkout.disabled' ]; then
|
|
272
|
+
mv '.git/hooks/post-checkout.disabled' '.git/hooks/post-checkout' 2>/dev/null || true
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
exit \$pull_exit
|
|
276
|
+
" 2>&1 | while IFS= read -r line; do
|
|
277
|
+
# Filter out routine messages, keep important ones
|
|
278
|
+
case "$line" in
|
|
279
|
+
*"Already on"*|*"Your branch is up to date"*|*"Already up to date"*|*"Warning: Permanently added"*)
|
|
280
|
+
# Skip routine messages
|
|
281
|
+
;;
|
|
282
|
+
*)
|
|
283
|
+
# Show important messages (errors, updates, etc.)
|
|
284
|
+
echo " $line"
|
|
285
|
+
[ -n "$LOG_FILE" ] && echo " $line" >> "$LOG_FILE"
|
|
286
|
+
;;
|
|
287
|
+
esac
|
|
288
|
+
done
|
|
289
|
+
local update_exit=${PIPESTATUS[0]}
|
|
290
|
+
else
|
|
291
|
+
# Already running as coder, execute directly
|
|
292
|
+
(
|
|
293
|
+
cd "$repo_path" || exit 1
|
|
294
|
+
|
|
295
|
+
# Temporarily disable post-checkout hook during startup
|
|
296
|
+
if [ -f '.git/hooks/post-checkout' ] && [ -x '.git/hooks/post-checkout' ]; then
|
|
297
|
+
mv '.git/hooks/post-checkout' '.git/hooks/post-checkout.disabled' 2>/dev/null || true
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Optimize git performance for startup operations
|
|
301
|
+
git config --local fetch.prune true 2>/dev/null || true
|
|
302
|
+
git config --local diff.autoRefreshIndex false 2>/dev/null || true
|
|
303
|
+
|
|
304
|
+
# Fetch latest refs from remote (required for new branches not known locally)
|
|
305
|
+
git fetch --depth=50 origin "$branch" 2>&1 || true
|
|
306
|
+
|
|
307
|
+
# Checkout branch
|
|
308
|
+
git checkout "$branch" 2>&1 || exit $?
|
|
309
|
+
|
|
310
|
+
# Reset package-lock.json to avoid merge conflicts from npm version differences
|
|
311
|
+
git checkout -- package-lock.json '**/package-lock.json' 2>/dev/null || true
|
|
312
|
+
|
|
313
|
+
# Pull latest changes
|
|
314
|
+
git pull --depth=50 --no-stat 2>&1
|
|
315
|
+
pull_exit=$?
|
|
316
|
+
|
|
317
|
+
# Re-enable post-checkout hook
|
|
318
|
+
if [ -f '.git/hooks/post-checkout.disabled' ]; then
|
|
319
|
+
mv '.git/hooks/post-checkout.disabled' '.git/hooks/post-checkout' 2>/dev/null || true
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
exit $pull_exit
|
|
323
|
+
) 2>&1 | while IFS= read -r line; do
|
|
324
|
+
# Filter out routine messages, keep important ones
|
|
325
|
+
case "$line" in
|
|
326
|
+
*"Already on"*|*"Your branch is up to date"*|*"Already up to date"*|*"Warning: Permanently added"*)
|
|
327
|
+
# Skip routine messages
|
|
328
|
+
;;
|
|
329
|
+
*)
|
|
330
|
+
# Show important messages (errors, updates, etc.)
|
|
331
|
+
echo " $line"
|
|
332
|
+
[ -n "$LOG_FILE" ] && echo " $line" >> "$LOG_FILE"
|
|
333
|
+
;;
|
|
334
|
+
esac
|
|
335
|
+
done
|
|
336
|
+
local update_exit=${PIPESTATUS[0]}
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
if [ $update_exit -eq 0 ]; then
|
|
340
|
+
timing_checkpoint "update_repositories: $repo_name git pull SUCCESS"
|
|
341
|
+
else
|
|
342
|
+
timing_checkpoint "update_repositories: $repo_name git pull FAILED"
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
timing_checkpoint "update_repositories: Updating $repo_name END"
|
|
346
|
+
return $update_exit
|
|
347
|
+
else
|
|
348
|
+
# Fallback: clone if not present (shouldn't happen in proper setup)
|
|
349
|
+
log "Cloning $repo_name from $repo_url (branch: $branch)..." >&2
|
|
350
|
+
|
|
351
|
+
# Ensure parent directory exists
|
|
352
|
+
mkdir -p "$(dirname "$repo_path")"
|
|
353
|
+
|
|
354
|
+
# Run clone as coder user with environment preserved
|
|
355
|
+
if [ "$(id -u)" -eq 0 ]; then
|
|
356
|
+
# Running as root, use runuser
|
|
357
|
+
GIT_SSH_COMMAND="$GIT_SSH_COMMAND" runuser -u coder -- bash -c "
|
|
358
|
+
if git clone --depth=50 --no-single-branch --quiet --branch '$branch' '$repo_url' '$repo_path' 2>&1; then
|
|
359
|
+
echo 'Successfully cloned $repo_name' >&2
|
|
360
|
+
exit 0
|
|
361
|
+
else
|
|
362
|
+
echo 'ERROR: Failed to clone $repo_name from $repo_url' >&2
|
|
363
|
+
exit 1
|
|
364
|
+
fi
|
|
365
|
+
"
|
|
366
|
+
else
|
|
367
|
+
# Already running as coder
|
|
368
|
+
if git clone --depth=50 --no-single-branch --quiet --branch "$branch" "$repo_url" "$repo_path" 2>&1; then
|
|
369
|
+
echo "Successfully cloned $repo_name" >&2
|
|
370
|
+
exit 0
|
|
371
|
+
else
|
|
372
|
+
echo "ERROR: Failed to clone $repo_name from $repo_url" >&2
|
|
373
|
+
exit 1
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
376
|
+
return $?
|
|
377
|
+
fi
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Run update either in background or inline
|
|
381
|
+
if [ "$parallel_updates" = "true" ]; then
|
|
382
|
+
# Launch in background and track PID
|
|
383
|
+
update_single_repo "$repo_name" "$repo_url" "$repo_branch" "$repo_path" &
|
|
384
|
+
update_pids+=($!)
|
|
385
|
+
else
|
|
386
|
+
# Run immediately (sequential)
|
|
387
|
+
update_single_repo "$repo_name" "$repo_url" "$repo_branch" "$repo_path"
|
|
388
|
+
fi
|
|
389
|
+
done
|
|
390
|
+
|
|
391
|
+
# Wait for all background jobs if using parallel mode
|
|
392
|
+
if [ "$parallel_updates" = "true" ]; then
|
|
393
|
+
timing_checkpoint "update_repositories: Waiting for parallel updates"
|
|
394
|
+
log "Waiting for ${#update_pids[@]} repository updates to complete..." >&2
|
|
395
|
+
|
|
396
|
+
local failed_count=0
|
|
397
|
+
for pid in "${update_pids[@]}"; do
|
|
398
|
+
if ! wait $pid; then
|
|
399
|
+
failed_count=$((failed_count + 1))
|
|
400
|
+
fi
|
|
401
|
+
done
|
|
402
|
+
|
|
403
|
+
if [ $failed_count -gt 0 ]; then
|
|
404
|
+
log_error "$failed_count repository update(s) failed" >&2
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
log "Repository update complete" >&2
|
|
409
|
+
timing_checkpoint "update_repositories: END (all repos complete)"
|
|
410
|
+
return 0
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
# Execute environment setup script if provided
|
|
414
|
+
run_setup_script() {
|
|
415
|
+
if [ -z "$SETUP_SCRIPT" ]; then
|
|
416
|
+
log "No setup script specified" >&2
|
|
417
|
+
return 0
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
if [ ! -f "$SETUP_SCRIPT" ]; then
|
|
421
|
+
log_error "Setup script not found: $SETUP_SCRIPT" >&2
|
|
422
|
+
return 1
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
log "Executing setup script: $SETUP_SCRIPT" >&2
|
|
426
|
+
|
|
427
|
+
# Run setup script as coder user with .bashrc sourced
|
|
428
|
+
if [ "$(id -u)" -eq 0 ]; then
|
|
429
|
+
# Running as root, use runuser
|
|
430
|
+
local setup_output=$(runuser -u coder -- bash -c "
|
|
431
|
+
# Source .bashrc if it exists to get user environment (for env vars like NODE_PATH, etc.)
|
|
432
|
+
[ -f ~/.bashrc ] && source ~/.bashrc 2>/dev/null || true
|
|
433
|
+
# Execute the setup script
|
|
434
|
+
bash '$SETUP_SCRIPT'
|
|
435
|
+
" 2>&1)
|
|
436
|
+
local setup_exit=$?
|
|
437
|
+
else
|
|
438
|
+
# Already running as coder
|
|
439
|
+
local setup_output=$(bash -c "
|
|
440
|
+
# Source .bashrc if it exists to get user environment (for env vars like NODE_PATH, etc.)
|
|
441
|
+
[ -f ~/.bashrc ] && source ~/.bashrc 2>/dev/null || true
|
|
442
|
+
# Execute the setup script
|
|
443
|
+
bash '$SETUP_SCRIPT'
|
|
444
|
+
" 2>&1)
|
|
445
|
+
local setup_exit=$?
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
# Output the captured setup script output to stderr for logging
|
|
449
|
+
if [ -n "$setup_output" ]; then
|
|
450
|
+
echo "$setup_output" >&2
|
|
451
|
+
fi
|
|
452
|
+
|
|
453
|
+
if [ $setup_exit -eq 0 ]; then
|
|
454
|
+
log "Setup script completed successfully" >&2
|
|
455
|
+
return 0
|
|
456
|
+
else
|
|
457
|
+
log_error "Setup script failed with exit code $setup_exit" >&2
|
|
458
|
+
return 1
|
|
459
|
+
fi
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# Generate git patches from changed repositories
|
|
463
|
+
# Optimized version: parallel processing, uses git diff --stat for faster stats
|
|
464
|
+
generate_patches() {
|
|
465
|
+
local workspace_dir="${WORKSPACE_DIR:-/workspace}"
|
|
466
|
+
local patches_dir="$TASK_OUTPUT_DIR/patches"
|
|
467
|
+
local repos_changed_json="[]"
|
|
468
|
+
|
|
469
|
+
log "Checking for repository changes in $workspace_dir..." >&2
|
|
470
|
+
|
|
471
|
+
# Create patches directory and temp directory for parallel results
|
|
472
|
+
mkdir -p "$patches_dir"
|
|
473
|
+
local temp_dir=$(mktemp -d)
|
|
474
|
+
trap "rm -rf $temp_dir" RETURN
|
|
475
|
+
|
|
476
|
+
# Check if workspace directory exists
|
|
477
|
+
if [ ! -d "$workspace_dir" ]; then
|
|
478
|
+
log "No workspace directory found at $workspace_dir" >&2
|
|
479
|
+
echo "$repos_changed_json"
|
|
480
|
+
return
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
# Helper function to process a single repository (runs in subshell for parallel execution)
|
|
484
|
+
# Writes JSON result to a temp file instead of stdout
|
|
485
|
+
_process_repo_parallel() {
|
|
486
|
+
local repo_name="$1"
|
|
487
|
+
local repo_path="$2"
|
|
488
|
+
local output_file="$3"
|
|
489
|
+
local patches_dir="$4"
|
|
490
|
+
local ignore_lockfile="${IGNORE_LOCKFILE_CHANGES:-false}"
|
|
491
|
+
|
|
492
|
+
cd "$repo_path" || return 1
|
|
493
|
+
|
|
494
|
+
# Quick check: any changes at all? (single git status call)
|
|
495
|
+
local status_output=$(git status --porcelain 2>/dev/null)
|
|
496
|
+
if [ -z "$status_output" ]; then
|
|
497
|
+
log "No changes in $repo_name" >&2
|
|
498
|
+
return 1
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
log "Changes detected in $repo_name, generating patch..." >&2
|
|
502
|
+
|
|
503
|
+
# Stage all changes
|
|
504
|
+
git add -A
|
|
505
|
+
|
|
506
|
+
# Generate patch with optional lockfile exclusion
|
|
507
|
+
local patch_file="$repo_name.patch"
|
|
508
|
+
if [ "$ignore_lockfile" = "true" ]; then
|
|
509
|
+
git diff --cached -- ':!package-lock.json' ':!**/package-lock.json' > "$patches_dir/$patch_file"
|
|
510
|
+
else
|
|
511
|
+
git diff --cached > "$patches_dir/$patch_file"
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
# Check if patch has content
|
|
515
|
+
if [ ! -s "$patches_dir/$patch_file" ]; then
|
|
516
|
+
log "No changes to patch for $repo_name" >&2
|
|
517
|
+
rm -f "$patches_dir/$patch_file"
|
|
518
|
+
return 1
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# Use git diff --stat for fast stats
|
|
522
|
+
local stat_output
|
|
523
|
+
if [ "$ignore_lockfile" = "true" ]; then
|
|
524
|
+
stat_output=$(git diff --cached --stat -- ':!package-lock.json' ':!**/package-lock.json' 2>/dev/null | tail -1)
|
|
525
|
+
else
|
|
526
|
+
stat_output=$(git diff --cached --stat 2>/dev/null | tail -1)
|
|
527
|
+
fi
|
|
528
|
+
|
|
529
|
+
# Parse stat output: "X files changed, Y insertions(+), Z deletions(-)"
|
|
530
|
+
local files_changed=0
|
|
531
|
+
local lines_added=0
|
|
532
|
+
local lines_deleted=0
|
|
533
|
+
|
|
534
|
+
if [ -n "$stat_output" ]; then
|
|
535
|
+
files_changed=$(echo "$stat_output" | grep -oE '[0-9]+ file' | grep -oE '[0-9]+' || echo "0")
|
|
536
|
+
lines_added=$(echo "$stat_output" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
|
|
537
|
+
lines_deleted=$(echo "$stat_output" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
# Ensure we have valid numbers
|
|
541
|
+
files_changed=${files_changed:-0}
|
|
542
|
+
lines_added=${lines_added:-0}
|
|
543
|
+
lines_deleted=${lines_deleted:-0}
|
|
544
|
+
|
|
545
|
+
# Get list of changed files using git diff --name-only
|
|
546
|
+
local files_list
|
|
547
|
+
if [ "$ignore_lockfile" = "true" ]; then
|
|
548
|
+
files_list=$(git diff --cached --name-only -- ':!package-lock.json' ':!**/package-lock.json' 2>/dev/null)
|
|
549
|
+
else
|
|
550
|
+
files_list=$(git diff --cached --name-only 2>/dev/null)
|
|
551
|
+
fi
|
|
552
|
+
local files_detail=$(echo "$files_list" | jq -R -s -c 'split("\n") | map(select(length > 0)) | map({path: ., added: 0, deleted: 0})')
|
|
553
|
+
|
|
554
|
+
# Fallback if jq fails
|
|
555
|
+
if [ -z "$files_detail" ] || [ "$files_detail" = "null" ]; then
|
|
556
|
+
files_detail='[]'
|
|
557
|
+
fi
|
|
558
|
+
|
|
559
|
+
log "Patch generated: $patch_file ($files_changed files, +$lines_added -$lines_deleted)" >&2
|
|
560
|
+
|
|
561
|
+
# Write JSON to output file (for parallel collection)
|
|
562
|
+
cat > "$output_file" <<REPO_EOF
|
|
563
|
+
{
|
|
564
|
+
"name": "$repo_name",
|
|
565
|
+
"path": "$repo_path",
|
|
566
|
+
"files_changed": $files_changed,
|
|
567
|
+
"lines_added": $lines_added,
|
|
568
|
+
"lines_deleted": $lines_deleted,
|
|
569
|
+
"patch_file": "$patch_file",
|
|
570
|
+
"files": $files_detail
|
|
571
|
+
}
|
|
572
|
+
REPO_EOF
|
|
573
|
+
return 0
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
# Export function and variables for subshells
|
|
577
|
+
export -f _process_repo_parallel
|
|
578
|
+
export -f log
|
|
579
|
+
export IGNORE_LOCKFILE_CHANGES
|
|
580
|
+
|
|
581
|
+
local pids=()
|
|
582
|
+
local repo_index=0
|
|
583
|
+
|
|
584
|
+
# If REPOS_CONFIG is set, use it to find repositories (respects path property)
|
|
585
|
+
if [ -n "$REPOS_CONFIG" ]; then
|
|
586
|
+
local config_repo_count=$(echo "$REPOS_CONFIG" | jq 'length' 2>/dev/null)
|
|
587
|
+
|
|
588
|
+
if [ -n "$config_repo_count" ] && [ "$config_repo_count" -gt 0 ]; then
|
|
589
|
+
log "Using REPOS_CONFIG to check $config_repo_count repositories (parallel)" >&2
|
|
590
|
+
|
|
591
|
+
for index in $(seq 0 $((config_repo_count - 1))); do
|
|
592
|
+
local repo_json=$(echo "$REPOS_CONFIG" | jq -c ".[$index]" 2>/dev/null)
|
|
593
|
+
|
|
594
|
+
if [ -z "$repo_json" ] || [ "$repo_json" = "null" ]; then
|
|
595
|
+
continue
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
local repo_name=$(echo "$repo_json" | jq -r 'if type == "object" then (.name // empty) else . end' 2>/dev/null)
|
|
599
|
+
local repo_path_from_json=$(echo "$repo_json" | jq -r 'if type == "object" then (.path // empty) else empty end' 2>/dev/null)
|
|
600
|
+
|
|
601
|
+
if [ -z "$repo_name" ] || [ "$repo_name" = "null" ]; then
|
|
602
|
+
continue
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# Use path field from JSON if available, otherwise default to repo_name
|
|
606
|
+
local repo_path
|
|
607
|
+
if [ -n "$repo_path_from_json" ] && [ "$repo_path_from_json" != "null" ]; then
|
|
608
|
+
repo_path="$workspace_dir/$repo_path_from_json"
|
|
609
|
+
else
|
|
610
|
+
repo_path="$workspace_dir/$repo_name"
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
# Check if this is a git repository
|
|
614
|
+
if [ ! -d "$repo_path/.git" ]; then
|
|
615
|
+
log "Skipping $repo_name (not a git repository at $repo_path)" >&2
|
|
616
|
+
continue
|
|
617
|
+
fi
|
|
618
|
+
|
|
619
|
+
# Launch repo processing in background
|
|
620
|
+
local output_file="$temp_dir/repo_$repo_index.json"
|
|
621
|
+
(
|
|
622
|
+
_process_repo_parallel "$repo_name" "$repo_path" "$output_file" "$patches_dir"
|
|
623
|
+
) &
|
|
624
|
+
pids+=($!)
|
|
625
|
+
repo_index=$((repo_index + 1))
|
|
626
|
+
done
|
|
627
|
+
fi
|
|
628
|
+
else
|
|
629
|
+
# Fallback: no REPOS_CONFIG, scan top-level directories
|
|
630
|
+
log "No REPOS_CONFIG found, scanning top-level directories (parallel)" >&2
|
|
631
|
+
for repo_path in "$workspace_dir"/*; do
|
|
632
|
+
if [ -d "$repo_path/.git" ]; then
|
|
633
|
+
local repo_name=$(basename "$repo_path")
|
|
634
|
+
|
|
635
|
+
# Launch repo processing in background
|
|
636
|
+
local output_file="$temp_dir/repo_$repo_index.json"
|
|
637
|
+
(
|
|
638
|
+
_process_repo_parallel "$repo_name" "$repo_path" "$output_file" "$patches_dir"
|
|
639
|
+
) &
|
|
640
|
+
pids+=($!)
|
|
641
|
+
repo_index=$((repo_index + 1))
|
|
642
|
+
fi
|
|
643
|
+
done
|
|
644
|
+
fi
|
|
645
|
+
|
|
646
|
+
# Wait for all background jobs to complete
|
|
647
|
+
for pid in "${pids[@]}"; do
|
|
648
|
+
wait $pid 2>/dev/null || true
|
|
649
|
+
done
|
|
650
|
+
|
|
651
|
+
# Collect results from temp files into JSON array
|
|
652
|
+
local repo_count=0
|
|
653
|
+
for result_file in "$temp_dir"/repo_*.json; do
|
|
654
|
+
if [ -f "$result_file" ] && [ -s "$result_file" ]; then
|
|
655
|
+
local repo_result=$(cat "$result_file")
|
|
656
|
+
if [ -n "$repo_result" ]; then
|
|
657
|
+
if [ "$repo_count" -eq 0 ]; then
|
|
658
|
+
repos_changed_json="[$repo_result"
|
|
659
|
+
else
|
|
660
|
+
repos_changed_json="$repos_changed_json,$repo_result"
|
|
661
|
+
fi
|
|
662
|
+
repo_count=$((repo_count + 1))
|
|
663
|
+
fi
|
|
664
|
+
fi
|
|
665
|
+
done
|
|
666
|
+
|
|
667
|
+
# Close JSON array
|
|
668
|
+
if [ "$repo_count" -gt 0 ]; then
|
|
669
|
+
repos_changed_json="$repos_changed_json]"
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
log "Patch generation complete: $repo_count repositories with changes" >&2
|
|
673
|
+
echo "$repos_changed_json"
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
# Generate task.json metadata file
|
|
677
|
+
generate_task_json() {
|
|
678
|
+
local exit_code=$1
|
|
679
|
+
shift
|
|
680
|
+
local end_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
681
|
+
local status="completed"
|
|
682
|
+
|
|
683
|
+
if [ $exit_code -ne 0 ]; then
|
|
684
|
+
status="failed"
|
|
685
|
+
fi
|
|
686
|
+
|
|
687
|
+
# Source environment variables set by setup script (e.g., IGNORE_LOCKFILE_CHANGES)
|
|
688
|
+
if [ -f ~/.bash_env ]; then
|
|
689
|
+
source ~/.bash_env
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
# Generate patches and get repos_changed JSON
|
|
693
|
+
local repos_changed=$(generate_patches)
|
|
694
|
+
|
|
695
|
+
# Read commit message if available
|
|
696
|
+
local commit_message=""
|
|
697
|
+
local commit_message_file="$TASK_OUTPUT_DIR/commit-message.txt"
|
|
698
|
+
if [ -f "$commit_message_file" ] && [ -s "$commit_message_file" ]; then
|
|
699
|
+
commit_message=$(cat "$commit_message_file")
|
|
700
|
+
# Properly escape commit message for JSON
|
|
701
|
+
commit_message="${commit_message//\\/\\\\}" # Escape backslashes first
|
|
702
|
+
commit_message="${commit_message//\"/\\\"}" # Escape quotes
|
|
703
|
+
commit_message="${commit_message//$'\n'/\\n}" # Escape newlines
|
|
704
|
+
commit_message="${commit_message//$'\r'/\\r}" # Escape carriage returns
|
|
705
|
+
commit_message="${commit_message//$'\t'/\\t}" # Escape tabs
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
# Properly escape command for JSON
|
|
709
|
+
local escaped_command="$*"
|
|
710
|
+
escaped_command="${escaped_command//\\/\\\\}" # Escape backslashes first
|
|
711
|
+
escaped_command="${escaped_command//\"/\\\"}" # Escape quotes
|
|
712
|
+
escaped_command="${escaped_command//$'\n'/\\n}" # Escape newlines
|
|
713
|
+
escaped_command="${escaped_command//$'\r'/\\r}" # Escape carriage returns
|
|
714
|
+
escaped_command="${escaped_command//$'\t'/\\t}" # Escape tabs
|
|
715
|
+
|
|
716
|
+
# Create task.json in the output directory
|
|
717
|
+
cat > "$TASK_OUTPUT_DIR/task.json" <<EOF
|
|
718
|
+
{
|
|
719
|
+
"task_id": "$TASK_ID",
|
|
720
|
+
"task_type": "$TASK_TYPE",
|
|
721
|
+
"environment": "$ENVIRONMENT",
|
|
722
|
+
"status": "$status",
|
|
723
|
+
"started_at": "$START_TIME",
|
|
724
|
+
"completed_at": "$end_time",
|
|
725
|
+
"exit_code": $exit_code,
|
|
726
|
+
"container_id": "$(hostname)",
|
|
727
|
+
"command": "$escaped_command",
|
|
728
|
+
"parameters": $PARAMETERS_JSON,
|
|
729
|
+
"env_vars": $ENV_VARS_JSON,
|
|
730
|
+
"summary_path": "summary.md",
|
|
731
|
+
"log_path": "log.txt",
|
|
732
|
+
"instructions_path": "task-instructions.md",
|
|
733
|
+
"commit_message": "$commit_message",
|
|
734
|
+
"repos_changed": $repos_changed
|
|
735
|
+
}
|
|
736
|
+
EOF
|
|
737
|
+
|
|
738
|
+
log "Task metadata written to $TASK_OUTPUT_DIR/task.json"
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
# Main execution
|
|
742
|
+
main() {
|
|
743
|
+
timing_checkpoint "main: START"
|
|
744
|
+
log "CoderFlow task execution starting..."
|
|
745
|
+
log "Working directory: $(pwd)"
|
|
746
|
+
|
|
747
|
+
setup_git_credentials
|
|
748
|
+
setup_git_user_identity
|
|
749
|
+
setup_credential_helper_cache
|
|
750
|
+
|
|
751
|
+
# Log once if skipping all initialization (for follow-up tasks)
|
|
752
|
+
if [ "$SKIP_INIT" = "true" ]; then
|
|
753
|
+
log "Skipping initialization: repository updates, local state, AGENTS.md, setup script (SKIP_INIT=true)"
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
timing_checkpoint "main: Before repository updates"
|
|
757
|
+
# Update repositories before task execution (pull if exists, clone if not)
|
|
758
|
+
# Skip if SKIP_INIT is set (e.g., for follow-up tasks where initialization is already done)
|
|
759
|
+
# Always update repos first, even with local state (get latest from remote, then apply local changes on top)
|
|
760
|
+
# For interactive containers WITHOUT local state, run in background to avoid blocking terminal startup
|
|
761
|
+
# For task containers OR when local state will be applied, run synchronously
|
|
762
|
+
if [ "$SKIP_INIT" = "true" ]; then
|
|
763
|
+
timing_checkpoint "main: Repository updates skipped"
|
|
764
|
+
elif [ "$CONTAINER_MODE" = "interactive" ] && [ ! -f "/task-output/local-state.json" ]; then
|
|
765
|
+
log "Starting repository updates in background..."
|
|
766
|
+
update_repositories &
|
|
767
|
+
REPO_UPDATE_PID=$!
|
|
768
|
+
timing_checkpoint "main: Repository updates backgrounded (PID: $REPO_UPDATE_PID)"
|
|
769
|
+
else
|
|
770
|
+
update_repositories
|
|
771
|
+
timing_checkpoint "main: Repository updates complete (synchronous)"
|
|
772
|
+
fi
|
|
773
|
+
|
|
774
|
+
timing_checkpoint "main: Before local state application"
|
|
775
|
+
# Apply local repository state if provided (branch setup, staged/unstaged changes, untracked files)
|
|
776
|
+
# Skip if SKIP_INIT is set
|
|
777
|
+
# Local state is applied ON TOP of the latest remote state (git pull already completed synchronously above)
|
|
778
|
+
if [ "$SKIP_INIT" = "true" ]; then
|
|
779
|
+
timing_checkpoint "main: Local state application skipped"
|
|
780
|
+
elif [ -f "/task-output/local-state.json" ]; then
|
|
781
|
+
log "Applying local repository state..."
|
|
782
|
+
# Run as coder user to avoid permission issues with git files
|
|
783
|
+
if [ "$(id -u)" -eq 0 ]; then
|
|
784
|
+
# Running as root, use runuser
|
|
785
|
+
if runuser -u coder bash /usr/local/bin/apply-local-state.sh; then
|
|
786
|
+
log "Local state applied successfully" >&2
|
|
787
|
+
timing_checkpoint "main: Local state application complete"
|
|
788
|
+
else
|
|
789
|
+
log_error "Failed to apply local state (non-fatal, continuing...)" >&2
|
|
790
|
+
timing_checkpoint "main: Local state application failed"
|
|
791
|
+
fi
|
|
792
|
+
else
|
|
793
|
+
# Already running as coder
|
|
794
|
+
if bash /usr/local/bin/apply-local-state.sh; then
|
|
795
|
+
log "Local state applied successfully" >&2
|
|
796
|
+
timing_checkpoint "main: Local state application complete"
|
|
797
|
+
else
|
|
798
|
+
log_error "Failed to apply local state (non-fatal, continuing...)" >&2
|
|
799
|
+
timing_checkpoint "main: Local state application failed"
|
|
800
|
+
fi
|
|
801
|
+
fi
|
|
802
|
+
|
|
803
|
+
# Create marker to signal local state is complete (startup script will wait for this)
|
|
804
|
+
touch /tmp/.local-state-ready 2>/dev/null || true
|
|
805
|
+
log "Local state ready marker created"
|
|
806
|
+
else
|
|
807
|
+
timing_checkpoint "main: No local state file found"
|
|
808
|
+
fi
|
|
809
|
+
|
|
810
|
+
# Copy AGENTS.md to workspace if available (centralized for all environments)
|
|
811
|
+
# This provides AI agents with environment-specific instructions
|
|
812
|
+
# NOTE: Only AGENTS.md is copied - README.md stays in the environment directory
|
|
813
|
+
# README.md is for human users/admins, not for AI agents
|
|
814
|
+
# Skip if SKIP_INIT is set (e.g., for follow-up tasks where AGENTS.md is already present)
|
|
815
|
+
if [ "$SKIP_INIT" = "true" ]; then
|
|
816
|
+
: # Skip - already logged above
|
|
817
|
+
elif [ -n "$ENVIRONMENT" ] && [ -f "/coder-setup/$ENVIRONMENT/AGENTS.md" ]; then
|
|
818
|
+
log "Copying AGENTS.md from /coder-setup/$ENVIRONMENT/" >&2
|
|
819
|
+
cp "/coder-setup/$ENVIRONMENT/AGENTS.md" /workspace/AGENTS.md
|
|
820
|
+
|
|
821
|
+
# Create agent-specific symlink based on CODER_AGENT or default_agent
|
|
822
|
+
AGENT="${CODER_AGENT:-${default_agent:-claude}}"
|
|
823
|
+
if [ "$AGENT" = "claude" ]; then
|
|
824
|
+
ln -sf /workspace/AGENTS.md /workspace/CLAUDE.md
|
|
825
|
+
log "AGENTS.md copied and CLAUDE.md symlink created" >&2
|
|
826
|
+
elif [ "$AGENT" = "gemini" ]; then
|
|
827
|
+
ln -sf /workspace/AGENTS.md /workspace/GEMINI.md
|
|
828
|
+
log "AGENTS.md copied and GEMINI.md symlink created" >&2
|
|
829
|
+
elif [ "$AGENT" = "codex" ]; then
|
|
830
|
+
# Codex doesn't use agent-specific files, just AGENTS.md
|
|
831
|
+
log "AGENTS.md copied (no agent-specific symlink for Codex)" >&2
|
|
832
|
+
else
|
|
833
|
+
log "AGENTS.md copied (unknown agent: $AGENT)" >&2
|
|
834
|
+
fi
|
|
835
|
+
else
|
|
836
|
+
log "No AGENTS.md found for environment: $ENVIRONMENT" >&2
|
|
837
|
+
fi
|
|
838
|
+
|
|
839
|
+
timing_checkpoint "main: Before setup script"
|
|
840
|
+
# Run environment-specific setup script when available
|
|
841
|
+
# Skip if SKIP_INIT is set (e.g., for follow-up tasks where environment is already configured)
|
|
842
|
+
if [ "$SKIP_INIT" = "true" ]; then
|
|
843
|
+
timing_checkpoint "main: Setup script skipped"
|
|
844
|
+
elif ! run_setup_script; then
|
|
845
|
+
log_error "Environment setup script failed; aborting task execution"
|
|
846
|
+
generate_task_json 1 "setup_script"
|
|
847
|
+
return 1
|
|
848
|
+
else
|
|
849
|
+
timing_checkpoint "main: Setup script complete"
|
|
850
|
+
fi
|
|
851
|
+
|
|
852
|
+
local exit_code=0
|
|
853
|
+
|
|
854
|
+
# Update status to indicate initialization is complete
|
|
855
|
+
echo "{\"progress\": 0.01, \"phase\": \"Ready\", \"updated_at\": \"$(date -Iseconds)\"}" > "$TASK_OUTPUT_DIR/.status"
|
|
856
|
+
timing_checkpoint "main: Status file written, ready for task execution"
|
|
857
|
+
|
|
858
|
+
# If SETUP_ONLY mode, return here without executing tasks (for interactive containers)
|
|
859
|
+
if [ "${SETUP_ONLY:-false}" = "true" ]; then
|
|
860
|
+
log "Setup complete (SETUP_ONLY mode), skipping task execution"
|
|
861
|
+
return 0
|
|
862
|
+
fi
|
|
863
|
+
|
|
864
|
+
# If STAGED_TASK mode, complete setup but skip agent execution (user will start later)
|
|
865
|
+
if [ "${STAGED_TASK:-false}" = "true" ]; then
|
|
866
|
+
echo "{\"progress\": 0.01, \"phase\": \"Staged\", \"updated_at\": \"$(date -Iseconds)\"}" > "$TASK_OUTPUT_DIR/.status"
|
|
867
|
+
log "Setup complete (STAGED_TASK mode), waiting for instructions"
|
|
868
|
+
return 0
|
|
869
|
+
fi
|
|
870
|
+
|
|
871
|
+
# If a command was passed, execute it
|
|
872
|
+
# Otherwise, execute default task
|
|
873
|
+
if [ $# -gt 0 ]; then
|
|
874
|
+
timing_checkpoint "main: Before task execution"
|
|
875
|
+
log "Executing task..."
|
|
876
|
+
|
|
877
|
+
{
|
|
878
|
+
# Source ~/.bashrc to load environment variables written by setup.sh
|
|
879
|
+
# This is needed for non-interactive shells (tasks) where .bashrc isn't auto-sourced
|
|
880
|
+
if [ -f ~/.bashrc ]; then
|
|
881
|
+
source ~/.bashrc
|
|
882
|
+
fi
|
|
883
|
+
"$@"
|
|
884
|
+
} 2>&1 | tee -a "$LOG_FILE"
|
|
885
|
+
exit_code=${PIPESTATUS[0]}
|
|
886
|
+
|
|
887
|
+
if [ $exit_code -eq 0 ]; then
|
|
888
|
+
log_success "Task completed successfully (exit code: $exit_code)"
|
|
889
|
+
else
|
|
890
|
+
log_error "Task failed (exit code: $exit_code)"
|
|
891
|
+
fi
|
|
892
|
+
else
|
|
893
|
+
# Default task: simple hello world
|
|
894
|
+
log "No command specified, executing default task..."
|
|
895
|
+
|
|
896
|
+
{
|
|
897
|
+
echo "Hello from CoderFlow!"
|
|
898
|
+
echo "Node.js version: $(node --version)"
|
|
899
|
+
echo "Git version: $(git --version)"
|
|
900
|
+
echo "Current directory: $(pwd)"
|
|
901
|
+
echo "Task completed successfully!"
|
|
902
|
+
} 2>&1 | tee -a "$LOG_FILE"
|
|
903
|
+
|
|
904
|
+
exit_code=0
|
|
905
|
+
log_success "Default task completed (exit code: $exit_code)"
|
|
906
|
+
fi
|
|
907
|
+
|
|
908
|
+
log "Task execution finished"
|
|
909
|
+
|
|
910
|
+
local summary_file="$TASK_OUTPUT_DIR/summary.md"
|
|
911
|
+
|
|
912
|
+
if [ ! -s "$summary_file" ]; then
|
|
913
|
+
if [ $exit_code -eq 0 ]; then
|
|
914
|
+
cat <<SUMMARY_EOF > "$summary_file"
|
|
915
|
+
Task ${TASK_ID} completed successfully.
|
|
916
|
+
|
|
917
|
+
- Task Type: ${TASK_TYPE}
|
|
918
|
+
- Environment: ${ENVIRONMENT}
|
|
919
|
+
SUMMARY_EOF
|
|
920
|
+
else
|
|
921
|
+
cat <<SUMMARY_EOF > "$summary_file"
|
|
922
|
+
Task ${TASK_ID} failed with exit code ${exit_code}.
|
|
923
|
+
|
|
924
|
+
- Task Type: ${TASK_TYPE}
|
|
925
|
+
- Environment: ${ENVIRONMENT}
|
|
926
|
+
SUMMARY_EOF
|
|
927
|
+
fi
|
|
928
|
+
log "Summary file created at $summary_file"
|
|
929
|
+
fi
|
|
930
|
+
|
|
931
|
+
# Generate task metadata
|
|
932
|
+
generate_task_json $exit_code "$@"
|
|
933
|
+
|
|
934
|
+
timing_checkpoint "main: END"
|
|
935
|
+
return $exit_code
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
# Run main and preserve exit code (unless SKIP_MAIN_EXECUTION is set)
|
|
939
|
+
if [ "${SKIP_MAIN_EXECUTION:-false}" != "true" ]; then
|
|
940
|
+
main "$@"
|
|
941
|
+
exit $?
|
|
942
|
+
fi
|