@rokkit/themes 1.1.15 → 1.1.16

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/build.mjs CHANGED
@@ -178,16 +178,53 @@ function splitTopLevelSelectors(text) {
178
178
  return parts
179
179
  }
180
180
 
181
+ // ─── Regression guard: no @apply may survive into dist ────────────────────────
182
+
183
+ /**
184
+ * Strip /* … *​/ block comments so the scan matches real CSS, not prose
185
+ * (some files document the @apply pitfall in comments). Mirrors the
186
+ * `stripComments` helper in spec/coverage.spec.js.
187
+ */
188
+ const stripComments = (s) => s.replace(/\/\*[\s\S]*?\*\//g, '')
189
+
190
+ /**
191
+ * Fail the build if any real `@apply` directive survived into a dist file.
192
+ *
193
+ * A leftover `@apply` means a utility didn't resolve (e.g. a named-token
194
+ * `/opacity` shortcut, which UnoCSS can't expand) and would ship raw to
195
+ * consumers — triggering `[lightningcss minify] Unknown at rule: @apply`
196
+ * and rendering nothing. This is the recurrence guard for issue #135.
197
+ */
198
+ function assertNoApply(outputName) {
199
+ const content = readFileSync(join(distDir, outputName), 'utf-8') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — outputName is derived from a hardcoded string array, not user input
200
+ const lines = stripComments(content).split('\n')
201
+ const offending = []
202
+ lines.forEach((line, i) => {
203
+ if (/@apply\b/.test(line)) offending.push(` dist/${outputName}:${i + 1} ${line.trim()}`)
204
+ })
205
+ if (offending.length > 0) {
206
+ throw new Error(
207
+ `Unresolved @apply leaked into dist/${outputName} (issue #135 recurrence).\n` +
208
+ `These utilities did not resolve during build — rewrite them to raw CSS ` +
209
+ `(e.g. color-mix for named-token /opacity):\n${offending.join('\n')}`
210
+ )
211
+ }
212
+ }
213
+
181
214
  // ─── Build ────────────────────────────────────────────────────────────────────
182
215
 
183
216
  const srcDir = join(__dirname, 'src')
184
217
  const distDir = join(__dirname, 'dist')
185
218
 
219
+ /** Names of every dist file emitted by this build, scanned by the guard. */
220
+ const emitted = []
221
+
186
222
  async function buildFile(inputPath, outputName, label) {
187
223
  const fullCSS = resolveImports(inputPath)
188
224
  const compiled = await processCSS(fullCSS, outputName)
189
225
  const fixed = fixModeSelectors(compiled)
190
226
  writeFileSync(join(distDir, outputName), fixed, 'utf-8') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — outputName is derived from a hardcoded string array, not user input
227
+ emitted.push(outputName)
191
228
  console.log(`✓ dist/${outputName} (${label})`)
192
229
  }
193
230
 
@@ -202,6 +239,7 @@ async function build() {
202
239
  const compiledBase = await processCSS(baseCSS, 'base.css')
203
240
  const baseFull = fixModeSelectors(compiledPalette + '\n' + zScaleCSS + '\n' + compiledBase)
204
241
  writeFileSync(join(distDir, 'base.css'), baseFull, 'utf-8')
242
+ emitted.push('base.css')
205
243
  console.log('✓ dist/base.css (structural styles + palette defaults)')
206
244
 
207
245
  // Per-theme files
@@ -227,8 +265,12 @@ async function build() {
227
265
  // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal — name is from a hardcoded string array, not user input
228
266
  const bundleParts = allThemes.map((name) => readFileSync(join(distDir, `${name}.css`), 'utf-8'))
229
267
  writeFileSync(join(distDir, 'index.css'), bundleParts.join('\n'), 'utf-8')
268
+ emitted.push('index.css')
230
269
  console.log('✓ dist/index.css (full bundle)')
231
270
 
271
+ // Regression guard (#135): no unresolved @apply may ship in dist.
272
+ for (const outputName of emitted) assertNoApply(outputName)
273
+
232
274
  console.log('\n@rokkit/themes build complete.')
233
275
  }
234
276