Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"dependencies": {
"@inquirer/prompts": "^8.3.0",
"@notionhq/client": "^5.16.0",
"@notionhq/client": "^5.17.0",
"chalk": "^5.6.0",
"commander": "^14.0.0",
"yaml": "^2.8.0"
Expand Down
50 changes: 42 additions & 8 deletions src/commands/archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,48 @@ import { parseNotionId, toUuid } from '../notion/url-parser.js';
import { formatJSON, getOutputMode } from '../output/format.js';
import { reportTokenSource } from '../output/stderr.js';

/**
* Try to archive as a page first. If that fails (e.g. the ID is a database
* or data source), fall back to trashing via dataSources.update, then
* databases.update.
*/
async function archiveEntity(
client: ReturnType<typeof createNotionClient>,
uuid: string,
): Promise<{ result: unknown; kind: 'page' | 'data_source' | 'database' }> {
try {
const result = await client.pages.update({
page_id: uuid,
in_trash: true,
});
return { result, kind: 'page' };
} catch {
// Not a page — try as data source
}

try {
const result = await client.dataSources.update({
data_source_id: uuid,
in_trash: true,
});
return { result, kind: 'data_source' };
} catch {
// Not a data source — try as database
}

const result = await client.databases.update({
database_id: uuid,
in_trash: true,
});
return { result, kind: 'database' };
}

export function archiveCommand(): Command {
const cmd = new Command('archive');

cmd
.description('archive (trash) a Notion page')
.argument('<id/url>', 'Notion page ID or URL')
.description('archive (trash) a Notion page or database')
.argument('<id/url>', 'Notion page or database ID/URL')
.action(
withErrorHandling(async (idOrUrl: string) => {
const { token, source } = await resolveToken();
Expand All @@ -21,16 +57,14 @@ export function archiveCommand(): Command {
const id = parseNotionId(idOrUrl);
const uuid = toUuid(id);

const updatedPage = await client.pages.update({
page_id: uuid,
archived: true,
});
const { result, kind } = await archiveEntity(client, uuid);

const mode = getOutputMode();
if (mode === 'json') {
process.stdout.write(`${formatJSON(updatedPage)}\n`);
process.stdout.write(`${formatJSON(result)}\n`);
} else {
process.stdout.write('Page archived.\n');
const label = kind === 'page' ? 'Page' : 'Database';
process.stdout.write(`${label} archived.\n`);
}
}),
);
Expand Down
8 changes: 6 additions & 2 deletions src/commands/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Command } from 'commander';
import { resolveToken } from '../config/token.js';
import { withErrorHandling } from '../errors/error-handler.js';
import { createNotionClient } from '../notion/client.js';
import { printOutput, setOutputMode } from '../output/format.js';
import { getOutputMode, printOutput, setOutputMode } from '../output/format.js';
import { reportTokenSource } from '../output/stderr.js';

function getTitle(item: PageObjectResponse | DataSourceObjectResponse): string {
Expand Down Expand Up @@ -101,7 +101,11 @@ export function lsCommand(): Command {
}

if (items.length === 0) {
process.stdout.write('No accessible content found\n');
if (getOutputMode() === 'json') {
process.stdout.write('[]\n');
} else {
process.stdout.write('No accessible content found\n');
}
return;
}

Expand Down
8 changes: 6 additions & 2 deletions src/commands/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Command } from 'commander';
import { resolveToken } from '../config/token.js';
import { withErrorHandling } from '../errors/error-handler.js';
import { createNotionClient } from '../notion/client.js';
import { printOutput, setOutputMode } from '../output/format.js';
import { getOutputMode, printOutput, setOutputMode } from '../output/format.js';
import { reportTokenSource } from '../output/stderr.js';

function getTitle(item: PageObjectResponse | DataSourceObjectResponse): string {
Expand Down Expand Up @@ -105,7 +105,11 @@ export function searchCommand(): Command {
) as (PageObjectResponse | DataSourceObjectResponse)[];

if (fullResults.length === 0) {
process.stdout.write(`No results found for "${query}"\n`);
if (getOutputMode() === 'json') {
process.stdout.write('[]\n');
} else {
process.stdout.write(`No results found for "${query}"\n`);
}
return;
}

Expand Down
15 changes: 9 additions & 6 deletions src/services/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ export async function fetchDatabaseSchema(
client: Client,
dbId: string,
): Promise<DatabaseSchema> {
// In Notion SDK v5, databases are exposed as "data sources"
// client.dataSources.retrieve() returns DataSourceObjectResponse with .properties
const ds = await client.dataSources.retrieve({ data_source_id: dbId });
// Resolve the data source ID — the input may be a data source ID or a database page ID.
// resolveDataSourceId handles the fallback (try as data source, then as database).
const resolvedId = await resolveDataSourceId(client, dbId);
const ds = await client.dataSources.retrieve({ data_source_id: resolvedId });

// Only full data sources have title and properties
const title =
'title' in ds ? ds.title.map((rt) => rt.plain_text).join('') || dbId : dbId;
'title' in ds
? ds.title.map((rt) => rt.plain_text).join('') || resolvedId
: resolvedId;

const properties: Record<string, DatabasePropertyConfig> = {};

Expand Down Expand Up @@ -85,9 +88,9 @@ export async function fetchDatabaseSchema(
typeof ds.parent === 'object' &&
'database_id' in ds.parent
? (ds.parent as { database_id: string }).database_id
: dbId;
: resolvedId;

return { id: dbId, databaseId, title, properties };
return { id: resolvedId, databaseId, title, properties };
}

export async function queryDatabase(
Expand Down
4 changes: 2 additions & 2 deletions tests/commands/archive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ describe('archiveCommand', () => {
setOutputMode('auto');
});

it('calls client.pages.update with archived: true', async () => {
it('calls client.pages.update with in_trash: true', async () => {
const cmd = archiveCommand();
await cmd.parseAsync(['node', 'test', 'b55c9c91384d452b81dbd1ef79372b75']);

expect(mockPagesUpdate).toHaveBeenCalledWith({
page_id: 'b55c9c91-384d-452b-81db-d1ef79372b75',
archived: true,
in_trash: true,
});
});

Expand Down
Loading