attio 0.0.1-experimental.20251104.3 → 0.0.1-experimental.20251105.1
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/README.md +6 -0
- package/lib/api/api.js +183 -0
- package/lib/api/fetcher.js +69 -0
- package/lib/api/schemas.js +89 -0
- package/lib/attio-logo.js +24 -0
- package/lib/attio.js +22 -40290
- package/lib/auth/auth.js +174 -0
- package/lib/auth/keychain.js +108 -0
- package/lib/build/client/generate-client-entry.js +1 -1
- package/lib/build/server/generate-server-entry.js +1 -1
- package/lib/build/workflow-block-modules.js +1 -0
- package/lib/build.js +1 -1
- package/lib/commands/build.js +2 -1
- package/lib/commands/dev/boot.js +2 -1
- package/lib/commands/dev/graphql-code-gen.js +1 -1
- package/lib/commands/dev/graphql-server.js +6 -5
- package/lib/commands/dev/onboarding.js +2 -1
- package/lib/commands/dev/upload.js +2 -1
- package/lib/commands/dev.js +4 -5
- package/lib/commands/init/create-project.js +36 -0
- package/lib/commands/init.js +41 -9
- package/lib/commands/login.js +2 -1
- package/lib/commands/logout.js +2 -1
- package/lib/commands/logs.js +3 -1
- package/lib/commands/version/create.js +4 -1
- package/lib/commands/version/list.js +2 -1
- package/lib/commands/whoami.js +3 -1
- package/lib/constants/settings-files.js +2 -0
- package/lib/env.js +5 -0
- package/lib/errors.js +1 -0
- package/lib/print-errors.js +177 -0
- package/lib/sdk-version.js +1 -0
- package/lib/spinners/determine-workspace.spinner.js +2 -1
- package/lib/spinners/get-app-info.spinner.js +2 -1
- package/lib/spinners/get-versions.spinner.js +2 -1
- package/lib/template/README.md +21 -0
- package/lib/template/biome.jsonc +59 -0
- package/lib/template/eslint.attio.config.js +41 -0
- package/lib/template/graphql.config.json +9 -0
- package/lib/template/package.json +35 -0
- package/lib/template/src/app.settings.ts +8 -0
- package/lib/template/src/app.ts +10 -0
- package/lib/template/src/assets/icon.png +0 -0
- package/lib/template/src/get-stoic-quote.server.ts +14 -0
- package/lib/template/src/hello-world-action.tsx +18 -0
- package/lib/template/src/hello-world-dialog.tsx +27 -0
- package/lib/template/src/stoic-quote.tsx +21 -0
- package/lib/template/tsconfig.json +42 -0
- package/lib/util/assert-app-settings.js +1 -1
- package/lib/util/can-write.js +11 -0
- package/lib/util/copy-with-replace.js +56 -0
- package/lib/util/create-directory.js +27 -0
- package/lib/util/exit-with-missing-app-settings.js +1 -1
- package/lib/util/exit-with-missing-entry-point.js +1 -1
- package/lib/util/find-available-port.js +37 -0
- package/lib/util/generate-settings-files.js +1 -1
- package/lib/util/hard-exit.js +6 -0
- package/lib/util/print-logo.js +5 -0
- package/lib/util/realtime.js +1 -1
- package/lib/util/spinner.js +61 -0
- package/lib/util/text-gradient.js +28 -0
- package/package.json +1 -1
package/lib/auth/auth.js
ADDED
|
@@ -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()).
|
|
10
|
+
additionalLines: z.array(z.string()).optional(),
|
|
11
11
|
namespace: z.string(),
|
|
12
12
|
suggestion: z.string(),
|
|
13
13
|
}),
|
package/lib/commands/build.js
CHANGED
|
@@ -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";
|
package/lib/commands/dev/boot.js
CHANGED
|
@@ -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 {
|
|
5
|
-
import
|
|
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 () => {
|
package/lib/commands/dev.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/commands/init.js
CHANGED
|
@@ -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
|
|
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 (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
});
|
package/lib/commands/login.js
CHANGED
|
@@ -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 () => {
|
package/lib/commands/logout.js
CHANGED
|
@@ -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)) {
|
package/lib/commands/logs.js
CHANGED
|
@@ -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";
|
package/lib/commands/whoami.js
CHANGED
|
@@ -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")
|
package/lib/env.js
ADDED
package/lib/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|