@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.
- package/README.md +28 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +34 -4
- 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/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/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/react.d.ts +2 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1 -0
- package/package.json +16 -2
- package/src/cli/build.test.ts +54 -0
- package/src/cli/build.ts +41 -4
- 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/custom-block.test.ts +19 -0
- package/src/custom-block.ts +41 -0
- package/src/custom-blocks.ts +1 -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/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.
|
package/dist/cli/build.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
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
|
};
|
package/dist/cli/codegen.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/cli/codegen.js
CHANGED
|
@@ -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(
|
|
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
|
};
|
package/dist/cli/discover.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cli/discover.js
CHANGED
|
@@ -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,
|
|
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(
|
|
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
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"custom-block.test.d.ts","sourceRoot":"","sources":["../src/custom-block.test.ts"],"names":[],"mappings":""}
|
|
@@ -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";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -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;
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
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,
|
|
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
|
-
|
|
6
|
-
|
|
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);
|
package/dist/react.d.ts
ADDED
|
@@ -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.
|
|
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
|
},
|
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",
|
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
|
}
|
|
@@ -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
|
+
}
|
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,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
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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";
|