diff --git a/src/commands/init.ts b/src/commands/init.ts new file mode 100644 index 0000000..59a89a9 --- /dev/null +++ b/src/commands/init.ts @@ -0,0 +1,36 @@ +import { isProjectFileExists, saveProjectConfigToDisk } from "../lib/utils"; +import { + askIfOverrideProjectFile, + askForProjectDetails, + askForFeatures +} from "../lib/prompt"; +import chalk from "chalk"; + +module.exports = async function() { + if (isProjectFileExists()) { + const shouldOverrideConfigFile = await askIfOverrideProjectFile(); + if (shouldOverrideConfigFile.override === false) { + process.exit(0); + } + } + + const project = await askForProjectDetails(); + const { features } = await askForFeatures(); + const featuresConfiguration: any = {}; + + for await (let feature of features) { + console.log(`Configuring ${chalk.green(feature)}:`); + try { + const featureImplementation = require(`./lib/features/${feature}/index`); + const config = await featureImplementation(); + featuresConfiguration[feature] = config; + } catch (error) { + console.error(error.toString()); + } + } + + saveProjectConfigToDisk({ + project, + ...featuresConfiguration + }); +}; diff --git a/src/commands/login.ts b/src/commands/login.ts new file mode 100644 index 0000000..513ddb7 --- /dev/null +++ b/src/commands/login.ts @@ -0,0 +1,27 @@ +import { az, saveProjectConfigToDisk } from "../lib/utils"; +import { chooseSubscription } from "../lib/prompt"; + +module.exports = async function() { + // console.log(chalk.green(`Fetching subscriptions...`)); + + // @todo save these subscriptions globally. + let subscriptions: string = await az( + `login --query '[].{name:name, state:state, id:id}'`, + `Loading your subscriptions...` + ); + + if (subscriptions.length) { + const subscriptionsList = JSON.parse(subscriptions) as AzureSubscription[]; + let selectedSubscription = (await chooseSubscription(subscriptionsList)) + .subscription as string; + const { id, name } = subscriptionsList.find( + (sub: AzureSubscription) => sub.name === selectedSubscription + ) as AzureSubscription; + saveProjectConfigToDisk({ + subscription: { + id, + name + } + }); + } +}; diff --git a/src/features/hosting/command.ts b/src/features/hosting/command.ts new file mode 100644 index 0000000..37ece26 --- /dev/null +++ b/src/features/hosting/command.ts @@ -0,0 +1,13 @@ +import { az } from "../../lib/utils"; + +export async function push() { + await az( + `storage blob service-properties update --account-name --static-website --404-document --index-document ` + ); + await az( + `storage blob upload-batch -s -d \$web --account-name ` + ); + await az( + `storage account show -n -g --query "primaryEndpoints.web"` + ); +} diff --git a/src/features/hosting/index.ts b/src/features/hosting/index.ts new file mode 100644 index 0000000..57ef1c8 --- /dev/null +++ b/src/features/hosting/index.ts @@ -0,0 +1,23 @@ +import { QuestionCollection } from "inquirer"; +import { createDirectoryIfNotExists } from "../../lib/utils"; +import inquirer = require("inquirer"); + +// Note: use commonJs exports +module.exports = async function(): Promise { + const questions: QuestionCollection = [ + { + type: "input", + name: "folder", + message: "Enter public folder (will be created if not present):", + default: "public", + validate: function(value: string) { + if (value && value.length) { + return createDirectoryIfNotExists(value); + } else { + return "Please enter a public folder."; + } + } + } + ]; + return inquirer.prompt(questions); +}; diff --git a/src/features/storage/index.ts b/src/features/storage/index.ts new file mode 100644 index 0000000..4ebd4ea --- /dev/null +++ b/src/features/storage/index.ts @@ -0,0 +1,33 @@ +import { QuestionCollection } from "inquirer"; +import inquirer = require("inquirer"); + +// Note: use commonJs exports +module.exports = async function(): Promise { + const questions: QuestionCollection = [ + { + type: "input", + name: "name", + message: "Enter your storage account name:", + validate: function(value: string) { + if (value.length) { + return true; + } else { + return "Please enter a valid name."; + } + } + }, + { + type: "input", + name: "sas", + message: "Enter your storage SAS token:", + validate: function(value: string) { + if (value.length) { + return true; + } else { + return "Please enter a valid SAS token."; + } + } + } + ]; + return inquirer.prompt(questions); +}; diff --git a/src/index.ts b/src/index.ts index e0509a7..a780d2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,34 +3,32 @@ import chalk from "chalk"; import clear from "clear"; import figlet from "figlet"; -import { saveProjectConfigToDisk, isProjectFileExists } from "./lib/files"; -import { - askForProjectDetails, - askIfOverrideProjectFile, - askForFeatures -} from "./lib/prompt"; -import { Answers } from "inquirer"; - -const files = require("./lib/files"); +import program from "commander"; clear(); console.log( - chalk.red(figlet.textSync("NITRO", { horizontalLayout: "fitted" })) + chalk.red( + figlet.textSync(" NITRO", { + font: "ANSI Shadow", + horizontalLayout: "full" + }) + ) ); (async () => { - if (isProjectFileExists()) { - const shouldOverrideConfigFile = await askIfOverrideProjectFile(); - if (shouldOverrideConfigFile.override === false) { - process.exit(0); - } + program + .name("nitro") + .usage("") + .version(require("../package.json").version) + .option("--init", "initialise a new workspace") + .option("--login", "connect to your Azure") + .parse(process.argv); + + if (!process.argv.slice(2).length) { + program.outputHelp(); } - const project = await askForProjectDetails(); - const { features } = await askForFeatures(); + const commandName = program.args[0]; - saveProjectConfigToDisk({ - project, - features - }); + (await require(`./commands/${commandName}`))(); })(); diff --git a/src/lib/config.ts b/src/lib/config.ts deleted file mode 100644 index 6e64cdc..0000000 --- a/src/lib/config.ts +++ /dev/null @@ -1 +0,0 @@ -const Configstore = require('configstore'); \ No newline at end of file diff --git a/src/lib/files.ts b/src/lib/files.ts deleted file mode 100644 index 6c5a08c..0000000 --- a/src/lib/files.ts +++ /dev/null @@ -1,22 +0,0 @@ -import fs from "fs"; -import path from "path"; - -export function getCurrentDirectoryBase() { - return path.basename(process.cwd()); -} - -export function directoryExists(filePath: string) { - try { - return fs.statSync(filePath).isDirectory(); - } catch (err) { - return false; - } -} - -export function saveProjectConfigToDisk(config: object) { - fs.writeFileSync("nitro.json", JSON.stringify(config, null, 2)); -} - -export function isProjectFileExists() { - return fs.existsSync("nitro.json"); -} \ No newline at end of file diff --git a/src/lib/prompt.ts b/src/lib/prompt.ts index f24c0ea..679b2da 100644 --- a/src/lib/prompt.ts +++ b/src/lib/prompt.ts @@ -1,6 +1,29 @@ -import inquirer, { QuestionCollection, Answers } from "inquirer"; -import { getCurrentDirectoryBase } from "./files"; -import PromptUI from "inquirer/lib/ui/prompt"; +import inquirer, { Answers, QuestionCollection } from "inquirer"; +import { getCurrentDirectoryBase } from "./utils"; + +export function chooseSubscription(subscriptionsList: any[]): Promise { + const questions: QuestionCollection = [ + { + type: "list", + name: "subscription", + message: "Choose your subscription:", + choices: subscriptionsList.map((sub: AzureSubscription) => { + return { + name: `${sub.name}`, + disabled: sub.state !== "Enabled" + }; + }), + validate: function(value: string) { + if (value.length) { + return true; + } else { + return "Please enter a name for the project."; + } + } + } + ]; + return inquirer.prompt(questions); +} export function askForFeatures(): Promise { const questions: QuestionCollection = [ @@ -8,10 +31,32 @@ export function askForFeatures(): Promise { type: "checkbox", name: "features", message: "Choose the features you want to enable", - choices: [{ - name: "hosting", - checked: true - }], + choices: [ + { + name: "storage", + checked: true, + required: true + }, + { + name: "hosting" + }, + { + name: "functions (coming soon)", + disabled: true + }, + { + name: "database (coming soon)", + disabled: true + }, + { + name: "cdn (coming soon)", + disabled: true + }, + { + name: "auth (coming soon)", + disabled: true + } + ], validate: function(value: string) { if (value.length) { return true; @@ -25,14 +70,12 @@ export function askForFeatures(): Promise { } export function askForProjectDetails(): Promise { - const argv = require("minimist")(process.argv.slice(2)); - const questions: QuestionCollection = [ { type: "input", name: "name", message: "Enter a name for the project:", - default: argv._[0] || getCurrentDirectoryBase(), + default: getCurrentDirectoryBase(), validate: function(value: string) { if (value.length) { return true; @@ -50,7 +93,7 @@ export function askIfOverrideProjectFile(): Promise { { type: "confirm", name: "override", - message: "nitro.json found. Do you want to override it?", + message: "Configuration file found. Do you want to override it?", default: false } ]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..6692ca9 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,88 @@ +import fs from "fs"; +import path from "path"; +const shell = require("shelljs"); +const ora = require("ora"); + +export const WORKSPACE_FILENAME = "nitro.json"; + +export async function runCmd(command: string, loadingMessage?: string): Promise { + let spinner: typeof ora = null; + if (loadingMessage) { + spinner = ora(loadingMessage).start(); + } + + return new Promise((resolve, _reject) => { + const { stdout } = shell.exec(`${command} --output json`, { + silent: true, + async: true + }); + stdout.on("data", (data: string) => { + resolve(data); + + if (spinner) { + spinner.stop(); + } + }); + }); +} + +export async function az(command: string, loadingMessage?: string) { + return await runCmd(`az ${command}`, loadingMessage); +} + +export function getCurrentDirectoryBase() { + return path.basename(process.cwd()); +} + +export function directoryExists(filePath: string) { + try { + return fs.statSync(filePath).isDirectory(); + } catch (err) { + return false; + } +} + +export function createDirectoryIfNotExists(filePath: string) { + if (directoryExists(filePath) === false) { + fs.mkdirSync(filePath); + } + + return true; +} + +export function fileExists(filePath: string) { + try { + return fs.existsSync(filePath); + } catch (err) { + return false; + } +} + +export function readFileFromDisk(filePath: string) { + if (fileExists(filePath)) { + return fs.readFileSync(filePath).toString("utf-8"); + } + return null; +} + +export function saveProjectConfigToDisk(config: object) { + let oldConfig = {}; + if (fileExists(WORKSPACE_FILENAME)) { + oldConfig = JSON.parse(readFileFromDisk(WORKSPACE_FILENAME) || "{}"); + } + fs.writeFileSync( + WORKSPACE_FILENAME, + JSON.stringify( + { + ...oldConfig, + ...config + }, + null, + 2 + ) + ); +} + +export function isProjectFileExists() { + return fileExists(WORKSPACE_FILENAME); +} diff --git a/src/lib/workspace.ts b/src/lib/workspace.ts deleted file mode 100644 index dc867f9..0000000 --- a/src/lib/workspace.ts +++ /dev/null @@ -1 +0,0 @@ -export async function createProjectConfig() {}