fix(dist): ship a self-contained bundle so it installs from the registry
The unscoped package lives on the Gitea npm registry but its deps span two
registries (`@punktfunk/host` on Gitea, `effect`/`undici` on npmjs). Installing
by name with `--registry <gitea>` sets the default registry for the whole tree,
so `effect` 404s (Gitea doesn't proxy npm), and an unscoped name can't be
scope-mapped. Fix: bundle the backend into ONE self-contained `dist/index.js`
(+ `dist/cli.js`) via `bun build --target=bun` with `@punktfunk/host` + `effect`
inlined — mirroring the scripting runner. The published package now has zero
runtime dependencies and installs with a single `--registry` flag.
- build → `bun build src/{index,cli}.ts --target=bun --outdir dist --external undici`
- @punktfunk/host + effect → devDependencies (bundled, not resolved at install)
- resolveUiDir(): walk to the SPA dir so staticDir works in the bundled
(dist/index.js) and unbundled (dist/server/index.js) layouts
- version 0.1.0 → 0.1.1
Verified: the bundle loads + scans/detects/previews with NO node_modules present.
This commit is contained in:
@@ -4,13 +4,11 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "punktfunk-plugin-rom-manager",
|
"name": "punktfunk-plugin-rom-manager",
|
||||||
"dependencies": {
|
|
||||||
"@punktfunk/host": "^0.1.0",
|
|
||||||
"effect": "^4.0.0-beta.98",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.5.2",
|
"@biomejs/biome": "^2.5.2",
|
||||||
|
"@punktfunk/host": "^0.1.0",
|
||||||
"@types/bun": "^1.3.0",
|
"@types/bun": "^1.3.0",
|
||||||
|
"effect": "^4.0.0-beta.98",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-7
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "punktfunk-plugin-rom-manager",
|
"name": "punktfunk-plugin-rom-manager",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "punktfunk plugin: scans ROM directories, maps them to emulators, and reconciles them into the host game library as a provider — with a console-hosted web UI.",
|
"description": "punktfunk plugin: scans ROM directories, maps them to emulators, and reconciles them into the host game library as a provider — with a console-hosted web UI.",
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
],
|
],
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"punktfunk-plugin-rom-manager": "./dist/cli.js"
|
"punktfunk-plugin-rom-manager": "./dist/cli.js"
|
||||||
},
|
},
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"build": "tsc -p tsconfig.build.json",
|
"build": "rm -rf dist && bun build src/index.ts src/cli.ts --target=bun --outdir dist --external undici",
|
||||||
"build:ui": "cd ui && bun install --silent && bun run build",
|
"build:ui": "cd ui && bun install --silent && bun run build",
|
||||||
"build:all": "bun run build && bun run build:ui",
|
"build:all": "bun run build && bun run build:ui",
|
||||||
"sync": "bun src/cli.ts sync",
|
"sync": "bun src/cli.ts sync",
|
||||||
@@ -41,13 +40,12 @@
|
|||||||
"detect": "bun src/cli.ts detect",
|
"detect": "bun src/cli.ts detect",
|
||||||
"prepublishOnly": "bun run build:all"
|
"prepublishOnly": "bun run build:all"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"@punktfunk/host": "^0.1.0",
|
|
||||||
"effect": "^4.0.0-beta.98"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.5.2",
|
"@biomejs/biome": "^2.5.2",
|
||||||
|
"@punktfunk/host": "^0.1.0",
|
||||||
"@types/bun": "^1.3.0",
|
"@types/bun": "^1.3.0",
|
||||||
|
"effect": "^4.0.0-beta.98",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// Where the plugin's own files live. The host config dir resolution mirrors the SDK's `configDir`
|
// Where the plugin's own files live. The host config dir resolution mirrors the SDK's `configDir`
|
||||||
// (`@punktfunk/host` does not re-export it) so we always land in the same place the host and runner
|
// (`@punktfunk/host` does not re-export it) so we always land in the same place the host and runner
|
||||||
// use; the plugin owns a `rom-manager/` subtree under it (design §9), created 0700.
|
// use; the plugin owns a `rom-manager/` subtree under it (design §9), created 0700.
|
||||||
|
import * as fs from "node:fs";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
/** The host's config dir — the same resolution the host and SDK use. */
|
/** The host's config dir — the same resolution the host and SDK use. */
|
||||||
export const hostConfigDir = (): string => {
|
export const hostConfigDir = (): string => {
|
||||||
@@ -26,3 +28,28 @@ export const configPath = (): string => path.join(pluginDir(), "config.json");
|
|||||||
|
|
||||||
/** `<config_dir>/rom-manager/cache.json` — disposable derived state (art verdicts, detection, fingerprint). */
|
/** `<config_dir>/rom-manager/cache.json` — disposable derived state (art verdicts, detection, fingerprint). */
|
||||||
export const cachePath = (): string => path.join(pluginDir(), "cache.json");
|
export const cachePath = (): string => path.join(pluginDir(), "cache.json");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate the built SPA directory (`dist/ui`) at runtime. The package ships as ONE self-contained bundle
|
||||||
|
* (`dist/index.js`) so it installs from the Gitea registry with no external deps — which means
|
||||||
|
* `import.meta.url` can be `dist/index.js` (bundled) or `dist/server/index.js` (unbundled dev). We walk
|
||||||
|
* up from the calling module looking for a `ui/index.html`, so both layouts resolve. `fromUrl` is the
|
||||||
|
* caller's `import.meta.url`.
|
||||||
|
*/
|
||||||
|
export const resolveUiDir = (fromUrl: string): string => {
|
||||||
|
let dir = path.dirname(fileURLToPath(fromUrl));
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
for (const cand of [path.join(dir, "ui"), path.join(dir, "dist", "ui")]) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(path.join(cand, "index.html"))) return cand;
|
||||||
|
} catch {
|
||||||
|
// keep walking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = path.dirname(dir);
|
||||||
|
if (parent === dir) break;
|
||||||
|
dir = parent;
|
||||||
|
}
|
||||||
|
// Fallback to the bundled layout (dist/index.js → dist/ui) even if unbuilt.
|
||||||
|
return path.join(path.dirname(fileURLToPath(fromUrl)), "ui");
|
||||||
|
};
|
||||||
|
|||||||
+3
-5
@@ -11,12 +11,10 @@ import {
|
|||||||
} from "@punktfunk/host";
|
} from "@punktfunk/host";
|
||||||
import { PLUGIN_NAME, UI_ICON, UI_TITLE } from "../const.js";
|
import { PLUGIN_NAME, UI_ICON, UI_TITLE } from "../const.js";
|
||||||
import type { Engine } from "../engine/index.js";
|
import type { Engine } from "../engine/index.js";
|
||||||
|
import { resolveUiDir } from "../paths.js";
|
||||||
import { makeRouter } from "./router.js";
|
import { makeRouter } from "./router.js";
|
||||||
|
|
||||||
/**
|
/** Serve the UI through the console. The SPA dir resolves whether we run bundled or from source. */
|
||||||
* Serve the UI through the console. `../../dist/ui` resolves to `<repo>/dist/ui` whether this module
|
|
||||||
* runs from source (`src/server/index.ts`) or built (`dist/server/index.js`).
|
|
||||||
*/
|
|
||||||
export const serveConsoleUi = (
|
export const serveConsoleUi = (
|
||||||
pf: Punktfunk,
|
pf: Punktfunk,
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
@@ -27,6 +25,6 @@ export const serveConsoleUi = (
|
|||||||
title: UI_TITLE,
|
title: UI_TITLE,
|
||||||
icon: UI_ICON,
|
icon: UI_ICON,
|
||||||
version: opts?.version,
|
version: opts?.version,
|
||||||
staticDir: new URL("../../dist/ui", import.meta.url),
|
staticDir: resolveUiDir(import.meta.url),
|
||||||
fetch: makeRouter(engine),
|
fetch: makeRouter(engine),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
// non-loopback bind REQUIRES a password (a UI that can rewrite launch templates is host-user code).
|
// non-loopback bind REQUIRES a password (a UI that can rewrite launch templates is host-user code).
|
||||||
|
|
||||||
import * as nodePath from "node:path";
|
import * as nodePath from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import type { Config } from "../config.js";
|
import type { Config } from "../config.js";
|
||||||
import type { Engine } from "../engine/index.js";
|
import type { Engine } from "../engine/index.js";
|
||||||
import { log } from "../log.js";
|
import { log } from "../log.js";
|
||||||
|
import { resolveUiDir } from "../paths.js";
|
||||||
import { verifyPassword } from "./password.js";
|
import { verifyPassword } from "./password.js";
|
||||||
import { makeRouter } from "./router.js";
|
import { makeRouter } from "./router.js";
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export const serveStandaloneUi = (
|
|||||||
const authRequired = Boolean(passwordHash);
|
const authRequired = Boolean(passwordHash);
|
||||||
// A per-boot session token — any client presenting it in the cookie is authed until the plugin restarts.
|
// A per-boot session token — any client presenting it in the cookie is authed until the plugin restarts.
|
||||||
const sessionToken = crypto.randomUUID().replace(/-/g, "");
|
const sessionToken = crypto.randomUUID().replace(/-/g, "");
|
||||||
const root = fileURLToPath(new URL("../../dist/ui", import.meta.url));
|
const root = resolveUiDir(import.meta.url);
|
||||||
const router = makeRouter(engine);
|
const router = makeRouter(engine);
|
||||||
|
|
||||||
const authed = (req: Request) =>
|
const authed = (req: Request) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user