attio 0.0.1-experimental.20250403 → 0.0.1-experimental.20250408
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/{api → auth}/auth.js +5 -3
- package/lib/{api → auth}/keychain.js +1 -1
- 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 -17
- package/lib/commands/build/validate-typescript.js +7 -14
- package/lib/commands/build.js +29 -12
- package/lib/commands/dev/boot.js +38 -11
- package/lib/commands/dev/bundle-javascript.js +29 -28
- package/lib/commands/dev/graphql-code-gen.js +1 -1
- package/lib/commands/dev/onboarding.js +19 -5
- 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 +34 -6
- package/lib/commands/dev/validate-typescript.js +3 -13
- package/lib/commands/dev.js +49 -3
- package/lib/commands/init/create-project.js +45 -18
- package/lib/commands/init.js +28 -4
- package/lib/commands/login.js +1 -1
- package/lib/commands/logout.js +1 -1
- package/lib/commands/version/create/bundle-javascript.js +28 -0
- package/lib/commands/version/create.js +84 -34
- package/lib/commands/version/list.js +23 -6
- package/lib/commands/whoami.js +12 -4
- package/lib/spinners/determine-workspace.spinner.js +58 -0
- package/lib/{api → spinners}/get-app-info.spinner.js +2 -2
- package/lib/spinners/get-app-slug-from-package-json.js +46 -0
- package/lib/{api → spinners}/get-versions.spinner.js +2 -4
- package/lib/util/copy-with-replace.js +40 -18
- package/lib/util/create-directory.js +21 -14
- package/lib/util/find-available-port.js +1 -1
- package/lib/util/load-attio-cli-version.js +86 -0
- package/lib/util/spinner.js +4 -1
- package/lib/util/upload-bundle.js +3 -2
- package/package.json +3 -1
- package/lib/api/api-fetch.js +0 -32
- package/lib/api/complete-bundle-upload.js +0 -8
- package/lib/api/complete-prod-bundle-upload.js +0 -8
- package/lib/api/create-dev-version.js +0 -17
- package/lib/api/create-version.js +0 -19
- package/lib/api/determine-workspace.spinner.js +0 -32
- package/lib/api/fetch-app-info.js +0 -20
- package/lib/api/fetch-installation.js +0 -9
- package/lib/api/fetch-versions.js +0 -17
- package/lib/api/fetch-workspaces.js +0 -25
- package/lib/api/get-app-slug-from-package-json.js +0 -27
- package/lib/api/start-upload.js +0 -11
- package/lib/api/whoami.js +0 -15
- package/lib/commands/dev/build-javascript.js +0 -47
- package/lib/util/load-attio-cli-package-json.js +0 -27
- /package/lib/{api → auth}/ensure-authed.js +0 -0
- /package/lib/{api → util}/hard-exit.js +0 -0
|
@@ -1,51 +1,101 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { spinnerify } from "../../util/spinner.js";
|
|
4
|
-
import {
|
|
5
|
-
import { createVersion } from "../../api/create-version.js";
|
|
6
|
-
import { completeProdBundleUpload } from "../../api/complete-prod-bundle-upload.js";
|
|
7
|
-
import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
|
|
4
|
+
import { loadAttioCliVersion, printCliVersionError } from "../../util/load-attio-cli-version.js";
|
|
8
5
|
import { uploadBundle } from "../../util/upload-bundle.js";
|
|
9
|
-
import { getVersions } from "../../
|
|
10
|
-
import { getAppInfo } from "../../
|
|
11
|
-
import {
|
|
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";
|
|
12
15
|
export const versionCreate = new Command("create")
|
|
13
16
|
.description("Create a new unpublished version of your Attio app")
|
|
14
17
|
.action(async () => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
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({
|
|
29
63
|
appId: appInfo.app_id,
|
|
30
64
|
major: versions.length === 0
|
|
31
65
|
? 1
|
|
32
66
|
: Math.max(...versions.map((version) => version.major), 1),
|
|
33
|
-
cliVersion
|
|
67
|
+
cliVersion,
|
|
34
68
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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),
|
|
38
77
|
]);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
major: version.major,
|
|
45
|
-
minor: version.minor,
|
|
46
|
-
bundleId: version.app_prod_version_bundle_id,
|
|
47
|
-
});
|
|
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;
|
|
48
83
|
});
|
|
84
|
+
if (isErrored(versionResult)) {
|
|
85
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to create version: ${versionResult.error}\n`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
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);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
49
99
|
process.stdout.write(`\nVersion ${chalk.green(`${version.major}.${version.minor}`)} created!\n\n`);
|
|
50
100
|
process.exit(0);
|
|
51
101
|
});
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { getAppInfo } from "../../
|
|
3
|
-
import { getAppSlugFromPackageJson } from "../../
|
|
4
|
-
import { getVersions } from "../../
|
|
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";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import Table from "cli-table3";
|
|
7
7
|
import { format as formatDate } from "date-fns";
|
|
8
|
+
import { isErrored } from "@attio/fetchable";
|
|
9
|
+
import { printFetcherError } from "../../api/fetcher.js";
|
|
8
10
|
export const versionList = new Command()
|
|
9
11
|
.name("list")
|
|
10
12
|
.description("List all versions of your app")
|
|
11
13
|
.action(async () => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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;
|
|
15
32
|
if (versions.length === 0) {
|
|
16
33
|
process.stdout.write("No versions found\n");
|
|
17
34
|
process.exit(0);
|
package/lib/commands/whoami.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { loadAuthToken } from "../auth/keychain.js";
|
|
3
|
+
import { API } from "../api/api.js";
|
|
4
|
+
import { isErrored } from "@attio/fetchable";
|
|
5
|
+
import { printFetcherError } from "../api/fetcher.js";
|
|
4
6
|
export const whoami = new Command()
|
|
5
7
|
.name("whoami")
|
|
6
8
|
.description("Identify the current user")
|
|
@@ -10,7 +12,13 @@ export const whoami = new Command()
|
|
|
10
12
|
process.stdout.write("🔒 Not logged in.\n");
|
|
11
13
|
process.exit(0);
|
|
12
14
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
+
const result = await API.whoami();
|
|
16
|
+
if (isErrored(result)) {
|
|
17
|
+
const { fetcherError } = result.error;
|
|
18
|
+
printFetcherError("Error fetching user", fetcherError);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const { name, email_address } = result.value;
|
|
22
|
+
process.stdout.write(`👤 ${name.full} (${email_address})\n`);
|
|
15
23
|
process.exit(0);
|
|
16
24
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import { APP } from "../env.js";
|
|
3
|
+
import { spinnerify } from "../util/spinner.js";
|
|
4
|
+
import { API } from "../api/api.js";
|
|
5
|
+
import { isErrored, complete, errored } from "@attio/fetchable";
|
|
6
|
+
import { printFetcherError } from "../api/fetcher.js";
|
|
7
|
+
export async function determineWorkspace(workspaceSlug) {
|
|
8
|
+
const workspacesResult = await spinnerify("Loading workspaces...", "Workspaces loaded", async () => await API.fetchWorkspaces());
|
|
9
|
+
if (isErrored(workspacesResult)) {
|
|
10
|
+
return workspacesResult;
|
|
11
|
+
}
|
|
12
|
+
const workspaces = workspacesResult.value;
|
|
13
|
+
const workspace = workspaces.find((workspace) => workspace.slug === workspaceSlug);
|
|
14
|
+
if (workspace) {
|
|
15
|
+
process.stdout.write(`Using workspace: ${workspace.name}`);
|
|
16
|
+
return complete(workspace);
|
|
17
|
+
}
|
|
18
|
+
if (workspaceSlug) {
|
|
19
|
+
return errored({ code: "NO_WORKSPACE_FOUND", workspaceSlug });
|
|
20
|
+
}
|
|
21
|
+
if (workspaces.length === 0) {
|
|
22
|
+
return errored({ code: "NO_WORKSPACES_FOUND" });
|
|
23
|
+
}
|
|
24
|
+
if (workspaces.length === 1) {
|
|
25
|
+
process.stdout.write(`Using workspace: ${workspaces[0].name}`);
|
|
26
|
+
return complete(workspaces[0]);
|
|
27
|
+
}
|
|
28
|
+
const choice = await select({
|
|
29
|
+
message: "Choose a workspace",
|
|
30
|
+
choices: workspaces.map((workspace) => ({
|
|
31
|
+
name: workspace.name,
|
|
32
|
+
value: workspace,
|
|
33
|
+
})),
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(`Using workspace: ${choice.name}`);
|
|
36
|
+
return complete(choice);
|
|
37
|
+
}
|
|
38
|
+
export function printDetermineWorkspaceError(error) {
|
|
39
|
+
switch (error.code) {
|
|
40
|
+
case "FETCH_WORKSPACES_ERROR":
|
|
41
|
+
printFetcherError("Error fetching workspaces", error.fetcherError);
|
|
42
|
+
break;
|
|
43
|
+
case "NO_WORKSPACE_FOUND":
|
|
44
|
+
process.stderr.write(`You are not the admin any workspace with the slug "${error.workspaceSlug}". Either request permission from "${error.workspaceSlug}" or create your own.
|
|
45
|
+
|
|
46
|
+
${APP}/welcome/workspace-details
|
|
47
|
+
`);
|
|
48
|
+
break;
|
|
49
|
+
case "NO_WORKSPACES_FOUND":
|
|
50
|
+
process.stderr.write(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
|
|
51
|
+
|
|
52
|
+
${APP}/welcome/workspace-details
|
|
53
|
+
`);
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { API } from "../api/api.js";
|
|
2
2
|
import { spinnerify } from "../util/spinner.js";
|
|
3
3
|
export async function getAppInfo(appSlug) {
|
|
4
4
|
return await spinnerify("Loading app information...", (app) => `App found: ${app.title}`, async () => {
|
|
5
|
-
return await fetchAppInfo(appSlug);
|
|
5
|
+
return await API.fetchAppInfo(appSlug);
|
|
6
6
|
});
|
|
7
7
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { complete, errored } from "@attio/fetchable";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
const packageJsonSchema = z.object({
|
|
7
|
+
name: z.string({
|
|
8
|
+
required_error: "No name field found in package.json",
|
|
9
|
+
invalid_type_error: `"name" must be a string in package.json`,
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
export async function getAppSlugFromPackageJson() {
|
|
13
|
+
try {
|
|
14
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
15
|
+
const packageJsonRaw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
16
|
+
const result = packageJsonSchema.safeParse(packageJsonRaw);
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
return errored({ code: "MALFORMED_PACKAGE_JSON", error: result.error.issues[0]?.message });
|
|
19
|
+
}
|
|
20
|
+
return complete(result.data.name);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof SyntaxError) {
|
|
24
|
+
return errored({ code: "INVALID_JSON", error });
|
|
25
|
+
}
|
|
26
|
+
return errored({ code: "FILE_SYSTEM_ERROR", error });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function printPackageJsonError({ code, error }) {
|
|
30
|
+
switch (code) {
|
|
31
|
+
case "MALFORMED_PACKAGE_JSON":
|
|
32
|
+
if (error) {
|
|
33
|
+
process.stderr.write(`${chalk.red("✖ ")}Malformed package.json: ${error}\n`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
process.stderr.write(`${chalk.red("✖ ")}Malformed package.json\n`);
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
case "FILE_SYSTEM_ERROR":
|
|
40
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json\n`);
|
|
41
|
+
break;
|
|
42
|
+
case "INVALID_JSON":
|
|
43
|
+
process.stderr.write(`${chalk.red("✖ ")}Invalid JSON in package.json: ${error}\n`);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { fetchVersions } from "./fetch-versions.js";
|
|
2
1
|
import { spinnerify } from "../util/spinner.js";
|
|
2
|
+
import { API } from "../api/api.js";
|
|
3
3
|
export async function getVersions(appInfo) {
|
|
4
|
-
return await spinnerify("Loading versions...", "Versions loaded", async () =>
|
|
5
|
-
return await fetchVersions(appInfo.app_id);
|
|
6
|
-
});
|
|
4
|
+
return await spinnerify("Loading versions...", "Versions loaded", async () => await API.fetchVersions(appInfo.app_id));
|
|
7
5
|
}
|
|
@@ -1,34 +1,56 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
4
|
-
export
|
|
3
|
+
import { combineAsync, complete, errored, isErrored } from "@attio/fetchable";
|
|
4
|
+
export async function copyWithTransform(srcDir, destDir, transform) {
|
|
5
5
|
try {
|
|
6
6
|
await fs.mkdir(destDir, { recursive: true });
|
|
7
7
|
}
|
|
8
8
|
catch {
|
|
9
|
-
|
|
9
|
+
return errored({ code: "FAILED_TO_CREATE_DIRECTORY", directory: destDir });
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
let entries;
|
|
12
|
+
try {
|
|
13
|
+
entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return errored({ code: "FAILED_TO_LIST_FILES", directory: srcDir });
|
|
17
|
+
}
|
|
18
|
+
const results = await combineAsync(entries.map(async (entry) => {
|
|
13
19
|
const srcPath = path.join(srcDir, entry.name);
|
|
14
20
|
const destPath = path.join(destDir, entry.name);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
return await copyWithTransform(srcPath, destPath, transform);
|
|
23
|
+
}
|
|
24
|
+
else if (entry.isFile()) {
|
|
25
|
+
if (/\.(jpg|jpeg|png|gif|svg|webp|ico)$/i.test(entry.name)) {
|
|
26
|
+
try {
|
|
21
27
|
await fs.copyFile(srcPath, destPath);
|
|
22
28
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
catch {
|
|
30
|
+
return errored({ code: "FAILED_TO_COPY_FILE", src: srcPath, dest: destPath });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
let content;
|
|
35
|
+
try {
|
|
36
|
+
content = await fs.readFile(srcPath, "utf8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return errored({ code: "FAILED_TO_READ_FILE", path: srcPath });
|
|
40
|
+
}
|
|
41
|
+
content = transform(content);
|
|
42
|
+
try {
|
|
26
43
|
await fs.writeFile(destPath, content, "utf8");
|
|
27
44
|
}
|
|
45
|
+
catch {
|
|
46
|
+
return errored({ code: "FAILED_TO_WRITE_FILE", path: destPath });
|
|
47
|
+
}
|
|
28
48
|
}
|
|
29
49
|
}
|
|
30
|
-
|
|
31
|
-
hardExit(`Failed to copy "${srcPath}" to "${destPath}"`);
|
|
32
|
-
}
|
|
50
|
+
return complete(undefined);
|
|
33
51
|
}));
|
|
34
|
-
|
|
52
|
+
if (isErrored(results)) {
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
return complete(undefined);
|
|
56
|
+
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants, promises as fs } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import {
|
|
4
|
-
export const createDirectory = (name) => {
|
|
3
|
+
import { complete, errored } from "@attio/fetchable";
|
|
4
|
+
export const createDirectory = async (name) => {
|
|
5
5
|
const currentDir = process.cwd();
|
|
6
6
|
const newPath = join(currentDir, name);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
catch {
|
|
17
|
-
hardExit(`Write access to create the directory "${name}" in the current directory is denied.`);
|
|
7
|
+
try {
|
|
8
|
+
if (await fs
|
|
9
|
+
.access(newPath)
|
|
10
|
+
.then(() => true)
|
|
11
|
+
.catch(() => false)) {
|
|
12
|
+
return errored({
|
|
13
|
+
code: "DIRECTORY_ALREADY_EXISTS",
|
|
14
|
+
path: newPath,
|
|
15
|
+
});
|
|
18
16
|
}
|
|
17
|
+
await fs.access(currentDir, constants.W_OK);
|
|
18
|
+
await fs.mkdir(newPath);
|
|
19
|
+
return complete(newPath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return errored({
|
|
23
|
+
code: "WRITE_ACCESS_DENIED",
|
|
24
|
+
directory: name,
|
|
25
|
+
});
|
|
19
26
|
}
|
|
20
27
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { findUpSync } from "find-up-simple";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { errored, complete } from "@attio/fetchable";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
const FILE_NAME = "package.json";
|
|
8
|
+
const packageJsonSchema = z.object({
|
|
9
|
+
name: z.literal("attio"),
|
|
10
|
+
version: z.string({
|
|
11
|
+
required_error: "No CLI version found",
|
|
12
|
+
invalid_type_error: "CLI version must be a string in package.json",
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
let packageJson;
|
|
16
|
+
export function loadAttioCliVersion() {
|
|
17
|
+
if (packageJson === undefined) {
|
|
18
|
+
const cwd = fileURLToPath(import.meta.url);
|
|
19
|
+
const packageJsonPath = findUpSync(FILE_NAME, { cwd });
|
|
20
|
+
if (packageJsonPath === undefined) {
|
|
21
|
+
return errored({
|
|
22
|
+
code: "UNABLE_TO_FIND_PACKAGE_JSON",
|
|
23
|
+
directory: cwd,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
let contents;
|
|
27
|
+
try {
|
|
28
|
+
contents = readFileSync(packageJsonPath, "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return errored({
|
|
32
|
+
code: "UNABLE_TO_READ_PACKAGE_JSON",
|
|
33
|
+
error,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
let json;
|
|
37
|
+
try {
|
|
38
|
+
json = JSON.parse(contents);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return errored({
|
|
42
|
+
code: "UNABLE_TO_PARSE_PACKAGE_JSON",
|
|
43
|
+
error,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const result = packageJsonSchema.safeParse(json);
|
|
47
|
+
if (!result.success) {
|
|
48
|
+
return errored({
|
|
49
|
+
code: "INVALID_PACKAGE_JSON",
|
|
50
|
+
error: result.error,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
packageJson = result.data;
|
|
54
|
+
}
|
|
55
|
+
const { version } = packageJson;
|
|
56
|
+
if (!version) {
|
|
57
|
+
return errored({
|
|
58
|
+
code: "NO_CLI_VERSION_FOUND",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return complete(version);
|
|
62
|
+
}
|
|
63
|
+
export function printCliVersionError({ error }) {
|
|
64
|
+
switch (error.code) {
|
|
65
|
+
case "UNABLE_TO_FIND_PACKAGE_JSON":
|
|
66
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to find package.json in ${error.directory}\n`);
|
|
67
|
+
break;
|
|
68
|
+
case "UNABLE_TO_READ_PACKAGE_JSON":
|
|
69
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to read package.json: ${error.error}\n`);
|
|
70
|
+
break;
|
|
71
|
+
case "UNABLE_TO_PARSE_PACKAGE_JSON":
|
|
72
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to parse package.json: ${error.error}\n`);
|
|
73
|
+
break;
|
|
74
|
+
case "INVALID_PACKAGE_JSON":
|
|
75
|
+
process.stderr.write(`${chalk.red("✖ ")}Invalid package.json: ${error.error}\n`);
|
|
76
|
+
break;
|
|
77
|
+
case "ERROR_LOADING_PACKAGE_JSON":
|
|
78
|
+
process.stderr.write(`${chalk.red("✖ ")}Error loading package.json: ${error.error}\n`);
|
|
79
|
+
break;
|
|
80
|
+
case "NO_CLI_VERSION_FOUND":
|
|
81
|
+
process.stderr.write(`${chalk.red("✖ ")}No CLI version found in attio's package.json\n`);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/lib/util/spinner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { isComplete } from "@attio/fetchable";
|
|
2
3
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
3
4
|
class Spinner {
|
|
4
5
|
frameIndex = 0;
|
|
@@ -49,7 +50,9 @@ export async function spinnerify(busyMessage, successMessage, fn) {
|
|
|
49
50
|
spinner.start(busyMessage);
|
|
50
51
|
try {
|
|
51
52
|
const result = await fn();
|
|
52
|
-
|
|
53
|
+
if (isComplete(result)) {
|
|
54
|
+
spinner.success(typeof successMessage === "string" ? successMessage : successMessage(result.value));
|
|
55
|
+
}
|
|
53
56
|
return result;
|
|
54
57
|
}
|
|
55
58
|
finally {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { complete, errored } from "@attio/fetchable";
|
|
2
2
|
export async function uploadBundle(bundle, uploadUrl) {
|
|
3
3
|
try {
|
|
4
4
|
await fetch(uploadUrl, {
|
|
@@ -9,8 +9,9 @@ export async function uploadBundle(bundle, uploadUrl) {
|
|
|
9
9
|
"Content-Length": String(Buffer.from(bundle).length),
|
|
10
10
|
},
|
|
11
11
|
});
|
|
12
|
+
return complete(undefined);
|
|
12
13
|
}
|
|
13
14
|
catch (error) {
|
|
14
|
-
|
|
15
|
+
return errored({ code: "BUNDLE_UPLOAD_ERROR", error, uploadUrl });
|
|
15
16
|
}
|
|
16
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "attio",
|
|
3
|
-
"version": "0.0.1-experimental.
|
|
3
|
+
"version": "0.0.1-experimental.20250408",
|
|
4
4
|
"bin": "lib/attio.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@attio/fetchable": "0.0.1-experimental.4",
|
|
34
35
|
"@babel/code-frame": "7.24.7",
|
|
35
36
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
|
36
37
|
"@graphql-codegen/core": "4.0.2",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"keytar": "^7.9.0",
|
|
55
56
|
"node-notifier": "^10.0.1",
|
|
56
57
|
"open": "^7.0.0",
|
|
58
|
+
"prettier": "^3.4.1",
|
|
57
59
|
"stdin-blocker": "^2.0.0",
|
|
58
60
|
"tiny-cursor": "^2.0.0",
|
|
59
61
|
"tmp-promise": "^3.0.3",
|
package/lib/api/api-fetch.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { API } from "../env.js";
|
|
2
|
-
import { ensureAuthed } from "./ensure-authed.js";
|
|
3
|
-
import { hardExit } from "./hard-exit.js";
|
|
4
|
-
export async function apiFetch(verb, path, fetchOptions, schema, allowNull = "error-on-404") {
|
|
5
|
-
const token = await ensureAuthed();
|
|
6
|
-
try {
|
|
7
|
-
const response = await fetch(path.startsWith("https") ? path : `${API}/${path}`, {
|
|
8
|
-
...fetchOptions,
|
|
9
|
-
headers: {
|
|
10
|
-
"x-attio-platform": "developer-cli",
|
|
11
|
-
"Authorization": `Bearer ${token}`,
|
|
12
|
-
"Content-Type": "application/json",
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
if (response.status === 404 && allowNull === "null-on-404") {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const errorText = await response.text();
|
|
20
|
-
return hardExit(`Error ${verb}: ${response.status} ${errorText}`);
|
|
21
|
-
}
|
|
22
|
-
const data = await response.json();
|
|
23
|
-
const result = schema.safeParse(data);
|
|
24
|
-
if (!result.success) {
|
|
25
|
-
return hardExit(`Invalid response ${verb}: ${result.error.message}`);
|
|
26
|
-
}
|
|
27
|
-
return result.data;
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
return hardExit(`Error ${verb}: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { apiFetch } from "./api-fetch.js";
|
|
3
|
-
const completeBundleUploadSchema = z.object({
|
|
4
|
-
success: z.literal(true),
|
|
5
|
-
});
|
|
6
|
-
export async function completeBundleUpload({ appId, devVersionId, bundleId, }) {
|
|
7
|
-
return (await apiFetch("completing bundle upload", `apps/${appId}/dev-versions/${devVersionId}/bundles/${bundleId}/complete`, { method: "POST" }, completeBundleUploadSchema)).success;
|
|
8
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { apiFetch } from "./api-fetch.js";
|
|
3
|
-
const completeBundleUploadSchema = z.object({
|
|
4
|
-
success: z.literal(true),
|
|
5
|
-
});
|
|
6
|
-
export async function completeProdBundleUpload({ appId, bundleId, major, minor, }) {
|
|
7
|
-
return (await apiFetch("completing production bundle upload", `apps/${appId}/prod-versions/${major}/${minor}/bundles/${bundleId}/complete`, { method: "POST" }, completeBundleUploadSchema)).success;
|
|
8
|
-
}
|