attio 0.0.1-experimental.20250402.1 → 0.0.1-experimental.20250403

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 (50) hide show
  1. package/lib/api/api-fetch.js +32 -0
  2. package/lib/api/auth.js +63 -61
  3. package/lib/api/complete-bundle-upload.js +3 -16
  4. package/lib/api/complete-prod-bundle-upload.js +3 -16
  5. package/lib/api/create-dev-version.js +4 -9
  6. package/lib/api/create-version.js +4 -9
  7. package/lib/api/determine-workspace.spinner.js +23 -30
  8. package/lib/api/ensure-authed.js +3 -20
  9. package/lib/api/fetch-app-info.js +14 -20
  10. package/lib/api/fetch-installation.js +5 -16
  11. package/lib/api/fetch-versions.js +9 -17
  12. package/lib/api/fetch-workspaces.js +10 -18
  13. package/lib/api/get-app-info.spinner.js +5 -16
  14. package/lib/api/get-app-slug-from-package-json.js +4 -3
  15. package/lib/api/get-versions.spinner.js +5 -19
  16. package/lib/api/hard-exit.js +6 -0
  17. package/lib/api/keychain.js +29 -7
  18. package/lib/api/start-upload.js +7 -14
  19. package/lib/api/whoami.js +3 -9
  20. package/lib/commands/build/build-javascript.js +0 -2
  21. package/lib/commands/build.js +8 -23
  22. package/lib/commands/dev/boot.js +4 -8
  23. package/lib/commands/dev/build-javascript.js +0 -2
  24. package/lib/commands/dev/bundle-javascript.js +41 -4
  25. package/lib/commands/dev/graphql-code-gen.js +3 -2
  26. package/lib/commands/dev/onboarding.js +2 -2
  27. package/lib/commands/dev/upload.js +8 -40
  28. package/lib/commands/dev/validate-typescript.js +0 -4
  29. package/lib/commands/dev.js +47 -11
  30. package/lib/commands/init/create-project.js +16 -25
  31. package/lib/commands/init.js +4 -7
  32. package/lib/commands/login.js +7 -11
  33. package/lib/commands/logout.js +4 -10
  34. package/lib/commands/version/create.js +26 -51
  35. package/lib/commands/version/list.js +36 -25
  36. package/lib/commands/whoami.js +8 -12
  37. package/lib/util/copy-with-replace.js +3 -2
  38. package/lib/util/create-directory.js +3 -2
  39. package/lib/util/find-available-port.js +2 -1
  40. package/lib/util/load-attio-cli-package-json.js +3 -2
  41. package/lib/util/spinner.js +13 -1
  42. package/lib/util/upload-bundle.js +16 -0
  43. package/package.json +1 -1
  44. package/lib/api/fetch-connections.js +0 -20
  45. package/lib/api/handle-error.js +0 -18
  46. package/lib/api/make-headers.js +0 -7
  47. package/lib/api/remove-connection-definition.js +0 -10
  48. package/lib/commands/init/boot.js +0 -14
  49. package/lib/commands/version/create/boot.js +0 -9
  50. package/lib/util/clear-terminal.js +0 -4
@@ -1,76 +1,51 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
- import { Spinner } from "../../util/spinner.js";
4
- import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
3
+ import { spinnerify } from "../../util/spinner.js";
4
+ import { buildJavaScript } from "../dev/build-javascript.js";
5
5
  import { createVersion } from "../../api/create-version.js";
6
- import { fetchVersions } from "../../api/fetch-versions.js";
7
6
  import { completeProdBundleUpload } from "../../api/complete-prod-bundle-upload.js";
8
- import { buildJavaScript } from "../dev/build-javascript.js";
9
- import { boot } from "./create/boot.js";
7
+ import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
8
+ import { uploadBundle } from "../../util/upload-bundle.js";
9
+ import { getVersions } from "../../api/get-versions.spinner.js";
10
+ import { getAppInfo } from "../../api/get-app-info.spinner.js";
11
+ import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
10
12
  export const versionCreate = new Command("create")
11
13
  .description("Create a new unpublished version of your Attio app")
12
14
  .action(async () => {
13
- try {
14
- const { token, appInfo } = await boot();
15
- const bundlingSpinner = new Spinner();
16
- bundlingSpinner.start("Bundling JavaScript...");
17
- const [clientBundle, serverBundle] = await new Promise((resolve) => {
15
+ const appSlug = await getAppSlugFromPackageJson();
16
+ const appInfo = await getAppInfo(appSlug);
17
+ const [clientBundle, serverBundle] = await spinnerify("Bundling JavaScript...", "Bundling complete", async () => {
18
+ return await new Promise((resolve) => {
18
19
  const cleanup = buildJavaScript((bundles) => {
19
20
  cleanup();
20
- bundlingSpinner.success("Bundling complete");
21
21
  resolve(bundles);
22
22
  });
23
23
  });
24
- const versions = await fetchVersions({
25
- token,
26
- appId: appInfo.app_id,
27
- });
28
- const uploadSpinner = new Spinner();
29
- uploadSpinner.start("Uploading...");
24
+ });
25
+ const versions = await getVersions(appInfo);
26
+ const version = await spinnerify("Uploading...", "Upload complete", async () => {
30
27
  const packageJson = loadAttioCliPackageJson();
31
- if (typeof packageJson === "string")
32
- throw packageJson;
33
- const major = versions.length === 0 ? 1 : Math.max(...versions.map((version) => version.major), 1);
34
28
  const version = await createVersion({
35
- token,
36
29
  appId: appInfo.app_id,
37
- major,
30
+ major: versions.length === 0
31
+ ? 1
32
+ : Math.max(...versions.map((version) => version.major), 1),
38
33
  cliVersion: packageJson.version,
39
34
  });
40
35
  await Promise.all([
41
- fetch(version.client_bundle_upload_url, {
42
- method: "PUT",
43
- body: clientBundle,
44
- headers: {
45
- "Content-Type": "text/javascript",
46
- "Content-Length": String(Buffer.from(clientBundle).length),
47
- },
48
- }),
49
- fetch(version.server_bundle_upload_url, {
50
- method: "PUT",
51
- body: serverBundle,
52
- headers: {
53
- "Content-Type": "text/javascript",
54
- "Content-Length": String(Buffer.from(serverBundle).length),
55
- },
56
- }),
36
+ uploadBundle(clientBundle, version.client_bundle_upload_url),
37
+ uploadBundle(serverBundle, version.server_bundle_upload_url),
57
38
  ]);
58
- uploadSpinner.success("Upload complete");
59
- const signSpinner = new Spinner();
60
- signSpinner.start("Signing bundles...");
39
+ return version;
40
+ });
41
+ await spinnerify("Signing bundles...", "Bundles signed", async () => {
61
42
  await completeProdBundleUpload({
62
- token,
63
- appId: version.app_id,
43
+ appId: appInfo.app_id,
64
44
  major: version.major,
65
45
  minor: version.minor,
66
46
  bundleId: version.app_prod_version_bundle_id,
67
47
  });
68
- signSpinner.success("Bundles signed");
69
- process.stdout.write(`\nVersion ${chalk.green(`${version.major}.${version.minor}`)} created!\n\n`);
70
- process.exit(0);
71
- }
72
- catch (error) {
73
- process.stderr.write(`${chalk.red("✖ " + error)}\n`);
74
- process.exit(1);
75
- }
48
+ });
49
+ process.stdout.write(`\nVersion ${chalk.green(`${version.major}.${version.minor}`)} created!\n\n`);
50
+ process.exit(0);
76
51
  });
@@ -1,36 +1,47 @@
1
1
  import { Command } from "commander";
2
- import { fetchVersions } from "../../api/fetch-versions.js";
3
- import Table from "cli-table3";
4
- import chalk from "chalk";
5
- import formatDate from "date-fns/format/index.js";
6
- import { ensureAuthed } from "../../api/ensure-authed.js";
7
- import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
8
2
  import { getAppInfo } from "../../api/get-app-info.spinner.js";
9
- export const versionList = new Command("list")
10
- .description("List all versions of your Attio app")
11
- .alias("ls")
3
+ import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
4
+ import { getVersions } from "../../api/get-versions.spinner.js";
5
+ import chalk from "chalk";
6
+ import Table from "cli-table3";
7
+ import { format as formatDate } from "date-fns";
8
+ export const versionList = new Command()
9
+ .name("list")
10
+ .description("List all versions of your app")
12
11
  .action(async () => {
13
- const token = await ensureAuthed();
14
12
  const appSlug = await getAppSlugFromPackageJson();
15
- const appInfo = await getAppInfo({ token, appSlug });
16
- const versions = await fetchVersions({
17
- token,
18
- appId: appInfo.app_id,
19
- });
13
+ const appInfo = await getAppInfo(appSlug);
14
+ const versions = await getVersions(appInfo);
15
+ if (versions.length === 0) {
16
+ process.stdout.write("No versions found\n");
17
+ process.exit(0);
18
+ }
20
19
  const table = new Table({
21
- head: ["Version", "Published", "Installations", "Created"].map((h) => chalk.bold(h)),
20
+ head: ["Version", "Status", "Published", "Installations", "Created"].map((h) => chalk.bold(h)),
22
21
  style: {
23
22
  head: [],
24
23
  border: [],
25
24
  },
26
- colAligns: ["center", "center", "right", "left"],
25
+ colAligns: ["center", "left", "center", "right", "left"],
26
+ });
27
+ versions.forEach((version) => {
28
+ const statusColors = {
29
+ private: chalk.gray,
30
+ in_review: chalk.yellow,
31
+ published: chalk.green,
32
+ rejected: chalk.red,
33
+ };
34
+ const formattedStatus = version.publication_status
35
+ .split("_")
36
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
37
+ .join(" ");
38
+ table.push([
39
+ `${version.major}.${version.minor}`,
40
+ statusColors[version.publication_status](formattedStatus),
41
+ version.is_published ? "Yes" : "No",
42
+ version.num_installations.toLocaleString(),
43
+ formatDate(new Date(version.created_at), "MMM d, yyyy, HH:mm"),
44
+ ]);
27
45
  });
28
- table.push(...versions.map((version) => [
29
- `${version.major}.${version.minor}`,
30
- version.is_published ? "Yes" : "No",
31
- version.num_installations.toLocaleString(),
32
- formatDate(new Date(version.created_at), "MMMM d, yyyy, HH:mm"),
33
- ]));
34
- process.stdout.write(table.toString());
35
- process.exit(0);
46
+ process.stdout.write("\n" + table.toString() + "\n");
36
47
  });
@@ -1,20 +1,16 @@
1
1
  import { Command } from "commander";
2
- import { auth as authApi } from "../api/auth.js";
3
2
  import { whoami as whoamiApi } from "../api/whoami.js";
4
- export const whoami = new Command("whoami")
3
+ import { loadAuthToken } from "../api/keychain.js";
4
+ export const whoami = new Command()
5
+ .name("whoami")
5
6
  .description("Identify the current user")
6
7
  .action(async () => {
7
- const token = await authApi();
8
- if (!token) {
8
+ const tokenResult = await loadAuthToken();
9
+ if (!tokenResult) {
9
10
  process.stdout.write("🔒 Not logged in.\n");
10
11
  process.exit(0);
11
12
  }
12
- try {
13
- const user = await whoamiApi({ token });
14
- process.stdout.write(`👤 ${user.name.full} (${user.email_address})\n`);
15
- }
16
- catch (error) {
17
- process.stderr.write(`❌ User lookup failed\n\n${error}\n`);
18
- process.exit(1);
19
- }
13
+ const user = await whoamiApi();
14
+ process.stdout.write(`👤 ${user.name.full} (${user.email_address})\n`);
15
+ process.exit(0);
20
16
  });
@@ -1,11 +1,12 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
+ import { hardExit } from "../api/hard-exit.js";
3
4
  export const copyWithTransform = async (srcDir, destDir, transform) => {
4
5
  try {
5
6
  await fs.mkdir(destDir, { recursive: true });
6
7
  }
7
8
  catch {
8
- throw new Error(`Failed to create "${destDir}".`);
9
+ hardExit(`Failed to create "${destDir}".`);
9
10
  }
10
11
  const entries = await fs.readdir(srcDir, { withFileTypes: true });
11
12
  await Promise.all(entries.map(async (entry) => {
@@ -27,7 +28,7 @@ export const copyWithTransform = async (srcDir, destDir, transform) => {
27
28
  }
28
29
  }
29
30
  catch {
30
- throw new Error(`Failed to copy "${srcPath}" to "${destPath}"`);
31
+ hardExit(`Failed to copy "${srcPath}" to "${destPath}"`);
31
32
  }
32
33
  }));
33
34
  };
@@ -1,10 +1,11 @@
1
1
  import { accessSync, constants, existsSync, mkdirSync } from "fs";
2
2
  import { join } from "path";
3
+ import { hardExit } from "../api/hard-exit.js";
3
4
  export const createDirectory = (name) => {
4
5
  const currentDir = process.cwd();
5
6
  const newPath = join(currentDir, name);
6
7
  if (existsSync(newPath)) {
7
- throw new Error(`The directory '${name}' already exists.`);
8
+ hardExit(`The directory '${name}' already exists.`);
8
9
  }
9
10
  else {
10
11
  try {
@@ -13,7 +14,7 @@ export const createDirectory = (name) => {
13
14
  return newPath;
14
15
  }
15
16
  catch {
16
- throw new Error(`Write access to create the directory "${name}" in the current directory is denied.`);
17
+ hardExit(`Write access to create the directory "${name}" in the current directory is denied.`);
17
18
  }
18
19
  }
19
20
  };
@@ -1,4 +1,5 @@
1
1
  import net from "net";
2
+ import { hardExit } from "../api/hard-exit.js";
2
3
  async function isPortAvailable(port) {
3
4
  return new Promise((resolve) => {
4
5
  const portTester = net.createConnection(port, "127.0.0.1");
@@ -32,5 +33,5 @@ export async function findAvailablePort(startPort, maxAttempts = 100) {
32
33
  return port;
33
34
  }
34
35
  }
35
- throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
36
+ hardExit(`Could not find an available port after ${maxAttempts} attempts`);
36
37
  }
@@ -2,6 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { findUpSync } from "find-up-simple";
3
3
  import { z } from "zod";
4
4
  import { fileURLToPath } from "url";
5
+ import { hardExit } from "../api/hard-exit.js";
5
6
  const FILE_NAME = "package.json";
6
7
  const packageJsonSchema = z.object({
7
8
  version: z.string(),
@@ -12,14 +13,14 @@ export function loadAttioCliPackageJson() {
12
13
  if (packageJson === undefined) {
13
14
  const packageJsonPath = findUpSync(FILE_NAME, { cwd: fileURLToPath(import.meta.url) });
14
15
  if (packageJsonPath === undefined) {
15
- throw new Error("Failed to find package.json");
16
+ hardExit("Failed to find package.json");
16
17
  }
17
18
  try {
18
19
  const packageJsonContent = JSON.parse(readFileSync(packageJsonPath, "utf8"));
19
20
  packageJson = packageJsonSchema.parse(packageJsonContent);
20
21
  }
21
22
  catch (error) {
22
- throw new Error("Failed to find version in package.json");
23
+ hardExit("Failed to find version in package.json");
23
24
  }
24
25
  }
25
26
  return packageJson;
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
3
- export class Spinner {
3
+ class Spinner {
4
4
  frameIndex = 0;
5
5
  stopped = false;
6
6
  interval = null;
@@ -44,3 +44,15 @@ export class Spinner {
44
44
  return this.stop(`${chalk.yellow("⚠")} ${message}`);
45
45
  }
46
46
  }
47
+ export async function spinnerify(busyMessage, successMessage, fn) {
48
+ const spinner = new Spinner();
49
+ spinner.start(busyMessage);
50
+ try {
51
+ const result = await fn();
52
+ spinner.success(typeof successMessage === "string" ? successMessage : successMessage(result));
53
+ return result;
54
+ }
55
+ finally {
56
+ spinner.stop();
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ import { hardExit } from "../api/hard-exit.js";
2
+ export async function uploadBundle(bundle, uploadUrl) {
3
+ try {
4
+ await fetch(uploadUrl, {
5
+ method: "PUT",
6
+ body: bundle,
7
+ headers: {
8
+ "Content-Type": "text/javascript",
9
+ "Content-Length": String(Buffer.from(bundle).length),
10
+ },
11
+ });
12
+ }
13
+ catch (error) {
14
+ hardExit(`Failed to upload bundle: ${error}`);
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20250402.1",
3
+ "version": "0.0.1-experimental.20250403",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { API } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const connectionDefinitionsResponseSchema = z.object({
6
- values: z.array(z.object({
7
- label: z.string(),
8
- connection_type: z.enum(["oauth2-code", "secret"]),
9
- description: z.string().nullable(),
10
- global: z.boolean(),
11
- })),
12
- });
13
- export async function fetchConnections({ token, appId, major, }) {
14
- const response = await fetch(`${API}/apps/${appId}/versions/${major}/connection-definitions`, {
15
- method: "GET",
16
- headers: makeHeaders(token),
17
- });
18
- await handleError(response);
19
- return connectionDefinitionsResponseSchema.parse(await response.json()).values;
20
- }
@@ -1,18 +0,0 @@
1
- import { z } from "zod";
2
- const serverErrorSchema = z.object({
3
- message: z.string(),
4
- });
5
- export async function handleError(response) {
6
- if (response.ok)
7
- return;
8
- const text = await response.text();
9
- let json;
10
- try {
11
- json = JSON.parse(text);
12
- }
13
- catch {
14
- throw new Error(`Error parsing JSON: ${JSON.stringify(text)}`);
15
- }
16
- const error = serverErrorSchema.parse(json);
17
- throw new Error(error.message);
18
- }
@@ -1,7 +0,0 @@
1
- export function makeHeaders(token) {
2
- return {
3
- "x-attio-platform": "developer-cli",
4
- "Authorization": `Bearer ${token}`,
5
- "Content-Type": "application/json",
6
- };
7
- }
@@ -1,10 +0,0 @@
1
- import { API } from "../env.js";
2
- import { handleError } from "./handle-error.js";
3
- import { makeHeaders } from "./make-headers.js";
4
- export async function removeConnectionDefinition({ token, appId, global, major, }) {
5
- const response = await fetch(`${API}/apps/${appId}/versions/${major}/connection-definitions/${global ? "workspace" : "user"}/remove`, {
6
- method: "DELETE",
7
- headers: makeHeaders(token),
8
- });
9
- await handleError(response);
10
- }
@@ -1,14 +0,0 @@
1
- import { ensureAuthed } from "../../api/ensure-authed.js";
2
- import { getAppInfo } from "../../api/get-app-info.spinner.js";
3
- import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
4
- export async function boot({ workspaceSlug, appSlug, }) {
5
- const token = await ensureAuthed();
6
- const appInfo = await getAppInfo({ token, appSlug });
7
- const workspace = await determineWorkspace({ token, workspaceSlug });
8
- return {
9
- token,
10
- appId: appInfo.app_id,
11
- appInfo,
12
- workspace,
13
- };
14
- }
@@ -1,9 +0,0 @@
1
- import { ensureAuthed } from "../../../api/ensure-authed.js";
2
- import { getAppSlugFromPackageJson } from "../../../api/get-app-slug-from-package-json.js";
3
- import { getAppInfo } from "../../../api/get-app-info.spinner.js";
4
- export async function boot() {
5
- const token = await ensureAuthed();
6
- const appSlug = await getAppSlugFromPackageJson();
7
- const appInfo = await getAppInfo({ token, appSlug });
8
- return { token, appInfo };
9
- }
@@ -1,4 +0,0 @@
1
- export function clearTerminal() {
2
- process.stdout.write("\x1b[2J");
3
- process.stdout.write("\x1b[H");
4
- }