@@ -39,15 +39,23 @@ pub enum DetectionResult {
3939 detection : WebAppDetection ,
4040 /// The firmware's web engine (for rendering the compat column).
4141 engine : Option < WebEngine > ,
42- /// Whether the firmware's engine supports the app's ES level.
42+ /// Whether the firmware's engine supports the app's ES **syntax** level
43+ /// (gating — syntax can't be polyfilled).
4344 es : CompatVerdict ,
45+ /// Whether the firmware's engine natively provides the runtime APIs the
46+ /// app uses (advisory — APIs can be polyfilled).
47+ api : CompatVerdict ,
4448 } ,
4549 Service {
4650 detection : ServiceRuntimeDetection ,
47- /// The firmware's Node.js version — informational only. There is no
48- /// compat verdict for services: `engines.node` isn't trusted and webOS
49- /// services carry no other reliable runtime requirement.
51+ /// The firmware's Node.js version.
5052 available_node : Option < Version > ,
53+ /// Whether the firmware's Node.js supports the service's ES **syntax**
54+ /// level (gating — derived from the code, not `engines.node`).
55+ node : CompatVerdict ,
56+ /// Whether the firmware's Node.js natively provides the runtime APIs the
57+ /// service uses (advisory).
58+ api : CompatVerdict ,
5159 } ,
5260}
5361
@@ -60,19 +68,27 @@ pub enum CompatVerdict {
6068}
6169
6270impl DetectionResult {
63- /// The compatibility verdict for this component on this firmware, if it has
64- /// one. Services are informational only (no verdict ).
65- pub fn verdict ( & self ) -> Option < & CompatVerdict > {
71+ /// The gating compatibility verdict for this component on this firmware
72+ /// (web app: ES syntax vs engine; service: ES syntax vs Node.js ).
73+ pub fn verdict ( & self ) -> & CompatVerdict {
6674 match self {
67- DetectionResult :: WebApp { es, .. } => Some ( es ) ,
68- DetectionResult :: Service { .. } => None ,
75+ DetectionResult :: WebApp { es, .. } => es ,
76+ DetectionResult :: Service { node , .. } => node ,
6977 }
7078 }
7179
72- /// Whether this component is definitively incompatible (a `Fail` verdict);
73- /// `Unknown`/no-verdict is not treated as incompatible.
80+ /// The advisory runtime-API verdict — never gates compatibility.
81+ pub fn api_advisory ( & self ) -> & CompatVerdict {
82+ match self {
83+ DetectionResult :: WebApp { api, .. } => api,
84+ DetectionResult :: Service { api, .. } => api,
85+ }
86+ }
87+
88+ /// Whether this component is definitively incompatible (a gating `Fail`);
89+ /// `Unknown` and the advisory API verdict never count.
7490 pub fn is_incompatible ( & self ) -> bool {
75- matches ! ( self . verdict( ) , Some ( CompatVerdict :: Fail { .. } ) )
91+ matches ! ( self . verdict( ) , CompatVerdict :: Fail { .. } )
7692 }
7793}
7894
@@ -134,11 +150,17 @@ impl VerifyForFirmware for Package {
134150
135151fn web_detection ( app : & Component < AppInfo > , engine : Option < & WebEngine > ) -> Option < DetectionResult > {
136152 let detection = app. info . web . clone ( ) ?;
137- let es = web_verdict ( detection. es_level , engine) ;
153+ let es = web_verdict ( detection. es_level , engine, "app uses" ) ;
154+ // Advisory: highest ES level implied by the runtime APIs used.
155+ let api_level = detection. es_apis . iter ( ) . map ( |a| a. level ) . max ( ) ;
156+ let api = web_verdict ( api_level, engine, "app calls APIs from" ) . demote_reason (
157+ "may need polyfills" ,
158+ ) ;
138159 return Some ( DetectionResult :: WebApp {
139160 detection,
140161 engine : engine. cloned ( ) ,
141162 es,
163+ api,
142164 } ) ;
143165}
144166
@@ -147,34 +169,81 @@ fn service_detection(
147169 node : Option < & Version > ,
148170) -> Option < DetectionResult > {
149171 let detection = svc. info . runtime . clone ( ) ?;
172+ let node_verdict = service_verdict ( detection. es_level , node, "service uses" ) ;
173+ let api_level = detection. es_apis . iter ( ) . map ( |a| a. level ) . max ( ) ;
174+ let api = service_verdict ( api_level, node, "service calls APIs from" )
175+ . demote_reason ( "may need polyfills" ) ;
150176 return Some ( DetectionResult :: Service {
151177 detection,
152178 available_node : node. cloned ( ) ,
179+ node : node_verdict,
180+ api,
153181 } ) ;
154182}
155183
156- /// Does the firmware's web engine support the app's required ES level?
157- fn web_verdict ( es_level : Option < EsLevel > , engine : Option < & WebEngine > ) -> CompatVerdict {
184+ /// Whether the firmware's web engine supports the given ES level.
185+ fn web_verdict ( es_level : Option < EsLevel > , engine : Option < & WebEngine > , verb : & str ) -> CompatVerdict {
158186 let Some ( es_level) = es_level else {
159187 return CompatVerdict :: Unknown ;
160188 } ;
161- let fw_max = match engine {
162- Some ( WebEngine :: Chromium ( v) ) => EsLevel :: from_chromium_major ( v. major as u32 ) ,
163- // The LG WebKit port (537.x) predates reliable ES2015 support.
164- Some ( WebEngine :: WebKit ( _) ) => EsLevel :: Es5 ,
165- None => return CompatVerdict :: Unknown ,
189+ let Some ( engine) = engine else {
190+ return CompatVerdict :: Unknown ;
166191 } ;
192+ let fw_max = engine_max_es ( engine) ;
167193 if es_level <= fw_max {
168194 CompatVerdict :: Ok
169195 } else {
170196 CompatVerdict :: Fail {
171197 reason : format ! (
172- "app uses {}, but {} supports up to {}" ,
198+ "{verb} {}, but {} supports up to {}" ,
173199 es_level. label( ) ,
174- engine. map ( |e| e . label( ) ) . unwrap_or_default ( ) ,
200+ engine. label( ) ,
175201 fw_max. label( )
176202 ) ,
177203 }
178204 }
179205}
180206
207+ /// Whether the firmware's Node.js supports the given ES level.
208+ fn service_verdict ( es_level : Option < EsLevel > , node : Option < & Version > , verb : & str ) -> CompatVerdict {
209+ let Some ( es_level) = es_level else {
210+ return CompatVerdict :: Unknown ;
211+ } ;
212+ let Some ( node) = node else {
213+ return CompatVerdict :: Unknown ;
214+ } ;
215+ let ( maj, min) = es_level. min_node_version ( ) ;
216+ if node. major > maj || ( node. major == maj && node. minor >= min) {
217+ CompatVerdict :: Ok
218+ } else {
219+ CompatVerdict :: Fail {
220+ reason : format ! (
221+ "{verb} {}, which needs Node.js {maj}.{min}, but firmware ships {node}" ,
222+ es_level. label( ) ,
223+ ) ,
224+ }
225+ }
226+ }
227+
228+ /// The highest ES level a firmware's web engine supports.
229+ pub fn engine_max_es ( engine : & WebEngine ) -> EsLevel {
230+ match engine {
231+ WebEngine :: Chromium ( v) => EsLevel :: from_chromium_major ( v. major as u32 ) ,
232+ // The LG WebKit port (537.x) predates reliable ES2015 support.
233+ WebEngine :: WebKit ( _) => EsLevel :: Es5 ,
234+ }
235+ }
236+
237+ impl CompatVerdict {
238+ /// Rewrite a `Fail` reason's lead-in so the advisory reads as a polyfill
239+ /// note rather than a hard failure. No-op for `Ok`/`Unknown`.
240+ fn demote_reason ( self , note : & str ) -> CompatVerdict {
241+ match self {
242+ CompatVerdict :: Fail { reason } => CompatVerdict :: Fail {
243+ reason : format ! ( "{reason} ({note})" ) ,
244+ } ,
245+ other => other,
246+ }
247+ }
248+ }
249+
0 commit comments