@dogsbay/format-gitbook 0.2.0-beta.131 → 0.2.0-beta.133

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/dist/corpus.d.ts CHANGED
@@ -1,18 +1,2 @@
1
- export interface Corpus {
2
- /** Every file, posix, relative to the corpus root; plain paths only (`isPlainPath`). */
3
- paths: string[];
4
- /** The file's text, or undefined when it is not in the corpus. */
5
- text(path: string): string | undefined;
6
- /**
7
- * Does `path` still lie inside the folder `dir` once links resolve? A folder corpus answers
8
- * (a symlink in the docs root must not bring in the repo's `.env` or `.git`); a map of files
9
- * has no links and leaves it out.
10
- */
11
- contains?(path: string, dir: string): boolean;
12
- }
13
- export type CorpusFiles = Map<string, string | Uint8Array>;
14
- /**
15
- * A corpus over a map of files; bytes are read as UTF-8. A key that is not a plain path
16
- * (`../x`, `/etc/x`, `a/./b`, a backslash) is left out: the keys come from someone else's repo.
17
- */
18
- export declare function corpusFromMap(files: CorpusFiles): Corpus;
1
+ /** The corpus an importer reads: `@dogsbay/corpus`, shared by every format's browser import. */
2
+ export { corpusFromMap, type Corpus, type CorpusFiles, type Unfetched, unfetchedMessage } from "@dogsbay/corpus";
package/dist/corpus.js CHANGED
@@ -1,26 +1,2 @@
1
- /**
2
- * A corpus as the importer sees it: its file paths (posix, relative to the corpus root) and a
3
- * way to read one as text. The CLI builds one from a folder (`loaders/folder.ts`, which also
4
- * keeps symlinks inside it); the browser builds one from a map of files (`corpusFromMap`).
5
- * Assets are never read through it: the project lists them as copies, and each host copies
6
- * the bytes its own way.
7
- */
8
- import { isPlainPath } from "@dogsbay/detect";
9
- /**
10
- * A corpus over a map of files; bytes are read as UTF-8. A key that is not a plain path
11
- * (`../x`, `/etc/x`, `a/./b`, a backslash) is left out: the keys come from someone else's repo.
12
- */
13
- export function corpusFromMap(files) {
14
- const decoder = new TextDecoder();
15
- const paths = [...files.keys()].filter(isPlainPath).sort();
16
- const listed = new Set(paths);
17
- return {
18
- paths,
19
- text(path) {
20
- if (!listed.has(path))
21
- return undefined;
22
- const v = files.get(path);
23
- return v === undefined ? undefined : typeof v === "string" ? v : decoder.decode(v);
24
- },
25
- };
26
- }
1
+ /** The corpus an importer reads: `@dogsbay/corpus`, shared by every format's browser import. */
2
+ export { corpusFromMap, unfetchedMessage } from "@dogsbay/corpus";
@@ -1,19 +1,7 @@
1
- import type { Corpus } from "../corpus.js";
1
+ import { readFolder, type FolderCorpus } from "@dogsbay/corpus/folder";
2
2
  import { type GitbookConfig } from "./config.js";
3
3
  import { type GitbookLoadOptions, type GitbookLoadResult } from "./load.js";
4
- export interface FolderCorpus extends Corpus {
5
- /** The folder's absolute path. */
6
- root: string;
7
- /** A corpus path as an absolute path on disk. */
8
- absolute(path: string): string;
9
- }
10
- /**
11
- * List `dir` (skipping `node_modules` and `.git`) and read files from it on demand. A
12
- * directory reached by two names is listed under the first one walked, so `first` names the
13
- * folders to walk before the rest (the docs roots): a `docs -> content-src` link must list the
14
- * pages under `docs/`, where `root: ./docs/` looks for them.
15
- */
16
- export declare function readFolder(dir: string, maxDepth?: number, first?: string[]): FolderCorpus;
4
+ export { readFolder, type FolderCorpus };
17
5
  /** Read `.gitbook.yaml` from `dir` if present; else return defaults. */
18
6
  export declare function readGitbookConfig(dir: string): GitbookConfig;
19
7
  /**
@@ -1,126 +1,15 @@
1
1
  /**
2
- * Node only: a folder as a {@link Corpus}, and the CLI's on-disk detector. Everything else in
3
- * the loader reads the corpus, so the CLI and the browser import run the same code.
4
- *
5
- * The walk keeps to the folder once links resolve, and visits each directory once: a symlink
6
- * out of the repo read pages from elsewhere, and two links to `.` made the walk branch twice
7
- * per level until ELOOP (about 2^40 visits). Text is read only when asked for.
2
+ * Node only: a GitBook folder as a corpus (`@dogsbay/corpus/folder`, its docs roots walked
3
+ * first), and the CLI's on-disk detector. Everything else in the loader reads the corpus, so
4
+ * the CLI and the browser import run the same code.
8
5
  */
9
- import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
10
- import { join, sep } from "node:path";
11
- import { detectGitbook as detectGitbookPaths, gitbookSpaces, isPlainPath, joinPath, normalizePath } from "@dogsbay/detect";
6
+ import { existsSync, readFileSync, statSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { detectGitbook as detectGitbookPaths, gitbookSpaces, joinPath, normalizePath } from "@dogsbay/detect";
9
+ import { readFolder } from "@dogsbay/corpus/folder";
12
10
  import { parseGitbookConfig } from "./config.js";
13
11
  import { loadGitbookCorpus } from "./load.js";
14
- /**
15
- * List `dir` (skipping `node_modules` and `.git`) and read files from it on demand. A
16
- * directory reached by two names is listed under the first one walked, so `first` names the
17
- * folders to walk before the rest (the docs roots): a `docs -> content-src` link must list the
18
- * pages under `docs/`, where `root: ./docs/` looks for them.
19
- */
20
- export function readFolder(dir, maxDepth = Infinity, first = []) {
21
- const paths = [];
22
- /** Each listed path's real path, for `contains`. */
23
- const reals = new Map();
24
- let realRoot;
25
- try {
26
- realRoot = realpathSync(dir);
27
- }
28
- catch {
29
- realRoot = "";
30
- }
31
- if (realRoot) {
32
- const seen = new Set([realRoot]);
33
- const walk = (abs, rel, depth) => {
34
- let entries;
35
- try {
36
- entries = readdirSync(abs).sort();
37
- }
38
- catch {
39
- return;
40
- }
41
- for (const name of entries) {
42
- if (name === "node_modules" || name === ".git")
43
- continue;
44
- const full = join(abs, name);
45
- const path = rel ? `${rel}/${name}` : name;
46
- if (!isPlainPath(path))
47
- continue;
48
- let real;
49
- let st;
50
- try {
51
- real = realpathSync(full);
52
- if (real !== realRoot && !real.startsWith(realRoot + sep))
53
- continue;
54
- st = statSync(real);
55
- }
56
- catch {
57
- continue;
58
- }
59
- if (st.isDirectory()) {
60
- if (seen.has(real) || depth + 1 > maxDepth)
61
- continue;
62
- seen.add(real);
63
- walk(full, path, depth + 1);
64
- }
65
- else if (st.isFile()) {
66
- paths.push(path);
67
- reals.set(path, real);
68
- }
69
- }
70
- };
71
- for (const d of first) {
72
- if (!d || !isPlainPath(d))
73
- continue;
74
- let real;
75
- try {
76
- real = realpathSync(join(dir, d));
77
- if (!real.startsWith(realRoot + sep) || seen.has(real) || !statSync(real).isDirectory())
78
- continue;
79
- }
80
- catch {
81
- continue;
82
- }
83
- seen.add(real);
84
- walk(join(dir, d), d, d.split("/").length);
85
- }
86
- walk(dir, "", 0);
87
- }
88
- const listed = new Set(paths);
89
- const realDirs = new Map();
90
- const realDir = (d) => {
91
- if (!realDirs.has(d)) {
92
- let r;
93
- try {
94
- r = realpathSync(join(dir, d));
95
- }
96
- catch {
97
- r = undefined;
98
- }
99
- realDirs.set(d, r);
100
- }
101
- return realDirs.get(d);
102
- };
103
- return {
104
- root: dir,
105
- paths,
106
- absolute: (path) => join(dir, path),
107
- contains(path, d) {
108
- const real = reals.get(path);
109
- const root = realDir(d);
110
- return !!real && !!root && (real === root || real.startsWith(root + sep));
111
- },
112
- text(path) {
113
- if (!listed.has(path))
114
- return undefined;
115
- try {
116
- return readFileSync(join(dir, path), "utf-8");
117
- }
118
- catch {
119
- return undefined;
120
- }
121
- },
122
- };
123
- }
12
+ export { readFolder };
124
13
  /** Read `.gitbook.yaml` from `dir` if present; else return defaults. */
125
14
  export function readGitbookConfig(dir) {
126
15
  const p = join(dir, ".gitbook.yaml");
package/dist/migrate.d.ts CHANGED
@@ -5,11 +5,17 @@
5
5
  * no console, clock or network. Find the corpus with `@dogsbay/detect` first.
6
6
  */
7
7
  import type { ImportLedgerEntry } from "@dogsbay/types";
8
- import { type CorpusFiles } from "./corpus.js";
9
- export type { CorpusFiles } from "./corpus.js";
8
+ import { type CorpusFiles, type Unfetched } from "./corpus.js";
9
+ export type { CorpusFiles, Unfetched } from "./corpus.js";
10
10
  export interface MigrateInput {
11
11
  /** The corpus: paths relative to its root (a detection's `root`), text or bytes. */
12
12
  files: CorpusFiles;
13
+ /**
14
+ * Every path of the project (relative to the same root as `files`), when the host lists more
15
+ * than it fetched (a GitHub tree). The import then sees the repo as it is, and `unfetched`
16
+ * says what it needed from those not given: fetch them and call `migrate` again.
17
+ */
18
+ paths?: Iterable<string>;
13
19
  /** One space of a multi-space repo (a detection's `choices.spaces`); all when omitted. */
14
20
  choose?: {
15
21
  space?: string;
@@ -28,5 +34,14 @@ export interface MigrateResult {
28
34
  from: string;
29
35
  to: string;
30
36
  }[];
37
+ /**
38
+ * The listed `paths` the conversion needed but `files` did not hold. `read`: it read them,
39
+ * so fetch them and convert again (each round can find one more level of an include or
40
+ * `require` chain: cap the rounds). While `read` is not empty the result is not a
41
+ * conversion; when the import could not go on, `files` is empty and the ledger says where
42
+ * it stopped. `copy`: assets the output copies; fetch them for the finished files, no
43
+ * rerun. Both empty: the result is complete.
44
+ */
45
+ unfetched: Unfetched;
31
46
  }
32
47
  export declare function migrate(input: MigrateInput): Promise<MigrateResult>;
package/dist/migrate.js CHANGED
@@ -1,19 +1,38 @@
1
- import { corpusFromMap } from "./corpus.js";
1
+ import { corpusFromMap, unfetchedMessage } from "./corpus.js";
2
2
  import { importGitbookCorpus } from "./import.js";
3
3
  import { gitbookProject } from "./project.js";
4
+ /** The ledger line for a conversion that still needs files, or nothing. */
5
+ function unfetchedLedger(read, stoppedOn) {
6
+ const message = unfetchedMessage(read, stoppedOn);
7
+ return message ? [{ severity: "warning", code: "unfetched", message }] : [];
8
+ }
4
9
  export async function migrate(input) {
5
- const corpus = corpusFromMap(input.files);
6
- const imported = importGitbookCorpus(corpus, { space: input.choose?.space });
7
- const project = gitbookProject(imported, corpus, {
8
- siteName: input.siteName?.trim() || "Docs",
9
- siteUrl: input.siteUrl,
10
- sourceLabel: input.sourceLabel ?? "GitBook corpus",
11
- });
10
+ const corpus = corpusFromMap(input.files, { paths: input.paths });
11
+ let project;
12
+ try {
13
+ const imported = importGitbookCorpus(corpus, { space: input.choose?.space });
14
+ project = gitbookProject(imported, corpus, {
15
+ siteName: input.siteName?.trim() || "Docs",
16
+ siteUrl: input.siteUrl,
17
+ sourceLabel: input.sourceLabel ?? "GitBook corpus",
18
+ });
19
+ }
20
+ catch (e) {
21
+ // A file it needed was not given: that, not the error, is the answer
22
+ const read = corpus.missing();
23
+ if (read.length)
24
+ return { files: new Map(), ledger: unfetchedLedger(read, e), redirects: [], unfetched: { read, copy: [] } };
25
+ throw e;
26
+ }
12
27
  const files = new Map(project.files);
28
+ const copy = [];
13
29
  for (const { from, to } of project.assets) {
14
30
  const bytes = input.files.get(from);
15
31
  if (bytes !== undefined)
16
32
  files.set(to, bytes);
33
+ else
34
+ copy.push(from);
17
35
  }
18
- return { files, ledger: project.ledger, redirects: project.redirects };
36
+ const read = corpus.missing();
37
+ return { files, ledger: [...unfetchedLedger(read), ...project.ledger], redirects: project.redirects, unfetched: { read, copy: copy.sort() } };
19
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/format-gitbook",
3
- "version": "0.2.0-beta.131",
3
+ "version": "0.2.0-beta.133",
4
4
  "description": "GitBook format plugin for Dogsbay — imports GitBook Git Sync (and legacy gitbook-cli) corpora by translating the Git Sync markdown dialect to Dogsbay-MD. ./migrate converts a corpus given as a map of files in the browser (no Node), the same import the CLI runs.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,9 +25,10 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "js-yaml": "^5.2.1",
28
- "@dogsbay/detect": "0.2.0-beta.131",
29
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.131",
30
- "@dogsbay/types": "0.2.0-beta.131"
28
+ "@dogsbay/corpus": "0.2.0-beta.133",
29
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.133",
30
+ "@dogsbay/detect": "0.2.0-beta.133",
31
+ "@dogsbay/types": "0.2.0-beta.133"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@types/js-yaml": "^4.0.9",