generate-pw 2.1.1 → 2.2.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/README.md CHANGED
@@ -25,12 +25,12 @@
25
25
  <img height=31 src="https://img.shields.io/npm/dm/generate-pw?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
26
26
  <a href="#%EF%B8%8F-mit-license">
27
27
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
- <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.1.1">
29
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.1.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
+ <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.2.0">
29
+ <img height=31 src="https://img.shields.io/badge/Latest_Build-2.2.0-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
30
30
  <a href="https://www.npmjs.com/package/generate-pw?activeTab=code">
31
31
  <img height=31 src="https://img.shields.io/npm/unpacked-size/generate-pw?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
32
- <a href="https://github.com/adamlui/js-utils/blob/generate-pw-2.1.1/generate-pw/dist/generate-pw.min.js">
33
- <img height=31 src="https://img.shields.io/github/size/adamlui/js-utils/generate-pw/dist/generate-pw.min.js?branch=generate-pw-2.1.1&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
32
+ <a href="#">
33
+ <img height=31 src="https://img.shields.io/bundlejs/size/generate-pw%402.1.2?label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
34
34
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:generate-pw/src/generate-pw.js">
35
35
  <img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Agenerate-pw%2Fsrc%2Fgenerate-pw.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
36
36
  <a href="https://github.com/toolleeo/cli-apps#password-managers">
@@ -94,14 +94,14 @@ const pw = require('generate-pw')
94
94
  #### <> HTML script tag:
95
95
 
96
96
  ```html
97
- <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js"></script>
97
+ <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js"></script>
98
98
  ```
99
99
 
100
100
  #### ES6:
101
101
 
102
102
  ```js
103
103
  (async () => {
104
- await import('https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js')
104
+ await import('https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js')
105
105
  // Your code here...
106
106
  })()
107
107
  ```
@@ -110,7 +110,7 @@ const pw = require('generate-pw')
110
110
 
111
111
  ```js
112
112
  ...
113
- // @require https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js
113
+ // @require https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js
114
114
  // ==/UserScript==
115
115
 
116
116
  // Your code here...
@@ -118,7 +118,7 @@ const pw = require('generate-pw')
118
118
 
119
119
  <br>
120
120
 
121
- **💡 Note:** To always import the latest version (not recommended in production!) remove the `@2.1.1` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/generate-pw/dist/generate-pw.min.js`
121
+ **💡 Note:** To always import the latest version (not recommended in production!) remove the `@2.2.0` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/generate-pw/dist/generate-pw.min.js`
122
122
 
123
123
  <br>
124
124
 
@@ -255,7 +255,7 @@ Name | Type | Description
255
255
  `uppercase` | Boolean | Allow uppercase letters in password(s). | `true`
256
256
  `similarChars` | Boolean | Include similar characters (e.g. o,0,O,i,l,1,\|) in password(s). | `false`
257
257
  `strict` | Boolean | Require at least one character from each allowed character set in password(s). | `true`
258
- `entropy` | Boolean | Calculate/log estimated entropy. | `false`
258
+ `entropy` | Boolean | Calculate/log estimated entropy. | `true`
259
259
 
260
260
  ##### _*Only available in [`generatePassword([options])`](#generatepasswordoptions) since [`generatePasswords(qty[, options])`](#generatepasswordsqty-options) takes a `qty` argument_
261
261
 
@@ -297,13 +297,15 @@ Boolean options:
297
297
  -S, --similar-chars Include similar characters in password(s).
298
298
  -S, --unstrict Don't require at least one character from
299
299
  each allowed character set in password(s).
300
- -e, --entropy Calculate/log estimated entropy.
300
+ -E, --no-entropy Don't calculate/log estimated entropy.
301
301
  -q, --quiet Suppress all logging except errors.
302
302
 
303
303
  Commands:
304
304
  -i, --init Create config file (in project root).
305
305
  -h, --help Display help screen.
306
306
  -v, --version Show version number.
307
+ --stats Show npm stats.
308
+ --debug [targetKey] Show debug logs.
307
309
  ```
308
310
 
309
311
  #
@@ -327,7 +329,7 @@ export default {
327
329
  excludeUpperChars: false, // disallow uppercase letters in password(s)
328
330
  similarChars: false, // include similar chars in password(s)
329
331
  unstrict: false, // don't require 1+ char from each allowed charset in password(s)
330
- entropy: false, // calculate/log estimated entropy
332
+ noEntropy: false, // don't calculate/log estimated entropy
331
333
  quietMode: false // suppress all logging except errors
332
334
  }
333
335
  ```
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ (async () => {
4
+ 'use strict'
5
+
6
+ globalThis.env = {
7
+ args: process.argv.slice(2),
8
+ modes: { dev: /[\\/]src(?:[\\/]|$)/i.test(__dirname) },
9
+ paths: { lib: './lib' }
10
+ }
11
+ env.modes.debug = env.args.some(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
12
+
13
+ // Import LIBS
14
+ globalThis.log = require(`${env.paths.lib}/log`)
15
+ const clipboardy = require('node-clipboardy'),
16
+ { generatePassword } = require(`../generate-pw${ env.modes.dev ? '' : '.min' }.js`),
17
+ init = require(`${env.paths.lib}/init`)
18
+
19
+ await init.cli()
20
+
21
+ // Exec CMD arg if passed
22
+ if (cli.config.init) return init.configFile()
23
+ else if (cli.config.help) return log.help()
24
+ else if (cli.config.version) return log.version()
25
+ else if (cli.config.stats) return log.stats()
26
+
27
+ // Copy random PASSWORD(s)
28
+ const genOptions = {
29
+ length: cli.config.length,
30
+ qty: cli.config.qty,
31
+ strength: cli.config.mode || cli.config.strength,
32
+ charset: cli.config.charset,
33
+ exclude: cli.config.excludeChars,
34
+ numbers: !cli.config.excludeNums,
35
+ symbols: !cli.config.excludeSymbols,
36
+ lowercase: !cli.config.excludeLowerChars,
37
+ uppercase: !cli.config.excludeUpperChars,
38
+ similarChars: cli.config.similarChars,
39
+ strict: !cli.config.unstrict,
40
+ entropy: !cli.config.noEntropy,
41
+ verbose: !cli.config.quietMode
42
+ }
43
+ log.break()
44
+ clipboardy.writeSync([].concat(generatePassword(genOptions)).join('\n'))
45
+ log.ifNotQuiet(`${cli.msgs.info_copyingToClip}...`)
46
+
47
+ })()
@@ -0,0 +1,31 @@
1
+ const color = module.exports = {
2
+ nc: '\x1b[0m',
3
+ hex: {
4
+ br: '#ff0000', by: '#ffff00', bo: '#ffa500', bg: '#00ff00',
5
+ bw: '#ffffff', gry: '#808080', blk: '#000000', tlBG: '#008080'
6
+ },
7
+ schemes: {
8
+ get default() {
9
+ return [
10
+ '#00e5bc', '#18c8ae', '#30ac9f', '#488f91', '#607383',
11
+ '#775674', '#8f3966', '#a71d57', '#bf0049', '#9a1b5e'
12
+ ].map(color.hexToANSI)
13
+ },
14
+ get rainbow() {
15
+ return [
16
+ '#e41a1c', '#ff7f00', '#ffff33', '#4daf4a', '#377eb8',
17
+ '#984ea3', '#f781bf', '#999999', '#a65628', '#d95f02'
18
+ ].map(color.hexToANSI)
19
+ }
20
+ },
21
+
22
+ hexToANSI(hex) {
23
+ const r = parseInt(hex.slice(1,3), 16),
24
+ g = parseInt(hex.slice(3,5), 16),
25
+ b = parseInt(hex.slice(5,7), 16)
26
+ return `\x1b[38;2;${r};${g};${b}m`
27
+ }
28
+ }
29
+
30
+ for (const hexKey of Object.keys(color.hex)) // add color[hexKey] getters that return ANSI
31
+ Object.defineProperty(color, hexKey, { get: () => color.hexToANSI(color.hex[hexKey]) })
@@ -0,0 +1,30 @@
1
+ module.exports = {
2
+
3
+ atomicWrite(filePath, data, encoding = 'utf8') { // to prevent TOCTOU
4
+ const fs = require('fs'),
5
+ path = require('path'),
6
+ tmpPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.tmp`)
7
+ fs.writeFileSync(tmpPath, data, encoding) ; fs.renameSync(tmpPath, filePath)
8
+ },
9
+
10
+ fetch(url) { // to support Node.js < v21
11
+ return typeof fetch == 'undefined' ? new Promise((resolve, reject) => { // using https?.get()
12
+ const protocol = url.match(/^([^:]+):\/\//)[1]
13
+ if (!/^https?$/.test(protocol))
14
+ reject(new Error(`${cli.msgs.error_invalidURL}.`))
15
+ require(protocol).get(url, resp => {
16
+ let rawData = ''
17
+ resp.on('data', chunk => rawData += chunk)
18
+ resp.on('end', () => resolve({ json: () => JSON.parse(rawData), text: () => rawData }))
19
+ }).on('error', reject)
20
+ }) : fetch(url) // using Node.js fetch()
21
+ },
22
+
23
+ flatten(json, { key = 'message' } = {}) { // eliminate need to ref nested keys
24
+ const flatObj = {}
25
+ for (const jsonKey in json) flatObj[jsonKey] =
26
+ typeof json[jsonKey] == 'object' && key in json[jsonKey] ? json[jsonKey][key]
27
+ : json[jsonKey]
28
+ return flatObj
29
+ }
30
+ }
@@ -0,0 +1,49 @@
1
+ const language = require('./language'),
2
+ settings = require('./settings')
3
+
4
+ const dataPath = `../../${ env.modes.dev ? '../' : 'data/' }`
5
+
6
+ module.exports = {
7
+
8
+ async cli() {
9
+ Object.assign(globalThis.cli ??= {}, require(`${dataPath}package-data.json`))
10
+ cli.msgs = await language.getMsgs('en')
11
+ cli.msgs = await language.getMsgs(cli.lang = settings.load('uiLang') || (
12
+ env.modes.debug ? language.generateRandomLang({ excludes: ['en'] }) : language.getSysLang() ))
13
+ cli.urls.cliDocs = `${cli.urls.docs}/#-command-line-usage`
14
+ if (!cli.lang.startsWith('en')) { // localize cli.urls.cliDocs
15
+ cli.docLocale = cli.lang.replace('_', '-').toLowerCase()
16
+ cli.docLocales ??= await language.getDocLocales()
17
+ if (cli.docLocales?.includes(cli.docLocale))
18
+ log.debug(cli.urls.cliDocs = `${cli.urls.docs}/${cli.docLocale}#readme`)
19
+ }
20
+ settings.load() // all keys to cli.config
21
+ },
22
+
23
+ async configFile(filename = settings.configFilename) {
24
+ const fs = require('fs'),
25
+ path = require('path'),
26
+ paths = { target: path.resolve(process.cwd(), filename) }
27
+
28
+ if (fs.existsSync(paths.target)) // use existing config file
29
+ return log.warn(`${cli.msgs.warn_configFileExists}:`, paths.target)
30
+ if (fs.existsSync(paths.src = path.resolve(__dirname, `${dataPath}${filename}`)))
31
+ fs.copyFileSync(paths.src, paths.target) // use found template
32
+
33
+ else { // use jsDelivr copy
34
+ const jsdURL = `${require('./jsdelivr').pkgVerURL()}/${filename}`
35
+ log.data(`${cli.msgs.info_fetchingRemoteConfigFrom} ${jsdURL}...`)
36
+ try {
37
+ const data = require('./data'),
38
+ resp = await data.fetch(jsdURL)
39
+ if (resp.ok) data.atomicWrite(paths.target, await resp.text())
40
+ else return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${jsdURL} (${resp.status})`)
41
+ } catch (err) {
42
+ return log.warn(`${cli.msgs.warn_remoteConfigFailed}: ${jsdURL} ${err.message}`) }
43
+ }
44
+
45
+ log.success(`${cli.msgs.info_configFileCreated}: ${paths.target}\n`)
46
+ log.tip(`${cli.msgs.tip_editToSetDefaults}.`)
47
+ log.tip(`${cli.msgs.tip_cliArgsPrioritized}.`)
48
+ }
49
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+
3
+ pkgVerURL(version) {
4
+ version ||= cli.version ||= require('./pkg').getVer('local') || 'none'
5
+ const verTag = !/^\d+\.\d+\.\d+$/.test(version) ? 'latest' : `${cli.name}-${version}`
6
+ return `${cli.urls.jsdelivr}@${verTag}/${cli.name}`
7
+ },
8
+
9
+ commitURL(hash = 'latest') { return `${cli.urls.jsdelivr}@${hash}/${cli.name}` }
10
+ }
@@ -0,0 +1,106 @@
1
+ const data = require('./data')
2
+
3
+ module.exports = {
4
+
5
+ formatCode(langCode) { // to match locale dir name
6
+ return langCode.replace(
7
+ /([a-z]{2,8})[-_]([a-z]{2})/i, (_, lang, region) =>`${lang.toLowerCase()}_${region.toUpperCase()}`) },
8
+
9
+ async getDocLocales() {
10
+ cli.version ||= require('./pkg').getVer('local') || 'none'
11
+ const jsdURL = `${require('./jsdelivr').pkgVerURL()}/docs/`,
12
+ locales = []
13
+ try {
14
+ const respText = await (await data.fetch(jsdURL)).text(),
15
+ reLocale = /href=".*\/docs\/([^/]+)\/"/g
16
+ let match ; while ((match = reLocale.exec(respText))) locales.push(match[1]) // store locale dir names
17
+ } catch (err) {
18
+ log.warn(`${cli.msgs.warn_docLocalesFetchFailed}:`, err.message)
19
+ }
20
+ return locales
21
+ },
22
+
23
+ generateRandomLang({ includes = [], excludes = [] } = {}) {
24
+ const fs = require('fs'),
25
+ path = require('path')
26
+
27
+ let locales = includes.length ? includes : (() => {
28
+
29
+ // Read cache if found
30
+ const cacheDir = path.join(__dirname, '..', '.cache'),
31
+ localeCache = path.join(cacheDir, 'locales.json')
32
+ if (fs.existsSync(localeCache))
33
+ try { return JSON.parse(fs.readFileSync(localeCache, 'utf8')) } catch (err) {}
34
+
35
+ // Discover pkg _locales
36
+ const localesDir = path.resolve(process.cwd(), '_locales')
37
+ if (!fs.existsSync(localesDir)) return ['en']
38
+ const locales = fs.readdirSync(localesDir, { withFileTypes: true })
39
+ .filter(entry => entry.isDirectory()).map(entry => entry.name)
40
+ .filter(name => /^\w{2}[-_]?\w{0,2}$/.test(name))
41
+
42
+ // Cache result
43
+ fs.mkdirSync(cacheDir, { recursive: true })
44
+ data.atomicWrite(localeCache, JSON.stringify(locales, null, 2))
45
+
46
+ return locales
47
+ })()
48
+
49
+ // Filter out excludes
50
+ const excludeSet = new Set(excludes)
51
+ locales = locales.filter(locale => !excludeSet.has(locale))
52
+
53
+ // Get random language
54
+ let randomLang = 'en'
55
+ if (locales.length)
56
+ randomLang = locales[Math.floor(Math.random() * locales.length)]
57
+ log.debug(`Random language: ${randomLang}`)
58
+
59
+ return randomLang
60
+ },
61
+
62
+ async getMsgs(langCode = 'en') {
63
+ langCode = module.exports.formatCode(langCode)
64
+ if (env.msgs && langCode == cli.lang) return env.msgs // don't re-fetch same msgs
65
+
66
+ let msgs = data.flatten( // local ones
67
+ require(`../../${ env.modes.dev ? '../_locales/en/' : 'data/' }messages.json`))
68
+
69
+ if (!langCode.startsWith('en')) { // fetch non-English msgs from jsDelivr
70
+ const msgHostURL = `${require('./jsdelivr').commitURL(cli.commitHashes.locales)}/_locales/`
71
+ let msgHref = `${msgHostURL}${langCode}/messages.json`, msgFetchesTried = 0
72
+ while (msgFetchesTried < 3)
73
+ try { // fetch remote msgs
74
+ msgs = data.flatten(await (await data.fetch(msgHref)).json())
75
+ break
76
+ } catch (err) { // retry up to 2X (region-stripped + EN)
77
+ msgFetchesTried++ ; if (msgFetchesTried >= 3) break
78
+ log.debug(msgHref = langCode.includes('-') && msgFetchesTried == 1 ?
79
+ msgHref.replace(/([^_]*)_[^/]*(\/.*)/, '$1$2') // strip region before retrying
80
+ : `${msgHostURL}en/messages.json` // else use EN msgs
81
+ )
82
+ }
83
+ }
84
+
85
+ return msgs
86
+ },
87
+
88
+ getSysLang() {
89
+ try {
90
+ if (process.platform == 'win32')
91
+ return require('child_process').execSync(
92
+ '(Get-Culture).TwoLetterISOLanguageName', { shell: 'powershell', encoding: 'utf-8' }
93
+ ).trim()
94
+ else { // macOS/Linux
95
+ const pe = process.env
96
+ return (pe.LANG || pe.LANGUAGE || pe.LC_ALL || pe.LC_MESSAGES || pe.LC_NAME)
97
+ .split('.')[0] // strip encoding e.g. .UTF-8
98
+ }
99
+ } catch (err) {
100
+ log.error(`${cli.msgs.error_failedToFetchSysLang}:`, err.message)
101
+ return 'en'
102
+ }
103
+ },
104
+
105
+ validateLangCode(code) { return typeof code != 'string' ? false : /^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(code) }
106
+ }
@@ -0,0 +1,171 @@
1
+ const colors = require('./color'),
2
+ { getDownloads, getVer } = require('./pkg'),
3
+ string = require('./string')
4
+
5
+ const nextMajVer = require('../../../package.json').version.replace(/^(\d+)\..*/, (_, major) => `${ +major +1 }.0.0`)
6
+
7
+ module.exports = {
8
+ colors,
9
+
10
+ configURL() { this.info(`${cli.msgs.info_exampleValidConfigFile}: ${cli.urls.config}`) },
11
+ configURLandExit(...args) { this.error(...args); this.configURL(); process.exit(1) },
12
+ data(msg) { console.log(`\n${colors.bw}${msg}${colors.nc}`) },
13
+ dim(msg) { console.log(`${colors.gry}${msg}${colors.nc}`) },
14
+ error(...args) { console.error(`\n${colors.br}ERROR:`, ...args, colors.nc) },
15
+ errorAndExit(...args) { this.error(...args); this.helpDocsCmdsDocsURL(); process.exit(1) },
16
+ ifNotQuiet(msg) { if (!cli.config.quietMode) this.info(msg) },
17
+ info(msg) { console.info(`\n${colors.schemes.default[0]}${msg}${colors.nc}`) },
18
+ break() { console.log() },
19
+ tip(msg) { console.info(`${colors.by}TIP: ${msg}${colors.nc}`) },
20
+ success(msg) { console.log(`\n${colors.bg}${msg}${colors.nc}`) },
21
+ warn(...args) { console.warn(`\n${colors.bo}WARNING:`, ...args, colors.nc) },
22
+
23
+ argDoesNothing(arg) {
24
+ this.warn(`${cli.msgs.warn_option} ${arg} ${cli.msgs.warn_noLongerHasAnyEffect} ${
25
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`)
26
+ },
27
+
28
+ configKeyReplacedBy(oldKey, newKey, oldVal) {
29
+ if (!this[`${oldKey}Warned`]) {
30
+ this.warn(
31
+ `${cli.msgs.info_configFile} ${cli.msgs.warn_option.toLowerCase()} '${oldKey}: ${oldVal}' ${
32
+ cli.msgs.warn_hasBeenReplacedBy} '${
33
+ newKey}: ${ isNegKey(oldKey) != isNegKey(newKey) ? !oldVal : oldVal }' ${
34
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`
35
+ )
36
+ this[`${oldKey}Warned`] = true
37
+ function isNegKey(key) { return /^(?:no|disable|exclude)[A-Z]/.test(key) }
38
+ }
39
+ },
40
+
41
+ debug(msg) {
42
+ if (!env.modes.debug) return
43
+ this.argIdx ??= env.args.findIndex(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
44
+ if (this.argIdx +1 < env.args.length && !env.args[this.argIdx +1].startsWith('-')) // use --debug [targetKey]
45
+ this.key ??= env.args[this.argIdx +1].replace('-', '_')
46
+ if (this.key)
47
+ this.val = cli.config[this.key] || `cli.config key "${this.key}" ${cli.msgs.warn_notFound.toLowerCase()}`
48
+ else
49
+ this.val = cli.config
50
+ msg += `\n${colors.gry}${JSON.stringify(this.val)}${colors.nc}`
51
+ console.debug(`\n${colors.by}DEBUG:`, msg, colors.nc)
52
+ },
53
+
54
+ help(includeSections = ['header', 'usage', 'params', 'flags', 'cmds']) {
55
+ cli.prefix = `${this.colors.tlBG}${this.colors.blk}\x1b[30m ${cli.name} ${this.colors.nc} `
56
+ const helpSections = {
57
+ header: [
58
+ `\n├ ${cli.prefix}${cli.msgs.pkg_copyright}.`,
59
+ `${cli.prefix}${cli.msgs.prefix_source}: ${cli.urls.src}`
60
+ ],
61
+ usage: [
62
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_usage}:${this.colors.nc}`,
63
+ ` ${this.colors.bw}» ${this.colors.bg}${cli.cmdFormat}${this.colors.nc}`
64
+ ],
65
+ params: [
66
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_params}:${this.colors.nc}`,
67
+ ` --length=n ${cli.msgs.optionDesc_length}.`,
68
+ ` --qty=n ${cli.msgs.optionDesc_qty}.`,
69
+ ` --charset=chars ${cli.msgs.optionDesc_charset}.`,
70
+ ` --exclude=chars ${cli.msgs.optionDesc_exclude}.`,
71
+ ` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,
72
+ ` --config="path/to/file" ${cli.msgs.optionDesc_config}.`
73
+ ],
74
+ flags: [
75
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_flags}:${this.colors.nc}`,
76
+ ` -w, --weak ${cli.msgs.optionDesc_weak}.`,
77
+ ` -b, --basic ${cli.msgs.optionDesc_basic}.`,
78
+ ` -t, --strong ${cli.msgs.optionDesc_strong}.`,
79
+ ` -N, --no-numbers ${cli.msgs.optionDesc_excludeNums}.`,
80
+ ` -Y, --no-symbols ${cli.msgs.optionDesc_excludeSymbols}.`,
81
+ ` -L, --no-lowercase ${cli.msgs.optionDesc_noLower}.`,
82
+ ` -U, --no-uppercase ${cli.msgs.optionDesc_noUpper}.`,
83
+ ` -s, --similar-chars ${cli.msgs.optionDesc_similarChars}.`,
84
+ ` -S, --unstrict ${cli.msgs.optionDesc_unstrict}.`,
85
+ ` -E, --no-entropy ${cli.msgs.optionDesc_noEntropy}.`,
86
+ ` -q, --quiet ${cli.msgs.optionDesc_quiet}.`
87
+ ],
88
+ cmds: [
89
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_cmds}:${this.colors.nc}`,
90
+ ` -i, --init ${cli.msgs.optionDesc_init}.`,
91
+ ` -h, --help ${cli.msgs.optionDesc_help}.`,
92
+ ` -v, --version ${cli.msgs.optionDesc_version}.`,
93
+ ` -v, --stats ${cli.msgs.optionDesc_stats}.`,
94
+ ` -V, --debug ${cli.msgs.optionDesc_debug}.`
95
+ ]
96
+ }
97
+ includeSections.forEach(section => // print valid arg elems
98
+ helpSections[section]?.forEach(line => printHelpMsg(line, /header|usage/.test(section) ? 1 : 29)))
99
+ console.info(`\n${cli.msgs.info_moreHelp}, ${
100
+ cli.msgs.info_visit}: ${this.colors.bw}${cli.urls.cliDocs}${this.colors.nc}`)
101
+
102
+ function printHelpMsg(msg, indent) { // wrap msg + indent 2nd+ lines
103
+ const terminalWidth = process.stdout.columns || 80,
104
+ words = msg.match(/\S+|\s+/g),
105
+ lines = [], prefix = '| '
106
+
107
+ // Split msg into lines of appropriate lengths
108
+ let currentLine = ''
109
+ words.forEach(word => {
110
+ const lineLength = terminalWidth -( !lines.length ? 0 : indent )
111
+ if (currentLine.length + prefix.length + word.length > lineLength) { // cap/store it
112
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
113
+ currentLine = ''
114
+ }
115
+ currentLine += word
116
+ })
117
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
118
+
119
+ // Print formatted msg
120
+ lines.forEach((line, idx) => console.info(prefix +(
121
+ idx == 0 ? line // print 1st line unindented
122
+ : ' '.repeat(indent) + line // print subsequent lines indented
123
+ )))
124
+ }
125
+ },
126
+
127
+ helpDocsCmdsDocsURL() {
128
+ console.info(`\n${
129
+ cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${
130
+ colors.bw}${cli.name} --<docs|help>${colors.nc} ${
131
+ cli.msgs.info_or} ${cli.msgs.info_visit}\n${colors.by}${cli.urls.docs}${colors.nc}`
132
+ )
133
+ },
134
+
135
+ initCmd(invalidKey) {
136
+ if (invalidKey)
137
+ this.warn(
138
+ `${cli.msgs.error_invalidKey} '${invalidKey}' ${cli.msgs.error_foundIn}\n`
139
+ + `${log.colors.gry}${cli.configPath}`
140
+ )
141
+ if (!this.initTipped) {
142
+ this.break()
143
+ this.tip(`${
144
+ string.toTitleCase(cli.msgs.info_type)} '${cli.name} init' ${
145
+ cli.msgs.info_toCreateDefaultConfig}`
146
+ )
147
+ this.initTipped = true
148
+ }
149
+ },
150
+
151
+ invalidConfigKey(key) { if (!this[`${key}Tipped`]) { this.initCmd(key) ; this[`${key}Tipped`] = true } },
152
+
153
+ async stats(pkgName = cli.name, options = { ecosystem: 'npm', maxDays: 8, maxVers: 5, scheme: 'default' }) {
154
+ const pkgStats = await getDownloads(pkgName, options),
155
+ schemeData = colors.schemes[options.scheme]
156
+ if (!schemeData) return this.error(`Scheme '${options.scheme}' not found!`)
157
+ const colorMap = Object.fromEntries(schemeData.map((hex, idx) => [`c${idx}`, hex])),
158
+ statsTable = new (require('console-table-printer').Table)({ colorMap })
159
+ pkgStats.forEach((row, idx) => // build colored rows
160
+ statsTable.addRow(row, { color: `c${Math.floor(idx / pkgStats.length * schemeData.length)}` }))
161
+ statsTable.printTable()
162
+ },
163
+
164
+ version() {
165
+ this.info(cli.name)
166
+ this.data(`${
167
+ cli.msgs.prefix_globalVer}: ${ getVer('global') || 'none' }\n${
168
+ cli.msgs.prefix_localVer }: ${ getVer('local') || 'none' }`
169
+ )
170
+ }
171
+ }
@@ -0,0 +1,78 @@
1
+ const data = require('./data')
2
+
3
+ const endpoints = {
4
+ npmjsDLs: 'https://api.npmjs.org/downloads',
5
+ pepyProjects: 'https://pepy.tech/projects'
6
+ }
7
+
8
+ module.exports = {
9
+
10
+ async getDownloads(
11
+ pkgName, // e.g. some-npm-pkg, npm:@adamlui/minify.js, pypi:translate-messages
12
+ { ecosystem = 'npm', maxDays = 10, maxVers = 10 } = {}
13
+ ) {
14
+ if (pkgName.includes(':')) [ecosystem, pkgName] = pkgName.split(':')
15
+
16
+ if (/npm|node/i.test(ecosystem)) { // fetch from endpoints.npmjsDLs
17
+ function formatDate(date) { return date.toISOString().split('T')[0] }
18
+ const dates = { end: new Date(), start: new Date() }
19
+ dates.start.setMonth(dates.end.getMonth() -3)
20
+ const npmjsURL = `${endpoints.npmjsDLs}/range/${formatDate(dates.start)}:${formatDate(dates.end)}/${pkgName}`
21
+ log.info(`Fetching npm stats for ${pkgName}${
22
+ env.modes.debug ? ` from\n${log.colors.bw}${npmjsURL}` : '' }...\n`)
23
+ return (await (await data.fetch(npmjsURL)).json()).downloads // { downloads: [{ day, downloads }] }
24
+ .sort((a, b) => new Date(b.day) - new Date(a.day)) // new ⇅ old
25
+ .slice(0, maxDays) // cap rows
26
+ .map(({ day: date, downloads }) => ({ date, downloads }))
27
+
28
+ } else if (/^py/i.test(ecosystem)) { // fetch from endpoints.pepyProjects
29
+ let rows = []
30
+ const pepyURL = `${endpoints.pepyProjects}/${pkgName}`
31
+ log.info(`Fetching PyPI/mirror stats for ${pkgName}${
32
+ env.modes.debug ? ` from\n${log.colors.bw}${pepyURL}` : '' }...\n`)
33
+ const respText = await (await data.fetch(pepyURL)).text(),
34
+ rePush = /self\.__next_f\.push\(\[\d+,\s*"((?:\\.|[^"\\])*)"\]\)/g
35
+ let downloads = {}, match
36
+ while ((match = rePush.exec(respText))) {
37
+ try { // extract project.downloads
38
+ const inner = JSON.parse(`"${match[1]}"`),
39
+ data = JSON.parse(inner.substring(inner.indexOf(':') +1))
40
+ if (data[3]?.project) downloads = data[3].project.downloads
41
+ } catch (err) {}
42
+ }
43
+ rows = Object.entries(downloads)
44
+ .sort(([a], [b]) => new Date(b) - new Date(a)) // new ⇅ old
45
+ .slice(0, maxDays) // cap rows
46
+ .map(([date, data]) => ({ date, ...data }))
47
+ const activeVers = new Set()
48
+ rows.forEach(row => Object.keys(row).forEach(key => {
49
+ if (key != 'date' && row[key] > 0) activeVers.add(key) }))
50
+ const finalVers = [...activeVers]
51
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true })) // new ⇆ old
52
+ .slice(0, maxVers) // cap columns
53
+ return rows.map(row => {
54
+ const result = { date: row.date }
55
+ finalVers.forEach(ver => result[ver] = row[ver] || 0)
56
+ return result
57
+ })
58
+
59
+ } else return log.debug(`${ecosystem} not supported.`)
60
+ },
61
+
62
+ getVer(type = 'any') { // or <'global'|'local'>
63
+ let pkgVer
64
+ if (type != 'global')
65
+ try { // get local ver
66
+ const localManifestPath = require('path').resolve(
67
+ process.cwd(), 'node_modules', cli.name, 'package.json')
68
+ pkgVer = require(localManifestPath).version
69
+ } catch (err) { log.debug(`${cli.msgs.error_readingLocalPkgVer}: ${err.message}`) }
70
+ if (type == 'global' || type == 'all' && !pkgVer)
71
+ try { // get global ver
72
+ pkgVer = require('child_process').execSync(
73
+ `npm view ${JSON.stringify(cli.name)} version`
74
+ ).toString().trim()
75
+ } catch (err) { log.debug(`${cli.msgs.error_failedToFetchGlobalVer}: ${err.message}`) }
76
+ return pkgVer
77
+ }
78
+ }
@@ -0,0 +1,153 @@
1
+ const fs = require('fs'),
2
+ path = require('path')
3
+
4
+ ;(globalThis.cli ??= {}).config = {}
5
+
6
+ module.exports = {
7
+ configFilename: 'generate-pw.config.mjs',
8
+ configFileKeyWhitelist: ['strength'],
9
+
10
+ controls: {
11
+ length: { type: 'param', valType: 'positiveInt', defaultVal: 12, regex: /^--?length(?:[=\s].*|$)/},
12
+ qty: { type: 'param', valType: 'positiveInt', defaultVal: 1, regex: /^--?qu?a?n?ti?t?y(?:[=\s].*|$)/},
13
+ charset: { type: 'param', regex: /^--?char[-_]?se?t?(?:[=\s].*|$)/ },
14
+ excludeChars: { type: 'param', regex:/^--?exclude(?:[=\s].*|$)/ },
15
+ uiLang: { type: 'param', valType: 'langCode', regex: /^--?ui[-_]?lang(?:[=\s].*|$)/ },
16
+ config: { type: 'param', valType: 'filepath', regex: /^--?config(?:[=\s].*|$)/ },
17
+ weak: { type: 'flag', mode: true, regex: /^--?(?:w|weak)$/ },
18
+ basic: { type: 'flag', mode: true, regex: /^--?(?:b|basic)$/ },
19
+ strong: { type: 'flag', mode: true, regex: /^--?(?:t|strong)$/ },
20
+ excludeNums: { type: 'flag', regex: /^--?(?:N|(?:exclude|disable|no)[-_]?num(?:ber)?s?=?(?:true|1)?)$/ },
21
+ excludeSymbols: { type: 'flag', regex: /^--?(?:Y|(?:exclude|disable|no)[-_]?symbols?=?(?:true|1)?)$/ },
22
+ excludeLowerChars: {
23
+ type: 'flag',
24
+ regex: /^--?(?:L|(?:exclude|disable|no)[-_]?lower[-_]?(?:case)?|lower[-_]?(?:case)?=(?:false|0))$/
25
+ },
26
+ excludeUpperChars: {
27
+ type: 'flag',
28
+ regex: /^--?(?:U|(?:exclude|disable|no)[-_]?upper[-_]?(?:case)?|upper[-_]?(?:case)?=(?:false|0))$/
29
+ },
30
+ similarChars: { type: 'flag', regex: /^--?(?:s|(?:include[-_]?)?similar[-_]?chars?=?(?:true|1)?)$/ },
31
+ unstrict: { type: 'flag', regex: /^--?(?:S|(?:un[-_]?strict)?(?:[-_]?mode)?)$/ },
32
+ noEntropy: { type: 'flag', regex: /^--?(?:E|no[-_]?e(?:ntropy)?)$/ },
33
+ quietMode: { type: 'flag', regex: /^--?q(?:uiet)?(?:[-_]?mode)?$/ },
34
+ init: { type: 'cmd', regex: /^-{0,2}i(?:nit)?$/ },
35
+ help: { type: 'cmd', regex: /^--?h(?:elp)?$/ },
36
+ version: { type: 'cmd', regex: /^--?ve?r?s?i?o?n?$/ },
37
+ stats: { type: 'cmd', regex: /^--?stats?$/ },
38
+ entropy: { type: 'legacy', replacedBy: 'noEntropy', regex: /^--?e(?:ntropy)?$/ }
39
+ },
40
+
41
+ load(ctrlKeys = Object.keys(this.controls)) {
42
+ const inputCtrlKeys = [].concat(ctrlKeys) // force array
43
+
44
+ if (!cli.defaultsSet && !arguments.length) { // init all defaults on arg-less load()
45
+ inputCtrlKeys.forEach(key => {
46
+ const ctrl = this.controls[key] ; if (ctrl.mode || ctrl.type == 'legacy') return
47
+ cli.config[key] ??= ctrl.defaultVal ?? ( ctrl.type == 'param' ? '' : false )
48
+ })
49
+ cli.defaultsSet = true
50
+ log.debug('All cli.config default vals set!')
51
+ }
52
+
53
+ if (!cli.configPathTried) { // init config file path
54
+ const configArg = env.args.find(arg => this.controls.config.regex.test(arg))
55
+
56
+ if (configArg) { // resolve input path, then validate
57
+ if (!/=/.test(configArg))
58
+ log.errorAndExit(`[${configArg}] ${cli.msgs.error_mustIncludePath}`)
59
+ const inputPath = configArg.split('=')[1]
60
+ cli.configPath = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath)
61
+ if (!fs.existsSync(cli.configPath))
62
+ log.configURLandExit(`${cli.msgs.error_configFileNotFound}:`, cli.configPath)
63
+
64
+ } else // auto-discover .config.[mc]?js file
65
+ for (const configExt of ['.mjs', '.cjs', '.js']) {
66
+ const autoPath = path.resolve(process.cwd(), this.configFilename.replace(/\.[^.]+$/, configExt))
67
+ if (fs.existsSync(autoPath)) { cli.configPath = autoPath ; break }
68
+ }
69
+
70
+ cli.configPathTried = true
71
+ }
72
+
73
+ if (cli.configPath) // load from config file
74
+ try {
75
+ const mod = require(cli.configPath), fileConfig = mod?.default ?? mod
76
+ if (!fileConfig || typeof fileConfig != 'object')
77
+ log.configURLandExit(`${cli.msgs.error_invalidConfigFile}.`)
78
+ ;(arguments.length ? inputCtrlKeys : Object.keys(fileConfig)).forEach(key => {
79
+ if (!(key in fileConfig)) return
80
+ const val = fileConfig[key], ctrl = this.controls[key]
81
+ if (!ctrl) {
82
+ if (this.configFileKeyWhitelist && !this.configFileKeyWhitelist.includes(key))
83
+ log.invalidConfigKey(key)
84
+ return
85
+ } else if (ctrl.type == 'legacy' && ctrl.replacedBy) {
86
+ if (key.toLowerCase().includes('no') != ctrl.replacedBy.toLowerCase().includes('no'))
87
+ cli.config[ctrl.replacedBy] = !val // assign opposite val to current key
88
+ else // assign direct val to current key
89
+ cli.config[ctrl.replacedBy] = val
90
+ return log.configKeyReplacedBy(key, ctrl.replacedBy, val)
91
+ }
92
+ cli.config[key] = val
93
+ })
94
+ if (!arguments.length) log.debug('Config file loaded!')
95
+ } catch (err) {
96
+ log.configURLandExit(`${cli.msgs.error_failedToLoadConfigFile}:`, cli.configPath, `\n${err.message}`) }
97
+
98
+ for (let i = 0 ; i < env.args.length ; i++) { // load from CLI arg (overriding config file loads)
99
+ const arg = env.args[i]
100
+ if (/^[^-]|--?(?:config|debug)/.test(arg) && arg != 'init') continue
101
+ const ctrlKey = Object.keys(this.controls).find(key => this.controls[key]?.regex?.test(arg))
102
+ if (!ctrlKey && cli.msgs) log.errorAndExit(`[${arg}] ${cli.msgs.error_notRecognized}.`)
103
+ if (!inputCtrlKeys.includes(ctrlKey)) return // don't process env.args when load() specific keys
104
+ const ctrl = this.controls[ctrlKey]
105
+ if (ctrl.type == 'legacy') { log.argDoesNothing(arg) ; continue }
106
+ if (ctrl.mode) // set cli.config.mode to mode name
107
+ cli.config.mode = ctrlKey.replace(/mode$/i, '').toLowerCase()
108
+ else { // init flag/param/cmd cli.config[ctrlKey] val
109
+ if (ctrl.type == 'param')
110
+ cli.config[ctrlKey] =
111
+ arg.includes('=') ? arg.split('=')[1]?.trim() || '' // =val
112
+ : (i +1 < env.args.length && !env.args[i +1].startsWith('-')) ? env.args[++i] // dashless val
113
+ : '' // val-less --param passed
114
+ else // flag/cmd
115
+ cli.config[ctrlKey] = true
116
+ }
117
+ }
118
+
119
+ if (!arguments.length) log.debug('Args parsed!')
120
+
121
+ this.parseValidateConfig(inputCtrlKeys)
122
+ if (!arguments.length) log.debug('All cli.config vals parsed/validated!')
123
+
124
+ return inputCtrlKeys.length == 1 ? cli.config[inputCtrlKeys[0]] : cli.config
125
+ },
126
+
127
+ parseValidateConfig(ctrlKeys = Object.keys(this.controls)) {
128
+ const language = require('./language')
129
+ for (const key of [].concat(ctrlKeys)) {
130
+ const ctrl = this.controls[key], configVal = cli.config[key]
131
+
132
+ if (ctrl.parser && !ctrl.parsed) {
133
+ cli.config[key] = ctrl.parser(configVal) ; ctrl.parsed = true }
134
+
135
+ if (ctrl.valType) ({
136
+ positiveInt() {
137
+ const numVal = parseInt(configVal, 10)
138
+ if (isNaN(numVal) || numVal < 1)
139
+ log.errorAndExit(`[${key}] ${cli.msgs.error_nonPositiveNum}: ${configVal}`)
140
+ cli.config[key] = numVal
141
+ },
142
+ filepath() {
143
+ if (configVal && !fs.existsSync(configVal))
144
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidFilepath}: ${configVal}`)
145
+ },
146
+ langCode() {
147
+ if (configVal && !language.validateLangCode(configVal))
148
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidLangCode}: ${configVal}`)
149
+ }
150
+ })[ctrl.valType]()
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ toTitleCase(str) { return str.toLowerCase().replace(/\b\w/g, char => char.toUpperCase()) }
3
+ }
@@ -23,10 +23,10 @@ export default {
23
23
  similarChars: false, // include similar chars (e.g. o,0,O,i,l,1,\|) in password(s)
24
24
  unstrict: false, // don't require 1+ char from each allowed charset in password(s)
25
25
  charset: '', // only include chars in password(s)
26
- exclude: '', // exclude chars from password(s)
26
+ excludeChars: '', // exclude chars from password(s)
27
27
 
28
28
  // Info options
29
- entropy: false, // calculate/log estimated entropy
29
+ noEntropy: false, // calculate/log estimated entropy
30
30
  quietMode: false, // suppress all logging except errors
31
31
  uiLang: '' // ISO 639-1 code of language to display UI in
32
32
  }
@@ -1,6 +1,5 @@
1
1
  {
2
- "appName": { "message": "generate-pw" },
3
- "appCopyright": { "message": "© 2024–2026 Adam Lui & contributors under the MIT license" },
2
+ "pkg_copyright": { "message": "© 2024–2026 Adam Lui & contributors under the MIT license" },
4
3
  "prefix_globalVer": { "message": "Global version" },
5
4
  "prefix_localVer": { "message": "Local version" },
6
5
  "prefix_source": { "message": "Source" },
@@ -11,6 +10,8 @@
11
10
  "error_invalidLangCode": { "message": "is an invalid language code" },
12
11
  "error_invalidURL": { "message": "Invalid URL" },
13
12
  "error_invalidConfigFile": { "message": "Config file must export an object" },
13
+ "error_invalidKey": { "message": "Invalid key" },
14
+ "error_foundIn": { "message": "found in" },
14
15
  "error_configFileNotFound": { "message": "Config file not found" },
15
16
  "error_failedToLoadConfigFile": { "message": "Failed to load config file" },
16
17
  "error_failedToFetchGlobalVer": { "message": "Failed to fetch global version" },
@@ -20,12 +21,19 @@
20
21
  "warn_docLocalesFetchFailed": { "message": "Failed to fetch doc locales" },
21
22
  "warn_remoteConfigNotFound": { "message": "Remote config file not found" },
22
23
  "warn_remoteConfigFailed": { "message": "Failed to fetch remote config file" },
24
+ "warn_option": { "message": "Option" },
25
+ "warn_noLongerHasAnyEffect": { "message": "no longer has any effect" },
26
+ "warn_hasBeenReplacedBy": { "message": "has been replaced by" },
27
+ "warn_andWillBeRemoved": { "message": "and will be removed" },
28
+ "warn_notFound": { "message": "Not found" },
29
+ "info_configFile": { "message": "Config file" },
23
30
  "info_exampleValidConfigFile": { "message": "Example valid config file" },
24
31
  "info_copyingToClip": { "message": "Copying to clipboard" },
25
32
  "info_moreHelp": { "message": "For more help" },
26
33
  "info_type": { "message": "type" },
27
34
  "info_or": { "message": "or" },
28
35
  "info_visit": { "message": "visit" },
36
+ "info_toCreateDefaultConfig": { "message": "to create default config file" },
29
37
  "info_configFileCreated": { "message": "Config file created" },
30
38
  "info_fetchingRemoteConfigFrom": { "message": "Fetching remote config file from" },
31
39
  "tip_editToSetDefaults": { "message": "Edit this file to customize defaults" },
@@ -49,9 +57,11 @@
49
57
  "optionDesc_noUpper": { "message": "Disallow uppercase letters in password(s)" },
50
58
  "optionDesc_similarChars": { "message": "Include similar characters in password(s)" },
51
59
  "optionDesc_unstrict": { "message": "Don't require at least one character from each allowed character set in password(s)" },
52
- "optionDesc_entropy": { "message": "Calculate/log estimated entropy" },
60
+ "optionDesc_noEntropy": { "message": "Don't calculate/log estimated entropy" },
53
61
  "optionDesc_quiet": { "message": "Suppress all logging except errors" },
54
62
  "optionDesc_help": { "message": "Display help screen" },
55
63
  "optionDesc_init": { "message": "Create config file (in project root)" },
56
- "optionDesc_version": { "message": "Show version number" }
64
+ "optionDesc_version": { "message": "Show version number" },
65
+ "optionDesc_stats": { "message": "Show npm stats" },
66
+ "optionDesc_debug": { "message": "Show debug logs" }
57
67
  }
@@ -13,6 +13,6 @@
13
13
  "src": "https://github.com/adamlui/js-utils/tree/main/generate-pw/src"
14
14
  },
15
15
  "commitHashes": {
16
- "locales": "69618f8"
16
+ "locales": "78c0809"
17
17
  }
18
18
  }
@@ -3,4 +3,4 @@
3
3
  * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
4
  * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
5
  */
6
- function generatePassword(s={}){var e={verbose:!0,length:12,qty:1,strength:"",charset:"",exclude:"",numbers:!0,symbols:!0,lowercase:!0,uppercase:!0,similarChars:!1,strict:!0,entropy:!1};if(log.prefix="generatePassword()",validateOptions({options:s,defaultOptions:e,helpURL:"https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#generatepasswordoptions",exampleCall:"generatePassword({ verbose: false, numbers: true })"})){if(1<(s=(s={...e,...s}).strength&&(e=api.strengthPresets[s.strength.toLowerCase()])?{...s,...e}:s).qty){let{qty:e,...r}=s;return generatePasswords(e,r)}{var a,e="generatePasswords"==generatePassword.caller?.name;s.verbose&&!e&&log.info("Initializing character set...");let r=s.charset?.toString()||(s.numbers?api.charsets.numbers:"")+(s.symbols?api.charsets.symbols:"")+(s.lowercase?api.charsets.lower:"")+(s.uppercase?api.charsets.upper:""),t=(""==r&&(r=api.charsets.lower+api.charsets.upper),s.exclude&&(s.verbose&&!e&&log.info("Removing excluded characters..."),r=r.replace(new RegExp(`[${s.exclude}]`,"g"),"")),s.similarChars||(s.verbose&&!e&&log.info("Excluding similar characters..."),r=r.replace(/[o0Oil1|]/g,"")),s.verbose&&!e&&log.info("Generating password..."),"");for(let e=0;e<s.length;e++){var o=randomInt(0,r.length);t+=r[o]}return s.strict&&!s.charset&&(s.verbose&&!e&&log.info("Enforcing strict mode..."),a=["numbers","symbols","lower","upper"].filter(e=>s[e]||s[e+"case"]),t=strictify(t,a)),s.verbose&&(e||(log.info("Password generated!"),"undefined"!=typeof window&&log.info("Check returned string.")),s.entropy)&&(e=(a=r.length)<2?0:Math.log2(a**t.length),log.info(`Estimated entropy: ${e.toFixed(2)} bits (charset length: ${a})`)),t}}}function generatePasswords(r,t={}){var e="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#generatepasswordsqty-options",s={verbose:!0,length:12,strength:"",charset:"",exclude:"",numbers:!0,symbols:!0,lowercase:!0,uppercase:!0,similarChars:!1,strict:!0,entropy:!1};if(log.prefix="generatePasswords()",r=parseInt(r,10),isNaN(r)||r<1)return log.error("1st arg <qty> can only be an integer > 0."),log.helpURL(e);if(validateOptions({options:t,defaultOptions:s,helpURL:e,exampleCall:"generatePasswords(3, { verbose: false, symbols: true })"})){(t=(t={...s,...t}).strength&&(e=api.strengthPresets[t.strength.toLowerCase()])?{...t,...e}:t).verbose&&log.info(`Generating password${1<r?"s":""}...`);var a=[];for(let e=0;e<r;e++)a.push(generatePassword(t));return t.verbose&&(log.info(`Password${1<r?"s":""} generated!`),"undefined"!=typeof window)&&log.info("Check returned array."),a}}function strictify(o,n=["numbers","symbols","lower","upper"],e={}){var r="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#strictifypassword-requiredchartypes-options",i={verbose:!0};if(log.prefix="strictify()","string"!=typeof o)return log.error("1st arg <password> must be a string."),log.helpURL(r);var t,s=["numbers","symbols","lower","upper"];for(t of n=[].concat(n))if(!s.includes(t))return log.error(`2nd arg \`${t}\` is an invalid character type.`),void log.info([`Valid character types: ['${s.join("', '")}']`,"Pass one as a string or more as an array, or all types will be required.","For more help, please visit "+r].join("\n"));if(o.length<n.length){for(;n.length>o.length;)n.splice(randomInt(0,n.length),1);log.info("Reduced required char types to: "+n.join(", "))}if(validateOptions({options:e,defaultOptions:i,helpURL:r,exampleCall:"strictify('pa55word', ['symbol', 'upper'], { verbose: false })"})){e={...i,...e};let r={},t=[];n.forEach(e=>r[e]=!1);for(let e=0;e<o.length;e++)for(var l of n)!r[l]&&api.charsets[l].includes(o[e])&&(r[l]=!0,t.push(e));e.verbose&&log.info("Strictifying password...");var p,g=Math.min(o.length,n.length);let s=0,a=o;for(p of n)if(s<g&&!r[p]){let e;for(;e=randomInt(0,o.length),t.includes(e););t.push(e);var c=api.charsets[p]||api.charsets[p+"s"];a=a.substring(0,e)+c[randomInt(0,c.length)]+a.substring(e+1),s++}return e.verbose&&(0<s?(log.info("Password is now strict!"),log.info("Check returned string.")):(log.info(`Password already includes ${n.join(" + ")} characters!`),log.info("No modifications made."))),a}}function validateStrength(r,t={}){var e="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#validatestrengthpassword-options",s={minlength:12,minLower:1,minUpper:1,minNumber:1,minSymbol:1},a={verbose:!0};if(log.prefix="validateStrength()","string"!=typeof r)return log.error("1st arg <password> must be a string."),log.helpURL(e);if(validateOptions({options:t,defaultOptions:a,helpURL:e,exampleCall:"validateStrength('pa55word', { verbose: false })"})){(t={...a,...t}).verbose&&log.info("Validating password strength...");var o,n={lower:0,upper:0,number:0,symbol:0};for(o of r)for(var i of Object.keys(n))(api.charsets[i]||api.charsets[i+"s"]).includes(o)&&n[i]++;var l,p,g=[];r.length<s.minLength&&g.push(`Make it at least ${s.minLength} characters long.`);for(l of Object.keys(n))n[l]<s["min"+l[0].toUpperCase()+l.slice(1)]&&g.push(`Include at least one ${l}${["upper","lower"].includes(l)?"case letter":""}.`);let e=0;e+=r.length>=s.minLength?20:0;for(p of Object.keys(n))e+=n[p]>=s["min"+p[0].toUpperCase()+p.slice(1)]?20:0;return t.verbose&&(log.info("Password strength validated!"),log.info("Check returned object for score/recommendations.")),{strengthScore:e,recommendations:g,isGood:80<=e}}}function randomInt(e,r){var t;return"undefined"==typeof require?(t=(window.crypto||window.msCrypto)?.getRandomValues(new Uint32Array(1))[0]/4294967295||Math.random(),Math.floor(t*(r-e))+e):require("crypto").randomInt(e,r)}function validateOptions({options:e,defaultOptions:r,helpURL:t,exampleCall:s}){var a=Object.keys(r).filter(e=>"boolean"==typeof r[e]),o=Object.keys(r).filter(e=>Number.isInteger(r[e]));if("object"!=typeof e)return i=s.split(",").findIndex(e=>e.trim().startsWith("{"))+1,i+=["st","nd","rd"][i-1]||"th",log.error(`${"0th"==i?"[O":i+" arg [o"}ptions] can only be an object of key/vals.`),log.info("Example valid call:",s),log.validOptions(r),log.helpURL(t),!1;var n,i=["weak","basic","strong"];if("strength"in e&&e.strength&&!i.includes(e.strength.toLowerCase()))return log.error("[strength] must be one of: "+i.join(", ")),log.helpURL(t),!1;for(n in e){if(!Object.prototype.hasOwnProperty.call(r,n))return log.error(`\`${n}\` is an invalid option.`),log.validOptions(r),log.helpURL(t),!1;if(a.includes(n)&&"boolean"!=typeof e[n])return log.error(`[${n}] option can only be \`true\` or \`false\`.`),log.helpURL(t),!1;if(o.includes(n)&&(e[n]=parseInt(e[n],10),isNaN(e[n])||e[n]<1))return log.error(`[${n}] option can only be an integer > 0.`),log.helpURL(t),!1}return!0}Object.assign(globalThis.api??={},{name:"generate-pw",aliases:{generatePassword:["generate","generatepassword","generatepw","generatePw","generatePW","Generate","Generatepassword","GeneratePassword","Generatepw","GeneratePw","GeneratePW"],generatePasswords:["generatepasswords","generatepws","generatePws","generatePWs","generatePWS","Generatepasswords","GeneratePasswords","Generatepws","GeneratePws","GeneratePWs","GeneratePWS"],strictify:["Strictify"],validateStrength:["validate","Validate","validatestrength","Validatestrength","ValidateStrength"]},charsets:{lower:"abcdefghijklmnopqrstuvwxyz",upper:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",numbers:"0123456789",symbols:"!@#$%^&*()-_=+[]{}/\\|;:'\",.<>?"},strengthPresets:{weak:{length:6,lowercase:!0,uppercase:!1,numbers:!1,symbols:!1,similarChars:!0,strict:!1},basic:{length:8,lowercase:!0,uppercase:!0,numbers:!0,symbols:!1,similarChars:!0,strict:!1},strong:{length:12,lowercase:!0,uppercase:!0,numbers:!0,symbols:!0,similarChars:!1,strict:!0}}});let log={prefix:api.name,error(...e){console.error(this.prefix+" » ERROR:",...e)},helpURL(e=api.urls?.docs){this.info("For more help, please visit",e)},info(...e){console.info(this.prefix+" »",...e)},validOptions(e){var r=Object.keys(e).join(", "),e=JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:").replace(/"/g,"'").replace(/\n\s*/g," ");this.info(`Valid options: [${r}]`),this.info("If omitted, default settings are: "+e)}};api.exports={generatePassword:generatePassword,generatePasswords:generatePasswords,strictify:strictify,validateStrength:validateStrength};try{module.exports={...api.exports}}catch(e){}try{window.pw={...api.exports}}catch(e){}for(let r in api.aliases){try{api.aliases[r].forEach(e=>module.exports[e]??=module.exports[r])}catch(e){}try{api.aliases[r].forEach(e=>window.pw[e]??=window.pw[r])}catch(e){}}
6
+ function generatePassword(s={}){var e={verbose:!0,length:12,qty:1,strength:"",charset:"",exclude:"",numbers:!0,symbols:!0,lowercase:!0,uppercase:!0,similarChars:!1,strict:!0,entropy:!0};if(_log.prefix="generatePassword()",_validateOptions({options:s,defaultOptions:e,helpURL:"https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#generatepasswordoptions",exampleCall:"generatePassword({ verbose: false, numbers: true })"})){if(1<(s=(s={...e,...s}).strength&&(e=api.strengthPresets[s.strength.toLowerCase()])?{...s,...e}:s).qty){let{qty:e,...r}=s;return generatePasswords(e,r)}{var o,e="generatePasswords"==generatePassword.caller?.name;s.verbose&&!e&&_log.info("Initializing character set...");let r=s.charset?.toString()||(s.numbers?api.charsets.numbers:"")+(s.symbols?api.charsets.symbols:"")+(s.lowercase?api.charsets.lower:"")+(s.uppercase?api.charsets.upper:""),t=(""==r&&(r=api.charsets.lower+api.charsets.upper),s.exclude&&(s.verbose&&!e&&_log.info("Removing excluded characters..."),r=r.replace(new RegExp(`[${s.exclude}]`,"g"),"")),s.similarChars||(s.verbose&&!e&&_log.info("Excluding similar characters..."),r=r.replace(/[o0Oil1|]/g,"")),s.verbose&&!e&&_log.info("Generating password..."),"");for(let e=0;e<s.length;e++){var a=randomInt(0,r.length);t+=r[a]}return s.strict&&!s.charset&&(s.verbose&&!e&&_log.info("Enforcing strict mode..."),o=["numbers","symbols","lower","upper"].filter(e=>s[e]||s[e+"case"]),t=strictify(t,o)),s.verbose&&(e||(_log.info("Password generated!"),"undefined"!=typeof window&&_log.info("Check returned string.")),s.entropy)&&(e=(o=r.length)<2?0:Math.log2(o**t.length),_log.info(`Estimated entropy: ${e.toFixed(2)} bits (charset length: ${o})`)),t}}}function generatePasswords(r,t={}){var e="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#generatepasswordsqty-options",s={verbose:!0,length:12,strength:"",charset:"",exclude:"",numbers:!0,symbols:!0,lowercase:!0,uppercase:!0,similarChars:!1,strict:!0,entropy:!0};if(_log.prefix="generatePasswords()",r=parseInt(r,10),(isNaN(r)||r<1)&&_log.errHelpURLandThrow({errMsg:"1st arg <qty> can only be an integer > 0.",helpURL:e}),_validateOptions({options:t,defaultOptions:s,helpURL:e,exampleCall:"generatePasswords(3, { verbose: false, symbols: true })"})){(t=(t={...s,...t}).strength&&(e=api.strengthPresets[t.strength.toLowerCase()])?{...t,...e}:t).verbose&&_log.info(`Generating password${1<r?"s":""}...`);var o=[];for(let e=0;e<r;e++)o.push(generatePassword(t));return t.verbose&&(_log.info(`Password${1<r?"s":""} generated!`),"undefined"!=typeof window)&&_log.info("Check returned array."),o}}function strictify(a,n=["numbers","symbols","lower","upper"],e={}){var r,t="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#strictifypassword-requiredchartypes-options",i={verbose:!0},s=(_log.prefix="strictify()","string"!=typeof a&&_log.errHelpURLandThrow({errMsg:"1st arg <password> must be a string.",helpURL:t}),["numbers","symbols","lower","upper"]);for(r of n=[].concat(n))if(!s.includes(r))return _log.error(`2nd arg \`${r}\` is an invalid character type.`),void _log.info([`Valid character types: ['${s.join("', '")}']`,"Pass one as a string or more as an array, or all types will be required.","For more help, please visit "+t].join("\n"));if(a.length<n.length){for(;n.length>a.length;)n.splice(randomInt(0,n.length),1);_log.info("Reduced required char types to: "+n.join(", "))}if(_validateOptions({options:e,defaultOptions:i,helpURL:t,exampleCall:"strictify('pa55word', ['symbol', 'upper'], { verbose: false })"})){e={...i,...e};let r={},t=[];n.forEach(e=>r[e]=!1);for(let e=0;e<a.length;e++)for(var l of n)!r[l]&&api.charsets[l].includes(a[e])&&(r[l]=!0,t.push(e));e.verbose&&_log.info("Strictifying password...");var p,g=Math.min(a.length,n.length);let s=0,o=a;for(p of n)if(s<g&&!r[p]){let e;for(;e=randomInt(0,a.length),t.includes(e););t.push(e);var c=api.charsets[p]||api.charsets[p+"s"];o=o.substring(0,e)+c[randomInt(0,c.length)]+o.substring(e+1),s++}return e.verbose&&(0<s?(_log.info("Password is now strict!"),_log.info("Check returned string.")):(_log.info(`Password already includes ${n.join(" + ")} characters!`),_log.info("No modifications made."))),o}}function validateStrength(r,t={}){var e="https://github.com/adamlui/js-utils/tree/main/generate-pw/docs/#validatestrengthpassword-options",s={minlength:12,minLower:1,minUpper:1,minNumber:1,minSymbol:1},o={verbose:!0};if(_log.prefix="validateStrength()","string"!=typeof r&&_log.errHelpURLandThrow({errMsg:"1st arg <password> must be a string.",helpURL:e}),_validateOptions({options:t,defaultOptions:o,helpURL:e,exampleCall:"validateStrength('pa55word', { verbose: false })"})){(t={...o,...t}).verbose&&_log.info("Validating password strength...");var a,n={lower:0,upper:0,number:0,symbol:0};for(a of r)for(var i of Object.keys(n))(api.charsets[i]||api.charsets[i+"s"]).includes(a)&&n[i]++;var l,p,g=[];r.length<s.minLength&&g.push(`Make it at least ${s.minLength} characters long.`);for(l of Object.keys(n))n[l]<s["min"+l[0].toUpperCase()+l.slice(1)]&&g.push(`Include at least one ${l}${["upper","lower"].includes(l)?"case letter":""}.`);let e=0;e+=r.length>=s.minLength?20:0;for(p of Object.keys(n))e+=n[p]>=s["min"+p[0].toUpperCase()+p.slice(1)]?20:0;return t.verbose&&(_log.info("Password strength validated!"),_log.info("Check returned object for score/recommendations.")),{strengthScore:e,recommendations:g,isGood:80<=e}}}function randomInt(e,r){var t;return"undefined"==typeof require?(t=(window.crypto||window.msCrypto)?.getRandomValues(new Uint32Array(1))[0]/4294967295||Math.random(),Math.floor(t*(r-e))+e):require("crypto").randomInt(e,r)}function _validateOptions({options:e,defaultOptions:r,helpURL:t,exampleCall:s}){var o=Object.keys(r).filter(e=>"boolean"==typeof r[e]),a=Object.keys(r).filter(e=>Number.isInteger(r[e]));if("object"!=typeof e)return i=s.split(",").findIndex(e=>e.trim().startsWith("{"))+1,i+=["st","nd","rd"][i-1]||"th",_log.error(`${"0th"==i?"[O":i+" arg [o"}ptions] can only be an object of key/vals.`),_log.info("Example valid call:",s),_log.validOptions(r),_log.helpURL(t),!1;var n,i=["weak","basic","strong"];if("strength"in e&&e.strength&&!i.includes(e.strength.toLowerCase()))return _log.error("[strength] must be one of: "+i.join(", ")),_log.helpURL(t),!1;for(n in e){if(!Object.prototype.hasOwnProperty.call(r,n))return _log.error(`\`${n}\` is an invalid option.`),_log.validOptions(r),_log.helpURL(t),!1;if(o.includes(n)&&"boolean"!=typeof e[n])return _log.error(`[${n}] option can only be \`true\` or \`false\`.`),_log.helpURL(t),!1;if(a.includes(n)&&(e[n]=parseInt(e[n],10),isNaN(e[n])||e[n]<1))return _log.error(`[${n}] option can only be an integer > 0.`),_log.helpURL(t),!1}return!0}Object.assign(globalThis.api??={},{name:"generate-pw",regex:{generatePassword:/^gen(?:erate)?(?:password|pw)?$/i,generatePasswords:/^gen(?:erate)?(?:password|pw)s$/i,strictify:/^strictify$/i,validateStrength:/^validate(?:strength)?$/i},charsets:{lower:"abcdefghijklmnopqrstuvwxyz",upper:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",numbers:"0123456789",symbols:"!@#$%^&*()-_=+[]{}/\\|;:'\",.<>?"},strengthPresets:{weak:{length:6,lowercase:!0,uppercase:!1,numbers:!1,symbols:!1,similarChars:!0,strict:!1},basic:{length:8,lowercase:!0,uppercase:!0,numbers:!0,symbols:!1,similarChars:!0,strict:!1},strong:{length:12,lowercase:!0,uppercase:!0,numbers:!0,symbols:!0,similarChars:!1,strict:!0}}});let _log={prefix:api.name,errHelpURLandThrow({errMsg:e,helpURL:r}){throw this.error(e),this.helpURL(r),new Error(e)},error(...e){console.error(this.prefix+" » ERROR:",...e)},helpURL(e=api.urls?.docs){this.info("For more help, please visit",e)},info(...e){console.info(this.prefix+" »",...e)},validOptions(e){var r=Object.keys(e).join(", "),e=JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:").replace(/"/g,"'").replace(/\n\s*/g," ");this.info(`Valid options: [${r}]`),this.info("If omitted, default settings are: "+e)}};api.exports=new Proxy({generatePassword:generatePassword,generatePasswords:generatePasswords,strictify:strictify,validateStrength:validateStrength},{get(e,r){for(var[t,s]of Object.entries(api.regex))if(s.test(r))return e[t]}});try{module.exports=api.exports}catch(e){}try{Object.assign(window,api.exports)}catch(e){}
package/docs/README.md CHANGED
@@ -25,12 +25,12 @@
25
25
  <img height=31 src="https://img.shields.io/npm/dm/generate-pw?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
26
26
  <a href="#%EF%B8%8F-mit-license">
27
27
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
- <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.1.1">
29
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.1.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
+ <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.2.0">
29
+ <img height=31 src="https://img.shields.io/badge/Latest_Build-2.2.0-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
30
30
  <a href="https://www.npmjs.com/package/generate-pw?activeTab=code">
31
31
  <img height=31 src="https://img.shields.io/npm/unpacked-size/generate-pw?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
32
- <a href="https://github.com/adamlui/js-utils/blob/generate-pw-2.1.1/generate-pw/dist/generate-pw.min.js">
33
- <img height=31 src="https://img.shields.io/github/size/adamlui/js-utils/generate-pw/dist/generate-pw.min.js?branch=generate-pw-2.1.1&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
32
+ <a href="#">
33
+ <img height=31 src="https://img.shields.io/bundlejs/size/generate-pw%402.1.2?label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
34
34
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:generate-pw/src/generate-pw.js">
35
35
  <img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Agenerate-pw%2Fsrc%2Fgenerate-pw.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
36
36
  <a href="https://github.com/toolleeo/cli-apps#password-managers">
@@ -94,14 +94,14 @@ const pw = require('generate-pw')
94
94
  #### <> HTML script tag:
95
95
 
96
96
  ```html
97
- <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js"></script>
97
+ <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js"></script>
98
98
  ```
99
99
 
100
100
  #### ES6:
101
101
 
102
102
  ```js
103
103
  (async () => {
104
- await import('https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js')
104
+ await import('https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js')
105
105
  // Your code here...
106
106
  })()
107
107
  ```
@@ -110,7 +110,7 @@ const pw = require('generate-pw')
110
110
 
111
111
  ```js
112
112
  ...
113
- // @require https://cdn.jsdelivr.net/npm/generate-pw@2.1.1/dist/generate-pw.min.js
113
+ // @require https://cdn.jsdelivr.net/npm/generate-pw@2.2.0/dist/generate-pw.min.js
114
114
  // ==/UserScript==
115
115
 
116
116
  // Your code here...
@@ -118,7 +118,7 @@ const pw = require('generate-pw')
118
118
 
119
119
  <br>
120
120
 
121
- **💡 Note:** To always import the latest version (not recommended in production!) remove the `@2.1.1` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/generate-pw/dist/generate-pw.min.js`
121
+ **💡 Note:** To always import the latest version (not recommended in production!) remove the `@2.2.0` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/generate-pw/dist/generate-pw.min.js`
122
122
 
123
123
  <br>
124
124
 
@@ -255,7 +255,7 @@ Name | Type | Description
255
255
  `uppercase` | Boolean | Allow uppercase letters in password(s). | `true`
256
256
  `similarChars` | Boolean | Include similar characters (e.g. o,0,O,i,l,1,\|) in password(s). | `false`
257
257
  `strict` | Boolean | Require at least one character from each allowed character set in password(s). | `true`
258
- `entropy` | Boolean | Calculate/log estimated entropy. | `false`
258
+ `entropy` | Boolean | Calculate/log estimated entropy. | `true`
259
259
 
260
260
  ##### _*Only available in [`generatePassword([options])`](#generatepasswordoptions) since [`generatePasswords(qty[, options])`](#generatepasswordsqty-options) takes a `qty` argument_
261
261
 
@@ -297,13 +297,15 @@ Boolean options:
297
297
  -S, --similar-chars Include similar characters in password(s).
298
298
  -S, --unstrict Don't require at least one character from
299
299
  each allowed character set in password(s).
300
- -e, --entropy Calculate/log estimated entropy.
300
+ -E, --no-entropy Don't calculate/log estimated entropy.
301
301
  -q, --quiet Suppress all logging except errors.
302
302
 
303
303
  Commands:
304
304
  -i, --init Create config file (in project root).
305
305
  -h, --help Display help screen.
306
306
  -v, --version Show version number.
307
+ --stats Show npm stats.
308
+ --debug [targetKey] Show debug logs.
307
309
  ```
308
310
 
309
311
  #
@@ -327,7 +329,7 @@ export default {
327
329
  excludeUpperChars: false, // disallow uppercase letters in password(s)
328
330
  similarChars: false, // include similar chars in password(s)
329
331
  unstrict: false, // don't require 1+ char from each allowed charset in password(s)
330
- entropy: false, // calculate/log estimated entropy
332
+ noEntropy: false, // don't calculate/log estimated entropy
331
333
  quietMode: false // suppress all logging except errors
332
334
  }
333
335
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generate-pw",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Randomly generate, strengthen, and validate cryptographically-secure passwords.",
5
5
  "author": {
6
6
  "name": "Adam Lui",
@@ -34,22 +34,24 @@
34
34
  "!docs/*/"
35
35
  ],
36
36
  "bin": {
37
- "generatepw": "dist/cli/index.min.js",
38
- "generate-pw": "dist/cli/index.min.js"
37
+ "generatepw": "dist/cli/index.js",
38
+ "generate-pw": "dist/cli/index.js"
39
39
  },
40
40
  "directories": {
41
41
  "lib": "./dist",
42
42
  "doc": "./docs"
43
43
  },
44
44
  "scripts": {
45
+ "dev": "npm run build && npm i -g && generate-pw --help",
45
46
  "build": "node utils/build",
46
47
  "build:js": "node utils/build --js",
47
48
  "build:data": "node utils/build --data",
48
49
  "build:json": "node utils/build --json",
49
50
  "debug": "node src/cli --debug",
50
- "translate": "py utils/translate-en-messages.py",
51
+ "translate": "translate-messages",
51
52
  "bump:patch": "bash utils/bump.sh patch",
52
53
  "bump:minor": "bash utils/bump.sh minor",
54
+ "bump:feat": "npm run bump:minor",
53
55
  "bump:major": "bash utils/bump.sh major"
54
56
  },
55
57
  "repository": {
@@ -75,6 +77,8 @@
75
77
  "node-clipboardy": "^1.0.3"
76
78
  },
77
79
  "devDependencies": {
78
- "@adamlui/minify.js": "^2.2.1"
80
+ "@adamlui/minify.js": "^2.3.1",
81
+ "console-table-printer": "^2.15.0",
82
+ "copyfiles": "^2.4.1"
79
83
  }
80
84
  }
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * © 2024–2026 Adam Lui & contributors under the MIT license.
4
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
5
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
6
- */
7
- (async()=>{globalThis.env={args:process.argv.slice(2),devMode:/[\\/]src(?:[\\/]|$)/i.test(__dirname)},env.debugMode=env.args.some(e=>/^--?debug(?:-?mode)?$/.test(e)),env.modExt=`${env.devMode?"":".min"}.js`;var e=require("node-clipboardy"),i=require("../generate-pw"+env.modExt).generatePassword,c=require("./lib/init"+env.modExt),o=require("./lib/log"+env.modExt);return await c.cli(),cli.config.init?c.configFile():cli.config.help?o.help():cli.config.version?o.version():(c={length:cli.config.length,qty:cli.config.qty,strength:cli.config.mode,charset:cli.config.charset,exclude:cli.config.excludeChars,numbers:!cli.config.excludeNums,symbols:!cli.config.excludeSymbols,lowercase:!cli.config.excludeLowerChars,uppercase:!cli.config.excludeUpperChars,similarChars:cli.config.similarChars,strict:!cli.config.unstrict,entropy:cli.config.entropy,verbose:!cli.config.quietMode},e.writeSync([].concat(i(c)).join("\n")),void o.ifNotQuiet(`
8
- ${cli.msgs.info_copyingToClip}...`))})();
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- module.exports={atomicWrite(e,r,t="utf8"){var n=require("fs"),a=require("path"),a=a.join(a.dirname(e),`.${a.basename(e)}.tmp`);n.writeFileSync(a,r,t),n.renameSync(a,e)},fetch(n){return"undefined"==typeof fetch?new Promise((t,e)=>{var r=n.match(/^([^:]+):\/\//)[1];/^https?$/.test(r)||e(new Error(cli.msgs.error_invalidURL+".")),require(r).get(n,e=>{let r="";e.on("data",e=>r+=e),e.on("end",()=>t({json:()=>JSON.parse(r)}))}).on("error",e)}):fetch(n)},flatten(e,{key:r="message"}={}){var t,n={};for(t in e)n[t]="object"==typeof e[t]&&r in e[t]?e[t][r]:e[t];return n}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- let language=require("./language"+env.modExt),log=require("./log"+env.modExt),settings=require("./settings"+env.modExt),dataPath="../../"+(env.devMode?"../":"data/");module.exports={async cli(){Object.assign(globalThis.cli??={},require(dataPath+"package-data.json")),cli.lang=settings.load("uiLang")||(env.debugMode?language.generateRandomLang({excludes:["en"]}):language.getSysLang()),cli.msgs=await language.getMsgs(cli.lang),cli.urls.cliDocs=cli.urls.docs+"/#-command-line-usage",cli.lang.startsWith("en")||(cli.docLocale=cli.lang.replace("_","-").toLowerCase(),cli.docLocales??=await language.getDocLocales(),cli.docLocales?.includes(cli.docLocale)&&log.debug(cli.urls.cliDocs=cli.urls.docs+`/${cli.docLocale}#readme`)),settings.load()},async configFile(e=settings.configFilename){var a=require("fs"),i=require("path"),t={target:i.resolve(process.cwd(),e)};if(a.existsSync(t.target))return log.warn(cli.msgs.warn_configFileExists+":",t.target);if(a.existsSync(t.src=i.resolve(__dirname,""+dataPath+e)))a.copyFileSync(t.src,t.target);else{i=require("./jsdelivr"+env.modExt).pkgVerURL+`/${e}/`;log.data(cli.msgs.info_fetchingRemoteConfigFrom+` ${i}...`);try{var l=require("./data"+env.modExt),s=await l.fetch(i);if(!s.ok)return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${i} (${s.status})`);l.atomicWrite(t.target,await s.text())}catch(e){return log.warn(cli.msgs.warn_remoteConfigFailed+`: ${i} `+e.message)}}log.success(`${cli.msgs.info_configFileCreated}: ${t.target}\n`),log.tip(cli.msgs.tip_editToSetDefaults+"."),log.tip(cli.msgs.tip_cliArgsPrioritized+".")}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- module.exports={pkgVerURL(e){e||=cli.version||=require("./pkg"+env.modExt).getVer("local")||"none";e=/^\d+\.\d+\.\d+$/.test(e)?cli.name+"-"+e:"latest";return cli.urls.jsdelivr+`@${e}/`+cli.name},commitURL(e="latest"){return cli.urls.jsdelivr+`@${e}/`+cli.name}};
@@ -1,7 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- let data=require("./data"+env.modExt),log=require("./log"+env.modExt);module.exports={formatCode(e){return e.replace(/([a-z]{2,8})[-_]([a-z]{2})/i,(e,r,a)=>r.toLowerCase()+"_"+a.toUpperCase())},async getDocLocales(){cli.version||=require("./pkg"+env.modExt).getVer("local")||"none";var e=require("./jsdelivr"+env.modExt).pkgVerURL()+"/docs/",r=[];try{for(var a,t=await(await data.fetch(e)).text(),s=/href=".*\/docs\/([^/]+)\/"/g;a=s.exec(t);)r.push(a[1])}catch(e){log.warn(cli.msgs.warn_docLocalesFetchFailed+":",e.message)}return r},generateRandomLang({includes:e=[],excludes:r=[]}={}){let t=require("fs"),s=require("path"),a=e.length?e:(()=>{var e=s.join(__dirname,"..",".cache"),r=s.join(e,"locales.json");if(t.existsSync(r))try{return JSON.parse(t.readFileSync(r,"utf8"))}catch(e){}var a=s.resolve(process.cwd(),"_locales");return t.existsSync(a)?(a=t.readdirSync(a,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name).filter(e=>/^\w{2}[-_]?\w{0,2}$/.test(e)),t.mkdirSync(e,{recursive:!0}),data.atomicWrite(r,JSON.stringify(a,null,2)),a):["en"]})(),n=new Set(r),o="en";return(a=a.filter(e=>!n.has(e))).length&&(o=a[Math.floor(Math.random()*a.length)]),log.debug(`Random language: ${o}
7
- `),o},async getMsgs(t="en"){if(t=module.exports.formatCode(t),env.msgs&&t==cli.lang)return env.msgs;let e=data.flatten(require(`../../${env.devMode?"../_locales/en/":"data/"}messages.json`));if(!t.startsWith("en")){var s=require("./jsdelivr"+env.modExt).commitURL(cli.commitHashes.locales)+"/_locales/";let r=s+t+"/messages.json",a=0;for(;a<3;)try{e=data.flatten(await(await data.fetch(r)).json());break}catch(e){if(3<=++a)break;log.debug(r=t.includes("-")&&1==a?r.replace(/([^_]*)_[^/]*(\/.*)/,"$1$2"):s+"en/messages.json")}}return e},getSysLang(){try{var e;return"win32"==process.platform?require("child_process").execSync("(Get-Culture).TwoLetterISOLanguageName",{shell:"powershell",encoding:"utf-8"}).trim():((e=process.env).LANG||e.LANGUAGE||e.LC_ALL||e.LC_MESSAGES||e.LC_NAME).split(".")[0]}catch(e){return log.error(cli.msgs.error_failedToFetchSysLang+":",e.message),"en"}},validateLangCode(e){return"string"==typeof e&&/^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(e)}};
@@ -1,22 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- module.exports={colors:{nc:"",br:"",by:"",bo:"",bg:"",bw:"",gry:"",blk:"",tlBG:""},configURL(){this.info(`
7
- ${cli.msgs.info_exampleValidConfigFile}: `+cli.urls.config)},configURLandExit(...s){this.error(...s),this.configURL(),process.exit(1)},data(s){console.log(`
8
- `+this.colors.bw+s+this.colors.nc)},debug(s){env.debugMode&&console.debug(`
9
- ${this.colors.bo}DEBUG:`,s,this.colors.nc,"\n")},dim(s){console.log(""+this.colors.gry+s+this.colors.nc)},error(...s){console.error(`
10
- ${this.colors.br}ERROR:`,...s,this.colors.nc)},errorAndExit(...s){this.error(...s),this.helpCmdAndDocURL(),process.exit(1)},ifNotQuiet(s){cli.config.quietMode||console.info(s)},info(s){console.info(`
11
- `+this.colors.by+s+this.colors.nc)},tip(s){console.info(this.colors.by+"TIP: "+s+this.colors.nc)},success(s){console.log(`
12
- `+this.colors.bg+s+this.colors.nc)},warn(...s){console.warn(`
13
- ${this.colors.bo}WARNING:`,...s,this.colors.nc)},help(s=["header","usage","params","flags","cmds"]){cli.prefix=""+this.colors.tlBG+this.colors.blk+` ${cli.name} ${this.colors.nc} `;let o={header:[`
14
- ├ ${cli.prefix}${cli.msgs.appCopyright}.`,""+cli.prefix+cli.msgs.prefix_source+": "+cli.urls.src],usage:[`
15
- ${this.colors.bw}o ${cli.msgs.helpSection_usage}:`+this.colors.nc,` ${this.colors.bw}» `+this.colors.bg+cli.cmdFormat+this.colors.nc],params:[`
16
- ${this.colors.bw}o ${cli.msgs.helpSection_params}:`+this.colors.nc,` --length=n ${cli.msgs.optionDesc_length}.`,` --qty=n ${cli.msgs.optionDesc_qty}.`,` --charset=chars ${cli.msgs.optionDesc_charset}.`,` --exclude=chars ${cli.msgs.optionDesc_exclude}.`,` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,` --config="path/to/file" ${cli.msgs.optionDesc_config}.`],flags:[`
17
- ${this.colors.bw}o ${cli.msgs.helpSection_flags}:`+this.colors.nc,` -w, --weak ${cli.msgs.optionDesc_weak}.`,` -b, --basic ${cli.msgs.optionDesc_basic}.`,` -t, --strong ${cli.msgs.optionDesc_strong}.`,` -N, --no-numbers ${cli.msgs.optionDesc_excludeNums}.`,` -Y, --no-symbols ${cli.msgs.optionDesc_excludeSymbols}.`,` -L, --no-lowercase ${cli.msgs.optionDesc_noLower}.`,` -U, --no-uppercase ${cli.msgs.optionDesc_noUpper}.`,` -s, --similar-chars ${cli.msgs.optionDesc_similarChars}.`,` -S, --unstrict ${cli.msgs.optionDesc_unstrict}.`,` -e, --entropy ${cli.msgs.optionDesc_entropy}.`,` -q, --quiet ${cli.msgs.optionDesc_quiet}.`],cmds:[`
18
- ${this.colors.bw}o ${cli.msgs.helpSection_cmds}:`+this.colors.nc,` -i, --init ${cli.msgs.optionDesc_init}.`,` -h, --help ${cli.msgs.optionDesc_help}.`,` -v, --version ${cli.msgs.optionDesc_version}.`]};s.forEach(t=>o[t]?.forEach(o=>{{var e=/header|usage/.test(t)?1:29;let i=process.stdout.columns||80,s=o.match(/\S+|\s+/g),c=[],l="";s.forEach(s=>{var o=i-(c.length?e:0);l.length+"| ".length+s.length>o&&(c.push(c.length?l.trimStart():l),l=""),l+=s}),c.push(c.length?l.trimStart():l),c.forEach((s,o)=>console.info("| "+(0==o?s:" ".repeat(e)+s)))}})),console.info(`
19
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_visit}: `+this.colors.bw+cli.urls.cliDocs+this.colors.nc)},helpCmdAndDocURL(){console.info(`
20
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${cli.name} --help' ${cli.msgs.info_or} ${cli.msgs.info_visit}
21
- `+this.colors.bw+cli.urls.docs+this.colors.nc)},version(){var s=require("./pkg"+env.modExt).getVer;this.info(cli.name),this.data(`${cli.msgs.prefix_globalVer}: ${s("global")||"none"}
22
- ${cli.msgs.prefix_localVer}: `+(s("local")||"none"))}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- let log=require("./log"+env.modExt);module.exports={getVer(e="any"){let r;if("global"!=e)try{var l=require("path").resolve(process.cwd(),"node_modules",cli.name,"package.json");r=require(l).version}catch(e){log.debug(cli.msgs.error_readingLocalPkgVer+": "+e.message)}if("global"==e||"all"==e&&!r)try{r=require("child_process").execSync(`npm view ${JSON.stringify(cli.name)} version`).toString().trim()}catch(e){log.debug(cli.msgs.error_failedToFetchGlobalVer+": "+e.message)}return r}};
@@ -1,7 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/generate-pw/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/generate-pw/docs
5
- */
6
- let fs=require("fs"),log=require("./log"+env.modExt),path=require("path");(globalThis.cli??={}).config={},module.exports={configFilename:"generate-pw.config.mjs",controls:{length:{type:"param",valType:"positiveInt",defaultVal:12,regex:/^--?length(?:=.*|$)/},qty:{type:"param",valType:"positiveInt",defaultVal:1,regex:/^--?qu?a?n?ti?t?y(?:=.*|$)/},charset:{type:"param",regex:/^--?charse?t?(?:=.*|$)/},excludeChars:{type:"param",regex:/^--?exclude(?:=.*|$)/},uiLang:{type:"param",valType:"langCode",regex:/^--?ui-?lang(?:=.*|$)/},config:{type:"param",valType:"filepath",regex:/^--?config(?:=.*|$)/},weak:{type:"flag",mode:!0,regex:/^--?(?:w|weak)$/},basic:{type:"flag",mode:!0,regex:/^--?(?:b|basic)$/},strong:{type:"flag",mode:!0,regex:/^--?(?:t|strong)$/},excludeNums:{type:"flag",regex:/^--?(?:N|(?:exclude|disable|no)-?num(?:ber)?s?=?(?:true|1)?)$/},excludeSymbols:{type:"flag",regex:/^--?(?:Y|(?:exclude|disable|no)-?symbols?=?(?:true|1)?)$/},excludeLowerChars:{type:"flag",regex:/^--?(?:L|(?:exclude|disable|no)-?lower-?(?:case)?|lower-?(?:case)?=(?:false|0))$/},excludeUpperChars:{type:"flag",regex:/^--?(?:U|(?:exclude|disable|no)-?upper-?(?:case)?|upper-?(?:case)?=(?:false|0))$/},similarChars:{type:"flag",regex:/^--?(?:s|(?:include-?)?similar-?chars?=?(?:true|1)?)$/},unstrict:{type:"flag",regex:/^--?(?:S|(?:un-?strict)?(?:-?mode)?)$/},entropy:{type:"flag",regex:/^--?e(?:ntropy)?$/},quietMode:{type:"flag",regex:/^--?q(?:uiet)?(?:-?mode)?$/},init:{type:"cmd",regex:/^-{0,2}i(?:nit)?$/},help:{type:"cmd",regex:/^--?h(?:elp)?$/},version:{type:"cmd",regex:/^--?ve?r?s?i?o?n?$/}},load(e=Object.keys(this.controls)){let r=[].concat(e);if(cli.defaultsSet||arguments.length||(r.forEach(e=>{var i=this.controls[e];i.mode||(cli.config[e]??=i.defaultVal??("param"==i.type&&""))}),cli.defaultsSet=!0),!cli.configPathTried){e=env.args.find(e=>this.controls.config.regex.test(e));if(e){/=/.test(e)||log.errorAndExit(`[${e}] `+cli.msgs.error_mustIncludePath);e=e.split("=")[1];cli.configPath=path.isAbsolute(e)?e:path.resolve(process.cwd(),e),fs.existsSync(cli.configPath)||log.configURLandExit(cli.msgs.error_configFileNotFound+":",cli.configPath)}else for(var i of[".mjs",".cjs",".js"]){i=path.resolve(process.cwd(),this.configFilename.replace(/\.[^.]+$/,i));if(fs.existsSync(i)){cli.configPath=i;break}}cli.configPathTried=!0}if(cli.configPath)try{let e=require(cli.configPath),t=e?.default??e;t&&"object"==typeof t||log.configURLandExit(cli.msgs.error_invalidConfigFile+"."),Object.assign(cli.config,arguments.length?r.reduce((e,i)=>t[i]?{...e,[i]:t[i]}:e,{}):t)}catch(e){log.configURLandExit(cli.msgs.error_failedToLoadConfigFile+":",cli.configPath,`
7
- `+e.message)}return env.args.forEach(i=>{var e,t;/^[^-]|--?(?:config|debug)/.test(i)&&"init"!=i||(!(e=Object.keys(this.controls).find(e=>this.controls[e]?.regex?.test(i)))&&cli.msgs&&log.errorAndExit(`[${i}] ${cli.msgs.error_notRecognized}.`),r.includes(e)&&((t=this.controls[e]).mode?cli.config.mode=e.replace(/mode$/i,"").toLowerCase():cli.config[e]="param"!=t.type||(i.split("=")[1]?.trim()??"")))}),this.parseValidateConfig(r),1==r.length?cli.config[r[0]]:cli.config},parseValidateConfig(e=Object.keys(this.controls)){let r=require("./language"+env.modExt);for(let t of[].concat(e)){let e=this.controls[t],i=cli.config[t];e.parser&&!e.parsed&&(cli.config[t]=e.parser(i),e.parsed=!0),e.valType&&{positiveInt(){var e=parseInt(i,10);(isNaN(e)||e<1)&&log.errorAndExit(`[${t}] ${cli.msgs.error_nonPositiveNum}: `+i),cli.config[t]=e},filepath(){i&&!fs.existsSync(i)&&log.errorAndExit(`[${t}] ${cli.msgs.error_invalidFilepath}: `+i)},langCode(){i&&!r.validateLangCode(i)&&log.errorAndExit(`[${t}] ${cli.msgs.error_invalidLangCode}: `+i)}}[e.valType]()}}};