create-ncblock 0.0.17 → 0.0.18
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 +41 -46
- package/scripts/scaffold-assets/AGENTS.md +4 -2
- package/scripts/utils/classifyId.ts +227 -0
- package/scripts/utils/resolveInitArgs.ts +27 -36
- package/sdk-version.json +1 -1
package/package.json
CHANGED
package/scripts/init.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync, spawnSync } from "child_process"
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs"
|
|
3
|
-
import {
|
|
3
|
+
import { dirname, resolve } from "path"
|
|
4
4
|
import {
|
|
5
5
|
clearScreenDown,
|
|
6
6
|
createInterface,
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
moveCursor,
|
|
10
10
|
} from "readline"
|
|
11
11
|
import { fileURLToPath } from "url"
|
|
12
|
+
import {
|
|
13
|
+
type Classified,
|
|
14
|
+
classifyPositionalId,
|
|
15
|
+
EXIT_USER_ID_MISTAKE,
|
|
16
|
+
} from "./utils/classifyId"
|
|
12
17
|
import { resolveInitArgs } from "./utils/resolveInitArgs"
|
|
13
18
|
import {
|
|
14
19
|
getTemplateByName,
|
|
@@ -19,8 +24,8 @@ import {
|
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* Seed `.notion/target.json` with whatever's known at scaffold time. Empty
|
|
22
|
-
* defaults are intentional — subsequent
|
|
23
|
-
*
|
|
27
|
+
* defaults are intentional — subsequent `ncblock connect` / `deploy` calls
|
|
28
|
+
* merge into this file.
|
|
24
29
|
*/
|
|
25
30
|
type InitialTarget = {
|
|
26
31
|
env: string
|
|
@@ -58,6 +63,10 @@ function writeInitialTarget(
|
|
|
58
63
|
*
|
|
59
64
|
* Env is sourced from target.json (written by writeInitialTarget before
|
|
60
65
|
* any ncblock call) — no need to thread ENV through the spawn.
|
|
66
|
+
*
|
|
67
|
+
* Exits init immediately on `EXIT_USER_ID_MISTAKE` so we don't fall through
|
|
68
|
+
* to "Ready! Next steps:" with a broken target.json. Any other non-zero exit
|
|
69
|
+
* is thrown so the caller's try/catch can suppress without forwarding stderr.
|
|
61
70
|
*/
|
|
62
71
|
function runNcblock(dest: string, args: string[]): void {
|
|
63
72
|
const result = spawnSync(
|
|
@@ -65,9 +74,10 @@ function runNcblock(dest: string, args: string[]): void {
|
|
|
65
74
|
[resolve(dest, "node_modules/ncblock/bin/cli/cli.js"), ...args],
|
|
66
75
|
{ cwd: dest, stdio: "inherit" },
|
|
67
76
|
)
|
|
77
|
+
if (result.status === EXIT_USER_ID_MISTAKE) {
|
|
78
|
+
process.exit(EXIT_USER_ID_MISTAKE)
|
|
79
|
+
}
|
|
68
80
|
if (result.status !== 0) {
|
|
69
|
-
// Child already printed a user-friendly message; throw so the caller's
|
|
70
|
-
// try/catch can suppress without us having to forward stderr.
|
|
71
81
|
throw new Error(
|
|
72
82
|
`ncblock ${args.join(" ")} exited with code ${result.status}`,
|
|
73
83
|
)
|
|
@@ -564,11 +574,8 @@ async function main() {
|
|
|
564
574
|
args.install as boolean | undefined,
|
|
565
575
|
)
|
|
566
576
|
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
// Notion ID is almost always a block inside a collection_view (or the
|
|
570
|
-
// view block itself); `manifest pull`'s resolveId() handles both cases,
|
|
571
|
-
// so we can feed the block ID straight through without re-prompting.
|
|
577
|
+
// Precedence: --collection > --block/positional > prompt. Classification
|
|
578
|
+
// downstream walks a positional block/view ID up to its parent database.
|
|
572
579
|
const blockIdArg = args.block as string | undefined
|
|
573
580
|
const promptedCollection =
|
|
574
581
|
args.collection === undefined && blockIdArg === undefined && installDeps
|
|
@@ -585,9 +592,14 @@ async function main() {
|
|
|
585
592
|
block: blockIdArg,
|
|
586
593
|
envBlockId: process.env.BLOCK_ID,
|
|
587
594
|
envName: process.env.ENV,
|
|
588
|
-
installDeps,
|
|
589
595
|
})
|
|
590
|
-
const
|
|
596
|
+
const classified: Classified | null = resolved.idToClassify
|
|
597
|
+
? classifyPositionalId(resolved.idToClassify, resolved.env)
|
|
598
|
+
: null
|
|
599
|
+
// Explicit --collection wins over classification; classify normalizes
|
|
600
|
+
// the positional input to a dashed UUID and falls back to the raw value.
|
|
601
|
+
const blockId = classified?.blockId ?? resolved.blockId
|
|
602
|
+
const databaseId = resolved.databaseId ?? classified?.databaseId
|
|
591
603
|
|
|
592
604
|
printSummary({
|
|
593
605
|
dest,
|
|
@@ -628,17 +640,19 @@ async function main() {
|
|
|
628
640
|
step("Initialized git repo")
|
|
629
641
|
}
|
|
630
642
|
|
|
631
|
-
// Always seed .notion/target.json so every project starts in a known
|
|
632
|
-
// state. env / block_id are populated from what we have; data_sources
|
|
633
|
-
// gets filled in by `manifest pull` if it runs.
|
|
634
643
|
writeInitialTarget(dest, { env: resolved.env, blockId })
|
|
635
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
|
+
)
|
|
649
|
+
}
|
|
636
650
|
|
|
637
|
-
let
|
|
651
|
+
let connected = false
|
|
638
652
|
if (installDeps) {
|
|
639
653
|
// Close the readline interface before shelling out — otherwise it
|
|
640
654
|
// holds onto stdin/stdout and the inherited stdio in child processes
|
|
641
|
-
// gets buffered, which makes
|
|
655
|
+
// gets buffered, which makes ncblock output land out of order.
|
|
642
656
|
closePromptInterface()
|
|
643
657
|
|
|
644
658
|
console.log("")
|
|
@@ -646,26 +660,16 @@ async function main() {
|
|
|
646
660
|
execSync(`${pm} ${installArgs}`, { cwd: dest, stdio: "inherit" })
|
|
647
661
|
step("Installed dependencies")
|
|
648
662
|
|
|
649
|
-
|
|
650
|
-
// block URL to a manifest entry AND merges the resolved data_source_id
|
|
651
|
-
// into target.json's data_sources. Then `connect` (no flags needed —
|
|
652
|
-
// reads everything from target.json) PATCHes the block if one's set.
|
|
653
|
-
if (collectionId) {
|
|
663
|
+
if (databaseId) {
|
|
654
664
|
console.log("")
|
|
655
665
|
try {
|
|
656
|
-
runNcblock(dest, ["
|
|
657
|
-
step(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (pulled && blockId) {
|
|
665
|
-
console.log("")
|
|
666
|
-
try {
|
|
667
|
-
runNcblock(dest, ["connect"])
|
|
668
|
-
step("Wired block bindings (.notion/target.json + PATCH)")
|
|
666
|
+
runNcblock(dest, ["connect", databaseId, "--quiet"])
|
|
667
|
+
step(
|
|
668
|
+
blockId
|
|
669
|
+
? "Wired block bindings (custom_blocks.json + .notion/target.json + PATCH)"
|
|
670
|
+
: "Recorded data source in custom_blocks.json + .notion/target.json",
|
|
671
|
+
)
|
|
672
|
+
connected = true
|
|
669
673
|
} catch {
|
|
670
674
|
// connect already printed a user-friendly message
|
|
671
675
|
}
|
|
@@ -690,20 +694,11 @@ async function main() {
|
|
|
690
694
|
)
|
|
691
695
|
}
|
|
692
696
|
|
|
693
|
-
|
|
694
|
-
// pull/connect failure), surface the manual CLI commands explicitly.
|
|
695
|
-
// Agents read this scrollback — being concrete here keeps them from
|
|
696
|
-
// reinventing the bridge or hardcoding IDs.
|
|
697
|
-
if (installDeps && !pulled) {
|
|
697
|
+
if (installDeps && !connected) {
|
|
698
698
|
console.log(
|
|
699
699
|
`\n ${c.yellow}No data source connected yet.${c.reset} To wire one up:`,
|
|
700
700
|
)
|
|
701
|
-
console.log(` npx ncblock
|
|
702
|
-
if (!blockId) {
|
|
703
|
-
console.log(` npx ncblock connect --block <block-id-or-url>`)
|
|
704
|
-
} else {
|
|
705
|
-
console.log(` npx ncblock connect`)
|
|
706
|
-
}
|
|
701
|
+
console.log(` npx ncblock connect <database-url-or-id>`)
|
|
707
702
|
}
|
|
708
703
|
console.log("")
|
|
709
704
|
|
|
@@ -4,12 +4,14 @@ A **Notion custom view** — a sandboxed `<iframe>` rendered inside a Notion blo
|
|
|
4
4
|
|
|
5
5
|
## Connecting to a Notion database
|
|
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
|
|
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:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx ncblock
|
|
10
|
+
npx ncblock connect <database-url-or-id>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
That writes `custom_blocks.json`, `.notion/target.json`, and PATCHes any blocks already listed there. Discover other commands with `npx ncblock --help`.
|
|
14
|
+
|
|
13
15
|
## Talking to the host
|
|
14
16
|
|
|
15
17
|
- Always use the React hooks from `ncblock`. Never call `window.parent.postMessage` directly — the SDK owns the protocol.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { execSync } from "node:child_process"
|
|
2
|
+
|
|
3
|
+
/** Sentinel exit code for "user pasted the wrong kind of ID" errors. */
|
|
4
|
+
export const EXIT_USER_ID_MISTAKE = 2
|
|
5
|
+
|
|
6
|
+
const MAX_PARENT_HOPS = 5
|
|
7
|
+
|
|
8
|
+
const YELLOW = "\x1b[33m"
|
|
9
|
+
const RESET = "\x1b[0m"
|
|
10
|
+
|
|
11
|
+
export type Classified = {
|
|
12
|
+
kind: "block" | "view"
|
|
13
|
+
/** What we'll write to `target.json#block_id`. Equals the user's input. */
|
|
14
|
+
blockId: string
|
|
15
|
+
/** What we'll pass to `ncblock connect` (if known). */
|
|
16
|
+
databaseId?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the user's positional ID into `{ blockId, databaseId }` BEFORE
|
|
21
|
+
* scaffolding, so bad inputs don't leave a half-written project on disk and
|
|
22
|
+
* good inputs already know which DB to pull from.
|
|
23
|
+
*
|
|
24
|
+
* - `/v1/databases/<id>` succeeds → user pasted a database ID into the
|
|
25
|
+
* block-ID slot. Fail-fast.
|
|
26
|
+
* - `/v1/blocks/<id>` succeeds → it's a block. `blockId` is the input;
|
|
27
|
+
* `databaseId` is walked from `block.parent` (database_id parent, or
|
|
28
|
+
* data_source parent, or recurse through block/page parents).
|
|
29
|
+
* - `/v1/views/<id>` succeeds → it's a view (Notion URL `?v=<id>` form).
|
|
30
|
+
* `blockId` = input (the view ID is the custom_block ID for the deploy
|
|
31
|
+
* target); `databaseId` = `view.parent.database_id`.
|
|
32
|
+
* - All three 404 → wrong workspace. Fail-fast.
|
|
33
|
+
* - Any non-404 failure → surface ntn's stderr and exit with its status,
|
|
34
|
+
* instead of misclassifying as "not in your workspace."
|
|
35
|
+
*
|
|
36
|
+
* Best-effort: if `ntn` isn't on PATH, returns `null` (with a warning logged
|
|
37
|
+
* by `isNtnAvailable`) — `ncblock connect`'s downstream errors will surface
|
|
38
|
+
* real issues.
|
|
39
|
+
*/
|
|
40
|
+
export function classifyPositionalId(
|
|
41
|
+
rawId: string,
|
|
42
|
+
env: string,
|
|
43
|
+
): Classified | null {
|
|
44
|
+
const id = formatDashedUuid(rawId)
|
|
45
|
+
|
|
46
|
+
if (!isNtnAvailable()) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dbResult = tryNtnApi(env, `/v1/databases/${id}`)
|
|
51
|
+
if (dbResult.ok && isDatabaseShape(dbResult.body)) {
|
|
52
|
+
bailUserInput(
|
|
53
|
+
`${id} is a database id, please paste the block id or view id`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const blockResult = tryNtnApi(env, `/v1/blocks/${id}`)
|
|
58
|
+
if (blockResult.ok) {
|
|
59
|
+
return {
|
|
60
|
+
kind: "block",
|
|
61
|
+
blockId: id,
|
|
62
|
+
databaseId: walkBlockToDatabase(blockResult.body, env, 0),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const viewResult = tryNtnApi(env, `/v1/views/${id}`)
|
|
67
|
+
if (viewResult.ok) {
|
|
68
|
+
const v = viewResult.body as
|
|
69
|
+
| { parent?: { database_id?: string } }
|
|
70
|
+
| undefined
|
|
71
|
+
return { kind: "view", blockId: id, databaseId: v?.parent?.database_id }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Every probe failed. If any returned a non-404, show ntn's actual stderr
|
|
75
|
+
// — saying "not in your workspace" would mislead the user when the real
|
|
76
|
+
// issue is e.g. a 400 validation error. Only the all-404 case gets the
|
|
77
|
+
// wrong-workspace nudge.
|
|
78
|
+
const firstError = [dbResult, blockResult, viewResult].find(r => r.error)
|
|
79
|
+
if (firstError) {
|
|
80
|
+
process.stderr.write(firstError.error ?? "")
|
|
81
|
+
process.exit(firstError.status ?? 1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
bailUserInput(
|
|
85
|
+
`${id} doesn't look like a valid id for your workspace. Ensure you're logged in to the right workspace with \`${loginCommandHint(env)}\`.`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Walk up a block's parent chain looking for an enclosing database. The
|
|
91
|
+
* common case is `parent.type === "database_id"` (one hop), but blocks can
|
|
92
|
+
* also be children of pages or other blocks, so we recurse up to
|
|
93
|
+
* MAX_PARENT_HOPS.
|
|
94
|
+
*/
|
|
95
|
+
function walkBlockToDatabase(
|
|
96
|
+
block: unknown,
|
|
97
|
+
env: string,
|
|
98
|
+
depth: number,
|
|
99
|
+
): string | undefined {
|
|
100
|
+
if (depth >= MAX_PARENT_HOPS) {
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
const parent = (block as { parent?: Record<string, unknown> } | undefined)
|
|
104
|
+
?.parent
|
|
105
|
+
const parentType = typeof parent?.type === "string" ? parent.type : undefined
|
|
106
|
+
if (!parent || !parentType) {
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parentId = parent[parentType]
|
|
111
|
+
if (typeof parentId !== "string") {
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
switch (parentType) {
|
|
116
|
+
case "database_id":
|
|
117
|
+
return parentId
|
|
118
|
+
case "data_source_id": {
|
|
119
|
+
const ds = tryNtnApi(env, `/v1/data_sources/${parentId}`)
|
|
120
|
+
if (!ds.ok) {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
// TODO(custom-blocks): validate the data source response shape with
|
|
124
|
+
// a proper type guard instead of casting.
|
|
125
|
+
const dbId = (
|
|
126
|
+
ds.body as { parent?: { database_id?: string } } | undefined
|
|
127
|
+
)?.parent?.database_id
|
|
128
|
+
return dbId
|
|
129
|
+
}
|
|
130
|
+
case "block_id":
|
|
131
|
+
case "page_id": {
|
|
132
|
+
const parentBlock = tryNtnApi(env, `/v1/blocks/${parentId}`)
|
|
133
|
+
return parentBlock.ok
|
|
134
|
+
? walkBlockToDatabase(parentBlock.body, env, depth + 1)
|
|
135
|
+
: undefined
|
|
136
|
+
}
|
|
137
|
+
default:
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isNtnAvailable(): boolean {
|
|
143
|
+
try {
|
|
144
|
+
execSync("ntn --version", { stdio: "ignore" })
|
|
145
|
+
return true
|
|
146
|
+
} catch {
|
|
147
|
+
console.warn(
|
|
148
|
+
` ${YELLOW}Warning:${RESET} \`ntn\` not on PATH — skipping ID classification. Install ntn to catch wrong-workspace / wrong-ID-kind mistakes upfront.`,
|
|
149
|
+
)
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type NtnApiResult = {
|
|
155
|
+
ok: boolean
|
|
156
|
+
body?: unknown
|
|
157
|
+
/** Captured stderr from a non-404 failure (so the caller can surface it). */
|
|
158
|
+
error?: string
|
|
159
|
+
/** ntn's exit code on failure, for propagation. */
|
|
160
|
+
status?: number
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 404s mean "not this kind of resource" — keep probing. Other failures (400
|
|
165
|
+
* validation errors, 401/403, network) get captured so the classifier can
|
|
166
|
+
* surface them only if every probe fails (i.e. we'd otherwise mislead the user
|
|
167
|
+
* with "not in your workspace").
|
|
168
|
+
*/
|
|
169
|
+
function tryNtnApi(env: string, apiPath: string): NtnApiResult {
|
|
170
|
+
const prefix =
|
|
171
|
+
env && env !== "production" ? `NOTION_KEYRING=0 ntn --env ${env}` : "ntn"
|
|
172
|
+
try {
|
|
173
|
+
const out = execSync(`${prefix} api ${apiPath}`, {
|
|
174
|
+
encoding: "utf-8",
|
|
175
|
+
stdio: "pipe",
|
|
176
|
+
})
|
|
177
|
+
try {
|
|
178
|
+
return { ok: true, body: JSON.parse(out) }
|
|
179
|
+
} catch {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: `Failed to parse successful response from ntn api ${apiPath}`,
|
|
183
|
+
status: 1,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const stderr =
|
|
188
|
+
(err as { stderr?: Buffer | string })?.stderr?.toString() ?? ""
|
|
189
|
+
if (stderr.includes("404")) {
|
|
190
|
+
return { ok: false }
|
|
191
|
+
}
|
|
192
|
+
const status = (err as { status?: number | null })?.status ?? 1
|
|
193
|
+
return { ok: false, error: stderr, status: status ?? 1 }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function loginCommandHint(env: string): string {
|
|
198
|
+
return env && env !== "production"
|
|
199
|
+
? `NOTION_KEYRING=0 ntn --env ${env} login`
|
|
200
|
+
: "ntn login"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isDatabaseShape(body: unknown): boolean {
|
|
204
|
+
if (typeof body !== "object" || body === null) {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
if ("object" in body && (body as { object: unknown }).object === "database") {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
return (
|
|
211
|
+
"data_sources" in body &&
|
|
212
|
+
Array.isArray((body as { data_sources: unknown }).data_sources)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function formatDashedUuid(raw: string): string {
|
|
217
|
+
const h = raw.replace(/-/g, "")
|
|
218
|
+
if (h.length !== 32) {
|
|
219
|
+
return raw
|
|
220
|
+
}
|
|
221
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function bailUserInput(message: string): never {
|
|
225
|
+
console.error(`\n ${YELLOW}Error:${RESET} ${message}\n`)
|
|
226
|
+
process.exit(EXIT_USER_ID_MISTAKE)
|
|
227
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Decide what ID the user is actually passing to the init script — the
|
|
3
|
+
* "parse the inputs" half of init's ID handling. The other half (deciding
|
|
4
|
+
* what an ID *is* — block? view? database in the wrong slot?) lives in
|
|
5
|
+
* `classifyId.ts`.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* are unit-testable without spawning a tsx subprocess.
|
|
7
|
+
* Kept out of `init.ts` so the CLI/env precedence rules are unit-testable
|
|
8
|
+
* without spawning a tsx subprocess.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
export type InitArgsInput = {
|
|
@@ -11,49 +13,38 @@ export type InitArgsInput = {
|
|
|
11
13
|
collection?: string
|
|
12
14
|
/** Value of --block or a positional Notion ID, if passed. */
|
|
13
15
|
block?: string
|
|
14
|
-
/** `process.env.BLOCK_ID` —
|
|
16
|
+
/** `process.env.BLOCK_ID` — last-resort source for the block ID. */
|
|
15
17
|
envBlockId?: string
|
|
16
18
|
/** `process.env.ENV` — populates `.notion/target.json#env`. */
|
|
17
19
|
envName?: string
|
|
18
|
-
/** Whether `init` will run install + ncblock pull/connect (--install / --no-install). */
|
|
19
|
-
installDeps: boolean
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export type InitArgsResolution = {
|
|
23
|
+
/** Block ID (from --block, positional, or BLOCK_ID env). */
|
|
23
24
|
blockId: string | undefined
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Explicit --collection value. When set, classification is skipped — we
|
|
27
|
+
* already know the database. When undefined, we'll classify the
|
|
28
|
+
* block-shaped input to figure out the parent DB.
|
|
29
|
+
*/
|
|
30
|
+
databaseId: string | undefined
|
|
31
|
+
/**
|
|
32
|
+
* The single ID to feed into `classifyPositionalId`, or `undefined` to
|
|
33
|
+
* skip classification. Set when we have a block-shaped input AND no
|
|
34
|
+
* explicit `--collection` (otherwise the DB is already known).
|
|
35
|
+
*/
|
|
36
|
+
idToClassify: string | undefined
|
|
37
|
+
/** Env name for `.notion/target.json#env` and env-aware ntn calls. */
|
|
25
38
|
env: string
|
|
26
|
-
/** Will init invoke `ncblock manifest pull`? */
|
|
27
|
-
willPullManifest: boolean
|
|
28
|
-
/** Will init invoke `ncblock connect`? (only when pull will also run) */
|
|
29
|
-
willConnect: boolean
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
/**
|
|
33
|
-
* Decide what init will actually do, given the args. Precedence:
|
|
34
|
-
*
|
|
35
|
-
* 1. `--collection` is the explicit manifest-pull target (highest priority).
|
|
36
|
-
* 2. Otherwise, a positional/`--block` Notion ID doubles as the pull target —
|
|
37
|
-
* `manifest pull`'s resolveId() handles "block inside collection_view" and
|
|
38
|
-
* walks to the parent data source. This is the path users hit with
|
|
39
|
-
* `bun create ncblock <block-id>`.
|
|
40
|
-
* 3. When neither is set, no pull happens.
|
|
41
|
-
*
|
|
42
|
-
* `manifest pull` and `connect` only run when install ran (otherwise the
|
|
43
|
-
* ncblock CLI isn't on disk to call).
|
|
44
|
-
*/
|
|
45
41
|
export function resolveInitArgs(input: InitArgsInput): InitArgsResolution {
|
|
46
42
|
const blockId = input.block || input.envBlockId || undefined
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const collectionId = input.collection ?? blockId
|
|
43
|
+
const databaseId = input.collection
|
|
44
|
+
// `--collection` is the user's assertion that they already know the
|
|
45
|
+
// database; otherwise we need to probe the block-shaped input to find
|
|
46
|
+
// the parent DB.
|
|
47
|
+
const idToClassify = databaseId === undefined ? blockId : undefined
|
|
53
48
|
const env = input.envName ?? "production"
|
|
54
|
-
|
|
55
|
-
const willPullManifest = input.installDeps && collectionId !== undefined
|
|
56
|
-
const willConnect = willPullManifest && blockId !== undefined
|
|
57
|
-
|
|
58
|
-
return { blockId, collectionId, env, willPullManifest, willConnect }
|
|
49
|
+
return { blockId, databaseId, idToClassify, env }
|
|
59
50
|
}
|
package/sdk-version.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.0.
|
|
1
|
+
{"version":"0.0.16"}
|