@fgv/ts-res-browser 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.
Files changed (95) hide show
  1. package/.rush/temp/5fc90bc7c2ccf812114ea099111568e5429be2a2.tar.log +15 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-browser.build.chunks.jsonl +35 -0
  3. package/.rush/temp/operation/build/all.log +35 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +35 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1217 -0
  7. package/README.md +95 -0
  8. package/TS_RES_BROWSER_PLAN.md +689 -0
  9. package/babel.config.js +17 -0
  10. package/config/jest.config.json +13 -0
  11. package/config/rig.json +16 -0
  12. package/dist/bundle.js +2 -0
  13. package/dist/bundle.js.LICENSE.txt +62 -0
  14. package/dist/index.html +1 -0
  15. package/package.json +78 -0
  16. package/postcss.config.js +3 -0
  17. package/public/index.html +15 -0
  18. package/rush-logs/ts-res-browser.build.cache.log +3 -0
  19. package/rush-logs/ts-res-browser.build.log +35 -0
  20. package/src/App.tsx +153 -0
  21. package/src/cli.ts +44 -0
  22. package/src/components/common/FileImporter.tsx +138 -0
  23. package/src/components/common/NavigationWarningModal.tsx +62 -0
  24. package/src/components/layout/AppLayout.tsx +46 -0
  25. package/src/components/layout/Header.tsx +22 -0
  26. package/src/components/layout/MessagesWindow.tsx +108 -0
  27. package/src/components/layout/Sidebar.tsx +92 -0
  28. package/src/components/tools/CompiledBrowser.tsx +949 -0
  29. package/src/components/tools/ConfigurationTool.tsx +1402 -0
  30. package/src/components/tools/FilterTool.tsx +1027 -0
  31. package/src/components/tools/ImportTool.tsx +246 -0
  32. package/src/components/tools/ResolutionViewer.tsx +1672 -0
  33. package/src/components/tools/SourceBrowser.tsx +496 -0
  34. package/src/hooks/useAppState.ts +107 -0
  35. package/src/hooks/useFileImport.ts +86 -0
  36. package/src/hooks/useNavigationWarning.ts +67 -0
  37. package/src/hooks/useResourceManager.ts +244 -0
  38. package/src/index.css +38 -0
  39. package/src/main.tsx +16 -0
  40. package/src/types/app.ts +62 -0
  41. package/src/types/fileSystemAccess.d.ts +94 -0
  42. package/src/utils/browserFileTreeAccessors.ts +245 -0
  43. package/src/utils/fileImport.ts +334 -0
  44. package/src/utils/filterResources.ts +677 -0
  45. package/src/utils/tsResIntegration.ts +467 -0
  46. package/tailwind.config.js +21 -0
  47. package/test-data/README.md +184 -0
  48. package/test-data/config-variations/README.md +64 -0
  49. package/test-data/config-variations/composition-terminology.json +112 -0
  50. package/test-data/config-variations/enterprise-complex.json +129 -0
  51. package/test-data/config-variations/gaming-app.json +87 -0
  52. package/test-data/config-variations/high-priority-language.json +65 -0
  53. package/test-data/config-variations/minimal-basic.json +37 -0
  54. package/test-data/config-variations/qualifier-default-values.json +71 -0
  55. package/test-data/config-variations/territory-first.json +68 -0
  56. package/test-data/config-variations/territory-hierarchy-test.json +31 -0
  57. package/test-data/config-variations/territory-hierarchy-unconstrained.json +32 -0
  58. package/test-data/default-matching-test/greeting.res.json +83 -0
  59. package/test-data/default-matching-test/only-defaults.res.json +48 -0
  60. package/test-data/sample-project/package.json +27 -0
  61. package/test-data/sample-project/src/resources/app-config.json +27 -0
  62. package/test-data/sample-project/src/resources/app.json +177 -0
  63. package/test-data/sample-project/src/resources/config/app.env=development.json +21 -0
  64. package/test-data/sample-project/src/resources/config/app.json +21 -0
  65. package/test-data/sample-project/src/resources/config/app.platform=mobile.json +23 -0
  66. package/test-data/sample-project/src/resources/feature-flags.json +23 -0
  67. package/test-data/sample-project/src/resources/home=CA/common.json +16 -0
  68. package/test-data/sample-project/src/resources/images/density=hdpi.json +29 -0
  69. package/test-data/sample-project/src/resources/images/home.json +29 -0
  70. package/test-data/sample-project/src/resources/images/logout.json +14 -0
  71. package/test-data/sample-project/src/resources/images/settings.json +14 -0
  72. package/test-data/sample-project/src/resources/images/ui-icons.json +29 -0
  73. package/test-data/sample-project/src/resources/images/user.density=hdpi.json +14 -0
  74. package/test-data/sample-project/src/resources/images/user.json +14 -0
  75. package/test-data/sample-project/src/resources/lang=en-US/common.json +15 -0
  76. package/test-data/sample-project/src/resources/language=en-CA/ui-terms.json +19 -0
  77. package/test-data/sample-project/src/resources/language=en-GB/ui-terms.json +30 -0
  78. package/test-data/sample-project/src/resources/language=fr-CA/ui-terms.json +25 -0
  79. package/test-data/sample-project/src/resources/language=nl-BE/ui-terms.json +17 -0
  80. package/test-data/sample-project/src/resources/region=europe/feature-flags.json +17 -0
  81. package/test-data/sample-project/src/resources/region=north-america/feature-flags.json +18 -0
  82. package/test-data/sample-project/src/resources/strings/dashboard.home=CA,language=fr.json +20 -0
  83. package/test-data/sample-project/src/resources/strings/dashboard.json +18 -0
  84. package/test-data/sample-project/src/resources/strings/dashboard.role=admin.json +23 -0
  85. package/test-data/sample-project/src/resources/strings/dashboard.role=user.json +21 -0
  86. package/test-data/sample-project/src/resources/strings/language=en-US.json +16 -0
  87. package/test-data/sample-project/src/resources/strings.json +78 -0
  88. package/test-data/sample-project/src/resources/territory=BE/app-config.json +19 -0
  89. package/test-data/sample-project/src/resources/territory=CA/app-config.json +21 -0
  90. package/test-data/sample-project/src/resources/territory=GB/app-config.json +25 -0
  91. package/test-data/sample-project/src/resources/territory=NL/app-config.json +25 -0
  92. package/test-data/sample-project/src/resources/ui-terms.json +96 -0
  93. package/test-data/sample-project/src/resources-config.json +111 -0
  94. package/tsconfig.json +27 -0
  95. package/webpack.config.js +54 -0
@@ -0,0 +1,677 @@
1
+ import { Result, succeed, fail } from '@fgv/ts-utils';
2
+ import { Context, Runtime, Import, Resources } from '@fgv/ts-res';
3
+ import { ProcessedResources, TsResSystem, createTsResSystemFromConfig } from './tsResIntegration';
4
+
5
+ export interface FilterOptions {
6
+ partialContextMatch?: boolean;
7
+ enableDebugLogging?: boolean;
8
+ reduceQualifiers?: boolean;
9
+ }
10
+
11
+ export interface FilteredResource {
12
+ id: string;
13
+ originalCandidateCount: number;
14
+ filteredCandidateCount: number;
15
+ hasWarning: boolean;
16
+ }
17
+
18
+ export interface FilterResult {
19
+ success: boolean;
20
+ filteredResources: FilteredResource[];
21
+ processedResources?: ProcessedResources;
22
+ error?: string;
23
+ warnings: string[];
24
+ }
25
+
26
+ /**
27
+ * Creates a filtered resource manager based on partial context matching
28
+ */
29
+ // Helper function for conditional debug logging
30
+ const debugLog = (enableDebug: boolean, ...args: any[]) => {
31
+ if (enableDebug) {
32
+ console.log(...args);
33
+ }
34
+ };
35
+
36
+ /**
37
+ * Creates a filtered resource manager using the new base library filtering functionality.
38
+ * This is a much simpler implementation that leverages the ResourceManagerBuilder.clone() method.
39
+ */
40
+ export const createFilteredResourceManagerSimple = async (
41
+ originalSystem: TsResSystem,
42
+ partialContext: Record<string, string>,
43
+ options: FilterOptions = { partialContextMatch: true }
44
+ ): Promise<Result<ProcessedResources>> => {
45
+ try {
46
+ const enableDebug = options.enableDebugLogging === true;
47
+
48
+ debugLog(enableDebug, '=== SIMPLE FILTER CREATION ===');
49
+ debugLog(enableDebug, 'Original system:', originalSystem);
50
+ debugLog(enableDebug, 'Partial context:', partialContext);
51
+
52
+ // Validate the original system
53
+ if (!originalSystem?.resourceManager) {
54
+ return fail('Original system or resourceManager is undefined');
55
+ }
56
+
57
+ // Use Result pattern chaining as recommended
58
+ debugLog(enableDebug, 'Validating context and cloning manager:', partialContext);
59
+ const cloneResult = originalSystem.resourceManager
60
+ .validateContext(partialContext)
61
+ .onSuccess((validatedContext) => {
62
+ debugLog(enableDebug, 'Context validated, creating clone with context:', validatedContext);
63
+ return originalSystem.resourceManager.clone({
64
+ filterForContext: validatedContext,
65
+ reduceQualifiers: options.reduceQualifiers
66
+ });
67
+ })
68
+ .onFailure((error) => {
69
+ debugLog(enableDebug, 'Failed to validate context or clone:', error);
70
+ return fail(error);
71
+ });
72
+
73
+ if (cloneResult.isFailure()) {
74
+ return fail(`Failed to create filtered resource manager: ${cloneResult.message}`);
75
+ }
76
+
77
+ const filteredManager = cloneResult.value;
78
+ debugLog(enableDebug, 'Filtered manager created:', filteredManager);
79
+
80
+ // Create new ImportManager for the filtered system
81
+ const newImportManagerResult = Import.ImportManager.create({
82
+ resources: filteredManager
83
+ });
84
+
85
+ if (newImportManagerResult.isFailure()) {
86
+ return fail(`Failed to create filtered import manager: ${newImportManagerResult.message}`);
87
+ }
88
+
89
+ // Create new ContextQualifierProvider for the filtered system
90
+ const newContextQualifierProviderResult = Runtime.ValidatingSimpleContextQualifierProvider.create({
91
+ qualifiers: originalSystem.qualifiers
92
+ });
93
+
94
+ if (newContextQualifierProviderResult.isFailure()) {
95
+ return fail(`Failed to create filtered context provider: ${newContextQualifierProviderResult.message}`);
96
+ }
97
+
98
+ // Create TsResSystem for the filtered manager
99
+ const filteredSystem: TsResSystem = {
100
+ qualifierTypes: originalSystem.qualifierTypes,
101
+ qualifiers: originalSystem.qualifiers,
102
+ resourceTypes: originalSystem.resourceTypes,
103
+ resourceManager: filteredManager,
104
+ importManager: newImportManagerResult.value,
105
+ contextQualifierProvider: newContextQualifierProviderResult.value
106
+ };
107
+
108
+ // Process the filtered system into ProcessedResources
109
+ const compiledResult = filteredSystem.resourceManager.getCompiledResourceCollection({
110
+ includeMetadata: true
111
+ });
112
+ if (compiledResult.isFailure()) {
113
+ return fail(`Failed to compile filtered resources: ${compiledResult.message}`);
114
+ }
115
+
116
+ const resolverResult = Runtime.ResourceResolver.create({
117
+ resourceManager: filteredSystem.resourceManager,
118
+ qualifierTypes: filteredSystem.qualifierTypes,
119
+ contextQualifierProvider: filteredSystem.contextQualifierProvider
120
+ });
121
+
122
+ if (resolverResult.isFailure()) {
123
+ return fail(`Failed to create filtered resolver: ${resolverResult.message}`);
124
+ }
125
+
126
+ // Create resource summary
127
+ const resourceIds = Array.from(filteredSystem.resourceManager.resources.keys());
128
+ const summary = {
129
+ totalResources: resourceIds.length,
130
+ resourceIds,
131
+ errorCount: 0,
132
+ warnings: []
133
+ };
134
+
135
+ const processedResources: ProcessedResources = {
136
+ system: filteredSystem,
137
+ compiledCollection: compiledResult.value,
138
+ resolver: resolverResult.value,
139
+ resourceCount: resourceIds.length,
140
+ summary
141
+ };
142
+
143
+ debugLog(enableDebug, 'Simple filtering completed successfully');
144
+ return succeed(processedResources);
145
+ } catch (error) {
146
+ return fail(
147
+ `Unexpected error during simple filtering: ${error instanceof Error ? error.message : String(error)}`
148
+ );
149
+ }
150
+ };
151
+
152
+ export const createFilteredResourceManager = async (
153
+ originalSystem: TsResSystem,
154
+ partialContext: Record<string, string>,
155
+ options: FilterOptions = { partialContextMatch: true }
156
+ ): Promise<Result<ProcessedResources>> => {
157
+ try {
158
+ const enableDebug = options.enableDebugLogging === true;
159
+
160
+ debugLog(enableDebug, '=== FILTER CREATION DEBUG ===');
161
+ debugLog(enableDebug, 'Original system:', originalSystem);
162
+ debugLog(enableDebug, 'Partial context:', partialContext);
163
+ debugLog(enableDebug, 'Options:', options);
164
+
165
+ // Validate the original system has required components
166
+ if (!originalSystem) {
167
+ return fail('Original system is undefined - ensure resources are loaded before filtering');
168
+ }
169
+
170
+ debugLog(enableDebug, 'System validation - qualifiers:', originalSystem.qualifiers);
171
+ debugLog(enableDebug, 'System validation - resourceTypes:', originalSystem.resourceTypes);
172
+ debugLog(enableDebug, 'System validation - resourceManager:', originalSystem.resourceManager);
173
+
174
+ if (!originalSystem.qualifiers) {
175
+ return fail('Original system qualifiers are undefined - system may not be properly initialized');
176
+ }
177
+
178
+ if (!originalSystem.resourceTypes) {
179
+ return fail('Original system resourceTypes are undefined - system may not be properly initialized');
180
+ }
181
+
182
+ if (!originalSystem.resourceManager) {
183
+ return fail('Original system resourceManager is undefined - system may not be properly initialized');
184
+ }
185
+
186
+ // Additional validation for the objects themselves
187
+ if (typeof originalSystem.qualifiers !== 'object') {
188
+ return fail(`Original system qualifiers has invalid type: ${typeof originalSystem.qualifiers}`);
189
+ }
190
+
191
+ if (typeof originalSystem.resourceTypes !== 'object') {
192
+ return fail(`Original system resourceTypes has invalid type: ${typeof originalSystem.resourceTypes}`);
193
+ }
194
+
195
+ // Create validated context from user input
196
+ debugLog(enableDebug, 'Creating validated context...');
197
+ debugLog(enableDebug, 'Partial context (raw):', partialContext);
198
+
199
+ // Filter out empty string values - only include qualifiers with actual values
200
+ const cleanedContext = Object.fromEntries(
201
+ Object.entries(partialContext).filter(([, value]) => value.trim() !== '')
202
+ );
203
+
204
+ debugLog(enableDebug, 'Cleaned context (empty strings removed):', cleanedContext);
205
+ debugLog(enableDebug, 'Available qualifiers:', originalSystem.qualifiers);
206
+
207
+ const contextResult = Context.Convert.validatedContextDecl.convert(cleanedContext, {
208
+ qualifiers: originalSystem.qualifiers
209
+ });
210
+
211
+ if (contextResult.isFailure()) {
212
+ debugLog(enableDebug, 'Context creation failed:', contextResult.message);
213
+ return fail(`Failed to create context: ${contextResult.message}`);
214
+ }
215
+
216
+ const validatedContext = contextResult.value;
217
+ debugLog(enableDebug, 'Validated context created:', validatedContext);
218
+
219
+ // Get filtered resource builders that match the context
220
+ debugLog(enableDebug, 'Getting resources for context...');
221
+ debugLog(enableDebug, 'Validated context:', validatedContext);
222
+ debugLog(enableDebug, 'Filter options:', options);
223
+
224
+ const filteredResourceBuilders = originalSystem.resourceManager.getResourcesForContext(
225
+ validatedContext,
226
+ options
227
+ );
228
+
229
+ debugLog(enableDebug, 'Filtered resource builders count:', filteredResourceBuilders.length);
230
+ debugLog(enableDebug, 'Filtered resource builders:', filteredResourceBuilders);
231
+
232
+ // Debug: Show detailed condition evaluation for each resource
233
+ if (enableDebug) {
234
+ debugLog(enableDebug, '=== DETAILED CANDIDATE FILTERING DEBUG ===');
235
+ const allResourceBuilders = originalSystem.resourceManager.getAllResources();
236
+ for (const resourceBuilder of allResourceBuilders) {
237
+ const builtResult = resourceBuilder.build();
238
+ if (builtResult.isSuccess()) {
239
+ const resource = builtResult.value;
240
+ debugLog(enableDebug, `\n--- Resource: ${resource.id} ---`);
241
+ debugLog(enableDebug, `Candidates: ${resource.candidates.length}`);
242
+
243
+ resource.candidates.forEach((candidate, index) => {
244
+ debugLog(enableDebug, ` Candidate ${index}:`);
245
+ if (candidate.conditions && candidate.conditions.conditions) {
246
+ candidate.conditions.conditions.forEach((condition, condIndex) => {
247
+ const canMatch = condition.canMatchPartialContext(validatedContext, options);
248
+ const contextMatch = condition.getContextMatch(validatedContext, options);
249
+ debugLog(
250
+ enableDebug,
251
+ ` Condition ${condIndex}: ${condition.qualifier.name}=${condition.value} (operator: ${condition.operator})`
252
+ );
253
+ debugLog(enableDebug, ` canMatchPartialContext: ${canMatch}`);
254
+ debugLog(enableDebug, ` contextMatch: ${contextMatch}`);
255
+ debugLog(
256
+ enableDebug,
257
+ ` qualifier in context: ${condition.qualifier.name in validatedContext}`
258
+ );
259
+ if (condition.qualifier.name in validatedContext) {
260
+ debugLog(enableDebug, ` context value: ${validatedContext[condition.qualifier.name]}`);
261
+ }
262
+ });
263
+ } else {
264
+ debugLog(enableDebug, ` No conditions (default candidate)`);
265
+ }
266
+
267
+ const candidateCanMatch = candidate.canMatchPartialContext(validatedContext, options);
268
+ debugLog(enableDebug, ` -> Candidate ${index} canMatchPartialContext: ${candidateCanMatch}`);
269
+ });
270
+
271
+ const resourceCandidatesForContext = resourceBuilder.getCandidatesForContext(
272
+ validatedContext,
273
+ options
274
+ );
275
+ debugLog(
276
+ enableDebug,
277
+ ` -> Resource has ${resourceCandidatesForContext.length} matching candidates`
278
+ );
279
+ debugLog(
280
+ enableDebug,
281
+ ` -> Resource included in filter: ${filteredResourceBuilders.includes(resourceBuilder)}`
282
+ );
283
+ }
284
+ }
285
+ }
286
+
287
+ // Convert filtered resources to declarations with only matching candidates
288
+ const resourceDeclarations: ResourceJson.Json.ILooseResourceDecl[] = [];
289
+
290
+ debugLog(enableDebug, '=== CONVERTING FILTERED RESOURCES TO DECLARATIONS ===');
291
+ for (const resourceBuilder of filteredResourceBuilders) {
292
+ const builtResourceResult = resourceBuilder.build();
293
+ if (builtResourceResult.isFailure()) {
294
+ debugLog(enableDebug, `Failed to build resource: ${builtResourceResult.message}`);
295
+ continue;
296
+ }
297
+
298
+ const builtResource = builtResourceResult.value;
299
+ debugLog(enableDebug, `\n--- Converting Resource: ${builtResource.id} ---`);
300
+ debugLog(enableDebug, `Original candidates: ${builtResource.candidates.length}`);
301
+
302
+ // Filter candidates to only include those that match the context
303
+ const matchingCandidates = builtResource.candidates.filter((candidate) => {
304
+ const canMatch = candidate.canMatchPartialContext(validatedContext, options);
305
+ debugLog(
306
+ enableDebug,
307
+ ` Candidate with conditions ${
308
+ candidate.conditions?.conditions.map((c) => `${c.qualifier.name}=${c.value}`).join(', ') || 'none'
309
+ }: canMatch = ${canMatch}`
310
+ );
311
+ return canMatch;
312
+ });
313
+
314
+ debugLog(enableDebug, `Filtered to ${matchingCandidates.length} matching candidates`);
315
+
316
+ // Create a new resource declaration with only the matching candidates
317
+ const originalDeclaration = builtResource.toLooseResourceDecl();
318
+ const filteredDeclaration: ResourceJson.Json.ILooseResourceDecl = {
319
+ ...originalDeclaration,
320
+ candidates: matchingCandidates.map((candidate) => candidate.toLooseResourceCandidateDecl())
321
+ };
322
+
323
+ debugLog(enableDebug, `Final declaration candidates: ${filteredDeclaration.candidates?.length || 0}`);
324
+
325
+ // Debug: Check what candidates are in the final declaration
326
+ if (enableDebug) {
327
+ filteredDeclaration.candidates?.forEach((candidate, index) => {
328
+ debugLog(enableDebug, ` Final Declaration Candidate ${index}:`);
329
+ if (candidate.conditions) {
330
+ Object.entries(candidate.conditions).forEach(([qualifier, value]) => {
331
+ debugLog(enableDebug, ` Condition: ${qualifier}=${value}`);
332
+ });
333
+ } else {
334
+ debugLog(enableDebug, ` No conditions (default candidate)`);
335
+ }
336
+ });
337
+ }
338
+
339
+ resourceDeclarations.push(filteredDeclaration);
340
+ }
341
+
342
+ // Validate system components before creating new manager
343
+ if (!originalSystem.qualifiers) {
344
+ return fail('Original system qualifiers is undefined');
345
+ }
346
+ if (!originalSystem.resourceTypes) {
347
+ return fail('Original system resourceTypes is undefined');
348
+ }
349
+
350
+ // Create new ResourceManagerBuilder with same configuration
351
+ debugLog(enableDebug, 'Creating new resource manager...');
352
+ debugLog(enableDebug, 'Qualifiers for new manager:', originalSystem.qualifiers);
353
+ debugLog(enableDebug, 'ResourceTypes for new manager:', originalSystem.resourceTypes);
354
+
355
+ const newManagerResult = Resources.ResourceManagerBuilder.create({
356
+ qualifiers: originalSystem.qualifiers,
357
+ resourceTypes: originalSystem.resourceTypes
358
+ });
359
+
360
+ if (newManagerResult.isFailure()) {
361
+ debugLog(enableDebug, 'New manager creation failed:', newManagerResult.message, newManagerResult);
362
+ return fail(`Failed to create new manager: ${newManagerResult.message}`);
363
+ }
364
+
365
+ const newManager = newManagerResult.value;
366
+ debugLog(enableDebug, 'New manager created successfully:', newManager);
367
+
368
+ // Add each filtered resource to the new manager
369
+ debugLog(enableDebug, '=== ADDING RESOURCES TO NEW FILTERED MANAGER ===');
370
+ for (const declaration of resourceDeclarations) {
371
+ debugLog(
372
+ enableDebug,
373
+ `Adding resource: ${declaration.id} with ${declaration.candidates?.length || 0} candidates`
374
+ );
375
+ const addResult = newManager.addResource(declaration);
376
+ if (addResult.isFailure()) {
377
+ debugLog(enableDebug, `Failed to add resource ${declaration.id}: ${addResult.message}`);
378
+ } else if (enableDebug) {
379
+ // Verify what actually got added to the manager
380
+ const retrievedResult = newManager.getBuiltResource(declaration.id);
381
+ if (retrievedResult.isSuccess()) {
382
+ const retrievedResource = retrievedResult.value;
383
+ debugLog(
384
+ enableDebug,
385
+ ` -> Resource added successfully with ${retrievedResource.candidates.length} candidates:`
386
+ );
387
+ retrievedResource.candidates.forEach((candidate, index) => {
388
+ debugLog(enableDebug, ` Final Candidate ${index}:`);
389
+ if (candidate.conditions && candidate.conditions.conditions) {
390
+ candidate.conditions.conditions.forEach((condition, condIndex) => {
391
+ debugLog(
392
+ enableDebug,
393
+ ` Condition ${condIndex}: ${condition.qualifier.name}=${condition.value}`
394
+ );
395
+ });
396
+ } else {
397
+ debugLog(enableDebug, ` No conditions (default candidate)`);
398
+ }
399
+ });
400
+ }
401
+ }
402
+ }
403
+
404
+ // Create new ImportManager for the filtered system
405
+ const newImportManagerResult = Import.ImportManager.create({
406
+ resources: newManager
407
+ });
408
+
409
+ if (newImportManagerResult.isFailure()) {
410
+ return fail(`Failed to create filtered import manager: ${newImportManagerResult.message}`);
411
+ }
412
+
413
+ // Create new ContextQualifierProvider for the filtered system
414
+ // Validate qualifiers again before using them
415
+ if (!originalSystem.qualifiers) {
416
+ return fail('Original system qualifiers became undefined during processing');
417
+ }
418
+
419
+ const newContextQualifierProviderResult = Runtime.ValidatingSimpleContextQualifierProvider.create({
420
+ qualifiers: originalSystem.qualifiers
421
+ });
422
+
423
+ if (newContextQualifierProviderResult.isFailure()) {
424
+ return fail(`Failed to create filtered context provider: ${newContextQualifierProviderResult.message}`);
425
+ }
426
+
427
+ // Create TsResSystem for the filtered manager
428
+ const filteredSystem: TsResSystem = {
429
+ qualifierTypes: originalSystem.qualifierTypes,
430
+ qualifiers: originalSystem.qualifiers,
431
+ resourceTypes: originalSystem.resourceTypes,
432
+ resourceManager: newManager,
433
+ importManager: newImportManagerResult.value,
434
+ contextQualifierProvider: newContextQualifierProviderResult.value
435
+ };
436
+
437
+ // Process the filtered system into ProcessedResources
438
+ const compiledResult = filteredSystem.resourceManager.getCompiledResourceCollection({
439
+ includeMetadata: true
440
+ });
441
+ if (compiledResult.isFailure()) {
442
+ return fail(`Failed to compile filtered resources: ${compiledResult.message}`);
443
+ }
444
+
445
+ const resolverResult = Runtime.ResourceResolver.create({
446
+ resourceManager: filteredSystem.resourceManager,
447
+ qualifierTypes: filteredSystem.qualifierTypes,
448
+ contextQualifierProvider: filteredSystem.contextQualifierProvider
449
+ });
450
+
451
+ if (resolverResult.isFailure()) {
452
+ return fail(`Failed to create filtered resolver: ${resolverResult.message}`);
453
+ }
454
+
455
+ // Create resource summary
456
+ const resourceIds = Array.from(filteredSystem.resourceManager.resources.keys());
457
+ const summary = {
458
+ totalResources: resourceIds.length,
459
+ resourceIds,
460
+ errorCount: 0,
461
+ warnings: []
462
+ };
463
+
464
+ const processedResources: ProcessedResources = {
465
+ system: filteredSystem,
466
+ compiledCollection: compiledResult.value,
467
+ resolver: resolverResult.value,
468
+ resourceCount: resourceIds.length,
469
+ summary
470
+ };
471
+
472
+ return succeed(processedResources);
473
+ } catch (error) {
474
+ return fail(
475
+ `Unexpected error during filtering: ${error instanceof Error ? error.message : String(error)}`
476
+ );
477
+ }
478
+ };
479
+
480
+ /**
481
+ * Analyzes resources to create filter result with validation
482
+ */
483
+ export const analyzeFilteredResources = (
484
+ originalResources: string[],
485
+ filteredProcessedResources: ProcessedResources | null,
486
+ originalProcessedResources: ProcessedResources
487
+ ): FilterResult => {
488
+ const warnings: string[] = [];
489
+ const filteredResources: FilteredResource[] = [];
490
+
491
+ if (!filteredProcessedResources) {
492
+ return {
493
+ success: false,
494
+ filteredResources: [],
495
+ error: 'Failed to create filtered resources',
496
+ warnings: []
497
+ };
498
+ }
499
+
500
+ const filteredResourceIds = filteredProcessedResources.summary.resourceIds || [];
501
+
502
+ // Analyze each original resource
503
+ for (const resourceId of originalResources) {
504
+ // Get original candidate count
505
+ const originalResourceResult =
506
+ originalProcessedResources.system.resourceManager.getBuiltResource(resourceId);
507
+ const originalCandidateCount = originalResourceResult.isSuccess()
508
+ ? originalResourceResult.value.candidates.length
509
+ : 0;
510
+
511
+ // Check if resource exists in filtered set
512
+ const isInFilteredSet = filteredResourceIds.includes(resourceId);
513
+ let filteredCandidateCount = 0;
514
+ let hasWarning = false;
515
+
516
+ if (isInFilteredSet) {
517
+ // Get filtered candidate count
518
+ const filteredResourceResult =
519
+ filteredProcessedResources.system.resourceManager.getBuiltResource(resourceId);
520
+ if (filteredResourceResult.isSuccess()) {
521
+ filteredCandidateCount = filteredResourceResult.value.candidates.length;
522
+ hasWarning = filteredCandidateCount === 0;
523
+
524
+ if (hasWarning) {
525
+ warnings.push(`Resource "${resourceId}" has no matching candidates`);
526
+ }
527
+ }
528
+ } else {
529
+ // Resource was completely filtered out
530
+ hasWarning = true;
531
+ warnings.push(`Resource "${resourceId}" was completely filtered out`);
532
+ }
533
+
534
+ filteredResources.push({
535
+ id: resourceId,
536
+ originalCandidateCount,
537
+ filteredCandidateCount,
538
+ hasWarning
539
+ });
540
+ }
541
+
542
+ return {
543
+ success: true,
544
+ filteredResources,
545
+ processedResources: filteredProcessedResources,
546
+ warnings
547
+ };
548
+ };
549
+
550
+ /**
551
+ * Helper to check if context has any meaningful values
552
+ */
553
+ export const hasFilterValues = (context: Record<string, string>): boolean => {
554
+ return Object.values(context).some((value) => value.trim() !== '');
555
+ };
556
+
557
+ /**
558
+ * Helper to create filter display text
559
+ */
560
+ export const getFilterSummary = (context: Record<string, string>): string => {
561
+ const setValues = Object.entries(context)
562
+ .filter(([, value]) => value.trim() !== '')
563
+ .map(([key, value]) => `${key}=${value}`);
564
+
565
+ if (setValues.length === 0) {
566
+ return 'No filters applied';
567
+ }
568
+
569
+ return `Filtering by: ${setValues.join(', ')}`;
570
+ };
571
+
572
+ /**
573
+ * Alternative approach: Creates a filtered resource manager by recreating the system from scratch
574
+ * This is more robust but potentially slower than the direct approach
575
+ */
576
+ export const createFilteredResourceManagerAlternative = async (
577
+ originalSystem: TsResSystem,
578
+ partialContext: Record<string, string>,
579
+ options: FilterOptions = { partialContextMatch: true }
580
+ ): Promise<Result<ProcessedResources>> => {
581
+ const enableDebug = options.enableDebugLogging === true;
582
+ try {
583
+ // Validate inputs first
584
+ if (!originalSystem?.qualifiers || !originalSystem?.resourceTypes || !originalSystem?.resourceManager) {
585
+ return fail('Original system is missing required components');
586
+ }
587
+
588
+ // Create a fresh system using the original system's configuration
589
+ const freshSystemResult = createTsResSystemFromConfig();
590
+
591
+ if (freshSystemResult.isFailure()) {
592
+ return fail(`Failed to create fresh system: ${freshSystemResult.message}`);
593
+ }
594
+
595
+ const freshSystem = freshSystemResult.value;
596
+
597
+ // Get all resources from the original system that match the context
598
+ // Filter out empty string values - only include qualifiers with actual values
599
+ const cleanedContext = Object.fromEntries(
600
+ Object.entries(partialContext).filter(([, value]) => value.trim() !== '')
601
+ );
602
+
603
+ const contextResult = Context.Convert.validatedContextDecl.convert(cleanedContext, {
604
+ qualifiers: originalSystem.qualifiers
605
+ });
606
+
607
+ if (contextResult.isFailure()) {
608
+ return fail(`Failed to create context: ${contextResult.message}`);
609
+ }
610
+
611
+ const validatedContext = contextResult.value;
612
+ const filteredResourceBuilders = originalSystem.resourceManager.getResourcesForContext(
613
+ validatedContext,
614
+ options
615
+ );
616
+
617
+ // Add each filtered resource to the fresh system
618
+ for (const resourceBuilder of filteredResourceBuilders) {
619
+ const builtResourceResult = resourceBuilder.build();
620
+ if (builtResourceResult.isFailure()) {
621
+ debugLog(enableDebug, `Failed to build resource: ${builtResourceResult.message}`);
622
+ continue;
623
+ }
624
+
625
+ const builtResource = builtResourceResult.value;
626
+ const declaration = builtResource.toLooseResourceDecl();
627
+
628
+ const addResult = freshSystem.resourceManager.addResource(declaration);
629
+ if (addResult.isFailure()) {
630
+ debugLog(enableDebug, `Failed to add resource ${declaration.id}: ${addResult.message}`);
631
+ }
632
+ }
633
+
634
+ // Finalize the fresh system similar to how it's done in tsResIntegration
635
+ const compiledResult = freshSystem.resourceManager.getCompiledResourceCollection({
636
+ includeMetadata: true
637
+ });
638
+ if (compiledResult.isFailure()) {
639
+ return fail(`Failed to compile filtered resources: ${compiledResult.message}`);
640
+ }
641
+
642
+ const resolverResult = Runtime.ResourceResolver.create({
643
+ resourceManager: freshSystem.resourceManager,
644
+ qualifierTypes: freshSystem.qualifierTypes,
645
+ contextQualifierProvider: freshSystem.contextQualifierProvider
646
+ });
647
+
648
+ if (resolverResult.isFailure()) {
649
+ return fail(`Failed to create filtered resolver: ${resolverResult.message}`);
650
+ }
651
+
652
+ // Create resource summary
653
+ const resourceIds = Array.from(freshSystem.resourceManager.resources.keys());
654
+ const summary = {
655
+ totalResources: resourceIds.length,
656
+ resourceIds,
657
+ errorCount: 0,
658
+ warnings: []
659
+ };
660
+
661
+ const processedResources: ProcessedResources = {
662
+ system: freshSystem,
663
+ compiledCollection: compiledResult.value,
664
+ resolver: resolverResult.value,
665
+ resourceCount: resourceIds.length,
666
+ summary
667
+ };
668
+
669
+ return succeed(processedResources);
670
+ } catch (error) {
671
+ return fail(
672
+ `Unexpected error during alternative filtering: ${
673
+ error instanceof Error ? error.message : String(error)
674
+ }`
675
+ );
676
+ }
677
+ };