local-kv-cli 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +118 -10
- package/dist/args.d.ts +7 -0
- package/dist/args.js +71 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +306 -56
- package/dist/clipboard.d.ts +2 -0
- package/dist/clipboard.js +24 -0
- package/dist/env.d.ts +13 -0
- package/dist/env.js +59 -0
- package/dist/format.d.ts +34 -0
- package/dist/format.js +86 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +9 -2
- package/dist/locate.d.ts +19 -0
- package/dist/locate.js +75 -0
- package/dist/store.d.ts +66 -14
- package/dist/store.js +262 -58
- package/dist/values.d.ts +14 -0
- package/dist/values.js +73 -0
- package/package.json +12 -6
package/dist/cli.js
CHANGED
|
@@ -1,100 +1,350 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.
|
|
37
|
+
exports.main = main;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const args_1 = require("./args");
|
|
41
|
+
const clipboard_1 = require("./clipboard");
|
|
42
|
+
const env_1 = require("./env");
|
|
43
|
+
const format_1 = require("./format");
|
|
44
|
+
const locate_1 = require("./locate");
|
|
5
45
|
const store_1 = require("./store");
|
|
46
|
+
const values_1 = require("./values");
|
|
6
47
|
const HELP = `lkv - store key-value pairs locally
|
|
7
48
|
|
|
8
49
|
Usage:
|
|
9
|
-
lkv set <key> <value>
|
|
10
|
-
lkv get <key>
|
|
11
|
-
lkv update <key> <value>
|
|
12
|
-
lkv delete <key>
|
|
13
|
-
lkv
|
|
14
|
-
lkv
|
|
15
|
-
lkv
|
|
16
|
-
lkv
|
|
50
|
+
lkv set <key> <value> Create a pair (--force to overwrite)
|
|
51
|
+
lkv get <key> Print a value (--copy to copy it, --json for JSON)
|
|
52
|
+
lkv update <key> <value> Change an existing value
|
|
53
|
+
lkv delete <key> Remove a pair (alias: rm)
|
|
54
|
+
lkv rename <old> <new> Rename a key (--force to overwrite) (alias: mv)
|
|
55
|
+
lkv incr <key> [amount] Add to a number (starts at 0)
|
|
56
|
+
lkv decr <key> [amount] Subtract from a number
|
|
57
|
+
lkv list Show all pairs (alias: ls)
|
|
58
|
+
--long show type and last update
|
|
59
|
+
--filter only keys containing some text
|
|
60
|
+
--json print as JSON
|
|
61
|
+
lkv search <text> Find pairs whose key or value contains text
|
|
62
|
+
lkv history <key> Show previous values of a key
|
|
63
|
+
lkv namespaces List namespaces (alias: ns)
|
|
64
|
+
lkv clear Remove all pairs in the namespace
|
|
65
|
+
lkv export [file] Back up as JSON (--all for every namespace)
|
|
66
|
+
lkv import <file> Restore a backup (--force to overwrite existing keys)
|
|
67
|
+
lkv env Print pairs as environment variables
|
|
68
|
+
--shell sh|powershell|cmd, --upper for UPPERCASE names
|
|
69
|
+
lkv run <command...> Run a command with the pairs as environment variables
|
|
70
|
+
lkv init Create a project store (.lkv.json) in this folder
|
|
71
|
+
lkv path Show which store file is in use
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
-n, --namespace <name> Work in a namespace (default: "default")
|
|
75
|
+
-t, --type <type> Value type for set/update: ${values_1.VALUE_TYPES.join(", ")}
|
|
76
|
+
-g, --global Use the global store even inside a project
|
|
77
|
+
-v, --version Show the version
|
|
78
|
+
|
|
79
|
+
Values like 42, true or {"a":1} are stored as numbers, booleans and JSON.
|
|
80
|
+
Use --type string to keep them as text. Quote values that contain spaces.
|
|
17
81
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
82
|
+
Examples:
|
|
83
|
+
lkv set api_url https://example.com
|
|
84
|
+
lkv incr build_count
|
|
85
|
+
lkv -n work set token abc123
|
|
86
|
+
eval "$(lkv env)" (bash/zsh)
|
|
87
|
+
lkv env | Invoke-Expression (PowerShell)`;
|
|
88
|
+
class CliError extends Error {
|
|
22
89
|
}
|
|
23
|
-
function
|
|
90
|
+
function need(args, count, usage) {
|
|
24
91
|
if (args.length < count)
|
|
25
|
-
|
|
92
|
+
throw new CliError(`Usage: lkv ${usage}`);
|
|
26
93
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
94
|
+
/** Runs the CLI and returns the exit code. */
|
|
95
|
+
function main(argv) {
|
|
96
|
+
const parsed = (0, args_1.parseArgs)(argv);
|
|
97
|
+
const { command, flags } = parsed;
|
|
98
|
+
if (flags.has("version")) {
|
|
99
|
+
console.log(readVersion());
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
if (flags.has("help") || command === undefined || command === "help") {
|
|
103
|
+
console.log(HELP);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (command === "init")
|
|
107
|
+
return init();
|
|
108
|
+
const store = new store_1.Store({ namespace: parsed.options.namespace, global: flags.has("global") });
|
|
109
|
+
try {
|
|
110
|
+
return runCommand(store, parsed);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
if (store.migratedBackup) {
|
|
114
|
+
console.error(`Note: upgraded your data to the v2 format (backup saved to ${store.migratedBackup})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function runCommand(store, { command, args, options, flags }) {
|
|
30
119
|
switch (command) {
|
|
31
120
|
case "set": {
|
|
32
|
-
|
|
121
|
+
need(args, 2, "set <key> <value>");
|
|
33
122
|
const [key, ...rest] = args;
|
|
34
|
-
store.set(key, rest.
|
|
123
|
+
store.set(key, readValue(rest, options.type), { force: flags.has("force") });
|
|
35
124
|
console.log(`Saved "${key}"`);
|
|
36
|
-
|
|
125
|
+
return 0;
|
|
37
126
|
}
|
|
38
127
|
case "get": {
|
|
39
|
-
|
|
128
|
+
need(args, 1, "get <key>");
|
|
40
129
|
const value = store.get(args[0]);
|
|
41
130
|
if (value === undefined)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
131
|
+
throw new CliError(`Key "${args[0]}" not found`);
|
|
132
|
+
if (flags.has("copy")) {
|
|
133
|
+
(0, clipboard_1.copyToClipboard)((0, values_1.formatValue)(value));
|
|
134
|
+
console.error(`Copied "${args[0]}" to the clipboard`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.log(flags.has("json") ? JSON.stringify(value) : (0, values_1.formatValue)(value, true));
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
45
140
|
}
|
|
46
141
|
case "update": {
|
|
47
|
-
|
|
142
|
+
need(args, 2, "update <key> <value>");
|
|
48
143
|
const [key, ...rest] = args;
|
|
49
|
-
store.update(key, rest.
|
|
144
|
+
store.update(key, readValue(rest, options.type));
|
|
50
145
|
console.log(`Updated "${key}"`);
|
|
51
|
-
|
|
146
|
+
return 0;
|
|
52
147
|
}
|
|
53
148
|
case "delete":
|
|
54
149
|
case "rm": {
|
|
55
|
-
|
|
150
|
+
need(args, 1, "delete <key>");
|
|
56
151
|
if (!store.delete(args[0]))
|
|
57
|
-
|
|
152
|
+
throw new CliError(`Key "${args[0]}" not found`);
|
|
58
153
|
console.log(`Deleted "${args[0]}"`);
|
|
59
|
-
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
case "rename":
|
|
157
|
+
case "mv": {
|
|
158
|
+
need(args, 2, "rename <old> <new>");
|
|
159
|
+
store.rename(args[0], args[1], { force: flags.has("force") });
|
|
160
|
+
console.log(`Renamed "${args[0]}" to "${args[1]}"`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
case "incr":
|
|
164
|
+
case "decr": {
|
|
165
|
+
need(args, 1, `${command} <key> [amount]`);
|
|
166
|
+
const amount = args[1] === undefined ? 1 : Number(args[1]);
|
|
167
|
+
if (args[1] !== undefined && (args[1].trim() === "" || !Number.isFinite(amount))) {
|
|
168
|
+
throw new CliError(`"${args[1]}" is not a number`);
|
|
169
|
+
}
|
|
170
|
+
// String() so console.log never colors the number (it does when FORCE_COLOR is set).
|
|
171
|
+
console.log(String(store.incr(args[0], command === "incr" ? amount : -amount)));
|
|
172
|
+
return 0;
|
|
60
173
|
}
|
|
61
174
|
case "list":
|
|
62
175
|
case "ls": {
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
176
|
+
let data = store.all();
|
|
177
|
+
if (options.filter !== undefined) {
|
|
178
|
+
const needle = options.filter.toLowerCase();
|
|
179
|
+
data = pick(data, Object.keys(data).filter((k) => k.toLowerCase().includes(needle)));
|
|
180
|
+
}
|
|
181
|
+
printPairs(store, data, flags);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
case "search": {
|
|
185
|
+
need(args, 1, "search <text>");
|
|
186
|
+
printPairs(store, store.search(args.join(" ")), flags);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
case "history": {
|
|
190
|
+
need(args, 1, "history <key>");
|
|
191
|
+
const entry = store.entry(args[0]);
|
|
192
|
+
if (!entry)
|
|
193
|
+
throw new CliError(`Key "${args[0]}" not found`);
|
|
194
|
+
if (flags.has("json")) {
|
|
195
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
console.log(`${formatDate(entry.updated)} ${oneLine(entry.value)} (current)`);
|
|
199
|
+
for (const item of [...entry.history].reverse()) {
|
|
200
|
+
console.log(`${formatDate(item.updated)} ${oneLine(item.value)}`);
|
|
201
|
+
}
|
|
202
|
+
if (entry.history.length === 0)
|
|
203
|
+
console.log("(no previous values)");
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
case "namespaces":
|
|
207
|
+
case "ns": {
|
|
208
|
+
const counts = store.namespaces();
|
|
209
|
+
const names = Object.keys(counts).sort();
|
|
210
|
+
const width = Math.max(...names.map((n) => n.length));
|
|
211
|
+
for (const name of names) {
|
|
212
|
+
const marker = name === store.namespace ? "*" : " ";
|
|
213
|
+
const n = counts[name];
|
|
214
|
+
console.log(`${marker} ${name.padEnd(width)} ${n} ${n === 1 ? "key" : "keys"}`);
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
case "clear": {
|
|
219
|
+
const removed = store.clear();
|
|
220
|
+
console.log(`Removed ${removed} ${removed === 1 ? "pair" : "pairs"} from "${store.namespace}"`);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
case "export": {
|
|
224
|
+
const data = store.export(flags.has("all"));
|
|
225
|
+
const json = JSON.stringify(data, null, 2);
|
|
226
|
+
if (args[0]) {
|
|
227
|
+
fs.writeFileSync(args[0], json + "\n");
|
|
228
|
+
const count = Object.values(data.namespaces).reduce((sum, ns) => sum + Object.keys(ns).length, 0);
|
|
229
|
+
console.log(`Exported ${count} ${count === 1 ? "pair" : "pairs"} to ${args[0]}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.log(json);
|
|
67
233
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
case "import": {
|
|
237
|
+
need(args, 1, "import <file> (use - to read from stdin)");
|
|
238
|
+
const text = (0, store_1.decodeText)(fs.readFileSync(args[0] === "-" ? 0 : args[0]));
|
|
239
|
+
let data;
|
|
240
|
+
try {
|
|
241
|
+
data = JSON.parse(text);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
throw new CliError(`${args[0]} is not valid JSON`);
|
|
72
245
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
246
|
+
const r = store.import(data, { force: flags.has("force") });
|
|
247
|
+
console.log(`Imported: ${r.added} added, ${r.overwritten} overwritten, ${r.skipped} skipped`);
|
|
248
|
+
if (r.skipped > 0)
|
|
249
|
+
console.log("Use --force to overwrite existing keys.");
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
case "env": {
|
|
253
|
+
const shell = (options.shell ?? (0, env_1.defaultShell)());
|
|
254
|
+
if (!env_1.SHELLS.includes(shell))
|
|
255
|
+
throw new CliError(`Unknown shell "${shell}". Use one of: ${env_1.SHELLS.join(", ")}`);
|
|
256
|
+
const { vars, skipped } = (0, env_1.toEnv)(store.all(), flags.has("upper"));
|
|
257
|
+
warnSkipped(skipped);
|
|
258
|
+
const script = (0, env_1.envScript)(vars, shell);
|
|
259
|
+
if (script)
|
|
260
|
+
console.log(script);
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
case "run": {
|
|
264
|
+
need(args, 1, "run <command...>");
|
|
265
|
+
const { vars, skipped } = (0, env_1.toEnv)(store.all(), flags.has("upper"));
|
|
266
|
+
warnSkipped(skipped);
|
|
267
|
+
return (0, env_1.runWithEnv)(args, vars);
|
|
268
|
+
}
|
|
82
269
|
case "path":
|
|
83
270
|
console.log(store.path);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case "help":
|
|
87
|
-
console.log(HELP);
|
|
88
|
-
break;
|
|
271
|
+
console.error(`(${store.kind} store, namespace "${store.namespace}")`);
|
|
272
|
+
return 0;
|
|
89
273
|
default:
|
|
90
|
-
|
|
274
|
+
throw new CliError(`Unknown command "${command}". Run "lkv help".`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function init() {
|
|
278
|
+
const file = path.join(process.cwd(), locate_1.PROJECT_FILE);
|
|
279
|
+
if (fs.existsSync(file))
|
|
280
|
+
throw new CliError(`${locate_1.PROJECT_FILE} already exists here`);
|
|
281
|
+
fs.writeFileSync(file, JSON.stringify((0, format_1.emptyFile)(), null, 2));
|
|
282
|
+
console.log(`Created ${file}`);
|
|
283
|
+
console.log(`lkv commands run in this folder (and below) now use it. Use --global for the global store.`);
|
|
284
|
+
console.log(`Tip: add ${locate_1.PROJECT_FILE} to .gitignore if it will hold private values.`);
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
function readValue(words, type) {
|
|
288
|
+
if (type !== undefined && !(0, values_1.isValueType)(type)) {
|
|
289
|
+
throw new CliError(`Unknown type "${type}". Use one of: ${values_1.VALUE_TYPES.join(", ")}`);
|
|
91
290
|
}
|
|
291
|
+
return (0, values_1.parseValue)(words.join(" "), type);
|
|
292
|
+
}
|
|
293
|
+
function printPairs(store, data, flags) {
|
|
294
|
+
if (flags.has("json")) {
|
|
295
|
+
console.log(JSON.stringify(data, null, 2));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const keys = Object.keys(data);
|
|
299
|
+
if (keys.length === 0) {
|
|
300
|
+
console.log("(empty)");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const width = Math.max(...keys.map((k) => k.length));
|
|
304
|
+
if (!flags.has("long")) {
|
|
305
|
+
for (const key of keys)
|
|
306
|
+
console.log(`${key.padEnd(width)} ${oneLine(data[key])}`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const entries = store.entries();
|
|
310
|
+
const keyW = Math.max(width, 3);
|
|
311
|
+
console.log(`${"KEY".padEnd(keyW)} TYPE UPDATED VALUE`);
|
|
312
|
+
for (const key of keys) {
|
|
313
|
+
const e = entries[key];
|
|
314
|
+
console.log(`${key.padEnd(keyW)} ${e.type.padEnd(7)} ${formatDate(e.updated)} ${oneLine(e.value)}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function pick(data, keys) {
|
|
318
|
+
const out = Object.create(null);
|
|
319
|
+
for (const key of keys)
|
|
320
|
+
out[key] = data[key];
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
function oneLine(value) {
|
|
324
|
+
return (0, values_1.formatValue)(value).replace(/\r?\n/g, "\\n");
|
|
325
|
+
}
|
|
326
|
+
function formatDate(iso) {
|
|
327
|
+
const d = new Date(iso);
|
|
328
|
+
if (isNaN(d.getTime()))
|
|
329
|
+
return iso;
|
|
330
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
331
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
332
|
+
}
|
|
333
|
+
function warnSkipped(keys) {
|
|
334
|
+
if (keys.length > 0) {
|
|
335
|
+
console.error(`Skipped keys that aren't valid variable names: ${keys.join(", ")}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function readVersion() {
|
|
339
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
340
|
+
return pkg.version;
|
|
92
341
|
}
|
|
93
342
|
if (require.main === module) {
|
|
94
343
|
try {
|
|
95
|
-
|
|
344
|
+
process.exitCode = main(process.argv.slice(2));
|
|
96
345
|
}
|
|
97
346
|
catch (err) {
|
|
98
|
-
|
|
347
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
348
|
+
process.exitCode = 1;
|
|
99
349
|
}
|
|
100
350
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.copyToClipboard = copyToClipboard;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
/** Copy text to the system clipboard using the OS's own tool. */
|
|
6
|
+
function copyToClipboard(text) {
|
|
7
|
+
const attempts = [];
|
|
8
|
+
if (process.platform === "win32") {
|
|
9
|
+
// clip.exe detects UTF-16 input (a byte order mark would end up in the clipboard).
|
|
10
|
+
attempts.push(["clip", [], Buffer.from(text, "utf16le")]);
|
|
11
|
+
}
|
|
12
|
+
else if (process.platform === "darwin") {
|
|
13
|
+
attempts.push(["pbcopy", [], Buffer.from(text)]);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
attempts.push(["wl-copy", [], Buffer.from(text)], ["xclip", ["-selection", "clipboard"], Buffer.from(text)], ["xsel", ["--clipboard", "--input"], Buffer.from(text)]);
|
|
17
|
+
}
|
|
18
|
+
for (const [cmd, args, input] of attempts) {
|
|
19
|
+
const result = (0, child_process_1.spawnSync)(cmd, args, { input });
|
|
20
|
+
if (!result.error && result.status === 0)
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
throw new Error("Could not access the clipboard (on Linux, install wl-clipboard, xclip or xsel)");
|
|
24
|
+
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Value } from "./values";
|
|
2
|
+
export type Shell = "sh" | "powershell" | "cmd";
|
|
3
|
+
export declare const SHELLS: readonly Shell[];
|
|
4
|
+
export declare function defaultShell(): Shell;
|
|
5
|
+
/** Turn stored pairs into environment variables. Keys that aren't valid names are reported. */
|
|
6
|
+
export declare function toEnv(data: Record<string, Value>, upper?: boolean): {
|
|
7
|
+
vars: Record<string, string>;
|
|
8
|
+
skipped: string[];
|
|
9
|
+
};
|
|
10
|
+
/** Lines that set the variables when evaluated by the given shell. */
|
|
11
|
+
export declare function envScript(vars: Record<string, string>, shell: Shell): string;
|
|
12
|
+
/** Run a command with extra environment variables. Returns its exit code. */
|
|
13
|
+
export declare function runWithEnv(command: string[], vars: Record<string, string>): number;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHELLS = void 0;
|
|
4
|
+
exports.defaultShell = defaultShell;
|
|
5
|
+
exports.toEnv = toEnv;
|
|
6
|
+
exports.envScript = envScript;
|
|
7
|
+
exports.runWithEnv = runWithEnv;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const values_1 = require("./values");
|
|
10
|
+
exports.SHELLS = ["sh", "powershell", "cmd"];
|
|
11
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
12
|
+
function defaultShell() {
|
|
13
|
+
return process.platform === "win32" ? "powershell" : "sh";
|
|
14
|
+
}
|
|
15
|
+
/** Turn stored pairs into environment variables. Keys that aren't valid names are reported. */
|
|
16
|
+
function toEnv(data, upper = false) {
|
|
17
|
+
const vars = {};
|
|
18
|
+
const skipped = [];
|
|
19
|
+
for (const key of Object.keys(data)) {
|
|
20
|
+
const name = upper ? key.toUpperCase() : key;
|
|
21
|
+
if (ENV_NAME.test(name))
|
|
22
|
+
vars[name] = (0, values_1.formatValue)(data[key]);
|
|
23
|
+
else
|
|
24
|
+
skipped.push(key);
|
|
25
|
+
}
|
|
26
|
+
return { vars, skipped };
|
|
27
|
+
}
|
|
28
|
+
/** Lines that set the variables when evaluated by the given shell. */
|
|
29
|
+
function envScript(vars, shell) {
|
|
30
|
+
return Object.keys(vars)
|
|
31
|
+
.map((name) => {
|
|
32
|
+
const value = vars[name];
|
|
33
|
+
switch (shell) {
|
|
34
|
+
case "sh":
|
|
35
|
+
return `export ${name}='${value.replace(/'/g, `'\\''`)}'`;
|
|
36
|
+
case "powershell":
|
|
37
|
+
return `$env:${name} = '${value.replace(/'/g, "''")}'`;
|
|
38
|
+
case "cmd":
|
|
39
|
+
return `set "${name}=${value}"`;
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.join("\n");
|
|
43
|
+
}
|
|
44
|
+
/** Run a command with extra environment variables. Returns its exit code. */
|
|
45
|
+
function runWithEnv(command, vars) {
|
|
46
|
+
// Go through the shell so things like `npm` (npm.cmd on Windows) resolve.
|
|
47
|
+
const line = command.map(quoteArg).join(" ");
|
|
48
|
+
const result = (0, child_process_1.spawnSync)(line, { stdio: "inherit", shell: true, env: { ...process.env, ...vars } });
|
|
49
|
+
if (result.error)
|
|
50
|
+
throw result.error;
|
|
51
|
+
return result.status ?? 1;
|
|
52
|
+
}
|
|
53
|
+
function quoteArg(arg) {
|
|
54
|
+
if (/^[\w@%+=:,./\\-]+$/.test(arg))
|
|
55
|
+
return arg;
|
|
56
|
+
return process.platform === "win32"
|
|
57
|
+
? `"${arg.replace(/"/g, '\\"')}"`
|
|
58
|
+
: `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
59
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Value, ValueType } from "./values";
|
|
2
|
+
export declare const FORMAT_VERSION = 2;
|
|
3
|
+
export declare const DEFAULT_NAMESPACE = "default";
|
|
4
|
+
/** How many previous values are kept per key. */
|
|
5
|
+
export declare const HISTORY_LIMIT = 20;
|
|
6
|
+
export interface HistoryItem {
|
|
7
|
+
value: Value;
|
|
8
|
+
type: ValueType;
|
|
9
|
+
updated: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Entry {
|
|
12
|
+
value: Value;
|
|
13
|
+
type: ValueType;
|
|
14
|
+
created: string;
|
|
15
|
+
updated: string;
|
|
16
|
+
history: HistoryItem[];
|
|
17
|
+
}
|
|
18
|
+
export type Namespace = Record<string, Entry>;
|
|
19
|
+
export interface StoreFile {
|
|
20
|
+
version: typeof FORMAT_VERSION;
|
|
21
|
+
namespaces: Record<string, Namespace>;
|
|
22
|
+
}
|
|
23
|
+
/** Prototype-less object, so keys like "constructor" or "__proto__" are just normal keys. */
|
|
24
|
+
export declare function dict<T>(source?: Record<string, T>): Record<string, T>;
|
|
25
|
+
export declare function emptyFile(): StoreFile;
|
|
26
|
+
export declare function isV2(data: unknown): data is StoreFile;
|
|
27
|
+
export declare function isPlainObject(x: unknown): x is Record<string, unknown>;
|
|
28
|
+
export declare function newEntry(value: Value, now: string): Entry;
|
|
29
|
+
/** Build a clean Entry from untrusted JSON (a store file or an import). */
|
|
30
|
+
export declare function toEntry(raw: unknown, now: string): Entry;
|
|
31
|
+
/** Parse a v2 file (keeps the null prototypes throughout). */
|
|
32
|
+
export declare function normalizeV2(data: StoreFile, now: string): StoreFile;
|
|
33
|
+
/** v1 files were a flat { key: "string value" } object. Values stay strings. */
|
|
34
|
+
export declare function migrateV1(data: Record<string, unknown>, now: string): StoreFile;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HISTORY_LIMIT = exports.DEFAULT_NAMESPACE = exports.FORMAT_VERSION = void 0;
|
|
4
|
+
exports.dict = dict;
|
|
5
|
+
exports.emptyFile = emptyFile;
|
|
6
|
+
exports.isV2 = isV2;
|
|
7
|
+
exports.isPlainObject = isPlainObject;
|
|
8
|
+
exports.newEntry = newEntry;
|
|
9
|
+
exports.toEntry = toEntry;
|
|
10
|
+
exports.normalizeV2 = normalizeV2;
|
|
11
|
+
exports.migrateV1 = migrateV1;
|
|
12
|
+
const values_1 = require("./values");
|
|
13
|
+
exports.FORMAT_VERSION = 2;
|
|
14
|
+
exports.DEFAULT_NAMESPACE = "default";
|
|
15
|
+
/** How many previous values are kept per key. */
|
|
16
|
+
exports.HISTORY_LIMIT = 20;
|
|
17
|
+
/** Prototype-less object, so keys like "constructor" or "__proto__" are just normal keys. */
|
|
18
|
+
function dict(source) {
|
|
19
|
+
const out = Object.create(null);
|
|
20
|
+
if (source)
|
|
21
|
+
for (const key of Object.keys(source))
|
|
22
|
+
out[key] = source[key];
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function emptyFile() {
|
|
26
|
+
return { version: exports.FORMAT_VERSION, namespaces: dict({ [exports.DEFAULT_NAMESPACE]: dict() }) };
|
|
27
|
+
}
|
|
28
|
+
function isV2(data) {
|
|
29
|
+
return (isPlainObject(data) &&
|
|
30
|
+
data.version === exports.FORMAT_VERSION &&
|
|
31
|
+
isPlainObject(data.namespaces));
|
|
32
|
+
}
|
|
33
|
+
function isPlainObject(x) {
|
|
34
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
35
|
+
}
|
|
36
|
+
function newEntry(value, now) {
|
|
37
|
+
return { value, type: (0, values_1.typeOf)(value), created: now, updated: now, history: [] };
|
|
38
|
+
}
|
|
39
|
+
/** Build a clean Entry from untrusted JSON (a store file or an import). */
|
|
40
|
+
function toEntry(raw, now) {
|
|
41
|
+
if (!isPlainObject(raw) || !("value" in raw))
|
|
42
|
+
throw new Error("Invalid entry in data");
|
|
43
|
+
const value = raw.value;
|
|
44
|
+
const history = Array.isArray(raw.history)
|
|
45
|
+
? raw.history
|
|
46
|
+
.filter((h) => isPlainObject(h) && "value" in h)
|
|
47
|
+
.map((h) => ({
|
|
48
|
+
value: h.value,
|
|
49
|
+
type: (0, values_1.typeOf)(h.value),
|
|
50
|
+
updated: typeof h.updated === "string" ? h.updated : now,
|
|
51
|
+
}))
|
|
52
|
+
: [];
|
|
53
|
+
return {
|
|
54
|
+
value,
|
|
55
|
+
type: (0, values_1.typeOf)(value),
|
|
56
|
+
created: typeof raw.created === "string" ? raw.created : now,
|
|
57
|
+
updated: typeof raw.updated === "string" ? raw.updated : now,
|
|
58
|
+
history: history.slice(-exports.HISTORY_LIMIT),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Parse a v2 file (keeps the null prototypes throughout). */
|
|
62
|
+
function normalizeV2(data, now) {
|
|
63
|
+
const namespaces = dict();
|
|
64
|
+
for (const name of Object.keys(data.namespaces)) {
|
|
65
|
+
const ns = data.namespaces[name];
|
|
66
|
+
if (!isPlainObject(ns))
|
|
67
|
+
continue;
|
|
68
|
+
const entries = dict();
|
|
69
|
+
for (const key of Object.keys(ns))
|
|
70
|
+
entries[key] = toEntry(ns[key], now);
|
|
71
|
+
namespaces[name] = entries;
|
|
72
|
+
}
|
|
73
|
+
if (!(exports.DEFAULT_NAMESPACE in namespaces))
|
|
74
|
+
namespaces[exports.DEFAULT_NAMESPACE] = dict();
|
|
75
|
+
return { version: exports.FORMAT_VERSION, namespaces };
|
|
76
|
+
}
|
|
77
|
+
/** v1 files were a flat { key: "string value" } object. Values stay strings. */
|
|
78
|
+
function migrateV1(data, now) {
|
|
79
|
+
const file = emptyFile();
|
|
80
|
+
const ns = file.namespaces[exports.DEFAULT_NAMESPACE];
|
|
81
|
+
for (const key of Object.keys(data)) {
|
|
82
|
+
const value = data[key];
|
|
83
|
+
ns[key] = newEntry(typeof value === "string" ? value : JSON.stringify(value), now);
|
|
84
|
+
}
|
|
85
|
+
return file;
|
|
86
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export { Store
|
|
2
|
-
export type {
|
|
1
|
+
export { Store } from "./store";
|
|
2
|
+
export type { StoreOptions, ImportResult } from "./store";
|
|
3
|
+
export type { Entry, HistoryItem, StoreFile } from "./format";
|
|
4
|
+
export { locateStore, globalStorePath, PROJECT_FILE } from "./locate";
|
|
5
|
+
export type { StoreLocation } from "./locate";
|
|
6
|
+
export { parseValue, formatValue, typeOf } from "./values";
|
|
7
|
+
export type { Value, ValueType } from "./values";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.typeOf = exports.formatValue = exports.parseValue = exports.PROJECT_FILE = exports.globalStorePath = exports.locateStore = exports.Store = void 0;
|
|
4
4
|
// Public API, so the store can also be used from code: import { Store } from "local-kv-cli"
|
|
5
5
|
var store_1 = require("./store");
|
|
6
6
|
Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_1.Store; } });
|
|
7
|
-
|
|
7
|
+
var locate_1 = require("./locate");
|
|
8
|
+
Object.defineProperty(exports, "locateStore", { enumerable: true, get: function () { return locate_1.locateStore; } });
|
|
9
|
+
Object.defineProperty(exports, "globalStorePath", { enumerable: true, get: function () { return locate_1.globalStorePath; } });
|
|
10
|
+
Object.defineProperty(exports, "PROJECT_FILE", { enumerable: true, get: function () { return locate_1.PROJECT_FILE; } });
|
|
11
|
+
var values_1 = require("./values");
|
|
12
|
+
Object.defineProperty(exports, "parseValue", { enumerable: true, get: function () { return values_1.parseValue; } });
|
|
13
|
+
Object.defineProperty(exports, "formatValue", { enumerable: true, get: function () { return values_1.formatValue; } });
|
|
14
|
+
Object.defineProperty(exports, "typeOf", { enumerable: true, get: function () { return values_1.typeOf; } });
|