@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/README.md +3 -2
- package/dist/{chunk-BHZORLOF.js → chunk-BOFSNQQ2.js} +4 -16
- package/dist/chunk-BOFSNQQ2.js.map +1 -0
- package/dist/{chunk-VCECVN3F.js → chunk-FAMJPKO4.js} +152 -4
- package/dist/chunk-FAMJPKO4.js.map +1 -0
- package/dist/chunk-HRNOAQDN.js +20 -0
- package/dist/chunk-HRNOAQDN.js.map +1 -0
- package/dist/{chunk-3JICA4FZ.js → chunk-QHJ2HM5J.js} +71 -3
- package/dist/chunk-QHJ2HM5J.js.map +1 -0
- package/dist/cli.js +614 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-7FK4MAQC.js → config-Y6IE6GY7.js} +3 -2
- package/dist/http-TAINU2LF.js +12 -0
- package/dist/{output-3G3WZVZA.js → output-HDUIIXUI.js} +3 -2
- package/package.json +2 -1
- package/skills/pncli/SKILL.md +3 -2
- package/skills/pncli/alation.md +101 -0
- package/skills/pncli/figma.md +9 -0
- package/skills/pncli/jira.md +19 -1
- package/skills/pncli/marketplace.md +2 -0
- package/dist/chunk-3JICA4FZ.js.map +0 -1
- package/dist/chunk-BHZORLOF.js.map +0 -1
- package/dist/chunk-VCECVN3F.js.map +0 -1
- package/dist/http-73CNKAH6.js +0 -11
- /package/dist/{config-7FK4MAQC.js.map → config-Y6IE6GY7.js.map} +0 -0
- /package/dist/{http-73CNKAH6.js.map → http-TAINU2LF.js.map} +0 -0
- /package/dist/{output-3G3WZVZA.js.map → output-HDUIIXUI.js.map} +0 -0
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-
|
|
13
|
+
} from "./chunk-QHJ2HM5J.js";
|
|
13
14
|
import {
|
|
14
|
-
createHttpClient
|
|
15
|
-
|
|
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-
|
|
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((
|
|
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((
|
|
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 =
|
|
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
|
-
|
|
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 &&
|
|
7056
|
-
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl,
|
|
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
|
-
|
|
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
|
|
10013
|
-
const existing = JSON.parse(
|
|
10135
|
+
const fs8 = await import("fs");
|
|
10136
|
+
const existing = JSON.parse(fs8.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
10014
10137
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
10015
|
-
|
|
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
|
|
10045
|
-
const existing = JSON.parse(
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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)
|