mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-11-07 22:10:30 +00:00
Add --bitgate-* theme tokens and a build-injected VERSION.
Widget styles now prefer --bitgate-* custom properties, falling back to the deprecated --gov-* names and then the existing defaults so current pages and overrides keep working. Browser bundles and ESM entry points expose VERSION (and window.bitgate.version) from package.json. Document allowlist on the widget policy preset list. Sync the lockfile so npm ci sees @bitgate/standalone. Co-authored-by: thePR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
90d4290989
commit
9e6f6c659e
24 files changed
+369
-22
No files matched your search
@@ -87,6 +87,11 @@ allowlisted publishers are shown — the symmetric twin of a denylist, for
|
|||||||
invite-only or curated surfaces). Every number in them is a starting point —
|
invite-only or curated surfaces). Every number in them is a starting point —
|
||||||
see the [integration guide](docs/integration-guide.md) to write your own.
|
see the [integration guide](docs/integration-guide.md) to write your own.
|
||||||
|
|
||||||
|
The package version is exported as `VERSION` and, in browser bundles, as
|
||||||
|
`window.bitgate.version`. Both are injected from `package.json` at build time.
|
||||||
|
Widget theme tokens use `--bitgate-*` custom properties; the older `--gov-*`
|
||||||
|
names remain as fallbacks (see [`packages/widget/README.md`](packages/widget/README.md)).
|
||||||
|
|
||||||
## Quick start — JavaScript
|
## Quick start — JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
+3
-1
@@ -175,7 +175,9 @@ unchanged.
|
|||||||
authority or leaking private application context.
|
authority or leaking private application context.
|
||||||
- [ ] Decide whether standalone output is generated from the canonical engine
|
- [ ] Decide whether standalone output is generated from the canonical engine
|
||||||
directly or from a separately maintained browser entry point.
|
directly or from a separately maintained browser entry point.
|
||||||
- [ ] Decide how browser builds expose version and integrity metadata.
|
- [x] Decide how browser builds expose version and integrity metadata.
|
||||||
|
`VERSION` + `window.bitgate.version` equal `package.json`, injected at
|
||||||
|
build time. Integrity manifests remain open (distribution / D7).
|
||||||
|
|
||||||
## BitBlocks App Kit compatibility layer
|
## BitBlocks App Kit compatibility layer
|
||||||
|
|
||||||
|
|||||||
Generated
+16
@@ -54,6 +54,10 @@
|
|||||||
"resolved": "packages/site",
|
"resolved": "packages/site",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@bitgate/standalone": {
|
||||||
|
"resolved": "packages/standalone",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@bitgate/testing": {
|
"node_modules/@bitgate/testing": {
|
||||||
"resolved": "packages/testing",
|
"resolved": "packages/testing",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -1837,6 +1841,18 @@
|
|||||||
"name": "@bitgate/site",
|
"name": "@bitgate/site",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
},
|
},
|
||||||
|
"packages/standalone": {
|
||||||
|
"name": "@bitgate/standalone",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"@bitgate/core": "*",
|
||||||
|
"@bitgate/nostr": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.28.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/testing": {
|
"packages/testing": {
|
||||||
"name": "@bitgate/testing",
|
"name": "@bitgate/testing",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export {
|
|||||||
aggregateMutes,
|
aggregateMutes,
|
||||||
} from "./evaluator.js";
|
} from "./evaluator.js";
|
||||||
|
|
||||||
|
export { VERSION } from "./version.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./identifiers.js').GovernanceTarget} GovernanceTarget
|
* @typedef {import('./identifiers.js').GovernanceTarget} GovernanceTarget
|
||||||
* @typedef {import('./identifiers.js').UserTarget} UserTarget
|
* @typedef {import('./identifiers.js').UserTarget} UserTarget
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* BitGate library version.
|
||||||
|
*
|
||||||
|
* Keep this string in lockstep with the root and `@bitgate/core` `package.json`
|
||||||
|
* versions. `packages/core/test/version.test.js` asserts they match. Browser
|
||||||
|
* bundles replace this module at build time with the same value read from
|
||||||
|
* `package.json`, so the shipped file cannot drift.
|
||||||
|
*/
|
||||||
|
export const VERSION = "0.1.0";
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { VERSION } from "../src/index.js";
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootPkg = JSON.parse(readFileSync(join(HERE, "..", "..", "..", "package.json"), "utf8"));
|
||||||
|
const corePkg = JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8"));
|
||||||
|
|
||||||
|
describe("VERSION", () => {
|
||||||
|
it("matches package.json (root and @bitgate/core)", () => {
|
||||||
|
expect(VERSION).toBe(rootPkg.version);
|
||||||
|
expect(VERSION).toBe(corePkg.version);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -181,6 +181,7 @@ provider.runtime.policies.setLocalPolicy(policy);</code></pre>
|
|||||||
<tr><td><code>social</code></td><td>feed, profile, playback</td><td>Timelines and posts. Discovery declines to hard-hide.</td></tr>
|
<tr><td><code>social</code></td><td>feed, profile, playback</td><td>Timelines and posts. Discovery declines to hard-hide.</td></tr>
|
||||||
<tr><td><code>commerce</code></td><td>browse, detail, checkout, seller-dashboard</td><td>Marketplaces. Checkout decides transactions only.</td></tr>
|
<tr><td><code>commerce</code></td><td>browse, detail, checkout, seller-dashboard</td><td>Marketplaces. Checkout decides transactions only.</td></tr>
|
||||||
<tr><td><code>admin-only</code></td><td>default</td><td>Operator deny lists, with no trust-graph signals at all.</td></tr>
|
<tr><td><code>admin-only</code></td><td>default</td><td>Operator deny lists, with no trust-graph signals at all.</td></tr>
|
||||||
|
<tr><td><code>allowlist</code></td><td>default</td><td>Only allowlisted publishers are shown; unlisted ones are hidden. Symmetric twin of a denylist, for invite-only or curated surfaces.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { build, context } from "esbuild";
|
|||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import { bitgateVersionPlugin } from "../../scripts/esbuild-bitgate-version.mjs";
|
||||||
|
|
||||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
/** @type {import("esbuild").BuildOptions} */
|
/** @type {import("esbuild").BuildOptions} */
|
||||||
@@ -31,6 +33,7 @@ const options = {
|
|||||||
banner: {
|
banner: {
|
||||||
js: "/*! BitGate (headless standalone) — GPL-3.0-or-later — https://github.com/PR0M3TH3AN/Nostr-Governance */",
|
js: "/*! BitGate (headless standalone) — GPL-3.0-or-later — https://github.com/PR0M3TH3AN/Nostr-Governance */",
|
||||||
},
|
},
|
||||||
|
plugins: [bitgateVersionPlugin()],
|
||||||
};
|
};
|
||||||
|
|
||||||
const watch = process.argv.includes("--watch");
|
const watch = process.argv.includes("--watch");
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { VERSION } from "@bitgate/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish `window.bitgate.version` for script-tag integrators.
|
||||||
|
*
|
||||||
|
* Additive: creates `window.bitgate` when missing, otherwise writes `version`
|
||||||
|
* onto the existing object so a host-provided namespace is not replaced.
|
||||||
|
* No-op in Node (no `window`).
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>} [target] - Defaults to `globalThis.window`
|
||||||
|
* @returns {Record<string, any>|null} The namespace that received `version`
|
||||||
|
*/
|
||||||
|
export function exposeBitGateVersion(target) {
|
||||||
|
const root =
|
||||||
|
target ??
|
||||||
|
(typeof globalThis.window !== "undefined"
|
||||||
|
? /** @type {Record<string, any>} */ (globalThis.window)
|
||||||
|
: null);
|
||||||
|
if (!root || typeof root !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = root.bitgate;
|
||||||
|
const ns =
|
||||||
|
existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
||||||
|
ns.version = VERSION;
|
||||||
|
root.bitgate = ns;
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
@@ -11,5 +11,10 @@
|
|||||||
// Both re-exported packages are dependency-free (@bitgate/verify, the only
|
// Both re-exported packages are dependency-free (@bitgate/verify, the only
|
||||||
// package with third-party deps, is intentionally not pulled in), so the bundle
|
// package with third-party deps, is intentionally not pulled in), so the bundle
|
||||||
// carries no external code.
|
// carries no external code.
|
||||||
|
import { exposeBitGateVersion } from "./expose-version.js";
|
||||||
|
|
||||||
export * from "@bitgate/core";
|
export * from "@bitgate/core";
|
||||||
export * from "@bitgate/nostr";
|
export * from "@bitgate/nostr";
|
||||||
|
export { exposeBitGateVersion };
|
||||||
|
|
||||||
|
exposeBitGateVersion();
|
||||||
@@ -2,10 +2,16 @@
|
|||||||
// engine (core) + label codec (nostr) — so a no-build site gets everything from
|
// engine (core) + label codec (nostr) — so a no-build site gets everything from
|
||||||
// the single bundled file. Tests the entry, not the built artifact (dist is
|
// the single bundled file. Tests the entry, not the built artifact (dist is
|
||||||
// gitignored); the build itself is exercised by `npm run build:standalone`.
|
// gitignored); the build itself is exercised by `npm run build:standalone`.
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import * as bitgate from "../src/index.js";
|
import * as bitgate from "../src/index.js";
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootPkg = JSON.parse(readFileSync(join(HERE, "..", "..", "..", "package.json"), "utf8"));
|
||||||
|
|
||||||
describe("standalone entry surface", () => {
|
describe("standalone entry surface", () => {
|
||||||
it("re-exports the core engine and the Nostr label codec together", () => {
|
it("re-exports the core engine and the Nostr label codec together", () => {
|
||||||
for (const name of [
|
for (const name of [
|
||||||
@@ -17,6 +23,7 @@ describe("standalone entry surface", () => {
|
|||||||
"createPolicyDefinition",
|
"createPolicyDefinition",
|
||||||
"createEventTarget",
|
"createEventTarget",
|
||||||
"ALLOWLIST_POLICY",
|
"ALLOWLIST_POLICY",
|
||||||
|
"VERSION",
|
||||||
// nostr label codec
|
// nostr label codec
|
||||||
"decodeLabels",
|
"decodeLabels",
|
||||||
"encodeLabel",
|
"encodeLabel",
|
||||||
@@ -67,4 +74,15 @@ describe("standalone entry surface", () => {
|
|||||||
|
|
||||||
expect(decision.transaction?.effect).toBe("deny");
|
expect(decision.transaction?.effect).toBe("deny");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exports VERSION matching package.json", () => {
|
||||||
|
expect(bitgate.VERSION).toBe(rootPkg.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("publishes version onto a window-like global without replacing other keys", () => {
|
||||||
|
const target = { bitgate: { extra: true } };
|
||||||
|
bitgate.exposeBitGateVersion(target);
|
||||||
|
expect(target.bitgate.version).toBe(bitgate.VERSION);
|
||||||
|
expect(target.bitgate.extra).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -34,10 +34,14 @@ both with every descendant. Nothing else to wire.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `relays` | Comma-separated relay URLs (required) |
|
| `relays` | Comma-separated relay URLs (required) |
|
||||||
| `root` | Root administrator pubkey, from your deployment config |
|
| `root` | Root administrator pubkey, from your deployment config |
|
||||||
| `policy` | `social`, `commerce`, or `admin-only` (default) |
|
| `policy` | `social`, `commerce`, `admin-only` (default), or `allowlist` |
|
||||||
| `viewer` | Viewer pubkey, if known at render time |
|
| `viewer` | Viewer pubkey, if known at render time |
|
||||||
| `application` / `namespace` | Storage and event namespacing |
|
| `application` / `namespace` | Storage and event namespacing |
|
||||||
|
|
||||||
|
`allowlist` is the symmetric twin of a denylist: only publishers with a
|
||||||
|
`user-allow` contribution are shown, unlisted ones are hidden across all four
|
||||||
|
dimensions, and protected actors (root, moderators) are never gated.
|
||||||
|
|
||||||
### Target attributes
|
### Target attributes
|
||||||
|
|
||||||
Any target-bearing element reads its target from markup:
|
Any target-bearing element reads its target from markup:
|
||||||
@@ -161,13 +165,44 @@ button teaches nothing about the authority model. The command layer enforces
|
|||||||
the same check independently — the UI is an explanation, never the gate.
|
the same check independently — the UI is an explanation, never the gate.
|
||||||
|
|
||||||
**Styling is yours.** Elements expose `part` attributes and inherit `color` and
|
**Styling is yours.** Elements expose `part` attributes and inherit `color` and
|
||||||
`font` from the host. Override the CSS custom properties (`--gov-fg`,
|
`font` from the host. Override the CSS custom properties or target parts:
|
||||||
`--gov-surface`, `--gov-warn`, `--gov-danger`, `--gov-radius`) or target parts:
|
|
||||||
|
| Token | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `--bitgate-fg` | Foreground / text (`currentColor`) |
|
||||||
|
| `--bitgate-muted` | Secondary text |
|
||||||
|
| `--bitgate-border` | Control borders |
|
||||||
|
| `--bitgate-surface` | Button / panel background |
|
||||||
|
| `--bitgate-warn` | Warning copy |
|
||||||
|
| `--bitgate-danger` | Danger / destructive accent |
|
||||||
|
| `--bitgate-radius` | Control corner radius |
|
||||||
|
|
||||||
|
Each token prefers `--bitgate-*`, then the deprecated `--gov-*` name
|
||||||
|
(`--gov-fg`, `--gov-muted`, `--gov-border`, `--gov-surface`, `--gov-warn`,
|
||||||
|
`--gov-danger`, `--gov-radius`), then the built-in default. Existing `--gov-*`
|
||||||
|
overrides keep working; new pages should set `--bitgate-*`. `::part()` names
|
||||||
|
are unchanged.
|
||||||
|
|
||||||
```css
|
```css
|
||||||
|
bitgate-veil { --bitgate-warn: #c2410c; }
|
||||||
bitgate-veil::part(veil) { border: 1px solid red; }
|
bitgate-veil::part(veil) { border: 1px solid red; }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
The library version is the `package.json` version, injected into browser
|
||||||
|
bundles at build time:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { VERSION } from "@bitgate/widget";
|
||||||
|
console.log(VERSION); // e.g. "0.1.0"
|
||||||
|
console.log(window.bitgate.version); // same string, for script-tag hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
`window.bitgate` is created if missing; if a host already put an object there,
|
||||||
|
only `.version` is written. Spec / wire versions (`bitgate/1`, policy
|
||||||
|
`schemaVersion`) are independent of this package version.
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
`defineBitGateElements()` is safe to call repeatedly and safe to import in
|
`defineBitGateElements()` is safe to call repeatedly and safe to import in
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import { build, context } from "esbuild";
|
|||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import { bitgateVersionPlugin } from "../../scripts/esbuild-bitgate-version.mjs";
|
||||||
|
|
||||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
/** @type {import("esbuild").BuildOptions} */
|
/** @type {import("esbuild").BuildOptions} */
|
||||||
@@ -37,6 +39,7 @@ const options = {
|
|||||||
banner: {
|
banner: {
|
||||||
js: "/*! BitGate widget — GPL-3.0-or-later — https://github.com/PR0M3TH3AN/Nostr-Governance */",
|
js: "/*! BitGate widget — GPL-3.0-or-later — https://github.com/PR0M3TH3AN/Nostr-Governance */",
|
||||||
},
|
},
|
||||||
|
plugins: [bitgateVersionPlugin()],
|
||||||
};
|
};
|
||||||
|
|
||||||
const watch = process.argv.includes("--watch");
|
const watch = process.argv.includes("--watch");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// Second, nothing here computes policy. The console displays effective state
|
// Second, nothing here computes policy. The console displays effective state
|
||||||
// and issues commands; every accept/reject decision belongs to the runtime.
|
// and issues commands; every accept/reject decision belongs to the runtime.
|
||||||
|
|
||||||
import { GovernanceElement, defineElement, escapeHtml, shortenKey } from "./base.js";
|
import { GovernanceElement, defineElement, escapeHtml, shortenKey, themeVar } from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('@bitgate/runtime').GovernanceCommands} GovernanceCommands
|
* @typedef {import('@bitgate/runtime').GovernanceCommands} GovernanceCommands
|
||||||
@@ -213,10 +213,10 @@ export class GovernanceAdminPanel extends GovernanceElement {
|
|||||||
this.renderHtml(`
|
this.renderHtml(`
|
||||||
<style>
|
<style>
|
||||||
table { border-collapse: collapse; width: 100%; }
|
table { border-collapse: collapse; width: 100%; }
|
||||||
td, th { text-align: left; padding: 0.3em 0.5em; border-bottom: 1px solid var(--gov-border); }
|
td, th { text-align: left; padding: 0.3em 0.5em; border-bottom: 1px solid ${themeVar("border")}; }
|
||||||
code { font-size: 0.9em; }
|
code { font-size: 0.9em; }
|
||||||
h3 { margin: 0; font-size: 1em; }
|
h3 { margin: 0; font-size: 1em; }
|
||||||
.stale { color: var(--gov-warn); }
|
.stale { color: ${themeVar("warn")}; }
|
||||||
</style>
|
</style>
|
||||||
<div class="stack" part="panel">
|
<div class="stack" part="panel">
|
||||||
${
|
${
|
||||||
|
|||||||
+54
-13
@@ -27,34 +27,75 @@ export function canRegisterElements() {
|
|||||||
return typeof globalThis.HTMLElement === "function" && typeof globalThis.customElements === "object";
|
return typeof globalThis.HTMLElement === "function" && typeof globalThis.customElements === "object";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for widget theme tokens.
|
||||||
|
*
|
||||||
|
* `--gov-*` names are the historical prefix (from Nostr-Governance). `--bitgate-*`
|
||||||
|
* is the agreed prefix. Usage sites prefer the new name, then the old one, then
|
||||||
|
* this default, so existing pages and `--gov-*` overrides keep working.
|
||||||
|
*
|
||||||
|
* @type {Readonly<{
|
||||||
|
* fg: string,
|
||||||
|
* muted: string,
|
||||||
|
* border: string,
|
||||||
|
* surface: string,
|
||||||
|
* warn: string,
|
||||||
|
* danger: string,
|
||||||
|
* radius: string,
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
export const THEME_DEFAULTS = Object.freeze({
|
||||||
|
fg: "currentColor",
|
||||||
|
muted: "color-mix(in srgb, currentColor 60%, transparent)",
|
||||||
|
border: "color-mix(in srgb, currentColor 20%, transparent)",
|
||||||
|
surface: "color-mix(in srgb, canvas 96%, currentColor)",
|
||||||
|
warn: "#b45309",
|
||||||
|
danger: "#b91c1c",
|
||||||
|
radius: "8px",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS value for a theme token: `--bitgate-*` wins, then deprecated `--gov-*`,
|
||||||
|
* then the existing default.
|
||||||
|
*
|
||||||
|
* `--bitgate-*` is deliberately not assigned on `:host`. Assigning it would
|
||||||
|
* make `var(--bitgate-*, …)` always hit and ignore `--gov-*` overrides.
|
||||||
|
*
|
||||||
|
* @param {keyof typeof THEME_DEFAULTS} name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function themeVar(name) {
|
||||||
|
return `var(--bitgate-${name}, var(--gov-${name}, ${THEME_DEFAULTS[name]}))`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Base styles shared by every element, scoped inside each shadow root. */
|
/** Base styles shared by every element, scoped inside each shadow root. */
|
||||||
export const BASE_STYLES = `
|
export const BASE_STYLES = `
|
||||||
:host {
|
:host {
|
||||||
--gov-fg: currentColor;
|
--gov-fg: ${THEME_DEFAULTS.fg};
|
||||||
--gov-muted: color-mix(in srgb, currentColor 60%, transparent);
|
--gov-muted: ${THEME_DEFAULTS.muted};
|
||||||
--gov-border: color-mix(in srgb, currentColor 20%, transparent);
|
--gov-border: ${THEME_DEFAULTS.border};
|
||||||
--gov-surface: color-mix(in srgb, canvas 96%, currentColor);
|
--gov-surface: ${THEME_DEFAULTS.surface};
|
||||||
--gov-warn: #b45309;
|
--gov-warn: ${THEME_DEFAULTS.warn};
|
||||||
--gov-danger: #b91c1c;
|
--gov-danger: ${THEME_DEFAULTS.danger};
|
||||||
--gov-radius: 8px;
|
--gov-radius: ${THEME_DEFAULTS.radius};
|
||||||
display: block;
|
display: block;
|
||||||
color: var(--gov-fg);
|
color: ${themeVar("fg")};
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
:host([hidden]) { display: none; }
|
:host([hidden]) { display: none; }
|
||||||
button {
|
button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: var(--gov-surface);
|
background: ${themeVar("surface")};
|
||||||
border: 1px solid var(--gov-border);
|
border: 1px solid ${themeVar("border")};
|
||||||
border-radius: var(--gov-radius);
|
border-radius: ${themeVar("radius")};
|
||||||
padding: 0.4em 0.8em;
|
padding: 0.4em 0.8em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
button:hover:not(:disabled) { border-color: var(--gov-fg); }
|
button:hover:not(:disabled) { border-color: ${themeVar("fg")}; }
|
||||||
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
.reason {
|
.reason {
|
||||||
color: var(--gov-muted);
|
color: ${themeVar("muted")};
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
.row { display: flex; gap: 0.5em; align-items: center; flex-wrap: wrap; }
|
.row { display: flex; gap: 0.5em; align-items: center; flex-wrap: wrap; }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { VERSION } from "@bitgate/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish `window.bitgate.version` for script-tag integrators.
|
||||||
|
*
|
||||||
|
* Additive: creates `window.bitgate` when missing, otherwise writes `version`
|
||||||
|
* onto the existing object so a host-provided namespace is not replaced.
|
||||||
|
* No-op in Node (no `window`).
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>} [target] - Defaults to `globalThis.window`
|
||||||
|
* @returns {Record<string, any>|null} The namespace that received `version`
|
||||||
|
*/
|
||||||
|
export function exposeBitGateVersion(target) {
|
||||||
|
const root =
|
||||||
|
target ??
|
||||||
|
(typeof globalThis.window !== "undefined"
|
||||||
|
? /** @type {Record<string, any>} */ (globalThis.window)
|
||||||
|
: null);
|
||||||
|
if (!root || typeof root !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = root.bitgate;
|
||||||
|
const ns =
|
||||||
|
existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
||||||
|
ns.version = VERSION;
|
||||||
|
root.bitgate = ns;
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
@@ -21,9 +21,11 @@ export {
|
|||||||
} from "@bitgate/runtime";
|
} from "@bitgate/runtime";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
VERSION,
|
||||||
SOCIAL_POLICY,
|
SOCIAL_POLICY,
|
||||||
COMMERCE_POLICY,
|
COMMERCE_POLICY,
|
||||||
ADMIN_ONLY_POLICY,
|
ADMIN_ONLY_POLICY,
|
||||||
|
ALLOWLIST_POLICY,
|
||||||
POLICY_PRESETS,
|
POLICY_PRESETS,
|
||||||
getPolicyPreset,
|
getPolicyPreset,
|
||||||
createPolicyDefinition,
|
createPolicyDefinition,
|
||||||
@@ -42,6 +44,7 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
BASE_STYLES,
|
BASE_STYLES,
|
||||||
|
THEME_DEFAULTS,
|
||||||
GovernanceElement,
|
GovernanceElement,
|
||||||
canRegisterElements,
|
canRegisterElements,
|
||||||
defineElement,
|
defineElement,
|
||||||
@@ -49,8 +52,11 @@ export {
|
|||||||
requestContext,
|
requestContext,
|
||||||
shortenKey,
|
shortenKey,
|
||||||
targetFromAttributes,
|
targetFromAttributes,
|
||||||
|
themeVar,
|
||||||
} from "./base.js";
|
} from "./base.js";
|
||||||
|
|
||||||
|
export { exposeBitGateVersion } from "./expose-version.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
BitGateError,
|
BitGateError,
|
||||||
BitGateProvider,
|
BitGateProvider,
|
||||||
@@ -76,9 +82,12 @@ export {
|
|||||||
} from "./admin.js";
|
} from "./admin.js";
|
||||||
|
|
||||||
import { defineAdminElements } from "./admin.js";
|
import { defineAdminElements } from "./admin.js";
|
||||||
|
import { exposeBitGateVersion } from "./expose-version.js";
|
||||||
import { defineProviderElements } from "./provider.js";
|
import { defineProviderElements } from "./provider.js";
|
||||||
import { defineViewerElements } from "./viewer.js";
|
import { defineViewerElements } from "./viewer.js";
|
||||||
|
|
||||||
|
exposeBitGateVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register every governance element.
|
* Register every governance element.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class BitGateProvider extends ProviderBase {
|
|||||||
|
|
||||||
if (!policy) {
|
if (!policy) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown policy preset "${policyName}". Use social, commerce, admin-only, or set .policy directly.`,
|
`Unknown policy preset "${policyName}". Use social, commerce, admin-only, allowlist, or set .policy directly.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (relays.length === 0) {
|
if (relays.length === 0) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
escapeHtml,
|
escapeHtml,
|
||||||
shortenKey,
|
shortenKey,
|
||||||
targetFromAttributes,
|
targetFromAttributes,
|
||||||
|
themeVar,
|
||||||
} from "./base.js";
|
} from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +162,7 @@ export class GovernanceVeil extends GovernanceElement {
|
|||||||
position: absolute; inset: 0; display: grid; place-content: center;
|
position: absolute; inset: 0; display: grid; place-content: center;
|
||||||
gap: 0.5em; text-align: center; padding: 1em;
|
gap: 0.5em; text-align: center; padding: 1em;
|
||||||
}
|
}
|
||||||
.warn { color: var(--gov-warn); }
|
.warn { color: ${themeVar("warn")}; }
|
||||||
</style>
|
</style>
|
||||||
<div class="overlay" part="container">
|
<div class="overlay" part="container">
|
||||||
<div class="${blurred ? "blurred" : ""}" part="content"><slot></slot></div>
|
<div class="${blurred ? "blurred" : ""}" part="content"><slot></slot></div>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { build } from "esbuild";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { bitgateVersionPlugin, readLibraryVersion } from "../../../scripts/esbuild-bitgate-version.mjs";
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const versionJs = join(HERE, "..", "..", "core", "src", "version.js");
|
||||||
|
|
||||||
|
describe("build-time version injection", () => {
|
||||||
|
it("replaces the version module with package.json's version", async () => {
|
||||||
|
const version = readLibraryVersion();
|
||||||
|
const result = await build({
|
||||||
|
entryPoints: [versionJs],
|
||||||
|
bundle: true,
|
||||||
|
write: false,
|
||||||
|
format: "esm",
|
||||||
|
platform: "neutral",
|
||||||
|
plugins: [bitgateVersionPlugin(version)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundled = result.outputFiles[0].text;
|
||||||
|
expect(bundled).toContain(`VERSION = ${JSON.stringify(version)}`);
|
||||||
|
expect(bundled).not.toContain("Keep this string in lockstep");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -135,6 +135,11 @@ describe("<bitgate-provider>", () => {
|
|||||||
expect(provider.runtime.policies.policy.id).toBe("bitgate-commerce");
|
expect(provider.runtime.policies.policy.id).toBe("bitgate-commerce");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts the allowlist policy preset", async () => {
|
||||||
|
const provider = await mountProvider({ policy: "allowlist" });
|
||||||
|
expect(provider.runtime.policies.policy.id).toBe("bitgate-allowlist");
|
||||||
|
});
|
||||||
|
|
||||||
it("defaults to administrative-only governance", async () => {
|
it("defaults to administrative-only governance", async () => {
|
||||||
const provider = await mountProvider({ policy: null });
|
const provider = await mountProvider({ policy: null });
|
||||||
expect(provider.runtime.policies.policy.id).toBe("bitgate-admin-only");
|
expect(provider.runtime.policies.policy.id).toBe("bitgate-admin-only");
|
||||||
@@ -154,6 +159,7 @@ describe("<bitgate-provider>", () => {
|
|||||||
const provider = await mountProvider({ policy: "invented" });
|
const provider = await mountProvider({ policy: "invented" });
|
||||||
expect(provider.runtime).toBeNull();
|
expect(provider.runtime).toBeNull();
|
||||||
expect(provider.getAttribute("data-error")).toMatch(/Unknown policy preset/);
|
expect(provider.getAttribute("data-error")).toMatch(/Unknown policy preset/);
|
||||||
|
expect(provider.getAttribute("data-error")).toMatch(/allowlist/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports a missing relays attribute", async () => {
|
it("reports a missing relays attribute", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { BASE_STYLES, THEME_DEFAULTS, themeVar } from "../src/base.js";
|
||||||
|
|
||||||
|
describe("theme tokens", () => {
|
||||||
|
it("resolves --bitgate-* first, then deprecated --gov-*, then the default", () => {
|
||||||
|
for (const name of Object.keys(THEME_DEFAULTS)) {
|
||||||
|
expect(themeVar(/** @type {keyof typeof THEME_DEFAULTS} */ (name))).toBe(
|
||||||
|
`var(--bitgate-${name}, var(--gov-${name}, ${THEME_DEFAULTS[name]}))`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps --gov-* host defaults so existing overrides still apply", () => {
|
||||||
|
for (const [name, value] of Object.entries(THEME_DEFAULTS)) {
|
||||||
|
expect(BASE_STYLES).toContain(`--gov-${name}: ${value}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not assign --bitgate-* on :host (that would shadow --gov-* overrides)", () => {
|
||||||
|
expect(BASE_STYLES).not.toMatch(/--bitgate-[a-z]+:/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the fallback chain at every painted token", () => {
|
||||||
|
expect(BASE_STYLES).toContain(themeVar("fg"));
|
||||||
|
expect(BASE_STYLES).toContain(themeVar("muted"));
|
||||||
|
expect(BASE_STYLES).toContain(themeVar("border"));
|
||||||
|
expect(BASE_STYLES).toContain(themeVar("surface"));
|
||||||
|
expect(BASE_STYLES).toContain(themeVar("radius"));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { VERSION, exposeBitGateVersion } from "../src/index.js";
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootPkg = JSON.parse(readFileSync(join(HERE, "..", "..", "..", "package.json"), "utf8"));
|
||||||
|
|
||||||
|
describe("VERSION", () => {
|
||||||
|
it("matches package.json", () => {
|
||||||
|
expect(VERSION).toBe(rootPkg.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is published on window.bitgate when the widget is imported", () => {
|
||||||
|
expect(/** @type {any} */ (window).bitgate.version).toBe(VERSION);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("writes version onto an existing window.bitgate object", () => {
|
||||||
|
const target = { bitgate: { extra: true } };
|
||||||
|
exposeBitGateVersion(target);
|
||||||
|
expect(target.bitgate.version).toBe(VERSION);
|
||||||
|
expect(target.bitgate.extra).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* esbuild plugin: replace `@bitgate/core`'s version module with the value from
|
||||||
|
* package.json so browser bundles cannot drift from the package.
|
||||||
|
*/
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
|
||||||
|
/** @returns {string} */
|
||||||
|
export function readLibraryVersion() {
|
||||||
|
return JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} [injected]
|
||||||
|
* @returns {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export function bitgateVersionPlugin(injected = readLibraryVersion()) {
|
||||||
|
return {
|
||||||
|
name: "bitgate-version",
|
||||||
|
setup(pluginBuild) {
|
||||||
|
pluginBuild.onLoad({ filter: /[/\\]core[/\\]src[/\\]version\.js$/ }, () => ({
|
||||||
|
contents: `export const VERSION = ${JSON.stringify(injected)};\n`,
|
||||||
|
loader: "js",
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in new issue
Block a user