From 3ee4c4e11bacf52ba840866ecc287f9465932c5d Mon Sep 17 00:00:00 2001 From: Tsurg Date: Mon, 31 Aug 2026 13:34:12 +0100 Subject: [PATCH 1/2] Add Linux packaging for the desktop app Builds and ships the desktop app for Linux across five targets: - deb (Debian/Ubuntu) and zip via Electron Forge makers - rpm (Fedora) via a custom spec + rpmbuild script, since electron-installer-redhat is incompatible with RPM 6's build layout - AppImage via appimagetool - Arch Linux (.pkg.tar.zst) via makepkg + a PKGBUILD Also makes the staged `dapi` CLI wrapper platform-aware so it resolves the app binary on Linux (it previously hardcoded the macOS Contents/MacOS path), and adds `publish-linux` / `publish-arch` jobs to the release workflow. --- .github/workflows/release.yml | 82 ++++++ apps/desktop/forge.config.ts | 20 +- apps/desktop/package.json | 5 + apps/desktop/scripts/make-appimage.mjs | 88 +++++++ apps/desktop/scripts/make-arch.mjs | 66 +++++ apps/desktop/scripts/make-rpm.mjs | 91 +++++++ apps/desktop/scripts/stage-cli.mjs | 12 +- package-lock.json | 327 ++++++++++++++++++++++++ packaging/arch/PKGBUILD | 48 ++++ packaging/arch/diffusion-studio.desktop | 10 + packaging/arch/launcher | 5 + 11 files changed, 751 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/scripts/make-appimage.mjs create mode 100644 apps/desktop/scripts/make-arch.mjs create mode 100644 apps/desktop/scripts/make-rpm.mjs create mode 100644 packaging/arch/PKGBUILD create mode 100644 packaging/arch/diffusion-studio.desktop create mode 100644 packaging/arch/launcher diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9f04121..e6b920d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,7 @@ name: Release on: push: tags: ["v*"] + workflow_dispatch: jobs: publish-macos: @@ -18,6 +19,7 @@ jobs: cache: npm - name: Check versions match tag + if: github.event_name != 'workflow_dispatch' run: | TAG="${GITHUB_REF_NAME#v}" for PKG in package.json apps/desktop/package.json apps/cli/package.json apps/web/package.json; do @@ -54,3 +56,83 @@ jobs: APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: npm run publish --workspace=@diffusionstudio/desktop + + publish-linux: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install packaging tools + run: sudo apt-get update && sudo apt-get install -y rpm fakeroot + + - run: npm ci + + - name: Provide client env for web build + run: cp apps/web/.env.example apps/web/.env + + - name: Build deb and zip + run: npm run make --workspace=@diffusionstudio/desktop + + - name: Build rpm + run: npm run make:rpm --workspace=@diffusionstudio/desktop + + - name: Build AppImage + run: npm run make:appimage --workspace=@diffusionstudio/desktop + + - name: Upload Linux assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 \ + || gh release create "$GITHUB_REF_NAME" --draft --title "$GITHUB_REF_NAME" --generate-notes + gh release upload "$GITHUB_REF_NAME" \ + apps/desktop/out/make/deb/x64/*.deb \ + apps/desktop/out/make/rpm/x64/*.rpm \ + apps/desktop/out/make/zip/linux/x64/*.zip \ + apps/desktop/out/make/*.AppImage \ + --clobber + + publish-arch: + runs-on: ubuntu-latest + container: archlinux:latest + permissions: + contents: write + steps: + # Install git before checkout; the archlinux image is minimal and + # actions/checkout needs it. + - name: Install build tools + run: | + pacman -Sy --noconfirm archlinux-keyring + pacman -Syu --noconfirm base-devel git gh curl + + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - name: Provide client env for web build + run: cp apps/web/.env.example apps/web/.env + + - name: Package app (no makers) + run: npm run package --workspace=@diffusionstudio/desktop + + - name: Build Arch package + run: npm run make:arch --workspace=@diffusionstudio/desktop + + - name: Upload Arch package + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 \ + || gh release create "$GITHUB_REF_NAME" --draft --title "$GITHUB_REF_NAME" --generate-notes + gh release upload "$GITHUB_REF_NAME" apps/desktop/out/arch/*.pkg.tar.zst --clobber diff --git a/apps/desktop/forge.config.ts b/apps/desktop/forge.config.ts index e4dbe0e6..e0913cd1 100644 --- a/apps/desktop/forge.config.ts +++ b/apps/desktop/forge.config.ts @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { ForgeConfig } from '@electron-forge/shared-types'; +import { MakerDeb } from '@electron-forge/maker-deb'; import { MakerDMG } from '@electron-forge/maker-dmg'; import { MakerZIP } from '@electron-forge/maker-zip'; import { PublisherGithub } from '@electron-forge/publisher-github'; @@ -41,7 +42,24 @@ const config: ForgeConfig = { : undefined, }, makers: [ - new MakerZIP({}, ['darwin']), + new MakerZIP({}, ['darwin', 'linux']), + new MakerDeb( + { + options: { + name: 'diffusion-studio', + productName: 'Diffusion Studio', + genericName: 'Video Editor', + bin: 'Diffusion Studio', + description: 'The professional video editor built for agents', + maintainer: 'Diffusion Studio ', + homepage: 'https://diffusion.studio', + section: 'video', + categories: ['AudioVideo', 'Video'], + icon: './assets/icon.png', + }, + }, + ['linux'], + ), new MakerDMG({ name: `Diffusion-Studio-${process.arch}`, icon: './assets/icon.icns', diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3fdf7711..732053f7 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -20,11 +20,16 @@ "dev": "npm run build && electron-forge start", "package": "npm run build && npm run build:web && npm run stage:cli && npm run stage:docs && electron-forge package", "make": "npm run build && npm run build:web && npm run stage:cli && npm run stage:docs && electron-forge make", + "make:linux": "npm run make && npm run make:rpm && npm run make:appimage", + "make:rpm": "node scripts/make-rpm.mjs", + "make:appimage": "node scripts/make-appimage.mjs", + "make:arch": "node scripts/make-arch.mjs", "publish": "npm run build && npm run build:web && npm run stage:cli && npm run stage:docs && electron-forge publish", "make:icns": "sh scripts/make-icns.sh" }, "devDependencies": { "@electron-forge/cli": "^7.11.1", + "@electron-forge/maker-deb": "^7.11.2", "@electron-forge/maker-dmg": "^7.11.1", "@electron-forge/maker-zip": "^7.11.1", "@electron-forge/publisher-github": "^7.11.1", diff --git a/apps/desktop/scripts/make-appimage.mjs b/apps/desktop/scripts/make-appimage.mjs new file mode 100644 index 00000000..1cad4323 --- /dev/null +++ b/apps/desktop/scripts/make-appimage.mjs @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Builds a Linux AppImage from the `electron-forge package` output. Run it +// after `electron-forge make` (or `package`), which leaves the unpacked app at +// `out/Diffusion Studio-linux-x64`. +// +// The app binary is named "Diffusion Studio" (with a space); AppImage's +// desktop `Exec=` and AppRun want a space-free command, so we alias it to +// `diffusion-studio` via a symlink inside the AppDir. +// +// appimagetool is downloaded on first use to ~/.cache/appimagetool (override +// with APPIMAGETOOL_URL). It runs extracted rather than via FUSE so it works +// in containers and CI. + +import { execFileSync, spawnSync } from "node:child_process"; +import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const desktopDir = join(dirname(fileURLToPath(import.meta.url)), ".."); +const require = createRequire(import.meta.url); +const pkg = require(join(desktopDir, "package.json")); + +const ARCH_LABEL = { x64: "x86_64", arm64: "aarch64" }[process.arch] ?? process.arch; +const APP_NAME = "Diffusion Studio"; +const BIN_NAME = "diffusion-studio"; +const version = pkg.version; + +// Locate the packaged directory electron-forge leaves behind. +const outDir = join(desktopDir, "out"); +const packaged = readdirSync(outDir).find((entry) => entry.startsWith(`${APP_NAME}-linux-`)); +if (!packaged) { + console.error("make-appimage: no packaged app found in out/. Run `electron-forge package` first."); + process.exit(1); +} +const packagedDir = join(outDir, packaged); + +const appDir = join(outDir, "appimage", "AppDir"); +rmSync(join(outDir, "appimage"), { recursive: true, force: true }); +mkdirSync(appDir, { recursive: true }); + +// The packaged app, flat at the AppDir root (resources/, the binary, etc.). +cpSync(packagedDir, appDir, { recursive: true }); + +// Space-free alias for the electron binary, referenced by AppRun + desktop Exec. +symlinkSync(`./${APP_NAME}`, join(appDir, BIN_NAME)); + +writeFileSync( + join(appDir, "AppRun"), + `#!/bin/sh\nSELF=$(readlink -f "$0")\nHERE=$(dirname "$SELF")\nexec "$HERE/${BIN_NAME}" "$@"\n`, +); +chmodSync(join(appDir, "AppRun"), 0o755); + +writeFileSync( + join(appDir, `${BIN_NAME}.desktop`), + `[Desktop Entry]\nName=Diffusion Studio\nComment=The professional video editor built for agents\nExec=${BIN_NAME}\nIcon=${BIN_NAME}\nTerminal=false\nType=Application\nCategories=AudioVideo;Video;Graphics;\nStartupWMClass=Diffusion Studio\n`, +); + +copyFileSync(join(desktopDir, "assets", "icon.png"), join(appDir, `${BIN_NAME}.png`)); + +// Fetch appimagetool (a self-contained AppImage) once, then run it extracted +// so no FUSE mount is required. +const toolDir = join(homedir(), ".cache", "appimagetool"); +mkdirSync(toolDir, { recursive: true }); +const tool = join(toolDir, "appimagetool-x86_64.AppImage"); +if (!existsSync(tool)) { + const url = process.env.APPIMAGETOOL_URL + ?? "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"; + console.log(`make-appimage: downloading appimagetool from ${url}`); + spawnSync("curl", ["-fsSL", "-o", tool, url], { stdio: "inherit" }); + chmodSync(tool, 0o755); +} + +const makeDir = join(outDir, "make"); +mkdirSync(makeDir, { recursive: true }); +const output = join(makeDir, `Diffusion-Studio-${version}-${ARCH_LABEL}.AppImage`); + +console.log(`make-appimage: building ${output}`); +execFileSync(tool, [appDir, output], { + stdio: "inherit", + env: { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1", ARCH: ARCH_LABEL }, +}); + +console.log(`make-appimage: wrote ${output}`); diff --git a/apps/desktop/scripts/make-arch.mjs b/apps/desktop/scripts/make-arch.mjs new file mode 100644 index 00000000..99b4d207 --- /dev/null +++ b/apps/desktop/scripts/make-arch.mjs @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Stages an Arch Linux package from the `electron-forge package` output and +// builds it with makepkg. Run after `electron-forge make` (or `package`), +// which leaves the unpacked app at `out/Diffusion Studio-linux-x64`. +// +// The staged source tree lives at out/arch/ and the finished package is +// out/arch/diffusion-studio--.pkg.tar.zst. + +import { execFileSync } from "node:child_process"; +import { cpSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const desktopDir = join(dirname(fileURLToPath(import.meta.url)), ".."); +const repoRoot = join(desktopDir, "..", ".."); +const require = createRequire(import.meta.url); +const version = require(join(desktopDir, "package.json")).version; + +const APP_NAME = "Diffusion Studio"; +const PKG_NAME = "diffusion-studio"; + +const outDir = join(desktopDir, "out"); +const packaged = readdirSync(outDir).find((entry) => entry.startsWith(`${APP_NAME}-linux-`)); +if (!packaged) { + console.error("make-arch: no packaged app found in out/. Run `electron-forge package` first."); + process.exit(1); +} + +const archDir = join(outDir, "arch"); +rmSync(archDir, { recursive: true, force: true }); +mkdirSync(archDir, { recursive: true }); + +// Source tree that makepkg extracts from the tarball. +const src = join(archDir, `${PKG_NAME}-${version}-linux-x64`); +mkdirSync(src, { recursive: true }); +cpSync(join(outDir, packaged), src, { recursive: true }); + +const packagingDir = join(repoRoot, "packaging", "arch"); +cpSync(join(packagingDir, "launcher"), join(src, "launcher")); +cpSync(join(packagingDir, `${PKG_NAME}.desktop`), join(src, `${PKG_NAME}.desktop`)); +cpSync(join(desktopDir, "assets", "icon.png"), join(src, `${PKG_NAME}.png`)); + +// Tar the source (makepkg expects the name in source=()). +const tarball = `${PKG_NAME}-${version}-linux-x64.tar.gz`; +execFileSync("tar", ["-czf", join(archDir, tarball), "-C", archDir, `${PKG_NAME}-${version}-linux-x64`], { + stdio: "inherit", +}); + +// Stage the PKGBUILD with the version substituted. +const pkbuild = readFileSync(join(packagingDir, "PKGBUILD"), "utf8").replaceAll("__VERSION__", version); +writeFileSync(join(archDir, "PKGBUILD"), pkbuild); + +// Build the package in-place. Requires makepkg (pacman) on the host. +console.log(`make-arch: building with makepkg in ${archDir}`); +execFileSync("makepkg", ["-f", "--noconfirm"], { + cwd: archDir, + stdio: "inherit", + env: { ...process.env, PACKAGER: "Diffusion Studio " }, +}); + +const built = readdirSync(archDir).find((entry) => entry.endsWith(".pkg.tar.zst")); +console.log(`make-arch: wrote ${join(archDir, built)}`); diff --git a/apps/desktop/scripts/make-rpm.mjs b/apps/desktop/scripts/make-rpm.mjs new file mode 100644 index 00000000..7e531804 --- /dev/null +++ b/apps/desktop/scripts/make-rpm.mjs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Builds a Fedora RPM from the `electron-forge package` output. Run after +// `electron-forge make` (or `package`), which leaves the unpacked app at +// `out/Diffusion Studio-linux-x64`. +// +// We generate the .spec ourselves rather than use `@electron-forge/maker-rpm` +// (electron-installer-redhat): that library stages to `BUILD/usr` and runs +// `cp -r usr/*` from the build subdir, which breaks on RPM 6's build-directory +// layout. This script works on both RPM 4 (Fedora) and RPM 6. + +import { execFileSync } from "node:child_process"; +import { copyFileSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const desktopDir = join(dirname(fileURLToPath(import.meta.url)), ".."); +const require = createRequire(import.meta.url); +const version = require(join(desktopDir, "package.json")).version; + +const APP_NAME = "Diffusion Studio"; +const PKG_NAME = "diffusion-studio"; +const RPM_ARCH = { x64: "x86_64", arm64: "aarch64" }[process.arch] ?? process.arch; + +const outDir = join(desktopDir, "out"); +const packaged = readdirSync(outDir).find((entry) => entry.startsWith(`${APP_NAME}-linux-`)); +if (!packaged) { + console.error("make-rpm: no packaged app found in out/. Run `electron-forge package` first."); + process.exit(1); +} +const packagedDir = join(outDir, packaged); + +const stageDir = join(outDir, "rpmbuild"); +rmSync(stageDir, { recursive: true, force: true }); +mkdirSync(join(stageDir, "SPECS"), { recursive: true }); + +const desktopFile = join(desktopDir, "..", "..", "packaging", "arch", `${PKG_NAME}.desktop`); +const iconFile = join(desktopDir, "assets", "icon.png"); + +const spec = `Name: ${PKG_NAME} +Version: ${version} +Release: 1 +Summary: The professional video editor built for agents +License: MPL-2.0 +URL: https://diffusion.studio +AutoReqProv: no +Requires: gtk3, nss, libXScrnSaver, alsa-lib, libXcomposite, libXdamage, libXrandr, libXtst, libXcursor, libXfixes, mesa-libgbm, at-spi2-core, libX11 + +%description +The professional video editor built for agents. + +%global __strip /bin/true +%global debug_package %{nil} +%global __os_install_post %{nil} + +%install +mkdir -p %{buildroot}/usr/lib/${PKG_NAME} +cp -a '${packagedDir}/.' %{buildroot}/usr/lib/${PKG_NAME}/ +chmod 4755 %{buildroot}/usr/lib/${PKG_NAME}/chrome-sandbox +mkdir -p %{buildroot}/usr/bin +ln -s '../lib/${PKG_NAME}/${APP_NAME}' %{buildroot}/usr/bin/${PKG_NAME} +mkdir -p %{buildroot}/usr/share/applications +install -m 644 '${desktopFile}' %{buildroot}/usr/share/applications/${PKG_NAME}.desktop +mkdir -p %{buildroot}/usr/share/pixmaps +install -m 644 '${iconFile}' %{buildroot}/usr/share/pixmaps/${PKG_NAME}.png + +%files +/usr/bin/${PKG_NAME} +/usr/lib/${PKG_NAME}/ +/usr/share/applications/${PKG_NAME}.desktop +/usr/share/pixmaps/${PKG_NAME}.png +`; +writeFileSync(join(stageDir, "SPECS", `${PKG_NAME}.spec`), spec); + +console.log(`make-rpm: building with rpmbuild (${stageDir})`); +execFileSync("rpmbuild", ["-bb", join(stageDir, "SPECS", `${PKG_NAME}.spec`), "--define", `_topdir ${stageDir}`], { + stdio: "inherit", +}); + +// rpmbuild writes to RPMS//; move it next to the other Linux artifacts. +const rpmsDir = join(stageDir, "RPMS", RPM_ARCH); +const built = readdirSync(rpmsDir).find((entry) => entry.endsWith(".rpm")); +const makeDir = join(outDir, "make", "rpm", "x64"); +mkdirSync(makeDir, { recursive: true }); +const output = join(makeDir, built); +copyFileSync(join(rpmsDir, built), output); + +console.log(`make-rpm: wrote ${output}`); diff --git a/apps/desktop/scripts/stage-cli.mjs b/apps/desktop/scripts/stage-cli.mjs index 90590cda..70aabc39 100644 --- a/apps/desktop/scripts/stage-cli.mjs +++ b/apps/desktop/scripts/stage-cli.mjs @@ -51,6 +51,14 @@ execFileSync("npm", ["install", "--omit=dev", "--no-audit", "--no-fund", "--no-p // The wrapper runs the CLI bundle on the app's own Electron binary in Node // mode, so users need no separate Node install. It resolves symlinks first // because both Homebrew and the in-app installer link it into PATH. +// +// The path from the staged `cli/bin` directory to the app binary differs per +// platform: on macOS the binary lives inside the .app bundle at +// Contents/MacOS/Diffusion Studio, while on Linux it sits at the packaged +// directory root (e.g. `Diffusion Studio-linux-x64/Diffusion Studio`). +const isDarwin = process.platform === "darwin"; +const appPathRel = isDarwin ? "$DIR/../../../.." : "$DIR/../../.."; +const binaryRel = isDarwin ? "$DIR/../../../MacOS/Diffusion Studio" : "$DIR/../../../Diffusion Studio"; const wrapper = `#!/bin/sh SELF="$0" while [ -L "$SELF" ]; do @@ -61,8 +69,8 @@ while [ -L "$SELF" ]; do esac done DIR="$(cd "$(dirname "$SELF")" && pwd)" -export DIFFUSION_APP_PATH="$(cd "$DIR/../../../.." && pwd)" -ELECTRON_RUN_AS_NODE=1 exec "$DIR/../../../MacOS/Diffusion Studio" "$DIR/../dapi.js" "$@" +export DIFFUSION_APP_PATH="$(cd "${appPathRel}" && pwd)" +ELECTRON_RUN_AS_NODE=1 exec "${binaryRel}" "$DIR/../dapi.js" "$@" `; writeFileSync(join(stageDir, "bin", "dapi"), wrapper); chmodSync(join(stageDir, "bin", "dapi"), 0o755); diff --git a/package-lock.json b/package-lock.json index 162608cb..e475cd5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ }, "devDependencies": { "@electron-forge/cli": "^7.11.1", + "@electron-forge/maker-deb": "^7.11.2", "@electron-forge/maker-dmg": "^7.11.1", "@electron-forge/maker-zip": "^7.11.1", "@electron-forge/publisher-github": "^7.11.1", @@ -786,6 +787,23 @@ "node": ">= 16.4.0" } }, + "node_modules/@electron-forge/maker-deb": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.11.2.tgz", + "integrity": "sha512-MYSdCTsqzKNmsmaq7CIFh2kJdBWUZ4njxnVGrIRClzueVITk5Kots3+eQo+e5QQLvXTVn2XTNDc2nYjvtBh+Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.11.2", + "@electron-forge/shared-types": "7.11.2" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-installer-debian": "^3.2.0" + } + }, "node_modules/@electron-forge/maker-dmg": { "version": "7.11.2", "resolved": "https://registry.npmjs.org/@electron-forge/maker-dmg/-/maker-dmg-7.11.2.tgz", @@ -4127,6 +4145,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/har-format": { "version": "1.2.16", "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", @@ -6210,6 +6239,242 @@ "node": ">= 22.12.0" } }, + "node_modules/electron-installer-common": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.4.tgz", + "integrity": "sha512-8gMNPXfAqUE5CfXg8RL0vXpLE9HAaPkgLXVoHE3BMUzogMWenf4LmwQ27BdCUrEhkjrKl+igs2IHJibclR3z3Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@electron/asar": "^3.2.5", + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "glob": "^7.1.4", + "lodash": "^4.17.15", + "parse-author": "^2.0.0", + "semver": "^7.1.1", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "url": "https://github.com/electron-userland/electron-installer-common?sponsor=1" + }, + "optionalDependencies": { + "@types/fs-extra": "^9.0.1" + } + }, + "node_modules/electron-installer-common/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-common/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz", + "integrity": "sha512-58ZrlJ1HQY80VucsEIG9tQ//HrTlG6sfofA3nRGr6TmkX661uJyu4cMPPh6kXW+aHdq/7+q25KyQhDrXvRL7jw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "get-folder-size": "^2.0.1", + "lodash": "^4.17.4", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-debian": "src/cli.js" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-installer-debian/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-debian/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/electron-installer-debian/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron-installer-debian/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-debian/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-debian/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/electron-installer-debian/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-debian/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/electron-installer-dmg": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/electron-installer-dmg/-/electron-installer-dmg-5.0.1.tgz", @@ -7215,6 +7480,15 @@ "node": ">= 12" } }, + "node_modules/gar": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/gar/-/gar-1.0.4.tgz", + "integrity": "sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -7256,6 +7530,21 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-folder-size": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-2.0.1.tgz", + "integrity": "sha512-+CEb+GDCM7tkOS2wdMKTn9vU7DgnKUTuDlehkNJKNSovdCOVxs14OfKCk4cvSaR3za4gj+OBdl9opPN9xrJ0zA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "gar": "^1.0.4", + "tiny-each-async": "2.0.3" + }, + "bin": { + "get-folder-size": "bin/get-folder-size" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -8682,6 +8971,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -11317,6 +11614,14 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-each-async": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz", + "integrity": "sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/tinyest": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyest/-/tinyest-0.3.2.tgz", @@ -11397,6 +11702,28 @@ "node": ">=0.6.0" } }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tmp-promise/node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/tn1150": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tn1150/-/tn1150-0.1.0.tgz", diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD new file mode 100644 index 00000000..aaeec8f8 --- /dev/null +++ b/packaging/arch/PKGBUILD @@ -0,0 +1,48 @@ +# Maintainer: Diffusion Studio +# Packages the prebuilt Electron app produced by `electron-forge package` +# into an Arch Linux package. The source tarball (and this file) are staged by +# apps/desktop/scripts/stage-arch.mjs, which substitutes __VERSION__. + +pkgname=diffusion-studio +pkgver=__VERSION__ +pkgrel=1 +pkgdesc="The professional video editor built for agents" +arch=('x86_64') +url="https://diffusion.studio" +license=('MPL-2.0') +# Electron's shared libraries ship pre-stripped release builds; stripping them +# again yields gdb-add-index warnings and a useless -debug package. +options=(!strip !debug) +depends=( + 'gtk3' + 'nss' + 'libxss' + 'alsa-lib' + 'libxtst' + 'libxcursor' + 'libxrandr' + 'libxcomposite' + 'libxdamage' + 'libxfixes' + 'mesa' + 'at-spi2-core' + 'ca-certificates' +) +# Built locally from the forge package output; the tarball is staged next to +# this file by stage-arch.mjs rather than downloaded from a URL. +source=("diffusion-studio-$pkgver-linux-x64.tar.gz") +sha256sums=('SKIP') + +package() { + cd "$srcdir/diffusion-studio-$pkgver-linux-x64" + + install -d "$pkgdir/usr/lib/$pkgname" + cp -a . "$pkgdir/usr/lib/$pkgname/" + + install -Dm755 launcher "$pkgdir/usr/bin/$pkgname" + install -Dm644 "$pkgname.desktop" "$pkgdir/usr/share/applications/$pkgname.desktop" + install -Dm644 "$pkgname.png" "$pkgdir/usr/share/pixmaps/$pkgname.png" + + # The dapi CLI is shipped inside the app's resources; expose it on PATH. + ln -sf "/usr/lib/$pkgname/resources/cli/bin/dapi" "$pkgdir/usr/bin/dapi" +} diff --git a/packaging/arch/diffusion-studio.desktop b/packaging/arch/diffusion-studio.desktop new file mode 100644 index 00000000..1c0bba7d --- /dev/null +++ b/packaging/arch/diffusion-studio.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Diffusion Studio +Comment=The professional video editor built for agents +Exec=diffusion-studio %U +Icon=diffusion-studio +Terminal=false +Type=Application +Categories=AudioVideo;Video;Graphics; +StartupWMClass=Diffusion Studio +MimeType=x-scheme-handler/diffusion; diff --git a/packaging/arch/launcher b/packaging/arch/launcher new file mode 100644 index 00000000..6a884fb5 --- /dev/null +++ b/packaging/arch/launcher @@ -0,0 +1,5 @@ +#!/bin/sh +# Launcher for the installed Arch package. The binary keeps its original name +# ("Diffusion Studio", with a space), so this space-free entry point is what +# /usr/bin/diffusion-studio and the .desktop Exec point at. +exec "/usr/lib/diffusion-studio/Diffusion Studio" "$@" From d5603e95bb944b3dacb1c3b32802c4a85990bf0c Mon Sep 17 00:00:00 2001 From: Tsurg Date: Mon, 31 Aug 2026 13:35:50 +0100 Subject: [PATCH 2/2] Document Linux install and package builds Add a Download badge and an Install section covering the deb, rpm, Arch, and AppImage artifacts, plus build instructions for contributors. --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 5158a6c8..c4b15966 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@

Download for macOS (Apple Silicon) + Download for Linux Discord Follow on X Y Combinator F24 @@ -53,6 +54,32 @@ npx skills add diffusionstudio/skills `/editor` is the main skill you'll use. Ask for what you want in plain language. Behind it is `dapi`, the CLI that drives the app. +## Install + +Download the latest release from the [releases page](https://github.com/diffusionstudio/editor/releases/latest). Linux packages are published alongside the macOS build: + +**Linux** + +```sh +# Arch Linux (and derivatives) +sudo pacman -U diffusion-studio--x86_64.pkg.tar.zst + +# Debian / Ubuntu +sudo dpkg -i diffusion-studio__amd64.deb + +# Fedora / RHEL +sudo dnf install diffusion-studio--1.x86_64.rpm +``` + +Or run the portable [AppImage](https://appimage.org) with no installation: + +```sh +chmod +x Diffusion-Studio--x86_64.AppImage +./Diffusion-Studio--x86_64.AppImage +``` + +The packages put both the editor (`diffusion-studio`) and the agent CLI (`dapi`) on your `PATH`. + ## Prompt examples

@@ -235,6 +262,19 @@ npm run symlink:create --workspace=@diffusionstudio/cli The link points at the CLI build, which `npm run dev:desktop` refreshes on every start, so the linked `dapi` always runs the latest code. +### Building Linux packages + +```sh +npm run make:linux --workspace=@diffusionstudio/desktop # deb, rpm, zip, AppImage +npm run make:arch --workspace=@diffusionstudio/desktop # Arch (.pkg.tar.zst) +``` + +Builds require Node 20 (newer Node versions can break Electron Packager's zip +extraction) plus system tools for each format: `dpkg` and `fakeroot` (deb), +`rpmbuild` (rpm), and `makepkg` (Arch). AppImage builds fetch `appimagetool` +automatically. Artifacts land in `apps/desktop/out/make/` and +`apps/desktop/out/arch/`. + Before sending a PR: ```sh