@teacss/engine 0.9.1 → 0.9.3

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 CHANGED
@@ -22,19 +22,64 @@ Every package needs Node.js 22.12.0 or later; the wasm package needs `>=22.13.0
22
22
 
23
23
  ## Engine selection
24
24
 
25
- - `TEACSS_ENGINE=auto|rust|ts` overrides everything else. An unrecognized value is ignored, and the selection result carries it as `invalidEnv`.
26
- - Otherwise the integration's `engine` option decides, and without it TeaCSS uses the TypeScript engine.
27
- - `package.json#engineCompat.defaultMode` sets what the option `"auto"` means. While it is `"opt-in"` (the alpha releases), `engine: "auto"` stays on TypeScript, and only `TEACSS_ENGINE=rust` or `auto`, or `engine: "rust"`, tries Rust. From the beta, `"auto"` tries Rust and falls back when a configuration cannot run there.
28
- - With `"auto"`, a configuration Rust cannot serve falls back to TypeScript with a machine-readable reason; with `"rust"`, the same failure throws. The fallback reasons are grouped in [docs/architecture.md](../../docs/architecture.md) §9; the complete set is the `fail(...)` calls in `src/select.ts`.
25
+ - `TEACSS_ENGINE=auto|rust|ts` overrides the `engine` option. An unrecognized value is ignored, and the selection result carries it as `invalidEnv`.
26
+ - Otherwise the integration's `engine` option decides. `selectEngineRequest` with no option returns TypeScript; what an unset `engine` means is the teacss-ts hook's call.
27
+ - `package.json#engineCompat.defaultMode` sets what the option `"auto"` means. It is `"auto"`: `engine: "auto"`, which the first-party integrations pass, tries Rust and falls back to TypeScript when the configuration cannot run there. Releases up to 0.9.1 shipped `"opt-in"`, under which the option `"auto"` stayed on TypeScript and only `TEACSS_ENGINE=rust|auto` or `engine: "rust"` tried Rust.
28
+ - With `"auto"`, a configuration Rust cannot serve falls back to TypeScript with a machine-readable reason (see [Fallback reasons](#fallback-reasons)); with `"rust"`, the teacss-ts hook prints the same fallback as a warning, and throws instead when `TEACSS_ENGINE_STRICT=1` is set.
29
29
 
30
30
  `selectEngineRequest` from `@teacss/engine/select` (which does not load the binding) stays on TypeScript in three cases: `mode: "ts"` (TypeScript chosen explicitly), `resolvedBy: "opt-in"` (the option `"auto"` while `defaultMode` is `"opt-in"`), and `reason: "engine-compat"` (the host `@teacss/core` is outside `engineCompat.core`).
31
31
 
32
+ ## Fallback reasons
33
+
34
+ A fallback prints one line per reason and path per process: `[teacss] engine: falling back to the TypeScript engine (<reason> at <path>)`. In teacss-ts, `TEACSS_ENGINE_STATS=1` prints on exit how many generators ran on each engine and which reasons fell back. `TEACSS_ENGINE_DEBUG=1` makes this package print the translated request or the first failure to stderr. The facade stops at the first item it cannot translate, so fixing one reason can reveal the next.
35
+
36
+ To keep TypeScript for any configuration, set `TEACSS_ENGINE=ts` (it covers every integration), or write `engine: "ts"` in the configuration or in the host's defaults, for example `pluginTeacss(config, { engine: "ts" })`. A fallback is not an error: the TypeScript engine produces the same CSS it always has.
37
+
38
+ `<i>` is the preset's index after nested presets are flattened and ordered by `enforce`.
39
+
40
+ | Reason | Path | What it means and why | To run on Rust |
41
+ | --- | --- | --- | --- |
42
+ | `engine-unavailable` | `@teacss/engine` | Reported by the teacss-ts hook, not by this package: importing the facade failed, usually because no platform package matches the machine or the native binding did not load. | Install the platform package for your machine; see [Troubleshooting](#troubleshooting). `TEACSS_ENGINE=rust` throws with the loader's error instead of falling back. |
43
+ | `engine-compat` | `@teacss/core` | The host `@teacss/core` is outside `engineCompat.core`, so the pair was not tested together. `TEACSS_ENGINE=rust` throws `E_ENGINE_COMPAT` instead. | Align `@teacss/core` and `@teacss/engine`; check the lockfile for a second copy of either. |
44
+ | `unknown-key` | the key | A top-level config key the facade does not know, such as a typo or an option from a newer core. Rust cannot know what it means. | Remove or correct the key. |
45
+ | `rules`, `variants`, `extractors`, `extractorDefault`, `preflights`, `preprocess`, `postprocess`, `extendTheme`, `configResolved`, `autocomplete`, `cli` | the key | The key has a non-empty value. These hold functions or matchers that only JavaScript can run, and Rust does not execute JavaScript. | Remove the key from the configuration the integration loads, or express it statically with `shortcuts`, `theme`, `safelist` or a preset option. |
46
+ | `separators` | `separators` | Separators other than the default `[":", "-"]`; Rust parses only the default. | Remove `separators`. |
47
+ | `mergeSelectors` | `mergeSelectors` | `mergeSelectors: false`; Rust always merges selectors. | Remove it. |
48
+ | `details` | `details` | `details` is set; Rust does not support it. | Remove it. |
49
+ | `sortLayers` | `sortLayers` | A `sortLayers` function. | Remove it and order layers with `layers`. |
50
+ | `content` | `content` or `content.filesystem` | `content` is not an object, or `content.filesystem` is not an array of strings. | Use an array of glob strings. |
51
+ | `content.inline` | `content.inline` | Inline content sources, which only the TypeScript engine reads. | Move the content into files matched by `content.filesystem`. |
52
+ | `envMode` | `envMode` | A value other than `"dev"` or `"build"`. | Use one of the two. |
53
+ | `unmatched` | `unmatched` | A value other than `"warn"`, `"error"` or `"off"`. | Use one of the three. |
54
+ | `outputToCssLayers` | `outputToCssLayers` | Neither a boolean nor an object. | Use `true`, `false` or `{ allLayers }`. |
55
+ | `outputToCssLayers.cssLayerName` | `outputToCssLayers.cssLayerName` | A `cssLayerName` value; Rust does not support it. | Remove it. |
56
+ | `transformers` | `transformers` or `transformers[<n>]` | A transformer other than `@teacss/transformer-compile-class` and `@teacss/directives`, or a `transformers` value that is not an array. Other transformers run JavaScript over the source. | Remove the transformer, or keep TypeScript. |
57
+ | `transformers.compile-class` | `transformers[<n>]` | The config lists its own `transformerCompileClass()` and the last Standard preset does not enable `compileClass`, or there is no Standard preset. Its options live in a closure, and the classes it registers never reach Rust. | Enable compile-class through `presetStandard({ compileClass })`, or keep TypeScript. |
58
+ | `shortcuts`, `safelist`, `blocklist`, `theme`, `layers` | the key and the item inside it | A value that is not static data: a shortcut whose expansion is not a string, or that has a regular expression or meta; a `safelist` or `blocklist` item that is not a string; a theme leaf that is not a string or number, the removed `rolePalettes` or `themes` theme key, or a `defaultMode` or mode name that is empty or holds a NUL or a lone surrogate (Standard rejects these; the TypeScript engine raises its error); a layer order that is not a number. | Replace the item with static data. |
59
+ | `preset-not-instantiated` | `presets[<n>]` | A preset that is a function, a promise or not an object. | Call the factory (`presetStandard()`, not `presetStandard`) and await it before passing it. |
60
+ | `preset-cycle` | `presets[<n>]…` | A preset that contains itself through nested `presets`. | Break the cycle. |
61
+ | `accessor` | the key | A getter or setter on the config, the defaults or a preset. Translation would run it once and the TypeScript engine again. | Use plain data properties. |
62
+ | `standard.unknown-option` | `presets[<i>].options.<key>` | A Standard option other than `modeSelectors`, `containers`, `preflight`, `variablePrefix` and `compileClass`. | Remove the option. |
63
+ | `standard.compileClass.hashFn`, `standard.compileClass.trigger` | `presets[<i>].options.compileClass.…` | A custom hash function or trigger for compiled classes. | Remove it; `classPrefix`, `alwaysHash`, `keepUnknown` and `layer` are supported. |
64
+ | `icons.collections` | `presets[<i>].options.collections.<name>` | An icon collection other than the bundled `carbon`, `crypto`, `flagpack`, `logo` and `lucide`. | Use a bundled collection, or keep TypeScript. |
65
+ | `icons.customizations`, `icons.processor` | `presets[<i>].options.…` | Icon customizations or a processor, which run JavaScript. | Remove them. |
66
+ | `role-palettes-without-entry` | `presets[<i>]` | The `@teacss/config/role-palettes` preset in a configuration that did not come from an entry stylesheet. On Rust the preset needs the entry. | Load the configuration from its entry stylesheet through `@teacss/config`. |
67
+ | `preset.name` | `presets[<i>].name` | A preset without a string `name`. | Give the preset a name. |
68
+ | `preset.rules`, `preset.variants`, `preset.extractors`, `preset.preflights`, `preset.preprocess`, `preset.postprocess`, `preset.extendTheme`, `preset.configResolved`, `preset.transformers`, `preset.autocomplete`, `preset.extractorDefault` | `presets[<i>].<key>` | A preset that is not one of the built-in presets carries a dynamic field, as for the top-level keys above. | Remove the field from the preset, or keep TypeScript. |
69
+ | `preset.content` | `presets[<i>].content` | A preset's `content` holds something other than `pipeline`, such as `filesystem` or `inline`; Rust reads content globs only from the configuration. | Move the globs to the configuration's `content.filesystem`. |
70
+ | `preset.shortcuts`, `preset.safelist`, `preset.blocklist`, `preset.theme`, `preset.layers` | `presets[<i>].<key>…` | A preset field that is not static data, as for the top-level keys above. | Replace the item with static data. |
71
+ | `preset.enforce`, `preset.layer` | `presets[<i>].<key>` | `enforce` other than `"pre"` or `"post"`, or a `layer` that is not a string. | Correct the value. |
72
+ | `declarations-unsaved` | the declaration file | A declaration file whose text came from an unsaved editor buffer. Rust reads declaration files from disk. | Save the file. |
73
+ | `declarations-unavailable` | `content.filesystem` | Finding declaration files under `content.filesystem` failed: the `@teacss/config` source scanner could not be loaded, or it threw. | Install `@teacss/config`. |
74
+
75
+ `content.pipeline` never falls back, in the configuration or in a preset, whatever it holds (RegExp, functions or `false`). It only decides which modules the integration feeds in, so it stays on the facade's `config.content` and is not sent to Rust.
76
+
32
77
  ## Compatibility
33
78
 
34
79
  | `engineCompat` field | Meaning |
35
80
  | --- | --- |
36
81
  | `core` | The `@teacss/core` versions this engine accepts, written as `>=A.B.C <X.Y.Z`; prereleases compare by SemVer, so `0.9.0-alpha.1 < 0.9.0` |
37
- | `defaultMode` | What the option `"auto"` means: `"opt-in"` keeps TypeScript, `"auto"` tries Rust |
82
+ | `defaultMode` | What the option `"auto"` means: `"opt-in"` keeps TypeScript, `"auto"` (the current value) tries Rust |
38
83
  | `oracle.teacssTs` / `oracle.commit` | The teacss-ts version and commit this release was compared against byte for byte |
39
84
 
40
85
  | `@teacss/core` | `@teacss/engine` | `engineCompat.core` | Result |
package/binding.js CHANGED
@@ -92,8 +92,8 @@ function requireNative() {
92
92
  try {
93
93
  const binding = require('@teacss/engine-android-arm64')
94
94
  const bindingPackageVersion = require('@teacss/engine-android-arm64/package.json').version
95
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
96
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
95
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
96
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
97
97
  }
98
98
  return binding
99
99
  } catch (e) {
@@ -108,8 +108,8 @@ function requireNative() {
108
108
  try {
109
109
  const binding = require('@teacss/engine-android-arm-eabi')
110
110
  const bindingPackageVersion = require('@teacss/engine-android-arm-eabi/package.json').version
111
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
112
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
111
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
112
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
113
113
  }
114
114
  return binding
115
115
  } catch (e) {
@@ -129,8 +129,8 @@ function requireNative() {
129
129
  try {
130
130
  const binding = require('@teacss/engine-win32-x64-gnu')
131
131
  const bindingPackageVersion = require('@teacss/engine-win32-x64-gnu/package.json').version
132
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
133
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
132
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
133
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
134
134
  }
135
135
  return binding
136
136
  } catch (e) {
@@ -145,8 +145,8 @@ function requireNative() {
145
145
  try {
146
146
  const binding = require('@teacss/engine-win32-x64-msvc')
147
147
  const bindingPackageVersion = require('@teacss/engine-win32-x64-msvc/package.json').version
148
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
149
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
148
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
149
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
150
150
  }
151
151
  return binding
152
152
  } catch (e) {
@@ -162,8 +162,8 @@ function requireNative() {
162
162
  try {
163
163
  const binding = require('@teacss/engine-win32-ia32-msvc')
164
164
  const bindingPackageVersion = require('@teacss/engine-win32-ia32-msvc/package.json').version
165
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
166
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
165
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
166
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
167
167
  }
168
168
  return binding
169
169
  } catch (e) {
@@ -178,8 +178,8 @@ function requireNative() {
178
178
  try {
179
179
  const binding = require('@teacss/engine-win32-arm64-msvc')
180
180
  const bindingPackageVersion = require('@teacss/engine-win32-arm64-msvc/package.json').version
181
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
182
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
181
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
182
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
183
183
  }
184
184
  return binding
185
185
  } catch (e) {
@@ -197,8 +197,8 @@ function requireNative() {
197
197
  try {
198
198
  const binding = require('@teacss/engine-darwin-universal')
199
199
  const bindingPackageVersion = require('@teacss/engine-darwin-universal/package.json').version
200
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
201
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
200
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
201
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
202
202
  }
203
203
  return binding
204
204
  } catch (e) {
@@ -213,8 +213,8 @@ function requireNative() {
213
213
  try {
214
214
  const binding = require('@teacss/engine-darwin-x64')
215
215
  const bindingPackageVersion = require('@teacss/engine-darwin-x64/package.json').version
216
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
217
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
216
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
217
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
218
218
  }
219
219
  return binding
220
220
  } catch (e) {
@@ -229,8 +229,8 @@ function requireNative() {
229
229
  try {
230
230
  const binding = require('@teacss/engine-darwin-arm64')
231
231
  const bindingPackageVersion = require('@teacss/engine-darwin-arm64/package.json').version
232
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
233
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
232
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
233
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
234
234
  }
235
235
  return binding
236
236
  } catch (e) {
@@ -249,8 +249,8 @@ function requireNative() {
249
249
  try {
250
250
  const binding = require('@teacss/engine-freebsd-x64')
251
251
  const bindingPackageVersion = require('@teacss/engine-freebsd-x64/package.json').version
252
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
253
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
252
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
253
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
254
254
  }
255
255
  return binding
256
256
  } catch (e) {
@@ -265,8 +265,8 @@ function requireNative() {
265
265
  try {
266
266
  const binding = require('@teacss/engine-freebsd-arm64')
267
267
  const bindingPackageVersion = require('@teacss/engine-freebsd-arm64/package.json').version
268
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
269
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
268
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
269
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
270
270
  }
271
271
  return binding
272
272
  } catch (e) {
@@ -286,8 +286,8 @@ function requireNative() {
286
286
  try {
287
287
  const binding = require('@teacss/engine-linux-x64-musl')
288
288
  const bindingPackageVersion = require('@teacss/engine-linux-x64-musl/package.json').version
289
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
290
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
289
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
290
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
291
291
  }
292
292
  return binding
293
293
  } catch (e) {
@@ -302,8 +302,8 @@ function requireNative() {
302
302
  try {
303
303
  const binding = require('@teacss/engine-linux-x64-gnu')
304
304
  const bindingPackageVersion = require('@teacss/engine-linux-x64-gnu/package.json').version
305
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
306
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
305
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
306
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
307
307
  }
308
308
  return binding
309
309
  } catch (e) {
@@ -320,8 +320,8 @@ function requireNative() {
320
320
  try {
321
321
  const binding = require('@teacss/engine-linux-arm64-musl')
322
322
  const bindingPackageVersion = require('@teacss/engine-linux-arm64-musl/package.json').version
323
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
324
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
323
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
324
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
325
325
  }
326
326
  return binding
327
327
  } catch (e) {
@@ -336,8 +336,8 @@ function requireNative() {
336
336
  try {
337
337
  const binding = require('@teacss/engine-linux-arm64-gnu')
338
338
  const bindingPackageVersion = require('@teacss/engine-linux-arm64-gnu/package.json').version
339
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
340
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
339
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
340
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
341
341
  }
342
342
  return binding
343
343
  } catch (e) {
@@ -354,8 +354,8 @@ function requireNative() {
354
354
  try {
355
355
  const binding = require('@teacss/engine-linux-arm-musleabihf')
356
356
  const bindingPackageVersion = require('@teacss/engine-linux-arm-musleabihf/package.json').version
357
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
358
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
357
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
358
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
359
359
  }
360
360
  return binding
361
361
  } catch (e) {
@@ -370,8 +370,8 @@ function requireNative() {
370
370
  try {
371
371
  const binding = require('@teacss/engine-linux-arm-gnueabihf')
372
372
  const bindingPackageVersion = require('@teacss/engine-linux-arm-gnueabihf/package.json').version
373
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
374
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
373
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
374
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
375
375
  }
376
376
  return binding
377
377
  } catch (e) {
@@ -388,8 +388,8 @@ function requireNative() {
388
388
  try {
389
389
  const binding = require('@teacss/engine-linux-loong64-musl')
390
390
  const bindingPackageVersion = require('@teacss/engine-linux-loong64-musl/package.json').version
391
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
392
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
391
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
392
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
393
393
  }
394
394
  return binding
395
395
  } catch (e) {
@@ -404,8 +404,8 @@ function requireNative() {
404
404
  try {
405
405
  const binding = require('@teacss/engine-linux-loong64-gnu')
406
406
  const bindingPackageVersion = require('@teacss/engine-linux-loong64-gnu/package.json').version
407
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
408
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
407
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
408
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
409
409
  }
410
410
  return binding
411
411
  } catch (e) {
@@ -422,8 +422,8 @@ function requireNative() {
422
422
  try {
423
423
  const binding = require('@teacss/engine-linux-riscv64-musl')
424
424
  const bindingPackageVersion = require('@teacss/engine-linux-riscv64-musl/package.json').version
425
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
426
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
425
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
426
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
427
427
  }
428
428
  return binding
429
429
  } catch (e) {
@@ -438,8 +438,8 @@ function requireNative() {
438
438
  try {
439
439
  const binding = require('@teacss/engine-linux-riscv64-gnu')
440
440
  const bindingPackageVersion = require('@teacss/engine-linux-riscv64-gnu/package.json').version
441
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
442
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
441
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
442
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
443
443
  }
444
444
  return binding
445
445
  } catch (e) {
@@ -455,8 +455,8 @@ function requireNative() {
455
455
  try {
456
456
  const binding = require('@teacss/engine-linux-ppc64-gnu')
457
457
  const bindingPackageVersion = require('@teacss/engine-linux-ppc64-gnu/package.json').version
458
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
459
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
458
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
459
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
460
460
  }
461
461
  return binding
462
462
  } catch (e) {
@@ -471,8 +471,8 @@ function requireNative() {
471
471
  try {
472
472
  const binding = require('@teacss/engine-linux-s390x-gnu')
473
473
  const bindingPackageVersion = require('@teacss/engine-linux-s390x-gnu/package.json').version
474
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
475
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
474
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
475
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
476
476
  }
477
477
  return binding
478
478
  } catch (e) {
@@ -491,8 +491,8 @@ function requireNative() {
491
491
  try {
492
492
  const binding = require('@teacss/engine-openharmony-arm64')
493
493
  const bindingPackageVersion = require('@teacss/engine-openharmony-arm64/package.json').version
494
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
495
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
494
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
495
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
496
496
  }
497
497
  return binding
498
498
  } catch (e) {
@@ -507,8 +507,8 @@ function requireNative() {
507
507
  try {
508
508
  const binding = require('@teacss/engine-openharmony-x64')
509
509
  const bindingPackageVersion = require('@teacss/engine-openharmony-x64/package.json').version
510
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
511
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
510
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
511
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
512
512
  }
513
513
  return binding
514
514
  } catch (e) {
@@ -523,8 +523,8 @@ function requireNative() {
523
523
  try {
524
524
  const binding = require('@teacss/engine-openharmony-arm')
525
525
  const bindingPackageVersion = require('@teacss/engine-openharmony-arm/package.json').version
526
- if (bindingPackageVersion !== '0.9.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
527
- throw new Error(`Native binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
526
+ if (bindingPackageVersion !== '0.9.3' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
527
+ throw new Error(`Native binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
528
528
  }
529
529
  return binding
530
530
  } catch (e) {
@@ -664,8 +664,8 @@ if (!nativeBinding || forceWasi) {
664
664
  if (!candidateFailed) {
665
665
  if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
666
666
  const bindingPackageVersion = require('@teacss/engine-wasm32-wasi/package.json').version
667
- if (bindingPackageVersion !== '0.9.1') {
668
- throw new Error(`WASI binding package version mismatch, expected 0.9.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
667
+ if (bindingPackageVersion !== '0.9.3') {
668
+ throw new Error(`WASI binding package version mismatch, expected 0.9.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
669
669
  }
670
670
  }
671
671
  wasiBinding = require('@teacss/engine-wasm32-wasi')
package/contract.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Mirror of docs/specs/v02.2-engine-contract.md §3 (spec-revision 11). Do not edit without bumping the spec revision.
1
+ // Mirror of docs/specs/v02.2-engine-contract.md §3 (spec-revision 13). Do not edit without bumping the spec revision.
2
2
  export interface InspectEntryRequest {
3
3
  /** 项目根(绝对路径)。相对 @source 以声明它的文件所在目录为基准。 */
4
4
  root: string;
@@ -23,5 +23,14 @@ export interface ConfigViewArgs {
23
23
  compileClassShortcuts(): unknown[];
24
24
  };
25
25
  }
26
+ /**
27
+ * The merged `content.pipeline` of the presets and the config, as `mergeContentOptions`
28
+ * (core/config.ts) builds it: `false` anywhere disables it, otherwise includes and excludes
29
+ * concatenate in source order without duplicates. The runtime builds its module filter from it.
30
+ */
31
+ export declare function mergePipeline(sources: readonly unknown[]): false | {
32
+ include?: Array<string | RegExp>;
33
+ exclude?: Array<string | RegExp>;
34
+ } | undefined;
26
35
  export declare function createConfigView({ view, presets, merged, transformers, host, fallback, engine }: ConfigViewArgs): Record<string, unknown>;
27
36
  //# sourceMappingURL=config-view.d.ts.map
@@ -38,6 +38,40 @@ const RESOLVED_KEYS = Object.freeze([
38
38
  "content",
39
39
  "transformers",
40
40
  ]);
41
+ const ownValue = (source, key) => (source !== null && typeof source === "object" && Object.hasOwn(source, key) ? source[key] : undefined);
42
+ const flattenPatterns = (patterns) => [...new Set(patterns.flatMap((pattern) => (Array.isArray(pattern) ? pattern : pattern ? [pattern] : [])))];
43
+ /**
44
+ * The merged `content.pipeline` of the presets and the config, as `mergeContentOptions`
45
+ * (core/config.ts) builds it: `false` anywhere disables it, otherwise includes and excludes
46
+ * concatenate in source order without duplicates. The runtime builds its module filter from it.
47
+ */
48
+ export function mergePipeline(sources) {
49
+ const includes = [];
50
+ const excludes = [];
51
+ let configured = false;
52
+ for (const content of new Set(sources.map((source) => ownValue(source, "content")).filter((content) => content !== undefined && content !== null))) {
53
+ const pipeline = ownValue(content, "pipeline");
54
+ if (pipeline === false)
55
+ return false;
56
+ if (pipeline === undefined)
57
+ continue;
58
+ configured = true;
59
+ const include = pipeline && ownValue(pipeline, "include");
60
+ const exclude = pipeline && ownValue(pipeline, "exclude");
61
+ if (include)
62
+ includes.push(include);
63
+ if (exclude)
64
+ excludes.push(exclude);
65
+ }
66
+ if (!configured)
67
+ return undefined;
68
+ const merged = {};
69
+ if (includes.length)
70
+ merged.include = flattenPatterns(includes);
71
+ if (excludes.length)
72
+ merged.exclude = flattenPatterns(excludes);
73
+ return merged;
74
+ }
41
75
  export function createConfigView({ view, presets, merged, transformers, host, fallback, engine }) {
42
76
  const config = {};
43
77
  const data = (key, value) => Object.defineProperty(config, key, { value, enumerable: true, configurable: true, writable: true });
@@ -54,7 +88,10 @@ export function createConfigView({ view, presets, merged, transformers, host, fa
54
88
  const extractors = host.core ? [host.core.extractorSplit] : [{ name: view.extractors?.[0]?.name ?? "@teacss/core/extractor-split" }];
55
89
  const fields = {
56
90
  presets: () => data("presets", presets),
57
- content: () => data("content", view.content ?? {}),
91
+ content: () => {
92
+ const pipeline = mergePipeline([...presets, merged]);
93
+ data("content", pipeline === undefined ? (view.content ?? {}) : { pipeline, ...view.content });
94
+ },
58
95
  transformers: () => data("transformers", transformers),
59
96
  extractors: () => data("extractors", extractors),
60
97
  extractorDefault: () => data("extractorDefault", extractors[0]),
package/dist/errors.d.ts CHANGED
@@ -6,6 +6,8 @@ export declare class TeacssEngineError extends Error {
6
6
  }
7
7
  /** v02.9 §2.1: the member needs `@teacss/core`, which is not installed. */
8
8
  export declare function tsRequiredError(member: string): TeacssEngineError;
9
+ /** The generator was disposed; the native Engine reports the same code after its own `dispose()`. */
10
+ export declare function disposedError(member: string): TeacssEngineError;
9
11
  /** v02.9 §3.3: the member needs the TypeScript engine, which has not been materialized. */
10
12
  export declare function tsNotReadyError(member: string): TeacssEngineError;
11
13
  //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js CHANGED
@@ -11,6 +11,10 @@ export class TeacssEngineError extends Error {
11
11
  export function tsRequiredError(member) {
12
12
  return new TeacssEngineError("E_TS_REQUIRED", `[@teacss/engine] ${member} requires @teacss/core (optional peer) to be installed`, { member });
13
13
  }
14
+ /** The generator was disposed; the native Engine reports the same code after its own `dispose()`. */
15
+ export function disposedError(member) {
16
+ return new TeacssEngineError("E_DISPOSED", `[@teacss/engine] ${member} called after dispose()`);
17
+ }
14
18
  /** v02.9 §3.3: the member needs the TypeScript engine, which has not been materialized. */
15
19
  export function tsNotReadyError(member) {
16
20
  return new TeacssEngineError("E_TS_REQUIRED", `[@teacss/engine] ${member} needs the TypeScript engine; await generator[Symbol.for('@teacss/engine/materialize')]() first or set engine: 'ts'`, { member });
@@ -19,7 +19,8 @@ export declare function engineStats(): {
19
19
  };
20
20
  /**
21
21
  * v02.9 §9: each reason and path prints once per process; the counter grows on every call. A
22
- * host `report(reason, path, level)` (the hook's consola) replaces the stderr line.
22
+ * host `report(reason, path, level)` (core's `reportFallback`) gets every call and does both
23
+ * itself, so the counter is not touched here.
23
24
  */
24
25
  export declare function reportFallback(reason: string, path: string, host?: FacadeHost, level?: "info" | "warn"): void;
25
26
  export declare class FacadeGenerator {
@@ -121,6 +122,13 @@ export declare class FacadeGenerator {
121
122
  userConfig: Record<string, unknown>;
122
123
  version: number;
123
124
  }): boolean;
125
+ /**
126
+ * Releases the Engine this generator owns; later `generate`, `parseToken`, `applyExtractors`,
127
+ * `prepareConfig` and `commitConfig` throw `E_DISPOSED`, and a second call does nothing. A
128
+ * candidate committed into another generator handed its Engine over and leaves it alone.
129
+ */
130
+ dispose(): void;
131
+ [Symbol.dispose](): void;
124
132
  }
125
133
  /**
126
134
  * v02.9 §3.1: a Rust facade, or the reason the configuration needs TypeScript. Errors from
@@ -140,7 +148,7 @@ export interface FacadeHost {
140
148
  /** core/utils/warn.ts 的 warnOnce;传入时 shortcut 警告与 TS 引擎共用一个去重集合。 */
141
149
  warnOnce?: (message: string) => void;
142
150
  } | undefined;
143
- /** 挂钩注入的日志出口;缺省时写 process.stderr。 */
151
+ /** 挂钩注入的回退出口(core 的 reportFallback):每次回退都调用,由它计数并按 reason 与 path 去重打印;缺省时 facade 自己计数并写 process.stderr。 */
144
152
  report?: (reason: string, path: string, level: "info" | "warn") => void;
145
153
  }
146
154
  export type FacadeCreateResult = {
package/dist/generator.js CHANGED
@@ -6,7 +6,7 @@ import { Engine } from "../binding.js";
6
6
  import { createConfigView } from "./config-view.js";
7
7
  import { ENGINE_INSTANCE, ENGINE_MATERIALIZE, ENGINE_PACKAGE_VERSION, ENGINE_STATS } from "./constants.js";
8
8
  import { BoundedSet, BoundedTokenCache, CountableSet, copyCountableSet, expandInputGroups, isCountableSet, MAX_RUNTIME_CACHE_ENTRIES } from "./countable-set.js";
9
- import { tsNotReadyError } from "./errors.js";
9
+ import { disposedError, tsNotReadyError } from "./errors.js";
10
10
  import { buildMatched, createFacadeResult, describeUnmatched, fallbackIsTokenShaped, formatUnmatchedError, shortcutWarningMessages, splitMatched, toTuple } from "./result.js";
11
11
  import { mergeDefinedConfig, translateConfig } from "./select.js";
12
12
  import { createCompileClassTransformer } from "./transformer.js";
@@ -232,19 +232,21 @@ export function engineStats() {
232
232
  const reported = new Set();
233
233
  /**
234
234
  * v02.9 §9: each reason and path prints once per process; the counter grows on every call. A
235
- * host `report(reason, path, level)` (the hook's consola) replaces the stderr line.
235
+ * host `report(reason, path, level)` (core's `reportFallback`) gets every call and does both
236
+ * itself, so the counter is not touched here.
236
237
  */
237
238
  export function reportFallback(reason, path, host = {}, level = "info") {
239
+ if (typeof host.report === "function") {
240
+ host.report(reason, path, level);
241
+ return;
242
+ }
238
243
  const stats = engineStats();
239
244
  stats.fallbacks[reason] = (stats.fallbacks[reason] ?? 0) + 1;
240
245
  const key = `${reason}\0${path}`;
241
246
  if (reported.has(key))
242
247
  return;
243
248
  reported.add(key);
244
- if (typeof host.report === "function")
245
- host.report(reason, path, level);
246
- else
247
- process.stderr.write(`[teacss] engine: falling back to the TypeScript engine (${reason} at ${path})\n`);
249
+ process.stderr.write(`[teacss] engine: falling back to the TypeScript engine (${reason} at ${path})\n`);
248
250
  }
249
251
  /** array-internal.ts:7-13: ownership is rechecked before each read. */
250
252
  function* ownArrayValues(array) {
@@ -301,7 +303,10 @@ export class FacadeGenerator {
301
303
  disposeEngine(held.engine);
302
304
  });
303
305
  #backend;
306
+ #disposed = false;
304
307
  #held = null;
308
+ /** A TS candidate's own commit, run when another generator commits it. */
309
+ #commitTs = null;
305
310
  #host;
306
311
  #tsFallback;
307
312
  /** The compile-class transformer: created once and handed to every candidate (v02.9 §5.2). */
@@ -410,6 +415,7 @@ export class FacadeGenerator {
410
415
  * tokens; a counted set adds each token's occurrences.
411
416
  */
412
417
  async applyExtractors(code, id, extracted = new Set()) {
418
+ this.#assertLive("applyExtractors");
413
419
  await Promise.resolve();
414
420
  const backend = this.#backend;
415
421
  if (backend.kind === "ts")
@@ -447,6 +453,7 @@ export class FacadeGenerator {
447
453
  * runs `applyExtractors` on it, generator.ts:2378).
448
454
  */
449
455
  async generate(input, options = {}) {
456
+ this.#assertLive("generate");
450
457
  const backend = this.#backend;
451
458
  if (backend.kind === "ts") {
452
459
  // A rule may commit a new configuration through this facade while the TS backend runs; the
@@ -610,6 +617,7 @@ export class FacadeGenerator {
610
617
  * already cached reads back as `null`. With an `alias` nothing is cached.
611
618
  */
612
619
  async parseToken(raw, alias) {
620
+ this.#assertLive("parseToken");
613
621
  await Promise.resolve();
614
622
  const backend = this.#backend;
615
623
  if (backend.kind === "ts")
@@ -727,6 +735,7 @@ export class FacadeGenerator {
727
735
  * one. A configuration Rust cannot serve gets a TypeScript candidate.
728
736
  */
729
737
  async prepareConfig(userConfig, defaults) {
738
+ this.#assertLive("prepareConfig");
730
739
  await Promise.resolve();
731
740
  const version = ++this.#configVersion;
732
741
  const nextDefaults = defaults ?? this.defaults;
@@ -741,13 +750,14 @@ export class FacadeGenerator {
741
750
  else {
742
751
  this.#reportFallback(translated);
743
752
  const ts = await this.#tsFallback.create(userConfig, nextDefaults);
744
- candidate = new FacadeGenerator({ ts, userConfig, defaults: nextDefaults, host: this.#host });
753
+ candidate = new FacadeGenerator({ ts: ts.generator, userConfig, defaults: nextDefaults, host: this.#host });
754
+ candidate.#commitTs = ts.commit;
745
755
  }
746
756
  const prepared = Object.freeze({ config: candidate.config, defaults: nextDefaults, generator: candidate, userConfig, version });
747
757
  this.#prepared.add(prepared);
748
758
  const held = candidate.#backend.kind === "rust" ? { kind: "rust", engine: candidate.#backend.engine, committed: false } : { kind: "ts", committed: false };
749
759
  candidate.#held = held;
750
- FacadeGenerator.#registry.register(candidate, held);
760
+ FacadeGenerator.#registry.register(candidate, held, candidate);
751
761
  return prepared;
752
762
  }
753
763
  /**
@@ -755,6 +765,7 @@ export class FacadeGenerator {
755
765
  * superseded Rust candidate is disposed; so is the previous Engine once it is replaced.
756
766
  */
757
767
  commitConfig(prepared) {
768
+ this.#assertLive("commitConfig");
758
769
  if (!this.#prepared.delete(prepared))
759
770
  return false;
760
771
  const candidate = prepared.generator;
@@ -764,11 +775,18 @@ export class FacadeGenerator {
764
775
  }
765
776
  if (candidate.config !== prepared.config)
766
777
  return false;
778
+ let next = candidate.#backend;
779
+ if (candidate.#commitTs) {
780
+ const committed = candidate.#commitTs();
781
+ if (!committed)
782
+ return false;
783
+ next = { kind: "ts", generator: committed };
784
+ }
767
785
  const previous = this.#backend;
768
786
  this.defaults = prepared.defaults;
769
787
  this.userConfig = prepared.userConfig;
770
788
  this.#clearRuntimeCaches();
771
- this.#backend = candidate.#backend;
789
+ this.#backend = next;
772
790
  if (candidate.#held)
773
791
  candidate.#held.committed = true;
774
792
  this.#tsFallback.invalidate(prepared.userConfig, prepared.defaults);
@@ -777,6 +795,30 @@ export class FacadeGenerator {
777
795
  this.#dispatchConfig(this.config);
778
796
  return true;
779
797
  }
798
+ /**
799
+ * Releases the Engine this generator owns; later `generate`, `parseToken`, `applyExtractors`,
800
+ * `prepareConfig` and `commitConfig` throw `E_DISPOSED`, and a second call does nothing. A
801
+ * candidate committed into another generator handed its Engine over and leaves it alone.
802
+ */
803
+ dispose() {
804
+ if (this.#disposed)
805
+ return;
806
+ this.#disposed = true;
807
+ FacadeGenerator.#registry.unregister(this);
808
+ const backend = this.#backend;
809
+ if (backend.kind !== "rust")
810
+ return;
811
+ const handedOver = this.#held?.committed === true && this.#held.engine === backend.engine;
812
+ if (!handedOver)
813
+ disposeEngine(backend.engine);
814
+ }
815
+ [Symbol.dispose]() {
816
+ this.dispose();
817
+ }
818
+ #assertLive(member) {
819
+ if (this.#disposed)
820
+ throw disposedError(member);
821
+ }
780
822
  /** An explicit `engine: "rust"` asked for Rust, so its fallback is a warning. */
781
823
  #reportFallback(failure) {
782
824
  const explicit = this.defaults?.engine === "rust" || this.userConfig?.engine === "rust";
package/dist/select.d.ts CHANGED
@@ -54,7 +54,7 @@ export declare function pick(obj: Record<string, unknown>, keys: Iterable<string
54
54
  export declare function translateTaggedPreset(preset: object, path: string, ctx: {
55
55
  hasEntry: boolean;
56
56
  }): Success<object | null> | Failure | undefined;
57
- /** Keys of an unmarked preset whose non-empty value means fallback. */
57
+ /** Keys of an unmarked preset whose non-empty value means fallback; `content` is checked by `hasContentBeyondPipeline`. */
58
58
  export declare const PRESET_DYNAMIC_KEYS: readonly string[];
59
59
  /**
60
60
  * v02.9 §4.5: the static shortcut forms as `[name, expansion]` pairs. A shortcut meta (`[n, e, meta]`)
@@ -88,6 +88,14 @@ export declare const ALLOWED_TRANSFORMER_NAMES: Set<string>;
88
88
  * checked per call (v02.9 §6.3), so none are read here.
89
89
  */
90
90
  export declare function translateTransformers(list: unknown): Success<object[]> | Failure;
91
+ /**
92
+ * A config's own `transformerCompileClass(options)` keeps its options in a closure the facade
93
+ * cannot read, and it registers classes by pushing onto `config.shortcuts`, which never reaches
94
+ * the Engine. It is harmless only when the last Standard preset enables compileClass: the facade's
95
+ * Rust transformer then comes first and `uniqueNamed` drops it, as the oracle drops it after the
96
+ * preset's own. Otherwise it would run and its classes would generate no CSS, so it falls back.
97
+ */
98
+ export declare function hostCompileClass(transformers: readonly object[], presetSpecs: readonly object[]): Failure | undefined;
91
99
  export type EntryProvenance = {
92
100
  base: string;
93
101
  entryFile: string | undefined;
package/dist/select.js CHANGED
@@ -223,11 +223,23 @@ export function translateTaggedPreset(preset, path, ctx) {
223
223
  return ctx.hasEntry ? ok(null) : fail("role-palettes-without-entry", path);
224
224
  return undefined;
225
225
  }
226
- /** Keys of an unmarked preset whose non-empty value means fallback. */
227
- export const PRESET_DYNAMIC_KEYS = Object.freeze(["rules", "variants", "extractors", "preflights", "preprocess", "postprocess", "extendTheme", "configResolved", "transformers", "autocomplete", "extractorDefault", "content"]);
226
+ /** Keys of an unmarked preset whose non-empty value means fallback; `content` is checked by `hasContentBeyondPipeline`. */
227
+ export const PRESET_DYNAMIC_KEYS = Object.freeze(["rules", "variants", "extractors", "preflights", "preprocess", "postprocess", "extendTheme", "configResolved", "transformers", "autocomplete", "extractorDefault"]);
228
228
  const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
229
229
  /** Values `undefined` are left out. */
230
230
  const defined = (entries) => Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== undefined));
231
+ /**
232
+ * `content.pipeline` only filters the modules an integration feeds in; generation never reads it
233
+ * (v02.2 §4), so it stays in the config view and never reaches Rust. Any other content field of a
234
+ * preset is not translated.
235
+ */
236
+ function hasContentBeyondPipeline(content) {
237
+ if (!isNonEmpty(content))
238
+ return false;
239
+ if (!isPlainObject(content))
240
+ return true;
241
+ return Object.keys(content).some((key) => key !== "pipeline" && isNonEmpty(content[key]));
242
+ }
231
243
  /**
232
244
  * v02.9 §4.5: the static shortcut forms as `[name, expansion]` pairs. A shortcut meta (`[n, e, meta]`)
233
245
  * has no place in `StaticPreset.shortcuts`, so any meta key falls back.
@@ -282,6 +294,26 @@ export function translateShortcuts(value, path, reasonOnFail) {
282
294
  }
283
295
  return ok(pairs.length > 0 ? pairs : undefined);
284
296
  }
297
+ /** context.ts `isContextName` behind its falsy check: no NUL and no lone surrogate. */
298
+ function isContextName(value) {
299
+ if (!value)
300
+ return false;
301
+ if (typeof value !== "string")
302
+ return true;
303
+ for (let index = 0; index < value.length; index++) {
304
+ const code = value.charCodeAt(index);
305
+ if (code === 0)
306
+ return false;
307
+ if (code >= 0xd800 && code <= 0xdbff) {
308
+ const next = value.charCodeAt(++index);
309
+ if (!(next >= 0xdc00 && next <= 0xdfff))
310
+ return false;
311
+ }
312
+ else if (code >= 0xdc00 && code <= 0xdfff)
313
+ return false;
314
+ }
315
+ return true;
316
+ }
285
317
  /**
286
318
  * v02.9 §4.5: a theme as dotted keys to string leaves (numbers stringified); empty objects vanish.
287
319
  *
@@ -299,6 +331,16 @@ export function flattenTheme(theme, path, reasonOnFail) {
299
331
  if (isPlainObject(values) && Object.hasOwn(values, "rolePalettes"))
300
332
  return fail(reasonOnFail, `${path}.modes.${mode}.rolePalettes`);
301
333
  }
334
+ // Standard's `validateContextNames` (context.ts) throws on these. Flattening drops an empty
335
+ // object and the binding replaces lone surrogates, so Rust cannot see them.
336
+ if (Object.hasOwn(theme, "themes"))
337
+ return fail(reasonOnFail, `${path}.themes`);
338
+ if (Object.hasOwn(theme, "defaultMode") && !isContextName(theme.defaultMode))
339
+ return fail(reasonOnFail, `${path}.defaultMode`);
340
+ for (const [mode] of modes) {
341
+ if (!isContextName(mode))
342
+ return fail(reasonOnFail, `${path}.modes.${mode}`);
343
+ }
302
344
  const flat = {};
303
345
  const visit = (object, prefix) => {
304
346
  for (const [key, value] of Object.entries(object)) {
@@ -354,6 +396,8 @@ export function translateStaticPreset(preset, path) {
354
396
  if (isNonEmpty(preset[key]))
355
397
  return fail(`preset.${key}`, `${path}.${key}`);
356
398
  }
399
+ if (hasContentBeyondPipeline(preset.content))
400
+ return fail("preset.content", `${path}.content`);
357
401
  const shortcuts = translateShortcuts(preset.shortcuts, `${path}.shortcuts`, "preset.shortcuts");
358
402
  if (!shortcuts.ok)
359
403
  return shortcuts;
@@ -401,8 +445,6 @@ export function translateInline(merged) {
401
445
  if (merged.content !== undefined) {
402
446
  if (!isPlainObject(merged.content))
403
447
  return fail("content", "content");
404
- if (Object.hasOwn(merged.content, "pipeline") && merged.content.pipeline !== undefined)
405
- return fail("content.pipeline", "content.pipeline");
406
448
  if (isNonEmpty(merged.content.inline))
407
449
  return fail("content.inline", "content.inline");
408
450
  const filesystem = merged.content.filesystem;
@@ -436,6 +478,22 @@ export function translateTransformers(list) {
436
478
  }
437
479
  return ok(kept);
438
480
  }
481
+ /**
482
+ * A config's own `transformerCompileClass(options)` keeps its options in a closure the facade
483
+ * cannot read, and it registers classes by pushing onto `config.shortcuts`, which never reaches
484
+ * the Engine. It is harmless only when the last Standard preset enables compileClass: the facade's
485
+ * Rust transformer then comes first and `uniqueNamed` drops it, as the oracle drops it after the
486
+ * preset's own. Otherwise it would run and its classes would generate no CSS, so it falls back.
487
+ */
488
+ export function hostCompileClass(transformers, presetSpecs) {
489
+ const index = transformers.findIndex((transformer) => transformer.name === "@teacss/transformer-compile-class");
490
+ if (index < 0)
491
+ return undefined;
492
+ const standard = presetSpecs.findLast((spec) => spec.kind === "standard");
493
+ if (standard && standard.options.compileClass !== false)
494
+ return undefined;
495
+ return fail("transformers.compile-class", `transformers[${index}]`);
496
+ }
439
497
  export const scanners = {
440
498
  load: null,
441
499
  };
@@ -551,6 +609,9 @@ async function translate(userConfig, defaults) {
551
609
  const transformers = translateTransformers(merged.transformers);
552
610
  if (!transformers.ok)
553
611
  return transformers;
612
+ const compileClass = hostCompileClass(transformers.value, presetSpecs);
613
+ if (compileClass)
614
+ return compileClass;
554
615
  const inline = translateInline(merged);
555
616
  if (!inline.ok)
556
617
  return inline;
@@ -16,7 +16,16 @@ export interface TsFallback {
16
16
  invalidate(userConfig: object, defaults: object): void;
17
17
  generator(member?: string): Promise<TsGenerator>;
18
18
  peek(): TsGenerator | null;
19
- create(userConfig: object, defaults: object): Promise<TsGenerator>;
19
+ create(userConfig: object, defaults: object): Promise<TsCandidate>;
20
+ }
21
+ /**
22
+ * A prepared TS configuration. `commit` runs the base generator's own `commitConfig`, which
23
+ * re-reads in-place edits of the prepared config (generator.ts:2036-2063), and returns the
24
+ * generator that now serves it, or `null` when the TS generator refused the commit.
25
+ */
26
+ export interface TsCandidate {
27
+ generator: TsGenerator;
28
+ commit(): TsGenerator | null;
20
29
  }
21
30
  /**
22
31
  * `engine: "ts"` goes into the defaults, which take precedence over `config.engine`, so the TS
@@ -70,8 +70,9 @@ export function createTsFallback(host, committed) {
70
70
  // translate it, so `createGenerator` resolves to TS and creates no Engine.
71
71
  const base = await createTs(binding, typeof binding.createTsGenerator === "function" ? {} : userConfig, defaults);
72
72
  if (ENGINE_INSTANCE in Object(base) || typeof base?.prepareConfig !== "function")
73
- return base;
74
- return (await base.prepareConfig(userConfig, tsDefaults)).generator;
73
+ return { generator: base, commit: () => base };
74
+ const prepared = await base.prepareConfig(userConfig, tsDefaults);
75
+ return { generator: prepared.generator, commit: () => (base.commitConfig(prepared) ? base : null) };
75
76
  },
76
77
  };
77
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teacss/engine",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "In-process Rust engine for TeaCSS 0.9 (napi-rs binding and 0.8.x-compatible facade)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -65,21 +65,21 @@
65
65
  },
66
66
  "engineCompat": {
67
67
  "core": ">=0.8.5 <0.10.0",
68
- "defaultMode": "opt-in",
68
+ "defaultMode": "auto",
69
69
  "oracle": {
70
70
  "teacssTs": "0.8.5",
71
71
  "commit": "786b4d40"
72
72
  }
73
73
  },
74
74
  "optionalDependencies": {
75
- "@teacss/engine-darwin-arm64": "0.9.1",
76
- "@teacss/engine-darwin-x64": "0.9.1",
77
- "@teacss/engine-linux-arm64-gnu": "0.9.1",
78
- "@teacss/engine-linux-arm64-musl": "0.9.1",
79
- "@teacss/engine-linux-x64-gnu": "0.9.1",
80
- "@teacss/engine-linux-x64-musl": "0.9.1",
81
- "@teacss/engine-win32-arm64-msvc": "0.9.1",
82
- "@teacss/engine-win32-x64-msvc": "0.9.1"
75
+ "@teacss/engine-darwin-arm64": "0.9.3",
76
+ "@teacss/engine-darwin-x64": "0.9.3",
77
+ "@teacss/engine-linux-arm64-gnu": "0.9.3",
78
+ "@teacss/engine-linux-arm64-musl": "0.9.3",
79
+ "@teacss/engine-linux-x64-gnu": "0.9.3",
80
+ "@teacss/engine-linux-x64-musl": "0.9.3",
81
+ "@teacss/engine-win32-arm64-msvc": "0.9.3",
82
+ "@teacss/engine-win32-x64-msvc": "0.9.3"
83
83
  },
84
84
  "peerDependencies": {
85
85
  "@teacss/config": ">=0.8.5 <0.10.0 || >=0.9.0-alpha.0 <0.10.0",