@tremho/mist-lift 1.1.1 → 1.1.3-pre-release.1
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/LICENSE +21 -21
- package/README.md +65 -65
- package/build/commands/actions/initQuestions.js +35 -33
- package/build/commands/actions/initQuestions.js.map +1 -1
- package/build/commands/build.js +1 -1
- package/build/commands/build.js.map +1 -1
- package/build/commands/create.js +1 -1
- package/build/commands/create.js.map +1 -1
- package/build/commands/deploy.js +1 -1
- package/build/commands/deploy.js.map +1 -1
- package/build/commands/info.js +1 -1
- package/build/commands/info.js.map +1 -1
- package/build/commands/init.js +2 -2
- package/build/commands/init.js.map +1 -1
- package/build/commands/package.js +2 -2
- package/build/commands/package.js.map +1 -1
- package/build/commands/publish.js +2 -2
- package/build/commands/publish.js.map +1 -1
- package/build/expressRoutes/functionBinder.js.map +1 -1
- package/build/integration-tests/quickstart-scenario.test.js +76 -0
- package/build/integration-tests/quickstart-scenario.test.js.map +1 -0
- package/build/lib/utils.js +1 -1
- package/build/lib/utils.js.map +1 -1
- package/build/lift.js +1 -1
- package/build/lift.js.map +1 -1
- package/package.json +79 -72
- package/src/commands/actions/initQuestions.ts +133 -131
- package/src/commands/actions/setupPackageJson.ts +32 -32
- package/src/commands/build.ts +170 -170
- package/src/commands/builtin/ApiDocMaker.ts +102 -102
- package/src/commands/builtin/BuiltInHandler.ts +47 -47
- package/src/commands/builtin/DeployBuiltInZip.ts +25 -25
- package/src/commands/builtin/StageWebrootZip.ts +36 -36
- package/src/commands/create.ts +52 -52
- package/src/commands/deploy.ts +161 -161
- package/src/commands/doctor.ts +106 -106
- package/src/commands/help.ts +178 -178
- package/src/commands/info.ts +42 -43
- package/src/commands/init.ts +61 -60
- package/src/commands/package.ts +234 -234
- package/src/commands/publish.ts +330 -330
- package/src/commands/settings.ts +73 -73
- package/src/commands/start.ts +43 -43
- package/src/commands/test.ts +37 -37
- package/src/commands/user.ts +20 -20
- package/src/expressRoutes/all.ts +99 -99
- package/src/expressRoutes/api.ts +22 -22
- package/src/expressRoutes/functionBinder.ts +155 -156
- package/src/integration-tests/quickstart-scenario.test.ts +74 -0
- package/src/lib/CaseUtils.ts +63 -63
- package/src/lib/DirectoryUtils.ts +34 -34
- package/src/lib/LiftConfig.ts +74 -74
- package/src/lib/LiftVersion.ts +87 -87
- package/src/lib/Tests/fileCompare.test.ts +35 -35
- package/src/lib/askQuestion.ts +17 -17
- package/src/lib/executeCommand.ts +45 -45
- package/src/lib/fileCompare.ts +55 -55
- package/src/lib/openAPI/ApiBuildCollector.ts +47 -47
- package/src/lib/openAPI/WebrootFileSupport.ts +19 -19
- package/src/lib/openAPI/openApiConstruction.ts +196 -196
- package/src/lib/pathResolve.ts +26 -26
- package/src/lib/utils.ts +43 -43
- package/src/lift.ts +87 -87
- package/templateData/function-definition-template +20 -20
- package/templateData/function-local-ts +16 -16
- package/templateData/function-main-ts +16 -16
- package/templateData/function-runmain-mjs +6 -6
- package/templateData/function-test-template +11 -11
- package/templateData/swagger-ui-bundle.js +2 -2
- package/templateData/swagger-ui-standalone-preset.js +2 -2
- package/templateData/swagger-ui.css +2 -2
- package/tsconfig.json +28 -28
- /package/build/commands/builtin/{prebuilt-zips → prebult-zips}/API.zip +0 -0
- /package/build/commands/builtin/{prebuilt-zips → prebult-zips}/FileServe.zip +0 -0
- /package/build/commands/builtin/{prebuilt-zips → prebult-zips}/Webroot.zip +0 -0
|
@@ -1,156 +1,155 @@
|
|
|
1
|
-
/* eslint @typescript-eslint/no-var-requires: "off" */
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
import * as ac from 'ansi-colors'
|
|
5
|
-
import { resolvePaths } from '../lib/pathResolve'
|
|
6
|
-
|
|
7
|
-
import express from 'express'
|
|
8
|
-
import { gatherFunctionDefinitions } from '../lib/openAPI/ApiBuildCollector'
|
|
9
|
-
import { buildOpenApi } from '../lib/openAPI/openApiConstruction'
|
|
10
|
-
import {Log} from '@tremho/inverse-y'
|
|
11
|
-
|
|
12
|
-
const clearModule = require('clear-module')
|
|
13
|
-
const router = express.Router()
|
|
14
|
-
|
|
15
|
-
export function functionBinder (): void {
|
|
16
|
-
const defs = gatherFunctionDefinitions()
|
|
17
|
-
buildOpenApi(defs).then(() => { // creates apidoc.yaml for when /api is run
|
|
18
|
-
const projectPaths = resolvePaths()
|
|
19
|
-
|
|
20
|
-
for (const def of defs) {
|
|
21
|
-
const { name, pathMap, allowedMethods } = def
|
|
22
|
-
const methods = allowedMethods.split(',')
|
|
23
|
-
for (let method of methods) {
|
|
24
|
-
try {
|
|
25
|
-
method = method.trim().toLowerCase()
|
|
26
|
-
const rpath = path.join(projectPaths.buildPath, 'functions', name, 'src', 'main.js')
|
|
27
|
-
clearModule(rpath)
|
|
28
|
-
const { start } = require(rpath)
|
|
29
|
-
|
|
30
|
-
let entryRoot: string = pathMap
|
|
31
|
-
const n = entryRoot.indexOf('/{')
|
|
32
|
-
if (n !== -1) entryRoot = entryRoot.substring(0, n) + '/*'
|
|
33
|
-
|
|
34
|
-
const callHandler = (pathMap: string, req: any, res: any): void => {
|
|
35
|
-
const event = requestToEvent(pathMap, req)
|
|
36
|
-
Promise.resolve(start(event, null, null)).then(respOut => {
|
|
37
|
-
handleResponse(res, respOut)
|
|
38
|
-
}).catch<any>((reason: any) => undefined)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (method === 'get') {
|
|
42
|
-
router.get(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
43
|
-
} else if (method === 'post') {
|
|
44
|
-
router.post(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
45
|
-
} else if (method === 'put') {
|
|
46
|
-
router.put(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
47
|
-
} else if (method === 'patch') {
|
|
48
|
-
router.patch(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
49
|
-
} else if (method === 'delete') {
|
|
50
|
-
router.delete(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
51
|
-
} else {
|
|
52
|
-
console.log(ac.red.bold('Cannot map method ') + ac.blue.bold(method))
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// console.log("
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
export default router
|
|
1
|
+
/* eslint @typescript-eslint/no-var-requires: "off" */
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import * as ac from 'ansi-colors'
|
|
5
|
+
import { resolvePaths } from '../lib/pathResolve'
|
|
6
|
+
|
|
7
|
+
import express from 'express'
|
|
8
|
+
import { gatherFunctionDefinitions } from '../lib/openAPI/ApiBuildCollector'
|
|
9
|
+
import { buildOpenApi } from '../lib/openAPI/openApiConstruction'
|
|
10
|
+
import { Log } from '@tremho/inverse-y'
|
|
11
|
+
|
|
12
|
+
const clearModule = require('clear-module')
|
|
13
|
+
const router = express.Router()
|
|
14
|
+
|
|
15
|
+
export function functionBinder (): void {
|
|
16
|
+
const defs = gatherFunctionDefinitions()
|
|
17
|
+
buildOpenApi(defs).then(() => { // creates apidoc.yaml for when /api is run
|
|
18
|
+
const projectPaths = resolvePaths()
|
|
19
|
+
|
|
20
|
+
for (const def of defs) {
|
|
21
|
+
const { name, pathMap, allowedMethods } = def
|
|
22
|
+
const methods = allowedMethods.split(',')
|
|
23
|
+
for (let method of methods) {
|
|
24
|
+
try {
|
|
25
|
+
method = method.trim().toLowerCase()
|
|
26
|
+
const rpath = path.join(projectPaths.buildPath, 'functions', name, 'src', 'main.js')
|
|
27
|
+
clearModule(rpath)
|
|
28
|
+
const { start } = require(rpath)
|
|
29
|
+
|
|
30
|
+
let entryRoot: string = pathMap
|
|
31
|
+
const n = entryRoot.indexOf('/{')
|
|
32
|
+
if (n !== -1) entryRoot = entryRoot.substring(0, n) + '/*'
|
|
33
|
+
|
|
34
|
+
const callHandler = (pathMap: string, req: any, res: any): void => {
|
|
35
|
+
const event = requestToEvent(pathMap, req)
|
|
36
|
+
Promise.resolve(start(event, null, null)).then(respOut => {
|
|
37
|
+
handleResponse(res, respOut)
|
|
38
|
+
}).catch<any>((reason: any) => undefined)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (method === 'get') {
|
|
42
|
+
router.get(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
43
|
+
} else if (method === 'post') {
|
|
44
|
+
router.post(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
45
|
+
} else if (method === 'put') {
|
|
46
|
+
router.put(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
47
|
+
} else if (method === 'patch') {
|
|
48
|
+
router.patch(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
49
|
+
} else if (method === 'delete') {
|
|
50
|
+
router.delete(entryRoot, (req, res) => callHandler(pathMap, req, res))
|
|
51
|
+
} else {
|
|
52
|
+
console.log(ac.red.bold('Cannot map method ') + ac.blue.bold(method))
|
|
53
|
+
}
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
Log.Error(ac.bold.red(e.message.split('\n')[0]))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}).catch<any>((reason: any) => undefined)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function requestToEvent (template: string, req: any): any {
|
|
63
|
+
let path: string = req.originalUrl ?? ''
|
|
64
|
+
const qi = path.indexOf('?')
|
|
65
|
+
if (qi !== -1) path = path.substring(0, qi)
|
|
66
|
+
const ptci: number = path.indexOf('://') + 3
|
|
67
|
+
const ei: number = path.indexOf('/', ptci)
|
|
68
|
+
let host: string = path.substring(0, ei)
|
|
69
|
+
if (ptci < 3) {
|
|
70
|
+
host = req.headers?.host ?? req.headers?.origin ?? req.headers?.referer ?? ''
|
|
71
|
+
if (!host.startsWith('http')) {
|
|
72
|
+
if (!host.startsWith('https')) {
|
|
73
|
+
host = 'http://' + host
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const cookies: any = {}
|
|
78
|
+
const cookieString = req.headers?.cookie ?? ''
|
|
79
|
+
const crumbs = cookieString.split(';')
|
|
80
|
+
for (const c of crumbs) {
|
|
81
|
+
const pair: string[] = c.split('=')
|
|
82
|
+
if (pair.length === 2) cookies[pair[0]] = pair[1]
|
|
83
|
+
}
|
|
84
|
+
const parameters: any = {}
|
|
85
|
+
const tslots = template.split('/')
|
|
86
|
+
const pslots = path.split('/')
|
|
87
|
+
for (let i = 0; i < tslots.length; i++) {
|
|
88
|
+
const brknm = (tslots[i] ?? '').trim()
|
|
89
|
+
if (brknm.charAt(0) === '{') {
|
|
90
|
+
const pn = brknm.substring(1, brknm.length - 1)
|
|
91
|
+
parameters[pn] = (pslots[i] ?? '').trim()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const p of Object.getOwnPropertyNames(req.query)) {
|
|
95
|
+
parameters[p] = req.query[p]
|
|
96
|
+
}
|
|
97
|
+
const eventOut: any = {
|
|
98
|
+
requestContext: req,
|
|
99
|
+
request: {
|
|
100
|
+
originalUrl: host + (req.originalUrl as string),
|
|
101
|
+
headers: req.headers
|
|
102
|
+
},
|
|
103
|
+
body: req.body,
|
|
104
|
+
cookies,
|
|
105
|
+
parameters
|
|
106
|
+
}
|
|
107
|
+
return eventOut
|
|
108
|
+
}
|
|
109
|
+
function handleResponse (res: any, resp: any): void {
|
|
110
|
+
// console.log(">>>>>>>> Handling response >>>>>>>>>")
|
|
111
|
+
|
|
112
|
+
if (resp !== undefined) {
|
|
113
|
+
if (resp.cookies !== undefined) {
|
|
114
|
+
// console.log("--- see cookies", resp.cookies)
|
|
115
|
+
let cookies: any = []
|
|
116
|
+
if (Array.isArray(resp.cookies)) {
|
|
117
|
+
cookies = resp.cookies
|
|
118
|
+
} else {
|
|
119
|
+
const age: number = resp.cookies.expireSeconds ?? 60 // 1 minute
|
|
120
|
+
delete resp.expireSeconds
|
|
121
|
+
Object.getOwnPropertyNames(resp.cookies).forEach(name => {
|
|
122
|
+
const value: string = resp.cookies[name]
|
|
123
|
+
cookies.push(`${name}=${value}; Max-Age=${age}; `)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
// console.log("cookies being set", cookies)
|
|
127
|
+
res.setHeader('set-cookie', cookies)
|
|
128
|
+
delete resp.cookies
|
|
129
|
+
}
|
|
130
|
+
if (resp.headers !== undefined) {
|
|
131
|
+
// if (resp.statusCode === 301) ClogTrace("Redirecting...");
|
|
132
|
+
for (const hdr of Object.getOwnPropertyNames(resp.headers)) {
|
|
133
|
+
// ClogTrace("Setting header ", hdr, resp.headers[hdr]);
|
|
134
|
+
res.setHeader(hdr, resp.headers[hdr])
|
|
135
|
+
}
|
|
136
|
+
delete resp.headers
|
|
137
|
+
// console.log("past setting headers");
|
|
138
|
+
}
|
|
139
|
+
if (resp.statusCode !== undefined) {
|
|
140
|
+
res.statusCode = resp.statusCode
|
|
141
|
+
delete resp.statusCode
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (resp.contentType !== undefined) {
|
|
145
|
+
res.setHeader('Content-Type', resp.contentType)
|
|
146
|
+
delete resp.contentType
|
|
147
|
+
}
|
|
148
|
+
if (resp.body !== undefined) resp = resp.body
|
|
149
|
+
}
|
|
150
|
+
// console.log("headers to be sent", res.getHeaders())
|
|
151
|
+
// console.log("-- Sending response", resp)
|
|
152
|
+
res.send(resp)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default router
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
/* eslint @typescript-eslint/no-floating-promises: "off" */
|
|
3
|
+
|
|
4
|
+
import Tap from 'tap'
|
|
5
|
+
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
|
|
9
|
+
import { doInit } from '../commands/init'
|
|
10
|
+
import { doCreate } from '../commands/create'
|
|
11
|
+
import { doBuildAsync } from '../commands/build'
|
|
12
|
+
import { doDeployAsync } from '../commands/deploy'
|
|
13
|
+
import { doPublishAsync } from '../commands/publish'
|
|
14
|
+
|
|
15
|
+
import axios from 'axios'
|
|
16
|
+
|
|
17
|
+
async function test (t: any): Promise<void> {
|
|
18
|
+
const testMessage = `test on ${Date.now()}`
|
|
19
|
+
// start clean
|
|
20
|
+
fs.rmSync('./QSTest', { recursive: true })
|
|
21
|
+
// init
|
|
22
|
+
await doInit('QSTest', true)
|
|
23
|
+
|
|
24
|
+
// verify project got made
|
|
25
|
+
t.ok(fs.existsSync('./QSTest'), 'created test project')
|
|
26
|
+
// verify we have a functions dir
|
|
27
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'functions')), 'functions exists')
|
|
28
|
+
// verify we have a node_modules dir
|
|
29
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'node_modules')), 'node_modules exists')
|
|
30
|
+
// verify we have a webroot dir
|
|
31
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'webroot')), 'webroot exists')
|
|
32
|
+
// verify we have a package.json
|
|
33
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'package.json')), 'package.json exists')
|
|
34
|
+
// verify we have webroot/docs
|
|
35
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'webroot', 'docs', 'apidoc.yaml')), 'yaml exists')
|
|
36
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'webroot', 'docs', 'swagger-ui-bundle.js')), 'js exists')
|
|
37
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'webroot', 'docs', 'swagger-ui-standalone-preset.js')), 'js exists')
|
|
38
|
+
t.ok(fs.existsSync(path.join('./QSTest', 'webroot', 'docs', 'swagger-ui.css')), 'css exists')
|
|
39
|
+
|
|
40
|
+
// create
|
|
41
|
+
process.chdir('./QSTest')
|
|
42
|
+
await doCreate('IntegrationTest')
|
|
43
|
+
t.ok(fs.existsSync(path.join('functions', 'IntegrationTest')), 'function exists')
|
|
44
|
+
t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'definition.json')), 'definition.json exists')
|
|
45
|
+
t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'local.ts')), 'local.ts exists')
|
|
46
|
+
const maints = path.join('functions', 'IntegrationTest', 'src', 'main.ts')
|
|
47
|
+
t.ok(fs.existsSync(maints), 'main.ts exists')
|
|
48
|
+
const content = fs.readFileSync(maints).toString().replace('Hello, World!', testMessage)
|
|
49
|
+
fs.writeFileSync(maints, content)
|
|
50
|
+
|
|
51
|
+
// build
|
|
52
|
+
await doBuildAsync(['--clean'])
|
|
53
|
+
|
|
54
|
+
await doDeployAsync([])
|
|
55
|
+
|
|
56
|
+
await doPublishAsync()
|
|
57
|
+
|
|
58
|
+
const publishedJson = fs.readFileSync('.published').toString()
|
|
59
|
+
const publishedInfo = JSON.parse(publishedJson)
|
|
60
|
+
const apiUrl: string = publishedInfo.url
|
|
61
|
+
t.ok(apiUrl.length > 0, 'url exists')
|
|
62
|
+
const testUrl = apiUrl + '/integrationtest'
|
|
63
|
+
|
|
64
|
+
const resp: any = await axios.get(testUrl)
|
|
65
|
+
t.ok(resp.status === 200, 'status is 200')
|
|
66
|
+
const data: string = resp.data.toString()
|
|
67
|
+
t.ok(data === testMessage, 'saw expected data')
|
|
68
|
+
|
|
69
|
+
t.end()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Tap.test('Integration Quickstart', async t => {
|
|
73
|
+
await test(t)
|
|
74
|
+
})
|
package/src/lib/CaseUtils.ts
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
// separate a case-joined string into a string of space-separated words
|
|
3
|
-
export function separated (
|
|
4
|
-
instr: string
|
|
5
|
-
): string {
|
|
6
|
-
let outstr = ''
|
|
7
|
-
for (let i = 0; i < instr.length; i++) {
|
|
8
|
-
const c = instr.charAt(i)
|
|
9
|
-
if (!isAlphaNum(c) || isUpperCase(c)) {
|
|
10
|
-
outstr += ' '
|
|
11
|
-
}
|
|
12
|
-
outstr += c.toLowerCase()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return outstr.trim()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function isUpperCase (instr: string): boolean {
|
|
19
|
-
return instr?.toUpperCase() === instr
|
|
20
|
-
}
|
|
21
|
-
function isAlphaNum (char: string): boolean {
|
|
22
|
-
let alnum = false
|
|
23
|
-
const cc = char.toUpperCase().charCodeAt(0)
|
|
24
|
-
if (cc >= 'A'.charCodeAt(0) && cc <= 'Z'.charCodeAt(0)) alnum = true
|
|
25
|
-
if (cc >= '0'.charCodeAt(0) && cc <= '9'.charCodeAt(0)) alnum = true
|
|
26
|
-
return alnum
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function camelCase (
|
|
30
|
-
instr: string
|
|
31
|
-
): string {
|
|
32
|
-
const seps = separated(instr).split(' ')
|
|
33
|
-
for (let i = 1; i < seps.length; i++) {
|
|
34
|
-
seps[i] = seps[i].charAt(0).toUpperCase() + seps[i].substring(1)
|
|
35
|
-
}
|
|
36
|
-
return seps.join('')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function pascalCase (
|
|
40
|
-
instr: string
|
|
41
|
-
): string {
|
|
42
|
-
const seps = separated(instr).split(' ')
|
|
43
|
-
for (let i = 0; i < seps.length; i++) {
|
|
44
|
-
seps[i] = seps[i].charAt(0).toUpperCase() + seps[i].substring(1)
|
|
45
|
-
}
|
|
46
|
-
return seps.join('')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function snakeCase (
|
|
50
|
-
instr: string
|
|
51
|
-
): string {
|
|
52
|
-
return dashCase(instr, '_')
|
|
53
|
-
}
|
|
54
|
-
export function dashCase (
|
|
55
|
-
instr: string,
|
|
56
|
-
dashChar: string = '-',
|
|
57
|
-
allCaps: boolean = false
|
|
58
|
-
): string {
|
|
59
|
-
const seps = separated(instr).split(' ')
|
|
60
|
-
let out = seps.join(dashChar)
|
|
61
|
-
if (allCaps) out = out.toUpperCase()
|
|
62
|
-
return out
|
|
63
|
-
}
|
|
1
|
+
|
|
2
|
+
// separate a case-joined string into a string of space-separated words
|
|
3
|
+
export function separated (
|
|
4
|
+
instr: string
|
|
5
|
+
): string {
|
|
6
|
+
let outstr = ''
|
|
7
|
+
for (let i = 0; i < instr.length; i++) {
|
|
8
|
+
const c = instr.charAt(i)
|
|
9
|
+
if (!isAlphaNum(c) || isUpperCase(c)) {
|
|
10
|
+
outstr += ' '
|
|
11
|
+
}
|
|
12
|
+
outstr += c.toLowerCase()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return outstr.trim()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isUpperCase (instr: string): boolean {
|
|
19
|
+
return instr?.toUpperCase() === instr
|
|
20
|
+
}
|
|
21
|
+
function isAlphaNum (char: string): boolean {
|
|
22
|
+
let alnum = false
|
|
23
|
+
const cc = char.toUpperCase().charCodeAt(0)
|
|
24
|
+
if (cc >= 'A'.charCodeAt(0) && cc <= 'Z'.charCodeAt(0)) alnum = true
|
|
25
|
+
if (cc >= '0'.charCodeAt(0) && cc <= '9'.charCodeAt(0)) alnum = true
|
|
26
|
+
return alnum
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function camelCase (
|
|
30
|
+
instr: string
|
|
31
|
+
): string {
|
|
32
|
+
const seps = separated(instr).split(' ')
|
|
33
|
+
for (let i = 1; i < seps.length; i++) {
|
|
34
|
+
seps[i] = seps[i].charAt(0).toUpperCase() + seps[i].substring(1)
|
|
35
|
+
}
|
|
36
|
+
return seps.join('')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function pascalCase (
|
|
40
|
+
instr: string
|
|
41
|
+
): string {
|
|
42
|
+
const seps = separated(instr).split(' ')
|
|
43
|
+
for (let i = 0; i < seps.length; i++) {
|
|
44
|
+
seps[i] = seps[i].charAt(0).toUpperCase() + seps[i].substring(1)
|
|
45
|
+
}
|
|
46
|
+
return seps.join('')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function snakeCase (
|
|
50
|
+
instr: string
|
|
51
|
+
): string {
|
|
52
|
+
return dashCase(instr, '_')
|
|
53
|
+
}
|
|
54
|
+
export function dashCase (
|
|
55
|
+
instr: string,
|
|
56
|
+
dashChar: string = '-',
|
|
57
|
+
allCaps: boolean = false
|
|
58
|
+
): string {
|
|
59
|
+
const seps = separated(instr).split(' ')
|
|
60
|
+
let out = seps.join(dashChar)
|
|
61
|
+
if (allCaps) out = out.toUpperCase()
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
import fs, { Stats } from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
// Callback definition for recurseDirectory
|
|
6
|
-
export type RecurseCB = (filepath: string, stats: Stats) => boolean
|
|
7
|
-
|
|
8
|
-
// Recurse a directory, calling back to a 'for-each' callback for all files in tree
|
|
9
|
-
export function recurseDirectory (dirpath: string, callback?: RecurseCB): void {
|
|
10
|
-
fs.readdirSync(dirpath).forEach((file: string) => {
|
|
11
|
-
const fpath = path.join(dirpath, file)
|
|
12
|
-
const stat = fs.lstatSync(fpath)
|
|
13
|
-
if ((callback != null) && !callback(fpath, stat)) {
|
|
14
|
-
if (stat.isDirectory()) {
|
|
15
|
-
recurseDirectory(fpath, callback)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// find the latest mod time for matching files in this folder tree
|
|
22
|
-
export function latestModification (dirpath: string, match: string): Date {
|
|
23
|
-
let latestTime = new Date(0)
|
|
24
|
-
if (fs.existsSync(dirpath)) {
|
|
25
|
-
recurseDirectory(dirpath, (filepath: string, stats: Stats) => {
|
|
26
|
-
const basefile = filepath.substring(filepath.lastIndexOf('/') + 1)
|
|
27
|
-
if (basefile.match(match) != null) {
|
|
28
|
-
if (stats.mtime > latestTime) latestTime = stats.mtime
|
|
29
|
-
}
|
|
30
|
-
return false
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
return latestTime
|
|
34
|
-
}
|
|
1
|
+
|
|
2
|
+
import fs, { Stats } from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
// Callback definition for recurseDirectory
|
|
6
|
+
export type RecurseCB = (filepath: string, stats: Stats) => boolean
|
|
7
|
+
|
|
8
|
+
// Recurse a directory, calling back to a 'for-each' callback for all files in tree
|
|
9
|
+
export function recurseDirectory (dirpath: string, callback?: RecurseCB): void {
|
|
10
|
+
fs.readdirSync(dirpath).forEach((file: string) => {
|
|
11
|
+
const fpath = path.join(dirpath, file)
|
|
12
|
+
const stat = fs.lstatSync(fpath)
|
|
13
|
+
if ((callback != null) && !callback(fpath, stat)) {
|
|
14
|
+
if (stat.isDirectory()) {
|
|
15
|
+
recurseDirectory(fpath, callback)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// find the latest mod time for matching files in this folder tree
|
|
22
|
+
export function latestModification (dirpath: string, match: string): Date {
|
|
23
|
+
let latestTime = new Date(0)
|
|
24
|
+
if (fs.existsSync(dirpath)) {
|
|
25
|
+
recurseDirectory(dirpath, (filepath: string, stats: Stats) => {
|
|
26
|
+
const basefile = filepath.substring(filepath.lastIndexOf('/') + 1)
|
|
27
|
+
if (basefile.match(match) != null) {
|
|
28
|
+
if (stats.mtime > latestTime) latestTime = stats.mtime
|
|
29
|
+
}
|
|
30
|
+
return false
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
return latestTime
|
|
34
|
+
}
|