nconv-cli 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -1
- package/GITHUB_ABOUT.md +53 -0
- package/README.md +285 -131
- package/dist/index.js +1036 -40
- package/dist/index.js.map +1 -1
- package/nconv-cli-1.0.0.tgz +0 -0
- package/nconv-cli-1.0.2.tgz +0 -0
- package/package.json +22 -9
- package/patches/notion-exporter+0.8.1.patch +32 -0
- package/src/commands/html.ts +135 -0
- package/src/commands/init.ts +55 -0
- package/src/commands/md.ts +0 -4
- package/src/commands/pdf.ts +150 -0
- package/src/config.ts +105 -13
- package/src/core/exporter.ts +159 -2
- package/src/index.ts +52 -3
- package/src/repl/commands.ts +535 -0
- package/src/repl/index.ts +87 -0
- package/src/repl/prompts.ts +123 -0
- package/src/utils/logger.ts +5 -5
- package/test/test-export-types.ts +54 -0
- package/test/test-markdown.ts +45 -0
- package/test/test-pdf.ts +44 -0
- package/test-output/drf-serializer/drf-serializer.pdf +0 -0
- package/test-output/drf-serializer/images/Untitled-1.png +0 -0
- package/test-output/drf-serializer/images/Untitled.png +0 -0
- package/README.en.md +0 -200
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { input, confirm } from '@inquirer/prompts';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import * as logger from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
export interface TokenConfig {
|
|
8
|
+
TOKEN_V2: string;
|
|
9
|
+
FILE_TOKEN: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getConfigPath(): string {
|
|
13
|
+
return path.join(os.homedir(), '.nconv', '.env');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getConfigDir(): string {
|
|
17
|
+
return path.join(os.homedir(), '.nconv');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load existing config from file
|
|
22
|
+
*/
|
|
23
|
+
export function loadConfig(): TokenConfig | null {
|
|
24
|
+
const configPath = getConfigPath();
|
|
25
|
+
if (!fs.existsSync(configPath)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
30
|
+
const config: TokenConfig = { TOKEN_V2: '', FILE_TOKEN: '' };
|
|
31
|
+
|
|
32
|
+
content.split('\n').forEach((line) => {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (trimmed.startsWith('#') || !trimmed) return;
|
|
35
|
+
|
|
36
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
37
|
+
const value = valueParts.join('=').trim();
|
|
38
|
+
|
|
39
|
+
if (key === 'TOKEN_V2') config.TOKEN_V2 = value;
|
|
40
|
+
if (key === 'FILE_TOKEN') config.FILE_TOKEN = value;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Save config to file
|
|
48
|
+
*/
|
|
49
|
+
export function saveConfig(config: TokenConfig): void {
|
|
50
|
+
const configDir = getConfigDir();
|
|
51
|
+
const configPath = getConfigPath();
|
|
52
|
+
|
|
53
|
+
const envContent = `# Notion Access Tokens
|
|
54
|
+
# These tokens are required to fetch content from Notion.
|
|
55
|
+
# You can find them in your browser's cookies when logged into Notion.
|
|
56
|
+
|
|
57
|
+
TOKEN_V2=${config.TOKEN_V2}
|
|
58
|
+
FILE_TOKEN=${config.FILE_TOKEN}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
if (!fs.existsSync(configDir)) {
|
|
63
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
fs.writeFileSync(configPath, envContent);
|
|
66
|
+
logger.info('✅ Configuration saved successfully.');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error('Failed to save configuration.');
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
logger.error(error.message);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Prompt user to input tokens (for /init)
|
|
78
|
+
*/
|
|
79
|
+
export async function promptInitConfig(): Promise<TokenConfig> {
|
|
80
|
+
logger.info('Please enter your Notion access tokens.');
|
|
81
|
+
logger.info('You can find these in your browser cookies when logged into Notion.\n');
|
|
82
|
+
|
|
83
|
+
const TOKEN_V2 = await input({
|
|
84
|
+
message: 'TOKEN_V2:',
|
|
85
|
+
required: true,
|
|
86
|
+
validate: (value) => {
|
|
87
|
+
if (!value.trim()) return 'TOKEN_V2 is required';
|
|
88
|
+
return true;
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const FILE_TOKEN = await input({
|
|
93
|
+
message: 'FILE_TOKEN:',
|
|
94
|
+
required: true,
|
|
95
|
+
validate: (value) => {
|
|
96
|
+
if (!value.trim()) return 'FILE_TOKEN is required';
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return { TOKEN_V2, FILE_TOKEN };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Prompt user to edit existing config (for /config)
|
|
106
|
+
*/
|
|
107
|
+
export async function promptEditConfig(existing: TokenConfig): Promise<TokenConfig> {
|
|
108
|
+
logger.info('Current configuration:\n');
|
|
109
|
+
|
|
110
|
+
const TOKEN_V2 = await input({
|
|
111
|
+
message: 'TOKEN_V2:',
|
|
112
|
+
default: existing.TOKEN_V2,
|
|
113
|
+
required: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const FILE_TOKEN = await input({
|
|
117
|
+
message: 'FILE_TOKEN:',
|
|
118
|
+
default: existing.FILE_TOKEN,
|
|
119
|
+
required: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return { TOKEN_V2, FILE_TOKEN };
|
|
123
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -2,35 +2,35 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora, { Ora } from 'ora';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* print success log
|
|
6
6
|
*/
|
|
7
7
|
export function success(message: string): void {
|
|
8
8
|
console.log(chalk.green('✓'), message);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* print error log
|
|
13
13
|
*/
|
|
14
14
|
export function error(message: string): void {
|
|
15
15
|
console.error(chalk.red('✗'), message);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* print info log
|
|
20
20
|
*/
|
|
21
21
|
export function info(message: string): void {
|
|
22
22
|
console.log(chalk.blue('ℹ'), message);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* print warn log
|
|
27
27
|
*/
|
|
28
28
|
export function warn(message: string): void {
|
|
29
29
|
console.log(chalk.yellow('⚠'), message);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* print spinner
|
|
34
34
|
*/
|
|
35
35
|
export function spinner(text: string): Ora {
|
|
36
36
|
return ora(text).start();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NotionExporter } from 'notion-exporter';
|
|
2
|
+
import { getNotionConfig, validateConfig } from '../src/config.js';
|
|
3
|
+
|
|
4
|
+
async function testExportTypes() {
|
|
5
|
+
try {
|
|
6
|
+
const validation = validateConfig();
|
|
7
|
+
if (!validation.valid) {
|
|
8
|
+
console.error('❌ Configuration error:', validation.message);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const config = getNotionConfig();
|
|
13
|
+
const notionUrl = process.argv[2];
|
|
14
|
+
|
|
15
|
+
if (!notionUrl) {
|
|
16
|
+
console.error('Usage: tsx test-export-types.ts <notion-url>');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const exportTypes = ['markdown', 'html', 'pdf', 'PDF'];
|
|
21
|
+
|
|
22
|
+
console.log('🔍 Testing different export types...\n');
|
|
23
|
+
|
|
24
|
+
for (const exportType of exportTypes) {
|
|
25
|
+
try {
|
|
26
|
+
console.log(`Testing exportType: "${exportType}"`);
|
|
27
|
+
const exporter = new NotionExporter(config.tokenV2, config.fileToken, {
|
|
28
|
+
exportType: exportType as any,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const taskId = await exporter.getTaskId(notionUrl);
|
|
32
|
+
console.log(` ✅ Task created: ${taskId}`);
|
|
33
|
+
|
|
34
|
+
// Wait a bit for the task to process
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
36
|
+
|
|
37
|
+
// Try to get the result
|
|
38
|
+
const zip = await exporter.getZip(notionUrl);
|
|
39
|
+
const entries = zip.getEntries().map(e => e.entryName);
|
|
40
|
+
console.log(` 📦 Files in ZIP:`, entries);
|
|
41
|
+
console.log('');
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
console.log(` ❌ Failed: ${error.message}`);
|
|
44
|
+
console.log('');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('❌ Error:', error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
testExportTypes();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NotionMarkdownExporter } from '../src/core/exporter.js';
|
|
2
|
+
import { getNotionConfig, validateConfig } from '../src/config.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
async function testMarkdownExport() {
|
|
7
|
+
try {
|
|
8
|
+
// Validate config first
|
|
9
|
+
const validation = validateConfig();
|
|
10
|
+
if (!validation.valid) {
|
|
11
|
+
console.error('❌ Configuration error:', validation.message);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const config = getNotionConfig();
|
|
16
|
+
|
|
17
|
+
// Markdown exporter 생성
|
|
18
|
+
const mdExporter = new NotionMarkdownExporter(config, 'markdown');
|
|
19
|
+
|
|
20
|
+
// 테스트할 Notion URL
|
|
21
|
+
const notionUrl = process.argv[2];
|
|
22
|
+
|
|
23
|
+
if (!notionUrl) {
|
|
24
|
+
console.error('Usage: tsx test-markdown.ts <notion-url>');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('🚀 Starting Markdown export...');
|
|
29
|
+
console.log('📄 Notion URL:', notionUrl);
|
|
30
|
+
|
|
31
|
+
const tempDir = path.join(tmpdir(), 'nconv-md-test');
|
|
32
|
+
const result = await mdExporter.exportWithImages(notionUrl, tempDir);
|
|
33
|
+
|
|
34
|
+
console.log('✅ Markdown export successful!');
|
|
35
|
+
console.log('📝 Markdown length:', result.markdown.length, 'characters');
|
|
36
|
+
console.log('🖼️ Images found:', result.imageFiles.length);
|
|
37
|
+
console.log('📁 Temp directory:', tempDir);
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('❌ Error:', error);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
testMarkdownExport();
|
package/test/test-pdf.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NotionMarkdownExporter } from '../src/core/exporter.js';
|
|
2
|
+
import { getNotionConfig, validateConfig } from '../src/config.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
|
|
6
|
+
async function testPDFExport() {
|
|
7
|
+
try {
|
|
8
|
+
// Validate config first
|
|
9
|
+
const validation = validateConfig();
|
|
10
|
+
if (!validation.valid) {
|
|
11
|
+
console.error('❌ Configuration error:', validation.message);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const config = getNotionConfig();
|
|
16
|
+
|
|
17
|
+
// PDF exporter 생성
|
|
18
|
+
const pdfExporter = new NotionMarkdownExporter(config, 'pdf');
|
|
19
|
+
|
|
20
|
+
// 테스트할 Notion URL (여기에 실제 URL을 넣으세요)
|
|
21
|
+
const notionUrl = process.argv[2];
|
|
22
|
+
|
|
23
|
+
if (!notionUrl) {
|
|
24
|
+
console.error('Usage: tsx test-pdf.ts <notion-url>');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('🚀 Starting PDF export...');
|
|
29
|
+
console.log('📄 Notion URL:', notionUrl);
|
|
30
|
+
|
|
31
|
+
const tempDir = path.join(tmpdir(), 'nconv-pdf-test');
|
|
32
|
+
const result = await pdfExporter.exportPDF(notionUrl, tempDir);
|
|
33
|
+
|
|
34
|
+
console.log('✅ PDF export successful!');
|
|
35
|
+
console.log('📁 PDF saved to:', result.pdfPath);
|
|
36
|
+
console.log('📝 Filename:', result.filename);
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('❌ Error:', error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
testPDFExport();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/README.en.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
<p align="right">
|
|
2
|
-
<a href="./README.md">한국어</a>
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
# nconv-cli (Notion Convertor CLI)
|
|
6
|
-
|
|
7
|
-
> A CLI tool that converts Notion pages into blog-ready Markdown
|
|
8
|
-
> (with automatic image extraction and path normalization)
|
|
9
|
-
|
|
10
|
-
nconv-cli converts public Notion pages into Markdown
|
|
11
|
-
and extracts images into local files, organizing everything
|
|
12
|
-
in a blog-friendly directory structure.
|
|
13
|
-
|
|
14
|
-
## Features
|
|
15
|
-
|
|
16
|
-
- 🚀 Convert public Notion pages into blog-ready Markdown
|
|
17
|
-
- 🖼️ Automatically download images and rewrite them as relative paths
|
|
18
|
-
- 📁 Output organized by post-level directory structure
|
|
19
|
-
- 🎨 Simple and intuitive CLI interface
|
|
20
|
-
|
|
21
|
-
## Problems with Existing Notion → Markdown Workflows
|
|
22
|
-
|
|
23
|
-
When moving content written in Notion to a blog,
|
|
24
|
-
simple copy & paste or the built-in Markdown export often causes issues.
|
|
25
|
-
|
|
26
|
-
- **Copy & Paste**
|
|
27
|
-
- Text is converted into Markdown-like syntax
|
|
28
|
-
- Images remain linked to Notion CDN URLs
|
|
29
|
-
- Image access frequently breaks with `Access Denied` errors
|
|
30
|
-
|
|
31
|
-
- **Notion Markdown Export**
|
|
32
|
-
- Requires manual download and extraction of exported files
|
|
33
|
-
- Difficult to organize content by individual posts
|
|
34
|
-
|
|
35
|
-
As a result, publishing to a blog usually involves
|
|
36
|
-
manually downloading images, renaming files, fixing paths,
|
|
37
|
-
and reorganizing directory structures.
|
|
38
|
-
|
|
39
|
-
| Method | Image Handling | Blog Usability |
|
|
40
|
-
|------|---------------|----------------|
|
|
41
|
-
| Copy & Paste | ❌ Dependent on Notion CDN | ❌ Broken images |
|
|
42
|
-
| Notion Export | ⚠️ Manual organization required | ⚠️ Inconvenient |
|
|
43
|
-
| **nconv-cli** | ✅ Local image extraction | ✅ Ready to publish |
|
|
44
|
-
|
|
45
|
-
## Recommended For
|
|
46
|
-
|
|
47
|
-
- Developers who write in Notion and publish Markdown posts to
|
|
48
|
-
Velog, Tistory, GitHub Pages, Hugo, or similar platforms
|
|
49
|
-
- Anyone who has experienced broken images due to Notion CDN issues
|
|
50
|
-
- Users looking to migrate Notion content into tools like Obsidian
|
|
51
|
-
|
|
52
|
-
## Environment
|
|
53
|
-
|
|
54
|
-
- **Node.js**: v20 or higher (npm v10 or higher)
|
|
55
|
-
- **TypeScript**: v5 or higher
|
|
56
|
-
|
|
57
|
-
## Installation
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
# Install dependencies
|
|
61
|
-
npm install
|
|
62
|
-
|
|
63
|
-
# Build
|
|
64
|
-
npm run build
|
|
65
|
-
|
|
66
|
-
# Install CLI locally
|
|
67
|
-
npm link
|
|
68
|
-
````
|
|
69
|
-
|
|
70
|
-
## Configuration
|
|
71
|
-
|
|
72
|
-
Set your Notion authentication tokens in the `.env` file.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# Create .env file
|
|
76
|
-
cp .env.example .env
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Edit `.env` and set the following values:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
TOKEN_V2=your_token_v2_here
|
|
83
|
-
FILE_TOKEN=your_file_token_here
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### How to Find Notion Tokens
|
|
87
|
-
|
|
88
|
-
1. Log in to [notion.so](https://notion.so)
|
|
89
|
-
2. Open browser developer tools (F12)
|
|
90
|
-
3. Application > Cookies > notion.so
|
|
91
|
-
4. Copy `token_v2` → paste into `.env` as `TOKEN_V2`
|
|
92
|
-
5. Copy `file_token` → paste into `.env` as `FILE_TOKEN`
|
|
93
|
-
|
|
94
|
-
## Usage
|
|
95
|
-
|
|
96
|
-
### Basic Usage
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
nconv md <notion-url>
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Examples
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
# Default output (saved to ./output)
|
|
106
|
-
nconv md "https://notion.so/My-Page-abc123"
|
|
107
|
-
|
|
108
|
-
# Specify output directory
|
|
109
|
-
nconv md "https://notion.so/My-Page-abc123" -o ./blog-posts
|
|
110
|
-
|
|
111
|
-
# Custom filename
|
|
112
|
-
nconv md "https://notion.so/My-Page-abc123" -f "my-article"
|
|
113
|
-
|
|
114
|
-
# Verbose logging
|
|
115
|
-
nconv md "https://notion.so/My-Page-abc123" -v
|
|
116
|
-
|
|
117
|
-
# All options
|
|
118
|
-
nconv md "https://notion.so/My-Page-abc123" -o ./blog -i assets -f "article-1" -v
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Options
|
|
122
|
-
|
|
123
|
-
| Option | Short | Description | Default |
|
|
124
|
-
| ------------------- | ----- | ------------------------------------------- | ------------------ |
|
|
125
|
-
| `--output <dir>` | `-o` | Output directory | `./output` |
|
|
126
|
-
| `--image-dir <dir>` | `-i` | Image directory (relative to output) | `images` |
|
|
127
|
-
| `--filename <name>` | `-f` | Output filename (with or without extension) | Extracted from URL |
|
|
128
|
-
| `--verbose` | `-v` | Enable verbose logging | `false` |
|
|
129
|
-
|
|
130
|
-
## Output Structure
|
|
131
|
-
|
|
132
|
-
```text
|
|
133
|
-
output/
|
|
134
|
-
├── my-article-folder/
|
|
135
|
-
│ ├── my-article.md
|
|
136
|
-
│ └── images/
|
|
137
|
-
│ ├── abc12345.png
|
|
138
|
-
│ ├── def67890.jpg
|
|
139
|
-
│ └── ...
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Image paths inside the Markdown file are converted to relative paths:
|
|
143
|
-
|
|
144
|
-
```md
|
|
145
|
-

|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Libraries
|
|
149
|
-
|
|
150
|
-
### Main Libraries
|
|
151
|
-
|
|
152
|
-
* **notion-exporter**: Export Notion pages to Markdown
|
|
153
|
-
* **commander**: CLI command definition and parsing
|
|
154
|
-
* **axios**: HTTP client (image downloads)
|
|
155
|
-
* **dotenv**: Environment variable management
|
|
156
|
-
* **chalk**: Terminal output styling
|
|
157
|
-
* **ora**: Terminal spinner for progress display
|
|
158
|
-
* **slugify**: Convert strings to URL-friendly slugs
|
|
159
|
-
* **uuid**: Generate unique IDs
|
|
160
|
-
|
|
161
|
-
### Open Source Licenses
|
|
162
|
-
|
|
163
|
-
* **nconv-cli**: [ISC License](LICENSE)
|
|
164
|
-
* This project uses multiple open source libraries,
|
|
165
|
-
each distributed under its respective license.
|
|
166
|
-
|
|
167
|
-
## Development
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
# Development mode (watch files)
|
|
171
|
-
npm run dev
|
|
172
|
-
|
|
173
|
-
# Build
|
|
174
|
-
npm run build
|
|
175
|
-
|
|
176
|
-
# Local testing
|
|
177
|
-
npm link
|
|
178
|
-
nconv md "https://notion.so/test-page"
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Project Structure
|
|
182
|
-
|
|
183
|
-
```text
|
|
184
|
-
nconv/
|
|
185
|
-
├── src/
|
|
186
|
-
│ ├── index.ts
|
|
187
|
-
│ ├── config.ts
|
|
188
|
-
│ ├── commands/
|
|
189
|
-
│ │ └── md.ts
|
|
190
|
-
│ ├── core/
|
|
191
|
-
│ │ ├── exporter.ts
|
|
192
|
-
│ │ └── image-processor.ts
|
|
193
|
-
│ └── utils/
|
|
194
|
-
│ ├── file.ts
|
|
195
|
-
│ └── logger.ts
|
|
196
|
-
├── bin/
|
|
197
|
-
│ └── nconv.js
|
|
198
|
-
├── package.json
|
|
199
|
-
├── tsconfig.json
|
|
200
|
-
└── tsup.config.ts
|