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,39 +0,0 @@
1
- import { select } from "@inquirer/prompts";
2
- import { fetchWorkspaces } from "./fetch-workspaces.js";
3
- import { APP } from "../env.js";
4
- import { Spinner } from "../util/spinner.js";
5
- export async function determineWorkspace({ token, workspaceSlug, }) {
6
- const spinner = new Spinner();
7
- spinner.start("Loading workspaces...");
8
- try {
9
- const workspaces = await fetchWorkspaces({ token });
10
- spinner.success("Workspaces loaded");
11
- const workspace = workspaces.find((workspace) => workspace.slug === workspaceSlug);
12
- if (workspace) {
13
- spinner.success(`Using workspace: ${workspace.name}`);
14
- return workspace;
15
- }
16
- if (workspaces.length === 0) {
17
- throw new Error(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
18
-
19
- ${APP}/welcome/workspace-details
20
- `);
21
- }
22
- if (workspaces.length === 1) {
23
- spinner.success(`Using workspace: ${workspaces[0].name}`);
24
- return workspaces[0];
25
- }
26
- const choice = await select({
27
- message: "Choose a workspace",
28
- choices: workspaces.map((workspace) => ({
29
- name: workspace.name,
30
- value: workspace,
31
- })),
32
- });
33
- spinner.success(`Using workspace: ${choice.name}`);
34
- return choice;
35
- }
36
- finally {
37
- spinner.stop();
38
- }
39
- }
@@ -1,31 +0,0 @@
1
- import { auth as authApi } from "./auth.js";
2
- import { deleteAuthToken, loadAuthToken } from "./keychain.js";
3
- import { whoami } from "./whoami.js";
4
- async function auth() {
5
- await deleteAuthToken();
6
- if (process.env.NODE_ENV !== "test") {
7
- process.stdout.write("You need to log in with Attio. Press Enter to continue...\n\n");
8
- await new Promise((resolve) => {
9
- process.stdin.once("data", () => {
10
- resolve();
11
- });
12
- });
13
- }
14
- return await authApi();
15
- }
16
- export async function ensureAuthed() {
17
- let token = await loadAuthToken();
18
- if (!token) {
19
- token = await auth();
20
- }
21
- try {
22
- const user = await whoami({ token });
23
- if (!user) {
24
- token = await auth();
25
- }
26
- }
27
- catch (error) {
28
- return await auth();
29
- }
30
- return token;
31
- }
@@ -1,26 +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 isTest = process.env.NODE_ENV === "test";
6
- const appSchema = z.object({
7
- app: z
8
- .object({
9
- app_id: z.string().uuid(),
10
- title: z.string(),
11
- })
12
- .nullable(),
13
- });
14
- export async function fetchAppInfo({ token, appSlug, }) {
15
- if (isTest) {
16
- return {
17
- app_id: "test-id",
18
- title: "Test App",
19
- };
20
- }
21
- const response = await fetch(`${API}/apps/by-slug/${appSlug}`, {
22
- headers: makeHeaders(token),
23
- });
24
- await handleError(response);
25
- return appSchema.parse(await response.json()).app;
26
- }
@@ -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,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 installationSchema = z.object({
6
- workspace_slug: z.string(),
7
- workspace_id: z.string().uuid(),
8
- app_id: z.string().uuid(),
9
- installation_id: z.string().uuid(),
10
- });
11
- export async function fetchInstallation({ token, appId, workspaceId, }) {
12
- const response = await fetch(`${API}/apps/${appId}/workspace/${workspaceId}/dev-installation`, {
13
- method: "GET",
14
- headers: makeHeaders(token),
15
- });
16
- if (response.status === 404)
17
- return null;
18
- await handleError(response);
19
- return installationSchema.parse(await response.json());
20
- }
@@ -1,25 +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 prodVersionSchema = z.object({
6
- app_id: z.string(),
7
- major: z.number(),
8
- minor: z.number(),
9
- is_published: z.boolean(),
10
- num_installations: z.number(),
11
- created_at: z.string(),
12
- updated_at: z.string(),
13
- accurate_at: z.string(),
14
- });
15
- const fetchVersionsSchema = z.object({
16
- app_prod_versions: z.array(prodVersionSchema),
17
- });
18
- export async function fetchVersions({ token, appId, }) {
19
- const response = await fetch(`${API}/apps/${appId}/prod-versions`, {
20
- method: "GET",
21
- headers: makeHeaders(token),
22
- });
23
- await handleError(response);
24
- return fetchVersionsSchema.parse(await response.json()).app_prod_versions;
25
- }
@@ -1,33 +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 isTest = process.env.NODE_ENV === "test";
6
- const workspaceResponseSchema = z.object({
7
- workspace_id: z.string().uuid(),
8
- slug: z.string(),
9
- name: z.string(),
10
- logo_url: z.string().nullable(),
11
- });
12
- const listDevWorkspacesResponseSchema = z.object({
13
- workspaces: z.array(workspaceResponseSchema),
14
- accurate_at: z.string().datetime(),
15
- });
16
- export async function fetchWorkspaces({ token }) {
17
- if (isTest) {
18
- return [
19
- {
20
- workspace_id: "test-id",
21
- slug: "test-slug",
22
- name: "Test Workspace",
23
- logo_url: null,
24
- },
25
- ];
26
- }
27
- const response = await fetch(`${API}/dev-workspaces`, {
28
- method: "GET",
29
- headers: makeHeaders(token),
30
- });
31
- await handleError(response);
32
- return listDevWorkspacesResponseSchema.parse(await response.json()).workspaces;
33
- }
@@ -1,18 +0,0 @@
1
- import { fetchAppInfo } from "./fetch-app-info.js";
2
- import { Spinner } from "../util/spinner.js";
3
- export async function getAppInfo({ token, appSlug, }) {
4
- const spinner = new Spinner();
5
- spinner.start("Loading app information...");
6
- try {
7
- const appInfo = await fetchAppInfo({ token, appSlug });
8
- if (appInfo === null) {
9
- spinner.error("App not found");
10
- throw new Error("App not found");
11
- }
12
- spinner.success(`App found: ${appInfo.title}`);
13
- return appInfo;
14
- }
15
- finally {
16
- spinner.stop();
17
- }
18
- }
@@ -1,26 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import { join } from "path";
3
- import { z } from "zod";
4
- const packageJsonSchema = z.object({
5
- name: z.string({
6
- required_error: "No name field found in package.json",
7
- invalid_type_error: "name must be a string in package.json",
8
- }),
9
- });
10
- export async function getAppSlugFromPackageJson() {
11
- try {
12
- const packageJsonPath = join(process.cwd(), "package.json");
13
- const packageJsonRaw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
14
- const result = packageJsonSchema.safeParse(packageJsonRaw);
15
- if (!result.success) {
16
- throw new Error(result.error.issues[0]?.message || "Malformed package.json");
17
- }
18
- return result.data.name;
19
- }
20
- catch (error) {
21
- if (error instanceof SyntaxError) {
22
- throw new Error("Invalid JSON in package.json");
23
- }
24
- throw new Error("Failed to read package.json");
25
- }
26
- }
@@ -1,21 +0,0 @@
1
- import { fetchVersions } from "./fetch-versions.js";
2
- import { Spinner } from "../util/spinner.js";
3
- export async function getVersions({ token, appInfo, }) {
4
- const spinner = new Spinner();
5
- try {
6
- spinner.start("Loading versions...");
7
- const versions = await fetchVersions({
8
- token,
9
- appId: appInfo.app_id,
10
- });
11
- spinner.success("Versions loaded");
12
- return versions;
13
- }
14
- catch (error) {
15
- spinner.error("Error loading versions");
16
- throw error;
17
- }
18
- finally {
19
- spinner.stop();
20
- }
21
- }
@@ -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,30 +0,0 @@
1
- let keytar = null;
2
- if (process.env.NODE_ENV !== "test") {
3
- try {
4
- keytar = (await import("keytar")).default;
5
- }
6
- catch (e) {
7
- console.warn("Keychain functionality not available");
8
- }
9
- }
10
- const SERVICE_NAME = "attio-cli";
11
- const ACCOUNT_NAME = "developer";
12
- export async function saveAuthToken(token) {
13
- if (!keytar) {
14
- return;
15
- }
16
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
17
- }
18
- export async function loadAuthToken() {
19
- if (!keytar) {
20
- return "TEST";
21
- }
22
- const token = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
23
- return token || null;
24
- }
25
- export async function deleteAuthToken() {
26
- if (!keytar) {
27
- return true;
28
- }
29
- return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
30
- }
@@ -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,18 +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 startUploadSchema = z.object({
6
- app_dev_version_id: z.string(),
7
- app_dev_version_bundle_id: z.string(),
8
- client_bundle_upload_url: z.string(),
9
- server_bundle_upload_url: z.string(),
10
- });
11
- export async function startUpload({ token, appId, devVersionId, }) {
12
- const response = await fetch(`${API}/apps/${appId}/dev-versions/${devVersionId}/bundles`, {
13
- method: "POST",
14
- headers: makeHeaders(token),
15
- });
16
- await handleError(response);
17
- return startUploadSchema.parse(await response.json());
18
- }
package/lib/api/whoami.js DELETED
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
- import { APP } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const whoamiSchema = z.object({
6
- user: z.object({
7
- id: z.string(),
8
- email_address: z.string(),
9
- name: z.object({
10
- full: z.string(),
11
- }),
12
- }),
13
- });
14
- export async function whoami({ token }) {
15
- const response = await fetch(`${APP}/api/auth/whoami`, {
16
- method: "GET",
17
- headers: makeHeaders(token),
18
- });
19
- await handleError(response);
20
- return whoamiSchema.parse(await response.json()).user;
21
- }
@@ -1,49 +0,0 @@
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 buildJavaScript(onSuccess) {
15
- const watcher = chokidar.watch(["src/**/*.{js,jsx,ts,tsx}"], {
16
- ignored: ["**/node_modules/**", "**/dist/**", "**/*.graphql.d.ts", "**/*.gql.d.ts"],
17
- cwd: ".",
18
- });
19
- let buildContexts;
20
- async function handleBuild() {
21
- try {
22
- if (!buildContexts) {
23
- buildContexts = await prepareBuildContexts("in-memory");
24
- }
25
- const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
26
- const bundles = results.map((result) => result.outputFiles[0].text);
27
- onSuccess?.(bundles);
28
- }
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);
35
- }
36
- }
37
- watcher.on("ready", () => {
38
- handleBuild();
39
- watcher.on("all", (event) => {
40
- if (event === "add" || event === "change" || event === "unlink") {
41
- handleBuild();
42
- }
43
- });
44
- });
45
- return async () => {
46
- await Promise.all(buildContexts?.map(async (context) => context.dispose()) ?? []);
47
- await watcher.close();
48
- };
49
- }
@@ -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
- }
@@ -1,26 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import { findUpSync } from "find-up-simple";
3
- import { z } from "zod";
4
- import { fileURLToPath } from "url";
5
- const FILE_NAME = "package.json";
6
- const packageJsonSchema = z.object({
7
- version: z.string(),
8
- name: z.literal("attio"),
9
- });
10
- let packageJson;
11
- export function loadAttioCliPackageJson() {
12
- if (packageJson === undefined) {
13
- const packageJsonPath = findUpSync(FILE_NAME, { cwd: fileURLToPath(import.meta.url) });
14
- if (packageJsonPath === undefined) {
15
- throw new Error("Failed to find package.json");
16
- }
17
- try {
18
- const packageJsonContent = JSON.parse(readFileSync(packageJsonPath, "utf8"));
19
- packageJson = packageJsonSchema.parse(packageJsonContent);
20
- }
21
- catch (error) {
22
- throw new Error("Failed to find version in package.json");
23
- }
24
- }
25
- return packageJson;
26
- }