@nmakarov/cli-toolkit 0.18.0 → 0.23.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.
Files changed (69) hide show
  1. package/README.md +9 -0
  2. package/dist/args.cjs +1 -4
  3. package/dist/args.cjs.map +1 -1
  4. package/dist/args.js +1 -1
  5. package/dist/args.js.map +1 -1
  6. package/dist/cli-runner.cjs +1493 -516
  7. package/dist/cli-runner.cjs.map +1 -1
  8. package/dist/cli-runner.js +1509 -531
  9. package/dist/cli-runner.js.map +1 -1
  10. package/dist/db.cjs +85 -157
  11. package/dist/db.cjs.map +1 -1
  12. package/dist/db.js +84 -150
  13. package/dist/db.js.map +1 -1
  14. package/dist/errors.cjs +2 -2
  15. package/dist/errors.cjs.map +1 -1
  16. package/dist/errors.js +2 -1
  17. package/dist/errors.js.map +1 -1
  18. package/dist/filedatabase.cjs +19 -19
  19. package/dist/filedatabase.cjs.map +1 -1
  20. package/dist/filedatabase.js +19 -16
  21. package/dist/filedatabase.js.map +1 -1
  22. package/dist/http-client.cjs +9 -11
  23. package/dist/http-client.cjs.map +1 -1
  24. package/dist/http-client.js +10 -9
  25. package/dist/http-client.js.map +1 -1
  26. package/dist/http-client2.cjs +34 -33
  27. package/dist/http-client2.cjs.map +1 -1
  28. package/dist/http-client2.js +34 -30
  29. package/dist/http-client2.js.map +1 -1
  30. package/dist/index.cjs +2063 -658
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.js +2063 -663
  33. package/dist/index.js.map +1 -1
  34. package/dist/init.cjs +97 -69
  35. package/dist/init.cjs.map +1 -1
  36. package/dist/init.js +112 -83
  37. package/dist/init.js.map +1 -1
  38. package/dist/logger.cjs +5 -5
  39. package/dist/logger.cjs.map +1 -1
  40. package/dist/logger.js +5 -4
  41. package/dist/logger.js.map +1 -1
  42. package/dist/mock-server.cjs +21 -33
  43. package/dist/mock-server.cjs.map +1 -1
  44. package/dist/mock-server.js +21 -28
  45. package/dist/mock-server.js.map +1 -1
  46. package/dist/params.cjs +22 -10
  47. package/dist/params.cjs.map +1 -1
  48. package/dist/params.js +22 -7
  49. package/dist/params.js.map +1 -1
  50. package/dist/s3.cjs +286 -0
  51. package/dist/s3.cjs.map +1 -0
  52. package/dist/s3.js +273 -0
  53. package/dist/s3.js.map +1 -0
  54. package/dist/screen.cjs +34 -39
  55. package/dist/screen.cjs.map +1 -1
  56. package/dist/screen.js +48 -46
  57. package/dist/screen.js.map +1 -1
  58. package/dist/tasks.cjs +1640 -416
  59. package/dist/tasks.cjs.map +1 -1
  60. package/dist/tasks.js +1614 -412
  61. package/dist/tasks.js.map +1 -1
  62. package/dist/utils.cjs +7 -8
  63. package/dist/utils.cjs.map +1 -1
  64. package/dist/utils.js +6 -6
  65. package/dist/utils.js.map +1 -1
  66. package/package.json +36 -44
  67. package/scripts/ssm/parse-cli.js +35 -0
  68. package/scripts/ssm/ssm-admin.js +151 -0
  69. package/scripts/ssm/ssm-pull.js +147 -0
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Local admin CLI for SSM Parameter Store (uses your workstation AWS credentials / profile).
4
+ *
5
+ * Prerequisites: IAM permission for ssm:Get*, ssm:PutParameter, ssm:DeleteParameter on your parameters
6
+ * (and kms:Decrypt/Encrypt if you use SecureString).
7
+ *
8
+ * Run from this package root (`subprojects/cli-toolkit`):
9
+ *
10
+ * npx tsx scripts/ssm/ssm-admin.js list --prefix /mlsfarm/tf3/
11
+ * npm run ssm:list -- --prefix /mlsfarm/tf3/
12
+ *
13
+ * Or from a project that depends on `@nmakarov/cli-toolkit` (after a release that ships `scripts/ssm/`):
14
+ *
15
+ * npx tsx node_modules/@nmakarov/cli-toolkit/scripts/ssm/ssm-admin.js list --prefix /mlsfarm/tf3/
16
+ *
17
+ * Optional: AWS_REGION, AWS_PROFILE (standard SDK env).
18
+ */
19
+
20
+ import {
21
+ SSMClient,
22
+ GetParametersByPathCommand,
23
+ GetParameterCommand,
24
+ PutParameterCommand,
25
+ DeleteParameterCommand,
26
+ } from "@aws-sdk/client-ssm";
27
+ import { parseCli, getRegion } from "./parse-cli.js";
28
+
29
+ function usage() {
30
+ console.error(`Usage:
31
+ npx tsx scripts/ssm/ssm-admin.js list --prefix /path/ [--recursive false]
32
+ npx tsx scripts/ssm/ssm-admin.js get --name /path/KEY
33
+ npx tsx scripts/ssm/ssm-admin.js put --name /path/KEY --value "..." [--type String|SecureString] [--overwrite]
34
+ npx tsx scripts/ssm/ssm-admin.js delete --name /path/KEY`);
35
+ }
36
+
37
+ function ensureLeadingSlash(path) {
38
+ const p = path.trim();
39
+ if (!p.startsWith("/")) return `/${p}`;
40
+ return p;
41
+ }
42
+
43
+ async function main() {
44
+ const { _, flags } = parseCli(process.argv.slice(2));
45
+ const cmd = (_[0] || "").toLowerCase();
46
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
47
+ usage();
48
+ process.exit(cmd ? 0 : 1);
49
+ }
50
+
51
+ const region = typeof flags.region === "string" ? flags.region : getRegion();
52
+ const client = new SSMClient({ region });
53
+
54
+ if (cmd === "list") {
55
+ const prefix = ensureLeadingSlash(String(flags.prefix || "/mlsfarm/tf3/"));
56
+ const recursive = flags.recursive === false || flags.recursive === "false" ? false : true;
57
+ let next;
58
+ const rows = [];
59
+ do {
60
+ const out = await client.send(
61
+ new GetParametersByPathCommand({
62
+ Path: prefix,
63
+ Recursive: recursive,
64
+ WithDecryption: true,
65
+ NextToken: next,
66
+ })
67
+ );
68
+ for (const p of out.Parameters || []) {
69
+ rows.push({ name: p.Name, type: p.Type, version: p.Version });
70
+ }
71
+ next = out.NextToken;
72
+ } while (next);
73
+
74
+ if (rows.length === 0) {
75
+ console.log(`(no parameters under ${prefix})`);
76
+ return;
77
+ }
78
+ const w = Math.max(...rows.map((r) => (r.name || "").length), 4);
79
+ console.log(`${"NAME".padEnd(w)} TYPE VER`);
80
+ for (const r of rows) {
81
+ console.log(`${(r.name || "").padEnd(w)} ${(r.type || "").padEnd(14)} ${r.version ?? ""}`);
82
+ }
83
+ return;
84
+ }
85
+
86
+ if (cmd === "get") {
87
+ const name = flags.name;
88
+ if (typeof name !== "string" || !name.trim()) {
89
+ console.error("get: --name is required");
90
+ usage();
91
+ process.exit(1);
92
+ }
93
+ const out = await client.send(
94
+ new GetParameterCommand({ Name: ensureLeadingSlash(name), WithDecryption: true })
95
+ );
96
+ const p = out.Parameter;
97
+ if (!p?.Name) {
98
+ console.error("Parameter not found");
99
+ process.exit(1);
100
+ }
101
+ console.log(`Name: ${p.Name}`);
102
+ console.log(`Type: ${p.Type}`);
103
+ console.log(`Value: ${p.Value ?? ""}`);
104
+ return;
105
+ }
106
+
107
+ if (cmd === "put") {
108
+ const name = flags.name;
109
+ const value = flags.value;
110
+ if (typeof name !== "string" || !name.trim()) {
111
+ console.error("put: --name is required");
112
+ process.exit(1);
113
+ }
114
+ if (typeof value !== "string") {
115
+ console.error("put: --value is required (string)");
116
+ process.exit(1);
117
+ }
118
+ const type = flags.type === "SecureString" ? "SecureString" : "String";
119
+ const overwrite = flags.overwrite === true || flags.overwrite === "true";
120
+ const ver = await client.send(
121
+ new PutParameterCommand({
122
+ Name: ensureLeadingSlash(name),
123
+ Value: value,
124
+ Type: type,
125
+ Overwrite: overwrite,
126
+ })
127
+ );
128
+ console.log(`OK Version ${ver.Version}`);
129
+ return;
130
+ }
131
+
132
+ if (cmd === "delete") {
133
+ const name = flags.name;
134
+ if (typeof name !== "string" || !name.trim()) {
135
+ console.error("delete: --name is required");
136
+ process.exit(1);
137
+ }
138
+ await client.send(new DeleteParameterCommand({ Name: ensureLeadingSlash(name) }));
139
+ console.log("OK deleted");
140
+ return;
141
+ }
142
+
143
+ console.error(`Unknown command: ${cmd}`);
144
+ usage();
145
+ process.exit(1);
146
+ }
147
+
148
+ main().catch((e) => {
149
+ console.error(e instanceof Error ? e.message : e);
150
+ process.exit(1);
151
+ });
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pull all SSM parameters under a path (recursive) for use on servers or locally.
4
+ * Uses default credential chain (on EC2: instance profile; e.g. mlsfarm tf3 `04.4.ssm_iam.tf`).
5
+ *
6
+ * Output modes:
7
+ * print — table to stdout (default)
8
+ * dotenv — KEY=value lines (dotenv-friendly; optional --file)
9
+ * shell — export KEY='...' lines for: eval "$(npx tsx ... --output shell)"
10
+ *
11
+ * Env key = last path segment of the parameter name (e.g. /mlsfarm/tf3/DATABASE_URL → DATABASE_URL).
12
+ *
13
+ * From package root:
14
+ *
15
+ * npx tsx scripts/ssm/ssm-pull.js --path /mlsfarm/tf3/
16
+ * npm run ssm:pull -- --path /mlsfarm/tf3/ --output dotenv --file .env.from-ssm
17
+ * eval "$(npx tsx scripts/ssm/ssm-pull.js --path /mlsfarm/tf3/ --output shell)"
18
+ */
19
+
20
+ import { SSMClient, GetParametersByPathCommand } from "@aws-sdk/client-ssm";
21
+ import { writeFileSync } from "fs";
22
+ import { parseCli, getRegion } from "./parse-cli.js";
23
+
24
+ function ensureLeadingSlash(path) {
25
+ const p = path.trim();
26
+ if (!p.startsWith("/")) return `/${p}`;
27
+ return p.endsWith("/") ? p : `${p}/`;
28
+ }
29
+
30
+ function leafKey(paramName) {
31
+ const trimmed = paramName.replace(/^\/+|\/+$/g, "");
32
+ const parts = trimmed.split("/");
33
+ const leaf = parts[parts.length - 1] || trimmed;
34
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(leaf)) {
35
+ return leaf.replace(/[^A-Za-z0-9_]+/g, "_").replace(/^(\d)/, "_$1");
36
+ }
37
+ return leaf;
38
+ }
39
+
40
+ function escapeDotenvValue(value) {
41
+ if (/[\s#'"]/u.test(value) || value.includes("\n")) {
42
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
43
+ return `"${escaped}"`;
44
+ }
45
+ return value;
46
+ }
47
+
48
+ function escapeShellSingleQuoted(value) {
49
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
50
+ }
51
+
52
+ async function fetchAll(client, path) {
53
+ const outList = [];
54
+ let next;
55
+ do {
56
+ const out = await client.send(
57
+ new GetParametersByPathCommand({
58
+ Path: path,
59
+ Recursive: true,
60
+ WithDecryption: true,
61
+ NextToken: next,
62
+ })
63
+ );
64
+ for (const p of out.Parameters || []) {
65
+ if (p.Name && p.Value !== undefined) {
66
+ outList.push({ name: p.Name, value: p.Value, type: p.Type });
67
+ }
68
+ }
69
+ next = out.NextToken;
70
+ } while (next);
71
+ outList.sort((a, b) => a.name.localeCompare(b.name));
72
+ return outList;
73
+ }
74
+
75
+ async function main() {
76
+ const { flags } = parseCli(process.argv.slice(2));
77
+ const pathRaw = flags.path;
78
+ if (typeof pathRaw !== "string" || !pathRaw.trim()) {
79
+ console.error("Required: --path /your/prefix/");
80
+ process.exit(1);
81
+ }
82
+ const path = ensureLeadingSlash(pathRaw);
83
+ const output = String(flags.output || "print").toLowerCase();
84
+ const file = typeof flags.file === "string" ? flags.file : undefined;
85
+
86
+ const region = typeof flags.region === "string" ? flags.region : getRegion();
87
+ const client = new SSMClient({ region });
88
+
89
+ const params = await fetchAll(client, path);
90
+ if (params.length === 0) {
91
+ const msg = `(no parameters under ${path})`;
92
+ if (file) writeFileSync(file, "", "utf8");
93
+ else console.log(msg);
94
+ return;
95
+ }
96
+
97
+ if (output === "print") {
98
+ const lines = [
99
+ `region=${region} path=${path} count=${params.length}`,
100
+ "",
101
+ ...params.map((p) => {
102
+ const k = leafKey(p.name);
103
+ const preview =
104
+ p.value.length > 120 ? `${p.value.slice(0, 117)}...` : p.value;
105
+ return `${p.name} [${k}] (${p.type}) ${preview}`;
106
+ }),
107
+ ];
108
+ const text = lines.join("\n");
109
+ if (file) writeFileSync(file, text + "\n", "utf8");
110
+ else console.log(text);
111
+ return;
112
+ }
113
+
114
+ if (output === "dotenv") {
115
+ const body = params
116
+ .map((p) => {
117
+ const k = leafKey(p.name);
118
+ return `${k}=${escapeDotenvValue(p.value)}`;
119
+ })
120
+ .join("\n");
121
+ const text = `${body}\n`;
122
+ if (file) writeFileSync(file, text, "utf8");
123
+ else process.stdout.write(text);
124
+ return;
125
+ }
126
+
127
+ if (output === "shell") {
128
+ const body = params
129
+ .map((p) => {
130
+ const k = leafKey(p.name);
131
+ return `export ${k}=${escapeShellSingleQuoted(p.value)}`;
132
+ })
133
+ .join("\n");
134
+ const text = `${body}\n`;
135
+ if (file) writeFileSync(file, text, "utf8");
136
+ else process.stdout.write(text);
137
+ return;
138
+ }
139
+
140
+ console.error(`Unknown --output ${output}. Use: print | dotenv | shell`);
141
+ process.exit(1);
142
+ }
143
+
144
+ main().catch((e) => {
145
+ console.error(e instanceof Error ? e.message : e);
146
+ process.exit(1);
147
+ });