@glw907/cairn-cms 0.60.1 → 0.62.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/CHANGELOG.md +78 -0
- package/dist/components/AdminLayout.svelte +22 -0
- package/dist/components/CairnAdmin.svelte +3 -0
- package/dist/components/CairnTidySettings.svelte +2 -2
- package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
- package/dist/components/EditPage.svelte +116 -39
- package/dist/components/HelpHome.svelte +824 -0
- package/dist/components/HelpHome.svelte.d.ts +22 -0
- package/dist/components/MarkdownHelpDialog.svelte +4 -15
- package/dist/components/client-ingest.d.ts +16 -8
- package/dist/components/client-ingest.js +12 -6
- package/dist/components/editor-media.js +16 -8
- package/dist/components/editor-placeholder.d.ts +4 -2
- package/dist/components/editor-tidy.d.ts +24 -12
- package/dist/components/editor-tidy.js +8 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/link-completion.d.ts +12 -6
- package/dist/components/link-completion.js +12 -6
- package/dist/components/markdown-directives.d.ts +9 -6
- package/dist/components/markdown-directives.js +9 -6
- package/dist/components/markdown-format.d.ts +7 -2
- package/dist/components/markdown-format.js +59 -28
- package/dist/components/markdown-reference.d.ts +8 -0
- package/dist/components/markdown-reference.js +22 -0
- package/dist/components/media-upload-outcome.d.ts +12 -6
- package/dist/components/objective-errors.d.ts +8 -4
- package/dist/components/objective-errors.js +8 -4
- package/dist/components/preview-doc.d.ts +4 -2
- package/dist/components/preview-doc.js +4 -2
- package/dist/components/spellcheck.d.ts +55 -29
- package/dist/components/spellcheck.js +39 -21
- package/dist/components/tidy-categorize.d.ts +20 -10
- package/dist/components/tidy-categorize.js +16 -8
- package/dist/components/tidy-validate.d.ts +12 -6
- package/dist/components/tidy-validate.js +20 -10
- package/dist/components/topbar-context.d.ts +4 -2
- package/dist/content/advisories.d.ts +56 -0
- package/dist/content/advisories.js +87 -0
- package/dist/content/compose.d.ts +4 -2
- package/dist/content/compose.js +1 -0
- package/dist/content/excerpt.js +4 -2
- package/dist/content/getting-started.d.ts +18 -0
- package/dist/content/getting-started.js +12 -0
- package/dist/content/links.d.ts +16 -8
- package/dist/content/links.js +12 -6
- package/dist/content/manifest.d.ts +36 -18
- package/dist/content/manifest.js +32 -16
- package/dist/content/media-refs.d.ts +4 -2
- package/dist/content/media-refs.js +4 -2
- package/dist/content/media-rewrite.d.ts +8 -4
- package/dist/content/media-rewrite.js +76 -38
- package/dist/content/schema.d.ts +20 -10
- package/dist/content/site-dictionary.d.ts +4 -2
- package/dist/content/site-dictionary.js +8 -4
- package/dist/content/types.d.ts +97 -42
- package/dist/delivery/content-index.d.ts +16 -8
- package/dist/delivery/feeds.js +4 -2
- package/dist/delivery/json-ld.d.ts +3 -0
- package/dist/delivery/json-ld.js +3 -0
- package/dist/delivery/manifest.d.ts +4 -2
- package/dist/delivery/manifest.js +4 -2
- package/dist/delivery/public-routes.d.ts +12 -6
- package/dist/delivery/public-routes.js +4 -2
- package/dist/delivery/seo-fields.d.ts +12 -6
- package/dist/delivery/seo-fields.js +8 -4
- package/dist/delivery/site-indexes.d.ts +4 -2
- package/dist/delivery/site-resolver.d.ts +4 -2
- package/dist/delivery/site-resolver.js +4 -2
- package/dist/doctor/cloudflare-api.d.ts +6 -0
- package/dist/doctor/cloudflare-api.js +6 -0
- package/dist/doctor/index.d.ts +12 -6
- package/dist/doctor/report.d.ts +3 -0
- package/dist/doctor/report.js +3 -0
- package/dist/doctor/run.d.ts +3 -0
- package/dist/doctor/run.js +3 -0
- package/dist/doctor/types.d.ts +10 -2
- package/dist/doctor/types.js +6 -0
- package/dist/doctor/wrangler-config.d.ts +7 -2
- package/dist/doctor/wrangler-config.js +3 -0
- package/dist/email.d.ts +4 -2
- package/dist/env.d.ts +0 -3
- package/dist/env.js +0 -3
- package/dist/github/branches.d.ts +4 -2
- package/dist/github/branches.js +4 -2
- package/dist/github/signing.d.ts +1 -1
- package/dist/github/signing.js +2 -2
- package/dist/log/events.d.ts +1 -1
- package/dist/media/bulk-delete-plan.d.ts +8 -4
- package/dist/media/config.d.ts +12 -6
- package/dist/media/config.js +16 -8
- package/dist/media/delivery-bucket.d.ts +4 -2
- package/dist/media/library-entry.d.ts +4 -2
- package/dist/media/library-entry.js +4 -2
- package/dist/media/manifest.d.ts +29 -15
- package/dist/media/manifest.js +29 -16
- package/dist/media/naming.d.ts +12 -6
- package/dist/media/naming.js +24 -12
- package/dist/media/orphan-scan.d.ts +4 -2
- package/dist/media/reconcile.d.ts +21 -11
- package/dist/media/reconcile.js +12 -6
- package/dist/media/reference.d.ts +8 -4
- package/dist/media/reference.js +12 -6
- package/dist/media/rewrite-plan.d.ts +12 -6
- package/dist/media/sniff.d.ts +4 -2
- package/dist/media/sniff.js +28 -14
- package/dist/media/store.d.ts +16 -8
- package/dist/media/store.js +4 -2
- package/dist/media/transform-url.d.ts +12 -6
- package/dist/media/transform-url.js +8 -4
- package/dist/media/usage.d.ts +8 -4
- package/dist/nav/site-config.d.ts +16 -8
- package/dist/render/component-grammar.d.ts +23 -10
- package/dist/render/component-grammar.js +19 -8
- package/dist/render/component-insert.d.ts +8 -4
- package/dist/render/component-insert.js +4 -2
- package/dist/render/component-reference.d.ts +4 -2
- package/dist/render/component-reference.js +4 -2
- package/dist/render/component-validate.d.ts +3 -0
- package/dist/render/component-validate.js +3 -0
- package/dist/render/glyph.d.ts +4 -2
- package/dist/render/glyph.js +4 -2
- package/dist/render/pipeline.d.ts +20 -10
- package/dist/render/pipeline.js +4 -2
- package/dist/render/registry.d.ts +40 -20
- package/dist/render/registry.js +16 -8
- package/dist/render/rehype-dispatch.d.ts +22 -8
- package/dist/render/rehype-dispatch.js +22 -8
- package/dist/render/remark-directives.d.ts +3 -0
- package/dist/render/remark-directives.js +3 -0
- package/dist/render/remark-figure.d.ts +4 -2
- package/dist/render/remark-figure.js +4 -2
- package/dist/render/resolve-links.d.ts +4 -2
- package/dist/render/resolve-links.js +4 -2
- package/dist/render/resolve-media.d.ts +16 -8
- package/dist/render/resolve-media.js +12 -6
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +9 -3
- package/dist/sveltekit/auth-routes.d.ts +3 -0
- package/dist/sveltekit/auth-routes.js +3 -0
- package/dist/sveltekit/cairn-admin.d.ts +16 -5
- package/dist/sveltekit/cairn-admin.js +26 -10
- package/dist/sveltekit/content-routes.d.ts +191 -86
- package/dist/sveltekit/content-routes.js +297 -107
- package/dist/sveltekit/editors-routes.d.ts +3 -0
- package/dist/sveltekit/editors-routes.js +3 -0
- package/dist/sveltekit/guard.d.ts +4 -2
- package/dist/sveltekit/guard.js +4 -2
- package/dist/sveltekit/https-required-page.d.ts +1 -1
- package/dist/sveltekit/https-required-page.js +1 -1
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/media-route.d.ts +1 -2
- package/dist/sveltekit/media-route.js +13 -8
- package/dist/sveltekit/nav-routes.d.ts +7 -2
- package/dist/sveltekit/nav-routes.js +3 -0
- package/dist/sveltekit/types.d.ts +4 -2
- package/dist/vite/index.d.ts +32 -16
- package/dist/vite/index.js +52 -26
- package/dist/vite/resolve-root.d.ts +8 -4
- package/dist/vite/resolve-root.js +4 -2
- package/package.json +7 -1
- package/src/lib/components/AdminLayout.svelte +22 -0
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +2 -2
- package/src/lib/components/ComponentForm.svelte +0 -1
- package/src/lib/components/EditPage.svelte +133 -41
- package/src/lib/components/HelpHome.svelte +850 -0
- package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
- package/src/lib/components/client-ingest.ts +20 -10
- package/src/lib/components/editor-media.ts +20 -10
- package/src/lib/components/editor-placeholder.ts +12 -6
- package/src/lib/components/editor-tidy.ts +28 -14
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/link-completion.ts +12 -6
- package/src/lib/components/markdown-directives.ts +13 -8
- package/src/lib/components/markdown-format.ts +63 -30
- package/src/lib/components/markdown-reference.ts +30 -0
- package/src/lib/components/media-upload-outcome.ts +12 -6
- package/src/lib/components/objective-errors.ts +16 -8
- package/src/lib/components/preview-doc.ts +4 -2
- package/src/lib/components/spellcheck.ts +79 -41
- package/src/lib/components/tidy-categorize.ts +28 -14
- package/src/lib/components/tidy-validate.ts +28 -14
- package/src/lib/components/topbar-context.ts +4 -2
- package/src/lib/content/advisories.ts +150 -0
- package/src/lib/content/compose.ts +5 -2
- package/src/lib/content/excerpt.ts +4 -2
- package/src/lib/content/getting-started.ts +31 -0
- package/src/lib/content/links.ts +16 -8
- package/src/lib/content/manifest.ts +36 -18
- package/src/lib/content/media-refs.ts +4 -2
- package/src/lib/content/media-rewrite.ts +100 -50
- package/src/lib/content/schema.ts +20 -10
- package/src/lib/content/site-dictionary.ts +8 -4
- package/src/lib/content/types.ts +97 -42
- package/src/lib/delivery/content-index.ts +16 -8
- package/src/lib/delivery/feeds.ts +4 -2
- package/src/lib/delivery/json-ld.ts +3 -0
- package/src/lib/delivery/manifest.ts +4 -2
- package/src/lib/delivery/public-routes.ts +16 -8
- package/src/lib/delivery/seo-fields.ts +12 -6
- package/src/lib/delivery/site-indexes.ts +4 -2
- package/src/lib/delivery/site-resolver.ts +4 -2
- package/src/lib/doctor/cloudflare-api.ts +6 -0
- package/src/lib/doctor/index.ts +12 -6
- package/src/lib/doctor/report.ts +3 -0
- package/src/lib/doctor/run.ts +3 -0
- package/src/lib/doctor/types.ts +10 -2
- package/src/lib/doctor/wrangler-config.ts +7 -2
- package/src/lib/email.ts +4 -2
- package/src/lib/env.ts +0 -3
- package/src/lib/github/branches.ts +4 -2
- package/src/lib/github/signing.ts +2 -2
- package/src/lib/log/events.ts +1 -0
- package/src/lib/media/bulk-delete-plan.ts +8 -4
- package/src/lib/media/config.ts +24 -12
- package/src/lib/media/delivery-bucket.ts +4 -2
- package/src/lib/media/library-entry.ts +4 -2
- package/src/lib/media/manifest.ts +33 -18
- package/src/lib/media/naming.ts +24 -12
- package/src/lib/media/orphan-scan.ts +4 -2
- package/src/lib/media/reconcile.ts +21 -11
- package/src/lib/media/reference.ts +12 -6
- package/src/lib/media/rewrite-plan.ts +12 -6
- package/src/lib/media/sniff.ts +28 -14
- package/src/lib/media/store.ts +16 -8
- package/src/lib/media/transform-url.ts +12 -6
- package/src/lib/media/usage.ts +8 -4
- package/src/lib/nav/site-config.ts +16 -8
- package/src/lib/render/component-grammar.ts +23 -10
- package/src/lib/render/component-insert.ts +8 -4
- package/src/lib/render/component-reference.ts +4 -2
- package/src/lib/render/component-validate.ts +3 -0
- package/src/lib/render/glyph.ts +4 -2
- package/src/lib/render/pipeline.ts +20 -10
- package/src/lib/render/registry.ts +44 -22
- package/src/lib/render/rehype-dispatch.ts +22 -8
- package/src/lib/render/remark-directives.ts +3 -0
- package/src/lib/render/remark-figure.ts +4 -2
- package/src/lib/render/resolve-links.ts +4 -2
- package/src/lib/render/resolve-media.ts +16 -8
- package/src/lib/sveltekit/admin-dispatch.ts +10 -4
- package/src/lib/sveltekit/auth-routes.ts +3 -0
- package/src/lib/sveltekit/cairn-admin.ts +37 -15
- package/src/lib/sveltekit/content-routes.ts +494 -197
- package/src/lib/sveltekit/editors-routes.ts +3 -0
- package/src/lib/sveltekit/guard.ts +4 -2
- package/src/lib/sveltekit/https-required-page.ts +1 -1
- package/src/lib/sveltekit/index.ts +3 -0
- package/src/lib/sveltekit/media-route.ts +13 -8
- package/src/lib/sveltekit/nav-routes.ts +7 -2
- package/src/lib/sveltekit/types.ts +4 -2
- package/src/lib/vite/index.ts +60 -30
- package/src/lib/vite/resolve-root.ts +8 -4
|
@@ -23,6 +23,9 @@ function parseRole(value: FormDataEntryValue | null): Role {
|
|
|
23
23
|
return value === 'owner' ? 'owner' : 'editor';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
26
29
|
export function createEditorRoutes() {
|
|
27
30
|
/** GET /admin/editors. Owner-only. Returns the allowlist and the acting owner's email. */
|
|
28
31
|
async function editorsLoad(event: RequestContext): Promise<{ editors: Editor[]; self: string }> {
|
|
@@ -102,9 +102,11 @@ export function createAuthGuard() {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* For a protected load/action: the session the guard already resolved, or a login redirect.
|
|
106
107
|
* The parameter is the minimal structural need (just `locals`), so every engine event shape
|
|
107
|
-
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
108
|
+
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
109
|
+
*/
|
|
108
110
|
export function requireSession(event: { locals: { editor?: Editor | null } }): Editor {
|
|
109
111
|
const editor = event.locals.editor;
|
|
110
112
|
if (!editor) throw redirect(303, '/admin/login');
|
|
@@ -9,7 +9,7 @@ import { renderStaticAdminPage } from './static-admin-page.js';
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Render the full HTML document for the HTTPS-required page.
|
|
12
|
-
* @param httpsUrl The same request rebuilt over https, offered as the one-click recovery link.
|
|
12
|
+
* @param httpsUrl - The same request rebuilt over https, offered as the one-click recovery link.
|
|
13
13
|
*/
|
|
14
14
|
export function httpsRequiredPage(httpsUrl: string): string {
|
|
15
15
|
const href = escapeHtml(httpsUrl);
|
|
@@ -17,13 +17,17 @@ import type { ResolvedAssetConfig } from '../media/config.js';
|
|
|
17
17
|
/** A 16-character lowercase hex content-hash prefix, validated before any R2 lookup. */
|
|
18
18
|
const HASH_RE = /^[0-9a-f]{16}$/;
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
20
|
+
/**
|
|
21
|
+
* The closed delivery extension allow-list. A filename ext outside this set is a 404 with no R2
|
|
22
|
+
* read, so the route can never serve a type it cannot vouch for.
|
|
23
|
+
*/
|
|
22
24
|
const DELIVERY_EXTS: ReadonlySet<string> = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif']);
|
|
23
25
|
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The load-bearing XSS control: set on every non-404 response, so a served object can never run as
|
|
25
28
|
* active content. `Content-Type` comes from the stored, server-validated metadata via
|
|
26
|
-
* `writeHttpMetadata`; these override or add to it.
|
|
29
|
+
* `writeHttpMetadata`; these override or add to it.
|
|
30
|
+
*/
|
|
27
31
|
function applySecurityHeaders(headers: Headers, etag: string): void {
|
|
28
32
|
headers.set('X-Content-Type-Options', 'nosniff');
|
|
29
33
|
headers.set('Content-Disposition', 'inline');
|
|
@@ -36,8 +40,10 @@ function applySecurityHeaders(headers: Headers, etag: string): void {
|
|
|
36
40
|
if (!headers.has('Content-Type')) headers.set('Content-Type', 'application/octet-stream');
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
43
|
+
/**
|
|
44
|
+
* True when the returned object carries a body (a full or ranged read), narrowing it to the body
|
|
45
|
+
* variant. R2 returns a body-less object on an `If-None-Match` hit.
|
|
46
|
+
*/
|
|
41
47
|
function hasBody(obj: DeliveryObject | DeliveryObjectBody): obj is DeliveryObjectBody {
|
|
42
48
|
return 'body' in obj && (obj as DeliveryObjectBody).body != null;
|
|
43
49
|
}
|
|
@@ -48,8 +54,7 @@ function hasBody(obj: DeliveryObject | DeliveryObjectBody): obj is DeliveryObjec
|
|
|
48
54
|
* The handler validates the hash and extension before any R2 call, derives the object key from the
|
|
49
55
|
* validated values only (never trusting the URL's fan-out), guards the Cloudflare Images self-loop,
|
|
50
56
|
* and sets the security headers on every served response.
|
|
51
|
-
*
|
|
52
|
-
* @param resolved the adapter's resolved media config; when media is off the handler always 404s.
|
|
57
|
+
* @param resolved - the adapter's resolved media config; when media is off the handler always 404s.
|
|
53
58
|
*/
|
|
54
59
|
export function createMediaRoute(resolved: ResolvedAssetConfig): RequestHandler {
|
|
55
60
|
return async (event) => {
|
|
@@ -29,11 +29,16 @@ export interface NavLoadData {
|
|
|
29
29
|
|
|
30
30
|
/** Injectable dependencies; tests stub the token mint to avoid signing a real key. */
|
|
31
31
|
export interface NavRoutesDeps {
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* Mint a GitHub App installation token from the Worker env. Defaults to the real signer.
|
|
34
|
+
* A bare string works too; the routes await whatever comes back.
|
|
35
|
+
*/
|
|
34
36
|
mintToken?: (env: GithubKeyEnv) => string | Promise<string>;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
37
42
|
export function createNavRoutes(runtime: CairnRuntime, deps: NavRoutesDeps = {}) {
|
|
38
43
|
const mintToken =
|
|
39
44
|
deps.mintToken ?? ((env: GithubKeyEnv) => cachedInstallationToken(appCredentials(runtime.backend, env)));
|
|
@@ -23,9 +23,11 @@ export interface PlatformContext<Env> {
|
|
|
23
23
|
context?: { waitUntil(promise: Promise<unknown>): void };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The structural core every engine event type extends, parameterized by the Worker env the
|
|
27
28
|
* surface reads. Each shared field is defined once here; the extensions add only what their
|
|
28
|
-
* surface needs (cookies, params, setHeaders).
|
|
29
|
+
* surface needs (cookies, params, setHeaders).
|
|
30
|
+
*/
|
|
29
31
|
export interface EventBase<Env> {
|
|
30
32
|
url: URL;
|
|
31
33
|
request: Request;
|
package/src/lib/vite/index.ts
CHANGED
|
@@ -10,15 +10,19 @@ import { writeFile, mkdir } from 'node:fs/promises';
|
|
|
10
10
|
import { dirname, join } from 'node:path';
|
|
11
11
|
import { resolveViteRoot } from './resolve-root.js';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
13
|
+
/**
|
|
14
|
+
* The key the cairnManifest plugin stashes its options under, so the write path can read them off the
|
|
15
|
+
* plugin instance in the consumer's loaded config without re-parsing the config file.
|
|
16
|
+
*/
|
|
15
17
|
const CAIRN_OPTIONS = Symbol.for('cairn-cms.manifest-options');
|
|
16
18
|
|
|
17
19
|
/** A cairnManifest plugin instance with its options stashed for the write path to read. */
|
|
18
20
|
type CairnManifestPlugin = Plugin & { [CAIRN_OPTIONS]?: CairnManifestOptions };
|
|
19
21
|
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@link cairnManifest}. Paths are app-root-absolute (the form `import.meta.glob` wants),
|
|
24
|
+
* so they match the build's own resolution.
|
|
25
|
+
*/
|
|
22
26
|
export interface CairnManifestOptions {
|
|
23
27
|
/** The module exporting the `cairn` adapter and the parsed `siteConfig`, app-root-absolute. */
|
|
24
28
|
configModule: string;
|
|
@@ -34,9 +38,11 @@ const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
|
34
38
|
/** The default committed manifest path, app-root-absolute. */
|
|
35
39
|
const DEFAULT_MANIFEST_PATH = '/src/content/.cairn/index.json';
|
|
36
40
|
|
|
37
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Build the virtual module source. In verify mode it throws on drift; in write mode it exports the
|
|
38
43
|
* serialized manifest as `result`. The module runs in the app graph, so its `import.meta.glob`,
|
|
39
|
-
* package, and `?raw` resolution is the build's own.
|
|
44
|
+
* package, and `?raw` resolution is the build's own.
|
|
45
|
+
*/
|
|
40
46
|
function virtualSource(opts: CairnManifestOptions, mode: 'verify' | 'write'): string {
|
|
41
47
|
const manifestPath = opts.manifestPath ?? DEFAULT_MANIFEST_PATH;
|
|
42
48
|
const globEntries = Object.entries(opts.content)
|
|
@@ -62,11 +68,13 @@ export const result = ${resultExpr};
|
|
|
62
68
|
`;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Evaluate a virtual module source inside the consumer's own Vite resolution, then return the
|
|
66
73
|
* module's `result`. It reuses the consumer's loaded config (so `$lib`, the config module,
|
|
67
74
|
* `import.meta.glob`, and `?raw` resolve exactly as the build does) and strips the cairnManifest
|
|
68
75
|
* plugin from the nested server's plugin list, so its buildStart never recurses. This runs at
|
|
69
|
-
* build time and in the bins, never in the request lifecycle.
|
|
76
|
+
* build time and in the bins, never in the request lifecycle.
|
|
77
|
+
*/
|
|
70
78
|
async function evalVirtual(source: string, root: string): Promise<string> {
|
|
71
79
|
const { createServer, loadConfigFromFile } = await import('vite');
|
|
72
80
|
// Load the consumer's real Vite config so the nested server inherits SvelteKit's resolution
|
|
@@ -91,38 +99,48 @@ async function evalVirtual(source: string, root: string): Promise<string> {
|
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* True for any plugin object whose name is the cairnManifest plugin, so the nested server drops it
|
|
95
104
|
* and cannot recurse into another buildStart. The consumer's plugin list may nest arrays and hold
|
|
96
|
-
* falsy slots, so guard the shape.
|
|
105
|
+
* falsy slots, so guard the shape.
|
|
106
|
+
*/
|
|
97
107
|
function isCairnManifestPlugin(p: unknown): boolean {
|
|
98
108
|
return !!p && typeof p === 'object' && 'name' in p && (p as { name?: unknown }).name === 'cairn-manifest';
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
|
|
102
113
|
* the nested verify server can never re-enter its buildStart. Vite supports (and flattens) nested
|
|
103
114
|
* plugin arrays, and findCairnOptions recurses into them, so a flat single-level filter would miss a
|
|
104
115
|
* cairnManifest nested inside a shared preset's sub-array and let it survive into the nested server.
|
|
105
|
-
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
116
|
+
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
117
|
+
*/
|
|
106
118
|
export function stripCairnManifest(plugins: PluginOption | PluginOption[]): PluginOption[] {
|
|
107
119
|
if (Array.isArray(plugins)) return plugins.flatMap(stripCairnManifest);
|
|
108
120
|
if (isCairnManifestPlugin(plugins)) return [];
|
|
109
121
|
return [plugins];
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
124
|
+
/**
|
|
125
|
+
* Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
|
|
126
|
+
* and the plugin share this; the spike proved it runs cleanly inside the consumer's config.
|
|
127
|
+
*/
|
|
114
128
|
export async function verifyManifestFromVite(opts: CairnManifestOptions, root: string): Promise<void> {
|
|
115
129
|
await evalVirtual(virtualSource(opts, 'verify'), root);
|
|
116
130
|
}
|
|
117
131
|
|
|
118
|
-
/**
|
|
119
|
-
*
|
|
132
|
+
/**
|
|
133
|
+
* Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
|
|
134
|
+
* resolution. The cairn-manifest bin (a later task) will call this and write the result.
|
|
135
|
+
*/
|
|
120
136
|
export async function buildManifestFromVite(opts: CairnManifestOptions, root: string): Promise<string> {
|
|
121
137
|
return evalVirtual(virtualSource(opts, 'write'), root);
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
/**
|
|
125
|
-
*
|
|
140
|
+
/**
|
|
141
|
+
* The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
|
|
142
|
+
* buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build.
|
|
143
|
+
*/
|
|
126
144
|
export function cairnManifest(opts: CairnManifestOptions): Plugin {
|
|
127
145
|
let root = process.cwd();
|
|
128
146
|
const plugin: CairnManifestPlugin = {
|
|
@@ -152,13 +170,15 @@ export function cairnManifest(opts: CairnManifestOptions): Plugin {
|
|
|
152
170
|
return plugin;
|
|
153
171
|
}
|
|
154
172
|
|
|
155
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* Regenerate the committed manifest from the consumer's corpus and write it to the configured
|
|
156
175
|
* manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
|
|
157
176
|
* Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
|
|
158
177
|
* reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
|
|
159
178
|
* module through the build's own resolution, and writes the serialized manifest under the Vite
|
|
160
179
|
* root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
|
|
161
|
-
* from the CLI shell.
|
|
180
|
+
* from the CLI shell.
|
|
181
|
+
*/
|
|
162
182
|
export async function writeManifest(cwd: string = process.cwd()): Promise<void> {
|
|
163
183
|
const { loadConfigFromFile } = await import('vite');
|
|
164
184
|
const loaded = await loadConfigFromFile({ command: 'build', mode: 'production' }, undefined, cwd);
|
|
@@ -181,8 +201,10 @@ export async function writeManifest(cwd: string = process.cwd()): Promise<void>
|
|
|
181
201
|
await writeFile(outPath, serialized);
|
|
182
202
|
}
|
|
183
203
|
|
|
184
|
-
/**
|
|
185
|
-
*
|
|
204
|
+
/**
|
|
205
|
+
* Walk a Vite plugins option (which may nest arrays, hold falsy slots, or be a thenable) and return
|
|
206
|
+
* the stashed cairnManifest options from the first matching plugin, or null if there is none.
|
|
207
|
+
*/
|
|
186
208
|
function findCairnOptions(plugins: unknown): CairnManifestOptions | null {
|
|
187
209
|
if (!plugins) return null;
|
|
188
210
|
if (Array.isArray(plugins)) {
|
|
@@ -198,8 +220,10 @@ function findCairnOptions(plugins: unknown): CairnManifestOptions | null {
|
|
|
198
220
|
return null;
|
|
199
221
|
}
|
|
200
222
|
|
|
201
|
-
/**
|
|
202
|
-
*
|
|
223
|
+
/**
|
|
224
|
+
* A minimal plugin that serves only the given virtual module source, for the nested SSR load. It
|
|
225
|
+
* carries no buildStart, so the nested server never recurses into the verify.
|
|
226
|
+
*/
|
|
203
227
|
function cairnVirtualOnly(source: string): Plugin {
|
|
204
228
|
return {
|
|
205
229
|
name: 'cairn-manifest-virtual',
|
|
@@ -220,15 +244,19 @@ export interface AdapterFacts {
|
|
|
220
244
|
repo?: string;
|
|
221
245
|
/** `cairn.sender.from`. */
|
|
222
246
|
from?: string;
|
|
223
|
-
/**
|
|
224
|
-
*
|
|
247
|
+
/**
|
|
248
|
+
* `cairn.assets.bucketBinding`, the media R2 binding name; undefined when the adapter declares no
|
|
249
|
+
* assets. The doctor's conditional media-bucket check reads it.
|
|
250
|
+
*/
|
|
225
251
|
mediaBucketBinding?: string;
|
|
226
252
|
}
|
|
227
253
|
|
|
228
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* Build the virtual module that reads only the adapter facts the doctor derives. It imports the
|
|
229
256
|
* configured config module and exports the string-typed `owner`, `repo`, `from`, and the media
|
|
230
257
|
* `bucketBinding` as JSON, so nothing else of the adapter (least of all a secret) crosses the
|
|
231
|
-
* boundary.
|
|
258
|
+
* boundary.
|
|
259
|
+
*/
|
|
232
260
|
function adapterFactsSource(opts: CairnManifestOptions): string {
|
|
233
261
|
return `
|
|
234
262
|
import { cairn } from ${JSON.stringify(opts.configModule)};
|
|
@@ -244,12 +272,14 @@ export const result = JSON.stringify(facts);
|
|
|
244
272
|
`;
|
|
245
273
|
}
|
|
246
274
|
|
|
247
|
-
/**
|
|
275
|
+
/**
|
|
276
|
+
* Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
|
|
248
277
|
* through the consumer's own Vite resolution, the same machinery the cairn-manifest bin uses.
|
|
249
278
|
* cairn-doctor calls this to fill inputs the operator did not pass. Derivation is best-effort:
|
|
250
279
|
* any failure (no Vite config, no cairnManifest plugin, a config module that throws) returns
|
|
251
280
|
* null, so the doctor degrades to flags instead of crashing. This runs only on the bin path,
|
|
252
|
-
* never in a Worker.
|
|
281
|
+
* never in a Worker.
|
|
282
|
+
*/
|
|
253
283
|
export async function readAdapterFacts(cwd: string = process.cwd()): Promise<AdapterFacts | null> {
|
|
254
284
|
try {
|
|
255
285
|
const { loadConfigFromFile } = await import('vite');
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// /vite surface (only src/lib/vite/index.ts is the package subpath; this sibling is internal).
|
|
3
3
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
5
|
+
/**
|
|
6
|
+
* The shape of `loadConfigFromFile`'s result that the root derivation reads: the config file's own
|
|
7
|
+
* path and its `root` field. Typed structurally so the helper is testable without a real load.
|
|
8
|
+
*/
|
|
7
9
|
export interface LoadedViteConfig {
|
|
8
10
|
/** The resolved path of the config file Vite loaded. */
|
|
9
11
|
path: string;
|
|
@@ -11,11 +13,13 @@ export interface LoadedViteConfig {
|
|
|
11
13
|
config: { root?: string };
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
|
|
15
18
|
* resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
|
|
16
19
|
* stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
|
|
17
20
|
* separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
|
|
18
|
-
* sets `root` reads and writes the manifest under the real app root.
|
|
21
|
+
* sets `root` reads and writes the manifest under the real app root.
|
|
22
|
+
*/
|
|
19
23
|
export function resolveViteRoot(loaded: LoadedViteConfig, cwd: string): string {
|
|
20
24
|
const root = loaded.config.root;
|
|
21
25
|
if (!root) return cwd;
|