Get started on CLI

This commit is contained in:
rijkvanzanten
2020-07-10 18:10:56 -04:00
parent decdad7caf
commit 531992f9b3
10 changed files with 897 additions and 600 deletions

1220
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,16 @@
{
"name": "api-node",
"version": "0.0.1",
"version": "9.0.0-alpha.3",
"description": "The Directus API in Node.js",
"main": "dist/server.js",
"bin": {
"directus": "dist/cli/index.js"
},
"scripts": {
"start": "NODE_ENV=production node dist/server.js",
"build": "rimraf dist && tsc -b && copyfiles \"src/**/*.*\" -e \"src/**/*.ts\" -u 1 dist",
"dev": "LOG_LEVEL=trace ts-node-dev src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada"
"dev": "LOG_LEVEL=trace ts-node-dev src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada",
"cli": "ts-node --script-mode --transpile-only src/cli/index.ts"
},
"repository": {
"type": "git",
@@ -27,14 +31,60 @@
"url": "https://github.com/directus/api-node/issues"
},
"homepage": "https://github.com/directus/api-node#readme",
"dependencies": {
"@hapi/joi": "^17.1.1",
"@slynova/flydrive": "^1.0.2",
"@slynova/flydrive-gcs": "^1.0.2",
"@slynova/flydrive-s3": "^1.0.2",
"argon2": "^0.26.2",
"atob": "^2.1.2",
"body-parser": "^1.19.0",
"busboy": "^0.3.1",
"camelcase": "^6.0.0",
"chalk": "^4.1.0",
"clear": "^0.1.0",
"commander": "^5.1.0",
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0",
"exif-reader": "^1.0.3",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"express-pino-logger": "^5.0.0",
"express-session": "^1.17.1",
"fs-extra": "^9.0.1",
"get-port": "^5.1.1",
"grant": "^5.2.0",
"icc": "^2.0.0",
"inquirer": "^7.3.1",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"liquidjs": "^9.14.1",
"lodash": "^4.17.19",
"ms": "^2.1.2",
"mssql": "^6.2.0",
"mysql": "^2.18.1",
"nanoid": "^3.1.10",
"nodemailer": "^6.4.10",
"oracledb": "^5.0.0",
"pg": "^8.3.0",
"pino": "^6.3.2",
"resolve-cwd": "^3.0.0",
"sharp": "^0.25.4",
"sqlite3": "^5.0.0",
"uuid": "^8.2.0",
"uuid-validate": "0.0.3"
},
"devDependencies": {
"@types/atob": "^2.1.2",
"@types/busboy": "^0.2.3",
"@types/clear": "^0.1.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.7",
"@types/express-pino-logger": "^4.0.2",
"@types/express-session": "^1.17.0",
"@types/fs-extra": "^9.0.1",
"@types/hapi__joi": "^17.1.3",
"@types/inquirer": "^6.5.0",
"@types/jsonwebtoken": "^8.5.0",
"@types/lodash": "^4.14.157",
"@types/ms": "^0.7.31",
@@ -52,6 +102,7 @@
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"ts-node": "^8.10.2",
"ts-node-dev": "^1.0.0-pre.51",
"tslint": "^6.1.2",
"typescript": "^3.9.6"
},
@@ -64,43 +115,5 @@
"*.{js,ts}": [
"prettier --write"
]
},
"dependencies": {
"@hapi/joi": "^17.1.1",
"@slynova/flydrive": "^1.0.2",
"@slynova/flydrive-gcs": "^1.0.2",
"@slynova/flydrive-s3": "^1.0.2",
"argon2": "^0.26.2",
"atob": "^2.1.2",
"body-parser": "^1.19.0",
"busboy": "^0.3.1",
"camelcase": "^6.0.0",
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0",
"exif-reader": "^1.0.3",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"express-pino-logger": "^5.0.0",
"express-session": "^1.17.1",
"get-port": "^5.1.1",
"grant": "^5.2.0",
"icc": "^2.0.0",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"liquidjs": "^9.14.1",
"lodash": "^4.17.19",
"ms": "^2.1.2",
"mssql": "^6.2.0",
"mysql": "^2.18.1",
"nanoid": "^3.1.10",
"nodemailer": "^6.4.10",
"oracledb": "^5.0.0",
"pg": "^8.3.0",
"pino": "^6.3.2",
"sharp": "^0.25.4",
"sqlite3": "^5.0.0",
"ts-node-dev": "^1.0.0-pre.51",
"uuid": "^8.2.0",
"uuid-validate": "0.0.3"
}
}

13
src/cli/create/drivers.ts Normal file
View File

@@ -0,0 +1,13 @@
export const drivers = {
sqlite3: 'SQLite',
mysql: 'MySQL (/ MariaDB / Aurora)',
pg: 'PostgreSQL (/ Amazon Redshift)',
oracledb: 'Oracle Database',
mssql: 'Microsoft SQL Server',
};
export function getDriverForClient(client: string) {
for (const [key, value] of Object.entries(drivers)) {
if (value === client) return key;
}
}

66
src/cli/create/index.ts Normal file
View File

@@ -0,0 +1,66 @@
import fse from 'fs-extra';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { resolve } from 'path';
import { databaseQuestions } from './questions';
import { drivers, getDriverForClient } from './drivers';
export default async function create(directory: string, options: Record<string, any>) {
const path = resolve(directory);
checkRequirements();
if (await fse.pathExists(path)) {
const stat = await fse.stat(path);
if (stat.isDirectory() === false) {
console.error(
`Destination '${chalk.red(directory)}' already exists and is not a directory.`
);
process.exit(1);
}
const files = await fse.readdir(path);
if (files.length > 0) {
console.error(
`Destination '${chalk.red(
directory
)}' already exists and is not an empty directory.`
);
process.exit(1);
}
}
let { client } = await inquirer.prompt([
{
type: 'list',
name: 'client',
message: 'Choose your database client',
choices: Object.values(drivers),
},
]);
client = getDriverForClient(client);
const responses = await inquirer.prompt(
databaseQuestions[client].map((question) => question({ client }))
);
/** @todo
* - See if you can connect to DB
* - Install Directus system stuff into DB
* - Start the Node API
*/
}
function checkRequirements() {
const nodeVersion = process.versions.node;
const major = +nodeVersion.split('.')[0];
if (major < 12) {
console.error(`You are running Node ${nodeVersion}.`);
console.error('Directus requires Node 12 and up.');
console.error('Please update your Node version and try again.');
process.exit(1);
}
}

View File

@@ -0,0 +1,50 @@
const filename = () => ({
type: 'input',
name: 'filename',
message: 'Filename:',
default: './data.db',
});
const host = () => ({
type: 'input',
name: 'host',
message: 'Host:',
default: '127.0.0.1',
});
const port = ({ client }) => ({
type: 'input',
name: 'port',
message: 'Port:',
default() {
const ports = {
pg: 5432,
mysql: 3306,
oracledb: 1521,
mssql: 1433,
};
return ports[client];
},
});
const username = () => ({
type: 'input',
name: 'username',
message: 'Username:',
});
const password = () => ({
type: 'password',
name: 'password',
message: 'Password:',
mask: '*',
});
export const databaseQuestions = {
sqlite3: [filename],
mysql: [host, port, username, password],
pg: [host, port, username, password],
oracledb: [host, port, username, password],
mssql: [host, port, username, password],
};

18
src/cli/index.ts Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env node
import program from 'commander';
const pkg = require('../../package.json');
import start from './start';
import create from './create';
program.version(pkg.version, '-v, --version');
program.name('directus').usage('[command] [options]');
program.command('create <directory>').description('Create a new Directus Project').action(create);
program.command('start').description('Start the Directus API').action(start);
program.parseAsync(process.argv);

14
src/cli/start.ts Normal file
View File

@@ -0,0 +1,14 @@
import app from '../app';
import logger from '../logger';
import getPort from 'get-port';
import clear from 'clear';
export default async function start() {
clear();
const port = process.env.PORT || (await getPort({ port: 3000 }));
app.listen(port, () => {
logger.info(`Server started at port ${port}`);
});
}

View File

@@ -39,18 +39,6 @@ if (emailTransport === 'sendmail') {
pass: process.env.EMAIL_SMTP_PASSWORD,
},
} as any);
logger.trace('[Email] Verifying SMTP connection.');
transporter
.verify()
.then(() => {
logger.info('[Email] SMTP connected. Ready to send emails.');
})
.catch((err) => {
logger.error(`[Email] Couldn't connect to SMTP server:`);
logger.error(err);
});
}
export type EmailOptions = {

View File

@@ -1,5 +1,4 @@
import database, { schemaInspector } from '../database';
import { FIELD_SPECIAL_ALIAS_TYPES } from '../constants';
import { uniq } from 'lodash';
export default async function hasFields(fields: { collection: string; field: string }[]) {
@@ -28,8 +27,7 @@ export async function collectionHasFields(collection: string, fieldKeys: string[
.select('field')
.from('directus_fields')
.where({ collection })
.whereIn('field', fieldKeys)
.whereIn('special', FIELD_SPECIAL_ALIAS_TYPES),
.whereIn('field', fieldKeys),
]);
const existingFields = uniq([

View File

@@ -4,10 +4,11 @@
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
"sourceMap": false,
"outDir": "dist",
"rootDir": "src"
},
"lib": [
"es2015"
]
],
}