create-ncblock 0.0.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.
Files changed (238) hide show
  1. package/README.md +49 -0
  2. package/bin/cli.js +33 -0
  3. package/package.json +25 -0
  4. package/scripts/init.ts +527 -0
  5. package/scripts/scaffold-assets/AGENTS.md +65 -0
  6. package/scripts/utils/templates.ts +293 -0
  7. package/sdk-version.json +1 -0
  8. package/templates/debug/README.md +36 -0
  9. package/templates/debug/_gitignore +2 -0
  10. package/templates/debug/custom_blocks.json +9 -0
  11. package/templates/debug/dist/assets/index-Cet2SsjS.css +2 -0
  12. package/templates/debug/dist/assets/index-DAzv_fuh.js +9 -0
  13. package/templates/debug/dist/custom_blocks.json +9 -0
  14. package/templates/debug/dist/index.html +16 -0
  15. package/templates/debug/index.html +15 -0
  16. package/templates/debug/node_modules/.bin/browserslist +21 -0
  17. package/templates/debug/node_modules/.bin/esbuild +21 -0
  18. package/templates/debug/node_modules/.bin/jiti +21 -0
  19. package/templates/debug/node_modules/.bin/rollup +21 -0
  20. package/templates/debug/node_modules/.bin/tsc +21 -0
  21. package/templates/debug/node_modules/.bin/tsserver +21 -0
  22. package/templates/debug/node_modules/.bin/tsx +21 -0
  23. package/templates/debug/node_modules/.bin/vite +21 -0
  24. package/templates/debug/node_modules/.vite/deps/_metadata.json +50 -0
  25. package/templates/debug/node_modules/.vite/deps/package.json +3 -0
  26. package/templates/debug/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  27. package/templates/debug/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  28. package/templates/debug/node_modules/.vite/deps/react-dom.js +185 -0
  29. package/templates/debug/node_modules/.vite/deps/react-dom.js.map +1 -0
  30. package/templates/debug/node_modules/.vite/deps/react-dom_client.js +14384 -0
  31. package/templates/debug/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  32. package/templates/debug/node_modules/.vite/deps/react.js +2 -0
  33. package/templates/debug/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  34. package/templates/debug/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  35. package/templates/debug/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  36. package/templates/debug/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  37. package/templates/debug/node_modules/.vite/deps/valibot.js +6623 -0
  38. package/templates/debug/node_modules/.vite/deps/valibot.js.map +1 -0
  39. package/templates/debug/node_modules/.vite-temp/vite.config.ts.timestamp-1778623720803-0bcf523a67aa8.mjs +15 -0
  40. package/templates/debug/package.json +30 -0
  41. package/templates/debug/src/index.css +62 -0
  42. package/templates/debug/src/index.tsx +1963 -0
  43. package/templates/debug/tsconfig.json +17 -0
  44. package/templates/debug/vite.config.ts +8 -0
  45. package/templates/empty/README.md +10 -0
  46. package/templates/empty/_gitignore +2 -0
  47. package/templates/empty/custom_blocks.json +12 -0
  48. package/templates/empty/dist/assets/index-CodJADav.js +9 -0
  49. package/templates/empty/dist/custom_blocks.json +12 -0
  50. package/templates/empty/dist/index.html +15 -0
  51. package/templates/empty/index.html +15 -0
  52. package/templates/empty/node_modules/.bin/esbuild +21 -0
  53. package/templates/empty/node_modules/.bin/jiti +21 -0
  54. package/templates/empty/node_modules/.bin/tsc +21 -0
  55. package/templates/empty/node_modules/.bin/tsserver +21 -0
  56. package/templates/empty/node_modules/.bin/tsx +21 -0
  57. package/templates/empty/node_modules/.bin/vite +21 -0
  58. package/templates/empty/node_modules/.vite/deps/_metadata.json +50 -0
  59. package/templates/empty/node_modules/.vite/deps/package.json +3 -0
  60. package/templates/empty/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  61. package/templates/empty/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  62. package/templates/empty/node_modules/.vite/deps/react-dom.js +185 -0
  63. package/templates/empty/node_modules/.vite/deps/react-dom.js.map +1 -0
  64. package/templates/empty/node_modules/.vite/deps/react-dom_client.js +14384 -0
  65. package/templates/empty/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  66. package/templates/empty/node_modules/.vite/deps/react.js +2 -0
  67. package/templates/empty/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  68. package/templates/empty/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  69. package/templates/empty/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  70. package/templates/empty/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  71. package/templates/empty/node_modules/.vite/deps/valibot.js +6623 -0
  72. package/templates/empty/node_modules/.vite/deps/valibot.js.map +1 -0
  73. package/templates/empty/package.json +28 -0
  74. package/templates/empty/src/index.tsx +12 -0
  75. package/templates/empty/tsconfig.json +17 -0
  76. package/templates/empty/vite.config.ts +7 -0
  77. package/templates/gantt-chart/node_modules/.bin/tsc +21 -0
  78. package/templates/gantt-chart/node_modules/.bin/tsserver +21 -0
  79. package/templates/gantt-chart/node_modules/.bin/vite +21 -0
  80. package/templates/gantt-chart/node_modules/.vite/deps/_metadata.json +50 -0
  81. package/templates/gantt-chart/node_modules/.vite/deps/package.json +3 -0
  82. package/templates/gantt-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  83. package/templates/gantt-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  84. package/templates/gantt-chart/node_modules/.vite/deps/react-dom.js +185 -0
  85. package/templates/gantt-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  86. package/templates/gantt-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  87. package/templates/gantt-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  88. package/templates/gantt-chart/node_modules/.vite/deps/react.js +2 -0
  89. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  90. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  91. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  92. package/templates/gantt-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  93. package/templates/gantt-chart/node_modules/.vite/deps/valibot.js +6623 -0
  94. package/templates/gantt-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  95. package/templates/hello-world/node_modules/.bin/tsc +21 -0
  96. package/templates/hello-world/node_modules/.bin/tsserver +21 -0
  97. package/templates/hello-world/node_modules/.bin/vite +21 -0
  98. package/templates/hello-world/node_modules/.vite/deps/_metadata.json +50 -0
  99. package/templates/hello-world/node_modules/.vite/deps/package.json +3 -0
  100. package/templates/hello-world/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  101. package/templates/hello-world/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  102. package/templates/hello-world/node_modules/.vite/deps/react-dom.js +185 -0
  103. package/templates/hello-world/node_modules/.vite/deps/react-dom.js.map +1 -0
  104. package/templates/hello-world/node_modules/.vite/deps/react-dom_client.js +14384 -0
  105. package/templates/hello-world/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  106. package/templates/hello-world/node_modules/.vite/deps/react.js +2 -0
  107. package/templates/hello-world/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  108. package/templates/hello-world/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  109. package/templates/hello-world/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  110. package/templates/hello-world/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  111. package/templates/hello-world/node_modules/.vite/deps/valibot.js +6623 -0
  112. package/templates/hello-world/node_modules/.vite/deps/valibot.js.map +1 -0
  113. package/templates/interactive-resize/node_modules/.bin/tsc +21 -0
  114. package/templates/interactive-resize/node_modules/.bin/tsserver +21 -0
  115. package/templates/interactive-resize/node_modules/.bin/vite +21 -0
  116. package/templates/interactive-resize/node_modules/.vite/deps/_metadata.json +50 -0
  117. package/templates/interactive-resize/node_modules/.vite/deps/package.json +3 -0
  118. package/templates/interactive-resize/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  119. package/templates/interactive-resize/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  120. package/templates/interactive-resize/node_modules/.vite/deps/react-dom.js +185 -0
  121. package/templates/interactive-resize/node_modules/.vite/deps/react-dom.js.map +1 -0
  122. package/templates/interactive-resize/node_modules/.vite/deps/react-dom_client.js +14384 -0
  123. package/templates/interactive-resize/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  124. package/templates/interactive-resize/node_modules/.vite/deps/react.js +2 -0
  125. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  126. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  127. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  128. package/templates/interactive-resize/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  129. package/templates/interactive-resize/node_modules/.vite/deps/valibot.js +6623 -0
  130. package/templates/interactive-resize/node_modules/.vite/deps/valibot.js.map +1 -0
  131. package/templates/org-chart/node_modules/.bin/tsc +21 -0
  132. package/templates/org-chart/node_modules/.bin/tsserver +21 -0
  133. package/templates/org-chart/node_modules/.bin/vite +21 -0
  134. package/templates/org-chart/node_modules/.vite/deps/_metadata.json +50 -0
  135. package/templates/org-chart/node_modules/.vite/deps/package.json +3 -0
  136. package/templates/org-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  137. package/templates/org-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  138. package/templates/org-chart/node_modules/.vite/deps/react-dom.js +185 -0
  139. package/templates/org-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  140. package/templates/org-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  141. package/templates/org-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  142. package/templates/org-chart/node_modules/.vite/deps/react.js +2 -0
  143. package/templates/org-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  144. package/templates/org-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  145. package/templates/org-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  146. package/templates/org-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  147. package/templates/org-chart/node_modules/.vite/deps/valibot.js +6623 -0
  148. package/templates/org-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  149. package/templates/radar-chart/README.md +55 -0
  150. package/templates/radar-chart/_gitignore +2 -0
  151. package/templates/radar-chart/custom_blocks.json +34 -0
  152. package/templates/radar-chart/dist/assets/index-DOf05oXg.css +2 -0
  153. package/templates/radar-chart/dist/assets/index-DWpNd1qt.js +9 -0
  154. package/templates/radar-chart/dist/custom_blocks.json +34 -0
  155. package/templates/radar-chart/dist/index.html +16 -0
  156. package/templates/radar-chart/index.html +15 -0
  157. package/templates/radar-chart/node_modules/.bin/esbuild +21 -0
  158. package/templates/radar-chart/node_modules/.bin/jiti +21 -0
  159. package/templates/radar-chart/node_modules/.bin/tsc +21 -0
  160. package/templates/radar-chart/node_modules/.bin/tsserver +21 -0
  161. package/templates/radar-chart/node_modules/.bin/tsx +21 -0
  162. package/templates/radar-chart/node_modules/.bin/vite +21 -0
  163. package/templates/radar-chart/node_modules/.vite/deps/_metadata.json +50 -0
  164. package/templates/radar-chart/node_modules/.vite/deps/package.json +3 -0
  165. package/templates/radar-chart/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  166. package/templates/radar-chart/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  167. package/templates/radar-chart/node_modules/.vite/deps/react-dom.js +185 -0
  168. package/templates/radar-chart/node_modules/.vite/deps/react-dom.js.map +1 -0
  169. package/templates/radar-chart/node_modules/.vite/deps/react-dom_client.js +14384 -0
  170. package/templates/radar-chart/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  171. package/templates/radar-chart/node_modules/.vite/deps/react.js +2 -0
  172. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  173. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  174. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  175. package/templates/radar-chart/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  176. package/templates/radar-chart/node_modules/.vite/deps/valibot.js +6623 -0
  177. package/templates/radar-chart/node_modules/.vite/deps/valibot.js.map +1 -0
  178. package/templates/radar-chart/package.json +30 -0
  179. package/templates/radar-chart/src/index.css +44 -0
  180. package/templates/radar-chart/src/index.tsx +531 -0
  181. package/templates/radar-chart/tsconfig.json +17 -0
  182. package/templates/radar-chart/vite.config.ts +8 -0
  183. package/templates/table-view/README.md +43 -0
  184. package/templates/table-view/_gitignore +2 -0
  185. package/templates/table-view/custom_blocks.json +9 -0
  186. package/templates/table-view/dist/assets/index-Bd8u_e4X.js +12 -0
  187. package/templates/table-view/dist/assets/index-BkZn3aQZ.css +1 -0
  188. package/templates/table-view/dist/custom_blocks.json +9 -0
  189. package/templates/table-view/dist/index.html +16 -0
  190. package/templates/table-view/index.html +15 -0
  191. package/templates/table-view/node_modules/.bin/esbuild +21 -0
  192. package/templates/table-view/node_modules/.bin/jiti +21 -0
  193. package/templates/table-view/node_modules/.bin/rollup +21 -0
  194. package/templates/table-view/node_modules/.bin/tsc +21 -0
  195. package/templates/table-view/node_modules/.bin/tsserver +21 -0
  196. package/templates/table-view/node_modules/.bin/tsx +21 -0
  197. package/templates/table-view/node_modules/.bin/vite +21 -0
  198. package/templates/table-view/node_modules/.vite/deps/@tanstack_react-table.js +2809 -0
  199. package/templates/table-view/node_modules/.vite/deps/@tanstack_react-table.js.map +1 -0
  200. package/templates/table-view/node_modules/.vite/deps/_metadata.json +56 -0
  201. package/templates/table-view/node_modules/.vite/deps/package.json +3 -0
  202. package/templates/table-view/node_modules/.vite/deps/react-D5jdVkJj.js +790 -0
  203. package/templates/table-view/node_modules/.vite/deps/react-D5jdVkJj.js.map +1 -0
  204. package/templates/table-view/node_modules/.vite/deps/react-dom.js +185 -0
  205. package/templates/table-view/node_modules/.vite/deps/react-dom.js.map +1 -0
  206. package/templates/table-view/node_modules/.vite/deps/react-dom_client.js +14384 -0
  207. package/templates/table-view/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  208. package/templates/table-view/node_modules/.vite/deps/react.js +2 -0
  209. package/templates/table-view/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  210. package/templates/table-view/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  211. package/templates/table-view/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  212. package/templates/table-view/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  213. package/templates/table-view/node_modules/.vite/deps/valibot.js +6623 -0
  214. package/templates/table-view/node_modules/.vite/deps/valibot.js.map +1 -0
  215. package/templates/table-view/package.json +31 -0
  216. package/templates/table-view/src/index.css +256 -0
  217. package/templates/table-view/src/index.tsx +1814 -0
  218. package/templates/table-view/src/table-model.ts +663 -0
  219. package/templates/table-view/tsconfig.json +17 -0
  220. package/templates/table-view/vite.config.ts +8 -0
  221. package/templates/us-heatmap/node_modules/.bin/tsc +21 -0
  222. package/templates/us-heatmap/node_modules/.bin/tsserver +21 -0
  223. package/templates/us-heatmap/node_modules/.bin/vite +21 -0
  224. package/templates/us-heatmap/node_modules/.vite/deps/_metadata.json +50 -0
  225. package/templates/us-heatmap/node_modules/.vite/deps/package.json +3 -0
  226. package/templates/us-heatmap/node_modules/.vite/deps/react-CsV5wVHy.js +770 -0
  227. package/templates/us-heatmap/node_modules/.vite/deps/react-CsV5wVHy.js.map +1 -0
  228. package/templates/us-heatmap/node_modules/.vite/deps/react-dom.js +185 -0
  229. package/templates/us-heatmap/node_modules/.vite/deps/react-dom.js.map +1 -0
  230. package/templates/us-heatmap/node_modules/.vite/deps/react-dom_client.js +14384 -0
  231. package/templates/us-heatmap/node_modules/.vite/deps/react-dom_client.js.map +1 -0
  232. package/templates/us-heatmap/node_modules/.vite/deps/react.js +2 -0
  233. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-dev-runtime.js +204 -0
  234. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -0
  235. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-runtime.js +208 -0
  236. package/templates/us-heatmap/node_modules/.vite/deps/react_jsx-runtime.js.map +1 -0
  237. package/templates/us-heatmap/node_modules/.vite/deps/valibot.js +6623 -0
  238. package/templates/us-heatmap/node_modules/.vite/deps/valibot.js.map +1 -0
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # create-ncblock
2
+
3
+ > [!NOTE]
4
+ > Nothing to see here. This is **extreme** alpha and currently only works for an unreleased product.
5
+
6
+ Works with `bun create`, `npm create`, `pnpm create`, and `yarn create`.
7
+
8
+ ```
9
+ bun create ncblock my-view
10
+ ```
11
+
12
+ No GitHub authentication required — templates are bundled in the package.
13
+
14
+ > **Pre-release.** Breaking changes may land at any time before 1.0.
15
+
16
+ ## Usage
17
+
18
+ ```
19
+ bun create ncblock [destination] [options]
20
+ ```
21
+
22
+ ### Options
23
+
24
+ | Flag | Default | Description |
25
+ | ----------------------- | -------------------- | --------------------------------------------- |
26
+ | `--template` / `-t` | _(interactive)_ | Template to scaffold from. |
27
+ | `--name` / `-n` | template name | Package name for the new project. |
28
+ | `--git` / `--no-git` | _(interactive)_ | Initialize a git repo. |
29
+ | `--install` / `--no-install` | _(interactive)_ | Install dependencies after scaffolding. |
30
+
31
+ The first positional argument is the destination directory. If omitted, defaults to `./<name>`.
32
+
33
+ ### Examples
34
+
35
+ ```bash
36
+ # Interactive — pick a template, name, etc.
37
+ bun create ncblock
38
+
39
+ # Non-interactive — scaffold "empty" template into ./my-view
40
+ bun create ncblock ./my-view --template empty --name my-view --no-git --install
41
+
42
+ # Works with npm and pnpm too
43
+ npm create ncblock ./my-view -- --template empty --no-git --no-install
44
+ pnpm create ncblock ./my-view --template empty --no-git --no-install
45
+ ```
46
+
47
+ ## Requirements
48
+
49
+ - Node ≥ 18
package/bin/cli.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process"
3
+ import { createRequire } from "node:module"
4
+ import { dirname, resolve } from "node:path"
5
+
6
+ const require = createRequire(import.meta.url)
7
+ let tsxBin
8
+ try {
9
+ const tsxPkg = require.resolve("tsx/package.json")
10
+ tsxBin = resolve(dirname(tsxPkg), "dist/cli.mjs")
11
+ } catch {
12
+ process.stderr.write(
13
+ "✗ Could not locate the bundled tsx runtime. Reinstall create-ncblock.\n",
14
+ )
15
+ process.exit(1)
16
+ }
17
+
18
+ const entryPath = resolve(
19
+ dirname(new URL(import.meta.url).pathname),
20
+ "..",
21
+ "scripts",
22
+ "init.ts",
23
+ )
24
+ const result = spawnSync(
25
+ "node",
26
+ [tsxBin, entryPath, ...process.argv.slice(2)],
27
+ {
28
+ stdio: "inherit",
29
+ env: process.env,
30
+ },
31
+ )
32
+
33
+ process.exit(result.status ?? 1)
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "create-ncblock",
3
+ "version": "0.0.1",
4
+ "description": "Create a Notion custom view block project.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-ncblock": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "scripts",
12
+ "templates",
13
+ "sdk-version.json",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "dependencies": {
20
+ "tsx": "^4.21.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }
@@ -0,0 +1,527 @@
1
+ import { execSync } from "child_process"
2
+ import { basename, dirname, resolve } from "path"
3
+ import {
4
+ clearScreenDown,
5
+ createInterface,
6
+ cursorTo,
7
+ emitKeypressEvents,
8
+ moveCursor,
9
+ } from "readline"
10
+ import { fileURLToPath } from "url"
11
+ import {
12
+ getTemplateByName,
13
+ getTemplates,
14
+ scaffoldTemplate,
15
+ type TemplateMetadata,
16
+ } from "./utils/templates"
17
+
18
+ function detectPM(): string {
19
+ const ua = process.env.npm_config_user_agent ?? ""
20
+ if (ua.startsWith("pnpm")) {
21
+ return "pnpm"
22
+ }
23
+ if (ua.startsWith("bun")) {
24
+ return "bun"
25
+ }
26
+ if (ua.startsWith("npm")) {
27
+ return "npm"
28
+ }
29
+ for (const pm of ["pnpm", "bun", "npm"]) {
30
+ try {
31
+ execSync(`${pm} --version`, { stdio: "ignore" })
32
+ return pm
33
+ } catch {}
34
+ }
35
+ return "pnpm"
36
+ }
37
+
38
+ function parseArgs(argv: string[]) {
39
+ const args: Record<string, string | boolean> = {}
40
+ const positional: string[] = []
41
+ let i = 2
42
+ while (i < argv.length) {
43
+ const arg = argv[i]
44
+ if (arg === "--template" || arg === "-t") {
45
+ args.template = argv[++i]
46
+ } else if (arg === "--name" || arg === "-n") {
47
+ args.name = argv[++i]
48
+ } else if (arg === "--git") {
49
+ args.git = true
50
+ } else if (arg === "--no-git") {
51
+ args.git = false
52
+ } else if (arg === "--install") {
53
+ args.install = true
54
+ } else if (arg === "--no-install") {
55
+ args.install = false
56
+ } else if (!arg.startsWith("-")) {
57
+ positional.push(arg)
58
+ }
59
+ i++
60
+ }
61
+ return { args, positional }
62
+ }
63
+
64
+ const pm = detectPM()
65
+ const __dirname = dirname(fileURLToPath(import.meta.url))
66
+ const root = resolve(__dirname, "..")
67
+ const templateBaseDir = resolve(root, "templates")
68
+ const templates = getTemplates(templateBaseDir)
69
+
70
+ const c = {
71
+ reset: "\x1b[0m",
72
+ dim: "\x1b[2m",
73
+ bold: "\x1b[1m",
74
+ green: "\x1b[32m",
75
+ cyan: "\x1b[36m",
76
+ yellow: "\x1b[33m",
77
+ }
78
+
79
+ let rl: ReturnType<typeof createInterface> | undefined
80
+
81
+ function getPromptInterface() {
82
+ rl ??= createInterface({ input: process.stdin, output: process.stdout })
83
+ return rl
84
+ }
85
+
86
+ function closePromptInterface() {
87
+ rl?.close()
88
+ rl = undefined
89
+ }
90
+
91
+ function question(prompt: string): Promise<string> {
92
+ return new Promise(res => {
93
+ getPromptInterface().question(prompt, res)
94
+ })
95
+ }
96
+
97
+ function truncateText(value: string, maxLength: number): string {
98
+ if (maxLength <= 0) {
99
+ return ""
100
+ }
101
+ if (value.length <= maxLength) {
102
+ return value
103
+ }
104
+ if (maxLength === 1) {
105
+ return "…"
106
+ }
107
+ return `${value.slice(0, maxLength - 1)}…`
108
+ }
109
+
110
+ function wrapText(value: string, maxWidth: number): string[] {
111
+ if (maxWidth <= 0) {
112
+ return [value]
113
+ }
114
+
115
+ const words = value.trim().split(/\s+/).filter(Boolean)
116
+ if (words.length === 0) {
117
+ return [""]
118
+ }
119
+
120
+ const lines: string[] = []
121
+ let currentLine = ""
122
+
123
+ for (const word of words) {
124
+ if (word.length > maxWidth) {
125
+ if (currentLine) {
126
+ lines.push(currentLine)
127
+ currentLine = ""
128
+ }
129
+ let remaining = word
130
+ while (remaining.length > maxWidth) {
131
+ lines.push(remaining.slice(0, maxWidth - 1) + "…")
132
+ remaining = remaining.slice(maxWidth - 1)
133
+ }
134
+ currentLine = remaining
135
+ continue
136
+ }
137
+
138
+ const nextLine = currentLine ? `${currentLine} ${word}` : word
139
+ if (nextLine.length <= maxWidth) {
140
+ currentLine = nextLine
141
+ continue
142
+ }
143
+
144
+ lines.push(currentLine)
145
+ currentLine = word
146
+ }
147
+
148
+ if (currentLine) {
149
+ lines.push(currentLine)
150
+ }
151
+
152
+ return lines
153
+ }
154
+
155
+ function validateProjectName(value: string): string | undefined {
156
+ if (value.trim().length === 0) {
157
+ return "Project name cannot be empty."
158
+ }
159
+ if (!/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(value)) {
160
+ return "Use a valid package name: lowercase, no spaces."
161
+ }
162
+ return undefined
163
+ }
164
+
165
+ function validateLocation(value: string): string | undefined {
166
+ return value.trim().length > 0 ? undefined : "Location cannot be empty."
167
+ }
168
+
169
+ async function ask(
170
+ label: string,
171
+ fallback: string,
172
+ override?: string,
173
+ validate?: (value: string) => string | undefined,
174
+ ): Promise<string> {
175
+ if (override !== undefined) {
176
+ return override
177
+ }
178
+ while (true) {
179
+ const answer = (
180
+ await question(
181
+ ` ${c.cyan}${label}${c.reset} ${c.dim}(${fallback})${c.reset} `,
182
+ )
183
+ ).trim()
184
+ const value = answer || fallback
185
+ const error = validate?.(value)
186
+ if (!error) {
187
+ return value
188
+ }
189
+ console.log(` ${c.yellow}${error}${c.reset}`)
190
+ }
191
+ }
192
+
193
+ async function confirm(
194
+ label: string,
195
+ override?: boolean,
196
+ defaultYes = true,
197
+ ): Promise<boolean> {
198
+ if (override !== undefined) {
199
+ return override
200
+ }
201
+ while (true) {
202
+ const hint = defaultYes
203
+ ? `${c.dim}(Y/n)${c.reset}`
204
+ : `${c.dim}(y/N)${c.reset}`
205
+ const answer = (await question(` ${c.cyan}${label}${c.reset} ${hint} `))
206
+ .trim()
207
+ .toLowerCase()
208
+ if (!answer) {
209
+ return defaultYes
210
+ }
211
+ if (answer === "y" || answer === "yes") {
212
+ return true
213
+ }
214
+ if (answer === "n" || answer === "no") {
215
+ return false
216
+ }
217
+ console.log(` ${c.yellow}Please enter y or n.${c.reset}`)
218
+ }
219
+ }
220
+
221
+ const step = (msg: string) => console.log(`\n ${c.green}✓${c.reset} ${msg}`)
222
+
223
+ function formatTemplateBadges(template: TemplateMetadata): string {
224
+ return template.recommended ? "recommended" : ""
225
+ }
226
+
227
+ function formatTemplateLabel(template: TemplateMetadata): string {
228
+ const badges = formatTemplateBadges(template)
229
+ return `${template.title}${badges ? ` · ${badges}` : ""}`
230
+ }
231
+
232
+ function printTemplateCatalog() {
233
+ console.log(` ${c.bold}Available templates${c.reset}`)
234
+ for (const template of templates) {
235
+ console.log(
236
+ ` ${c.cyan}${template.name}${c.reset} ${c.dim}(${formatTemplateLabel(template)})${c.reset}`,
237
+ )
238
+ console.log(` ${template.description}`)
239
+ }
240
+ console.log("")
241
+ }
242
+
243
+ function printTemplateError(templateName: string) {
244
+ console.error(`\n Template "${templateName}" not found.`)
245
+ console.error(` Available templates:\n`)
246
+ for (const template of templates) {
247
+ console.error(` - ${template.name} (${formatTemplateLabel(template)})`)
248
+ console.error(` ${template.description}`)
249
+ }
250
+ }
251
+
252
+ function formatShellPath(path: string): string {
253
+ if (!/[\s"'`$\\]/.test(path)) {
254
+ return path
255
+ }
256
+ return `"${path.replace(/["\\$`]/g, "\\$&")}"`
257
+ }
258
+
259
+ function formatTemplatePickerLines(
260
+ index: number,
261
+ template: TemplateMetadata,
262
+ selected: boolean,
263
+ ): string[] {
264
+ const cursor = selected ? `${c.green}>${c.reset}` : " "
265
+ const number = `${index + 1}.`
266
+ const name = selected ? `${c.bold}${template.name}${c.reset}` : template.name
267
+ const badge = template.recommended ? ` ${c.dim}[recommended]${c.reset}` : ""
268
+ const columns = process.stdout.columns ?? 80
269
+ const descriptionPrefix = " "
270
+ const descriptionWidth = Math.max(20, columns - descriptionPrefix.length - 2)
271
+ const wrappedDescription = wrapText(template.description, descriptionWidth)
272
+
273
+ return [
274
+ ` ${cursor} ${number} ${name}${badge}`,
275
+ ...wrappedDescription.map(
276
+ line => `${descriptionPrefix}${c.dim}${line}${c.reset}`,
277
+ ),
278
+ ]
279
+ }
280
+
281
+ async function selectTemplateInteractively(
282
+ templateList: TemplateMetadata[],
283
+ defaultTemplate: TemplateMetadata,
284
+ ): Promise<TemplateMetadata> {
285
+ closePromptInterface()
286
+
287
+ const defaultIndex = Math.max(
288
+ 0,
289
+ templateList.findIndex(template => template.name === defaultTemplate.name),
290
+ )
291
+ const maxDirectJump = Math.min(templateList.length, 9)
292
+
293
+ return new Promise(resolve => {
294
+ let selectedIndex = defaultIndex
295
+ let renderedLineCount = 0
296
+
297
+ const render = () => {
298
+ const lines = [
299
+ ` ${c.bold}Choose a template${c.reset}`,
300
+ ` ${c.dim}Use up/down (or j/k), Enter to select${maxDirectJump > 0 ? `, 1-${maxDirectJump} to jump` : ""}.${c.reset}`,
301
+ "",
302
+ ...templateList.flatMap((template, index) =>
303
+ formatTemplatePickerLines(index, template, index === selectedIndex),
304
+ ),
305
+ "",
306
+ ]
307
+
308
+ if (renderedLineCount > 0) {
309
+ moveCursor(process.stdout, 0, -renderedLineCount)
310
+ cursorTo(process.stdout, 0)
311
+ clearScreenDown(process.stdout)
312
+ }
313
+
314
+ process.stdout.write(lines.join("\n") + "\n")
315
+ renderedLineCount = lines.length
316
+ }
317
+
318
+ const cleanup = () => {
319
+ process.stdout.write(`${c.reset}\x1b[?25h`)
320
+ if (renderedLineCount > 0) {
321
+ moveCursor(process.stdout, 0, -renderedLineCount)
322
+ cursorTo(process.stdout, 0)
323
+ clearScreenDown(process.stdout)
324
+ }
325
+ process.stdin.off("keypress", handleKeypress)
326
+ if (process.stdin.isTTY) {
327
+ process.stdin.setRawMode(false)
328
+ }
329
+ process.stdin.pause()
330
+ }
331
+
332
+ const handleKeypress = (
333
+ input: string,
334
+ key: { ctrl?: boolean; name?: string },
335
+ ) => {
336
+ if (key.ctrl && key.name === "c") {
337
+ cleanup()
338
+ process.stdout.write("\n")
339
+ process.exit(1)
340
+ }
341
+
342
+ if (key.name === "up" || input === "k") {
343
+ selectedIndex =
344
+ (selectedIndex - 1 + templateList.length) % templateList.length
345
+ render()
346
+ return
347
+ }
348
+
349
+ if (key.name === "down" || input === "j") {
350
+ selectedIndex = (selectedIndex + 1) % templateList.length
351
+ render()
352
+ return
353
+ }
354
+
355
+ if (input && /^[1-9]$/.test(input)) {
356
+ const jumpIndex = Number(input) - 1
357
+ if (jumpIndex < templateList.length) {
358
+ selectedIndex = jumpIndex
359
+ render()
360
+ }
361
+ return
362
+ }
363
+
364
+ if (key.name === "return") {
365
+ const selectedTemplate = templateList[selectedIndex]
366
+ cleanup()
367
+ resolve(selectedTemplate)
368
+ }
369
+ }
370
+
371
+ emitKeypressEvents(process.stdin)
372
+ process.stdout.write("\x1b[?25l")
373
+ if (process.stdin.isTTY) {
374
+ process.stdin.setRawMode(true)
375
+ }
376
+ process.stdin.resume()
377
+ process.stdin.on("keypress", handleKeypress)
378
+ render()
379
+ })
380
+ }
381
+
382
+ async function selectTemplate(
383
+ defaultTemplate: TemplateMetadata,
384
+ override?: string,
385
+ ): Promise<TemplateMetadata> {
386
+ if (override !== undefined) {
387
+ const selectedTemplate = getTemplateByName(templateBaseDir, override)
388
+ if (!selectedTemplate) {
389
+ printTemplateError(override)
390
+ process.exit(1)
391
+ }
392
+ return selectedTemplate
393
+ }
394
+
395
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
396
+ printTemplateCatalog()
397
+ const templateName = await ask(
398
+ "Template:",
399
+ defaultTemplate.name,
400
+ undefined,
401
+ value =>
402
+ getTemplateByName(templateBaseDir, value)
403
+ ? undefined
404
+ : `Choose one of: ${templates.map(template => template.name).join(", ")}`,
405
+ )
406
+ return getTemplateByName(templateBaseDir, templateName) ?? defaultTemplate
407
+ }
408
+
409
+ return selectTemplateInteractively(templates, defaultTemplate)
410
+ }
411
+
412
+ function printSummary(args: {
413
+ dest: string
414
+ name: string
415
+ template: TemplateMetadata
416
+ initGit: boolean
417
+ installDeps: boolean
418
+ }) {
419
+ const { dest, name, template, initGit, installDeps } = args
420
+ console.log(`\n ${c.bold}Summary${c.reset}`)
421
+ console.log(` ${c.dim}Location:${c.reset} ${dest}`)
422
+ console.log(` ${c.dim}Project name:${c.reset} ${name}`)
423
+ console.log(
424
+ ` ${c.dim}Template:${c.reset} ${template.name} ${c.dim}(${template.title})${c.reset}`,
425
+ )
426
+ console.log(` ${c.dim}Git:${c.reset} ${initGit ? "yes" : "no"}`)
427
+ console.log(` ${c.dim}Install:${c.reset} ${installDeps ? "yes" : "no"}`)
428
+ }
429
+
430
+ async function main() {
431
+ const { args, positional } = parseArgs(process.argv)
432
+ if (templates.length === 0) {
433
+ throw new Error("No templates found in templates/")
434
+ }
435
+ const defaultTemplate =
436
+ templates.find(template => template.recommended) ?? templates[0]
437
+
438
+ console.log(`\n${c.bold} Create a new custom view${c.reset}\n`)
439
+
440
+ const selectedTemplate = await selectTemplate(
441
+ defaultTemplate,
442
+ args.template as string | undefined,
443
+ )
444
+ const name = await ask(
445
+ "Project name:",
446
+ selectedTemplate.name,
447
+ args.name as string | undefined,
448
+ validateProjectName,
449
+ )
450
+ const dir = await ask(
451
+ "Location:",
452
+ positional[0] || `./${name}`,
453
+ positional[0],
454
+ validateLocation,
455
+ )
456
+ const dest = resolve(dir)
457
+ const templateDir = resolve(templateBaseDir, selectedTemplate.name)
458
+
459
+ step(
460
+ `Using template ${c.bold}${selectedTemplate.name}${c.reset} ${c.dim}(${selectedTemplate.title})${c.reset}`,
461
+ )
462
+ console.log(` ${selectedTemplate.description}`)
463
+
464
+ const initGit = await confirm(
465
+ "Initialize git repo?",
466
+ args.git as boolean | undefined,
467
+ )
468
+ const installDeps = await confirm(
469
+ "Install dependencies?",
470
+ args.install as boolean | undefined,
471
+ )
472
+
473
+ printSummary({
474
+ dest,
475
+ name,
476
+ template: selectedTemplate,
477
+ initGit,
478
+ installDeps,
479
+ })
480
+
481
+ scaffoldTemplate({ root, templateDir, dest, name })
482
+
483
+ step(`Scaffolded project in ${c.bold}${dest}${c.reset}`)
484
+
485
+ if (initGit) {
486
+ execSync("git init", { cwd: dest, stdio: "ignore" })
487
+ execSync("git add -A", { cwd: dest, stdio: "ignore" })
488
+ execSync('git commit -m "Initial commit"', { cwd: dest, stdio: "ignore" })
489
+ step("Initialized git repo")
490
+ }
491
+
492
+ if (installDeps) {
493
+ console.log("")
494
+ const installArgs = pm === "pnpm" ? "install --ignore-workspace" : "install"
495
+ execSync(`${pm} ${installArgs}`, { cwd: dest, stdio: "inherit" })
496
+ step("Installed dependencies")
497
+ }
498
+
499
+ const blockId = process.env.BLOCK_ID
500
+ const envName = process.env.ENV
501
+ const deployCommand =
502
+ blockId && envName
503
+ ? `NOTION_KEYRING=0 ntn --env ${envName} custom deploy --block ${blockId} dist/`
504
+ : blockId
505
+ ? `ntn custom deploy --block ${blockId} dist/`
506
+ : undefined
507
+
508
+ console.log(`\n${c.bold} Ready!${c.reset} Next steps:\n`)
509
+ if (dest !== resolve(".")) {
510
+ console.log(` cd ${formatShellPath(dest)}`)
511
+ }
512
+ if (!installDeps) {
513
+ console.log(` ${pm} install`)
514
+ }
515
+ console.log(` ${pm} run build`)
516
+ if (deployCommand) {
517
+ console.log(` ${deployCommand}\n`)
518
+ } else {
519
+ console.log(
520
+ `\n Then, go back to the instructions in Notion and paste the ${c.bold}ntn deploy${c.reset} command.\n`,
521
+ )
522
+ }
523
+
524
+ closePromptInterface()
525
+ }
526
+
527
+ main()
@@ -0,0 +1,65 @@
1
+ # {{name}}
2
+
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
+
5
+ ## Talking to the host
6
+
7
+ - Always use the React hooks from `ncblock`. Never call `window.parent.postMessage` directly — the SDK owns the protocol.
8
+ - Render app code inside `<NotionCustomBlock>` so the handshake completes before hooks like `useTheme` or `useCustomBlockContext` run.
9
+
10
+ Hooks at a glance:
11
+
12
+ - `useCustomBlockContext()` — `{ customBlockId, parent, page }`.
13
+ - `useTheme()` — `"light" | "dark"`.
14
+ - `useDataSource(key, initialLimit?)` — `{ items, isLoading, hasMore, fetchMore, error }`.
15
+ - `useDataSourceDefinitions()` — resolved data-source definitions (for debug/schema-driven UIs).
16
+ - `pages.create(input)` — creates a page; pass `parent: { type: "data_source_key", key }` to target the block's wired data source.
17
+
18
+ `<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.
19
+
20
+ ## Sizing
21
+
22
+ - The host owns width and height — no hard-coded widths. Layouts must reflow from a phone column to a desktop block.
23
+ - `100vh` ≠ a screen inside an iframe. Use intrinsic sizing with `min-height`.
24
+ - Use container queries (`@container`) — iframe width, not device width, is the real constraint.
25
+
26
+ ## Forbidden APIs
27
+
28
+ - No top-level navigation, `window.open`, or auth redirects.
29
+ - No network requests. All data comes through host data sources.
30
+
31
+ ## Conventions
32
+
33
+ - Mount into `<div id="root">` — `useCustomBlockAutoResize` looks for that exact id.
34
+ - `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.
35
+
36
+ ## Previewing during development
37
+
38
+ Two options:
39
+
40
+ 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.
41
+ 2. **Mock host** — run the dev shell in another tab:
42
+ ```bash
43
+ git clone https://github.com/makenotion/custom
44
+ cd custom && pnpm install && pnpm run dev:shell # http://localhost:9875
45
+ ```
46
+
47
+ ## Deployment
48
+
49
+ Ask the user to create a block via `/custom` and copy the deploy command. It looks like:
50
+
51
+ ```
52
+ ntn deploy --block <block-id> <dist-path>
53
+ ```
54
+
55
+ ## Where to look next
56
+
57
+ - `node_modules/ncblock/README.md` — landing page with a TOC into the per-category docs below.
58
+ - `node_modules/ncblock/docs/lifecycle.md` — `<NotionCustomBlock>`, init, sizing, auto-resize.
59
+ - `node_modules/ncblock/docs/context.md` — `useCustomBlockContext`, `useTheme`.
60
+ - `node_modules/ncblock/docs/data-sources.md` — `useDataSource`, row/property/date types, worked example.
61
+ - `node_modules/ncblock/docs/pages.md` — `pages.create / get / update / delete`.
62
+ - `node_modules/ncblock/docs/users.md` — `users.list / get`, `NotionUser`.
63
+ - `node_modules/ncblock/docs/manifest.md` — `custom_blocks.json` + Vite plugin.
64
+ - `node_modules/ncblock/dist/*.d.ts` — typed surface; hover in your editor.
65
+ - https://github.com/makenotion/custom — source and examples.