attio 0.0.1-experimental.20250402.1 → 0.0.1-experimental.20250407

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.
Files changed (66) hide show
  1. package/lib/api/api.js +98 -0
  2. package/lib/api/fetcher.js +65 -0
  3. package/lib/api/schemas.js +76 -0
  4. package/lib/auth/auth.js +121 -0
  5. package/lib/auth/ensure-authed.js +14 -0
  6. package/lib/auth/keychain.js +52 -0
  7. package/lib/build/client/generate-client-entry.js +14 -4
  8. package/lib/build/server/generate-server-entry.js +24 -15
  9. package/lib/commands/build/build-javascript.js +13 -19
  10. package/lib/commands/build/validate-typescript.js +7 -14
  11. package/lib/commands/build.js +27 -25
  12. package/lib/commands/dev/boot.js +38 -15
  13. package/lib/commands/dev/bundle-javascript.js +65 -27
  14. package/lib/commands/dev/graphql-code-gen.js +3 -2
  15. package/lib/commands/dev/onboarding.js +20 -6
  16. package/lib/commands/dev/prepare-build-contexts.js +163 -32
  17. package/lib/{api → commands/dev}/start-graphql-server.js +2 -2
  18. package/lib/commands/dev/upload.js +40 -44
  19. package/lib/commands/dev/validate-typescript.js +3 -17
  20. package/lib/commands/dev.js +95 -13
  21. package/lib/commands/init/create-project.js +53 -35
  22. package/lib/commands/init.js +30 -9
  23. package/lib/commands/login.js +7 -11
  24. package/lib/commands/logout.js +5 -11
  25. package/lib/commands/version/create/bundle-javascript.js +28 -0
  26. package/lib/commands/version/create.js +89 -64
  27. package/lib/commands/version/list.js +54 -26
  28. package/lib/commands/whoami.js +15 -11
  29. package/lib/spinners/determine-workspace.spinner.js +58 -0
  30. package/lib/spinners/get-app-info.spinner.js +7 -0
  31. package/lib/spinners/get-app-slug-from-package-json.js +46 -0
  32. package/lib/spinners/get-versions.spinner.js +5 -0
  33. package/lib/util/copy-with-replace.js +40 -17
  34. package/lib/util/create-directory.js +21 -13
  35. package/lib/util/find-available-port.js +2 -1
  36. package/lib/util/hard-exit.js +6 -0
  37. package/lib/util/load-attio-cli-version.js +86 -0
  38. package/lib/util/spinner.js +16 -1
  39. package/lib/util/upload-bundle.js +17 -0
  40. package/package.json +2 -1
  41. package/lib/api/auth.js +0 -117
  42. package/lib/api/complete-bundle-upload.js +0 -21
  43. package/lib/api/complete-prod-bundle-upload.js +0 -21
  44. package/lib/api/create-dev-version.js +0 -22
  45. package/lib/api/create-version.js +0 -24
  46. package/lib/api/determine-workspace.spinner.js +0 -39
  47. package/lib/api/ensure-authed.js +0 -31
  48. package/lib/api/fetch-app-info.js +0 -26
  49. package/lib/api/fetch-connections.js +0 -20
  50. package/lib/api/fetch-installation.js +0 -20
  51. package/lib/api/fetch-versions.js +0 -25
  52. package/lib/api/fetch-workspaces.js +0 -33
  53. package/lib/api/get-app-info.spinner.js +0 -18
  54. package/lib/api/get-app-slug-from-package-json.js +0 -26
  55. package/lib/api/get-versions.spinner.js +0 -21
  56. package/lib/api/handle-error.js +0 -18
  57. package/lib/api/keychain.js +0 -30
  58. package/lib/api/make-headers.js +0 -7
  59. package/lib/api/remove-connection-definition.js +0 -10
  60. package/lib/api/start-upload.js +0 -18
  61. package/lib/api/whoami.js +0 -21
  62. package/lib/commands/dev/build-javascript.js +0 -49
  63. package/lib/commands/init/boot.js +0 -14
  64. package/lib/commands/version/create/boot.js +0 -9
  65. package/lib/util/clear-terminal.js +0 -4
  66. package/lib/util/load-attio-cli-package-json.js +0 -26
@@ -1,26 +1,49 @@
1
- import { ensureAuthed } from "../../api/ensure-authed.js";
1
+ import { getAppSlugFromPackageJson, printPackageJsonError, } from "../../spinners/get-app-slug-from-package-json.js";
2
+ import { determineWorkspace, printDetermineWorkspaceError, } from "../../spinners/determine-workspace.spinner.js";
3
+ import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
2
4
  import { loadEnv } from "../../util/load-env.js";
3
- import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
4
- import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
5
- import { createDevVersion } from "../../api/create-dev-version.js";
6
- import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
7
- import { getAppInfo } from "../../api/get-app-info.spinner.js";
5
+ import { printFetcherError } from "../../api/fetcher.js";
6
+ import { isErrored } from "@attio/fetchable";
7
+ import { loadAttioCliVersion, printCliVersionError } from "../../util/load-attio-cli-version.js";
8
+ import { API } from "../../api/api.js";
8
9
  export async function boot({ workspaceSlug }) {
9
- const token = await ensureAuthed();
10
- const appSlug = await getAppSlugFromPackageJson();
11
- const appInfo = await getAppInfo({ token, appSlug });
12
- const workspace = await determineWorkspace({ token, workspaceSlug });
10
+ const appSlugResult = await getAppSlugFromPackageJson();
11
+ if (isErrored(appSlugResult)) {
12
+ printPackageJsonError(appSlugResult.error);
13
+ process.exit(1);
14
+ }
15
+ const appSlug = appSlugResult.value;
16
+ const appInfoResult = await getAppInfo(appSlug);
17
+ if (isErrored(appInfoResult)) {
18
+ printFetcherError("Error loading app info", appInfoResult.error.fetcherError);
19
+ process.exit(1);
20
+ }
21
+ const appInfo = appInfoResult.value;
22
+ const workspaceResult = await determineWorkspace(workspaceSlug);
23
+ if (isErrored(workspaceResult)) {
24
+ printDetermineWorkspaceError(workspaceResult.error);
25
+ process.exit(1);
26
+ }
27
+ const workspace = workspaceResult.value;
13
28
  const environmentVariables = await loadEnv();
14
- const packageJson = loadAttioCliPackageJson();
15
- const devVersion = await createDevVersion({
16
- token,
29
+ const cliVersionResult = loadAttioCliVersion();
30
+ if (isErrored(cliVersionResult)) {
31
+ printCliVersionError(cliVersionResult);
32
+ process.exit(1);
33
+ }
34
+ const cliVersion = cliVersionResult.value;
35
+ const devVersionResult = await API.createDevVersion({
17
36
  appId: appInfo.app_id,
37
+ cliVersion,
18
38
  targetWorkspaceId: workspace.workspace_id,
19
39
  environmentVariables,
20
- cliVersion: packageJson.version,
21
40
  });
41
+ if (isErrored(devVersionResult)) {
42
+ printFetcherError("Error creating dev version", devVersionResult.error.fetcherError);
43
+ process.exit(1);
44
+ }
45
+ const devVersion = devVersionResult.value;
22
46
  return {
23
- token,
24
47
  appId: appInfo.app_id,
25
48
  appSlug,
26
49
  devVersionId: devVersion.app_dev_version_id,
@@ -1,49 +1,87 @@
1
1
  import chokidar from "chokidar";
2
- import notifier from "node-notifier";
3
- import { clearTerminal } from "../../util/clear-terminal.js";
4
- import { errorsAndWarningsSchema } from "../../build.js";
5
- import { prepareBuildContexts } from "./prepare-build-contexts.js";
6
- import { printJsError } from "../../util/typescript.js";
7
- const notify = (errors) => {
8
- const totalErrors = (errors.errors?.length || 0) + (errors.warnings?.length || 0);
9
- notifier.notify({
10
- title: `JavaScript ${totalErrors === 1 ? "Error" : "Errors"}`,
11
- message: `There ${totalErrors === 1 ? "was one error" : `were ${totalErrors} errors`} in your JavaScript code`,
12
- });
13
- };
14
- export function bundleJavaScript(onSuccess) {
2
+ import { prepareBuildContexts, } from "./prepare-build-contexts.js";
3
+ import { combineAsync, complete, isErrored } from "@attio/fetchable";
4
+ export function bundleJavaScript(onSuccess, onError) {
15
5
  const watcher = chokidar.watch(["./src/**/*.{js,jsx,ts,tsx}"], {
16
6
  ignored: ["**/node_modules/**", "**/dist/**", "**/*.graphql.d.ts", "**/*.gql.d.ts"],
17
7
  cwd: process.cwd(),
18
8
  });
19
9
  let buildContexts;
10
+ let isBuilding = false;
11
+ let buildQueued = false;
12
+ let isDisposing = false;
20
13
  async function handleBuild() {
14
+ if (isBuilding || isDisposing) {
15
+ buildQueued = true;
16
+ return complete(undefined);
17
+ }
18
+ isBuilding = true;
21
19
  try {
22
20
  if (!buildContexts) {
23
- buildContexts = await prepareBuildContexts("in-memory");
21
+ const buildContextsResult = await prepareBuildContexts("in-memory");
22
+ if (isErrored(buildContextsResult)) {
23
+ return buildContextsResult;
24
+ }
25
+ buildContexts = buildContextsResult.value;
24
26
  }
25
- const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
27
+ const bundleResults = await combineAsync(buildContexts.map(async (context) => context.rebuild()));
28
+ if (isErrored(bundleResults)) {
29
+ return bundleResults;
30
+ }
31
+ const results = bundleResults.value;
26
32
  const bundles = results.map((result) => result.outputFiles[0].text);
27
- onSuccess?.(bundles);
33
+ await onSuccess?.(bundles);
34
+ isBuilding = false;
35
+ if (buildQueued && !isDisposing) {
36
+ buildQueued = false;
37
+ return await handleBuild();
38
+ }
39
+ return complete(undefined);
28
40
  }
29
- catch (error) {
30
- const errors = errorsAndWarningsSchema.parse(error);
31
- clearTerminal();
32
- errors.errors?.forEach((error) => printJsError(error, "error"));
33
- errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
34
- notify(errors);
41
+ finally {
42
+ isBuilding = false;
35
43
  }
36
44
  }
37
- watcher.on("ready", () => {
38
- handleBuild();
39
- watcher.on("all", (event) => {
45
+ watcher.on("ready", async () => {
46
+ const result = await handleBuild();
47
+ if (isErrored(result)) {
48
+ onError?.(result.error);
49
+ }
50
+ watcher.on("all", async (event) => {
40
51
  if (event === "add" || event === "change" || event === "unlink") {
41
- handleBuild();
52
+ const result = await handleBuild();
53
+ if (isErrored(result)) {
54
+ onError?.(result.error);
55
+ }
42
56
  }
43
57
  });
44
58
  });
45
59
  return async () => {
46
- await Promise.all(buildContexts?.map(async (context) => context.dispose()) ?? []);
60
+ isDisposing = true;
61
+ if (isBuilding) {
62
+ await new Promise((resolve) => {
63
+ const checkBuild = setInterval(() => {
64
+ if (!isBuilding) {
65
+ clearInterval(checkBuild);
66
+ resolve(undefined);
67
+ }
68
+ }, 10);
69
+ });
70
+ }
47
71
  await watcher.close();
72
+ if (buildContexts) {
73
+ for (const context of buildContexts) {
74
+ try {
75
+ await Promise.race([
76
+ context.dispose(),
77
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Context disposal timeout")), 1_000)),
78
+ ]);
79
+ }
80
+ catch (e) {
81
+ process.stderr.write(`Error disposing build context: ${e}\n`);
82
+ }
83
+ }
84
+ buildContexts = undefined;
85
+ }
48
86
  };
49
87
  }
@@ -4,6 +4,7 @@ import { parse } from "graphql";
4
4
  import { findNodeModulesPath } from "../../util/find-node-modules-path.js";
5
5
  import { generateOperations } from "../../graphql/generate-operations.js";
6
6
  import { GraphQLError } from "../../graphql/graphql-error.js";
7
+ import { hardExit } from "../../util/hard-exit.js";
7
8
  export function graphqlCodeGen(onSuccess) {
8
9
  const watcher = chokidar.watch(["**/*.graphql", "**/*.gql"], {
9
10
  ignored: ["**/node_modules/**", "**/dist/**"],
@@ -13,7 +14,7 @@ export function graphqlCodeGen(onSuccess) {
13
14
  try {
14
15
  const schemaPath = await findNodeModulesPath(["schema.graphql"]);
15
16
  if (!schemaPath) {
16
- throw new Error("No schema.graphql found in node_modules");
17
+ hardExit("No schema.graphql found in node_modules");
17
18
  }
18
19
  const schema = parse(readFileSync(schemaPath, "utf8"));
19
20
  await generateOperations(".", schema);
@@ -24,7 +25,7 @@ export function graphqlCodeGen(onSuccess) {
24
25
  process.stderr.write(error.toString());
25
26
  }
26
27
  else {
27
- throw error;
28
+ hardExit(error instanceof Error ? error.message : "GraphQL code generation failed");
28
29
  }
29
30
  }
30
31
  }
@@ -1,22 +1,36 @@
1
1
  import { listenForKey } from "../../util/listen-for-key.js";
2
2
  import open from "open";
3
- import { fetchInstallation } from "../../api/fetch-installation.js";
4
3
  import { APP } from "../../env.js";
4
+ import { API } from "../../api/api.js";
5
+ import { isErrored } from "@attio/fetchable";
5
6
  function prompt() {
6
- process.stdout.write(`\n\n🚨 IMPORTANT: You will need to install your app in your workspace. Press "i" to open the app settings page, and then click "Install".\n\n`);
7
+ process.stdout.write(`🚨 IMPORTANT: You will need to install your app in your workspace. Press "i" to open the app settings page, and then click "Install".\n\n`);
7
8
  }
8
- export function onboarding({ token, appId, appSlug, workspace, }) {
9
- const haveInstallation = async () => (await fetchInstallation({ token, appId, workspaceId: workspace.workspace_id })) !== null;
9
+ export function onboarding({ appId, appSlug, workspace, }) {
10
10
  const cleanup = listenForKey("i", () => {
11
11
  open(`${APP}/${workspace.slug}/settings/apps/${appSlug}`);
12
12
  });
13
13
  const poll = async () => {
14
- let installation = await haveInstallation();
14
+ const installationResult = await API.fetchInstallation({
15
+ appId,
16
+ workspaceId: workspace.workspace_id,
17
+ });
18
+ if (isErrored(installationResult)) {
19
+ return;
20
+ }
21
+ let installation = installationResult.value;
15
22
  if (!installation) {
16
23
  prompt();
17
24
  while (!installation) {
18
25
  await new Promise((resolve) => setTimeout(resolve, 60_000));
19
- installation = await haveInstallation();
26
+ const installationResult = await API.fetchInstallation({
27
+ appId,
28
+ workspaceId: workspace.workspace_id,
29
+ });
30
+ if (isErrored(installationResult)) {
31
+ return;
32
+ }
33
+ installation = installationResult.value;
20
34
  if (!installation) {
21
35
  prompt();
22
36
  }
@@ -3,73 +3,204 @@ import tmp from "tmp-promise";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
5
5
  import { createClientBuildConfig } from "../../build/client/create-client-build-config.js";
6
- import { generateClientEntry } from "../../build/client/generate-client-entry.js";
6
+ import { generateClientEntry, } from "../../build/client/generate-client-entry.js";
7
7
  import { createServerBuildConfig } from "../../build/server/create-server-build-config.js";
8
8
  import { generateServerEntry } from "../../build/server/generate-server-entry.js";
9
+ import { errored, isErrored, complete, combineAsync } from "@attio/fetchable";
10
+ import chalk from "chalk";
11
+ import { errorsAndWarningsSchema } from "../../build.js";
9
12
  export async function prepareBuildContexts(mode) {
10
13
  const srcDir = "src";
11
14
  const assetsDir = path.join(srcDir, "assets");
12
15
  const webhooksDir = path.join(srcDir, "webhooks");
13
16
  const eventsDir = path.join(srcDir, "events");
14
- return Promise.all([
15
- tmp.file({ postfix: ".js" }).then(async (tempFile) => {
17
+ return await combineAsync([
18
+ tmp
19
+ .file({ postfix: ".js" })
20
+ .then(async (tempFile) => {
16
21
  let lastJS;
17
22
  const updateTempFile = async () => {
18
- const js = await generateClientEntry({
23
+ const jsResult = await generateClientEntry({
19
24
  srcDirAbsolute: path.resolve(srcDir),
20
25
  assetsDirAbsolute: path.resolve(assetsDir),
21
26
  });
27
+ if (isErrored(jsResult)) {
28
+ return jsResult;
29
+ }
30
+ const js = jsResult.value;
22
31
  if (js === lastJS)
23
- return;
32
+ return complete(undefined);
24
33
  lastJS = js;
25
- await fs.writeFile(tempFile.path, js);
34
+ try {
35
+ await fs.writeFile(tempFile.path, js);
36
+ return complete(undefined);
37
+ }
38
+ catch (error) {
39
+ return errored({
40
+ code: "FAILED_TO_CREATE_TEMP_FILE",
41
+ path: tempFile.path,
42
+ error,
43
+ });
44
+ }
26
45
  };
27
- const esbuildContext = await esbuild.context({
28
- ...createClientBuildConfig({
29
- entryPoint: tempFile.path,
30
- srcDir,
31
- }),
32
- write: mode === "write-to-disk",
33
- outfile: path.resolve("dist", "index.js"),
34
- loader: { ".png": "dataurl", ".graphql": "text", ".gql": "text" },
35
- });
36
- return {
46
+ let esbuildContext;
47
+ const outfile = path.resolve("dist", "index.js");
48
+ try {
49
+ esbuildContext = await esbuild.context({
50
+ ...createClientBuildConfig({
51
+ entryPoint: tempFile.path,
52
+ srcDir,
53
+ }),
54
+ write: mode === "write-to-disk",
55
+ outfile,
56
+ loader: { ".png": "dataurl", ".graphql": "text", ".gql": "text" },
57
+ });
58
+ }
59
+ catch (error) {
60
+ return errored({
61
+ code: "FAILED_TO_CREATE_ESBUILD_CONTEXT",
62
+ outfile,
63
+ error,
64
+ });
65
+ }
66
+ return complete({
37
67
  rebuild: async () => {
38
68
  await updateTempFile();
39
- return esbuildContext.rebuild();
69
+ try {
70
+ return complete(await esbuildContext.rebuild());
71
+ }
72
+ catch (error) {
73
+ const parseResult = errorsAndWarningsSchema.safeParse(error);
74
+ if (!parseResult.success) {
75
+ return errored({
76
+ code: "UNPARSABLE_BUILD_ERROR",
77
+ error,
78
+ });
79
+ }
80
+ return errored({
81
+ code: "BUILD_JAVASCRIPT_ERROR",
82
+ errors: parseResult.data.errors ?? [],
83
+ warnings: parseResult.data.warnings ?? [],
84
+ });
85
+ }
40
86
  },
41
87
  dispose: async () => {
42
- await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
88
+ try {
89
+ await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
90
+ }
91
+ catch (error) {
92
+ return errored({
93
+ code: "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT",
94
+ error,
95
+ });
96
+ }
97
+ return complete(undefined);
43
98
  },
44
- };
99
+ });
45
100
  }),
46
- tmp.file({ postfix: ".js" }).then(async (tempFile) => {
101
+ tmp
102
+ .file({ postfix: ".js" })
103
+ .then(async (tempFile) => {
47
104
  let lastJS;
48
105
  const updateTempFile = async () => {
49
- const js = await generateServerEntry({
106
+ const jsResult = await generateServerEntry({
50
107
  srcDirAbsolute: path.resolve(srcDir),
51
108
  webhooksDirAbsolute: path.resolve(webhooksDir),
52
109
  eventDirAbsolute: path.resolve(eventsDir),
53
110
  });
111
+ if (isErrored(jsResult)) {
112
+ return jsResult;
113
+ }
114
+ const js = jsResult.value;
54
115
  if (js === lastJS)
55
- return;
116
+ return complete(undefined);
56
117
  lastJS = js;
57
- await fs.writeFile(tempFile.path, js);
118
+ try {
119
+ await fs.writeFile(tempFile.path, js);
120
+ return complete(undefined);
121
+ }
122
+ catch (error) {
123
+ return errored({
124
+ code: "FAILED_TO_CREATE_TEMP_FILE",
125
+ path: tempFile.path,
126
+ error,
127
+ });
128
+ }
58
129
  };
59
- const esbuildContext = await esbuild.context({
60
- ...createServerBuildConfig(tempFile.path),
61
- write: mode === "write-to-disk",
62
- outfile: path.resolve("dist", "server.js"),
63
- });
64
- return {
130
+ let esbuildContext;
131
+ const outfile = path.resolve("dist", "server.js");
132
+ try {
133
+ esbuildContext = await esbuild.context({
134
+ ...createServerBuildConfig(tempFile.path),
135
+ write: mode === "write-to-disk",
136
+ outfile,
137
+ });
138
+ }
139
+ catch (error) {
140
+ return errored({
141
+ code: "FAILED_TO_CREATE_ESBUILD_CONTEXT",
142
+ outfile,
143
+ error,
144
+ });
145
+ }
146
+ return complete({
65
147
  rebuild: async () => {
66
148
  await updateTempFile();
67
- return esbuildContext.rebuild();
149
+ try {
150
+ return complete(await esbuildContext.rebuild());
151
+ }
152
+ catch (error) {
153
+ const parseResult = errorsAndWarningsSchema.safeParse(error);
154
+ if (!parseResult.success) {
155
+ return errored({
156
+ code: "UNPARSABLE_BUILD_ERROR",
157
+ error,
158
+ });
159
+ }
160
+ return errored({
161
+ code: "BUILD_JAVASCRIPT_ERROR",
162
+ errors: parseResult.data.errors ?? [],
163
+ warnings: parseResult.data.warnings ?? [],
164
+ });
165
+ }
68
166
  },
69
167
  dispose: async () => {
70
- await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
168
+ try {
169
+ await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
170
+ }
171
+ catch (error) {
172
+ return errored({
173
+ code: "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT",
174
+ error,
175
+ });
176
+ }
177
+ return complete(undefined);
71
178
  },
72
- };
179
+ });
73
180
  }),
74
181
  ]);
75
182
  }
183
+ export function printBuildContextError(error) {
184
+ switch (error.code) {
185
+ case "FAILED_TO_CREATE_TEMP_FILE":
186
+ process.stderr.write(`${chalk.red("✖ ")}Failed to create temp file: ${error.path}\n`);
187
+ break;
188
+ case "FAILED_TO_GENERATE_CLIENT_ENTRY":
189
+ process.stderr.write(`${chalk.red("✖ ")}Failed to generate client entry\n`);
190
+ break;
191
+ case "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT":
192
+ process.stderr.write(`${chalk.red("✖ ")}Failed to dispose of build context: ${error.error}\n`);
193
+ break;
194
+ case "FAILED_TO_CREATE_ESBUILD_CONTEXT":
195
+ process.stderr.write(`${chalk.red("✖ ")}Failed to create esbuild context (${error.outfile}): ${error.error}\n`);
196
+ break;
197
+ case "ERROR_FINDING_SURFACE_EXPORTS":
198
+ process.stderr.write(`${chalk.red("✖ ")}Failed to find surface exports: ${error.error}\n`);
199
+ break;
200
+ case "UNPARSABLE_BUILD_ERROR":
201
+ process.stderr.write(`${chalk.red("✖ ")}Failed to parse build error: ${error.error}\n`);
202
+ break;
203
+ default:
204
+ return error;
205
+ }
206
+ }
@@ -5,7 +5,7 @@ import { buildSchema } from "graphql";
5
5
  import { Hono } from "hono";
6
6
  import path, { dirname } from "path";
7
7
  import { fileURLToPath } from "url";
8
- import { findAvailablePort } from "../util/find-available-port.js";
8
+ import { findAvailablePort } from "../../util/find-available-port.js";
9
9
  export function startGraphqlServer(sendBack) {
10
10
  let server = null;
11
11
  const startServer = async () => {
@@ -21,7 +21,7 @@ export function startGraphqlServer(sendBack) {
21
21
  };
22
22
  app.use("/graphql", graphqlServer({ schema, rootResolver, graphiql: true }));
23
23
  server = serve({ fetch: app.fetch, port });
24
- sendBack({ type: "GraphQL Server Started", port });
24
+ sendBack({ code: "GraphQL Server Started", port });
25
25
  };
26
26
  startServer();
27
27
  return () => {
@@ -1,59 +1,55 @@
1
1
  import notifier from "node-notifier";
2
- import { startUpload } from "../../api/start-upload.js";
3
- import { completeBundleUpload } from "../../api/complete-bundle-upload.js";
4
- import { Spinner } from "../../util/spinner.js";
5
- export async function upload({ token, contents, devVersionId, appId, }) {
6
- const spinner = new Spinner().start("Uploading...");
7
- try {
8
- const { client_bundle_upload_url, server_bundle_upload_url, app_dev_version_bundle_id: bundleId, } = await startUpload({
9
- token,
2
+ import { spinnerify } from "../../util/spinner.js";
3
+ import { uploadBundle } from "../../util/upload-bundle.js";
4
+ import { API } from "../../api/api.js";
5
+ import { isErrored, combineAsync, complete } from "@attio/fetchable";
6
+ import { printFetcherError } from "../../api/fetcher.js";
7
+ import chalk from "chalk";
8
+ export async function upload({ contents, devVersionId, appId, }) {
9
+ return await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
10
+ const startUploadResult = await API.startUpload({
10
11
  appId,
11
12
  devVersionId,
12
13
  });
14
+ if (isErrored(startUploadResult)) {
15
+ return startUploadResult;
16
+ }
17
+ const { client_bundle_upload_url, server_bundle_upload_url, app_dev_version_bundle_id: bundleId, } = startUploadResult.value;
13
18
  const [clientBundle, serverBundle] = contents;
14
- await Promise.all([
15
- fetch(client_bundle_upload_url, {
16
- method: "PUT",
17
- body: clientBundle,
18
- headers: {
19
- "Content-Type": "text/javascript",
20
- "Content-Length": String(Buffer.from(clientBundle).length),
21
- },
22
- }),
23
- fetch(server_bundle_upload_url, {
24
- method: "PUT",
25
- body: serverBundle,
26
- headers: {
27
- "Content-Type": "text/javascript",
28
- "Content-Length": String(Buffer.from(serverBundle).length),
29
- },
30
- }),
31
- ]).catch((error) => {
32
- process.stderr.write(`Upload Error: ${error}`);
33
- });
34
- await completeBundleUpload({
35
- token,
19
+ const uploadResults = await combineAsync([
20
+ uploadBundle(clientBundle, client_bundle_upload_url),
21
+ uploadBundle(serverBundle, server_bundle_upload_url),
22
+ ]);
23
+ if (isErrored(uploadResults)) {
24
+ return uploadResults;
25
+ }
26
+ const completeBundleUploadResult = await API.completeBundleUpload({
36
27
  appId,
37
28
  devVersionId,
38
29
  bundleId,
39
30
  });
40
- spinner.success(`Upload completed at ${new Date().toLocaleString()}.`);
31
+ if (isErrored(completeBundleUploadResult)) {
32
+ return completeBundleUploadResult;
33
+ }
41
34
  notifier.notify({
42
35
  title: "Upload Complete",
43
36
  message: "New bundle uploaded to Attio",
44
37
  });
45
- }
46
- catch (error) {
47
- if (error instanceof Error) {
48
- spinner.error(`Upload failed: ${error.message}`);
49
- notifier.notify({
50
- title: "Upload Failed",
51
- message: "Bundle upload to Attio failed",
52
- });
53
- }
54
- throw error;
55
- }
56
- finally {
57
- spinner.stop();
38
+ return complete(undefined);
39
+ });
40
+ }
41
+ export function printUploadError(error) {
42
+ switch (error.code) {
43
+ case "BUNDLE_UPLOAD_ERROR":
44
+ process.stderr.write(chalk.red(`Error uploading bundle: ${error.error}\n`));
45
+ break;
46
+ case "START_UPLOAD_ERROR":
47
+ printFetcherError("Error starting upload", error.fetcherError);
48
+ break;
49
+ case "COMPLETE_BUNDLE_UPLOAD_ERROR":
50
+ printFetcherError("Error completing bundle upload", error.fetcherError);
51
+ break;
52
+ default:
53
+ return error;
58
54
  }
59
55
  }
@@ -1,16 +1,7 @@
1
1
  import chokidar from "chokidar";
2
- import notifier from "node-notifier";
3
2
  import { getDiagnostics, readConfig, typeScriptErrorSchema, } from "../../util/typescript.js";
4
- import { printTsError } from "../../util/typescript.js";
5
- import { clearTerminal } from "../../util/clear-terminal.js";
6
3
  import path from "path";
7
- const notify = (errors) => {
8
- notifier.notify({
9
- title: `TypeScript Error${errors.length === 1 ? "" : "s"}`,
10
- message: `There ${errors.length === 1 ? "was one error" : `were ${errors.length} errors`} in your TypeScript code`,
11
- });
12
- };
13
- export function validateTypeScript(onSuccess) {
4
+ export function validateTypeScript(onSuccess, onError) {
14
5
  let isShuttingDown = false;
15
6
  const watcher = chokidar.watch(["src/**/*.ts", "src/**/*.tsx"], {
16
7
  ignored: [
@@ -37,9 +28,7 @@ export function validateTypeScript(onSuccess) {
37
28
  }
38
29
  const errors = await getDiagnostics(program);
39
30
  if (errors.length) {
40
- clearTerminal();
41
- errors.forEach(printTsError);
42
- notify(errors);
31
+ onError?.(errors);
43
32
  }
44
33
  else {
45
34
  onSuccess?.();
@@ -54,16 +43,13 @@ export function validateTypeScript(onSuccess) {
54
43
  }
55
44
  if (error instanceof Error) {
56
45
  const tsError = typeScriptErrorSchema.parse({ text: error.message });
57
- clearTerminal();
58
- printTsError(tsError);
59
- notify([tsError]);
46
+ onError?.([tsError]);
60
47
  }
61
48
  }
62
49
  }
63
50
  let watcherReady = false;
64
51
  watcher.on("ready", () => {
65
52
  watcherReady = true;
66
- handleValidation();
67
53
  watcher.on("all", (event, path) => {
68
54
  if (event === "add" || event === "change" || event === "unlink") {
69
55
  handleValidation();