attio 0.0.1-experimental.20250402.1 → 0.0.1-experimental.20250407
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.js +98 -0
- package/lib/api/fetcher.js +65 -0
- package/lib/api/schemas.js +76 -0
- package/lib/auth/auth.js +121 -0
- package/lib/auth/ensure-authed.js +14 -0
- package/lib/auth/keychain.js +52 -0
- package/lib/build/client/generate-client-entry.js +14 -4
- package/lib/build/server/generate-server-entry.js +24 -15
- package/lib/commands/build/build-javascript.js +13 -19
- package/lib/commands/build/validate-typescript.js +7 -14
- package/lib/commands/build.js +27 -25
- package/lib/commands/dev/boot.js +38 -15
- package/lib/commands/dev/bundle-javascript.js +65 -27
- package/lib/commands/dev/graphql-code-gen.js +3 -2
- package/lib/commands/dev/onboarding.js +20 -6
- package/lib/commands/dev/prepare-build-contexts.js +163 -32
- package/lib/{api → commands/dev}/start-graphql-server.js +2 -2
- package/lib/commands/dev/upload.js +40 -44
- package/lib/commands/dev/validate-typescript.js +3 -17
- package/lib/commands/dev.js +95 -13
- package/lib/commands/init/create-project.js +53 -35
- package/lib/commands/init.js +30 -9
- package/lib/commands/login.js +7 -11
- package/lib/commands/logout.js +5 -11
- package/lib/commands/version/create/bundle-javascript.js +28 -0
- package/lib/commands/version/create.js +89 -64
- package/lib/commands/version/list.js +54 -26
- package/lib/commands/whoami.js +15 -11
- package/lib/spinners/determine-workspace.spinner.js +58 -0
- package/lib/spinners/get-app-info.spinner.js +7 -0
- package/lib/spinners/get-app-slug-from-package-json.js +46 -0
- package/lib/spinners/get-versions.spinner.js +5 -0
- package/lib/util/copy-with-replace.js +40 -17
- package/lib/util/create-directory.js +21 -13
- package/lib/util/find-available-port.js +2 -1
- package/lib/util/hard-exit.js +6 -0
- package/lib/util/load-attio-cli-version.js +86 -0
- package/lib/util/spinner.js +16 -1
- package/lib/util/upload-bundle.js +17 -0
- package/package.json +2 -1
- package/lib/api/auth.js +0 -117
- package/lib/api/complete-bundle-upload.js +0 -21
- package/lib/api/complete-prod-bundle-upload.js +0 -21
- package/lib/api/create-dev-version.js +0 -22
- package/lib/api/create-version.js +0 -24
- package/lib/api/determine-workspace.spinner.js +0 -39
- package/lib/api/ensure-authed.js +0 -31
- package/lib/api/fetch-app-info.js +0 -26
- package/lib/api/fetch-connections.js +0 -20
- package/lib/api/fetch-installation.js +0 -20
- package/lib/api/fetch-versions.js +0 -25
- package/lib/api/fetch-workspaces.js +0 -33
- package/lib/api/get-app-info.spinner.js +0 -18
- package/lib/api/get-app-slug-from-package-json.js +0 -26
- package/lib/api/get-versions.spinner.js +0 -21
- package/lib/api/handle-error.js +0 -18
- package/lib/api/keychain.js +0 -30
- package/lib/api/make-headers.js +0 -7
- package/lib/api/remove-connection-definition.js +0 -10
- package/lib/api/start-upload.js +0 -18
- package/lib/api/whoami.js +0 -21
- package/lib/commands/dev/build-javascript.js +0 -49
- 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/util/load-attio-cli-package-json.js +0 -26
package/lib/commands/dev.js
CHANGED
|
@@ -8,41 +8,123 @@ import { bundleJavaScript } from "./dev/bundle-javascript.js";
|
|
|
8
8
|
import { boot } from "./dev/boot.js";
|
|
9
9
|
import { onboarding } from "./dev/onboarding.js";
|
|
10
10
|
import { graphqlServer } from "./dev/graphql-server.js";
|
|
11
|
-
import { upload } from "./dev/upload.js";
|
|
11
|
+
import { printUploadError, upload } from "./dev/upload.js";
|
|
12
|
+
import { isErrored } from "@attio/fetchable";
|
|
13
|
+
import { printJsError, printTsError } from "../util/typescript.js";
|
|
14
|
+
import { printBuildContextError, } from "./dev/prepare-build-contexts.js";
|
|
15
|
+
import notifier from "node-notifier";
|
|
16
|
+
const notifyTsErrors = (errors) => {
|
|
17
|
+
notifier.notify({
|
|
18
|
+
title: `TypeScript Error${errors.length === 1 ? "" : "s"}`,
|
|
19
|
+
message: `There ${errors.length === 1 ? "was one error" : `were ${errors.length} errors`} in your TypeScript code`,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const notifyJsErrors = (errors) => {
|
|
23
|
+
const totalErrors = (errors.errors?.length || 0) + (errors.warnings?.length || 0);
|
|
24
|
+
notifier.notify({
|
|
25
|
+
title: `JavaScript ${totalErrors === 1 ? "Error" : "Errors"}`,
|
|
26
|
+
message: `There ${totalErrors === 1 ? "was one error" : `were ${totalErrors} errors`} in your JavaScript code`,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
12
29
|
export const optionsSchema = z.object({
|
|
13
30
|
workspace: z.string().optional(),
|
|
14
31
|
});
|
|
15
32
|
export const dev = new Command("dev")
|
|
16
33
|
.description("Develop your Attio app")
|
|
17
|
-
.addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
|
|
18
34
|
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
19
35
|
.action(async (unparsedOptions) => {
|
|
20
36
|
const { workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
|
|
21
37
|
const cleanupFunctions = [];
|
|
38
|
+
let isCleaningUp = false;
|
|
39
|
+
const cleanup = async () => {
|
|
40
|
+
if (isCleaningUp)
|
|
41
|
+
return;
|
|
42
|
+
isCleaningUp = true;
|
|
43
|
+
try {
|
|
44
|
+
for (const cleanup of cleanupFunctions.reverse()) {
|
|
45
|
+
try {
|
|
46
|
+
await Promise.race([
|
|
47
|
+
cleanup(),
|
|
48
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Cleanup timeout")), 2000)),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
process.stderr.write(chalk.yellow(`Warning during cleanup: ${error}\n`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
process.stderr.write(chalk.red(`Error during cleanup: ${error}\n`));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
22
60
|
try {
|
|
23
|
-
const {
|
|
61
|
+
const { appId, appSlug, workspace, devVersionId } = await boot({ workspaceSlug });
|
|
24
62
|
const cleanupGraphqlServer = graphqlServer();
|
|
25
63
|
cleanupFunctions.push(cleanupGraphqlServer);
|
|
26
|
-
const cleanupOnboardingDaemon = onboarding({
|
|
64
|
+
const cleanupOnboardingDaemon = onboarding({ appId, appSlug, workspace });
|
|
27
65
|
cleanupFunctions.push(cleanupOnboardingDaemon);
|
|
28
|
-
|
|
66
|
+
let haveTsErrors = false;
|
|
67
|
+
const [cleanupTs, triggerTs] = validateTypeScript(() => {
|
|
68
|
+
if (haveTsErrors) {
|
|
69
|
+
process.stdout.write(`${chalk.green("✓")} TypeScript errors fixed\n`);
|
|
70
|
+
haveTsErrors = false;
|
|
71
|
+
}
|
|
72
|
+
}, (errors) => {
|
|
73
|
+
haveTsErrors = true;
|
|
74
|
+
errors.forEach(printTsError);
|
|
75
|
+
notifyTsErrors(errors);
|
|
76
|
+
});
|
|
29
77
|
cleanupFunctions.push(cleanupTs);
|
|
30
78
|
const cleanupGraphqlCodeGen = graphqlCodeGen(triggerTs);
|
|
31
79
|
cleanupFunctions.push(cleanupGraphqlCodeGen);
|
|
32
|
-
|
|
33
|
-
|
|
80
|
+
let haveJsErrors = false;
|
|
81
|
+
const cleanupJs = bundleJavaScript(async (contents) => {
|
|
82
|
+
if (haveJsErrors) {
|
|
83
|
+
process.stdout.write(`${chalk.green("✓")} JavaScript errors fixed\n`);
|
|
84
|
+
haveJsErrors = false;
|
|
85
|
+
}
|
|
86
|
+
const uploadResult = await upload({ contents, devVersionId, appId });
|
|
87
|
+
if (isErrored(uploadResult)) {
|
|
88
|
+
printUploadError(uploadResult.error);
|
|
89
|
+
}
|
|
90
|
+
}, async (error) => {
|
|
91
|
+
haveJsErrors = true;
|
|
92
|
+
if (error.code === "BUILD_JAVASCRIPT_ERROR") {
|
|
93
|
+
notifyJsErrors(error);
|
|
94
|
+
const { errors, warnings } = error;
|
|
95
|
+
errors.forEach((error) => printJsError(error, "error"));
|
|
96
|
+
warnings.forEach((warning) => printJsError(warning, "warning"));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
printBuildContextError(error);
|
|
100
|
+
}
|
|
34
101
|
});
|
|
35
102
|
cleanupFunctions.push(cleanupJs);
|
|
36
103
|
printMessage("\n👀 Watching for changes...");
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
104
|
+
process.on("SIGINT", async () => {
|
|
105
|
+
await cleanup();
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
|
108
|
+
process.on("SIGTERM", async () => {
|
|
109
|
+
await cleanup();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
process.on("uncaughtException", async (error) => {
|
|
113
|
+
process.stderr.write(chalk.red(`Uncaught exception: ${error}\n`));
|
|
114
|
+
await cleanup();
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
process.on("unhandledRejection", async (error) => {
|
|
118
|
+
process.stderr.write(chalk.red(`Unhandled rejection: ${error}\n`));
|
|
119
|
+
await cleanup();
|
|
120
|
+
process.exit(1);
|
|
121
|
+
});
|
|
122
|
+
await new Promise(() => {
|
|
42
123
|
});
|
|
43
124
|
}
|
|
44
125
|
catch (error) {
|
|
45
|
-
process.stderr.write(
|
|
126
|
+
process.stderr.write(chalk.red(`✖ ${error}\n`));
|
|
127
|
+
await cleanup();
|
|
46
128
|
process.exit(1);
|
|
47
129
|
}
|
|
48
130
|
});
|
|
@@ -1,50 +1,68 @@
|
|
|
1
1
|
import { fileURLToPath } from "url";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import boxen from "boxen";
|
|
5
4
|
import { existsSync } from "fs";
|
|
6
5
|
import { createDirectory } from "../../util/create-directory.js";
|
|
7
6
|
import { canWrite } from "../../util/can-write.js";
|
|
8
7
|
import { copyWithTransform } from "../../util/copy-with-replace.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
export async function createProject({ appSlug, language, appInfo,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
import { spinnerify } from "../../util/spinner.js";
|
|
9
|
+
import { combineAsync, complete, errored, isErrored } from "@attio/fetchable";
|
|
10
|
+
export async function createProject({ appSlug, language, appInfo, }) {
|
|
11
|
+
return await spinnerify("Creating project...", "Project created", async () => {
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const projectPath = path.join(cwd, appSlug);
|
|
14
|
+
if (existsSync(projectPath)) {
|
|
15
|
+
return errored({ code: "DIRECTORY_ALREADY_EXISTS", path: projectPath });
|
|
16
|
+
}
|
|
17
|
+
if (!canWrite(cwd)) {
|
|
18
|
+
return errored({ code: "WRITE_ACCESS_DENIED", directory: cwd });
|
|
19
|
+
}
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const projectDirResult = await createDirectory(appSlug);
|
|
22
|
+
if (isErrored(projectDirResult)) {
|
|
23
|
+
return projectDirResult;
|
|
24
|
+
}
|
|
25
|
+
const projectDir = projectDirResult.value;
|
|
26
|
+
const templatesDir = path.resolve(__dirname, "../../templates", language);
|
|
27
|
+
const commonDir = path.resolve(__dirname, "../../templates", "common");
|
|
25
28
|
const transform = (contents) => contents
|
|
26
29
|
.replaceAll("title-to-be-replaced", appInfo.title)
|
|
27
|
-
.replaceAll("id-to-be-replaced",
|
|
30
|
+
.replaceAll("id-to-be-replaced", appInfo.app_id)
|
|
28
31
|
.replaceAll("slug-to-be-replaced", appSlug);
|
|
29
|
-
await
|
|
32
|
+
const results = await combineAsync([
|
|
30
33
|
copyWithTransform(templatesDir, projectDir, transform),
|
|
31
34
|
copyWithTransform(commonDir, projectDir, transform),
|
|
32
35
|
]);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
if (isErrored(results)) {
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
return complete(undefined);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function printCreateProjectError(error) {
|
|
43
|
+
switch (error.code) {
|
|
44
|
+
case "DIRECTORY_ALREADY_EXISTS":
|
|
45
|
+
process.stderr.write(chalk.red(`Directory ${error.path} already exists`));
|
|
46
|
+
break;
|
|
47
|
+
case "WRITE_ACCESS_DENIED":
|
|
48
|
+
process.stderr.write(chalk.red(`Write access denied to ${error.directory}`));
|
|
49
|
+
break;
|
|
50
|
+
case "FAILED_TO_CREATE_DIRECTORY":
|
|
51
|
+
process.stderr.write(chalk.red(`Failed to create directory ${error.directory}`));
|
|
52
|
+
break;
|
|
53
|
+
case "FAILED_TO_COPY_FILE":
|
|
54
|
+
process.stderr.write(chalk.red(`Failed to copy file ${error.src} to ${error.dest}`));
|
|
55
|
+
break;
|
|
56
|
+
case "FAILED_TO_LIST_FILES":
|
|
57
|
+
process.stderr.write(chalk.red(`Failed to list files in ${error.directory}`));
|
|
58
|
+
break;
|
|
59
|
+
case "FAILED_TO_READ_FILE":
|
|
60
|
+
process.stderr.write(chalk.red(`Failed to read file ${error.path}`));
|
|
61
|
+
break;
|
|
62
|
+
case "FAILED_TO_WRITE_FILE":
|
|
63
|
+
process.stderr.write(chalk.red(`Failed to write file ${error.path}`));
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
return error;
|
|
49
67
|
}
|
|
50
68
|
}
|
package/lib/commands/init.js
CHANGED
|
@@ -1,37 +1,58 @@
|
|
|
1
1
|
import { Argument, Command, Option } from "commander";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import {
|
|
5
|
-
import { createProject } from "./init/create-project.js";
|
|
4
|
+
import { createProject, printCreateProjectError } 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 "../spinners/get-app-info.spinner.js";
|
|
8
|
+
import { printFetcherError } from "../api/fetcher.js";
|
|
9
|
+
import { isErrored } from "@attio/fetchable";
|
|
10
|
+
import boxen from "boxen";
|
|
8
11
|
export const argsSchema = z.string();
|
|
9
12
|
export const optionsSchema = z.object({
|
|
10
13
|
language: z.enum(["javascript", "typescript"]).optional(),
|
|
11
|
-
workspace: z.string().optional(),
|
|
12
14
|
});
|
|
13
15
|
export const init = new Command("init")
|
|
14
16
|
.description("Initialize a new Attio app")
|
|
15
17
|
.addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
|
|
16
18
|
.addOption(new Option("--language <language>", "Language").choices(["javascript", "typescript"]))
|
|
17
|
-
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
18
19
|
.action(async (unparsedArgs, unparsedOptions) => {
|
|
19
20
|
try {
|
|
20
21
|
printLogo();
|
|
21
22
|
const appSlug = argsSchema.parse(unparsedArgs);
|
|
22
|
-
const { language: cliLanguage
|
|
23
|
-
const
|
|
23
|
+
const { language: cliLanguage } = optionsSchema.parse(unparsedOptions);
|
|
24
|
+
const appInfoResult = await getAppInfo(appSlug);
|
|
25
|
+
if (isErrored(appInfoResult)) {
|
|
26
|
+
printFetcherError("Failed to fetch app info", appInfoResult.error.fetcherError);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const appInfo = appInfoResult.value;
|
|
24
30
|
const language = cliLanguage ?? (await askLanguage());
|
|
25
|
-
await createProject({
|
|
31
|
+
const result = await createProject({
|
|
26
32
|
appSlug,
|
|
27
33
|
language,
|
|
28
34
|
appInfo,
|
|
29
|
-
appId,
|
|
30
35
|
});
|
|
36
|
+
if (isErrored(result)) {
|
|
37
|
+
printCreateProjectError(result.error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write(`${chalk.green(`SUCCESS!! 🎉 Your app directory has been created.`)}
|
|
41
|
+
|
|
42
|
+
To get started, run:
|
|
43
|
+
|
|
44
|
+
${boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
|
|
45
|
+
padding: 1,
|
|
46
|
+
margin: 1,
|
|
47
|
+
borderStyle: "round",
|
|
48
|
+
})}
|
|
49
|
+
|
|
50
|
+
(${chalk.yellow("yarn")}, ${chalk.yellow("pnpm")}, and ${chalk.yellow("bun")} also work!)
|
|
51
|
+
`);
|
|
31
52
|
process.exit(0);
|
|
32
53
|
}
|
|
33
54
|
catch (error) {
|
|
34
|
-
process.stderr.write(
|
|
55
|
+
process.stderr.write(chalk.red("✖ " + String(error)) + "\n");
|
|
35
56
|
process.exit(1);
|
|
36
57
|
}
|
|
37
58
|
});
|
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 "../auth/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
|
-
import { deleteAuthToken } from "../
|
|
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
|
-
});
|
|
2
|
+
import { deleteAuthToken } from "../auth/keychain.js";
|
|
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
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { complete, isErrored, errored, combineAsync } from "@attio/fetchable";
|
|
2
|
+
import { prepareBuildContexts, } from "../../dev/prepare-build-contexts.js";
|
|
3
|
+
export async function bundleJavaScript() {
|
|
4
|
+
const buildContextsResult = await prepareBuildContexts("in-memory");
|
|
5
|
+
if (isErrored(buildContextsResult)) {
|
|
6
|
+
return errored({
|
|
7
|
+
code: "ERROR_PREPARING_BUILD_CONTEXT",
|
|
8
|
+
buildContextError: buildContextsResult.error,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
const buildContexts = buildContextsResult.value;
|
|
12
|
+
const results = await combineAsync(buildContexts.map(async (context) => await context.rebuild()));
|
|
13
|
+
if (isErrored(results)) {
|
|
14
|
+
return errored({
|
|
15
|
+
code: "ERROR_BUILDING_BUNDLE",
|
|
16
|
+
error: results.error,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const bundles = results.value.map((result) => result.outputFiles[0].text);
|
|
20
|
+
const disposeResults = await combineAsync(buildContexts.map(async (context) => await context.dispose()));
|
|
21
|
+
if (isErrored(disposeResults)) {
|
|
22
|
+
return errored({
|
|
23
|
+
code: "ERROR_DISPOSING_BUILD_CONTEXT",
|
|
24
|
+
buildContextError: disposeResults.error,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return complete(bundles);
|
|
28
|
+
}
|
|
@@ -1,76 +1,101 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import { spinnerify } from "../../util/spinner.js";
|
|
4
|
+
import { loadAttioCliVersion, printCliVersionError } from "../../util/load-attio-cli-version.js";
|
|
5
|
+
import { uploadBundle } from "../../util/upload-bundle.js";
|
|
6
|
+
import { getVersions } from "../../spinners/get-versions.spinner.js";
|
|
7
|
+
import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
|
|
8
|
+
import { printFetcherError } from "../../api/fetcher.js";
|
|
9
|
+
import { combineAsync, isErrored } from "@attio/fetchable";
|
|
10
|
+
import { getAppSlugFromPackageJson, printPackageJsonError, } from "../../spinners/get-app-slug-from-package-json.js";
|
|
11
|
+
import { bundleJavaScript } from "./create/bundle-javascript.js";
|
|
12
|
+
import { printBuildContextError } from "../dev/prepare-build-contexts.js";
|
|
13
|
+
import { API } from "../../api/api.js";
|
|
14
|
+
import { printJsError } from "../../util/typescript.js";
|
|
10
15
|
export const versionCreate = new Command("create")
|
|
11
16
|
.description("Create a new unpublished version of your Attio app")
|
|
12
17
|
.action(async () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
const appSlugResult = await getAppSlugFromPackageJson();
|
|
19
|
+
if (isErrored(appSlugResult)) {
|
|
20
|
+
printPackageJsonError(appSlugResult.error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const appSlug = appSlugResult.value;
|
|
24
|
+
const appInfoResult = await getAppInfo(appSlug);
|
|
25
|
+
if (isErrored(appInfoResult)) {
|
|
26
|
+
printFetcherError("Error loading app info", appInfoResult.error.fetcherError);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const appInfo = appInfoResult.value;
|
|
30
|
+
const bundleResult = await spinnerify("Bundling JavaScript...", "Bundling complete", bundleJavaScript);
|
|
31
|
+
if (isErrored(bundleResult)) {
|
|
32
|
+
if (bundleResult.error.code === "ERROR_BUILDING_BUNDLE") {
|
|
33
|
+
const { error } = bundleResult.error;
|
|
34
|
+
if (error.code === "BUILD_JAVASCRIPT_ERROR") {
|
|
35
|
+
const { errors, warnings } = error;
|
|
36
|
+
errors.forEach((error) => printJsError(error, "error"));
|
|
37
|
+
warnings.forEach((warning) => printJsError(warning, "warning"));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
printBuildContextError(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
printBuildContextError(bundleResult.error.buildContextError);
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const [clientBundle, serverBundle] = bundleResult.value;
|
|
49
|
+
const versionsResult = await getVersions(appInfo);
|
|
50
|
+
if (isErrored(versionsResult)) {
|
|
51
|
+
printFetcherError("Error fetching versions", versionsResult.error.fetcherError);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const versions = versionsResult.value;
|
|
55
|
+
const versionResult = await spinnerify("Uploading...", "Upload complete", async () => {
|
|
56
|
+
const cliVersionResult = loadAttioCliVersion();
|
|
57
|
+
if (isErrored(cliVersionResult)) {
|
|
58
|
+
printCliVersionError(cliVersionResult);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const cliVersion = cliVersionResult.value;
|
|
62
|
+
const versionResult = await API.createVersion({
|
|
36
63
|
appId: appInfo.app_id,
|
|
37
|
-
major
|
|
38
|
-
|
|
64
|
+
major: versions.length === 0
|
|
65
|
+
? 1
|
|
66
|
+
: Math.max(...versions.map((version) => version.major), 1),
|
|
67
|
+
cliVersion,
|
|
39
68
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}),
|
|
49
|
-
fetch(version.server_bundle_upload_url, {
|
|
50
|
-
method: "PUT",
|
|
51
|
-
body: serverBundle,
|
|
52
|
-
headers: {
|
|
53
|
-
"Content-Type": "text/javascript",
|
|
54
|
-
"Content-Length": String(Buffer.from(serverBundle).length),
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
69
|
+
if (isErrored(versionResult)) {
|
|
70
|
+
printFetcherError("Error creating version", versionResult.error.fetcherError);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const { client_bundle_upload_url, server_bundle_upload_url } = versionResult.value;
|
|
74
|
+
const uploadResult = await combineAsync([
|
|
75
|
+
uploadBundle(clientBundle, client_bundle_upload_url),
|
|
76
|
+
uploadBundle(serverBundle, server_bundle_upload_url),
|
|
57
77
|
]);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
signSpinner.success("Bundles signed");
|
|
69
|
-
process.stdout.write(`\nVersion ${chalk.green(`${version.major}.${version.minor}`)} created!\n\n`);
|
|
70
|
-
process.exit(0);
|
|
78
|
+
if (isErrored(uploadResult)) {
|
|
79
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to upload bundle to: ${uploadResult.error.uploadUrl}\n`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
return versionResult;
|
|
83
|
+
});
|
|
84
|
+
if (isErrored(versionResult)) {
|
|
85
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to create version: ${versionResult.error}\n`);
|
|
86
|
+
process.exit(1);
|
|
71
87
|
}
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
const version = versionResult.value;
|
|
89
|
+
const signingResult = await spinnerify("Signing bundles...", "Bundles signed", async () => await API.completeProdBundleUpload({
|
|
90
|
+
appId: appInfo.app_id,
|
|
91
|
+
major: version.major,
|
|
92
|
+
minor: version.minor,
|
|
93
|
+
bundleId: version.app_prod_version_bundle_id,
|
|
94
|
+
}));
|
|
95
|
+
if (isErrored(signingResult)) {
|
|
96
|
+
printFetcherError("Error signing bundles", signingResult.error.fetcherError);
|
|
74
97
|
process.exit(1);
|
|
75
98
|
}
|
|
99
|
+
process.stdout.write(`\nVersion ${chalk.green(`${version.major}.${version.minor}`)} created!\n\n`);
|
|
100
|
+
process.exit(0);
|
|
76
101
|
});
|
|
@@ -1,36 +1,64 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
|
|
3
|
+
import { getAppSlugFromPackageJson, printPackageJsonError, } from "../../spinners/get-app-slug-from-package-json.js";
|
|
4
|
+
import { getVersions } from "../../spinners/get-versions.spinner.js";
|
|
4
5
|
import chalk from "chalk";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
export const versionList = new Command(
|
|
10
|
-
.
|
|
11
|
-
.
|
|
6
|
+
import Table from "cli-table3";
|
|
7
|
+
import { format as formatDate } from "date-fns";
|
|
8
|
+
import { isErrored } from "@attio/fetchable";
|
|
9
|
+
import { printFetcherError } from "../../api/fetcher.js";
|
|
10
|
+
export const versionList = new Command()
|
|
11
|
+
.name("list")
|
|
12
|
+
.description("List all versions of your app")
|
|
12
13
|
.action(async () => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const appSlugResult = await getAppSlugFromPackageJson();
|
|
15
|
+
if (isErrored(appSlugResult)) {
|
|
16
|
+
printPackageJsonError(appSlugResult.error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const appSlug = appSlugResult.value;
|
|
20
|
+
const appInfoResult = await getAppInfo(appSlug);
|
|
21
|
+
if (isErrored(appInfoResult)) {
|
|
22
|
+
printFetcherError("Error loading app info", appInfoResult.error.fetcherError);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const appInfo = appInfoResult.value;
|
|
26
|
+
const versionsResult = await getVersions(appInfo);
|
|
27
|
+
if (isErrored(versionsResult)) {
|
|
28
|
+
printFetcherError("Error loading versions", versionsResult.error.fetcherError);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const versions = versionsResult.value;
|
|
32
|
+
if (versions.length === 0) {
|
|
33
|
+
process.stdout.write("No versions found\n");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
20
36
|
const table = new Table({
|
|
21
|
-
head: ["Version", "Published", "Installations", "Created"].map((h) => chalk.bold(h)),
|
|
37
|
+
head: ["Version", "Status", "Published", "Installations", "Created"].map((h) => chalk.bold(h)),
|
|
22
38
|
style: {
|
|
23
39
|
head: [],
|
|
24
40
|
border: [],
|
|
25
41
|
},
|
|
26
|
-
colAligns: ["center", "center", "right", "left"],
|
|
42
|
+
colAligns: ["center", "left", "center", "right", "left"],
|
|
43
|
+
});
|
|
44
|
+
versions.forEach((version) => {
|
|
45
|
+
const statusColors = {
|
|
46
|
+
private: chalk.gray,
|
|
47
|
+
in_review: chalk.yellow,
|
|
48
|
+
published: chalk.green,
|
|
49
|
+
rejected: chalk.red,
|
|
50
|
+
};
|
|
51
|
+
const formattedStatus = version.publication_status
|
|
52
|
+
.split("_")
|
|
53
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
54
|
+
.join(" ");
|
|
55
|
+
table.push([
|
|
56
|
+
`${version.major}.${version.minor}`,
|
|
57
|
+
statusColors[version.publication_status](formattedStatus),
|
|
58
|
+
version.is_published ? "Yes" : "No",
|
|
59
|
+
version.num_installations.toLocaleString(),
|
|
60
|
+
formatDate(new Date(version.created_at), "MMM d, yyyy, HH:mm"),
|
|
61
|
+
]);
|
|
27
62
|
});
|
|
28
|
-
|
|
29
|
-
`${version.major}.${version.minor}`,
|
|
30
|
-
version.is_published ? "Yes" : "No",
|
|
31
|
-
version.num_installations.toLocaleString(),
|
|
32
|
-
formatDate(new Date(version.created_at), "MMMM d, yyyy, HH:mm"),
|
|
33
|
-
]));
|
|
34
|
-
process.stdout.write(table.toString());
|
|
35
|
-
process.exit(0);
|
|
63
|
+
process.stdout.write("\n" + table.toString() + "\n");
|
|
36
64
|
});
|