attio 0.0.1-experimental.20250408 → 0.0.1-experimental.20250414

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 (59) hide show
  1. package/lib/api/api.js +99 -31
  2. package/lib/api/fetcher.js +31 -27
  3. package/lib/api/schemas.js +6 -0
  4. package/lib/auth/auth.js +135 -97
  5. package/lib/auth/keychain.js +86 -43
  6. package/lib/build/server/generate-server-entry.js +6 -6
  7. package/lib/build.js +2 -2
  8. package/lib/commands/build/build-javascript.js +1 -1
  9. package/lib/commands/build/validate-typescript.js +1 -1
  10. package/lib/commands/build.js +2 -2
  11. package/lib/commands/dev/boot.js +9 -9
  12. package/lib/commands/dev/bundle-javascript.js +1 -1
  13. package/lib/commands/dev/onboarding.js +3 -3
  14. package/lib/commands/dev/prepare-build-contexts.js +1 -1
  15. package/lib/commands/dev/upload.js +4 -21
  16. package/lib/commands/dev.js +5 -4
  17. package/lib/commands/init/create-project.js +7 -39
  18. package/lib/commands/init.js +6 -14
  19. package/lib/commands/login.js +8 -2
  20. package/lib/commands/logout.js +8 -2
  21. package/lib/commands/version/create/bundle-javascript.js +3 -3
  22. package/lib/commands/version/create.js +14 -14
  23. package/lib/commands/version/list.js +4 -4
  24. package/lib/commands/whoami.js +7 -8
  25. package/lib/errors.js +1 -0
  26. package/lib/print-errors.js +165 -0
  27. package/lib/spinners/determine-workspace.spinner.js +3 -26
  28. package/lib/spinners/get-app-info.spinner.js +2 -2
  29. package/lib/spinners/get-app-slug-from-package-json.js +1 -20
  30. package/lib/spinners/get-versions.spinner.js +2 -2
  31. package/lib/util/copy-with-replace.js +2 -2
  32. package/lib/util/create-directory.js +1 -1
  33. package/lib/util/load-attio-cli-version.js +1 -26
  34. package/lib/util/upload-bundle.js +1 -1
  35. package/package.json +1 -1
  36. package/lib/auth/ensure-authed.js +0 -14
  37. package/lib/commands/dev/start-graphql-server.js +0 -32
  38. package/lib/commands/init/ask-language.js +0 -11
  39. package/lib/templates/javascript/.env +0 -12
  40. package/lib/templates/javascript/eslint.config.js +0 -48
  41. package/lib/templates/javascript/package.json +0 -26
  42. package/lib/templates/javascript/src/assets/icon.png +0 -0
  43. package/lib/templates/javascript/src/cat-fact.jsx +0 -16
  44. package/lib/templates/javascript/src/get-cat-fact.server.js +0 -6
  45. package/lib/templates/javascript/src/hello-world-action.jsx +0 -17
  46. package/lib/templates/javascript/src/hello-world-dialog.jsx +0 -26
  47. package/lib/templates/typescript/.eslintignore +0 -3
  48. package/lib/templates/typescript/src/assets/icon.png +0 -0
  49. /package/lib/{templates/typescript → template}/.env +0 -0
  50. /package/lib/{templates/javascript → template}/.eslintignore +0 -0
  51. /package/lib/{templates/common → template}/README.md +0 -0
  52. /package/lib/{templates/typescript → template}/eslint.config.js +0 -0
  53. /package/lib/{templates/typescript → template}/package.json +0 -0
  54. /package/lib/{templates/common → template}/src/assets/icon.png +0 -0
  55. /package/lib/{templates/typescript → template}/src/cat-fact.tsx +0 -0
  56. /package/lib/{templates/typescript → template}/src/get-cat-fact.server.ts +0 -0
  57. /package/lib/{templates/typescript → template}/src/hello-world-action.tsx +0 -0
  58. /package/lib/{templates/typescript → template}/src/hello-world-dialog.tsx +0 -0
  59. /package/lib/{templates/typescript → template}/tsconfig.json +0 -0
@@ -1,52 +1,95 @@
1
- import { hardExit } from "../util/hard-exit.js";
2
- const TEST_TOKEN = "TEST";
3
- let keytar = null;
4
- if (process.env.NODE_ENV !== "test") {
5
- try {
6
- keytar = (await import("keytar")).default;
1
+ import { complete, errored, isErrored } from "@attio/fetchable";
2
+ import { z } from "zod";
3
+ const authTokenSchema = z.object({
4
+ access_token: z.string(),
5
+ refresh_token: z.string(),
6
+ token_type: z.literal("Bearer"),
7
+ expires_at: z.number(),
8
+ });
9
+ class KeytarKeychain {
10
+ SERVICE_NAME = "attio-cli";
11
+ ACCOUNT_NAME = "developer";
12
+ keytarPromise;
13
+ constructor() {
14
+ this.keytarPromise = import("keytar").then((module) => module.default);
7
15
  }
8
- catch (e) {
9
- console.warn("Keychain functionality not available");
10
- }
11
- }
12
- const SERVICE_NAME = "attio-cli";
13
- const ACCOUNT_NAME = "developer";
14
- export async function saveAuthToken(token) {
15
- if (!keytar) {
16
- return hardExit("Keychain functionality not available");
17
- }
18
- try {
19
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
20
- }
21
- catch (error) {
22
- return hardExit(error instanceof Error ? error.message : "Failed to save token to keychain");
23
- }
24
- }
25
- export async function loadAuthToken() {
26
- if (process.env.NODE_ENV === "test") {
27
- return TEST_TOKEN;
28
- }
29
- if (!keytar) {
30
- return hardExit("Keychain functionality not available");
16
+ async save(token) {
17
+ const keytar = await this.keytarPromise;
18
+ try {
19
+ await keytar.setPassword(this.SERVICE_NAME, this.ACCOUNT_NAME, JSON.stringify(token));
20
+ return complete(undefined);
21
+ }
22
+ catch (error) {
23
+ return errored({
24
+ code: "SAVE_KEYCHAIN_ERROR",
25
+ error,
26
+ });
27
+ }
31
28
  }
32
- try {
33
- return await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
29
+ async load() {
30
+ const keytar = await this.keytarPromise;
31
+ let unparsedToken = null;
32
+ try {
33
+ unparsedToken = await keytar.getPassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
34
+ }
35
+ catch (error) {
36
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error });
37
+ }
38
+ if (unparsedToken === null) {
39
+ return complete(null);
40
+ }
41
+ let jsonToken;
42
+ try {
43
+ jsonToken = JSON.parse(unparsedToken);
44
+ }
45
+ catch (error) {
46
+ const deleteResult = await this.delete();
47
+ if (isErrored(deleteResult)) {
48
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
49
+ }
50
+ console.debug("Wiped keychain entry due to invalid JSON");
51
+ return complete(null);
52
+ }
53
+ const parsedToken = authTokenSchema.safeParse(jsonToken);
54
+ if (!parsedToken.success) {
55
+ const deleteResult = await this.delete();
56
+ if (isErrored(deleteResult)) {
57
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
58
+ }
59
+ console.debug("Wiped keychain entry due to schema mismatch");
60
+ return complete(null);
61
+ }
62
+ return complete(parsedToken.data);
34
63
  }
35
- catch (error) {
36
- return hardExit(error instanceof Error ? error.message : "Failed to load token from keychain");
64
+ async delete() {
65
+ const keytar = await this.keytarPromise;
66
+ try {
67
+ await keytar.deletePassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
68
+ return complete(undefined);
69
+ }
70
+ catch (error) {
71
+ return errored({
72
+ code: "DELETE_KEYCHAIN_ERROR",
73
+ error,
74
+ });
75
+ }
37
76
  }
38
77
  }
39
- export async function deleteAuthToken() {
40
- if (!keytar) {
41
- if (process.env.NODE_ENV === "test") {
42
- return;
43
- }
44
- return hardExit("Keychain functionality not available");
78
+ class TestKeychain {
79
+ TEST_TOKEN = {
80
+ access_token: "TEST",
81
+ refresh_token: "TEST",
82
+ token_type: "Bearer",
83
+ expires_at: Number.MAX_SAFE_INTEGER,
84
+ };
85
+ async save(_token) {
86
+ return complete(undefined);
45
87
  }
46
- try {
47
- await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
88
+ async load() {
89
+ return complete(this.TEST_TOKEN);
48
90
  }
49
- catch (error) {
50
- return hardExit(error instanceof Error ? error.message : "Failed to delete token from keychain");
91
+ async delete() {
92
+ return complete(undefined);
51
93
  }
52
94
  }
95
+ export const keychain = process.env.NODE_ENV === "test" ? new TestKeychain() : new KeytarKeychain();
@@ -1,14 +1,14 @@
1
1
  import { glob } from "glob";
2
2
  import path from "path";
3
3
  import { errored, complete, isErrored, combineAsync } from "@attio/fetchable";
4
- async function findPaths(dir, pattern) {
4
+ async function findServerFunctionModules(cwd, pattern) {
5
5
  try {
6
- return complete(await glob(`**/*.${pattern}.(js|ts)`, { nodir: true, cwd: dir }));
6
+ return complete(await glob(`**/*.${pattern}.{js,ts}`, { nodir: true, cwd }));
7
7
  }
8
8
  catch (error) {
9
9
  return errored({
10
10
  code: "ERROR_FINDING_SERVER_FUNCTION_MODULES",
11
- dir,
11
+ path: cwd,
12
12
  pattern,
13
13
  error,
14
14
  });
@@ -16,9 +16,9 @@ async function findPaths(dir, pattern) {
16
16
  }
17
17
  export async function generateServerEntry({ srcDirAbsolute, webhooksDirAbsolute, eventDirAbsolute, log, }) {
18
18
  const pathsResult = await combineAsync([
19
- findPaths(srcDirAbsolute, "server"),
20
- findPaths(webhooksDirAbsolute, "webhook"),
21
- findPaths(eventDirAbsolute, "event"),
19
+ findServerFunctionModules(srcDirAbsolute, "server"),
20
+ findServerFunctionModules(webhooksDirAbsolute, "webhook"),
21
+ findServerFunctionModules(eventDirAbsolute, "event"),
22
22
  ]);
23
23
  if (isErrored(pathsResult)) {
24
24
  return pathsResult;
package/lib/build.js CHANGED
@@ -13,6 +13,6 @@ const jsErrorSchema = z.object({
13
13
  }),
14
14
  });
15
15
  export const errorsAndWarningsSchema = z.object({
16
- errors: z.array(jsErrorSchema).optional(),
17
- warnings: z.array(jsErrorSchema).optional(),
16
+ errors: z.array(jsErrorSchema).optional().readonly(),
17
+ warnings: z.array(jsErrorSchema).optional().readonly(),
18
18
  });
@@ -1,5 +1,5 @@
1
- import { prepareBuildContexts, } from "../dev/prepare-build-contexts.js";
2
1
  import { combineAsync, complete, isErrored } from "@attio/fetchable";
2
+ import { prepareBuildContexts } from "../dev/prepare-build-contexts.js";
3
3
  export async function buildJavaScript() {
4
4
  const buildContextsResult = await prepareBuildContexts("write-to-disk");
5
5
  if (isErrored(buildContextsResult)) {
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import { getDiagnostics, readConfig, typeScriptErrorSchema, } from "../../util/typescript.js";
2
+ import { getDiagnostics, readConfig, typeScriptErrorSchema } from "../../util/typescript.js";
3
3
  import { complete, errored } from "@attio/fetchable";
4
4
  export async function validateTypeScript() {
5
5
  try {
@@ -24,8 +24,8 @@ export const build = new Command("build")
24
24
  if (isErrored(jsResult)) {
25
25
  if (jsResult.error.code === "BUILD_JAVASCRIPT_ERROR") {
26
26
  const { errors, warnings } = jsResult.error;
27
- errors.forEach((error) => printJsError(error, "error"));
28
- warnings.forEach((warning) => printJsError(warning, "warning"));
27
+ errors?.forEach((error) => printJsError(error, "error"));
28
+ warnings?.forEach((warning) => printJsError(warning, "warning"));
29
29
  process.stderr.write("JavaScript build failed");
30
30
  process.exit(1);
31
31
  }
@@ -1,11 +1,11 @@
1
- import { getAppSlugFromPackageJson, printPackageJsonError, } from "../../spinners/get-app-slug-from-package-json.js";
2
- import { determineWorkspace, printDetermineWorkspaceError, } from "../../spinners/determine-workspace.spinner.js";
1
+ import { isErrored } from "@attio/fetchable";
2
+ import { printFetcherError, printCliVersionError, printPackageJsonError, printDetermineWorkspaceError, } from "../../print-errors.js";
3
+ import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
4
+ import { determineWorkspace } from "../../spinners/determine-workspace.spinner.js";
5
+ import { loadAttioCliVersion } from "../../util/load-attio-cli-version.js";
6
+ import { api } from "../../api/api.js";
3
7
  import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
4
8
  import { loadEnv } from "../../util/load-env.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";
9
9
  export async function boot({ workspaceSlug }) {
10
10
  const appSlugResult = await getAppSlugFromPackageJson();
11
11
  if (isErrored(appSlugResult)) {
@@ -15,7 +15,7 @@ export async function boot({ workspaceSlug }) {
15
15
  const appSlug = appSlugResult.value;
16
16
  const appInfoResult = await getAppInfo(appSlug);
17
17
  if (isErrored(appInfoResult)) {
18
- printFetcherError("Error loading app info", appInfoResult.error.fetcherError);
18
+ printFetcherError("Error loading app info", appInfoResult.error);
19
19
  process.exit(1);
20
20
  }
21
21
  const appInfo = appInfoResult.value;
@@ -32,14 +32,14 @@ export async function boot({ workspaceSlug }) {
32
32
  process.exit(1);
33
33
  }
34
34
  const cliVersion = cliVersionResult.value;
35
- const devVersionResult = await API.createDevVersion({
35
+ const devVersionResult = await api.createDevVersion({
36
36
  appId: appInfo.app_id,
37
37
  cliVersion,
38
38
  targetWorkspaceId: workspace.workspace_id,
39
39
  environmentVariables,
40
40
  });
41
41
  if (isErrored(devVersionResult)) {
42
- printFetcherError("Error creating dev version", devVersionResult.error.fetcherError);
42
+ printFetcherError("Error creating dev version", devVersionResult.error);
43
43
  process.exit(1);
44
44
  }
45
45
  const devVersion = devVersionResult.value;
@@ -1,6 +1,6 @@
1
1
  import chokidar from "chokidar";
2
- import { prepareBuildContexts, } from "./prepare-build-contexts.js";
3
2
  import { combineAsync, complete, isErrored } from "@attio/fetchable";
3
+ import { prepareBuildContexts } from "./prepare-build-contexts.js";
4
4
  export function bundleJavaScript(onSuccess, onError) {
5
5
  const watcher = chokidar.watch(["./src/**/*.{js,jsx,ts,tsx}"], {
6
6
  ignored: ["**/node_modules/**", "**/dist/**", "**/*.graphql.d.ts", "**/*.gql.d.ts"],
@@ -1,7 +1,7 @@
1
1
  import { listenForKey } from "../../util/listen-for-key.js";
2
2
  import open from "open";
3
3
  import { APP } from "../../env.js";
4
- import { API } from "../../api/api.js";
4
+ import { api } from "../../api/api.js";
5
5
  import { isErrored } from "@attio/fetchable";
6
6
  function prompt() {
7
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`);
@@ -11,7 +11,7 @@ export function onboarding({ appId, appSlug, workspace, }) {
11
11
  open(`${APP}/${workspace.slug}/settings/apps/${appSlug}`);
12
12
  });
13
13
  const poll = async () => {
14
- const installationResult = await API.fetchInstallation({
14
+ const installationResult = await api.fetchInstallation({
15
15
  appId,
16
16
  workspaceId: workspace.workspace_id,
17
17
  });
@@ -23,7 +23,7 @@ export function onboarding({ appId, appSlug, workspace, }) {
23
23
  prompt();
24
24
  while (!installation) {
25
25
  await new Promise((resolve) => setTimeout(resolve, 60_000));
26
- const installationResult = await API.fetchInstallation({
26
+ const installationResult = await api.fetchInstallation({
27
27
  appId,
28
28
  workspaceId: workspace.workspace_id,
29
29
  });
@@ -3,7 +3,7 @@ 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
9
  import { errored, isErrored, complete, combineAsync } from "@attio/fetchable";
@@ -1,13 +1,11 @@
1
1
  import notifier from "node-notifier";
2
2
  import { spinnerify } from "../../util/spinner.js";
3
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";
4
+ import { api } from "../../api/api.js";
5
+ import { isErrored, complete, combineAsync } from "@attio/fetchable";
8
6
  export async function upload({ contents, devVersionId, appId, }) {
9
7
  return await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
10
- const startUploadResult = await API.startUpload({
8
+ const startUploadResult = await api.startUpload({
11
9
  appId,
12
10
  devVersionId,
13
11
  });
@@ -23,7 +21,7 @@ export async function upload({ contents, devVersionId, appId, }) {
23
21
  if (isErrored(uploadResults)) {
24
22
  return uploadResults;
25
23
  }
26
- const completeBundleUploadResult = await API.completeBundleUpload({
24
+ const completeBundleUploadResult = await api.completeBundleUpload({
27
25
  appId,
28
26
  devVersionId,
29
27
  bundleId,
@@ -38,18 +36,3 @@ export async function upload({ contents, devVersionId, appId, }) {
38
36
  return complete(undefined);
39
37
  });
40
38
  }
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;
54
- }
55
- }
@@ -8,10 +8,11 @@ import { bundleJavaScript } from "./dev/bundle-javascript.js";
8
8
  import { boot } from "./dev/boot.js";
9
9
  import { onboarding } from "./dev/onboarding.js";
10
10
  import { graphqlServer } from "./dev/graphql-server.js";
11
- import { printUploadError, upload } from "./dev/upload.js";
11
+ import { printUploadError } from "../print-errors.js";
12
+ import { upload } from "./dev/upload.js";
12
13
  import { isErrored } from "@attio/fetchable";
13
14
  import { printJsError, printTsError } from "../util/typescript.js";
14
- import { printBuildContextError, } from "./dev/prepare-build-contexts.js";
15
+ import { printBuildContextError } from "./dev/prepare-build-contexts.js";
15
16
  import notifier from "node-notifier";
16
17
  const notifyTsErrors = (errors) => {
17
18
  notifier.notify({
@@ -92,8 +93,8 @@ export const dev = new Command("dev")
92
93
  if (error.code === "BUILD_JAVASCRIPT_ERROR") {
93
94
  notifyJsErrors(error);
94
95
  const { errors, warnings } = error;
95
- errors.forEach((error) => printJsError(error, "error"));
96
- warnings.forEach((warning) => printJsError(warning, "warning"));
96
+ errors?.forEach((error) => printJsError(error, "error"));
97
+ warnings?.forEach((warning) => printJsError(warning, "warning"));
97
98
  }
98
99
  else {
99
100
  printBuildContextError(error);
@@ -1,13 +1,12 @@
1
1
  import { fileURLToPath } from "url";
2
2
  import path from "path";
3
- import chalk from "chalk";
4
3
  import { existsSync } from "fs";
5
4
  import { createDirectory } from "../../util/create-directory.js";
6
5
  import { canWrite } from "../../util/can-write.js";
7
6
  import { copyWithTransform } from "../../util/copy-with-replace.js";
8
7
  import { spinnerify } from "../../util/spinner.js";
9
- import { combineAsync, complete, errored, isErrored } from "@attio/fetchable";
10
- export async function createProject({ appSlug, language, appInfo, }) {
8
+ import { complete, errored, isErrored } from "@attio/fetchable";
9
+ export async function createProject({ appSlug, appInfo, }) {
11
10
  return await spinnerify("Creating project...", "Project created", async () => {
12
11
  const cwd = process.cwd();
13
12
  const projectPath = path.join(cwd, appSlug);
@@ -15,7 +14,7 @@ export async function createProject({ appSlug, language, appInfo, }) {
15
14
  return errored({ code: "DIRECTORY_ALREADY_EXISTS", path: projectPath });
16
15
  }
17
16
  if (!canWrite(cwd)) {
18
- return errored({ code: "WRITE_ACCESS_DENIED", directory: cwd });
17
+ return errored({ code: "WRITE_ACCESS_DENIED", path: cwd });
19
18
  }
20
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
20
  const projectDirResult = await createDirectory(appSlug);
@@ -23,46 +22,15 @@ export async function createProject({ appSlug, language, appInfo, }) {
23
22
  return projectDirResult;
24
23
  }
25
24
  const projectDir = projectDirResult.value;
26
- const templatesDir = path.resolve(__dirname, "../../templates", language);
27
- const commonDir = path.resolve(__dirname, "../../templates", "common");
25
+ const templateDir = path.resolve(__dirname, "../../template");
28
26
  const transform = (contents) => contents
29
27
  .replaceAll("title-to-be-replaced", appInfo.title)
30
28
  .replaceAll("id-to-be-replaced", appInfo.app_id)
31
29
  .replaceAll("slug-to-be-replaced", appSlug);
32
- const results = await combineAsync([
33
- copyWithTransform(templatesDir, projectDir, transform),
34
- copyWithTransform(commonDir, projectDir, transform),
35
- ]);
36
- if (isErrored(results)) {
37
- return results;
30
+ const result = await copyWithTransform(templateDir, projectDir, transform);
31
+ if (isErrored(result)) {
32
+ return result;
38
33
  }
39
34
  return complete(undefined);
40
35
  });
41
36
  }
42
- export function printCreateProjectError(error) {
43
- switch (error.code) {
44
- case "DIRECTORY_ALREADY_EXISTS":
45
- process.stderr.write(chalk.red(`Directory ${error.path} already exists`));
46
- break;
47
- case "WRITE_ACCESS_DENIED":
48
- process.stderr.write(chalk.red(`Write access denied to ${error.directory}`));
49
- break;
50
- case "FAILED_TO_CREATE_DIRECTORY":
51
- process.stderr.write(chalk.red(`Failed to create directory ${error.directory}`));
52
- break;
53
- case "FAILED_TO_COPY_FILE":
54
- process.stderr.write(chalk.red(`Failed to copy file ${error.src} to ${error.dest}`));
55
- break;
56
- case "FAILED_TO_LIST_FILES":
57
- process.stderr.write(chalk.red(`Failed to list files in ${error.directory}`));
58
- break;
59
- case "FAILED_TO_READ_FILE":
60
- process.stderr.write(chalk.red(`Failed to read file ${error.path}`));
61
- break;
62
- case "FAILED_TO_WRITE_FILE":
63
- process.stderr.write(chalk.red(`Failed to write file ${error.path}`));
64
- break;
65
- default:
66
- return error;
67
- }
68
- }
@@ -1,36 +1,28 @@
1
- import { Argument, Command, Option } from "commander";
1
+ import { Argument, Command } from "commander";
2
2
  import { z } from "zod";
3
3
  import chalk from "chalk";
4
- import { createProject, printCreateProjectError } from "./init/create-project.js";
5
- import { askLanguage } from "./init/ask-language.js";
4
+ import { createProject } from "./init/create-project.js";
5
+ import { printCreateProjectError, printFetcherError } from "../print-errors.js";
6
6
  import { printLogo } from "../util/print-logo.js";
7
7
  import { getAppInfo } from "../spinners/get-app-info.spinner.js";
8
- import { printFetcherError } from "../api/fetcher.js";
9
8
  import { isErrored } from "@attio/fetchable";
10
9
  import boxen from "boxen";
11
10
  export const argsSchema = z.string();
12
- export const optionsSchema = z.object({
13
- language: z.enum(["javascript", "typescript"]).optional(),
14
- });
15
11
  export const init = new Command("init")
16
12
  .description("Initialize a new Attio app")
17
13
  .addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
18
- .addOption(new Option("--language <language>", "Language").choices(["javascript", "typescript"]))
19
- .action(async (unparsedArgs, unparsedOptions) => {
14
+ .action(async (unparsedArgs) => {
20
15
  try {
21
16
  printLogo();
22
17
  const appSlug = argsSchema.parse(unparsedArgs);
23
- const { language: cliLanguage } = optionsSchema.parse(unparsedOptions);
24
18
  const appInfoResult = await getAppInfo(appSlug);
25
19
  if (isErrored(appInfoResult)) {
26
- printFetcherError("Failed to fetch app info", appInfoResult.error.fetcherError);
20
+ printFetcherError("Failed to fetch app info", appInfoResult.error);
27
21
  process.exit(1);
28
22
  }
29
23
  const appInfo = appInfoResult.value;
30
- const language = cliLanguage ?? (await askLanguage());
31
24
  const result = await createProject({
32
25
  appSlug,
33
- language,
34
26
  appInfo,
35
27
  });
36
28
  if (isErrored(result)) {
@@ -52,7 +44,7 @@ ${boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
52
44
  process.exit(0);
53
45
  }
54
46
  catch (error) {
55
- process.stderr.write(chalk.red("✖ " + String(error)) + "\n");
47
+ process.stderr.write(`${chalk.red(`✖ ${error}`)}\n`);
56
48
  process.exit(1);
57
49
  }
58
50
  });
@@ -1,9 +1,15 @@
1
1
  import { Command } from "commander";
2
- import { auth } from "../auth/auth.js";
2
+ import { authenticator } from "../auth/auth.js";
3
+ import { isErrored } from "@attio/fetchable";
4
+ import { printAuthenticationError } from "../print-errors.js";
3
5
  export const login = new Command("login")
4
6
  .description("Authenticate with Attio")
5
7
  .action(async () => {
6
- await auth();
8
+ const result = await authenticator.authenticate();
9
+ if (isErrored(result)) {
10
+ printAuthenticationError(result.error);
11
+ process.exit(1);
12
+ }
7
13
  process.stdout.write("🔓 Successfully authenticated.\n");
8
14
  process.exit(0);
9
15
  });
@@ -1,7 +1,13 @@
1
1
  import { Command } from "commander";
2
- import { deleteAuthToken } from "../auth/keychain.js";
2
+ import { keychain } from "../auth/keychain.js";
3
+ import { printKeychainError } from "../print-errors.js";
4
+ import { isErrored } from "@attio/fetchable";
3
5
  export const logout = new Command("logout").description("Log out from Attio").action(async () => {
4
- await deleteAuthToken();
6
+ const result = await keychain.delete();
7
+ if (isErrored(result)) {
8
+ printKeychainError(result.error);
9
+ process.exit(1);
10
+ }
5
11
  process.stdout.write("🔒 Successfully logged out.\n");
6
12
  process.exit(0);
7
13
  });
@@ -1,11 +1,11 @@
1
1
  import { complete, isErrored, errored, combineAsync } from "@attio/fetchable";
2
- import { prepareBuildContexts, } from "../../dev/prepare-build-contexts.js";
2
+ import { prepareBuildContexts } from "../../dev/prepare-build-contexts.js";
3
3
  export async function bundleJavaScript() {
4
4
  const buildContextsResult = await prepareBuildContexts("in-memory");
5
5
  if (isErrored(buildContextsResult)) {
6
6
  return errored({
7
7
  code: "ERROR_PREPARING_BUILD_CONTEXT",
8
- buildContextError: buildContextsResult.error,
8
+ error: buildContextsResult.error,
9
9
  });
10
10
  }
11
11
  const buildContexts = buildContextsResult.value;
@@ -21,7 +21,7 @@ export async function bundleJavaScript() {
21
21
  if (isErrored(disposeResults)) {
22
22
  return errored({
23
23
  code: "ERROR_DISPOSING_BUILD_CONTEXT",
24
- buildContextError: disposeResults.error,
24
+ error: disposeResults.error,
25
25
  });
26
26
  }
27
27
  return complete(bundles);