@littlebearapps/platform-admin-sdk 1.0.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/README.md +112 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +89 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +80 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +65 -0
- package/dist/templates.d.ts +16 -0
- package/dist/templates.js +131 -0
- package/package.json +46 -0
- package/templates/full/migrations/006_pattern_discovery.sql +199 -0
- package/templates/full/migrations/007_notifications_search.sql +127 -0
- package/templates/full/workers/lib/pattern-discovery/ai-prompt.ts +644 -0
- package/templates/full/workers/lib/pattern-discovery/clustering.ts +278 -0
- package/templates/full/workers/lib/pattern-discovery/shadow-evaluation.ts +603 -0
- package/templates/full/workers/lib/pattern-discovery/storage.ts +806 -0
- package/templates/full/workers/lib/pattern-discovery/types.ts +159 -0
- package/templates/full/workers/lib/pattern-discovery/validation.ts +278 -0
- package/templates/full/workers/pattern-discovery.ts +661 -0
- package/templates/full/workers/platform-alert-router.ts +1809 -0
- package/templates/full/workers/platform-notifications.ts +424 -0
- package/templates/full/workers/platform-search.ts +480 -0
- package/templates/full/workers/platform-settings.ts +436 -0
- package/templates/full/wrangler.alert-router.jsonc.hbs +34 -0
- package/templates/full/wrangler.notifications.jsonc.hbs +23 -0
- package/templates/full/wrangler.pattern-discovery.jsonc.hbs +33 -0
- package/templates/full/wrangler.search.jsonc.hbs +16 -0
- package/templates/full/wrangler.settings.jsonc.hbs +23 -0
- package/templates/shared/README.md.hbs +69 -0
- package/templates/shared/config/budgets.yaml.hbs +72 -0
- package/templates/shared/config/services.yaml.hbs +45 -0
- package/templates/shared/migrations/001_core_tables.sql +117 -0
- package/templates/shared/migrations/002_usage_warehouse.sql +830 -0
- package/templates/shared/migrations/003_feature_tracking.sql +250 -0
- package/templates/shared/migrations/004_settings_alerts.sql +452 -0
- package/templates/shared/migrations/seed.sql.hbs +4 -0
- package/templates/shared/package.json.hbs +21 -0
- package/templates/shared/scripts/sync-config.ts +242 -0
- package/templates/shared/tsconfig.json +12 -0
- package/templates/shared/workers/lib/analytics-engine.ts +357 -0
- package/templates/shared/workers/lib/billing.ts +293 -0
- package/templates/shared/workers/lib/circuit-breaker-middleware.ts +25 -0
- package/templates/shared/workers/lib/control.ts +292 -0
- package/templates/shared/workers/lib/economics.ts +368 -0
- package/templates/shared/workers/lib/metrics.ts +103 -0
- package/templates/shared/workers/lib/platform-settings.ts +407 -0
- package/templates/shared/workers/lib/shared/allowances.ts +333 -0
- package/templates/shared/workers/lib/shared/cloudflare.ts +1362 -0
- package/templates/shared/workers/lib/shared/types.ts +58 -0
- package/templates/shared/workers/lib/telemetry-sampling.ts +360 -0
- package/templates/shared/workers/lib/usage/collectors/example.ts +96 -0
- package/templates/shared/workers/lib/usage/collectors/index.ts +128 -0
- package/templates/shared/workers/lib/usage/handlers/audit.ts +306 -0
- package/templates/shared/workers/lib/usage/handlers/backfill.ts +845 -0
- package/templates/shared/workers/lib/usage/handlers/behavioral.ts +429 -0
- package/templates/shared/workers/lib/usage/handlers/data-queries.ts +507 -0
- package/templates/shared/workers/lib/usage/handlers/dlq-admin.ts +364 -0
- package/templates/shared/workers/lib/usage/handlers/health-trends.ts +222 -0
- package/templates/shared/workers/lib/usage/handlers/index.ts +35 -0
- package/templates/shared/workers/lib/usage/handlers/usage-admin.ts +421 -0
- package/templates/shared/workers/lib/usage/handlers/usage-features.ts +1262 -0
- package/templates/shared/workers/lib/usage/handlers/usage-metrics.ts +2420 -0
- package/templates/shared/workers/lib/usage/handlers/usage-settings.ts +610 -0
- package/templates/shared/workers/lib/usage/queue/budget-enforcement.ts +1032 -0
- package/templates/shared/workers/lib/usage/queue/cost-budget-enforcement.ts +128 -0
- package/templates/shared/workers/lib/usage/queue/cost-calculator.ts +77 -0
- package/templates/shared/workers/lib/usage/queue/dlq-handler.ts +161 -0
- package/templates/shared/workers/lib/usage/queue/index.ts +19 -0
- package/templates/shared/workers/lib/usage/queue/telemetry-processor.ts +790 -0
- package/templates/shared/workers/lib/usage/scheduled/anomaly-detection.ts +732 -0
- package/templates/shared/workers/lib/usage/scheduled/data-collection.ts +956 -0
- package/templates/shared/workers/lib/usage/scheduled/error-digest.ts +343 -0
- package/templates/shared/workers/lib/usage/scheduled/index.ts +18 -0
- package/templates/shared/workers/lib/usage/scheduled/rollups.ts +1561 -0
- package/templates/shared/workers/lib/usage/shared/constants.ts +362 -0
- package/templates/shared/workers/lib/usage/shared/index.ts +14 -0
- package/templates/shared/workers/lib/usage/shared/types.ts +1066 -0
- package/templates/shared/workers/lib/usage/shared/utils.ts +795 -0
- package/templates/shared/workers/platform-usage.ts +1915 -0
- package/templates/shared/wrangler.usage.jsonc.hbs +58 -0
- package/templates/standard/migrations/005_error_collection.sql +162 -0
- package/templates/standard/workers/error-collector.ts +2670 -0
- package/templates/standard/workers/lib/error-collector/capture.ts +213 -0
- package/templates/standard/workers/lib/error-collector/digest.ts +448 -0
- package/templates/standard/workers/lib/error-collector/email-health-alerts.ts +262 -0
- package/templates/standard/workers/lib/error-collector/fingerprint.ts +258 -0
- package/templates/standard/workers/lib/error-collector/gap-alerts.ts +293 -0
- package/templates/standard/workers/lib/error-collector/github.ts +329 -0
- package/templates/standard/workers/lib/error-collector/types.ts +262 -0
- package/templates/standard/workers/lib/sentinel/gap-detection.ts +734 -0
- package/templates/standard/workers/lib/shared/slack-alerts.ts +585 -0
- package/templates/standard/workers/platform-sentinel.ts +1744 -0
- package/templates/standard/wrangler.error-collector.jsonc.hbs +44 -0
- package/templates/standard/wrangler.sentinel.jsonc.hbs +45 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Allowance & Licensing Configuration
|
|
3
|
+
*
|
|
4
|
+
* Defines monthly limits for Cloudflare services to track "Usage vs. Included Thresholds".
|
|
5
|
+
* These values represent the Workers Paid plan allowances and free tier limits.
|
|
6
|
+
*
|
|
7
|
+
* @see https://developers.cloudflare.com/workers/platform/pricing/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service type identifiers
|
|
12
|
+
*/
|
|
13
|
+
export type ServiceType =
|
|
14
|
+
| 'workers'
|
|
15
|
+
| 'd1'
|
|
16
|
+
| 'kv'
|
|
17
|
+
| 'r2'
|
|
18
|
+
| 'durableObjects'
|
|
19
|
+
| 'vectorize'
|
|
20
|
+
| 'aiGateway'
|
|
21
|
+
| 'workersAI'
|
|
22
|
+
| 'pages'
|
|
23
|
+
| 'queues';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Utilization status based on percentage thresholds
|
|
27
|
+
*/
|
|
28
|
+
export type UtilizationStatus = 'green' | 'yellow' | 'red';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Service allowance definition
|
|
32
|
+
*/
|
|
33
|
+
export interface ServiceAllowance {
|
|
34
|
+
/** Human-readable service name */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Monthly allowance value (in native units) */
|
|
37
|
+
monthlyLimit: number;
|
|
38
|
+
/** Unit of measurement */
|
|
39
|
+
unit: string;
|
|
40
|
+
/** Whether this is a paid plan limit (vs free tier) */
|
|
41
|
+
isPaidPlan: boolean;
|
|
42
|
+
/** Description of the allowance */
|
|
43
|
+
description: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project-specific allowance overrides
|
|
48
|
+
*/
|
|
49
|
+
export interface ProjectAllowance {
|
|
50
|
+
projectId: string;
|
|
51
|
+
projectName: string;
|
|
52
|
+
/** Primary resource type for this project (e.g., D1 writes for data-heavy projects) */
|
|
53
|
+
primaryResource: ServiceType;
|
|
54
|
+
/** Custom limits that override account-level defaults */
|
|
55
|
+
overrides?: Partial<Record<ServiceType, number>>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Account-level Cloudflare service allowances (Workers Paid Plan)
|
|
60
|
+
*
|
|
61
|
+
* Based on Cloudflare pricing as of January 2026:
|
|
62
|
+
* - Workers Paid: $5/month includes 10M requests
|
|
63
|
+
* - D1: 50M writes/month for paid accounts
|
|
64
|
+
* - R2: 10 GB Class A operations included
|
|
65
|
+
*/
|
|
66
|
+
export const CF_ALLOWANCES: Record<ServiceType, ServiceAllowance> = {
|
|
67
|
+
workers: {
|
|
68
|
+
name: 'Workers Requests',
|
|
69
|
+
monthlyLimit: 10_000_000,
|
|
70
|
+
unit: 'requests',
|
|
71
|
+
isPaidPlan: true,
|
|
72
|
+
description: '10M requests included with Workers Paid ($5/mo)',
|
|
73
|
+
},
|
|
74
|
+
d1: {
|
|
75
|
+
name: 'D1 Writes',
|
|
76
|
+
monthlyLimit: 50_000_000,
|
|
77
|
+
unit: 'rows written',
|
|
78
|
+
isPaidPlan: true,
|
|
79
|
+
description: '25B rows read + 50M rows written per month included',
|
|
80
|
+
},
|
|
81
|
+
kv: {
|
|
82
|
+
name: 'KV Writes',
|
|
83
|
+
monthlyLimit: 1_000_000,
|
|
84
|
+
unit: 'writes',
|
|
85
|
+
isPaidPlan: true,
|
|
86
|
+
description: '10M reads + 1M writes/deletes/lists per month included',
|
|
87
|
+
},
|
|
88
|
+
r2: {
|
|
89
|
+
name: 'R2 Storage',
|
|
90
|
+
monthlyLimit: 10_000_000_000,
|
|
91
|
+
unit: 'bytes',
|
|
92
|
+
isPaidPlan: true,
|
|
93
|
+
description: '10GB storage + 1M Class A + 10M Class B ops per month included',
|
|
94
|
+
},
|
|
95
|
+
durableObjects: {
|
|
96
|
+
name: 'Durable Objects Requests',
|
|
97
|
+
monthlyLimit: 1_000_000,
|
|
98
|
+
unit: 'requests',
|
|
99
|
+
isPaidPlan: true,
|
|
100
|
+
description: '1M requests included in Workers Paid Plan',
|
|
101
|
+
},
|
|
102
|
+
vectorize: {
|
|
103
|
+
name: 'Vectorize Stored Dimensions',
|
|
104
|
+
monthlyLimit: 10_000_000,
|
|
105
|
+
unit: 'dimensions',
|
|
106
|
+
isPaidPlan: true,
|
|
107
|
+
description:
|
|
108
|
+
'10M stored dimensions + 50M queried dimensions per month included in Workers Paid Plan',
|
|
109
|
+
},
|
|
110
|
+
aiGateway: {
|
|
111
|
+
name: 'AI Gateway Requests',
|
|
112
|
+
monthlyLimit: Infinity,
|
|
113
|
+
unit: 'requests',
|
|
114
|
+
isPaidPlan: false,
|
|
115
|
+
description: 'AI Gateway is free (cost is from underlying AI provider)',
|
|
116
|
+
},
|
|
117
|
+
workersAI: {
|
|
118
|
+
name: 'Workers AI Neurons',
|
|
119
|
+
monthlyLimit: 0,
|
|
120
|
+
unit: 'neurons',
|
|
121
|
+
isPaidPlan: true,
|
|
122
|
+
description:
|
|
123
|
+
'Pay-as-you-go: $0.011/1K neurons. No paid plan inclusion (free tier may apply but not tracked).',
|
|
124
|
+
},
|
|
125
|
+
pages: {
|
|
126
|
+
name: 'Pages Builds',
|
|
127
|
+
monthlyLimit: 500,
|
|
128
|
+
unit: 'builds',
|
|
129
|
+
isPaidPlan: false,
|
|
130
|
+
description: '500 builds per month in free tier',
|
|
131
|
+
},
|
|
132
|
+
queues: {
|
|
133
|
+
name: 'Queues Messages',
|
|
134
|
+
monthlyLimit: 1_000_000,
|
|
135
|
+
unit: 'messages',
|
|
136
|
+
isPaidPlan: false,
|
|
137
|
+
description: '1M messages per month in free tier',
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Daily limits (derived from monthly for rate limiting purposes)
|
|
143
|
+
*/
|
|
144
|
+
export const CF_DAILY_LIMITS: Record<ServiceType, number> = {
|
|
145
|
+
workers: Math.floor(CF_ALLOWANCES.workers.monthlyLimit / 30),
|
|
146
|
+
d1: Math.floor(CF_ALLOWANCES.d1.monthlyLimit / 30),
|
|
147
|
+
kv: Math.floor(CF_ALLOWANCES.kv.monthlyLimit / 30),
|
|
148
|
+
r2: Math.floor(CF_ALLOWANCES.r2.monthlyLimit / 30),
|
|
149
|
+
durableObjects: Math.floor(CF_ALLOWANCES.durableObjects.monthlyLimit / 30),
|
|
150
|
+
vectorize: 0,
|
|
151
|
+
aiGateway: Infinity,
|
|
152
|
+
workersAI: 0,
|
|
153
|
+
pages: Math.floor(CF_ALLOWANCES.pages.monthlyLimit / 30),
|
|
154
|
+
queues: Math.floor(CF_ALLOWANCES.queues.monthlyLimit / 30),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Project-specific configurations with primary resource assignments.
|
|
159
|
+
*
|
|
160
|
+
* TODO: Customise these for your projects. Each project should declare
|
|
161
|
+
* which Cloudflare resource type is its "primary" resource for utilization tracking.
|
|
162
|
+
*/
|
|
163
|
+
export const PROJECT_ALLOWANCES: ProjectAllowance[] = [
|
|
164
|
+
// Example: a data-heavy project using D1 as primary resource
|
|
165
|
+
// {
|
|
166
|
+
// projectId: 'my-project',
|
|
167
|
+
// projectName: 'My Project',
|
|
168
|
+
// primaryResource: 'd1',
|
|
169
|
+
// },
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Utilization threshold percentages for traffic light status
|
|
174
|
+
*/
|
|
175
|
+
export const UTILIZATION_THRESHOLDS = {
|
|
176
|
+
/** Green zone: < 70% utilization */
|
|
177
|
+
green: 70,
|
|
178
|
+
/** Yellow zone: 70-90% utilization */
|
|
179
|
+
yellow: 90,
|
|
180
|
+
/** Red zone: > 90% utilization (or overage) */
|
|
181
|
+
red: 100,
|
|
182
|
+
} as const;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Simplified allowances for worker usage (limit + unit only).
|
|
186
|
+
* Used by platform-usage worker for utilization calculations.
|
|
187
|
+
*/
|
|
188
|
+
export type SimpleAllowanceType =
|
|
189
|
+
| 'workers'
|
|
190
|
+
| 'd1'
|
|
191
|
+
| 'kv'
|
|
192
|
+
| 'r2'
|
|
193
|
+
| 'vectorize'
|
|
194
|
+
| 'workersAI'
|
|
195
|
+
| 'durableObjects'
|
|
196
|
+
| 'queues';
|
|
197
|
+
|
|
198
|
+
export const CF_SIMPLE_ALLOWANCES: Record<SimpleAllowanceType, { limit: number; unit: string }> = {
|
|
199
|
+
workers: { limit: CF_ALLOWANCES.workers.monthlyLimit, unit: CF_ALLOWANCES.workers.unit },
|
|
200
|
+
d1: { limit: CF_ALLOWANCES.d1.monthlyLimit, unit: CF_ALLOWANCES.d1.unit },
|
|
201
|
+
kv: { limit: CF_ALLOWANCES.kv.monthlyLimit, unit: CF_ALLOWANCES.kv.unit },
|
|
202
|
+
r2: { limit: CF_ALLOWANCES.r2.monthlyLimit, unit: CF_ALLOWANCES.r2.unit },
|
|
203
|
+
vectorize: { limit: CF_ALLOWANCES.vectorize.monthlyLimit, unit: CF_ALLOWANCES.vectorize.unit },
|
|
204
|
+
workersAI: { limit: CF_ALLOWANCES.workersAI.monthlyLimit, unit: CF_ALLOWANCES.workersAI.unit },
|
|
205
|
+
durableObjects: {
|
|
206
|
+
limit: CF_ALLOWANCES.durableObjects.monthlyLimit,
|
|
207
|
+
unit: CF_ALLOWANCES.durableObjects.unit,
|
|
208
|
+
},
|
|
209
|
+
queues: {
|
|
210
|
+
limit: CF_ALLOWANCES.queues.monthlyLimit,
|
|
211
|
+
unit: CF_ALLOWANCES.queues.unit,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// =============================================================================
|
|
216
|
+
// GITHUB ALLOWANCES & PRICING
|
|
217
|
+
// =============================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* GitHub plan allowances
|
|
221
|
+
*
|
|
222
|
+
* @see https://docs.github.com/en/billing/managing-billing-for-your-products/managing-billing-for-github-actions/about-billing-for-github-actions
|
|
223
|
+
*/
|
|
224
|
+
export const GITHUB_ALLOWANCES = {
|
|
225
|
+
free: {
|
|
226
|
+
actionsMinutes: 2000,
|
|
227
|
+
actionsStorageGb: 0.5,
|
|
228
|
+
packagesStorageGb: 0.5,
|
|
229
|
+
packagesBandwidthGb: 1,
|
|
230
|
+
lfsStorageGb: 1,
|
|
231
|
+
lfsBandwidthGb: 1,
|
|
232
|
+
},
|
|
233
|
+
pro: {
|
|
234
|
+
actionsMinutes: 3000,
|
|
235
|
+
actionsStorageGb: 2,
|
|
236
|
+
packagesStorageGb: 2,
|
|
237
|
+
packagesBandwidthGb: 10,
|
|
238
|
+
lfsStorageGb: 1,
|
|
239
|
+
lfsBandwidthGb: 1,
|
|
240
|
+
},
|
|
241
|
+
team: {
|
|
242
|
+
actionsMinutes: 3000,
|
|
243
|
+
actionsStorageGb: 2,
|
|
244
|
+
packagesStorageGb: 2,
|
|
245
|
+
packagesBandwidthGb: 10,
|
|
246
|
+
lfsStorageGb: 1,
|
|
247
|
+
lfsBandwidthGb: 1,
|
|
248
|
+
},
|
|
249
|
+
enterprise: {
|
|
250
|
+
actionsMinutes: 50000,
|
|
251
|
+
actionsStorageGb: 50,
|
|
252
|
+
packagesStorageGb: 50,
|
|
253
|
+
packagesBandwidthGb: 100,
|
|
254
|
+
lfsStorageGb: 250,
|
|
255
|
+
lfsBandwidthGb: 250,
|
|
256
|
+
},
|
|
257
|
+
} as const;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* GitHub pricing for overages and paid features (USD per unit)
|
|
261
|
+
*/
|
|
262
|
+
export const GITHUB_PRICING = {
|
|
263
|
+
actions: {
|
|
264
|
+
linux: 0.006,
|
|
265
|
+
macos: 0.062,
|
|
266
|
+
windows: 0.01,
|
|
267
|
+
linuxLarge: 0.012,
|
|
268
|
+
linuxXLarge: 0.024,
|
|
269
|
+
gpuLinux: 0.07,
|
|
270
|
+
},
|
|
271
|
+
actionsStorageGb: 0.25,
|
|
272
|
+
packagesStorageGb: 0.25,
|
|
273
|
+
packagesBandwidthGb: 0.50,
|
|
274
|
+
lfsStorageGb: 0.07,
|
|
275
|
+
lfsBandwidthGb: 0.007,
|
|
276
|
+
ghecPerUser: 21,
|
|
277
|
+
ghasCodeSecurity: 49,
|
|
278
|
+
ghasSecretProtection: 31,
|
|
279
|
+
copilotBusiness: 19,
|
|
280
|
+
copilotEnterprise: 39,
|
|
281
|
+
} as const;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get GitHub plan allowances by plan name
|
|
285
|
+
*/
|
|
286
|
+
export function getGitHubPlanAllowances(
|
|
287
|
+
planName: string
|
|
288
|
+
): (typeof GITHUB_ALLOWANCES)[keyof typeof GITHUB_ALLOWANCES] {
|
|
289
|
+
const lowerPlan = planName.toLowerCase();
|
|
290
|
+
if (lowerPlan.includes('enterprise')) return GITHUB_ALLOWANCES.enterprise;
|
|
291
|
+
if (lowerPlan.includes('team')) return GITHUB_ALLOWANCES.team;
|
|
292
|
+
if (lowerPlan.includes('pro')) return GITHUB_ALLOWANCES.pro;
|
|
293
|
+
return GITHUB_ALLOWANCES.free;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Calculate utilization percentage
|
|
298
|
+
*/
|
|
299
|
+
export function calculateUtilizationPct(current: number, limit: number): number {
|
|
300
|
+
if (limit === Infinity || limit === 0) return 0;
|
|
301
|
+
return Math.min((current / limit) * 100, 999);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Get utilization status (traffic light) based on percentage
|
|
306
|
+
*/
|
|
307
|
+
export function getUtilizationStatus(percentage: number): UtilizationStatus {
|
|
308
|
+
if (percentage < UTILIZATION_THRESHOLDS.green) return 'green';
|
|
309
|
+
if (percentage < UTILIZATION_THRESHOLDS.yellow) return 'yellow';
|
|
310
|
+
return 'red';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get the effective limit for a project/service combination
|
|
315
|
+
*/
|
|
316
|
+
export function getEffectiveLimit(_projectId: string, serviceType: ServiceType): number {
|
|
317
|
+
return CF_ALLOWANCES[serviceType].monthlyLimit;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get the list of tracked project IDs
|
|
322
|
+
*/
|
|
323
|
+
export function getTrackedProjectIds(): string[] {
|
|
324
|
+
return PROJECT_ALLOWANCES.map((p) => p.projectId);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get project display name by ID
|
|
329
|
+
*/
|
|
330
|
+
export function getProjectDisplayName(projectId: string): string {
|
|
331
|
+
const project = PROJECT_ALLOWANCES.find((p) => p.projectId === projectId);
|
|
332
|
+
return project?.projectName ?? projectId;
|
|
333
|
+
}
|