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.
- package/README.md +93 -11
- package/bin/purgetss +140 -1
- package/dist/purgetss.ui.js +65 -26
- package/dist/utilities.tss +21 -4
- package/experimental/completions2.js +1 -1
- package/lib/completions/titanium/completions-v3.json +62 -1
- package/lib/templates/purgetss.config.js.cjs +15 -1
- package/lib/templates/purgetss.ui.js.cjs +64 -25
- package/package.json +3 -1
- package/src/cli/commands/brand.js +69 -0
- package/src/cli/commands/create.js +11 -7
- package/src/cli/commands/fonts.js +9 -9
- package/src/cli/commands/icon-library.js +18 -16
- package/src/cli/commands/images.js +116 -0
- package/src/cli/commands/init.js +4 -0
- package/src/cli/commands/module.js +4 -2
- package/src/cli/commands/purge.js +77 -101
- package/src/cli/commands/semantic.js +180 -0
- package/src/cli/commands/shades.js +332 -13
- package/src/cli/utils/project-detection.js +4 -2
- package/src/core/analyzers/class-extractor.js +110 -3
- package/src/core/branding/brand-config.js +111 -0
- package/src/core/branding/branding-logger.js +40 -0
- package/src/core/branding/cleanup-legacy.js +220 -0
- package/src/core/branding/ensure-brand-section.js +80 -0
- package/src/core/branding/gen-android-adaptive.js +116 -0
- package/src/core/branding/gen-android-legacy.js +63 -0
- package/src/core/branding/gen-ic-launcher-xml.js +29 -0
- package/src/core/branding/gen-ios-dark.js +70 -0
- package/src/core/branding/gen-ios-tinted.js +55 -0
- package/src/core/branding/gen-ios.js +69 -0
- package/src/core/branding/gen-marketplace.js +71 -0
- package/src/core/branding/gen-notification.js +76 -0
- package/src/core/branding/gen-splash.js +64 -0
- package/src/core/branding/index.js +336 -0
- package/src/core/branding/post-gen-notes.js +145 -0
- package/src/core/branding/prepare-master.js +108 -0
- package/src/core/branding/tiapp-reader.js +110 -0
- package/src/core/builders/tailwind-helpers.js +1 -1
- package/src/core/images/ensure-images-section.js +57 -0
- package/src/core/images/gen-scales.js +181 -0
- package/src/core/images/index.js +171 -0
- package/src/shared/config-manager.js +46 -0
- package/src/shared/config-writer.js +84 -0
- package/src/shared/constants.js +3 -0
- package/src/shared/helpers/typography.js +38 -3
- package/src/shared/logger.js +69 -4
- package/src/shared/prompt.js +64 -0
- package/src/shared/svg-utils.js +80 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
395
|
-
|
|
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
|
+
}
|
package/src/cli/commands/init.js
CHANGED
|
@@ -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.
|
|
45
|
-
|
|
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
|
|
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
|
|
239
|
-
if (!trimmed || trimmed.startsWith('
|
|
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.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
-
|
|
707
|
+
let tempPurged = copyResetTemplateAnd_appTSS()
|
|
735
708
|
|
|
736
|
-
|
|
709
|
+
tempPurged += purgeTailwind(uniqueClasses, purgingDebug)
|
|
737
710
|
|
|
738
|
-
|
|
711
|
+
const cleanUniqueClasses = cleanClasses(uniqueClasses)
|
|
739
712
|
|
|
740
|
-
|
|
713
|
+
tempPurged += purgeFontAwesome(uniqueClasses, cleanUniqueClasses, purgingDebug)
|
|
741
714
|
|
|
742
|
-
|
|
715
|
+
tempPurged += purgeMaterialIcons(uniqueClasses, cleanUniqueClasses, purgingDebug)
|
|
743
716
|
|
|
744
|
-
|
|
717
|
+
tempPurged += purgeMaterialSymbols(uniqueClasses, cleanUniqueClasses, purgingDebug)
|
|
745
718
|
|
|
746
|
-
|
|
719
|
+
tempPurged += purgeFramework7(uniqueClasses, cleanUniqueClasses, purgingDebug)
|
|
747
720
|
|
|
748
|
-
|
|
721
|
+
tempPurged += purgeFonts(uniqueClasses, cleanUniqueClasses, purgingDebug)
|
|
749
722
|
|
|
750
|
-
|
|
723
|
+
tempPurged += processMissingClasses(tempPurged)
|
|
751
724
|
|
|
752
|
-
|
|
725
|
+
saveFile(projectsAppTSS, tempPurged)
|
|
753
726
|
|
|
754
|
-
|
|
727
|
+
logger.file('app.tss')
|
|
755
728
|
|
|
756
|
-
|
|
729
|
+
finish()
|
|
730
|
+
} finally {
|
|
731
|
+
logger.endSection()
|
|
732
|
+
}
|
|
757
733
|
|
|
758
734
|
return true
|
|
759
735
|
} else {
|