attio 0.0.1-experimental.20250328 → 0.0.1-experimental.20250401
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/auth.js +1 -3
- package/lib/api/determine-workspace.spinner.js +39 -0
- package/lib/api/ensure-authed.js +8 -1
- package/lib/api/{get-app-info.js → fetch-app-info.js} +1 -1
- package/lib/api/fetch-workspaces.js +11 -0
- package/lib/api/get-app-info.spinner.js +18 -0
- package/lib/api/get-app-slug-from-package-json.js +26 -0
- package/lib/api/get-versions.spinner.js +21 -0
- package/lib/commands/build/build-javascript.js +24 -0
- package/lib/commands/build/validate-typescript.js +30 -0
- package/lib/commands/build.js +32 -16
- package/lib/commands/dev/boot.js +4 -4
- package/lib/commands/dev/build-javascript.js +2 -2
- package/lib/commands/dev/bundle-javascript.js +1 -1
- package/lib/commands/dev/prepare-build-contexts.js +3 -3
- package/lib/commands/dev/upload.js +2 -3
- package/lib/commands/dev.js +9 -6
- package/lib/commands/init/ask-language.js +11 -0
- package/lib/commands/init/boot.js +14 -0
- package/lib/commands/init/create-project.js +50 -0
- package/lib/commands/init.js +21 -16
- package/lib/commands/login.js +2 -0
- package/lib/commands/logout.js +2 -0
- package/lib/commands/version/create/boot.js +9 -0
- package/lib/commands/version/create.js +71 -15
- package/lib/commands/version/list.js +4 -2
- package/lib/commands/whoami.js +2 -1
- package/lib/util/print-message.js +3 -2
- package/lib/util/spinner.js +46 -0
- package/package.json +1 -5
- package/lib/machines/actions.js +0 -8
- package/lib/machines/actors.js +0 -123
- package/lib/machines/build-machine.js +0 -192
- package/lib/machines/build-orchestrator.js +0 -1
- package/lib/machines/code-gen-machine.js +0 -97
- package/lib/machines/create-version-machine.js +0 -308
- package/lib/machines/env-machine.js +0 -82
- package/lib/machines/init-machine.js +0 -192
- package/lib/machines/js-machine.js +0 -231
- package/lib/machines/ts-machine.js +0 -105
package/lib/api/auth.js
CHANGED
|
@@ -74,9 +74,7 @@ export async function auth() {
|
|
|
74
74
|
return c.html(`
|
|
75
75
|
<html>
|
|
76
76
|
<body>
|
|
77
|
-
<
|
|
78
|
-
<p>You can close this window and return to the CLI.</p>
|
|
79
|
-
<script>window.close();</script>
|
|
77
|
+
<script>window.location.href = '${APP}/authorized';</script>
|
|
80
78
|
</body>
|
|
81
79
|
</html>
|
|
82
80
|
`);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import { fetchWorkspaces } from "./fetch-workspaces.js";
|
|
3
|
+
import { APP } from "../env.js";
|
|
4
|
+
import { Spinner } from "../util/spinner.js";
|
|
5
|
+
export async function determineWorkspace({ token, workspaceSlug, }) {
|
|
6
|
+
const spinner = new Spinner();
|
|
7
|
+
spinner.start("Loading workspaces...");
|
|
8
|
+
try {
|
|
9
|
+
const workspaces = await fetchWorkspaces({ token });
|
|
10
|
+
spinner.success("Workspaces loaded");
|
|
11
|
+
const workspace = workspaces.find((workspace) => workspace.slug === workspaceSlug);
|
|
12
|
+
if (workspace) {
|
|
13
|
+
spinner.success(`Using workspace: ${workspace.name}`);
|
|
14
|
+
return workspace;
|
|
15
|
+
}
|
|
16
|
+
if (workspaces.length === 0) {
|
|
17
|
+
throw new Error(`You are not the admin of any workspaces. Either request permission from an existing workspace or create your own.
|
|
18
|
+
|
|
19
|
+
${APP}/welcome/workspace-details
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
if (workspaces.length === 1) {
|
|
23
|
+
spinner.success(`Using workspace: ${workspaces[0].name}`);
|
|
24
|
+
return workspaces[0];
|
|
25
|
+
}
|
|
26
|
+
const choice = await select({
|
|
27
|
+
message: "Choose a workspace",
|
|
28
|
+
choices: workspaces.map((workspace) => ({
|
|
29
|
+
name: workspace.name,
|
|
30
|
+
value: workspace,
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
spinner.success(`Using workspace: ${choice.name}`);
|
|
34
|
+
return choice;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
spinner.stop();
|
|
38
|
+
}
|
|
39
|
+
}
|
package/lib/api/ensure-authed.js
CHANGED
|
@@ -2,8 +2,15 @@ import { auth as authApi } from "./auth.js";
|
|
|
2
2
|
import { deleteAuthToken, loadAuthToken } from "./keychain.js";
|
|
3
3
|
import { whoami } from "./whoami.js";
|
|
4
4
|
async function auth() {
|
|
5
|
-
process.stdout.write("You need to log in with Attio.\n\n");
|
|
6
5
|
await deleteAuthToken();
|
|
6
|
+
if (process.env.NODE_ENV !== "test") {
|
|
7
|
+
process.stdout.write("You need to log in with Attio. Press Enter to continue...\n\n");
|
|
8
|
+
await new Promise((resolve) => {
|
|
9
|
+
process.stdin.once("data", () => {
|
|
10
|
+
resolve();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
7
14
|
return await authApi();
|
|
8
15
|
}
|
|
9
16
|
export async function ensureAuthed() {
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { API } from "../env.js";
|
|
3
3
|
import { handleError } from "./handle-error.js";
|
|
4
4
|
import { makeHeaders } from "./make-headers.js";
|
|
5
|
+
const isTest = process.env.NODE_ENV === "test";
|
|
5
6
|
const workspaceResponseSchema = z.object({
|
|
6
7
|
workspace_id: z.string().uuid(),
|
|
7
8
|
slug: z.string(),
|
|
@@ -13,6 +14,16 @@ const listDevWorkspacesResponseSchema = z.object({
|
|
|
13
14
|
accurate_at: z.string().datetime(),
|
|
14
15
|
});
|
|
15
16
|
export async function fetchWorkspaces({ token }) {
|
|
17
|
+
if (isTest) {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
workspace_id: "test-id",
|
|
21
|
+
slug: "test-slug",
|
|
22
|
+
name: "Test Workspace",
|
|
23
|
+
logo_url: null,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
}
|
|
16
27
|
const response = await fetch(`${API}/dev-workspaces`, {
|
|
17
28
|
method: "GET",
|
|
18
29
|
headers: makeHeaders(token),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fetchAppInfo } from "./fetch-app-info.js";
|
|
2
|
+
import { Spinner } from "../util/spinner.js";
|
|
3
|
+
export async function getAppInfo({ token, appSlug, }) {
|
|
4
|
+
const spinner = new Spinner();
|
|
5
|
+
spinner.start("Loading app information...");
|
|
6
|
+
try {
|
|
7
|
+
const appInfo = await fetchAppInfo({ token, appSlug });
|
|
8
|
+
if (appInfo === null) {
|
|
9
|
+
spinner.error("App not found");
|
|
10
|
+
throw new Error("App not found");
|
|
11
|
+
}
|
|
12
|
+
spinner.success(`App found: ${appInfo.title}`);
|
|
13
|
+
return appInfo;
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
spinner.stop();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const packageJsonSchema = z.object({
|
|
5
|
+
name: z.string({
|
|
6
|
+
required_error: "No name field found in package.json",
|
|
7
|
+
invalid_type_error: "name must be a string in package.json",
|
|
8
|
+
}),
|
|
9
|
+
});
|
|
10
|
+
export async function getAppSlugFromPackageJson() {
|
|
11
|
+
try {
|
|
12
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
13
|
+
const packageJsonRaw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
14
|
+
const result = packageJsonSchema.safeParse(packageJsonRaw);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new Error(result.error.issues[0]?.message || "Malformed package.json");
|
|
17
|
+
}
|
|
18
|
+
return result.data.name;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof SyntaxError) {
|
|
22
|
+
throw new Error("Invalid JSON in package.json");
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Failed to read package.json");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { fetchVersions } from "./fetch-versions.js";
|
|
2
|
+
import { Spinner } from "../util/spinner.js";
|
|
3
|
+
export async function getVersions({ token, appInfo, }) {
|
|
4
|
+
const spinner = new Spinner();
|
|
5
|
+
try {
|
|
6
|
+
spinner.start("Loading versions...");
|
|
7
|
+
const versions = await fetchVersions({
|
|
8
|
+
token,
|
|
9
|
+
appId: appInfo.app_id,
|
|
10
|
+
});
|
|
11
|
+
spinner.success("Versions loaded");
|
|
12
|
+
return versions;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
spinner.error("Error loading versions");
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
spinner.stop();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { clearTerminal } from "../../util/clear-terminal.js";
|
|
2
|
+
import { prepareBuildContexts } from "../dev/prepare-build-contexts.js";
|
|
3
|
+
import { printJsError } from "../../util/typescript.js";
|
|
4
|
+
import { errorsAndWarningsSchema } from "../../build.js";
|
|
5
|
+
export async function buildJavaScript() {
|
|
6
|
+
let buildContexts;
|
|
7
|
+
try {
|
|
8
|
+
buildContexts = await prepareBuildContexts("write-to-disk");
|
|
9
|
+
await Promise.all(buildContexts.map(async (context) => context.rebuild()));
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const errors = errorsAndWarningsSchema.parse(error);
|
|
14
|
+
clearTerminal();
|
|
15
|
+
errors.errors?.forEach((error) => printJsError(error, "error"));
|
|
16
|
+
errors.warnings?.forEach((warning) => printJsError(warning, "warning"));
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (buildContexts) {
|
|
21
|
+
await Promise.all(buildContexts.map(async (context) => context.dispose()));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { getDiagnostics, readConfig, typeScriptErrorSchema, printTsError, } from "../../util/typescript.js";
|
|
3
|
+
export async function validateTypeScript() {
|
|
4
|
+
try {
|
|
5
|
+
const program = await readConfig(path.resolve("tsconfig.json"));
|
|
6
|
+
if (program === "Not a TypeScript project") {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
const errors = await getDiagnostics(program);
|
|
10
|
+
if (errors.length) {
|
|
11
|
+
errors.forEach(printTsError);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
const tsError = typeScriptErrorSchema.parse({ text: error.message });
|
|
19
|
+
printTsError(tsError);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (typeof error === "object" &&
|
|
23
|
+
error !== null &&
|
|
24
|
+
"code" in error &&
|
|
25
|
+
(error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM")) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/lib/commands/build.js
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import { Command
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
export const optionsSchema = z.object({
|
|
6
|
-
dev: z.boolean().default(false),
|
|
7
|
-
});
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { validateTypeScript } from "./build/validate-typescript.js";
|
|
3
|
+
import { buildJavaScript } from "./build/build-javascript.js";
|
|
4
|
+
import { Spinner } from "../util/spinner.js";
|
|
8
5
|
export const build = new Command("build")
|
|
9
6
|
.description("Build your Attio extension locally")
|
|
10
|
-
.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
.action(async (unparsedOptions) => {
|
|
8
|
+
const spinner = new Spinner();
|
|
9
|
+
spinner.start("Validating TypeScript...");
|
|
10
|
+
try {
|
|
11
|
+
const tsValid = await validateTypeScript();
|
|
12
|
+
if (!tsValid) {
|
|
13
|
+
spinner.error("TypeScript validation failed");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
spinner.success("TypeScript validation passed");
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
spinner.error(`TypeScript validation failed: ${error}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
spinner.start("Building JavaScript...");
|
|
23
|
+
try {
|
|
24
|
+
const jsBuilt = await buildJavaScript();
|
|
25
|
+
if (!jsBuilt) {
|
|
26
|
+
spinner.error("JavaScript build failed");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
spinner.success("Build completed successfully");
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
spinner.error(`JavaScript build failed: ${error}`);
|
|
34
|
+
process.exit(1);
|
|
18
35
|
}
|
|
19
|
-
actor.start();
|
|
20
36
|
});
|
package/lib/commands/dev/boot.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ensureAuthed } from "../../api/ensure-authed.js";
|
|
2
2
|
import { loadEnv } from "../../util/load-env.js";
|
|
3
|
-
import {
|
|
4
|
-
import { fetchAppInfo } from "../../machines/actors.js";
|
|
5
|
-
import { getAppSlugFromPackageJson } from "../../machines/actors.js";
|
|
3
|
+
import { getAppSlugFromPackageJson } from "../../api/get-app-slug-from-package-json.js";
|
|
6
4
|
import { loadAttioCliPackageJson } from "../../util/load-attio-cli-package-json.js";
|
|
7
5
|
import { createDevVersion } from "../../api/create-dev-version.js";
|
|
6
|
+
import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
|
|
7
|
+
import { getAppInfo } from "../../api/get-app-info.spinner.js";
|
|
8
8
|
export async function boot({ workspaceSlug }) {
|
|
9
9
|
const token = await ensureAuthed();
|
|
10
10
|
const appSlug = await getAppSlugFromPackageJson();
|
|
11
|
-
const appInfo = await
|
|
11
|
+
const appInfo = await getAppInfo({ token, appSlug });
|
|
12
12
|
const workspace = await determineWorkspace({ token, workspaceSlug });
|
|
13
13
|
const environmentVariables = await loadEnv();
|
|
14
14
|
const packageJson = loadAttioCliPackageJson();
|
|
@@ -20,7 +20,7 @@ export function buildJavaScript(onSuccess) {
|
|
|
20
20
|
async function handleBuild() {
|
|
21
21
|
try {
|
|
22
22
|
if (!buildContexts) {
|
|
23
|
-
buildContexts = await prepareBuildContexts();
|
|
23
|
+
buildContexts = await prepareBuildContexts("in-memory");
|
|
24
24
|
}
|
|
25
25
|
const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
|
|
26
26
|
const bundles = results.map((result) => result.outputFiles[0].text);
|
|
@@ -36,7 +36,7 @@ export function buildJavaScript(onSuccess) {
|
|
|
36
36
|
}
|
|
37
37
|
watcher.on("ready", () => {
|
|
38
38
|
handleBuild();
|
|
39
|
-
watcher.on("all", (event
|
|
39
|
+
watcher.on("all", (event) => {
|
|
40
40
|
if (event === "add" || event === "change" || event === "unlink") {
|
|
41
41
|
handleBuild();
|
|
42
42
|
}
|
|
@@ -20,7 +20,7 @@ export function bundleJavaScript(onSuccess) {
|
|
|
20
20
|
async function handleBuild() {
|
|
21
21
|
try {
|
|
22
22
|
if (!buildContexts) {
|
|
23
|
-
buildContexts = await prepareBuildContexts();
|
|
23
|
+
buildContexts = await prepareBuildContexts("in-memory");
|
|
24
24
|
}
|
|
25
25
|
const results = await Promise.all(buildContexts.map(async (context) => context.rebuild()));
|
|
26
26
|
const bundles = results.map((result) => result.outputFiles[0].text);
|
|
@@ -6,7 +6,7 @@ import { createClientBuildConfig } from "../../build/client/create-client-build-
|
|
|
6
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
|
-
export async function prepareBuildContexts() {
|
|
9
|
+
export async function prepareBuildContexts(mode) {
|
|
10
10
|
const srcDir = "src";
|
|
11
11
|
const assetsDir = path.join(srcDir, "assets");
|
|
12
12
|
const webhooksDir = path.join(srcDir, "webhooks");
|
|
@@ -29,7 +29,7 @@ export async function prepareBuildContexts() {
|
|
|
29
29
|
entryPoint: tempFile.path,
|
|
30
30
|
srcDir,
|
|
31
31
|
}),
|
|
32
|
-
write:
|
|
32
|
+
write: mode === "write-to-disk",
|
|
33
33
|
outfile: path.resolve("dist", "index.js"),
|
|
34
34
|
loader: { ".png": "dataurl", ".graphql": "text", ".gql": "text" },
|
|
35
35
|
});
|
|
@@ -58,7 +58,7 @@ export async function prepareBuildContexts() {
|
|
|
58
58
|
};
|
|
59
59
|
const esbuildContext = await esbuild.context({
|
|
60
60
|
...createServerBuildConfig(tempFile.path),
|
|
61
|
-
write:
|
|
61
|
+
write: mode === "write-to-disk",
|
|
62
62
|
outfile: path.resolve("dist", "server.js"),
|
|
63
63
|
});
|
|
64
64
|
return {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import Spinner from "tiny-spinner";
|
|
2
1
|
import notifier from "node-notifier";
|
|
3
2
|
import { startUpload } from "../../api/start-upload.js";
|
|
4
3
|
import { completeBundleUpload } from "../../api/complete-bundle-upload.js";
|
|
4
|
+
import { Spinner } from "../../util/spinner.js";
|
|
5
5
|
export async function upload({ token, contents, devVersionId, appId, }) {
|
|
6
|
-
const spinner = new Spinner();
|
|
6
|
+
const spinner = new Spinner().start("Uploading...");
|
|
7
7
|
try {
|
|
8
|
-
spinner.start("Uploading...");
|
|
9
8
|
const { client_bundle_upload_url, server_bundle_upload_url, app_dev_version_bundle_id: bundleId, } = await startUpload({
|
|
10
9
|
token,
|
|
11
10
|
appId,
|
package/lib/commands/dev.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { boot } from "./dev/boot.js";
|
|
5
|
-
import { bundleJavaScript } from "./dev/bundle-javascript.js";
|
|
6
|
-
import { upload } from "./dev/upload.js";
|
|
7
4
|
import { printMessage } from "../util/print-message.js";
|
|
8
|
-
import { onboarding } from "./dev/onboarding.js";
|
|
9
5
|
import { validateTypeScript } from "./dev/validate-typescript.js";
|
|
10
6
|
import { graphqlCodeGen } from "./dev/graphql-code-gen.js";
|
|
7
|
+
import { bundleJavaScript } from "./dev/bundle-javascript.js";
|
|
8
|
+
import { boot } from "./dev/boot.js";
|
|
9
|
+
import { onboarding } from "./dev/onboarding.js";
|
|
10
|
+
import { graphqlServer } from "./dev/graphql-server.js";
|
|
11
|
+
import { upload } from "./dev/upload.js";
|
|
11
12
|
export const optionsSchema = z.object({
|
|
12
13
|
workspace: z.string().optional(),
|
|
13
14
|
});
|
|
14
15
|
export const dev = new Command("dev")
|
|
15
16
|
.description("Develop your Attio app")
|
|
16
17
|
.addOption(new Option("--dev", "Run in development mode (additional debugging info)"))
|
|
17
|
-
.addOption(new Option("--workspace", "The slug of the workspace to use"))
|
|
18
|
+
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
18
19
|
.action(async (unparsedOptions) => {
|
|
19
20
|
const { workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
|
|
20
21
|
const cleanupFunctions = [];
|
|
21
22
|
try {
|
|
22
|
-
const { token, appId,
|
|
23
|
+
const { token, appId, appSlug, workspace, devVersionId } = await boot({ workspaceSlug });
|
|
24
|
+
const cleanupGraphqlServer = graphqlServer();
|
|
25
|
+
cleanupFunctions.push(cleanupGraphqlServer);
|
|
23
26
|
const cleanupOnboardingDaemon = onboarding({ token, appId, appSlug, workspace });
|
|
24
27
|
cleanupFunctions.push(cleanupOnboardingDaemon);
|
|
25
28
|
const [cleanupTs, triggerTs] = validateTypeScript();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
export const LANGUAGES = [
|
|
3
|
+
{ name: "TypeScript (recommended)", value: "typescript" },
|
|
4
|
+
{ name: "JavaScript", value: "javascript" },
|
|
5
|
+
];
|
|
6
|
+
export async function askLanguage() {
|
|
7
|
+
return select({
|
|
8
|
+
message: "What language would you like to use?",
|
|
9
|
+
choices: LANGUAGES,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ensureAuthed } from "../../api/ensure-authed.js";
|
|
2
|
+
import { getAppInfo } from "../../api/get-app-info.spinner.js";
|
|
3
|
+
import { determineWorkspace } from "../../api/determine-workspace.spinner.js";
|
|
4
|
+
export async function boot({ workspaceSlug, appSlug, }) {
|
|
5
|
+
const token = await ensureAuthed();
|
|
6
|
+
const appInfo = await getAppInfo({ token, appSlug });
|
|
7
|
+
const workspace = await determineWorkspace({ token, workspaceSlug });
|
|
8
|
+
return {
|
|
9
|
+
token,
|
|
10
|
+
appId: appInfo.app_id,
|
|
11
|
+
appInfo,
|
|
12
|
+
workspace,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import boxen from "boxen";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { createDirectory } from "../../util/create-directory.js";
|
|
7
|
+
import { canWrite } from "../../util/validate-slug.js";
|
|
8
|
+
import { copyWithTransform } from "../../util/copy-with-replace.js";
|
|
9
|
+
import { printMessage } from "../../util/print-message.js";
|
|
10
|
+
import { Spinner } from "../../util/spinner.js";
|
|
11
|
+
export async function createProject({ appSlug, language, appInfo, appId, }) {
|
|
12
|
+
const spinner = new Spinner();
|
|
13
|
+
if (existsSync(path.join(process.cwd(), appSlug))) {
|
|
14
|
+
throw new Error(`Directory "${appSlug}" already exists`);
|
|
15
|
+
}
|
|
16
|
+
if (!canWrite(process.cwd())) {
|
|
17
|
+
throw new Error("Write access denied to current directory");
|
|
18
|
+
}
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const projectDir = createDirectory(appSlug);
|
|
21
|
+
const templatesDir = path.resolve(__dirname, "../../templates", language);
|
|
22
|
+
const commonDir = path.resolve(__dirname, "../../templates", "common");
|
|
23
|
+
spinner.start("Creating project...");
|
|
24
|
+
try {
|
|
25
|
+
const transform = (contents) => contents
|
|
26
|
+
.replaceAll("title-to-be-replaced", appInfo.title)
|
|
27
|
+
.replaceAll("id-to-be-replaced", appId)
|
|
28
|
+
.replaceAll("slug-to-be-replaced", appSlug);
|
|
29
|
+
await Promise.all([
|
|
30
|
+
copyWithTransform(templatesDir, projectDir, transform),
|
|
31
|
+
copyWithTransform(commonDir, projectDir, transform),
|
|
32
|
+
]);
|
|
33
|
+
spinner.success("Project created");
|
|
34
|
+
printMessage("\n" + chalk.green(`SUCCESS!! 🎉 Your app directory has been created.`));
|
|
35
|
+
printMessage("\nTo get started, run:\n");
|
|
36
|
+
printMessage(boxen(`cd ${appSlug}\nnpm install\nnpm run dev`, {
|
|
37
|
+
padding: 1,
|
|
38
|
+
margin: 1,
|
|
39
|
+
borderStyle: "round",
|
|
40
|
+
}) + "\n");
|
|
41
|
+
printMessage(`(${chalk.yellow("yarn")}, ${chalk.yellow("pnpm")}, and ${chalk.yellow("bun")} also work!)\n`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
spinner.error("Error creating project");
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
spinner.stop();
|
|
49
|
+
}
|
|
50
|
+
}
|
package/lib/commands/init.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import { Argument, Command, Option } from "commander";
|
|
2
|
-
import { createActor } from "xstate";
|
|
3
2
|
import { z } from "zod";
|
|
4
|
-
import
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { boot } from "./init/boot.js";
|
|
5
|
+
import { createProject } from "./init/create-project.js";
|
|
6
|
+
import { askLanguage } from "./init/ask-language.js";
|
|
5
7
|
export const argsSchema = z.string();
|
|
6
8
|
export const optionsSchema = z.object({
|
|
7
9
|
language: z.enum(["javascript", "typescript"]).optional(),
|
|
8
|
-
|
|
10
|
+
workspace: z.string().optional(),
|
|
9
11
|
});
|
|
10
12
|
export const init = new Command("init")
|
|
11
13
|
.description("Initialize a new Attio app")
|
|
12
14
|
.addArgument(new Argument("<app-slug>", "The app slug, chosen in the developer dashboard"))
|
|
13
15
|
.addOption(new Option("--language <language>", "Language").choices(["javascript", "typescript"]))
|
|
14
|
-
.addOption(new Option("--
|
|
15
|
-
.action((unparsedArgs, unparsedOptions) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
.addOption(new Option("--workspace <slug>", "The slug of the workspace to use"))
|
|
17
|
+
.action(async (unparsedArgs, unparsedOptions) => {
|
|
18
|
+
try {
|
|
19
|
+
const appSlug = argsSchema.parse(unparsedArgs);
|
|
20
|
+
const { language: cliLanguage, workspace: workspaceSlug } = optionsSchema.parse(unparsedOptions);
|
|
21
|
+
const { appId, appInfo } = await boot({ workspaceSlug, appSlug });
|
|
22
|
+
const language = cliLanguage ?? (await askLanguage());
|
|
23
|
+
await createProject({
|
|
20
24
|
appSlug,
|
|
21
|
-
language
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (options.dev) {
|
|
25
|
-
actor.subscribe((state) => {
|
|
26
|
-
console.log("state:", state.value);
|
|
25
|
+
language,
|
|
26
|
+
appInfo,
|
|
27
|
+
appId,
|
|
27
28
|
});
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
process.stderr.write(`${chalk.red("✖ " + error)}\n`);
|
|
33
|
+
process.exit(1);
|
|
28
34
|
}
|
|
29
|
-
actor.start();
|
|
30
35
|
});
|
package/lib/commands/login.js
CHANGED
|
@@ -4,8 +4,10 @@ export const login = new Command("login").description("Authenticate with Attio")
|
|
|
4
4
|
authApi()
|
|
5
5
|
.then((token) => {
|
|
6
6
|
process.stdout.write("🔓 Successfully authenticated.\n");
|
|
7
|
+
process.exit(0);
|
|
7
8
|
})
|
|
8
9
|
.catch((error) => {
|
|
9
10
|
process.stderr.write(`❌ Authentication failed\n\n${error}\n`);
|
|
11
|
+
process.exit(1);
|
|
10
12
|
});
|
|
11
13
|
});
|
package/lib/commands/logout.js
CHANGED
|
@@ -4,8 +4,10 @@ export const logout = new Command("logout").description("Log out from Attio").ac
|
|
|
4
4
|
deleteAuthToken()
|
|
5
5
|
.then(() => {
|
|
6
6
|
process.stdout.write("🔒 Successfully logged out.\n");
|
|
7
|
+
process.exit(0);
|
|
7
8
|
})
|
|
8
9
|
.catch((error) => {
|
|
9
10
|
process.stderr.write(`❌ Logout failed\n\n${error}\n`);
|
|
11
|
+
process.exit(1);
|
|
10
12
|
});
|
|
11
13
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ensureAuthed } from "../../../api/ensure-authed.js";
|
|
2
|
+
import { getAppSlugFromPackageJson } from "../../../api/get-app-slug-from-package-json.js";
|
|
3
|
+
import { getAppInfo } from "../../../api/get-app-info.spinner.js";
|
|
4
|
+
export async function boot() {
|
|
5
|
+
const token = await ensureAuthed();
|
|
6
|
+
const appSlug = await getAppSlugFromPackageJson();
|
|
7
|
+
const appInfo = await getAppInfo({ token, appSlug });
|
|
8
|
+
return { token, appInfo };
|
|
9
|
+
}
|