Skip to content

Commit fbd8d91

Browse files
authored
feat: added stale comment for stale features in generate types (#452)
1 parent 6b442b9 commit fbd8d91

5 files changed

Lines changed: 94 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ $ npm install -g @devcycle/cli
108108
$ dvc COMMAND
109109
running command...
110110
$ dvc (--version)
111-
@devcycle/cli/5.20.3 linux-x64 node-v22.12.0
111+
@devcycle/cli/5.20.3 darwin-arm64 node-v20.11.1
112112
$ dvc --help [COMMAND]
113113
USAGE
114114
$ dvc COMMAND

oclif.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "5.20.2",
2+
"version": "5.20.3",
33
"commands": {
44
"authCommand": {
55
"id": "authCommand",

src/api/features.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const fetchFeatures = async (
1818
page?: number
1919
perPage?: number
2020
search?: string
21+
staleness?: string
2122
} = {},
2223
): Promise<Feature[]> => {
2324
const response = await apiClient.get(FEATURE_URL, {

src/api/zodClient.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ const ObfuscationSettings = z.object({
2727
enabled: z.boolean(),
2828
required: z.boolean(),
2929
})
30+
const StalenessSettings = z
31+
.object({
32+
enabled: z.boolean(),
33+
})
34+
.optional()
3035
const ProjectSettings = z.object({
3136
edgeDB: EdgeDBSettings,
3237
optIn: OptInSettings,
3338
sdkTypeVisibility: SDKTypeVisibilitySettings,
3439
obfuscation: ObfuscationSettings,
40+
staleness: StalenessSettings,
3541
})
3642
const CreateProjectDto = z.object({
3743
name: z.string().max(100),
@@ -290,12 +296,12 @@ const UpdateAudienceDto = z
290296
})
291297
.partial()
292298
const VariableValidationEntity = z.object({
293-
schemaType: z.object({}).partial(),
294-
enumValues: z.object({}).partial().optional(),
299+
schemaType: z.string(),
300+
enumValues: z.array(z.string()).optional(),
295301
regexPattern: z.string().optional(),
296302
jsonSchema: z.string().optional(),
297303
description: z.string(),
298-
exampleValue: z.object({}).partial(),
304+
exampleValue: z.any(),
299305
})
300306
const CreateVariableDto = z.object({
301307
name: z.string().max(100).optional(),
@@ -540,7 +546,7 @@ const Target = z.object({
540546
_id: z.string(),
541547
name: z.string().optional(),
542548
audience: TargetAudience,
543-
rollout: Rollout.optional(),
549+
rollout: Rollout.nullable().optional(),
544550
distribution: z.array(TargetDistribution),
545551
})
546552
const FeatureConfig = z.object({
@@ -556,7 +562,7 @@ const FeatureConfig = z.object({
556562
const UpdateTargetDto = z.object({
557563
_id: z.string().optional(),
558564
name: z.string().optional(),
559-
rollout: Rollout.optional(),
565+
rollout: Rollout.nullable().optional(),
560566
distribution: z.array(TargetDistribution),
561567
audience: TargetAudience,
562568
})
@@ -615,6 +621,16 @@ const Feature = z.object({
615621
readonly: z.boolean(),
616622
settings: FeatureSettings.partial().optional(),
617623
sdkVisibility: FeatureSDKVisibility.optional(),
624+
staleness: z
625+
.object({
626+
stale: z.boolean(),
627+
updatedAt: z.string().datetime().optional(),
628+
disabled: z.boolean().optional(),
629+
snoozedUntil: z.string().datetime().optional(),
630+
reason: z.string().optional(),
631+
metaData: z.record(z.string(), z.unknown()).optional(),
632+
})
633+
.optional(),
618634
})
619635
const FeatureDataPoint = z.object({
620636
values: z.object({}).partial(),

src/commands/generate/types.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { OrganizationMember, fetchOrganizationMembers } from '../../api/members'
77
import { upperCase } from 'lodash'
88
import { createHash } from 'crypto'
99
import path from 'path'
10-
import { fetchAllCompletedOrArchivedFeatures } from '../../api/features'
10+
import {
11+
fetchAllCompletedOrArchivedFeatures,
12+
fetchFeatures,
13+
} from '../../api/features'
1114
import { fetchCustomProperties } from '../../api/customProperties'
1215

1316
const reactImports = (oldRepos: boolean, strictCustomData: boolean) => {
@@ -176,10 +179,23 @@ export default class GenerateTypes extends Base {
176179
)
177180
}
178181

179-
this.features = await fetchAllCompletedOrArchivedFeatures(
180-
this.authToken,
181-
this.projectKey,
182-
)
182+
if (this.project.settings?.staleness?.enabled) {
183+
const [completedFeatures, staleFeatures] = await Promise.all([
184+
fetchAllCompletedOrArchivedFeatures(
185+
this.authToken,
186+
this.projectKey,
187+
),
188+
fetchFeatures(this.authToken, this.projectKey, {
189+
staleness: 'all',
190+
}),
191+
])
192+
this.features = [...completedFeatures, ...staleFeatures]
193+
} else {
194+
this.features = await fetchAllCompletedOrArchivedFeatures(
195+
this.authToken,
196+
this.projectKey,
197+
)
198+
}
183199

184200
const variables = await fetchAllVariables(
185201
this.authToken,
@@ -294,19 +310,33 @@ export default class GenerateTypes extends Base {
294310
const createdDate = variable.createdAt.split('T')[0]
295311

296312
const deprecationInfo = isVariableDeprecated(variable, this.features)
313+
297314
const isDeprecated =
298315
this.includeDeprecationWarnings && deprecationInfo.deprecated
299316
const deprecationWarning = isDeprecated
300317
? `@deprecated This variable is part of ${deprecationInfo.feature?.status} feature "${deprecationInfo.feature?.name}" and should be cleaned up.\n`
301318
: ''
302319

320+
let staleWarning = ''
321+
if (this.project.settings?.staleness?.enabled) {
322+
const staleInfo = isVariableStale(variable, this.features)
323+
const recommendedValue = getRecommendedValueForStale(
324+
variable,
325+
staleInfo.feature as Feature,
326+
)
327+
staleWarning = staleInfo.stale
328+
? `@stale This variable is part of "${staleInfo.feature?.name}" feature with stale reason: ${staleInfo.feature?.staleness?.reason}. ${recommendedValue ? `Recommended value to set it to: ${recommendedValue}` : ''}\n`
329+
: ''
330+
}
331+
303332
return blockComment(
304333
descriptionText,
305334
creator,
306335
createdDate,
307336
indent,
308337
!this.obfuscate ? variable.key : undefined,
309338
deprecationWarning,
339+
staleWarning,
310340
)
311341
}
312342

@@ -385,6 +415,7 @@ export const blockComment = (
385415
indent: boolean,
386416
key?: string,
387417
deprecationWarning?: string,
418+
staleWarning?: string,
388419
) => {
389420
const indentString = indent ? ' ' : ''
390421
return (
@@ -399,6 +430,7 @@ export const blockComment = (
399430
(deprecationWarning
400431
? `${indentString} * ${deprecationWarning}\n`
401432
: '') +
433+
(staleWarning ? `${indentString} * ${staleWarning}\n` : '') +
402434
indentString +
403435
'*/'
404436
)
@@ -430,6 +462,39 @@ function isVariableDeprecated(variable: Variable, features: Feature[]) {
430462
return { deprecated: feature && feature.status !== 'active', feature }
431463
}
432464

465+
function isVariableStale(variable: Variable, features: Feature[]) {
466+
if (!variable._feature || variable.persistent) return { stale: false }
467+
const feature = features.find((f) => f._id === variable._feature)
468+
return { stale: feature && feature.staleness, feature }
469+
}
470+
471+
function getRecommendedValueForStale(variable: Variable, feature: Feature) {
472+
if (!feature) {
473+
return variable.defaultValue
474+
}
475+
const reason = feature.staleness?.reason
476+
if (reason === 'unused') {
477+
return variable.defaultValue
478+
} else if (reason === 'released') {
479+
if (feature.staleness?.metaData?.releaseVariation) {
480+
const stalenessMetaData = feature.staleness?.metaData
481+
?.releaseVariation as {
482+
_variation: string
483+
variationKey: string
484+
variationName: string
485+
}
486+
const releaseVariation = feature.variations?.find(
487+
(v) => v._id === stalenessMetaData._variation,
488+
)
489+
return (
490+
releaseVariation?.variables?.[variable.key] ||
491+
variable.defaultValue
492+
)
493+
}
494+
}
495+
return variable.defaultValue
496+
}
497+
433498
const generateCustomDataType = (
434499
customProperties: CustomProperty[],
435500
strict: boolean,

0 commit comments

Comments
 (0)