Skip to content

Commit 1097095

Browse files
committed
chore(release): merge staging into the v0.9.44 release
2 parents 45e36ff + e5cdbcf commit 1097095

6 files changed

Lines changed: 28 additions & 51 deletions

File tree

‎.github/workflows/desktop-release.yml‎

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,16 @@ jobs:
6565
runs-on: ubuntu-24.04
6666
timeout-minutes: 5
6767
outputs:
68-
commit: ${{ steps.source.outputs.commit }}
6968
name: ${{ steps.channel.outputs.name }}
7069
app_id: ${{ steps.channel.outputs.app_id }}
7170
origin: ${{ steps.channel.outputs.origin }}
7271
release_repository: ${{ steps.channel.outputs.release_repository }}
7372
token_kind: ${{ steps.channel.outputs.token_kind }}
74-
smoke_shards: ${{ steps.smoke.outputs.shards }}
7573
steps:
7674
- name: Checkout code
7775
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
7876
with:
7977
persist-credentials: false
80-
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.sha }}
8178

8279
# Prerelease versions carry their environment in the tag: -dev.N is a
8380
# dev build, -staging.N a staging build. Legacy -alpha/-beta tags remain
@@ -108,7 +105,6 @@ jobs:
108105
echo "Building $NAME ($APP_ID) for $RELEASE_REPOSITORY; default origin: ${ORIGIN:-production}"
109106
110107
- name: Validate release source
111-
id: source
112108
env:
113109
PUBLISH: ${{ inputs.publish }}
114110
SIGN: ${{ inputs.sign }}
@@ -123,6 +119,10 @@ jobs:
123119
echo "::error::Manual desktop releases must use a stable source-repository tag."
124120
exit 1
125121
fi
122+
if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ] && [ "$GITHUB_REF" != "refs/tags/$VERSION" ]; then
123+
echo "::error::Run this workflow from the $VERSION tag, so it executes only that tag's code and its caches stay scoped to that tag."
124+
exit 1
125+
fi
126126
if [ "$PUBLISH" = true ] && [ "$SIGN" != true ]; then
127127
echo "::error::Desktop releases must be signed before publication."
128128
exit 1
@@ -137,17 +137,6 @@ jobs:
137137
fi
138138
fi
139139
140-
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
141-
142-
- name: Select available smoke partitioning
143-
id: smoke
144-
run: |
145-
if [ -f scripts/desktop-e2e.ts ]; then
146-
echo 'shards=[1,2,3,4]' >> "$GITHUB_OUTPUT"
147-
else
148-
echo 'shards=[1]' >> "$GITHUB_OUTPUT"
149-
fi
150-
151140
- name: Validate release authentication
152141
if: ${{ inputs.publish }}
153142
env:
@@ -168,17 +157,15 @@ jobs:
168157
strategy:
169158
fail-fast: false
170159
matrix:
171-
shard: ${{ fromJSON(needs.source.outputs.smoke_shards) }}
160+
shard: [1, 2, 3, 4]
172161
steps:
173162
- &checkout-source
174163
name: Checkout verified source
175164
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
176165
with:
177166
persist-credentials: false
178-
ref: ${{ needs.source.outputs.commit }}
179167

180-
- &setup-bun
181-
name: Setup Bun
168+
- name: Setup Bun
182169
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
183170
with:
184171
bun-version: 1.4.4
@@ -218,9 +205,7 @@ jobs:
218205
exit 1
219206
fi
220207
221-
- &native-cache
222-
name: Restore native compilation
223-
if: hashFiles('.github/actions/desktop-native-cache/action.yml') != ''
208+
- name: Restore native compilation
224209
uses: ./.github/actions/desktop-native-cache
225210

226211
- &bundle-desktop
@@ -245,12 +230,7 @@ jobs:
245230
BACKGROUND_EXECUTOR_REPORT_PATH: test-results/background-executor-report.json
246231
TERMINAL_CANCEL_REPORT_PATH: test-results/terminal-cancel-report.json
247232
CONTINUE_SIGN_IN_REPORT_PATH: test-results/continue-sign-in-report.json
248-
run: |
249-
if [ -f ../../scripts/desktop-e2e.ts ]; then
250-
bun run ../../scripts/desktop-e2e.ts ${{ matrix.shard }}/${{ strategy.job-total }}
251-
else
252-
bun run test:e2e --reporter=line,json
253-
fi
233+
run: bun run ../../scripts/desktop-e2e.ts ${{ matrix.shard }}/${{ strategy.job-total }}
254234

255235
- name: Upload smoke coverage and timings
256236
if: always()
@@ -271,6 +251,8 @@ jobs:
271251
if-no-files-found: ignore
272252
retention-days: 7
273253

254+
# Restores no caches: every byte that gets signed comes from this checkout or
255+
# a fresh download, never from a cache another run could have written.
274256
build:
275257
name: build
276258
needs: source
@@ -281,24 +263,18 @@ jobs:
281263
steps:
282264
- *checkout-source
283265

284-
- *setup-bun
266+
- name: Setup Bun
267+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
268+
with:
269+
bun-version: 1.4.4
270+
no-cache: true
285271

286272
- *setup-node
287273

288-
- name: Cache Electron binaries
289-
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
290-
with:
291-
path: |
292-
~/Library/Caches/electron
293-
~/Library/Caches/electron-builder
294-
key: electron-cache-${{ runner.os }}-${{ hashFiles('apps/desktop/package.json') }}
295-
296274
- *install-dependencies
297275

298276
- *inject-version
299277

300-
- *native-cache
301-
302278
- name: Verify desktop source
303279
run: |
304280
bun run --cwd apps/desktop lint:check

‎apps/desktop/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ The build also derives the app icon from `SIM_DESKTOP_DEFAULT_ORIGIN`. Every cha
108108

109109
CI (`.github/workflows/desktop-release.yml`, wired into `ci.yml`):
110110
- Stable builds run only after `release` on a `vX.Y.Z:` commit to main — **never before**: `scripts/create-single-release.ts` skips creation if the tag exists, so a desktop job publishing first would eat the changelog. Stable assets remain on `simstudioai/sim`; dev/staging assets publish to the public `simstudioai/sim-desktop-releases` repository so source-repository followers are not notified for internal shell builds. The job builds `--publish never`; reruns verify the size and SHA-256 digest of existing release assets instead of overwriting them.
111-
- **Secrets gate**: `desktop-signing` in `ci.yml` probes the six Apple secrets and skips the desktop job with a warning until they exist — releases never fail on a missing Apple account, and the first release after the secrets land ships desktop artifacts automatically. Manual/one-off builds: Actions → "desktop-release" → Run workflow with a `vX.Y.Z` version (`publish: false` uploads artifacts to the run instead of the release).
111+
- **Secrets gate**: `desktop-signing` in `ci.yml` probes the six Apple secrets and skips the desktop job with a warning until they exist — releases never fail on a missing Apple account, and the first release after the secrets land ships desktop artifacts automatically. Manual/one-off builds: Actions → "desktop-release" → Run workflow, select the `vX.Y.Z` tag under "Use workflow from", and enter the same version (`publish: false` uploads artifacts to the run instead of the release). Any other ref is refused, so a run only executes the tag's own code and workflow, and its caches stay scoped to that tag.
112+
- **Cache trust**: the signed `build` job restores no caches, so nothing it signs can come from a cache another run wrote. Only the unsigned smoke jobs use caches.
112113
- The product semver is **injected** from the release tag into `apps/desktop/package.json` at build time (repo package versions are placeholders). A mismatch guard fails the build.
113114
- Fuses are flipped at package time (`electronFuses` in `electron-builder.yml`): runAsNode off, NODE_OPTIONS off, inspect args off, ASAR-only + integrity validation, and cookie encryption on. The packaged smoke test asserts every fuse byte so Electron upgrades fail until new fuses receive an explicit policy.
114115
- **Cookie-encryption go/no-go**: on every Electron bump, verify a packaged build keeps its session across relaunch (there are historical cookie-persistence bugs with the `EnableCookieEncryption` fuse). If it reproduces, set `enableCookieEncryption: false` and record it here.

‎apps/desktop/docs/electron-upgrade-checklist.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ The rendering-parity guarantee (identical to Chrome of the pinned version) is on
1313
- Workflow canvas (WebGL/ReactFlow), Monaco editing, a table export download.
1414
- Offline page appears with networking off; Retry recovers.
1515
6. **E2E:** `bun run test:e2e` green locally on the new pin; `desktop-e2e.yml` green in CI (the `latest` canary leg should already have hinted at surprises).
16-
7. **Signing/notarization smoke:** run `desktop-release.yml` via `workflow_dispatch` with `publish: false` against a test tag; `spctl`/`stapler` steps must pass.
16+
7. **Signing/notarization smoke:** run `desktop-release.yml` via `workflow_dispatch` with `publish: false`, selecting a test tag under "Use workflow from" and entering the same version; `spctl`/`stapler` steps must pass.
1717
8. **Ship** behind a staged rollout (10% `stagingPercentage`) and watch `update_error` / `renderer_gone` rates in the event logs before raising.

‎apps/sim/app/o/[organizationId]/links/search-params.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { createSerializer, parseAsString } from 'nuqs/server'
2-
import { SIM_LINK_SORTS } from '@/lib/sim-links/validation'
1+
import { truncateAtCodePoint } from '@sim/utils/string'
2+
import { createParser, createSerializer, parseAsString } from 'nuqs/server'
3+
import { SIM_LINK_SEARCH_MAX_LENGTH, SIM_LINK_SORTS } from '@/lib/sim-links/validation'
34
import { createSortParams } from '@/lib/url-state'
45

6+
const boundedSearch = (value: string) => truncateAtCodePoint(value, SIM_LINK_SEARCH_MAX_LENGTH, '')
7+
58
/**
69
* `create` opens the new-link editor, prefilled with its value as the name. `search` filters by
710
* name, destination, or description.
811
*/
912
export const simLinksParsers = {
1013
create: parseAsString,
11-
search: parseAsString.withDefault(''),
14+
search: createParser({ parse: boundedSearch, serialize: boundedSearch }).withDefault(''),
1215
} as const
1316

1417
export const serializeSimLinkView = createSerializer(simLinksParsers)

‎apps/sim/app/o/[organizationId]/links/sim-links.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useQueryStates } from 'nuqs'
88
import type { SimLink } from '@/lib/api/contracts/sim-links'
99
import { useDeploymentShape } from '@/lib/core/config/deployment-shape'
1010
import { simLinkPath, simLinkUrl } from '@/lib/navigation/paths'
11-
import { toSimLinkName } from '@/lib/sim-links/validation'
11+
import { SIM_LINK_SEARCH_MAX_LENGTH, toSimLinkName } from '@/lib/sim-links/validation'
1212
import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state'
1313
import {
1414
INSTALL_EXTENSION_ACTION,
@@ -132,6 +132,7 @@ export function SimLinks({ organizationId }: SimLinksProps) {
132132

133133
const searchConfig: SearchConfig = {
134134
value: search,
135+
maxLength: SIM_LINK_SEARCH_MAX_LENGTH,
135136
onChange: setSearch,
136137
onClearAll: () => setSearch(''),
137138
placeholder: SIM_LINKS_SEARCH_PLACEHOLDER,

‎apps/sim/app/sim/[[...path]]/page.tsx‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ import {
99
type SimLinkResolution,
1010
suggestSimLinks,
1111
} from '@/lib/sim-links/application/use-cases'
12-
import {
13-
parseTypedSimLink,
14-
SIM_LINK_SEARCH_MAX_LENGTH,
15-
simLinkNameSchema,
16-
} from '@/lib/sim-links/validation'
12+
import { parseTypedSimLink, simLinkNameSchema } from '@/lib/sim-links/validation'
1713
import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect'
1814
import { LogoShell } from '@/app/(landing)/components/logo-shell'
1915
import { serializeSimLinkView } from '@/app/o/[organizationId]/links/search-params'
@@ -108,7 +104,7 @@ export default async function SimLinkPage({ params, searchParams }: SimLinkPageP
108104
const validName = simLinkNameSchema.safeParse(name).success
109105
// A name can't hold a space, so `sim card prices` is someone looking for a link, not naming one.
110106
if (result.kind === 'not_found' && /\s/.test(name)) {
111-
redirect(serializeSimLinkView(library, { search: name.slice(0, SIM_LINK_SEARCH_MAX_LENGTH) }))
107+
redirect(serializeSimLinkView(library, { search: name }))
112108
}
113109
const creatable = result.kind === 'not_found' && validName
114110
const similar = creatable

0 commit comments

Comments
 (0)