Skip to content

Commit

Permalink
improve link command (#10257)
Browse files Browse the repository at this point in the history
FredKSchott authored Feb 29, 2024
1 parent 3df811a commit 2ecead4
Showing 1 changed file with 184 additions and 34 deletions.
218 changes: 184 additions & 34 deletions packages/db/src/core/cli/commands/link/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 2ecead4

Please sign in to comment.