purgetss 7.5.2 → 7.6.1

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 (50) hide show
  1. package/README.md +93 -11
  2. package/bin/purgetss +140 -1
  3. package/dist/purgetss.ui.js +65 -26
  4. package/dist/utilities.tss +21 -4
  5. package/experimental/completions2.js +1 -1
  6. package/lib/completions/titanium/completions-v3.json +62 -1
  7. package/lib/templates/purgetss.config.js.cjs +15 -1
  8. package/lib/templates/purgetss.ui.js.cjs +64 -25
  9. package/package.json +3 -1
  10. package/src/cli/commands/brand.js +69 -0
  11. package/src/cli/commands/create.js +11 -7
  12. package/src/cli/commands/fonts.js +9 -9
  13. package/src/cli/commands/icon-library.js +18 -16
  14. package/src/cli/commands/images.js +116 -0
  15. package/src/cli/commands/init.js +4 -0
  16. package/src/cli/commands/module.js +4 -2
  17. package/src/cli/commands/purge.js +77 -101
  18. package/src/cli/commands/semantic.js +180 -0
  19. package/src/cli/commands/shades.js +332 -13
  20. package/src/cli/utils/project-detection.js +4 -2
  21. package/src/core/analyzers/class-extractor.js +110 -3
  22. package/src/core/branding/brand-config.js +111 -0
  23. package/src/core/branding/branding-logger.js +40 -0
  24. package/src/core/branding/cleanup-legacy.js +220 -0
  25. package/src/core/branding/ensure-brand-section.js +80 -0
  26. package/src/core/branding/gen-android-adaptive.js +116 -0
  27. package/src/core/branding/gen-android-legacy.js +63 -0
  28. package/src/core/branding/gen-ic-launcher-xml.js +29 -0
  29. package/src/core/branding/gen-ios-dark.js +70 -0
  30. package/src/core/branding/gen-ios-tinted.js +55 -0
  31. package/src/core/branding/gen-ios.js +69 -0
  32. package/src/core/branding/gen-marketplace.js +71 -0
  33. package/src/core/branding/gen-notification.js +76 -0
  34. package/src/core/branding/gen-splash.js +64 -0
  35. package/src/core/branding/index.js +336 -0
  36. package/src/core/branding/post-gen-notes.js +145 -0
  37. package/src/core/branding/prepare-master.js +108 -0
  38. package/src/core/branding/tiapp-reader.js +110 -0
  39. package/src/core/builders/tailwind-helpers.js +1 -1
  40. package/src/core/images/ensure-images-section.js +57 -0
  41. package/src/core/images/gen-scales.js +181 -0
  42. package/src/core/images/index.js +171 -0
  43. package/src/shared/config-manager.js +46 -0
  44. package/src/shared/config-writer.js +84 -0
  45. package/src/shared/constants.js +3 -0
  46. package/src/shared/helpers/typography.js +38 -3
  47. package/src/shared/logger.js +69 -4
  48. package/src/shared/prompt.js +64 -0
  49. package/src/shared/svg-utils.js +80 -0
  50. package/src/shared/utils.js +8 -4
@@ -431,7 +431,7 @@ function copyFreeFonts() {
431
431
  fs.copyFile(srcFonts_Folder + '/FontAwesome7Free-Regular.ttf', projectsFontsFolder + '/FontAwesome7Free-Regular.ttf', callback)
432
432
  fs.copyFile(srcFonts_Folder + '/FontAwesome7Free-Solid.ttf', projectsFontsFolder + '/FontAwesome7Free-Solid.ttf', callback)
433
433
 
434
- logger.warn(' - Font Awesome Free')
434
+ logger.item(chalk.green('Font Awesome Free'))
435
435
  }
436
436
 
437
437
  /**
@@ -444,7 +444,7 @@ function copyFreeFonts() {
444
444
  function copyProFonts(fontFamilies, webFonts) {
445
445
  _.each(fontFamilies, (dest, src) => {
446
446
  if (copyFile(`${webFonts}/${src}`, dest)) {
447
- logger.warn(` - ${dest} Font copied to`, chalk.yellow('./app/assets/fonts'), 'folder')
447
+ logger.item(`${dest} copied to ${chalk.yellow('./app/assets/fonts')}`)
448
448
  }
449
449
  })
450
450
  }
@@ -467,7 +467,7 @@ function copyMaterialIconsFonts() {
467
467
  copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
468
468
  })
469
469
 
470
- logger.warn(' - Material Icons')
470
+ logger.item(chalk.green('Material Icons'))
471
471
  }
472
472
 
473
473
  /**
@@ -486,7 +486,7 @@ function copyMaterialSymbolsFonts() {
486
486
  copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
487
487
  })
488
488
 
489
- logger.warn(' - Material Symbols')
489
+ logger.item(chalk.green('Material Symbols'))
490
490
  }
491
491
 
492
492
  /**
@@ -496,7 +496,7 @@ function copyMaterialSymbolsFonts() {
496
496
  function copyFramework7IconsFonts() {
497
497
  // Framework7 Font
498
498
  copyFile(srcFonts_Folder + '/Framework7-Icons.ttf', 'Framework7-Icons.ttf')
499
- logger.warn(' - Framework 7')
499
+ logger.item(chalk.green('Framework 7'))
500
500
  }
501
501
 
502
502
  /**
@@ -572,23 +572,23 @@ function copyFontLibrary(vendor) {
572
572
  buildFontAwesomeJS()
573
573
  } else {
574
574
  fs.copyFileSync(srcLibFA, projectsLibFolder + '/fontawesome.js')
575
- logger.warn(' - fontawesome.js')
575
+ logger.item(chalk.yellow('fontawesome.js'))
576
576
  }
577
577
  break
578
578
  case 'mi':
579
579
  case 'materialicons':
580
580
  fs.copyFileSync(srcLibMI, projectsLibFolder + '/materialicons.js')
581
- logger.warn(' - materialicons.js')
581
+ logger.item(chalk.yellow('materialicons.js'))
582
582
  break
583
583
  case 'ms':
584
584
  case 'materialsymbol':
585
585
  fs.copyFileSync(srcLibMS, projectsLibFolder + '/materialsymbols.js')
586
- logger.warn(' - materialsymbols.js')
586
+ logger.item(chalk.yellow('materialsymbols.js'))
587
587
  break
588
588
  case 'f7':
589
589
  case 'framework7':
590
590
  fs.copyFileSync(srcLibF7, projectsLibFolder + '/framework7icons.js')
591
- logger.warn(' - framework7icons.js')
591
+ logger.item(chalk.yellow('framework7icons.js'))
592
592
  break
593
593
  }
594
594
  }
@@ -100,7 +100,7 @@ function copyFreeFonts() {
100
100
  fs.copyFile(srcFonts_Folder + '/FontAwesome7Free-Regular.ttf', projectsFontsFolder + '/FontAwesome7Free-Regular.ttf', callback)
101
101
  fs.copyFile(srcFonts_Folder + '/FontAwesome7Free-Solid.ttf', projectsFontsFolder + '/FontAwesome7Free-Solid.ttf', callback)
102
102
 
103
- logger.warn(' - Font Awesome Free')
103
+ logger.item(chalk.green('Font Awesome Free'))
104
104
  }
105
105
 
106
106
  /**
@@ -111,7 +111,7 @@ function copyFreeFonts() {
111
111
  function copyProFonts(fontFamilies, webFonts) {
112
112
  _.each(fontFamilies, (dest, src) => {
113
113
  if (copyFile(`${webFonts}/${src}`, dest)) {
114
- logger.warn(` - ${dest} Font copied to`, chalk.yellow('./app/assets/fonts'), 'folder')
114
+ logger.item(`${dest} copied to ${chalk.yellow('./app/assets/fonts')}`)
115
115
  }
116
116
  })
117
117
  }
@@ -133,7 +133,7 @@ function copyMaterialIconsFonts() {
133
133
  copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
134
134
  })
135
135
 
136
- logger.warn(' - Material Icons')
136
+ logger.item(chalk.green('Material Icons'))
137
137
  }
138
138
 
139
139
  /**
@@ -151,7 +151,7 @@ function copyMaterialSymbolsFonts() {
151
151
  copyFile(`${srcFonts_Folder}/${familyName}`, familyName)
152
152
  })
153
153
 
154
- logger.warn(' - Material Symbols')
154
+ logger.item(chalk.green('Material Symbols'))
155
155
  }
156
156
 
157
157
  /**
@@ -160,7 +160,7 @@ function copyMaterialSymbolsFonts() {
160
160
  function copyFramework7IconsFonts() {
161
161
  // Framework7 Font
162
162
  copyFile(srcFonts_Folder + '/Framework7-Icons.ttf', 'Framework7-Icons.ttf')
163
- logger.warn(' - Framework 7')
163
+ logger.item(chalk.green('Framework 7'))
164
164
  }
165
165
 
166
166
  /**
@@ -170,7 +170,7 @@ function copyFramework7IconsFonts() {
170
170
  function buildFontAwesomeJS() {
171
171
  // This function should be imported from the fonts module
172
172
  // For now, just log that it would be called
173
- logger.warn(' - Font Awesome JS module would be built')
173
+ logger.item(chalk.yellow('Font Awesome JS module would be built'))
174
174
  }
175
175
 
176
176
  /**
@@ -218,23 +218,23 @@ function copyFontLibrary(vendor) {
218
218
  buildFontAwesomeJS()
219
219
  } else {
220
220
  fs.copyFileSync(srcLibFA, projectsLibFolder + '/fontawesome.js')
221
- logger.warn(' - fontawesome.js')
221
+ logger.item(chalk.yellow('fontawesome.js'))
222
222
  }
223
223
  break
224
224
  case 'mi':
225
225
  case 'materialicons':
226
226
  fs.copyFileSync(srcLibMI, projectsLibFolder + '/materialicons.js')
227
- logger.warn(' - materialicons.js')
227
+ logger.item(chalk.yellow('materialicons.js'))
228
228
  break
229
229
  case 'ms':
230
230
  case 'materialsymbol':
231
231
  fs.copyFileSync(srcLibMS, projectsLibFolder + '/materialsymbols.js')
232
- logger.warn(' - materialsymbols.js')
232
+ logger.item(chalk.yellow('materialsymbols.js'))
233
233
  break
234
234
  case 'f7':
235
235
  case 'framework7':
236
236
  fs.copyFileSync(srcLibF7, projectsLibFolder + '/framework7icons.js')
237
- logger.warn(' - framework7icons.js')
237
+ logger.item(chalk.yellow('framework7icons.js'))
238
238
  break
239
239
  }
240
240
  }
@@ -251,23 +251,23 @@ function copyFontStyle(vendor) {
251
251
  buildFontAwesomeJS()
252
252
  } else {
253
253
  fs.copyFileSync(srcFontAwesomeTSSFile, projectsPurge_TSS_Styles_Folder + '/fontawesome.tss')
254
- logger.warn(' - fontawesome.tss')
254
+ logger.item(chalk.yellow('fontawesome.tss'))
255
255
  }
256
256
  break
257
257
  case 'mi':
258
258
  case 'materialicons':
259
259
  fs.copyFileSync(srcMaterialIconsTSSFile, projectsPurge_TSS_Styles_Folder + '/materialicons.tss')
260
- logger.warn(' - materialicons.tss')
260
+ logger.item(chalk.yellow('materialicons.tss'))
261
261
  break
262
262
  case 'ms':
263
263
  case 'materialsymbol':
264
264
  fs.copyFileSync(srcMaterialSymbolsTSSFile, projectsPurge_TSS_Styles_Folder + '/materialsymbols.tss')
265
- logger.warn(' - materialsymbols.tss')
265
+ logger.item(chalk.yellow('materialsymbols.tss'))
266
266
  break
267
267
  case 'f7':
268
268
  case 'framework7':
269
269
  fs.copyFileSync(srcFramework7FontTSSFile, projectsPurge_TSS_Styles_Folder + '/framework7icons.tss')
270
- logger.warn(' - framework7icons.tss')
270
+ logger.item(chalk.yellow('framework7icons.tss'))
271
271
  break
272
272
  }
273
273
  }
@@ -391,8 +391,10 @@ export async function copyModulesLibrary() {
391
391
  logger.info(chalk.yellow('purgetss.ui'), 'module copied to', chalk.yellow('./Resources/lib'), 'folder')
392
392
  return true
393
393
  } else {
394
- logger.info(`Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`)
395
- logger.info(`For more information, visit ${chalk.green('https://purgetss.com')}`)
394
+ logger.block(
395
+ `Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`,
396
+ `For more information, visit ${chalk.green('https://purgetss.com')}`
397
+ )
396
398
  return false
397
399
  }
398
400
  } catch (error) {
@@ -0,0 +1,116 @@
1
+ /**
2
+ * PurgeTSS - Images Command
3
+ *
4
+ * Generates multi-density variants of UI images for Titanium Alloy or Classic
5
+ * projects. Auto-discovers sources in `./purgetss/images/` by default; accepts
6
+ * a path argument to override (file or directory).
7
+ *
8
+ * Precedence for every option: CLI flag > `images:` section in config > default.
9
+ *
10
+ * @fileoverview Assets command entry point
11
+ * @author César Estrada
12
+ */
13
+
14
+ import fs from 'fs'
15
+ import path from 'path'
16
+ import chalk from 'chalk'
17
+ import { runImages } from '../../core/images/index.js'
18
+ import { logger } from '../../core/branding/branding-logger.js'
19
+ import { ensureImagesSection } from '../../core/images/ensure-images-section.js'
20
+ import { getConfigFile } from '../../shared/config-manager.js'
21
+ import { projectsPurge_TSS_Images_Folder } from '../../shared/constants.js'
22
+
23
+ const VALID_FORMATS = new Set(['webp', 'jpeg', 'jpg', 'png', 'avif', 'gif', 'tiff'])
24
+
25
+ export async function images(cliSource, options = {}) {
26
+ if (options.debug) logger.setDebugMode(true)
27
+
28
+ const projectRoot = options.project ? path.resolve(options.project) : process.cwd()
29
+
30
+ if (!options.project) ensureImagesSection()
31
+
32
+ const cfg = loadImagesSection()
33
+
34
+ // --android and --ios are mutually exclusive.
35
+ if (options.android && options.ios) {
36
+ logger.error('--android and --ios are mutually exclusive. Pass neither to generate both, or pick one.')
37
+ process.exit(1)
38
+ }
39
+
40
+ const format = options.format ?? cfg.format ?? null
41
+ if (format && !VALID_FORMATS.has(format.toLowerCase())) {
42
+ logger.error(`Invalid --format '${format}'. Valid: ${[...VALID_FORMATS].join(', ')}`)
43
+ process.exit(1)
44
+ }
45
+
46
+ const source = resolveSource(cliSource, projectRoot)
47
+ if (!source) {
48
+ printMissingSourceHelp(projectRoot)
49
+ process.exit(1)
50
+ }
51
+
52
+ try {
53
+ await runImages({
54
+ source,
55
+ projectRoot,
56
+ androidOnly: Boolean(options.android),
57
+ iphoneOnly: Boolean(options.ios),
58
+ format: format ? format.toLowerCase() : null,
59
+ quality: options.quality ?? cfg.quality ?? 85,
60
+ dryRun: Boolean(options.dryRun),
61
+ yes: Boolean(options.yes),
62
+ confirmOverwrites: cfg.confirmOverwrites !== false
63
+ })
64
+ } catch (err) {
65
+ logger.error(err.message)
66
+ if (options.debug) console.error(err.stack)
67
+ process.exit(1)
68
+ }
69
+ }
70
+
71
+ function loadImagesSection() {
72
+ try {
73
+ const cfg = getConfigFile()
74
+ if (cfg && typeof cfg.images === 'object') return cfg.images
75
+ } catch {}
76
+ return {}
77
+ }
78
+
79
+ function resolveSource(cliSource, projectRoot) {
80
+ const imagesFolder = projectRoot === process.cwd()
81
+ ? projectsPurge_TSS_Images_Folder
82
+ : path.join(projectRoot, 'purgetss', 'images')
83
+
84
+ if (cliSource) {
85
+ if (path.isAbsolute(cliSource)) {
86
+ return fs.existsSync(cliSource) ? cliSource : null
87
+ }
88
+ // Relative paths: try purgetss/images/ first (convention), then cwd.
89
+ // Lets users write short paths like `background/pink.png` without the prefix.
90
+ const insideImages = path.resolve(imagesFolder, cliSource)
91
+ if (fs.existsSync(insideImages)) return insideImages
92
+
93
+ const cwdResolved = path.resolve(projectRoot, cliSource)
94
+ if (fs.existsSync(cwdResolved)) return cwdResolved
95
+
96
+ return null
97
+ }
98
+ return fs.existsSync(imagesFolder) ? imagesFolder : null
99
+ }
100
+
101
+ function printMissingSourceHelp(projectRoot) {
102
+ const rel = (p) => path.relative(projectRoot, p) || '.'
103
+ const imagesDir = path.join(projectRoot, 'purgetss', 'images')
104
+
105
+ logger.error('No source images found.')
106
+ console.log()
107
+ console.log(` Expected images inside ${chalk.cyan(rel(imagesDir) + '/')}.`)
108
+ console.log(` The folder already exists — drop your images into it (subdirectories are preserved):`)
109
+ console.log(` ${chalk.cyan('cp my-ui-asset.png ' + rel(imagesDir) + '/')}`)
110
+ console.log()
111
+ console.log(' Alternatives:')
112
+ console.log(` ${chalk.gray('•')} Pass a file or directory explicitly:`)
113
+ console.log(` ${chalk.cyan('purgetss images ./docs/screenshots')}`)
114
+ console.log(` ${chalk.cyan('purgetss images ./logo.png')}`)
115
+ console.log()
116
+ }
@@ -21,6 +21,8 @@ import {
21
21
  projectsAlloyJMKFile,
22
22
  projectsPurgeTSSFolder,
23
23
  projectsPurge_TSS_Fonts_Folder,
24
+ projectsPurge_TSS_Brand_Folder,
25
+ projectsPurge_TSS_Images_Folder,
24
26
  srcConfigFile,
25
27
  projectsFA_TSS_File,
26
28
  srcFontAwesomeTSSFile,
@@ -70,6 +72,8 @@ export function createConfigFile() {
70
72
  if (alloyProject()) {
71
73
  makeSureFolderExists(projectsPurgeTSSFolder)
72
74
  makeSureFolderExists(projectsPurge_TSS_Fonts_Folder)
75
+ makeSureFolderExists(projectsPurge_TSS_Brand_Folder)
76
+ makeSureFolderExists(projectsPurge_TSS_Images_Folder)
73
77
 
74
78
  if (fs.existsSync(projectsConfigJS)) {
75
79
  logger.warn('./purgetss/config.cjs', chalk.red('file already exists!'))
@@ -41,8 +41,10 @@ export function copyModulesLibrary() {
41
41
  return true
42
42
  } else {
43
43
  // Not in a valid project
44
- logger.info(`Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`)
45
- logger.info(`For more information, visit ${chalk.green('https://purgetss.com')}`)
44
+ logger.block(
45
+ `Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`,
46
+ `For more information, visit ${chalk.green('https://purgetss.com')}`
47
+ )
46
48
  return false
47
49
  }
48
50
  }
@@ -31,6 +31,7 @@ import { init } from './init.js'
31
31
  import { getConfigOptions, getConfigFile, ensureConfig } from '../../shared/config-manager.js'
32
32
 
33
33
  // Import purger functions from core modules
34
+ import { processControllers } from '../../core/analyzers/class-extractor.js'
34
35
  import { purgeTailwind } from '../../core/purger/tailwind-purger.js'
35
36
  import {
36
37
  purgeFontAwesome,
@@ -191,7 +192,15 @@ function validateXML(xmlText, filePath) {
191
192
  function findSuspectLine(lines) {
192
193
  for (let i = 0; i < lines.length; i++) {
193
194
  const trimmed = lines[i].trim()
194
- if (!trimmed || trimmed.startsWith('<!--')) continue
195
+ if (!trimmed) continue
196
+
197
+ // "--" inside XML comments (illegal in XML spec)
198
+ if (trimmed.includes('<!--')) {
199
+ const commentBody = trimmed.replace(/<!--/, '').replace(/-->.*$/, '')
200
+ if (/--/.test(commentBody)) return i + 1
201
+ }
202
+
203
+ if (trimmed.startsWith('<!--')) continue
195
204
 
196
205
  // Opening tag without tag name: "< class=..."
197
206
  if (/^<\s+\w+=/.test(trimmed)) return i + 1
@@ -235,8 +244,26 @@ function preValidateXML(xmlText, filePath) {
235
244
  const line = lines[i]
236
245
  const trimmed = line.trim()
237
246
 
238
- // Skip empty lines and comments
239
- if (!trimmed || trimmed.startsWith('<!--') || trimmed.startsWith('<Alloy')) {
247
+ // Skip empty lines and Alloy root tag
248
+ if (!trimmed || trimmed.startsWith('<Alloy')) {
249
+ continue
250
+ }
251
+
252
+ // Check for "--" inside XML comments (illegal in XML spec)
253
+ // e.g. <!-- Section: --modules Option --> is invalid because of the "--" before "modules"
254
+ if (trimmed.includes('<!--')) {
255
+ const commentBody = trimmed.replace(/<!--/, '').replace(/-->.*$/, '')
256
+ if (/--/.test(commentBody)) {
257
+ const dashMatch = commentBody.match(/--(\S*)/)
258
+ const offender = dashMatch ? `--${dashMatch[1]}` : '--'
259
+ throwPreValidationError({
260
+ relativePath,
261
+ lineNumber: i + 1,
262
+ lineContent: trimmed,
263
+ message: `XML comment contains illegal "--" sequence ("${offender}")`,
264
+ fix: `Replace "--" with "—" (em-dash) or reword the comment to avoid double dashes`
265
+ })
266
+ }
240
267
  continue
241
268
  }
242
269
 
@@ -359,12 +386,16 @@ function throwPreValidationError({ relativePath, lineNumber, lineContent, messag
359
386
  error.lineNumber = lineNumber
360
387
  error.lineContent = lineContent
361
388
 
362
- logger.error('XML Syntax Error\n')
363
- logger.info(`File: "${relativePath}"`)
364
- logger.info(`Line: ${lineNumber}`)
365
- logger.info(`Content: "${lineContent}"\n`)
366
- logger.error(message)
367
- logger.info(chalk.green(`Fix: ${fix}\n`))
389
+ logger.block(
390
+ chalk.red('XML Syntax Error'),
391
+ `File: ${chalk.yellow(`"${relativePath}"`)}`,
392
+ `Line: ${chalk.yellow(lineNumber)}`,
393
+ `Content: ${chalk.yellow(`"${lineContent}"`)}`,
394
+ '',
395
+ chalk.red(message),
396
+ '',
397
+ `${chalk.green('Fix:')} ${fix}`
398
+ )
368
399
 
369
400
  throw error
370
401
  }
@@ -386,69 +417,6 @@ function extractClasses(currentText, currentFile) {
386
417
  }
387
418
  }
388
419
 
389
- /**
390
- * Process controller files for classes
391
- * COPIED exactly from original processControllers() function
392
- */
393
- function processControllers(data) {
394
- const allWords = []
395
- const lines = data.split(/\r?\n/)
396
-
397
- lines.forEach(line => {
398
- const words = extractWordsFromLine(line)
399
- if (words.length > 0) {
400
- allWords.push(...words)
401
- }
402
- })
403
-
404
- return allWords
405
- }
406
-
407
- /**
408
- * Extract words from a line of controller code
409
- * COPIED exactly from original extractWordsFromLine() function
410
- */
411
- function extractWordsFromLine(line) {
412
- const patterns = [
413
- {
414
- // apply: 'classes'
415
- regex: /apply:\s*'([^']+)'/,
416
- process: match => match[1].split(/\s+/)
417
- },
418
- {
419
- // classes: ['class1', 'class2'] o classes: ['class1 class2']
420
- regex: /classes:\s*\[([^\]]+)\]/,
421
- process: match => match[1].split(',').map(item => item.trim().replace(/['"]/g, ''))
422
- },
423
- {
424
- // classes: 'class1 class2'
425
- regex: /classes:\s*'([^']+)'/,
426
- process: match => match[1].split(/\s+/)
427
- }
428
- ]
429
-
430
- // Process simple patterns
431
- const words = patterns.reduce((acc, { regex, process }) => {
432
- const match = regex.exec(line)
433
- return match ? [...acc, ...process(match)] : acc
434
- }, [])
435
-
436
- // Process addClass, removeClass, resetClass
437
- const classFunctionRegex = /(?:\.\w+Class|resetClass)\([^,]+,\s*(?:'([^']+)'|\[([^\]]+)\])/g
438
- let classFunctionMatch
439
- while ((classFunctionMatch = classFunctionRegex.exec(line)) !== null) {
440
- const content = classFunctionMatch[1] || classFunctionMatch[2]
441
- if (content) {
442
- const classes = content.includes(',')
443
- ? content.split(',').map(item => item.trim().replace(/['"]/g, ''))
444
- : content.replace(/['"]/g, '').split(/\s+/)
445
- words.push(...classes)
446
- }
447
- }
448
-
449
- return words
450
- }
451
-
452
420
  /**
453
421
  * Filter invalid characters from class names
454
422
  * COPIED exactly from original filterCharacters() function
@@ -711,49 +679,57 @@ export function purgeClasses(options) {
711
679
 
712
680
  if (Date.now() > (fs.statSync(projectsAppTSS).mtimeMs + 2000) || recentlyCreated) {
713
681
  start()
714
-
715
- init(options)
716
-
717
- backupOriginalAppTss()
718
-
719
- let uniqueClasses
720
-
682
+ logger.startSection()
721
683
  try {
722
- uniqueClasses = getUniqueClasses()
723
- } catch (error) {
724
- // Handle pre-validation errors (XML syntax errors detected before parsing)
725
- if (error.isPreValidationError) {
726
- // Error already printed by preValidateXML, exit cleanly
727
- // eslint-disable-next-line n/no-process-exit
728
- process.exit(1)
684
+ // Explicit header so every purge run shows which project is being
685
+ // processed mirrors the Auto-Purging line emitted by the alloy.jmk hook.
686
+ logger.info('Purging', chalk.yellow(cwd))
687
+
688
+ init(options)
689
+
690
+ backupOriginalAppTss()
691
+
692
+ let uniqueClasses
693
+
694
+ try {
695
+ uniqueClasses = getUniqueClasses()
696
+ } catch (error) {
697
+ // Handle pre-validation errors (XML syntax errors detected before parsing)
698
+ if (error.isPreValidationError) {
699
+ // Error already printed by preValidateXML, exit cleanly
700
+ // eslint-disable-next-line n/no-process-exit
701
+ process.exit(1)
702
+ }
703
+ // Re-throw other errors
704
+ throw error
729
705
  }
730
- // Re-throw other errors
731
- throw error
732
- }
733
706
 
734
- let tempPurged = copyResetTemplateAnd_appTSS()
707
+ let tempPurged = copyResetTemplateAnd_appTSS()
735
708
 
736
- tempPurged += purgeTailwind(uniqueClasses, purgingDebug)
709
+ tempPurged += purgeTailwind(uniqueClasses, purgingDebug)
737
710
 
738
- const cleanUniqueClasses = cleanClasses(uniqueClasses)
711
+ const cleanUniqueClasses = cleanClasses(uniqueClasses)
739
712
 
740
- tempPurged += purgeFontAwesome(uniqueClasses, cleanUniqueClasses, purgingDebug)
713
+ tempPurged += purgeFontAwesome(uniqueClasses, cleanUniqueClasses, purgingDebug)
741
714
 
742
- tempPurged += purgeMaterialIcons(uniqueClasses, cleanUniqueClasses, purgingDebug)
715
+ tempPurged += purgeMaterialIcons(uniqueClasses, cleanUniqueClasses, purgingDebug)
743
716
 
744
- tempPurged += purgeMaterialSymbols(uniqueClasses, cleanUniqueClasses, purgingDebug)
717
+ tempPurged += purgeMaterialSymbols(uniqueClasses, cleanUniqueClasses, purgingDebug)
745
718
 
746
- tempPurged += purgeFramework7(uniqueClasses, cleanUniqueClasses, purgingDebug)
719
+ tempPurged += purgeFramework7(uniqueClasses, cleanUniqueClasses, purgingDebug)
747
720
 
748
- tempPurged += purgeFonts(uniqueClasses, cleanUniqueClasses, purgingDebug)
721
+ tempPurged += purgeFonts(uniqueClasses, cleanUniqueClasses, purgingDebug)
749
722
 
750
- tempPurged += processMissingClasses(tempPurged)
723
+ tempPurged += processMissingClasses(tempPurged)
751
724
 
752
- saveFile(projectsAppTSS, tempPurged)
725
+ saveFile(projectsAppTSS, tempPurged)
753
726
 
754
- logger.file('app.tss')
727
+ logger.file('app.tss')
755
728
 
756
- finish()
729
+ finish()
730
+ } finally {
731
+ logger.endSection()
732
+ }
757
733
 
758
734
  return true
759
735
  } else {