attio 0.0.1-experimental.20251001 → 0.0.1-experimental.20251001.1
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/lib/build/client/create-client-build-config.js +7 -1
- package/lib/build/client/generate-client-entry.js +9 -0
- package/lib/build/get-file-exports.js +1 -2
- package/lib/build/proxy-block-modules-plugin.js +68 -0
- package/lib/build/server/create-server-build-config.js +4 -0
- package/lib/build/server/find-workflow-block-server-modules.js +300 -30
- package/lib/build/server/fine-workflow-block.test.js +66 -0
- package/lib/build/server/generate-server-entry.js +40 -27
- package/lib/build/workflow-block-modules.js +1 -0
- package/lib/client/app.d.ts +7 -5
- package/lib/client/app.d.ts.map +1 -1
- package/lib/client/experimental/index.d.ts +3 -0
- package/lib/client/experimental/index.d.ts.map +1 -0
- package/lib/client/experimental/index.js +10 -0
- package/lib/client/experimental/index.js.map +1 -0
- package/lib/client/experimental/settings/get-settings.d.ts +7 -0
- package/lib/client/experimental/settings/get-settings.d.ts.map +1 -0
- package/lib/client/{workflow-block.js → experimental/settings/get-settings.js} +1 -1
- package/lib/client/experimental/settings/get-settings.js.map +1 -0
- package/lib/client/experimental/settings/index.d.ts +5 -0
- package/lib/client/experimental/settings/index.d.ts.map +1 -0
- package/lib/client/experimental/settings/index.js +8 -0
- package/lib/client/experimental/settings/index.js.map +1 -0
- package/lib/client/experimental/settings/set-user-setting.d.ts +7 -0
- package/lib/client/experimental/settings/set-user-setting.d.ts.map +1 -0
- package/lib/client/experimental/settings/set-user-setting.js +3 -0
- package/lib/client/experimental/settings/set-user-setting.js.map +1 -0
- package/lib/client/experimental/settings/set-workspace-setting.d.ts +7 -0
- package/lib/client/experimental/settings/set-workspace-setting.d.ts.map +1 -0
- package/lib/client/experimental/settings/set-workspace-setting.js +3 -0
- package/lib/client/experimental/settings/set-workspace-setting.js.map +1 -0
- package/lib/client/experimental/settings/use-settings.d.ts +7 -0
- package/lib/client/experimental/settings/use-settings.d.ts.map +1 -0
- package/lib/client/experimental/settings/use-settings.js +3 -0
- package/lib/client/experimental/settings/use-settings.js.map +1 -0
- package/lib/client/index.d.ts +3 -1
- package/lib/client/index.d.ts.map +1 -1
- package/lib/client/index.js +1 -0
- package/lib/client/index.js.map +1 -1
- package/lib/client/workflow/index.d.ts +0 -19
- package/lib/client/workflow/index.d.ts.map +1 -1
- package/lib/client/workflow/index.js.map +1 -1
- package/lib/client/{workflow-block.d.ts → workflow-step-block.d.ts} +6 -2
- package/lib/client/workflow-step-block.d.ts.map +1 -0
- package/lib/client/workflow-step-block.js +3 -0
- package/lib/client/workflow-step-block.js.map +1 -0
- package/lib/client/workflow-trigger-block.d.ts +18 -0
- package/lib/client/workflow-trigger-block.d.ts.map +1 -0
- package/lib/client/workflow-trigger-block.js +3 -0
- package/lib/client/workflow-trigger-block.js.map +1 -0
- package/lib/commands/build.js +9 -1
- package/lib/commands/dev/client-builder.js +9 -3
- package/lib/commands/dev/prepare-build-context.js +17 -27
- package/lib/commands/dev/server-builder.js +3 -2
- package/lib/commands/dev.js +10 -1
- package/lib/commands/version/create.js +9 -1
- package/lib/constants/hidden-attio-directory.js +1 -0
- package/lib/constants/settings-files.js +2 -0
- package/lib/env.js +1 -0
- package/lib/root/index.d.ts +6 -0
- package/lib/root/index.d.ts.map +1 -0
- package/lib/root/index.js +12 -0
- package/lib/root/index.js.map +1 -0
- package/lib/root/settings/app-settings-schema.d.ts +6 -0
- package/lib/root/settings/app-settings-schema.d.ts.map +1 -0
- package/lib/root/settings/app-settings-schema.js +3 -0
- package/lib/root/settings/app-settings-schema.js.map +1 -0
- package/lib/root/settings/app-settings.d.ts +26 -0
- package/lib/root/settings/app-settings.d.ts.map +1 -0
- package/lib/root/settings/app-settings.js +3 -0
- package/lib/root/settings/app-settings.js.map +1 -0
- package/lib/root/settings/index.d.ts +5 -0
- package/lib/root/settings/index.d.ts.map +1 -0
- package/lib/root/settings/index.js +8 -0
- package/lib/root/settings/index.js.map +1 -0
- package/lib/root/settings/node/boolean.d.ts +11 -0
- package/lib/root/settings/node/boolean.d.ts.map +1 -0
- package/lib/root/settings/node/boolean.js +11 -0
- package/lib/root/settings/node/boolean.js.map +1 -0
- package/lib/root/settings/node/index.d.ts +7 -0
- package/lib/root/settings/node/index.d.ts.map +1 -0
- package/lib/root/settings/node/index.js +12 -0
- package/lib/root/settings/node/index.js.map +1 -0
- package/lib/root/settings/node/number.d.ts +11 -0
- package/lib/root/settings/node/number.d.ts.map +1 -0
- package/lib/root/settings/node/number.js +11 -0
- package/lib/root/settings/node/number.js.map +1 -0
- package/lib/root/settings/node/settings.d.ts +4 -0
- package/lib/root/settings/node/settings.d.ts.map +1 -0
- package/lib/root/settings/node/settings.js +10 -0
- package/lib/root/settings/node/settings.js.map +1 -0
- package/lib/root/settings/node/string.d.ts +11 -0
- package/lib/root/settings/node/string.d.ts.map +1 -0
- package/lib/root/settings/node/string.js +11 -0
- package/lib/root/settings/node/string.js.map +1 -0
- package/lib/root/settings/settings-schema.d.ts +7 -0
- package/lib/root/settings/settings-schema.d.ts.map +1 -0
- package/lib/root/settings/settings-schema.js +3 -0
- package/lib/root/settings/settings-schema.js.map +1 -0
- package/lib/root/settings/utils/typeof-setting-node.d.ts +3 -0
- package/lib/root/settings/utils/typeof-setting-node.d.ts.map +1 -0
- package/lib/root/settings/utils/typeof-setting-node.js +3 -0
- package/lib/root/settings/utils/typeof-setting-node.js.map +1 -0
- package/lib/server/experimental/index.d.ts +1 -0
- package/lib/server/experimental/index.d.ts.map +1 -1
- package/lib/server/experimental/index.js +9 -1
- package/lib/server/experimental/index.js.map +1 -1
- package/lib/server/experimental/kv-store.d.ts +10 -0
- package/lib/server/experimental/kv-store.d.ts.map +1 -1
- package/lib/server/experimental/settings/get-settings.d.ts +7 -0
- package/lib/server/experimental/settings/get-settings.d.ts.map +1 -0
- package/lib/server/experimental/settings/get-settings.js +3 -0
- package/lib/server/experimental/settings/get-settings.js.map +1 -0
- package/lib/server/experimental/settings/get-user-setting.d.ts +7 -0
- package/lib/server/experimental/settings/get-user-setting.d.ts.map +1 -0
- package/lib/server/experimental/settings/get-user-setting.js +3 -0
- package/lib/server/experimental/settings/get-user-setting.js.map +1 -0
- package/lib/server/experimental/settings/get-user-settings.d.ts +7 -0
- package/lib/server/experimental/settings/get-user-settings.d.ts.map +1 -0
- package/lib/server/experimental/settings/get-user-settings.js +3 -0
- package/lib/server/experimental/settings/get-user-settings.js.map +1 -0
- package/lib/server/experimental/settings/get-workspace-setting.d.ts +7 -0
- package/lib/server/experimental/settings/get-workspace-setting.d.ts.map +1 -0
- package/lib/server/experimental/settings/get-workspace-setting.js +3 -0
- package/lib/server/experimental/settings/get-workspace-setting.js.map +1 -0
- package/lib/server/experimental/settings/get-workspace-settings.d.ts +7 -0
- package/lib/server/experimental/settings/get-workspace-settings.d.ts.map +1 -0
- package/lib/server/experimental/settings/get-workspace-settings.js +3 -0
- package/lib/server/experimental/settings/get-workspace-settings.js.map +1 -0
- package/lib/server/experimental/settings/index.d.ts +8 -0
- package/lib/server/experimental/settings/index.d.ts.map +1 -0
- package/lib/server/experimental/settings/index.js +18 -0
- package/lib/server/experimental/settings/index.js.map +1 -0
- package/lib/server/experimental/settings/set-user-setting.d.ts +7 -0
- package/lib/server/experimental/settings/set-user-setting.d.ts.map +1 -0
- package/lib/server/experimental/settings/set-user-setting.js +3 -0
- package/lib/server/experimental/settings/set-user-setting.js.map +1 -0
- package/lib/server/experimental/settings/set-workspace-setting.d.ts +7 -0
- package/lib/server/experimental/settings/set-workspace-setting.d.ts.map +1 -0
- package/lib/server/experimental/settings/set-workspace-setting.js +3 -0
- package/lib/server/experimental/settings/set-workspace-setting.js.map +1 -0
- package/lib/shared/schema-utils/index.d.ts +1 -1
- package/lib/shared/schema-utils/index.d.ts.map +1 -1
- package/lib/shared/schema-utils/index.js +3 -0
- package/lib/shared/schema-utils/index.js.map +1 -1
- package/lib/tsconfig.lib.tsbuildinfo +1 -1
- package/lib/util/assert-app-settings.js +13 -0
- package/lib/util/exit-with-missing-app-settings.js +5 -0
- package/lib/util/exit-with-missing-entry-point.js +2 -8
- package/lib/util/generate-gitignore.js +42 -0
- package/lib/util/generate-settings-files.js +71 -0
- package/lib/util/get-package-manager-command.js +9 -0
- package/package.json +8 -1
- package/lib/client/workflow-block.d.ts.map +0 -1
- package/lib/client/workflow-block.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import globalExternals from "@fal-works/esbuild-plugin-global-externals";
|
|
2
|
+
import { proxyBlockModulesPlugin } from "../proxy-block-modules-plugin.js";
|
|
2
3
|
import { proxyServerModulesPlugin } from "../proxy-server-modules-plugin.js";
|
|
3
|
-
export function createClientBuildConfig({ srcDir, entryPoint, }) {
|
|
4
|
+
export function createClientBuildConfig({ appDir, srcDir, entryPoint, workflowBlockModulesRef, }) {
|
|
4
5
|
return {
|
|
5
6
|
entryPoints: [entryPoint],
|
|
6
7
|
logLevel: "silent",
|
|
@@ -50,6 +51,10 @@ export function createClientBuildConfig({ srcDir, entryPoint, }) {
|
|
|
50
51
|
"useTransition",
|
|
51
52
|
],
|
|
52
53
|
},
|
|
54
|
+
attio: {
|
|
55
|
+
varName: "ATTIO_ROOT_SDK",
|
|
56
|
+
type: "cjs",
|
|
57
|
+
},
|
|
53
58
|
"attio/client": {
|
|
54
59
|
varName: "ATTIO_CLIENT_EXTENSION_SDK",
|
|
55
60
|
type: "cjs",
|
|
@@ -60,6 +65,7 @@ export function createClientBuildConfig({ srcDir, entryPoint, }) {
|
|
|
60
65
|
},
|
|
61
66
|
}),
|
|
62
67
|
proxyServerModulesPlugin({ srcDir }),
|
|
68
|
+
proxyBlockModulesPlugin({ appDir, workflowBlockModulesRef }),
|
|
63
69
|
],
|
|
64
70
|
};
|
|
65
71
|
}
|
|
@@ -2,6 +2,8 @@ import fs from "fs/promises";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Project } from "ts-morph";
|
|
4
4
|
import { complete, errored, fromPromise, isComplete } from "@attio/fetchable-npm";
|
|
5
|
+
import { APP_SETTINGS_FILENAME } from "../../constants/settings-files.js";
|
|
6
|
+
import { USE_APP_TS } from "../../env.js";
|
|
5
7
|
import { getAppEntryPoint } from "../../util/get-app-entry-point.js";
|
|
6
8
|
const ASSET_FILE_EXTENSIONS = ["png"];
|
|
7
9
|
export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }) {
|
|
@@ -31,6 +33,11 @@ export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }
|
|
|
31
33
|
path: path.join(assetsDirAbsolute, relativeAssetPath),
|
|
32
34
|
name: relativeAssetPath,
|
|
33
35
|
}));
|
|
36
|
+
const initSettingsJS = `
|
|
37
|
+
import appSettingsSchema from ${JSON.stringify(path.join(srcDirAbsolute, APP_SETTINGS_FILENAME))}
|
|
38
|
+
|
|
39
|
+
registerSettingsSchema(appSettingsSchema)
|
|
40
|
+
`;
|
|
34
41
|
const importAppJS = `
|
|
35
42
|
import {app} from ${JSON.stringify(appEntryPoint.path)}
|
|
36
43
|
`;
|
|
@@ -77,6 +84,8 @@ export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }
|
|
|
77
84
|
${importAppJS}
|
|
78
85
|
${importAssetsJS}
|
|
79
86
|
|
|
87
|
+
${USE_APP_TS ? initSettingsJS : ""}
|
|
88
|
+
|
|
80
89
|
${registerSurfacesJS}
|
|
81
90
|
|
|
82
91
|
${registerAssetsJS}
|
|
@@ -9,6 +9,5 @@ export async function getFileExports(filePath) {
|
|
|
9
9
|
const content = contentResult.value.toString();
|
|
10
10
|
const tsProject = new Project({ useInMemoryFileSystem: true });
|
|
11
11
|
const appSourceFile = tsProject.createSourceFile(filePath, content);
|
|
12
|
-
|
|
13
|
-
return complete(new Set(exportsMap.keys()));
|
|
12
|
+
return complete(appSourceFile.getExportedDeclarations());
|
|
14
13
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
function getBlockModuleProxy(modulePath, workflowBlockModulesRef) {
|
|
3
|
+
let counter = 0;
|
|
4
|
+
const exports = new Map();
|
|
5
|
+
for (const { execute, schema, activate, deactivate, } of workflowBlockModulesRef.current.values()) {
|
|
6
|
+
for (const handler of [execute, activate, deactivate]) {
|
|
7
|
+
if (!handler || handler.path !== modulePath) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (handler.export === "default") {
|
|
11
|
+
exports.set("default", `export default async function() {
|
|
12
|
+
throw new Error("Exports from *.block.ts files cannot be called from the client.")
|
|
13
|
+
}`);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
exports.set(handler.export, `export async function ${handler.export}() {
|
|
17
|
+
throw new Error("Exports from *.block.ts files cannot be called from the client.")
|
|
18
|
+
}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (schema && schema.path === modulePath) {
|
|
22
|
+
if (schema.export === "default") {
|
|
23
|
+
let name;
|
|
24
|
+
do {
|
|
25
|
+
name = `schema_${counter++}`;
|
|
26
|
+
} while (!exports.has(name));
|
|
27
|
+
exports.set("default", `const ${name} = new Proxy({}, {
|
|
28
|
+
get() {
|
|
29
|
+
throw new Error("Exports from *.block.ts files cannot be called from the client.");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default ${name};`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
exports.set(schema.export, `export const ${schema.export} = new Proxy({}, {
|
|
37
|
+
get() {
|
|
38
|
+
throw new Error("Exports from *.block.ts files cannot be called from the client.");
|
|
39
|
+
}
|
|
40
|
+
});`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...exports.values()].join("\n");
|
|
45
|
+
}
|
|
46
|
+
const PLUGIN_NAME = "proxy-block-modules-plugin";
|
|
47
|
+
export function proxyBlockModulesPlugin({ appDir, workflowBlockModulesRef, }) {
|
|
48
|
+
return {
|
|
49
|
+
name: PLUGIN_NAME,
|
|
50
|
+
setup(build) {
|
|
51
|
+
build.onResolve({ filter: /\.block$/ }, (args) => {
|
|
52
|
+
const modulePath = path.relative(appDir, path.resolve(args.resolveDir, args.path)) + ".ts";
|
|
53
|
+
return {
|
|
54
|
+
path: args.path,
|
|
55
|
+
namespace: PLUGIN_NAME,
|
|
56
|
+
pluginData: { modulePath },
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
build.onLoad({ filter: /.*/, namespace: PLUGIN_NAME }, async (args) => {
|
|
60
|
+
const pluginData = args.pluginData;
|
|
61
|
+
return {
|
|
62
|
+
contents: getBlockModuleProxy(pluginData.modulePath, workflowBlockModulesRef),
|
|
63
|
+
loader: "js",
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -1,37 +1,307 @@
|
|
|
1
|
-
import fs from "fs
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { parse } from "@typescript-eslint/parser";
|
|
3
|
+
import { analyze } from "@typescript-eslint/scope-manager";
|
|
4
|
+
import { walk } from "zimmerframe";
|
|
5
|
+
import { complete, errored } from "@attio/fetchable-npm";
|
|
6
|
+
import { getAppEntryPoint } from "../../util/get-app-entry-point.js";
|
|
7
|
+
function checkPath(path, doesModuleExist) {
|
|
8
|
+
if (doesModuleExist(path)) {
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function resolvePathWithExtension(path, doesModuleExist) {
|
|
14
|
+
return (checkPath(path + ".ts", doesModuleExist) ||
|
|
15
|
+
checkPath(path + ".tsx", doesModuleExist) ||
|
|
16
|
+
checkPath(path + ".js", doesModuleExist));
|
|
17
|
+
}
|
|
18
|
+
function loadModule(sourcePath, currPath, helpers) {
|
|
19
|
+
const absolutePath = resolveAbsolutePath(sourcePath, currPath);
|
|
20
|
+
const fullPath = resolvePathWithExtension(absolutePath, helpers.doesModuleExist);
|
|
21
|
+
if (!fullPath) {
|
|
22
|
+
throw new Error(`Unable to resolve path for module ${sourcePath} from ${currPath}`);
|
|
23
|
+
}
|
|
24
|
+
const modules = helpers.modules;
|
|
25
|
+
if (modules.has(fullPath)) {
|
|
26
|
+
return modules.get(fullPath);
|
|
27
|
+
}
|
|
28
|
+
const content = helpers.getModuleSource(fullPath);
|
|
29
|
+
const ast = parse(content, {
|
|
30
|
+
range: true,
|
|
31
|
+
jsx: true,
|
|
32
|
+
});
|
|
33
|
+
const scopeManager = analyze(ast, {
|
|
34
|
+
sourceType: "module",
|
|
35
|
+
});
|
|
36
|
+
const moduleScope = scopeManager.acquire(ast, true);
|
|
37
|
+
const namedExports = new Map();
|
|
38
|
+
const module = {
|
|
39
|
+
content,
|
|
40
|
+
ast,
|
|
41
|
+
scope: moduleScope,
|
|
42
|
+
namedExports,
|
|
43
|
+
path: fullPath,
|
|
44
|
+
};
|
|
45
|
+
modules.set(fullPath, module);
|
|
46
|
+
walk(ast, null, {
|
|
47
|
+
ExportNamedDeclaration(node) {
|
|
48
|
+
if (node.declaration?.type === "VariableDeclaration" &&
|
|
49
|
+
node.declaration.declarations.length === 1 &&
|
|
50
|
+
node.declaration.declarations[0].type === "VariableDeclarator" &&
|
|
51
|
+
node.declaration.declarations[0].id.type === "Identifier") {
|
|
52
|
+
const declaration = node.declaration.declarations[0];
|
|
53
|
+
const id = declaration.id;
|
|
54
|
+
const init = declaration.init;
|
|
55
|
+
if (init?.type === "Identifier") {
|
|
56
|
+
const resolved = resolveIdentifierToValue(init, moduleScope);
|
|
57
|
+
if (resolved?.type !== "ImportBinding") {
|
|
58
|
+
namedExports.set(id.name, declaration.init);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
namedExports.set(id.name, declaration.init);
|
|
63
|
+
}
|
|
64
|
+
if (node?.specifiers) {
|
|
65
|
+
for (const specifier of node.specifiers) {
|
|
66
|
+
if (specifier.type === "ExportSpecifier" &&
|
|
67
|
+
specifier.exported.type === "Identifier" &&
|
|
68
|
+
specifier.local.type === "Identifier") {
|
|
69
|
+
if (node.source !== null) {
|
|
70
|
+
namedExports.set(specifier.exported.name, node);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const resolved = resolveIdentifierToValue(specifier.local, moduleScope);
|
|
74
|
+
namedExports.set(specifier.exported.name, resolved);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return module;
|
|
82
|
+
}
|
|
83
|
+
function resolveIdentifierToValue(node, scope) {
|
|
84
|
+
const variable = scope.set.get(node.name);
|
|
85
|
+
if (!variable) {
|
|
86
|
+
throw new Error(`Unable to find variable ${node.name} in scope`);
|
|
87
|
+
}
|
|
88
|
+
if (variable.defs.length !== 1) {
|
|
89
|
+
throw new Error(`Expected exactly one definition for variable ${node.name}, found ${variable.defs.length}`);
|
|
90
|
+
}
|
|
91
|
+
const def = variable.defs[0];
|
|
92
|
+
if (def.node.type === "VariableDeclarator") {
|
|
93
|
+
return def.node.init;
|
|
94
|
+
}
|
|
95
|
+
if (def.type == "ImportBinding") {
|
|
96
|
+
return def;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function resolveAbsolutePath(sourcePath, currPath) {
|
|
101
|
+
if (sourcePath.startsWith(".")) {
|
|
102
|
+
const currDir = currPath.split("/").slice(0, -1).join("/");
|
|
103
|
+
const combinedPath = currDir + "/" + sourcePath;
|
|
104
|
+
const parts = combinedPath.split("/");
|
|
105
|
+
const resolvedParts = [];
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
if (part === "..") {
|
|
108
|
+
resolvedParts.pop();
|
|
109
|
+
}
|
|
110
|
+
else if (part !== ".") {
|
|
111
|
+
resolvedParts.push(part);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return resolvedParts.join("/");
|
|
115
|
+
}
|
|
116
|
+
return sourcePath;
|
|
117
|
+
}
|
|
118
|
+
function captureImportPath(name, node, scope, currPath, helpers) {
|
|
119
|
+
const importValue = node.type === "Identifier" ? resolveIdentifierToValue(node, scope) : node;
|
|
120
|
+
if (!importValue ||
|
|
121
|
+
(importValue.type !== "ImportBinding" && importValue.type !== "ExportNamedDeclaration")) {
|
|
122
|
+
throw new Error(`Expected parent of import binding to be an import binding`);
|
|
123
|
+
}
|
|
124
|
+
const importDeclaration = importValue.type === "ExportNamedDeclaration"
|
|
125
|
+
? importValue
|
|
126
|
+
: importValue.parent;
|
|
127
|
+
const sourcePath = importDeclaration.source.value;
|
|
128
|
+
let absolutePath = resolveAbsolutePath(sourcePath, currPath);
|
|
129
|
+
if (sourcePath.endsWith(".server")) {
|
|
130
|
+
throw new Error(`Unexpected .server import, did you mean to import "${name}" from a .block file?`);
|
|
131
|
+
}
|
|
132
|
+
if (!sourcePath.endsWith(".block") &&
|
|
133
|
+
!sourcePath.endsWith(".block.ts") &&
|
|
134
|
+
!sourcePath.endsWith(".block.tsx") &&
|
|
135
|
+
!sourcePath.endsWith(".block.js")) {
|
|
136
|
+
const module = loadModule(sourcePath, absolutePath, helpers);
|
|
137
|
+
if (!module.namedExports.has(name)) {
|
|
138
|
+
throw new Error(`Unable to find named export ${name} in module ${sourcePath}`);
|
|
139
|
+
}
|
|
140
|
+
const value = module.namedExports.get(name);
|
|
141
|
+
const scope = module.scope;
|
|
142
|
+
const path = module.path;
|
|
143
|
+
return captureImportPath(name, value, scope, path, helpers);
|
|
144
|
+
}
|
|
145
|
+
let targetExport = name;
|
|
146
|
+
if (importValue.type === "ImportBinding" && importValue.name.name !== name) {
|
|
147
|
+
targetExport = importValue.name.name;
|
|
148
|
+
}
|
|
149
|
+
absolutePath = resolvePathWithExtension(absolutePath, helpers.doesModuleExist) || absolutePath;
|
|
150
|
+
return { path: absolutePath, export: targetExport };
|
|
151
|
+
}
|
|
152
|
+
function visitBlock(node, scope, result, currPath, helpers) {
|
|
153
|
+
if (node.type === "Identifier") {
|
|
154
|
+
let targetScope = scope;
|
|
155
|
+
let value = resolveIdentifierToValue(node, scope);
|
|
156
|
+
let targetPath = currPath;
|
|
157
|
+
if (value?.type === "ImportBinding") {
|
|
158
|
+
const importDeclaration = value.parent;
|
|
159
|
+
const module = loadModule(importDeclaration.source.value, currPath, helpers);
|
|
160
|
+
const local = value.name.name;
|
|
161
|
+
const specififier = importDeclaration.specifiers.find((s) => s.local.name === local);
|
|
162
|
+
const name = (specififier
|
|
163
|
+
?.imported).name;
|
|
164
|
+
if (!module.namedExports.has(name)) {
|
|
165
|
+
throw new Error(`Unable to find named export ${name} in module ${importDeclaration.source.value}`);
|
|
166
|
+
}
|
|
167
|
+
value = module.namedExports.get(name);
|
|
168
|
+
targetScope = module.scope;
|
|
169
|
+
targetPath = module.path;
|
|
170
|
+
}
|
|
171
|
+
if (!value || value.type !== "ObjectExpression") {
|
|
172
|
+
throw new Error(`Expected variable ${node.name} to have an initializer`);
|
|
173
|
+
}
|
|
174
|
+
visitBlock(value, targetScope, result, targetPath, helpers);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (node.type === "ObjectExpression") {
|
|
178
|
+
let id;
|
|
179
|
+
let execute;
|
|
180
|
+
let schema;
|
|
181
|
+
let activate;
|
|
182
|
+
let deactivate;
|
|
183
|
+
for (const property of node.properties) {
|
|
184
|
+
if (property.type !== "Property" || property.key.type !== "Identifier") {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const name = property.key.name;
|
|
188
|
+
if (name === "id") {
|
|
189
|
+
if (property.value.type !== "Literal" || typeof property.value.value !== "string") {
|
|
190
|
+
throw new Error("Expected block id to be a string literal");
|
|
191
|
+
}
|
|
192
|
+
id = property.value.value;
|
|
193
|
+
}
|
|
194
|
+
else if (name === "execute") {
|
|
195
|
+
execute = captureImportPath(name, property.value, scope, currPath, helpers);
|
|
196
|
+
}
|
|
197
|
+
else if (name === "schema") {
|
|
198
|
+
schema = captureImportPath(name, property.value, scope, currPath, helpers);
|
|
199
|
+
}
|
|
200
|
+
else if (name === "activate") {
|
|
201
|
+
activate = captureImportPath(name, property.value, scope, currPath, helpers);
|
|
202
|
+
}
|
|
203
|
+
else if (name === "deactivate") {
|
|
204
|
+
deactivate = captureImportPath(name, property.value, scope, currPath, helpers);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (!id || !schema) {
|
|
208
|
+
throw new Error("Expected block to have id and schema properties");
|
|
209
|
+
}
|
|
210
|
+
result.set(id, {
|
|
211
|
+
execute,
|
|
212
|
+
schema,
|
|
213
|
+
activate,
|
|
214
|
+
deactivate,
|
|
18
215
|
});
|
|
19
216
|
}
|
|
20
|
-
|
|
21
|
-
|
|
217
|
+
}
|
|
218
|
+
function findProperty(obj, name, scope) {
|
|
219
|
+
const property = obj.properties.find((property) => property.type === "Property" &&
|
|
220
|
+
property.key.type === "Identifier" &&
|
|
221
|
+
property.key.name === name);
|
|
222
|
+
if (!property) {
|
|
223
|
+
throw new Error(`Expected property ${name} to be present`);
|
|
224
|
+
}
|
|
225
|
+
let value = property.value;
|
|
226
|
+
if (value.type === "Identifier") {
|
|
227
|
+
const resolvedValue = resolveIdentifierToValue(value, scope);
|
|
228
|
+
if (resolvedValue && resolvedValue?.type !== "ImportBinding") {
|
|
229
|
+
value = resolvedValue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
export function findWorkflowBlockModulesFromSource(source, currPath, helpers) {
|
|
235
|
+
const result = new Map();
|
|
236
|
+
try {
|
|
237
|
+
const ast = parse(source, {
|
|
238
|
+
range: true,
|
|
239
|
+
jsx: true,
|
|
240
|
+
});
|
|
241
|
+
const scopeManager = analyze(ast, {
|
|
242
|
+
sourceType: "module",
|
|
243
|
+
});
|
|
244
|
+
const moduleScope = scopeManager.acquire(ast, true);
|
|
245
|
+
if (moduleScope === null) {
|
|
246
|
+
throw new Error("Expected to be able to acquire module scope");
|
|
247
|
+
}
|
|
248
|
+
walk(ast, null, {
|
|
249
|
+
ExportNamedDeclaration(node, context) {
|
|
250
|
+
if (!node.declaration || node.declaration.type !== "VariableDeclaration") {
|
|
251
|
+
throw new Error("Expected export to be a variable declaration");
|
|
252
|
+
}
|
|
253
|
+
if (node.declaration.declarations.length !== 1) {
|
|
254
|
+
throw new Error("Expected exactly one declaration per export");
|
|
255
|
+
}
|
|
256
|
+
const declaration = node.declaration.declarations[0];
|
|
257
|
+
if (declaration.id.type !== "Identifier" || declaration.id.name !== "app") {
|
|
258
|
+
throw new Error("Expected export to be named 'app'");
|
|
259
|
+
}
|
|
260
|
+
const appValue = declaration.init;
|
|
261
|
+
if (!appValue || appValue.type !== "ObjectExpression") {
|
|
262
|
+
throw new Error("Expected app to be initialized to an object");
|
|
263
|
+
}
|
|
264
|
+
const workflow = findProperty(appValue, "workflow", moduleScope);
|
|
265
|
+
if (workflow.type !== "ObjectExpression") {
|
|
266
|
+
throw new Error("Expected app to have a workflow property as an object");
|
|
267
|
+
}
|
|
268
|
+
const blocks = findProperty(workflow, "blocks", moduleScope);
|
|
269
|
+
if (blocks.type !== "ObjectExpression") {
|
|
270
|
+
throw new Error("Expected workflow to have a blocks property as an object");
|
|
271
|
+
}
|
|
272
|
+
const steps = findProperty(blocks, "steps", moduleScope);
|
|
273
|
+
if (steps.type !== "ArrayExpression") {
|
|
274
|
+
throw new Error("Expected blocks to have a steps property as an array");
|
|
275
|
+
}
|
|
276
|
+
for (const element of steps.elements) {
|
|
277
|
+
if (element) {
|
|
278
|
+
visitBlock(element, moduleScope, result, currPath, helpers);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
});
|
|
22
283
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return complete([{ file, path: filePath, blockId }]);
|
|
284
|
+
catch (error) {
|
|
285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
+
return errored({ code: "WORKFLOW_BLOCK_RESOLUTION_FAILED", message });
|
|
27
287
|
}
|
|
28
|
-
return
|
|
288
|
+
return complete(result);
|
|
29
289
|
}
|
|
30
|
-
export async function findWorkflowBlockModules(
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
return complete(
|
|
290
|
+
export async function findWorkflowBlockModules(srcDirAbsolute) {
|
|
291
|
+
const appEntryPoint = await getAppEntryPoint(srcDirAbsolute);
|
|
292
|
+
if (!appEntryPoint) {
|
|
293
|
+
return complete(new Map());
|
|
34
294
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
295
|
+
const getModuleSource = (path) => {
|
|
296
|
+
return fs.readFileSync(path, "utf-8");
|
|
297
|
+
};
|
|
298
|
+
const doesModuleExist = (path) => {
|
|
299
|
+
return fs.existsSync(path);
|
|
300
|
+
};
|
|
301
|
+
const modules = new Map();
|
|
302
|
+
return findWorkflowBlockModulesFromSource(appEntryPoint.content, appEntryPoint.path, {
|
|
303
|
+
getModuleSource,
|
|
304
|
+
doesModuleExist,
|
|
305
|
+
modules,
|
|
306
|
+
});
|
|
37
307
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { findWorkflowBlockModulesFromSource } from "./find-workflow-block-server-modules.js";
|
|
3
|
+
describe("findWorkflowBlockModulesFromSource", () => {
|
|
4
|
+
it("should find workflow block modules in source code", () => {
|
|
5
|
+
const appTsSource = `
|
|
6
|
+
import { block } from "./test-block"
|
|
7
|
+
export const app = {
|
|
8
|
+
workflow: {
|
|
9
|
+
blocks: {
|
|
10
|
+
steps: [block],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}`;
|
|
14
|
+
const blockTsSource = `
|
|
15
|
+
import {execute, schema} from "./test-block.block"
|
|
16
|
+
export const block = {
|
|
17
|
+
id: "test-block",
|
|
18
|
+
title: "test",
|
|
19
|
+
schema,
|
|
20
|
+
execute,
|
|
21
|
+
Configurator: () => {},
|
|
22
|
+
}`;
|
|
23
|
+
const blockBlockTsSource = `
|
|
24
|
+
import {experimental_Workflow} from "attio/server"
|
|
25
|
+
export const schema = async () => ({
|
|
26
|
+
test: experimental_Workflow.number(),
|
|
27
|
+
})
|
|
28
|
+
export const execute = async (config) => {}`;
|
|
29
|
+
const getModuleSource = (path) => {
|
|
30
|
+
if (path === "/test-block.ts") {
|
|
31
|
+
return blockTsSource;
|
|
32
|
+
}
|
|
33
|
+
if (path === "/test-block.block.ts") {
|
|
34
|
+
return blockBlockTsSource;
|
|
35
|
+
}
|
|
36
|
+
return "";
|
|
37
|
+
};
|
|
38
|
+
const doesModuleExist = (path) => {
|
|
39
|
+
if (path === "/test-block.ts" || path === "/test-block.block.ts") {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
const modules = new Map();
|
|
45
|
+
const result = findWorkflowBlockModulesFromSource(appTsSource, "", {
|
|
46
|
+
modules,
|
|
47
|
+
getModuleSource,
|
|
48
|
+
doesModuleExist,
|
|
49
|
+
});
|
|
50
|
+
expect(result.state).toBe("complete");
|
|
51
|
+
if (result.state === "complete") {
|
|
52
|
+
const value = result.value;
|
|
53
|
+
const entries = value.entries();
|
|
54
|
+
expect(value.size).toBe(1);
|
|
55
|
+
expect(entries.next().value).toEqual([
|
|
56
|
+
"test-block",
|
|
57
|
+
{
|
|
58
|
+
activate: undefined,
|
|
59
|
+
deactivate: undefined,
|
|
60
|
+
execute: { export: "execute", path: "/test-block.block.ts" },
|
|
61
|
+
schema: { export: "schema", path: "/test-block.block.ts" },
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|