@gesslar/sassy 0.19.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/README.md +605 -0
- package/UNLICENSE.txt +24 -0
- package/package.json +60 -0
- package/src/BuildCommand.js +183 -0
- package/src/Cache.js +73 -0
- package/src/Colour.js +414 -0
- package/src/Command.js +212 -0
- package/src/Compiler.js +310 -0
- package/src/Data.js +545 -0
- package/src/DirectoryObject.js +188 -0
- package/src/Evaluator.js +348 -0
- package/src/File.js +334 -0
- package/src/FileObject.js +226 -0
- package/src/LintCommand.js +498 -0
- package/src/ResolveCommand.js +433 -0
- package/src/Sass.js +165 -0
- package/src/Session.js +360 -0
- package/src/Term.js +175 -0
- package/src/Theme.js +289 -0
- package/src/ThemePool.js +139 -0
- package/src/ThemeToken.js +280 -0
- package/src/Type.js +206 -0
- package/src/Util.js +132 -0
- package/src/Valid.js +50 -0
- package/src/cli.js +155 -0
package/src/Type.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Type specification and validation utilities.
|
|
3
|
+
* Provides TypeSpec class for parsing and validating complex type specifications
|
|
4
|
+
* including arrays, unions, and options.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Sass from "./Sass.js"
|
|
8
|
+
import Data from "./Data.js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type specification class for parsing and validating complex type definitions.
|
|
12
|
+
* Supports union types, array types, and validation options.
|
|
13
|
+
*/
|
|
14
|
+
export default class TypeSpec {
|
|
15
|
+
#specs
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new TypeSpec instance.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} string - The type specification string (e.g., "string|number", "object[]")
|
|
21
|
+
* @param {object} options - Additional parsing options
|
|
22
|
+
*/
|
|
23
|
+
constructor(string, options) {
|
|
24
|
+
this.#specs = []
|
|
25
|
+
this.#parse(string, options)
|
|
26
|
+
Object.freeze(this.#specs)
|
|
27
|
+
this.specs = this.#specs
|
|
28
|
+
this.length = this.#specs.length
|
|
29
|
+
this.stringRepresentation = this.toString()
|
|
30
|
+
Object.freeze(this)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns a string representation of the type specification.
|
|
35
|
+
*
|
|
36
|
+
* @returns {string} The type specification as a string (e.g., "string|number[]")
|
|
37
|
+
*/
|
|
38
|
+
toString() {
|
|
39
|
+
return this.#specs
|
|
40
|
+
.map(spec => {
|
|
41
|
+
return `${spec.typeName}${spec.array ? "[]" : ""}`
|
|
42
|
+
})
|
|
43
|
+
.join("|")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns a JSON representation of the TypeSpec.
|
|
48
|
+
*
|
|
49
|
+
* @returns {object} Object containing specs, length, and string representation
|
|
50
|
+
*/
|
|
51
|
+
toJSON() {
|
|
52
|
+
// Serialize as a string representation or as raw data
|
|
53
|
+
return {
|
|
54
|
+
specs: this.#specs,
|
|
55
|
+
length: this.length,
|
|
56
|
+
stringRepresentation: this.toString(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Executes a provided function once for each type specification.
|
|
62
|
+
*
|
|
63
|
+
* @param {Function} callback - Function to execute for each spec
|
|
64
|
+
*/
|
|
65
|
+
forEach(callback) {
|
|
66
|
+
this.#specs.forEach(callback)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tests whether all type specifications pass the provided test function.
|
|
71
|
+
*
|
|
72
|
+
* @param {Function} callback - Function to test each spec
|
|
73
|
+
* @returns {boolean} True if all specs pass the test
|
|
74
|
+
*/
|
|
75
|
+
every(callback) {
|
|
76
|
+
return this.#specs.every(callback)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Tests whether at least one type specification passes the provided test function.
|
|
81
|
+
*
|
|
82
|
+
* @param {Function} callback - Function to test each spec
|
|
83
|
+
* @returns {boolean} True if at least one spec passes the test
|
|
84
|
+
*/
|
|
85
|
+
some(callback) {
|
|
86
|
+
return this.#specs.some(callback)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a new array with all type specifications that pass the provided test function.
|
|
91
|
+
*
|
|
92
|
+
* @param {Function} callback - Function to test each spec
|
|
93
|
+
* @returns {Array} New array with filtered specs
|
|
94
|
+
*/
|
|
95
|
+
filter(callback) {
|
|
96
|
+
return this.#specs.filter(callback)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new array populated with the results of calling the provided function on every spec.
|
|
101
|
+
*
|
|
102
|
+
* @param {Function} callback - Function to call on each spec
|
|
103
|
+
* @returns {Array} New array with mapped values
|
|
104
|
+
*/
|
|
105
|
+
map(callback) {
|
|
106
|
+
return this.#specs.map(callback)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Executes a reducer function on each spec, resulting in a single output value.
|
|
111
|
+
*
|
|
112
|
+
* @param {Function} callback - Function to execute on each spec
|
|
113
|
+
* @param {*} initialValue - Initial value for the accumulator
|
|
114
|
+
* @returns {*} The final accumulated value
|
|
115
|
+
*/
|
|
116
|
+
reduce(callback, initialValue) {
|
|
117
|
+
return this.#specs.reduce(callback, initialValue)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns the first type specification that satisfies the provided testing function.
|
|
122
|
+
*
|
|
123
|
+
* @param {Function} callback - Function to test each spec
|
|
124
|
+
* @returns {object|undefined} The first spec that matches, or undefined
|
|
125
|
+
*/
|
|
126
|
+
find(callback) {
|
|
127
|
+
return this.#specs.find(callback)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Tests whether a value matches any of the type specifications.
|
|
132
|
+
* Handles array types, union types, and empty value validation.
|
|
133
|
+
*
|
|
134
|
+
* @param {*} value - The value to test against the type specifications
|
|
135
|
+
* @param {object} options - Validation options
|
|
136
|
+
* @param {boolean} options.allowEmpty - Whether empty values are allowed
|
|
137
|
+
* @returns {boolean} True if the value matches any type specification
|
|
138
|
+
*/
|
|
139
|
+
match(value, options) {
|
|
140
|
+
const allowEmpty = options?.allowEmpty ?? true
|
|
141
|
+
const empty = Data.isEmpty(value)
|
|
142
|
+
|
|
143
|
+
// If we have a list of types, because the string was validly parsed,
|
|
144
|
+
// we need to ensure that all of the types that were parsed are valid types
|
|
145
|
+
// in JavaScript.
|
|
146
|
+
if(this.length && !this.every(t => Data.isValidType(t.typeName)))
|
|
147
|
+
return false
|
|
148
|
+
|
|
149
|
+
// Now, let's do some checking with the types, respecting the array flag
|
|
150
|
+
// with the value
|
|
151
|
+
const valueType = Data.typeOf(value)
|
|
152
|
+
const isArray = valueType === "array"
|
|
153
|
+
|
|
154
|
+
// We need to ensure that we match the type and the consistency of the types
|
|
155
|
+
// in an array, if it is an array and an array is allowed.
|
|
156
|
+
const matchingTypeSpec = this.filter(spec => {
|
|
157
|
+
const {typeName: allowedType, array: allowedArray} = spec
|
|
158
|
+
|
|
159
|
+
if(valueType === allowedType && !isArray && !allowedArray)
|
|
160
|
+
return !allowEmpty ? !empty : true
|
|
161
|
+
|
|
162
|
+
if(isArray) {
|
|
163
|
+
if(allowedType === "array")
|
|
164
|
+
if(!allowedArray)
|
|
165
|
+
return true
|
|
166
|
+
|
|
167
|
+
if(empty)
|
|
168
|
+
if(allowEmpty)
|
|
169
|
+
return true
|
|
170
|
+
|
|
171
|
+
return Data.isArrayUniform(value, allowedType)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return matchingTypeSpec.length > 0
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Parses a type specification string into individual type specs.
|
|
180
|
+
* Handles union types separated by delimiters and array notation.
|
|
181
|
+
*
|
|
182
|
+
* @private
|
|
183
|
+
* @param {string} string - The type specification string to parse
|
|
184
|
+
* @param {object} options - Parsing options
|
|
185
|
+
* @param {string} options.delimiter - The delimiter for union types
|
|
186
|
+
* @throws {TypeError} If the type specification is invalid
|
|
187
|
+
*/
|
|
188
|
+
#parse(string, options) {
|
|
189
|
+
const delimiter = options?.delimiter ?? "|"
|
|
190
|
+
const parts = string.split(delimiter)
|
|
191
|
+
|
|
192
|
+
this.#specs = parts.map(part => {
|
|
193
|
+
const typeMatches = /(\w+)(\[\])?/.exec(part)
|
|
194
|
+
if(!typeMatches || typeMatches.length !== 3)
|
|
195
|
+
throw Sass.new(`Invalid type: ${part}`)
|
|
196
|
+
|
|
197
|
+
if(!Data.isValidType(typeMatches[1]))
|
|
198
|
+
throw Sass.new(`Invalid type: ${typeMatches[1]}`)
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
typeName: typeMatches[1],
|
|
202
|
+
array: typeMatches[2] === "[]",
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
package/src/Util.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {createHash} from "node:crypto"
|
|
2
|
+
import {performance} from "node:perf_hooks"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility class providing common helper functions for string manipulation,
|
|
6
|
+
* timing, hashing, and option parsing.
|
|
7
|
+
*/
|
|
8
|
+
export default class Util {
|
|
9
|
+
/**
|
|
10
|
+
* Capitalizes the first letter of a string.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} text - The text to capitalize
|
|
13
|
+
* @returns {string} Text with first letter capitalized
|
|
14
|
+
*/
|
|
15
|
+
static capitalize(text) {
|
|
16
|
+
return `${text.slice(0,1).toUpperCase()}${text.slice(1)}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Measure wall-clock time for an async function.
|
|
21
|
+
*
|
|
22
|
+
* @template T
|
|
23
|
+
* @param {() => Promise<T>} fn - Thunk returning a promise.
|
|
24
|
+
* @returns {Promise<{result: T, cost: number}>} Object containing result and elapsed ms (number, 1 decimal).
|
|
25
|
+
*/
|
|
26
|
+
static async time(fn) {
|
|
27
|
+
const t0 = performance.now()
|
|
28
|
+
const result = await fn()
|
|
29
|
+
const cost = Math.round((performance.now() - t0) * 10) / 10
|
|
30
|
+
|
|
31
|
+
return {result, cost}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Right-align a string inside a fixed width (left pad with spaces).
|
|
36
|
+
* If the string exceeds width it is returned unchanged.
|
|
37
|
+
*
|
|
38
|
+
* @param {string|number} text - Text to align.
|
|
39
|
+
* @param {number} width - Target field width (default 80).
|
|
40
|
+
* @returns {string} Padded string.
|
|
41
|
+
*/
|
|
42
|
+
static rightAlignText(text, width=80) {
|
|
43
|
+
const work = String(text)
|
|
44
|
+
|
|
45
|
+
if(work.length > width)
|
|
46
|
+
return work
|
|
47
|
+
|
|
48
|
+
const diff = width-work.length
|
|
49
|
+
|
|
50
|
+
return `${" ".repeat(diff)}${work}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compute sha256 hash (hex) of the provided string.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} s - Input string.
|
|
57
|
+
* @returns {string} 64-char hexadecimal digest.
|
|
58
|
+
*/
|
|
59
|
+
static hashOf(s) {
|
|
60
|
+
return createHash("sha256").update(s).digest("hex")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extracts canonical option names from a Commander-style options object.
|
|
65
|
+
*
|
|
66
|
+
* Each key in the input object is a string containing one or more option
|
|
67
|
+
* forms, separated by commas (e.g. "-w, --watch"). This function splits each
|
|
68
|
+
* key, trims whitespace, and parses out the long option name (e.g. "watch")
|
|
69
|
+
* for each entry. If no long option ("--") is present, the short option (e.g.
|
|
70
|
+
* "v" from "-v") will be included in the result array. If both are present,
|
|
71
|
+
* the long option is preferred.
|
|
72
|
+
*
|
|
73
|
+
* Example:
|
|
74
|
+
* generateOptionNames({"-w, --watch": "desc", "-v": "desc"})
|
|
75
|
+
* → ["watch", "v"]
|
|
76
|
+
*
|
|
77
|
+
* Edge cases:
|
|
78
|
+
* - If a key contains only a short option ("-v"), that short name will be
|
|
79
|
+
* included in the result.
|
|
80
|
+
* - If multiple long options are present, only the first is used.
|
|
81
|
+
* - If the option string is malformed, may return undefined for that entry
|
|
82
|
+
* (filtered out).
|
|
83
|
+
*
|
|
84
|
+
* @param {object} object - Mapping of option strings to descriptions.
|
|
85
|
+
* @returns {string[]} Array of canonical option names (long preferred, short if no long present).
|
|
86
|
+
*/
|
|
87
|
+
static generateOptionNames(object) {
|
|
88
|
+
return Object.keys(object)
|
|
89
|
+
.map(key => {
|
|
90
|
+
return key
|
|
91
|
+
.split(",")
|
|
92
|
+
.map(o => o.trim())
|
|
93
|
+
.map(o => o.match(/^(?<sign>--?)(?<option>[\w-]+)/).groups)
|
|
94
|
+
.reduce((acc, curr) => acc.sign === "--" ? acc : curr, {})
|
|
95
|
+
?.option
|
|
96
|
+
})
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Asynchronously awaits all promises in parallel.
|
|
102
|
+
* Wrapper around Promise.all for consistency with other utility methods.
|
|
103
|
+
*
|
|
104
|
+
* @param {Promise[]} promises - Array of promises to await
|
|
105
|
+
* @returns {Promise<any[]>} Results of all promises
|
|
106
|
+
*/
|
|
107
|
+
static async awaitAll(promises) {
|
|
108
|
+
return await Promise.all(promises)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Settles all promises (both fulfilled and rejected) in parallel.
|
|
113
|
+
* Wrapper around Promise.allSettled for consistency with other utility methods.
|
|
114
|
+
*
|
|
115
|
+
* @param {Promise[]} promises - Array of promises to settle
|
|
116
|
+
* @returns {Promise<Array>} Results of all settled promises with status and value/reason
|
|
117
|
+
*/
|
|
118
|
+
static async settleAll(promises) {
|
|
119
|
+
return await Promise.allSettled(promises)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns the first promise to resolve or reject from an array of promises.
|
|
124
|
+
* Wrapper around Promise.race for consistency with other utility methods.
|
|
125
|
+
*
|
|
126
|
+
* @param {Promise[]} promises - Array of promises to race
|
|
127
|
+
* @returns {Promise<any>} Result of the first settled promise
|
|
128
|
+
*/
|
|
129
|
+
static async race(promises) {
|
|
130
|
+
return await Promise.race(promises)
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/Valid.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import _assert from "node:assert/strict"
|
|
2
|
+
|
|
3
|
+
import Sass from "./Sass.js"
|
|
4
|
+
import Data from "./Data.js"
|
|
5
|
+
|
|
6
|
+
export default class Valid {
|
|
7
|
+
/**
|
|
8
|
+
* Validates a value against a type
|
|
9
|
+
*
|
|
10
|
+
* @param {*} value - The value to validate
|
|
11
|
+
* @param {string} type - The expected type in the form of "object",
|
|
12
|
+
* "object[]", "object|object[]"
|
|
13
|
+
* @param {object} [options] - Additional options for validation.
|
|
14
|
+
*/
|
|
15
|
+
static validType(value, type, options) {
|
|
16
|
+
Valid.assert(
|
|
17
|
+
Data.isType(value, type, options),
|
|
18
|
+
`Invalid type. Expected ${type}, got ${JSON.stringify(value)}`,
|
|
19
|
+
1,
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Asserts a condition
|
|
25
|
+
*
|
|
26
|
+
* @param {boolean} condition - The condition to assert
|
|
27
|
+
* @param {string} message - The message to display if the condition is not
|
|
28
|
+
* met
|
|
29
|
+
* @param {number} [arg] - The argument to display if the condition is not
|
|
30
|
+
* met (optional)
|
|
31
|
+
*/
|
|
32
|
+
static assert(condition, message, arg = null) {
|
|
33
|
+
_assert(
|
|
34
|
+
Data.isType(condition, "boolean"),
|
|
35
|
+
`Condition must be a boolean, got ${condition}`,
|
|
36
|
+
)
|
|
37
|
+
_assert(
|
|
38
|
+
Data.isType(message, "string"),
|
|
39
|
+
`Message must be a string, got ${message}`,
|
|
40
|
+
)
|
|
41
|
+
_assert(
|
|
42
|
+
arg !== null || arg !== undefined && typeof arg === "number",
|
|
43
|
+
`Arg must be a number, got ${arg}`,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if(!condition)
|
|
47
|
+
throw Sass.new(`${message}${arg ? `: ${arg}` : ""}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Sassy theme compiler CLI.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* - Parse CLI arguments (supports JSON5 / YAML theme entries, globs resolved externally by the shell)
|
|
8
|
+
* - Create Theme instances for compilation units
|
|
9
|
+
* - Delegate compilation to Theme.build() which internally uses Compiler.compile()
|
|
10
|
+
* - Write (or print with --dry-run) the resulting VS Code colour theme JSON
|
|
11
|
+
* - Prevent unnecessary writes by hashing previous output
|
|
12
|
+
* - (Optional) Watch all participating source + imported files and recompile on change
|
|
13
|
+
*
|
|
14
|
+
* Key Concepts:
|
|
15
|
+
* Theme: {
|
|
16
|
+
* sourceFile: FileObject // entry theme file
|
|
17
|
+
* source: object // raw parsed data (must contain `config`)
|
|
18
|
+
* output: object // final theme JSON object
|
|
19
|
+
* dependencies: FileObject[] // secondary sources discovered during compile
|
|
20
|
+
* lookup: object // variable lookup data for compilation
|
|
21
|
+
* breadcrumbs: Map // variable resolution tracking
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* The Theme class manages its complete lifecycle:
|
|
25
|
+
* - load() - loads and parses the source file
|
|
26
|
+
* - build() - compiles the theme via Compiler
|
|
27
|
+
* - write() - outputs the compiled theme to file or stdout
|
|
28
|
+
* - Internal watch mode support with chokidar integration
|
|
29
|
+
*
|
|
30
|
+
* NOTE: The --profile flag is currently parsed but not yet producing timing output.
|
|
31
|
+
* Future enhancement could surface per-phase timings (load, compile, write, etc.).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import {program} from "commander"
|
|
35
|
+
import process from "node:process"
|
|
36
|
+
import url from "node:url"
|
|
37
|
+
import c from "@gesslar/colours"
|
|
38
|
+
|
|
39
|
+
import Cache from "./Cache.js"
|
|
40
|
+
import Sass from "./Sass.js"
|
|
41
|
+
import BuildCommand from "./BuildCommand.js"
|
|
42
|
+
import DirectoryObject from "./DirectoryObject.js"
|
|
43
|
+
import FileObject from "./FileObject.js"
|
|
44
|
+
import LintCommand from "./LintCommand.js"
|
|
45
|
+
import ResolveCommand from "./ResolveCommand.js"
|
|
46
|
+
import Term from "./Term.js"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Main application entry point.
|
|
50
|
+
* Sets up command line interface, validates input files, and handles compilation.
|
|
51
|
+
* Supports watch mode for automatic recompilation when files change.
|
|
52
|
+
*
|
|
53
|
+
* @returns {Promise<void>} Resolves when build process completes or exits on error.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/* =========================
|
|
57
|
+
Main
|
|
58
|
+
========================= */
|
|
59
|
+
|
|
60
|
+
void (async function main() {
|
|
61
|
+
// we need nerd mode info here so that it's available in 'catch'
|
|
62
|
+
const sassyOptions = {}
|
|
63
|
+
|
|
64
|
+
setupAbortHandlers()
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// setup the colour aliases
|
|
68
|
+
// Term status stuff
|
|
69
|
+
c.alias.set("success", "{F070}")
|
|
70
|
+
c.alias.set("success-bracket", "{F076}")
|
|
71
|
+
c.alias.set("info", "{F038}")
|
|
72
|
+
c.alias.set("info-bracket", "{F087}")
|
|
73
|
+
c.alias.set("warn", "{F215}")
|
|
74
|
+
c.alias.set("warn-bracket", "{F208}")
|
|
75
|
+
c.alias.set("error", "{F196}")
|
|
76
|
+
c.alias.set("error-bracket", "{F160}")
|
|
77
|
+
c.alias.set("modified", "{F127}")
|
|
78
|
+
c.alias.set("modified-bracket", "{F165}")
|
|
79
|
+
c.alias.set("muted", "{F240}")
|
|
80
|
+
c.alias.set("muted-bracket", "{F244}")
|
|
81
|
+
// Lint command
|
|
82
|
+
c.alias.set("context", "{F159}")
|
|
83
|
+
// Resolve command
|
|
84
|
+
c.alias.set("head", "{F220}")
|
|
85
|
+
c.alias.set("leaf", "{F151}")
|
|
86
|
+
c.alias.set("func", "{F111}")
|
|
87
|
+
c.alias.set("parens", "{F098}")
|
|
88
|
+
c.alias.set("line", "{F142}")
|
|
89
|
+
c.alias.set("hex", "{F140}")
|
|
90
|
+
c.alias.set("hash", "{F147}{<B}")
|
|
91
|
+
c.alias.set("hexAlpha", "{F127}{<I}")
|
|
92
|
+
c.alias.set("arrow", "{F033}")
|
|
93
|
+
|
|
94
|
+
const cache = new Cache()
|
|
95
|
+
const cr = new DirectoryObject(url.fileURLToPath(new url.URL("..", import.meta.url)))
|
|
96
|
+
const cwd = new DirectoryObject(process.cwd())
|
|
97
|
+
const packageJson = new FileObject("package.json", cr)
|
|
98
|
+
const pkgJsonResult = await cache.loadCachedData(packageJson)
|
|
99
|
+
const pkgJson = pkgJsonResult
|
|
100
|
+
|
|
101
|
+
// These are available to all subcommands in addition to whatever they
|
|
102
|
+
// provide.
|
|
103
|
+
const alwaysAvailable = {
|
|
104
|
+
"nerd": ["--nerd", "enable stack tracing for debug purposes when errors are thrown"]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.name(pkgJson.name)
|
|
109
|
+
.description(pkgJson.description)
|
|
110
|
+
.version(pkgJson.version)
|
|
111
|
+
|
|
112
|
+
// Add the build subcommand
|
|
113
|
+
const buildCommand = new BuildCommand({cwd, packageJson: pkgJson})
|
|
114
|
+
buildCommand.cache = cache
|
|
115
|
+
|
|
116
|
+
void(await buildCommand.buildCli(program))
|
|
117
|
+
.addCliOptions(alwaysAvailable, false)
|
|
118
|
+
|
|
119
|
+
// Add the resolve subcommand
|
|
120
|
+
const resolveCommand = new ResolveCommand({cwd, packageJson: pkgJson})
|
|
121
|
+
resolveCommand.cache = cache
|
|
122
|
+
|
|
123
|
+
void(await resolveCommand.buildCli(program))
|
|
124
|
+
.addCliOptions(alwaysAvailable, false)
|
|
125
|
+
|
|
126
|
+
// Add the lint subcommand
|
|
127
|
+
const lintCommand = new LintCommand({cwd, packageJson: pkgJson})
|
|
128
|
+
lintCommand.cache = cache
|
|
129
|
+
|
|
130
|
+
void(await lintCommand.buildCli(program))
|
|
131
|
+
.addCliOptions(alwaysAvailable, false)
|
|
132
|
+
|
|
133
|
+
// Let'er rip, bitches! VROOM VROOM, motherfucker!!
|
|
134
|
+
await program.parseAsync()
|
|
135
|
+
|
|
136
|
+
} catch(error) {
|
|
137
|
+
Sass.new("Starting Sassy.", error)
|
|
138
|
+
.report(sassyOptions.nerd || true)
|
|
139
|
+
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Creates handlers for various reasons that the application may crash.
|
|
145
|
+
*/
|
|
146
|
+
function setupAbortHandlers() {
|
|
147
|
+
void["SIGINT", "SIGTERM", "SIGHUP"].forEach(signal => {
|
|
148
|
+
process.on(signal, async() => {
|
|
149
|
+
Term.log(`Received ${signal}, performing graceful shutdown...`)
|
|
150
|
+
await Term.resetTerminal()
|
|
151
|
+
process.exit(0)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
})()
|