lsh-framework 3.1.24 → 3.1.25

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/dist/cli.js CHANGED
@@ -62,6 +62,7 @@ program
62
62
  console.log(CLI_HELP.CMD_GET);
63
63
  console.log(CLI_HELP.CMD_SET);
64
64
  console.log(CLI_HELP.CMD_DELETE);
65
+ console.log(CLI_HELP.CMD_CP);
65
66
  console.log(CLI_HELP.CMD_STATUS);
66
67
  console.log('');
67
68
  console.log(CLI_HELP.SECTION_IPFS);
@@ -232,6 +232,7 @@ export const CLI_HELP = {
232
232
  CMD_GET: ' get <key> Get a specific secret value (--all for all)',
233
233
  CMD_SET: ' set <key> <value> Set a specific secret value',
234
234
  CMD_DELETE: ' delete Delete .env file',
235
+ CMD_CP: ' cp <from> <to> Copy env variables between files (--name to copy one variable)',
235
236
  CMD_STATUS: ' status Get detailed secrets status',
236
237
  // IPFS commands
237
238
  CMD_SYNC_INIT: ' sync init Full IPFS setup (install, init, start)',
@@ -1090,5 +1090,130 @@ API_KEY=
1090
1090
  process.exit(1);
1091
1091
  }
1092
1092
  });
1093
+ // Copy env variables from one file to another
1094
+ program
1095
+ .command('cp <from> <to>')
1096
+ .description('Copy env variables from <from> to <to> (like cp, but for .env files)')
1097
+ .option('--merge', 'Merge into destination instead of overwriting; source takes precedence on conflicts')
1098
+ .option('--no-overwrite', 'With --merge, skip keys that already exist in destination')
1099
+ .option('-n, --name <key>', 'Copy only the specified variable (implies --merge into destination)')
1100
+ .action(async (from, to, options) => {
1101
+ try {
1102
+ const fromPath = path.resolve(from);
1103
+ const toPath = path.resolve(to);
1104
+ if (!fs.existsSync(fromPath)) {
1105
+ console.error(`❌ Source file not found: ${fromPath}`);
1106
+ process.exit(1);
1107
+ }
1108
+ // Parse source file
1109
+ const srcContent = fs.readFileSync(fromPath, 'utf8');
1110
+ const srcLines = srcContent.split('\n');
1111
+ const srcVars = new Map();
1112
+ for (const line of srcLines) {
1113
+ if (line.trim().startsWith('#') || !line.trim())
1114
+ continue;
1115
+ const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
1116
+ if (match) {
1117
+ const key = match[1].trim();
1118
+ let value = match[2].trim();
1119
+ if ((value.startsWith('"') && value.endsWith('"')) ||
1120
+ (value.startsWith("'") && value.endsWith("'"))) {
1121
+ value = value.slice(1, -1);
1122
+ }
1123
+ srcVars.set(key, value);
1124
+ }
1125
+ }
1126
+ if (srcVars.size === 0) {
1127
+ console.error(`❌ No env variables found in: ${fromPath}`);
1128
+ process.exit(1);
1129
+ }
1130
+ // --name: copy only a single named variable (always merges into destination)
1131
+ if (options.name) {
1132
+ const targetKey = options.name;
1133
+ if (!srcVars.has(targetKey)) {
1134
+ console.error(`❌ Variable '${targetKey}' not found in ${from}`);
1135
+ process.exit(1);
1136
+ }
1137
+ const targetValue = srcVars.get(targetKey);
1138
+ await setSingleSecret(toPath, targetKey, targetValue);
1139
+ console.log(`✅ Copied ${targetKey} from ${from} to ${to}`);
1140
+ return;
1141
+ }
1142
+ if (!options.merge) {
1143
+ // Default: overwrite destination entirely (like regular cp)
1144
+ const newLines = [];
1145
+ for (const [key, value] of srcVars.entries()) {
1146
+ newLines.push(formatEnvLine(key, value, toPath));
1147
+ }
1148
+ fs.writeFileSync(toPath, newLines.join('\n') + '\n', 'utf8');
1149
+ console.log(`✅ Copied ${srcVars.size} variable(s) from ${from} to ${to}`);
1150
+ return;
1151
+ }
1152
+ // Merge mode: apply source vars into destination
1153
+ const noOverwrite = options.overwrite === false;
1154
+ const destLines = [];
1155
+ if (fs.existsSync(toPath)) {
1156
+ const destContent = fs.readFileSync(toPath, 'utf8');
1157
+ for (const line of destContent.split('\n')) {
1158
+ destLines.push(line);
1159
+ }
1160
+ }
1161
+ // Build merged content: walk destination lines, update/keep keys
1162
+ const processedKeys = new Set();
1163
+ const mergedLines = [];
1164
+ for (const line of destLines) {
1165
+ if (line.trim().startsWith('#') || !line.trim()) {
1166
+ mergedLines.push(line);
1167
+ continue;
1168
+ }
1169
+ const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
1170
+ if (match) {
1171
+ const key = match[1].trim();
1172
+ if (srcVars.has(key)) {
1173
+ if (noOverwrite) {
1174
+ mergedLines.push(line);
1175
+ }
1176
+ else {
1177
+ mergedLines.push(formatEnvLine(key, srcVars.get(key), toPath));
1178
+ }
1179
+ processedKeys.add(key);
1180
+ }
1181
+ else {
1182
+ mergedLines.push(line);
1183
+ }
1184
+ }
1185
+ else {
1186
+ mergedLines.push(line);
1187
+ }
1188
+ }
1189
+ // Append new keys from source not in destination
1190
+ let added = 0;
1191
+ for (const [key, value] of srcVars.entries()) {
1192
+ if (!processedKeys.has(key)) {
1193
+ mergedLines.push(formatEnvLine(key, value, toPath));
1194
+ added++;
1195
+ }
1196
+ }
1197
+ let finalContent = mergedLines.join('\n');
1198
+ if (!finalContent.endsWith('\n'))
1199
+ finalContent += '\n';
1200
+ fs.writeFileSync(toPath, finalContent, 'utf8');
1201
+ const updated = srcVars.size - added;
1202
+ const skipped = noOverwrite ? updated : 0;
1203
+ const overwritten = noOverwrite ? 0 : updated;
1204
+ console.log(`✅ Merged ${from} into ${to}:`);
1205
+ if (added > 0)
1206
+ console.log(` Added: ${added} new variable(s)`);
1207
+ if (overwritten > 0)
1208
+ console.log(` Overwritten: ${overwritten} existing variable(s)`);
1209
+ if (skipped > 0)
1210
+ console.log(` Skipped: ${skipped} existing variable(s) (--no-overwrite)`);
1211
+ }
1212
+ catch (error) {
1213
+ const err = error;
1214
+ console.error('❌ Failed to copy env file:', err.message);
1215
+ process.exit(1);
1216
+ }
1217
+ });
1093
1218
  }
1094
1219
  export default init_secrets;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "3.1.24",
3
+ "version": "3.1.25",
4
4
  "description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {