@osfactory/har 0.56.1 → 0.58.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/dist/index.js +1601 -593
- package/dist/templates/profiles/cli/profile.manifest.json +8 -0
- package/dist/templates/profiles/default/profile.manifest.json +9 -0
- package/dist/templates/profiles/ios/profile.manifest.json +9 -0
- package/dist/templates/runtime-bundles/pm2-runtime/README.md +4 -0
- package/dist/templates/runtime-bundles/pm2-runtime/attach.sh +22 -0
- package/dist/templates/runtime-bundles/pm2-runtime/ecosystem.agent.template.cjs +46 -0
- package/dist/templates/runtime-bundles/pm2-runtime/env.template +40 -0
- package/dist/templates/runtime-bundles/shared-kernel/README.md +3 -0
- package/dist/templates/runtime-bundles/shared-kernel/STAGES.md +99 -0
- package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +407 -0
- package/dist/templates/runtime-bundles/shared-kernel/stages/README.sh +11 -0
- package/dist/templates/runtime-bundles/xcode-sim/README.md +4 -0
- package/dist/templates/runtime-bundles/xcode-sim/simulator.sh +492 -0
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "default",
|
|
3
|
+
"description": "Web app — shared kernel + PM2 runtime + Docker infra",
|
|
4
|
+
"bundles": [
|
|
5
|
+
{ "id": "shared-kernel", "path": "runtime-bundles/shared-kernel" },
|
|
6
|
+
{ "id": "pm2-runtime", "path": "runtime-bundles/pm2-runtime" },
|
|
7
|
+
{ "id": "runtime-default", "path": "har-boilerplate" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "ios",
|
|
3
|
+
"description": "iOS — shared kernel + Xcode/Simulator overlay",
|
|
4
|
+
"bundles": [
|
|
5
|
+
{ "id": "shared-kernel", "path": "runtime-bundles/shared-kernel" },
|
|
6
|
+
{ "id": "xcode-sim", "path": "runtime-bundles/xcode-sim" },
|
|
7
|
+
{ "id": "runtime-ios", "path": "har-boilerplate-ios" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Attach to the tmux session for an agent.
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./.har/attach.sh <agent-id>
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
# shellcheck source=/dev/null
|
|
9
|
+
source "$SCRIPT_DIR/harness.env"
|
|
10
|
+
# shellcheck source=/dev/null
|
|
11
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
12
|
+
|
|
13
|
+
AGENT_ID="${1:?Usage: attach.sh <agent-id>}"
|
|
14
|
+
SESSION="$(har_tmux_session "$AGENT_ID")"
|
|
15
|
+
|
|
16
|
+
if ! tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
17
|
+
echo "No tmux session found: $SESSION" >&2
|
|
18
|
+
echo "Launch with: ./.har/launch.sh ${AGENT_ID} --claude" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
exec tmux attach -t "$SESSION"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PM2 Ecosystem Configuration for Agent ${AGENT_ID}
|
|
3
|
+
*
|
|
4
|
+
* Auto-generated by launch.sh — do not edit manually.
|
|
5
|
+
* Customize ecosystem.agent.template.cjs in .har/ to change processes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// launch.sh writes this file (and .env.agent.N) to the work dir root,
|
|
9
|
+
// so __dirname IS the work dir — repo root or the agent's git worktree.
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const dotenv = require('dotenv');
|
|
12
|
+
|
|
13
|
+
const agentEnvPath = path.resolve(__dirname, '.env.agent.${AGENT_ID}');
|
|
14
|
+
const env = dotenv.config({ path: agentEnvPath }).parsed || {};
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
apps: [
|
|
18
|
+
// TODO: Replace with the PRIMARY application's dev processes — the ONE app
|
|
19
|
+
// agents modify (HARNESS_PRIMARY_APP in harness.env). In a monolith or
|
|
20
|
+
// monorepo with several services, do NOT start them all here: supporting
|
|
21
|
+
// services the agent depends on but does not change run ONCE for all slots,
|
|
22
|
+
// either as compose services in docker-compose.agent.yml or via an optional
|
|
23
|
+
// .har/ecosystem.shared.config.cjs (started by setup-infra.sh, processes
|
|
24
|
+
// named "har-shared-<name>", fixed ports). A primary app may still need two
|
|
25
|
+
// processes (e.g. api + frontend) — add one entry per process.
|
|
26
|
+
// Example Node dev process. Replace with this repo's actual primary app
|
|
27
|
+
// command (Rails, Django, Go, Java, etc. should not keep this npm entry).
|
|
28
|
+
{
|
|
29
|
+
name: 'har-${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}-api',
|
|
30
|
+
script: 'npm',
|
|
31
|
+
args: 'run dev',
|
|
32
|
+
interpreter: 'none',
|
|
33
|
+
cwd: __dirname,
|
|
34
|
+
env: {
|
|
35
|
+
...env,
|
|
36
|
+
NODE_ENV: 'development',
|
|
37
|
+
PORT: env.API_PORT || env.PORT,
|
|
38
|
+
},
|
|
39
|
+
watch: false,
|
|
40
|
+
autorestart: true,
|
|
41
|
+
kill_timeout: 5000,
|
|
42
|
+
merge_logs: true,
|
|
43
|
+
time: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Agent environment — generated by launch.sh from env.template
|
|
2
|
+
# DO NOT EDIT — regenerated on every launch
|
|
3
|
+
AGENT_ID=${AGENT_ID}
|
|
4
|
+
|
|
5
|
+
# Ports (allocated at launch — may differ from BASE + AGENT_ID × STEP when busy)
|
|
6
|
+
PORT=${API_PORT}
|
|
7
|
+
FE_PORT=${FE_PORT}
|
|
8
|
+
API_PORT=${API_PORT}
|
|
9
|
+
DEBUG_PORT=${DEBUG_PORT}
|
|
10
|
+
|
|
11
|
+
# PostgreSQL
|
|
12
|
+
PGHOST=localhost
|
|
13
|
+
PGPORT=${DB_PORT}
|
|
14
|
+
PGDATABASE=agent_${AGENT_ID}
|
|
15
|
+
PGUSER=postgres
|
|
16
|
+
PGPASSWORD=password
|
|
17
|
+
DATABASE_URL=postgresql://postgres:password@localhost:${DB_PORT}/agent_${AGENT_ID}
|
|
18
|
+
|
|
19
|
+
# Application
|
|
20
|
+
NODE_ENV=development
|
|
21
|
+
SITE_URL=http://localhost:${API_PORT}
|
|
22
|
+
CORS_ORIGIN=http://localhost:${FE_PORT}
|
|
23
|
+
|
|
24
|
+
# MinIO / S3 (if enabled)
|
|
25
|
+
S3_ENDPOINT=http://localhost:${MINIO_PORT}
|
|
26
|
+
S3_ACCESS_KEY=minioadmin
|
|
27
|
+
S3_SECRET_KEY=minioadmin
|
|
28
|
+
S3_BUCKET=agent-${AGENT_ID}
|
|
29
|
+
S3_REGION=us-east-1
|
|
30
|
+
S3_FORCE_PATH_STYLE=true
|
|
31
|
+
|
|
32
|
+
# Headless browser (if enabled)
|
|
33
|
+
HEADLESS_BROWSER_URL=http://localhost:${BROWSER_PORT}
|
|
34
|
+
|
|
35
|
+
# Toolchain paths — appended by provision-toolchain.sh during launch
|
|
36
|
+
# NODE_BIN= NPM_BIN= PYTHON_BIN= VIRTUAL_ENV= GO_BIN= CARGO_BIN= JAVA_HOME=
|
|
37
|
+
|
|
38
|
+
# Mail (if enabled)
|
|
39
|
+
SMTP_HOST=localhost
|
|
40
|
+
SMTP_PORT=11025
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# HAR Stages — authoring guide
|
|
2
|
+
|
|
3
|
+
Stages are the harness's single vocabulary for runnable checks and lifecycle
|
|
4
|
+
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`, `trivy`, `semgrep`), your
|
|
5
|
+
project's test/lint commands, bespoke validation scripts — registers in
|
|
6
|
+
`.har/stages.json` with the same schema, and agents interact with stages only
|
|
7
|
+
through that registry (CLI `har env verify`, MCP `har_run_stage` /
|
|
8
|
+
`har_run_verification`), never through stack-specific tooling.
|
|
9
|
+
|
|
10
|
+
Philosophy: *plugins install stages; agents only talk to the stage registry.*
|
|
11
|
+
|
|
12
|
+
## The registry: `.har/stages.json`
|
|
13
|
+
|
|
14
|
+
```jsonc
|
|
15
|
+
{
|
|
16
|
+
"verificationStages": ["typecheck", "unit-tests", "api-health", "browser-e2e"],
|
|
17
|
+
"stages": [
|
|
18
|
+
{
|
|
19
|
+
"id": "browser-e2e", // stable, shell-friendly slug
|
|
20
|
+
"kind": "test", // setup | launch | verify | test | inspect | reset | teardown | custom
|
|
21
|
+
"description": "Playwright E2E",
|
|
22
|
+
"script": "stages/browser-e2e.sh", // relative to .har/ — OR use "command"
|
|
23
|
+
"requiresAgentId": true, // default true for test/verify/custom kinds
|
|
24
|
+
"artifacts": [{ "path": ".har/artifacts/browser-e2e", "kind": "directory" }]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "unit-tests-fast",
|
|
28
|
+
"kind": "test",
|
|
29
|
+
"command": "npm test -- --agent {agentId}" // {agentId} is substituted at run time
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Optional stage fields: `cwd` (working directory), `env` (extra env vars),
|
|
36
|
+
`group`, `acceptsArgs` (extra CLI args the stage accepts, e.g. `["--full"]`).
|
|
37
|
+
|
|
38
|
+
## Two ways to define a stage
|
|
39
|
+
|
|
40
|
+
**Command stages** — the default for simple checks (`npm test`, `swiftlint`,
|
|
41
|
+
`make check`). One JSON entry, zero files:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
har env add-stage unit-tests-fast --custom --kind test --command "npm test" --verification
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Script stages** — for anything that needs the slot's env, ports, or
|
|
48
|
+
artifacts. Scaffold a contract-compliant skeleton:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
har env add-stage db-integrity --custom --script --description "Check DB invariants"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
then implement the TODO block in `.har/stages/db-integrity.sh`.
|
|
55
|
+
|
|
56
|
+
## The stage script contract
|
|
57
|
+
|
|
58
|
+
Every script under `.har/stages/` must:
|
|
59
|
+
|
|
60
|
+
1. Source `harness.env` and `agent-slot.sh` from `.har/`. Before sourcing,
|
|
61
|
+
set `SCRIPT_DIR` to the `.har/` directory (not `stages/`) — `agent-slot.sh`
|
|
62
|
+
resolves the slot registry via `$SCRIPT_DIR/slots/...`.
|
|
63
|
+
2. Take the agent slot id as `$1` (validate with `validate_agent_id`); extra
|
|
64
|
+
args may follow.
|
|
65
|
+
3. Load the slot env via `resolve_agent_env_file` and run checks from the
|
|
66
|
+
agent's work dir (`resolve_agent_work_dir`).
|
|
67
|
+
4. Write artifacts (reports, screenshots, logs) under `.har/artifacts/<id>/`.
|
|
68
|
+
5. Print **only** the normalized JSON result object on stdout
|
|
69
|
+
(`status`, `stageId`, `agent_id`, `total_ms`, …); log progress to stderr.
|
|
70
|
+
6. Exit with the real status code (0 = pass).
|
|
71
|
+
|
|
72
|
+
The scaffolded skeleton implements all of this — replace its TODO block.
|
|
73
|
+
|
|
74
|
+
## Verification membership
|
|
75
|
+
|
|
76
|
+
Listing a stage id in `verificationStages` is what includes it in
|
|
77
|
+
`har env verify <id> --full`. Ids that match a registered stage run via their
|
|
78
|
+
script/command; ids without a registry entry (e.g. `typecheck`, `api-health`)
|
|
79
|
+
are inline steps owned by `.har/verify.sh`. Lifecycle kinds
|
|
80
|
+
(`setup`/`launch`/`reset`/`teardown`/`inspect`) and `verify` itself never run
|
|
81
|
+
as part of verification, even if listed.
|
|
82
|
+
|
|
83
|
+
## Commit gate
|
|
84
|
+
|
|
85
|
+
The registry also holds the optional `commitGate` config (installed via
|
|
86
|
+
`har hooks install`): `{ "commitGate": { "mode": "block" | "warn", "scope":
|
|
87
|
+
"worktrees" | "all" } }` controls whether unverified change batches may be
|
|
88
|
+
committed.
|
|
89
|
+
|
|
90
|
+
## Shipped plugins
|
|
91
|
+
|
|
92
|
+
`har env add-plugin --list` shows available plugins; `har env add-plugin
|
|
93
|
+
playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
|
|
94
|
+
add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
|
|
95
|
+
stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
|
|
96
|
+
or `har env add-plugin semgrep` (SAST, any stack)
|
|
97
|
+
installs one. A
|
|
98
|
+
plugin is just packaging: it copies files, merges `package.json` fragments,
|
|
99
|
+
and registers stages through the exact same registry as `add-stage --custom`.
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Provision the project toolchain and append resolved paths to .env.agent.<id>.
|
|
3
|
+
# Called from launch.sh after the agent env file is created.
|
|
4
|
+
#
|
|
5
|
+
# Configure in harness.env:
|
|
6
|
+
# HARNESS_ECOSYSTEM — auto (default) | node | python | go | rust | java | ruby | ios | none
|
|
7
|
+
# HARNESS_INSTALL_CMD — optional override (eval in HAR_WORK_DIR)
|
|
8
|
+
# Ecosystem-specific optional overrides:
|
|
9
|
+
# HARNESS_PYTHON_VENV_DIR — Python venv path relative to HAR_WORK_DIR (default: .har/venv)
|
|
10
|
+
#
|
|
11
|
+
# Required env from caller: HAR_WORK_DIR, HAR_ENV_FILE
|
|
12
|
+
# Optional: HAR_WORKTREE_DIR, HAR_REL_PREFIX, HAR_AGENT_ID (for logging)
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
: "${HAR_WORK_DIR:?HAR_WORK_DIR is required}"
|
|
16
|
+
: "${HAR_ENV_FILE:?HAR_ENV_FILE is required}"
|
|
17
|
+
|
|
18
|
+
HAR_WORKTREE_DIR="${HAR_WORKTREE_DIR:-}"
|
|
19
|
+
HAR_REL_PREFIX="${HAR_REL_PREFIX:-}"
|
|
20
|
+
HAR_AGENT_ID="${HAR_AGENT_ID:-}"
|
|
21
|
+
|
|
22
|
+
pt_log() {
|
|
23
|
+
if [ -n "$HAR_AGENT_ID" ]; then
|
|
24
|
+
echo "==> [agent-${HAR_AGENT_ID}] toolchain: $*" >&2
|
|
25
|
+
else
|
|
26
|
+
echo "==> [provision-toolchain] $*" >&2
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ── Node package manager helpers ──────────────────────────────────────────────
|
|
31
|
+
# Mirrored verbatim in harness.env and provision-toolchain.sh so a sourcing
|
|
32
|
+
# script and the provisioning subprocess resolve the same tool.
|
|
33
|
+
|
|
34
|
+
# Package managers HAR can drive, in fallback preference order.
|
|
35
|
+
HAR_NODE_PACKAGE_MANAGERS="npm bun pnpm yarn"
|
|
36
|
+
|
|
37
|
+
# Package manager the repo declares: an explicit HARNESS_NODE_PACKAGE_MANAGER
|
|
38
|
+
# wins, then package.json "packageManager", then the lockfile. Empty when the
|
|
39
|
+
# repo says nothing.
|
|
40
|
+
har_node_declared_package_manager() {
|
|
41
|
+
local dir="${1:-.}"
|
|
42
|
+
|
|
43
|
+
if [ -n "${HARNESS_NODE_PACKAGE_MANAGER:-}" ]; then
|
|
44
|
+
echo "$HARNESS_NODE_PACKAGE_MANAGER"
|
|
45
|
+
return
|
|
46
|
+
fi
|
|
47
|
+
if [ -f "$dir/package.json" ]; then
|
|
48
|
+
local field
|
|
49
|
+
field="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"\([a-z]*\)@.*/\1/p' "$dir/package.json" | head -1)"
|
|
50
|
+
if [ -n "$field" ]; then
|
|
51
|
+
echo "$field"
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
if [ -f "$dir/bun.lock" ] || [ -f "$dir/bun.lockb" ]; then echo bun; return; fi
|
|
56
|
+
if [ -f "$dir/pnpm-lock.yaml" ]; then echo pnpm; return; fi
|
|
57
|
+
if [ -f "$dir/yarn.lock" ]; then echo yarn; return; fi
|
|
58
|
+
if [ -f "$dir/package-lock.json" ]; then echo npm; return; fi
|
|
59
|
+
echo ""
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Package manager to actually run. A manager the repo declares but this machine
|
|
63
|
+
# lacks falls back to one that is installed, so a repo pinned to npm still
|
|
64
|
+
# provisions on a bun-only machine (and the reverse).
|
|
65
|
+
har_node_package_manager() {
|
|
66
|
+
local dir="${1:-.}"
|
|
67
|
+
local declared
|
|
68
|
+
declared="$(har_node_declared_package_manager "$dir")"
|
|
69
|
+
|
|
70
|
+
if [ -n "$declared" ] && command -v "$declared" >/dev/null 2>&1; then
|
|
71
|
+
echo "$declared"
|
|
72
|
+
return
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
local candidate
|
|
76
|
+
for candidate in $HAR_NODE_PACKAGE_MANAGERS; do
|
|
77
|
+
if command -v "$candidate" >/dev/null 2>&1; then
|
|
78
|
+
echo "$candidate"
|
|
79
|
+
return
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
echo "${declared:-npm}"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Lockfile a manager writes, so a substitute install can clean up after itself.
|
|
87
|
+
har_node_lockfile() {
|
|
88
|
+
case "$1" in
|
|
89
|
+
bun) echo "bun.lock" ;;
|
|
90
|
+
npm) echo "package-lock.json" ;;
|
|
91
|
+
pnpm) echo "pnpm-lock.yaml" ;;
|
|
92
|
+
yarn) echo "yarn.lock" ;;
|
|
93
|
+
*) echo "" ;;
|
|
94
|
+
esac
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Install dependencies in a directory. When a substitute manager stands in for
|
|
98
|
+
# the one the repo declares, any lockfile it creates is removed afterwards:
|
|
99
|
+
# provisioning must not migrate the repo to a different package manager.
|
|
100
|
+
# (bun writes bun.lock even under --no-save, so the flags alone are not enough.)
|
|
101
|
+
har_node_install() {
|
|
102
|
+
local dir="$1"
|
|
103
|
+
local manager="$2"
|
|
104
|
+
local declared="${3:-}"
|
|
105
|
+
local substituting=false
|
|
106
|
+
local lockfile=""
|
|
107
|
+
local had_lockfile=false
|
|
108
|
+
|
|
109
|
+
if [ -n "$declared" ] && [ "$declared" != "$manager" ]; then
|
|
110
|
+
substituting=true
|
|
111
|
+
lockfile="$(har_node_lockfile "$manager")"
|
|
112
|
+
if [ -n "$lockfile" ] && [ -e "$dir/$lockfile" ]; then
|
|
113
|
+
had_lockfile=true
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
local code=0
|
|
118
|
+
(cd "$dir" && "$manager" install --silent) || code=$?
|
|
119
|
+
|
|
120
|
+
if [ "$substituting" = true ] && [ "$had_lockfile" = false ] && [ -n "$lockfile" ]; then
|
|
121
|
+
rm -f "$dir/$lockfile"
|
|
122
|
+
[ "$manager" = "bun" ] && rm -f "$dir/bun.lockb"
|
|
123
|
+
fi
|
|
124
|
+
return $code
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Package runner (npx equivalent) for one-off CLIs such as pm2, including the
|
|
128
|
+
# flag that keeps it non-interactive. Takes a manager name, or none to resolve
|
|
129
|
+
# from the environment and PATH.
|
|
130
|
+
har_pkg_exec() {
|
|
131
|
+
case "${1:-${HARNESS_NODE_PACKAGE_MANAGER:-}}" in
|
|
132
|
+
bun) echo "bunx"; return ;;
|
|
133
|
+
pnpm) echo "pnpm dlx"; return ;;
|
|
134
|
+
yarn) echo "yarn dlx"; return ;;
|
|
135
|
+
npm) echo "npx --yes"; return ;;
|
|
136
|
+
esac
|
|
137
|
+
if command -v npx >/dev/null 2>&1; then echo "npx --yes"; return; fi
|
|
138
|
+
if command -v bunx >/dev/null 2>&1; then echo "bunx"; return; fi
|
|
139
|
+
if command -v pnpm >/dev/null 2>&1; then echo "pnpm dlx"; return; fi
|
|
140
|
+
echo "npx --yes"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
append_env() {
|
|
144
|
+
local key="$1"
|
|
145
|
+
local value="$2"
|
|
146
|
+
# %q, not a raw value: an unquoted value with spaces (an Xcode scheme, a
|
|
147
|
+
# simulator name, a path under /Applications) breaks `source .env.agent.<id>`.
|
|
148
|
+
printf '%s=%q\n' "$key" "$value" >> "$HAR_ENV_FILE"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
append_path_prefix() {
|
|
152
|
+
local prefix="$1"
|
|
153
|
+
[ -n "$prefix" ] && [ -d "$prefix" ] || return 0
|
|
154
|
+
append_env "PATH" "${prefix}:${PATH:-$PATH}"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
detect_ecosystem() {
|
|
158
|
+
local dir="$1"
|
|
159
|
+
local configured="${HARNESS_ECOSYSTEM:-auto}"
|
|
160
|
+
if [ -n "$configured" ] && [ "$configured" != "auto" ]; then
|
|
161
|
+
echo "$configured"
|
|
162
|
+
return
|
|
163
|
+
fi
|
|
164
|
+
if [ -f "$dir/package.json" ]; then echo node; return; fi
|
|
165
|
+
if [ -f "$dir/pyproject.toml" ] || [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ] \
|
|
166
|
+
|| [ -f "$dir/requirements.txt" ] || [ -f "$dir/Pipfile" ]; then
|
|
167
|
+
echo python
|
|
168
|
+
return
|
|
169
|
+
fi
|
|
170
|
+
if [ -f "$dir/go.mod" ]; then echo go; return; fi
|
|
171
|
+
if [ -f "$dir/Cargo.toml" ]; then echo rust; return; fi
|
|
172
|
+
if [ -f "$dir/pom.xml" ] || [ -f "$dir/build.gradle" ] || [ -f "$dir/build.gradle.kts" ]; then
|
|
173
|
+
echo java
|
|
174
|
+
return
|
|
175
|
+
fi
|
|
176
|
+
if [ -f "$dir/Gemfile" ]; then echo ruby; return; fi
|
|
177
|
+
if [ -n "${HARNESS_XCODE_SCHEME:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ] \
|
|
178
|
+
|| [ -n "${HARNESS_XCODE_WORKSPACE:-}" ]; then
|
|
179
|
+
echo ios
|
|
180
|
+
return
|
|
181
|
+
fi
|
|
182
|
+
echo none
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
run_install_cmd() {
|
|
186
|
+
local dir="$1"
|
|
187
|
+
if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
|
|
188
|
+
pt_log "Running HARNESS_INSTALL_CMD..."
|
|
189
|
+
(cd "$dir" && eval "$HARNESS_INSTALL_CMD")
|
|
190
|
+
return
|
|
191
|
+
fi
|
|
192
|
+
return 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
provision_node() {
|
|
196
|
+
local dir="$1"
|
|
197
|
+
local node_bin="node"
|
|
198
|
+
local pkg_manager declared_manager
|
|
199
|
+
declared_manager="$(har_node_declared_package_manager "$dir")"
|
|
200
|
+
pkg_manager="$(har_node_package_manager "$dir")"
|
|
201
|
+
|
|
202
|
+
if [ -n "$declared_manager" ] && [ "$declared_manager" != "$pkg_manager" ]; then
|
|
203
|
+
pt_log "Repo declares ${declared_manager}, which is not on PATH — installing with ${pkg_manager} and leaving the lockfile untouched."
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
if ! run_install_cmd "$dir"; then
|
|
207
|
+
pt_log "Installing Node dependencies with ${pkg_manager}..."
|
|
208
|
+
har_node_install "$dir" "$pkg_manager" "$declared_manager"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if command -v node >/dev/null 2>&1; then
|
|
212
|
+
node_bin="$(command -v node)"
|
|
213
|
+
elif [ "$pkg_manager" = "bun" ]; then
|
|
214
|
+
# bun-only machine: bun runs the `node -e` snippets verify.sh relies on.
|
|
215
|
+
node_bin="$(command -v bun)"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
local npm_bin="$pkg_manager"
|
|
219
|
+
if command -v "$pkg_manager" >/dev/null 2>&1; then
|
|
220
|
+
npm_bin="$(command -v "$pkg_manager")"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
append_env "HARNESS_ECOSYSTEM" "node"
|
|
224
|
+
append_env "NODE_BIN" "$node_bin"
|
|
225
|
+
append_env "NPM_BIN" "$npm_bin"
|
|
226
|
+
append_env "HARNESS_NODE_PACKAGE_MANAGER" "$pkg_manager"
|
|
227
|
+
append_env "HARNESS_PKG_EXEC" "$(har_pkg_exec "$pkg_manager")"
|
|
228
|
+
append_path_prefix "$dir/node_modules/.bin"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
provision_python() {
|
|
232
|
+
local dir="$1"
|
|
233
|
+
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
|
+
local venv_dir="$dir/$venv_rel"
|
|
235
|
+
local python_bin="python3"
|
|
236
|
+
|
|
237
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
238
|
+
pt_log "python3 not found on PATH — skipping venv provisioning"
|
|
239
|
+
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
|
+
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
|
+
return
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
if [ ! -d "$venv_dir" ]; then
|
|
245
|
+
pt_log "Creating Python venv at $venv_rel..."
|
|
246
|
+
if ! python3 -m venv "$venv_dir"; then
|
|
247
|
+
rm -rf "$venv_dir"
|
|
248
|
+
pt_log "python3 -m venv failed (on Debian/Ubuntu install python3-venv) — using system python3"
|
|
249
|
+
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
+
append_env "PYTHON_BIN" "$(command -v python3)"
|
|
251
|
+
return
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
+
pt_log "Python venv at $venv_rel is missing or broken — using system python3"
|
|
257
|
+
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
+
append_env "PYTHON_BIN" "$(command -v python3)"
|
|
259
|
+
return
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
python_bin="$venv_dir/bin/python"
|
|
263
|
+
# shellcheck disable=SC1091
|
|
264
|
+
source "$venv_dir/bin/activate"
|
|
265
|
+
|
|
266
|
+
if ! run_install_cmd "$dir"; then
|
|
267
|
+
pt_log "Installing Python dependencies..."
|
|
268
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
269
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
270
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
271
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
272
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
273
|
+
(cd "$dir" && pip install -q -e .)
|
|
274
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
275
|
+
(cd "$dir" && pipenv install --dev)
|
|
276
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
277
|
+
fi
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
append_env "HARNESS_ECOSYSTEM" "python"
|
|
281
|
+
append_env "PYTHON_BIN" "$python_bin"
|
|
282
|
+
append_env "VIRTUAL_ENV" "$venv_dir"
|
|
283
|
+
append_path_prefix "$venv_dir/bin"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
provision_go() {
|
|
287
|
+
local dir="$1"
|
|
288
|
+
if ! run_install_cmd "$dir"; then
|
|
289
|
+
if command -v go >/dev/null 2>&1; then
|
|
290
|
+
pt_log "Downloading Go modules..."
|
|
291
|
+
(cd "$dir" && go mod download)
|
|
292
|
+
else
|
|
293
|
+
pt_log "go not found on PATH — record paths only"
|
|
294
|
+
fi
|
|
295
|
+
fi
|
|
296
|
+
append_env "HARNESS_ECOSYSTEM" "go"
|
|
297
|
+
append_env "GO_BIN" "$(command -v go 2>/dev/null || echo go)"
|
|
298
|
+
if [ -n "${GOPATH:-}" ]; then append_env "GOPATH" "$GOPATH"; fi
|
|
299
|
+
if [ -n "${GOROOT:-}" ]; then append_env "GOROOT" "$GOROOT"; fi
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
provision_rust() {
|
|
303
|
+
local dir="$1"
|
|
304
|
+
if ! run_install_cmd "$dir"; then
|
|
305
|
+
if command -v cargo >/dev/null 2>&1; then
|
|
306
|
+
pt_log "Fetching Rust dependencies..."
|
|
307
|
+
(cd "$dir" && cargo fetch)
|
|
308
|
+
else
|
|
309
|
+
pt_log "cargo not found on PATH — record paths only"
|
|
310
|
+
fi
|
|
311
|
+
fi
|
|
312
|
+
append_env "HARNESS_ECOSYSTEM" "rust"
|
|
313
|
+
append_env "CARGO_BIN" "$(command -v cargo 2>/dev/null || echo cargo)"
|
|
314
|
+
append_env "RUSTC_BIN" "$(command -v rustc 2>/dev/null || echo rustc)"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
provision_java() {
|
|
318
|
+
local dir="$1"
|
|
319
|
+
run_install_cmd "$dir" || true
|
|
320
|
+
append_env "HARNESS_ECOSYSTEM" "java"
|
|
321
|
+
if [ -n "${JAVA_HOME:-}" ]; then
|
|
322
|
+
append_env "JAVA_HOME" "$JAVA_HOME"
|
|
323
|
+
append_path_prefix "$JAVA_HOME/bin"
|
|
324
|
+
fi
|
|
325
|
+
if command -v mvn >/dev/null 2>&1; then
|
|
326
|
+
append_env "MVN_BIN" "$(command -v mvn)"
|
|
327
|
+
elif [ -f "$dir/gradlew" ]; then
|
|
328
|
+
append_env "GRADLE_BIN" "$dir/gradlew"
|
|
329
|
+
elif command -v gradle >/dev/null 2>&1; then
|
|
330
|
+
append_env "GRADLE_BIN" "$(command -v gradle)"
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
provision_ruby() {
|
|
335
|
+
local dir="$1"
|
|
336
|
+
if ! run_install_cmd "$dir"; then
|
|
337
|
+
if command -v bundle >/dev/null 2>&1; then
|
|
338
|
+
pt_log "Installing Ruby gems..."
|
|
339
|
+
(cd "$dir" && bundle install --quiet)
|
|
340
|
+
else
|
|
341
|
+
pt_log "bundle not found on PATH — record paths only"
|
|
342
|
+
fi
|
|
343
|
+
fi
|
|
344
|
+
append_env "HARNESS_ECOSYSTEM" "ruby"
|
|
345
|
+
append_env "RUBY_BIN" "$(command -v ruby 2>/dev/null || echo ruby)"
|
|
346
|
+
append_env "BUNDLE_BIN" "$(command -v bundle 2>/dev/null || echo bundle)"
|
|
347
|
+
append_path_prefix "$dir/vendor/bundle/bin"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
provision_ios() {
|
|
351
|
+
local dir="$1"
|
|
352
|
+
run_install_cmd "$dir" || true
|
|
353
|
+
append_env "HARNESS_ECOSYSTEM" "ios"
|
|
354
|
+
append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
|
|
355
|
+
if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
|
|
356
|
+
append_env "HARNESS_XCODE_SCHEME" "$HARNESS_XCODE_SCHEME"
|
|
357
|
+
fi
|
|
358
|
+
# The simulator is not recorded here: launch.sh writes the device reserved for
|
|
359
|
+
# this slot, and harness.env already carries the shared one.
|
|
360
|
+
if [ -n "${HARNESS_BUNDLE_ID:-}" ]; then
|
|
361
|
+
append_env "HARNESS_BUNDLE_ID" "$HARNESS_BUNDLE_ID"
|
|
362
|
+
fi
|
|
363
|
+
if [ -n "${DEVELOPER_DIR:-}" ]; then
|
|
364
|
+
append_env "DEVELOPER_DIR" "$DEVELOPER_DIR"
|
|
365
|
+
fi
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
provision_monorepo_root() {
|
|
369
|
+
[ -n "$HAR_REL_PREFIX" ] || return 0
|
|
370
|
+
[ -n "$HAR_WORKTREE_DIR" ] || return 0
|
|
371
|
+
[ -f "$HAR_WORKTREE_DIR/package.json" ] || return 0
|
|
372
|
+
[ -d "$HAR_WORKTREE_DIR/node_modules" ] && return 0
|
|
373
|
+
local pkg_manager
|
|
374
|
+
pkg_manager="$(har_node_package_manager "$HAR_WORKTREE_DIR")"
|
|
375
|
+
pt_log "Installing monorepo root dependencies in $HAR_WORKTREE_DIR with ${pkg_manager}..."
|
|
376
|
+
har_node_install "$HAR_WORKTREE_DIR" "$pkg_manager" \
|
|
377
|
+
"$(har_node_declared_package_manager "$HAR_WORKTREE_DIR")"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
provision_ecosystem() {
|
|
381
|
+
local dir="$1"
|
|
382
|
+
local ecosystem
|
|
383
|
+
ecosystem="$(detect_ecosystem "$dir")"
|
|
384
|
+
pt_log "Toolchain ecosystem: ${ecosystem} (work dir: ${dir})"
|
|
385
|
+
|
|
386
|
+
case "$ecosystem" in
|
|
387
|
+
node) provision_node "$dir" ;;
|
|
388
|
+
python) provision_python "$dir" ;;
|
|
389
|
+
go) provision_go "$dir" ;;
|
|
390
|
+
rust) provision_rust "$dir" ;;
|
|
391
|
+
java) provision_java "$dir" ;;
|
|
392
|
+
ruby) provision_ruby "$dir" ;;
|
|
393
|
+
ios) provision_ios "$dir" ;;
|
|
394
|
+
none)
|
|
395
|
+
if run_install_cmd "$dir"; then
|
|
396
|
+
append_env "HARNESS_ECOSYSTEM" "custom"
|
|
397
|
+
else
|
|
398
|
+
pt_log "No ecosystem manifest detected — set HARNESS_ECOSYSTEM or HARNESS_INSTALL_CMD in harness.env"
|
|
399
|
+
append_env "HARNESS_ECOSYSTEM" "none"
|
|
400
|
+
fi
|
|
401
|
+
;;
|
|
402
|
+
esac
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
append_env "HARNESS_TOOLCHAIN_PROVISIONED" "true"
|
|
406
|
+
provision_ecosystem "$HAR_WORK_DIR"
|
|
407
|
+
provision_monorepo_root
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Per-stage scripts live here (e.g. ./.har/stages/browser-e2e.sh <agent-id>).
|
|
3
|
+
#
|
|
4
|
+
# Read .har/STAGES.md for the authoring guide: the stage script contract,
|
|
5
|
+
# command vs script stages, and how verificationStages controls verify --full.
|
|
6
|
+
#
|
|
7
|
+
# Scaffold a new one with:
|
|
8
|
+
# har env add-stage <id> --custom --script
|
|
9
|
+
|
|
10
|
+
echo "Add stage scripts under .har/stages/ and register them in stages.json — see .har/STAGES.md." >&2
|
|
11
|
+
exit 1
|