jsgui3-server 0.0.147 → 0.0.149

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 (145) hide show
  1. package/.github/workflows/control-scan-manifest-check.yml +31 -0
  2. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
  3. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
  4. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
  5. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
  6. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
  7. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
  8. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
  9. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
  10. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
  11. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
  12. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
  13. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
  14. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
  15. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
  16. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
  17. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
  18. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
  19. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
  20. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
  21. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
  22. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
  23. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
  24. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
  25. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
  26. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
  27. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
  28. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
  29. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
  30. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
  31. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
  32. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
  33. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
  34. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
  35. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
  36. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
  37. package/README.md +85 -3
  38. package/admin-ui/client.js +213 -0
  39. package/admin-ui/server.js +104 -0
  40. package/client/controls/auto-observable.js +207 -0
  41. package/dev-status.svg +139 -0
  42. package/docs/api-reference.md +301 -43
  43. package/docs/books/admin-ui/01-introduction.md +32 -0
  44. package/docs/books/admin-ui/02-architecture.md +92 -0
  45. package/docs/books/admin-ui/03-controls.md +194 -0
  46. package/docs/books/admin-ui/04-implementation-plan.md +62 -0
  47. package/docs/books/admin-ui/README.md +26 -0
  48. package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
  49. package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
  50. package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
  51. package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
  52. package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
  53. package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
  54. package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
  55. package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
  56. package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
  57. package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
  58. package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
  59. package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
  60. package/docs/bundling-system-deep-dive.md +9 -4
  61. package/docs/comprehensive-documentation.md +49 -18
  62. package/docs/configuration-reference.md +152 -27
  63. package/docs/core/README.md +19 -0
  64. package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
  65. package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
  66. package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
  67. package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
  68. package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
  69. package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
  70. package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
  71. package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
  72. package/docs/publishers-guide.md +59 -4
  73. package/docs/resources-guide.md +184 -35
  74. package/docs/simple-server-api-design.md +72 -17
  75. package/docs/system-architecture.md +18 -14
  76. package/examples/controls/15) window, observable SSE/server.js +6 -1
  77. package/examples/controls/19) window, auto observable ui/client.js +125 -0
  78. package/examples/controls/19) window, auto observable ui/server.js +73 -0
  79. package/examples/controls/20) window, task manager app/README.md +133 -0
  80. package/examples/controls/20) window, task manager app/client.js +797 -0
  81. package/examples/controls/20) window, task manager app/server.js +178 -0
  82. package/examples/controls/6) window, color_palette/client.js +165 -68
  83. package/examples/controls/9) window, date picker/client.js +362 -76
  84. package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
  85. package/examples/jsgui3-html/06) theming/client.js +22 -1
  86. package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
  87. package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
  88. package/lab/experiments/capture-color-controls.js +196 -0
  89. package/lab/results/screenshots/color-controls/full_page.png +0 -0
  90. package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
  91. package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
  92. package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
  93. package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
  94. package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
  95. package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
  96. package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
  97. package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
  98. package/lab/screenshot-utils.js +248 -0
  99. package/module.js +11 -4
  100. package/package.json +14 -4
  101. package/publishers/Publishers.js +4 -3
  102. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
  103. package/publishers/http-observable-publisher.js +8 -0
  104. package/publishers/http-sse-publisher.js +341 -0
  105. package/publishers/http-webpage-publisher.js +13 -3
  106. package/publishers/http-webpageorsite-publisher.js +18 -0
  107. package/resources/process-resource.js +950 -0
  108. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
  109. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
  110. package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
  111. package/resources/remote-process-resource.js +355 -0
  112. package/resources/server-resource-pool.js +354 -41
  113. package/serve-factory.js +441 -259
  114. package/server.js +161 -16
  115. package/tests/README.md +66 -4
  116. package/tests/admin-ui-render.test.js +24 -0
  117. package/tests/assigners.test.js +56 -40
  118. package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
  119. package/tests/configuration-validation.test.js +21 -18
  120. package/tests/content-analysis.test.js +7 -6
  121. package/tests/control-optimizer-cache-behavior.test.js +52 -0
  122. package/tests/control-scan-manifest-regression.test.js +144 -0
  123. package/tests/end-to-end.test.js +15 -14
  124. package/tests/error-handling.test.js +222 -179
  125. package/tests/fixtures/bundling-default-button-client.js +37 -0
  126. package/tests/fixtures/bundling-default-window-client.js +34 -0
  127. package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
  128. package/tests/fixtures/resource-monitor-client.js +319 -0
  129. package/tests/helpers/puppeteer-e2e-harness.js +317 -0
  130. package/tests/http-sse-publisher.test.js +136 -0
  131. package/tests/performance.test.js +69 -65
  132. package/tests/process-resource.test.js +138 -0
  133. package/tests/publishers.test.js +7 -7
  134. package/tests/remote-process-resource.test.js +160 -0
  135. package/tests/sass-controls.e2e.test.js +7 -1
  136. package/tests/serve-resources.test.js +270 -0
  137. package/tests/serve.test.js +120 -50
  138. package/tests/server-resource-pool.test.js +106 -0
  139. package/tests/small-controls-bundle-size.test.js +252 -0
  140. package/tests/test-runner.js +13 -1
  141. package/tests/window-examples.puppeteer.test.js +204 -1
  142. package/tests/window-resource-integration.puppeteer.test.js +585 -0
  143. package/tests/temp_invalid.js +0 -7
  144. package/tests/temp_invalid_utf8.js +0 -1
  145. package/tests/temp_malformed.js +0 -10
@@ -0,0 +1,829 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+
5
+ const fs_async = fs.promises;
6
+
7
+ const local_module_extensions = ['.js', '.mjs', '.cjs'];
8
+
9
+ const escape_for_regexp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ const to_module_literal = (file_path) => file_path.replace(/\\/g, '\\\\');
11
+
12
+ const path_exists = async (file_path) => {
13
+ try {
14
+ await fs_async.access(file_path, fs.constants.F_OK);
15
+ return true;
16
+ } catch (err) {
17
+ return false;
18
+ }
19
+ };
20
+
21
+ const read_path_stat = async (file_path) => {
22
+ try {
23
+ return await fs_async.stat(file_path);
24
+ } catch (err) {
25
+ return null;
26
+ }
27
+ };
28
+
29
+ const sort_path_items = (items) => {
30
+ return Array.isArray(items)
31
+ ? Array.from(items).sort((a, b) => String(a).localeCompare(String(b)))
32
+ : [];
33
+ };
34
+
35
+ const sanitize_identifier = (value) => {
36
+ const candidate = (value || '').trim().replace(/\.\.\./g, '');
37
+ if (!candidate) return null;
38
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate)) return null;
39
+ return candidate;
40
+ };
41
+
42
+ const parse_identifier_list = (list_text) => {
43
+ const text = (list_text || '').trim();
44
+ if (!text) return [];
45
+
46
+ const tokens = text.split(',');
47
+ const identifiers = [];
48
+
49
+ for (const raw_token of tokens) {
50
+ let token = (raw_token || '').trim();
51
+ if (!token) continue;
52
+
53
+ // import { Name as Alias } ...
54
+ if (token.includes(' as ')) {
55
+ token = token.split(/\s+as\s+/)[0].trim();
56
+ }
57
+ // const { Name: alias } = ...
58
+ if (token.includes(':')) {
59
+ token = token.split(':')[0].trim();
60
+ }
61
+ // const { Name = fallback } = ...
62
+ if (token.includes('=')) {
63
+ token = token.split('=')[0].trim();
64
+ }
65
+
66
+ const identifier = sanitize_identifier(token);
67
+ if (identifier) identifiers.push(identifier);
68
+ }
69
+
70
+ return identifiers;
71
+ };
72
+
73
+ const parse_destructured_entries = (list_text) => {
74
+ const text = (list_text || '').trim();
75
+ if (!text) return [];
76
+
77
+ const tokens = text.split(',');
78
+ const entries = [];
79
+
80
+ for (const raw_token of tokens) {
81
+ let token = (raw_token || '').trim();
82
+ if (!token) continue;
83
+
84
+ if (token.startsWith('...')) {
85
+ token = token.slice(3).trim();
86
+ }
87
+
88
+ if (token.includes('=')) {
89
+ token = token.split('=')[0].trim();
90
+ }
91
+
92
+ let source_name = token;
93
+ let local_name = token;
94
+
95
+ if (token.includes(' as ')) {
96
+ const [source_part, local_part] = token.split(/\s+as\s+/);
97
+ source_name = (source_part || '').trim();
98
+ local_name = (local_part || '').trim();
99
+ } else if (token.includes(':')) {
100
+ const [source_part, local_part] = token.split(':');
101
+ source_name = (source_part || '').trim();
102
+ local_name = (local_part || '').trim();
103
+ }
104
+
105
+ const normalized_source = sanitize_identifier(source_name);
106
+ const normalized_local = sanitize_identifier(local_name);
107
+ if (!normalized_source || !normalized_local) continue;
108
+
109
+ entries.push({
110
+ source: normalized_source,
111
+ local: normalized_local
112
+ });
113
+ }
114
+
115
+ return entries;
116
+ };
117
+
118
+ class JSGUI3_HTML_Control_Optimizer {
119
+ constructor(spec = {}) {
120
+ this.package_name = spec.package_name || 'jsgui3-html';
121
+ this.emit_manifest = spec.emit_manifest === true;
122
+ this.allow_dynamic_controls = spec.allow_dynamic_controls === true;
123
+ this.log = spec.log === true;
124
+ this.include_controls = Array.isArray(spec.include_controls) ? spec.include_controls : [];
125
+ this.exclude_controls = new Set(Array.isArray(spec.exclude_controls) ? spec.exclude_controls : []);
126
+ this.cache_enabled = spec.cacheEnabled !== false;
127
+ this.shared_cache_enabled = spec.sharedCache !== false;
128
+ const default_cache_root = path.join(process.cwd(), '.jsgui3-server-cache');
129
+ this.manifest_dir = spec.manifest_dir || path.join(default_cache_root, 'control-scan-manifests');
130
+ this.shim_dir = spec.shim_dir || path.join(default_cache_root, 'jsgui3-html-shims');
131
+ const include_controls_key = sort_path_items(this.include_controls).join(',');
132
+ const exclude_controls_key = sort_path_items(Array.from(this.exclude_controls)).join(',');
133
+ this.cache_namespace_key = `${this.package_name}|include:${include_controls_key}|exclude:${exclude_controls_key}`;
134
+
135
+ if (this.cache_enabled && this.shared_cache_enabled) {
136
+ if (!JSGUI3_HTML_Control_Optimizer.shared_entry_analysis_cache) {
137
+ JSGUI3_HTML_Control_Optimizer.shared_entry_analysis_cache = new Map();
138
+ }
139
+ if (!JSGUI3_HTML_Control_Optimizer.shared_file_scan_cache) {
140
+ JSGUI3_HTML_Control_Optimizer.shared_file_scan_cache = new Map();
141
+ }
142
+ if (!JSGUI3_HTML_Control_Optimizer.shared_local_module_resolution_cache) {
143
+ JSGUI3_HTML_Control_Optimizer.shared_local_module_resolution_cache = new Map();
144
+ }
145
+ if (!JSGUI3_HTML_Control_Optimizer.shared_controls_map_cache) {
146
+ JSGUI3_HTML_Control_Optimizer.shared_controls_map_cache = new Map();
147
+ }
148
+ this.entry_analysis_cache = JSGUI3_HTML_Control_Optimizer.shared_entry_analysis_cache;
149
+ this.file_scan_cache = JSGUI3_HTML_Control_Optimizer.shared_file_scan_cache;
150
+ this.local_module_resolution_cache = JSGUI3_HTML_Control_Optimizer.shared_local_module_resolution_cache;
151
+ this.controls_map_cache = JSGUI3_HTML_Control_Optimizer.shared_controls_map_cache;
152
+ } else {
153
+ this.entry_analysis_cache = new Map();
154
+ this.file_scan_cache = new Map();
155
+ this.local_module_resolution_cache = new Map();
156
+ this.controls_map_cache = new Map();
157
+ }
158
+
159
+ this.cache_stats = {
160
+ entry_analysis_hits: 0,
161
+ entry_analysis_misses: 0,
162
+ file_scan_hits: 0,
163
+ file_scan_misses: 0,
164
+ module_resolution_hits: 0,
165
+ module_resolution_misses: 0,
166
+ controls_map_hits: 0,
167
+ controls_map_misses: 0
168
+ };
169
+ }
170
+
171
+ async optimize(entry_file_path) {
172
+ const analysis = await this.scan_entry(entry_file_path);
173
+ const manifest = this.build_manifest(analysis);
174
+
175
+ if (!analysis.uses_jsgui3_html) {
176
+ return {
177
+ enabled: false,
178
+ reason: 'no_jsgui3_html_usage',
179
+ manifest
180
+ };
181
+ }
182
+
183
+ if (analysis.dynamic_control_access_detected && !this.allow_dynamic_controls) {
184
+ return {
185
+ enabled: false,
186
+ reason: 'dynamic_control_access_detected',
187
+ manifest
188
+ };
189
+ }
190
+
191
+ const shim_file_path = await this.write_shim_file(manifest);
192
+ const plugin = this.create_esbuild_plugin(shim_file_path);
193
+
194
+ const result = {
195
+ enabled: true,
196
+ reason: 'optimized',
197
+ shim_file_path,
198
+ plugin,
199
+ manifest: Object.assign({}, manifest, { shim_file_path })
200
+ };
201
+
202
+ if (this.emit_manifest) {
203
+ await this.write_manifest_file(result.manifest);
204
+ }
205
+
206
+ if (this.log) {
207
+ const { selected_controls, reachable_files } = result.manifest;
208
+ console.log('[JSGUI3_HTML_Control_Optimizer] enabled');
209
+ console.log('[JSGUI3_HTML_Control_Optimizer] reachable_files:', reachable_files.length);
210
+ console.log('[JSGUI3_HTML_Control_Optimizer] selected_controls:', selected_controls);
211
+ console.log('[JSGUI3_HTML_Control_Optimizer] shim_file_path:', shim_file_path);
212
+ }
213
+
214
+ return result;
215
+ }
216
+
217
+ clone_analysis(analysis) {
218
+ if (!analysis || typeof analysis !== 'object') {
219
+ return analysis;
220
+ }
221
+ return {
222
+ ...analysis,
223
+ reachable_files: sort_path_items(analysis.reachable_files),
224
+ used_identifiers: sort_path_items(analysis.used_identifiers),
225
+ selected_controls: sort_path_items(analysis.selected_controls),
226
+ unmatched_identifiers: sort_path_items(analysis.unmatched_identifiers),
227
+ package_aliases: sort_path_items(analysis.package_aliases),
228
+ controls_aliases: sort_path_items(analysis.controls_aliases),
229
+ dependency_items: Array.isArray(analysis.dependency_items)
230
+ ? analysis.dependency_items.map((item) => ({...item}))
231
+ : []
232
+ };
233
+ }
234
+
235
+ async is_cached_analysis_valid(cached_analysis) {
236
+ const dependency_items = Array.isArray(cached_analysis && cached_analysis.dependency_items)
237
+ ? cached_analysis.dependency_items
238
+ : [];
239
+
240
+ for (const dependency_item of dependency_items) {
241
+ const {file_path, mtime_ms, size} = dependency_item;
242
+ const stat_result = await read_path_stat(file_path);
243
+ if (!stat_result) return false;
244
+ if (stat_result.mtimeMs !== mtime_ms) return false;
245
+ if (stat_result.size !== size) return false;
246
+ }
247
+
248
+ return true;
249
+ }
250
+
251
+ async scan_entry(entry_file_path) {
252
+ const absolute_entry_file_path = path.resolve(entry_file_path);
253
+ const entry_cache_key = `${this.cache_namespace_key}::${absolute_entry_file_path}`;
254
+ const jsgui3_html_root = path.dirname(require.resolve(this.package_name));
255
+ if (this.cache_enabled) {
256
+ const cached_analysis = this.entry_analysis_cache.get(entry_cache_key);
257
+ if (cached_analysis && await this.is_cached_analysis_valid(cached_analysis)) {
258
+ this.cache_stats.entry_analysis_hits += 1;
259
+ return this.clone_analysis(cached_analysis);
260
+ }
261
+ this.cache_stats.entry_analysis_misses += 1;
262
+ }
263
+
264
+ const controls_map = await this.read_controls_require_map(jsgui3_html_root);
265
+ const traversal_result = await this.collect_reachable_files(absolute_entry_file_path);
266
+ const {
267
+ reachable_files,
268
+ dependency_items
269
+ } = traversal_result;
270
+ const dependency_stat_map = new Map(
271
+ dependency_items.map((item) => [item.file_path, {
272
+ mtimeMs: item.mtime_ms,
273
+ size: item.size
274
+ }])
275
+ );
276
+
277
+ const used_identifiers_set = new Set();
278
+ const package_aliases_set = new Set();
279
+ const controls_aliases_set = new Set();
280
+ let uses_jsgui3_html = false;
281
+ let dynamic_control_access_detected = false;
282
+
283
+ for (const file_path of reachable_files) {
284
+ const stat_result = dependency_stat_map.get(file_path) || await read_path_stat(file_path);
285
+ if (!stat_result) continue;
286
+ const scan_result = await this.read_file_scan_result(file_path, stat_result);
287
+
288
+ if (scan_result.uses_jsgui3_html) uses_jsgui3_html = true;
289
+ if (scan_result.dynamic_control_access_detected) dynamic_control_access_detected = true;
290
+
291
+ for (const identifier of scan_result.control_identifiers) {
292
+ used_identifiers_set.add(identifier);
293
+ }
294
+ for (const package_alias of scan_result.package_aliases || []) {
295
+ package_aliases_set.add(package_alias);
296
+ }
297
+ for (const controls_alias of scan_result.controls_aliases || []) {
298
+ controls_aliases_set.add(controls_alias);
299
+ }
300
+ }
301
+
302
+ for (const identifier of this.include_controls) {
303
+ used_identifiers_set.add(identifier);
304
+ }
305
+
306
+ const used_identifiers = Array.from(used_identifiers_set).sort();
307
+ const selected_controls = [];
308
+ const unmatched_identifiers = [];
309
+
310
+ for (const identifier of used_identifiers) {
311
+ if (this.exclude_controls.has(identifier)) continue;
312
+ if (controls_map[identifier]) {
313
+ selected_controls.push(identifier);
314
+ } else {
315
+ unmatched_identifiers.push(identifier);
316
+ }
317
+ }
318
+
319
+ const analysis = {
320
+ package_name: this.package_name,
321
+ entry_file_path: absolute_entry_file_path,
322
+ reachable_files,
323
+ uses_jsgui3_html,
324
+ dynamic_control_access_detected,
325
+ used_identifiers,
326
+ selected_controls,
327
+ unmatched_identifiers,
328
+ package_aliases: Array.from(package_aliases_set).sort(),
329
+ controls_aliases: Array.from(controls_aliases_set).sort(),
330
+ controls_map,
331
+ jsgui3_html_root,
332
+ dependency_items
333
+ };
334
+
335
+ if (this.cache_enabled) {
336
+ this.entry_analysis_cache.set(entry_cache_key, this.clone_analysis(analysis));
337
+ }
338
+ return analysis;
339
+ }
340
+
341
+ async collect_reachable_files(entry_file_path) {
342
+ const pending = [entry_file_path];
343
+ const visited = new Set();
344
+ const dependency_items = [];
345
+
346
+ while (pending.length > 0) {
347
+ const next_file_path = pending.pop();
348
+ const resolved_path = path.resolve(next_file_path);
349
+ if (visited.has(resolved_path)) continue;
350
+
351
+ if (!(await path_exists(resolved_path))) continue;
352
+ visited.add(resolved_path);
353
+
354
+ const stat_result = await read_path_stat(resolved_path);
355
+ if (!stat_result) continue;
356
+ dependency_items.push({
357
+ file_path: resolved_path,
358
+ mtime_ms: stat_result.mtimeMs,
359
+ size: stat_result.size
360
+ });
361
+
362
+ const ext = path.extname(resolved_path).toLowerCase();
363
+ if (!local_module_extensions.includes(ext)) continue;
364
+
365
+ const module_requests = await this.read_file_module_requests(resolved_path, stat_result);
366
+ for (const request_path of module_requests) {
367
+ if (!(request_path.startsWith('.') || request_path.startsWith('/'))) continue;
368
+ const resolved_request_path = await this.resolve_local_module(resolved_path, request_path);
369
+ if (resolved_request_path) pending.push(resolved_request_path);
370
+ }
371
+ }
372
+
373
+ dependency_items.sort((a, b) => String(a.file_path).localeCompare(String(b.file_path)));
374
+ return {
375
+ reachable_files: Array.from(visited).sort(),
376
+ dependency_items
377
+ };
378
+ }
379
+
380
+ async read_cached_file_source(file_path, stat_result) {
381
+ if (!this.cache_enabled) {
382
+ this.cache_stats.file_scan_misses += 1;
383
+ return {
384
+ file_path,
385
+ mtime_ms: stat_result.mtimeMs,
386
+ size: stat_result.size,
387
+ source_text: await fs_async.readFile(file_path, 'utf8'),
388
+ module_requests: null,
389
+ scan_result: null
390
+ };
391
+ }
392
+
393
+ const cached_item = this.file_scan_cache.get(file_path);
394
+ if (cached_item && cached_item.mtime_ms === stat_result.mtimeMs && cached_item.size === stat_result.size) {
395
+ this.cache_stats.file_scan_hits += 1;
396
+ return cached_item;
397
+ }
398
+
399
+ this.cache_stats.file_scan_misses += 1;
400
+ const source_text = await fs_async.readFile(file_path, 'utf8');
401
+ const next_cached_item = {
402
+ file_path,
403
+ mtime_ms: stat_result.mtimeMs,
404
+ size: stat_result.size,
405
+ source_text,
406
+ module_requests: null,
407
+ scan_result: null
408
+ };
409
+ this.file_scan_cache.set(file_path, next_cached_item);
410
+ return next_cached_item;
411
+ }
412
+
413
+ async read_file_module_requests(file_path, stat_result) {
414
+ const cached_item = await this.read_cached_file_source(file_path, stat_result);
415
+ if (!cached_item.module_requests) {
416
+ cached_item.module_requests = this.extract_module_requests(cached_item.source_text);
417
+ }
418
+ return cached_item.module_requests;
419
+ }
420
+
421
+ async read_file_scan_result(file_path, stat_result) {
422
+ const cached_item = await this.read_cached_file_source(file_path, stat_result);
423
+ if (!cached_item.scan_result) {
424
+ cached_item.scan_result = this.scan_source_text(cached_item.source_text);
425
+ }
426
+ return cached_item.scan_result;
427
+ }
428
+
429
+ extract_module_requests(source_text) {
430
+ const requests = new Set();
431
+ const regexes = [
432
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
433
+ /\bimport\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
434
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g
435
+ ];
436
+
437
+ for (const regex of regexes) {
438
+ let match;
439
+ while ((match = regex.exec(source_text)) !== null) {
440
+ const module_request = match[1];
441
+ if (module_request) requests.add(module_request);
442
+ }
443
+ }
444
+
445
+ return Array.from(requests);
446
+ }
447
+
448
+ async resolve_local_module(from_file_path, request_path) {
449
+ if (!this.cache_enabled) {
450
+ this.cache_stats.module_resolution_misses += 1;
451
+ return this._resolve_local_module_uncached(from_file_path, request_path);
452
+ }
453
+
454
+ const resolution_cache_key = `${from_file_path}::${request_path}`;
455
+ if (this.local_module_resolution_cache.has(resolution_cache_key)) {
456
+ this.cache_stats.module_resolution_hits += 1;
457
+ return this.local_module_resolution_cache.get(resolution_cache_key);
458
+ }
459
+
460
+ this.cache_stats.module_resolution_misses += 1;
461
+ const resolved_value = await this._resolve_local_module_uncached(from_file_path, request_path);
462
+ this.local_module_resolution_cache.set(resolution_cache_key, resolved_value);
463
+ return resolved_value;
464
+ }
465
+
466
+ async _resolve_local_module_uncached(from_file_path, request_path) {
467
+ const from_dir = path.dirname(from_file_path);
468
+ const base_path = request_path.startsWith('/')
469
+ ? path.resolve(request_path)
470
+ : path.resolve(from_dir, request_path);
471
+
472
+ const candidates = [];
473
+ const ext = path.extname(base_path);
474
+
475
+ if (ext) {
476
+ candidates.push(base_path);
477
+ } else {
478
+ candidates.push(base_path);
479
+ for (const candidate_ext of local_module_extensions) {
480
+ candidates.push(`${base_path}${candidate_ext}`);
481
+ }
482
+ for (const candidate_ext of local_module_extensions) {
483
+ candidates.push(path.join(base_path, `index${candidate_ext}`));
484
+ }
485
+ }
486
+
487
+ for (const candidate of candidates) {
488
+ if (await path_exists(candidate)) {
489
+ return candidate;
490
+ }
491
+ }
492
+
493
+ return null;
494
+ }
495
+
496
+ scan_source_text(source_text) {
497
+ const control_identifiers = new Set();
498
+ const package_name_regex = escape_for_regexp(this.package_name);
499
+ const package_aliases = new Set();
500
+ const controls_aliases = new Set(['controls']);
501
+
502
+ const package_usage_regexes = [
503
+ new RegExp(`require\\s*\\(\\s*['"]${package_name_regex}['"]\\s*\\)`, 'g'),
504
+ new RegExp(`\\bfrom\\s*['"]${package_name_regex}['"]`, 'g')
505
+ ];
506
+ const uses_jsgui3_html = package_usage_regexes.some((regex) => regex.test(source_text));
507
+
508
+ // Detect package aliases:
509
+ // const ui = require('jsgui3-html')
510
+ // import ui from 'jsgui3-html'
511
+ // import * as ui from 'jsgui3-html'
512
+ const package_alias_regexes = [
513
+ new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=\\s*require\\s*\\(\\s*['"]${package_name_regex}['"]\\s*\\)`, 'g'),
514
+ new RegExp(`\\bimport\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s+from\\s*['"]${package_name_regex}['"]`, 'g'),
515
+ new RegExp(`\\bimport\\s*\\*\\s*as\\s*([A-Za-z_$][A-Za-z0-9_$]*)\\s+from\\s*['"]${package_name_regex}['"]`, 'g')
516
+ ];
517
+
518
+ for (const regex of package_alias_regexes) {
519
+ let match;
520
+ while ((match = regex.exec(source_text)) !== null) {
521
+ const alias_name = sanitize_identifier(match[1]);
522
+ if (alias_name) package_aliases.add(alias_name);
523
+ }
524
+ }
525
+
526
+ if (/\bjsgui\.controls\b/.test(source_text) || /\bjsgui\.[A-Z][A-Za-z0-9_$]*/.test(source_text)) {
527
+ package_aliases.add('jsgui');
528
+ }
529
+
530
+ // Detect controls aliases:
531
+ // const c = require('jsgui3-html').controls
532
+ // const c = ui.controls
533
+ const controls_alias_direct_regex = new RegExp(
534
+ `\\b(?:const|let|var)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=\\s*require\\s*\\(\\s*['"]${package_name_regex}['"]\\s*\\)\\.controls\\b`,
535
+ 'g'
536
+ );
537
+ let direct_match;
538
+ while ((direct_match = controls_alias_direct_regex.exec(source_text)) !== null) {
539
+ const alias_name = sanitize_identifier(direct_match[1]);
540
+ if (alias_name) controls_aliases.add(alias_name);
541
+ }
542
+
543
+ for (const package_alias of package_aliases) {
544
+ const escaped_package_alias = escape_for_regexp(package_alias);
545
+
546
+ const alias_from_controls_regex = new RegExp(
547
+ `\\b(?:const|let|var)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=\\s*${escaped_package_alias}\\.controls\\b`,
548
+ 'g'
549
+ );
550
+ let alias_match;
551
+ while ((alias_match = alias_from_controls_regex.exec(source_text)) !== null) {
552
+ const alias_name = sanitize_identifier(alias_match[1]);
553
+ if (alias_name) controls_aliases.add(alias_name);
554
+ }
555
+
556
+ const destructure_package_regex = new RegExp(
557
+ `\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*${escaped_package_alias}\\b`,
558
+ 'g'
559
+ );
560
+ let destructure_match;
561
+ while ((destructure_match = destructure_package_regex.exec(source_text)) !== null) {
562
+ const entries = parse_destructured_entries(destructure_match[1]);
563
+ for (const entry of entries) {
564
+ if (entry.source === 'controls') {
565
+ controls_aliases.add(entry.local);
566
+ } else {
567
+ control_identifiers.add(entry.source);
568
+ }
569
+ }
570
+ }
571
+ }
572
+
573
+ const import_destructure_regex = new RegExp(
574
+ `\\bimport\\s*\\{([^}]+)\\}\\s*from\\s*['"]${package_name_regex}['"]`,
575
+ 'g'
576
+ );
577
+ let import_destructure_match;
578
+ while ((import_destructure_match = import_destructure_regex.exec(source_text)) !== null) {
579
+ const entries = parse_destructured_entries(import_destructure_match[1]);
580
+ for (const entry of entries) {
581
+ if (entry.source === 'controls') {
582
+ controls_aliases.add(entry.local);
583
+ } else {
584
+ control_identifiers.add(entry.source);
585
+ }
586
+ }
587
+ }
588
+
589
+ let dynamic_control_access_detected =
590
+ /\bcontrols\s*\[[^\]]+\]/.test(source_text) ||
591
+ /\bjsgui\.controls\s*\[[^\]]+\]/.test(source_text);
592
+
593
+ for (const controls_alias of controls_aliases) {
594
+ const escaped_controls_alias = escape_for_regexp(controls_alias);
595
+ const alias_dynamic_regex = new RegExp(`\\b${escaped_controls_alias}\\s*\\[[^\\]]+\\]`);
596
+ if (alias_dynamic_regex.test(source_text)) dynamic_control_access_detected = true;
597
+ }
598
+
599
+ for (const package_alias of package_aliases) {
600
+ const escaped_package_alias = escape_for_regexp(package_alias);
601
+ const package_controls_dynamic_regex = new RegExp(`\\b${escaped_package_alias}\\.controls\\s*\\[[^\\]]+\\]`);
602
+ const package_dynamic_regex = new RegExp(`\\b${escaped_package_alias}\\s*\\[[^\\]]+\\]`);
603
+ if (package_controls_dynamic_regex.test(source_text) || package_dynamic_regex.test(source_text)) {
604
+ dynamic_control_access_detected = true;
605
+ }
606
+ }
607
+
608
+ // controls.Button / jsgui.controls.Button
609
+ const dot_access_regexes = [/\bcontrols\.([A-Za-z_$][A-Za-z0-9_$]*)/g, /\bjsgui\.controls\.([A-Za-z_$][A-Za-z0-9_$]*)/g, /\bjsgui\.([A-Z][A-Za-z0-9_$]*)/g];
610
+ for (const controls_alias of controls_aliases) {
611
+ dot_access_regexes.push(new RegExp(`\\b${escape_for_regexp(controls_alias)}\\.([A-Za-z_$][A-Za-z0-9_$]*)`, 'g'));
612
+ }
613
+ for (const package_alias of package_aliases) {
614
+ const escaped_package_alias = escape_for_regexp(package_alias);
615
+ dot_access_regexes.push(new RegExp(`\\b${escaped_package_alias}\\.controls\\.([A-Za-z_$][A-Za-z0-9_$]*)`, 'g'));
616
+ dot_access_regexes.push(new RegExp(`\\b${escaped_package_alias}\\.([A-Z][A-Za-z0-9_$]*)`, 'g'));
617
+ }
618
+
619
+ for (const regex of dot_access_regexes) {
620
+ let match;
621
+ while ((match = regex.exec(source_text)) !== null) {
622
+ const identifier = sanitize_identifier(match[1]);
623
+ if (identifier) control_identifiers.add(identifier);
624
+ }
625
+ }
626
+
627
+ // const { Button } = controls / jsgui.controls / require('jsgui3-html').controls / require('jsgui3-html')
628
+ const destructuring_regexes = [
629
+ /\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*controls\b/g,
630
+ /\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*jsgui\.controls\b/g,
631
+ new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*require\\s*\\(\\s*['"]${package_name_regex}['"]\\s*\\)\\.controls\\b`, 'g'),
632
+ new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*require\\s*\\(\\s*['"]${package_name_regex}['"]\\s*\\)\\b`, 'g'),
633
+ new RegExp(`\\bimport\\s*\\{([^}]+)\\}\\s*from\\s*['"]${package_name_regex}['"]`, 'g')
634
+ ];
635
+ for (const controls_alias of controls_aliases) {
636
+ destructuring_regexes.push(new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*${escape_for_regexp(controls_alias)}\\b`, 'g'));
637
+ }
638
+ for (const package_alias of package_aliases) {
639
+ const escaped_package_alias = escape_for_regexp(package_alias);
640
+ destructuring_regexes.push(new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*${escaped_package_alias}\\.controls\\b`, 'g'));
641
+ destructuring_regexes.push(new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]+)\\}\\s*=\\s*${escaped_package_alias}\\b`, 'g'));
642
+ }
643
+
644
+ for (const regex of destructuring_regexes) {
645
+ let match;
646
+ while ((match = regex.exec(source_text)) !== null) {
647
+ const entries = parse_destructured_entries(match[1]);
648
+ for (const entry of entries) {
649
+ if (entry.source === 'controls') {
650
+ controls_aliases.add(entry.local);
651
+ } else {
652
+ control_identifiers.add(entry.source);
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ return {
659
+ uses_jsgui3_html,
660
+ dynamic_control_access_detected,
661
+ control_identifiers: Array.from(control_identifiers),
662
+ package_aliases: Array.from(package_aliases),
663
+ controls_aliases: Array.from(controls_aliases)
664
+ };
665
+ }
666
+
667
+ async read_controls_require_map(jsgui3_html_root) {
668
+ if (!this.cache_enabled) {
669
+ this.cache_stats.controls_map_misses += 1;
670
+ return this._read_controls_require_map_uncached(jsgui3_html_root);
671
+ }
672
+
673
+ if (this.controls_map_cache.has(jsgui3_html_root)) {
674
+ this.cache_stats.controls_map_hits += 1;
675
+ return this.controls_map_cache.get(jsgui3_html_root);
676
+ }
677
+ this.cache_stats.controls_map_misses += 1;
678
+ const controls_map = await this._read_controls_require_map_uncached(jsgui3_html_root);
679
+ this.controls_map_cache.set(jsgui3_html_root, controls_map);
680
+ return controls_map;
681
+ }
682
+
683
+ async _read_controls_require_map_uncached(jsgui3_html_root) {
684
+ const controls_file_path = path.join(jsgui3_html_root, 'controls', 'controls.js');
685
+ const controls_source = await fs_async.readFile(controls_file_path, 'utf8');
686
+ const controls_file_dir = path.dirname(controls_file_path);
687
+
688
+ const controls_map = {};
689
+ const require_regex = /([A-Za-z0-9_]+)\s*:\s*require\((['"])([^'"]+)\2\)(\.[A-Za-z0-9_]+)?/g;
690
+ let match;
691
+ while ((match = require_regex.exec(controls_source)) !== null) {
692
+ const control_name = match[1];
693
+ const require_path = match[3];
694
+ const property_suffix = match[4] || '';
695
+
696
+ const absolute_require_path = path.resolve(controls_file_dir, require_path);
697
+ controls_map[control_name] = {
698
+ absolute_require_path,
699
+ property_suffix
700
+ };
701
+ }
702
+
703
+ return controls_map;
704
+ }
705
+
706
+ build_manifest(analysis) {
707
+ return {
708
+ package_name: analysis.package_name,
709
+ entry_file_path: analysis.entry_file_path,
710
+ reachable_files: analysis.reachable_files,
711
+ uses_jsgui3_html: analysis.uses_jsgui3_html,
712
+ dynamic_control_access_detected: analysis.dynamic_control_access_detected,
713
+ used_identifiers: analysis.used_identifiers,
714
+ selected_controls: analysis.selected_controls,
715
+ unmatched_identifiers: analysis.unmatched_identifiers,
716
+ package_aliases: analysis.package_aliases || [],
717
+ controls_aliases: analysis.controls_aliases || []
718
+ };
719
+ }
720
+
721
+ async write_shim_file(manifest) {
722
+ await fs_async.mkdir(this.shim_dir, {recursive: true});
723
+ const jsgui3_html_root = path.dirname(require.resolve(this.package_name));
724
+
725
+ const hash_input = JSON.stringify({
726
+ entry: manifest.entry_file_path,
727
+ selected_controls: manifest.selected_controls,
728
+ package_name: manifest.package_name
729
+ });
730
+ const hash = crypto.createHash('sha256').update(hash_input).digest('hex').slice(0, 24);
731
+ const shim_file_path = path.join(this.shim_dir, `jsgui3-html-controls-shim-${hash}.js`);
732
+
733
+ const controls_map = await this.read_controls_require_map(jsgui3_html_root);
734
+ const selected_control_lines = [];
735
+ for (const control_name of manifest.selected_controls) {
736
+ const map_item = controls_map[control_name];
737
+ if (!map_item) continue;
738
+ const {absolute_require_path, property_suffix} = map_item;
739
+ selected_control_lines.push(
740
+ ` ${control_name}: require('${to_module_literal(absolute_require_path)}')${property_suffix}`
741
+ );
742
+ }
743
+
744
+ const html_core_path = to_module_literal(path.join(jsgui3_html_root, 'html-core', 'html-core.js'));
745
+ const router_path = to_module_literal(path.join(jsgui3_html_root, 'router', 'router.js'));
746
+ const resource_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'resource.js'));
747
+ const resource_pool_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'pool.js'));
748
+ const data_kv_resource_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'data-kv-resource.js'));
749
+ const data_transform_resource_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'data-transform-resource.js'));
750
+ const compilation_resource_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'compilation-resource.js'));
751
+ const compiler_resource_path = to_module_literal(path.join(jsgui3_html_root, 'resource', 'compiler-resource.js'));
752
+ const mixins_path = to_module_literal(path.join(jsgui3_html_root, 'control_mixins', 'mx.js'));
753
+ let gfx_require_target = 'jsgui3-gfx-core';
754
+ try {
755
+ gfx_require_target = to_module_literal(require.resolve('jsgui3-gfx-core', {paths: [jsgui3_html_root]}));
756
+ } catch (err) {
757
+ // Keep package-name fallback when direct path resolution is unavailable.
758
+ }
759
+
760
+ const shim_source = `
761
+ const jsgui = require('${html_core_path}');
762
+ jsgui.Router = require('${router_path}');
763
+ jsgui.Resource = require('${resource_path}');
764
+ jsgui.Resource_Pool = require('${resource_pool_path}');
765
+ jsgui.Resource.Data_KV = require('${data_kv_resource_path}');
766
+ jsgui.Resource.Data_Transform = require('${data_transform_resource_path}');
767
+ jsgui.Resource.Compilation = require('${compilation_resource_path}');
768
+ jsgui.Resource.Compiler = require('${compiler_resource_path}');
769
+ jsgui.gfx = require('${gfx_require_target}');
770
+ jsgui.Resource.load_compiler = (name, jsfn, options) => {
771
+ const compiler_name = name;
772
+ const compiler_fn = jsfn;
773
+ const compiler_options = options || {};
774
+ if (typeof compiler_name !== 'string' || compiler_name.length === 0) {
775
+ throw new Error('Resource.load_compiler(name, fn, options) requires a non-empty string name');
776
+ }
777
+ if (typeof compiler_fn !== 'function') {
778
+ throw new Error('Resource.load_compiler(name, fn, options) requires a function compiler implementation');
779
+ }
780
+ const compiler_resource = new jsgui.Resource.Compiler({ name: compiler_name });
781
+ compiler_resource.transform = (input, transform_options = {}) => {
782
+ const merged_options = Object.assign({}, compiler_options, transform_options);
783
+ return compiler_fn(input, merged_options);
784
+ };
785
+ jsgui.Resource.compilers = jsgui.Resource.compilers || {};
786
+ jsgui.Resource.compilers[compiler_name] = compiler_resource;
787
+ const pool = compiler_options.pool || compiler_options.resource_pool;
788
+ if (pool && typeof pool.add === 'function') {
789
+ pool.add(compiler_resource);
790
+ }
791
+ return compiler_resource;
792
+ };
793
+ jsgui.controls = jsgui.controls || {};
794
+ Object.assign(jsgui.controls, {
795
+ ${selected_control_lines.join(',\n')}
796
+ });
797
+ Object.assign(jsgui, jsgui.controls);
798
+ jsgui.mixins = jsgui.mx = require('${mixins_path}');
799
+ module.exports = jsgui;
800
+ `.trimStart();
801
+
802
+ await fs_async.writeFile(shim_file_path, shim_source, 'utf8');
803
+ return shim_file_path;
804
+ }
805
+
806
+ async write_manifest_file(manifest) {
807
+ await fs_async.mkdir(this.manifest_dir, {recursive: true});
808
+ const hash_input = JSON.stringify({
809
+ entry: manifest.entry_file_path,
810
+ selected_controls: manifest.selected_controls,
811
+ package_name: manifest.package_name
812
+ });
813
+ const hash = crypto.createHash('sha256').update(hash_input).digest('hex').slice(0, 24);
814
+ const manifest_file_path = path.join(this.manifest_dir, `jsgui3-html-control-scan-${hash}.json`);
815
+ await fs_async.writeFile(manifest_file_path, JSON.stringify(manifest, null, 2), 'utf8');
816
+ }
817
+
818
+ create_esbuild_plugin(shim_file_path) {
819
+ const package_name_regex = new RegExp(`^${escape_for_regexp(this.package_name)}$`);
820
+ return {
821
+ name: 'jsgui3-html-control-optimizer',
822
+ setup(build) {
823
+ build.onResolve({filter: package_name_regex}, () => ({path: shim_file_path}));
824
+ }
825
+ };
826
+ }
827
+ }
828
+
829
+ module.exports = JSGUI3_HTML_Control_Optimizer;