@getvitops/emdash 0.3.9 → 1.0.0-alpha.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/CHANGELOG.md +128 -0
- package/README.md +9 -8
- package/dist/index.d.mts +41 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +184 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -9
- package/src/admin/DesignSystemEditorPage.tsx +51 -0
- package/src/admin/core-editor.d.ts +7 -0
- package/src/admin/index.tsx +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
# @getvitops/emdash
|
|
2
2
|
|
|
3
|
+
## 1.0.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
8
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
9
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
10
|
+
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
11
|
+
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
12
|
+
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
13
|
+
`page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
|
|
14
|
+
secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
|
|
15
|
+
reaches the git-tracked `site.json` through your repo's normal review path rather than
|
|
16
|
+
becoming a second, unreviewed source of truth. Until that's configured, the live half still
|
|
17
|
+
works; only the auto-commit half is skipped (and says so in the admin page). The save route
|
|
18
|
+
runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
|
|
19
|
+
synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
|
|
20
|
+
|
|
21
|
+
Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
|
|
22
|
+
EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
|
|
23
|
+
Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
|
|
24
|
+
auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
|
|
25
|
+
plugin-install time.
|
|
26
|
+
|
|
27
|
+
**Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
|
|
28
|
+
capability is now also granted whenever `designSystemEditor` is set, independent of the
|
|
29
|
+
existing `scripts: 'fragments'` option (which governs something unrelated — web-component
|
|
30
|
+
script delivery). No change if you don't use `designSystemEditor`.
|
|
31
|
+
|
|
32
|
+
Also new, supporting the above:
|
|
33
|
+
- `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
|
|
34
|
+
`/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
|
|
35
|
+
`design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
|
|
36
|
+
standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
|
|
37
|
+
into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
|
|
38
|
+
`mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
|
|
39
|
+
(re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
|
|
40
|
+
subtree" implementation shared by the dev endpoint and the new plugin route. New
|
|
41
|
+
`site.deployment.repo`/`branch` config fields.
|
|
42
|
+
- `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
|
|
43
|
+
wrapper over GitHub's Contents API.
|
|
44
|
+
- `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
|
|
45
|
+
unchanged — `/__vitops/design-system`) so the same element can target the new plugin
|
|
46
|
+
route instead of the dev-only Vite endpoint.
|
|
47
|
+
|
|
48
|
+
- 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
|
|
49
|
+
both projects' GA releases.
|
|
50
|
+
|
|
51
|
+
**Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
|
|
52
|
+
`emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
|
|
53
|
+
`^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
|
|
54
|
+
the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
|
|
55
|
+
code here needed to change, only the declared range.
|
|
56
|
+
|
|
57
|
+
The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
|
|
58
|
+
the version bump:
|
|
59
|
+
- `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
|
|
60
|
+
`css.format: 'tailwind'` without installing either, which threw on build.
|
|
61
|
+
- `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
|
|
62
|
+
`vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
|
|
63
|
+
the build never generated.
|
|
64
|
+
- `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
|
|
65
|
+
`site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
|
|
66
|
+
migration sequence explicitly, rather than relying solely on first-request auto-migration. This
|
|
67
|
+
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
68
|
+
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
69
|
+
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
70
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
71
|
+
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
72
|
+
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
73
|
+
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
74
|
+
Uncomment it once scaffolded into its own repo.
|
|
75
|
+
|
|
76
|
+
No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
|
|
77
|
+
to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
|
|
78
|
+
losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
|
|
79
|
+
now names the bad key instead of reporting a bare "Invalid input").
|
|
80
|
+
|
|
81
|
+
Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
|
|
82
|
+
(`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
|
|
83
|
+
has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
|
|
84
|
+
by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
|
|
85
|
+
hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
|
|
86
|
+
`postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
|
|
87
|
+
`node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
|
|
88
|
+
hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
|
|
89
|
+
(the same path `astro sync` would generate for real). Both are gitignored and only ever exist
|
|
90
|
+
inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
|
|
91
|
+
Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
|
|
92
|
+
evolving default) and making the template a real pnpm workspace member (would change how its
|
|
93
|
+
`@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
|
|
94
|
+
|
|
95
|
+
**Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
|
|
96
|
+
`AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
|
|
97
|
+
`.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
|
|
98
|
+
repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
|
|
99
|
+
compose with vite-plus's own `defineConfig`). A type-level shim
|
|
100
|
+
(`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
|
|
101
|
+
runtime `vp test` failure for this one file is still open.
|
|
102
|
+
|
|
103
|
+
### Patch Changes
|
|
104
|
+
|
|
105
|
+
- Updated dependencies [65a13cc]
|
|
106
|
+
- Updated dependencies [67e2088]
|
|
107
|
+
- Updated dependencies [0574388]
|
|
108
|
+
- Updated dependencies [c3c1bcb]
|
|
109
|
+
- Updated dependencies [0574388]
|
|
110
|
+
- Updated dependencies [0574388]
|
|
111
|
+
- Updated dependencies [0574388]
|
|
112
|
+
- Updated dependencies [1a3e811]
|
|
113
|
+
- Updated dependencies [2a0a559]
|
|
114
|
+
- Updated dependencies [0574388]
|
|
115
|
+
- Updated dependencies [dde3df3]
|
|
116
|
+
- Updated dependencies [d9f213b]
|
|
117
|
+
- Updated dependencies [0f8c018]
|
|
118
|
+
- Updated dependencies [3bbb8c3]
|
|
119
|
+
- @getvitops/generator@8.2.0-alpha.0
|
|
120
|
+
- @getvitops/astro@8.2.0-alpha.0
|
|
121
|
+
- @getvitops/utils@8.2.0-alpha.0
|
|
122
|
+
- @getvitops/core@8.2.0-alpha.0
|
|
123
|
+
|
|
124
|
+
## 0.3.10
|
|
125
|
+
|
|
126
|
+
### Patch Changes
|
|
127
|
+
|
|
128
|
+
- Updated dependencies
|
|
129
|
+
- @getvitops/utils@8.1.0
|
|
130
|
+
|
|
3
131
|
## 0.3.9
|
|
4
132
|
|
|
5
133
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ export default defineConfig({
|
|
|
36
36
|
});
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Tested against `emdash@0.
|
|
39
|
+
Tested against `emdash@1.0.x`.
|
|
40
40
|
|
|
41
41
|
## Rendering mode
|
|
42
42
|
|
|
@@ -127,13 +127,14 @@ export default defineConfig({
|
|
|
127
127
|
|
|
128
128
|
## Editor blocks (v1)
|
|
129
129
|
|
|
130
|
-
| Slash-menu entry | `_type` | Renders
|
|
131
|
-
| ---------------- | --------------------- |
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
130
|
+
| Slash-menu entry | `_type` | Renders |
|
|
131
|
+
| ---------------- | --------------------- | ----------------------------------------------- |
|
|
132
|
+
| Link or button | `vitops.actionLink` | sprite-icon `<a>`, styled `.link`/`.btn`/`.cta` |
|
|
133
|
+
| Image compare | `vitops.imageCompare` | `<wc-image-compare>` before/after slider |
|
|
134
|
+
| Copy snippet | `vitops.copyButton` | `<wc-copy>` with code/inline fallback |
|
|
135
|
+
| Banner | `vitops.banner` | `<wc-dismissable>` banner with tone colours |
|
|
136
|
+
| Disclosure | `vitops.details` | native `<details>`/`<summary>` |
|
|
137
|
+
| Carousel | `vitops.carousel` | `@getvitops/astro`'s `<Carousel />` |
|
|
137
138
|
|
|
138
139
|
All blocks render accessible markup that works without JavaScript; the
|
|
139
140
|
web components progressively enhance it.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { PluginDescriptor, PortableTextBlockConfig } from "emdash";
|
|
2
2
|
import { AstroIntegration } from "astro";
|
|
3
|
-
|
|
3
|
+
//#region src/design-system-route.d.ts
|
|
4
|
+
interface DesignSystemEditorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The site's own config, typically `import config from '../../site.json'
|
|
7
|
+
* with { type: 'json' }`. A static, build-time-bundled value — this plugin
|
|
8
|
+
* never reads it from disk at runtime (a deployed Worker has no
|
|
9
|
+
* filesystem), so an edit doesn't take effect here until the *next*
|
|
10
|
+
* deploy; that's exactly the point of the auto-commit half.
|
|
11
|
+
*/
|
|
12
|
+
config: unknown;
|
|
13
|
+
/** Which `designSystem.themes` entry this edits. Default: `'default'`. */
|
|
14
|
+
theme?: string;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
4
17
|
//#region src/blocks.d.ts
|
|
5
18
|
/** The v1 subset of EmDash Block Kit field elements used by these blocks. */
|
|
6
19
|
type BlockKitFieldDef = Extract<NonNullable<PortableTextBlockConfig['fields']>[number], {
|
|
@@ -9,7 +22,7 @@ type BlockKitFieldDef = Extract<NonNullable<PortableTextBlockConfig['fields']>[n
|
|
|
9
22
|
type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {
|
|
10
23
|
fields?: BlockKitFieldDef[];
|
|
11
24
|
};
|
|
12
|
-
declare const BLOCKS: PortableTextBlockDef[];
|
|
25
|
+
export declare const BLOCKS: PortableTextBlockDef[];
|
|
13
26
|
//#endregion
|
|
14
27
|
//#region src/hosting.d.ts
|
|
15
28
|
type HostingTarget = 'cloudflare' | 'node';
|
|
@@ -17,14 +30,21 @@ interface VitopsHostingOptions {
|
|
|
17
30
|
/** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */
|
|
18
31
|
target?: HostingTarget;
|
|
19
32
|
cloudflare?: {
|
|
20
|
-
/** D1 binding name in wrangler config (default 'DB'). */
|
|
21
|
-
|
|
33
|
+
/** D1 binding name in wrangler config (default 'DB'). */
|
|
34
|
+
dbBinding?: string;
|
|
35
|
+
/** R2 binding name in wrangler config (default 'MEDIA'). */
|
|
36
|
+
mediaBinding?: string;
|
|
37
|
+
/** D1 Sessions API read-replication mode (default 'auto'). */
|
|
22
38
|
session?: string;
|
|
23
39
|
};
|
|
24
40
|
node?: {
|
|
25
|
-
/** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
/** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */
|
|
42
|
+
databaseUrl?: string;
|
|
43
|
+
/** Full database descriptor override (e.g. postgres() from 'emdash/db'). */
|
|
44
|
+
database?: unknown;
|
|
45
|
+
/** Local uploads directory (default './data/uploads'). */
|
|
46
|
+
uploadsDir?: string;
|
|
47
|
+
/** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */
|
|
28
48
|
storage?: unknown;
|
|
29
49
|
};
|
|
30
50
|
}
|
|
@@ -36,10 +56,10 @@ interface VitopsHosting {
|
|
|
36
56
|
/** EmDash storage descriptor — pass to emdash({ storage }). */
|
|
37
57
|
storage: unknown;
|
|
38
58
|
}
|
|
39
|
-
declare function vitopsHosting(options?: VitopsHostingOptions): Promise<VitopsHosting>;
|
|
59
|
+
export declare function vitopsHosting(options?: VitopsHostingOptions): Promise<VitopsHosting>;
|
|
40
60
|
//#endregion
|
|
41
61
|
//#region src/index.d.ts
|
|
42
|
-
interface VitopsEmdashOptions {
|
|
62
|
+
export interface VitopsEmdashOptions {
|
|
43
63
|
/**
|
|
44
64
|
* How the web-component runtime scripts reach the page:
|
|
45
65
|
* - `'integration'` (default) — the site's layout renders `<Head />` from
|
|
@@ -53,14 +73,23 @@ interface VitopsEmdashOptions {
|
|
|
53
73
|
scripts?: 'integration' | 'fragments';
|
|
54
74
|
/** Public base path of the web-component bundles (default '/vitops'). */
|
|
55
75
|
wcBase?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Mounts a `<wc-theme-editor>` admin page at `/design-system`, backed by a
|
|
78
|
+
* plugin API route — see AGENTS.md's "EmDash plugin as the design-system
|
|
79
|
+
* editing surface" section and `design-system-route.ts`'s own docblock.
|
|
80
|
+
* Off by default. Requires the `network:request` capability (declared
|
|
81
|
+
* automatically when this is set) for the GitHub auto-commit half —
|
|
82
|
+
* reviewed once at plugin-install time, same as any other capability.
|
|
83
|
+
*/
|
|
84
|
+
designSystemEditor?: DesignSystemEditorOptions | false;
|
|
56
85
|
}
|
|
57
86
|
/**
|
|
58
87
|
* Descriptor factory — import in `astro.config.mjs` and pass to the emdash()
|
|
59
88
|
* integration's `plugins` array.
|
|
60
89
|
*/
|
|
61
|
-
declare function vitopsEmdash(options?: VitopsEmdashOptions): PluginDescriptor;
|
|
90
|
+
export declare function vitopsEmdash(options?: VitopsEmdashOptions): PluginDescriptor;
|
|
62
91
|
/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */
|
|
63
|
-
declare function createPlugin(options?: VitopsEmdashOptions): import("emdash").ResolvedPlugin<import("emdash").PluginStorageConfig>;
|
|
92
|
+
export declare function createPlugin(options?: VitopsEmdashOptions): import("emdash").ResolvedPlugin<import("emdash").PluginStorageConfig>;
|
|
64
93
|
//#endregion
|
|
65
|
-
export {
|
|
94
|
+
export { type BlockKitFieldDef, type DesignSystemEditorOptions, type HostingTarget, type PortableTextBlockDef, type VitopsHosting, type VitopsHostingOptions, createPlugin as default };
|
|
66
95
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/design-system-route.ts","../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;UAuDiB;;;;;;;;EAQf;;EAEA;;;;;KCjDU,mBAAmB,QAC7B,YAAY;EACV;;KAGQ,uBAAuB,KAAK;EACtC,SAAS;;qBAGE,QAAQ;;;KCOT;UAIK;;EAEf,SAAS;EACT;;IAEE;;IAEA;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;;UAIa;EACf,QAAQ;EACR,SAAS;;EAET;;EAEA;;wBAoBoB,cAAc,UAAS,uBAA4B,QAAQ;;;iBCxBhE;;;;;;;;;;;EAWf;;EAEA;;;;;;;;;EASA,qBAAqB;;;;;;wBAOP,aAAa,UAAS,sBAA2B;;wBAmBjD,aAAa,UAAS,uCAAwB,gCAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { definePlugin } from "emdash";
|
|
1
|
+
import { definePlugin, pluginResponse } from "emdash";
|
|
2
2
|
import { iconMap } from "@getvitops/utils";
|
|
3
|
+
import { designSystemPath, formatConfig, getAt, isConfig, mergePatch, resolveInput, setAt } from "@getvitops/generator/config";
|
|
4
|
+
import { buildReverseIndex, patchToOverrideCss, validateColorContract } from "@getvitops/generator/tokens";
|
|
5
|
+
import { commitFile } from "@getvitops/utils/git";
|
|
3
6
|
|
|
4
7
|
//#region src/icon-options.ts
|
|
5
8
|
/**
|
|
@@ -308,6 +311,148 @@ const BLOCKS = [
|
|
|
308
311
|
}
|
|
309
312
|
];
|
|
310
313
|
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/design-system-route.ts
|
|
316
|
+
/**
|
|
317
|
+
* The `vitops.design-system` plugin route — what `<wc-theme-editor>` probes
|
|
318
|
+
* and POSTs its patch to when mounted inside the EmDash admin (see this
|
|
319
|
+
* package's README and AGENTS.md's "EmDash plugin as the design-system
|
|
320
|
+
* editing surface" section). Mounted at
|
|
321
|
+
* `/_emdash/api/plugins/vitops/design-system`.
|
|
322
|
+
*
|
|
323
|
+
* Two responsibilities, matching the dev-only `@getvitops/vite` endpoint's
|
|
324
|
+
* contract byte-for-byte (`GET → { writable }`, `POST { patch } → { ok } |
|
|
325
|
+
* { errors }`) so `<wc-theme-editor>` needs no branch for which one it's
|
|
326
|
+
* talking to:
|
|
327
|
+
*
|
|
328
|
+
* 1. **Immediate**: persist the patch to `ctx.settings` — read back by the
|
|
329
|
+
* `page:fragments` hook in `index.ts`, which turns it into a site-wide
|
|
330
|
+
* `:root` override `<style>` on every public page. This is what makes an
|
|
331
|
+
* edit visible without a rebuild.
|
|
332
|
+
* 2. **Canonical**: auto-commit the merged file to `site.deployment.branch`
|
|
333
|
+
* (default `dev`) via `@getvitops/utils/git`'s `commitFile()`, so the edit
|
|
334
|
+
* reaches the git-tracked `site.json` through this repo's own normal
|
|
335
|
+
* `dev → promote → main` review path — never a silent second source of
|
|
336
|
+
* truth. A commit failure (missing token, misconfigured repo, a transient
|
|
337
|
+
* GitHub error) is logged and surfaces as a warning, but never fails the
|
|
338
|
+
* save: the immediate override already works regardless.
|
|
339
|
+
*
|
|
340
|
+
* **Assumes `config` is a full site `Config`** (has `organization`/`site`),
|
|
341
|
+
* not a bare `design-system.json` — true for any real EmDash site (it always
|
|
342
|
+
* has a `site.json`), and the only case this plugin already handles anywhere
|
|
343
|
+
* else (`vitopsHosting()`, `BLOCKS`).
|
|
344
|
+
*
|
|
345
|
+
* The GitHub token is read as `process.env.VITOPS_GITHUB_TOKEN` — matching
|
|
346
|
+
* `hosting.ts`'s own `process.env.HOSTING` read in this same package, rather
|
|
347
|
+
* than a `cloudflare:workers`-specific import: a native EmDash plugin runs
|
|
348
|
+
* in-process with no isolation boundary (EmDash's own docs say plugins "can
|
|
349
|
+
* access environment variables" directly), and this package already targets
|
|
350
|
+
* both the Cloudflare and Node hosting targets through one `process.env`
|
|
351
|
+
* convention (`nodejs_compat` on Workers maps bindings into `process.env`).
|
|
352
|
+
*/
|
|
353
|
+
const SETTINGS_KEY = "designSystemPatch";
|
|
354
|
+
function json(status, body) {
|
|
355
|
+
return pluginResponse({
|
|
356
|
+
status,
|
|
357
|
+
headers: { "content-type": "application/json" },
|
|
358
|
+
body: {
|
|
359
|
+
kind: "text",
|
|
360
|
+
value: JSON.stringify(body)
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/** Fetches the currently-stored patch, clearing it if the bundled `config`
|
|
365
|
+
* (the last real build) already reflects every value in it — see the
|
|
366
|
+
* module docblock's "self-clearing" note in AGENTS.md. */
|
|
367
|
+
async function currentPatch(ctx, ds) {
|
|
368
|
+
const stored = await ctx.settings.get(SETTINGS_KEY);
|
|
369
|
+
if (!stored) return null;
|
|
370
|
+
const patchedColors = stored.patch["colors"];
|
|
371
|
+
if (patchedColors != null && JSON.stringify(mergePatch(ds.colors, patchedColors)) === JSON.stringify(ds.colors)) {
|
|
372
|
+
await ctx.settings.delete(SETTINGS_KEY);
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
return stored;
|
|
376
|
+
}
|
|
377
|
+
function createDesignSystemRoute(opts) {
|
|
378
|
+
const theme = opts.theme ?? "default";
|
|
379
|
+
return {
|
|
380
|
+
methods: ["GET", "POST"],
|
|
381
|
+
response: "raw",
|
|
382
|
+
permission: "plugins:manage",
|
|
383
|
+
handler: async (ctx) => {
|
|
384
|
+
const { designSystem: ds, config } = resolveInput(opts.config, {
|
|
385
|
+
theme,
|
|
386
|
+
siteEnv: "production"
|
|
387
|
+
});
|
|
388
|
+
if (ctx.request.method === "GET") {
|
|
389
|
+
await currentPatch(ctx, ds);
|
|
390
|
+
return json(200, { writable: true });
|
|
391
|
+
}
|
|
392
|
+
const patch = ctx.input?.patch;
|
|
393
|
+
if (patch == null || typeof patch !== "object" || Array.isArray(patch)) return json(400, { errors: ["body must be { patch: object }"] });
|
|
394
|
+
const mergedColors = mergePatch(ds.colors, patch["colors"] ?? {});
|
|
395
|
+
const contrastErrors = validateColorContract(mergedColors);
|
|
396
|
+
if (contrastErrors.length) return json(400, { errors: contrastErrors });
|
|
397
|
+
await ctx.settings.set(SETTINGS_KEY, {
|
|
398
|
+
patch,
|
|
399
|
+
at: Date.now()
|
|
400
|
+
});
|
|
401
|
+
const warnings = [];
|
|
402
|
+
const repoSpec = config?.site.deployment?.repo;
|
|
403
|
+
const token = process.env["VITOPS_GITHUB_TOKEN"];
|
|
404
|
+
if (!repoSpec || !token) warnings.push(!repoSpec ? "design-system sync skipped: site.deployment.repo is not set — the change is live but not yet synced to source." : "design-system sync skipped: no GitHub token configured — the change is live but not yet synced to source.");
|
|
405
|
+
else {
|
|
406
|
+
const [owner, repo] = repoSpec.split("/");
|
|
407
|
+
if (!owner || !repo) warnings.push(`design-system sync skipped: site.deployment.repo "${repoSpec}" isn't "owner/name".`);
|
|
408
|
+
else if (!isConfig(opts.config)) warnings.push("design-system sync skipped: config is not a recognised site Config.");
|
|
409
|
+
else {
|
|
410
|
+
const at = designSystemPath(opts.config, theme);
|
|
411
|
+
const mergedSubtree = mergePatch(getAt(opts.config, at), patch);
|
|
412
|
+
const content = `${formatConfig(setAt(opts.config, at, mergedSubtree))}\n`;
|
|
413
|
+
const result = await commitFile({
|
|
414
|
+
owner,
|
|
415
|
+
repo,
|
|
416
|
+
branch: config.site.deployment?.branch ?? "dev",
|
|
417
|
+
path: "site.json",
|
|
418
|
+
content,
|
|
419
|
+
message: "Sync design-system edit from EmDash admin",
|
|
420
|
+
token,
|
|
421
|
+
fetchImpl: ctx.http && ((url, init) => ctx.http.fetch(url, init))
|
|
422
|
+
});
|
|
423
|
+
if (!result.ok) {
|
|
424
|
+
ctx.log.warn("design-system sync commit failed", { message: result.message });
|
|
425
|
+
warnings.push(`design-system sync failed (change is still live): ${result.message}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return json(200, {
|
|
430
|
+
ok: true,
|
|
431
|
+
warnings
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* The `page:fragments` contribution serving the stored patch's override
|
|
438
|
+
* CSS site-wide. Returns `null` (no fragment) when there's nothing unsynced
|
|
439
|
+
* — the same "no gated tag, no cost" discipline `@getvitops/astro`'s consent
|
|
440
|
+
* gate already follows.
|
|
441
|
+
*/
|
|
442
|
+
async function designSystemOverrideFragment(ctx, opts) {
|
|
443
|
+
const { designSystem: ds } = resolveInput(opts.config, { theme: opts.theme ?? "default" });
|
|
444
|
+
const stored = await currentPatch(ctx, ds);
|
|
445
|
+
if (!stored) return null;
|
|
446
|
+
const css = patchToOverrideCss(stored.patch, buildReverseIndex(ds));
|
|
447
|
+
if (!css) return null;
|
|
448
|
+
return {
|
|
449
|
+
kind: "html",
|
|
450
|
+
placement: "head",
|
|
451
|
+
html: `<style id="ds-overrides">${css}</style>`,
|
|
452
|
+
key: "vitops-ds-overrides"
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
311
456
|
//#endregion
|
|
312
457
|
//#region src/hosting.ts
|
|
313
458
|
const TARGETS = ["cloudflare", "node"];
|
|
@@ -370,7 +515,7 @@ async function vitopsHosting(options = {}) {
|
|
|
370
515
|
|
|
371
516
|
//#endregion
|
|
372
517
|
//#region package.json
|
|
373
|
-
var version = "0.
|
|
518
|
+
var version = "1.0.0-alpha.0";
|
|
374
519
|
|
|
375
520
|
//#endregion
|
|
376
521
|
//#region src/index.ts
|
|
@@ -388,10 +533,12 @@ var version = "0.3.9";
|
|
|
388
533
|
*
|
|
389
534
|
* ```js
|
|
390
535
|
* // astro.config.mjs
|
|
536
|
+
* import config from './site.json' with { type: 'json' };
|
|
537
|
+
*
|
|
391
538
|
* integrations: [
|
|
392
539
|
* react(),
|
|
393
540
|
* vitops({ css: {...} }),
|
|
394
|
-
* emdash({ database, storage, plugins: [vitopsEmdash()] }),
|
|
541
|
+
* emdash({ database, storage, plugins: [vitopsEmdash({ designSystemEditor: { config } })] }),
|
|
395
542
|
* ]
|
|
396
543
|
* ```
|
|
397
544
|
*/
|
|
@@ -407,6 +554,8 @@ var version = "0.3.9";
|
|
|
407
554
|
const VERSION = version;
|
|
408
555
|
const PLUGIN_ID = "vitops";
|
|
409
556
|
const FRAGMENTS_CAPABILITY = "hooks.page-fragments:register";
|
|
557
|
+
/** For the design-system-editor's GitHub auto-commit — see design-system-route.ts. */
|
|
558
|
+
const NETWORK_CAPABILITY = "network:request";
|
|
410
559
|
/**
|
|
411
560
|
* Descriptor factory — import in `astro.config.mjs` and pass to the emdash()
|
|
412
561
|
* integration's `plugins` array.
|
|
@@ -418,19 +567,20 @@ function vitopsEmdash(options = {}) {
|
|
|
418
567
|
format: "native",
|
|
419
568
|
entrypoint: "@getvitops/emdash",
|
|
420
569
|
componentsEntry: "@getvitops/emdash/astro",
|
|
570
|
+
...options.designSystemEditor ? { adminEntry: "@getvitops/emdash/admin" } : {},
|
|
421
571
|
options,
|
|
422
|
-
capabilities: options.scripts === "fragments" ? [FRAGMENTS_CAPABILITY] : []
|
|
572
|
+
capabilities: [...options.scripts === "fragments" || options.designSystemEditor ? [FRAGMENTS_CAPABILITY] : [], ...options.designSystemEditor ? [NETWORK_CAPABILITY] : []]
|
|
423
573
|
};
|
|
424
574
|
}
|
|
425
575
|
/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */
|
|
426
576
|
function createPlugin(options = {}) {
|
|
427
577
|
const scripts = options.scripts ?? "integration";
|
|
428
578
|
const wcBase = options.wcBase ?? "/vitops";
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
579
|
+
const editor = options.designSystemEditor || void 0;
|
|
580
|
+
const fragmentsEnabled = scripts === "fragments" || !!editor;
|
|
581
|
+
const pageFragments = async (_event, ctx) => {
|
|
582
|
+
const contributions = [];
|
|
583
|
+
if (scripts === "fragments") contributions.push({
|
|
434
584
|
kind: "html",
|
|
435
585
|
placement: "head",
|
|
436
586
|
html: `<link rel="modulepreload" href="${wcBase}/polyfills.js"><script type="module" src="${wcBase}/polyfills.js"><\/script><script type="module" src="${wcBase}/elements.js"><\/script>`,
|
|
@@ -441,8 +591,31 @@ function createPlugin(options = {}) {
|
|
|
441
591
|
src: `${wcBase}/deferred.js`,
|
|
442
592
|
defer: true,
|
|
443
593
|
key: "vitops-deferred"
|
|
444
|
-
}
|
|
445
|
-
|
|
594
|
+
});
|
|
595
|
+
if (editor) {
|
|
596
|
+
const fragment = await designSystemOverrideFragment(ctx, editor);
|
|
597
|
+
if (fragment) contributions.push(fragment);
|
|
598
|
+
}
|
|
599
|
+
return contributions;
|
|
600
|
+
};
|
|
601
|
+
return definePlugin({
|
|
602
|
+
id: PLUGIN_ID,
|
|
603
|
+
version: VERSION,
|
|
604
|
+
capabilities: [...fragmentsEnabled ? [FRAGMENTS_CAPABILITY] : [], ...editor ? [NETWORK_CAPABILITY] : []],
|
|
605
|
+
...editor ? { allowedHosts: ["api.github.com"] } : {},
|
|
606
|
+
...fragmentsEnabled ? { hooks: { "page:fragments": pageFragments } } : {},
|
|
607
|
+
admin: {
|
|
608
|
+
portableTextBlocks: BLOCKS,
|
|
609
|
+
...editor ? {
|
|
610
|
+
entry: "@getvitops/emdash/admin",
|
|
611
|
+
pages: [{
|
|
612
|
+
path: "/design-system",
|
|
613
|
+
label: "Design System",
|
|
614
|
+
icon: "palette"
|
|
615
|
+
}]
|
|
616
|
+
} : {}
|
|
617
|
+
},
|
|
618
|
+
...editor ? { routes: { "design-system": createDesignSystemRoute(editor) } } : {}
|
|
446
619
|
});
|
|
447
620
|
}
|
|
448
621
|
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/icon-options.ts","../src/blocks.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Semantic icon names, as Block Kit combobox options.\n *\n * Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a\n * generated copy, so that the package carried no runtime `@getvitops/*`\n * dependency and could version independently of the fixed toolchain group —\n * but that independence was already fiction: the blocks render from the\n * toolchain's SVG sprite (`<use href=\"…/icons.svg#icon-menu\">`), so an emdash\n * built against one icon vocabulary and a sprite built against another produce\n * empty boxes, silently and with no install-time error. Depending on the real\n * map makes the vocabulary one thing rather than two that can drift.\n *\n * `fa7` is the reference collection: every semantic name is defined there, and\n * the other sets are alternate resolutions of the same names.\n */\nimport { iconMap } from '@getvitops/utils';\n\nexport const SEMANTIC_ICON_OPTIONS: { label: string; value: string }[] = Object.keys(iconMap.fa7)\n .sort()\n // Label and value are the same string on purpose: the editor picks a semantic\n // name, and showing the set-specific glyph id would leak the icon set into\n // authored content — the thing the semantic layer exists to prevent.\n .map((name) => ({ label: name, value: name }));\n","/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\nimport { SEMANTIC_ICON_OPTIONS } from './icon-options.ts';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n /**\n * A link or button, optionally with an icon at either end.\n *\n * Flattened on purpose: Block Kit has no object-group element, so a shape\n * like `{ label, url }` becomes sibling fields. Same constraint the\n * marketing-blocks plugin documents.\n *\n * The icons are `combobox` — a searchable typeahead over a STATIC option\n * list, which is exactly what the semantic name list is. It is the closest\n * fit Block Kit offers: its element union is closed (no custom element, no\n * raw HTML), so `<wc-icon-picker>` cannot be mounted inside a block modal\n * however much nicer it would be. Revisit if EmDash opens that union.\n */\n type: 'vitops.actionLink',\n label: 'Link or button',\n icon: 'link',\n placeholder: 'A link or button, with optional icons',\n fields: [\n { type: 'text_input', action_id: 'label', label: 'Label' },\n { type: 'text_input', action_id: 'href', label: 'URL' },\n {\n type: 'select',\n action_id: 'variant',\n label: 'Style',\n initial_value: 'link',\n options: [\n { label: 'Link', value: 'link' },\n { label: 'Button', value: 'btn' },\n { label: 'Call to action', value: 'cta' },\n ],\n },\n {\n type: 'combobox',\n action_id: 'startIcon',\n label: 'Icon before the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'combobox',\n action_id: 'endIcon',\n label: 'Icon after the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'select',\n action_id: 'role',\n label: 'Colour role',\n initial_value: '',\n options: [\n { label: 'Default', value: '' },\n { label: 'Success', value: 'success' },\n { label: 'Danger', value: 'danger' },\n { label: 'Warning', value: 'warning' },\n { label: 'Info', value: 'info' },\n ],\n },\n { type: 'toggle', action_id: 'newTab', label: 'Open in a new tab', initial_value: false },\n ],\n },\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n {\n type: 'toggle',\n action_id: 'loop',\n label: 'Loop (clones the slides — autoplay implies it)',\n initial_value: false,\n },\n {\n type: 'text_input',\n action_id: 'aspect',\n label: 'Slide aspect ratio',\n initial_value: '16 / 9',\n },\n ],\n },\n];\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `vitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * integrations: [\n * react(),\n * vitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash()] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type { PluginDescriptor } from 'emdash';\nimport { BLOCKS } from './blocks.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY = 'hooks.page-fragments:register';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `vitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n options,\n capabilities: options.scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n\n hooks:\n scripts === 'fragments'\n ? {\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements\n // in head, deferred enhancements at end of body.\n 'page:fragments': async () => [\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n ],\n }\n : {},\n\n admin: {\n portableTextBlocks: BLOCKS,\n },\n });\n}\n\nexport default createPlugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/icon-options.ts","../src/blocks.ts","../src/design-system-route.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Semantic icon names, as Block Kit combobox options.\n *\n * Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a\n * generated copy, so that the package carried no runtime `@getvitops/*`\n * dependency and could version independently of the fixed toolchain group —\n * but that independence was already fiction: the blocks render from the\n * toolchain's SVG sprite (`<use href=\"…/icons.svg#icon-menu\">`), so an emdash\n * built against one icon vocabulary and a sprite built against another produce\n * empty boxes, silently and with no install-time error. Depending on the real\n * map makes the vocabulary one thing rather than two that can drift.\n *\n * `fa7` is the reference collection: every semantic name is defined there, and\n * the other sets are alternate resolutions of the same names.\n */\nimport { iconMap } from '@getvitops/utils';\n\nexport const SEMANTIC_ICON_OPTIONS: { label: string; value: string }[] = Object.keys(iconMap.fa7)\n .sort()\n // Label and value are the same string on purpose: the editor picks a semantic\n // name, and showing the set-specific glyph id would leak the icon set into\n // authored content — the thing the semantic layer exists to prevent.\n .map((name) => ({ label: name, value: name }));\n","/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\nimport { SEMANTIC_ICON_OPTIONS } from './icon-options.ts';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n /**\n * A link or button, optionally with an icon at either end.\n *\n * Flattened on purpose: Block Kit has no object-group element, so a shape\n * like `{ label, url }` becomes sibling fields. Same constraint the\n * marketing-blocks plugin documents.\n *\n * The icons are `combobox` — a searchable typeahead over a STATIC option\n * list, which is exactly what the semantic name list is. It is the closest\n * fit Block Kit offers: its element union is closed (no custom element, no\n * raw HTML), so `<wc-icon-picker>` cannot be mounted inside a block modal\n * however much nicer it would be. Revisit if EmDash opens that union.\n */\n type: 'vitops.actionLink',\n label: 'Link or button',\n icon: 'link',\n placeholder: 'A link or button, with optional icons',\n fields: [\n { type: 'text_input', action_id: 'label', label: 'Label' },\n { type: 'text_input', action_id: 'href', label: 'URL' },\n {\n type: 'select',\n action_id: 'variant',\n label: 'Style',\n initial_value: 'link',\n options: [\n { label: 'Link', value: 'link' },\n { label: 'Button', value: 'btn' },\n { label: 'Call to action', value: 'cta' },\n ],\n },\n {\n type: 'combobox',\n action_id: 'startIcon',\n label: 'Icon before the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'combobox',\n action_id: 'endIcon',\n label: 'Icon after the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'select',\n action_id: 'role',\n label: 'Colour role',\n initial_value: '',\n options: [\n { label: 'Default', value: '' },\n { label: 'Success', value: 'success' },\n { label: 'Danger', value: 'danger' },\n { label: 'Warning', value: 'warning' },\n { label: 'Info', value: 'info' },\n ],\n },\n { type: 'toggle', action_id: 'newTab', label: 'Open in a new tab', initial_value: false },\n ],\n },\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n {\n type: 'toggle',\n action_id: 'loop',\n label: 'Loop (clones the slides — autoplay implies it)',\n initial_value: false,\n },\n {\n type: 'text_input',\n action_id: 'aspect',\n label: 'Slide aspect ratio',\n initial_value: '16 / 9',\n },\n ],\n },\n];\n","/**\n * The `vitops.design-system` plugin route — what `<wc-theme-editor>` probes\n * and POSTs its patch to when mounted inside the EmDash admin (see this\n * package's README and AGENTS.md's \"EmDash plugin as the design-system\n * editing surface\" section). Mounted at\n * `/_emdash/api/plugins/vitops/design-system`.\n *\n * Two responsibilities, matching the dev-only `@getvitops/vite` endpoint's\n * contract byte-for-byte (`GET → { writable }`, `POST { patch } → { ok } |\n * { errors }`) so `<wc-theme-editor>` needs no branch for which one it's\n * talking to:\n *\n * 1. **Immediate**: persist the patch to `ctx.settings` — read back by the\n * `page:fragments` hook in `index.ts`, which turns it into a site-wide\n * `:root` override `<style>` on every public page. This is what makes an\n * edit visible without a rebuild.\n * 2. **Canonical**: auto-commit the merged file to `site.deployment.branch`\n * (default `dev`) via `@getvitops/utils/git`'s `commitFile()`, so the edit\n * reaches the git-tracked `site.json` through this repo's own normal\n * `dev → promote → main` review path — never a silent second source of\n * truth. A commit failure (missing token, misconfigured repo, a transient\n * GitHub error) is logged and surfaces as a warning, but never fails the\n * save: the immediate override already works regardless.\n *\n * **Assumes `config` is a full site `Config`** (has `organization`/`site`),\n * not a bare `design-system.json` — true for any real EmDash site (it always\n * has a `site.json`), and the only case this plugin already handles anywhere\n * else (`vitopsHosting()`, `BLOCKS`).\n *\n * The GitHub token is read as `process.env.VITOPS_GITHUB_TOKEN` — matching\n * `hosting.ts`'s own `process.env.HOSTING` read in this same package, rather\n * than a `cloudflare:workers`-specific import: a native EmDash plugin runs\n * in-process with no isolation boundary (EmDash's own docs say plugins \"can\n * access environment variables\" directly), and this package already targets\n * both the Cloudflare and Node hosting targets through one `process.env`\n * convention (`nodejs_compat` on Workers maps bindings into `process.env`).\n */\nimport { pluginResponse } from 'emdash';\nimport type { PageFragmentContribution, PluginContext, PluginRoute, RouteContext } from 'emdash';\nimport {\n designSystemPath,\n formatConfig,\n getAt,\n isConfig,\n mergePatch,\n resolveInput,\n setAt,\n} from '@getvitops/generator/config';\nimport {\n buildReverseIndex,\n patchToOverrideCss,\n validateColorContract,\n} from '@getvitops/generator/tokens';\nimport { commitFile } from '@getvitops/utils/git';\n\nexport interface DesignSystemEditorOptions {\n /**\n * The site's own config, typically `import config from '../../site.json'\n * with { type: 'json' }`. A static, build-time-bundled value — this plugin\n * never reads it from disk at runtime (a deployed Worker has no\n * filesystem), so an edit doesn't take effect here until the *next*\n * deploy; that's exactly the point of the auto-commit half.\n */\n config: unknown;\n /** Which `designSystem.themes` entry this edits. Default: `'default'`. */\n theme?: string;\n}\n\nconst SETTINGS_KEY = 'designSystemPatch';\n\ninterface StoredPatch {\n patch: Record<string, unknown>;\n at: number;\n}\n\nfunction json(status: number, body: unknown) {\n return pluginResponse({\n status,\n headers: { 'content-type': 'application/json' },\n body: { kind: 'text', value: JSON.stringify(body) },\n });\n}\n\n/** Fetches the currently-stored patch, clearing it if the bundled `config`\n * (the last real build) already reflects every value in it — see the\n * module docblock's \"self-clearing\" note in AGENTS.md. */\nasync function currentPatch(\n ctx: Pick<RouteContext, 'settings'>,\n ds: { colors: Record<string, unknown> },\n): Promise<StoredPatch | null> {\n const stored = await ctx.settings.get<StoredPatch>(SETTINGS_KEY);\n if (!stored) return null;\n const patchedColors = stored.patch['colors'];\n if (\n patchedColors != null &&\n JSON.stringify(mergePatch(ds.colors, patchedColors)) === JSON.stringify(ds.colors)\n ) {\n await ctx.settings.delete(SETTINGS_KEY);\n return null;\n }\n return stored;\n}\n\nexport function createDesignSystemRoute(opts: DesignSystemEditorOptions): PluginRoute {\n const theme = opts.theme ?? 'default';\n\n return {\n methods: ['GET', 'POST'],\n response: 'raw',\n permission: 'plugins:manage',\n handler: async (ctx: RouteContext) => {\n // `resolveInput` handles both a bare design-system.json and a full site\n // Config (following `extends`, resolving `themes[theme]`), the same\n // entry point every other consumer of a config goes through.\n const { designSystem: ds, config } = resolveInput(opts.config, {\n theme,\n siteEnv: 'production',\n });\n\n if (ctx.request.method === 'GET') {\n await currentPatch(ctx, ds);\n return json(200, { writable: true });\n }\n\n const body = ctx.input as { patch?: unknown } | undefined;\n const patch = body?.patch;\n if (patch == null || typeof patch !== 'object' || Array.isArray(patch))\n return json(400, { errors: ['body must be { patch: object }'] });\n\n const mergedColors = mergePatch(\n ds.colors,\n (patch as Record<string, unknown>)['colors'] ?? {},\n );\n const contrastErrors = validateColorContract(\n mergedColors as Parameters<typeof validateColorContract>[0],\n );\n if (contrastErrors.length) return json(400, { errors: contrastErrors });\n\n await ctx.settings.set(SETTINGS_KEY, {\n patch: patch as Record<string, unknown>,\n at: Date.now(),\n } satisfies StoredPatch);\n\n const warnings: string[] = [];\n const repoSpec = config?.site.deployment?.repo;\n const token = process.env['VITOPS_GITHUB_TOKEN'];\n if (!repoSpec || !token) {\n warnings.push(\n !repoSpec\n ? 'design-system sync skipped: site.deployment.repo is not set — the change is live but not yet synced to source.'\n : 'design-system sync skipped: no GitHub token configured — the change is live but not yet synced to source.',\n );\n } else {\n const [owner, repo] = repoSpec.split('/');\n if (!owner || !repo) {\n warnings.push(\n `design-system sync skipped: site.deployment.repo \"${repoSpec}\" isn't \"owner/name\".`,\n );\n } else if (!isConfig(opts.config)) {\n warnings.push('design-system sync skipped: config is not a recognised site Config.');\n } else {\n const at = designSystemPath(opts.config, theme);\n const mergedSubtree = mergePatch(getAt(opts.config, at), patch);\n const content = `${formatConfig(setAt(opts.config, at, mergedSubtree))}\\n`;\n const result = await commitFile({\n owner,\n repo,\n branch: config.site.deployment?.branch ?? 'dev',\n path: 'site.json',\n content,\n message: 'Sync design-system edit from EmDash admin',\n token,\n fetchImpl:\n ctx.http && ((url: string, init?: RequestInit) => ctx.http!.fetch(url, init)),\n });\n if (!result.ok) {\n ctx.log.warn('design-system sync commit failed', { message: result.message });\n warnings.push(`design-system sync failed (change is still live): ${result.message}`);\n }\n }\n }\n\n return json(200, { ok: true, warnings });\n },\n };\n}\n\n/**\n * The `page:fragments` contribution serving the stored patch's override\n * CSS site-wide. Returns `null` (no fragment) when there's nothing unsynced\n * — the same \"no gated tag, no cost\" discipline `@getvitops/astro`'s consent\n * gate already follows.\n */\nexport async function designSystemOverrideFragment(\n ctx: Pick<PluginContext, 'settings'>,\n opts: DesignSystemEditorOptions,\n): Promise<PageFragmentContribution | null> {\n const { designSystem: ds } = resolveInput(opts.config, { theme: opts.theme ?? 'default' });\n const stored = await currentPatch(ctx, ds);\n if (!stored) return null;\n\n const css = patchToOverrideCss(stored.patch, buildReverseIndex(ds));\n if (!css) return null;\n return {\n kind: 'html',\n placement: 'head',\n html: `<style id=\"ds-overrides\">${css}</style>`,\n key: 'vitops-ds-overrides',\n };\n}\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `vitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * import config from './site.json' with { type: 'json' };\n *\n * integrations: [\n * react(),\n * vitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash({ designSystemEditor: { config } })] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type {\n PageFragmentContribution,\n PageFragmentEvent,\n PluginCapability,\n PluginContext,\n PluginDescriptor,\n} from 'emdash';\nimport { BLOCKS } from './blocks.ts';\nimport {\n createDesignSystemRoute,\n designSystemOverrideFragment,\n type DesignSystemEditorOptions,\n} from './design-system-route.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\nexport type { DesignSystemEditorOptions } from './design-system-route.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY: PluginCapability = 'hooks.page-fragments:register';\n/** For the design-system-editor's GitHub auto-commit — see design-system-route.ts. */\nconst NETWORK_CAPABILITY: PluginCapability = 'network:request';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `vitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n /**\n * Mounts a `<wc-theme-editor>` admin page at `/design-system`, backed by a\n * plugin API route — see AGENTS.md's \"EmDash plugin as the design-system\n * editing surface\" section and `design-system-route.ts`'s own docblock.\n * Off by default. Requires the `network:request` capability (declared\n * automatically when this is set) for the GitHub auto-commit half —\n * reviewed once at plugin-install time, same as any other capability.\n */\n designSystemEditor?: DesignSystemEditorOptions | false;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n ...(options.designSystemEditor ? { adminEntry: '@getvitops/emdash/admin' } : {}),\n options,\n capabilities: [\n ...(options.scripts === 'fragments' || options.designSystemEditor\n ? [FRAGMENTS_CAPABILITY]\n : []),\n ...(options.designSystemEditor ? [NETWORK_CAPABILITY] : []),\n ] as PluginCapability[],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n const editor = options.designSystemEditor || undefined;\n const fragmentsEnabled = scripts === 'fragments' || !!editor;\n\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements in\n // head, deferred enhancements at end of body. Composed with the\n // design-system-editor's override fragment below — either half can be on\n // independently of the other.\n const pageFragments = async (\n _event: PageFragmentEvent,\n ctx: PluginContext,\n ): Promise<PageFragmentContribution[]> => {\n const contributions: PageFragmentContribution[] = [];\n if (scripts === 'fragments') {\n contributions.push(\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n );\n }\n if (editor) {\n const fragment = await designSystemOverrideFragment(ctx, editor);\n if (fragment) contributions.push(fragment);\n }\n return contributions;\n };\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: [\n ...(fragmentsEnabled ? [FRAGMENTS_CAPABILITY] : []),\n ...(editor ? [NETWORK_CAPABILITY] : []),\n ],\n ...(editor ? { allowedHosts: ['api.github.com'] } : {}),\n ...(fragmentsEnabled ? { hooks: { 'page:fragments': pageFragments } } : {}),\n\n admin: {\n portableTextBlocks: BLOCKS,\n ...(editor\n ? {\n entry: '@getvitops/emdash/admin',\n pages: [{ path: '/design-system', label: 'Design System', icon: 'palette' }],\n }\n : {}),\n },\n\n ...(editor\n ? {\n routes: {\n 'design-system': createDesignSystemRoute(editor),\n },\n }\n : {}),\n });\n}\n\nexport default createPlugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/emdash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
4
|
"description": "EmDash CMS native plugin for the Vitops design system — Portable Text block types editors can insert from the admin, rendered with the design-system web components and CSS framework.",
|
|
5
5
|
"homepage": "https://docs.vitops.ca",
|
|
6
6
|
"bugs": "https://github.com/getvitops/design-system/issues",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"src/astro",
|
|
16
|
+
"src/admin",
|
|
16
17
|
"CHANGELOG.md"
|
|
17
18
|
],
|
|
18
19
|
"type": "module",
|
|
@@ -23,35 +24,58 @@
|
|
|
23
24
|
"import": "./dist/index.mjs"
|
|
24
25
|
},
|
|
25
26
|
"./astro": "./src/astro/index.ts",
|
|
27
|
+
"./admin": "./src/admin/index.tsx",
|
|
26
28
|
"./package.json": "./package.json"
|
|
27
29
|
},
|
|
28
30
|
"publishConfig": {
|
|
29
31
|
"access": "public"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
|
-
"@getvitops/utils": "8.0.
|
|
34
|
+
"@getvitops/utils": "8.2.0-alpha.0",
|
|
35
|
+
"@getvitops/generator": "8.2.0-alpha.0"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@astrojs/cloudflare": "^14.0.0",
|
|
36
|
-
"@
|
|
39
|
+
"@astrojs/react": "^7.0.0",
|
|
40
|
+
"@emdash-cms/cloudflare": "^1.0.1",
|
|
41
|
+
"@types/react": "^19.3.0",
|
|
42
|
+
"@types/react-dom": "^19.3.0",
|
|
37
43
|
"astro": "^7.1.2",
|
|
38
|
-
"emdash": "^0.
|
|
39
|
-
"
|
|
40
|
-
"
|
|
44
|
+
"emdash": "^1.0.1",
|
|
45
|
+
"react": "^19.3.0",
|
|
46
|
+
"react-dom": "^19.3.0",
|
|
47
|
+
"@getvitops/astro": "8.2.0-alpha.0",
|
|
48
|
+
"@getvitops/core": "8.2.0-alpha.0"
|
|
41
49
|
},
|
|
42
50
|
"peerDependencies": {
|
|
43
51
|
"@astrojs/cloudflare": ">=14",
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
52
|
+
"@astrojs/react": ">=7",
|
|
53
|
+
"@emdash-cms/cloudflare": ">=1.0.0 <2",
|
|
54
|
+
"@getvitops/astro": ">=8.2.0-alpha.0",
|
|
55
|
+
"@getvitops/core": ">=8.2.0-alpha.0",
|
|
46
56
|
"astro": ">=7",
|
|
47
|
-
"emdash": ">=0.
|
|
57
|
+
"emdash": ">=1.0.0 <2",
|
|
58
|
+
"react": ">=19",
|
|
59
|
+
"react-dom": ">=19"
|
|
48
60
|
},
|
|
49
61
|
"peerDependenciesMeta": {
|
|
50
62
|
"@astrojs/cloudflare": {
|
|
51
63
|
"optional": true
|
|
52
64
|
},
|
|
65
|
+
"@astrojs/react": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
53
68
|
"@emdash-cms/cloudflare": {
|
|
54
69
|
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@getvitops/core": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"react": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"react-dom": {
|
|
78
|
+
"optional": true
|
|
55
79
|
}
|
|
56
80
|
},
|
|
57
81
|
"engines": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/design-system` admin page — mounts `<wc-theme-editor>` (a Lit web
|
|
3
|
+
* component, `@getvitops/core/editor`) pointed at this site's own
|
|
4
|
+
* `design-manifest.json` and the `vitops.design-system` plugin route (see
|
|
5
|
+
* `../design-system-route.ts` and AGENTS.md's "EmDash plugin as the
|
|
6
|
+
* design-system editing surface" section).
|
|
7
|
+
*
|
|
8
|
+
* `React.createElement` rather than JSX for the custom element: React's JSX
|
|
9
|
+
* typing for an unknown tag name needs an `IntrinsicElements` augmentation,
|
|
10
|
+
* and this ships as raw source into a consumer's own build (whatever React/
|
|
11
|
+
* JSX-runtime version and config they have) — `createElement('wc-theme-
|
|
12
|
+
* editor', props)` needs no such augmentation and works identically.
|
|
13
|
+
*
|
|
14
|
+
* The editor itself needs no rework: it already reads the `manifest`
|
|
15
|
+
* attribute and (as of this release) a `save-endpoint` attribute for where
|
|
16
|
+
* to probe/POST — this page only has to supply both and load the module
|
|
17
|
+
* that registers the custom element.
|
|
18
|
+
*/
|
|
19
|
+
import * as React from 'react';
|
|
20
|
+
|
|
21
|
+
const MANIFEST_URL = '/vitops/design-manifest.json';
|
|
22
|
+
const SAVE_ENDPOINT = '/_emdash/api/plugins/vitops/design-system';
|
|
23
|
+
|
|
24
|
+
export function DesignSystemEditorPage(): React.ReactElement {
|
|
25
|
+
const [ready, setReady] = React.useState(false);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
// Side-effect import: @getvitops/core/editor calls customElements.define
|
|
30
|
+
// on load. Dynamic, and guarded against a fast unmount, since this runs
|
|
31
|
+
// in a browser-only admin page with no SSR concern.
|
|
32
|
+
void import('@getvitops/core/editor').then(() => {
|
|
33
|
+
if (!cancelled) setReady(true);
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
cancelled = true;
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return React.createElement(
|
|
41
|
+
'section',
|
|
42
|
+
{ className: 'space-y-4' },
|
|
43
|
+
React.createElement('h1', { className: 'text-2xl font-semibold' }, 'Design System'),
|
|
44
|
+
ready
|
|
45
|
+
? React.createElement('wc-theme-editor', {
|
|
46
|
+
manifest: MANIFEST_URL,
|
|
47
|
+
'save-endpoint': SAVE_ENDPOINT,
|
|
48
|
+
})
|
|
49
|
+
: React.createElement('p', null, 'Loading editor…'),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@getvitops/core/editor` is a side-effect-only browser bundle (registers
|
|
3
|
+
* `<wc-theme-editor>` via `customElements.define`) with no exports and, on
|
|
4
|
+
* purpose, no published `.d.ts` — see that package's `exports` map. This is
|
|
5
|
+
* the ambient shim so `import('@getvitops/core/editor')` type-checks here.
|
|
6
|
+
*/
|
|
7
|
+
declare module '@getvitops/core/editor' {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin React entrypoint — mirrors `../astro/index.ts`'s "ships as raw
|
|
3
|
+
* source, EmDash's own build bundles it via `adminEntry`" shape: no `pack`
|
|
4
|
+
* entry for this file, `@getvitops/emdash/admin` maps straight at the
|
|
5
|
+
* source in `package.json`.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginAdminExports } from 'emdash';
|
|
8
|
+
import { DesignSystemEditorPage } from './DesignSystemEditorPage.tsx';
|
|
9
|
+
|
|
10
|
+
export const pages: PluginAdminExports['pages'] = {
|
|
11
|
+
'/design-system': DesignSystemEditorPage,
|
|
12
|
+
};
|