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/boot.js
CHANGED
|
@@ -1,26 +1,49 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
4
|
import { loadEnv } from "../../util/load-env.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { getAppInfo } from "../../api/get-app-info.spinner.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";
|
|
8
9
|
export async function boot({ workspaceSlug }) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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;
|
|
13
28
|
const environmentVariables = await loadEnv();
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
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({
|
|
17
36
|
appId: appInfo.app_id,
|
|
37
|
+
cliVersion,
|
|
18
38
|
targetWorkspaceId: workspace.workspace_id,
|
|
19
39
|
environmentVariables,
|
|
20
|
-
cliVersion: packageJson.version,
|
|
21
40
|
});
|
|
41
|
+
if (isErrored(devVersionResult)) {
|
|
42
|
+
printFetcherError("Error creating dev version", devVersionResult.error.fetcherError);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const devVersion = devVersionResult.value;
|
|
22
46
|
return {
|
|
23
|
-
token,
|
|
24
47
|
appId: appInfo.app_id,
|
|
25
48
|
appSlug,
|
|
26
49
|
devVersionId: devVersion.app_dev_version_id,
|
|
@@ -1,49 +1,87 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { prepareBuildContexts } from "./prepare-build-contexts.js";
|
|
6
|
-
import { printJsError } from "../../util/typescript.js";
|
|
7
|
-
const notify = (errors) => {
|
|
8
|
-
const totalErrors = (errors.errors?.length || 0) + (errors.warnings?.length || 0);
|
|
9
|
-
notifier.notify({
|
|
10
|
-
title: `JavaScript ${totalErrors === 1 ? "Error" : "Errors"}`,
|
|
11
|
-
message: `There ${totalErrors === 1 ? "was one error" : `were ${totalErrors} errors`} in your JavaScript code`,
|
|
12
|
-
});
|
|
13
|
-
};
|
|
14
|
-
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) {
|
|
15
5
|
const watcher = chokidar.watch(["./src/**/*.{js,jsx,ts,tsx}"], {
|
|
16
6
|
ignored: ["**/node_modules/**", "**/dist/**", "**/*.graphql.d.ts", "**/*.gql.d.ts"],
|
|
17
7
|
cwd: process.cwd(),
|
|
18
8
|
});
|
|
19
9
|
let buildContexts;
|
|
10
|
+
let isBuilding = false;
|
|
11
|
+
let buildQueued = false;
|
|
12
|
+
let isDisposing = false;
|
|
20
13
|
async function handleBuild() {
|
|
14
|
+
if (isBuilding || isDisposing) {
|
|
15
|
+
buildQueued = true;
|
|
16
|
+
return complete(undefined);
|
|
17
|
+
}
|
|
18
|
+
isBuilding = true;
|
|
21
19
|
try {
|
|
22
20
|
if (!buildContexts) {
|
|
23
|
-
|
|
21
|
+
const buildContextsResult = await prepareBuildContexts("in-memory");
|
|
22
|
+
if (isErrored(buildContextsResult)) {
|
|
23
|
+
return buildContextsResult;
|
|
24
|
+
}
|
|
25
|
+
buildContexts = buildContextsResult.value;
|
|
24
26
|
}
|
|
25
|
-
const
|
|
27
|
+
const bundleResults = await combineAsync(buildContexts.map(async (context) => context.rebuild()));
|
|
28
|
+
if (isErrored(bundleResults)) {
|
|
29
|
+
return bundleResults;
|
|
30
|
+
}
|
|
31
|
+
const results = bundleResults.value;
|
|
26
32
|
const bundles = results.map((result) => result.outputFiles[0].text);
|
|
27
|
-
onSuccess?.(bundles);
|
|
33
|
+
await onSuccess?.(bundles);
|
|
34
|
+
isBuilding = false;
|
|
35
|
+
if (buildQueued && !isDisposing) {
|
|
36
|
+
buildQueued = false;
|
|
37
|
+
return await handleBuild();
|
|
38
|
+
}
|
|
39
|
+
return complete(undefined);
|
|
28
40
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
clearTerminal();
|
|
32
|
-
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
33
|
-
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
34
|
-
notify(errors);
|
|
41
|
+
finally {
|
|
42
|
+
isBuilding = false;
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
|
-
watcher.on("ready", () => {
|
|
38
|
-
handleBuild();
|
|
39
|
-
|
|
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) => {
|
|
40
51
|
if (event === "add" || event === "change" || event === "unlink") {
|
|
41
|
-
handleBuild();
|
|
52
|
+
const result = await handleBuild();
|
|
53
|
+
if (isErrored(result)) {
|
|
54
|
+
onError?.(result.error);
|
|
55
|
+
}
|
|
42
56
|
}
|
|
43
57
|
});
|
|
44
58
|
});
|
|
45
59
|
return async () => {
|
|
46
|
-
|
|
60
|
+
isDisposing = true;
|
|
61
|
+
if (isBuilding) {
|
|
62
|
+
await new Promise((resolve) => {
|
|
63
|
+
const checkBuild = setInterval(() => {
|
|
64
|
+
if (!isBuilding) {
|
|
65
|
+
clearInterval(checkBuild);
|
|
66
|
+
resolve(undefined);
|
|
67
|
+
}
|
|
68
|
+
}, 10);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
47
71
|
await watcher.close();
|
|
72
|
+
if (buildContexts) {
|
|
73
|
+
for (const context of buildContexts) {
|
|
74
|
+
try {
|
|
75
|
+
await Promise.race([
|
|
76
|
+
context.dispose(),
|
|
77
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Context disposal timeout")), 1_000)),
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
process.stderr.write(`Error disposing build context: ${e}\n`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
buildContexts = undefined;
|
|
85
|
+
}
|
|
48
86
|
};
|
|
49
87
|
}
|
|
@@ -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 "../../util/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
|
}
|
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { listenForKey } from "../../util/listen-for-key.js";
|
|
2
2
|
import open from "open";
|
|
3
|
-
import { fetchInstallation } from "../../api/fetch-installation.js";
|
|
4
3
|
import { APP } from "../../env.js";
|
|
4
|
+
import { API } from "../../api/api.js";
|
|
5
|
+
import { isErrored } from "@attio/fetchable";
|
|
5
6
|
function prompt() {
|
|
6
|
-
process.stdout.write(
|
|
7
|
+
process.stdout.write(`🚨 IMPORTANT: You will need to install your app in your workspace. Press "i" to open the app settings page, and then click "Install".\n\n`);
|
|
7
8
|
}
|
|
8
|
-
export function onboarding({
|
|
9
|
-
const haveInstallation = async () => (await fetchInstallation({ token, appId, workspaceId: workspace.workspace_id })) !== null;
|
|
9
|
+
export function onboarding({ appId, appSlug, workspace, }) {
|
|
10
10
|
const cleanup = listenForKey("i", () => {
|
|
11
11
|
open(`${APP}/${workspace.slug}/settings/apps/${appSlug}`);
|
|
12
12
|
});
|
|
13
13
|
const poll = async () => {
|
|
14
|
-
|
|
14
|
+
const installationResult = await API.fetchInstallation({
|
|
15
|
+
appId,
|
|
16
|
+
workspaceId: workspace.workspace_id,
|
|
17
|
+
});
|
|
18
|
+
if (isErrored(installationResult)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
let installation = installationResult.value;
|
|
15
22
|
if (!installation) {
|
|
16
23
|
prompt();
|
|
17
24
|
while (!installation) {
|
|
18
25
|
await new Promise((resolve) => setTimeout(resolve, 60_000));
|
|
19
|
-
|
|
26
|
+
const installationResult = await API.fetchInstallation({
|
|
27
|
+
appId,
|
|
28
|
+
workspaceId: workspace.workspace_id,
|
|
29
|
+
});
|
|
30
|
+
if (isErrored(installationResult)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
installation = installationResult.value;
|
|
20
34
|
if (!installation) {
|
|
21
35
|
prompt();
|
|
22
36
|
}
|
|
@@ -3,73 +3,204 @@ import tmp from "tmp-promise";
|
|
|
3
3
|
import fs from "fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { createClientBuildConfig } from "../../build/client/create-client-build-config.js";
|
|
6
|
-
import { generateClientEntry } from "../../build/client/generate-client-entry.js";
|
|
6
|
+
import { generateClientEntry, } from "../../build/client/generate-client-entry.js";
|
|
7
7
|
import { createServerBuildConfig } from "../../build/server/create-server-build-config.js";
|
|
8
8
|
import { generateServerEntry } from "../../build/server/generate-server-entry.js";
|
|
9
|
+
import { errored, isErrored, complete, combineAsync } from "@attio/fetchable";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { errorsAndWarningsSchema } from "../../build.js";
|
|
9
12
|
export async function prepareBuildContexts(mode) {
|
|
10
13
|
const srcDir = "src";
|
|
11
14
|
const assetsDir = path.join(srcDir, "assets");
|
|
12
15
|
const webhooksDir = path.join(srcDir, "webhooks");
|
|
13
16
|
const eventsDir = path.join(srcDir, "events");
|
|
14
|
-
return
|
|
15
|
-
tmp
|
|
17
|
+
return await combineAsync([
|
|
18
|
+
tmp
|
|
19
|
+
.file({ postfix: ".js" })
|
|
20
|
+
.then(async (tempFile) => {
|
|
16
21
|
let lastJS;
|
|
17
22
|
const updateTempFile = async () => {
|
|
18
|
-
const
|
|
23
|
+
const jsResult = await generateClientEntry({
|
|
19
24
|
srcDirAbsolute: path.resolve(srcDir),
|
|
20
25
|
assetsDirAbsolute: path.resolve(assetsDir),
|
|
21
26
|
});
|
|
27
|
+
if (isErrored(jsResult)) {
|
|
28
|
+
return jsResult;
|
|
29
|
+
}
|
|
30
|
+
const js = jsResult.value;
|
|
22
31
|
if (js === lastJS)
|
|
23
|
-
return;
|
|
32
|
+
return complete(undefined);
|
|
24
33
|
lastJS = js;
|
|
25
|
-
|
|
34
|
+
try {
|
|
35
|
+
await fs.writeFile(tempFile.path, js);
|
|
36
|
+
return complete(undefined);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return errored({
|
|
40
|
+
code: "FAILED_TO_CREATE_TEMP_FILE",
|
|
41
|
+
path: tempFile.path,
|
|
42
|
+
error,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
26
45
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
let esbuildContext;
|
|
47
|
+
const outfile = path.resolve("dist", "index.js");
|
|
48
|
+
try {
|
|
49
|
+
esbuildContext = await esbuild.context({
|
|
50
|
+
...createClientBuildConfig({
|
|
51
|
+
entryPoint: tempFile.path,
|
|
52
|
+
srcDir,
|
|
53
|
+
}),
|
|
54
|
+
write: mode === "write-to-disk",
|
|
55
|
+
outfile,
|
|
56
|
+
loader: { ".png": "dataurl", ".graphql": "text", ".gql": "text" },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return errored({
|
|
61
|
+
code: "FAILED_TO_CREATE_ESBUILD_CONTEXT",
|
|
62
|
+
outfile,
|
|
63
|
+
error,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return complete({
|
|
37
67
|
rebuild: async () => {
|
|
38
68
|
await updateTempFile();
|
|
39
|
-
|
|
69
|
+
try {
|
|
70
|
+
return complete(await esbuildContext.rebuild());
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const parseResult = errorsAndWarningsSchema.safeParse(error);
|
|
74
|
+
if (!parseResult.success) {
|
|
75
|
+
return errored({
|
|
76
|
+
code: "UNPARSABLE_BUILD_ERROR",
|
|
77
|
+
error,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return errored({
|
|
81
|
+
code: "BUILD_JAVASCRIPT_ERROR",
|
|
82
|
+
errors: parseResult.data.errors ?? [],
|
|
83
|
+
warnings: parseResult.data.warnings ?? [],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
40
86
|
},
|
|
41
87
|
dispose: async () => {
|
|
42
|
-
|
|
88
|
+
try {
|
|
89
|
+
await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return errored({
|
|
93
|
+
code: "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT",
|
|
94
|
+
error,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return complete(undefined);
|
|
43
98
|
},
|
|
44
|
-
};
|
|
99
|
+
});
|
|
45
100
|
}),
|
|
46
|
-
tmp
|
|
101
|
+
tmp
|
|
102
|
+
.file({ postfix: ".js" })
|
|
103
|
+
.then(async (tempFile) => {
|
|
47
104
|
let lastJS;
|
|
48
105
|
const updateTempFile = async () => {
|
|
49
|
-
const
|
|
106
|
+
const jsResult = await generateServerEntry({
|
|
50
107
|
srcDirAbsolute: path.resolve(srcDir),
|
|
51
108
|
webhooksDirAbsolute: path.resolve(webhooksDir),
|
|
52
109
|
eventDirAbsolute: path.resolve(eventsDir),
|
|
53
110
|
});
|
|
111
|
+
if (isErrored(jsResult)) {
|
|
112
|
+
return jsResult;
|
|
113
|
+
}
|
|
114
|
+
const js = jsResult.value;
|
|
54
115
|
if (js === lastJS)
|
|
55
|
-
return;
|
|
116
|
+
return complete(undefined);
|
|
56
117
|
lastJS = js;
|
|
57
|
-
|
|
118
|
+
try {
|
|
119
|
+
await fs.writeFile(tempFile.path, js);
|
|
120
|
+
return complete(undefined);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return errored({
|
|
124
|
+
code: "FAILED_TO_CREATE_TEMP_FILE",
|
|
125
|
+
path: tempFile.path,
|
|
126
|
+
error,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
58
129
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
130
|
+
let esbuildContext;
|
|
131
|
+
const outfile = path.resolve("dist", "server.js");
|
|
132
|
+
try {
|
|
133
|
+
esbuildContext = await esbuild.context({
|
|
134
|
+
...createServerBuildConfig(tempFile.path),
|
|
135
|
+
write: mode === "write-to-disk",
|
|
136
|
+
outfile,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return errored({
|
|
141
|
+
code: "FAILED_TO_CREATE_ESBUILD_CONTEXT",
|
|
142
|
+
outfile,
|
|
143
|
+
error,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return complete({
|
|
65
147
|
rebuild: async () => {
|
|
66
148
|
await updateTempFile();
|
|
67
|
-
|
|
149
|
+
try {
|
|
150
|
+
return complete(await esbuildContext.rebuild());
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const parseResult = errorsAndWarningsSchema.safeParse(error);
|
|
154
|
+
if (!parseResult.success) {
|
|
155
|
+
return errored({
|
|
156
|
+
code: "UNPARSABLE_BUILD_ERROR",
|
|
157
|
+
error,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return errored({
|
|
161
|
+
code: "BUILD_JAVASCRIPT_ERROR",
|
|
162
|
+
errors: parseResult.data.errors ?? [],
|
|
163
|
+
warnings: parseResult.data.warnings ?? [],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
68
166
|
},
|
|
69
167
|
dispose: async () => {
|
|
70
|
-
|
|
168
|
+
try {
|
|
169
|
+
await Promise.all([esbuildContext.dispose(), tempFile.cleanup()]);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
return errored({
|
|
173
|
+
code: "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT",
|
|
174
|
+
error,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return complete(undefined);
|
|
71
178
|
},
|
|
72
|
-
};
|
|
179
|
+
});
|
|
73
180
|
}),
|
|
74
181
|
]);
|
|
75
182
|
}
|
|
183
|
+
export function printBuildContextError(error) {
|
|
184
|
+
switch (error.code) {
|
|
185
|
+
case "FAILED_TO_CREATE_TEMP_FILE":
|
|
186
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to create temp file: ${error.path}\n`);
|
|
187
|
+
break;
|
|
188
|
+
case "FAILED_TO_GENERATE_CLIENT_ENTRY":
|
|
189
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to generate client entry\n`);
|
|
190
|
+
break;
|
|
191
|
+
case "FAILED_TO_DISPOSE_OF_BUILD_CONTEXT":
|
|
192
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to dispose of build context: ${error.error}\n`);
|
|
193
|
+
break;
|
|
194
|
+
case "FAILED_TO_CREATE_ESBUILD_CONTEXT":
|
|
195
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to create esbuild context (${error.outfile}): ${error.error}\n`);
|
|
196
|
+
break;
|
|
197
|
+
case "ERROR_FINDING_SURFACE_EXPORTS":
|
|
198
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to find surface exports: ${error.error}\n`);
|
|
199
|
+
break;
|
|
200
|
+
case "UNPARSABLE_BUILD_ERROR":
|
|
201
|
+
process.stderr.write(`${chalk.red("✖ ")}Failed to parse build error: ${error.error}\n`);
|
|
202
|
+
break;
|
|
203
|
+
default:
|
|
204
|
+
return error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -5,7 +5,7 @@ import { buildSchema } from "graphql";
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import path, { dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { findAvailablePort } from "
|
|
8
|
+
import { findAvailablePort } from "../../util/find-available-port.js";
|
|
9
9
|
export function startGraphqlServer(sendBack) {
|
|
10
10
|
let server = null;
|
|
11
11
|
const startServer = async () => {
|
|
@@ -21,7 +21,7 @@ export function startGraphqlServer(sendBack) {
|
|
|
21
21
|
};
|
|
22
22
|
app.use("/graphql", graphqlServer({ schema, rootResolver, graphiql: true }));
|
|
23
23
|
server = serve({ fetch: app.fetch, port });
|
|
24
|
-
sendBack({
|
|
24
|
+
sendBack({ code: "GraphQL Server Started", port });
|
|
25
25
|
};
|
|
26
26
|
startServer();
|
|
27
27
|
return () => {
|
|
@@ -1,59 +1,55 @@
|
|
|
1
1
|
import notifier from "node-notifier";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import { spinnerify } from "../../util/spinner.js";
|
|
3
|
+
import { uploadBundle } from "../../util/upload-bundle.js";
|
|
4
|
+
import { API } from "../../api/api.js";
|
|
5
|
+
import { isErrored, combineAsync, complete } from "@attio/fetchable";
|
|
6
|
+
import { printFetcherError } from "../../api/fetcher.js";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
export async function upload({ contents, devVersionId, appId, }) {
|
|
9
|
+
return await spinnerify("Uploading...", () => `Upload complete at ${new Date().toLocaleTimeString()}`, async () => {
|
|
10
|
+
const startUploadResult = await API.startUpload({
|
|
10
11
|
appId,
|
|
11
12
|
devVersionId,
|
|
12
13
|
});
|
|
14
|
+
if (isErrored(startUploadResult)) {
|
|
15
|
+
return startUploadResult;
|
|
16
|
+
}
|
|
17
|
+
const { client_bundle_upload_url, server_bundle_upload_url, app_dev_version_bundle_id: bundleId, } = startUploadResult.value;
|
|
13
18
|
const [clientBundle, serverBundle] = contents;
|
|
14
|
-
await
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
});
|
|
34
|
-
await completeBundleUpload({
|
|
35
|
-
token,
|
|
19
|
+
const uploadResults = await combineAsync([
|
|
20
|
+
uploadBundle(clientBundle, client_bundle_upload_url),
|
|
21
|
+
uploadBundle(serverBundle, server_bundle_upload_url),
|
|
22
|
+
]);
|
|
23
|
+
if (isErrored(uploadResults)) {
|
|
24
|
+
return uploadResults;
|
|
25
|
+
}
|
|
26
|
+
const completeBundleUploadResult = await API.completeBundleUpload({
|
|
36
27
|
appId,
|
|
37
28
|
devVersionId,
|
|
38
29
|
bundleId,
|
|
39
30
|
});
|
|
40
|
-
|
|
31
|
+
if (isErrored(completeBundleUploadResult)) {
|
|
32
|
+
return completeBundleUploadResult;
|
|
33
|
+
}
|
|
41
34
|
notifier.notify({
|
|
42
35
|
title: "Upload Complete",
|
|
43
36
|
message: "New bundle uploaded to Attio",
|
|
44
37
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
38
|
+
return complete(undefined);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function printUploadError(error) {
|
|
42
|
+
switch (error.code) {
|
|
43
|
+
case "BUNDLE_UPLOAD_ERROR":
|
|
44
|
+
process.stderr.write(chalk.red(`Error uploading bundle: ${error.error}\n`));
|
|
45
|
+
break;
|
|
46
|
+
case "START_UPLOAD_ERROR":
|
|
47
|
+
printFetcherError("Error starting upload", error.fetcherError);
|
|
48
|
+
break;
|
|
49
|
+
case "COMPLETE_BUNDLE_UPLOAD_ERROR":
|
|
50
|
+
printFetcherError("Error completing bundle upload", error.fetcherError);
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
return error;
|
|
58
54
|
}
|
|
59
55
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
|
-
import notifier from "node-notifier";
|
|
3
2
|
import { getDiagnostics, readConfig, typeScriptErrorSchema, } from "../../util/typescript.js";
|
|
4
|
-
import { printTsError } from "../../util/typescript.js";
|
|
5
|
-
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
6
3
|
import path from "path";
|
|
7
|
-
|
|
8
|
-
notifier.notify({
|
|
9
|
-
title: `TypeScript Error${errors.length === 1 ? "" : "s"}`,
|
|
10
|
-
message: `There ${errors.length === 1 ? "was one error" : `were ${errors.length} errors`} in your TypeScript code`,
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
export function validateTypeScript(onSuccess) {
|
|
4
|
+
export function validateTypeScript(onSuccess, onError) {
|
|
14
5
|
let isShuttingDown = false;
|
|
15
6
|
const watcher = chokidar.watch(["src/**/*.ts", "src/**/*.tsx"], {
|
|
16
7
|
ignored: [
|
|
@@ -37,9 +28,7 @@ export function validateTypeScript(onSuccess) {
|
|
|
37
28
|
}
|
|
38
29
|
const errors = await getDiagnostics(program);
|
|
39
30
|
if (errors.length) {
|
|
40
|
-
|
|
41
|
-
errors.forEach(printTsError);
|
|
42
|
-
notify(errors);
|
|
31
|
+
onError?.(errors);
|
|
43
32
|
}
|
|
44
33
|
else {
|
|
45
34
|
onSuccess?.();
|
|
@@ -54,16 +43,13 @@ export function validateTypeScript(onSuccess) {
|
|
|
54
43
|
}
|
|
55
44
|
if (error instanceof Error) {
|
|
56
45
|
const tsError = typeScriptErrorSchema.parse({ text: error.message });
|
|
57
|
-
|
|
58
|
-
printTsError(tsError);
|
|
59
|
-
notify([tsError]);
|
|
46
|
+
onError?.([tsError]);
|
|
60
47
|
}
|
|
61
48
|
}
|
|
62
49
|
}
|
|
63
50
|
let watcherReady = false;
|
|
64
51
|
watcher.on("ready", () => {
|
|
65
52
|
watcherReady = true;
|
|
66
|
-
handleValidation();
|
|
67
53
|
watcher.on("all", (event, path) => {
|
|
68
54
|
if (event === "add" || event === "change" || event === "unlink") {
|
|
69
55
|
handleValidation();
|