narrarium-astro-reader 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/cli-dist/cli.js +1 -1
- package/cli-dist/cli.js.map +1 -1
- package/cli-dist/lib/book.d.ts.map +1 -1
- package/cli-dist/lib/book.js +2 -1
- package/cli-dist/lib/book.js.map +1 -1
- package/cli-dist/lib/env.d.ts +7 -0
- package/cli-dist/lib/env.d.ts.map +1 -0
- package/cli-dist/lib/env.js +44 -0
- package/cli-dist/lib/env.js.map +1 -0
- package/cli-dist/lib/reader-mode.d.ts.map +1 -1
- package/cli-dist/lib/reader-mode.js +2 -1
- package/cli-dist/lib/reader-mode.js.map +1 -1
- package/cli-dist/scaffold.d.ts.map +1 -1
- package/cli-dist/scaffold.js +42 -8
- package/cli-dist/scaffold.js.map +1 -1
- package/package.json +2 -2
- package/scripts/book-dev-utils.mjs +36 -1
- package/scripts/env-loader.mjs +12 -0
- package/src/cli.ts +1 -1
- package/src/components/ReaderRuntime.astro +44 -16
- package/src/layouts/BaseLayout.astro +22 -0
- package/src/lib/book.ts +2 -1
- package/src/lib/env.ts +61 -0
- package/src/lib/reader-mode.ts +3 -3
- package/src/pages/index.astro +1 -1
- package/src/scaffold.ts +50 -8
package/README.md
CHANGED
|
@@ -54,10 +54,9 @@ NARRARIUM_READER_ALLOW_FULL_CANON=true
|
|
|
54
54
|
|
|
55
55
|
## Local development
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
The scaffold creates `.env` with the computed book root already filled in. Update it if the reader should point somewhere else:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
cp .env.example .env
|
|
61
60
|
npm install
|
|
62
61
|
npm run dev
|
|
63
62
|
```
|
package/cli-dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ output.write([
|
|
|
18
18
|
"Next steps:",
|
|
19
19
|
`- cd ${result.targetRoot}`,
|
|
20
20
|
"- npm install",
|
|
21
|
-
"-
|
|
21
|
+
"- adjust .env if this reader should point at a different book root",
|
|
22
22
|
"- npm run dev",
|
|
23
23
|
"- The dev server will watch the linked book repo, refresh the EPUB, and reload the site while you write",
|
|
24
24
|
].join("\n"));
|
package/cli-dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAUnD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;AAC3C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,SAAS,EAAE;IAC1D,QAAQ,EAAE,QAAQ,CAAC,QAAQ;IAC3B,WAAW,EAAE,QAAQ,CAAC,WAAW;IACjC,cAAc,EAAE,QAAQ,CAAC,cAAc;IACvC,WAAW,EAAE,QAAQ,CAAC,WAAW;CAClC,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,CACV;IACE,kCAAkC,MAAM,CAAC,UAAU,EAAE;IACrD,sBAAsB,MAAM,CAAC,QAAQ,EAAE;IACvC,oBAAoB,MAAM,CAAC,cAAc,EAAE;IAC3C,EAAE;IACF,aAAa;IACb,QAAQ,MAAM,CAAC,UAAU,EAAE;IAC3B,eAAe;IACf,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"book.d.ts","sourceRoot":"","sources":["../../src/lib/book.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"book.d.ts","sourceRoot":"","sources":["../../src/lib/book.ts"],"names":[],"mappings":"AAaA,KAAK,gBAAgB,GACjB,WAAW,GACX,UAAU,GACV,SAAS,GACT,MAAM,GACN,QAAQ,GACR,gBAAgB,CAAC;AAErB,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED,wBAAsB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CrC;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM;;;;;;;;GAG5D;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB;;;;;;;;;GAa/D;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM;;;;;GAG5E;AAED,wBAAsB,oBAAoB;;;;;;;;;;;;;GAoBzC"}
|
package/cli-dist/lib/book.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { listChapters, listEntities, pathExists, readBook, readChapter, readEntity, readTimelineMain, } from "narrarium";
|
|
3
3
|
import { defaultBookRoot } from "./book-config.js";
|
|
4
|
+
import { readReaderBookRootEnv } from "./env.js";
|
|
4
5
|
export function getBookRoot() {
|
|
5
|
-
const configured =
|
|
6
|
+
const configured = readReaderBookRootEnv();
|
|
6
7
|
if (configured)
|
|
7
8
|
return path.resolve(configured);
|
|
8
9
|
return path.resolve(process.cwd(), defaultBookRoot);
|
package/cli-dist/lib/book.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"book.js","sourceRoot":"","sources":["../../src/lib/book.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"book.js","sourceRoot":"","sources":["../../src/lib/book.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type EnvSource = Record<string, string | undefined> | null | undefined;
|
|
2
|
+
export declare function normalizeReaderEnvValue(value: string | null | undefined): string | undefined;
|
|
3
|
+
export declare function isClearlyInvalidBookRootValue(value: string | null | undefined): boolean;
|
|
4
|
+
export declare function readReaderEnv(keys: string[], sources?: EnvSource[]): string | undefined;
|
|
5
|
+
export declare function readReaderBookRootEnv(sources?: EnvSource[]): string | undefined;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/lib/env.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAOvE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkB5F;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAOvF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,SAAS,EAA4B,GAAG,MAAM,GAAG,SAAS,CAehH;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,SAAS,EAA4B,GAAG,MAAM,GAAG,SAAS,CAOxG"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const astroEnv = (import.meta.env ?? {});
|
|
2
|
+
export function normalizeReaderEnvValue(value) {
|
|
3
|
+
if (typeof value !== "string") {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
11
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
12
|
+
return trimmed.slice(1, -1).trim() || undefined;
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
export function isClearlyInvalidBookRootValue(value) {
|
|
17
|
+
const normalized = normalizeReaderEnvValue(value);
|
|
18
|
+
if (!normalized) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return normalized === "/" || normalized === "\\" || /^[a-zA-Z]:(?:[\\/])?$/.test(normalized);
|
|
22
|
+
}
|
|
23
|
+
export function readReaderEnv(keys, sources = [process.env, astroEnv]) {
|
|
24
|
+
for (const source of sources) {
|
|
25
|
+
if (!source) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
const value = normalizeReaderEnvValue(source[key]);
|
|
30
|
+
if (value) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
export function readReaderBookRootEnv(sources = [process.env, astroEnv]) {
|
|
38
|
+
const value = readReaderEnv(["NARRARIUM_BOOK_ROOT", "GHOSTWRITER_BOOK_ROOT"], sources);
|
|
39
|
+
if (!value || isClearlyInvalidBookRootValue(value)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/lib/env.ts"],"names":[],"mappings":"AAEA,MAAM,QAAQ,GAAG,CAAE,MAAM,CAAC,IAAkE,CAAC,GAAG,IAAI,EAAE,CAGrG,CAAC;AAEF,MAAM,UAAU,uBAAuB,CAAC,KAAgC;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IACE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAClD,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IAClD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,KAAgC;IAC5E,MAAM,UAAU,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,IAAI,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAc,EAAE,UAAuB,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;IAC1F,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,uBAAuB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAuB,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC;IAClF,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,EAAE,OAAO,CAAC,CAAC;IACvF,IAAI,CAAC,KAAK,IAAI,6BAA6B,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader-mode.d.ts","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reader-mode.d.ts","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,IAAI,OAAO,CAMzC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { readReaderEnv } from "./env.js";
|
|
1
2
|
export function isFullCanonMode() {
|
|
2
|
-
const raw = String(
|
|
3
|
+
const raw = String(readReaderEnv(["NARRARIUM_READER_CANON_MODE", "NARRARIUM_READER_ALLOW_FULL_CANON"]) ?? "")
|
|
3
4
|
.trim()
|
|
4
5
|
.toLowerCase();
|
|
5
6
|
return raw === "1" || raw === "true" || raw === "full" || raw === "author" || raw === "spoilers";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader-mode.js","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"reader-mode.js","sourceRoot":"","sources":["../../src/lib/reader-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,6BAA6B,EAAE,mCAAmC,CAAC,CAAC,IAAI,EAAE,CAAC;SAC1G,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IAEjB,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,UAAU,CAAC;AACnG,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAKA,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB;;;;;GAkGxF"}
|
package/cli-dist/scaffold.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cp, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isClearlyInvalidBookRootValue, normalizeReaderEnvValue } from "./lib/env.js";
|
|
4
5
|
export async function scaffoldReaderSite(targetDir, options = {}) {
|
|
5
6
|
const targetRoot = path.resolve(targetDir);
|
|
6
7
|
const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
|
|
@@ -52,13 +53,13 @@ export async function scaffoldReaderSite(targetDir, options = {}) {
|
|
|
52
53
|
if (pagesDomain) {
|
|
53
54
|
await writeFile(path.join(targetRoot, "public", "CNAME"), `${pagesDomain}\n`, "utf8");
|
|
54
55
|
}
|
|
55
|
-
await writeFile(path.join(targetRoot, ".env.example"),
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
""
|
|
61
|
-
|
|
56
|
+
await writeFile(path.join(targetRoot, ".env.example"), buildReaderEnvFile(bookRoot), "utf8");
|
|
57
|
+
const envPath = path.join(targetRoot, ".env");
|
|
58
|
+
const existingEnv = await readFile(envPath, "utf8").catch(() => null);
|
|
59
|
+
const nextEnv = mergeReaderEnvFile(existingEnv, bookRoot);
|
|
60
|
+
if (nextEnv !== existingEnv) {
|
|
61
|
+
await writeFile(envPath, nextEnv, "utf8");
|
|
62
|
+
}
|
|
62
63
|
await writeFile(path.join(targetRoot, ".gitignore"), "node_modules/\ndist/\n.astro/\n.env\npublic/downloads/\n", "utf8");
|
|
63
64
|
await writeFile(path.join(targetRoot, "README.md"), buildReaderReadme(bookRoot), "utf8");
|
|
64
65
|
return {
|
|
@@ -75,7 +76,7 @@ This site was scaffolded from \`narrarium-astro-reader\`.
|
|
|
75
76
|
|
|
76
77
|
## Configure
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
The scaffold creates a local \`.env\` with the book root already filled in. Adjust it if this reader should point somewhere else:
|
|
79
80
|
|
|
80
81
|
\`\`\`bash
|
|
81
82
|
NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}
|
|
@@ -123,6 +124,39 @@ By default it deploys to standard GitHub Pages using the repository name as the
|
|
|
123
124
|
function buildBookConfigScript(bookRoot) {
|
|
124
125
|
return `export const defaultBookRoot = ${JSON.stringify(toPosix(bookRoot))};\n`;
|
|
125
126
|
}
|
|
127
|
+
function buildReaderEnvFile(bookRoot) {
|
|
128
|
+
return [
|
|
129
|
+
`NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}`,
|
|
130
|
+
"# NARRARIUM_READER_CANON_MODE=full",
|
|
131
|
+
"# EPUBCHECK_CMD=epubcheck",
|
|
132
|
+
"# EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar",
|
|
133
|
+
"",
|
|
134
|
+
].join("\n");
|
|
135
|
+
}
|
|
136
|
+
function mergeReaderEnvFile(existingContent, bookRoot) {
|
|
137
|
+
const desiredRoot = toPosix(bookRoot);
|
|
138
|
+
if (existingContent === null) {
|
|
139
|
+
return buildReaderEnvFile(bookRoot);
|
|
140
|
+
}
|
|
141
|
+
const lines = existingContent.split(/\r?\n/);
|
|
142
|
+
let handled = false;
|
|
143
|
+
const nextLines = lines.map((line) => {
|
|
144
|
+
const match = line.match(/^(\s*NARRARIUM_BOOK_ROOT\s*=\s*)(.*)$/);
|
|
145
|
+
if (!match) {
|
|
146
|
+
return line;
|
|
147
|
+
}
|
|
148
|
+
handled = true;
|
|
149
|
+
const currentValue = normalizeReaderEnvValue(match[2]);
|
|
150
|
+
if (currentValue && !isClearlyInvalidBookRootValue(currentValue)) {
|
|
151
|
+
return line;
|
|
152
|
+
}
|
|
153
|
+
return `${match[1]}${desiredRoot}`;
|
|
154
|
+
});
|
|
155
|
+
if (handled) {
|
|
156
|
+
return nextLines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
return `${buildReaderEnvFile(bookRoot).trimEnd()}\n${existingContent}`;
|
|
159
|
+
}
|
|
126
160
|
function buildPagesWorkflow(pagesDomain) {
|
|
127
161
|
const envBlock = pagesDomain
|
|
128
162
|
? [" SITE_BASE: /", ` SITE_URL: https://${pagesDomain}`].join("\n")
|
package/cli-dist/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "narrarium-astro-reader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Astro reader and scaffolding CLI for Narrarium book repositories.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"test": "npm run build:cli && node --test test/**/*.test.mjs"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"narrarium": "^0.1.
|
|
53
|
+
"narrarium": "^0.1.6",
|
|
54
54
|
"astro": "^5.14.1",
|
|
55
55
|
"chokidar": "^4.0.3",
|
|
56
56
|
"marked": "^16.3.0"
|
|
@@ -2,6 +2,9 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { exportEpub } from "narrarium";
|
|
5
|
+
import { loadReaderEnvFiles } from "./env-loader.mjs";
|
|
6
|
+
|
|
7
|
+
loadReaderEnvFiles();
|
|
5
8
|
|
|
6
9
|
const watchPatterns = [
|
|
7
10
|
"book.md",
|
|
@@ -24,7 +27,7 @@ const watchPatterns = [
|
|
|
24
27
|
];
|
|
25
28
|
|
|
26
29
|
export function resolveBookRoot(defaultBookRoot, cwd = process.cwd()) {
|
|
27
|
-
const configured =
|
|
30
|
+
const configured = readBookRootEnv();
|
|
28
31
|
return path.resolve(cwd, configured ?? defaultBookRoot);
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -99,3 +102,35 @@ function runCommand(command, args) {
|
|
|
99
102
|
function toPosix(value) {
|
|
100
103
|
return value.split(path.sep).join("/");
|
|
101
104
|
}
|
|
105
|
+
|
|
106
|
+
function readBookRootEnv() {
|
|
107
|
+
for (const key of ["NARRARIUM_BOOK_ROOT", "GHOSTWRITER_BOOK_ROOT"]) {
|
|
108
|
+
const value = normalizeEnvValue(process.env[key]);
|
|
109
|
+
if (value && !isClearlyInvalidBookRootValue(value)) {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeEnvValue(value) {
|
|
118
|
+
if (typeof value !== "string") {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const trimmed = value.trim();
|
|
123
|
+
if (!trimmed) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
128
|
+
return trimmed.slice(1, -1).trim() || undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return trimmed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isClearlyInvalidBookRootValue(value) {
|
|
135
|
+
return value === "/" || value === "\\" || /^[a-zA-Z]:(?:[\\/])?$/.test(value);
|
|
136
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
export function loadReaderEnvFiles(cwd = process.cwd()) {
|
|
5
|
+
for (const fileName of [".env.local", ".env"]) {
|
|
6
|
+
try {
|
|
7
|
+
process.loadEnvFile(path.join(cwd, fileName));
|
|
8
|
+
} catch {
|
|
9
|
+
// Ignore missing or unreadable local env files.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -30,7 +30,7 @@ output.write(
|
|
|
30
30
|
"Next steps:",
|
|
31
31
|
`- cd ${result.targetRoot}`,
|
|
32
32
|
"- npm install",
|
|
33
|
-
"-
|
|
33
|
+
"- adjust .env if this reader should point at a different book root",
|
|
34
34
|
"- npm run dev",
|
|
35
35
|
"- The dev server will watch the linked book repo, refresh the EPUB, and reload the site while you write",
|
|
36
36
|
].join("\n"),
|
|
@@ -141,8 +141,8 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
141
141
|
const toggle = document.querySelector("[data-theme-toggle]");
|
|
142
142
|
if (!toggle) return;
|
|
143
143
|
|
|
144
|
-
const saved =
|
|
145
|
-
const initialTheme = saved || (
|
|
144
|
+
const saved = readStorage(themeStorageKey);
|
|
145
|
+
const initialTheme = saved || (prefersDarkTheme() ? "dark" : "light");
|
|
146
146
|
applyTheme(initialTheme);
|
|
147
147
|
|
|
148
148
|
toggle.addEventListener("click", () => {
|
|
@@ -158,7 +158,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
158
158
|
toggle.setAttribute("aria-label", theme === "dark" ? "Switch to light mode" : "Switch to dark mode");
|
|
159
159
|
}
|
|
160
160
|
if (persist) {
|
|
161
|
-
|
|
161
|
+
writeStorage(themeStorageKey, theme);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -184,7 +184,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
184
184
|
root.style.setProperty("--reader-leading", String(preferences.lineHeight || 1.72));
|
|
185
185
|
root.style.setProperty("--reader-measure", preferences.measure || "72ch");
|
|
186
186
|
root.style.setProperty("--reader-copy-family", fontFamilyValue(preferences.fontFamily));
|
|
187
|
-
|
|
187
|
+
writeStorage(preferencesStorageKey, JSON.stringify(preferences));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
function initializeReaderSettings() {
|
|
@@ -235,7 +235,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
235
235
|
);
|
|
236
236
|
spoilerLimitInput.value = String(getSelectedSpoilerLimit() || currentPageChapterNumber);
|
|
237
237
|
spoilerLimitInput.addEventListener("change", () => {
|
|
238
|
-
|
|
238
|
+
writeStorage(spoilerLimitStorageKey, spoilerLimitInput.value);
|
|
239
239
|
window.location.reload();
|
|
240
240
|
});
|
|
241
241
|
}
|
|
@@ -294,7 +294,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
294
294
|
};
|
|
295
295
|
|
|
296
296
|
if (!progress.chapterNumber || currentPageChapterNumber >= progress.chapterNumber) {
|
|
297
|
-
|
|
297
|
+
writeStorage(progressStorageKey, JSON.stringify(nextProgress));
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
const bookmarkButton = document.querySelector("[data-bookmark-toggle]");
|
|
@@ -547,7 +547,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
547
547
|
return null;
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
const saved = Number(
|
|
550
|
+
const saved = Number(readStorage(spoilerLimitStorageKey) || "");
|
|
551
551
|
if (Number.isFinite(saved)) {
|
|
552
552
|
return Math.min(saved, currentPageChapterNumber);
|
|
553
553
|
}
|
|
@@ -578,9 +578,9 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
578
578
|
function toggleBookmark(button, bookmark) {
|
|
579
579
|
const current = readJson(bookmarkStorageKey);
|
|
580
580
|
if (current?.href === bookmark.href) {
|
|
581
|
-
|
|
581
|
+
removeStorage(bookmarkStorageKey);
|
|
582
582
|
} else {
|
|
583
|
-
|
|
583
|
+
writeStorage(bookmarkStorageKey, JSON.stringify(bookmark));
|
|
584
584
|
}
|
|
585
585
|
syncBookmarkButton(button, bookmark);
|
|
586
586
|
}
|
|
@@ -638,8 +638,8 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
638
638
|
glossaryRules: buildPronunciationRules(glossaryEntries),
|
|
639
639
|
triggers,
|
|
640
640
|
voices: [],
|
|
641
|
-
voiceUri:
|
|
642
|
-
rate: clampNumber(Number(
|
|
641
|
+
voiceUri: readStorage(ttsVoiceStorageKey) || "",
|
|
642
|
+
rate: clampNumber(Number(readStorage(ttsRateStorageKey) || "1"), 0.7, 1.35, 1),
|
|
643
643
|
scopeId: "chapter",
|
|
644
644
|
index: 0,
|
|
645
645
|
runId: 0,
|
|
@@ -851,7 +851,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
851
851
|
|
|
852
852
|
state.voiceSelect.addEventListener("change", () => {
|
|
853
853
|
state.voiceUri = state.voiceSelect.value;
|
|
854
|
-
|
|
854
|
+
writeStorage(ttsVoiceStorageKey, state.voiceUri);
|
|
855
855
|
updateTtsUi(state);
|
|
856
856
|
if (state.speech.speaking && !state.speech.paused) {
|
|
857
857
|
restartCurrentTtsSegment(state);
|
|
@@ -860,7 +860,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
860
860
|
|
|
861
861
|
state.rateInput.addEventListener("input", () => {
|
|
862
862
|
state.rate = clampNumber(Number(state.rateInput.value || "1"), 0.7, 1.35, 1);
|
|
863
|
-
|
|
863
|
+
writeStorage(ttsRateStorageKey, String(state.rate));
|
|
864
864
|
updateTtsUi(state);
|
|
865
865
|
if (state.speech.speaking && !state.speech.paused) {
|
|
866
866
|
restartCurrentTtsSegment(state);
|
|
@@ -1038,7 +1038,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
1038
1038
|
return;
|
|
1039
1039
|
}
|
|
1040
1040
|
|
|
1041
|
-
|
|
1041
|
+
writeStorage(ttsProgressStorageKey, JSON.stringify({
|
|
1042
1042
|
path: window.location.pathname,
|
|
1043
1043
|
scopeId: state.scopeId,
|
|
1044
1044
|
scopeLabel: scope.label,
|
|
@@ -1047,7 +1047,7 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
1047
1047
|
}
|
|
1048
1048
|
|
|
1049
1049
|
function clearTtsProgress() {
|
|
1050
|
-
|
|
1050
|
+
removeStorage(ttsProgressStorageKey);
|
|
1051
1051
|
}
|
|
1052
1052
|
|
|
1053
1053
|
function updateTtsUi(state) {
|
|
@@ -1145,9 +1145,37 @@ const chaptersJson = JSON.stringify(chapters).replace(/</g, "\\u003c");
|
|
|
1145
1145
|
return Math.max(min, Math.min(max, value));
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
|
+
function prefersDarkTheme() {
|
|
1149
|
+
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function readStorage(key) {
|
|
1153
|
+
try {
|
|
1154
|
+
return localStorage.getItem(key);
|
|
1155
|
+
} catch {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function writeStorage(key, value) {
|
|
1161
|
+
try {
|
|
1162
|
+
localStorage.setItem(key, value);
|
|
1163
|
+
} catch {
|
|
1164
|
+
// Ignore storage failures so reader controls still work in restricted contexts.
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function removeStorage(key) {
|
|
1169
|
+
try {
|
|
1170
|
+
localStorage.removeItem(key);
|
|
1171
|
+
} catch {
|
|
1172
|
+
// Ignore storage failures so reader controls still work in restricted contexts.
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1148
1176
|
function readJson(key) {
|
|
1149
1177
|
try {
|
|
1150
|
-
return JSON.parse(
|
|
1178
|
+
return JSON.parse(readStorage(key) || "null");
|
|
1151
1179
|
} catch {
|
|
1152
1180
|
return null;
|
|
1153
1181
|
}
|
|
@@ -37,6 +37,21 @@ const fullCanonMode = isFullCanonMode();
|
|
|
37
37
|
<title>{title}</title>
|
|
38
38
|
{description && <meta name="description" content={description} />}
|
|
39
39
|
<base href={import.meta.env.BASE_URL} />
|
|
40
|
+
<script is:inline>
|
|
41
|
+
(() => {
|
|
42
|
+
const themeStorageKey = "narrarium-reader-theme";
|
|
43
|
+
const root = document.documentElement;
|
|
44
|
+
const prefersDark =
|
|
45
|
+
typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const saved = localStorage.getItem(themeStorageKey);
|
|
49
|
+
root.dataset.theme = saved || (prefersDark ? "dark" : "light");
|
|
50
|
+
} catch {
|
|
51
|
+
root.dataset.theme = prefersDark ? "dark" : "light";
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
</script>
|
|
40
55
|
<style>
|
|
41
56
|
:root {
|
|
42
57
|
--paper: #f3ecdd;
|
|
@@ -319,6 +334,13 @@ const fullCanonMode = isFullCanonMode();
|
|
|
319
334
|
font-size: 1.15rem;
|
|
320
335
|
}
|
|
321
336
|
|
|
337
|
+
.value-path {
|
|
338
|
+
overflow-wrap: anywhere;
|
|
339
|
+
word-break: break-word;
|
|
340
|
+
font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
|
|
341
|
+
font-size: 0.98rem;
|
|
342
|
+
}
|
|
343
|
+
|
|
322
344
|
.chapter-list {
|
|
323
345
|
margin-top: 2rem;
|
|
324
346
|
display: grid;
|
package/src/lib/book.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
readTimelineMain,
|
|
10
10
|
} from "narrarium";
|
|
11
11
|
import { defaultBookRoot } from "./book-config.js";
|
|
12
|
+
import { readReaderBookRootEnv } from "./env.js";
|
|
12
13
|
|
|
13
14
|
type ReaderEntityKind =
|
|
14
15
|
| "character"
|
|
@@ -19,7 +20,7 @@ type ReaderEntityKind =
|
|
|
19
20
|
| "timeline-event";
|
|
20
21
|
|
|
21
22
|
export function getBookRoot(): string {
|
|
22
|
-
const configured =
|
|
23
|
+
const configured = readReaderBookRootEnv();
|
|
23
24
|
if (configured) return path.resolve(configured);
|
|
24
25
|
return path.resolve(process.cwd(), defaultBookRoot);
|
|
25
26
|
}
|
package/src/lib/env.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type EnvSource = Record<string, string | undefined> | null | undefined;
|
|
2
|
+
|
|
3
|
+
const astroEnv = ((import.meta as ImportMeta & { env?: Record<string, string | undefined> }).env ?? {}) as Record<
|
|
4
|
+
string,
|
|
5
|
+
string | undefined
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export function normalizeReaderEnvValue(value: string | null | undefined): string | undefined {
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
if (!trimmed) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
20
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
21
|
+
) {
|
|
22
|
+
return trimmed.slice(1, -1).trim() || undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isClearlyInvalidBookRootValue(value: string | null | undefined): boolean {
|
|
29
|
+
const normalized = normalizeReaderEnvValue(value);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return normalized === "/" || normalized === "\\" || /^[a-zA-Z]:(?:[\\/])?$/.test(normalized);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readReaderEnv(keys: string[], sources: EnvSource[] = [process.env, astroEnv]): string | undefined {
|
|
38
|
+
for (const source of sources) {
|
|
39
|
+
if (!source) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
const value = normalizeReaderEnvValue(source[key]);
|
|
45
|
+
if (value) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readReaderBookRootEnv(sources: EnvSource[] = [process.env, astroEnv]): string | undefined {
|
|
55
|
+
const value = readReaderEnv(["NARRARIUM_BOOK_ROOT", "GHOSTWRITER_BOOK_ROOT"], sources);
|
|
56
|
+
if (!value || isClearlyInvalidBookRootValue(value)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value;
|
|
61
|
+
}
|
package/src/lib/reader-mode.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { readReaderEnv } from "./env.js";
|
|
2
|
+
|
|
1
3
|
export function isFullCanonMode(): boolean {
|
|
2
|
-
const raw = String(
|
|
3
|
-
process.env.NARRARIUM_READER_CANON_MODE ?? process.env.NARRARIUM_READER_ALLOW_FULL_CANON ?? "",
|
|
4
|
-
)
|
|
4
|
+
const raw = String(readReaderEnv(["NARRARIUM_READER_CANON_MODE", "NARRARIUM_READER_ALLOW_FULL_CANON"]) ?? "")
|
|
5
5
|
.trim()
|
|
6
6
|
.toLowerCase();
|
|
7
7
|
|
package/src/pages/index.astro
CHANGED
|
@@ -40,7 +40,7 @@ const chapterCards = await Promise.all(
|
|
|
40
40
|
<div class="grid stats">
|
|
41
41
|
<div class="card">
|
|
42
42
|
<div class="label">Book Root</div>
|
|
43
|
-
<div class="value">{root}</div>
|
|
43
|
+
<div class="value value-path">{root}</div>
|
|
44
44
|
</div>
|
|
45
45
|
<div class="card">
|
|
46
46
|
<div class="label">Chapters</div>
|
package/src/scaffold.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cp, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isClearlyInvalidBookRootValue, normalizeReaderEnvValue } from "./lib/env.js";
|
|
4
5
|
|
|
5
6
|
type ScaffoldOptions = {
|
|
6
7
|
bookRoot?: string;
|
|
@@ -85,15 +86,15 @@ export async function scaffoldReaderSite(targetDir: string, options: ScaffoldOpt
|
|
|
85
86
|
|
|
86
87
|
await writeFile(
|
|
87
88
|
path.join(targetRoot, ".env.example"),
|
|
88
|
-
|
|
89
|
-
`NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}`,
|
|
90
|
-
"# NARRARIUM_READER_CANON_MODE=full",
|
|
91
|
-
"# EPUBCHECK_CMD=epubcheck",
|
|
92
|
-
"# EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar",
|
|
93
|
-
"",
|
|
94
|
-
].join("\n"),
|
|
89
|
+
buildReaderEnvFile(bookRoot),
|
|
95
90
|
"utf8",
|
|
96
91
|
);
|
|
92
|
+
const envPath = path.join(targetRoot, ".env");
|
|
93
|
+
const existingEnv = await readFile(envPath, "utf8").catch(() => null);
|
|
94
|
+
const nextEnv = mergeReaderEnvFile(existingEnv, bookRoot);
|
|
95
|
+
if (nextEnv !== existingEnv) {
|
|
96
|
+
await writeFile(envPath, nextEnv, "utf8");
|
|
97
|
+
}
|
|
97
98
|
await writeFile(path.join(targetRoot, ".gitignore"), "node_modules/\ndist/\n.astro/\n.env\npublic/downloads/\n", "utf8");
|
|
98
99
|
await writeFile(
|
|
99
100
|
path.join(targetRoot, "README.md"),
|
|
@@ -116,7 +117,7 @@ This site was scaffolded from \`narrarium-astro-reader\`.
|
|
|
116
117
|
|
|
117
118
|
## Configure
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
The scaffold creates a local \`.env\` with the book root already filled in. Adjust it if this reader should point somewhere else:
|
|
120
121
|
|
|
121
122
|
\`\`\`bash
|
|
122
123
|
NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}
|
|
@@ -166,6 +167,47 @@ function buildBookConfigScript(bookRoot: string): string {
|
|
|
166
167
|
return `export const defaultBookRoot = ${JSON.stringify(toPosix(bookRoot))};\n`;
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
function buildReaderEnvFile(bookRoot: string): string {
|
|
171
|
+
return [
|
|
172
|
+
`NARRARIUM_BOOK_ROOT=${toPosix(bookRoot)}`,
|
|
173
|
+
"# NARRARIUM_READER_CANON_MODE=full",
|
|
174
|
+
"# EPUBCHECK_CMD=epubcheck",
|
|
175
|
+
"# EPUBCHECK_JAR=/absolute/path/to/epubcheck.jar",
|
|
176
|
+
"",
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mergeReaderEnvFile(existingContent: string | null, bookRoot: string): string {
|
|
181
|
+
const desiredRoot = toPosix(bookRoot);
|
|
182
|
+
if (existingContent === null) {
|
|
183
|
+
return buildReaderEnvFile(bookRoot);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const lines = existingContent.split(/\r?\n/);
|
|
187
|
+
let handled = false;
|
|
188
|
+
|
|
189
|
+
const nextLines = lines.map((line) => {
|
|
190
|
+
const match = line.match(/^(\s*NARRARIUM_BOOK_ROOT\s*=\s*)(.*)$/);
|
|
191
|
+
if (!match) {
|
|
192
|
+
return line;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
handled = true;
|
|
196
|
+
const currentValue = normalizeReaderEnvValue(match[2]);
|
|
197
|
+
if (currentValue && !isClearlyInvalidBookRootValue(currentValue)) {
|
|
198
|
+
return line;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return `${match[1]}${desiredRoot}`;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (handled) {
|
|
205
|
+
return nextLines.join("\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `${buildReaderEnvFile(bookRoot).trimEnd()}\n${existingContent}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
169
211
|
function buildPagesWorkflow(pagesDomain?: string): string {
|
|
170
212
|
const envBlock = pagesDomain
|
|
171
213
|
? [" SITE_BASE: /", ` SITE_URL: https://${pagesDomain}`].join("\n")
|