@jordancoin/notioncli 1.2.1 → 1.3.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,196 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ register(program, ctx) {
6
+ const {
7
+ getNotion,
8
+ resolveDb,
9
+ resolvePageId,
10
+ getDbSchema,
11
+ buildProperties,
12
+ jsonOutput,
13
+ richTextToPlain,
14
+ propValue,
15
+ extractDynamicProps,
16
+ markdownToBlocks,
17
+ runCommand,
18
+ } = ctx;
19
+
20
+ // ─── add ──────────────────────────────────────────────────────────────────
21
+ program
22
+ .command('add <database>')
23
+ .description('Add a new page to a database (e.g. notion add tasks --name "Ship it" --status "Done")')
24
+ .option('--prop <key=value...>', 'Property value — repeatable (e.g. --prop "Name=Hello")', (v, prev) => prev.concat([v]), [])
25
+ .option('--from <file>', 'Import content from a .md file as page body')
26
+ .allowUnknownOption()
27
+ .allowExcessArguments()
28
+ .action(async (db, opts, cmd) => runCommand('Add', async () => {
29
+ const notion = getNotion();
30
+ const dbIds = resolveDb(db);
31
+
32
+ // Merge --prop flags with dynamic property flags (--name, --status, etc.)
33
+ const schema = await getDbSchema(dbIds);
34
+ const knownFlags = ['prop', 'from', 'json', 'workspace', 'w', 'filter', 'limit', 'sort', 'output'];
35
+ const dynamicProps = extractDynamicProps(process.argv, knownFlags, schema);
36
+ const allProps = [...(opts.prop || []), ...dynamicProps];
37
+
38
+ if (allProps.length === 0) {
39
+ console.error('No properties provided. Use property flags or --prop:');
40
+ console.error(` notion add ${db} --name "My Page" --status "Active"`);
41
+ console.error(` notion add ${db} --prop "Name=My Page" --prop "Status=Active"`);
42
+ const propNames = Object.values(schema).map(s => `--${s.name.toLowerCase().replace(/\s+/g, '-')}`);
43
+ console.error(`\nAvailable: ${propNames.join(', ')}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ const properties = await buildProperties(dbIds, allProps);
48
+ const res = await notion.pages.create({
49
+ parent: { type: 'data_source_id', data_source_id: dbIds.data_source_id },
50
+ properties,
51
+ });
52
+
53
+ // If --from file, parse and append blocks
54
+ if (opts.from) {
55
+ const filePath = path.resolve(opts.from);
56
+ if (!fs.existsSync(filePath)) {
57
+ console.error(`File not found: ${filePath}`);
58
+ process.exit(1);
59
+ }
60
+ const content = fs.readFileSync(filePath, 'utf-8');
61
+ const ext = path.extname(filePath).toLowerCase();
62
+
63
+ let blocks;
64
+ if (ext === '.md' || ext === '.markdown') {
65
+ blocks = markdownToBlocks(content);
66
+ } else {
67
+ // Treat as plain text
68
+ blocks = [{ object: 'block', type: 'paragraph', paragraph: { rich_text: [{ type: 'text', text: { content } }] } }];
69
+ }
70
+
71
+ // Notion API limits to 100 blocks per append call
72
+ for (let i = 0; i < blocks.length; i += 100) {
73
+ await notion.blocks.children.append({
74
+ block_id: res.id,
75
+ children: blocks.slice(i, i + 100),
76
+ });
77
+ }
78
+ }
79
+
80
+ if (jsonOutput(cmd, res)) return;
81
+ console.log(`✅ Created page: ${res.id}`);
82
+ console.log(` URL: ${res.url}`);
83
+ if (opts.from) console.log(` Content imported from: ${opts.from}`);
84
+ }));
85
+
86
+ // ─── update ────────────────────────────────────────────────────────────────
87
+ program
88
+ .command('update <page-or-alias>')
89
+ .description('Update a page\'s properties by ID or alias + filter (e.g. notion update tasks --filter "Name=Ship it" --status "Done")')
90
+ .option('--filter <key=value...>', 'Filter to find the page — repeatable for AND (required with alias)', (v, prev) => prev.concat([v]), [])
91
+ .option('--prop <key=value...>', 'Property value — repeatable', (v, prev) => prev.concat([v]), [])
92
+ .allowUnknownOption()
93
+ .allowExcessArguments()
94
+ .action(async (target, opts, cmd) => runCommand('Update', async () => {
95
+ const notion = getNotion();
96
+ const { pageId, dbIds: resolvedDbIds } = await resolvePageId(target, opts.filter);
97
+ let dbIds = resolvedDbIds;
98
+ if (!dbIds) {
99
+ const page = await notion.pages.retrieve({ page_id: pageId });
100
+ const dsId = page.parent?.data_source_id;
101
+ if (!dsId) {
102
+ console.error('Page is not in a database — cannot auto-detect property types.');
103
+ process.exit(1);
104
+ }
105
+ dbIds = { data_source_id: dsId, database_id: page.parent?.database_id || dsId };
106
+ }
107
+ // Merge --prop flags with dynamic property flags
108
+ const schema = await getDbSchema(dbIds);
109
+ const knownFlags = ['prop', 'filter', 'json', 'workspace', 'w', 'limit', 'sort', 'output'];
110
+ const dynamicProps = extractDynamicProps(process.argv, knownFlags, schema);
111
+ const allProps = [...(opts.prop || []), ...dynamicProps];
112
+
113
+ if (allProps.length === 0) {
114
+ console.error('No properties to update. Use property flags or --prop:');
115
+ console.error(` notion update ${target} --filter "Name=..." --status "Done"`);
116
+ process.exit(1);
117
+ }
118
+
119
+ const properties = await buildProperties(dbIds, allProps);
120
+ const res = await notion.pages.update({ page_id: pageId, properties });
121
+ if (jsonOutput(cmd, res)) return;
122
+ console.log(`✅ Updated page: ${res.id}`);
123
+ }));
124
+
125
+ // ─── delete (archive) ──────────────────────────────────────────────────────
126
+ program
127
+ .command('delete <page-or-alias>')
128
+ .description('Delete (archive) a page by ID or alias + filter')
129
+ .option('--filter <key=value>', 'Filter to find the page (required when using an alias)')
130
+ .action(async (target, opts, cmd) => runCommand('Delete', async () => {
131
+ const notion = getNotion();
132
+ const { pageId } = await resolvePageId(target, opts.filter);
133
+ const res = await notion.pages.update({ page_id: pageId, archived: true });
134
+ if (jsonOutput(cmd, res)) return;
135
+ console.log('🗑️ Archived page: ' + res.id);
136
+ console.log(' (Restore it from the trash in Notion if needed)');
137
+ }));
138
+
139
+ // ─── get ──────────────────────────────────────────────────────────────────
140
+ program
141
+ .command('get <page-or-alias>')
142
+ .description('Get a page\'s properties by ID or alias + filter')
143
+ .option('--filter <key=value>', 'Filter to find the page (required when using an alias)')
144
+ .action(async (target, opts, cmd) => runCommand('Get', async () => {
145
+ const notion = getNotion();
146
+ const { pageId } = await resolvePageId(target, opts.filter);
147
+ const page = await notion.pages.retrieve({ page_id: pageId });
148
+ if (jsonOutput(cmd, page)) return;
149
+ console.log(`Page: ${page.id}`);
150
+ console.log(`URL: ${page.url}`);
151
+ console.log(`Created: ${page.created_time}`);
152
+ console.log(`Updated: ${page.last_edited_time}`);
153
+ console.log('');
154
+ console.log('Properties:');
155
+ for (const [name, prop] of Object.entries(page.properties)) {
156
+ if (prop.type === 'relation') {
157
+ const rels = prop.relation || [];
158
+ if (rels.length === 0) {
159
+ console.log(` ${name}: (none)`);
160
+ } else {
161
+ // Resolve relation titles
162
+ const titles = [];
163
+ for (const rel of rels) {
164
+ try {
165
+ const linked = await notion.pages.retrieve({ page_id: rel.id });
166
+ let t = '';
167
+ for (const [, p] of Object.entries(linked.properties)) {
168
+ if (p.type === 'title') { t = propValue(p); break; }
169
+ }
170
+ titles.push(t || rel.id.slice(0, 8) + '…');
171
+ } catch {
172
+ titles.push(rel.id.slice(0, 8) + '…');
173
+ }
174
+ }
175
+ console.log(` ${name}: ${titles.join(', ')}`);
176
+ }
177
+ } else if (prop.type === 'rollup') {
178
+ const r = prop.rollup;
179
+ if (!r) {
180
+ console.log(` ${name}: (empty)`);
181
+ } else if (r.type === 'number') {
182
+ console.log(` ${name}: ${r.number != null ? r.number : '(empty)'}`);
183
+ } else if (r.type === 'date') {
184
+ console.log(` ${name}: ${r.date ? r.date.start : '(empty)'}`);
185
+ } else if (r.type === 'array' && r.array) {
186
+ console.log(` ${name}: ${r.array.map(item => propValue(item)).join(', ')}`);
187
+ } else {
188
+ console.log(` ${name}: ${JSON.stringify(r)}`);
189
+ }
190
+ } else {
191
+ console.log(` ${name}: ${propValue(prop)}`);
192
+ }
193
+ }
194
+ }));
195
+ },
196
+ };
@@ -0,0 +1,241 @@
1
+ module.exports = {
2
+ register(program, ctx) {
3
+ const {
4
+ getNotion,
5
+ resolveDb,
6
+ loadConfig,
7
+ saveConfig,
8
+ getWorkspaceName,
9
+ jsonOutput,
10
+ richTextToPlain,
11
+ propValue,
12
+ printTable,
13
+ paginate,
14
+ runCommand,
15
+ } = ctx;
16
+
17
+ // ─── dbs ────────────────────────────────────────────────────────────────
18
+ program
19
+ .command('dbs')
20
+ .description('List all databases shared with your integration')
21
+ .action(async (opts, cmd) => runCommand('List databases', async () => {
22
+ const notion = getNotion();
23
+ const { results, response } = await paginate(
24
+ ({ start_cursor, page_size }) => notion.search({
25
+ filter: { value: 'data_source', property: 'object' },
26
+ start_cursor,
27
+ page_size,
28
+ }),
29
+ { pageSizeLimit: 100 },
30
+ );
31
+ if (jsonOutput(cmd, response)) return;
32
+ const rows = results.map(db => ({
33
+ id: db.id,
34
+ title: richTextToPlain(db.title),
35
+ url: db.url || '',
36
+ }));
37
+ if (rows.length === 0) {
38
+ console.log('No databases found. Make sure you\'ve shared databases with your integration.');
39
+ console.log('In Notion: open a database → ••• menu → Connections → Add your integration');
40
+ return;
41
+ }
42
+ printTable(rows, ['id', 'title', 'url']);
43
+ }));
44
+
45
+ // ─── templates ───────────────────────────────────────────────────────────
46
+ program
47
+ .command('templates <database>')
48
+ .description('List page templates available for a database')
49
+ .action(async (db, opts, cmd) => runCommand('Templates', async () => {
50
+ const notion = getNotion();
51
+ const dbIds = resolveDb(db);
52
+ const res = await notion.dataSources.listTemplates({
53
+ data_source_id: dbIds.data_source_id,
54
+ });
55
+ if (jsonOutput(cmd, res)) return;
56
+ if (!res.results || res.results.length === 0) {
57
+ console.log('No templates found for this database.');
58
+ return;
59
+ }
60
+ const rows = res.results.map(t => {
61
+ let title = '';
62
+ if (t.properties) {
63
+ for (const [, prop] of Object.entries(t.properties)) {
64
+ if (prop.type === 'title') {
65
+ title = propValue(prop);
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ return {
71
+ id: t.id,
72
+ title: title || '(untitled)',
73
+ url: t.url || '',
74
+ };
75
+ });
76
+ printTable(rows, ['id', 'title', 'url']);
77
+ }));
78
+
79
+ // ─── db-create ───────────────────────────────────────────────────────────
80
+ program
81
+ .command('db-create <parent-page-id> <title>')
82
+ .description('Create a new database under a page')
83
+ .option('--prop <name:type...>', 'Property definition — repeatable (e.g. --prop "Status:select" --prop "Priority:number")', (v, prev) => prev.concat([v]), [])
84
+ .option('--alias <name>', 'Auto-create an alias for the new database')
85
+ .action(async (parentPageId, title, opts, cmd) => runCommand('Database create', async () => {
86
+ const notion = getNotion();
87
+
88
+ // Build properties — always include a title property
89
+ const properties = {};
90
+ let hasTitleProp = false;
91
+
92
+ for (const kv of opts.prop) {
93
+ const colonIdx = kv.indexOf(':');
94
+ if (colonIdx === -1) {
95
+ console.error(`Invalid property format: ${kv} (expected name:type)`);
96
+ console.error('Supported types: title, rich_text, number, select, multi_select, date, checkbox, url, email, phone_number, status');
97
+ process.exit(1);
98
+ }
99
+ const name = kv.slice(0, colonIdx);
100
+ const type = kv.slice(colonIdx + 1).toLowerCase();
101
+ if (type === 'title') hasTitleProp = true;
102
+ properties[name] = { [type]: {} };
103
+ }
104
+
105
+ // Ensure there's a title property
106
+ if (!hasTitleProp) {
107
+ properties['Name'] = { title: {} };
108
+ }
109
+
110
+ // 2025 API: databases.create() only handles title property reliably.
111
+ // Non-title properties must be added via dataSources.update() after creation.
112
+ const titleProps = {};
113
+ const extraProps = {};
114
+ for (const [name, prop] of Object.entries(properties)) {
115
+ if (prop.title) {
116
+ titleProps[name] = prop;
117
+ } else {
118
+ extraProps[name] = prop;
119
+ }
120
+ }
121
+ // Ensure title property exists in create call
122
+ if (Object.keys(titleProps).length === 0) {
123
+ titleProps['Name'] = { title: {} };
124
+ }
125
+
126
+ const res = await notion.databases.create({
127
+ parent: { type: 'page_id', page_id: parentPageId },
128
+ title: [{ text: { content: title } }],
129
+ properties: titleProps,
130
+ });
131
+
132
+ // Extract correct dual IDs from response
133
+ const databaseId = res.id;
134
+ const dataSourceId = (res.data_sources && res.data_sources[0])
135
+ ? res.data_sources[0].id
136
+ : res.id;
137
+
138
+ // Add non-title properties via dataSources.update()
139
+ if (Object.keys(extraProps).length > 0) {
140
+ await notion.dataSources.update({
141
+ data_source_id: dataSourceId,
142
+ properties: extraProps,
143
+ });
144
+ }
145
+
146
+ if (jsonOutput(cmd, res)) return;
147
+
148
+ console.log(`✅ Created database: ${databaseId.slice(0, 8)}…`);
149
+ console.log(` Title: ${title}`);
150
+ console.log(` Properties: ${Object.keys(properties).join(', ')}`);
151
+
152
+ // Auto-create alias if requested
153
+ if (opts.alias) {
154
+ const config = loadConfig();
155
+ const wsName = getWorkspaceName() || config.activeWorkspace || 'default';
156
+ if (!config.workspaces[wsName]) config.workspaces[wsName] = { aliases: {} };
157
+ if (!config.workspaces[wsName].aliases) config.workspaces[wsName].aliases = {};
158
+ config.workspaces[wsName].aliases[opts.alias] = {
159
+ database_id: databaseId,
160
+ data_source_id: dataSourceId,
161
+ };
162
+ saveConfig(config);
163
+ console.log(` Alias: ${opts.alias}`);
164
+ }
165
+ }));
166
+
167
+ // ─── db-update ───────────────────────────────────────────────────────────
168
+ program
169
+ .command('db-update <database>')
170
+ .description('Update a database title or add properties')
171
+ .option('--title <text>', 'New database title')
172
+ .option('--add-prop <name:type...>', 'Add a property (e.g. --add-prop "Priority:number")', (v, prev) => prev.concat([v]), [])
173
+ .option('--remove-prop <name...>', 'Remove a property by name', (v, prev) => prev.concat([v]), [])
174
+ .action(async (db, opts, cmd) => runCommand('Database update', async () => {
175
+ const notion = getNotion();
176
+ const dbIds = resolveDb(db);
177
+
178
+ // 2025 API: property changes go through dataSources.update(), NOT databases.update().
179
+ // databases.update() silently ignores property modifications.
180
+ // Title changes still go through databases.update().
181
+ let canonicalId = dbIds.database_id;
182
+ const dataSourceId = dbIds.data_source_id;
183
+
184
+ // Resolve canonical database_id if both IDs are the same
185
+ if (canonicalId === dataSourceId) {
186
+ try {
187
+ const ds = await notion.dataSources.retrieve({ data_source_id: canonicalId });
188
+ if (ds.parent && ds.parent.type === 'database_id') {
189
+ canonicalId = ds.parent.database_id;
190
+ }
191
+ } catch (_) { /* fall through with what we have */ }
192
+ }
193
+
194
+ // Build property changes for dataSources.update()
195
+ let propChanges = null;
196
+ if (opts.addProp.length > 0 || opts.removeProp.length > 0) {
197
+ propChanges = {};
198
+
199
+ for (const kv of opts.addProp) {
200
+ const colonIdx = kv.indexOf(':');
201
+ if (colonIdx === -1) {
202
+ console.error(`Invalid property format: ${kv} (expected name:type)`);
203
+ process.exit(1);
204
+ }
205
+ const name = kv.slice(0, colonIdx);
206
+ const type = kv.slice(colonIdx + 1).toLowerCase();
207
+ propChanges[name] = { [type]: {} };
208
+ }
209
+
210
+ for (const name of opts.removeProp) {
211
+ propChanges[name] = null;
212
+ }
213
+ }
214
+
215
+ let res;
216
+
217
+ // Title changes go through databases.update()
218
+ if (opts.title) {
219
+ res = await notion.databases.update({
220
+ database_id: canonicalId,
221
+ title: [{ text: { content: opts.title } }],
222
+ });
223
+ }
224
+
225
+ // Property changes go through dataSources.update()
226
+ if (propChanges) {
227
+ res = await notion.dataSources.update({
228
+ data_source_id: dataSourceId,
229
+ properties: propChanges,
230
+ });
231
+ }
232
+
233
+ if (jsonOutput(cmd, res)) return;
234
+
235
+ console.log(`✅ Updated database: ${(dbIds.database_id || dbIds.data_source_id).slice(0, 8)}…`);
236
+ if (opts.title) console.log(` Title: ${opts.title}`);
237
+ if (opts.addProp.length > 0) console.log(` Added: ${opts.addProp.join(', ')}`);
238
+ if (opts.removeProp.length > 0) console.log(` Removed: ${opts.removeProp.join(', ')}`);
239
+ }));
240
+ },
241
+ };
@@ -0,0 +1,162 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ register(program, ctx) {
6
+ const {
7
+ getNotion,
8
+ resolveDb,
9
+ getDbSchema,
10
+ buildProperties,
11
+ parseCsv,
12
+ markdownToBlocks,
13
+ blocksToMarkdown,
14
+ runCommand,
15
+ } = ctx;
16
+
17
+ // ─── import ──────────────────────────────────────────────────────────────
18
+ program
19
+ .command('import <file>')
20
+ .description('Import data from a file (.csv/.json → database pages, .md → page content)')
21
+ .option('--to <database>', 'Target database alias for CSV/JSON import')
22
+ .option('--parent <page-id>', 'Parent page for markdown import')
23
+ .option('--title <text>', 'Page title for markdown import')
24
+ .action(async (file, opts, cmd) => runCommand('Import', async () => {
25
+ const filePath = path.resolve(file);
26
+ if (!fs.existsSync(filePath)) {
27
+ console.error(`File not found: ${filePath}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ const ext = path.extname(filePath).toLowerCase();
32
+ const content = fs.readFileSync(filePath, 'utf-8');
33
+
34
+ if (ext === '.csv' || ext === '.json') {
35
+ // Database import: CSV/JSON → pages
36
+ if (!opts.to) {
37
+ console.error('--to <database> is required for CSV/JSON import.');
38
+ console.error('Example: notion import data.csv --to tasks');
39
+ process.exit(1);
40
+ }
41
+
42
+ const notion = getNotion();
43
+ const dbIds = resolveDb(opts.to);
44
+ const schema = await getDbSchema(dbIds);
45
+
46
+ let rows;
47
+ if (ext === '.csv') {
48
+ rows = parseCsv(content);
49
+ } else {
50
+ const parsed = JSON.parse(content);
51
+ rows = Array.isArray(parsed) ? parsed : [parsed];
52
+ }
53
+
54
+ if (rows.length === 0) {
55
+ console.error('No data found in file.');
56
+ process.exit(1);
57
+ }
58
+
59
+ console.log(`Importing ${rows.length} row${rows.length !== 1 ? 's' : ''} to ${opts.to}...`);
60
+
61
+ let created = 0;
62
+ let failed = 0;
63
+ for (const row of rows) {
64
+ try {
65
+ // Map row keys to schema properties
66
+ const propStrs = [];
67
+ for (const [key, value] of Object.entries(row)) {
68
+ if (value === '' || value === null || value === undefined) continue;
69
+ const schemaEntry = schema[key.toLowerCase()];
70
+ if (schemaEntry) {
71
+ propStrs.push(`${schemaEntry.name}=${value}`);
72
+ }
73
+ }
74
+ if (propStrs.length === 0) continue;
75
+
76
+ const properties = await buildProperties(dbIds, propStrs);
77
+ await notion.pages.create({
78
+ parent: { type: 'data_source_id', data_source_id: dbIds.data_source_id },
79
+ properties,
80
+ });
81
+ created++;
82
+ } catch (err) {
83
+ failed++;
84
+ if (failed <= 3) console.error(` Row failed: ${err.message}`);
85
+ }
86
+ }
87
+
88
+ console.log(`✅ Imported ${created} page${created !== 1 ? 's' : ''}${failed > 0 ? ` (${failed} failed)` : ''}`);
89
+
90
+ } else if (ext === '.md' || ext === '.markdown') {
91
+ // Page import: Markdown → page with blocks
92
+ const notion = getNotion();
93
+ const title = opts.title || path.basename(filePath, ext);
94
+
95
+ let parentId = opts.parent;
96
+ if (!parentId && opts.to) {
97
+ // If --to is an alias, create as a database page
98
+ const dbIds = resolveDb(opts.to);
99
+ const properties = await buildProperties(dbIds, [`Name=${title}`]);
100
+ const res = await notion.pages.create({
101
+ parent: { type: 'data_source_id', data_source_id: dbIds.data_source_id },
102
+ properties,
103
+ });
104
+ parentId = res.id;
105
+ console.log(`✅ Created page: ${res.id}`);
106
+ } else if (parentId) {
107
+ // Create as a child page
108
+ const res = await notion.pages.create({
109
+ parent: { type: 'page_id', page_id: parentId },
110
+ properties: { title: { title: [{ text: { content: title } }] } },
111
+ });
112
+ parentId = res.id;
113
+ console.log(`✅ Created page: ${res.id}`);
114
+ } else {
115
+ console.error('Specify --to <database> or --parent <page-id> for markdown import.');
116
+ process.exit(1);
117
+ }
118
+
119
+ // Parse markdown and append blocks
120
+ const blocks = markdownToBlocks(content);
121
+ for (let i = 0; i < blocks.length; i += 100) {
122
+ await notion.blocks.children.append({
123
+ block_id: parentId,
124
+ children: blocks.slice(i, i + 100),
125
+ });
126
+ }
127
+
128
+ console.log(` Imported ${blocks.length} block${blocks.length !== 1 ? 's' : ''} from ${path.basename(filePath)}`);
129
+ } else {
130
+ console.error(`Unsupported file type: ${ext}`);
131
+ console.error('Supported: .csv, .json (→ database), .md (→ page)');
132
+ process.exit(1);
133
+ }
134
+ }));
135
+
136
+ // ─── export ──────────────────────────────────────────────────────────────
137
+ program
138
+ .command('export <page-or-alias>')
139
+ .description('Export page content as markdown')
140
+ .option('--filter <key=value>', 'Filter to find the page (required when using an alias)')
141
+ .action(async (target, opts, cmd) => runCommand('Export', async () => {
142
+ const notion = getNotion();
143
+ const { pageId } = await ctx.resolvePageId(target, opts.filter);
144
+
145
+ // Fetch all blocks
146
+ let blocks = [];
147
+ let cursor;
148
+ do {
149
+ const res = await notion.blocks.children.list({
150
+ block_id: pageId,
151
+ start_cursor: cursor,
152
+ page_size: 100,
153
+ });
154
+ blocks = blocks.concat(res.results);
155
+ cursor = res.has_more ? res.next_cursor : null;
156
+ } while (cursor);
157
+
158
+ const md = blocksToMarkdown(blocks);
159
+ console.log(md);
160
+ }));
161
+ },
162
+ };