summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Fred K. Schott <fkschott@gmail.com> 2024-02-28 21:25:02 -0800
committerGravatar GitHub <noreply@github.com> 2024-02-28 21:25:02 -0800
commit2ecead463d128f28b4ac13b97723716d048397d1 (patch)
tree98d70a9057fbc2cbe6ed3959b6e503660831a04c
parent3df811a3bd00dfd5d04cbd16e4d9015a416c319d (diff)
downloadastro-2ecead463d128f28b4ac13b97723716d048397d1.tar.gz
astro-2ecead463d128f28b4ac13b97723716d048397d1.tar.zst
astro-2ecead463d128f28b4ac13b97723716d048397d1.zip
improve link command (#10257)
-rw-r--r--packages/db/src/core/cli/commands/link/index.ts218
1 files changed, 184 insertions, 34 deletions
diff --git a/packages/db/src/core/cli/commands/link/index.ts b/packages/db/src/core/cli/commands/link/index.ts
index 542782bd3..28f8cdb63 100644
--- a/packages/db/src/core/cli/commands/link/index.ts
+++ b/packages/db/src/core/cli/commands/link/index.ts
@@ -1,36 +1,60 @@
-import { mkdir, writeFile } from 'node:fs/promises';
import type { AstroConfig } from 'astro';
+import { slug } from 'github-slugger';
import { bgRed, cyan } from 'kleur/colors';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { homedir } from 'node:os';
+import { basename } from 'node:path';
+import ora from 'ora';
import prompts from 'prompts';
import type { Arguments } from 'yargs-parser';
import { MISSING_SESSION_ID_ERROR } from '../../../errors.js';
import { PROJECT_ID_FILE, getSessionIdFromFile } from '../../../tokens.js';
import { getAstroStudioUrl } from '../../../utils.js';
-export async function cmd({ flags }: { config: AstroConfig; flags: Arguments }) {
- const linkUrl = new URL(getAstroStudioUrl() + '/auth/cli/link');
+export async function cmd({}: { config: AstroConfig; flags: Arguments }) {
const sessionToken = await getSessionIdFromFile();
if (!sessionToken) {
console.error(MISSING_SESSION_ID_ERROR);
process.exit(1);
}
- let body = { id: flags._[4] } as {
- id?: string;
- projectIdName?: string;
- workspaceIdName?: string;
- };
- if (!body.id) {
- const workspaceIdName = await promptWorkspaceName();
- const projectIdName = await promptProjectName();
- body = { projectIdName, workspaceIdName };
+ const getWorkspaceIdAsync = getWorkspaceId();
+ await promptBegin();
+ const isLinkExisting = await promptLinkExisting();
+ if (isLinkExisting) {
+ const workspaceId = await getWorkspaceIdAsync;
+ const existingProjectData = await promptExistingProjectName({workspaceId});
+ return await linkProject(existingProjectData.id);
+ }
+
+ const isLinkNew = await promptLinkNew();
+ if (isLinkNew) {
+ const workspaceId = await getWorkspaceIdAsync;
+ const newProjectName = await promptNewProjectName();
+ const newProjectRegion = await promptNewProjectRegion();
+ const spinner = ora('Creating new project...').start();
+ const newProjectData = await createNewProject({workspaceId, name: newProjectName, region: newProjectRegion});
+ // TODO(fks): Actually listen for project creation before continuing
+ // This is just a dumb spinner that roughly matches database creation time.
+ await new Promise((r) => setTimeout(r, 4000));
+ spinner.succeed('Project created!');
+ return await linkProject(newProjectData.id);
}
+}
+
+async function linkProject(id: string) {
+ await mkdir(new URL('.', PROJECT_ID_FILE), { recursive: true });
+ await writeFile(PROJECT_ID_FILE, `${id}`);
+ console.info('Project linked.');
+}
+
+async function getWorkspaceId(): Promise<string> {
+ const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/workspaces.list');
const response = await fetch(linkUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${await getSessionIdFromFile()}`,
'Content-Type': 'application/json',
},
- body: JSON.stringify(body),
});
if (!response.ok) {
// Unauthorized
@@ -42,38 +66,164 @@ export async function cmd({ flags }: { config: AstroConfig; flags: Arguments })
);
process.exit(1);
}
+ console.error(`Failed to fetch user workspace: ${response.status} ${response.statusText}`);
+ process.exit(1);
+ }
+ const { data, success } = await response.json() as {success: false, data: unknown} | {success: true, data: {id: string}[]};
+ if (!success) {
+ console.error(`Failed to fetch user's workspace.`);
+ process.exit(1);
+ }
+ return data[0].id;
+}
- console.error(`Failed to link project: ${response.status} ${response.statusText}`);
+export async function createNewProject({workspaceId, name, region}: {workspaceId: string; name: string, region: string}) {
+ const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.create');
+ const response = await fetch(linkUrl, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${await getSessionIdFromFile()}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ workspaceId, name, region }),
+ });
+ if (!response.ok) {
+ // Unauthorized
+ if (response.status === 401) {
+ console.error(
+ `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan(
+ 'astro db login'
+ )} to authenticate and then try linking again.\n\n`
+ );
+ process.exit(1);
+ }
+ console.error(`Failed to create project: ${response.status} ${response.statusText}`);
process.exit(1);
}
- const { data } = await response.json();
- await mkdir(new URL('.', PROJECT_ID_FILE), { recursive: true });
- await writeFile(PROJECT_ID_FILE, `${data.id}`);
- console.info('Project linked.');
+ const { data, success } = await response.json() as {success: false, data: unknown} | {success: true, data: {id: string; idName: string}};
+ if (!success) {
+ console.error(`Failed to create project.`);
+ process.exit(1);
+ }
+ return {id: data.id, idName: data.idName};
}
-export async function promptProjectName(defaultName?: string): Promise<string> {
- const { projectName } = await prompts({
- type: 'text',
- name: 'projectName',
- message: 'Project ID',
- initial: defaultName,
+export async function promptExistingProjectName({workspaceId}: {workspaceId: string}) {
+ const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.list');
+ const response = await fetch(linkUrl, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${await getSessionIdFromFile()}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({workspaceId}),
+ });
+ if (!response.ok) {
+ // Unauthorized
+ if (response.status === 401) {
+ console.error(
+ `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan(
+ 'astro db login'
+ )} to authenticate and then try linking again.\n\n`
+ );
+ process.exit(1);
+ }
+ console.error(`Failed to fetch projects: ${response.status} ${response.statusText}`);
+ process.exit(1);
+ }
+ const { data, success } = await response.json() as {success: false, data: unknown} | {success: true, data: {id: string; idName: string}[]};
+ if (!success) {
+ console.error(`Failed to fetch projects.`);
+ process.exit(1);
+ }
+ const { projectId } = await prompts({
+ type: 'autocomplete',
+ name: 'projectId',
+ message: 'What is your project name?',
+ limit: 5,
+ choices: data.map((p: any) => ({title: p.name, value: p.id})),
});
- if (typeof projectName !== 'string') {
+ if (typeof projectId !== 'string') {
+ console.log('Canceled.')
process.exit(0);
}
- return projectName;
+ const selectedProjectData = data.find((p: any) => p.id === projectId)!;
+ return selectedProjectData;
+}
+
+export async function promptBegin(): Promise<void> {
+ // Get the current working directory relative to the user's home directory
+ const prettyCwd = process.cwd().replace(homedir(), '~');
+
+ // prompt
+ const { begin } = await prompts({
+ type: 'confirm',
+ name: 'begin',
+ message: `Link "${prettyCwd}" with Astro Studio?`,
+ initial: true,
+ });
+ if (!begin) {
+ console.log('Canceled.')
+ process.exit(0);
+ };
+}
+
+export async function promptLinkExisting(): Promise<boolean> {
+ // prompt
+ const { linkExisting } = await prompts({
+ type: 'confirm',
+ name: 'linkExisting',
+ message: `Link with an existing project in Astro Studio?`,
+ initial: true,
+ });
+ return !!linkExisting;
+}
+
+export async function promptLinkNew(): Promise<boolean> {
+ // prompt
+ const { linkNew } = await prompts({
+ type: 'confirm',
+ name: 'linkNew',
+ message: `Create a new project in Astro Studio?`,
+ initial: true,
+ });
+ if (!linkNew) {
+ console.log('Canceled.')
+ process.exit(0);
+ };
+ return true;
}
-export async function promptWorkspaceName(defaultName?: string): Promise<string> {
- const { workspaceName } = await prompts({
+
+export async function promptNewProjectName(): Promise<string> {
+ const { newProjectName } = await prompts({
type: 'text',
- name: 'workspaceName',
- message: 'Workspace ID',
- initial: defaultName,
+ name: 'newProjectName',
+ message: `What is your new project's name?`,
+ initial: basename(process.cwd()),
+ format: (val) => slug(val),
});
- if (typeof workspaceName !== 'string') {
+ if (!newProjectName) {
+ console.log('Canceled.')
process.exit(0);
- }
- return workspaceName;
+ };
+ return newProjectName;
}
+
+export async function promptNewProjectRegion(): Promise<string> {
+ const { newProjectRegion } = await prompts({
+ type: 'select',
+ name: 'newProjectRegion',
+ message: `Where should your new database live?`,
+ choices: [
+ {title: 'North America (East)', value: 'NorthAmericaEast'},
+ {title: 'North America (West)', value: 'NorthAmericaWest'}
+ ],
+ initial: 0,
+ });
+ if (!newProjectRegion) {
+ console.log('Canceled.')
+ process.exit(0);
+ };
+ return newProjectRegion;
+} \ No newline at end of file