featuredrop 1.1.0 → 1.3.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.
Files changed (84) hide show
  1. package/README.md +547 -4
  2. package/dist/admin.cjs +212 -0
  3. package/dist/admin.cjs.map +1 -0
  4. package/dist/admin.d.cts +176 -0
  5. package/dist/admin.d.ts +176 -0
  6. package/dist/admin.js +207 -0
  7. package/dist/admin.js.map +1 -0
  8. package/dist/angular.cjs +296 -0
  9. package/dist/angular.cjs.map +1 -0
  10. package/dist/angular.d.cts +233 -0
  11. package/dist/angular.d.ts +233 -0
  12. package/dist/angular.js +293 -0
  13. package/dist/angular.js.map +1 -0
  14. package/dist/bridges.cjs +401 -0
  15. package/dist/bridges.cjs.map +1 -0
  16. package/dist/bridges.d.cts +194 -0
  17. package/dist/bridges.d.ts +194 -0
  18. package/dist/bridges.js +394 -0
  19. package/dist/bridges.js.map +1 -0
  20. package/dist/ci.cjs +328 -0
  21. package/dist/ci.cjs.map +1 -0
  22. package/dist/ci.d.cts +176 -0
  23. package/dist/ci.d.ts +176 -0
  24. package/dist/ci.js +324 -0
  25. package/dist/ci.js.map +1 -0
  26. package/dist/featuredrop.cjs +1377 -0
  27. package/dist/featuredrop.cjs.map +1 -0
  28. package/dist/flags.cjs +51 -0
  29. package/dist/flags.cjs.map +1 -0
  30. package/dist/flags.d.cts +48 -0
  31. package/dist/flags.d.ts +48 -0
  32. package/dist/flags.js +47 -0
  33. package/dist/flags.js.map +1 -0
  34. package/dist/index.cjs +4734 -70
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +1516 -9
  37. package/dist/index.d.ts +1516 -9
  38. package/dist/index.js +4660 -71
  39. package/dist/index.js.map +1 -1
  40. package/dist/preact.cjs +7790 -0
  41. package/dist/preact.cjs.map +1 -0
  42. package/dist/preact.d.cts +1213 -0
  43. package/dist/preact.d.ts +1213 -0
  44. package/dist/preact.js +7760 -0
  45. package/dist/preact.js.map +1 -0
  46. package/dist/react.cjs +6678 -159
  47. package/dist/react.cjs.map +1 -1
  48. package/dist/react.d.cts +852 -112
  49. package/dist/react.d.ts +852 -112
  50. package/dist/react.js +6657 -156
  51. package/dist/react.js.map +1 -1
  52. package/dist/schema.cjs +292 -0
  53. package/dist/schema.cjs.map +1 -0
  54. package/dist/schema.d.cts +345 -0
  55. package/dist/schema.d.ts +345 -0
  56. package/dist/schema.js +286 -0
  57. package/dist/schema.js.map +1 -0
  58. package/dist/solid.cjs +383 -0
  59. package/dist/solid.cjs.map +1 -0
  60. package/dist/solid.d.cts +246 -0
  61. package/dist/solid.d.ts +246 -0
  62. package/dist/solid.js +376 -0
  63. package/dist/solid.js.map +1 -0
  64. package/dist/svelte.cjs +339 -0
  65. package/dist/svelte.cjs.map +1 -0
  66. package/dist/svelte.js +334 -0
  67. package/dist/svelte.js.map +1 -0
  68. package/dist/testing.cjs +1543 -0
  69. package/dist/testing.cjs.map +1 -0
  70. package/dist/testing.d.cts +361 -0
  71. package/dist/testing.d.ts +361 -0
  72. package/dist/testing.js +1536 -0
  73. package/dist/testing.js.map +1 -0
  74. package/dist/vue.cjs +1094 -0
  75. package/dist/vue.cjs.map +1 -0
  76. package/dist/vue.js +1082 -0
  77. package/dist/vue.js.map +1 -0
  78. package/dist/web-components.cjs +493 -0
  79. package/dist/web-components.cjs.map +1 -0
  80. package/dist/web-components.d.cts +215 -0
  81. package/dist/web-components.d.ts +215 -0
  82. package/dist/web-components.js +487 -0
  83. package/dist/web-components.js.map +1 -0
  84. package/package.json +184 -3
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/dependencies.ts
6
+ function getDirectDependencies(feature) {
7
+ const dependsOn = feature.dependsOn;
8
+ if (!dependsOn) return [];
9
+ const seen = dependsOn.seen ?? [];
10
+ const clicked = dependsOn.clicked ?? [];
11
+ const dismissed = dependsOn.dismissed ?? [];
12
+ const unique = /* @__PURE__ */ new Set();
13
+ for (const id of [...seen, ...clicked, ...dismissed]) {
14
+ if (id) unique.add(id);
15
+ }
16
+ return Array.from(unique);
17
+ }
18
+ function hasDependencyCycle(manifest) {
19
+ const ids = new Set(manifest.map((feature) => feature.id));
20
+ const outgoing = /* @__PURE__ */ new Map();
21
+ const indegree = /* @__PURE__ */ new Map();
22
+ for (const feature of manifest) {
23
+ outgoing.set(feature.id, /* @__PURE__ */ new Set());
24
+ indegree.set(feature.id, 0);
25
+ }
26
+ for (const feature of manifest) {
27
+ for (const dependencyId of getDirectDependencies(feature)) {
28
+ if (!ids.has(dependencyId)) continue;
29
+ const edges = outgoing.get(dependencyId);
30
+ if (!edges || edges.has(feature.id)) continue;
31
+ edges.add(feature.id);
32
+ indegree.set(feature.id, (indegree.get(feature.id) ?? 0) + 1);
33
+ }
34
+ }
35
+ const queue = [];
36
+ for (const feature of manifest) {
37
+ if ((indegree.get(feature.id) ?? 0) === 0) queue.push(feature.id);
38
+ }
39
+ let visited = 0;
40
+ while (queue.length > 0) {
41
+ const id = queue.shift();
42
+ if (!id) continue;
43
+ visited += 1;
44
+ const edges = outgoing.get(id);
45
+ if (!edges) continue;
46
+ for (const nextId of edges) {
47
+ const nextDegree = (indegree.get(nextId) ?? 0) - 1;
48
+ indegree.set(nextId, nextDegree);
49
+ if (nextDegree === 0) queue.push(nextId);
50
+ }
51
+ }
52
+ return visited !== manifest.length;
53
+ }
54
+ var featureEntryJsonSchema = {
55
+ type: "object",
56
+ required: ["id", "label", "releasedAt", "showNewUntil"],
57
+ properties: {
58
+ id: { type: "string" },
59
+ label: { type: "string" },
60
+ description: { type: "string" },
61
+ releasedAt: { type: "string", format: "date-time" },
62
+ showNewUntil: { type: "string", format: "date-time" },
63
+ flagKey: { type: "string" },
64
+ product: { type: "string" },
65
+ url: { type: "string" },
66
+ image: { type: "string" },
67
+ type: { enum: ["feature", "improvement", "fix", "breaking"] },
68
+ priority: { enum: ["critical", "normal", "low"] },
69
+ cta: {
70
+ type: "object",
71
+ properties: {
72
+ label: { type: "string" },
73
+ url: { type: "string" }
74
+ }
75
+ },
76
+ meta: { type: "object" }
77
+ }
78
+ };
79
+ var featureManifestJsonSchema = {
80
+ type: "array",
81
+ items: featureEntryJsonSchema
82
+ };
83
+ function isRecord(value) {
84
+ return !!value && typeof value === "object" && !Array.isArray(value);
85
+ }
86
+ function isValidDate(value) {
87
+ return Number.isFinite(new Date(value).getTime());
88
+ }
89
+ var nonEmptyString = zod.z.string().trim().min(1, "must be a non-empty string");
90
+ var isoDateString = nonEmptyString.refine(isValidDate, {
91
+ message: "must be a valid date",
92
+ params: { featuredropCode: "invalid_date" }
93
+ });
94
+ var dependsOnSchema = zod.z.object({
95
+ seen: zod.z.array(zod.z.string()).optional(),
96
+ clicked: zod.z.array(zod.z.string()).optional(),
97
+ dismissed: zod.z.array(zod.z.string()).optional()
98
+ }).optional();
99
+ var ctaSchema = zod.z.object({
100
+ label: nonEmptyString,
101
+ url: nonEmptyString
102
+ }).optional();
103
+ var featureEntrySchema = zod.z.object({
104
+ id: nonEmptyString,
105
+ label: nonEmptyString,
106
+ releasedAt: isoDateString,
107
+ showNewUntil: isoDateString,
108
+ description: zod.z.string().optional(),
109
+ flagKey: zod.z.string().optional(),
110
+ product: zod.z.string().optional(),
111
+ url: zod.z.string().optional(),
112
+ image: zod.z.string().optional(),
113
+ type: zod.z.enum(["feature", "improvement", "fix", "breaking"]).optional(),
114
+ priority: zod.z.enum(["critical", "normal", "low"]).optional(),
115
+ cta: ctaSchema,
116
+ meta: zod.z.record(zod.z.unknown()).optional(),
117
+ dependsOn: dependsOnSchema
118
+ }).passthrough();
119
+ var featureManifestSchema = zod.z.array(featureEntrySchema);
120
+ function toIssuePath(path) {
121
+ if (path.length === 0) return "$";
122
+ let output = "";
123
+ for (const part of path) {
124
+ if (typeof part === "number") output += `[${part}]`;
125
+ else output += output ? `.${part}` : part;
126
+ }
127
+ return output;
128
+ }
129
+ function mapZodIssue(issue) {
130
+ const codeParam = issue.params?.featuredropCode;
131
+ if (codeParam === "invalid_date") {
132
+ return {
133
+ path: toIssuePath(issue.path),
134
+ message: issue.message,
135
+ code: "invalid_date"
136
+ };
137
+ }
138
+ if (issue.code === "invalid_type") {
139
+ return {
140
+ path: toIssuePath(issue.path),
141
+ message: issue.message,
142
+ code: issue.received === "undefined" ? "missing_required" : "invalid_type"
143
+ };
144
+ }
145
+ return {
146
+ path: toIssuePath(issue.path),
147
+ message: issue.message,
148
+ code: "invalid_value"
149
+ };
150
+ }
151
+ var UNSAFE_META_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
152
+ function isSafeUrl(value) {
153
+ const normalized = value.trim();
154
+ if (!normalized) return false;
155
+ if (/^(\/|\.\/|\.\.\/|\?|#)/.test(normalized)) return true;
156
+ if (/^https?:\/\//i.test(normalized)) return true;
157
+ return false;
158
+ }
159
+ function findUnsafeMetaPath(value, path = "meta") {
160
+ if (Array.isArray(value)) {
161
+ for (let index = 0; index < value.length; index++) {
162
+ const nested = findUnsafeMetaPath(value[index], `${path}[${index}]`);
163
+ if (nested) return nested;
164
+ }
165
+ return null;
166
+ }
167
+ if (!isRecord(value)) return null;
168
+ for (const [key, nestedValue] of Object.entries(value)) {
169
+ if (UNSAFE_META_KEYS.has(key)) {
170
+ return `${path}.${key}`;
171
+ }
172
+ const nested = findUnsafeMetaPath(nestedValue, `${path}.${key}`);
173
+ if (nested) return nested;
174
+ }
175
+ return null;
176
+ }
177
+ function validateFeatureEntry(raw, index) {
178
+ if (!isRecord(raw)) {
179
+ return {
180
+ issues: [
181
+ {
182
+ path: `[${index}]`,
183
+ message: "Feature entry must be an object",
184
+ code: "invalid_type"
185
+ }
186
+ ]
187
+ };
188
+ }
189
+ const parsed = featureEntrySchema.safeParse(raw);
190
+ if (!parsed.success) {
191
+ return {
192
+ issues: parsed.error.issues.map((issue) => ({
193
+ ...mapZodIssue(issue),
194
+ path: `[${index}]${issue.path.length > 0 ? `.${toIssuePath(issue.path)}` : ""}`
195
+ }))
196
+ };
197
+ }
198
+ return {
199
+ issues: [],
200
+ entry: parsed.data
201
+ };
202
+ }
203
+ function validateManifest(data) {
204
+ const errors = [];
205
+ if (!Array.isArray(data)) {
206
+ return {
207
+ valid: false,
208
+ errors: [
209
+ {
210
+ path: "$",
211
+ message: "Manifest must be an array",
212
+ code: "invalid_type"
213
+ }
214
+ ]
215
+ };
216
+ }
217
+ const entries = [];
218
+ const seenIds = /* @__PURE__ */ new Set();
219
+ data.forEach((item, index) => {
220
+ const result = validateFeatureEntry(item, index);
221
+ errors.push(...result.issues);
222
+ if (!result.entry) return;
223
+ if (seenIds.has(result.entry.id)) {
224
+ errors.push({
225
+ path: `[${index}].id`,
226
+ message: `Duplicate feature id "${result.entry.id}"`,
227
+ code: "duplicate_id"
228
+ });
229
+ return;
230
+ }
231
+ seenIds.add(result.entry.id);
232
+ entries.push(result.entry);
233
+ });
234
+ if (entries.length > 0 && hasDependencyCycle(entries)) {
235
+ errors.push({
236
+ path: "$",
237
+ message: "Circular dependsOn relationship detected",
238
+ code: "circular_dependency"
239
+ });
240
+ }
241
+ for (let index = 0; index < entries.length; index++) {
242
+ const entry = entries[index];
243
+ if (new Date(entry.showNewUntil).getTime() <= new Date(entry.releasedAt).getTime()) {
244
+ errors.push({
245
+ path: `[${index}].showNewUntil`,
246
+ message: "showNewUntil must be after releasedAt",
247
+ code: "invalid_value"
248
+ });
249
+ }
250
+ if (entry.url && !isSafeUrl(entry.url)) {
251
+ errors.push({
252
+ path: `[${index}].url`,
253
+ message: "url must be http, https, or relative",
254
+ code: "invalid_value"
255
+ });
256
+ }
257
+ if (entry.image && !isSafeUrl(entry.image)) {
258
+ errors.push({
259
+ path: `[${index}].image`,
260
+ message: "image must be http, https, or relative",
261
+ code: "invalid_value"
262
+ });
263
+ }
264
+ if (entry.cta?.url && !isSafeUrl(entry.cta.url)) {
265
+ errors.push({
266
+ path: `[${index}].cta.url`,
267
+ message: "cta.url must be http, https, or relative",
268
+ code: "invalid_value"
269
+ });
270
+ }
271
+ const unsafeMetaPath = findUnsafeMetaPath(entry.meta);
272
+ if (unsafeMetaPath) {
273
+ errors.push({
274
+ path: `[${index}].${unsafeMetaPath}`,
275
+ message: `meta contains unsafe key "${unsafeMetaPath.split(".").pop()}"`,
276
+ code: "invalid_value"
277
+ });
278
+ }
279
+ }
280
+ return {
281
+ valid: errors.length === 0,
282
+ errors
283
+ };
284
+ }
285
+
286
+ exports.featureEntryJsonSchema = featureEntryJsonSchema;
287
+ exports.featureEntrySchema = featureEntrySchema;
288
+ exports.featureManifestJsonSchema = featureManifestJsonSchema;
289
+ exports.featureManifestSchema = featureManifestSchema;
290
+ exports.validateManifest = validateManifest;
291
+ //# sourceMappingURL=schema.cjs.map
292
+ //# sourceMappingURL=schema.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dependencies.ts","../src/schema.ts"],"names":["z"],"mappings":"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","file":"schema.cjs","sourcesContent":["import type { FeatureEntry, FeatureManifest } from \"./types\";\n\nfunction getDirectDependencies(feature: FeatureEntry): string[] {\n const dependsOn = feature.dependsOn;\n if (!dependsOn) return [];\n const seen = dependsOn.seen ?? [];\n const clicked = dependsOn.clicked ?? [];\n const dismissed = dependsOn.dismissed ?? [];\n const unique = new Set<string>();\n for (const id of [...seen, ...clicked, ...dismissed]) {\n if (id) unique.add(id);\n }\n return Array.from(unique);\n}\n\nexport function resolveDependencyOrder(manifest: FeatureManifest): string[] {\n const ids = new Set(manifest.map((feature) => feature.id));\n const outgoing = new Map<string, Set<string>>();\n const indegree = new Map<string, number>();\n\n for (const feature of manifest) {\n outgoing.set(feature.id, new Set());\n indegree.set(feature.id, 0);\n }\n\n for (const feature of manifest) {\n for (const dependencyId of getDirectDependencies(feature)) {\n if (!ids.has(dependencyId)) continue;\n const edges = outgoing.get(dependencyId);\n if (!edges || edges.has(feature.id)) continue;\n edges.add(feature.id);\n indegree.set(feature.id, (indegree.get(feature.id) ?? 0) + 1);\n }\n }\n\n const queue: string[] = [];\n for (const feature of manifest) {\n if ((indegree.get(feature.id) ?? 0) === 0) queue.push(feature.id);\n }\n\n const ordered: string[] = [];\n while (queue.length > 0) {\n const id = queue.shift();\n if (!id) continue;\n ordered.push(id);\n const edges = outgoing.get(id);\n if (!edges) continue;\n for (const nextId of edges) {\n const nextDegree = (indegree.get(nextId) ?? 0) - 1;\n indegree.set(nextId, nextDegree);\n if (nextDegree === 0) queue.push(nextId);\n }\n }\n\n // On cycles, append remaining IDs in original order to keep behavior stable.\n if (ordered.length < manifest.length) {\n const included = new Set(ordered);\n for (const feature of manifest) {\n if (included.has(feature.id)) continue;\n ordered.push(feature.id);\n }\n }\n\n return ordered;\n}\n\nexport function hasDependencyCycle(manifest: FeatureManifest): boolean {\n const ids = new Set(manifest.map((feature) => feature.id));\n const outgoing = new Map<string, Set<string>>();\n const indegree = new Map<string, number>();\n\n for (const feature of manifest) {\n outgoing.set(feature.id, new Set());\n indegree.set(feature.id, 0);\n }\n\n for (const feature of manifest) {\n for (const dependencyId of getDirectDependencies(feature)) {\n if (!ids.has(dependencyId)) continue;\n const edges = outgoing.get(dependencyId);\n if (!edges || edges.has(feature.id)) continue;\n edges.add(feature.id);\n indegree.set(feature.id, (indegree.get(feature.id) ?? 0) + 1);\n }\n }\n\n const queue: string[] = [];\n for (const feature of manifest) {\n if ((indegree.get(feature.id) ?? 0) === 0) queue.push(feature.id);\n }\n\n let visited = 0;\n while (queue.length > 0) {\n const id = queue.shift();\n if (!id) continue;\n visited += 1;\n const edges = outgoing.get(id);\n if (!edges) continue;\n for (const nextId of edges) {\n const nextDegree = (indegree.get(nextId) ?? 0) - 1;\n indegree.set(nextId, nextDegree);\n if (nextDegree === 0) queue.push(nextId);\n }\n }\n\n return visited !== manifest.length;\n}\n\nexport function sortFeaturesByDependencies(features: FeatureEntry[]): FeatureEntry[] {\n if (features.length <= 1) return [...features];\n const order = resolveDependencyOrder(features);\n const rank = new Map(order.map((id, index) => [id, index]));\n return [...features].sort((a, b) => {\n const ra = rank.get(a.id);\n const rb = rank.get(b.id);\n if (ra === undefined || rb === undefined) return 0;\n return ra - rb;\n });\n}\n","import { hasDependencyCycle } from \"./dependencies\";\nimport type { FeatureEntry, FeatureManifest } from \"./types\";\nimport { z } from \"zod\";\n\nexport interface ValidationIssue {\n path: string;\n message: string;\n code:\n | \"invalid_type\"\n | \"missing_required\"\n | \"invalid_value\"\n | \"invalid_date\"\n | \"duplicate_id\"\n | \"circular_dependency\";\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ValidationIssue[];\n}\n\nexport const featureEntryJsonSchema = {\n type: \"object\",\n required: [\"id\", \"label\", \"releasedAt\", \"showNewUntil\"],\n properties: {\n id: { type: \"string\" },\n label: { type: \"string\" },\n description: { type: \"string\" },\n releasedAt: { type: \"string\", format: \"date-time\" },\n showNewUntil: { type: \"string\", format: \"date-time\" },\n flagKey: { type: \"string\" },\n product: { type: \"string\" },\n url: { type: \"string\" },\n image: { type: \"string\" },\n type: { enum: [\"feature\", \"improvement\", \"fix\", \"breaking\"] },\n priority: { enum: [\"critical\", \"normal\", \"low\"] },\n cta: {\n type: \"object\",\n properties: {\n label: { type: \"string\" },\n url: { type: \"string\" },\n },\n },\n meta: { type: \"object\" },\n },\n} as const;\n\nexport const featureManifestJsonSchema = {\n type: \"array\",\n items: featureEntryJsonSchema,\n} as const;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isValidDate(value: string): boolean {\n return Number.isFinite(new Date(value).getTime());\n}\n\nconst nonEmptyString = z.string().trim().min(1, \"must be a non-empty string\");\n\nconst isoDateString = nonEmptyString.refine(isValidDate, {\n message: \"must be a valid date\",\n params: { featuredropCode: \"invalid_date\" },\n});\n\nconst dependsOnSchema = z\n .object({\n seen: z.array(z.string()).optional(),\n clicked: z.array(z.string()).optional(),\n dismissed: z.array(z.string()).optional(),\n })\n .optional();\n\nconst ctaSchema = z\n .object({\n label: nonEmptyString,\n url: nonEmptyString,\n })\n .optional();\n\nexport const featureEntrySchema = z\n .object({\n id: nonEmptyString,\n label: nonEmptyString,\n releasedAt: isoDateString,\n showNewUntil: isoDateString,\n description: z.string().optional(),\n flagKey: z.string().optional(),\n product: z.string().optional(),\n url: z.string().optional(),\n image: z.string().optional(),\n type: z.enum([\"feature\", \"improvement\", \"fix\", \"breaking\"]).optional(),\n priority: z.enum([\"critical\", \"normal\", \"low\"]).optional(),\n cta: ctaSchema,\n meta: z.record(z.unknown()).optional(),\n dependsOn: dependsOnSchema,\n })\n .passthrough();\n\nexport const featureManifestSchema = z.array(featureEntrySchema);\n\nfunction toIssuePath(path: Array<string | number>): string {\n if (path.length === 0) return \"$\";\n let output = \"\";\n for (const part of path) {\n if (typeof part === \"number\") output += `[${part}]`;\n else output += output ? `.${part}` : part;\n }\n return output;\n}\n\nfunction mapZodIssue(issue: z.ZodIssue): ValidationIssue {\n const codeParam = (issue as { params?: Record<string, unknown> }).params?.featuredropCode;\n if (codeParam === \"invalid_date\") {\n return {\n path: toIssuePath(issue.path),\n message: issue.message,\n code: \"invalid_date\",\n };\n }\n if (issue.code === \"invalid_type\") {\n return {\n path: toIssuePath(issue.path),\n message: issue.message,\n code: issue.received === \"undefined\" ? \"missing_required\" : \"invalid_type\",\n };\n }\n return {\n path: toIssuePath(issue.path),\n message: issue.message,\n code: \"invalid_value\",\n };\n}\n\nconst UNSAFE_META_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nfunction isSafeUrl(value: string): boolean {\n const normalized = value.trim();\n if (!normalized) return false;\n if (/^(\\/|\\.\\/|\\.\\.\\/|\\?|#)/.test(normalized)) return true;\n if (/^https?:\\/\\//i.test(normalized)) return true;\n return false;\n}\n\nfunction findUnsafeMetaPath(value: unknown, path = \"meta\"): string | null {\n if (Array.isArray(value)) {\n for (let index = 0; index < value.length; index++) {\n const nested = findUnsafeMetaPath(value[index], `${path}[${index}]`);\n if (nested) return nested;\n }\n return null;\n }\n\n if (!isRecord(value)) return null;\n for (const [key, nestedValue] of Object.entries(value)) {\n if (UNSAFE_META_KEYS.has(key)) {\n return `${path}.${key}`;\n }\n const nested = findUnsafeMetaPath(nestedValue, `${path}.${key}`);\n if (nested) return nested;\n }\n return null;\n}\n\nfunction validateFeatureEntry(raw: unknown, index: number): { entry?: FeatureEntry; issues: ValidationIssue[] } {\n if (!isRecord(raw)) {\n return {\n issues: [\n {\n path: `[${index}]`,\n message: \"Feature entry must be an object\",\n code: \"invalid_type\",\n },\n ],\n };\n }\n\n const parsed = featureEntrySchema.safeParse(raw);\n if (!parsed.success) {\n return {\n issues: parsed.error.issues.map((issue) => ({\n ...mapZodIssue(issue),\n path: `[${index}]${issue.path.length > 0 ? `.${toIssuePath(issue.path)}` : \"\"}`,\n })),\n };\n }\n\n return {\n issues: [],\n entry: parsed.data as FeatureEntry,\n };\n}\n\nexport function validateManifest(data: unknown): ValidationResult {\n const errors: ValidationIssue[] = [];\n if (!Array.isArray(data)) {\n return {\n valid: false,\n errors: [\n {\n path: \"$\",\n message: \"Manifest must be an array\",\n code: \"invalid_type\",\n },\n ],\n };\n }\n\n const entries: FeatureEntry[] = [];\n const seenIds = new Set<string>();\n data.forEach((item, index) => {\n const result = validateFeatureEntry(item, index);\n errors.push(...result.issues);\n if (!result.entry) return;\n if (seenIds.has(result.entry.id)) {\n errors.push({\n path: `[${index}].id`,\n message: `Duplicate feature id \"${result.entry.id}\"`,\n code: \"duplicate_id\",\n });\n return;\n }\n seenIds.add(result.entry.id);\n entries.push(result.entry);\n });\n\n if (entries.length > 0 && hasDependencyCycle(entries as FeatureManifest)) {\n errors.push({\n path: \"$\",\n message: \"Circular dependsOn relationship detected\",\n code: \"circular_dependency\",\n });\n }\n\n for (let index = 0; index < entries.length; index++) {\n const entry = entries[index];\n if (new Date(entry.showNewUntil).getTime() <= new Date(entry.releasedAt).getTime()) {\n errors.push({\n path: `[${index}].showNewUntil`,\n message: \"showNewUntil must be after releasedAt\",\n code: \"invalid_value\",\n });\n }\n\n if (entry.url && !isSafeUrl(entry.url)) {\n errors.push({\n path: `[${index}].url`,\n message: \"url must be http, https, or relative\",\n code: \"invalid_value\",\n });\n }\n\n if (entry.image && !isSafeUrl(entry.image)) {\n errors.push({\n path: `[${index}].image`,\n message: \"image must be http, https, or relative\",\n code: \"invalid_value\",\n });\n }\n\n if (entry.cta?.url && !isSafeUrl(entry.cta.url)) {\n errors.push({\n path: `[${index}].cta.url`,\n message: \"cta.url must be http, https, or relative\",\n code: \"invalid_value\",\n });\n }\n\n const unsafeMetaPath = findUnsafeMetaPath(entry.meta);\n if (unsafeMetaPath) {\n errors.push({\n path: `[${index}].${unsafeMetaPath}`,\n message: `meta contains unsafe key \"${unsafeMetaPath.split(\".\").pop()}\"`,\n code: \"invalid_value\",\n });\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n"]}
@@ -0,0 +1,345 @@
1
+ import { z } from 'zod';
2
+
3
+ interface ValidationIssue {
4
+ path: string;
5
+ message: string;
6
+ code: "invalid_type" | "missing_required" | "invalid_value" | "invalid_date" | "duplicate_id" | "circular_dependency";
7
+ }
8
+ interface ValidationResult {
9
+ valid: boolean;
10
+ errors: ValidationIssue[];
11
+ }
12
+ declare const featureEntryJsonSchema: {
13
+ readonly type: "object";
14
+ readonly required: readonly ["id", "label", "releasedAt", "showNewUntil"];
15
+ readonly properties: {
16
+ readonly id: {
17
+ readonly type: "string";
18
+ };
19
+ readonly label: {
20
+ readonly type: "string";
21
+ };
22
+ readonly description: {
23
+ readonly type: "string";
24
+ };
25
+ readonly releasedAt: {
26
+ readonly type: "string";
27
+ readonly format: "date-time";
28
+ };
29
+ readonly showNewUntil: {
30
+ readonly type: "string";
31
+ readonly format: "date-time";
32
+ };
33
+ readonly flagKey: {
34
+ readonly type: "string";
35
+ };
36
+ readonly product: {
37
+ readonly type: "string";
38
+ };
39
+ readonly url: {
40
+ readonly type: "string";
41
+ };
42
+ readonly image: {
43
+ readonly type: "string";
44
+ };
45
+ readonly type: {
46
+ readonly enum: readonly ["feature", "improvement", "fix", "breaking"];
47
+ };
48
+ readonly priority: {
49
+ readonly enum: readonly ["critical", "normal", "low"];
50
+ };
51
+ readonly cta: {
52
+ readonly type: "object";
53
+ readonly properties: {
54
+ readonly label: {
55
+ readonly type: "string";
56
+ };
57
+ readonly url: {
58
+ readonly type: "string";
59
+ };
60
+ };
61
+ };
62
+ readonly meta: {
63
+ readonly type: "object";
64
+ };
65
+ };
66
+ };
67
+ declare const featureManifestJsonSchema: {
68
+ readonly type: "array";
69
+ readonly items: {
70
+ readonly type: "object";
71
+ readonly required: readonly ["id", "label", "releasedAt", "showNewUntil"];
72
+ readonly properties: {
73
+ readonly id: {
74
+ readonly type: "string";
75
+ };
76
+ readonly label: {
77
+ readonly type: "string";
78
+ };
79
+ readonly description: {
80
+ readonly type: "string";
81
+ };
82
+ readonly releasedAt: {
83
+ readonly type: "string";
84
+ readonly format: "date-time";
85
+ };
86
+ readonly showNewUntil: {
87
+ readonly type: "string";
88
+ readonly format: "date-time";
89
+ };
90
+ readonly flagKey: {
91
+ readonly type: "string";
92
+ };
93
+ readonly product: {
94
+ readonly type: "string";
95
+ };
96
+ readonly url: {
97
+ readonly type: "string";
98
+ };
99
+ readonly image: {
100
+ readonly type: "string";
101
+ };
102
+ readonly type: {
103
+ readonly enum: readonly ["feature", "improvement", "fix", "breaking"];
104
+ };
105
+ readonly priority: {
106
+ readonly enum: readonly ["critical", "normal", "low"];
107
+ };
108
+ readonly cta: {
109
+ readonly type: "object";
110
+ readonly properties: {
111
+ readonly label: {
112
+ readonly type: "string";
113
+ };
114
+ readonly url: {
115
+ readonly type: "string";
116
+ };
117
+ };
118
+ };
119
+ readonly meta: {
120
+ readonly type: "object";
121
+ };
122
+ };
123
+ };
124
+ };
125
+ declare const featureEntrySchema: z.ZodObject<{
126
+ id: z.ZodString;
127
+ label: z.ZodString;
128
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
129
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
130
+ description: z.ZodOptional<z.ZodString>;
131
+ flagKey: z.ZodOptional<z.ZodString>;
132
+ product: z.ZodOptional<z.ZodString>;
133
+ url: z.ZodOptional<z.ZodString>;
134
+ image: z.ZodOptional<z.ZodString>;
135
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
136
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
137
+ cta: z.ZodOptional<z.ZodObject<{
138
+ label: z.ZodString;
139
+ url: z.ZodString;
140
+ }, "strip", z.ZodTypeAny, {
141
+ label: string;
142
+ url: string;
143
+ }, {
144
+ label: string;
145
+ url: string;
146
+ }>>;
147
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
148
+ dependsOn: z.ZodOptional<z.ZodObject<{
149
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
150
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
151
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
152
+ }, "strip", z.ZodTypeAny, {
153
+ seen?: string[] | undefined;
154
+ clicked?: string[] | undefined;
155
+ dismissed?: string[] | undefined;
156
+ }, {
157
+ seen?: string[] | undefined;
158
+ clicked?: string[] | undefined;
159
+ dismissed?: string[] | undefined;
160
+ }>>;
161
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
162
+ id: z.ZodString;
163
+ label: z.ZodString;
164
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
165
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
166
+ description: z.ZodOptional<z.ZodString>;
167
+ flagKey: z.ZodOptional<z.ZodString>;
168
+ product: z.ZodOptional<z.ZodString>;
169
+ url: z.ZodOptional<z.ZodString>;
170
+ image: z.ZodOptional<z.ZodString>;
171
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
172
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
173
+ cta: z.ZodOptional<z.ZodObject<{
174
+ label: z.ZodString;
175
+ url: z.ZodString;
176
+ }, "strip", z.ZodTypeAny, {
177
+ label: string;
178
+ url: string;
179
+ }, {
180
+ label: string;
181
+ url: string;
182
+ }>>;
183
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
184
+ dependsOn: z.ZodOptional<z.ZodObject<{
185
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
186
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
187
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
188
+ }, "strip", z.ZodTypeAny, {
189
+ seen?: string[] | undefined;
190
+ clicked?: string[] | undefined;
191
+ dismissed?: string[] | undefined;
192
+ }, {
193
+ seen?: string[] | undefined;
194
+ clicked?: string[] | undefined;
195
+ dismissed?: string[] | undefined;
196
+ }>>;
197
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
198
+ id: z.ZodString;
199
+ label: z.ZodString;
200
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
201
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
202
+ description: z.ZodOptional<z.ZodString>;
203
+ flagKey: z.ZodOptional<z.ZodString>;
204
+ product: z.ZodOptional<z.ZodString>;
205
+ url: z.ZodOptional<z.ZodString>;
206
+ image: z.ZodOptional<z.ZodString>;
207
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
208
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
209
+ cta: z.ZodOptional<z.ZodObject<{
210
+ label: z.ZodString;
211
+ url: z.ZodString;
212
+ }, "strip", z.ZodTypeAny, {
213
+ label: string;
214
+ url: string;
215
+ }, {
216
+ label: string;
217
+ url: string;
218
+ }>>;
219
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
220
+ dependsOn: z.ZodOptional<z.ZodObject<{
221
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
222
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
223
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
224
+ }, "strip", z.ZodTypeAny, {
225
+ seen?: string[] | undefined;
226
+ clicked?: string[] | undefined;
227
+ dismissed?: string[] | undefined;
228
+ }, {
229
+ seen?: string[] | undefined;
230
+ clicked?: string[] | undefined;
231
+ dismissed?: string[] | undefined;
232
+ }>>;
233
+ }, z.ZodTypeAny, "passthrough">>;
234
+ declare const featureManifestSchema: z.ZodArray<z.ZodObject<{
235
+ id: z.ZodString;
236
+ label: z.ZodString;
237
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
238
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
239
+ description: z.ZodOptional<z.ZodString>;
240
+ flagKey: z.ZodOptional<z.ZodString>;
241
+ product: z.ZodOptional<z.ZodString>;
242
+ url: z.ZodOptional<z.ZodString>;
243
+ image: z.ZodOptional<z.ZodString>;
244
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
245
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
246
+ cta: z.ZodOptional<z.ZodObject<{
247
+ label: z.ZodString;
248
+ url: z.ZodString;
249
+ }, "strip", z.ZodTypeAny, {
250
+ label: string;
251
+ url: string;
252
+ }, {
253
+ label: string;
254
+ url: string;
255
+ }>>;
256
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
257
+ dependsOn: z.ZodOptional<z.ZodObject<{
258
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
259
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
260
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
261
+ }, "strip", z.ZodTypeAny, {
262
+ seen?: string[] | undefined;
263
+ clicked?: string[] | undefined;
264
+ dismissed?: string[] | undefined;
265
+ }, {
266
+ seen?: string[] | undefined;
267
+ clicked?: string[] | undefined;
268
+ dismissed?: string[] | undefined;
269
+ }>>;
270
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
271
+ id: z.ZodString;
272
+ label: z.ZodString;
273
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
274
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
275
+ description: z.ZodOptional<z.ZodString>;
276
+ flagKey: z.ZodOptional<z.ZodString>;
277
+ product: z.ZodOptional<z.ZodString>;
278
+ url: z.ZodOptional<z.ZodString>;
279
+ image: z.ZodOptional<z.ZodString>;
280
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
281
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
282
+ cta: z.ZodOptional<z.ZodObject<{
283
+ label: z.ZodString;
284
+ url: z.ZodString;
285
+ }, "strip", z.ZodTypeAny, {
286
+ label: string;
287
+ url: string;
288
+ }, {
289
+ label: string;
290
+ url: string;
291
+ }>>;
292
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
293
+ dependsOn: z.ZodOptional<z.ZodObject<{
294
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
295
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
296
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
297
+ }, "strip", z.ZodTypeAny, {
298
+ seen?: string[] | undefined;
299
+ clicked?: string[] | undefined;
300
+ dismissed?: string[] | undefined;
301
+ }, {
302
+ seen?: string[] | undefined;
303
+ clicked?: string[] | undefined;
304
+ dismissed?: string[] | undefined;
305
+ }>>;
306
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
307
+ id: z.ZodString;
308
+ label: z.ZodString;
309
+ releasedAt: z.ZodEffects<z.ZodString, string, string>;
310
+ showNewUntil: z.ZodEffects<z.ZodString, string, string>;
311
+ description: z.ZodOptional<z.ZodString>;
312
+ flagKey: z.ZodOptional<z.ZodString>;
313
+ product: z.ZodOptional<z.ZodString>;
314
+ url: z.ZodOptional<z.ZodString>;
315
+ image: z.ZodOptional<z.ZodString>;
316
+ type: z.ZodOptional<z.ZodEnum<["feature", "improvement", "fix", "breaking"]>>;
317
+ priority: z.ZodOptional<z.ZodEnum<["critical", "normal", "low"]>>;
318
+ cta: z.ZodOptional<z.ZodObject<{
319
+ label: z.ZodString;
320
+ url: z.ZodString;
321
+ }, "strip", z.ZodTypeAny, {
322
+ label: string;
323
+ url: string;
324
+ }, {
325
+ label: string;
326
+ url: string;
327
+ }>>;
328
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
329
+ dependsOn: z.ZodOptional<z.ZodObject<{
330
+ seen: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
331
+ clicked: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
332
+ dismissed: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
333
+ }, "strip", z.ZodTypeAny, {
334
+ seen?: string[] | undefined;
335
+ clicked?: string[] | undefined;
336
+ dismissed?: string[] | undefined;
337
+ }, {
338
+ seen?: string[] | undefined;
339
+ clicked?: string[] | undefined;
340
+ dismissed?: string[] | undefined;
341
+ }>>;
342
+ }, z.ZodTypeAny, "passthrough">>, "many">;
343
+ declare function validateManifest(data: unknown): ValidationResult;
344
+
345
+ export { type ValidationIssue, type ValidationResult, featureEntryJsonSchema, featureEntrySchema, featureManifestJsonSchema, featureManifestSchema, validateManifest };