Skip to content
Open
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
82 changes: 82 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Release
on:
push:
tags: ["v*"]
workflow_dispatch:

jobs:
publish-macos:
Expand All @@ -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
Expand Down Expand Up @@ -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
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<p align="center">
<a href="https://github.com/diffusionstudio/editor/releases/latest/download/Diffusion-Studio-arm64.dmg"><img src="https://img.shields.io/badge/Download-macOS%20Apple%20Silicon-161616?style=flat&logo=apple&logoColor=F8F8F8&labelColor=000000" alt="Download for macOS (Apple Silicon)" /></a>
<a href="https://github.com/diffusionstudio/editor/releases/latest"><img src="https://img.shields.io/badge/Download-Linux-161616?style=flat&logo=linux&logoColor=F8F8F8&labelColor=000000" alt="Download for Linux" /></a>
<a href="https://discord.com/invite/zPQJrNGuFB"><img src="https://img.shields.io/discord/1115673443141156924?style=flat&logo=discord&logoColor=F8F8F8&label=Discord&labelColor=000000&color=161616" alt="Discord" /></a>
<a href="https://x.com/diffusionhq"><img src="https://img.shields.io/badge/Follow%20for-Updates-161616?style=flat&logo=x&logoColor=F8F8F8&labelColor=000000" alt="Follow on X" /></a>
<a href="https://www.ycombinator.com/companies/diffusion-studio"><img src="https://img.shields.io/badge/Combinator-F24-161616?style=flat&logo=ycombinator&logoColor=F8F8F8&labelColor=000000" alt="Y Combinator F24" /></a>
Expand Down Expand Up @@ -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-<version>-x86_64.pkg.tar.zst

# Debian / Ubuntu
sudo dpkg -i diffusion-studio_<version>_amd64.deb

# Fedora / RHEL
sudo dnf install diffusion-studio-<version>-1.x86_64.rpm
```

Or run the portable [AppImage](https://appimage.org) with no installation:

```sh
chmod +x Diffusion-Studio-<version>-x86_64.AppImage
./Diffusion-Studio-<version>-x86_64.AppImage
```

The packages put both the editor (`diffusion-studio`) and the agent CLI (`dapi`) on your `PATH`.

## Prompt examples

<details open>
Expand Down Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion apps/desktop/forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 <support@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',
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
88 changes: 88 additions & 0 deletions apps/desktop/scripts/make-appimage.mjs
Original file line number Diff line number Diff line change
@@ -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}`);
66 changes: 66 additions & 0 deletions apps/desktop/scripts/make-arch.mjs
Original file line number Diff line number Diff line change
@@ -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-<version>-<arch>.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 <support@diffusion.studio>" },
});

const built = readdirSync(archDir).find((entry) => entry.endsWith(".pkg.tar.zst"));
console.log(`make-arch: wrote ${join(archDir, built)}`);
Loading