@platforma-sdk/pl-cli 0.7.83 → 0.8.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/dist/cli.js +143 -125
- package/dist/cli.js.map +1 -1
- package/dist/cmd/admin/user-list.d.ts +2 -0
- package/dist/cmd-opts.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/{output-Npj-8KEL.js → output-KEq0q5fS.js} +7 -3
- package/dist/{output-Npj-8KEL.js.map → output-KEq0q5fS.js.map} +1 -1
- package/dist/output.d.ts +3 -1
- package/package.json +4 -4
- package/src/cli.ts +2 -0
- package/src/cmd/admin/user-list.ts +30 -0
- package/src/cmd-opts.ts +8 -2
- package/src/output.ts +8 -1
package/dist/cli.js
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Command as
|
|
3
|
-
import { field as
|
|
4
|
-
import { ProjectMetaKey as
|
|
5
|
-
import { randomUUID as
|
|
6
|
-
import { createInterface as
|
|
1
|
+
import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g } from "./output-KEq0q5fS.js";
|
|
2
|
+
import { Command as _, Option as v } from "commander";
|
|
3
|
+
import { field as y, resourceIdToString as b } from "@milaboratories/pl-client";
|
|
4
|
+
import { ProjectMetaKey as x } from "@milaboratories/pl-middle-layer";
|
|
5
|
+
import { randomUUID as S } from "node:crypto";
|
|
6
|
+
import { createInterface as C } from "node:readline";
|
|
7
7
|
//#region src/base_command.ts
|
|
8
|
-
async function
|
|
9
|
-
return
|
|
10
|
-
address:
|
|
11
|
-
adminUser:
|
|
12
|
-
adminPassword:
|
|
13
|
-
}) :
|
|
14
|
-
address:
|
|
15
|
-
user:
|
|
16
|
-
password:
|
|
8
|
+
async function w(t) {
|
|
9
|
+
return t.adminUser && t.adminPassword ? a({
|
|
10
|
+
address: t.address,
|
|
11
|
+
adminUser: t.adminUser,
|
|
12
|
+
adminPassword: t.adminPassword
|
|
13
|
+
}) : e({
|
|
14
|
+
address: t.address,
|
|
15
|
+
user: t.user,
|
|
16
|
+
password: t.password
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
async function
|
|
19
|
+
async function T(e) {
|
|
20
20
|
let t = !!e.adminUser, n = !!e.adminPassword, i = !!e.targetUser;
|
|
21
21
|
if (i && !(t && n)) throw Error("--target-user requires --admin-user and --admin-password");
|
|
22
22
|
if ((t || n) && !i) throw Error("--admin-user/--admin-password require --target-user for project commands");
|
|
23
|
-
let a = await
|
|
24
|
-
return o = i ? (await
|
|
23
|
+
let a = await w(e), o;
|
|
24
|
+
return o = i ? (await f(a, e.targetUser)).projectListRid : await r(a), {
|
|
25
25
|
pl: a,
|
|
26
26
|
projectListRid: o
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region src/cmd-opts.ts
|
|
31
|
-
function
|
|
31
|
+
function E(e, ...t) {
|
|
32
32
|
for (let n of t) for (let t of n) e.addOption(t);
|
|
33
33
|
return e;
|
|
34
34
|
}
|
|
35
|
-
var
|
|
36
|
-
new
|
|
37
|
-
new
|
|
38
|
-
new
|
|
35
|
+
var D = (e = "text", t = ["text", "json"]) => [new v("-a, --address <url>", "Platforma server address").env("PL_ADDRESS").makeOptionMandatory(), new v("-f, --format <format>", "Output format").choices(t).default(e)], O = () => [new v("-u, --user <user>", "Username for authentication").env("PL_USER"), new v("-p, --password <password>", "Password for authentication").env("PL_PASSWORD")], k = () => [new v("--admin-user <user>", "Admin/controller username").env("PL_ADMIN_USER").makeOptionMandatory(), new v("--admin-password <password>", "Admin/controller password").env("PL_ADMIN_PASSWORD").makeOptionMandatory()], A = () => [
|
|
36
|
+
new v("--admin-user <user>", "Admin/controller username (enables admin mode)").env("PL_ADMIN_USER"),
|
|
37
|
+
new v("--admin-password <password>", "Admin/controller password").env("PL_ADMIN_PASSWORD"),
|
|
38
|
+
new v("--target-user <user>", "Operate on this user's data (requires admin credentials)").env("PL_TARGET_USER")
|
|
39
39
|
];
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/cmd/project/list.ts
|
|
42
|
-
function
|
|
43
|
-
let e = new
|
|
44
|
-
return
|
|
45
|
-
let { pl:
|
|
42
|
+
function j() {
|
|
43
|
+
let e = new _("list").description("List all projects for the authenticated user.");
|
|
44
|
+
return E(e, D(), O(), A()), e.action(async (e) => {
|
|
45
|
+
let { pl: n, projectListRid: r } = await T(e);
|
|
46
46
|
try {
|
|
47
|
-
let
|
|
48
|
-
if (e.format === "json")
|
|
47
|
+
let a = await i(n, r);
|
|
48
|
+
if (e.format === "json") s(a.map((e) => ({
|
|
49
49
|
id: e.id,
|
|
50
|
-
rid:
|
|
50
|
+
rid: b(e.rid),
|
|
51
51
|
label: e.label,
|
|
52
52
|
created: e.created.toISOString(),
|
|
53
53
|
lastModified: e.lastModified.toISOString()
|
|
54
54
|
})));
|
|
55
55
|
else {
|
|
56
|
-
if (
|
|
57
|
-
|
|
56
|
+
if (a.length === 0) {
|
|
57
|
+
t("No projects found.");
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
t(u([
|
|
61
61
|
"ID",
|
|
62
62
|
"RID",
|
|
63
63
|
"LABEL",
|
|
64
64
|
"CREATED",
|
|
65
65
|
"LAST MODIFIED"
|
|
66
|
-
],
|
|
66
|
+
], a.map((e) => [
|
|
67
67
|
e.id,
|
|
68
|
-
|
|
68
|
+
b(e.rid),
|
|
69
69
|
e.label,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]))),
|
|
70
|
+
h(e.created),
|
|
71
|
+
h(e.lastModified)
|
|
72
|
+
]))), t(`\n${a.length} project(s)`);
|
|
73
73
|
}
|
|
74
74
|
} finally {
|
|
75
|
-
await
|
|
75
|
+
await n.close();
|
|
76
76
|
}
|
|
77
77
|
}), e;
|
|
78
78
|
}
|
|
79
79
|
//#endregion
|
|
80
80
|
//#region src/cmd/project/info.ts
|
|
81
|
-
function
|
|
82
|
-
let e = new
|
|
83
|
-
return e.argument("<project>", "Project ID or label"),
|
|
84
|
-
let { pl:
|
|
81
|
+
function M() {
|
|
82
|
+
let e = new _("info").description("Show detailed information about a project.");
|
|
83
|
+
return e.argument("<project>", "Project ID or label"), E(e, D(), O(), A()), e.action(async (e, n) => {
|
|
84
|
+
let { pl: r, projectListRid: i } = await T(n);
|
|
85
85
|
try {
|
|
86
|
-
let { id:
|
|
87
|
-
if (
|
|
86
|
+
let { id: a, rid: c } = await o(r, i, e), l = await g(r, a, c);
|
|
87
|
+
if (n.format === "json") s({
|
|
88
88
|
id: l.id,
|
|
89
|
-
rid:
|
|
89
|
+
rid: b(l.rid),
|
|
90
90
|
label: l.label,
|
|
91
91
|
schemaVersion: l.schemaVersion,
|
|
92
92
|
blockCount: l.blockCount,
|
|
@@ -95,146 +95,164 @@ function j() {
|
|
|
95
95
|
lastModified: l.lastModified.toISOString()
|
|
96
96
|
});
|
|
97
97
|
else {
|
|
98
|
-
if (
|
|
99
|
-
|
|
98
|
+
if (t(`Project: ${l.label}`), t(`ID: ${l.id}`), t(`RID: ${b(l.rid)}`), t(`Schema: ${l.schemaVersion ?? "(unknown)"}`), t(`Blocks: ${l.blockCount}`), l.blockIds.length > 0) for (let e of l.blockIds) t(` - ${e}`);
|
|
99
|
+
t(`Created: ${h(l.created)}`), t(`Last Modified: ${h(l.lastModified)}`);
|
|
100
100
|
}
|
|
101
101
|
} finally {
|
|
102
|
-
await
|
|
102
|
+
await r.close();
|
|
103
103
|
}
|
|
104
104
|
}), e;
|
|
105
105
|
}
|
|
106
106
|
//#endregion
|
|
107
107
|
//#region src/cmd/project/duplicate.ts
|
|
108
|
-
function
|
|
109
|
-
let
|
|
110
|
-
return
|
|
111
|
-
let { pl: i, projectListRid: a } = await
|
|
108
|
+
function N() {
|
|
109
|
+
let e = new _("duplicate").description("Duplicate a project within the same user. Auto-renames on collision by default.");
|
|
110
|
+
return e.argument("<project>", "Project ID or label"), E(e, D(), O(), A()), e.option("-n, --name <name>", "Name for the duplicate"), e.addOption(new v("--auto-rename", "Auto-rename on collision (default: true)").default(!0)), e.option("--no-auto-rename", "Fail on name collision instead of auto-renaming"), e.action(async (e, r) => {
|
|
111
|
+
let { pl: i, projectListRid: a } = await T(r);
|
|
112
112
|
try {
|
|
113
|
-
let { rid:
|
|
114
|
-
let
|
|
113
|
+
let { rid: l } = await o(i, a, e), u = S(), f = await i.withWriteTx("duplicateProject", async (e) => {
|
|
114
|
+
let t = await e.getKValueString(l, x), i = JSON.parse(t).label, o = await c(e, a), s;
|
|
115
115
|
if (r.name) {
|
|
116
|
-
if (!r.autoRename &&
|
|
117
|
-
|
|
118
|
-
} else
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
let
|
|
116
|
+
if (!r.autoRename && o.includes(r.name)) throw Error(`Project name "${r.name}" already exists.`);
|
|
117
|
+
s = o.includes(r.name) ? d(r.name, o) : r.name;
|
|
118
|
+
} else s = d(i, o);
|
|
119
|
+
let f = await n(e, l, { label: s });
|
|
120
|
+
e.createField(y(a, u), "Dynamic", f), await e.commit();
|
|
121
|
+
let p = await f.globalId;
|
|
122
122
|
return {
|
|
123
|
-
id:
|
|
124
|
-
rid:
|
|
125
|
-
label:
|
|
123
|
+
id: b(p),
|
|
124
|
+
rid: p,
|
|
125
|
+
label: s
|
|
126
126
|
};
|
|
127
127
|
});
|
|
128
|
-
r.format === "json" ?
|
|
129
|
-
id:
|
|
130
|
-
rid:
|
|
131
|
-
label:
|
|
132
|
-
}) :
|
|
128
|
+
r.format === "json" ? s({
|
|
129
|
+
id: f.id,
|
|
130
|
+
rid: b(f.rid),
|
|
131
|
+
label: f.label
|
|
132
|
+
}) : t(`Duplicated project as "${f.label}" (id: ${f.id})`);
|
|
133
133
|
} finally {
|
|
134
134
|
await i.close();
|
|
135
135
|
}
|
|
136
|
-
}),
|
|
136
|
+
}), e;
|
|
137
137
|
}
|
|
138
138
|
//#endregion
|
|
139
139
|
//#region src/cmd/project/rename.ts
|
|
140
|
-
function
|
|
141
|
-
let e = new
|
|
142
|
-
return e.argument("<project>", "Project ID or label"),
|
|
143
|
-
let { pl:
|
|
140
|
+
function P() {
|
|
141
|
+
let e = new _("rename").description("Rename a project.");
|
|
142
|
+
return e.argument("<project>", "Project ID or label"), E(e, D(), O(), A()), e.addOption(new v("-n, --name <name>", "New name for the project").makeOptionMandatory()), e.action(async (e, n) => {
|
|
143
|
+
let { pl: r, projectListRid: i } = await T(n);
|
|
144
144
|
try {
|
|
145
|
-
let { id:
|
|
146
|
-
await
|
|
147
|
-
id:
|
|
148
|
-
rid:
|
|
149
|
-
label:
|
|
150
|
-
}) :
|
|
145
|
+
let { id: a, rid: c } = await o(r, i, e);
|
|
146
|
+
await l(r, c, n.name), n.format === "json" ? s({
|
|
147
|
+
id: a,
|
|
148
|
+
rid: b(c),
|
|
149
|
+
label: n.name
|
|
150
|
+
}) : t(`Renamed project to "${n.name}"`);
|
|
151
151
|
} finally {
|
|
152
|
-
await
|
|
152
|
+
await r.close();
|
|
153
153
|
}
|
|
154
154
|
}), e;
|
|
155
155
|
}
|
|
156
156
|
//#endregion
|
|
157
157
|
//#region src/cmd/project/delete.ts
|
|
158
|
-
function
|
|
159
|
-
let e = new
|
|
160
|
-
return e.argument("<project>", "Project ID or label"),
|
|
161
|
-
let { pl:
|
|
158
|
+
function F() {
|
|
159
|
+
let e = new _("delete").description("Delete a project. This permanently destroys all project data.");
|
|
160
|
+
return e.argument("<project>", "Project ID or label"), E(e, D(), O(), A()), e.option("--force", "Skip confirmation", !1), e.action(async (e, n) => {
|
|
161
|
+
let { pl: r, projectListRid: i } = await T(n);
|
|
162
162
|
try {
|
|
163
|
-
let { id:
|
|
164
|
-
if (!
|
|
165
|
-
let e =
|
|
163
|
+
let { id: a, rid: c, fieldName: l } = await o(r, i, e), u = await g(r, a, c);
|
|
164
|
+
if (!n.force) {
|
|
165
|
+
let e = C({
|
|
166
166
|
input: process.stdin,
|
|
167
167
|
output: process.stderr
|
|
168
168
|
});
|
|
169
169
|
try {
|
|
170
170
|
if ((await new Promise((t) => {
|
|
171
|
-
e.question(`Delete project "${
|
|
171
|
+
e.question(`Delete project "${u.label}" (${u.blockCount} blocks)? [y/N] `, t);
|
|
172
172
|
})).toLowerCase() !== "y") {
|
|
173
|
-
|
|
173
|
+
t("Aborted.");
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
} finally {
|
|
177
177
|
e.close();
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
await
|
|
180
|
+
await m(r, i, l), n.format === "json" ? s({
|
|
181
181
|
deleted: !0,
|
|
182
|
-
id:
|
|
183
|
-
label:
|
|
184
|
-
}) :
|
|
182
|
+
id: a,
|
|
183
|
+
label: u.label
|
|
184
|
+
}) : t(`Deleted project "${u.label}"`);
|
|
185
185
|
} finally {
|
|
186
|
-
await
|
|
186
|
+
await r.close();
|
|
187
187
|
}
|
|
188
188
|
}), e;
|
|
189
189
|
}
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/cmd/admin/copy-project.ts
|
|
192
|
-
function
|
|
193
|
-
let
|
|
194
|
-
return
|
|
195
|
-
let
|
|
192
|
+
function I() {
|
|
193
|
+
let e = new _("copy-project").description("Copy a project from one user to another. Requires admin/controller credentials.");
|
|
194
|
+
return E(e, D(), k()), e.addOption(new v("--source-user <user>", "Username of the source project owner").makeOptionMandatory()), e.addOption(new v("--source-project <project>", "Source project ID or label").makeOptionMandatory()), e.option("--target-user <user>", "Username of the target user (defaults to source-user for same-user copy)"), e.option("-n, --name <name>", "Name for the copied project"), e.addOption(new v("--auto-rename", "Auto-rename on collision (default: true)").default(!0)), e.option("--no-auto-rename", "Fail on name collision instead of auto-renaming"), e.action(async (e) => {
|
|
195
|
+
let r = await w(e);
|
|
196
196
|
try {
|
|
197
|
-
let
|
|
198
|
-
let
|
|
199
|
-
if (
|
|
200
|
-
if (!
|
|
201
|
-
|
|
197
|
+
let i = e.targetUser ?? e.sourceUser, a = await f(r, e.sourceUser), { rid: l } = await o(r, a.projectListRid, e.sourceProject), u = i === e.sourceUser ? a : await f(r, i), p = S(), m = await r.withWriteTx("adminCopyProject", async (t) => {
|
|
198
|
+
let r = await t.getKValueString(l, x), i = JSON.parse(r).label, a = await c(t, u.projectListRid), o = e.name ?? i;
|
|
199
|
+
if (a.includes(o)) {
|
|
200
|
+
if (!e.autoRename) throw Error(`Project name "${o}" already exists for target user.`);
|
|
201
|
+
o = d(e.name ?? i, a);
|
|
202
202
|
}
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
let f = await
|
|
203
|
+
let s = await n(t, l, { label: o });
|
|
204
|
+
t.createField(y(u.projectListRid, p), "Dynamic", s), await t.commit();
|
|
205
|
+
let f = await s.globalId;
|
|
206
206
|
return {
|
|
207
|
-
id:
|
|
207
|
+
id: b(f),
|
|
208
208
|
rid: f,
|
|
209
|
-
label:
|
|
209
|
+
label: o
|
|
210
210
|
};
|
|
211
211
|
});
|
|
212
|
-
|
|
212
|
+
e.format === "json" ? s({
|
|
213
213
|
id: m.id,
|
|
214
|
-
rid:
|
|
214
|
+
rid: b(m.rid),
|
|
215
215
|
label: m.label,
|
|
216
|
-
sourceUser:
|
|
217
|
-
targetUser:
|
|
218
|
-
}) :
|
|
216
|
+
sourceUser: e.sourceUser,
|
|
217
|
+
targetUser: i
|
|
218
|
+
}) : t(`Copied project from ${e.sourceUser} to ${i} as "${m.label}" (id: ${m.id}, rid: ${b(m.rid)})`);
|
|
219
219
|
} finally {
|
|
220
|
-
await
|
|
220
|
+
await r.close();
|
|
221
221
|
}
|
|
222
|
-
}),
|
|
222
|
+
}), e;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/cmd/admin/user-list.ts
|
|
226
|
+
function L() {
|
|
227
|
+
let e = new _("user-list").description("List all known users' logins from a live server. Requires admin/controller credentials.");
|
|
228
|
+
return E(e, D("csv", [
|
|
229
|
+
"text",
|
|
230
|
+
"json",
|
|
231
|
+
"csv"
|
|
232
|
+
]), k()), e.action(async (e) => {
|
|
233
|
+
let n = await w(e);
|
|
234
|
+
try {
|
|
235
|
+
let r = (await n.listUsers()).map((e) => e.login).sort();
|
|
236
|
+
e.format === "json" ? s(r) : e.format === "text" ? t(r.join("\n")) : p(["login"], r.map((e) => [e]));
|
|
237
|
+
} finally {
|
|
238
|
+
await n.close();
|
|
239
|
+
}
|
|
240
|
+
}), e;
|
|
223
241
|
}
|
|
224
242
|
//#endregion
|
|
225
243
|
//#region src/cli.ts
|
|
226
|
-
function
|
|
227
|
-
let e = new
|
|
244
|
+
function R() {
|
|
245
|
+
let e = new _();
|
|
228
246
|
e.name("pl-cli").description("Platforma command-line client");
|
|
229
|
-
let t = new
|
|
230
|
-
t.addCommand(
|
|
231
|
-
let n = new
|
|
232
|
-
return n.addCommand(
|
|
247
|
+
let t = new _("project").description("Manage projects");
|
|
248
|
+
t.addCommand(j()), t.addCommand(M()), t.addCommand(N()), t.addCommand(P()), t.addCommand(F()), e.addCommand(t);
|
|
249
|
+
let n = new _("admin").description("Admin operations (requires controller credentials)");
|
|
250
|
+
return n.addCommand(I()), n.addCommand(L()), e.addCommand(n), e;
|
|
233
251
|
}
|
|
234
|
-
async function
|
|
235
|
-
await
|
|
252
|
+
async function z(e = process.argv) {
|
|
253
|
+
await R().parseAsync(e);
|
|
236
254
|
}
|
|
237
255
|
//#endregion
|
|
238
|
-
export {
|
|
256
|
+
export { R as buildProgram, z as run };
|
|
239
257
|
|
|
240
258
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/base_command.ts","../src/cmd-opts.ts","../src/cmd/project/list.ts","../src/cmd/project/info.ts","../src/cmd/project/duplicate.ts","../src/cmd/project/rename.ts","../src/cmd/project/delete.ts","../src/cmd/admin/copy-project.ts","../src/cli.ts"],"sourcesContent":["import type { PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { createPlConnection, createAdminPlConnection } from \"./connection\";\nimport { getProjectListRid, navigateToUserRoot } from \"./project_ops\";\n\n/**\n * Low-level: get an authenticated PlClient without resolving a project list.\n * Use this for commands that navigate to multiple users (e.g. admin copy-project).\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connectClient(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n}): Promise<PlClient> {\n if (flags.adminUser && flags.adminPassword) {\n return createAdminPlConnection({\n address: flags.address,\n adminUser: flags.adminUser,\n adminPassword: flags.adminPassword,\n });\n }\n return createPlConnection({\n address: flags.address,\n user: flags.user,\n password: flags.password,\n });\n}\n\n/**\n * Connect and resolve the project list for a single user.\n * In admin mode (--admin-user + --admin-password + --target-user), operates on the target user's data.\n * In user mode, operates on the authenticated user's own data.\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connect(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n targetUser?: string;\n}): Promise<{ pl: PlClient; projectListRid: SignedResourceId }> {\n const hasAdminUser = !!flags.adminUser;\n const hasAdminPassword = !!flags.adminPassword;\n const hasTarget = !!flags.targetUser;\n\n // Validate flag combinations\n if (hasTarget && !(hasAdminUser && hasAdminPassword)) {\n throw new Error(\"--target-user requires --admin-user and --admin-password\");\n }\n if ((hasAdminUser || hasAdminPassword) && !hasTarget) {\n throw new Error(\"--admin-user/--admin-password require --target-user for project commands\");\n }\n\n const pl = await connectClient(flags);\n\n let projectListRid: SignedResourceId;\n if (hasTarget) {\n const nav = await navigateToUserRoot(pl, flags.targetUser!);\n projectListRid = nav.projectListRid;\n } else {\n projectListRid = await getProjectListRid(pl);\n }\n\n return { pl, projectListRid };\n}\n","import { Command, Option } from \"commander\";\n\n/** Add one or more groups of options to a command, preserving group order. */\nexport function addOptions(cmd: Command, ...optionGroups: Option[][]): Command {\n for (const group of optionGroups) {\n for (const opt of group) cmd.addOption(opt);\n }\n return cmd;\n}\n\nexport const GlobalOptions = (): Option[] => [\n new Option(\"-a, --address <url>\", \"Platforma server address\")\n .env(\"PL_ADDRESS\")\n .makeOptionMandatory(),\n new Option(\"-f, --format <format>\", \"Output format\").choices([\"text\", \"json\"]).default(\"text\"),\n];\n\nexport const UserAuthOptions = (): Option[] => [\n new Option(\"-u, --user <user>\", \"Username for authentication\").env(\"PL_USER\"),\n new Option(\"-p, --password <password>\", \"Password for authentication\").env(\"PL_PASSWORD\"),\n];\n\n/** Admin credentials only (for purely admin commands like copy-project). */\nexport const AdminAuthOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username\")\n .env(\"PL_ADMIN_USER\")\n .makeOptionMandatory(),\n new Option(\"--admin-password <password>\", \"Admin/controller password\")\n .env(\"PL_ADMIN_PASSWORD\")\n .makeOptionMandatory(),\n];\n\n/** Admin credentials + target user (for regular commands that can optionally operate on another user). */\nexport const AdminTargetOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username (enables admin mode)\").env(\n \"PL_ADMIN_USER\",\n ),\n new Option(\"--admin-password <password>\", \"Admin/controller password\").env(\"PL_ADMIN_PASSWORD\"),\n new Option(\n \"--target-user <user>\",\n \"Operate on this user's data (requires admin credentials)\",\n ).env(\"PL_TARGET_USER\"),\n];\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { listProjects } from \"../../project_ops\";\nimport { formatTable, formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectListCommand(): Command {\n const cmd = new Command(\"list\").description(\"List all projects for the authenticated user.\");\n\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const projects = await listProjects(pl, projectListRid);\n\n if (flags.format === \"json\") {\n outputJson(\n projects.map((p) => ({\n id: p.id,\n rid: resourceIdToString(p.rid),\n label: p.label,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n } else {\n if (projects.length === 0) {\n outputText(\"No projects found.\");\n return;\n }\n\n outputText(\n formatTable(\n [\"ID\", \"RID\", \"LABEL\", \"CREATED\", \"LAST MODIFIED\"],\n projects.map((p) => [\n p.id,\n resourceIdToString(p.rid),\n p.label,\n formatDate(p.created),\n formatDate(p.lastModified),\n ]),\n ),\n );\n outputText(`\\n${projects.length} project(s)`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectInfoCommand(): Command {\n const cmd = new Command(\"info\").description(\"Show detailed information about a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n const info = await getProjectInfo(pl, id, rid);\n\n if (flags.format === \"json\") {\n outputJson({\n id: info.id,\n rid: resourceIdToString(info.rid),\n label: info.label,\n schemaVersion: info.schemaVersion,\n blockCount: info.blockCount,\n blockIds: info.blockIds,\n created: info.created.toISOString(),\n lastModified: info.lastModified.toISOString(),\n });\n } else {\n outputText(`Project: ${info.label}`);\n outputText(`ID: ${info.id}`);\n outputText(`RID: ${resourceIdToString(info.rid)}`);\n outputText(`Schema: ${info.schemaVersion ?? \"(unknown)\"}`);\n outputText(`Blocks: ${info.blockCount}`);\n if (info.blockIds.length > 0) {\n for (const bid of info.blockIds) {\n outputText(` - ${bid}`);\n }\n }\n outputText(`Created: ${formatDate(info.created)}`);\n outputText(`Last Modified: ${formatDate(info.lastModified)}`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDuplicateCommand(): Command {\n const cmd = new Command(\"duplicate\").description(\n \"Duplicate a project within the same user. Auto-renames on collision by default.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"-n, --name <name>\", \"Name for the duplicate\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { rid: sourceRid } = await resolveProject(pl, projectListRid, project);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"duplicateProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, projectListRid);\n\n // Compute new label\n let newLabel: string;\n if (flags.name) {\n if (!flags.autoRename && existingLabels.includes(flags.name)) {\n throw new Error(`Project name \"${flags.name}\" already exists.`);\n }\n newLabel = existingLabels.includes(flags.name)\n ? deduplicateName(flags.name, existingLabels)\n : flags.name;\n } else {\n newLabel = deduplicateName(sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const projectResourceId = await newPrj.globalId;\n const projectId = resourceIdToString(projectResourceId);\n return { id: projectId, rid: projectResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });\n } else {\n outputText(`Duplicated project as \"${result.label}\" (id: ${result.id})`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectRenameCommand(): Command {\n const cmd = new Command(\"rename\").description(\"Rename a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.addOption(new Option(\"-n, --name <name>\", \"New name for the project\").makeOptionMandatory());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n\n await renameProject(pl, rid, flags.name);\n\n if (flags.format === \"json\") {\n outputJson({ id, rid: resourceIdToString(rid), label: flags.name });\n } else {\n outputText(`Renamed project to \"${flags.name}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { createInterface } from \"node:readline\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, deleteProject, getProjectInfo } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDeleteCommand(): Command {\n const cmd = new Command(\"delete\").description(\n \"Delete a project. This permanently destroys all project data.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"--force\", \"Skip confirmation\", false);\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid, fieldName } = await resolveProject(pl, projectListRid, project);\n\n const info = await getProjectInfo(pl, id, rid);\n\n if (!flags.force) {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n `Delete project \"${info.label}\" (${info.blockCount} blocks)? [y/N] `,\n resolve,\n );\n });\n if (answer.toLowerCase() !== \"y\") {\n outputText(\"Aborted.\");\n return;\n }\n } finally {\n rl.close();\n }\n }\n\n await deleteProject(pl, projectListRid, fieldName);\n\n if (flags.format === \"json\") {\n outputJson({ deleted: true, id, label: info.label });\n } else {\n outputText(`Deleted project \"${info.label}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connectClient } from \"../../base_command\";\nimport { addOptions, GlobalOptions, AdminAuthOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n navigateToUserRoot,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function adminCopyProjectCommand(): Command {\n const cmd = new Command(\"copy-project\").description(\n \"Copy a project from one user to another. Requires admin/controller credentials.\",\n );\n\n addOptions(cmd, GlobalOptions(), AdminAuthOptions());\n cmd.addOption(\n new Option(\n \"--source-user <user>\",\n \"Username of the source project owner\",\n ).makeOptionMandatory(),\n );\n cmd.addOption(\n new Option(\"--source-project <project>\", \"Source project ID or label\").makeOptionMandatory(),\n );\n cmd.option(\n \"--target-user <user>\",\n \"Username of the target user (defaults to source-user for same-user copy)\",\n );\n cmd.option(\"-n, --name <name>\", \"Name for the copied project\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (flags) => {\n const pl = await connectClient(flags);\n try {\n const targetUser = flags.targetUser ?? flags.sourceUser;\n\n const source = await navigateToUserRoot(pl, flags.sourceUser);\n const { rid: sourceRid } = await resolveProject(\n pl,\n source.projectListRid,\n flags.sourceProject,\n );\n\n const target =\n targetUser === flags.sourceUser ? source : await navigateToUserRoot(pl, targetUser);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"adminCopyProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, target.projectListRid);\n\n let newLabel: string = flags.name ?? sourceLabel;\n\n if (existingLabels.includes(newLabel)) {\n if (!flags.autoRename) {\n throw new Error(`Project name \"${newLabel}\" already exists for target user.`);\n }\n newLabel = deduplicateName(flags.name ?? sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(target.projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const prjResourceId = await newPrj.globalId;\n const prjId = resourceIdToString(prjResourceId);\n return { id: prjId, rid: prjResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({\n id: result.id,\n rid: resourceIdToString(result.rid),\n label: result.label,\n sourceUser: flags.sourceUser,\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags.sourceUser} to ${targetUser} as \"${result.label}\" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,\n );\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport projectListCommand from \"./cmd/project/list\";\nimport projectInfoCommand from \"./cmd/project/info\";\nimport projectDuplicateCommand from \"./cmd/project/duplicate\";\nimport projectRenameCommand from \"./cmd/project/rename\";\nimport projectDeleteCommand from \"./cmd/project/delete\";\nimport adminCopyProjectCommand from \"./cmd/admin/copy-project\";\n\nexport function buildProgram(): Command {\n const program = new Command();\n program.name(\"pl-cli\").description(\"Platforma command-line client\");\n\n const project = new Command(\"project\").description(\"Manage projects\");\n project.addCommand(projectListCommand());\n project.addCommand(projectInfoCommand());\n project.addCommand(projectDuplicateCommand());\n project.addCommand(projectRenameCommand());\n project.addCommand(projectDeleteCommand());\n program.addCommand(project);\n\n const admin = new Command(\"admin\").description(\n \"Admin operations (requires controller credentials)\",\n );\n admin.addCommand(adminCopyProjectCommand());\n program.addCommand(admin);\n\n return program;\n}\n\nexport async function run(argv: string[] = process.argv): Promise<void> {\n await buildProgram().parseAsync(argv);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/base_command.ts","../src/cmd-opts.ts","../src/cmd/project/list.ts","../src/cmd/project/info.ts","../src/cmd/project/duplicate.ts","../src/cmd/project/rename.ts","../src/cmd/project/delete.ts","../src/cmd/admin/copy-project.ts","../src/cmd/admin/user-list.ts","../src/cli.ts"],"sourcesContent":["import type { PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { createPlConnection, createAdminPlConnection } from \"./connection\";\nimport { getProjectListRid, navigateToUserRoot } from \"./project_ops\";\n\n/**\n * Low-level: get an authenticated PlClient without resolving a project list.\n * Use this for commands that navigate to multiple users (e.g. admin copy-project).\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connectClient(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n}): Promise<PlClient> {\n if (flags.adminUser && flags.adminPassword) {\n return createAdminPlConnection({\n address: flags.address,\n adminUser: flags.adminUser,\n adminPassword: flags.adminPassword,\n });\n }\n return createPlConnection({\n address: flags.address,\n user: flags.user,\n password: flags.password,\n });\n}\n\n/**\n * Connect and resolve the project list for a single user.\n * In admin mode (--admin-user + --admin-password + --target-user), operates on the target user's data.\n * In user mode, operates on the authenticated user's own data.\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connect(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n targetUser?: string;\n}): Promise<{ pl: PlClient; projectListRid: SignedResourceId }> {\n const hasAdminUser = !!flags.adminUser;\n const hasAdminPassword = !!flags.adminPassword;\n const hasTarget = !!flags.targetUser;\n\n // Validate flag combinations\n if (hasTarget && !(hasAdminUser && hasAdminPassword)) {\n throw new Error(\"--target-user requires --admin-user and --admin-password\");\n }\n if ((hasAdminUser || hasAdminPassword) && !hasTarget) {\n throw new Error(\"--admin-user/--admin-password require --target-user for project commands\");\n }\n\n const pl = await connectClient(flags);\n\n let projectListRid: SignedResourceId;\n if (hasTarget) {\n const nav = await navigateToUserRoot(pl, flags.targetUser!);\n projectListRid = nav.projectListRid;\n } else {\n projectListRid = await getProjectListRid(pl);\n }\n\n return { pl, projectListRid };\n}\n","import { Command, Option } from \"commander\";\nimport type { OutputFormat } from \"./output\";\n\n/** Add one or more groups of options to a command, preserving group order. */\nexport function addOptions(cmd: Command, ...optionGroups: Option[][]): Command {\n for (const group of optionGroups) {\n for (const opt of group) cmd.addOption(opt);\n }\n return cmd;\n}\n\nexport const GlobalOptions = (\n formatDefault: OutputFormat = \"text\",\n allowedFormats: OutputFormat[] = [\"text\", \"json\"],\n): Option[] => [\n new Option(\"-a, --address <url>\", \"Platforma server address\")\n .env(\"PL_ADDRESS\")\n .makeOptionMandatory(),\n new Option(\"-f, --format <format>\", \"Output format\")\n .choices(allowedFormats)\n .default(formatDefault),\n];\n\nexport const UserAuthOptions = (): Option[] => [\n new Option(\"-u, --user <user>\", \"Username for authentication\").env(\"PL_USER\"),\n new Option(\"-p, --password <password>\", \"Password for authentication\").env(\"PL_PASSWORD\"),\n];\n\n/** Admin credentials only (for purely admin commands like copy-project). */\nexport const AdminAuthOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username\")\n .env(\"PL_ADMIN_USER\")\n .makeOptionMandatory(),\n new Option(\"--admin-password <password>\", \"Admin/controller password\")\n .env(\"PL_ADMIN_PASSWORD\")\n .makeOptionMandatory(),\n];\n\n/** Admin credentials + target user (for regular commands that can optionally operate on another user). */\nexport const AdminTargetOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username (enables admin mode)\").env(\n \"PL_ADMIN_USER\",\n ),\n new Option(\"--admin-password <password>\", \"Admin/controller password\").env(\"PL_ADMIN_PASSWORD\"),\n new Option(\n \"--target-user <user>\",\n \"Operate on this user's data (requires admin credentials)\",\n ).env(\"PL_TARGET_USER\"),\n];\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { listProjects } from \"../../project_ops\";\nimport { formatTable, formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectListCommand(): Command {\n const cmd = new Command(\"list\").description(\"List all projects for the authenticated user.\");\n\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const projects = await listProjects(pl, projectListRid);\n\n if (flags.format === \"json\") {\n outputJson(\n projects.map((p) => ({\n id: p.id,\n rid: resourceIdToString(p.rid),\n label: p.label,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n } else {\n if (projects.length === 0) {\n outputText(\"No projects found.\");\n return;\n }\n\n outputText(\n formatTable(\n [\"ID\", \"RID\", \"LABEL\", \"CREATED\", \"LAST MODIFIED\"],\n projects.map((p) => [\n p.id,\n resourceIdToString(p.rid),\n p.label,\n formatDate(p.created),\n formatDate(p.lastModified),\n ]),\n ),\n );\n outputText(`\\n${projects.length} project(s)`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectInfoCommand(): Command {\n const cmd = new Command(\"info\").description(\"Show detailed information about a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n const info = await getProjectInfo(pl, id, rid);\n\n if (flags.format === \"json\") {\n outputJson({\n id: info.id,\n rid: resourceIdToString(info.rid),\n label: info.label,\n schemaVersion: info.schemaVersion,\n blockCount: info.blockCount,\n blockIds: info.blockIds,\n created: info.created.toISOString(),\n lastModified: info.lastModified.toISOString(),\n });\n } else {\n outputText(`Project: ${info.label}`);\n outputText(`ID: ${info.id}`);\n outputText(`RID: ${resourceIdToString(info.rid)}`);\n outputText(`Schema: ${info.schemaVersion ?? \"(unknown)\"}`);\n outputText(`Blocks: ${info.blockCount}`);\n if (info.blockIds.length > 0) {\n for (const bid of info.blockIds) {\n outputText(` - ${bid}`);\n }\n }\n outputText(`Created: ${formatDate(info.created)}`);\n outputText(`Last Modified: ${formatDate(info.lastModified)}`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDuplicateCommand(): Command {\n const cmd = new Command(\"duplicate\").description(\n \"Duplicate a project within the same user. Auto-renames on collision by default.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"-n, --name <name>\", \"Name for the duplicate\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { rid: sourceRid } = await resolveProject(pl, projectListRid, project);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"duplicateProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, projectListRid);\n\n // Compute new label\n let newLabel: string;\n if (flags.name) {\n if (!flags.autoRename && existingLabels.includes(flags.name)) {\n throw new Error(`Project name \"${flags.name}\" already exists.`);\n }\n newLabel = existingLabels.includes(flags.name)\n ? deduplicateName(flags.name, existingLabels)\n : flags.name;\n } else {\n newLabel = deduplicateName(sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const projectResourceId = await newPrj.globalId;\n const projectId = resourceIdToString(projectResourceId);\n return { id: projectId, rid: projectResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });\n } else {\n outputText(`Duplicated project as \"${result.label}\" (id: ${result.id})`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectRenameCommand(): Command {\n const cmd = new Command(\"rename\").description(\"Rename a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.addOption(new Option(\"-n, --name <name>\", \"New name for the project\").makeOptionMandatory());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n\n await renameProject(pl, rid, flags.name);\n\n if (flags.format === \"json\") {\n outputJson({ id, rid: resourceIdToString(rid), label: flags.name });\n } else {\n outputText(`Renamed project to \"${flags.name}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { createInterface } from \"node:readline\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, deleteProject, getProjectInfo } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDeleteCommand(): Command {\n const cmd = new Command(\"delete\").description(\n \"Delete a project. This permanently destroys all project data.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"--force\", \"Skip confirmation\", false);\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid, fieldName } = await resolveProject(pl, projectListRid, project);\n\n const info = await getProjectInfo(pl, id, rid);\n\n if (!flags.force) {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n `Delete project \"${info.label}\" (${info.blockCount} blocks)? [y/N] `,\n resolve,\n );\n });\n if (answer.toLowerCase() !== \"y\") {\n outputText(\"Aborted.\");\n return;\n }\n } finally {\n rl.close();\n }\n }\n\n await deleteProject(pl, projectListRid, fieldName);\n\n if (flags.format === \"json\") {\n outputJson({ deleted: true, id, label: info.label });\n } else {\n outputText(`Deleted project \"${info.label}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connectClient } from \"../../base_command\";\nimport { addOptions, GlobalOptions, AdminAuthOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n navigateToUserRoot,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function adminCopyProjectCommand(): Command {\n const cmd = new Command(\"copy-project\").description(\n \"Copy a project from one user to another. Requires admin/controller credentials.\",\n );\n\n addOptions(cmd, GlobalOptions(), AdminAuthOptions());\n cmd.addOption(\n new Option(\n \"--source-user <user>\",\n \"Username of the source project owner\",\n ).makeOptionMandatory(),\n );\n cmd.addOption(\n new Option(\"--source-project <project>\", \"Source project ID or label\").makeOptionMandatory(),\n );\n cmd.option(\n \"--target-user <user>\",\n \"Username of the target user (defaults to source-user for same-user copy)\",\n );\n cmd.option(\"-n, --name <name>\", \"Name for the copied project\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (flags) => {\n const pl = await connectClient(flags);\n try {\n const targetUser = flags.targetUser ?? flags.sourceUser;\n\n const source = await navigateToUserRoot(pl, flags.sourceUser);\n const { rid: sourceRid } = await resolveProject(\n pl,\n source.projectListRid,\n flags.sourceProject,\n );\n\n const target =\n targetUser === flags.sourceUser ? source : await navigateToUserRoot(pl, targetUser);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"adminCopyProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, target.projectListRid);\n\n let newLabel: string = flags.name ?? sourceLabel;\n\n if (existingLabels.includes(newLabel)) {\n if (!flags.autoRename) {\n throw new Error(`Project name \"${newLabel}\" already exists for target user.`);\n }\n newLabel = deduplicateName(flags.name ?? sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(target.projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const prjResourceId = await newPrj.globalId;\n const prjId = resourceIdToString(prjResourceId);\n return { id: prjId, rid: prjResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({\n id: result.id,\n rid: resourceIdToString(result.rid),\n label: result.label,\n sourceUser: flags.sourceUser,\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags.sourceUser} to ${targetUser} as \"${result.label}\" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,\n );\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { connectClient } from \"../../base_command\";\nimport { addOptions, GlobalOptions, AdminAuthOptions } from \"../../cmd-opts\";\nimport { outputJson, outputText, outputCsv } from \"../../output\";\n\nexport default function adminUserListCommand(): Command {\n const cmd = new Command(\"user-list\").description(\n \"List all known users' logins from a live server. Requires admin/controller credentials.\",\n );\n\n addOptions(cmd, GlobalOptions(\"csv\", [\"text\", \"json\", \"csv\"]), AdminAuthOptions());\n\n cmd.action(async (flags) => {\n const pl = await connectClient(flags);\n try {\n const logins = (await pl.listUsers()).map((u) => u.login).sort();\n if (flags.format === \"json\") outputJson(logins);\n else if (flags.format === \"text\") outputText(logins.join(\"\\n\"));\n else\n outputCsv(\n [\"login\"],\n logins.map((l) => [l]),\n );\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport projectListCommand from \"./cmd/project/list\";\nimport projectInfoCommand from \"./cmd/project/info\";\nimport projectDuplicateCommand from \"./cmd/project/duplicate\";\nimport projectRenameCommand from \"./cmd/project/rename\";\nimport projectDeleteCommand from \"./cmd/project/delete\";\nimport adminCopyProjectCommand from \"./cmd/admin/copy-project\";\nimport adminUserListCommand from \"./cmd/admin/user-list\";\n\nexport function buildProgram(): Command {\n const program = new Command();\n program.name(\"pl-cli\").description(\"Platforma command-line client\");\n\n const project = new Command(\"project\").description(\"Manage projects\");\n project.addCommand(projectListCommand());\n project.addCommand(projectInfoCommand());\n project.addCommand(projectDuplicateCommand());\n project.addCommand(projectRenameCommand());\n project.addCommand(projectDeleteCommand());\n program.addCommand(project);\n\n const admin = new Command(\"admin\").description(\n \"Admin operations (requires controller credentials)\",\n );\n admin.addCommand(adminCopyProjectCommand());\n admin.addCommand(adminUserListCommand());\n program.addCommand(admin);\n\n return program;\n}\n\nexport async function run(argv: string[] = process.argv): Promise<void> {\n await buildProgram().parseAsync(argv);\n}\n"],"mappings":"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"}
|
package/dist/cmd-opts.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
|
+
import { OutputFormat } from './output';
|
|
2
3
|
/** Add one or more groups of options to a command, preserving group order. */
|
|
3
4
|
export declare function addOptions(cmd: Command, ...optionGroups: Option[][]): Command;
|
|
4
|
-
export declare const GlobalOptions: () => Option[];
|
|
5
|
+
export declare const GlobalOptions: (formatDefault?: OutputFormat, allowedFormats?: OutputFormat[]) => Option[];
|
|
5
6
|
export declare const UserAuthOptions: () => Option[];
|
|
6
7
|
/** Admin credentials only (for purely admin commands like copy-project). */
|
|
7
8
|
export declare const AdminAuthOptions: () => Option[];
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as createAdminPlConnection,
|
|
1
|
+
import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, s as p, t as m, u as h } from "./output-KEq0q5fS.js";
|
|
2
|
+
export { a as createAdminPlConnection, e as createPlConnection, d as deduplicateName, p as deleteProject, n as duplicateProject, m as formatDate, u as formatTable, c as getExistingLabelsInTx, h as getProjectInfo, r as getProjectListRid, i as listProjects, f as navigateToUserRoot, s as outputJson, t as outputText, l as renameProject, o as resolveProject };
|
|
@@ -146,6 +146,10 @@ function T(e) {
|
|
|
146
146
|
console.log(JSON.stringify(e, null, 2));
|
|
147
147
|
}
|
|
148
148
|
function E(e, t) {
|
|
149
|
+
let n = (e) => /[",\r\n]/.test(e) ? `"${e.replace(/"/g, "\"\"")}"` : e, r = [e, ...t].map((e) => e.map(n).join(","));
|
|
150
|
+
console.log(r.join("\n"));
|
|
151
|
+
}
|
|
152
|
+
function D(e, t) {
|
|
149
153
|
let n = e.map((e, n) => Math.max(e.length, ...t.map((e) => (e[n] ?? "").length))), r = n.map((e) => "-".repeat(e)).join(" ");
|
|
150
154
|
return [
|
|
151
155
|
e.map((e, t) => e.padEnd(n[t])).join(" "),
|
|
@@ -153,10 +157,10 @@ function E(e, t) {
|
|
|
153
157
|
...t.map((e) => e.map((e, t) => (e ?? "").padEnd(n[t])).join(" "))
|
|
154
158
|
].join("\n");
|
|
155
159
|
}
|
|
156
|
-
function
|
|
160
|
+
function O(e) {
|
|
157
161
|
return e.getTime() === 0 ? "(unknown)" : e.toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
158
162
|
}
|
|
159
163
|
//#endregion
|
|
160
|
-
export {
|
|
164
|
+
export { p as _, w as a, f as c, S as d, h as f, m as g, _ as h, T as i, v as l, b as m, D as n, y as o, C as p, E as r, x as s, O as t, g as u };
|
|
161
165
|
|
|
162
|
-
//# sourceMappingURL=output-
|
|
166
|
+
//# sourceMappingURL=output-KEq0q5fS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-Npj-8KEL.js","names":[],"sources":["../src/connection.ts","../src/project_ops.ts","../src/output.ts"],"sourcesContent":["import {\n PlClient,\n UnauthenticatedPlClient,\n plAddressToConfig,\n type AuthInformation,\n type PlClientConfig,\n} from \"@milaboratories/pl-client\";\n\nexport interface PlConnectionOptions {\n address: string;\n user?: string;\n password?: string;\n}\n\nexport interface AdminConnectionOptions {\n address: string;\n adminUser: string;\n adminPassword: string;\n}\n\n/** Creates an authenticated PlClient from address + credentials. */\nexport async function createPlConnection(opts: PlConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n if (opts.user !== undefined) config.user = opts.user;\n if (opts.password !== undefined) config.password = opts.password;\n\n const unauth = await UnauthenticatedPlClient.build(config);\n let authInformation: AuthInformation;\n\n if (await unauth.requireAuth()) {\n if (config.user === undefined || config.password === undefined) {\n throw new Error(\n \"Server requires authentication but no credentials provided. \" +\n \"Use --user/--password flags or PL_USER/PL_PASSWORD env vars.\",\n );\n }\n authInformation = await unauth.login(config.user, config.password);\n } else {\n authInformation = {};\n }\n\n return await PlClient.init(config, { authInformation });\n}\n\n/** Creates a PlClient with admin/controller credentials. */\nexport async function createAdminPlConnection(opts: AdminConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n const unauth = await UnauthenticatedPlClient.build(config);\n const authInformation: AuthInformation = await unauth.login(opts.adminUser, opts.adminPassword);\n\n return await PlClient.init(config, { authInformation });\n}\n","import type { PlClient, SignedResourceId, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, isNullSignedResourceId, resourceIdToString } from \"@milaboratories/pl-client\";\nimport {\n ProjectMetaKey,\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n SchemaVersionKey,\n ProjectStructureKey,\n ProjectsField,\n duplicateProject,\n} from \"@milaboratories/pl-middle-layer\";\nimport type { ProjectMeta } from \"@milaboratories/pl-middle-layer\";\n\nexport interface ProjectEntry {\n id: string;\n rid: SignedResourceId;\n label: string;\n created: Date;\n lastModified: Date;\n}\n\nexport interface ProjectIdentity {\n /** Project ID (resourceIdToString of resource ID) */\n id: string;\n /** Signed resource ID for use in transactions */\n rid: SignedResourceId;\n /** UUID field name in the project list resource */\n fieldName: string;\n}\n\nexport interface ProjectInfo extends ProjectEntry {\n schemaVersion: string | undefined;\n blockCount: number;\n blockIds: string[];\n}\n\n/** List all projects from a project list resource. */\nexport async function listProjects(\n pl: PlClient,\n projectListRid: SignedResourceId,\n): Promise<ProjectEntry[]> {\n return await pl.withReadTx(\"listProjects\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n const entries: ProjectEntry[] = [];\n\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n\n const [metaStr, createdStr, modifiedStr] = await Promise.all([\n tx.getKValueStringIfExists(f.value, ProjectMetaKey),\n tx.getKValueStringIfExists(f.value, ProjectCreatedTimestamp),\n tx.getKValueStringIfExists(f.value, ProjectLastModifiedTimestamp),\n ]);\n\n const meta: ProjectMeta = metaStr ? JSON.parse(metaStr) : { label: \"(unknown)\" };\n\n const projectId = resourceIdToString(f.value);\n entries.push({\n id: projectId,\n rid: f.value,\n label: meta.label,\n created: createdStr ? new Date(Number(createdStr)) : new Date(0),\n lastModified: modifiedStr ? new Date(Number(modifiedStr)) : new Date(0),\n });\n }\n\n entries.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n return entries;\n });\n}\n\n/** Get detailed info about a project. */\nexport async function getProjectInfo(\n pl: PlClient,\n projectId: string,\n rid: SignedResourceId,\n): Promise<ProjectInfo> {\n return await pl.withReadTx(\"getProjectInfo\", async (tx) => {\n const kvs = await tx.listKeyValuesString(rid);\n\n const metaKV = kvs.find((kv: { key: string }) => kv.key === ProjectMetaKey);\n const createdKV = kvs.find((kv: { key: string }) => kv.key === ProjectCreatedTimestamp);\n const modifiedKV = kvs.find((kv: { key: string }) => kv.key === ProjectLastModifiedTimestamp);\n const schemaKV = kvs.find((kv: { key: string }) => kv.key === SchemaVersionKey);\n const structureKV = kvs.find((kv: { key: string }) => kv.key === ProjectStructureKey);\n\n const meta: ProjectMeta = metaKV ? JSON.parse(metaKV.value) : { label: \"(unknown)\" };\n const schemaVersion = schemaKV ? JSON.parse(schemaKV.value) : undefined;\n\n const blockIds: string[] = [];\n if (structureKV) {\n const structure = JSON.parse(structureKV.value);\n for (const group of structure.groups ?? []) {\n for (const block of group.blocks ?? []) {\n blockIds.push(block.id);\n }\n }\n }\n\n return {\n id: projectId,\n rid: rid,\n label: meta.label,\n created: createdKV ? new Date(Number(createdKV.value)) : new Date(0),\n lastModified: modifiedKV ? new Date(Number(modifiedKV.value)) : new Date(0),\n schemaVersion,\n blockCount: blockIds.length,\n blockIds,\n };\n });\n}\n\n/** Resolve a project identifier (id or label) to its project ID, ResourceId and field name. */\nexport async function resolveProject(\n pl: PlClient,\n projectListRid: SignedResourceId,\n identifier: string,\n): Promise<ProjectIdentity> {\n return await pl.withReadTx(\"resolveProject\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const projectId = resourceIdToString(f.value);\n if (projectId === identifier) {\n return { id: projectId, rid: f.value, fieldName: f.name };\n }\n }\n // Fallback: match by label\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n if (meta.label === identifier) {\n return { id: resourceIdToString(f.value), rid: f.value, fieldName: f.name };\n }\n }\n }\n\n throw new Error(`Project \"${identifier}\" not found (searched by id and label).`);\n });\n}\n\n/** Read all project labels within an existing transaction. */\nexport async function getExistingLabelsInTx(\n tx: PlTransaction,\n projectListRid: SignedResourceId,\n): Promise<string[]> {\n const data = await tx.getResourceData(projectListRid, true);\n const labels: string[] = [];\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n labels.push(meta.label);\n }\n }\n return labels;\n}\n\n/** Get all project labels from a project list. */\nexport async function getProjectLabels(\n pl: PlClient,\n projectListRid: SignedResourceId,\n): Promise<string[]> {\n return await pl.withReadTx(\"getProjectLabels\", async (tx) => {\n return getExistingLabelsInTx(tx, projectListRid);\n });\n}\n\n/**\n * Deduplicates a project name against existing labels.\n * \"X\" → \"X (Copy)\" → \"X (Copy 2)\" → ...\n */\nexport function deduplicateName(baseName: string, existingLabels: string[]): string {\n let candidate = `${baseName} (Copy)`;\n let i = 2;\n while (existingLabels.includes(candidate)) {\n candidate = `${baseName} (Copy ${i})`;\n i++;\n }\n return candidate;\n}\n\n/** Rename a project (update its label). */\nexport async function renameProject(\n pl: PlClient,\n projectRid: SignedResourceId,\n newLabel: string,\n): Promise<void> {\n await pl.withWriteTx(\"renameProject\", async (tx) => {\n const metaStr = await tx.getKValueString(projectRid, ProjectMetaKey);\n const meta: ProjectMeta = JSON.parse(metaStr);\n const updated: ProjectMeta = { ...meta, label: newLabel };\n tx.setKValue(projectRid, ProjectMetaKey, JSON.stringify(updated));\n tx.setKValue(projectRid, ProjectLastModifiedTimestamp, String(Date.now()));\n await tx.commit();\n });\n}\n\n/** Delete a project from the project list. */\nexport async function deleteProject(\n pl: PlClient,\n projectListRid: SignedResourceId,\n fieldName: string,\n): Promise<void> {\n await pl.withWriteTx(\"deleteProject\", async (tx) => {\n tx.removeField(field(projectListRid, fieldName));\n await tx.commit();\n });\n}\n\n/** Get the project list ResourceId for the connected user. */\nexport async function getProjectListRid(pl: PlClient): Promise<SignedResourceId> {\n return await pl.withReadTx(\"getProjectList\", async (tx) => {\n const fieldData = await tx.getField({\n resourceId: tx.clientRoot,\n fieldName: ProjectsField,\n });\n if (isNullSignedResourceId(fieldData.value)) {\n throw new Error(\"No project list found for this user.\");\n }\n return fieldData.value;\n });\n}\n\n/**\n * Navigates to a specific user's project list resource ID.\n * Tries ListUserResources first (new backend), falls back to SHA256 named resource lookup.\n */\nexport async function navigateToUserRoot(\n pl: PlClient,\n username: string,\n): Promise<{ userRoot: SignedResourceId; projectListRid: SignedResourceId }> {\n let userRootRid: SignedResourceId | undefined;\n\n userRootRid = await pl.getUserRoot({ login: username });\n if (userRootRid === undefined) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n\n // Read the projects field from the resolved user root\n return await pl.withReadTx(\"navigateToUserProjects\", async (tx) => {\n const projectsFieldData = await tx.getField({\n resourceId: userRootRid,\n fieldName: ProjectsField,\n });\n\n if (isNullSignedResourceId(projectsFieldData.value)) {\n throw new Error(`User \"${username}\" has no project list.`);\n }\n\n return { userRoot: userRootRid, projectListRid: projectsFieldData.value };\n });\n}\n\n// Re-export duplicateProject from pl-middle-layer for use in commands\nexport { duplicateProject };\n","export type OutputFormat = \"text\" | \"json\";\n\n/** Outputs a line of text to stdout. */\nexport function outputText(message: string): void {\n console.log(message);\n}\n\n/** Outputs data as formatted JSON to stdout. */\nexport function outputJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\n/** Formats a table as aligned text columns. */\nexport function formatTable(headers: string[], rows: string[][]): string {\n const colWidths = headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const sep = colWidths.map((w) => \"-\".repeat(w)).join(\" \");\n const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(\" \");\n const dataLines = rows.map((row) =>\n row.map((cell, i) => (cell ?? \"\").padEnd(colWidths[i])).join(\" \"),\n );\n\n return [headerLine, sep, ...dataLines].join(\"\\n\");\n}\n\n/** Formats a Date as a short human-readable string. */\nexport function formatDate(d: Date): string {\n if (d.getTime() === 0) return \"(unknown)\";\n return d\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \"Z\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"output-KEq0q5fS.js","names":[],"sources":["../src/connection.ts","../src/project_ops.ts","../src/output.ts"],"sourcesContent":["import {\n PlClient,\n UnauthenticatedPlClient,\n plAddressToConfig,\n type AuthInformation,\n type PlClientConfig,\n} from \"@milaboratories/pl-client\";\n\nexport interface PlConnectionOptions {\n address: string;\n user?: string;\n password?: string;\n}\n\nexport interface AdminConnectionOptions {\n address: string;\n adminUser: string;\n adminPassword: string;\n}\n\n/** Creates an authenticated PlClient from address + credentials. */\nexport async function createPlConnection(opts: PlConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n if (opts.user !== undefined) config.user = opts.user;\n if (opts.password !== undefined) config.password = opts.password;\n\n const unauth = await UnauthenticatedPlClient.build(config);\n let authInformation: AuthInformation;\n\n if (await unauth.requireAuth()) {\n if (config.user === undefined || config.password === undefined) {\n throw new Error(\n \"Server requires authentication but no credentials provided. \" +\n \"Use --user/--password flags or PL_USER/PL_PASSWORD env vars.\",\n );\n }\n authInformation = await unauth.login(config.user, config.password);\n } else {\n authInformation = {};\n }\n\n return await PlClient.init(config, { authInformation });\n}\n\n/** Creates a PlClient with admin/controller credentials. */\nexport async function createAdminPlConnection(opts: AdminConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n const unauth = await UnauthenticatedPlClient.build(config);\n const authInformation: AuthInformation = await unauth.login(opts.adminUser, opts.adminPassword);\n\n return await PlClient.init(config, { authInformation });\n}\n","import type { PlClient, SignedResourceId, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, isNullSignedResourceId, resourceIdToString } from \"@milaboratories/pl-client\";\nimport {\n ProjectMetaKey,\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n SchemaVersionKey,\n ProjectStructureKey,\n ProjectsField,\n duplicateProject,\n} from \"@milaboratories/pl-middle-layer\";\nimport type { ProjectMeta } from \"@milaboratories/pl-middle-layer\";\n\nexport interface ProjectEntry {\n id: string;\n rid: SignedResourceId;\n label: string;\n created: Date;\n lastModified: Date;\n}\n\nexport interface ProjectIdentity {\n /** Project ID (resourceIdToString of resource ID) */\n id: string;\n /** Signed resource ID for use in transactions */\n rid: SignedResourceId;\n /** UUID field name in the project list resource */\n fieldName: string;\n}\n\nexport interface ProjectInfo extends ProjectEntry {\n schemaVersion: string | undefined;\n blockCount: number;\n blockIds: string[];\n}\n\n/** List all projects from a project list resource. */\nexport async function listProjects(\n pl: PlClient,\n projectListRid: SignedResourceId,\n): Promise<ProjectEntry[]> {\n return await pl.withReadTx(\"listProjects\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n const entries: ProjectEntry[] = [];\n\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n\n const [metaStr, createdStr, modifiedStr] = await Promise.all([\n tx.getKValueStringIfExists(f.value, ProjectMetaKey),\n tx.getKValueStringIfExists(f.value, ProjectCreatedTimestamp),\n tx.getKValueStringIfExists(f.value, ProjectLastModifiedTimestamp),\n ]);\n\n const meta: ProjectMeta = metaStr ? JSON.parse(metaStr) : { label: \"(unknown)\" };\n\n const projectId = resourceIdToString(f.value);\n entries.push({\n id: projectId,\n rid: f.value,\n label: meta.label,\n created: createdStr ? new Date(Number(createdStr)) : new Date(0),\n lastModified: modifiedStr ? new Date(Number(modifiedStr)) : new Date(0),\n });\n }\n\n entries.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n return entries;\n });\n}\n\n/** Get detailed info about a project. */\nexport async function getProjectInfo(\n pl: PlClient,\n projectId: string,\n rid: SignedResourceId,\n): Promise<ProjectInfo> {\n return await pl.withReadTx(\"getProjectInfo\", async (tx) => {\n const kvs = await tx.listKeyValuesString(rid);\n\n const metaKV = kvs.find((kv: { key: string }) => kv.key === ProjectMetaKey);\n const createdKV = kvs.find((kv: { key: string }) => kv.key === ProjectCreatedTimestamp);\n const modifiedKV = kvs.find((kv: { key: string }) => kv.key === ProjectLastModifiedTimestamp);\n const schemaKV = kvs.find((kv: { key: string }) => kv.key === SchemaVersionKey);\n const structureKV = kvs.find((kv: { key: string }) => kv.key === ProjectStructureKey);\n\n const meta: ProjectMeta = metaKV ? JSON.parse(metaKV.value) : { label: \"(unknown)\" };\n const schemaVersion = schemaKV ? JSON.parse(schemaKV.value) : undefined;\n\n const blockIds: string[] = [];\n if (structureKV) {\n const structure = JSON.parse(structureKV.value);\n for (const group of structure.groups ?? []) {\n for (const block of group.blocks ?? []) {\n blockIds.push(block.id);\n }\n }\n }\n\n return {\n id: projectId,\n rid: rid,\n label: meta.label,\n created: createdKV ? new Date(Number(createdKV.value)) : new Date(0),\n lastModified: modifiedKV ? new Date(Number(modifiedKV.value)) : new Date(0),\n schemaVersion,\n blockCount: blockIds.length,\n blockIds,\n };\n });\n}\n\n/** Resolve a project identifier (id or label) to its project ID, ResourceId and field name. */\nexport async function resolveProject(\n pl: PlClient,\n projectListRid: SignedResourceId,\n identifier: string,\n): Promise<ProjectIdentity> {\n return await pl.withReadTx(\"resolveProject\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const projectId = resourceIdToString(f.value);\n if (projectId === identifier) {\n return { id: projectId, rid: f.value, fieldName: f.name };\n }\n }\n // Fallback: match by label\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n if (meta.label === identifier) {\n return { id: resourceIdToString(f.value), rid: f.value, fieldName: f.name };\n }\n }\n }\n\n throw new Error(`Project \"${identifier}\" not found (searched by id and label).`);\n });\n}\n\n/** Read all project labels within an existing transaction. */\nexport async function getExistingLabelsInTx(\n tx: PlTransaction,\n projectListRid: SignedResourceId,\n): Promise<string[]> {\n const data = await tx.getResourceData(projectListRid, true);\n const labels: string[] = [];\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n labels.push(meta.label);\n }\n }\n return labels;\n}\n\n/** Get all project labels from a project list. */\nexport async function getProjectLabels(\n pl: PlClient,\n projectListRid: SignedResourceId,\n): Promise<string[]> {\n return await pl.withReadTx(\"getProjectLabels\", async (tx) => {\n return getExistingLabelsInTx(tx, projectListRid);\n });\n}\n\n/**\n * Deduplicates a project name against existing labels.\n * \"X\" → \"X (Copy)\" → \"X (Copy 2)\" → ...\n */\nexport function deduplicateName(baseName: string, existingLabels: string[]): string {\n let candidate = `${baseName} (Copy)`;\n let i = 2;\n while (existingLabels.includes(candidate)) {\n candidate = `${baseName} (Copy ${i})`;\n i++;\n }\n return candidate;\n}\n\n/** Rename a project (update its label). */\nexport async function renameProject(\n pl: PlClient,\n projectRid: SignedResourceId,\n newLabel: string,\n): Promise<void> {\n await pl.withWriteTx(\"renameProject\", async (tx) => {\n const metaStr = await tx.getKValueString(projectRid, ProjectMetaKey);\n const meta: ProjectMeta = JSON.parse(metaStr);\n const updated: ProjectMeta = { ...meta, label: newLabel };\n tx.setKValue(projectRid, ProjectMetaKey, JSON.stringify(updated));\n tx.setKValue(projectRid, ProjectLastModifiedTimestamp, String(Date.now()));\n await tx.commit();\n });\n}\n\n/** Delete a project from the project list. */\nexport async function deleteProject(\n pl: PlClient,\n projectListRid: SignedResourceId,\n fieldName: string,\n): Promise<void> {\n await pl.withWriteTx(\"deleteProject\", async (tx) => {\n tx.removeField(field(projectListRid, fieldName));\n await tx.commit();\n });\n}\n\n/** Get the project list ResourceId for the connected user. */\nexport async function getProjectListRid(pl: PlClient): Promise<SignedResourceId> {\n return await pl.withReadTx(\"getProjectList\", async (tx) => {\n const fieldData = await tx.getField({\n resourceId: tx.clientRoot,\n fieldName: ProjectsField,\n });\n if (isNullSignedResourceId(fieldData.value)) {\n throw new Error(\"No project list found for this user.\");\n }\n return fieldData.value;\n });\n}\n\n/**\n * Navigates to a specific user's project list resource ID.\n * Tries ListUserResources first (new backend), falls back to SHA256 named resource lookup.\n */\nexport async function navigateToUserRoot(\n pl: PlClient,\n username: string,\n): Promise<{ userRoot: SignedResourceId; projectListRid: SignedResourceId }> {\n let userRootRid: SignedResourceId | undefined;\n\n userRootRid = await pl.getUserRoot({ login: username });\n if (userRootRid === undefined) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n\n // Read the projects field from the resolved user root\n return await pl.withReadTx(\"navigateToUserProjects\", async (tx) => {\n const projectsFieldData = await tx.getField({\n resourceId: userRootRid,\n fieldName: ProjectsField,\n });\n\n if (isNullSignedResourceId(projectsFieldData.value)) {\n throw new Error(`User \"${username}\" has no project list.`);\n }\n\n return { userRoot: userRootRid, projectListRid: projectsFieldData.value };\n });\n}\n\n// Re-export duplicateProject from pl-middle-layer for use in commands\nexport { duplicateProject };\n","export type OutputFormat = \"text\" | \"json\" | \"csv\";\n\n/** Outputs a line of text to stdout. */\nexport function outputText(message: string): void {\n console.log(message);\n}\n\n/** Outputs data as formatted JSON to stdout. */\nexport function outputJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\n/** Outputs a header row + data rows as RFC-4180-ish CSV to stdout. */\nexport function outputCsv(headers: string[], rows: string[][]): void {\n const esc = (v: string) => (/[\",\\r\\n]/.test(v) ? `\"${v.replace(/\"/g, '\"\"')}\"` : v);\n const lines = [headers, ...rows].map((r) => r.map(esc).join(\",\"));\n console.log(lines.join(\"\\n\"));\n}\n\n/** Formats a table as aligned text columns. */\nexport function formatTable(headers: string[], rows: string[][]): string {\n const colWidths = headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const sep = colWidths.map((w) => \"-\".repeat(w)).join(\" \");\n const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(\" \");\n const dataLines = rows.map((row) =>\n row.map((cell, i) => (cell ?? \"\").padEnd(colWidths[i])).join(\" \"),\n );\n\n return [headerLine, sep, ...dataLines].join(\"\\n\");\n}\n\n/** Formats a Date as a short human-readable string. */\nexport function formatDate(d: Date): string {\n if (d.getTime() === 0) return \"(unknown)\";\n return d\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \"Z\");\n}\n"],"mappings":"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"}
|
package/dist/output.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export type OutputFormat = "text" | "json";
|
|
1
|
+
export type OutputFormat = "text" | "json" | "csv";
|
|
2
2
|
/** Outputs a line of text to stdout. */
|
|
3
3
|
export declare function outputText(message: string): void;
|
|
4
4
|
/** Outputs data as formatted JSON to stdout. */
|
|
5
5
|
export declare function outputJson(data: unknown): void;
|
|
6
|
+
/** Outputs a header row + data rows as RFC-4180-ish CSV to stdout. */
|
|
7
|
+
export declare function outputCsv(headers: string[], rows: string[][]): void;
|
|
6
8
|
/** Formats a table as aligned text columns. */
|
|
7
9
|
export declare function formatTable(headers: string[], rows: string[][]): string;
|
|
8
10
|
/** Formats a Date as a short human-readable string. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/pl-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "CLI for Platforma server state manipulation",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"commander": "^15.0.0",
|
|
26
|
-
"@milaboratories/pl-client": "3.
|
|
27
|
-
"@milaboratories/pl-middle-layer": "1.65.
|
|
26
|
+
"@milaboratories/pl-client": "3.14.0",
|
|
27
|
+
"@milaboratories/pl-middle-layer": "1.65.8"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "~24.5.2",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"vite": "^8.0.6",
|
|
34
34
|
"vite-plugin-dts": "^4.5.3",
|
|
35
35
|
"vitest": "^4.1.3",
|
|
36
|
-
"@milaboratories/build-configs": "2.0.0",
|
|
37
36
|
"@milaboratories/ts-builder": "1.6.0",
|
|
37
|
+
"@milaboratories/build-configs": "2.0.0",
|
|
38
38
|
"@milaboratories/ts-configs": "1.3.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import projectDuplicateCommand from "./cmd/project/duplicate";
|
|
|
5
5
|
import projectRenameCommand from "./cmd/project/rename";
|
|
6
6
|
import projectDeleteCommand from "./cmd/project/delete";
|
|
7
7
|
import adminCopyProjectCommand from "./cmd/admin/copy-project";
|
|
8
|
+
import adminUserListCommand from "./cmd/admin/user-list";
|
|
8
9
|
|
|
9
10
|
export function buildProgram(): Command {
|
|
10
11
|
const program = new Command();
|
|
@@ -22,6 +23,7 @@ export function buildProgram(): Command {
|
|
|
22
23
|
"Admin operations (requires controller credentials)",
|
|
23
24
|
);
|
|
24
25
|
admin.addCommand(adminCopyProjectCommand());
|
|
26
|
+
admin.addCommand(adminUserListCommand());
|
|
25
27
|
program.addCommand(admin);
|
|
26
28
|
|
|
27
29
|
return program;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { connectClient } from "../../base_command";
|
|
3
|
+
import { addOptions, GlobalOptions, AdminAuthOptions } from "../../cmd-opts";
|
|
4
|
+
import { outputJson, outputText, outputCsv } from "../../output";
|
|
5
|
+
|
|
6
|
+
export default function adminUserListCommand(): Command {
|
|
7
|
+
const cmd = new Command("user-list").description(
|
|
8
|
+
"List all known users' logins from a live server. Requires admin/controller credentials.",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
addOptions(cmd, GlobalOptions("csv", ["text", "json", "csv"]), AdminAuthOptions());
|
|
12
|
+
|
|
13
|
+
cmd.action(async (flags) => {
|
|
14
|
+
const pl = await connectClient(flags);
|
|
15
|
+
try {
|
|
16
|
+
const logins = (await pl.listUsers()).map((u) => u.login).sort();
|
|
17
|
+
if (flags.format === "json") outputJson(logins);
|
|
18
|
+
else if (flags.format === "text") outputText(logins.join("\n"));
|
|
19
|
+
else
|
|
20
|
+
outputCsv(
|
|
21
|
+
["login"],
|
|
22
|
+
logins.map((l) => [l]),
|
|
23
|
+
);
|
|
24
|
+
} finally {
|
|
25
|
+
await pl.close();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return cmd;
|
|
30
|
+
}
|
package/src/cmd-opts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
|
+
import type { OutputFormat } from "./output";
|
|
2
3
|
|
|
3
4
|
/** Add one or more groups of options to a command, preserving group order. */
|
|
4
5
|
export function addOptions(cmd: Command, ...optionGroups: Option[][]): Command {
|
|
@@ -8,11 +9,16 @@ export function addOptions(cmd: Command, ...optionGroups: Option[][]): Command {
|
|
|
8
9
|
return cmd;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export const GlobalOptions = (
|
|
12
|
+
export const GlobalOptions = (
|
|
13
|
+
formatDefault: OutputFormat = "text",
|
|
14
|
+
allowedFormats: OutputFormat[] = ["text", "json"],
|
|
15
|
+
): Option[] => [
|
|
12
16
|
new Option("-a, --address <url>", "Platforma server address")
|
|
13
17
|
.env("PL_ADDRESS")
|
|
14
18
|
.makeOptionMandatory(),
|
|
15
|
-
new Option("-f, --format <format>", "Output format")
|
|
19
|
+
new Option("-f, --format <format>", "Output format")
|
|
20
|
+
.choices(allowedFormats)
|
|
21
|
+
.default(formatDefault),
|
|
16
22
|
];
|
|
17
23
|
|
|
18
24
|
export const UserAuthOptions = (): Option[] => [
|
package/src/output.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type OutputFormat = "text" | "json";
|
|
1
|
+
export type OutputFormat = "text" | "json" | "csv";
|
|
2
2
|
|
|
3
3
|
/** Outputs a line of text to stdout. */
|
|
4
4
|
export function outputText(message: string): void {
|
|
@@ -10,6 +10,13 @@ export function outputJson(data: unknown): void {
|
|
|
10
10
|
console.log(JSON.stringify(data, null, 2));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Outputs a header row + data rows as RFC-4180-ish CSV to stdout. */
|
|
14
|
+
export function outputCsv(headers: string[], rows: string[][]): void {
|
|
15
|
+
const esc = (v: string) => (/[",\r\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v);
|
|
16
|
+
const lines = [headers, ...rows].map((r) => r.map(esc).join(","));
|
|
17
|
+
console.log(lines.join("\n"));
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/** Formats a table as aligned text columns. */
|
|
14
21
|
export function formatTable(headers: string[], rows: string[][]): string {
|
|
15
22
|
const colWidths = headers.map((h, i) =>
|