@platforma-sdk/pl-cli 0.2.41 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-BLm2ZDFN.js";
|
|
2
2
|
import { Args as g, Command as _, Flags as v } from "@oclif/core";
|
|
3
3
|
import { field as y, toGlobalResourceId as b } from "@milaboratories/pl-client";
|
|
4
4
|
import { ProjectMetaKey as x } from "@milaboratories/pl-middle-layer";
|
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-BLm2ZDFN.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,56 +1,56 @@
|
|
|
1
|
-
import { PlClient as e, UnauthenticatedPlClient as t, field as n, isNullResourceId as r,
|
|
2
|
-
import { ProjectCreatedTimestamp as
|
|
3
|
-
import { createHash as
|
|
1
|
+
import { PlClient as e, UnauthenticatedPlClient as t, field as n, isNullResourceId as r, isUnimplementedError as i, plAddressToConfig as a } from "@milaboratories/pl-client";
|
|
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
4
|
//#region src/connection.ts
|
|
5
|
-
async function
|
|
6
|
-
let r =
|
|
5
|
+
async function m(n) {
|
|
6
|
+
let r = a(n.address);
|
|
7
7
|
n.user !== void 0 && (r.user = n.user), n.password !== void 0 && (r.password = n.password);
|
|
8
|
-
let
|
|
9
|
-
if (await
|
|
8
|
+
let i = await t.build(r), o;
|
|
9
|
+
if (await i.requireAuth()) {
|
|
10
10
|
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
|
|
11
|
+
o = await i.login(r.user, r.password);
|
|
12
12
|
} else o = {};
|
|
13
13
|
return await e.init(r, { authInformation: o });
|
|
14
14
|
}
|
|
15
|
-
async function
|
|
16
|
-
let r =
|
|
17
|
-
return await e.init(r, { authInformation:
|
|
15
|
+
async function h(n) {
|
|
16
|
+
let r = a(n.address), i = await (await t.build(r)).login(n.adminUser, n.adminPassword);
|
|
17
|
+
return await e.init(r, { authInformation: i });
|
|
18
18
|
}
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/project_ops.ts
|
|
21
|
-
async function
|
|
21
|
+
async function g(e, t) {
|
|
22
22
|
return await e.withReadTx("listProjects", async (e) => {
|
|
23
23
|
let n = await e.getResourceData(t, !0), i = [];
|
|
24
24
|
for (let t of n.fields) {
|
|
25
25
|
if (r(t.value)) continue;
|
|
26
|
-
let [n,
|
|
27
|
-
e.getKValueStringIfExists(t.value,
|
|
28
|
-
e.getKValueStringIfExists(t.value,
|
|
29
|
-
e.getKValueStringIfExists(t.value,
|
|
26
|
+
let [n, a, l] = await Promise.all([
|
|
27
|
+
e.getKValueStringIfExists(t.value, c),
|
|
28
|
+
e.getKValueStringIfExists(t.value, o),
|
|
29
|
+
e.getKValueStringIfExists(t.value, s)
|
|
30
30
|
]), u = n ? JSON.parse(n) : { label: "(unknown)" };
|
|
31
31
|
i.push({
|
|
32
32
|
id: t.name,
|
|
33
33
|
rid: String(t.value),
|
|
34
34
|
label: u.label,
|
|
35
|
-
created:
|
|
35
|
+
created: a ? new Date(Number(a)) : /* @__PURE__ */ new Date(0),
|
|
36
36
|
lastModified: l ? new Date(Number(l)) : /* @__PURE__ */ new Date(0)
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
return i.sort((e, t) => t.lastModified.getTime() - e.lastModified.getTime()), i;
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
async function
|
|
42
|
+
async function _(e, t, i) {
|
|
43
43
|
return await e.withReadTx("getProjectInfo", async (e) => {
|
|
44
|
-
let
|
|
45
|
-
if (r(
|
|
46
|
-
let
|
|
44
|
+
let a = await e.getField(n(t, i));
|
|
45
|
+
if (r(a.value)) throw Error(`Project "${i}" not found.`);
|
|
46
|
+
let u = a.value, f = await e.listKeyValuesString(u), p = f.find((e) => e.key === c), m = f.find((e) => e.key === o), h = f.find((e) => e.key === s), g = f.find((e) => e.key === d), _ = f.find((e) => e.key === l), v = p ? JSON.parse(p.value) : { label: "(unknown)" }, y = g ? JSON.parse(g.value) : void 0, b = [];
|
|
47
47
|
if (_) {
|
|
48
48
|
let e = JSON.parse(_.value);
|
|
49
49
|
for (let t of e.groups ?? []) for (let e of t.blocks ?? []) b.push(e.id);
|
|
50
50
|
}
|
|
51
51
|
return {
|
|
52
52
|
id: i,
|
|
53
|
-
rid: String(
|
|
53
|
+
rid: String(u),
|
|
54
54
|
label: v.label,
|
|
55
55
|
created: m ? new Date(Number(m.value)) : /* @__PURE__ */ new Date(0),
|
|
56
56
|
lastModified: h ? new Date(Number(h.value)) : /* @__PURE__ */ new Date(0),
|
|
@@ -60,7 +60,7 @@ async function g(e, t, i) {
|
|
|
60
60
|
};
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
async function
|
|
63
|
+
async function v(e, t, n) {
|
|
64
64
|
return await e.withReadTx("resolveProject", async (e) => {
|
|
65
65
|
let i = await e.getResourceData(t, !0);
|
|
66
66
|
for (let t of i.fields) {
|
|
@@ -69,7 +69,7 @@ async function _(e, t, n) {
|
|
|
69
69
|
id: t.name,
|
|
70
70
|
rid: t.value
|
|
71
71
|
};
|
|
72
|
-
let i = await e.getKValueStringIfExists(t.value,
|
|
72
|
+
let i = await e.getKValueStringIfExists(t.value, c);
|
|
73
73
|
if (i && JSON.parse(i).label === n) return {
|
|
74
74
|
id: t.name,
|
|
75
75
|
rid: t.value
|
|
@@ -78,11 +78,11 @@ async function _(e, t, n) {
|
|
|
78
78
|
throw Error(`Project "${n}" not found (searched by id and label).`);
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
-
async function
|
|
81
|
+
async function y(e, t) {
|
|
82
82
|
let n = await e.getResourceData(t, !0), i = [];
|
|
83
83
|
for (let t of n.fields) {
|
|
84
84
|
if (r(t.value)) continue;
|
|
85
|
-
let n = await e.getKValueStringIfExists(t.value,
|
|
85
|
+
let n = await e.getKValueStringIfExists(t.value, c);
|
|
86
86
|
if (n) {
|
|
87
87
|
let e = JSON.parse(n);
|
|
88
88
|
i.push(e.label);
|
|
@@ -90,59 +90,70 @@ async function v(e, t) {
|
|
|
90
90
|
}
|
|
91
91
|
return i;
|
|
92
92
|
}
|
|
93
|
-
function
|
|
93
|
+
function b(e, t) {
|
|
94
94
|
let n = `${e} (Copy)`, r = 2;
|
|
95
95
|
for (; t.includes(n);) n = `${e} (Copy ${r})`, r++;
|
|
96
96
|
return n;
|
|
97
97
|
}
|
|
98
|
-
async function
|
|
98
|
+
async function x(e, t, n) {
|
|
99
99
|
await e.withWriteTx("renameProject", async (e) => {
|
|
100
|
-
let r = await e.getKValueString(t,
|
|
100
|
+
let r = await e.getKValueString(t, c), i = {
|
|
101
101
|
...JSON.parse(r),
|
|
102
102
|
label: n
|
|
103
103
|
};
|
|
104
|
-
e.setKValue(t,
|
|
104
|
+
e.setKValue(t, c, JSON.stringify(i)), e.setKValue(t, s, String(Date.now())), await e.commit();
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
async function
|
|
107
|
+
async function S(e, t, r) {
|
|
108
108
|
await e.withWriteTx("deleteProject", async (e) => {
|
|
109
109
|
e.removeField(n(t, r)), await e.commit();
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
-
async function
|
|
112
|
+
async function C(e) {
|
|
113
113
|
return await e.withReadTx("getProjectList", async (e) => {
|
|
114
114
|
let t = await e.getField({
|
|
115
115
|
resourceId: e.clientRoot,
|
|
116
|
-
fieldName:
|
|
116
|
+
fieldName: u
|
|
117
117
|
});
|
|
118
118
|
if (r(t.value)) throw Error("No project list found for this user.");
|
|
119
119
|
return t.value;
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
|
-
async function
|
|
123
|
-
let n
|
|
122
|
+
async function w(e, t) {
|
|
123
|
+
let n;
|
|
124
|
+
try {
|
|
125
|
+
n = await e.getUserRoot({ login: t });
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (!i(e)) throw e;
|
|
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
|
+
}
|
|
124
136
|
return await e.withReadTx("navigateToUserRoot", async (e) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
fieldName: l
|
|
137
|
+
let i = await e.getField({
|
|
138
|
+
resourceId: n,
|
|
139
|
+
fieldName: u
|
|
129
140
|
});
|
|
130
|
-
if (r(
|
|
141
|
+
if (r(i.value)) throw Error(`User "${t}" has no project list.`);
|
|
131
142
|
return {
|
|
132
|
-
userRoot:
|
|
133
|
-
projectListRid:
|
|
143
|
+
userRoot: n,
|
|
144
|
+
projectListRid: i.value
|
|
134
145
|
};
|
|
135
146
|
});
|
|
136
147
|
}
|
|
137
148
|
//#endregion
|
|
138
149
|
//#region src/output.ts
|
|
139
|
-
function
|
|
150
|
+
function T(e) {
|
|
140
151
|
console.log(e);
|
|
141
152
|
}
|
|
142
|
-
function
|
|
153
|
+
function E(e) {
|
|
143
154
|
console.log(JSON.stringify(e, null, 2));
|
|
144
155
|
}
|
|
145
|
-
function
|
|
156
|
+
function D(e, t) {
|
|
146
157
|
let n = e.map((e, n) => Math.max(e.length, ...t.map((e) => (e[n] ?? "").length))), r = n.map((e) => "-".repeat(e)).join(" ");
|
|
147
158
|
return [
|
|
148
159
|
e.map((e, t) => e.padEnd(n[t])).join(" "),
|
|
@@ -150,10 +161,10 @@ function E(e, t) {
|
|
|
150
161
|
...t.map((e) => e.map((e, t) => (e ?? "").padEnd(n[t])).join(" "))
|
|
151
162
|
].join("\n");
|
|
152
163
|
}
|
|
153
|
-
function
|
|
164
|
+
function O(e) {
|
|
154
165
|
return e.getTime() === 0 ? "(unknown)" : e.toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
155
166
|
}
|
|
156
167
|
//#endregion
|
|
157
|
-
export {
|
|
168
|
+
export { b as a, y as c, g as d, w as f, m as g, h, T as i, _ as l, v as m, D as n, S as o, x as p, E as r, f as s, O as t, C as u };
|
|
158
169
|
|
|
159
|
-
//# sourceMappingURL=output-
|
|
170
|
+
//# sourceMappingURL=output-BLm2ZDFN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
package/dist/project_ops.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare function deleteProject(pl: PlClient, projectListRid: ResourceId,
|
|
|
38
38
|
export declare function getProjectListRid(pl: PlClient): Promise<ResourceId>;
|
|
39
39
|
/**
|
|
40
40
|
* Navigates to a specific user's project list resource ID.
|
|
41
|
-
*
|
|
41
|
+
* Tries ListUserResources first (new backend), falls back to SHA256 named resource lookup.
|
|
42
42
|
*/
|
|
43
43
|
export declare function navigateToUserRoot(pl: PlClient, username: string): Promise<{
|
|
44
44
|
userRoot: ResourceId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/pl-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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.1
|
|
27
|
-
"@milaboratories/pl-middle-layer": "1.55.
|
|
26
|
+
"@milaboratories/pl-client": "3.2.1",
|
|
27
|
+
"@milaboratories/pl-middle-layer": "1.55.25"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "~24.5.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"vite-plugin-dts": "^4.5.3",
|
|
35
35
|
"vitest": "^4.1.3",
|
|
36
36
|
"@milaboratories/build-configs": "2.0.0",
|
|
37
|
-
"@milaboratories/ts-builder": "1.3.2",
|
|
38
37
|
"@milaboratories/oclif-index": "1.1.1",
|
|
38
|
+
"@milaboratories/ts-builder": "1.3.2",
|
|
39
39
|
"@milaboratories/ts-configs": "1.2.3"
|
|
40
40
|
},
|
|
41
41
|
"oclif": {
|
package/src/project_ops.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PlClient, ResourceId, PlTransaction } from "@milaboratories/pl-client";
|
|
2
|
-
import { field, isNullResourceId } from "@milaboratories/pl-client";
|
|
2
|
+
import { field, isNullResourceId, isUnimplementedError } from "@milaboratories/pl-client";
|
|
3
3
|
import {
|
|
4
4
|
ProjectMetaKey,
|
|
5
5
|
ProjectCreatedTimestamp,
|
|
@@ -219,23 +219,37 @@ export async function getProjectListRid(pl: PlClient): Promise<ResourceId> {
|
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
221
|
* Navigates to a specific user's project list resource ID.
|
|
222
|
-
*
|
|
222
|
+
* Tries ListUserResources first (new backend), falls back to SHA256 named resource lookup.
|
|
223
223
|
*/
|
|
224
224
|
export async function navigateToUserRoot(
|
|
225
225
|
pl: PlClient,
|
|
226
226
|
username: string,
|
|
227
227
|
): Promise<{ userRoot: ResourceId; projectListRid: ResourceId }> {
|
|
228
|
-
|
|
228
|
+
let userRootRid: ResourceId | undefined;
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
}
|
|
234
237
|
|
|
235
|
-
|
|
238
|
+
// Legacy fallback: SHA256 named resource lookup
|
|
239
|
+
if (userRootRid === undefined) {
|
|
240
|
+
const rootName = createHash("sha256").update(username).digest("hex");
|
|
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
|
+
});
|
|
247
|
+
}
|
|
236
248
|
|
|
249
|
+
// Read the projects field from the resolved user root
|
|
250
|
+
return await pl.withReadTx("navigateToUserRoot", async (tx) => {
|
|
237
251
|
const projectsFieldData = await tx.getField({
|
|
238
|
-
resourceId: userRootRid
|
|
252
|
+
resourceId: userRootRid!,
|
|
239
253
|
fieldName: ProjectsField,
|
|
240
254
|
});
|
|
241
255
|
|
|
@@ -243,7 +257,7 @@ export async function navigateToUserRoot(
|
|
|
243
257
|
throw new Error(`User "${username}" has no project list.`);
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
return { userRoot: userRootRid
|
|
260
|
+
return { userRoot: userRootRid!, projectListRid: projectsFieldData.value };
|
|
247
261
|
});
|
|
248
262
|
}
|
|
249
263
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"output-CXaoTHzT.js","names":[],"sources":["../src/connection.ts","../src/project_ops.ts","../src/output.ts"],"sourcesContent":["import {\n PlClient,\n UnauthenticatedPlClient,\n plAddressToConfig,\n type AuthInformation,\n type PlClientConfig,\n} from \"@milaboratories/pl-client\";\n\nexport interface PlConnectionOptions {\n address: string;\n user?: string;\n password?: string;\n}\n\nexport interface AdminConnectionOptions {\n address: string;\n adminUser: string;\n adminPassword: string;\n}\n\n/** Creates an authenticated PlClient from address + credentials. */\nexport async function createPlConnection(opts: PlConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n if (opts.user !== undefined) config.user = opts.user;\n if (opts.password !== undefined) config.password = opts.password;\n\n const unauth = await UnauthenticatedPlClient.build(config);\n let authInformation: AuthInformation;\n\n if (await unauth.requireAuth()) {\n if (config.user === undefined || config.password === undefined) {\n throw new Error(\n \"Server requires authentication but no credentials provided. \" +\n \"Use --user/--password flags or PL_USER/PL_PASSWORD env vars.\",\n );\n }\n authInformation = await unauth.login(config.user, config.password);\n } else {\n authInformation = {};\n }\n\n return await PlClient.init(config, { authInformation });\n}\n\n/** Creates a PlClient with admin/controller credentials. */\nexport async function createAdminPlConnection(opts: AdminConnectionOptions): Promise<PlClient> {\n const config: PlClientConfig = plAddressToConfig(opts.address);\n\n const unauth = await UnauthenticatedPlClient.build(config);\n const authInformation: AuthInformation = await unauth.login(opts.adminUser, opts.adminPassword);\n\n return await PlClient.init(config, { authInformation });\n}\n","import type { PlClient, ResourceId, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, isNullResourceId } from \"@milaboratories/pl-client\";\nimport {\n ProjectMetaKey,\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n SchemaVersionKey,\n ProjectStructureKey,\n ProjectsField,\n duplicateProject,\n} from \"@milaboratories/pl-middle-layer\";\nimport type { ProjectMeta } from \"@milaboratories/pl-middle-layer\";\nimport { createHash } from \"node:crypto\";\n\nexport interface ProjectEntry {\n id: string;\n rid: string;\n label: string;\n created: Date;\n lastModified: Date;\n}\n\nexport interface ProjectInfo extends ProjectEntry {\n schemaVersion: string | undefined;\n blockCount: number;\n blockIds: string[];\n}\n\n/** List all projects from a project list resource. */\nexport async function listProjects(\n pl: PlClient,\n projectListRid: ResourceId,\n): Promise<ProjectEntry[]> {\n return await pl.withReadTx(\"listProjects\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n const entries: ProjectEntry[] = [];\n\n for (const f of data.fields) {\n if (isNullResourceId(f.value)) continue;\n\n const [metaStr, createdStr, modifiedStr] = await Promise.all([\n tx.getKValueStringIfExists(f.value, ProjectMetaKey),\n tx.getKValueStringIfExists(f.value, ProjectCreatedTimestamp),\n tx.getKValueStringIfExists(f.value, ProjectLastModifiedTimestamp),\n ]);\n\n const meta: ProjectMeta = metaStr ? JSON.parse(metaStr) : { label: \"(unknown)\" };\n\n entries.push({\n id: f.name,\n rid: String(f.value),\n label: meta.label,\n created: createdStr ? new Date(Number(createdStr)) : new Date(0),\n lastModified: modifiedStr ? new Date(Number(modifiedStr)) : new Date(0),\n });\n }\n\n entries.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n return entries;\n });\n}\n\n/** Get detailed info about a project. */\nexport async function getProjectInfo(\n pl: PlClient,\n projectListRid: ResourceId,\n projectId: string,\n): Promise<ProjectInfo> {\n return await pl.withReadTx(\"getProjectInfo\", async (tx) => {\n const fieldData = await tx.getField(field(projectListRid, projectId));\n if (isNullResourceId(fieldData.value)) {\n throw new Error(`Project \"${projectId}\" not found.`);\n }\n\n const rid = fieldData.value;\n const kvs = await tx.listKeyValuesString(rid);\n\n const metaKV = kvs.find((kv: { key: string }) => kv.key === ProjectMetaKey);\n const createdKV = kvs.find((kv: { key: string }) => kv.key === ProjectCreatedTimestamp);\n const modifiedKV = kvs.find((kv: { key: string }) => kv.key === ProjectLastModifiedTimestamp);\n const schemaKV = kvs.find((kv: { key: string }) => kv.key === SchemaVersionKey);\n const structureKV = kvs.find((kv: { key: string }) => kv.key === ProjectStructureKey);\n\n const meta: ProjectMeta = metaKV ? JSON.parse(metaKV.value) : { label: \"(unknown)\" };\n const schemaVersion = schemaKV ? JSON.parse(schemaKV.value) : undefined;\n\n const blockIds: string[] = [];\n if (structureKV) {\n const structure = JSON.parse(structureKV.value);\n for (const group of structure.groups ?? []) {\n for (const block of group.blocks ?? []) {\n blockIds.push(block.id);\n }\n }\n }\n\n return {\n id: projectId,\n rid: String(rid),\n label: meta.label,\n created: createdKV ? new Date(Number(createdKV.value)) : new Date(0),\n lastModified: modifiedKV ? new Date(Number(modifiedKV.value)) : new Date(0),\n schemaVersion,\n blockCount: blockIds.length,\n blockIds,\n };\n });\n}\n\n/** Resolve a project identifier (id or label) to its field ID and ResourceId. */\nexport async function resolveProject(\n pl: PlClient,\n projectListRid: ResourceId,\n identifier: string,\n): Promise<{ id: string; rid: ResourceId }> {\n return await pl.withReadTx(\"resolveProject\", async (tx) => {\n const data = await tx.getResourceData(projectListRid, true);\n for (const f of data.fields) {\n if (isNullResourceId(f.value)) continue;\n if (f.name === identifier) {\n return { id: f.name, rid: f.value };\n }\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n if (meta.label === identifier) {\n return { id: f.name, rid: f.value };\n }\n }\n }\n\n throw new Error(`Project \"${identifier}\" not found (searched by id and label).`);\n });\n}\n\n/** Read all project labels within an existing transaction. */\nexport async function getExistingLabelsInTx(\n tx: PlTransaction,\n projectListRid: ResourceId,\n): Promise<string[]> {\n const data = await tx.getResourceData(projectListRid, true);\n const labels: string[] = [];\n for (const f of data.fields) {\n if (isNullResourceId(f.value)) continue;\n const metaStr = await tx.getKValueStringIfExists(f.value, ProjectMetaKey);\n if (metaStr) {\n const meta: ProjectMeta = JSON.parse(metaStr);\n labels.push(meta.label);\n }\n }\n return labels;\n}\n\n/** Get all project labels from a project list. */\nexport async function getProjectLabels(\n pl: PlClient,\n projectListRid: ResourceId,\n): Promise<string[]> {\n return await pl.withReadTx(\"getProjectLabels\", async (tx) => {\n return getExistingLabelsInTx(tx, projectListRid);\n });\n}\n\n/**\n * Deduplicates a project name against existing labels.\n * \"X\" → \"X (Copy)\" → \"X (Copy 2)\" → ...\n */\nexport function deduplicateName(baseName: string, existingLabels: string[]): string {\n let candidate = `${baseName} (Copy)`;\n let i = 2;\n while (existingLabels.includes(candidate)) {\n candidate = `${baseName} (Copy ${i})`;\n i++;\n }\n return candidate;\n}\n\n/** Rename a project (update its label). */\nexport async function renameProject(\n pl: PlClient,\n projectRid: ResourceId,\n newLabel: string,\n): Promise<void> {\n await pl.withWriteTx(\"renameProject\", async (tx) => {\n const metaStr = await tx.getKValueString(projectRid, ProjectMetaKey);\n const meta: ProjectMeta = JSON.parse(metaStr);\n const updated: ProjectMeta = { ...meta, label: newLabel };\n tx.setKValue(projectRid, ProjectMetaKey, JSON.stringify(updated));\n tx.setKValue(projectRid, ProjectLastModifiedTimestamp, String(Date.now()));\n await tx.commit();\n });\n}\n\n/** Delete a project from the project list. */\nexport async function deleteProject(\n pl: PlClient,\n projectListRid: ResourceId,\n projectId: string,\n): Promise<void> {\n await pl.withWriteTx(\"deleteProject\", async (tx) => {\n tx.removeField(field(projectListRid, projectId));\n await tx.commit();\n });\n}\n\n/** Get the project list ResourceId for the connected user. */\nexport async function getProjectListRid(pl: PlClient): Promise<ResourceId> {\n return await pl.withReadTx(\"getProjectList\", async (tx) => {\n const fieldData = await tx.getField({\n resourceId: tx.clientRoot,\n fieldName: ProjectsField,\n });\n if (isNullResourceId(fieldData.value)) {\n throw new Error(\"No project list found for this user.\");\n }\n return fieldData.value;\n });\n}\n\n/**\n * Navigates to a specific user's project list resource ID.\n * Computes SHA256(username) to find the user's root, then reads the \"projects\" field.\n */\nexport async function navigateToUserRoot(\n pl: PlClient,\n username: string,\n): Promise<{ userRoot: ResourceId; projectListRid: ResourceId }> {\n const rootName = createHash(\"sha256\").update(username).digest(\"hex\");\n\n return await pl.withReadTx(\"navigateToUserRoot\", async (tx) => {\n if (!(await tx.checkResourceNameExists(rootName))) {\n throw new Error(`User \"${username}\" not found on this server (no root resource).`);\n }\n\n const userRootRid = await tx.getResourceByName(rootName);\n\n const projectsFieldData = await tx.getField({\n resourceId: userRootRid,\n fieldName: ProjectsField,\n });\n\n if (isNullResourceId(projectsFieldData.value)) {\n throw new Error(`User \"${username}\" has no project list.`);\n }\n\n return { userRoot: userRootRid, projectListRid: projectsFieldData.value };\n });\n}\n\n// Re-export duplicateProject from pl-middle-layer for use in commands\nexport { duplicateProject };\n","export type OutputFormat = \"text\" | \"json\";\n\n/** Outputs a line of text to stdout. */\nexport function outputText(message: string): void {\n console.log(message);\n}\n\n/** Outputs data as formatted JSON to stdout. */\nexport function outputJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\n/** Formats a table as aligned text columns. */\nexport function formatTable(headers: string[], rows: string[][]): string {\n const colWidths = headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const sep = colWidths.map((w) => \"-\".repeat(w)).join(\" \");\n const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(\" \");\n const dataLines = rows.map((row) =>\n row.map((cell, i) => (cell ?? \"\").padEnd(colWidths[i])).join(\" \"),\n );\n\n return [headerLine, sep, ...dataLines].join(\"\\n\");\n}\n\n/** Formats a Date as a short human-readable string. */\nexport function formatDate(d: Date): string {\n if (d.getTime() === 0) return \"(unknown)\";\n return d\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \"Z\");\n}\n"],"mappings":"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"}
|