@norskvideo/ctl-dev-kit 0.2.2 → 0.2.3
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/docs-theme/README.md +82 -0
- package/docs-theme/components/Footer.astro +40 -0
- package/docs-theme/components/Sidebar.astro +73 -0
- package/docs-theme/components/SiteTitle.astro +52 -0
- package/docs-theme/content.config.ts +25 -0
- package/docs-theme/custom.css +108 -0
- package/docs-theme/fonts/Geist-LICENSE.txt +92 -0
- package/docs-theme/fonts/Geist.woff2 +0 -0
- package/docs-theme/fonts/GeistMono.woff2 +0 -0
- package/docs-theme/starlight.ts +162 -0
- package/docs-theme/sync.ts +145 -0
- package/package.json +2 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `docs-theme/` — the shared Norsk docs site theme
|
|
2
|
+
|
|
3
|
+
The parts of an Astro + Starlight docs site that must **never** differ between
|
|
4
|
+
Norsk products: the theme, the three component overrides, the content schema,
|
|
5
|
+
and the Astro/Starlight configuration itself. A product's site owns only what
|
|
6
|
+
genuinely differs — its content, its sidebar, its origin and its two names.
|
|
7
|
+
|
|
8
|
+
custom.css the palette + self-hosted Geist faces
|
|
9
|
+
fonts/ the woff2 those @font-face rules point at
|
|
10
|
+
components/Footer.astro the "captured against" line on a generated guide
|
|
11
|
+
components/Sidebar.astro the "Built against" versions footer
|
|
12
|
+
components/SiteTitle.astro the masthead wordmark, decoupled from the tab title
|
|
13
|
+
content.config.ts the docs collection, incl. `capturedAgainst`
|
|
14
|
+
starlight.ts `norskDocsSite()` — the whole Astro config bar the site's own parts
|
|
15
|
+
sync.ts the writer and the drift gate
|
|
16
|
+
|
|
17
|
+
## Consuming it
|
|
18
|
+
|
|
19
|
+
A docs site's `astro.config.mjs` becomes its sidebar plus five lines:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { defineConfig } from "astro/config";
|
|
23
|
+
import { isPublicBuild, norskDocsSite } from "./src/starlight.ts";
|
|
24
|
+
|
|
25
|
+
export default defineConfig(
|
|
26
|
+
norskDocsSite({
|
|
27
|
+
origin: "https://ctl.norsk.video",
|
|
28
|
+
tabTitle: "Norsk", // browser tab suffix — tabs truncate
|
|
29
|
+
wordmark: "norsk-ctl", // masthead — names the tool in hand
|
|
30
|
+
buildVersions: [{ label: "norsk-ctl", value: version }],
|
|
31
|
+
sidebar: [...],
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`norskDocsSite()` supplies the two-build shape (`dist-embedded` at base `/docs`
|
|
37
|
+
for the copy baked into a product binary, `dist-public` at base `/` for the
|
|
38
|
+
site on its own domain, selected by `ASTRO_BUILD=public`), the /docs-prefix
|
|
39
|
+
strip that makes the public build's absolute links work, the mermaid pipeline,
|
|
40
|
+
the favicon convention, and the component + CSS wiring.
|
|
41
|
+
|
|
42
|
+
## Why the files are copied into the site, not imported from here
|
|
43
|
+
|
|
44
|
+
A docs site is a **standalone package** — its own `bun.lock`, its own
|
|
45
|
+
`node_modules`, outside the monorepo's workspaces, because Astro's dependency
|
|
46
|
+
tree has no business in a product install. It therefore cannot depend on this
|
|
47
|
+
package the way every other dev-kit consumer does. That is measured, not
|
|
48
|
+
assumed: adding `"@norskvideo/ctl-dev-kit": "file:../../dev-kit"` to norsk-ctl's
|
|
49
|
+
website and installing fails with
|
|
50
|
+
|
|
51
|
+
error: @norskvideo/ctl-sdk@workspace:* failed to resolve
|
|
52
|
+
|
|
53
|
+
because bun must then resolve this package's own `workspace:*` dependencies
|
|
54
|
+
from a directory that is not in a workspace.
|
|
55
|
+
|
|
56
|
+
So the theme is a **forced copy**, handled the way this package already handles
|
|
57
|
+
every forced copy — canonical bytes here, a writer, and a gate:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts sync <site>
|
|
61
|
+
bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts check <site>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`<site>` is the directory holding `astro.config.mjs`; the copies land in its
|
|
65
|
+
`src/`, so this directory mirrors a site's `src/` and the path map is an
|
|
66
|
+
identity. A site runs `check` in its own test suite (norsk-ctl:
|
|
67
|
+
`website/tests/theme-drift.test.ts`, which `bun run check` runs), so a
|
|
68
|
+
hand-edited copy fails before it is published.
|
|
69
|
+
|
|
70
|
+
**Keeping the copies at those exact paths is load-bearing.** Astro derives a
|
|
71
|
+
component's scoped-style class from its path, so moving `Footer.astro` rewrites
|
|
72
|
+
a class attribute on every page of the site.
|
|
73
|
+
|
|
74
|
+
## Not yet
|
|
75
|
+
|
|
76
|
+
- **`conventions/check-drift.ts` does not know about this directory.** Its gate
|
|
77
|
+
runs at a product repo's root against files at fixed repo-root paths; a docs
|
|
78
|
+
site is a nested package a repo may or may not have. When the first product
|
|
79
|
+
repo outside this monorepo grows a site, the two gates should become one —
|
|
80
|
+
`check-drift` gaining a "if `website/astro.config.mjs` exists, check the theme"
|
|
81
|
+
clause is the obvious shape.
|
|
82
|
+
- **`create-product` does not render a site.** No generated repo has one yet.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Starlight's footer plus the "captured against" line on a generated guide.
|
|
4
|
+
*
|
|
5
|
+
* The test-driven guides every Norsk docs site carries are captured by a
|
|
6
|
+
* Playwright run against a real stack and republished from a cache, so a page
|
|
7
|
+
* can be older than the product it documents — some guides ride every release,
|
|
8
|
+
* the expensive ones a weekly run. The shared renderer writes what the run knew
|
|
9
|
+
* into the page's front matter (`capturedAgainst`, see content.config.ts) and
|
|
10
|
+
* this says it on the page: "Captured 2026-09-17 against norsk-mgr 0.1.0-…". A
|
|
11
|
+
* page without the field (hand-written, or captured before the field existed)
|
|
12
|
+
* shows nothing extra.
|
|
13
|
+
*/
|
|
14
|
+
import Default from "@astrojs/starlight/components/Footer.astro";
|
|
15
|
+
|
|
16
|
+
const against = Astro.locals.starlightRoute.entry.data.capturedAgainst;
|
|
17
|
+
const parts: string[] = [];
|
|
18
|
+
if (against?.norskMgr) parts.push(`norsk-mgr ${against.norskMgr}`);
|
|
19
|
+
if (against?.norsk) parts.push(`Norsk Manager ${against.norsk}`);
|
|
20
|
+
const line = against ? `Captured ${against.when}${parts.length ? ` against ${parts.join(", ")}` : ""}` : null;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
line && (
|
|
25
|
+
<p class="captured-against">
|
|
26
|
+
{line} — every command and screenshot on this page is from that run.
|
|
27
|
+
</p>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
<Default><slot /></Default>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.captured-against {
|
|
34
|
+
margin-top: 3rem;
|
|
35
|
+
padding-top: 1rem;
|
|
36
|
+
border-top: 1px solid var(--sl-color-hairline-light);
|
|
37
|
+
font-size: var(--sl-text-sm);
|
|
38
|
+
color: var(--sl-color-gray-3);
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Starlight's sidebar plus a footer stating what this docs build was built
|
|
4
|
+
* against. Every product site wants this line and none of them should word it
|
|
5
|
+
* differently, so the markup is shared and only the rows differ: the site
|
|
6
|
+
* passes them to `norskDocsSite({ buildVersions })`, which injects them as
|
|
7
|
+
* `__BUILD_VERSIONS__` (see starlight.ts on why a vite define rather than an
|
|
8
|
+
* import). They are read at build time from the product's own files, so they
|
|
9
|
+
* cannot drift from what ships the way a hand-written literal in MDX did.
|
|
10
|
+
*/
|
|
11
|
+
import Default from "@astrojs/starlight/components/Sidebar.astro";
|
|
12
|
+
|
|
13
|
+
// Injected by vite.define in starlight.ts — see the note there on why this is
|
|
14
|
+
// not resolved from within the component.
|
|
15
|
+
declare const __BUILD_VERSIONS__: Array<{ label: string; value: string; splitOnLastColon?: boolean }>;
|
|
16
|
+
// The repo/tag split keeps a long image pin wrapping sensibly in a narrow
|
|
17
|
+
// sidebar: `norskvideo/x:tag` breaks after the colon rather than mid-word.
|
|
18
|
+
const rows = __BUILD_VERSIONS__.map((v) => ({
|
|
19
|
+
label: v.label,
|
|
20
|
+
parts: v.splitOnLastColon ? v.value.split(/:(?=[^:]*$)/) : [v.value],
|
|
21
|
+
}));
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<Default><slot /></Default>
|
|
25
|
+
|
|
26
|
+
<div class="build-versions">
|
|
27
|
+
<p class="build-versions__heading">Built against</p>
|
|
28
|
+
<dl>
|
|
29
|
+
{/* One line per row on purpose: whitespace between expressions inside a
|
|
30
|
+
template expression survives into the emitted <code>, and a stray
|
|
31
|
+
space in the middle of an image pin is a rendering change. */}
|
|
32
|
+
{rows.map((row) => (
|
|
33
|
+
<Fragment><dt>{row.label}</dt><dd><code>{row.parts[0]}{row.parts.length > 1 && <Fragment>:<wbr />{row.parts[1]}</Fragment>}</code></dd></Fragment>
|
|
34
|
+
))}
|
|
35
|
+
</dl>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.build-versions {
|
|
40
|
+
margin-top: 1.5rem;
|
|
41
|
+
padding-top: 1rem;
|
|
42
|
+
border-top: 1px solid var(--sl-color-hairline-light);
|
|
43
|
+
font-size: var(--sl-text-xs);
|
|
44
|
+
color: var(--sl-color-gray-3);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.build-versions__heading {
|
|
48
|
+
margin: 0 0 0.5rem;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
letter-spacing: 0.05em;
|
|
52
|
+
color: var(--sl-color-gray-2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.build-versions dl {
|
|
56
|
+
margin: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.build-versions dt {
|
|
60
|
+
margin-top: 0.5rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.build-versions dd {
|
|
64
|
+
margin: 0.125rem 0 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.build-versions code {
|
|
68
|
+
font-size: var(--sl-text-xs);
|
|
69
|
+
overflow-wrap: anywhere;
|
|
70
|
+
background: none;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The masthead wordmark, top-left of every page.
|
|
4
|
+
*
|
|
5
|
+
* Exists only to say something different from `starlight.title`. Starlight
|
|
6
|
+
* drives the masthead and the browser-tab suffix from that single value, and
|
|
7
|
+
* the two want different things: the tab wants the short family name because
|
|
8
|
+
* tabs truncate, the masthead wants to name the tool the reader is holding.
|
|
9
|
+
* `norskDocsSite({ tabTitle, wordmark })` is where a site says both, and the
|
|
10
|
+
* wordmark reaches here as `__SITE_WORDMARK__`.
|
|
11
|
+
*
|
|
12
|
+
* The wordmark is the binary name, deliberately. Everything under it — the
|
|
13
|
+
* sidebar, the commands, the paths — says the binary's name, and a masthead
|
|
14
|
+
* reading anything else would be a second name for one thing: it splits
|
|
15
|
+
* searches and makes the reader learn a mapping. How to SAY it is a real
|
|
16
|
+
* question, and it is answered in prose on the index page rather than by
|
|
17
|
+
* renaming the product.
|
|
18
|
+
*
|
|
19
|
+
* Not a wrapper around Starlight's own SiteTitle, because the text it renders
|
|
20
|
+
* comes from `Astro.locals.starlightRoute.siteTitle` and mutating that would
|
|
21
|
+
* also move the <title> — the exact coupling this override is here to break.
|
|
22
|
+
* Reimplemented instead, which is cheap: the upstream component is a link, a
|
|
23
|
+
* span and some logo handling, and no logo is configured here.
|
|
24
|
+
*/
|
|
25
|
+
declare const __SITE_WORDMARK__: string;
|
|
26
|
+
|
|
27
|
+
const { siteTitleHref } = Astro.locals.starlightRoute;
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<a href={siteTitleHref} class="site-title sl-flex">
|
|
31
|
+
<span translate="no">{__SITE_WORDMARK__}</span>
|
|
32
|
+
</a>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
/* Mirrors upstream SiteTitle.astro. Same layer, so a Starlight upgrade that
|
|
36
|
+
restyles the masthead still applies here. */
|
|
37
|
+
@layer starlight.core {
|
|
38
|
+
.site-title {
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: var(--sl-nav-gap);
|
|
41
|
+
font-size: var(--sl-text-h4);
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: var(--sl-color-text-accent);
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
span {
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineCollection, z } from "astro:content";
|
|
2
|
+
import { docsLoader } from "@astrojs/starlight/loaders";
|
|
3
|
+
import { docsSchema } from "@astrojs/starlight/schema";
|
|
4
|
+
|
|
5
|
+
// The docs content collection every Norsk product site declares. Shared so a
|
|
6
|
+
// page authored for one site is loadable by another and the guide renderer has
|
|
7
|
+
// one schema to satisfy.
|
|
8
|
+
//
|
|
9
|
+
// `capturedAgainst` is what the test-driven guides' renderer writes into a
|
|
10
|
+
// generated page's front matter (`renderMarkdown` in the shared guide
|
|
11
|
+
// renderer): the versions the page was captured against and the capture date.
|
|
12
|
+
// Footer.astro renders it; a hand-written page simply has none. Strings, not
|
|
13
|
+
// dates — the renderer quotes them for exactly that reason.
|
|
14
|
+
export const capturedAgainstSchema = z.object({
|
|
15
|
+
norsk: z.string().optional(),
|
|
16
|
+
norskMgr: z.string().optional(),
|
|
17
|
+
when: z.string(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const collections = {
|
|
21
|
+
docs: defineCollection({
|
|
22
|
+
loader: docsLoader(),
|
|
23
|
+
schema: docsSchema({ extend: z.object({ capturedAgainst: capturedAgainstSchema.optional() }) }),
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* Match the norsk-ctl frontend's Tailwind zinc palette. */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Self-hosted Geist Sans + Mono, byte-identical to @norskvideo/ctl-sdk/base.css so the
|
|
5
|
+
* docs chrome renders in the same face as the app and the guide screenshots.
|
|
6
|
+
* Standalone package — it can't @import the SDK's base.css, so it vendors the
|
|
7
|
+
* woff2 (see src/fonts/README obligations). Relative url() → Vite fingerprints
|
|
8
|
+
* them into the build; never a CDN.
|
|
9
|
+
*/
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: "Geist";
|
|
12
|
+
font-style: normal;
|
|
13
|
+
font-weight: 100 900; /* variable range → no faux bold */
|
|
14
|
+
font-display: swap;
|
|
15
|
+
src: url("./fonts/Geist.woff2") format("woff2-variations"),
|
|
16
|
+
url("./fonts/Geist.woff2") format("woff2");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@font-face {
|
|
20
|
+
font-family: "Geist Mono";
|
|
21
|
+
font-style: normal;
|
|
22
|
+
font-weight: 100 900;
|
|
23
|
+
font-display: swap;
|
|
24
|
+
src: url("./fonts/GeistMono.woff2") format("woff2-variations"),
|
|
25
|
+
url("./fonts/GeistMono.woff2") format("woff2");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:root {
|
|
29
|
+
--sl-font: "Geist", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
30
|
+
--sl-font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Dark theme — primary. Tailwind zinc + indigo accent matching the React app. */
|
|
34
|
+
:root[data-theme="dark"] {
|
|
35
|
+
/* Backgrounds */
|
|
36
|
+
--sl-color-bg: #18181b; /* zinc-900 */
|
|
37
|
+
--sl-color-bg-nav: #1f1f23; /* zinc-850 */
|
|
38
|
+
--sl-color-bg-sidebar: #1f1f23; /* zinc-850 */
|
|
39
|
+
--sl-color-bg-inline-code: #27272a; /* zinc-800 */
|
|
40
|
+
--sl-color-bg-accent: #818cf8; /* indigo-400 */
|
|
41
|
+
|
|
42
|
+
/* Text */
|
|
43
|
+
--sl-color-text: #e4e4e7; /* zinc-200 */
|
|
44
|
+
--sl-color-text-accent: #a5b4fc; /* indigo-300 */
|
|
45
|
+
--sl-color-text-invert: #18181b; /* zinc-900 */
|
|
46
|
+
|
|
47
|
+
/* Gray ramp */
|
|
48
|
+
--sl-color-white: #fafafa; /* zinc-50 */
|
|
49
|
+
--sl-color-gray-1: #e4e4e7; /* zinc-200 */
|
|
50
|
+
--sl-color-gray-2: #a1a1aa; /* zinc-400 */
|
|
51
|
+
--sl-color-gray-3: #71717a; /* zinc-500 */
|
|
52
|
+
--sl-color-gray-4: #52525b; /* zinc-600 */
|
|
53
|
+
--sl-color-gray-5: #3f3f46; /* zinc-700 */
|
|
54
|
+
--sl-color-gray-6: #27272a; /* zinc-800 */
|
|
55
|
+
--sl-color-black: #18181b; /* zinc-900 */
|
|
56
|
+
|
|
57
|
+
/* Accent */
|
|
58
|
+
--sl-color-accent-low: #1e1b4b; /* indigo-950 */
|
|
59
|
+
--sl-color-accent: #6366f1; /* indigo-500 */
|
|
60
|
+
--sl-color-accent-high: #c7d2fe; /* indigo-200 */
|
|
61
|
+
|
|
62
|
+
/* Hairlines */
|
|
63
|
+
--sl-color-hairline-light: #27272a; /* zinc-800 */
|
|
64
|
+
--sl-color-hairline: #3f3f46; /* zinc-700 */
|
|
65
|
+
--sl-color-hairline-shade: #52525b; /* zinc-600 */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Light theme — reasonable defaults, zinc-tinted. */
|
|
69
|
+
:root {
|
|
70
|
+
--sl-color-accent-low: #eef2ff; /* indigo-50 */
|
|
71
|
+
--sl-color-accent: #4f46e5; /* indigo-600 */
|
|
72
|
+
--sl-color-accent-high: #312e81; /* indigo-900 */
|
|
73
|
+
--sl-color-hairline-light: #e4e4e7; /* zinc-200 */
|
|
74
|
+
--sl-color-hairline: #d4d4d8; /* zinc-300 */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Scrollbars — mirror frontend. */
|
|
78
|
+
::-webkit-scrollbar {
|
|
79
|
+
width: 6px;
|
|
80
|
+
height: 6px;
|
|
81
|
+
}
|
|
82
|
+
::-webkit-scrollbar-track {
|
|
83
|
+
background: #18181b;
|
|
84
|
+
}
|
|
85
|
+
::-webkit-scrollbar-thumb {
|
|
86
|
+
background: #3f3f46;
|
|
87
|
+
border-radius: 3px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Nav title weight + tracking to match the app brand. */
|
|
91
|
+
.site-title {
|
|
92
|
+
font-weight: 700 !important;
|
|
93
|
+
letter-spacing: -0.02em;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Tighter heading tracking. */
|
|
97
|
+
h1 { letter-spacing: -0.035em; }
|
|
98
|
+
h2 { letter-spacing: -0.025em; }
|
|
99
|
+
h3 { letter-spacing: -0.015em; }
|
|
100
|
+
|
|
101
|
+
/* Cards — rounded, accent on hover. */
|
|
102
|
+
.sl-link-card,
|
|
103
|
+
.card {
|
|
104
|
+
border-radius: 12px;
|
|
105
|
+
}
|
|
106
|
+
.sl-link-card:hover {
|
|
107
|
+
border-color: var(--sl-color-accent);
|
|
108
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Copyright (c) 2023 Vercel, in collaboration with basement.studio
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font creation
|
|
14
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
15
|
+
open framework in which fonts may be shared and improved in partnership
|
|
16
|
+
with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply
|
|
25
|
+
to any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software components as
|
|
36
|
+
distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
39
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
40
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
41
|
+
new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION AND CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
49
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
50
|
+
Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components,
|
|
53
|
+
in Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
64
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
65
|
+
presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created
|
|
77
|
+
using the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Astro + Starlight configuration every Norsk product docs site is built
|
|
3
|
+
* from. A site's own `astro.config.mjs` is reduced to what genuinely differs:
|
|
4
|
+
* its origin, its two names, its sidebar, and the versions it was built
|
|
5
|
+
* against.
|
|
6
|
+
*
|
|
7
|
+
* export default defineConfig(
|
|
8
|
+
* norskDocsSite({
|
|
9
|
+
* origin: PUBLIC_SITE_ORIGIN,
|
|
10
|
+
* tabTitle: "Norsk",
|
|
11
|
+
* wordmark: "norsk-ctl",
|
|
12
|
+
* buildVersions: [...],
|
|
13
|
+
* sidebar: [...],
|
|
14
|
+
* }),
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* Everything here is the part that must NOT differ between sites: the theme,
|
|
18
|
+
* the component overrides, the mermaid pipeline, the favicon convention, and
|
|
19
|
+
* the two-build (public / embedded) shape described on `publicBuild` below.
|
|
20
|
+
*/
|
|
21
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { unified } from "@astrojs/markdown-remark";
|
|
25
|
+
import starlight from "@astrojs/starlight";
|
|
26
|
+
import rehypeMermaid from "rehype-mermaid";
|
|
27
|
+
|
|
28
|
+
/** One row of the sidebar's "Built against" footer (components/Sidebar.astro). */
|
|
29
|
+
export interface BuildVersion {
|
|
30
|
+
/** Row label, e.g. "norsk-ctl". */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Row value, read at build time from the product's own files. */
|
|
33
|
+
value: string;
|
|
34
|
+
/** Break a `repo:tag` image pin after the last colon so it wraps sensibly. */
|
|
35
|
+
splitOnLastColon?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface NorskDocsSiteOptions {
|
|
39
|
+
/** Canonical public origin. Drives the sitemap and <link rel="canonical">. */
|
|
40
|
+
origin: string;
|
|
41
|
+
/** BROWSER TAB suffix only ("Quickstart | Norsk"), kept short — tabs truncate. */
|
|
42
|
+
tabTitle: string;
|
|
43
|
+
/** Masthead wordmark. Deliberately different from `tabTitle`; see SiteTitle.astro. */
|
|
44
|
+
wordmark: string;
|
|
45
|
+
/** Starlight's sidebar. The one part of the nav a site fully owns. */
|
|
46
|
+
sidebar: unknown[];
|
|
47
|
+
/** Rows for the sidebar's "Built against" footer. */
|
|
48
|
+
buildVersions: BuildVersion[];
|
|
49
|
+
/**
|
|
50
|
+
* Dev-preview only: machine names `astro dev --host` may be reached by on the
|
|
51
|
+
* LAN (Vite 403s unknown Host headers otherwise). No effect on builds.
|
|
52
|
+
*/
|
|
53
|
+
devAllowedHosts?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Two builds from one content collection, selected by ASTRO_BUILD:
|
|
58
|
+
* `dist-embedded` (base `/docs`) is the bundle baked into a product binary and
|
|
59
|
+
* served by the daemon, `dist-public` (base `/`) is the standalone site on the
|
|
60
|
+
* product's own domain. Exported because a site's sidebar legitimately differs
|
|
61
|
+
* between the two — a section that only exists once it is published.
|
|
62
|
+
*/
|
|
63
|
+
export function isPublicBuild(): boolean {
|
|
64
|
+
return process.env.ASTRO_BUILD === "public";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Content (and any vendored HTML) is authored with absolute /docs/... links —
|
|
69
|
+
* correct for the daemon-embedded build (served under /docs), but a 404 in the
|
|
70
|
+
* public build (served at /). Astro doesn't base-prefix absolute links in
|
|
71
|
+
* MDX/components/static files, so for the public build a post-build pass strips
|
|
72
|
+
* the /docs prefix from href/src attributes across the emitted HTML.
|
|
73
|
+
* Attribute-scoped so it never touches /docs literals in body text. No-op for
|
|
74
|
+
* the embedded build.
|
|
75
|
+
*/
|
|
76
|
+
function stripDocsBaseForPublic() {
|
|
77
|
+
const rewriteHtml = (html: string) =>
|
|
78
|
+
html.replace(/((?:href|src)=")\/docs(\/|")/g, (_m, attr, tail) => (tail === "/" ? `${attr}/` : `${attr}/"`));
|
|
79
|
+
const walk = (root: string, fn: (p: string) => void) => {
|
|
80
|
+
for (const name of readdirSync(root)) {
|
|
81
|
+
const p = join(root, name);
|
|
82
|
+
if (statSync(p).isDirectory()) walk(p, fn);
|
|
83
|
+
else if (p.endsWith(".html")) fn(p);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
name: "strip-docs-base-public",
|
|
88
|
+
hooks: {
|
|
89
|
+
"astro:build:done": ({ dir }: { dir: URL }) => {
|
|
90
|
+
if (!isPublicBuild()) return;
|
|
91
|
+
walk(fileURLToPath(dir), (p) => {
|
|
92
|
+
writeFileSync(p, rewriteHtml(readFileSync(p, "utf-8")));
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function norskDocsSite(options: NorskDocsSiteOptions) {
|
|
100
|
+
const isPublic = isPublicBuild();
|
|
101
|
+
const mermaidLaunchOptions = process.env.BROWSER_FOR_TESTING
|
|
102
|
+
? { executablePath: process.env.BROWSER_FOR_TESTING }
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
// The public site lives at the domain root; the embedded bundle is served
|
|
107
|
+
// by the daemon under /docs. `site` is the canonical origin: it drives the
|
|
108
|
+
// sitemap and <link rel="canonical">, and without it @astrojs/sitemap
|
|
109
|
+
// (pulled in by Starlight) skips and warns. The embedded build reuses the
|
|
110
|
+
// same origin — its sitemap is inert since it is never crawled, but setting
|
|
111
|
+
// `site` keeps the build warning-free.
|
|
112
|
+
site: options.origin,
|
|
113
|
+
base: isPublic ? "/" : "/docs",
|
|
114
|
+
outDir: isPublic ? "./dist-public" : "./dist-embedded",
|
|
115
|
+
markdown: {
|
|
116
|
+
syntaxHighlight: { excludeLangs: ["mermaid"] },
|
|
117
|
+
// Astro 6 deprecated top-level `rehypePlugins`; the pipeline now lives in
|
|
118
|
+
// a `unified()` processor. `syntaxHighlight` stays at the markdown level.
|
|
119
|
+
processor: unified({
|
|
120
|
+
rehypePlugins: [
|
|
121
|
+
[
|
|
122
|
+
rehypeMermaid,
|
|
123
|
+
{ strategy: "img-svg", dark: true, mermaidConfig: { theme: "dark" }, launchOptions: mermaidLaunchOptions },
|
|
124
|
+
],
|
|
125
|
+
],
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
integrations: [
|
|
129
|
+
stripDocsBaseForPublic(),
|
|
130
|
+
starlight({
|
|
131
|
+
title: options.tabTitle,
|
|
132
|
+
customCss: ["./src/custom.css"],
|
|
133
|
+
components: {
|
|
134
|
+
// Appends the "Built against" footer below the nav.
|
|
135
|
+
Sidebar: "./src/components/Sidebar.astro",
|
|
136
|
+
// Decouples the masthead wordmark from `title` above.
|
|
137
|
+
SiteTitle: "./src/components/SiteTitle.astro",
|
|
138
|
+
// Adds the "captured against" line to a generated guide (front
|
|
139
|
+
// matter `capturedAgainst`, see content.config.ts).
|
|
140
|
+
Footer: "./src/components/Footer.astro",
|
|
141
|
+
},
|
|
142
|
+
// Starlight base-prefixes this (emits a `/docs/favicon.png`
|
|
143
|
+
// shortcut-icon link for the embedded build); a raw head <link> would
|
|
144
|
+
// not, and 404s the embedded link-check.
|
|
145
|
+
favicon: "/favicon.png",
|
|
146
|
+
sidebar: options.sidebar,
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
vite: {
|
|
150
|
+
server: { allowedHosts: options.devAllowedHosts ?? [] },
|
|
151
|
+
// Injected rather than imported: a component resolving its own path at
|
|
152
|
+
// runtime finds dist-embedded/.prerender/chunks/ instead of the source
|
|
153
|
+
// tree and fails the build, and the site's version readers want the
|
|
154
|
+
// product's files, not the site's. Both values are build-time constants,
|
|
155
|
+
// so a define is the cheapest correct channel.
|
|
156
|
+
define: {
|
|
157
|
+
__BUILD_VERSIONS__: JSON.stringify(options.buildVersions),
|
|
158
|
+
__SITE_WORDMARK__: JSON.stringify(options.wordmark),
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* The docs theme is a FORCED COPY, and this is its writer and its gate.
|
|
4
|
+
*
|
|
5
|
+
* sync.ts check <site> diff the site's copies against this dev-kit's
|
|
6
|
+
* canonical bytes; exit 1 on drift, naming the file.
|
|
7
|
+
* sync.ts sync <site> rewrite the copies from the canonical.
|
|
8
|
+
*
|
|
9
|
+
* `<site>` is the docs site's package root (the directory holding
|
|
10
|
+
* `astro.config.mjs`); it defaults to the current directory.
|
|
11
|
+
*
|
|
12
|
+
* WHY A COPY, when the dev-kit's rule is to single-source anything consumable.
|
|
13
|
+
* A docs site is a STANDALONE package: it carries its own bun.lock and its own
|
|
14
|
+
* node_modules, outside the monorepo's workspaces, because Astro's dependency
|
|
15
|
+
* tree has no business in the product install. So it cannot depend on the
|
|
16
|
+
* dev-kit the way every other consumer does. That is not a preference — it is
|
|
17
|
+
* checked: adding `"@norskvideo/ctl-dev-kit": "file:../../dev-kit"` to
|
|
18
|
+
* norsk-ctl's website and installing fails outright, because bun then has to
|
|
19
|
+
* resolve the dev-kit's own `workspace:*` dependencies (ctl-sdk, foundation,
|
|
20
|
+
* test-harness, product-template-schema) from a directory that is not in a
|
|
21
|
+
* workspace:
|
|
22
|
+
*
|
|
23
|
+
* error: @norskvideo/ctl-sdk@workspace:* failed to resolve
|
|
24
|
+
*
|
|
25
|
+
* A product repo outside this monorepo installs the published tarball and hits
|
|
26
|
+
* no such wall — but it would then consume the theme by a second mechanism, and
|
|
27
|
+
* one theme reaching two sites two ways is exactly the drift this package
|
|
28
|
+
* exists to stop. So every site, here and elsewhere, gets the same deal: copies
|
|
29
|
+
* at fixed paths, rewritten by `sync`, and `check` in its test suite.
|
|
30
|
+
*
|
|
31
|
+
* The copies land at the paths Starlight and Astro already expect
|
|
32
|
+
* (`src/custom.css`, `src/components/*.astro`, `src/content.config.ts`), which
|
|
33
|
+
* is also why the canonical directory mirrors a site's `src/`: the map below is
|
|
34
|
+
* an identity. Keeping the component paths identical is load-bearing for more
|
|
35
|
+
* than tidiness — Astro derives a component's scoped-style class from its path,
|
|
36
|
+
* so moving one rewrites a class attribute on every page.
|
|
37
|
+
*
|
|
38
|
+
* Canonical bytes are resolved relative to THIS file, so the script works both
|
|
39
|
+
* workspace-symlinked and installed from the published tarball — the same
|
|
40
|
+
* arrangement as conventions/check-drift.ts.
|
|
41
|
+
*/
|
|
42
|
+
import { cpSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
43
|
+
import { dirname, join, relative } from "node:path";
|
|
44
|
+
import { fileURLToPath } from "node:url";
|
|
45
|
+
|
|
46
|
+
const CANONICAL = dirname(fileURLToPath(import.meta.url));
|
|
47
|
+
|
|
48
|
+
/** The site subdirectory the canonical tree is copied into. */
|
|
49
|
+
export const SITE_DIR = "src";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every canonical file, as a path relative to this directory — which is also
|
|
53
|
+
* its path relative to the site's `src/`. Derived from the tree rather than
|
|
54
|
+
* listed, so a file added here reaches every site without a second edit; the
|
|
55
|
+
* script's own source and its test are the only exclusions.
|
|
56
|
+
*/
|
|
57
|
+
export function themeFiles(canonical: string = CANONICAL): string[] {
|
|
58
|
+
const out: string[] = [];
|
|
59
|
+
const walk = (dir: string) => {
|
|
60
|
+
for (const name of readdirSync(dir).sort()) {
|
|
61
|
+
const p = join(dir, name);
|
|
62
|
+
if (statSync(p).isDirectory()) {
|
|
63
|
+
walk(p);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const rel = relative(canonical, p);
|
|
67
|
+
if (rel === "sync.ts" || rel === "sync.test.ts" || rel === "README.md") continue;
|
|
68
|
+
out.push(rel);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walk(canonical);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ThemeDrift {
|
|
76
|
+
ok: boolean;
|
|
77
|
+
problems: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const RESYNC = "Edit the dev-kit source and re-run `sync`, never hand-edit the copy.";
|
|
81
|
+
|
|
82
|
+
/** Compare a site's copies against the canonical bytes. Text is diffed by line
|
|
83
|
+
* so the failure names the drift; binaries (the woff2) compare byte-wise. */
|
|
84
|
+
export function checkTheme(siteRoot: string, canonical: string = CANONICAL): ThemeDrift {
|
|
85
|
+
const problems: string[] = [];
|
|
86
|
+
for (const rel of themeFiles(canonical)) {
|
|
87
|
+
const copy = join(siteRoot, SITE_DIR, rel);
|
|
88
|
+
const want = readFileSync(join(canonical, rel));
|
|
89
|
+
let got: Buffer;
|
|
90
|
+
try {
|
|
91
|
+
got = readFileSync(copy);
|
|
92
|
+
} catch {
|
|
93
|
+
problems.push(`${SITE_DIR}/${rel}: missing — the docs theme is not synced. ${RESYNC}`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (got.equals(want)) continue;
|
|
97
|
+
problems.push(`${SITE_DIR}/${rel}: ${firstDiff(got, want)}. ${RESYNC}`);
|
|
98
|
+
}
|
|
99
|
+
return { ok: problems.length === 0, problems };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function firstDiff(got: Buffer, want: Buffer): string {
|
|
103
|
+
// A woff2 has no lines worth naming; say so rather than printing binary.
|
|
104
|
+
if (got.includes(0) || want.includes(0))
|
|
105
|
+
return `differs from the canonical (${want.length} bytes, found ${got.length})`;
|
|
106
|
+
const a = got.toString("utf8").split("\n");
|
|
107
|
+
const e = want.toString("utf8").split("\n");
|
|
108
|
+
for (let i = 0; i < Math.max(a.length, e.length); i++) {
|
|
109
|
+
if (a[i] !== e[i]) {
|
|
110
|
+
return `line ${i + 1}: expected ${JSON.stringify(e[i] ?? "<missing>")}, found ${JSON.stringify(a[i] ?? "<missing>")}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return "content differs";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Rewrite the site's copies from the canonical bytes. */
|
|
117
|
+
export function syncTheme(siteRoot: string, canonical: string = CANONICAL): string[] {
|
|
118
|
+
const written: string[] = [];
|
|
119
|
+
for (const rel of themeFiles(canonical)) {
|
|
120
|
+
const to = join(siteRoot, SITE_DIR, rel);
|
|
121
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
122
|
+
cpSync(join(canonical, rel), to);
|
|
123
|
+
written.push(`${SITE_DIR}/${rel}`);
|
|
124
|
+
}
|
|
125
|
+
return written;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (import.meta.main) {
|
|
129
|
+
const [mode, site] = process.argv.slice(2);
|
|
130
|
+
const siteRoot = site ?? process.cwd();
|
|
131
|
+
if (mode === "sync") {
|
|
132
|
+
for (const f of syncTheme(siteRoot)) console.log(`wrote ${f}`);
|
|
133
|
+
} else if (mode === "check") {
|
|
134
|
+
const { ok, problems } = checkTheme(siteRoot);
|
|
135
|
+
if (!ok) {
|
|
136
|
+
console.error("docs theme drift:");
|
|
137
|
+
for (const p of problems) console.error(` - ${p}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
console.log(`docs theme: ${themeFiles().length} files in step with the dev-kit.`);
|
|
141
|
+
} else {
|
|
142
|
+
console.error("usage: sync.ts check|sync [site-root]");
|
|
143
|
+
process.exit(2);
|
|
144
|
+
}
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-dev-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./create-product": "./create-product/create-product.ts",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"./doc-guide/instance-proxy": "./doc-guide/instance-proxy.js",
|
|
13
13
|
"./doc-guide/live-browser": "./doc-guide/live-browser.js",
|
|
14
14
|
"./doc-guide/prose-bundle": "./doc-guide/prose-bundle.js",
|
|
15
|
+
"./docs-theme/sync": "./docs-theme/sync.ts",
|
|
15
16
|
"./docs/path-existence": "./docs/path-existence.ts",
|
|
16
17
|
"./docs/splice-generated": "./docs/splice-generated.ts",
|
|
17
18
|
"./licence/check-entitlement": "./licence/check-entitlement.ts",
|