@gesslar/sassy 0.20.0 → 0.21.0
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/package.json +12 -8
- package/src/BuildCommand.js +8 -9
- package/src/Cache.js +1 -0
- package/src/Colour.js +8 -2
- package/src/Command.js +92 -32
- package/src/Compiler.js +62 -36
- package/src/Data.js +24 -14
- package/src/Evaluator.js +8 -4
- package/src/File.js +14 -2
- package/src/LintCommand.js +381 -103
- package/src/ResolveCommand.js +44 -27
- package/src/Sass.js +2 -1
- package/src/Session.js +211 -42
- package/src/Term.js +7 -7
- package/src/Theme.js +378 -53
- package/src/ThemePool.js +1 -1
- package/src/ThemeToken.js +1 -0
- package/src/Type.js +11 -10
- package/src/Util.js +2 -2
- package/src/Valid.js +1 -1
- package/src/cli.js +27 -15
package/src/ResolveCommand.js
CHANGED
|
@@ -10,7 +10,6 @@ import Theme from "./Theme.js"
|
|
|
10
10
|
import Util from "./Util.js"
|
|
11
11
|
import Data from "./Data.js"
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
// ansiColors.enabled = colorSupport.hasBasic
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -26,12 +25,12 @@ export default class ResolveCommand extends Command {
|
|
|
26
25
|
constructor(base) {
|
|
27
26
|
super(base)
|
|
28
27
|
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
28
|
+
this.setCliCommand("resolve <file>")
|
|
29
|
+
this.setCliOptions({
|
|
31
30
|
"color": ["-c, --color <key>", "resolve a color key to its final evaluated value"],
|
|
32
31
|
"tokenColor": ["-t, --tokenColor <scope>", "resolve a tokenColors scope to its final evaluated value"],
|
|
33
32
|
"semanticTokenColor": ["-s, --semanticTokenColor <scope>", "resolve a semanticTokenColors scope to its final evaluated value"],
|
|
34
|
-
}
|
|
33
|
+
})
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
@@ -43,22 +42,23 @@ export default class ResolveCommand extends Command {
|
|
|
43
42
|
* @returns {Promise<void>} Resolves when resolution is complete
|
|
44
43
|
*/
|
|
45
44
|
async execute(inputArg, options={}) {
|
|
45
|
+
const cliOptionNames = this.getCliOptionNames()
|
|
46
46
|
const intersection =
|
|
47
|
-
Data.arrayIntersection(
|
|
47
|
+
Data.arrayIntersection(cliOptionNames, Object.keys(options))
|
|
48
48
|
|
|
49
49
|
if(intersection.length > 1)
|
|
50
50
|
throw Sass.new(
|
|
51
|
-
`The options ${
|
|
51
|
+
`The options ${cliOptionNames.join(", ")} are ` +
|
|
52
52
|
`mutually exclusive and may only have one expressed in the request.`
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const cwd = this.getCwd()
|
|
56
56
|
const optionName = Object.keys(options??{})
|
|
57
|
-
.find(o =>
|
|
57
|
+
.find(o => cliOptionNames.includes(o))
|
|
58
58
|
|
|
59
59
|
if(!optionName) {
|
|
60
60
|
throw Sass.new(
|
|
61
|
-
`No valid option provided. Please specify one of: ${
|
|
61
|
+
`No valid option provided. Please specify one of: ${cliOptionNames.join(", ")}.`
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -71,7 +71,8 @@ export default class ResolveCommand extends Command {
|
|
|
71
71
|
|
|
72
72
|
const fileObject = await this.resolveThemeFileName(inputArg, cwd)
|
|
73
73
|
const theme = new Theme(fileObject, cwd, options)
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
theme.setCache(this.getCache())
|
|
75
76
|
|
|
76
77
|
await theme.load()
|
|
77
78
|
await theme.build()
|
|
@@ -88,11 +89,12 @@ export default class ResolveCommand extends Command {
|
|
|
88
89
|
* @returns {void}
|
|
89
90
|
*/
|
|
90
91
|
async resolveColor(theme, colorName) {
|
|
91
|
-
const pool = theme.
|
|
92
|
+
const pool = theme.getPool()
|
|
93
|
+
|
|
92
94
|
if(!pool || !pool.has(colorName))
|
|
93
95
|
return Term.info(`'${colorName}' not found.`)
|
|
94
96
|
|
|
95
|
-
const tokens = pool.getTokens
|
|
97
|
+
const tokens = pool.getTokens()
|
|
96
98
|
const token = tokens.get(colorName)
|
|
97
99
|
const trail = token.getTrail()
|
|
98
100
|
const fullTrail = this.#buildCompleteTrail(token, trail)
|
|
@@ -114,7 +116,7 @@ export default class ResolveCommand extends Command {
|
|
|
114
116
|
* @returns {void}
|
|
115
117
|
*/
|
|
116
118
|
async resolveTokenColor(theme, scopeName) {
|
|
117
|
-
const tokenColors = theme.
|
|
119
|
+
const tokenColors = theme.getOutput()?.tokenColors || []
|
|
118
120
|
|
|
119
121
|
// Check if this is a disambiguated scope (ends with .1, .2, etc.)
|
|
120
122
|
const disambiguatedMatch = scopeName.match(/^(.+)\.(\d+)$/)
|
|
@@ -127,7 +129,9 @@ export default class ResolveCommand extends Command {
|
|
|
127
129
|
|
|
128
130
|
if(index >= 0 && index < matches.length) {
|
|
129
131
|
const match = matches[index]
|
|
132
|
+
|
|
130
133
|
await this.#resolveScopeMatch(theme, match, `${baseScope}.${indexStr}`)
|
|
134
|
+
|
|
131
135
|
return
|
|
132
136
|
} else {
|
|
133
137
|
return Term.info(`'${scopeName}' not found. Available: ${baseScope}.1 through ${baseScope}.${matches.length}`)
|
|
@@ -149,6 +153,7 @@ export default class ResolveCommand extends Command {
|
|
|
149
153
|
Term.info(`Multiple entries found for '${scopeName}', please try again with the specific query:\n`)
|
|
150
154
|
matches.forEach((match, index) => {
|
|
151
155
|
const name = match.name || `Entry ${index + 1}`
|
|
156
|
+
|
|
152
157
|
Term.info(`${name}: ${scopeName}.${index + 1}`)
|
|
153
158
|
})
|
|
154
159
|
}
|
|
@@ -161,24 +166,26 @@ export default class ResolveCommand extends Command {
|
|
|
161
166
|
|
|
162
167
|
// Handle comma-separated scopes
|
|
163
168
|
const scopes = entry.scope.split(",").map(s => s.trim())
|
|
169
|
+
|
|
164
170
|
return scopes.includes(targetScope)
|
|
165
171
|
})
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
async #resolveScopeMatch(theme, match, displayName) {
|
|
169
|
-
const pool = theme.
|
|
175
|
+
const pool = theme.getPool()
|
|
170
176
|
const settings = match.settings || {}
|
|
171
177
|
const name = match.name || "Unnamed"
|
|
172
178
|
|
|
173
179
|
// Look for the foreground property specifically
|
|
174
180
|
const foreground = settings.foreground
|
|
181
|
+
|
|
175
182
|
if(!foreground) {
|
|
176
183
|
return Term.info(`${displayName} (${name})\n\n(no foreground property)`)
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
// First, try to find the token by looking for variables that resolve to this value
|
|
180
187
|
// but prioritize source variable names over computed results
|
|
181
|
-
const tokens = pool ? pool.getTokens : new Map()
|
|
188
|
+
const tokens = pool ? pool.getTokens() : new Map()
|
|
182
189
|
let bestToken = null
|
|
183
190
|
|
|
184
191
|
// First try to find a scope.* token that matches
|
|
@@ -205,16 +212,18 @@ export default class ResolveCommand extends Command {
|
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
if(!bestToken)
|
|
209
|
-
return Term.info(
|
|
210
|
-
|
|
215
|
+
if(!bestToken)
|
|
216
|
+
return Term.info(
|
|
217
|
+
`${displayName} (${name})\n\n(resolved to static value: ${foreground})`
|
|
218
|
+
)
|
|
211
219
|
|
|
212
220
|
const trail = bestToken.getTrail()
|
|
213
221
|
const fullTrail = this.#buildCompleteTrail(bestToken, trail)
|
|
214
222
|
const finalValue = bestToken.getValue()
|
|
215
223
|
const [formattedFinalValue] = this.#formatLeaf(finalValue)
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
const output = c`{head}${displayName}{/} {hex}${(`${name}`)}{/}\n`+
|
|
225
|
+
`${this.#formatOutput(fullTrail)}\n\n{head}`+
|
|
226
|
+
`${"Resolution:"}{/} ${formattedFinalValue}`
|
|
218
227
|
|
|
219
228
|
Term.info(output)
|
|
220
229
|
}
|
|
@@ -230,18 +239,19 @@ export default class ResolveCommand extends Command {
|
|
|
230
239
|
async resolveSemanticTokenColor(theme, scopeName) {
|
|
231
240
|
// semanticTokenColors has the same structure as tokenColors, so we can reuse the logic
|
|
232
241
|
// but we need to look at the semanticTokenColors array instead
|
|
233
|
-
const originalTokenColors = theme.
|
|
242
|
+
const originalTokenColors = theme.getOutput()?.tokenColors
|
|
234
243
|
|
|
235
244
|
// Temporarily replace tokenColors with semanticTokenColors for resolution
|
|
236
|
-
|
|
237
|
-
|
|
245
|
+
const themeOutput = theme.getOutput()
|
|
246
|
+
if(themeOutput?.semanticTokenColors) {
|
|
247
|
+
themeOutput.tokenColors = themeOutput.semanticTokenColors
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
await this.resolveTokenColor(theme, scopeName)
|
|
241
251
|
|
|
242
252
|
// Restore original tokenColors
|
|
243
|
-
if(originalTokenColors) {
|
|
244
|
-
|
|
253
|
+
if(originalTokenColors && themeOutput) {
|
|
254
|
+
themeOutput.tokenColors = originalTokenColors
|
|
245
255
|
}
|
|
246
256
|
}
|
|
247
257
|
|
|
@@ -251,6 +261,7 @@ export default class ResolveCommand extends Command {
|
|
|
251
261
|
|
|
252
262
|
// Add the root token's original expression
|
|
253
263
|
const rootRaw = rootToken.getRawValue()
|
|
264
|
+
|
|
254
265
|
if(rootRaw !== rootToken.getName()) {
|
|
255
266
|
steps.push({
|
|
256
267
|
value: rootRaw,
|
|
@@ -265,6 +276,7 @@ export default class ResolveCommand extends Command {
|
|
|
265
276
|
return
|
|
266
277
|
|
|
267
278
|
const id = `${token.getName()}-${token.getRawValue()}`
|
|
279
|
+
|
|
268
280
|
if(seen.has(id))
|
|
269
281
|
return
|
|
270
282
|
|
|
@@ -290,7 +302,8 @@ export default class ResolveCommand extends Command {
|
|
|
290
302
|
const depFinal = dependency.getValue()
|
|
291
303
|
|
|
292
304
|
// Add dependency's expression if it's a function call
|
|
293
|
-
if(depRaw !== dependency.getName() &&
|
|
305
|
+
if(depRaw !== dependency.getName() &&
|
|
306
|
+
!steps.some(s => s.value === depRaw)) {
|
|
294
307
|
steps.push({
|
|
295
308
|
value: depRaw,
|
|
296
309
|
type: "expression",
|
|
@@ -300,6 +313,7 @@ export default class ResolveCommand extends Command {
|
|
|
300
313
|
|
|
301
314
|
// Process dependency's trail
|
|
302
315
|
const depTrail = dependency.getTrail()
|
|
316
|
+
|
|
303
317
|
if(depTrail && depTrail.length > 0) {
|
|
304
318
|
depTrail.forEach(depToken => processToken(depToken, level + 1))
|
|
305
319
|
}
|
|
@@ -372,10 +386,12 @@ export default class ResolveCommand extends Command {
|
|
|
372
386
|
// Hex results are indented one extra level with just spaces and arrow
|
|
373
387
|
const prefix = " ".repeat(depth + 1)
|
|
374
388
|
const arrow = c`{arrow}→{/} `
|
|
389
|
+
|
|
375
390
|
out.push(`${prefix}${arrow}${line}`)
|
|
376
391
|
} else {
|
|
377
392
|
// Everything else just gets clean indentation
|
|
378
393
|
const prefix = " ".repeat(depth)
|
|
394
|
+
|
|
379
395
|
out.push(`${prefix}${line}`)
|
|
380
396
|
}
|
|
381
397
|
})
|
|
@@ -411,17 +427,18 @@ export default class ResolveCommand extends Command {
|
|
|
411
427
|
|
|
412
428
|
if(this.#func.test(value)) {
|
|
413
429
|
const {func,args} = this.#func.exec(value).groups
|
|
430
|
+
|
|
414
431
|
return [
|
|
415
432
|
c`{func}${func}{/}{parens}${"("}{/}{leaf}${args}{/}{parens}${")"}{/}`,
|
|
416
433
|
"function"
|
|
417
434
|
]
|
|
418
435
|
}
|
|
419
436
|
|
|
420
|
-
|
|
421
437
|
if(this.#sub.test(value)) {
|
|
422
438
|
const {parens,none,braces} = Evaluator.sub.exec(value)?.groups || {}
|
|
423
439
|
const style = (braces && ["{","}"]) || (parens && ["(",")"]) || (none && ["",""])
|
|
424
440
|
const varValue = braces || parens || none || value
|
|
441
|
+
|
|
425
442
|
return [
|
|
426
443
|
c`{func}{/}{parens}${style[0]}{/}{leaf}${varValue}{/}{parens}${style[1]}{/}`,
|
|
427
444
|
"variable"
|
package/src/Sass.js
CHANGED
|
@@ -24,7 +24,7 @@ export default class Sass extends Error {
|
|
|
24
24
|
* Creates a new Sass instance.
|
|
25
25
|
*
|
|
26
26
|
* @param {string} message - The error message
|
|
27
|
-
* @param {...
|
|
27
|
+
* @param {...unknown} [arg] - Additional arguments passed to parent Error constructor
|
|
28
28
|
*/
|
|
29
29
|
constructor(message, ...arg) {
|
|
30
30
|
super(message, ...arg)
|
|
@@ -115,6 +115,7 @@ export default class Sass extends Error {
|
|
|
115
115
|
.split("\n")
|
|
116
116
|
.map(line => {
|
|
117
117
|
const at = line.match(/^\s{4}at\s(?<at>.*)$/)?.groups?.at ?? {}
|
|
118
|
+
|
|
118
119
|
return at
|
|
119
120
|
? `* ${at}`
|
|
120
121
|
: line
|
package/src/Session.js
CHANGED
|
@@ -7,19 +7,156 @@ import Term from "./Term.js"
|
|
|
7
7
|
import Theme from "./Theme.js"
|
|
8
8
|
import Util from "./Util.js"
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} SessionOptions
|
|
12
|
+
* @property {boolean} [watch] - Whether to enable file watching
|
|
13
|
+
* @property {boolean} [nerd] - Whether to show verbose output
|
|
14
|
+
* @property {boolean} [dryRun] - Whether to skip file writes
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} BuildRecord
|
|
19
|
+
* @property {number} timestamp - Epoch ms when the build started
|
|
20
|
+
* @property {number} loadTime - Time (ms) spent loading theme sources
|
|
21
|
+
* @property {number} buildTime - Time (ms) spent compiling the theme
|
|
22
|
+
* @property {number} writeTime - Time (ms) spent writing the output file
|
|
23
|
+
* @property {boolean} success - Whether the build completed successfully
|
|
24
|
+
* @property {string} [error] - Error message when success is false
|
|
25
|
+
*/
|
|
26
|
+
|
|
10
27
|
export default class Session {
|
|
28
|
+
/**
|
|
29
|
+
* The theme instance managed by this session.
|
|
30
|
+
*
|
|
31
|
+
* @type {Theme|null}
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
11
34
|
#theme = null
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The parent command orchestrating this session.
|
|
38
|
+
*
|
|
39
|
+
* @type {Command|null}
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
12
42
|
#command = null
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build configuration options for this session.
|
|
46
|
+
*
|
|
47
|
+
* @type {SessionOptions|null}
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
13
50
|
#options = null
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Active file system watcher for theme dependencies.
|
|
54
|
+
*
|
|
55
|
+
* @type {import("chokidar").FSWatcher|null}
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
14
58
|
#watcher = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Historical records of builds executed during this session.
|
|
62
|
+
*
|
|
63
|
+
* @type {Array<BuildRecord>}
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
15
66
|
#history = []
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Cumulative build statistics for this session.
|
|
70
|
+
*
|
|
71
|
+
* @type {{builds: number, failures: number}}
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
16
74
|
#stats = Object.seal({builds: 0, failures: 0})
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Flag indicating whether a build is currently in progress.
|
|
78
|
+
*
|
|
79
|
+
* @type {boolean}
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
17
82
|
#building = false
|
|
18
83
|
|
|
19
84
|
get theme() {
|
|
20
85
|
return this.#theme
|
|
21
86
|
}
|
|
22
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Gets the theme instance managed by this session.
|
|
90
|
+
*
|
|
91
|
+
* @returns {Theme} The theme instance
|
|
92
|
+
*/
|
|
93
|
+
getTheme() {
|
|
94
|
+
return this.#theme
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gets the command instance orchestrating this session.
|
|
99
|
+
*
|
|
100
|
+
* @returns {Command} The command instance
|
|
101
|
+
*/
|
|
102
|
+
getCommand() {
|
|
103
|
+
return this.#command
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the session options.
|
|
108
|
+
*
|
|
109
|
+
* @returns {SessionOptions} The session options
|
|
110
|
+
*/
|
|
111
|
+
getOptions() {
|
|
112
|
+
return this.#options
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets the build history for this session.
|
|
117
|
+
*
|
|
118
|
+
* @returns {Array<BuildRecord>} Array of build records
|
|
119
|
+
*/
|
|
120
|
+
getHistory() {
|
|
121
|
+
return this.#history
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Gets the build statistics for this session.
|
|
126
|
+
*
|
|
127
|
+
* @returns {{builds: number, failures: number}} Build statistics
|
|
128
|
+
*/
|
|
129
|
+
getStats() {
|
|
130
|
+
return this.#stats
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks if a build is currently in progress.
|
|
135
|
+
*
|
|
136
|
+
* @returns {boolean} True if building
|
|
137
|
+
*/
|
|
138
|
+
isBuilding() {
|
|
139
|
+
return this.#building
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks if watch mode is enabled.
|
|
144
|
+
*
|
|
145
|
+
* @returns {boolean} True if watching
|
|
146
|
+
*/
|
|
147
|
+
isWatching() {
|
|
148
|
+
return this.#options?.watch === true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Checks if there's an active file watcher.
|
|
153
|
+
*
|
|
154
|
+
* @returns {boolean} True if watcher exists
|
|
155
|
+
*/
|
|
156
|
+
hasWatcher() {
|
|
157
|
+
return this.#watcher !== null
|
|
158
|
+
}
|
|
159
|
+
|
|
23
160
|
/**
|
|
24
161
|
* Creates a new Session instance for managing theme compilation lifecycle.
|
|
25
162
|
* Sessions provide persistent state across rebuilds, error tracking, and
|
|
@@ -27,10 +164,7 @@ export default class Session {
|
|
|
27
164
|
*
|
|
28
165
|
* @param {Command} command - The parent build command instance
|
|
29
166
|
* @param {Theme} theme - The theme instance to manage
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {boolean} [options.watch] - Whether to enable file watching
|
|
32
|
-
* @param {boolean} [options.nerd] - Whether to show verbose output
|
|
33
|
-
* @param {boolean} [options.dryRun] - Whether to skip file writes
|
|
167
|
+
* @param {SessionOptions} options - Build configuration options
|
|
34
168
|
*/
|
|
35
169
|
constructor(command, theme, options) {
|
|
36
170
|
this.#command = command
|
|
@@ -39,6 +173,15 @@ export default class Session {
|
|
|
39
173
|
}
|
|
40
174
|
|
|
41
175
|
async run() {
|
|
176
|
+
|
|
177
|
+
this.#building = true
|
|
178
|
+
await this.#command.asyncEmit("building")
|
|
179
|
+
this.#command.asyncEmit("recordBuildStart", this.#theme)
|
|
180
|
+
await this.#buildPipeline()
|
|
181
|
+
|
|
182
|
+
// This must come after, or you will fuck up the watching!
|
|
183
|
+
// Themes won't have their dependencies yet unless you build them
|
|
184
|
+
// at least once. - Samwise Gamgee
|
|
42
185
|
if(this.#options.watch) {
|
|
43
186
|
this.#command.emitter.on("closeSession", async() =>
|
|
44
187
|
await this.#handleCloseSession())
|
|
@@ -54,11 +197,6 @@ export default class Session {
|
|
|
54
197
|
|
|
55
198
|
await this.#resetWatcher()
|
|
56
199
|
}
|
|
57
|
-
|
|
58
|
-
this.#building = true
|
|
59
|
-
await this.#command.asyncEmit("building")
|
|
60
|
-
this.#command.asyncEmit("recordBuildStart", this.#theme)
|
|
61
|
-
await this.#buildPipeline()
|
|
62
200
|
}
|
|
63
201
|
|
|
64
202
|
/**
|
|
@@ -84,11 +222,12 @@ export default class Session {
|
|
|
84
222
|
*/
|
|
85
223
|
|
|
86
224
|
loadCost = (await Util.time(() => this.#theme.load())).cost
|
|
87
|
-
const bytes = await File.fileSize(this.#theme.
|
|
225
|
+
const bytes = await File.fileSize(this.#theme.getSourceFile())
|
|
226
|
+
|
|
88
227
|
Term.status([
|
|
89
228
|
["success", Util.rightAlignText(`${loadCost.toLocaleString()}ms`, 10), ["[","]"]],
|
|
90
|
-
`${this.#theme.
|
|
91
|
-
["info", `${bytes} bytes`, ["[","]"]]
|
|
229
|
+
`${this.#theme.getName()} loaded`,
|
|
230
|
+
["info", `${bytes.toLocaleString()} bytes`, ["[","]"]]
|
|
92
231
|
], this.#options)
|
|
93
232
|
/**
|
|
94
233
|
* ****************************************************************
|
|
@@ -97,30 +236,48 @@ export default class Session {
|
|
|
97
236
|
*/
|
|
98
237
|
|
|
99
238
|
buildCost = (await Util.time(() => this.#theme.build())).cost
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
239
|
+
const dependencyFiles = Array
|
|
240
|
+
.from(this.#theme.getDependencies())
|
|
241
|
+
.map(d => d.getSourceFile())
|
|
242
|
+
.filter(f => f != null) // Filter out any null/undefined files
|
|
243
|
+
|
|
244
|
+
const compileResult = await Promise
|
|
245
|
+
.allSettled(dependencyFiles.map(async fileObject => {
|
|
246
|
+
if(!fileObject) {
|
|
247
|
+
throw new Error("Invalid dependency file object")
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const fileName = File.relativeOrAbsolutePath(
|
|
251
|
+
this.#command.getCwd(), fileObject
|
|
252
|
+
)
|
|
253
|
+
const fileSize = await File.fileSize(fileObject)
|
|
254
|
+
|
|
255
|
+
return [fileName, fileSize]
|
|
110
256
|
}))
|
|
111
257
|
|
|
112
258
|
const rejected = compileResult.filter(result => result.status === "rejected")
|
|
259
|
+
|
|
113
260
|
if(rejected.length > 0) {
|
|
114
261
|
rejected.forEach(reject => Term.error(reject.reason))
|
|
115
262
|
throw new Error("Compilation failed")
|
|
116
263
|
}
|
|
117
264
|
|
|
118
|
-
const dependencies = compileResult
|
|
119
|
-
|
|
265
|
+
const dependencies = compileResult
|
|
266
|
+
.slice(1)
|
|
267
|
+
.map(dep => dep?.value)
|
|
268
|
+
.filter(Boolean)
|
|
269
|
+
|
|
270
|
+
const totalBytes = compileResult.reduce(
|
|
271
|
+
(acc,curr) => acc + (curr?.value[1] ?? 0), 0
|
|
272
|
+
)
|
|
120
273
|
|
|
121
274
|
Term.status([
|
|
122
|
-
[
|
|
123
|
-
|
|
275
|
+
[
|
|
276
|
+
"success",
|
|
277
|
+
Util.rightAlignText(`${buildCost.toLocaleString()}ms`, 10),
|
|
278
|
+
["[","]"]
|
|
279
|
+
],
|
|
280
|
+
`${this.#theme.getName()} compiled`,
|
|
124
281
|
["success", `${compileResult[0].value[1].toLocaleString()} bytes`, ["[","]"]],
|
|
125
282
|
["info", `${totalBytes.toLocaleString()} total bytes`, ["(",")"]],
|
|
126
283
|
], this.#options)
|
|
@@ -146,19 +303,25 @@ export default class Session {
|
|
|
146
303
|
*/
|
|
147
304
|
|
|
148
305
|
const writeResult = await Util.time(() => this.#theme.write(forceWrite))
|
|
306
|
+
|
|
149
307
|
writeCost = writeResult.cost
|
|
150
|
-
const result = writeResult.result
|
|
151
308
|
const {
|
|
152
309
|
status: writeStatus,
|
|
153
310
|
file: outputFile,
|
|
154
311
|
bytes: writeBytes
|
|
155
|
-
} = result
|
|
156
|
-
const outputFilename = File.relativeOrAbsolutePath(
|
|
312
|
+
} = writeResult.result
|
|
313
|
+
const outputFilename = File.relativeOrAbsolutePath(
|
|
314
|
+
this.#command.getCwd(), outputFile
|
|
315
|
+
)
|
|
157
316
|
const status = [
|
|
158
|
-
[
|
|
317
|
+
[
|
|
318
|
+
"success",
|
|
319
|
+
Util.rightAlignText(`${writeCost.toLocaleString()}ms`, 10),
|
|
320
|
+
["[","]"]
|
|
321
|
+
],
|
|
159
322
|
]
|
|
160
323
|
|
|
161
|
-
if(writeStatus === "written") {
|
|
324
|
+
if(writeStatus.description === "written") {
|
|
162
325
|
status.push(
|
|
163
326
|
`${outputFilename} written`,
|
|
164
327
|
["success", `${writeBytes.toLocaleString()} bytes`, ["[","]"]]
|
|
@@ -166,7 +329,7 @@ export default class Session {
|
|
|
166
329
|
} else {
|
|
167
330
|
status.push(
|
|
168
331
|
`${outputFilename}`,
|
|
169
|
-
["warn", writeStatus.toLocaleUpperCase(), ["[","]"]]
|
|
332
|
+
["warn", writeStatus.description.toLocaleUpperCase(), ["[","]"]]
|
|
170
333
|
)
|
|
171
334
|
}
|
|
172
335
|
|
|
@@ -194,8 +357,7 @@ export default class Session {
|
|
|
194
357
|
error: error.message
|
|
195
358
|
})
|
|
196
359
|
|
|
197
|
-
|
|
198
|
-
error.report(this.#options.nerd)
|
|
360
|
+
Sass.new("Build process failed.", error).report(this.#options.nerd)
|
|
199
361
|
} finally {
|
|
200
362
|
this.#building = false
|
|
201
363
|
this.#command.asyncEmit("finishedBuilding")
|
|
@@ -206,9 +368,10 @@ export default class Session {
|
|
|
206
368
|
* Handles a file change event and triggers a rebuild for the theme.
|
|
207
369
|
*
|
|
208
370
|
* @param {string} changed - Path to the changed file
|
|
371
|
+
* @param {object} _stats - OS-level file stat information
|
|
209
372
|
* @returns {Promise<void>}
|
|
210
373
|
*/
|
|
211
|
-
async #handleFileChange(changed) {
|
|
374
|
+
async #handleFileChange(changed, _stats) {
|
|
212
375
|
try {
|
|
213
376
|
if(this.#building)
|
|
214
377
|
return
|
|
@@ -216,16 +379,20 @@ export default class Session {
|
|
|
216
379
|
this.#building = true
|
|
217
380
|
this.#command.asyncEmit("building")
|
|
218
381
|
|
|
219
|
-
const changedFile = this.#theme.
|
|
382
|
+
const changedFile = Array.from(this.#theme.getDependencies()).find(
|
|
383
|
+
dep => dep.getSourceFile().path === changed
|
|
384
|
+
)?.getSourceFile()
|
|
220
385
|
|
|
221
386
|
if(!changedFile)
|
|
222
387
|
return
|
|
223
388
|
|
|
224
|
-
const fileName = File.relativeOrAbsolutePath(
|
|
389
|
+
const fileName = File.relativeOrAbsolutePath(
|
|
390
|
+
this.#command.getCwd(), changedFile
|
|
391
|
+
)
|
|
225
392
|
|
|
226
393
|
const message = [
|
|
227
394
|
["info", "REBUILDING", ["[","]"]],
|
|
228
|
-
this.#theme.
|
|
395
|
+
this.#theme.getName(),
|
|
229
396
|
]
|
|
230
397
|
|
|
231
398
|
if(this.#options.nerd)
|
|
@@ -257,7 +424,7 @@ export default class Session {
|
|
|
257
424
|
|
|
258
425
|
Term.status([
|
|
259
426
|
[builds > 0 ? "success" : "error", "SESSION SUMMARY"],
|
|
260
|
-
[builds > 0 ? "info" : "error", this.#theme.
|
|
427
|
+
[builds > 0 ? "info" : "error", this.#theme.getName(), ["[", "]"]]
|
|
261
428
|
], this.#options)
|
|
262
429
|
|
|
263
430
|
Term.status([
|
|
@@ -272,7 +439,8 @@ export default class Session {
|
|
|
272
439
|
|
|
273
440
|
if(this.#history.length > 0) {
|
|
274
441
|
const lastBuild = this.#history[this.#history.length - 1]
|
|
275
|
-
const totalTime = lastBuild.loadTime +
|
|
442
|
+
const totalTime = lastBuild.loadTime +
|
|
443
|
+
lastBuild.buildTime + lastBuild.writeTime
|
|
276
444
|
|
|
277
445
|
Term.status([
|
|
278
446
|
[builds > 0 ? "info" : "muted", "Last Build", ["[", "]"]],
|
|
@@ -313,10 +481,11 @@ export default class Session {
|
|
|
313
481
|
if(this.#watcher)
|
|
314
482
|
await this.#watcher.close()
|
|
315
483
|
|
|
316
|
-
const dependencies = this.#theme.
|
|
484
|
+
const dependencies = Array.from(this.#theme.getDependencies()).map(d => d.getSourceFile().path)
|
|
485
|
+
|
|
317
486
|
this.#watcher = chokidar.watch(dependencies, {
|
|
318
487
|
// Prevent watching own output files
|
|
319
|
-
ignored: [this.#theme.
|
|
488
|
+
ignored: [this.#theme.getOutputFileName()],
|
|
320
489
|
// Add some stability options
|
|
321
490
|
awaitWriteFinish: {
|
|
322
491
|
stabilityThreshold: 100,
|
package/src/Term.js
CHANGED
|
@@ -9,7 +9,7 @@ export default class Term {
|
|
|
9
9
|
/**
|
|
10
10
|
* Log an informational message.
|
|
11
11
|
*
|
|
12
|
-
* @param {...
|
|
12
|
+
* @param {...unknown} [arg] - Values to log.
|
|
13
13
|
*/
|
|
14
14
|
static log(...arg) {
|
|
15
15
|
console.log(...arg)
|
|
@@ -18,7 +18,7 @@ export default class Term {
|
|
|
18
18
|
/**
|
|
19
19
|
* Log an informational message.
|
|
20
20
|
*
|
|
21
|
-
* @param {...
|
|
21
|
+
* @param {...unknown} [arg] - Values to log.
|
|
22
22
|
*/
|
|
23
23
|
static info(...arg) {
|
|
24
24
|
console.info(...arg)
|
|
@@ -27,16 +27,16 @@ export default class Term {
|
|
|
27
27
|
/**
|
|
28
28
|
* Log a warning message.
|
|
29
29
|
*
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {...unknown} [arg] - Warning text / object.
|
|
31
31
|
*/
|
|
32
|
-
static warn(...
|
|
33
|
-
console.warn(...
|
|
32
|
+
static warn(...arg) {
|
|
33
|
+
console.warn(...arg)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Log an error message (plus optional details).
|
|
38
38
|
*
|
|
39
|
-
* @param {...
|
|
39
|
+
* @param {...unknown} [arg] - Values to log.
|
|
40
40
|
*/
|
|
41
41
|
static error(...arg) {
|
|
42
42
|
console.error(...arg)
|
|
@@ -45,7 +45,7 @@ export default class Term {
|
|
|
45
45
|
/**
|
|
46
46
|
* Log a debug message (no-op unless console.debug provided/visible by env).
|
|
47
47
|
*
|
|
48
|
-
* @param {...
|
|
48
|
+
* @param {...unknown} [arg] - Values to log.
|
|
49
49
|
*/
|
|
50
50
|
static debug(...arg) {
|
|
51
51
|
console.debug(...arg)
|