@infograb/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 +1386 -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/batch/retrieve.d.ts +43 -0
- package/dist/commands/batch/retrieve.js +265 -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 +94 -0
- package/dist/commands/block/retrieve/children.d.ts +31 -0
- package/dist/commands/block/retrieve/children.js +174 -0
- package/dist/commands/block/retrieve.d.ts +30 -0
- package/dist/commands/block/retrieve.js +98 -0
- package/dist/commands/block/update.d.ts +45 -0
- package/dist/commands/block/update.js +241 -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 +30 -0
- package/dist/commands/config/set-token.js +201 -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 +355 -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 +57 -0
- package/dist/commands/init.js +471 -0
- package/dist/commands/list.d.ts +29 -0
- package/dist/commands/list.js +184 -0
- package/dist/commands/page/create.d.ts +33 -0
- package/dist/commands/page/create.js +240 -0
- package/dist/commands/page/retrieve/property_item.d.ts +24 -0
- package/dist/commands/page/retrieve/property_item.js +72 -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/search.d.ts +40 -0
- package/dist/commands/search.js +348 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.js +183 -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 +570 -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/disk-cache.d.ts +80 -0
- package/dist/utils/disk-cache.js +291 -0
- package/dist/utils/markdown-to-blocks.d.ts +19 -0
- package/dist/utils/markdown-to-blocks.js +259 -0
- package/dist/utils/notion-resolver.d.ts +48 -0
- package/dist/utils/notion-resolver.js +262 -0
- package/dist/utils/notion-url-parser.d.ts +46 -0
- package/dist/utils/notion-url-parser.js +111 -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/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +122 -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 +55 -0
- package/dist/utils/token-validator.js +85 -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 +4497 -0
- package/package.json +115 -0
- package/scripts/banner.js +38 -0
- package/scripts/postinstall.js +56 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Table formatting utility to replace oclif v2's ux.table
|
|
4
|
+
* Provides backward-compatible table flags and formatting
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.tableFlags = void 0;
|
|
8
|
+
exports.formatTable = formatTable;
|
|
9
|
+
const core_1 = require("@oclif/core");
|
|
10
|
+
const Table = require("cli-table3");
|
|
11
|
+
/**
|
|
12
|
+
* Table flags compatible with oclif v2's ux.table.flags()
|
|
13
|
+
*/
|
|
14
|
+
exports.tableFlags = {
|
|
15
|
+
columns: core_1.Flags.string({
|
|
16
|
+
description: 'Only show provided columns (comma-separated)',
|
|
17
|
+
exclusive: ['extended'],
|
|
18
|
+
}),
|
|
19
|
+
sort: core_1.Flags.string({
|
|
20
|
+
description: 'Property to sort by (prepend with - for descending)',
|
|
21
|
+
}),
|
|
22
|
+
filter: core_1.Flags.string({
|
|
23
|
+
description: 'Filter property by substring match',
|
|
24
|
+
}),
|
|
25
|
+
csv: core_1.Flags.boolean({
|
|
26
|
+
description: 'Output in CSV format',
|
|
27
|
+
exclusive: ['no-truncate'],
|
|
28
|
+
}),
|
|
29
|
+
extended: core_1.Flags.boolean({
|
|
30
|
+
char: 'x',
|
|
31
|
+
description: 'Show extra columns',
|
|
32
|
+
}),
|
|
33
|
+
'no-truncate': core_1.Flags.boolean({
|
|
34
|
+
description: 'Do not truncate output to fit screen',
|
|
35
|
+
exclusive: ['csv'],
|
|
36
|
+
}),
|
|
37
|
+
'no-header': core_1.Flags.boolean({
|
|
38
|
+
description: 'Hide table header from output',
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Format and display a table (compatible with oclif v2's ux.table)
|
|
43
|
+
*/
|
|
44
|
+
function formatTable(data, columns, options = {}) {
|
|
45
|
+
if (data.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const printLine = options.printLine || console.log;
|
|
49
|
+
// Filter columns based on options
|
|
50
|
+
let selectedColumns = Object.keys(columns);
|
|
51
|
+
if (options.columns) {
|
|
52
|
+
const requestedCols = options.columns.split(',').map(c => c.trim());
|
|
53
|
+
selectedColumns = selectedColumns.filter(col => requestedCols.includes(col));
|
|
54
|
+
}
|
|
55
|
+
if (!options.extended) {
|
|
56
|
+
selectedColumns = selectedColumns.filter(col => !columns[col].extended);
|
|
57
|
+
}
|
|
58
|
+
// Filter rows
|
|
59
|
+
let filteredData = data;
|
|
60
|
+
if (options.filter) {
|
|
61
|
+
const [filterCol, filterVal] = options.filter.split('=');
|
|
62
|
+
if (filterVal) {
|
|
63
|
+
filteredData = data.filter(row => {
|
|
64
|
+
var _a;
|
|
65
|
+
const val = ((_a = columns[filterCol]) === null || _a === void 0 ? void 0 : _a.get) ? columns[filterCol].get(row) : row[filterCol];
|
|
66
|
+
return String(val).includes(filterVal);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Sort data
|
|
71
|
+
if (options.sort) {
|
|
72
|
+
const descending = options.sort.startsWith('-');
|
|
73
|
+
const sortCol = descending ? options.sort.slice(1) : options.sort;
|
|
74
|
+
filteredData = [...filteredData].sort((a, b) => {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
const aVal = ((_a = columns[sortCol]) === null || _a === void 0 ? void 0 : _a.get) ? columns[sortCol].get(a) : a[sortCol];
|
|
77
|
+
const bVal = ((_b = columns[sortCol]) === null || _b === void 0 ? void 0 : _b.get) ? columns[sortCol].get(b) : b[sortCol];
|
|
78
|
+
const comparison = String(aVal).localeCompare(String(bVal));
|
|
79
|
+
return descending ? -comparison : comparison;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Output as CSV
|
|
83
|
+
if (options.csv) {
|
|
84
|
+
if (!options['no-header']) {
|
|
85
|
+
const headers = selectedColumns.map(col => columns[col].header || col);
|
|
86
|
+
printLine(headers.join(','));
|
|
87
|
+
}
|
|
88
|
+
filteredData.forEach(row => {
|
|
89
|
+
const values = selectedColumns.map(col => {
|
|
90
|
+
const val = columns[col].get ? columns[col].get(row) : row[col];
|
|
91
|
+
const str = String(val || '');
|
|
92
|
+
// Escape CSV values
|
|
93
|
+
return str.includes(',') || str.includes('"') ? `"${str.replace(/"/g, '""')}"` : str;
|
|
94
|
+
});
|
|
95
|
+
printLine(values.join(','));
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Output as table
|
|
100
|
+
const headers = selectedColumns.map(col => columns[col].header || col);
|
|
101
|
+
const table = new Table({
|
|
102
|
+
head: options['no-header'] ? [] : headers,
|
|
103
|
+
style: {
|
|
104
|
+
head: ['cyan'],
|
|
105
|
+
border: ['gray']
|
|
106
|
+
},
|
|
107
|
+
wordWrap: !options['no-truncate'],
|
|
108
|
+
colWidths: selectedColumns.map(col => {
|
|
109
|
+
if (options['no-truncate'])
|
|
110
|
+
return undefined;
|
|
111
|
+
return columns[col].minWidth || undefined;
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
filteredData.forEach(row => {
|
|
115
|
+
const values = selectedColumns.map(col => {
|
|
116
|
+
const val = columns[col].get ? columns[col].get(row) : row[col];
|
|
117
|
+
return String(val !== undefined && val !== null ? val : '');
|
|
118
|
+
});
|
|
119
|
+
table.push(values);
|
|
120
|
+
});
|
|
121
|
+
printLine(table.toString());
|
|
122
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal banner and color utilities for consistent branding
|
|
3
|
+
* Used across postinstall script and init command
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* ANSI color codes for cross-platform terminal compatibility
|
|
7
|
+
*/
|
|
8
|
+
export declare const colors: {
|
|
9
|
+
readonly reset: "\u001B[0m";
|
|
10
|
+
readonly bright: "\u001B[1m";
|
|
11
|
+
readonly dim: "\u001B[2m";
|
|
12
|
+
readonly green: "\u001B[32m";
|
|
13
|
+
readonly blue: "\u001B[34m";
|
|
14
|
+
readonly cyan: "\u001B[36m";
|
|
15
|
+
readonly gray: "\u001B[90m";
|
|
16
|
+
readonly yellow: "\u001B[33m";
|
|
17
|
+
readonly magenta: "\u001B[35m";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* ASCII art banner for Notion CLI
|
|
21
|
+
* Displayed during install and setup
|
|
22
|
+
* Uses terminal's default color (black/white depending on theme)
|
|
23
|
+
*/
|
|
24
|
+
export declare const ASCII_BANNER = "\n\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\n\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\n\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\n\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\n\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\n\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\n";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Terminal banner and color utilities for consistent branding
|
|
4
|
+
* Used across postinstall script and init command
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ASCII_BANNER = exports.colors = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* ANSI color codes for cross-platform terminal compatibility
|
|
10
|
+
*/
|
|
11
|
+
exports.colors = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bright: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
cyan: '\x1b[36m',
|
|
18
|
+
gray: '\x1b[90m',
|
|
19
|
+
yellow: '\x1b[33m',
|
|
20
|
+
magenta: '\x1b[35m',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ASCII art banner for Notion CLI
|
|
24
|
+
* Displayed during install and setup
|
|
25
|
+
* Uses terminal's default color (black/white depending on theme)
|
|
26
|
+
*/
|
|
27
|
+
exports.ASCII_BANNER = `
|
|
28
|
+
███╗ ██╗ ██████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██████╗██╗ ██╗
|
|
29
|
+
████╗ ██║██╔═══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██║ ██║
|
|
30
|
+
██╔██╗ ██║██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║ ██║ ██║ ██║
|
|
31
|
+
██║╚██╗██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██║ ██║ ██║
|
|
32
|
+
██║ ╚████║╚██████╔╝ ██║ ██║╚██████╔╝██║ ╚████║ ╚██████╗███████╗██║
|
|
33
|
+
╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝
|
|
34
|
+
`;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Validation Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent token validation across all commands that interact with the Notion API.
|
|
5
|
+
* This ensures users get helpful, actionable error messages before attempting API calls.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Masks a Notion token for safe display in logs and console output
|
|
9
|
+
*
|
|
10
|
+
* Shows only the prefix and last 3 characters to prevent token leakage
|
|
11
|
+
* in screen recordings, terminal sharing, or logs.
|
|
12
|
+
*
|
|
13
|
+
* @param token - The token to mask
|
|
14
|
+
* @returns Masked token string (e.g., "secret_***...***abc")
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const token = "secret_1234567890abcdef"
|
|
19
|
+
* const masked = maskToken(token)
|
|
20
|
+
* // Returns: "secret_***...***def"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function maskToken(token: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Validates that NOTION_TOKEN environment variable is set
|
|
26
|
+
*
|
|
27
|
+
* @throws {NotionCLIError} If token is not set, throws with helpful suggestions
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { validateNotionToken } from '../utils/token-validator'
|
|
32
|
+
*
|
|
33
|
+
* // In your command's run() method:
|
|
34
|
+
* async run() {
|
|
35
|
+
* const { flags } = await this.parse(MyCommand)
|
|
36
|
+
* validateNotionToken() // Throws if token not set
|
|
37
|
+
*
|
|
38
|
+
* // Continue with API calls...
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Valid Notion token prefixes.
|
|
44
|
+
* - "secret_": Internal integration tokens (legacy format, still valid)
|
|
45
|
+
* - "ntn_": New token format introduced September 2024
|
|
46
|
+
*/
|
|
47
|
+
export declare const VALID_TOKEN_PREFIXES: readonly ["secret_", "ntn_"];
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether a token string has a recognized Notion token prefix.
|
|
50
|
+
*
|
|
51
|
+
* @param token - The token to check
|
|
52
|
+
* @returns true if the token starts with a known Notion prefix
|
|
53
|
+
*/
|
|
54
|
+
export declare function hasValidTokenPrefix(token: string): boolean;
|
|
55
|
+
export declare function validateNotionToken(): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token Validation Utility
|
|
4
|
+
*
|
|
5
|
+
* Provides consistent token validation across all commands that interact with the Notion API.
|
|
6
|
+
* This ensures users get helpful, actionable error messages before attempting API calls.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.VALID_TOKEN_PREFIXES = void 0;
|
|
10
|
+
exports.maskToken = maskToken;
|
|
11
|
+
exports.hasValidTokenPrefix = hasValidTokenPrefix;
|
|
12
|
+
exports.validateNotionToken = validateNotionToken;
|
|
13
|
+
const errors_1 = require("../errors");
|
|
14
|
+
/**
|
|
15
|
+
* Masks a Notion token for safe display in logs and console output
|
|
16
|
+
*
|
|
17
|
+
* Shows only the prefix and last 3 characters to prevent token leakage
|
|
18
|
+
* in screen recordings, terminal sharing, or logs.
|
|
19
|
+
*
|
|
20
|
+
* @param token - The token to mask
|
|
21
|
+
* @returns Masked token string (e.g., "secret_***...***abc")
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const token = "secret_1234567890abcdef"
|
|
26
|
+
* const masked = maskToken(token)
|
|
27
|
+
* // Returns: "secret_***...***def"
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function maskToken(token) {
|
|
31
|
+
if (!token)
|
|
32
|
+
return "";
|
|
33
|
+
if (token.length <= 10) {
|
|
34
|
+
// Token too short to safely mask, obscure completely
|
|
35
|
+
// Threshold: 10 chars ensures at least 3 chars are masked after prefix+suffix
|
|
36
|
+
return "***";
|
|
37
|
+
}
|
|
38
|
+
// Show prefix (secret_ or ntn_) and last 3 chars
|
|
39
|
+
// For unknown prefixes: use max 4 chars to ensure at least 4 chars are masked
|
|
40
|
+
const prefix = token.startsWith("secret_")
|
|
41
|
+
? "secret_"
|
|
42
|
+
: token.startsWith("ntn_")
|
|
43
|
+
? "ntn_"
|
|
44
|
+
: token.slice(0, Math.min(4, token.length - 7));
|
|
45
|
+
const suffix = token.slice(-3);
|
|
46
|
+
return `${prefix}***...***${suffix}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validates that NOTION_TOKEN environment variable is set
|
|
50
|
+
*
|
|
51
|
+
* @throws {NotionCLIError} If token is not set, throws with helpful suggestions
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { validateNotionToken } from '../utils/token-validator'
|
|
56
|
+
*
|
|
57
|
+
* // In your command's run() method:
|
|
58
|
+
* async run() {
|
|
59
|
+
* const { flags } = await this.parse(MyCommand)
|
|
60
|
+
* validateNotionToken() // Throws if token not set
|
|
61
|
+
*
|
|
62
|
+
* // Continue with API calls...
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* Valid Notion token prefixes.
|
|
68
|
+
* - "secret_": Internal integration tokens (legacy format, still valid)
|
|
69
|
+
* - "ntn_": New token format introduced September 2024
|
|
70
|
+
*/
|
|
71
|
+
exports.VALID_TOKEN_PREFIXES = ["secret_", "ntn_"];
|
|
72
|
+
/**
|
|
73
|
+
* Checks whether a token string has a recognized Notion token prefix.
|
|
74
|
+
*
|
|
75
|
+
* @param token - The token to check
|
|
76
|
+
* @returns true if the token starts with a known Notion prefix
|
|
77
|
+
*/
|
|
78
|
+
function hasValidTokenPrefix(token) {
|
|
79
|
+
return exports.VALID_TOKEN_PREFIXES.some((prefix) => token.startsWith(prefix));
|
|
80
|
+
}
|
|
81
|
+
function validateNotionToken() {
|
|
82
|
+
if (!process.env.NOTION_TOKEN) {
|
|
83
|
+
throw errors_1.NotionCLIErrorFactory.tokenMissing();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update notifier utility
|
|
3
|
+
* Checks for new versions of notion-cli and notifies users non-intrusively
|
|
4
|
+
*
|
|
5
|
+
* Runs asynchronously in background, doesn't block CLI execution
|
|
6
|
+
* Caches results for 1 day to avoid unnecessary npm registry checks
|
|
7
|
+
* Respects NO_UPDATE_NOTIFIER environment variable and CI environments
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Check for updates and notify user if a new version is available
|
|
11
|
+
*
|
|
12
|
+
* This runs asynchronously and won't block CLI execution.
|
|
13
|
+
* Checks are cached for 1 day by default.
|
|
14
|
+
*
|
|
15
|
+
* Set DEBUG=1 environment variable to see error messages if update check fails.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```bash
|
|
19
|
+
* # Silent mode (default)
|
|
20
|
+
* notion-cli --version
|
|
21
|
+
*
|
|
22
|
+
* # Debug mode
|
|
23
|
+
* DEBUG=1 notion-cli --version
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkForUpdates(): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Update notifier utility
|
|
4
|
+
* Checks for new versions of notion-cli and notifies users non-intrusively
|
|
5
|
+
*
|
|
6
|
+
* Runs asynchronously in background, doesn't block CLI execution
|
|
7
|
+
* Caches results for 1 day to avoid unnecessary npm registry checks
|
|
8
|
+
* Respects NO_UPDATE_NOTIFIER environment variable and CI environments
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.checkForUpdates = checkForUpdates;
|
|
12
|
+
/**
|
|
13
|
+
* Check for updates and notify user if a new version is available
|
|
14
|
+
*
|
|
15
|
+
* This runs asynchronously and won't block CLI execution.
|
|
16
|
+
* Checks are cached for 1 day by default.
|
|
17
|
+
*
|
|
18
|
+
* Set DEBUG=1 environment variable to see error messages if update check fails.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```bash
|
|
22
|
+
* # Silent mode (default)
|
|
23
|
+
* notion-cli --version
|
|
24
|
+
*
|
|
25
|
+
* # Debug mode
|
|
26
|
+
* DEBUG=1 notion-cli --version
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function checkForUpdates() {
|
|
30
|
+
try {
|
|
31
|
+
// Load dependencies dynamically to avoid rootDir issues
|
|
32
|
+
const updateNotifier = require('update-notifier').default || require('update-notifier');
|
|
33
|
+
const packageJson = require('../../package.json');
|
|
34
|
+
// Initialize update notifier with package info
|
|
35
|
+
const notifier = updateNotifier({
|
|
36
|
+
pkg: packageJson,
|
|
37
|
+
updateCheckInterval: 1000 * 60 * 60 * 24, // Check once per day
|
|
38
|
+
});
|
|
39
|
+
// Show notification if update is available
|
|
40
|
+
// This displays a yellow-bordered box with update info
|
|
41
|
+
notifier.notify({
|
|
42
|
+
defer: true, // Show notification after command completes (non-intrusive)
|
|
43
|
+
isGlobal: true, // This is a global CLI tool
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
// Silently fail - don't break CLI if update check fails
|
|
48
|
+
// This could happen if npm registry is unreachable, network issues, etc.
|
|
49
|
+
// Debug mode: Show error details for troubleshooting
|
|
50
|
+
if (process.env.DEBUG) {
|
|
51
|
+
console.error('Update check failed:', error instanceof Error ? error.message : error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Cache Utility
|
|
3
|
+
*
|
|
4
|
+
* Manages persistent caching of workspace databases for fast name-to-ID resolution.
|
|
5
|
+
* Cache is stored at ~/.notion-cli/databases.json
|
|
6
|
+
*/
|
|
7
|
+
import { GetDataSourceResponse, DataSourceObjectResponse } from '@notionhq/client/build/src/api-endpoints';
|
|
8
|
+
export interface CachedDatabase {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
titleNormalized: string;
|
|
12
|
+
aliases: string[];
|
|
13
|
+
url?: string;
|
|
14
|
+
lastEditedTime?: string;
|
|
15
|
+
properties?: Record<string, any>;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkspaceCache {
|
|
18
|
+
version: string;
|
|
19
|
+
lastSync: string;
|
|
20
|
+
databases: CachedDatabase[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the cache directory path
|
|
24
|
+
*/
|
|
25
|
+
export declare function getCacheDir(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Get the cache file path
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCachePath(): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Ensure cache directory exists
|
|
32
|
+
*/
|
|
33
|
+
export declare function ensureCacheDir(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Load cache from disk
|
|
36
|
+
* Returns null if cache doesn't exist or is corrupted
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadCache(): Promise<WorkspaceCache | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Save cache to disk (atomic write)
|
|
41
|
+
*/
|
|
42
|
+
export declare function saveCache(data: WorkspaceCache): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Generate search aliases from a database title
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* generateAliases('Tasks Database')
|
|
48
|
+
* // Returns: ['tasks database', 'tasks', 'task', 'tasks db', 'task db', 'td']
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateAliases(title: string): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Build a cached database entry from a data source response
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildCacheEntry(dataSource: GetDataSourceResponse | DataSourceObjectResponse): CachedDatabase;
|
|
55
|
+
/**
|
|
56
|
+
* Create an empty cache
|
|
57
|
+
*/
|
|
58
|
+
export declare function createEmptyCache(): WorkspaceCache;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workspace Cache Utility
|
|
4
|
+
*
|
|
5
|
+
* Manages persistent caching of workspace databases for fast name-to-ID resolution.
|
|
6
|
+
* Cache is stored at ~/.notion-cli/databases.json
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getCacheDir = getCacheDir;
|
|
10
|
+
exports.getCachePath = getCachePath;
|
|
11
|
+
exports.ensureCacheDir = ensureCacheDir;
|
|
12
|
+
exports.loadCache = loadCache;
|
|
13
|
+
exports.saveCache = saveCache;
|
|
14
|
+
exports.generateAliases = generateAliases;
|
|
15
|
+
exports.buildCacheEntry = buildCacheEntry;
|
|
16
|
+
exports.createEmptyCache = createEmptyCache;
|
|
17
|
+
const fs = require("fs/promises");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const os = require("os");
|
|
20
|
+
const client_1 = require("@notionhq/client");
|
|
21
|
+
const CACHE_VERSION = '1.0.0';
|
|
22
|
+
const CACHE_DIR_NAME = '.notion-cli';
|
|
23
|
+
const CACHE_FILE_NAME = 'databases.json';
|
|
24
|
+
/**
|
|
25
|
+
* Get the cache directory path
|
|
26
|
+
*/
|
|
27
|
+
function getCacheDir() {
|
|
28
|
+
return path.join(os.homedir(), CACHE_DIR_NAME);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the cache file path
|
|
32
|
+
*/
|
|
33
|
+
async function getCachePath() {
|
|
34
|
+
return path.join(getCacheDir(), CACHE_FILE_NAME);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ensure cache directory exists
|
|
38
|
+
*/
|
|
39
|
+
async function ensureCacheDir() {
|
|
40
|
+
const cacheDir = getCacheDir();
|
|
41
|
+
try {
|
|
42
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.code !== 'EEXIST') {
|
|
46
|
+
throw new Error(`Failed to create cache directory: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Load cache from disk
|
|
52
|
+
* Returns null if cache doesn't exist or is corrupted
|
|
53
|
+
*/
|
|
54
|
+
async function loadCache() {
|
|
55
|
+
try {
|
|
56
|
+
const cachePath = await getCachePath();
|
|
57
|
+
const content = await fs.readFile(cachePath, 'utf-8');
|
|
58
|
+
const cache = JSON.parse(content);
|
|
59
|
+
// Validate cache structure
|
|
60
|
+
if (!cache.version || !Array.isArray(cache.databases)) {
|
|
61
|
+
console.warn('Cache file is corrupted, will rebuild on next sync');
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return cache;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code === 'ENOENT') {
|
|
68
|
+
// Cache doesn't exist yet
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
// Parse error or other error
|
|
72
|
+
console.warn(`Failed to load cache: ${error.message}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Save cache to disk (atomic write)
|
|
78
|
+
*/
|
|
79
|
+
async function saveCache(data) {
|
|
80
|
+
await ensureCacheDir();
|
|
81
|
+
const cachePath = await getCachePath();
|
|
82
|
+
const tmpPath = `${cachePath}.tmp`;
|
|
83
|
+
try {
|
|
84
|
+
// Write to temporary file
|
|
85
|
+
await fs.writeFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
86
|
+
// Atomic rename (replaces old file)
|
|
87
|
+
await fs.rename(tmpPath, cachePath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Clean up temp file if it exists
|
|
91
|
+
try {
|
|
92
|
+
await fs.unlink(tmpPath);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Intentionally empty - cache directory may not exist
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Failed to save cache: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generate search aliases from a database title
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* generateAliases('Tasks Database')
|
|
105
|
+
* // Returns: ['tasks database', 'tasks', 'task', 'tasks db', 'task db', 'td']
|
|
106
|
+
*/
|
|
107
|
+
function generateAliases(title) {
|
|
108
|
+
const aliases = new Set();
|
|
109
|
+
const normalized = title.toLowerCase().trim();
|
|
110
|
+
// Add full title (normalized)
|
|
111
|
+
aliases.add(normalized);
|
|
112
|
+
// Add title without common suffixes
|
|
113
|
+
const withoutSuffixes = normalized.replace(/\s+(database|db|table|list|tracker|log)$/i, '');
|
|
114
|
+
if (withoutSuffixes !== normalized) {
|
|
115
|
+
aliases.add(withoutSuffixes);
|
|
116
|
+
}
|
|
117
|
+
// Add title with common suffixes
|
|
118
|
+
if (withoutSuffixes !== normalized) {
|
|
119
|
+
aliases.add(`${withoutSuffixes} db`);
|
|
120
|
+
aliases.add(`${withoutSuffixes} database`);
|
|
121
|
+
}
|
|
122
|
+
// Add singular/plural variants
|
|
123
|
+
if (withoutSuffixes.endsWith('s')) {
|
|
124
|
+
aliases.add(withoutSuffixes.slice(0, -1)); // Remove 's'
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
aliases.add(`${withoutSuffixes}s`); // Add 's'
|
|
128
|
+
}
|
|
129
|
+
// Add acronym if multi-word (e.g., "Meeting Notes" → "mn")
|
|
130
|
+
const words = withoutSuffixes.split(/\s+/);
|
|
131
|
+
if (words.length > 1) {
|
|
132
|
+
const acronym = words.map(w => w[0]).join('');
|
|
133
|
+
if (acronym.length >= 2) {
|
|
134
|
+
aliases.add(acronym);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return Array.from(aliases);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build a cached database entry from a data source response
|
|
141
|
+
*/
|
|
142
|
+
function buildCacheEntry(dataSource) {
|
|
143
|
+
let title = 'Untitled';
|
|
144
|
+
let properties = {};
|
|
145
|
+
let url;
|
|
146
|
+
let lastEditedTime;
|
|
147
|
+
if ((0, client_1.isFullDataSource)(dataSource)) {
|
|
148
|
+
if (dataSource.title && dataSource.title.length > 0) {
|
|
149
|
+
title = dataSource.title[0].plain_text;
|
|
150
|
+
}
|
|
151
|
+
// Extract property schema
|
|
152
|
+
if (dataSource.properties) {
|
|
153
|
+
properties = dataSource.properties;
|
|
154
|
+
}
|
|
155
|
+
// Extract URL if available
|
|
156
|
+
if ('url' in dataSource) {
|
|
157
|
+
url = dataSource.url;
|
|
158
|
+
}
|
|
159
|
+
// Extract last edited time
|
|
160
|
+
if ('last_edited_time' in dataSource) {
|
|
161
|
+
lastEditedTime = dataSource.last_edited_time;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const titleNormalized = title.toLowerCase().trim();
|
|
165
|
+
const aliases = generateAliases(title);
|
|
166
|
+
return {
|
|
167
|
+
id: dataSource.id,
|
|
168
|
+
title,
|
|
169
|
+
titleNormalized,
|
|
170
|
+
aliases,
|
|
171
|
+
url,
|
|
172
|
+
lastEditedTime,
|
|
173
|
+
properties,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Create an empty cache
|
|
178
|
+
*/
|
|
179
|
+
function createEmptyCache() {
|
|
180
|
+
return {
|
|
181
|
+
version: CACHE_VERSION,
|
|
182
|
+
lastSync: new Date().toISOString(),
|
|
183
|
+
databases: [],
|
|
184
|
+
};
|
|
185
|
+
}
|