attio 0.0.1-experimental.20250328.1 → 0.0.1-experimental.20250401

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 (35) hide show
  1. package/lib/api/determine-workspace.spinner.js +39 -0
  2. package/lib/api/{get-app-info.js → fetch-app-info.js} +1 -1
  3. package/lib/api/fetch-workspaces.js +11 -0
  4. package/lib/api/get-app-info.spinner.js +18 -0
  5. package/lib/api/get-app-slug-from-package-json.js +26 -0
  6. package/lib/api/get-versions.spinner.js +21 -0
  7. package/lib/commands/build/build-javascript.js +24 -0
  8. package/lib/commands/build/validate-typescript.js +30 -0
  9. package/lib/commands/build.js +32 -16
  10. package/lib/commands/dev/boot.js +4 -4
  11. package/lib/commands/dev/build-javascript.js +2 -2
  12. package/lib/commands/dev/bundle-javascript.js +1 -1
  13. package/lib/commands/dev/prepare-build-contexts.js +3 -3
  14. package/lib/commands/init/ask-language.js +11 -0
  15. package/lib/commands/init/boot.js +14 -0
  16. package/lib/commands/init/create-project.js +50 -0
  17. package/lib/commands/init.js +21 -16
  18. package/lib/commands/login.js +2 -0
  19. package/lib/commands/logout.js +2 -0
  20. package/lib/commands/version/create/boot.js +9 -0
  21. package/lib/commands/version/create.js +71 -15
  22. package/lib/commands/version/list.js +4 -2
  23. package/lib/commands/whoami.js +2 -1
  24. package/lib/util/print-message.js +3 -2
  25. package/package.json +1 -4
  26. package/lib/machines/actions.js +0 -8
  27. package/lib/machines/actors.js +0 -123
  28. package/lib/machines/build-machine.js +0 -192
  29. package/lib/machines/build-orchestrator.js +0 -1
  30. package/lib/machines/code-gen-machine.js +0 -97
  31. package/lib/machines/create-version-machine.js +0 -308
  32. package/lib/machines/env-machine.js +0 -82
  33. package/lib/machines/init-machine.js +0 -192
  34. package/lib/machines/js-machine.js +0 -231
  35. package/lib/machines/ts-machine.js +0 -105
@@ -0,0 +1,39 @@
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
+ }
@@ -11,7 +11,7 @@ const appSchema = z.object({
11
11
  })
12
12
  .nullable(),
13
13
  });
14
- export async function getAppInfo({ token, appSlug, }) {
14
+ export async function fetchAppInfo({ token, appSlug, }) {
15
15
  if (isTest) {
16
16
  return {
17
17
  app_id: "test-id",
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { API } from "../env.js";
3
3
  import { handleError } from "./handle-error.js";
4
4
  import { makeHeaders } from "./make-headers.js";
5
+ const isTest = process.env.NODE_ENV === "test";
5
6
  const workspaceResponseSchema = z.object({
6
7
  workspace_id: z.string().uuid(),
7
8
  slug: z.string(),
@@ -13,6 +14,16 @@ const listDevWorkspacesResponseSchema = z.object({
13
14
  accurate_at: z.string().datetime(),
14
15
  });
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
+ }
16
27
  const response = await fetch(`${API}/dev-workspaces`, {
17
28
  method: "GET",
18
29
  headers: makeHeaders(token),
@@ -0,0 +1,18 @@
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
+ }
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,21 @@
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
+ }
@@ -0,0 +1,24 @@
1
+ import { clearTerminal } from "../../util/clear-terminal.js";
2
+ import { prepareBuildContexts } from "../dev/prepare-build-contexts.js";
3
+ import { printJsError } from "../../util/typescript.js";
4
+ import { errorsAndWarningsSchema } from "../../build.js";
5
+ export async function buildJavaScript() {
6
+ let buildContexts;
7
+ try {
8
+ buildContexts = await prepareBuildContexts("write-to-disk");
9
+ await Promise.all(buildContexts.map(async (context) => context.rebuild()));
10
+ return true;
11
+ }
12
+ catch (error) {
13
+ const errors = errorsAndWarningsSchema.parse(error);
14
+ clearTerminal();
15
+ errors.errors?.forEach((error) => printJsError(error, "error"));
16
+ errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
17
+ return false;
18
+ }
19
+ finally {
20
+ if (buildContexts) {
21
+ await Promise.all(buildContexts.map(async (context) => context.dispose()));
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ import path from "path";
2
+ import { getDiagnostics, readConfig, typeScriptErrorSchema, printTsError, } from "../../util/typescript.js";
3
+ export async function validateTypeScript() {
4
+ try {
5
+ const program = await readConfig(path.resolve("tsconfig.json"));
6
+ if (program === "Not a TypeScript project") {
7
+ return true;
8
+ }
9
+ const errors = await getDiagnostics(program);
10
+ if (errors.length) {
11
+ errors.forEach(printTsError);
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+ catch (error) {
17
+ if (error instanceof Error) {
18
+ const tsError = typeScriptErrorSchema.parse({ text: error.message });
19
+ printTsError(tsError);
20
+ return false;
21
+ }
22
+ if (typeof error === "object" &&
23
+ error !== null &&
24
+ "code" in error &&
25
+ (error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM")) {
26
+ throw error;
27
+ }
28
+ return false;
29
+ }
30
+ }
@@ -1,20 +1,36 @@
1
- import { Command, Option } from "commander";
2
- import { createActor } from "xstate";
3
- import { z } from "zod";
4
- import { buildMachine } from "../machines/build-machine.js";
5
- export const optionsSchema = z.object({
6
- dev: z.boolean().default(false),
7
- });
1
+ import { Command } from "commander";
2
+ import { validateTypeScript } from "./build/validate-typescript.js";
3
+ import { buildJavaScript } from "./build/build-javascript.js";
4
+ import { Spinner } from "../util/spinner.js";
8
5
  export const build = new Command("build")
9
6
  .description("Build your Attio extension locally")
10
- .addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
11
- .action((unparsedOptions) => {
12
- const options = optionsSchema.parse(unparsedOptions);
13
- const actor = createActor(buildMachine);
14
- if (options.dev) {
15
- actor.subscribe((state) => {
16
- console.log("state:", state.value);
17
- });
7
+ .action(async (unparsedOptions) => {
8
+ const spinner = new Spinner();
9
+ spinner.start("Validating TypeScript...");
10
+ try {
11
+ const tsValid = await validateTypeScript();
12
+ if (!tsValid) {
13
+ spinner.error("TypeScript validation failed");
14
+ process.exit(1);
15
+ }
16
+ spinner.success("TypeScript validation passed");
17
+ }
18
+ catch (error) {
19
+ spinner.error(`TypeScript validation failed: ${error}`);
20
+ process.exit(1);
21
+ }
22
+ spinner.start("Building JavaScript...");
23
+ try {
24
+ const jsBuilt = await buildJavaScript();
25
+ if (!jsBuilt) {
26
+ spinner.error("JavaScript build failed");
27
+ process.exit(1);
28
+ }
29
+ spinner.success("Build completed successfully");
30
+ process.exit(0);
31
+ }
32
+ catch (error) {
33
+ spinner.error(`JavaScript build failed: ${error}`);
34
+ process.exit(1);
18
35
  }
19
- actor.start();
20
36
  });
@@ -1,14 +1,14 @@
1
1
  import { ensureAuthed } from "../../api/ensure-authed.js";
2
2
  import { loadEnv } from "../../util/load-env.js";
3
- import { determineWorkspace } from "../../machines/actors.js";
4
- import { fetchAppInfo } from "../../machines/actors.js";
5
- import { getAppSlugFromPackageJson } from "../../machines/actors.js";
3
+ import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
6
4
  import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
7
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";
8
8
  export async function boot({ workspaceSlug }) {
9
9
  const token = await ensureAuthed();
10
10
  const appSlug = await getAppSlugFromPackageJson();
11
- const appInfo = await fetchAppInfo({ token, appSlug });
11
+ const appInfo = await getAppInfo({ token, appSlug });
12
12
  const workspace = await determineWorkspace({ token, workspaceSlug });
13
13
  const environmentVariables = await loadEnv();
14
14
  const packageJson = loadAttioCliPackageJson();
@@ -20,7 +20,7 @@ export function buildJavaScript(onSuccess) {
20
20
  async function handleBuild() {
21
21
  try {
22
22
  if (!buildContexts) {
23
- buildContexts = await prepareBuildContexts();
23
+ buildContexts = await prepareBuildContexts("in-memory");
24
24
  }
25
25
  const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
26
26
  const bundles = results.map((result) => result.outputFiles[0].text);
@@ -36,7 +36,7 @@ export function buildJavaScript(onSuccess) {
36
36
  }
37
37
  watcher.on("ready", () => {
38
38
  handleBuild();
39
- watcher.on("all", (event, path) => {
39
+ watcher.on("all", (event) => {
40
40
  if (event === "add" || event === "change" || event === "unlink") {
41
41
  handleBuild();
42
42
  }
@@ -20,7 +20,7 @@ export function bundleJavaScript(onSuccess) {
20
20
  async function handleBuild() {
21
21
  try {
22
22
  if (!buildContexts) {
23
- buildContexts = await prepareBuildContexts();
23
+ buildContexts = await prepareBuildContexts("in-memory");
24
24
  }
25
25
  const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
26
26
  const bundles = results.map((result) => result.outputFiles[0].text);
@@ -6,7 +6,7 @@ import { createClientBuildConfig } from "../../build/client/create-client-build-
6
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
- export async function prepareBuildContexts() {
9
+ export async function prepareBuildContexts(mode) {
10
10
  const srcDir = "src";
11
11
  const assetsDir = path.join(srcDir, "assets");
12
12
  const webhooksDir = path.join(srcDir, "webhooks");
@@ -29,7 +29,7 @@ export async function prepareBuildContexts() {
29
29
  entryPoint: tempFile.path,
30
30
  srcDir,
31
31
  }),
32
- write: false,
32
+ write: mode === "write-to-disk",
33
33
  outfile: path.resolve("dist", "index.js"),
34
34
  loader: { ".png": "dataurl", ".graphql": "text", ".gql": "text" },
35
35
  });
@@ -58,7 +58,7 @@ export async function prepareBuildContexts() {
58
58
  };
59
59
  const esbuildContext = await esbuild.context({
60
60
  ...createServerBuildConfig(tempFile.path),
61
- write: false,
61
+ write: mode === "write-to-disk",
62
62
  outfile: path.resolve("dist", "server.js"),
63
63
  });
64
64
  return {
@@ -0,0 +1,11 @@
1
+ import { select } from "@inquirer/prompts";
2
+ export const LANGUAGES = [
3
+ { name: "TypeScript (recommended)", value: "typescript" },
4
+ { name: "JavaScript", value: "javascript" },
5
+ ];
6
+ export async function askLanguage() {
7
+ return select({
8
+ message: "What language would you like to use?",
9
+ choices: LANGUAGES,
10
+ });
11
+ }
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,50 @@
1
+ import { fileURLToPath } from "url";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import boxen from "boxen";
5
+ import { existsSync } from "fs";
6
+ import { createDirectory } from "../../util/create-directory.js";
7
+ import { canWrite } from "../../util/validate-slug.js";
8
+ import { copyWithTransform } from "../../util/copy-with-replace.js";
9
+ import { printMessage } from "../../util/print-message.js";
10
+ import { Spinner } from "../../util/spinner.js";
11
+ export async function createProject({ appSlug, language, appInfo, appId, }) {
12
+ const spinner = new Spinner();
13
+ if (existsSync(path.join(process.cwd(), appSlug))) {
14
+ throw new Error(`Directory "${appSlug}" already exists`);
15
+ }
16
+ if (!canWrite(process.cwd())) {
17
+ throw new Error("Write access denied to current directory");
18
+ }
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const projectDir = createDirectory(appSlug);
21
+ const templatesDir = path.resolve(__dirname, "../../templates", language);
22
+ const commonDir = path.resolve(__dirname, "../../templates", "common");
23
+ spinner.start("Creating project...");
24
+ try {
25
+ const transform = (contents) => contents
26
+ .replaceAll("title-to-be-replaced", appInfo.title)
27
+ .replaceAll("id-to-be-replaced", appId)
28
+ .replaceAll("slug-to-be-replaced", appSlug);
29
+ await Promise.all([
30
+ copyWithTransform(templatesDir, projectDir, transform),
31
+ copyWithTransform(commonDir, projectDir, transform),
32
+ ]);
33
+ spinner.success("Project created");
34
+ printMessage("\n" + chalk.green(`SUCCESS!! 🎉 Your app directory has been created.`));
35
+ printMessage("\nTo get started, run:\n");
36
+ printMessage(boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
37
+ padding: 1,
38
+ margin: 1,
39
+ borderStyle: "round",
40
+ }) + "\n");
41
+ printMessage(`(${chalk.yellow("yarn")}, ${chalk.yellow("pnpm")}, and ${chalk.yellow("bun")} also work!)\n`);
42
+ }
43
+ catch (error) {
44
+ spinner.error("Error creating project");
45
+ throw error;
46
+ }
47
+ finally {
48
+ spinner.stop();
49
+ }
50
+ }
@@ -1,30 +1,35 @@
1
1
  import { Argument, Command, Option } from "commander";
2
- import { createActor } from "xstate";
3
2
  import { z } from "zod";
4
- import { initMachine } from "../machines/init-machine.js";
3
+ import chalk from "chalk";
4
+ import { boot } from "./init/boot.js";
5
+ import { createProject } from "./init/create-project.js";
6
+ import { askLanguage } from "./init/ask-language.js";
5
7
  export const argsSchema = z.string();
6
8
  export const optionsSchema = z.object({
7
9
  language: z.enum(["javascript", "typescript"]).optional(),
8
- dev: z.boolean().default(false),
10
+ workspace: z.string().optional(),
9
11
  });
10
12
  export const init = new Command("init")
11
13
  .description("Initialize a new Attio app")
12
14
  .addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
13
15
  .addOption(new Option("--language <language>", "Language").choices(["javascript", "typescript"]))
14
- .addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
15
- .action((unparsedArgs, unparsedOptions) => {
16
- const appSlug = argsSchema.parse(unparsedArgs);
17
- const options = optionsSchema.parse(unparsedOptions);
18
- const actor = createActor(initMachine, {
19
- input: {
16
+ .addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
17
+ .action(async (unparsedArgs, unparsedOptions) => {
18
+ try {
19
+ const appSlug = argsSchema.parse(unparsedArgs);
20
+ const { language: cliLanguage, workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
21
+ const { appId, appInfo } = await boot({ workspaceSlug, appSlug });
22
+ const language = cliLanguage ?? (await askLanguage());
23
+ await createProject({
20
24
  appSlug,
21
- language: options.language,
22
- },
23
- });
24
- if (options.dev) {
25
- actor.subscribe((state) => {
26
- console.log("state:", state.value);
25
+ language,
26
+ appInfo,
27
+ appId,
27
28
  });
29
+ process.exit(0);
30
+ }
31
+ catch (error) {
32
+ process.stderr.write(`${chalk.red("✖ " + error)}\n`);
33
+ process.exit(1);
28
34
  }
29
- actor.start();
30
35
  });
@@ -4,8 +4,10 @@ export const login = new Command("login").description("Authenticate with Attio")
4
4
  authApi()
5
5
  .then((token) => {
6
6
  process.stdout.write("🔓 Successfully authenticated.\n");
7
+ process.exit(0);
7
8
  })
8
9
  .catch((error) => {
9
10
  process.stderr.write(`❌ Authentication failed\n\n${error}\n`);
11
+ process.exit(1);
10
12
  });
11
13
  });
@@ -4,8 +4,10 @@ export const logout = new Command("logout").description("Log out from Attio").ac
4
4
  deleteAuthToken()
5
5
  .then(() => {
6
6
  process.stdout.write("🔒 Successfully logged out.\n");
7
+ process.exit(0);
7
8
  })
8
9
  .catch((error) => {
9
10
  process.stderr.write(`❌ Logout failed\n\n${error}\n`);
11
+ process.exit(1);
10
12
  });
11
13
  });
@@ -0,0 +1,9 @@
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,20 +1,76 @@
1
- import { Command, Option } from "commander";
2
- import { createActor } from "xstate";
3
- import { z } from "zod";
4
- import { createVersionMachine } from "../../machines/create-version-machine.js";
5
- export const optionsSchema = z.object({
6
- dev: z.boolean().default(false),
7
- });
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { Spinner } from "../../util/spinner.js";
4
+ import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
5
+ import { createVersion } from "../../api/create-version.js";
6
+ import { fetchVersions } from "../../api/fetch-versions.js";
7
+ import { completeProdBundleUpload } from "../../api/complete-prod-bundle-upload.js";
8
+ import { buildJavaScript } from "../dev/build-javascript.js";
9
+ import { boot } from "./create/boot.js";
8
10
  export const versionCreate = new Command("create")
9
11
  .description("Create a new unpublished version of your Attio app")
10
- .addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
11
- .action((unparsedOptions) => {
12
- const options = optionsSchema.parse(unparsedOptions);
13
- const actor = createActor(createVersionMachine);
14
- if (options.dev) {
15
- actor.subscribe((state) => {
16
- console.log("state:", state.value);
12
+ .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) => {
18
+ const cleanup = buildJavaScript((bundles) => {
19
+ cleanup();
20
+ bundlingSpinner.success("Bundling complete");
21
+ resolve(bundles);
22
+ });
23
+ });
24
+ const versions = await fetchVersions({
25
+ token,
26
+ appId: appInfo.app_id,
27
+ });
28
+ const uploadSpinner = new Spinner();
29
+ uploadSpinner.start("Uploading...");
30
+ 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
+ const version = await createVersion({
35
+ token,
36
+ appId: appInfo.app_id,
37
+ major,
38
+ cliVersion: packageJson.version,
17
39
  });
40
+ 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
+ }),
57
+ ]);
58
+ uploadSpinner.success("Upload complete");
59
+ const signSpinner = new Spinner();
60
+ signSpinner.start("Signing bundles...");
61
+ await completeProdBundleUpload({
62
+ token,
63
+ appId: version.app_id,
64
+ major: version.major,
65
+ minor: version.minor,
66
+ bundleId: version.app_prod_version_bundle_id,
67
+ });
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);
18
75
  }
19
- actor.start();
20
76
  });
@@ -4,14 +4,15 @@ import Table from "cli-table3";
4
4
  import chalk from "chalk";
5
5
  import formatDate from "date-fns/format/index.js";
6
6
  import { ensureAuthed } from "../../api/ensure-authed.js";
7
- import { getAppSlugFromPackageJson, fetchAppInfo } from "../../machines/actors.js";
7
+ import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
8
+ import { getAppInfo } from "../../api/get-app-info.spinner.js";
8
9
  export const versionList = new Command("list")
9
10
  .description("List all versions of your Attio app")
10
11
  .alias("ls")
11
12
  .action(async () => {
12
13
  const token = await ensureAuthed();
13
14
  const appSlug = await getAppSlugFromPackageJson();
14
- const appInfo = await fetchAppInfo({ token, appSlug });
15
+ const appInfo = await getAppInfo({ token, appSlug });
15
16
  const versions = await fetchVersions({
16
17
  token,
17
18
  appId: appInfo.app_id,
@@ -31,4 +32,5 @@ export const versionList = new Command("list")
31
32
  formatDate(new Date(version.created_at), "MMMM d, yyyy, HH:mm"),
32
33
  ]));
33
34
  process.stdout.write(table.toString());
35
+ process.exit(0);
34
36
  });
@@ -7,7 +7,7 @@ export const whoami = new Command("whoami")
7
7
  const token = await authApi();
8
8
  if (!token) {
9
9
  process.stdout.write("🔒 Not logged in.\n");
10
- return;
10
+ process.exit(0);
11
11
  }
12
12
  try {
13
13
  const user = await whoamiApi({ token });
@@ -15,5 +15,6 @@ export const whoami = new Command("whoami")
15
15
  }
16
16
  catch (error) {
17
17
  process.stderr.write(`❌ User lookup failed\n\n${error}\n`);
18
+ process.exit(1);
18
19
  }
19
20
  });
@@ -1,9 +1,10 @@
1
1
  import Blocker from "stdin-blocker";
2
2
  import Cursor from "tiny-cursor";
3
+ import readline from "readline";
3
4
  export function printMessage(message) {
4
5
  Blocker.block();
5
6
  Cursor.hide();
6
- process.stdout.cursorTo(0);
7
+ readline.cursorTo(process.stdout, 0);
7
8
  process.stdout.write(message);
8
- process.stdout.clearLine(1);
9
+ readline.clearLine(process.stdout, 1);
9
10
  }