@deepnote/convert 2.0.0 → 2.1.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.
@@ -0,0 +1,1716 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let node_fs_promises = require("node:fs/promises");
25
+ node_fs_promises = __toESM(node_fs_promises);
26
+ let node_path = require("node:path");
27
+ node_path = __toESM(node_path);
28
+ let __deepnote_blocks = require("@deepnote/blocks");
29
+ __deepnote_blocks = __toESM(__deepnote_blocks);
30
+ let uuid = require("uuid");
31
+ uuid = __toESM(uuid);
32
+ let yaml = require("yaml");
33
+ yaml = __toESM(yaml);
34
+
35
+ //#region src/utils.ts
36
+ /**
37
+ * Creates a sorting key for Deepnote blocks.
38
+ * Uses a base-36 encoding to generate compact, sortable keys.
39
+ *
40
+ * @param index - The zero-based index of the block
41
+ * @returns A sortable string key
42
+ */
43
+ function createSortingKey(index) {
44
+ const maxLength = 6;
45
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
46
+ const base = 36;
47
+ if (index < 0) throw new Error("Index must be non-negative");
48
+ let result = "";
49
+ let num = index + 1;
50
+ let iterations = 0;
51
+ while (num > 0 && iterations < maxLength) {
52
+ num--;
53
+ result = chars[num % base] + result;
54
+ num = Math.floor(num / base);
55
+ iterations++;
56
+ }
57
+ if (num > 0) throw new Error(`Index ${index} exceeds maximum key length of ${maxLength}`);
58
+ return result;
59
+ }
60
+ /**
61
+ * Sanitizes a filename by removing invalid characters and replacing spaces.
62
+ *
63
+ * @param name - The original filename
64
+ * @returns A sanitized filename safe for all platforms
65
+ */
66
+ function sanitizeFileName(name) {
67
+ return name.replace(/[<>:"/\\|?*]/g, "_").replace(/\s+/g, "_");
68
+ }
69
+ /**
70
+ * Deepnote block types that should be converted to code cells.
71
+ * Unknown types default to markdown (less lossy).
72
+ */
73
+ const CODE_BLOCK_TYPES = [
74
+ "big-number",
75
+ "button",
76
+ "code",
77
+ "notebook-function",
78
+ "sql",
79
+ "visualization"
80
+ ];
81
+ /**
82
+ * Checks if a Deepnote block type should be converted to a markdown cell.
83
+ * Uses a whitelist of code types and defaults unknown types to markdown (less lossy).
84
+ *
85
+ * @param blockType - The type of the Deepnote block
86
+ * @returns true if the block should be treated as markdown
87
+ */
88
+ function isMarkdownBlockType(blockType) {
89
+ if (blockType.startsWith("input-")) return false;
90
+ if (CODE_BLOCK_TYPES.includes(blockType)) return false;
91
+ return true;
92
+ }
93
+ /**
94
+ * Sorts object keys alphabetically while preserving the type.
95
+ * Useful for producing stable, predictable output in serialized formats like YAML.
96
+ *
97
+ * @param obj - The object whose keys should be sorted
98
+ * @returns A new object with the same values but keys in alphabetical order
99
+ */
100
+ function sortKeysAlphabetically(obj) {
101
+ const sorted = {};
102
+ for (const key of Object.keys(obj).sort()) sorted[key] = obj[key];
103
+ return sorted;
104
+ }
105
+
106
+ //#endregion
107
+ //#region src/deepnote-to-jupyter.ts
108
+ /**
109
+ * Converts an array of Deepnote blocks into a single Jupyter Notebook.
110
+ * This is the lowest-level conversion function, suitable for use in Deepnote Cloud.
111
+ *
112
+ * @param blocks - Array of DeepnoteBlock objects to convert
113
+ * @param options - Notebook metadata options
114
+ * @returns A JupyterNotebook object
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * import { convertBlocksToJupyterNotebook } from '@deepnote/convert'
119
+ *
120
+ * const notebook = convertBlocksToJupyterNotebook(blocks, {
121
+ * notebookId: 'abc123',
122
+ * notebookName: 'My Notebook',
123
+ * executionMode: 'block'
124
+ * })
125
+ * ```
126
+ */
127
+ function convertBlocksToJupyterNotebook(blocks, options) {
128
+ return {
129
+ cells: blocks.map((block) => convertBlockToCell$3(block)),
130
+ metadata: {
131
+ deepnote_notebook_id: options.notebookId,
132
+ deepnote_notebook_name: options.notebookName,
133
+ deepnote_execution_mode: options.executionMode,
134
+ deepnote_is_module: options.isModule,
135
+ deepnote_working_directory: options.workingDirectory,
136
+ deepnote_environment: options.environment,
137
+ deepnote_execution: options.execution
138
+ },
139
+ nbformat: 4,
140
+ nbformat_minor: 0
141
+ };
142
+ }
143
+ /**
144
+ * Converts a Deepnote project into Jupyter Notebook objects.
145
+ * This is a pure conversion function that doesn't perform any file I/O.
146
+ * Each notebook in the Deepnote project is converted to a separate Jupyter notebook.
147
+ *
148
+ * @param deepnoteFile - The deserialized Deepnote project file
149
+ * @returns Array of objects containing filename and corresponding Jupyter notebook
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * import { deserializeDeepnoteFile } from '@deepnote/blocks'
154
+ * import { convertDeepnoteToJupyterNotebooks } from '@deepnote/convert'
155
+ *
156
+ * const yamlContent = await fs.readFile('project.deepnote', 'utf-8')
157
+ * const deepnoteFile = deserializeDeepnoteFile(yamlContent)
158
+ * const notebooks = convertDeepnoteToJupyterNotebooks(deepnoteFile)
159
+ *
160
+ * for (const { filename, notebook } of notebooks) {
161
+ * console.log(`${filename}: ${notebook.cells.length} cells`)
162
+ * }
163
+ * ```
164
+ */
165
+ function convertDeepnoteToJupyterNotebooks(deepnoteFile) {
166
+ return deepnoteFile.project.notebooks.map((notebook) => {
167
+ const jupyterNotebook = convertNotebookToJupyter(deepnoteFile, notebook);
168
+ return {
169
+ filename: `${sanitizeFileName(notebook.name)}.ipynb`,
170
+ notebook: jupyterNotebook
171
+ };
172
+ });
173
+ }
174
+ /**
175
+ * Converts a Deepnote project file into separate Jupyter Notebook (.ipynb) files.
176
+ * Each notebook in the Deepnote project becomes a separate .ipynb file.
177
+ */
178
+ async function convertDeepnoteFileToJupyterFiles(deepnoteFilePath, options) {
179
+ const notebooks = convertDeepnoteToJupyterNotebooks((0, __deepnote_blocks.deserializeDeepnoteFile)(await node_fs_promises.default.readFile(deepnoteFilePath, "utf-8")));
180
+ await node_fs_promises.default.mkdir(options.outputDir, { recursive: true });
181
+ for (const { filename, notebook } of notebooks) {
182
+ const filePath = (0, node_path.join)(options.outputDir, filename);
183
+ await node_fs_promises.default.writeFile(filePath, JSON.stringify(notebook, null, 2), "utf-8");
184
+ }
185
+ }
186
+ function convertBlockToCell$3(block) {
187
+ const content = block.content || "";
188
+ const jupyterCellType = convertBlockTypeToJupyter(block.type);
189
+ const executionStartedAt = "executionStartedAt" in block ? block.executionStartedAt : void 0;
190
+ const executionFinishedAt = "executionFinishedAt" in block ? block.executionFinishedAt : void 0;
191
+ const executionCount = "executionCount" in block ? block.executionCount : null;
192
+ const outputs = "outputs" in block ? block.outputs : void 0;
193
+ const metadata = {
194
+ ...block.metadata || {},
195
+ cell_id: block.id,
196
+ deepnote_block_group: block.blockGroup,
197
+ deepnote_cell_type: block.type,
198
+ deepnote_sorting_key: block.sortingKey,
199
+ deepnote_content_hash: block.contentHash,
200
+ deepnote_execution_started_at: executionStartedAt,
201
+ deepnote_execution_finished_at: executionFinishedAt
202
+ };
203
+ metadata.deepnote_source = content;
204
+ return {
205
+ block_group: block.blockGroup,
206
+ cell_type: jupyterCellType,
207
+ execution_count: executionCount ?? null,
208
+ metadata,
209
+ outputs,
210
+ source: getSourceForBlock(block, jupyterCellType, content)
211
+ };
212
+ }
213
+ function getSourceForBlock(block, jupyterCellType, content) {
214
+ if (jupyterCellType === "markdown") return (0, __deepnote_blocks.createMarkdown)(block);
215
+ if (block.type === "code") return content;
216
+ return (0, __deepnote_blocks.createPythonCode)(block);
217
+ }
218
+ function convertBlockTypeToJupyter(blockType) {
219
+ return isMarkdownBlockType(blockType) ? "markdown" : "code";
220
+ }
221
+ function convertNotebookToJupyter(deepnoteFile, notebook) {
222
+ return convertBlocksToJupyterNotebook(notebook.blocks, {
223
+ notebookId: notebook.id,
224
+ notebookName: notebook.name,
225
+ executionMode: notebook.executionMode,
226
+ isModule: notebook.isModule,
227
+ workingDirectory: notebook.workingDirectory,
228
+ environment: deepnoteFile.environment,
229
+ execution: deepnoteFile.execution
230
+ });
231
+ }
232
+
233
+ //#endregion
234
+ //#region src/deepnote-to-marimo.ts
235
+ /** Current Marimo version for generated files */
236
+ const MARIMO_VERSION = "0.10.0";
237
+ /**
238
+ * Converts an array of Deepnote blocks into a Marimo app.
239
+ * This is the lowest-level conversion function.
240
+ *
241
+ * @param blocks - Array of DeepnoteBlock objects to convert
242
+ * @param notebookName - Name of the notebook (used for app title)
243
+ * @returns A MarimoApp object
244
+ */
245
+ function convertBlocksToMarimoApp(blocks, notebookName) {
246
+ return {
247
+ generatedWith: MARIMO_VERSION,
248
+ width: "medium",
249
+ title: notebookName,
250
+ cells: blocks.map((block) => convertBlockToCell$2(block))
251
+ };
252
+ }
253
+ /**
254
+ * Converts a Deepnote project into Marimo app objects.
255
+ * This is a pure conversion function that doesn't perform any file I/O.
256
+ * Each notebook in the Deepnote project is converted to a separate Marimo app.
257
+ *
258
+ * @param deepnoteFile - The deserialized Deepnote project file
259
+ * @returns Array of objects containing filename and corresponding Marimo app
260
+ */
261
+ function convertDeepnoteToMarimoApps(deepnoteFile) {
262
+ return deepnoteFile.project.notebooks.map((notebook) => {
263
+ const app = convertBlocksToMarimoApp(notebook.blocks, notebook.name);
264
+ return {
265
+ filename: `${sanitizeFileName(notebook.name)}.py`,
266
+ app
267
+ };
268
+ });
269
+ }
270
+ /**
271
+ * Serializes a Marimo app to a Python file string.
272
+ *
273
+ * @param app - The Marimo app to serialize
274
+ * @returns The serialized Python code string
275
+ */
276
+ function serializeMarimoFormat(app) {
277
+ const lines = [];
278
+ const hasMarkdownOrSqlCells = app.cells.some((cell) => cell.cellType === "markdown" || cell.cellType === "sql");
279
+ if (hasMarkdownOrSqlCells) lines.push("import marimo as mo");
280
+ else lines.push("import marimo");
281
+ lines.push("");
282
+ lines.push(`__generated_with = "${app.generatedWith || MARIMO_VERSION}"`);
283
+ const appOptions = [];
284
+ if (app.width) appOptions.push(`width="${app.width}"`);
285
+ if (app.title) appOptions.push(`title="${escapeString(app.title)}"`);
286
+ const marimoRef = hasMarkdownOrSqlCells ? "mo" : "marimo";
287
+ const optionsStr = appOptions.length > 0 ? appOptions.join(", ") : "";
288
+ lines.push(`app = ${marimoRef}.App(${optionsStr})`);
289
+ lines.push("");
290
+ lines.push("");
291
+ for (const cell of app.cells) {
292
+ const decoratorOptions = [];
293
+ if (cell.hidden) decoratorOptions.push("hide_code=True");
294
+ if (cell.disabled) decoratorOptions.push("disabled=True");
295
+ const decoratorStr = decoratorOptions.length > 0 ? `@app.cell(${decoratorOptions.join(", ")})` : "@app.cell";
296
+ lines.push(decoratorStr);
297
+ const funcName = cell.functionName || "__";
298
+ const params = cell.dependencies?.join(", ") || "";
299
+ lines.push(`def ${funcName}(${params}):`);
300
+ if (cell.cellType === "markdown") {
301
+ const escaped = escapeTripleQuote(cell.content);
302
+ lines.push(` mo.md(r"""`);
303
+ for (const contentLine of escaped.split("\n")) if (contentLine.trim() === "") lines.push("");
304
+ else lines.push(` ${contentLine}`);
305
+ lines.push(` """)`);
306
+ lines.push(" return");
307
+ } else if (cell.cellType === "sql") {
308
+ const escaped = escapeTripleQuote(cell.content);
309
+ const varName = cell.exports && cell.exports.length > 0 ? cell.exports[0] : "df";
310
+ const engineParam = cell.dependencies?.includes("engine") ? ", engine=engine" : "";
311
+ lines.push(` ${varName} = mo.sql(`);
312
+ lines.push(` f"""`);
313
+ for (const contentLine of escaped.split("\n")) if (contentLine === "" || contentLine.trim() === "") lines.push("");
314
+ else lines.push(` ${contentLine}`);
315
+ lines.push(` """${engineParam}`);
316
+ lines.push(` )`);
317
+ if (cell.exports && cell.exports.length > 0) lines.push(` return ${cell.exports.join(", ")},`);
318
+ else lines.push(" return");
319
+ } else {
320
+ const contentLines = cell.content.split("\n");
321
+ for (const contentLine of contentLines) if (contentLine.trim() === "") lines.push("");
322
+ else lines.push(` ${contentLine}`);
323
+ if (cell.exports && cell.exports.length > 0) lines.push(` return ${cell.exports.join(", ")},`);
324
+ else lines.push(" return");
325
+ }
326
+ lines.push("");
327
+ lines.push("");
328
+ }
329
+ lines.push("if __name__ == \"__main__\":");
330
+ lines.push(" app.run()");
331
+ lines.push("");
332
+ return lines.join("\n");
333
+ }
334
+ /**
335
+ * Converts a Deepnote project file into separate Marimo (.py) files.
336
+ * Each notebook in the Deepnote project becomes a separate .py file.
337
+ */
338
+ async function convertDeepnoteFileToMarimoFiles(deepnoteFilePath, options) {
339
+ const apps = convertDeepnoteToMarimoApps((0, __deepnote_blocks.deserializeDeepnoteFile)(await node_fs_promises.default.readFile(deepnoteFilePath, "utf-8")));
340
+ await node_fs_promises.default.mkdir(options.outputDir, { recursive: true });
341
+ for (const { filename, app } of apps) {
342
+ const filePath = (0, node_path.join)(options.outputDir, filename);
343
+ try {
344
+ const content = serializeMarimoFormat(app);
345
+ await node_fs_promises.default.writeFile(filePath, content, "utf-8");
346
+ } catch (err) {
347
+ const errorMessage = err instanceof Error ? err.message : String(err);
348
+ throw new Error(`Failed to write ${filename}: ${errorMessage}`, { cause: err });
349
+ }
350
+ }
351
+ }
352
+ function convertBlockToCell$2(block) {
353
+ const isMarkdown = isMarkdownBlockType(block.type);
354
+ const isSql = block.type === "sql";
355
+ const metadata = block.metadata || {};
356
+ let content;
357
+ let cellType;
358
+ if (isMarkdown) {
359
+ cellType = "markdown";
360
+ try {
361
+ content = (0, __deepnote_blocks.createMarkdown)(block);
362
+ } catch {
363
+ content = block.content || "";
364
+ }
365
+ } else if (isSql) {
366
+ cellType = "sql";
367
+ content = block.content || "";
368
+ } else if (block.type === "code") {
369
+ cellType = "code";
370
+ content = block.content || "";
371
+ } else {
372
+ cellType = "code";
373
+ try {
374
+ content = (0, __deepnote_blocks.createPythonCode)(block);
375
+ } catch {
376
+ content = block.content || "";
377
+ }
378
+ }
379
+ const dependencies = metadata.marimo_dependencies;
380
+ const exports$1 = metadata.marimo_exports;
381
+ const hidden = metadata.is_code_hidden;
382
+ const disabled = metadata.marimo_disabled;
383
+ const functionName = metadata.marimo_function_name;
384
+ return {
385
+ cellType,
386
+ content,
387
+ ...functionName ? { functionName } : {},
388
+ ...dependencies && dependencies.length > 0 ? { dependencies } : {},
389
+ ...exports$1 && exports$1.length > 0 ? { exports: exports$1 } : {},
390
+ ...hidden ? { hidden } : {},
391
+ ...disabled ? { disabled } : {}
392
+ };
393
+ }
394
+ /**
395
+ * Escapes a string for use in a Python double-quoted string literal.
396
+ * Handles backslashes, quotes, control characters, and non-printable characters.
397
+ */
398
+ function escapeString(str) {
399
+ return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/[\x08]/g, "\\b").replace(/[\x0C]/g, "\\f").replace(/[\x00-\x07\x0B\x0E-\x1F\x7F-\x9F]/g, (char) => {
400
+ return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
401
+ });
402
+ }
403
+ /**
404
+ * Escapes content for use in a Python raw triple-quoted string (r""").
405
+ * Raw strings don't interpret escape sequences, so we can't use backslashes to escape.
406
+ * When we encounter triple quotes, we break the raw string and concatenate with a regular string.
407
+ */
408
+ function escapeTripleQuote(str) {
409
+ return str.replace(/"""/g, "\"\"\"+'\"'+r\"\"\"");
410
+ }
411
+
412
+ //#endregion
413
+ //#region src/deepnote-to-percent.ts
414
+ /**
415
+ * Converts an array of Deepnote blocks into a percent format notebook.
416
+ * This is the lowest-level conversion function.
417
+ *
418
+ * @param blocks - Array of DeepnoteBlock objects to convert
419
+ * @returns A PercentNotebook object
420
+ */
421
+ function convertBlocksToPercentNotebook(blocks) {
422
+ return { cells: blocks.map((block) => convertBlockToCell$1(block)) };
423
+ }
424
+ /**
425
+ * Converts a Deepnote project into percent format notebook objects.
426
+ * This is a pure conversion function that doesn't perform any file I/O.
427
+ * Each notebook in the Deepnote project is converted to a separate percent notebook.
428
+ *
429
+ * @param deepnoteFile - The deserialized Deepnote project file
430
+ * @returns Array of objects containing filename and corresponding percent notebook
431
+ */
432
+ function convertDeepnoteToPercentNotebooks(deepnoteFile) {
433
+ return deepnoteFile.project.notebooks.map((notebook) => {
434
+ const percentNotebook = convertBlocksToPercentNotebook(notebook.blocks);
435
+ return {
436
+ filename: `${sanitizeFileName(notebook.name)}.py`,
437
+ notebook: percentNotebook
438
+ };
439
+ });
440
+ }
441
+ /**
442
+ * Serializes a percent format notebook to a string.
443
+ *
444
+ * @param notebook - The percent notebook to serialize
445
+ * @returns The serialized percent format string
446
+ */
447
+ function serializePercentFormat(notebook) {
448
+ const lines = [];
449
+ for (const cell of notebook.cells) {
450
+ let marker = "# %%";
451
+ if (cell.cellType === "markdown") marker += " [markdown]";
452
+ else if (cell.cellType === "raw") marker += " [raw]";
453
+ if (cell.title) marker += ` ${cell.title}`;
454
+ if (cell.tags && cell.tags.length > 0) {
455
+ const tagsStr = cell.tags.map((t) => JSON.stringify(t)).join(", ");
456
+ marker += ` tags=[${tagsStr}]`;
457
+ }
458
+ lines.push(marker);
459
+ if (cell.cellType === "markdown") {
460
+ const contentLines = cell.content.split("\n");
461
+ for (const contentLine of contentLines) if (contentLine === "") lines.push("#");
462
+ else lines.push(`# ${contentLine}`);
463
+ } else lines.push(cell.content);
464
+ lines.push("");
465
+ }
466
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
467
+ return `${lines.join("\n")}\n`;
468
+ }
469
+ /**
470
+ * Converts a Deepnote project file into separate percent format (.py) files.
471
+ * Each notebook in the Deepnote project becomes a separate .py file.
472
+ */
473
+ async function convertDeepnoteFileToPercentFiles(deepnoteFilePath, options) {
474
+ const notebooks = convertDeepnoteToPercentNotebooks((0, __deepnote_blocks.deserializeDeepnoteFile)(await node_fs_promises.default.readFile(deepnoteFilePath, "utf-8")));
475
+ await node_fs_promises.default.mkdir(options.outputDir, { recursive: true });
476
+ for (const { filename, notebook } of notebooks) {
477
+ const filePath = (0, node_path.join)(options.outputDir, filename);
478
+ const content = serializePercentFormat(notebook);
479
+ await node_fs_promises.default.writeFile(filePath, content, "utf-8");
480
+ }
481
+ }
482
+ function convertBlockToCell$1(block) {
483
+ const isMarkdown = isMarkdownBlockType(block.type);
484
+ let content;
485
+ if (isMarkdown) try {
486
+ content = (0, __deepnote_blocks.createMarkdown)(block);
487
+ } catch {
488
+ content = block.content || "";
489
+ }
490
+ else if (block.type === "code") content = block.content || "";
491
+ else try {
492
+ content = (0, __deepnote_blocks.createPythonCode)(block);
493
+ } catch {
494
+ content = block.content || "";
495
+ }
496
+ const title = block.metadata?.title;
497
+ const tags = block.metadata?.tags;
498
+ return {
499
+ cellType: block.metadata?.percent_cell_type === "raw" ? "raw" : isMarkdown ? "markdown" : "code",
500
+ content,
501
+ ...title ? { title } : {},
502
+ ...tags && tags.length > 0 ? { tags } : {}
503
+ };
504
+ }
505
+
506
+ //#endregion
507
+ //#region src/deepnote-to-quarto.ts
508
+ /**
509
+ * Converts an array of Deepnote blocks into a Quarto document.
510
+ * This is the lowest-level conversion function.
511
+ *
512
+ * @param blocks - Array of DeepnoteBlock objects to convert
513
+ * @param notebookName - Name of the notebook (used for document title)
514
+ * @returns A QuartoDocument object
515
+ */
516
+ function convertBlocksToQuartoDocument(blocks, notebookName) {
517
+ const cells = blocks.map((block) => convertBlockToCell(block));
518
+ return {
519
+ frontmatter: {
520
+ title: notebookName,
521
+ jupyter: "python3"
522
+ },
523
+ cells
524
+ };
525
+ }
526
+ /**
527
+ * Converts a Deepnote project into Quarto document objects.
528
+ * This is a pure conversion function that doesn't perform any file I/O.
529
+ * Each notebook in the Deepnote project is converted to a separate Quarto document.
530
+ *
531
+ * @param deepnoteFile - The deserialized Deepnote project file
532
+ * @returns Array of objects containing filename and corresponding Quarto document
533
+ */
534
+ function convertDeepnoteToQuartoDocuments(deepnoteFile) {
535
+ return deepnoteFile.project.notebooks.map((notebook) => {
536
+ const document = convertBlocksToQuartoDocument(notebook.blocks, notebook.name);
537
+ return {
538
+ filename: `${sanitizeFileName(notebook.name)}.qmd`,
539
+ document
540
+ };
541
+ });
542
+ }
543
+ /**
544
+ * Serializes a Quarto document to a string.
545
+ *
546
+ * @param document - The Quarto document to serialize
547
+ * @returns The serialized Quarto format string
548
+ */
549
+ function serializeQuartoFormat(document) {
550
+ const lines = [];
551
+ if (document.frontmatter && Object.keys(document.frontmatter).length > 0) {
552
+ lines.push("---");
553
+ for (const [key, value] of Object.entries(document.frontmatter)) if (typeof value === "string") if (value.includes(":") || value.includes("#") || value.includes("'") || value.includes("\"")) lines.push(`${key}: "${value.replace(/"/g, "\\\"")}"`);
554
+ else lines.push(`${key}: ${value}`);
555
+ else if (typeof value === "boolean") lines.push(`${key}: ${value}`);
556
+ else if (value !== void 0) lines.push(`${key}: ${JSON.stringify(value)}`);
557
+ lines.push("---");
558
+ lines.push("");
559
+ }
560
+ let previousCellType = null;
561
+ for (const cell of document.cells) {
562
+ if (cell.cellType === "markdown") {
563
+ if (previousCellType === "markdown") {
564
+ lines.push("<!-- cell -->");
565
+ lines.push("");
566
+ }
567
+ lines.push(cell.content);
568
+ lines.push("");
569
+ } else {
570
+ const language = cell.language || "python";
571
+ lines.push(`\`\`\`{${language}}`);
572
+ if (cell.options) {
573
+ const optionLines = serializeQuartoCellOptions(cell.options);
574
+ lines.push(...optionLines);
575
+ }
576
+ lines.push(cell.content);
577
+ lines.push("```");
578
+ lines.push("");
579
+ }
580
+ previousCellType = cell.cellType;
581
+ }
582
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
583
+ return `${lines.join("\n")}\n`;
584
+ }
585
+ /**
586
+ * Serializes Quarto cell options to #| format lines.
587
+ */
588
+ function serializeQuartoCellOptions(options) {
589
+ const lines = [];
590
+ if (options.label) lines.push(`#| label: ${options.label}`);
591
+ if (options.echo !== void 0) lines.push(`#| echo: ${options.echo}`);
592
+ if (options.eval !== void 0) lines.push(`#| eval: ${options.eval}`);
593
+ if (options.output !== void 0) lines.push(`#| output: ${options.output}`);
594
+ if (options.figCap) lines.push(`#| fig-cap: "${options.figCap}"`);
595
+ if (options.figWidth !== void 0) lines.push(`#| fig-width: ${options.figWidth}`);
596
+ if (options.figHeight !== void 0) lines.push(`#| fig-height: ${options.figHeight}`);
597
+ if (options.tblCap) lines.push(`#| tbl-cap: "${options.tblCap}"`);
598
+ if (options.warning !== void 0) lines.push(`#| warning: ${options.warning}`);
599
+ if (options.message !== void 0) lines.push(`#| message: ${options.message}`);
600
+ if (options.raw) for (const [key, value] of Object.entries(options.raw)) if (typeof value === "string") lines.push(`#| ${key}: "${value}"`);
601
+ else lines.push(`#| ${key}: ${value}`);
602
+ return lines;
603
+ }
604
+ /**
605
+ * Converts a Deepnote project file into separate Quarto (.qmd) files.
606
+ * Each notebook in the Deepnote project becomes a separate .qmd file.
607
+ */
608
+ async function convertDeepnoteFileToQuartoFiles(deepnoteFilePath, options) {
609
+ const documents = convertDeepnoteToQuartoDocuments((0, __deepnote_blocks.deserializeDeepnoteFile)(await node_fs_promises.default.readFile(deepnoteFilePath, "utf-8")));
610
+ await node_fs_promises.default.mkdir(options.outputDir, { recursive: true });
611
+ for (const { filename, document } of documents) {
612
+ const filePath = (0, node_path.join)(options.outputDir, filename);
613
+ const content = serializeQuartoFormat(document);
614
+ await node_fs_promises.default.writeFile(filePath, content, "utf-8");
615
+ }
616
+ }
617
+ function convertBlockToCell(block) {
618
+ const isMarkdown = isMarkdownBlockType(block.type);
619
+ let content;
620
+ if (isMarkdown) try {
621
+ content = (0, __deepnote_blocks.createMarkdown)(block);
622
+ } catch {
623
+ content = block.content || "";
624
+ }
625
+ else if (block.type === "code") content = block.content || "";
626
+ else try {
627
+ content = (0, __deepnote_blocks.createPythonCode)(block);
628
+ } catch {
629
+ content = block.content || "";
630
+ }
631
+ let options;
632
+ const metadata = block.metadata || {};
633
+ if (metadata.quarto_label || metadata.is_code_hidden || metadata.quarto_fig_cap || metadata.quarto_tbl_cap) {
634
+ options = {};
635
+ if (metadata.quarto_label) options.label = metadata.quarto_label;
636
+ if (metadata.is_code_hidden) options.echo = false;
637
+ if (metadata.quarto_fig_cap) options.figCap = metadata.quarto_fig_cap;
638
+ if (metadata.quarto_tbl_cap) options.tblCap = metadata.quarto_tbl_cap;
639
+ if (metadata.quarto_options) options.raw = metadata.quarto_options;
640
+ }
641
+ const cell = {
642
+ cellType: isMarkdown ? "markdown" : "code",
643
+ content,
644
+ ...options ? { options } : {}
645
+ };
646
+ if (metadata.language && metadata.language !== "python") cell.language = metadata.language;
647
+ return cell;
648
+ }
649
+
650
+ //#endregion
651
+ //#region src/format-detection.ts
652
+ /** Check if file content is Marimo format */
653
+ function isMarimoContent(content) {
654
+ return /^import marimo\b/m.test(content) && /@app\.cell\b/.test(content) && !/^\s*['"]{3}[\s\S]*?import marimo/m.test(content);
655
+ }
656
+ /** Check if file content is percent format */
657
+ function isPercentContent(content) {
658
+ return /^# %%/m.test(content) && !/^\s*['"]{3}[\s\S]*?# %%/m.test(content);
659
+ }
660
+ /**
661
+ * Detects the notebook format from filename and optionally content.
662
+ * For .py files, content is required to distinguish between Marimo and Percent formats.
663
+ */
664
+ function detectFormat(filename, content) {
665
+ const lowercaseFilename = filename.toLowerCase();
666
+ if (lowercaseFilename.endsWith(".ipynb")) return "jupyter";
667
+ if (lowercaseFilename.endsWith(".deepnote")) return "deepnote";
668
+ if (lowercaseFilename.endsWith(".qmd")) return "quarto";
669
+ if (lowercaseFilename.endsWith(".py")) {
670
+ if (!content) throw new Error("Content is required to detect format for .py files");
671
+ if (isMarimoContent(content)) return "marimo";
672
+ if (isPercentContent(content)) return "percent";
673
+ throw new Error("Unsupported Python file format. File must be percent format (# %%) or Marimo (@app.cell).");
674
+ }
675
+ throw new Error(`Unsupported file format: ${filename}`);
676
+ }
677
+
678
+ //#endregion
679
+ //#region src/jupyter-to-deepnote.ts
680
+ /**
681
+ * Converts a single Jupyter Notebook into an array of Deepnote blocks.
682
+ * This is the lowest-level conversion function, suitable for use in Deepnote Cloud.
683
+ *
684
+ * @param notebook - The Jupyter notebook object to convert
685
+ * @param options - Optional conversion options including custom ID generator
686
+ * @returns Array of DeepnoteBlock objects
687
+ *
688
+ * @example
689
+ * ```typescript
690
+ * import { convertJupyterNotebookToBlocks } from '@deepnote/convert'
691
+ *
692
+ * const notebook = JSON.parse(ipynbContent)
693
+ * const blocks = convertJupyterNotebookToBlocks(notebook, {
694
+ * idGenerator: () => myCustomIdGenerator()
695
+ * })
696
+ * ```
697
+ */
698
+ function convertJupyterNotebookToBlocks(notebook, options) {
699
+ const idGenerator = options?.idGenerator ?? uuid.v4;
700
+ return notebook.cells.map((cell, index) => convertCellToBlock$3(cell, index, idGenerator));
701
+ }
702
+ /**
703
+ * Converts Jupyter Notebook objects into a Deepnote project file.
704
+ * This is a pure conversion function that doesn't perform any file I/O.
705
+ *
706
+ * @param notebooks - Array of Jupyter notebooks with filenames
707
+ * @param options - Conversion options including project name
708
+ * @returns A DeepnoteFile object
709
+ */
710
+ function convertJupyterNotebooksToDeepnote(notebooks, options) {
711
+ let environment;
712
+ let execution;
713
+ for (const { notebook } of notebooks) {
714
+ if (!environment && notebook.metadata?.deepnote_environment) {
715
+ const parsed = __deepnote_blocks.environmentSchema.safeParse(notebook.metadata.deepnote_environment);
716
+ if (parsed.success) environment = parsed.data;
717
+ }
718
+ if (!execution && notebook.metadata?.deepnote_execution) {
719
+ const parsed = __deepnote_blocks.executionSchema.safeParse(notebook.metadata.deepnote_execution);
720
+ if (parsed.success) execution = parsed.data;
721
+ }
722
+ }
723
+ const firstNotebookId = notebooks.length > 0 ? notebooks[0].notebook.metadata?.deepnote_notebook_id ?? (0, uuid.v4)() : void 0;
724
+ const deepnoteFile = {
725
+ environment,
726
+ execution,
727
+ metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString() },
728
+ project: {
729
+ id: (0, uuid.v4)(),
730
+ initNotebookId: firstNotebookId,
731
+ integrations: [],
732
+ name: options.projectName,
733
+ notebooks: [],
734
+ settings: {}
735
+ },
736
+ version: "1.0.0"
737
+ };
738
+ for (let i = 0; i < notebooks.length; i++) {
739
+ const { filename, notebook } = notebooks[i];
740
+ const filenameWithoutExt = (0, node_path.basename)(filename, (0, node_path.extname)(filename)) || "Untitled notebook";
741
+ const blocks = convertJupyterNotebookToBlocks(notebook);
742
+ const notebookId = notebook.metadata?.deepnote_notebook_id;
743
+ const notebookName = notebook.metadata?.deepnote_notebook_name;
744
+ const executionMode = notebook.metadata?.deepnote_execution_mode;
745
+ const isModule = notebook.metadata?.deepnote_is_module;
746
+ const workingDirectory = notebook.metadata?.deepnote_working_directory;
747
+ const resolvedNotebookId = i === 0 && firstNotebookId ? firstNotebookId : notebookId ?? (0, uuid.v4)();
748
+ deepnoteFile.project.notebooks.push({
749
+ blocks,
750
+ executionMode: executionMode ?? "block",
751
+ id: resolvedNotebookId,
752
+ isModule: isModule ?? false,
753
+ name: notebookName ?? filenameWithoutExt,
754
+ workingDirectory
755
+ });
756
+ }
757
+ return deepnoteFile;
758
+ }
759
+ /**
760
+ * Converts multiple Jupyter Notebook (.ipynb) files into a single Deepnote project file.
761
+ */
762
+ async function convertIpynbFilesToDeepnoteFile(inputFilePaths, options) {
763
+ const notebooks = [];
764
+ for (const filePath of inputFilePaths) {
765
+ const notebook = await parseIpynbFile(filePath);
766
+ notebooks.push({
767
+ filename: (0, node_path.basename)(filePath),
768
+ notebook
769
+ });
770
+ }
771
+ const yamlContent = (0, yaml.stringify)(convertJupyterNotebooksToDeepnote(notebooks, { projectName: options.projectName }));
772
+ const parentDir = (0, node_path.dirname)(options.outputPath);
773
+ await node_fs_promises.default.mkdir(parentDir, { recursive: true });
774
+ await node_fs_promises.default.writeFile(options.outputPath, yamlContent, "utf-8");
775
+ }
776
+ async function parseIpynbFile(filePath) {
777
+ let ipynbJson;
778
+ try {
779
+ ipynbJson = await node_fs_promises.default.readFile(filePath, "utf-8");
780
+ } catch (error) {
781
+ const message = error instanceof Error ? error.message : String(error);
782
+ throw new Error(`Failed to read ${filePath}: ${message}`);
783
+ }
784
+ try {
785
+ return JSON.parse(ipynbJson);
786
+ } catch (error) {
787
+ const message = error instanceof Error ? error.message : String(error);
788
+ throw new Error(`Failed to parse ${filePath}: invalid JSON - ${message}`);
789
+ }
790
+ }
791
+ function convertCellToBlock$3(cell, index, idGenerator) {
792
+ let source = Array.isArray(cell.source) ? cell.source.join("") : cell.source;
793
+ const cellId = cell.metadata?.cell_id;
794
+ const deepnoteCellType = cell.metadata?.deepnote_cell_type;
795
+ const sortingKey = cell.metadata?.deepnote_sorting_key;
796
+ const contentHash = cell.metadata?.deepnote_content_hash;
797
+ const executionStartedAt = cell.metadata?.deepnote_execution_started_at;
798
+ const executionFinishedAt = cell.metadata?.deepnote_execution_finished_at;
799
+ const blockGroup = cell.metadata?.deepnote_block_group ?? cell.block_group ?? idGenerator();
800
+ const deepnoteSource = cell.metadata?.deepnote_source;
801
+ if (deepnoteSource !== void 0) source = deepnoteSource;
802
+ const blockType = deepnoteCellType ?? (cell.cell_type === "code" ? "code" : "markdown");
803
+ const originalMetadata = { ...cell.metadata };
804
+ delete originalMetadata.cell_id;
805
+ delete originalMetadata.deepnote_cell_type;
806
+ delete originalMetadata.deepnote_block_group;
807
+ delete originalMetadata.deepnote_sorting_key;
808
+ delete originalMetadata.deepnote_source;
809
+ delete originalMetadata.deepnote_content_hash;
810
+ delete originalMetadata.deepnote_execution_started_at;
811
+ delete originalMetadata.deepnote_execution_finished_at;
812
+ delete cell.block_group;
813
+ const executionCount = cell.execution_count ?? void 0;
814
+ const hasExecutionCount = executionCount !== void 0;
815
+ const hasOutputs = cell.cell_type === "code" && cell.outputs !== void 0;
816
+ return sortKeysAlphabetically(__deepnote_blocks.deepnoteBlockSchema.parse({
817
+ blockGroup,
818
+ content: source,
819
+ ...contentHash ? { contentHash } : {},
820
+ ...hasExecutionCount ? { executionCount } : {},
821
+ ...executionFinishedAt ? { executionFinishedAt } : {},
822
+ ...executionStartedAt ? { executionStartedAt } : {},
823
+ id: cellId ?? idGenerator(),
824
+ metadata: originalMetadata,
825
+ ...hasOutputs ? { outputs: cell.outputs } : {},
826
+ sortingKey: sortingKey ?? createSortingKey(index),
827
+ type: blockType
828
+ }));
829
+ }
830
+
831
+ //#endregion
832
+ //#region src/marimo-to-deepnote.ts
833
+ /**
834
+ * Splits a string on commas that are at the top level (not inside parentheses, brackets, braces, or string literals).
835
+ * This handles cases like "func(a, b), other" and 'return "a,b", x' correctly.
836
+ * Supports single quotes, double quotes, and backticks, with proper escape handling.
837
+ */
838
+ function splitOnTopLevelCommas(str) {
839
+ const results = [];
840
+ let current = "";
841
+ let depth = 0;
842
+ let inString = null;
843
+ let escaped = false;
844
+ for (const char of str) {
845
+ if (escaped) {
846
+ current += char;
847
+ escaped = false;
848
+ continue;
849
+ }
850
+ if (char === "\\") {
851
+ current += char;
852
+ escaped = true;
853
+ continue;
854
+ }
855
+ if (char === "\"" || char === "'" || char === "`") {
856
+ if (inString === null) inString = char;
857
+ else if (inString === char) inString = null;
858
+ current += char;
859
+ continue;
860
+ }
861
+ if (inString !== null) {
862
+ current += char;
863
+ continue;
864
+ }
865
+ if (char === "(" || char === "[" || char === "{") {
866
+ depth++;
867
+ current += char;
868
+ } else if (char === ")" || char === "]" || char === "}") {
869
+ depth--;
870
+ current += char;
871
+ } else if (char === "," && depth === 0) {
872
+ results.push(current);
873
+ current = "";
874
+ } else current += char;
875
+ }
876
+ if (current) results.push(current);
877
+ return results;
878
+ }
879
+ /**
880
+ * Parses a Marimo Python file into a MarimoApp structure.
881
+ *
882
+ * @param content - The raw content of the .py file
883
+ * @returns A MarimoApp object
884
+ *
885
+ * @example
886
+ * ```typescript
887
+ * const content = `import marimo
888
+ *
889
+ * app = marimo.App()
890
+ *
891
+ * @app.cell
892
+ * def __():
893
+ * print("hello")
894
+ * return
895
+ * `
896
+ * const app = parseMarimoFormat(content)
897
+ * ```
898
+ */
899
+ function parseMarimoFormat(content) {
900
+ const cells = [];
901
+ const generatedWith = /__generated_with\s*=\s*["']([^"']+)["']/.exec(content)?.[1];
902
+ const width = /(?:marimo|mo)\.App\([^)]*width\s*=\s*["']([^"']+)["']/.exec(content)?.[1];
903
+ const title = /(?:marimo|mo)\.App\([^)]*title\s*=\s*["']([^"']+)["']/.exec(content)?.[1];
904
+ const cellRegex = /@app\.cell(?:\(([^)]*)\))?\s*\n\s*def\s+(\w+)\s*\(([^)]*)\)\s*(?:->.*?)?\s*:\s*\n([\s\S]*?)(?=@app\.cell|if\s+__name__|$)/g;
905
+ let match = cellRegex.exec(content);
906
+ while (match !== null) {
907
+ const decoratorArgs = match[1] || "";
908
+ const functionName = match[2];
909
+ const params = match[3].trim();
910
+ let body = match[4];
911
+ const dependencies = params ? params.split(",").map((p) => p.trim()).filter((p) => p.length > 0) : void 0;
912
+ const hidden = /hide_code\s*=\s*True/.test(decoratorArgs);
913
+ const disabled = /disabled\s*=\s*True/.test(decoratorArgs);
914
+ const lines = body.split("\n");
915
+ const firstNonEmptyLine = lines.find((l) => l.trim().length > 0);
916
+ const baseIndent = (firstNonEmptyLine ? /^(\s*)/.exec(firstNonEmptyLine) : null)?.[1] || "";
917
+ let exports$1;
918
+ for (let i = lines.length - 1; i >= 0; i--) {
919
+ const line = lines[i];
920
+ if (line.startsWith(baseIndent) && !line.startsWith(`${baseIndent} `) && !line.startsWith(`${baseIndent}\t`)) {
921
+ const lineContent = line.slice(baseIndent.length);
922
+ const returnMatch = /^return\s*([^\n]*?)(?:,\s*)?$/.exec(lineContent);
923
+ if (returnMatch) {
924
+ const returnVal = returnMatch[1].trim();
925
+ if (returnVal && returnVal !== "None" && returnVal !== "") {
926
+ exports$1 = splitOnTopLevelCommas(returnVal.replace(/^\(|\)$/g, "").replace(/,\s*$/, "")).map((e) => e.trim()).filter((e) => e.length > 0 && e !== "None");
927
+ if (exports$1.length === 0) exports$1 = void 0;
928
+ }
929
+ break;
930
+ }
931
+ }
932
+ }
933
+ const filteredLines = lines.filter((line) => {
934
+ const normalizedLine = line.replace(/\t/g, " ");
935
+ const normalizedBaseIndent = baseIndent.replace(/\t/g, " ");
936
+ if (normalizedLine.startsWith(normalizedBaseIndent) && !normalizedLine.startsWith(`${normalizedBaseIndent} `) && !normalizedLine.startsWith(`${normalizedBaseIndent}\t`)) {
937
+ const lineContent = normalizedLine.slice(normalizedBaseIndent.length);
938
+ if (/^return\s*(?:[^\n]*)?(?:,\s*)?$/.test(lineContent)) return false;
939
+ }
940
+ return true;
941
+ });
942
+ let processedBody;
943
+ if (baseIndent.length > 0) processedBody = filteredLines.map((l) => l.startsWith(baseIndent) ? l.slice(baseIndent.length) : l).join("\n");
944
+ else processedBody = filteredLines.join("\n");
945
+ body = processedBody.trim();
946
+ const isMarkdown = /^\s*mo\.md\s*\(/.test(body) || /^\s*marimo\.md\s*\(/.test(body);
947
+ const isSql = /^\s*(?:\w+\s*=\s*)?(?:mo|marimo)\.sql\s*\(/.test(body);
948
+ if (isMarkdown) {
949
+ const mdMatch = /(?:mo|marimo)\.md\s*\(\s*(?:(?:r|f|rf|fr)?(?:"""([\s\S]*?)"""|'''([\s\S]*?)'''|"([^"]*)"|'([^']*)'))\s*\)/.exec(body);
950
+ if (mdMatch) {
951
+ const mdContent = mdMatch[1] || mdMatch[2] || mdMatch[3] || mdMatch[4] || "";
952
+ cells.push({
953
+ cellType: "markdown",
954
+ content: mdContent.trim(),
955
+ functionName,
956
+ ...dependencies && dependencies.length > 0 ? { dependencies } : {},
957
+ ...hidden ? { hidden } : {},
958
+ ...disabled ? { disabled } : {}
959
+ });
960
+ }
961
+ } else if (isSql) {
962
+ const sqlMatch = /(?:mo|marimo)\.sql\s*\(\s*(?:f)?(?:"""([\s\S]*?)"""|'''([\s\S]*?)'''|"([^"]*)"|'([^']*)')\s*(?:,[\s\S]*)?\)/.exec(body);
963
+ if (sqlMatch) {
964
+ const sqlQuery = sqlMatch[1] || sqlMatch[2] || sqlMatch[3] || sqlMatch[4] || "";
965
+ cells.push({
966
+ cellType: "sql",
967
+ content: sqlQuery.trim(),
968
+ functionName,
969
+ ...dependencies && dependencies.length > 0 ? { dependencies } : {},
970
+ ...exports$1 && exports$1.length > 0 ? { exports: exports$1 } : {},
971
+ ...hidden ? { hidden } : {},
972
+ ...disabled ? { disabled } : {}
973
+ });
974
+ }
975
+ } else cells.push({
976
+ cellType: "code",
977
+ content: body,
978
+ functionName,
979
+ ...dependencies && dependencies.length > 0 ? { dependencies } : {},
980
+ ...exports$1 && exports$1.length > 0 ? { exports: exports$1 } : {},
981
+ ...hidden ? { hidden } : {},
982
+ ...disabled ? { disabled } : {}
983
+ });
984
+ match = cellRegex.exec(content);
985
+ }
986
+ return {
987
+ ...generatedWith ? { generatedWith } : {},
988
+ ...width ? { width } : {},
989
+ ...title ? { title } : {},
990
+ cells
991
+ };
992
+ }
993
+ /**
994
+ * Converts a single Marimo app into an array of Deepnote blocks.
995
+ * This is the lowest-level conversion function.
996
+ *
997
+ * @param app - The Marimo app object to convert
998
+ * @param options - Optional conversion options including custom ID generator
999
+ * @returns Array of DeepnoteBlock objects
1000
+ */
1001
+ function convertMarimoAppToBlocks(app, options) {
1002
+ const idGenerator = options?.idGenerator ?? uuid.v4;
1003
+ return app.cells.map((cell, index) => convertCellToBlock$2(cell, index, idGenerator));
1004
+ }
1005
+ /**
1006
+ * Converts Marimo app objects into a Deepnote project file.
1007
+ * This is a pure conversion function that doesn't perform any file I/O.
1008
+ *
1009
+ * @param apps - Array of Marimo apps with filenames
1010
+ * @param options - Conversion options including project name and optional ID generator
1011
+ * @returns A DeepnoteFile object
1012
+ */
1013
+ function convertMarimoAppsToDeepnote(apps, options) {
1014
+ const idGenerator = options.idGenerator ?? uuid.v4;
1015
+ const firstNotebookId = apps.length > 0 ? idGenerator() : void 0;
1016
+ const deepnoteFile = {
1017
+ metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1018
+ project: {
1019
+ id: idGenerator(),
1020
+ initNotebookId: firstNotebookId,
1021
+ integrations: [],
1022
+ name: options.projectName,
1023
+ notebooks: [],
1024
+ settings: {}
1025
+ },
1026
+ version: "1.0.0"
1027
+ };
1028
+ for (let i = 0; i < apps.length; i++) {
1029
+ const { filename, app } = apps[i];
1030
+ const filenameWithoutExt = (0, node_path.basename)(filename, (0, node_path.extname)(filename)) || "Untitled notebook";
1031
+ const notebookName = app.title || filenameWithoutExt;
1032
+ const blocks = convertMarimoAppToBlocks(app, { idGenerator });
1033
+ const notebookId = i === 0 && firstNotebookId ? firstNotebookId : idGenerator();
1034
+ deepnoteFile.project.notebooks.push({
1035
+ blocks,
1036
+ executionMode: "block",
1037
+ id: notebookId,
1038
+ isModule: false,
1039
+ name: notebookName
1040
+ });
1041
+ }
1042
+ return deepnoteFile;
1043
+ }
1044
+ /**
1045
+ * Converts multiple Marimo (.py) files into a single Deepnote project file.
1046
+ */
1047
+ async function convertMarimoFilesToDeepnoteFile(inputFilePaths, options) {
1048
+ const apps = [];
1049
+ for (const filePath of inputFilePaths) try {
1050
+ const app = parseMarimoFormat(await node_fs_promises.default.readFile(filePath, "utf-8"));
1051
+ apps.push({
1052
+ filename: (0, node_path.basename)(filePath),
1053
+ app
1054
+ });
1055
+ } catch (err) {
1056
+ const errorMessage = err instanceof Error ? err.message : String(err);
1057
+ const errorStack = err instanceof Error ? err.stack : void 0;
1058
+ throw new Error(`Failed to read or parse file ${(0, node_path.basename)(filePath)}: ${errorMessage}`, { cause: errorStack ? {
1059
+ originalError: err,
1060
+ stack: errorStack
1061
+ } : err });
1062
+ }
1063
+ const yamlContent = (0, yaml.stringify)(convertMarimoAppsToDeepnote(apps, { projectName: options.projectName }));
1064
+ const parentDir = (0, node_path.dirname)(options.outputPath);
1065
+ await node_fs_promises.default.mkdir(parentDir, { recursive: true });
1066
+ await node_fs_promises.default.writeFile(options.outputPath, yamlContent, "utf-8");
1067
+ }
1068
+ function convertCellToBlock$2(cell, index, idGenerator) {
1069
+ let blockType;
1070
+ if (cell.cellType === "markdown") blockType = "markdown";
1071
+ else if (cell.cellType === "sql") blockType = "sql";
1072
+ else blockType = "code";
1073
+ const metadata = {};
1074
+ if (cell.dependencies && cell.dependencies.length > 0) metadata.marimo_dependencies = cell.dependencies;
1075
+ if (cell.exports && cell.exports.length > 0) metadata.marimo_exports = cell.exports;
1076
+ if (cell.hidden) metadata.is_code_hidden = true;
1077
+ if (cell.disabled) metadata.marimo_disabled = true;
1078
+ if (cell.functionName && cell.functionName !== "__") metadata.marimo_function_name = cell.functionName;
1079
+ if (cell.cellType === "sql" && cell.exports && cell.exports.length > 0) metadata.deepnote_variable_name = cell.exports[0];
1080
+ return {
1081
+ blockGroup: idGenerator(),
1082
+ content: cell.content,
1083
+ id: idGenerator(),
1084
+ metadata: Object.keys(metadata).length > 0 ? metadata : {},
1085
+ sortingKey: createSortingKey(index),
1086
+ type: blockType
1087
+ };
1088
+ }
1089
+
1090
+ //#endregion
1091
+ //#region src/percent-to-deepnote.ts
1092
+ /**
1093
+ * Parses a percent format Python file into a PercentNotebook structure.
1094
+ *
1095
+ * @param content - The raw content of the .py file
1096
+ * @returns A PercentNotebook object
1097
+ *
1098
+ * @example
1099
+ * ```typescript
1100
+ * const content = `# %% [markdown]
1101
+ * # # My Title
1102
+ *
1103
+ * # %%
1104
+ * print("hello")
1105
+ * `
1106
+ * const notebook = parsePercentFormat(content)
1107
+ * ```
1108
+ */
1109
+ function parsePercentFormat(content) {
1110
+ const cells = [];
1111
+ const lines = content.split("\n");
1112
+ if (lines.length === 0 || lines.length === 1 && lines[0] === "") return { cells: [] };
1113
+ const cellMarkerRegex = /^# %%\s*(?:\[(\w+)\])?\s*(.*)$/;
1114
+ let currentCell = null;
1115
+ let currentContent = [];
1116
+ function finalizeCell() {
1117
+ if (currentCell) {
1118
+ if (currentCell.cellType === "markdown") currentCell.content = currentContent.map((line) => {
1119
+ if (line.startsWith("# ")) return line.slice(2);
1120
+ if (line === "#") return "";
1121
+ return line;
1122
+ }).join("\n").trim();
1123
+ else currentCell.content = currentContent.join("\n").trim();
1124
+ cells.push(currentCell);
1125
+ }
1126
+ currentContent = [];
1127
+ }
1128
+ for (const line of lines) {
1129
+ const match = cellMarkerRegex.exec(line);
1130
+ if (match) {
1131
+ finalizeCell();
1132
+ const cellTypeStr = match[1]?.toLowerCase() || "code";
1133
+ const rest = match[2]?.trim() || "";
1134
+ let cellType = "code";
1135
+ if (cellTypeStr === "markdown" || cellTypeStr === "md") cellType = "markdown";
1136
+ else if (cellTypeStr === "raw") cellType = "raw";
1137
+ let title;
1138
+ let tags;
1139
+ const tagsMatch = /tags\s*=\s*\[([^\]]*)\]/.exec(rest);
1140
+ if (tagsMatch) {
1141
+ tags = tagsMatch[1].split(",").map((t) => t.trim().replace(/^["']|["']$/g, "")).filter((t) => t.length > 0);
1142
+ const titlePart = rest.replace(/tags\s*=\s*\[[^\]]*\]/, "").trim();
1143
+ if (titlePart) title = titlePart;
1144
+ } else if (rest) title = rest;
1145
+ currentCell = {
1146
+ cellType,
1147
+ content: "",
1148
+ ...title ? { title } : {},
1149
+ ...tags && tags.length > 0 ? { tags } : {}
1150
+ };
1151
+ } else if (currentCell) currentContent.push(line);
1152
+ else if (line.trim() !== "" || currentContent.length > 0) {
1153
+ currentCell = {
1154
+ cellType: "code",
1155
+ content: ""
1156
+ };
1157
+ currentContent.push(line);
1158
+ }
1159
+ }
1160
+ finalizeCell();
1161
+ return { cells };
1162
+ }
1163
+ /**
1164
+ * Converts a single percent format notebook into an array of Deepnote blocks.
1165
+ * This is the lowest-level conversion function.
1166
+ *
1167
+ * @param notebook - The percent notebook object to convert
1168
+ * @param options - Optional conversion options including custom ID generator
1169
+ * @returns Array of DeepnoteBlock objects
1170
+ */
1171
+ function convertPercentNotebookToBlocks(notebook, options) {
1172
+ const idGenerator = options?.idGenerator ?? uuid.v4;
1173
+ return notebook.cells.map((cell, index) => convertCellToBlock$1(cell, index, idGenerator));
1174
+ }
1175
+ /**
1176
+ * Converts percent format notebook objects into a Deepnote project file.
1177
+ * This is a pure conversion function that doesn't perform any file I/O.
1178
+ *
1179
+ * @param notebooks - Array of percent notebooks with filenames
1180
+ * @param options - Conversion options including project name
1181
+ * @returns A DeepnoteFile object
1182
+ */
1183
+ function convertPercentNotebooksToDeepnote(notebooks, options) {
1184
+ const firstNotebookId = notebooks.length > 0 ? (0, uuid.v4)() : void 0;
1185
+ const deepnoteFile = {
1186
+ metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1187
+ project: {
1188
+ id: (0, uuid.v4)(),
1189
+ initNotebookId: firstNotebookId,
1190
+ integrations: [],
1191
+ name: options.projectName,
1192
+ notebooks: [],
1193
+ settings: {}
1194
+ },
1195
+ version: "1.0.0"
1196
+ };
1197
+ for (let i = 0; i < notebooks.length; i++) {
1198
+ const { filename, notebook } = notebooks[i];
1199
+ const filenameWithoutExt = (0, node_path.basename)(filename, (0, node_path.extname)(filename)) || "Untitled notebook";
1200
+ const blocks = convertPercentNotebookToBlocks(notebook);
1201
+ const notebookId = i === 0 && firstNotebookId ? firstNotebookId : (0, uuid.v4)();
1202
+ deepnoteFile.project.notebooks.push({
1203
+ blocks,
1204
+ executionMode: "block",
1205
+ id: notebookId,
1206
+ isModule: false,
1207
+ name: filenameWithoutExt
1208
+ });
1209
+ }
1210
+ return deepnoteFile;
1211
+ }
1212
+ /**
1213
+ * Converts multiple percent format (.py) files into a single Deepnote project file.
1214
+ */
1215
+ async function convertPercentFilesToDeepnoteFile(inputFilePaths, options) {
1216
+ const notebooks = [];
1217
+ for (const filePath of inputFilePaths) {
1218
+ const notebook = parsePercentFormat(await node_fs_promises.default.readFile(filePath, "utf-8"));
1219
+ notebooks.push({
1220
+ filename: (0, node_path.basename)(filePath),
1221
+ notebook
1222
+ });
1223
+ }
1224
+ const yamlContent = (0, yaml.stringify)(convertPercentNotebooksToDeepnote(notebooks, { projectName: options.projectName }));
1225
+ const parentDir = (0, node_path.dirname)(options.outputPath);
1226
+ await node_fs_promises.default.mkdir(parentDir, { recursive: true });
1227
+ await node_fs_promises.default.writeFile(options.outputPath, yamlContent, "utf-8");
1228
+ }
1229
+ function convertCellToBlock$1(cell, index, idGenerator) {
1230
+ const blockType = cell.cellType === "markdown" || cell.cellType === "raw" ? "markdown" : "code";
1231
+ const metadata = {};
1232
+ if (cell.cellType === "raw") metadata.percent_cell_type = "raw";
1233
+ if (cell.title) metadata.title = cell.title;
1234
+ if (cell.tags && cell.tags.length > 0) metadata.tags = cell.tags;
1235
+ return {
1236
+ blockGroup: idGenerator(),
1237
+ content: cell.content,
1238
+ id: idGenerator(),
1239
+ metadata: Object.keys(metadata).length > 0 ? metadata : {},
1240
+ sortingKey: createSortingKey(index),
1241
+ type: blockType
1242
+ };
1243
+ }
1244
+
1245
+ //#endregion
1246
+ //#region src/quarto-to-deepnote.ts
1247
+ /**
1248
+ * Parses a Quarto (.qmd) file into a QuartoDocument structure.
1249
+ *
1250
+ * @param content - The raw content of the .qmd file
1251
+ * @returns A QuartoDocument object
1252
+ *
1253
+ * @example
1254
+ * ```typescript
1255
+ * const content = `---
1256
+ * title: "My Document"
1257
+ * ---
1258
+ *
1259
+ * # Introduction
1260
+ *
1261
+ * \`\`\`{python}
1262
+ * print("hello")
1263
+ * \`\`\`
1264
+ * `
1265
+ * const doc = parseQuartoFormat(content)
1266
+ * ```
1267
+ */
1268
+ function parseQuartoFormat(content) {
1269
+ const cells = [];
1270
+ let frontmatter;
1271
+ let mainContent = content;
1272
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)---\r?\n?/.exec(content);
1273
+ if (frontmatterMatch) {
1274
+ frontmatter = parseYamlFrontmatter(frontmatterMatch[1]);
1275
+ mainContent = content.slice(frontmatterMatch[0].length);
1276
+ }
1277
+ const codeChunkRegex = /```\{([\w-]+)\}\r?\n([\s\S]*?)```/g;
1278
+ let lastIndex = 0;
1279
+ let match = codeChunkRegex.exec(mainContent);
1280
+ while (match !== null) {
1281
+ const markdownBefore = mainContent.slice(lastIndex, match.index).trim();
1282
+ if (markdownBefore) addMarkdownCells(cells, markdownBefore);
1283
+ const language = match[1];
1284
+ let codeContent = match[2];
1285
+ const options = parseQuartoCellOptions(codeContent);
1286
+ if (options) codeContent = codeContent.split("\n").filter((line) => !line.trimStart().startsWith("#|")).join("\n").trim();
1287
+ else codeContent = codeContent.trim();
1288
+ cells.push({
1289
+ cellType: "code",
1290
+ content: codeContent,
1291
+ language,
1292
+ ...options ? { options } : {}
1293
+ });
1294
+ lastIndex = match.index + match[0].length;
1295
+ match = codeChunkRegex.exec(mainContent);
1296
+ }
1297
+ const markdownAfter = mainContent.slice(lastIndex).trim();
1298
+ if (markdownAfter) addMarkdownCells(cells, markdownAfter);
1299
+ return {
1300
+ ...frontmatter ? { frontmatter } : {},
1301
+ cells
1302
+ };
1303
+ }
1304
+ /**
1305
+ * Splits markdown content at cell delimiter comments and adds each part as a separate cell.
1306
+ * The delimiter `<!-- cell -->` is used to preserve cell boundaries during roundtrip conversion.
1307
+ */
1308
+ function addMarkdownCells(cells, content) {
1309
+ const parts = content.split(/\s*<!--\s*cell\s*-->\s*/);
1310
+ for (const part of parts) {
1311
+ const trimmed = part.trim();
1312
+ if (trimmed) cells.push({
1313
+ cellType: "markdown",
1314
+ content: trimmed
1315
+ });
1316
+ }
1317
+ }
1318
+ /**
1319
+ * Parses YAML frontmatter string into a QuartoFrontmatter object.
1320
+ * Uses the yaml package to properly handle nested objects, arrays, and all YAML features.
1321
+ */
1322
+ function parseYamlFrontmatter(yamlString) {
1323
+ if (!yamlString || yamlString.trim() === "") return {};
1324
+ try {
1325
+ const parsed = (0, yaml.parse)(yamlString);
1326
+ if (parsed === null || parsed === void 0) return {};
1327
+ if (typeof parsed !== "object" || Array.isArray(parsed)) return {};
1328
+ return parsed;
1329
+ } catch {
1330
+ return {};
1331
+ }
1332
+ }
1333
+ /**
1334
+ * Parses Quarto cell options from #| lines.
1335
+ */
1336
+ function parseQuartoCellOptions(content) {
1337
+ const optionLines = content.split("\n").filter((line) => line.trimStart().startsWith("#|"));
1338
+ if (optionLines.length === 0) return;
1339
+ const options = {};
1340
+ const raw = {};
1341
+ for (const line of optionLines) {
1342
+ const match = /^#\|\s*(\S+):\s*(.*)$/.exec(line.trimStart());
1343
+ if (match) {
1344
+ const key = match[1];
1345
+ let value = match[2].trim();
1346
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1347
+ else if (value === "true") value = true;
1348
+ else if (value === "false") value = false;
1349
+ else {
1350
+ const num = Number(value);
1351
+ if (!Number.isNaN(num) && Number.isFinite(num)) value = num;
1352
+ }
1353
+ switch (key) {
1354
+ case "label":
1355
+ if (typeof value === "string") options.label = value;
1356
+ else raw[key] = value;
1357
+ break;
1358
+ case "echo":
1359
+ if (typeof value === "boolean") options.echo = value;
1360
+ else if (value === "true" || value === "false") options.echo = value === "true";
1361
+ else raw[key] = value;
1362
+ break;
1363
+ case "eval":
1364
+ if (typeof value === "boolean") options.eval = value;
1365
+ else if (value === "true" || value === "false") options.eval = value === "true";
1366
+ else raw[key] = value;
1367
+ break;
1368
+ case "output":
1369
+ if (typeof value === "boolean") options.output = value;
1370
+ else if (value === "true" || value === "false") options.output = value === "true";
1371
+ else raw[key] = value;
1372
+ break;
1373
+ case "fig-cap":
1374
+ if (typeof value === "string") options.figCap = value;
1375
+ else raw[key] = value;
1376
+ break;
1377
+ case "fig-width":
1378
+ if (typeof value === "number" && Number.isFinite(value)) options.figWidth = value;
1379
+ else if (typeof value === "string") {
1380
+ const num = Number(value);
1381
+ if (!Number.isNaN(num) && Number.isFinite(num)) options.figWidth = num;
1382
+ else raw[key] = value;
1383
+ } else raw[key] = value;
1384
+ break;
1385
+ case "fig-height":
1386
+ if (typeof value === "number" && Number.isFinite(value)) options.figHeight = value;
1387
+ else if (typeof value === "string") {
1388
+ const num = Number(value);
1389
+ if (!Number.isNaN(num) && Number.isFinite(num)) options.figHeight = num;
1390
+ else raw[key] = value;
1391
+ } else raw[key] = value;
1392
+ break;
1393
+ case "tbl-cap":
1394
+ if (typeof value === "string") options.tblCap = value;
1395
+ else raw[key] = value;
1396
+ break;
1397
+ case "warning":
1398
+ if (typeof value === "boolean") options.warning = value;
1399
+ else if (value === "true" || value === "false") options.warning = value === "true";
1400
+ else raw[key] = value;
1401
+ break;
1402
+ case "message":
1403
+ if (typeof value === "boolean") options.message = value;
1404
+ else if (value === "true" || value === "false") options.message = value === "true";
1405
+ else raw[key] = value;
1406
+ break;
1407
+ default: raw[key] = value;
1408
+ }
1409
+ }
1410
+ }
1411
+ if (Object.keys(raw).length > 0) options.raw = raw;
1412
+ return Object.keys(options).length > 0 ? options : void 0;
1413
+ }
1414
+ /**
1415
+ * Converts a single Quarto document into an array of Deepnote blocks.
1416
+ * This is the lowest-level conversion function.
1417
+ *
1418
+ * @param document - The Quarto document object to convert
1419
+ * @param options - Optional conversion options including custom ID generator
1420
+ * @returns Array of DeepnoteBlock objects
1421
+ */
1422
+ function convertQuartoDocumentToBlocks(document, options) {
1423
+ const idGenerator = options?.idGenerator ?? uuid.v4;
1424
+ const blocks = [];
1425
+ if (document.frontmatter?.title) blocks.push({
1426
+ blockGroup: idGenerator(),
1427
+ content: `# ${document.frontmatter.title}`,
1428
+ id: idGenerator(),
1429
+ metadata: {},
1430
+ sortingKey: createSortingKey(blocks.length),
1431
+ type: "markdown"
1432
+ });
1433
+ for (const cell of document.cells) blocks.push(convertCellToBlock(cell, blocks.length, idGenerator));
1434
+ return blocks;
1435
+ }
1436
+ /**
1437
+ * Converts Quarto document objects into a Deepnote project file.
1438
+ * This is a pure conversion function that doesn't perform any file I/O.
1439
+ *
1440
+ * @param documents - Array of Quarto documents with filenames
1441
+ * @param options - Conversion options including project name
1442
+ * @returns A DeepnoteFile object
1443
+ */
1444
+ function convertQuartoDocumentsToDeepnote(documents, options) {
1445
+ const firstNotebookId = documents.length > 0 ? (0, uuid.v4)() : void 0;
1446
+ const deepnoteFile = {
1447
+ metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1448
+ project: {
1449
+ id: (0, uuid.v4)(),
1450
+ initNotebookId: firstNotebookId,
1451
+ integrations: [],
1452
+ name: options.projectName,
1453
+ notebooks: [],
1454
+ settings: {}
1455
+ },
1456
+ version: "1.0.0"
1457
+ };
1458
+ for (let i = 0; i < documents.length; i++) {
1459
+ const { filename, document } = documents[i];
1460
+ const filenameWithoutExt = (0, node_path.basename)(filename, (0, node_path.extname)(filename)) || "Untitled notebook";
1461
+ const notebookName = document.frontmatter?.title || filenameWithoutExt;
1462
+ const blocks = convertQuartoDocumentToBlocks(document);
1463
+ const notebookId = i === 0 && firstNotebookId ? firstNotebookId : (0, uuid.v4)();
1464
+ deepnoteFile.project.notebooks.push({
1465
+ blocks,
1466
+ executionMode: "block",
1467
+ id: notebookId,
1468
+ isModule: false,
1469
+ name: typeof notebookName === "string" ? notebookName : filenameWithoutExt
1470
+ });
1471
+ }
1472
+ return deepnoteFile;
1473
+ }
1474
+ /**
1475
+ * Converts multiple Quarto (.qmd) files into a single Deepnote project file.
1476
+ */
1477
+ async function convertQuartoFilesToDeepnoteFile(inputFilePaths, options) {
1478
+ const documents = [];
1479
+ for (const filePath of inputFilePaths) {
1480
+ const document = parseQuartoFormat(await node_fs_promises.default.readFile(filePath, "utf-8"));
1481
+ documents.push({
1482
+ filename: (0, node_path.basename)(filePath),
1483
+ document
1484
+ });
1485
+ }
1486
+ const yamlContent = (0, yaml.stringify)(convertQuartoDocumentsToDeepnote(documents, { projectName: options.projectName }));
1487
+ const parentDir = (0, node_path.dirname)(options.outputPath);
1488
+ await node_fs_promises.default.mkdir(parentDir, { recursive: true });
1489
+ await node_fs_promises.default.writeFile(options.outputPath, yamlContent, "utf-8");
1490
+ }
1491
+ function convertCellToBlock(cell, index, idGenerator) {
1492
+ const blockType = cell.cellType === "markdown" ? "markdown" : "code";
1493
+ const metadata = {};
1494
+ if (cell.options) {
1495
+ if (cell.options.label) metadata.quarto_label = cell.options.label;
1496
+ if (cell.options.echo === false) metadata.is_code_hidden = true;
1497
+ if (cell.options.figCap) metadata.quarto_fig_cap = cell.options.figCap;
1498
+ if (cell.options.tblCap) metadata.quarto_tbl_cap = cell.options.tblCap;
1499
+ if (cell.options.raw) metadata.quarto_options = cell.options.raw;
1500
+ }
1501
+ if (cell.language && cell.language !== "python") metadata.language = cell.language;
1502
+ return {
1503
+ blockGroup: idGenerator(),
1504
+ content: cell.content,
1505
+ id: idGenerator(),
1506
+ metadata: Object.keys(metadata).length > 0 ? metadata : {},
1507
+ sortingKey: createSortingKey(index),
1508
+ type: blockType
1509
+ };
1510
+ }
1511
+
1512
+ //#endregion
1513
+ Object.defineProperty(exports, '__toESM', {
1514
+ enumerable: true,
1515
+ get: function () {
1516
+ return __toESM;
1517
+ }
1518
+ });
1519
+ Object.defineProperty(exports, 'convertBlocksToJupyterNotebook', {
1520
+ enumerable: true,
1521
+ get: function () {
1522
+ return convertBlocksToJupyterNotebook;
1523
+ }
1524
+ });
1525
+ Object.defineProperty(exports, 'convertBlocksToMarimoApp', {
1526
+ enumerable: true,
1527
+ get: function () {
1528
+ return convertBlocksToMarimoApp;
1529
+ }
1530
+ });
1531
+ Object.defineProperty(exports, 'convertBlocksToPercentNotebook', {
1532
+ enumerable: true,
1533
+ get: function () {
1534
+ return convertBlocksToPercentNotebook;
1535
+ }
1536
+ });
1537
+ Object.defineProperty(exports, 'convertBlocksToQuartoDocument', {
1538
+ enumerable: true,
1539
+ get: function () {
1540
+ return convertBlocksToQuartoDocument;
1541
+ }
1542
+ });
1543
+ Object.defineProperty(exports, 'convertDeepnoteFileToJupyterFiles', {
1544
+ enumerable: true,
1545
+ get: function () {
1546
+ return convertDeepnoteFileToJupyterFiles;
1547
+ }
1548
+ });
1549
+ Object.defineProperty(exports, 'convertDeepnoteFileToMarimoFiles', {
1550
+ enumerable: true,
1551
+ get: function () {
1552
+ return convertDeepnoteFileToMarimoFiles;
1553
+ }
1554
+ });
1555
+ Object.defineProperty(exports, 'convertDeepnoteFileToPercentFiles', {
1556
+ enumerable: true,
1557
+ get: function () {
1558
+ return convertDeepnoteFileToPercentFiles;
1559
+ }
1560
+ });
1561
+ Object.defineProperty(exports, 'convertDeepnoteFileToQuartoFiles', {
1562
+ enumerable: true,
1563
+ get: function () {
1564
+ return convertDeepnoteFileToQuartoFiles;
1565
+ }
1566
+ });
1567
+ Object.defineProperty(exports, 'convertDeepnoteToJupyterNotebooks', {
1568
+ enumerable: true,
1569
+ get: function () {
1570
+ return convertDeepnoteToJupyterNotebooks;
1571
+ }
1572
+ });
1573
+ Object.defineProperty(exports, 'convertDeepnoteToMarimoApps', {
1574
+ enumerable: true,
1575
+ get: function () {
1576
+ return convertDeepnoteToMarimoApps;
1577
+ }
1578
+ });
1579
+ Object.defineProperty(exports, 'convertDeepnoteToPercentNotebooks', {
1580
+ enumerable: true,
1581
+ get: function () {
1582
+ return convertDeepnoteToPercentNotebooks;
1583
+ }
1584
+ });
1585
+ Object.defineProperty(exports, 'convertDeepnoteToQuartoDocuments', {
1586
+ enumerable: true,
1587
+ get: function () {
1588
+ return convertDeepnoteToQuartoDocuments;
1589
+ }
1590
+ });
1591
+ Object.defineProperty(exports, 'convertIpynbFilesToDeepnoteFile', {
1592
+ enumerable: true,
1593
+ get: function () {
1594
+ return convertIpynbFilesToDeepnoteFile;
1595
+ }
1596
+ });
1597
+ Object.defineProperty(exports, 'convertJupyterNotebookToBlocks', {
1598
+ enumerable: true,
1599
+ get: function () {
1600
+ return convertJupyterNotebookToBlocks;
1601
+ }
1602
+ });
1603
+ Object.defineProperty(exports, 'convertJupyterNotebooksToDeepnote', {
1604
+ enumerable: true,
1605
+ get: function () {
1606
+ return convertJupyterNotebooksToDeepnote;
1607
+ }
1608
+ });
1609
+ Object.defineProperty(exports, 'convertMarimoAppToBlocks', {
1610
+ enumerable: true,
1611
+ get: function () {
1612
+ return convertMarimoAppToBlocks;
1613
+ }
1614
+ });
1615
+ Object.defineProperty(exports, 'convertMarimoAppsToDeepnote', {
1616
+ enumerable: true,
1617
+ get: function () {
1618
+ return convertMarimoAppsToDeepnote;
1619
+ }
1620
+ });
1621
+ Object.defineProperty(exports, 'convertMarimoFilesToDeepnoteFile', {
1622
+ enumerable: true,
1623
+ get: function () {
1624
+ return convertMarimoFilesToDeepnoteFile;
1625
+ }
1626
+ });
1627
+ Object.defineProperty(exports, 'convertPercentFilesToDeepnoteFile', {
1628
+ enumerable: true,
1629
+ get: function () {
1630
+ return convertPercentFilesToDeepnoteFile;
1631
+ }
1632
+ });
1633
+ Object.defineProperty(exports, 'convertPercentNotebookToBlocks', {
1634
+ enumerable: true,
1635
+ get: function () {
1636
+ return convertPercentNotebookToBlocks;
1637
+ }
1638
+ });
1639
+ Object.defineProperty(exports, 'convertPercentNotebooksToDeepnote', {
1640
+ enumerable: true,
1641
+ get: function () {
1642
+ return convertPercentNotebooksToDeepnote;
1643
+ }
1644
+ });
1645
+ Object.defineProperty(exports, 'convertQuartoDocumentToBlocks', {
1646
+ enumerable: true,
1647
+ get: function () {
1648
+ return convertQuartoDocumentToBlocks;
1649
+ }
1650
+ });
1651
+ Object.defineProperty(exports, 'convertQuartoDocumentsToDeepnote', {
1652
+ enumerable: true,
1653
+ get: function () {
1654
+ return convertQuartoDocumentsToDeepnote;
1655
+ }
1656
+ });
1657
+ Object.defineProperty(exports, 'convertQuartoFilesToDeepnoteFile', {
1658
+ enumerable: true,
1659
+ get: function () {
1660
+ return convertQuartoFilesToDeepnoteFile;
1661
+ }
1662
+ });
1663
+ Object.defineProperty(exports, 'detectFormat', {
1664
+ enumerable: true,
1665
+ get: function () {
1666
+ return detectFormat;
1667
+ }
1668
+ });
1669
+ Object.defineProperty(exports, 'isMarimoContent', {
1670
+ enumerable: true,
1671
+ get: function () {
1672
+ return isMarimoContent;
1673
+ }
1674
+ });
1675
+ Object.defineProperty(exports, 'isPercentContent', {
1676
+ enumerable: true,
1677
+ get: function () {
1678
+ return isPercentContent;
1679
+ }
1680
+ });
1681
+ Object.defineProperty(exports, 'parseMarimoFormat', {
1682
+ enumerable: true,
1683
+ get: function () {
1684
+ return parseMarimoFormat;
1685
+ }
1686
+ });
1687
+ Object.defineProperty(exports, 'parsePercentFormat', {
1688
+ enumerable: true,
1689
+ get: function () {
1690
+ return parsePercentFormat;
1691
+ }
1692
+ });
1693
+ Object.defineProperty(exports, 'parseQuartoFormat', {
1694
+ enumerable: true,
1695
+ get: function () {
1696
+ return parseQuartoFormat;
1697
+ }
1698
+ });
1699
+ Object.defineProperty(exports, 'serializeMarimoFormat', {
1700
+ enumerable: true,
1701
+ get: function () {
1702
+ return serializeMarimoFormat;
1703
+ }
1704
+ });
1705
+ Object.defineProperty(exports, 'serializePercentFormat', {
1706
+ enumerable: true,
1707
+ get: function () {
1708
+ return serializePercentFormat;
1709
+ }
1710
+ });
1711
+ Object.defineProperty(exports, 'serializeQuartoFormat', {
1712
+ enumerable: true,
1713
+ get: function () {
1714
+ return serializeQuartoFormat;
1715
+ }
1716
+ });