@tremho/mist-lift 1.1.4-pre-release.1 → 1.1.5

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.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +70 -66
  3. package/build/QSTest/functions/IntegrationTest/src/main.js +1 -1
  4. package/build/src/commands/builtin/ApiDocMaker.js +0 -1
  5. package/build/src/commands/builtin/ApiDocMaker.js.map +1 -1
  6. package/build/src/commands/doctor.js +12 -1
  7. package/build/src/commands/doctor.js.map +1 -1
  8. package/build/src/lib/LiftVersion.js +1 -1
  9. package/build/src/lib/LiftVersion.js.map +1 -1
  10. package/package.json +79 -79
  11. package/src/commands/actions/initQuestions.ts +133 -133
  12. package/src/commands/actions/setupPackageJson.ts +32 -32
  13. package/src/commands/build.ts +173 -173
  14. package/src/commands/builtin/ApiDocMaker.ts +105 -106
  15. package/src/commands/builtin/BuiltInHandler.ts +47 -47
  16. package/src/commands/builtin/DeployBuiltInZip.ts +25 -25
  17. package/src/commands/builtin/StageWebrootZip.ts +36 -36
  18. package/src/commands/create.ts +52 -52
  19. package/src/commands/deploy.ts +161 -161
  20. package/src/commands/doctor.ts +118 -107
  21. package/src/commands/help.ts +178 -178
  22. package/src/commands/info.ts +42 -42
  23. package/src/commands/init.ts +61 -61
  24. package/src/commands/package.ts +234 -234
  25. package/src/commands/publish.ts +330 -330
  26. package/src/commands/settings.ts +73 -73
  27. package/src/commands/start.ts +43 -43
  28. package/src/commands/test.ts +37 -37
  29. package/src/commands/user.ts +20 -20
  30. package/src/expressRoutes/all.ts +99 -99
  31. package/src/expressRoutes/api.ts +22 -22
  32. package/src/expressRoutes/functionBinder.ts +155 -155
  33. package/src/integration-tests/quickstart-scenario.test.ts +76 -76
  34. package/src/lib/CaseUtils.ts +63 -63
  35. package/src/lib/DirectoryUtils.ts +34 -34
  36. package/src/lib/LiftConfig.ts +74 -74
  37. package/src/lib/LiftVersion.ts +87 -87
  38. package/src/lib/Tests/fileCompare.test.ts +35 -35
  39. package/src/lib/askQuestion.ts +17 -17
  40. package/src/lib/executeCommand.ts +45 -45
  41. package/src/lib/fileCompare.ts +55 -55
  42. package/src/lib/openAPI/ApiBuildCollector.ts +47 -47
  43. package/src/lib/openAPI/WebrootFileSupport.ts +19 -19
  44. package/src/lib/openAPI/openApiConstruction.ts +196 -196
  45. package/src/lib/pathResolve.ts +26 -26
  46. package/src/lib/utils.ts +43 -43
  47. package/src/lift.ts +87 -87
  48. package/templateData/function-definition-template +20 -20
  49. package/templateData/function-local-ts +16 -16
  50. package/templateData/function-main-ts +16 -16
  51. package/templateData/function-runmain-mjs +6 -6
  52. package/templateData/function-test-template +11 -11
  53. package/templateData/swagger-ui-bundle.js +2 -2
  54. package/templateData/swagger-ui-standalone-preset.js +2 -2
  55. package/templateData/swagger-ui.css +2 -2
  56. package/tsconfig.json +28 -28
  57. package/build/commands/builtin/prebuilt-zips/API.zip +0 -0
  58. package/build/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
  59. package/build/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
@@ -1,155 +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
- } 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
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
@@ -1,76 +1,76 @@
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
- if(fs.existsSync('QSTest')) {
21
- fs.rmSync('QSTest', { recursive: true })
22
- }
23
- // init
24
- await doInit('QSTest', true)
25
-
26
- // verify project got made
27
- t.ok(fs.existsSync('QSTest'), 'created test project')
28
- // verify we have a functions dir
29
- t.ok(fs.existsSync(path.join('QSTest', 'functions')), 'functions exists')
30
- // verify we have a node_modules dir
31
- t.ok(fs.existsSync(path.join('QSTest', 'node_modules')), 'node_modules exists')
32
- // verify we have a webroot dir
33
- t.ok(fs.existsSync(path.join('QSTest', 'webroot')), 'webroot exists')
34
- // verify we have a package.json
35
- t.ok(fs.existsSync(path.join('QSTest', 'package.json')), 'package.json exists')
36
- // verify we have webroot/docs
37
- t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'apidoc.yaml')), 'yaml exists')
38
- t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui-bundle.js')), 'js exists')
39
- t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui-standalone-preset.js')), 'js exists')
40
- t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui.css')), 'css exists')
41
-
42
- // create
43
- process.chdir('QSTest')
44
- await doCreate('IntegrationTest')
45
- t.ok(fs.existsSync(path.join('functions', 'IntegrationTest')), 'function exists')
46
- t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'definition.json')), 'definition.json exists')
47
- t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'local.ts')), 'local.ts exists')
48
- const maints = path.join('functions', 'IntegrationTest', 'src', 'main.ts')
49
- t.ok(fs.existsSync(maints), 'main.ts exists')
50
- const content = fs.readFileSync(maints).toString().replace('Hello, World!', testMessage)
51
- fs.writeFileSync(maints, content)
52
-
53
- // build
54
- await doBuildAsync(['--clean'])
55
-
56
- await doDeployAsync([])
57
-
58
- await doPublishAsync()
59
-
60
- const publishedJson = fs.readFileSync('.published').toString()
61
- const publishedInfo = JSON.parse(publishedJson)
62
- const apiUrl: string = publishedInfo.url
63
- t.ok(apiUrl.length > 0, 'url exists')
64
- const testUrl = apiUrl + '/integrationtest'
65
-
66
- const resp: any = await axios.get(testUrl)
67
- t.ok(resp.status === 200, 'status is 200')
68
- const data: string = resp.data.toString()
69
- t.ok(data === testMessage, 'saw expected data')
70
-
71
- t.end()
72
- }
73
-
74
- Tap.test('Integration Quickstart', async t => {
75
- await test(t)
76
- })
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
+ if(fs.existsSync('QSTest')) {
21
+ fs.rmSync('QSTest', { recursive: true })
22
+ }
23
+ // init
24
+ await doInit('QSTest', true)
25
+
26
+ // verify project got made
27
+ t.ok(fs.existsSync('QSTest'), 'created test project')
28
+ // verify we have a functions dir
29
+ t.ok(fs.existsSync(path.join('QSTest', 'functions')), 'functions exists')
30
+ // verify we have a node_modules dir
31
+ t.ok(fs.existsSync(path.join('QSTest', 'node_modules')), 'node_modules exists')
32
+ // verify we have a webroot dir
33
+ t.ok(fs.existsSync(path.join('QSTest', 'webroot')), 'webroot exists')
34
+ // verify we have a package.json
35
+ t.ok(fs.existsSync(path.join('QSTest', 'package.json')), 'package.json exists')
36
+ // verify we have webroot/docs
37
+ t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'apidoc.yaml')), 'yaml exists')
38
+ t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui-bundle.js')), 'js exists')
39
+ t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui-standalone-preset.js')), 'js exists')
40
+ t.ok(fs.existsSync(path.join('QSTest', 'webroot', 'docs', 'swagger-ui.css')), 'css exists')
41
+
42
+ // create
43
+ process.chdir('QSTest')
44
+ await doCreate('IntegrationTest')
45
+ t.ok(fs.existsSync(path.join('functions', 'IntegrationTest')), 'function exists')
46
+ t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'definition.json')), 'definition.json exists')
47
+ t.ok(fs.existsSync(path.join('functions', 'IntegrationTest', 'src', 'local.ts')), 'local.ts exists')
48
+ const maints = path.join('functions', 'IntegrationTest', 'src', 'main.ts')
49
+ t.ok(fs.existsSync(maints), 'main.ts exists')
50
+ const content = fs.readFileSync(maints).toString().replace('Hello, World!', testMessage)
51
+ fs.writeFileSync(maints, content)
52
+
53
+ // build
54
+ await doBuildAsync(['--clean'])
55
+
56
+ await doDeployAsync([])
57
+
58
+ await doPublishAsync()
59
+
60
+ const publishedJson = fs.readFileSync('.published').toString()
61
+ const publishedInfo = JSON.parse(publishedJson)
62
+ const apiUrl: string = publishedInfo.url
63
+ t.ok(apiUrl.length > 0, 'url exists')
64
+ const testUrl = apiUrl + '/integrationtest'
65
+
66
+ const resp: any = await axios.get(testUrl)
67
+ t.ok(resp.status === 200, 'status is 200')
68
+ const data: string = resp.data.toString()
69
+ t.ok(data === testMessage, 'saw expected data')
70
+
71
+ t.end()
72
+ }
73
+
74
+ Tap.test('Integration Quickstart', async t => {
75
+ await test(t)
76
+ })
@@ -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
+ }