@splendidlabz/utils 1.3.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/.eslintrc.cjs +3 -0
- package/.turbo/turbo-lint.log +10 -0
- package/.turbo/turbo-test.log +10 -0
- package/CHANGELOG.md +37 -0
- package/actions/index.js +5 -0
- package/actions/masonry.js +38 -0
- package/actions/mutation-observer.js +43 -0
- package/actions/prefer-horizontal-scroll.js +125 -0
- package/actions/resize-observer.js +38 -0
- package/actions/sticky.js +88 -0
- package/dom/bounding-box.js +54 -0
- package/dom/clipboard.js +14 -0
- package/dom/cookie.js +31 -0
- package/dom/css-vars.js +11 -0
- package/dom/events.js +76 -0
- package/dom/events.test.js +99 -0
- package/dom/focusable.js +56 -0
- package/dom/font-size.js +24 -0
- package/dom/get-element.js +69 -0
- package/dom/hash.js +9 -0
- package/dom/index.js +18 -0
- package/dom/keyboard.js +39 -0
- package/dom/local-store.js +29 -0
- package/dom/media.js +27 -0
- package/dom/pkce.js +34 -0
- package/dom/query-params.js +14 -0
- package/dom/random-string.js +18 -0
- package/dom/session-store.js +29 -0
- package/dom/trap-focus.js +55 -0
- package/dom/ui/aria-current.js +20 -0
- package/dom/ui/inconsistent-button-fix.js +8 -0
- package/dom/ui/index.js +4 -0
- package/dom/ui/scroll-container.js +25 -0
- package/dom/ui/traverse-and-scramble.js +40 -0
- package/lib/arrays/index.js +62 -0
- package/lib/auth/index.js +1 -0
- package/lib/auth/route-manager.js +44 -0
- package/lib/checks.js +11 -0
- package/lib/date/days.js +9 -0
- package/lib/date/index.js +2 -0
- package/lib/date/months.js +74 -0
- package/lib/form/form-data.js +73 -0
- package/lib/form/index.js +2 -0
- package/lib/form/sanitize.js +47 -0
- package/lib/functions/debounce.js +18 -0
- package/lib/functions/env.js +5 -0
- package/lib/functions/functional.js +31 -0
- package/lib/functions/index.js +5 -0
- package/lib/functions/throttle.js +11 -0
- package/lib/functions/timeout.js +15 -0
- package/lib/index.js +12 -0
- package/lib/index.test.js +9 -0
- package/lib/numbers/index.js +10 -0
- package/lib/objects/camelcase-keys.js +17 -0
- package/lib/objects/camelcase-keys.test.js +72 -0
- package/lib/objects/empty.js +10 -0
- package/lib/objects/extend.js +16 -0
- package/lib/objects/extend.test.js +35 -0
- package/lib/objects/flatten.js +19 -0
- package/lib/objects/index.js +12 -0
- package/lib/objects/json.js +7 -0
- package/lib/objects/loop.js +11 -0
- package/lib/objects/loop.test.js +31 -0
- package/lib/objects/mix/mix.js +100 -0
- package/lib/objects/mix/mix.md +78 -0
- package/lib/objects/mix/mix.test.js +324 -0
- package/lib/objects/nested-property.js +36 -0
- package/lib/objects/normalize-object.js +10 -0
- package/lib/objects/omit-empty.js +23 -0
- package/lib/objects/omit-empty.test.js +85 -0
- package/lib/objects/size.js +41 -0
- package/lib/objects/split.js +19 -0
- package/lib/promises/index.js +1 -0
- package/lib/promises/reject.js +17 -0
- package/lib/strings/convert-case/convert-case.js +86 -0
- package/lib/strings/convert-case/convert-case.md +44 -0
- package/lib/strings/convert-case/convert-case.test.js +50 -0
- package/lib/strings/index.js +4 -0
- package/lib/strings/markdown.js +39 -0
- package/lib/strings/pluralize.js +2 -0
- package/lib/strings/query-string.js +18 -0
- package/lib/style/index.js +11 -0
- package/lib/symbols/index.js +1 -0
- package/lib/symbols/symbols.js +9 -0
- package/node/common.js +5 -0
- package/node/dirname.js +17 -0
- package/node/file-cache.js +135 -0
- package/node/file.js +13 -0
- package/node/hash.js +5 -0
- package/node/index.js +6 -0
- package/node/pkce.js +33 -0
- package/node/random-string.js +10 -0
- package/package.json +35 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ========================
|
|
2
|
+
// Case Conversion Utilities
|
|
3
|
+
// Note: All case conversion functions require `toKebab` to work.
|
|
4
|
+
// ------------------------
|
|
5
|
+
export function toKebab(string) {
|
|
6
|
+
if (!string) return ''
|
|
7
|
+
return string
|
|
8
|
+
.split('')
|
|
9
|
+
.map((letter, index) => {
|
|
10
|
+
const previousLetter = string[index - 1] || ''
|
|
11
|
+
const currentLetter = letter
|
|
12
|
+
|
|
13
|
+
if (isDigit(currentLetter) && !isDigit(previousLetter)) {
|
|
14
|
+
return `-${currentLetter}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!isCaps(currentLetter)) return currentLetter
|
|
18
|
+
|
|
19
|
+
if (previousLetter === '') {
|
|
20
|
+
return `${currentLetter.toLowerCase()}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isCaps(previousLetter)) {
|
|
24
|
+
return `${currentLetter.toLowerCase()}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `-${currentLetter.toLowerCase()}`
|
|
28
|
+
})
|
|
29
|
+
.join('')
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/[-_\s]+/g, '-')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function toCamel(string) {
|
|
35
|
+
return toKebab(string)
|
|
36
|
+
.split('-')
|
|
37
|
+
.map((word, index) => {
|
|
38
|
+
if (index === 0) return word
|
|
39
|
+
return word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase()
|
|
40
|
+
})
|
|
41
|
+
.join('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toPascal(string) {
|
|
45
|
+
const camel = toCamel(string)
|
|
46
|
+
return camel.slice(0, 1).toUpperCase() + camel.slice(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function toTitle(string) {
|
|
50
|
+
return toKebab(string)
|
|
51
|
+
.split('-')
|
|
52
|
+
.map(word => {
|
|
53
|
+
return word.slice(0, 1).toUpperCase() + word.slice(1)
|
|
54
|
+
})
|
|
55
|
+
.join(' ')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function toSlug(string) {
|
|
59
|
+
return toKebab(string)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function toSentence(string) {
|
|
63
|
+
const interim = toKebab(string).replace(/-/g, ' ')
|
|
64
|
+
return interim.slice(0, 1).toUpperCase() + interim.slice(1)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function toUpper(string) {
|
|
68
|
+
const interim = toKebab(string).replace(/-/g, ' ')
|
|
69
|
+
return interim.toUpperCase()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function toLower(string) {
|
|
73
|
+
const interim = toKebab(string).replace(/-/g, ' ')
|
|
74
|
+
return interim.toLowerCase()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Checks whether character is Uppercase.
|
|
78
|
+
// Crude version. Checks only A-Z.
|
|
79
|
+
function isCaps(char) {
|
|
80
|
+
return /\p{Lu}/u.test(char)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Checks whether character is digit.
|
|
84
|
+
function isDigit(char) {
|
|
85
|
+
return /[0-9]/.test(char)
|
|
86
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Case Conversion Utilities
|
|
2
|
+
|
|
3
|
+
This file contains utilities to convert any string into the following cases:
|
|
4
|
+
|
|
5
|
+
1. `kebab-case`
|
|
6
|
+
2. `camelCase`
|
|
7
|
+
3. `Title Case`
|
|
8
|
+
4. `Sentence case`
|
|
9
|
+
|
|
10
|
+
[See my process for creating these case-conversion utilties](https://zellwk.com/blog/case-conversion)
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
toKebab('caseWithSomeWords') // case-with-some-words
|
|
16
|
+
toKebab('CaseWithSomeWords') // case-with-some-words
|
|
17
|
+
toKebab('case_with_some_words') // case-with-some-words
|
|
18
|
+
toKebab('Case with some words') // case-with-some-words
|
|
19
|
+
toKebab('Case With Some Words') // case-with-some-words
|
|
20
|
+
toKebab('Case_with SomeWords') // case-with-some-words
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Manual installation:
|
|
26
|
+
|
|
27
|
+
- Copy and paste the entire file into your javascript file. 😉
|
|
28
|
+
- If you use ES5 Modules, you can import them into your file
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { toKebab } from "./convert-case.js"
|
|
32
|
+
toKebab('someString')
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
NPM:
|
|
36
|
+
|
|
37
|
+
- Run `npm install @zellwk/javascript`
|
|
38
|
+
- Import `convert-case.js`
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { toKebab } from "@zellwk/javascript/convert-case"
|
|
42
|
+
toKebab('someString')
|
|
43
|
+
```
|
|
44
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-env jest */
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { toCamel, toKebab, toSentence, toTitle } from './convert-case'
|
|
4
|
+
|
|
5
|
+
const cases = {
|
|
6
|
+
kebab: 'case-with-long-name100',
|
|
7
|
+
snake: 'case_with_long_name100',
|
|
8
|
+
camel: 'caseWithLongName100',
|
|
9
|
+
pascal: 'CaseWithLongName100',
|
|
10
|
+
sentence: 'Case with long name100',
|
|
11
|
+
title: 'Case With Long Name100',
|
|
12
|
+
constant: 'CASE_WITH_LONG_NAME100',
|
|
13
|
+
mixCases: 'case_WITH-Long name100',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('To Kebab', _ => {
|
|
17
|
+
Object.entries(cases).forEach(c => {
|
|
18
|
+
it(`From ${c[0]}`, () => {
|
|
19
|
+
const result = toKebab(c[1])
|
|
20
|
+
expect(result, c[0]).toBe('case-with-long-name-100')
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('To Camel', _ => {
|
|
26
|
+
Object.entries(cases).forEach(c => {
|
|
27
|
+
it(`From ${c[0]}`, () => {
|
|
28
|
+
const result = toCamel(c[1])
|
|
29
|
+
expect(result, c[0]).toBe('caseWithLongName100')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('To Sentence', _ => {
|
|
35
|
+
Object.entries(cases).forEach(c => {
|
|
36
|
+
it(`From ${c[0]}`, () => {
|
|
37
|
+
const result = toSentence(c[1])
|
|
38
|
+
expect(result, c[0]).toBe('Case with long name 100')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('To Title', _ => {
|
|
44
|
+
Object.entries(cases).forEach(c => {
|
|
45
|
+
it(`From ${c[0]}`, () => {
|
|
46
|
+
const result = toTitle(c[1])
|
|
47
|
+
expect(result, c[0]).toBe('Case With Long Name 100')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes leading whitespaces from a Markdown content so they can be rendered appropriately.
|
|
3
|
+
* @param {string} content - The content to treat
|
|
4
|
+
* @param {Boolean} options.inline - Whether content is inline, not block
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function treatMarkdownWhitespace(
|
|
8
|
+
content,
|
|
9
|
+
{ inline, isSlot = false } = {},
|
|
10
|
+
) {
|
|
11
|
+
const lines = content.split('\n')
|
|
12
|
+
|
|
13
|
+
// Check for inline content
|
|
14
|
+
if (lines[0] !== '' && inline) return { content, inline: true }
|
|
15
|
+
|
|
16
|
+
// If it's not inline content, we need to strip indentation
|
|
17
|
+
// For this, we need to check for the first new line with leading spaces
|
|
18
|
+
const firstLineWithLeadingSpacesIndex = lines.findIndex(line =>
|
|
19
|
+
line.match(/^\s+/),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const firstLineWithLeadingSpaces = lines[firstLineWithLeadingSpacesIndex]
|
|
23
|
+
|
|
24
|
+
const indentation =
|
|
25
|
+
firstLineWithLeadingSpaces?.match(/^\s+/)?.[0]?.length ?? 0
|
|
26
|
+
|
|
27
|
+
// Once the indentation is found, we strip the indentation from each line. This treats the Markdown whitespace and prevents it from being rendered as code blocks.
|
|
28
|
+
const retContent = lines
|
|
29
|
+
.map((line, index) => {
|
|
30
|
+
if (index < firstLineWithLeadingSpacesIndex) return line + '\n'
|
|
31
|
+
return line.slice(indentation) + '\n'
|
|
32
|
+
})
|
|
33
|
+
.join('')
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: retContent,
|
|
37
|
+
inline,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts object into a query string
|
|
3
|
+
* @param {Object} object
|
|
4
|
+
* @returns
|
|
5
|
+
*/
|
|
6
|
+
export function toQueryString(object) {
|
|
7
|
+
const searchParams = new URLSearchParams(object)
|
|
8
|
+
return searchParams.toString()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function plural(count, noun, suffix = 's') {
|
|
12
|
+
console.warn('plural is deprecated. Use pluralize instead')
|
|
13
|
+
return `${count} ${noun}${count !== 1 ? suffix : ''}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function stripNumbers(string) {
|
|
17
|
+
return string.replace(/\d*/, '')
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { toKebab } from '../strings/convert-case/convert-case.js'
|
|
2
|
+
|
|
3
|
+
export function toStyleString(style) {
|
|
4
|
+
return Object.entries(style)
|
|
5
|
+
.map(([k, v]) => {
|
|
6
|
+
if (k.startsWith('--')) return `${k}:${v}`
|
|
7
|
+
else return `${toKebab(k)}:${v}`
|
|
8
|
+
})
|
|
9
|
+
.join('; ')
|
|
10
|
+
.concat(';')
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './symbols.js'
|
package/node/common.js
ADDED
package/node/dirname.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns __dirname
|
|
6
|
+
* @param url — import.meta.url
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function __dirname(url) {
|
|
10
|
+
if (!url) throw Error("Please provide 'import.meta.url' to dirname")
|
|
11
|
+
return path.dirname(fileURLToPath(url))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Just an alias
|
|
15
|
+
export function dirname(url) {
|
|
16
|
+
return __dirname(url)
|
|
17
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import glob from 'glob-promise'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { sort } from '../lib/arrays/index.js'
|
|
5
|
+
|
|
6
|
+
// Newer and nicer API
|
|
7
|
+
export async function createCache(dir) {
|
|
8
|
+
const absDirpath = path.join(process.cwd(), dir)
|
|
9
|
+
await fs.mkdir(absDirpath, { recursive: true })
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
async cacheAt(file) {
|
|
13
|
+
if (file) {
|
|
14
|
+
// Return the cached file's modified timestamp
|
|
15
|
+
const absFilepath = path.join(absDirpath, file)
|
|
16
|
+
try {
|
|
17
|
+
const stat = await fs.stat(absFilepath)
|
|
18
|
+
return stat.mtimeMs
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return 0
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
// Return the last modified timestamp of the cache directory
|
|
24
|
+
return getLastModifiedTime(dir + '/**/*')
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
getAbsPath(file) {
|
|
29
|
+
return path.join(absDirpath, file)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async hasFile(file) {
|
|
33
|
+
const absFilepath = path.join(absDirpath, file)
|
|
34
|
+
try {
|
|
35
|
+
await fs.stat(absFilepath)
|
|
36
|
+
return true
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async loadFile(file) {
|
|
43
|
+
try {
|
|
44
|
+
const absFilepath = path.join(absDirpath, file)
|
|
45
|
+
return fs.readFile(absFilepath)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`File not found: ${file}`)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async saveFile(file, data) {
|
|
52
|
+
const absfilepath = path.join(absDirpath, file)
|
|
53
|
+
await fs.writeFile(absfilepath, data)
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async loadJSON(file) {
|
|
57
|
+
const data = await this.loadFile(file)
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(data)
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.warn(`Error parsing JSON: ${file}`)
|
|
62
|
+
return {}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async saveJSON(file, data) {
|
|
67
|
+
await this.saveFile(file, JSON.stringify(data))
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Legacy, not so nice API
|
|
73
|
+
export async function fileCache({ dir, file }) {
|
|
74
|
+
// Makes the path if it doesn't exist
|
|
75
|
+
await fs.mkdir(path.join(process.cwd(), dir), { recursive: true })
|
|
76
|
+
const cachePath = path.join(process.cwd(), dir, `${file}`)
|
|
77
|
+
|
|
78
|
+
let modifiedAt = 0
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const stat = await fs.stat(cachePath)
|
|
82
|
+
modifiedAt = stat.mtimeMs
|
|
83
|
+
} catch (error) {} // Silent error
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
modifiedAt,
|
|
87
|
+
|
|
88
|
+
async load() {
|
|
89
|
+
return fs.readFile(cachePath)
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async save(data) {
|
|
93
|
+
await fs.writeFile(cachePath, data)
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async loadJSON() {
|
|
97
|
+
return JSON.parse(await fs.readFile(cachePath))
|
|
98
|
+
},
|
|
99
|
+
async saveJSON(data) {
|
|
100
|
+
await fs.writeFile(cachePath, JSON.stringify(data))
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getLastModifiedTime(globPath) {
|
|
106
|
+
let files
|
|
107
|
+
|
|
108
|
+
if (typeof globPath === 'string') {
|
|
109
|
+
files = await glob(removeFirstSlash(globPath))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(globPath)) {
|
|
113
|
+
const a = await Promise.all(
|
|
114
|
+
globPath.map(path => glob(removeFirstSlash(path))),
|
|
115
|
+
)
|
|
116
|
+
files = a.flat()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
files = await Promise.all(files.map(fs.stat))
|
|
120
|
+
files = files.map(file => file.mtimeMs)
|
|
121
|
+
|
|
122
|
+
const sorted = sort(files, { sortBy: 'mtimeMs', order: 'desc' })
|
|
123
|
+
return sorted[0] || 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function getLatestModifiedTime(globPath) {
|
|
127
|
+
console.warn(
|
|
128
|
+
'`getLatestModifiedTime` has been renamed to `getLastModifiedTime` and will be deprecated in future versions. Please use `getLastModifiedTime` instead.',
|
|
129
|
+
)
|
|
130
|
+
return getLastModifiedTime(globPath)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function removeFirstSlash(x) {
|
|
134
|
+
return x.startsWith('/') ? x.slice(1) : x
|
|
135
|
+
}
|
package/node/file.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Common FS stuff
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export async function mkdir(path) {
|
|
6
|
+
return fs.mkdir(path, { recursive: true })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function copyFile(from, to) {
|
|
10
|
+
const dir = path.dirname(to)
|
|
11
|
+
await mkdir(dir)
|
|
12
|
+
await fs.copyFile(from, to)
|
|
13
|
+
}
|
package/node/hash.js
ADDED
package/node/index.js
ADDED
package/node/pkce.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// For Node only.
|
|
2
|
+
// Check the utils version for browsers.
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { randomString } from './random-string.js'
|
|
5
|
+
|
|
6
|
+
export async function PKCE() {
|
|
7
|
+
const codeVerifier = await randomString()
|
|
8
|
+
const codeChallenge = await getCodeChallenge(codeVerifier)
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
state: await randomString(),
|
|
12
|
+
code_verifier: codeVerifier,
|
|
13
|
+
code_challenge: codeChallenge,
|
|
14
|
+
code_challenge_method: 'S256',
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generates code challenge in node
|
|
20
|
+
* @param {string} verifier
|
|
21
|
+
* @returns string
|
|
22
|
+
*/
|
|
23
|
+
async function getCodeChallenge(verifier) {
|
|
24
|
+
// Hash to code verifier with SHA-256
|
|
25
|
+
const hash = crypto.createHash('sha256').update(verifier).digest()
|
|
26
|
+
|
|
27
|
+
// // Base64url encode the hash
|
|
28
|
+
return hash
|
|
29
|
+
.toString('base64')
|
|
30
|
+
.replace(/\+/g, '-')
|
|
31
|
+
.replace(/\//g, '_')
|
|
32
|
+
.replace(/=/g, '')
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// For Node only
|
|
2
|
+
import crypto from 'node:crypto'
|
|
3
|
+
|
|
4
|
+
export async function randomString(length = 32) {
|
|
5
|
+
return crypto.randomBytes(length).toString('base64')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function randomStringSync(length = 32) {
|
|
9
|
+
return crypto.randomBytes(length).toString('hex')
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@splendidlabz/utils",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./lib/*": "./lib/*.js",
|
|
10
|
+
"./dom": "./dom/index.js",
|
|
11
|
+
"./dom/*": "./dom/*.js",
|
|
12
|
+
"./actions": "./actions/index.js",
|
|
13
|
+
"./actions/*": "./actions/*.js",
|
|
14
|
+
"./node": "./node/index.js",
|
|
15
|
+
"./node/*": "./node/*.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"lint": "eslint . --fix",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest"
|
|
21
|
+
},
|
|
22
|
+
"author": "Zell Liew <zellwk@gmail.com>",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"dompurify": "^3.1.6",
|
|
25
|
+
"glob-promise": "^6.0.5",
|
|
26
|
+
"pluralize": "^8.0.0",
|
|
27
|
+
"statuses": "^2.0.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@splendidlabz/eslint-config": "*",
|
|
31
|
+
"jsdom": "^24.0.0",
|
|
32
|
+
"np": "^8.0.4",
|
|
33
|
+
"vitest": "^1.4.0"
|
|
34
|
+
}
|
|
35
|
+
}
|