fluxplugin 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -10
- package/dist/{node/chunks/chunk-XPLGAEBH.js → browser/chunks/chunk-TL63576R.js} +106 -81
- package/dist/browser/chunks/{chunk-ZZ25CRMR.js → chunk-VXQJIDXW.js} +479 -6
- package/dist/browser/index.js +5 -1
- package/dist/browser/manager-ui.js +93 -18
- package/dist/browser/react.js +34 -4
- package/dist/cli.js +955 -318
- package/dist/node/chunks/chunk-3FH6NAJG.js +38 -0
- package/dist/node/chunks/{chunk-BZ7NZF7I.js → chunk-C7VWZZOX.js} +259 -44
- package/dist/{browser/chunks/chunk-Q75AOCXD.js → node/chunks/chunk-EVX2O65A.js} +185 -2
- package/dist/node/chunks/chunk-WSFCRVEQ.js +9 -0
- package/dist/node/chunks/tailwind-TJQVTBG5.js +9 -0
- package/dist/node/cli-api.js +663 -288
- package/dist/node/index.js +6 -1
- package/dist/node/main.js +19 -2
- package/dist/node/preload.js +2 -1
- package/dist/types/cli/build.d.ts +1 -1
- package/dist/types/cli/config.d.ts +3 -0
- package/dist/types/cli/host-files.generated.d.ts +2 -0
- package/dist/types/cli/host-templates.d.ts +3 -0
- package/dist/types/cli/host.d.ts +10 -1
- package/dist/types/cli/package.d.ts +18 -0
- package/dist/types/cli/publish.d.ts +23 -0
- package/dist/types/cli/store.d.ts +7 -0
- package/dist/types/cli/tailwind.d.ts +10 -0
- package/dist/types/cli/templates.d.ts +2 -0
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/core/manager.d.ts +2 -0
- package/dist/types/core/manifest.d.ts +9 -0
- package/dist/types/core/notifications.d.ts +48 -0
- package/dist/types/core/plugin.d.ts +7 -0
- package/dist/types/core/protocol.d.ts +27 -0
- package/dist/types/main/framework.d.ts +7 -1
- package/dist/types/main/github-registry.d.ts +49 -0
- package/dist/types/main/index.d.ts +2 -0
- package/dist/types/main/registry.d.ts +30 -0
- package/dist/types/main/stores.d.ts +29 -0
- package/dist/types/react/base-css.d.ts +1 -1
- package/dist/types/react/icons.d.ts +12 -0
- package/dist/types/react/index.d.ts +5 -0
- package/dist/types/react/markdown.d.ts +8 -0
- package/dist/types/react/os.d.ts +5 -0
- package/dist/types/react/runtime.d.ts +2 -1
- package/dist/types/react/theme.d.ts +53 -0
- package/dist/types/react/titlebar.d.ts +28 -0
- package/docs/CUSTOMIZING.md +257 -0
- package/docs/PLUGIN-AUTHORS.md +36 -9
- package/docs/RELEASE.md +1 -1
- package/docs/SECURITY.md +5 -0
- package/docs/STORE.md +123 -0
- package/docs/TROUBLESHOOTING.md +35 -0
- package/docs/screenshots/command-palette.png +0 -0
- package/docs/screenshots/dashboard.png +0 -0
- package/docs/screenshots/electron-app.png +0 -0
- package/docs/screenshots/electron-plugin-page.png +0 -0
- package/docs/screenshots/electron-titlebar.png +0 -0
- package/docs/screenshots/plugin-details.png +0 -0
- package/docs/screenshots/plugin-manager.png +0 -0
- package/docs/screenshots/plugin-video.png +0 -0
- package/docs/screenshots/stats.png +0 -0
- package/docs/screenshots/store-available.png +0 -0
- package/docs/screenshots/tailwind-dark.png +0 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ cd my-app && npm install
|
|
|
9
9
|
npm run dev:web # browser mode, no Electron needed
|
|
10
10
|
npm run dev # Electron, plugins hot-reload
|
|
11
11
|
npm run plugin:new orders -- -t backend # scaffold a plugin in ./plugins
|
|
12
|
+
npx fluxplugin init my-app --tailwindcss # …or preconfigure Tailwind
|
|
12
13
|
npm run build && npm start
|
|
13
14
|
```
|
|
14
15
|
|
|
@@ -36,15 +37,39 @@ export default definePlugin({ import { usePluginIPC } from
|
|
|
36
37
|
preload: { orders: { count: "orders.count" } }, meta: { title: "Orders", menu: { label: "Orders" } } }] });
|
|
37
38
|
});
|
|
38
39
|
```
|
|
39
|
-
|
|
40
|
+
## What you get out of the box
|
|
41
|
+
Custom **title bar + menu bar** that adapts to macOS / Windows / Linux · **search** (commands, pages, plugin providers) ·
|
|
42
|
+
**notifications** (bell + toasts) · **themes** (light/dark/system + plugin themes) · icons · command palette · plugin manager with a
|
|
43
|
+
store-style page per plugin (icon, screenshots, YouTube video, Markdown description) · plugins shipped as **JavaScript only** ·
|
|
44
|
+
optional **Tailwind v4** · permissions, signatures, rollback · a **plugin store** (GitHub by default, or your own) with `fluxplugin publish`.
|
|
45
|
+
|
|
46
|
+
## Docs
|
|
47
|
+
**[STORE](docs/STORE.md)** (develop → publish → install; your own store) · **[CUSTOMIZING](docs/CUSTOMIZING.md)** (add your own APIs, permissions, hooks, title/menu bar, search, notifications, themes, Tailwind) ·
|
|
48
|
+
[PLUGIN-AUTHORS](docs/PLUGIN-AUTHORS.md) · [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) · [ARCHITECTURE](docs/ARCHITECTURE.md) · [SECURITY](docs/SECURITY.md) · [RELEASE](docs/RELEASE.md)
|
|
49
|
+
|
|
50
|
+
## What `fluxplugin init` generates
|
|
51
|
+
All application code lives in `src/app`, in layers (`shared` → `main` / `preload` / `renderer`):
|
|
52
|
+
```
|
|
53
|
+
src/app/
|
|
54
|
+
shared/ constants.ts · types.ts (depends on nothing)
|
|
55
|
+
main/ index.ts (Electron) · web.ts (browser) · framework.ts · operations.ts · window.ts · paths.ts
|
|
56
|
+
preload/ index.ts
|
|
57
|
+
renderer/ index.tsx · bootstrap.ts · extension-points.ts · routes.ts · App.tsx
|
|
58
|
+
providers/ · shell/ · pages/ · hooks/ · services/ · styles/
|
|
59
|
+
plugins/ loaded at startup
|
|
60
|
+
```
|
|
40
61
|
|
|
41
62
|
## Status — read this
|
|
42
|
-
* **Verified:**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* **
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* **Not
|
|
63
|
+
* **Verified:** ~140 automated tests and strict typecheck; the packed tarball installed into freshly generated apps (default and
|
|
64
|
+
`--tailwindcss`) → build → run; the generated app run in **real Electron 44** (Linux/Xvfb) *with Electron's install script blocked*
|
|
65
|
+
(auto-download works): custom title bar, plugin menus, search, notifications, theme, plugin page (icon/README), no Node leak
|
|
66
|
+
into the page; a Playwright E2E in a browser with 3 plugins.
|
|
67
|
+
* **Store:** publish → search → install is tested end to end against an in-process imitation of the GitHub API (release, asset upload,
|
|
68
|
+
topics, checksum, tampering, rate limit, pruning of `.map`/`.ts`), the GitHub search request shape was confirmed against the real API
|
|
69
|
+
(read-only, HTTP 200), and installing from a custom HTTP store was run through the app's UI. **`fluxplugin publish` itself has not been run
|
|
70
|
+
against real github.com** (no credentials here): try it with `--dry-run` first, then on a throw-away repository.
|
|
71
|
+
* **Not verified:** Windows and macOS runs (only Linux was available — the OS-specific branches follow Electron's documented API:
|
|
72
|
+
`hiddenInset` on macOS, `titleBarOverlay` on Windows/Linux); installers/auto-update (electron-builder/forge not set up);
|
|
73
|
+
YouTube playback itself (no internet video in the test environment — the embed URL and sandboxed iframe are asserted).
|
|
74
|
+
* **Not published** on npm — see `docs/RELEASE.md`. **Security:** plugin `main` code is not sandboxed (see `docs/SECURITY.md`).
|
|
75
|
+
* **Not done:** per-plugin process isolation, hosted marketplace (only `PluginRegistry` + local/HTTP), OS-level (native) notifications.
|
|
@@ -1,81 +1,3 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __export = (target, all) => {
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
// src/core/index.ts
|
|
8
|
-
var core_exports = {};
|
|
9
|
-
__export(core_exports, {
|
|
10
|
-
BUILTIN_PERMISSIONS: () => BUILTIN_PERMISSIONS,
|
|
11
|
-
CHANNELS: () => CHANNELS,
|
|
12
|
-
CommandRegistry: () => CommandRegistry,
|
|
13
|
-
DisposableStore: () => DisposableStore,
|
|
14
|
-
EventBus: () => EventBus,
|
|
15
|
-
ExtensionPointRegistry: () => ExtensionPointRegistry,
|
|
16
|
-
FRAMEWORK_ENGINE_KEY: () => FRAMEWORK_ENGINE_KEY,
|
|
17
|
-
FRAMEWORK_VERSION: () => FRAMEWORK_VERSION,
|
|
18
|
-
FrameworkError: () => FrameworkError,
|
|
19
|
-
HookCancelledError: () => HookCancelledError,
|
|
20
|
-
HookSystem: () => HookSystem,
|
|
21
|
-
IpcError: () => IpcError,
|
|
22
|
-
IpcRegistry: () => IpcRegistry,
|
|
23
|
-
LifecycleError: () => LifecycleError,
|
|
24
|
-
LogStore: () => LogStore,
|
|
25
|
-
MGMT: () => MGMT,
|
|
26
|
-
MemoryStorageBackend: () => MemoryStorageBackend,
|
|
27
|
-
MenuRegistry: () => MenuRegistry,
|
|
28
|
-
PermissionDeniedError: () => PermissionDeniedError,
|
|
29
|
-
PermissionManager: () => PermissionManager,
|
|
30
|
-
PluginManager: () => PluginManager,
|
|
31
|
-
PreloadRegistry: () => PreloadRegistry,
|
|
32
|
-
Registry: () => Registry,
|
|
33
|
-
RouteRegistry: () => RouteRegistry,
|
|
34
|
-
ServiceRegistry: () => ServiceRegistry,
|
|
35
|
-
SlotRegistry: () => SlotRegistry,
|
|
36
|
-
StyleRegistry: () => StyleRegistry,
|
|
37
|
-
TimeoutError: () => TimeoutError,
|
|
38
|
-
ValidationError: () => ValidationError,
|
|
39
|
-
activationOrderFor: () => activationOrderFor,
|
|
40
|
-
buildDependencyGraph: () => buildDependencyGraph,
|
|
41
|
-
compareVersions: () => compareVersions,
|
|
42
|
-
createPluginConfig: () => createPluginConfig,
|
|
43
|
-
createPluginState: () => createPluginState,
|
|
44
|
-
createPluginStorage: () => createPluginStorage,
|
|
45
|
-
defineCommand: () => defineCommand,
|
|
46
|
-
defineComponent: () => defineComponent,
|
|
47
|
-
defineEvent: () => defineEvent,
|
|
48
|
-
defineHook: () => defineHook,
|
|
49
|
-
defineIPC: () => defineIPC,
|
|
50
|
-
defineIpcContract: () => defineIpcContract,
|
|
51
|
-
defineMenu: () => defineMenu,
|
|
52
|
-
definePlugin: () => definePlugin,
|
|
53
|
-
defineRoute: () => defineRoute,
|
|
54
|
-
defineService: () => defineService,
|
|
55
|
-
dependentsOf: () => dependentsOf,
|
|
56
|
-
describeIssue: () => describeIssue,
|
|
57
|
-
errorMessage: () => errorMessage,
|
|
58
|
-
hasPluginApi: () => hasPluginApi,
|
|
59
|
-
isValidRange: () => isValidRange,
|
|
60
|
-
isValidVersion: () => isValidVersion,
|
|
61
|
-
manifestSchema: () => manifestSchema,
|
|
62
|
-
matchesPattern: () => matchesPattern,
|
|
63
|
-
menuLocations: () => menuLocations,
|
|
64
|
-
normalizePath: () => normalizePath,
|
|
65
|
-
parseOrThrow: () => parseOrThrow,
|
|
66
|
-
parseVersion: () => parseVersion,
|
|
67
|
-
pluginApi: () => pluginApi,
|
|
68
|
-
preloadMember: () => preloadMember,
|
|
69
|
-
routePatternMatches: () => routePatternMatches,
|
|
70
|
-
s: () => s,
|
|
71
|
-
satisfies: () => satisfies,
|
|
72
|
-
scopeCss: () => scopeCss,
|
|
73
|
-
serializeError: () => serializeError,
|
|
74
|
-
toDisposable: () => toDisposable,
|
|
75
|
-
validateManifest: () => validateManifest,
|
|
76
|
-
withTimeout: () => withTimeout
|
|
77
|
-
});
|
|
78
|
-
|
|
79
1
|
// src/core/common.ts
|
|
80
2
|
var toDisposable = (fn) => {
|
|
81
3
|
let done = false;
|
|
@@ -603,6 +525,18 @@ var manifestSchema = s.object({
|
|
|
603
525
|
repository: s.union(s.string(), s.object({ type: s.string().optional(), url: s.string() })).optional(),
|
|
604
526
|
license: s.string().optional(),
|
|
605
527
|
icon: s.string().optional(),
|
|
528
|
+
/** Wide image shown at the top of the plugin page (png/jpg/webp/svg inside the plugin). */
|
|
529
|
+
banner: s.string().optional(),
|
|
530
|
+
/** Screenshots shown in a gallery (paths inside the plugin). */
|
|
531
|
+
screenshots: s.array(s.string()).default([]),
|
|
532
|
+
/** Detailed description as Markdown (default: ./README.md). Rendered safely: no raw HTML. */
|
|
533
|
+
readme: s.string().optional(),
|
|
534
|
+
/** Explanation video (https URL; YouTube and Vimeo are embedded, other hosts become a link). */
|
|
535
|
+
video: s.string({ pattern: /^https:\/\/\S+$/ }).optional(),
|
|
536
|
+
categories: s.array(s.string()).default([]),
|
|
537
|
+
keywords: s.array(s.string()).default([]),
|
|
538
|
+
/** Extra links: docs, support, changelog, funding... */
|
|
539
|
+
links: s.record(s.string({ pattern: /^https:\/\/\S+$/ })).default({}),
|
|
606
540
|
/** Entry executed in the Electron main process. */
|
|
607
541
|
main: s.string().optional(),
|
|
608
542
|
/** Entry executed in the preload context (sandbox-compatible, no Node). */
|
|
@@ -659,6 +593,25 @@ function validateManifest(raw, opts = {}) {
|
|
|
659
593
|
}
|
|
660
594
|
return { ok: issues.length === 0, manifest: issues.length ? void 0 : manifest, issues, warnings };
|
|
661
595
|
}
|
|
596
|
+
function videoEmbedUrl(url) {
|
|
597
|
+
if (!url) return void 0;
|
|
598
|
+
let u;
|
|
599
|
+
try {
|
|
600
|
+
u = new URL(url);
|
|
601
|
+
} catch {
|
|
602
|
+
return void 0;
|
|
603
|
+
}
|
|
604
|
+
const host = u.hostname.replace(/^www\./, "");
|
|
605
|
+
let id;
|
|
606
|
+
if (host === "youtu.be") id = u.pathname.slice(1);
|
|
607
|
+
else if (host === "youtube.com" || host === "m.youtube.com") id = u.searchParams.get("v") ?? (/^\/(embed|shorts)\//.test(u.pathname) ? u.pathname.split("/")[2] : void 0);
|
|
608
|
+
if (id && /^[\w-]{6,20}$/.test(id)) return `https://www.youtube-nocookie.com/embed/${id}`;
|
|
609
|
+
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
610
|
+
const vid = u.pathname.split("/").filter(Boolean).pop();
|
|
611
|
+
if (vid && /^\d+$/.test(vid)) return `https://player.vimeo.com/video/${vid}`;
|
|
612
|
+
}
|
|
613
|
+
return void 0;
|
|
614
|
+
}
|
|
662
615
|
|
|
663
616
|
// src/core/events.ts
|
|
664
617
|
function matchesPattern(pattern, name) {
|
|
@@ -1795,6 +1748,70 @@ var defineEvent = (name, handler, opts = {}) => ({ name, handler, ...opts });
|
|
|
1795
1748
|
var defineComponent = (c) => c;
|
|
1796
1749
|
var defineMenu = (m) => m;
|
|
1797
1750
|
|
|
1751
|
+
// src/core/notifications.ts
|
|
1752
|
+
var NotificationCenter = class {
|
|
1753
|
+
constructor(capacity = 200) {
|
|
1754
|
+
this.capacity = capacity;
|
|
1755
|
+
}
|
|
1756
|
+
capacity;
|
|
1757
|
+
items = [];
|
|
1758
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1759
|
+
pushListeners = /* @__PURE__ */ new Set();
|
|
1760
|
+
seq = 0;
|
|
1761
|
+
version = 0;
|
|
1762
|
+
push(input, owner = "host") {
|
|
1763
|
+
const id = input.id ?? `${owner}#${Date.now().toString(36)}-${this.seq++}`;
|
|
1764
|
+
this.items = this.items.filter((n) => n.id !== id);
|
|
1765
|
+
const item = {
|
|
1766
|
+
id,
|
|
1767
|
+
title: input.title,
|
|
1768
|
+
body: input.body,
|
|
1769
|
+
level: input.level ?? "info",
|
|
1770
|
+
actions: input.actions ?? [],
|
|
1771
|
+
timeoutMs: input.timeoutMs ?? 5e3,
|
|
1772
|
+
owner,
|
|
1773
|
+
time: Date.now(),
|
|
1774
|
+
read: false
|
|
1775
|
+
};
|
|
1776
|
+
this.items.unshift(item);
|
|
1777
|
+
if (this.items.length > this.capacity) this.items.length = this.capacity;
|
|
1778
|
+
this.changed();
|
|
1779
|
+
for (const l of [...this.pushListeners]) l(item);
|
|
1780
|
+
return { id, dismiss: () => this.dismiss(id) };
|
|
1781
|
+
}
|
|
1782
|
+
list() {
|
|
1783
|
+
return this.items;
|
|
1784
|
+
}
|
|
1785
|
+
get unread() {
|
|
1786
|
+
return this.items.filter((n) => !n.read).length;
|
|
1787
|
+
}
|
|
1788
|
+
markRead(id) {
|
|
1789
|
+
for (const n of this.items) if (!id || n.id === id) n.read = true;
|
|
1790
|
+
this.changed();
|
|
1791
|
+
}
|
|
1792
|
+
dismiss(id) {
|
|
1793
|
+
this.items = this.items.filter((n) => n.id !== id);
|
|
1794
|
+
this.changed();
|
|
1795
|
+
}
|
|
1796
|
+
clear() {
|
|
1797
|
+
this.items = [];
|
|
1798
|
+
this.changed();
|
|
1799
|
+
}
|
|
1800
|
+
subscribe = (l) => {
|
|
1801
|
+
this.listeners.add(l);
|
|
1802
|
+
return () => void this.listeners.delete(l);
|
|
1803
|
+
};
|
|
1804
|
+
/** Called for every new notification (toast host, native bridges, main→renderer forwarding). */
|
|
1805
|
+
onPush(l) {
|
|
1806
|
+
this.pushListeners.add(l);
|
|
1807
|
+
return toDisposable(() => this.pushListeners.delete(l));
|
|
1808
|
+
}
|
|
1809
|
+
changed() {
|
|
1810
|
+
this.version++;
|
|
1811
|
+
for (const l of [...this.listeners]) l();
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1798
1815
|
// src/core/manager.ts
|
|
1799
1816
|
var DEFAULT_TIMEOUTS = { load: 15e3, initialize: 1e4, activate: 15e3, ready: 1e4, deactivate: 1e4, unload: 1e4 };
|
|
1800
1817
|
var PluginManager = class {
|
|
@@ -1826,6 +1843,7 @@ var PluginManager = class {
|
|
|
1826
1843
|
ipc = new IpcRegistry();
|
|
1827
1844
|
preload = new PreloadRegistry();
|
|
1828
1845
|
styles = new StyleRegistry();
|
|
1846
|
+
notifications = new NotificationCenter();
|
|
1829
1847
|
storageBackend;
|
|
1830
1848
|
records = /* @__PURE__ */ new Map();
|
|
1831
1849
|
states = /* @__PURE__ */ new Map();
|
|
@@ -1889,7 +1907,11 @@ var PluginManager = class {
|
|
|
1889
1907
|
optionalDependencies: {},
|
|
1890
1908
|
peerDependencies: {},
|
|
1891
1909
|
engines: {},
|
|
1892
|
-
contributes: {}
|
|
1910
|
+
contributes: {},
|
|
1911
|
+
screenshots: [],
|
|
1912
|
+
categories: [],
|
|
1913
|
+
keywords: [],
|
|
1914
|
+
links: {}
|
|
1893
1915
|
};
|
|
1894
1916
|
const rec = {
|
|
1895
1917
|
id,
|
|
@@ -2472,6 +2494,7 @@ var PluginManager = class {
|
|
|
2472
2494
|
run: (op, input, exec) => (need("hooks", "hooks.run"), this.hooks.run(op, input, exec)),
|
|
2473
2495
|
filter: (name, value, extra) => (need("hooks", "hooks.filter"), this.hooks.filter(name, value, extra))
|
|
2474
2496
|
},
|
|
2497
|
+
notifications: { push: (n) => (need("notifications", "notifications.push"), this.notifications.push(n, id)) },
|
|
2475
2498
|
styles: { add: (st) => (need("styles", "styles.add"), track(this.styles.register(st, id))) },
|
|
2476
2499
|
host: {
|
|
2477
2500
|
has: (name) => this.hostApis.has(name),
|
|
@@ -2537,6 +2560,7 @@ var MGMT = {
|
|
|
2537
2560
|
configSchema: "flux.plugins.config.schema",
|
|
2538
2561
|
logs: "flux.plugins.logs",
|
|
2539
2562
|
diagnostics: "flux.plugins.diagnostics",
|
|
2563
|
+
details: "flux.plugins.details",
|
|
2540
2564
|
changed: "flux.plugins.changed"
|
|
2541
2565
|
};
|
|
2542
2566
|
|
|
@@ -2576,6 +2600,7 @@ export {
|
|
|
2576
2600
|
menuLocations,
|
|
2577
2601
|
manifestSchema,
|
|
2578
2602
|
validateManifest,
|
|
2603
|
+
videoEmbedUrl,
|
|
2579
2604
|
matchesPattern,
|
|
2580
2605
|
EventBus,
|
|
2581
2606
|
HookSystem,
|
|
@@ -2613,11 +2638,11 @@ export {
|
|
|
2613
2638
|
defineEvent,
|
|
2614
2639
|
defineComponent,
|
|
2615
2640
|
defineMenu,
|
|
2641
|
+
NotificationCenter,
|
|
2616
2642
|
PluginManager,
|
|
2617
2643
|
routePatternMatches,
|
|
2618
2644
|
CHANNELS,
|
|
2619
2645
|
MGMT,
|
|
2620
2646
|
pluginApi,
|
|
2621
|
-
hasPluginApi
|
|
2622
|
-
core_exports
|
|
2647
|
+
hasPluginApi
|
|
2623
2648
|
};
|