@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.
- package/bin/run.mjs +5 -7
- package/dist/base_command.d.ts +31 -42
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +181 -247
- package/dist/cli.js.map +1 -1
- package/dist/cmd/admin/copy-project.d.ts +2 -16
- package/dist/cmd/project/delete.d.ts +2 -18
- package/dist/cmd/project/duplicate.d.ts +2 -19
- package/dist/cmd/project/info.d.ts +2 -17
- package/dist/cmd/project/list.d.ts +2 -14
- package/dist/cmd/project/rename.d.ts +2 -18
- package/dist/cmd-opts.d.ts +7 -17
- package/package.json +6 -26
- package/src/base_command.ts +58 -78
- package/src/cli.ts +32 -0
- package/src/cmd/admin/copy-project.ts +75 -76
- package/src/cmd/project/delete.ts +45 -48
- package/src/cmd/project/duplicate.ts +52 -59
- package/src/cmd/project/info.ts +40 -42
- package/src/cmd/project/list.ts +44 -39
- package/src/cmd/project/rename.ts +23 -30
- package/src/cmd-opts.ts +37 -54
- package/dist/cmd/index.d.ts +0 -14
- package/src/cmd/index.ts +0 -17
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, 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":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/base_command.ts","../src/cmd-opts.ts","../src/cmd/project/list.ts","../src/cmd/project/info.ts","../src/cmd/project/duplicate.ts","../src/cmd/project/rename.ts","../src/cmd/project/delete.ts","../src/cmd/admin/copy-project.ts","../src/cli.ts"],"sourcesContent":["import type { PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { createPlConnection, createAdminPlConnection } from \"./connection\";\nimport { getProjectListRid, navigateToUserRoot } from \"./project_ops\";\n\n/**\n * Low-level: get an authenticated PlClient without resolving a project list.\n * Use this for commands that navigate to multiple users (e.g. admin copy-project).\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connectClient(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n}): Promise<PlClient> {\n if (flags.adminUser && flags.adminPassword) {\n return createAdminPlConnection({\n address: flags.address,\n adminUser: flags.adminUser,\n adminPassword: flags.adminPassword,\n });\n }\n return createPlConnection({\n address: flags.address,\n user: flags.user,\n password: flags.password,\n });\n}\n\n/**\n * Connect and resolve the project list for a single user.\n * In admin mode (--admin-user + --admin-password + --target-user), operates on the target user's data.\n * In user mode, operates on the authenticated user's own data.\n *\n * Callers own the returned client and must `await pl.close()` when done.\n */\nexport async function connect(flags: {\n address: string;\n user?: string;\n password?: string;\n adminUser?: string;\n adminPassword?: string;\n targetUser?: string;\n}): Promise<{ pl: PlClient; projectListRid: SignedResourceId }> {\n const hasAdminUser = !!flags.adminUser;\n const hasAdminPassword = !!flags.adminPassword;\n const hasTarget = !!flags.targetUser;\n\n // Validate flag combinations\n if (hasTarget && !(hasAdminUser && hasAdminPassword)) {\n throw new Error(\"--target-user requires --admin-user and --admin-password\");\n }\n if ((hasAdminUser || hasAdminPassword) && !hasTarget) {\n throw new Error(\"--admin-user/--admin-password require --target-user for project commands\");\n }\n\n const pl = await connectClient(flags);\n\n let projectListRid: SignedResourceId;\n if (hasTarget) {\n const nav = await navigateToUserRoot(pl, flags.targetUser!);\n projectListRid = nav.projectListRid;\n } else {\n projectListRid = await getProjectListRid(pl);\n }\n\n return { pl, projectListRid };\n}\n","import { Command, Option } from \"commander\";\n\n/** Add one or more groups of options to a command, preserving group order. */\nexport function addOptions(cmd: Command, ...optionGroups: Option[][]): Command {\n for (const group of optionGroups) {\n for (const opt of group) cmd.addOption(opt);\n }\n return cmd;\n}\n\nexport const GlobalOptions = (): Option[] => [\n new Option(\"-a, --address <url>\", \"Platforma server address\")\n .env(\"PL_ADDRESS\")\n .makeOptionMandatory(),\n new Option(\"-f, --format <format>\", \"Output format\").choices([\"text\", \"json\"]).default(\"text\"),\n];\n\nexport const UserAuthOptions = (): Option[] => [\n new Option(\"-u, --user <user>\", \"Username for authentication\").env(\"PL_USER\"),\n new Option(\"-p, --password <password>\", \"Password for authentication\").env(\"PL_PASSWORD\"),\n];\n\n/** Admin credentials only (for purely admin commands like copy-project). */\nexport const AdminAuthOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username\")\n .env(\"PL_ADMIN_USER\")\n .makeOptionMandatory(),\n new Option(\"--admin-password <password>\", \"Admin/controller password\")\n .env(\"PL_ADMIN_PASSWORD\")\n .makeOptionMandatory(),\n];\n\n/** Admin credentials + target user (for regular commands that can optionally operate on another user). */\nexport const AdminTargetOptions = (): Option[] => [\n new Option(\"--admin-user <user>\", \"Admin/controller username (enables admin mode)\").env(\n \"PL_ADMIN_USER\",\n ),\n new Option(\"--admin-password <password>\", \"Admin/controller password\").env(\"PL_ADMIN_PASSWORD\"),\n new Option(\n \"--target-user <user>\",\n \"Operate on this user's data (requires admin credentials)\",\n ).env(\"PL_TARGET_USER\"),\n];\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { listProjects } from \"../../project_ops\";\nimport { formatTable, formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectListCommand(): Command {\n const cmd = new Command(\"list\").description(\"List all projects for the authenticated user.\");\n\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const projects = await listProjects(pl, projectListRid);\n\n if (flags.format === \"json\") {\n outputJson(\n projects.map((p) => ({\n id: p.id,\n rid: resourceIdToString(p.rid),\n label: p.label,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n } else {\n if (projects.length === 0) {\n outputText(\"No projects found.\");\n return;\n }\n\n outputText(\n formatTable(\n [\"ID\", \"RID\", \"LABEL\", \"CREATED\", \"LAST MODIFIED\"],\n projects.map((p) => [\n p.id,\n resourceIdToString(p.rid),\n p.label,\n formatDate(p.created),\n formatDate(p.lastModified),\n ]),\n ),\n );\n outputText(`\\n${projects.length} project(s)`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, getProjectInfo } from \"../../project_ops\";\nimport { formatDate, outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectInfoCommand(): Command {\n const cmd = new Command(\"info\").description(\"Show detailed information about a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n const info = await getProjectInfo(pl, id, rid);\n\n if (flags.format === \"json\") {\n outputJson({\n id: info.id,\n rid: resourceIdToString(info.rid),\n label: info.label,\n schemaVersion: info.schemaVersion,\n blockCount: info.blockCount,\n blockIds: info.blockIds,\n created: info.created.toISOString(),\n lastModified: info.lastModified.toISOString(),\n });\n } else {\n outputText(`Project: ${info.label}`);\n outputText(`ID: ${info.id}`);\n outputText(`RID: ${resourceIdToString(info.rid)}`);\n outputText(`Schema: ${info.schemaVersion ?? \"(unknown)\"}`);\n outputText(`Blocks: ${info.blockCount}`);\n if (info.blockIds.length > 0) {\n for (const bid of info.blockIds) {\n outputText(` - ${bid}`);\n }\n }\n outputText(`Created: ${formatDate(info.created)}`);\n outputText(`Last Modified: ${formatDate(info.lastModified)}`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDuplicateCommand(): Command {\n const cmd = new Command(\"duplicate\").description(\n \"Duplicate a project within the same user. Auto-renames on collision by default.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"-n, --name <name>\", \"Name for the duplicate\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { rid: sourceRid } = await resolveProject(pl, projectListRid, project);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"duplicateProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, projectListRid);\n\n // Compute new label\n let newLabel: string;\n if (flags.name) {\n if (!flags.autoRename && existingLabels.includes(flags.name)) {\n throw new Error(`Project name \"${flags.name}\" already exists.`);\n }\n newLabel = existingLabels.includes(flags.name)\n ? deduplicateName(flags.name, existingLabels)\n : flags.name;\n } else {\n newLabel = deduplicateName(sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const projectResourceId = await newPrj.globalId;\n const projectId = resourceIdToString(projectResourceId);\n return { id: projectId, rid: projectResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({ id: result.id, rid: resourceIdToString(result.rid), label: result.label });\n } else {\n outputText(`Duplicated project as \"${result.label}\" (id: ${result.id})`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, renameProject } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\nimport { resourceIdToString } from \"@milaboratories/pl-client\";\n\nexport default function projectRenameCommand(): Command {\n const cmd = new Command(\"rename\").description(\"Rename a project.\");\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.addOption(new Option(\"-n, --name <name>\", \"New name for the project\").makeOptionMandatory());\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid } = await resolveProject(pl, projectListRid, project);\n\n await renameProject(pl, rid, flags.name);\n\n if (flags.format === \"json\") {\n outputJson({ id, rid: resourceIdToString(rid), label: flags.name });\n } else {\n outputText(`Renamed project to \"${flags.name}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport { createInterface } from \"node:readline\";\nimport { connect } from \"../../base_command\";\nimport { addOptions, GlobalOptions, UserAuthOptions, AdminTargetOptions } from \"../../cmd-opts\";\nimport { resolveProject, deleteProject, getProjectInfo } from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function projectDeleteCommand(): Command {\n const cmd = new Command(\"delete\").description(\n \"Delete a project. This permanently destroys all project data.\",\n );\n\n cmd.argument(\"<project>\", \"Project ID or label\");\n addOptions(cmd, GlobalOptions(), UserAuthOptions(), AdminTargetOptions());\n cmd.option(\"--force\", \"Skip confirmation\", false);\n\n cmd.action(async (project: string, flags) => {\n const { pl, projectListRid } = await connect(flags);\n try {\n const { id, rid, fieldName } = await resolveProject(pl, projectListRid, project);\n\n const info = await getProjectInfo(pl, id, rid);\n\n if (!flags.force) {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n `Delete project \"${info.label}\" (${info.blockCount} blocks)? [y/N] `,\n resolve,\n );\n });\n if (answer.toLowerCase() !== \"y\") {\n outputText(\"Aborted.\");\n return;\n }\n } finally {\n rl.close();\n }\n }\n\n await deleteProject(pl, projectListRid, fieldName);\n\n if (flags.format === \"json\") {\n outputJson({ deleted: true, id, label: info.label });\n } else {\n outputText(`Deleted project \"${info.label}\"`);\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command, Option } from \"commander\";\nimport { field, resourceIdToString } from \"@milaboratories/pl-client\";\nimport { ProjectMetaKey } from \"@milaboratories/pl-middle-layer\";\nimport { randomUUID } from \"node:crypto\";\nimport { connectClient } from \"../../base_command\";\nimport { addOptions, GlobalOptions, AdminAuthOptions } from \"../../cmd-opts\";\nimport {\n resolveProject,\n deduplicateName,\n duplicateProject,\n getExistingLabelsInTx,\n navigateToUserRoot,\n} from \"../../project_ops\";\nimport { outputJson, outputText } from \"../../output\";\n\nexport default function adminCopyProjectCommand(): Command {\n const cmd = new Command(\"copy-project\").description(\n \"Copy a project from one user to another. Requires admin/controller credentials.\",\n );\n\n addOptions(cmd, GlobalOptions(), AdminAuthOptions());\n cmd.addOption(\n new Option(\n \"--source-user <user>\",\n \"Username of the source project owner\",\n ).makeOptionMandatory(),\n );\n cmd.addOption(\n new Option(\"--source-project <project>\", \"Source project ID or label\").makeOptionMandatory(),\n );\n cmd.option(\n \"--target-user <user>\",\n \"Username of the target user (defaults to source-user for same-user copy)\",\n );\n cmd.option(\"-n, --name <name>\", \"Name for the copied project\");\n cmd.addOption(\n new Option(\"--auto-rename\", \"Auto-rename on collision (default: true)\").default(true),\n );\n cmd.option(\"--no-auto-rename\", \"Fail on name collision instead of auto-renaming\");\n\n cmd.action(async (flags) => {\n const pl = await connectClient(flags);\n try {\n const targetUser = flags.targetUser ?? flags.sourceUser;\n\n const source = await navigateToUserRoot(pl, flags.sourceUser);\n const { rid: sourceRid } = await resolveProject(\n pl,\n source.projectListRid,\n flags.sourceProject,\n );\n\n const target =\n targetUser === flags.sourceUser ? source : await navigateToUserRoot(pl, targetUser);\n\n const newId = randomUUID();\n\n const result = await pl.withWriteTx(\"adminCopyProject\", async (tx) => {\n const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);\n const sourceMeta = JSON.parse(sourceMetaStr);\n const sourceLabel: string = sourceMeta.label;\n\n const existingLabels = await getExistingLabelsInTx(tx, target.projectListRid);\n\n let newLabel: string = flags.name ?? sourceLabel;\n\n if (existingLabels.includes(newLabel)) {\n if (!flags.autoRename) {\n throw new Error(`Project name \"${newLabel}\" already exists for target user.`);\n }\n newLabel = deduplicateName(flags.name ?? sourceLabel, existingLabels);\n }\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(target.projectListRid, newId), \"Dynamic\", newPrj);\n await tx.commit();\n\n const prjResourceId = await newPrj.globalId;\n const prjId = resourceIdToString(prjResourceId);\n return { id: prjId, rid: prjResourceId, label: newLabel };\n });\n\n if (flags.format === \"json\") {\n outputJson({\n id: result.id,\n rid: resourceIdToString(result.rid),\n label: result.label,\n sourceUser: flags.sourceUser,\n targetUser,\n });\n } else {\n outputText(\n `Copied project from ${flags.sourceUser} to ${targetUser} as \"${result.label}\" (id: ${result.id}, rid: ${resourceIdToString(result.rid)})`,\n );\n }\n } finally {\n await pl.close();\n }\n });\n\n return cmd;\n}\n","import { Command } from \"commander\";\nimport projectListCommand from \"./cmd/project/list\";\nimport projectInfoCommand from \"./cmd/project/info\";\nimport projectDuplicateCommand from \"./cmd/project/duplicate\";\nimport projectRenameCommand from \"./cmd/project/rename\";\nimport projectDeleteCommand from \"./cmd/project/delete\";\nimport adminCopyProjectCommand from \"./cmd/admin/copy-project\";\n\nexport function buildProgram(): Command {\n const program = new Command();\n program.name(\"pl-cli\").description(\"Platforma command-line client\");\n\n const project = new Command(\"project\").description(\"Manage projects\");\n project.addCommand(projectListCommand());\n project.addCommand(projectInfoCommand());\n project.addCommand(projectDuplicateCommand());\n project.addCommand(projectRenameCommand());\n project.addCommand(projectDeleteCommand());\n program.addCommand(project);\n\n const admin = new Command(\"admin\").description(\n \"Admin operations (requires controller credentials)\",\n );\n admin.addCommand(adminCopyProjectCommand());\n program.addCommand(admin);\n\n return program;\n}\n\nexport async function run(argv: string[] = process.argv): Promise<void> {\n await buildProgram().parseAsync(argv);\n}\n"],"mappings":"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"}
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static flags: {
|
|
5
|
-
"source-user": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
6
|
-
"source-project": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
7
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
8
|
-
name: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
9
|
-
"auto-rename": import('@oclif/core/interfaces').BooleanFlag<boolean>;
|
|
10
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
14
|
-
};
|
|
15
|
-
run(): Promise<void>;
|
|
16
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function adminCopyProjectCommand(): Command;
|
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static args: {
|
|
5
|
-
project: import('@oclif/core/interfaces').Arg<string, Record<string, unknown>>;
|
|
6
|
-
};
|
|
7
|
-
static flags: {
|
|
8
|
-
force: import('@oclif/core/interfaces').BooleanFlag<boolean>;
|
|
9
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
10
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
14
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
15
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
16
|
-
};
|
|
17
|
-
run(): Promise<void>;
|
|
18
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function projectDeleteCommand(): Command;
|
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static args: {
|
|
5
|
-
project: import('@oclif/core/interfaces').Arg<string, Record<string, unknown>>;
|
|
6
|
-
};
|
|
7
|
-
static flags: {
|
|
8
|
-
name: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
9
|
-
"auto-rename": import('@oclif/core/interfaces').BooleanFlag<boolean>;
|
|
10
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
14
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
15
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
16
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
17
|
-
};
|
|
18
|
-
run(): Promise<void>;
|
|
19
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function projectDuplicateCommand(): Command;
|
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static args: {
|
|
5
|
-
project: import('@oclif/core/interfaces').Arg<string, Record<string, unknown>>;
|
|
6
|
-
};
|
|
7
|
-
static flags: {
|
|
8
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
9
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
10
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
14
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
15
|
-
};
|
|
16
|
-
run(): Promise<void>;
|
|
17
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function projectInfoCommand(): Command;
|
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static flags: {
|
|
5
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
6
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
7
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
8
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
9
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
10
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
};
|
|
13
|
-
run(): Promise<void>;
|
|
14
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function projectListCommand(): Command;
|
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
static description: string;
|
|
4
|
-
static args: {
|
|
5
|
-
project: import('@oclif/core/interfaces').Arg<string, Record<string, unknown>>;
|
|
6
|
-
};
|
|
7
|
-
static flags: {
|
|
8
|
-
name: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
9
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
10
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
11
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
14
|
-
address: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
15
|
-
format: import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
16
|
-
};
|
|
17
|
-
run(): Promise<void>;
|
|
18
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export default function projectRenameCommand(): Command;
|
package/dist/cmd-opts.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare const
|
|
6
|
-
user: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
7
|
-
password: import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
8
|
-
};
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
/** Add one or more groups of options to a command, preserving group order. */
|
|
3
|
+
export declare function addOptions(cmd: Command, ...optionGroups: Option[][]): Command;
|
|
4
|
+
export declare const GlobalOptions: () => Option[];
|
|
5
|
+
export declare const UserAuthOptions: () => Option[];
|
|
9
6
|
/** Admin credentials only (for purely admin commands like copy-project). */
|
|
10
|
-
export declare const
|
|
11
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
12
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string, import('@oclif/core/interfaces').CustomOptions>;
|
|
13
|
-
};
|
|
7
|
+
export declare const AdminAuthOptions: () => Option[];
|
|
14
8
|
/** Admin credentials + target user (for regular commands that can optionally operate on another user). */
|
|
15
|
-
export declare const
|
|
16
|
-
"admin-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
17
|
-
"admin-password": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
18
|
-
"target-user": import('@oclif/core/interfaces').OptionFlag<string | undefined, import('@oclif/core/interfaces').CustomOptions>;
|
|
19
|
-
};
|
|
9
|
+
export declare const AdminTargetOptions: () => Option[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/pl-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.72",
|
|
4
4
|
"description": "CLI for Platforma server state manipulation",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
26
|
-
"@milaboratories/pl-
|
|
27
|
-
"@milaboratories/pl-
|
|
25
|
+
"commander": "^15.0.0",
|
|
26
|
+
"@milaboratories/pl-middle-layer": "1.64.38",
|
|
27
|
+
"@milaboratories/pl-client": "3.11.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "~24.5.2",
|
|
@@ -33,31 +33,11 @@
|
|
|
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
|
-
"@milaboratories/ts-builder": "1.5.2",
|
|
38
36
|
"@milaboratories/build-configs": "2.0.0",
|
|
39
|
-
"@milaboratories/ts-
|
|
40
|
-
|
|
41
|
-
"oclif": {
|
|
42
|
-
"bin": "pl-cli",
|
|
43
|
-
"commands": {
|
|
44
|
-
"identifier": "COMMANDS",
|
|
45
|
-
"strategy": "explicit",
|
|
46
|
-
"target": "./dist/cli.js"
|
|
47
|
-
},
|
|
48
|
-
"dirname": "pl-cli",
|
|
49
|
-
"topicSeparator": " ",
|
|
50
|
-
"topics": {
|
|
51
|
-
"admin": {
|
|
52
|
-
"description": "Admin operations (requires controller credentials)"
|
|
53
|
-
},
|
|
54
|
-
"project": {
|
|
55
|
-
"description": "Manage projects"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
37
|
+
"@milaboratories/ts-builder": "1.6.0",
|
|
38
|
+
"@milaboratories/ts-configs": "1.3.0"
|
|
58
39
|
},
|
|
59
40
|
"scripts": {
|
|
60
|
-
"oclif:index": "oclif-index --commands-root=./src/cmd/ --index-file=./src/cmd/index.ts && node --run fmt",
|
|
61
41
|
"build": "vite build",
|
|
62
42
|
"test": "vitest run --passWithNoTests",
|
|
63
43
|
"do-pack": "rm -f *.tgz && pnpm pack && mv *.tgz package.tgz",
|
package/src/base_command.ts
CHANGED
|
@@ -1,90 +1,70 @@
|
|
|
1
|
-
import { Command } from "@oclif/core";
|
|
2
1
|
import type { PlClient, SignedResourceId } from "@milaboratories/pl-client";
|
|
3
2
|
import { createPlConnection, createAdminPlConnection } from "./connection";
|
|
4
3
|
import { getProjectListRid, navigateToUserRoot } from "./project_ops";
|
|
5
|
-
import { GlobalFlags, UserAuthFlags, AdminTargetFlags } from "./cmd-opts";
|
|
6
4
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"admin-password"?: string;
|
|
27
|
-
}): Promise<PlClient> {
|
|
28
|
-
if (this._pl) throw new Error("connectClient() called twice");
|
|
29
|
-
|
|
30
|
-
if (flags["admin-user"] && flags["admin-password"]) {
|
|
31
|
-
this._pl = await createAdminPlConnection({
|
|
32
|
-
address: flags.address,
|
|
33
|
-
adminUser: flags["admin-user"],
|
|
34
|
-
adminPassword: flags["admin-password"],
|
|
35
|
-
});
|
|
36
|
-
} else {
|
|
37
|
-
this._pl = await createPlConnection({
|
|
38
|
-
address: flags.address,
|
|
39
|
-
user: flags.user,
|
|
40
|
-
password: flags.password,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return this._pl;
|
|
5
|
+
/**
|
|
6
|
+
* Low-level: get an authenticated PlClient without resolving a project list.
|
|
7
|
+
* Use this for commands that navigate to multiple users (e.g. admin copy-project).
|
|
8
|
+
*
|
|
9
|
+
* Callers own the returned client and must `await pl.close()` when done.
|
|
10
|
+
*/
|
|
11
|
+
export async function connectClient(flags: {
|
|
12
|
+
address: string;
|
|
13
|
+
user?: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
adminUser?: string;
|
|
16
|
+
adminPassword?: string;
|
|
17
|
+
}): Promise<PlClient> {
|
|
18
|
+
if (flags.adminUser && flags.adminPassword) {
|
|
19
|
+
return createAdminPlConnection({
|
|
20
|
+
address: flags.address,
|
|
21
|
+
adminUser: flags.adminUser,
|
|
22
|
+
adminPassword: flags.adminPassword,
|
|
23
|
+
});
|
|
45
24
|
}
|
|
25
|
+
return createPlConnection({
|
|
26
|
+
address: flags.address,
|
|
27
|
+
user: flags.user,
|
|
28
|
+
password: flags.password,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (hasTarget && !(hasAdminUser && hasAdminPassword)) {
|
|
66
|
-
throw new Error("--target-user requires --admin-user and --admin-password");
|
|
67
|
-
}
|
|
68
|
-
if ((hasAdminUser || hasAdminPassword) && !hasTarget) {
|
|
69
|
-
throw new Error("--admin-user/--admin-password require --target-user for project commands");
|
|
70
|
-
}
|
|
32
|
+
/**
|
|
33
|
+
* Connect and resolve the project list for a single user.
|
|
34
|
+
* In admin mode (--admin-user + --admin-password + --target-user), operates on the target user's data.
|
|
35
|
+
* In user mode, operates on the authenticated user's own data.
|
|
36
|
+
*
|
|
37
|
+
* Callers own the returned client and must `await pl.close()` when done.
|
|
38
|
+
*/
|
|
39
|
+
export async function connect(flags: {
|
|
40
|
+
address: string;
|
|
41
|
+
user?: string;
|
|
42
|
+
password?: string;
|
|
43
|
+
adminUser?: string;
|
|
44
|
+
adminPassword?: string;
|
|
45
|
+
targetUser?: string;
|
|
46
|
+
}): Promise<{ pl: PlClient; projectListRid: SignedResourceId }> {
|
|
47
|
+
const hasAdminUser = !!flags.adminUser;
|
|
48
|
+
const hasAdminPassword = !!flags.adminPassword;
|
|
49
|
+
const hasTarget = !!flags.targetUser;
|
|
71
50
|
|
|
72
|
-
|
|
51
|
+
// Validate flag combinations
|
|
52
|
+
if (hasTarget && !(hasAdminUser && hasAdminPassword)) {
|
|
53
|
+
throw new Error("--target-user requires --admin-user and --admin-password");
|
|
54
|
+
}
|
|
55
|
+
if ((hasAdminUser || hasAdminPassword) && !hasTarget) {
|
|
56
|
+
throw new Error("--admin-user/--admin-password require --target-user for project commands");
|
|
57
|
+
}
|
|
73
58
|
|
|
74
|
-
|
|
75
|
-
if (hasTarget) {
|
|
76
|
-
const nav = await navigateToUserRoot(pl, flags["target-user"]!);
|
|
77
|
-
projectListRid = nav.projectListRid;
|
|
78
|
-
} else {
|
|
79
|
-
projectListRid = await getProjectListRid(pl);
|
|
80
|
-
}
|
|
59
|
+
const pl = await connectClient(flags);
|
|
81
60
|
|
|
82
|
-
|
|
61
|
+
let projectListRid: SignedResourceId;
|
|
62
|
+
if (hasTarget) {
|
|
63
|
+
const nav = await navigateToUserRoot(pl, flags.targetUser!);
|
|
64
|
+
projectListRid = nav.projectListRid;
|
|
65
|
+
} else {
|
|
66
|
+
projectListRid = await getProjectListRid(pl);
|
|
83
67
|
}
|
|
84
68
|
|
|
85
|
-
|
|
86
|
-
if (this._pl) {
|
|
87
|
-
await this._pl.close();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
69
|
+
return { pl, projectListRid };
|
|
90
70
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import projectListCommand from "./cmd/project/list";
|
|
3
|
+
import projectInfoCommand from "./cmd/project/info";
|
|
4
|
+
import projectDuplicateCommand from "./cmd/project/duplicate";
|
|
5
|
+
import projectRenameCommand from "./cmd/project/rename";
|
|
6
|
+
import projectDeleteCommand from "./cmd/project/delete";
|
|
7
|
+
import adminCopyProjectCommand from "./cmd/admin/copy-project";
|
|
8
|
+
|
|
9
|
+
export function buildProgram(): Command {
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program.name("pl-cli").description("Platforma command-line client");
|
|
12
|
+
|
|
13
|
+
const project = new Command("project").description("Manage projects");
|
|
14
|
+
project.addCommand(projectListCommand());
|
|
15
|
+
project.addCommand(projectInfoCommand());
|
|
16
|
+
project.addCommand(projectDuplicateCommand());
|
|
17
|
+
project.addCommand(projectRenameCommand());
|
|
18
|
+
project.addCommand(projectDeleteCommand());
|
|
19
|
+
program.addCommand(project);
|
|
20
|
+
|
|
21
|
+
const admin = new Command("admin").description(
|
|
22
|
+
"Admin operations (requires controller credentials)",
|
|
23
|
+
);
|
|
24
|
+
admin.addCommand(adminCopyProjectCommand());
|
|
25
|
+
program.addCommand(admin);
|
|
26
|
+
|
|
27
|
+
return program;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function run(argv: string[] = process.argv): Promise<void> {
|
|
31
|
+
await buildProgram().parseAsync(argv);
|
|
32
|
+
}
|