@osfactory/har 0.62.0 → 0.62.2

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 CHANGED
@@ -28777,7 +28777,7 @@ var init_control_config = __esm({
28777
28777
  "use strict";
28778
28778
  init_portal_credentials();
28779
28779
  DEFAULT_CONTROL_API_URL = "http://localhost:3847";
28780
- DEFAULT_PORTAL_URL = "https://har.kerno.io";
28780
+ DEFAULT_PORTAL_URL = "https://app.harhq.com";
28781
28781
  }
28782
28782
  });
28783
28783
 
@@ -586,9 +586,129 @@ provision_ruby() {
586
586
  append_path_prefix "$dir/vendor/bundle/bin"
587
587
  }
588
588
 
589
+ # ── iOS Xcode project helpers ─────────────────────────────────────────────────
590
+ # Tuist, XcodeGen and CocoaPods treat the .xcodeproj / .xcworkspace as a build
591
+ # product rather than a tracked file, so a fresh worktree has nothing for
592
+ # xcodebuild to open. Generate it here — and fail naming the missing generator,
593
+ # instead of letting xcodebuild report an opaque "scheme not found" later.
594
+
595
+ # First project file of a kind in the work dir. Runs from inside the dir so a dot
596
+ # in the worktree path itself cannot trip the dotfile filter, and skips both the
597
+ # project.xcworkspace every .xcodeproj carries inside it and whatever CocoaPods
598
+ # generates under Pods/.
599
+ har_ios_find_project_file() {
600
+ local dir="$1"
601
+ local pattern="$2"
602
+ local match=""
603
+ match="$(cd "$dir" 2>/dev/null && find . -maxdepth 2 -name "$pattern" \
604
+ ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1)" || true
605
+ [ -n "$match" ] || return 1
606
+ echo "${match#./}"
607
+ }
608
+
609
+ # True when this worktree already has something xcodebuild can open. Adapt-time
610
+ # HARNESS_XCODE_WORKSPACE / HARNESS_XCODE_PROJECT decide it when set: they name
611
+ # the generated file, so a missing one means the generator still has to run.
612
+ har_ios_has_project() {
613
+ local dir="$1"
614
+
615
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ]; then
616
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$dir/$HARNESS_XCODE_WORKSPACE" ]; then
617
+ return 0
618
+ fi
619
+ if [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$dir/$HARNESS_XCODE_PROJECT" ]; then
620
+ return 0
621
+ fi
622
+ return 1
623
+ fi
624
+
625
+ if har_ios_find_project_file "$dir" '*.xcworkspace' >/dev/null; then return 0; fi
626
+ if har_ios_find_project_file "$dir" '*.xcodeproj' >/dev/null; then return 0; fi
627
+ return 1
628
+ }
629
+
630
+ # Generator this repo declares, or empty. A Podfile counts: `pod install` is what
631
+ # writes the .xcworkspace for a CocoaPods project.
632
+ har_ios_project_generator() {
633
+ local dir="$1"
634
+ if [ -f "$dir/Project.swift" ] || [ -f "$dir/Workspace.swift" ]; then echo tuist; return; fi
635
+ if [ -f "$dir/project.yml" ] || [ -f "$dir/project.yaml" ]; then echo xcodegen; return; fi
636
+ if [ -f "$dir/Podfile" ]; then echo pod; return; fi
637
+ echo ""
638
+ }
639
+
640
+ har_ios_generate_project() {
641
+ local dir="$1"
642
+ local generator=""
643
+
644
+ if har_ios_has_project "$dir"; then
645
+ return 0
646
+ fi
647
+
648
+ generator="$(har_ios_project_generator "$dir")"
649
+ if [ -z "$generator" ]; then
650
+ pt_log "No .xcodeproj/.xcworkspace found and no generator manifest (Project.swift, project.yml, Podfile) — set HARNESS_XCODE_PROJECT/HARNESS_XCODE_WORKSPACE or HARNESS_INSTALL_CMD in harness.env"
651
+ return 0
652
+ fi
653
+
654
+ if ! command -v "$generator" >/dev/null 2>&1; then
655
+ local label="$generator"
656
+ [ "$generator" = "pod" ] && label="CocoaPods (pod)"
657
+ pt_log "ERROR: this repo generates its Xcode project with ${label}, which is not on PATH."
658
+ pt_log " Install ${label}, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
659
+ return 1
660
+ fi
661
+
662
+ case "$generator" in
663
+ tuist)
664
+ pt_log "Generating the Xcode project with tuist..."
665
+ if ! (cd "$dir" && tuist generate --no-open); then
666
+ pt_log "tuist generate --no-open failed — retrying without the flag"
667
+ (cd "$dir" && tuist generate)
668
+ fi
669
+ ;;
670
+ xcodegen)
671
+ pt_log "Generating the Xcode project with xcodegen..."
672
+ (cd "$dir" && xcodegen generate)
673
+ ;;
674
+ pod)
675
+ # har_ios_pod_install writes the workspace right after this.
676
+ :
677
+ ;;
678
+ esac
679
+ }
680
+
681
+ # CocoaPods dependencies. Independent of project generation: a tracked
682
+ # .xcworkspace still cannot build when Pods/ is missing in a fresh worktree.
683
+ har_ios_pod_install() {
684
+ local dir="$1"
685
+
686
+ [ -f "$dir/Podfile" ] || return 0
687
+ [ -d "$dir/Pods" ] && return 0
688
+
689
+ if ! command -v pod >/dev/null 2>&1; then
690
+ pt_log "ERROR: this repo uses CocoaPods (Podfile, no Pods/) and pod is not on PATH."
691
+ pt_log " Install CocoaPods, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
692
+ return 1
693
+ fi
694
+
695
+ pt_log "Installing CocoaPods dependencies..."
696
+ (cd "$dir" && pod install)
697
+ }
698
+
589
699
  provision_ios() {
590
700
  local dir="$1"
591
- run_install_cmd "$dir" || true
701
+
702
+ # An explicit HARNESS_INSTALL_CMD owns provisioning outright: the default
703
+ # generators stay out of the way, and a failing override fails the launch
704
+ # rather than being papered over.
705
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
706
+ run_install_cmd "$dir"
707
+ else
708
+ har_ios_generate_project "$dir"
709
+ har_ios_pod_install "$dir"
710
+ fi
711
+
592
712
  append_env "HARNESS_ECOSYSTEM" "ios"
593
713
  append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
594
714
  if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
@@ -586,9 +586,129 @@ provision_ruby() {
586
586
  append_path_prefix "$dir/vendor/bundle/bin"
587
587
  }
588
588
 
589
+ # ── iOS Xcode project helpers ─────────────────────────────────────────────────
590
+ # Tuist, XcodeGen and CocoaPods treat the .xcodeproj / .xcworkspace as a build
591
+ # product rather than a tracked file, so a fresh worktree has nothing for
592
+ # xcodebuild to open. Generate it here — and fail naming the missing generator,
593
+ # instead of letting xcodebuild report an opaque "scheme not found" later.
594
+
595
+ # First project file of a kind in the work dir. Runs from inside the dir so a dot
596
+ # in the worktree path itself cannot trip the dotfile filter, and skips both the
597
+ # project.xcworkspace every .xcodeproj carries inside it and whatever CocoaPods
598
+ # generates under Pods/.
599
+ har_ios_find_project_file() {
600
+ local dir="$1"
601
+ local pattern="$2"
602
+ local match=""
603
+ match="$(cd "$dir" 2>/dev/null && find . -maxdepth 2 -name "$pattern" \
604
+ ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1)" || true
605
+ [ -n "$match" ] || return 1
606
+ echo "${match#./}"
607
+ }
608
+
609
+ # True when this worktree already has something xcodebuild can open. Adapt-time
610
+ # HARNESS_XCODE_WORKSPACE / HARNESS_XCODE_PROJECT decide it when set: they name
611
+ # the generated file, so a missing one means the generator still has to run.
612
+ har_ios_has_project() {
613
+ local dir="$1"
614
+
615
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ]; then
616
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$dir/$HARNESS_XCODE_WORKSPACE" ]; then
617
+ return 0
618
+ fi
619
+ if [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$dir/$HARNESS_XCODE_PROJECT" ]; then
620
+ return 0
621
+ fi
622
+ return 1
623
+ fi
624
+
625
+ if har_ios_find_project_file "$dir" '*.xcworkspace' >/dev/null; then return 0; fi
626
+ if har_ios_find_project_file "$dir" '*.xcodeproj' >/dev/null; then return 0; fi
627
+ return 1
628
+ }
629
+
630
+ # Generator this repo declares, or empty. A Podfile counts: `pod install` is what
631
+ # writes the .xcworkspace for a CocoaPods project.
632
+ har_ios_project_generator() {
633
+ local dir="$1"
634
+ if [ -f "$dir/Project.swift" ] || [ -f "$dir/Workspace.swift" ]; then echo tuist; return; fi
635
+ if [ -f "$dir/project.yml" ] || [ -f "$dir/project.yaml" ]; then echo xcodegen; return; fi
636
+ if [ -f "$dir/Podfile" ]; then echo pod; return; fi
637
+ echo ""
638
+ }
639
+
640
+ har_ios_generate_project() {
641
+ local dir="$1"
642
+ local generator=""
643
+
644
+ if har_ios_has_project "$dir"; then
645
+ return 0
646
+ fi
647
+
648
+ generator="$(har_ios_project_generator "$dir")"
649
+ if [ -z "$generator" ]; then
650
+ pt_log "No .xcodeproj/.xcworkspace found and no generator manifest (Project.swift, project.yml, Podfile) — set HARNESS_XCODE_PROJECT/HARNESS_XCODE_WORKSPACE or HARNESS_INSTALL_CMD in harness.env"
651
+ return 0
652
+ fi
653
+
654
+ if ! command -v "$generator" >/dev/null 2>&1; then
655
+ local label="$generator"
656
+ [ "$generator" = "pod" ] && label="CocoaPods (pod)"
657
+ pt_log "ERROR: this repo generates its Xcode project with ${label}, which is not on PATH."
658
+ pt_log " Install ${label}, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
659
+ return 1
660
+ fi
661
+
662
+ case "$generator" in
663
+ tuist)
664
+ pt_log "Generating the Xcode project with tuist..."
665
+ if ! (cd "$dir" && tuist generate --no-open); then
666
+ pt_log "tuist generate --no-open failed — retrying without the flag"
667
+ (cd "$dir" && tuist generate)
668
+ fi
669
+ ;;
670
+ xcodegen)
671
+ pt_log "Generating the Xcode project with xcodegen..."
672
+ (cd "$dir" && xcodegen generate)
673
+ ;;
674
+ pod)
675
+ # har_ios_pod_install writes the workspace right after this.
676
+ :
677
+ ;;
678
+ esac
679
+ }
680
+
681
+ # CocoaPods dependencies. Independent of project generation: a tracked
682
+ # .xcworkspace still cannot build when Pods/ is missing in a fresh worktree.
683
+ har_ios_pod_install() {
684
+ local dir="$1"
685
+
686
+ [ -f "$dir/Podfile" ] || return 0
687
+ [ -d "$dir/Pods" ] && return 0
688
+
689
+ if ! command -v pod >/dev/null 2>&1; then
690
+ pt_log "ERROR: this repo uses CocoaPods (Podfile, no Pods/) and pod is not on PATH."
691
+ pt_log " Install CocoaPods, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
692
+ return 1
693
+ fi
694
+
695
+ pt_log "Installing CocoaPods dependencies..."
696
+ (cd "$dir" && pod install)
697
+ }
698
+
589
699
  provision_ios() {
590
700
  local dir="$1"
591
- run_install_cmd "$dir" || true
701
+
702
+ # An explicit HARNESS_INSTALL_CMD owns provisioning outright: the default
703
+ # generators stay out of the way, and a failing override fails the launch
704
+ # rather than being papered over.
705
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
706
+ run_install_cmd "$dir"
707
+ else
708
+ har_ios_generate_project "$dir"
709
+ har_ios_pod_install "$dir"
710
+ fi
711
+
592
712
  append_env "HARNESS_ECOSYSTEM" "ios"
593
713
  append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
594
714
  if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
@@ -21,6 +21,12 @@ structure, then make the changes listed below. Commit when done.
21
21
  | `HARNESS_SIMULATOR_NAME` | Display name exactly as shown in `xcrun simctl list devices` (e.g. `iPhone 16`). |
22
22
  | `HARNESS_BUNDLE_ID` | App bundle identifier from the Xcode target (e.g. `com.example.myapp`). |
23
23
 
24
+ If the project is generated (`Project.swift`, `project.yml`, or a `Podfile` with no
25
+ tracked `.xcworkspace`), still set the path the generator produces — launch runs
26
+ `tuist generate` / `xcodegen generate` / `pod install` when the file is absent from
27
+ a fresh worktree. Use `HARNESS_INSTALL_CMD` only when that default is wrong; it then
28
+ owns provisioning outright.
29
+
24
30
  Run `./.har/setup-infra.sh` after editing to confirm the simulator boots.
25
31
 
26
32
  ## 3 — Verify `.har/verify.sh` builds and tests correctly
@@ -21,7 +21,7 @@ Generated and maintained by [`har`](https://github.com/os-factory/har). Run `har
21
21
  | `setup-infra.sh` | Check the toolchain; start optional Docker services |
22
22
  | `simulator.sh` | Create, boot and delete one iOS Simulator per agent slot |
23
23
  | `launch.sh` | Launch one agent slot (git worktree, toolchain provisioning, simulator, env file) |
24
- | `provision-toolchain.sh` | Write Xcode/simulator paths (`XCODEBUILD_BIN`, …) to `.env.agent.<id>` |
24
+ | `provision-toolchain.sh` | Generate the Xcode project when it is a build product (Tuist / XcodeGen / CocoaPods) and write Xcode paths (`XCODEBUILD_BIN`, …) to `.env.agent.<id>` |
25
25
  | `verify.sh` | Verification pipeline (build smoke by default; --full adds tests, lint, flows) |
26
26
  | `teardown.sh` | Tear down one agent slot (worktree + env file) |
27
27
  | `agent-cli.sh` | Inspect slot status, run xcodebuild commands, install/launch app |
@@ -9,6 +9,12 @@ export HARNESS_AGENT_SLOT_MAX=3
9
9
 
10
10
  # Toolchain provisioning — launch writes Xcode/simulator paths into .env.agent.<id>
11
11
  export HARNESS_ECOSYSTEM="ios"
12
+ # Generated projects: when the worktree has no .xcodeproj/.xcworkspace, launch runs
13
+ # the generator this repo declares — tuist generate (Project.swift), xcodegen
14
+ # generate (project.yml) — plus pod install when a Podfile is present and Pods/ is
15
+ # missing. A missing generator fails the launch by name.
16
+ # HARNESS_INSTALL_CMD owns provisioning outright: set it and no default generator
17
+ # runs, and its failure fails the launch.
12
18
  # export HARNESS_INSTALL_CMD="xcodebuild -resolvePackageDependencies ..."
13
19
  # Node package manager: auto-detected from package.json "packageManager" and the
14
20
  # lockfile (bun.lock → bun, pnpm-lock.yaml → pnpm, yarn.lock → yarn, else npm),
@@ -586,9 +586,129 @@ provision_ruby() {
586
586
  append_path_prefix "$dir/vendor/bundle/bin"
587
587
  }
588
588
 
589
+ # ── iOS Xcode project helpers ─────────────────────────────────────────────────
590
+ # Tuist, XcodeGen and CocoaPods treat the .xcodeproj / .xcworkspace as a build
591
+ # product rather than a tracked file, so a fresh worktree has nothing for
592
+ # xcodebuild to open. Generate it here — and fail naming the missing generator,
593
+ # instead of letting xcodebuild report an opaque "scheme not found" later.
594
+
595
+ # First project file of a kind in the work dir. Runs from inside the dir so a dot
596
+ # in the worktree path itself cannot trip the dotfile filter, and skips both the
597
+ # project.xcworkspace every .xcodeproj carries inside it and whatever CocoaPods
598
+ # generates under Pods/.
599
+ har_ios_find_project_file() {
600
+ local dir="$1"
601
+ local pattern="$2"
602
+ local match=""
603
+ match="$(cd "$dir" 2>/dev/null && find . -maxdepth 2 -name "$pattern" \
604
+ ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1)" || true
605
+ [ -n "$match" ] || return 1
606
+ echo "${match#./}"
607
+ }
608
+
609
+ # True when this worktree already has something xcodebuild can open. Adapt-time
610
+ # HARNESS_XCODE_WORKSPACE / HARNESS_XCODE_PROJECT decide it when set: they name
611
+ # the generated file, so a missing one means the generator still has to run.
612
+ har_ios_has_project() {
613
+ local dir="$1"
614
+
615
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ]; then
616
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$dir/$HARNESS_XCODE_WORKSPACE" ]; then
617
+ return 0
618
+ fi
619
+ if [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$dir/$HARNESS_XCODE_PROJECT" ]; then
620
+ return 0
621
+ fi
622
+ return 1
623
+ fi
624
+
625
+ if har_ios_find_project_file "$dir" '*.xcworkspace' >/dev/null; then return 0; fi
626
+ if har_ios_find_project_file "$dir" '*.xcodeproj' >/dev/null; then return 0; fi
627
+ return 1
628
+ }
629
+
630
+ # Generator this repo declares, or empty. A Podfile counts: `pod install` is what
631
+ # writes the .xcworkspace for a CocoaPods project.
632
+ har_ios_project_generator() {
633
+ local dir="$1"
634
+ if [ -f "$dir/Project.swift" ] || [ -f "$dir/Workspace.swift" ]; then echo tuist; return; fi
635
+ if [ -f "$dir/project.yml" ] || [ -f "$dir/project.yaml" ]; then echo xcodegen; return; fi
636
+ if [ -f "$dir/Podfile" ]; then echo pod; return; fi
637
+ echo ""
638
+ }
639
+
640
+ har_ios_generate_project() {
641
+ local dir="$1"
642
+ local generator=""
643
+
644
+ if har_ios_has_project "$dir"; then
645
+ return 0
646
+ fi
647
+
648
+ generator="$(har_ios_project_generator "$dir")"
649
+ if [ -z "$generator" ]; then
650
+ pt_log "No .xcodeproj/.xcworkspace found and no generator manifest (Project.swift, project.yml, Podfile) — set HARNESS_XCODE_PROJECT/HARNESS_XCODE_WORKSPACE or HARNESS_INSTALL_CMD in harness.env"
651
+ return 0
652
+ fi
653
+
654
+ if ! command -v "$generator" >/dev/null 2>&1; then
655
+ local label="$generator"
656
+ [ "$generator" = "pod" ] && label="CocoaPods (pod)"
657
+ pt_log "ERROR: this repo generates its Xcode project with ${label}, which is not on PATH."
658
+ pt_log " Install ${label}, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
659
+ return 1
660
+ fi
661
+
662
+ case "$generator" in
663
+ tuist)
664
+ pt_log "Generating the Xcode project with tuist..."
665
+ if ! (cd "$dir" && tuist generate --no-open); then
666
+ pt_log "tuist generate --no-open failed — retrying without the flag"
667
+ (cd "$dir" && tuist generate)
668
+ fi
669
+ ;;
670
+ xcodegen)
671
+ pt_log "Generating the Xcode project with xcodegen..."
672
+ (cd "$dir" && xcodegen generate)
673
+ ;;
674
+ pod)
675
+ # har_ios_pod_install writes the workspace right after this.
676
+ :
677
+ ;;
678
+ esac
679
+ }
680
+
681
+ # CocoaPods dependencies. Independent of project generation: a tracked
682
+ # .xcworkspace still cannot build when Pods/ is missing in a fresh worktree.
683
+ har_ios_pod_install() {
684
+ local dir="$1"
685
+
686
+ [ -f "$dir/Podfile" ] || return 0
687
+ [ -d "$dir/Pods" ] && return 0
688
+
689
+ if ! command -v pod >/dev/null 2>&1; then
690
+ pt_log "ERROR: this repo uses CocoaPods (Podfile, no Pods/) and pod is not on PATH."
691
+ pt_log " Install CocoaPods, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
692
+ return 1
693
+ fi
694
+
695
+ pt_log "Installing CocoaPods dependencies..."
696
+ (cd "$dir" && pod install)
697
+ }
698
+
589
699
  provision_ios() {
590
700
  local dir="$1"
591
- run_install_cmd "$dir" || true
701
+
702
+ # An explicit HARNESS_INSTALL_CMD owns provisioning outright: the default
703
+ # generators stay out of the way, and a failing override fails the launch
704
+ # rather than being papered over.
705
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
706
+ run_install_cmd "$dir"
707
+ else
708
+ har_ios_generate_project "$dir"
709
+ har_ios_pod_install "$dir"
710
+ fi
711
+
592
712
  append_env "HARNESS_ECOSYSTEM" "ios"
593
713
  append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
594
714
  if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
@@ -58,12 +58,18 @@ xc_target_flags() {
58
58
  elif [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$WORK_DIR/${HARNESS_XCODE_PROJECT}" ]; then
59
59
  echo "-project $WORK_DIR/${HARNESS_XCODE_PROJECT}"
60
60
  else
61
- # Auto-detect: prefer workspace (CocoaPods), then project
61
+ # Auto-detect: prefer workspace (CocoaPods), then project. Runs from inside
62
+ # WORK_DIR so a dot in the worktree path itself cannot trip the dotfile
63
+ # filter, and skips both the project.xcworkspace every .xcodeproj carries
64
+ # inside it and the CocoaPods project under Pods/ — either would point
65
+ # xcodebuild at the wrong target.
62
66
  local ws prj
63
- ws="$(find "$WORK_DIR" -maxdepth 2 -name "*.xcworkspace" ! -path "*/\.*" 2>/dev/null | head -1 || true)"
64
- prj="$(find "$WORK_DIR" -maxdepth 2 -name "*.xcodeproj" ! -path "*/\.*" 2>/dev/null | head -1 || true)"
65
- if [ -n "$ws" ]; then echo "-workspace $ws"
66
- elif [ -n "$prj" ]; then echo "-project $prj"
67
+ ws="$(cd "$WORK_DIR" && find . -maxdepth 2 -name "*.xcworkspace" \
68
+ ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1 || true)"
69
+ prj="$(cd "$WORK_DIR" && find . -maxdepth 2 -name "*.xcodeproj" \
70
+ ! -path "./.*" ! -path "*/Pods/*" 2>/dev/null | head -1 || true)"
71
+ if [ -n "$ws" ]; then echo "-workspace $WORK_DIR/${ws#./}"
72
+ elif [ -n "$prj" ]; then echo "-project $WORK_DIR/${prj#./}"
67
73
  fi
68
74
  fi
69
75
  }
@@ -586,9 +586,129 @@ provision_ruby() {
586
586
  append_path_prefix "$dir/vendor/bundle/bin"
587
587
  }
588
588
 
589
+ # ── iOS Xcode project helpers ─────────────────────────────────────────────────
590
+ # Tuist, XcodeGen and CocoaPods treat the .xcodeproj / .xcworkspace as a build
591
+ # product rather than a tracked file, so a fresh worktree has nothing for
592
+ # xcodebuild to open. Generate it here — and fail naming the missing generator,
593
+ # instead of letting xcodebuild report an opaque "scheme not found" later.
594
+
595
+ # First project file of a kind in the work dir. Runs from inside the dir so a dot
596
+ # in the worktree path itself cannot trip the dotfile filter, and skips both the
597
+ # project.xcworkspace every .xcodeproj carries inside it and whatever CocoaPods
598
+ # generates under Pods/.
599
+ har_ios_find_project_file() {
600
+ local dir="$1"
601
+ local pattern="$2"
602
+ local match=""
603
+ match="$(cd "$dir" 2>/dev/null && find . -maxdepth 2 -name "$pattern" \
604
+ ! -path "./.*" ! -path "*.xcodeproj/*" ! -path "*/Pods/*" 2>/dev/null | head -1)" || true
605
+ [ -n "$match" ] || return 1
606
+ echo "${match#./}"
607
+ }
608
+
609
+ # True when this worktree already has something xcodebuild can open. Adapt-time
610
+ # HARNESS_XCODE_WORKSPACE / HARNESS_XCODE_PROJECT decide it when set: they name
611
+ # the generated file, so a missing one means the generator still has to run.
612
+ har_ios_has_project() {
613
+ local dir="$1"
614
+
615
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] || [ -n "${HARNESS_XCODE_PROJECT:-}" ]; then
616
+ if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$dir/$HARNESS_XCODE_WORKSPACE" ]; then
617
+ return 0
618
+ fi
619
+ if [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$dir/$HARNESS_XCODE_PROJECT" ]; then
620
+ return 0
621
+ fi
622
+ return 1
623
+ fi
624
+
625
+ if har_ios_find_project_file "$dir" '*.xcworkspace' >/dev/null; then return 0; fi
626
+ if har_ios_find_project_file "$dir" '*.xcodeproj' >/dev/null; then return 0; fi
627
+ return 1
628
+ }
629
+
630
+ # Generator this repo declares, or empty. A Podfile counts: `pod install` is what
631
+ # writes the .xcworkspace for a CocoaPods project.
632
+ har_ios_project_generator() {
633
+ local dir="$1"
634
+ if [ -f "$dir/Project.swift" ] || [ -f "$dir/Workspace.swift" ]; then echo tuist; return; fi
635
+ if [ -f "$dir/project.yml" ] || [ -f "$dir/project.yaml" ]; then echo xcodegen; return; fi
636
+ if [ -f "$dir/Podfile" ]; then echo pod; return; fi
637
+ echo ""
638
+ }
639
+
640
+ har_ios_generate_project() {
641
+ local dir="$1"
642
+ local generator=""
643
+
644
+ if har_ios_has_project "$dir"; then
645
+ return 0
646
+ fi
647
+
648
+ generator="$(har_ios_project_generator "$dir")"
649
+ if [ -z "$generator" ]; then
650
+ pt_log "No .xcodeproj/.xcworkspace found and no generator manifest (Project.swift, project.yml, Podfile) — set HARNESS_XCODE_PROJECT/HARNESS_XCODE_WORKSPACE or HARNESS_INSTALL_CMD in harness.env"
651
+ return 0
652
+ fi
653
+
654
+ if ! command -v "$generator" >/dev/null 2>&1; then
655
+ local label="$generator"
656
+ [ "$generator" = "pod" ] && label="CocoaPods (pod)"
657
+ pt_log "ERROR: this repo generates its Xcode project with ${label}, which is not on PATH."
658
+ pt_log " Install ${label}, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
659
+ return 1
660
+ fi
661
+
662
+ case "$generator" in
663
+ tuist)
664
+ pt_log "Generating the Xcode project with tuist..."
665
+ if ! (cd "$dir" && tuist generate --no-open); then
666
+ pt_log "tuist generate --no-open failed — retrying without the flag"
667
+ (cd "$dir" && tuist generate)
668
+ fi
669
+ ;;
670
+ xcodegen)
671
+ pt_log "Generating the Xcode project with xcodegen..."
672
+ (cd "$dir" && xcodegen generate)
673
+ ;;
674
+ pod)
675
+ # har_ios_pod_install writes the workspace right after this.
676
+ :
677
+ ;;
678
+ esac
679
+ }
680
+
681
+ # CocoaPods dependencies. Independent of project generation: a tracked
682
+ # .xcworkspace still cannot build when Pods/ is missing in a fresh worktree.
683
+ har_ios_pod_install() {
684
+ local dir="$1"
685
+
686
+ [ -f "$dir/Podfile" ] || return 0
687
+ [ -d "$dir/Pods" ] && return 0
688
+
689
+ if ! command -v pod >/dev/null 2>&1; then
690
+ pt_log "ERROR: this repo uses CocoaPods (Podfile, no Pods/) and pod is not on PATH."
691
+ pt_log " Install CocoaPods, or set HARNESS_INSTALL_CMD in harness.env to provision the project another way."
692
+ return 1
693
+ fi
694
+
695
+ pt_log "Installing CocoaPods dependencies..."
696
+ (cd "$dir" && pod install)
697
+ }
698
+
589
699
  provision_ios() {
590
700
  local dir="$1"
591
- run_install_cmd "$dir" || true
701
+
702
+ # An explicit HARNESS_INSTALL_CMD owns provisioning outright: the default
703
+ # generators stay out of the way, and a failing override fails the launch
704
+ # rather than being papered over.
705
+ if [ -n "${HARNESS_INSTALL_CMD:-}" ]; then
706
+ run_install_cmd "$dir"
707
+ else
708
+ har_ios_generate_project "$dir"
709
+ har_ios_pod_install "$dir"
710
+ fi
711
+
592
712
  append_env "HARNESS_ECOSYSTEM" "ios"
593
713
  append_env "XCODEBUILD_BIN" "$(command -v xcodebuild 2>/dev/null || echo xcodebuild)"
594
714
  if [ -n "${HARNESS_XCODE_SCHEME:-}" ]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfactory/har",
3
- "version": "0.62.0",
3
+ "version": "0.62.2",
4
4
  "description": "Open harness for reproducible multi-agent coding workflows",
5
5
  "keywords": [
6
6
  "har",