@deepnote/convert 2.1.3 → 2.3.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
@@ -192,14 +192,14 @@ await convertMarimoFilesToDeepnoteFile(["notebook.py"], {
192
192
 
193
193
  ```typescript
194
194
  import {
195
- convertDeepnoteFileToJupyter,
195
+ convertDeepnoteFileToJupyterFiles,
196
196
  convertDeepnoteFileToQuartoFiles,
197
197
  convertDeepnoteFileToPercentFiles,
198
198
  convertDeepnoteFileToMarimoFiles,
199
199
  } from "@deepnote/convert";
200
200
 
201
201
  // To Jupyter
202
- await convertDeepnoteFileToJupyter("project.deepnote", {
202
+ await convertDeepnoteFileToJupyterFiles("project.deepnote", {
203
203
  outputDir: "./jupyter-notebooks",
204
204
  });
205
205
 
package/dist/bin.cjs CHANGED
@@ -1,19 +1,23 @@
1
1
  #!/usr/bin/env node
2
- const require_src = require('./src-1XGCb_OZ.cjs');
2
+ const require_src = require('./src-CrnuwKOU.cjs');
3
3
  let cleye = require("cleye");
4
4
  cleye = require_src.__toESM(cleye);
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  node_fs_promises = require_src.__toESM(node_fs_promises);
7
7
  let node_path = require("node:path");
8
8
  node_path = require_src.__toESM(node_path);
9
+ let __deepnote_blocks = require("@deepnote/blocks");
10
+ __deepnote_blocks = require_src.__toESM(__deepnote_blocks);
9
11
  let chalk = require("chalk");
10
12
  chalk = require_src.__toESM(chalk);
11
13
  let ora = require("ora");
12
14
  ora = require_src.__toESM(ora);
15
+ let yaml = require("yaml");
16
+ yaml = require_src.__toESM(yaml);
13
17
 
14
18
  //#region src/cli.ts
15
19
  async function convert(options) {
16
- const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd(), outputFormat = "jupyter" } = options;
20
+ const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd(), outputFormat = "jupyter", singleFile = false } = options;
17
21
  const resolveProjectName = (possibleName) => {
18
22
  if (customProjectName) return customProjectName;
19
23
  if (possibleName) return possibleName;
@@ -57,25 +61,57 @@ async function convert(options) {
57
61
  }
58
62
  const marimoFiles = results.filter((r) => r.type === "marimo").map((r) => r.file);
59
63
  const percentFiles = results.filter((r) => r.type === "percent").map((r) => r.file);
60
- if (ipynbFiles.length > 0) return convertDirectory(absolutePath, ipynbFiles, "jupyter", resolveProjectName, resolveOutputPath);
61
- if (quartoFiles.length > 0) return convertDirectory(absolutePath, quartoFiles, "quarto", resolveProjectName, resolveOutputPath);
62
- if (marimoFiles.length > 0) return convertDirectory(absolutePath, marimoFiles, "marimo", resolveProjectName, resolveOutputPath);
63
- if (percentFiles.length > 0) return convertDirectory(absolutePath, percentFiles, "percent", resolveProjectName, resolveOutputPath);
64
+ const directoryOptions = {
65
+ resolveProjectName,
66
+ resolveOutputPath,
67
+ singleFile
68
+ };
69
+ if (ipynbFiles.length > 0) return convertDirectory({
70
+ dirPath: absolutePath,
71
+ files: ipynbFiles,
72
+ format: "jupyter",
73
+ ...directoryOptions
74
+ });
75
+ if (quartoFiles.length > 0) return convertDirectory({
76
+ dirPath: absolutePath,
77
+ files: quartoFiles,
78
+ format: "quarto",
79
+ ...directoryOptions
80
+ });
81
+ if (marimoFiles.length > 0) return convertDirectory({
82
+ dirPath: absolutePath,
83
+ files: marimoFiles,
84
+ format: "marimo",
85
+ ...directoryOptions
86
+ });
87
+ if (percentFiles.length > 0) return convertDirectory({
88
+ dirPath: absolutePath,
89
+ files: percentFiles,
90
+ format: "percent",
91
+ ...directoryOptions
92
+ });
64
93
  throw new Error("No supported notebook files found in the specified directory (.ipynb, .qmd, .py)");
65
94
  }
66
95
  const ext = (0, node_path.extname)(absolutePath).toLowerCase();
67
- if (ext === ".ipynb") return convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
68
- if (ext === ".qmd") return convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
96
+ const fileOptions = {
97
+ absolutePath,
98
+ resolveProjectName,
99
+ resolveOutputPath,
100
+ singleFile
101
+ };
102
+ if (ext === ".ipynb") return convertJupyterToDeepnote(fileOptions);
103
+ if (ext === ".qmd") return convertQuartoToDeepnote(fileOptions);
69
104
  if (ext === ".py") {
70
105
  const content = await node_fs_promises.default.readFile(absolutePath, "utf-8");
71
- if (require_src.isMarimoContent(content)) return convertMarimoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
72
- if (require_src.isPercentContent(content)) return convertPercentToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
106
+ if (require_src.isMarimoContent(content)) return convertMarimoToDeepnote(fileOptions);
107
+ if (require_src.isPercentContent(content)) return convertPercentToDeepnote(fileOptions);
73
108
  throw new Error("Unsupported Python file format. File must be a percent format (# %% markers) or Marimo notebook (@app.cell decorators).");
74
109
  }
75
110
  if (ext === ".deepnote") return convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd);
76
111
  throw new Error("Unsupported file type. Please provide a .ipynb, .qmd, .py (percent/marimo), or .deepnote file.");
77
112
  }
78
- async function convertDirectory(dirPath, files, format, resolveProjectName, resolveOutputPath) {
113
+ async function convertDirectory(options) {
114
+ const { dirPath, files, format, resolveProjectName, resolveOutputPath, singleFile } = options;
79
115
  const spinner = (0, ora.default)(`Converting ${{
80
116
  jupyter: "Jupyter Notebooks",
81
117
  quarto: "Quarto documents",
@@ -87,67 +123,83 @@ async function convertDirectory(dirPath, files, format, resolveProjectName, reso
87
123
  const projectName = resolveProjectName(filenameWithoutExtension);
88
124
  const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
89
125
  const inputFilePaths = files.map((file) => (0, node_path.resolve)(dirPath, file));
126
+ let deepnoteFile;
90
127
  switch (format) {
91
128
  case "jupyter":
92
- await require_src.convertIpynbFilesToDeepnoteFile(inputFilePaths, {
93
- projectName,
94
- outputPath
95
- });
129
+ deepnoteFile = await require_src.readAndConvertIpynbFiles(inputFilePaths, { projectName });
96
130
  break;
97
131
  case "quarto":
98
- await require_src.convertQuartoFilesToDeepnoteFile(inputFilePaths, {
99
- projectName,
100
- outputPath
101
- });
132
+ deepnoteFile = await require_src.readAndConvertQuartoFiles(inputFilePaths, { projectName });
102
133
  break;
103
134
  case "percent":
104
- await require_src.convertPercentFilesToDeepnoteFile(inputFilePaths, {
105
- projectName,
106
- outputPath
107
- });
135
+ deepnoteFile = await require_src.readAndConvertPercentFiles(inputFilePaths, { projectName });
108
136
  break;
109
137
  case "marimo":
110
- await require_src.convertMarimoFilesToDeepnoteFile(inputFilePaths, {
111
- projectName,
112
- outputPath
113
- });
138
+ deepnoteFile = await require_src.readAndConvertMarimoFiles(inputFilePaths, { projectName });
114
139
  break;
115
140
  }
116
- spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}`);
141
+ const { snapshotPath } = await require_src.writeDeepnoteFile({
142
+ file: deepnoteFile,
143
+ outputPath,
144
+ projectName,
145
+ singleFile
146
+ });
147
+ if (snapshotPath) spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}\nSnapshot saved to ${chalk.default.bold(snapshotPath)}`);
148
+ else spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}`);
117
149
  return outputPath;
118
150
  } catch (error) {
119
151
  spinner.fail("Conversion failed");
120
152
  throw error;
121
153
  }
122
154
  }
123
- async function convertSingleFileToDeepnote(absolutePath, formatName, converter, resolveProjectName, resolveOutputPath) {
155
+ async function convertSingleFileToDeepnote(options) {
156
+ const { absolutePath, formatName, converter, resolveProjectName, resolveOutputPath, singleFile } = options;
124
157
  const spinner = (0, ora.default)(`Converting the ${formatName} to a Deepnote project...`).start();
125
158
  try {
126
159
  const filenameWithoutExtension = (0, node_path.basename)(absolutePath, (0, node_path.extname)(absolutePath));
127
160
  const projectName = resolveProjectName(filenameWithoutExtension);
128
161
  const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
129
- await converter([absolutePath], {
162
+ const { snapshotPath } = await require_src.writeDeepnoteFile({
163
+ file: await converter([absolutePath], { projectName }),
164
+ outputPath,
130
165
  projectName,
131
- outputPath
166
+ singleFile
132
167
  });
133
- spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}`);
168
+ if (snapshotPath) spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}\nSnapshot saved to ${chalk.default.bold(snapshotPath)}`);
169
+ else spinner.succeed(`The Deepnote project has been saved to ${chalk.default.bold(outputPath)}`);
134
170
  return outputPath;
135
171
  } catch (error) {
136
172
  spinner.fail("Conversion failed");
137
173
  throw error;
138
174
  }
139
175
  }
140
- function convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
141
- return convertSingleFileToDeepnote(absolutePath, "Jupyter Notebook", require_src.convertIpynbFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
176
+ function convertJupyterToDeepnote(options) {
177
+ return convertSingleFileToDeepnote({
178
+ ...options,
179
+ formatName: "Jupyter Notebook",
180
+ converter: require_src.readAndConvertIpynbFiles
181
+ });
142
182
  }
143
- function convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
144
- return convertSingleFileToDeepnote(absolutePath, "Quarto document", require_src.convertQuartoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
183
+ function convertQuartoToDeepnote(options) {
184
+ return convertSingleFileToDeepnote({
185
+ ...options,
186
+ formatName: "Quarto document",
187
+ converter: require_src.readAndConvertQuartoFiles
188
+ });
145
189
  }
146
- function convertPercentToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
147
- return convertSingleFileToDeepnote(absolutePath, "percent format notebook", require_src.convertPercentFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
190
+ function convertPercentToDeepnote(options) {
191
+ return convertSingleFileToDeepnote({
192
+ ...options,
193
+ formatName: "percent format notebook",
194
+ converter: require_src.readAndConvertPercentFiles
195
+ });
148
196
  }
149
- function convertMarimoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
150
- return convertSingleFileToDeepnote(absolutePath, "Marimo notebook", require_src.convertMarimoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
197
+ function convertMarimoToDeepnote(options) {
198
+ return convertSingleFileToDeepnote({
199
+ ...options,
200
+ formatName: "Marimo notebook",
201
+ converter: require_src.readAndConvertMarimoFiles
202
+ });
151
203
  }
152
204
  async function convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd) {
153
205
  const formatNames = {
@@ -158,21 +210,35 @@ async function convertDeepnoteToFormat(absolutePath, outputFormat, customOutputP
158
210
  };
159
211
  const spinner = (0, ora.default)(`Converting Deepnote project to ${formatNames[outputFormat]}...`).start();
160
212
  try {
161
- const outputDirName = (0, node_path.basename)(absolutePath, (0, node_path.extname)(absolutePath));
162
- const outputDir = customOutputPath ? (0, node_path.resolve)(cwd, customOutputPath) : (0, node_path.resolve)(cwd, outputDirName);
163
- switch (outputFormat) {
164
- case "jupyter":
165
- await require_src.convertDeepnoteFileToJupyterFiles(absolutePath, { outputDir });
166
- break;
167
- case "percent":
168
- await require_src.convertDeepnoteFileToPercentFiles(absolutePath, { outputDir });
169
- break;
170
- case "quarto":
171
- await require_src.convertDeepnoteFileToQuartoFiles(absolutePath, { outputDir });
172
- break;
173
- case "marimo":
174
- await require_src.convertDeepnoteFileToMarimoFiles(absolutePath, { outputDir });
175
- break;
213
+ const filenameWithoutExtension = (0, node_path.basename)(absolutePath, (0, node_path.extname)(absolutePath));
214
+ const outputDir = customOutputPath ? (0, node_path.resolve)(cwd, customOutputPath) : (0, node_path.resolve)(cwd, filenameWithoutExtension);
215
+ let deepnoteFile = (0, __deepnote_blocks.deserializeDeepnoteFile)(await node_fs_promises.default.readFile(absolutePath, "utf-8"));
216
+ const snapshot = await require_src.loadLatestSnapshot(absolutePath, deepnoteFile.project.id);
217
+ if (snapshot) deepnoteFile = require_src.mergeSnapshotIntoSource(deepnoteFile, snapshot, { skipMismatched: true });
218
+ const tempDir = await node_fs_promises.default.mkdtemp((0, node_path.resolve)((0, node_path.dirname)(absolutePath), ".deepnote-merge-"));
219
+ const tempPath = (0, node_path.resolve)(tempDir, `${filenameWithoutExtension}.merged.deepnote`);
220
+ try {
221
+ const mergedYaml = (0, yaml.stringify)(deepnoteFile);
222
+ await node_fs_promises.default.writeFile(tempPath, mergedYaml, "utf-8");
223
+ switch (outputFormat) {
224
+ case "jupyter":
225
+ await require_src.convertDeepnoteFileToJupyterFiles(tempPath, { outputDir });
226
+ break;
227
+ case "percent":
228
+ await require_src.convertDeepnoteFileToPercentFiles(tempPath, { outputDir });
229
+ break;
230
+ case "quarto":
231
+ await require_src.convertDeepnoteFileToQuartoFiles(tempPath, { outputDir });
232
+ break;
233
+ case "marimo":
234
+ await require_src.convertDeepnoteFileToMarimoFiles(tempPath, { outputDir });
235
+ break;
236
+ }
237
+ } finally {
238
+ await node_fs_promises.default.rm(tempDir, {
239
+ recursive: true,
240
+ force: true
241
+ }).catch(() => {});
176
242
  }
177
243
  spinner.succeed(`${formatNames[outputFormat]} have been saved to ${chalk.default.bold(outputDir)}`);
178
244
  return outputDir;
@@ -201,6 +267,11 @@ async function main() {
201
267
  cwd: {
202
268
  description: "The working directory to resolve paths relative to.",
203
269
  type: String
270
+ },
271
+ singleFile: {
272
+ description: "Output a single file with outputs included (disables snapshot mode).",
273
+ type: Boolean,
274
+ default: false
204
275
  }
205
276
  }
206
277
  });
@@ -208,7 +279,8 @@ async function main() {
208
279
  inputPath: argv._.path,
209
280
  projectName: argv.flags.projectName,
210
281
  outputPath: argv.flags.outputPath,
211
- cwd: argv.flags.cwd ?? process.cwd()
282
+ cwd: argv.flags.cwd ?? process.cwd(),
283
+ singleFile: argv.flags.singleFile
212
284
  });
213
285
  }
214
286
  if (process.env.NODE_ENV !== "test" && process.env.VITEST !== "true") main().catch((error) => {
package/dist/bin.js CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { M as convertDeepnoteFileToJupyterFiles, O as convertDeepnoteFileToMarimoFiles, _ as isMarimoContent, a as convertPercentFilesToDeepnoteFile, b as convertDeepnoteFileToQuartoFiles, d as convertMarimoFilesToDeepnoteFile, p as convertIpynbFilesToDeepnoteFile, r as convertQuartoFilesToDeepnoteFile, v as isPercentContent, w as convertDeepnoteFileToPercentFiles } from "./src-zxlAKpnq.js";
2
+ import { G as convertDeepnoteFileToPercentFiles, I as readAndConvertIpynbFiles, R as isMarimoContent, S as mergeSnapshotIntoSource, T as loadLatestSnapshot, V as convertDeepnoteFileToQuartoFiles, Y as convertDeepnoteFileToMarimoFiles, d as readAndConvertPercentFiles, et as convertDeepnoteFileToJupyterFiles, g as readAndConvertMarimoFiles, o as readAndConvertQuartoFiles, t as writeDeepnoteFile, z as isPercentContent } from "./src-C0XPNDnj.js";
3
3
  import { cli } from "cleye";
4
4
  import fs from "node:fs/promises";
5
- import { basename, extname, resolve } from "node:path";
5
+ import { basename, dirname, extname, resolve } from "node:path";
6
+ import { deserializeDeepnoteFile } from "@deepnote/blocks";
6
7
  import chalk from "chalk";
7
8
  import ora from "ora";
9
+ import { stringify } from "yaml";
8
10
 
9
11
  //#region src/cli.ts
10
12
  async function convert(options) {
11
- const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd(), outputFormat = "jupyter" } = options;
13
+ const { inputPath, projectName: customProjectName, outputPath: customOutputPath, cwd = process.cwd(), outputFormat = "jupyter", singleFile = false } = options;
12
14
  const resolveProjectName = (possibleName) => {
13
15
  if (customProjectName) return customProjectName;
14
16
  if (possibleName) return possibleName;
@@ -52,25 +54,57 @@ async function convert(options) {
52
54
  }
53
55
  const marimoFiles = results.filter((r) => r.type === "marimo").map((r) => r.file);
54
56
  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);
57
+ const directoryOptions = {
58
+ resolveProjectName,
59
+ resolveOutputPath,
60
+ singleFile
61
+ };
62
+ if (ipynbFiles.length > 0) return convertDirectory({
63
+ dirPath: absolutePath,
64
+ files: ipynbFiles,
65
+ format: "jupyter",
66
+ ...directoryOptions
67
+ });
68
+ if (quartoFiles.length > 0) return convertDirectory({
69
+ dirPath: absolutePath,
70
+ files: quartoFiles,
71
+ format: "quarto",
72
+ ...directoryOptions
73
+ });
74
+ if (marimoFiles.length > 0) return convertDirectory({
75
+ dirPath: absolutePath,
76
+ files: marimoFiles,
77
+ format: "marimo",
78
+ ...directoryOptions
79
+ });
80
+ if (percentFiles.length > 0) return convertDirectory({
81
+ dirPath: absolutePath,
82
+ files: percentFiles,
83
+ format: "percent",
84
+ ...directoryOptions
85
+ });
59
86
  throw new Error("No supported notebook files found in the specified directory (.ipynb, .qmd, .py)");
60
87
  }
61
88
  const ext = extname(absolutePath).toLowerCase();
62
- if (ext === ".ipynb") return convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
63
- if (ext === ".qmd") return convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath);
89
+ const fileOptions = {
90
+ absolutePath,
91
+ resolveProjectName,
92
+ resolveOutputPath,
93
+ singleFile
94
+ };
95
+ if (ext === ".ipynb") return convertJupyterToDeepnote(fileOptions);
96
+ if (ext === ".qmd") return convertQuartoToDeepnote(fileOptions);
64
97
  if (ext === ".py") {
65
98
  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);
99
+ if (isMarimoContent(content)) return convertMarimoToDeepnote(fileOptions);
100
+ if (isPercentContent(content)) return convertPercentToDeepnote(fileOptions);
68
101
  throw new Error("Unsupported Python file format. File must be a percent format (# %% markers) or Marimo notebook (@app.cell decorators).");
69
102
  }
70
103
  if (ext === ".deepnote") return convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd);
71
104
  throw new Error("Unsupported file type. Please provide a .ipynb, .qmd, .py (percent/marimo), or .deepnote file.");
72
105
  }
73
- async function convertDirectory(dirPath, files, format, resolveProjectName, resolveOutputPath) {
106
+ async function convertDirectory(options) {
107
+ const { dirPath, files, format, resolveProjectName, resolveOutputPath, singleFile } = options;
74
108
  const spinner = ora(`Converting ${{
75
109
  jupyter: "Jupyter Notebooks",
76
110
  quarto: "Quarto documents",
@@ -82,67 +116,83 @@ async function convertDirectory(dirPath, files, format, resolveProjectName, reso
82
116
  const projectName = resolveProjectName(filenameWithoutExtension);
83
117
  const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
84
118
  const inputFilePaths = files.map((file) => resolve(dirPath, file));
119
+ let deepnoteFile;
85
120
  switch (format) {
86
121
  case "jupyter":
87
- await convertIpynbFilesToDeepnoteFile(inputFilePaths, {
88
- projectName,
89
- outputPath
90
- });
122
+ deepnoteFile = await readAndConvertIpynbFiles(inputFilePaths, { projectName });
91
123
  break;
92
124
  case "quarto":
93
- await convertQuartoFilesToDeepnoteFile(inputFilePaths, {
94
- projectName,
95
- outputPath
96
- });
125
+ deepnoteFile = await readAndConvertQuartoFiles(inputFilePaths, { projectName });
97
126
  break;
98
127
  case "percent":
99
- await convertPercentFilesToDeepnoteFile(inputFilePaths, {
100
- projectName,
101
- outputPath
102
- });
128
+ deepnoteFile = await readAndConvertPercentFiles(inputFilePaths, { projectName });
103
129
  break;
104
130
  case "marimo":
105
- await convertMarimoFilesToDeepnoteFile(inputFilePaths, {
106
- projectName,
107
- outputPath
108
- });
131
+ deepnoteFile = await readAndConvertMarimoFiles(inputFilePaths, { projectName });
109
132
  break;
110
133
  }
111
- spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
134
+ const { snapshotPath } = await writeDeepnoteFile({
135
+ file: deepnoteFile,
136
+ outputPath,
137
+ projectName,
138
+ singleFile
139
+ });
140
+ if (snapshotPath) spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}\nSnapshot saved to ${chalk.bold(snapshotPath)}`);
141
+ else spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
112
142
  return outputPath;
113
143
  } catch (error) {
114
144
  spinner.fail("Conversion failed");
115
145
  throw error;
116
146
  }
117
147
  }
118
- async function convertSingleFileToDeepnote(absolutePath, formatName, converter, resolveProjectName, resolveOutputPath) {
148
+ async function convertSingleFileToDeepnote(options) {
149
+ const { absolutePath, formatName, converter, resolveProjectName, resolveOutputPath, singleFile } = options;
119
150
  const spinner = ora(`Converting the ${formatName} to a Deepnote project...`).start();
120
151
  try {
121
152
  const filenameWithoutExtension = basename(absolutePath, extname(absolutePath));
122
153
  const projectName = resolveProjectName(filenameWithoutExtension);
123
154
  const outputPath = await resolveOutputPath(`${filenameWithoutExtension}.deepnote`);
124
- await converter([absolutePath], {
155
+ const { snapshotPath } = await writeDeepnoteFile({
156
+ file: await converter([absolutePath], { projectName }),
157
+ outputPath,
125
158
  projectName,
126
- outputPath
159
+ singleFile
127
160
  });
128
- spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
161
+ if (snapshotPath) spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}\nSnapshot saved to ${chalk.bold(snapshotPath)}`);
162
+ else spinner.succeed(`The Deepnote project has been saved to ${chalk.bold(outputPath)}`);
129
163
  return outputPath;
130
164
  } catch (error) {
131
165
  spinner.fail("Conversion failed");
132
166
  throw error;
133
167
  }
134
168
  }
135
- function convertJupyterToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
136
- return convertSingleFileToDeepnote(absolutePath, "Jupyter Notebook", convertIpynbFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
169
+ function convertJupyterToDeepnote(options) {
170
+ return convertSingleFileToDeepnote({
171
+ ...options,
172
+ formatName: "Jupyter Notebook",
173
+ converter: readAndConvertIpynbFiles
174
+ });
137
175
  }
138
- function convertQuartoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
139
- return convertSingleFileToDeepnote(absolutePath, "Quarto document", convertQuartoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
176
+ function convertQuartoToDeepnote(options) {
177
+ return convertSingleFileToDeepnote({
178
+ ...options,
179
+ formatName: "Quarto document",
180
+ converter: readAndConvertQuartoFiles
181
+ });
140
182
  }
141
- function convertPercentToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
142
- return convertSingleFileToDeepnote(absolutePath, "percent format notebook", convertPercentFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
183
+ function convertPercentToDeepnote(options) {
184
+ return convertSingleFileToDeepnote({
185
+ ...options,
186
+ formatName: "percent format notebook",
187
+ converter: readAndConvertPercentFiles
188
+ });
143
189
  }
144
- function convertMarimoToDeepnote(absolutePath, resolveProjectName, resolveOutputPath) {
145
- return convertSingleFileToDeepnote(absolutePath, "Marimo notebook", convertMarimoFilesToDeepnoteFile, resolveProjectName, resolveOutputPath);
190
+ function convertMarimoToDeepnote(options) {
191
+ return convertSingleFileToDeepnote({
192
+ ...options,
193
+ formatName: "Marimo notebook",
194
+ converter: readAndConvertMarimoFiles
195
+ });
146
196
  }
147
197
  async function convertDeepnoteToFormat(absolutePath, outputFormat, customOutputPath, cwd) {
148
198
  const formatNames = {
@@ -153,21 +203,35 @@ async function convertDeepnoteToFormat(absolutePath, outputFormat, customOutputP
153
203
  };
154
204
  const spinner = ora(`Converting Deepnote project to ${formatNames[outputFormat]}...`).start();
155
205
  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;
206
+ const filenameWithoutExtension = basename(absolutePath, extname(absolutePath));
207
+ const outputDir = customOutputPath ? resolve(cwd, customOutputPath) : resolve(cwd, filenameWithoutExtension);
208
+ let deepnoteFile = deserializeDeepnoteFile(await fs.readFile(absolutePath, "utf-8"));
209
+ const snapshot = await loadLatestSnapshot(absolutePath, deepnoteFile.project.id);
210
+ if (snapshot) deepnoteFile = mergeSnapshotIntoSource(deepnoteFile, snapshot, { skipMismatched: true });
211
+ const tempDir = await fs.mkdtemp(resolve(dirname(absolutePath), ".deepnote-merge-"));
212
+ const tempPath = resolve(tempDir, `${filenameWithoutExtension}.merged.deepnote`);
213
+ try {
214
+ const mergedYaml = stringify(deepnoteFile);
215
+ await fs.writeFile(tempPath, mergedYaml, "utf-8");
216
+ switch (outputFormat) {
217
+ case "jupyter":
218
+ await convertDeepnoteFileToJupyterFiles(tempPath, { outputDir });
219
+ break;
220
+ case "percent":
221
+ await convertDeepnoteFileToPercentFiles(tempPath, { outputDir });
222
+ break;
223
+ case "quarto":
224
+ await convertDeepnoteFileToQuartoFiles(tempPath, { outputDir });
225
+ break;
226
+ case "marimo":
227
+ await convertDeepnoteFileToMarimoFiles(tempPath, { outputDir });
228
+ break;
229
+ }
230
+ } finally {
231
+ await fs.rm(tempDir, {
232
+ recursive: true,
233
+ force: true
234
+ }).catch(() => {});
171
235
  }
172
236
  spinner.succeed(`${formatNames[outputFormat]} have been saved to ${chalk.bold(outputDir)}`);
173
237
  return outputDir;
@@ -196,6 +260,11 @@ async function main() {
196
260
  cwd: {
197
261
  description: "The working directory to resolve paths relative to.",
198
262
  type: String
263
+ },
264
+ singleFile: {
265
+ description: "Output a single file with outputs included (disables snapshot mode).",
266
+ type: Boolean,
267
+ default: false
199
268
  }
200
269
  }
201
270
  });
@@ -203,7 +272,8 @@ async function main() {
203
272
  inputPath: argv._.path,
204
273
  projectName: argv.flags.projectName,
205
274
  outputPath: argv.flags.outputPath,
206
- cwd: argv.flags.cwd ?? process.cwd()
275
+ cwd: argv.flags.cwd ?? process.cwd(),
276
+ singleFile: argv.flags.singleFile
207
277
  });
208
278
  }
209
279
  if (process.env.NODE_ENV !== "test" && process.env.VITEST !== "true") main().catch((error) => {
package/dist/index.cjs CHANGED
@@ -1,10 +1,14 @@
1
- const require_src = require('./src-1XGCb_OZ.cjs');
1
+ const require_src = require('./src-CrnuwKOU.cjs');
2
2
 
3
+ exports.addContentHashes = require_src.addContentHashes;
4
+ exports.computeContentHash = require_src.computeContentHash;
5
+ exports.computeSnapshotHash = require_src.computeSnapshotHash;
6
+ exports.convertBlockToJupyterCell = require_src.convertBlockToJupyterCell;
3
7
  exports.convertBlocksToJupyterNotebook = require_src.convertBlocksToJupyterNotebook;
4
8
  exports.convertBlocksToMarimoApp = require_src.convertBlocksToMarimoApp;
5
9
  exports.convertBlocksToPercentNotebook = require_src.convertBlocksToPercentNotebook;
6
10
  exports.convertBlocksToQuartoDocument = require_src.convertBlocksToQuartoDocument;
7
- exports.convertDeepnoteFileToJupyter = require_src.convertDeepnoteFileToJupyterFiles;
11
+ exports.convertDeepnoteFileToJupyterFiles = require_src.convertDeepnoteFileToJupyterFiles;
8
12
  exports.convertDeepnoteFileToMarimoFiles = require_src.convertDeepnoteFileToMarimoFiles;
9
13
  exports.convertDeepnoteFileToPercentFiles = require_src.convertDeepnoteFileToPercentFiles;
10
14
  exports.convertDeepnoteFileToQuartoFiles = require_src.convertDeepnoteFileToQuartoFiles;
@@ -24,10 +28,28 @@ exports.convertPercentNotebooksToDeepnote = require_src.convertPercentNotebooksT
24
28
  exports.convertQuartoDocumentToBlocks = require_src.convertQuartoDocumentToBlocks;
25
29
  exports.convertQuartoDocumentsToDeepnote = require_src.convertQuartoDocumentsToDeepnote;
26
30
  exports.convertQuartoFilesToDeepnoteFile = require_src.convertQuartoFilesToDeepnoteFile;
31
+ exports.countBlocksWithOutputs = require_src.countBlocksWithOutputs;
27
32
  exports.detectFormat = require_src.detectFormat;
33
+ exports.findSnapshotsForProject = require_src.findSnapshotsForProject;
34
+ exports.generateSnapshotFilename = require_src.generateSnapshotFilename;
35
+ exports.getSnapshotDir = require_src.getSnapshotDir;
36
+ exports.hasOutputs = require_src.hasOutputs;
37
+ exports.loadLatestSnapshot = require_src.loadLatestSnapshot;
38
+ exports.loadSnapshotFile = require_src.loadSnapshotFile;
39
+ exports.mergeSnapshotIntoSource = require_src.mergeSnapshotIntoSource;
28
40
  exports.parseMarimoFormat = require_src.parseMarimoFormat;
29
41
  exports.parsePercentFormat = require_src.parsePercentFormat;
30
42
  exports.parseQuartoFormat = require_src.parseQuartoFormat;
43
+ exports.parseSnapshotFilename = require_src.parseSnapshotFilename;
44
+ exports.parseSourceFilePath = require_src.parseSourceFilePath;
45
+ exports.readAndConvertIpynbFiles = require_src.readAndConvertIpynbFiles;
46
+ exports.readAndConvertMarimoFiles = require_src.readAndConvertMarimoFiles;
47
+ exports.readAndConvertPercentFiles = require_src.readAndConvertPercentFiles;
48
+ exports.readAndConvertQuartoFiles = require_src.readAndConvertQuartoFiles;
31
49
  exports.serializeMarimoFormat = require_src.serializeMarimoFormat;
32
50
  exports.serializePercentFormat = require_src.serializePercentFormat;
33
- exports.serializeQuartoFormat = require_src.serializeQuartoFormat;
51
+ exports.serializeQuartoFormat = require_src.serializeQuartoFormat;
52
+ exports.slugifyProjectName = require_src.slugifyProjectName;
53
+ exports.snapshotExists = require_src.snapshotExists;
54
+ exports.splitDeepnoteFile = require_src.splitDeepnoteFile;
55
+ exports.writeDeepnoteFile = require_src.writeDeepnoteFile;