Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.
Merged
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
30 changes: 28 additions & 2 deletions .github/workflows/release-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,40 @@ jobs:
- name: Build renderer/main/preload bundles (demo mode)
run: npm run build:demo

- name: Package, sign, notarize & publish prerelease to GitHub Releases
- name: Pre-create draft release
# electron-builder races itself creating the GitHub release: the dmg
# and zip artifacts finish near-simultaneously, PublishManager spawns
# two GitHubPublisher instances (async cache-miss race), and both POST
# /releases for the same tag - the loser dies with 422 "Published
# releases must have a valid tag" and the job fails after a successful
# notarization, leaving a half-uploaded release without beta.yml.
# Pre-creating the release means every publisher instance finds and
# reuses it instead of racing to create it. Draft keeps it invisible
# to auto-updaters until all assets (incl. beta.yml) are up.
run: |
TAG="v${{ steps.ver.outputs.version }}"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 || \
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "${{ steps.ver.outputs.version }}" \
--draft --prerelease \
--notes "Demo prerelease build (run ${{ github.run_number }})"

- name: Package, sign, notarize & upload prerelease assets
# extraMetadata.version sets the app/asset/yaml version without editing
# package.json. channel: beta writes beta.yml; releaseType: prerelease
# marks the GitHub release as a prerelease (both required for the GitHub
# provider to resolve the demo channel correctly).
# provider to resolve the demo channel correctly). Assets go into the
# draft pre-created above; the next step flips it public.
run: |
npx electron-builder --mac --publish always \
-c.extraMetadata.version="${{ steps.ver.outputs.version }}" \
-c.mac.notarize=${{ inputs.notarize }} \
-c.publish.channel=beta \
-c.publish.releaseType=prerelease

- name: Publish prerelease
# All assets + beta.yml are uploaded - make the release visible.
run: |
gh release edit "v${{ steps.ver.outputs.version }}" \
--repo "$GITHUB_REPOSITORY" --draft=false --prerelease
38 changes: 30 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,37 @@ jobs:
- name: Build renderer/main/preload bundles (release mode)
run: npm run build:release

- name: Package, sign, notarize & publish to GitHub Releases
# Tag = v${version} from package.json. releaseType `release` publishes
# immediately; `draft` uploads assets into a draft you publish in the UI
# (safer once Windows/Linux jobs are added - publish once all are up).
- name: Pre-create draft release
# electron-builder races itself creating the GitHub release (two
# publisher instances, both POST /releases, loser 422s - see
# release-demo.yml for details). Pre-create the release as a draft so
# every publisher instance finds and reuses it; the last step publishes
# it once all assets (incl. latest.yml) are uploaded.
run: |
RELEASE_TYPE=release
[ "${{ inputs.publish }}" != "true" ] && RELEASE_TYPE=draft
echo "Releasing $(node -p "require('./package.json').version") as: $RELEASE_TYPE"
VERSION=$(node -p "require('./package.json').version")
gh release view "v$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 || \
gh release create "v$VERSION" --repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "$VERSION" \
--draft \
--notes "Edison Watch $VERSION"

- name: Package, sign, notarize & upload to GitHub Releases
# Tag = v${version} from package.json. Assets upload into the draft
# pre-created above; the next step publishes it (or leaves it a draft
# to publish manually in the UI when the publish input is false).
run: |
echo "Releasing $(node -p "require('./package.json').version")"
npx electron-builder --mac --publish always \
-c.mac.notarize=true \
-c.publish.channel=latest \
-c.publish.releaseType="$RELEASE_TYPE"
-c.publish.releaseType=draft

- name: Publish release
# Only when publish=true; otherwise the draft stays for manual publish
# in the UI (safer once Windows/Linux jobs are added - publish once all
# are up).
if: ${{ inputs.publish }}
run: |
VERSION=$(node -p "require('./package.json').version")
gh release edit "v$VERSION" --repo "$GITHUB_REPOSITORY" --draft=false --latest
12 changes: 9 additions & 3 deletions src/main/infra/setupConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,21 @@ if (DRY_RUN) console.log("[dry-run] Dry-run mode enabled - config files will not

// ── Debug environment switcher ───────────────────────────────────────

export const DEBUG_ENV_NAMES = ["demo", "release", "dev"] as const;
// "temp-local-stack" = Railway-hosted offline stack (local-stack-backend in the
// edison-watch demo project); URLs come from the shared TEMP_LOCAL_STACK_CONFIG.
export const DEBUG_ENV_NAMES = ["demo", "release", "dev", "temp-local-stack"] as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Selecting temp-local-stack can persist the new env without actually resolving its Railway URLs, because the locked @edison-watch/shared@0.1.0 does not contain that config. Updating the shared package/lockfile with the companion config keeps the new menu option from silently pointing at the previous environment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/infra/setupConfig.ts, line 27:

<comment>Selecting `temp-local-stack` can persist the new env without actually resolving its Railway URLs, because the locked `@edison-watch/shared@0.1.0` does not contain that config. Updating the shared package/lockfile with the companion config keeps the new menu option from silently pointing at the previous environment.</comment>

<file context>
@@ -22,15 +22,21 @@ if (DRY_RUN) console.log("[dry-run] Dry-run mode enabled - config files will not
-export const DEBUG_ENV_NAMES = ["demo", "release", "dev"] as const;
+// "temp-local-stack" = Railway-hosted offline stack (local-stack-backend in the
+// edison-watch demo project); URLs come from the shared TEMP_LOCAL_STACK_CONFIG.
+export const DEBUG_ENV_NAMES = ["demo", "release", "dev", "temp-local-stack"] as const;
 export type DebugEnvName = (typeof DEBUG_ENV_NAMES)[number];
 
</file context>

export type DebugEnvName = (typeof DEBUG_ENV_NAMES)[number];

function isDebugEnvName(v: string | undefined): v is DebugEnvName {
return (DEBUG_ENV_NAMES as readonly string[]).includes(v ?? "");
}

/** The environment this binary was compiled for (from VITE_DEPLOY_ENV at build time). */
export function getBuildDefaultEnv(): DebugEnvName | null {
const is = { get dev() { return !app.isPackaged; } };
if (is.dev) return "dev";
const v = import.meta.env.VITE_DEPLOY_ENV as string | undefined;
if (v === "demo" || v === "release" || v === "dev") return v;
if (isDebugEnvName(v)) return v;
return null;
}

Expand Down Expand Up @@ -60,7 +66,7 @@ export function getDebugEnvOverride(): DebugEnvName | null {
if (!existsSync(p)) return null;
const raw = readFileSync(p, "utf-8");
const data = JSON.parse(raw) as { env?: string };
if (data.env === "demo" || data.env === "release" || data.env === "dev") return data.env;
if (isDebugEnvName(data.env)) return data.env;
return null;
} catch {
return null;
Expand Down
7 changes: 6 additions & 1 deletion src/main/menus/appMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export function buildAppMenu(deps: AppMenuDeps): Menu {
const showDeveloperMenu = getBuildDefaultEnv() !== 'release'
const currentEnv = getDebugEnvOverride() ?? getBuildDefaultEnv()
const envSubmenu: MenuItemConstructorOptions[] = DEBUG_ENV_NAMES.map((name) => ({
label: name === 'dev' ? 'dev (localhost)' : name,
label:
name === 'dev'
? 'dev (localhost)'
: name === 'temp-local-stack'
? 'temp-local-stack (railway offline)'
: name,
type: 'radio' as const,
checked: currentEnv === name,
click: async () => {
Expand Down
Loading