From 4eac6d977d803892d893516c2768c02211282e77 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Fri, 9 Oct 2026 07:37:56 -0500 Subject: [PATCH] fix: beta version stamping, homebrew cask bump, dockerignore and readme platforms --- .dockerignore | 65 ++++++++----------------- .github/workflows/docker.yml | 17 +++++-- .github/workflows/electron.yml | 30 +++++++++--- README.md | 14 ++++-- components.json | 4 +- docker/Dockerfile | 8 +-- electron-builder.json | 2 +- eslint.config.mjs | 6 +-- scripts/generate-dialect-schema.cjs | 16 +++--- scripts/generate-dialect-schema.test.ts | 2 +- scripts/generate-icons.mjs | 4 +- scripts/generate-release-body.cjs | 10 ++-- scripts/verify-dialects.mjs | 10 ++-- 13 files changed, 96 insertions(+), 92 deletions(-) diff --git a/.dockerignore b/.dockerignore index ca837371f..353e19038 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,13 +5,22 @@ yarn-error.log* dist build -.next -.nuxt +coverage +release -.env.local -.env.development.local -.env.test.local -.env.production.local +# Local data, secrets and editor state +db +old_db +ssl +uploads +plugins +.env +.env.* +.mcp.json +.claude +CLAUDE.md +termix-plugin-signing.key +*.tmxplug.sig .vscode .idea @@ -29,63 +38,31 @@ Thumbs.db .git .gitignore +.husky README.md CONTRIBUTING.md +CHANGELOG.md LICENSE - -uploads/ - electron/ electron-builder.json +Casks +packaging *.log *.tmp *.temp - logs -*.log - pids *.pid *.seed *.pid.lock -coverage - .nyc_output - -jspm_packages/ - .npm - .eslintcache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -.node_repl_history - -*.tgz - -.yarn-integrity - .cache .parcel-cache - -.next - -.nuxt - -.vuepress/dist - -.serverless - -.fusebox/ - -.dynamodb/ - -.tern-port +*.tgz +.node_repl_history diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7698ddeb5..72a5d23fd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: version: - description: "Version to build (e.g., 1.8.0)" + description: "Version to build (e.g., 26.10.0)" required: true build_type: description: "Build type" @@ -22,11 +22,11 @@ on: workflow_call: inputs: version: - description: "Version to build (e.g., 1.8.0)" + description: "Version to build (e.g., 26.10.0)" required: true type: string build_type: - description: "Build type (Development or Production)" + description: "Build type (Development, Beta or Production)" required: true type: string dry_run: @@ -52,7 +52,14 @@ jobs: fetch-depth: 1 - name: Resolve source revision - run: echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV" + run: | + echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV" + echo "BUILD_CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + + # The image reads its version from package.json, so a beta has to say so. + - name: Stamp beta version + if: ${{ inputs.build_type == 'Beta' }} + run: node scripts/sync-version.cjs --version "${{ inputs.version }}" - name: Set up QEMU uses: docker/setup-qemu-action@v4 @@ -120,7 +127,7 @@ jobs: labels: | org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.revision=${{ env.SOURCE_SHA }} - org.opencontainers.image.created=${{ github.run_id }} + org.opencontainers.image.created=${{ env.BUILD_CREATED }} outputs: ${{ inputs.dry_run && 'type=cacheonly' || 'type=registry,compression=zstd' }} - name: Login to Docker Hub as termixssh diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index 9dba3910e..a3e29608c 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -117,6 +117,11 @@ jobs: - name: Install dependencies run: npm ci + # A beta build has to know it is a beta, or it reports the stable version. + - name: Stamp beta version + if: inputs.version_override != '' + run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}" + - name: Get version id: package-version run: | @@ -220,6 +225,11 @@ jobs: npm install --force @rollup/rollup-linux-arm64-gnu npm install --force @rollup/rollup-linux-arm-gnueabihf + # A beta build has to know it is a beta, or it reports the stable version. + - name: Stamp beta version + if: inputs.version_override != '' + run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}" + - name: Build Linux x64 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -429,6 +439,11 @@ jobs: npm install dmg-license node scripts/install-macos-sharp.cjs + # A beta build has to know it is a beta, or it reports the stable version. + - name: Stamp beta version + if: inputs.version_override != '' + run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}" + - name: Check for Code Signing Certificates id: check_certs run: | @@ -468,14 +483,15 @@ jobs: security find-identity -v -p codesigning $KEYCHAIN_PATH + # Betas never go to the App Store, so they skip the MAS package. - name: Build macOS App Store Package - if: steps.check_certs.outputs.has_certs == 'true' + id: mas_build + if: steps.check_certs.outputs.has_certs == 'true' && inputs.version_override == '' env: ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=4096 run: | - CURRENT_VERSION=$(node -p "require('./package.json').version") BUILD_VERSION="${{ github.run_number }}" npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION" @@ -534,7 +550,7 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then + if [ "${{ steps.mas_build.outcome }}" != "success" ]; then npm run build fi npx electron-builder --mac dmg --universal --x64 --arm64 --publish never @@ -555,7 +571,7 @@ jobs: echo "path=$MAS_PKG" >> "$GITHUB_OUTPUT" - name: Upload macOS MAS PKG - if: steps.check_certs.outputs.has_certs == 'true' && steps.locate_mas_pkg.outputs.path != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit') + if: steps.mas_build.outcome == 'success' && steps.locate_mas_pkg.outputs.path != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit') uses: actions/upload-artifact@v7 with: name: termix_macos_universal_mas @@ -722,7 +738,7 @@ jobs: choco pack termix-ssh.nuspec if ($LASTEXITCODE -ne 0) { - throw "Chocolatey push failed with exit code $LASTEXITCODE" + throw "Chocolatey pack failed with exit code $LASTEXITCODE" } - name: Check for Chocolatey API Key @@ -952,8 +968,8 @@ jobs: cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb - sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb - sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb + sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-submission/Casks/t/termix.rb + sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-submission/Casks/t/termix.rb - name: Verify Cask syntax run: | diff --git a/README.md b/README.md index 7f3386e77..4fcb7af5b 100644 --- a/README.md +++ b/README.md @@ -109,14 +109,14 @@ Want to build your own? Start from the [plugin template](https://github.com/Term Windows x64/ia32 -Portable · MSI Installer · Chocolatey +Portable · EXE and MSI Installer · Chocolatey -Linux x64/ia32 +Linux x64/arm64/armv7l Portable · AUR · AppImage · Deb · Flatpak -macOS x64/ia32, v12.0+ +macOS Universal/x64/arm64, v12.0+ Apple App Store · DMG · Homebrew @@ -149,6 +149,10 @@ services: - termix-data:/app/data environment: PORT: "8080" + GUACD_HOST: "guacd" + GUACD_TUNNEL_HOST: "termix" + GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole" + GUACD_DRIVE_PATH: "/termix-data/rdp-drive" depends_on: - guacd networks: @@ -158,8 +162,8 @@ services: image: guacamole/guacd:1.6.0 container_name: guacd restart: unless-stopped - ports: - - "4822:4822" + volumes: + - termix-data:/termix-data networks: - termix-net diff --git a/components.json b/components.json index 26b31d574..10e27bb21 100644 --- a/components.json +++ b/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/index.css", + "css": "src/ui/index.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" @@ -15,7 +15,7 @@ "aliases": { "components": "@/components", "utils": "@/lib/utils", - "ui": "@/components/ui", + "ui": "@/components", "lib": "@/lib", "hooks": "@/hooks" }, diff --git a/docker/Dockerfile b/docker/Dockerfile index 157708a49..9159f321a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # Stage 1: Install dependencies # The builders only produce JS, so they run on the build machine's own arch # once instead of under QEMU per target. Rolldown hangs under arm64 emulation. -FROM --platform=$BUILDPLATFORM node:26-slim AS deps +FROM --platform=$BUILDPLATFORM node:24-slim AS deps WORKDIR /app RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* @@ -37,7 +37,7 @@ COPY . . RUN npm run build:backend # Stage 4: Download OPKSSH binary for the target platform so the image works offline -FROM node:26-slim AS opkssh-downloader +FROM node:24-slim AS opkssh-downloader ARG TARGETARCH ARG OPKSSH_VERSION=v0.16.0 ARG OPKSSH_SHA256_AMD64=c018c3e7baf98612b923e742dd87be38650bf61e3b755fb2bc90de177568b1bf @@ -58,7 +58,7 @@ RUN case "$TARGETARCH" in \ echo -n "$OPKSSH_VERSION" > version.txt # Stage 5: Production dependencies only -FROM node:26-slim AS production-deps +FROM node:24-slim AS production-deps WORKDIR /app RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* @@ -81,7 +81,7 @@ RUN npm ci --omit=dev --ignore-scripts && \ npm cache clean --force # Stage 6: Final optimized image -FROM node:26-slim +FROM node:24-slim WORKDIR /app # The release the opkssh-downloader stage baked in, so the plugin trusts that diff --git a/electron-builder.json b/electron-builder.json index 59d437a4e..8d2cd7207 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -123,7 +123,7 @@ "entitlements": "packaging/build/entitlements.mac.plist", "entitlementsInherit": "packaging/build/entitlements.mac.inherit.plist", "type": "distribution", - "minimumSystemVersion": "10.15", + "minimumSystemVersion": "12.0", "mergeASARs": false, "singleArchFiles": "**/*.{node,bare,dylib}", "x64ArchFiles": "**/{*.{node,bare,dylib},node-pty/prebuilds/*/spawn-helper}" diff --git a/eslint.config.mjs b/eslint.config.mjs index 675a66a6b..1e4fad2b4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -53,7 +53,7 @@ export default tseslint.config([ }, { // MySQL has no RETURNING clause, and drizzle's mysql-core does not expose - // the method at all — a bare .returning() is a TypeError there, not a bad + // the method at all. A bare .returning() is a TypeError there, not a bad // query, and it only fails on the engine no test in this repo runs against. // // 175 call sites were migrated off it. This is what stops number 176. @@ -85,14 +85,14 @@ export default tseslint.config([ { // Postgres and SQLite spell it ON CONFLICT; MySQL spells it ON // DUPLICATE KEY and names no columns, so drizzle's mysql-core has no - // onConflictDoUpdate at all — another TypeError, not a bad query. + // onConflictDoUpdate at all, another TypeError, not a bad query. selector: "CallExpression[callee.property.name='onConflictDoUpdate']", message: "MySQL has no ON CONFLICT. Use upsert() from ./returning.js.", }, { // better-sqlite3 puts these on a write result; node-postgres and - // mysql2 do not, so reading them directly yields undefined — and + // mysql2 do not, so reading them directly yields undefined, and // Number(undefined) is NaN, which reaches the database as the string // "NaN" and fails an integer column. Three call sites did exactly // this and only broke on Postgres. diff --git a/scripts/generate-dialect-schema.cjs b/scripts/generate-dialect-schema.cjs index 337360111..2e2c60295 100644 --- a/scripts/generate-dialect-schema.cjs +++ b/scripts/generate-dialect-schema.cjs @@ -7,8 +7,8 @@ * drizzle/mysql. Nothing imports them to run a query. * * That is not an oversight. The query builder needs two things from a table - * object — the identifiers to interpolate, and the encoders that turn JS values - * into driver values — and the sqlite definitions supply both correctly for + * object: the identifiers to interpolate, and the encoders that turn JS values + * into driver values. The sqlite definitions supply both correctly for * every engine, which is why all 44 repositories import schema.ts directly: * * - text and integer encode as themselves everywhere @@ -17,15 +17,15 @@ * which is true for JS `true` as well as for 1 * - real is a plain number on all three * - * What genuinely differs between the dialects is DDL — column types, key - * lengths, autoincrement syntax — and DDL is exactly what these files exist to + * What genuinely differs between the dialects is DDL (column types, key + * lengths, autoincrement syntax), and DDL is exactly what these files exist to * generate. See scripts/verify-dialects.mjs, which asserts the round-trips * above against real servers rather than trusting this comment. * * The schema is declared once, in sqlite-core, and the other two dialects are * derived. Hand-maintaining three copies of 52 tables would mean a renamed * table has to land in three places consistently or a foreign key silently - * points at the wrong one — and the schema is regular enough that the mapping + * points at the wrong one, and the schema is regular enough that the mapping * is mechanical. * * What varies between dialects is small and closed: @@ -198,7 +198,7 @@ function transform(source, dialect) { ); // Timestamps are stored as text (see sql-timestamp.ts). MySQL only accepts - // DEFAULT CURRENT_TIMESTAMP on a DATETIME or TIMESTAMP column — on a TEXT + // DEFAULT CURRENT_TIMESTAMP on a DATETIME or TIMESTAMP column. On a TEXT // one it is ER_INVALID_DEFAULT, "Invalid default value". Since 8.0.13 an // expression default works on any type, and an expression is written // parenthesised. MariaDB accepts the bare form, which is why this only @@ -272,13 +272,13 @@ function transform(source, dialect) { } function header(dialect) { - return `// GENERATED FILE — do not edit. + return `// GENERATED FILE, do not edit. // // Produced from schema.ts by scripts/generate-dialect-schema.cjs. // Edit the sqlite schema and re-run \`node scripts/generate-dialect-schema.cjs\`. // Target dialect: ${dialect}. // -// DDL source for drizzle-kit. NOT imported to run queries — repositories use +// DDL source for drizzle-kit. NOT imported to run queries. Repositories use // schema.ts on every dialect. See the generator header for why that is correct. `; } diff --git a/scripts/generate-dialect-schema.test.ts b/scripts/generate-dialect-schema.test.ts index 39ae102e7..9cd455569 100644 --- a/scripts/generate-dialect-schema.test.ts +++ b/scripts/generate-dialect-schema.test.ts @@ -137,7 +137,7 @@ describe("mysql output", () => { expect(out).toContain('double("score")'); }); - it("makes key columns varchar — MySQL cannot index unbounded TEXT", () => { + it("makes key columns varchar since MySQL cannot index unbounded TEXT", () => { expect(out).toContain('varchar("user_id", { length: 255 })'); expect(out).toContain('text("name")'); }); diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs index fac5e9f10..7343b34c7 100644 --- a/scripts/generate-icons.mjs +++ b/scripts/generate-icons.mjs @@ -46,7 +46,7 @@ await sharp(svgBuffer) .toFile(join(publicDir, "full-icon.png")); console.log(" ✓ full-icon.png"); -// favicon.ico — embed 16, 32, 48 px layers +// favicon.ico: embed 16, 32, 48 px layers console.log("Generating favicon.ico..."); const icoSizes = [16, 32, 48]; const icoBuffers = await Promise.all( @@ -55,7 +55,7 @@ const icoBuffers = await Promise.all( writeFileSync(join(publicDir, "favicon.ico"), buildIco(icoBuffers, icoSizes)); console.log(" ✓ favicon.ico"); -// icon.ico — embed 16, 32, 48, 64, 128, 256 px layers +// icon.ico: embed 16, 32, 48, 64, 128, 256 px layers console.log("Generating icon.ico..."); const winSizes = [16, 32, 48, 64, 128, 256]; const winBuffers = await Promise.all( diff --git a/scripts/generate-release-body.cjs b/scripts/generate-release-body.cjs index 36e571e03..92a26b03d 100644 --- a/scripts/generate-release-body.cjs +++ b/scripts/generate-release-body.cjs @@ -37,11 +37,11 @@ function buildTable(version, mobileVersion, mobileTag) { return [ `| Architecture | Windows | Linux | Mac | Android | iOS |`, `|------------------|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|---------------------------------------------|-----------------------------------|`, - `| **x86-64 (64-bit)** | [EXE](${win("termix_windows_x64_nsis.exe")}) · [MSI](${win("termix_windows_x64_msi.msi")}) · [Portable](${win("termix_windows_x64_portable.zip")}) | [AppImage](${linux("termix_linux_x64_appimage.AppImage")}) · [DEB](${linux("termix_linux_x64_deb.deb")}) · [Portable](${linux("termix_linux_x64_portable.tar.gz")}) | [DMG](${mac("termix_macos_x64_dmg.dmg")}) | — | — |`, - `| **AArch64 (ARM64)** | — | [AppImage](${linux("termix_linux_arm64_appimage.AppImage")}) · [DEB](${linux("termix_linux_arm64_deb.deb")}) · [Portable](${linux("termix_linux_arm64_portable.tar.gz")}) | [DMG](${mac("termix_macos_arm64_dmg.dmg")}) | [APK (${mobileVersion})](${mobileBase}/termix_android.apk) | [IPA (${mobileVersion})](${mobileBase}/termix_ios.ipa) |`, - `| **ARMv7 (32-bit)** | — | [AppImage](${linux("termix_linux_armv7l_appimage.AppImage")}) · [DEB](${linux("termix_linux_armv7l_deb.deb")}) · [Portable](${linux("termix_linux_armv7l_portable.tar.gz")}) | — | — | — |`, - `| **x86-32 (32-bit)** | [EXE](${win("termix_windows_ia32_nsis.exe")}) · [MSI](${win("termix_windows_ia32_msi.msi")}) · [Portable](${win("termix_windows_ia32_portable.zip")}) | — | — | — | — |`, - `| **Universal** | [Chocolatey](https://docs.termix.site/install/connector/windows) | [Flatpak](https://docs.termix.site/install/connector/linux) | [DMG](${mac("termix_macos_universal_dmg.dmg")}) · [App Store](https://apps.apple.com/us/app/termix-ssh-companion/id6752672071) · [Homebrew](https://docs.termix.site/install/connector/macos) | — | — |`, + `| **x86-64 (64-bit)** | [EXE](${win("termix_windows_x64_nsis.exe")}) · [MSI](${win("termix_windows_x64_msi.msi")}) · [Portable](${win("termix_windows_x64_portable.zip")}) | [AppImage](${linux("termix_linux_x64_appimage.AppImage")}) · [DEB](${linux("termix_linux_x64_deb.deb")}) · [Portable](${linux("termix_linux_x64_portable.tar.gz")}) | [DMG](${mac("termix_macos_x64_dmg.dmg")}) | - | - |`, + `| **AArch64 (ARM64)** | - | [AppImage](${linux("termix_linux_arm64_appimage.AppImage")}) · [DEB](${linux("termix_linux_arm64_deb.deb")}) · [Portable](${linux("termix_linux_arm64_portable.tar.gz")}) | [DMG](${mac("termix_macos_arm64_dmg.dmg")}) | [APK (${mobileVersion})](${mobileBase}/termix_android.apk) | [IPA (${mobileVersion})](${mobileBase}/termix_ios.ipa) |`, + `| **ARMv7 (32-bit)** | - | [AppImage](${linux("termix_linux_armv7l_appimage.AppImage")}) · [DEB](${linux("termix_linux_armv7l_deb.deb")}) · [Portable](${linux("termix_linux_armv7l_portable.tar.gz")}) | - | - | - |`, + `| **x86-32 (32-bit)** | [EXE](${win("termix_windows_ia32_nsis.exe")}) · [MSI](${win("termix_windows_ia32_msi.msi")}) · [Portable](${win("termix_windows_ia32_portable.zip")}) | - | - | - | - |`, + `| **Universal** | [Chocolatey](https://docs.termix.site/install/apps/windows) | [Flatpak](https://docs.termix.site/install/apps/linux) | [DMG](${mac("termix_macos_universal_dmg.dmg")}) · [App Store](https://apps.apple.com/us/app/termix-ssh-companion/id6752672071) · [Homebrew](https://docs.termix.site/install/apps/macos) | - | - |`, ].join("\n"); } diff --git a/scripts/verify-dialects.mjs b/scripts/verify-dialects.mjs index 7251798ae..abac9aab0 100644 --- a/scripts/verify-dialects.mjs +++ b/scripts/verify-dialects.mjs @@ -2,8 +2,8 @@ * Runs the repository layer against a real Postgres or MySQL server. * * The unit tests only ever see SQLite, so the parts of this codebase that - * differ per engine — the RETURNING replacements, the read-then-write - * transactions, the value encoders — have no coverage there at all. This is + * differ per engine (the RETURNING replacements, the read-then-write + * transactions, the value encoders) have no coverage there at all. This is * what covers them, and it needs a live server, which is why it is a script * rather than a test. * @@ -12,7 +12,7 @@ * npm run verify:dialect -- mysql://user:pass@host:3306/db * * Applies the migrations first, through the same runRemoteMigrations() the - * application uses at startup — so a broken migration fails here rather than in + * application uses at startup, so a broken migration fails here rather than in * production. Writes real rows: point it at a scratch database. */ @@ -40,7 +40,7 @@ const { drizzle } = await import( dialect === "postgres" ? "drizzle-orm/node-postgres" : "drizzle-orm/mysql2" ); -// No schema option on purpose — see connect.ts. +// No schema option on purpose, see connect.ts. const db = drizzle(url); const context = { dialect, drizzle: db }; @@ -87,7 +87,7 @@ check("insert returns the stored row", created?.username, "before"); check("boolean true survives the round trip", created?.isAdmin, true); // updateReturning must report the state AFTER the write. Reading first would -// return the value the update replaced — silently, with no error. +// return the value the update replaced, silently, with no error. const updated = await users.update(userId, { username: "after" }); check("update returns the new value", updated?.username, "after");