@notionhq/custom-blocks 0.0.79 → 0.1.1
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 +3 -1
- package/dist/bridge/dataSources/errors.d.ts +1 -1
- package/dist/bridge/dataSources/errors.d.ts.map +1 -1
- package/dist/bridge/dataSources/resolveProperty.d.ts +1 -1
- package/dist/bridge/dataSources/resolveProperty.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/docs/data-sources.md +4 -2
- package/docs/vite-plugin.md +46 -0
- package/package.json +1 -2
- package/src/bridge/dataSources/errors.ts +1 -1
- package/src/bridge/dataSources/resolveProperty.ts +1 -1
- package/bin/cli/attach.js +0 -180
- package/bin/cli/cli.js +0 -248
- package/bin/cli/create.js +0 -106
- package/bin/cli/datasources.js +0 -255
- package/bin/cli/deploy.js +0 -109
- package/bin/cli/ids.js +0 -13
- package/bin/cli/ntn.js +0 -95
- package/bin/cli/pullData.js +0 -76
- package/bin/cli/pullManifest.js +0 -158
- package/bin/cli/target.js +0 -95
- package/bin/src/bridge/dataSources/propertySchema.js +0 -148
- package/bin/src/bridge/manifest.js +0 -40
- package/docs/manifest.md +0 -42
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ API surface, one page per category. Import framework-neutral APIs from `@notionh
|
|
|
47
47
|
- [`docs/pages.md`](./docs/pages.md) — `pages.create / get / update / delete`, parent variants (including the recommended `data_source_key`), property input shapes.
|
|
48
48
|
- [`docs/users.md`](./docs/users.md) — `users.list / get`, the `NotionUser` shape, paging.
|
|
49
49
|
- [`docs/errors.md`](./docs/errors.md) — request results, error format, error codes, retries, and initialization failures.
|
|
50
|
-
- [`docs/
|
|
50
|
+
- [`docs/vite-plugin.md`](./docs/vite-plugin.md) — the `notionCustomBlock()` Vite plugin and `custom_blocks.json`, the block's local-preview manifest.
|
|
51
|
+
|
|
52
|
+
Declare the block source and data-source schema in the worker file with `worker.customBlock(...)`, then build and deploy it with `ntn workers deploy`. The worker definition is the source of truth; the iframe frontend uses semantic keys from that definition with APIs such as `useDataSource`. For local previews, the Vite plugin serves the block's `custom_blocks.json` — see [`docs/vite-plugin.md`](./docs/vite-plugin.md).
|
|
51
53
|
|
|
52
54
|
## Forbidden APIs
|
|
53
55
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import type { CustomBlockErrorInfo } from "../errors";
|
|
2
|
+
import type { CustomBlockErrorInfo } from "../errors.js";
|
|
3
3
|
export declare const customBlockDataSourceResolutionErrorCodeSchema: v.StringSchema<undefined>;
|
|
4
4
|
export type CustomBlockDataSourceResolutionErrorCode = "unknown_data_source_key" | "unmapped_data_source" | (string & {});
|
|
5
5
|
export type CustomBlockDataSourceResolutionErrorInfo = CustomBlockErrorInfo<CustomBlockDataSourceResolutionErrorCode>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAIxD,eAAO,MAAM,8CAA8C,2BAAa,CAAA;AAExE,MAAM,MAAM,wCAAwC,GACjD,yBAAyB,GACzB,sBAAsB,GACtB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,MAAM,MAAM,wCAAwC,GACnD,oBAAoB,CAAC,wCAAwC,CAAC,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import type { CustomBlockErrorInfo } from "../errors";
|
|
2
|
+
import type { CustomBlockErrorInfo } from "../errors.js";
|
|
3
3
|
import type { NotionPagePropertyInputMap, NotionPagePropertyWriteMap } from "../pages/page.js";
|
|
4
4
|
import type { NotionDataSource } from "./dataSource.js";
|
|
5
5
|
export declare const customBlockPropertyErrorCodeSchema: v.StringSchema<undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveProperty.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolveProperty.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"resolveProperty.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolveProperty.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,KAAK,EACX,0BAA0B,EAC1B,0BAA0B,EAC1B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAIvD,eAAO,MAAM,kCAAkC,2BAAa,CAAA;AAE5D,MAAM,MAAM,4BAA4B,GACrC,mBAAmB,GACnB,sBAAsB,GACtB,oBAAoB,GACpB,wBAAwB,GACxB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,MAAM,MAAM,4BAA4B,GACvC,oBAAoB,CAAC,4BAA4B,CAAC,CAAA;AAEnD,MAAM,MAAM,gCAAgC,GACzC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,0BAA0B,CAAA;CAAE,GAC7D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,4BAA4B,CAAA;CAAE,CAAA;AAS3D;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CAAC,IAAI,EAAE;IAC1D,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACxC,UAAU,EAAE,0BAA0B,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;CACrB,GAAG,gCAAgC,CAwDnC;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE;IAC5D,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;CACrB,GACE;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,4BAA4B,CAAA;CAAE,CAsB1D"}
|
package/dist/version.js
CHANGED
package/docs/data-sources.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Data sources
|
|
2
2
|
|
|
3
|
-
A custom block declares its **data sources** — semantic keys like `people` or `tasks` — in `
|
|
3
|
+
A custom block declares its **data sources** — semantic keys like `people` or `tasks` — in the worker's `worker.customBlock(...)` configuration.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The mapping from those semantic keys to concrete Notion data sources is stored on each block instance and configured in Notion.
|
|
6
|
+
|
|
7
|
+
Use `ManifestDataSource`, `ManifestProperty`, and `ManifestIcon` when code needs the corresponding definition metadata types.
|
|
6
8
|
|
|
7
9
|
At runtime, your code references the semantic key and the SDK handles the lookup for you. Use `useDataSource(key)` for the rows themselves (it also exposes the resolved schema), and `useManifest()` when you need the declared data-source keys and their declarations (e.g. picking a default key, or rendering a key switcher). For non-React renderers, `customBlock.getManifest()` exposes the same manifest, but row querying is currently only exposed through the React `useDataSource` hook.
|
|
8
10
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Vite plugin
|
|
2
|
+
|
|
3
|
+
The `notionCustomBlock()` plugin from `@notionhq/custom-blocks/vite` wires a block's local dev server and build output to the SDK handshake.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { defineConfig } from "vite";
|
|
7
|
+
import react from "@vitejs/plugin-react";
|
|
8
|
+
import { notionCustomBlock } from "@notionhq/custom-blocks/vite";
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
plugins: [react(), notionCustomBlock()],
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
In dev, the plugin serves `custom_blocks.json` from the project root so HMR and the SDK handshake see the same file. On `vite build`, it emits `custom_blocks.json` into `dist/` as a separate asset alongside the bundled HTML and JS. It also forces Vite's `base` to `"./"` (and rejects any other explicit value) — custom blocks are served from a content-addressed path, so asset URLs in the built bundle must be relative.
|
|
16
|
+
|
|
17
|
+
## `custom_blocks.json` and local previews
|
|
18
|
+
|
|
19
|
+
`custom_blocks.json` is the block's local manifest — the data-source keys and property shapes the block expects:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"version": 1,
|
|
24
|
+
"dataSources": {
|
|
25
|
+
"tasks": {
|
|
26
|
+
"name": "Tasks",
|
|
27
|
+
"description": "The collection of tasks to render",
|
|
28
|
+
"properties": {
|
|
29
|
+
"title": { "name": "Title", "type": "title" },
|
|
30
|
+
"dueDate": { "name": "Due date", "type": "date" }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For a worker-backed block, the `worker.customBlock(...)` declaration in the worker's `src/index.ts` is the source of truth at deploy time. But local previews (pasting the Vite dev URL into a live Notion block, or the dev shell) don't read the worker declaration — the SDK fetches `custom_blocks.json` from the plugin and forwards it on `connect`, and the host binds data sources against those keys. Keep the file mirroring the worker's `dataSources` schema so previews see the same slots the deployed block will; leave it as `{ "version": 1, "dataSources": {} }` if the block reads no host data.
|
|
38
|
+
|
|
39
|
+
If the file is missing, the SDK omits `manifest` from `connect`. If it is unreadable or invalid, the SDK sends `connect` with `status: "error"` and an `error` payload. The host returns its authoritative manifest in `init`, and the SDK uses that one even when it differs from what was sent in `connect`.
|
|
40
|
+
|
|
41
|
+
## Types
|
|
42
|
+
|
|
43
|
+
- `CustomBlockManifest` — the parsed shape of `custom_blocks.json`.
|
|
44
|
+
- `ManifestDataSource` — a single entry in `dataSources` (name, description, properties).
|
|
45
|
+
- `ManifestProperty` — a single property declaration inside a `ManifestDataSource`.
|
|
46
|
+
- `ManifestIcon` — the icon variant accepted on a `ManifestDataSource`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/custom-blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"bin": {
|
|
35
|
-
"ncblock": "./bin/cli/cli.js",
|
|
36
35
|
"notion-custom-blocks": "./bin/notion-custom-blocks/cli.js"
|
|
37
36
|
},
|
|
38
37
|
"scripts": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot"
|
|
2
|
-
import type { CustomBlockErrorInfo } from "../errors"
|
|
2
|
+
import type { CustomBlockErrorInfo } from "../errors.js"
|
|
3
3
|
|
|
4
4
|
// The schema accepts any string code. The type lists known codes for
|
|
5
5
|
// autocomplete, with an open string fallback for newer senders.
|
package/bin/cli/attach.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { extractId, formatUuid } from "./ids.js";
|
|
2
|
-
import { ntnApi, ntnApiPatch, ntnWorkersCapabilities } from "./ntn.js";
|
|
3
|
-
// ── Capability resolution ───────────────────────────────────────
|
|
4
|
-
function normalizeCapabilities(raw) {
|
|
5
|
-
if (Array.isArray(raw)) {
|
|
6
|
-
return raw;
|
|
7
|
-
}
|
|
8
|
-
if (raw && typeof raw === "object") {
|
|
9
|
-
const obj = raw;
|
|
10
|
-
for (const field of ["capabilities", "results", "data"]) {
|
|
11
|
-
if (Array.isArray(obj[field])) {
|
|
12
|
-
return obj[field];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
function capabilityKey(cap) {
|
|
19
|
-
for (const field of ["key", "name", "id"]) {
|
|
20
|
-
const value = cap[field];
|
|
21
|
-
if (typeof value === "string" && value.length > 0) {
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
function capabilityType(cap) {
|
|
28
|
-
// `ntn` reports the capability kind under `_tag`; accept `type` too in case
|
|
29
|
-
// the wire shape shifts.
|
|
30
|
-
for (const field of ["_tag", "type"]) {
|
|
31
|
-
const value = cap[field];
|
|
32
|
-
if (typeof value === "string") {
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
function isCustomBlockCapability(cap) {
|
|
39
|
-
const type = capabilityType(cap);
|
|
40
|
-
// When no type field is present we can't filter — keep it and let the
|
|
41
|
-
// count-based selection below decide.
|
|
42
|
-
if (type === undefined) {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
return type === "customBlock" || type === "custom_block";
|
|
46
|
-
}
|
|
47
|
-
function firstString(obj, fields) {
|
|
48
|
-
for (const field of fields) {
|
|
49
|
-
const value = obj[field];
|
|
50
|
-
if (typeof value === "string" && value.length > 0) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
function capabilityDefinitionId(cap) {
|
|
57
|
-
const top = firstString(cap, ["definition_id", "definitionId"]);
|
|
58
|
-
if (top) {
|
|
59
|
-
return top;
|
|
60
|
-
}
|
|
61
|
-
// The real `ntn` shape nests it under `state.definitionId`.
|
|
62
|
-
const state = cap.state;
|
|
63
|
-
if (state && typeof state === "object") {
|
|
64
|
-
const nested = firstString(state, [
|
|
65
|
-
"definitionId",
|
|
66
|
-
"definition_id",
|
|
67
|
-
]);
|
|
68
|
-
if (nested) {
|
|
69
|
-
return nested;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// Some shapes nest the definition under a `definition` object.
|
|
73
|
-
const definition = cap.definition;
|
|
74
|
-
if (definition && typeof definition === "object") {
|
|
75
|
-
const id = definition.id;
|
|
76
|
-
if (typeof id === "string" && id.length > 0) {
|
|
77
|
-
return id;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Resolve a worker's custom-block capability to a `definition_id`.
|
|
84
|
-
*
|
|
85
|
-
* - Filters `ntn workers capabilities list` down to customBlock capabilities.
|
|
86
|
-
* - With `capability` set, picks the entry with that key.
|
|
87
|
-
* - With exactly one candidate, uses it.
|
|
88
|
-
* - With more than one and no `capability`, errors with the available keys.
|
|
89
|
-
*/
|
|
90
|
-
export function resolveDefinitionId(capability) {
|
|
91
|
-
const raw = ntnWorkersCapabilities();
|
|
92
|
-
const all = normalizeCapabilities(raw);
|
|
93
|
-
if (all.length === 0) {
|
|
94
|
-
console.error("Error: no worker capabilities found. Deploy the worker first with `ntn workers deploy`.\n");
|
|
95
|
-
console.error(" Raw `ntn workers capabilities list --json` output:");
|
|
96
|
-
console.error(JSON.stringify(raw, null, "\t"));
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
const customBlocks = all.filter(isCustomBlockCapability);
|
|
100
|
-
if (customBlocks.length === 0) {
|
|
101
|
-
console.error("Error: this worker exposes no customBlock capability to attach.\n");
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
let selected;
|
|
105
|
-
if (capability) {
|
|
106
|
-
const match = customBlocks.find(c => capabilityKey(c) === capability);
|
|
107
|
-
if (!match) {
|
|
108
|
-
const keys = customBlocks.map(c => capabilityKey(c) ?? "(unnamed)");
|
|
109
|
-
console.error(`Error: no customBlock capability named '${capability}'.\n`);
|
|
110
|
-
console.error(` Available: ${keys.join(", ")}`);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
selected = match;
|
|
114
|
-
}
|
|
115
|
-
else if (customBlocks.length > 1) {
|
|
116
|
-
const keys = customBlocks.map(c => capabilityKey(c) ?? "(unnamed)");
|
|
117
|
-
console.error(`Error: this worker exposes ${customBlocks.length} customBlock capabilities. Pick one with --capability <key>.\n`);
|
|
118
|
-
console.error(` Available: ${keys.join(", ")}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
selected = customBlocks[0];
|
|
123
|
-
}
|
|
124
|
-
const definitionId = capabilityDefinitionId(selected);
|
|
125
|
-
if (!definitionId) {
|
|
126
|
-
console.error("Error: could not read a definition_id from the selected capability.\n");
|
|
127
|
-
console.error(" Raw capability entry:");
|
|
128
|
-
console.error(JSON.stringify(selected, null, "\t"));
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
return { definitionId, capabilityKey: capabilityKey(selected) };
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Body for pointing a custom block at a worker's definition. The update-block
|
|
135
|
-
* endpoint requires the payload be keyed by block type — a top-level
|
|
136
|
-
* `definition_id` is rejected (`should be not present`). So it nests under
|
|
137
|
-
* `custom_block`, matching how `patchBlock` (datasources.ts) sets data_sources.
|
|
138
|
-
*/
|
|
139
|
-
function definitionPatchBody(definitionId) {
|
|
140
|
-
return { custom_block: { definition_id: definitionId } };
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Point an existing custom block at a worker's customBlock definition.
|
|
144
|
-
*
|
|
145
|
-
* Resolves the block, verifies it's a `custom_block`, resolves the worker's
|
|
146
|
-
* definition_id (erroring on ambiguity), then PATCHes the block. Returns the
|
|
147
|
-
* dashed block UUID so callers (e.g. `create`) can chain.
|
|
148
|
-
*/
|
|
149
|
-
export function attach(options) {
|
|
150
|
-
const uuid = formatUuid(extractId(options.idOrUrl));
|
|
151
|
-
if (!options.jsonOutput) {
|
|
152
|
-
console.log(`\nFetching block ${uuid}…`);
|
|
153
|
-
}
|
|
154
|
-
const block = ntnApi(`/v1/blocks/${uuid}`);
|
|
155
|
-
if (block.type !== "custom_block") {
|
|
156
|
-
console.error(`Error: block ${uuid} is type "${block.type}", not "custom_block".\n`);
|
|
157
|
-
console.error(" Pass a custom block's ID/URL, or create one with `ncblock create`.");
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
const { definitionId, capabilityKey: key } = resolveDefinitionId(options.capability);
|
|
161
|
-
if (!options.jsonOutput) {
|
|
162
|
-
console.log(`Attaching definition ${definitionId}${key ? ` (capability "${key}")` : ""}…`);
|
|
163
|
-
}
|
|
164
|
-
ntnApiPatch(`/v1/blocks/${uuid}`, definitionPatchBody(definitionId));
|
|
165
|
-
const emitResult = options.emitResult ?? true;
|
|
166
|
-
if (options.jsonOutput) {
|
|
167
|
-
if (emitResult) {
|
|
168
|
-
console.log(JSON.stringify({
|
|
169
|
-
action: "attach",
|
|
170
|
-
block_id: uuid,
|
|
171
|
-
definition_id: definitionId,
|
|
172
|
-
capability: key,
|
|
173
|
-
}, null, "\t"));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
console.log(`\n✓ Attached block ${uuid} to definition ${definitionId}\n`);
|
|
178
|
-
}
|
|
179
|
-
return uuid;
|
|
180
|
-
}
|
package/bin/cli/cli.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { attach } from "./attach.js";
|
|
3
|
-
import { create } from "./create.js";
|
|
4
|
-
import { connect, getDataSources } from "./datasources.js";
|
|
5
|
-
import { deploy } from "./deploy.js";
|
|
6
|
-
import { pullData } from "./pullData.js";
|
|
7
|
-
import { pullManifest } from "./pullManifest.js";
|
|
8
|
-
const HELP = `
|
|
9
|
-
ncblock — Notion Custom Block SDK CLI
|
|
10
|
-
|
|
11
|
-
Usage:
|
|
12
|
-
ncblock connect <database-url-or-id> [options]
|
|
13
|
-
ncblock connect [--block <id>] [--bind <key>=<ds-id>] [options]
|
|
14
|
-
ncblock data_sources get <block-id-or-url>
|
|
15
|
-
ncblock deploy <dist-path> [--block <id>] [options]
|
|
16
|
-
ncblock pull_data <id-or-url> [--limit <n>] [--out <path>]
|
|
17
|
-
ncblock attach <block-id-or-url> [--capability <key>]
|
|
18
|
-
ncblock create [--parent <id-or-url>] [--capability <key>]
|
|
19
|
-
|
|
20
|
-
Commands:
|
|
21
|
-
connect Pull the data source schema and PATCH the block(s) in .notion/target.json
|
|
22
|
-
data_sources get Read data source bindings from a custom block
|
|
23
|
-
deploy Run \`ntn custom deploy\` for each block; appends --block IDs into target.json on success
|
|
24
|
-
pull_data Resolve an ID/URL to a data source; print its schema + N rows as JSON to stdout
|
|
25
|
-
attach Point a custom block at the worker's definition (from \`ntn workers capabilities\`)
|
|
26
|
-
create Create a custom block (private page parent by default) and attach it
|
|
27
|
-
|
|
28
|
-
Options:
|
|
29
|
-
--block <id> Block ID/URL (repeatable; accumulates into target.json's block_id[])
|
|
30
|
-
--bind <key>=<id> Bind a manifest key to a data source ID (repeatable)
|
|
31
|
-
--capability <key> customBlock capability key to attach (required if the worker has more than one)
|
|
32
|
-
--parent <id> Parent ID/URL for \`create\` (a page hosts the block; omit for a new private page)
|
|
33
|
-
--limit <n> Rows to pull for \`pull_data\` (default: 25, max: 100)
|
|
34
|
-
--env <name> Notion env (defaults to target.json's env, or "production")
|
|
35
|
-
--key <key> Data source key in manifest (default: "default") — for connect <id> only
|
|
36
|
-
--out <path> Output path — manifest for connect, JSON file for pull_data
|
|
37
|
-
--manifest <path> Path to custom_blocks.json (default: "./custom_blocks.json")
|
|
38
|
-
--dry-run Print the intended action without performing it
|
|
39
|
-
--json Emit machine-parseable JSON output
|
|
40
|
-
--help, -h Show this help message
|
|
41
|
-
|
|
42
|
-
Examples:
|
|
43
|
-
ncblock connect <db-url>
|
|
44
|
-
ncblock connect --block <block-id> --bind default=<db-id>
|
|
45
|
-
ncblock data_sources get <block-id>
|
|
46
|
-
ncblock deploy dist/
|
|
47
|
-
ncblock pull_data <db-url> --limit 50 --out sample.json
|
|
48
|
-
ncblock attach <block-url> --capability hello
|
|
49
|
-
ncblock create
|
|
50
|
-
`;
|
|
51
|
-
function parseArgs(argv) {
|
|
52
|
-
const flags = {};
|
|
53
|
-
const bindings = {};
|
|
54
|
-
const blocks = [];
|
|
55
|
-
const positional = [];
|
|
56
|
-
let i = 2;
|
|
57
|
-
while (i < argv.length) {
|
|
58
|
-
const arg = argv[i];
|
|
59
|
-
if (arg === "--key") {
|
|
60
|
-
flags.key = argv[++i];
|
|
61
|
-
}
|
|
62
|
-
else if (arg === "--out") {
|
|
63
|
-
flags.out = argv[++i];
|
|
64
|
-
}
|
|
65
|
-
else if (arg === "--dry-run") {
|
|
66
|
-
flags.dryRun = true;
|
|
67
|
-
}
|
|
68
|
-
else if (arg === "--json") {
|
|
69
|
-
flags.jsonOutput = true;
|
|
70
|
-
}
|
|
71
|
-
else if (arg === "--quiet") {
|
|
72
|
-
flags.quiet = true;
|
|
73
|
-
}
|
|
74
|
-
else if (arg === "--manifest") {
|
|
75
|
-
flags.manifest = argv[++i];
|
|
76
|
-
}
|
|
77
|
-
else if (arg === "--capability") {
|
|
78
|
-
flags.capability = argv[++i];
|
|
79
|
-
}
|
|
80
|
-
else if (arg === "--parent") {
|
|
81
|
-
flags.parent = argv[++i];
|
|
82
|
-
}
|
|
83
|
-
else if (arg === "--limit") {
|
|
84
|
-
flags.limit = argv[++i];
|
|
85
|
-
}
|
|
86
|
-
else if (arg === "--env") {
|
|
87
|
-
flags.env = argv[++i];
|
|
88
|
-
}
|
|
89
|
-
else if (arg === "--block") {
|
|
90
|
-
const value = argv[++i];
|
|
91
|
-
if (!value) {
|
|
92
|
-
console.error("Error: --block requires a value.\n");
|
|
93
|
-
console.error("Usage: --block <id-or-url>");
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
blocks.push(value);
|
|
97
|
-
}
|
|
98
|
-
else if (arg === "--bind") {
|
|
99
|
-
const pair = argv[++i];
|
|
100
|
-
const eq = pair?.indexOf("=");
|
|
101
|
-
if (!pair || eq === undefined || eq < 1) {
|
|
102
|
-
console.error(`Error: invalid --bind value: ${pair}\n`);
|
|
103
|
-
console.error("Expected format: --bind <key>=<data-source-id>");
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
bindings[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
107
|
-
}
|
|
108
|
-
else if (arg === "--help" || arg === "-h") {
|
|
109
|
-
flags.help = true;
|
|
110
|
-
}
|
|
111
|
-
else if (!arg.startsWith("-")) {
|
|
112
|
-
positional.push(arg);
|
|
113
|
-
}
|
|
114
|
-
i++;
|
|
115
|
-
}
|
|
116
|
-
return { flags, bindings, blocks, positional };
|
|
117
|
-
}
|
|
118
|
-
function main() {
|
|
119
|
-
const { flags, bindings, blocks, positional } = parseArgs(process.argv);
|
|
120
|
-
if (flags.help || positional.length === 0) {
|
|
121
|
-
console.log(HELP);
|
|
122
|
-
process.exit(0);
|
|
123
|
-
}
|
|
124
|
-
const [command, subcommand, ...rest] = positional;
|
|
125
|
-
// `manifest pull` is kept as an internal/advanced command — not advertised
|
|
126
|
-
// in HELP, but still callable for users who want pull without the PATCH
|
|
127
|
-
// step. Prefer `ncblock connect <id>`.
|
|
128
|
-
if (command === "manifest" && subcommand === "pull") {
|
|
129
|
-
const idOrUrl = rest[0];
|
|
130
|
-
if (!idOrUrl) {
|
|
131
|
-
console.error("Error: missing <id-or-url> argument.\n");
|
|
132
|
-
console.error("Usage: ncblock manifest pull <id-or-url> [options]");
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
pullManifest({
|
|
136
|
-
idOrUrl,
|
|
137
|
-
key: flags.key,
|
|
138
|
-
out: flags.out,
|
|
139
|
-
dryRun: flags.dryRun,
|
|
140
|
-
quiet: flags.quiet,
|
|
141
|
-
});
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (command === "connect") {
|
|
145
|
-
// `ncblock connect <database-url-or-id>` pulls the schema first then
|
|
146
|
-
// runs the binding/PATCH flow against target.json. The positional
|
|
147
|
-
// must point at a database (or a data source); ID-kind classification
|
|
148
|
-
// (block vs view vs wrong-workspace) happens upstream in `init.ts`.
|
|
149
|
-
connect({
|
|
150
|
-
idOrUrl: subcommand,
|
|
151
|
-
blockIds: blocks,
|
|
152
|
-
bindings,
|
|
153
|
-
key: flags.key,
|
|
154
|
-
out: flags.out,
|
|
155
|
-
manifest: flags.manifest,
|
|
156
|
-
env: flags.env,
|
|
157
|
-
dryRun: flags.dryRun,
|
|
158
|
-
jsonOutput: flags.jsonOutput,
|
|
159
|
-
quiet: flags.quiet,
|
|
160
|
-
});
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (command === "data_sources" && subcommand === "get") {
|
|
164
|
-
const idOrUrl = rest[0];
|
|
165
|
-
if (!idOrUrl) {
|
|
166
|
-
console.error("Error: missing <block-id-or-url> argument.\n");
|
|
167
|
-
console.error("Usage: ncblock data_sources get <block-id-or-url>");
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
getDataSources(idOrUrl);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (command === "deploy") {
|
|
174
|
-
const distPath = subcommand;
|
|
175
|
-
if (!distPath) {
|
|
176
|
-
console.error("Error: missing <dist-path> argument.\n");
|
|
177
|
-
console.error("Usage: ncblock deploy <dist-path> [--block <id>...] [--env <name>]");
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
deploy({
|
|
181
|
-
distPath,
|
|
182
|
-
blockIds: blocks,
|
|
183
|
-
env: flags.env,
|
|
184
|
-
dryRun: flags.dryRun,
|
|
185
|
-
jsonOutput: flags.jsonOutput,
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
// The commands below route their API calls through ntn.ts, which resolves
|
|
190
|
-
// the env from `process.env.ENV` (falling back to target.json). Honor an
|
|
191
|
-
// explicit --env by setting it here so every ntn call inherits it.
|
|
192
|
-
if (flags.env) {
|
|
193
|
-
process.env.ENV = flags.env;
|
|
194
|
-
}
|
|
195
|
-
if (command === "pull_data") {
|
|
196
|
-
const idOrUrl = subcommand;
|
|
197
|
-
if (!idOrUrl) {
|
|
198
|
-
console.error("Error: missing <id-or-url> argument.\n");
|
|
199
|
-
console.error("Usage: ncblock pull_data <id-or-url> [--limit <n>] [--out <path>]");
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
const limitRaw = flags.limit;
|
|
203
|
-
const limit = limitRaw === undefined ? undefined : Number(limitRaw);
|
|
204
|
-
if (limit !== undefined && !Number.isFinite(limit)) {
|
|
205
|
-
console.error(`Error: --limit must be a number, got '${limitRaw}'.`);
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
pullData({
|
|
209
|
-
idOrUrl,
|
|
210
|
-
limit,
|
|
211
|
-
out: flags.out,
|
|
212
|
-
});
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (command === "attach") {
|
|
216
|
-
const idOrUrl = subcommand;
|
|
217
|
-
if (!idOrUrl) {
|
|
218
|
-
console.error("Error: missing <block-id-or-url> argument.\n");
|
|
219
|
-
console.error("Usage: ncblock attach <block-id-or-url> [--capability <key>]");
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
attach({
|
|
223
|
-
idOrUrl,
|
|
224
|
-
capability: flags.capability,
|
|
225
|
-
jsonOutput: flags.jsonOutput,
|
|
226
|
-
});
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (command === "create") {
|
|
230
|
-
create({
|
|
231
|
-
parent: flags.parent,
|
|
232
|
-
capability: flags.capability,
|
|
233
|
-
jsonOutput: flags.jsonOutput,
|
|
234
|
-
});
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
console.error(`Unknown command: ${positional.join(" ")}\n`);
|
|
238
|
-
console.log(HELP);
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
try {
|
|
242
|
-
main();
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
-
console.error(`\nError: ${message}\n`);
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|