Skip to content

Commit

Permalink
tag v1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
m1guelpf committed Apr 21, 2020
1 parent cad819b commit c4d8bd7
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 111 deletions.
10 changes: 10 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
'use strict';
const yargs = require('yargs')
const config = require('./src/config/local')

yargs.scriptName('$ sitesauce')
.usage('$0 <cmd> [args]')
Expand All @@ -11,4 +12,13 @@ yargs.scriptName('$ sitesauce')
.recommendCommands()
.demandCommand(1, '')
.showHelpOnFail()
.onFinishCommand(cleanConfig)
.argv

function cleanConfig() {
if (config.empty()) {
config.deleteFile().removeDir()
} else {
config.addReadme().addGitignore()
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sitesauce-cli",
"version": "0.0.7",
"version": "1.0.0",
"description": "The Sitesauce CLI.",
"license": "MIT",
"repository": "sitesauce/cli",
Expand Down
12 changes: 7 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# The Sitesauce CLI
> Deploy your local sites directly to Sitesauce
> Deploy sites running in your computer directly to Sitesauce
## Motivation
While Sitesauce aims to remove the need for hosting the dynamic version of your website, it still requires you to host it somewhere Sitesauce can acess so we can generate your static sites. The Sitesauce CLI allows you to deploy your sites directly from your computer, removing the requirement of paying for an external server. While this may not be the first option for everyone (as it doesn't allow you to share an admin panel with the rest of your team), it provides for an interesting alternative, and greatly reduces the friction (and cost) of publishing a new website.
While Sitesauce aims to remove the need for hosting the dynamic version of your website, it still requires you to host it somewhere acessible so we can generate your static sites. The Sitesauce CLI allows you to deploy your sites directly from your computer, completely removing the need for servers. While this may not be the first option for everyone (as it doesn't allow you to share an admin panel with the rest of your team), it provides for an interesting alternative, and greatly reduces the friction (and cost) of publishing a new website.

## Install

Expand Down Expand Up @@ -32,11 +32,11 @@ If you want to log out, you can do so by running `$ sitesauce logout`.

## Configuring a project

Projects associate Sitesauce sites and the directories on your machine that contain those sites. To associate a directory with a site, open that directory in your terminal and run `$ sitesauce init`. This will create a `.sitesauce.json` config file that you can add to your `.gitignore` if you don't want to commit (there's no sensitive information, so it shouldn't really matter).
Projects associate Sitesauce sites and the directories on your machine that contain those sites. To associate a directory with a site, open that directory in your terminal and run `$ sitesauce init`. This will create a `.sitesauce` folder with your config. We'll also automatically add that folder to your `.gitignore` if you have one.

## Deploying a project

> NOTE: Make sure you've configured your project before attempting to deploy it.
> NOTE: Make sure you've configured your project deploying it.
To deploy a project, open the project directory and run `$ sitesauce deploy`. This will ask you for the port your application is running in and open a secure tunnel between your computer and our server that will be closed as soon as the deployment is finished.

Expand All @@ -50,6 +50,8 @@ $ sitesauce deploy --port 80 --host laravel.test

You might also have noticed we're specifying the port via the `--port` flag, skipping the port prompt and making the whole process faster.

If you use port 80 but don't specify a host, you'll be asked if you want to use a virtual host.

### Known limitations

If your local server uses a self-signed certificate and forces HTTPS support, there's a chance the deployment will fail. To fix it, you can temporally unsecure your site while you deploy. For example, when deploying a Valet site you've used `valet secure` on, you might have to run `valet unsecure` before deploying.
Expand All @@ -70,4 +72,4 @@ $ sitesauce deploy --help

## License

Licensed under the MIT license. For more information, [check the license file](license).
Licensed under the MIT license. For more information, [check the license file](LICENSE).
15 changes: 8 additions & 7 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class Client {
baseURL: 'https://app.sitesauce.app/api/',
headers: {
Authorization: `Bearer ${config.get('token')}`
}
},

});
}

Expand All @@ -20,19 +21,19 @@ class Client {
}

getTeam() {
return this.client.get('team').then(response => response.data);
return this.client.get(`team/${config.get('teamId')}`).then(response => response.data);
}

getTeams() {
return this.client.get('teams').then(response => response.data);
return this.client.get('user/teams').then(response => response.data);
}

switchTeam(teamId) {
return this.client.post(`teams/switch/${teamId}`).then(response => response.data);
getSites() {
return this.client.get(`team/${config.get('teamId')}/sites`).then(response => response.data);
}

getSites() {
return this.client.get('team/sites').then(response => response.data);
createSite(opts) {
return this.client.post('sites/create', opts).then(response => response.data)
}

createDeployment(siteId, opts) {
Expand Down
11 changes: 10 additions & 1 deletion src/commands/auth/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ const open = require('open');
const uuid = require('uuid/v1');
const hapi = require('@hapi/hapi');
const config = require('../../config/global');
const client = require('../../client');

async function handler() {
const spinner = ora('Logging you in...').start();
const token = await executeAuthFlow();

config.set({token});
config.set('token', token);

try {
const user = await client.getUser()
config.set('teamId', user.current_team_id)
} catch (err) {
config.reset()
return spinner.fail('We had some problems logging you in. Please try again or contact us if the issue persists.')
}

spinner.succeed('Successfully logged in to your Sitesauce account!');
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/auth/logout.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const config = require('../../config/global');

function handler() {
config.delete('token');
config.reset()

console.log('You\'ve been logged out!');
}
Expand Down
5 changes: 2 additions & 3 deletions src/commands/auth/switch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const ora = require('ora')
const inquirer = require('inquirer')
const config = require('../../config/global')
const client = require('./../../client');

async function handler() {
Expand All @@ -16,9 +17,7 @@ async function handler() {
}])

const team = Object.values(teams).filter(team => team.id === teamId)[0]

spinner = ora('Switching to team').start();
await client.switchTeam(teamId)
config.set('teamId', teamId)
spinner.succeed(`Successfully switched to ${team.is_personal ? 'your personal team' : team.name}.`);
}

Expand Down
140 changes: 68 additions & 72 deletions src/commands/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,36 @@ const config = require('./../config/local')
const isPortReachable = require('is-port-reachable')

async function handler(argv) {
if (! config.get('init')) {
config.deleteFile()
console.log('This project has not been configured. Run sitesauce init to configure it.')
process.exit()
}
if (! config.get('siteId')) return ora().fail('This project has not been configured. Run sitesauce init to configure it.')

const port = await getPort(argv)

let port = await validatePort(await getPort(argv))
if (! isPortReachable(port)) return ora().fail('The port you specified is not reachable.')

const baseUrl = getBaseUrl(port, argv.host)
const { baseUrl, host } = await getBaseUrl(port, argv.host)

let spinner = ora('Starting reverse tunnel...').start();
const tunnel = await localtunnel({
port,
host: 'https://tunnel.sitesauce.app',
local_host: argv.host ? argv.host : 'localhost',
allow_invalid_cert: true,
}).catch(() => {
spinner.fail()
console.error('There was an error when opening the reverse tunnel, please try again later.')
process.exit()
});
spinner.succeed('Reverse tunnel started')

spinner = ora('Starting deployment...').start();
let deployment = await startDeployment(tunnel.url, baseUrl)
spinner.succeed('Deployment started')
let tunnel;

deployment = await waitForDeployment(deployment.id, () => {
tunnel.close()
})
try {
tunnel = await localtunnel({
port,
host: 'https://tunnel.sitesauce.app',
local_host: host,
allow_invalid_cert: true,
})

console.log(`Successfully deployed ${baseUrl} to ${deployment.provider}.`)
spinner.succeed('Reverse tunnel started')
} catch {
return spinner.fail('There was an error when opening the reverse tunnel, please try again later.')
}

spinner = ora('Starting deployment...').start();
let deployment = await startDeployment(tunnel.url, baseUrl)
spinner.text = 'Deploying your site...'

process.exit()
await waitForDeployment(deployment.id, spinner, tunnel)
}

async function getPort(argv) {
Expand All @@ -49,21 +45,32 @@ async function getPort(argv) {
type: 'number',
name: 'port',
message: 'What port is this project running its server on?',
default: 8080,
default: 3000,
}]).then(answers => answers.port)
}

async function validatePort(port) {
if (await isPortReachable(port)) return port
async function getBaseUrl(port, host) {
if (host) return { baseUrl : `http://${host}`, host }

console.log('The port you specified is not reachable.')
process.exit(1)
}
if (port !== 80) return { baseUrl: `http://localhost:${port}`, host: 'localhost' }

const { shouldDemandHost } = await inquirer.prompt([{
name: 'shouldDemandHost',
type: 'confirm',
message: "Do you want to specify a virtual host? Do this if you're using something like Laravel Valet to serve your sites",
default: false,
}])

function getBaseUrl(port, host) {
if (host) return `http://${host}`
if (!shouldDemandHost) return { baseUrl: `http://localhost:${port}`, host: 'localhost' }

return `http://localhost:${port}`
host = await inquirer.prompt([{
type: 'input',
name: 'host',
message: 'What virtual host should we connect to? (domain.tld)',
validate: host => host.trim().length > 0 && host.includes('.') && ! host.includes('/')
}]).then(answers => answers.host)

return getBaseUrl(port, host)
}

function startDeployment(tunnelUrl, baseUrl) {
Expand All @@ -73,46 +80,46 @@ function startDeployment(tunnelUrl, baseUrl) {
})
}

function waitForDeployment(deploymentId, stopTunnel) {
function waitForDeployment(deploymentId, spinner, tunnel) {
const deploymentPipeline = [
{
stage: 'site_exists',
spinner: ora('Ensure Site Exists').start()
spinnerText:'Ensuring Site Exists...'
},
{
spinner: ora('Initialize Deployment'),
stage: 'init'
stage: 'init',
spinnerText: 'Initializing Deployment...',
},
{
spinner: ora('Generate Artifact'),
stage: 'generate_artifact'
stage: 'zeit_deploy',
spinnerText: 'Deploying to ZEIT',
},
{
spinner: ora('Upload Artifact'),
stage: 'upload_artifact'
stage: 'zeit_build',
spinnerText: 'Building static site...',
},
{
spinner: ora('Build pages'),
stage: 'provider_build',
stage: 'finalize',
spinnerText: 'Finishing up deployment...',
},
{
spinner: ora('Finalize Deployment'),
stage: 'finalize'
stage: 'done',
spinnerText: 'Cleaning up the tunnel...',
},
]

const stages = deploymentPipeline.map(stage => stage.stage)

let oldStage;

return new Promise((resolve, reject) => {
const request = () => {
client.getDeploymentInfo(config.get('siteId'), deploymentId).then(deployment => {
if (deployment.stage && deployment.stage !== oldStage) stageChanged(oldStage, deployment.stage)
if (deployment.stage) {
if (deployment.stage !== oldStage) stageChangedTo(deployment.stage)

if (deployment.stage && deployment.stage === 'done') resolve(deployment)
if (deployment.stage === 'done') resolve(deployment)

if (deployment.stage && deployment.stage.startsWith('failed_')) reject(deployment)
if (deployment.stage.startsWith('failed_')) reject(deployment)
}

setTimeout(() => {
request()
Expand All @@ -122,32 +129,21 @@ function waitForDeployment(deploymentId, stopTunnel) {
})
}

const stageChanged = (oldStage, newStage) => {
if (newStage.startsWith('failed_')) return;
const stageChangedTo = newStage => {
if (newStage.startsWith('failed_')) return spinner.fail();

deploymentPipeline.filter(stage => {
return parseInt(Object.keys(deploymentPipeline).find(key => deploymentPipeline[key].stage === newStage)) > parseInt(Object.keys(deploymentPipeline).find(key => deploymentPipeline[key].stage === stage.stage))
}).forEach(stage => {
if (stage.spinner.isSpinning) {
stage.spinner.succeed()
}

if (stage.stage === 'generate_artifact') stopTunnel()
})

if(newStage === 'done') return;

deploymentPipeline.find(stage => stage.stage == newStage).spinner.start()
spinner.text = deploymentPipeline.find(stage => stage.stage == newStage).spinnerText
}

request()
}).then(() => {
tunnel.close()

spinner.succeed(`Successfully deployed to Sitesauce.`)
}).catch(deployment => {
if (deployment.stage && deployment.stage.startsWith('failed_')) {
deploymentPipeline.find(stage => stage.stage == deployment.stage.split('failed_').filter(str => str !== '')[0]).spinner.fail()
spinner.fail()
}

console.error('Failed to deploy to Sitesauce')
process.exit()
})
}

Expand All @@ -163,7 +159,7 @@ module.exports = {
host: {
alias: 'h',
type: 'string',
description: 'The host to bind your server to.'
description: 'The virtual host to bind your server to.'
},
},
handler
Expand Down
Loading

0 comments on commit c4d8bd7

Please sign in to comment.