mirror of
https://github.com/placeholder-soft/libs.git
synced 2026-01-12 22:29:15 +08:00
feat: type-env lib (#1)
* feat: type-env lib * feat/typed env cli (#2) * chore: create typed-env cli * feat: typed-env cli implementation * chore: build cli * publish cli * feat: supplementary documents * chore: change phs to placeholdersoft * chore: add keywords/homepage/repository * chore: del description
This commit is contained in:
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
42
.eslintrc.json
Normal file
42
.eslintrc.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["**/*"],
|
||||
"plugins": ["@nrwl/nx"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {
|
||||
"@nrwl/nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
"enforceBuildableLibDependency": true,
|
||||
"allow": [],
|
||||
"depConstraints": [
|
||||
{
|
||||
"sourceTag": "*",
|
||||
"onlyDependOnLibsWithTags": ["*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"extends": ["plugin:@nrwl/nx/typescript"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"extends": ["plugin:@nrwl/nx/javascript"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -39,4 +39,4 @@ testem.log
|
||||
Thumbs.db
|
||||
|
||||
**/__fixtures__/.env
|
||||
**/__fixtures__/env-name.ts
|
||||
**/__fixtures__/env-name.ts
|
||||
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
# Add files here to ignore them from prettier formatting
|
||||
|
||||
/dist
|
||||
/coverage
|
||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner"
|
||||
]
|
||||
}
|
||||
165
README.md
165
README.md
@@ -0,0 +1,165 @@
|
||||
# typed-env
|
||||
|
||||
typed-env can help us better handle environment variables
|
||||
|
||||
## [Methods](#usage-typed-env-cli)
|
||||
|
||||
### [commands](#usage-typed-env-cli)
|
||||
|
||||
- [diff-env](#diff-env)
|
||||
- [diff-env-name](#diff-env-name)
|
||||
- [gen-env](#gen-env)
|
||||
- [gen-env-name-type](#gen-env-name-type)
|
||||
- [gen-typed-env-call-usage-report](#gen-typed-env-call-usage-report)
|
||||
|
||||
### [lib](#usage-typed-env)
|
||||
|
||||
- [`typeEnv`](#typeenv)
|
||||
- [`anyEnv`](#anyenv)
|
||||
|
||||
## Install
|
||||
|
||||
`typed-env-cli`
|
||||
|
||||
```bash
|
||||
$ npm install -g @placeholdersoft/typed-env-cli
|
||||
```
|
||||
|
||||
`typed-env`
|
||||
|
||||
```bash
|
||||
$ npm install @placeholdersoft/typed-env
|
||||
```
|
||||
|
||||
## Usage typed-env-cli
|
||||
|
||||
### `diff-env`
|
||||
|
||||
```
|
||||
Output diff env
|
||||
|
||||
USAGE
|
||||
$ typed-env diff-env -p <value> -a <value>
|
||||
|
||||
FLAGS
|
||||
-a, --after-env=<value> (required) enter change env json
|
||||
-p, --prev-env=<value> (required) enter current env json
|
||||
|
||||
DESCRIPTION
|
||||
Output diff env
|
||||
|
||||
EXAMPLES
|
||||
$ typed-env diff-env -a "$(env)" -p "$(env)"
|
||||
```
|
||||
|
||||
### `diff-env-name`
|
||||
|
||||
```
|
||||
Output diff env name
|
||||
|
||||
USAGE
|
||||
$ typed-env diff-env-name -p <value> -a <value>
|
||||
|
||||
FLAGS
|
||||
-a, --after-env=<value> (required) enter change env json
|
||||
-p, --prev-env=<value> (required) enter current env json
|
||||
|
||||
DESCRIPTION
|
||||
Output diff env name
|
||||
|
||||
EXAMPLES
|
||||
$ typed-env diff-env-name -a "$(env)" -p "$(env)"
|
||||
```
|
||||
|
||||
### `gen-env`
|
||||
|
||||
```
|
||||
Generate .env
|
||||
|
||||
USAGE
|
||||
$ typed-env gen-env -s <value> -t <value> [-o <value>]
|
||||
|
||||
FLAGS
|
||||
-o, --output=<value> enter the output file path
|
||||
-s, --source-file=<value> (required) enter the file path to check
|
||||
-t, --tsconfig=<value> (required) enter the tsconfig.json path
|
||||
|
||||
DESCRIPTION
|
||||
Generate .env
|
||||
|
||||
EXAMPLES
|
||||
$ typed-env gen-env -s ./src/index.ts -t ./tsconfig.json -o ./src/.env
|
||||
```
|
||||
|
||||
### `gen-env-name-type`
|
||||
|
||||
```
|
||||
Generate env name type definition
|
||||
|
||||
USAGE
|
||||
$ typed-env gen-env-name-type -s <value> -t <value> [-o <value>]
|
||||
|
||||
FLAGS
|
||||
-o, --output=<value> enter the output file path
|
||||
-s, --source-file=<value> (required) enter the file path to check
|
||||
-t, --tsconfig=<value> (required) enter the tsconfig.json path
|
||||
|
||||
DESCRIPTION
|
||||
Generate env name type definition
|
||||
|
||||
EXAMPLES
|
||||
$ typed-env gen-env-name-type -s ./src/index.ts -t ./tsconfig.json -o ./src/env.d.ts
|
||||
```
|
||||
|
||||
### `gen-typed-env-call-usage-report`
|
||||
|
||||
```
|
||||
Generate typed-env call usage report
|
||||
|
||||
USAGE
|
||||
$ typed-env gen-typed-env-call-usage-report -s <value> -t <value> [-o <value>]
|
||||
|
||||
FLAGS
|
||||
-o, --output=<value> enter the output file path
|
||||
-s, --source-file=<value> (required) enter the file path to check
|
||||
-t, --tsconfig=<value> (required) enter the tsconfig.json path
|
||||
|
||||
DESCRIPTION
|
||||
Generate typed-env call usage report
|
||||
|
||||
EXAMPLES
|
||||
$ typed-env gen-typed-env-call-usage-report -s ./src/index.ts -t ./tsconfig.json -o ./src/typed-env-call-usage-report.json
|
||||
```
|
||||
|
||||
## Usage typed-env
|
||||
|
||||
### `typeEnv`
|
||||
|
||||
```typescript
|
||||
// Support evn-name type check
|
||||
/**
|
||||
* @param key env key
|
||||
* @returns EnvBox
|
||||
*/
|
||||
export declare function typedEnv<T extends string>(
|
||||
key: T
|
||||
): EnvBox<NodeJS.ProcessEnv[T]>;
|
||||
|
||||
type EnvName = 'NODE_VERSION' | 'PORT' | 'LOG_LEVEL'
|
||||
|
||||
typedEnv<EnvName>('PORT').required().toInt(); // ok
|
||||
|
||||
typedEnv<EnvName>('DEBUG').required().toInt(); // error: env name not found
|
||||
```
|
||||
|
||||
### `anyEnv`
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @param key env key
|
||||
* @returns EnvBox
|
||||
*/
|
||||
export declare function typedEnv(key: string): EnvBox;
|
||||
|
||||
anyEnv('DEBUG').required().toInt(); // ok
|
||||
```
|
||||
|
||||
5
jest.config.ts
Normal file
5
jest.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { getJestProjects } from '@nrwl/jest';
|
||||
|
||||
export default {
|
||||
projects: getJestProjects(),
|
||||
};
|
||||
3
jest.preset.js
Normal file
3
jest.preset.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const nxPreset = require('@nrwl/jest/preset').default;
|
||||
|
||||
module.exports = { ...nxPreset };
|
||||
36
nx.json
Normal file
36
nx.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||
"npmScope": "libs",
|
||||
"tasksRunnerOptions": {
|
||||
"default": {
|
||||
"runner": "@nrwl/nx-cloud"
|
||||
}
|
||||
},
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["production", "^production"]
|
||||
},
|
||||
"lint": {
|
||||
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
|
||||
},
|
||||
"test": {
|
||||
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
|
||||
}
|
||||
},
|
||||
"namedInputs": {
|
||||
"default": ["{projectRoot}/**/*", "sharedGlobals"],
|
||||
"production": [
|
||||
"default",
|
||||
"!{projectRoot}/.eslintrc.json",
|
||||
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
||||
"!{projectRoot}/tsconfig.spec.json",
|
||||
"!{projectRoot}/jest.config.[jt]s"
|
||||
],
|
||||
"sharedGlobals": []
|
||||
},
|
||||
"workspaceLayout": {
|
||||
"appsDir": "packages",
|
||||
"libsDir": "packages"
|
||||
}
|
||||
}
|
||||
12592
package-lock.json
generated
Normal file
12592
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@@ -2,7 +2,47 @@
|
||||
"name": "libs",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"build:typed-env": "nx build typed-env",
|
||||
"build:typed-env-cli": "nx build typed-env-cli",
|
||||
"build": "nx run-many --target=build",
|
||||
"test": "nx run-many --target=test"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {}
|
||||
"devDependencies": {
|
||||
"@nrwl/devkit": "^15.4.5",
|
||||
"@nrwl/eslint-plugin-nx": "15.4.5",
|
||||
"@nrwl/jest": "15.4.5",
|
||||
"@nrwl/js": "15.4.5",
|
||||
"@nrwl/linter": "15.4.5",
|
||||
"@nrwl/nx-cloud": "latest",
|
||||
"@nrwl/workspace": "15.4.5",
|
||||
"@types/jest": "28.1.1",
|
||||
"@types/node": "16.11.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||
"@typescript-eslint/parser": "^5.36.1",
|
||||
"chalk": "^4.1.2",
|
||||
"eslint": "~8.15.0",
|
||||
"eslint-config-prettier": "8.1.0",
|
||||
"jest": "28.1.1",
|
||||
"jest-environment-jsdom": "28.1.1",
|
||||
"nx": "15.4.5",
|
||||
"oclif": "^3.4.3",
|
||||
"prettier": "^2.6.2",
|
||||
"ts-jest": "28.0.5",
|
||||
"ts-node": "10.9.1",
|
||||
"tsd": "^0.25.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "~4.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/core": "^1.23.2",
|
||||
"@oclif/plugin-autocomplete": "^1.3.10",
|
||||
"@oclif/plugin-help": "^5.1.22",
|
||||
"@oclif/plugin-legacy": "^1.2.7",
|
||||
"@oclif/plugin-plugins": "^2.1.12",
|
||||
"@oclif/plugin-update": "^3.0.12",
|
||||
"@oclif/plugin-warn-if-update-available": "^2.0.18",
|
||||
"ts-morph": "^17.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
0
packages/.gitkeep
Normal file
0
packages/.gitkeep
Normal file
18
packages/typed-env-cli/.eslintrc.json
Normal file
18
packages/typed-env-cli/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
21
packages/typed-env-cli/LICENSE
Normal file
21
packages/typed-env-cli/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018, Nick Gavrilov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
5
packages/typed-env-cli/README.md
Normal file
5
packages/typed-env-cli/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# typed-env-cli
|
||||
|
||||
typed-env-cli can help us better handle environment variables
|
||||
|
||||
## [Help Documents](https://github.com/placeholder-soft/libs#commands)
|
||||
17
packages/typed-env-cli/bin/dev
Executable file
17
packages/typed-env-cli/bin/dev
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
const path = require('path')
|
||||
const project = path.join(__dirname, '..', 'tsconfig.json')
|
||||
|
||||
// In dev mode -> use ts-node and dev plugins
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
require('ts-node').register({project})
|
||||
|
||||
// In dev mode, always show stack traces
|
||||
oclif.settings.debug = true;
|
||||
|
||||
// Start the CLI
|
||||
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
|
||||
3
packages/typed-env-cli/bin/dev.cmd
Normal file
3
packages/typed-env-cli/bin/dev.cmd
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\dev" %*
|
||||
5
packages/typed-env-cli/bin/run
Executable file
5
packages/typed-env-cli/bin/run
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
|
||||
3
packages/typed-env-cli/bin/run.cmd
Normal file
3
packages/typed-env-cli/bin/run.cmd
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
||||
15
packages/typed-env-cli/jest.config.ts
Normal file
15
packages/typed-env-cli/jest.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'typed-env-cli',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/packages/typed-env-cli',
|
||||
};
|
||||
28
packages/typed-env-cli/npm-shrinkwrap.json
generated
Normal file
28
packages/typed-env-cli/npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@placeholdersoft/typed-env-cli",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@placeholdersoft/typed-env-cli",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oclif/core": "^1.23.2",
|
||||
"@oclif/plugin-autocomplete": "^1.3.10",
|
||||
"@oclif/plugin-help": "^5.1.22",
|
||||
"@oclif/plugin-plugins": "^2.1.12",
|
||||
"@oclif/plugin-update": "^3.0.12",
|
||||
"@oclif/plugin-warn-if-update-available": "^2.0.18",
|
||||
"ts-morph": "^17.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"typed-env": "bin/run"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/typed-env-cli/oclif.manifest.json
Normal file
1
packages/typed-env-cli/oclif.manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"0.0.3","commands":{"diff-env-name":{"id":"diff-env-name","description":"Output diff env name","strict":true,"pluginName":"@placeholdersoft/typed-env-cli","pluginAlias":"@placeholdersoft/typed-env-cli","pluginType":"core","aliases":[],"examples":["$ typed-env diff-env-name -a \"$(env)\" -p \"$(env)\""],"flags":{"prev-env":{"name":"prev-env","type":"option","char":"p","description":"enter current env json","required":true,"multiple":false},"after-env":{"name":"after-env","type":"option","char":"a","description":"enter change env json","required":true,"multiple":false}},"args":[]},"diff-env":{"id":"diff-env","description":"Output diff env","strict":true,"pluginName":"@placeholdersoft/typed-env-cli","pluginAlias":"@placeholdersoft/typed-env-cli","pluginType":"core","aliases":[],"examples":["$ typed-env diff-env -a \"$(env)\" -p \"$(env)\""],"flags":{"prev-env":{"name":"prev-env","type":"option","char":"p","description":"enter current env json","required":true,"multiple":false},"after-env":{"name":"after-env","type":"option","char":"a","description":"enter change env json","required":true,"multiple":false}},"args":[]},"gen-env-name-type":{"id":"gen-env-name-type","description":"Generate env name type definition","strict":true,"pluginName":"@placeholdersoft/typed-env-cli","pluginAlias":"@placeholdersoft/typed-env-cli","pluginType":"core","aliases":[],"examples":["$ typed-env gen-env-name-type -s ./src/index.ts -t ./tsconfig.json -o ./src/env.d.ts"],"flags":{"source-file":{"name":"source-file","type":"option","char":"s","description":"enter the file path to check","required":true,"multiple":false},"tsconfig":{"name":"tsconfig","type":"option","char":"t","description":"enter the tsconfig.json path","required":true,"multiple":false},"output":{"name":"output","type":"option","char":"o","description":"enter the output file path","multiple":false}},"args":[]},"gen-env":{"id":"gen-env","description":"Generate .env","strict":true,"pluginName":"@placeholdersoft/typed-env-cli","pluginAlias":"@placeholdersoft/typed-env-cli","pluginType":"core","aliases":[],"examples":["$ typed-env gen-env -s ./src/index.ts -t ./tsconfig.json -o ./src/.env"],"flags":{"source-file":{"name":"source-file","type":"option","char":"s","description":"enter the file path to check","required":true,"multiple":false},"tsconfig":{"name":"tsconfig","type":"option","char":"t","description":"enter the tsconfig.json path","required":true,"multiple":false},"output":{"name":"output","type":"option","char":"o","description":"enter the output file path","multiple":false}},"args":[]},"gen-typed-env-call-usage-report":{"id":"gen-typed-env-call-usage-report","description":"Generate typed-env call usage report","strict":true,"pluginName":"@placeholdersoft/typed-env-cli","pluginAlias":"@placeholdersoft/typed-env-cli","pluginType":"core","aliases":[],"examples":["$ typed-env gen-typed-env-call-usage-report -s ./src/index.ts -t ./tsconfig.json -o ./src/typed-env-call-usage-report.json"],"flags":{"source-file":{"name":"source-file","type":"option","char":"s","description":"enter the file path to check","required":true,"multiple":false},"tsconfig":{"name":"tsconfig","type":"option","char":"t","description":"enter the tsconfig.json path","required":true,"multiple":false},"output":{"name":"output","type":"option","char":"o","description":"enter the output file path","multiple":false}},"args":[]}}}
|
||||
62
packages/typed-env-cli/package.json
Normal file
62
packages/typed-env-cli/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@placeholdersoft/typed-env-cli",
|
||||
"version": "0.0.3",
|
||||
"type": "commonjs",
|
||||
"bin": {
|
||||
"typed-env": "./bin/run"
|
||||
},
|
||||
"homepage": "https://github.com/placeholder-soft/libs",
|
||||
"repository": "https://github.com/placeholder-soft/libs#usage-typed-env-cli",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"/bin",
|
||||
"/src",
|
||||
"/npm-shrinkwrap.json",
|
||||
"/oclif.manifest.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@oclif/core": "^1.23.2",
|
||||
"@oclif/plugin-autocomplete": "^1.3.10",
|
||||
"@oclif/plugin-help": "^5.1.22",
|
||||
"@oclif/plugin-plugins": "^2.1.12",
|
||||
"@oclif/plugin-update": "^3.0.12",
|
||||
"@oclif/plugin-warn-if-update-available": "^2.0.18",
|
||||
"ts-morph": "^17.0.1"
|
||||
},
|
||||
"oclif": {
|
||||
"bin": "typed-env",
|
||||
"dirname": "typed-env-cli",
|
||||
"macos": {
|
||||
"identifier": "phs.typed-env.cli"
|
||||
},
|
||||
"win": {
|
||||
"identifier": "phs.typed-env.cli"
|
||||
},
|
||||
"commands": "../../dist/packages/typed-env-cli/src/commands",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-plugins",
|
||||
"@oclif/plugin-update",
|
||||
"@oclif/plugin-warn-if-update-available",
|
||||
"@oclif/plugin-autocomplete"
|
||||
],
|
||||
"topicSeparator": " ",
|
||||
"topics": {}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"typed-env",
|
||||
"typed-env-cli",
|
||||
"environment variables",
|
||||
"env",
|
||||
"parser",
|
||||
"env parser",
|
||||
"typescript",
|
||||
"typed",
|
||||
"cli"
|
||||
],
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
66
packages/typed-env-cli/project.json
Normal file
66
packages/typed-env-cli/project.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "typed-env-cli",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/typed-env-cli/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"postpack": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "rm -f oclif.manifest.json"
|
||||
}
|
||||
},
|
||||
"prepack": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "cd packages/typed-env-cli && npx oclif manifest && npx oclif readme"
|
||||
}
|
||||
},
|
||||
"build-ts": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/typed-env-cli",
|
||||
"main": "packages/typed-env-cli/src/index.ts",
|
||||
"tsConfig": "packages/typed-env-cli/tsconfig.lib.json",
|
||||
"assets": ["packages/typed-env-cli/*.md"]
|
||||
},
|
||||
"defaultConfiguration": "",
|
||||
"dependsOn": ["prepack"]
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "cp -r packages/typed-env-cli/bin dist/packages/typed-env-cli",
|
||||
"outputPath": "dist/packages/typed-env-cli",
|
||||
"main": "packages/typed-env-cli/src/index.ts",
|
||||
"tsConfig": "packages/typed-env-cli/tsconfig.lib.json",
|
||||
"assets": ["packages/typed-env-cli/*.md"]
|
||||
},
|
||||
"dependsOn": ["build-ts"]
|
||||
},
|
||||
"publish": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "node tools/scripts/publish.mjs typed-env-cli {args.ver} {args.tag}"
|
||||
},
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["packages/typed-env-cli/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "packages/typed-env-cli/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
41
packages/typed-env-cli/src/commands/diff-env-name.ts
Normal file
41
packages/typed-env-cli/src/commands/diff-env-name.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import { parseEnv } from '../lib/utils/util';
|
||||
|
||||
export default class DiffEnvName extends Command {
|
||||
static override description = 'Output diff env name';
|
||||
|
||||
static override examples = [
|
||||
`$ typed-env diff-env-name -a "$(env)" -p "$(env)"`,
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'prev-env': Flags.string({
|
||||
char: 'p',
|
||||
description: 'enter current env json',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
'after-env': Flags.string({
|
||||
char: 'a',
|
||||
description: 'enter change env json',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
async run(): Promise<void> {
|
||||
const { flags } = await this.parse(DiffEnvName);
|
||||
|
||||
const prev = parseEnv(flags['prev-env']);
|
||||
const after = parseEnv(flags['after-env']);
|
||||
|
||||
const names: { [key in string]: string } = {};
|
||||
for (const [k] of Object.entries(after)) {
|
||||
// only print changed vars
|
||||
if (prev[k] !== after[k]) {
|
||||
names[k] = k;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(names);
|
||||
}
|
||||
}
|
||||
39
packages/typed-env-cli/src/commands/diff-env.ts
Normal file
39
packages/typed-env-cli/src/commands/diff-env.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import { parseEnv } from '../lib/utils/util';
|
||||
|
||||
export default class DiffEnv extends Command {
|
||||
static override description = 'Output diff env';
|
||||
|
||||
static override examples = [`$ typed-env diff-env -a "$(env)" -p "$(env)"`];
|
||||
|
||||
static override flags = {
|
||||
'prev-env': Flags.string({
|
||||
char: 'p',
|
||||
description: 'enter current env json',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
'after-env': Flags.string({
|
||||
char: 'a',
|
||||
description: 'enter change env json',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
async run(): Promise<void> {
|
||||
const { flags } = await this.parse(DiffEnv);
|
||||
|
||||
const prev = parseEnv(flags['prev-env']);
|
||||
const after = parseEnv(flags['after-env']);
|
||||
|
||||
const env: string[] = [];
|
||||
|
||||
for (const [k, v] of Object.entries(after)) {
|
||||
if (prev[k] !== after[k]) {
|
||||
env.push(`${k}=${v}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(env);
|
||||
}
|
||||
}
|
||||
68
packages/typed-env-cli/src/commands/gen-env-name-type.ts
Normal file
68
packages/typed-env-cli/src/commands/gen-env-name-type.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { ensureDirSync } from '../lib/utils/fs';
|
||||
import { generateEnvName } from '../lib/generate-env';
|
||||
|
||||
export default class GenEnvNameType extends Command {
|
||||
static override description = 'Generate env name type definition';
|
||||
|
||||
static override examples = [
|
||||
`$ typed-env gen-env-name-type -s ./src/index.ts -t ./tsconfig.json -o ./src/env.d.ts`,
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'source-file': Flags.string({
|
||||
char: 's',
|
||||
description: 'enter the file path to check',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
tsconfig: Flags.string({
|
||||
char: 't',
|
||||
description: 'enter the tsconfig.json path',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
output: Flags.string({
|
||||
char: 'o',
|
||||
description: 'enter the output file path',
|
||||
}),
|
||||
};
|
||||
|
||||
async run(): Promise<void> {
|
||||
const { flags } = await this.parse(GenEnvNameType);
|
||||
|
||||
const envNames = generateEnvName({
|
||||
sourceFilePath: flags['source-file'],
|
||||
options: { tsConfigFilePath: flags.tsconfig },
|
||||
});
|
||||
|
||||
const fileContent = `export const AllProjectEnvNames = ${JSON.stringify(
|
||||
envNames,
|
||||
null,
|
||||
2
|
||||
)} as const;
|
||||
export type ProjectEnvName = keyof typeof AllProjectEnvNames;`;
|
||||
|
||||
if (flags.output) {
|
||||
const tempstats = fs.statSync(flags.output);
|
||||
|
||||
if (tempstats.isDirectory()) {
|
||||
ensureDirSync(flags.output);
|
||||
const outputFilePath = path.join(flags.output, 'env.d.ts');
|
||||
fs.writeFileSync(outputFilePath, fileContent);
|
||||
|
||||
console.log(`output file: ${outputFilePath}`);
|
||||
} else {
|
||||
const folderPath = path.parse(flags.output);
|
||||
ensureDirSync(folderPath.dir);
|
||||
fs.writeFileSync(flags.output, fileContent);
|
||||
|
||||
console.log(`output file: ${flags.output}`);
|
||||
}
|
||||
} else {
|
||||
console.log(fileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
packages/typed-env-cli/src/commands/gen-env.ts
Normal file
61
packages/typed-env-cli/src/commands/gen-env.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { ensureDirSync } from '../lib/utils/fs';
|
||||
import { generateEnv } from '../lib/generate-env';
|
||||
|
||||
export default class GenEnv extends Command {
|
||||
static override description = 'Generate .env';
|
||||
|
||||
static override examples = [
|
||||
`$ typed-env gen-env -s ./src/index.ts -t ./tsconfig.json -o ./src/.env`,
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'source-file': Flags.string({
|
||||
char: 's',
|
||||
description: 'enter the file path to check',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
tsconfig: Flags.string({
|
||||
char: 't',
|
||||
description: 'enter the tsconfig.json path',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
output: Flags.string({
|
||||
char: 'o',
|
||||
description: 'enter the output file path',
|
||||
}),
|
||||
};
|
||||
|
||||
async run(): Promise<void> {
|
||||
const { flags } = await this.parse(GenEnv);
|
||||
|
||||
const info = generateEnv({
|
||||
sourceFilePath: flags['source-file'],
|
||||
options: { tsConfigFilePath: flags.tsconfig },
|
||||
});
|
||||
|
||||
if (flags.output) {
|
||||
const tempstats = fs.statSync(flags.output);
|
||||
|
||||
if (tempstats.isDirectory()) {
|
||||
ensureDirSync(flags.output);
|
||||
const outputFilePath = path.join(flags.output, '.env');
|
||||
fs.writeFileSync(outputFilePath, info.join('\n'));
|
||||
|
||||
console.log(`output file: ${outputFilePath}`);
|
||||
} else {
|
||||
const folderPath = path.parse(flags.output);
|
||||
ensureDirSync(folderPath.dir);
|
||||
fs.writeFileSync(flags.output, info.join('\n'));
|
||||
|
||||
console.log(`output file: ${flags.output}`);
|
||||
}
|
||||
} else {
|
||||
console.log(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Command, Flags } from '@oclif/core';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { generateTypedEnvCallUsageReport } from '../lib/generate-typed-env-call-usage-report';
|
||||
import { ensureDirSync } from '../lib/utils/fs';
|
||||
|
||||
export default class GenTypedEnvCallUsageReport extends Command {
|
||||
static override description = 'Generate typed-env call usage report';
|
||||
|
||||
static override examples = [
|
||||
`$ typed-env gen-typed-env-call-usage-report -s ./src/index.ts -t ./tsconfig.json -o ./src/typed-env-call-usage-report.json`,
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
'source-file': Flags.string({
|
||||
char: 's',
|
||||
description: 'enter the file path to check',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
tsconfig: Flags.string({
|
||||
char: 't',
|
||||
description: 'enter the tsconfig.json path',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
output: Flags.string({
|
||||
char: 'o',
|
||||
description: 'enter the output file path',
|
||||
}),
|
||||
};
|
||||
|
||||
async run(): Promise<void> {
|
||||
const { flags } = await this.parse(GenTypedEnvCallUsageReport);
|
||||
|
||||
const info = generateTypedEnvCallUsageReport({
|
||||
sourceFilePath: flags['source-file'],
|
||||
options: { tsConfigFilePath: flags.tsconfig },
|
||||
});
|
||||
|
||||
if (flags.output) {
|
||||
const tempstats = fs.statSync(flags.output);
|
||||
|
||||
if (tempstats.isDirectory()) {
|
||||
ensureDirSync(flags.output);
|
||||
const outputFilePath = path.join(
|
||||
flags.output,
|
||||
'typed-env-call-usage-report.json'
|
||||
);
|
||||
fs.writeFileSync(outputFilePath, JSON.stringify(info));
|
||||
|
||||
console.log(`output file: ${outputFilePath}`);
|
||||
} else {
|
||||
const folderPath = path.parse(flags.output);
|
||||
ensureDirSync(folderPath.dir);
|
||||
fs.writeFileSync(flags.output, JSON.stringify(info));
|
||||
|
||||
console.log(`output file: ${flags.output}`);
|
||||
}
|
||||
} else {
|
||||
console.log(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/typed-env-cli/src/index.ts
Normal file
1
packages/typed-env-cli/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { run } from '@oclif/core';
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": ["lib", "node_modules", "**/*.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { typedEnv } from '@placeholdersoft/typed-env';
|
||||
|
||||
typedEnv('bbb').required().default('111').toInt(111);
|
||||
typedEnv('bbb').default('222').required().toInt(222);
|
||||
typedEnv('bbb').default('333').required().toInt(333);
|
||||
typedEnv('bbb').default('333').required().toString();
|
||||
|
||||
typedEnv('ccc').required().default('111').toInt(111);
|
||||
typedEnv('ccc').default('222').required().toInt(222);
|
||||
typedEnv('ccc').default('333').required().toInt(333);
|
||||
typedEnv('ccc').default('333').required().toString();
|
||||
|
||||
typedEnv('ddd');
|
||||
|
||||
typedEnv('eee').default('');
|
||||
|
||||
// typedEnv("");typedEnv("bbb").required().toInt(111);typedEnv("bbb").required().toInt(222);
|
||||
|
||||
// typedEnv("bbb").required().toInt(111);typedEnv("bbb").required().toInt(111);
|
||||
// typedEnv("bbb").required().toInt(111);
|
||||
// typedEnv().required().toInt();
|
||||
// typedEnv("");
|
||||
// typedEnv("ccc").required().toInt(222);
|
||||
@@ -0,0 +1,36 @@
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { generateEnv, generateEnvName } from '../generate-env';
|
||||
|
||||
const kSourceFilePath = path.resolve(
|
||||
__dirname,
|
||||
'../__fixtures__/usage-typed-env.ts'
|
||||
);
|
||||
const kTsconfigPath = path.resolve(
|
||||
__dirname,
|
||||
'../__fixtures__/tsconfig.t.json'
|
||||
);
|
||||
|
||||
test('generate env name', () => {
|
||||
const kOutputPath = path.resolve(__dirname, '../__fixtures__/env-name.ts');
|
||||
generateEnvName({
|
||||
sourceFilePath: kSourceFilePath,
|
||||
options: {
|
||||
tsConfigFilePath: kTsconfigPath,
|
||||
},
|
||||
output: kOutputPath,
|
||||
});
|
||||
expect(existsSync(kOutputPath)).toBe(true);
|
||||
});
|
||||
|
||||
test('generate env', () => {
|
||||
const kOutputPath = path.resolve(__dirname, '../__fixtures__/.env');
|
||||
generateEnv({
|
||||
sourceFilePath: kSourceFilePath,
|
||||
options: {
|
||||
tsConfigFilePath: kTsconfigPath,
|
||||
},
|
||||
output: kOutputPath,
|
||||
});
|
||||
expect(existsSync(kOutputPath)).toBe(true);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import path from 'path';
|
||||
import { generateTypedEnvCallUsageReport } from '../generate-typed-env-call-usage-report';
|
||||
|
||||
const kSourceFilePath = path.resolve(
|
||||
__dirname,
|
||||
'../__fixtures__/usage-typed-env.ts'
|
||||
);
|
||||
const kTsconfigPath = path.resolve(
|
||||
__dirname,
|
||||
'../__fixtures__/tsconfig.t.json'
|
||||
);
|
||||
|
||||
test('generate typeEnv call usage report', () => {
|
||||
const info = generateTypedEnvCallUsageReport({
|
||||
sourceFilePath: kSourceFilePath,
|
||||
options: { tsConfigFilePath: kTsconfigPath },
|
||||
});
|
||||
expect(info.envNames[0]).toBe('bbb');
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Project } from 'ts-morph';
|
||||
import { getCallUsageArgsFromStatement } from './get-args/statement';
|
||||
import {
|
||||
hasConvertData,
|
||||
TCallUsageArgs,
|
||||
TGenerateCallUsageReport,
|
||||
TGenerateCallUsageReportBase,
|
||||
TParseParameters,
|
||||
} from './types';
|
||||
|
||||
function callUsageArgs<T>(args: TCallUsageArgs): TParseParameters[][] {
|
||||
const {
|
||||
sourceFile,
|
||||
chainCallFuncNames,
|
||||
analysisPathIgnorePatterns = true,
|
||||
} = args;
|
||||
|
||||
const filePath = sourceFile.getFilePath();
|
||||
if (analysisPathIgnorePatterns) {
|
||||
if (filePath.includes('node_modules')) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
const result = [];
|
||||
|
||||
result.push(
|
||||
...getCallUsageArgsFromStatement({
|
||||
source: sourceFile,
|
||||
filePath,
|
||||
chainCallFuncNames,
|
||||
})
|
||||
);
|
||||
|
||||
const files = sourceFile.getReferencedSourceFiles();
|
||||
if (files.length > 0) {
|
||||
for (const f of files) {
|
||||
result.push(
|
||||
...callUsageArgs<T>({
|
||||
sourceFile: f,
|
||||
chainCallFuncNames,
|
||||
analysisPathIgnorePatterns,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the call report of the function
|
||||
*/
|
||||
export function generateCallUsageReport<T>(
|
||||
arg: TGenerateCallUsageReportBase
|
||||
): TParseParameters[][];
|
||||
export function generateCallUsageReport<T>(
|
||||
args: TGenerateCallUsageReport<T>
|
||||
): T;
|
||||
|
||||
export function generateCallUsageReport<T>(
|
||||
args: TGenerateCallUsageReportBase | TGenerateCallUsageReport<T>
|
||||
) {
|
||||
const {
|
||||
sourceFilePath,
|
||||
chainCallFuncNames,
|
||||
analysisPathIgnorePatterns,
|
||||
options,
|
||||
} = args;
|
||||
|
||||
const convertData = hasConvertData<TGenerateCallUsageReport<T>>(args)
|
||||
? args.convertData
|
||||
: undefined;
|
||||
|
||||
const project = new Project(options);
|
||||
const result = callUsageArgs({
|
||||
sourceFile: project.getSourceFileOrThrow(sourceFilePath),
|
||||
chainCallFuncNames,
|
||||
analysisPathIgnorePatterns,
|
||||
});
|
||||
|
||||
return convertData ? convertData(result) : result;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { CallExpression, SyntaxKind, ts } from 'ts-morph';
|
||||
import { TGetArgsFromExpression, TParseParameters } from '../types';
|
||||
|
||||
export function getArgsFromCall(
|
||||
args: TGetArgsFromExpression
|
||||
): TParseParameters[] | undefined {
|
||||
const { source, filePath, chainCallFuncNames } = args;
|
||||
|
||||
const calls = source.getDescendantsOfKind(SyntaxKind.CallExpression);
|
||||
|
||||
const argsInfo: TParseParameters[] = [];
|
||||
|
||||
for (const c of calls) {
|
||||
const pe = c.getExpressionIfKind(SyntaxKind.PropertyAccessExpression);
|
||||
|
||||
const peName = pe?.getName();
|
||||
|
||||
if (peName && chainCallFuncNames.includes(peName)) {
|
||||
argsInfo.push(
|
||||
generateArgINfo({
|
||||
filePath,
|
||||
funcName: peName,
|
||||
source: c,
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const identifierText = c
|
||||
.getExpressionIfKind(SyntaxKind.Identifier)
|
||||
?.getText();
|
||||
|
||||
if (identifierText && chainCallFuncNames.includes(identifierText)) {
|
||||
argsInfo.push(
|
||||
generateArgINfo({
|
||||
filePath,
|
||||
funcName: identifierText,
|
||||
source: c,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (argsInfo.length > 0) {
|
||||
return argsInfo;
|
||||
}
|
||||
}
|
||||
|
||||
const generateArgINfo = ({
|
||||
filePath,
|
||||
funcName,
|
||||
source,
|
||||
}: {
|
||||
filePath: string;
|
||||
funcName: string;
|
||||
source: CallExpression<ts.CallExpression>;
|
||||
}) => {
|
||||
return {
|
||||
path: `${filePath}#L${source.getStartLineNumber()}#S${source.getFullStart()}`,
|
||||
pos: {
|
||||
line: source.getStartLineNumber(),
|
||||
fullStart: source.getFullStart(),
|
||||
},
|
||||
funcName,
|
||||
args: source.getArguments().map((r) => r.getText()),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { SyntaxKind } from 'ts-morph';
|
||||
import { TGetArgsFromExpression, TParseParameters } from '../types';
|
||||
import { getArgsFromCall } from './call';
|
||||
|
||||
export function getCallUsageArgsFromStatement(
|
||||
args: TGetArgsFromExpression
|
||||
): TParseParameters[][] {
|
||||
const { source, filePath, chainCallFuncNames } = args;
|
||||
|
||||
const argsInfo: TParseParameters[][] = [];
|
||||
|
||||
const expressionStatements = source.getDescendantsOfKind(
|
||||
SyntaxKind.ExpressionStatement
|
||||
);
|
||||
|
||||
for (const es of expressionStatements) {
|
||||
const result = getArgsFromCall({
|
||||
source: es,
|
||||
filePath,
|
||||
chainCallFuncNames,
|
||||
});
|
||||
|
||||
result && argsInfo.push(result);
|
||||
}
|
||||
|
||||
return argsInfo;
|
||||
}
|
||||
103
packages/typed-env-cli/src/lib/function-call/types.ts
Normal file
103
packages/typed-env-cli/src/lib/function-call/types.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Block,
|
||||
CallExpression,
|
||||
ExpressionStatement,
|
||||
ProjectOptions,
|
||||
SourceFile,
|
||||
ts,
|
||||
} from 'ts-morph';
|
||||
|
||||
export type TSource =
|
||||
| SourceFile
|
||||
| Block
|
||||
| ExpressionStatement
|
||||
| CallExpression<ts.CallExpression>;
|
||||
|
||||
export type TParseParameters = {
|
||||
/**
|
||||
* function name.
|
||||
*/
|
||||
funcName: string;
|
||||
/**
|
||||
* Current file path
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* File location of the function
|
||||
*/
|
||||
pos: {
|
||||
line: number;
|
||||
fullStart: number;
|
||||
};
|
||||
/**
|
||||
* function call arguments.
|
||||
*/
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export type TGetArgsFromExpression = {
|
||||
/**
|
||||
* File source to be resolved
|
||||
*/
|
||||
source: TSource;
|
||||
/**
|
||||
* Chain call function name.
|
||||
*/
|
||||
chainCallFuncNames: string[];
|
||||
/**
|
||||
* Current file path
|
||||
*/
|
||||
filePath: string;
|
||||
};
|
||||
|
||||
export type TCallUsageArgs = {
|
||||
/**
|
||||
* File source to be resolved
|
||||
*/
|
||||
sourceFile: SourceFile;
|
||||
/**
|
||||
* Chain call function name.
|
||||
*/
|
||||
chainCallFuncNames: string[];
|
||||
/**
|
||||
* Whether to ignore the third-party package. default: true
|
||||
*/
|
||||
analysisPathIgnorePatterns?: boolean;
|
||||
};
|
||||
|
||||
export type TGenerateCallUsageReportBase = {
|
||||
/**
|
||||
* The path to the source file to analyze.
|
||||
*/
|
||||
sourceFilePath: string;
|
||||
/**
|
||||
* Chain call function name.
|
||||
*/
|
||||
chainCallFuncNames: string[];
|
||||
/**
|
||||
* Whether to ignore the third-party package. default: true
|
||||
*/
|
||||
analysisPathIgnorePatterns?: boolean;
|
||||
/**
|
||||
* ts-morph options
|
||||
*/
|
||||
options?: ProjectOptions;
|
||||
};
|
||||
|
||||
export type TGenerateCallUsageReport<T> = TGenerateCallUsageReportBase & {
|
||||
/**
|
||||
*
|
||||
* @param v call usage args
|
||||
* @returns data
|
||||
*/
|
||||
convertData: (v: TParseParameters[][]) => T;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Whether the parameter contains convertData
|
||||
* @param args
|
||||
* @returns
|
||||
*/
|
||||
export const hasConvertData = <T>(args: any): args is T => {
|
||||
return args['convertData'] != null;
|
||||
};
|
||||
49
packages/typed-env-cli/src/lib/generate-env.ts
Normal file
49
packages/typed-env-cli/src/lib/generate-env.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { uniqBy } from './utils/util';
|
||||
import {
|
||||
generateTypedEnvCallUsageReport,
|
||||
Report,
|
||||
} from './generate-typed-env-call-usage-report';
|
||||
|
||||
export const generateEnvName = (arg: Report) => {
|
||||
const envNames = generateTypedEnvCallUsageReport(arg).envNames.reduce(
|
||||
(pre, current) => {
|
||||
return {
|
||||
...pre,
|
||||
[current]: current,
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return envNames;
|
||||
};
|
||||
|
||||
export const generateEnv = (arg: Report) => {
|
||||
const report = generateTypedEnvCallUsageReport(arg);
|
||||
|
||||
const envValue = report.envNames.reduce<string[]>((acc, cur) => {
|
||||
const envInfo = report.data[cur];
|
||||
const paths = Object.keys(envInfo);
|
||||
|
||||
const values = uniqBy(
|
||||
paths.map((p) => envInfo[p].default).filter((r) => r != null) as string[],
|
||||
(t) => t
|
||||
);
|
||||
|
||||
if (values.length > 1) {
|
||||
acc.push(`// default=[${values.join(',')}]`);
|
||||
acc.push(`${cur}=${values[0]}`);
|
||||
} else if (values.length === 1 && values[0].length > 0) {
|
||||
if (["''", '""'].includes(values[0])) {
|
||||
acc.push(`// default=[${values.join(',')}]`);
|
||||
}
|
||||
acc.push(`${cur}=`);
|
||||
} else {
|
||||
acc.push(`${cur}=`);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return envValue;
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
import { ProjectOptions } from 'ts-morph';
|
||||
import { generateCallUsageReport } from './function-call/generate-call-usage-report';
|
||||
import { TParseParameters } from './function-call/types';
|
||||
import { uniqBy } from './utils/util';
|
||||
|
||||
type TResult = {
|
||||
required: boolean;
|
||||
type: string;
|
||||
default?: string;
|
||||
errorMsg?: string;
|
||||
};
|
||||
|
||||
type TFunCallArg = {
|
||||
[secretKey in string]: { [path in string]: TResult };
|
||||
};
|
||||
|
||||
export type Report = { sourceFilePath: string; options?: ProjectOptions };
|
||||
|
||||
/**
|
||||
* @description Get the call report of all typedEnv functions
|
||||
*/
|
||||
export function generateTypedEnvCallUsageReport({
|
||||
sourceFilePath,
|
||||
options,
|
||||
}: Report) {
|
||||
return generateCallUsageReport({
|
||||
sourceFilePath,
|
||||
options,
|
||||
chainCallFuncNames: [
|
||||
'typedEnv',
|
||||
'default',
|
||||
'required',
|
||||
'optional',
|
||||
'toString',
|
||||
'toInt',
|
||||
'toBoolean',
|
||||
],
|
||||
convertData: (data: TParseParameters[][]) => {
|
||||
const result = parseEnvInfo(data);
|
||||
const exceptionResult = exceptionReport(result);
|
||||
return {
|
||||
envNames: Object.keys(result),
|
||||
data: result,
|
||||
exceptionReport: exceptionResult,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const exceptionReport = (data: TFunCallArg) => {
|
||||
const envNames = Object.keys(data);
|
||||
|
||||
return envNames
|
||||
.map((r) => {
|
||||
const env = data[r];
|
||||
const paths = Object.keys(env);
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
const types = uniqBy(
|
||||
paths.map((p) => env[p].type),
|
||||
(t) => t
|
||||
);
|
||||
|
||||
if (types.length > 0) {
|
||||
errors.push(`secretKey: ${r} has different types: ${types.join(',')}`);
|
||||
}
|
||||
|
||||
const defaultValue = uniqBy(
|
||||
paths.map((p) => env[p].default).filter((r) => r != null) as string[],
|
||||
(t) => t
|
||||
);
|
||||
|
||||
if (defaultValue.length > 0) {
|
||||
warnings.push(
|
||||
`secretKey: ${r} has different default value: ${defaultValue.join(
|
||||
','
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
const line = paths
|
||||
.map((p) => ({
|
||||
path: p,
|
||||
errors: env[p].errorMsg,
|
||||
}))
|
||||
.filter((e) => e != null);
|
||||
|
||||
if (errors.length === 0 && warnings.length === 0 && line.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
secretKey: r,
|
||||
types,
|
||||
line,
|
||||
errors,
|
||||
warnings,
|
||||
};
|
||||
})
|
||||
.filter((r) => r != null);
|
||||
};
|
||||
|
||||
const parseEnvInfo = (result: TParseParameters[][]) => {
|
||||
return result.reduce<TFunCallArg>((acc, cur) => {
|
||||
if (cur == null) {
|
||||
return acc;
|
||||
}
|
||||
const value = parseCallChaining(cur);
|
||||
|
||||
if (value == null) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const secret = acc[value.secretKey];
|
||||
|
||||
if (secret) {
|
||||
if (secret[value.path] == null) {
|
||||
secret[value.path] = {
|
||||
required: value.required,
|
||||
type: value.type,
|
||||
...(value.default ? { default: value.default } : {}),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
acc[value.secretKey] = {
|
||||
[value.path]: {
|
||||
required: value.required,
|
||||
type: value.type,
|
||||
...(value.default ? { default: value.default } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const parseCallChaining = (args: TParseParameters[]) => {
|
||||
const typeEnvInfo = args.find((r) => r.funcName === 'typedEnv');
|
||||
|
||||
if (typeEnvInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultInfo = args.find((r) => r.funcName === 'default');
|
||||
|
||||
const optionalInfoIndex = args.findIndex((r) => r.funcName === 'optional');
|
||||
const requiredInfoIndex = args.findIndex((r) => r.funcName === 'required');
|
||||
|
||||
const toStringInfo = args.find((r) => r.funcName === 'toString');
|
||||
const toInt = args.find((r) => r.funcName === 'toInt');
|
||||
const toBoolean = args.find((r) => r.funcName === 'toBoolean');
|
||||
|
||||
if (
|
||||
typeEnvInfo.args[0] != null &&
|
||||
["''", '""'].includes(typeEnvInfo.args[0])
|
||||
) {
|
||||
// console.warn(
|
||||
// `not support secretKey is not \"\" or '' in ${typeEnvInfo.path}`
|
||||
// );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the front and back quotation marks of the string
|
||||
const secretKey: string = typeEnvInfo.args[0].substring(
|
||||
1,
|
||||
typeEnvInfo.args[0].length - 1
|
||||
);
|
||||
|
||||
let errorMsg = '';
|
||||
|
||||
const type = toStringInfo
|
||||
? 'string'
|
||||
: toInt
|
||||
? 'number'
|
||||
: toBoolean
|
||||
? 'boolean'
|
||||
: 'string';
|
||||
|
||||
switch (type) {
|
||||
case 'number': {
|
||||
try {
|
||||
const toIntArg = parseInt(toInt!.args[0]);
|
||||
if (toIntArg < 2 || toIntArg > 36) {
|
||||
errorMsg = `radix must be between 2 and 36, but got ${toIntArg}`;
|
||||
}
|
||||
} catch (e) {
|
||||
errorMsg = `parseInt(${args[0]}) is not a number`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'boolean': {
|
||||
const toBooleanArg = toBoolean!.args[0];
|
||||
if (toBooleanArg !== 'true' && toBooleanArg !== 'false') {
|
||||
errorMsg = `toBoolean(${toBooleanArg}) is not a boolean`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
secretKey,
|
||||
path: typeEnvInfo.path,
|
||||
required:
|
||||
(optionalInfoIndex === -1 && requiredInfoIndex !== -1) ||
|
||||
optionalInfoIndex > requiredInfoIndex,
|
||||
type,
|
||||
...(defaultInfo
|
||||
? {
|
||||
default: defaultInfo.args[0],
|
||||
}
|
||||
: {}),
|
||||
...(errorMsg.length > 0 ? { errorMsg } : {}),
|
||||
};
|
||||
};
|
||||
32
packages/typed-env-cli/src/lib/utils/fs.ts
Normal file
32
packages/typed-env-cli/src/lib/utils/fs.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const checkPath = (pth: string) => {
|
||||
if (process.platform === 'win32') {
|
||||
const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(
|
||||
pth.replace(path.parse(pth).root, '')
|
||||
);
|
||||
|
||||
if (pathHasInvalidWinCharacters) {
|
||||
const error = new Error(`Path contains invalid characters: ${pth}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type Option = { mode?: number | undefined } | number;
|
||||
|
||||
const getMode = (options?: Option) => {
|
||||
const defaults = { mode: 0o777 };
|
||||
if (typeof options === 'number') return options;
|
||||
return { ...defaults, ...options }.mode;
|
||||
};
|
||||
|
||||
export const ensureDirSync = (dir: string, options?: Option) => {
|
||||
checkPath(dir);
|
||||
|
||||
return fs.mkdirSync(dir, {
|
||||
mode: getMode(options),
|
||||
recursive: true,
|
||||
});
|
||||
};
|
||||
51
packages/typed-env-cli/src/lib/utils/util.ts
Normal file
51
packages/typed-env-cli/src/lib/utils/util.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export function uniqBy<T>(input: T[], by: (i: T) => string): T[] {
|
||||
const result = new Map<string, T>();
|
||||
for (const item of input) {
|
||||
if (result.has(by(item))) {
|
||||
continue;
|
||||
}
|
||||
result.set(by(item), item);
|
||||
}
|
||||
return Array.from(result.values());
|
||||
}
|
||||
|
||||
const LINE =
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
|
||||
export function parseEnv(src: string) {
|
||||
const obj: { [key in string]: string | number | boolean | null } = {};
|
||||
|
||||
// Convert buffer to string
|
||||
let lines = src.toString();
|
||||
|
||||
// Convert line breaks to same format
|
||||
lines = lines.replace(/\r\n?/gm, '\n');
|
||||
|
||||
let match;
|
||||
while ((match = LINE.exec(lines)) != null) {
|
||||
const key = match[1];
|
||||
|
||||
// Default undefined or null to empty string
|
||||
let value = match[2] || '';
|
||||
|
||||
// Remove whitespace
|
||||
value = value.trim();
|
||||
|
||||
// Check if double quoted
|
||||
const maybeQuote = value[0];
|
||||
|
||||
// Remove surrounding quotes
|
||||
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2');
|
||||
|
||||
// Expand newlines if double quoted
|
||||
if (maybeQuote === '"') {
|
||||
value = value.replace(/\\n/g, '\n');
|
||||
value = value.replace(/\\r/g, '\r');
|
||||
}
|
||||
|
||||
// Add to object
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
22
packages/typed-env-cli/tsconfig.json
Normal file
22
packages/typed-env-cli/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
packages/typed-env-cli/tsconfig.lib.json
Normal file
19
packages/typed-env-cli/tsconfig.lib.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"sourceMap": false,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"**/__tests__/*",
|
||||
"**/__fixtures__/*"
|
||||
]
|
||||
}
|
||||
16
packages/typed-env-cli/tsconfig.spec.json
Normal file
16
packages/typed-env-cli/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"node_modules",
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"**/__tests__/*",
|
||||
"**/__fixtures__/*"
|
||||
]
|
||||
}
|
||||
18
packages/typed-env/.eslintrc.json
Normal file
18
packages/typed-env/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
21
packages/typed-env/LICENSE
Normal file
21
packages/typed-env/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018, Nick Gavrilov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
5
packages/typed-env/README.md
Normal file
5
packages/typed-env/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# typed-env
|
||||
|
||||
typed-env can help us better handle environment variables
|
||||
|
||||
## [Help Documents](https://github.com/placeholder-soft/libs#lib)
|
||||
15
packages/typed-env/jest.config.ts
Normal file
15
packages/typed-env/jest.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'typed-env',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/packages/typed-env',
|
||||
};
|
||||
18
packages/typed-env/package.json
Normal file
18
packages/typed-env/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@placeholdersoft/typed-env",
|
||||
"version": "0.0.3",
|
||||
"type": "commonjs",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/placeholder-soft/libs",
|
||||
"repository": "https://github.com/placeholder-soft/libs#usage-typed-env",
|
||||
"keywords": [
|
||||
"typed-env",
|
||||
"typed-env-cli",
|
||||
"environment variables",
|
||||
"env",
|
||||
"parser",
|
||||
"env parser",
|
||||
"typescript",
|
||||
"typed"
|
||||
]
|
||||
}
|
||||
41
packages/typed-env/project.json
Normal file
41
packages/typed-env/project.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "typed-env",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/typed-env/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/typed-env",
|
||||
"main": "packages/typed-env/src/index.ts",
|
||||
"tsConfig": "packages/typed-env/tsconfig.lib.json",
|
||||
"assets": ["packages/typed-env/*.md"]
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "node tools/scripts/publish.mjs typed-env {args.ver} {args.tag}"
|
||||
},
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["packages/typed-env/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "packages/typed-env/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
2
packages/typed-env/src/index.ts
Normal file
2
packages/typed-env/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/env-box';
|
||||
export * from './lib/typed-env';
|
||||
65
packages/typed-env/src/lib/env-box.ts
Normal file
65
packages/typed-env/src/lib/env-box.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import * as process from "process";
|
||||
|
||||
type EnvBoxValueType = string | undefined;
|
||||
type DefinedAs<T, U> = T extends undefined ? U | undefined : U;
|
||||
|
||||
export class EnvBox<T extends EnvBoxValueType> {
|
||||
constructor(readonly name: string, private readonly value: T) {}
|
||||
|
||||
required() {
|
||||
if (this.value == null) {
|
||||
throw new Error(
|
||||
`Required env variable for name: ${this.name} is not set`
|
||||
);
|
||||
}
|
||||
return new EnvBox(this.name, this.value);
|
||||
}
|
||||
|
||||
optional() {
|
||||
return new EnvBox(this.name, this.value);
|
||||
}
|
||||
|
||||
default(value: string) {
|
||||
return new EnvBox(this.name, this.value ?? value);
|
||||
}
|
||||
nonEmpty() {
|
||||
if (this.value == null || this.value === "") {
|
||||
throw new Error(`Env variable for name: ${this.name} is empty`);
|
||||
}
|
||||
return new EnvBox(this.name, this.value);
|
||||
}
|
||||
toBoolean() {
|
||||
const value = this.value;
|
||||
if (value === "true" || value === "TRUE") {
|
||||
return true;
|
||||
}
|
||||
if (value === "false" || value === "FALSE") {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Env variable for name: ${this.name} is not boolean, value: ${value}`
|
||||
);
|
||||
}
|
||||
|
||||
toInt(radix?: number): DefinedAs<T, number> {
|
||||
const value = this.toString();
|
||||
if (value == null) {
|
||||
return undefined as unknown as DefinedAs<T, number>;
|
||||
}
|
||||
const val = parseInt(value, radix);
|
||||
if (isNaN(val)) {
|
||||
throw new Error(`Env variable for name: ${this.name} is not an integer`);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
static of<T extends string>(name: T) {
|
||||
const val = process.env[name];
|
||||
return new EnvBox(name, val);
|
||||
}
|
||||
}
|
||||
35
packages/typed-env/src/lib/typed-env.test.ts
Normal file
35
packages/typed-env/src/lib/typed-env.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { anyEnv, typedEnv } from "./typed-env";
|
||||
|
||||
test("boolean env", () => {
|
||||
process.env["NX_WORKSPACE_ROOT"] = "true";
|
||||
const envBox = typedEnv("NX_WORKSPACE_ROOT");
|
||||
const rs = envBox.required().toBoolean();
|
||||
expect(rs).toBe(true);
|
||||
|
||||
process.env["ENV_TEXT_BOOLEAN_2"] = "false";
|
||||
const envBox2 = anyEnv("ENV_TEXT_BOOLEAN_2");
|
||||
const s = envBox2.optional().toBoolean();
|
||||
expect(s).toBe(false);
|
||||
});
|
||||
|
||||
test("integer env", () => {
|
||||
process.env["ENV_TEST_NUMBER"] = "123";
|
||||
const envBox = typedEnv("ENV_TEST_NUMBER");
|
||||
const rs = envBox.required().toInt();
|
||||
expect(rs).toBe(123);
|
||||
|
||||
const envBox2 = anyEnv("ENV_TEST_NUMBER_2");
|
||||
const s = envBox2.optional().toInt();
|
||||
expect(s).toBe(undefined);
|
||||
});
|
||||
|
||||
test("string env", () => {
|
||||
process.env["NX_WORKSPACE_ROOT"] = "aaa";
|
||||
const envBox = typedEnv("NX_WORKSPACE_ROOT");
|
||||
const rs = envBox.required().toString();
|
||||
expect(rs).toBe("aaa");
|
||||
|
||||
const envBox2 = anyEnv("ENV_TEST_STRING_2");
|
||||
const s = envBox2.optional().toString();
|
||||
expect(s).toBe(undefined);
|
||||
});
|
||||
17
packages/typed-env/src/lib/typed-env.ts
Normal file
17
packages/typed-env/src/lib/typed-env.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { EnvBox } from "./env-box";
|
||||
|
||||
/**
|
||||
* @param key env key
|
||||
* @returns EnvBox
|
||||
*/
|
||||
export function typedEnv<T extends string>(key: T) {
|
||||
return EnvBox.of(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key env key
|
||||
* @returns EnvBox
|
||||
*/
|
||||
export function anyEnv(key: string) {
|
||||
return EnvBox.of(key);
|
||||
}
|
||||
29
packages/typed-env/src/lib/typed-env.type.test.ts
Normal file
29
packages/typed-env/src/lib/typed-env.type.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { expectType } from "tsd";
|
||||
import { typedEnv } from "./typed-env";
|
||||
|
||||
// string
|
||||
process.env["TEST"] = "abc";
|
||||
expectType<string>(typedEnv("TEST").required().toString());
|
||||
expectType<string | undefined>(typedEnv("TEST").optional().toString());
|
||||
|
||||
// number
|
||||
process.env["TEST"] = "1";
|
||||
expectType<number>(typedEnv("TEST").required().toInt());
|
||||
expectType<number | undefined>(typedEnv("TEST").optional().toInt());
|
||||
expectType<number>(typedEnv("TEST").required().optional().toInt());
|
||||
expectType<number>(typedEnv("TEST").optional().required().toInt());
|
||||
|
||||
// boolean
|
||||
process.env["TEST"] = "true";
|
||||
expectType<boolean>(typedEnv("TEST").required().toBoolean());
|
||||
expectType<boolean | undefined>(typedEnv("TEST").optional().toBoolean());
|
||||
expectType<boolean | undefined>(typedEnv("TEST").toBoolean());
|
||||
expectType<boolean>(typedEnv("TEST").required().optional().toBoolean());
|
||||
expectType<boolean>(typedEnv("TEST").optional().required().toBoolean());
|
||||
|
||||
// default
|
||||
expectType<string>(typedEnv("TEST").default("abc").toString());
|
||||
expectType<string>(typedEnv("TEST").default("abc").optional().toString());
|
||||
expectType<string>(typedEnv("TEST").default("abc").required().toString());
|
||||
expectType<number>(typedEnv("TEST").default("1").toInt());
|
||||
expectType<boolean>(typedEnv("TEST").default("true").toBoolean());
|
||||
22
packages/typed-env/tsconfig.json
Normal file
22
packages/typed-env/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
packages/typed-env/tsconfig.lib.json
Normal file
19
packages/typed-env/tsconfig.lib.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"sourceMap": false,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"**/__tests__/*",
|
||||
"**/__fixtures__/*"
|
||||
]
|
||||
}
|
||||
14
packages/typed-env/tsconfig.spec.json
Normal file
14
packages/typed-env/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
7418
pnpm-lock.yaml
generated
Normal file
7418
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
tools/generators/.gitkeep
Normal file
0
tools/generators/.gitkeep
Normal file
61
tools/scripts/publish.mjs
Normal file
61
tools/scripts/publish.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* This is a minimal script to publish your package to "npm".
|
||||
* This is meant to be used as-is or customize as you see fit.
|
||||
*
|
||||
* This script is executed on "dist/path/to/library" as "cwd" by default.
|
||||
*
|
||||
* You might need to authenticate with NPM before running this script.
|
||||
*/
|
||||
|
||||
import { readCachedProjectGraph } from '@nrwl/devkit';
|
||||
import { execSync } from 'child_process';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import chalk from 'chalk';
|
||||
|
||||
function invariant(condition, message) {
|
||||
if (!condition) {
|
||||
console.error(chalk.bold.red(message));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Executing publish script: node path/to/publish.mjs {name} --version {version} --tag {tag}
|
||||
// Default "tag" to "next" so we won't publish the "latest" tag by accident.
|
||||
const [, , name, version, tag = 'next'] = process.argv;
|
||||
|
||||
// A simple SemVer validation to validate the version
|
||||
const validVersion = /^\d+\.\d+\.\d+(-\w+\.\d+)?/;
|
||||
invariant(
|
||||
version && validVersion.test(version),
|
||||
`No version provided or version did not match Semantic Versioning, expected: #.#.#-tag.# or #.#.#, got ${version}.`
|
||||
);
|
||||
|
||||
const graph = readCachedProjectGraph();
|
||||
const project = graph.nodes[name];
|
||||
|
||||
invariant(
|
||||
project,
|
||||
`Could not find project "${name}" in the workspace. Is the project.json configured correctly?`
|
||||
);
|
||||
|
||||
const outputPath = project.data?.targets?.build?.options?.outputPath;
|
||||
invariant(
|
||||
outputPath,
|
||||
`Could not find "build.options.outputPath" of project "${name}". Is project.json configured correctly?`
|
||||
);
|
||||
|
||||
process.chdir(outputPath);
|
||||
|
||||
// Updating the version in "package.json" before publishing
|
||||
try {
|
||||
const json = JSON.parse(readFileSync(`package.json`).toString());
|
||||
json.version = version;
|
||||
writeFileSync(`package.json`, JSON.stringify(json, null, 2));
|
||||
} catch (e) {
|
||||
console.error(
|
||||
chalk.bold.red(`Error reading package.json file from library build output.`)
|
||||
);
|
||||
}
|
||||
|
||||
// Execute "npm publish" to publish
|
||||
execSync(`npm publish --access public --tag ${tag}`);
|
||||
12
tools/tsconfig.tools.json
Normal file
12
tools/tsconfig.tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/out-tsc/tools",
|
||||
"rootDir": ".",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": ["node"],
|
||||
"importHelpers": false
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
29
tsconfig.base.json
Normal file
29
tsconfig.base.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"esModuleInterop": true,
|
||||
"preserveConstEnums": true,
|
||||
"preserveWatchOutput": true,
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"module": "esnext",
|
||||
"lib": ["es2017", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@placeholdersoft/typed-env": ["packages/typed-env/src/index.ts"],
|
||||
"@placeholdersoft/typed-env-cli": ["packages/typed-env-cli/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "tmp"]
|
||||
}
|
||||
Reference in New Issue
Block a user