@wafflebase/cli 0.4.0 → 0.4.2
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 +2 -0
- package/dist/client/http-client.d.ts +3 -1
- package/dist/client/http-client.js +6 -0
- package/dist/commands/slides.d.ts +3 -0
- package/dist/commands/slides.js +38 -0
- package/dist/slides/import.d.ts +82 -0
- package/dist/slides/import.js +169 -0
- package/dist/slides/pptx-import.d.ts +35 -0
- package/dist/slides/pptx-import.js +49 -0
- package/package.json +3 -2
package/dist/bin.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createProgram } from './commands/root.js';
|
|
3
3
|
import { registerDocsCommand } from './commands/docs.js';
|
|
4
4
|
import { registerSheetsCommand } from './commands/sheets.js';
|
|
5
|
+
import { registerSlidesCommand } from './commands/slides.js';
|
|
5
6
|
import { registerApiKeysCommand } from './commands/api-keys.js';
|
|
6
7
|
import { registerSchemaCommand } from './commands/schema.js';
|
|
7
8
|
import { registerLoginCommand } from './commands/login.js';
|
|
@@ -15,6 +16,7 @@ registerStatusCommand(program);
|
|
|
15
16
|
registerCtxCommand(program);
|
|
16
17
|
registerDocsCommand(program);
|
|
17
18
|
registerSheetsCommand(program);
|
|
19
|
+
registerSlidesCommand(program);
|
|
18
20
|
registerApiKeysCommand(program);
|
|
19
21
|
registerSchemaCommand(program);
|
|
20
22
|
program.parse();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Document } from '@wafflebase/docs';
|
|
2
|
+
import type { SlidesDocument } from '@wafflebase/slides/node';
|
|
2
3
|
import type { CliConfig } from '../config/config.js';
|
|
3
4
|
export interface ApiResponse<T = unknown> {
|
|
4
5
|
ok: boolean;
|
|
@@ -23,12 +24,13 @@ export declare class HttpClient {
|
|
|
23
24
|
private refreshSession;
|
|
24
25
|
request<T>(method: string, path: string, body?: unknown): Promise<ApiResponse<T>>;
|
|
25
26
|
listDocuments(): Promise<ApiResponse<unknown[]>>;
|
|
26
|
-
createDocument(title: string, type?: 'doc' | 'sheet'): Promise<ApiResponse<unknown>>;
|
|
27
|
+
createDocument(title: string, type?: 'doc' | 'sheet' | 'slides'): Promise<ApiResponse<unknown>>;
|
|
27
28
|
getDocument(id: string): Promise<ApiResponse<unknown>>;
|
|
28
29
|
updateDocument(id: string, title: string): Promise<ApiResponse<unknown>>;
|
|
29
30
|
deleteDocument(id: string): Promise<ApiResponse<unknown>>;
|
|
30
31
|
getDocContent(docId: string): Promise<ApiResponse<Document>>;
|
|
31
32
|
putDocContent(docId: string, doc: Document): Promise<ApiResponse<Document>>;
|
|
33
|
+
putSlidesContent(docId: string, deck: SlidesDocument): Promise<ApiResponse<SlidesDocument>>;
|
|
32
34
|
listTabs(docId: string): Promise<ApiResponse<unknown[]>>;
|
|
33
35
|
getCells(docId: string, tabId: string, range?: string): Promise<ApiResponse<unknown>>;
|
|
34
36
|
getCell(docId: string, tabId: string, sref: string): Promise<ApiResponse<unknown>>;
|
|
@@ -116,6 +116,12 @@ export class HttpClient {
|
|
|
116
116
|
putDocContent(docId, doc) {
|
|
117
117
|
return this.request('PUT', `/documents/${docId}/content`, doc);
|
|
118
118
|
}
|
|
119
|
+
// Slides content — same endpoint as docs; the backend dispatches on
|
|
120
|
+
// the persisted document type, picking the docs writer for `'doc'`
|
|
121
|
+
// and the slides writer for `'slides'`.
|
|
122
|
+
putSlidesContent(docId, deck) {
|
|
123
|
+
return this.request('PUT', `/documents/${docId}/content`, deck);
|
|
124
|
+
}
|
|
119
125
|
// Tabs
|
|
120
126
|
listTabs(docId) {
|
|
121
127
|
return this.request('GET', `/documents/${docId}/tabs`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getGlobalOpts, getClient } from './root.js';
|
|
2
|
+
import { outputError } from '../output/formatter.js';
|
|
3
|
+
import { runSlidesImport } from '../slides/import.js';
|
|
4
|
+
export function registerSlidesCommand(program) {
|
|
5
|
+
const slides = program
|
|
6
|
+
.command('slides')
|
|
7
|
+
.alias('slide')
|
|
8
|
+
.alias('deck')
|
|
9
|
+
.description('Manage slide decks');
|
|
10
|
+
registerSlidesImportCommand(slides);
|
|
11
|
+
}
|
|
12
|
+
export function registerSlidesImportCommand(slides) {
|
|
13
|
+
slides
|
|
14
|
+
.command('import <file>')
|
|
15
|
+
.description('Import a .pptx file as a new (or replacement) slides deck')
|
|
16
|
+
.option('--title <title>', 'Deck title (default: file basename)')
|
|
17
|
+
.option('--replace <doc-id>', 'Replace content of an existing deck')
|
|
18
|
+
.option('--yes', 'Skip --replace confirmation prompt', false)
|
|
19
|
+
.action(async function (file) {
|
|
20
|
+
const opts = getGlobalOpts(this);
|
|
21
|
+
const local = this.opts();
|
|
22
|
+
try {
|
|
23
|
+
const result = await runSlidesImport({
|
|
24
|
+
file,
|
|
25
|
+
title: local.title,
|
|
26
|
+
replace: local.replace,
|
|
27
|
+
yes: local.yes,
|
|
28
|
+
quiet: opts.quiet,
|
|
29
|
+
dryRun: opts.dryRun,
|
|
30
|
+
}, getClient(opts));
|
|
31
|
+
if (result.exitCode !== 0)
|
|
32
|
+
process.exitCode = result.exitCode;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
outputError(e, opts.quiet);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { SlidesDocument, UploadImage } from '@wafflebase/slides/node';
|
|
2
|
+
import { type CliPptxImportOptions } from './pptx-import.js';
|
|
3
|
+
import type { ImportReport } from '@wafflebase/slides/node';
|
|
4
|
+
/**
|
|
5
|
+
* Parsing surface — split out so tests can inject a stub that returns
|
|
6
|
+
* a synthetic `SlidesDocument` without building a real `.pptx` byte
|
|
7
|
+
* buffer. Production callers use the default (`importPptx` from
|
|
8
|
+
* `./pptx-import.js`).
|
|
9
|
+
*/
|
|
10
|
+
export type SlidesImportParser = (buf: Uint8Array, opts?: CliPptxImportOptions) => Promise<{
|
|
11
|
+
document: SlidesDocument;
|
|
12
|
+
report: ImportReport;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Minimal HTTP surface `runSlidesImport` needs from the CLI's
|
|
16
|
+
* `HttpClient`. Spelled out so tests can pass a stub without depending
|
|
17
|
+
* on the full client. Mirrors `ImportClient` in `../docs/import.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export interface SlidesImportClient {
|
|
20
|
+
createDocument: (title: string, type?: 'doc' | 'sheet' | 'slides') => Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
status: number;
|
|
23
|
+
data: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
putSlidesContent: (docId: string, deck: SlidesDocument) => Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
status: number;
|
|
28
|
+
data: unknown;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export interface SlidesImportIO {
|
|
32
|
+
/** Final structured payload — written to stdout as JSON. */
|
|
33
|
+
stdout: (line: string) => void;
|
|
34
|
+
stderr: (line: string) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Read the pptx bytes for `path`. `'-'` reads from stdin.
|
|
37
|
+
*
|
|
38
|
+
* Split out so tests can inject in-memory buffers without juggling
|
|
39
|
+
* tmp files; the default reads `process.stdin` (or the file
|
|
40
|
+
* synchronously) at the action site.
|
|
41
|
+
*/
|
|
42
|
+
readBytes: (path: string) => Promise<Uint8Array>;
|
|
43
|
+
/**
|
|
44
|
+
* Prompt the user for a yes/no answer when running interactively.
|
|
45
|
+
* Returns `true` for affirmative responses (case-insensitive `y` /
|
|
46
|
+
* `yes`); anything else (including end-of-stream) → `false`.
|
|
47
|
+
*/
|
|
48
|
+
confirm: (prompt: string) => Promise<boolean>;
|
|
49
|
+
/** Whether the current process is interactive (a TTY on stdin). */
|
|
50
|
+
isTTY: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare const defaultSlidesImportIO: SlidesImportIO;
|
|
53
|
+
export interface RunSlidesImportArgs {
|
|
54
|
+
/** Source path. `'-'` reads from stdin. */
|
|
55
|
+
file: string;
|
|
56
|
+
/** Override the deck title. Falls back to the file basename. */
|
|
57
|
+
title?: string;
|
|
58
|
+
/** Existing document ID to overwrite. Pairs with `--yes`. */
|
|
59
|
+
replace?: string;
|
|
60
|
+
/** Skip the interactive `--replace` confirmation. */
|
|
61
|
+
yes?: boolean;
|
|
62
|
+
/** Override the default base64 image uploader. Tests pass a no-op. */
|
|
63
|
+
uploadImage?: UploadImage;
|
|
64
|
+
/** Suppress informational stderr output (errors still reported). */
|
|
65
|
+
quiet?: boolean;
|
|
66
|
+
/** Print the request that *would* fire instead of issuing it. */
|
|
67
|
+
dryRun?: boolean;
|
|
68
|
+
/** Override the .pptx parser — primarily for tests. */
|
|
69
|
+
parser?: SlidesImportParser;
|
|
70
|
+
}
|
|
71
|
+
export interface RunSlidesImportResult {
|
|
72
|
+
/** Process exit code. The action sets `process.exitCode` accordingly. */
|
|
73
|
+
exitCode: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Pure orchestration for `slides import`. Reads the source `.pptx`,
|
|
77
|
+
* parses it into a `SlidesDocument`, then either creates a new deck
|
|
78
|
+
* (default) or overwrites an existing one (`--replace`). Mirrors
|
|
79
|
+
* `runDocsImport` — see that file for the rationale behind the split
|
|
80
|
+
* between this function and the Commander action.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runSlidesImport(args: RunSlidesImportArgs, client: SlidesImportClient, io?: SlidesImportIO): Promise<RunSlidesImportResult>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, extname } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { importPptx as defaultImportPptx, InvalidPptxError, } from './pptx-import.js';
|
|
5
|
+
export const defaultSlidesImportIO = {
|
|
6
|
+
stdout: (line) => {
|
|
7
|
+
process.stdout.write(line);
|
|
8
|
+
if (!line.endsWith('\n'))
|
|
9
|
+
process.stdout.write('\n');
|
|
10
|
+
},
|
|
11
|
+
stderr: (line) => {
|
|
12
|
+
console.error(line);
|
|
13
|
+
},
|
|
14
|
+
readBytes: async (path) => {
|
|
15
|
+
if (path === '-') {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
19
|
+
process.stdin.on('end', () => resolve(new Uint8Array(Buffer.concat(chunks))));
|
|
20
|
+
process.stdin.on('error', reject);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return new Uint8Array(readFileSync(path));
|
|
24
|
+
},
|
|
25
|
+
confirm: (prompt) => {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
28
|
+
rl.question(prompt, (answer) => {
|
|
29
|
+
rl.close();
|
|
30
|
+
const a = answer.trim().toLowerCase();
|
|
31
|
+
resolve(a === 'y' || a === 'yes');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
isTTY: process.stdin.isTTY ?? false,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Pure orchestration for `slides import`. Reads the source `.pptx`,
|
|
39
|
+
* parses it into a `SlidesDocument`, then either creates a new deck
|
|
40
|
+
* (default) or overwrites an existing one (`--replace`). Mirrors
|
|
41
|
+
* `runDocsImport` — see that file for the rationale behind the split
|
|
42
|
+
* between this function and the Commander action.
|
|
43
|
+
*/
|
|
44
|
+
export async function runSlidesImport(args, client, io = defaultSlidesImportIO) {
|
|
45
|
+
const { file, replace, yes = false, uploadImage, quiet = false, dryRun = false, parser = defaultImportPptx, } = args;
|
|
46
|
+
const inferredTitle = args.title ?? defaultTitleFor(file);
|
|
47
|
+
if (replace) {
|
|
48
|
+
// `--replace` is destructive: confirm interactively unless `--yes`.
|
|
49
|
+
if (!yes) {
|
|
50
|
+
if (!io.isTTY) {
|
|
51
|
+
io.stderr(JSON.stringify({
|
|
52
|
+
error: {
|
|
53
|
+
code: 'CONFIRMATION_REQ',
|
|
54
|
+
message: `Pass --yes to confirm replacing deck "${replace}".`,
|
|
55
|
+
},
|
|
56
|
+
}, null, 2));
|
|
57
|
+
return { exitCode: 1 };
|
|
58
|
+
}
|
|
59
|
+
const ok = await io.confirm(`This will replace content of ${replace}. Continue? [y/N] `);
|
|
60
|
+
if (!ok) {
|
|
61
|
+
if (!quiet)
|
|
62
|
+
io.stderr('Aborted.');
|
|
63
|
+
return { exitCode: 0 };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const buf = await io.readBytes(file);
|
|
67
|
+
const parsed = await safeImportPptx(buf, uploadImage, io, parser);
|
|
68
|
+
if (parsed === null)
|
|
69
|
+
return { exitCode: 1 };
|
|
70
|
+
const { document: deck, report } = parsed;
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
io.stdout(JSON.stringify({
|
|
73
|
+
method: 'PUT',
|
|
74
|
+
path: `/documents/${replace}/content`,
|
|
75
|
+
body: deck,
|
|
76
|
+
report: summariseReport(report),
|
|
77
|
+
}, null, 2));
|
|
78
|
+
return { exitCode: 0 };
|
|
79
|
+
}
|
|
80
|
+
const res = await client.putSlidesContent(replace, deck);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
io.stderr(JSON.stringify(res.data ?? { error: { code: 'HTTP_ERROR' } }, null, 2));
|
|
83
|
+
return { exitCode: 1 };
|
|
84
|
+
}
|
|
85
|
+
io.stdout(JSON.stringify({ id: replace, replaced: true, report: summariseReport(report) }, null, 2));
|
|
86
|
+
return { exitCode: 0 };
|
|
87
|
+
}
|
|
88
|
+
// Default flow: POST + PUT.
|
|
89
|
+
const buf = await io.readBytes(file);
|
|
90
|
+
const parsedNew = await safeImportPptx(buf, uploadImage, io, parser);
|
|
91
|
+
if (parsedNew === null)
|
|
92
|
+
return { exitCode: 1 };
|
|
93
|
+
const { document: deck, report } = parsedNew;
|
|
94
|
+
// The parser fills `meta.title` with whatever string the .pptx carries
|
|
95
|
+
// (often "PowerPoint Presentation" or "Imported deck"). The user's
|
|
96
|
+
// `--title` flag is the source of truth for the deck name — keep the
|
|
97
|
+
// Yorkie root's title in lock-step with the Document row so the deck
|
|
98
|
+
// list, editor header, and GET /content all agree.
|
|
99
|
+
deck.meta.title = inferredTitle;
|
|
100
|
+
if (dryRun) {
|
|
101
|
+
io.stdout(JSON.stringify({
|
|
102
|
+
method: 'POST',
|
|
103
|
+
path: '/documents',
|
|
104
|
+
body: { title: inferredTitle, type: 'slides' },
|
|
105
|
+
followUp: { method: 'PUT', path: '/documents/<new-id>/content' },
|
|
106
|
+
report: summariseReport(report),
|
|
107
|
+
}, null, 2));
|
|
108
|
+
return { exitCode: 0 };
|
|
109
|
+
}
|
|
110
|
+
const created = await client.createDocument(inferredTitle, 'slides');
|
|
111
|
+
if (!created.ok) {
|
|
112
|
+
io.stderr(JSON.stringify(created.data ?? { error: { code: 'HTTP_ERROR' } }, null, 2));
|
|
113
|
+
return { exitCode: 1 };
|
|
114
|
+
}
|
|
115
|
+
const newId = created.data?.id;
|
|
116
|
+
if (!newId) {
|
|
117
|
+
io.stderr(JSON.stringify({ error: { code: 'INVALID_RESPONSE', message: 'Server did not return an id' } }, null, 2));
|
|
118
|
+
return { exitCode: 1 };
|
|
119
|
+
}
|
|
120
|
+
const put = await client.putSlidesContent(newId, deck);
|
|
121
|
+
if (!put.ok) {
|
|
122
|
+
io.stderr(JSON.stringify(put.data ?? { error: { code: 'HTTP_ERROR' } }, null, 2));
|
|
123
|
+
return { exitCode: 1 };
|
|
124
|
+
}
|
|
125
|
+
io.stdout(JSON.stringify({ id: newId, title: inferredTitle, report: summariseReport(report) }, null, 2));
|
|
126
|
+
return { exitCode: 0 };
|
|
127
|
+
}
|
|
128
|
+
function defaultTitleFor(file) {
|
|
129
|
+
if (file === '-')
|
|
130
|
+
return 'Untitled';
|
|
131
|
+
return basename(file, extname(file)) || 'Untitled';
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Collect the parser's `ImportReport` counters into a plain object so
|
|
135
|
+
* the CLI can print them as JSON. We surface every counter rather than
|
|
136
|
+
* the `.summary()` string so agentic callers can parse without
|
|
137
|
+
* regex-matching prose.
|
|
138
|
+
*/
|
|
139
|
+
function summariseReport(report) {
|
|
140
|
+
return {
|
|
141
|
+
groupsFlattened: report.groupsFlattened,
|
|
142
|
+
tablesFlattened: report.tablesFlattened,
|
|
143
|
+
shadowsDropped: report.shadowsDropped,
|
|
144
|
+
textBoxesPreScaled: report.textBoxesPreScaled,
|
|
145
|
+
unknownShapes: report.unknownShapes,
|
|
146
|
+
unknownLayoutTypes: report.unknownLayoutTypes,
|
|
147
|
+
tableMergesIgnored: report.tableMergesIgnored,
|
|
148
|
+
tableBordersApproximated: report.tableBordersApproximated,
|
|
149
|
+
skippedImages: report.skippedImages,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Run `importPptx` and turn `InvalidPptxError` into a structured stderr
|
|
154
|
+
* body + `null` return so callers can short-circuit with `exitCode: 1`
|
|
155
|
+
* while keeping the rest of the error envelope (`HTTP_ERROR`,
|
|
156
|
+
* `INVALID_RESPONSE`, etc.) consistent.
|
|
157
|
+
*/
|
|
158
|
+
async function safeImportPptx(buf, uploadImage, io, parser) {
|
|
159
|
+
try {
|
|
160
|
+
return await parser(buf, { uploadImage });
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
if (e instanceof InvalidPptxError) {
|
|
164
|
+
io.stderr(JSON.stringify({ error: { code: e.code, message: e.message } }, null, 2));
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import '../docs/dom-polyfill.js';
|
|
2
|
+
import { type ImportPptxResult, type UploadImage } from '@wafflebase/slides/node';
|
|
3
|
+
/**
|
|
4
|
+
* Default `UploadImage` for the CLI: encodes each embedded image as a
|
|
5
|
+
* `data:` URL. Matches the docs CLI's `inlineBase64Uploader` — the
|
|
6
|
+
* frontend treats slide image `src` values as opaque strings, so a
|
|
7
|
+
* self-contained data URL round-trips cleanly through the API and
|
|
8
|
+
* renders without needing the workspace's `/images` endpoint.
|
|
9
|
+
*
|
|
10
|
+
* The trade-off is payload size: a benchmark deck with 25 unique
|
|
11
|
+
* images and 63 references gains roughly the image-byte total to the
|
|
12
|
+
* Yorkie document. Acceptable for a v1 CLI import — power users who
|
|
13
|
+
* want hosted URLs can pass a custom uploader.
|
|
14
|
+
*/
|
|
15
|
+
export declare const inlineBase64SlidesUploader: UploadImage;
|
|
16
|
+
export interface CliPptxImportOptions {
|
|
17
|
+
/** Override the default base64 uploader — primarily for tests. */
|
|
18
|
+
uploadImage?: UploadImage;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a .pptx byte buffer into a `SlidesDocument` via the slides
|
|
22
|
+
* parser. The DOM polyfill side-effect import installs xmldom's
|
|
23
|
+
* `DOMParser` globally so the parser's browser-targeted XML reads work
|
|
24
|
+
* in Node.
|
|
25
|
+
*
|
|
26
|
+
* Throws a wrapped `INVALID_PPTX` error when the underlying parser
|
|
27
|
+
* cannot make sense of the buffer. The CLI command translates that
|
|
28
|
+
* into the documented `{ error.code: 'INVALID_PPTX' }` exit body
|
|
29
|
+
* without leaking implementation details.
|
|
30
|
+
*/
|
|
31
|
+
export declare function importPptx(buf: Uint8Array, opts?: CliPptxImportOptions): Promise<ImportPptxResult>;
|
|
32
|
+
export declare class InvalidPptxError extends Error {
|
|
33
|
+
readonly code = "INVALID_PPTX";
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import '../docs/dom-polyfill.js';
|
|
2
|
+
import { importPptx as importPptxFromSlides, } from '@wafflebase/slides/node';
|
|
3
|
+
/**
|
|
4
|
+
* Default `UploadImage` for the CLI: encodes each embedded image as a
|
|
5
|
+
* `data:` URL. Matches the docs CLI's `inlineBase64Uploader` — the
|
|
6
|
+
* frontend treats slide image `src` values as opaque strings, so a
|
|
7
|
+
* self-contained data URL round-trips cleanly through the API and
|
|
8
|
+
* renders without needing the workspace's `/images` endpoint.
|
|
9
|
+
*
|
|
10
|
+
* The trade-off is payload size: a benchmark deck with 25 unique
|
|
11
|
+
* images and 63 references gains roughly the image-byte total to the
|
|
12
|
+
* Yorkie document. Acceptable for a v1 CLI import — power users who
|
|
13
|
+
* want hosted URLs can pass a custom uploader.
|
|
14
|
+
*/
|
|
15
|
+
export const inlineBase64SlidesUploader = async (bytes, mime) => {
|
|
16
|
+
const base64 = Buffer.from(bytes).toString('base64');
|
|
17
|
+
const mediaType = mime || 'application/octet-stream';
|
|
18
|
+
return `data:${mediaType};base64,${base64}`;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Parse a .pptx byte buffer into a `SlidesDocument` via the slides
|
|
22
|
+
* parser. The DOM polyfill side-effect import installs xmldom's
|
|
23
|
+
* `DOMParser` globally so the parser's browser-targeted XML reads work
|
|
24
|
+
* in Node.
|
|
25
|
+
*
|
|
26
|
+
* Throws a wrapped `INVALID_PPTX` error when the underlying parser
|
|
27
|
+
* cannot make sense of the buffer. The CLI command translates that
|
|
28
|
+
* into the documented `{ error.code: 'INVALID_PPTX' }` exit body
|
|
29
|
+
* without leaking implementation details.
|
|
30
|
+
*/
|
|
31
|
+
export async function importPptx(buf, opts = {}) {
|
|
32
|
+
const arrayBuf = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
33
|
+
try {
|
|
34
|
+
return await importPptxFromSlides(arrayBuf, {
|
|
35
|
+
uploadImage: opts.uploadImage ?? inlineBase64SlidesUploader,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
40
|
+
throw new InvalidPptxError(message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class InvalidPptxError extends Error {
|
|
44
|
+
code = 'INVALID_PPTX';
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = 'InvalidPptxError';
|
|
48
|
+
}
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wafflebase/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "CLI for Wafflebase spreadsheet API",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"open": "^11.0.0",
|
|
21
21
|
"pdf-lib": "^1.17.1",
|
|
22
22
|
"yaml": "^2.0.0",
|
|
23
|
-
"@wafflebase/docs": "0.4.
|
|
23
|
+
"@wafflebase/docs": "0.4.2",
|
|
24
|
+
"@wafflebase/slides": "0.4.2"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/fontkit": "^2.0.0",
|