Skip to content

Commit 4599a28

Browse files
committed
feat: provide the essential procedures for generating nodes
1 parent 22c949d commit 4599a28

File tree

5 files changed

+417
-0
lines changed

5 files changed

+417
-0
lines changed

gatsby-node.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Proxy to TypeScript-compiled output
2+
module.exports = require('./lib/gatsby-node');

source/gatsby-node.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* *** MIT LICENSE ***
3+
* -------------------------------------------------------------------------
4+
* This code may be modified and distributed under the MIT license.
5+
* See the LICENSE file for details.
6+
* -------------------------------------------------------------------------
7+
*
8+
* @summary A Gatsby source plugin for Notion
9+
*
10+
* @author Alvis HT Tang <alvis@hilbert.space>
11+
* @license MIT
12+
* @copyright Copyright (c) 2021 - All Rights Reserved.
13+
* -------------------------------------------------------------------------
14+
*/
15+
16+
import { version as gatsbyVersion } from 'gatsby/package.json';
17+
18+
import { name } from '#.';
19+
import { Notion } from '#client';
20+
import { getDatabases, getPages } from '#plugin';
21+
import { NodeManager } from '#node';
22+
23+
import type { GatsbyNode } from 'gatsby';
24+
25+
import { PluginConfig } from '#plugin';
26+
27+
/* eslint-disable jsdoc/require-param, jsdoc/require-returns */
28+
29+
/** Define a schema for the options using Joi to validate the options users pass to the plugin. */
30+
export const pluginOptionsSchema: NonNullable<
31+
GatsbyNode['pluginOptionsSchema']
32+
> = ({ Joi: joi }) => {
33+
return joi.object({
34+
token: joi.string().optional(),
35+
version: joi.string().optional(),
36+
databases: joi.array().items(joi.string()).optional(),
37+
pages: joi.array().items(joi.string()).optional(),
38+
});
39+
};
40+
41+
// see https://www.gatsbyjs.com/docs/conceptual/overview-of-the-gatsby-build-process
42+
43+
export const onPreBootstrap: NonNullable<GatsbyNode['onPreBootstrap']> = async (
44+
args,
45+
) => {
46+
const { reporter } = args;
47+
48+
const MINIMUM_SUPPORTED_VERSION = 3;
49+
if (Number(gatsbyVersion.split('.')[0]) !== MINIMUM_SUPPORTED_VERSION) {
50+
reporter.panic(`[${name}] unsupported gatsby version detected`);
51+
}
52+
};
53+
54+
export const sourceNodes: NonNullable<GatsbyNode['sourceNodes']> = async (
55+
args,
56+
pluginConfig: PluginConfig,
57+
) => {
58+
const client = new Notion(pluginConfig);
59+
60+
// getting entries from notion
61+
const databases = await getDatabases(client, pluginConfig);
62+
const pages = await getPages(client, pluginConfig);
63+
for (const database of databases) {
64+
pages.push(...database.pages);
65+
}
66+
67+
// update nodes
68+
const manager = new NodeManager(args);
69+
manager.update([...databases, ...pages]);
70+
};
71+
72+
/* eslint-enable */

source/plugin.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* *** MIT LICENSE ***
3+
* -------------------------------------------------------------------------
4+
* This code may be modified and distributed under the MIT license.
5+
* See the LICENSE file for details.
6+
* -------------------------------------------------------------------------
7+
*
8+
* @summary Procedure for the source plugin
9+
*
10+
* @author Alvis HT Tang <alvis@hilbert.space>
11+
* @license MIT
12+
* @copyright Copyright (c) 2021 - All Rights Reserved.
13+
* -------------------------------------------------------------------------
14+
*/
15+
16+
import { Notion } from '#client';
17+
18+
import type { PluginOptions } from 'gatsby';
19+
20+
import type { NotionOptions } from '#client';
21+
import type { FullDatabase, FullPage } from '#types';
22+
23+
/** options for the source plugin */
24+
export interface PluginConfig extends PluginOptions, NotionOptions {
25+
/** id of databases to be sourced, default to be all shared databases */
26+
databases?: string[];
27+
/** id of pages to be sourced, default to be all shared pages */
28+
pages?: string[];
29+
}
30+
31+
/**
32+
* gat relevant databases from Notion
33+
* @param client a Notion client
34+
* @param pluginConfig pluginConfig passed from the plugin options
35+
* @returns a list of databases
36+
*/
37+
export async function getDatabases(
38+
client: Notion,
39+
pluginConfig: PluginConfig,
40+
): Promise<FullDatabase[]> {
41+
const databases: FullDatabase[] = [];
42+
43+
const databaseIDs = [
44+
...(pluginConfig.databases ?? []),
45+
...(process.env['GATSBY_NOTION_DATABASES']?.split(/, +/) ?? []),
46+
];
47+
48+
for (const databaseID of databaseIDs) {
49+
const database = await client.getDatabase(databaseID);
50+
databases.push(database);
51+
}
52+
53+
return databases;
54+
}
55+
56+
/**
57+
* gat relevant pages from Notion
58+
* @param client a Notion client
59+
* @param pluginConfig pluginConfig passed from the plugin options
60+
* @returns a list of pages
61+
*/
62+
export async function getPages(
63+
client: Notion,
64+
pluginConfig: PluginConfig,
65+
): Promise<FullPage[]> {
66+
const pages: FullPage[] = [];
67+
68+
const pageIDs = [
69+
...(pluginConfig.pages ?? []),
70+
...(process.env['GATSBY_NOTION_PAGES']?.split(/, +/) ?? []),
71+
];
72+
73+
for (const pageID of pageIDs) {
74+
pages.push(await client.getPage(pageID));
75+
}
76+
77+
return pages;
78+
}

spec/gatsby-node.spec.ts

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* *** MIT LICENSE ***
3+
* -------------------------------------------------------------------------
4+
* This code may be modified and distributed under the MIT license.
5+
* See the LICENSE file for details.
6+
* -------------------------------------------------------------------------
7+
*
8+
* @summary Tests on node generation
9+
*
10+
* @author Alvis HT Tang <alvis@hilbert.space>
11+
* @license MIT
12+
* @copyright Copyright (c) 2021 - All Rights Reserved.
13+
* -------------------------------------------------------------------------
14+
*/
15+
16+
import gatsbyPackage from 'gatsby/package.json';
17+
import joi from 'joi';
18+
19+
import { pluginOptionsSchema, sourceNodes } from '#gatsby-node';
20+
21+
import { mockDatabase, mockPage } from './mock';
22+
23+
jest.mock('gatsby/package.json', () => {
24+
return { version: '3.0.0' };
25+
});
26+
27+
const mockUpdate = jest.fn();
28+
jest.mock('#node', () => ({
29+
__esModule: true,
30+
NodeManager: jest.fn().mockImplementation(() => {
31+
return { update: mockUpdate };
32+
}),
33+
}));
34+
35+
describe('fn:pluginOptionsSchema', () => {
36+
it('pass with no option provided at all', () => {
37+
expect(
38+
pluginOptionsSchema({
39+
Joi: joi,
40+
} as any).validate({}).error,
41+
).toEqual(undefined);
42+
});
43+
44+
it('give an error if some data is given in the wrong format', () => {
45+
expect(
46+
pluginOptionsSchema({
47+
Joi: joi,
48+
} as any).validate({ databases: ['database', 0] }).error,
49+
).toBeInstanceOf(Error);
50+
});
51+
});
52+
53+
describe('fn:onPreBootstrap', () => {
54+
const testVersion = (version: string, shouldPass: boolean) => {
55+
return async () => {
56+
// edit the mocked package version here
57+
gatsbyPackage.version = version;
58+
const { onPreBootstrap } = await import('#gatsby-node');
59+
const panic = jest.fn();
60+
await onPreBootstrap(
61+
{
62+
reporter: { panic },
63+
} as any,
64+
{ plugins: [] },
65+
jest.fn(),
66+
);
67+
68+
expect(panic).toBeCalledTimes(shouldPass ? 0 : 1);
69+
};
70+
};
71+
72+
it('fail with gatsby earlier than v3', testVersion('2.0.0', false));
73+
it('pass with gatsby v3', testVersion('3.0.0', true));
74+
it('fail with future gatsby after v3', testVersion('4.0.0', false));
75+
});
76+
77+
describe('fn:sourceNodes', () => {
78+
mockDatabase('database');
79+
mockPage('page');
80+
81+
it('source all nodes', async () => {
82+
await sourceNodes(
83+
{} as any,
84+
{ token: 'token', databases: ['database'], pages: ['page'], plugins: [] },
85+
jest.fn(),
86+
);
87+
88+
expect(mockUpdate).toBeCalledWith([
89+
{
90+
created_time: '2020-01-01T00:00:00Z',
91+
id: 'database',
92+
last_edited_time: '2020-01-01T00:00:00Z',
93+
object: 'database',
94+
pages: [],
95+
parent: { type: 'workspace' },
96+
properties: { Name: { id: 'title', title: {}, type: 'title' } },
97+
title: 'Title',
98+
},
99+
{
100+
archived: false,
101+
blocks: [
102+
{
103+
children: [
104+
{
105+
created_time: '2020-01-01T00:00:00Z',
106+
has_children: false,
107+
id: 'page-block0-block0',
108+
last_edited_time: '2020-01-01T00:00:00Z',
109+
object: 'block',
110+
paragraph: {
111+
text: [
112+
{
113+
annotations: {
114+
bold: false,
115+
code: false,
116+
color: 'default',
117+
italic: false,
118+
strikethrough: false,
119+
underline: false,
120+
},
121+
href: null,
122+
plain_text: 'block 0 for block page-block0',
123+
text: {
124+
content: 'block 0 for block page-block0',
125+
link: null,
126+
},
127+
type: 'text',
128+
},
129+
],
130+
},
131+
type: 'paragraph',
132+
},
133+
],
134+
created_time: '2020-01-01T00:00:00Z',
135+
has_children: true,
136+
id: 'page-block0',
137+
last_edited_time: '2020-01-01T00:00:00Z',
138+
object: 'block',
139+
paragraph: {
140+
text: [
141+
{
142+
annotations: {
143+
bold: false,
144+
code: false,
145+
color: 'default',
146+
italic: false,
147+
strikethrough: false,
148+
underline: false,
149+
},
150+
href: null,
151+
plain_text: 'block 0 for block page',
152+
text: { content: 'block 0 for block page', link: null },
153+
type: 'text',
154+
},
155+
],
156+
},
157+
type: 'paragraph',
158+
},
159+
],
160+
created_time: '2020-01-01T00:00:00Z',
161+
id: 'page',
162+
last_edited_time: '2020-01-01T00:00:00Z',
163+
markdown:
164+
'---\nid: page\ntitle: Title\n---\nblock 0 for block page\n\nblock 0 for block page-block0\n',
165+
object: 'page',
166+
parent: { database_id: 'database-page', type: 'database_id' },
167+
properties: {
168+
title: {
169+
id: 'title',
170+
title: [
171+
{
172+
annotations: {
173+
bold: false,
174+
code: false,
175+
color: 'default',
176+
italic: false,
177+
strikethrough: false,
178+
underline: false,
179+
},
180+
href: null,
181+
plain_text: 'Title',
182+
text: { content: 'Title', link: null },
183+
type: 'text',
184+
},
185+
],
186+
type: 'title',
187+
},
188+
},
189+
title: 'Title',
190+
url: 'https://www.notion.so/page',
191+
},
192+
]);
193+
});
194+
});

0 commit comments

Comments
 (0)