@deepnote/convert 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @deepnote/convert
2
2
 
3
- Bidirectional converter between Jupyter Notebook files (`.ipynb`) and Deepnote project files (`.deepnote`) with lossless roundtrip support.
3
+ Bidirectional converter between Deepnote project files (`.deepnote`) and multiple notebook formats: Jupyter (`.ipynb`), Quarto (`.qmd`), Percent (`.py`), and Marimo (`.py`).
4
4
 
5
5
  ```bash
6
- # Convert a Jupyter notebook to a Deepnote project
6
+ # Convert any supported format to Deepnote
7
7
  npx @deepnote/convert notebook.ipynb
8
-
9
- # Convert a Deepnote project to Jupyter notebooks
10
- npx @deepnote/convert project.deepnote
8
+ npx @deepnote/convert document.qmd
9
+ npx @deepnote/convert notebook.py # percent or marimo format
10
+
11
+ # Convert Deepnote to any supported format
12
+ npx @deepnote/convert project.deepnote # defaults to Jupyter
13
+ npx @deepnote/convert project.deepnote --outputFormat quarto
14
+ npx @deepnote/convert project.deepnote --outputFormat percent
15
+ npx @deepnote/convert project.deepnote --outputFormat marimo
11
16
  ```
12
17
 
13
18
  ## Installation
@@ -16,153 +21,231 @@ npx @deepnote/convert project.deepnote
16
21
  npm install -g @deepnote/convert
17
22
  ```
18
23
 
19
- ## CLI Usage
20
-
21
- The package provides a `deepnote-convert` command-line tool for bidirectional conversion between Jupyter and Deepnote formats.
24
+ ## Supported Formats
22
25
 
23
- ### Convert Jupyter Deepnote
26
+ | Format | Extension | Description |
27
+ | ----------- | --------- | ------------------------------------------------------- |
28
+ | **Jupyter** | `.ipynb` | Standard Jupyter Notebook JSON format |
29
+ | **Quarto** | `.qmd` | Quarto markdown documents with code chunks |
30
+ | **Percent** | `.py` | Python files with `# %%` cell markers (VS Code, Spyder) |
31
+ | **Marimo** | `.py` | Marimo reactive notebooks with `@app.cell` decorators |
24
32
 
25
- #### Convert a Single Notebook
26
-
27
- Convert a single `.ipynb` file to a `.deepnote` file:
28
-
29
- ```bash
30
- deepnote-convert path/to/notebook.ipynb
31
- ```
33
+ ## CLI Usage
32
34
 
33
- This will create a `notebook.deepnote` file in the current directory.
35
+ The package provides a `deepnote-convert` command-line tool for bidirectional conversion.
34
36
 
35
- #### Convert a Directory of Notebooks
37
+ ### Convert to Deepnote
36
38
 
37
- Convert all `.ipynb` files in a directory to a single `.deepnote` project:
39
+ Any supported format converts to `.deepnote` automatically:
38
40
 
39
41
  ```bash
42
+ # Single file
43
+ deepnote-convert notebook.ipynb
44
+ deepnote-convert document.qmd
45
+ deepnote-convert notebook.py # auto-detects percent vs marimo
46
+
47
+ # Directory of files
40
48
  deepnote-convert path/to/notebooks/
41
49
  ```
42
50
 
43
- This will create a `notebooks.deepnote` file in the current directory containing all notebooks from the directory.
51
+ ### Convert from Deepnote
44
52
 
45
- ### Convert Deepnote Jupyter
46
-
47
- Convert a `.deepnote` file to Jupyter notebooks:
53
+ Use `--outputFormat` to choose the target format (defaults to `jupyter`):
48
54
 
49
55
  ```bash
50
- deepnote-convert path/to/project.deepnote
56
+ deepnote-convert project.deepnote # → Jupyter notebooks
57
+ deepnote-convert project.deepnote --outputFormat jupyter # → Jupyter notebooks
58
+ deepnote-convert project.deepnote --outputFormat quarto # → Quarto documents
59
+ deepnote-convert project.deepnote --outputFormat percent # → Percent format files
60
+ deepnote-convert project.deepnote --outputFormat marimo # → Marimo notebooks
51
61
  ```
52
62
 
53
- This will create a `project/` directory containing separate `.ipynb` files for each notebook in the Deepnote project.
54
-
55
63
  ### Options
56
64
 
57
65
  #### `--projectName <name>`
58
66
 
59
- Set a custom name for the Deepnote project:
67
+ Set a custom name for the Deepnote project (when converting to `.deepnote`):
60
68
 
61
69
  ```bash
62
70
  deepnote-convert notebook.ipynb --projectName "My Analysis"
63
71
  ```
64
72
 
65
- If not specified, the project name will default to the filename (without extension) or directory name.
66
-
67
73
  #### `-o, --outputPath <path>`
68
74
 
69
- Specify where to save the output file(s):
75
+ Specify where to save the output:
70
76
 
71
77
  ```bash
72
- # For Jupyter → Deepnote: Save to a specific file
78
+ # To Deepnote: Save to a specific file or directory
73
79
  deepnote-convert notebook.ipynb -o output/project.deepnote
74
-
75
- # For Jupyter → Deepnote: Save to a directory (filename will be auto-generated)
76
80
  deepnote-convert notebook.ipynb -o output/
77
81
 
78
- # For Deepnote → Jupyter: Specify output directory for notebooks
79
- deepnote-convert project.deepnote -o output/jupyter-notebooks/
82
+ # From Deepnote: Specify output directory
83
+ deepnote-convert project.deepnote -o output/notebooks/
80
84
  ```
81
85
 
82
- If not specified:
86
+ #### `--outputFormat <format>`
87
+
88
+ Choose output format when converting from `.deepnote`:
89
+
90
+ ```bash
91
+ deepnote-convert project.deepnote --outputFormat quarto
92
+ ```
83
93
 
84
- - For Jupyter → Deepnote: Output file will be saved in the current directory
85
- - For Deepnote → Jupyter: A directory will be created using the `.deepnote` filename (e.g., `project.deepnote` → `project/`)
94
+ Options: `jupyter` (default), `quarto`, `percent`, `marimo`
86
95
 
87
96
  ### Examples
88
97
 
89
98
  ```bash
90
- # Jupyter → Deepnote: Convert a single notebook with custom name
99
+ # Jupyter → Deepnote
91
100
  deepnote-convert titanic.ipynb --projectName "Titanic Analysis"
101
+ deepnote-convert ./notebooks -o ./output
92
102
 
93
- # Jupyter → Deepnote: Convert all notebooks in a directory
94
- deepnote-convert ./analysis --projectName "Data Science Project" -o ./output
103
+ # Quarto → Deepnote
104
+ deepnote-convert analysis.qmd --projectName "Data Report"
95
105
 
96
- # Jupyter → Deepnote: Convert multiple notebooks from a folder
97
- deepnote-convert ~/notebooks/ml-experiments -o ~/projects/
106
+ # Percent → Deepnote (auto-detected from # %% markers)
107
+ deepnote-convert script.py
98
108
 
99
- # DeepnoteJupyter: Convert a Deepnote project to Jupyter notebooks
100
- deepnote-convert my-project.deepnote
109
+ # Marimo → Deepnote (auto-detected from @app.cell decorators)
110
+ deepnote-convert reactive.py
101
111
 
102
- # Deepnote → Jupyter: Specify output directory
103
- deepnote-convert my-project.deepnote -o ./jupyter-notebooks/
112
+ # Deepnote → various formats
113
+ deepnote-convert project.deepnote # Jupyter (default)
114
+ deepnote-convert project.deepnote --outputFormat quarto # Quarto
115
+ deepnote-convert project.deepnote --outputFormat percent # Percent
116
+ deepnote-convert project.deepnote --outputFormat marimo # Marimo
104
117
  ```
105
118
 
119
+ ### Python File Detection
120
+
121
+ When converting `.py` files, the converter auto-detects the format:
122
+
123
+ - **Marimo**: Contains `import marimo` and `@app.cell` decorators
124
+ - **Percent**: Contains `# %%` cell markers
125
+
126
+ For directory scanning, use explicit naming:
127
+
128
+ - `*.marimo.py` for Marimo files
129
+ - `*.percent.py` for percent format files
130
+
106
131
  ### Lossless Roundtrip Conversion
107
132
 
108
- The converter supports lossless roundtrip conversions:
133
+ The converter supports lossless roundtrip conversions for Jupyter:
109
134
 
110
- - **Deepnote → Jupyter → Deepnote**: Preserves all Deepnote-specific metadata in Jupyter cell metadata, enabling faithful reconstruction of the original notebook's structure and metadata (note: serialization formatting or key ordering may differ)
135
+ - **Deepnote → Jupyter → Deepnote**: Preserves all Deepnote-specific metadata in Jupyter cell metadata
111
136
  - **Jupyter → Deepnote → Jupyter**: Preserves original Jupyter content while adding Deepnote metadata
112
137
 
113
- This is achieved by storing Deepnote-specific metadata as flat `deepnote_*` keys directly on Jupyter notebook metadata (e.g., `deepnote_notebook_id`, `deepnote_execution_mode`) and cell metadata (e.g., `deepnote_cell_type`, `deepnote_sorting_key`, `deepnote_source`).
138
+ Other formats (Quarto, Percent, Marimo) preserve content and structure but may not retain all Deepnote-specific metadata.
114
139
 
115
- ## Programmatic Usage
140
+ ### Platform Compatibility
116
141
 
117
- You can also use the conversion functions programmatically in your Node.js or TypeScript applications.
142
+ Since Jupyter (`.ipynb`) is the standard format, notebooks from cloud platforms that use Jupyter are fully supported with metadata preservation:
118
143
 
119
- ### Jupyter Deepnote
144
+ | Platform | Status | Notes |
145
+ | ---------------------- | ------------------- | -------------------------------------------------- |
146
+ | **Google Colab** | ✅ Fully compatible | Preserves Colab cell IDs, form cells, GPU settings |
147
+ | **Amazon SageMaker** | ✅ Fully compatible | Preserves tags, training/inference markers |
148
+ | **Kaggle** | ✅ Fully compatible | Preserves UUIDs, cell GUIDs, hide input/output |
149
+ | **Azure ML Notebooks** | ✅ Fully compatible | Standard Jupyter with Azure metadata |
150
+ | **JupyterLab/Hub** | ✅ Fully compatible | Standard Jupyter format |
151
+
152
+ Platform-specific cell metadata is preserved during roundtrip conversion, allowing notebooks to be edited in Deepnote and exported back to the original platform without losing settings.
153
+
154
+ ## Programmatic Usage
155
+
156
+ ### Convert to Deepnote
120
157
 
121
158
  ```typescript
122
- import { convertIpynbFilesToDeepnoteFile } from "@deepnote/convert";
159
+ import {
160
+ convertIpynbFilesToDeepnoteFile,
161
+ convertQuartoFilesToDeepnoteFile,
162
+ convertPercentFilesToDeepnoteFile,
163
+ convertMarimoFilesToDeepnoteFile,
164
+ } from "@deepnote/convert";
165
+
166
+ // From Jupyter
167
+ await convertIpynbFilesToDeepnoteFile(["notebook.ipynb"], {
168
+ outputPath: "output.deepnote",
169
+ projectName: "My Project",
170
+ });
123
171
 
124
- await convertIpynbFilesToDeepnoteFile(["path/to/notebook.ipynb"], {
172
+ // From Quarto
173
+ await convertQuartoFilesToDeepnoteFile(["document.qmd"], {
125
174
  outputPath: "output.deepnote",
126
175
  projectName: "My Project",
127
176
  });
128
- ```
129
177
 
130
- ### Deepnote → Jupyter
178
+ // From Percent
179
+ await convertPercentFilesToDeepnoteFile(["script.py"], {
180
+ outputPath: "output.deepnote",
181
+ projectName: "My Project",
182
+ });
131
183
 
132
- #### File-based Conversion
184
+ // From Marimo
185
+ await convertMarimoFilesToDeepnoteFile(["notebook.py"], {
186
+ outputPath: "output.deepnote",
187
+ projectName: "My Project",
188
+ });
189
+ ```
133
190
 
134
- For automatic file I/O (reading and writing files):
191
+ ### Convert from Deepnote
135
192
 
136
193
  ```typescript
137
- import { convertDeepnoteFileToJupyter } from "@deepnote/convert";
138
-
139
- await convertDeepnoteFileToJupyter("path/to/project.deepnote", {
194
+ import {
195
+ convertDeepnoteFileToJupyter,
196
+ convertDeepnoteFileToQuartoFiles,
197
+ convertDeepnoteFileToPercentFiles,
198
+ convertDeepnoteFileToMarimoFiles,
199
+ } from "@deepnote/convert";
200
+
201
+ // To Jupyter
202
+ await convertDeepnoteFileToJupyter("project.deepnote", {
140
203
  outputDir: "./jupyter-notebooks",
141
204
  });
205
+
206
+ // To Quarto
207
+ await convertDeepnoteFileToQuartoFiles("project.deepnote", {
208
+ outputDir: "./quarto-docs",
209
+ });
210
+
211
+ // To Percent
212
+ await convertDeepnoteFileToPercentFiles("project.deepnote", {
213
+ outputDir: "./percent-scripts",
214
+ });
215
+
216
+ // To Marimo
217
+ await convertDeepnoteFileToMarimoFiles("project.deepnote", {
218
+ outputDir: "./marimo-notebooks",
219
+ });
142
220
  ```
143
221
 
144
- #### Pure Conversion (No File I/O)
222
+ ### Pure Conversion (No File I/O)
145
223
 
146
224
  For programmatic use with in-memory data:
147
225
 
148
226
  ```typescript
149
227
  import fs from "node:fs/promises";
150
228
  import { deserializeDeepnoteFile } from "@deepnote/blocks";
151
- import { convertDeepnoteToJupyterNotebooks } from "@deepnote/convert";
229
+ import {
230
+ convertDeepnoteToJupyterNotebooks,
231
+ convertDeepnoteToQuartoDocuments,
232
+ convertDeepnoteToPercentNotebooks,
233
+ convertDeepnoteToMarimoApps,
234
+ } from "@deepnote/convert";
152
235
 
153
236
  // Read and deserialize the Deepnote file
154
237
  const yamlContent = await fs.readFile("project.deepnote", "utf-8");
155
238
  const deepnoteFile = deserializeDeepnoteFile(yamlContent);
156
239
 
157
- // Convert to Jupyter notebooks (pure function, no I/O)
158
- const notebooks = convertDeepnoteToJupyterNotebooks(deepnoteFile);
240
+ // Convert to any format (pure functions, no I/O)
241
+ const jupyterNotebooks = convertDeepnoteToJupyterNotebooks(deepnoteFile);
242
+ const quartoDocuments = convertDeepnoteToQuartoDocuments(deepnoteFile);
243
+ const percentNotebooks = convertDeepnoteToPercentNotebooks(deepnoteFile);
244
+ const marimoApps = convertDeepnoteToMarimoApps(deepnoteFile);
159
245
 
160
- // Now you can work with the notebooks in memory
161
- for (const { filename, notebook } of notebooks) {
246
+ // Work with the results in memory
247
+ for (const { filename, notebook } of jupyterNotebooks) {
162
248
  console.log(`${filename}: ${notebook.cells.length} cells`);
163
-
164
- // Or save them yourself
165
- await fs.writeFile(filename, JSON.stringify(notebook, null, 2));
166
249
  }
167
250
  ```
168
251
 
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as convertDeepnoteFileToJupyterFiles, t as convertIpynbFilesToDeepnoteFile } from "./src-j4HyYJfD.js";
2
+ import { T as convertDeepnoteFileToMarimoFiles, _ as convertDeepnoteFileToQuartoFiles, a as convertPercentFilesToDeepnoteFile, d as convertMarimoFilesToDeepnoteFile, k as convertDeepnoteFileToJupyterFiles, p as convertIpynbFilesToDeepnoteFile, r as convertQuartoFilesToDeepnoteFile, x as convertDeepnoteFileToPercentFiles } from "./src-CUESP0m8.js";
3
3
  import { cli } from "cleye";
4
4
  import fs from "node:fs/promises";
5
5
  import { basename, extname, resolve } from "node:path";
@@ -8,7 +8,7 @@ import ora from "ora";
8
8
 
9
9
  //#region src/cli.ts
10
10
  async function convert(options) {
11
- const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd() } = options;
11
+ const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd(), outputFormat = "jupyter" } = options;
12
12
  const resolveProjectName = (possibleName) => {
13
13
  if (customProjectName) return customProjectName;
14
14
  if (possibleName) return possibleName;
@@ -24,56 +24,165 @@ async function convert(options) {
24
24
  };
25
25
  const absolutePath = resolve(cwd, inputPath);
26
26
  if ((await fs.stat(absolutePath)).isDirectory()) {
27
- const ipynbFiles = (await fs.readdir(absolutePath, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".ipynb")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
28
- if (ipynbFiles.length === 0) throw new Error("No .ipynb files found in the specified directory.");
29
- const spinner = ora("Converting Jupyter Notebooks to a Deepnote project...").start();
30
- try {
31
- const filenameWithoutExtension = basename(absolutePath);
32
- const projectName = resolveProjectName(filenameWithoutExtension);
33
- const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
34
- await convertIpynbFilesToDeepnoteFile(ipynbFiles.map((file) => resolve(absolutePath, file)), {
35
- projectName,
36
- outputPath
37
- });
38
- spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
39
- return outputPath;
40
- } catch (error) {
41
- spinner.fail("Conversion failed");
42
- throw error;
27
+ const entries = await fs.readdir(absolutePath, { withFileTypes: true });
28
+ const ipynbFiles = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".ipynb")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
29
+ const quartoFiles = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".qmd")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
30
+ const pyFiles = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".py")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
31
+ const classifyPyFile = async (pyFile) => {
32
+ const content = await fs.readFile(resolve(absolutePath, pyFile), "utf-8");
33
+ if (isMarimoContent(content)) return {
34
+ file: pyFile,
35
+ type: "marimo"
36
+ };
37
+ if (isPercentContent(content)) return {
38
+ file: pyFile,
39
+ type: "percent"
40
+ };
41
+ return {
42
+ file: pyFile,
43
+ type: "unknown"
44
+ };
45
+ };
46
+ const CONCURRENCY_LIMIT = 10;
47
+ const results = [];
48
+ for (let i = 0; i < pyFiles.length; i += CONCURRENCY_LIMIT) {
49
+ const batch = pyFiles.slice(i, i + CONCURRENCY_LIMIT);
50
+ const batchResults = await Promise.all(batch.map(classifyPyFile));
51
+ results.push(...batchResults);
43
52
  }
53
+ const marimoFiles = results.filter((r) => r.type === "marimo").map((r) => r.file);
54
+ const percentFiles = results.filter((r) => r.type === "percent").map((r) => r.file);
55
+ if (ipynbFiles.length > 0) return convertDirectory(absolutePath, ipynbFiles, "jupyter", resolveProjectName, resolveOutputPath);
56
+ if (quartoFiles.length > 0) return convertDirectory(absolutePath, quartoFiles, "quarto", resolveProjectName, resolveOutputPath);
57
+ if (marimoFiles.length > 0) return convertDirectory(absolutePath, marimoFiles, "marimo", resolveProjectName, resolveOutputPath);
58
+ if (percentFiles.length > 0) return convertDirectory(absolutePath, percentFiles, "percent", resolveProjectName, resolveOutputPath);
59
+ throw new Error("No supported notebook files found in the specified directory (.ipynb, .qmd, .py)");
44
60
  }
45
61
  const ext = extname(absolutePath).toLowerCase();
46
- if (ext === ".ipynb") {
47
- const spinner = ora("Converting the Jupyter Notebook to a Deepnote project...").start();
48
- try {
49
- const filenameWithoutExtension = basename(absolutePath, ext);
50
- const projectName = resolveProjectName(filenameWithoutExtension);
51
- const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
52
- await convertIpynbFilesToDeepnoteFile([absolutePath], {
53
- projectName,
54
- outputPath
55
- });
56
- spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
57
- return outputPath;
58
- } catch (error) {
59
- spinner.fail("Conversion failed");
60
- throw error;
62
+ if (ext === ".ipynb") return convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
63
+ if (ext === ".qmd") return convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
64
+ if (ext === ".py") {
65
+ const content = await fs.readFile(absolutePath, "utf-8");
66
+ if (isMarimoContent(content)) return convertMarimoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
67
+ if (isPercentContent(content)) return convertPercentToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
68
+ throw new Error("Unsupported Python file format. File must be a percent format (# %% markers) or Marimo notebook (@app.cell decorators).");
69
+ }
70
+ if (ext === ".deepnote") return convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd);
71
+ throw new Error("Unsupported file type. Please provide a .ipynb, .qmd, .py (percent/marimo), or .deepnote file.");
72
+ }
73
+ async function convertDirectory(dirPath, files, format, resolveProjectName, resolveOutputPath) {
74
+ const spinner = ora(`Converting ${{
75
+ jupyter: "Jupyter Notebooks",
76
+ quarto: "Quarto documents",
77
+ percent: "percent format notebooks",
78
+ marimo: "Marimo notebooks"
79
+ }[format]} to a Deepnote project...`).start();
80
+ try {
81
+ const filenameWithoutExtension = basename(dirPath);
82
+ const projectName = resolveProjectName(filenameWithoutExtension);
83
+ const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
84
+ const inputFilePaths = files.map((file) => resolve(dirPath, file));
85
+ switch (format) {
86
+ case "jupyter":
87
+ await convertIpynbFilesToDeepnoteFile(inputFilePaths, {
88
+ projectName,
89
+ outputPath
90
+ });
91
+ break;
92
+ case "quarto":
93
+ await convertQuartoFilesToDeepnoteFile(inputFilePaths, {
94
+ projectName,
95
+ outputPath
96
+ });
97
+ break;
98
+ case "percent":
99
+ await convertPercentFilesToDeepnoteFile(inputFilePaths, {
100
+ projectName,
101
+ outputPath
102
+ });
103
+ break;
104
+ case "marimo":
105
+ await convertMarimoFilesToDeepnoteFile(inputFilePaths, {
106
+ projectName,
107
+ outputPath
108
+ });
109
+ break;
61
110
  }
111
+ spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
112
+ return outputPath;
113
+ } catch (error) {
114
+ spinner.fail("Conversion failed");
115
+ throw error;
116
+ }
117
+ }
118
+ async function convertSingleFileToDeepnote(absolutePath, formatName, converter, resolveProjectName, resolveOutputPath) {
119
+ const spinner = ora(`Converting the ${formatName} to a Deepnote project...`).start();
120
+ try {
121
+ const filenameWithoutExtension = basename(absolutePath, extname(absolutePath));
122
+ const projectName = resolveProjectName(filenameWithoutExtension);
123
+ const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
124
+ await converter([absolutePath], {
125
+ projectName,
126
+ outputPath
127
+ });
128
+ spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
129
+ return outputPath;
130
+ } catch (error) {
131
+ spinner.fail("Conversion failed");
132
+ throw error;
62
133
  }
63
- if (ext === ".deepnote") {
64
- const spinner = ora("Converting Deepnote project to Jupyter Notebooks...").start();
65
- try {
66
- const outputDirName = basename(absolutePath, ext);
67
- const outputDir = customOutputPath ? resolve(cwd, customOutputPath) : resolve(cwd, outputDirName);
68
- await convertDeepnoteFileToJupyterFiles(absolutePath, { outputDir });
69
- spinner.succeed(`Jupyter Notebooks have been saved to ${chalk.bold(outputDir)}`);
70
- return outputDir;
71
- } catch (error) {
72
- spinner.fail("Conversion failed");
73
- throw error;
134
+ }
135
+ function convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
136
+ return convertSingleFileToDeepnote(absolutePath, "Jupyter Notebook", convertIpynbFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
137
+ }
138
+ function convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
139
+ return convertSingleFileToDeepnote(absolutePath, "Quarto document", convertQuartoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
140
+ }
141
+ function convertPercentToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
142
+ return convertSingleFileToDeepnote(absolutePath, "percent format notebook", convertPercentFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
143
+ }
144
+ function convertMarimoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
145
+ return convertSingleFileToDeepnote(absolutePath, "Marimo notebook", convertMarimoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
146
+ }
147
+ async function convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd) {
148
+ const formatNames = {
149
+ jupyter: "Jupyter Notebooks",
150
+ percent: "percent format files",
151
+ quarto: "Quarto documents",
152
+ marimo: "Marimo notebooks"
153
+ };
154
+ const spinner = ora(`Converting Deepnote project to ${formatNames[outputFormat]}...`).start();
155
+ try {
156
+ const outputDirName = basename(absolutePath, extname(absolutePath));
157
+ const outputDir = customOutputPath ? resolve(cwd, customOutputPath) : resolve(cwd, outputDirName);
158
+ switch (outputFormat) {
159
+ case "jupyter":
160
+ await convertDeepnoteFileToJupyterFiles(absolutePath, { outputDir });
161
+ break;
162
+ case "percent":
163
+ await convertDeepnoteFileToPercentFiles(absolutePath, { outputDir });
164
+ break;
165
+ case "quarto":
166
+ await convertDeepnoteFileToQuartoFiles(absolutePath, { outputDir });
167
+ break;
168
+ case "marimo":
169
+ await convertDeepnoteFileToMarimoFiles(absolutePath, { outputDir });
170
+ break;
74
171
  }
172
+ spinner.succeed(`${formatNames[outputFormat]} have been saved to ${chalk.bold(outputDir)}`);
173
+ return outputDir;
174
+ } catch (error) {
175
+ spinner.fail("Conversion failed");
176
+ throw error;
75
177
  }
76
- throw new Error("Unsupported file type. Please provide a .ipynb or .deepnote file.");
178
+ }
179
+ /** Check if file content is Marimo format */
180
+ function isMarimoContent(content) {
181
+ return /^import marimo\b/m.test(content) && /@app\.cell\b/.test(content) && !/^\s*['"]{3}[\s\S]*?import marimo/m.test(content);
182
+ }
183
+ /** Check if file content is percent format */
184
+ function isPercentContent(content) {
185
+ return /^# %%/m.test(content) && !/^\s*['"]{3}[\s\S]*?# %%/m.test(content);
77
186
  }
78
187
 
79
188
  //#endregion