feat(apps): M3 publisher — guided publish, updates, register-existing

- publisher (§11): resumable state machine persisted in .rowboat-publish.json
  (packaged → repo_created → source_pushed → release_created →
  assets_uploaded → registered → published); Git Data API single-commit
  source push with generated README/LICENSE/.gitignore when absent; both
  release assets uploaded (bundle + standalone manifest); fork+PR registry
  registration with rejected:<code> parsing and rename-retry surface
- publish update (§11.3): semver bump, package, push, release — no registry
- register existing release (§11.5) with client-side asset probe
- IPC: apps:publish/publishUpdate/registerExisting + apps:progress pushes
This commit is contained in:
Gagan 2026-07-06 15:08:36 +05:30
parent 6dea528fb4
commit 35c781b8e4
3 changed files with 480 additions and 0 deletions

View file

@ -69,6 +69,7 @@ import { capture } from '@x/core/dist/analytics/posthog.js';
import * as githubAuth from '@x/core/dist/apps/github-auth.js';
import * as appsInstaller from '@x/core/dist/apps/installer.js';
import { registryClient } from '@x/core/dist/apps/registry.js';
import * as appsPublisher from '@x/core/dist/apps/publisher.js';
// D18 install previews awaiting confirmation, keyed by app name.
const appInstallPreviews = new Map<string, Awaited<ReturnType<typeof appsInstaller.previewInstall>>>();
@ -1673,6 +1674,22 @@ export function setupIpcHandlers() {
'apps:rollback': async (_event, args) => {
return { app: await appsInstaller.rollbackApp(args.folder) };
},
'apps:publish': async (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
const result = await appsPublisher.publishApp(args.folder, (step, detail) => {
win?.webContents.send('apps:progress', { folder: args.folder, step, detail });
});
capture('app_published', { firstPublish: true });
return result;
},
'apps:publishUpdate': async (_event, args) => {
const result = await appsPublisher.publishUpdate(args.folder, args.increment);
capture('app_published', { version: result.version, firstPublish: false });
return result;
},
'apps:registerExisting': async (_event, args) => {
return appsPublisher.registerExisting(args.name, args.repo);
},
'githubAuth:start': async () => {
const result = await githubAuth.startDeviceFlow();
// Surface the code and open GitHub's verification page externally (§10).