@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 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/manifest.md`](./docs/manifest.md) — `custom_blocks.json`, the Vite plugin, manifest types.
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,WAAW,CAAA;AAIrD,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
+ {"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,WAAW,CAAA;AACrD,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"}
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
@@ -4,4 +4,4 @@
4
4
  *
5
5
  * WARNING: Generated during SDK publish. Do not edit in the published package.
6
6
  */
7
- export const CUSTOM_BLOCKS_SDK_VERSION = "0.0.79"
7
+ export const CUSTOM_BLOCKS_SDK_VERSION = "0.1.1"
@@ -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 `custom_blocks.json`.
3
+ A custom block declares its **data sources** — semantic keys like `people` or `tasks` — in the worker's `worker.customBlock(...)` configuration.
4
4
 
5
- Data source mapping - resolving keys to ids - is stored on the block itself. This can be read/written programmatically using the `ncblock` CLI (try `npx ncblock --help`) or in the Notion UI.
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.0.79",
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.
@@ -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 {
4
4
  NotionPagePropertyInputMap,
5
5
  NotionPagePropertyWriteMap,
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
- }