@configdirector/cli 0.8.0-beta.1 → 0.9.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +4 -0
- package/dist/configdirector.mjs +120 -27
- package/package.json +1 -1
- package/schema/scan-output.schema.json +55 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to `@configdirector/cli` are documented here. The format fol
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.0-beta.1] - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `configdirector scan --json` prints, and `scan --upload` sends, a snippet with each direct and indirect reference: its line and the 2 lines before and after it, so the dashboard can show the code around the reference. A snippet stops at the start and end of the file, leaves out line endings, and cuts a line longer than 250 characters to its first 250, listing it in `truncatedLines`. Dynamic references have no snippet, and the text output is unchanged. `schema/scan-output.schema.json` describes the `snippet` field.
|
|
12
|
+
- `--context-lines <number>` sets how many lines before and after each reference the snippet includes, from 0 to 5; `--context-lines 0` includes the reference line alone. `--no-snippets` sends no snippet. Giving both, or a `--context-lines` that is not a whole number from 0 to 5, exits with 2.
|
|
13
|
+
- When the project's Code snippets setting is off, `scan --upload` sends no snippets and ends with `Code snippets were not sent: this project does not store them.` `scan` without `--upload` still prints them with `--json`.
|
|
14
|
+
|
|
7
15
|
## [0.8.0-beta.1] - 2026-10-18
|
|
8
16
|
|
|
9
17
|
### Added
|
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ The scan skips what `.gitignore` ignores, the `node_modules`, `vendor`, `build`,
|
|
|
54
54
|
|
|
55
55
|
With `--json`, `scan` prints the references for scripts and CI; `schema/scan-output.schema.json` in the package describes that output. Each reference has a `kind`, `direct` or `indirect`, and a direct reference resolved from a constant carries the constant as `argument`.
|
|
56
56
|
|
|
57
|
+
Each direct and indirect reference in the JSON also carries a `snippet`: its line and the 2 lines before and after it, without line endings, with any line longer than 250 characters cut to its first 250. `--context-lines <number>` includes from 0 to 5 lines on each side, and `--no-snippets` leaves every snippet out; giving both exits with 2. Dynamic references have no snippet, and the text output shows none.
|
|
58
|
+
|
|
57
59
|
## Upload the scan to ConfigDirector
|
|
58
60
|
|
|
59
61
|
From the repository root, on the default branch:
|
|
@@ -68,6 +70,8 @@ The repository is named after the path of its git remote `origin`, without the h
|
|
|
68
70
|
|
|
69
71
|
The upload holds the references and dynamic references `scan --json` prints, whatever `ignoreKeys` lists, except a reference to a key that cannot be a config key, such as one shorter than 4 characters, which is left out and counted. The output is the scan's, then a line naming the repository, the commit, the project, how many references were sent and how many were left out. A scan that finds no source file exits with 2 and uploads nothing, so a wrong path or `include` never empties the repository's references; a scan that reads files and finds no reference uploads an empty set. With `--json`, it prints the Admin API's response as received.
|
|
70
72
|
|
|
73
|
+
Each direct and indirect reference is sent with its snippet, so the dashboard can show the code around it; `--no-snippets` sends no code, and `--context-lines` sets how many lines are sent. When the project's Code snippets setting is off, `scan --upload` sends no snippets and ends with `Code snippets were not sent: this project does not store them.`
|
|
74
|
+
|
|
71
75
|
A project holds at most 50 scanned repositories, and one upload holds at most 10,000 references, dynamic references included; going over either exits with 2. `scan --upload` needs `projects:read`, `configs:read` and `code-references:update`. Uploads do not appear in the audit log.
|
|
72
76
|
|
|
73
77
|
## List and remove scanned repositories
|
package/dist/configdirector.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { promisify } from "node:util";
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { text } from "node:stream/consumers";
|
|
14
14
|
//#region package.json
|
|
15
|
-
var version = "0.
|
|
15
|
+
var version = "0.9.0-beta.1";
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/CliError.ts
|
|
18
18
|
var CliError = class extends Error {
|
|
@@ -677,7 +677,8 @@ const ConfigEvaluationSchema = v.object({
|
|
|
677
677
|
const ProjectSchema = v.object({
|
|
678
678
|
id: v.string(),
|
|
679
679
|
name: v.string(),
|
|
680
|
-
slug: v.string()
|
|
680
|
+
slug: v.string(),
|
|
681
|
+
storeCodeSnippets: v.optional(v.boolean(), true)
|
|
681
682
|
});
|
|
682
683
|
const configFields = {
|
|
683
684
|
key: v.string(),
|
|
@@ -1530,6 +1531,25 @@ const languageOfExtension = {
|
|
|
1530
1531
|
".go": "go"
|
|
1531
1532
|
};
|
|
1532
1533
|
const isSourceExtension = (extension) => Object.hasOwn(languageOfExtension, extension);
|
|
1534
|
+
const snippetLanguageOfExtension = {
|
|
1535
|
+
".js": "javascript",
|
|
1536
|
+
".jsx": "jsx",
|
|
1537
|
+
".mjs": "javascript",
|
|
1538
|
+
".cjs": "javascript",
|
|
1539
|
+
".ts": "typescript",
|
|
1540
|
+
".tsx": "tsx",
|
|
1541
|
+
".mts": "typescript",
|
|
1542
|
+
".cts": "typescript",
|
|
1543
|
+
".vue": "vue",
|
|
1544
|
+
".svelte": "svelte",
|
|
1545
|
+
".dart": "dart",
|
|
1546
|
+
".swift": "swift",
|
|
1547
|
+
".kt": "kotlin",
|
|
1548
|
+
".java": "java",
|
|
1549
|
+
".py": "python",
|
|
1550
|
+
".cs": "csharp",
|
|
1551
|
+
".go": "go"
|
|
1552
|
+
};
|
|
1533
1553
|
const languageSyntax = {
|
|
1534
1554
|
javascript: {
|
|
1535
1555
|
lineComment: "//",
|
|
@@ -3587,6 +3607,43 @@ const sdkCallMatches = (code, callShapes) => {
|
|
|
3587
3607
|
return matches;
|
|
3588
3608
|
};
|
|
3589
3609
|
//#endregion
|
|
3610
|
+
//#region src/scan/snippets.ts
|
|
3611
|
+
const MAXIMUM_LINE_LENGTH = 250;
|
|
3612
|
+
const linesOf = (contents) => {
|
|
3613
|
+
const lines = contents.split("\n");
|
|
3614
|
+
return lines.at(-1) === "" ? lines.slice(0, -1) : lines;
|
|
3615
|
+
};
|
|
3616
|
+
const withoutCarriageReturn = (line) => line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
3617
|
+
const snippetOfLines = (fileLines, line, contextLines, language) => {
|
|
3618
|
+
const firstLine = Math.max(1, line - contextLines);
|
|
3619
|
+
const lastLine = Math.min(fileLines.length, line + contextLines);
|
|
3620
|
+
const lines = [];
|
|
3621
|
+
const truncatedLines = [];
|
|
3622
|
+
for (let lineNumber = firstLine; lineNumber <= lastLine; lineNumber += 1) {
|
|
3623
|
+
const text = withoutCarriageReturn(fileLines[lineNumber - 1]);
|
|
3624
|
+
if (text.length > MAXIMUM_LINE_LENGTH) truncatedLines.push(lineNumber);
|
|
3625
|
+
lines.push(text.slice(0, MAXIMUM_LINE_LENGTH));
|
|
3626
|
+
}
|
|
3627
|
+
return {
|
|
3628
|
+
firstLine,
|
|
3629
|
+
lines,
|
|
3630
|
+
language,
|
|
3631
|
+
truncatedLines
|
|
3632
|
+
};
|
|
3633
|
+
};
|
|
3634
|
+
const snippetCutterFor = (file, cut) => {
|
|
3635
|
+
switch (cut.kind) {
|
|
3636
|
+
case "no-snippets": return () => void 0;
|
|
3637
|
+
case "around-each-reference": {
|
|
3638
|
+
let fileLines;
|
|
3639
|
+
return (line) => {
|
|
3640
|
+
fileLines ??= linesOf(file.contents);
|
|
3641
|
+
return snippetOfLines(fileLines, line, cut.contextLines, snippetLanguageOfExtension[file.extension]);
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
};
|
|
3646
|
+
//#endregion
|
|
3590
3647
|
//#region src/scan/test-files.ts
|
|
3591
3648
|
const TEST_DIRECTORY_NAMES = new Set([
|
|
3592
3649
|
"test",
|
|
@@ -3644,12 +3701,13 @@ const matchesIn = (file, code, callShapes, projectKeys, constantNameOfToken) =>
|
|
|
3644
3701
|
};
|
|
3645
3702
|
const matchOutcomes = (matches, constantIndex) => {
|
|
3646
3703
|
const constantsReadByCalls = new Set(matches.flatMap((match) => match.kind === "named" && constantIndex.keyOf(match.name) !== void 0 ? [match.name] : []));
|
|
3647
|
-
return matches.map(({ file, ...match }) => {
|
|
3704
|
+
return matches.map(({ file, snippet, ...match }) => {
|
|
3648
3705
|
switch (match.kind) {
|
|
3649
3706
|
case "direct": return {
|
|
3650
3707
|
file,
|
|
3651
3708
|
kind: "direct",
|
|
3652
|
-
reference: match.reference
|
|
3709
|
+
reference: match.reference,
|
|
3710
|
+
snippet
|
|
3653
3711
|
};
|
|
3654
3712
|
case "dynamic": return {
|
|
3655
3713
|
file,
|
|
@@ -3668,7 +3726,8 @@ const matchOutcomes = (matches, constantIndex) => {
|
|
|
3668
3726
|
reference: {
|
|
3669
3727
|
...match.reference,
|
|
3670
3728
|
key
|
|
3671
|
-
}
|
|
3729
|
+
},
|
|
3730
|
+
snippet
|
|
3672
3731
|
};
|
|
3673
3732
|
}
|
|
3674
3733
|
case "indirect": return match.constantName !== void 0 && constantsReadByCalls.has(match.constantName) ? {
|
|
@@ -3677,7 +3736,8 @@ const matchOutcomes = (matches, constantIndex) => {
|
|
|
3677
3736
|
} : {
|
|
3678
3737
|
file,
|
|
3679
3738
|
kind: "indirect",
|
|
3680
|
-
reference: match.reference
|
|
3739
|
+
reference: match.reference,
|
|
3740
|
+
snippet
|
|
3681
3741
|
};
|
|
3682
3742
|
}
|
|
3683
3743
|
});
|
|
@@ -3762,7 +3822,7 @@ const sdkFamiliesUsedIn = (highestDirectory) => {
|
|
|
3762
3822
|
return usedFamilies;
|
|
3763
3823
|
};
|
|
3764
3824
|
};
|
|
3765
|
-
const scanCodeReferences = async (scope, projectKeys) => {
|
|
3825
|
+
const scanCodeReferences = async (scope, projectKeys, snippetCut) => {
|
|
3766
3826
|
const scan = {
|
|
3767
3827
|
filesScanned: 0,
|
|
3768
3828
|
references: [],
|
|
@@ -3781,9 +3841,11 @@ const scanCodeReferences = async (scope, projectKeys) => {
|
|
|
3781
3841
|
const constantAssignments = constantAssignmentsIn(code);
|
|
3782
3842
|
for (const assignment of constantAssignments) constantIndex.add(assignment);
|
|
3783
3843
|
const constantNameOfToken = new Map(constantAssignments.map((assignment) => [assignment.tokenIndex, assignment.name]));
|
|
3844
|
+
const snippetAround = snippetCutterFor(file, snippetCut);
|
|
3784
3845
|
matches.push(...matchesIn(file, code, callShapes, projectKeys, constantNameOfToken).map((match) => ({
|
|
3785
3846
|
...match,
|
|
3786
|
-
file: file.displayPath
|
|
3847
|
+
file: file.displayPath,
|
|
3848
|
+
snippet: snippetAround(match.reference.line)
|
|
3787
3849
|
})));
|
|
3788
3850
|
}
|
|
3789
3851
|
for (const outcome of matchOutcomes(matches, constantIndex)) switch (outcome.kind) {
|
|
@@ -3791,14 +3853,16 @@ const scanCodeReferences = async (scope, projectKeys) => {
|
|
|
3791
3853
|
scan.references.push({
|
|
3792
3854
|
...outcome.reference,
|
|
3793
3855
|
kind: "direct",
|
|
3794
|
-
file: outcome.file
|
|
3856
|
+
file: outcome.file,
|
|
3857
|
+
snippet: outcome.snippet
|
|
3795
3858
|
});
|
|
3796
3859
|
break;
|
|
3797
3860
|
case "indirect":
|
|
3798
3861
|
scan.references.push({
|
|
3799
3862
|
...outcome.reference,
|
|
3800
3863
|
kind: "indirect",
|
|
3801
|
-
file: outcome.file
|
|
3864
|
+
file: outcome.file,
|
|
3865
|
+
snippet: outcome.snippet
|
|
3802
3866
|
});
|
|
3803
3867
|
break;
|
|
3804
3868
|
case "dynamic":
|
|
@@ -3813,7 +3877,7 @@ const scanCodeReferences = async (scope, projectKeys) => {
|
|
|
3813
3877
|
};
|
|
3814
3878
|
//#endregion
|
|
3815
3879
|
//#region src/project-scan.ts
|
|
3816
|
-
const
|
|
3880
|
+
const connectToScannableProject = async (context) => {
|
|
3817
3881
|
const { workingDirectory } = context.invocation;
|
|
3818
3882
|
const settings = await readProjectSettings(workingDirectory);
|
|
3819
3883
|
const projectSlug = requireLinkedProjectSlug(context, settings);
|
|
@@ -3824,18 +3888,17 @@ const scanProject = async (context, root) => {
|
|
|
3824
3888
|
const project = await findProjectBySlug(client, projectSlug);
|
|
3825
3889
|
const configs = (await client.listConfigs(project.id, { form: "every-config" })).map((listedConfig) => listedConfig.config);
|
|
3826
3890
|
const projectKeys = new Set(configs.flatMap((config) => [config.key, ...config.deprecatedKeys.map((deprecatedKey) => deprecatedKey.key)]));
|
|
3827
|
-
const codeReferenceScan = await scanCodeReferences({
|
|
3828
|
-
root,
|
|
3829
|
-
workingDirectory,
|
|
3830
|
-
include: settings?.include ?? "everyFile",
|
|
3831
|
-
exclude: settings?.exclude ?? []
|
|
3832
|
-
}, projectKeys);
|
|
3833
3891
|
return {
|
|
3834
3892
|
client,
|
|
3835
3893
|
project,
|
|
3836
3894
|
configs,
|
|
3837
3895
|
ignoredKeys: new Set(settings?.ignoreKeys ?? []),
|
|
3838
|
-
|
|
3896
|
+
codeReferencesIn: (root, snippetCut) => scanCodeReferences({
|
|
3897
|
+
root,
|
|
3898
|
+
workingDirectory,
|
|
3899
|
+
include: settings?.include ?? "everyFile",
|
|
3900
|
+
exclude: settings?.exclude ?? []
|
|
3901
|
+
}, projectKeys, snippetCut)
|
|
3839
3902
|
};
|
|
3840
3903
|
};
|
|
3841
3904
|
//#endregion
|
|
@@ -3945,14 +4008,16 @@ const referenceJson = (reference) => {
|
|
|
3945
4008
|
...reference.argument === void 0 ? {} : { argument: reference.argument },
|
|
3946
4009
|
...reference.inCodeDefault === void 0 ? {} : { default: reference.inCodeDefault },
|
|
3947
4010
|
...reference.getterType === void 0 ? {} : { getterType: reference.getterType },
|
|
3948
|
-
test: reference.test
|
|
4011
|
+
test: reference.test,
|
|
4012
|
+
...reference.snippet === void 0 ? {} : { snippet: reference.snippet }
|
|
3949
4013
|
};
|
|
3950
4014
|
case "indirect": return {
|
|
3951
4015
|
key: reference.key,
|
|
3952
4016
|
file: reference.file,
|
|
3953
4017
|
line: reference.line,
|
|
3954
4018
|
kind: reference.kind,
|
|
3955
|
-
test: reference.test
|
|
4019
|
+
test: reference.test,
|
|
4020
|
+
...reference.snippet === void 0 ? {} : { snippet: reference.snippet }
|
|
3956
4021
|
};
|
|
3957
4022
|
}
|
|
3958
4023
|
};
|
|
@@ -4010,9 +4075,24 @@ const scanText = (scan, scannedLocation) => {
|
|
|
4010
4075
|
};
|
|
4011
4076
|
const shortCommitText = (commit) => commit === null ? "" : ` at ${shortCommit(commit)}`;
|
|
4012
4077
|
const uploadLine = (projectSlug, repository, leftOutCount) => `Uploaded the scan of ${repository.name}${shortCommitText(repository.commit)} to ${projectSlug}: ${repository.referenceCount} ${plural(repository.referenceCount, "reference", "references")} and ${repository.dynamicReferenceCount} ${plural(repository.dynamicReferenceCount, "dynamic reference", "dynamic references")}, with ${leftOutCount} left out for a key that cannot be a config key.`;
|
|
4013
|
-
const
|
|
4078
|
+
const snippetsSentTo = (project, requestedSnippetCut) => project.storeCodeSnippets ? {
|
|
4079
|
+
kind: "as-requested",
|
|
4080
|
+
snippetCut: requestedSnippetCut
|
|
4081
|
+
} : {
|
|
4082
|
+
kind: "project-stores-none",
|
|
4083
|
+
snippetCut: { kind: "no-snippets" }
|
|
4084
|
+
};
|
|
4085
|
+
const snippetsSentText = (snippetsSent) => {
|
|
4086
|
+
switch (snippetsSent.kind) {
|
|
4087
|
+
case "as-requested": return "";
|
|
4088
|
+
case "project-stores-none": return "Code snippets were not sent: this project does not store them.\n";
|
|
4089
|
+
}
|
|
4090
|
+
};
|
|
4091
|
+
const uploadScan = async (context, root, repositoryOption, requestedSnippetCut) => {
|
|
4014
4092
|
const repository = repositoryIdentityOf(await readGitCheckout(scanRootDirectory(root), context.invocation.environmentVariables), repositoryOption, root.description);
|
|
4015
|
-
const { client, project,
|
|
4093
|
+
const { client, project, codeReferencesIn } = await connectToScannableProject(context);
|
|
4094
|
+
const snippetsSent = snippetsSentTo(project, requestedSnippetCut);
|
|
4095
|
+
const codeReferenceScan = await codeReferencesIn(root, snippetsSent.snippetCut);
|
|
4016
4096
|
if (codeReferenceScan.filesScanned === 0) throw new CliError(`No source files to scan in ${root.description}, so nothing was uploaded and ${project.slug} keeps its code references for ${repository.name}. Check the path given to scan, and include and exclude in configdirector.json.`);
|
|
4017
4097
|
const configKeyReferences = codeReferenceScan.references.filter((reference) => fitsKeyGrammar(reference.key));
|
|
4018
4098
|
const leftOutCount = codeReferenceScan.references.length - configKeyReferences.length;
|
|
@@ -4026,14 +4106,26 @@ const uploadScan = async (context, root, repositoryOption) => {
|
|
|
4026
4106
|
});
|
|
4027
4107
|
return {
|
|
4028
4108
|
json: responseBody,
|
|
4029
|
-
text: `${scanText(codeReferenceScan, root.description)}\n${uploadLine(project.slug, scannedRepository, leftOutCount)}\n
|
|
4109
|
+
text: `${scanText(codeReferenceScan, root.description)}\n${uploadLine(project.slug, scannedRepository, leftOutCount)}\n` + snippetsSentText(snippetsSent),
|
|
4030
4110
|
exitCode: ExitCode.success
|
|
4031
4111
|
};
|
|
4032
4112
|
};
|
|
4113
|
+
const contextLinesOf = (text) => {
|
|
4114
|
+
if (text === void 0) return 2;
|
|
4115
|
+
const contextLines = /^\d+$/.test(text) ? Number(text) : void 0;
|
|
4116
|
+
if (contextLines === void 0 || contextLines > 5) throw new CliError(`--context-lines takes a whole number from 0 to 5, not ${text}.`);
|
|
4117
|
+
return contextLines;
|
|
4118
|
+
};
|
|
4119
|
+
const snippetCutOf = (options) => options.snippets ? {
|
|
4120
|
+
kind: "around-each-reference",
|
|
4121
|
+
contextLines: contextLinesOf(options.contextLines)
|
|
4122
|
+
} : { kind: "no-snippets" };
|
|
4033
4123
|
const scan = async (context, [path], options) => {
|
|
4124
|
+
const snippetCut = snippetCutOf(options);
|
|
4034
4125
|
const root = await scanRootAt(context.invocation.workingDirectory, path);
|
|
4035
|
-
if (options.upload) return uploadScan(context, root, options.repository);
|
|
4036
|
-
const { project,
|
|
4126
|
+
if (options.upload) return uploadScan(context, root, options.repository, snippetCut);
|
|
4127
|
+
const { project, codeReferencesIn } = await connectToScannableProject(context);
|
|
4128
|
+
const codeReferenceScan = await codeReferencesIn(root, snippetCut);
|
|
4037
4129
|
return {
|
|
4038
4130
|
json: scanJson(project.slug, codeReferenceScan),
|
|
4039
4131
|
text: scanText(codeReferenceScan, root.description),
|
|
@@ -4370,7 +4462,8 @@ const validateText = (projectSlug, scan, scannedLocation, check) => {
|
|
|
4370
4462
|
};
|
|
4371
4463
|
const validate = async (context, [path]) => {
|
|
4372
4464
|
const root = await scanRootAt(context.invocation.workingDirectory, path);
|
|
4373
|
-
const { project, configs, ignoredKeys,
|
|
4465
|
+
const { project, configs, ignoredKeys, codeReferencesIn } = await connectToScannableProject(context);
|
|
4466
|
+
const codeReferenceScan = await codeReferencesIn(root, { kind: "no-snippets" });
|
|
4374
4467
|
const check = checkCodeReferences(codeReferenceScan.references, configs, ignoredKeys);
|
|
4375
4468
|
return {
|
|
4376
4469
|
json: {
|
|
@@ -4422,7 +4515,7 @@ const run = async (invocation) => {
|
|
|
4422
4515
|
});
|
|
4423
4516
|
program.command("login").description("save an API token for later runs, after checking it can list projects").action(commandAction(login));
|
|
4424
4517
|
program.command("init").description("write configdirector.json for a project in the current directory").action(commandAction(init));
|
|
4425
|
-
program.command("scan").argument("[path]", "directory or file to scan (default: the current directory)").description("list where the code reads config keys through a ConfigDirector SDK").option("--upload", "send the code references to the project, replacing those it holds for this repository").option("--repository <name>", "the repository name to upload under (default: the path of the git remote origin, such as ConfigDirector/cli)").action(commandAction(scan));
|
|
4518
|
+
program.command("scan").argument("[path]", "directory or file to scan (default: the current directory)").description("list where the code reads config keys through a ConfigDirector SDK").option("--upload", "send the code references to the project, replacing those it holds for this repository").option("--repository <name>", "the repository name to upload under (default: the path of the git remote origin, such as ConfigDirector/cli)").addOption(new Option("--context-lines <number>", `how many lines of code to include before and after each reference, from 0 to 5 (default: 2)`).conflicts("snippets")).option("--no-snippets", "leave out the lines of code around each reference").action(commandAction(scan));
|
|
4426
4519
|
program.command("validate").argument("[path]", "directory or file to check (default: the current directory)").description("check every config key the code reads against the project's configs, exiting 1 when one is wrong").action(commandAction(validate));
|
|
4427
4520
|
program.command("evaluate").argument("<key>", "the config's key").description("show the value a context gets for a config in an environment, and why").requiredOption("--environment <slug>", "slug of the environment whose targeting rules to evaluate (required)").option("--context-kind <kind>", "context kind of the context to evaluate for (default: user)").option("--context-id <identifier>", "identifier of the context").option("--context-name <name>", "name of the context").option("--context-trait <name=value>", "a trait of the context, as a string, or name:=<json> for a number, boolean, array or object; repeatable", repeatedOption).option("--context-json <json>", "the whole context as a JSON object, or @<file> to read it from a file").option("--saved-context <kind:identifier>", "a context ConfigDirector saved from SDK evaluations in the environment, such as user:123").addHelpText("after", "\nGive the context with the --context-* options, with --context-json, or with --saved-context. Without any of them, the config is evaluated for an empty context, as an SDK given none would.").action(commandAction(evaluate));
|
|
4428
4521
|
program.command("projects").description("list the projects of the API token's organization").command("list").description("list the organization's projects, whatever project is selected").action(commandAction(listProjects));
|
package/package.json
CHANGED
|
@@ -149,6 +149,9 @@
|
|
|
149
149
|
},
|
|
150
150
|
"test": {
|
|
151
151
|
"$ref": "#/$defs/test"
|
|
152
|
+
},
|
|
153
|
+
"snippet": {
|
|
154
|
+
"$ref": "#/$defs/snippet"
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
},
|
|
@@ -174,6 +177,58 @@
|
|
|
174
177
|
"test": {
|
|
175
178
|
"description": "Whether the file is in a test directory or has a test name.",
|
|
176
179
|
"type": "boolean"
|
|
180
|
+
},
|
|
181
|
+
"snippet": {
|
|
182
|
+
"$ref": "#/$defs/snippet"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"snippet": {
|
|
187
|
+
"description": "The reference line and the lines around it, as many before and after it as --context-lines asks for and the file holds. Absent with --no-snippets, and in `configdirector validate --json` output.",
|
|
188
|
+
"type": "object",
|
|
189
|
+
"required": ["firstLine", "lines", "language", "truncatedLines"],
|
|
190
|
+
"additionalProperties": false,
|
|
191
|
+
"properties": {
|
|
192
|
+
"firstLine": {
|
|
193
|
+
"description": "The number of the snippet's first line in the file, starting at 1.",
|
|
194
|
+
"type": "integer",
|
|
195
|
+
"minimum": 1
|
|
196
|
+
},
|
|
197
|
+
"lines": {
|
|
198
|
+
"description": "The lines, in order, without their line endings. A line longer than 250 characters is cut to its first 250.",
|
|
199
|
+
"type": "array",
|
|
200
|
+
"minItems": 1,
|
|
201
|
+
"maxItems": 11,
|
|
202
|
+
"items": {
|
|
203
|
+
"type": "string",
|
|
204
|
+
"maxLength": 250
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"language": {
|
|
208
|
+
"description": "The language to highlight the lines as, from the file's extension.",
|
|
209
|
+
"enum": [
|
|
210
|
+
"typescript",
|
|
211
|
+
"tsx",
|
|
212
|
+
"javascript",
|
|
213
|
+
"jsx",
|
|
214
|
+
"vue",
|
|
215
|
+
"svelte",
|
|
216
|
+
"dart",
|
|
217
|
+
"swift",
|
|
218
|
+
"kotlin",
|
|
219
|
+
"java",
|
|
220
|
+
"python",
|
|
221
|
+
"csharp",
|
|
222
|
+
"go"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
"truncatedLines": {
|
|
226
|
+
"description": "The numbers of the lines that were cut to 250 characters, empty when none was.",
|
|
227
|
+
"type": "array",
|
|
228
|
+
"items": {
|
|
229
|
+
"type": "integer",
|
|
230
|
+
"minimum": 1
|
|
231
|
+
}
|
|
177
232
|
}
|
|
178
233
|
}
|
|
179
234
|
},
|