attio 0.0.1-experimental.20250829 → 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
|
@@ -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
|
+
}
|