@notionhq/apps 0.0.11 → 0.0.13

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 (53) hide show
  1. package/README.md +28 -0
  2. package/dist/cli/build.d.ts.map +1 -1
  3. package/dist/cli/build.js +34 -4
  4. package/dist/cli/codegen.d.ts +2 -0
  5. package/dist/cli/codegen.d.ts.map +1 -1
  6. package/dist/cli/codegen.js +13 -2
  7. package/dist/cli/discover.d.ts +2 -0
  8. package/dist/cli/discover.d.ts.map +1 -1
  9. package/dist/cli/discover.js +4 -2
  10. package/dist/cli/emit-manifest.d.ts +3 -1
  11. package/dist/cli/emit-manifest.d.ts.map +1 -1
  12. package/dist/cli/emit-manifest.js +10 -4
  13. package/dist/cli/index.js +33 -1
  14. package/dist/custom-block.d.ts +54 -0
  15. package/dist/custom-block.d.ts.map +1 -0
  16. package/dist/custom-block.js +30 -0
  17. package/dist/custom-block.test.d.ts +2 -0
  18. package/dist/custom-block.test.d.ts.map +1 -0
  19. package/dist/custom-blocks.d.ts +2 -0
  20. package/dist/custom-blocks.d.ts.map +1 -0
  21. package/dist/custom-blocks.js +1 -0
  22. package/dist/manifest.d.ts +1 -1
  23. package/dist/manifest.d.ts.map +1 -1
  24. package/dist/nds.css +1 -0
  25. package/dist/notion-as-code/database.d.ts +4 -3
  26. package/dist/notion-as-code/database.d.ts.map +1 -1
  27. package/dist/notion-as-code/database.js +11 -2
  28. package/dist/notion-as-code/intents.d.ts +4 -1
  29. package/dist/notion-as-code/intents.d.ts.map +1 -1
  30. package/dist/notion-as-code/page.d.ts +4 -4
  31. package/dist/notion-as-code/page.d.ts.map +1 -1
  32. package/dist/notion-as-code/page.js +11 -2
  33. package/dist/react.d.ts +2 -0
  34. package/dist/react.d.ts.map +1 -0
  35. package/dist/react.js +1 -0
  36. package/package.json +16 -2
  37. package/src/cli/build.test.ts +54 -0
  38. package/src/cli/build.ts +41 -4
  39. package/src/cli/codegen.test.ts +2 -0
  40. package/src/cli/codegen.ts +25 -7
  41. package/src/cli/discover.test.ts +12 -0
  42. package/src/cli/discover.ts +6 -2
  43. package/src/cli/emit-manifest.ts +13 -3
  44. package/src/cli/index.ts +33 -1
  45. package/src/custom-block.test.ts +19 -0
  46. package/src/custom-block.ts +41 -0
  47. package/src/custom-blocks.ts +1 -0
  48. package/src/manifest.ts +1 -1
  49. package/src/nds.css +1 -0
  50. package/src/notion-as-code/database.ts +19 -11
  51. package/src/notion-as-code/intents.ts +2 -1
  52. package/src/notion-as-code/page.ts +17 -11
  53. package/src/react.ts +1 -0
package/README.md CHANGED
@@ -92,3 +92,31 @@ Publishing is initiated only from the GitHub Actions UI:
92
92
  The workflow always increments the patch version. It builds, checks, and tests the
93
93
  SDK before committing the new version, creating a `v*` GitHub release,
94
94
  and publishing `@notionhq/apps` to npm.
95
+
96
+ ## Custom blocks
97
+
98
+ Declare browser blocks in `src/customBlocks/<key>.ts`:
99
+
100
+ ```ts
101
+ import { createCustomBlock } from "@notionhq/apps/custom-block";
102
+
103
+ export default createCustomBlock({
104
+ path: "./blocks/hello",
105
+ slashCommand: "hello",
106
+ dataSources: {},
107
+ });
108
+ ```
109
+
110
+ The filename supplies the block key. `path` is required and relative to the app
111
+ root. Browser source belongs in the directory it names, with `index.html` and a
112
+ Vite config. The project root supplies React, React DOM, Vite, and the React Vite
113
+ plugin. `command` defaults to `npx vite build`, and `output` defaults to `dist`.
114
+ Use `static: true` for an already-built directory. `slashCommand` accepts one
115
+ optional leading `/`, then 1–64 lowercase letters, numbers, hyphens or underscores,
116
+ starting with a letter or number. `dataSources` describes the block's schemas.
117
+
118
+ `notion-apps build` reads declarations in a separate temporary bundle and emits
119
+ `custom_block` capabilities into the manifest. Blocks never enter `worker.js`.
120
+
121
+ `notion-apps build-blocks` and `notion-apps upload-blocks` are invoked by the CLI
122
+ during `ntn apps deploy` for local builds.
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAoCxE"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwCxE"}
package/dist/cli/build.js CHANGED
@@ -5,9 +5,9 @@ import {
5
5
  createProvisioningArtifact
6
6
  } from "../notion-as-code/provisioning.js";
7
7
  import { clearRegisteredPacers } from "../pacer.js";
8
- import { generateEntry, generateMetadataEntry } from "./codegen.js";
8
+ import { generateEntry, generateMetadataEntry, generateBlocksEntry } from "./codegen.js";
9
9
  import { discoverCapabilities } from "./discover.js";
10
- import { extractManifest, extractProvisioning } from "./emit-manifest.js";
10
+ import { extractManifest, extractProvisioning, extractBlockConfigs } from "./emit-manifest.js";
11
11
  async function buildApp(projectRoot) {
12
12
  const capabilities = await discoverCapabilities(projectRoot);
13
13
  if (capabilities.length === 0) {
@@ -29,11 +29,15 @@ async function buildApp(projectRoot) {
29
29
  } finally {
30
30
  await bundle.close();
31
31
  }
32
- const manifest = await extractManifest(bundlePath, capabilities);
32
+ const blockConfigs = await buildBlockDeclarations(projectRoot, capabilities);
33
+ const manifest = await extractManifest(bundlePath, capabilities, blockConfigs);
33
34
  const manifestPath = path.join(projectRoot, "dist", "manifest.json");
34
35
  await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, " ")}
35
36
  `);
36
- await emitProvisioningArtifact(projectRoot, capabilities);
37
+ await emitProvisioningArtifact(
38
+ projectRoot,
39
+ capabilities.filter((capability) => capability.runtime === "worker")
40
+ );
37
41
  return { manifest, bundlePath, manifestPath };
38
42
  }
39
43
  async function emitProvisioningArtifact(projectRoot, capabilities) {
@@ -73,6 +77,32 @@ async function buildMetadata(projectRoot, capabilities) {
73
77
  await fs.promises.rm(metadataBundlePath, { force: true });
74
78
  }
75
79
  }
80
+ async function buildBlockDeclarations(projectRoot, capabilities) {
81
+ if (!capabilities.some((capability) => capability.runtime === "build")) return {};
82
+ const entryPath = path.join(projectRoot, ".notion", "blocks-entry.ts");
83
+ const bundlePath = path.join(projectRoot, ".notion", "blocks.js");
84
+ await fs.promises.writeFile(entryPath, generateBlocksEntry(capabilities));
85
+ try {
86
+ const bundle = await rolldown({
87
+ input: entryPath,
88
+ platform: "node",
89
+ external: (id) => !id.startsWith(".") && !path.isAbsolute(id)
90
+ });
91
+ try {
92
+ await bundle.write({ format: "esm", file: bundlePath, codeSplitting: false });
93
+ } finally {
94
+ await bundle.close();
95
+ }
96
+ return await extractBlockConfigs(bundlePath);
97
+ } catch (error) {
98
+ throw new Error(
99
+ `Failed to read custom block declarations (${capabilities.filter((c) => c.runtime === "build").map((c) => c.sourcePath).join(", ")}): ${error instanceof Error ? error.message : String(error)}`,
100
+ { cause: error }
101
+ );
102
+ } finally {
103
+ await fs.promises.rm(bundlePath, { force: true });
104
+ }
105
+ }
76
106
  export {
77
107
  buildApp
78
108
  };
@@ -12,4 +12,6 @@ export declare function generateEntry(capabilities: readonly DiscoveredCapabilit
12
12
  * recorder starts and remain isolated from the deployed Worker entry.
13
13
  */
14
14
  export declare function generateMetadataEntry(capabilities: readonly DiscoveredCapability[]): string;
15
+ /** Evaluate block declarations only while building, separately from the Worker. */
16
+ export declare function generateBlocksEntry(capabilities: readonly DiscoveredCapability[]): string;
15
17
  //# sourceMappingURL=codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/cli/codegen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,MAAM,CAyCnF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,MAAM,CAe3F"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/cli/codegen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,MAAM,CA6CnF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,MAAM,CAe3F;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,MAAM,CAWzF"}
@@ -1,9 +1,11 @@
1
1
  function generateEntry(capabilities) {
2
2
  const imports = [];
3
3
  const registryEntries = /* @__PURE__ */ new Map();
4
- capabilities.forEach((capability, index) => {
4
+ capabilities.filter((capability) => capability.runtime !== "build").forEach((capability, index) => {
5
5
  const binding = `capability_${index}`;
6
- imports.push(`import ${binding} from ${JSON.stringify(`../${capability.sourcePath}`)};`);
6
+ imports.push(
7
+ `import ${binding} from ${JSON.stringify(`../${capability.sourcePath}`)};`
8
+ );
7
9
  const entries = registryEntries.get(capability.type) ?? [];
8
10
  entries.push(` ${JSON.stringify(capability.key)}: ${binding},`);
9
11
  registryEntries.set(capability.type, entries);
@@ -49,7 +51,16 @@ ${imports}
49
51
  export default finishMetadataRecording(recorder);
50
52
  `;
51
53
  }
54
+ function generateBlocksEntry(capabilities) {
55
+ const entries = capabilities.filter((capability) => capability.runtime === "build");
56
+ return entries.map(
57
+ (capability, index) => `import block_${index} from ${JSON.stringify(`../${capability.sourcePath}`)};`
58
+ ).join("\n") + `
59
+ export const capabilities = { custom_block: {${entries.map((capability, index) => `${JSON.stringify(capability.key)}: block_${index}`).join(",")}} };
60
+ `;
61
+ }
52
62
  export {
63
+ generateBlocksEntry,
53
64
  generateEntry,
54
65
  generateMetadataEntry
55
66
  };
@@ -8,6 +8,7 @@ export type CapabilityTypeDefinition = {
8
8
  type: CapabilityType;
9
9
  directory: string;
10
10
  tag: string;
11
+ runtime: "worker" | "build";
11
12
  };
12
13
  export declare const CAPABILITY_TYPES: readonly CapabilityTypeDefinition[];
13
14
  /**
@@ -17,6 +18,7 @@ export declare const CAPABILITY_TYPES: readonly CapabilityTypeDefinition[];
17
18
  export type DiscoveredCapability = {
18
19
  type: CapabilityType;
19
20
  tag: string;
21
+ runtime: "worker" | "build";
20
22
  key: string;
21
23
  /** Path to the source file, relative to the project root. */
22
24
  sourcePath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/cli/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAAS,wBAAwB,EAG/D,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAiC/F"}
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/cli/discover.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAAS,wBAAwB,EAI/D,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAkC/F"}
@@ -1,8 +1,9 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  const CAPABILITY_TYPES = [
4
- { type: "workflow", directory: "workflows", tag: "workflow" },
5
- { type: "sync", directory: "syncs", tag: "sync" }
4
+ { type: "workflow", directory: "workflows", tag: "workflow", runtime: "worker" },
5
+ { type: "sync", directory: "syncs", tag: "sync", runtime: "worker" },
6
+ { type: "custom_block", directory: "customBlocks", tag: "custom_block", runtime: "build" }
6
7
  ];
7
8
  async function discoverCapabilities(projectRoot) {
8
9
  const discovered = [];
@@ -25,6 +26,7 @@ async function discoverCapabilities(projectRoot) {
25
26
  discovered.push({
26
27
  type: definition.type,
27
28
  tag: definition.tag,
29
+ runtime: definition.runtime,
28
30
  key: entry.name.slice(0, -".ts".length),
29
31
  sourcePath: path.join("src", definition.directory, entry.name)
30
32
  });
@@ -2,7 +2,7 @@ import { type RecordedProvisioning } from "../notion-as-code/provisioning.js";
2
2
  import type { AppManifest } from "../manifest.js";
3
3
  import type { DiscoveredCapability } from "./discover.js";
4
4
  /** Extract the app manifest from a built bundle. */
5
- export declare function extractManifest(bundlePath: string, capabilities: readonly DiscoveredCapability[]): Promise<AppManifest>;
5
+ export declare function extractManifest(bundlePath: string, capabilities: readonly DiscoveredCapability[], blockConfigs?: Record<string, unknown>): Promise<AppManifest>;
6
6
  /** Import the metadata bundle and return the provisioning declarations it recorded. */
7
7
  export declare function extractProvisioning(bundlePath: string): Promise<RecordedProvisioning>;
8
8
  /**
@@ -10,4 +10,6 @@ export declare function extractProvisioning(bundlePath: string): Promise<Recorde
10
10
  * rewrite, so invalid manifest configuration fails during the build.
11
11
  */
12
12
  export declare function assertJsonSafe(value: unknown, path: string): void;
13
+ /** Read the isolated declaration bundle; browser declarations never enter the Worker. */
14
+ export declare function extractBlockConfigs(bundlePath: string): Promise<Record<string, unknown>>;
13
15
  //# sourceMappingURL=emit-manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"emit-manifest.d.ts","sourceRoot":"","sources":["../../src/cli/emit-manifest.ts"],"names":[],"mappings":"AAMA,OAAO,EAEN,KAAK,oBAAoB,EACzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAA6C,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AA0M1D,oDAAoD;AACpD,wBAAsB,eAAe,CACpC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAAC,WAAW,CAAC,CA6EtB;AAED,uFAAuF;AACvF,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAW3F;AAkCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAoCjE"}
1
+ {"version":3,"file":"emit-manifest.d.ts","sourceRoot":"","sources":["../../src/cli/emit-manifest.ts"],"names":[],"mappings":"AAMA,OAAO,EAEN,KAAK,oBAAoB,EACzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAA6C,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AA0M1D,oDAAoD;AACpD,wBAAsB,eAAe,CACpC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAC7C,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACxC,OAAO,CAAC,WAAW,CAAC,CAiFtB;AAED,uFAAuF;AACvF,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAW3F;AAkCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAoCjE;AAED,yFAAyF;AACzF,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAE9F"}
@@ -65,7 +65,7 @@ const MANIFEST_SCHEMA = {
65
65
  items: {
66
66
  type: "object",
67
67
  properties: {
68
- type: { type: "string", enum: ["workflow", "sync"] },
68
+ type: { type: "string", enum: ["workflow", "sync", "custom_block"] },
69
69
  key: { type: "string" },
70
70
  config: {}
71
71
  },
@@ -162,17 +162,19 @@ class DatabaseCollector {
162
162
  });
163
163
  }
164
164
  }
165
- async function extractManifest(bundlePath, capabilities) {
165
+ async function extractManifest(bundlePath, capabilities, blockConfigs = {}) {
166
166
  clearRegisteredPacers();
167
167
  const bundle = await importBundle(bundlePath);
168
168
  const registry = getCapabilityRegistry(bundle);
169
169
  const databases = new DatabaseCollector();
170
170
  const primaryKeyByDatabase = /* @__PURE__ */ new Map();
171
171
  const capabilityEntries = capabilities.map((capability) => {
172
- const tagged = asTagged(registry[capability.type]?.[capability.key]);
172
+ const tagged = asTagged(
173
+ capability.runtime === "build" ? blockConfigs[capability.key] : registry[capability.type]?.[capability.key]
174
+ );
173
175
  if (!tagged || tagged._tag !== capability.tag) {
174
176
  throw new Error(
175
- `${capability.sourcePath}: default export is not a ${capability.type} \u2014 files under this directory must default-export create${capitalize(capability.type)}(...)`
177
+ `${capability.sourcePath}: default export is not a ${capability.type} \u2014 files under this directory must default-export create${capability.type === "custom_block" ? "CustomBlock" : capitalize(capability.type)}(...)`
176
178
  );
177
179
  }
178
180
  let config = { ...tagged.config };
@@ -285,8 +287,12 @@ function assertJsonSafe(value, path) {
285
287
  if (property !== void 0) assertJsonSafe(property, `${path}.${key}`);
286
288
  }
287
289
  }
290
+ async function extractBlockConfigs(bundlePath) {
291
+ return getCapabilityRegistry(await importBundle(bundlePath)).custom_block ?? {};
292
+ }
288
293
  export {
289
294
  assertJsonSafe,
295
+ extractBlockConfigs,
290
296
  extractManifest,
291
297
  extractProvisioning
292
298
  };
package/dist/cli/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { build, upload, printBuildOutput, parseUploadsInput } from "@notionhq/custom-blocks/bundle";
3
5
  import { buildApp } from "./build.js";
4
6
  const USAGE = `Usage: notion-apps <command>
5
7
 
6
8
  Commands:
7
- build Build the app in the current directory
9
+ build Build the app in the current directory
10
+ build-blocks Build browser bundles (--manifest <path> --out <dir>)
11
+ upload-blocks Upload browser bundles (--uploads <json>)
8
12
  `;
9
13
  async function main() {
10
14
  const command = process.argv[2];
@@ -18,6 +22,34 @@ async function main() {
18
22
  );
19
23
  break;
20
24
  }
25
+ case "build-blocks": {
26
+ const { values } = parseArgs({
27
+ args: process.argv.slice(3),
28
+ options: {
29
+ manifest: { type: "string" },
30
+ out: { type: "string" },
31
+ cwd: { type: "string" }
32
+ }
33
+ });
34
+ if (!values.out) throw new Error("build-blocks requires --out <dir>");
35
+ printBuildOutput(
36
+ await build({
37
+ out: values.out,
38
+ manifest: values.manifest ?? "dist/manifest.json",
39
+ cwd: values.cwd
40
+ })
41
+ );
42
+ break;
43
+ }
44
+ case "upload-blocks": {
45
+ const { values } = parseArgs({
46
+ args: process.argv.slice(3),
47
+ options: { uploads: { type: "string" } }
48
+ });
49
+ if (!values.uploads) throw new Error("upload-blocks requires --uploads <json>");
50
+ console.log(JSON.stringify(await upload(parseUploadsInput(values.uploads))));
51
+ break;
52
+ }
21
53
  case void 0:
22
54
  case "help":
23
55
  case "--help":
@@ -0,0 +1,54 @@
1
+ import type { ManifestDataSource } from "@notionhq/custom-blocks";
2
+ export type CustomBlockOptions = {
3
+ path: string;
4
+ slashCommand?: string;
5
+ dataSources?: Record<string, ManifestDataSource>;
6
+ } & ({
7
+ static: true;
8
+ command?: never;
9
+ output?: never;
10
+ } | {
11
+ static?: false;
12
+ command?: string;
13
+ output?: string;
14
+ });
15
+ /** Declare a browser project. Its filename under src/customBlocks supplies its key. */
16
+ export declare function createCustomBlock(options: CustomBlockOptions): {
17
+ _tag: "custom_block";
18
+ config: {
19
+ source: {
20
+ type: "static";
21
+ path: string;
22
+ command?: never;
23
+ output?: never;
24
+ } | {
25
+ type: "project";
26
+ path: string;
27
+ command: string;
28
+ output: string;
29
+ };
30
+ manifest: {
31
+ dataSources: Record<string, {
32
+ name: string;
33
+ description?: string | undefined;
34
+ icon?: {
35
+ type: "emoji";
36
+ emoji: string;
37
+ } | {
38
+ type: "external";
39
+ url: string;
40
+ } | undefined;
41
+ properties: {
42
+ [x: string]: {
43
+ name: string;
44
+ description?: string | undefined;
45
+ type: "number" | "date" | "url" | "title" | "rich_text" | "checkbox" | "email" | "phone_number" | "select" | "multi_select" | "status" | "people" | "files" | "unique_id" | "relation" | "place" | "formula" | "rollup" | "button" | "verification" | "last_visited_time" | "location" | "created_time" | "last_edited_time" | "created_by" | "last_edited_by";
46
+ };
47
+ };
48
+ }>;
49
+ slashCommand?: string;
50
+ version: 1;
51
+ };
52
+ };
53
+ };
54
+ //# sourceMappingURL=custom-block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-block.d.ts","sourceRoot":"","sources":["../src/custom-block.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CACjD,GAAG,CACD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GACjD;IAAE,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CACvD,CAAC;AAEF,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B5D"}
@@ -0,0 +1,30 @@
1
+ function createCustomBlock(options) {
2
+ if (typeof options.path !== "string" || options.path.trim() === "") {
3
+ throw new Error("createCustomBlock requires a non-empty path relative to the app root");
4
+ }
5
+ const slashCommand = options.slashCommand?.replace(/^\//, "");
6
+ if (slashCommand !== void 0 && !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(slashCommand)) {
7
+ throw new Error(
8
+ "Custom block slashCommand must be 1\u201364 lowercase letters, numbers, hyphens or underscores, starting with a letter or number"
9
+ );
10
+ }
11
+ return {
12
+ _tag: "custom_block",
13
+ config: {
14
+ source: options.static ? { type: "static", path: options.path } : {
15
+ type: "project",
16
+ path: options.path,
17
+ command: options.command ?? "npx vite build",
18
+ output: options.output ?? "dist"
19
+ },
20
+ manifest: {
21
+ version: 1,
22
+ ...slashCommand === void 0 ? {} : { slashCommand },
23
+ dataSources: options.dataSources ?? {}
24
+ }
25
+ }
26
+ };
27
+ }
28
+ export {
29
+ createCustomBlock
30
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=custom-block.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-block.test.d.ts","sourceRoot":"","sources":["../src/custom-block.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from "@notionhq/custom-blocks";
2
+ //# sourceMappingURL=custom-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-blocks.d.ts","sourceRoot":"","sources":["../src/custom-blocks.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "@notionhq/custom-blocks";
@@ -2,7 +2,7 @@ import type { NormalizedSchedule } from "./types.js";
2
2
  /** Identifies the schema used to validate this manifest independently of the SDK package version. */
3
3
  export declare const APPS_MANIFEST_SCHEMA: "notion:apps-manifest:v1";
4
4
  /** Capability types supported by this SDK. */
5
- export type CapabilityType = "workflow" | "sync";
5
+ export type CapabilityType = "workflow" | "sync" | "custom_block";
6
6
  export type DatabaseManifestConfig = {
7
7
  initialTitle?: string;
8
8
  schema: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,qGAAqG;AACrG,eAAO,MAAM,oBAAoB,EAAG,yBAAkC,CAAC;AAEvE,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,sBAAsB,GAAG;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IACjC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,OAAO,oBAAoB,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,qBAAqB,EAAE,CAAC;IACnC,mEAAmE;IACnE,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,YAAY,EAAE,uBAAuB,EAAE,CAAC;CACxC,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,qGAAqG;AACrG,eAAO,MAAM,oBAAoB,EAAG,yBAAkC,CAAC;AAEvE,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAAG;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,sBAAsB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IACjC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,OAAO,oBAAoB,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,qBAAqB,EAAE,CAAC;IACnC,mEAAmE;IACnE,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,YAAY,EAAE,uBAAuB,EAAE,CAAC;CACxC,CAAC"}
package/dist/nds.css ADDED
@@ -0,0 +1 @@
1
+ @import "@notionhq/custom-blocks/nds.css";
@@ -1,11 +1,12 @@
1
1
  import type { Database } from "../database.js";
2
- import type { DataSourceDefinition, NotionAsCodeIcon, NotionAsCodeProperty, Parent, ResourceId } from "./intents.js";
2
+ import { type DataSourceDefinition, type NotionAsCodeIcon, type NotionAsCodeProperty, type Parent, type ResourceId } from "./intents.js";
3
3
  import { type ChildPageArgs, type PageHandle } from "./page.js";
4
4
  import { type AppsSchemaForProperties } from "./schema.js";
5
5
  /** Input for declaring a database and its optional data sources. */
6
6
  export type DatabaseArgs<DataSources extends readonly DataSourceDefinition[] = readonly DataSourceDefinition[]> = {
7
7
  resourceId: ResourceId;
8
- parent: Parent;
8
+ /** Omit to create a private top-level database in the Apps workspace. */
9
+ parent?: Parent;
9
10
  dataSources?: DataSources;
10
11
  views?: readonly unknown[];
11
12
  icon?: NotionAsCodeIcon;
@@ -36,7 +37,7 @@ export type DatabaseHandle<DataSources extends readonly DataSourceDefinition[] =
36
37
  };
37
38
  addView: (view: unknown) => void;
38
39
  };
39
- /** Declare a top-level database and its data sources. */
40
+ /** Declare a database, defaulting to a private top-level database in the Apps workspace. */
40
41
  export declare function database<const DataSources extends readonly DataSourceDefinition[]>(args: DatabaseArgs<DataSources>): DatabaseHandle<DataSources>;
41
42
  /** Record a database declaration and create handles for each of its data sources. */
42
43
  export declare function createDatabase(args: DatabaseArgs, parent: Parent): DatabaseHandle;
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EACX,oBAAoB,EAEpB,gBAAgB,EAChB,oBAAoB,EACpB,MAAM,EACN,UAAU,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAc,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAG5E,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE/E,oEAAoE;AACpE,MAAM,MAAM,YAAY,CACvB,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF;IACH,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,gBAAgB,CAAC;IAExB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,CAC5B,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE9C,qEAAqE;AACrE,KAAK,iCAAiC,CACrC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACnD,UAAU,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,IAEpD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE;IAAE,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,SAAS,oBAAoB,CACpF,MAAM,UAAU,CAChB,GACE,UAAU,GACV,KAAK,CAAC;AAEV,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,CAC3B,UAAU,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IACjF;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;CAC7C,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,cAAc,CACzB,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE;QACrB,QAAQ,EAAE,UAAU,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,GAAG,gBAAgB,CAC3E,iCAAiC,CAAC,WAAW,EAAE,UAAU,CAAC,CAC1D;KACD,CAAC;IACF,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,yDAAyD;AACzD,wBAAgB,QAAQ,CAAC,KAAK,CAAC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACjF,IAAI,EAAE,YAAY,CAAC,WAAW,CAAC,GAC7B,cAAc,CAAC,WAAW,CAAC,CAAC;AAM/B,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CA+BjF"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAEN,KAAK,oBAAoB,EAEzB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,MAAM,EACX,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAc,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAG5E,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE/E,oEAAoE;AACpE,MAAM,MAAM,YAAY,CACvB,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF;IACH,UAAU,EAAE,UAAU,CAAC;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,gBAAgB,CAAC;IAExB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,CAC5B,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE9C,qEAAqE;AACrE,KAAK,iCAAiC,CACrC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACnD,UAAU,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,IAEpD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE;IAAE,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,SAAS,oBAAoB,CACpF,MAAM,UAAU,CAChB,GACE,UAAU,GACV,KAAK,CAAC;AAEV,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,CAC3B,UAAU,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IACjF;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;CAC7C,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,cAAc,CACzB,WAAW,SAAS,SAAS,oBAAoB,EAAE,GAAG,SAAS,oBAAoB,EAAE,IAClF;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE;QACrB,QAAQ,EAAE,UAAU,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,GAAG,gBAAgB,CAC3E,iCAAiC,CAAC,WAAW,EAAE,UAAU,CAAC,CAC1D;KACD,CAAC;IACF,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,KAAK,CAAC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACjF,IAAI,EAAE,YAAY,CAAC,WAAW,CAAC,GAC7B,cAAc,CAAC,WAAW,CAAC,CAAC;AAY/B,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CA+BjF"}
@@ -1,10 +1,19 @@
1
+ import {
2
+ APPS_WORKSPACE_RESOURCE_ID
3
+ } from "./intents.js";
1
4
  import { createPage } from "./page.js";
2
5
  import { recordIntent } from "./recorder.js";
3
6
  import { assertParent, assertUserResourceId } from "./resource.js";
4
7
  import { createAppsDatabase } from "./schema.js";
5
8
  function database(args) {
6
- assertParent(args.parent);
7
- return createDatabase(args, args.parent);
9
+ if (args.parent) {
10
+ assertParent(args.parent);
11
+ }
12
+ const parent = args.parent ?? {
13
+ type: "resourceId",
14
+ resourceId: APPS_WORKSPACE_RESOURCE_ID
15
+ };
16
+ return createDatabase(args, parent);
8
17
  }
9
18
  function createDatabase(args, parent) {
10
19
  assertUserResourceId(args.resourceId);
@@ -92,9 +92,12 @@ export type TeamspaceIntent = TeamspaceArgs & {
92
92
  parent: Parent;
93
93
  };
94
94
  export type DatabaseIntent = DatabaseArgs & {
95
+ parent: Parent;
95
96
  dataSources: readonly DataSourceDefinition[];
96
97
  };
97
- export type PageIntent = PageArgs;
98
+ export type PageIntent = PageArgs & {
99
+ parent: Parent;
100
+ };
98
101
  export type CustomAgentIntent = CustomAgentArgs;
99
102
  export type ViewIntent = {
100
103
  databaseResourceId: ResourceId;
@@ -1 +1 @@
1
- {"version":3,"file":"intents.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/intents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intents.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/intents.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
- import type { DataSourceDefinition, NotionAsCodeIcon, Parent, ResourceId } from "./intents.js";
1
+ import { type DataSourceDefinition, type Parent, type ResourceId } from "./intents.js";
2
2
  import { type ChildDatabaseArgs, type DatabaseHandle } from "./database.js";
3
3
  export type PageArgs = {
4
4
  resourceId: ResourceId;
5
- parent: Parent;
6
- icon?: NotionAsCodeIcon;
5
+ /** Omit to create a private top-level page in the Apps workspace. */
6
+ parent?: Parent;
7
7
  cover?: unknown;
8
8
  properties?: Record<string, unknown>;
9
9
  content?: string;
@@ -15,7 +15,7 @@ export type PageHandle = {
15
15
  addDatabase: <const DataSources extends readonly DataSourceDefinition[]>(args: ChildDatabaseArgs<DataSources>) => DatabaseHandle<DataSources>;
16
16
  addPage: (args: ChildPageArgs) => PageHandle;
17
17
  };
18
- /** Declare a top-level page. */
18
+ /** Declare a page, defaulting to a private top-level page in the Apps workspace. */
19
19
  export declare function page(args: PageArgs): PageHandle;
20
20
  /** Record a page declaration beneath its parent resource. */
21
21
  export declare function createPage(args: ChildPageArgs | PageArgs, parent: Parent): PageHandle;
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,gBAAgB,EAEhB,MAAM,EACN,UAAU,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAY,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAItF,MAAM,MAAM,QAAQ,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IAExB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACtE,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,KAChC,cAAc,CAAC,WAAW,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;CAC7C,CAAC;AAEF,gCAAgC;AAChC,wBAAgB,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAG/C;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAoBrF"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,oBAAoB,EAEzB,KAAK,MAAM,EACX,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAY,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAItF,MAAM,MAAM,QAAQ,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,SAAS,SAAS,oBAAoB,EAAE,EACtE,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC,KAChC,cAAc,CAAC,WAAW,CAAC,CAAC;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;CAC7C,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAS/C;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAoBrF"}
@@ -1,9 +1,18 @@
1
+ import {
2
+ APPS_WORKSPACE_RESOURCE_ID
3
+ } from "./intents.js";
1
4
  import { database } from "./database.js";
2
5
  import { recordIntent } from "./recorder.js";
3
6
  import { assertParent, assertUserResourceId } from "./resource.js";
4
7
  function page(args) {
5
- assertParent(args.parent);
6
- return createPage(args, args.parent);
8
+ if (args.parent) {
9
+ assertParent(args.parent);
10
+ }
11
+ const parent = args.parent ?? {
12
+ type: "resourceId",
13
+ resourceId: APPS_WORKSPACE_RESOURCE_ID
14
+ };
15
+ return createPage(args, parent);
7
16
  }
8
17
  function createPage(args, parent) {
9
18
  assertUserResourceId(args.resourceId);
@@ -0,0 +1,2 @@
1
+ export * from "@notionhq/custom-blocks/react";
2
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC"}
package/dist/react.js ADDED
@@ -0,0 +1 @@
1
+ export * from "@notionhq/custom-blocks/react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/apps",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "An SDK for building workflow apps for Notion",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -66,7 +66,20 @@
66
66
  "./notion-as-code/recorder": {
67
67
  "types": "./dist/notion-as-code/recorder.d.ts",
68
68
  "default": "./dist/notion-as-code/recorder.js"
69
- }
69
+ },
70
+ "./custom-block": {
71
+ "types": "./dist/custom-block.d.ts",
72
+ "default": "./dist/custom-block.js"
73
+ },
74
+ "./custom-blocks": {
75
+ "types": "./dist/custom-blocks.d.ts",
76
+ "default": "./dist/custom-blocks.js"
77
+ },
78
+ "./react": {
79
+ "types": "./dist/react.d.ts",
80
+ "default": "./dist/react.js"
81
+ },
82
+ "./nds.css": "./dist/nds.css"
70
83
  },
71
84
  "publishConfig": {
72
85
  "access": "public"
@@ -89,6 +102,7 @@
89
102
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json"
90
103
  },
91
104
  "dependencies": {
105
+ "@notionhq/custom-blocks": "latest",
92
106
  "ajv": "^8.17.1",
93
107
  "rolldown": "^1.1.5"
94
108
  },
@@ -34,6 +34,42 @@ afterEach(async () => {
34
34
  });
35
35
 
36
36
  describe("buildApp", () => {
37
+ it("emits workflow and block metadata without adding blocks to the Worker", async () => {
38
+ const root = fixture("custom-block");
39
+ const { manifest, bundlePath } = await buildApp(root);
40
+ expect(manifest.capabilities).toEqual([
41
+ { type: "workflow", key: "hello", config: { name: "Hello", triggers: [] } },
42
+ {
43
+ type: "custom_block",
44
+ key: "hello",
45
+ config: {
46
+ source: {
47
+ type: "project",
48
+ path: "./blocks/hello",
49
+ command: "npx vite build",
50
+ output: "dist",
51
+ },
52
+ manifest: { version: 1, slashCommand: "hello", dataSources: {} },
53
+ },
54
+ },
55
+ ]);
56
+ const worker = await fs.promises.readFile(bundlePath, "utf8");
57
+ expect(worker).not.toMatch(
58
+ /custom_block|createCustomBlock|react|createRoot|NotionCustomBlock/,
59
+ );
60
+ expect(fs.existsSync(path.join(root, ".notion/blocks.js"))).toBe(false);
61
+ });
62
+ it("names the block file when path is missing", async () => {
63
+ await expect(buildApp(fixture("custom-block-missing-path"))).rejects.toThrow(
64
+ /hello.ts.*non-empty path/,
65
+ );
66
+ });
67
+ it("rejects a block file that exports a different capability", async () => {
68
+ await expect(buildApp(fixture("custom-block-wrong-tag"))).rejects.toThrow(
69
+ /hello.ts.*createCustomBlock/,
70
+ );
71
+ });
72
+
37
73
  it("builds a sync manifest with a Notion-as-Code meetings database", async () => {
38
74
  const { manifest } = await buildApp(example("sync"));
39
75
 
@@ -142,6 +178,24 @@ describe("buildApp", () => {
142
178
  name: "GitHub app data",
143
179
  accessLevel: "open",
144
180
  },
181
+ {
182
+ type: "page",
183
+ resourceId: "github-app-private-page",
184
+ parent: {
185
+ type: "resourceId",
186
+ resourceId: "__notion_apps_workspace__",
187
+ },
188
+ properties: { title: [["GitHub app home"]] },
189
+ },
190
+ {
191
+ type: "database",
192
+ resourceId: "github-app-private-database",
193
+ parent: {
194
+ type: "resourceId",
195
+ resourceId: "__notion_apps_workspace__",
196
+ },
197
+ dataSources: [],
198
+ },
145
199
  {
146
200
  type: "database",
147
201
  resourceId: "issues-db",
package/src/cli/build.ts CHANGED
@@ -9,9 +9,9 @@ import {
9
9
  type RecordedProvisioning,
10
10
  } from "../notion-as-code/provisioning.js";
11
11
  import { clearRegisteredPacers } from "../pacer.js";
12
- import { generateEntry, generateMetadataEntry } from "./codegen.js";
12
+ import { generateEntry, generateMetadataEntry, generateBlocksEntry } from "./codegen.js";
13
13
  import { discoverCapabilities, type DiscoveredCapability } from "./discover.js";
14
- import { extractManifest, extractProvisioning } from "./emit-manifest.js";
14
+ import { extractManifest, extractProvisioning, extractBlockConfigs } from "./emit-manifest.js";
15
15
 
16
16
  export type BuildResult = {
17
17
  manifest: AppManifest;
@@ -53,11 +53,15 @@ export async function buildApp(projectRoot: string): Promise<BuildResult> {
53
53
  await bundle.close();
54
54
  }
55
55
 
56
- const manifest = await extractManifest(bundlePath, capabilities);
56
+ const blockConfigs = await buildBlockDeclarations(projectRoot, capabilities);
57
+ const manifest = await extractManifest(bundlePath, capabilities, blockConfigs);
57
58
  const manifestPath = path.join(projectRoot, "dist", "manifest.json");
58
59
  await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
59
60
 
60
- await emitProvisioningArtifact(projectRoot, capabilities);
61
+ await emitProvisioningArtifact(
62
+ projectRoot,
63
+ capabilities.filter((capability) => capability.runtime === "worker"),
64
+ );
61
65
 
62
66
  return { manifest, bundlePath, manifestPath };
63
67
  }
@@ -110,3 +114,36 @@ async function buildMetadata(
110
114
  await fs.promises.rm(metadataBundlePath, { force: true });
111
115
  }
112
116
  }
117
+
118
+ async function buildBlockDeclarations(
119
+ projectRoot: string,
120
+ capabilities: readonly DiscoveredCapability[],
121
+ ): Promise<Record<string, unknown>> {
122
+ if (!capabilities.some((capability) => capability.runtime === "build")) return {};
123
+ const entryPath = path.join(projectRoot, ".notion", "blocks-entry.ts");
124
+ const bundlePath = path.join(projectRoot, ".notion", "blocks.js");
125
+ await fs.promises.writeFile(entryPath, generateBlocksEntry(capabilities));
126
+ try {
127
+ const bundle = await rolldown({
128
+ input: entryPath,
129
+ platform: "node",
130
+ external: (id) => !id.startsWith(".") && !path.isAbsolute(id),
131
+ });
132
+ try {
133
+ await bundle.write({ format: "esm", file: bundlePath, codeSplitting: false });
134
+ } finally {
135
+ await bundle.close();
136
+ }
137
+ return await extractBlockConfigs(bundlePath);
138
+ } catch (error) {
139
+ throw new Error(
140
+ `Failed to read custom block declarations (${capabilities
141
+ .filter((c) => c.runtime === "build")
142
+ .map((c) => c.sourcePath)
143
+ .join(", ")}): ${error instanceof Error ? error.message : String(error)}`,
144
+ { cause: error },
145
+ );
146
+ } finally {
147
+ await fs.promises.rm(bundlePath, { force: true });
148
+ }
149
+ }
@@ -8,6 +8,7 @@ describe("generateEntry", () => {
8
8
  {
9
9
  type: "workflow",
10
10
  tag: "workflow",
11
+ runtime: "worker",
11
12
  key: "onPageCreated",
12
13
  sourcePath: "src/workflows/onPageCreated.ts",
13
14
  },
@@ -24,6 +25,7 @@ describe("generateEntry", () => {
24
25
  {
25
26
  type: "sync",
26
27
  tag: "sync",
28
+ runtime: "worker",
27
29
  key: "issues",
28
30
  sourcePath: "src/syncs/issues.ts",
29
31
  },
@@ -11,14 +11,18 @@ export function generateEntry(capabilities: readonly DiscoveredCapability[]): st
11
11
  const imports: string[] = [];
12
12
  const registryEntries = new Map<string, string[]>();
13
13
 
14
- capabilities.forEach((capability, index) => {
15
- const binding = `capability_${index}`;
16
- imports.push(`import ${binding} from ${JSON.stringify(`../${capability.sourcePath}`)};`);
14
+ capabilities
15
+ .filter((capability) => capability.runtime !== "build")
16
+ .forEach((capability, index) => {
17
+ const binding = `capability_${index}`;
18
+ imports.push(
19
+ `import ${binding} from ${JSON.stringify(`../${capability.sourcePath}`)};`,
20
+ );
17
21
 
18
- const entries = registryEntries.get(capability.type) ?? [];
19
- entries.push(`\t\t${JSON.stringify(capability.key)}: ${binding},`);
20
- registryEntries.set(capability.type, entries);
21
- });
22
+ const entries = registryEntries.get(capability.type) ?? [];
23
+ entries.push(`\t\t${JSON.stringify(capability.key)}: ${binding},`);
24
+ registryEntries.set(capability.type, entries);
25
+ });
22
26
 
23
27
  const registry = Array.from(registryEntries.entries())
24
28
  .map(([type, entries]) => `\t${JSON.stringify(type)}: {\n${entries.join("\n")}\n\t},`)
@@ -70,3 +74,17 @@ ${imports}
70
74
  export default finishMetadataRecording(recorder);
71
75
  `;
72
76
  }
77
+
78
+ /** Evaluate block declarations only while building, separately from the Worker. */
79
+ export function generateBlocksEntry(capabilities: readonly DiscoveredCapability[]): string {
80
+ const entries = capabilities.filter((capability) => capability.runtime === "build");
81
+ return (
82
+ entries
83
+ .map(
84
+ (capability, index) =>
85
+ `import block_${index} from ${JSON.stringify(`../${capability.sourcePath}`)};`,
86
+ )
87
+ .join("\n") +
88
+ `\nexport const capabilities = { custom_block: {${entries.map((capability, index) => `${JSON.stringify(capability.key)}: block_${index}`).join(",")}} };\n`
89
+ );
90
+ }
@@ -9,11 +9,21 @@ const EXAMPLES = fileURLToPath(new URL("../../examples/", import.meta.url));
9
9
  const FIXTURES = fileURLToPath(new URL("../../test/fixtures/", import.meta.url));
10
10
 
11
11
  describe("discoverCapabilities", () => {
12
+ it("discovers custom block declaration files as build-only capabilities", async () => {
13
+ expect(await discoverCapabilities(path.join(FIXTURES, "custom-block"))).toContainEqual({
14
+ type: "custom_block",
15
+ tag: "custom_block",
16
+ runtime: "build",
17
+ key: "hello",
18
+ sourcePath: path.join("src", "customBlocks", "hello.ts"),
19
+ });
20
+ });
12
21
  it("discovers workflow files", async () => {
13
22
  await expect(discoverCapabilities(path.join(EXAMPLES, "workflow"))).resolves.toEqual([
14
23
  {
15
24
  type: "workflow",
16
25
  tag: "workflow",
26
+ runtime: "worker",
17
27
  key: "onPageCreated",
18
28
  sourcePath: path.join("src", "workflows", "onPageCreated.ts"),
19
29
  },
@@ -25,12 +35,14 @@ describe("discoverCapabilities", () => {
25
35
  {
26
36
  type: "sync",
27
37
  tag: "sync",
38
+ runtime: "worker",
28
39
  key: "issues",
29
40
  sourcePath: path.join("src", "syncs", "issues.ts"),
30
41
  },
31
42
  {
32
43
  type: "sync",
33
44
  tag: "sync",
45
+ runtime: "worker",
34
46
  key: "notionAsCodeIssues",
35
47
  sourcePath: path.join("src", "syncs", "notionAsCodeIssues.ts"),
36
48
  },
@@ -12,11 +12,13 @@ export type CapabilityTypeDefinition = {
12
12
  type: CapabilityType;
13
13
  directory: string;
14
14
  tag: string;
15
+ runtime: "worker" | "build";
15
16
  };
16
17
 
17
18
  export const CAPABILITY_TYPES: readonly CapabilityTypeDefinition[] = [
18
- { type: "workflow", directory: "workflows", tag: "workflow" },
19
- { type: "sync", directory: "syncs", tag: "sync" },
19
+ { type: "workflow", directory: "workflows", tag: "workflow", runtime: "worker" },
20
+ { type: "sync", directory: "syncs", tag: "sync", runtime: "worker" },
21
+ { type: "custom_block", directory: "customBlocks", tag: "custom_block", runtime: "build" },
20
22
  ];
21
23
 
22
24
  /**
@@ -26,6 +28,7 @@ export const CAPABILITY_TYPES: readonly CapabilityTypeDefinition[] = [
26
28
  export type DiscoveredCapability = {
27
29
  type: CapabilityType;
28
30
  tag: string;
31
+ runtime: "worker" | "build";
29
32
  key: string;
30
33
  /** Path to the source file, relative to the project root. */
31
34
  sourcePath: string;
@@ -64,6 +67,7 @@ export async function discoverCapabilities(projectRoot: string): Promise<Discove
64
67
  discovered.push({
65
68
  type: definition.type,
66
69
  tag: definition.tag,
70
+ runtime: definition.runtime,
67
71
  key: entry.name.slice(0, -".ts".length),
68
72
  sourcePath: path.join("src", definition.directory, entry.name),
69
73
  });
@@ -77,7 +77,7 @@ const MANIFEST_SCHEMA = {
77
77
  items: {
78
78
  type: "object",
79
79
  properties: {
80
- type: { type: "string", enum: ["workflow", "sync"] },
80
+ type: { type: "string", enum: ["workflow", "sync", "custom_block"] },
81
81
  key: { type: "string" },
82
82
  config: {},
83
83
  },
@@ -216,6 +216,7 @@ class DatabaseCollector {
216
216
  export async function extractManifest(
217
217
  bundlePath: string,
218
218
  capabilities: readonly DiscoveredCapability[],
219
+ blockConfigs: Record<string, unknown> = {},
219
220
  ): Promise<AppManifest> {
220
221
  clearRegisteredPacers();
221
222
  const bundle = await importBundle(bundlePath);
@@ -224,11 +225,15 @@ export async function extractManifest(
224
225
  const primaryKeyByDatabase = new Map<string, string>();
225
226
 
226
227
  const capabilityEntries = capabilities.map((capability) => {
227
- const tagged = asTagged(registry[capability.type]?.[capability.key]);
228
+ const tagged = asTagged(
229
+ capability.runtime === "build"
230
+ ? blockConfigs[capability.key]
231
+ : registry[capability.type]?.[capability.key],
232
+ );
228
233
  if (!tagged || tagged._tag !== capability.tag) {
229
234
  throw new Error(
230
235
  `${capability.sourcePath}: default export is not a ${capability.type} — ` +
231
- `files under this directory must default-export create${capitalize(capability.type)}(...)`,
236
+ `files under this directory must default-export create${capability.type === "custom_block" ? "CustomBlock" : capitalize(capability.type)}(...)`,
232
237
  );
233
238
  }
234
239
 
@@ -382,3 +387,8 @@ export function assertJsonSafe(value: unknown, path: string): void {
382
387
  if (property !== undefined) assertJsonSafe(property, `${path}.${key}`);
383
388
  }
384
389
  }
390
+
391
+ /** Read the isolated declaration bundle; browser declarations never enter the Worker. */
392
+ export async function extractBlockConfigs(bundlePath: string): Promise<Record<string, unknown>> {
393
+ return getCapabilityRegistry(await importBundle(bundlePath)).custom_block ?? {};
394
+ }
package/src/cli/index.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { build, upload, printBuildOutput, parseUploadsInput } from "@notionhq/custom-blocks/bundle";
3
5
 
4
6
  import { buildApp } from "./build.js";
5
7
 
6
8
  const USAGE = `Usage: notion-apps <command>
7
9
 
8
10
  Commands:
9
- build Build the app in the current directory
11
+ build Build the app in the current directory
12
+ build-blocks Build browser bundles (--manifest <path> --out <dir>)
13
+ upload-blocks Upload browser bundles (--uploads <json>)
10
14
  `;
11
15
 
12
16
  async function main(): Promise<void> {
@@ -24,6 +28,34 @@ async function main(): Promise<void> {
24
28
  );
25
29
  break;
26
30
  }
31
+ case "build-blocks": {
32
+ const { values } = parseArgs({
33
+ args: process.argv.slice(3),
34
+ options: {
35
+ manifest: { type: "string" },
36
+ out: { type: "string" },
37
+ cwd: { type: "string" },
38
+ },
39
+ });
40
+ if (!values.out) throw new Error("build-blocks requires --out <dir>");
41
+ printBuildOutput(
42
+ await build({
43
+ out: values.out,
44
+ manifest: values.manifest ?? "dist/manifest.json",
45
+ cwd: values.cwd,
46
+ }),
47
+ );
48
+ break;
49
+ }
50
+ case "upload-blocks": {
51
+ const { values } = parseArgs({
52
+ args: process.argv.slice(3),
53
+ options: { uploads: { type: "string" } },
54
+ });
55
+ if (!values.uploads) throw new Error("upload-blocks requires --uploads <json>");
56
+ console.log(JSON.stringify(await upload(parseUploadsInput(values.uploads))));
57
+ break;
58
+ }
27
59
  case undefined:
28
60
  case "help":
29
61
  case "--help":
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createCustomBlock } from "./custom-block.js";
3
+
4
+ describe("createCustomBlock", () => {
5
+ it("supports static assets without a build command", () => {
6
+ expect(createCustomBlock({ path: "assets", static: true }).config.source).toEqual({
7
+ type: "static",
8
+ path: "assets",
9
+ });
10
+ });
11
+ it.each(["//hello", "Hello", "-hello", "", "a".repeat(65)])(
12
+ "rejects invalid slash command %s",
13
+ (slashCommand) => {
14
+ expect(() => createCustomBlock({ path: "blocks/hello", slashCommand })).toThrow(
15
+ /slashCommand/,
16
+ );
17
+ },
18
+ );
19
+ });
@@ -0,0 +1,41 @@
1
+ import type { ManifestDataSource } from "@notionhq/custom-blocks";
2
+
3
+ export type CustomBlockOptions = {
4
+ path: string;
5
+ slashCommand?: string;
6
+ dataSources?: Record<string, ManifestDataSource>;
7
+ } & (
8
+ | { static: true; command?: never; output?: never }
9
+ | { static?: false; command?: string; output?: string }
10
+ );
11
+
12
+ /** Declare a browser project. Its filename under src/customBlocks supplies its key. */
13
+ export function createCustomBlock(options: CustomBlockOptions) {
14
+ if (typeof options.path !== "string" || options.path.trim() === "") {
15
+ throw new Error("createCustomBlock requires a non-empty path relative to the app root");
16
+ }
17
+ const slashCommand = options.slashCommand?.replace(/^\//, "");
18
+ if (slashCommand !== undefined && !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(slashCommand)) {
19
+ throw new Error(
20
+ "Custom block slashCommand must be 1–64 lowercase letters, numbers, hyphens or underscores, starting with a letter or number",
21
+ );
22
+ }
23
+ return {
24
+ _tag: "custom_block" as const,
25
+ config: {
26
+ source: options.static
27
+ ? { type: "static" as const, path: options.path }
28
+ : {
29
+ type: "project" as const,
30
+ path: options.path,
31
+ command: options.command ?? "npx vite build",
32
+ output: options.output ?? "dist",
33
+ },
34
+ manifest: {
35
+ version: 1 as const,
36
+ ...(slashCommand === undefined ? {} : { slashCommand }),
37
+ dataSources: options.dataSources ?? {},
38
+ },
39
+ },
40
+ };
41
+ }
@@ -0,0 +1 @@
1
+ export * from "@notionhq/custom-blocks";
package/src/manifest.ts CHANGED
@@ -4,7 +4,7 @@ import type { NormalizedSchedule } from "./types.js";
4
4
  export const APPS_MANIFEST_SCHEMA = "notion:apps-manifest:v1" as const;
5
5
 
6
6
  /** Capability types supported by this SDK. */
7
- export type CapabilityType = "workflow" | "sync";
7
+ export type CapabilityType = "workflow" | "sync" | "custom_block";
8
8
 
9
9
  export type DatabaseManifestConfig = {
10
10
  initialTitle?: string;
package/src/nds.css ADDED
@@ -0,0 +1 @@
1
+ @import "@notionhq/custom-blocks/nds.css";
@@ -1,11 +1,12 @@
1
1
  import type { Database } from "../database.js";
2
- import type {
3
- DataSourceDefinition,
4
- DatabaseIntent,
5
- NotionAsCodeIcon,
6
- NotionAsCodeProperty,
7
- Parent,
8
- ResourceId,
2
+ import {
3
+ APPS_WORKSPACE_RESOURCE_ID,
4
+ type DataSourceDefinition,
5
+ type DatabaseIntent,
6
+ type NotionAsCodeIcon,
7
+ type NotionAsCodeProperty,
8
+ type Parent,
9
+ type ResourceId,
9
10
  } from "./intents.js";
10
11
  import { createPage, type ChildPageArgs, type PageHandle } from "./page.js";
11
12
  import { recordIntent } from "./recorder.js";
@@ -17,7 +18,8 @@ export type DatabaseArgs<
17
18
  DataSources extends readonly DataSourceDefinition[] = readonly DataSourceDefinition[],
18
19
  > = {
19
20
  resourceId: ResourceId;
20
- parent: Parent;
21
+ /** Omit to create a private top-level database in the Apps workspace. */
22
+ parent?: Parent;
21
23
  dataSources?: DataSources;
22
24
  // TODO: Port view types.
23
25
  views?: readonly unknown[];
@@ -69,13 +71,19 @@ export type DatabaseHandle<
69
71
  addView: (view: unknown) => void;
70
72
  };
71
73
 
72
- /** Declare a top-level database and its data sources. */
74
+ /** Declare a database, defaulting to a private top-level database in the Apps workspace. */
73
75
  export function database<const DataSources extends readonly DataSourceDefinition[]>(
74
76
  args: DatabaseArgs<DataSources>,
75
77
  ): DatabaseHandle<DataSources>;
76
78
  export function database(args: DatabaseArgs): DatabaseHandle {
77
- assertParent(args.parent);
78
- return createDatabase(args, args.parent);
79
+ if (args.parent) {
80
+ assertParent(args.parent);
81
+ }
82
+ const parent = args.parent ?? {
83
+ type: "resourceId",
84
+ resourceId: APPS_WORKSPACE_RESOURCE_ID,
85
+ };
86
+ return createDatabase(args, parent);
79
87
  }
80
88
 
81
89
  /** Record a database declaration and create handles for each of its data sources. */
@@ -107,10 +107,11 @@ export type TeamspaceIntent = TeamspaceArgs & {
107
107
  };
108
108
 
109
109
  export type DatabaseIntent = DatabaseArgs & {
110
+ parent: Parent;
110
111
  dataSources: readonly DataSourceDefinition[];
111
112
  };
112
113
 
113
- export type PageIntent = PageArgs;
114
+ export type PageIntent = PageArgs & { parent: Parent };
114
115
  export type CustomAgentIntent = CustomAgentArgs;
115
116
 
116
117
  export type ViewIntent = {
@@ -1,9 +1,9 @@
1
- import type {
2
- DataSourceDefinition,
3
- NotionAsCodeIcon,
4
- PageIntent,
5
- Parent,
6
- ResourceId,
1
+ import {
2
+ APPS_WORKSPACE_RESOURCE_ID,
3
+ type DataSourceDefinition,
4
+ type PageIntent,
5
+ type Parent,
6
+ type ResourceId,
7
7
  } from "./intents.js";
8
8
  import { database, type ChildDatabaseArgs, type DatabaseHandle } from "./database.js";
9
9
  import { recordIntent } from "./recorder.js";
@@ -11,8 +11,8 @@ import { assertParent, assertUserResourceId } from "./resource.js";
11
11
 
12
12
  export type PageArgs = {
13
13
  resourceId: ResourceId;
14
- parent: Parent;
15
- icon?: NotionAsCodeIcon;
14
+ /** Omit to create a private top-level page in the Apps workspace. */
15
+ parent?: Parent;
16
16
  // TODO: Port cover types.
17
17
  cover?: unknown;
18
18
  // TODO: Port page property types.
@@ -31,10 +31,16 @@ export type PageHandle = {
31
31
  addPage: (args: ChildPageArgs) => PageHandle;
32
32
  };
33
33
 
34
- /** Declare a top-level page. */
34
+ /** Declare a page, defaulting to a private top-level page in the Apps workspace. */
35
35
  export function page(args: PageArgs): PageHandle {
36
- assertParent(args.parent);
37
- return createPage(args, args.parent);
36
+ if (args.parent) {
37
+ assertParent(args.parent);
38
+ }
39
+ const parent = args.parent ?? {
40
+ type: "resourceId",
41
+ resourceId: APPS_WORKSPACE_RESOURCE_ID,
42
+ };
43
+ return createPage(args, parent);
38
44
  }
39
45
 
40
46
  /** Record a page declaration beneath its parent resource. */
package/src/react.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@notionhq/custom-blocks/react";