attio 0.0.1-experimental.20250825 → 0.0.1-experimental.20250829.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/generate-client-entry.js +50 -53
- package/lib/build/client/legacy-generate-client-entry.js +79 -0
- package/lib/commands/build.js +9 -0
- package/lib/commands/dev/prepare-build-contexts.js +21 -5
- package/lib/commands/dev.js +19 -5
- package/lib/commands/version/create.js +9 -0
- package/lib/env.js +1 -0
- package/lib/util/ensure-app-entry-point.js +30 -0
- package/lib/util/exit-with-missing-entry-point.js +11 -0
- package/lib/util/find-surface-exports/parse-file-exports.js +12 -12
- package/lib/util/generate-app-entry-point.js +75 -0
- package/lib/util/get-app-entry-point.js +18 -0
- package/lib/util/to-camel-case.js +13 -0
- package/package.json +1 -1
- package/schema.graphql +74 -0
|
@@ -1,79 +1,76 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Project } from "ts-morph";
|
|
4
|
+
import { complete, errored, fromPromise, isComplete } from "@attio/fetchable-npm";
|
|
5
|
+
import { getAppEntryPoint } from "../../util/get-app-entry-point.js";
|
|
5
6
|
const ASSET_FILE_EXTENSIONS = ["png"];
|
|
6
7
|
export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
surfaceExports = await findSurfaceExports(srcDirAbsolute);
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
8
|
+
const appEntryPoint = await getAppEntryPoint(srcDirAbsolute);
|
|
9
|
+
if (!appEntryPoint) {
|
|
12
10
|
return errored({
|
|
13
|
-
code: "
|
|
14
|
-
error,
|
|
11
|
+
code: "APP_ENTRY_POINT_NOT_FOUND",
|
|
15
12
|
});
|
|
16
13
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
const tsProject = new Project({
|
|
15
|
+
useInMemoryFileSystem: true,
|
|
16
|
+
});
|
|
17
|
+
const appSourceFile = tsProject.createSourceFile(appEntryPoint.path, appEntryPoint.content);
|
|
18
|
+
const exported = appSourceFile.getExportedDeclarations();
|
|
19
|
+
const hasAppExport = exported.has("app");
|
|
20
|
+
if (!hasAppExport) {
|
|
21
|
+
return errored({
|
|
22
|
+
code: "APP_EXPORT_NOT_FOUND",
|
|
23
|
+
path: appEntryPoint.path,
|
|
24
|
+
});
|
|
23
25
|
}
|
|
26
|
+
const assetFilesResult = await fromPromise(fs.readdir(assetsDirAbsolute, { recursive: true }));
|
|
27
|
+
const assetFiles = isComplete(assetFilesResult) ? assetFilesResult.value : [];
|
|
24
28
|
const assets = assetFiles
|
|
25
29
|
.filter((relativeAssetPath) => ASSET_FILE_EXTENSIONS.some((extension) => relativeAssetPath.endsWith("." + extension)))
|
|
26
30
|
.map((relativeAssetPath) => ({
|
|
27
31
|
path: path.join(assetsDirAbsolute, relativeAssetPath),
|
|
28
32
|
name: relativeAssetPath,
|
|
29
33
|
}));
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"record-widget": [${surfaceImportNamesBySurfaceType.recordWidget.join(", ")}],
|
|
53
|
-
"call-recording-insight-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingInsightTextSelectionAction.join(", ")}],
|
|
54
|
-
"call-recording-summary-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingSummaryTextSelectionAction.join(", ")}],
|
|
55
|
-
"call-recording-transcript-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingTranscriptTextSelectionAction.join(", ")}],
|
|
56
|
-
"workflow-block": [${surfaceImportNamesBySurfaceType.workflowBlock.join(", ")}]
|
|
57
|
-
});`;
|
|
34
|
+
const importAppJS = `
|
|
35
|
+
import {app} from ${JSON.stringify(appEntryPoint.path)}
|
|
36
|
+
`;
|
|
37
|
+
const registerSurfacesJS = `
|
|
38
|
+
const recordActions = app?.record?.actions
|
|
39
|
+
const bulkRecordActions = app?.record?.bulkActions
|
|
40
|
+
const recordWidgets = app?.record?.widgets
|
|
41
|
+
const callRecordingInsights = app?.callRecording?.insight?.textActions
|
|
42
|
+
const callRecordingSummaries = app?.callRecording?.summary?.textActions
|
|
43
|
+
const callRecordingTranscripts = app?.callRecording?.transcript?.textActions
|
|
44
|
+
const workflowBlocks = app?.workflow?.blocks
|
|
45
|
+
|
|
46
|
+
registerSurfaces({
|
|
47
|
+
"record-action": Array.isArray(recordActions) ? recordActions : [],
|
|
48
|
+
"bulk-record-action": Array.isArray(bulkRecordActions) ? bulkRecordActions : [],
|
|
49
|
+
"record-widget": Array.isArray(recordWidgets) ? recordWidgets : [],
|
|
50
|
+
"call-recording-insight-text-selection-action": Array.isArray(callRecordingInsights) ? callRecordingInsights : [],
|
|
51
|
+
"call-recording-summary-text-selection-action": Array.isArray(callRecordingSummaries) ? callRecordingSummaries : [],
|
|
52
|
+
"call-recording-transcript-text-selection-action": Array.isArray(callRecordingTranscripts) ? callRecordingTranscripts : [],
|
|
53
|
+
"workflow-block": Array.isArray(workflowBlocks) ? workflowBlocks : [],
|
|
54
|
+
})
|
|
55
|
+
`;
|
|
58
56
|
const importAssetsJS = assets
|
|
59
57
|
.map((asset, index) => `import A${index} from ${JSON.stringify(asset.path)};`)
|
|
60
58
|
.join("\n");
|
|
61
59
|
const registerAssetsJS = `
|
|
62
|
-
const assets = [];
|
|
60
|
+
const assets = [];
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
${assets
|
|
65
63
|
.map((asset, index) => `assets.push({name: ${JSON.stringify(asset.name)}, data: A${index}});`)
|
|
66
64
|
.join("\n")}
|
|
67
65
|
|
|
68
|
-
registerAssets(assets);
|
|
69
|
-
`;
|
|
66
|
+
registerAssets(assets);
|
|
67
|
+
`;
|
|
70
68
|
return complete(`
|
|
71
|
-
${
|
|
72
|
-
|
|
73
|
-
${importAssetsJS}
|
|
69
|
+
${importAppJS}
|
|
70
|
+
${importAssetsJS}
|
|
74
71
|
|
|
75
|
-
${registerSurfacesJS}
|
|
72
|
+
${registerSurfacesJS}
|
|
76
73
|
|
|
77
|
-
${registerAssetsJS}
|
|
78
|
-
`);
|
|
74
|
+
${registerAssetsJS}
|
|
75
|
+
`);
|
|
79
76
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { complete, errored } from "@attio/fetchable-npm";
|
|
4
|
+
import { findSurfaceExports } from "../../util/find-surface-exports/find-surface-exports.js";
|
|
5
|
+
const ASSET_FILE_EXTENSIONS = ["png"];
|
|
6
|
+
export async function legacyGenerateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }) {
|
|
7
|
+
let surfaceExports;
|
|
8
|
+
try {
|
|
9
|
+
surfaceExports = await findSurfaceExports(srcDirAbsolute);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return errored({
|
|
13
|
+
code: "ERROR_FINDING_SURFACE_EXPORTS",
|
|
14
|
+
error,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
let assetFiles;
|
|
18
|
+
try {
|
|
19
|
+
assetFiles = await fs.readdir(assetsDirAbsolute, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
assetFiles = [];
|
|
23
|
+
}
|
|
24
|
+
const assets = assetFiles
|
|
25
|
+
.filter((relativeAssetPath) => ASSET_FILE_EXTENSIONS.some((extension) => relativeAssetPath.endsWith("." + extension)))
|
|
26
|
+
.map((relativeAssetPath) => ({
|
|
27
|
+
path: path.join(assetsDirAbsolute, relativeAssetPath),
|
|
28
|
+
name: relativeAssetPath,
|
|
29
|
+
}));
|
|
30
|
+
const importSurfacesJS = surfaceExports
|
|
31
|
+
.map(([filePath, surfaces], index) => `import {${surfaces
|
|
32
|
+
.map((surface) => `${surface.surfaceType} as ${surface.surfaceType}${index}`)
|
|
33
|
+
.join(", ")}} from ${JSON.stringify(filePath)};`)
|
|
34
|
+
.join("\n");
|
|
35
|
+
const surfaceImportNamesBySurfaceType = {
|
|
36
|
+
recordAction: [],
|
|
37
|
+
bulkRecordAction: [],
|
|
38
|
+
recordWidget: [],
|
|
39
|
+
callRecordingInsightTextSelectionAction: [],
|
|
40
|
+
callRecordingSummaryTextSelectionAction: [],
|
|
41
|
+
callRecordingTranscriptTextSelectionAction: [],
|
|
42
|
+
workflowBlock: [],
|
|
43
|
+
};
|
|
44
|
+
surfaceExports.forEach(([, surfaces], index) => {
|
|
45
|
+
surfaces.forEach((surface) => {
|
|
46
|
+
surfaceImportNamesBySurfaceType[surface.surfaceType].push(`${surface.surfaceType}${index}`);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const registerSurfacesJS = `registerSurfaces({
|
|
50
|
+
"record-action": [${surfaceImportNamesBySurfaceType.recordAction.join(", ")}],
|
|
51
|
+
"bulk-record-action": [${surfaceImportNamesBySurfaceType.bulkRecordAction.join(", ")}],
|
|
52
|
+
"record-widget": [${surfaceImportNamesBySurfaceType.recordWidget.join(", ")}],
|
|
53
|
+
"call-recording-insight-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingInsightTextSelectionAction.join(", ")}],
|
|
54
|
+
"call-recording-summary-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingSummaryTextSelectionAction.join(", ")}],
|
|
55
|
+
"call-recording-transcript-text-selection-action": [${surfaceImportNamesBySurfaceType.callRecordingTranscriptTextSelectionAction.join(", ")}],
|
|
56
|
+
"workflow-block": [${surfaceImportNamesBySurfaceType.workflowBlock.join(", ")}]
|
|
57
|
+
});`;
|
|
58
|
+
const importAssetsJS = assets
|
|
59
|
+
.map((asset, index) => `import A${index} from ${JSON.stringify(asset.path)};`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
const registerAssetsJS = `
|
|
62
|
+
const assets = [];
|
|
63
|
+
|
|
64
|
+
${assets
|
|
65
|
+
.map((asset, index) => `assets.push({name: ${JSON.stringify(asset.name)}, data: A${index}});`)
|
|
66
|
+
.join("\n")}
|
|
67
|
+
|
|
68
|
+
registerAssets(assets);
|
|
69
|
+
`;
|
|
70
|
+
return complete(`
|
|
71
|
+
${importSurfacesJS}
|
|
72
|
+
|
|
73
|
+
${importAssetsJS}
|
|
74
|
+
|
|
75
|
+
${registerSurfacesJS}
|
|
76
|
+
|
|
77
|
+
${registerAssetsJS}
|
|
78
|
+
`);
|
|
79
|
+
}
|
package/lib/commands/build.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { isErrored } from "@attio/fetchable-npm";
|
|
3
|
+
import { USE_APP_TS } from "../env.js";
|
|
4
|
+
import { ensureAppEntryPoint } from "../util/ensure-app-entry-point.js";
|
|
5
|
+
import { exitWithMissingEntryPoint } from "../util/exit-with-missing-entry-point.js";
|
|
3
6
|
import { hardExit } from "../util/hard-exit.js";
|
|
4
7
|
import { spinnerify } from "../util/spinner.js";
|
|
5
8
|
import { printJsError, printTsError } from "../util/typescript.js";
|
|
@@ -10,6 +13,12 @@ import { printBuildContextError } from "./dev/prepare-build-contexts.js";
|
|
|
10
13
|
export const build = new Command("build")
|
|
11
14
|
.description("Build your Attio extension locally")
|
|
12
15
|
.action(async () => {
|
|
16
|
+
if (USE_APP_TS) {
|
|
17
|
+
const appEntryPointResult = await ensureAppEntryPoint();
|
|
18
|
+
if (isErrored(appEntryPointResult)) {
|
|
19
|
+
exitWithMissingEntryPoint();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
const generateGraphqlOperationsResult = await spinnerify("Generating GraphQL types...", "GraphQL types generated successfully", graphqlCodeGen);
|
|
14
23
|
if (isErrored(generateGraphqlOperationsResult)) {
|
|
15
24
|
hardExit(generateGraphqlOperationsResult.error.error.toString());
|
|
@@ -6,9 +6,11 @@ import tmp from "tmp-promise";
|
|
|
6
6
|
import { combineAsync, complete, errored, isErrored } from "@attio/fetchable-npm";
|
|
7
7
|
import { createClientBuildConfig } from "../../build/client/create-client-build-config.js";
|
|
8
8
|
import { generateClientEntry } from "../../build/client/generate-client-entry.js";
|
|
9
|
+
import { legacyGenerateClientEntry } from "../../build/client/legacy-generate-client-entry.js";
|
|
9
10
|
import { createServerBuildConfig } from "../../build/server/create-server-build-config.js";
|
|
10
11
|
import { generateServerEntry } from "../../build/server/generate-server-entry.js";
|
|
11
12
|
import { errorsAndWarningsSchema } from "../../build.js";
|
|
13
|
+
import { USE_APP_TS } from "../../env.js";
|
|
12
14
|
export async function prepareBuildContexts(mode) {
|
|
13
15
|
const srcDir = "src";
|
|
14
16
|
const assetsDir = path.join(srcDir, "assets");
|
|
@@ -20,10 +22,15 @@ export async function prepareBuildContexts(mode) {
|
|
|
20
22
|
.then(async (tempFile) => {
|
|
21
23
|
let lastJS;
|
|
22
24
|
const updateTempFile = async () => {
|
|
23
|
-
const jsResult =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const jsResult = USE_APP_TS
|
|
26
|
+
? await generateClientEntry({
|
|
27
|
+
srcDirAbsolute: path.resolve(srcDir),
|
|
28
|
+
assetsDirAbsolute: path.resolve(assetsDir),
|
|
29
|
+
})
|
|
30
|
+
: await legacyGenerateClientEntry({
|
|
31
|
+
srcDirAbsolute: path.resolve(srcDir),
|
|
32
|
+
assetsDirAbsolute: path.resolve(assetsDir),
|
|
33
|
+
});
|
|
27
34
|
if (isErrored(jsResult)) {
|
|
28
35
|
return jsResult;
|
|
29
36
|
}
|
|
@@ -65,7 +72,10 @@ export async function prepareBuildContexts(mode) {
|
|
|
65
72
|
}
|
|
66
73
|
return complete({
|
|
67
74
|
rebuild: async () => {
|
|
68
|
-
await updateTempFile();
|
|
75
|
+
const updateResult = await updateTempFile();
|
|
76
|
+
if (isErrored(updateResult)) {
|
|
77
|
+
return updateResult;
|
|
78
|
+
}
|
|
69
79
|
try {
|
|
70
80
|
return complete(await esbuildContext.rebuild());
|
|
71
81
|
}
|
|
@@ -197,6 +207,12 @@ export function printBuildContextError(error) {
|
|
|
197
207
|
case "ERROR_FINDING_SURFACE_EXPORTS":
|
|
198
208
|
process.stderr.write(`${chalk.red("✖ ")}Failed to find surface exports: ${error.error}\n`);
|
|
199
209
|
break;
|
|
210
|
+
case "APP_ENTRY_POINT_NOT_FOUND":
|
|
211
|
+
process.stderr.write(`${chalk.red("✖ ")}Could not find app.ts\n`);
|
|
212
|
+
break;
|
|
213
|
+
case "APP_EXPORT_NOT_FOUND":
|
|
214
|
+
process.stderr.write(`${chalk.red("✖ ")}Could not find a named export "app" in ${path.basename(error.path)}.\n`);
|
|
215
|
+
break;
|
|
200
216
|
case "UNPARSABLE_BUILD_ERROR":
|
|
201
217
|
process.stderr.write(`${chalk.red("✖ ")}Failed to parse build error: ${error.error}\n`);
|
|
202
218
|
break;
|
package/lib/commands/dev.js
CHANGED
|
@@ -4,7 +4,10 @@ import notifier from "node-notifier";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { isErrored } from "@attio/fetchable-npm";
|
|
6
6
|
import { authenticator } from "../auth/auth.js";
|
|
7
|
+
import { USE_APP_TS } from "../env.js";
|
|
7
8
|
import { printUploadError } from "../print-errors.js";
|
|
9
|
+
import { ensureAppEntryPoint } from "../util/ensure-app-entry-point.js";
|
|
10
|
+
import { hardExit } from "../util/hard-exit.js";
|
|
8
11
|
import { printMessage } from "../util/print-message.js";
|
|
9
12
|
import { printJsError, printTsError } from "../util/typescript.js";
|
|
10
13
|
import { boot } from "./dev/boot.js";
|
|
@@ -46,6 +49,17 @@ export const dev = new Command("dev")
|
|
|
46
49
|
const { workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
|
|
47
50
|
const cleanupFunctions = [];
|
|
48
51
|
let isCleaningUp = false;
|
|
52
|
+
if (USE_APP_TS) {
|
|
53
|
+
const appEntryPointResult = await ensureAppEntryPoint(true);
|
|
54
|
+
if (isErrored(appEntryPointResult)) {
|
|
55
|
+
switch (appEntryPointResult.error.code) {
|
|
56
|
+
case "APP_ENTRY_POINT_NOT_FOUND":
|
|
57
|
+
hardExit("Could not find app.ts");
|
|
58
|
+
case "FAILED_TO_GENERATE_ENTRY_POINT":
|
|
59
|
+
hardExit("Failed to generate app.ts");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
await authenticator.ensureAuthed();
|
|
50
64
|
const cleanup = async () => {
|
|
51
65
|
if (isCleaningUp)
|
|
@@ -98,18 +112,18 @@ export const dev = new Command("dev")
|
|
|
98
112
|
process.stderr.write(error);
|
|
99
113
|
});
|
|
100
114
|
cleanupFunctions.push(cleanupGraphqlCodeGen);
|
|
101
|
-
let
|
|
115
|
+
let haveBundlingErrors = false;
|
|
102
116
|
const cleanupJs = bundleJavaScript(async (contents) => {
|
|
103
|
-
if (
|
|
104
|
-
process.stdout.write(`${chalk.green("✓")}
|
|
105
|
-
|
|
117
|
+
if (haveBundlingErrors) {
|
|
118
|
+
process.stdout.write(`${chalk.green("✓")} Bundling errors fixed\n`);
|
|
119
|
+
haveBundlingErrors = false;
|
|
106
120
|
}
|
|
107
121
|
const uploadResult = await upload({ contents, devVersionId, appId });
|
|
108
122
|
if (isErrored(uploadResult)) {
|
|
109
123
|
printUploadError(uploadResult.error);
|
|
110
124
|
}
|
|
111
125
|
}, async (error) => {
|
|
112
|
-
|
|
126
|
+
haveBundlingErrors = true;
|
|
113
127
|
if (error.code === "BUILD_JAVASCRIPT_ERROR") {
|
|
114
128
|
notifyJsErrors(error);
|
|
115
129
|
const { errors, warnings } = error;
|
|
@@ -3,10 +3,13 @@ import { Command } from "commander";
|
|
|
3
3
|
import { combineAsync, isErrored } from "@attio/fetchable-npm";
|
|
4
4
|
import { api } from "../../api/api.js";
|
|
5
5
|
import { authenticator } from "../../auth/auth.js";
|
|
6
|
+
import { USE_APP_TS } from "../../env.js";
|
|
6
7
|
import { printCliVersionError, printFetcherError, printPackageJsonError } from "../../print-errors.js";
|
|
7
8
|
import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
|
|
8
9
|
import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
|
|
9
10
|
import { getVersions } from "../../spinners/get-versions.spinner.js";
|
|
11
|
+
import { ensureAppEntryPoint } from "../../util/ensure-app-entry-point.js";
|
|
12
|
+
import { exitWithMissingEntryPoint } from "../../util/exit-with-missing-entry-point.js";
|
|
10
13
|
import { loadAttioCliVersion } from "../../util/load-attio-cli-version.js";
|
|
11
14
|
import { spinnerify } from "../../util/spinner.js";
|
|
12
15
|
import { printJsError } from "../../util/typescript.js";
|
|
@@ -16,6 +19,12 @@ import { bundleJavaScript } from "./create/bundle-javascript.js";
|
|
|
16
19
|
export const versionCreate = new Command("create")
|
|
17
20
|
.description("Create a new unpublished version of your Attio app")
|
|
18
21
|
.action(async () => {
|
|
22
|
+
if (USE_APP_TS) {
|
|
23
|
+
const appEntryPointResult = await ensureAppEntryPoint();
|
|
24
|
+
if (isErrored(appEntryPointResult)) {
|
|
25
|
+
exitWithMissingEntryPoint();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
await authenticator.ensureAuthed();
|
|
20
29
|
const appSlugResult = await getAppSlugFromPackageJson();
|
|
21
30
|
if (isErrored(appSlugResult)) {
|
package/lib/env.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import readline from "readline/promises";
|
|
3
|
+
import { complete, errored, isErrored } from "@attio/fetchable-npm";
|
|
4
|
+
import { generateAppEntryPoint } from "./generate-app-entry-point.js";
|
|
5
|
+
import { getAppEntryPoint } from "./get-app-entry-point.js";
|
|
6
|
+
export async function ensureAppEntryPoint(promptToGenerate = false) {
|
|
7
|
+
const srcDirAbsolute = path.resolve("src");
|
|
8
|
+
const appEntryPoint = await getAppEntryPoint(srcDirAbsolute);
|
|
9
|
+
if (appEntryPoint !== null) {
|
|
10
|
+
return complete(true);
|
|
11
|
+
}
|
|
12
|
+
if (!promptToGenerate) {
|
|
13
|
+
return errored({
|
|
14
|
+
code: "APP_ENTRY_POINT_NOT_FOUND",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
let readlineInterface = readline.createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stdout,
|
|
20
|
+
});
|
|
21
|
+
await readlineInterface.question("Could not find app.ts entry point. Press Enter to generate it...");
|
|
22
|
+
readlineInterface.close();
|
|
23
|
+
const generateResult = await generateAppEntryPoint(srcDirAbsolute);
|
|
24
|
+
if (isErrored(generateResult)) {
|
|
25
|
+
return errored({
|
|
26
|
+
code: "FAILED_TO_GENERATE_ENTRY_POINT",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return complete(true);
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hardExit } from "./hard-exit.js";
|
|
2
|
+
export function exitWithMissingEntryPoint() {
|
|
3
|
+
const packageManager = process.env.npm_config_user_agent?.split("/")?.[0];
|
|
4
|
+
const command = {
|
|
5
|
+
npm: "npm run dev",
|
|
6
|
+
yarn: "yarn dev",
|
|
7
|
+
pnpm: "pnpm dev",
|
|
8
|
+
bun: "bun run dev",
|
|
9
|
+
}[packageManager ?? "npm"] ?? "npm run dev";
|
|
10
|
+
hardExit(`Could not find app.ts. Run \`${command}\` to generate it`);
|
|
11
|
+
}
|
|
@@ -14,8 +14,8 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
14
14
|
return aliasedSymbol ?? declarationSymbol;
|
|
15
15
|
}
|
|
16
16
|
const declarationsByName = sourceFile.getExportedDeclarations();
|
|
17
|
-
for (const
|
|
18
|
-
const declarations = declarationsByName.get(
|
|
17
|
+
for (const surfaceType of SURFACE_TYPES) {
|
|
18
|
+
const declarations = declarationsByName.get(surfaceType);
|
|
19
19
|
if (!declarations) {
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
@@ -27,12 +27,12 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
27
27
|
}
|
|
28
28
|
const exportType = declaration.getType();
|
|
29
29
|
if (isTypeScript(sourceFile) &&
|
|
30
|
-
!typeChecker.isTypeAssignableTo(exportType, surfaceTypes[
|
|
30
|
+
!typeChecker.isTypeAssignableTo(exportType, surfaceTypes[surfaceType])) {
|
|
31
31
|
const node = declaration.getFirstChild() || declaration;
|
|
32
32
|
throw {
|
|
33
33
|
errors: [
|
|
34
34
|
{
|
|
35
|
-
text: `${
|
|
35
|
+
text: `${surfaceType} in ${filePath} is not assignable to ${getSurfaceTypeName(surfaceType)}`,
|
|
36
36
|
location: {
|
|
37
37
|
file: filePath,
|
|
38
38
|
line: node.getStartLineNumber(),
|
|
@@ -40,8 +40,8 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
40
40
|
lineText: declaration.getText().split("\n")[0],
|
|
41
41
|
additionalLines: declaration.getText().split("\n").slice(1),
|
|
42
42
|
length: node.getWidth(),
|
|
43
|
-
namespace:
|
|
44
|
-
suggestion: `Ensure the export matches the type ${getSurfaceTypeName(
|
|
43
|
+
namespace: surfaceType,
|
|
44
|
+
suggestion: `Ensure the export matches the type ${getSurfaceTypeName(surfaceType)}`,
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
],
|
|
@@ -49,24 +49,24 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
49
49
|
}
|
|
50
50
|
function getPropertyValueOrThrow(propertyName) {
|
|
51
51
|
if (declaration.getKind() !== SyntaxKind.VariableDeclaration) {
|
|
52
|
-
throw new Error(`${
|
|
52
|
+
throw new Error(`${surfaceType} is not an object in ${filePath} ${declaration.getKind()}`);
|
|
53
53
|
}
|
|
54
54
|
const initializer = declaration
|
|
55
55
|
.asKindOrThrow(SyntaxKind.VariableDeclaration)
|
|
56
56
|
.getInitializer();
|
|
57
57
|
if (initializer?.getKind() !== SyntaxKind.ObjectLiteralExpression) {
|
|
58
|
-
throw new Error(`${
|
|
58
|
+
throw new Error(`${surfaceType} must be defined as an object literal expression in ${filePath}`);
|
|
59
59
|
}
|
|
60
60
|
const objectLiteral = initializer.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
61
61
|
const property = objectLiteral.getProperty(propertyName);
|
|
62
62
|
if (property?.getKind() !== SyntaxKind.PropertyAssignment) {
|
|
63
|
-
throw new Error(`Property ${propertyName} on ${
|
|
63
|
+
throw new Error(`Property ${propertyName} on ${surfaceType} must be directly declared in the object declaration in ${filePath}`);
|
|
64
64
|
}
|
|
65
65
|
const propertyInitializer = property
|
|
66
66
|
.asKindOrThrow(SyntaxKind.PropertyAssignment)
|
|
67
67
|
.getInitializer();
|
|
68
68
|
if (propertyInitializer?.getKind() !== SyntaxKind.StringLiteral) {
|
|
69
|
-
throw new Error(`Property ${propertyName} on ${
|
|
69
|
+
throw new Error(`Property ${propertyName} on ${surfaceType} must be a string literal in ${filePath}`);
|
|
70
70
|
}
|
|
71
71
|
return propertyInitializer.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
|
|
72
72
|
}
|
|
@@ -83,7 +83,7 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
83
83
|
length: declaration.getWidth(),
|
|
84
84
|
lineText: declaration.getText().split("\n")[0],
|
|
85
85
|
additionalLines: declaration.getText().split("\n").slice(1),
|
|
86
|
-
namespace:
|
|
86
|
+
namespace: surfaceType,
|
|
87
87
|
suggestion: `Ensure the id is unique`,
|
|
88
88
|
},
|
|
89
89
|
},
|
|
@@ -92,7 +92,7 @@ export function parseFileExports({ sourceFile, existingExportSymbols, existingId
|
|
|
92
92
|
}
|
|
93
93
|
existingExportSymbols.add(originalSymbol);
|
|
94
94
|
existingIds.add(surfaceId);
|
|
95
|
-
surfaceExports.add(
|
|
95
|
+
surfaceExports.add({ surfaceType, id: surfaceId });
|
|
96
96
|
}
|
|
97
97
|
return surfaceExports;
|
|
98
98
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { complete, fromPromise, isErrored } from "@attio/fetchable-npm";
|
|
4
|
+
import { findSurfaceExports } from "./find-surface-exports/find-surface-exports.js";
|
|
5
|
+
import { SURFACE_TYPES } from "./surfaces.js";
|
|
6
|
+
import { toCamelCase } from "./to-camel-case.js";
|
|
7
|
+
export async function generateAppEntryPoint(srcDirAbsolute) {
|
|
8
|
+
const surfaceExports = await findSurfaceExports(srcDirAbsolute);
|
|
9
|
+
const surfaceImportFilesByType = SURFACE_TYPES.reduce((surfaceExports, surfaceType) => {
|
|
10
|
+
surfaceExports[surfaceType] = [];
|
|
11
|
+
return surfaceExports;
|
|
12
|
+
}, {});
|
|
13
|
+
for (const [filePath, surfaces] of surfaceExports) {
|
|
14
|
+
for (const surface of surfaces) {
|
|
15
|
+
surfaceImportFilesByType[surface.surfaceType].push({
|
|
16
|
+
importPath: getRelativeImportPath(srcDirAbsolute, filePath),
|
|
17
|
+
id: surface.id,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const appImportStatement = `import type {App} from "attio/client"`;
|
|
22
|
+
const surfaceImportsStatements = SURFACE_TYPES.flatMap((surfaceType) => {
|
|
23
|
+
const surfaceImports = surfaceImportFilesByType[surfaceType];
|
|
24
|
+
return surfaceImports.map((surface) => {
|
|
25
|
+
const surfaceName = getSurfaceImportName(surface.id);
|
|
26
|
+
return `import {${surfaceType}${surfaceName !== surfaceType ? ` as ${surfaceName}` : ""}} from ${JSON.stringify(surface.importPath)}`;
|
|
27
|
+
});
|
|
28
|
+
}).join("\n");
|
|
29
|
+
const getSurfaceNamesArray = (surfaceType) => {
|
|
30
|
+
const surfaceImports = surfaceImportFilesByType[surfaceType];
|
|
31
|
+
return surfaceImports.map((surface) => getSurfaceImportName(surface.id));
|
|
32
|
+
};
|
|
33
|
+
const appExportStatement = `export const app: App = {
|
|
34
|
+
record: {
|
|
35
|
+
actions: [${getSurfaceNamesArray("recordAction").join(",")}],
|
|
36
|
+
bulkActions: [${getSurfaceNamesArray("bulkRecordAction").join(",")}],
|
|
37
|
+
widgets: [${getSurfaceNamesArray("recordWidget").join(",")}],
|
|
38
|
+
},
|
|
39
|
+
callRecording: {
|
|
40
|
+
insight: {
|
|
41
|
+
textActions: [${getSurfaceNamesArray("callRecordingInsightTextSelectionAction")}]
|
|
42
|
+
},
|
|
43
|
+
summary: {
|
|
44
|
+
textActions: [${getSurfaceNamesArray("callRecordingSummaryTextSelectionAction")}]
|
|
45
|
+
},
|
|
46
|
+
transcript: {
|
|
47
|
+
textActions: [${getSurfaceNamesArray("callRecordingTranscriptTextSelectionAction")}]
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}`;
|
|
51
|
+
const appEntryPointContent = [
|
|
52
|
+
appImportStatement,
|
|
53
|
+
surfaceImportsStatements,
|
|
54
|
+
appExportStatement,
|
|
55
|
+
].join("\n\n");
|
|
56
|
+
const writeResult = await fromPromise(fs.writeFile(path.join(srcDirAbsolute, "app.ts"), appEntryPointContent));
|
|
57
|
+
if (isErrored(writeResult)) {
|
|
58
|
+
return writeResult;
|
|
59
|
+
}
|
|
60
|
+
return complete(true);
|
|
61
|
+
}
|
|
62
|
+
function getRelativeImportPath(dir, filePath) {
|
|
63
|
+
const relativePath = path
|
|
64
|
+
.relative(dir, filePath)
|
|
65
|
+
.split(path.sep)
|
|
66
|
+
.join("/")
|
|
67
|
+
.replace(/\.[^/.]+$/, "");
|
|
68
|
+
if (relativePath.startsWith(".")) {
|
|
69
|
+
return relativePath;
|
|
70
|
+
}
|
|
71
|
+
return `./${relativePath}`;
|
|
72
|
+
}
|
|
73
|
+
function getSurfaceImportName(surfaceId) {
|
|
74
|
+
return toCamelCase(surfaceId);
|
|
75
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fromPromise, isErrored } from "@attio/fetchable-npm";
|
|
4
|
+
const APP_ENTRY_POINT_EXTENSIONS = ["ts", "tsx"];
|
|
5
|
+
export async function getAppEntryPoint(srcDirAbsolute) {
|
|
6
|
+
const appEntryPoints = await Promise.all(APP_ENTRY_POINT_EXTENSIONS.map(async (extension) => {
|
|
7
|
+
const filePath = path.join(srcDirAbsolute, `app.${extension}`);
|
|
8
|
+
const contentResult = await fromPromise(fs.readFile(filePath));
|
|
9
|
+
if (isErrored(contentResult)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
path: filePath,
|
|
14
|
+
content: contentResult.value.toString(),
|
|
15
|
+
};
|
|
16
|
+
}));
|
|
17
|
+
return appEntryPoints.find((entryPoint) => entryPoint !== null) ?? null;
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function toCamelCase(input) {
|
|
2
|
+
return input
|
|
3
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
4
|
+
.trim()
|
|
5
|
+
.split(" ")
|
|
6
|
+
.map((word, index) => {
|
|
7
|
+
if (index === 0) {
|
|
8
|
+
return word.toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
11
|
+
})
|
|
12
|
+
.join("");
|
|
13
|
+
}
|
package/package.json
CHANGED
package/schema.graphql
CHANGED
|
@@ -11,6 +11,12 @@ interface IObject {
|
|
|
11
11
|
The slug of the object.
|
|
12
12
|
"""
|
|
13
13
|
slug: String!
|
|
14
|
+
attributes(
|
|
15
|
+
"""
|
|
16
|
+
Filter attributes by types.
|
|
17
|
+
"""
|
|
18
|
+
types: [AttributeType!]
|
|
19
|
+
): [Attribute!]!
|
|
14
20
|
record(
|
|
15
21
|
"""
|
|
16
22
|
ID of the record.
|
|
@@ -25,6 +31,38 @@ interface IObject {
|
|
|
25
31
|
): [Record]!
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
"""
|
|
35
|
+
An attribute of an object.
|
|
36
|
+
"""
|
|
37
|
+
type Attribute {
|
|
38
|
+
title: String!
|
|
39
|
+
slug: String!
|
|
40
|
+
type: AttributeType!
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
The type of an attribute.
|
|
45
|
+
"""
|
|
46
|
+
enum AttributeType {
|
|
47
|
+
TEXT
|
|
48
|
+
NUMBER
|
|
49
|
+
CHECKBOX
|
|
50
|
+
CURRENCY
|
|
51
|
+
DATE
|
|
52
|
+
TIMESTAMP
|
|
53
|
+
RATING
|
|
54
|
+
PIPELINE
|
|
55
|
+
SELECT
|
|
56
|
+
ENTITY_REFERENCE
|
|
57
|
+
ACTOR_REFERENCE
|
|
58
|
+
LOCATION
|
|
59
|
+
DOMAIN
|
|
60
|
+
EMAIL_ADDRESS
|
|
61
|
+
PHONE_NUMBER
|
|
62
|
+
INTERACTION
|
|
63
|
+
PERSONAL_NAME
|
|
64
|
+
}
|
|
65
|
+
|
|
28
66
|
"""
|
|
29
67
|
A record in the workspace.
|
|
30
68
|
"""
|
|
@@ -256,6 +294,12 @@ type Object implements IObject {
|
|
|
256
294
|
The slug of the object.
|
|
257
295
|
"""
|
|
258
296
|
slug: String!
|
|
297
|
+
attributes(
|
|
298
|
+
"""
|
|
299
|
+
Filter attributes by types.
|
|
300
|
+
"""
|
|
301
|
+
types: [AttributeType!]
|
|
302
|
+
): [Attribute!]!
|
|
259
303
|
record(
|
|
260
304
|
"""
|
|
261
305
|
ID of the record.
|
|
@@ -422,6 +466,12 @@ type people implements IObject {
|
|
|
422
466
|
The slug of the object.
|
|
423
467
|
"""
|
|
424
468
|
slug: String!
|
|
469
|
+
attributes(
|
|
470
|
+
"""
|
|
471
|
+
Filter attributes by types.
|
|
472
|
+
"""
|
|
473
|
+
types: [AttributeType!]
|
|
474
|
+
): [Attribute!]!
|
|
425
475
|
record(
|
|
426
476
|
"""
|
|
427
477
|
ID of the person record.
|
|
@@ -657,6 +707,12 @@ type companies implements IObject {
|
|
|
657
707
|
The slug of the object.
|
|
658
708
|
"""
|
|
659
709
|
slug: String!
|
|
710
|
+
attributes(
|
|
711
|
+
"""
|
|
712
|
+
Filter attributes by types.
|
|
713
|
+
"""
|
|
714
|
+
types: [AttributeType!]
|
|
715
|
+
): [Attribute!]!
|
|
660
716
|
record(
|
|
661
717
|
"""
|
|
662
718
|
ID of the company record.
|
|
@@ -684,6 +740,12 @@ type deals implements IObject {
|
|
|
684
740
|
The slug of the object.
|
|
685
741
|
"""
|
|
686
742
|
slug: String!
|
|
743
|
+
attributes(
|
|
744
|
+
"""
|
|
745
|
+
Filter attributes by types.
|
|
746
|
+
"""
|
|
747
|
+
types: [AttributeType!]
|
|
748
|
+
): [Attribute!]!
|
|
687
749
|
record(
|
|
688
750
|
"""
|
|
689
751
|
ID of the deal record.
|
|
@@ -711,6 +773,12 @@ type workspaces implements IObject {
|
|
|
711
773
|
The slug of the object.
|
|
712
774
|
"""
|
|
713
775
|
slug: String!
|
|
776
|
+
attributes(
|
|
777
|
+
"""
|
|
778
|
+
Filter attributes by types.
|
|
779
|
+
"""
|
|
780
|
+
types: [AttributeType!]
|
|
781
|
+
): [Attribute!]!
|
|
714
782
|
record(
|
|
715
783
|
"""
|
|
716
784
|
ID of the workspace record.
|
|
@@ -738,6 +806,12 @@ type users implements IObject {
|
|
|
738
806
|
The slug of the object.
|
|
739
807
|
"""
|
|
740
808
|
slug: String!
|
|
809
|
+
attributes(
|
|
810
|
+
"""
|
|
811
|
+
Filter attributes by types.
|
|
812
|
+
"""
|
|
813
|
+
types: [AttributeType!]
|
|
814
|
+
): [Attribute!]!
|
|
741
815
|
record(
|
|
742
816
|
"""
|
|
743
817
|
ID of the user record.
|