attio 0.0.1-experimental.20250324.1 → 0.0.1-experimental.20250325

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.
@@ -15,22 +15,6 @@ const prodVersionSchema = z.object({
15
15
  const fetchVersionsSchema = z.object({
16
16
  app_prod_versions: z.array(prodVersionSchema),
17
17
  });
18
- function onlyPublishableVersions(versions) {
19
- const publishedVersions = versions.filter((v) => v.is_published);
20
- if (publishedVersions.length === 0)
21
- return versions.filter((v) => !v.is_published);
22
- const highestPublishedVersion = publishedVersions.reduce((highest, current) => {
23
- if (current.major > highest.major ||
24
- (current.major === highest.major && current.minor > highest.minor)) {
25
- return current;
26
- }
27
- return highest;
28
- });
29
- return versions.filter((v) => !v.is_published &&
30
- (v.major > highestPublishedVersion.major ||
31
- (v.major === highestPublishedVersion.major &&
32
- v.minor > highestPublishedVersion.minor)));
33
- }
34
18
  export async function fetchVersions({ token, appId, }) {
35
19
  const response = await fetch(`${API}/apps/${appId}/prod-versions`, {
36
20
  method: "GET",
@@ -39,11 +23,3 @@ export async function fetchVersions({ token, appId, }) {
39
23
  await handleError(response);
40
24
  return fetchVersionsSchema.parse(await response.json()).app_prod_versions;
41
25
  }
42
- export async function fetchPublishableVersions({ token, devSlug, appId, }) {
43
- const versions = await fetchVersions({ token, devSlug, appId });
44
- return onlyPublishableVersions(versions);
45
- }
46
- export async function fetchUnpublishedVersions({ token, devSlug, appId, }) {
47
- const versions = await fetchVersions({ token, devSlug, appId });
48
- return versions.filter((v) => !v.is_published);
49
- }
@@ -11,14 +11,14 @@ const appSchema = z.object({
11
11
  })
12
12
  .nullable(),
13
13
  });
14
- export async function getAppInfo({ token, developerSlug, appSlug, }) {
14
+ export async function getAppInfo({ token, appSlug, }) {
15
15
  if (isTest) {
16
16
  return {
17
17
  app_id: "test-id",
18
18
  title: "Test App",
19
19
  };
20
20
  }
21
- const response = await fetch(`${API}/developer-accounts/${developerSlug}/apps/by-slug/${appSlug}`, {
21
+ const response = await fetch(`${API}/apps/by-slug/${appSlug}`, {
22
22
  headers: makeHeaders(token),
23
23
  });
24
24
  await handleError(response);
@@ -4,13 +4,19 @@ import { z } from "zod";
4
4
  import { devMachine } from "../machines/dev-machine.js";
5
5
  export const optionsSchema = z.object({
6
6
  dev: z.boolean().default(false),
7
+ workspace: z.string().optional(),
7
8
  });
8
9
  export const dev = new Command("dev")
9
10
  .description("Develop your Attio app")
10
11
  .addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
12
+ .addOption(new Option("--workspace", "The slug of the workspace to use"))
11
13
  .action((unparsedOptions) => {
12
14
  const options = optionsSchema.parse(unparsedOptions);
13
- const actor = createActor(devMachine);
15
+ const actor = createActor(devMachine, {
16
+ input: {
17
+ workspaceSlug: options.workspace,
18
+ },
19
+ });
14
20
  if (options.dev) {
15
21
  actor.subscribe((state) => {
16
22
  console.log("state:", state.value);
@@ -3,19 +3,18 @@ import { fetchVersions } from "../../api/fetch-versions.js";
3
3
  import Table from "cli-table3";
4
4
  import chalk from "chalk";
5
5
  import formatDate from "date-fns/format/index.js";
6
- import { loadAppId } from "../../api/load-app-id.js";
7
- import { loadDevSlug } from "../../api/load-dev-slug.js";
8
6
  import { ensureAuthed } from "../../api/ensure-authed.js";
7
+ import { getAppSlugFromPackageJson, fetchAppInfo } from "../../machines/actors.js";
9
8
  export const versionList = new Command("list")
10
9
  .description("List all versions of your Attio app")
11
10
  .alias("ls")
12
11
  .action(async () => {
13
12
  const token = await ensureAuthed();
14
- const [appId, devSlug] = await Promise.all([loadAppId(), loadDevSlug()]);
13
+ const appSlug = await getAppSlugFromPackageJson();
14
+ const appInfo = await fetchAppInfo({ token, appSlug });
15
15
  const versions = await fetchVersions({
16
16
  token,
17
- devSlug,
18
- appId,
17
+ appId: appInfo.app_id,
19
18
  });
20
19
  const table = new Table({
21
20
  head: ["Version", "Published", "Installations", "Created"].map((h) => chalk.bold(h)),
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import { textGradient } from "../util/text-gradient.js";
3
3
  import { attioLogoAndName } from "../attio-logo.js";
4
4
  export const showError = (_, params) => process.stderr.write(`${chalk.red("✖ " + params.error)}\n`);
5
+ export const showActorError = (_, params) => process.stderr.write(`${chalk.red("✖ " + params.error)}\n`);
5
6
  export const printLogo = () => {
6
7
  process.stdout.write(`\n${textGradient(attioLogoAndName, "#ff5f6d", "#ffc371")}\n\n`);
7
8
  };
@@ -1,51 +1,75 @@
1
1
  import { fetchVersions as fetchVersionsApi } from "../api/fetch-versions.js";
2
2
  import { input as inputPrompt, password as passwordPrompt, select as selectPrompt, confirm as confirmPrompt, } from "@inquirer/prompts";
3
3
  import { fromCallback, fromPromise } from "xstate";
4
- import { loadAppConfigFile } from "../util/app-config.js";
5
- import { loadDeveloperConfig as loadDeveloperConfigFile } from "../util/load-developer-config.js";
4
+ import { readFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { z } from "zod";
6
7
  import Spinner from "tiny-spinner";
8
+ import { getAppInfo } from "../api/get-app-info.js";
7
9
  import { ensureAuthed } from "../api/ensure-authed.js";
8
- export const loadDeveloperConfig = fromCallback(({ sendBack }) => {
9
- const load = async () => {
10
- const config = await loadDeveloperConfigFile();
11
- if (typeof config === "string") {
12
- sendBack({ type: "No Developer Config", configError: config });
13
- return;
14
- }
15
- const token = await ensureAuthed();
16
- sendBack({
17
- type: "Developer Config Loaded",
18
- token,
19
- slug: config.developer_slug,
20
- });
21
- };
22
- load();
10
+ import { fetchWorkspaces } from "../api/fetch-workspaces.js";
11
+ import { APP } from "../env.js";
12
+ export const authenticate = fromPromise(ensureAuthed);
13
+ const packageJsonSchema = z.object({
14
+ name: z.string({
15
+ required_error: "No name field found in package.json",
16
+ invalid_type_error: "name must be a string in package.json",
17
+ }),
23
18
  });
24
- export const loadAppConfig = fromCallback(({ sendBack }) => {
25
- const config = loadAppConfigFile();
26
- if (typeof config === "string") {
27
- sendBack({ type: "Error", error: config });
28
- return;
19
+ export const getAppSlugFromPackageJson = async () => {
20
+ try {
21
+ const packageJsonPath = join(process.cwd(), "package.json");
22
+ const packageJsonRaw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
23
+ const result = packageJsonSchema.safeParse(packageJsonRaw);
24
+ if (!result.success) {
25
+ throw new Error(result.error.issues[0]?.message || "Malformed package.json");
26
+ }
27
+ return result.data.name;
29
28
  }
30
- sendBack({ type: "App Config Loaded", config });
31
- });
32
- export const fetchVersions = fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, }, }) => {
29
+ catch (error) {
30
+ if (error instanceof SyntaxError) {
31
+ throw new Error("Invalid JSON in package.json");
32
+ }
33
+ throw new Error("Failed to read package.json");
34
+ }
35
+ };
36
+ export const loadAppSlug = fromPromise(getAppSlugFromPackageJson);
37
+ export const fetchVersions = fromPromise(async ({ input: { token, appInfo } }) => {
33
38
  const spinner = new Spinner();
34
- spinner.start("Loading versions...");
35
- const getVersions = async () => {
39
+ try {
40
+ spinner.start("Loading versions...");
36
41
  const versions = await fetchVersionsApi({
37
42
  token,
38
- devSlug,
39
- appId: config.id,
43
+ appId: appInfo.app_id,
40
44
  });
41
45
  spinner.success("Versions loaded");
42
- sendBack({ type: "Versions Fetched", versions });
43
- };
44
- getVersions().catch((error) => {
46
+ return versions;
47
+ }
48
+ catch (error) {
45
49
  spinner.error("Error loading versions");
46
- sendBack({ type: "Error", error: error.message });
47
- });
50
+ throw error;
51
+ }
52
+ finally {
53
+ spinner.stop();
54
+ }
48
55
  });
56
+ export async function fetchAppInfo({ token, appSlug, }) {
57
+ const spinner = new Spinner();
58
+ spinner.start("Loading app information...");
59
+ try {
60
+ const appInfo = await getAppInfo({ token, appSlug });
61
+ if (appInfo === null) {
62
+ spinner.error("App not found");
63
+ throw new Error("App not found");
64
+ }
65
+ spinner.success(`App found: ${appInfo.title}`);
66
+ return appInfo;
67
+ }
68
+ finally {
69
+ spinner.stop();
70
+ }
71
+ }
72
+ export const loadAppInfo = fromPromise(async ({ input: { token, appSlug } }) => await fetchAppInfo({ token, appSlug }));
49
73
  export const ask = fromPromise(async ({ input }) => await inputPrompt(input));
50
74
  export const askPassword = fromPromise(async ({ input }) => await passwordPrompt(input));
51
75
  export const askWithChoices = fromPromise(async ({ input, }) => await selectPrompt(input));
@@ -62,3 +86,39 @@ export const fromCallbackWithErrorHandling = (callback) => fromCallback((...args
62
86
  });
63
87
  }
64
88
  });
89
+ export const determineWorkspace = fromPromise(async ({ input: { token, workspaceSlug } }) => {
90
+ const spinner = new Spinner();
91
+ spinner.start("Loading workspaces...");
92
+ try {
93
+ const workspaces = await fetchWorkspaces({ token });
94
+ spinner.success("Workspaces loaded");
95
+ const workspace = workspaces.find((workspace) => workspace.slug === workspaceSlug);
96
+ if (workspace) {
97
+ spinner.success(`Using workspace: ${workspace.name}`);
98
+ return workspace;
99
+ }
100
+ const hasAdmin = workspaces.some((workspace) => workspace.access_level === "admin");
101
+ if (!hasAdmin) {
102
+ throw new Error(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
103
+
104
+ ${APP}/welcome/workspace-details
105
+ `);
106
+ }
107
+ if (workspaces.length === 1) {
108
+ spinner.success(`Using workspace: ${workspaces[0].name}`);
109
+ return workspaces[0];
110
+ }
111
+ const choice = await selectPrompt({
112
+ message: "Choose a workspace",
113
+ choices: workspaces.map((workspace) => ({
114
+ name: workspace.name,
115
+ value: workspace,
116
+ })),
117
+ });
118
+ spinner.success(`Using workspace: ${choice.name}`);
119
+ return choice;
120
+ }
121
+ finally {
122
+ spinner.stop();
123
+ }
124
+ });
@@ -1,13 +1,10 @@
1
- import { assign, setup, fromCallback } from "xstate";
1
+ import { assign, setup, fromPromise } from "xstate";
2
2
  import chalk from "chalk";
3
3
  import { completeProdBundleUpload } from "../api/complete-prod-bundle-upload.js";
4
4
  import { createVersion } from "../api/create-version.js";
5
- import { emptyConfig } from "../schema.js";
6
- import { updateAppConfig } from "../util/app-config.js";
7
- import { askWithTypedChoices, loadAppConfig, loadDeveloperConfig, confirm, fetchVersions, } from "./actors.js";
5
+ import { authenticate, confirm, fetchVersions, loadAppInfo, loadAppSlug } from "./actors.js";
8
6
  import { jsMachine } from "./js-machine.js";
9
- import { printInstallInstructions } from "../util/print-install-instructions.js";
10
- import { showError, printLogo } from "./actions.js";
7
+ import { printLogo, showActorError } from "./actions.js";
11
8
  import Spinner from "tiny-spinner";
12
9
  import { loadAttioCliPackageJson } from "../util/load-attio-cli-package-json.js";
13
10
  export const connectionTypes = [
@@ -33,38 +30,38 @@ export const createVersionMachine = setup({
33
30
  actors: {
34
31
  confirm,
35
32
  fetchVersions,
36
- askForUpgradeType: askWithTypedChoices(),
37
33
  javascript: jsMachine,
38
- createVersion: fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, upgradeType, }, }) => {
39
- const add = async () => {
40
- const spinner = new Spinner();
41
- spinner.start("Creating version...");
42
- try {
43
- const packageJson = loadAttioCliPackageJson();
44
- if (typeof packageJson === "string")
45
- throw packageJson;
46
- const res = await createVersion({
47
- token,
48
- devSlug,
49
- appId: config.id,
50
- major: upgradeType === "initial" ? 1 : config.major,
51
- cliVersion: packageJson.version,
52
- });
53
- spinner.success("Version created");
54
- sendBack({ type: "Version Created", version: res });
55
- }
56
- catch (error) {
57
- spinner.error("Error creating version");
58
- sendBack({ type: "Error", error: error.message });
59
- }
60
- };
61
- add();
34
+ createVersion: fromPromise(async ({ input: { token, appInfo, upgradeType, versions } }) => {
35
+ const spinner = new Spinner();
36
+ spinner.start("Creating version...");
37
+ try {
38
+ const packageJson = loadAttioCliPackageJson();
39
+ if (typeof packageJson === "string")
40
+ throw packageJson;
41
+ const major = Math.max(...versions.map((version) => version.major), 1);
42
+ const version = await createVersion({
43
+ token,
44
+ appId: appInfo.app_id,
45
+ major: upgradeType === "initial" ? 1 : major,
46
+ cliVersion: packageJson.version,
47
+ });
48
+ spinner.success("Version created");
49
+ return version;
50
+ }
51
+ catch (error) {
52
+ spinner.error("Error creating version");
53
+ throw error;
54
+ }
55
+ finally {
56
+ spinner.stop();
57
+ }
62
58
  }),
63
- loadDeveloperConfig,
64
- loadAppConfig,
65
- upload: fromCallback(({ sendBack, input: { developer: { token, slug: developerSlug }, jsContents, version: { app_id: appId, client_bundle_upload_url, server_bundle_upload_url, major, minor, app_prod_version_bundle_id: bundleId, }, }, }) => {
59
+ authenticate,
60
+ loadAppInfo,
61
+ loadAppSlug: loadAppSlug,
62
+ upload: fromPromise(async ({ input: { token, jsContents, version: { app_id: appId, client_bundle_upload_url, server_bundle_upload_url, major, minor, app_prod_version_bundle_id: bundleId, }, }, }) => {
66
63
  const uploadSpinner = new Spinner();
67
- const upload = async () => {
64
+ try {
68
65
  uploadSpinner.start("Uploading...");
69
66
  const [clientBundle, serverBundle] = jsContents;
70
67
  await Promise.all([
@@ -85,36 +82,31 @@ export const createVersionMachine = setup({
85
82
  },
86
83
  }),
87
84
  ]);
88
- uploadSpinner.success("Uploading complete");
85
+ uploadSpinner.success("Upload complete");
89
86
  const signSpinner = new Spinner();
90
87
  signSpinner.start("Signing bundles...");
91
88
  await completeProdBundleUpload({
92
89
  token,
93
- developerSlug,
94
90
  appId,
95
91
  major,
96
92
  minor,
97
93
  bundleId,
98
94
  });
99
95
  signSpinner.success("Bundles signed");
100
- updateAppConfig((config) => ({
101
- ...config,
102
- major,
103
- minor,
104
- }));
105
96
  process.stdout.write(`\nVersion ${chalk.green(`${major}.${minor}`)} created!\n\n`);
106
- sendBack({ type: "Upload Complete" });
107
- };
108
- upload().catch((error) => {
97
+ }
98
+ catch (error) {
109
99
  uploadSpinner.error("Upload failed");
110
- sendBack({ type: "Error", error: error.message });
111
- });
100
+ throw error;
101
+ }
102
+ finally {
103
+ uploadSpinner.stop();
104
+ }
112
105
  }),
113
106
  },
114
107
  actions: {
115
108
  printLogo,
116
- showError,
117
- showConfigInstructions: ({ context }) => printInstallInstructions(context.configError),
109
+ showActorError,
118
110
  startBundlingSpinner: () => {
119
111
  bundlingSpinner.start("Bundling JavaScript...");
120
112
  },
@@ -124,96 +116,71 @@ export const createVersionMachine = setup({
124
116
  errorBundlingSpinner: () => {
125
117
  bundlingSpinner.error("Bundling failed");
126
118
  },
127
- clearError: assign({
128
- error: () => undefined,
129
- }),
130
119
  setUpgradeType: assign({
131
120
  upgradeType: (_, params) => params.output,
132
121
  }),
133
- setError: assign({
134
- error: (_, params) => params.error,
135
- }),
136
122
  setVersions: assign({
137
- versions: (_, params) => params.versions,
138
- }),
139
- setDeveloperConfig: assign({
140
- developer: (_, params) => params,
141
- }),
142
- setConfigError: assign({
143
- configError: (_, params) => params.configError,
123
+ versions: (_, params) => params.output,
144
124
  }),
145
125
  setJsContents: assign({
146
126
  jsContents: (_, params) => params.contents,
147
127
  }),
148
128
  setVersion: assign({
149
- version: (_, params) => params.version,
129
+ version: (_, params) => params.output,
150
130
  }),
151
- setAppConfig: assign({
152
- config: (_, params) => params.config,
131
+ setToken: assign({
132
+ token: (_, params) => params.output,
133
+ }),
134
+ setAppSlug: assign({
135
+ appSlug: (_, params) => params.output,
136
+ }),
137
+ setAppInfo: assign({
138
+ appInfo: (_, params) => params.output,
153
139
  }),
154
140
  },
155
141
  }).createMachine({
156
142
  context: {
157
- developer: { slug: "", token: "" },
158
- config: emptyConfig,
143
+ token: "",
144
+ appSlug: "",
159
145
  upgradeType: "minor",
160
146
  versions: [],
161
147
  },
162
148
  id: "Create Version Machine",
163
149
  states: {
164
- "Loading Developer Config": {
150
+ "Authenticate": {
165
151
  invoke: {
166
- src: "loadDeveloperConfig",
167
- },
168
- on: {
169
- "Developer Config Loaded": {
170
- target: "Loading App Config",
171
- actions: { type: "setDeveloperConfig", params: ({ event }) => event },
152
+ src: "authenticate",
153
+ onDone: {
154
+ target: "Loading App Slug",
155
+ actions: { type: "setToken", params: ({ event }) => event },
172
156
  },
173
- "No Developer Config": {
174
- target: "Show config instructions",
175
- actions: { type: "setConfigError", params: ({ event }) => event },
176
- },
177
- },
178
- },
179
- "Show config instructions": {
180
- type: "final",
181
- entry: "showConfigInstructions",
182
- },
183
- "Loading App Config": {
184
- invoke: {
185
- src: "loadAppConfig",
186
- },
187
- on: {
188
- "Error": {
157
+ onError: {
189
158
  target: "Error",
190
- actions: { type: "setError", params: ({ event }) => event },
191
- },
192
- "App Config Loaded": {
193
- target: "JavaScript",
194
- actions: { type: "setAppConfig", params: ({ event }) => event },
195
- reenter: true,
159
+ actions: { type: "showActorError", params: ({ event }) => event },
196
160
  },
197
161
  },
198
162
  },
199
163
  "Error": {
200
164
  type: "final",
201
- entry: { type: "showError", params: ({ context }) => ({ error: context.error }) },
202
165
  },
203
166
  "Creating version": {
204
167
  invoke: {
205
168
  src: "createVersion",
206
- input: ({ context }) => context,
207
- },
208
- on: {
209
- "Error": {
210
- target: "Error",
211
- actions: { type: "setError", params: ({ event }) => event },
212
- },
213
- "Version Created": {
169
+ input: ({ context }) => ({
170
+ token: context.token,
171
+ appSlug: context.appSlug,
172
+ upgradeType: context.upgradeType,
173
+ appInfo: context.appInfo,
174
+ versions: context.versions,
175
+ }),
176
+ onDone: {
214
177
  target: "Uploading",
215
178
  actions: { type: "setVersion", params: ({ event }) => event },
216
179
  },
180
+ onError: {
181
+ target: "Error",
182
+ actions: { type: "showActorError", params: ({ event }) => event },
183
+ },
217
184
  },
218
185
  },
219
186
  "Success": {
@@ -253,20 +220,14 @@ export const createVersionMachine = setup({
253
220
  invoke: {
254
221
  src: "upload",
255
222
  input: ({ context }) => ({
223
+ token: context.token,
256
224
  version: context.version,
257
- developer: context.developer,
258
225
  jsContents: context.jsContents,
259
226
  }),
260
- },
261
- on: {
262
- "Upload Complete": {
263
- target: "Success",
264
- reenter: true,
265
- },
266
- "Error": {
227
+ onDone: "Success",
228
+ onError: {
267
229
  target: "Error",
268
- reenter: true,
269
- actions: { type: "setError", params: ({ event }) => event },
230
+ actions: { type: "showActorError", params: ({ event }) => event },
270
231
  },
271
232
  },
272
233
  },
@@ -296,20 +257,52 @@ export const createVersionMachine = setup({
296
257
  "Fetching Versions": {
297
258
  invoke: {
298
259
  src: "fetchVersions",
299
- input: ({ context }) => context,
300
- },
301
- on: {
302
- "Versions Fetched": {
260
+ input: ({ context }) => ({
261
+ token: context.token,
262
+ appInfo: context.appInfo,
263
+ }),
264
+ onDone: {
303
265
  target: "Determine if first version",
304
- actions: { type: "setVersions", params: ({ event }) => event },
266
+ actions: {
267
+ type: "setVersions",
268
+ params: ({ event }) => event,
269
+ },
270
+ },
271
+ onError: {
272
+ target: "Error",
273
+ actions: { type: "showActorError", params: ({ event }) => event },
274
+ },
275
+ },
276
+ },
277
+ "Loading App Slug": {
278
+ invoke: {
279
+ src: "loadAppSlug",
280
+ onDone: {
281
+ target: "Loading App Info",
282
+ actions: { type: "setAppSlug", params: ({ event }) => event },
283
+ reenter: true,
284
+ },
285
+ onError: {
286
+ target: "Error",
287
+ actions: { type: "showActorError", params: ({ event }) => event },
288
+ },
289
+ },
290
+ },
291
+ "Loading App Info": {
292
+ invoke: {
293
+ src: "loadAppInfo",
294
+ input: ({ context }) => context,
295
+ onDone: {
296
+ target: "JavaScript",
297
+ actions: { type: "setAppInfo", params: ({ event }) => event },
305
298
  },
306
- "Error": {
299
+ onError: {
307
300
  target: "Error",
308
- actions: { type: "setError", params: ({ event }) => event },
301
+ actions: { type: "showActorError", params: ({ event }) => event },
309
302
  },
310
303
  },
311
304
  },
312
305
  },
313
- initial: "Loading Developer Config",
306
+ initial: "Authenticate",
314
307
  entry: "printLogo",
315
308
  });