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
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name: Fs Build
name: Core Build

on:
workflow_dispatch: {}
push:
paths:
- .github/workflows/fs_build.yaml
- packages/fs/**
- .github/workflows/core_build.yaml
- packages/core/**
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
pull_request:
paths:
- .github/workflows/fs_build.yaml
- packages/fs/**
- .github/workflows/core_build.yaml
- packages/core/**
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
Expand All @@ -22,7 +22,7 @@ permissions:

jobs:
build:
name: Build Fs
name: Build Core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -35,5 +35,5 @@ jobs:
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @elsewise/fs check
- run: pnpm --filter @elsewise/fs typecheck
- run: pnpm --filter @elsewise/core check
- run: pnpm --filter @elsewise/core typecheck
4 changes: 2 additions & 2 deletions .github/workflows/desktop_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- apps/web/**
- packages/bridge/**
- packages/components/**
- packages/fs/**
- packages/core/**
- packages/plugin/**
- package.json
- pnpm-lock.yaml
Expand All @@ -21,7 +21,7 @@ on:
- apps/web/**
- packages/bridge/**
- packages/components/**
- packages/fs/**
- packages/core/**
- packages/plugin/**
- package.json
- pnpm-lock.yaml
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/electron-builder.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
appId: com.duobase.elsewise
appId: io.duobase.elsewise
productName: Elsewise
directories:
output: dist
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@biomejs/biome": "^2.5.14",
"@electron/fuses": "^2.1.3",
"@elsewise/bridge": "workspace:*",
"@elsewise/fs": "workspace:*",
"@elsewise/core": "workspace:*",
"@elsewise/web": "workspace:*",
"@types/node": "^26.4.1",
"electron": "44.5.1",
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/scripts/dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ console.log(`[dev] waiting for renderer at ${RENDERER_URL} ...`);
await waitForUrl(RENDERER_URL);
startElectron();

// Rebuild + restart Electron on main/preload changes, and on changes to the bridge and fs bundled into them
// Rebuild + restart Electron on main/preload changes, and on changes to the bridge and core bundled into them
// (debounced).
let timer;
for (const folder of ['src', '../../packages/bridge/src', '../../packages/fs/src']) {
for (const folder of ['src', '../../packages/bridge/src', '../../packages/core/src']) {
watch(folder, { recursive: true }, () => {
clearTimeout(timer);
timer = setTimeout(async () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';
import type { Brightness } from '@elsewise/bridge';
import { dataLocalDir } from '@elsewise/fs';
import { dataLocalDir } from '@elsewise/core';
import { app, BrowserWindow, nativeTheme, protocol, shell } from 'electron';
import { installMenu } from './menu';
import { handlePlugins } from './plugins';
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import fs from 'node:fs';
import path from 'node:path';
import { dataLocalDir } from '@elsewise/fs';
import { dataLocalDir } from '@elsewise/core';
import { ipcMain } from 'electron';
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest';
import { handlePlugins } from './plugins';

vi.mock('electron', () => ({ ipcMain: { handle: vi.fn() } }));
// The real `writeJsonSync`, but in a temporary folder rather than the user's data folder.
vi.mock('@elsewise/fs', async (importOriginal) => {
vi.mock('@elsewise/core', async (importOriginal) => {
const { mkdtempSync } = await import('node:fs');
const { tmpdir } = await import('node:os');
const { join } = await import('node:path');
const data = mkdtempSync(join(tmpdir(), 'elsewise-'));
return { ...(await importOriginal<typeof import('@elsewise/fs')>()), dataLocalDir: () => data };
return { ...(await importOriginal<typeof import('@elsewise/core')>()), dataLocalDir: () => data };
});

const FILE = path.join(dataLocalDir(), 'plugins.json');
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { InstalledPlugin } from '@elsewise/bridge';
import { dataLocalDir, writeJsonSync } from '@elsewise/fs';
import { dataLocalDir, writeJsonSync } from '@elsewise/core';
import { ipcMain } from 'electron';

const FILE = path.join(dataLocalDir(), 'plugins.json');
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/protocol.electron.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import http from 'node:http';
import type { AddressInfo } from 'node:net';
import os from 'node:os';
import path from 'node:path';
import { dataLocalDir } from '@elsewise/fs';
import { dataLocalDir } from '@elsewise/core';
import electron from 'electron';
import { _electron, type ElectronApplication } from 'playwright';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vi.mock('electron', () => ({
},
session: { defaultSession: { protocol: { handle: vi.fn() } } },
}));
vi.mock('@elsewise/fs', () => ({ dataLocalDir: () => DATA }));
vi.mock('@elsewise/core', () => ({ dataLocalDir: () => DATA }));

beforeEach(() => {
files.clear();
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { dataLocalDir } from '@elsewise/fs';
import { dataLocalDir } from '@elsewise/core';
import { net, session } from 'electron';

const MIME_TYPES: Record<string, string> = {
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/settings.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import fs from 'node:fs';
import path from 'node:path';
import { DEFAULT_SETTINGS, type Settings } from '@elsewise/bridge';
import { configLocalDir, dataLocalDir } from '@elsewise/fs';
import { configLocalDir, dataLocalDir } from '@elsewise/core';
import { ipcMain } from 'electron';
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest';
import { handleSettings } from './settings';

vi.mock('electron', () => ({ ipcMain: { handle: vi.fn() } }));
// The real `writeJsonSync`, but in a temporary folder rather than the user's config and data folders.
vi.mock('@elsewise/fs', async (importOriginal) => {
vi.mock('@elsewise/core', async (importOriginal) => {
const { mkdtempSync } = await import('node:fs');
const { tmpdir } = await import('node:os');
const { join } = await import('node:path');
const home = mkdtempSync(join(tmpdir(), 'elsewise-'));
return {
...(await importOriginal<typeof import('@elsewise/fs')>()),
...(await importOriginal<typeof import('@elsewise/core')>()),
configLocalDir: () => join(home, 'config'),
dataLocalDir: () => join(home, 'data'),
};
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/settings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { DEFAULT_SETTINGS, PluginId, Settings } from '@elsewise/bridge';
import { configLocalDir, dataLocalDir, writeJsonSync } from '@elsewise/fs';
import { configLocalDir, dataLocalDir, writeJsonSync } from '@elsewise/core';
import { ipcMain } from 'electron';

/**
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/main/window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ vi.mock('electron', () => ({
},
},
}));
vi.mock('@elsewise/fs', async (original) => ({
...(await original<typeof import('@elsewise/fs')>()),
vi.mock('@elsewise/core', async (original) => ({
...(await original<typeof import('@elsewise/core')>()),
dataLocalDir: () => state.dir,
}));

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/window.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { dataLocalDir, writeJsonSync } from '@elsewise/fs';
import { dataLocalDir, writeJsonSync } from '@elsewise/core';
import { type BrowserWindow, type Rectangle, screen } from 'electron';

interface Window extends Rectangle {
Expand Down
4 changes: 2 additions & 2 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Shared Elsewise libraries.
|-----------------------------------------|-----------------|-----------------------------------------------------|
| [`@elsewise/bridge`](./bridge) | `bridge/` | The contract between the UI and its embedder |
| [`@elsewise/components`](./components) | `components/` | Primitive UI components and design system |
| [`@elsewise/fs`](./fs) | `fs/` | Where Elsewise keeps its files on disk |
| [`@elsewise/core`](./core) | `core/` | Node-side code shared by every Elsewise process |
| [`@elsewise/plugin`](./plugin) | `plugin/` | The contract between Elsewise and its plugins |
| [`@elsewise/transport`](./transport/ts) | `transport/ts/` | The transport protocol implementation in TypeScript |

Expand All @@ -17,7 +17,7 @@ Run pnpm from the repo root, filtered to the package:
```bash
pnpm --filter @elsewise/bridge typecheck # also: check
pnpm --filter @elsewise/components test # also: typecheck, check, test:browser, test:visual
pnpm --filter @elsewise/fs typecheck # also: check
pnpm --filter @elsewise/core typecheck # also: check
pnpm --filter @elsewise/plugin typecheck # also: check
pnpm --filter @elsewise/transport test # also: typecheck, check, generate
```
8 changes: 4 additions & 4 deletions packages/fs/AGENTS.md → packages/core/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Fs
# Core

Where Elsewise keeps its files on disk, and how it writes them. Shared by every Node process that touches those files,
today the desktop app and the daemon, so they agree on one location.
Node-side code shared by every Elsewise process, today the desktop app and the daemon. It currently holds where
Elsewise keeps its files on disk and how it writes them, so both agree on one location.

Run pnpm from the **repo root**: `pnpm --filter @elsewise/fs <script>`.
Run pnpm from the **repo root**: `pnpm --filter @elsewise/core <script>`.

This is a **source package**: `exports` points straight at `src/index.ts` and there is no build step.

Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions packages/fs/package.json → packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@elsewise/fs",
"name": "@elsewise/core",
"private": true,
"type": "module",
"devEngines": {
Expand All @@ -8,7 +8,7 @@
"onFail": "error"
}
},
"description": "Where Elsewise keeps its files on disk, and how it writes them.",
"description": "Node-side code shared by every Elsewise process.",
"exports": {
".": "./src/index.ts"
},
Expand Down
15 changes: 2 additions & 13 deletions packages/fs/src/index.ts → packages/core/src/dirs.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

const ORGANIZATION = 'duobase';
const APPLICATION = 'elsewise';

/**
* Atomically writes `value` as pretty-printed JSON to `target`, creating its parent directories if they do not exist.
*/
export function writeJsonSync(target: string, value: unknown): void {
fs.mkdirSync(path.dirname(target), { recursive: true });
const temporary = `${target}.tmp`;
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
fs.renameSync(temporary, target);
}

/**
* Returns the directory where the user-facing settings and configuration live.
*/
Expand All @@ -23,7 +12,7 @@ export function configLocalDir(): string {
case 'win32':
return path.join(localAppData(), ORGANIZATION, APPLICATION, 'config');
case 'darwin':
return path.join(os.homedir(), 'Library', 'Application Support', `com.${ORGANIZATION}.${APPLICATION}`);
return path.join(os.homedir(), 'Library', 'Application Support', `io.${ORGANIZATION}.${APPLICATION}`, 'config');
Comment thread
kawaijoe marked this conversation as resolved.
default:
return path.join(xdg('XDG_CONFIG_HOME', '.config'), APPLICATION);
}
Expand All @@ -37,7 +26,7 @@ export function dataLocalDir(): string {
case 'win32':
return path.join(localAppData(), ORGANIZATION, APPLICATION, 'data');
case 'darwin':
return path.join(os.homedir(), 'Library', 'Application Support', `com.${ORGANIZATION}.${APPLICATION}`);
return path.join(os.homedir(), 'Library', 'Application Support', `io.${ORGANIZATION}.${APPLICATION}`, 'data');
default:
return path.join(xdg('XDG_DATA_HOME', '.local/share'), APPLICATION);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dirs';
export * from './json';
12 changes: 12 additions & 0 deletions packages/core/src/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import fs from 'node:fs';
import path from 'node:path';

/**
* Atomically writes `value` as pretty-printed JSON to `target`, creating its parent directories if they do not exist.
*/
export function writeJsonSync(target: string, value: unknown): void {
fs.mkdirSync(path.dirname(target), { recursive: true });
const temporary = `${target}.tmp`;
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
fs.renameSync(temporary, target);
}
File renamed without changes.
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading