@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.
- package/README.md +32 -0
- package/dist/box.generated.d.ts +164 -0
- package/dist/box.generated.d.ts.map +1 -0
- package/dist/box.generated.js +24 -0
- package/dist/calendar.generated.d.ts +1036 -0
- package/dist/calendar.generated.d.ts.map +1 -0
- package/dist/calendar.generated.js +42 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +41 -10
- package/dist/cli/codegen.d.ts +2 -0
- package/dist/cli/codegen.d.ts.map +1 -1
- package/dist/cli/codegen.js +13 -2
- package/dist/cli/discover.d.ts +2 -0
- package/dist/cli/discover.d.ts.map +1 -1
- package/dist/cli/discover.js +4 -2
- package/dist/cli/emit-manifest.d.ts +3 -1
- package/dist/cli/emit-manifest.d.ts.map +1 -1
- package/dist/cli/emit-manifest.js +10 -4
- package/dist/cli/index.js +33 -1
- package/dist/confluence.generated.d.ts +98 -0
- package/dist/confluence.generated.d.ts.map +1 -0
- package/dist/confluence.generated.js +18 -0
- package/dist/connection-actions.d.ts +2 -0
- package/dist/connection-actions.d.ts.map +1 -0
- package/dist/connection-actions.js +0 -0
- package/dist/connection-trigger-definitions.generated.d.ts +34 -0
- package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
- package/dist/connection-trigger-definitions.generated.js +45 -0
- package/dist/connections.d.ts +15 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +79 -0
- package/dist/connections.test.d.ts +2 -0
- package/dist/connections.test.d.ts.map +1 -0
- package/dist/cursor.generated.d.ts +154 -0
- package/dist/cursor.generated.d.ts.map +1 -0
- package/dist/cursor.generated.js +18 -0
- package/dist/custom-block.d.ts +54 -0
- package/dist/custom-block.d.ts.map +1 -0
- package/dist/custom-block.js +30 -0
- package/dist/custom-block.test.d.ts +2 -0
- package/dist/custom-block.test.d.ts.map +1 -0
- package/dist/custom-blocks.d.ts +2 -0
- package/dist/custom-blocks.d.ts.map +1 -0
- package/dist/custom-blocks.js +1 -0
- package/dist/discord.generated.d.ts +279 -0
- package/dist/discord.generated.d.ts.map +1 -0
- package/dist/discord.generated.js +33 -0
- package/dist/gmail.generated.d.ts +184 -0
- package/dist/gmail.generated.d.ts.map +1 -0
- package/dist/gmail.generated.js +21 -0
- package/dist/googleCalendar.generated.d.ts +83 -0
- package/dist/googleCalendar.generated.d.ts.map +1 -0
- package/dist/googleCalendar.generated.js +15 -0
- package/dist/googleDrive.generated.d.ts +210 -0
- package/dist/googleDrive.generated.d.ts.map +1 -0
- package/dist/googleDrive.generated.js +27 -0
- package/dist/googleDriveOauth.generated.d.ts +851 -0
- package/dist/googleDriveOauth.generated.d.ts.map +1 -0
- package/dist/googleDriveOauth.generated.js +84 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/nds.css +1 -0
- package/dist/notion-as-code/database.d.ts +4 -3
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +11 -2
- package/dist/notion-as-code/intents.d.ts +4 -1
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/page.d.ts +4 -4
- package/dist/notion-as-code/page.d.ts.map +1 -1
- package/dist/notion-as-code/page.js +11 -2
- package/dist/outlook.generated.d.ts +224 -0
- package/dist/outlook.generated.d.ts.map +1 -0
- package/dist/outlook.generated.js +24 -0
- package/dist/providers.generated.d.ts +117 -0
- package/dist/providers.generated.d.ts.map +1 -0
- package/dist/providers.generated.js +108 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1 -0
- package/dist/salesforce.generated.d.ts +151 -0
- package/dist/salesforce.generated.d.ts.map +1 -0
- package/dist/salesforce.generated.js +24 -0
- package/dist/slack.generated.d.ts +290 -0
- package/dist/slack.generated.d.ts.map +1 -0
- package/dist/slack.generated.js +30 -0
- package/dist/triggers.generated.d.ts +107 -32
- package/dist/triggers.generated.d.ts.map +1 -1
- package/dist/triggers.generated.js +36 -24
- package/dist/workflow-connections-types.test.d.ts +2 -0
- package/dist/workflow-connections-types.test.d.ts.map +1 -0
- package/dist/workflow-types.test.d.ts +2 -0
- package/dist/workflow-types.test.d.ts.map +1 -0
- package/dist/workflow.d.ts +20 -16
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +14 -17
- package/docs/BUILD.md +83 -14
- package/docs/CONNECTIONS.md +129 -0
- package/package.json +16 -2
- package/src/box.generated.ts +187 -0
- package/src/calendar.generated.ts +1117 -0
- package/src/cli/build.test.ts +87 -0
- package/src/cli/build.ts +47 -9
- package/src/cli/codegen.test.ts +2 -0
- package/src/cli/codegen.ts +25 -7
- package/src/cli/discover.test.ts +12 -0
- package/src/cli/discover.ts +6 -2
- package/src/cli/emit-manifest.ts +13 -3
- package/src/cli/index.ts +33 -1
- package/src/confluence.generated.ts +109 -0
- package/src/connection-actions.ts +2 -0
- package/src/connection-trigger-definitions.generated.ts +48 -0
- package/src/connections.test.ts +157 -0
- package/src/connections.ts +126 -0
- package/src/cursor.generated.ts +167 -0
- package/src/custom-block.test.ts +19 -0
- package/src/custom-block.ts +41 -0
- package/src/custom-blocks.ts +1 -0
- package/src/discord.generated.ts +318 -0
- package/src/gmail.generated.ts +205 -0
- package/src/googleCalendar.generated.ts +95 -0
- package/src/googleDrive.generated.ts +235 -0
- package/src/googleDriveOauth.generated.ts +980 -0
- package/src/manifest.ts +1 -1
- package/src/nds.css +1 -0
- package/src/notion-as-code/database.ts +19 -11
- package/src/notion-as-code/intents.ts +2 -1
- package/src/notion-as-code/page.ts +17 -11
- package/src/outlook.generated.ts +249 -0
- package/src/providers.generated.ts +207 -0
- package/src/react.ts +1 -0
- package/src/salesforce.generated.ts +170 -0
- package/src/slack.generated.ts +330 -0
- package/src/triggers.generated.ts +112 -32
- package/src/workflow-connections-types.test.ts +59 -0
- package/src/workflow-types.test.ts +144 -0
- package/src/workflow.test.ts +112 -2
- package/src/workflow.ts +58 -29
package/src/cli/build.test.ts
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
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
|
+
}
|
package/src/cli/codegen.test.ts
CHANGED
|
@@ -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
|
},
|
package/src/cli/codegen.ts
CHANGED
|
@@ -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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
}
|
package/src/cli/discover.test.ts
CHANGED
|
@@ -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
|
},
|
package/src/cli/discover.ts
CHANGED
|
@@ -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
|
});
|
package/src/cli/emit-manifest.ts
CHANGED
|
@@ -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(
|
|
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
|
|
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,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
|