create-ncblock 0.0.14 → 0.0.16
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 +1 -1
- package/scripts/init.ts +136 -29
- package/scripts/scaffold-assets/AGENTS.md +14 -4
- package/sdk-version.json +1 -1
- package/templates/debug/src/index.tsx +8 -2
package/package.json
CHANGED
package/scripts/init.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { execSync } from "child_process"
|
|
2
|
-
import { existsSync, readdirSync } from "fs"
|
|
1
|
+
import { execSync, spawnSync } from "child_process"
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs"
|
|
3
3
|
import { basename, dirname, resolve } from "path"
|
|
4
4
|
import {
|
|
5
5
|
clearScreenDown,
|
|
@@ -16,6 +16,63 @@ import {
|
|
|
16
16
|
type TemplateMetadata,
|
|
17
17
|
} from "./utils/templates"
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Seed `.notion/target.json` with whatever's known at scaffold time. Empty
|
|
21
|
+
* defaults are intentional — subsequent commands (`manifest pull`, `connect`,
|
|
22
|
+
* `deploy`) all merge into this file.
|
|
23
|
+
*/
|
|
24
|
+
type InitialTarget = {
|
|
25
|
+
env: string
|
|
26
|
+
block_id: string[]
|
|
27
|
+
data_sources: Record<
|
|
28
|
+
string,
|
|
29
|
+
{ data_source_id: string; property_ids_by_key: Record<string, string> }
|
|
30
|
+
>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeInitialTarget(
|
|
34
|
+
dest: string,
|
|
35
|
+
args: { env?: string; blockId?: string },
|
|
36
|
+
): void {
|
|
37
|
+
const target: InitialTarget = {
|
|
38
|
+
env: args.env ?? "production",
|
|
39
|
+
block_id: args.blockId ? [args.blockId] : [],
|
|
40
|
+
data_sources: {},
|
|
41
|
+
}
|
|
42
|
+
mkdirSync(resolve(dest, ".notion"), { recursive: true })
|
|
43
|
+
writeFileSync(
|
|
44
|
+
resolve(dest, ".notion/target.json"),
|
|
45
|
+
JSON.stringify(target, null, "\t") + "\n",
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Run the locally-installed `ncblock` CLI from a scaffolded project.
|
|
51
|
+
*
|
|
52
|
+
* We bypass `npx` / `pnpm exec` / `bunx` deliberately: those wrappers can
|
|
53
|
+
* spawn the binary in a way that lets `execSync` return before the child's
|
|
54
|
+
* stdout fully drains, which makes pull-manifest's output land out of order
|
|
55
|
+
* (e.g. after init has already exited). Invoking the .js entrypoint via
|
|
56
|
+
* `node` is synchronous and deterministic.
|
|
57
|
+
*
|
|
58
|
+
* Env is sourced from target.json (written by writeInitialTarget before
|
|
59
|
+
* any ncblock call) — no need to thread ENV through the spawn.
|
|
60
|
+
*/
|
|
61
|
+
function runNcblock(dest: string, args: string[]): void {
|
|
62
|
+
const result = spawnSync(
|
|
63
|
+
process.execPath,
|
|
64
|
+
[resolve(dest, "node_modules/ncblock/bin/cli/cli.js"), ...args],
|
|
65
|
+
{ cwd: dest, stdio: "inherit" },
|
|
66
|
+
)
|
|
67
|
+
if (result.status !== 0) {
|
|
68
|
+
// Child already printed a user-friendly message; throw so the caller's
|
|
69
|
+
// try/catch can suppress without us having to forward stderr.
|
|
70
|
+
throw new Error(
|
|
71
|
+
`ncblock ${args.join(" ")} exited with code ${result.status}`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
19
76
|
function detectPM(): string {
|
|
20
77
|
const ua = process.env.npm_config_user_agent ?? ""
|
|
21
78
|
if (ua.startsWith("pnpm")) {
|
|
@@ -61,9 +118,16 @@ function parseArgs(argv: string[]) {
|
|
|
61
118
|
args.force = true
|
|
62
119
|
} else if (arg === "--collection") {
|
|
63
120
|
args.collection = argv[++i]
|
|
121
|
+
} else if (arg === "--block") {
|
|
122
|
+
args.block = argv[++i]
|
|
123
|
+
} else if (arg === "--local-sdk") {
|
|
124
|
+
args.localSdk = true
|
|
64
125
|
} else if (!arg.startsWith("-")) {
|
|
126
|
+
// Positional Notion ID has always been captured as a block ID — the
|
|
127
|
+
// variable name (and the deploy hint that uses it) reflect that.
|
|
128
|
+
// Prefer --block when set; otherwise positional is the block.
|
|
65
129
|
if (NOTION_ID_RE.test(arg)) {
|
|
66
|
-
args.
|
|
130
|
+
args.block ??= arg
|
|
67
131
|
} else {
|
|
68
132
|
positional.push(arg)
|
|
69
133
|
}
|
|
@@ -499,6 +563,19 @@ async function main() {
|
|
|
499
563
|
args.install as boolean | undefined,
|
|
500
564
|
)
|
|
501
565
|
|
|
566
|
+
// Ask for the database URL/ID upfront — alongside the other prompts. The
|
|
567
|
+
// prompt itself only fires when we'll actually install (no manifest pull
|
|
568
|
+
// otherwise), but --collection is always honored.
|
|
569
|
+
const collectionAnswer =
|
|
570
|
+
(args.collection as string | undefined) ??
|
|
571
|
+
(installDeps
|
|
572
|
+
? (await ask("Database URL/ID (enter to skip):", "skip")).trim()
|
|
573
|
+
: "skip")
|
|
574
|
+
const collectionId =
|
|
575
|
+
collectionAnswer && collectionAnswer !== "skip"
|
|
576
|
+
? collectionAnswer
|
|
577
|
+
: undefined
|
|
578
|
+
|
|
502
579
|
printSummary({
|
|
503
580
|
dest,
|
|
504
581
|
name,
|
|
@@ -507,9 +584,29 @@ async function main() {
|
|
|
507
584
|
installDeps,
|
|
508
585
|
})
|
|
509
586
|
|
|
510
|
-
|
|
587
|
+
const useLocalSdk = args.localSdk === true
|
|
588
|
+
// Dynamic import: `pack-local-sdk` depends on `build-sdk`, which is not
|
|
589
|
+
// bundled into the published `create-ncblock` package. Resolving it
|
|
590
|
+
// statically here would crash `bun create ncblock`.
|
|
591
|
+
const sdkDep = useLocalSdk
|
|
592
|
+
? (await import("./utils/pack-local-sdk")).packLocalSdk(dest)
|
|
593
|
+
: undefined
|
|
594
|
+
|
|
595
|
+
scaffoldTemplate({
|
|
596
|
+
root,
|
|
597
|
+
templateDir,
|
|
598
|
+
dest,
|
|
599
|
+
name,
|
|
600
|
+
overwrite: destNonEmpty,
|
|
601
|
+
sdkDep,
|
|
602
|
+
})
|
|
511
603
|
|
|
512
604
|
step(`Scaffolded project in ${c.bold}${dest}${c.reset}`)
|
|
605
|
+
if (useLocalSdk) {
|
|
606
|
+
step(
|
|
607
|
+
`Using locally-packed SDK ${c.dim}(file:${sdkDep?.replace("file:./", "")})${c.reset}`,
|
|
608
|
+
)
|
|
609
|
+
}
|
|
513
610
|
|
|
514
611
|
if (initGit) {
|
|
515
612
|
execSync("git init", { cwd: dest, stdio: "ignore" })
|
|
@@ -518,45 +615,55 @@ async function main() {
|
|
|
518
615
|
step("Initialized git repo")
|
|
519
616
|
}
|
|
520
617
|
|
|
618
|
+
const blockId =
|
|
619
|
+
(args.block as string | undefined) || process.env.BLOCK_ID || undefined
|
|
620
|
+
const envName = process.env.ENV
|
|
621
|
+
|
|
622
|
+
// Always seed .notion/target.json so every project starts in a known
|
|
623
|
+
// state. env / block_id are populated from what we have; data_sources
|
|
624
|
+
// gets filled in by `manifest pull` if it runs.
|
|
625
|
+
writeInitialTarget(dest, { env: envName, blockId })
|
|
626
|
+
step(`Seeded .notion/target.json`)
|
|
627
|
+
|
|
521
628
|
if (installDeps) {
|
|
629
|
+
// Close the readline interface before shelling out — otherwise it
|
|
630
|
+
// holds onto stdin/stdout and the inherited stdio in child processes
|
|
631
|
+
// gets buffered, which makes pull-manifest output land out of order.
|
|
632
|
+
closePromptInterface()
|
|
633
|
+
|
|
522
634
|
console.log("")
|
|
523
635
|
const installArgs = pm === "pnpm" ? "install --ignore-workspace" : "install"
|
|
524
636
|
execSync(`${pm} ${installArgs}`, { cwd: dest, stdio: "inherit" })
|
|
525
637
|
step("Installed dependencies")
|
|
526
638
|
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
(args.blockId as string | undefined) ??
|
|
534
|
-
process.env.BLOCK_ID
|
|
535
|
-
const blockOrCollectionId =
|
|
536
|
-
explicitId ??
|
|
537
|
-
(await ask("Database URL/ID (enter to skip):", "skip")).trim()
|
|
538
|
-
if (blockOrCollectionId && blockOrCollectionId !== "skip") {
|
|
639
|
+
// `manifest pull` resolves a database / data source / collection_view
|
|
640
|
+
// block URL to a manifest entry AND merges the resolved data_source_id
|
|
641
|
+
// into target.json's data_sources. Then `connect` (no flags needed —
|
|
642
|
+
// reads everything from target.json) PATCHes the block if one's set.
|
|
643
|
+
let pulled = false
|
|
644
|
+
if (collectionId) {
|
|
539
645
|
console.log("")
|
|
540
646
|
try {
|
|
541
|
-
|
|
542
|
-
cwd: dest,
|
|
543
|
-
stdio: "inherit",
|
|
544
|
-
})
|
|
647
|
+
runNcblock(dest, ["manifest", "pull", collectionId])
|
|
545
648
|
step("Updated custom_blocks.json from collection schema")
|
|
649
|
+
pulled = true
|
|
546
650
|
} catch {
|
|
547
|
-
// pull
|
|
651
|
+
// manifest pull already printed a user-friendly message
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (pulled && blockId) {
|
|
656
|
+
console.log("")
|
|
657
|
+
try {
|
|
658
|
+
runNcblock(dest, ["connect"])
|
|
659
|
+
step("Wired block bindings (.notion/target.json + PATCH)")
|
|
660
|
+
} catch {
|
|
661
|
+
// connect already printed a user-friendly message
|
|
548
662
|
}
|
|
549
663
|
}
|
|
550
664
|
}
|
|
551
665
|
|
|
552
|
-
const
|
|
553
|
-
const envName = process.env.ENV
|
|
554
|
-
const deployCommand =
|
|
555
|
-
blockId && envName
|
|
556
|
-
? `NOTION_KEYRING=0 ntn --env ${envName} custom deploy --block ${blockId} dist/`
|
|
557
|
-
: blockId
|
|
558
|
-
? `ntn custom deploy --block ${blockId} dist/`
|
|
559
|
-
: undefined
|
|
666
|
+
const deployCommand = blockId ? `npx ncblock deploy dist/` : undefined
|
|
560
667
|
|
|
561
668
|
console.log(`\n${c.bold} Ready!${c.reset} Next steps:\n`)
|
|
562
669
|
if (dest !== resolve(".")) {
|
|
@@ -32,6 +32,7 @@ Hooks at a glance:
|
|
|
32
32
|
|
|
33
33
|
- Mount into `<div id="root">` — `useCustomBlockAutoResize` looks for that exact id.
|
|
34
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
|
+
- `custom_blocks.json` is the **portable contract** — no IDs. `.notion/target.json` holds the per-deploy IDs (block IDs, data-source IDs, resolved property IDs) and is committed alongside it.
|
|
35
36
|
|
|
36
37
|
## Previewing during development
|
|
37
38
|
|
|
@@ -44,13 +45,22 @@ Two options:
|
|
|
44
45
|
cd custom && pnpm install && pnpm run dev:shell # http://localhost:9875
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
##
|
|
48
|
+
## Connecting to Notion
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
`ncblock` cli provides helpers to wire a scaffolded project to a Notion block and data source. Wirings are saved to `.notion/target.json`. Check the output of the cli for latest syntax instead of copying these commands verbatim.
|
|
50
51
|
|
|
52
|
+
```bash
|
|
53
|
+
# Pull a schema manifest from a Notion database into custom_blocks.json
|
|
54
|
+
npx ncblock manifest pull <db-id-or-url>
|
|
55
|
+
|
|
56
|
+
# Connect a local project to a Notion block and/or data source.
|
|
57
|
+
npx ncblock connect
|
|
58
|
+
|
|
59
|
+
# Deploy a dist/ directory to a block.
|
|
60
|
+
npx ncblock deploy
|
|
51
61
|
```
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
|
|
63
|
+
Templates will often already have some of these set in `.notion/target.json`.
|
|
54
64
|
|
|
55
65
|
## Where to look next
|
|
56
66
|
|
package/sdk-version.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.0.
|
|
1
|
+
{"version":"0.0.14"}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type NotionPage,
|
|
8
8
|
type NotionPageId,
|
|
9
9
|
pages,
|
|
10
|
+
useCurrentUser,
|
|
10
11
|
useCustomBlockContext,
|
|
11
12
|
useDataSourceDefinitions,
|
|
12
13
|
useTheme,
|
|
@@ -47,6 +48,7 @@ const metaTextClass =
|
|
|
47
48
|
|
|
48
49
|
function App() {
|
|
49
50
|
const ctx = useCustomBlockContext()
|
|
51
|
+
const currentUser = useCurrentUser()
|
|
50
52
|
const hostThemeValue = useTheme()
|
|
51
53
|
const dataSources = useDataSourceDefinitions()
|
|
52
54
|
const [isPaused, setIsPaused] = useState(false)
|
|
@@ -78,6 +80,7 @@ function App() {
|
|
|
78
80
|
customBlockId: ctx?.customBlockId,
|
|
79
81
|
parent: ctx?.parent,
|
|
80
82
|
page: ctx?.page,
|
|
83
|
+
currentUser,
|
|
81
84
|
dataSources,
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -93,6 +96,9 @@ function App() {
|
|
|
93
96
|
["viewport", `${viewport.innerWidth} \u00d7 ${viewport.innerHeight}`],
|
|
94
97
|
["contentHeight", `${viewport.scrollHeight}px`],
|
|
95
98
|
["devicePixelRatio", String(viewport.devicePixelRatio)],
|
|
99
|
+
["currentUser.id", currentUser.id],
|
|
100
|
+
["currentUser.name", currentUser.name ?? ""],
|
|
101
|
+
["currentUser.email", currentUser.person.email],
|
|
96
102
|
]
|
|
97
103
|
|
|
98
104
|
return (
|
|
@@ -856,7 +862,7 @@ function FilterMenu({
|
|
|
856
862
|
</IconButton>
|
|
857
863
|
{open && (
|
|
858
864
|
<div className="absolute right-0 top-full z-10 mt-1 w-56 rounded-lg border border-(--border) bg-(--card-bg) p-2 shadow-(--toast-shadow)">
|
|
859
|
-
<div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-
|
|
865
|
+
<div className="mb-1 px-1 text-[10px] font-semibold uppercase tracking-wider opacity-40">
|
|
860
866
|
Direction
|
|
861
867
|
</div>
|
|
862
868
|
{directions.map(direction => (
|
|
@@ -875,7 +881,7 @@ function FilterMenu({
|
|
|
875
881
|
}}
|
|
876
882
|
/>
|
|
877
883
|
))}
|
|
878
|
-
<div className="mt-2 mb-1 px-1 text-[10px] font-semibold uppercase tracking-
|
|
884
|
+
<div className="mt-2 mb-1 px-1 text-[10px] font-semibold uppercase tracking-wider opacity-40">
|
|
879
885
|
Types
|
|
880
886
|
</div>
|
|
881
887
|
{availableTypes.length === 0 ? (
|