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
package/src/shared/constants.js
CHANGED
|
@@ -29,6 +29,7 @@ export const projectsAppTSS = `${cwd}/app/styles/app.tss`
|
|
|
29
29
|
export const projects_AppTSS = `${cwd}/app/styles/_app.tss`
|
|
30
30
|
export const projectsAlloyJMKFile = `${cwd}/app/alloy.jmk`
|
|
31
31
|
export const projectsFontsFolder = `${cwd}/app/assets/fonts`
|
|
32
|
+
export const projectsSemanticColorsJSON = `${cwd}/app/assets/semantic.colors.json`
|
|
32
33
|
export const projectsFontAwesomeJS = `${cwd}/app/lib/fontawesome.js`
|
|
33
34
|
|
|
34
35
|
// ============================================================================
|
|
@@ -39,6 +40,8 @@ export const projectsPurgeTSSFolder = `${cwd}/purgetss`
|
|
|
39
40
|
export const projectsConfigJS = `${cwd}/purgetss/config.cjs`
|
|
40
41
|
export const projectsTailwind_TSS = `${cwd}/purgetss/styles/utilities.tss`
|
|
41
42
|
export const projectsPurge_TSS_Fonts_Folder = `${cwd}/purgetss/fonts`
|
|
43
|
+
export const projectsPurge_TSS_Brand_Folder = `${cwd}/purgetss/brand`
|
|
44
|
+
export const projectsPurge_TSS_Images_Folder = `${cwd}/purgetss/images`
|
|
42
45
|
export const projectsPurge_TSS_Styles_Folder = `${cwd}/purgetss/styles`
|
|
43
46
|
export const projectsFA_TSS_File = `${cwd}/purgetss/styles/fontawesome.tss`
|
|
44
47
|
|
|
@@ -26,18 +26,53 @@ function removeFractions(modifiersAndValues, extras = []) {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Font family property for text components
|
|
29
|
+
*
|
|
30
|
+
* Built-in platform defaults:
|
|
31
|
+
* - font-sans → Android: 'sans-serif', iOS: 'Helvetica Neue'
|
|
32
|
+
* - font-serif → Android: 'serif', iOS: 'Georgia'
|
|
33
|
+
* - font-mono → 'monospace' (both platforms)
|
|
34
|
+
*
|
|
35
|
+
* User values from config.cjs override defaults cross-platform.
|
|
36
|
+
*
|
|
29
37
|
* @param {Object} modifiersAndValues - Modifier and value pairs
|
|
30
38
|
* @returns {string} Generated styles
|
|
31
39
|
*/
|
|
32
40
|
export function fontFamily(modifiersAndValues) {
|
|
41
|
+
const platformDefaults = {
|
|
42
|
+
sans: { ios: 'Helvetica Neue', android: 'sans-serif' },
|
|
43
|
+
serif: { ios: 'Georgia', android: 'serif' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const crossPlatformDefaults = { mono: 'monospace' }
|
|
47
|
+
|
|
48
|
+
const defaults = { ...modifiersAndValues }
|
|
49
|
+
const ios = {}
|
|
50
|
+
const android = {}
|
|
51
|
+
|
|
52
|
+
_.each(crossPlatformDefaults, (value, key) => {
|
|
53
|
+
if (!(key in defaults)) {
|
|
54
|
+
defaults[key] = value
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
_.each(platformDefaults, (platforms, key) => {
|
|
59
|
+
if (!(key in defaults)) {
|
|
60
|
+
ios[key] = platforms.ios
|
|
61
|
+
android[key] = platforms.android
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const selectorsAndValues = {}
|
|
66
|
+
if (!_.isEmpty(defaults)) selectorsAndValues.default = defaults
|
|
67
|
+
if (!_.isEmpty(ios)) selectorsAndValues.ios = ios
|
|
68
|
+
if (!_.isEmpty(android)) selectorsAndValues.android = android
|
|
69
|
+
|
|
33
70
|
return processProperties({
|
|
34
71
|
prop: 'fontFamily',
|
|
35
72
|
modules: 'Ti.UI.ActivityIndicator, Ti.UI.Button, Ti.UI.Label, Ti.UI.ListItem, Ti.UI.Picker, Ti.UI.PickerColumn, Ti.UI.PickerRow, Ti.UI.ProgressBar, Ti.UI.Switch, Ti.UI.TableViewRow, Ti.UI.TextArea, Ti.UI.TextField'
|
|
36
73
|
}, {
|
|
37
74
|
font: '{ font: { fontFamily: {value} } }'
|
|
38
|
-
},
|
|
39
|
-
default: modifiersAndValues
|
|
40
|
-
})
|
|
75
|
+
}, selectorsAndValues)
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
/**
|
package/src/shared/logger.js
CHANGED
|
@@ -19,6 +19,27 @@ const purgeLabel = colores.purgeLabel
|
|
|
19
19
|
// Debug mode flag (can be set externally)
|
|
20
20
|
let purgingDebug = false
|
|
21
21
|
|
|
22
|
+
// Section mode: when active, the first info/warn/error/file call acts as a
|
|
23
|
+
// ::PurgeTSS:: header and every subsequent call prints indented 3 spaces
|
|
24
|
+
// without the prefix. Used by long-running flows (e.g. purge) that want a
|
|
25
|
+
// single signed line per run instead of one per step. Always wrap with
|
|
26
|
+
// try/finally in the caller to guarantee endSection() runs.
|
|
27
|
+
let _sectionMode = false
|
|
28
|
+
let _sectionHeaderEmitted = false
|
|
29
|
+
|
|
30
|
+
function _emit(text) {
|
|
31
|
+
if (_sectionMode) {
|
|
32
|
+
if (_sectionHeaderEmitted) {
|
|
33
|
+
console.log(' ' + text)
|
|
34
|
+
} else {
|
|
35
|
+
console.log(purgeLabel, text)
|
|
36
|
+
_sectionHeaderEmitted = true
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
console.log(purgeLabel, text)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
/**
|
|
23
44
|
* Main logger object with different log levels
|
|
24
45
|
* Maintains exact same API as original for compatibility
|
|
@@ -29,7 +50,7 @@ export const logger = {
|
|
|
29
50
|
* @param {...any} args - Arguments to log
|
|
30
51
|
*/
|
|
31
52
|
info: function(...args) {
|
|
32
|
-
|
|
53
|
+
_emit(args.join(' '))
|
|
33
54
|
},
|
|
34
55
|
|
|
35
56
|
/**
|
|
@@ -37,7 +58,7 @@ export const logger = {
|
|
|
37
58
|
* @param {...any} args - Arguments to log
|
|
38
59
|
*/
|
|
39
60
|
warn: function(...args) {
|
|
40
|
-
|
|
61
|
+
_emit(chalk.yellow(args.join(' ')))
|
|
41
62
|
},
|
|
42
63
|
|
|
43
64
|
/**
|
|
@@ -45,7 +66,7 @@ export const logger = {
|
|
|
45
66
|
* @param {...any} args - Arguments to log
|
|
46
67
|
*/
|
|
47
68
|
error: function(...args) {
|
|
48
|
-
|
|
69
|
+
_emit(chalk.red(args.join(' ')))
|
|
49
70
|
},
|
|
50
71
|
|
|
51
72
|
/**
|
|
@@ -53,7 +74,51 @@ export const logger = {
|
|
|
53
74
|
* @param {...any} args - Arguments to log
|
|
54
75
|
*/
|
|
55
76
|
file: function(...args) {
|
|
56
|
-
|
|
77
|
+
_emit(chalk.yellow(args.join(' ')) + ' file created!')
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Enable section mode. The next info/warn/error/file call becomes the
|
|
82
|
+
* ::PurgeTSS:: header; subsequent calls print indented without prefix.
|
|
83
|
+
* MUST be paired with endSection() via try/finally to avoid state leaks.
|
|
84
|
+
*/
|
|
85
|
+
startSection: function() {
|
|
86
|
+
_sectionMode = true
|
|
87
|
+
_sectionHeaderEmitted = false
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Exit section mode. Safe to call even if section wasn't started.
|
|
92
|
+
*/
|
|
93
|
+
endSection: function() {
|
|
94
|
+
_sectionMode = false
|
|
95
|
+
_sectionHeaderEmitted = false
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Log a multi-line block with a single ::PurgeTSS:: header.
|
|
100
|
+
* First arg is the header (printed next to purgeLabel); remaining args are
|
|
101
|
+
* continuation lines indented 3 spaces. Pass '' for a blank separator line.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} header - Header line (printed after purgeLabel)
|
|
104
|
+
* @param {...string} lines - Continuation lines (indented, or '' for blank)
|
|
105
|
+
*/
|
|
106
|
+
block: function(header, ...lines) {
|
|
107
|
+
console.log(purgeLabel, header)
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
console.log(line === '' ? '' : ' ' + line)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Log a sub-line without the ::PurgeTSS:: prefix, indented 3 spaces.
|
|
115
|
+
* Use inside sequential flows where a prior logger.info emitted the header.
|
|
116
|
+
* Prints synchronously, preserving timing with logger.info.
|
|
117
|
+
*
|
|
118
|
+
* @param {...any} args - Arguments to log
|
|
119
|
+
*/
|
|
120
|
+
item: function(...args) {
|
|
121
|
+
console.log(' ' + args.join(' '))
|
|
57
122
|
}
|
|
58
123
|
}
|
|
59
124
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PurgeTSS - Interactive prompt helpers
|
|
3
|
+
*
|
|
4
|
+
* Small wrapper around Node's readline for yes/no confirmations on destructive
|
|
5
|
+
* operations (in-place brand/images writes). Auto-skips when:
|
|
6
|
+
* - the caller sets `yes: true` (e.g. from --yes flag)
|
|
7
|
+
* - the PURGETSS_YES=1 environment variable is set
|
|
8
|
+
* - stdin is not a TTY (CI, hooks, piped input)
|
|
9
|
+
*
|
|
10
|
+
* Default answer is NO on empty input — requires explicit "y" / "yes" to proceed.
|
|
11
|
+
*
|
|
12
|
+
* @fileoverview TTY-aware confirmation prompts
|
|
13
|
+
* @author César Estrada
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import readline from 'node:readline/promises'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ask the user for a yes/no confirmation.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} message - Prompt text shown to the user. A trailing space is
|
|
22
|
+
* added automatically, so pass something like "Continue? [y/N]".
|
|
23
|
+
* @param {Object} [opts]
|
|
24
|
+
* @param {boolean} [opts.yes=false] - Skip prompt and answer yes (e.g. --yes flag).
|
|
25
|
+
* @returns {Promise<boolean>} True when confirmed or skipped; false otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export async function confirm(message, { yes = false } = {}) {
|
|
28
|
+
if (yes) return true
|
|
29
|
+
if (process.env.PURGETSS_YES === '1') return true
|
|
30
|
+
if (!process.stdin.isTTY) return true
|
|
31
|
+
|
|
32
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
33
|
+
try {
|
|
34
|
+
const answer = await rl.question(`${message} `)
|
|
35
|
+
return /^y(es)?$/i.test(answer.trim())
|
|
36
|
+
} finally {
|
|
37
|
+
rl.close()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Three-way confirmation: yes / no / always. Same skip rules as confirm()
|
|
43
|
+
* (--yes flag, PURGETSS_YES env, non-TTY all resolve to 'yes').
|
|
44
|
+
*
|
|
45
|
+
* @param {string} message - Prompt text (a trailing space is added).
|
|
46
|
+
* @param {Object} [opts]
|
|
47
|
+
* @param {boolean} [opts.yes=false] - Skip prompt, answer 'yes'.
|
|
48
|
+
* @returns {Promise<'yes'|'no'|'always'>} The user's choice.
|
|
49
|
+
*/
|
|
50
|
+
export async function confirmWithAlways(message, { yes = false } = {}) {
|
|
51
|
+
if (yes) return 'yes'
|
|
52
|
+
if (process.env.PURGETSS_YES === '1') return 'yes'
|
|
53
|
+
if (!process.stdin.isTTY) return 'yes'
|
|
54
|
+
|
|
55
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
56
|
+
try {
|
|
57
|
+
const answer = (await rl.question(`${message} `)).trim().toLowerCase()
|
|
58
|
+
if (answer === 'a' || answer === 'always') return 'always'
|
|
59
|
+
if (answer === 'y' || answer === 'yes') return 'yes'
|
|
60
|
+
return 'no'
|
|
61
|
+
} finally {
|
|
62
|
+
rl.close()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PurgeTSS - Shared SVG/Sharp utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the logic for safely rasterizing SVGs with Sharp — specifically,
|
|
5
|
+
* handling SVGs that come out of vector editors (Affinity, Illustrator) with
|
|
6
|
+
* absurdly large viewBoxes that would otherwise trigger Sharp's pixel limit.
|
|
7
|
+
*
|
|
8
|
+
* Shared by:
|
|
9
|
+
* - src/core/branding/prepare-master.js (brand pipeline)
|
|
10
|
+
* - src/core/images/gen-scales.js (images pipeline)
|
|
11
|
+
*
|
|
12
|
+
* @fileoverview SVG rasterization helpers for the Sharp pipeline
|
|
13
|
+
* @author César Estrada
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'fs'
|
|
17
|
+
import path from 'path'
|
|
18
|
+
import sharp from 'sharp'
|
|
19
|
+
|
|
20
|
+
// SVGs with natural dimensions above this (in points) are almost always the
|
|
21
|
+
// result of a vector editor baking in transforms — e.g. Affinity exporting a
|
|
22
|
+
// "pliego" as 29559×13542 pt. Callers compensate with adaptive density; this
|
|
23
|
+
// threshold only controls the user-facing warning.
|
|
24
|
+
export const VIEWBOX_WARN_THRESHOLD = 4096
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute a Sharp density (DPI) so that rasterization of an SVG lands at
|
|
28
|
+
* approximately `targetSupersampledMax` pixels on the longest side, regardless
|
|
29
|
+
* of the SVG's intrinsic viewBox. Clamped to Sharp's valid range (1..2400).
|
|
30
|
+
*
|
|
31
|
+
* @param {number} naturalMax - Largest SVG dimension in points (at 72 DPI).
|
|
32
|
+
* @param {number} targetSupersampledMax - Desired raster size on the longest side.
|
|
33
|
+
* @returns {number} A safe density value in [1, 2400].
|
|
34
|
+
*/
|
|
35
|
+
export function computeSvgDensity(naturalMax, targetSupersampledMax) {
|
|
36
|
+
return Math.min(
|
|
37
|
+
2400,
|
|
38
|
+
Math.max(1, Math.round((targetSupersampledMax / naturalMax) * 72))
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read an SVG safely — buffer + metadata with the pixel-limit check disabled
|
|
44
|
+
* (Sharp enforces it even during header parsing for SVG inputs). Optionally
|
|
45
|
+
* emits a warning via the provided logger when the viewBox is disproportionate.
|
|
46
|
+
*
|
|
47
|
+
* The caller is responsible for computing a bounded density (via
|
|
48
|
+
* computeSvgDensity) before actually rendering — this keeps the real pixel
|
|
49
|
+
* output inside Sharp's limit even though the limit was disabled at read time.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} svgPath - Absolute path to the SVG file.
|
|
52
|
+
* @param {Object} [opts]
|
|
53
|
+
* @param {Object} [opts.logger] - Logger exposing a .warning(msg) method. If
|
|
54
|
+
* omitted, no warning is emitted (silent mode, for unit tests).
|
|
55
|
+
* @param {boolean} [opts.withAdvice=false] - Include the "re-export" advice
|
|
56
|
+
* line. Enable for the brand pipeline (where the logo is central); keep
|
|
57
|
+
* false for the images pipeline (which may process many files).
|
|
58
|
+
* @returns {Promise<{buffer: Buffer, meta: Object, naturalMax: number}>}
|
|
59
|
+
*/
|
|
60
|
+
export async function readSvgSafely(svgPath, { logger, withAdvice = false } = {}) {
|
|
61
|
+
const buffer = fs.readFileSync(svgPath)
|
|
62
|
+
const meta = await sharp(buffer, { limitInputPixels: false }).metadata()
|
|
63
|
+
const naturalMax = Math.max(meta.width, meta.height)
|
|
64
|
+
|
|
65
|
+
if (logger && naturalMax > VIEWBOX_WARN_THRESHOLD) {
|
|
66
|
+
logger.warning(
|
|
67
|
+
`⚠ ${path.basename(svgPath)} has a disproportionate viewBox (${meta.width}×${meta.height} pt).`
|
|
68
|
+
)
|
|
69
|
+
if (withAdvice) {
|
|
70
|
+
logger.warning(
|
|
71
|
+
` Re-export from your vector editor with a canvas-sized viewBox if possible.`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
logger.warning(
|
|
75
|
+
` Adapting rasterization density to compensate.`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { buffer, meta, naturalMax }
|
|
80
|
+
}
|
package/src/shared/utils.js
CHANGED
|
@@ -73,8 +73,10 @@ export function alloyProject(silent = false) {
|
|
|
73
73
|
|
|
74
74
|
if (!fs.existsSync(`${cwd}/app/views`)) {
|
|
75
75
|
if (!silent) {
|
|
76
|
-
logger.
|
|
77
|
-
|
|
76
|
+
logger.block(
|
|
77
|
+
`Please make sure you are running ${chalk.green('purgetss')} within an Alloy Project.`,
|
|
78
|
+
`For more information, visit ${chalk.green('https://purgetss.com')}`
|
|
79
|
+
)
|
|
78
80
|
}
|
|
79
81
|
return false
|
|
80
82
|
}
|
|
@@ -93,8 +95,10 @@ export function classicProject(silent = false) {
|
|
|
93
95
|
|
|
94
96
|
if (!fs.existsSync(`${cwd}/Resources`)) {
|
|
95
97
|
if (!silent) {
|
|
96
|
-
logger.
|
|
97
|
-
|
|
98
|
+
logger.block(
|
|
99
|
+
`Please make sure you are running ${chalk.green('purgetss')} within a Titanium's Classic Project.`,
|
|
100
|
+
`For more information, visit ${chalk.green('https://purgetss.com')}`
|
|
101
|
+
)
|
|
98
102
|
}
|
|
99
103
|
return false
|
|
100
104
|
}
|