Skip to content
Back to skills

Create New Electron App

ASecurity

Генерация каркаса Electron-приложения — протокол, ассоциации файлов, грабли платформы

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
developmenttypescriptgobashsqlnextjsnodetestinggitapi

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Scanned October 20, 2026

npx -y skills add kamiletar/letar --skill create-new-electron-app --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Create New Electron App?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Create New Electron App
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kamiletar-create-new-electron-app/badge)](https://www.skillsdirectory.com/skills/kamiletar-create-new-electron-app)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: create-new-electron-app
description: Генерация каркаса Electron-приложения — протокол, ассоциации файлов, грабли платформы
allowed-tools: Bash, Read, Grep, Glob
disable-model-invocation: true
---

> Параметры берутся из запроса пользователя. В Claude Code маркеры `$ARGUMENTS` и `$1`…`$9` подставляются при вызове навыка.

# New Electron App — создание десктопного приложения

Создай новое Electron-приложение (Electron main + Next.js renderer).

**Имя приложения:** $ARGUMENTS

Для веб-приложений эта инструкция не подходит — там `/create-new-app`. Общие грабли платформы —
[.claude/rules/electron.md](/.claude/rules/electron.md), они обязательны к прочтению до первого
коммита.

## Electron-приложения монорепо (что копировать)

| Приложение                 | Чем полезно как образец                                                                                                          |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `poster-microtext-desktop` | минимальный каркас + бизнес-логика: статический экспорт, `file://`, внешний exe                                                  |
| `label-printer-desktop`    | сложное приложение: Prisma/ZenStack, автообновление, аппаратная периферия                                                        |
| `animatrona`               | максимальная сложность: Next standalone-сервер внутри приложения, свои протоколы, БД через sql.js, deep links, e2e на Playwright |

## Шаг 1 — Генерация каркаса

```bash
nx g @letar/generators:electron-app <name>
```

С явными метаданными (`displayName` идёт в заголовок окна, `productName` и ярлыки инсталлятора):

```bash
nx g @letar/generators:electron-app <name> --displayName="Моё приложение" --description="Что делает" --private
```

Генератор раскладывает: `main/` (`background.ts`, `preload.ts`, `ipc/`), `renderer/` (Next.js
`output: 'export'` + Chakra UI v3), `nextron.config.js`, `main/webpack.config.js`,
`electron-builder.yml`, `project.json` (`dev`, `build:win`, `lint`, `typecheck:tsgo`, `format`),
`scripts/dev.js` + `scripts/generate-icons.mjs`, `resources/icon.svg`, `PLAN.md`,
`PLAN_TESTING.md`, `.gitignore`. Версия `electron`/`electron-builder` берётся из корневого
`package.json` и пиннится до точной (диапазон `^x.y.z` electron-builder не умеет).

Не перезаписывает существующее `apps/<name>` — падает с понятной ошибкой. Детали:
[libs/generators/README.md § electron-app](/libs/generators/README.md).

```bash
nx dev <name>              # каркас должен открыться и показать версию Electron
nx typecheck:tsgo <name>
nx lint <name>
```

## Шаг 2 — Решить главное: `file://` или `app://`

Генератор отдаёт renderer через `loadFile()` (`file://`) — самый дешёвый вариант. **Он не
подойдёт, если приложению нужен Web Worker или WASM:** под `file://` origin равен `null`, и
Chromium блокирует и Worker, и `fetch` к соседним файлам. Ловится только на живом запуске — сборка
и типы зелёные.

Задай себе вопрос **до** написания кода:

- [ ] Нужны Worker / WASM / secure context (`crypto.subtle`, Service Worker)? — например
      SubtitlesOctopus для ASS-субтитров, ffmpeg.wasm, любой парсер на WASM
- [ ] Нужно больше одной страницы (вложенные роуты)?

Хотя бы одно «да» → переходи на `app://`: `protocol.registerSchemesAsPrivileged` **до**
`app.whenReady()` + `protocol.handle` после, вместо `loadFile()` — `loadURL('app://bundle/')`.
Заодно снимается хак `assetPrefix: './'` и ограничение «одна страница на корне».

Готовый код, таблица выбора между `file://` / `app://` / localhost-сервером и способ проверить,
что дело именно в origin — [electron-app-protocol.md](/.claude/docs/electron-app-protocol.md).

Третий вариант (Next standalone-сервер внутри приложения, как в `animatrona`) нужен только когда
renderer'у требуются серверные возможности Next.js: API-роуты, server actions, ORM в рантайме.

## Шаг 3 — Открытие файлов: ассоциации + single instance lock

Нужно, если приложение открывает файлы (плеер, просмотрщик, редактор). Главный вход в такое
приложение — **двойной щелчок по файлу**, а не иконка, поэтому это не «потом», а часть первой
версии.

**1. Ассоциации в `electron-builder.yml`:**

```yaml
fileAssociations:
  - ext: [mkv, mp4]
    name: Video
    description: Видеофайл
    role: Viewer # macOS
    icon: resources/icon.ico
```

**2. Приём пути к файлу в main-процессе — три разных канала:**

```typescript
// Windows/Linux: путь приходит в argv при холодном старте.
// В dev первый аргумент — путь к скрипту, поэтому срезаем его через process.defaultApp
function fileFromArgv(argv: string[]): string | undefined {
  const args = process.defaultApp ? argv.slice(2) : argv.slice(1)
  return args.find((arg) => !arg.startsWith('--'))
}

// macOS: только событие, argv пустой. Вешать ДО whenReady — иначе событие потеряется
app.on('open-file', (event, filePath) => {
  event.preventDefault()
  openFile(filePath)
})

// Приложение уже запущено — второй экземпляр не нужен, путь передаём первому
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  app.quit()
} else {
  app.on('second-instance', (_event, argv) => {
    const filePath = fileFromArgv(argv)
    if (filePath) {
      openFile(filePath)
    }
    // Развернуть и сфокусировать окно — иначе клик по файлу выглядит как «ничего не произошло»
    const win = BrowserWindow.getAllWindows()[0]
    if (win) {
      if (win.isMinimized()) {
        win.restore()
      }
      win.show()
      win.focus()
    }
  })
}
```

⚠️ **Путь может прийти раньше, чем renderer готов его принять.** Складывай его в переменную
(`pendingFile`) и отдавай renderer'у, когда тот сам запросит через IPC — иначе первый файл при
холодном старте молча теряется. Рабочий образец этого паттерна (для deep links, но логика та же) —
`apps/animatrona/main/services/deep-link.ts`: `pendingAction` + отдача по запросу renderer'а.

Если нужен ещё и свой URL-протокол (`myapp://...`) — `app.setAsDefaultProtocolClient` из того же
файла, там же различие dev/prod.

## Шаг 4 — Грабли, на которых спотыкались все три приложения

Полный список — [.claude/rules/electron.md](/.claude/rules/electron.md) § «Грабли». Минимум, что
нужно знать до первого билда:

- **Статический экспорт + `file://`** — нужен `assetPrefix: './'`, иначе JS не грузится и
  интерактивность мертва при внешне нормальной странице (или сразу `app://`, см. Шаг 2).
- **Native-модули под Bun + electron-builder** — транзитивные зависимости (`sharp` →
  `detect-libc`/`semver`) не резолвятся после упаковки. Предпочитай портативный exe через
  `child_process` вместо npm-пакета с нативным биндингом. Внешние бинарники — в `extraResources`,
  не в `files`.
- **Electron ≥32: `File.path` у drag&drop больше нет** — только `webUtils.getPathForFile(file)`
  через `contextBridge`.
- **Точная версия electron** в `devDependencies` (`"42.6.1"`, не `"^42.6.1"`) и `publish: null` в
  `electron-builder.yml`, если нет provider'а.
- **Вывод сторонних CLI парси с оглядкой на локаль** — на русской Windows проценты приходят как
  `0,00%`, регулярка на `\d+\.\d+%` молча не совпадает.

## Шаг 5 — Как проверять то, что нельзя проверить в песочнице

**GUI-уровень (клики, диалоги, drag&drop) в песочнице Claude Code не проверяется** — Chromium
network service падает даже с `--no-sandbox`. Не считай «собралось и алгоритм проверен» равным
«протестировано»: настоящие GUI-баги вылезают на первом живом запуске.

**Main-процесс проверяется headless, окно создавать не нужно:**

```bash
bun build main/services/<сервис>.ts --target=node --format=cjs \
  --external electron --outfile scripts/.bundle.cjs
npx electron scripts/verify-<что-проверяем>.cjs <аргументы>
```

Скрипт доходит до `app.whenReady()` и гоняет любые сервисы main-процесса — Chromium при этом не
поднимается, падать нечему. Так проверен весь пайплайн `poster-microtext-desktop` на реальной
картинке, без единого клика.

**Для GUI — e2e через Playwright + `electron.launch()`** (образец: `apps/animatrona-e2e`,
`helpers/electron.helpers.ts` — изолированный `--user-data-dir`, стабы `dialog.showOpenDialog`,
ожидание main-окна после splash). Заводится генератором:

```bash
nx g @letar/generators:e2e-suite <name>
```

⚠️ **E2E-тест, который «проходит», может ничего не проверять.** Прецедент в `animatrona-e2e`: все
тесты плеера искали `getByRole('link', { name: /плеер/i })`, а пункты сайдбара — `Box as="button"`.
Локатор не находился, тесты уходили в `test.skip()` и репорт был зелёным. Правила, чтобы не
повторить:

- скипать только по одной причине — отсутствие production-билда; всё остальное — провал теста;
- не использовать `isVisible()` для ожидания (возвращает результат немедленно) — только
  `expect(...).toBeVisible()` / `waitFor`;
- новый тест сначала прогнать на **старом** билде и убедиться, что он **падает** — иначе он не
  доказывает ничего.

## Шаг 6 — Дальнейшие шаги

- [ ] Иконка: заменить `resources/icon.svg` → `node scripts/generate-icons.mjs`
- [ ] `PLAN.md` приложения: занести реальные фазы вместо шаблонных
- [ ] `.agents/skills/<name>/SKILL.md` — общий навык воркфлоу (шаблон в `/create-new-app`)
- [ ] Приватное приложение → git submodule `kamiletar/letar-private-<name>` (пошагово — в
      `/create-new-app` § «Приватные приложения»; `.gitignore` положить **до** первого `git add`)
- [ ] Обновить `paths:` в [.claude/rules/electron.md](/.claude/rules/electron.md) — добавить
      `apps/<name>/**`, иначе правила платформы не подхватятся в этой папке
- [ ] Сборка: `nx build:win <name>` (в генераторе только Windows-таргет — mac/linux дописывать
      руками по образцу `apps/animatrona/project.json`)
- [ ] Релизы десктопных приложений собираются из монорепо `letar` — отдельного репозитория не
      заводить

⛔ Деплой десктопных приложений через deploy-agent-dev не идёт — это не серверное приложение. Публикация
релиза (GitHub Releases, автообновление) — отдельная задача, образец — `label-printer-desktop`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.