@platforma-sdk/pl-cli 0.5.5 → 0.6.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/base_command.d.ts +2 -2
- package/dist/cli.js +39 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{output-BLm2ZDFN.js → output-BGnfm0z-.js} +60 -65
- package/dist/output-BGnfm0z-.js.map +1 -0
- package/dist/project_ops.d.ts +21 -16
- package/package.json +5 -5
- package/src/base_command.ts +3 -3
- package/src/cmd/admin/copy-project.ts +7 -5
- package/src/cmd/project/delete.ts +3 -3
- package/src/cmd/project/duplicate.ts +7 -5
- package/src/cmd/project/info.ts +5 -4
- package/src/cmd/project/list.ts +5 -3
- package/src/cmd/project/rename.ts +2 -1
- package/src/project_ops.ts +48 -54
- package/dist/output-BLm2ZDFN.js.map +0 -1
package/dist/base_command.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { PlClient,
|
|
2
|
+
import { PlClient, SignedResourceId } from '@milaboratories/pl-client';
|
|
3
3
|
/** Base command with dual-mode connection: user auth or admin + target-user. */
|
|
4
4
|
export declare abstract class PlCommand extends Command {
|
|
5
5
|
static baseFlags: {
|
|
@@ -37,7 +37,7 @@ export declare abstract class PlCommand extends Command {
|
|
|
37
37
|
"target-user"?: string;
|
|
38
38
|
}): Promise<{
|
|
39
39
|
pl: PlClient;
|
|
40
|
-
projectListRid:
|
|
40
|
+
projectListRid: SignedResourceId;
|
|
41
41
|
}>;
|
|
42
42
|
protected finally(_: Error | undefined): Promise<void>;
|
|
43
43
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
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-BGnfm0z-.js";
|
|
2
2
|
import { Args as g, Command as _, Flags as v } from "@oclif/core";
|
|
3
|
-
import { field as y,
|
|
3
|
+
import { field as y, resourceIdToString 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";
|
|
@@ -94,7 +94,7 @@ var w = {
|
|
|
94
94
|
let { flags: t } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(t), a = await n(r, i);
|
|
95
95
|
if (t.format === "json") f(a.map((e) => ({
|
|
96
96
|
id: e.id,
|
|
97
|
-
rid: e.rid,
|
|
97
|
+
rid: b(e.rid),
|
|
98
98
|
label: e.label,
|
|
99
99
|
created: e.created.toISOString(),
|
|
100
100
|
lastModified: e.lastModified.toISOString()
|
|
@@ -106,11 +106,13 @@ var w = {
|
|
|
106
106
|
}
|
|
107
107
|
o(l([
|
|
108
108
|
"ID",
|
|
109
|
+
"RID",
|
|
109
110
|
"LABEL",
|
|
110
111
|
"CREATED",
|
|
111
112
|
"LAST MODIFIED"
|
|
112
113
|
], a.map((e) => [
|
|
113
|
-
e.id
|
|
114
|
+
e.id,
|
|
115
|
+
b(e.rid),
|
|
114
116
|
e.label,
|
|
115
117
|
m(e.created),
|
|
116
118
|
m(e.lastModified)
|
|
@@ -126,20 +128,20 @@ var w = {
|
|
|
126
128
|
}) };
|
|
127
129
|
static flags = { ...O.baseFlags };
|
|
128
130
|
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),
|
|
131
|
+
let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a, rid: l } = await c(r, i, t.project), u = await s(r, a, l);
|
|
130
132
|
if (n.format === "json") f({
|
|
131
|
-
id:
|
|
132
|
-
rid:
|
|
133
|
-
label:
|
|
134
|
-
schemaVersion:
|
|
135
|
-
blockCount:
|
|
136
|
-
blockIds:
|
|
137
|
-
created:
|
|
138
|
-
lastModified:
|
|
133
|
+
id: u.id,
|
|
134
|
+
rid: b(u.rid),
|
|
135
|
+
label: u.label,
|
|
136
|
+
schemaVersion: u.schemaVersion,
|
|
137
|
+
blockCount: u.blockCount,
|
|
138
|
+
blockIds: u.blockIds,
|
|
139
|
+
created: u.created.toISOString(),
|
|
140
|
+
lastModified: u.lastModified.toISOString()
|
|
139
141
|
});
|
|
140
142
|
else {
|
|
141
|
-
if (o(`Project: ${
|
|
142
|
-
o(`Created: ${m(
|
|
143
|
+
if (o(`Project: ${u.label}`), o(`ID: ${u.id}`), o(`RID: ${b(u.rid)}`), o(`Schema: ${u.schemaVersion ?? "(unknown)"}`), o(`Blocks: ${u.blockCount}`), u.blockIds.length > 0) for (let e of u.blockIds) o(` - ${e}`);
|
|
144
|
+
o(`Created: ${m(u.created)}`), o(`Last Modified: ${m(u.lastModified)}`);
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
},
|
|
@@ -170,16 +172,19 @@ var w = {
|
|
|
170
172
|
c = o.includes(i.name) ? e(i.name, o) : i.name;
|
|
171
173
|
} else c = e(a, o);
|
|
172
174
|
let d = await p(n, l, { label: c });
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
n.createField(y(s, u), "Dynamic", d), await n.commit();
|
|
176
|
+
let f = await d.globalId;
|
|
177
|
+
return {
|
|
178
|
+
id: b(f),
|
|
179
|
+
rid: f,
|
|
175
180
|
label: c
|
|
176
181
|
};
|
|
177
182
|
});
|
|
178
183
|
i.format === "json" ? f({
|
|
179
|
-
id:
|
|
180
|
-
rid:
|
|
184
|
+
id: d.id,
|
|
185
|
+
rid: b(d.rid),
|
|
181
186
|
label: d.label
|
|
182
|
-
}) : o(`Duplicated project as "${d.label}" (id: ${
|
|
187
|
+
}) : o(`Duplicated project as "${d.label}" (id: ${d.id})`);
|
|
183
188
|
}
|
|
184
189
|
},
|
|
185
190
|
"project:rename": class e extends O {
|
|
@@ -201,7 +206,7 @@ var w = {
|
|
|
201
206
|
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
207
|
await d(r, s, n.name), n.format === "json" ? f({
|
|
203
208
|
id: a,
|
|
204
|
-
rid:
|
|
209
|
+
rid: b(s),
|
|
205
210
|
label: n.name
|
|
206
211
|
}) : o(`Renamed project to "${n.name}"`);
|
|
207
212
|
}
|
|
@@ -220,7 +225,7 @@ var w = {
|
|
|
220
225
|
})
|
|
221
226
|
};
|
|
222
227
|
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),
|
|
228
|
+
let { args: t, flags: n } = await this.parse(e), { pl: r, projectListRid: i } = await this.connect(n), { id: a, rid: l, fieldName: d } = await c(r, i, t.project), p = await s(r, a, l);
|
|
224
229
|
if (!n.force) {
|
|
225
230
|
let e = C({
|
|
226
231
|
input: process.stdin,
|
|
@@ -228,7 +233,7 @@ var w = {
|
|
|
228
233
|
});
|
|
229
234
|
try {
|
|
230
235
|
if ((await new Promise((t) => {
|
|
231
|
-
e.question(`Delete project "${
|
|
236
|
+
e.question(`Delete project "${p.label}" (${p.blockCount} blocks)? [y/N] `, t);
|
|
232
237
|
})).toLowerCase() !== "y") {
|
|
233
238
|
o("Aborted.");
|
|
234
239
|
return;
|
|
@@ -237,11 +242,11 @@ var w = {
|
|
|
237
242
|
e.close();
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
|
-
await u(r, i,
|
|
245
|
+
await u(r, i, d), n.format === "json" ? f({
|
|
241
246
|
deleted: !0,
|
|
242
247
|
id: a,
|
|
243
|
-
label:
|
|
244
|
-
}) : o(`Deleted project "${
|
|
248
|
+
label: p.label
|
|
249
|
+
}) : o(`Deleted project "${p.label}"`);
|
|
245
250
|
}
|
|
246
251
|
},
|
|
247
252
|
"admin:copy-project": class n extends O {
|
|
@@ -277,18 +282,21 @@ var w = {
|
|
|
277
282
|
s = e(i.name ?? a, o);
|
|
278
283
|
}
|
|
279
284
|
let c = await p(n, u, { label: s });
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
n.createField(y(d.projectListRid, m), "Dynamic", c), await n.commit();
|
|
286
|
+
let l = await c.globalId;
|
|
287
|
+
return {
|
|
288
|
+
id: b(l),
|
|
289
|
+
rid: l,
|
|
282
290
|
label: s
|
|
283
291
|
};
|
|
284
292
|
});
|
|
285
293
|
i.format === "json" ? f({
|
|
286
|
-
id:
|
|
287
|
-
rid:
|
|
294
|
+
id: h.id,
|
|
295
|
+
rid: b(h.rid),
|
|
288
296
|
label: h.label,
|
|
289
297
|
sourceUser: i["source-user"],
|
|
290
298
|
targetUser: s
|
|
291
|
-
}) : o(`Copied project from ${i["source-user"]} to ${s} as "${h.label}" (id: ${
|
|
299
|
+
}) : o(`Copied project from ${i["source-user"]} to ${s} as "${h.label}" (id: ${h.id}, rid: ${b(h.rid)})`);
|
|
292
300
|
}
|
|
293
301
|
}
|
|
294
302
|
};
|
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, SignedResourceId } 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: SignedResourceId }> {\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: SignedResourceId;\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\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\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: 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 }\n}\n","import { Args } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\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, rid } = await resolveProject(pl, projectListRid, args.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 }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { field, resourceIdToString } 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 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[\"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 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 }\n}\n","import { Args, Flags } from \"@oclif/core\";\nimport { PlCommand } from \"../../base_command\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\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: resourceIdToString(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, rid, fieldName } = await resolveProject(pl, projectListRid, args.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 }\n}\n","import { Flags } from \"@oclif/core\";\nimport { field, resourceIdToString } 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 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[\"source-user\"],\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags[\"source-user\"]} to ${targetUser} as \"${result.label}\" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,\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":";;;;;;;AAEA,IAAa,IAAc;CACzB,SAAS,EAAM,OAAO;EACpB,MAAM;EACN,SAAS;EACT,WAAW;EACX,KAAK;EACL,UAAU;EACX,CAAC;CACF,QAAQ,EAAM,OAAO;EACnB,MAAM;EACN,SAAS;EACT,SAAS,CAAC,QAAQ,OAAO;EACzB,SAAS;EACV,CAAC;CACH,EAEY,IAAgB;CAC3B,MAAM,EAAM,OAAO;EACjB,MAAM;EACN,SAAS;EACT,KAAK;EACN,CAAC;CACF,UAAU,EAAM,OAAO;EACrB,MAAM;EACN,SAAS;EACT,KAAK;EACN,CAAC;CACH,EAGY,IAAiB;CAC5B,cAAc,EAAM,OAAO;EACzB,SAAS;EACT,KAAK;EACL,UAAU;EACX,CAAC;CACF,kBAAkB,EAAM,OAAO;EAC7B,SAAS;EACT,KAAK;EACL,UAAU;EACX,CAAC;CACH,EAGY,IAAmB;CAC9B,cAAc,EAAM,OAAO;EACzB,SAAS;EACT,KAAK;EACN,CAAC;CACF,kBAAkB,EAAM,OAAO;EAC7B,SAAS;EACT,KAAK;EACN,CAAC;CACF,eAAe,EAAM,OAAO;EAC1B,SAAS;EACT,KAAK;EACN,CAAC;CACH,ECpDqB,IAAtB,cAAwC,EAAQ;CAC9C,OAAO,YAAY;EACjB,GAAG;EACH,GAAG;EACH,GAAG;EACJ;CAED;CAMA,MAAgB,cAAc,GAMR;AACpB,MAAI,KAAK,IAAK,OAAU,MAAM,+BAA+B;AAgB7D,SAdI,EAAM,iBAAiB,EAAM,oBAC/B,KAAK,MAAM,MAAM,EAAwB;GACvC,SAAS,EAAM;GACf,WAAW,EAAM;GACjB,eAAe,EAAM;GACtB,CAAC,GAEF,KAAK,MAAM,MAAM,EAAmB;GAClC,SAAS,EAAM;GACf,MAAM,EAAM;GACZ,UAAU,EAAM;GACjB,CAAC,EAGG,KAAK;;CAQd,MAAgB,QAAQ,GAOwC;EAC9D,IAAM,IAAe,CAAC,CAAC,EAAM,eACvB,IAAmB,CAAC,CAAC,EAAM,mBAC3B,IAAY,CAAC,CAAC,EAAM;AAG1B,MAAI,KAAa,EAAE,KAAgB,GACjC,OAAU,MAAM,2DAA2D;AAE7E,OAAK,KAAgB,MAAqB,CAAC,EACzC,OAAU,MAAM,2EAA2E;EAG7F,IAAM,IAAK,MAAM,KAAK,cAAc,EAAM,EAEtC;AAQJ,SAPA,AAIE,IAJE,KACU,MAAM,EAAmB,GAAI,EAAM,eAAgB,EAC1C,iBAEJ,MAAM,EAAkB,EAAG,EAGvC;GAAE;GAAI;GAAgB;;CAG/B,MAAgB,QAAQ,GAAqC;AAC3D,EAAI,KAAK,OACP,MAAM,KAAK,IAAI,OAAO;;GO7Ef,IAAW;CACtB,gBNLF,MAAqB,UAAoB,EAAU;EACjD,OAAgB,cAAc;EAE9B,OAAgB,QAAQ,EACtB,GAAG,EAAU,WACd;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,aAAU,MAAM,KAAK,MAAM,EAAY,EACzC,EAAE,OAAI,sBAAmB,MAAM,KAAK,QAAQ,EAAM,EAClD,IAAW,MAAM,EAAa,GAAI,EAAe;AAEvD,OAAI,EAAM,WAAW,OACnB,GACE,EAAS,KAAK,OAAO;IACnB,IAAI,EAAE;IACN,KAAK,EAAmB,EAAE,IAAI;IAC9B,OAAO,EAAE;IACT,SAAS,EAAE,QAAQ,aAAa;IAChC,cAAc,EAAE,aAAa,aAAa;IAC3C,EAAE,CACJ;QACI;AACL,QAAI,EAAS,WAAW,GAAG;AACzB,OAAW,qBAAqB;AAChC;;AAeF,IAZA,EACE,EACE;KAAC;KAAM;KAAO;KAAS;KAAW;KAAgB,EAClD,EAAS,KAAK,MAAM;KAClB,EAAE;KACF,EAAmB,EAAE,IAAI;KACzB,EAAE;KACF,EAAW,EAAE,QAAQ;KACrB,EAAW,EAAE,aAAa;KAC3B,CAAC,CACH,CACF,EACD,EAAW,KAAK,EAAS,OAAO,aAAa;;;;CMlCjD,gBLLF,MAAqB,UAAoB,EAAU;EACjD,OAAgB,cAAc;EAE9B,OAAgB,OAAO,EACrB,SAAS,EAAK,OAAO;GACnB,aAAa;GACb,UAAU;GACX,CAAC,EACH;EAED,OAAgB,QAAQ,EACtB,GAAG,EAAU,WACd;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,SAAM,aAAU,MAAM,KAAK,MAAM,EAAY,EAC/C,EAAE,OAAI,sBAAmB,MAAM,KAAK,QAAQ,EAAM,EAClD,EAAE,OAAI,WAAQ,MAAM,EAAe,GAAI,GAAgB,EAAK,QAAQ,EACpE,IAAO,MAAM,EAAe,GAAI,GAAI,EAAI;AAE9C,OAAI,EAAM,WAAW,OACnB,GAAW;IACT,IAAI,EAAK;IACT,KAAK,EAAmB,EAAK,IAAI;IACjC,OAAO,EAAK;IACZ,eAAe,EAAK;IACpB,YAAY,EAAK;IACjB,UAAU,EAAK;IACf,SAAS,EAAK,QAAQ,aAAa;IACnC,cAAc,EAAK,aAAa,aAAa;IAC9C,CAAC;QACG;AAML,QALA,EAAW,YAAY,EAAK,QAAQ,EACpC,EAAW,YAAY,EAAK,KAAK,EACjC,EAAW,YAAY,EAAmB,EAAK,IAAI,GAAG,EACtD,EAAW,YAAY,EAAK,iBAAiB,cAAc,EAC3D,EAAW,YAAY,EAAK,aAAa,EACrC,EAAK,SAAS,SAAS,EACzB,MAAK,IAAM,KAAO,EAAK,SACrB,GAAW,OAAO,IAAM;AAI5B,IADA,EAAW,kBAAkB,EAAW,EAAK,QAAQ,GAAG,EACxD,EAAW,kBAAkB,EAAW,EAAK,aAAa,GAAG;;;;CKrCjE,qBJCF,MAAqB,UAAyB,EAAU;EACtD,OAAgB,cACd;EAEF,OAAgB,OAAO,EACrB,SAAS,EAAK,OAAO;GACnB,aAAa;GACb,UAAU;GACX,CAAC,EACH;EAED,OAAgB,QAAQ;GACtB,GAAG,EAAU;GACb,MAAM,EAAM,OAAO;IACjB,MAAM;IACN,SAAS;IACT,WAAW;IACZ,CAAC;GACF,eAAe,EAAM,QAAQ;IAC3B,SAAS;IACT,SAAS;IACT,SAAS;IACV,CAAC;GACH;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,SAAM,aAAU,MAAM,KAAK,MAAM,EAAiB,EACpD,EAAE,OAAI,sBAAmB,MAAM,KAAK,QAAQ,EAAM,EAElD,EAAE,KAAK,MAAc,MAAM,EAAe,GAAI,GAAgB,EAAK,QAAQ,EAE3E,IAAQ,GAAY,EAEpB,IAAS,MAAM,EAAG,YAAY,oBAAoB,OAAO,MAAO;IACpE,IAAM,IAAgB,MAAM,EAAG,gBAAgB,GAAW,EAAe,EAEnE,IADa,KAAK,MAAM,EAAc,CACL,OAEjC,IAAiB,MAAM,EAAsB,GAAI,EAAe,EAGlE;AACJ,QAAI,EAAM,MAAM;AACd,SAAI,CAAC,EAAM,kBAAkB,EAAe,SAAS,EAAM,KAAK,CAC9D,OAAU,MAAM,iBAAiB,EAAM,KAAK,mBAAmB;AAEjE,SAAW,EAAe,SAAS,EAAM,KAAK,GAC1C,EAAgB,EAAM,MAAM,EAAe,GAC3C,EAAM;UAEV,KAAW,EAAgB,GAAa,EAAe;IAGzD,IAAM,IAAS,MAAM,EAAiB,GAAI,GAAW,EAAE,OAAO,GAAU,CAAC;AAEzE,IADA,EAAG,YAAY,EAAM,GAAgB,EAAM,EAAE,WAAW,EAAO,EAC/D,MAAM,EAAG,QAAQ;IAEjB,IAAM,IAAoB,MAAM,EAAO;AAEvC,WAAO;KAAE,IADS,EAAmB,EAAkB;KAC/B,KAAK;KAAmB,OAAO;KAAU;KACjE;AAEF,GAAI,EAAM,WAAW,SACnB,EAAW;IAAE,IAAI,EAAO;IAAI,KAAK,EAAmB,EAAO,IAAI;IAAE,OAAO,EAAO;IAAO,CAAC,GAEvF,EAAW,0BAA0B,EAAO,MAAM,SAAS,EAAO,GAAG,GAAG;;;CIjE5E,kBHPF,MAAqB,UAAsB,EAAU;EACnD,OAAgB,cAAc;EAE9B,OAAgB,OAAO,EACrB,SAAS,EAAK,OAAO;GACnB,aAAa;GACb,UAAU;GACX,CAAC,EACH;EAED,OAAgB,QAAQ;GACtB,GAAG,EAAU;GACb,MAAM,EAAM,OAAO;IACjB,MAAM;IACN,SAAS;IACT,WAAW;IACX,UAAU;IACX,CAAC;GACH;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,SAAM,aAAU,MAAM,KAAK,MAAM,EAAc,EACjD,EAAE,OAAI,sBAAmB,MAAM,KAAK,QAAQ,EAAM,EAClD,EAAE,OAAI,WAAQ,MAAM,EAAe,GAAI,GAAgB,EAAK,QAAQ;AAI1E,GAFA,MAAM,EAAc,GAAI,GAAK,EAAM,KAAK,EAEpC,EAAM,WAAW,SACnB,EAAW;IAAE;IAAI,KAAK,EAAmB,EAAI;IAAE,OAAO,EAAM;IAAM,CAAC,GAEnE,EAAW,uBAAuB,EAAM,KAAK,GAAG;;;CGtBpD,kBFRF,MAAqB,UAAsB,EAAU;EACnD,OAAgB,cAAc;EAE9B,OAAgB,OAAO,EACrB,SAAS,EAAK,OAAO;GACnB,aAAa;GACb,UAAU;GACX,CAAC,EACH;EAED,OAAgB,QAAQ;GACtB,GAAG,EAAU;GACb,OAAO,EAAM,QAAQ;IACnB,SAAS;IACT,SAAS;IACV,CAAC;GACH;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,SAAM,aAAU,MAAM,KAAK,MAAM,EAAc,EACjD,EAAE,OAAI,sBAAmB,MAAM,KAAK,QAAQ,EAAM,EAClD,EAAE,OAAI,QAAK,iBAAc,MAAM,EAAe,GAAI,GAAgB,EAAK,QAAQ,EAE/E,IAAO,MAAM,EAAe,GAAI,GAAI,EAAI;AAE9C,OAAI,CAAC,EAAM,OAAO;IAChB,IAAM,IAAK,EAAgB;KAAE,OAAO,QAAQ;KAAO,QAAQ,QAAQ;KAAQ,CAAC;AAC5E,QAAI;AAOF,UANe,MAAM,IAAI,SAAiB,MAAY;AACpD,QAAG,SACD,mBAAmB,EAAK,MAAM,KAAK,EAAK,WAAW,mBACnD,EACD;OACD,EACS,aAAa,KAAK,KAAK;AAChC,QAAW,WAAW;AACtB;;cAEM;AACR,OAAG,OAAO;;;AAMd,GAFA,MAAM,EAAc,GAAI,GAAgB,EAAU,EAE9C,EAAM,WAAW,SACnB,EAAW;IAAE,SAAS;IAAM;IAAI,OAAO,EAAK;IAAO,CAAC,GAEpD,EAAW,oBAAoB,EAAK,MAAM,GAAG;;;CEvCjD,sBDAF,MAAqB,UAAyB,EAAU;EACtD,OAAgB,cACd;EAEF,OAAgB,QAAQ;GACtB,GAAG;GACH,GAAG;GACH,eAAe,EAAM,OAAO;IAC1B,SAAS;IACT,UAAU;IACX,CAAC;GACF,kBAAkB,EAAM,OAAO;IAC7B,SAAS;IACT,UAAU;IACX,CAAC;GACF,eAAe,EAAM,OAAO,EAC1B,SAAS,4EACV,CAAC;GACF,MAAM,EAAM,OAAO;IACjB,MAAM;IACN,SAAS;IACT,WAAW;IACZ,CAAC;GACF,eAAe,EAAM,QAAQ;IAC3B,SAAS;IACT,SAAS;IACT,SAAS;IACV,CAAC;GACH;EAED,MAAa,MAAqB;GAChC,IAAM,EAAE,aAAU,MAAM,KAAK,MAAM,EAAiB,EAC9C,IAAK,MAAM,KAAK,cAAc,EAAM,EAEpC,IAAa,EAAM,kBAAkB,EAAM,gBAE3C,IAAS,MAAM,EAAmB,GAAI,EAAM,eAAe,EAC3D,EAAE,KAAK,MAAc,MAAM,EAC/B,GACA,EAAO,gBACP,EAAM,kBACP,EAEK,IACJ,MAAe,EAAM,iBAAiB,IAAS,MAAM,EAAmB,GAAI,EAAW,EAEnF,IAAQ,GAAY,EAEpB,IAAS,MAAM,EAAG,YAAY,oBAAoB,OAAO,MAAO;IACpE,IAAM,IAAgB,MAAM,EAAG,gBAAgB,GAAW,EAAe,EAEnE,IADa,KAAK,MAAM,EAAc,CACL,OAEjC,IAAiB,MAAM,EAAsB,GAAI,EAAO,eAAe,EAEzE,IAAmB,EAAM,QAAQ;AAErC,QAAI,EAAe,SAAS,EAAS,EAAE;AACrC,SAAI,CAAC,EAAM,eACT,OAAU,MAAM,iBAAiB,EAAS,mCAAmC;AAE/E,SAAW,EAAgB,EAAM,QAAQ,GAAa,EAAe;;IAGvE,IAAM,IAAS,MAAM,EAAiB,GAAI,GAAW,EAAE,OAAO,GAAU,CAAC;AAEzE,IADA,EAAG,YAAY,EAAM,EAAO,gBAAgB,EAAM,EAAE,WAAW,EAAO,EACtE,MAAM,EAAG,QAAQ;IAEjB,IAAM,IAAgB,MAAM,EAAO;AAEnC,WAAO;KAAE,IADK,EAAmB,EAAc;KAC3B,KAAK;KAAe,OAAO;KAAU;KACzD;AAEF,GAAI,EAAM,WAAW,SACnB,EAAW;IACT,IAAI,EAAO;IACX,KAAK,EAAmB,EAAO,IAAI;IACnC,OAAO,EAAO;IACd,YAAY,EAAM;IAClB;IACD,CAAC,GAEF,EACE,uBAAuB,EAAM,eAAe,MAAM,EAAW,OAAO,EAAO,MAAM,SAAS,EAAO,GAAG,SAAS,EAAmB,EAAO,IAAI,CAAC,GAC7I;;;CCnFN"}
|
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-
|
|
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-BGnfm0z-.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,84 +1,87 @@
|
|
|
1
|
-
import { PlClient as e, UnauthenticatedPlClient as t, field as n,
|
|
1
|
+
import { PlClient as e, UnauthenticatedPlClient as t, field as n, isNullSignedResourceId as r, plAddressToConfig as i, resourceIdToString as a } from "@milaboratories/pl-client";
|
|
2
2
|
import { ProjectCreatedTimestamp as o, ProjectLastModifiedTimestamp as s, ProjectMetaKey as c, ProjectStructureKey as l, ProjectsField as u, SchemaVersionKey as d, duplicateProject as f } from "@milaboratories/pl-middle-layer";
|
|
3
|
-
import { createHash as p } from "node:crypto";
|
|
4
3
|
//#region src/connection.ts
|
|
5
|
-
async function
|
|
6
|
-
let r =
|
|
4
|
+
async function p(n) {
|
|
5
|
+
let r = i(n.address);
|
|
7
6
|
n.user !== void 0 && (r.user = n.user), n.password !== void 0 && (r.password = n.password);
|
|
8
|
-
let
|
|
9
|
-
if (await
|
|
7
|
+
let a = await t.build(r), o;
|
|
8
|
+
if (await a.requireAuth()) {
|
|
10
9
|
if (r.user === void 0 || r.password === void 0) throw Error("Server requires authentication but no credentials provided. Use --user/--password flags or PL_USER/PL_PASSWORD env vars.");
|
|
11
|
-
o = await
|
|
10
|
+
o = await a.login(r.user, r.password);
|
|
12
11
|
} else o = {};
|
|
13
12
|
return await e.init(r, { authInformation: o });
|
|
14
13
|
}
|
|
15
|
-
async function
|
|
16
|
-
let r =
|
|
17
|
-
return await e.init(r, { authInformation:
|
|
14
|
+
async function m(n) {
|
|
15
|
+
let r = i(n.address), a = await (await t.build(r)).login(n.adminUser, n.adminPassword);
|
|
16
|
+
return await e.init(r, { authInformation: a });
|
|
18
17
|
}
|
|
19
18
|
//#endregion
|
|
20
19
|
//#region src/project_ops.ts
|
|
21
|
-
async function
|
|
20
|
+
async function h(e, t) {
|
|
22
21
|
return await e.withReadTx("listProjects", async (e) => {
|
|
23
22
|
let n = await e.getResourceData(t, !0), i = [];
|
|
24
23
|
for (let t of n.fields) {
|
|
25
24
|
if (r(t.value)) continue;
|
|
26
|
-
let [n,
|
|
25
|
+
let [n, l, u] = await Promise.all([
|
|
27
26
|
e.getKValueStringIfExists(t.value, c),
|
|
28
27
|
e.getKValueStringIfExists(t.value, o),
|
|
29
28
|
e.getKValueStringIfExists(t.value, s)
|
|
30
|
-
]),
|
|
29
|
+
]), d = n ? JSON.parse(n) : { label: "(unknown)" }, f = a(t.value);
|
|
31
30
|
i.push({
|
|
32
|
-
id:
|
|
33
|
-
rid:
|
|
34
|
-
label:
|
|
35
|
-
created:
|
|
36
|
-
lastModified:
|
|
31
|
+
id: f,
|
|
32
|
+
rid: t.value,
|
|
33
|
+
label: d.label,
|
|
34
|
+
created: l ? new Date(Number(l)) : /* @__PURE__ */ new Date(0),
|
|
35
|
+
lastModified: u ? new Date(Number(u)) : /* @__PURE__ */ new Date(0)
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
38
|
return i.sort((e, t) => t.lastModified.getTime() - e.lastModified.getTime()), i;
|
|
40
39
|
});
|
|
41
40
|
}
|
|
42
|
-
async function
|
|
41
|
+
async function g(e, t, n) {
|
|
43
42
|
return await e.withReadTx("getProjectInfo", async (e) => {
|
|
44
|
-
let
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let e = JSON.parse(_.value);
|
|
49
|
-
for (let t of e.groups ?? []) for (let e of t.blocks ?? []) b.push(e.id);
|
|
43
|
+
let r = await e.listKeyValuesString(n), i = r.find((e) => e.key === c), a = r.find((e) => e.key === o), u = r.find((e) => e.key === s), f = r.find((e) => e.key === d), p = r.find((e) => e.key === l), m = i ? JSON.parse(i.value) : { label: "(unknown)" }, h = f ? JSON.parse(f.value) : void 0, g = [];
|
|
44
|
+
if (p) {
|
|
45
|
+
let e = JSON.parse(p.value);
|
|
46
|
+
for (let t of e.groups ?? []) for (let e of t.blocks ?? []) g.push(e.id);
|
|
50
47
|
}
|
|
51
48
|
return {
|
|
52
|
-
id:
|
|
53
|
-
rid:
|
|
54
|
-
label:
|
|
55
|
-
created:
|
|
56
|
-
lastModified:
|
|
57
|
-
schemaVersion:
|
|
58
|
-
blockCount:
|
|
59
|
-
blockIds:
|
|
49
|
+
id: t,
|
|
50
|
+
rid: n,
|
|
51
|
+
label: m.label,
|
|
52
|
+
created: a ? new Date(Number(a.value)) : /* @__PURE__ */ new Date(0),
|
|
53
|
+
lastModified: u ? new Date(Number(u.value)) : /* @__PURE__ */ new Date(0),
|
|
54
|
+
schemaVersion: h,
|
|
55
|
+
blockCount: g.length,
|
|
56
|
+
blockIds: g
|
|
60
57
|
};
|
|
61
58
|
});
|
|
62
59
|
}
|
|
63
|
-
async function
|
|
60
|
+
async function _(e, t, n) {
|
|
64
61
|
return await e.withReadTx("resolveProject", async (e) => {
|
|
65
62
|
let i = await e.getResourceData(t, !0);
|
|
63
|
+
for (let e of i.fields) {
|
|
64
|
+
if (r(e.value)) continue;
|
|
65
|
+
let t = a(e.value);
|
|
66
|
+
if (t === n) return {
|
|
67
|
+
id: t,
|
|
68
|
+
rid: e.value,
|
|
69
|
+
fieldName: e.name
|
|
70
|
+
};
|
|
71
|
+
}
|
|
66
72
|
for (let t of i.fields) {
|
|
67
73
|
if (r(t.value)) continue;
|
|
68
|
-
if (t.name === n) return {
|
|
69
|
-
id: t.name,
|
|
70
|
-
rid: t.value
|
|
71
|
-
};
|
|
72
74
|
let i = await e.getKValueStringIfExists(t.value, c);
|
|
73
75
|
if (i && JSON.parse(i).label === n) return {
|
|
74
|
-
id: t.
|
|
75
|
-
rid: t.value
|
|
76
|
+
id: a(t.value),
|
|
77
|
+
rid: t.value,
|
|
78
|
+
fieldName: t.name
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
81
|
throw Error(`Project "${n}" not found (searched by id and label).`);
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
|
-
async function
|
|
84
|
+
async function v(e, t) {
|
|
82
85
|
let n = await e.getResourceData(t, !0), i = [];
|
|
83
86
|
for (let t of n.fields) {
|
|
84
87
|
if (r(t.value)) continue;
|
|
@@ -90,12 +93,12 @@ async function y(e, t) {
|
|
|
90
93
|
}
|
|
91
94
|
return i;
|
|
92
95
|
}
|
|
93
|
-
function
|
|
96
|
+
function y(e, t) {
|
|
94
97
|
let n = `${e} (Copy)`, r = 2;
|
|
95
98
|
for (; t.includes(n);) n = `${e} (Copy ${r})`, r++;
|
|
96
99
|
return n;
|
|
97
100
|
}
|
|
98
|
-
async function
|
|
101
|
+
async function b(e, t, n) {
|
|
99
102
|
await e.withWriteTx("renameProject", async (e) => {
|
|
100
103
|
let r = await e.getKValueString(t, c), i = {
|
|
101
104
|
...JSON.parse(r),
|
|
@@ -104,12 +107,12 @@ async function x(e, t, n) {
|
|
|
104
107
|
e.setKValue(t, c, JSON.stringify(i)), e.setKValue(t, s, String(Date.now())), await e.commit();
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
|
-
async function
|
|
110
|
+
async function x(e, t, r) {
|
|
108
111
|
await e.withWriteTx("deleteProject", async (e) => {
|
|
109
112
|
e.removeField(n(t, r)), await e.commit();
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
|
-
async function
|
|
115
|
+
async function S(e) {
|
|
113
116
|
return await e.withReadTx("getProjectList", async (e) => {
|
|
114
117
|
let t = await e.getField({
|
|
115
118
|
resourceId: e.clientRoot,
|
|
@@ -119,21 +122,13 @@ async function C(e) {
|
|
|
119
122
|
return t.value;
|
|
120
123
|
});
|
|
121
124
|
}
|
|
122
|
-
async function
|
|
125
|
+
async function C(e, t) {
|
|
123
126
|
let n;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (n === void 0) {
|
|
130
|
-
let r = p("sha256").update(t).digest("hex");
|
|
131
|
-
n = await e.withReadTx("navigateToUserRoot:legacy", async (e) => {
|
|
132
|
-
if (!await e.checkResourceNameExists(r)) throw Error(`User "${t}" not found on this server (no root resource).`);
|
|
133
|
-
return await e.getResourceByName(r);
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
return await e.withReadTx("navigateToUserRoot", async (e) => {
|
|
127
|
+
if (n = await e.getUserRoot({
|
|
128
|
+
login: t,
|
|
129
|
+
doNotCreate: !0
|
|
130
|
+
}), n === void 0) throw Error(`User "${t}" not found on this server (no root resource).`);
|
|
131
|
+
return await e.withReadTx("navigateToUserProjects", async (e) => {
|
|
137
132
|
let i = await e.getField({
|
|
138
133
|
resourceId: n,
|
|
139
134
|
fieldName: u
|
|
@@ -147,13 +142,13 @@ async function w(e, t) {
|
|
|
147
142
|
}
|
|
148
143
|
//#endregion
|
|
149
144
|
//#region src/output.ts
|
|
150
|
-
function
|
|
145
|
+
function w(e) {
|
|
151
146
|
console.log(e);
|
|
152
147
|
}
|
|
153
|
-
function
|
|
148
|
+
function T(e) {
|
|
154
149
|
console.log(JSON.stringify(e, null, 2));
|
|
155
150
|
}
|
|
156
|
-
function
|
|
151
|
+
function E(e, t) {
|
|
157
152
|
let n = e.map((e, n) => Math.max(e.length, ...t.map((e) => (e[n] ?? "").length))), r = n.map((e) => "-".repeat(e)).join(" ");
|
|
158
153
|
return [
|
|
159
154
|
e.map((e, t) => e.padEnd(n[t])).join(" "),
|
|
@@ -161,10 +156,10 @@ function D(e, t) {
|
|
|
161
156
|
...t.map((e) => e.map((e, t) => (e ?? "").padEnd(n[t])).join(" "))
|
|
162
157
|
].join("\n");
|
|
163
158
|
}
|
|
164
|
-
function
|
|
159
|
+
function D(e) {
|
|
165
160
|
return e.getTime() === 0 ? "(unknown)" : e.toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
166
161
|
}
|
|
167
162
|
//#endregion
|
|
168
|
-
export {
|
|
163
|
+
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, f as s, D as t, S as u };
|
|
169
164
|
|
|
170
|
-
//# sourceMappingURL=output-
|
|
165
|
+
//# sourceMappingURL=output-BGnfm0z-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-BGnfm0z-.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, doNotCreate: true });\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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"}
|
package/dist/project_ops.d.ts
CHANGED
|
@@ -1,47 +1,52 @@
|
|
|
1
|
-
import { PlClient,
|
|
1
|
+
import { PlClient, SignedResourceId, PlTransaction } from '@milaboratories/pl-client';
|
|
2
2
|
import { duplicateProject } from '@milaboratories/pl-middle-layer';
|
|
3
3
|
export interface ProjectEntry {
|
|
4
4
|
id: string;
|
|
5
|
-
rid:
|
|
5
|
+
rid: SignedResourceId;
|
|
6
6
|
label: string;
|
|
7
7
|
created: Date;
|
|
8
8
|
lastModified: Date;
|
|
9
9
|
}
|
|
10
|
+
export interface ProjectIdentity {
|
|
11
|
+
/** Project ID (resourceIdToString of resource ID) */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Signed resource ID for use in transactions */
|
|
14
|
+
rid: SignedResourceId;
|
|
15
|
+
/** UUID field name in the project list resource */
|
|
16
|
+
fieldName: string;
|
|
17
|
+
}
|
|
10
18
|
export interface ProjectInfo extends ProjectEntry {
|
|
11
19
|
schemaVersion: string | undefined;
|
|
12
20
|
blockCount: number;
|
|
13
21
|
blockIds: string[];
|
|
14
22
|
}
|
|
15
23
|
/** List all projects from a project list resource. */
|
|
16
|
-
export declare function listProjects(pl: PlClient, projectListRid:
|
|
24
|
+
export declare function listProjects(pl: PlClient, projectListRid: SignedResourceId): Promise<ProjectEntry[]>;
|
|
17
25
|
/** Get detailed info about a project. */
|
|
18
|
-
export declare function getProjectInfo(pl: PlClient,
|
|
19
|
-
/** Resolve a project identifier (id or label) to its
|
|
20
|
-
export declare function resolveProject(pl: PlClient, projectListRid:
|
|
21
|
-
id: string;
|
|
22
|
-
rid: ResourceId;
|
|
23
|
-
}>;
|
|
26
|
+
export declare function getProjectInfo(pl: PlClient, projectId: string, rid: SignedResourceId): Promise<ProjectInfo>;
|
|
27
|
+
/** Resolve a project identifier (id or label) to its project ID, ResourceId and field name. */
|
|
28
|
+
export declare function resolveProject(pl: PlClient, projectListRid: SignedResourceId, identifier: string): Promise<ProjectIdentity>;
|
|
24
29
|
/** Read all project labels within an existing transaction. */
|
|
25
|
-
export declare function getExistingLabelsInTx(tx: PlTransaction, projectListRid:
|
|
30
|
+
export declare function getExistingLabelsInTx(tx: PlTransaction, projectListRid: SignedResourceId): Promise<string[]>;
|
|
26
31
|
/** Get all project labels from a project list. */
|
|
27
|
-
export declare function getProjectLabels(pl: PlClient, projectListRid:
|
|
32
|
+
export declare function getProjectLabels(pl: PlClient, projectListRid: SignedResourceId): Promise<string[]>;
|
|
28
33
|
/**
|
|
29
34
|
* Deduplicates a project name against existing labels.
|
|
30
35
|
* "X" → "X (Copy)" → "X (Copy 2)" → ...
|
|
31
36
|
*/
|
|
32
37
|
export declare function deduplicateName(baseName: string, existingLabels: string[]): string;
|
|
33
38
|
/** Rename a project (update its label). */
|
|
34
|
-
export declare function renameProject(pl: PlClient, projectRid:
|
|
39
|
+
export declare function renameProject(pl: PlClient, projectRid: SignedResourceId, newLabel: string): Promise<void>;
|
|
35
40
|
/** Delete a project from the project list. */
|
|
36
|
-
export declare function deleteProject(pl: PlClient, projectListRid:
|
|
41
|
+
export declare function deleteProject(pl: PlClient, projectListRid: SignedResourceId, fieldName: string): Promise<void>;
|
|
37
42
|
/** Get the project list ResourceId for the connected user. */
|
|
38
|
-
export declare function getProjectListRid(pl: PlClient): Promise<
|
|
43
|
+
export declare function getProjectListRid(pl: PlClient): Promise<SignedResourceId>;
|
|
39
44
|
/**
|
|
40
45
|
* Navigates to a specific user's project list resource ID.
|
|
41
46
|
* Tries ListUserResources first (new backend), falls back to SHA256 named resource lookup.
|
|
42
47
|
*/
|
|
43
48
|
export declare function navigateToUserRoot(pl: PlClient, username: string): Promise<{
|
|
44
|
-
userRoot:
|
|
45
|
-
projectListRid:
|
|
49
|
+
userRoot: SignedResourceId;
|
|
50
|
+
projectListRid: SignedResourceId;
|
|
46
51
|
}>;
|
|
47
52
|
export { duplicateProject };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/pl-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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
|
"@oclif/core": "^4.0.37",
|
|
26
|
-
"@milaboratories/pl-client": "3.
|
|
27
|
-
"@milaboratories/pl-middle-layer": "1.
|
|
26
|
+
"@milaboratories/pl-client": "3.3.0",
|
|
27
|
+
"@milaboratories/pl-middle-layer": "1.59.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "~24.5.2",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"vite": "^8.0.6",
|
|
34
34
|
"vite-plugin-dts": "^4.5.3",
|
|
35
35
|
"vitest": "^4.1.3",
|
|
36
|
-
"@milaboratories/oclif-index": "1.1.1",
|
|
37
36
|
"@milaboratories/ts-configs": "1.2.3",
|
|
38
37
|
"@milaboratories/build-configs": "2.0.0",
|
|
39
|
-
"@milaboratories/
|
|
38
|
+
"@milaboratories/oclif-index": "1.1.1",
|
|
39
|
+
"@milaboratories/ts-builder": "1.4.0"
|
|
40
40
|
},
|
|
41
41
|
"oclif": {
|
|
42
42
|
"bin": "pl-cli",
|
package/src/base_command.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import type { PlClient,
|
|
2
|
+
import type { PlClient, SignedResourceId } from "@milaboratories/pl-client";
|
|
3
3
|
import { createPlConnection, createAdminPlConnection } from "./connection";
|
|
4
4
|
import { getProjectListRid, navigateToUserRoot } from "./project_ops";
|
|
5
5
|
import { GlobalFlags, UserAuthFlags, AdminTargetFlags } from "./cmd-opts";
|
|
@@ -56,7 +56,7 @@ export abstract class PlCommand extends Command {
|
|
|
56
56
|
"admin-user"?: string;
|
|
57
57
|
"admin-password"?: string;
|
|
58
58
|
"target-user"?: string;
|
|
59
|
-
}): Promise<{ pl: PlClient; projectListRid:
|
|
59
|
+
}): Promise<{ pl: PlClient; projectListRid: SignedResourceId }> {
|
|
60
60
|
const hasAdminUser = !!flags["admin-user"];
|
|
61
61
|
const hasAdminPassword = !!flags["admin-password"];
|
|
62
62
|
const hasTarget = !!flags["target-user"];
|
|
@@ -71,7 +71,7 @@ export abstract class PlCommand extends Command {
|
|
|
71
71
|
|
|
72
72
|
const pl = await this.connectClient(flags);
|
|
73
73
|
|
|
74
|
-
let projectListRid:
|
|
74
|
+
let projectListRid: SignedResourceId;
|
|
75
75
|
if (hasTarget) {
|
|
76
76
|
const nav = await navigateToUserRoot(pl, flags["target-user"]!);
|
|
77
77
|
projectListRid = nav.projectListRid;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
|
-
import { field,
|
|
2
|
+
import { field, resourceIdToString } from "@milaboratories/pl-client";
|
|
3
3
|
import { ProjectMetaKey } from "@milaboratories/pl-middle-layer";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { PlCommand } from "../../base_command";
|
|
@@ -81,20 +81,22 @@ export default class AdminCopyProject extends PlCommand {
|
|
|
81
81
|
tx.createField(field(target.projectListRid, newId), "Dynamic", newPrj);
|
|
82
82
|
await tx.commit();
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
const prjResourceId = await newPrj.globalId;
|
|
85
|
+
const prjId = resourceIdToString(prjResourceId);
|
|
86
|
+
return { id: prjId, rid: prjResourceId, label: newLabel };
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
if (flags.format === "json") {
|
|
88
90
|
outputJson({
|
|
89
|
-
id:
|
|
90
|
-
rid:
|
|
91
|
+
id: result.id,
|
|
92
|
+
rid: resourceIdToString(result.rid),
|
|
91
93
|
label: result.label,
|
|
92
94
|
sourceUser: flags["source-user"],
|
|
93
95
|
targetUser,
|
|
94
96
|
});
|
|
95
97
|
} else {
|
|
96
98
|
outputText(
|
|
97
|
-
`Copied project from ${flags["source-user"]} to ${targetUser} as "${result.label}" (id: ${
|
|
99
|
+
`Copied project from ${flags["source-user"]} to ${targetUser} as "${result.label}" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,
|
|
98
100
|
);
|
|
99
101
|
}
|
|
100
102
|
}
|
|
@@ -25,9 +25,9 @@ export default class ProjectDelete extends PlCommand {
|
|
|
25
25
|
public async run(): Promise<void> {
|
|
26
26
|
const { args, flags } = await this.parse(ProjectDelete);
|
|
27
27
|
const { pl, projectListRid } = await this.connect(flags);
|
|
28
|
-
const { id } = await resolveProject(pl, projectListRid, args.project);
|
|
28
|
+
const { id, rid, fieldName } = await resolveProject(pl, projectListRid, args.project);
|
|
29
29
|
|
|
30
|
-
const info = await getProjectInfo(pl,
|
|
30
|
+
const info = await getProjectInfo(pl, id, rid);
|
|
31
31
|
|
|
32
32
|
if (!flags.force) {
|
|
33
33
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -47,7 +47,7 @@ export default class ProjectDelete extends PlCommand {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
await deleteProject(pl, projectListRid,
|
|
50
|
+
await deleteProject(pl, projectListRid, fieldName);
|
|
51
51
|
|
|
52
52
|
if (flags.format === "json") {
|
|
53
53
|
outputJson({ deleted: true, id, label: info.label });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Args, Flags } from "@oclif/core";
|
|
2
|
-
import { field,
|
|
2
|
+
import { field, resourceIdToString } from "@milaboratories/pl-client";
|
|
3
3
|
import { ProjectMetaKey } from "@milaboratories/pl-middle-layer";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { PlCommand } from "../../base_command";
|
|
@@ -44,7 +44,7 @@ export default class ProjectDuplicate extends PlCommand {
|
|
|
44
44
|
|
|
45
45
|
const newId = randomUUID();
|
|
46
46
|
|
|
47
|
-
const
|
|
47
|
+
const result = await pl.withWriteTx("duplicateProject", async (tx) => {
|
|
48
48
|
const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
|
|
49
49
|
const sourceMeta = JSON.parse(sourceMetaStr);
|
|
50
50
|
const sourceLabel: string = sourceMeta.label;
|
|
@@ -68,13 +68,15 @@ export default class ProjectDuplicate extends PlCommand {
|
|
|
68
68
|
tx.createField(field(projectListRid, newId), "Dynamic", newPrj);
|
|
69
69
|
await tx.commit();
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
const projectResourceId = await newPrj.globalId;
|
|
72
|
+
const projectId = resourceIdToString(projectResourceId);
|
|
73
|
+
return { id: projectId, rid: projectResourceId, label: newLabel };
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
if (flags.format === "json") {
|
|
75
|
-
outputJson({ id:
|
|
77
|
+
outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });
|
|
76
78
|
} else {
|
|
77
|
-
outputText(`Duplicated project as "${
|
|
79
|
+
outputText(`Duplicated project as "${result.label}" (id: ${result.id})`);
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
}
|
package/src/cmd/project/info.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Args } from "@oclif/core";
|
|
|
2
2
|
import { PlCommand } from "../../base_command";
|
|
3
3
|
import { resolveProject, getProjectInfo } from "../../project_ops";
|
|
4
4
|
import { formatDate, outputJson, outputText } from "../../output";
|
|
5
|
+
import { resourceIdToString } from "@milaboratories/pl-client";
|
|
5
6
|
|
|
6
7
|
export default class ProjectInfo extends PlCommand {
|
|
7
8
|
static override description = "Show detailed information about a project.";
|
|
@@ -20,13 +21,13 @@ export default class ProjectInfo extends PlCommand {
|
|
|
20
21
|
public async run(): Promise<void> {
|
|
21
22
|
const { args, flags } = await this.parse(ProjectInfo);
|
|
22
23
|
const { pl, projectListRid } = await this.connect(flags);
|
|
23
|
-
const { id } = await resolveProject(pl, projectListRid, args.project);
|
|
24
|
-
const info = await getProjectInfo(pl,
|
|
24
|
+
const { id, rid } = await resolveProject(pl, projectListRid, args.project);
|
|
25
|
+
const info = await getProjectInfo(pl, id, rid);
|
|
25
26
|
|
|
26
27
|
if (flags.format === "json") {
|
|
27
28
|
outputJson({
|
|
28
29
|
id: info.id,
|
|
29
|
-
rid: info.rid,
|
|
30
|
+
rid: resourceIdToString(info.rid),
|
|
30
31
|
label: info.label,
|
|
31
32
|
schemaVersion: info.schemaVersion,
|
|
32
33
|
blockCount: info.blockCount,
|
|
@@ -37,7 +38,7 @@ export default class ProjectInfo extends PlCommand {
|
|
|
37
38
|
} else {
|
|
38
39
|
outputText(`Project: ${info.label}`);
|
|
39
40
|
outputText(`ID: ${info.id}`);
|
|
40
|
-
outputText(`RID: ${info.rid}`);
|
|
41
|
+
outputText(`RID: ${resourceIdToString(info.rid)}`);
|
|
41
42
|
outputText(`Schema: ${info.schemaVersion ?? "(unknown)"}`);
|
|
42
43
|
outputText(`Blocks: ${info.blockCount}`);
|
|
43
44
|
if (info.blockIds.length > 0) {
|
package/src/cmd/project/list.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PlCommand } from "../../base_command";
|
|
2
2
|
import { listProjects } from "../../project_ops";
|
|
3
3
|
import { formatTable, formatDate, outputJson, outputText } from "../../output";
|
|
4
|
+
import { resourceIdToString } from "@milaboratories/pl-client";
|
|
4
5
|
|
|
5
6
|
export default class ProjectList extends PlCommand {
|
|
6
7
|
static override description = "List all projects for the authenticated user.";
|
|
@@ -18,7 +19,7 @@ export default class ProjectList extends PlCommand {
|
|
|
18
19
|
outputJson(
|
|
19
20
|
projects.map((p) => ({
|
|
20
21
|
id: p.id,
|
|
21
|
-
rid: p.rid,
|
|
22
|
+
rid: resourceIdToString(p.rid),
|
|
22
23
|
label: p.label,
|
|
23
24
|
created: p.created.toISOString(),
|
|
24
25
|
lastModified: p.lastModified.toISOString(),
|
|
@@ -32,9 +33,10 @@ export default class ProjectList extends PlCommand {
|
|
|
32
33
|
|
|
33
34
|
outputText(
|
|
34
35
|
formatTable(
|
|
35
|
-
["ID", "LABEL", "CREATED", "LAST MODIFIED"],
|
|
36
|
+
["ID", "RID", "LABEL", "CREATED", "LAST MODIFIED"],
|
|
36
37
|
projects.map((p) => [
|
|
37
|
-
p.id
|
|
38
|
+
p.id,
|
|
39
|
+
resourceIdToString(p.rid),
|
|
38
40
|
p.label,
|
|
39
41
|
formatDate(p.created),
|
|
40
42
|
formatDate(p.lastModified),
|
|
@@ -2,6 +2,7 @@ import { Args, Flags } from "@oclif/core";
|
|
|
2
2
|
import { PlCommand } from "../../base_command";
|
|
3
3
|
import { resolveProject, renameProject } from "../../project_ops";
|
|
4
4
|
import { outputJson, outputText } from "../../output";
|
|
5
|
+
import { resourceIdToString } from "@milaboratories/pl-client";
|
|
5
6
|
|
|
6
7
|
export default class ProjectRename extends PlCommand {
|
|
7
8
|
static override description = "Rename a project.";
|
|
@@ -31,7 +32,7 @@ export default class ProjectRename extends PlCommand {
|
|
|
31
32
|
await renameProject(pl, rid, flags.name);
|
|
32
33
|
|
|
33
34
|
if (flags.format === "json") {
|
|
34
|
-
outputJson({ id, rid:
|
|
35
|
+
outputJson({ id, rid: resourceIdToString(rid), label: flags.name });
|
|
35
36
|
} else {
|
|
36
37
|
outputText(`Renamed project to "${flags.name}"`);
|
|
37
38
|
}
|
package/src/project_ops.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PlClient,
|
|
2
|
-
import { field,
|
|
1
|
+
import type { PlClient, SignedResourceId, PlTransaction } from "@milaboratories/pl-client";
|
|
2
|
+
import { field, isNullSignedResourceId, resourceIdToString } from "@milaboratories/pl-client";
|
|
3
3
|
import {
|
|
4
4
|
ProjectMetaKey,
|
|
5
5
|
ProjectCreatedTimestamp,
|
|
@@ -10,16 +10,24 @@ import {
|
|
|
10
10
|
duplicateProject,
|
|
11
11
|
} from "@milaboratories/pl-middle-layer";
|
|
12
12
|
import type { ProjectMeta } from "@milaboratories/pl-middle-layer";
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
14
13
|
|
|
15
14
|
export interface ProjectEntry {
|
|
16
15
|
id: string;
|
|
17
|
-
rid:
|
|
16
|
+
rid: SignedResourceId;
|
|
18
17
|
label: string;
|
|
19
18
|
created: Date;
|
|
20
19
|
lastModified: Date;
|
|
21
20
|
}
|
|
22
21
|
|
|
22
|
+
export interface ProjectIdentity {
|
|
23
|
+
/** Project ID (resourceIdToString of resource ID) */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Signed resource ID for use in transactions */
|
|
26
|
+
rid: SignedResourceId;
|
|
27
|
+
/** UUID field name in the project list resource */
|
|
28
|
+
fieldName: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
export interface ProjectInfo extends ProjectEntry {
|
|
24
32
|
schemaVersion: string | undefined;
|
|
25
33
|
blockCount: number;
|
|
@@ -29,14 +37,14 @@ export interface ProjectInfo extends ProjectEntry {
|
|
|
29
37
|
/** List all projects from a project list resource. */
|
|
30
38
|
export async function listProjects(
|
|
31
39
|
pl: PlClient,
|
|
32
|
-
projectListRid:
|
|
40
|
+
projectListRid: SignedResourceId,
|
|
33
41
|
): Promise<ProjectEntry[]> {
|
|
34
42
|
return await pl.withReadTx("listProjects", async (tx) => {
|
|
35
43
|
const data = await tx.getResourceData(projectListRid, true);
|
|
36
44
|
const entries: ProjectEntry[] = [];
|
|
37
45
|
|
|
38
46
|
for (const f of data.fields) {
|
|
39
|
-
if (
|
|
47
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
40
48
|
|
|
41
49
|
const [metaStr, createdStr, modifiedStr] = await Promise.all([
|
|
42
50
|
tx.getKValueStringIfExists(f.value, ProjectMetaKey),
|
|
@@ -46,9 +54,10 @@ export async function listProjects(
|
|
|
46
54
|
|
|
47
55
|
const meta: ProjectMeta = metaStr ? JSON.parse(metaStr) : { label: "(unknown)" };
|
|
48
56
|
|
|
57
|
+
const projectId = resourceIdToString(f.value);
|
|
49
58
|
entries.push({
|
|
50
|
-
id:
|
|
51
|
-
rid:
|
|
59
|
+
id: projectId,
|
|
60
|
+
rid: f.value,
|
|
52
61
|
label: meta.label,
|
|
53
62
|
created: createdStr ? new Date(Number(createdStr)) : new Date(0),
|
|
54
63
|
lastModified: modifiedStr ? new Date(Number(modifiedStr)) : new Date(0),
|
|
@@ -63,16 +72,10 @@ export async function listProjects(
|
|
|
63
72
|
/** Get detailed info about a project. */
|
|
64
73
|
export async function getProjectInfo(
|
|
65
74
|
pl: PlClient,
|
|
66
|
-
projectListRid: ResourceId,
|
|
67
75
|
projectId: string,
|
|
76
|
+
rid: SignedResourceId,
|
|
68
77
|
): Promise<ProjectInfo> {
|
|
69
78
|
return await pl.withReadTx("getProjectInfo", async (tx) => {
|
|
70
|
-
const fieldData = await tx.getField(field(projectListRid, projectId));
|
|
71
|
-
if (isNullResourceId(fieldData.value)) {
|
|
72
|
-
throw new Error(`Project "${projectId}" not found.`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const rid = fieldData.value;
|
|
76
79
|
const kvs = await tx.listKeyValuesString(rid);
|
|
77
80
|
|
|
78
81
|
const metaKV = kvs.find((kv: { key: string }) => kv.key === ProjectMetaKey);
|
|
@@ -96,7 +99,7 @@ export async function getProjectInfo(
|
|
|
96
99
|
|
|
97
100
|
return {
|
|
98
101
|
id: projectId,
|
|
99
|
-
rid:
|
|
102
|
+
rid: rid,
|
|
100
103
|
label: meta.label,
|
|
101
104
|
created: createdKV ? new Date(Number(createdKV.value)) : new Date(0),
|
|
102
105
|
lastModified: modifiedKV ? new Date(Number(modifiedKV.value)) : new Date(0),
|
|
@@ -107,24 +110,29 @@ export async function getProjectInfo(
|
|
|
107
110
|
});
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
/** Resolve a project identifier (id or label) to its
|
|
113
|
+
/** Resolve a project identifier (id or label) to its project ID, ResourceId and field name. */
|
|
111
114
|
export async function resolveProject(
|
|
112
115
|
pl: PlClient,
|
|
113
|
-
projectListRid:
|
|
116
|
+
projectListRid: SignedResourceId,
|
|
114
117
|
identifier: string,
|
|
115
|
-
): Promise<
|
|
118
|
+
): Promise<ProjectIdentity> {
|
|
116
119
|
return await pl.withReadTx("resolveProject", async (tx) => {
|
|
117
120
|
const data = await tx.getResourceData(projectListRid, true);
|
|
118
121
|
for (const f of data.fields) {
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
123
|
+
const projectId = resourceIdToString(f.value);
|
|
124
|
+
if (projectId === identifier) {
|
|
125
|
+
return { id: projectId, rid: f.value, fieldName: f.name };
|
|
122
126
|
}
|
|
127
|
+
}
|
|
128
|
+
// Fallback: match by label
|
|
129
|
+
for (const f of data.fields) {
|
|
130
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
123
131
|
const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);
|
|
124
132
|
if (metaStr) {
|
|
125
133
|
const meta: ProjectMeta = JSON.parse(metaStr);
|
|
126
134
|
if (meta.label === identifier) {
|
|
127
|
-
return { id: f.
|
|
135
|
+
return { id: resourceIdToString(f.value), rid: f.value, fieldName: f.name };
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
}
|
|
@@ -136,12 +144,12 @@ export async function resolveProject(
|
|
|
136
144
|
/** Read all project labels within an existing transaction. */
|
|
137
145
|
export async function getExistingLabelsInTx(
|
|
138
146
|
tx: PlTransaction,
|
|
139
|
-
projectListRid:
|
|
147
|
+
projectListRid: SignedResourceId,
|
|
140
148
|
): Promise<string[]> {
|
|
141
149
|
const data = await tx.getResourceData(projectListRid, true);
|
|
142
150
|
const labels: string[] = [];
|
|
143
151
|
for (const f of data.fields) {
|
|
144
|
-
if (
|
|
152
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
145
153
|
const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);
|
|
146
154
|
if (metaStr) {
|
|
147
155
|
const meta: ProjectMeta = JSON.parse(metaStr);
|
|
@@ -154,7 +162,7 @@ export async function getExistingLabelsInTx(
|
|
|
154
162
|
/** Get all project labels from a project list. */
|
|
155
163
|
export async function getProjectLabels(
|
|
156
164
|
pl: PlClient,
|
|
157
|
-
projectListRid:
|
|
165
|
+
projectListRid: SignedResourceId,
|
|
158
166
|
): Promise<string[]> {
|
|
159
167
|
return await pl.withReadTx("getProjectLabels", async (tx) => {
|
|
160
168
|
return getExistingLabelsInTx(tx, projectListRid);
|
|
@@ -178,7 +186,7 @@ export function deduplicateName(baseName: string, existingLabels: string[]): str
|
|
|
178
186
|
/** Rename a project (update its label). */
|
|
179
187
|
export async function renameProject(
|
|
180
188
|
pl: PlClient,
|
|
181
|
-
projectRid:
|
|
189
|
+
projectRid: SignedResourceId,
|
|
182
190
|
newLabel: string,
|
|
183
191
|
): Promise<void> {
|
|
184
192
|
await pl.withWriteTx("renameProject", async (tx) => {
|
|
@@ -194,23 +202,23 @@ export async function renameProject(
|
|
|
194
202
|
/** Delete a project from the project list. */
|
|
195
203
|
export async function deleteProject(
|
|
196
204
|
pl: PlClient,
|
|
197
|
-
projectListRid:
|
|
198
|
-
|
|
205
|
+
projectListRid: SignedResourceId,
|
|
206
|
+
fieldName: string,
|
|
199
207
|
): Promise<void> {
|
|
200
208
|
await pl.withWriteTx("deleteProject", async (tx) => {
|
|
201
|
-
tx.removeField(field(projectListRid,
|
|
209
|
+
tx.removeField(field(projectListRid, fieldName));
|
|
202
210
|
await tx.commit();
|
|
203
211
|
});
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
/** Get the project list ResourceId for the connected user. */
|
|
207
|
-
export async function getProjectListRid(pl: PlClient): Promise<
|
|
215
|
+
export async function getProjectListRid(pl: PlClient): Promise<SignedResourceId> {
|
|
208
216
|
return await pl.withReadTx("getProjectList", async (tx) => {
|
|
209
217
|
const fieldData = await tx.getField({
|
|
210
218
|
resourceId: tx.clientRoot,
|
|
211
219
|
fieldName: ProjectsField,
|
|
212
220
|
});
|
|
213
|
-
if (
|
|
221
|
+
if (isNullSignedResourceId(fieldData.value)) {
|
|
214
222
|
throw new Error("No project list found for this user.");
|
|
215
223
|
}
|
|
216
224
|
return fieldData.value;
|
|
@@ -224,40 +232,26 @@ export async function getProjectListRid(pl: PlClient): Promise<ResourceId> {
|
|
|
224
232
|
export async function navigateToUserRoot(
|
|
225
233
|
pl: PlClient,
|
|
226
234
|
username: string,
|
|
227
|
-
): Promise<{ userRoot:
|
|
228
|
-
let userRootRid:
|
|
229
|
-
|
|
230
|
-
// Try new ListUserResources API first
|
|
231
|
-
try {
|
|
232
|
-
userRootRid = await pl.getUserRoot({ login: username });
|
|
233
|
-
} catch (err) {
|
|
234
|
-
if (!isUnimplementedError(err)) throw err;
|
|
235
|
-
// Backend doesn't support ListUserResources — fall through to legacy
|
|
236
|
-
}
|
|
235
|
+
): Promise<{ userRoot: SignedResourceId; projectListRid: SignedResourceId }> {
|
|
236
|
+
let userRootRid: SignedResourceId | undefined;
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
userRootRid = await pl.getUserRoot({ login: username, doNotCreate: true });
|
|
239
239
|
if (userRootRid === undefined) {
|
|
240
|
-
|
|
241
|
-
userRootRid = await pl.withReadTx("navigateToUserRoot:legacy", async (tx) => {
|
|
242
|
-
if (!(await tx.checkResourceNameExists(rootName))) {
|
|
243
|
-
throw new Error(`User "${username}" not found on this server (no root resource).`);
|
|
244
|
-
}
|
|
245
|
-
return await tx.getResourceByName(rootName);
|
|
246
|
-
});
|
|
240
|
+
throw new Error(`User "${username}" not found on this server (no root resource).`);
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
// Read the projects field from the resolved user root
|
|
250
|
-
return await pl.withReadTx("
|
|
244
|
+
return await pl.withReadTx("navigateToUserProjects", async (tx) => {
|
|
251
245
|
const projectsFieldData = await tx.getField({
|
|
252
|
-
resourceId: userRootRid
|
|
246
|
+
resourceId: userRootRid,
|
|
253
247
|
fieldName: ProjectsField,
|
|
254
248
|
});
|
|
255
249
|
|
|
256
|
-
if (
|
|
250
|
+
if (isNullSignedResourceId(projectsFieldData.value)) {
|
|
257
251
|
throw new Error(`User "${username}" has no project list.`);
|
|
258
252
|
}
|
|
259
253
|
|
|
260
|
-
return { userRoot: userRootRid
|
|
254
|
+
return { userRoot: userRootRid, projectListRid: projectsFieldData.value };
|
|
261
255
|
});
|
|
262
256
|
}
|
|
263
257
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"output-BLm2ZDFN.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, isUnimplementedError } 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 * 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: ResourceId; projectListRid: ResourceId }> {\n let userRootRid: ResourceId | undefined;\n\n // Try new ListUserResources API first\n try {\n userRootRid = await pl.getUserRoot({ login: username });\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n // Backend doesn't support ListUserResources — fall through to legacy\n }\n\n // Legacy fallback: SHA256 named resource lookup\n if (userRootRid === undefined) {\n const rootName = createHash(\"sha256\").update(username).digest(\"hex\");\n userRootRid = await pl.withReadTx(\"navigateToUserRoot:legacy\", async (tx) => {\n if (!(await tx.checkResourceNameExists(rootName))) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n return await tx.getResourceByName(rootName);\n });\n }\n\n // Read the projects field from the resolved user root\n return await pl.withReadTx(\"navigateToUserRoot\", async (tx) => {\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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"}
|