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
package/lib/api/api.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Fetcher } from "./fetcher.js";
|
|
2
|
+
import { APP } from "../env.js";
|
|
3
|
+
import { isErrored, complete, errored } from "@attio/fetchable";
|
|
4
|
+
import { whoamiSchema, listDevWorkspacesResponseSchema, createVersionSchema, appInfoSchema, completeBundleUploadSchema, startUploadSchema, createDevVersionSchema, installationSchema, versionsSchema, TEST_WORKSPACES, TEST_APP_INFO, } from "./schemas.js";
|
|
5
|
+
class CliApi {
|
|
6
|
+
_fetcher;
|
|
7
|
+
constructor() {
|
|
8
|
+
this._fetcher = new Fetcher();
|
|
9
|
+
}
|
|
10
|
+
async whoami() {
|
|
11
|
+
const result = await this._fetcher.get(`${APP}/api/auth/whoami`, whoamiSchema);
|
|
12
|
+
if (isErrored(result)) {
|
|
13
|
+
return errored({ code: "WHOAMI_ERROR", fetcherError: result.error });
|
|
14
|
+
}
|
|
15
|
+
return complete(result.value.user);
|
|
16
|
+
}
|
|
17
|
+
async fetchWorkspaces() {
|
|
18
|
+
if (process.env.NODE_ENV === "test") {
|
|
19
|
+
return complete(TEST_WORKSPACES);
|
|
20
|
+
}
|
|
21
|
+
const result = await this._fetcher.get("/dev-workspaces", listDevWorkspacesResponseSchema);
|
|
22
|
+
if (isErrored(result)) {
|
|
23
|
+
return errored({ code: "FETCH_WORKSPACES_ERROR", fetcherError: result.error });
|
|
24
|
+
}
|
|
25
|
+
return complete(result.value.workspaces);
|
|
26
|
+
}
|
|
27
|
+
async createVersion({ appId, major, cliVersion, }) {
|
|
28
|
+
const result = await this._fetcher.post(`/apps/${appId}/prod-versions`, {
|
|
29
|
+
major,
|
|
30
|
+
cli_version: cliVersion,
|
|
31
|
+
}, createVersionSchema);
|
|
32
|
+
if (isErrored(result)) {
|
|
33
|
+
return errored({ code: "CREATE_VERSION_ERROR", fetcherError: result.error });
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
async fetchAppInfo(appSlug) {
|
|
38
|
+
if (process.env.NODE_ENV === "test") {
|
|
39
|
+
return complete(TEST_APP_INFO.app);
|
|
40
|
+
}
|
|
41
|
+
const result = await this._fetcher.get(`/apps/by-slug/${appSlug}`, appInfoSchema);
|
|
42
|
+
if (isErrored(result)) {
|
|
43
|
+
return errored({ code: "FETCH_APP_INFO_ERROR", fetcherError: result.error });
|
|
44
|
+
}
|
|
45
|
+
return complete(result.value.app);
|
|
46
|
+
}
|
|
47
|
+
async completeBundleUpload({ appId, devVersionId, bundleId, }) {
|
|
48
|
+
const result = await this._fetcher.post(`/apps/${appId}/dev-versions/${devVersionId}/bundles/${bundleId}/complete`, {}, completeBundleUploadSchema);
|
|
49
|
+
if (isErrored(result)) {
|
|
50
|
+
return errored({ code: "COMPLETE_BUNDLE_UPLOAD_ERROR", fetcherError: result.error });
|
|
51
|
+
}
|
|
52
|
+
return complete(undefined);
|
|
53
|
+
}
|
|
54
|
+
async completeProdBundleUpload({ appId, bundleId, major, minor, }) {
|
|
55
|
+
const result = await this._fetcher.post(`/apps/${appId}/prod-versions/${major}/${minor}/bundles/${bundleId}/complete`, {}, completeBundleUploadSchema);
|
|
56
|
+
if (isErrored(result)) {
|
|
57
|
+
return errored({ code: "COMPLETE_PROD_BUNDLE_UPLOAD_ERROR", fetcherError: result.error });
|
|
58
|
+
}
|
|
59
|
+
return complete(undefined);
|
|
60
|
+
}
|
|
61
|
+
async startUpload({ appId, devVersionId, }) {
|
|
62
|
+
const result = await this._fetcher.post(`/apps/${appId}/dev-versions/${devVersionId}/bundles`, {}, startUploadSchema);
|
|
63
|
+
if (isErrored(result)) {
|
|
64
|
+
return errored({ code: "START_UPLOAD_ERROR", fetcherError: result.error });
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async createDevVersion({ appId, cliVersion, targetWorkspaceId, environmentVariables, }) {
|
|
69
|
+
const result = await this._fetcher.post(`/apps/${appId}/dev-versions`, {
|
|
70
|
+
major: 1,
|
|
71
|
+
target_workspace_id: targetWorkspaceId,
|
|
72
|
+
environment_variables: environmentVariables,
|
|
73
|
+
cli_version: cliVersion,
|
|
74
|
+
}, createDevVersionSchema);
|
|
75
|
+
if (isErrored(result)) {
|
|
76
|
+
return errored({ code: "CREATE_DEV_VERSION_ERROR", fetcherError: result.error });
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
async fetchInstallation({ appId, workspaceId, }) {
|
|
81
|
+
const result = await this._fetcher.get(`/apps/${appId}/workspace/${workspaceId}/dev-installation`, installationSchema);
|
|
82
|
+
if (isErrored(result)) {
|
|
83
|
+
if (result.error.code === "HTTP_ERROR" && result.error.status === 404) {
|
|
84
|
+
return complete(null);
|
|
85
|
+
}
|
|
86
|
+
return errored({ code: "FETCH_INSTALLATION_ERROR", fetcherError: result.error });
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
async fetchVersions(appId) {
|
|
91
|
+
const result = await this._fetcher.get(`/apps/${appId}/prod-versions`, versionsSchema);
|
|
92
|
+
if (isErrored(result)) {
|
|
93
|
+
return errored({ code: "FETCH_VERSIONS_ERROR", fetcherError: result.error });
|
|
94
|
+
}
|
|
95
|
+
return complete(result.value.app_prod_versions);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export const API = new CliApi();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { complete, errored } from "@attio/fetchable";
|
|
2
|
+
import { API } from "../env.js";
|
|
3
|
+
import { ensureAuthed } from "../auth/ensure-authed.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
export class Fetcher {
|
|
6
|
+
async _fetch(path, fetchOptions, schema) {
|
|
7
|
+
const token = await ensureAuthed();
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(path.startsWith("https") ? path : `${API}${path}`, {
|
|
10
|
+
...fetchOptions,
|
|
11
|
+
headers: {
|
|
12
|
+
"x-attio-platform": "developer-cli",
|
|
13
|
+
"Authorization": `Bearer ${token}`,
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const errorText = await response.text();
|
|
19
|
+
return errored({
|
|
20
|
+
code: "HTTP_ERROR",
|
|
21
|
+
status: response.status,
|
|
22
|
+
message: errorText,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
const result = schema.safeParse(data);
|
|
27
|
+
if (!result.success) {
|
|
28
|
+
return errored({
|
|
29
|
+
code: "INVALID_RESPONSE",
|
|
30
|
+
message: result.error.message,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return complete(result.data);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return errored({
|
|
37
|
+
code: "NETWORK_ERROR",
|
|
38
|
+
message: error instanceof Error ? error.message : String(error),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async get(path, schema) {
|
|
43
|
+
return this._fetch(path, { method: "GET" }, schema);
|
|
44
|
+
}
|
|
45
|
+
async post(path, body, schema) {
|
|
46
|
+
return this._fetch(path, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
}, schema);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function printFetcherError(message, error) {
|
|
53
|
+
process.stderr.write(`${chalk.red("✖ ")}${message}\n`);
|
|
54
|
+
switch (error.code) {
|
|
55
|
+
case "HTTP_ERROR":
|
|
56
|
+
process.stderr.write(`HTTP Error (${error.status}): ${error.message}\n`);
|
|
57
|
+
break;
|
|
58
|
+
case "INVALID_RESPONSE":
|
|
59
|
+
process.stderr.write(`Invalid response: ${error.message}\n`);
|
|
60
|
+
break;
|
|
61
|
+
case "NETWORK_ERROR":
|
|
62
|
+
process.stderr.write(`Network error: ${error.message}\n`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const whoamiSchema = z.object({
|
|
3
|
+
user: z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
email_address: z.string(),
|
|
6
|
+
name: z.object({
|
|
7
|
+
full: z.string(),
|
|
8
|
+
}),
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
export const workspaceResponseSchema = z.object({
|
|
12
|
+
workspace_id: z.string().uuid(),
|
|
13
|
+
slug: z.string(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
logo_url: z.string().nullable(),
|
|
16
|
+
});
|
|
17
|
+
export const TEST_WORKSPACES = [
|
|
18
|
+
workspaceResponseSchema.parse({
|
|
19
|
+
workspace_id: "a85e3bcf-a9a2-4df9-9e92-e708bf98d238",
|
|
20
|
+
slug: "test-slug",
|
|
21
|
+
name: "Test Workspace",
|
|
22
|
+
logo_url: null,
|
|
23
|
+
}),
|
|
24
|
+
];
|
|
25
|
+
export const listDevWorkspacesResponseSchema = z.object({
|
|
26
|
+
workspaces: z.array(workspaceResponseSchema),
|
|
27
|
+
});
|
|
28
|
+
export const createVersionSchema = z.object({
|
|
29
|
+
app_id: z.string().uuid(),
|
|
30
|
+
major: z.number(),
|
|
31
|
+
minor: z.number(),
|
|
32
|
+
app_prod_version_bundle_id: z.string(),
|
|
33
|
+
client_bundle_upload_url: z.string(),
|
|
34
|
+
server_bundle_upload_url: z.string(),
|
|
35
|
+
});
|
|
36
|
+
export const appInfoSchema = z.object({
|
|
37
|
+
app: z.object({
|
|
38
|
+
app_id: z.string().uuid(),
|
|
39
|
+
title: z.string(),
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
export const TEST_APP_INFO = appInfoSchema.parse({
|
|
43
|
+
app: {
|
|
44
|
+
app_id: "0cbafb09-2ef7-473c-a048-08a5d190a395",
|
|
45
|
+
title: "Test App",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export const completeBundleUploadSchema = z.object({
|
|
49
|
+
success: z.literal(true),
|
|
50
|
+
});
|
|
51
|
+
export const startUploadSchema = z.object({
|
|
52
|
+
app_dev_version_id: z.string().uuid(),
|
|
53
|
+
app_dev_version_bundle_id: z.string().uuid(),
|
|
54
|
+
client_bundle_upload_url: z.string().url(),
|
|
55
|
+
server_bundle_upload_url: z.string().url(),
|
|
56
|
+
});
|
|
57
|
+
export const createDevVersionSchema = z.object({
|
|
58
|
+
app_id: z.string().uuid(),
|
|
59
|
+
app_dev_version_id: z.string(),
|
|
60
|
+
});
|
|
61
|
+
export const installationSchema = z.object({
|
|
62
|
+
app_id: z.string().uuid(),
|
|
63
|
+
workspace_id: z.string(),
|
|
64
|
+
});
|
|
65
|
+
export const versionSchema = z.object({
|
|
66
|
+
major: z.number(),
|
|
67
|
+
minor: z.number(),
|
|
68
|
+
created_at: z.string(),
|
|
69
|
+
released_at: z.string().nullable().optional(),
|
|
70
|
+
is_published: z.boolean(),
|
|
71
|
+
num_installations: z.number(),
|
|
72
|
+
publication_status: z.enum(["private", "in_review", "published", "rejected"]),
|
|
73
|
+
});
|
|
74
|
+
export const versionsSchema = z.object({
|
|
75
|
+
app_prod_versions: z.array(versionSchema),
|
|
76
|
+
});
|
package/lib/{api → auth}/auth.js
RENAMED
|
@@ -6,8 +6,9 @@ import { APP } from "../env.js";
|
|
|
6
6
|
import { findAvailablePort } from "../util/find-available-port.js";
|
|
7
7
|
import { loadAuthToken, saveAuthToken, deleteAuthToken } from "./keychain.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { hardExit } from "../util/hard-exit.js";
|
|
10
|
+
import { API } from "../api/api.js";
|
|
11
|
+
import { isComplete } from "@attio/fetchable";
|
|
11
12
|
const CLIENT_ID = "f881c6f1-82d7-48a5-a581-649596167845";
|
|
12
13
|
const tokenResponseSchema = z.object({
|
|
13
14
|
access_token: z.string(),
|
|
@@ -48,7 +49,8 @@ async function exchangeCodeForToken(code, codeVerifier, redirectUri) {
|
|
|
48
49
|
export async function auth() {
|
|
49
50
|
const existingTokenResult = await loadAuthToken();
|
|
50
51
|
if (existingTokenResult !== null) {
|
|
51
|
-
const
|
|
52
|
+
const userResult = await API.whoami();
|
|
53
|
+
const user = isComplete(userResult) ? userResult.value : null;
|
|
52
54
|
if (user !== null) {
|
|
53
55
|
return existingTokenResult;
|
|
54
56
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { findSurfaceExports } from "../../util/find-surface-exports/find-surface-exports.js";
|
|
3
|
+
import { findSurfaceExports, } from "../../util/find-surface-exports/find-surface-exports.js";
|
|
4
|
+
import { errored, complete } from "@attio/fetchable";
|
|
4
5
|
const ASSET_FILE_EXTENSIONS = ["png"];
|
|
5
6
|
export async function generateClientEntry({ srcDirAbsolute, assetsDirAbsolute, }) {
|
|
6
|
-
|
|
7
|
+
let surfaceExports;
|
|
8
|
+
try {
|
|
9
|
+
surfaceExports = await findSurfaceExports(srcDirAbsolute);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return errored({
|
|
13
|
+
code: "ERROR_FINDING_SURFACE_EXPORTS",
|
|
14
|
+
error,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
7
17
|
let assetFiles;
|
|
8
18
|
try {
|
|
9
19
|
assetFiles = await fs.readdir(assetsDirAbsolute, { recursive: true });
|
|
@@ -47,7 +57,7 @@ const assets = [];
|
|
|
47
57
|
|
|
48
58
|
registerAssets(assets);
|
|
49
59
|
`;
|
|
50
|
-
return `
|
|
60
|
+
return complete(`
|
|
51
61
|
${importSurfacesJS}
|
|
52
62
|
|
|
53
63
|
${importAssetsJS}
|
|
@@ -55,5 +65,5 @@ ${importAssetsJS}
|
|
|
55
65
|
${registerSurfacesJS}
|
|
56
66
|
|
|
57
67
|
${registerAssetsJS}
|
|
58
|
-
|
|
68
|
+
`);
|
|
59
69
|
}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { errored, complete, isErrored, combineAsync } from "@attio/fetchable";
|
|
4
|
+
async function findPaths(dir, pattern) {
|
|
5
|
+
try {
|
|
6
|
+
return complete(await glob(`**/*.${pattern}.(js|ts)`, { nodir: true, cwd: dir }));
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
return errored({
|
|
10
|
+
code: "ERROR_FINDING_SERVER_FUNCTION_MODULES",
|
|
11
|
+
dir,
|
|
12
|
+
pattern,
|
|
13
|
+
error,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
3
17
|
export async function generateServerEntry({ srcDirAbsolute, webhooksDirAbsolute, eventDirAbsolute, log, }) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
]).then((paths) => paths.flat()),
|
|
9
|
-
Promise.all([
|
|
10
|
-
glob("**/*.webhook.ts", { nodir: true, cwd: webhooksDirAbsolute }),
|
|
11
|
-
glob("**/*.webhook.js", { nodir: true, cwd: webhooksDirAbsolute }),
|
|
12
|
-
]).then((paths) => paths.flat()),
|
|
13
|
-
Promise.all([
|
|
14
|
-
glob("*.event.ts", { nodir: true, cwd: eventDirAbsolute }),
|
|
15
|
-
glob("*.event.js", { nodir: true, cwd: eventDirAbsolute }),
|
|
16
|
-
]).then((paths) => paths.flat()),
|
|
18
|
+
const pathsResult = await combineAsync([
|
|
19
|
+
findPaths(srcDirAbsolute, "server"),
|
|
20
|
+
findPaths(webhooksDirAbsolute, "webhook"),
|
|
21
|
+
findPaths(eventDirAbsolute, "event"),
|
|
17
22
|
]);
|
|
23
|
+
if (isErrored(pathsResult)) {
|
|
24
|
+
return pathsResult;
|
|
25
|
+
}
|
|
26
|
+
const [serverFunctionConcretePaths, webhookConcretePaths, eventConcretePaths] = pathsResult.value;
|
|
18
27
|
const serverFunctionModules = serverFunctionConcretePaths.map((path) => ({
|
|
19
28
|
path,
|
|
20
29
|
hash: path.replace(/\.(js|ts)$/, ""),
|
|
@@ -30,7 +39,7 @@ export async function generateServerEntry({ srcDirAbsolute, webhooksDirAbsolute,
|
|
|
30
39
|
for (const module of serverFunctionModules) {
|
|
31
40
|
log?.(`🔎 Found server module "${module.path}"`);
|
|
32
41
|
}
|
|
33
|
-
return `
|
|
42
|
+
return complete(`
|
|
34
43
|
|
|
35
44
|
const modules = new Map()
|
|
36
45
|
const webhookModules = new Map()
|
|
@@ -118,5 +127,5 @@ export async function generateServerEntry({ srcDirAbsolute, webhooksDirAbsolute,
|
|
|
118
127
|
|
|
119
128
|
main()
|
|
120
129
|
|
|
121
|
-
|
|
130
|
+
`);
|
|
122
131
|
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { prepareBuildContexts } from "../dev/prepare-build-contexts.js";
|
|
2
|
-
import {
|
|
3
|
-
import { errorsAndWarningsSchema } from "../../build.js";
|
|
1
|
+
import { prepareBuildContexts, } from "../dev/prepare-build-contexts.js";
|
|
2
|
+
import { combineAsync, complete, isErrored } from "@attio/fetchable";
|
|
4
3
|
export async function buildJavaScript() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
await Promise.all(buildContexts.map(async (context) => context.rebuild()));
|
|
9
|
-
return true;
|
|
4
|
+
const buildContextsResult = await prepareBuildContexts("write-to-disk");
|
|
5
|
+
if (isErrored(buildContextsResult)) {
|
|
6
|
+
return buildContextsResult;
|
|
10
7
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
8
|
+
const buildContexts = buildContextsResult.value;
|
|
9
|
+
const buildResult = await combineAsync(buildContexts.map(async (context) => context.rebuild()));
|
|
10
|
+
const disposeResult = await combineAsync(buildContexts.map(async (context) => context.dispose()));
|
|
11
|
+
if (isErrored(disposeResult)) {
|
|
12
|
+
return disposeResult;
|
|
16
13
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
await Promise.all(buildContexts.map(async (context) => context.dispose()));
|
|
20
|
-
}
|
|
14
|
+
if (isErrored(buildResult)) {
|
|
15
|
+
return buildResult;
|
|
21
16
|
}
|
|
17
|
+
return complete(true);
|
|
22
18
|
}
|
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { getDiagnostics, readConfig, typeScriptErrorSchema,
|
|
2
|
+
import { getDiagnostics, readConfig, typeScriptErrorSchema, } from "../../util/typescript.js";
|
|
3
|
+
import { complete, errored } from "@attio/fetchable";
|
|
3
4
|
export async function validateTypeScript() {
|
|
4
5
|
try {
|
|
5
6
|
const program = await readConfig(path.resolve("tsconfig.json"));
|
|
6
7
|
if (program === "Not a TypeScript project") {
|
|
7
|
-
return true;
|
|
8
|
+
return complete(true);
|
|
8
9
|
}
|
|
9
10
|
const errors = await getDiagnostics(program);
|
|
10
11
|
if (errors.length) {
|
|
11
|
-
errors
|
|
12
|
-
return false;
|
|
12
|
+
return errored({ code: "VALIDATE_TYPE_SCRIPT_ERROR", errors });
|
|
13
13
|
}
|
|
14
|
-
return true;
|
|
14
|
+
return complete(true);
|
|
15
15
|
}
|
|
16
16
|
catch (error) {
|
|
17
17
|
if (error instanceof Error) {
|
|
18
18
|
const tsError = typeScriptErrorSchema.parse({ text: error.message });
|
|
19
|
-
|
|
20
|
-
return false;
|
|
19
|
+
return errored({ code: "VALIDATE_TYPE_SCRIPT_ERROR", errors: [tsError] });
|
|
21
20
|
}
|
|
22
|
-
|
|
23
|
-
error !== null &&
|
|
24
|
-
"code" in error &&
|
|
25
|
-
(error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM")) {
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
return false;
|
|
21
|
+
return errored({ code: "FILE_SYSTEM_ERROR", error });
|
|
29
22
|
}
|
|
30
23
|
}
|
package/lib/commands/build.js
CHANGED
|
@@ -2,20 +2,37 @@ import { Command } from "commander";
|
|
|
2
2
|
import { validateTypeScript } from "./build/validate-typescript.js";
|
|
3
3
|
import { buildJavaScript } from "./build/build-javascript.js";
|
|
4
4
|
import { spinnerify } from "../util/spinner.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isErrored } from "@attio/fetchable";
|
|
6
|
+
import { printJsError, printTsError } from "../util/typescript.js";
|
|
7
|
+
import { printBuildContextError } from "./dev/prepare-build-contexts.js";
|
|
6
8
|
export const build = new Command("build")
|
|
7
9
|
.description("Build your Attio extension locally")
|
|
8
|
-
.action(async (
|
|
9
|
-
await spinnerify("Validating TypeScript...", "TypeScript validation passed",
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const tsResult = await spinnerify("Validating TypeScript...", "TypeScript validation passed", validateTypeScript);
|
|
12
|
+
if (isErrored(tsResult)) {
|
|
13
|
+
switch (tsResult.error.code) {
|
|
14
|
+
case "VALIDATE_TYPE_SCRIPT_ERROR":
|
|
15
|
+
tsResult.error.errors.forEach(printTsError);
|
|
16
|
+
process.stderr.write("TypeScript validation failed");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
case "FILE_SYSTEM_ERROR":
|
|
19
|
+
process.stderr.write(`TypeScript validation failed due to a file system error: ${tsResult.error.error}`);
|
|
20
|
+
process.exit(1);
|
|
13
21
|
}
|
|
14
|
-
}
|
|
15
|
-
await spinnerify("Building JavaScript...", "
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
22
|
+
}
|
|
23
|
+
const jsResult = await spinnerify("Building JavaScript...", "JavaScript build completed successfully", buildJavaScript);
|
|
24
|
+
if (isErrored(jsResult)) {
|
|
25
|
+
if (jsResult.error.code === "BUILD_JAVASCRIPT_ERROR") {
|
|
26
|
+
const { errors, warnings } = jsResult.error;
|
|
27
|
+
errors.forEach((error) => printJsError(error, "error"));
|
|
28
|
+
warnings.forEach((warning) => printJsError(warning, "warning"));
|
|
29
|
+
process.stderr.write("JavaScript build failed");
|
|
30
|
+
process.exit(1);
|
|
19
31
|
}
|
|
20
|
-
|
|
32
|
+
else {
|
|
33
|
+
printBuildContextError(jsResult.error);
|
|
34
|
+
process.stderr.write("Build context error");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
});
|
package/lib/commands/dev/boot.js
CHANGED
|
@@ -1,21 +1,48 @@
|
|
|
1
|
-
import { getAppSlugFromPackageJson } from "../../
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
|
|
5
|
-
import { getAppInfo } from "../../api/get-app-info.spinner.js";
|
|
1
|
+
import { getAppSlugFromPackageJson, printPackageJsonError, } from "../../spinners/get-app-slug-from-package-json.js";
|
|
2
|
+
import { determineWorkspace, printDetermineWorkspaceError, } from "../../spinners/determine-workspace.spinner.js";
|
|
3
|
+
import { getAppInfo } from "../../spinners/get-app-info.spinner.js";
|
|
6
4
|
import { loadEnv } from "../../util/load-env.js";
|
|
5
|
+
import { printFetcherError } from "../../api/fetcher.js";
|
|
6
|
+
import { isErrored } from "@attio/fetchable";
|
|
7
|
+
import { loadAttioCliVersion, printCliVersionError } from "../../util/load-attio-cli-version.js";
|
|
8
|
+
import { API } from "../../api/api.js";
|
|
7
9
|
export async function boot({ workspaceSlug }) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const appSlugResult = await getAppSlugFromPackageJson();
|
|
11
|
+
if (isErrored(appSlugResult)) {
|
|
12
|
+
printPackageJsonError(appSlugResult.error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const appSlug = appSlugResult.value;
|
|
16
|
+
const appInfoResult = await getAppInfo(appSlug);
|
|
17
|
+
if (isErrored(appInfoResult)) {
|
|
18
|
+
printFetcherError("Error loading app info", appInfoResult.error.fetcherError);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const appInfo = appInfoResult.value;
|
|
22
|
+
const workspaceResult = await determineWorkspace(workspaceSlug);
|
|
23
|
+
if (isErrored(workspaceResult)) {
|
|
24
|
+
printDetermineWorkspaceError(workspaceResult.error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const workspace = workspaceResult.value;
|
|
11
28
|
const environmentVariables = await loadEnv();
|
|
12
|
-
const
|
|
13
|
-
|
|
29
|
+
const cliVersionResult = loadAttioCliVersion();
|
|
30
|
+
if (isErrored(cliVersionResult)) {
|
|
31
|
+
printCliVersionError(cliVersionResult);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const cliVersion = cliVersionResult.value;
|
|
35
|
+
const devVersionResult = await API.createDevVersion({
|
|
14
36
|
appId: appInfo.app_id,
|
|
15
|
-
cliVersion
|
|
37
|
+
cliVersion,
|
|
16
38
|
targetWorkspaceId: workspace.workspace_id,
|
|
17
39
|
environmentVariables,
|
|
18
40
|
});
|
|
41
|
+
if (isErrored(devVersionResult)) {
|
|
42
|
+
printFetcherError("Error creating dev version", devVersionResult.error.fetcherError);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const devVersion = devVersionResult.value;
|
|
19
46
|
return {
|
|
20
47
|
appId: appInfo.app_id,
|
|
21
48
|
appSlug,
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { printJsError } from "../../util/typescript.js";
|
|
6
|
-
const notify = (errors) => {
|
|
7
|
-
const totalErrors = (errors.errors?.length || 0) + (errors.warnings?.length || 0);
|
|
8
|
-
notifier.notify({
|
|
9
|
-
title: `JavaScript ${totalErrors === 1 ? "Error" : "Errors"}`,
|
|
10
|
-
message: `There ${totalErrors === 1 ? "was one error" : `were ${totalErrors} errors`} in your JavaScript code`,
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
export function bundleJavaScript(onSuccess) {
|
|
2
|
+
import { prepareBuildContexts, } from "./prepare-build-contexts.js";
|
|
3
|
+
import { combineAsync, complete, isErrored } from "@attio/fetchable";
|
|
4
|
+
export function bundleJavaScript(onSuccess, onError) {
|
|
14
5
|
const watcher = chokidar.watch(["./src/**/*.{js,jsx,ts,tsx}"], {
|
|
15
6
|
ignored: ["**/node_modules/**", "**/dist/**", "**/*.graphql.d.ts", "**/*.gql.d.ts"],
|
|
16
7
|
cwd: process.cwd(),
|
|
@@ -22,36 +13,46 @@ export function bundleJavaScript(onSuccess) {
|
|
|
22
13
|
async function handleBuild() {
|
|
23
14
|
if (isBuilding || isDisposing) {
|
|
24
15
|
buildQueued = true;
|
|
25
|
-
return;
|
|
16
|
+
return complete(undefined);
|
|
26
17
|
}
|
|
27
18
|
isBuilding = true;
|
|
28
19
|
try {
|
|
29
20
|
if (!buildContexts) {
|
|
30
|
-
|
|
21
|
+
const buildContextsResult = await prepareBuildContexts("in-memory");
|
|
22
|
+
if (isErrored(buildContextsResult)) {
|
|
23
|
+
return buildContextsResult;
|
|
24
|
+
}
|
|
25
|
+
buildContexts = buildContextsResult.value;
|
|
26
|
+
}
|
|
27
|
+
const bundleResults = await combineAsync(buildContexts.map(async (context) => context.rebuild()));
|
|
28
|
+
if (isErrored(bundleResults)) {
|
|
29
|
+
return bundleResults;
|
|
31
30
|
}
|
|
32
|
-
const results =
|
|
31
|
+
const results = bundleResults.value;
|
|
33
32
|
const bundles = results.map((result) => result.outputFiles[0].text);
|
|
34
33
|
await onSuccess?.(bundles);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
const errors = errorsAndWarningsSchema.parse(error);
|
|
38
|
-
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
39
|
-
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
40
|
-
notify(errors);
|
|
41
|
-
}
|
|
42
|
-
finally {
|
|
43
34
|
isBuilding = false;
|
|
44
35
|
if (buildQueued && !isDisposing) {
|
|
45
36
|
buildQueued = false;
|
|
46
|
-
handleBuild();
|
|
37
|
+
return await handleBuild();
|
|
47
38
|
}
|
|
39
|
+
return complete(undefined);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
isBuilding = false;
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
|
-
watcher.on("ready", () => {
|
|
51
|
-
handleBuild();
|
|
52
|
-
|
|
45
|
+
watcher.on("ready", async () => {
|
|
46
|
+
const result = await handleBuild();
|
|
47
|
+
if (isErrored(result)) {
|
|
48
|
+
onError?.(result.error);
|
|
49
|
+
}
|
|
50
|
+
watcher.on("all", async (event) => {
|
|
53
51
|
if (event === "add" || event === "change" || event === "unlink") {
|
|
54
|
-
handleBuild();
|
|
52
|
+
const result = await handleBuild();
|
|
53
|
+
if (isErrored(result)) {
|
|
54
|
+
onError?.(result.error);
|
|
55
|
+
}
|
|
55
56
|
}
|
|
56
57
|
});
|
|
57
58
|
});
|
|
@@ -4,7 +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 "../../
|
|
7
|
+
import { hardExit } from "../../util/hard-exit.js";
|
|
8
8
|
export function graphqlCodeGen(onSuccess) {
|
|
9
9
|
const watcher = chokidar.watch(["**/*.graphql", "**/*.gql"], {
|
|
10
10
|
ignored: ["**/node_modules/**", "**/dist/**"],
|