uncial-cms 0.0.0
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 +618 -0
- package/dist/base64.d.ts +3 -0
- package/dist/base64.js +15 -0
- package/dist/cli/assert-clean-pages.d.ts +10 -0
- package/dist/cli/assert-clean-pages.js +153 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +3 -0
- package/dist/cli/doctor.d.ts +23 -0
- package/dist/cli/doctor.js +217 -0
- package/dist/cli/run.d.ts +4 -0
- package/dist/cli/run.js +99 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/define-site.d.ts +37 -0
- package/dist/define-site.js +24 -0
- package/dist/deploy-status.d.ts +55 -0
- package/dist/deploy-status.js +118 -0
- package/dist/document.d.ts +6 -0
- package/dist/document.js +23 -0
- package/dist/editor-controller.d.ts +76 -0
- package/dist/editor-controller.js +172 -0
- package/dist/editor-session.d.ts +60 -0
- package/dist/editor-session.js +63 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +14 -0
- package/dist/fit-image.d.ts +31 -0
- package/dist/fit-image.js +88 -0
- package/dist/github/adapter.d.ts +3 -0
- package/dist/github/adapter.js +135 -0
- package/dist/github/index.d.ts +3 -0
- package/dist/github/index.js +3 -0
- package/dist/github/pat.d.ts +7 -0
- package/dist/github/pat.js +35 -0
- package/dist/github/popup.d.ts +9 -0
- package/dist/github/popup.js +75 -0
- package/dist/index-actions.d.ts +74 -0
- package/dist/index-actions.js +147 -0
- package/dist/index-page.d.ts +19 -0
- package/dist/index-page.js +224 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +21 -0
- package/dist/local/adapter.d.ts +2 -0
- package/dist/local/adapter.js +63 -0
- package/dist/local/constants.d.ts +1 -0
- package/dist/local/constants.js +1 -0
- package/dist/local/index.d.ts +4 -0
- package/dist/local/index.js +4 -0
- package/dist/local/session.d.ts +2 -0
- package/dist/local/session.js +12 -0
- package/dist/local/vite.d.ts +8 -0
- package/dist/local/vite.js +243 -0
- package/dist/mount.d.ts +43 -0
- package/dist/mount.js +151 -0
- package/dist/paths/index.d.ts +17 -0
- package/dist/paths/index.js +47 -0
- package/dist/sentinel.d.ts +6 -0
- package/dist/sentinel.js +6 -0
- package/dist/served-url.d.ts +16 -0
- package/dist/served-url.js +19 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +30 -0
- package/dist/svelte/EditorPage.svelte +178 -0
- package/dist/svelte/EditorPage.svelte.d.ts +23 -0
- package/dist/svelte/index.d.ts +5 -0
- package/dist/svelte/index.js +5 -0
- package/dist/svelte/styles.d.ts +4 -0
- package/dist/sveltekit/index.d.ts +68 -0
- package/dist/sveltekit/index.js +98 -0
- package/dist/sveltekit/mapping.d.ts +1 -0
- package/dist/sveltekit/mapping.js +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.js +1 -0
- package/dist/upload-context.d.ts +24 -0
- package/dist/upload-context.js +10 -0
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +49 -0
- package/package.json +110 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `uncial-cms/sveltekit` — route factories for prerendered SvelteKit sites.
|
|
3
|
+
* `@sveltejs/kit` and `svelte` are peers of this subpath ONLY; the runtime
|
|
4
|
+
* root must not import them. This module runs at build time (prerender) and
|
|
5
|
+
* reads the local content directory with node:fs — do not import it from
|
|
6
|
+
* client-side code (the pure mapping lives in `./mapping.js` for that).
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { normalizeDocument } from 'uncial/core';
|
|
11
|
+
import { defaultMapPathToSource, defaultMapSourceToPath } from '../paths/index.js';
|
|
12
|
+
export { defaultMapPathToSource, defaultMapSourceToPath } from '../paths/index.js';
|
|
13
|
+
function resolveSite(opts) {
|
|
14
|
+
if (opts.site) {
|
|
15
|
+
return {
|
|
16
|
+
config: opts.site.config,
|
|
17
|
+
localContentDir: opts.site.localContentDir,
|
|
18
|
+
localOnly: opts.site.localOnly
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { config: opts.config, localContentDir: opts.localContentDir, localOnly: false };
|
|
22
|
+
}
|
|
23
|
+
function listContentSources(localContentDir, prefix = '') {
|
|
24
|
+
const sources = [];
|
|
25
|
+
for (const entry of readdirSync(join(localContentDir, prefix), { withFileTypes: true })) {
|
|
26
|
+
const rel = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
|
|
27
|
+
if (entry.isDirectory())
|
|
28
|
+
sources.push(...listContentSources(localContentDir, rel));
|
|
29
|
+
else if (entry.isFile() && entry.name.endsWith('.json'))
|
|
30
|
+
sources.push(rel);
|
|
31
|
+
}
|
|
32
|
+
return sources.sort();
|
|
33
|
+
}
|
|
34
|
+
function createEntries(opts, site) {
|
|
35
|
+
return () => listContentSources(site.localContentDir)
|
|
36
|
+
.map((rel) => {
|
|
37
|
+
const source = `${site.config.contentDir}/${rel}`;
|
|
38
|
+
return { path: defaultMapSourceToPath(source, site.config.contentDir), source };
|
|
39
|
+
})
|
|
40
|
+
.filter((entry) => !opts.exclude?.(entry))
|
|
41
|
+
.map(({ path }) => ({ path }));
|
|
42
|
+
}
|
|
43
|
+
/** The source for a site path, refusing one the site's `exclude` rules out. */
|
|
44
|
+
function resolveSource(opts, site, sitePath) {
|
|
45
|
+
const map = opts.mapPathToSource ?? ((path) => defaultMapPathToSource(path, site.config.contentDir));
|
|
46
|
+
const source = map(sitePath);
|
|
47
|
+
if (opts.exclude?.({ path: sitePath, source })) {
|
|
48
|
+
throw new Error(`Content path "${sitePath}" is excluded from this site's routes.`);
|
|
49
|
+
}
|
|
50
|
+
return source;
|
|
51
|
+
}
|
|
52
|
+
function schemaFor(opts, path) {
|
|
53
|
+
return typeof opts.schema === 'function' ? opts.schema(path) : opts.schema;
|
|
54
|
+
}
|
|
55
|
+
/** Repo-root-relative source → path relative to the content dir (for local FS reads). */
|
|
56
|
+
function contentDirRelative(source, contentDir) {
|
|
57
|
+
const prefix = `${contentDir}/`;
|
|
58
|
+
if (!source.startsWith(prefix)) {
|
|
59
|
+
throw new Error(`Source "${source}" is outside the content dir "${contentDir}".`);
|
|
60
|
+
}
|
|
61
|
+
return source.slice(prefix.length);
|
|
62
|
+
}
|
|
63
|
+
export function createContentHandlers(opts) {
|
|
64
|
+
const site = resolveSite(opts);
|
|
65
|
+
return {
|
|
66
|
+
entries: createEntries(opts, site),
|
|
67
|
+
load: async ({ params }) => {
|
|
68
|
+
const source = resolveSource(opts, site, params.path);
|
|
69
|
+
const rel = contentDirRelative(source, site.config.contentDir);
|
|
70
|
+
const raw = readFileSync(join(site.localContentDir, rel), 'utf-8');
|
|
71
|
+
const document = normalizeDocument(JSON.parse(raw), opts.blocks, schemaFor(opts, params.path));
|
|
72
|
+
return { document, meta: document.meta ?? {}, path: params.path };
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function createEditorHandlers(opts) {
|
|
77
|
+
const site = resolveSite(opts);
|
|
78
|
+
// A local-only site has no forge to commit to outside development, so its
|
|
79
|
+
// editor variants are development-only without anyone saying so.
|
|
80
|
+
const devOnly = opts.devOnly ?? site.localOnly;
|
|
81
|
+
const entries = createEntries(opts, site);
|
|
82
|
+
return {
|
|
83
|
+
// Prerendering a dynamic route walks `entries`: an empty list emits no editor pages.
|
|
84
|
+
// These routes are unlinked, so strict builds do not encounter them, while development
|
|
85
|
+
// servers still serve them on demand.
|
|
86
|
+
entries: () => (devOnly && !import.meta.env.DEV ? [] : entries()),
|
|
87
|
+
load: async ({ params }) => ({
|
|
88
|
+
sourcePath: resolveSource(opts, site, params.path),
|
|
89
|
+
pagePath: params.path
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function createIndexHandlers(opts) {
|
|
94
|
+
const config = opts.site ? opts.site.config : opts.config;
|
|
95
|
+
return {
|
|
96
|
+
load: async () => ({ config })
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defaultMapPathToSource, defaultMapSourceToPath } from '../paths/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defaultMapPathToSource, defaultMapSourceToPath } from '../paths/index.js';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface GitHubSiteConfig {
|
|
2
|
+
forge: 'github';
|
|
3
|
+
repo: string;
|
|
4
|
+
branch: string;
|
|
5
|
+
contentDir: string;
|
|
6
|
+
authWorkerUrl: string;
|
|
7
|
+
appSlug: string;
|
|
8
|
+
mediaDir?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LocalSiteConfig {
|
|
11
|
+
forge: 'local';
|
|
12
|
+
contentDir: string;
|
|
13
|
+
mediaDir?: string;
|
|
14
|
+
}
|
|
15
|
+
export type UncialCmsSiteConfig = GitHubSiteConfig | LocalSiteConfig;
|
|
16
|
+
export interface ForgeSession {
|
|
17
|
+
token: string;
|
|
18
|
+
expiresAt: number | null;
|
|
19
|
+
repo: string;
|
|
20
|
+
user: {
|
|
21
|
+
login: string;
|
|
22
|
+
name: string;
|
|
23
|
+
email: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export type SessionProvider = (config: UncialCmsSiteConfig) => Promise<ForgeSession>;
|
|
27
|
+
export interface ForgeAdapter {
|
|
28
|
+
authenticate(config: UncialCmsSiteConfig, provider: SessionProvider): Promise<ForgeSession>;
|
|
29
|
+
readFile(path: string): Promise<{
|
|
30
|
+
content: string;
|
|
31
|
+
sha: string;
|
|
32
|
+
}>;
|
|
33
|
+
writeFile(path: string, content: string | Uint8Array, opts: {
|
|
34
|
+
message: string;
|
|
35
|
+
sha?: string;
|
|
36
|
+
author: {
|
|
37
|
+
name: string;
|
|
38
|
+
email: string;
|
|
39
|
+
};
|
|
40
|
+
}): Promise<{
|
|
41
|
+
sha: string;
|
|
42
|
+
commitSha: string;
|
|
43
|
+
}>;
|
|
44
|
+
deleteFile(path: string, opts: {
|
|
45
|
+
message: string;
|
|
46
|
+
sha: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
listDir(path: string): Promise<Array<{
|
|
49
|
+
path: string;
|
|
50
|
+
type: 'file' | 'dir';
|
|
51
|
+
}>>;
|
|
52
|
+
commitStatus(commitSha: string): Promise<'pending' | 'success' | 'failure' | 'unknown'>;
|
|
53
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The active editor's forge, exposed so a block's in-editor UI (e.g. the Image
|
|
3
|
+
* block's Upload affordance) can reach the same authenticated adapter + author
|
|
4
|
+
* `mountEditorPage` uses — without threading them through the editor web
|
|
5
|
+
* component and every block's props.
|
|
6
|
+
*
|
|
7
|
+
* It is a single last-writer-wins slot: one page hosts one editor at a time.
|
|
8
|
+
* `createEditorController` sets it once the session is established;
|
|
9
|
+
* `mountEditorPage` clears it on teardown. A block reads it through
|
|
10
|
+
* {@link uploadImageAsset}, never directly.
|
|
11
|
+
*/
|
|
12
|
+
import type { ForgeAdapter, UncialCmsSiteConfig } from './types.js';
|
|
13
|
+
export interface ActiveForge {
|
|
14
|
+
adapter: ForgeAdapter;
|
|
15
|
+
author: {
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
};
|
|
19
|
+
/** The session's site config, so an upload can default its `mediaDir`. */
|
|
20
|
+
config: UncialCmsSiteConfig;
|
|
21
|
+
}
|
|
22
|
+
export declare function setActiveForge(forge: ActiveForge): void;
|
|
23
|
+
export declare function clearActiveForge(): void;
|
|
24
|
+
export declare function getActiveForge(): ActiveForge | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { SiteOptions } from '../define-site.js';
|
|
3
|
+
/**
|
|
4
|
+
* The forge the build targets, as a string literal in the bundle. An editor
|
|
5
|
+
* page gates on it statically so a local-only production build drops the whole
|
|
6
|
+
* editor stack rather than merely leaving it unrouted.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FORGE_DEFINE_KEY = "import.meta.env.UNCIAL_CMS_FORGE";
|
|
9
|
+
export declare function uncialCms(options: SiteOptions): Plugin[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `uncial-cms/vite` — the plugins a site installs from its Vite config. Node
|
|
3
|
+
* only: it resolves paths on disk and imports Vite's own types.
|
|
4
|
+
*/
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { createLocalVitePlugin } from '../local/vite.js';
|
|
7
|
+
/**
|
|
8
|
+
* The forge the build targets, as a string literal in the bundle. An editor
|
|
9
|
+
* page gates on it statically so a local-only production build drops the whole
|
|
10
|
+
* editor stack rather than merely leaving it unrouted.
|
|
11
|
+
*/
|
|
12
|
+
export const FORGE_DEFINE_KEY = 'import.meta.env.UNCIAL_CMS_FORGE';
|
|
13
|
+
/**
|
|
14
|
+
* The repository root the forge's paths are relative to. A site already declares
|
|
15
|
+
* the one pair of facts that fixes it: `contentDir` names the content directory
|
|
16
|
+
* from the repository root, `localContentDir` names the same directory on disk,
|
|
17
|
+
* so stripping the former's segments off the latter leaves the root above both.
|
|
18
|
+
*/
|
|
19
|
+
function repositoryRoot(options) {
|
|
20
|
+
const contentDir = trimSlashes(options.contentDir);
|
|
21
|
+
const local = resolve(options.localContentDir ?? options.contentDir);
|
|
22
|
+
const depth = contentDir === '' ? 0 : contentDir.split('/').length;
|
|
23
|
+
const root = resolve(local, ...Array.from({ length: depth }, () => '..'));
|
|
24
|
+
if (resolve(root, contentDir) !== local) {
|
|
25
|
+
throw new Error(`localContentDir must be where contentDir sits on disk: "${local}" does not end with "${contentDir}".`);
|
|
26
|
+
}
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
function trimSlashes(path) {
|
|
30
|
+
return path.replace(/^\/+|\/+$/g, '');
|
|
31
|
+
}
|
|
32
|
+
export function uncialCms(options) {
|
|
33
|
+
return [
|
|
34
|
+
createLocalVitePlugin({
|
|
35
|
+
root: repositoryRoot(options),
|
|
36
|
+
permittedRoots: [
|
|
37
|
+
trimSlashes(options.contentDir),
|
|
38
|
+
...(options.mediaDir ? [trimSlashes(options.mediaDir)] : [])
|
|
39
|
+
]
|
|
40
|
+
}),
|
|
41
|
+
{
|
|
42
|
+
name: 'uncial-cms:forge',
|
|
43
|
+
config(_config, env) {
|
|
44
|
+
const forge = env.command === 'serve' ? 'local' : options.github !== undefined ? 'github' : 'none';
|
|
45
|
+
return { define: { [FORGE_DEFINE_KEY]: JSON.stringify(forge) } };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uncial-cms",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/d-flood/uncial.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/d-flood/uncial/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://d-flood.github.io/uncial/",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "vite dev",
|
|
15
|
+
"build": "vite build",
|
|
16
|
+
"preview": "vite preview",
|
|
17
|
+
"prepare": "pnpm exec svelte-kit sync || true",
|
|
18
|
+
"package": "PACKAGE=1 svelte-kit sync && PACKAGE=1 svelte-package --input src/lib --output dist",
|
|
19
|
+
"prepack": "pnpm run package && pnpm exec publint",
|
|
20
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
21
|
+
"assert:clean-pages": "node dist/cli/bin.js assert-clean-pages",
|
|
22
|
+
"test:unit": "vitest --project server",
|
|
23
|
+
"test": "pnpm run test:unit -- --run && pnpm run test:browser -- --run && pnpm run test:e2e",
|
|
24
|
+
"test:e2e": "playwright test",
|
|
25
|
+
"test:cli": "node scripts/test-local-only-cli.mjs",
|
|
26
|
+
"test:browser": "vitest --project client"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"uncial-cms": "./dist/cli/bin.js"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"!dist/**/*.test.*",
|
|
38
|
+
"!dist/**/*.spec.*"
|
|
39
|
+
],
|
|
40
|
+
"type": "module",
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
"./package.json": "./package.json",
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"default": "./dist/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./session": {
|
|
50
|
+
"types": "./dist/editor-session.d.ts",
|
|
51
|
+
"default": "./dist/editor-session.js"
|
|
52
|
+
},
|
|
53
|
+
"./svelte": {
|
|
54
|
+
"types": "./dist/svelte/index.d.ts",
|
|
55
|
+
"svelte": "./dist/svelte/index.js",
|
|
56
|
+
"default": "./dist/svelte/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./github": {
|
|
59
|
+
"types": "./dist/github/index.d.ts",
|
|
60
|
+
"default": "./dist/github/index.js"
|
|
61
|
+
},
|
|
62
|
+
"./local": {
|
|
63
|
+
"types": "./dist/local/index.d.ts",
|
|
64
|
+
"default": "./dist/local/index.js"
|
|
65
|
+
},
|
|
66
|
+
"./paths": {
|
|
67
|
+
"types": "./dist/paths/index.d.ts",
|
|
68
|
+
"default": "./dist/paths/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./vite": {
|
|
71
|
+
"types": "./dist/vite/index.d.ts",
|
|
72
|
+
"default": "./dist/vite/index.js"
|
|
73
|
+
},
|
|
74
|
+
"./sveltekit": {
|
|
75
|
+
"types": "./dist/sveltekit/index.d.ts",
|
|
76
|
+
"default": "./dist/sveltekit/index.js"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@sveltejs/kit": "^2.0.0",
|
|
81
|
+
"svelte": "^5.0.0",
|
|
82
|
+
"uncial": "workspace:^"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@sveltejs/kit": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@playwright/test": "^1.58.2",
|
|
91
|
+
"@sveltejs/adapter-static": "^3.0.10",
|
|
92
|
+
"@sveltejs/kit": "^2.50.2",
|
|
93
|
+
"@sveltejs/package": "^2.5.7",
|
|
94
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
95
|
+
"@vitest/browser-playwright": "^4.0.18",
|
|
96
|
+
"playwright": "^1.58.2",
|
|
97
|
+
"publint": "^0.3.17",
|
|
98
|
+
"svelte": "^5.56.10",
|
|
99
|
+
"svelte-check": "^4.4.2",
|
|
100
|
+
"typescript": "^5.9.3",
|
|
101
|
+
"vite": "^7.3.1",
|
|
102
|
+
"vitest": "^4.0.18",
|
|
103
|
+
"vitest-browser-svelte": "^2.0.2"
|
|
104
|
+
},
|
|
105
|
+
"keywords": [
|
|
106
|
+
"svelte",
|
|
107
|
+
"cms",
|
|
108
|
+
"github"
|
|
109
|
+
]
|
|
110
|
+
}
|