attio 0.0.1-experimental.20250402.1 → 0.0.1-experimental.20250403
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/api-fetch.js +32 -0
- package/lib/api/auth.js +63 -61
- package/lib/api/complete-bundle-upload.js +3 -16
- package/lib/api/complete-prod-bundle-upload.js +3 -16
- package/lib/api/create-dev-version.js +4 -9
- package/lib/api/create-version.js +4 -9
- package/lib/api/determine-workspace.spinner.js +23 -30
- package/lib/api/ensure-authed.js +3 -20
- package/lib/api/fetch-app-info.js +14 -20
- package/lib/api/fetch-installation.js +5 -16
- package/lib/api/fetch-versions.js +9 -17
- package/lib/api/fetch-workspaces.js +10 -18
- package/lib/api/get-app-info.spinner.js +5 -16
- package/lib/api/get-app-slug-from-package-json.js +4 -3
- package/lib/api/get-versions.spinner.js +5 -19
- package/lib/api/hard-exit.js +6 -0
- package/lib/api/keychain.js +29 -7
- package/lib/api/start-upload.js +7 -14
- package/lib/api/whoami.js +3 -9
- package/lib/commands/build/build-javascript.js +0 -2
- package/lib/commands/build.js +8 -23
- package/lib/commands/dev/boot.js +4 -8
- package/lib/commands/dev/build-javascript.js +0 -2
- package/lib/commands/dev/bundle-javascript.js +41 -4
- package/lib/commands/dev/graphql-code-gen.js +3 -2
- package/lib/commands/dev/onboarding.js +2 -2
- package/lib/commands/dev/upload.js +8 -40
- package/lib/commands/dev/validate-typescript.js +0 -4
- package/lib/commands/dev.js +47 -11
- package/lib/commands/init/create-project.js +16 -25
- package/lib/commands/init.js +4 -7
- package/lib/commands/login.js +7 -11
- package/lib/commands/logout.js +4 -10
- package/lib/commands/version/create.js +26 -51
- package/lib/commands/version/list.js +36 -25
- package/lib/commands/whoami.js +8 -12
- package/lib/util/copy-with-replace.js +3 -2
- package/lib/util/create-directory.js +3 -2
- package/lib/util/find-available-port.js +2 -1
- package/lib/util/load-attio-cli-package-json.js +3 -2
- package/lib/util/spinner.js +13 -1
- package/lib/util/upload-bundle.js +16 -0
- package/package.json +1 -1
- package/lib/api/fetch-connections.js +0 -20
- package/lib/api/handle-error.js +0 -18
- package/lib/api/make-headers.js +0 -7
- package/lib/api/remove-connection-definition.js +0 -10
- package/lib/commands/init/boot.js +0 -14
- package/lib/commands/version/create/boot.js +0 -9
- package/lib/util/clear-terminal.js +0 -4
package/lib/api/keychain.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hardExit } from "./hard-exit.js";
|
|
2
|
+
const TEST_TOKEN = "TEST";
|
|
1
3
|
let keytar = null;
|
|
2
4
|
if (process.env.NODE_ENV !== "test") {
|
|
3
5
|
try {
|
|
@@ -11,20 +13,40 @@ const SERVICE_NAME = "attio-cli";
|
|
|
11
13
|
const ACCOUNT_NAME = "developer";
|
|
12
14
|
export async function saveAuthToken(token) {
|
|
13
15
|
if (!keytar) {
|
|
14
|
-
return;
|
|
16
|
+
return hardExit("Keychain functionality not available");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return hardExit(error instanceof Error ? error.message : "Failed to save token to keychain");
|
|
15
23
|
}
|
|
16
|
-
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
|
|
17
24
|
}
|
|
18
25
|
export async function loadAuthToken() {
|
|
26
|
+
if (process.env.NODE_ENV === "test") {
|
|
27
|
+
return TEST_TOKEN;
|
|
28
|
+
}
|
|
19
29
|
if (!keytar) {
|
|
20
|
-
return "
|
|
30
|
+
return hardExit("Keychain functionality not available");
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return hardExit(error instanceof Error ? error.message : "Failed to load token from keychain");
|
|
21
37
|
}
|
|
22
|
-
const token = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
23
|
-
return token || null;
|
|
24
38
|
}
|
|
25
39
|
export async function deleteAuthToken() {
|
|
26
40
|
if (!keytar) {
|
|
27
|
-
|
|
41
|
+
if (process.env.NODE_ENV === "test") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return hardExit("Keychain functionality not available");
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return hardExit(error instanceof Error ? error.message : "Failed to delete token from keychain");
|
|
28
51
|
}
|
|
29
|
-
return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
30
52
|
}
|
package/lib/api/start-upload.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
import { handleError } from "./handle-error.js";
|
|
4
|
-
import { makeHeaders } from "./make-headers.js";
|
|
2
|
+
import { apiFetch } from "./api-fetch.js";
|
|
5
3
|
const startUploadSchema = z.object({
|
|
6
|
-
app_dev_version_id: z.string(),
|
|
7
|
-
app_dev_version_bundle_id: z.string(),
|
|
8
|
-
client_bundle_upload_url: z.string(),
|
|
9
|
-
server_bundle_upload_url: z.string(),
|
|
4
|
+
app_dev_version_id: z.string().uuid(),
|
|
5
|
+
app_dev_version_bundle_id: z.string().uuid(),
|
|
6
|
+
client_bundle_upload_url: z.string().url(),
|
|
7
|
+
server_bundle_upload_url: z.string().url(),
|
|
10
8
|
});
|
|
11
|
-
export async function startUpload({
|
|
12
|
-
|
|
13
|
-
method: "POST",
|
|
14
|
-
headers: makeHeaders(token),
|
|
15
|
-
});
|
|
16
|
-
await handleError(response);
|
|
17
|
-
return startUploadSchema.parse(await response.json());
|
|
9
|
+
export async function startUpload({ appId, devVersionId, }) {
|
|
10
|
+
return await apiFetch("starting upload", `apps/${appId}/dev-versions/${devVersionId}/bundles`, { method: "POST" }, startUploadSchema);
|
|
18
11
|
}
|
package/lib/api/whoami.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { apiFetch } from "./api-fetch.js";
|
|
2
3
|
import { APP } from "../env.js";
|
|
3
|
-
import { handleError } from "./handle-error.js";
|
|
4
|
-
import { makeHeaders } from "./make-headers.js";
|
|
5
4
|
const whoamiSchema = z.object({
|
|
6
5
|
user: z.object({
|
|
7
6
|
id: z.string(),
|
|
@@ -11,11 +10,6 @@ const whoamiSchema = z.object({
|
|
|
11
10
|
}),
|
|
12
11
|
}),
|
|
13
12
|
});
|
|
14
|
-
export async function whoami(
|
|
15
|
-
|
|
16
|
-
method: "GET",
|
|
17
|
-
headers: makeHeaders(token),
|
|
18
|
-
});
|
|
19
|
-
await handleError(response);
|
|
20
|
-
return whoamiSchema.parse(await response.json()).user;
|
|
13
|
+
export async function whoami() {
|
|
14
|
+
return (await apiFetch("fetching current user", `${APP}/api/auth/whoami`, { method: "GET" }, whoamiSchema)).user;
|
|
21
15
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
2
1
|
import { prepareBuildContexts } from "../dev/prepare-build-contexts.js";
|
|
3
2
|
import { printJsError } from "../../util/typescript.js";
|
|
4
3
|
import { errorsAndWarningsSchema } from "../../build.js";
|
|
@@ -11,7 +10,6 @@ export async function buildJavaScript() {
|
|
|
11
10
|
}
|
|
12
11
|
catch (error) {
|
|
13
12
|
const errors = errorsAndWarningsSchema.parse(error);
|
|
14
|
-
clearTerminal();
|
|
15
13
|
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
16
14
|
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
17
15
|
return false;
|
package/lib/commands/build.js
CHANGED
|
@@ -1,36 +1,21 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { validateTypeScript } from "./build/validate-typescript.js";
|
|
3
3
|
import { buildJavaScript } from "./build/build-javascript.js";
|
|
4
|
-
import {
|
|
4
|
+
import { spinnerify } from "../util/spinner.js";
|
|
5
|
+
import { hardExit } from "../api/hard-exit.js";
|
|
5
6
|
export const build = new Command("build")
|
|
6
7
|
.description("Build your Attio extension locally")
|
|
7
8
|
.action(async (unparsedOptions) => {
|
|
8
|
-
|
|
9
|
-
spinner.start("Validating TypeScript...");
|
|
10
|
-
try {
|
|
9
|
+
await spinnerify("Validating TypeScript...", "TypeScript validation passed", async () => {
|
|
11
10
|
const tsValid = await validateTypeScript();
|
|
12
11
|
if (!tsValid) {
|
|
13
|
-
|
|
14
|
-
process.exit(1);
|
|
12
|
+
hardExit("TypeScript validation failed");
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
catch (error) {
|
|
19
|
-
spinner.error(`TypeScript validation failed: ${error}`);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
spinner.start("Building JavaScript...");
|
|
23
|
-
try {
|
|
14
|
+
});
|
|
15
|
+
await spinnerify("Building JavaScript...", "Build completed successfully", async () => {
|
|
24
16
|
const jsBuilt = await buildJavaScript();
|
|
25
17
|
if (!jsBuilt) {
|
|
26
|
-
|
|
27
|
-
process.exit(1);
|
|
18
|
+
hardExit("JavaScript build failed");
|
|
28
19
|
}
|
|
29
|
-
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
spinner.error(`JavaScript build failed: ${error}`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
20
|
+
});
|
|
36
21
|
});
|
package/lib/commands/dev/boot.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { ensureAuthed } from "../../api/ensure-authed.js";
|
|
2
|
-
import { loadEnv } from "../../util/load-env.js";
|
|
3
1
|
import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
|
|
4
2
|
import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
|
|
5
3
|
import { createDevVersion } from "../../api/create-dev-version.js";
|
|
6
4
|
import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
|
|
7
5
|
import { getAppInfo } from "../../api/get-app-info.spinner.js";
|
|
6
|
+
import { loadEnv } from "../../util/load-env.js";
|
|
8
7
|
export async function boot({ workspaceSlug }) {
|
|
9
|
-
const token = await ensureAuthed();
|
|
10
8
|
const appSlug = await getAppSlugFromPackageJson();
|
|
11
|
-
const appInfo = await getAppInfo(
|
|
12
|
-
const workspace = await determineWorkspace(
|
|
9
|
+
const appInfo = await getAppInfo(appSlug);
|
|
10
|
+
const workspace = await determineWorkspace(workspaceSlug);
|
|
13
11
|
const environmentVariables = await loadEnv();
|
|
14
12
|
const packageJson = loadAttioCliPackageJson();
|
|
15
13
|
const devVersion = await createDevVersion({
|
|
16
|
-
token,
|
|
17
14
|
appId: appInfo.app_id,
|
|
15
|
+
cliVersion: packageJson.version,
|
|
18
16
|
targetWorkspaceId: workspace.workspace_id,
|
|
19
17
|
environmentVariables,
|
|
20
|
-
cliVersion: packageJson.version,
|
|
21
18
|
});
|
|
22
19
|
return {
|
|
23
|
-
token,
|
|
24
20
|
appId: appInfo.app_id,
|
|
25
21
|
appSlug,
|
|
26
22
|
devVersionId: devVersion.app_dev_version_id,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
2
|
import notifier from "node-notifier";
|
|
3
|
-
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
4
3
|
import { errorsAndWarningsSchema } from "../../build.js";
|
|
5
4
|
import { prepareBuildContexts } from "./prepare-build-contexts.js";
|
|
6
5
|
import { printJsError } from "../../util/typescript.js";
|
|
@@ -28,7 +27,6 @@ export function buildJavaScript(onSuccess) {
|
|
|
28
27
|
}
|
|
29
28
|
catch (error) {
|
|
30
29
|
const errors = errorsAndWarningsSchema.parse(error);
|
|
31
|
-
clearTerminal();
|
|
32
30
|
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
33
31
|
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
34
32
|
notify(errors);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
2
|
import notifier from "node-notifier";
|
|
3
|
-
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
4
3
|
import { errorsAndWarningsSchema } from "../../build.js";
|
|
5
4
|
import { prepareBuildContexts } from "./prepare-build-contexts.js";
|
|
6
5
|
import { printJsError } from "../../util/typescript.js";
|
|
@@ -17,22 +16,36 @@ export function bundleJavaScript(onSuccess) {
|
|
|
17
16
|
cwd: process.cwd(),
|
|
18
17
|
});
|
|
19
18
|
let buildContexts;
|
|
19
|
+
let isBuilding = false;
|
|
20
|
+
let buildQueued = false;
|
|
21
|
+
let isDisposing = false;
|
|
20
22
|
async function handleBuild() {
|
|
23
|
+
if (isBuilding || isDisposing) {
|
|
24
|
+
buildQueued = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
isBuilding = true;
|
|
21
28
|
try {
|
|
22
29
|
if (!buildContexts) {
|
|
23
30
|
buildContexts = await prepareBuildContexts("in-memory");
|
|
24
31
|
}
|
|
25
32
|
const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
|
|
26
33
|
const bundles = results.map((result) => result.outputFiles[0].text);
|
|
27
|
-
onSuccess?.(bundles);
|
|
34
|
+
await onSuccess?.(bundles);
|
|
28
35
|
}
|
|
29
36
|
catch (error) {
|
|
30
37
|
const errors = errorsAndWarningsSchema.parse(error);
|
|
31
|
-
clearTerminal();
|
|
32
38
|
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
33
39
|
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
34
40
|
notify(errors);
|
|
35
41
|
}
|
|
42
|
+
finally {
|
|
43
|
+
isBuilding = false;
|
|
44
|
+
if (buildQueued && !isDisposing) {
|
|
45
|
+
buildQueued = false;
|
|
46
|
+
handleBuild();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
}
|
|
37
50
|
watcher.on("ready", () => {
|
|
38
51
|
handleBuild();
|
|
@@ -43,7 +56,31 @@ export function bundleJavaScript(onSuccess) {
|
|
|
43
56
|
});
|
|
44
57
|
});
|
|
45
58
|
return async () => {
|
|
46
|
-
|
|
59
|
+
isDisposing = true;
|
|
60
|
+
if (isBuilding) {
|
|
61
|
+
await new Promise((resolve) => {
|
|
62
|
+
const checkBuild = setInterval(() => {
|
|
63
|
+
if (!isBuilding) {
|
|
64
|
+
clearInterval(checkBuild);
|
|
65
|
+
resolve(undefined);
|
|
66
|
+
}
|
|
67
|
+
}, 10);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
47
70
|
await watcher.close();
|
|
71
|
+
if (buildContexts) {
|
|
72
|
+
for (const context of buildContexts) {
|
|
73
|
+
try {
|
|
74
|
+
await Promise.race([
|
|
75
|
+
context.dispose(),
|
|
76
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Context disposal timeout")), 1_000)),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
process.stderr.write(`Error disposing build context: ${e}\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
buildContexts = undefined;
|
|
84
|
+
}
|
|
48
85
|
};
|
|
49
86
|
}
|
|
@@ -4,6 +4,7 @@ import { parse } from "graphql";
|
|
|
4
4
|
import { findNodeModulesPath } from "../../util/find-node-modules-path.js";
|
|
5
5
|
import { generateOperations } from "../../graphql/generate-operations.js";
|
|
6
6
|
import { GraphQLError } from "../../graphql/graphql-error.js";
|
|
7
|
+
import { hardExit } from "../../api/hard-exit.js";
|
|
7
8
|
export function graphqlCodeGen(onSuccess) {
|
|
8
9
|
const watcher = chokidar.watch(["**/*.graphql", "**/*.gql"], {
|
|
9
10
|
ignored: ["**/node_modules/**", "**/dist/**"],
|
|
@@ -13,7 +14,7 @@ export function graphqlCodeGen(onSuccess) {
|
|
|
13
14
|
try {
|
|
14
15
|
const schemaPath = await findNodeModulesPath(["schema.graphql"]);
|
|
15
16
|
if (!schemaPath) {
|
|
16
|
-
|
|
17
|
+
hardExit("No schema.graphql found in node_modules");
|
|
17
18
|
}
|
|
18
19
|
const schema = parse(readFileSync(schemaPath, "utf8"));
|
|
19
20
|
await generateOperations(".", schema);
|
|
@@ -24,7 +25,7 @@ export function graphqlCodeGen(onSuccess) {
|
|
|
24
25
|
process.stderr.write(error.toString());
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
|
-
|
|
28
|
+
hardExit(error instanceof Error ? error.message : "GraphQL code generation failed");
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -5,8 +5,8 @@ import { APP } from "../../env.js";
|
|
|
5
5
|
function prompt() {
|
|
6
6
|
process.stdout.write(`\n\n🚨 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`);
|
|
7
7
|
}
|
|
8
|
-
export function onboarding({
|
|
9
|
-
const haveInstallation = async () => (await fetchInstallation({
|
|
8
|
+
export function onboarding({ appId, appSlug, workspace, }) {
|
|
9
|
+
const haveInstallation = async () => (await fetchInstallation({ appId, workspaceId: workspace.workspace_id })) !== null;
|
|
10
10
|
const cleanup = listenForKey("i", () => {
|
|
11
11
|
open(`${APP}/${workspace.slug}/settings/apps/${appSlug}`);
|
|
12
12
|
});
|
|
@@ -1,59 +1,27 @@
|
|
|
1
1
|
import notifier from "node-notifier";
|
|
2
2
|
import { startUpload } from "../../api/start-upload.js";
|
|
3
3
|
import { completeBundleUpload } from "../../api/complete-bundle-upload.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { spinnerify } from "../../util/spinner.js";
|
|
5
|
+
import { uploadBundle } from "../../util/upload-bundle.js";
|
|
6
|
+
export async function upload({ contents, devVersionId, appId, }) {
|
|
7
|
+
await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
|
|
8
8
|
const { client_bundle_upload_url, server_bundle_upload_url, app_dev_version_bundle_id: bundleId, } = await startUpload({
|
|
9
|
-
token,
|
|
10
9
|
appId,
|
|
11
10
|
devVersionId,
|
|
12
11
|
});
|
|
13
12
|
const [clientBundle, serverBundle] = contents;
|
|
14
13
|
await Promise.all([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
headers: {
|
|
19
|
-
"Content-Type": "text/javascript",
|
|
20
|
-
"Content-Length": String(Buffer.from(clientBundle).length),
|
|
21
|
-
},
|
|
22
|
-
}),
|
|
23
|
-
fetch(server_bundle_upload_url, {
|
|
24
|
-
method: "PUT",
|
|
25
|
-
body: serverBundle,
|
|
26
|
-
headers: {
|
|
27
|
-
"Content-Type": "text/javascript",
|
|
28
|
-
"Content-Length": String(Buffer.from(serverBundle).length),
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
]).catch((error) => {
|
|
32
|
-
process.stderr.write(`Upload Error: ${error}`);
|
|
33
|
-
});
|
|
14
|
+
uploadBundle(clientBundle, client_bundle_upload_url),
|
|
15
|
+
uploadBundle(serverBundle, server_bundle_upload_url),
|
|
16
|
+
]);
|
|
34
17
|
await completeBundleUpload({
|
|
35
|
-
token,
|
|
36
18
|
appId,
|
|
37
19
|
devVersionId,
|
|
38
20
|
bundleId,
|
|
39
21
|
});
|
|
40
|
-
spinner.success(`Upload completed at ${new Date().toLocaleString()}.`);
|
|
41
22
|
notifier.notify({
|
|
42
23
|
title: "Upload Complete",
|
|
43
24
|
message: "New bundle uploaded to Attio",
|
|
44
25
|
});
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (error instanceof Error) {
|
|
48
|
-
spinner.error(`Upload failed: ${error.message}`);
|
|
49
|
-
notifier.notify({
|
|
50
|
-
title: "Upload Failed",
|
|
51
|
-
message: "Bundle upload to Attio failed",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
spinner.stop();
|
|
58
|
-
}
|
|
26
|
+
});
|
|
59
27
|
}
|
|
@@ -2,7 +2,6 @@ import chokidar from "chokidar";
|
|
|
2
2
|
import notifier from "node-notifier";
|
|
3
3
|
import { getDiagnostics, readConfig, typeScriptErrorSchema, } from "../../util/typescript.js";
|
|
4
4
|
import { printTsError } from "../../util/typescript.js";
|
|
5
|
-
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
6
5
|
import path from "path";
|
|
7
6
|
const notify = (errors) => {
|
|
8
7
|
notifier.notify({
|
|
@@ -37,7 +36,6 @@ export function validateTypeScript(onSuccess) {
|
|
|
37
36
|
}
|
|
38
37
|
const errors = await getDiagnostics(program);
|
|
39
38
|
if (errors.length) {
|
|
40
|
-
clearTerminal();
|
|
41
39
|
errors.forEach(printTsError);
|
|
42
40
|
notify(errors);
|
|
43
41
|
}
|
|
@@ -54,7 +52,6 @@ export function validateTypeScript(onSuccess) {
|
|
|
54
52
|
}
|
|
55
53
|
if (error instanceof Error) {
|
|
56
54
|
const tsError = typeScriptErrorSchema.parse({ text: error.message });
|
|
57
|
-
clearTerminal();
|
|
58
55
|
printTsError(tsError);
|
|
59
56
|
notify([tsError]);
|
|
60
57
|
}
|
|
@@ -63,7 +60,6 @@ export function validateTypeScript(onSuccess) {
|
|
|
63
60
|
let watcherReady = false;
|
|
64
61
|
watcher.on("ready", () => {
|
|
65
62
|
watcherReady = true;
|
|
66
|
-
handleValidation();
|
|
67
63
|
watcher.on("all", (event, path) => {
|
|
68
64
|
if (event === "add" || event === "change" || event === "unlink") {
|
|
69
65
|
handleValidation();
|
package/lib/commands/dev.js
CHANGED
|
@@ -14,35 +14,71 @@ export const optionsSchema = z.object({
|
|
|
14
14
|
});
|
|
15
15
|
export const dev = new Command("dev")
|
|
16
16
|
.description("Develop your Attio app")
|
|
17
|
-
.addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
|
|
18
17
|
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
19
18
|
.action(async (unparsedOptions) => {
|
|
20
19
|
const { workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
|
|
21
20
|
const cleanupFunctions = [];
|
|
21
|
+
let isCleaningUp = false;
|
|
22
|
+
const cleanup = async () => {
|
|
23
|
+
if (isCleaningUp)
|
|
24
|
+
return;
|
|
25
|
+
isCleaningUp = true;
|
|
26
|
+
try {
|
|
27
|
+
for (const cleanup of cleanupFunctions.reverse()) {
|
|
28
|
+
try {
|
|
29
|
+
await Promise.race([
|
|
30
|
+
cleanup(),
|
|
31
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Cleanup timeout")), 2000)),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
process.stderr.write(chalk.yellow(`Warning during cleanup: ${error}\n`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
process.stderr.write(chalk.red(`Error during cleanup: ${error}\n`));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
22
43
|
try {
|
|
23
|
-
const {
|
|
44
|
+
const { appId, appSlug, workspace, devVersionId } = await boot({ workspaceSlug });
|
|
24
45
|
const cleanupGraphqlServer = graphqlServer();
|
|
25
46
|
cleanupFunctions.push(cleanupGraphqlServer);
|
|
26
|
-
const cleanupOnboardingDaemon = onboarding({
|
|
47
|
+
const cleanupOnboardingDaemon = onboarding({ appId, appSlug, workspace });
|
|
27
48
|
cleanupFunctions.push(cleanupOnboardingDaemon);
|
|
28
49
|
const [cleanupTs, triggerTs] = validateTypeScript();
|
|
29
50
|
cleanupFunctions.push(cleanupTs);
|
|
30
51
|
const cleanupGraphqlCodeGen = graphqlCodeGen(triggerTs);
|
|
31
52
|
cleanupFunctions.push(cleanupGraphqlCodeGen);
|
|
32
|
-
const cleanupJs = bundleJavaScript((contents) => {
|
|
33
|
-
upload({
|
|
53
|
+
const cleanupJs = bundleJavaScript(async (contents) => {
|
|
54
|
+
await upload({ contents, devVersionId, appId });
|
|
34
55
|
});
|
|
35
56
|
cleanupFunctions.push(cleanupJs);
|
|
36
57
|
printMessage("\n👀 Watching for changes...");
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
process.on("SIGINT", async () => {
|
|
59
|
+
await cleanup();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
process.on("SIGTERM", async () => {
|
|
63
|
+
await cleanup();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
});
|
|
66
|
+
process.on("uncaughtException", async (error) => {
|
|
67
|
+
process.stderr.write(chalk.red(`Uncaught exception: ${error}\n`));
|
|
68
|
+
await cleanup();
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
process.on("unhandledRejection", async (error) => {
|
|
72
|
+
process.stderr.write(chalk.red(`Unhandled rejection: ${error}\n`));
|
|
73
|
+
await cleanup();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
76
|
+
await new Promise(() => {
|
|
42
77
|
});
|
|
43
78
|
}
|
|
44
79
|
catch (error) {
|
|
45
|
-
process.stderr.write(
|
|
80
|
+
process.stderr.write(chalk.red(`✖ ${error}\n`));
|
|
81
|
+
await cleanup();
|
|
46
82
|
process.exit(1);
|
|
47
83
|
}
|
|
48
84
|
});
|
|
@@ -7,30 +7,28 @@ import { createDirectory } from "../../util/create-directory.js";
|
|
|
7
7
|
import { canWrite } from "../../util/can-write.js";
|
|
8
8
|
import { copyWithTransform } from "../../util/copy-with-replace.js";
|
|
9
9
|
import { printMessage } from "../../util/print-message.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
try {
|
|
10
|
+
import { spinnerify } from "../../util/spinner.js";
|
|
11
|
+
import { hardExit } from "../../api/hard-exit.js";
|
|
12
|
+
export async function createProject({ appSlug, language, appInfo, }) {
|
|
13
|
+
await spinnerify("Creating project...", "Project created", async () => {
|
|
14
|
+
if (existsSync(path.join(process.cwd(), appSlug))) {
|
|
15
|
+
hardExit(`Directory "${appSlug}" already exists`);
|
|
16
|
+
}
|
|
17
|
+
if (!canWrite(process.cwd())) {
|
|
18
|
+
hardExit("Write access denied to current directory");
|
|
19
|
+
}
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const projectDir = createDirectory(appSlug);
|
|
22
|
+
const templatesDir = path.resolve(__dirname, "../../templates", language);
|
|
23
|
+
const commonDir = path.resolve(__dirname, "../../templates", "common");
|
|
25
24
|
const transform = (contents) => contents
|
|
26
25
|
.replaceAll("title-to-be-replaced", appInfo.title)
|
|
27
|
-
.replaceAll("id-to-be-replaced",
|
|
26
|
+
.replaceAll("id-to-be-replaced", appInfo.app_id)
|
|
28
27
|
.replaceAll("slug-to-be-replaced", appSlug);
|
|
29
28
|
await Promise.all([
|
|
30
29
|
copyWithTransform(templatesDir, projectDir, transform),
|
|
31
30
|
copyWithTransform(commonDir, projectDir, transform),
|
|
32
31
|
]);
|
|
33
|
-
spinner.success("Project created");
|
|
34
32
|
printMessage("\n" + chalk.green(`SUCCESS!! 🎉 Your app directory has been created.`));
|
|
35
33
|
printMessage("\nTo get started, run:\n");
|
|
36
34
|
printMessage(boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
|
|
@@ -39,12 +37,5 @@ export async function createProject({ appSlug, language, appInfo, appId, }) {
|
|
|
39
37
|
borderStyle: "round",
|
|
40
38
|
}) + "\n");
|
|
41
39
|
printMessage(`(${chalk.yellow("yarn")}, ${chalk.yellow("pnpm")}, and ${chalk.yellow("bun")} also work!)\n`);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
spinner.error("Error creating project");
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
spinner.stop();
|
|
49
|
-
}
|
|
40
|
+
});
|
|
50
41
|
}
|
package/lib/commands/init.js
CHANGED
|
@@ -1,37 +1,34 @@
|
|
|
1
1
|
import { Argument, Command, Option } from "commander";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { boot } from "./init/boot.js";
|
|
5
4
|
import { createProject } from "./init/create-project.js";
|
|
6
5
|
import { askLanguage } from "./init/ask-language.js";
|
|
7
6
|
import { printLogo } from "../util/print-logo.js";
|
|
7
|
+
import { getAppInfo } from "../api/get-app-info.spinner.js";
|
|
8
8
|
export const argsSchema = z.string();
|
|
9
9
|
export const optionsSchema = z.object({
|
|
10
10
|
language: z.enum(["javascript", "typescript"]).optional(),
|
|
11
|
-
workspace: z.string().optional(),
|
|
12
11
|
});
|
|
13
12
|
export const init = new Command("init")
|
|
14
13
|
.description("Initialize a new Attio app")
|
|
15
14
|
.addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
|
|
16
15
|
.addOption(new Option("--language <language>", "Language").choices(["javascript", "typescript"]))
|
|
17
|
-
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
18
16
|
.action(async (unparsedArgs, unparsedOptions) => {
|
|
19
17
|
try {
|
|
20
18
|
printLogo();
|
|
21
19
|
const appSlug = argsSchema.parse(unparsedArgs);
|
|
22
|
-
const { language: cliLanguage
|
|
23
|
-
const
|
|
20
|
+
const { language: cliLanguage } = optionsSchema.parse(unparsedOptions);
|
|
21
|
+
const appInfo = await getAppInfo(appSlug);
|
|
24
22
|
const language = cliLanguage ?? (await askLanguage());
|
|
25
23
|
await createProject({
|
|
26
24
|
appSlug,
|
|
27
25
|
language,
|
|
28
26
|
appInfo,
|
|
29
|
-
appId,
|
|
30
27
|
});
|
|
31
28
|
process.exit(0);
|
|
32
29
|
}
|
|
33
30
|
catch (error) {
|
|
34
|
-
process.stderr.write(
|
|
31
|
+
process.stderr.write(chalk.red("✖ " + String(error)) + "\n");
|
|
35
32
|
process.exit(1);
|
|
36
33
|
}
|
|
37
34
|
});
|
package/lib/commands/login.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { auth
|
|
3
|
-
export const login = new Command("login")
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
.catch((error) => {
|
|
10
|
-
process.stderr.write(`❌ Authentication failed\n\n${error}\n`);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
});
|
|
2
|
+
import { auth } from "../api/auth.js";
|
|
3
|
+
export const login = new Command("login")
|
|
4
|
+
.description("Authenticate with Attio")
|
|
5
|
+
.action(async () => {
|
|
6
|
+
await auth();
|
|
7
|
+
process.stdout.write("🔓 Successfully authenticated.\n");
|
|
8
|
+
process.exit(0);
|
|
13
9
|
});
|
package/lib/commands/logout.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { deleteAuthToken } from "../api/keychain.js";
|
|
3
|
-
export const logout = new Command("logout").description("Log out from Attio").action(() => {
|
|
4
|
-
deleteAuthToken()
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
process.exit(0);
|
|
8
|
-
})
|
|
9
|
-
.catch((error) => {
|
|
10
|
-
process.stderr.write(`❌ Logout failed\n\n${error}\n`);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
});
|
|
3
|
+
export const logout = new Command("logout").description("Log out from Attio").action(async () => {
|
|
4
|
+
await deleteAuthToken();
|
|
5
|
+
process.stdout.write("🔒 Successfully logged out.\n");
|
|
6
|
+
process.exit(0);
|
|
13
7
|
});
|