├── .editorconfig
├── .gitignore
├── license
├── package.json
├── readme.md
├── resources
├── demo.gif
└── demo.mp4
├── src
├── bin.ts
├── constants.ts
└── index.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 |
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.err
3 | *.log
4 | ._*
5 | .cache
6 | .fseventsd
7 | .DocumentRevisions*
8 | .DS_Store
9 | .TemporaryItems
10 | .Trashes
11 | Thumbs.db
12 |
13 | dist
14 | node_modules
15 | package-lock.json
16 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021-present Fabio Spampinato
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fabiospampinato/secret",
3 | "repository": "github:fabiospampinato/secret",
4 | "description": "The simplest command to encrypt/decrypt a file, useful for committing encrypted \".env\" files to version control, among other things.",
5 | "version": "3.0.2",
6 | "type": "module",
7 | "bin": "dist/bin.js",
8 | "main": "dist/index.js",
9 | "exports": "./dist/index.js",
10 | "types": "dist/index.d.ts",
11 | "scripts": {
12 | "clean": "tsex clean",
13 | "compile": "tsex compile",
14 | "compile:watch": "tsex compile --watch",
15 | "prepublishOnly": "tsex prepare"
16 | },
17 | "keywords": [
18 | "encrypt",
19 | "decrypt",
20 | "file",
21 | "env",
22 | "version control",
23 | "vcs",
24 | "git",
25 | "secret",
26 | "secrets"
27 | ],
28 | "dependencies": {
29 | "prask": "^2.0.0",
30 | "specialist": "^1.3.0",
31 | "tiny-encryptor": "^1.0.0"
32 | },
33 | "devDependencies": {
34 | "@types/node": "^20.4.10",
35 | "tsex": "^3.0.1",
36 | "typescript": "^5.1.6"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # Secret
7 |
8 | The simplest command to encrypt/decrypt a file, useful for committing encrypted ".env" files to version control, among other things.
9 |
10 | Secret automatically detects if you want to encrypt or decrypt a file: if the file ends with the ".secret" extension then it will try to decrypt it, otherwise it will try to encrypt it.
11 |
12 | ## Install
13 |
14 | ```sh
15 | npm install -g @fabiospampinato/secret
16 | ```
17 |
18 | ## Usage
19 |
20 | Encrypt a file:
21 |
22 | ```sh
23 | secret myFile.txt
24 | ```
25 |
26 | Decrypt a file:
27 |
28 | ```sh
29 | secret myFile.txt.secret
30 | ```
31 |
32 | ## License
33 |
34 | MIT © Fabio Spampinato
35 |
--------------------------------------------------------------------------------
/resources/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fabiospampinato/secret/2febdb0dcf881a41a591606d4acb0b860d77a8a7/resources/demo.gif
--------------------------------------------------------------------------------
/resources/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fabiospampinato/secret/2febdb0dcf881a41a591606d4acb0b860d77a8a7/resources/demo.mp4
--------------------------------------------------------------------------------
/src/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /* IMPORT */
4 |
5 | import fs from 'node:fs';
6 | import path from 'node:path';
7 | import process from 'node:process';
8 | import * as prask from 'prask';
9 | import {bin} from 'specialist';
10 | import {SECRET_SUFFIX} from './constants';
11 | import Secret from '.';
12 |
13 | /* MAIN */
14 |
15 | bin ( 'secret', 'The simplest command to encrypt/decrypt a file' )
16 | /* DEFAULT COMMAND */
17 | .argument ( '', 'The file to encrypt or descrypt' )
18 | .action ( async ( options, inputFiles ) => {
19 | /* INPUT */
20 | const inputFile = inputFiles[0];
21 | const inputPath = path.resolve ( process.cwd (), inputFile );
22 | const inputExists = fs.existsSync ( inputPath );
23 | if ( !inputExists ) return prask.log.error ( `"${inputFile}" does not exist!` );
24 | const inputStat = fs.lstatSync ( inputPath );
25 | if ( !inputStat.isFile () ) return prask.log.error ( `"${inputFile}" is not a file!` );
26 | const inputBuffer = fs.readFileSync ( inputPath );
27 | /* PASSWORD */
28 | const isSecret = inputFile.endsWith ( SECRET_SUFFIX );
29 | const password = await prask.password ({ message: 'Password:', required: true });
30 | if ( !password ) return;
31 | const passwordConfirmation = isSecret ? password : await prask.password ({ message: 'Password confirmation:', required: true });
32 | if ( !passwordConfirmation ) return;
33 | if ( password !== passwordConfirmation ) return prask.log.error ( 'The two passwords do not match!' );
34 | /* OUTPUT */
35 | try {
36 | const transform = isSecret ? Secret.decrypt : Secret.encrypt;
37 | const outputBuffer = await transform ( inputBuffer, password );
38 | const outputFile = isSecret ? path.basename ( inputFile, path.extname ( inputFile ) ) : `${inputFile}${SECRET_SUFFIX}`;
39 | const outputPath = path.resolve ( process.cwd (), outputFile );
40 | const outputExists = fs.existsSync ( outputPath );
41 | const outputOverwrite = !outputExists || await prask.toggle ({ message: `"${outputFile}" already exists, do you want to overwrite it?` });
42 | if ( !outputOverwrite ) return;
43 | fs.writeFileSync ( outputPath, outputBuffer );
44 | prask.log.success ( `${isSecret ? 'Decrypted' : 'Encrypted'}, "${inputFile}" -> "${outputFile}"` );
45 | } catch {
46 | prask.log.error ( `${isSecret ? 'Decryption' : 'Encryption'} failed, try again!` );
47 | }
48 | })
49 | /* RETURN */
50 | .run ();
51 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 |
2 | /* MAIN */
3 |
4 | const ENCRYPTION_PBKDF2_ROUNDS = 500_000;
5 |
6 | const SECRET_SUFFIX = '.secret';
7 |
8 | /* EXPORT */
9 |
10 | export {ENCRYPTION_PBKDF2_ROUNDS, SECRET_SUFFIX};
11 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | /* IMPORT */
3 |
4 | import Encryptor from 'tiny-encryptor';
5 | import {ENCRYPTION_PBKDF2_ROUNDS} from './constants';
6 |
7 | /* MAIN */
8 |
9 | const Secret = {
10 |
11 | /* API */
12 |
13 | encrypt: ( data: Uint8Array, secret: string ): Promise => {
14 |
15 | return Encryptor.encrypt ( data, secret, undefined, ENCRYPTION_PBKDF2_ROUNDS );
16 |
17 | },
18 |
19 | decrypt: ( data: Uint8Array, secret: string ): Promise => {
20 |
21 | return Encryptor.decrypt ( data, secret );
22 |
23 | }
24 |
25 | };
26 |
27 | /* EXPORT */
28 |
29 | export default Secret;
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsex/tsconfig.json",
3 | "compilerOptions": {
4 | "noUnusedParameters": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------