attio 0.0.1-experimental.20251104.4 → 0.0.1-experimental.20251105.2

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 (68) hide show
  1. package/README.md +9 -1
  2. package/lib/api/api.js +183 -0
  3. package/lib/api/fetcher.js +69 -0
  4. package/lib/api/schemas.js +89 -0
  5. package/lib/attio-logo.js +24 -0
  6. package/lib/attio.js +22 -39637
  7. package/lib/auth/auth.js +174 -0
  8. package/lib/auth/keychain.js +108 -0
  9. package/lib/build/client/generate-client-entry.js +1 -1
  10. package/lib/build/server/generate-server-entry.js +1 -1
  11. package/lib/build/workflow-block-modules.js +1 -0
  12. package/lib/build.js +1 -1
  13. package/lib/client/workflow/use-configurator.d.ts +1 -1
  14. package/lib/client/workflow/use-configurator.d.ts.map +1 -1
  15. package/lib/commands/build.js +2 -1
  16. package/lib/commands/dev/boot.js +2 -1
  17. package/lib/commands/dev/graphql-code-gen.js +1 -1
  18. package/lib/commands/dev/graphql-server.js +6 -5
  19. package/lib/commands/dev/onboarding.js +2 -1
  20. package/lib/commands/dev/upload.js +2 -1
  21. package/lib/commands/dev.js +4 -5
  22. package/lib/commands/init/create-project.js +36 -0
  23. package/lib/commands/init.js +41 -9
  24. package/lib/commands/login.js +2 -1
  25. package/lib/commands/logout.js +2 -1
  26. package/lib/commands/logs.js +3 -1
  27. package/lib/commands/version/create.js +4 -1
  28. package/lib/commands/version/list.js +2 -1
  29. package/lib/commands/whoami.js +3 -1
  30. package/lib/constants/settings-files.js +2 -0
  31. package/lib/env.js +5 -0
  32. package/lib/errors.js +1 -0
  33. package/lib/print-errors.js +177 -0
  34. package/lib/sdk-version.js +1 -0
  35. package/lib/shared/workflow/config/friendly-value-of.d.ts +12 -6
  36. package/lib/shared/workflow/config/friendly-value-of.d.ts.map +1 -1
  37. package/lib/shared/workflow/config/node.d.ts +6 -1
  38. package/lib/shared/workflow/config/node.d.ts.map +1 -1
  39. package/lib/spinners/determine-workspace.spinner.js +2 -1
  40. package/lib/spinners/get-app-info.spinner.js +2 -1
  41. package/lib/spinners/get-versions.spinner.js +2 -1
  42. package/lib/template/README.md +21 -0
  43. package/lib/template/biome.jsonc +59 -0
  44. package/lib/template/eslint.attio.config.js +41 -0
  45. package/lib/template/graphql.config.json +9 -0
  46. package/lib/template/package.json +35 -0
  47. package/lib/template/src/app.settings.ts +8 -0
  48. package/lib/template/src/app.ts +10 -0
  49. package/lib/template/src/assets/icon.png +0 -0
  50. package/lib/template/src/get-stoic-quote.server.ts +14 -0
  51. package/lib/template/src/hello-world-action.tsx +18 -0
  52. package/lib/template/src/hello-world-dialog.tsx +27 -0
  53. package/lib/template/src/stoic-quote.tsx +21 -0
  54. package/lib/template/tsconfig.json +42 -0
  55. package/lib/tsconfig.lib.tsbuildinfo +1 -1
  56. package/lib/util/assert-app-settings.js +1 -1
  57. package/lib/util/can-write.js +11 -0
  58. package/lib/util/copy-with-replace.js +56 -0
  59. package/lib/util/create-directory.js +27 -0
  60. package/lib/util/exit-with-missing-app-settings.js +1 -1
  61. package/lib/util/exit-with-missing-entry-point.js +1 -1
  62. package/lib/util/find-available-port.js +37 -0
  63. package/lib/util/generate-settings-files.js +1 -1
  64. package/lib/util/hard-exit.js +6 -0
  65. package/lib/util/print-logo.js +4 -0
  66. package/lib/util/realtime.js +1 -1
  67. package/lib/util/spinner.js +61 -0
  68. package/package.json +1 -1
@@ -0,0 +1,174 @@
1
+ import { createHash, randomBytes } from "crypto";
2
+ import { serve } from "@hono/node-server";
3
+ import { Hono } from "hono";
4
+ import open from "open";
5
+ import { complete, errored, isErrored } from "@attio/fetchable-npm";
6
+ import { api } from "../api/api.js";
7
+ import { APP } from "../env.js";
8
+ import { printFetcherError, printKeychainError } from "../print-errors.js";
9
+ import { findAvailablePort } from "../util/find-available-port.js";
10
+ import { getKeychain } from "./keychain.js";
11
+ const MAX_TIMEOUT = 2_147_483_647;
12
+ class AuthenticatorImpl {
13
+ clientId = "f881c6f1-82d7-48a5-a581-649596167845";
14
+ isRefreshingToken = false;
15
+ refreshTimeout = null;
16
+ async ensureAuthed() {
17
+ const existingTokenResult = await getKeychain().load();
18
+ if (isErrored(existingTokenResult)) {
19
+ return this.promptToAuthenticate();
20
+ }
21
+ const existingToken = existingTokenResult.value;
22
+ if (existingToken === null || existingToken.expires_at < Date.now()) {
23
+ return this.promptToAuthenticate();
24
+ }
25
+ this.scheduleRefresh(existingToken);
26
+ return complete(existingToken.access_token);
27
+ }
28
+ async promptToAuthenticate() {
29
+ if (process.env.NODE_ENV !== "test") {
30
+ process.stdout.write("You need to log in with Attio. Press Enter to continue...\n\n");
31
+ await new Promise((resolve) => process.stdin.once("data", resolve));
32
+ }
33
+ return this.authenticate();
34
+ }
35
+ async authenticate() {
36
+ const existingTokenResult = await getKeychain().load();
37
+ if (isErrored(existingTokenResult)) {
38
+ return existingTokenResult;
39
+ }
40
+ const existingToken = existingTokenResult.value;
41
+ if (existingToken !== null && existingToken.expires_at > Date.now()) {
42
+ return complete(existingToken.access_token);
43
+ }
44
+ const verifier = randomBytes(32);
45
+ const verifierString = verifier.toString("base64url");
46
+ const hash = createHash("sha256");
47
+ hash.update(verifier);
48
+ const challenge = hash.digest();
49
+ const challengeString = challenge.toString("base64url");
50
+ const state = randomBytes(32).toString("base64url");
51
+ const port = await findAvailablePort(3000, 65000);
52
+ const redirectUri = `http://localhost:${port}`;
53
+ let resolveAsyncResult;
54
+ const asyncResult = new Promise((resolve) => {
55
+ resolveAsyncResult = resolve;
56
+ });
57
+ const app = new Hono();
58
+ let serverRef;
59
+ app.get("/", async (c) => {
60
+ const query = c.req.query();
61
+ const receivedCode = query.authorization_code;
62
+ const receivedState = query.state;
63
+ if (receivedState !== state) {
64
+ resolveAsyncResult(errored({ code: "OAUTH_STATE_MISMATCH" }));
65
+ }
66
+ if (!receivedCode) {
67
+ resolveAsyncResult(errored({ code: "NO_AUTHORIZATION_CODE" }));
68
+ }
69
+ const tokenResult = await api.exchangeToken({
70
+ receivedCode,
71
+ verifierString,
72
+ redirectUri,
73
+ clientId: this.clientId,
74
+ });
75
+ setTimeout(() => {
76
+ serverRef.close();
77
+ resolveAsyncResult(tokenResult);
78
+ }, 1_000);
79
+ return c.html(`
80
+ <html>
81
+ <body>
82
+ <script>window.location.href = '${APP}/authorized';</script>
83
+ </body>
84
+ </html>
85
+ `);
86
+ });
87
+ serverRef = serve({
88
+ fetch: app.fetch,
89
+ port,
90
+ });
91
+ try {
92
+ const authUrl = new URL(`${APP}/oidc/authorize`);
93
+ authUrl.searchParams.append("scope", "openid offline_access");
94
+ authUrl.searchParams.append("response_type", "code");
95
+ authUrl.searchParams.append("client_id", this.clientId);
96
+ authUrl.searchParams.append("redirect_uri", redirectUri);
97
+ authUrl.searchParams.append("state", state);
98
+ authUrl.searchParams.append("code_challenge", challengeString);
99
+ authUrl.searchParams.append("code_challenge_method", "S256");
100
+ await open(authUrl.toString());
101
+ const tokenResult = await asyncResult;
102
+ if (isErrored(tokenResult)) {
103
+ return tokenResult;
104
+ }
105
+ const token = tokenResult.value;
106
+ process.stdout.write("🔑 Saving new token to keychain\n");
107
+ const keychainToken = {
108
+ access_token: token.access_token,
109
+ refresh_token: token.refresh_token,
110
+ token_type: token.token_type,
111
+ expires_at: Date.now() + token.expires_in * 1000,
112
+ };
113
+ const saveResult = await getKeychain().save(keychainToken);
114
+ if (isErrored(saveResult)) {
115
+ return saveResult;
116
+ }
117
+ this.scheduleRefresh(keychainToken);
118
+ return complete(token.access_token);
119
+ }
120
+ finally {
121
+ serverRef.close();
122
+ }
123
+ }
124
+ scheduleRefresh(token) {
125
+ if (this.refreshTimeout !== null) {
126
+ clearTimeout(this.refreshTimeout);
127
+ }
128
+ const delay = Math.min(Math.max(0, (token.expires_at - Date.now()) / 10), MAX_TIMEOUT);
129
+ this.refreshTimeout = setTimeout(async () => {
130
+ if (this.isRefreshingToken) {
131
+ return;
132
+ }
133
+ this.isRefreshingToken = true;
134
+ try {
135
+ await this.refreshToken(token);
136
+ }
137
+ finally {
138
+ this.isRefreshingToken = false;
139
+ this.refreshTimeout = null;
140
+ }
141
+ }, delay);
142
+ }
143
+ async refreshToken(token) {
144
+ const refreshTokenResult = await api.refreshToken({
145
+ refreshToken: token.refresh_token,
146
+ clientId: this.clientId,
147
+ });
148
+ if (isErrored(refreshTokenResult)) {
149
+ printFetcherError("Error refreshing token", refreshTokenResult.error);
150
+ return;
151
+ }
152
+ const refreshedToken = refreshTokenResult.value;
153
+ const keychainToken = {
154
+ access_token: refreshedToken.access_token,
155
+ refresh_token: refreshedToken.refresh_token,
156
+ token_type: refreshedToken.token_type,
157
+ expires_at: Date.now() + refreshedToken.expires_in * 1000,
158
+ };
159
+ const saveResult = await getKeychain().save(keychainToken);
160
+ if (isErrored(saveResult)) {
161
+ printKeychainError(saveResult.error);
162
+ return;
163
+ }
164
+ this.scheduleRefresh(keychainToken);
165
+ }
166
+ async logout() {
167
+ if (this.refreshTimeout !== null) {
168
+ clearTimeout(this.refreshTimeout);
169
+ this.refreshTimeout = null;
170
+ }
171
+ await getKeychain().delete();
172
+ }
173
+ }
174
+ export const authenticator = new AuthenticatorImpl();
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { complete, errored, isErrored } from "@attio/fetchable-npm";
3
+ const authTokenSchema = z.object({
4
+ access_token: z.string(),
5
+ refresh_token: z.string(),
6
+ token_type: z.literal("Bearer"),
7
+ expires_at: z.number(),
8
+ });
9
+ class KeytarKeychain {
10
+ SERVICE_NAME = "attio-cli";
11
+ ACCOUNT_NAME = "developer";
12
+ keytarPromise;
13
+ constructor() {
14
+ this.keytarPromise = import("keytar").then((module) => module.default);
15
+ }
16
+ async save(token) {
17
+ const keytar = await this.keytarPromise;
18
+ try {
19
+ await keytar.setPassword(this.SERVICE_NAME, this.ACCOUNT_NAME, JSON.stringify(token));
20
+ return complete(undefined);
21
+ }
22
+ catch (error) {
23
+ return errored({
24
+ code: "SAVE_KEYCHAIN_ERROR",
25
+ error,
26
+ });
27
+ }
28
+ }
29
+ async load() {
30
+ const keytar = await this.keytarPromise;
31
+ let unparsedToken = null;
32
+ try {
33
+ unparsedToken = await keytar.getPassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
34
+ }
35
+ catch (error) {
36
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error });
37
+ }
38
+ if (unparsedToken === null) {
39
+ return complete(null);
40
+ }
41
+ let jsonToken;
42
+ try {
43
+ jsonToken = JSON.parse(unparsedToken);
44
+ }
45
+ catch {
46
+ const deleteResult = await this.delete();
47
+ if (isErrored(deleteResult)) {
48
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
49
+ }
50
+ console.debug("Wiped keychain entry due to invalid JSON");
51
+ return complete(null);
52
+ }
53
+ const parsedToken = authTokenSchema.safeParse(jsonToken);
54
+ if (!parsedToken.success) {
55
+ const deleteResult = await this.delete();
56
+ if (isErrored(deleteResult)) {
57
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
58
+ }
59
+ console.debug("Wiped keychain entry due to schema mismatch");
60
+ return complete(null);
61
+ }
62
+ if (parsedToken.data.expires_at < Date.now() + 60_000 * 5) {
63
+ const deleteResult = await this.delete();
64
+ if (isErrored(deleteResult)) {
65
+ return errored({ code: "LOAD_KEYCHAIN_ERROR", error: deleteResult.error });
66
+ }
67
+ return complete(null);
68
+ }
69
+ return complete(parsedToken.data);
70
+ }
71
+ async delete() {
72
+ const keytar = await this.keytarPromise;
73
+ try {
74
+ await keytar.deletePassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
75
+ return complete(undefined);
76
+ }
77
+ catch (error) {
78
+ return errored({
79
+ code: "DELETE_KEYCHAIN_ERROR",
80
+ error,
81
+ });
82
+ }
83
+ }
84
+ }
85
+ class TestKeychain {
86
+ TEST_TOKEN = {
87
+ access_token: "TEST",
88
+ refresh_token: "TEST",
89
+ token_type: "Bearer",
90
+ expires_at: Number.MAX_SAFE_INTEGER,
91
+ };
92
+ async save(_token) {
93
+ return complete(undefined);
94
+ }
95
+ async load() {
96
+ return complete(this.TEST_TOKEN);
97
+ }
98
+ async delete() {
99
+ return complete(undefined);
100
+ }
101
+ }
102
+ let keychain = null;
103
+ export const getKeychain = () => {
104
+ if (keychain === null) {
105
+ keychain = process.env.NODE_ENV === "test" ? new TestKeychain() : new KeytarKeychain();
106
+ }
107
+ return keychain;
108
+ };
@@ -1,8 +1,8 @@
1
1
  import fs from "fs/promises";
2
2
  import path from "path";
3
3
  import { Project } from "ts-morph";
4
- import { APP_SETTINGS_FILENAME } from "@attio/cli";
5
4
  import { complete, errored, fromPromise, isComplete } from "@attio/fetchable-npm";
5
+ import { APP_SETTINGS_FILENAME } from "../../constants/settings-files.js";
6
6
  import { getAppEntryPoint } from "../../util/get-app-entry-point.js";
7
7
  const ASSET_FILE_EXTENSIONS = ["png"];
8
8
  export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }) {
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import { glob } from "glob";
3
- import { APP_SETTINGS_FILENAME } from "@attio/cli";
4
3
  import { combineAsync, complete, errored, isErrored } from "@attio/fetchable-npm";
4
+ import { APP_SETTINGS_FILENAME } from "../../constants/settings-files.js";
5
5
  async function findServerFunctionModules(cwd, pattern) {
6
6
  try {
7
7
  return complete(await glob(`**/*.${pattern}.{js,ts}`, { nodir: true, cwd }));
@@ -0,0 +1 @@
1
+ export {};
package/lib/build.js CHANGED
@@ -7,7 +7,7 @@ const jsErrorSchema = z.object({
7
7
  length: z.number(),
8
8
  line: z.number(),
9
9
  lineText: z.string(),
10
- additionalLines: z.array(z.string()).readonly().optional(),
10
+ additionalLines: z.array(z.string()).optional(),
11
11
  namespace: z.string(),
12
12
  suggestion: z.string(),
13
13
  }),
@@ -12,7 +12,7 @@ import type { Outcome } from "./outcome";
12
12
  type Prettify<T> = {
13
13
  [K in keyof T]: T[K];
14
14
  } & {};
15
- type Watched<TConfigSchema extends WorkflowBlockConfigFriendlyStruct, TPath extends WorkflowBlockConfigPathTo<WorkflowBlockConfigNode, TConfigSchema> | undefined> = ResolvedPath<Normalized<TConfigSchema>, TPath> extends WorkflowBlockConfigNode | undefined ? Prettify<WorkflowBlockConfigFriendlyConfig<ResolvedPath<Normalized<TConfigSchema>, TPath>>> : never;
15
+ type Watched<TConfigSchema extends WorkflowBlockConfigFriendlyStruct, TPath extends WorkflowBlockConfigPathTo<WorkflowBlockConfigNode, TConfigSchema> | undefined> = TPath extends string | undefined ? ResolvedPath<Normalized<TConfigSchema>, TPath> extends WorkflowBlockConfigNode | undefined ? Prettify<WorkflowBlockConfigFriendlyConfig<ResolvedPath<Normalized<TConfigSchema>, TPath>>> : never : never;
16
16
  /**
17
17
  * Returns React components that render inputs for the given configuration schema.
18
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"use-configurator.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/use-configurator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EACV,YAAY,EACZ,iCAAiC,EACjC,iCAAiC,EACjC,kCAAkC,EAClC,uBAAuB,EACvB,yBAAyB,EAC5B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEtC,KAAK,QAAQ,CAAC,CAAC,IAAI;KACd,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACvB,GAAG,EAAE,CAAA;AAEN,KAAK,OAAO,CACR,aAAa,SAAS,iCAAiC,EACvD,KAAK,SAAS,yBAAyB,CAAC,uBAAuB,EAAE,aAAa,CAAC,GAAG,SAAS,IAC3F,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,uBAAuB,GAAG,SAAS,GACxF,QAAQ,CAAC,iCAAiC,CAAC,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAC3F,KAAK,CAAA;AAEX;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,aAAa,SAAS,iCAAiC,EAC3F,aAAa,CAAC,EAAE,kCAAkC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,GAC9E;IACC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC,aAAa,CAAC,CAAA;IAEvC;;OAEG;IACH,aAAa,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IAE3C;;OAEG;IACH,SAAS,EAAE,SAAS,CAAC,aAAa,CAAC,CAAA;IAEnC;;OAEG;IACH,cAAc,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IAE7C;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,cAAc,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IAE7C;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,mBAAmB,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAA;IAEvD;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAEhB,KAAK,EAAE;QACH,CAAC,KAAK,SAAS,yBAAyB,CAAC,uBAAuB,EAAE,aAAa,CAAC,EAC5E,IAAI,EAAE,KAAK,GACZ,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QAChC,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;KACxD,CAAA;CACJ,CAAA"}
1
+ {"version":3,"file":"use-configurator.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/use-configurator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EACV,YAAY,EACZ,iCAAiC,EACjC,iCAAiC,EACjC,kCAAkC,EAClC,uBAAuB,EACvB,yBAAyB,EAC5B,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEtC,KAAK,QAAQ,CAAC,CAAC,IAAI;KACd,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACvB,GAAG,EAAE,CAAA;AAEN,KAAK,OAAO,CACR,aAAa,SAAS,iCAAiC,EACvD,KAAK,SAAS,yBAAyB,CAAC,uBAAuB,EAAE,aAAa,CAAC,GAAG,SAAS,IAC3F,KAAK,SAAS,MAAM,GAAG,SAAS,GAC9B,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,uBAAuB,GAAG,SAAS,GACtF,QAAQ,CACJ,iCAAiC,CAAC,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,CACpF,GACD,KAAK,GACT,KAAK,CAAA;AAEX;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,aAAa,SAAS,iCAAiC,EAC3F,aAAa,CAAC,EAAE,kCAAkC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,GAC9E;IACC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC,aAAa,CAAC,CAAA;IAEvC;;OAEG;IACH,aAAa,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;IAE3C;;OAEG;IACH,SAAS,EAAE,SAAS,CAAC,aAAa,CAAC,CAAA;IAEnC;;OAEG;IACH,cAAc,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IAE7C;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,cAAc,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IAE7C;;OAEG;IACH,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IAEjD;;OAEG;IACH,mBAAmB,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAA;IAEvD;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAEhB,KAAK,EAAE;QACH,CAAC,KAAK,SAAS,yBAAyB,CAAC,uBAAuB,EAAE,aAAa,CAAC,EAC5E,IAAI,EAAE,KAAK,GACZ,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QAChC,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;KACxD,CAAA;CACJ,CAAA"}
@@ -1,10 +1,11 @@
1
1
  import { Command } from "commander";
2
- import { hardExit, spinnerify } from "@attio/cli";
3
2
  import { isErrored } from "@attio/fetchable-npm";
4
3
  import { assertAppSettings } from "../util/assert-app-settings.js";
5
4
  import { ensureAppEntryPoint } from "../util/ensure-app-entry-point.js";
6
5
  import { exitWithMissingAppSettings } from "../util/exit-with-missing-app-settings.js";
7
6
  import { exitWithMissingEntryPoint } from "../util/exit-with-missing-entry-point.js";
7
+ import { hardExit } from "../util/hard-exit.js";
8
+ import { spinnerify } from "../util/spinner.js";
8
9
  import { printJsError, printTsError } from "../util/typescript.js";
9
10
  import { buildJavaScript } from "./build/build-javascript.js";
10
11
  import { validateTypeScript } from "./build/validate-typescript.js";
@@ -1,5 +1,6 @@
1
- import { api, printCliVersionError, printDetermineWorkspaceError, printFetcherError, printPackageJsonError, } from "@attio/cli";
2
1
  import { isErrored } from "@attio/fetchable-npm";
2
+ import { api } from "../../api/api.js";
3
+ import { printCliVersionError, printDetermineWorkspaceError, printFetcherError, printPackageJsonError, } from "../../print-errors.js";
3
4
  import { determineWorkspace } from "../../spinners/determine-workspace.spinner.js";
4
5
  import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
5
6
  import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
@@ -1,11 +1,11 @@
1
1
  import { readFileSync } from "fs";
2
2
  import chokidar from "chokidar";
3
3
  import { parse } from "graphql";
4
- import { hardExit } from "@attio/cli";
5
4
  import { complete, errored, isErrored } from "@attio/fetchable-npm";
6
5
  import { generateOperations } from "../../graphql/generate-operations.js";
7
6
  import { GraphQLError } from "../../graphql/graphql-error.js";
8
7
  import { findNodeModulesPath } from "../../util/find-node-modules-path.js";
8
+ import { hardExit } from "../../util/hard-exit.js";
9
9
  export async function graphqlCodeGen() {
10
10
  try {
11
11
  const schemaPath = await findNodeModulesPath(["schema.graphql"]);
@@ -1,12 +1,13 @@
1
- import fs from "fs";
2
- import path, { dirname } from "path";
3
1
  import { fileURLToPath } from "url";
4
- import { graphqlServer as graphqlServerMiddleware } from "@hono/graphql-server";
5
- import { serve } from "@hono/node-server";
2
+ import { dirname } from "path";
3
+ import path from "path";
4
+ import fs from "fs";
6
5
  import { buildSchema } from "graphql";
7
6
  import { Hono } from "hono";
7
+ import { serve } from "@hono/node-server";
8
+ import { graphqlServer as graphqlServerMiddleware } from "@hono/graphql-server";
9
+ import { findAvailablePort } from "../../util/find-available-port.js";
8
10
  import open from "open";
9
- import { findAvailablePort } from "@attio/cli";
10
11
  import { listenForKey } from "../../util/listen-for-key.js";
11
12
  export function graphqlServer() {
12
13
  let server = null;
@@ -1,6 +1,7 @@
1
1
  import open from "open";
2
- import { APP, api } from "@attio/cli";
3
2
  import { isErrored } from "@attio/fetchable-npm";
3
+ import { api } from "../../api/api.js";
4
+ import { APP } from "../../env.js";
4
5
  import { listenForKey } from "../../util/listen-for-key.js";
5
6
  function prompt() {
6
7
  process.stdout.write(`🚨 IMPORTANT: You will need to install your app in your workspace. Press "i" to open the app settings page, and then click "Install".\n\n`);
@@ -1,6 +1,7 @@
1
1
  import notifier from "node-notifier";
2
- import { api, spinnerify } from "@attio/cli";
3
2
  import { combineAsync, complete, isErrored } from "@attio/fetchable-npm";
3
+ import { api } from "../../api/api.js";
4
+ import { spinnerify } from "../../util/spinner.js";
4
5
  import { uploadBundle } from "../../util/upload-bundle.js";
5
6
  export async function upload({ contents, devVersionId, appId, }) {
6
7
  return await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
@@ -2,13 +2,15 @@ import chalk from "chalk";
2
2
  import { Command, Option } from "commander";
3
3
  import notifier from "node-notifier";
4
4
  import { z } from "zod";
5
- import { authenticator, hardExit, printUploadError } from "@attio/cli";
6
5
  import { isErrored } from "@attio/fetchable-npm";
6
+ import { authenticator } from "../auth/auth.js";
7
7
  import { HIDDEN_ATTIO_DIRECTORY } from "../constants/hidden-attio-directory.js";
8
+ import { printUploadError } from "../print-errors.js";
8
9
  import { addAttioHiddenDirectoryToTsConfig } from "../util/add-attio-hidden-directory-to-ts-config.js";
9
10
  import { ensureAppEntryPoint } from "../util/ensure-app-entry-point.js";
10
11
  import { generateGitignore } from "../util/generate-gitignore.js";
11
12
  import { generateSettingsFiles } from "../util/generate-settings-files.js";
13
+ import { hardExit } from "../util/hard-exit.js";
12
14
  import { printMessage } from "../util/print-message.js";
13
15
  import { printJsError, printTsError } from "../util/typescript.js";
14
16
  import { boot } from "./dev/boot.js";
@@ -142,10 +144,7 @@ export const dev = new Command("dev")
142
144
  }, async (error) => {
143
145
  haveBundlingErrors = true;
144
146
  if (error.code === "BUILD_JAVASCRIPT_ERROR") {
145
- notifyJsErrors({
146
- errors: error.errors,
147
- warnings: error.warnings,
148
- });
147
+ notifyJsErrors(error);
149
148
  const { errors, warnings } = error;
150
149
  errors?.forEach((error) => printJsError(error, "error"));
151
150
  warnings?.forEach((warning) => printJsError(warning, "warning"));
@@ -0,0 +1,36 @@
1
+ import { existsSync } from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { complete, errored, isErrored } from "@attio/fetchable-npm";
5
+ import { canWrite } from "../../util/can-write.js";
6
+ import { copyWithTransform } from "../../util/copy-with-replace.js";
7
+ import { createDirectory } from "../../util/create-directory.js";
8
+ import { spinnerify } from "../../util/spinner.js";
9
+ export async function createProject({ appSlug, appInfo, }) {
10
+ return await spinnerify("Creating project...", "Project created", async () => {
11
+ const cwd = process.cwd();
12
+ const projectPath = path.join(cwd, appSlug);
13
+ if (existsSync(projectPath)) {
14
+ return errored({ code: "DIRECTORY_ALREADY_EXISTS", path: projectPath });
15
+ }
16
+ if (!canWrite(cwd)) {
17
+ return errored({ code: "WRITE_ACCESS_DENIED", path: cwd });
18
+ }
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+ const projectDirResult = await createDirectory(appSlug);
21
+ if (isErrored(projectDirResult)) {
22
+ return projectDirResult;
23
+ }
24
+ const projectDir = projectDirResult.value;
25
+ const templateDir = path.resolve(__dirname, "../../template");
26
+ const transform = (contents) => contents
27
+ .replaceAll("title-to-be-replaced", appInfo.title)
28
+ .replaceAll("id-to-be-replaced", appInfo.app_id)
29
+ .replaceAll("slug-to-be-replaced", appSlug);
30
+ const result = await copyWithTransform(templateDir, projectDir, transform);
31
+ if (isErrored(result)) {
32
+ return result;
33
+ }
34
+ return complete(undefined);
35
+ });
36
+ }
@@ -2,19 +2,51 @@ import boxen from "boxen";
2
2
  import chalk from "chalk";
3
3
  import { Argument, Command } from "commander";
4
4
  import { z } from "zod";
5
+ import { isErrored } from "@attio/fetchable-npm";
6
+ import { authenticator } from "../auth/auth.js";
7
+ import { printCreateProjectError, printFetcherError } from "../print-errors.js";
8
+ import { getAppInfo } from "../spinners/get-app-info.spinner.js";
9
+ import { printLogo } from "../util/print-logo.js";
10
+ import { createProject } from "./init/create-project.js";
5
11
  export const argsSchema = z.string();
6
12
  export const init = new Command("init")
7
- .description("Initialize a new Attio app (deprecated - use 'npm create attio' instead)")
13
+ .description("Initialize a new Attio app")
8
14
  .addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
9
- .action(async (appSlug) => {
10
- process.stdout.write(`${chalk.yellow("⚠️ The 'attio init' command has been deprecated.")}\n\n` +
11
- `Please use the following command instead:\n\n` +
12
- boxen(`npm create attio ${appSlug}`, {
15
+ .action(async (unparsedArgs) => {
16
+ try {
17
+ printLogo();
18
+ const appSlug = argsSchema.parse(unparsedArgs);
19
+ await authenticator.ensureAuthed();
20
+ const appInfoResult = await getAppInfo(appSlug);
21
+ if (isErrored(appInfoResult)) {
22
+ printFetcherError("Failed to fetch app info", appInfoResult.error);
23
+ process.exit(1);
24
+ }
25
+ const appInfo = appInfoResult.value;
26
+ const result = await createProject({
27
+ appSlug,
28
+ appInfo,
29
+ });
30
+ if (isErrored(result)) {
31
+ printCreateProjectError(result.error);
32
+ process.exit(1);
33
+ }
34
+ process.stdout.write(`${chalk.green(`SUCCESS!! 🎉 Your app directory has been created.`)}
35
+
36
+ To get started, run:
37
+
38
+ ${boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
13
39
  padding: 1,
14
40
  margin: 1,
15
41
  borderStyle: "round",
16
- }) +
17
- `\n` +
18
- `${chalk.dim("This provides a better experience and follows npm conventions.")}\n`);
19
- process.exit(0);
42
+ })}
43
+
44
+ (${chalk.yellow("yarn")}, ${chalk.yellow("pnpm")}, and ${chalk.yellow("bun")} also work!)
45
+ `);
46
+ process.exit(0);
47
+ }
48
+ catch (error) {
49
+ process.stderr.write(`${chalk.red(`✖ ${error}`)}\n`);
50
+ process.exit(1);
51
+ }
20
52
  });
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
- import { authenticator, printAuthenticationError } from "@attio/cli";
3
2
  import { isErrored } from "@attio/fetchable-npm";
3
+ import { authenticator } from "../auth/auth.js";
4
+ import { printAuthenticationError } from "../print-errors.js";
4
5
  export const login = new Command("login")
5
6
  .description("Authenticate with Attio")
6
7
  .action(async () => {
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
- import { getKeychain, printKeychainError } from "@attio/cli";
3
2
  import { isErrored } from "@attio/fetchable-npm";
3
+ import { getKeychain } from "../auth/keychain.js";
4
+ import { printKeychainError } from "../print-errors.js";
4
5
  export const logout = new Command("logout").description("Log out from Attio").action(async () => {
5
6
  const result = await getKeychain().delete();
6
7
  if (isErrored(result)) {
@@ -2,11 +2,13 @@ import readline from "node:readline/promises";
2
2
  import chalk from "chalk";
3
3
  import { Command, Option } from "commander";
4
4
  import { z } from "zod";
5
- import { authenticator, printDetermineWorkspaceError, printFetcherError, printLogSubscriptionError, printPackageJsonError, spinnerify, } from "@attio/cli";
6
5
  import { isErrored } from "@attio/fetchable-npm";
6
+ import { authenticator } from "../auth/auth.js";
7
+ import { printDetermineWorkspaceError, printFetcherError, printLogSubscriptionError, printPackageJsonError, } from "../print-errors.js";
7
8
  import { determineWorkspace } from "../spinners/determine-workspace.spinner.js";
8
9
  import { getAppInfo } from "../spinners/get-app-info.spinner.js";
9
10
  import { getAppSlugFromPackageJson } from "../spinners/get-app-slug-from-package-json.js";
11
+ import { spinnerify } from "../util/spinner.js";
10
12
  import { subscribeToLogs } from "./log/subscribe-to-logs.js";
11
13
  export const optionsSchema = z.object({
12
14
  workspace: z.string().optional(),
@@ -1,7 +1,9 @@
1
1
  import chalk from "chalk";
2
2
  import { Command } from "commander";
3
- import { api, authenticator, printCliVersionError, printFetcherError, printPackageJsonError, spinnerify, } from "@attio/cli";
4
3
  import { combineAsync, isErrored } from "@attio/fetchable-npm";
4
+ import { api } from "../../api/api.js";
5
+ import { authenticator } from "../../auth/auth.js";
6
+ import { printCliVersionError, printFetcherError, printPackageJsonError } from "../../print-errors.js";
5
7
  import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
6
8
  import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
7
9
  import { getVersions } from "../../spinners/get-versions.spinner.js";
@@ -10,6 +12,7 @@ import { ensureAppEntryPoint } from "../../util/ensure-app-entry-point.js";
10
12
  import { exitWithMissingAppSettings } from "../../util/exit-with-missing-app-settings.js";
11
13
  import { exitWithMissingEntryPoint } from "../../util/exit-with-missing-entry-point.js";
12
14
  import { loadAttioCliVersion } from "../../util/load-attio-cli-version.js";
15
+ import { spinnerify } from "../../util/spinner.js";
13
16
  import { printJsError } from "../../util/typescript.js";
14
17
  import { uploadBundle } from "../../util/upload-bundle.js";
15
18
  import { printBuildContextError } from "../dev/prepare-build-context.js";
@@ -2,8 +2,9 @@ import chalk from "chalk";
2
2
  import Table from "cli-table3";
3
3
  import { Command } from "commander";
4
4
  import { format as formatDate } from "date-fns";
5
- import { authenticator, printFetcherError, printPackageJsonError } from "@attio/cli";
6
5
  import { isErrored } from "@attio/fetchable-npm";
6
+ import { authenticator } from "../../auth/auth.js";
7
+ import { printFetcherError, printPackageJsonError } from "../../print-errors.js";
7
8
  import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
8
9
  import { getAppSlugFromPackageJson } from "../../spinners/get-app-slug-from-package-json.js";
9
10
  import { getVersions } from "../../spinners/get-versions.spinner.js";
@@ -1,6 +1,8 @@
1
1
  import { Command } from "commander";
2
- import { api, getKeychain, printFetcherError } from "@attio/cli";
3
2
  import { isErrored } from "@attio/fetchable-npm";
3
+ import { api } from "../api/api.js";
4
+ import { getKeychain } from "../auth/keychain.js";
5
+ import { printFetcherError } from "../print-errors.js";
4
6
  export const whoami = new Command()
5
7
  .name("whoami")
6
8
  .description("Identify the current user")
@@ -0,0 +1,2 @@
1
+ export const APP_SETTINGS_FILENAME = "app.settings.ts";
2
+ export const SETTINGS_TYPES_FILENAME = "settings.d.ts";
package/lib/env.js ADDED
@@ -0,0 +1,5 @@
1
+ export const IS_DEV = process.env.NODE_ENV === "development";
2
+ const DOMAIN = `attio.${IS_DEV ? "me" : "com"}`;
3
+ export const API = `https://build.${DOMAIN}/api`;
4
+ export const APP_NO_PROTOCOL = `app.${DOMAIN}`;
5
+ export const APP = `https://${APP_NO_PROTOCOL}`;
package/lib/errors.js ADDED
@@ -0,0 +1 @@
1
+ export {};