@gesslar/sassy 0.20.2 → 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 +200 -36
- 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
|
|
@@ -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,11 @@ 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.getDependencies()).map(d => d.getSourceFile().path)
|
|
485
|
+
|
|
322
486
|
this.#watcher = chokidar.watch(dependencies, {
|
|
323
487
|
// Prevent watching own output files
|
|
324
|
-
ignored: [this.#theme.
|
|
488
|
+
ignored: [this.#theme.getOutputFileName()],
|
|
325
489
|
// Add some stability options
|
|
326
490
|
awaitWriteFinish: {
|
|
327
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)
|