@featurevisor/core 1.31.0 → 1.32.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/CHANGELOG.md +11 -0
- package/coverage/clover.xml +11 -11
- package/coverage/coverage-final.json +2 -2
- package/coverage/lcov-report/index.html +7 -7
- 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 +5 -5
- package/coverage/lcov-report/lib/tester/matrix.js.html +5 -5
- 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 +5 -5
- package/coverage/lcov-report/src/tester/matrix.ts.html +5 -5
- package/coverage/lcov.info +30 -22
- package/lib/assess-distribution/index.d.ts +1 -1
- package/lib/assess-distribution/index.js +2 -2
- package/lib/assess-distribution/index.js.map +1 -1
- package/lib/benchmark/index.d.ts +1 -1
- package/lib/benchmark/index.js +2 -2
- package/lib/benchmark/index.js.map +1 -1
- package/lib/builder/buildDatafile.d.ts +1 -1
- package/lib/builder/buildDatafile.js +53 -40
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildProject.js +87 -50
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/config/projectConfig.d.ts +1 -1
- package/lib/datasource/adapter.d.ts +3 -3
- package/lib/datasource/adapter.js.map +1 -1
- package/lib/datasource/datasource.d.ts +2 -2
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.d.ts +1 -1
- package/lib/datasource/filesystemAdapter.js +9 -3
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/evaluate/index.d.ts +1 -1
- package/lib/evaluate/index.js +2 -2
- package/lib/evaluate/index.js.map +1 -1
- package/lib/find-usage/index.js +60 -26
- package/lib/find-usage/index.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +194 -5
- package/lib/linter/featureSchema.js +70 -60
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/testSchema.d.ts +1 -1
- package/lib/linter/testSchema.js +16 -13
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/site/generateSiteSearchIndex.js +65 -24
- package/lib/site/generateSiteSearchIndex.js.map +1 -1
- package/lib/tester/matrix.js +2 -2
- package/lib/tester/matrix.js.map +1 -1
- package/lib/tester/testFeature.d.ts +3 -4
- package/lib/tester/testFeature.js +1 -2
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testProject.d.ts +1 -3
- package/lib/tester/testProject.js +28 -12
- package/lib/tester/testProject.js.map +1 -1
- package/package.json +5 -5
- package/src/assess-distribution/index.ts +3 -3
- package/src/benchmark/index.ts +3 -3
- package/src/builder/buildDatafile.ts +25 -8
- package/src/builder/buildProject.ts +72 -31
- package/src/config/projectConfig.ts +1 -1
- package/src/datasource/adapter.ts +6 -3
- package/src/datasource/datasource.ts +2 -2
- package/src/datasource/filesystemAdapter.ts +12 -4
- package/src/evaluate/index.ts +3 -3
- package/src/find-usage/index.ts +65 -29
- package/src/linter/featureSchema.ts +100 -82
- package/src/linter/testSchema.ts +21 -16
- package/src/site/generateSiteSearchIndex.ts +71 -24
- package/src/tester/matrix.ts +2 -2
- package/src/tester/testFeature.ts +3 -2
- package/src/tester/testProject.ts +27 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { SCHEMA_VERSION } from "../config";
|
|
1
|
+
import { SCHEMA_VERSION, ProjectConfig } from "../config";
|
|
2
|
+
import { Datasource } from "../datasource";
|
|
2
3
|
|
|
3
4
|
import { getNextRevision } from "./revision";
|
|
4
5
|
import { buildDatafile, getCustomDatafile } from "./buildDatafile";
|
|
@@ -19,6 +20,58 @@ export interface BuildCLIOptions {
|
|
|
19
20
|
datafilesDir?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
async function buildForEnvironment({
|
|
24
|
+
projectConfig,
|
|
25
|
+
datasource,
|
|
26
|
+
nextRevision,
|
|
27
|
+
environment,
|
|
28
|
+
tags,
|
|
29
|
+
cliOptions,
|
|
30
|
+
}: {
|
|
31
|
+
projectConfig: ProjectConfig;
|
|
32
|
+
datasource: Datasource;
|
|
33
|
+
nextRevision: string;
|
|
34
|
+
environment: string | false;
|
|
35
|
+
tags: string[];
|
|
36
|
+
cliOptions: BuildCLIOptions;
|
|
37
|
+
}) {
|
|
38
|
+
console.log(`\nBuilding datafiles for environment: ${environment}`);
|
|
39
|
+
|
|
40
|
+
const existingState = await datasource.readState(environment);
|
|
41
|
+
|
|
42
|
+
for (const tag of tags) {
|
|
43
|
+
console.log(`\n => Tag: ${tag}`);
|
|
44
|
+
|
|
45
|
+
const datafileContent = await buildDatafile(
|
|
46
|
+
projectConfig,
|
|
47
|
+
datasource,
|
|
48
|
+
{
|
|
49
|
+
schemaVersion: cliOptions.schemaVersion || SCHEMA_VERSION,
|
|
50
|
+
revision: nextRevision,
|
|
51
|
+
environment: environment,
|
|
52
|
+
tag: tag,
|
|
53
|
+
inflate: cliOptions.inflate,
|
|
54
|
+
},
|
|
55
|
+
existingState,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// write datafile for environment/tag
|
|
59
|
+
await datasource.writeDatafile(datafileContent, {
|
|
60
|
+
environment,
|
|
61
|
+
tag,
|
|
62
|
+
datafilesDir: cliOptions.datafilesDir,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof cliOptions.stateFiles === "undefined" || cliOptions.stateFiles) {
|
|
67
|
+
// write state for environment
|
|
68
|
+
await datasource.writeState(environment, existingState);
|
|
69
|
+
|
|
70
|
+
// write revision
|
|
71
|
+
await datasource.writeRevision(nextRevision);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
22
75
|
export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptions = {}) {
|
|
23
76
|
const { projectConfig, datasource } = deps;
|
|
24
77
|
|
|
@@ -63,42 +116,30 @@ export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptio
|
|
|
63
116
|
const nextRevision =
|
|
64
117
|
(cliOptions.revision && cliOptions.revision.toString()) || getNextRevision(currentRevision);
|
|
65
118
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (const tag of tags) {
|
|
72
|
-
console.log(`\n => Tag: ${tag}`);
|
|
73
|
-
|
|
74
|
-
const datafileContent = await buildDatafile(
|
|
119
|
+
// with environments
|
|
120
|
+
if (Array.isArray(environments)) {
|
|
121
|
+
for (const environment of environments) {
|
|
122
|
+
await buildForEnvironment({
|
|
75
123
|
projectConfig,
|
|
76
124
|
datasource,
|
|
77
|
-
|
|
78
|
-
schemaVersion: cliOptions.schemaVersion || SCHEMA_VERSION,
|
|
79
|
-
revision: nextRevision,
|
|
80
|
-
environment: environment,
|
|
81
|
-
tag: tag,
|
|
82
|
-
inflate: cliOptions.inflate,
|
|
83
|
-
},
|
|
84
|
-
existingState,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
// write datafile for environment/tag
|
|
88
|
-
await datasource.writeDatafile(datafileContent, {
|
|
125
|
+
nextRevision,
|
|
89
126
|
environment,
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
tags,
|
|
128
|
+
cliOptions,
|
|
92
129
|
});
|
|
93
130
|
}
|
|
131
|
+
}
|
|
94
132
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
133
|
+
// no environment
|
|
134
|
+
if (environments === false) {
|
|
135
|
+
await buildForEnvironment({
|
|
136
|
+
projectConfig,
|
|
137
|
+
datasource,
|
|
138
|
+
nextRevision,
|
|
139
|
+
environment: false,
|
|
140
|
+
tags,
|
|
141
|
+
cliOptions,
|
|
142
|
+
});
|
|
102
143
|
}
|
|
103
144
|
|
|
104
145
|
console.log("\nLatest revision:", nextRevision);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "@featurevisor/types";
|
|
10
10
|
|
|
11
11
|
export interface DatafileOptions {
|
|
12
|
-
environment: EnvironmentKey;
|
|
12
|
+
environment: EnvironmentKey | false;
|
|
13
13
|
tag: string;
|
|
14
14
|
datafilesDir?: string;
|
|
15
15
|
}
|
|
@@ -23,8 +23,11 @@ export abstract class Adapter {
|
|
|
23
23
|
abstract deleteEntity(entityType: EntityType, entityKey: string): Promise<void>;
|
|
24
24
|
|
|
25
25
|
// state
|
|
26
|
-
abstract readState(environment: EnvironmentKey): Promise<ExistingState>;
|
|
27
|
-
abstract writeState(
|
|
26
|
+
abstract readState(environment: EnvironmentKey | false): Promise<ExistingState>;
|
|
27
|
+
abstract writeState(
|
|
28
|
+
environment: EnvironmentKey | false,
|
|
29
|
+
existingState: ExistingState,
|
|
30
|
+
): Promise<void>;
|
|
28
31
|
|
|
29
32
|
// datafile
|
|
30
33
|
abstract readDatafile(options: DatafileOptions): Promise<DatafileContent>;
|
|
@@ -35,11 +35,11 @@ export class Datasource {
|
|
|
35
35
|
/**
|
|
36
36
|
* State
|
|
37
37
|
*/
|
|
38
|
-
readState(environment: EnvironmentKey): Promise<ExistingState> {
|
|
38
|
+
readState(environment: EnvironmentKey | false): Promise<ExistingState> {
|
|
39
39
|
return this.adapter.readState(environment);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
writeState(environment: EnvironmentKey, existingState: ExistingState) {
|
|
42
|
+
writeState(environment: EnvironmentKey | false, existingState: ExistingState) {
|
|
43
43
|
return this.adapter.writeState(environment, existingState);
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -23,9 +23,11 @@ const commitRegex = /^commit (\w+)\nAuthor: (.+) <(.+)>\nDate: (.+)\n\n(.+)/gm
|
|
|
23
23
|
|
|
24
24
|
export function getExistingStateFilePath(
|
|
25
25
|
projectConfig: ProjectConfig,
|
|
26
|
-
environment: EnvironmentKey,
|
|
26
|
+
environment: EnvironmentKey | false,
|
|
27
27
|
): string {
|
|
28
|
-
|
|
28
|
+
const fileName = environment ? `existing-state-${environment}.json` : `existing-state.json`;
|
|
29
|
+
|
|
30
|
+
return path.join(projectConfig.stateDirectoryPath, fileName);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export function getRevisionFilePath(projectConfig: ProjectConfig): string {
|
|
@@ -185,7 +187,11 @@ export class FilesystemAdapter extends Adapter {
|
|
|
185
187
|
const fileName = `datafile-tag-${options.tag}.json`;
|
|
186
188
|
const dir = options.datafilesDir || this.config.outputDirectoryPath;
|
|
187
189
|
|
|
188
|
-
|
|
190
|
+
if (options.environment) {
|
|
191
|
+
return path.join(dir, options.environment, fileName);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return path.join(dir, fileName);
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
async readDatafile(options: DatafileOptions): Promise<DatafileContent> {
|
|
@@ -199,7 +205,9 @@ export class FilesystemAdapter extends Adapter {
|
|
|
199
205
|
async writeDatafile(datafileContent: DatafileContent, options: DatafileOptions): Promise<void> {
|
|
200
206
|
const dir = options.datafilesDir || this.config.outputDirectoryPath;
|
|
201
207
|
|
|
202
|
-
const outputEnvironmentDirPath =
|
|
208
|
+
const outputEnvironmentDirPath = options.environment
|
|
209
|
+
? path.join(dir, options.environment)
|
|
210
|
+
: dir;
|
|
203
211
|
mkdirp.sync(outputEnvironmentDirPath);
|
|
204
212
|
|
|
205
213
|
const outputFilePath = this.getDatafilePath(options);
|
package/src/evaluate/index.ts
CHANGED
|
@@ -50,7 +50,7 @@ function printHeader(message: string) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface EvaluateOptions {
|
|
53
|
-
environment
|
|
53
|
+
environment?: string;
|
|
54
54
|
feature: string;
|
|
55
55
|
context: Record<string, unknown>;
|
|
56
56
|
print?: boolean;
|
|
@@ -69,14 +69,14 @@ export interface Log {
|
|
|
69
69
|
export async function evaluateFeature(deps: Dependencies, options: EvaluateOptions) {
|
|
70
70
|
const { datasource, projectConfig } = deps;
|
|
71
71
|
|
|
72
|
-
const existingState = await datasource.readState(options.environment);
|
|
72
|
+
const existingState = await datasource.readState(options.environment || false);
|
|
73
73
|
const datafileContent = await buildDatafile(
|
|
74
74
|
projectConfig,
|
|
75
75
|
datasource,
|
|
76
76
|
{
|
|
77
77
|
schemaVersion: options.schemaVersion || SCHEMA_VERSION,
|
|
78
78
|
revision: "include-all-features",
|
|
79
|
-
environment: options.environment,
|
|
79
|
+
environment: options.environment || false,
|
|
80
80
|
inflate: options.inflate,
|
|
81
81
|
},
|
|
82
82
|
existingState,
|
package/src/find-usage/index.ts
CHANGED
|
@@ -52,34 +52,70 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// variable overrides inside variations
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
55
|
+
if (feature.variations) {
|
|
56
|
+
feature.variations.forEach((variation) => {
|
|
57
|
+
if (variation.variables) {
|
|
58
|
+
variation.variables.forEach((variable) => {
|
|
59
|
+
if (variable.overrides) {
|
|
60
|
+
variable.overrides.forEach((override) => {
|
|
61
|
+
if (override.segments) {
|
|
62
|
+
extractSegmentKeysFromGroupSegments(override.segments).forEach((segmentKey) =>
|
|
63
|
+
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (override.conditions) {
|
|
68
|
+
extractAttributeKeysFromConditions(override.conditions).forEach((attributeKey) =>
|
|
69
|
+
usageInFeatures[featureKey].attributes.add(attributeKey),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// with environments
|
|
80
|
+
if (Array.isArray(projectConfig.environments)) {
|
|
81
|
+
projectConfig.environments.forEach((environment) => {
|
|
82
|
+
if (!feature.environments) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// force
|
|
87
|
+
if (feature.environments[environment].force) {
|
|
88
|
+
feature.environments[environment].force?.forEach((force) => {
|
|
89
|
+
if (force.segments) {
|
|
90
|
+
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) =>
|
|
91
|
+
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (force.conditions) {
|
|
96
|
+
extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) =>
|
|
97
|
+
usageInFeatures[featureKey].attributes.add(attributeKey),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
79
102
|
|
|
103
|
+
// rules
|
|
104
|
+
if (feature.environments[environment].rules) {
|
|
105
|
+
feature.environments[environment].rules?.forEach((rule) => {
|
|
106
|
+
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) =>
|
|
107
|
+
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// no environments
|
|
115
|
+
if (projectConfig.environments === false) {
|
|
80
116
|
// force
|
|
81
|
-
if (feature.
|
|
82
|
-
feature.
|
|
117
|
+
if (feature.force) {
|
|
118
|
+
feature.force.forEach((force) => {
|
|
83
119
|
if (force.segments) {
|
|
84
120
|
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) =>
|
|
85
121
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
@@ -95,14 +131,14 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
95
131
|
}
|
|
96
132
|
|
|
97
133
|
// rules
|
|
98
|
-
if (feature.
|
|
99
|
-
feature.
|
|
134
|
+
if (feature.rules) {
|
|
135
|
+
feature.rules.forEach((rule) => {
|
|
100
136
|
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) =>
|
|
101
137
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
102
138
|
);
|
|
103
139
|
});
|
|
104
140
|
}
|
|
105
|
-
}
|
|
141
|
+
}
|
|
106
142
|
}
|
|
107
143
|
|
|
108
144
|
return usageInFeatures;
|
|
@@ -227,93 +227,102 @@ export function getFeatureZodSchema(
|
|
|
227
227
|
|
|
228
228
|
const groupSegmentsZodSchema = z.union([z.array(groupSegmentZodSchema), groupSegmentZodSchema]);
|
|
229
229
|
|
|
230
|
-
const
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
230
|
+
const exposeSchema = z
|
|
231
|
+
.union([z.boolean(), z.array(z.string().refine((value) => projectConfig.tags.includes(value)))])
|
|
232
|
+
.optional();
|
|
233
|
+
|
|
234
|
+
const rulesSchema = z
|
|
235
|
+
.array(
|
|
236
|
+
z
|
|
237
|
+
.object({
|
|
238
|
+
key: z.string(),
|
|
239
|
+
description: z.string().optional(),
|
|
240
|
+
segments: groupSegmentsZodSchema,
|
|
241
|
+
percentage: z.number().min(0).max(100),
|
|
242
|
+
|
|
243
|
+
enabled: z.boolean().optional(),
|
|
244
|
+
variation: variationValueZodSchema.optional(),
|
|
245
|
+
variables: z.record(variableValueZodSchema).optional(),
|
|
246
|
+
})
|
|
247
|
+
.strict(),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// must have at least one rule
|
|
251
|
+
.refine(
|
|
252
|
+
(value) => value.length > 0,
|
|
253
|
+
() => ({
|
|
254
|
+
message: "Must have at least one rule",
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// duplicate rules
|
|
259
|
+
.refine(
|
|
260
|
+
(value) => {
|
|
261
|
+
const keys = value.map((v) => v.key);
|
|
262
|
+
return keys.length === new Set(keys).size;
|
|
263
|
+
},
|
|
264
|
+
(value) => ({
|
|
265
|
+
message: "Duplicate rule keys found: " + value.map((v) => v.key).join(", "),
|
|
266
|
+
}),
|
|
267
|
+
)
|
|
261
268
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
(value) => ({
|
|
269
|
-
message: "Duplicate rule keys found: " + value.map((v) => v.key).join(", "),
|
|
270
|
-
}),
|
|
271
|
-
)
|
|
269
|
+
// enforce catch-all rule
|
|
270
|
+
.refine(
|
|
271
|
+
(value) => {
|
|
272
|
+
if (!projectConfig.enforceCatchAllRule) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
272
275
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
const hasCatchAllAsLastRule = value[value.length - 1].segments === "*";
|
|
277
|
+
return hasCatchAllAsLastRule;
|
|
278
|
+
},
|
|
279
|
+
() => ({
|
|
280
|
+
message: `Missing catch-all rule with \`segments: "*"\` at the end`,
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const forceSchema = z
|
|
285
|
+
.array(
|
|
286
|
+
z.union([
|
|
287
|
+
z
|
|
288
|
+
.object({
|
|
289
|
+
segments: groupSegmentsZodSchema,
|
|
290
|
+
enabled: z.boolean().optional(),
|
|
291
|
+
variation: variationValueZodSchema.optional(),
|
|
292
|
+
variables: z.record(variableValueZodSchema).optional(),
|
|
293
|
+
})
|
|
294
|
+
.strict(),
|
|
295
|
+
z
|
|
296
|
+
.object({
|
|
297
|
+
conditions: conditionsZodSchema,
|
|
298
|
+
enabled: z.boolean().optional(),
|
|
299
|
+
variation: variationValueZodSchema.optional(),
|
|
300
|
+
variables: z.record(variableValueZodSchema).optional(),
|
|
301
|
+
})
|
|
302
|
+
.strict(),
|
|
303
|
+
]),
|
|
304
|
+
)
|
|
305
|
+
.optional();
|
|
279
306
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}),
|
|
286
|
-
),
|
|
287
|
-
force: z
|
|
288
|
-
.array(
|
|
289
|
-
z.union([
|
|
290
|
-
z
|
|
291
|
-
.object({
|
|
292
|
-
segments: groupSegmentsZodSchema,
|
|
293
|
-
enabled: z.boolean().optional(),
|
|
294
|
-
variation: variationValueZodSchema.optional(),
|
|
295
|
-
variables: z.record(variableValueZodSchema).optional(),
|
|
296
|
-
})
|
|
297
|
-
.strict(),
|
|
298
|
-
z
|
|
299
|
-
.object({
|
|
300
|
-
conditions: conditionsZodSchema,
|
|
301
|
-
enabled: z.boolean().optional(),
|
|
302
|
-
variation: variationValueZodSchema.optional(),
|
|
303
|
-
variables: z.record(variableValueZodSchema).optional(),
|
|
304
|
-
})
|
|
305
|
-
.strict(),
|
|
306
|
-
]),
|
|
307
|
-
)
|
|
308
|
-
.optional(),
|
|
307
|
+
const environmentZodSchema = z
|
|
308
|
+
.object({
|
|
309
|
+
expose: exposeSchema,
|
|
310
|
+
rules: rulesSchema,
|
|
311
|
+
force: forceSchema,
|
|
309
312
|
})
|
|
310
313
|
.strict();
|
|
311
314
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
let allEnvironmentsZodSchema: z.ZodTypeAny = z.never();
|
|
316
|
+
|
|
317
|
+
if (Array.isArray(projectConfig.environments)) {
|
|
318
|
+
const allEnvironmentsSchema = {};
|
|
319
|
+
|
|
320
|
+
projectConfig.environments.forEach((environmentKey) => {
|
|
321
|
+
allEnvironmentsSchema[environmentKey] = environmentZodSchema;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
allEnvironmentsZodSchema = z.object(allEnvironmentsSchema).strict();
|
|
325
|
+
}
|
|
317
326
|
|
|
318
327
|
const attributeKeyZodSchema = z.string().refine(
|
|
319
328
|
(value) => value === "*" || availableAttributeKeys.includes(value),
|
|
@@ -447,7 +456,16 @@ export function getFeatureZodSchema(
|
|
|
447
456
|
}),
|
|
448
457
|
)
|
|
449
458
|
.optional(),
|
|
450
|
-
|
|
459
|
+
|
|
460
|
+
// with environments
|
|
461
|
+
environments: Array.isArray(projectConfig.environments)
|
|
462
|
+
? allEnvironmentsZodSchema
|
|
463
|
+
: z.never().optional(),
|
|
464
|
+
|
|
465
|
+
// no environments
|
|
466
|
+
expose: projectConfig.environments === false ? exposeSchema : z.never().optional(),
|
|
467
|
+
rules: projectConfig.environments === false ? rulesSchema : z.never().optional(),
|
|
468
|
+
force: projectConfig.environments === false ? forceSchema : z.never().optional(),
|
|
451
469
|
})
|
|
452
470
|
.strict()
|
|
453
471
|
.superRefine((value, ctx) => {
|
package/src/linter/testSchema.ts
CHANGED
|
@@ -59,24 +59,29 @@ export function getTestsZodSchema(
|
|
|
59
59
|
// because of supporting matrix
|
|
60
60
|
z.string(),
|
|
61
61
|
]),
|
|
62
|
-
environment:
|
|
63
|
-
(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
environment: Array.isArray(projectConfig.environments)
|
|
63
|
+
? z.string().refine(
|
|
64
|
+
(value) => {
|
|
65
|
+
if (value.indexOf("${{") === 0) {
|
|
66
|
+
// allow unknown strings for matrix
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
// otherwise only known environments should be passed
|
|
71
|
+
if (
|
|
72
|
+
Array.isArray(projectConfig.environments) &&
|
|
73
|
+
projectConfig.environments.includes(value)
|
|
74
|
+
) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
80
|
+
(value) => ({
|
|
81
|
+
message: `Unknown environment "${value}"`,
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
: z.never().optional(),
|
|
80
85
|
context: z.record(z.unknown()),
|
|
81
86
|
expectedToBeEnabled: z.boolean(),
|
|
82
87
|
expectedVariation: z.string().optional(),
|