attio 0.0.1-experimental.20250303 → 0.0.1-experimental.20250311

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.
@@ -0,0 +1,24 @@
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 workspacesResponseSchema = z.array(z.object({
6
+ workspace: z.object({
7
+ id: z.string(),
8
+ name: z.string(),
9
+ slug: z.string(),
10
+ }),
11
+ membership: z.object({
12
+ access_level: z.enum(["admin", "member", "suspended"]),
13
+ }),
14
+ }));
15
+ export async function fetchWorkspaces({ token }) {
16
+ const response = await fetch(`${APP}/api/common/workspaces`, {
17
+ method: "GET",
18
+ headers: makeHeaders(token),
19
+ });
20
+ await handleError(response);
21
+ return workspacesResponseSchema
22
+ .parse(await response.json())
23
+ .map(({ workspace, membership }) => ({ ...workspace, ...membership }));
24
+ }
@@ -1,13 +1,13 @@
1
1
  import chokidar from "chokidar";
2
2
  import open from "open";
3
- import { assign, setup, enqueueActions } from "xstate";
3
+ import { assign, setup, enqueueActions, fromPromise } from "xstate";
4
4
  import { APP } from "../env.js";
5
5
  import { completeBundleUpload } from "../api/complete-bundle-upload.js";
6
6
  import { createDevVersion } from "../api/create-dev-version.js";
7
7
  import { startGraphqlServer } from "../api/start-graphql-server.js";
8
8
  import { startUpload } from "../api/start-upload.js";
9
9
  import { loadAppConfigFile } from "../util/app-config.js";
10
- import { loadDeveloperConfig, loadInitialDeveloperConfig, } from "../util/load-developer-config.js";
10
+ import { loadDeveloperConfig, loadInitialDeveloperConfig, saveTargetWorkspaceToConfig, } from "../util/load-developer-config.js";
11
11
  import notifier from "node-notifier";
12
12
  import { loadEnv } from "../util/load-env.js";
13
13
  import { loadAttioCliPackageJson } from "../util/load-attio-cli-package-json.js";
@@ -23,7 +23,8 @@ import { clearTerminal } from "../util/clear-terminal.js";
23
23
  import { setTerminalTitle } from "../util/set-terminal-title.js";
24
24
  import { printInstallInstructions } from "../util/print-install-instructions.js";
25
25
  import { printLogo } from "./actions.js";
26
- import { fromCallbackWithErrorHandling } from "./actors.js";
26
+ import { askWithTypedChoices, fromCallbackWithErrorHandling } from "./actors.js";
27
+ import { fetchWorkspaces } from "../api/fetch-workspaces.js";
27
28
  process.on("SIGINT", () => {
28
29
  process.stdout.write("\x1B[?25h");
29
30
  process.exit();
@@ -40,12 +41,18 @@ export const devMachine = setup({
40
41
  guards: {
41
42
  "have dev version": ({ context }) => Boolean(context.devVersion),
42
43
  "have typescript errors": (_, params) => Boolean(params.typeScriptErrors?.length),
44
+ "have target workspace": (_, params) => Boolean(params.config?.target_workspace_id),
45
+ "have workspaces": (_, params) => Boolean(params.output?.length),
46
+ "is workspace admin": (_, params) => params.output.access_level === "admin",
47
+ "only one workspace and admin": (_, params) => params.output.length === 1 && params.output[0].access_level === "admin",
48
+ "only one workspace and not admin": (_, params) => params.output.length === 1 && params.output[0].access_level !== "admin",
43
49
  },
44
50
  actors: {
45
51
  "javascript": jsMachine,
46
52
  "typescript": tsMachine,
47
53
  "env": envMachine,
48
54
  "code-gen": codeGenMachine,
55
+ "askForWorkspace": askWithTypedChoices(),
49
56
  "loadConfig": fromCallbackWithErrorHandling(({ sendBack }) => {
50
57
  const config = loadInitialDeveloperConfig();
51
58
  if (typeof config === "string") {
@@ -98,6 +105,15 @@ export const devMachine = setup({
98
105
  }
99
106
  };
100
107
  }),
108
+ "loadWorkspaces": fromPromise(async ({ input }) => await fetchWorkspaces({ token: input.token })),
109
+ "loadWorkspaceDetails": fromPromise(async ({ input }) => {
110
+ const workspaces = await fetchWorkspaces({ token: input.config.token });
111
+ const workspace = workspaces.find((workspace) => workspace.id === input.config.target_workspace_id);
112
+ if (!workspace) {
113
+ throw new Error(`Workspace not found: ${input.config.target_workspace_id}`);
114
+ }
115
+ return workspace;
116
+ }),
101
117
  "prepareUpload": fromCallbackWithErrorHandling(({ sendBack }) => {
102
118
  const prepareUpload = async () => {
103
119
  const config = await loadDeveloperConfig();
@@ -248,8 +264,8 @@ export const devMachine = setup({
248
264
  printUploadError: (_, params) => {
249
265
  printMessage(params.uploadError.message);
250
266
  },
251
- printGenericError: (_, params) => {
252
- process.stderr.write(`${params.error.message}\n\n`);
267
+ printError: (_, params) => {
268
+ printMessage(params.error);
253
269
  },
254
270
  printLogo: (_) => {
255
271
  process.stdout.write("\x1B[?25l");
@@ -289,7 +305,7 @@ export const devMachine = setup({
289
305
  open(`http://localhost:${context.graphqlPort}/graphql`);
290
306
  },
291
307
  openInstallPage: ({ context }) => {
292
- open(`${APP}/_/settings/apps/${context.devVersion?.app_slug}`);
308
+ open(`${APP}/${context.workspace.slug}/settings/apps/${context.devVersion?.app_slug}`);
293
309
  },
294
310
  saveTypeScriptErrors: assign({
295
311
  typeScriptErrors: (_, params) => params.errors,
@@ -329,6 +345,21 @@ export const devMachine = setup({
329
345
  ? `attio dev – ${context.devVersion.app_id}`
330
346
  : `attio dev`);
331
347
  },
348
+ setTargetWorkspace: assign({
349
+ config: (_, params) => ({
350
+ ...params.config,
351
+ target_workspace_id: params.workspace.id,
352
+ }),
353
+ }),
354
+ saveTargetWorkspaceToConfig: (_, params) => {
355
+ saveTargetWorkspaceToConfig(params.workspace.id);
356
+ },
357
+ setWorkspaces: assign({
358
+ workspaces: (_, params) => params.output,
359
+ }),
360
+ setWorkspace: assign({
361
+ workspace: (_, params) => params.output,
362
+ }),
332
363
  setSuccess: assign({
333
364
  jsContents: (_, params) => params.contents,
334
365
  lastSuccessfulJavaScriptBuild: (_, params) => params.time,
@@ -610,8 +641,9 @@ export const devMachine = setup({
610
641
  "Read Config": {
611
642
  on: {
612
643
  "Initialized": {
613
- target: "Watching",
644
+ target: "Choose Target Workspace",
614
645
  actions: { type: "setConfig", params: ({ event }) => event },
646
+ reenter: true,
615
647
  },
616
648
  "Initialization Error": {
617
649
  target: "No Config",
@@ -632,15 +664,175 @@ export const devMachine = setup({
632
664
  "Auth Error": {
633
665
  type: "final",
634
666
  },
635
- "Generic Error": {
667
+ "Choose Target Workspace": {
668
+ states: {
669
+ "Need Target Workspace?": {
670
+ always: [
671
+ {
672
+ target: "#Dev Machine.Loading Workspace Details",
673
+ guard: { type: "have target workspace", params: ({ context }) => context },
674
+ reenter: true,
675
+ },
676
+ "Loading Workspaces",
677
+ ],
678
+ },
679
+ "Loading Workspaces": {
680
+ invoke: {
681
+ src: "loadWorkspaces",
682
+ input: ({ context }) => ({ token: context.config.token }),
683
+ onDone: [
684
+ {
685
+ target: "#Dev Machine.Loading Workspace Details",
686
+ guard: {
687
+ type: "only one workspace and admin",
688
+ params: ({ event }) => event,
689
+ },
690
+ reenter: true,
691
+ actions: [
692
+ {
693
+ type: "setTargetWorkspace",
694
+ params: ({ context, event }) => ({
695
+ config: context.config,
696
+ workspace: event.output[0],
697
+ }),
698
+ },
699
+ {
700
+ type: "saveTargetWorkspaceToConfig",
701
+ params: ({ event }) => ({ workspace: event.output[0] }),
702
+ },
703
+ ],
704
+ },
705
+ {
706
+ target: "#Dev Machine.Not admin of only workspace",
707
+ guard: {
708
+ type: "only one workspace and not admin",
709
+ params: ({ event }) => event,
710
+ },
711
+ reenter: true,
712
+ actions: {
713
+ type: "printError",
714
+ params: ({ event }) => ({
715
+ error: `You are not the admin of the workspace ${event.output[0].name}. Please ask an admin to make you an admin of the workspace or create your own workspace.
716
+
717
+ ${APP}/welcome/workspace-details
718
+ `,
719
+ }),
720
+ },
721
+ },
722
+ {
723
+ target: "Ask for Workspace",
724
+ guard: { type: "have workspaces", params: ({ event }) => event },
725
+ actions: { type: "setWorkspaces", params: ({ event }) => event },
726
+ },
727
+ {
728
+ target: "No Workspaces",
729
+ reenter: true,
730
+ actions: {
731
+ type: "printError",
732
+ params: () => ({
733
+ error: `You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
734
+
735
+ ${APP}/welcome/workspace-details
736
+ `,
737
+ }),
738
+ },
739
+ },
740
+ ],
741
+ onError: {
742
+ target: "Failed to load workspaces",
743
+ actions: {
744
+ type: "printError",
745
+ params: ({ event }) => ({
746
+ error: `Failed to load workspaces: ${event.error}`,
747
+ }),
748
+ },
749
+ },
750
+ },
751
+ },
752
+ "Ask for Workspace": {
753
+ invoke: {
754
+ src: "askForWorkspace",
755
+ input: ({ context }) => ({
756
+ message: "Choose a workspace",
757
+ choices: context.workspaces.map((workspace) => ({
758
+ name: workspace.name,
759
+ value: workspace,
760
+ })),
761
+ }),
762
+ onDone: [
763
+ {
764
+ target: "#Dev Machine.Loading Workspace Details",
765
+ actions: [
766
+ {
767
+ type: "setTargetWorkspace",
768
+ params: ({ context, event }) => ({
769
+ config: context.config,
770
+ workspace: event.output,
771
+ }),
772
+ },
773
+ {
774
+ type: "saveTargetWorkspaceToConfig",
775
+ params: ({ event }) => ({ workspace: event.output }),
776
+ },
777
+ ],
778
+ guard: { type: "is workspace admin", params: ({ event }) => event },
779
+ reenter: true,
780
+ },
781
+ {
782
+ target: "#Dev Machine.Require Admin Workspace",
783
+ reenter: true,
784
+ actions: {
785
+ type: "printError",
786
+ params: ({ event }) => ({
787
+ error: `You are not the admin of the workspace ${event.output.name}. Please ask an admin to make you an admin of the workspace or create your own workspace.
788
+
789
+ ${APP}/welcome/workspace-details
790
+ `,
791
+ }),
792
+ },
793
+ },
794
+ ],
795
+ },
796
+ },
797
+ "No Workspaces": {
798
+ type: "final",
799
+ },
800
+ "Failed to load workspaces": {
801
+ type: "final",
802
+ },
803
+ },
804
+ initial: "Need Target Workspace?",
805
+ },
806
+ "Require Admin Workspace": {
807
+ type: "final",
808
+ },
809
+ "Not admin of only workspace": {
636
810
  type: "final",
637
811
  },
638
- },
639
- entry: "printLogo",
640
- on: {
641
- Error: {
642
- target: ".Generic Error",
643
- actions: { type: "printGenericError", params: ({ event }) => event },
812
+ "Loading Workspace Details": {
813
+ invoke: {
814
+ src: "loadWorkspaceDetails",
815
+ input: ({ context }) => ({
816
+ config: context.config,
817
+ }),
818
+ onDone: {
819
+ target: "Watching",
820
+ actions: { type: "setWorkspace", params: ({ event }) => event },
821
+ },
822
+ onError: {
823
+ target: "Failed to load workspaces",
824
+ actions: {
825
+ type: "printError",
826
+ params: () => ({
827
+ error: "Failed to load workspace details",
828
+ }),
829
+ },
830
+ },
831
+ },
832
+ },
833
+ "Failed to load workspaces": {
834
+ type: "final",
644
835
  },
645
836
  },
837
+ entry: "printLogo",
646
838
  });
@@ -7,7 +7,7 @@ import { isValidSlug } from "./validate-slug.js";
7
7
  export const initialDeveloperConfigSchema = z.object({
8
8
  token: z.string(),
9
9
  developer_slug: z.string(),
10
- target_workspace_id: z.string().uuid(),
10
+ target_workspace_id: z.string().uuid().optional().nullable(),
11
11
  });
12
12
  const developerConfigSchema = initialDeveloperConfigSchema.extend({
13
13
  developer_account_id: z.string(),
@@ -76,8 +76,16 @@ export async function loadDeveloperConfig() {
76
76
  developer_slug: developerSlug,
77
77
  developer_account_id: devAccount.developer_account.developer_account_id,
78
78
  developer_account_member_id: devAccount.developer_account_member.developer_account_member_id,
79
- target_workspace_id: initialDeveloperConfig.target_workspace_id,
79
+ target_workspace_id: initialDeveloperConfig.target_workspace_id ?? null,
80
80
  });
81
81
  writeFileSync(configFile.path, stringify(config));
82
82
  return config;
83
83
  }
84
+ export async function saveTargetWorkspaceToConfig(workspaceId) {
85
+ const configFile = readDeveloperConfigFile();
86
+ if (configFile === "No config file" || configFile === "Invalid config file")
87
+ return;
88
+ const config = developerConfigSchema.parse(configFile.contents);
89
+ config.target_workspace_id = workspaceId;
90
+ writeFileSync(configFile.path, stringify(config));
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20250303",
3
+ "version": "0.0.1-experimental.20250311",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [