@typinghare/trick 1.0.4 → 1.0.7

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
@@ -3,5 +3,91 @@
3
3
  # Install
4
4
 
5
5
  ```shell
6
+ # npm
7
+ npm install -g @typinghare/trick
8
+
9
+ # pnpm
6
10
  pnpm add -g @typinghare/trick
11
+
12
+ # yarn
13
+ yarn add -g @typinghare/trick
14
+ ```
15
+
16
+ ## Philosophy
17
+
18
+ We often add sensitive files, such as `.env` and `api_key.conf`, to `.gitignore`, preventing them from being committed or even pushed to remote depots for safety reasons. Then, we have to manually copy the file to the server. It could be effortless if we have only one file, but imagine we have more in a bigger project. Even worse, some careless people (me) even lost their sensitive files after they changed their computers!
19
+
20
+ **Trick** helps you to encrypt sensitive files with a secret so that you can upload the file to Git platforms. Later on the server, just use the same secret to decrypt the files with ease.
21
+
22
+ ## Quick Example
23
+
24
+ Set up the **secret name** and the files needed to be encrypted:
25
+
26
+ ```bash
27
+ # This will create a trick.config.json at the first time
28
+ # trick add <secret-name> [files...]
29
+ $ trick add AN_EXAMPLE_SECRET .env api_key.conf
30
+
31
+ # Display the list of secret names and the files bound
32
+ $ trick list
33
+ ```
34
+
35
+ Set up the `AN_EXAMPLE_SECRET` using `export` or put it in the login file:
36
+
37
+ ```bash
38
+ $ export AN_EXAMPLE_SECRET="fQyX5O59MJuFD4l/tf2FYApqZY12N+UatEC6FC6mN7k="
39
+ # Alternatively, put the above command to login files like `.bashrc` and `.zshrc`
40
+ ```
41
+
42
+ Encrypt the files:
43
+
44
+ ```bash
45
+ $ trick encrypt AN_EXAMPLE_SECRET
46
+ ```
47
+
48
+ You will see the following output:
49
+
50
+ ```text
51
+ [ENCRYPTED] .env -> .trick/encrypted/.env.enc
52
+ [ENCRYPTED] api_key.conf -> .trick/encrypted/api_key.conf.enc
53
+ ```
54
+
55
+ Encrypted files are all saved to `.trick`. On the server side, you can encrypt the files effortlessly:
56
+
57
+ ```bash
58
+ $ export AN_EXAMPLE_SECRET="fQyX5O59MJuFD4l/tf2FYApqZY12N+UatEC6FC6mN7k="
59
+ $ trick decrypt AN_EXAMPLE_SECRET
60
+ ```
61
+
62
+ And you will see that the files are restored:
63
+
64
+ ```text
65
+ [DECRYPTED] .trick/encrypted/.env.enc -> .env
66
+ [DECRYPTED] .trick/encrypted/api_key.conf.enc -> api_key.conf
67
+ ```
68
+
69
+ > [!IMPORTANT]
70
+ >
71
+ > You need to keep the secret to yourself! Make a copy on Cloud or write it down!
72
+
73
+ ## More Features
74
+
75
+ ### Default Secret Name
76
+
77
+ You can set the default secret name so that you don't need to input it every time:
78
+
79
+ ```bash
80
+ # Set the default secret name
81
+ $ trick set-default AN_EXAMPLE_SECRET
82
+
83
+ # Display the default secret name
84
+ $ trick get-default
7
85
  ```
86
+
87
+ Now you can encrypt and decrypt more easily:
88
+
89
+ ```bash
90
+ $ trick encrypt
91
+ $ trick decrypt
92
+ ```
93
+
package/dist/app.js CHANGED
@@ -6,29 +6,65 @@ import { TRICK_ENCRYPTED_DIR } from './constant.js';
6
6
  import fsExtra from 'fs-extra';
7
7
  import chalk from 'chalk';
8
8
  const program = new Command();
9
- program.version('Trick v1.0.3 \nby James Chen (jameschan312.cn@gmail.com)');
9
+ program.version('1.0.7');
10
10
  program.description('Save credential files to remote safely.');
11
11
  program
12
12
  .command('add')
13
- .description('Adds a target.')
13
+ .description('Add a target or add files to an existing target.')
14
14
  .argument('<secret-name>', 'The name of secret in the environment')
15
15
  .argument('[files...]', 'Files this target will encrypt')
16
16
  .action(async (secretName, files) => {
17
17
  await updateConfig((config) => {
18
18
  try {
19
- getTargetFromConfig(config, secretName);
19
+ const target = getTargetFromConfig(config, secretName);
20
+ target.files.push(...files);
21
+ return true;
20
22
  }
21
23
  catch (err) {
22
24
  config.default_secret_name = secretName;
23
- config.targets.push({
24
- secret_name: secretName,
25
- files,
26
- });
25
+ config.targets.push({ secret_name: secretName, files });
27
26
  return true;
28
27
  }
29
- console.error(`Target with the secret name already exists: ${secretName}`);
30
- console.error('Abort adding target');
31
- process.exit(1);
28
+ });
29
+ });
30
+ program
31
+ .command('remove')
32
+ .description('Remove files from a specific target.')
33
+ .argument('<secret-name>', 'The name of secret in the environment.')
34
+ .argument('[files...]', 'Files to remove.')
35
+ .option('-t, --target', 'Remove the target instead.')
36
+ .action(async (secretName, files, options) => {
37
+ if (options.target) {
38
+ await updateConfig((config) => {
39
+ const index = config.targets.findIndex((target) => target.secret_name === secretName);
40
+ if (index == -1) {
41
+ console.log(chalk.yellow(`[WARNING] Target not found: ${secretName}`));
42
+ return false;
43
+ }
44
+ config.targets.splice(index, 1);
45
+ console.log(`[SUCCESS] Removed target: ${secretName}`);
46
+ return true;
47
+ });
48
+ return;
49
+ }
50
+ await updateConfig((config) => {
51
+ try {
52
+ const target = getTargetFromConfig(config, secretName);
53
+ const target_files = target.files;
54
+ for (const file of files) {
55
+ const index = target_files.indexOf(file);
56
+ if (index != -1) {
57
+ target_files.splice(index, 1);
58
+ console.log(`[SUCCESS] Removed file: ${file}`);
59
+ continue;
60
+ }
61
+ console.log(chalk.yellow(`[WARNING] File does not exist in the target: ${file}`));
62
+ }
63
+ }
64
+ catch (err) {
65
+ config.default_secret_name = secretName;
66
+ }
67
+ return true;
32
68
  });
33
69
  });
34
70
  function checkSecretName(secretName, defaultSecretName) {
package/dist/config.js CHANGED
@@ -10,7 +10,7 @@ export class ReadConfigError extends Error {
10
10
  }
11
11
  export async function writeConfig(config) {
12
12
  try {
13
- await fsExtra.writeJson(CONFIG_FILE_NAME, config);
13
+ await fsExtra.writeFile(CONFIG_FILE_NAME, JSON.stringify(config, null, 2));
14
14
  }
15
15
  catch (err) {
16
16
  throw new WriteConfigError();
package/dist/encrypt.js CHANGED
@@ -36,7 +36,7 @@ export async function encryptFile(srcFilePath, destFilePath, secret, iteration_c
36
36
  '-out',
37
37
  destFilePath,
38
38
  '-pass',
39
- `pass:${secret}`,
39
+ `'pass:${secret}'`,
40
40
  ].join(' ');
41
41
  await fsExtra.ensureDir(path.dirname(destFilePath));
42
42
  try {
@@ -69,7 +69,7 @@ export async function decryptFile(srcFilePath, destFilePath, secret, iteration_c
69
69
  '-out',
70
70
  srcFilePath,
71
71
  '-pass',
72
- `pass:${secret}`,
72
+ `'pass:${secret}'`,
73
73
  ].join(' ');
74
74
  await fsExtra.ensureDir(path.dirname(srcFilePath));
75
75
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@typinghare/trick",
3
3
  "description": "Save credential files to remote safely.",
4
- "version": "1.0.4",
4
+ "version": "1.0.7",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
@@ -22,9 +22,18 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/fs-extra": "^11.0.4",
25
- "prettier": "^3.3.3"
25
+ "prettier": "^3.3.3",
26
+ "typescript": "^5.8.2"
27
+ },
28
+ "prettier": {
29
+ "trailingComma": "es5",
30
+ "tabWidth": 4,
31
+ "semi": false,
32
+ "singleQuote": true,
33
+ "endOfLine": "lf",
34
+ "printWidth": 80
26
35
  },
27
36
  "scripts": {
28
- "build": "tsc"
37
+ "build": "node_modules/typescript/bin/tsc"
29
38
  }
30
39
  }
package/src/app.ts CHANGED
@@ -20,35 +20,95 @@ import chalk from 'chalk'
20
20
 
21
21
  const program = new Command()
22
22
 
23
- program.version('Trick v1.0.3 \nby James Chen (jameschan312.cn@gmail.com)')
23
+ program.version('1.0.7')
24
24
  program.description('Save credential files to remote safely.')
25
25
 
26
26
  program
27
27
  .command('add')
28
- .description('Adds a target.')
28
+ .description('Add a target or add files to an existing target.')
29
29
  .argument('<secret-name>', 'The name of secret in the environment')
30
30
  .argument('[files...]', 'Files this target will encrypt')
31
31
  .action(async (secretName: string, files: string[]): Promise<void> => {
32
32
  await updateConfig((config) => {
33
33
  try {
34
- getTargetFromConfig(config, secretName)
34
+ const target = getTargetFromConfig(config, secretName)
35
+ target.files.push(...files)
36
+
37
+ return true
35
38
  } catch (err) {
36
39
  config.default_secret_name = secretName
37
- config.targets.push({
38
- secret_name: secretName,
39
- files,
40
- })
40
+ config.targets.push({ secret_name: secretName, files })
41
+
41
42
  return true
42
43
  }
43
-
44
- console.error(
45
- `Target with the secret name already exists: ${secretName}`
46
- )
47
- console.error('Abort adding target')
48
- process.exit(1)
49
44
  })
50
45
  })
51
46
 
47
+ program
48
+ .command('remove')
49
+ .description('Remove files from a specific target.')
50
+ .argument('<secret-name>', 'The name of secret in the environment.')
51
+ .argument('[files...]', 'Files to remove.')
52
+ .option('-t, --target', 'Remove the target instead.')
53
+ .action(
54
+ async (
55
+ secretName: string,
56
+ files: string[],
57
+ options: {
58
+ target: boolean
59
+ }
60
+ ): Promise<void> => {
61
+ if (options.target) {
62
+ await updateConfig((config) => {
63
+ const index: number = config.targets.findIndex(
64
+ (target) => target.secret_name === secretName
65
+ )
66
+
67
+ if (index == -1) {
68
+ console.log(
69
+ chalk.yellow(
70
+ `[WARNING] Target not found: ${secretName}`
71
+ )
72
+ )
73
+
74
+ return false
75
+ }
76
+
77
+ config.targets.splice(index, 1)
78
+ console.log(`[SUCCESS] Removed target: ${secretName}`)
79
+
80
+ return true
81
+ })
82
+ return
83
+ }
84
+
85
+ await updateConfig((config) => {
86
+ try {
87
+ const target = getTargetFromConfig(config, secretName)
88
+ const target_files = target.files
89
+ for (const file of files) {
90
+ const index = target_files.indexOf(file)
91
+ if (index != -1) {
92
+ target_files.splice(index, 1)
93
+ console.log(`[SUCCESS] Removed file: ${file}`)
94
+ continue
95
+ }
96
+
97
+ console.log(
98
+ chalk.yellow(
99
+ `[WARNING] File does not exist in the target: ${file}`
100
+ )
101
+ )
102
+ }
103
+ } catch (err: unknown) {
104
+ config.default_secret_name = secretName
105
+ }
106
+
107
+ return true
108
+ })
109
+ }
110
+ )
111
+
52
112
  function checkSecretName(
53
113
  secretName?: string,
54
114
  defaultSecretName?: string
package/src/config.ts CHANGED
@@ -18,15 +18,16 @@ const defaultConfig: Config = {
18
18
  targets: [],
19
19
  }
20
20
 
21
- export class WriteConfigError extends Error {
22
- }
21
+ export class WriteConfigError extends Error {}
23
22
 
24
- export class ReadConfigError extends Error {
25
- }
23
+ export class ReadConfigError extends Error {}
26
24
 
27
25
  export async function writeConfig(config: Config): Promise<void> {
28
26
  try {
29
- await fsExtra.writeJson(CONFIG_FILE_NAME, config)
27
+ await fsExtra.writeFile(
28
+ CONFIG_FILE_NAME,
29
+ JSON.stringify(config, null, 2)
30
+ )
30
31
  } catch (err) {
31
32
  throw new WriteConfigError()
32
33
  }
package/src/encrypt.ts CHANGED
@@ -43,7 +43,7 @@ export async function encryptFile(
43
43
  '-out',
44
44
  destFilePath,
45
45
  '-pass',
46
- `pass:${secret}`,
46
+ `'pass:${secret}'`,
47
47
  ].join(' ')
48
48
 
49
49
  await fsExtra.ensureDir(path.dirname(destFilePath))
@@ -93,7 +93,7 @@ export async function decryptFile(
93
93
  '-out',
94
94
  srcFilePath,
95
95
  '-pass',
96
- `pass:${secret}`,
96
+ `'pass:${secret}'`,
97
97
  ].join(' ')
98
98
 
99
99
  await fsExtra.ensureDir(path.dirname(srcFilePath))
package/trick.config.json CHANGED
@@ -2,12 +2,19 @@
2
2
  "iteration_count": 114514,
3
3
  "targets": [
4
4
  {
5
- "secret_name": "ROBLOX_PROJECT_TRICK_SECRET",
5
+ "secret_name": "TRICK_SECRET_NAME",
6
6
  "files": [
7
7
  "test/resources/really.json",
8
8
  "test/resources/task.yml"
9
9
  ]
10
+ },
11
+ {
12
+ "secret_name": "ANOTHER_TRICK_SECRET_NAME",
13
+ "files": [
14
+ "test/main/resources/database.properties",
15
+ "test/main/resources/password.properties"
16
+ ]
10
17
  }
11
18
  ],
12
- "default_secret_name": "ROBLOX_PROJECT_TRICK_SECRET"
13
- }
19
+ "default_secret_name": "ANOTHER_TRICK_SECRET_NAME"
20
+ }
package/.prettierrc.yaml DELETED
@@ -1,6 +0,0 @@
1
- trailingComma: es5
2
- tabWidth: 4
3
- semi: false
4
- singleQuote: true
5
- endOfLine: lf
6
- printWidth: 80
@@ -1 +0,0 @@
1
- Salted__�;��a��S�K�9ℑ���2�����4�'Q���\0]�G~G01�߯G���
@@ -1 +0,0 @@
1
- Salted__�)�"�K 7�bk��aQ��ܰ^�B9��������.3.�g�!g�$�XjM�q�~q�c76C�i����0�S