@sodacore/cli
Version:
Sodacore CLI is a plugin that offers CLI functionality within the framework.
239 lines (238 loc) • 8.72 kB
JavaScript
import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, tasks, text } from '@clack/prompts';
import { addConnection, getConfig, removeConnection } from './config';
import { chdir, cwd, exit } from 'node:process';
import { mkdir, rm } from 'node:fs/promises';
import { $, file, write } from 'bun';
import { resolve } from 'node:path';
import { getFiles } from './files';
export async function main() {
intro('Hello, World!');
const meh = await text({ message: 'Meh' });
const results = await group({
name: () => text({ message: 'Field: A' }),
age: () => text({ message: 'Field: B' }),
colors: ({ results }) => multiselect({ message: `Field: C (${results.name} / ${results.age})`, options: [{ value: 'red', label: 'Red' }, { value: 'green', label: 'Green' }, { value: 'blue', label: 'Blue' }] }),
}, {
onCancel: () => {
cancel('Operation cancelled');
exit(0);
},
});
const foo = await text({ message: 'Foo', defaultValue: 'bar' });
outro('Done!');
console.log(meh, results, foo);
exit(0);
// Get the config.
const config = await getConfig();
// What would you like to do?
const intention = await select({
message: 'What would you like to do?',
options: [
{ value: 'create', label: 'Create a new project' },
{ value: 'connection:add', label: 'Add a new connection' },
{ value: 'connection:del', label: 'Remove a connection' },
...config.connections.map(config => ({
value: `${config.host}:${config.port}:${config.pass}`,
label: `Access: ${config.host}:${config.port}`,
})),
],
});
if (isCancel(intention)) {
cancel('Operation cancelled');
exit(0);
}
// Validate the intention.
if (intention === 'create') {
await createProjectMenu();
}
else if (intention === 'connection:add') {
await addConnectionMenu();
}
else if (intention === 'connection:del') {
await removeConnectionMenu();
}
else {
const [host, port, pass] = intention.split(':');
const connection = config.connections.find(c => c.host === host && c.port === Number.parseInt(port) && c.pass === pass);
if (!connection) {
cancel('Invalid connection');
exit(1);
}
await accessConnectionMenu(connection);
}
}
export async function createProjectMenu() {
// Ask for the project name.
const projectName = await text({
message: 'What is the project name?',
defaultValue: 'my-sodacore-project',
placeholder: 'my-sodacore-project',
});
if (isCancel(projectName)) {
cancel('Operation cancelled');
exit(0);
}
// Ask for the project path.
const projectBasePath = await text({
message: 'What is the project path?',
defaultValue: cwd(),
placeholder: cwd(),
});
if (isCancel(projectBasePath)) {
cancel('Operation cancelled');
exit(0);
}
// Confirm the path.
const isPathConfirmed = await confirm({
message: `Is the project path ${projectBasePath}/${projectName} correct?`,
});
if (isCancel(isPathConfirmed)) {
cancel('Operation cancelled');
exit(0);
}
// Define the project.
const projectPath = `${projectBasePath}/${projectName}`;
// What plugins should be installed?
const plugins = await multiselect({
message: 'What plugins would you like to install? (Use space to select, enter to confirm)',
options: [
{ value: '@sodacore/http@alpha', label: 'HTTP' },
{ value: '@sodacore/ws@alpha', label: 'WebSockets' },
{ value: '@sodacore/prisma@alpha', label: 'Prisma' },
{ value: '@sodacore/discord@alpha', label: 'Discord' },
{ value: '@sodacore/cli@alpha', label: 'CLI' },
],
});
if (isCancel(plugins)) {
cancel('Operation cancelled');
exit(0);
}
// Add the base packages.
const packages = ['@sodacore/di@alpha', '@sodacore/core@alpha'];
packages.push(...plugins);
// Run the tasks.
await tasks([
{
title: 'Initialising project folder',
task: async () => {
await mkdir(projectPath, { recursive: true });
chdir(projectPath);
$.cwd(projectPath);
await $ `bun init -y`.quiet();
await rm(resolve(projectPath, './index.ts'));
await mkdir(resolve(projectPath, './src'), { recursive: true });
return 'Initialised project folder';
},
},
{
title: 'Installing dependencies',
task: async () => {
chdir(projectPath);
await $ `bun install ${{ raw: packages.join(' ') }}`.quiet();
return 'Dependencies installed';
},
},
{
title: 'Creating template files',
task: async () => {
const files = getFiles(packages);
const createdFiles = [];
for (const file of files) {
if (!packages.includes(file.package))
continue;
const filePath = resolve(projectPath, file.path);
await mkdir(resolve(projectPath, file.path.split('/').slice(0, -1).join('/')), { recursive: true });
await write(filePath, file.content);
createdFiles.push(filePath);
}
return `Created ${createdFiles.length} files:\n${createdFiles.map(file => `- ${file}`).join('\n')}`;
},
},
{
title: 'Modifying files with context information.',
task: async () => {
// Load the JSON file.
const packageJson = file(resolve(projectPath, './package.json'));
if (!await packageJson.exists()) {
cancel('Package.json not found.');
exit(1);
}
// Write the version and script.
const packageMeta = await packageJson.json();
packageMeta.version = '0.0.0';
packageMeta.scripts = {};
packageMeta.scripts.dev = 'bun run ./src/main.ts';
await packageJson.write(JSON.stringify(packageMeta, null, '\t'));
},
},
]);
// Log the outcome.
log.success(`Created project at ${projectPath}\n\nRun \`cd ${projectPath}\` and \`bun run dev\` to get started the project.`);
}
export async function addConnectionMenu() {
// Ask for available hostname.
const hostName = await text({
message: 'What hostname would you like to access?',
defaultValue: 'localhost',
placeholder: 'localhost',
});
if (isCancel(hostName)) {
cancel('Operation cancelled');
exit(0);
}
// Ask for available port.
const port = await text({
message: 'What port would you like to access?',
defaultValue: '36445',
placeholder: '36445',
});
if (isCancel(port)) {
cancel('Operation cancelled');
exit(0);
}
// Ask for CLI password.
const password = await text({
message: 'What is the CLI password?',
defaultValue: '',
placeholder: 'This is set as a config option in the CLI project.',
});
if (isCancel(password)) {
cancel('Operation cancelled');
exit(0);
}
// Get the config.
const status = await addConnection(hostName, Number.parseInt(port), password);
if (!status) {
cancel('Failed to add connection');
exit(1);
}
// Return to main menu.
await main();
}
export async function removeConnectionMenu() {
// Get the config.
const config = await getConfig();
// Show the available connections.
const connectionId = await select({
message: 'Select a connection to remove',
options: config.connections.map((connection, index) => ({
value: index,
label: `${connection.host}:${connection.port}`,
})),
});
if (isCancel(connectionId)) {
cancel('Operation cancelled');
exit(0);
}
// Remove the connection.
const status = await removeConnection(connectionId);
if (!status) {
cancel('Failed to remove connection');
exit(1);
}
// Return to main menu.
await main();
}
export async function accessConnectionMenu(connection) {
console.log('accessProject', connection);
}