One product per machine: the CLI and the desktop app share one version, the store accounts and the app folder list
- one version: package.json's version is the desktop app's too (desktopVersion removed); tools/desktop-version.ts keeps the plugin manifests and the app's copies equal, desktop-release publish refuses another version, release order app first then npm publish; the unpublished desktop 0.1.0 candidate is noted in the docs and CHANGELOG - storebound desktop installs (per user, no admin: macOS DMG -> /Applications or ~/Applications, Windows NSIS /S, Linux AppImage + .desktop with the storebound:// handler), updates an older app, notes a newer one (downgrade only with --force), asks once ([Y/n], --yes, exit 3 without a terminal), opens on the folder; --dry / --json / --download; the install steps are pure plans (cli/lib/desktop-install.ts); the version's own manifest dl/<version>/latest.json - credentials: one store, the CLI's credential profiles in ~/.storebound/credentials; the desktop writes and reads them (profiles.rs, files 0600), moves old keychain accounts into profiles on first start, shows profiles the CLI made; precedence env > named profile > project file > default profile; config `account`, "account" in the project's credentials.json; storebound accounts list | import (dedup by key id / service account e-mail, key files copied 0600, project file keeps keystore and review contact, backup, --dry, never prints values) | use - app folders: one list ~/.storebound/projects.json shared by the desktop (registry.rs), storebound desktop, apps folders | add | remove, ws init and ws without a workspace file - desktop: notice when a folder pins another storebound version; storebound-desktop --version - tests: version drift, desktop decisions and per-OS plans, credential precedence and import, registry (TS + Rust), keychain migration and profile round trip (Rust), boot check with old keychain state and the new shared state
This commit is contained in:
parent
bba46b5c7f
commit
d2ce185441
60 changed files with 2443 additions and 246 deletions
|
|
@ -46,7 +46,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
- The webview gets no plugin API. It may call the app commands (each re-checks the window label and the page origin
|
||||
in Rust) and listen to two events. Strict CSP, no remote content. `src-tauri/tests/security.rs` pins all of it.
|
||||
- Store keys (Play service account JSON, App Store Connect key id, issuer id and .p8) are picked in native dialogs,
|
||||
stored in the OS keychain (`keyring`), never shown again and passed to the sidecar per call in memory only.
|
||||
written into the machine's credential profiles (`~/.storebound/credentials`, files 0600 in folders 0700, the same
|
||||
store the CLI reads: `profiles.rs`), never shown again and passed to the sidecar per call in memory only.
|
||||
|
||||
## Screens and data (D2)
|
||||
|
||||
|
|
@ -64,7 +65,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
(`apple.testflight_status`), Checks (offline `scan`: rejection risks with guideline, evidence file:line, fix
|
||||
and skill, owner-accepted ones marked; offline `check`; `validate` with fix hints).
|
||||
- **First-run guide**: what the app does, add or start an app folder (`project.init` = `storebound init --profile`,
|
||||
confirmed in a native dialog), store keys into the keychain and which app uses which, local tools, first read.
|
||||
confirmed in a native dialog), store keys into the shared credential profiles and which app uses which, local
|
||||
tools, first read.
|
||||
- **Command palette** (Cmd/Ctrl+K): apps, tabs, screens and actions. Cmd/Ctrl+1 Portfolio, +2-5 workspace screens,
|
||||
Cmd/Ctrl+Shift+[ ] app tabs, arrow keys in the tab list. Toasts show background jobs with progress and Cancel.
|
||||
- **Unknown, never false.** Store reads run on demand (they use the account's API quota) and are saved in app data
|
||||
|
|
@ -104,7 +106,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
- A **studio** is a name, a colour and an optional logo. It groups any number of store accounts:
|
||||
- a Play service account, which can be invited to several Play developer accounts and then sees all their apps;
|
||||
- an App Store Connect team key, one per team.
|
||||
- Keys stay in the keychain. The state file keeps labels and studio ids only.
|
||||
- Keys live in the credential profiles shared with the CLI. The state file keeps labels, studio ids and the profile
|
||||
name of each account only (see "One product with the CLI" below).
|
||||
- `storebound apps check` gives each account's health, with the fix in words:
|
||||
- ok;
|
||||
- no-apps;
|
||||
|
|
@ -159,7 +162,8 @@ host state file (`overrides_set`, every key checked in `workspace.rs`).
|
|||
- Privacy, with a button that reveals the data folder.
|
||||
- Shortcuts.
|
||||
- Updates: channel (stable / beta), check and install (D5).
|
||||
- Reset: native confirmation, then the keychain entries and every saved state are removed.
|
||||
- Reset: native confirmation, then every saved state of the app is removed; the shared credential profiles and the
|
||||
app folder list stay (the CLI uses them).
|
||||
|
||||
### Writes in the window
|
||||
|
||||
|
|
@ -197,12 +201,12 @@ Each write is a dry run, then the native dialog, then the real run, with one pro
|
|||
### The same model in the terminal
|
||||
|
||||
The CLI workspace (`storebound.workspace.json`, docs/studios.md "Many apps and studios") holds the same studios, accounts
|
||||
(credential profile names instead of keychain entries), folders and overrides. The pairing rules
|
||||
(by credential profile name, the same profiles the app uses), folders and overrides. The pairing rules
|
||||
(`cli/lib/identity.ts`) and the release lanes (`cli/lib/train.ts`) are one implementation for both; the window
|
||||
re-exports them (`src/lib/identity.ts`, `src/lib/model.ts`). `storebound ws desktop import` reads `state.json` into a
|
||||
workspace file (the keychain is never read; the profiles to create are listed); `storebound ws desktop export --merge
|
||||
--apply` adds a workspace to `state.json` while the app is closed (backup kept, nothing removed, new accounts get
|
||||
their keys in the Accounts screen). Project ids in an exported state are stable per folder path (16 hex).
|
||||
workspace file (each account keeps its profile; accounts of older apps get a profile name to create);
|
||||
`storebound ws desktop export --merge --apply` adds a workspace to `state.json` while the app is closed (backup kept,
|
||||
nothing removed; accounts carry their profile, so their keys are already there). Project ids in an exported state are stable per folder path (16 hex).
|
||||
|
||||
### Fill in console
|
||||
|
||||
|
|
@ -337,6 +341,45 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
- **Progress and cancel** go through the same progress events as sidecar jobs; a cancelled download removes its
|
||||
temporary file.
|
||||
|
||||
## One product with the CLI (0.8)
|
||||
|
||||
The CLI and the app are one product: one version number, one set of store accounts, one list of app folders.
|
||||
|
||||
- **One version.** `package.json`'s `version` is the app's too (`tools/desktop-version.ts` keeps the copies equal,
|
||||
CI and `src-tauri/tests/d5.rs` check it), and both are released together (docs/desktop-release.md). The app runs
|
||||
its bundled CLI (the sidecar), which is that same version. A folder whose `package.json` pins another storebound
|
||||
(`devDependencies.storebound`) gets a notice on its app page: "This project uses storebound X, the app runs Y"
|
||||
(`workspace::pin_differs`; tags, ranges and `file:` specs give none).
|
||||
- **`npx storebound desktop`** (cli/desktop.ts, the plans in cli/lib/desktop-install.ts): the app has the CLI's
|
||||
version: open it on the current folder. Not installed: download it for this OS (the CLI's own version from
|
||||
`storebound.dev/desktop/dl/<version>/latest.json`, else the channel's newest), check size, SHA-256 and the minisign
|
||||
signature, install it for this user without admin rights (macOS: the .app from the read-only mounted DMG into
|
||||
/Applications when writable, else ~/Applications, no extended attributes touched; Windows: the NSIS installer with
|
||||
`/S`, per user; Linux: the AppImage into ~/.local/bin plus `~/.local/share/applications/storebound.desktop` with
|
||||
the `storebound://` handler, registered with `update-desktop-database` / `xdg-mime` when they exist; a .deb is only
|
||||
printed, it needs root), then open it. Older app: the same path as an update (never while the app runs); newer
|
||||
app: opened with a note, a downgrade only with `--force`. In a terminal it asks once (`[Y/n]`), `--yes` skips the
|
||||
question, without a terminal and `--yes` it prints the command and exits 3. `--dry` and `--json` change nothing;
|
||||
`--download` only saves the checked installer. `storebound-desktop --version` prints the version (Windows, Linux).
|
||||
- **Accounts** live in the machine's credential profiles, `~/.storebound/credentials/<name>.json` with key files in
|
||||
`<name>/` (0600, folders 0700; `src-tauri/src/profiles.rs`, the CLI's cli/lib/credentials.ts and cli/lib/accounts.ts).
|
||||
An app account is one store of one profile (`Account.profile`); its id for a profile the CLI made is the CLI
|
||||
workspace's (`sha256("<store>:<profile>")`, 16 hex). Adding a key in the app writes the profile that already has
|
||||
that key (same key id / service account e-mail), else one named after the label; removing one moves its key file to
|
||||
`credentials/.removed/`. At start and whenever the list is shown, `profiles::sync` adds an account for every
|
||||
profile store the app does not have yet. **Migration:** accounts of earlier versions kept their keys in the OS
|
||||
keychain (`dev.storebound.desktop`); the first start of 0.8 writes each into a profile (keeping the account's id,
|
||||
label, studio and app mappings), reads it back and only then deletes the keychain entry. An account whose keychain
|
||||
entry is missing stays, marked "key not moved from the keychain yet" (host.log says which).
|
||||
Why files and not the keychain: the CLI has no runtime dependencies and runs on three systems and in agents'
|
||||
terminals; profile files were already its model, so one store means the app writes those files (owner-only modes,
|
||||
like `~/.ssh` and `~/.appstoreconnect/private_keys`) instead of the CLI learning three keychain APIs.
|
||||
- **App folders** are the machine's list `~/.storebound/projects.json` (`src-tauri/src/registry.rs`, the CLI's
|
||||
cli/lib/registry.ts): `storebound desktop` in an app folder, `storebound apps add`, `ws init` and the App folders
|
||||
screen add to it; `storebound apps folders` lists it; `storebound ws` without a workspace file works over it. The
|
||||
app keeps its per-folder extras (accounts) in `state.json` under the same id and syncs at start, when the list is
|
||||
shown and before a `storebound://open` link (a folder already on the list is selected without a question).
|
||||
|
||||
## Release, updates and the terminal bridge (D5)
|
||||
|
||||
- Releases: [desktop-release.md](desktop-release.md) (version authority, privacy gate, macOS signing and
|
||||
|
|
@ -344,9 +387,10 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
- **Updates**: Settings > Updates checks storebound.dev (stable `/desktop/latest.json` or beta
|
||||
`/desktop/beta/latest.json`) only when asked; Install shows a native dialog, then the updater downloads the bundle,
|
||||
checks its minisign signature against `plugins.updater.pubkey` and restarts (`update_check`, `update_install`).
|
||||
- **`storebound://open?path=<absolute folder>`** (sent by `storebound desktop` in an app folder): the host accepts
|
||||
one plain absolute path (`policy::deep_link_open_path`), selects that app folder when it is in the list, otherwise
|
||||
asks in a native dialog and adds it (`deep-link-folder` event). A folder without a storebound config gets a message,
|
||||
- **`storebound://open?path=<absolute folder>`** (sent by `storebound desktop` in an app folder, which first puts
|
||||
the folder on the machine's list): the host accepts one plain absolute path (`policy::deep_link_open_path`), syncs
|
||||
the list, selects that app folder when it is on it, otherwise asks in a native dialog and adds it
|
||||
(`deep-link-folder` event). A folder without a storebound config gets a message,
|
||||
nothing else.
|
||||
- **`storebound-desktop --self-test`**: starts the bundled sidecar, checks the `session.hello` handshake, prints one
|
||||
JSON line and exits, before Tauri starts (no window). The release runs it on the exact artifacts.
|
||||
|
|
@ -368,7 +412,7 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
from list. Portfolio reads skip them.
|
||||
- **Error screens.** A React error boundary around the whole window and one per screen and app tab show the error,
|
||||
Copy details, Reload and Reset saved state (keeps a backup: `state_reset` moves `state.json` to
|
||||
`state.json.reset-<time>.bak` after a native confirmation; keys stay in the keychain). `public/boot.js` runs before
|
||||
`state.json.reset-<time>.bak` after a native confirmation; keys stay in `~/.storebound/credentials`). `public/boot.js` runs before
|
||||
the bundle and shows the same panel in plain DOM when the bundle fails or the window stays empty.
|
||||
- **Logs** in `<app data>/logs`: `host.log` (start, saved state and migration, sidecar start / stop with its last
|
||||
stderr lines, panics) and `webview.log` (window.onerror, unhandled rejections, boundaries, through `log_webview`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue