@gesslar/sassy 0.20.2 → 0.21.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/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 +93 -32
- package/src/Compiler.js +62 -36
- package/src/Data.js +26 -16
- package/src/Evaluator.js +60 -7
- package/src/File.js +14 -2
- package/src/LintCommand.js +421 -185
- package/src/ResolveCommand.js +60 -29
- package/src/Sass.js +2 -1
- package/src/Session.js +202 -36
- package/src/Term.js +7 -7
- package/src/Theme.js +394 -54
- 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 +28 -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()
|
|
@@ -86,13 +87,22 @@ export default class ResolveCommand extends Command {
|
|
|
86
87
|
* @param {object} theme - The compiled theme object with pool
|
|
87
88
|
* @param {string} colorName - The color key to resolve
|
|
88
89
|
* @returns {void}
|
|
90
|
+
* @example
|
|
91
|
+
* // Resolve a color variable from a compiled theme
|
|
92
|
+
* await resolveCommand.resolveColor(theme, 'colors.primary');
|
|
93
|
+
* // Output:
|
|
94
|
+
* // colors.primary:
|
|
95
|
+
* // $(vars.accent)
|
|
96
|
+
* // → #3366cc
|
|
97
|
+
* // Resolution: #3366cc
|
|
89
98
|
*/
|
|
90
99
|
async resolveColor(theme, colorName) {
|
|
91
|
-
const pool = theme.
|
|
100
|
+
const pool = theme.getPool()
|
|
101
|
+
|
|
92
102
|
if(!pool || !pool.has(colorName))
|
|
93
103
|
return Term.info(`'${colorName}' not found.`)
|
|
94
104
|
|
|
95
|
-
const tokens = pool.getTokens
|
|
105
|
+
const tokens = pool.getTokens()
|
|
96
106
|
const token = tokens.get(colorName)
|
|
97
107
|
const trail = token.getTrail()
|
|
98
108
|
const fullTrail = this.#buildCompleteTrail(token, trail)
|
|
@@ -114,7 +124,7 @@ export default class ResolveCommand extends Command {
|
|
|
114
124
|
* @returns {void}
|
|
115
125
|
*/
|
|
116
126
|
async resolveTokenColor(theme, scopeName) {
|
|
117
|
-
const tokenColors = theme.
|
|
127
|
+
const tokenColors = theme.getOutput()?.tokenColors || []
|
|
118
128
|
|
|
119
129
|
// Check if this is a disambiguated scope (ends with .1, .2, etc.)
|
|
120
130
|
const disambiguatedMatch = scopeName.match(/^(.+)\.(\d+)$/)
|
|
@@ -127,7 +137,9 @@ export default class ResolveCommand extends Command {
|
|
|
127
137
|
|
|
128
138
|
if(index >= 0 && index < matches.length) {
|
|
129
139
|
const match = matches[index]
|
|
140
|
+
|
|
130
141
|
await this.#resolveScopeMatch(theme, match, `${baseScope}.${indexStr}`)
|
|
142
|
+
|
|
131
143
|
return
|
|
132
144
|
} else {
|
|
133
145
|
return Term.info(`'${scopeName}' not found. Available: ${baseScope}.1 through ${baseScope}.${matches.length}`)
|
|
@@ -149,6 +161,7 @@ export default class ResolveCommand extends Command {
|
|
|
149
161
|
Term.info(`Multiple entries found for '${scopeName}', please try again with the specific query:\n`)
|
|
150
162
|
matches.forEach((match, index) => {
|
|
151
163
|
const name = match.name || `Entry ${index + 1}`
|
|
164
|
+
|
|
152
165
|
Term.info(`${name}: ${scopeName}.${index + 1}`)
|
|
153
166
|
})
|
|
154
167
|
}
|
|
@@ -161,24 +174,26 @@ export default class ResolveCommand extends Command {
|
|
|
161
174
|
|
|
162
175
|
// Handle comma-separated scopes
|
|
163
176
|
const scopes = entry.scope.split(",").map(s => s.trim())
|
|
177
|
+
|
|
164
178
|
return scopes.includes(targetScope)
|
|
165
179
|
})
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
async #resolveScopeMatch(theme, match, displayName) {
|
|
169
|
-
const pool = theme.
|
|
183
|
+
const pool = theme.getPool()
|
|
170
184
|
const settings = match.settings || {}
|
|
171
185
|
const name = match.name || "Unnamed"
|
|
172
186
|
|
|
173
187
|
// Look for the foreground property specifically
|
|
174
188
|
const foreground = settings.foreground
|
|
189
|
+
|
|
175
190
|
if(!foreground) {
|
|
176
191
|
return Term.info(`${displayName} (${name})\n\n(no foreground property)`)
|
|
177
192
|
}
|
|
178
193
|
|
|
179
194
|
// First, try to find the token by looking for variables that resolve to this value
|
|
180
195
|
// but prioritize source variable names over computed results
|
|
181
|
-
const tokens = pool ? pool.getTokens : new Map()
|
|
196
|
+
const tokens = pool ? pool.getTokens() : new Map()
|
|
182
197
|
let bestToken = null
|
|
183
198
|
|
|
184
199
|
// First try to find a scope.* token that matches
|
|
@@ -205,16 +220,18 @@ export default class ResolveCommand extends Command {
|
|
|
205
220
|
}
|
|
206
221
|
}
|
|
207
222
|
|
|
208
|
-
if(!bestToken)
|
|
209
|
-
return Term.info(
|
|
210
|
-
|
|
223
|
+
if(!bestToken)
|
|
224
|
+
return Term.info(
|
|
225
|
+
`${displayName} (${name})\n\n(resolved to static value: ${foreground})`
|
|
226
|
+
)
|
|
211
227
|
|
|
212
228
|
const trail = bestToken.getTrail()
|
|
213
229
|
const fullTrail = this.#buildCompleteTrail(bestToken, trail)
|
|
214
230
|
const finalValue = bestToken.getValue()
|
|
215
231
|
const [formattedFinalValue] = this.#formatLeaf(finalValue)
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
const output = c`{head}${displayName}{/} {hex}${(`${name}`)}{/}\n`+
|
|
233
|
+
`${this.#formatOutput(fullTrail)}\n\n{head}`+
|
|
234
|
+
`${"Resolution:"}{/} ${formattedFinalValue}`
|
|
218
235
|
|
|
219
236
|
Term.info(output)
|
|
220
237
|
}
|
|
@@ -230,18 +247,20 @@ export default class ResolveCommand extends Command {
|
|
|
230
247
|
async resolveSemanticTokenColor(theme, scopeName) {
|
|
231
248
|
// semanticTokenColors has the same structure as tokenColors, so we can reuse the logic
|
|
232
249
|
// but we need to look at the semanticTokenColors array instead
|
|
233
|
-
const originalTokenColors = theme.
|
|
250
|
+
const originalTokenColors = theme.getOutput()?.tokenColors
|
|
234
251
|
|
|
235
252
|
// Temporarily replace tokenColors with semanticTokenColors for resolution
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
const themeOutput = theme.getOutput()
|
|
254
|
+
|
|
255
|
+
if(themeOutput?.semanticTokenColors) {
|
|
256
|
+
themeOutput.tokenColors = themeOutput.semanticTokenColors
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
await this.resolveTokenColor(theme, scopeName)
|
|
241
260
|
|
|
242
261
|
// Restore original tokenColors
|
|
243
|
-
if(originalTokenColors) {
|
|
244
|
-
|
|
262
|
+
if(originalTokenColors && themeOutput) {
|
|
263
|
+
themeOutput.tokenColors = originalTokenColors
|
|
245
264
|
}
|
|
246
265
|
}
|
|
247
266
|
|
|
@@ -251,6 +270,7 @@ export default class ResolveCommand extends Command {
|
|
|
251
270
|
|
|
252
271
|
// Add the root token's original expression
|
|
253
272
|
const rootRaw = rootToken.getRawValue()
|
|
273
|
+
|
|
254
274
|
if(rootRaw !== rootToken.getName()) {
|
|
255
275
|
steps.push({
|
|
256
276
|
value: rootRaw,
|
|
@@ -265,6 +285,7 @@ export default class ResolveCommand extends Command {
|
|
|
265
285
|
return
|
|
266
286
|
|
|
267
287
|
const id = `${token.getName()}-${token.getRawValue()}`
|
|
288
|
+
|
|
268
289
|
if(seen.has(id))
|
|
269
290
|
return
|
|
270
291
|
|
|
@@ -290,7 +311,8 @@ export default class ResolveCommand extends Command {
|
|
|
290
311
|
const depFinal = dependency.getValue()
|
|
291
312
|
|
|
292
313
|
// Add dependency's expression if it's a function call
|
|
293
|
-
if(depRaw !== dependency.getName() &&
|
|
314
|
+
if(depRaw !== dependency.getName() &&
|
|
315
|
+
!steps.some(s => s.value === depRaw)) {
|
|
294
316
|
steps.push({
|
|
295
317
|
value: depRaw,
|
|
296
318
|
type: "expression",
|
|
@@ -300,6 +322,7 @@ export default class ResolveCommand extends Command {
|
|
|
300
322
|
|
|
301
323
|
// Process dependency's trail
|
|
302
324
|
const depTrail = dependency.getTrail()
|
|
325
|
+
|
|
303
326
|
if(depTrail && depTrail.length > 0) {
|
|
304
327
|
depTrail.forEach(depToken => processToken(depToken, level + 1))
|
|
305
328
|
}
|
|
@@ -372,10 +395,12 @@ export default class ResolveCommand extends Command {
|
|
|
372
395
|
// Hex results are indented one extra level with just spaces and arrow
|
|
373
396
|
const prefix = " ".repeat(depth + 1)
|
|
374
397
|
const arrow = c`{arrow}→{/} `
|
|
398
|
+
|
|
375
399
|
out.push(`${prefix}${arrow}${line}`)
|
|
376
400
|
} else {
|
|
377
401
|
// Everything else just gets clean indentation
|
|
378
402
|
const prefix = " ".repeat(depth)
|
|
403
|
+
|
|
379
404
|
out.push(`${prefix}${line}`)
|
|
380
405
|
}
|
|
381
406
|
})
|
|
@@ -410,18 +435,24 @@ export default class ResolveCommand extends Command {
|
|
|
410
435
|
}
|
|
411
436
|
|
|
412
437
|
if(this.#func.test(value)) {
|
|
413
|
-
const
|
|
438
|
+
const result = Evaluator.extractFunctionCall(value)
|
|
439
|
+
|
|
440
|
+
if(!result)
|
|
441
|
+
return [c`{leaf}${value}{/}`, "literal"]
|
|
442
|
+
|
|
443
|
+
const {func, args} = result
|
|
444
|
+
|
|
414
445
|
return [
|
|
415
446
|
c`{func}${func}{/}{parens}${"("}{/}{leaf}${args}{/}{parens}${")"}{/}`,
|
|
416
447
|
"function"
|
|
417
448
|
]
|
|
418
449
|
}
|
|
419
450
|
|
|
420
|
-
|
|
421
451
|
if(this.#sub.test(value)) {
|
|
452
|
+
const varValue = Evaluator.extractVariableName(value) || value
|
|
422
453
|
const {parens,none,braces} = Evaluator.sub.exec(value)?.groups || {}
|
|
423
454
|
const style = (braces && ["{","}"]) || (parens && ["(",")"]) || (none && ["",""])
|
|
424
|
-
|
|
455
|
+
|
|
425
456
|
return [
|
|
426
457
|
c`{func}{/}{parens}${style[0]}{/}{leaf}${varValue}{/}{parens}${style[1]}{/}`,
|
|
427
458
|
"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
|
|
@@ -88,11 +222,12 @@ export default class Session {
|
|
|
88
222
|
*/
|
|
89
223
|
|
|
90
224
|
loadCost = (await Util.time(() => this.#theme.load())).cost
|
|
91
|
-
const bytes = await File.fileSize(this.#theme.
|
|
225
|
+
const bytes = await File.fileSize(this.#theme.getSourceFile())
|
|
226
|
+
|
|
92
227
|
Term.status([
|
|
93
228
|
["success", Util.rightAlignText(`${loadCost.toLocaleString()}ms`, 10), ["[","]"]],
|
|
94
|
-
`${this.#theme.
|
|
95
|
-
["info", `${bytes} bytes`, ["[","]"]]
|
|
229
|
+
`${this.#theme.getName()} loaded`,
|
|
230
|
+
["info", `${bytes.toLocaleString()} bytes`, ["[","]"]]
|
|
96
231
|
], this.#options)
|
|
97
232
|
/**
|
|
98
233
|
* ****************************************************************
|
|
@@ -101,30 +236,48 @@ export default class Session {
|
|
|
101
236
|
*/
|
|
102
237
|
|
|
103
238
|
buildCost = (await Util.time(() => this.#theme.build())).cost
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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]
|
|
114
256
|
}))
|
|
115
257
|
|
|
116
258
|
const rejected = compileResult.filter(result => result.status === "rejected")
|
|
259
|
+
|
|
117
260
|
if(rejected.length > 0) {
|
|
118
261
|
rejected.forEach(reject => Term.error(reject.reason))
|
|
119
262
|
throw new Error("Compilation failed")
|
|
120
263
|
}
|
|
121
264
|
|
|
122
|
-
const dependencies = compileResult
|
|
123
|
-
|
|
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
|
+
)
|
|
124
273
|
|
|
125
274
|
Term.status([
|
|
126
|
-
[
|
|
127
|
-
|
|
275
|
+
[
|
|
276
|
+
"success",
|
|
277
|
+
Util.rightAlignText(`${buildCost.toLocaleString()}ms`, 10),
|
|
278
|
+
["[","]"]
|
|
279
|
+
],
|
|
280
|
+
`${this.#theme.getName()} compiled`,
|
|
128
281
|
["success", `${compileResult[0].value[1].toLocaleString()} bytes`, ["[","]"]],
|
|
129
282
|
["info", `${totalBytes.toLocaleString()} total bytes`, ["(",")"]],
|
|
130
283
|
], this.#options)
|
|
@@ -150,19 +303,25 @@ export default class Session {
|
|
|
150
303
|
*/
|
|
151
304
|
|
|
152
305
|
const writeResult = await Util.time(() => this.#theme.write(forceWrite))
|
|
306
|
+
|
|
153
307
|
writeCost = writeResult.cost
|
|
154
|
-
const result = writeResult.result
|
|
155
308
|
const {
|
|
156
309
|
status: writeStatus,
|
|
157
310
|
file: outputFile,
|
|
158
311
|
bytes: writeBytes
|
|
159
|
-
} = result
|
|
160
|
-
const outputFilename = File.relativeOrAbsolutePath(
|
|
312
|
+
} = writeResult.result
|
|
313
|
+
const outputFilename = File.relativeOrAbsolutePath(
|
|
314
|
+
this.#command.getCwd(), outputFile
|
|
315
|
+
)
|
|
161
316
|
const status = [
|
|
162
|
-
[
|
|
317
|
+
[
|
|
318
|
+
"success",
|
|
319
|
+
Util.rightAlignText(`${writeCost.toLocaleString()}ms`, 10),
|
|
320
|
+
["[","]"]
|
|
321
|
+
],
|
|
163
322
|
]
|
|
164
323
|
|
|
165
|
-
if(writeStatus === "written") {
|
|
324
|
+
if(writeStatus.description === "written") {
|
|
166
325
|
status.push(
|
|
167
326
|
`${outputFilename} written`,
|
|
168
327
|
["success", `${writeBytes.toLocaleString()} bytes`, ["[","]"]]
|
|
@@ -170,7 +329,7 @@ export default class Session {
|
|
|
170
329
|
} else {
|
|
171
330
|
status.push(
|
|
172
331
|
`${outputFilename}`,
|
|
173
|
-
["warn", writeStatus.toLocaleUpperCase(), ["[","]"]]
|
|
332
|
+
["warn", writeStatus.description.toLocaleUpperCase(), ["[","]"]]
|
|
174
333
|
)
|
|
175
334
|
}
|
|
176
335
|
|
|
@@ -198,8 +357,7 @@ export default class Session {
|
|
|
198
357
|
error: error.message
|
|
199
358
|
})
|
|
200
359
|
|
|
201
|
-
|
|
202
|
-
error.report(this.#options.nerd)
|
|
360
|
+
Sass.new("Build process failed.", error).report(this.#options.nerd)
|
|
203
361
|
} finally {
|
|
204
362
|
this.#building = false
|
|
205
363
|
this.#command.asyncEmit("finishedBuilding")
|
|
@@ -221,16 +379,20 @@ export default class Session {
|
|
|
221
379
|
this.#building = true
|
|
222
380
|
this.#command.asyncEmit("building")
|
|
223
381
|
|
|
224
|
-
const changedFile = this.#theme.
|
|
382
|
+
const changedFile = Array.from(this.#theme.getDependencies()).find(
|
|
383
|
+
dep => dep.getSourceFile().path === changed
|
|
384
|
+
)?.getSourceFile()
|
|
225
385
|
|
|
226
386
|
if(!changedFile)
|
|
227
387
|
return
|
|
228
388
|
|
|
229
|
-
const fileName = File.relativeOrAbsolutePath(
|
|
389
|
+
const fileName = File.relativeOrAbsolutePath(
|
|
390
|
+
this.#command.getCwd(), changedFile
|
|
391
|
+
)
|
|
230
392
|
|
|
231
393
|
const message = [
|
|
232
394
|
["info", "REBUILDING", ["[","]"]],
|
|
233
|
-
this.#theme.
|
|
395
|
+
this.#theme.getName(),
|
|
234
396
|
]
|
|
235
397
|
|
|
236
398
|
if(this.#options.nerd)
|
|
@@ -262,7 +424,7 @@ export default class Session {
|
|
|
262
424
|
|
|
263
425
|
Term.status([
|
|
264
426
|
[builds > 0 ? "success" : "error", "SESSION SUMMARY"],
|
|
265
|
-
[builds > 0 ? "info" : "error", this.#theme.
|
|
427
|
+
[builds > 0 ? "info" : "error", this.#theme.getName(), ["[", "]"]]
|
|
266
428
|
], this.#options)
|
|
267
429
|
|
|
268
430
|
Term.status([
|
|
@@ -277,7 +439,8 @@ export default class Session {
|
|
|
277
439
|
|
|
278
440
|
if(this.#history.length > 0) {
|
|
279
441
|
const lastBuild = this.#history[this.#history.length - 1]
|
|
280
|
-
const totalTime = lastBuild.loadTime +
|
|
442
|
+
const totalTime = lastBuild.loadTime +
|
|
443
|
+
lastBuild.buildTime + lastBuild.writeTime
|
|
281
444
|
|
|
282
445
|
Term.status([
|
|
283
446
|
[builds > 0 ? "info" : "muted", "Last Build", ["[", "]"]],
|
|
@@ -318,10 +481,13 @@ export default class Session {
|
|
|
318
481
|
if(this.#watcher)
|
|
319
482
|
await this.#watcher.close()
|
|
320
483
|
|
|
321
|
-
const dependencies = this.#theme
|
|
484
|
+
const dependencies = Array.from(this.#theme
|
|
485
|
+
.getDependencies())
|
|
486
|
+
.map(d => d.getSourceFile().path)
|
|
487
|
+
|
|
322
488
|
this.#watcher = chokidar.watch(dependencies, {
|
|
323
489
|
// Prevent watching own output files
|
|
324
|
-
ignored: [this.#theme.
|
|
490
|
+
ignored: [this.#theme.getOutputFileName()],
|
|
325
491
|
// Add some stability options
|
|
326
492
|
awaitWriteFinish: {
|
|
327
493
|
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)
|