@lpenguin/notion-cli 1.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 +245 -0
- package/dist/commands/database/create.d.ts +8 -0
- package/dist/commands/database/create.d.ts.map +1 -0
- package/dist/commands/database/create.js +71 -0
- package/dist/commands/database/create.js.map +1 -0
- package/dist/commands/database/delete.d.ts +10 -0
- package/dist/commands/database/delete.d.ts.map +1 -0
- package/dist/commands/database/delete.js +83 -0
- package/dist/commands/database/delete.js.map +1 -0
- package/dist/commands/database/export.d.ts +11 -0
- package/dist/commands/database/export.d.ts.map +1 -0
- package/dist/commands/database/export.js +74 -0
- package/dist/commands/database/export.js.map +1 -0
- package/dist/commands/database/insert.d.ts +11 -0
- package/dist/commands/database/insert.d.ts.map +1 -0
- package/dist/commands/database/insert.js +93 -0
- package/dist/commands/database/insert.js.map +1 -0
- package/dist/commands/database/list.d.ts +10 -0
- package/dist/commands/database/list.d.ts.map +1 -0
- package/dist/commands/database/list.js +80 -0
- package/dist/commands/database/list.js.map +1 -0
- package/dist/commands/database/query.d.ts +10 -0
- package/dist/commands/database/query.d.ts.map +1 -0
- package/dist/commands/database/query.js +77 -0
- package/dist/commands/database/query.js.map +1 -0
- package/dist/commands/database/schema.d.ts +11 -0
- package/dist/commands/database/schema.d.ts.map +1 -0
- package/dist/commands/database/schema.js +76 -0
- package/dist/commands/database/schema.js.map +1 -0
- package/dist/commands/database/update.d.ts +11 -0
- package/dist/commands/database/update.d.ts.map +1 -0
- package/dist/commands/database/update.js +105 -0
- package/dist/commands/database/update.js.map +1 -0
- package/dist/commands/page/create.d.ts +11 -0
- package/dist/commands/page/create.d.ts.map +1 -0
- package/dist/commands/page/create.js +102 -0
- package/dist/commands/page/create.js.map +1 -0
- package/dist/commands/page/list.d.ts +10 -0
- package/dist/commands/page/list.d.ts.map +1 -0
- package/dist/commands/page/list.js +87 -0
- package/dist/commands/page/list.js.map +1 -0
- package/dist/commands/page/patch.d.ts +21 -0
- package/dist/commands/page/patch.d.ts.map +1 -0
- package/dist/commands/page/patch.js +156 -0
- package/dist/commands/page/patch.js.map +1 -0
- package/dist/commands/page/read.d.ts +10 -0
- package/dist/commands/page/read.d.ts.map +1 -0
- package/dist/commands/page/read.js +86 -0
- package/dist/commands/page/read.js.map +1 -0
- package/dist/commands/page/write-properties.d.ts +15 -0
- package/dist/commands/page/write-properties.d.ts.map +1 -0
- package/dist/commands/page/write-properties.js +128 -0
- package/dist/commands/page/write-properties.js.map +1 -0
- package/dist/commands/page/write.d.ts +14 -0
- package/dist/commands/page/write.d.ts.map +1 -0
- package/dist/commands/page/write.js +109 -0
- package/dist/commands/page/write.js.map +1 -0
- package/dist/commands/search.d.ts +18 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +129 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/block-patch.d.ts +61 -0
- package/dist/lib/block-patch.d.ts.map +1 -0
- package/dist/lib/block-patch.js +181 -0
- package/dist/lib/block-patch.js.map +1 -0
- package/dist/lib/client.d.ts +17 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +63 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/config.d.ts +19 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +65 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/csv.d.ts +45 -0
- package/dist/lib/csv.d.ts.map +1 -0
- package/dist/lib/csv.js +262 -0
- package/dist/lib/csv.js.map +1 -0
- package/dist/lib/db-properties.d.ts +11 -0
- package/dist/lib/db-properties.d.ts.map +1 -0
- package/dist/lib/db-properties.js +25 -0
- package/dist/lib/db-properties.js.map +1 -0
- package/dist/lib/errors.d.ts +34 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +86 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/file-upload.d.ts +25 -0
- package/dist/lib/file-upload.d.ts.map +1 -0
- package/dist/lib/file-upload.js +90 -0
- package/dist/lib/file-upload.js.map +1 -0
- package/dist/lib/markdown.d.ts +79 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/markdown.js +320 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/output.d.ts +20 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +67 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/patch.d.ts +23 -0
- package/dist/lib/patch.d.ts.map +1 -0
- package/dist/lib/patch.js +72 -0
- package/dist/lib/patch.js.map +1 -0
- package/dist/lib/rate-limit.d.ts +9 -0
- package/dist/lib/rate-limit.d.ts.map +1 -0
- package/dist/lib/rate-limit.js +67 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/safety.d.ts +17 -0
- package/dist/lib/safety.d.ts.map +1 -0
- package/dist/lib/safety.js +60 -0
- package/dist/lib/safety.js.map +1 -0
- package/dist/lib/types.d.ts +138 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/validator.d.ts +33 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +68 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/utils/id.d.ts +14 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +33 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +43 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/string.d.ts +14 -0
- package/dist/utils/string.d.ts.map +1 -0
- package/dist/utils/string.js +37 -0
- package/dist/utils/string.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surgical block-level patching.
|
|
3
|
+
*
|
|
4
|
+
* Instead of deleting all blocks and recreating them, this module
|
|
5
|
+
* enables targeted edits by tracking which markdown lines correspond
|
|
6
|
+
* to which Notion blocks.
|
|
7
|
+
*
|
|
8
|
+
* Key concepts:
|
|
9
|
+
* - BlockLineMapping: Maps a block ID to its line range in the markdown
|
|
10
|
+
* - PatchPlan: Describes which blocks to delete and what to insert
|
|
11
|
+
*
|
|
12
|
+
* The flow:
|
|
13
|
+
* 1. Fetch MdBlocks from Notion (via notion-to-md)
|
|
14
|
+
* 2. Build line mapping via mdBlocksToMarkdown (unified converter in markdown.ts)
|
|
15
|
+
* 3. Given a line range edit, compute which blocks are affected
|
|
16
|
+
* 4. Execute the plan: delete affected blocks, insert new content
|
|
17
|
+
*/
|
|
18
|
+
import {} from './types.js';
|
|
19
|
+
// Re-export the unified converter as buildBlockLineMap for backward compatibility
|
|
20
|
+
export { mdBlocksToMarkdown as buildBlockLineMap } from './markdown.js';
|
|
21
|
+
/**
|
|
22
|
+
* Compute a patch plan given block mappings and a line range to replace.
|
|
23
|
+
*
|
|
24
|
+
* Determines which blocks need to be deleted and where to insert new content.
|
|
25
|
+
*
|
|
26
|
+
* Rules:
|
|
27
|
+
* - Blocks fully contained in [startLine, endLine] are deleted
|
|
28
|
+
* - Blocks that partially overlap are also deleted (they need to be reconstructed)
|
|
29
|
+
* - New content is inserted after the last unaffected block before the range
|
|
30
|
+
*
|
|
31
|
+
* @param mappings - Block-to-line mappings from buildBlockLineMap
|
|
32
|
+
* @param startLine - 1-indexed start line of the edit (inclusive)
|
|
33
|
+
* @param endLine - 1-indexed end line of the edit (inclusive)
|
|
34
|
+
* @param newContent - The replacement markdown content
|
|
35
|
+
* @returns A plan describing deletions and insertions
|
|
36
|
+
*/
|
|
37
|
+
export function computePatchPlan(mappings, startLine, endLine, newContent) {
|
|
38
|
+
const blocksToDelete = [];
|
|
39
|
+
let insertAfterId = null;
|
|
40
|
+
let parentBlockId;
|
|
41
|
+
// Find the block just before the edit range (for insertion point)
|
|
42
|
+
// and collect all blocks that overlap with the edit range
|
|
43
|
+
for (const mapping of mappings) {
|
|
44
|
+
const blockResult = analyzeBlockOverlap(mapping, startLine, endLine);
|
|
45
|
+
// Collect blocks to delete
|
|
46
|
+
blocksToDelete.push(...blockResult.blocksToDelete);
|
|
47
|
+
// If edits target only children, track the parent block ID for insertion
|
|
48
|
+
if (blockResult.parentBlockId !== undefined) {
|
|
49
|
+
parentBlockId = blockResult.parentBlockId;
|
|
50
|
+
insertAfterId = blockResult.childInsertAfterId;
|
|
51
|
+
}
|
|
52
|
+
else if (mapping.endLine < startLine) {
|
|
53
|
+
// Track the last top-level block that ends before our edit range
|
|
54
|
+
insertAfterId = mapping.blockId;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Build insertion plan
|
|
58
|
+
const blocksToInsert = [];
|
|
59
|
+
if (newContent.trim() !== '') {
|
|
60
|
+
blocksToInsert.push({
|
|
61
|
+
afterId: insertAfterId,
|
|
62
|
+
markdown: newContent,
|
|
63
|
+
...(parentBlockId !== undefined ? { parentBlockId } : {}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
blocksToDelete,
|
|
68
|
+
blocksToInsert,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Analyze a block (and its children) for overlap with a line range.
|
|
73
|
+
*
|
|
74
|
+
* Key distinction: if only child blocks overlap (not the parent's own content),
|
|
75
|
+
* we delete only the affected children and mark the insertion as child-level.
|
|
76
|
+
* This prevents destroying the parent block when editing nested content.
|
|
77
|
+
*
|
|
78
|
+
* @returns Overlap analysis with block IDs to delete and insertion context
|
|
79
|
+
*/
|
|
80
|
+
function analyzeBlockOverlap(mapping, startLine, endLine) {
|
|
81
|
+
const blocksToDelete = [];
|
|
82
|
+
// Calculate the parent's OWN line range (excluding children)
|
|
83
|
+
const parentOwnEndLine = mapping.startLine + mapping.markdown.split('\n').length - 1;
|
|
84
|
+
// Check if the parent's own content overlaps with the edit range
|
|
85
|
+
const parentOwnOverlaps = mapping.startLine <= endLine && parentOwnEndLine >= startLine;
|
|
86
|
+
if (parentOwnOverlaps) {
|
|
87
|
+
// Parent's own content is affected - delete the whole block
|
|
88
|
+
blocksToDelete.push(mapping.blockId);
|
|
89
|
+
// Also delete all children (they'll be recreated with the parent)
|
|
90
|
+
for (const child of mapping.children) {
|
|
91
|
+
blocksToDelete.push(...collectAllBlockIds(child));
|
|
92
|
+
}
|
|
93
|
+
return { blocksToDelete, childInsertAfterId: null };
|
|
94
|
+
}
|
|
95
|
+
// Parent's own content is NOT affected - check children only
|
|
96
|
+
let childInsertAfterId = null;
|
|
97
|
+
let hasChildOverlap = false;
|
|
98
|
+
for (const child of mapping.children) {
|
|
99
|
+
const childOverlaps = child.startLine <= endLine && child.endLine >= startLine;
|
|
100
|
+
if (childOverlaps) {
|
|
101
|
+
hasChildOverlap = true;
|
|
102
|
+
blocksToDelete.push(child.blockId);
|
|
103
|
+
// Also delete grandchildren of affected children
|
|
104
|
+
for (const grandchild of child.children) {
|
|
105
|
+
blocksToDelete.push(...collectAllBlockIds(grandchild));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (child.endLine < startLine) {
|
|
109
|
+
// Track last child before the edit range for insertion point
|
|
110
|
+
childInsertAfterId = child.blockId;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (hasChildOverlap) {
|
|
114
|
+
return {
|
|
115
|
+
blocksToDelete,
|
|
116
|
+
parentBlockId: mapping.blockId,
|
|
117
|
+
childInsertAfterId,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { blocksToDelete, childInsertAfterId: null };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Recursively collect all block IDs from a mapping tree.
|
|
124
|
+
*/
|
|
125
|
+
function collectAllBlockIds(mapping) {
|
|
126
|
+
const ids = [mapping.blockId];
|
|
127
|
+
for (const child of mapping.children) {
|
|
128
|
+
ids.push(...collectAllBlockIds(child));
|
|
129
|
+
}
|
|
130
|
+
return ids;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Find a block mapping by line number.
|
|
134
|
+
*
|
|
135
|
+
* @param mappings - Block mappings to search
|
|
136
|
+
* @param line - The line number to find
|
|
137
|
+
* @returns The block containing this line, or undefined
|
|
138
|
+
*/
|
|
139
|
+
export function findBlockAtLine(mappings, line) {
|
|
140
|
+
for (const mapping of mappings) {
|
|
141
|
+
if (line >= mapping.startLine && line <= mapping.endLine) {
|
|
142
|
+
// Check if it's in a child
|
|
143
|
+
const childMatch = findBlockAtLine(mapping.children, line);
|
|
144
|
+
if (childMatch !== undefined) {
|
|
145
|
+
return childMatch;
|
|
146
|
+
}
|
|
147
|
+
return mapping;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get content that should be preserved from partially affected blocks.
|
|
154
|
+
*
|
|
155
|
+
* When a block partially overlaps with the edit range, we need to keep
|
|
156
|
+
* the lines that are outside the edit range.
|
|
157
|
+
*
|
|
158
|
+
* @param mapping - The block mapping
|
|
159
|
+
* @param markdown - Full markdown content
|
|
160
|
+
* @param startLine - Edit start line
|
|
161
|
+
* @param endLine - Edit end line
|
|
162
|
+
* @returns Lines to preserve (before and after the edit range)
|
|
163
|
+
*/
|
|
164
|
+
export function getPreservedContent(mapping, markdown, startLine, endLine) {
|
|
165
|
+
const allLines = markdown.split('\n');
|
|
166
|
+
// Lines before the edit range that belong to this block
|
|
167
|
+
const beforeLines = [];
|
|
168
|
+
for (let i = mapping.startLine; i < startLine && i <= mapping.endLine; i++) {
|
|
169
|
+
beforeLines.push(allLines[i - 1] ?? '');
|
|
170
|
+
}
|
|
171
|
+
// Lines after the edit range that belong to this block
|
|
172
|
+
const afterLines = [];
|
|
173
|
+
for (let i = endLine + 1; i <= mapping.endLine; i++) {
|
|
174
|
+
afterLines.push(allLines[i - 1] ?? '');
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
before: beforeLines.join('\n'),
|
|
178
|
+
after: afterLines.join('\n'),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=block-patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-patch.js","sourceRoot":"","sources":["../../src/lib/block-patch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion API client singleton with token resolution.
|
|
3
|
+
*/
|
|
4
|
+
import { Client } from '@notionhq/client';
|
|
5
|
+
/**
|
|
6
|
+
* Get or create the Notion API client.
|
|
7
|
+
* Token is resolved from CLI flag → env → config file.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getClient(cliToken?: string): Client;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a database ID to its primary data source ID.
|
|
12
|
+
* In the new Notion API (2025-09-03+), we must query data sources, not databases directly.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDataSourceId(client: Client, dbId: string): Promise<string>;
|
|
15
|
+
/** Reset client (useful for testing). */
|
|
16
|
+
export declare function resetClient(): void;
|
|
17
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAkD,MAAM,kBAAkB,CAAC;AAO1F;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCvF;AAED,yCAAyC;AACzC,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion API client singleton with token resolution.
|
|
3
|
+
*/
|
|
4
|
+
import { Client, APIResponseError, APIErrorCode, isFullDatabase } from '@notionhq/client';
|
|
5
|
+
import { resolveToken } from './config.js';
|
|
6
|
+
import * as logger from '../utils/logger.js';
|
|
7
|
+
import { withRateLimit } from './rate-limit.js';
|
|
8
|
+
let clientInstance;
|
|
9
|
+
/**
|
|
10
|
+
* Get or create the Notion API client.
|
|
11
|
+
* Token is resolved from CLI flag → env → config file.
|
|
12
|
+
*/
|
|
13
|
+
export function getClient(cliToken) {
|
|
14
|
+
if (clientInstance !== undefined) {
|
|
15
|
+
return clientInstance;
|
|
16
|
+
}
|
|
17
|
+
const token = resolveToken(cliToken);
|
|
18
|
+
logger.debug('Initializing Notion API client.');
|
|
19
|
+
clientInstance = new Client({
|
|
20
|
+
auth: token,
|
|
21
|
+
timeoutMs: 30_000,
|
|
22
|
+
});
|
|
23
|
+
return clientInstance;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a database ID to its primary data source ID.
|
|
27
|
+
* In the new Notion API (2025-09-03+), we must query data sources, not databases directly.
|
|
28
|
+
*/
|
|
29
|
+
export async function resolveDataSourceId(client, dbId) {
|
|
30
|
+
try {
|
|
31
|
+
// We attempt to retrieve as a database first
|
|
32
|
+
const db = await withRateLimit(() => client.databases.retrieve({ database_id: dbId }), 'databases.retrieve');
|
|
33
|
+
// Check if the database has data_sources (new API)
|
|
34
|
+
if (isFullDatabase(db) && db.data_sources.length > 0) {
|
|
35
|
+
const firstDataSource = db.data_sources[0];
|
|
36
|
+
if (firstDataSource !== undefined) {
|
|
37
|
+
return firstDataSource.id;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// If retrieve fails with 404, it might already be a data_source ID (which databases.retrieve won't find)
|
|
43
|
+
if (APIResponseError.isAPIResponseError(err) && err.code === APIErrorCode.ObjectNotFound) {
|
|
44
|
+
try {
|
|
45
|
+
const ds = await withRateLimit(
|
|
46
|
+
// @ts-ignore - dataSources might be new in the SDK
|
|
47
|
+
() => client.dataSources.retrieve({ data_source_id: dbId }), 'dataSources.retrieve');
|
|
48
|
+
return ds.id;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Fallback to original ID if all else fails
|
|
52
|
+
return dbId;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
return dbId;
|
|
58
|
+
}
|
|
59
|
+
/** Reset client (useful for testing). */
|
|
60
|
+
export function resetClient() {
|
|
61
|
+
clientInstance = undefined;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,IAAI,cAAkC,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,QAAiB;IACzC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAEhD,cAAc,GAAG,IAAI,MAAM,CAAC;QAC1B,IAAI,EAAE,KAAK;QACX,SAAS,EAAE,MAAM;KAClB,CAAC,CAAC;IAEH,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAAY;IACpE,IAAI,CAAC;QACH,6CAA6C;QAC7C,MAAM,EAAE,GAAG,MAAM,aAAa,CAC5B,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EACtD,oBAAoB,CACrB,CAAC;QAEF,mDAAmD;QACnD,IAAI,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,eAAe,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;gBAClC,OAAO,eAAe,CAAC,EAAE,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,yGAAyG;QACzG,IAAI,gBAAgB,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC,cAAc,EAAE,CAAC;YACzF,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,MAAM,aAAa;gBAC5B,mDAAmD;gBACnD,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,EAC3D,sBAAsB,CACvB,CAAC;gBACF,OAAO,EAAE,CAAC,EAAE,CAAC;YACf,CAAC;YAAC,MAAM,CAAC;gBACP,4CAA4C;gBAC5C,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,WAAW;IACzB,cAAc,GAAG,SAAS,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loading with precedence:
|
|
3
|
+
* 1. --token CLI flag (highest)
|
|
4
|
+
* 2. NOTION_TOKEN environment variable
|
|
5
|
+
* 3. ~/.notion-cli.json config file
|
|
6
|
+
* 4. ./.notion-cli.json in current directory
|
|
7
|
+
*/
|
|
8
|
+
import { type CliConfig } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the Notion API token from available sources.
|
|
11
|
+
* Throws AuthError if no token is found.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveToken(cliToken?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Load config from ~/.notion-cli.json or ./.notion-cli.json.
|
|
16
|
+
* Returns undefined if no config file exists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadConfig(): CliConfig | undefined;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBtD;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,SAAS,GAAG,SAAS,CAoBlD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loading with precedence:
|
|
3
|
+
* 1. --token CLI flag (highest)
|
|
4
|
+
* 2. NOTION_TOKEN environment variable
|
|
5
|
+
* 3. ~/.notion-cli.json config file
|
|
6
|
+
* 4. ./.notion-cli.json in current directory
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { config as loadDotenv } from 'dotenv';
|
|
12
|
+
import {} from './types.js';
|
|
13
|
+
import { AuthError } from './errors.js';
|
|
14
|
+
import * as logger from '../utils/logger.js';
|
|
15
|
+
/** Load .env file if present. */
|
|
16
|
+
loadDotenv();
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the Notion API token from available sources.
|
|
19
|
+
* Throws AuthError if no token is found.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveToken(cliToken) {
|
|
22
|
+
// 1. CLI flag
|
|
23
|
+
if (cliToken !== undefined && cliToken !== '') {
|
|
24
|
+
logger.debug(`Using token from --token flag: ${logger.maskToken(cliToken)}`);
|
|
25
|
+
return cliToken;
|
|
26
|
+
}
|
|
27
|
+
// 2. Environment variable
|
|
28
|
+
const envToken = process.env['NOTION_TOKEN'];
|
|
29
|
+
if (envToken !== undefined && envToken !== '') {
|
|
30
|
+
logger.debug(`Using token from NOTION_TOKEN env var: ${logger.maskToken(envToken)}`);
|
|
31
|
+
return envToken;
|
|
32
|
+
}
|
|
33
|
+
// 3. Config file (home dir, then current dir)
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
if (config?.token !== undefined && config.token !== '') {
|
|
36
|
+
logger.debug(`Using token from config file: ${logger.maskToken(config.token)}`);
|
|
37
|
+
return config.token;
|
|
38
|
+
}
|
|
39
|
+
throw new AuthError();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load config from ~/.notion-cli.json or ./.notion-cli.json.
|
|
43
|
+
* Returns undefined if no config file exists.
|
|
44
|
+
*/
|
|
45
|
+
export function loadConfig() {
|
|
46
|
+
const paths = [
|
|
47
|
+
join(homedir(), '.notion-cli.json'),
|
|
48
|
+
join(process.cwd(), '.notion-cli.json'),
|
|
49
|
+
];
|
|
50
|
+
for (const configPath of paths) {
|
|
51
|
+
if (existsSync(configPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
logger.debug(`Loaded config from ${configPath}`);
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger.warn(`Failed to parse config at ${configPath}: ${String(err)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAkB,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,oBAAoB,CAAC;AAE7C,iCAAiC;AACjC,UAAU,EAAE,CAAC;AAEb;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAiB;IAC5C,cAAc;IACd,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,kCAAkC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7E,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,0CAA0C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,8CAA8C;IAC9C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,EAAE,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,iCAAiC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAChF,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,MAAM,IAAI,SAAS,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG;QACZ,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,CAAC;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,CAAC;KACxC,CAAC;IAEF,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;gBAC5C,MAAM,CAAC,KAAK,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;gBACjD,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV conversion utilities for Notion database rows.
|
|
3
|
+
*
|
|
4
|
+
* - Notion DB rows → CSV (export)
|
|
5
|
+
* - CSV → Notion DB row properties (import)
|
|
6
|
+
*
|
|
7
|
+
* Property type mapping:
|
|
8
|
+
* title, rich_text → string
|
|
9
|
+
* number → number
|
|
10
|
+
* select → string
|
|
11
|
+
* multi_select → semicolon-separated string
|
|
12
|
+
* date → ISO string (start / start|end for ranges)
|
|
13
|
+
* checkbox → "true" / "false"
|
|
14
|
+
* url, email, phone → string
|
|
15
|
+
* formula → computed string (read-only)
|
|
16
|
+
* relation → comma-separated page IDs
|
|
17
|
+
* rollup → computed string (read-only)
|
|
18
|
+
* status → string
|
|
19
|
+
* people → comma-separated user IDs
|
|
20
|
+
* files → comma-separated URLs
|
|
21
|
+
*/
|
|
22
|
+
type NotionPropertyValue = Record<string, unknown>;
|
|
23
|
+
type NotionPageResult = Record<string, unknown> & {
|
|
24
|
+
id: string;
|
|
25
|
+
properties: Record<string, NotionPropertyValue>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Convert Notion database query results to CSV string.
|
|
29
|
+
*/
|
|
30
|
+
export declare function rowsToCsv(rows: readonly NotionPageResult[], propertyNames: readonly string[]): string;
|
|
31
|
+
/**
|
|
32
|
+
* Parse CSV string into row objects for database import.
|
|
33
|
+
* Returns an array of { id?: string, properties: Record<string, string> }.
|
|
34
|
+
*/
|
|
35
|
+
export declare function csvToRows(csvContent: string): Array<{
|
|
36
|
+
id?: string;
|
|
37
|
+
properties: Record<string, string>;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Build Notion property objects from CSV row values.
|
|
41
|
+
* Maps string values back to Notion property types.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildPropertyValue(type: string, value: string): NotionPropertyValue | undefined;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=csv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/lib/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,KAAK,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CACjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,MAAM,CAgBR;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,GACjB,KAAK,CAAC;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAsB5D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,mBAAmB,GAAG,SAAS,CAsDjC"}
|