@platforma-sdk/pl-cli 0.7.70 → 0.7.72

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.
@@ -1,9 +1,9 @@
1
- import { Flags } from "@oclif/core";
1
+ import { Command, Option } from "commander";
2
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
- import { PlCommand } from "../../base_command";
6
- import { GlobalFlags, AdminAuthFlags } from "../../cmd-opts";
5
+ import { connectClient } from "../../base_command";
6
+ import { addOptions, GlobalOptions, AdminAuthOptions } from "../../cmd-opts";
7
7
  import {
8
8
  resolveProject,
9
9
  deduplicateName,
@@ -13,91 +13,90 @@ import {
13
13
  } from "../../project_ops";
14
14
  import { outputJson, outputText } from "../../output";
15
15
 
16
- export default class AdminCopyProject extends PlCommand {
17
- static override description =
18
- "Copy a project from one user to another. Requires admin/controller credentials.";
16
+ export default function adminCopyProjectCommand(): Command {
17
+ const cmd = new Command("copy-project").description(
18
+ "Copy a project from one user to another. Requires admin/controller credentials.",
19
+ );
19
20
 
20
- static override flags = {
21
- ...GlobalFlags,
22
- ...AdminAuthFlags,
23
- "source-user": Flags.string({
24
- summary: "Username of the source project owner",
25
- required: true,
26
- }),
27
- "source-project": Flags.string({
28
- summary: "Source project ID or label",
29
- required: true,
30
- }),
31
- "target-user": Flags.string({
32
- summary: "Username of the target user (defaults to source-user for same-user copy)",
33
- }),
34
- name: Flags.string({
35
- char: "n",
36
- summary: "Name for the copied project",
37
- helpValue: "<name>",
38
- }),
39
- "auto-rename": Flags.boolean({
40
- summary: "Auto-rename on collision (default: true)",
41
- default: true,
42
- allowNo: true,
43
- }),
44
- };
21
+ addOptions(cmd, GlobalOptions(), AdminAuthOptions());
22
+ cmd.addOption(
23
+ new Option(
24
+ "--source-user <user>",
25
+ "Username of the source project owner",
26
+ ).makeOptionMandatory(),
27
+ );
28
+ cmd.addOption(
29
+ new Option("--source-project <project>", "Source project ID or label").makeOptionMandatory(),
30
+ );
31
+ cmd.option(
32
+ "--target-user <user>",
33
+ "Username of the target user (defaults to source-user for same-user copy)",
34
+ );
35
+ cmd.option("-n, --name <name>", "Name for the copied project");
36
+ cmd.addOption(
37
+ new Option("--auto-rename", "Auto-rename on collision (default: true)").default(true),
38
+ );
39
+ cmd.option("--no-auto-rename", "Fail on name collision instead of auto-renaming");
45
40
 
46
- public async run(): Promise<void> {
47
- const { flags } = await this.parse(AdminCopyProject);
48
- const pl = await this.connectClient(flags);
41
+ cmd.action(async (flags) => {
42
+ const pl = await connectClient(flags);
43
+ try {
44
+ const targetUser = flags.targetUser ?? flags.sourceUser;
49
45
 
50
- const targetUser = flags["target-user"] ?? flags["source-user"];
51
-
52
- const source = await navigateToUserRoot(pl, flags["source-user"]);
53
- const { rid: sourceRid } = await resolveProject(
54
- pl,
55
- source.projectListRid,
56
- flags["source-project"],
57
- );
46
+ const source = await navigateToUserRoot(pl, flags.sourceUser);
47
+ const { rid: sourceRid } = await resolveProject(
48
+ pl,
49
+ source.projectListRid,
50
+ flags.sourceProject,
51
+ );
58
52
 
59
- const target =
60
- targetUser === flags["source-user"] ? source : await navigateToUserRoot(pl, targetUser);
53
+ const target =
54
+ targetUser === flags.sourceUser ? source : await navigateToUserRoot(pl, targetUser);
61
55
 
62
- const newId = randomUUID();
56
+ const newId = randomUUID();
63
57
 
64
- const result = await pl.withWriteTx("adminCopyProject", async (tx) => {
65
- const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
66
- const sourceMeta = JSON.parse(sourceMetaStr);
67
- const sourceLabel: string = sourceMeta.label;
58
+ const result = await pl.withWriteTx("adminCopyProject", async (tx) => {
59
+ const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
60
+ const sourceMeta = JSON.parse(sourceMetaStr);
61
+ const sourceLabel: string = sourceMeta.label;
68
62
 
69
- const existingLabels = await getExistingLabelsInTx(tx, target.projectListRid);
63
+ const existingLabels = await getExistingLabelsInTx(tx, target.projectListRid);
70
64
 
71
- let newLabel: string = flags.name ?? sourceLabel;
65
+ let newLabel: string = flags.name ?? sourceLabel;
72
66
 
73
- if (existingLabels.includes(newLabel)) {
74
- if (!flags["auto-rename"]) {
75
- throw new Error(`Project name "${newLabel}" already exists for target user.`);
67
+ if (existingLabels.includes(newLabel)) {
68
+ if (!flags.autoRename) {
69
+ throw new Error(`Project name "${newLabel}" already exists for target user.`);
70
+ }
71
+ newLabel = deduplicateName(flags.name ?? sourceLabel, existingLabels);
76
72
  }
77
- newLabel = deduplicateName(flags.name ?? sourceLabel, existingLabels);
78
- }
79
-
80
- const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });
81
- tx.createField(field(target.projectListRid, newId), "Dynamic", newPrj);
82
- await tx.commit();
83
73
 
84
- const prjResourceId = await newPrj.globalId;
85
- const prjId = resourceIdToString(prjResourceId);
86
- return { id: prjId, rid: prjResourceId, label: newLabel };
87
- });
74
+ const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });
75
+ tx.createField(field(target.projectListRid, newId), "Dynamic", newPrj);
76
+ await tx.commit();
88
77
 
89
- if (flags.format === "json") {
90
- outputJson({
91
- id: result.id,
92
- rid: resourceIdToString(result.rid),
93
- label: result.label,
94
- sourceUser: flags["source-user"],
95
- targetUser,
78
+ const prjResourceId = await newPrj.globalId;
79
+ const prjId = resourceIdToString(prjResourceId);
80
+ return { id: prjId, rid: prjResourceId, label: newLabel };
96
81
  });
97
- } else {
98
- outputText(
99
- `Copied project from ${flags["source-user"]} to ${targetUser} as "${result.label}" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,
100
- );
82
+
83
+ if (flags.format === "json") {
84
+ outputJson({
85
+ id: result.id,
86
+ rid: resourceIdToString(result.rid),
87
+ label: result.label,
88
+ sourceUser: flags.sourceUser,
89
+ targetUser,
90
+ });
91
+ } else {
92
+ outputText(
93
+ `Copied project from ${flags.sourceUser} to ${targetUser} as "${result.label}" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,
94
+ );
95
+ }
96
+ } finally {
97
+ await pl.close();
101
98
  }
102
- }
99
+ });
100
+
101
+ return cmd;
103
102
  }
@@ -1,58 +1,55 @@
1
- import { Args, Flags } from "@oclif/core";
1
+ import { Command } from "commander";
2
2
  import { createInterface } from "node:readline";
3
- import { PlCommand } from "../../base_command";
3
+ import { connect } from "../../base_command";
4
+ import { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from "../../cmd-opts";
4
5
  import { resolveProject, deleteProject, getProjectInfo } from "../../project_ops";
5
6
  import { outputJson, outputText } from "../../output";
6
7
 
7
- export default class ProjectDelete extends PlCommand {
8
- static override description = "Delete a project. This permanently destroys all project data.";
9
-
10
- static override args = {
11
- project: Args.string({
12
- description: "Project ID or label",
13
- required: true,
14
- }),
15
- };
16
-
17
- static override flags = {
18
- ...PlCommand.baseFlags,
19
- force: Flags.boolean({
20
- summary: "Skip confirmation",
21
- default: false,
22
- }),
23
- };
24
-
25
- public async run(): Promise<void> {
26
- const { args, flags } = await this.parse(ProjectDelete);
27
- const { pl, projectListRid } = await this.connect(flags);
28
- const { id, rid, fieldName } = await resolveProject(pl, projectListRid, args.project);
29
-
30
- const info = await getProjectInfo(pl, id, rid);
31
-
32
- if (!flags.force) {
33
- const rl = createInterface({ input: process.stdin, output: process.stderr });
34
- try {
35
- const answer = await new Promise<string>((resolve) => {
36
- rl.question(
37
- `Delete project "${info.label}" (${info.blockCount} blocks)? [y/N] `,
38
- resolve,
39
- );
40
- });
41
- if (answer.toLowerCase() !== "y") {
42
- outputText("Aborted.");
43
- return;
8
+ export default function projectDeleteCommand(): Command {
9
+ const cmd = new Command("delete").description(
10
+ "Delete a project. This permanently destroys all project data.",
11
+ );
12
+
13
+ cmd.argument("<project>", "Project ID or label");
14
+ addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());
15
+ cmd.option("--force", "Skip confirmation", false);
16
+
17
+ cmd.action(async (project: string, flags) => {
18
+ const { pl, projectListRid } = await connect(flags);
19
+ try {
20
+ const { id, rid, fieldName } = await resolveProject(pl, projectListRid, project);
21
+
22
+ const info = await getProjectInfo(pl, id, rid);
23
+
24
+ if (!flags.force) {
25
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
26
+ try {
27
+ const answer = await new Promise<string>((resolve) => {
28
+ rl.question(
29
+ `Delete project "${info.label}" (${info.blockCount} blocks)? [y/N] `,
30
+ resolve,
31
+ );
32
+ });
33
+ if (answer.toLowerCase() !== "y") {
34
+ outputText("Aborted.");
35
+ return;
36
+ }
37
+ } finally {
38
+ rl.close();
44
39
  }
45
- } finally {
46
- rl.close();
47
40
  }
48
- }
49
41
 
50
- await deleteProject(pl, projectListRid, fieldName);
42
+ await deleteProject(pl, projectListRid, fieldName);
51
43
 
52
- if (flags.format === "json") {
53
- outputJson({ deleted: true, id, label: info.label });
54
- } else {
55
- outputText(`Deleted project "${info.label}"`);
44
+ if (flags.format === "json") {
45
+ outputJson({ deleted: true, id, label: info.label });
46
+ } else {
47
+ outputText(`Deleted project "${info.label}"`);
48
+ }
49
+ } finally {
50
+ await pl.close();
56
51
  }
57
- }
52
+ });
53
+
54
+ return cmd;
58
55
  }
@@ -1,8 +1,9 @@
1
- import { Args, Flags } from "@oclif/core";
1
+ import { Command, Option } from "commander";
2
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
- import { PlCommand } from "../../base_command";
5
+ import { connect } from "../../base_command";
6
+ import { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from "../../cmd-opts";
6
7
  import {
7
8
  resolveProject,
8
9
  deduplicateName,
@@ -11,72 +12,64 @@ import {
11
12
  } from "../../project_ops";
12
13
  import { outputJson, outputText } from "../../output";
13
14
 
14
- export default class ProjectDuplicate extends PlCommand {
15
- static override description =
16
- "Duplicate a project within the same user. Auto-renames on collision by default.";
15
+ export default function projectDuplicateCommand(): Command {
16
+ const cmd = new Command("duplicate").description(
17
+ "Duplicate a project within the same user. Auto-renames on collision by default.",
18
+ );
17
19
 
18
- static override args = {
19
- project: Args.string({
20
- description: "Project ID or label",
21
- required: true,
22
- }),
23
- };
20
+ cmd.argument("<project>", "Project ID or label");
21
+ addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());
22
+ cmd.option("-n, --name <name>", "Name for the duplicate");
23
+ cmd.addOption(
24
+ new Option("--auto-rename", "Auto-rename on collision (default: true)").default(true),
25
+ );
26
+ cmd.option("--no-auto-rename", "Fail on name collision instead of auto-renaming");
24
27
 
25
- static override flags = {
26
- ...PlCommand.baseFlags,
27
- name: Flags.string({
28
- char: "n",
29
- summary: "Name for the duplicate",
30
- helpValue: "<name>",
31
- }),
32
- "auto-rename": Flags.boolean({
33
- summary: "Auto-rename on collision (default: true)",
34
- default: true,
35
- allowNo: true,
36
- }),
37
- };
28
+ cmd.action(async (project: string, flags) => {
29
+ const { pl, projectListRid } = await connect(flags);
30
+ try {
31
+ const { rid: sourceRid } = await resolveProject(pl, projectListRid, project);
38
32
 
39
- public async run(): Promise<void> {
40
- const { args, flags } = await this.parse(ProjectDuplicate);
41
- const { pl, projectListRid } = await this.connect(flags);
33
+ const newId = randomUUID();
42
34
 
43
- const { rid: sourceRid } = await resolveProject(pl, projectListRid, args.project);
35
+ const result = await pl.withWriteTx("duplicateProject", async (tx) => {
36
+ const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
37
+ const sourceMeta = JSON.parse(sourceMetaStr);
38
+ const sourceLabel: string = sourceMeta.label;
44
39
 
45
- const newId = randomUUID();
40
+ const existingLabels = await getExistingLabelsInTx(tx, projectListRid);
46
41
 
47
- const result = await pl.withWriteTx("duplicateProject", async (tx) => {
48
- const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
49
- const sourceMeta = JSON.parse(sourceMetaStr);
50
- const sourceLabel: string = sourceMeta.label;
51
-
52
- const existingLabels = await getExistingLabelsInTx(tx, projectListRid);
53
-
54
- // Compute new label
55
- let newLabel: string;
56
- if (flags.name) {
57
- if (!flags["auto-rename"] && existingLabels.includes(flags.name)) {
58
- throw new Error(`Project name "${flags.name}" already exists.`);
42
+ // Compute new label
43
+ let newLabel: string;
44
+ if (flags.name) {
45
+ if (!flags.autoRename && existingLabels.includes(flags.name)) {
46
+ throw new Error(`Project name "${flags.name}" already exists.`);
47
+ }
48
+ newLabel = existingLabels.includes(flags.name)
49
+ ? deduplicateName(flags.name, existingLabels)
50
+ : flags.name;
51
+ } else {
52
+ newLabel = deduplicateName(sourceLabel, existingLabels);
59
53
  }
60
- newLabel = existingLabels.includes(flags.name)
61
- ? deduplicateName(flags.name, existingLabels)
62
- : flags.name;
63
- } else {
64
- newLabel = deduplicateName(sourceLabel, existingLabels);
65
- }
66
54
 
67
- const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });
68
- tx.createField(field(projectListRid, newId), "Dynamic", newPrj);
69
- await tx.commit();
55
+ const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });
56
+ tx.createField(field(projectListRid, newId), "Dynamic", newPrj);
57
+ await tx.commit();
70
58
 
71
- const projectResourceId = await newPrj.globalId;
72
- const projectId = resourceIdToString(projectResourceId);
73
- return { id: projectId, rid: projectResourceId, label: newLabel };
74
- });
59
+ const projectResourceId = await newPrj.globalId;
60
+ const projectId = resourceIdToString(projectResourceId);
61
+ return { id: projectId, rid: projectResourceId, label: newLabel };
62
+ });
75
63
 
76
- if (flags.format === "json") {
77
- outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });
78
- } else {
79
- outputText(`Duplicated project as "${result.label}" (id: ${result.id})`);
64
+ if (flags.format === "json") {
65
+ outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });
66
+ } else {
67
+ outputText(`Duplicated project as "${result.label}" (id: ${result.id})`);
68
+ }
69
+ } finally {
70
+ await pl.close();
80
71
  }
81
- }
72
+ });
73
+
74
+ return cmd;
82
75
  }
@@ -1,53 +1,51 @@
1
- import { Args } from "@oclif/core";
2
- import { PlCommand } from "../../base_command";
1
+ import { Command } from "commander";
2
+ import { connect } from "../../base_command";
3
+ import { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from "../../cmd-opts";
3
4
  import { resolveProject, getProjectInfo } from "../../project_ops";
4
5
  import { formatDate, outputJson, outputText } from "../../output";
5
6
  import { resourceIdToString } from "@milaboratories/pl-client";
6
7
 
7
- export default class ProjectInfo extends PlCommand {
8
- static override description = "Show detailed information about a project.";
8
+ export default function projectInfoCommand(): Command {
9
+ const cmd = new Command("info").description("Show detailed information about a project.");
9
10
 
10
- static override args = {
11
- project: Args.string({
12
- description: "Project ID or label",
13
- required: true,
14
- }),
15
- };
11
+ cmd.argument("<project>", "Project ID or label");
12
+ addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());
16
13
 
17
- static override flags = {
18
- ...PlCommand.baseFlags,
19
- };
14
+ cmd.action(async (project: string, flags) => {
15
+ const { pl, projectListRid } = await connect(flags);
16
+ try {
17
+ const { id, rid } = await resolveProject(pl, projectListRid, project);
18
+ const info = await getProjectInfo(pl, id, rid);
20
19
 
21
- public async run(): Promise<void> {
22
- const { args, flags } = await this.parse(ProjectInfo);
23
- const { pl, projectListRid } = await this.connect(flags);
24
- const { id, rid } = await resolveProject(pl, projectListRid, args.project);
25
- const info = await getProjectInfo(pl, id, rid);
26
-
27
- if (flags.format === "json") {
28
- outputJson({
29
- id: info.id,
30
- rid: resourceIdToString(info.rid),
31
- label: info.label,
32
- schemaVersion: info.schemaVersion,
33
- blockCount: info.blockCount,
34
- blockIds: info.blockIds,
35
- created: info.created.toISOString(),
36
- lastModified: info.lastModified.toISOString(),
37
- });
38
- } else {
39
- outputText(`Project: ${info.label}`);
40
- outputText(`ID: ${info.id}`);
41
- outputText(`RID: ${resourceIdToString(info.rid)}`);
42
- outputText(`Schema: ${info.schemaVersion ?? "(unknown)"}`);
43
- outputText(`Blocks: ${info.blockCount}`);
44
- if (info.blockIds.length > 0) {
45
- for (const bid of info.blockIds) {
46
- outputText(` - ${bid}`);
20
+ if (flags.format === "json") {
21
+ outputJson({
22
+ id: info.id,
23
+ rid: resourceIdToString(info.rid),
24
+ label: info.label,
25
+ schemaVersion: info.schemaVersion,
26
+ blockCount: info.blockCount,
27
+ blockIds: info.blockIds,
28
+ created: info.created.toISOString(),
29
+ lastModified: info.lastModified.toISOString(),
30
+ });
31
+ } else {
32
+ outputText(`Project: ${info.label}`);
33
+ outputText(`ID: ${info.id}`);
34
+ outputText(`RID: ${resourceIdToString(info.rid)}`);
35
+ outputText(`Schema: ${info.schemaVersion ?? "(unknown)"}`);
36
+ outputText(`Blocks: ${info.blockCount}`);
37
+ if (info.blockIds.length > 0) {
38
+ for (const bid of info.blockIds) {
39
+ outputText(` - ${bid}`);
40
+ }
47
41
  }
42
+ outputText(`Created: ${formatDate(info.created)}`);
43
+ outputText(`Last Modified: ${formatDate(info.lastModified)}`);
48
44
  }
49
- outputText(`Created: ${formatDate(info.created)}`);
50
- outputText(`Last Modified: ${formatDate(info.lastModified)}`);
45
+ } finally {
46
+ await pl.close();
51
47
  }
52
- }
48
+ });
49
+
50
+ return cmd;
53
51
  }
@@ -1,49 +1,54 @@
1
- import { PlCommand } from "../../base_command";
1
+ import { Command } from "commander";
2
+ import { connect } from "../../base_command";
3
+ import { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from "../../cmd-opts";
2
4
  import { listProjects } from "../../project_ops";
3
5
  import { formatTable, formatDate, outputJson, outputText } from "../../output";
4
6
  import { resourceIdToString } from "@milaboratories/pl-client";
5
7
 
6
- export default class ProjectList extends PlCommand {
7
- static override description = "List all projects for the authenticated user.";
8
+ export default function projectListCommand(): Command {
9
+ const cmd = new Command("list").description("List all projects for the authenticated user.");
8
10
 
9
- static override flags = {
10
- ...PlCommand.baseFlags,
11
- };
11
+ addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());
12
12
 
13
- public async run(): Promise<void> {
14
- const { flags } = await this.parse(ProjectList);
15
- const { pl, projectListRid } = await this.connect(flags);
16
- const projects = await listProjects(pl, projectListRid);
13
+ cmd.action(async (flags) => {
14
+ const { pl, projectListRid } = await connect(flags);
15
+ try {
16
+ const projects = await listProjects(pl, projectListRid);
17
17
 
18
- if (flags.format === "json") {
19
- outputJson(
20
- projects.map((p) => ({
21
- id: p.id,
22
- rid: resourceIdToString(p.rid),
23
- label: p.label,
24
- created: p.created.toISOString(),
25
- lastModified: p.lastModified.toISOString(),
26
- })),
27
- );
28
- } else {
29
- if (projects.length === 0) {
30
- outputText("No projects found.");
31
- return;
32
- }
18
+ if (flags.format === "json") {
19
+ outputJson(
20
+ projects.map((p) => ({
21
+ id: p.id,
22
+ rid: resourceIdToString(p.rid),
23
+ label: p.label,
24
+ created: p.created.toISOString(),
25
+ lastModified: p.lastModified.toISOString(),
26
+ })),
27
+ );
28
+ } else {
29
+ if (projects.length === 0) {
30
+ outputText("No projects found.");
31
+ return;
32
+ }
33
33
 
34
- outputText(
35
- formatTable(
36
- ["ID", "RID", "LABEL", "CREATED", "LAST MODIFIED"],
37
- projects.map((p) => [
38
- p.id,
39
- resourceIdToString(p.rid),
40
- p.label,
41
- formatDate(p.created),
42
- formatDate(p.lastModified),
43
- ]),
44
- ),
45
- );
46
- outputText(`\n${projects.length} project(s)`);
34
+ outputText(
35
+ formatTable(
36
+ ["ID", "RID", "LABEL", "CREATED", "LAST MODIFIED"],
37
+ projects.map((p) => [
38
+ p.id,
39
+ resourceIdToString(p.rid),
40
+ p.label,
41
+ formatDate(p.created),
42
+ formatDate(p.lastModified),
43
+ ]),
44
+ ),
45
+ );
46
+ outputText(`\n${projects.length} project(s)`);
47
+ }
48
+ } finally {
49
+ await pl.close();
47
50
  }
48
- }
51
+ });
52
+
53
+ return cmd;
49
54
  }