next_on_rails 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +591 -0
- data/Rakefile +32 -0
- data/app/controllers/next_on_rails/application_controller.rb +21 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/concerns/current_resource_serializer.rb +15 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/confirmations_controller.rb +4 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/omniauth_callbacks_controller.rb +7 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/passwords_controller.rb +23 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/registrations_controller.rb +33 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/sessions_controller.rb +27 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/token_validations_controller.rb +13 -0
- data/app/controllers/next_on_rails/devise_token_auth_override/unlocks_controller.rb +23 -0
- data/app/controllers/next_on_rails/merger_controller.rb +36 -0
- data/app/serializers/next_on_rails/active_model_errors_serializer.rb +45 -0
- data/app/serializers/next_on_rails/errors_serializer.rb +16 -0
- data/config/routes.rb +12 -0
- data/lib/generators/nor/backend/backend_generator.rb +129 -0
- data/lib/generators/nor/backend/templates/Procfile +2 -0
- data/lib/generators/nor/backend/templates/application_controller.rb +2 -0
- data/lib/generators/nor/backend/templates/cors.rb +23 -0
- data/lib/generators/nor/backend/templates/current_user_serializer.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user.rb.tt +8 -0
- data/lib/generators/nor/backend/templates/user_migration.rb.tt +53 -0
- data/lib/generators/nor/backend/templates/user_serializer.rb.tt +11 -0
- data/lib/generators/nor/frontend/frontend_generator.rb +60 -0
- data/lib/generators/nor/frontend/templates/frontend/components/flash.js +29 -0
- data/lib/generators/nor/frontend/templates/frontend/components/inputs.js +69 -0
- data/lib/generators/nor/frontend/templates/frontend/components/login-form.js +23 -0
- data/lib/generators/nor/frontend/templates/frontend/components/registration-form.js +49 -0
- data/lib/generators/nor/frontend/templates/frontend/next-on-rails.config.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/next.config.js +20 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/_app.js +3 -0
- data/lib/generators/nor/frontend/templates/frontend/pages/index.js +57 -0
- data/lib/generators/nor/frontend/templates/frontend/server.js +31 -0
- data/lib/generators/nor/frontend/templates/frontend/stylesheets/application.scss +2 -0
- data/lib/generators/nor/install/USAGE +30 -0
- data/lib/generators/nor/install/install_generator.rb +8 -0
- data/lib/generators/nor/scaffold/USAGE +10 -0
- data/lib/generators/nor/scaffold/scaffold_generator.rb +53 -0
- data/lib/generators/nor/scaffold/templates/controller.rb.tt +40 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/details.js.tt +20 -0
- data/lib/generators/nor/scaffold/templates/frontend/components/form.js.tt +17 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/crud.js.tt +123 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/edit.js.tt +55 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/index.js.tt +88 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/new.js.tt +51 -0
- data/lib/generators/nor/scaffold/templates/frontend/pages/show.js.tt +43 -0
- data/lib/next_on_rails.rb +12 -0
- data/lib/next_on_rails/engine.rb +5 -0
- data/lib/next_on_rails/version.rb +3 -0
- data/lib/tasks/next_on_rails_tasks.rake +4 -0
- metadata +235 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Avoid CORS issues when API is called from the frontend app.
|
4
|
+
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
|
5
|
+
|
6
|
+
# Read more: https://github.com/cyu/rack-cors
|
7
|
+
|
8
|
+
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
9
|
+
allow do
|
10
|
+
origins 'localhost:3001'
|
11
|
+
|
12
|
+
resource '*',
|
13
|
+
headers: :any,
|
14
|
+
methods: [:get, :post, :put, :patch, :delete, :options, :head],
|
15
|
+
expose: [
|
16
|
+
DeviseTokenAuth.headers_names[:'access-token'],
|
17
|
+
DeviseTokenAuth.headers_names[:client],
|
18
|
+
DeviseTokenAuth.headers_names[:expiry],
|
19
|
+
DeviseTokenAuth.headers_names[:uid],
|
20
|
+
DeviseTokenAuth.headers_names[:'token-type']
|
21
|
+
]
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class <%= user_class %> < ApplicationRecord
|
2
|
+
# Include default devise modules. Others available are:
|
3
|
+
# :timeoutable
|
4
|
+
devise :database_authenticatable, :registerable,
|
5
|
+
:recoverable, :rememberable, :trackable, :validatable,
|
6
|
+
:confirmable, :lockable, :omniauthable
|
7
|
+
include DeviseTokenAuth::Concerns::<%= user_class %>
|
8
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
create_table(:<%= user_class.constantize.table_name %>) do |t|
|
4
|
+
## User Info
|
5
|
+
t.string :email
|
6
|
+
|
7
|
+
## Required
|
8
|
+
t.string :provider, null: false, default: 'email'
|
9
|
+
t.string :uid, null: false, default: ''
|
10
|
+
|
11
|
+
## Database authenticatable
|
12
|
+
t.string :encrypted_password, null: false, default: ''
|
13
|
+
|
14
|
+
## Recoverable
|
15
|
+
t.string :reset_password_token
|
16
|
+
t.datetime :reset_password_sent_at
|
17
|
+
t.boolean :allow_password_change, default: false
|
18
|
+
|
19
|
+
## Rememberable
|
20
|
+
t.datetime :remember_created_at
|
21
|
+
|
22
|
+
## Trackable
|
23
|
+
t.integer :sign_in_count, default: 0, null: false
|
24
|
+
t.datetime :current_sign_in_at
|
25
|
+
t.datetime :last_sign_in_at
|
26
|
+
t.string :current_sign_in_ip
|
27
|
+
t.string :last_sign_in_ip
|
28
|
+
|
29
|
+
## Confirmable
|
30
|
+
t.string :confirmation_token
|
31
|
+
t.datetime :confirmed_at
|
32
|
+
t.datetime :confirmation_sent_at
|
33
|
+
t.string :unconfirmed_email # Only if using reconfirmable
|
34
|
+
|
35
|
+
## Lockable
|
36
|
+
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
|
37
|
+
t.string :unlock_token # Only if unlock strategy is :email or :both
|
38
|
+
t.datetime :locked_at
|
39
|
+
|
40
|
+
## Tokens
|
41
|
+
t.text :tokens
|
42
|
+
|
43
|
+
t.timestamps
|
44
|
+
end
|
45
|
+
|
46
|
+
add_index :users, :email, unique: true
|
47
|
+
add_index :users, :uid, unique: true
|
48
|
+
add_index :users, [:uid, :provider], unique: true
|
49
|
+
add_index :users, :reset_password_token, unique: true
|
50
|
+
add_index :users, :confirmation_token, unique: true
|
51
|
+
add_index :users, :unlock_token, unique: true
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= user_class %>Serializer
|
2
|
+
include FastJsonapi::ObjectSerializer
|
3
|
+
|
4
|
+
set_type :<%= user_class.underscore %>
|
5
|
+
set_id :id
|
6
|
+
attributes :email
|
7
|
+
|
8
|
+
# link :<%= user_class.underscore %>_url do |<%= user_class.underscore %>|
|
9
|
+
# Rails.application.routes.url_helpers.<%= user_class.underscore %>_url(<%= user_class.underscore %>, host: 'http://localhost:3000')
|
10
|
+
# end
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Nor
|
2
|
+
class FrontendGenerator < Rails::Generators::Base
|
3
|
+
attr_reader :frontend_path
|
4
|
+
source_root File.expand_path('templates', __dir__)
|
5
|
+
|
6
|
+
def initialize(args, *options)
|
7
|
+
super
|
8
|
+
@frontend_path = File.join(destination_root, 'frontend')
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_next_app
|
12
|
+
if revoke? && frontend_exist?
|
13
|
+
say_status :remove, relative_to_original_destination_root(frontend_path)
|
14
|
+
FileUtils.remove_dir(frontend_path, force: true)
|
15
|
+
elsif frontend_exist?
|
16
|
+
say_status('skipped', 'Create Next app: directory frontend already exist!')
|
17
|
+
else
|
18
|
+
run 'yarn create next-app frontend'
|
19
|
+
inside frontend_path do
|
20
|
+
gsub_file 'package.json', 'create-next-example-app', 'next-on-rails-frontend'
|
21
|
+
gsub_file 'package.json', '"dev": "next dev"', '"dev": "node server.js"'
|
22
|
+
if `which json`.present?
|
23
|
+
run "json -I -f package.json -e 'this.license=\"MIT\"'"
|
24
|
+
run "json -I -f package.json -e 'this.private=true'"
|
25
|
+
run "json -I -f package.json -e 'this.version=\"0.1.0\"'"
|
26
|
+
end
|
27
|
+
remove_file 'components/head.js'
|
28
|
+
remove_file 'components/nav.js'
|
29
|
+
remove_file 'pages/index.js'
|
30
|
+
remove_file 'next.config.js'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def install_dependencies
|
36
|
+
return if revoke?
|
37
|
+
|
38
|
+
inside frontend_path do
|
39
|
+
run 'yarn add next-compose-plugins @zeit/next-sass node-sass bootstrap compression express inflection lodash nookies nprogress next-on-rails next@8.1.0 react@16.8.6 react-dom@16.8.6'
|
40
|
+
run 'yarn add --dev babel-eslint eslint eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-react eslint-plugin-standard prettier sass-lint'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def install_templates
|
45
|
+
return if revoke?
|
46
|
+
|
47
|
+
directory 'frontend'
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def frontend_exist?
|
53
|
+
File.exist?(frontend_path)
|
54
|
+
end
|
55
|
+
|
56
|
+
def revoke?
|
57
|
+
behavior == :revoke
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { useFlash } from 'next-on-rails/utils'
|
2
|
+
|
3
|
+
const Flash = () => {
|
4
|
+
const { flash } = useFlash()
|
5
|
+
if (flash) {
|
6
|
+
return Object.entries(flash)
|
7
|
+
.filter(([key, value]) => value && value !== '')
|
8
|
+
.map(([key, value]) => (
|
9
|
+
<div key={key} className={`alert alert-${alertClass(key)}`}>
|
10
|
+
{value}
|
11
|
+
</div>
|
12
|
+
))
|
13
|
+
} else {
|
14
|
+
return null
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
const alertClass = flashType => {
|
19
|
+
switch (flashType) {
|
20
|
+
case 'notice':
|
21
|
+
return 'success'
|
22
|
+
case 'alert':
|
23
|
+
return 'danger'
|
24
|
+
default:
|
25
|
+
return 'info'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
export default Flash
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import { dasherize, titleize } from 'inflection'
|
2
|
+
|
3
|
+
const Input = ({ name, value, label, error }) => {
|
4
|
+
const id = `input-${dasherize(name).toLowerCase()}`
|
5
|
+
const className = `form-control ${error ? 'is-invalid' : null}`
|
6
|
+
if (label === undefined) label = titleize(name)
|
7
|
+
return (
|
8
|
+
<div className="form-group">
|
9
|
+
{label ? <label htmlFor={id}>{label}</label> : null}
|
10
|
+
<input type="text" id={id} className={className} name={name} defaultValue={value} />
|
11
|
+
{error ? <div className="invalid-feedback">{error}</div> : null}
|
12
|
+
</div>
|
13
|
+
)
|
14
|
+
}
|
15
|
+
|
16
|
+
const TextArea = ({ name, value, label, error }) => {
|
17
|
+
const id = `input-${dasherize(name).toLowerCase()}`
|
18
|
+
const className = `form-control ${error ? 'is-invalid' : null}`
|
19
|
+
if (label === undefined) label = titleize(name)
|
20
|
+
return (
|
21
|
+
<div className="form-group">
|
22
|
+
{label ? <label htmlFor={id}>{label}</label> : null}
|
23
|
+
<textarea id={id} className={className} name={name} defaultValue={value}></textarea>
|
24
|
+
{error ? <div className="invalid-feedback">{error}</div> : null}
|
25
|
+
</div>
|
26
|
+
)
|
27
|
+
}
|
28
|
+
|
29
|
+
const CheckBox = ({ name, value, label, error }) => {
|
30
|
+
const id = `input-${dasherize(name).toLowerCase()}`
|
31
|
+
const className = `form-check-input ${error ? 'is-invalid' : null}`
|
32
|
+
if (label === undefined) label = titleize(name)
|
33
|
+
return (
|
34
|
+
<div className="form-group form-check">
|
35
|
+
<input type="hidden" name={name} defaultValue={false} />
|
36
|
+
<input type="checkbox" id={id} className={className} name={name} defaultChecked={value} />
|
37
|
+
{error ? <div className="invalid-feedback">{error}</div> : null}
|
38
|
+
{label ? (
|
39
|
+
<label htmlFor={id} className="form-post-label">
|
40
|
+
{label}
|
41
|
+
</label>
|
42
|
+
) : null}
|
43
|
+
</div>
|
44
|
+
)
|
45
|
+
}
|
46
|
+
|
47
|
+
const Select = ({ name, value, label, error, collection, selectPlaceholder = 'Select one...' }) => {
|
48
|
+
const id = `input-${dasherize(name).toLowerCase()}`
|
49
|
+
const className = `form-control ${error ? 'is-invalid' : null}`
|
50
|
+
if (label === undefined) label = titleize(name)
|
51
|
+
return (
|
52
|
+
<div className="form-group">
|
53
|
+
{label ? <label htmlFor={id}>{label}</label> : null}
|
54
|
+
<select id={id} className={className} name={name} defaultValue={value}>
|
55
|
+
{selectPlaceholder ? <option>{selectPlaceholder}</option> : null}
|
56
|
+
{collection.map(([value, display]) => (
|
57
|
+
<option key={value} value={value}>
|
58
|
+
{display}
|
59
|
+
</option>
|
60
|
+
))}
|
61
|
+
</select>
|
62
|
+
{error ? <div className="invalid-feedback">{error}</div> : null}
|
63
|
+
</div>
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
const HiddenIdField = ({ id }) => (id ? <input type="hidden" name="id" defaultValue={id} /> : null)
|
68
|
+
|
69
|
+
export { Input, TextArea, CheckBox, Select, HiddenIdField }
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { useLoginForm } from 'next-on-rails/devise'
|
2
|
+
|
3
|
+
const LoginForm = ({ successCallback, errorCallback }) => {
|
4
|
+
const login = useLoginForm(successCallback, errorCallback)
|
5
|
+
|
6
|
+
return (
|
7
|
+
<form onSubmit={login}>
|
8
|
+
<div className="form-group">
|
9
|
+
<label htmlFor="form-login-email">Email</label>
|
10
|
+
<input id="form-login-email" type="text" name="email" className="form-control" />
|
11
|
+
</div>
|
12
|
+
<div className="form-group">
|
13
|
+
<label htmlFor="form-login-password">Password</label>
|
14
|
+
<input id="form-login-password" type="password" name="password" className="form-control" />
|
15
|
+
</div>
|
16
|
+
<button type="submit" className="btn btn-primary">
|
17
|
+
Login
|
18
|
+
</button>
|
19
|
+
</form>
|
20
|
+
)
|
21
|
+
}
|
22
|
+
|
23
|
+
export default LoginForm
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { useRegistrationForm } from 'next-on-rails/devise'
|
2
|
+
|
3
|
+
const RegistrationForm = ({ successCallback, errorCallback }) => {
|
4
|
+
const [register, getError] = useRegistrationForm(successCallback, errorCallback)
|
5
|
+
|
6
|
+
return (
|
7
|
+
<form onSubmit={register}>
|
8
|
+
<div className="form-group">
|
9
|
+
<label htmlFor="form-registration-email">Email</label>
|
10
|
+
<input
|
11
|
+
id="form-registration-email"
|
12
|
+
type="text"
|
13
|
+
name="email"
|
14
|
+
className={`form-control ${getError('email') ? 'is-invalid' : null}`}
|
15
|
+
/>
|
16
|
+
{getError('email') ? <div className="invalid-feedback">{getError('email')}</div> : null}
|
17
|
+
</div>
|
18
|
+
<div className="form-group">
|
19
|
+
<label htmlFor="form-registration-password">Password</label>
|
20
|
+
<input
|
21
|
+
id="form-registration-password"
|
22
|
+
type="password"
|
23
|
+
name="password"
|
24
|
+
className={`form-control ${getError('password') ? 'is-invalid' : null}`}
|
25
|
+
defaultValue="qwerty"
|
26
|
+
/>
|
27
|
+
{getError('password') ? <div className="invalid-feedback">{getError('password')}</div> : null}
|
28
|
+
</div>
|
29
|
+
<div className="form-group">
|
30
|
+
<label htmlFor="form-registration-password-confirmation">Confirm Password</label>
|
31
|
+
<input
|
32
|
+
id="form-registration-password-confirmation"
|
33
|
+
type="password"
|
34
|
+
name="password_confirmation"
|
35
|
+
className={`form-control ${getError('password') ? 'is-invalid' : null}`}
|
36
|
+
defaultValue="qwerty"
|
37
|
+
/>
|
38
|
+
{getError('password_confirmation') ? (
|
39
|
+
<div className="invalid-feedback">{getError('password_confirmation')}</div>
|
40
|
+
) : null}
|
41
|
+
</div>
|
42
|
+
<button type="submit" className="btn btn-primary">
|
43
|
+
Register
|
44
|
+
</button>
|
45
|
+
</form>
|
46
|
+
)
|
47
|
+
}
|
48
|
+
|
49
|
+
export default RegistrationForm
|
@@ -0,0 +1,20 @@
|
|
1
|
+
const withPlugins = require('next-compose-plugins')
|
2
|
+
const withSass = require('@zeit/next-sass')
|
3
|
+
// const optimizedImages = require('next-optimized-images')
|
4
|
+
|
5
|
+
module.exports = withPlugins(
|
6
|
+
[
|
7
|
+
[withSass, {}]
|
8
|
+
// [optimizedImages, {}],
|
9
|
+
// [exportPathMap, {
|
10
|
+
// '/': { page: '/' }
|
11
|
+
// }]
|
12
|
+
],
|
13
|
+
{
|
14
|
+
webpack: config => {
|
15
|
+
// Fixes npm packages that depend on `fs` module
|
16
|
+
config.node = { fs: 'empty' }
|
17
|
+
return config
|
18
|
+
}
|
19
|
+
}
|
20
|
+
)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { useCurrentUser } from 'next-on-rails/current-user'
|
2
|
+
import { useLogout } from 'next-on-rails/devise'
|
3
|
+
import { getDetailFromResponseError, useFlash } from 'next-on-rails/utils'
|
4
|
+
|
5
|
+
import Flash from '../components/flash'
|
6
|
+
import LoginForm from '../components/login-form'
|
7
|
+
import RegistrationForm from '../components/registration-form'
|
8
|
+
|
9
|
+
const Home = props => {
|
10
|
+
const { setFlash } = useFlash()
|
11
|
+
const logout = useLogout()
|
12
|
+
const { currentUser } = useCurrentUser()
|
13
|
+
|
14
|
+
const loginSuccessCallback = response => {
|
15
|
+
setFlash({ notice: `Welcome ${response.data.email}!` })
|
16
|
+
}
|
17
|
+
|
18
|
+
const loginErrorCallback = error => {
|
19
|
+
setFlash({ alert: getDetailFromResponseError(error) })
|
20
|
+
}
|
21
|
+
|
22
|
+
const registrationSuccessCallback = response => {
|
23
|
+
setFlash({
|
24
|
+
notice: `Welcome ${response.data.email}! An email to confirm your account has been sent to this email address.`
|
25
|
+
})
|
26
|
+
}
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className="container pt-4">
|
30
|
+
<Flash />
|
31
|
+
<h1>Welcome to Next On Rails!</h1>
|
32
|
+
|
33
|
+
<img src="https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif" alt="Magic" />
|
34
|
+
|
35
|
+
<p>{currentUser ? `Hi, ${currentUser.email}` : 'You are not logged'}</p>
|
36
|
+
|
37
|
+
{currentUser ? (
|
38
|
+
<button className="btn btn-primary" onClick={logout}>
|
39
|
+
Logout
|
40
|
+
</button>
|
41
|
+
) : (
|
42
|
+
<div className="row">
|
43
|
+
<div className="col">
|
44
|
+
<h6>Login</h6>
|
45
|
+
<LoginForm successCallback={loginSuccessCallback} errorCallback={loginErrorCallback} />
|
46
|
+
</div>
|
47
|
+
<div className="col">
|
48
|
+
<h6>Register</h6>
|
49
|
+
<RegistrationForm successCallback={registrationSuccessCallback} />
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
)}
|
53
|
+
</div>
|
54
|
+
)
|
55
|
+
}
|
56
|
+
|
57
|
+
export default Home
|
@@ -0,0 +1,31 @@
|
|
1
|
+
const express = require('express')
|
2
|
+
const next = require('next')
|
3
|
+
const compression = require('compression')
|
4
|
+
|
5
|
+
const dev = process.env.NODE_ENV !== 'production'
|
6
|
+
const app = next({ dev })
|
7
|
+
const handle = app.getRequestHandler()
|
8
|
+
|
9
|
+
app
|
10
|
+
.prepare()
|
11
|
+
.then(() => {
|
12
|
+
const server = express()
|
13
|
+
server.use(compression())
|
14
|
+
|
15
|
+
// server.get('/post/:id', (req, res) => {
|
16
|
+
// app.render(req, res, '/post', { id: req.params.id })
|
17
|
+
// })
|
18
|
+
|
19
|
+
server.get('*', (req, res) => {
|
20
|
+
return handle(req, res)
|
21
|
+
})
|
22
|
+
|
23
|
+
server.listen(3001, err => {
|
24
|
+
if (err) throw err
|
25
|
+
console.log('> Ready on http://localhost:3001')
|
26
|
+
})
|
27
|
+
})
|
28
|
+
.catch(ex => {
|
29
|
+
console.error(ex.stack)
|
30
|
+
process.exit(1)
|
31
|
+
})
|