Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0c82f75
feat(plugin-api)!: the contract names no Fresh type (#97, decision 17)
zumbrunn Nov 5, 2026
af993ba
docs(plugin-api): say that an unannotated handler ctx is a PluginRout…
zumbrunn Nov 6, 2026
fbd3636
test(contract): core and @dune/plugin-api agree on the first slice of…
zumbrunn Nov 6, 2026
01b1c47
test(contract): plugin services, identity, engines and history match …
zumbrunn Nov 6, 2026
d5dee8b
test(contract): plugin object, hooks, capabilities, config and runtim…
zumbrunn Nov 6, 2026
85188ca
test(contract): identity types are checked core-to-package now that t…
zumbrunn Nov 6, 2026
5480f7e
feat(config)!: runtime_dir and max_revisions are core settings, not a…
zumbrunn Nov 6, 2026
9eb743a
merge main into feat/plugin-api-compat (#115 and #116 merged)
zumbrunn Nov 6, 2026
0ea5229
feat(plugin-api)!: the admin declares where it is mounted (admin.pref…
zumbrunn Nov 6, 2026
439d7e4
chore: drop the deno.lock change that slipped into the step 2 commit
zumbrunn Nov 6, 2026
8ad89d5
feat(plugins)!: core no longer loads the admin, it is a plugin you li…
zumbrunn Nov 6, 2026
f72041b
feat(plugin-api)!: machine translation and the audit trail leave core…
zumbrunn Nov 6, 2026
3e609c4
feat(config)!: the admin's settings are the admin plugin's config, no…
zumbrunn Nov 7, 2026
145b7ab
feat(config)!: machine_translation moves into the admin plugin's conf…
zumbrunn Nov 7, 2026
c5de0fa
chore: drop the deno.lock change that slipped into the step 5 commits
zumbrunn Nov 7, 2026
b0d38c0
fix(authz)!: site users' roles grant editing, checked on app:site (#130)
zumbrunn Nov 7, 2026
2191190
fix(authz): a role granted or revoked in a running app takes effect a…
zumbrunn Nov 7, 2026
ca675bb
Merge origin/main into feat/explicit-admin-loading
zumbrunn Nov 7, 2026
bf41539
Merge feat/explicit-admin-loading into feat/mt-audit-to-admin
zumbrunn Nov 7, 2026
25067b4
Merge feat/mt-audit-to-admin into feat/admin-settings-in-plugin
zumbrunn Nov 7, 2026
87db9e4
Merge feat/admin-settings-in-plugin into fix/site-user-editing
zumbrunn Nov 7, 2026
d3e5c3a
feat(plugin-api)!: the contract keeps what core consumes; plugins dec…
zumbrunn Nov 7, 2026
292a3dd
feat(plugin-api)!: transforms read capabilities from ResponseTransfor…
zumbrunn Nov 7, 2026
3c1b2be
docs: the unprefixed capability namespaces are editor, scheduler and …
zumbrunn Nov 7, 2026
efdb350
feat(plugin-api)!: core gates /api/inline-edit/* and hands requests t…
zumbrunn Nov 7, 2026
929043b
docs: changelog for the inline-edit HTTP gate and page-event actor (#…
zumbrunn Nov 7, 2026
740da47
feat(plugin-api)!: typed capability keys instead of module augmentati…
zumbrunn Nov 8, 2026
e2a696e
docs: capability keys in the plugins module comment
zumbrunn Nov 8, 2026
37c6f05
Merge remote-tracking branch 'origin/feat/inline-edit-routes' into fe…
zumbrunn Nov 8, 2026
f7d2608
test(contract): follow the split contract (typed capability keys, edi…
zumbrunn Nov 8, 2026
979fc52
fix: headless smoke boots without the admin entry; headless scaffold …
zumbrunn Nov 8, 2026
c5ed518
fix(auth): log a failed role-grant bootstrap, own-property override l…
zumbrunn Nov 8, 2026
5f85916
Merge branch 'feat/explicit-admin-loading' into feat/mt-audit-to-admin
zumbrunn Nov 8, 2026
0588f2b
Merge branch 'feat/mt-audit-to-admin' into feat/admin-settings-in-plugin
zumbrunn Nov 8, 2026
42f5f23
test: the overlay admin.enabled warning, with #131's manual-move exit
zumbrunn Nov 8, 2026
e648e84
Merge branch 'feat/admin-settings-in-plugin' into fix/site-user-editing
zumbrunn Nov 8, 2026
b204168
chore: keep deno.lock as on the base branch
zumbrunn Nov 8, 2026
2d41672
Merge branch 'fix/site-user-editing' into refactor/contract-split
zumbrunn Nov 8, 2026
2d57358
Merge branch 'refactor/contract-split' into feat/inline-edit-routes
zumbrunn Nov 8, 2026
7f1e939
Merge branch 'feat/inline-edit-routes' into feat/plugin-api-compat
zumbrunn Nov 8, 2026
caf77e6
feat(auth): live editing sessions respect page ownership; name where …
zumbrunn Nov 8, 2026
4e552c4
Merge branch 'feat/inline-edit-routes' into feat/plugin-api-compat
zumbrunn Nov 8, 2026
84547bd
fix(cli): headless scaffold registers plugin islands with registerIsland
zumbrunn Nov 8, 2026
83b8207
Merge remote-tracking branch 'origin/feat/explicit-admin-loading' int…
zumbrunn Nov 10, 2026
db082f9
Merge origin/main into feat/mt-audit-to-admin
zumbrunn Nov 10, 2026
7445cfb
fix(migrate:config): keep core settings of a disabled admin block; ne…
zumbrunn Nov 10, 2026
89a25ec
Merge remote-tracking branch 'origin/feat/mt-audit-to-admin' into fea…
zumbrunn Nov 10, 2026
5672444
Merge remote-tracking branch 'origin/main' into feat/admin-settings-i…
zumbrunn Nov 10, 2026
2b50403
Merge remote-tracking branch 'origin/feat/admin-settings-in-plugin' i…
zumbrunn Nov 10, 2026
70f485b
Merge remote-tracking branch 'origin/main' into fix/site-user-editing
zumbrunn Nov 10, 2026
2cd6e29
Merge remote-tracking branch 'origin/fix/site-user-editing' into refa…
zumbrunn Nov 10, 2026
42a1cff
Merge origin/main into refactor/contract-split
zumbrunn Nov 10, 2026
53ef3a7
Merge remote-tracking branch 'origin/refactor/contract-split' into fe…
zumbrunn Nov 10, 2026
6cbd371
Merge origin/main into feat/inline-edit-routes
zumbrunn Nov 10, 2026
3f262c6
Merge remote-tracking branch 'origin/feat/inline-edit-routes' into fe…
zumbrunn Nov 10, 2026
4db7d10
Merge origin/main into feat/plugin-api-compat
zumbrunn Nov 10, 2026
0f4a077
Merge remote-tracking branch 'origin/main' into feat/plugin-api-compat
zumbrunn Nov 10, 2026
c68a03e
Merge origin/main into feat/plugin-api-compat
zumbrunn Nov 10, 2026
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
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "zumbrunn (https://zumbrunn.com)",
"minimumDependencyAge": {
"age": "P1D",
"exclude": ["jsr:@dune/core", "jsr:@dune/theme-api"]
"exclude": ["jsr:@dune/core", "jsr:@dune/plugin-api", "jsr:@dune/theme-api"]
},
"exports": {
".": "./src/mod.ts",
Expand Down Expand Up @@ -57,6 +57,7 @@
"./watchdog": "./src/runtime/watchdog.ts"
},
"imports": {
"@dune/plugin-api": "jsr:@dune/plugin-api@^1",
"@dune/theme-api": "jsr:@dune/theme-api@^1",
"fresh": "jsr:@fresh/core@^2",
"preact": "npm:preact@^10",
Expand Down
197 changes: 197 additions & 0 deletions tests/contract/plugin_api_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* `@dune/plugin-api` is a leaf package that redeclares the plugin contract.
* These tests keep core and the package from drifting apart. They are mostly
* compile-time: `deno check` is the assertion.
*
* Two directions (plugin-api extraction plan, decision 10):
* - what core hands a plugin is assignable to the package's types;
* - what a plugin hands in (an engine, an editor manager, a record) is
* accepted by core.
* Types that are identical on both sides are checked both ways.
*/

import { assertEquals } from "@std/assert";
import type * as P from "@dune/plugin-api";
import { isDuneError as pluginIsDuneError } from "@dune/plugin-api";
import type { PageIndex } from "../../src/content/types.ts";
import type {
ContentApi,
ContentSearchResult,
PagesOptions,
ResolvedPage,
TaxonomyTerm,
} from "../../src/content/api.ts";
import type {
FacetCounts,
InjectedSearchRecord,
SearchEngine,
SearchEngineCreateContext,
SearchFilter,
SearchOptions,
SearchRecordsCollectContext,
SearchResult,
} from "../../src/search/engine.ts";
import type { StorageAdapter, StorageEntry, StorageStat, WatchEvent } from "../../src/storage/types.ts";
import type { Submission, SubmissionFile, SubmissionMeta, SubmissionStatus } from "../../src/forms/submissions.ts";
import type { BlueprintField } from "../../src/blueprints/types.ts";
import type { EditorIdentity, InlineEditManager, InlineEditRequestContext } from "../../src/inline-edit/types.ts";
import type { ScheduledAction } from "../../src/workflow/types.ts";
import type { PluginApp, PluginRouteContext } from "../../src/plugin-api/app.ts";
import type { PluginServices } from "../../src/plugin-api/services.ts";
import type { User, UserCreate } from "../../src/auth/types.ts";
import type { UserStore } from "../../src/auth/user-store.ts";
import type { Session } from "../../src/session/types.ts";
import type { SessionManager } from "../../src/session/mod.ts";
import type { HistoryEngine } from "../../src/history/engine.ts";
import type { ContentDiff, ContentRevision } from "../../src/workflow/types.ts";
import type { WorkflowEngine } from "../../src/workflow/engine.ts";
import type { Scheduler } from "../../src/workflow/scheduler.ts";
import type { StagingEngine } from "../../src/staging/engine.ts";
import type { RateLimiter } from "../../src/security/rate-limit.ts";
import type { RateLimitStore } from "../../src/security/rate-limit-store.ts";
import type { Logger } from "../../src/core/logger.ts";
import type { DuneConfig } from "../../src/config/types.ts";
import type { DuneEngine } from "../../src/core/engine.ts";
import type { Page } from "../../src/content/types.ts";
import type { SearchManager } from "../../src/search/manager.ts";
import type { FlexEngine } from "../../src/flex/engine.ts";
import type { SubmissionManager } from "../../src/forms/submissions.ts";
import type { SectionRegistry } from "../../src/sections/registry.ts";
import type { MetricsCollector } from "../../src/metrics/mod.ts";
import type { JobScheduler } from "../../src/jobs/mod.ts";
import type { DuneAuthSystem } from "../../src/auth/authz.ts";
import type { ThemeLoader } from "../../src/themes/loader.ts";
import type { RouteResolver } from "../../src/routing/resolver.ts";
import type { PluginRuntime } from "../../src/plugin-api/runtime.ts";
import type {
DunePlugin,
FormSubmitPayload,
HookContext,
HookEvent,
HookRegistry,
MountApi,
PageMovePayload,
PluginApi,
PublicRouteRegistration,
ResponseTransformContext,
SystemActionPayload,
} from "../../src/hooks/types.ts";
import type {
AuthenticateResult,
Capabilities,
CapabilityContext,
CapabilityKey,
CapabilityReader,
CapabilityRegistrar,
} from "../../src/plugin-api/capabilities.ts";
import { isDuneError as coreIsDuneError } from "../../src/plugin-api/errors.ts";
import { capabilityKey as coreCapabilityKey } from "../../src/plugin-api/capabilities.ts";

// Both directions: identical on both sides.
function same<A, B>(..._ok: [A] extends [B] ? ([B] extends [A] ? [] : [never]) : [never]): void {}
// Core -> package only: core hands out something wider.
function handsOut<Core extends Pkg, Pkg>(): void {}
// Package -> core only: a plugin supplies it and core consumes it.
function takesIn<Pkg extends Core, Core>(): void {}

// --- what core hands a plugin ---------------------------------------------
handsOut<PageIndex, P.PageIndex>();
handsOut<ContentApi, P.ContentApi>();
handsOut<ResolvedPage, P.ResolvedPage>();
handsOut<ContentSearchResult, P.ContentSearchResult>();
handsOut<TaxonomyTerm, P.TaxonomyTerm>();
handsOut<PagesOptions, P.PagesOptions>();
handsOut<SearchEngineCreateContext, P.SearchEngineCreateContext>();
handsOut<SearchRecordsCollectContext, P.SearchRecordsCollectContext>();

handsOut<PluginServices, P.PluginServices>();
handsOut<HistoryEngine, P.HistoryEngine>();
handsOut<WorkflowEngine, P.WorkflowEngine>();
handsOut<Scheduler, P.Scheduler>();
handsOut<StagingEngine, P.StagingEngine>();
handsOut<RateLimiter, P.RateLimiter>();
handsOut<Logger, P.Logger>();

// --- what a plugin hands in -----------------------------------------------
takesIn<P.SearchEngine, SearchEngine>();
takesIn<P.InjectedSearchRecord, InjectedSearchRecord>();
takesIn<P.InlineEditManager, InlineEditManager>();
same<EditorIdentity, P.EditorIdentity>();
same<InlineEditRequestContext, P.InlineEditRequestContext>();

// --- identity: a provider supplies its own stores and cores consumes them -
handsOut<User, P.User>();
takesIn<P.UserCreate, UserCreate>();
handsOut<UserStore, P.UserStore>();
handsOut<Session, P.Session>();
handsOut<SessionManager, P.SessionManager>();
same<ContentRevision, P.ContentRevision>();
same<ContentDiff, P.ContentDiff>();
same<RateLimitStore, P.RateLimitStore>();

// --- identical both ways ---------------------------------------------------
same<StorageAdapter, P.StorageAdapter>();
same<StorageEntry, P.StorageEntry>();
same<StorageStat, P.StorageStat>();
same<WatchEvent, P.WatchEvent>();
same<Submission, P.Submission>();
same<SubmissionFile, P.SubmissionFile>();
same<SubmissionMeta, P.SubmissionMeta>();
same<SubmissionStatus, P.SubmissionStatus>();
same<BlueprintField, P.BlueprintField>();
same<ScheduledAction, P.ScheduledAction>();
same<SearchResult, P.SearchResult>();
same<SearchFilter, P.SearchFilter>();
same<SearchOptions, P.SearchOptions>();
same<FacetCounts, P.FacetCounts>();
same<PluginApp, P.PluginApp>();
same<PluginRouteContext, P.PluginRouteContext>();

// --- slice 2b: config, runtime, hooks, plugin object, capabilities ---------
handsOut<DuneConfig, P.DuneConfig>();
handsOut<DuneEngine, P.PluginEngine>();
handsOut<Page, P.Page>();
handsOut<RouteResolver, P.RouteResolver>();
handsOut<ThemeLoader, P.ThemeLoader>();
handsOut<SearchManager, P.PluginSearch>();
handsOut<FlexEngine, P.FlexEngine>();
handsOut<SubmissionManager, P.SubmissionManager>();
handsOut<SectionRegistry, P.SectionRegistry>();
handsOut<MetricsCollector, P.MetricsCollector>();
handsOut<JobScheduler, P.JobScheduler>();
handsOut<DuneAuthSystem, P.PluginAuthz>();
handsOut<PluginRuntime, P.PluginRuntime>();
handsOut<MountApi, P.MountApi>();
handsOut<PluginApi, P.PluginApi>();
handsOut<HookContext, P.HookContext>();
handsOut<HookRegistry, P.HookRegistry>();
handsOut<ResponseTransformContext, P.ResponseTransformContext>();
handsOut<CapabilityContext, P.CapabilityContext>();
handsOut<CapabilityReader, P.CapabilityReader>();
handsOut<CapabilityRegistrar, P.CapabilityRegistrar>();

// What a plugin supplies and core consumes.
takesIn<P.DunePlugin, DunePlugin>();
takesIn<P.PublicRouteRegistration, PublicRouteRegistration>();
takesIn<P.Capabilities, Capabilities>();
// Extension points a plugin owns are typed keys, not entries in a shared interface.
same<CapabilityKey<unknown>, P.CapabilityKey<unknown>>();

same<HookEvent, P.HookEvent>();
same<PageMovePayload, P.PageMovePayload>();
same<FormSubmitPayload, P.FormSubmitPayload>();
same<SystemActionPayload, P.SystemActionPayload>();
same<AuthenticateResult, P.AuthenticateResult>();

Deno.test("isDuneError behaves the same in the package and in core", () => {
const err = { code: "ssrf_blocked" };
for (const code of ["ssrf_blocked", "body_too_large", "weak_password", "duplicate_email"] as const) {
assertEquals(pluginIsDuneError(err, code), coreIsDuneError(err, code));
}
assertEquals(pluginIsDuneError(new Error("x"), "ssrf_blocked"), false);
});

Deno.test("a capability key made by the package is the key core reads", async () => {
const { capabilityKey: pluginCapabilityKey } = await import("@dune/plugin-api");
assertEquals(pluginCapabilityKey("bar.actions"), coreCapabilityKey("bar.actions"));
});
Loading