@notionhq/custom-blocks 0.1.37 → 0.1.38
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 +5 -0
- package/bin/notion-custom-blocks/build.d.ts +23 -0
- package/bin/notion-custom-blocks/bundle.d.ts +4 -0
- package/bin/notion-custom-blocks/bundle.js +2 -0
- package/bin/notion-custom-blocks/cli.d.ts +8 -0
- package/bin/notion-custom-blocks/tar.d.ts +11 -0
- package/bin/notion-custom-blocks/upload.d.ts +14 -0
- package/bin/notion-custom-blocks/upload.js +14 -0
- package/bin/notion-custom-blocks/upload.test.d.ts +1 -0
- package/bin/notion-custom-blocks/upload.test.js +38 -0
- package/dist/version.js +1 -1
- package/docs/deployment.md +15 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -7,6 +7,11 @@ SDK for building Notion custom blocks.
|
|
|
7
7
|
|
|
8
8
|
A custom block runs as a sandboxed `<iframe>` inside the Notion app that has no direct access to the internet. The only channel between your block and Notion is a local `postMessage` bridge. This library implements the sandbox side of the bridge protocol and wraps it in a framework-neutral TypeScript API (`@notionhq/custom-blocks`) and typed React hooks (`@notionhq/custom-blocks/react`).
|
|
9
9
|
|
|
10
|
+
## Bundle tooling
|
|
11
|
+
|
|
12
|
+
Node build tools can import `build` and `upload` from
|
|
13
|
+
`@notionhq/custom-blocks/bundle`. See [deployment tooling](./docs/deployment.md#apps-sdk-integration).
|
|
14
|
+
|
|
10
15
|
## Install
|
|
11
16
|
|
|
12
17
|
Create a worker project with `ntn workers new --template custom` and use the dependencies and
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type BuiltBlock = {
|
|
2
|
+
key: string;
|
|
3
|
+
checksumCrc32: string;
|
|
4
|
+
bundleDir: string;
|
|
5
|
+
bundleSizeBytes: number;
|
|
6
|
+
};
|
|
7
|
+
export type BuildOutput = {
|
|
8
|
+
blocks: BuiltBlock[];
|
|
9
|
+
};
|
|
10
|
+
export type BuildArgs = {
|
|
11
|
+
out: string;
|
|
12
|
+
cwd?: string | undefined;
|
|
13
|
+
manifest?: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Build every `_tag: "custom_block"` view declared by the worker.
|
|
17
|
+
*
|
|
18
|
+
* Imports the worker bundle (`<cwd>/dist/index.js`), reads its runtime manifest,
|
|
19
|
+
* and runs the command specified (default `npm run build`)
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildCustomBlocks(args: BuildArgs): Promise<BuildOutput>;
|
|
22
|
+
/** Print the build output as the sentinel-wrapped single-line JSON contract. */
|
|
23
|
+
export declare function printBuildOutput(output: BuildOutput): void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { BuildArgs, BuildOutput, BuiltBlock } from "./build.js";
|
|
2
|
+
export { buildCustomBlocks as build, printBuildOutput } from "./build.js";
|
|
3
|
+
export type { UploadOutput, UploadsInput, UploadTarget } from "./upload.js";
|
|
4
|
+
export { parseUploadsInput, uploadViews as upload } from "./upload.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Custom block deployment CLI invoked by Notion's worker build sandbox.
|
|
4
|
+
*
|
|
5
|
+
* It ships with the SDK so worker projects expose the executable expected by
|
|
6
|
+
* the deployment pipeline at `node_modules/.bin/notion-custom-blocks`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type PackedTarGz = {
|
|
2
|
+
tarGz: Buffer;
|
|
3
|
+
checksumCrc32: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Pack a directory into a deterministic gzipped tarball and return both the
|
|
7
|
+
* bytes and their base64 big-endian crc32 checksum (the form
|
|
8
|
+
* `CreateCustomBlockDeploy` signs into the presigned PUT URL).
|
|
9
|
+
*/
|
|
10
|
+
export declare function packDirToTarGz(dir: string): PackedTarGz;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type UploadTarget = {
|
|
2
|
+
uploadUrl: string;
|
|
3
|
+
bundleDir: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export type UploadsInput = Record<string, UploadTarget>;
|
|
7
|
+
export type UploadOutput = {
|
|
8
|
+
uploaded: Record<string, true>;
|
|
9
|
+
};
|
|
10
|
+
export type UploadDeps = {
|
|
11
|
+
fetch: typeof fetch;
|
|
12
|
+
};
|
|
13
|
+
export declare function uploadViews(uploads: UploadsInput, deps?: UploadDeps): Promise<UploadOutput>;
|
|
14
|
+
export declare function parseUploadsInput(raw: string): UploadsInput;
|
|
@@ -7,6 +7,7 @@ export async function uploadViews(uploads, deps = DEFAULT_DEPS) {
|
|
|
7
7
|
const response = await deps.fetch(target.uploadUrl, {
|
|
8
8
|
method: "PUT",
|
|
9
9
|
headers: {
|
|
10
|
+
...target.headers,
|
|
10
11
|
"Content-Type": "application/gzip",
|
|
11
12
|
"x-amz-checksum-crc32": checksumCrc32,
|
|
12
13
|
},
|
|
@@ -46,10 +47,23 @@ export function parseUploadsInput(raw) {
|
|
|
46
47
|
result[key] = {
|
|
47
48
|
uploadUrl: record.uploadUrl,
|
|
48
49
|
bundleDir: record.bundleDir,
|
|
50
|
+
headers: parseHeaders(record.headers, key),
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
return result;
|
|
52
54
|
}
|
|
55
|
+
function parseHeaders(value, key) {
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
if (value === null ||
|
|
60
|
+
typeof value !== "object" ||
|
|
61
|
+
Array.isArray(value) ||
|
|
62
|
+
Object.values(value).some(header => typeof header !== "string")) {
|
|
63
|
+
throw new Error(`--uploads["${key}"].headers must be an object of string values`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
53
67
|
async function safeReadText(response) {
|
|
54
68
|
try {
|
|
55
69
|
const text = await response.text();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { parseUploadsInput } from "./upload.js";
|
|
4
|
+
describe("parseUploadsInput", () => {
|
|
5
|
+
it("preserves signed headers and supports targets without headers", () => {
|
|
6
|
+
const targets = {
|
|
7
|
+
hello: {
|
|
8
|
+
uploadUrl: "https://example.com/bundle?signature=a%2Bb",
|
|
9
|
+
bundleDir: "/tmp/block",
|
|
10
|
+
headers: { "Content-Length": "42", "x-amz-tagging": "source=cli" },
|
|
11
|
+
},
|
|
12
|
+
legacy: {
|
|
13
|
+
uploadUrl: "https://example.com/legacy",
|
|
14
|
+
bundleDir: "/tmp/legacy",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
expect(parseUploadsInput(JSON.stringify(targets))).toEqual(targets);
|
|
18
|
+
});
|
|
19
|
+
it.each([
|
|
20
|
+
"broken",
|
|
21
|
+
"null",
|
|
22
|
+
"[]",
|
|
23
|
+
"42",
|
|
24
|
+
'{"hello":null}',
|
|
25
|
+
'{"hello":{"bundleDir":"x"}}',
|
|
26
|
+
'{"hello":{"uploadUrl":"x"}}',
|
|
27
|
+
])("rejects malformed target input %s", raw => {
|
|
28
|
+
expect(() => parseUploadsInput(raw)).toThrow();
|
|
29
|
+
});
|
|
30
|
+
it.each([
|
|
31
|
+
null,
|
|
32
|
+
[],
|
|
33
|
+
"bad",
|
|
34
|
+
{ "Content-Length": 42 },
|
|
35
|
+
])("rejects invalid signed headers %j", headers => {
|
|
36
|
+
expect(() => parseUploadsInput(JSON.stringify({ hello: { uploadUrl: "x", bundleDir: "x", headers } }))).toThrow("headers must be an object of string values");
|
|
37
|
+
});
|
|
38
|
+
});
|
package/dist/version.js
CHANGED
package/docs/deployment.md
CHANGED
|
@@ -27,6 +27,21 @@ static Apps SDK capabilities. Paths in the manifest stay relative to the project
|
|
|
27
27
|
root (the current directory, or `--cwd`). Without `--manifest`, the command
|
|
28
28
|
continues to import the Workers SDK bundle at `dist/index.js`.
|
|
29
29
|
|
|
30
|
+
Node build tools use one entrypoint:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { build, upload } from "@notionhq/custom-blocks/bundle"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`build` accepts `BuildArgs` and returns `BuildOutput` containing `BuiltBlock`
|
|
37
|
+
results. `upload` accepts `UploadsInput`, a map of block keys to `UploadTarget`,
|
|
38
|
+
and returns `UploadOutput`. `printBuildOutput` writes the sentinel-wrapped build
|
|
39
|
+
result; `parseUploadsInput` validates JSON upload arguments. The Apps CLI calls
|
|
40
|
+
these functions from `build-blocks` and `upload-blocks`.
|
|
30
41
|
|
|
31
42
|
Relative `--out` paths also resolve from `--cwd`. Build results include
|
|
32
43
|
`bundleSizeBytes`, the exact compressed tarball length, for size-bound uploads.
|
|
44
|
+
|
|
45
|
+
Upload targets may include a `headers` map containing the headers returned by
|
|
46
|
+
the server presign, including `Content-Length` and object tags. The uploader
|
|
47
|
+
forwards these alongside the tarball checksum and content type.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/custom-blocks",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./vite": {
|
|
26
26
|
"import": "./vite-plugin/index.js",
|
|
27
27
|
"types": "./vite-plugin/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./bundle": {
|
|
30
|
+
"types": "./bin/notion-custom-blocks/bundle.d.ts",
|
|
31
|
+
"import": "./bin/notion-custom-blocks/bundle.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"bin": {
|