@deepnote/convert 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepnote/convert",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "ora": "^9.0.0",
34
34
  "uuid": "^13.0.0",
35
35
  "yaml": "^2.8.1",
36
- "@deepnote/blocks": "1.4.0"
36
+ "@deepnote/blocks": "2.0.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.0.0",
@@ -1,309 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import { basename, dirname, extname, join } from "node:path";
3
- import { createMarkdown, createPythonCode, deserializeDeepnoteFile, environmentSchema, executionSchema } from "@deepnote/blocks";
4
- import { v4 } from "uuid";
5
- import { stringify } from "yaml";
6
-
7
- //#region src/deepnote-to-jupyter.ts
8
- /**
9
- * Converts an array of Deepnote blocks into a single Jupyter Notebook.
10
- * This is the lowest-level conversion function, suitable for use in Deepnote Cloud.
11
- *
12
- * @param blocks - Array of DeepnoteBlock objects to convert
13
- * @param options - Notebook metadata options
14
- * @returns A JupyterNotebook object
15
- *
16
- * @example
17
- * ```typescript
18
- * import { convertBlocksToJupyterNotebook } from '@deepnote/convert'
19
- *
20
- * const notebook = convertBlocksToJupyterNotebook(blocks, {
21
- * notebookId: 'abc123',
22
- * notebookName: 'My Notebook',
23
- * executionMode: 'block'
24
- * })
25
- * ```
26
- */
27
- function convertBlocksToJupyterNotebook(blocks, options) {
28
- return {
29
- cells: blocks.map((block) => convertBlockToCell(block)),
30
- metadata: {
31
- deepnote_notebook_id: options.notebookId,
32
- deepnote_notebook_name: options.notebookName,
33
- deepnote_execution_mode: options.executionMode,
34
- deepnote_is_module: options.isModule,
35
- deepnote_working_directory: options.workingDirectory,
36
- deepnote_environment: options.environment,
37
- deepnote_execution: options.execution
38
- },
39
- nbformat: 4,
40
- nbformat_minor: 0
41
- };
42
- }
43
- /**
44
- * Converts a Deepnote project into Jupyter Notebook objects.
45
- * This is a pure conversion function that doesn't perform any file I/O.
46
- * Each notebook in the Deepnote project is converted to a separate Jupyter notebook.
47
- *
48
- * @param deepnoteFile - The deserialized Deepnote project file
49
- * @returns Array of objects containing filename and corresponding Jupyter notebook
50
- *
51
- * @example
52
- * ```typescript
53
- * import { deserializeDeepnoteFile } from '@deepnote/blocks'
54
- * import { convertDeepnoteToJupyterNotebooks } from '@deepnote/convert'
55
- *
56
- * const yamlContent = await fs.readFile('project.deepnote', 'utf-8')
57
- * const deepnoteFile = deserializeDeepnoteFile(yamlContent)
58
- * const notebooks = convertDeepnoteToJupyterNotebooks(deepnoteFile)
59
- *
60
- * for (const { filename, notebook } of notebooks) {
61
- * console.log(`${filename}: ${notebook.cells.length} cells`)
62
- * }
63
- * ```
64
- */
65
- function convertDeepnoteToJupyterNotebooks(deepnoteFile) {
66
- return deepnoteFile.project.notebooks.map((notebook) => {
67
- const jupyterNotebook = convertNotebookToJupyter(deepnoteFile, notebook);
68
- return {
69
- filename: `${sanitizeFileName(notebook.name)}.ipynb`,
70
- notebook: jupyterNotebook
71
- };
72
- });
73
- }
74
- /**
75
- * Converts a Deepnote project file into separate Jupyter Notebook (.ipynb) files.
76
- * Each notebook in the Deepnote project becomes a separate .ipynb file.
77
- */
78
- async function convertDeepnoteFileToJupyterFiles(deepnoteFilePath, options) {
79
- const notebooks = convertDeepnoteToJupyterNotebooks(deserializeDeepnoteFile(await fs.readFile(deepnoteFilePath, "utf-8")));
80
- await fs.mkdir(options.outputDir, { recursive: true });
81
- for (const { filename, notebook } of notebooks) {
82
- const filePath = join(options.outputDir, filename);
83
- await fs.writeFile(filePath, JSON.stringify(notebook, null, 2), "utf-8");
84
- }
85
- }
86
- function convertBlockToCell(block) {
87
- const content = block.content || "";
88
- const jupyterCellType = convertBlockTypeToJupyter(block.type);
89
- const metadata = {
90
- cell_id: block.id,
91
- deepnote_block_group: block.blockGroup,
92
- deepnote_cell_type: block.type,
93
- deepnote_sorting_key: block.sortingKey,
94
- deepnote_content_hash: block.contentHash,
95
- deepnote_execution_started_at: block.executionStartedAt,
96
- deepnote_execution_finished_at: block.executionFinishedAt,
97
- ...block.metadata || {}
98
- };
99
- metadata.deepnote_source = content;
100
- return {
101
- block_group: block.blockGroup,
102
- cell_type: jupyterCellType,
103
- execution_count: block.executionCount ?? null,
104
- metadata,
105
- outputs: block.outputs,
106
- source: getSourceForBlock(block, jupyterCellType, content)
107
- };
108
- }
109
- function getSourceForBlock(block, jupyterCellType, content) {
110
- if (jupyterCellType === "markdown") return createMarkdown(block);
111
- if (block.type === "code") return content;
112
- return createPythonCode(block);
113
- }
114
- function convertBlockTypeToJupyter(blockType) {
115
- const codeTypes = [
116
- "big-number",
117
- "button",
118
- "code",
119
- "notebook-function",
120
- "sql",
121
- "visualization"
122
- ];
123
- if (blockType.startsWith("input-")) return "code";
124
- return codeTypes.includes(blockType) ? "code" : "markdown";
125
- }
126
- function convertNotebookToJupyter(deepnoteFile, notebook) {
127
- return convertBlocksToJupyterNotebook(notebook.blocks, {
128
- notebookId: notebook.id,
129
- notebookName: notebook.name,
130
- executionMode: notebook.executionMode,
131
- isModule: notebook.isModule,
132
- workingDirectory: notebook.workingDirectory,
133
- environment: deepnoteFile.environment,
134
- execution: deepnoteFile.execution
135
- });
136
- }
137
- function sanitizeFileName(name) {
138
- return name.replace(/[<>:"/\\|?*]/g, "_").replace(/\s+/g, "-");
139
- }
140
-
141
- //#endregion
142
- //#region src/jupyter-to-deepnote.ts
143
- /**
144
- * Converts a single Jupyter Notebook into an array of Deepnote blocks.
145
- * This is the lowest-level conversion function, suitable for use in Deepnote Cloud.
146
- *
147
- * @param notebook - The Jupyter notebook object to convert
148
- * @param options - Optional conversion options including custom ID generator
149
- * @returns Array of DeepnoteBlock objects
150
- *
151
- * @example
152
- * ```typescript
153
- * import { convertJupyterNotebookToBlocks } from '@deepnote/convert'
154
- *
155
- * const notebook = JSON.parse(ipynbContent)
156
- * const blocks = convertJupyterNotebookToBlocks(notebook, {
157
- * idGenerator: () => myCustomIdGenerator()
158
- * })
159
- * ```
160
- */
161
- function convertJupyterNotebookToBlocks(notebook, options) {
162
- const idGenerator = options?.idGenerator ?? v4;
163
- return notebook.cells.map((cell, index) => convertCellToBlock(cell, index, idGenerator));
164
- }
165
- /**
166
- * Converts Jupyter Notebook objects into a Deepnote project file.
167
- * This is a pure conversion function that doesn't perform any file I/O.
168
- *
169
- * @param notebooks - Array of Jupyter notebooks with filenames
170
- * @param options - Conversion options including project name
171
- * @returns A DeepnoteFile object
172
- */
173
- function convertJupyterNotebooksToDeepnote(notebooks, options) {
174
- let environment;
175
- let execution;
176
- for (const { notebook } of notebooks) {
177
- if (!environment && notebook.metadata?.deepnote_environment) {
178
- const parsed = environmentSchema.safeParse(notebook.metadata.deepnote_environment);
179
- if (parsed.success) environment = parsed.data;
180
- }
181
- if (!execution && notebook.metadata?.deepnote_execution) {
182
- const parsed = executionSchema.safeParse(notebook.metadata.deepnote_execution);
183
- if (parsed.success) execution = parsed.data;
184
- }
185
- }
186
- const deepnoteFile = {
187
- environment,
188
- execution,
189
- metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString() },
190
- project: {
191
- id: v4(),
192
- initNotebookId: void 0,
193
- integrations: [],
194
- name: options.projectName,
195
- notebooks: [],
196
- settings: {}
197
- },
198
- version: "1.0.0"
199
- };
200
- for (const { filename, notebook } of notebooks) {
201
- const filenameWithoutExt = basename(filename, extname(filename)) || "Untitled notebook";
202
- const blocks = convertJupyterNotebookToBlocks(notebook);
203
- const notebookId = notebook.metadata?.deepnote_notebook_id;
204
- const notebookName = notebook.metadata?.deepnote_notebook_name;
205
- const executionMode = notebook.metadata?.deepnote_execution_mode;
206
- const isModule = notebook.metadata?.deepnote_is_module;
207
- const workingDirectory = notebook.metadata?.deepnote_working_directory;
208
- deepnoteFile.project.notebooks.push({
209
- blocks,
210
- executionMode: executionMode ?? "block",
211
- id: notebookId ?? v4(),
212
- isModule: isModule ?? false,
213
- name: notebookName ?? filenameWithoutExt,
214
- workingDirectory
215
- });
216
- }
217
- return deepnoteFile;
218
- }
219
- /**
220
- * Converts multiple Jupyter Notebook (.ipynb) files into a single Deepnote project file.
221
- */
222
- async function convertIpynbFilesToDeepnoteFile(inputFilePaths, options) {
223
- const notebooks = [];
224
- for (const filePath of inputFilePaths) {
225
- const notebook = await parseIpynbFile(filePath);
226
- notebooks.push({
227
- filename: basename(filePath),
228
- notebook
229
- });
230
- }
231
- const yamlContent = stringify(convertJupyterNotebooksToDeepnote(notebooks, { projectName: options.projectName }));
232
- const parentDir = dirname(options.outputPath);
233
- await fs.mkdir(parentDir, { recursive: true });
234
- await fs.writeFile(options.outputPath, yamlContent, "utf-8");
235
- }
236
- async function parseIpynbFile(filePath) {
237
- let ipynbJson;
238
- try {
239
- ipynbJson = await fs.readFile(filePath, "utf-8");
240
- } catch (error) {
241
- const message = error instanceof Error ? error.message : String(error);
242
- throw new Error(`Failed to read ${filePath}: ${message}`);
243
- }
244
- try {
245
- return JSON.parse(ipynbJson);
246
- } catch (error) {
247
- const message = error instanceof Error ? error.message : String(error);
248
- throw new Error(`Failed to parse ${filePath}: invalid JSON - ${message}`);
249
- }
250
- }
251
- function convertCellToBlock(cell, index, idGenerator) {
252
- let source = Array.isArray(cell.source) ? cell.source.join("") : cell.source;
253
- const cellId = cell.metadata?.cell_id;
254
- const deepnoteCellType = cell.metadata?.deepnote_cell_type;
255
- const sortingKey = cell.metadata?.deepnote_sorting_key;
256
- const contentHash = cell.metadata?.deepnote_content_hash;
257
- const executionStartedAt = cell.metadata?.deepnote_execution_started_at;
258
- const executionFinishedAt = cell.metadata?.deepnote_execution_finished_at;
259
- const blockGroup = cell.metadata?.deepnote_block_group ?? cell.block_group ?? idGenerator();
260
- const deepnoteSource = cell.metadata?.deepnote_source;
261
- if (deepnoteSource !== void 0) source = deepnoteSource;
262
- const blockType = deepnoteCellType ?? (cell.cell_type === "code" ? "code" : "markdown");
263
- const originalMetadata = { ...cell.metadata };
264
- delete originalMetadata.cell_id;
265
- delete originalMetadata.deepnote_cell_type;
266
- delete originalMetadata.deepnote_block_group;
267
- delete originalMetadata.deepnote_sorting_key;
268
- delete originalMetadata.deepnote_source;
269
- delete originalMetadata.deepnote_content_hash;
270
- delete originalMetadata.deepnote_execution_started_at;
271
- delete originalMetadata.deepnote_execution_finished_at;
272
- delete cell.block_group;
273
- const executionCount = cell.execution_count ?? void 0;
274
- const hasExecutionCount = executionCount !== void 0;
275
- const hasOutputs = cell.cell_type === "code" && cell.outputs !== void 0;
276
- return {
277
- blockGroup,
278
- content: source,
279
- ...contentHash ? { contentHash } : {},
280
- ...hasExecutionCount ? { executionCount } : {},
281
- ...executionFinishedAt ? { executionFinishedAt } : {},
282
- ...executionStartedAt ? { executionStartedAt } : {},
283
- id: cellId ?? idGenerator(),
284
- metadata: originalMetadata,
285
- ...hasOutputs ? { outputs: cell.outputs } : {},
286
- sortingKey: sortingKey ?? createSortingKey(index),
287
- type: blockType
288
- };
289
- }
290
- function createSortingKey(index) {
291
- const maxLength = 6;
292
- const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
293
- const base = 36;
294
- if (index < 0) throw new Error("Index must be non-negative");
295
- let result = "";
296
- let num = index + 1;
297
- let iterations = 0;
298
- while (num > 0 && iterations < maxLength) {
299
- num--;
300
- result = chars[num % base] + result;
301
- num = Math.floor(num / base);
302
- iterations++;
303
- }
304
- if (num > 0) throw new Error(`Index ${index} exceeds maximum key length of ${maxLength}`);
305
- return result;
306
- }
307
-
308
- //#endregion
309
- export { convertDeepnoteFileToJupyterFiles as a, convertBlocksToJupyterNotebook as i, convertJupyterNotebookToBlocks as n, convertDeepnoteToJupyterNotebooks as o, convertJupyterNotebooksToDeepnote as r, convertIpynbFilesToDeepnoteFile as t };