@platforma-sdk/pl-cli 0.2.19 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
- import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./output-INk3CKvr.js";
1
+ import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./output-CXaoTHzT.js";
2
2
  import { Args as g, Command as _, Flags as v } from "@oclif/core";
3
3
  import { field as y, toGlobalResourceId as b } from "@milaboratories/pl-client";
4
4
  import { ProjectMetaKey as x } from "@milaboratories/pl-middle-layer";
5
5
  import { randomUUID as S } from "node:crypto";
6
6
  import { createInterface as C } from "node:readline";
7
- const w = {
7
+ //#region src/cmd-opts.ts
8
+ var w = {
8
9
  address: v.string({
9
10
  char: "a",
10
11
  summary: "Platforma server address",
@@ -53,19 +54,13 @@ const w = {
53
54
  summary: "Operate on this user's data (requires admin credentials)",
54
55
  env: "PL_TARGET_USER"
55
56
  })
56
- };
57
- /** Base command with dual-mode connection: user auth or admin + target-user. */
58
- var O = class extends _ {
57
+ }, O = class extends _ {
59
58
  static baseFlags = {
60
59
  ...w,
61
60
  ...T,
62
61
  ...D
63
62
  };
64
63
  _pl;
65
- /**
66
- * Low-level: get an authenticated PlClient without resolving a project list.
67
- * Use this for commands that navigate to multiple users (e.g. admin copy-project).
68
- */
69
64
  async connectClient(e) {
70
65
  if (this._pl) throw Error("connectClient() called twice");
71
66
  return e["admin-user"] && e["admin-password"] ? this._pl = await a({
@@ -78,11 +73,6 @@ var O = class extends _ {
78
73
  password: e.password
79
74
  }), this._pl;
80
75
  }
81
- /**
82
- * Connect and resolve the project list for a single user.
83
- * In admin mode (--admin-user + --admin-password + --target-user), operates on the target user's data.
84
- * In user mode, operates on the authenticated user's own data.
85
- */
86
76
  async connect(e) {
87
77
  let t = !!e["admin-user"], n = !!e["admin-password"], i = !!e["target-user"];
88
78
  if (i && !(t && n)) throw Error("--target-user requires --admin-user and --admin-password");
@@ -96,213 +86,213 @@ var O = class extends _ {
96
86
  async finally(e) {
97
87
  this._pl && await this._pl.close();
98
88
  }
99
- }, k = class e extends O {
100
- static description = "List all projects for the authenticated user.";
101
- static flags = { ...O.baseFlags };
102
- async run() {
103
- let { flags: t } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(t), a = await n(r, i);
104
- if (t.format === "json") f(a.map((e) => ({
105
- id: e.id,
106
- rid: e.rid,
107
- label: e.label,
108
- created: e.created.toISOString(),
109
- lastModified: e.lastModified.toISOString()
110
- })));
111
- else {
112
- if (a.length === 0) {
113
- o("No projects found.");
114
- return;
89
+ }, k = {
90
+ "project:list": class e extends O {
91
+ static description = "List all projects for the authenticated user.";
92
+ static flags = { ...O.baseFlags };
93
+ async run() {
94
+ let { flags: t } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(t), a = await n(r, i);
95
+ if (t.format === "json") f(a.map((e) => ({
96
+ id: e.id,
97
+ rid: e.rid,
98
+ label: e.label,
99
+ created: e.created.toISOString(),
100
+ lastModified: e.lastModified.toISOString()
101
+ })));
102
+ else {
103
+ if (a.length === 0) {
104
+ o("No projects found.");
105
+ return;
106
+ }
107
+ o(l([
108
+ "ID",
109
+ "LABEL",
110
+ "CREATED",
111
+ "LAST MODIFIED"
112
+ ], a.map((e) => [
113
+ e.id.substring(0, 8) + "...",
114
+ e.label,
115
+ m(e.created),
116
+ m(e.lastModified)
117
+ ]))), o(`\n${a.length} project(s)`);
115
118
  }
116
- o(l([
117
- "ID",
118
- "LABEL",
119
- "CREATED",
120
- "LAST MODIFIED"
121
- ], a.map((e) => [
122
- e.id.substring(0, 8) + "...",
123
- e.label,
124
- m(e.created),
125
- m(e.lastModified)
126
- ]))), o(`\n${a.length} project(s)`);
127
119
  }
128
- }
129
- }, A = class e extends O {
130
- static description = "Show detailed information about a project.";
131
- static args = { project: g.string({
132
- description: "Project ID or label",
133
- required: !0
134
- }) };
135
- static flags = { ...O.baseFlags };
136
- async run() {
137
- let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a } = await c(r, i, t.project), l = await s(r, i, a);
138
- if (n.format === "json") f({
139
- id: l.id,
140
- rid: l.rid,
141
- label: l.label,
142
- schemaVersion: l.schemaVersion,
143
- blockCount: l.blockCount,
144
- blockIds: l.blockIds,
145
- created: l.created.toISOString(),
146
- lastModified: l.lastModified.toISOString()
147
- });
148
- else {
149
- if (o(`Project: ${l.label}`), o(`ID: ${l.id}`), o(`RID: ${l.rid}`), o(`Schema: ${l.schemaVersion ?? "(unknown)"}`), o(`Blocks: ${l.blockCount}`), l.blockIds.length > 0) for (let e of l.blockIds) o(` - ${e}`);
150
- o(`Created: ${m(l.created)}`), o(`Last Modified: ${m(l.lastModified)}`);
151
- }
152
- }
153
- }, j = class n extends O {
154
- static description = "Duplicate a project within the same user. Auto-renames on collision by default.";
155
- static args = { project: g.string({
156
- description: "Project ID or label",
157
- required: !0
158
- }) };
159
- static flags = {
160
- ...O.baseFlags,
161
- name: v.string({
162
- char: "n",
163
- summary: "Name for the duplicate",
164
- helpValue: "<name>"
165
- }),
166
- "auto-rename": v.boolean({
167
- summary: "Auto-rename on collision (default: true)",
168
- default: !0,
169
- allowNo: !0
170
- })
171
- };
172
- async run() {
173
- let { args: r, flags: i } = await this.parse(n), { pl: a, projectListRid: s } = await this.connect(i), { rid: l } = await c(a, s, r.project), u = S(), d = await a.withWriteTx("duplicateProject", async (n) => {
174
- let r = await n.getKValueString(l, x), a = JSON.parse(r).label, o = await t(n, s), c;
175
- if (i.name) {
176
- if (!i["auto-rename"] && o.includes(i.name)) throw Error(`Project name "${i.name}" already exists.`);
177
- c = o.includes(i.name) ? e(i.name, o) : i.name;
178
- } else c = e(a, o);
179
- let d = await p(n, l, { label: c });
180
- return n.createField(y(s, u), "Dynamic", d), await n.commit(), {
181
- rid: await b(d),
182
- label: c
183
- };
184
- });
185
- i.format === "json" ? f({
186
- id: u,
187
- rid: String(d.rid),
188
- label: d.label
189
- }) : o(`Duplicated project as "${d.label}" (id: ${u})`);
190
- }
191
- }, M = class e extends O {
192
- static description = "Rename a project.";
193
- static args = { project: g.string({
194
- description: "Project ID or label",
195
- required: !0
196
- }) };
197
- static flags = {
198
- ...O.baseFlags,
199
- name: v.string({
200
- char: "n",
201
- summary: "New name for the project",
202
- helpValue: "<name>",
120
+ },
121
+ "project:info": class e extends O {
122
+ static description = "Show detailed information about a project.";
123
+ static args = { project: g.string({
124
+ description: "Project ID or label",
203
125
  required: !0
204
- })
205
- };
206
- async run() {
207
- let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a, rid: s } = await c(r, i, t.project);
208
- await d(r, s, n.name), n.format === "json" ? f({
209
- id: a,
210
- rid: String(s),
211
- label: n.name
212
- }) : o(`Renamed project to "${n.name}"`);
213
- }
214
- }, N = class e extends O {
215
- static description = "Delete a project. This permanently destroys all project data.";
216
- static args = { project: g.string({
217
- description: "Project ID or label",
218
- required: !0
219
- }) };
220
- static flags = {
221
- ...O.baseFlags,
222
- force: v.boolean({
223
- summary: "Skip confirmation",
224
- default: !1
225
- })
226
- };
227
- async run() {
228
- let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a } = await c(r, i, t.project), l = await s(r, i, a);
229
- if (!n.force) {
230
- let e = C({
231
- input: process.stdin,
232
- output: process.stderr
126
+ }) };
127
+ static flags = { ...O.baseFlags };
128
+ async run() {
129
+ let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a } = await c(r, i, t.project), l = await s(r, i, a);
130
+ if (n.format === "json") f({
131
+ id: l.id,
132
+ rid: l.rid,
133
+ label: l.label,
134
+ schemaVersion: l.schemaVersion,
135
+ blockCount: l.blockCount,
136
+ blockIds: l.blockIds,
137
+ created: l.created.toISOString(),
138
+ lastModified: l.lastModified.toISOString()
233
139
  });
234
- try {
235
- if ((await new Promise((t) => {
236
- e.question(`Delete project "${l.label}" (${l.blockCount} blocks)? [y/N] `, t);
237
- })).toLowerCase() !== "y") {
238
- o("Aborted.");
239
- return;
240
- }
241
- } finally {
242
- e.close();
140
+ else {
141
+ if (o(`Project: ${l.label}`), o(`ID: ${l.id}`), o(`RID: ${l.rid}`), o(`Schema: ${l.schemaVersion ?? "(unknown)"}`), o(`Blocks: ${l.blockCount}`), l.blockIds.length > 0) for (let e of l.blockIds) o(` - ${e}`);
142
+ o(`Created: ${m(l.created)}`), o(`Last Modified: ${m(l.lastModified)}`);
243
143
  }
244
144
  }
245
- await u(r, i, a), n.format === "json" ? f({
246
- deleted: !0,
247
- id: a,
248
- label: l.label
249
- }) : o(`Deleted project "${l.label}"`);
250
- }
251
- }, P = class n extends O {
252
- static description = "Copy a project from one user to another. Requires admin/controller credentials.";
253
- static flags = {
254
- ...w,
255
- ...E,
256
- "source-user": v.string({
257
- summary: "Username of the source project owner",
145
+ },
146
+ "project:duplicate": class n extends O {
147
+ static description = "Duplicate a project within the same user. Auto-renames on collision by default.";
148
+ static args = { project: g.string({
149
+ description: "Project ID or label",
258
150
  required: !0
259
- }),
260
- "source-project": v.string({
261
- summary: "Source project ID or label",
151
+ }) };
152
+ static flags = {
153
+ ...O.baseFlags,
154
+ name: v.string({
155
+ char: "n",
156
+ summary: "Name for the duplicate",
157
+ helpValue: "<name>"
158
+ }),
159
+ "auto-rename": v.boolean({
160
+ summary: "Auto-rename on collision (default: true)",
161
+ default: !0,
162
+ allowNo: !0
163
+ })
164
+ };
165
+ async run() {
166
+ let { args: r, flags: i } = await this.parse(n), { pl: a, projectListRid: s } = await this.connect(i), { rid: l } = await c(a, s, r.project), u = S(), d = await a.withWriteTx("duplicateProject", async (n) => {
167
+ let r = await n.getKValueString(l, x), a = JSON.parse(r).label, o = await t(n, s), c;
168
+ if (i.name) {
169
+ if (!i["auto-rename"] && o.includes(i.name)) throw Error(`Project name "${i.name}" already exists.`);
170
+ c = o.includes(i.name) ? e(i.name, o) : i.name;
171
+ } else c = e(a, o);
172
+ let d = await p(n, l, { label: c });
173
+ return n.createField(y(s, u), "Dynamic", d), await n.commit(), {
174
+ rid: await b(d),
175
+ label: c
176
+ };
177
+ });
178
+ i.format === "json" ? f({
179
+ id: u,
180
+ rid: String(d.rid),
181
+ label: d.label
182
+ }) : o(`Duplicated project as "${d.label}" (id: ${u})`);
183
+ }
184
+ },
185
+ "project:rename": class e extends O {
186
+ static description = "Rename a project.";
187
+ static args = { project: g.string({
188
+ description: "Project ID or label",
262
189
  required: !0
263
- }),
264
- "target-user": v.string({ summary: "Username of the target user (defaults to source-user for same-user copy)" }),
265
- name: v.string({
266
- char: "n",
267
- summary: "Name for the copied project",
268
- helpValue: "<name>"
269
- }),
270
- "auto-rename": v.boolean({
271
- summary: "Auto-rename on collision (default: true)",
272
- default: !0,
273
- allowNo: !0
274
- })
275
- };
276
- async run() {
277
- let { flags: i } = await this.parse(n), a = await this.connectClient(i), s = i["target-user"] ?? i["source-user"], l = await r(a, i["source-user"]), { rid: u } = await c(a, l.projectListRid, i["source-project"]), d = s === i["source-user"] ? l : await r(a, s), m = S(), h = await a.withWriteTx("adminCopyProject", async (n) => {
278
- let r = await n.getKValueString(u, x), a = JSON.parse(r).label, o = await t(n, d.projectListRid), s = i.name ?? a;
279
- if (o.includes(s)) {
280
- if (!i["auto-rename"]) throw Error(`Project name "${s}" already exists for target user.`);
281
- s = e(i.name ?? a, o);
190
+ }) };
191
+ static flags = {
192
+ ...O.baseFlags,
193
+ name: v.string({
194
+ char: "n",
195
+ summary: "New name for the project",
196
+ helpValue: "<name>",
197
+ required: !0
198
+ })
199
+ };
200
+ async run() {
201
+ let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a, rid: s } = await c(r, i, t.project);
202
+ await d(r, s, n.name), n.format === "json" ? f({
203
+ id: a,
204
+ rid: String(s),
205
+ label: n.name
206
+ }) : o(`Renamed project to "${n.name}"`);
207
+ }
208
+ },
209
+ "project:delete": class e extends O {
210
+ static description = "Delete a project. This permanently destroys all project data.";
211
+ static args = { project: g.string({
212
+ description: "Project ID or label",
213
+ required: !0
214
+ }) };
215
+ static flags = {
216
+ ...O.baseFlags,
217
+ force: v.boolean({
218
+ summary: "Skip confirmation",
219
+ default: !1
220
+ })
221
+ };
222
+ async run() {
223
+ let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a } = await c(r, i, t.project), l = await s(r, i, a);
224
+ if (!n.force) {
225
+ let e = C({
226
+ input: process.stdin,
227
+ output: process.stderr
228
+ });
229
+ try {
230
+ if ((await new Promise((t) => {
231
+ e.question(`Delete project "${l.label}" (${l.blockCount} blocks)? [y/N] `, t);
232
+ })).toLowerCase() !== "y") {
233
+ o("Aborted.");
234
+ return;
235
+ }
236
+ } finally {
237
+ e.close();
238
+ }
282
239
  }
283
- let c = await p(n, u, { label: s });
284
- return n.createField(y(d.projectListRid, m), "Dynamic", c), await n.commit(), {
285
- rid: await b(c),
286
- label: s
287
- };
288
- });
289
- i.format === "json" ? f({
290
- id: m,
291
- rid: String(h.rid),
292
- label: h.label,
293
- sourceUser: i["source-user"],
294
- targetUser: s
295
- }) : o(`Copied project from ${i["source-user"]} to ${s} as "${h.label}" (id: ${m})`);
240
+ await u(r, i, a), n.format === "json" ? f({
241
+ deleted: !0,
242
+ id: a,
243
+ label: l.label
244
+ }) : o(`Deleted project "${l.label}"`);
245
+ }
246
+ },
247
+ "admin:copy-project": class n extends O {
248
+ static description = "Copy a project from one user to another. Requires admin/controller credentials.";
249
+ static flags = {
250
+ ...w,
251
+ ...E,
252
+ "source-user": v.string({
253
+ summary: "Username of the source project owner",
254
+ required: !0
255
+ }),
256
+ "source-project": v.string({
257
+ summary: "Source project ID or label",
258
+ required: !0
259
+ }),
260
+ "target-user": v.string({ summary: "Username of the target user (defaults to source-user for same-user copy)" }),
261
+ name: v.string({
262
+ char: "n",
263
+ summary: "Name for the copied project",
264
+ helpValue: "<name>"
265
+ }),
266
+ "auto-rename": v.boolean({
267
+ summary: "Auto-rename on collision (default: true)",
268
+ default: !0,
269
+ allowNo: !0
270
+ })
271
+ };
272
+ async run() {
273
+ let { flags: i } = await this.parse(n), a = await this.connectClient(i), s = i["target-user"] ?? i["source-user"], l = await r(a, i["source-user"]), { rid: u } = await c(a, l.projectListRid, i["source-project"]), d = s === i["source-user"] ? l : await r(a, s), m = S(), h = await a.withWriteTx("adminCopyProject", async (n) => {
274
+ let r = await n.getKValueString(u, x), a = JSON.parse(r).label, o = await t(n, d.projectListRid), s = i.name ?? a;
275
+ if (o.includes(s)) {
276
+ if (!i["auto-rename"]) throw Error(`Project name "${s}" already exists for target user.`);
277
+ s = e(i.name ?? a, o);
278
+ }
279
+ let c = await p(n, u, { label: s });
280
+ return n.createField(y(d.projectListRid, m), "Dynamic", c), await n.commit(), {
281
+ rid: await b(c),
282
+ label: s
283
+ };
284
+ });
285
+ i.format === "json" ? f({
286
+ id: m,
287
+ rid: String(h.rid),
288
+ label: h.label,
289
+ sourceUser: i["source-user"],
290
+ targetUser: s
291
+ }) : o(`Copied project from ${i["source-user"]} to ${s} as "${h.label}" (id: ${m})`);
292
+ }
296
293
  }
297
294
  };
298
- const F = {
299
- "project:list": k,
300
- "project:info": A,
301
- "project:duplicate": j,
302
- "project:rename": M,
303
- "project:delete": N,
304
- "admin:copy-project": P
305
- };
306
- export { F as COMMANDS };
295
+ //#endregion
296
+ export { k as COMMANDS };
307
297
 
308
298
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cmd-opts.ts","../src/base_command.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/index.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nexport const GlobalFlags = {\n address: Flags.string({\n char: \"a\",\n summary: \"Platforma server address\",\n helpValue: \"<url>\",\n env: \"PL_ADDRESS\",\n required: true,\n }),\n format: Flags.string({\n char: \"f\",\n summary: \"Output format\",\n options: [\"text\", \"json\"],\n default: \"text\",\n }),\n};\n\nexport const UserAuthFlags = {\n user: Flags.string({\n char: \"u\",\n summary: \"Username for authentication\",\n env: \"PL_USER\",\n }),\n password: Flags.string({\n char: \"p\",\n summary: \"Password for authentication\",\n env: \"PL_PASSWORD\",\n }),\n};\n\n/** Admin credentials only (for purely admin commands like copy-project). */\nexport const AdminAuthFlags = {\n \"admin-user\": Flags.string({\n summary: \"Admin/controller username\",\n env: \"PL_ADMIN_USER\",\n required: true,\n }),\n \"admin-password\": Flags.string({\n summary: \"Admin/controller password\",\n env: \"PL_ADMIN_PASSWORD\",\n required: true,\n }),\n};\n\n/** Admin credentials + target user (for regular commands that can optionally operate on another user). */\nexport const AdminTargetFlags = {\n \"admin-user\": Flags.string({\n summary: \"Admin/controller username (enables admin mode)\",\n env: \"PL_ADMIN_USER\",\n }),\n \"admin-password\": Flags.string({\n summary: \"Admin/controller password\",\n env: \"PL_ADMIN_PASSWORD\",\n }),\n \"target-user\": Flags.string({\n summary: \"Operate on this user's data (requires admin credentials)\",\n env: \"PL_TARGET_USER\",\n }),\n};\n","import { Command } from \"@oclif/core\";\nimport type { PlClient, ResourceId } from \"@milaboratories/pl-client\";\nimport { createPlConnection, createAdminPlConnection } from \"./connection\";\nimport { getProjectListRid, navigateToUserRoot } from \"./project_ops\";\nimport { GlobalFlags, UserAuthFlags, AdminTargetFlags } from \"./cmd-opts\";\n\n/** Base command with dual-mode connection: user auth or admin + target-user. */\nexport abstract class PlCommand extends Command {\n static baseFlags = {\n ...GlobalFlags,\n ...UserAuthFlags,\n ...AdminTargetFlags,\n };\n\n private _pl?: PlClient;\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 protected async connectClient(flags: {\n address: string;\n user?: string;\n password?: string;\n \"admin-user\"?: string;\n \"admin-password\"?: string;\n }): Promise<PlClient> {\n if (this._pl) throw new Error(\"connectClient() called twice\");\n\n if (flags[\"admin-user\"] && flags[\"admin-password\"]) {\n this._pl = await createAdminPlConnection({\n address: flags.address,\n adminUser: flags[\"admin-user\"],\n adminPassword: flags[\"admin-password\"],\n });\n } else {\n this._pl = await createPlConnection({\n address: flags.address,\n user: flags.user,\n password: flags.password,\n });\n }\n\n return this._pl;\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 protected async connect(flags: {\n address: string;\n user?: string;\n password?: string;\n \"admin-user\"?: string;\n \"admin-password\"?: string;\n \"target-user\"?: string;\n }): Promise<{ pl: PlClient; projectListRid: ResourceId }> {\n const hasAdminUser = !!flags[\"admin-user\"];\n const hasAdminPassword = !!flags[\"admin-password\"];\n const hasTarget = !!flags[\"target-user\"];\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 this.connectClient(flags);\n\n let projectListRid: ResourceId;\n if (hasTarget) {\n const nav = await navigateToUserRoot(pl, flags[\"target-user\"]!);\n projectListRid = nav.projectListRid;\n } else {\n projectListRid = await getProjectListRid(pl);\n }\n\n return { pl, projectListRid };\n }\n\n protected async finally(_: Error | undefined): Promise<void> {\n if (this._pl) {\n await this._pl.close();\n }\n }\n}\n","import { PlCommand } from \"../../base_command\";\nimport { listProjects } from \"../../project_ops\";\nimport { formatTable, formatDate, outputJson, outputText } from \"../../output\";\n\nexport default class ProjectList extends PlCommand {\n static override description = \"List all projects for the authenticated user.\";\n\n static override flags = {\n ...PlCommand.baseFlags,\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(ProjectList);\n const { pl, projectListRid } = await this.connect(flags);\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: 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\", \"LABEL\", \"CREATED\", \"LAST MODIFIED\"],\n projects.map((p) => [\n p.id.substring(0, 8) + \"...\",\n p.label,\n formatDate(p.created),\n formatDate(p.lastModified),\n ]),\n ),\n );\n outputText(`\\n${projects.length} project(s)`);\n }\n }\n}\n","import { Args } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\n\nexport default class ProjectInfo extends PlCommand {\n static override description = \"Show detailed information about a project.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectInfo);\n const { pl, projectListRid } = await this.connect(flags);\n const { id } = await resolveProject(pl, projectListRid, args.project);\n const info = await getProjectInfo(pl, projectListRid, id);\n\n if (flags.format === \"json\") {\n outputJson({\n id: info.id,\n rid: 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: ${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 }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { field, toGlobalResourceId } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { PlCommand } from \"../../base_command\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectDuplicate extends PlCommand {\n static override description =\n \"Duplicate a project within the same user. Auto-renames on collision by default.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n name: Flags.string({\n char: \"n\",\n summary: \"Name for the duplicate\",\n helpValue: \"<name>\",\n }),\n \"auto-rename\": Flags.boolean({\n summary: \"Auto-rename on collision (default: true)\",\n default: true,\n allowNo: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectDuplicate);\n const { pl, projectListRid } = await this.connect(flags);\n\n const { rid: sourceRid } = await resolveProject(pl, projectListRid, args.project);\n\n const newId = randomUUID();\n\n const newRid = 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[\"auto-rename\"] && 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 return { rid: await toGlobalResourceId(newPrj), label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({ id: newId, rid: String(newRid.rid), label: newRid.label });\n } else {\n outputText(`Duplicated project as \"${newRid.label}\" (id: ${newId})`);\n }\n }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectRename extends PlCommand {\n static override description = \"Rename a project.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n name: Flags.string({\n char: \"n\",\n summary: \"New name for the project\",\n helpValue: \"<name>\",\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectRename);\n const { pl, projectListRid } = await this.connect(flags);\n const { id, rid } = await resolveProject(pl, projectListRid, args.project);\n\n await renameProject(pl, rid, flags.name);\n\n if (flags.format === \"json\") {\n outputJson({ id, rid: String(rid), label: flags.name });\n } else {\n outputText(`Renamed project to \"${flags.name}\"`);\n }\n }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { createInterface } from \"node:readline\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, deleteProject, getProjectInfo } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectDelete extends PlCommand {\n static override description = \"Delete a project. This permanently destroys all project data.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n force: Flags.boolean({\n summary: \"Skip confirmation\",\n default: false,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectDelete);\n const { pl, projectListRid } = await this.connect(flags);\n const { id } = await resolveProject(pl, projectListRid, args.project);\n\n const info = await getProjectInfo(pl, projectListRid, id);\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, id);\n\n if (flags.format === \"json\") {\n outputJson({ deleted: true, id, label: info.label });\n } else {\n outputText(`Deleted project \"${info.label}\"`);\n }\n }\n}\n","import { Flags } from \"@oclif/core\";\nimport { field, toGlobalResourceId } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { PlCommand } from \"../../base_command\";\nimport { GlobalFlags, AdminAuthFlags } 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 class AdminCopyProject extends PlCommand {\n static override description =\n \"Copy a project from one user to another. Requires admin/controller credentials.\";\n\n static override flags = {\n ...GlobalFlags,\n ...AdminAuthFlags,\n \"source-user\": Flags.string({\n summary: \"Username of the source project owner\",\n required: true,\n }),\n \"source-project\": Flags.string({\n summary: \"Source project ID or label\",\n required: true,\n }),\n \"target-user\": Flags.string({\n summary: \"Username of the target user (defaults to source-user for same-user copy)\",\n }),\n name: Flags.string({\n char: \"n\",\n summary: \"Name for the copied project\",\n helpValue: \"<name>\",\n }),\n \"auto-rename\": Flags.boolean({\n summary: \"Auto-rename on collision (default: true)\",\n default: true,\n allowNo: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(AdminCopyProject);\n const pl = await this.connectClient(flags);\n\n const targetUser = flags[\"target-user\"] ?? flags[\"source-user\"];\n\n const source = await navigateToUserRoot(pl, flags[\"source-user\"]);\n const { rid: sourceRid } = await resolveProject(\n pl,\n source.projectListRid,\n flags[\"source-project\"],\n );\n\n const target =\n targetUser === flags[\"source-user\"] ? 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[\"auto-rename\"]) {\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 return { rid: await toGlobalResourceId(newPrj), label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({\n id: newId,\n rid: String(result.rid),\n label: result.label,\n sourceUser: flags[\"source-user\"],\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags[\"source-user\"]} to ${targetUser} as \"${result.label}\" (id: ${newId})`,\n );\n }\n }\n}\n","// Command index for oclif. Run `pnpm run oclif:index` to regenerate.\n\nimport ProjectList from \"./project/list\";\nimport ProjectInfo from \"./project/info\";\nimport ProjectDuplicate from \"./project/duplicate\";\nimport ProjectRename from \"./project/rename\";\nimport ProjectDelete from \"./project/delete\";\nimport AdminCopyProject from \"./admin/copy-project\";\n\nexport const COMMANDS = {\n \"project:list\": ProjectList,\n \"project:info\": ProjectInfo,\n \"project:duplicate\": ProjectDuplicate,\n \"project:rename\": ProjectRename,\n \"project:delete\": ProjectDelete,\n \"admin:copy-project\": AdminCopyProject,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cmd-opts.ts","../src/base_command.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/index.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nexport const GlobalFlags = {\n address: Flags.string({\n char: \"a\",\n summary: \"Platforma server address\",\n helpValue: \"<url>\",\n env: \"PL_ADDRESS\",\n required: true,\n }),\n format: Flags.string({\n char: \"f\",\n summary: \"Output format\",\n options: [\"text\", \"json\"],\n default: \"text\",\n }),\n};\n\nexport const UserAuthFlags = {\n user: Flags.string({\n char: \"u\",\n summary: \"Username for authentication\",\n env: \"PL_USER\",\n }),\n password: Flags.string({\n char: \"p\",\n summary: \"Password for authentication\",\n env: \"PL_PASSWORD\",\n }),\n};\n\n/** Admin credentials only (for purely admin commands like copy-project). */\nexport const AdminAuthFlags = {\n \"admin-user\": Flags.string({\n summary: \"Admin/controller username\",\n env: \"PL_ADMIN_USER\",\n required: true,\n }),\n \"admin-password\": Flags.string({\n summary: \"Admin/controller password\",\n env: \"PL_ADMIN_PASSWORD\",\n required: true,\n }),\n};\n\n/** Admin credentials + target user (for regular commands that can optionally operate on another user). */\nexport const AdminTargetFlags = {\n \"admin-user\": Flags.string({\n summary: \"Admin/controller username (enables admin mode)\",\n env: \"PL_ADMIN_USER\",\n }),\n \"admin-password\": Flags.string({\n summary: \"Admin/controller password\",\n env: \"PL_ADMIN_PASSWORD\",\n }),\n \"target-user\": Flags.string({\n summary: \"Operate on this user's data (requires admin credentials)\",\n env: \"PL_TARGET_USER\",\n }),\n};\n","import { Command } from \"@oclif/core\";\nimport type { PlClient, ResourceId } from \"@milaboratories/pl-client\";\nimport { createPlConnection, createAdminPlConnection } from \"./connection\";\nimport { getProjectListRid, navigateToUserRoot } from \"./project_ops\";\nimport { GlobalFlags, UserAuthFlags, AdminTargetFlags } from \"./cmd-opts\";\n\n/** Base command with dual-mode connection: user auth or admin + target-user. */\nexport abstract class PlCommand extends Command {\n static baseFlags = {\n ...GlobalFlags,\n ...UserAuthFlags,\n ...AdminTargetFlags,\n };\n\n private _pl?: PlClient;\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 protected async connectClient(flags: {\n address: string;\n user?: string;\n password?: string;\n \"admin-user\"?: string;\n \"admin-password\"?: string;\n }): Promise<PlClient> {\n if (this._pl) throw new Error(\"connectClient() called twice\");\n\n if (flags[\"admin-user\"] && flags[\"admin-password\"]) {\n this._pl = await createAdminPlConnection({\n address: flags.address,\n adminUser: flags[\"admin-user\"],\n adminPassword: flags[\"admin-password\"],\n });\n } else {\n this._pl = await createPlConnection({\n address: flags.address,\n user: flags.user,\n password: flags.password,\n });\n }\n\n return this._pl;\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 protected async connect(flags: {\n address: string;\n user?: string;\n password?: string;\n \"admin-user\"?: string;\n \"admin-password\"?: string;\n \"target-user\"?: string;\n }): Promise<{ pl: PlClient; projectListRid: ResourceId }> {\n const hasAdminUser = !!flags[\"admin-user\"];\n const hasAdminPassword = !!flags[\"admin-password\"];\n const hasTarget = !!flags[\"target-user\"];\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 this.connectClient(flags);\n\n let projectListRid: ResourceId;\n if (hasTarget) {\n const nav = await navigateToUserRoot(pl, flags[\"target-user\"]!);\n projectListRid = nav.projectListRid;\n } else {\n projectListRid = await getProjectListRid(pl);\n }\n\n return { pl, projectListRid };\n }\n\n protected async finally(_: Error | undefined): Promise<void> {\n if (this._pl) {\n await this._pl.close();\n }\n }\n}\n","import { PlCommand } from \"../../base_command\";\nimport { listProjects } from \"../../project_ops\";\nimport { formatTable, formatDate, outputJson, outputText } from \"../../output\";\n\nexport default class ProjectList extends PlCommand {\n static override description = \"List all projects for the authenticated user.\";\n\n static override flags = {\n ...PlCommand.baseFlags,\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(ProjectList);\n const { pl, projectListRid } = await this.connect(flags);\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: 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\", \"LABEL\", \"CREATED\", \"LAST MODIFIED\"],\n projects.map((p) => [\n p.id.substring(0, 8) + \"...\",\n p.label,\n formatDate(p.created),\n formatDate(p.lastModified),\n ]),\n ),\n );\n outputText(`\\n${projects.length} project(s)`);\n }\n }\n}\n","import { Args } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\n\nexport default class ProjectInfo extends PlCommand {\n static override description = \"Show detailed information about a project.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectInfo);\n const { pl, projectListRid } = await this.connect(flags);\n const { id } = await resolveProject(pl, projectListRid, args.project);\n const info = await getProjectInfo(pl, projectListRid, id);\n\n if (flags.format === \"json\") {\n outputJson({\n id: info.id,\n rid: 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: ${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 }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { field, toGlobalResourceId } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { PlCommand } from \"../../base_command\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectDuplicate extends PlCommand {\n static override description =\n \"Duplicate a project within the same user. Auto-renames on collision by default.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n name: Flags.string({\n char: \"n\",\n summary: \"Name for the duplicate\",\n helpValue: \"<name>\",\n }),\n \"auto-rename\": Flags.boolean({\n summary: \"Auto-rename on collision (default: true)\",\n default: true,\n allowNo: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectDuplicate);\n const { pl, projectListRid } = await this.connect(flags);\n\n const { rid: sourceRid } = await resolveProject(pl, projectListRid, args.project);\n\n const newId = randomUUID();\n\n const newRid = 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[\"auto-rename\"] && 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 return { rid: await toGlobalResourceId(newPrj), label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({ id: newId, rid: String(newRid.rid), label: newRid.label });\n } else {\n outputText(`Duplicated project as \"${newRid.label}\" (id: ${newId})`);\n }\n }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectRename extends PlCommand {\n static override description = \"Rename a project.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n name: Flags.string({\n char: \"n\",\n summary: \"New name for the project\",\n helpValue: \"<name>\",\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectRename);\n const { pl, projectListRid } = await this.connect(flags);\n const { id, rid } = await resolveProject(pl, projectListRid, args.project);\n\n await renameProject(pl, rid, flags.name);\n\n if (flags.format === \"json\") {\n outputJson({ id, rid: String(rid), label: flags.name });\n } else {\n outputText(`Renamed project to \"${flags.name}\"`);\n }\n }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { createInterface } from \"node:readline\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, deleteProject, getProjectInfo } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default class ProjectDelete extends PlCommand {\n static override description = \"Delete a project. This permanently destroys all project data.\";\n\n static override args = {\n project: Args.string({\n description: \"Project ID or label\",\n required: true,\n }),\n };\n\n static override flags = {\n ...PlCommand.baseFlags,\n force: Flags.boolean({\n summary: \"Skip confirmation\",\n default: false,\n }),\n };\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(ProjectDelete);\n const { pl, projectListRid } = await this.connect(flags);\n const { id } = await resolveProject(pl, projectListRid, args.project);\n\n const info = await getProjectInfo(pl, projectListRid, id);\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, id);\n\n if (flags.format === \"json\") {\n outputJson({ deleted: true, id, label: info.label });\n } else {\n outputText(`Deleted project \"${info.label}\"`);\n }\n }\n}\n","import { Flags } from \"@oclif/core\";\nimport { field, toGlobalResourceId } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { PlCommand } from \"../../base_command\";\nimport { GlobalFlags, AdminAuthFlags } 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 class AdminCopyProject extends PlCommand {\n static override description =\n \"Copy a project from one user to another. Requires admin/controller credentials.\";\n\n static override flags = {\n ...GlobalFlags,\n ...AdminAuthFlags,\n \"source-user\": Flags.string({\n summary: \"Username of the source project owner\",\n required: true,\n }),\n \"source-project\": Flags.string({\n summary: \"Source project ID or label\",\n required: true,\n }),\n \"target-user\": Flags.string({\n summary: \"Username of the target user (defaults to source-user for same-user copy)\",\n }),\n name: Flags.string({\n char: \"n\",\n summary: \"Name for the copied project\",\n helpValue: \"<name>\",\n }),\n \"auto-rename\": Flags.boolean({\n summary: \"Auto-rename on collision (default: true)\",\n default: true,\n allowNo: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(AdminCopyProject);\n const pl = await this.connectClient(flags);\n\n const targetUser = flags[\"target-user\"] ?? flags[\"source-user\"];\n\n const source = await navigateToUserRoot(pl, flags[\"source-user\"]);\n const { rid: sourceRid } = await resolveProject(\n pl,\n source.projectListRid,\n flags[\"source-project\"],\n );\n\n const target =\n targetUser === flags[\"source-user\"] ? 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[\"auto-rename\"]) {\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 return { rid: await toGlobalResourceId(newPrj), label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({\n id: newId,\n rid: String(result.rid),\n label: result.label,\n sourceUser: flags[\"source-user\"],\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags[\"source-user\"]} to ${targetUser} as \"${result.label}\" (id: ${newId})`,\n );\n }\n }\n}\n","// Command index for oclif. Run `pnpm run oclif:index` to regenerate.\n\nimport ProjectList from \"./project/list\";\nimport ProjectInfo from \"./project/info\";\nimport ProjectDuplicate from \"./project/duplicate\";\nimport ProjectRename from \"./project/rename\";\nimport ProjectDelete from \"./project/delete\";\nimport AdminCopyProject from \"./admin/copy-project\";\n\nexport const COMMANDS = {\n \"project:list\": ProjectList,\n \"project:info\": ProjectInfo,\n \"project:duplicate\": ProjectDuplicate,\n \"project:rename\": ProjectRename,\n \"project:delete\": ProjectDelete,\n \"admin:copy-project\": AdminCopyProject,\n};\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./output-INk3CKvr.js";
1
+ import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./output-CXaoTHzT.js";
2
2
  export { a as createAdminPlConnection, i as createPlConnection, e as deduplicateName, u as deleteProject, p as duplicateProject, m as formatDate, l as formatTable, t as getExistingLabelsInTx, s as getProjectInfo, h as getProjectListRid, n as listProjects, r as navigateToUserRoot, f as outputJson, o as outputText, d as renameProject, c as resolveProject };
@@ -1,7 +1,7 @@
1
1
  import { PlClient as e, UnauthenticatedPlClient as t, field as n, isNullResourceId as r, plAddressToConfig as i } from "@milaboratories/pl-client";
2
2
  import { ProjectCreatedTimestamp as a, ProjectLastModifiedTimestamp as o, ProjectMetaKey as s, ProjectStructureKey as c, ProjectsField as l, SchemaVersionKey as u, duplicateProject as d } from "@milaboratories/pl-middle-layer";
3
3
  import { createHash as f } from "node:crypto";
4
- /** Creates an authenticated PlClient from address + credentials. */
4
+ //#region src/connection.ts
5
5
  async function p(n) {
6
6
  let r = i(n.address);
7
7
  n.user !== void 0 && (r.user = n.user), n.password !== void 0 && (r.password = n.password);
@@ -12,12 +12,12 @@ async function p(n) {
12
12
  } else o = {};
13
13
  return await e.init(r, { authInformation: o });
14
14
  }
15
- /** Creates a PlClient with admin/controller credentials. */
16
15
  async function m(n) {
17
16
  let r = i(n.address), a = await (await t.build(r)).login(n.adminUser, n.adminPassword);
18
17
  return await e.init(r, { authInformation: a });
19
18
  }
20
- /** List all projects from a project list resource. */
19
+ //#endregion
20
+ //#region src/project_ops.ts
21
21
  async function h(e, t) {
22
22
  return await e.withReadTx("listProjects", async (e) => {
23
23
  let n = await e.getResourceData(t, !0), i = [];
@@ -39,7 +39,6 @@ async function h(e, t) {
39
39
  return i.sort((e, t) => t.lastModified.getTime() - e.lastModified.getTime()), i;
40
40
  });
41
41
  }
42
- /** Get detailed info about a project. */
43
42
  async function g(e, t, i) {
44
43
  return await e.withReadTx("getProjectInfo", async (e) => {
45
44
  let l = await e.getField(n(t, i));
@@ -61,7 +60,6 @@ async function g(e, t, i) {
61
60
  };
62
61
  });
63
62
  }
64
- /** Resolve a project identifier (id or label) to its field ID and ResourceId. */
65
63
  async function _(e, t, n) {
66
64
  return await e.withReadTx("resolveProject", async (e) => {
67
65
  let i = await e.getResourceData(t, !0);
@@ -80,7 +78,6 @@ async function _(e, t, n) {
80
78
  throw Error(`Project "${n}" not found (searched by id and label).`);
81
79
  });
82
80
  }
83
- /** Read all project labels within an existing transaction. */
84
81
  async function v(e, t) {
85
82
  let n = await e.getResourceData(t, !0), i = [];
86
83
  for (let t of n.fields) {
@@ -93,16 +90,11 @@ async function v(e, t) {
93
90
  }
94
91
  return i;
95
92
  }
96
- /**
97
- * Deduplicates a project name against existing labels.
98
- * "X" → "X (Copy)" → "X (Copy 2)" → ...
99
- */
100
93
  function y(e, t) {
101
94
  let n = `${e} (Copy)`, r = 2;
102
95
  for (; t.includes(n);) n = `${e} (Copy ${r})`, r++;
103
96
  return n;
104
97
  }
105
- /** Rename a project (update its label). */
106
98
  async function b(e, t, n) {
107
99
  await e.withWriteTx("renameProject", async (e) => {
108
100
  let r = await e.getKValueString(t, s), i = {
@@ -112,13 +104,11 @@ async function b(e, t, n) {
112
104
  e.setKValue(t, s, JSON.stringify(i)), e.setKValue(t, o, String(Date.now())), await e.commit();
113
105
  });
114
106
  }
115
- /** Delete a project from the project list. */
116
107
  async function x(e, t, r) {
117
108
  await e.withWriteTx("deleteProject", async (e) => {
118
109
  e.removeField(n(t, r)), await e.commit();
119
110
  });
120
111
  }
121
- /** Get the project list ResourceId for the connected user. */
122
112
  async function S(e) {
123
113
  return await e.withReadTx("getProjectList", async (e) => {
124
114
  let t = await e.getField({
@@ -129,10 +119,6 @@ async function S(e) {
129
119
  return t.value;
130
120
  });
131
121
  }
132
- /**
133
- * Navigates to a specific user's project list resource ID.
134
- * Computes SHA256(username) to find the user's root, then reads the "projects" field.
135
- */
136
122
  async function C(e, t) {
137
123
  let n = f("sha256").update(t).digest("hex");
138
124
  return await e.withReadTx("navigateToUserRoot", async (e) => {
@@ -148,15 +134,14 @@ async function C(e, t) {
148
134
  };
149
135
  });
150
136
  }
151
- /** Outputs a line of text to stdout. */
137
+ //#endregion
138
+ //#region src/output.ts
152
139
  function w(e) {
153
140
  console.log(e);
154
141
  }
155
- /** Outputs data as formatted JSON to stdout. */
156
142
  function T(e) {
157
143
  console.log(JSON.stringify(e, null, 2));
158
144
  }
159
- /** Formats a table as aligned text columns. */
160
145
  function E(e, t) {
161
146
  let n = e.map((e, n) => Math.max(e.length, ...t.map((e) => (e[n] ?? "").length))), r = n.map((e) => "-".repeat(e)).join(" ");
162
147
  return [
@@ -165,10 +150,10 @@ function E(e, t) {
165
150
  ...t.map((e) => e.map((e, t) => (e ?? "").padEnd(n[t])).join(" "))
166
151
  ].join("\n");
167
152
  }
168
- /** Formats a Date as a short human-readable string. */
169
153
  function D(e) {
170
154
  return e.getTime() === 0 ? "(unknown)" : e.toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
171
155
  }
156
+ //#endregion
172
157
  export { y as a, v as c, h as d, C as f, p as g, m as h, w as i, g as l, _ as m, E as n, x as o, b as p, T as r, d as s, D as t, S as u };
173
158
 
174
- //# sourceMappingURL=output-INk3CKvr.js.map
159
+ //# sourceMappingURL=output-CXaoTHzT.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"output-INk3CKvr.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, ResourceId, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, isNullResourceId } 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\";\nimport { createHash } from \"node:crypto\";\n\nexport interface ProjectEntry {\n id: string;\n rid: string;\n label: string;\n created: Date;\n lastModified: Date;\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: ResourceId,\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 (isNullResourceId(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 entries.push({\n id: f.name,\n rid: String(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 projectListRid: ResourceId,\n projectId: string,\n): Promise<ProjectInfo> {\n return await pl.withReadTx(\"getProjectInfo\", async (tx) => {\n const fieldData = await tx.getField(field(projectListRid, projectId));\n if (isNullResourceId(fieldData.value)) {\n throw new Error(`Project \"${projectId}\" not found.`);\n }\n\n const rid = fieldData.value;\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: String(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 field ID and ResourceId. */\nexport async function resolveProject(\n pl: PlClient,\n projectListRid: ResourceId,\n identifier: string,\n): Promise<{ id: string; rid: ResourceId }> {\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 (isNullResourceId(f.value)) continue;\n if (f.name === identifier) {\n return { id: f.name, rid: f.value };\n }\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: f.name, rid: f.value };\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: ResourceId,\n): Promise<string[]> {\n const data = await tx.getResourceData(projectListRid, true);\n const labels: string[] = [];\n for (const f of data.fields) {\n if (isNullResourceId(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: ResourceId,\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: ResourceId,\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: ResourceId,\n projectId: string,\n): Promise<void> {\n await pl.withWriteTx(\"deleteProject\", async (tx) => {\n tx.removeField(field(projectListRid, projectId));\n await tx.commit();\n });\n}\n\n/** Get the project list ResourceId for the connected user. */\nexport async function getProjectListRid(pl: PlClient): Promise<ResourceId> {\n return await pl.withReadTx(\"getProjectList\", async (tx) => {\n const fieldData = await tx.getField({\n resourceId: tx.clientRoot,\n fieldName: ProjectsField,\n });\n if (isNullResourceId(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 * Computes SHA256(username) to find the user's root, then reads the \"projects\" field.\n */\nexport async function navigateToUserRoot(\n pl: PlClient,\n username: string,\n): Promise<{ userRoot: ResourceId; projectListRid: ResourceId }> {\n const rootName = createHash(\"sha256\").update(username).digest(\"hex\");\n\n return await pl.withReadTx(\"navigateToUserRoot\", async (tx) => {\n if (!(await tx.checkResourceNameExists(rootName))) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n\n const userRootRid = await tx.getResourceByName(rootName);\n\n const projectsFieldData = await tx.getField({\n resourceId: userRootRid,\n fieldName: ProjectsField,\n });\n\n if (isNullResourceId(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-CXaoTHzT.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, ResourceId, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, isNullResourceId } 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\";\nimport { createHash } from \"node:crypto\";\n\nexport interface ProjectEntry {\n id: string;\n rid: string;\n label: string;\n created: Date;\n lastModified: Date;\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: ResourceId,\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 (isNullResourceId(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 entries.push({\n id: f.name,\n rid: String(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 projectListRid: ResourceId,\n projectId: string,\n): Promise<ProjectInfo> {\n return await pl.withReadTx(\"getProjectInfo\", async (tx) => {\n const fieldData = await tx.getField(field(projectListRid, projectId));\n if (isNullResourceId(fieldData.value)) {\n throw new Error(`Project \"${projectId}\" not found.`);\n }\n\n const rid = fieldData.value;\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: String(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 field ID and ResourceId. */\nexport async function resolveProject(\n pl: PlClient,\n projectListRid: ResourceId,\n identifier: string,\n): Promise<{ id: string; rid: ResourceId }> {\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 (isNullResourceId(f.value)) continue;\n if (f.name === identifier) {\n return { id: f.name, rid: f.value };\n }\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: f.name, rid: f.value };\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: ResourceId,\n): Promise<string[]> {\n const data = await tx.getResourceData(projectListRid, true);\n const labels: string[] = [];\n for (const f of data.fields) {\n if (isNullResourceId(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: ResourceId,\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: ResourceId,\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: ResourceId,\n projectId: string,\n): Promise<void> {\n await pl.withWriteTx(\"deleteProject\", async (tx) => {\n tx.removeField(field(projectListRid, projectId));\n await tx.commit();\n });\n}\n\n/** Get the project list ResourceId for the connected user. */\nexport async function getProjectListRid(pl: PlClient): Promise<ResourceId> {\n return await pl.withReadTx(\"getProjectList\", async (tx) => {\n const fieldData = await tx.getField({\n resourceId: tx.clientRoot,\n fieldName: ProjectsField,\n });\n if (isNullResourceId(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 * Computes SHA256(username) to find the user's root, then reads the \"projects\" field.\n */\nexport async function navigateToUserRoot(\n pl: PlClient,\n username: string,\n): Promise<{ userRoot: ResourceId; projectListRid: ResourceId }> {\n const rootName = createHash(\"sha256\").update(username).digest(\"hex\");\n\n return await pl.withReadTx(\"navigateToUserRoot\", async (tx) => {\n if (!(await tx.checkResourceNameExists(rootName))) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n\n const userRootRid = await tx.getResourceByName(rootName);\n\n const projectsFieldData = await tx.getField({\n resourceId: userRootRid,\n fieldName: ProjectsField,\n });\n\n if (isNullResourceId(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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/pl-cli",
3
- "version": "0.2.19",
3
+ "version": "0.2.21",
4
4
  "description": "CLI for Platforma server state manipulation",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -23,20 +23,20 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@oclif/core": "^4.0.37",
26
- "@milaboratories/pl-client": "3.0.0",
27
- "@milaboratories/pl-middle-layer": "1.55.1"
26
+ "@milaboratories/pl-client": "3.1.0",
27
+ "@milaboratories/pl-middle-layer": "1.55.3"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "~24.5.2",
31
31
  "rollup-plugin-node-externals": "^8.0.0",
32
32
  "typescript": "~5.9.3",
33
- "vite": "8.0.0-beta.15",
33
+ "vite": "^8.0.6",
34
34
  "vite-plugin-dts": "^4.5.3",
35
- "vitest": "^4.0.18",
36
- "@milaboratories/build-configs": "1.5.2",
37
- "@milaboratories/ts-configs": "1.2.2",
35
+ "vitest": "^4.1.3",
38
36
  "@milaboratories/oclif-index": "1.1.1",
39
- "@milaboratories/ts-builder": "1.3.0"
37
+ "@milaboratories/ts-builder": "1.3.1",
38
+ "@milaboratories/build-configs": "2.0.0",
39
+ "@milaboratories/ts-configs": "1.2.3"
40
40
  },
41
41
  "oclif": {
42
42
  "bin": "pl-cli",