attio 0.0.1-experimental.20241219 → 0.0.1-experimental.20250101

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 (54) hide show
  1. package/lib/api/create-version.js +2 -2
  2. package/lib/attio-logo.js +24 -0
  3. package/lib/attio.js +17 -8
  4. package/lib/commands/build.js +18 -35
  5. package/lib/commands/connection/add.js +51 -168
  6. package/lib/commands/connection/index.js +9 -1
  7. package/lib/commands/connection/list.js +17 -47
  8. package/lib/commands/connection/remove.js +17 -65
  9. package/lib/commands/create.js +27 -126
  10. package/lib/commands/dev.js +17 -106
  11. package/lib/commands/version/create.js +17 -68
  12. package/lib/commands/version/index.js +11 -1
  13. package/lib/commands/version/invite.js +40 -84
  14. package/lib/commands/version/list.js +18 -40
  15. package/lib/commands/version/publish.js +40 -64
  16. package/lib/machines/actions.js +7 -0
  17. package/lib/machines/actors.js +7 -1
  18. package/lib/machines/add-connection-machine.js +169 -201
  19. package/lib/machines/build-machine.js +35 -4
  20. package/lib/machines/create-machine.js +95 -70
  21. package/lib/machines/create-version-machine.js +121 -4
  22. package/lib/machines/dev-machine.js +173 -53
  23. package/lib/machines/generate-invite-machine.js +64 -10
  24. package/lib/machines/list-connections-machine.js +57 -2
  25. package/lib/machines/list-versions-machine.js +33 -0
  26. package/lib/machines/publish-version-machine.js +45 -16
  27. package/lib/machines/remove-connection-machine.js +64 -17
  28. package/lib/machines/ts-machine.js +4 -1
  29. package/lib/schema.js +2 -2
  30. package/lib/util/clear-terminal.js +4 -0
  31. package/lib/util/load-developer-config.js +2 -2
  32. package/lib/util/print-install-instructions.js +32 -0
  33. package/lib/util/print-message.js +9 -0
  34. package/lib/util/set-terminal-title.js +8 -0
  35. package/lib/util/text-gradient.js +28 -0
  36. package/lib/util/typescript.js +25 -0
  37. package/package.json +13 -12
  38. package/schema.graphql +8 -1
  39. package/lib/components/BuildError.js +0 -46
  40. package/lib/components/BuildLog.js +0 -6
  41. package/lib/components/CodeGenErrors.js +0 -22
  42. package/lib/components/Disclaimer.js +0 -9
  43. package/lib/components/InitialInstructions.js +0 -69
  44. package/lib/components/Log.js +0 -69
  45. package/lib/components/Logo.js +0 -10
  46. package/lib/components/MultiSelect.js +0 -65
  47. package/lib/components/ScrollBox.js +0 -87
  48. package/lib/components/ScrollBox.store.js +0 -36
  49. package/lib/components/ScrollBox.util.js +0 -27
  50. package/lib/components/Select.js +0 -6
  51. package/lib/components/Table.js +0 -33
  52. package/lib/components/TypeScriptErrors.js +0 -38
  53. package/lib/hooks/useFullScreen.js +0 -22
  54. package/lib/hooks/useTerminalTitle.js +0 -11
@@ -1,9 +1,12 @@
1
- import readline from "readline";
2
1
  import { assign, setup, fromCallback } from "xstate";
3
2
  import { addConnectionDefinition } from "../api/add-connection-definition.js";
4
3
  import { fetchConnections } from "../api/fetch-connections.js";
5
- import { options } from "../commands/connection/add.js";
6
- import { loadAppConfig, loadDeveloperConfig } from "./actors.js";
4
+ import { optionsSchema } from "../commands/connection/add.js";
5
+ import { ask, askPassword, askWithChoices, loadAppConfig, loadDeveloperConfig } from "./actors.js";
6
+ import { printInstallInstructions } from "../util/print-install-instructions.js";
7
+ import { printLogo, showError } from "./actions.js";
8
+ import Spinner from "tiny-spinner";
9
+ import chalk from "chalk";
7
10
  export const connectionTypes = [
8
11
  { value: "secret", label: "Secret" },
9
12
  { value: "oauth2-code", label: "OAuth2" },
@@ -15,8 +18,13 @@ export const addConnectionMachine = setup({
15
18
  input: {},
16
19
  },
17
20
  actors: {
18
- "createConnectionDefinition": fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, label, description, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
21
+ ask,
22
+ askWithChoices,
23
+ askPassword,
24
+ createConnectionDefinition: fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, label, description, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
25
+ const spinner = new Spinner();
19
26
  const add = async () => {
27
+ spinner.start("Creating connection definition...");
20
28
  try {
21
29
  await addConnectionDefinition({
22
30
  token,
@@ -33,9 +41,11 @@ export const addConnectionMachine = setup({
33
41
  clientSecret,
34
42
  scopes,
35
43
  });
44
+ spinner.success("Connection definition created");
36
45
  sendBack({ type: "Success" });
37
46
  }
38
47
  catch (error) {
48
+ spinner.error(`Failed to create connection definition: ${chalk.red(error.message)}`);
39
49
  sendBack({ type: "Error", error: error.message });
40
50
  }
41
51
  };
@@ -43,95 +53,78 @@ export const addConnectionMachine = setup({
43
53
  }),
44
54
  loadDeveloperConfig,
45
55
  loadAppConfig,
46
- "keyboard-navigation": fromCallback(({ sendBack }) => {
47
- readline.emitKeypressEvents(process.stdin);
48
- if (process.stdin.isTTY) {
49
- process.stdin.setRawMode(true);
50
- }
51
- const handleKeyPress = (_, key) => {
52
- if (key.meta) {
53
- switch (key.name) {
54
- case "down":
55
- sendBack({ type: "Next" });
56
- break;
57
- case "up":
58
- sendBack({ type: "Previous" });
59
- break;
60
- default:
61
- break;
62
- }
63
- }
64
- };
65
- process.stdin.on("keypress", handleKeyPress);
66
- return () => {
67
- process.stdin.removeListener("keypress", handleKeyPress);
68
- if (process.stdin.isTTY) {
69
- process.stdin.setRawMode(false);
70
- }
71
- };
72
- }),
73
- "validateGlobal": fromCallback(({ sendBack, input: { developer, config, global } }) => {
56
+ validateGlobal: fromCallback(({ sendBack, input: { developer, config, global } }) => {
57
+ const spinner = new Spinner();
58
+ spinner.start("Checking if global connection already exists..." + global);
74
59
  fetchConnections({
75
60
  token: developer.token,
76
61
  devSlug: developer.slug,
77
62
  appId: config.id,
78
63
  major: config.major,
79
- }).then((connections) => {
80
- if (connections.some((connection) => connection.global === global)) {
64
+ })
65
+ .then((connections) => {
66
+ const weHaveGlobal = connections.some((connection) => connection.global);
67
+ const weHaveUser = connections.some((connection) => !connection.global);
68
+ if (weHaveGlobal && weHaveUser) {
69
+ spinner.stop();
70
+ sendBack({
71
+ type: "Error",
72
+ error: "Both global and user connections already exist",
73
+ });
74
+ }
75
+ else if (weHaveGlobal && global) {
76
+ spinner.error("Global connection already exists");
77
+ sendBack({ type: "Global Taken" });
78
+ }
79
+ else if (weHaveUser && !global) {
80
+ spinner.error("A user connection already exists");
81
81
  sendBack({ type: "Global Taken" });
82
82
  }
83
83
  else {
84
+ spinner.success("All good.");
84
85
  sendBack({ type: "Valid Global" });
85
86
  }
87
+ })
88
+ .catch((error) => {
89
+ spinner.stop();
90
+ sendBack({ type: "Error", error: error.message });
86
91
  });
87
92
  }),
88
93
  },
89
94
  actions: {
95
+ printLogo,
96
+ showError,
97
+ showConfigInstructions: ({ context }) => printInstallInstructions(context.configError),
90
98
  clearError: assign({
91
99
  error: () => undefined,
92
100
  }),
93
- initializeLabel: assign({
94
- label: (_, params) => {
95
- if (params.label?.trim())
96
- return params.label;
97
- switch (params.connectionType) {
98
- case "oauth2-code":
99
- return "Connection";
100
- case "secret":
101
- case undefined:
102
- return "Access Token";
103
- default:
104
- return params.connectionType;
105
- }
106
- },
107
- }),
108
101
  setError: assign({
109
102
  error: (_, params) => params.error,
110
103
  }),
111
104
  setConnectionType: assign({
112
- connectionType: (_, params) => params.connectionType,
105
+ connectionType: (_, params) => params.output,
113
106
  }),
114
107
  setAuthorizeUrl: assign({
115
- authorizeUrl: (_, params) => params.authorizeUrl,
108
+ authorizeUrl: (_, params) => params.output,
116
109
  }),
117
110
  setAccessTokenUrl: assign({
118
- accessTokenUrl: (_, params) => params.accessTokenUrl,
111
+ accessTokenUrl: (_, params) => params.output,
119
112
  }),
120
113
  setScopes: assign({
121
- scopes: (_, params) => params.scopes,
114
+ scopes: (_, params) => params.output,
122
115
  }),
123
116
  setClientId: assign({
124
- clientId: (_, params) => params.clientId,
117
+ clientId: (_, params) => params.output,
125
118
  }),
126
119
  setClientSecret: assign({
127
- clientSecret: (_, params) => params.clientSecret,
120
+ clientSecret: (_, params) => params.output,
128
121
  }),
129
122
  setDeveloperConfig: assign({
130
123
  developer: (_, params) => params,
131
124
  }),
132
125
  setInvalidAuthorizeUrl: assign({
133
126
  error: (_, params) => {
134
- const result = options.shape.authorizeUrl
127
+ const result = optionsSchema.shape.authorizeUrl
135
128
  .unwrap()
136
129
  .safeParse(params.authorizeUrl?.trim());
137
130
  return result.success ? undefined : result.error.errors[0].message;
@@ -139,7 +132,7 @@ export const addConnectionMachine = setup({
139
132
  }),
140
133
  setInvalidAccessTokenUrl: assign({
141
134
  error: (_, params) => {
142
- const result = options.shape.accessTokenUrl
135
+ const result = optionsSchema.shape.accessTokenUrl
143
136
  .unwrap()
144
137
  .safeParse(params.accessTokenUrl?.trim());
145
138
  return result.success ? undefined : result.error.errors[0].message;
@@ -147,19 +140,21 @@ export const addConnectionMachine = setup({
147
140
  }),
148
141
  setInvalidScopes: assign({
149
142
  error: (_, params) => {
150
- const result = options.shape.scopes.unwrap().safeParse(params.scopes?.trim());
143
+ const result = optionsSchema.shape.scopes.unwrap().safeParse(params.scopes?.trim());
151
144
  return result.success ? undefined : result.error.errors[0].message;
152
145
  },
153
146
  }),
154
147
  setInvalidClientId: assign({
155
148
  error: (_, params) => {
156
- const result = options.shape.clientId.unwrap().safeParse(params.clientId?.trim());
149
+ const result = optionsSchema.shape.clientId
150
+ .unwrap()
151
+ .safeParse(params.clientId?.trim());
157
152
  return result.success ? undefined : result.error.errors[0].message;
158
153
  },
159
154
  }),
160
155
  setInvalidClientSecret: assign({
161
156
  error: (_, params) => {
162
- const result = options.shape.clientSecret
157
+ const result = optionsSchema.shape.clientSecret
163
158
  .unwrap()
164
159
  .safeParse(params.clientSecret?.trim());
165
160
  return result.success ? undefined : result.error.errors[0].message;
@@ -169,13 +164,13 @@ export const addConnectionMachine = setup({
169
164
  configError: (_, params) => params.configError,
170
165
  }),
171
166
  setLabel: assign({
172
- label: (_, params) => params.label,
167
+ label: (_, params) => params.output,
173
168
  }),
174
169
  setDescription: assign({
175
- description: (_, params) => params.description,
170
+ description: (_, params) => params.output,
176
171
  }),
177
172
  setGlobal: assign({
178
- global: (_, params) => params.global,
173
+ global: (_, params) => params.output === "true",
179
174
  }),
180
175
  setAppConfig: assign({
181
176
  config: (_, params) => params.config,
@@ -189,16 +184,19 @@ export const addConnectionMachine = setup({
189
184
  "have label": (_, params) => Boolean(params.label),
190
185
  "have description": (_, params) => Boolean(params.description),
191
186
  "have authorize url": (_, params) => params.connectionType === "secret" ||
192
- options.shape.authorizeUrl.unwrap().safeParse(params.authorizeUrl?.trim()).success,
187
+ optionsSchema.shape.authorizeUrl.unwrap().safeParse(params.authorizeUrl?.trim())
188
+ .success,
193
189
  "have access token url": (_, params) => params.connectionType === "secret" ||
194
- options.shape.accessTokenUrl.unwrap().safeParse(params.accessTokenUrl?.trim()).success,
190
+ optionsSchema.shape.accessTokenUrl.unwrap().safeParse(params.accessTokenUrl?.trim())
191
+ .success,
195
192
  "have scopes": (_, params) => params.connectionType === "secret" ||
196
- options.shape.scopes.unwrap().safeParse(params.scopes?.trim()).success,
193
+ optionsSchema.shape.scopes.unwrap().safeParse(params.scopes?.trim()).success,
197
194
  "have client id": (_, params) => params.connectionType === "secret" ||
198
- options.shape.clientId.unwrap().safeParse(params.clientId?.trim()).success,
195
+ optionsSchema.shape.clientId.unwrap().safeParse(params.clientId?.trim()).success,
199
196
  "have global": (_, params) => params.global !== undefined,
200
197
  "have client secret": (_, params) => params.connectionType === "secret" ||
201
- options.shape.clientSecret.unwrap().safeParse(params.clientSecret?.trim()).success,
198
+ optionsSchema.shape.clientSecret.unwrap().safeParse(params.clientSecret?.trim())
199
+ .success,
202
200
  "is oauth": (_, params) => params.connectionType === "oauth2-code",
203
201
  },
204
202
  }).createMachine({
@@ -225,6 +223,7 @@ export const addConnectionMachine = setup({
225
223
  },
226
224
  "Show config instructions": {
227
225
  type: "final",
226
+ entry: "showConfigInstructions",
228
227
  },
229
228
  "Loading App Config": {
230
229
  invoke: {
@@ -244,6 +243,7 @@ export const addConnectionMachine = setup({
244
243
  },
245
244
  "Error": {
246
245
  type: "final",
246
+ entry: { type: "showError", params: ({ context }) => ({ error: context.error }) },
247
247
  },
248
248
  "Creating connection definition": {
249
249
  invoke: {
@@ -274,114 +274,84 @@ export const addConnectionMachine = setup({
274
274
  type: "final",
275
275
  },
276
276
  "Ask for Authorize URL": {
277
- on: {
278
- "Update Authorize URL": {
279
- actions: { type: "setAuthorizeUrl", params: ({ event }) => event },
280
- },
281
- "Submit": [
282
- {
283
- target: "Do we have an access token url?",
284
- guard: { type: "have authorize url", params: ({ context }) => context },
285
- actions: "clearError",
286
- },
287
- {
288
- target: "Ask for Authorize URL",
289
- actions: { type: "setInvalidAuthorizeUrl", params: ({ context }) => context },
277
+ invoke: {
278
+ src: "ask",
279
+ input: {
280
+ message: "e.g. https://authorization-server.com/oauth/authorize\nOAuth Authorize URL:",
281
+ required: true,
282
+ validate: (value) => {
283
+ const result = optionsSchema.shape.authorizeUrl
284
+ .unwrap()
285
+ .safeParse(value.trim());
286
+ return result.success ? true : result.error.errors[0].message;
290
287
  },
291
- ],
292
- "Previous": "Ask for global",
293
- "Next": {
294
- target: "Ask for Access Token URL",
295
- guard: { type: "have authorize url", params: ({ context }) => context },
288
+ },
289
+ onDone: {
290
+ target: "Do we have an access token url?",
291
+ actions: { type: "setAuthorizeUrl", params: ({ event }) => event },
296
292
  },
297
293
  },
298
294
  },
299
295
  "Ask for Access Token URL": {
300
- on: {
301
- "Submit": [
302
- {
303
- target: "Do we have scopes?",
304
- guard: { type: "have access token url", params: ({ context }) => context },
305
- actions: "clearError",
306
- },
307
- {
308
- target: "Ask for Access Token URL",
309
- actions: { type: "setInvalidAccessTokenUrl", params: ({ context }) => context },
296
+ invoke: {
297
+ src: "ask",
298
+ input: {
299
+ message: "e.g. https://authorization-server.com/oauth/token\nOAuth Access Token URL:",
300
+ required: true,
301
+ validate: (value) => {
302
+ const result = optionsSchema.shape.accessTokenUrl
303
+ .unwrap()
304
+ .safeParse(value.trim());
305
+ return result.success ? true : result.error.errors[0].message;
310
306
  },
311
- ],
312
- "Update Access Token URL": {
313
- actions: { type: "setAccessTokenUrl", params: ({ event }) => event },
314
307
  },
315
- "Previous": "Ask for Authorize URL",
316
- "Next": {
317
- target: "Ask for Scopes",
318
- guard: { type: "have access token url", params: ({ context }) => context },
308
+ onDone: {
309
+ target: "Do we have scopes?",
310
+ actions: { type: "setAccessTokenUrl", params: ({ event }) => event },
319
311
  },
320
312
  },
321
313
  },
322
314
  "Ask for Scopes": {
323
- on: {
324
- "Update Scopes": {
325
- actions: { type: "setScopes", params: ({ event }) => event },
326
- },
327
- "Submit": [
328
- {
329
- target: "Do we have a client id?",
330
- guard: { type: "have scopes", params: ({ context }) => context },
331
- actions: "clearError",
332
- },
333
- {
334
- target: "Ask for Scopes",
335
- actions: { type: "setInvalidScopes", params: ({ context }) => context },
315
+ invoke: {
316
+ src: "ask",
317
+ input: {
318
+ message: "e.g. read:user,read:org\nOAuth Scopes:",
319
+ required: true,
320
+ validate: (value) => {
321
+ const result = optionsSchema.shape.scopes.unwrap().safeParse(value.trim());
322
+ return result.success ? true : result.error.errors[0].message;
336
323
  },
337
- ],
338
- "Previous": "Ask for Access Token URL",
339
- "Next": {
340
- target: "Ask for Client ID",
341
- guard: { type: "have scopes", params: ({ context }) => context },
324
+ },
325
+ onDone: {
326
+ target: "Do we have a client id?",
327
+ actions: { type: "setScopes", params: ({ event }) => event },
342
328
  },
343
329
  },
344
330
  },
345
331
  "Ask for Client ID": {
346
- on: {
347
- "Update Client ID": {
348
- actions: { type: "setClientId", params: ({ event }) => event },
332
+ invoke: {
333
+ src: "ask",
334
+ input: {
335
+ message: "OAuth Client ID:",
336
+ required: true,
349
337
  },
350
- "Submit": [
351
- {
352
- target: "Do we have a client secret?",
353
- guard: { type: "have client id", params: ({ context }) => context },
354
- actions: "clearError",
355
- },
356
- {
357
- target: "Ask for Client ID",
358
- actions: { type: "setInvalidClientId", params: ({ context }) => context },
359
- },
360
- ],
361
- "Previous": "Ask for Scopes",
362
- "Next": {
363
- target: "Ask for Client Secret",
364
- guard: { type: "have client id", params: ({ context }) => context },
338
+ onDone: {
339
+ target: "Do we have a client secret?",
340
+ actions: { type: "setClientId", params: ({ event }) => event },
365
341
  },
366
342
  },
367
343
  },
368
344
  "Ask for Client Secret": {
369
- on: {
370
- "Update Client Secret": {
345
+ invoke: {
346
+ src: "askPassword",
347
+ input: {
348
+ message: "OAuth Client Secret:",
349
+ required: true,
350
+ },
351
+ onDone: {
352
+ target: "Creating connection definition",
371
353
  actions: { type: "setClientSecret", params: ({ event }) => event },
372
354
  },
373
- "Submit": [
374
- {
375
- target: "Creating connection definition",
376
- guard: { type: "have client secret", params: ({ context }) => context },
377
- actions: "clearError",
378
- },
379
- {
380
- target: "Ask for Client Secret",
381
- actions: { type: "setInvalidClientSecret", params: ({ context }) => context },
382
- },
383
- ],
384
- "Previous": "Ask for Client ID",
385
355
  },
386
356
  },
387
357
  "Do we have an authorize url?": {
@@ -450,15 +420,18 @@ export const addConnectionMachine = setup({
450
420
  ],
451
421
  },
452
422
  "Ask for connection type": {
453
- on: {
454
- "Choose Connection Type": {
423
+ invoke: {
424
+ src: "askWithChoices",
425
+ input: {
426
+ message: "What type of connection would you like to add?",
427
+ choices: [
428
+ { name: "OAuth2 Code", value: "oauth2-code" },
429
+ { name: "Secret", value: "secret" },
430
+ ],
431
+ },
432
+ onDone: {
455
433
  target: "Do we have a label?",
456
- actions: [{ type: "setConnectionType", params: ({ event }) => event }],
457
- reenter: true,
458
- },
459
- "Next": {
460
- target: "Ask for label",
461
- guard: { type: "have connection type", params: ({ context }) => context },
434
+ actions: { type: "setConnectionType", params: ({ event }) => event },
462
435
  },
463
436
  },
464
437
  },
@@ -509,57 +482,49 @@ export const addConnectionMachine = setup({
509
482
  ],
510
483
  },
511
484
  "Ask for label": {
512
- on: {
513
- "Submit": {
485
+ invoke: {
486
+ src: "ask",
487
+ input: ({ context }) => ({
488
+ message: "What would you like to call this connection?",
489
+ required: true,
490
+ default: context.connectionType === "oauth2-code"
491
+ ? "Connection"
492
+ : context.connectionType === "secret"
493
+ ? "Access Token"
494
+ : "",
495
+ }),
496
+ onDone: {
514
497
  target: "Do we have a description?",
515
- guard: { type: "have label", params: ({ context }) => context },
516
- reenter: true,
517
- },
518
- "Update Label": {
519
498
  actions: { type: "setLabel", params: ({ event }) => event },
520
499
  },
521
- "Previous": "Ask for connection type",
522
- "Next": {
523
- target: "Ask for description",
524
- guard: { type: "have label", params: ({ context }) => context },
525
- },
526
500
  },
527
- entry: [
528
- {
529
- type: "initializeLabel",
530
- params: ({ context }) => context,
531
- },
532
- ],
533
501
  },
534
502
  "Ask for description": {
535
- on: {
536
- "Update Description": {
537
- actions: { type: "setDescription", params: ({ event }) => event },
503
+ invoke: {
504
+ src: "ask",
505
+ input: {
506
+ message: "Description: (will be displayed in the UI)",
507
+ required: true,
538
508
  },
539
- "Submit": {
509
+ onDone: {
540
510
  target: "Do we have global?",
541
- guard: { type: "have description", params: ({ context }) => context },
542
- actions: "clearError",
543
- },
544
- "Previous": "Ask for label",
545
- "Next": {
546
- target: "Ask for global",
547
- guard: { type: "have description", params: ({ context }) => context },
511
+ actions: { type: "setDescription", params: ({ event }) => event },
548
512
  },
549
513
  },
550
514
  },
551
515
  "Ask for global": {
552
- on: {
553
- "Choose Global": {
516
+ invoke: {
517
+ src: "askWithChoices",
518
+ input: ({ context }) => ({
519
+ message: `A ${context.connectionType === "secret" ? "secret" : "token"} will be created for:`,
520
+ choices: [
521
+ { name: "The entire workspace", value: "true" },
522
+ { name: "An individual user", value: "false" },
523
+ ],
524
+ }),
525
+ onDone: {
554
526
  target: "Validating Global",
555
527
  actions: { type: "setGlobal", params: ({ event }) => event },
556
- reenter: true,
557
- },
558
- "Previous": "Ask for description",
559
- "Next": {
560
- target: "Validating Global",
561
- guard: { type: "have global", params: ({ context }) => context },
562
- reenter: true,
563
528
  },
564
529
  },
565
530
  },
@@ -573,6 +538,11 @@ export const addConnectionMachine = setup({
573
538
  target: "Split on connection type",
574
539
  reenter: true,
575
540
  },
541
+ "Error": {
542
+ target: "Error",
543
+ reenter: true,
544
+ actions: { type: "setError", params: ({ event }) => event },
545
+ },
576
546
  },
577
547
  invoke: {
578
548
  src: "validateGlobal",
@@ -585,7 +555,5 @@ export const addConnectionMachine = setup({
585
555
  },
586
556
  },
587
557
  initial: "Loading Developer Config",
588
- invoke: {
589
- src: "keyboard-navigation",
590
- },
558
+ entry: "printLogo",
591
559
  });
@@ -1,8 +1,12 @@
1
1
  import { assign, setup } from "xstate";
2
+ import Spinner from "tiny-spinner";
2
3
  import { codeGenMachine } from "./code-gen-machine.js";
3
4
  import { jsMachine } from "./js-machine.js";
4
5
  import { tsMachine } from "./ts-machine.js";
6
+ import { printError } from "../util/typescript.js";
7
+ import { printLogo } from "./actions.js";
5
8
  const processes = ["javascript", "typescript"];
9
+ const spinner = new Spinner();
6
10
  export const buildMachine = setup({
7
11
  types: {
8
12
  context: {},
@@ -19,9 +23,26 @@ export const buildMachine = setup({
19
23
  "all success": ({ context }) => context.successful.length === processes.length,
20
24
  },
21
25
  actions: {
26
+ buildingStart: () => {
27
+ spinner.start("🔨 Building...");
28
+ },
29
+ buildFail: ({ self }) => {
30
+ spinner.error("Build failed");
31
+ const snapshot = self.getSnapshot();
32
+ const { tsErrors = [] } = snapshot.context;
33
+ process.stderr.write("\n");
34
+ tsErrors.forEach(printError);
35
+ },
36
+ buildSuccess: () => {
37
+ spinner.success("Build succeeded");
38
+ },
39
+ printLogo,
22
40
  setError: assign({
23
41
  failed: ({ context }, params) => [...context.failed, params.process],
24
42
  }),
43
+ setTypeScriptErrors: assign({
44
+ tsErrors: ({ context }, params) => params.errors,
45
+ }),
25
46
  setSuccess: assign({
26
47
  successful: ({ context }, params) => [
27
48
  ...context.successful,
@@ -35,9 +56,11 @@ export const buildMachine = setup({
35
56
  states: {
36
57
  "Errored": {
37
58
  type: "final",
59
+ entry: "buildFail",
38
60
  },
39
61
  "Success": {
40
62
  type: "final",
63
+ entry: "buildSuccess",
41
64
  },
42
65
  "Building": {
43
66
  type: "parallel",
@@ -89,10 +112,16 @@ export const buildMachine = setup({
89
112
  },
90
113
  "TypeScript Error": {
91
114
  target: "Waiting",
92
- actions: {
93
- type: "setError",
94
- params: { process: "typescript" },
95
- },
115
+ actions: [
116
+ {
117
+ type: "setError",
118
+ params: { process: "typescript" },
119
+ },
120
+ {
121
+ type: "setTypeScriptErrors",
122
+ params: ({ event }) => event,
123
+ },
124
+ ],
96
125
  },
97
126
  },
98
127
  invoke: {
@@ -129,6 +158,7 @@ export const buildMachine = setup({
129
158
  initial: "Code Generating",
130
159
  },
131
160
  },
161
+ entry: "buildingStart",
132
162
  },
133
163
  "All Done": {
134
164
  always: [
@@ -144,4 +174,5 @@ export const buildMachine = setup({
144
174
  },
145
175
  },
146
176
  initial: "Building",
177
+ entry: "printLogo",
147
178
  });