docpensieve 0.1.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/src/theme.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Builds the theme engine from the configuration.
3
+ *
4
+ * @module docpensieve/theme
5
+ */
6
+
7
+ import { setThemeClasses } from '@docpensieve/components';
8
+ import { ConfigError, THEME_FRAMEWORKS } from '@docpensieve/shared';
9
+ import { CustomProvider, TailwindProvider, ThemeEngine } from '@docpensieve/theme';
10
+
11
+ /**
12
+ * Mounts the ThemeEngine matching the declared framework.
13
+ *
14
+ * The CLI does this wiring: `core` deliberately ignores the `theme` package
15
+ * and receives the engine by injection (ADR-002).
16
+ *
17
+ * Along the way, the class table is handed to the components: that is what
18
+ * lets them ask for their class instead of hard-coding it (ADR-007).
19
+ *
20
+ * @param {Record<string, any>} config Normalised configuration.
21
+ * @param {string} [extraCss] CSS appended after the theme's — the look of the
22
+ * components, which the caller has read.
23
+ * @returns {ThemeEngine}
24
+ * @throws {ConfigError} For a framework the project does not know.
25
+ */
26
+ export function createTheme(config, extraCss = '') {
27
+ const theme = config.theme ?? {};
28
+ const framework = theme.framework ?? 'tailwind';
29
+
30
+ // Components first, project CSS second: the latter must be able to correct
31
+ // them.
32
+ const options = {
33
+ tokens: theme.tokens,
34
+ css: [extraCss, theme.css].filter(Boolean).join('\n\n'),
35
+ };
36
+
37
+ /** @param {ThemeEngine} engine */
38
+ const mount = (engine) => {
39
+ setThemeClasses(engine.classes);
40
+ return engine;
41
+ };
42
+
43
+ if (framework === 'custom') {
44
+ return mount(new ThemeEngine().register('custom', new CustomProvider(options)));
45
+ }
46
+ if (framework === 'tailwind') {
47
+ return mount(
48
+ new ThemeEngine().register(
49
+ 'tailwind',
50
+ new TailwindProvider({ ...options, source: theme.source }),
51
+ ),
52
+ );
53
+ }
54
+
55
+ // The announced frameworks are all written: getting here means a faulty
56
+ // configuration, not an upcoming milestone.
57
+ throw new ConfigError(`Unknown theme framework: "${framework}".`, {
58
+ hint: `Accepted values: ${THEME_FRAMEWORKS.join(', ')}.`,
59
+ });
60
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `docpensieve build` command.
3
+ *
4
+ * @module docpensieve/commands/build
5
+ */
6
+ /**
7
+ * @param {string | undefined} versionSlug Version to generate, or all of them when omitted.
8
+ * @param {{ out?: string, cwd?: string }} [options]
9
+ * @returns {Promise<void>}
10
+ */
11
+ export declare function build(versionSlug: string | undefined, options?: {
12
+ out?: string;
13
+ cwd?: string;
14
+ }): Promise<void>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `docpensieve check` command — reads the produced site back.
3
+ *
4
+ * A successful build says nothing of a dead link or of invalid markup:
5
+ * nothing in the chain looks at them, and they only show when opening the
6
+ * pages one by one. This command does that reading.
7
+ *
8
+ * @module docpensieve/commands/check
9
+ */
10
+ export type Fault = {
11
+ /**
12
+ * Page path, relative to the checked root.
13
+ */
14
+ page: string;
15
+ /**
16
+ * What is at stake — a target, a tag.
17
+ */
18
+ subject: string;
19
+ /**
20
+ * What is wrong.
21
+ */
22
+ reason: string;
23
+ };
24
+ export type Page = {
25
+ /**
26
+ * Path relative to the checked root.
27
+ */
28
+ relative: string;
29
+ /**
30
+ * File contents.
31
+ */
32
+ html: string;
33
+ };
34
+ /**
35
+ * Checks the internal links of a generated site.
36
+ *
37
+ * Exported apart from the command: it reads no configuration and addresses no
38
+ * one, which makes it usable elsewhere and testable on its own.
39
+ *
40
+ * @param {string} root Folder of the produced site.
41
+ * @param {string} [baseUrl] Deployment prefix, slashes included.
42
+ * @returns {Promise<{ pages: number, faults: Fault[] }>}
43
+ */
44
+ export declare function verifyLinks(root: string, baseUrl?: string): Promise<{
45
+ pages: number;
46
+ faults: Fault[];
47
+ }>;
48
+ /**
49
+ * Checks the markup of a generated site.
50
+ *
51
+ * @param {string} root Folder of the produced site.
52
+ * @returns {Promise<{ pages: number, faults: Fault[] }>}
53
+ */
54
+ export declare function verifyMarkup(root: string): Promise<{
55
+ pages: number;
56
+ faults: Fault[];
57
+ }>;
58
+ /**
59
+ * Reads the produced site back and reports what is wrong.
60
+ *
61
+ * @param {{ dir?: string, cwd?: string }} [options]
62
+ * @returns {Promise<{ root: string, pages: number, faults: Fault[] }>}
63
+ * @throws {DocPensieveError} When the folder does not exist, or when something
64
+ * is left to fix — the exit code is then that of an expected error, which is
65
+ * enough to fail a continuous integration run.
66
+ */
67
+ export declare function check(options?: {
68
+ dir?: string;
69
+ cwd?: string;
70
+ }): Promise<{
71
+ root: string;
72
+ pages: number;
73
+ faults: Fault[];
74
+ }>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `docpensieve dev` command — build, watch and reload.
3
+ *
4
+ * @module docpensieve/commands/dev
5
+ */
6
+ /**
7
+ * @param {{ port?: number, cwd?: string }} [options]
8
+ * @returns {Promise<{
9
+ * server: import('node:http').Server,
10
+ * watcher: import('chokidar').FSWatcher,
11
+ * port: number,
12
+ * url: string,
13
+ * close: () => Promise<void>,
14
+ * }>}
15
+ */
16
+ export declare function dev(options?: {
17
+ port?: number;
18
+ cwd?: string;
19
+ }): Promise<{
20
+ server: import('node:http').Server;
21
+ watcher: import('chokidar').FSWatcher;
22
+ port: number;
23
+ url: string;
24
+ close: () => Promise<void>;
25
+ }>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `docpensieve init` command — sets up a documentation project.
3
+ *
4
+ * @module docpensieve/commands/init
5
+ */
6
+ /**
7
+ * Sets up a documentation project.
8
+ *
9
+ * @param {string} [dir] Target folder, created if needed.
10
+ * @param {{
11
+ * name?: string, theme?: string, siteUrl?: string, version?: string,
12
+ * yes?: boolean, force?: boolean,
13
+ * }} [options]
14
+ * @returns {Promise<{ dir: string, theme: string }>}
15
+ * @throws {DocPensieveError} Unknown framework, or project already initialised.
16
+ */
17
+ export declare function init(dir?: string, options?: {
18
+ name?: string;
19
+ theme?: string;
20
+ siteUrl?: string;
21
+ version?: string;
22
+ yes?: boolean;
23
+ force?: boolean;
24
+ }): Promise<{
25
+ dir: string;
26
+ theme: string;
27
+ }>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `docpensieve serve` command — serves the output folder statically.
3
+ *
4
+ * @module docpensieve/commands/serve
5
+ */
6
+ /**
7
+ * @param {{ port?: number, dir?: string, cwd?: string }} [options]
8
+ * @returns {Promise<{ server: import('node:http').Server, port: number, url: string }>}
9
+ * @throws {DocPensieveError} When the folder to serve does not exist.
10
+ */
11
+ export declare function serve(options?: {
12
+ port?: number;
13
+ dir?: string;
14
+ cwd?: string;
15
+ }): Promise<{
16
+ server: import('node:http').Server;
17
+ port: number;
18
+ url: string;
19
+ }>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * docpensieve — implementations of the commands.
3
+ *
4
+ * The binary (`bin/docpensieve.js`) only parses arguments: the logic lives
5
+ * here, to stay testable without spawning a subprocess.
6
+ *
7
+ * @module docpensieve
8
+ */
9
+ export { build } from './commands/build.js';
10
+ export { check, verifyLinks, verifyMarkup } from './commands/check.js';
11
+ export { dev } from './commands/dev.js';
12
+ export { init } from './commands/init.js';
13
+ export { serve } from './commands/serve.js';
14
+ export { createStaticServer, listen, resolveRequestPath } from './server.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Static HTTP server shared by the `serve` and `dev` commands.
3
+ *
4
+ * @module docpensieve/server
5
+ */
6
+ /** Path reserved for the reload stream of the development server. */
7
+ export declare const RELOAD_PATH = "/__docpensieve/reload";
8
+ /**
9
+ * Resolves a request URL into a file path, without leaving the root.
10
+ *
11
+ * @param {string} pathname Request path, `basePath` already removed.
12
+ * @param {string} root Served folder.
13
+ * @returns {string | null} Absolute path, or `null` when the target escapes `root`.
14
+ */
15
+ export declare function resolveRequestPath(pathname: string, root: string): string | null;
16
+ /**
17
+ * Creates a static file server.
18
+ *
19
+ * @param {{
20
+ * root: string,
21
+ * basePath?: string,
22
+ * inject?: string | null,
23
+ * onReload?: (send: () => void) => void,
24
+ * }} options
25
+ * `basePath` is the prefix under which the site is mounted: it must reflect
26
+ * the configuration's `baseUrl`, otherwise the links of the pages do not
27
+ * resolve locally. `inject` is an HTML fragment inserted before `</body>` —
28
+ * the development server uses it for its reload script, which leaves the
29
+ * generated output intact.
30
+ * @returns {import('node:http').Server}
31
+ */
32
+ export declare function createStaticServer({ root, basePath, inject, onReload }: {
33
+ root: string;
34
+ basePath?: string;
35
+ inject?: string | null;
36
+ onReload?: (send: () => void) => void;
37
+ }): import('node:http').Server;
38
+ /**
39
+ * Starts listening, looking for a free port if needed.
40
+ *
41
+ * @param {import('node:http').Server} server
42
+ * @param {number} port Desired port.
43
+ * @param {number} [attempts] Number of ports tried from `port` on.
44
+ * @returns {Promise<number>} The port actually used.
45
+ * @throws {DocPensieveError} When no port is free in the range.
46
+ */
47
+ export declare function listen(server: import('node:http').Server, port: number, attempts?: number): Promise<number>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Builds the theme engine from the configuration.
3
+ *
4
+ * @module docpensieve/theme
5
+ */
6
+ import { ThemeEngine } from '@docpensieve/theme';
7
+ /**
8
+ * Mounts the ThemeEngine matching the declared framework.
9
+ *
10
+ * The CLI does this wiring: `core` deliberately ignores the `theme` package
11
+ * and receives the engine by injection (ADR-002).
12
+ *
13
+ * Along the way, the class table is handed to the components: that is what
14
+ * lets them ask for their class instead of hard-coding it (ADR-007).
15
+ *
16
+ * @param {Record<string, any>} config Normalised configuration.
17
+ * @param {string} [extraCss] CSS appended after the theme's — the look of the
18
+ * components, which the caller has read.
19
+ * @returns {ThemeEngine}
20
+ * @throws {ConfigError} For a framework the project does not know.
21
+ */
22
+ export declare function createTheme(config: Record<string, any>, extraCss?: string): ThemeEngine;