@uscreen.de/create-fastify-app 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml.example +15 -0
- package/bin/cli.js +149 -10
- package/package.json +5 -2
- package/skeleton/.env.example +4 -0
- package/skeleton/.gitlab-ci.yml.example +15 -0
- package/skeleton/Makefile +29 -0
- package/skeleton/app/app.js +19 -0
- package/skeleton/app/config.js +47 -0
- package/skeleton/app/plugins/noop.js +11 -0
- package/skeleton/app/schemas.js +41 -0
- package/skeleton/app/server.js +28 -0
- package/skeleton/app/services/noop.js +7 -0
- package/skeleton/package.json +47 -0
- package/skeleton/pm2-dev.config.js +16 -0
- package/skeleton/pm2.config.js +28 -0
- package/skeleton/test/helper.js +28 -0
- package/skeleton/test/noop.test.js +54 -0
package/bin/cli.js
CHANGED
|
@@ -1,23 +1,162 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const path = require('path')
|
|
3
4
|
const cli = require('commander')
|
|
5
|
+
const readPkgUp = require('read-pkg-up')
|
|
6
|
+
const writePackage = require('write-pkg')
|
|
7
|
+
const fs = require('fs-extra')
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
let optValue
|
|
9
|
+
const { spawn } = require('child_process')
|
|
7
10
|
|
|
11
|
+
let root
|
|
12
|
+
let skeleton
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* init new git
|
|
16
|
+
*/
|
|
17
|
+
const initializeGitRepository = path =>
|
|
18
|
+
new Promise((resolve, reject) => {
|
|
19
|
+
const git = spawn('git', ['init', path])
|
|
20
|
+
git.stdout.on('data', data => process.stdout.write(data))
|
|
21
|
+
git.stderr.on('data', data => process.stderr.write(data))
|
|
22
|
+
git.on('close', code => {
|
|
23
|
+
if (code === 0) return resolve(code)
|
|
24
|
+
reject(code)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* init new yarn project
|
|
30
|
+
*/
|
|
31
|
+
const initializeYarn = path =>
|
|
32
|
+
new Promise((resolve, reject) => {
|
|
33
|
+
const yarn = spawn('yarn', ['init'], {
|
|
34
|
+
cwd: path,
|
|
35
|
+
stdio: [0, 1, 2]
|
|
36
|
+
})
|
|
37
|
+
yarn.on('close', code => {
|
|
38
|
+
if (code === 0) return resolve(code)
|
|
39
|
+
reject(code)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* install extra dev packages from skeleleton
|
|
45
|
+
*/
|
|
46
|
+
const installDevPackages = (appPath, skelPath) => {
|
|
47
|
+
const skelPack = readPkgUp.sync({ cwd: skelPath })
|
|
48
|
+
const devDependencies = Object.keys(skelPack.packageJson.devDependencies)
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const yarn = spawn('yarn', ['add', ...devDependencies, '-D'], {
|
|
51
|
+
cwd: appPath
|
|
52
|
+
})
|
|
53
|
+
yarn.stdout.on('data', data => process.stdout.write(data))
|
|
54
|
+
yarn.stderr.on('data', data => process.stderr.write(data))
|
|
55
|
+
yarn.on('close', code => {
|
|
56
|
+
if (code === 0) return resolve(code)
|
|
57
|
+
reject(code)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* install extra prod packages from skeleleton
|
|
64
|
+
*/
|
|
65
|
+
const installPackages = (appPath, skelPath) => {
|
|
66
|
+
const skelPack = readPkgUp.sync({ cwd: skelPath })
|
|
67
|
+
const dependencies = Object.keys(skelPack.packageJson.dependencies)
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const yarn = spawn('yarn', ['add', ...dependencies], {
|
|
70
|
+
cwd: appPath
|
|
71
|
+
})
|
|
72
|
+
yarn.stdout.on('data', data => process.stdout.write(data))
|
|
73
|
+
yarn.stderr.on('data', data => process.stderr.write(data))
|
|
74
|
+
yarn.on('close', code => {
|
|
75
|
+
if (code === 0) return resolve(code)
|
|
76
|
+
reject(code)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* configure package.json to use linting, testing, stuff
|
|
83
|
+
*/
|
|
84
|
+
const addPackageConfig = (path, skelPath) => {
|
|
85
|
+
const skelPack = readPkgUp.sync({ cwd: skelPath })
|
|
86
|
+
const pack = readPkgUp.sync({ cwd: path })
|
|
87
|
+
delete pack.packageJson._id
|
|
88
|
+
delete pack.packageJson.readme
|
|
89
|
+
|
|
90
|
+
pack.packageJson.main = skelPack.packageJson.main
|
|
91
|
+
|
|
92
|
+
pack.packageJson.scripts = Object.assign(pack.packageJson.scripts || {}, {
|
|
93
|
+
start: 'pm2 start pm2-dev.config.js',
|
|
94
|
+
stop: 'pm2 delete pm2-dev.config.js',
|
|
95
|
+
logs: `pm2 logs ${pack.packageJson.name} --raw | pino-pretty -t`,
|
|
96
|
+
lint: "eslint '**/*.js' --fix",
|
|
97
|
+
test: 'tap test/**/*.test.js',
|
|
98
|
+
'test:cov': 'tap --coverage-report=html test/**/*.test.js',
|
|
99
|
+
'test:ci': 'tap --coverage-report=text-summary test/**/*.test.js',
|
|
100
|
+
deploy: 'pm2 deploy pm2.config.js',
|
|
101
|
+
postdeploy: 'pm2 reload pm2.config.js'
|
|
102
|
+
})
|
|
103
|
+
pack.packageJson.gitHooks = {
|
|
104
|
+
'pre-commit': 'lint-staged'
|
|
105
|
+
}
|
|
106
|
+
pack.packageJson['lint-staged'] = {
|
|
107
|
+
'*.{js}': ['eslint --fix', 'git add']
|
|
108
|
+
}
|
|
109
|
+
return writePackage(pack.path, pack.packageJson)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* copy app skeleton to destination
|
|
114
|
+
*/
|
|
115
|
+
const copySkeleton = (appPath, skelPath) => {
|
|
116
|
+
return fs.copy(skelPath, appPath, { overwrite: false })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* define the command
|
|
121
|
+
*/
|
|
8
122
|
cli
|
|
9
123
|
.version('0.1.0')
|
|
10
124
|
.arguments('<name> [opt]')
|
|
11
|
-
.action(
|
|
12
|
-
|
|
13
|
-
|
|
125
|
+
.action(async (name, opt) => {
|
|
126
|
+
if (typeof name === 'undefined') {
|
|
127
|
+
console.error('please specify your new apps name...')
|
|
128
|
+
process.exit(1)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* the root directory of new project
|
|
133
|
+
*/
|
|
134
|
+
root = path.resolve(process.cwd(), name)
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* the root directory of chosen skeleton
|
|
138
|
+
*/
|
|
139
|
+
skeleton = path.join(__dirname, '..', 'skeleton')
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* setup new app
|
|
143
|
+
*/
|
|
144
|
+
await initializeGitRepository(root)
|
|
145
|
+
await initializeYarn(root)
|
|
146
|
+
await installDevPackages(root, skeleton)
|
|
147
|
+
await installPackages(root, skeleton)
|
|
148
|
+
await addPackageConfig(root, skeleton)
|
|
149
|
+
await copySkeleton(root, skeleton)
|
|
14
150
|
})
|
|
15
151
|
|
|
152
|
+
/**
|
|
153
|
+
* read args
|
|
154
|
+
*/
|
|
16
155
|
cli.parse(process.argv)
|
|
17
156
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
157
|
+
/**
|
|
158
|
+
* output help as default
|
|
159
|
+
*/
|
|
160
|
+
if (!process.argv.slice(2).length) {
|
|
161
|
+
cli.help()
|
|
21
162
|
}
|
|
22
|
-
console.log('name:', nameValue)
|
|
23
|
-
console.log('opt:', optValue)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uscreen.de/create-fastify-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "cli to create a new @uscreen.de/fastify-app",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
]
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"commander": "^3.0.2"
|
|
28
|
+
"commander": "^3.0.2",
|
|
29
|
+
"fs-extra": "^8.1.0",
|
|
30
|
+
"read-pkg-up": "^7.0.0",
|
|
31
|
+
"write-pkg": "^4.0.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"eslint": "^6.5.1",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
start:
|
|
2
|
+
yarn
|
|
3
|
+
yarn start
|
|
4
|
+
|
|
5
|
+
stop:
|
|
6
|
+
yarn stop
|
|
7
|
+
|
|
8
|
+
logs:
|
|
9
|
+
yarn logs
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
yarn test
|
|
13
|
+
|
|
14
|
+
test.coverage:
|
|
15
|
+
yarn test:cov
|
|
16
|
+
|
|
17
|
+
deploy:
|
|
18
|
+
yarn deploy stage
|
|
19
|
+
|
|
20
|
+
deploy.setup:
|
|
21
|
+
yarn deploy stage setup
|
|
22
|
+
|
|
23
|
+
live.deploy:
|
|
24
|
+
yarn deploy live
|
|
25
|
+
|
|
26
|
+
live.deploy.setup:
|
|
27
|
+
yarn deploy live setup
|
|
28
|
+
|
|
29
|
+
.PHONY: test
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fastifyApp = require('@uscreen.de/fastify-app')
|
|
4
|
+
const fp = require('fastify-plugin')
|
|
5
|
+
const schemas = require('./schemas')
|
|
6
|
+
|
|
7
|
+
module.exports = fp(async (fastify, opts, next) => {
|
|
8
|
+
/**
|
|
9
|
+
* add schemas
|
|
10
|
+
*/
|
|
11
|
+
fastify.register(schemas)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* register app
|
|
15
|
+
*/
|
|
16
|
+
fastify.register(fastifyApp, opts)
|
|
17
|
+
|
|
18
|
+
next()
|
|
19
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const envSchema = require('env-schema')
|
|
5
|
+
|
|
6
|
+
const schema = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
httpPort: {
|
|
10
|
+
default: 3000
|
|
11
|
+
},
|
|
12
|
+
httpBind: {
|
|
13
|
+
default: '127.0.0.1'
|
|
14
|
+
},
|
|
15
|
+
prefix: {
|
|
16
|
+
default: '/api'
|
|
17
|
+
},
|
|
18
|
+
logEnabled: {
|
|
19
|
+
default: true
|
|
20
|
+
},
|
|
21
|
+
logLevel: {
|
|
22
|
+
default: 'info'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config = envSchema({
|
|
28
|
+
schema: schema,
|
|
29
|
+
dotenv: true
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
config.autoloads = [
|
|
33
|
+
path.join(__dirname, 'plugins'),
|
|
34
|
+
path.join(__dirname, 'services')
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
config.swagger = {
|
|
38
|
+
routePrefix: '/api/docs',
|
|
39
|
+
exposeRoute: true,
|
|
40
|
+
addModels: true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
config.healthCheck = {
|
|
44
|
+
exposeStatusRoute: '/api/health'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = config
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fp = require('fastify-plugin')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Usage of the Globaly Shared Schema feature
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = fp(async (fastify, opts, next) => {
|
|
10
|
+
fastify.addSchema({
|
|
11
|
+
$id: 'http200',
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
statusCode: {
|
|
15
|
+
type: 'integer',
|
|
16
|
+
example: 200
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
fastify.addSchema({
|
|
22
|
+
$id: 'http404',
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
statusCode: {
|
|
26
|
+
type: 'integer',
|
|
27
|
+
example: 404
|
|
28
|
+
},
|
|
29
|
+
error: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
example: 'Not Found'
|
|
32
|
+
},
|
|
33
|
+
message: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
example: 'Not Found'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
next()
|
|
41
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fastify = require('fastify')
|
|
4
|
+
const config = require('./config')
|
|
5
|
+
const myapp = require('./app')
|
|
6
|
+
|
|
7
|
+
const app = fastify({
|
|
8
|
+
logger: config.logEnabled
|
|
9
|
+
? {
|
|
10
|
+
level: config.logLevel
|
|
11
|
+
}
|
|
12
|
+
: false
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
app.register(myapp, config)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* post-treatment
|
|
19
|
+
*/
|
|
20
|
+
app.ready(err => {
|
|
21
|
+
if (err) throw err
|
|
22
|
+
app.log.debug('Application ready, routes are set:\n' + app.printRoutes())
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* start http server
|
|
27
|
+
*/
|
|
28
|
+
app.listen(config.httpPort, config.httpBind)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "new-fastify-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"main": "app/server.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@uscreen.de/fastify-app": "^0.2.9",
|
|
8
|
+
"env-schema": "^1.0.0",
|
|
9
|
+
"fastify": "^2.9.0",
|
|
10
|
+
"fastify-plugin": "^1.6.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"eslint": "^6.5.1",
|
|
14
|
+
"eslint-config-prettier": "^6.3.0",
|
|
15
|
+
"eslint-config-standard": "^14.1.0",
|
|
16
|
+
"eslint-plugin-import": "^2.18.2",
|
|
17
|
+
"eslint-plugin-node": "^10.0.0",
|
|
18
|
+
"eslint-plugin-prettier": "^3.1.1",
|
|
19
|
+
"eslint-plugin-promise": "^4.2.1",
|
|
20
|
+
"eslint-plugin-standard": "^4.0.1",
|
|
21
|
+
"lint-staged": "^9.4.1",
|
|
22
|
+
"pm2": "^3.5.1",
|
|
23
|
+
"prettier": "^1.18.2",
|
|
24
|
+
"tap": "^14.6.9",
|
|
25
|
+
"yorkie": "^2.0.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "pm2 start pm2-dev.config.js",
|
|
29
|
+
"stop": "pm2 delete pm2-dev.config.js",
|
|
30
|
+
"logs": "pm2 logs new-fastify-app --raw | pino-pretty -t",
|
|
31
|
+
"lint": "eslint '**/*.js' --fix",
|
|
32
|
+
"test": "tap test/**/*.test.js",
|
|
33
|
+
"test:cov": "tap --coverage-report=html test/**/*.test.js",
|
|
34
|
+
"test:ci": "tap --coverage-report=text-summary test/**/*.test.js",
|
|
35
|
+
"deploy": "pm2 deploy pm2.config.js",
|
|
36
|
+
"postdeploy": "pm2 reload pm2.config.js"
|
|
37
|
+
},
|
|
38
|
+
"gitHooks": {
|
|
39
|
+
"pre-commit": "lint-staged"
|
|
40
|
+
},
|
|
41
|
+
"lint-staged": {
|
|
42
|
+
"*.{js}": [
|
|
43
|
+
"eslint --fix",
|
|
44
|
+
"git add"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { name, main, repository } = require('./package.json')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
apps: [
|
|
5
|
+
{
|
|
6
|
+
name,
|
|
7
|
+
script: main,
|
|
8
|
+
merge_logs: true,
|
|
9
|
+
ignore_watch: ['.git', 'app/*.pid'],
|
|
10
|
+
env: {
|
|
11
|
+
NODE_ENV: 'production'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
deploy: {
|
|
16
|
+
stage: {
|
|
17
|
+
user: 'user',
|
|
18
|
+
host: 'server-stage.example.com',
|
|
19
|
+
ref: 'origin/master',
|
|
20
|
+
repo: repository,
|
|
21
|
+
path: `/home/user/${name}`,
|
|
22
|
+
'pre-setup': 'yarn add pm2 pino-pretty;',
|
|
23
|
+
'post-setup':
|
|
24
|
+
'cp ./.env.example ../shared/.env; ln -s ../shared/.env ./.env',
|
|
25
|
+
'post-deploy': 'yarn install --production; yarn postdeploy'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// This file contains code that we reuse
|
|
4
|
+
// between our tests.
|
|
5
|
+
|
|
6
|
+
const Fastify = require('fastify')
|
|
7
|
+
const fp = require('fastify-plugin')
|
|
8
|
+
|
|
9
|
+
// setup to require YOUR app
|
|
10
|
+
const App = require('../app/app')
|
|
11
|
+
const Config = require('../app/config')
|
|
12
|
+
|
|
13
|
+
// automatically build and tear down our instance
|
|
14
|
+
function build(t, ConfigOverwrite = {}) {
|
|
15
|
+
const app = Fastify()
|
|
16
|
+
|
|
17
|
+
// setup to register YOUR app
|
|
18
|
+
app.register(fp(App), { ...Config, ...ConfigOverwrite })
|
|
19
|
+
|
|
20
|
+
// tear down our app after we are done
|
|
21
|
+
t.tearDown(app.close.bind(app))
|
|
22
|
+
|
|
23
|
+
return app
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
build
|
|
28
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const tap = require('tap')
|
|
2
|
+
const { build } = require('./helper')
|
|
3
|
+
|
|
4
|
+
tap.test('Test Setup', t => {
|
|
5
|
+
t.strictEqual(true, true, 'Tests and assertions should work')
|
|
6
|
+
t.end()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
tap.test('Healthcheck', async t => {
|
|
10
|
+
const fastify = build(t)
|
|
11
|
+
await fastify.ready()
|
|
12
|
+
|
|
13
|
+
t.test('a valid GET Request', t => {
|
|
14
|
+
fastify.inject(
|
|
15
|
+
{
|
|
16
|
+
method: 'GET',
|
|
17
|
+
url: '/api/health'
|
|
18
|
+
},
|
|
19
|
+
(e, response) => {
|
|
20
|
+
t.error(e)
|
|
21
|
+
t.same(response.statusCode, 200, 'response ok')
|
|
22
|
+
t.same(JSON.parse(response.body), { status: 'ok' }, 'payload ok')
|
|
23
|
+
t.end()
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
tap.test('Noop Service', async t => {
|
|
30
|
+
const fastify = build(t)
|
|
31
|
+
await fastify.ready()
|
|
32
|
+
|
|
33
|
+
t.test('a valid GET Request', t => {
|
|
34
|
+
fastify.inject(
|
|
35
|
+
{
|
|
36
|
+
method: 'GET',
|
|
37
|
+
url: '/api/noop'
|
|
38
|
+
},
|
|
39
|
+
(e, response) => {
|
|
40
|
+
t.error(e)
|
|
41
|
+
t.same(response.statusCode, 200, 'response ok')
|
|
42
|
+
t.same(
|
|
43
|
+
JSON.parse(response.body),
|
|
44
|
+
{
|
|
45
|
+
noop: 'Hello world',
|
|
46
|
+
plugin: 'Hello Universe'
|
|
47
|
+
},
|
|
48
|
+
'payload ok'
|
|
49
|
+
)
|
|
50
|
+
t.end()
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
})
|