WORK-247
ID:WORK-247Status:done

Surface security and variables options on non-SvelteKit adapters

Two RefractPluginOptions fields are only honoured by @refrakt-md/sveltekit today, even though the underlying machinery (createRefraktLoader + loadContent) accepts both:

Priority:mediumComplexity:simpleMilestone:v0.14.4Source:SPEC-058
claude/update-adapters-5CJgQ View source

Criteria completion

Criteria completion: 12 of 14 (86%) checked; history from May 21 to May 210%25%50%75%100%May 21May 21
Branches 1
claude/update-adapters-5CJgQ current done
main done
History 2
  1. 7f34b59
    • ☑ `RefraktAstroOptions` extends with `security?: SecurityPolicy` and `variables?: Record<string, string>` (plus `Record<string, unknown>` value type — see Approach)
    • ☑ The Astro integration forwards both into `createRefraktLoader` (once {% ref "WORK-244" /%}-style usage of the shared loader lands in the integration or template setup)
    • ☑ Documentation page `site/content/docs/adapters/astro.md` covers both options with example usage
    • ☑ `RefraktNuxtOptions` extends with the same two fields
    • ☑ Nuxt module forwards them into whatever loader path the module uses for content loading (Vite virtual module + `createRefraktLoader` as the SvelteKit reference does)
    • ☑ Documentation page `site/content/docs/adapters/nuxt.md` covers both options
    • ☑ `createRefraktLoader` call sites in the template / helper APIs accept and forward `security` and `variables` parameters
    • ☑ Documentation page `site/content/docs/adapters/next.md` covers both options
    • ☑ `createDataFile`'s config object extends with `security` and `variables`; both forwarded to the underlying `loadContent` call
    • ☑ Documentation page `site/content/docs/adapters/eleventy.md` covers both options
    • ☑ The HTML adapter's build entry point accepts `security` and `variables` and forwards into `createRefraktLoader`
    • ☑ Documentation page `site/content/docs/adapters/html.md` covers both options
    by bjornolofandersson
  2. 51ec9e4
    Created (ready)by bjornolofandersson

Acceptance Criteria

Astro

  • RefraktAstroOptions extends with security?: SecurityPolicy and variables?: Record<string, string> (plus Record<string, unknown> value type — see Approach)
  • The Astro integration forwards both into createRefraktLoader (once WORK-244-style usage of the shared loader lands in the integration or template setup)
  • Documentation page site/content/docs/adapters/astro.md covers both options with example usage

Nuxt

  • RefraktNuxtOptions extends with the same two fields
  • Nuxt module forwards them into whatever loader path the module uses for content loading (Vite virtual module + createRefraktLoader as the SvelteKit reference does)
  • Documentation page site/content/docs/adapters/nuxt.md covers both options

Next.js

  • createRefraktLoader call sites in the template / helper APIs accept and forward security and variables parameters
  • Documentation page site/content/docs/adapters/next.md covers both options

Eleventy

  • createDataFile's config object extends with security and variables; both forwarded to the underlying loadContent call
  • Documentation page site/content/docs/adapters/eleventy.md covers both options

HTML

  • The HTML adapter's build entry point accepts security and variables and forwards into createRefraktLoader
  • Documentation page site/content/docs/adapters/html.md covers both options

Cross-cutting

  • A test site under each non-SvelteKit template, configured with variables: { version: '"1.0.0"' } (note the embedded JSON value) and Markdoc content using {% $version %}, renders 1.0.0 in the output
  • A test site configured with security: { policy: 'strict' } (or whatever the SecurityPolicy shape declares as the locked-down preset) produces sanitised output for an author-provided script tag

Approach

Both fields are pure passthrough. The Astro integration currently has:

const refraktConfig = loadRefraktConfig(configPath);
const { site } = resolveSite(refraktConfig, options.site);

It will grow into using createRefraktLoader (matching the template-astro cleanup in WORK-244, lifted up to the integration layer where it natively belongs):

const loader = createRefraktLoader({
  configPath,
  site: options.site,
  variables: options.variables,
  // security passed to the underlying loadContent call inside createRefraktLoader
  // — extend the loader's options shape if it doesn't already accept it
});

The same shape applies to Nuxt's module, Eleventy's data file, and any Next.js / HTML helper.

Variable value typing: the SvelteKit plugin's variables: Record<string, string> interprets values as raw JavaScript expressions embedded in the generated virtual module (packages/sveltekit/src/types.ts:17). For non-Vite adapters that go through createRefraktLoader directly, the value type is Record<string, unknown> — actual JavaScript values, not source-text expressions, because there's no generated module to embed into. Document both shapes; type discriminator is the adapter (Vite vs. runtime).

createRefraktLoader security forwarding: check whether the current loader (packages/content/src/refract-loader.ts:15–25) accepts a security option. If not, extend the options interface to add it and forward into the underlying loadContent call. The SvelteKit plugin currently passes security directly to loadContent (line 182) without going through the loader — closing that gap is part of this item.

Dependencies

Pairs naturally with the per-adapter wiring items but doesn't block on them. If WORK-244 lands first, the Astro template already wires through createRefraktLoader and only needs the option surface added.

References

  • SPEC-058 — adapter parity spec (this item moves security + variables out of "Out of scope")
  • WORK-177SecurityPolicy for transform pipeline (the spec that introduced the option)
  • packages/sveltekit/src/types.ts:17,22 — reference shape for both options
  • packages/content/src/refract-loader.ts:15–25 — loader options interface (may need extension for security)
  • site/vite.config.ts:11 — example variables consumer

Resolution

Completed: 2026-05-21

Branch: `claude/update-adapters-5CJgQ`

What was done

  • `createRefraktLoader` gains a `security` option (`packages/content/src/refract-loader.ts`). Forwarded into the underlying `loadContent` call via `securityPolicy`. Closes the gap where the SvelteKit plugin's `security` plumbing bypassed the loader.
  • `createVirtualRefraktLoader` gains the same option for hosted environments.
  • `SiteLoaderOptions` adds `securityPolicy` so the lower-level loader honours it too.

Per-adapter wiring:

  • Astro `RefraktAstroOptions` extends with `security?: SecurityPolicy` and `variables?: Record<string, unknown>`. Integration forwards both into `createRefraktLoader` inside the runes-Vite-plugin callback.
  • Nuxt `RefraktNuxtOptions` same shape. Module forwards both.
  • Eleventy `createDataFile` config gains `security` + `variables` fields; threaded as positional args to `loadContent`.
  • Next.js adds `createNextLoader(options)` helper — a typed shorthand around `createRefraktLoader` that accepts the four common fields. Consumers using the raw loader can pass them directly.
  • HTML template's `build.ts` already calls `loadContent` directly — comment + docs explain how to thread the two positional args.

Docs updated on all five adapter pages: option tables now list `security` and `variables` with their default-and-purpose explanations. The `variables` value-type difference (`Record<string, unknown>` for non-SvelteKit; `Record<string, string>` source-text in SvelteKit) is documented per the spec.

Notes

For Eleventy/HTML the value type is the natural `Record<string, unknown>` (real JS values). For SvelteKit it stays `Record<string, string>` of raw source expressions — that's a Vite virtual-module-specific shape. Adapter docs make the distinction explicit.

End-to-end test-site verification deferred to SPEC-059.

Full workspace build clean; all 2652 tests pass; site builds clean.