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.
Files changed (66) hide show
  1. package/lib/api/api.js +98 -0
  2. package/lib/api/fetcher.js +65 -0
  3. package/lib/api/schemas.js +76 -0
  4. package/lib/auth/auth.js +121 -0
  5. package/lib/auth/ensure-authed.js +14 -0
  6. package/lib/auth/keychain.js +52 -0
  7. package/lib/build/client/generate-client-entry.js +14 -4
  8. package/lib/build/server/generate-server-entry.js +24 -15
  9. package/lib/commands/build/build-javascript.js +13 -19
  10. package/lib/commands/build/validate-typescript.js +7 -14
  11. package/lib/commands/build.js +27 -25
  12. package/lib/commands/dev/boot.js +38 -15
  13. package/lib/commands/dev/bundle-javascript.js +65 -27
  14. package/lib/commands/dev/graphql-code-gen.js +3 -2
  15. package/lib/commands/dev/onboarding.js +20 -6
  16. package/lib/commands/dev/prepare-build-contexts.js +163 -32
  17. package/lib/{api → commands/dev}/start-graphql-server.js +2 -2
  18. package/lib/commands/dev/upload.js +40 -44
  19. package/lib/commands/dev/validate-typescript.js +3 -17
  20. package/lib/commands/dev.js +95 -13
  21. package/lib/commands/init/create-project.js +53 -35
  22. package/lib/commands/init.js +30 -9
  23. package/lib/commands/login.js +7 -11
  24. package/lib/commands/logout.js +5 -11
  25. package/lib/commands/version/create/bundle-javascript.js +28 -0
  26. package/lib/commands/version/create.js +89 -64
  27. package/lib/commands/version/list.js +54 -26
  28. package/lib/commands/whoami.js +15 -11
  29. package/lib/spinners/determine-workspace.spinner.js +58 -0
  30. package/lib/spinners/get-app-info.spinner.js +7 -0
  31. package/lib/spinners/get-app-slug-from-package-json.js +46 -0
  32. package/lib/spinners/get-versions.spinner.js +5 -0
  33. package/lib/util/copy-with-replace.js +40 -17
  34. package/lib/util/create-directory.js +21 -13
  35. package/lib/util/find-available-port.js +2 -1
  36. package/lib/util/hard-exit.js +6 -0
  37. package/lib/util/load-attio-cli-version.js +86 -0
  38. package/lib/util/spinner.js +16 -1
  39. package/lib/util/upload-bundle.js +17 -0
  40. package/package.json +2 -1
  41. package/lib/api/auth.js +0 -117
  42. package/lib/api/complete-bundle-upload.js +0 -21
  43. package/lib/api/complete-prod-bundle-upload.js +0 -21
  44. package/lib/api/create-dev-version.js +0 -22
  45. package/lib/api/create-version.js +0 -24
  46. package/lib/api/determine-workspace.spinner.js +0 -39
  47. package/lib/api/ensure-authed.js +0 -31
  48. package/lib/api/fetch-app-info.js +0 -26
  49. package/lib/api/fetch-connections.js +0 -20
  50. package/lib/api/fetch-installation.js +0 -20
  51. package/lib/api/fetch-versions.js +0 -25
  52. package/lib/api/fetch-workspaces.js +0 -33
  53. package/lib/api/get-app-info.spinner.js +0 -18
  54. package/lib/api/get-app-slug-from-package-json.js +0 -26
  55. package/lib/api/get-versions.spinner.js +0 -21
  56. package/lib/api/handle-error.js +0 -18
  57. package/lib/api/keychain.js +0 -30
  58. package/lib/api/make-headers.js +0 -7
  59. package/lib/api/remove-connection-definition.js +0 -10
  60. package/lib/api/start-upload.js +0 -18
  61. package/lib/api/whoami.js +0 -21
  62. package/lib/commands/dev/build-javascript.js +0 -49
  63. package/lib/commands/init/boot.js +0 -14
  64. package/lib/commands/version/create/boot.js +0 -9
  65. package/lib/util/clear-terminal.js +0 -4
  66. package/lib/util/load-attio-cli-package-json.js +0 -26
@@ -1,20 +1,24 @@
1
1
  import { Command } from "commander";
2
- import { auth as authApi } from "../api/auth.js";
3
- import { whoami as whoamiApi } from "../api/whoami.js";
4
- export const whoami = new Command("whoami")
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";
6
+ export const whoami = new Command()
7
+ .name("whoami")
5
8
  .description("Identify the current user")
6
9
  .action(async () => {
7
- const token = await authApi();
8
- if (!token) {
10
+ const tokenResult = await loadAuthToken();
11
+ if (!tokenResult) {
9
12
  process.stdout.write("🔒 Not logged in.\n");
10
13
  process.exit(0);
11
14
  }
12
- try {
13
- const user = await whoamiApi({ token });
14
- process.stdout.write(`👤 ${user.name.full} (${user.email_address})\n`);
15
- }
16
- catch (error) {
17
- process.stderr.write(`❌ User lookup failed\n\n${error}\n`);
15
+ const result = await API.whoami();
16
+ if (isErrored(result)) {
17
+ const { fetcherError } = result.error;
18
+ printFetcherError("Error fetching user", fetcherError);
18
19
  process.exit(1);
19
20
  }
21
+ const { name, email_address } = result.value;
22
+ process.stdout.write(`👤 ${name.full} (${email_address})\n`);
23
+ process.exit(0);
20
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
+ }
@@ -0,0 +1,7 @@
1
+ import { API } from "../api/api.js";
2
+ import { spinnerify } from "../util/spinner.js";
3
+ export async function getAppInfo(appSlug) {
4
+ return await spinnerify("Loading app information...", (app) => `App found: ${app.title}`, async () => {
5
+ return await API.fetchAppInfo(appSlug);
6
+ });
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
+ }
@@ -0,0 +1,5 @@
1
+ import { spinnerify } from "../util/spinner.js";
2
+ import { API } from "../api/api.js";
3
+ export async function getVersions(appInfo) {
4
+ return await spinnerify("Loading versions...", "Versions loaded", async () => await API.fetchVersions(appInfo.app_id));
5
+ }
@@ -1,33 +1,56 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
- export const copyWithTransform = async (srcDir, destDir, transform) => {
3
+ import { combineAsync, complete, errored, isErrored } from "@attio/fetchable";
4
+ export async function copyWithTransform(srcDir, destDir, transform) {
4
5
  try {
5
6
  await fs.mkdir(destDir, { recursive: true });
6
7
  }
7
8
  catch {
8
- throw new Error(`Failed to create "${destDir}".`);
9
+ return errored({ code: "FAILED_TO_CREATE_DIRECTORY", directory: destDir });
9
10
  }
10
- const entries = await fs.readdir(srcDir, { withFileTypes: true });
11
- await Promise.all(entries.map(async (entry) => {
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) => {
12
19
  const srcPath = path.join(srcDir, entry.name);
13
20
  const destPath = path.join(destDir, entry.name);
14
- try {
15
- if (entry.isDirectory()) {
16
- await copyWithTransform(srcPath, destPath, transform);
17
- }
18
- else if (entry.isFile()) {
19
- if (/\.(jpg|jpeg|png|gif|svg|webp|ico)$/i.test(entry.name)) {
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 {
20
27
  await fs.copyFile(srcPath, destPath);
21
28
  }
22
- else {
23
- let content = await fs.readFile(srcPath, "utf8");
24
- content = transform(content);
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 {
25
43
  await fs.writeFile(destPath, content, "utf8");
26
44
  }
45
+ catch {
46
+ return errored({ code: "FAILED_TO_WRITE_FILE", path: destPath });
47
+ }
27
48
  }
28
49
  }
29
- catch {
30
- throw new Error(`Failed to copy "${srcPath}" to "${destPath}"`);
31
- }
50
+ return complete(undefined);
32
51
  }));
33
- };
52
+ if (isErrored(results)) {
53
+ return results;
54
+ }
55
+ return complete(undefined);
56
+ }
@@ -1,19 +1,27 @@
1
- import { accessSync, constants, existsSync, mkdirSync } from "fs";
1
+ import { constants, promises as fs } from "fs";
2
2
  import { join } from "path";
3
- export const createDirectory = (name) => {
3
+ import { complete, errored } from "@attio/fetchable";
4
+ export const createDirectory = async (name) => {
4
5
  const currentDir = process.cwd();
5
6
  const newPath = join(currentDir, name);
6
- if (existsSync(newPath)) {
7
- throw new Error(`The directory '${name}' already exists.`);
8
- }
9
- else {
10
- try {
11
- accessSync(currentDir, constants.W_OK);
12
- mkdirSync(newPath);
13
- return newPath;
14
- }
15
- catch {
16
- throw new Error(`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
+ });
17
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
+ });
18
26
  }
19
27
  };
@@ -1,4 +1,5 @@
1
1
  import net from "net";
2
+ import { hardExit } from "./hard-exit.js";
2
3
  async function isPortAvailable(port) {
3
4
  return new Promise((resolve) => {
4
5
  const portTester = net.createConnection(port, "127.0.0.1");
@@ -32,5 +33,5 @@ export async function findAvailablePort(startPort, maxAttempts = 100) {
32
33
  return port;
33
34
  }
34
35
  }
35
- throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
36
+ hardExit(`Could not find an available port after ${maxAttempts} attempts`);
36
37
  }
@@ -0,0 +1,6 @@
1
+ import chalk from "chalk";
2
+ import process from "process";
3
+ export function hardExit(message) {
4
+ process.stderr.write(chalk.red("✖ " + message) + "\n");
5
+ process.exit(1);
6
+ }
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import chalk from "chalk";
2
+ import { isComplete } from "@attio/fetchable";
2
3
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
3
- export class Spinner {
4
+ class Spinner {
4
5
  frameIndex = 0;
5
6
  stopped = false;
6
7
  interval = null;
@@ -44,3 +45,17 @@ export class Spinner {
44
45
  return this.stop(`${chalk.yellow("⚠")} ${message}`);
45
46
  }
46
47
  }
48
+ export async function spinnerify(busyMessage, successMessage, fn) {
49
+ const spinner = new Spinner();
50
+ spinner.start(busyMessage);
51
+ try {
52
+ const result = await fn();
53
+ if (isComplete(result)) {
54
+ spinner.success(typeof successMessage === "string" ? successMessage : successMessage(result.value));
55
+ }
56
+ return result;
57
+ }
58
+ finally {
59
+ spinner.stop();
60
+ }
61
+ }
@@ -0,0 +1,17 @@
1
+ import { complete, errored } from "@attio/fetchable";
2
+ export async function uploadBundle(bundle, uploadUrl) {
3
+ try {
4
+ await fetch(uploadUrl, {
5
+ method: "PUT",
6
+ body: bundle,
7
+ headers: {
8
+ "Content-Type": "text/javascript",
9
+ "Content-Length": String(Buffer.from(bundle).length),
10
+ },
11
+ });
12
+ return complete(undefined);
13
+ }
14
+ catch (error) {
15
+ return errored({ code: "BUNDLE_UPLOAD_ERROR", error, uploadUrl });
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20250402.1",
3
+ "version": "0.0.1-experimental.20250407",
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",
package/lib/api/auth.js DELETED
@@ -1,117 +0,0 @@
1
- import { Hono } from "hono";
2
- import { serve } from "@hono/node-server";
3
- import { randomBytes, createHash } from "crypto";
4
- import open from "open";
5
- import { APP } from "../env.js";
6
- import { findAvailablePort } from "../util/find-available-port.js";
7
- import { loadAuthToken, saveAuthToken } from "./keychain.js";
8
- import { z } from "zod";
9
- const CLIENT_ID = "f881c6f1-82d7-48a5-a581-649596167845";
10
- const tokenResponseSchema = z.object({
11
- access_token: z.string(),
12
- token_type: z.string(),
13
- expires_in: z.number(),
14
- });
15
- async function exchangeCodeForToken(code, codeVerifier, redirectUri) {
16
- const tokenUrl = `${APP}/oidc/token`;
17
- const params = new URLSearchParams();
18
- params.append("grant_type", "authorization_code");
19
- params.append("code", code);
20
- params.append("client_id", CLIENT_ID);
21
- params.append("redirect_uri", redirectUri);
22
- params.append("code_verifier", codeVerifier);
23
- const response = await fetch(tokenUrl, {
24
- method: "POST",
25
- headers: {
26
- "Content-Type": "application/x-www-form-urlencoded",
27
- },
28
- body: params.toString(),
29
- });
30
- if (!response.ok) {
31
- const errorText = await response.text();
32
- throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
33
- }
34
- return tokenResponseSchema.parse(await response.json());
35
- }
36
- export async function auth() {
37
- const existingToken = await loadAuthToken();
38
- if (existingToken) {
39
- return existingToken;
40
- }
41
- const verifier = randomBytes(32);
42
- const verifierString = verifier.toString("base64url");
43
- const hash = createHash("sha256");
44
- hash.update(verifier);
45
- const challenge = hash.digest();
46
- const challengeString = challenge.toString("base64url");
47
- const state = randomBytes(32).toString("base64url");
48
- const port = await findAvailablePort(3000, 65000);
49
- const redirectUri = `http://localhost:${port}`;
50
- let resolveToken;
51
- let rejectToken;
52
- const tokenPromise = new Promise((resolve, reject) => {
53
- resolveToken = resolve;
54
- rejectToken = reject;
55
- });
56
- const app = new Hono();
57
- let serverRef;
58
- app.get("/", async (c) => {
59
- try {
60
- const query = c.req.query();
61
- const receivedCode = query.authorization_code;
62
- const receivedState = query.state;
63
- if (receivedState !== state) {
64
- throw new Error("State mismatch - possible CSRF attack");
65
- }
66
- if (!receivedCode) {
67
- throw new Error("No authorization code received");
68
- }
69
- const tokenResponse = await exchangeCodeForToken(receivedCode, verifierString, redirectUri);
70
- setTimeout(() => {
71
- serverRef.close();
72
- }, 1000);
73
- resolveToken(tokenResponse.access_token);
74
- return c.html(`
75
- <html>
76
- <body>
77
- <script>window.location.href = '${APP}/authorized';</script>
78
- </body>
79
- </html>
80
- `);
81
- }
82
- catch (error) {
83
- rejectToken(error instanceof Error ? error : new Error(String(error)));
84
- return c.html(`
85
- <html>
86
- <body>
87
- <h1>Authentication Failed</h1>
88
- <p>Error: ${error instanceof Error ? error.message : String(error)}</p>
89
- <p>Please close this window and try again.</p>
90
- </body>
91
- </html>
92
- `);
93
- }
94
- });
95
- serverRef = serve({
96
- fetch: app.fetch,
97
- port,
98
- });
99
- const authUrl = new URL(`${APP}/oidc/authorize`);
100
- authUrl.searchParams.append("scope", "openid");
101
- authUrl.searchParams.append("response_type", "code");
102
- authUrl.searchParams.append("client_id", CLIENT_ID);
103
- authUrl.searchParams.append("redirect_uri", redirectUri);
104
- authUrl.searchParams.append("state", state);
105
- authUrl.searchParams.append("code_challenge", challengeString);
106
- authUrl.searchParams.append("code_challenge_method", "S256");
107
- await open(authUrl.toString());
108
- try {
109
- const accessToken = await tokenPromise;
110
- await saveAuthToken(accessToken);
111
- return accessToken;
112
- }
113
- catch (error) {
114
- serverRef.close();
115
- throw error;
116
- }
117
- }
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
- import { API } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const completeBundleUploadSchema = z.object({
6
- success: z.literal(true),
7
- });
8
- export async function completeBundleUpload({ token, appId, devVersionId, bundleId, }) {
9
- const response = await fetch(`${API}/apps/${appId}/dev-versions/${devVersionId}/bundles/${bundleId}/complete`, {
10
- method: "POST",
11
- headers: makeHeaders(token),
12
- });
13
- await handleError(response);
14
- const json = await response.json();
15
- try {
16
- return completeBundleUploadSchema.parse(json).success;
17
- }
18
- catch {
19
- throw new Error(JSON.stringify(json));
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
- import { API } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const completeBundleUploadSchema = z.object({
6
- success: z.literal(true),
7
- });
8
- export async function completeProdBundleUpload({ token, appId, bundleId, major, minor, }) {
9
- const response = await fetch(`${API}/apps/${appId}/prod-versions/${major}/${minor}/bundles/${bundleId}/complete`, {
10
- method: "POST",
11
- headers: makeHeaders(token),
12
- });
13
- await handleError(response);
14
- const json = await response.json();
15
- try {
16
- return completeBundleUploadSchema.parse(json).success;
17
- }
18
- catch {
19
- throw new Error(JSON.stringify(json));
20
- }
21
- }
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
- import { API } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const createDevVersionSchema = z.object({
6
- app_id: z.string(),
7
- app_dev_version_id: z.string(),
8
- });
9
- export async function createDevVersion({ token, appId, targetWorkspaceId, environmentVariables, cliVersion, }) {
10
- const response = await fetch(`${API}/apps/${appId}/dev-versions`, {
11
- method: "POST",
12
- headers: makeHeaders(token),
13
- body: JSON.stringify({
14
- major: 1,
15
- target_workspace_id: targetWorkspaceId,
16
- environment_variables: environmentVariables,
17
- cli_version: cliVersion,
18
- }),
19
- });
20
- await handleError(response);
21
- return createDevVersionSchema.parse(await response.json());
22
- }
@@ -1,24 +0,0 @@
1
- import { z } from "zod";
2
- import { API } from "../env.js";
3
- import { handleError } from "./handle-error.js";
4
- import { makeHeaders } from "./make-headers.js";
5
- const createVersionSchema = z.object({
6
- app_id: z.string(),
7
- major: z.number(),
8
- minor: z.number(),
9
- app_prod_version_bundle_id: z.string(),
10
- client_bundle_upload_url: z.string(),
11
- server_bundle_upload_url: z.string(),
12
- });
13
- export async function createVersion({ token, appId, major, cliVersion, }) {
14
- const response = await fetch(`${API}/apps/${appId}/prod-versions`, {
15
- method: "POST",
16
- headers: makeHeaders(token),
17
- body: JSON.stringify({
18
- major,
19
- cli_version: cliVersion,
20
- }),
21
- });
22
- await handleError(response);
23
- return createVersionSchema.parse(await response.json());
24
- }