@wafflebase/cli 0.4.9 → 0.5.1
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/bin.js +99988 -11
- package/package.json +7 -6
- package/dist/bin.d.ts +0 -2
- package/dist/client/dry-run.d.ts +0 -5
- package/dist/client/dry-run.js +0 -16
- package/dist/client/http-client.d.ts +0 -59
- package/dist/client/http-client.js +0 -178
- package/dist/commands/api-keys.d.ts +0 -2
- package/dist/commands/api-keys.js +0 -54
- package/dist/commands/cells.d.ts +0 -2
- package/dist/commands/cells.js +0 -108
- package/dist/commands/ctx.d.ts +0 -5
- package/dist/commands/ctx.js +0 -56
- package/dist/commands/docs.d.ts +0 -3
- package/dist/commands/docs.js +0 -320
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -220
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.js +0 -10
- package/dist/commands/root.d.ts +0 -18
- package/dist/commands/root.js +0 -37
- package/dist/commands/schema.d.ts +0 -2
- package/dist/commands/schema.js +0 -28
- package/dist/commands/sheets-export.d.ts +0 -2
- package/dist/commands/sheets-export.js +0 -52
- package/dist/commands/sheets-import.d.ts +0 -2
- package/dist/commands/sheets-import.js +0 -88
- package/dist/commands/sheets.d.ts +0 -2
- package/dist/commands/sheets.js +0 -17
- package/dist/commands/slides.d.ts +0 -3
- package/dist/commands/slides.js +0 -227
- package/dist/commands/status.d.ts +0 -2
- package/dist/commands/status.js +0 -22
- package/dist/commands/tabs.d.ts +0 -2
- package/dist/commands/tabs.js +0 -20
- package/dist/config/config.d.ts +0 -38
- package/dist/config/config.js +0 -135
- package/dist/config/session.d.ts +0 -49
- package/dist/config/session.js +0 -74
- package/dist/docs/content.d.ts +0 -37
- package/dist/docs/content.js +0 -90
- package/dist/docs/docx-export.d.ts +0 -23
- package/dist/docs/docx-export.js +0 -14
- package/dist/docs/docx-import.d.ts +0 -36
- package/dist/docs/docx-import.js +0 -70
- package/dist/docs/dom-polyfill.d.ts +0 -1
- package/dist/docs/dom-polyfill.js +0 -29
- package/dist/docs/fontkit-measurer.d.ts +0 -41
- package/dist/docs/fontkit-measurer.js +0 -64
- package/dist/docs/image-fetcher.d.ts +0 -37
- package/dist/docs/image-fetcher.js +0 -38
- package/dist/docs/import.d.ts +0 -72
- package/dist/docs/import.js +0 -146
- package/dist/docs/page-range.d.ts +0 -29
- package/dist/docs/page-range.js +0 -74
- package/dist/docs/page-slice.d.ts +0 -45
- package/dist/docs/page-slice.js +0 -61
- package/dist/docs/paginate.d.ts +0 -13
- package/dist/docs/paginate.js +0 -19
- package/dist/docs/pdf-export.d.ts +0 -47
- package/dist/docs/pdf-export.js +0 -95
- package/dist/output/binary.d.ts +0 -37
- package/dist/output/binary.js +0 -47
- package/dist/output/csv.d.ts +0 -4
- package/dist/output/csv.js +0 -25
- package/dist/output/formatter.d.ts +0 -4
- package/dist/output/formatter.js +0 -41
- package/dist/output/json.d.ts +0 -1
- package/dist/output/json.js +0 -3
- package/dist/output/table.d.ts +0 -4
- package/dist/output/table.js +0 -14
- package/dist/schema/registry.d.ts +0 -42
- package/dist/schema/registry.js +0 -375
- package/dist/slides/content.d.ts +0 -41
- package/dist/slides/content.js +0 -122
- package/dist/slides/import.d.ts +0 -82
- package/dist/slides/import.js +0 -170
- package/dist/slides/pptx-export.d.ts +0 -6
- package/dist/slides/pptx-export.js +0 -10
- package/dist/slides/pptx-import.d.ts +0 -35
- package/dist/slides/pptx-import.js +0 -49
- package/dist/util/csv-parse.d.ts +0 -21
- package/dist/util/csv-parse.js +0 -99
package/dist/config/session.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, chmodSync, } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
/**
|
|
5
|
-
* Returns the default session file path: ~/.wafflebase/session.json
|
|
6
|
-
*/
|
|
7
|
-
export function getSessionPath() {
|
|
8
|
-
if (process.env.WAFFLEBASE_SESSION) {
|
|
9
|
-
return process.env.WAFFLEBASE_SESSION;
|
|
10
|
-
}
|
|
11
|
-
return join(homedir(), '.wafflebase', 'session.json');
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Loads the session from disk. Returns null if the file does not exist or
|
|
15
|
-
* contains invalid JSON.
|
|
16
|
-
*/
|
|
17
|
-
export function loadSession(path = getSessionPath()) {
|
|
18
|
-
if (!existsSync(path))
|
|
19
|
-
return null;
|
|
20
|
-
try {
|
|
21
|
-
const raw = readFileSync(path, 'utf-8');
|
|
22
|
-
return JSON.parse(raw);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Writes the session to disk with owner-only (0600) permissions.
|
|
30
|
-
* Creates the parent directory if it does not exist.
|
|
31
|
-
*/
|
|
32
|
-
export function saveSession(session, path = getSessionPath()) {
|
|
33
|
-
const dir = dirname(path);
|
|
34
|
-
mkdirSync(dir, { recursive: true });
|
|
35
|
-
writeFileSync(path, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
36
|
-
// Explicitly chmod in case the file already existed with different permissions
|
|
37
|
-
chmodSync(path, 0o600);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Deletes the session file if it exists. No-op otherwise.
|
|
41
|
-
*/
|
|
42
|
-
export function clearSession(path = getSessionPath()) {
|
|
43
|
-
if (existsSync(path)) {
|
|
44
|
-
unlinkSync(path);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Returns true when the session's expiresAt timestamp is in the past.
|
|
49
|
-
*/
|
|
50
|
-
export function isSessionExpired(session) {
|
|
51
|
-
return new Date(session.expiresAt) <= new Date();
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Decodes the `exp` claim from a JWT's payload segment and returns an ISO
|
|
55
|
-
* 8601 string.
|
|
56
|
-
*
|
|
57
|
-
* JWT format: <header>.<payload>.<signature>
|
|
58
|
-
* The payload is base64url-encoded JSON.
|
|
59
|
-
*/
|
|
60
|
-
export function decodeJwtExpiry(token) {
|
|
61
|
-
const parts = token.split('.');
|
|
62
|
-
if (parts.length < 2) {
|
|
63
|
-
throw new Error('Invalid JWT: expected at least 2 segments');
|
|
64
|
-
}
|
|
65
|
-
// Add padding back if needed for base64 decoding
|
|
66
|
-
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
67
|
-
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
68
|
-
const decoded = Buffer.from(padded, 'base64').toString('utf-8');
|
|
69
|
-
const payload = JSON.parse(decoded);
|
|
70
|
-
if (typeof payload.exp !== 'number') {
|
|
71
|
-
throw new Error('JWT payload does not contain an exp claim');
|
|
72
|
-
}
|
|
73
|
-
return new Date(payload.exp * 1000).toISOString();
|
|
74
|
-
}
|
package/dist/docs/content.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { type Document } from '@wafflebase/docs';
|
|
2
|
-
export declare const VALID_CONTENT_FORMATS: readonly ["json", "md", "text"];
|
|
3
|
-
export type ContentFormat = (typeof VALID_CONTENT_FORMATS)[number];
|
|
4
|
-
export declare const LOSSY_NOTICE = "Lossy conversion: see docs-cli design for the exact mapping";
|
|
5
|
-
export declare const PAGES_FONT_WARNING: string;
|
|
6
|
-
export declare function parseContentFormat(value: string): ContentFormat;
|
|
7
|
-
/**
|
|
8
|
-
* Surface for `runDocsContent` to talk back to the CLI without taking a
|
|
9
|
-
* direct dependency on `process.stdout` / `process.stderr` / `fs`. The
|
|
10
|
-
* command wires the real implementations; tests inject in-memory
|
|
11
|
-
* collectors.
|
|
12
|
-
*/
|
|
13
|
-
export interface ContentIO {
|
|
14
|
-
stdout: (text: string) => void;
|
|
15
|
-
stderr: (line: string) => void;
|
|
16
|
-
/** Write `text` to `path`. Throws if the file exists and `force` is false. */
|
|
17
|
-
writeFile: (path: string, text: string, force: boolean) => void;
|
|
18
|
-
}
|
|
19
|
-
export declare const defaultIO: ContentIO;
|
|
20
|
-
export interface RunContentArgs {
|
|
21
|
-
doc: Document;
|
|
22
|
-
format: ContentFormat;
|
|
23
|
-
pages?: string;
|
|
24
|
-
includeHeaderFooter?: boolean;
|
|
25
|
-
inlineImages?: boolean;
|
|
26
|
-
out?: string;
|
|
27
|
-
force?: boolean;
|
|
28
|
-
quiet?: boolean;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Pure orchestration for `docs content`: takes an already-fetched
|
|
32
|
-
* Document plus user flags, produces the rendered output, and routes it
|
|
33
|
-
* through the supplied IO surface. Everything between the HTTP fetch
|
|
34
|
-
* and the side-effecting IO lives here so it can be tested without
|
|
35
|
-
* spawning the CLI binary.
|
|
36
|
-
*/
|
|
37
|
-
export declare function runDocsContent(args: RunContentArgs, io?: ContentIO): void;
|
package/dist/docs/content.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { writeFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { serializeJson, serializeMarkdown, serializeText, } from '@wafflebase/docs';
|
|
3
|
-
import { FontkitMeasurer } from './fontkit-measurer.js';
|
|
4
|
-
import { parsePageRange } from './page-range.js';
|
|
5
|
-
import { sliceBlocksByPages } from './page-slice.js';
|
|
6
|
-
import { paginateForCli } from './paginate.js';
|
|
7
|
-
export const VALID_CONTENT_FORMATS = ['json', 'md', 'text'];
|
|
8
|
-
export const LOSSY_NOTICE = 'Lossy conversion: see docs-cli design for the exact mapping';
|
|
9
|
-
export const PAGES_FONT_WARNING = 'Note: --pages uses approximate pagination (no document fonts loaded). ' +
|
|
10
|
-
'Page boundaries may drift on CJK or font-heavy documents — ' +
|
|
11
|
-
'use `docs export pdf --pages` for exact subsets.';
|
|
12
|
-
export function parseContentFormat(value) {
|
|
13
|
-
if (!VALID_CONTENT_FORMATS.includes(value)) {
|
|
14
|
-
throw new Error(`Invalid --format "${value}". Use one of: ${VALID_CONTENT_FORMATS.join(', ')}.`);
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
export const defaultIO = {
|
|
19
|
-
stdout: (text) => {
|
|
20
|
-
process.stdout.write(text);
|
|
21
|
-
if (!text.endsWith('\n'))
|
|
22
|
-
process.stdout.write('\n');
|
|
23
|
-
},
|
|
24
|
-
stderr: (line) => {
|
|
25
|
-
console.error(line);
|
|
26
|
-
},
|
|
27
|
-
writeFile: (path, text, force) => {
|
|
28
|
-
if (existsSync(path) && !force) {
|
|
29
|
-
throw new Error(`Refusing to overwrite "${path}". Pass --force to allow overwrite.`);
|
|
30
|
-
}
|
|
31
|
-
writeFileSync(path, text.endsWith('\n') ? text : text + '\n', 'utf-8');
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* Pure orchestration for `docs content`: takes an already-fetched
|
|
36
|
-
* Document plus user flags, produces the rendered output, and routes it
|
|
37
|
-
* through the supplied IO surface. Everything between the HTTP fetch
|
|
38
|
-
* and the side-effecting IO lives here so it can be tested without
|
|
39
|
-
* spawning the CLI binary.
|
|
40
|
-
*/
|
|
41
|
-
export function runDocsContent(args, io = defaultIO) {
|
|
42
|
-
const { format, pages, includeHeaderFooter = false, inlineImages = false, out, force = false, quiet = false, } = args;
|
|
43
|
-
let working = args.doc;
|
|
44
|
-
let layout;
|
|
45
|
-
if (pages) {
|
|
46
|
-
const measurer = new FontkitMeasurer();
|
|
47
|
-
// `docs content --pages` paginates with a font-less measurer, so
|
|
48
|
-
// every glyph falls back to the 0.5em estimate. That's accurate
|
|
49
|
-
// enough for ASCII-heavy docs but underestimates CJK by ~50% and
|
|
50
|
-
// mis-sizes narrow glyphs (`i`, `:`, `.`) — page boundaries can
|
|
51
|
-
// drift by ±1 page on real documents. `docs export pdf` doesn't
|
|
52
|
-
// suffer from this because PdfPainter does its own measurement at
|
|
53
|
-
// paint time. Pre-loading document fonts here is tracked as a
|
|
54
|
-
// follow-up; until then we surface the limitation on stderr.
|
|
55
|
-
if (!quiet) {
|
|
56
|
-
io.stderr(PAGES_FONT_WARNING);
|
|
57
|
-
}
|
|
58
|
-
const fullLayout = paginateForCli(working, measurer);
|
|
59
|
-
const range = parsePageRange(pages, fullLayout.pages.length);
|
|
60
|
-
if (!quiet) {
|
|
61
|
-
for (const w of range.warnings)
|
|
62
|
-
io.stderr(w);
|
|
63
|
-
}
|
|
64
|
-
const slice = sliceBlocksByPages(working, fullLayout, range, format);
|
|
65
|
-
working = { ...working, blocks: slice.blocks };
|
|
66
|
-
layout = fullLayout;
|
|
67
|
-
}
|
|
68
|
-
if (format === 'md' && !quiet) {
|
|
69
|
-
io.stderr(LOSSY_NOTICE);
|
|
70
|
-
}
|
|
71
|
-
let text;
|
|
72
|
-
switch (format) {
|
|
73
|
-
case 'json':
|
|
74
|
-
text = JSON.stringify(serializeJson(working, layout), null, 2);
|
|
75
|
-
break;
|
|
76
|
-
case 'md':
|
|
77
|
-
text = serializeMarkdown(working, { includeHeaderFooter, inlineImages });
|
|
78
|
-
break;
|
|
79
|
-
case 'text':
|
|
80
|
-
text = serializeText(working, { includeHeaderFooter });
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
if (!out || out === '-') {
|
|
84
|
-
io.stdout(text);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
io.writeFile(out, text, force);
|
|
88
|
-
if (!quiet)
|
|
89
|
-
io.stderr(`Wrote to ${out}`);
|
|
90
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { type Document, type DocxImageFetcher } from '@wafflebase/docs';
|
|
2
|
-
export interface CliDocxExportOptions {
|
|
3
|
-
/** Mirrors `--include-header-footer`; informational only — DOCX always
|
|
4
|
-
* emits the document's `header` / `footer` regions when they exist. */
|
|
5
|
-
includeHeaderFooter?: boolean;
|
|
6
|
-
/** Test seam: provide a stub image fetcher so `exportDocx` doesn't
|
|
7
|
-
* reach out to the network for image inlines. The aliased
|
|
8
|
-
* `DocxImageFetcher` (vs. the bare `ImageFetcher`) survives the
|
|
9
|
-
* Node-bundle resolution path; the bare alias only exists on the
|
|
10
|
-
* browser bundle, so importing it would silently break under any TS
|
|
11
|
-
* config that prefers the `node` exports condition. */
|
|
12
|
-
imageFetcher?: DocxImageFetcher;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Render `doc` to a `.docx` byte buffer via the shared `DocxExporter`.
|
|
16
|
-
*
|
|
17
|
-
* `--pages` is intentionally not handled here — the design (§5.2) says
|
|
18
|
-
* the docx path warns and ignores it; the CLI command emits that
|
|
19
|
-
* warning before calling this function so the helper itself stays a
|
|
20
|
-
* pure renderer. Throws if the document contains image inlines and no
|
|
21
|
-
* `imageFetcher` is supplied (matching `DocxExporter.export` behavior).
|
|
22
|
-
*/
|
|
23
|
-
export declare function exportDocx(doc: Document, opts?: CliDocxExportOptions): Promise<Uint8Array>;
|
package/dist/docs/docx-export.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { DocxExporter, } from '@wafflebase/docs';
|
|
2
|
-
/**
|
|
3
|
-
* Render `doc` to a `.docx` byte buffer via the shared `DocxExporter`.
|
|
4
|
-
*
|
|
5
|
-
* `--pages` is intentionally not handled here — the design (§5.2) says
|
|
6
|
-
* the docx path warns and ignores it; the CLI command emits that
|
|
7
|
-
* warning before calling this function so the helper itself stays a
|
|
8
|
-
* pure renderer. Throws if the document contains image inlines and no
|
|
9
|
-
* `imageFetcher` is supplied (matching `DocxExporter.export` behavior).
|
|
10
|
-
*/
|
|
11
|
-
export async function exportDocx(doc, opts = {}) {
|
|
12
|
-
const blob = await DocxExporter.export(doc, opts.imageFetcher);
|
|
13
|
-
return new Uint8Array(await blob.arrayBuffer());
|
|
14
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import './dom-polyfill.js';
|
|
2
|
-
import { type Document, type ImageUploader } from '@wafflebase/docs';
|
|
3
|
-
/**
|
|
4
|
-
* Default `ImageUploader` for the CLI: encodes each image inline as a
|
|
5
|
-
* `data:` URL. The Yorkie store treats inline-image `src` values as
|
|
6
|
-
* opaque strings, so a self-contained data URL round-trips cleanly
|
|
7
|
-
* without needing a backend image upload endpoint.
|
|
8
|
-
*
|
|
9
|
-
* The MIME type is derived from the filename's extension (matching what
|
|
10
|
-
* `DocxImporter` infers from `.rels` content types). Unknown
|
|
11
|
-
* extensions fall back to `application/octet-stream` — pdf-lib /
|
|
12
|
-
* browser image rendering will refuse to display those, but at least
|
|
13
|
-
* the document round-trip stays lossless at the JSON layer.
|
|
14
|
-
*/
|
|
15
|
-
export declare const inlineBase64Uploader: ImageUploader;
|
|
16
|
-
export interface CliDocxImportOptions {
|
|
17
|
-
/** Override the default base64 uploader — primarily for tests. */
|
|
18
|
-
imageUploader?: ImageUploader;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Parse a .docx byte buffer into a `Document` using the editor's
|
|
22
|
-
* `DocxImporter`. The polyfill side-effect import at the top of this
|
|
23
|
-
* file installs `DOMParser` from `@xmldom/xmldom` so the importer's
|
|
24
|
-
* browser-targeted XML parsing works in Node.
|
|
25
|
-
*
|
|
26
|
-
* Throws a wrapped `INVALID_DOCX` error when the underlying importer
|
|
27
|
-
* cannot make sense of the buffer (missing `word/document.xml`,
|
|
28
|
-
* malformed XML, unsupported zip layout). The CLI command translates
|
|
29
|
-
* the wrapper into the documented `{ error.code: 'INVALID_DOCX' }`
|
|
30
|
-
* exit body without leaking implementation details.
|
|
31
|
-
*/
|
|
32
|
-
export declare function importDocx(buf: Uint8Array, opts?: CliDocxImportOptions): Promise<Document>;
|
|
33
|
-
export declare class InvalidDocxError extends Error {
|
|
34
|
-
readonly code = "INVALID_DOCX";
|
|
35
|
-
constructor(message: string);
|
|
36
|
-
}
|
package/dist/docs/docx-import.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import './dom-polyfill.js';
|
|
2
|
-
import { DocxImporter, } from '@wafflebase/docs';
|
|
3
|
-
/**
|
|
4
|
-
* Default `ImageUploader` for the CLI: encodes each image inline as a
|
|
5
|
-
* `data:` URL. The Yorkie store treats inline-image `src` values as
|
|
6
|
-
* opaque strings, so a self-contained data URL round-trips cleanly
|
|
7
|
-
* without needing a backend image upload endpoint.
|
|
8
|
-
*
|
|
9
|
-
* The MIME type is derived from the filename's extension (matching what
|
|
10
|
-
* `DocxImporter` infers from `.rels` content types). Unknown
|
|
11
|
-
* extensions fall back to `application/octet-stream` — pdf-lib /
|
|
12
|
-
* browser image rendering will refuse to display those, but at least
|
|
13
|
-
* the document round-trip stays lossless at the JSON layer.
|
|
14
|
-
*/
|
|
15
|
-
export const inlineBase64Uploader = async (blob, filename) => {
|
|
16
|
-
const arrayBuf = await blob.arrayBuffer();
|
|
17
|
-
const base64 = Buffer.from(arrayBuf).toString('base64');
|
|
18
|
-
const mime = blob.type || mimeFromFilename(filename) || 'application/octet-stream';
|
|
19
|
-
return `data:${mime};base64,${base64}`;
|
|
20
|
-
};
|
|
21
|
-
function mimeFromFilename(filename) {
|
|
22
|
-
const dot = filename.lastIndexOf('.');
|
|
23
|
-
if (dot < 0)
|
|
24
|
-
return undefined;
|
|
25
|
-
const ext = filename.slice(dot + 1).toLowerCase();
|
|
26
|
-
switch (ext) {
|
|
27
|
-
case 'png':
|
|
28
|
-
return 'image/png';
|
|
29
|
-
case 'jpg':
|
|
30
|
-
case 'jpeg':
|
|
31
|
-
return 'image/jpeg';
|
|
32
|
-
case 'gif':
|
|
33
|
-
return 'image/gif';
|
|
34
|
-
case 'webp':
|
|
35
|
-
return 'image/webp';
|
|
36
|
-
case 'bmp':
|
|
37
|
-
return 'image/bmp';
|
|
38
|
-
default:
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Parse a .docx byte buffer into a `Document` using the editor's
|
|
44
|
-
* `DocxImporter`. The polyfill side-effect import at the top of this
|
|
45
|
-
* file installs `DOMParser` from `@xmldom/xmldom` so the importer's
|
|
46
|
-
* browser-targeted XML parsing works in Node.
|
|
47
|
-
*
|
|
48
|
-
* Throws a wrapped `INVALID_DOCX` error when the underlying importer
|
|
49
|
-
* cannot make sense of the buffer (missing `word/document.xml`,
|
|
50
|
-
* malformed XML, unsupported zip layout). The CLI command translates
|
|
51
|
-
* the wrapper into the documented `{ error.code: 'INVALID_DOCX' }`
|
|
52
|
-
* exit body without leaking implementation details.
|
|
53
|
-
*/
|
|
54
|
-
export async function importDocx(buf, opts = {}) {
|
|
55
|
-
const arrayBuf = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
56
|
-
try {
|
|
57
|
-
return await DocxImporter.import(arrayBuf, opts.imageUploader ?? inlineBase64Uploader);
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
61
|
-
throw new InvalidDocxError(message);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
export class InvalidDocxError extends Error {
|
|
65
|
-
code = 'INVALID_DOCX';
|
|
66
|
-
constructor(message) {
|
|
67
|
-
super(message);
|
|
68
|
-
this.name = 'InvalidDocxError';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { DOMParser as XmldomParser } from '@xmldom/xmldom';
|
|
2
|
-
/**
|
|
3
|
-
* Make `DOMParser` available globally so `@wafflebase/docs`'s
|
|
4
|
-
* `DocxImporter` can call `new DOMParser()` from a Node CLI process.
|
|
5
|
-
*
|
|
6
|
-
* `DocxImporter` was originally written for the browser and parses
|
|
7
|
-
* `.docx` XML through the WHATWG DOMParser. Node has no built-in DOM,
|
|
8
|
-
* so we substitute `@xmldom/xmldom`'s implementation — it's an XML-only
|
|
9
|
-
* parser (no HTML quirks, no full DOM tree mutation) but covers the
|
|
10
|
-
* `getElementsByTagNameNS` / `textContent` / `getAttribute` surface the
|
|
11
|
-
* importer actually touches.
|
|
12
|
-
*
|
|
13
|
-
* The shim is idempotent: if a real `DOMParser` is already present
|
|
14
|
-
* (jsdom-environment tests, future Node releases that ship one) we
|
|
15
|
-
* leave it alone so production code continues to use the more featureful
|
|
16
|
-
* implementation.
|
|
17
|
-
*
|
|
18
|
-
* Side-effect import — bring this file in *before* any code path that
|
|
19
|
-
* calls `DocxImporter.import`.
|
|
20
|
-
*/
|
|
21
|
-
const g = globalThis;
|
|
22
|
-
if (typeof g.DOMParser === 'undefined') {
|
|
23
|
-
// Assign through an `unknown` cast because `@xmldom/xmldom`'s
|
|
24
|
-
// `DOMParser` shape is a runtime superset of WHATWG's but uses its
|
|
25
|
-
// own type declarations. `DocxImporter` only consumes the XML subset
|
|
26
|
-
// both implementations share, so the runtime contract holds even
|
|
27
|
-
// though the static types don't unify.
|
|
28
|
-
g.DOMParser = XmldomParser;
|
|
29
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer';
|
|
2
|
-
import type { TextMeasurer, ResolvedFont } from '@wafflebase/docs';
|
|
3
|
-
export interface FontkitMeasurerOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Width per character in em-units used when no font is registered for
|
|
6
|
-
* the requested variant. Multiplied by `font.size` to produce a px value.
|
|
7
|
-
* Defaults to `0.5` — a coarse proportional-font approximation that
|
|
8
|
-
* keeps pagination from collapsing to zero when a font is missing.
|
|
9
|
-
*
|
|
10
|
-
* Callers that pre-load every font referenced by a document never hit
|
|
11
|
-
* this path; the fallback exists so a single missing variant does not
|
|
12
|
-
* crash the CLI mid-pagination.
|
|
13
|
-
*/
|
|
14
|
-
fallbackEmWidth?: number;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* `TextMeasurer` backed by `fontkit` for use in Node CLI contexts where
|
|
18
|
-
* no Canvas 2D API is available. Width is computed as
|
|
19
|
-
* `glyphRun.advanceWidth ÷ unitsPerEm × size` for each registered font.
|
|
20
|
-
*
|
|
21
|
-
* Measurement is synchronous; callers must `register()` every font
|
|
22
|
-
* variant they need *before* invoking `paginateLayout` / `measureWidth`.
|
|
23
|
-
* Loading is async (file/network IO) but the layout pipeline is not, so
|
|
24
|
-
* we split the two phases.
|
|
25
|
-
*/
|
|
26
|
-
export declare class FontkitMeasurer implements TextMeasurer {
|
|
27
|
-
private readonly fonts;
|
|
28
|
-
private readonly fallbackEmWidth;
|
|
29
|
-
constructor(opts?: FontkitMeasurerOptions);
|
|
30
|
-
/**
|
|
31
|
-
* Register a font buffer for a specific variant. Subsequent
|
|
32
|
-
* `measureWidth` calls whose `(family, weight, style)` matches will use
|
|
33
|
-
* this font's metrics. A `FontCollection` (.ttc) input is rejected
|
|
34
|
-
* here — the caller must pick the desired face out of the collection
|
|
35
|
-
* first to keep the cache key unambiguous.
|
|
36
|
-
*/
|
|
37
|
-
register(family: string, weight: ResolvedFont['weight'], style: ResolvedFont['style'], buffer: Uint8Array | ArrayBuffer | Buffer): void;
|
|
38
|
-
/** True when the variant has a registered font (no fallback path). */
|
|
39
|
-
has(family: string, weight: ResolvedFont['weight'], style: ResolvedFont['style']): boolean;
|
|
40
|
-
measureWidth(text: string, font: ResolvedFont): number;
|
|
41
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer';
|
|
2
|
-
import * as fontkit from 'fontkit';
|
|
3
|
-
/**
|
|
4
|
-
* Variant key for the in-memory font cache. Lowercased family name plus
|
|
5
|
-
* weight/style ensures `'Helvetica'` and `'helvetica'` resolve to the same
|
|
6
|
-
* registered buffer — Canvas measurement is case-insensitive on family
|
|
7
|
-
* lookups, so we mirror that here so the CLI matches browser pagination.
|
|
8
|
-
*/
|
|
9
|
-
function variantKey(family, weight, style) {
|
|
10
|
-
return `${family.toLowerCase()}|${weight}|${style}`;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* `TextMeasurer` backed by `fontkit` for use in Node CLI contexts where
|
|
14
|
-
* no Canvas 2D API is available. Width is computed as
|
|
15
|
-
* `glyphRun.advanceWidth ÷ unitsPerEm × size` for each registered font.
|
|
16
|
-
*
|
|
17
|
-
* Measurement is synchronous; callers must `register()` every font
|
|
18
|
-
* variant they need *before* invoking `paginateLayout` / `measureWidth`.
|
|
19
|
-
* Loading is async (file/network IO) but the layout pipeline is not, so
|
|
20
|
-
* we split the two phases.
|
|
21
|
-
*/
|
|
22
|
-
export class FontkitMeasurer {
|
|
23
|
-
fonts = new Map();
|
|
24
|
-
fallbackEmWidth;
|
|
25
|
-
constructor(opts = {}) {
|
|
26
|
-
this.fallbackEmWidth = opts.fallbackEmWidth ?? 0.5;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Register a font buffer for a specific variant. Subsequent
|
|
30
|
-
* `measureWidth` calls whose `(family, weight, style)` matches will use
|
|
31
|
-
* this font's metrics. A `FontCollection` (.ttc) input is rejected
|
|
32
|
-
* here — the caller must pick the desired face out of the collection
|
|
33
|
-
* first to keep the cache key unambiguous.
|
|
34
|
-
*/
|
|
35
|
-
register(family, weight, style, buffer) {
|
|
36
|
-
const buf = toBuffer(buffer);
|
|
37
|
-
const parsed = fontkit.create(buf);
|
|
38
|
-
if ('fonts' in parsed) {
|
|
39
|
-
throw new Error(`FontkitMeasurer.register("${family}"): TrueType collections (.ttc) ` +
|
|
40
|
-
`must be split into individual faces before registration.`);
|
|
41
|
-
}
|
|
42
|
-
this.fonts.set(variantKey(family, weight, style), parsed);
|
|
43
|
-
}
|
|
44
|
-
/** True when the variant has a registered font (no fallback path). */
|
|
45
|
-
has(family, weight, style) {
|
|
46
|
-
return this.fonts.has(variantKey(family, weight, style));
|
|
47
|
-
}
|
|
48
|
-
measureWidth(text, font) {
|
|
49
|
-
if (text.length === 0)
|
|
50
|
-
return 0;
|
|
51
|
-
const f = this.fonts.get(variantKey(font.family, font.weight, font.style));
|
|
52
|
-
if (!f)
|
|
53
|
-
return text.length * this.fallbackEmWidth * font.size;
|
|
54
|
-
const run = f.layout(text);
|
|
55
|
-
return (run.advanceWidth / f.unitsPerEm) * font.size;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function toBuffer(input) {
|
|
59
|
-
if (Buffer.isBuffer(input))
|
|
60
|
-
return input;
|
|
61
|
-
if (input instanceof ArrayBuffer)
|
|
62
|
-
return Buffer.from(new Uint8Array(input));
|
|
63
|
-
return Buffer.from(input);
|
|
64
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { DocxImageFetcher } from '@wafflebase/docs';
|
|
2
|
-
export interface ImageFetcherOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Base URL of the Wafflebase API server. Used to resolve image
|
|
5
|
-
* inlines whose `src` is a server-relative path (`/images/<id>`).
|
|
6
|
-
* Absolute URLs (`http(s)://...`, `data:`, `blob:`, ...) pass through
|
|
7
|
-
* untouched so docs imported from external sources keep working.
|
|
8
|
-
*/
|
|
9
|
-
serverBase: string;
|
|
10
|
-
/**
|
|
11
|
-
* Optional `fetch` override — kept as a seam for tests. Defaults to
|
|
12
|
-
* the global `fetch`. The signature matches the WHATWG fetch so a
|
|
13
|
-
* stub can be a plain async function without pulling DOM types in.
|
|
14
|
-
*/
|
|
15
|
-
fetch?: typeof globalThis.fetch;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a possibly-relative image URL against the configured server
|
|
19
|
-
* base. Mirrors `resolveImageUrl` in
|
|
20
|
-
* `packages/frontend/src/app/docs/export-utils.ts` so the CLI and
|
|
21
|
-
* browser agree on what counts as "absolute". The shared rule: any URL
|
|
22
|
-
* that starts with a scheme (`http:`, `https:`, `data:`, `blob:`,
|
|
23
|
-
* `file:`, ...) is left alone; everything else gets prefixed.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveImageUrl(url: string, serverBase: string): string;
|
|
26
|
-
/**
|
|
27
|
-
* Build an `ImageFetcher` for the CLI export pipelines. The fetcher
|
|
28
|
-
* downloads each unique image inline `src` once, returning a Blob the
|
|
29
|
-
* exporter can embed verbatim (PDF) or stream into the DOCX zip.
|
|
30
|
-
*
|
|
31
|
-
* Backend's `GET /images/:id` is publicly readable, so we don't attach
|
|
32
|
-
* an Authorization header — sending JWT cookies via the CLI isn't
|
|
33
|
-
* possible anyway. Relative URLs resolve against `serverBase`; absolute
|
|
34
|
-
* URLs (e.g., the canonical `https://api.wafflebase.io/images/...`
|
|
35
|
-
* surfaced by `imageFetcher required` errors) pass through.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createImageFetcher(opts: ImageFetcherOptions): DocxImageFetcher;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a possibly-relative image URL against the configured server
|
|
3
|
-
* base. Mirrors `resolveImageUrl` in
|
|
4
|
-
* `packages/frontend/src/app/docs/export-utils.ts` so the CLI and
|
|
5
|
-
* browser agree on what counts as "absolute". The shared rule: any URL
|
|
6
|
-
* that starts with a scheme (`http:`, `https:`, `data:`, `blob:`,
|
|
7
|
-
* `file:`, ...) is left alone; everything else gets prefixed.
|
|
8
|
-
*/
|
|
9
|
-
export function resolveImageUrl(url, serverBase) {
|
|
10
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(url))
|
|
11
|
-
return url;
|
|
12
|
-
const base = serverBase.replace(/\/$/, '');
|
|
13
|
-
if (!base)
|
|
14
|
-
return url;
|
|
15
|
-
return `${base}${url.startsWith('/') ? url : `/${url}`}`;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Build an `ImageFetcher` for the CLI export pipelines. The fetcher
|
|
19
|
-
* downloads each unique image inline `src` once, returning a Blob the
|
|
20
|
-
* exporter can embed verbatim (PDF) or stream into the DOCX zip.
|
|
21
|
-
*
|
|
22
|
-
* Backend's `GET /images/:id` is publicly readable, so we don't attach
|
|
23
|
-
* an Authorization header — sending JWT cookies via the CLI isn't
|
|
24
|
-
* possible anyway. Relative URLs resolve against `serverBase`; absolute
|
|
25
|
-
* URLs (e.g., the canonical `https://api.wafflebase.io/images/...`
|
|
26
|
-
* surfaced by `imageFetcher required` errors) pass through.
|
|
27
|
-
*/
|
|
28
|
-
export function createImageFetcher(opts) {
|
|
29
|
-
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
30
|
-
return async (url) => {
|
|
31
|
-
const resolved = resolveImageUrl(url, opts.serverBase);
|
|
32
|
-
const res = await fetchImpl(resolved);
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
throw new Error(`Image fetch failed: ${res.status} ${res.statusText} for ${resolved}`);
|
|
35
|
-
}
|
|
36
|
-
return res.blob();
|
|
37
|
-
};
|
|
38
|
-
}
|
package/dist/docs/import.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { Document, ImageUploader } from '@wafflebase/docs';
|
|
2
|
-
/**
|
|
3
|
-
* Minimal HTTP surface `runDocsImport` needs from the CLI's
|
|
4
|
-
* `HttpClient`. Spelled out so tests can pass a stub without depending
|
|
5
|
-
* on the full client.
|
|
6
|
-
*/
|
|
7
|
-
export interface ImportClient {
|
|
8
|
-
createDocument: (title: string, type?: 'doc' | 'sheet') => Promise<{
|
|
9
|
-
ok: boolean;
|
|
10
|
-
status: number;
|
|
11
|
-
data: unknown;
|
|
12
|
-
}>;
|
|
13
|
-
putDocContent: (docId: string, doc: Document) => Promise<{
|
|
14
|
-
ok: boolean;
|
|
15
|
-
status: number;
|
|
16
|
-
data: unknown;
|
|
17
|
-
}>;
|
|
18
|
-
}
|
|
19
|
-
export interface ImportIO {
|
|
20
|
-
/** Final structured payload — written to stdout as JSON. */
|
|
21
|
-
stdout: (line: string) => void;
|
|
22
|
-
stderr: (line: string) => void;
|
|
23
|
-
/**
|
|
24
|
-
* Read the docx bytes for `path`. `'-'` reads from stdin.
|
|
25
|
-
*
|
|
26
|
-
* Split out so tests can inject in-memory buffers without juggling
|
|
27
|
-
* tmp files; the default reads `process.stdin` (or the file
|
|
28
|
-
* synchronously) at the action site.
|
|
29
|
-
*/
|
|
30
|
-
readBytes: (path: string) => Promise<Uint8Array>;
|
|
31
|
-
/**
|
|
32
|
-
* Prompt the user for a yes/no answer when running interactively.
|
|
33
|
-
* Returns `true` for affirmative responses (case-insensitive `y` /
|
|
34
|
-
* `yes`); anything else (including end-of-stream) → `false`.
|
|
35
|
-
*/
|
|
36
|
-
confirm: (prompt: string) => Promise<boolean>;
|
|
37
|
-
/** Whether the current process is interactive (a TTY on stdin). * Tests inject `false` to exercise the non-interactive branches. */
|
|
38
|
-
isTTY: boolean;
|
|
39
|
-
}
|
|
40
|
-
export declare const defaultImportIO: ImportIO;
|
|
41
|
-
export interface RunImportArgs {
|
|
42
|
-
/** Source path. `'-'` reads from stdin. */
|
|
43
|
-
file: string;
|
|
44
|
-
/** Override the doc title. Falls back to the file basename. */
|
|
45
|
-
title?: string;
|
|
46
|
-
/** Existing document ID to overwrite. Pairs with `--yes`. */
|
|
47
|
-
replace?: string;
|
|
48
|
-
/** Skip the interactive `--replace` confirmation. */
|
|
49
|
-
yes?: boolean;
|
|
50
|
-
/** Override the default base64 image uploader. Tests pass a no-op. */
|
|
51
|
-
imageUploader?: ImageUploader;
|
|
52
|
-
/** Suppress informational stderr output (errors still reported). */
|
|
53
|
-
quiet?: boolean;
|
|
54
|
-
/** Print the request that *would* fire instead of issuing it. */
|
|
55
|
-
dryRun?: boolean;
|
|
56
|
-
}
|
|
57
|
-
export interface RunImportResult {
|
|
58
|
-
/** Process exit code. The action sets `process.exitCode` accordingly. */
|
|
59
|
-
exitCode: number;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Pure orchestration for `docs import`. Reads the source `.docx`,
|
|
63
|
-
* parses it into a `Document`, then either creates a new document
|
|
64
|
-
* (default) or overwrites an existing one (`--replace`).
|
|
65
|
-
*
|
|
66
|
-
* Why split out from the command action: the action's layered handling
|
|
67
|
-
* of stdin / TTY / interactive prompt is the easiest path to test
|
|
68
|
-
* regressions in. Mocking `process.stdin` at the harness level is
|
|
69
|
-
* fragile; passing in an `ImportIO` lets us drive every branch
|
|
70
|
-
* deterministically.
|
|
71
|
-
*/
|
|
72
|
-
export declare function runDocsImport(args: RunImportArgs, client: ImportClient, io?: ImportIO): Promise<RunImportResult>;
|