@notionhq/apps 0.0.12 → 0.0.14

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 (137) hide show
  1. package/README.md +32 -0
  2. package/dist/box.generated.d.ts +164 -0
  3. package/dist/box.generated.d.ts.map +1 -0
  4. package/dist/box.generated.js +24 -0
  5. package/dist/calendar.generated.d.ts +1036 -0
  6. package/dist/calendar.generated.d.ts.map +1 -0
  7. package/dist/calendar.generated.js +42 -0
  8. package/dist/cli/build.d.ts.map +1 -1
  9. package/dist/cli/build.js +41 -10
  10. package/dist/cli/codegen.d.ts +2 -0
  11. package/dist/cli/codegen.d.ts.map +1 -1
  12. package/dist/cli/codegen.js +13 -2
  13. package/dist/cli/discover.d.ts +2 -0
  14. package/dist/cli/discover.d.ts.map +1 -1
  15. package/dist/cli/discover.js +4 -2
  16. package/dist/cli/emit-manifest.d.ts +3 -1
  17. package/dist/cli/emit-manifest.d.ts.map +1 -1
  18. package/dist/cli/emit-manifest.js +10 -4
  19. package/dist/cli/index.js +33 -1
  20. package/dist/confluence.generated.d.ts +98 -0
  21. package/dist/confluence.generated.d.ts.map +1 -0
  22. package/dist/confluence.generated.js +18 -0
  23. package/dist/connection-actions.d.ts +2 -0
  24. package/dist/connection-actions.d.ts.map +1 -0
  25. package/dist/connection-actions.js +0 -0
  26. package/dist/connection-trigger-definitions.generated.d.ts +34 -0
  27. package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
  28. package/dist/connection-trigger-definitions.generated.js +45 -0
  29. package/dist/connections.d.ts +15 -0
  30. package/dist/connections.d.ts.map +1 -0
  31. package/dist/connections.js +79 -0
  32. package/dist/connections.test.d.ts +2 -0
  33. package/dist/connections.test.d.ts.map +1 -0
  34. package/dist/cursor.generated.d.ts +154 -0
  35. package/dist/cursor.generated.d.ts.map +1 -0
  36. package/dist/cursor.generated.js +18 -0
  37. package/dist/custom-block.d.ts +54 -0
  38. package/dist/custom-block.d.ts.map +1 -0
  39. package/dist/custom-block.js +30 -0
  40. package/dist/custom-block.test.d.ts +2 -0
  41. package/dist/custom-block.test.d.ts.map +1 -0
  42. package/dist/custom-blocks.d.ts +2 -0
  43. package/dist/custom-blocks.d.ts.map +1 -0
  44. package/dist/custom-blocks.js +1 -0
  45. package/dist/discord.generated.d.ts +279 -0
  46. package/dist/discord.generated.d.ts.map +1 -0
  47. package/dist/discord.generated.js +33 -0
  48. package/dist/gmail.generated.d.ts +184 -0
  49. package/dist/gmail.generated.d.ts.map +1 -0
  50. package/dist/gmail.generated.js +21 -0
  51. package/dist/googleCalendar.generated.d.ts +83 -0
  52. package/dist/googleCalendar.generated.d.ts.map +1 -0
  53. package/dist/googleCalendar.generated.js +15 -0
  54. package/dist/googleDrive.generated.d.ts +210 -0
  55. package/dist/googleDrive.generated.d.ts.map +1 -0
  56. package/dist/googleDrive.generated.js +27 -0
  57. package/dist/googleDriveOauth.generated.d.ts +851 -0
  58. package/dist/googleDriveOauth.generated.d.ts.map +1 -0
  59. package/dist/googleDriveOauth.generated.js +84 -0
  60. package/dist/manifest.d.ts +1 -1
  61. package/dist/manifest.d.ts.map +1 -1
  62. package/dist/nds.css +1 -0
  63. package/dist/notion-as-code/database.d.ts +4 -3
  64. package/dist/notion-as-code/database.d.ts.map +1 -1
  65. package/dist/notion-as-code/database.js +11 -2
  66. package/dist/notion-as-code/intents.d.ts +4 -1
  67. package/dist/notion-as-code/intents.d.ts.map +1 -1
  68. package/dist/notion-as-code/page.d.ts +4 -4
  69. package/dist/notion-as-code/page.d.ts.map +1 -1
  70. package/dist/notion-as-code/page.js +11 -2
  71. package/dist/outlook.generated.d.ts +224 -0
  72. package/dist/outlook.generated.d.ts.map +1 -0
  73. package/dist/outlook.generated.js +24 -0
  74. package/dist/providers.generated.d.ts +117 -0
  75. package/dist/providers.generated.d.ts.map +1 -0
  76. package/dist/providers.generated.js +108 -0
  77. package/dist/react.d.ts +2 -0
  78. package/dist/react.d.ts.map +1 -0
  79. package/dist/react.js +1 -0
  80. package/dist/salesforce.generated.d.ts +151 -0
  81. package/dist/salesforce.generated.d.ts.map +1 -0
  82. package/dist/salesforce.generated.js +24 -0
  83. package/dist/slack.generated.d.ts +290 -0
  84. package/dist/slack.generated.d.ts.map +1 -0
  85. package/dist/slack.generated.js +30 -0
  86. package/dist/triggers.generated.d.ts +107 -32
  87. package/dist/triggers.generated.d.ts.map +1 -1
  88. package/dist/triggers.generated.js +36 -24
  89. package/dist/workflow-connections-types.test.d.ts +2 -0
  90. package/dist/workflow-connections-types.test.d.ts.map +1 -0
  91. package/dist/workflow-types.test.d.ts +2 -0
  92. package/dist/workflow-types.test.d.ts.map +1 -0
  93. package/dist/workflow.d.ts +20 -16
  94. package/dist/workflow.d.ts.map +1 -1
  95. package/dist/workflow.js +14 -17
  96. package/docs/BUILD.md +83 -14
  97. package/docs/CONNECTIONS.md +129 -0
  98. package/package.json +16 -2
  99. package/src/box.generated.ts +187 -0
  100. package/src/calendar.generated.ts +1117 -0
  101. package/src/cli/build.test.ts +87 -0
  102. package/src/cli/build.ts +47 -9
  103. package/src/cli/codegen.test.ts +2 -0
  104. package/src/cli/codegen.ts +25 -7
  105. package/src/cli/discover.test.ts +12 -0
  106. package/src/cli/discover.ts +6 -2
  107. package/src/cli/emit-manifest.ts +13 -3
  108. package/src/cli/index.ts +33 -1
  109. package/src/confluence.generated.ts +109 -0
  110. package/src/connection-actions.ts +2 -0
  111. package/src/connection-trigger-definitions.generated.ts +48 -0
  112. package/src/connections.test.ts +157 -0
  113. package/src/connections.ts +126 -0
  114. package/src/cursor.generated.ts +167 -0
  115. package/src/custom-block.test.ts +19 -0
  116. package/src/custom-block.ts +41 -0
  117. package/src/custom-blocks.ts +1 -0
  118. package/src/discord.generated.ts +318 -0
  119. package/src/gmail.generated.ts +205 -0
  120. package/src/googleCalendar.generated.ts +95 -0
  121. package/src/googleDrive.generated.ts +235 -0
  122. package/src/googleDriveOauth.generated.ts +980 -0
  123. package/src/manifest.ts +1 -1
  124. package/src/nds.css +1 -0
  125. package/src/notion-as-code/database.ts +19 -11
  126. package/src/notion-as-code/intents.ts +2 -1
  127. package/src/notion-as-code/page.ts +17 -11
  128. package/src/outlook.generated.ts +249 -0
  129. package/src/providers.generated.ts +207 -0
  130. package/src/react.ts +1 -0
  131. package/src/salesforce.generated.ts +170 -0
  132. package/src/slack.generated.ts +330 -0
  133. package/src/triggers.generated.ts +112 -32
  134. package/src/workflow-connections-types.test.ts +59 -0
  135. package/src/workflow-types.test.ts +144 -0
  136. package/src/workflow.test.ts +112 -2
  137. package/src/workflow.ts +58 -29
@@ -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",
@@ -162,6 +216,39 @@ describe("buildApp", () => {
162
216
  });
163
217
  });
164
218
 
219
+ it("removes provisioning when a rebuild no longer declares Notion resources", async () => {
220
+ const root = await fs.promises.mkdtemp(path.join(FIXTURES, "provisioning-rebuild-"));
221
+ try {
222
+ await fs.promises.cp(
223
+ path.join(FIXTURES, "notion-as-code", "src"),
224
+ path.join(root, "src"),
225
+ {
226
+ recursive: true,
227
+ },
228
+ );
229
+ await buildApp(root);
230
+ const provisioningPath = path.join(root, "dist", "provisioning.json");
231
+ const provisioning = JSON.parse(await fs.promises.readFile(provisioningPath, "utf8"));
232
+ expect(provisioning.intents).toEqual(
233
+ expect.arrayContaining([expect.objectContaining({ resourceId: "issues-db" })]),
234
+ );
235
+
236
+ await fs.promises.rm(path.join(root, "src"), { recursive: true });
237
+ await fs.promises.cp(path.join(EXAMPLES, "workflow", "src"), path.join(root, "src"), {
238
+ recursive: true,
239
+ });
240
+ const { manifest } = await buildApp(root);
241
+ expect(manifest.capabilities).toEqual([
242
+ expect.objectContaining({ type: "workflow", key: "onPageCreated" }),
243
+ ]);
244
+ await expect(fs.promises.readFile(provisioningPath)).rejects.toMatchObject({
245
+ code: "ENOENT",
246
+ });
247
+ } finally {
248
+ await fs.promises.rm(root, { recursive: true, force: true });
249
+ }
250
+ });
251
+
165
252
  it("builds a workflow bundle and manifest", async () => {
166
253
  const { manifest, bundlePath, manifestPath } = await buildApp(example("workflow"));
167
254
 
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
  }
@@ -68,13 +72,14 @@ async function emitProvisioningArtifact(
68
72
  capabilities: readonly DiscoveredCapability[],
69
73
  ): Promise<void> {
70
74
  const entityProvisioning = await buildMetadata(projectRoot, capabilities);
71
- if (entityProvisioning.intents.length === 0) return;
75
+ const provisioningPath = path.join(projectRoot, "dist", "provisioning.json");
76
+ if (entityProvisioning.intents.length === 0) {
77
+ await fs.promises.rm(provisioningPath, { force: true });
78
+ return;
79
+ }
72
80
 
73
81
  const artifact = createProvisioningArtifact(entityProvisioning.intents);
74
- await fs.promises.writeFile(
75
- path.join(projectRoot, "dist", "provisioning.json"),
76
- `${JSON.stringify(artifact, null, "\t")}\n`,
77
- );
82
+ await fs.promises.writeFile(provisioningPath, `${JSON.stringify(artifact, null, "\t")}\n`);
78
83
  }
79
84
 
80
85
  /** Bundle capabilities separately to record their Notion-as-Code declarations without shipping them. */
@@ -110,3 +115,36 @@ async function buildMetadata(
110
115
  await fs.promises.rm(metadataBundlePath, { force: true });
111
116
  }
112
117
  }
118
+
119
+ async function buildBlockDeclarations(
120
+ projectRoot: string,
121
+ capabilities: readonly DiscoveredCapability[],
122
+ ): Promise<Record<string, unknown>> {
123
+ if (!capabilities.some((capability) => capability.runtime === "build")) return {};
124
+ const entryPath = path.join(projectRoot, ".notion", "blocks-entry.ts");
125
+ const bundlePath = path.join(projectRoot, ".notion", "blocks.js");
126
+ await fs.promises.writeFile(entryPath, generateBlocksEntry(capabilities));
127
+ try {
128
+ const bundle = await rolldown({
129
+ input: entryPath,
130
+ platform: "node",
131
+ external: (id) => !id.startsWith(".") && !path.isAbsolute(id),
132
+ });
133
+ try {
134
+ await bundle.write({ format: "esm", file: bundlePath, codeSplitting: false });
135
+ } finally {
136
+ await bundle.close();
137
+ }
138
+ return await extractBlockConfigs(bundlePath);
139
+ } catch (error) {
140
+ throw new Error(
141
+ `Failed to read custom block declarations (${capabilities
142
+ .filter((c) => c.runtime === "build")
143
+ .map((c) => c.sourcePath)
144
+ .join(", ")}): ${error instanceof Error ? error.message : String(error)}`,
145
+ { cause: error },
146
+ );
147
+ } finally {
148
+ await fs.promises.rm(bundlePath, { force: true });
149
+ }
150
+ }
@@ -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,109 @@
1
+ // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
+ import type { ConnectionActionInvoker } from "./connection-actions.js"
3
+ /**
4
+ * Script input for the "Query Confluence with CQL" effect (`confluence.cqlQuery`),
5
+ * derived from its Tool Core definition's call_function input projection.
6
+ *
7
+ * Run a CQL query and return results.
8
+ */
9
+ export type CqlQueryScriptInput = {
10
+ /**
11
+ * CQL query string. Only read-only queries are supported. Example: type=page AND space=DEV AND text~"deployment"
12
+ */
13
+ query: string
14
+ /**
15
+ * Maximum number of results to return. Defaults to 20.
16
+ */
17
+ maxResults?: number
18
+ }
19
+
20
+ /**
21
+ * Canonical Tool Core output for the "Query Confluence with CQL" effect (`confluence.cqlQuery`).
22
+ * This is the definition's output schema; the effect's script surface may
23
+ * re-encode it before returning.
24
+ */
25
+ export type CqlQueryScriptOutput = {
26
+ results: Array<{
27
+ id: string
28
+ title: string
29
+ blocks: Array<string>
30
+ }>
31
+ query: string
32
+ baseUrl: string
33
+ }
34
+
35
+ /**
36
+ * Script input for the "Load Confluence page" effect (`confluence.loadPage`),
37
+ * derived from its Tool Core definition's call_function input projection.
38
+ *
39
+ * Load a Confluence page by ID.
40
+ */
41
+ export type LoadPageScriptInput = {
42
+ /**
43
+ * The Confluence page ID to load.
44
+ */
45
+ pageId: string
46
+ }
47
+
48
+ /**
49
+ * Canonical Tool Core output for the "Load Confluence page" effect (`confluence.loadPage`).
50
+ * This is the definition's output schema; the effect's script surface may
51
+ * re-encode it before returning.
52
+ */
53
+ export type LoadPageScriptOutput = {
54
+ type: "confluence-page"
55
+ title: string
56
+ blocks: Array<string>
57
+ pageId: string
58
+ }
59
+
60
+ /**
61
+ * Script input for the "Search Confluence" effect (`confluence.search`),
62
+ * derived from its Tool Core definition's call_function input projection.
63
+ *
64
+ * Search Confluence via the configured connector.
65
+ */
66
+ export type SearchScriptInput = {
67
+ /**
68
+ * A single, focused question to search for.
69
+ */
70
+ question: string
71
+ /**
72
+ * Brief keywords capturing the core entities.
73
+ */
74
+ keywords?: string
75
+ /**
76
+ * Optional time window. Use only "default", "all_time", a duration matching /^\d+[dwmy]$/ (for example, "7d", "2w", "3m", or "1y"), or a date in "YYYY-MM-DD" format. Do not use natural-language durations or combine units.
77
+ */
78
+ lookback?: string
79
+ }
80
+
81
+ /**
82
+ * Canonical Tool Core output for the "Search Confluence" effect (`confluence.search`).
83
+ * This is the definition's output schema; the effect's script surface may
84
+ * re-encode it before returning.
85
+ */
86
+ export type SearchScriptOutput = {
87
+ results: Array<{
88
+ id: string
89
+ title: string
90
+ path: string
91
+ text: string
92
+ lastEdited: string
93
+ isPrivate: boolean
94
+ pageId: string
95
+ }>
96
+ }
97
+
98
+ export class ConfluenceConnectionClient {
99
+ constructor(private readonly invoke: ConnectionActionInvoker) {}
100
+ cqlQuery(args: CqlQueryScriptInput): Promise<CqlQueryScriptOutput> {
101
+ return this.invoke<CqlQueryScriptOutput>("cqlQuery", args)
102
+ }
103
+ loadPage(args: LoadPageScriptInput): Promise<LoadPageScriptOutput> {
104
+ return this.invoke<LoadPageScriptOutput>("loadPage", args)
105
+ }
106
+ search(args: SearchScriptInput): Promise<SearchScriptOutput> {
107
+ return this.invoke<SearchScriptOutput>("search", args)
108
+ }
109
+ }
@@ -0,0 +1,2 @@
1
+ /** @internal Transport used by generated Tool Core provider clients. */
2
+ export type ConnectionActionInvoker = <Result>(action: string, args: object) => Promise<Result>;
@@ -0,0 +1,48 @@
1
+ // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
+ export const connectionTriggerDefinitions = [
3
+ {
4
+ type: "calendar.event.created",
5
+ provider: "calendar",
6
+ description: "Triggers immediately when a calendar event is created",
7
+ },
8
+ {
9
+ type: "calendar.event.updated",
10
+ provider: "calendar",
11
+ description: "Triggers immediately when a calendar event is updated",
12
+ },
13
+ {
14
+ type: "calendar.event.canceled",
15
+ provider: "calendar",
16
+ description: "Triggers immediately when a calendar event is canceled",
17
+ },
18
+ {
19
+ type: "slack.message",
20
+ provider: "slack",
21
+ description:
22
+ "Triggers immediately after a new message is posted in Slack (includes new threads and replies)",
23
+ },
24
+ {
25
+ type: "slack.reaction.added",
26
+ provider: "slack",
27
+ description:
28
+ "Triggers immediately after a reaction is added to a message in Slack",
29
+ },
30
+ {
31
+ type: "slack.app.mention",
32
+ provider: "slack",
33
+ description:
34
+ "Triggers when the agent is mentioned in a Slack channel or direct message",
35
+ },
36
+ {
37
+ type: "googleDriveOauth.filesChanged",
38
+ provider: "googleDriveOauth",
39
+ description:
40
+ "Watches for file changes in a specific Google Drive folder (direct children only) or shared drive. The My Drive root is not supported. Fires on a schedule and provides change tokens for fetching the list of changes.",
41
+ },
42
+ {
43
+ type: "discord.interaction",
44
+ provider: "discord",
45
+ description:
46
+ "Triggers when a user invokes the /ai slash command in a configured Discord channel",
47
+ },
48
+ ] as const