@kolatts/pncli 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ assertValidCustomFields,
3
4
  getGlobalConfigPath,
4
5
  loadConfig,
5
6
  loadJsonFile,
@@ -9,13 +10,13 @@ import {
9
10
  setRepoConfigValue,
10
11
  writeGlobalConfig,
11
12
  writeRepoConfig
12
- } from "./chunk-3JICA4FZ.js";
13
+ } from "./chunk-QHJ2HM5J.js";
13
14
  import {
14
- createHttpClient
15
- } from "./chunk-VCECVN3F.js";
15
+ createHttpClient,
16
+ validateAlationAccessToken
17
+ } from "./chunk-FAMJPKO4.js";
16
18
  import {
17
19
  ExitCode,
18
- PncliError,
19
20
  fail,
20
21
  log,
21
22
  setGlobalOptions,
@@ -23,7 +24,10 @@ import {
23
24
  success,
24
25
  warn,
25
26
  writeRawOutput
26
- } from "./chunk-BHZORLOF.js";
27
+ } from "./chunk-BOFSNQQ2.js";
28
+ import {
29
+ PncliError
30
+ } from "./chunk-HRNOAQDN.js";
27
31
  import {
28
32
  AdoCoreClient
29
33
  } from "./chunk-VDL32PSG.js";
@@ -604,7 +608,7 @@ var BitbucketClient = class {
604
608
  project: { key: opts.project }
605
609
  }
606
610
  },
607
- reviewers: (opts.reviewers ?? []).map((slug) => ({ user: { slug } }))
611
+ reviewers: (opts.reviewers ?? []).map((name) => ({ user: { name } }))
608
612
  }
609
613
  }
610
614
  );
@@ -613,7 +617,7 @@ var BitbucketClient = class {
613
617
  const body = { version: opts.version };
614
618
  if (opts.title) body.title = opts.title;
615
619
  if (opts.description !== void 0) body.description = opts.description;
616
- if (opts.reviewers) body.reviewers = opts.reviewers.map((slug) => ({ user: { slug } }));
620
+ if (opts.reviewers) body.reviewers = opts.reviewers.map((name) => ({ user: { name } }));
617
621
  return this.http.bitbucket(
618
622
  `${API}/projects/${opts.project}/repos/${opts.repo}/pull-requests/${opts.id}`,
619
623
  { method: "PUT", body }
@@ -1228,6 +1232,7 @@ function collectMetaFields(projects, issueType) {
1228
1232
  function buildFieldMap(fields) {
1229
1233
  const byName = /* @__PURE__ */ new Map();
1230
1234
  const byId = /* @__PURE__ */ new Map();
1235
+ assertValidCustomFields(fields);
1231
1236
  for (const f of fields) {
1232
1237
  byName.set(f.name.toLowerCase(), f);
1233
1238
  byId.set(f.id, f);
@@ -6488,6 +6493,20 @@ async function runCredentialChecks(cfg, http) {
6488
6493
  results.figma = categorize(err);
6489
6494
  }
6490
6495
  }
6496
+ if (!cfg.alation.refreshToken) {
6497
+ results.alation = { status: "blank", message: "not configured" };
6498
+ } else if (!cfg.alation.baseUrl) {
6499
+ results.alation = { status: "error", message: "baseUrl not configured" };
6500
+ } else if (cfg.alation.userId === void 0 || cfg.alation.userId === "") {
6501
+ results.alation = { status: "error", message: "userId not configured" };
6502
+ } else {
6503
+ try {
6504
+ await validateAlationAccessToken(cfg);
6505
+ results.alation = { status: "valid", message: "ok" };
6506
+ } catch (err) {
6507
+ results.alation = categorize(err);
6508
+ }
6509
+ }
6491
6510
  return results;
6492
6511
  }
6493
6512
 
@@ -6553,6 +6572,47 @@ function marketplaceLabel(m) {
6553
6572
  function scrubToken(msg) {
6554
6573
  return msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@");
6555
6574
  }
6575
+ function isConfiguredGitHubHost(repoUrl, githubBaseUrl) {
6576
+ try {
6577
+ const host = new URL(repoUrl).hostname;
6578
+ if (host === "github.com") return true;
6579
+ if (!githubBaseUrl) return false;
6580
+ return new URL(githubBaseUrl).hostname === host;
6581
+ } catch {
6582
+ return false;
6583
+ }
6584
+ }
6585
+ function resolveMarketplaceToken(explicitToken, repoUrl) {
6586
+ if (explicitToken) return explicitToken;
6587
+ if (!repoUrl) return void 0;
6588
+ const resolved = loadConfig();
6589
+ if (!isConfiguredGitHubHost(repoUrl, resolved.github.baseUrl)) return void 0;
6590
+ return resolved.github.token;
6591
+ }
6592
+ var GIT_AUTH_FAILURE_PATTERNS = [
6593
+ /invalid username or (password|token)/i,
6594
+ /authentication failed/i,
6595
+ /could not read username/i,
6596
+ /could not read password/i,
6597
+ /support for password authentication was removed/i
6598
+ ];
6599
+ var GIT_NOT_FOUND_PATTERNS = [/repository not found/i];
6600
+ function describeGitFailure(rawMessage, marketplaceName, tokenSource) {
6601
+ const msg = scrubToken(rawMessage);
6602
+ if (GIT_AUTH_FAILURE_PATTERNS.some((p) => p.test(msg))) {
6603
+ const hint = tokenSource === "explicit" ? `The token configured for marketplace "${marketplaceName}" was rejected \u2014 it may be expired, revoked, or missing required scopes. Update it with: pncli skills marketplace add <url> --token <new-token>` : tokenSource === "fallback" ? `The GitHub token pncli is using (PNCLI_GITHUB_TOKEN / GITHUB_TOKEN / github.token) was rejected for marketplace "${marketplaceName}" \u2014 it may be expired, revoked, or missing required scopes for this repo. Rotate that token, or set one specifically for this marketplace: pncli skills marketplace add <url> --token <new-token>` : `Marketplace "${marketplaceName}" requires authentication but no token is configured. Add one with: pncli skills marketplace add <url> --token <token>`;
6604
+ return new Error(`${hint}
6605
+
6606
+ Git reported: ${msg}`);
6607
+ }
6608
+ if (GIT_NOT_FOUND_PATTERNS.some((p) => p.test(msg))) {
6609
+ const hint = tokenSource === "explicit" ? `Repository for marketplace "${marketplaceName}" was not found \u2014 check the URL and that the configured token has access to it.` : tokenSource === "fallback" ? `Repository for marketplace "${marketplaceName}" was not found \u2014 check the URL and that pncli's configured GitHub token has access to it.` : `Repository for marketplace "${marketplaceName}" was not found \u2014 if it's private, add a token: pncli skills marketplace add <url> --token <token>`;
6610
+ return new Error(`${hint}
6611
+
6612
+ Git reported: ${msg}`);
6613
+ }
6614
+ return new Error(msg);
6615
+ }
6556
6616
  function assertInteractive(hint) {
6557
6617
  if (!process.stdin.isTTY) {
6558
6618
  throw new Error(`This selection requires an interactive terminal. ${hint}`);
@@ -7019,7 +7079,7 @@ function upsertMarketplace(all, entry) {
7019
7079
  all.push(entry);
7020
7080
  }
7021
7081
  }
7022
- function cloneOrReuseMarketplace(url, resolvedPath, opts) {
7082
+ function cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName) {
7023
7083
  const hasGit = fs2.existsSync(path7.join(resolvedPath, ".git"));
7024
7084
  if (fs2.existsSync(resolvedPath) && !hasGit && fs2.readdirSync(resolvedPath).length > 0) {
7025
7085
  throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
@@ -7028,9 +7088,10 @@ function cloneOrReuseMarketplace(url, resolvedPath, opts) {
7028
7088
  warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config and re-installing plugins.`);
7029
7089
  return;
7030
7090
  }
7091
+ const resolvedToken = resolveMarketplaceToken(opts.token, url);
7031
7092
  const branchLabel = opts.branch ?? "remote default";
7032
7093
  warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
7033
- const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
7094
+ const cloneUrl = resolvedToken ? injectTokenIntoUrl(url, resolvedToken) : url;
7034
7095
  const cloneArgs = ["clone"];
7035
7096
  if (opts.branch) cloneArgs.push("--branch", opts.branch);
7036
7097
  cloneArgs.push(cloneUrl, resolvedPath);
@@ -7045,15 +7106,17 @@ function cloneOrReuseMarketplace(url, resolvedPath, opts) {
7045
7106
  }
7046
7107
  if (!cloneActuallySucceeded) {
7047
7108
  const msg = e instanceof Error ? e.message : String(e);
7048
- throw new Error(scrubToken(msg));
7109
+ const tokenSource = opts.token ? "explicit" : resolvedToken ? "fallback" : "none";
7110
+ throw describeGitFailure(msg, marketplaceName, tokenSource);
7049
7111
  }
7050
7112
  }
7051
7113
  }
7052
7114
  function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
7115
+ const resolvedToken = resolveMarketplaceToken(token, repoUrl);
7053
7116
  warn(`Pulling latest content for "${marketplaceName}"...`);
7054
7117
  const gitArgs = ["-C", marketplacePath];
7055
- if (repoUrl && token) {
7056
- gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
7118
+ if (repoUrl && resolvedToken) {
7119
+ gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, resolvedToken)}`);
7057
7120
  }
7058
7121
  gitArgs.push("pull");
7059
7122
  let pullOutput;
@@ -7061,7 +7124,8 @@ function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
7061
7124
  pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
7062
7125
  } catch (e) {
7063
7126
  const msg = e instanceof Error ? e.message : String(e);
7064
- throw new Error(scrubToken(msg));
7127
+ const tokenSource = token ? "explicit" : resolvedToken ? "fallback" : "none";
7128
+ throw describeGitFailure(msg, marketplaceName, tokenSource);
7065
7129
  }
7066
7130
  const updated = !pullOutput.includes("Already up to date");
7067
7131
  if (pullOutput.trim() && updated) warn(pullOutput.trim());
@@ -7152,7 +7216,7 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter, installedOnly
7152
7216
  function performMarketplaceAdd(url, localPath, opts) {
7153
7217
  const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
7154
7218
  const marketplaceName = opts.name ?? repoNameFromUrl(url);
7155
- cloneOrReuseMarketplace(url, resolvedPath, opts);
7219
+ cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName);
7156
7220
  const configPath = getGlobalConfigPath();
7157
7221
  const { existing, all } = loadMarketplacesConfig(configPath);
7158
7222
  const entry = {
@@ -8307,6 +8371,16 @@ function registerConfigCommands(program2) {
8307
8371
  } else {
8308
8372
  results.figma = { ok: null, message: "not configured" };
8309
8373
  }
8374
+ if (cfg.alation.baseUrl && cfg.alation.refreshToken && cfg.alation.userId !== void 0 && cfg.alation.userId !== "") {
8375
+ try {
8376
+ await validateAlationAccessToken(cfg);
8377
+ results.alation = { ok: true, message: "connected" };
8378
+ } catch (err) {
8379
+ results.alation = { ok: false, message: err instanceof Error ? err.message : String(err) };
8380
+ }
8381
+ } else {
8382
+ results.alation = { ok: null, message: "not configured" };
8383
+ }
8310
8384
  success(results, "config", "test", start);
8311
8385
  } catch (err) {
8312
8386
  fail(err, "config", "test", start);
@@ -8348,7 +8422,8 @@ function registerConfigCommands(program2) {
8348
8422
  ...dynamicEnvKeys,
8349
8423
  "logscale",
8350
8424
  "splitio",
8351
- "figma"
8425
+ "figma",
8426
+ "alation"
8352
8427
  ];
8353
8428
  if (cmdOpts.output === "table") {
8354
8429
  const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
@@ -8964,6 +9039,47 @@ async function initGlobalConfig(start) {
8964
9039
  }
8965
9040
  }
8966
9041
  }
9042
+ process.stderr.write("\n\u2500\u2500 Alation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
9043
+ const useAlation = await confirm({
9044
+ message: "Configure Alation for data catalog and Document Hub access?",
9045
+ default: false
9046
+ });
9047
+ let alationBaseUrl = "";
9048
+ let alationRefreshToken = "";
9049
+ let alationUserId = "";
9050
+ if (useAlation) {
9051
+ process.stderr.write(" Generate a refresh token in Alation under your profile \u2192 Settings \u2192 Authentication \u2192 Create Refresh Token.\n");
9052
+ process.stderr.write(" Note the numeric user ID shown with it; pncli exchanges the refresh token for short-lived API tokens itself.\n");
9053
+ alationBaseUrl = await input2({
9054
+ message: "Alation base URL (e.g. https://alation.imagile.dev):",
9055
+ validate: (v) => v.trim().length > 0 || "Required"
9056
+ });
9057
+ alationRefreshToken = await password({
9058
+ message: "Alation refresh token:"
9059
+ });
9060
+ alationUserId = await input2({
9061
+ message: "Alation user ID (numeric):",
9062
+ validate: (v) => /^\d+$/.test(v.trim()) || "Must be a positive integer"
9063
+ });
9064
+ if (alationBaseUrl && alationRefreshToken && alationUserId) {
9065
+ process.stderr.write("\n Verifying connection...\n");
9066
+ try {
9067
+ const tempConfig = {
9068
+ ...loadConfig(),
9069
+ alation: {
9070
+ baseUrl: normalizeBaseUrl(alationBaseUrl),
9071
+ refreshToken: alationRefreshToken,
9072
+ userId: alationUserId.trim()
9073
+ }
9074
+ };
9075
+ await validateAlationAccessToken(tempConfig);
9076
+ process.stderr.write(" Connected.\n");
9077
+ } catch (err) {
9078
+ warn(`Could not connect to Alation: ${err instanceof Error ? err.message : String(err)}`);
9079
+ warn("Config will be saved anyway. Check your URL, refresh token, and user ID and re-run pncli config init or pncli config test.");
9080
+ }
9081
+ }
9082
+ }
8967
9083
  process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
8968
9084
  const jiraProject = await input2({
8969
9085
  message: "Default Jira project key (optional):",
@@ -9121,6 +9237,13 @@ async function initGlobalConfig(start) {
9121
9237
  token: figmaToken
9122
9238
  }
9123
9239
  } : {},
9240
+ ...useAlation && alationBaseUrl ? {
9241
+ alation: {
9242
+ baseUrl: normalizeBaseUrl(alationBaseUrl),
9243
+ refreshToken: alationRefreshToken || void 0,
9244
+ userId: alationUserId.trim() || void 0
9245
+ }
9246
+ } : {},
9124
9247
  defaults: {
9125
9248
  jira: {
9126
9249
  project: jiraProject || void 0
@@ -10009,10 +10132,10 @@ function registerAdoWorkCommands(ado) {
10009
10132
  const types = await discoverTypes(workClient, collection, project);
10010
10133
  if (opts.save) {
10011
10134
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
10012
- const fs7 = await import("fs");
10013
- const existing = JSON.parse(fs7.readFileSync(globalConfigPath, "utf8") || "{}");
10135
+ const fs8 = await import("fs");
10136
+ const existing = JSON.parse(fs8.readFileSync(globalConfigPath, "utf8") || "{}");
10014
10137
  existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
10015
- fs7.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
10138
+ fs8.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
10016
10139
  warn(`Saved ${types.length} types to ${globalConfigPath}`);
10017
10140
  }
10018
10141
  success(types, "ado", "work-types", start);
@@ -10041,11 +10164,11 @@ function registerAdoWorkCommands(ado) {
10041
10164
  ) : allFields;
10042
10165
  if (opts.save) {
10043
10166
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
10044
- const fs7 = await import("fs");
10045
- const existing = JSON.parse(fs7.readFileSync(globalConfigPath, "utf8") || "{}");
10167
+ const fs8 = await import("fs");
10168
+ const existing = JSON.parse(fs8.readFileSync(globalConfigPath, "utf8") || "{}");
10046
10169
  const aliases = buildDefaultAliases(allFields);
10047
10170
  existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
10048
- fs7.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
10171
+ fs8.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
10049
10172
  warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
10050
10173
  }
10051
10174
  success(fields, "ado", "work-fields", start);
@@ -10342,11 +10465,11 @@ function registerAdoRepoCommands(ado) {
10342
10465
  const start = Date.now();
10343
10466
  try {
10344
10467
  const globalOpts = ado.optsWithGlobals();
10345
- const { loadConfig: lc } = await import("./config-7FK4MAQC.js");
10468
+ const { loadConfig: lc } = await import("./config-Y6IE6GY7.js");
10346
10469
  const config = lc({ configPath: globalOpts.config });
10347
10470
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
10348
10471
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
10349
- const { createHttpClient: createHttpClient2 } = await import("./http-73CNKAH6.js");
10472
+ const { createHttpClient: createHttpClient2 } = await import("./http-TAINU2LF.js");
10350
10473
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
10351
10474
  const coreClient = new AdoCoreClient2(http);
10352
10475
  const me = await coreClient.getConnectionData(collection);
@@ -12345,6 +12468,35 @@ Expected format: https://www.figma.com/design/<file-key>/...`);
12345
12468
  }
12346
12469
  return input3;
12347
12470
  }
12471
+ var NODE_ID_PATTERN = /^[A-Za-z0-9]+:[A-Za-z0-9]+(?:;[A-Za-z0-9]+:[A-Za-z0-9]+)*$/;
12472
+ var NODE_ID_HINT = "Expected a node ID like 12:34 or 12-34, or a Figma URL with a node-id query param";
12473
+ function isUrl(input3) {
12474
+ return input3.startsWith("http://") || input3.startsWith("https://");
12475
+ }
12476
+ function parseNodeId(input3) {
12477
+ let raw = input3;
12478
+ if (isUrl(input3)) {
12479
+ try {
12480
+ raw = new URL(input3).searchParams.get("node-id");
12481
+ } catch {
12482
+ raw = null;
12483
+ }
12484
+ if (raw === null) {
12485
+ throw new Error(`Could not extract a Figma node ID from URL: ${input3}
12486
+ ${NODE_ID_HINT}`);
12487
+ }
12488
+ }
12489
+ const nodeId = raw.trim().replace(/-/g, ":");
12490
+ if (!NODE_ID_PATTERN.test(nodeId)) {
12491
+ throw new Error(`Invalid Figma node ID: ${input3}
12492
+ ${NODE_ID_HINT}`);
12493
+ }
12494
+ return nodeId;
12495
+ }
12496
+ function findNodeIdInUrl(url) {
12497
+ if (!new URL(url).searchParams.has("node-id")) return void 0;
12498
+ return parseNodeId(url);
12499
+ }
12348
12500
  function getHttp5(program2) {
12349
12501
  const opts = program2.optsWithGlobals();
12350
12502
  return createHttpClient(
@@ -12354,11 +12506,38 @@ function getHttp5(program2) {
12354
12506
  }
12355
12507
  function registerFigmaCommands(program2) {
12356
12508
  const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
12357
- figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...)").option("--document", "Include the full document node tree (can be large; omitted by default)").action(async (fileKeyOrUrl, opts) => {
12509
+ figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory (or a single node with --node-id)").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...); a node-id query param is auto-detected").option("--document", "Include the full document node tree (can be large; omitted by default)").option("--node-id <id>", "Fetch only this node and its descendants instead of the whole file (overrides any node-id detected in the URL)").action(async (fileKeyOrUrl, opts) => {
12358
12510
  const start = Date.now();
12359
- const { success: success2, fail: fail2 } = await import("./output-3G3WZVZA.js");
12511
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
12360
12512
  try {
12361
12513
  const fileKey = parseFileKey(fileKeyOrUrl);
12514
+ const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
12515
+ if (nodeId) {
12516
+ const data2 = await getHttp5(program2).figma(
12517
+ `/v1/files/${encodeURIComponent(fileKey)}/nodes`,
12518
+ { params: { ids: nodeId } }
12519
+ );
12520
+ const node = data2.nodes[nodeId];
12521
+ if (!node) {
12522
+ throw new Error(`Node ${nodeId} not found in file ${fileKey}`);
12523
+ }
12524
+ success2(
12525
+ {
12526
+ fileKey,
12527
+ nodeId,
12528
+ name: data2.name,
12529
+ lastModified: data2.lastModified,
12530
+ thumbnailUrl: data2.thumbnailUrl,
12531
+ document: node.document,
12532
+ componentCount: node.components ? Object.keys(node.components).length : 0,
12533
+ styleCount: node.styles ? Object.keys(node.styles).length : 0
12534
+ },
12535
+ "figma",
12536
+ "file",
12537
+ start
12538
+ );
12539
+ return;
12540
+ }
12362
12541
  const params = {};
12363
12542
  if (!opts.document) {
12364
12543
  params["depth"] = 1;
@@ -12388,7 +12567,7 @@ function registerFigmaCommands(program2) {
12388
12567
  });
12389
12568
  figma.command("comments").description("Get comments on a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").option("--as-of <date>", "ISO 8601 timestamp \u2014 return only comments created before this time").action(async (fileKeyOrUrl, opts) => {
12390
12569
  const start = Date.now();
12391
- const { success: success2, fail: fail2 } = await import("./output-3G3WZVZA.js");
12570
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
12392
12571
  try {
12393
12572
  const fileKey = parseFileKey(fileKeyOrUrl);
12394
12573
  const params = {};
@@ -12406,7 +12585,7 @@ function registerFigmaCommands(program2) {
12406
12585
  });
12407
12586
  figma.command("versions").description("Get the version history of a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").action(async (fileKeyOrUrl) => {
12408
12587
  const start = Date.now();
12409
- const { success: success2, fail: fail2 } = await import("./output-3G3WZVZA.js");
12588
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
12410
12589
  try {
12411
12590
  const fileKey = parseFileKey(fileKeyOrUrl);
12412
12591
  const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
@@ -12422,7 +12601,7 @@ function registerFigmaCommands(program2) {
12422
12601
  });
12423
12602
  figma.command("project-files").description("List files in a Figma project").argument("<project-id>", "Figma project ID (visible in the project URL)").action(async (projectId) => {
12424
12603
  const start = Date.now();
12425
- const { success: success2, fail: fail2 } = await import("./output-3G3WZVZA.js");
12604
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
12426
12605
  try {
12427
12606
  const data = await getHttp5(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
12428
12607
  success2(
@@ -12437,7 +12616,7 @@ function registerFigmaCommands(program2) {
12437
12616
  });
12438
12617
  figma.command("me").description("Get the current Figma user (useful for verifying credentials)").action(async () => {
12439
12618
  const start = Date.now();
12440
- const { success: success2, fail: fail2 } = await import("./output-3G3WZVZA.js");
12619
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
12441
12620
  try {
12442
12621
  const data = await getHttp5(program2).figma("/v1/me");
12443
12622
  success2(data, "figma", "me", start);
@@ -12447,6 +12626,410 @@ function registerFigmaCommands(program2) {
12447
12626
  });
12448
12627
  }
12449
12628
 
12629
+ // src/services/alation/commands.ts
12630
+ import fs7 from "fs";
12631
+ var MAX_PAGE = 1e3;
12632
+ function parseIntOption(value, flag) {
12633
+ if (value === void 0) return void 0;
12634
+ const trimmed = value.trim();
12635
+ if (!/^\d+$/.test(trimmed)) {
12636
+ throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
12637
+ }
12638
+ return Number(trimmed);
12639
+ }
12640
+ function parsePositiveId(value, what) {
12641
+ const trimmed = value.trim();
12642
+ if (!/^[1-9]\d*$/.test(trimmed)) {
12643
+ throw new PncliError(`Invalid ${what} "${value}". Expected a positive integer ID.`, 1);
12644
+ }
12645
+ return Number(trimmed);
12646
+ }
12647
+ function pageParams(opts) {
12648
+ const limit = parseIntOption(opts.limit, "--limit") ?? 100;
12649
+ const skip = parseIntOption(opts.skip, "--skip") ?? 0;
12650
+ if (limit < 1 || limit > MAX_PAGE) {
12651
+ throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
12652
+ }
12653
+ return { limit, skip };
12654
+ }
12655
+ var SEARCH_OTYPES = [
12656
+ "api_resource",
12657
+ "api_resource_field",
12658
+ "api_resource_folder",
12659
+ "article",
12660
+ "bi_field",
12661
+ "catalog_set",
12662
+ "column",
12663
+ "dataflow",
12664
+ "datasource",
12665
+ "doc_schema",
12666
+ "docstore_collection",
12667
+ "docstore_folder",
12668
+ "domain",
12669
+ "execution_result",
12670
+ "file",
12671
+ "filesystem",
12672
+ "function",
12673
+ "glossary",
12674
+ "glossary_term",
12675
+ "glossary_v3",
12676
+ "group",
12677
+ "query_or_statement",
12678
+ "report_collection",
12679
+ "report_datasource",
12680
+ "report_object",
12681
+ "report_source",
12682
+ "schema",
12683
+ "table",
12684
+ "tag",
12685
+ "thread",
12686
+ "user",
12687
+ "value"
12688
+ ];
12689
+ function parseOtypes(raw) {
12690
+ if (raw === void 0) return void 0;
12691
+ const otypes = raw.split(",").map((s) => s.trim()).filter(Boolean);
12692
+ if (otypes.length === 0) throw new PncliError("--otype cannot be empty", 1);
12693
+ const bad = otypes.filter((o) => !SEARCH_OTYPES.includes(o));
12694
+ if (bad.length) {
12695
+ throw new PncliError(`Unknown --otype value(s): ${bad.join(", ")}. Valid: ${SEARCH_OTYPES.join(", ")}`, 1);
12696
+ }
12697
+ return otypes;
12698
+ }
12699
+ function toCustomFields(fields) {
12700
+ if (fields === void 0) return [];
12701
+ if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
12702
+ throw new PncliError('--input-file "fields" must be an object keyed by Alation custom field ID', 1);
12703
+ }
12704
+ return Object.entries(fields).map(([key, value]) => {
12705
+ if (!/^\d+$/.test(key)) {
12706
+ throw new PncliError(`--input-file "fields" key "${key}" is not a numeric Alation field_id`, 1);
12707
+ }
12708
+ return { field_id: Number(key), value: resolveAtFileRef(value) };
12709
+ });
12710
+ }
12711
+ var ALATION_DOCUMENT_INPUT_FILE_SCHEMA = {
12712
+ type: "object",
12713
+ properties: {
12714
+ title: { type: "string", description: "Document title" },
12715
+ description: { type: "string", description: 'Document summary; "@path" reads the value from a file' },
12716
+ hub: { type: "integer", description: "document_hub_id (required on create)" },
12717
+ folder: { type: "integer", description: "parent_folder_id (required on create)" },
12718
+ template: { type: "integer", description: "template_id; required whenever fields are supplied" },
12719
+ parentDocument: { type: "integer", description: "parent_document_id for a sub-document" },
12720
+ navLinkFolders: { type: "array", items: { type: "integer" }, description: "nav_link_folder_ids" },
12721
+ fields: {
12722
+ type: "object",
12723
+ description: 'Custom field values keyed by numeric Alation field_id; string values starting with "@" are read from that file',
12724
+ additionalProperties: true
12725
+ }
12726
+ }
12727
+ };
12728
+ var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
12729
+ title: "Customer table onboarding notes",
12730
+ description: "@docs/customer-notes.md",
12731
+ hub: 1,
12732
+ folder: 42,
12733
+ template: 7,
12734
+ fields: { "10001": "Team Data Platform" }
12735
+ };
12736
+ function getHttp6(program2) {
12737
+ const opts = program2.optsWithGlobals();
12738
+ return createHttpClient(
12739
+ loadConfig({ configPath: opts.config }),
12740
+ Boolean(opts.dryRun)
12741
+ );
12742
+ }
12743
+ async function getOne(http, path10, id, what) {
12744
+ const rows = await http.alation(path10, { params: { id, limit: 1 } });
12745
+ const row = Array.isArray(rows) ? rows.find((r) => r.id === id) ?? rows[0] : void 0;
12746
+ if (!row) throw new PncliError(`${what} ${id} not found`, 404);
12747
+ return row;
12748
+ }
12749
+ function buildDocumentPayload(opts) {
12750
+ const fromJson = {};
12751
+ if (opts.inputFile) {
12752
+ const parsed = readJsonInputFile(opts.inputFile);
12753
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
12754
+ throw new PncliError("--input-file must contain a JSON object. Run: pncli alation document schema", 1);
12755
+ }
12756
+ Object.assign(fromJson, parsed);
12757
+ }
12758
+ const fromFlags = {
12759
+ title: opts.title,
12760
+ description: opts.description,
12761
+ hub: opts.hub !== void 0 ? parsePositiveId(opts.hub, "--hub") : void 0,
12762
+ folder: opts.folder !== void 0 ? parsePositiveId(opts.folder, "--folder") : void 0,
12763
+ template: opts.template !== void 0 ? parsePositiveId(opts.template, "--template") : void 0,
12764
+ parentDocument: opts.parentDocument !== void 0 ? parsePositiveId(opts.parentDocument, "--parent-document") : void 0
12765
+ };
12766
+ const { merged, overrides } = mergeWithOverrides(fromJson, fromFlags);
12767
+ if (overrides.length) warn(`--input-file values overridden by flags: ${overrides.join(", ")}`);
12768
+ const payload = {};
12769
+ if (merged.title !== void 0) payload.title = merged.title;
12770
+ if (merged.description !== void 0) payload.description = resolveAtFileRef(merged.description);
12771
+ if (merged.hub !== void 0) payload.document_hub_id = merged.hub;
12772
+ if (merged.folder !== void 0) payload.parent_folder_id = merged.folder;
12773
+ if (merged.template !== void 0) payload.template_id = merged.template;
12774
+ if (merged.parentDocument !== void 0) payload.parent_document_id = merged.parentDocument;
12775
+ if (merged.navLinkFolders !== void 0) payload.nav_link_folder_ids = merged.navLinkFolders;
12776
+ const customFields = toCustomFields(merged.fields);
12777
+ if (customFields.length) {
12778
+ if (payload.template_id === void 0) {
12779
+ throw new PncliError('Alation requires template_id whenever custom fields are set. Pass --template <id> or "template" in --input-file.', 1);
12780
+ }
12781
+ payload.custom_fields = customFields;
12782
+ }
12783
+ return { payload, overrides };
12784
+ }
12785
+ function registerAlationCommands(program2) {
12786
+ const alation = program2.command("alation").description("Alation data catalog (data sources, schemas, tables, columns, search, documents)");
12787
+ const token = alation.command("token").description("API access token operations");
12788
+ token.command("status").description("Mint an API access token from the configured refresh token and report its expiry (verifies credentials)").action(async () => {
12789
+ const start = Date.now();
12790
+ try {
12791
+ const opts = program2.optsWithGlobals();
12792
+ const config = loadConfig({ configPath: opts.config });
12793
+ if (opts.dryRun) {
12794
+ fs7.writeSync(process.stderr.fd, `DRY RUN: POST ${config.alation.baseUrl ?? "<alation.baseUrl>"}/integration/v1/createAPIAccessToken/ (token exchange skipped)
12795
+ `);
12796
+ process.exitCode = ExitCode.SUCCESS;
12797
+ throw new PncliError("dry-run", 0);
12798
+ }
12799
+ const data = await validateAlationAccessToken(config);
12800
+ success(data, "alation", "token-status", start);
12801
+ } catch (err) {
12802
+ fail(err, "alation", "token-status", start);
12803
+ }
12804
+ });
12805
+ const datasource = alation.command("datasource").description("Data source operations");
12806
+ datasource.command("list").description("List data sources").option("--include-undeployed", "Include data sources whose setup is incomplete").option("--include-hidden", "Include hidden data sources").action(async (opts) => {
12807
+ const start = Date.now();
12808
+ try {
12809
+ const params = {};
12810
+ if (opts.includeUndeployed) params.include_undeployed = true;
12811
+ if (opts.includeHidden) params.include_hidden = true;
12812
+ const data = await getHttp6(program2).alation("integration/v1/datasource/", { params });
12813
+ success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
12814
+ } catch (err) {
12815
+ fail(err, "alation", "datasource-list", start);
12816
+ }
12817
+ });
12818
+ datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id) => {
12819
+ const start = Date.now();
12820
+ try {
12821
+ const dsId = parsePositiveId(id, "data source ID");
12822
+ const data = await getHttp6(program2).alation(`integration/v1/datasource/${dsId}/`);
12823
+ success(data, "alation", "datasource-get", start);
12824
+ } catch (err) {
12825
+ fail(err, "alation", "datasource-get", start);
12826
+ }
12827
+ });
12828
+ const schema = alation.command("schema").description("Schema operations");
12829
+ schema.command("list").description("List schemas, optionally within one data source").option("--ds <id>", "Filter by data source ID").option("--name <name>", "Filter by exact schema name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
12830
+ const start = Date.now();
12831
+ try {
12832
+ const params = { ...pageParams(opts) };
12833
+ if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
12834
+ if (opts.name) params.name = opts.name;
12835
+ const data = await getHttp6(program2).alation("integration/v2/schema/", { params });
12836
+ success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
12837
+ } catch (err) {
12838
+ fail(err, "alation", "schema-list", start);
12839
+ }
12840
+ });
12841
+ schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id) => {
12842
+ const start = Date.now();
12843
+ try {
12844
+ const data = await getOne(getHttp6(program2), "integration/v2/schema/", parsePositiveId(id, "schema ID"), "Schema");
12845
+ success(data, "alation", "schema-get", start);
12846
+ } catch (err) {
12847
+ fail(err, "alation", "schema-get", start);
12848
+ }
12849
+ });
12850
+ const table = alation.command("table").description("Table operations");
12851
+ table.command("list").description("List tables (descriptions, custom fields, fully-qualified keys)").option("--ds <id>", "Filter by data source ID").option("--schema <id>", "Filter by schema ID").option("--schema-name <name>", "Filter by schema name").option("--name <name>", "Filter by exact table name").option("--search <text>", "Case-insensitive substring match on table name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
12852
+ const start = Date.now();
12853
+ try {
12854
+ const params = { ...pageParams(opts) };
12855
+ if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
12856
+ if (opts.schema) params.schema_id = parsePositiveId(opts.schema, "--schema");
12857
+ if (opts.schemaName) params.schema_name = opts.schemaName;
12858
+ if (opts.name) params.name = opts.name;
12859
+ if (opts.search) params.name__icontains = opts.search;
12860
+ const data = await getHttp6(program2).alation("integration/v2/table/", { params });
12861
+ success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
12862
+ } catch (err) {
12863
+ fail(err, "alation", "table-list", start);
12864
+ }
12865
+ });
12866
+ table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (id, opts) => {
12867
+ const start = Date.now();
12868
+ try {
12869
+ const http = getHttp6(program2);
12870
+ const tableId = parsePositiveId(id, "table ID");
12871
+ const data = await getOne(http, "integration/v2/table/", tableId, "Table");
12872
+ if (opts.columns) {
12873
+ const columns = await http.alation("integration/v2/column/", {
12874
+ params: { table_id: tableId, limit: MAX_PAGE, order_by: "id" }
12875
+ });
12876
+ success({ ...data, columnCount: columns.length, columns }, "alation", "table-get", start);
12877
+ return;
12878
+ }
12879
+ success(data, "alation", "table-get", start);
12880
+ } catch (err) {
12881
+ fail(err, "alation", "table-get", start);
12882
+ }
12883
+ });
12884
+ const column = alation.command("column").description("Column operations");
12885
+ column.command("list").description("List columns of a table (types, descriptions, keys, custom fields)").option("--table <id>", "Filter by table ID").option("--table-name <name>", "Filter by table name").option("--schema <id>", "Filter by schema ID").option("--ds <id>", "Filter by data source ID").option("--name <name>", "Filter by exact column name").option("--search <text>", "Case-insensitive substring match on column name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
12886
+ const start = Date.now();
12887
+ try {
12888
+ if (!opts.table && !opts.tableName && !opts.schema && !opts.ds && !opts.name && !opts.search) {
12889
+ throw new PncliError("Pass at least one filter (--table, --table-name, --schema, --ds, --name, or --search); listing every column in the catalog is not supported.", 1);
12890
+ }
12891
+ const params = { ...pageParams(opts) };
12892
+ if (opts.table) params.table_id = parsePositiveId(opts.table, "--table");
12893
+ if (opts.tableName) params.table_name = opts.tableName;
12894
+ if (opts.schema) params.schema_id = parsePositiveId(opts.schema, "--schema");
12895
+ if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
12896
+ if (opts.name) params.name = opts.name;
12897
+ if (opts.search) params.name__icontains = opts.search;
12898
+ const data = await getHttp6(program2).alation("integration/v2/column/", { params });
12899
+ success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
12900
+ } catch (err) {
12901
+ fail(err, "alation", "column-list", start);
12902
+ }
12903
+ });
12904
+ column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id) => {
12905
+ const start = Date.now();
12906
+ try {
12907
+ const data = await getOne(getHttp6(program2), "integration/v2/column/", parsePositiveId(id, "column ID"), "Column");
12908
+ success(data, "alation", "column-get", start);
12909
+ } catch (err) {
12910
+ fail(err, "alation", "column-get", start);
12911
+ }
12912
+ });
12913
+ alation.command("search").description("Full-text search across the catalog").argument("<query>", "Search text").option("--otype <types>", `Comma-separated object types to restrict to (e.g. table,column). Valid: ${SEARCH_OTYPES.join(", ")}`).option("--limit <n>", "Maximum results (default 20, max 10000)", "20").option("--offset <n>", "Index of the first result", "0").action(async (query, opts) => {
12914
+ const start = Date.now();
12915
+ try {
12916
+ const limit = parseIntOption(opts.limit, "--limit") ?? 20;
12917
+ const offset = parseIntOption(opts.offset, "--offset") ?? 0;
12918
+ if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
12919
+ const params = { q: query, limit, offset };
12920
+ const otypes = parseOtypes(opts.otype);
12921
+ if (otypes) params.filters = JSON.stringify({ otypes });
12922
+ const data = await getHttp6(program2).alation("integration/v1/search/", { params });
12923
+ success(
12924
+ { query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
12925
+ "alation",
12926
+ "search",
12927
+ start
12928
+ );
12929
+ } catch (err) {
12930
+ fail(err, "alation", "search", start);
12931
+ }
12932
+ });
12933
+ const folder = alation.command("folder").description("Document Hub folder operations");
12934
+ folder.command("list").description("List folders in a Document Hub").option("--hub <id>", "Document hub ID").option("--parent <id>", "Parent folder ID (list its subfolders)").option("--search <text>", "Filter on textual fields").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
12935
+ const start = Date.now();
12936
+ try {
12937
+ const params = { ...pageParams(opts) };
12938
+ if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
12939
+ if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
12940
+ if (opts.search) params.search = opts.search;
12941
+ const data = await getHttp6(program2).alation("integration/v2/folder/", { params });
12942
+ success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
12943
+ } catch (err) {
12944
+ fail(err, "alation", "folder-list", start);
12945
+ }
12946
+ });
12947
+ folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id) => {
12948
+ const start = Date.now();
12949
+ try {
12950
+ const data = await getOne(getHttp6(program2), "integration/v2/folder/", parsePositiveId(id, "folder ID"), "Folder");
12951
+ success(data, "alation", "folder-get", start);
12952
+ } catch (err) {
12953
+ fail(err, "alation", "folder-get", start);
12954
+ }
12955
+ });
12956
+ const document = alation.command("document").description("Document Hub document operations");
12957
+ document.command("list").description("List documents in a Document Hub or folder").option("--hub <id>", "Document hub ID").option("--folder <id>", "Parent folder ID").option("--parent-document <id>", "Parent document ID (list sub-documents)").option("--search <text>", "Filter on all textual fields").option("--deleted", "Return only deleted documents").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
12958
+ const start = Date.now();
12959
+ try {
12960
+ const params = { ...pageParams(opts) };
12961
+ if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
12962
+ if (opts.folder) params.parent_folder_id = parsePositiveId(opts.folder, "--folder");
12963
+ if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
12964
+ if (opts.search) params.search = opts.search;
12965
+ if (opts.deleted) params.deleted = true;
12966
+ const data = await getHttp6(program2).alation("integration/v2/document/", { params });
12967
+ success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
12968
+ } catch (err) {
12969
+ fail(err, "alation", "document-list", start);
12970
+ }
12971
+ });
12972
+ document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id) => {
12973
+ const start = Date.now();
12974
+ try {
12975
+ const data = await getOne(getHttp6(program2), "integration/v2/document/", parsePositiveId(id, "document ID"), "Document");
12976
+ success(data, "alation", "document-get", start);
12977
+ } catch (err) {
12978
+ fail(err, "alation", "document-get", start);
12979
+ }
12980
+ });
12981
+ document.command("schema").description("Print the --input-file JSON schema and an example for document create/update").option("--example-only", "Print only the runnable example JSON (no envelope) \u2014 pipeable straight into --input-file").action((opts) => {
12982
+ const start = Date.now();
12983
+ if (opts.exampleOnly) {
12984
+ writeRawOutput(JSON.stringify(ALATION_DOCUMENT_INPUT_FILE_EXAMPLE, null, 2) + "\n");
12985
+ return;
12986
+ }
12987
+ success({ schema: ALATION_DOCUMENT_INPUT_FILE_SCHEMA, example: ALATION_DOCUMENT_INPUT_FILE_EXAMPLE }, "alation", "document-schema", start);
12988
+ });
12989
+ document.command("create").description("Create a document (async \u2014 returns a job ID; check it with `alation job get`)").option("--title <title>", "Document title").option("--description <text>", "Document summary").option("--hub <id>", "Document hub ID").option("--folder <id>", "Parent folder ID").option("--template <id>", "Template ID (required when custom fields are set)").option("--parent-document <id>", "Parent document ID for a sub-document").option("--input-file <path>", 'JSON file with title/description/hub/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (opts) => {
12990
+ const start = Date.now();
12991
+ try {
12992
+ const { payload, overrides } = buildDocumentPayload(opts);
12993
+ for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
12994
+ if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
12995
+ }
12996
+ const data = await getHttp6(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
12997
+ success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
12998
+ } catch (err) {
12999
+ fail(err, "alation", "document-create", start);
13000
+ }
13001
+ });
13002
+ document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (id, opts) => {
13003
+ const start = Date.now();
13004
+ try {
13005
+ const documentId = parsePositiveId(id, "document ID");
13006
+ const { payload, overrides } = buildDocumentPayload(opts);
13007
+ if (payload.document_hub_id !== void 0) {
13008
+ throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
13009
+ }
13010
+ if (Object.keys(payload).length === 0) {
13011
+ throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
13012
+ }
13013
+ const body = [{ id: documentId, ...payload }];
13014
+ const data = await getHttp6(program2).alation("integration/v2/document/", { method: "PUT", body });
13015
+ success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
13016
+ } catch (err) {
13017
+ fail(err, "alation", "document-update", start);
13018
+ }
13019
+ });
13020
+ const job = alation.command("job").description("Background job operations");
13021
+ job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id) => {
13022
+ const start = Date.now();
13023
+ try {
13024
+ const jobId = parsePositiveId(id, "job ID");
13025
+ const data = await getHttp6(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
13026
+ success({ jobId, ...data }, "alation", "job-get", start);
13027
+ } catch (err) {
13028
+ fail(err, "alation", "job-get", start);
13029
+ }
13030
+ });
13031
+ }
13032
+
12450
13033
  // src/cli.ts
12451
13034
  if (!process.env.PNCLI_VERIFY_TLS) {
12452
13035
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
@@ -12492,6 +13075,7 @@ registerDynatraceCommands(program);
12492
13075
  registerLogscaleCommands(program);
12493
13076
  registerSplitioCommands(program);
12494
13077
  registerFigmaCommands(program);
13078
+ registerAlationCommands(program);
12495
13079
  program.addHelpText("after", `
12496
13080
  Services:
12497
13081
  git Local git operations (status, diff, log, branch)
@@ -12513,6 +13097,7 @@ Services:
12513
13097
  logscale LogScale (log query, repositories)
12514
13098
  splitio Split.IO (feature flags, Change Requests)
12515
13099
  figma Figma (files, comments, version history)
13100
+ alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
12516
13101
  config Manage pncli configuration
12517
13102
  doctor Diagnose pncli setup (config files, credentials, agent skills)
12518
13103
  skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)