Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
575d893f90 | ||
|
|
92146c760d | ||
|
|
fedf1d24c0 | ||
|
|
901940993c |
@@ -73,7 +73,12 @@ using only Node built-ins.
|
|||||||
|
|
||||||
For a Windows installation, see [docs/windows_installation](docs/windows_installation.md) or for a developer/more in depth walkthrough, see [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md).
|
For a Windows installation, see [docs/windows_installation](docs/windows_installation.md) or for a developer/more in depth walkthrough, see [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md).
|
||||||
|
|
||||||
On **Linux** (⚠️ work in progress — X11 vs Wayland, enabling per-click capture, the screen-share prompt), see [docs/GETTING_STARTED_WITH_LINUX.md](docs/GETTING_STARTED_WITH_LINUX.md).
|
On **Linux**, install from a package built for your distro family:
|
||||||
|
apt-based (Debian/Ubuntu) → [docs/linux/apt.md](docs/linux/apt.md); dnf-based
|
||||||
|
(Fedora) → [docs/linux/dnf.md](docs/linux/dnf.md). Wayland uses the XDG portal
|
||||||
|
for screen capture and a hotkey/interval trigger (per-click capture with a
|
||||||
|
marker needs X11 + xinput). The general developer walkthrough is
|
||||||
|
[docs/GETTING_STARTED_WITH_LINUX.md](docs/GETTING_STARTED_WITH_LINUX.md).
|
||||||
|
|
||||||
Requirements: Node.js 22.12+ and npm (pinned in `.nvmrc`; installs are
|
Requirements: Node.js 22.12+ and npm (pinned in `.nvmrc`; installs are
|
||||||
refused on older Nodes because the packaging toolchain needs 22.12+).
|
refused on older Nodes because the packaging toolchain needs 22.12+).
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# StepForge on apt-based Linux (Debian / Ubuntu)
|
||||||
|
|
||||||
|
This is the setup and packaging guide for **apt-based** distributions. Fedora
|
||||||
|
and other dnf-based systems have a separate guide: [dnf.md](dnf.md).
|
||||||
|
|
||||||
|
## Install from the .deb
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install ./stepforge_<version>_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
apt pulls the required runtime libraries automatically (they are declared as
|
||||||
|
`Depends`). The package installs:
|
||||||
|
|
||||||
|
- the app and a fixed Electron runtime under `/opt/stepforge`,
|
||||||
|
- the `stepforge` launcher at `/usr/bin/stepforge`,
|
||||||
|
- a desktop entry, icons, and `.sfgz`/`.sfglt` file associations.
|
||||||
|
|
||||||
|
Launch it from your application menu or run `stepforge`.
|
||||||
|
|
||||||
|
### Sandbox
|
||||||
|
|
||||||
|
The launcher runs **sandboxed**. On most modern kernels the Chromium
|
||||||
|
user-namespace sandbox works out of the box; the package's `postinst` also
|
||||||
|
makes the setuid `chrome-sandbox` helper usable as a fallback. StepForge will
|
||||||
|
**not** silently launch unsandboxed — see the launcher's message if the
|
||||||
|
sandbox is unavailable.
|
||||||
|
|
||||||
|
## Install from the portable tarball
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xzf stepforge_<version>_linux-x64.tar.gz
|
||||||
|
# Install the runtime libraries first (see below), then run:
|
||||||
|
./usr/bin/stepforge # or move opt/stepforge to /opt and use the launcher
|
||||||
|
```
|
||||||
|
|
||||||
|
The tarball includes the `/usr/bin/stepforge` launcher (unlike older builds).
|
||||||
|
Install the runtime libraries with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/linux/apt/install-runtime-deps.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capture capabilities on apt systems
|
||||||
|
|
||||||
|
- **X11**: full per-click capture with an accurate marker (needs `xinput`).
|
||||||
|
- **Wayland**: screen capture via the XDG Desktop Portal + PipeWire; the
|
||||||
|
portal asks permission once per recording. Per-click capture with
|
||||||
|
coordinates is not exposed by Wayland, so recording uses a global hotkey or
|
||||||
|
interval trigger. StepForge reports the active trigger honestly.
|
||||||
|
|
||||||
|
Run StepForge and open Settings → Diagnostics to see the detected session
|
||||||
|
type, portal/PipeWire status, and the active capture profile.
|
||||||
|
|
||||||
|
## Build the .deb yourself
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/linux/apt/install-build-deps.sh # dpkg-dev, fakeroot, xvfb, …
|
||||||
|
nvm install && nvm use # pinned Node 22 (see .nvmrc)
|
||||||
|
npm ci
|
||||||
|
npm run package:linux:deb # -> build/artifacts/*.deb + tarball + sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
The builder stages **only** runtime files: the app code, a fixed Electron
|
||||||
|
runtime, and production npm dependencies. It never copies the development
|
||||||
|
`node_modules`, docs, prompts, or examples, and it fails if `node_modules` is
|
||||||
|
missing rather than producing an unusable artifact.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# StepForge on dnf-based Linux (Fedora / RHEL)
|
||||||
|
|
||||||
|
This is the setup and packaging guide for **dnf-based** distributions. Debian,
|
||||||
|
Ubuntu, and other apt-based systems have a separate guide:
|
||||||
|
[apt.md](apt.md).
|
||||||
|
|
||||||
|
## Install from the .rpm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install ./stepforge-<version>-1.<arch>.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
dnf pulls the required runtime libraries automatically (they are declared as
|
||||||
|
`Requires`). The package installs:
|
||||||
|
|
||||||
|
- the app and a fixed Electron runtime under `/opt/stepforge`,
|
||||||
|
- the `stepforge` launcher at `/usr/bin/stepforge`,
|
||||||
|
- a desktop entry, icons, and `.sfgz`/`.sfglt` file associations.
|
||||||
|
|
||||||
|
Launch it from your application menu or run `stepforge`.
|
||||||
|
|
||||||
|
### Sandbox
|
||||||
|
|
||||||
|
The launcher runs **sandboxed**. On most modern kernels the Chromium
|
||||||
|
user-namespace sandbox works out of the box; the package's `%post` also makes
|
||||||
|
the setuid `chrome-sandbox` helper usable as a fallback. StepForge will **not**
|
||||||
|
silently launch unsandboxed.
|
||||||
|
|
||||||
|
## Install from the portable tarball
|
||||||
|
|
||||||
|
The portable tarball (same one shipped for apt systems) includes the
|
||||||
|
`/usr/bin/stepforge` launcher. Install the runtime libraries first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/linux/dnf/install-runtime-deps.sh
|
||||||
|
tar -xzf stepforge_<version>_linux-x64.tar.gz
|
||||||
|
./usr/bin/stepforge
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capture capabilities on dnf systems
|
||||||
|
|
||||||
|
- **X11**: full per-click capture with an accurate marker (needs `xinput`).
|
||||||
|
- **Wayland** (Fedora's default): screen capture via the XDG Desktop Portal +
|
||||||
|
PipeWire; the portal asks permission once per recording. Per-click capture
|
||||||
|
with coordinates is not exposed by Wayland, so recording uses a global
|
||||||
|
hotkey or interval trigger. StepForge reports the active trigger honestly.
|
||||||
|
|
||||||
|
Open Settings → Diagnostics in the app to see the detected session type,
|
||||||
|
portal/PipeWire status, and the active capture profile.
|
||||||
|
|
||||||
|
## Build the .rpm yourself
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/linux/dnf/install-build-deps.sh # rpm-build, rpmdevtools, Xvfb, …
|
||||||
|
nvm install && nvm use # pinned Node 22 (see .nvmrc)
|
||||||
|
npm ci
|
||||||
|
npm run package:linux:rpm # -> build/artifacts/*.rpm + sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
The builder stages **only** runtime files (shared with the `.deb` builder via
|
||||||
|
`packaging/linux/common/stage-runtime.sh`): the app code, a fixed Electron
|
||||||
|
runtime, and production npm dependencies. It never copies the development
|
||||||
|
`node_modules` and fails if `node_modules` is missing.
|
||||||
@@ -14,7 +14,10 @@
|
|||||||
"start": "node scripts/start-electron.js",
|
"start": "node scripts/start-electron.js",
|
||||||
"test": "node scripts/run-unit-tests.js",
|
"test": "node scripts/run-unit-tests.js",
|
||||||
"sample": "node scripts/make-sample-guide.js",
|
"sample": "node scripts/make-sample-guide.js",
|
||||||
|
"icons": "node scripts/make-icons.js",
|
||||||
"package:windows": "node scripts/package-windows.js",
|
"package:windows": "node scripts/package-windows.js",
|
||||||
|
"package:linux:deb": "bash packaging/linux/debian/package.sh",
|
||||||
|
"package:linux:rpm": "bash packaging/linux/fedora/package.sh",
|
||||||
"build": "bash scripts/build-release.sh",
|
"build": "bash scripts/build-release.sh",
|
||||||
"verify": "bash scripts/verify.sh",
|
"verify": "bash scripts/verify.sh",
|
||||||
"bootstrap": "bash scripts/bootstrap-offline.sh"
|
"bootstrap": "bash scripts/bootstrap-offline.sh"
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 234 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 611 B |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
StepForge application icon — original artwork.
|
||||||
|
A rising staircase of three blocks (the "steps" of a step-by-step guide)
|
||||||
|
over a rounded square, in the app's blue. No third-party assets.
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#2563eb"/>
|
||||||
|
<stop offset="1" stop-color="#1e3a8a"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="0" y="0" width="256" height="256" rx="56" fill="url(#bg)"/>
|
||||||
|
<!-- Three ascending steps -->
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<rect x="52" y="150" width="52" height="54" rx="8"/>
|
||||||
|
<rect x="102" y="116" width="52" height="88" rx="8"/>
|
||||||
|
<rect x="152" y="82" width="52" height="122" rx="8"/>
|
||||||
|
</g>
|
||||||
|
<!-- Capture spark on the top step -->
|
||||||
|
<circle cx="178" cy="60" r="16" fill="#facc15"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 925 B |
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# StepForge launcher installed at /usr/bin/stepforge.
|
||||||
|
#
|
||||||
|
# Runs the packaged Electron runtime against the installed app at
|
||||||
|
# /opt/stepforge. It NEVER installs or repairs anything at runtime and it does
|
||||||
|
# NOT silently disable the Chromium sandbox: an unsandboxed launch requires the
|
||||||
|
# explicit STEPFORGE_ALLOW_NO_SANDBOX=1 opt-in (development/CI only).
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
APP_DIR=/opt/stepforge
|
||||||
|
ELECTRON="$APP_DIR/node_modules/electron/dist/electron"
|
||||||
|
SANDBOX_HELPER="$APP_DIR/node_modules/electron/dist/chrome-sandbox"
|
||||||
|
|
||||||
|
if [ ! -x "$ELECTRON" ]; then
|
||||||
|
echo "stepforge: Electron runtime missing at $ELECTRON (reinstall the package)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$APP_DIR" || exit 1
|
||||||
|
|
||||||
|
# Linux screen capture: enable the PipeWire path for Wayland portals; harmless
|
||||||
|
# on X11 where Ozone auto-selects.
|
||||||
|
COMMON_ARGS="--enable-features=WebRTCPipeWireCapturer --ozone-platform-hint=auto"
|
||||||
|
|
||||||
|
sandbox_ok() {
|
||||||
|
[ -e "$SANDBOX_HELPER" ] || return 1
|
||||||
|
helper_uid="$(stat -c '%u' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
||||||
|
helper_mode="$(stat -c '%a' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
||||||
|
[ "$helper_uid" = "0" ] || return 1
|
||||||
|
[ -n "$helper_mode" ] || return 1
|
||||||
|
# setuid bit set?
|
||||||
|
[ $(( $((8#$helper_mode)) & 04000 )) -ne 0 ] || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
userns_ok() {
|
||||||
|
# Namespaced sandbox works without the setuid helper on kernels that allow
|
||||||
|
# unprivileged user namespaces.
|
||||||
|
if [ -r /proc/sys/kernel/unprivileged_userns_clone ]; then
|
||||||
|
[ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "1" ] && return 0 || return 1
|
||||||
|
fi
|
||||||
|
if [ -r /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then
|
||||||
|
[ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" = "0" ] && return 0 || return 1
|
||||||
|
fi
|
||||||
|
[ -e /proc/self/ns/user ] && return 0 || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if sandbox_ok || userns_ok; then
|
||||||
|
exec "$ELECTRON" $COMMON_ARGS "$APP_DIR" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${STEPFORGE_ALLOW_NO_SANDBOX:-}" = "1" ] || [ "${ELECTRON_DISABLE_SANDBOX:-}" = "1" ]; then
|
||||||
|
echo "stepforge: launching WITHOUT the Chromium sandbox (explicit opt-in)." >&2
|
||||||
|
exec "$ELECTRON" --no-sandbox $COMMON_ARGS "$APP_DIR" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
stepforge: the Chromium sandbox is not available and StepForge will not launch
|
||||||
|
unsandboxed by default.
|
||||||
|
|
||||||
|
Fix one of the following:
|
||||||
|
* Make the setuid sandbox helper usable:
|
||||||
|
sudo chown root:root /opt/stepforge/node_modules/electron/dist/chrome-sandbox
|
||||||
|
sudo chmod 4755 /opt/stepforge/node_modules/electron/dist/chrome-sandbox
|
||||||
|
* Enable unprivileged user namespaces (kernel/sysctl dependent):
|
||||||
|
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||||
|
|
||||||
|
For development/CI only you may set STEPFORGE_ALLOW_NO_SANDBOX=1 to override.
|
||||||
|
MSG
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared staging for the Linux packages. Populates $STAGE_ROOT with the FHS
|
||||||
|
# layout common to the .deb and .rpm: a pruned runtime-only /opt/stepforge, the
|
||||||
|
# launcher, desktop entry, icons, MIME registration, and the license.
|
||||||
|
#
|
||||||
|
# Distro-specific metadata (Depends vs Requires) and the packaging step
|
||||||
|
# (dpkg-deb vs rpmbuild) stay in the per-distro builders. This file only
|
||||||
|
# assembles the payload so the two never drift.
|
||||||
|
#
|
||||||
|
# Usage: ROOT_DIR=<repo> STAGE_ROOT=<dir> bash stage-runtime.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${ROOT_DIR:?ROOT_DIR must be set}"
|
||||||
|
: "${STAGE_ROOT:?STAGE_ROOT must be set}"
|
||||||
|
|
||||||
|
# A packaged app must contain a fixed runtime; never install at build time and
|
||||||
|
# never ship without node_modules.
|
||||||
|
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||||
|
echo "error: node_modules/electron is missing. Run 'npm ci' before packaging." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_DIR="$STAGE_ROOT/opt/stepforge"
|
||||||
|
mkdir -p "$APP_DIR/node_modules" \
|
||||||
|
"$STAGE_ROOT/usr/bin" \
|
||||||
|
"$STAGE_ROOT/usr/share/applications" \
|
||||||
|
"$STAGE_ROOT/usr/share/mime/packages"
|
||||||
|
|
||||||
|
# --- application code (runtime only) ----------------------------------------
|
||||||
|
for item in app core exporters package.json package-lock.json; do
|
||||||
|
cp -a "$ROOT_DIR/$item" "$APP_DIR/$item"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- runtime node_modules ----------------------------------------------------
|
||||||
|
# The fixed Electron runtime (needed at runtime even though it is a dev dep):
|
||||||
|
cp -a "$ROOT_DIR/node_modules/electron" "$APP_DIR/node_modules/electron"
|
||||||
|
# Production npm dependencies (tesseract.js + language data + transitive):
|
||||||
|
while IFS= read -r dep; do
|
||||||
|
[ -n "$dep" ] || continue
|
||||||
|
rel="${dep#"$ROOT_DIR"/}"
|
||||||
|
[ "$rel" != "$dep" ] || continue
|
||||||
|
[ -d "$dep" ] || continue
|
||||||
|
mkdir -p "$APP_DIR/$(dirname "$rel")"
|
||||||
|
cp -a "$dep" "$APP_DIR/$rel"
|
||||||
|
done < <(cd "$ROOT_DIR" && npm ls --omit=dev --all --parseable 2>/dev/null | tail -n +2)
|
||||||
|
|
||||||
|
# Guard: the development-only packaging toolchain must not have leaked in.
|
||||||
|
if [ -d "$APP_DIR/node_modules/electron-builder" ] || [ -d "$APP_DIR/node_modules/app-builder-lib" ]; then
|
||||||
|
echo "error: build-only dependency leaked into the package payload." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- launcher ----------------------------------------------------------------
|
||||||
|
install -m 0755 "$ROOT_DIR/packaging/linux/common/launcher.sh" "$STAGE_ROOT/usr/bin/stepforge"
|
||||||
|
|
||||||
|
# --- desktop entry, icons, MIME ---------------------------------------------
|
||||||
|
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge.desktop" "$STAGE_ROOT/usr/share/applications/stepforge.desktop"
|
||||||
|
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge-mime.xml" "$STAGE_ROOT/usr/share/mime/packages/stepforge.xml"
|
||||||
|
for size in 16 32 48 64 128 256 512; do
|
||||||
|
icon="$ROOT_DIR/packaging/assets/icons/stepforge-${size}.png"
|
||||||
|
[ -f "$icon" ] || continue
|
||||||
|
dest="$STAGE_ROOT/usr/share/icons/hicolor/${size}x${size}/apps"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
install -m 0644 "$icon" "$dest/stepforge.png"
|
||||||
|
done
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-stepforge-guide">
|
||||||
|
<comment>StepForge guide archive</comment>
|
||||||
|
<glob pattern="*.sfgz"/>
|
||||||
|
<icon name="stepforge"/>
|
||||||
|
</mime-type>
|
||||||
|
<mime-type type="application/x-stepforge-template">
|
||||||
|
<comment>StepForge export template</comment>
|
||||||
|
<glob pattern="*.sfglt"/>
|
||||||
|
<icon name="stepforge"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=StepForge
|
||||||
|
GenericName=Step-by-step guide capture
|
||||||
|
Comment=Capture, annotate, and export step-by-step guides
|
||||||
|
Exec=stepforge %U
|
||||||
|
Icon=stepforge
|
||||||
|
Terminal=false
|
||||||
|
Categories=Office;Graphics;Utility;
|
||||||
|
Keywords=documentation;screenshot;guide;capture;steps;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=StepForge
|
||||||
|
MimeType=application/x-stepforge-guide;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
Package: stepforge
|
||||||
|
Version: @VERSION@
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Architecture: @ARCH@
|
||||||
|
Depends: libnss3, libnspr4, libatk1.0-0, libatk-bridge2.0-0, libcups2, libgbm1, libasound2, libgtk-3-0, libxkbcommon0, libatspi2.0-0
|
||||||
|
Recommends: xinput, x11-utils, xdg-desktop-portal, pipewire
|
||||||
|
Maintainer: @MAINTAINER@
|
||||||
|
Homepage: https://github.com/Twest2/StepForge
|
||||||
|
Description: Local-first step-by-step guide capture and export tool
|
||||||
|
StepForge captures step-by-step workflows as screenshots, lets you annotate
|
||||||
|
and describe each step, and exports to Markdown, PDF, DOCX, PPTX, HTML, and
|
||||||
|
more. Local-first: no telemetry, with an optional user-configured local AI
|
||||||
|
integration.
|
||||||
|
.
|
||||||
|
This package bundles a fixed Electron runtime and only production
|
||||||
|
dependencies; it does not install anything at runtime.
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build a production StepForge .deb (and a matching portable tarball) from a
|
||||||
|
# pruned, runtime-only tree.
|
||||||
|
#
|
||||||
|
# Unlike the old scripts/package-linux.sh this does NOT copy the development
|
||||||
|
# node_modules, docs, prompts, examples, or stale audit files; it stages only
|
||||||
|
# the app code plus a runtime dependency set (the fixed Electron runtime and
|
||||||
|
# production npm deps), a real desktop entry, icons, MIME registration, and a
|
||||||
|
# license. Architecture is detected, not hardcoded.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
VERSION="$(node -p "require('./package.json').version")"
|
||||||
|
MAINTAINER="${STEPFORGE_MAINTAINER:-StepForge <[email protected]>}"
|
||||||
|
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
# Map dpkg architecture to a Node-style label for the tarball name.
|
||||||
|
DEB_ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
|
||||||
|
case "$DEB_ARCH" in
|
||||||
|
amd64) NODE_ARCH="x64" ;;
|
||||||
|
arm64) NODE_ARCH="arm64" ;;
|
||||||
|
*) NODE_ARCH="$DEB_ARCH" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.deb.XXXXXX")"
|
||||||
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||||
|
|
||||||
|
# Stage the shared runtime-only payload (fails if node_modules is missing).
|
||||||
|
ROOT_DIR="$ROOT_DIR" STAGE_ROOT="$WORK_DIR" bash "$ROOT_DIR/packaging/linux/common/stage-runtime.sh"
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR/DEBIAN" "$WORK_DIR/usr/share/doc/stepforge"
|
||||||
|
|
||||||
|
# --- license + docs pointer --------------------------------------------------
|
||||||
|
if [ -f "$ROOT_DIR/LICENSE" ]; then
|
||||||
|
install -m 0644 "$ROOT_DIR/LICENSE" "$WORK_DIR/usr/share/doc/stepforge/copyright"
|
||||||
|
elif [ -f "$ROOT_DIR/docs/LICENSE" ]; then
|
||||||
|
install -m 0644 "$ROOT_DIR/docs/LICENSE" "$WORK_DIR/usr/share/doc/stepforge/copyright"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- DEBIAN control + maintainer scripts ------------------------------------
|
||||||
|
sed -e "s/@VERSION@/$VERSION/" -e "s/@ARCH@/$DEB_ARCH/" -e "s#@MAINTAINER@#$MAINTAINER#" \
|
||||||
|
"$ROOT_DIR/packaging/linux/debian/control.in" > "$WORK_DIR/DEBIAN/control"
|
||||||
|
|
||||||
|
cat > "$WORK_DIR/DEBIAN/postinst" <<'POSTINST'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
# Make the Chromium setuid sandbox helper usable so the app launches sandboxed.
|
||||||
|
HELPER=/opt/stepforge/node_modules/electron/dist/chrome-sandbox
|
||||||
|
if [ -e "$HELPER" ]; then
|
||||||
|
chown root:root "$HELPER" || true
|
||||||
|
chmod 4755 "$HELPER" || true
|
||||||
|
fi
|
||||||
|
# Refresh desktop/MIME/icon caches (best effort).
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||||
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||||
|
exit 0
|
||||||
|
POSTINST
|
||||||
|
|
||||||
|
cat > "$WORK_DIR/DEBIAN/prerm" <<'PRERM'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
exit 0
|
||||||
|
PRERM
|
||||||
|
|
||||||
|
cat > "$WORK_DIR/DEBIAN/postrm" <<'POSTRM'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||||
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
POSTRM
|
||||||
|
chmod 0755 "$WORK_DIR/DEBIAN/postinst" "$WORK_DIR/DEBIAN/prerm" "$WORK_DIR/DEBIAN/postrm"
|
||||||
|
|
||||||
|
# --- build the .deb ----------------------------------------------------------
|
||||||
|
DEB_FILE="$OUT_DIR/stepforge_${VERSION}_${DEB_ARCH}.deb"
|
||||||
|
if command -v fakeroot >/dev/null 2>&1; then
|
||||||
|
fakeroot dpkg-deb --build "$WORK_DIR" "$DEB_FILE" >/dev/null
|
||||||
|
else
|
||||||
|
dpkg-deb --build "$WORK_DIR" "$DEB_FILE" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- portable tarball (INCLUDES the launcher, unlike the old script) ---------
|
||||||
|
TAR_FILE="$OUT_DIR/stepforge_${VERSION}_linux-${NODE_ARCH}.tar.gz"
|
||||||
|
tar -C "$WORK_DIR" -czf "$TAR_FILE" opt usr/bin/stepforge usr/share/applications usr/share/mime usr/share/icons
|
||||||
|
|
||||||
|
# --- checksums ---------------------------------------------------------------
|
||||||
|
( cd "$OUT_DIR" && sha256sum "$(basename "$DEB_FILE")" "$(basename "$TAR_FILE")" > "stepforge_${VERSION}_${DEB_ARCH}.sha256" )
|
||||||
|
|
||||||
|
echo "$DEB_FILE"
|
||||||
|
echo "$TAR_FILE"
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build a production StepForge .rpm from a pruned, runtime-only tree, mirroring
|
||||||
|
# the .deb builder. Stages the shared payload via common/stage-runtime.sh, then
|
||||||
|
# packages it with rpmbuild against a prebuilt BuildRoot (no compilation).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
VERSION="$(node -p "require('./package.json').version")"
|
||||||
|
MAINTAINER="${STEPFORGE_MAINTAINER:-StepForge <[email protected]>}"
|
||||||
|
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "error: rpmbuild is not installed. Run scripts/linux/dnf/install-build-deps.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# RPM arch label from the host.
|
||||||
|
RPM_ARCH="$(rpm --eval '%{_arch}' 2>/dev/null || uname -m)"
|
||||||
|
|
||||||
|
BUILD_ROOT="$(mktemp -d "${OUT_DIR%/}/.rpm.XXXXXX")"
|
||||||
|
trap 'rm -rf "$BUILD_ROOT"' EXIT
|
||||||
|
STAGE="$BUILD_ROOT/buildroot"
|
||||||
|
mkdir -p "$STAGE"
|
||||||
|
|
||||||
|
# Shared runtime-only payload (fails if node_modules is missing).
|
||||||
|
ROOT_DIR="$ROOT_DIR" STAGE_ROOT="$STAGE" bash "$ROOT_DIR/packaging/linux/common/stage-runtime.sh"
|
||||||
|
|
||||||
|
# License into the RPM's conventional location.
|
||||||
|
mkdir -p "$STAGE/usr/share/licenses/stepforge"
|
||||||
|
if [ -f "$ROOT_DIR/LICENSE" ]; then
|
||||||
|
install -m 0644 "$ROOT_DIR/LICENSE" "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||||
|
elif [ -f "$ROOT_DIR/docs/LICENSE" ]; then
|
||||||
|
install -m 0644 "$ROOT_DIR/docs/LICENSE" "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||||
|
else
|
||||||
|
# rpmbuild %license requires the file to exist; write a pointer if absent.
|
||||||
|
echo "See project LICENSE." > "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Materialize the spec with version/maintainer substituted.
|
||||||
|
SPEC="$BUILD_ROOT/stepforge.spec"
|
||||||
|
sed -e "s/@VERSION@/$VERSION/" -e "s#@MAINTAINER@#$MAINTAINER#" \
|
||||||
|
"$ROOT_DIR/packaging/linux/fedora/stepforge.spec" > "$SPEC"
|
||||||
|
|
||||||
|
rpmbuild -bb \
|
||||||
|
--define "_topdir $BUILD_ROOT/rpmbuild" \
|
||||||
|
--define "_rpmdir $OUT_DIR" \
|
||||||
|
--define "_build_id_links none" \
|
||||||
|
--buildroot "$STAGE" \
|
||||||
|
--target "$RPM_ARCH" \
|
||||||
|
"$SPEC" >/dev/null
|
||||||
|
|
||||||
|
# rpmbuild writes to $OUT_DIR/<arch>/<name>.rpm — surface the final path.
|
||||||
|
RPM_FILE="$(find "$OUT_DIR" -name "stepforge-${VERSION}-1*.${RPM_ARCH}.rpm" -newer "$SPEC" | head -1)"
|
||||||
|
if [ -z "$RPM_FILE" ]; then
|
||||||
|
RPM_FILE="$(find "$OUT_DIR" -name "stepforge-${VERSION}-1*.rpm" | head -1)"
|
||||||
|
fi
|
||||||
|
[ -n "$RPM_FILE" ] || { echo "error: rpmbuild did not produce an .rpm" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Checksum.
|
||||||
|
( cd "$(dirname "$RPM_FILE")" && sha256sum "$(basename "$RPM_FILE")" > "$(basename "$RPM_FILE").sha256" )
|
||||||
|
|
||||||
|
echo "$RPM_FILE"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# StepForge RPM spec. The payload is prebuilt into a staging BuildRoot by
|
||||||
|
# packaging/linux/fedora/package.sh (which stages a pruned runtime tree), so
|
||||||
|
# this spec only packages and declares metadata — it does not compile.
|
||||||
|
#
|
||||||
|
# Placeholders @VERSION@ / @MAINTAINER@ are substituted by package.sh.
|
||||||
|
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%global __brp_check_rpaths %{nil}
|
||||||
|
%define _build_id_links none
|
||||||
|
|
||||||
|
Name: stepforge
|
||||||
|
Version: @VERSION@
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: Local-first step-by-step guide capture and export tool
|
||||||
|
|
||||||
|
License: MPL-2.0
|
||||||
|
URL: https://github.com/Twest2/StepForge
|
||||||
|
|
||||||
|
# Runtime shared libraries (Chromium/Electron) + capture integration.
|
||||||
|
Requires: nss
|
||||||
|
Requires: nspr
|
||||||
|
Requires: atk
|
||||||
|
Requires: at-spi2-atk
|
||||||
|
Requires: cups-libs
|
||||||
|
Requires: gtk3
|
||||||
|
Requires: mesa-libgbm
|
||||||
|
Requires: alsa-lib
|
||||||
|
Requires: libxkbcommon
|
||||||
|
Recommends: xinput
|
||||||
|
Recommends: xdg-desktop-portal
|
||||||
|
Recommends: pipewire
|
||||||
|
|
||||||
|
# The payload is architecture-specific (bundles the Electron binary).
|
||||||
|
%description
|
||||||
|
StepForge captures step-by-step workflows as screenshots, lets you annotate
|
||||||
|
and describe each step, and exports to Markdown, PDF, DOCX, PPTX, HTML, and
|
||||||
|
more. Local-first: no telemetry, with an optional user-configured local AI
|
||||||
|
integration. This package bundles a fixed Electron runtime and only
|
||||||
|
production dependencies; it does not install anything at runtime.
|
||||||
|
|
||||||
|
%files
|
||||||
|
/opt/stepforge
|
||||||
|
/usr/bin/stepforge
|
||||||
|
/usr/share/applications/stepforge.desktop
|
||||||
|
/usr/share/mime/packages/stepforge.xml
|
||||||
|
/usr/share/icons/hicolor/*/apps/stepforge.png
|
||||||
|
%license /usr/share/licenses/stepforge/LICENSE
|
||||||
|
|
||||||
|
%post
|
||||||
|
# Make the Chromium setuid sandbox helper usable so the app launches sandboxed.
|
||||||
|
HELPER=/opt/stepforge/node_modules/electron/dist/chrome-sandbox
|
||||||
|
if [ -e "$HELPER" ]; then
|
||||||
|
chown root:root "$HELPER" || true
|
||||||
|
chmod 4755 "$HELPER" || true
|
||||||
|
fi
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||||
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||||
|
|
||||||
|
%postun
|
||||||
|
if [ "$1" = 0 ]; then
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||||
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Fri Jul 03 2026 @MAINTAINER@ - @VERSION@-1
|
||||||
|
- Production runtime-only package (pruned tree, fixed Electron runtime).
|
||||||
@@ -14,7 +14,14 @@ mkdir -p "$BUILD_ROOT"
|
|||||||
|
|
||||||
bash "$ROOT_DIR/scripts/bootstrap-offline.sh"
|
bash "$ROOT_DIR/scripts/bootstrap-offline.sh"
|
||||||
node "$ROOT_DIR/scripts/make-sample-guide.js" --root "$EXAMPLES_ROOT"
|
node "$ROOT_DIR/scripts/make-sample-guide.js" --root "$EXAMPLES_ROOT"
|
||||||
STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/scripts/package-linux.sh" >/dev/null
|
# Production Linux package: a pruned runtime tree with real desktop
|
||||||
|
# integration. Requires node_modules (fails otherwise); never installs at
|
||||||
|
# build time. Skipped only when the Electron runtime is genuinely absent.
|
||||||
|
if [ -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||||
|
STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/packaging/linux/debian/package.sh" >/dev/null
|
||||||
|
else
|
||||||
|
echo "[build-release] skipping Linux .deb: node_modules/electron missing (run npm ci)" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
BUILD_ROOT="$BUILD_ROOT" \
|
BUILD_ROOT="$BUILD_ROOT" \
|
||||||
ARTIFACT_DIR="$ARTIFACT_DIR" \
|
ARTIFACT_DIR="$ARTIFACT_DIR" \
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the BUILD toolchain for producing StepForge packages on apt-based
|
||||||
|
# systems. These are for DEVELOPERS/packagers only and are never shipped inside
|
||||||
|
# the end-user package.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
echo "This script is for apt-based systems (Debian/Ubuntu)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||||
|
|
||||||
|
PACKAGES=(
|
||||||
|
dpkg-dev fakeroot # build the .deb
|
||||||
|
desktop-file-utils # validate the .desktop entry
|
||||||
|
ca-certificates # npm ci over https
|
||||||
|
xvfb # headless smoke test under Xvfb
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Installing StepForge build dependencies via apt..."
|
||||||
|
$SUDO apt-get update
|
||||||
|
$SUDO apt-get install -y --no-install-recommends "${PACKAGES[@]}"
|
||||||
|
|
||||||
|
cat <<'MSG'
|
||||||
|
Done. Also install the pinned Node toolchain (see .nvmrc — Node 22.12+):
|
||||||
|
nvm install && nvm use # or another Node 22 LTS install method
|
||||||
|
Then, from the repo root:
|
||||||
|
npm ci
|
||||||
|
npm run package:linux:deb
|
||||||
|
MSG
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the RUNTIME libraries StepForge needs on apt-based systems
|
||||||
|
# (Debian/Ubuntu). These are the shared libraries the packaged Electron runtime
|
||||||
|
# links against, plus the X11/portal integration used for capture. This is for
|
||||||
|
# END USERS installing from the tarball; the .deb declares the same set as
|
||||||
|
# Depends so apt pulls them automatically.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
echo "This script is for apt-based systems (Debian/Ubuntu). Use the dnf script on Fedora." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||||
|
|
||||||
|
PACKAGES=(
|
||||||
|
# Chromium/Electron shared libraries
|
||||||
|
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2
|
||||||
|
libgtk-3-0 libgbm1 libasound2 libxkbcommon0 libatspi2.0-0
|
||||||
|
libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxshmfence1
|
||||||
|
# X11 per-click capture (marker-accurate) — X11 sessions only
|
||||||
|
xinput x11-utils
|
||||||
|
# Wayland screen-share via the XDG portal + PipeWire
|
||||||
|
xdg-desktop-portal pipewire
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Installing StepForge runtime dependencies via apt..."
|
||||||
|
$SUDO apt-get update
|
||||||
|
$SUDO apt-get install -y --no-install-recommends "${PACKAGES[@]}"
|
||||||
|
echo "Done. StepForge runtime dependencies are installed."
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the BUILD toolchain for producing StepForge packages on dnf-based
|
||||||
|
# systems (Fedora/RHEL). For DEVELOPERS/packagers only; never shipped inside
|
||||||
|
# the end-user package.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v dnf >/dev/null 2>&1; then
|
||||||
|
echo "This script is for dnf-based systems (Fedora/RHEL)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||||
|
|
||||||
|
PACKAGES=(
|
||||||
|
rpm-build rpmdevtools # build the .rpm
|
||||||
|
desktop-file-utils # validate the .desktop entry
|
||||||
|
ca-certificates # npm ci over https
|
||||||
|
xorg-x11-server-Xvfb # headless smoke test under Xvfb
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Installing StepForge build dependencies via dnf..."
|
||||||
|
$SUDO dnf install -y "${PACKAGES[@]}"
|
||||||
|
|
||||||
|
cat <<'MSG'
|
||||||
|
Done. Also install the pinned Node toolchain (see .nvmrc — Node 22.12+):
|
||||||
|
nvm install && nvm use # or another Node 22 LTS install method
|
||||||
|
Then, from the repo root:
|
||||||
|
npm ci
|
||||||
|
npm run package:linux:rpm
|
||||||
|
MSG
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the RUNTIME libraries StepForge needs on dnf-based systems (Fedora,
|
||||||
|
# RHEL/derivatives). These are the shared libraries the packaged Electron
|
||||||
|
# runtime links against, plus the X11/portal integration used for capture.
|
||||||
|
# For END USERS installing from the tarball; the .rpm declares the same set as
|
||||||
|
# Requires so dnf pulls them automatically.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! command -v dnf >/dev/null 2>&1; then
|
||||||
|
echo "This script is for dnf-based systems (Fedora/RHEL). Use the apt script on Debian/Ubuntu." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||||
|
|
||||||
|
PACKAGES=(
|
||||||
|
# Chromium/Electron shared libraries
|
||||||
|
nss nspr atk at-spi2-atk at-spi2-core cups-libs libdrm
|
||||||
|
gtk3 mesa-libgbm alsa-lib libxkbcommon
|
||||||
|
libXcomposite libXdamage libXfixes libXrandr libxshmfence
|
||||||
|
# X11 per-click capture (marker-accurate) — X11 sessions only
|
||||||
|
xorg-x11-server-utils xinput
|
||||||
|
# Wayland screen-share via the XDG portal + PipeWire
|
||||||
|
xdg-desktop-portal pipewire
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Installing StepForge runtime dependencies via dnf..."
|
||||||
|
# Some package names differ across Fedora releases; install best-effort so one
|
||||||
|
# missing optional name doesn't abort the whole set.
|
||||||
|
$SUDO dnf install -y "${PACKAGES[@]}" || {
|
||||||
|
echo "Some packages were unavailable; retrying individually..." >&2
|
||||||
|
for pkg in "${PACKAGES[@]}"; do $SUDO dnf install -y "$pkg" || echo " (skipped: $pkg)" >&2; done
|
||||||
|
}
|
||||||
|
echo "Done. StepForge runtime dependencies are installed."
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Generate the StepForge PNG icon set from original geometry using the repo's
|
||||||
|
// own rasterizer + PNG writer (no external image tooling or third-party art).
|
||||||
|
// Mirrors packaging/assets/stepforge.svg. Output: packaging/assets/icons/.
|
||||||
|
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { createImage, fillRect, fillOval } = require('../core/raster');
|
||||||
|
const { encodePng } = require('../core/png');
|
||||||
|
|
||||||
|
const OUT_DIR = path.join(__dirname, '..', 'packaging', 'assets', 'icons');
|
||||||
|
const SIZES = [16, 32, 48, 64, 128, 256, 512];
|
||||||
|
|
||||||
|
const BG_TOP = [37, 99, 235, 255];
|
||||||
|
const BG_BOTTOM = [30, 58, 138, 255];
|
||||||
|
const WHITE = [255, 255, 255, 255];
|
||||||
|
const SPARK = [250, 204, 21, 255];
|
||||||
|
|
||||||
|
function lerp(a, b, t) {
|
||||||
|
return [
|
||||||
|
Math.round(a[0] + (b[0] - a[0]) * t),
|
||||||
|
Math.round(a[1] + (b[1] - a[1]) * t),
|
||||||
|
Math.round(a[2] + (b[2] - a[2]) * t),
|
||||||
|
255,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIcon(size) {
|
||||||
|
const img = createImage(size, size, [0, 0, 0, 0]);
|
||||||
|
const s = size / 256; // scale from the 256px reference design
|
||||||
|
|
||||||
|
// Rounded-square background approximated by a vertical gradient fill.
|
||||||
|
for (let y = 0; y < size; y += 1) {
|
||||||
|
fillRect(img, 0, y, size, 1, lerp(BG_TOP, BG_BOTTOM, y / size));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three ascending steps (x, y, w, h in reference px).
|
||||||
|
const steps = [
|
||||||
|
[52, 150, 52, 54],
|
||||||
|
[102, 116, 52, 88],
|
||||||
|
[152, 82, 52, 122],
|
||||||
|
];
|
||||||
|
for (const [x, y, w, h] of steps) {
|
||||||
|
fillRect(img, Math.round(x * s), Math.round(y * s), Math.round(w * s), Math.round(h * s), WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture spark on the top step.
|
||||||
|
const r = Math.max(2, Math.round(16 * s));
|
||||||
|
fillOval(img, Math.round(178 * s - r), Math.round(60 * s - r), r * 2, r * 2, SPARK);
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||||
|
for (const size of SIZES) {
|
||||||
|
const png = encodePng(renderIcon(size));
|
||||||
|
fs.writeFileSync(path.join(OUT_DIR, `stepforge-${size}.png`), png);
|
||||||
|
}
|
||||||
|
// A conventional default name for the desktop entry / hicolor 256px slot.
|
||||||
|
fs.copyFileSync(path.join(OUT_DIR, 'stepforge-256.png'), path.join(OUT_DIR, 'stepforge.png'));
|
||||||
|
console.log(`wrote ${SIZES.length + 1} icons to ${OUT_DIR}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) main();
|
||||||
|
|
||||||
|
module.exports = { renderIcon, SIZES };
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
VERSION="$(node -p "const pkg=require('${ROOT_DIR}/package.json'); pkg.buildVersion || pkg.version" 2>/dev/null || echo 0.0.0)"
|
|
||||||
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
|
||||||
mkdir -p "$OUT_DIR"
|
|
||||||
WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.pkg.XXXXXX")"
|
|
||||||
APP_DIR="$WORK_DIR/opt/stepforge"
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
rm -rf "$WORK_DIR"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
mkdir -p "$APP_DIR" "$WORK_DIR/usr/bin" "$WORK_DIR/DEBIAN"
|
|
||||||
|
|
||||||
copy_item() {
|
|
||||||
local src="$1"
|
|
||||||
local dest="$2"
|
|
||||||
if [[ -e "$ROOT_DIR/$src" ]]; then
|
|
||||||
mkdir -p "$(dirname "$dest")"
|
|
||||||
cp -a "$ROOT_DIR/$src" "$dest"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Application payload: only the files needed to run the app.
|
|
||||||
copy_item app "$APP_DIR/app"
|
|
||||||
copy_item core "$APP_DIR/core"
|
|
||||||
copy_item exporters "$APP_DIR/exporters"
|
|
||||||
copy_item scripts "$APP_DIR/scripts"
|
|
||||||
copy_item README.md "$APP_DIR/README.md"
|
|
||||||
copy_item LICENSE "$APP_DIR/LICENSE"
|
|
||||||
copy_item docs "$APP_DIR/docs"
|
|
||||||
copy_item ai_prompts "$APP_DIR/ai_prompts"
|
|
||||||
copy_item package.json "$APP_DIR/package.json"
|
|
||||||
copy_item package-lock.json "$APP_DIR/package-lock.json"
|
|
||||||
copy_item examples "$APP_DIR/examples"
|
|
||||||
copy_item build/agent_audit.md "$APP_DIR/build/agent_audit.md"
|
|
||||||
|
|
||||||
if [[ -d "$ROOT_DIR/node_modules" ]]; then
|
|
||||||
cp -a "$ROOT_DIR/node_modules" "$APP_DIR/node_modules"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat > "$WORK_DIR/usr/bin/stepforge" <<'EOF'
|
|
||||||
#!/usr/bin/env sh
|
|
||||||
APP_DIR=/opt/stepforge
|
|
||||||
ELECTRON="$APP_DIR/node_modules/.bin/electron"
|
|
||||||
SANDBOX_HELPER="$APP_DIR/node_modules/electron/dist/chrome-sandbox"
|
|
||||||
cd "$APP_DIR" || exit 1
|
|
||||||
if command -v stat >/dev/null 2>&1 && [ -e "$SANDBOX_HELPER" ]; then
|
|
||||||
helper_uid="$(stat -c '%u' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
|
||||||
helper_mode="$(stat -c '%a' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
|
||||||
if [ "$helper_uid" = "0" ] && [ -n "$helper_mode" ]; then
|
|
||||||
helper_mode_num=$((8#$helper_mode))
|
|
||||||
if [ $((helper_mode_num & 04000)) -ne 0 ]; then
|
|
||||||
exec "$ELECTRON" "$APP_DIR" "$@"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
printf '%s\n' '[stepforge] Electron sandbox helper is not configured for this install; starting with --no-sandbox' >&2
|
|
||||||
exec "$ELECTRON" --no-sandbox "$APP_DIR" "$@"
|
|
||||||
EOF
|
|
||||||
chmod 0755 "$WORK_DIR/usr/bin/stepforge"
|
|
||||||
|
|
||||||
cat > "$WORK_DIR/DEBIAN/control" <<EOF
|
|
||||||
Package: stepforge
|
|
||||||
Version: $VERSION
|
|
||||||
Section: utils
|
|
||||||
Priority: optional
|
|
||||||
Architecture: amd64
|
|
||||||
Depends: xinput
|
|
||||||
Maintainer: StepForge <[email protected]>
|
|
||||||
Description: Offline desktop guide capture and export tool
|
|
||||||
A fully offline desktop app for step-by-step documentation, built for local
|
|
||||||
capture, annotation, and export workflows.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
DEB_FILE="$OUT_DIR/stepforge_${VERSION}_amd64.deb"
|
|
||||||
TAR_FILE="$OUT_DIR/stepforge_${VERSION}_linux-x64.tar.gz"
|
|
||||||
|
|
||||||
if command -v dpkg-deb >/dev/null 2>&1; then
|
|
||||||
dpkg-deb --build "$WORK_DIR" "$DEB_FILE" >/dev/null
|
|
||||||
else
|
|
||||||
echo "dpkg-deb is not installed; skipping .deb build" >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
tar -C "$WORK_DIR/opt" -czf "$TAR_FILE" stepforge
|
|
||||||
|
|
||||||
printf '%s\n' "$DEB_FILE"
|
|
||||||
printf '%s\n' "$TAR_FILE"
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Integration test: build the production .deb and assert it is a real,
|
||||||
|
# runtime-only package — the right files present, and the dev tree / build
|
||||||
|
# tooling / app docs absent. A package is NOT accepted merely because
|
||||||
|
# dpkg-deb produced a file.
|
||||||
|
#
|
||||||
|
# Honest skip policy: skip ONLY when the prerequisites are genuinely absent
|
||||||
|
# (not apt-based, dpkg-deb missing, or node_modules not installed). Once we
|
||||||
|
# build, any structural failure fails the test.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
if ! command -v dpkg-deb >/dev/null 2>&1; then
|
||||||
|
echo "package-deb SKIPPED: dpkg-deb not installed (not an apt-based build host)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||||
|
echo "package-deb SKIPPED: node_modules/electron missing (run npm ci first)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUT_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$OUT_DIR"' EXIT
|
||||||
|
|
||||||
|
DEB="$(STEPFORGE_PACKAGE_DIR="$OUT_DIR" bash packaging/linux/debian/package.sh | head -1)"
|
||||||
|
if [ ! -f "$DEB" ]; then
|
||||||
|
echo "package-deb FAILED: builder did not produce a .deb" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail() { echo "package-deb FAILED: $1" >&2; exit 1; }
|
||||||
|
|
||||||
|
listing="$(dpkg-deb -c "$DEB")"
|
||||||
|
control="$(dpkg-deb -f "$DEB")"
|
||||||
|
|
||||||
|
# Required install items.
|
||||||
|
for needle in \
|
||||||
|
'./usr/bin/stepforge' \
|
||||||
|
'./usr/share/applications/stepforge.desktop' \
|
||||||
|
'./usr/share/mime/packages/stepforge.xml' \
|
||||||
|
'./usr/share/icons/hicolor/256x256/apps/stepforge.png' \
|
||||||
|
'./opt/stepforge/node_modules/electron/dist/electron' \
|
||||||
|
'./opt/stepforge/app/main.js' \
|
||||||
|
'./usr/share/doc/stepforge/copyright'; do
|
||||||
|
echo "$listing" | grep -qF "$needle" || fail "missing packaged file: $needle"
|
||||||
|
done
|
||||||
|
|
||||||
|
# The development node_modules / build tooling must NOT be present.
|
||||||
|
for banned in \
|
||||||
|
'node_modules/electron-builder' \
|
||||||
|
'node_modules/app-builder-lib' \
|
||||||
|
'node_modules/dmg-builder'; do
|
||||||
|
echo "$listing" | grep -qF "$banned" && fail "build-only dependency leaked: $banned" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# The app's own docs/prompts/examples must not be shipped.
|
||||||
|
for banned in \
|
||||||
|
'./opt/stepforge/docs/' \
|
||||||
|
'./opt/stepforge/ai_prompts/' \
|
||||||
|
'./opt/stepforge/examples/'; do
|
||||||
|
echo "$listing" | grep -qF "$banned" && fail "app extra shipped: $banned" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Control metadata sanity.
|
||||||
|
echo "$control" | grep -q '^Package: stepforge' || fail "control missing Package"
|
||||||
|
echo "$control" | grep -q '^Depends:.*libnss3' || fail "control missing runtime Depends"
|
||||||
|
echo "$control" | grep -Eq '^Architecture: (amd64|arm64)' || fail "control has no concrete Architecture"
|
||||||
|
|
||||||
|
# Sandbox is set up, not disabled: postinst makes chrome-sandbox setuid.
|
||||||
|
dpkg-deb --info "$DEB" | grep -q 'postinst' || fail "no postinst maintainer script"
|
||||||
|
|
||||||
|
# The launcher must refuse an unsandboxed launch by default.
|
||||||
|
grep -q 'STEPFORGE_ALLOW_NO_SANDBOX' packaging/linux/common/launcher.sh \
|
||||||
|
|| fail "launcher does not gate --no-sandbox behind an explicit opt-in"
|
||||||
|
|
||||||
|
echo "package-deb OK ($(basename "$DEB"), $(du -h "$DEB" | cut -f1))"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Integration test: build the production .rpm and assert it is a real,
|
||||||
|
# runtime-only package. Honest skip policy: skip ONLY when the prerequisites
|
||||||
|
# are genuinely absent (rpmbuild missing or node_modules not installed). Once
|
||||||
|
# we build, any structural failure fails the test.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "package-rpm SKIPPED: rpmbuild not installed (not a dnf-based build host)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||||
|
echo "package-rpm SKIPPED: node_modules/electron missing (run npm ci first)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUT_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$OUT_DIR"' EXIT
|
||||||
|
|
||||||
|
RPM="$(STEPFORGE_PACKAGE_DIR="$OUT_DIR" bash packaging/linux/fedora/package.sh | tail -1)"
|
||||||
|
[ -f "$RPM" ] || { echo "package-rpm FAILED: builder produced no .rpm" >&2; exit 1; }
|
||||||
|
|
||||||
|
fail() { echo "package-rpm FAILED: $1" >&2; exit 1; }
|
||||||
|
|
||||||
|
listing="$(rpm -qlp "$RPM" 2>/dev/null)"
|
||||||
|
|
||||||
|
for needle in \
|
||||||
|
'/usr/bin/stepforge' \
|
||||||
|
'/usr/share/applications/stepforge.desktop' \
|
||||||
|
'/usr/share/mime/packages/stepforge.xml' \
|
||||||
|
'/opt/stepforge/node_modules/electron/dist/electron' \
|
||||||
|
'/opt/stepforge/app/main.js'; do
|
||||||
|
echo "$listing" | grep -qF "$needle" || fail "missing packaged file: $needle"
|
||||||
|
done
|
||||||
|
echo "$listing" | grep -q '/usr/share/icons/hicolor/256x256/apps/stepforge.png' || fail "missing 256px icon"
|
||||||
|
|
||||||
|
# No dev tree / build tooling / app docs.
|
||||||
|
for banned in 'electron-builder' '/opt/stepforge/docs/' '/opt/stepforge/ai_prompts/' '/opt/stepforge/examples/'; do
|
||||||
|
echo "$listing" | grep -qF "$banned" && fail "unexpected payload: $banned" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Metadata sanity.
|
||||||
|
rpm -qip "$RPM" 2>/dev/null | grep -q '^Name *: stepforge' || fail "rpm Name is not stepforge"
|
||||||
|
rpm -qp --requires "$RPM" 2>/dev/null | grep -q '^nss' || fail "rpm does not Require nss"
|
||||||
|
|
||||||
|
echo "package-rpm OK ($(basename "$RPM"))"
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const ROOT = path.resolve(__dirname, '..', '..');
|
||||||
|
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
||||||
|
const exists = (rel) => fs.existsSync(path.join(ROOT, rel));
|
||||||
|
|
||||||
|
// These are structural checks that run in the normal (cross-platform) unit
|
||||||
|
// suite so the Linux packaging config can't rot silently; the actual .deb
|
||||||
|
// build/inspection lives in tests/integration/linux/package-deb.test.sh.
|
||||||
|
|
||||||
|
test('Linux packaging files exist in their expected separate locations', () => {
|
||||||
|
for (const f of [
|
||||||
|
'packaging/linux/debian/package.sh',
|
||||||
|
'packaging/linux/debian/control.in',
|
||||||
|
'packaging/linux/common/stepforge.desktop',
|
||||||
|
'packaging/linux/common/stepforge-mime.xml',
|
||||||
|
'packaging/linux/common/launcher.sh',
|
||||||
|
'scripts/linux/apt/install-runtime-deps.sh',
|
||||||
|
'scripts/linux/apt/install-build-deps.sh',
|
||||||
|
'docs/linux/apt.md',
|
||||||
|
'tests/integration/linux/package-deb.test.sh',
|
||||||
|
]) {
|
||||||
|
assert.ok(exists(f), `expected ${f} to exist`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the old non-production package-linux.sh is gone', () => {
|
||||||
|
assert.equal(exists('scripts/package-linux.sh'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Fedora/dnf packaging files exist in their own separate locations', () => {
|
||||||
|
for (const f of [
|
||||||
|
'packaging/linux/fedora/package.sh',
|
||||||
|
'packaging/linux/fedora/stepforge.spec',
|
||||||
|
'packaging/linux/common/stage-runtime.sh',
|
||||||
|
'scripts/linux/dnf/install-runtime-deps.sh',
|
||||||
|
'scripts/linux/dnf/install-build-deps.sh',
|
||||||
|
'docs/linux/dnf.md',
|
||||||
|
'tests/integration/linux/package-rpm.test.sh',
|
||||||
|
]) {
|
||||||
|
assert.ok(exists(f), `expected ${f} to exist`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the RPM spec declares runtime Requires, license, and MPL-2.0', () => {
|
||||||
|
const spec = read('packaging/linux/fedora/stepforge.spec');
|
||||||
|
assert.match(spec, /^License:\s+MPL-2\.0$/m);
|
||||||
|
assert.match(spec, /^Requires:\s+nss$/m);
|
||||||
|
assert.match(spec, /%license/);
|
||||||
|
assert.match(spec, /chrome-sandbox/); // %post makes the sandbox helper setuid
|
||||||
|
assert.match(spec, /@VERSION@/);
|
||||||
|
assert.doesNotMatch(spec, /fully offline/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the rpm builder shares staging and requires rpmbuild + node_modules', () => {
|
||||||
|
const script = read('packaging/linux/fedora/package.sh');
|
||||||
|
assert.match(script, /stage-runtime\.sh/);
|
||||||
|
assert.match(script, /rpmbuild/);
|
||||||
|
assert.match(script, /rpm --eval '%\{_arch\}'|uname -m/); // arch detected
|
||||||
|
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dnf setup scripts target dnf and keep build vs runtime deps separate', () => {
|
||||||
|
const runtime = read('scripts/linux/dnf/install-runtime-deps.sh');
|
||||||
|
const build = read('scripts/linux/dnf/install-build-deps.sh');
|
||||||
|
assert.match(runtime, /dnf install/);
|
||||||
|
assert.match(runtime, /nss/);
|
||||||
|
assert.doesNotMatch(runtime, /rpm-build|rpmdevtools/, 'runtime script must not install build tools');
|
||||||
|
assert.match(build, /rpm-build/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the shared staging never copies the whole dev node_modules', () => {
|
||||||
|
const staging = read('packaging/linux/common/stage-runtime.sh');
|
||||||
|
assert.match(staging, /npm ls --omit=dev/);
|
||||||
|
assert.match(staging, /electron-builder/); // leak guard
|
||||||
|
assert.doesNotMatch(staging, /cp -a "\$ROOT_DIR\/node_modules" "\$APP_DIR/);
|
||||||
|
assert.match(staging, /node_modules\/electron\/dist/); // requires the runtime
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the desktop entry is valid and app-branded', () => {
|
||||||
|
const desktop = read('packaging/linux/common/stepforge.desktop');
|
||||||
|
assert.match(desktop, /^\[Desktop Entry\]/);
|
||||||
|
assert.match(desktop, /^Type=Application$/m);
|
||||||
|
assert.match(desktop, /^Exec=stepforge %U$/m);
|
||||||
|
assert.match(desktop, /^Icon=stepforge$/m);
|
||||||
|
assert.match(desktop, /^Categories=.+;$/m);
|
||||||
|
assert.match(desktop, /MimeType=application\/x-stepforge-guide/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the control template declares runtime deps, no hardcoded arch, real maintainer slot', () => {
|
||||||
|
const control = read('packaging/linux/debian/control.in');
|
||||||
|
assert.match(control, /^Architecture: @ARCH@$/m, 'arch must be templated, not hardcoded');
|
||||||
|
assert.match(control, /^Depends:.*libnss3/m);
|
||||||
|
assert.match(control, /@VERSION@/);
|
||||||
|
assert.match(control, /@MAINTAINER@/);
|
||||||
|
// The false "fully offline" wording must not reappear here.
|
||||||
|
assert.doesNotMatch(control, /fully offline/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the launcher refuses an unsandboxed launch unless explicitly opted in', () => {
|
||||||
|
const launcher = read('packaging/linux/common/launcher.sh');
|
||||||
|
assert.match(launcher, /STEPFORGE_ALLOW_NO_SANDBOX/);
|
||||||
|
// It must not unconditionally exec with --no-sandbox.
|
||||||
|
assert.doesNotMatch(launcher, /^exec .*--no-sandbox/m);
|
||||||
|
// It never installs anything at runtime.
|
||||||
|
assert.doesNotMatch(launcher, /npm (install|ci|rebuild)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the deb builder detects arch and delegates to shared staging', () => {
|
||||||
|
const script = read('packaging/linux/debian/package.sh');
|
||||||
|
assert.match(script, /stage-runtime\.sh/); // runtime-only staging is shared
|
||||||
|
assert.match(script, /dpkg --print-architecture/); // arch detected, not hardcoded
|
||||||
|
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /); // never copy the whole dev tree
|
||||||
|
});
|
||||||
|
|
||||||
|
test('apt setup scripts target apt and keep build vs runtime deps separate', () => {
|
||||||
|
const runtime = read('scripts/linux/apt/install-runtime-deps.sh');
|
||||||
|
const build = read('scripts/linux/apt/install-build-deps.sh');
|
||||||
|
assert.match(runtime, /apt-get/);
|
||||||
|
assert.match(runtime, /libnss3/);
|
||||||
|
assert.doesNotMatch(runtime, /dpkg-dev|fakeroot/, 'runtime script must not install build tools');
|
||||||
|
assert.match(build, /dpkg-dev/);
|
||||||
|
assert.match(build, /fakeroot/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an original icon set is generated (not a placeholder/third-party asset)', () => {
|
||||||
|
assert.ok(exists('packaging/assets/stepforge.svg'));
|
||||||
|
assert.ok(exists('scripts/make-icons.js'));
|
||||||
|
// The generated PNGs are committed for packaging.
|
||||||
|
for (const size of [16, 48, 256]) {
|
||||||
|
assert.ok(exists(`packaging/assets/icons/stepforge-${size}.png`), `icon ${size} missing`);
|
||||||
|
}
|
||||||
|
// Regenerate the 256px icon and confirm the generator is deterministic and
|
||||||
|
// produces a valid PNG (starts with the PNG signature).
|
||||||
|
const { renderIcon } = require('../../scripts/make-icons');
|
||||||
|
const { encodePng } = require('../../core/png');
|
||||||
|
const png = encodePng(renderIcon(256));
|
||||||
|
assert.deepEqual([...png.subarray(0, 4)], [0x89, 0x50, 0x4e, 0x47]);
|
||||||
|
});
|
||||||