@notionhq/custom-blocks-dev-shell 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.
- package/README.md +30 -0
- package/bin/cli.js +37 -0
- package/cli/block-server.ts +108 -0
- package/cli/main.ts +293 -0
- package/cli/serve-ui.ts +100 -0
- package/cli/worker-manifest.ts +196 -0
- package/dist/assets/index-CEUqyStq.js +9 -0
- package/dist/assets/index-DvRqSoqY.css +2 -0
- package/dist/index.html +13 -0
- package/package.json +47 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a worker's manifest without deploying it, following the localhost
|
|
3
|
+
* verification strategy: build the worker, then read the manifest off the
|
|
4
|
+
* built module's default export. This is the one prerequisite the dev shell
|
|
5
|
+
* needs before it can list a worker's blocks and data sources.
|
|
6
|
+
*
|
|
7
|
+
* The worker manifest is otherwise in-memory only (tied to `worker.ts` + cloud
|
|
8
|
+
* build), so we materialize it under the worker's git-ignored `.dev-shell/` dir
|
|
9
|
+
* (clearly a dev-shell artifact, not something the author maintains).
|
|
10
|
+
* Regenerated on every spin-up.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync } from "node:child_process"
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
15
|
+
import { dirname, resolve } from "node:path"
|
|
16
|
+
import { pathToFileURL } from "node:url"
|
|
17
|
+
|
|
18
|
+
/** One custom-block capability in the worker manifest. */
|
|
19
|
+
export type WorkerBlockCapability = {
|
|
20
|
+
_tag: "custom_block"
|
|
21
|
+
key: string
|
|
22
|
+
config: {
|
|
23
|
+
source: { type: string; path: string; command?: string; output?: string }
|
|
24
|
+
manifest: {
|
|
25
|
+
version: number
|
|
26
|
+
dataSources: Record<
|
|
27
|
+
string,
|
|
28
|
+
{
|
|
29
|
+
name: string
|
|
30
|
+
description?: string
|
|
31
|
+
icon?: unknown
|
|
32
|
+
properties?: Record<string, { name: string; type: string }>
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One worker-defined database in the manifest. */
|
|
40
|
+
export type WorkerDatabaseEntry = {
|
|
41
|
+
key: string
|
|
42
|
+
config: {
|
|
43
|
+
type: string
|
|
44
|
+
initialTitle?: string
|
|
45
|
+
primaryKeyProperty?: string
|
|
46
|
+
schema?: {
|
|
47
|
+
properties?: Record<string, { type: string; [k: string]: unknown }>
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type WorkerManifest = {
|
|
53
|
+
sdkVersion?: string
|
|
54
|
+
databases: WorkerDatabaseEntry[]
|
|
55
|
+
pacers: { key: string; config: unknown }[]
|
|
56
|
+
capabilities: { _tag: string; key: string; config: unknown }[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const WORKER_MANIFEST_FILENAME = "worker_manifest.json"
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Heuristic for the no-flag fallback: does `dir` look like a worker project?
|
|
63
|
+
* True if it carries a worker deploy binding (`worker.json`/`workers.json`) or
|
|
64
|
+
* depends on `@notionhq/workers`.
|
|
65
|
+
*/
|
|
66
|
+
export function looksLikeWorkerDir(dir: string): boolean {
|
|
67
|
+
if (
|
|
68
|
+
existsSync(resolve(dir, "worker.json")) ||
|
|
69
|
+
existsSync(resolve(dir, "workers.json"))
|
|
70
|
+
) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
const pkgPath = resolve(dir, "package.json")
|
|
74
|
+
if (!existsSync(pkgPath)) {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
|
|
79
|
+
dependencies?: Record<string, string>
|
|
80
|
+
devDependencies?: Record<string, string>
|
|
81
|
+
}
|
|
82
|
+
return Boolean(
|
|
83
|
+
pkg.dependencies?.["@notionhq/workers"] ??
|
|
84
|
+
pkg.devDependencies?.["@notionhq/workers"],
|
|
85
|
+
)
|
|
86
|
+
} catch {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Walk up from `startDir` looking for a worker directory (see
|
|
93
|
+
* `looksLikeWorkerDir`), so the shell can be launched from anywhere inside a
|
|
94
|
+
* worker, not only its root. Stops once it reaches a git-repo boundary (a
|
|
95
|
+
* `.git` at the current level, checked after the worker test) or the filesystem
|
|
96
|
+
* root — it should never escape the project the user is in.
|
|
97
|
+
*/
|
|
98
|
+
export function findWorkerDir(startDir: string): string | undefined {
|
|
99
|
+
let current = resolve(startDir)
|
|
100
|
+
while (true) {
|
|
101
|
+
if (looksLikeWorkerDir(current)) {
|
|
102
|
+
return current
|
|
103
|
+
}
|
|
104
|
+
if (existsSync(resolve(current, ".git"))) {
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
const parent = dirname(current)
|
|
108
|
+
if (parent === current) {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
current = parent
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The custom-block capabilities in a manifest, narrowed by `_tag`. */
|
|
116
|
+
export function blockCapabilities(
|
|
117
|
+
manifest: WorkerManifest,
|
|
118
|
+
): WorkerBlockCapability[] {
|
|
119
|
+
return manifest.capabilities.filter(
|
|
120
|
+
(capability): capability is WorkerBlockCapability =>
|
|
121
|
+
capability._tag === "custom_block",
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isManifestShape(value: unknown): value is WorkerManifest {
|
|
126
|
+
return (
|
|
127
|
+
typeof value === "object" &&
|
|
128
|
+
value !== null &&
|
|
129
|
+
Array.isArray((value as WorkerManifest).capabilities)
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build the worker in `workerDir` and read its manifest off the built default
|
|
135
|
+
* export. Mirrors the deploy path (build → read the built module) rather than
|
|
136
|
+
* importing TypeScript source, so what the dev shell sees matches what a deploy
|
|
137
|
+
* would produce.
|
|
138
|
+
*/
|
|
139
|
+
export async function extractWorkerManifest(
|
|
140
|
+
workerDir: string,
|
|
141
|
+
options: { build?: boolean } = {},
|
|
142
|
+
): Promise<WorkerManifest> {
|
|
143
|
+
const root = resolve(workerDir)
|
|
144
|
+
if (!existsSync(resolve(root, "package.json"))) {
|
|
145
|
+
throw new Error(`No package.json found in worker directory: ${root}`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (options.build !== false) {
|
|
149
|
+
execSync("npm run build", { cwd: root, stdio: "inherit" })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const entry = resolve(root, "dist/index.js")
|
|
153
|
+
if (!existsSync(entry)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Built worker entry not found at ${entry}. The worker's build must emit dist/index.js.`,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const mod = (await import(pathToFileURL(entry).href)) as {
|
|
160
|
+
default?: { manifest?: unknown; capabilities?: unknown }
|
|
161
|
+
}
|
|
162
|
+
const worker = mod.default
|
|
163
|
+
if (worker === undefined) {
|
|
164
|
+
throw new Error(`Built worker at ${entry} has no default export.`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Read the built worker's manifest, falling back to a bare capabilities array
|
|
168
|
+
// (per the localhost verification doc's `w.manifest || w.capabilities`).
|
|
169
|
+
const raw = worker.manifest ?? worker.capabilities
|
|
170
|
+
if (isManifestShape(raw)) {
|
|
171
|
+
return raw
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(raw)) {
|
|
174
|
+
return { databases: [], pacers: [], capabilities: raw }
|
|
175
|
+
}
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Built worker at ${entry} exposed no usable manifest (expected .manifest or .capabilities).`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract the worker manifest and write it to `.dev-shell/worker_manifest.json`
|
|
183
|
+
* inside the worker (a git-ignored dev-shell artifact dir). Returns the parsed
|
|
184
|
+
* manifest and the path written.
|
|
185
|
+
*/
|
|
186
|
+
export async function generateWorkerManifest(
|
|
187
|
+
workerDir: string,
|
|
188
|
+
options: { build?: boolean } = {},
|
|
189
|
+
): Promise<{ manifest: WorkerManifest; manifestPath: string }> {
|
|
190
|
+
const manifest = await extractWorkerManifest(workerDir, options)
|
|
191
|
+
const dir = resolve(workerDir, ".dev-shell")
|
|
192
|
+
mkdirSync(dir, { recursive: true })
|
|
193
|
+
const manifestPath = resolve(dir, WORKER_MANIFEST_FILENAME)
|
|
194
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
195
|
+
return { manifest, manifestPath }
|
|
196
|
+
}
|