Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 130 additions & 16 deletions .github/workflows/mobile-eas-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
publish-development-update:
name: Verify and publish iOS development update
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 120
env:
APP_VARIANT: development
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
Expand Down Expand Up @@ -92,6 +92,101 @@ jobs:
builds_json="$RUNNER_TEMP/mobile-ios-builds.json"
build_json="$RUNNER_TEMP/mobile-ios-build.json"
update_json="$RUNNER_TEMP/mobile-ios-update.json"
poll_json="$RUNNER_TEMP/mobile-ios-build-poll.json"
build_wait_timeout_seconds="${BUILD_WAIT_TIMEOUT_SECONDS:-5400}"
build_poll_interval_seconds="${BUILD_POLL_INTERVAL_SECONDS:-30}"

find_matching_build() {
local statuses_json="$1"
node -e '
const fs = require("fs");
const builds = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const statuses = new Set(JSON.parse(process.argv[2]));
const build = builds.find((item) => statuses.has(item.status));
if (build) {
process.stdout.write(JSON.stringify({
id: build.id,
status: build.status,
url: build.buildDetailsPageUrl ?? build.buildUrl ?? "",
}));
}
' "$builds_json" "$statuses_json"
}

read_build_id() {
node -e '
const fs = require("fs");
const builds = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
process.stdout.write(builds[0]?.id ?? "");
' "$build_json"
}

poll_build_status() {
local build_id="$1"

eas build:list \
--platform ios \
--buildProfile development \
--runtimeVersion "$fingerprint_hash" \
--limit 10 \
--json \
--non-interactive > "$poll_json"

node -e '
const fs = require("fs");
const builds = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const build = builds.find((item) => item.id === process.argv[2]);
if (!build) process.exit(2);
process.stdout.write(JSON.stringify({
status: build.status,
url: build.buildDetailsPageUrl ?? build.buildUrl ?? "",
}));
' "$poll_json" "$build_id"
}

wait_for_finished_build() {
local build_id="$1"
local deadline=$((SECONDS + build_wait_timeout_seconds))

while true; do
local status_json
if ! status_json="$(poll_build_status "$build_id")"; then
echo "::error::Could not find iOS EAS build $build_id while polling for completion." >&2
exit 1
fi

local status
local url
status="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.status ?? "");' "$status_json")"
url="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.url ?? "");' "$status_json")"
echo "iOS EAS build $build_id status: $status"

case "$status" in
FINISHED)
build_url="$url"
return 0
;;
NEW|IN_QUEUE|IN_PROGRESS)
if [ "$SECONDS" -ge "$deadline" ]; then
echo "::error::Timed out waiting for iOS EAS build $build_id to finish after ${build_wait_timeout_seconds}s." >&2
[ -n "$url" ] && echo "::error::Build logs: $url" >&2
exit 1
fi
sleep "$build_poll_interval_seconds"
;;
ERRORED|CANCELED)
echo "::error::iOS EAS build $build_id ended with status $status. No EAS update will be published for fingerprint $fingerprint_hash." >&2
[ -n "$url" ] && echo "::error::Build logs: $url" >&2
exit 1
;;
*)
echo "::error::iOS EAS build $build_id returned unexpected status '$status'." >&2
[ -n "$url" ] && echo "::error::Build logs: $url" >&2
exit 1
;;
esac
done
}

eas env:exec --non-interactive development \
"npx expo-updates fingerprint:generate --platform ios" > "$fingerprint_json"
Expand All @@ -102,34 +197,52 @@ jobs:
--platform ios \
--buildProfile development \
--runtimeVersion "$fingerprint_hash" \
--limit 1 \
--limit 10 \
--json \
--non-interactive > "$builds_json"

build_id="$(node -e 'const fs = require("fs"); const builds = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const valid = new Set(["NEW", "IN_QUEUE", "IN_PROGRESS", "FINISHED"]); const build = builds.find((item) => valid.has(item.status)); process.stdout.write(build?.id ?? "");' "$builds_json")"
build_id=""
build_status=""
build_url=""
matching_build="$(find_matching_build '["FINISHED"]')"

if [ -n "$build_id" ]; then
echo "Existing iOS build found with matching fingerprint: $build_id"
if [ -n "$matching_build" ]; then
build_id="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.id);' "$matching_build")"
build_status="FINISHED"
build_url="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.url ?? "");' "$matching_build")"
echo "Existing FINISHED iOS build found with matching fingerprint: $build_id"
else
echo "No existing iOS build found for fingerprint, starting a new build..."
if ! eas build \
--profile development \
--platform ios \
--non-interactive \
--json \
--no-wait > "$build_json"; then
echo "::error::Non-interactive iOS build failed. If credentials are missing or expired, run one interactive build locally to refresh them: cd apps/mobile && APP_VARIANT=development eas build --profile development --platform ios" >&2
exit 1
matching_build="$(find_matching_build '["NEW","IN_QUEUE","IN_PROGRESS"]')"
if [ -n "$matching_build" ]; then
build_id="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.id);' "$matching_build")"
build_status="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.status);' "$matching_build")"
build_url="$(node -e 'const data = JSON.parse(process.argv[1]); process.stdout.write(data.url ?? "");' "$matching_build")"
echo "Existing iOS build found with matching fingerprint: $build_id ($build_status); waiting for FINISHED."
else
echo "No existing FINISHED or active iOS build found for fingerprint, starting a new build..."
if ! eas build \
--profile development \
--platform ios \
--non-interactive \
--json \
--no-wait > "$build_json"; then
echo "::error::Non-interactive iOS build failed. If credentials are missing or expired, run one interactive build locally to refresh them: cd apps/mobile && APP_VARIANT=development eas build --profile development --platform ios" >&2
exit 1
fi
build_id="$(read_build_id)"
echo "Started iOS EAS build: $build_id"
fi
build_id="$(node -e 'const fs = require("fs"); const builds = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(builds[0]?.id ?? "");' "$build_json")"
echo "Started iOS EAS build: $build_id"
fi

if [ -z "$build_id" ]; then
echo "EAS did not return an iOS build id." >&2
exit 1
fi

if [ "$build_status" != "FINISHED" ]; then
wait_for_finished_build "$build_id"
fi

eas update \
--branch development \
--platform ios \
Expand All @@ -148,5 +261,6 @@ jobs:
echo "### Mobile EAS development deploy"
echo "- iOS fingerprint: \`$fingerprint_hash\`"
echo "- iOS build: \`$build_id\`"
[ -n "$build_url" ] && echo "- iOS build logs: $build_url"
echo "- iOS update: \`$update_id\`"
} >> "$GITHUB_STEP_SUMMARY"
15 changes: 9 additions & 6 deletions apps/mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,15 @@ CI uses Expo fingerprinting for development and preview dev-client builds to reu

Pushes to `main` that touch mobile/runtime paths run
`Mobile EAS Development Update`, which verifies the repo and deploys the iOS
development lane with explicit EAS CLI build/update commands. The iOS app has a
widget extension, so ad hoc credentials exist for both `com.brad.t3code.dev`
and `com.brad.t3code.dev.widgets`. CI never contacts Apple: it consumes the
signing credentials stored on EAS servers. When the native fingerprint changes
and stored credentials are missing or expired, refresh them with one
interactive local build (Apple ID auth):
development lane with explicit EAS CLI build/update commands. The workflow only
publishes an OTA update after EAS has a `FINISHED` iOS development build whose
runtime version matches the current iOS fingerprint. If that build errors or is
canceled, the workflow fails without publishing the update. The iOS app has a
widget extension, so ad hoc credentials exist for both `com.brad.t3code.dev` and
`com.brad.t3code.dev.widgets`. CI never contacts Apple: it consumes the signing
credentials stored on EAS servers. When the native fingerprint changes and
stored credentials are missing or expired, refresh them with one interactive
local build (Apple ID auth):

```bash
cd apps/mobile
Expand Down
12 changes: 6 additions & 6 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@
"@noble/hashes": "catalog:",
"@pierre/diffs": "catalog:",
"@react-native-menu/menu": "^2.0.0",
"@shikijs/core": "3.23.0",
"@shikijs/engine-javascript": "3.23.0",
"@shikijs/langs": "3.23.0",
"@shikijs/themes": "3.23.0",
"@shikijs/core": "4.2.0",
"@shikijs/engine-javascript": "4.2.0",
"@shikijs/langs": "4.2.0",
"@shikijs/themes": "4.2.0",
"@t3tools/client-runtime": "workspace:*",
"@t3tools/contracts": "workspace:*",
"@t3tools/mobile-markdown-text": "file:./modules/t3-markdown-text",
Expand Down Expand Up @@ -98,10 +98,10 @@
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-shiki-engine": "^0.3.9",
"react-native-shiki-engine": "^0.3.12",
"react-native-svg": "15.15.4",
"react-native-worklets": "0.8.3",
"shiki": "3.23.0",
"shiki": "4.2.0",
"tailwind-merge": "^3.5.0",
"uniwind": "^1.6.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
diff --git a/dist/editor/editor.js b/dist/editor/editor.js
index 4346c77a4e43a98c8524ea5186559fdee7433174..bb2a7ecd1d7f497916a5f220215d5191b095575b 100644
index e8013fc6eb6f243a6c912facf3fc0319ac66a8d0..80c82df4cdeb828bd331f5ec2f443d216bedc304 100644
--- a/dist/editor/editor.js
+++ b/dist/editor/editor.js
@@ -77,16 +77,13 @@ var Editor = class {
@@ -77,15 +77,12 @@ var Editor = class {
this.#options = options;
}
edit(component) {
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, diffStyle, lineHoverHighlight,...rest } = component.options;
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && Object.hasOwn(component, "fileDiff") || diffStyle === "unified" || lineHoverHighlight !== "disabled") {
+ const { useTokenTransformer, expandUnchanged, diffStyle,...rest } = component.options;
+ if (useTokenTransformer !== true || expandUnchanged !== true && Object.hasOwn(component, "fileDiff") || diffStyle === "unified") {
const isDiff = component.type === "file-diff";
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || lineHoverHighlight !== "disabled" || expandUnchanged !== true && isDiff || diffStyle === "unified" && isDiff) {
+ if (useTokenTransformer !== true || expandUnchanged !== true && isDiff || diffStyle === "unified" && isDiff) {
component.setOptions({
...rest,
useTokenTransformer: true,
- enableGutterUtility: false,
- enableLineSelection: false,
- lineHoverHighlight: "disabled",
expandUnchanged: true,
- diffStyle: "split",
- lineHoverHighlight: "disabled"
+ diffStyle: "split"
diffStyle: "split"
});
component.rerender();
}
@@ -481,6 +478,7 @@ var Editor = class {
@@ -511,6 +508,7 @@ var Editor = class {
return lineNumber - 1;
};
this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
Expand All @@ -47,7 +45,7 @@ index cb8e2026fb5d7a19f489c0a2402efbcb7dff3322..510fad6364d4a2214c7dd65fe2b114f1
return merged;
}
diff --git a/package.json b/package.json
index cff9c6b2a955e7568f44279a5b706da164c4f142..3d1f25f8bd1be682549677718d9ed8433872c854 100644
index d1558633de87044b7aa96cff09443db11f163cec..c0b16f0a0bec6fba2026f24f38b2c0a8fa06af7c 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,18 @@
Expand Down
Loading
Loading