@catafal/notion-cli 5.9.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/LICENSE +21 -0
- package/README.md +552 -0
- package/bin/dev +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/run +14 -0
- package/bin/run.cmd +3 -0
- package/dist/base-command.d.ts +73 -0
- package/dist/base-command.js +179 -0
- package/dist/base-flags.d.ts +14 -0
- package/dist/base-flags.js +59 -0
- package/dist/cache.d.ts +84 -0
- package/dist/cache.js +351 -0
- package/dist/commands/append.d.ts +37 -0
- package/dist/commands/append.js +120 -0
- package/dist/commands/batch/delete.d.ts +42 -0
- package/dist/commands/batch/delete.js +199 -0
- package/dist/commands/batch/retrieve.d.ts +43 -0
- package/dist/commands/batch/retrieve.js +272 -0
- package/dist/commands/block/append.d.ts +42 -0
- package/dist/commands/block/append.js +219 -0
- package/dist/commands/block/delete.d.ts +30 -0
- package/dist/commands/block/delete.js +97 -0
- package/dist/commands/block/retrieve/children.d.ts +31 -0
- package/dist/commands/block/retrieve/children.js +177 -0
- package/dist/commands/block/retrieve.d.ts +30 -0
- package/dist/commands/block/retrieve.js +101 -0
- package/dist/commands/block/update.d.ts +45 -0
- package/dist/commands/block/update.js +242 -0
- package/dist/commands/bookmark/list.d.ts +30 -0
- package/dist/commands/bookmark/list.js +60 -0
- package/dist/commands/bookmark/remove.d.ts +26 -0
- package/dist/commands/bookmark/remove.js +47 -0
- package/dist/commands/bookmark/set.d.ts +29 -0
- package/dist/commands/bookmark/set.js +96 -0
- package/dist/commands/browse.d.ts +13 -0
- package/dist/commands/browse.js +44 -0
- package/dist/commands/cache/info.d.ts +19 -0
- package/dist/commands/cache/info.js +145 -0
- package/dist/commands/config/set-token.d.ts +22 -0
- package/dist/commands/config/set-token.js +137 -0
- package/dist/commands/daily/index.d.ts +32 -0
- package/dist/commands/daily/index.js +135 -0
- package/dist/commands/daily/setup.d.ts +42 -0
- package/dist/commands/daily/setup.js +149 -0
- package/dist/commands/db/create.d.ts +31 -0
- package/dist/commands/db/create.js +124 -0
- package/dist/commands/db/query.d.ts +41 -0
- package/dist/commands/db/query.js +360 -0
- package/dist/commands/db/retrieve.d.ts +33 -0
- package/dist/commands/db/retrieve.js +134 -0
- package/dist/commands/db/schema.d.ts +32 -0
- package/dist/commands/db/schema.js +308 -0
- package/dist/commands/db/update.d.ts +31 -0
- package/dist/commands/db/update.js +117 -0
- package/dist/commands/doctor.d.ts +50 -0
- package/dist/commands/doctor.js +420 -0
- package/dist/commands/init.d.ts +65 -0
- package/dist/commands/init.js +479 -0
- package/dist/commands/list.d.ts +29 -0
- package/dist/commands/list.js +219 -0
- package/dist/commands/open.d.ts +29 -0
- package/dist/commands/open.js +100 -0
- package/dist/commands/page/create.d.ts +33 -0
- package/dist/commands/page/create.js +261 -0
- package/dist/commands/page/delete.d.ts +36 -0
- package/dist/commands/page/delete.js +107 -0
- package/dist/commands/page/export.d.ts +38 -0
- package/dist/commands/page/export.js +120 -0
- package/dist/commands/page/retrieve/property_item.d.ts +24 -0
- package/dist/commands/page/retrieve/property_item.js +75 -0
- package/dist/commands/page/retrieve.d.ts +36 -0
- package/dist/commands/page/retrieve.js +244 -0
- package/dist/commands/page/update.d.ts +34 -0
- package/dist/commands/page/update.js +184 -0
- package/dist/commands/quick.d.ts +35 -0
- package/dist/commands/quick.js +168 -0
- package/dist/commands/search.d.ts +43 -0
- package/dist/commands/search.js +361 -0
- package/dist/commands/stats.d.ts +35 -0
- package/dist/commands/stats.js +274 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.js +183 -0
- package/dist/commands/template/get.d.ts +28 -0
- package/dist/commands/template/get.js +59 -0
- package/dist/commands/template/list.d.ts +32 -0
- package/dist/commands/template/list.js +62 -0
- package/dist/commands/template/remove.d.ts +27 -0
- package/dist/commands/template/remove.js +48 -0
- package/dist/commands/template/save.d.ts +32 -0
- package/dist/commands/template/save.js +92 -0
- package/dist/commands/template/use.d.ts +34 -0
- package/dist/commands/template/use.js +142 -0
- package/dist/commands/user/list.d.ts +27 -0
- package/dist/commands/user/list.js +99 -0
- package/dist/commands/user/retrieve/bot.d.ts +28 -0
- package/dist/commands/user/retrieve/bot.js +96 -0
- package/dist/commands/user/retrieve.d.ts +30 -0
- package/dist/commands/user/retrieve.js +103 -0
- package/dist/commands/whoami.d.ts +19 -0
- package/dist/commands/whoami.js +175 -0
- package/dist/deduplication.d.ts +41 -0
- package/dist/deduplication.js +71 -0
- package/dist/envelope.d.ts +169 -0
- package/dist/envelope.js +257 -0
- package/dist/errors/enhanced-errors.d.ts +168 -0
- package/dist/errors/enhanced-errors.js +567 -0
- package/dist/errors/index.d.ts +18 -0
- package/dist/errors/index.js +33 -0
- package/dist/examples/cache-retry-examples.d.ts +64 -0
- package/dist/examples/cache-retry-examples.js +375 -0
- package/dist/helper.d.ts +102 -0
- package/dist/helper.js +885 -0
- package/dist/http-agent.d.ts +38 -0
- package/dist/http-agent.js +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/interface.d.ts +4 -0
- package/dist/interface.js +2 -0
- package/dist/notion.d.ts +144 -0
- package/dist/notion.js +547 -0
- package/dist/retry.d.ts +72 -0
- package/dist/retry.js +381 -0
- package/dist/utils/bookmarks.d.ts +32 -0
- package/dist/utils/bookmarks.js +98 -0
- package/dist/utils/daily-config.d.ts +22 -0
- package/dist/utils/daily-config.js +60 -0
- package/dist/utils/disk-cache.d.ts +80 -0
- package/dist/utils/disk-cache.js +291 -0
- package/dist/utils/fuzzy.d.ts +36 -0
- package/dist/utils/fuzzy.js +69 -0
- package/dist/utils/interactive-navigator.d.ts +63 -0
- package/dist/utils/interactive-navigator.js +123 -0
- package/dist/utils/markdown-to-blocks.d.ts +21 -0
- package/dist/utils/markdown-to-blocks.js +333 -0
- package/dist/utils/notion-resolver.d.ts +49 -0
- package/dist/utils/notion-resolver.js +278 -0
- package/dist/utils/notion-url-parser.d.ts +48 -0
- package/dist/utils/notion-url-parser.js +121 -0
- package/dist/utils/property-expander.d.ts +45 -0
- package/dist/utils/property-expander.js +323 -0
- package/dist/utils/schema-examples.d.ts +40 -0
- package/dist/utils/schema-examples.js +359 -0
- package/dist/utils/schema-extractor.d.ts +65 -0
- package/dist/utils/schema-extractor.js +235 -0
- package/dist/utils/shell-config.d.ts +30 -0
- package/dist/utils/shell-config.js +84 -0
- package/dist/utils/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +125 -0
- package/dist/utils/templates.d.ts +30 -0
- package/dist/utils/templates.js +82 -0
- package/dist/utils/terminal-banner.d.ts +24 -0
- package/dist/utils/terminal-banner.js +34 -0
- package/dist/utils/token-validator.d.ts +42 -0
- package/dist/utils/token-validator.js +66 -0
- package/dist/utils/update-notifier.d.ts +26 -0
- package/dist/utils/update-notifier.js +54 -0
- package/dist/utils/workspace-cache.d.ts +58 -0
- package/dist/utils/workspace-cache.js +185 -0
- package/oclif.manifest.json +6471 -0
- package/package.json +118 -0
- package/scripts/banner.js +38 -0
- package/scripts/postinstall.js +44 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced AI-Friendly Error Handling System
|
|
4
|
+
*
|
|
5
|
+
* Provides context-rich errors with actionable suggestions for:
|
|
6
|
+
* - AI assistants debugging automation failures
|
|
7
|
+
* - Human users troubleshooting CLI issues
|
|
8
|
+
* - Automated systems logging meaningful errors
|
|
9
|
+
*
|
|
10
|
+
* Key Features:
|
|
11
|
+
* - Error codes for programmatic handling
|
|
12
|
+
* - Contextual suggestions with fix commands
|
|
13
|
+
* - Support for both human and JSON output
|
|
14
|
+
* - Notion API error mapping
|
|
15
|
+
* - Common scenario detection
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.NotionCLIErrorFactory = exports.NotionCLIError = exports.NotionCLIErrorCode = void 0;
|
|
19
|
+
exports.wrapNotionError = wrapNotionError;
|
|
20
|
+
exports.handleCliError = handleCliError;
|
|
21
|
+
/**
|
|
22
|
+
* Comprehensive error codes covering all common scenarios
|
|
23
|
+
*/
|
|
24
|
+
var NotionCLIErrorCode;
|
|
25
|
+
(function (NotionCLIErrorCode) {
|
|
26
|
+
// Authentication & Authorization
|
|
27
|
+
NotionCLIErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
28
|
+
NotionCLIErrorCode["TOKEN_MISSING"] = "TOKEN_MISSING";
|
|
29
|
+
NotionCLIErrorCode["TOKEN_INVALID"] = "TOKEN_INVALID";
|
|
30
|
+
NotionCLIErrorCode["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
|
|
31
|
+
NotionCLIErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
32
|
+
NotionCLIErrorCode["INTEGRATION_NOT_SHARED"] = "INTEGRATION_NOT_SHARED";
|
|
33
|
+
// Resource Errors
|
|
34
|
+
NotionCLIErrorCode["NOT_FOUND"] = "NOT_FOUND";
|
|
35
|
+
NotionCLIErrorCode["OBJECT_NOT_FOUND"] = "OBJECT_NOT_FOUND";
|
|
36
|
+
NotionCLIErrorCode["DATABASE_NOT_FOUND"] = "DATABASE_NOT_FOUND";
|
|
37
|
+
NotionCLIErrorCode["PAGE_NOT_FOUND"] = "PAGE_NOT_FOUND";
|
|
38
|
+
NotionCLIErrorCode["BLOCK_NOT_FOUND"] = "BLOCK_NOT_FOUND";
|
|
39
|
+
// ID Format & Validation
|
|
40
|
+
NotionCLIErrorCode["INVALID_ID_FORMAT"] = "INVALID_ID_FORMAT";
|
|
41
|
+
NotionCLIErrorCode["INVALID_DATABASE_ID"] = "INVALID_DATABASE_ID";
|
|
42
|
+
NotionCLIErrorCode["INVALID_PAGE_ID"] = "INVALID_PAGE_ID";
|
|
43
|
+
NotionCLIErrorCode["INVALID_BLOCK_ID"] = "INVALID_BLOCK_ID";
|
|
44
|
+
NotionCLIErrorCode["INVALID_URL"] = "INVALID_URL";
|
|
45
|
+
// Common Confusions
|
|
46
|
+
NotionCLIErrorCode["DATABASE_ID_CONFUSION"] = "DATABASE_ID_CONFUSION";
|
|
47
|
+
NotionCLIErrorCode["WORKSPACE_VS_DATABASE"] = "WORKSPACE_VS_DATABASE";
|
|
48
|
+
// API & Network
|
|
49
|
+
NotionCLIErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
|
|
50
|
+
NotionCLIErrorCode["API_ERROR"] = "API_ERROR";
|
|
51
|
+
NotionCLIErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
52
|
+
NotionCLIErrorCode["TIMEOUT"] = "TIMEOUT";
|
|
53
|
+
NotionCLIErrorCode["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
54
|
+
// Validation Errors
|
|
55
|
+
NotionCLIErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
56
|
+
NotionCLIErrorCode["INVALID_PROPERTY"] = "INVALID_PROPERTY";
|
|
57
|
+
NotionCLIErrorCode["INVALID_FILTER"] = "INVALID_FILTER";
|
|
58
|
+
NotionCLIErrorCode["INVALID_JSON"] = "INVALID_JSON";
|
|
59
|
+
NotionCLIErrorCode["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD";
|
|
60
|
+
// Cache & State
|
|
61
|
+
NotionCLIErrorCode["CACHE_ERROR"] = "CACHE_ERROR";
|
|
62
|
+
NotionCLIErrorCode["WORKSPACE_NOT_SYNCED"] = "WORKSPACE_NOT_SYNCED";
|
|
63
|
+
// General
|
|
64
|
+
NotionCLIErrorCode["UNKNOWN"] = "UNKNOWN";
|
|
65
|
+
NotionCLIErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
66
|
+
})(NotionCLIErrorCode || (exports.NotionCLIErrorCode = NotionCLIErrorCode = {}));
|
|
67
|
+
/**
|
|
68
|
+
* Enhanced CLI Error with AI-friendly formatting
|
|
69
|
+
*/
|
|
70
|
+
class NotionCLIError extends Error {
|
|
71
|
+
constructor(code, userMessage, suggestions = [], context = {}) {
|
|
72
|
+
super(userMessage);
|
|
73
|
+
this.name = 'NotionCLIError';
|
|
74
|
+
this.code = code;
|
|
75
|
+
this.userMessage = userMessage;
|
|
76
|
+
this.suggestions = suggestions;
|
|
77
|
+
this.context = context;
|
|
78
|
+
this.timestamp = new Date().toISOString();
|
|
79
|
+
// Maintain proper stack trace
|
|
80
|
+
if (Error.captureStackTrace) {
|
|
81
|
+
Error.captureStackTrace(this, NotionCLIError);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format error for human-readable console output
|
|
86
|
+
*/
|
|
87
|
+
toHumanString() {
|
|
88
|
+
const parts = [];
|
|
89
|
+
// Error header with code
|
|
90
|
+
parts.push(`\n❌ ${this.userMessage}`);
|
|
91
|
+
parts.push(` Error Code: ${this.code}`);
|
|
92
|
+
// Add context if available
|
|
93
|
+
if (this.context.attemptedId) {
|
|
94
|
+
parts.push(` Attempted ID: ${this.context.attemptedId}`);
|
|
95
|
+
}
|
|
96
|
+
if (this.context.resourceType) {
|
|
97
|
+
parts.push(` Resource Type: ${this.context.resourceType}`);
|
|
98
|
+
}
|
|
99
|
+
// Add suggestions
|
|
100
|
+
if (this.suggestions.length > 0) {
|
|
101
|
+
parts.push('\n💡 Possible causes and fixes:');
|
|
102
|
+
this.suggestions.forEach((suggestion, index) => {
|
|
103
|
+
parts.push(` ${index + 1}. ${suggestion.description}`);
|
|
104
|
+
if (suggestion.command) {
|
|
105
|
+
parts.push(` $ ${suggestion.command}`);
|
|
106
|
+
}
|
|
107
|
+
if (suggestion.link) {
|
|
108
|
+
parts.push(` 🔗 ${suggestion.link}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Add debug context in debug mode (redact tokens to prevent leakage)
|
|
113
|
+
if (process.env.DEBUG && this.context.originalError) {
|
|
114
|
+
parts.push('\n🐛 Debug Information:');
|
|
115
|
+
const sanitized = JSON.stringify(this.context.originalError, null, 2)
|
|
116
|
+
.replace(/secret_[a-zA-Z0-9_-]+/g, 'secret_[REDACTED]')
|
|
117
|
+
.replace(/ntn_[a-zA-Z0-9_-]+/g, 'ntn_[REDACTED]');
|
|
118
|
+
parts.push(` ${sanitized}`);
|
|
119
|
+
}
|
|
120
|
+
parts.push(''); // Empty line at end
|
|
121
|
+
return parts.join('\n');
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Format error for JSON output (automation-friendly)
|
|
125
|
+
*/
|
|
126
|
+
toJSON() {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: {
|
|
130
|
+
code: this.code,
|
|
131
|
+
message: this.userMessage,
|
|
132
|
+
suggestions: this.suggestions,
|
|
133
|
+
context: this.context,
|
|
134
|
+
timestamp: this.timestamp,
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Format error for compact JSON (single-line)
|
|
140
|
+
*/
|
|
141
|
+
toCompactJSON() {
|
|
142
|
+
return JSON.stringify(this.toJSON());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.NotionCLIError = NotionCLIError;
|
|
146
|
+
/**
|
|
147
|
+
* Error factory functions for common scenarios
|
|
148
|
+
*/
|
|
149
|
+
class NotionCLIErrorFactory {
|
|
150
|
+
/**
|
|
151
|
+
* Token is missing or not set
|
|
152
|
+
*/
|
|
153
|
+
static tokenMissing() {
|
|
154
|
+
return new NotionCLIError(NotionCLIErrorCode.TOKEN_MISSING, 'NOTION_TOKEN environment variable is not set', [
|
|
155
|
+
{
|
|
156
|
+
description: 'Set your Notion integration token using the config command',
|
|
157
|
+
command: 'notion-cli config set-token'
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
description: 'Or export it manually (Mac/Linux)',
|
|
161
|
+
command: 'export NOTION_TOKEN="ntn_your_token_here"'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
description: 'Or set it manually (Windows PowerShell)',
|
|
165
|
+
command: '$env:NOTION_TOKEN="ntn_your_token_here"'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
description: 'Get your integration token from Notion',
|
|
169
|
+
link: 'https://developers.notion.com/docs/create-a-notion-integration'
|
|
170
|
+
}
|
|
171
|
+
], { metadata: { tokenSet: false } });
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Token is invalid or expired
|
|
175
|
+
*/
|
|
176
|
+
static tokenInvalid() {
|
|
177
|
+
return new NotionCLIError(NotionCLIErrorCode.TOKEN_INVALID, 'Authentication failed - your NOTION_TOKEN is invalid or expired', [
|
|
178
|
+
{
|
|
179
|
+
description: 'Verify your integration still exists and is active',
|
|
180
|
+
link: 'https://www.notion.so/my-integrations'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
description: 'Generate a new internal integration token',
|
|
184
|
+
link: 'https://developers.notion.com/docs/create-a-notion-integration'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
description: 'Update your token using the config command',
|
|
188
|
+
command: 'notion-cli config set-token'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
description: 'Check if the integration has been removed or revoked by workspace admin',
|
|
192
|
+
}
|
|
193
|
+
], { metadata: { tokenSet: true } });
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Integration not shared with resource
|
|
197
|
+
*/
|
|
198
|
+
static integrationNotShared(resourceType, resourceId) {
|
|
199
|
+
return new NotionCLIError(NotionCLIErrorCode.INTEGRATION_NOT_SHARED, `Your integration doesn't have access to this ${resourceType}`, [
|
|
200
|
+
{
|
|
201
|
+
description: `Open the ${resourceType} in Notion and click the "..." menu`,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
description: 'Select "Add connections" or "Connect to"',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
description: 'Choose your integration from the list',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
description: 'If you don\'t see your integration, verify it exists',
|
|
211
|
+
link: 'https://www.notion.so/my-integrations'
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
description: 'Learn more about sharing with integrations',
|
|
215
|
+
link: 'https://developers.notion.com/docs/create-a-notion-integration#give-your-integration-page-permissions'
|
|
216
|
+
}
|
|
217
|
+
], {
|
|
218
|
+
resourceType,
|
|
219
|
+
attemptedId: resourceId,
|
|
220
|
+
statusCode: 403
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Database/Page/Block not found
|
|
225
|
+
*/
|
|
226
|
+
static resourceNotFound(resourceType, identifier) {
|
|
227
|
+
const isId = /^[a-f0-9]{32}$/i.test(identifier.replace(/-/g, ''));
|
|
228
|
+
return new NotionCLIError(NotionCLIErrorCode.OBJECT_NOT_FOUND, `${resourceType.charAt(0).toUpperCase() + resourceType.slice(1)} not found: ${identifier}`, isId ? [
|
|
229
|
+
{
|
|
230
|
+
description: `The integration may not have access — open the ${resourceType} in Notion → "..." menu → "Add connections" → select your integration`,
|
|
231
|
+
link: 'https://developers.notion.com/docs/create-a-notion-integration#give-your-integration-page-permissions'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
description: 'The ID may be incorrect - verify it in Notion',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
description: 'The resource may have been deleted or archived',
|
|
238
|
+
}
|
|
239
|
+
] : [
|
|
240
|
+
{
|
|
241
|
+
description: 'Run sync to refresh your workspace database index',
|
|
242
|
+
command: 'notion-cli sync'
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
description: 'List all available databases to find the correct name',
|
|
246
|
+
command: 'notion-cli list'
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
description: 'Try using the database ID or URL instead of name',
|
|
250
|
+
command: `notion-cli ${resourceType === 'database' ? 'db' : resourceType} retrieve <ID_OR_URL>`
|
|
251
|
+
}
|
|
252
|
+
], {
|
|
253
|
+
resourceType,
|
|
254
|
+
attemptedId: identifier,
|
|
255
|
+
userInput: identifier,
|
|
256
|
+
statusCode: 404
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Invalid ID format
|
|
261
|
+
*/
|
|
262
|
+
static invalidIdFormat(input, resourceType) {
|
|
263
|
+
return new NotionCLIError(NotionCLIErrorCode.INVALID_ID_FORMAT, `Invalid ${resourceType || 'resource'} ID format: ${input}`, [
|
|
264
|
+
{
|
|
265
|
+
description: 'Notion IDs are 32 hexadecimal characters (with or without dashes)',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
description: 'Valid format: 1fb79d4c71bb8032b722c82305b63a00',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
description: 'Valid format: 1fb79d4c-71bb-8032-b722-c82305b63a00',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
description: 'Try using the full Notion URL instead',
|
|
275
|
+
command: `notion-cli ${resourceType === 'database' ? 'db' : resourceType || 'page'} retrieve https://notion.so/your-url-here`
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
description: 'Or find the resource by name after syncing',
|
|
279
|
+
command: 'notion-cli sync && notion-cli list'
|
|
280
|
+
}
|
|
281
|
+
], {
|
|
282
|
+
resourceType,
|
|
283
|
+
userInput: input,
|
|
284
|
+
attemptedId: input
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Common confusion: using database_id when data_source_id is needed
|
|
289
|
+
*/
|
|
290
|
+
static databaseIdConfusion(attemptedId) {
|
|
291
|
+
return new NotionCLIError(NotionCLIErrorCode.DATABASE_ID_CONFUSION, 'Notion API v5 uses "data_source_id" for databases, not "database_id"', [
|
|
292
|
+
{
|
|
293
|
+
description: 'This CLI automatically handles the conversion - you can use either',
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
description: 'If you copied this from Notion API docs, the ID itself is still valid',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
description: 'Verify the database exists and is shared with your integration',
|
|
300
|
+
command: 'notion-cli list'
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
description: 'Try retrieving the database to check access',
|
|
304
|
+
command: `notion-cli db retrieve ${attemptedId}`
|
|
305
|
+
}
|
|
306
|
+
], {
|
|
307
|
+
resourceType: 'database',
|
|
308
|
+
attemptedId,
|
|
309
|
+
metadata: { apiVersion: '5.2.1' }
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Workspace not synced (cache miss for name resolution)
|
|
314
|
+
*/
|
|
315
|
+
static workspaceNotSynced(databaseName) {
|
|
316
|
+
return new NotionCLIError(NotionCLIErrorCode.WORKSPACE_NOT_SYNCED, `Database "${databaseName}" not found in workspace cache`, [
|
|
317
|
+
{
|
|
318
|
+
description: 'Run sync to index all accessible databases in your workspace',
|
|
319
|
+
command: 'notion-cli sync'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
description: 'After syncing, list all databases to verify it was found',
|
|
323
|
+
command: 'notion-cli list'
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
description: 'If sync doesn\'t find it, the integration may not have access',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
description: 'Try using the database ID or URL directly instead of name',
|
|
330
|
+
command: 'notion-cli db retrieve <DATABASE_ID_OR_URL>'
|
|
331
|
+
}
|
|
332
|
+
], {
|
|
333
|
+
resourceType: 'database',
|
|
334
|
+
userInput: databaseName,
|
|
335
|
+
metadata: { cacheState: 'not_synced' }
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Rate limited by Notion API
|
|
340
|
+
*/
|
|
341
|
+
static rateLimited(retryAfter) {
|
|
342
|
+
return new NotionCLIError(NotionCLIErrorCode.RATE_LIMITED, 'Rate limited by Notion API - too many requests', [
|
|
343
|
+
{
|
|
344
|
+
description: retryAfter
|
|
345
|
+
? `Wait ${retryAfter} seconds before retrying`
|
|
346
|
+
: 'Wait a few seconds before retrying',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
description: 'The CLI will automatically retry with exponential backoff',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
description: 'Consider using --page-size flag to reduce API calls',
|
|
353
|
+
command: 'notion-cli db query <ID> --page-size 100'
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
description: 'Learn about Notion API rate limits',
|
|
357
|
+
link: 'https://developers.notion.com/reference/request-limits'
|
|
358
|
+
}
|
|
359
|
+
], {
|
|
360
|
+
statusCode: 429,
|
|
361
|
+
metadata: { retryAfter }
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Invalid JSON in filter or property value
|
|
366
|
+
*/
|
|
367
|
+
static invalidJson(jsonString, parseError) {
|
|
368
|
+
return new NotionCLIError(NotionCLIErrorCode.INVALID_JSON, 'Failed to parse JSON input', [
|
|
369
|
+
{
|
|
370
|
+
description: 'Check for common JSON syntax errors: missing quotes, trailing commas, unclosed brackets',
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
description: 'Use a JSON validator to check your syntax',
|
|
374
|
+
link: 'https://jsonlint.com/'
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
description: 'For filters, you can use a filter file instead of inline JSON',
|
|
378
|
+
command: 'notion-cli db query <ID> --file-filter ./filter.json'
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
description: 'See filter examples in the documentation',
|
|
382
|
+
link: 'https://developers.notion.com/reference/post-database-query-filter'
|
|
383
|
+
}
|
|
384
|
+
], {
|
|
385
|
+
userInput: jsonString,
|
|
386
|
+
originalError: parseError,
|
|
387
|
+
metadata: { parseError: parseError.message }
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Invalid property name or type
|
|
392
|
+
*/
|
|
393
|
+
static invalidProperty(propertyName, databaseId) {
|
|
394
|
+
return new NotionCLIError(NotionCLIErrorCode.INVALID_PROPERTY, `Property "${propertyName}" not found or invalid`, [
|
|
395
|
+
{
|
|
396
|
+
description: 'Get the database schema to see all available properties',
|
|
397
|
+
command: databaseId
|
|
398
|
+
? `notion-cli db schema ${databaseId}`
|
|
399
|
+
: 'notion-cli db schema <DATABASE_ID>'
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
description: 'Property names are case-sensitive - check exact spelling',
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
description: 'Some property types don\'t support all operations',
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
description: 'View the full database structure',
|
|
409
|
+
command: databaseId
|
|
410
|
+
? `notion-cli db retrieve ${databaseId} --raw`
|
|
411
|
+
: 'notion-cli db retrieve <DATABASE_ID> --raw'
|
|
412
|
+
}
|
|
413
|
+
], {
|
|
414
|
+
resourceType: 'database',
|
|
415
|
+
attemptedId: databaseId,
|
|
416
|
+
userInput: propertyName,
|
|
417
|
+
metadata: { propertyName }
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Network or connection error
|
|
422
|
+
*/
|
|
423
|
+
static networkError(originalError) {
|
|
424
|
+
return new NotionCLIError(NotionCLIErrorCode.NETWORK_ERROR, 'Network error - unable to reach Notion API', [
|
|
425
|
+
{
|
|
426
|
+
description: 'Check your internet connection',
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
description: 'Verify Notion API status',
|
|
430
|
+
link: 'https://status.notion.so/'
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
description: 'The CLI will automatically retry transient network errors',
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
description: 'If behind a proxy, ensure it\'s configured correctly',
|
|
437
|
+
}
|
|
438
|
+
], {
|
|
439
|
+
statusCode: 0,
|
|
440
|
+
originalError,
|
|
441
|
+
metadata: { errorCode: originalError.code }
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
exports.NotionCLIErrorFactory = NotionCLIErrorFactory;
|
|
446
|
+
/**
|
|
447
|
+
* Map Notion API errors to CLI errors with context
|
|
448
|
+
*/
|
|
449
|
+
function wrapNotionError(error, context = {}) {
|
|
450
|
+
var _a, _b, _c;
|
|
451
|
+
// Already a NotionCLIError
|
|
452
|
+
if (error instanceof NotionCLIError) {
|
|
453
|
+
return error;
|
|
454
|
+
}
|
|
455
|
+
// Handle Notion API errors
|
|
456
|
+
if (error.code) {
|
|
457
|
+
// const _notionError = error as APIResponseError
|
|
458
|
+
switch (error.code) {
|
|
459
|
+
case 'unauthorized':
|
|
460
|
+
case 'restricted_resource':
|
|
461
|
+
return NotionCLIErrorFactory.tokenInvalid();
|
|
462
|
+
case 'object_not_found':
|
|
463
|
+
// Only pass valid resource types to resourceNotFound
|
|
464
|
+
if (context.resourceType && ['database', 'page', 'block'].includes(context.resourceType)) {
|
|
465
|
+
return NotionCLIErrorFactory.resourceNotFound(context.resourceType, context.attemptedId || context.userInput || 'unknown');
|
|
466
|
+
}
|
|
467
|
+
return NotionCLIErrorFactory.resourceNotFound('database', context.attemptedId || context.userInput || 'unknown');
|
|
468
|
+
case 'validation_error':
|
|
469
|
+
if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('invalid json')) {
|
|
470
|
+
return NotionCLIErrorFactory.invalidJson(context.userInput || '', error);
|
|
471
|
+
}
|
|
472
|
+
return new NotionCLIError(NotionCLIErrorCode.VALIDATION_ERROR, error.message || 'Validation error', [
|
|
473
|
+
{
|
|
474
|
+
description: 'Check the API documentation for correct parameter format',
|
|
475
|
+
link: 'https://developers.notion.com/reference/intro'
|
|
476
|
+
}
|
|
477
|
+
], { ...context, originalError: error });
|
|
478
|
+
case 'rate_limited': {
|
|
479
|
+
const retryAfter = parseInt(((_b = error.headers) === null || _b === void 0 ? void 0 : _b['retry-after']) || '60', 10);
|
|
480
|
+
return NotionCLIErrorFactory.rateLimited(retryAfter);
|
|
481
|
+
}
|
|
482
|
+
case 'conflict_error':
|
|
483
|
+
return new NotionCLIError(NotionCLIErrorCode.API_ERROR, 'Conflict error - the resource is being modified by another request', [
|
|
484
|
+
{
|
|
485
|
+
description: 'Wait a moment and try again',
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
description: 'The CLI will automatically retry this operation',
|
|
489
|
+
}
|
|
490
|
+
], { ...context, originalError: error });
|
|
491
|
+
case 'service_unavailable':
|
|
492
|
+
return new NotionCLIError(NotionCLIErrorCode.SERVICE_UNAVAILABLE, 'Notion API is temporarily unavailable', [
|
|
493
|
+
{
|
|
494
|
+
description: 'Check Notion API status',
|
|
495
|
+
link: 'https://status.notion.so/'
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
description: 'The CLI will automatically retry this operation',
|
|
499
|
+
}
|
|
500
|
+
], { ...context, statusCode: 503, originalError: error });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Handle HTTP status codes
|
|
504
|
+
if (error.status) {
|
|
505
|
+
switch (error.status) {
|
|
506
|
+
case 401:
|
|
507
|
+
case 403: {
|
|
508
|
+
const isTokenMissing = !process.env.NOTION_TOKEN;
|
|
509
|
+
return isTokenMissing
|
|
510
|
+
? NotionCLIErrorFactory.tokenMissing()
|
|
511
|
+
: NotionCLIErrorFactory.tokenInvalid();
|
|
512
|
+
}
|
|
513
|
+
case 404:
|
|
514
|
+
// Only pass valid resource types to resourceNotFound
|
|
515
|
+
if (context.resourceType && ['database', 'page', 'block'].includes(context.resourceType)) {
|
|
516
|
+
return NotionCLIErrorFactory.resourceNotFound(context.resourceType, context.attemptedId || context.userInput || 'unknown');
|
|
517
|
+
}
|
|
518
|
+
return new NotionCLIError(NotionCLIErrorCode.NOT_FOUND, 'Resource not found', [], { ...context, statusCode: 404, originalError: error });
|
|
519
|
+
case 429: {
|
|
520
|
+
const retryAfter = parseInt(((_c = error.headers) === null || _c === void 0 ? void 0 : _c['retry-after']) || '60', 10);
|
|
521
|
+
return NotionCLIErrorFactory.rateLimited(retryAfter);
|
|
522
|
+
}
|
|
523
|
+
case 500:
|
|
524
|
+
case 502:
|
|
525
|
+
case 503:
|
|
526
|
+
case 504:
|
|
527
|
+
return new NotionCLIError(NotionCLIErrorCode.SERVICE_UNAVAILABLE, 'Notion API is experiencing issues', [
|
|
528
|
+
{
|
|
529
|
+
description: 'This is a temporary server error',
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
description: 'The CLI will automatically retry',
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
description: 'Check Notion API status',
|
|
536
|
+
link: 'https://status.notion.so/'
|
|
537
|
+
}
|
|
538
|
+
], { ...context, statusCode: error.status, originalError: error });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Handle network errors
|
|
542
|
+
if (error.code && ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN'].includes(error.code)) {
|
|
543
|
+
return NotionCLIErrorFactory.networkError(error);
|
|
544
|
+
}
|
|
545
|
+
// Generic error
|
|
546
|
+
return new NotionCLIError(NotionCLIErrorCode.UNKNOWN, error.message || 'An unexpected error occurred', [
|
|
547
|
+
{
|
|
548
|
+
description: 'If this error persists, please report it',
|
|
549
|
+
link: 'https://github.com/Catafal/notion-cli/issues'
|
|
550
|
+
}
|
|
551
|
+
], { ...context, originalError: error });
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Handle CLI errors with proper formatting based on output mode
|
|
555
|
+
*/
|
|
556
|
+
function handleCliError(error, outputJson = false, context = {}) {
|
|
557
|
+
const cliError = error instanceof NotionCLIError
|
|
558
|
+
? error
|
|
559
|
+
: wrapNotionError(error, context);
|
|
560
|
+
if (outputJson) {
|
|
561
|
+
console.log(JSON.stringify(cliError.toJSON(), null, 2));
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
console.error(cliError.toHumanString());
|
|
565
|
+
}
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling System - Clean Exports
|
|
3
|
+
*
|
|
4
|
+
* Central import point for all error-related functionality.
|
|
5
|
+
* Use this for clean imports in command files:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import {
|
|
10
|
+
* NotionCLIError,
|
|
11
|
+
* NotionCLIErrorCode,
|
|
12
|
+
* NotionCLIErrorFactory,
|
|
13
|
+
* handleCliError,
|
|
14
|
+
* wrapNotionError
|
|
15
|
+
* } from '../errors'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export { NotionCLIError, NotionCLIErrorCode, NotionCLIErrorFactory, wrapNotionError, handleCliError, ErrorSuggestion, ErrorContext, } from './enhanced-errors';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error Handling System - Clean Exports
|
|
4
|
+
*
|
|
5
|
+
* Central import point for all error-related functionality.
|
|
6
|
+
* Use this for clean imports in command files:
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import {
|
|
11
|
+
* NotionCLIError,
|
|
12
|
+
* NotionCLIErrorCode,
|
|
13
|
+
* NotionCLIErrorFactory,
|
|
14
|
+
* handleCliError,
|
|
15
|
+
* wrapNotionError
|
|
16
|
+
* } from '../errors'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.handleCliError = exports.wrapNotionError = exports.NotionCLIErrorFactory = exports.NotionCLIErrorCode = exports.NotionCLIError = void 0;
|
|
21
|
+
// Export enhanced error system
|
|
22
|
+
var enhanced_errors_1 = require("./enhanced-errors");
|
|
23
|
+
// Error Class
|
|
24
|
+
Object.defineProperty(exports, "NotionCLIError", { enumerable: true, get: function () { return enhanced_errors_1.NotionCLIError; } });
|
|
25
|
+
// Error Codes Enum
|
|
26
|
+
Object.defineProperty(exports, "NotionCLIErrorCode", { enumerable: true, get: function () { return enhanced_errors_1.NotionCLIErrorCode; } });
|
|
27
|
+
// Factory Functions
|
|
28
|
+
Object.defineProperty(exports, "NotionCLIErrorFactory", { enumerable: true, get: function () { return enhanced_errors_1.NotionCLIErrorFactory; } });
|
|
29
|
+
// Utility Functions
|
|
30
|
+
Object.defineProperty(exports, "wrapNotionError", { enumerable: true, get: function () { return enhanced_errors_1.wrapNotionError; } });
|
|
31
|
+
Object.defineProperty(exports, "handleCliError", { enumerable: true, get: function () { return enhanced_errors_1.handleCliError; } });
|
|
32
|
+
// Note: Legacy error system is in src/errors.ts
|
|
33
|
+
// Commands should import from this file (src/errors/index.ts) to get enhanced errors
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Examples demonstrating enhanced retry logic and caching layer
|
|
3
|
+
*
|
|
4
|
+
* This file provides practical examples of how to use the new features.
|
|
5
|
+
* These examples can be adapted to your specific use cases.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Example 1: Basic usage with automatic caching and retry
|
|
9
|
+
*/
|
|
10
|
+
export declare function example1_basicUsage(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Example 2: Monitoring cache performance
|
|
13
|
+
*/
|
|
14
|
+
export declare function example2_cacheStats(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Example 3: Manual cache invalidation
|
|
17
|
+
*/
|
|
18
|
+
export declare function example3_cacheInvalidation(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Example 4: Custom retry configuration
|
|
21
|
+
*/
|
|
22
|
+
export declare function example4_customRetry(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Example 5: Circuit breaker pattern
|
|
25
|
+
*/
|
|
26
|
+
export declare function example5_circuitBreaker(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Example 6: Batch operations with retry
|
|
29
|
+
*/
|
|
30
|
+
export declare function example6_batchOperations(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Example 7: Error categorization
|
|
33
|
+
*/
|
|
34
|
+
export declare function example7_errorCategorization(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Example 8: Delay calculation visualization
|
|
37
|
+
*/
|
|
38
|
+
export declare function example8_delayCalculation(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Example 9: Production-ready pattern
|
|
41
|
+
*/
|
|
42
|
+
export declare function example9_productionPattern(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Example 10: Configuration showcase
|
|
45
|
+
*/
|
|
46
|
+
export declare function example10_configurationShowcase(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Run all examples
|
|
49
|
+
*/
|
|
50
|
+
export declare function runAllExamples(): Promise<void>;
|
|
51
|
+
declare const _default: {
|
|
52
|
+
example1_basicUsage: typeof example1_basicUsage;
|
|
53
|
+
example2_cacheStats: typeof example2_cacheStats;
|
|
54
|
+
example3_cacheInvalidation: typeof example3_cacheInvalidation;
|
|
55
|
+
example4_customRetry: typeof example4_customRetry;
|
|
56
|
+
example5_circuitBreaker: typeof example5_circuitBreaker;
|
|
57
|
+
example6_batchOperations: typeof example6_batchOperations;
|
|
58
|
+
example7_errorCategorization: typeof example7_errorCategorization;
|
|
59
|
+
example8_delayCalculation: typeof example8_delayCalculation;
|
|
60
|
+
example9_productionPattern: typeof example9_productionPattern;
|
|
61
|
+
example10_configurationShowcase: typeof example10_configurationShowcase;
|
|
62
|
+
runAllExamples: typeof runAllExamples;
|
|
63
|
+
};
|
|
64
|
+
export default _default;
|