@featurevisor/core 1.26.0 → 1.27.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/.eslintcache +1 -1
- package/CHANGELOG.md +22 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/builder/index.html +1 -1
- package/coverage/lcov-report/lib/builder/revision.js.html +1 -1
- package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
- package/coverage/lcov-report/lib/tester/index.html +1 -1
- package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
- package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/builder/index.html +1 -1
- package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
- package/coverage/lcov-report/src/tester/index.html +1 -1
- package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
- package/lib/assess-distribution/index.d.ts +2 -0
- package/lib/assess-distribution/index.js +37 -1
- package/lib/assess-distribution/index.js.map +1 -1
- package/lib/benchmark/index.d.ts +2 -0
- package/lib/benchmark/index.js +43 -1
- package/lib/benchmark/index.js.map +1 -1
- package/lib/builder/buildProject.d.ts +2 -0
- package/lib/builder/buildProject.js +48 -1
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/cli/cli.d.ts +26 -0
- package/lib/cli/cli.js +133 -0
- package/lib/cli/cli.js.map +1 -0
- package/lib/cli/index.d.ts +2 -0
- package/lib/cli/index.js +19 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/cli/plugins.d.ts +4 -0
- package/lib/cli/plugins.js +37 -0
- package/lib/cli/plugins.js.map +1 -0
- package/lib/config/projectConfig.d.ts +3 -0
- package/lib/config/projectConfig.js +69 -1
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/evaluate/index.d.ts +2 -0
- package/lib/evaluate/index.js +35 -1
- package/lib/evaluate/index.js.map +1 -1
- package/lib/find-duplicate-segments/index.d.ts +2 -0
- package/lib/find-duplicate-segments/index.js +34 -1
- package/lib/find-duplicate-segments/index.js.map +1 -1
- package/lib/find-usage/index.d.ts +2 -0
- package/lib/find-usage/index.js +50 -1
- package/lib/find-usage/index.js.map +1 -1
- package/lib/generate-code/index.d.ts +2 -0
- package/lib/generate-code/index.js +31 -1
- package/lib/generate-code/index.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/info/index.d.ts +2 -0
- package/lib/info/index.js +28 -1
- package/lib/info/index.js.map +1 -1
- package/lib/init/index.d.ts +2 -0
- package/lib/init/index.js +65 -1
- package/lib/init/index.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +71 -1
- package/lib/linter/featureSchema.js +147 -8
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/lintProject.d.ts +3 -0
- package/lib/linter/lintProject.js +344 -163
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/linter/printError.js +1 -3
- package/lib/linter/printError.js.map +1 -1
- package/lib/restore/index.d.ts +2 -0
- package/lib/restore/index.js +28 -1
- package/lib/restore/index.js.map +1 -1
- package/lib/site/index.d.ts +2 -2
- package/lib/site/index.js +86 -14
- package/lib/site/index.js.map +1 -1
- package/lib/tester/cliFormat.d.ts +1 -0
- package/lib/tester/cliFormat.js +2 -1
- package/lib/tester/cliFormat.js.map +1 -1
- package/lib/tester/testProject.d.ts +2 -0
- package/lib/tester/testProject.js +52 -1
- package/lib/tester/testProject.js.map +1 -1
- package/package.json +3 -2
- package/src/assess-distribution/index.ts +32 -0
- package/src/benchmark/index.ts +40 -0
- package/src/builder/buildProject.ts +42 -0
- package/src/cli/cli.ts +105 -0
- package/src/cli/index.ts +2 -0
- package/src/cli/plugins.ts +38 -0
- package/src/config/projectConfig.ts +28 -0
- package/src/evaluate/index.ts +30 -0
- package/src/find-duplicate-segments/index.ts +28 -0
- package/src/find-usage/index.ts +44 -0
- package/src/generate-code/index.ts +25 -0
- package/src/index.ts +1 -0
- package/src/info/index.ts +19 -0
- package/src/init/index.ts +21 -0
- package/src/linter/featureSchema.ts +194 -10
- package/src/linter/lintProject.ts +170 -18
- package/src/linter/printError.ts +1 -3
- package/src/restore/index.ts +19 -0
- package/src/site/index.ts +46 -2
- package/src/tester/cliFormat.ts +1 -0
- package/src/tester/testProject.ts +46 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Plugin } from "./cli";
|
|
2
|
+
|
|
3
|
+
import { initPlugin } from "../init";
|
|
4
|
+
import { lintPlugin } from "../linter";
|
|
5
|
+
import { buildPlugin } from "../builder";
|
|
6
|
+
import { restorePlugin } from "../restore";
|
|
7
|
+
import { testPlugin } from "../tester";
|
|
8
|
+
import { generateCodePlugin } from "../generate-code";
|
|
9
|
+
import { findDuplicateSegmentsPlugin } from "../find-duplicate-segments";
|
|
10
|
+
import { findUsagePlugin } from "../find-usage";
|
|
11
|
+
import { benchmarkPlugin } from "../benchmark";
|
|
12
|
+
import { configPlugin } from "../config";
|
|
13
|
+
import { evaluatePlugin } from "../evaluate";
|
|
14
|
+
import { assessDistributionPlugin } from "../assess-distribution";
|
|
15
|
+
import { infoPlugin } from "../info";
|
|
16
|
+
import { sitePlugin } from "../site";
|
|
17
|
+
|
|
18
|
+
// that do not require an existing project
|
|
19
|
+
export const nonProjectPlugins: Plugin[] = [initPlugin];
|
|
20
|
+
|
|
21
|
+
// that require an existing Featurevisor project
|
|
22
|
+
export const projectBasedPlugins: Plugin[] = [
|
|
23
|
+
lintPlugin,
|
|
24
|
+
buildPlugin,
|
|
25
|
+
restorePlugin,
|
|
26
|
+
testPlugin,
|
|
27
|
+
generateCodePlugin,
|
|
28
|
+
findDuplicateSegmentsPlugin,
|
|
29
|
+
findUsagePlugin,
|
|
30
|
+
benchmarkPlugin,
|
|
31
|
+
configPlugin,
|
|
32
|
+
evaluatePlugin,
|
|
33
|
+
assessDistributionPlugin,
|
|
34
|
+
infoPlugin,
|
|
35
|
+
sitePlugin,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const commonPlugins: Plugin[] = [];
|
|
@@ -4,6 +4,7 @@ import { BucketBy } from "@featurevisor/types";
|
|
|
4
4
|
|
|
5
5
|
import { Parser, parsers } from "./parsers";
|
|
6
6
|
import { FilesystemAdapter } from "../datasource/filesystemAdapter";
|
|
7
|
+
import type { Plugin } from "../cli";
|
|
7
8
|
|
|
8
9
|
export const FEATURES_DIRECTORY_NAME = "features";
|
|
9
10
|
export const SEGMENTS_DIRECTORY_NAME = "segments";
|
|
@@ -46,6 +47,7 @@ export interface ProjectConfig {
|
|
|
46
47
|
siteExportDirectoryPath: string;
|
|
47
48
|
enforceCatchAllRule?: boolean;
|
|
48
49
|
adapter: any; // @TODO: type this properly later
|
|
50
|
+
plugins: Plugin[];
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// rootDirectoryPath: path to the root directory of the project (without ending with a slash)
|
|
@@ -73,6 +75,7 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
|
|
|
73
75
|
siteExportDirectoryPath: path.join(rootDirectoryPath, SITE_EXPORT_DIRECTORY_NAME),
|
|
74
76
|
|
|
75
77
|
enforceCatchAllRule: false,
|
|
78
|
+
plugins: [],
|
|
76
79
|
};
|
|
77
80
|
|
|
78
81
|
const configModulePath = path.join(rootDirectoryPath, CONFIG_MODULE_NAME);
|
|
@@ -134,3 +137,28 @@ export function showProjectConfig(
|
|
|
134
137
|
console.log(` - ${key.padEnd(longestKeyLength, " ")}: ${projectConfig[key]}`);
|
|
135
138
|
}
|
|
136
139
|
}
|
|
140
|
+
|
|
141
|
+
export const configPlugin: Plugin = {
|
|
142
|
+
command: "config",
|
|
143
|
+
handler: async ({ rootDirectoryPath, parsed }) => {
|
|
144
|
+
const projectConfig = getProjectConfig(rootDirectoryPath);
|
|
145
|
+
showProjectConfig(projectConfig, {
|
|
146
|
+
print: parsed.print,
|
|
147
|
+
pretty: parsed.pretty,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
examples: [
|
|
151
|
+
{
|
|
152
|
+
command: "config",
|
|
153
|
+
description: "show the project configuration",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
command: "config --print",
|
|
157
|
+
description: "show the project configuration as JSON",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
command: "config --print --pretty",
|
|
161
|
+
description: "show the project configuration (as pretty JSON)",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
package/src/evaluate/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { Dependencies } from "../dependencies";
|
|
12
12
|
import { SCHEMA_VERSION } from "../config";
|
|
13
13
|
import { buildDatafile } from "../builder";
|
|
14
|
+
import { Plugin } from "../cli";
|
|
14
15
|
|
|
15
16
|
function printEvaluationDetails(evaluation: Evaluation) {
|
|
16
17
|
const ignoreKeys = ["featureKey", "variableKey", "traffic", "force"];
|
|
@@ -191,3 +192,32 @@ export async function evaluateFeature(deps: Dependencies, options: EvaluateOptio
|
|
|
191
192
|
console.log("No variables defined.");
|
|
192
193
|
}
|
|
193
194
|
}
|
|
195
|
+
|
|
196
|
+
export const evaluatePlugin: Plugin = {
|
|
197
|
+
command: "evaluate",
|
|
198
|
+
handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
|
|
199
|
+
await evaluateFeature(
|
|
200
|
+
{
|
|
201
|
+
rootDirectoryPath,
|
|
202
|
+
projectConfig,
|
|
203
|
+
datasource,
|
|
204
|
+
options: parsed,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
environment: parsed.environment,
|
|
208
|
+
feature: parsed.feature,
|
|
209
|
+
context: parsed.context ? JSON.parse(parsed.context) : {},
|
|
210
|
+
print: parsed.print,
|
|
211
|
+
pretty: parsed.pretty,
|
|
212
|
+
verbose: parsed.verbose,
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
examples: [
|
|
217
|
+
{
|
|
218
|
+
command:
|
|
219
|
+
'evaluate --environment=production --feature=my_feature --context=\'{"userId": "123"}\'',
|
|
220
|
+
description: "evaluate a feature against provided context",
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findDuplicateSegments, DuplicateSegmentsOptions } from "./findDuplicateSegments";
|
|
2
2
|
import { Dependencies } from "../dependencies";
|
|
3
|
+
import { Plugin } from "../cli";
|
|
3
4
|
|
|
4
5
|
export async function findDuplicateSegmentsInProject(
|
|
5
6
|
deps: Dependencies,
|
|
@@ -26,3 +27,30 @@ export async function findDuplicateSegmentsInProject(
|
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
export const findDuplicateSegmentsPlugin: Plugin = {
|
|
32
|
+
command: "find-duplicate-segments",
|
|
33
|
+
handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
|
|
34
|
+
await findDuplicateSegmentsInProject(
|
|
35
|
+
{
|
|
36
|
+
rootDirectoryPath,
|
|
37
|
+
projectConfig,
|
|
38
|
+
datasource,
|
|
39
|
+
options: parsed,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
authors: parsed.authors,
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
examples: [
|
|
47
|
+
{
|
|
48
|
+
command: "find-duplicate-segments",
|
|
49
|
+
description: "Find duplicate segments in the project",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
command: "find-duplicate-segments --authors",
|
|
53
|
+
description: "Find duplicate segments in the project and list authors",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
package/src/find-usage/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Condition, FeatureKey, SegmentKey, AttributeKey } from "@featurevisor/types";
|
|
2
2
|
|
|
3
3
|
import { Dependencies } from "../dependencies";
|
|
4
|
+
import { Plugin } from "../cli";
|
|
4
5
|
import {
|
|
5
6
|
extractAttributeKeysFromConditions,
|
|
6
7
|
extractSegmentKeysFromGroupSegments,
|
|
@@ -371,3 +372,46 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
371
372
|
|
|
372
373
|
console.log("Please specify a segment or attribute.");
|
|
373
374
|
}
|
|
375
|
+
|
|
376
|
+
export const findUsagePlugin: Plugin = {
|
|
377
|
+
command: "find-usage",
|
|
378
|
+
handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
|
|
379
|
+
await findUsageInProject(
|
|
380
|
+
{
|
|
381
|
+
rootDirectoryPath,
|
|
382
|
+
projectConfig,
|
|
383
|
+
datasource,
|
|
384
|
+
options: parsed,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
segment: parsed.segment,
|
|
388
|
+
attribute: parsed.attribute,
|
|
389
|
+
unusedSegments: parsed.unusedSegments,
|
|
390
|
+
unusedAttributes: parsed.unusedAttributes,
|
|
391
|
+
authors: parsed.authors,
|
|
392
|
+
},
|
|
393
|
+
);
|
|
394
|
+
},
|
|
395
|
+
examples: [
|
|
396
|
+
{
|
|
397
|
+
command: "find-usage --segment=<segmentKey>",
|
|
398
|
+
description: "Find usage of a segment",
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
command: "find-usage --attribute=<attributeKey>",
|
|
402
|
+
description: "Find usage of an attribute",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
command: "find-usage --unused-segments",
|
|
406
|
+
description: "Find unused segments",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
command: "find-usage --unused-attributes",
|
|
410
|
+
description: "Find unused attributes",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
command: "find-usage --authors",
|
|
414
|
+
description: "List authors of the usage",
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
};
|
|
@@ -5,6 +5,7 @@ import * as mkdirp from "mkdirp";
|
|
|
5
5
|
|
|
6
6
|
import { generateTypeScriptCodeForProject } from "./typescript";
|
|
7
7
|
import { Dependencies } from "../dependencies";
|
|
8
|
+
import { Plugin } from "../cli";
|
|
8
9
|
|
|
9
10
|
export const ALLOWED_LANGUAGES_FOR_CODE_GENERATION = ["typescript"];
|
|
10
11
|
|
|
@@ -50,3 +51,27 @@ export async function generateCodeForProject(
|
|
|
50
51
|
|
|
51
52
|
throw new Error(`Language ${cliOptions.language} is not supported`);
|
|
52
53
|
}
|
|
54
|
+
|
|
55
|
+
export const generateCodePlugin: Plugin = {
|
|
56
|
+
command: "generate-code",
|
|
57
|
+
handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
|
|
58
|
+
await generateCodeForProject(
|
|
59
|
+
{
|
|
60
|
+
rootDirectoryPath,
|
|
61
|
+
projectConfig,
|
|
62
|
+
datasource,
|
|
63
|
+
options: parsed,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
language: parsed.language,
|
|
67
|
+
outDir: parsed.outDir,
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
examples: [
|
|
72
|
+
{
|
|
73
|
+
command: "generate-code --language typescript --out-dir src/generated",
|
|
74
|
+
description: "Generate TypeScript code for the project",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
package/src/index.ts
CHANGED
package/src/info/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Dependencies } from "../dependencies";
|
|
2
2
|
import { getMatrixCombinations } from "../tester/matrix";
|
|
3
|
+
import { Plugin } from "../cli";
|
|
3
4
|
|
|
4
5
|
export async function showProjectInfo(deps: Dependencies) {
|
|
5
6
|
const { datasource } = deps;
|
|
@@ -52,3 +53,21 @@ export async function showProjectInfo(deps: Dependencies) {
|
|
|
52
53
|
|
|
53
54
|
console.log(" - Total assertions: ", assertionsCount);
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
export const infoPlugin: Plugin = {
|
|
58
|
+
command: "info",
|
|
59
|
+
handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
|
|
60
|
+
await showProjectInfo({
|
|
61
|
+
rootDirectoryPath,
|
|
62
|
+
projectConfig,
|
|
63
|
+
datasource,
|
|
64
|
+
options: parsed,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
examples: [
|
|
68
|
+
{
|
|
69
|
+
command: "info",
|
|
70
|
+
description: "show various stats for the project",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
package/src/init/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import * as tar from "tar";
|
|
3
3
|
|
|
4
|
+
import { Plugin } from "../cli";
|
|
5
|
+
|
|
4
6
|
export const DEFAULT_EXAMPLE = "example-yml";
|
|
5
7
|
|
|
6
8
|
export const EXAMPLES_ORG_NAME = "fahad19";
|
|
@@ -42,3 +44,22 @@ export function initProject(
|
|
|
42
44
|
});
|
|
43
45
|
});
|
|
44
46
|
}
|
|
47
|
+
|
|
48
|
+
export const initPlugin: Plugin = {
|
|
49
|
+
command: "init",
|
|
50
|
+
handler: async function (options) {
|
|
51
|
+
const { rootDirectoryPath, parsed } = options;
|
|
52
|
+
|
|
53
|
+
await initProject(rootDirectoryPath, parsed.example);
|
|
54
|
+
},
|
|
55
|
+
examples: [
|
|
56
|
+
{
|
|
57
|
+
command: "init",
|
|
58
|
+
description: "scaffold a new project in current directory",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
command: "init --example=exampleName",
|
|
62
|
+
description: "scaffold a new project in current directory from known example",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
@@ -4,6 +4,124 @@ import { ProjectConfig } from "../config";
|
|
|
4
4
|
|
|
5
5
|
const tagRegex = /^[a-z0-9-]+$/;
|
|
6
6
|
|
|
7
|
+
function isFlatObject(value) {
|
|
8
|
+
let isFlat = true;
|
|
9
|
+
|
|
10
|
+
Object.keys(value).forEach((key) => {
|
|
11
|
+
if (typeof value[key] === "object") {
|
|
12
|
+
isFlat = false;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return isFlat;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isArrayOfStrings(value) {
|
|
20
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function superRefineVariableValue(variableSchema, variableValue, path, ctx) {
|
|
24
|
+
if (!variableSchema) {
|
|
25
|
+
let message = `Unknown variable with value: ${variableValue}`;
|
|
26
|
+
|
|
27
|
+
if (path.length > 0) {
|
|
28
|
+
const lastPath = path[path.length - 1];
|
|
29
|
+
|
|
30
|
+
if (typeof lastPath === "string") {
|
|
31
|
+
message = `Unknown variable "${lastPath}" with value: ${variableValue}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ctx.addIssue({
|
|
36
|
+
code: z.ZodIssueCode.custom,
|
|
37
|
+
message,
|
|
38
|
+
path,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// string
|
|
45
|
+
if (variableSchema.type === "string") {
|
|
46
|
+
if (typeof variableValue !== "string") {
|
|
47
|
+
ctx.addIssue({
|
|
48
|
+
code: z.ZodIssueCode.custom,
|
|
49
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
|
|
50
|
+
path,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// integer, double
|
|
58
|
+
if (["integer", "double"].indexOf(variableSchema.type) > -1) {
|
|
59
|
+
if (typeof variableValue !== "number") {
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: z.ZodIssueCode.custom,
|
|
62
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
|
|
63
|
+
path,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// boolean
|
|
71
|
+
if (variableSchema.type === "boolean") {
|
|
72
|
+
if (typeof variableValue !== "boolean") {
|
|
73
|
+
ctx.addIssue({
|
|
74
|
+
code: z.ZodIssueCode.custom,
|
|
75
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
|
|
76
|
+
path,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// array
|
|
84
|
+
if (variableSchema.type === "array") {
|
|
85
|
+
if (!Array.isArray(variableValue) || !isArrayOfStrings(variableValue)) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: z.ZodIssueCode.custom,
|
|
88
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
|
|
89
|
+
path,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// object
|
|
97
|
+
if (variableSchema.type === "object") {
|
|
98
|
+
if (typeof variableValue !== "object" || !isFlatObject(variableValue)) {
|
|
99
|
+
ctx.addIssue({
|
|
100
|
+
code: z.ZodIssueCode.custom,
|
|
101
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
|
|
102
|
+
path,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// json
|
|
110
|
+
if (variableSchema.type === "json") {
|
|
111
|
+
try {
|
|
112
|
+
JSON.parse(variableValue as string);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
ctx.addIssue({
|
|
115
|
+
code: z.ZodIssueCode.custom,
|
|
116
|
+
message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
|
|
117
|
+
path,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
7
125
|
export function getFeatureZodSchema(
|
|
8
126
|
projectConfig: ProjectConfig,
|
|
9
127
|
conditionsZodSchema,
|
|
@@ -19,15 +137,7 @@ export function getFeatureZodSchema(
|
|
|
19
137
|
z.array(z.string()),
|
|
20
138
|
z.record(z.unknown()).refine(
|
|
21
139
|
(value) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Object.keys(value).forEach((key) => {
|
|
25
|
-
if (typeof value[key] === "object") {
|
|
26
|
-
isFlat = false;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return isFlat;
|
|
140
|
+
return isFlatObject(value);
|
|
31
141
|
},
|
|
32
142
|
{
|
|
33
143
|
message: "object is not flat",
|
|
@@ -286,7 +396,81 @@ export function getFeatureZodSchema(
|
|
|
286
396
|
.optional(),
|
|
287
397
|
environments: allEnvironmentsZodSchema,
|
|
288
398
|
})
|
|
289
|
-
.strict()
|
|
399
|
+
.strict()
|
|
400
|
+
.superRefine((value, ctx) => {
|
|
401
|
+
if (!value.variablesSchema) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const allVariablesSchema = value.variablesSchema;
|
|
406
|
+
const variableSchemaByKey = {};
|
|
407
|
+
|
|
408
|
+
// variablesSchema[n].defaultValue
|
|
409
|
+
allVariablesSchema.forEach((variableSchema, n) => {
|
|
410
|
+
variableSchemaByKey[variableSchema.key] = variableSchema;
|
|
411
|
+
|
|
412
|
+
superRefineVariableValue(
|
|
413
|
+
variableSchema,
|
|
414
|
+
variableSchema.defaultValue,
|
|
415
|
+
["variablesSchema", n, "defaultValue"],
|
|
416
|
+
ctx,
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// variations[n].variables[n].value
|
|
421
|
+
if (value.variations) {
|
|
422
|
+
value.variations.forEach((variation, variationN) => {
|
|
423
|
+
if (!variation.variables) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
variation.variables.forEach((variable, variableN) => {
|
|
428
|
+
superRefineVariableValue(
|
|
429
|
+
variableSchemaByKey[variable.key],
|
|
430
|
+
variable.value,
|
|
431
|
+
["variations", variationN, "variables", variableN, "value"],
|
|
432
|
+
ctx,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// variations[n].variables[n].overrides[n].value
|
|
436
|
+
if (variable.overrides) {
|
|
437
|
+
variable.overrides.forEach((override, overrideN) => {
|
|
438
|
+
superRefineVariableValue(
|
|
439
|
+
variableSchemaByKey[variable.key],
|
|
440
|
+
override.value,
|
|
441
|
+
[
|
|
442
|
+
"variations",
|
|
443
|
+
variationN,
|
|
444
|
+
"variables",
|
|
445
|
+
variableN,
|
|
446
|
+
"overrides",
|
|
447
|
+
overrideN,
|
|
448
|
+
"value",
|
|
449
|
+
],
|
|
450
|
+
ctx,
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// environments[key].rules[n].variables[key]
|
|
459
|
+
Object.keys(value.environments).forEach((environmentKey) => {
|
|
460
|
+
value.environments[environmentKey].rules.forEach((rule, ruleN) => {
|
|
461
|
+
if (rule.variables) {
|
|
462
|
+
Object.keys(rule.variables).forEach((variableKey) => {
|
|
463
|
+
superRefineVariableValue(
|
|
464
|
+
variableSchemaByKey[variableKey],
|
|
465
|
+
rule.variables[variableKey],
|
|
466
|
+
["environments", environmentKey, "rules", ruleN, "variables", variableKey],
|
|
467
|
+
ctx,
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
290
474
|
|
|
291
475
|
return featureZodSchema;
|
|
292
476
|
}
|