Skip to content
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
6 changes: 6 additions & 0 deletions docs/api/ui/electron_shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ Behavior:
a compatible sidecar backend
- blocks until the Electron process exits

The Electron workspace allows pnpm to run install-time build scripts for
`electron` and `electron-winstaller` via `allowBuilds` in
`electron/pnpm-workspace.yaml`. Keep those approvals with the workspace
metadata so CI commands that use `pnpm --dir electron ...` see the same
dependency policy as local installs.

The Electron build also generates separate icon assets under `electron/build/`:

- `icon.png` for the app/window icon on all platforms
Expand Down
5 changes: 5 additions & 0 deletions docs/api/ui/labeling.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ For frontend development with hot reload:
taskclf ui --dev
```

The frontend workspace allows pnpm to run `esbuild`'s install-time build
script via `allowBuilds` in `src/taskclf/ui/frontend/pnpm-workspace.yaml`
so CI commands that use `pnpm --dir src/taskclf/ui/frontend ...` see the same
dependency policy as local installs.

For browser-based full-stack development with frontend HMR plus backend
auto-reload:

Expand Down
4 changes: 2 additions & 2 deletions electron/launcher_choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { compareVersions } from "./update_policy";

export interface LauncherReleaseEntry {
tag_name: string;
draft?: boolean;
prerelease?: boolean;
draft: boolean | undefined;
prerelease: boolean | undefined;
}

export function launcherVersionFromTag(tag: string): string | null {
Expand Down
48 changes: 25 additions & 23 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,27 @@ import { warmPillWindow } from "./shell_warm.js";
function readAppDisplayName(): string {
const pkgPath = path.join(__dirname, "..", "package.json");
const raw = fs.readFileSync(pkgPath, "utf-8");
const pkg = JSON.parse(raw) as { build?: { productName?: string } };
const pkg = JSON.parse(raw) as {
build: { productName: string | undefined } | undefined;
};
return pkg.build?.productName ?? "taskclf";
}

const APP_DISPLAY_NAME = readAppDisplayName();

type HostCommand = {
cmd: string;
mode?: string;
message?: string;
prompt?: {
mode: string | undefined;
message: string | undefined;
prompt: {
prev_app: string;
new_app: string;
block_start: string;
block_end: string;
duration_min: number;
suggested_label: string | null;
suggestion_text: string | null;
};
} | undefined;
};

const COMPACT_SIZE = { width: 150, height: 30 };
Expand Down Expand Up @@ -293,7 +295,7 @@ function launcherLogFilePath(): string {
function launcherLog(
message: string,
level: "info" | "error" = "info",
options?: { echoToConsole?: boolean },
options: { echoToConsole: boolean | undefined } | undefined = undefined,
): void {
const ts = new Date().toISOString();
const line = `[${ts}] [${level}] ${message}`;
Expand Down Expand Up @@ -364,7 +366,7 @@ function buildLauncherIssueUrl(title: string, detail: string): string {
return url;
}

function fatalDialogDetail(message: string, detail?: string): string {
function fatalDialogDetail(message: string, detail: string | undefined = undefined): string {
const parts: string[] = [message];
if (detail) {
parts.push(`Details:\n${detail}`);
Expand All @@ -379,7 +381,7 @@ function fatalDialogDetail(message: string, detail?: string): string {
async function showFatalLaunchError(
title: string,
message: string,
detail?: string,
detail: string | undefined = undefined,
): Promise<void> {
if (fatalLaunchErrorShown || isQuitting) {
return;
Expand Down Expand Up @@ -805,7 +807,7 @@ function transitionNotificationBody(prompt: NonNullable<HostCommand["prompt"]>):

async function notificationActionPost(
pathName: string,
body?: Record<string, unknown>,
body: Record<string, unknown> | undefined = undefined,
): Promise<{ ok: boolean; detail: string }> {
const response = await sidecarRequest(pathName, {
method: "POST",
Expand Down Expand Up @@ -1094,7 +1096,7 @@ async function waitForShell(url: string, timeoutMs = 30000): Promise<void> {

async function sidecarRequest(
pathName: string,
init?: RequestInit,
init: RequestInit | undefined = undefined,
): Promise<Response | null> {
try {
return await fetch(`http://127.0.0.1:${uiPort()}${pathName}`, init);
Expand Down Expand Up @@ -1170,10 +1172,10 @@ let updateCheckInProgress = false;
function payloadResolutionDetails(
resolution: PayloadResolution,
activeVersion: string | null,
options?: {
selectedVersion?: string | null;
note?: string | null;
},
options: {
selectedVersion: string | null | undefined;
note: string | null | undefined;
} | undefined = undefined,
): string {
const lines = [
`Launcher version: ${resolution.launcherManifest.launcher_version}`,
Expand Down Expand Up @@ -1213,9 +1215,9 @@ async function applyPayloadResolution(
async function applyPayloadResolutionAndRelaunch(
resolution: PayloadResolution,
heading: string,
options?: {
clearSelectedVersionBeforeRelaunch?: boolean;
},
options: {
clearSelectedVersionBeforeRelaunch: boolean | undefined;
} | undefined = undefined,
): Promise<void> {
await applyPayloadResolution(resolution, heading);
if (options?.clearSelectedVersionBeforeRelaunch) {
Expand All @@ -1225,7 +1227,7 @@ async function applyPayloadResolutionAndRelaunch(
app.quit();
}

async function showUpdateCheckFailureDialog(detail?: string): Promise<void> {
async function showUpdateCheckFailureDialog(detail: string | undefined = undefined): Promise<void> {
await dialog.showMessageBox({
type: "error",
title: "Update Check Failed",
Expand Down Expand Up @@ -2262,11 +2264,11 @@ function formatProgressLine(event: UpdateProgressEvent): string {
}
}

type ProgressWindowState = {
heading?: string;
detail?: string;
percent?: number | null;
};
type ProgressWindowState = Partial<{
heading: string | undefined;
detail: string | undefined;
percent: number | null | undefined;
}>;

function progressWindowPageHtml(heading: string, detail = ""): string {
const h = escapeHtmlAttr(heading);
Expand Down
10 changes: 5 additions & 5 deletions electron/node_http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import https from "node:https";
import { Readable } from "node:stream";

export interface NodeFetchInit extends RequestInit {
maxRedirects?: number;
maxRedirects: number | undefined;
}

const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
const NULL_BODY_STATUS_CODES = new Set([204, 205, 304]);

function abortError(reason?: unknown): Error {
function abortError(reason: unknown | undefined = undefined): Error {
if (reason instanceof Error) {
return reason;
}
Expand Down Expand Up @@ -71,7 +71,7 @@ async function nodeFetchRequest(
remainingRedirects: number,
): Promise<Response> {
if (request.signal.aborted) {
throw abortError((request.signal as AbortSignal & { reason?: unknown }).reason);
throw abortError((request.signal as AbortSignal & { reason: unknown | undefined }).reason);
}

const url = new URL(request.url);
Expand Down Expand Up @@ -170,7 +170,7 @@ async function nodeFetchRequest(
req.on("error", fail);

const onAbort = () => {
const error = abortError((request.signal as AbortSignal & { reason?: unknown }).reason);
const error = abortError((request.signal as AbortSignal & { reason: unknown | undefined }).reason);
req.destroy(error);
fail(error);
};
Expand All @@ -191,7 +191,7 @@ async function nodeFetchRequest(

export async function nodeFetch(
input: string | URL | Request,
init?: NodeFetchInit,
init: NodeFetchInit | undefined = undefined,
): Promise<Response> {
const request = input instanceof Request && init === undefined
? input
Expand Down
5 changes: 0 additions & 5 deletions electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,5 @@
},
"dependencies": {
"adm-zip": "0.5.16"
},
"pnpm": {
"onlyBuiltDependencies": [
"electron"
]
}
}
3 changes: 3 additions & 0 deletions electron/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
allowBuilds:
electron: true
electron-winstaller: true
22 changes: 11 additions & 11 deletions electron/port_conflict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ export type SpawnSyncResult = {
export type SpawnSyncFn = (
command: string,
args: readonly string[],
options?: SpawnSyncOptionsWithStringEncoding,
options: SpawnSyncOptionsWithStringEncoding | undefined,
) => SpawnSyncResult;

export function defaultSpawnSync(
command: string,
args: readonly string[],
options?: SpawnSyncOptionsWithStringEncoding,
options: SpawnSyncOptionsWithStringEncoding | undefined = undefined,
): SpawnSyncResult {
const r = spawnSync(command, args as string[], {
...options,
Expand Down Expand Up @@ -111,13 +111,13 @@ function getListeningPidUnix(
platform: NodeJS.Platform,
spawnSyncFn: SpawnSyncFn,
): number | null {
const lsof = spawnSyncFn("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]);
const lsof = spawnSyncFn("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], undefined);
const pidFromLsof = parseFirstPidFromLsofT(lsof.stdout);
if (pidFromLsof !== null) {
return pidFromLsof;
}
if (platform === "linux") {
const ss = spawnSyncFn("ss", ["-lntp", `sport = :${port}`]);
const ss = spawnSyncFn("ss", ["-lntp", `sport = :${port}`], undefined);
if (ss.status === 0 && ss.stdout) {
return parsePidFromSsOutput(ss.stdout);
}
Expand All @@ -128,7 +128,7 @@ function getListeningPidUnix(
function getListeningPidWindows(port: number, spawnSyncFn: SpawnSyncFn): number | null {
const script =
`(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty OwningProcess)`;
const ps = spawnSyncFn("powershell.exe", ["-NoProfile", "-Command", script]);
const ps = spawnSyncFn("powershell.exe", ["-NoProfile", "-Command", script], undefined);
if (ps.status !== 0 && ps.status !== null) {
return null;
}
Expand All @@ -148,14 +148,14 @@ function getProcessCommandLine(
if (platform === "win32") {
const script =
`(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`;
const ps = spawnSyncFn("powershell.exe", ["-NoProfile", "-Command", script]);
const ps = spawnSyncFn("powershell.exe", ["-NoProfile", "-Command", script], undefined);
if (ps.status === 0 && ps.stdout.trim().length > 0) {
return ps.stdout.trim();
}
return "";
}
const field = platform === "linux" ? "args=" : "command=";
const out = spawnSyncFn("ps", ["-p", String(pid), "-ww", "-o", field]);
const out = spawnSyncFn("ps", ["-p", String(pid), "-ww", "-o", field], undefined);
if (out.status === 0) {
return out.stdout.trim();
}
Expand Down Expand Up @@ -207,9 +207,9 @@ export async function killPidAndWaitForPortFree(
timeoutMs = DEFAULT_KILL_WAIT_MS,
): Promise<boolean> {
if (platform === "win32") {
spawnSyncFn("taskkill.exe", ["/PID", String(pid), "/T"]);
spawnSyncFn("taskkill.exe", ["/PID", String(pid), "/T"], undefined);
} else {
spawnSyncFn("kill", ["-TERM", String(pid)]);
spawnSyncFn("kill", ["-TERM", String(pid)], undefined);
}

const deadline = Date.now() + timeoutMs;
Expand All @@ -221,9 +221,9 @@ export async function killPidAndWaitForPortFree(
}

if (platform === "win32") {
spawnSyncFn("taskkill.exe", ["/PID", String(pid), "/T", "/F"]);
spawnSyncFn("taskkill.exe", ["/PID", String(pid), "/T", "/F"], undefined);
} else {
spawnSyncFn("kill", ["-KILL", String(pid)]);
spawnSyncFn("kill", ["-KILL", String(pid)], undefined);
}

const hardDeadline = Date.now() + 3000;
Expand Down
2 changes: 1 addition & 1 deletion electron/update_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function selectLatestCompatiblePayloadVersion(

export function manifestUrlForLauncherVersion(
version: string,
overrideUrl?: string,
overrideUrl: string | undefined,
): string {
if (overrideUrl && overrideUrl.length > 0) {
return overrideUrl;
Expand Down
Loading