create-ncblock 0.0.40 → 0.0.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ncblock",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "Create a Notion custom view block project.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/init.ts CHANGED
@@ -18,6 +18,7 @@ import { resolveInitArgs } from "./utils/resolveInitArgs"
18
18
  import {
19
19
  getTemplateByName,
20
20
  getTemplates,
21
+ isWorkerTemplate,
21
22
  scaffoldTemplate,
22
23
  type TemplateMetadata,
23
24
  } from "./utils/templates"
@@ -542,6 +543,7 @@ async function main() {
542
543
  )
543
544
  const dest = resolve(dir)
544
545
  const templateDir = resolve(templateBaseDir, selectedTemplate.name)
546
+ const workerShaped = isWorkerTemplate(templateDir)
545
547
 
546
548
  const destNonEmpty = existsSync(dest) && readdirSync(dest).length > 0
547
549
  if (destNonEmpty) {
@@ -578,7 +580,10 @@ async function main() {
578
580
  // downstream walks a positional block/view ID up to its parent database.
579
581
  const blockIdArg = args.block as string | undefined
580
582
  const promptedCollection =
581
- args.collection === undefined && blockIdArg === undefined && installDeps
583
+ !workerShaped &&
584
+ args.collection === undefined &&
585
+ blockIdArg === undefined &&
586
+ installDeps
582
587
  ? (await ask("Database URL/ID (enter to skip):", "skip")).trim()
583
588
  : undefined
584
589
  const collectionOverride =
@@ -640,12 +645,14 @@ async function main() {
640
645
  step("Initialized git repo")
641
646
  }
642
647
 
643
- writeInitialTarget(dest, { env: resolved.env, blockId })
644
- step(`Seeded .notion/target.json`)
645
- if (classified?.kind === "view") {
646
- console.log(
647
- ` ${c.dim}(view ID resolved — using as block id, database ${databaseId ?? "(none)"})${c.reset}`,
648
- )
648
+ if (!workerShaped) {
649
+ writeInitialTarget(dest, { env: resolved.env, blockId })
650
+ step(`Seeded .notion/target.json`)
651
+ if (classified?.kind === "view") {
652
+ console.log(
653
+ ` ${c.dim}(view ID resolved — using as block id, database ${databaseId ?? "(none)"})${c.reset}`,
654
+ )
655
+ }
649
656
  }
650
657
 
651
658
  let connected = false
@@ -660,7 +667,7 @@ async function main() {
660
667
  execSync(`${pm} ${installArgs}`, { cwd: dest, stdio: "inherit" })
661
668
  step("Installed dependencies")
662
669
 
663
- if (databaseId) {
670
+ if (databaseId && !workerShaped) {
664
671
  console.log("")
665
672
  try {
666
673
  runNcblock(dest, ["connect", databaseId, "--quiet"])
@@ -676,8 +683,6 @@ async function main() {
676
683
  }
677
684
  }
678
685
 
679
- const deployCommand = blockId ? `npx ncblock deploy dist/` : undefined
680
-
681
686
  console.log(`\n${c.bold} Ready!${c.reset} Next steps:\n`)
682
687
  if (dest !== resolve(".")) {
683
688
  console.log(` cd ${formatShellPath(dest)}`)
@@ -685,22 +690,33 @@ async function main() {
685
690
  if (!installDeps) {
686
691
  console.log(` ${pm} install`)
687
692
  }
688
- console.log(` ${pm} run build`)
689
- if (deployCommand) {
690
- console.log(` ${deployCommand}`)
693
+
694
+ if (workerShaped) {
695
+ // Workers build and bind their views/data sources as part of deploy; data
696
+ // sources are wired in code (see AGENTS.md). Carry a non-prod env through.
697
+ const envFlag =
698
+ resolved.env === "production" ? "" : ` --env=${resolved.env}`
699
+ console.log(` ntn${envFlag} workers deploy`)
700
+ console.log("")
691
701
  } else {
692
- console.log(
693
- `\n Then, go back to the instructions in Notion and paste the ${c.bold}ntn deploy${c.reset} command.`,
694
- )
695
- }
702
+ const deployCommand = blockId ? `npx ncblock deploy dist/` : undefined
703
+ console.log(` ${pm} run build`)
704
+ if (deployCommand) {
705
+ console.log(` ${deployCommand}`)
706
+ } else {
707
+ console.log(
708
+ `\n Then, go back to the instructions in Notion and paste the ${c.bold}ntn deploy${c.reset} command.`,
709
+ )
710
+ }
696
711
 
697
- if (installDeps && !connected) {
698
- console.log(
699
- `\n ${c.yellow}No data source connected yet.${c.reset} To wire one up:`,
700
- )
701
- console.log(` npx ncblock connect <database-url-or-id>`)
712
+ if (installDeps && !connected) {
713
+ console.log(
714
+ `\n ${c.yellow}No data source connected yet.${c.reset} To wire one up:`,
715
+ )
716
+ console.log(` npx ncblock connect <database-url-or-id>`)
717
+ }
718
+ console.log("")
702
719
  }
703
- console.log("")
704
720
 
705
721
  closePromptInterface()
706
722
  }
@@ -117,6 +117,17 @@ export function getTemplateByName(
117
117
  )
118
118
  }
119
119
 
120
+ // A template is worker-backed if @notionhq/workers is in its dependencies.
121
+ export function isWorkerTemplate(templateDir: string): boolean {
122
+ const pkgPath = resolve(templateDir, "package.json")
123
+ if (!existsSync(pkgPath)) {
124
+ return false
125
+ }
126
+ return Boolean(
127
+ readTemplatePackageJson(pkgPath).dependencies?.["@notionhq/workers"],
128
+ )
129
+ }
130
+
120
131
  type ScaffoldTemplateOptions = {
121
132
  root: string
122
133
  templateDir: string
@@ -217,6 +228,10 @@ function applyTemplateReplacements(
217
228
  return replaced
218
229
  }
219
230
 
231
+ function firstExisting(paths: string[]): string | undefined {
232
+ return paths.find(path => existsSync(path))
233
+ }
234
+
220
235
  function writeRenderedFile(args: {
221
236
  sourcePath: string
222
237
  targetPath: string
@@ -293,14 +308,25 @@ export function scaffoldTemplate({
293
308
  })
294
309
  }
295
310
 
311
+ // Worker templates ship their own AGENTS.md (a symlink to
312
+ // .agents/INSTRUCTIONS.md). copyDirectoryContents skips symlinks, and
313
+ // `npm publish` strips them from the package entirely, so prefer the
314
+ // symlink target's real file and fall back to the symlink for local runs.
296
315
  const agentsTarget = resolve(dest, "AGENTS.md")
297
316
  if (overwrite || !existsSync(agentsTarget)) {
298
- const agentsSource = resolve(root, "scripts/scaffold-assets/AGENTS.md")
299
- writeRenderedFile({
300
- sourcePath: agentsSource,
301
- targetPath: agentsTarget,
302
- replacements,
303
- })
317
+ const agentsSource = isWorkerTemplate(templateDir)
318
+ ? firstExisting([
319
+ resolve(templateDir, ".agents/INSTRUCTIONS.md"),
320
+ resolve(templateDir, "AGENTS.md"),
321
+ ])
322
+ : resolve(root, "scripts/scaffold-assets/AGENTS.md")
323
+ if (agentsSource) {
324
+ writeRenderedFile({
325
+ sourcePath: agentsSource,
326
+ targetPath: agentsTarget,
327
+ replacements,
328
+ })
329
+ }
304
330
  }
305
331
 
306
332
  const pkgPath = resolve(dest, "package.json")
package/sdk-version.json CHANGED
@@ -1 +1 @@
1
- {"version":"0.0.40"}
1
+ {"version":"0.0.42"}
@@ -5,6 +5,7 @@ Overall workers documentation lives at https://developers.notion.com/workers/get
5
5
  ## Project Structure & Module Organization
6
6
  - `src/index.ts` defines the worker and capabilities.
7
7
  - `.examples/` has focused samples (sync, tool, automation, OAuth, webhook).
8
+ - `views/` holds custom-block views (frontend React apps) shipped by the worker. Each view is its own buildable project (`views/<name>/`) with its own `AGENTS.md` describing how to author it. See [Custom blocks (views)](#custom-blocks-views).
8
9
  - Shared agent skills live in `.agents/skills/`. `.claude/skills` is kept as a compatibility symlink for Claude-specific discovery.
9
10
  - Generated: `dist/` build output, `workers.json` CLI config.
10
11
 
@@ -72,6 +73,12 @@ worker.webhook("onGithubPush", {
72
73
  }
73
74
  },
74
75
  });
76
+
77
+ // Ship a frontend view (custom block) rendered inside a Notion block.
78
+ // The view lives in views/dashboard/ and deploys with the worker.
79
+ worker.customBlock("dashboard", {
80
+ path: "./views/dashboard",
81
+ });
75
82
  ```
76
83
 
77
84
  ### Notion API access (`context.notion`)
@@ -367,6 +374,56 @@ This full URL can be retrieved using the `ntn workers webhooks list` command.
367
374
 
368
375
  It is also the responsibility of the worker to verify the webhook. Throw `WebhookVerificationError` if the payload is not valid. 5 invalid payloads in a row will cause webhooks to short circuit until redeployed.
369
376
 
377
+ ### Custom blocks (views)
378
+
379
+ A custom block is a **frontend view** — a sandboxed React app rendered inside a Notion block. It ships *with* the worker: there is no separate CLI. `ntn workers deploy` builds the view, bundles it, and binds its data sources in one step. **Do not use the `ncblock` CLI here** (e.g. `npx ncblock connect` / `deploy`) — that flow is for standalone custom-block projects and does not apply inside a worker.
380
+
381
+ Declare a view with `worker.customBlock(key, config)` in `src/index.ts`:
382
+
383
+ ```ts
384
+ worker.customBlock("dashboard", {
385
+ path: "./views/dashboard", // buildable project dir, relative to the worker root
386
+ });
387
+ ```
388
+
389
+ - `type` defaults to `"project"`: the view is built by running its `command` (default `npm run build`) and its `output` dir (default `dist`) is bundled. Pass `type: "static"` to serve an already-built directory with no build step.
390
+ - The view itself is a separate project under `views/<name>/`. Author it there — see `views/<name>/AGENTS.md` for the frontend rules (React hooks, sizing, forbidden APIs). It uses the `ncblock` package (React hooks + Vite plugin), which is the SDK surface, not the CLI.
391
+
392
+ #### Wiring data sources
393
+
394
+ A view reads data through **managed databases declared in the worker**, not through `ncblock connect`. Declare the database with `worker.database(...)`, then bind it in the `dataSources` map (data-source key → database key). The server binds the block to that managed database on deploy, and the SDK generates the view's `custom_blocks.json` from this declaration — so any hand-written or `ncblock connect`-generated `custom_blocks.json` in the view is ignored.
395
+
396
+ ```ts
397
+ const issues = worker.database("issues", {
398
+ type: "managed",
399
+ initialTitle: "Issues",
400
+ primaryKeyProperty: "Issue ID",
401
+ schema: { properties: { Title: Schema.title(), "Issue ID": Schema.richText() } },
402
+ });
403
+
404
+ worker.sync("issuesSync", { database: issues, execute: async () => { /* ... */ } });
405
+
406
+ worker.customBlock("dashboard", {
407
+ path: "./views/dashboard",
408
+ // Bind the view's "issues" data-source key to the managed `issues` database.
409
+ dataSources: { issues: "issues" },
410
+ });
411
+ ```
412
+
413
+ Inside the view, read that data source with the `ncblock` hooks (e.g. `useDataSource("issues")`). A view with no `dataSources` is fine — it just renders without host data.
414
+
415
+ #### Developing and deploying
416
+
417
+ ```shell
418
+ # Iterate on the view locally (run inside views/<name>/):
419
+ npm run dev # Vite dev server; preview against a live or mock Notion host
420
+
421
+ # Build + deploy the worker and all its views together:
422
+ ntn workers deploy
423
+ ```
424
+
425
+ See `views/<name>/AGENTS.md` for preview options (live Notion host vs. mock dev shell).
426
+
370
427
  ### Sync Management (CLI)
371
428
 
372
429
  **Monitor sync status:**
@@ -2,30 +2,23 @@
2
2
 
3
3
  A **Notion custom view** — a sandboxed `<iframe>` rendered inside a Notion block. The iframe is the entire viewport; the only channel to the host is a `postMessage` bridge wrapped by `ncblock`.
4
4
 
5
- ## Connecting to a Notion database
5
+ This view ships as part of a **worker**: it is declared with `worker.customBlock("empty", { path: "./views/empty" })` in the worker's `src/index.ts`, and it builds and deploys together with the worker via `ntn workers deploy`. There is **no separate `ncblock` CLI step** — do not run `npx ncblock connect` / `deploy`. This file covers how to author the view; for how the worker wires and deploys it, see the worker's `AGENTS.md` ("Custom blocks (views)").
6
6
 
7
- If `custom_blocks.json` or `.notion/target.json` doesn't yet reference a datasource, ask the user if they want to connect a database by url or id, then run:
7
+ ## Data sources
8
8
 
9
- ```bash
10
- npx ncblock connect <database-url-or-id>
11
- ```
9
+ This view reads data from **managed databases declared in the worker**, not from a database you connect here. Wiring happens in the worker's `src/index.ts`:
12
10
 
13
- That writes `custom_blocks.json`, `.notion/target.json`, and PATCHes any blocks already listed there. Discover other commands with `npx ncblock --help`.
14
-
15
- ## Talking to the host
11
+ 1. Declare a database with `worker.database(...)`.
12
+ 2. Bind it in the `dataSources` map of `worker.customBlock("empty", { path, dataSources: { <viewKey>: <databaseKey> } })`.
16
13
 
17
- - Always use the React hooks from `ncblock`. Never call `window.parent.postMessage` directly the SDK owns the protocol.
18
- - Render app code inside `<NotionCustomBlock>` so the handshake completes before hooks like `useTheme` or `useCustomBlockContext` run.
14
+ The worker generates this view's `custom_blocks.json` from that declaration on deploy, so don't hand-edit it (or run `ncblock connect`)those changes are ignored. Inside the view, read a bound data source with `useDataSource(<viewKey>)`.
19
15
 
20
- Hooks at a glance:
16
+ ## Talking to the host
21
17
 
22
- - `useCustomBlockContext()` — `{ customBlockId, parent, page }`.
23
- - `useTheme()` `"light" | "dark"`.
24
- - `useDataSource(key, initialLimit?)` — `{ items, isLoading, hasMore, fetchMore, error }`.
25
- - `useManifest()` — the declared manifest: data-source keys plus their property declarations.
26
- - `pages.create(input)` — creates a page; pass `parent: { type: "data_source_key", key }` to target the block's wired data source.
18
+ - Always use the React hooks from `ncblock/react`. Never call `window.parent.postMessage` directly the SDK owns the protocol.
19
+ - Render app code inside `<NotionCustomBlock>` so the handshake completes before hooks like `useTheme` or `useBlockId` run.
27
20
 
28
- `<NotionCustomBlock>` runs `useCustomBlockAutoResize` for you by default — no extra wiring needed. Pass `autoResize={false}` for full-bleed views. Full signatures live in `node_modules/ncblock/docs/*.md` and the `.d.ts` files.
21
+ `<NotionCustomBlock>` runs `useCustomBlockAutoResize` for you by default — no extra wiring needed. Pass `autoResize={false}` for full-bleed views. Read `node_modules/ncblock/README.md` and the `.d.ts` files for current APIs and signatures.
29
22
 
30
23
  ## Sizing
31
24
 
@@ -41,11 +34,17 @@ Hooks at a glance:
41
34
  ## Conventions
42
35
 
43
36
  - Mount into `<div id="root">` — `useCustomBlockAutoResize` looks for that exact id.
44
- - `custom_blocks.json` at the project root defines the data contract. The SDK's Vite plugin serves it in dev; `ntn` bundles it into the deploy.
37
+ - `custom_blocks.json` is the data contract. The SDK's Vite plugin serves it in dev; in a worker it is **generated** from the worker's `worker.customBlock(...)` declaration at deploy time.
45
38
 
46
39
  ## Previewing during development
47
40
 
48
- Two options:
41
+ Run the Vite dev server from this view's directory:
42
+
43
+ ```bash
44
+ npm run dev # http://localhost:5173
45
+ ```
46
+
47
+ Then preview against a host:
49
48
 
50
49
  1. **Live Notion host** — ask the user to create a block via `/custom` and paste the Vite dev URL (e.g. `http://localhost:5173`) into the block's URL field.
51
50
  2. **Mock host** — run the dev shell in another tab:
@@ -54,14 +53,19 @@ Two options:
54
53
  cd custom && pnpm install && pnpm run dev:shell # http://localhost:9875
55
54
  ```
56
55
 
56
+ When the view is ready, deploy it with the worker from the worker root:
57
+
58
+ ```bash
59
+ ntn workers deploy
60
+ ```
61
+
57
62
  ## Where to look next
58
63
 
59
64
  - `node_modules/ncblock/README.md` — landing page with a TOC into the per-category docs below.
60
65
  - `node_modules/ncblock/docs/lifecycle.md` — `<NotionCustomBlock>`, init, sizing, auto-resize.
61
- - `node_modules/ncblock/docs/context.md` — `useCustomBlockContext`, `useTheme`.
66
+ - `node_modules/ncblock/docs/block-location.md` — `useBlockId`, `useParent`, `usePage`, `useTheme`.
62
67
  - `node_modules/ncblock/docs/data-sources.md` — `useDataSource`, row/property/date types, worked example.
63
68
  - `node_modules/ncblock/docs/pages.md` — `pages.create / get / update / delete`.
64
69
  - `node_modules/ncblock/docs/users.md` — `users.list / get`, `NotionUser`.
65
70
  - `node_modules/ncblock/docs/manifest.md` — `custom_blocks.json` + Vite plugin.
66
- - `node_modules/ncblock/dist/*.d.ts` — typed surface; hover in your editor.
67
71
  - https://github.com/makenotion/custom — source and examples.