attio 0.0.1-experimental.20250303 → 0.0.1-experimental.20250321
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.
- package/lib/api/fetch-workspaces.js +24 -0
- package/lib/attio.js +0 -2
- package/lib/commands/version/index.js +1 -3
- package/lib/machines/dev-machine.js +206 -14
- package/lib/machines/init-machine.js +11 -6
- package/lib/templates/javascript/src/cat-fact.jsx +16 -0
- package/lib/templates/javascript/src/get-cat-fact.server.js +6 -0
- package/lib/templates/javascript/src/hello-world-dialog.jsx +6 -5
- package/lib/templates/typescript/src/cat-fact.tsx +16 -0
- package/lib/templates/typescript/src/get-cat-fact.server.ts +6 -0
- package/lib/templates/typescript/src/hello-world-dialog.tsx +5 -4
- package/lib/util/load-developer-config.js +10 -2
- package/package.json +1 -1
- package/lib/api/publish-version.js +0 -15
- package/lib/commands/connection/add.js +0 -67
- package/lib/commands/connection/index.js +0 -9
- package/lib/commands/connection/list.js +0 -20
- package/lib/commands/connection/remove.js +0 -20
- package/lib/commands/version/publish.js +0 -48
- package/lib/machines/add-connection-machine.js +0 -559
- package/lib/machines/list-connections-machine.js +0 -174
- package/lib/machines/publish-version-machine.js +0 -230
- package/lib/machines/remove-connection-machine.js +0 -253
- package/lib/templates/javascript/src/advice.jsx +0 -16
- package/lib/templates/javascript/src/get-advice.server.js +0 -6
- package/lib/templates/typescript/src/advice.tsx +0 -16
- package/lib/templates/typescript/src/get-advice.server.ts +0 -6
|
@@ -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
|
+
}
|
package/lib/attio.js
CHANGED
|
@@ -3,7 +3,6 @@ import { Command } from "commander";
|
|
|
3
3
|
import { init } from "./commands/init.js";
|
|
4
4
|
import { build } from "./commands/build.js";
|
|
5
5
|
import { dev } from "./commands/dev.js";
|
|
6
|
-
import { connection } from "./commands/connection/index.js";
|
|
7
6
|
import { version } from "./commands/version/index.js";
|
|
8
7
|
const program = new Command();
|
|
9
8
|
program
|
|
@@ -13,6 +12,5 @@ program
|
|
|
13
12
|
.addCommand(init)
|
|
14
13
|
.addCommand(build)
|
|
15
14
|
.addCommand(dev)
|
|
16
|
-
.addCommand(connection)
|
|
17
15
|
.addCommand(version)
|
|
18
16
|
.parse();
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { versionCreate } from "./create.js";
|
|
3
3
|
import { versionList } from "./list.js";
|
|
4
|
-
import { versionPublish } from "./publish.js";
|
|
5
4
|
export const version = new Command("version")
|
|
6
5
|
.description("Manage app versions")
|
|
7
6
|
.addCommand(versionCreate)
|
|
8
|
-
.addCommand(versionList)
|
|
9
|
-
.addCommand(versionPublish);
|
|
7
|
+
.addCommand(versionList);
|
|
@@ -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
|
-
|
|
252
|
-
|
|
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}/
|
|
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: "
|
|
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
|
-
"
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
});
|
|
@@ -86,13 +86,18 @@ export const initMachine = setup({
|
|
|
86
86
|
loadAppInfo: fromPromise(async ({ input: { token, developerSlug, appSlug } }) => {
|
|
87
87
|
const spinner = new Spinner();
|
|
88
88
|
spinner.start("Loading app information...");
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
try {
|
|
90
|
+
const appInfo = await getAppInfo({ token, developerSlug, appSlug });
|
|
91
|
+
if (appInfo === null) {
|
|
92
|
+
spinner.error("App not found");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
spinner.success(`App found: ${appInfo.title}`);
|
|
96
|
+
return appInfo;
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
spinner.stop();
|
|
93
100
|
}
|
|
94
|
-
spinner.success(`App found: ${appInfo.title}`);
|
|
95
|
-
return appInfo;
|
|
96
101
|
}),
|
|
97
102
|
},
|
|
98
103
|
actions: {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import {TextBlock, useAsyncCache} from "attio/client"
|
|
3
|
+
import getCatFact from "./get-cat-fact.server"
|
|
4
|
+
|
|
5
|
+
export function CatFact({recordId}) {
|
|
6
|
+
// By passing in the recordId, the result will be cached for each recordId
|
|
7
|
+
const {
|
|
8
|
+
values: {catFact},
|
|
9
|
+
// ^^^^^^^– this key matches
|
|
10
|
+
// vvvvvvv– this key
|
|
11
|
+
} = useAsyncCache({catFact: [getCatFact, recordId]})
|
|
12
|
+
// ^^^^^^^^^^ ^^^^^^^^
|
|
13
|
+
// async fn parameter(s)
|
|
14
|
+
|
|
15
|
+
return <TextBlock align="center">{`"${catFact}"`}</TextBlock>
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export default async function getCatFact(recordId) {
|
|
2
|
+
// We don't really need the recordId for this API, but this is how we could use a parameter
|
|
3
|
+
const response = await fetch(`https://catfact.ninja/fact?${recordId}`)
|
|
4
|
+
const data = await response.json()
|
|
5
|
+
return data.fact
|
|
6
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import {TextBlock} from "attio/client"
|
|
3
|
-
import {
|
|
3
|
+
import {CatFact} from "./cat-fact"
|
|
4
4
|
|
|
5
|
-
const Loading = () => <TextBlock>Loading
|
|
5
|
+
const Loading = () => <TextBlock>Loading cat fact...</TextBlock>
|
|
6
6
|
|
|
7
|
-
export function HelloWorldDialog
|
|
7
|
+
export function HelloWorldDialog({recordId}) {
|
|
8
|
+
// A simple counter to demonstrate that this is just regular React code.
|
|
8
9
|
const [seconds, setSeconds] = React.useState(0)
|
|
9
10
|
React.useEffect(() => {
|
|
10
11
|
const timeout = setTimeout(() => setSeconds(seconds + 1), 1000)
|
|
@@ -16,9 +17,9 @@ export function HelloWorldDialog ({recordId}) {
|
|
|
16
17
|
<TextBlock align="left">
|
|
17
18
|
I am a dialog. I have been open for: {seconds} second{seconds === 1 ? "" : "s"}
|
|
18
19
|
</TextBlock>
|
|
19
|
-
{/* The hook in
|
|
20
|
+
{/* The hook in CatFact will suspend until the cat fact is loaded. */}
|
|
20
21
|
<React.Suspense fallback={<Loading />}>
|
|
21
|
-
<
|
|
22
|
+
<CatFact recordId={recordId} />
|
|
22
23
|
</React.Suspense>
|
|
23
24
|
</>
|
|
24
25
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import {TextBlock, useAsyncCache} from "attio/client"
|
|
3
|
+
import getCatFact from "./get-cat-fact.server"
|
|
4
|
+
|
|
5
|
+
export function CatFact({recordId}: {recordId: string}) {
|
|
6
|
+
// By passing in the recordId, the result will be cached for each recordId
|
|
7
|
+
const {
|
|
8
|
+
values: {catFact},
|
|
9
|
+
// ^^^^^^^– this key matches
|
|
10
|
+
// vvvvvvv– this key
|
|
11
|
+
} = useAsyncCache({catFact: [getCatFact, recordId]})
|
|
12
|
+
// ^^^^^^^^^^ ^^^^^^^^
|
|
13
|
+
// async fn parameter(s)
|
|
14
|
+
|
|
15
|
+
return <TextBlock align="center">{`"${catFact}"`}</TextBlock>
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export default async function getCatFact(recordId: string): Promise<string> {
|
|
2
|
+
// We don't really need the recordId for this API, but this is how we could use a parameter
|
|
3
|
+
const response = await fetch(`https://catfact.ninja/fact?${recordId}`)
|
|
4
|
+
const data = await response.json()
|
|
5
|
+
return data.fact
|
|
6
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import {TextBlock} from "attio/client"
|
|
3
|
-
import {
|
|
3
|
+
import {CatFact} from "./cat-fact"
|
|
4
4
|
|
|
5
|
-
const Loading = () => <TextBlock>Loading
|
|
5
|
+
const Loading = () => <TextBlock>Loading cat fact...</TextBlock>
|
|
6
6
|
|
|
7
7
|
export function HelloWorldDialog({recordId}: {recordId: string}) {
|
|
8
|
+
// A simple counter to demonstrate that this is just regular React code.
|
|
8
9
|
const [seconds, setSeconds] = React.useState(0)
|
|
9
10
|
React.useEffect(() => {
|
|
10
11
|
const timeout = setTimeout(() => setSeconds(seconds + 1), 1000)
|
|
@@ -16,9 +17,9 @@ export function HelloWorldDialog({recordId}: {recordId: string}) {
|
|
|
16
17
|
<TextBlock align="left">
|
|
17
18
|
I am a dialog. I have been open for: {seconds} second{seconds === 1 ? "" : "s"}
|
|
18
19
|
</TextBlock>
|
|
19
|
-
{/* The hook in
|
|
20
|
+
{/* The hook in CatFact will suspend until the cat fact is loaded. */}
|
|
20
21
|
<React.Suspense fallback={<Loading />}>
|
|
21
|
-
<
|
|
22
|
+
<CatFact recordId={recordId} />
|
|
22
23
|
</React.Suspense>
|
|
23
24
|
</>
|
|
24
25
|
)
|
|
@@ -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,15 +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 publishVersionSchema = z.object({
|
|
6
|
-
success: z.literal(true),
|
|
7
|
-
});
|
|
8
|
-
export async function publishVersion({ token, devSlug, appId, major, minor, }) {
|
|
9
|
-
const response = await fetch(`${API}/developer-accounts/${devSlug}/apps/${appId}/prod-versions/${major}/${minor}/publish`, {
|
|
10
|
-
method: "POST",
|
|
11
|
-
headers: makeHeaders(token),
|
|
12
|
-
});
|
|
13
|
-
await handleError(response);
|
|
14
|
-
return publishVersionSchema.parse(await response.json());
|
|
15
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from "commander";
|
|
2
|
-
import { createActor } from "xstate";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { addConnectionMachine } from "../../machines/add-connection-machine.js";
|
|
5
|
-
export const optionsSchema = z.object({
|
|
6
|
-
label: z.string().optional(),
|
|
7
|
-
description: z.string().optional(),
|
|
8
|
-
type: z.enum(["secret", "oauth2-code"]).optional(),
|
|
9
|
-
authorizeUrl: z
|
|
10
|
-
.string()
|
|
11
|
-
.url("Invalid URL, e.g. https://authorization-server.com/authorize")
|
|
12
|
-
.startsWith("https://", { message: "Must provide secure authorize URL (https://...)" })
|
|
13
|
-
.optional(),
|
|
14
|
-
accessTokenUrl: z
|
|
15
|
-
.string()
|
|
16
|
-
.url("Invalid URL, e.g. https://authorization-server.com/token")
|
|
17
|
-
.startsWith("https://", { message: "Must provide secure access token URL (https://...)" })
|
|
18
|
-
.optional(),
|
|
19
|
-
scopes: z
|
|
20
|
-
.string()
|
|
21
|
-
.refine((value) => value.split(",").every((scope) => scope.trim().length > 0), {
|
|
22
|
-
message: "Invalid OAuth scopes format. Must be a comma-delimited list of non-empty strings.",
|
|
23
|
-
})
|
|
24
|
-
.optional(),
|
|
25
|
-
clientId: z.string().optional(),
|
|
26
|
-
clientSecret: z.string().optional(),
|
|
27
|
-
dev: z.boolean().default(false),
|
|
28
|
-
});
|
|
29
|
-
export const connectionAdd = new Command("add")
|
|
30
|
-
.description("Create a new connection for your Attio app")
|
|
31
|
-
.addOption(new Option("--label", "The label for your connection that will be displayed to users"))
|
|
32
|
-
.addOption(new Option("--description", "A more detailed description of your connection that will be displayed to users"))
|
|
33
|
-
.addOption(new Option("--type <type>", "The type of connection to create").choices([
|
|
34
|
-
"secret",
|
|
35
|
-
"oauth2-code",
|
|
36
|
-
]))
|
|
37
|
-
.addOption(new Option("--authorize-url <url>", "The authorize URL for OAuth connection"))
|
|
38
|
-
.addOption(new Option("--access-token-url <url>", "The access token URL for OAuth connection"))
|
|
39
|
-
.addOption(new Option("--scopes <scopes>", "A comma separated list of scopes for OAuth connection"))
|
|
40
|
-
.addOption(new Option("--client-id <id>", "The client ID for OAuth connection"))
|
|
41
|
-
.addOption(new Option("--client-secret <secret>", "The client secret for OAuth connection"))
|
|
42
|
-
.addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
|
|
43
|
-
.action((unparsedOptions) => {
|
|
44
|
-
let options;
|
|
45
|
-
try {
|
|
46
|
-
options = optionsSchema.parse(unparsedOptions);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
if (error instanceof z.ZodError) {
|
|
50
|
-
process.stderr.write("\nInvalid options:\n");
|
|
51
|
-
error.errors.forEach((err) => {
|
|
52
|
-
process.stderr.write(`- ${err.path.map((p) => String(p).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)).join(".")}: ${err.message}\n`);
|
|
53
|
-
});
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
const actor = createActor(addConnectionMachine, {
|
|
59
|
-
input: options,
|
|
60
|
-
});
|
|
61
|
-
if (options.dev) {
|
|
62
|
-
actor.subscribe((state) => {
|
|
63
|
-
console.log("state:", state.value);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
actor.start();
|
|
67
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { connectionAdd } from "./add.js";
|
|
3
|
-
import { connectionList } from "./list.js";
|
|
4
|
-
import { connectionRemove } from "./remove.js";
|
|
5
|
-
export const connection = new Command("connection")
|
|
6
|
-
.description("Manage app connections")
|
|
7
|
-
.addCommand(connectionAdd)
|
|
8
|
-
.addCommand(connectionList)
|
|
9
|
-
.addCommand(connectionRemove);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from "commander";
|
|
2
|
-
import { createActor } from "xstate";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { listConnectionsMachine } from "../../machines/list-connections-machine.js";
|
|
5
|
-
export const optionsSchema = z.object({
|
|
6
|
-
dev: z.boolean().default(false),
|
|
7
|
-
});
|
|
8
|
-
export const connectionList = new Command("list")
|
|
9
|
-
.description("List all connections for the current major 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(listConnectionsMachine);
|
|
14
|
-
if (options.dev) {
|
|
15
|
-
actor.subscribe((state) => {
|
|
16
|
-
console.log("state:", state.value);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
actor.start();
|
|
20
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from "commander";
|
|
2
|
-
import { createActor } from "xstate";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { removeConnectionMachine } from "../../machines/remove-connection-machine.js";
|
|
5
|
-
export const optionsSchema = z.object({
|
|
6
|
-
dev: z.boolean().default(false),
|
|
7
|
-
});
|
|
8
|
-
export const connectionRemove = new Command("remove")
|
|
9
|
-
.description("Remove a connection from 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(removeConnectionMachine);
|
|
14
|
-
if (options.dev) {
|
|
15
|
-
actor.subscribe((state) => {
|
|
16
|
-
console.log("state:", state.value);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
actor.start();
|
|
20
|
-
});
|