emily-css 1.2.8 → 1.2.10
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/CHANGELOG.md +20 -0
- package/package.json +3 -2
- package/src/index.js +1887 -1881
- package/src/init.js +1001 -942
- package/src/test/e2e.test.js +332 -0
- package/src/validate.js +227 -0
- package/src/validateConfig.js +36 -0
package/src/init.js
CHANGED
|
@@ -1,997 +1,1056 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const crossSpawn = require("cross-spawn");
|
|
4
|
-
const { Select, Input, Confirm } = require("enquirer");
|
|
5
|
-
const chalk = require("chalk");
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const crossSpawn = require("cross-spawn");
|
|
4
|
+
const { Select, Input, Confirm } = require("enquirer");
|
|
5
|
+
const chalk = require("chalk");
|
|
6
6
|
const ora = require("ora");
|
|
7
7
|
const boxen = require("boxen");
|
|
8
8
|
const { DEFAULT_PURGE_IGNORE, PURGE_EXTENSIONS } = require("./constants.js");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{ value: "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{ value: "
|
|
27
|
-
{ value: "#
|
|
28
|
-
{ value: "#
|
|
29
|
-
{ value: "#7C3AED", label: "Purple" },
|
|
30
|
-
{ value: "#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{ value: "#
|
|
36
|
-
{ value: "#
|
|
37
|
-
{ value: "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{ value: "
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{ value: "
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
];
|
|
9
|
+
const {
|
|
10
|
+
ALLOWED_FONT_FAMILIES,
|
|
11
|
+
validateHexColour,
|
|
12
|
+
validateSpacingValue,
|
|
13
|
+
validateFontFamily,
|
|
14
|
+
validateConfigShape,
|
|
15
|
+
} = require("./validate.js");
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
const COLOUR_PRESETS = {
|
|
22
|
+
primary: [
|
|
23
|
+
{ value: "custom", label: "Enter your own hex" },
|
|
24
|
+
{ value: "#DB2777", label: "Emily Pink" },
|
|
25
|
+
{ value: "#2563EB", label: "Blue" },
|
|
26
|
+
{ value: "#028090", label: "Teal" },
|
|
27
|
+
{ value: "#114B5F", label: "Deep Teal" },
|
|
28
|
+
{ value: "#15803D", label: "Green" },
|
|
29
|
+
{ value: "#7C3AED", label: "Purple" },
|
|
30
|
+
{ value: "#E05C00", label: "Burnt Orange" },
|
|
31
|
+
],
|
|
32
|
+
secondary: [
|
|
33
|
+
{ value: "custom", label: "Enter your own hex" },
|
|
34
|
+
{ value: "#2563EB", label: "Blue" },
|
|
35
|
+
{ value: "#028090", label: "Teal" },
|
|
36
|
+
{ value: "#7C3AED", label: "Purple" },
|
|
37
|
+
{ value: "#DB2777", label: "Emily Pink" },
|
|
38
|
+
{ value: "#F59E0B", label: "Amber" },
|
|
39
|
+
{ value: "#57534E", label: "Warm Grey" },
|
|
40
|
+
],
|
|
41
|
+
success: [
|
|
42
|
+
{ value: "#017F65", label: "Accessible Green (recommended)" },
|
|
43
|
+
{ value: "#15803D", label: "Forest Green" },
|
|
44
|
+
{ value: "custom", label: "Enter your own hex" },
|
|
45
|
+
],
|
|
46
|
+
warning: [
|
|
47
|
+
{ value: "#FFC107", label: "Amber (recommended)" },
|
|
48
|
+
{ value: "#F59E0B", label: "Orange Amber" },
|
|
49
|
+
{ value: "custom", label: "Enter your own hex" },
|
|
50
|
+
],
|
|
51
|
+
error: [
|
|
52
|
+
{ value: "#B20000", label: "Accessible Red (recommended)" },
|
|
53
|
+
{ value: "#DC2626", label: "Red" },
|
|
54
|
+
{ value: "custom", label: "Enter your own hex" },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
59
58
|
const CORE_COLOUR_KEYS = new Set([
|
|
60
|
-
"brand",
|
|
61
|
-
"accent",
|
|
62
|
-
"btn-primary",
|
|
63
|
-
"btn-secondary",
|
|
64
|
-
"success",
|
|
65
|
-
"warning",
|
|
66
|
-
"error",
|
|
67
|
-
"neutral",
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
// ============================================================================
|
|
71
|
-
// HELPERS
|
|
72
|
-
// ============================================================================
|
|
73
|
-
|
|
74
|
-
function isValidHex(hex) {
|
|
75
|
-
return /^#[0-9A-F]{6}$/i.test(hex);
|
|
76
|
-
}
|
|
77
|
-
|
|
59
|
+
"brand",
|
|
60
|
+
"accent",
|
|
61
|
+
"btn-primary",
|
|
62
|
+
"btn-secondary",
|
|
63
|
+
"success",
|
|
64
|
+
"warning",
|
|
65
|
+
"error",
|
|
66
|
+
"neutral",
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// HELPERS
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
78
73
|
function isPlainObject(value) {
|
|
79
74
|
return (
|
|
80
75
|
value !== null &&
|
|
81
|
-
typeof value === "object" &&
|
|
82
|
-
!Array.isArray(value)
|
|
83
|
-
);
|
|
76
|
+
typeof value === "object" &&
|
|
77
|
+
!Array.isArray(value)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mergeWithDefaults(defaults, existing) {
|
|
82
|
+
if (!isPlainObject(defaults)) {
|
|
83
|
+
return existing === undefined ? defaults : existing;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const output = { ...defaults };
|
|
87
|
+
|
|
88
|
+
if (!isPlainObject(existing)) {
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Object.keys(existing).forEach((key) => {
|
|
93
|
+
if (isPlainObject(defaults[key]) && isPlainObject(existing[key])) {
|
|
94
|
+
output[key] = mergeWithDefaults(defaults[key], existing[key]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
output[key] = existing[key];
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function colourSwatch(hex) {
|
|
105
|
+
return chalk.hex(hex)("■");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normaliseHex(value) {
|
|
109
|
+
if (typeof value !== "string") return null;
|
|
110
|
+
const trimmed = value.trim();
|
|
111
|
+
const result = validateHexColour(trimmed);
|
|
112
|
+
return result.valid ? trimmed.toUpperCase() : null;
|
|
84
113
|
}
|
|
85
114
|
|
|
86
|
-
function
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const output = { ...defaults };
|
|
92
|
-
|
|
93
|
-
if (!isPlainObject(existing)) {
|
|
94
|
-
return output;
|
|
95
|
-
}
|
|
115
|
+
function formatValueForMessage(value) {
|
|
116
|
+
if (value === null) return "null";
|
|
117
|
+
if (value === undefined) return "undefined";
|
|
118
|
+
return "'" + String(value) + "'";
|
|
119
|
+
}
|
|
96
120
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
async function askValidatedInput({
|
|
122
|
+
promptName,
|
|
123
|
+
message,
|
|
124
|
+
initial,
|
|
125
|
+
validator,
|
|
126
|
+
normalise,
|
|
127
|
+
}) {
|
|
128
|
+
let nextInitial = initial;
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
|
|
130
|
+
while (true) {
|
|
131
|
+
const raw = await new Input({
|
|
132
|
+
name: promptName,
|
|
133
|
+
message,
|
|
134
|
+
initial: nextInitial,
|
|
135
|
+
}).run();
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
const value = typeof raw === "string" ? raw.trim() : raw;
|
|
138
|
+
const result = validator(value);
|
|
108
139
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
140
|
+
if (result.valid) {
|
|
141
|
+
console.log(chalk.green("✓ Valid"));
|
|
142
|
+
return typeof normalise === "function" ? normalise(value) : value;
|
|
143
|
+
}
|
|
112
144
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
console.log(
|
|
146
|
+
chalk.red(
|
|
147
|
+
"✗ Invalid: " + result.reason + " (got " + formatValueForMessage(raw) + ")",
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
nextInitial = value || initial;
|
|
151
|
+
}
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
async function askHex(promptName, message, initial) {
|
|
120
|
-
|
|
121
|
-
|
|
155
|
+
return askValidatedInput({
|
|
156
|
+
promptName,
|
|
122
157
|
message,
|
|
123
158
|
initial: initial || "#000000",
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
: "Enter a valid hex colour, e.g. #0077B6";
|
|
159
|
+
validator: validateHexColour,
|
|
160
|
+
normalise: function (value) {
|
|
161
|
+
return String(value).toUpperCase();
|
|
128
162
|
},
|
|
129
|
-
}).run();
|
|
130
|
-
|
|
131
|
-
return value.toUpperCase();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function askColourFromPresets(label, presets, defaultHex, currentHex) {
|
|
135
|
-
const defaultHexValue = normaliseHex(defaultHex);
|
|
136
|
-
const currentHexValue = normaliseHex(currentHex);
|
|
137
|
-
|
|
138
|
-
const choices = presets.map(function (opt) {
|
|
139
|
-
if (opt.value === "custom") {
|
|
140
|
-
return { name: "custom", message: "Enter your own hex" };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const upperHex = String(opt.value).toUpperCase();
|
|
144
|
-
return {
|
|
145
|
-
name: upperHex,
|
|
146
|
-
message:
|
|
147
|
-
colourSwatch(upperHex) + " " + opt.label + " " + chalk.gray(upperHex),
|
|
148
|
-
};
|
|
149
163
|
});
|
|
150
|
-
|
|
151
|
-
let initial = Math.max(
|
|
152
|
-
0,
|
|
153
|
-
choices.findIndex((choice) => choice.name === "custom"),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (currentHexValue) {
|
|
157
|
-
const currentIndex = choices.findIndex(
|
|
158
|
-
(choice) => choice.name === currentHexValue,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
if (currentIndex !== -1) {
|
|
162
|
-
initial = currentIndex;
|
|
163
|
-
} else {
|
|
164
|
-
choices.unshift({
|
|
165
|
-
name: "__current__",
|
|
166
|
-
message:
|
|
167
|
-
"Keep current " +
|
|
168
|
-
label +
|
|
169
|
-
" " +
|
|
170
|
-
colourSwatch(currentHexValue) +
|
|
171
|
-
" " +
|
|
172
|
-
chalk.gray(currentHexValue),
|
|
173
|
-
});
|
|
174
|
-
initial = 0;
|
|
175
|
-
}
|
|
176
|
-
} else if (defaultHexValue) {
|
|
177
|
-
const defaultIndex = choices.findIndex(
|
|
178
|
-
(choice) => choice.name === defaultHexValue,
|
|
179
|
-
);
|
|
180
|
-
if (defaultIndex !== -1) {
|
|
181
|
-
initial = defaultIndex;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const selected = await new Select({
|
|
186
|
-
name: label,
|
|
187
|
-
message: label + " colour",
|
|
188
|
-
choices,
|
|
189
|
-
initial,
|
|
190
|
-
}).run();
|
|
191
|
-
|
|
192
|
-
if (selected === "__current__" && currentHexValue) return currentHexValue;
|
|
193
|
-
if (selected !== "custom") return selected.toUpperCase();
|
|
194
|
-
|
|
195
|
-
const fallbackHex = currentHexValue || defaultHexValue || "#000000";
|
|
196
|
-
return askHex(label + "Custom", "Enter " + label + " hex", fallbackHex);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function hasFile(fileName) {
|
|
200
|
-
return fs.existsSync(path.join(process.cwd(), fileName));
|
|
201
164
|
}
|
|
202
|
-
|
|
203
|
-
function
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
165
|
+
|
|
166
|
+
async function askColourFromPresets(label, presets, defaultHex, currentHex) {
|
|
167
|
+
const defaultHexValue = normaliseHex(defaultHex);
|
|
168
|
+
const currentHexValue = normaliseHex(currentHex);
|
|
169
|
+
|
|
170
|
+
const choices = presets.map(function (opt) {
|
|
171
|
+
if (opt.value === "custom") {
|
|
172
|
+
return { name: "custom", message: "Enter your own hex" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const upperHex = String(opt.value).toUpperCase();
|
|
176
|
+
return {
|
|
177
|
+
name: upperHex,
|
|
178
|
+
message:
|
|
179
|
+
colourSwatch(upperHex) + " " + opt.label + " " + chalk.gray(upperHex),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
let initial = Math.max(
|
|
184
|
+
0,
|
|
185
|
+
choices.findIndex((choice) => choice.name === "custom"),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (currentHexValue) {
|
|
189
|
+
const currentIndex = choices.findIndex(
|
|
190
|
+
(choice) => choice.name === currentHexValue,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (currentIndex !== -1) {
|
|
194
|
+
initial = currentIndex;
|
|
195
|
+
} else {
|
|
196
|
+
choices.unshift({
|
|
197
|
+
name: "__current__",
|
|
198
|
+
message:
|
|
199
|
+
"Keep current " +
|
|
200
|
+
label +
|
|
201
|
+
" " +
|
|
202
|
+
colourSwatch(currentHexValue) +
|
|
203
|
+
" " +
|
|
204
|
+
chalk.gray(currentHexValue),
|
|
205
|
+
});
|
|
206
|
+
initial = 0;
|
|
207
|
+
}
|
|
208
|
+
} else if (defaultHexValue) {
|
|
209
|
+
const defaultIndex = choices.findIndex(
|
|
210
|
+
(choice) => choice.name === defaultHexValue,
|
|
211
|
+
);
|
|
212
|
+
if (defaultIndex !== -1) {
|
|
213
|
+
initial = defaultIndex;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const selected = await new Select({
|
|
218
|
+
name: label,
|
|
219
|
+
message: label + " colour",
|
|
220
|
+
choices,
|
|
221
|
+
initial,
|
|
222
|
+
}).run();
|
|
223
|
+
|
|
224
|
+
if (selected === "__current__" && currentHexValue) return currentHexValue;
|
|
225
|
+
if (selected !== "custom") return selected.toUpperCase();
|
|
226
|
+
|
|
227
|
+
const fallbackHex = currentHexValue || defaultHexValue || "#000000";
|
|
228
|
+
return askHex(label + "Custom", "Enter " + label + " hex", fallbackHex);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function hasFile(fileName) {
|
|
232
|
+
return fs.existsSync(path.join(process.cwd(), fileName));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function readPackageJson() {
|
|
236
|
+
const packagePath = path.join(process.cwd(), "package.json");
|
|
237
|
+
|
|
238
|
+
if (!fs.existsSync(packagePath)) return null;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function readExistingConfig() {
|
|
248
|
+
const configPath = path.join(process.cwd(), "emily.config.json");
|
|
249
|
+
|
|
250
|
+
if (!fs.existsSync(configPath)) return null;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
234
259
|
function getExistingAdditionalColours(existingColours) {
|
|
235
|
-
if (!isPlainObject(existingColours)) return {};
|
|
236
|
-
|
|
237
|
-
const additional = {};
|
|
238
|
-
Object.entries(existingColours).forEach(([name, value]) => {
|
|
239
|
-
if (CORE_COLOUR_KEYS.has(name)) return;
|
|
240
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) return;
|
|
241
|
-
|
|
242
|
-
const upperHex = normaliseHex(value);
|
|
243
|
-
if (!upperHex) return;
|
|
244
|
-
additional[name] = upperHex;
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
return additional;
|
|
248
|
-
}
|
|
249
|
-
|
|
260
|
+
if (!isPlainObject(existingColours)) return {};
|
|
261
|
+
|
|
262
|
+
const additional = {};
|
|
263
|
+
Object.entries(existingColours).forEach(([name, value]) => {
|
|
264
|
+
if (CORE_COLOUR_KEYS.has(name)) return;
|
|
265
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) return;
|
|
266
|
+
|
|
267
|
+
const upperHex = normaliseHex(value);
|
|
268
|
+
if (!upperHex) return;
|
|
269
|
+
additional[name] = upperHex;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return additional;
|
|
273
|
+
}
|
|
274
|
+
|
|
250
275
|
function getBaseUnitInitial(config) {
|
|
251
276
|
const rawBaseUnit = config && typeof config.baseUnit === "string"
|
|
252
|
-
? config.baseUnit
|
|
277
|
+
? config.baseUnit.trim()
|
|
253
278
|
: "";
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return String(parsed);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function hasDependency(packageJson, dependencyName) {
|
|
260
|
-
if (!packageJson) return false;
|
|
261
|
-
|
|
262
|
-
return Boolean(
|
|
263
|
-
packageJson.dependencies?.[dependencyName] ||
|
|
264
|
-
packageJson.devDependencies?.[dependencyName],
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function titleCasePackageName(name) {
|
|
269
|
-
return name.replace(/-/g, " ").replace(/\b\w/g, function (c) {
|
|
270
|
-
return c.toUpperCase();
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function addEmilyScriptsToPackageJson() {
|
|
275
|
-
const packagePath = path.join(process.cwd(), "package.json");
|
|
276
|
-
|
|
277
|
-
if (!fs.existsSync(packagePath)) return false;
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
281
|
-
|
|
282
|
-
packageJson.scripts = packageJson.scripts || {};
|
|
283
|
-
|
|
284
|
-
let changed = false;
|
|
285
|
-
|
|
286
|
-
const scripts = {
|
|
287
|
-
"emily:build": "emily-css build",
|
|
288
|
-
"emily:watch": "emily-css watch",
|
|
289
|
-
"emily:doctor": "emily-css doctor",
|
|
290
|
-
"emily:migrate": "emily-css migrate",
|
|
291
|
-
"emily:info": "emily-css info",
|
|
292
|
-
"emily:manifest": "emily-css manifest",
|
|
293
|
-
"emily:version": "emily-css version",
|
|
294
|
-
"emily:help": "emily-css help",
|
|
295
|
-
"emily:showcase": "emily-css showcase",
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
for (const [key, value] of Object.entries(scripts)) {
|
|
299
|
-
if (!packageJson.scripts[key]) {
|
|
300
|
-
packageJson.scripts[key] = value;
|
|
301
|
-
changed = true;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (changed) {
|
|
306
|
-
fs.writeFileSync(
|
|
307
|
-
packagePath,
|
|
308
|
-
JSON.stringify(packageJson, null, 2) + "\n",
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return true;
|
|
313
|
-
} catch {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ============================================================================
|
|
319
|
-
// PROJECT DETECTION
|
|
320
|
-
// ============================================================================
|
|
321
|
-
|
|
322
|
-
function detectProject() {
|
|
323
|
-
const packageJson = readPackageJson();
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
hasFile("nuxt.config.ts") ||
|
|
327
|
-
hasFile("nuxt.config.js") ||
|
|
328
|
-
hasDependency(packageJson, "nuxt")
|
|
329
|
-
) {
|
|
330
|
-
return {
|
|
331
|
-
name: "Nuxt",
|
|
332
|
-
sourceDir: ".",
|
|
333
|
-
outputPath: "public/emily.min.css",
|
|
334
|
-
sourceGlobs: [
|
|
335
|
-
"./components/**/*.{vue,js,ts}",
|
|
336
|
-
"./pages/**/*.vue",
|
|
337
|
-
"./layouts/**/*.vue",
|
|
338
|
-
"./app.vue",
|
|
339
|
-
],
|
|
340
|
-
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (hasDependency(packageJson, "next")) {
|
|
345
|
-
return {
|
|
346
|
-
name: "Next.js",
|
|
347
|
-
sourceDir: ".",
|
|
348
|
-
outputPath: "public/emily.min.css",
|
|
349
|
-
sourceGlobs: [
|
|
350
|
-
"./app/**/*.{js,jsx,ts,tsx}",
|
|
351
|
-
"./pages/**/*.{js,jsx,ts,tsx}",
|
|
352
|
-
"./components/**/*.{js,jsx,ts,tsx}",
|
|
353
|
-
"./src/**/*.{js,jsx,ts,tsx}",
|
|
354
|
-
],
|
|
355
|
-
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (hasDependency(packageJson, "react")) {
|
|
360
|
-
return {
|
|
361
|
-
name: "React",
|
|
362
|
-
sourceDir: "./src",
|
|
363
|
-
outputPath: hasFile("public")
|
|
364
|
-
? "public/emily.min.css"
|
|
365
|
-
: "dist/emily.min.css",
|
|
366
|
-
sourceGlobs: [
|
|
367
|
-
"./src/**/*.{js,jsx,ts,tsx}",
|
|
368
|
-
"./components/**/*.{js,jsx,ts,tsx}",
|
|
369
|
-
],
|
|
370
|
-
linkHint: hasFile("public")
|
|
371
|
-
? '<link rel="stylesheet" href="/emily.min.css">'
|
|
372
|
-
: '<link rel="stylesheet" href="./dist/emily.min.css">',
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
hasDependency(packageJson, "vue") ||
|
|
378
|
-
hasFile("vite.config.ts") ||
|
|
379
|
-
hasFile("vite.config.js")
|
|
380
|
-
) {
|
|
381
|
-
return {
|
|
382
|
-
name: "Vue/Vite",
|
|
383
|
-
sourceDir: "./src",
|
|
384
|
-
outputPath: "public/emily.min.css",
|
|
385
|
-
sourceGlobs: ["./src/**/*.{vue,js,ts}"],
|
|
386
|
-
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (hasDependency(packageJson, "astro") || hasFile("astro.config.mjs")) {
|
|
391
|
-
return {
|
|
392
|
-
name: "Astro",
|
|
393
|
-
sourceDir: "./src",
|
|
394
|
-
outputPath: "public/emily.min.css",
|
|
395
|
-
sourceGlobs: ["./src/**/*.{astro,html,js,ts,vue,jsx,tsx,svelte}"],
|
|
396
|
-
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const rootFiles = fs.readdirSync(process.cwd());
|
|
401
|
-
const hasDrupalInfoFile = rootFiles.some(function (file) {
|
|
402
|
-
return file.endsWith(".info.yml");
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
if (
|
|
406
|
-
hasDrupalInfoFile ||
|
|
407
|
-
fs.existsSync(path.join(process.cwd(), "web/core"))
|
|
408
|
-
) {
|
|
409
|
-
return {
|
|
410
|
-
name: "Drupal",
|
|
411
|
-
sourceDir: ".",
|
|
412
|
-
outputPath: "dist/emily.min.css",
|
|
413
|
-
sourceGlobs: [
|
|
414
|
-
"./web/themes/custom/**/*.{twig,js,ts}",
|
|
415
|
-
"./templates/**/*.html.twig",
|
|
416
|
-
"./components/**/*.twig",
|
|
417
|
-
"./**/*.theme",
|
|
418
|
-
],
|
|
419
|
-
linkHint: "Attach dist/emily.min.css through your theme library YAML.",
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
name: "Static/Generic",
|
|
425
|
-
sourceDir: ".",
|
|
426
|
-
outputPath: "dist/emily.min.css",
|
|
427
|
-
sourceGlobs: [
|
|
428
|
-
"./**/*.{html,htm,twig,njk,liquid,hbs,php,astro,svelte,vue,blade.php,jinja,jinja2,j2}",
|
|
429
|
-
],
|
|
430
|
-
linkHint: '<link rel="stylesheet" href="./dist/emily.min.css">',
|
|
431
|
-
};
|
|
279
|
+
if (validateSpacingValue(rawBaseUnit).valid) return rawBaseUnit;
|
|
280
|
+
return "18px";
|
|
432
281
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
282
|
+
|
|
283
|
+
function hasDependency(packageJson, dependencyName) {
|
|
284
|
+
if (!packageJson) return false;
|
|
285
|
+
|
|
286
|
+
return Boolean(
|
|
287
|
+
packageJson.dependencies?.[dependencyName] ||
|
|
288
|
+
packageJson.devDependencies?.[dependencyName],
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function titleCasePackageName(name) {
|
|
293
|
+
return name.replace(/-/g, " ").replace(/\b\w/g, function (c) {
|
|
294
|
+
return c.toUpperCase();
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function addEmilyScriptsToPackageJson() {
|
|
299
|
+
const packagePath = path.join(process.cwd(), "package.json");
|
|
300
|
+
|
|
301
|
+
if (!fs.existsSync(packagePath)) return false;
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
305
|
+
|
|
306
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
307
|
+
|
|
308
|
+
let changed = false;
|
|
309
|
+
|
|
310
|
+
const scripts = {
|
|
311
|
+
"emily:build": "emily-css build",
|
|
312
|
+
"emily:watch": "emily-css watch",
|
|
313
|
+
"emily:doctor": "emily-css doctor",
|
|
314
|
+
"emily:migrate": "emily-css migrate",
|
|
315
|
+
"emily:info": "emily-css info",
|
|
316
|
+
"emily:manifest": "emily-css manifest",
|
|
317
|
+
"emily:version": "emily-css version",
|
|
318
|
+
"emily:help": "emily-css help",
|
|
319
|
+
"emily:showcase": "emily-css showcase",
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
for (const [key, value] of Object.entries(scripts)) {
|
|
323
|
+
if (!packageJson.scripts[key]) {
|
|
324
|
+
packageJson.scripts[key] = value;
|
|
325
|
+
changed = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (changed) {
|
|
330
|
+
fs.writeFileSync(
|
|
331
|
+
packagePath,
|
|
332
|
+
JSON.stringify(packageJson, null, 2) + "\n",
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// PROJECT DETECTION
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
function detectProject() {
|
|
347
|
+
const packageJson = readPackageJson();
|
|
348
|
+
|
|
349
|
+
if (
|
|
350
|
+
hasFile("nuxt.config.ts") ||
|
|
351
|
+
hasFile("nuxt.config.js") ||
|
|
352
|
+
hasDependency(packageJson, "nuxt")
|
|
353
|
+
) {
|
|
354
|
+
return {
|
|
355
|
+
name: "Nuxt",
|
|
356
|
+
sourceDir: ".",
|
|
357
|
+
outputPath: "public/emily.min.css",
|
|
358
|
+
sourceGlobs: [
|
|
359
|
+
"./components/**/*.{vue,js,ts}",
|
|
360
|
+
"./pages/**/*.vue",
|
|
361
|
+
"./layouts/**/*.vue",
|
|
362
|
+
"./app.vue",
|
|
363
|
+
],
|
|
364
|
+
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (hasDependency(packageJson, "next")) {
|
|
369
|
+
return {
|
|
370
|
+
name: "Next.js",
|
|
371
|
+
sourceDir: ".",
|
|
372
|
+
outputPath: "public/emily.min.css",
|
|
373
|
+
sourceGlobs: [
|
|
374
|
+
"./app/**/*.{js,jsx,ts,tsx}",
|
|
375
|
+
"./pages/**/*.{js,jsx,ts,tsx}",
|
|
376
|
+
"./components/**/*.{js,jsx,ts,tsx}",
|
|
377
|
+
"./src/**/*.{js,jsx,ts,tsx}",
|
|
378
|
+
],
|
|
379
|
+
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (hasDependency(packageJson, "react")) {
|
|
384
|
+
return {
|
|
385
|
+
name: "React",
|
|
386
|
+
sourceDir: "./src",
|
|
387
|
+
outputPath: hasFile("public")
|
|
388
|
+
? "public/emily.min.css"
|
|
389
|
+
: "dist/emily.min.css",
|
|
390
|
+
sourceGlobs: [
|
|
391
|
+
"./src/**/*.{js,jsx,ts,tsx}",
|
|
392
|
+
"./components/**/*.{js,jsx,ts,tsx}",
|
|
393
|
+
],
|
|
394
|
+
linkHint: hasFile("public")
|
|
395
|
+
? '<link rel="stylesheet" href="/emily.min.css">'
|
|
396
|
+
: '<link rel="stylesheet" href="./dist/emily.min.css">',
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
hasDependency(packageJson, "vue") ||
|
|
402
|
+
hasFile("vite.config.ts") ||
|
|
403
|
+
hasFile("vite.config.js")
|
|
404
|
+
) {
|
|
405
|
+
return {
|
|
406
|
+
name: "Vue/Vite",
|
|
407
|
+
sourceDir: "./src",
|
|
408
|
+
outputPath: "public/emily.min.css",
|
|
409
|
+
sourceGlobs: ["./src/**/*.{vue,js,ts}"],
|
|
410
|
+
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (hasDependency(packageJson, "astro") || hasFile("astro.config.mjs")) {
|
|
415
|
+
return {
|
|
416
|
+
name: "Astro",
|
|
417
|
+
sourceDir: "./src",
|
|
418
|
+
outputPath: "public/emily.min.css",
|
|
419
|
+
sourceGlobs: ["./src/**/*.{astro,html,js,ts,vue,jsx,tsx,svelte}"],
|
|
420
|
+
linkHint: '<link rel="stylesheet" href="/emily.min.css">',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const rootFiles = fs.readdirSync(process.cwd());
|
|
425
|
+
const hasDrupalInfoFile = rootFiles.some(function (file) {
|
|
426
|
+
return file.endsWith(".info.yml");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (
|
|
430
|
+
hasDrupalInfoFile ||
|
|
431
|
+
fs.existsSync(path.join(process.cwd(), "web/core"))
|
|
432
|
+
) {
|
|
433
|
+
return {
|
|
434
|
+
name: "Drupal",
|
|
435
|
+
sourceDir: ".",
|
|
436
|
+
outputPath: "dist/emily.min.css",
|
|
437
|
+
sourceGlobs: [
|
|
438
|
+
"./web/themes/custom/**/*.{twig,js,ts}",
|
|
439
|
+
"./templates/**/*.html.twig",
|
|
440
|
+
"./components/**/*.twig",
|
|
441
|
+
"./**/*.theme",
|
|
442
|
+
],
|
|
443
|
+
linkHint: "Attach dist/emily.min.css through your theme library YAML.",
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
name: "Static/Generic",
|
|
449
|
+
sourceDir: ".",
|
|
450
|
+
outputPath: "dist/emily.min.css",
|
|
451
|
+
sourceGlobs: [
|
|
452
|
+
"./**/*.{html,htm,twig,njk,liquid,hbs,php,astro,svelte,vue,blade.php,jinja,jinja2,j2}",
|
|
453
|
+
],
|
|
454
|
+
linkHint: '<link rel="stylesheet" href="./dist/emily.min.css">',
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ============================================================================
|
|
459
|
+
// CONFIG BUILDER
|
|
460
|
+
// ============================================================================
|
|
461
|
+
|
|
438
462
|
function createDefaultConfig({
|
|
439
463
|
name,
|
|
440
464
|
colours,
|
|
441
465
|
headingFont,
|
|
442
466
|
bodyFont,
|
|
443
|
-
baseUnit,
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
"
|
|
482
|
-
"
|
|
483
|
-
"
|
|
484
|
-
"
|
|
485
|
-
"
|
|
486
|
-
"
|
|
487
|
-
"
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
1
|
|
507
|
-
|
|
508
|
-
2
|
|
509
|
-
|
|
510
|
-
3
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
"
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
{ name: "
|
|
568
|
-
{ name: "
|
|
569
|
-
{ name: "
|
|
570
|
-
{ name: "
|
|
571
|
-
{ name: "
|
|
572
|
-
{ name: "
|
|
573
|
-
{ name: "
|
|
574
|
-
{ name: "
|
|
575
|
-
{ name: "
|
|
576
|
-
{ name: "
|
|
577
|
-
{ name: "
|
|
578
|
-
{ name: "
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
console.log(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
467
|
+
baseUnit,
|
|
468
|
+
baseFontSize,
|
|
469
|
+
detectedProject,
|
|
470
|
+
}) {
|
|
471
|
+
return {
|
|
472
|
+
name,
|
|
473
|
+
description: name + " design system",
|
|
474
|
+
|
|
475
|
+
baseUnit,
|
|
476
|
+
baseFontSize: baseFontSize || "16px",
|
|
477
|
+
|
|
478
|
+
fontFamily: {
|
|
479
|
+
heading: headingFont,
|
|
480
|
+
body: bodyFont,
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
customFonts: [],
|
|
484
|
+
|
|
485
|
+
output: {
|
|
486
|
+
css: detectedProject.outputPath,
|
|
487
|
+
fullCss: "dist/emily.css",
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
manifest: true,
|
|
491
|
+
|
|
492
|
+
colours,
|
|
493
|
+
|
|
494
|
+
semanticColours: {
|
|
495
|
+
dark: "#1A1A1A",
|
|
496
|
+
light: "#FAFAFA",
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
purge: {
|
|
500
|
+
projectType: detectedProject.name,
|
|
501
|
+
sourceDir: detectedProject.sourceDir,
|
|
502
|
+
sourceGlobs: detectedProject.sourceGlobs,
|
|
503
|
+
ignore: DEFAULT_PURGE_IGNORE,
|
|
504
|
+
safelist: [
|
|
505
|
+
"bg-dark",
|
|
506
|
+
"text-dark",
|
|
507
|
+
"border-dark",
|
|
508
|
+
"fill-dark",
|
|
509
|
+
"bg-light",
|
|
510
|
+
"text-light",
|
|
511
|
+
"border-light",
|
|
512
|
+
"fill-light",
|
|
513
|
+
],
|
|
514
|
+
extensions: PURGE_EXTENSIONS,
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
breakpoints: {
|
|
518
|
+
sm: "640px",
|
|
519
|
+
md: "768px",
|
|
520
|
+
lg: "1024px",
|
|
521
|
+
xl: "1280px",
|
|
522
|
+
"2xl": "1536px",
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
spacing: {
|
|
526
|
+
scale: {
|
|
527
|
+
0: "0px",
|
|
528
|
+
px: "1px",
|
|
529
|
+
0.5: "0.125rem",
|
|
530
|
+
1: "0.25rem",
|
|
531
|
+
1.5: "0.375rem",
|
|
532
|
+
2: "0.5rem",
|
|
533
|
+
2.5: "0.625rem",
|
|
534
|
+
3: "0.75rem",
|
|
535
|
+
3.5: "0.875rem",
|
|
536
|
+
4: "1rem",
|
|
537
|
+
5: "1.25rem",
|
|
538
|
+
6: "1.5rem",
|
|
539
|
+
7: "1.75rem",
|
|
540
|
+
8: "2rem",
|
|
541
|
+
9: "2.25rem",
|
|
542
|
+
10: "2.5rem",
|
|
543
|
+
11: "2.75rem",
|
|
544
|
+
12: "3rem",
|
|
545
|
+
14: "3.5rem",
|
|
546
|
+
16: "4rem",
|
|
547
|
+
20: "5rem",
|
|
548
|
+
24: "6rem",
|
|
549
|
+
28: "7rem",
|
|
550
|
+
32: "8rem",
|
|
551
|
+
36: "9rem",
|
|
552
|
+
40: "10rem",
|
|
553
|
+
44: "11rem",
|
|
554
|
+
48: "12rem",
|
|
555
|
+
52: "13rem",
|
|
556
|
+
56: "14rem",
|
|
557
|
+
60: "15rem",
|
|
558
|
+
64: "16rem",
|
|
559
|
+
72: "18rem",
|
|
560
|
+
80: "20rem",
|
|
561
|
+
96: "24rem",
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
borderWidths: [0, 2, 4, 8],
|
|
565
|
+
|
|
566
|
+
borderRadius: {
|
|
567
|
+
none: "0",
|
|
568
|
+
sm: "4px",
|
|
569
|
+
base: "8px",
|
|
570
|
+
md: "12px",
|
|
571
|
+
lg: "16px",
|
|
572
|
+
xl: "20px",
|
|
573
|
+
"2xl": "24px",
|
|
574
|
+
"3xl": "32px",
|
|
575
|
+
full: "9999px",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
typography: {
|
|
580
|
+
lineHeightRatio: 1.5,
|
|
581
|
+
|
|
582
|
+
fontWeights: {
|
|
583
|
+
light: 300,
|
|
584
|
+
normal: 400,
|
|
585
|
+
medium: 500,
|
|
586
|
+
semibold: 600,
|
|
587
|
+
bold: 700,
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
fontSizes: [
|
|
591
|
+
{ name: "xs", value: "12px", lineHeight: 1.5 },
|
|
592
|
+
{ name: "sm", value: "14px", lineHeight: 1.5 },
|
|
593
|
+
{ name: "base", value: "16px", lineHeight: 1.6 },
|
|
594
|
+
{ name: "lg", value: "18px", lineHeight: 1.6 },
|
|
595
|
+
{ name: "xl", value: "20px", lineHeight: 1.6 },
|
|
596
|
+
{ name: "2xl", value: "24px", lineHeight: 1.4 },
|
|
597
|
+
{ name: "3xl", value: "30px", lineHeight: 1.4 },
|
|
598
|
+
{ name: "4xl", value: "36px", lineHeight: 1.3 },
|
|
599
|
+
{ name: "5xl", value: "48px", lineHeight: 1.15 },
|
|
600
|
+
{ name: "6xl", value: "60px", lineHeight: 1.1 },
|
|
601
|
+
{ name: "7xl", value: "72px", lineHeight: 1.05 },
|
|
602
|
+
{ name: "8xl", value: "96px", lineHeight: 1 },
|
|
603
|
+
{ name: "9xl", value: "128px", lineHeight: 1 },
|
|
604
|
+
],
|
|
605
|
+
},
|
|
606
|
+
|
|
607
|
+
shadows: {
|
|
608
|
+
sm: "0 1px 2px rgba(0, 0, 0, 0.05)",
|
|
609
|
+
base: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
610
|
+
md: "0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06)",
|
|
611
|
+
lg: "0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05)",
|
|
612
|
+
xl: "0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04)",
|
|
613
|
+
"2xl": "0 25px 50px rgba(0, 0, 0, 0.25)",
|
|
614
|
+
inner: "inset 0 2px 4px rgba(0, 0, 0, 0.06)",
|
|
615
|
+
none: "none",
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
transitions: {
|
|
619
|
+
fast: "100ms",
|
|
620
|
+
base: "200ms",
|
|
621
|
+
slow: "300ms",
|
|
622
|
+
timing: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
zIndex: {
|
|
626
|
+
auto: "auto",
|
|
627
|
+
0: "0",
|
|
628
|
+
10: "10",
|
|
629
|
+
20: "20",
|
|
630
|
+
30: "30",
|
|
631
|
+
40: "40",
|
|
632
|
+
50: "50",
|
|
633
|
+
dropdown: "1000",
|
|
634
|
+
sticky: "1020",
|
|
635
|
+
fixed: "1030",
|
|
636
|
+
modal: "1040",
|
|
637
|
+
popover: "1060",
|
|
638
|
+
tooltip: "1070",
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
opacity: [0, 5, 10, 25, 50, 75, 90, 95, 100],
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// ============================================================================
|
|
646
|
+
// INIT
|
|
647
|
+
// ============================================================================
|
|
648
|
+
|
|
649
|
+
async function init() {
|
|
650
|
+
console.log(
|
|
651
|
+
chalk.bold.magenta("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"),
|
|
652
|
+
);
|
|
653
|
+
console.log(chalk.bold.magenta(" EmilyUI Setup"));
|
|
654
|
+
console.log(
|
|
655
|
+
chalk.bold.magenta("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
try {
|
|
659
|
+
const spinner = ora("Analysing project structure...").start();
|
|
660
|
+
const detectedProject = detectProject();
|
|
661
|
+
spinner.succeed("Detected project: " + chalk.cyan(detectedProject.name));
|
|
662
|
+
const existingConfig = readExistingConfig();
|
|
663
|
+
const existingColours = isPlainObject(existingConfig && existingConfig.colours)
|
|
664
|
+
? existingConfig.colours
|
|
665
|
+
: {};
|
|
666
|
+
|
|
667
|
+
if (existingConfig) {
|
|
668
|
+
console.log(
|
|
669
|
+
chalk.gray(
|
|
670
|
+
" Found existing emily.config.json. Prompts are pre-filled from current settings.",
|
|
671
|
+
),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const packageJsonData = readPackageJson();
|
|
676
|
+
const pkgName =
|
|
677
|
+
existingConfig && typeof existingConfig.name === "string" && existingConfig.name.trim()
|
|
678
|
+
? existingConfig.name.trim()
|
|
679
|
+
: packageJsonData && packageJsonData.name
|
|
680
|
+
? titleCasePackageName(packageJsonData.name)
|
|
681
|
+
: "My Design System";
|
|
682
|
+
|
|
683
|
+
const projectName = await askValidatedInput({
|
|
684
|
+
promptName: "projectName",
|
|
660
685
|
message: "Project name",
|
|
661
686
|
initial: pkgName,
|
|
662
|
-
|
|
663
|
-
|
|
687
|
+
validator: function (value) {
|
|
688
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
689
|
+
return { valid: false, reason: "project name is required" };
|
|
690
|
+
}
|
|
691
|
+
return { valid: true };
|
|
664
692
|
},
|
|
665
|
-
})
|
|
666
|
-
|
|
667
|
-
if (!projectName || !projectName.trim()) {
|
|
668
|
-
console.log(chalk.red("\nProject name is required.\n"));
|
|
669
|
-
process.exit(1);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// =========================================================================
|
|
673
|
-
// COLOURS
|
|
674
|
-
// =========================================================================
|
|
675
|
-
|
|
676
|
-
console.log(chalk.bold("\n" + chalk.magenta("→") + " Brand colours"));
|
|
677
|
-
|
|
678
|
-
const brand = await askColourFromPresets(
|
|
679
|
-
"brand",
|
|
680
|
-
COLOUR_PRESETS.primary,
|
|
681
|
-
"#DB2777",
|
|
682
|
-
existingColours.brand,
|
|
683
|
-
);
|
|
684
|
-
const accent = await askColourFromPresets(
|
|
685
|
-
"accent",
|
|
686
|
-
COLOUR_PRESETS.secondary,
|
|
687
|
-
"#2563EB",
|
|
688
|
-
existingColours.accent,
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
console.log(
|
|
692
|
-
chalk.gray(
|
|
693
|
-
"\n Button colour tokens will use your brand colours by default:",
|
|
694
|
-
),
|
|
695
|
-
);
|
|
696
|
-
console.log(chalk.gray(" - btn-primary = brand"));
|
|
697
|
-
console.log(chalk.gray(" - btn-secondary = accent"));
|
|
698
|
-
|
|
699
|
-
console.log(chalk.bold("\n" + chalk.magenta("→") + " Utility colours"));
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
if (!projectName || !projectName.trim()) {
|
|
696
|
+
console.log(chalk.red("\nProject name is required.\n"));
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// =========================================================================
|
|
701
|
+
// COLOURS
|
|
702
|
+
// =========================================================================
|
|
703
|
+
|
|
704
|
+
console.log(chalk.bold("\n" + chalk.magenta("→") + " Brand colours"));
|
|
705
|
+
|
|
706
|
+
const brand = await askColourFromPresets(
|
|
707
|
+
"brand",
|
|
708
|
+
COLOUR_PRESETS.primary,
|
|
709
|
+
"#DB2777",
|
|
710
|
+
existingColours.brand,
|
|
711
|
+
);
|
|
712
|
+
const accent = await askColourFromPresets(
|
|
713
|
+
"accent",
|
|
714
|
+
COLOUR_PRESETS.secondary,
|
|
715
|
+
"#2563EB",
|
|
716
|
+
existingColours.accent,
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
console.log(
|
|
720
|
+
chalk.gray(
|
|
721
|
+
"\n Button colour tokens will use your brand colours by default:",
|
|
722
|
+
),
|
|
723
|
+
);
|
|
724
|
+
console.log(chalk.gray(" - btn-primary = brand"));
|
|
725
|
+
console.log(chalk.gray(" - btn-secondary = accent"));
|
|
726
|
+
|
|
727
|
+
console.log(chalk.bold("\n" + chalk.magenta("→") + " Utility colours"));
|
|
728
|
+
console.log(
|
|
729
|
+
chalk.gray(
|
|
730
|
+
" Defaults shown. Press enter to accept or pick an alternative.\n",
|
|
731
|
+
),
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
const success = await askColourFromPresets(
|
|
735
|
+
"success",
|
|
736
|
+
COLOUR_PRESETS.success,
|
|
737
|
+
"#017F65",
|
|
738
|
+
existingColours.success,
|
|
739
|
+
);
|
|
740
|
+
const warning = await askColourFromPresets(
|
|
741
|
+
"warning",
|
|
742
|
+
COLOUR_PRESETS.warning,
|
|
743
|
+
"#FFC107",
|
|
744
|
+
existingColours.warning,
|
|
745
|
+
);
|
|
746
|
+
const error = await askColourFromPresets(
|
|
747
|
+
"error",
|
|
748
|
+
COLOUR_PRESETS.error,
|
|
749
|
+
"#B20000",
|
|
750
|
+
existingColours.error,
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
const colours = {
|
|
754
|
+
brand,
|
|
755
|
+
accent,
|
|
756
|
+
"btn-primary": brand,
|
|
757
|
+
"btn-secondary": accent,
|
|
758
|
+
success,
|
|
759
|
+
warning,
|
|
760
|
+
error,
|
|
761
|
+
neutral: normaliseHex(existingColours.neutral) || "#57534E",
|
|
762
|
+
...getExistingAdditionalColours(existingColours),
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
let addingMore = true;
|
|
766
|
+
|
|
767
|
+
while (addingMore) {
|
|
768
|
+
const wantsMore = await new Confirm({
|
|
769
|
+
name: "addMore",
|
|
770
|
+
message: "Add another utility colour?",
|
|
771
|
+
initial: false,
|
|
772
|
+
}).run();
|
|
773
|
+
|
|
774
|
+
if (!wantsMore) {
|
|
775
|
+
addingMore = false;
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const customName = await new Input({
|
|
780
|
+
name: "customName",
|
|
781
|
+
message: "Colour name (e.g. accent, highlight, brand-dark)",
|
|
782
|
+
validate: function (value) {
|
|
783
|
+
const trimmed = value.trim();
|
|
784
|
+
|
|
785
|
+
if (!trimmed) return "Name is required";
|
|
786
|
+
if (!/^[a-z][a-z0-9-]*$/.test(trimmed)) {
|
|
787
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
788
|
+
}
|
|
789
|
+
if (colours[trimmed]) return '"' + trimmed + '" is already defined';
|
|
790
|
+
|
|
791
|
+
return true;
|
|
792
|
+
},
|
|
793
|
+
}).run();
|
|
794
|
+
|
|
795
|
+
colours[customName.trim()] = await askHex(
|
|
796
|
+
"hex-" + customName,
|
|
797
|
+
"Hex for " + customName,
|
|
798
|
+
"#000000",
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// =========================================================================
|
|
803
|
+
// TYPOGRAPHY
|
|
804
|
+
// =========================================================================
|
|
805
|
+
|
|
806
|
+
console.log(chalk.bold("\n" + chalk.magenta("→") + " Typography"));
|
|
807
|
+
|
|
700
808
|
console.log(
|
|
701
809
|
chalk.gray(
|
|
702
|
-
"
|
|
810
|
+
" Allowed font families: " + ALLOWED_FONT_FAMILIES.join(", "),
|
|
703
811
|
),
|
|
704
812
|
);
|
|
705
813
|
|
|
706
|
-
const
|
|
707
|
-
"
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
);
|
|
712
|
-
const warning = await askColourFromPresets(
|
|
713
|
-
"warning",
|
|
714
|
-
COLOUR_PRESETS.warning,
|
|
715
|
-
"#FFC107",
|
|
716
|
-
existingColours.warning,
|
|
717
|
-
);
|
|
718
|
-
const error = await askColourFromPresets(
|
|
719
|
-
"error",
|
|
720
|
-
COLOUR_PRESETS.error,
|
|
721
|
-
"#B20000",
|
|
722
|
-
existingColours.error,
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
const colours = {
|
|
726
|
-
brand,
|
|
727
|
-
accent,
|
|
728
|
-
"btn-primary": brand,
|
|
729
|
-
"btn-secondary": accent,
|
|
730
|
-
success,
|
|
731
|
-
warning,
|
|
732
|
-
error,
|
|
733
|
-
neutral: normaliseHex(existingColours.neutral) || "#57534E",
|
|
734
|
-
...getExistingAdditionalColours(existingColours),
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
let addingMore = true;
|
|
738
|
-
|
|
739
|
-
while (addingMore) {
|
|
740
|
-
const wantsMore = await new Confirm({
|
|
741
|
-
name: "addMore",
|
|
742
|
-
message: "Add another utility colour?",
|
|
743
|
-
initial: false,
|
|
744
|
-
}).run();
|
|
745
|
-
|
|
746
|
-
if (!wantsMore) {
|
|
747
|
-
addingMore = false;
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const customName = await new Input({
|
|
752
|
-
name: "customName",
|
|
753
|
-
message: "Colour name (e.g. accent, highlight, brand-dark)",
|
|
754
|
-
validate: function (value) {
|
|
755
|
-
const trimmed = value.trim();
|
|
756
|
-
|
|
757
|
-
if (!trimmed) return "Name is required";
|
|
758
|
-
if (!/^[a-z][a-z0-9-]*$/.test(trimmed)) {
|
|
759
|
-
return "Use lowercase letters, numbers, and hyphens only";
|
|
760
|
-
}
|
|
761
|
-
if (colours[trimmed]) return '"' + trimmed + '" is already defined';
|
|
762
|
-
|
|
763
|
-
return true;
|
|
764
|
-
},
|
|
765
|
-
}).run();
|
|
766
|
-
|
|
767
|
-
colours[customName.trim()] = await askHex(
|
|
768
|
-
"hex-" + customName,
|
|
769
|
-
"Hex for " + customName,
|
|
770
|
-
"#000000",
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// =========================================================================
|
|
775
|
-
// TYPOGRAPHY
|
|
776
|
-
// =========================================================================
|
|
777
|
-
|
|
778
|
-
console.log(chalk.bold("\n" + chalk.magenta("→") + " Typography"));
|
|
779
|
-
|
|
780
|
-
const headingFont = await new Select({
|
|
781
|
-
name: "headingFont",
|
|
782
|
-
message: "Heading font",
|
|
783
|
-
choices: FONT_OPTIONS,
|
|
784
|
-
initial: getFontInitialIndex(
|
|
785
|
-
isPlainObject(existingConfig && existingConfig.fontFamily)
|
|
814
|
+
const headingFont = await askValidatedInput({
|
|
815
|
+
promptName: "headingFont",
|
|
816
|
+
message: "Heading font family",
|
|
817
|
+
initial: (function () {
|
|
818
|
+
const existingHeading = isPlainObject(existingConfig && existingConfig.fontFamily)
|
|
786
819
|
? existingConfig.fontFamily.heading
|
|
787
|
-
: existingConfig && existingConfig.fontFamily
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
820
|
+
: existingConfig && existingConfig.fontFamily;
|
|
821
|
+
if (typeof existingHeading !== "string") return "lexend";
|
|
822
|
+
const candidate = existingHeading.trim().toLowerCase();
|
|
823
|
+
return validateFontFamily(candidate).valid ? candidate : "lexend";
|
|
824
|
+
})(),
|
|
825
|
+
validator: validateFontFamily,
|
|
826
|
+
normalise: function (value) {
|
|
827
|
+
return String(value).trim().toLowerCase();
|
|
828
|
+
},
|
|
829
|
+
});
|
|
791
830
|
|
|
792
|
-
const bodyFont = await
|
|
793
|
-
|
|
794
|
-
message: "Body font",
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
isPlainObject(existingConfig && existingConfig.fontFamily)
|
|
831
|
+
const bodyFont = await askValidatedInput({
|
|
832
|
+
promptName: "bodyFont",
|
|
833
|
+
message: "Body font family",
|
|
834
|
+
initial: (function () {
|
|
835
|
+
const existingBody = isPlainObject(existingConfig && existingConfig.fontFamily)
|
|
798
836
|
? existingConfig.fontFamily.body
|
|
799
|
-
: existingConfig && existingConfig.fontFamily
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const baseUnitRaw = await new Input({
|
|
809
|
-
name: "baseUnit",
|
|
810
|
-
message: "Base spacing unit in px (label/documentation only)",
|
|
811
|
-
initial: getBaseUnitInitial(existingConfig),
|
|
812
|
-
validate: function (value) {
|
|
813
|
-
const parsed = Number.parseInt(value, 10);
|
|
814
|
-
|
|
815
|
-
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
816
|
-
return "Must be a positive number.";
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
return true;
|
|
837
|
+
: existingConfig && existingConfig.fontFamily;
|
|
838
|
+
if (typeof existingBody !== "string") return "inter";
|
|
839
|
+
const candidate = existingBody.trim().toLowerCase();
|
|
840
|
+
return validateFontFamily(candidate).valid ? candidate : "inter";
|
|
841
|
+
})(),
|
|
842
|
+
validator: validateFontFamily,
|
|
843
|
+
normalise: function (value) {
|
|
844
|
+
return String(value).trim().toLowerCase();
|
|
820
845
|
},
|
|
821
|
-
}).run();
|
|
822
|
-
|
|
823
|
-
const baseUnit = Number.parseInt(baseUnitRaw, 10);
|
|
824
|
-
|
|
825
|
-
// =========================================================================
|
|
826
|
-
// PURGE / OUTPUT
|
|
827
|
-
// =========================================================================
|
|
828
|
-
|
|
829
|
-
console.log(chalk.bold("\n" + chalk.magenta("→") + " Project files"));
|
|
830
|
-
|
|
831
|
-
console.log(
|
|
832
|
-
chalk.gray(
|
|
833
|
-
" Detected " +
|
|
834
|
-
detectedProject.name +
|
|
835
|
-
". EmilyCSS will scan the recommended files automatically.",
|
|
836
|
-
),
|
|
837
|
-
);
|
|
838
|
-
|
|
839
|
-
detectedProject.sourceGlobs.forEach(function (glob) {
|
|
840
|
-
console.log(chalk.gray(" - " + glob));
|
|
841
846
|
});
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
847
|
+
|
|
848
|
+
const baseFontSize = await new Select({
|
|
849
|
+
name: "baseFontSize",
|
|
850
|
+
message: "Base font size (sets html font-size, scales all rem values)",
|
|
851
|
+
choices: ["14px", "16px", "18px", "20px"],
|
|
852
|
+
initial: (function () {
|
|
853
|
+
const existing = existingConfig && existingConfig.baseFontSize;
|
|
854
|
+
const idx = ["14px", "16px", "18px", "20px"].indexOf(existing);
|
|
855
|
+
return idx >= 0 ? idx : 1;
|
|
856
|
+
})(),
|
|
857
|
+
}).run();
|
|
858
|
+
|
|
859
|
+
// =========================================================================
|
|
860
|
+
// SPACING
|
|
861
|
+
// =========================================================================
|
|
862
|
+
|
|
863
|
+
const baseUnit = await askValidatedInput({
|
|
864
|
+
promptName: "baseUnit",
|
|
865
|
+
message: "Base spacing unit in px (label/documentation only) e.g. 1rem or 10px",
|
|
866
|
+
initial: getBaseUnitInitial(existingConfig),
|
|
867
|
+
validator: validateSpacingValue,
|
|
868
|
+
normalise: function (value) {
|
|
869
|
+
return String(value).trim().toLowerCase();
|
|
870
|
+
},
|
|
857
871
|
});
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
872
|
+
|
|
873
|
+
// =========================================================================
|
|
874
|
+
// PURGE / OUTPUT
|
|
875
|
+
// =========================================================================
|
|
876
|
+
|
|
877
|
+
console.log(chalk.bold("\n" + chalk.magenta("→") + " Project files"));
|
|
878
|
+
|
|
879
|
+
console.log(
|
|
880
|
+
chalk.gray(
|
|
881
|
+
" Detected " +
|
|
882
|
+
detectedProject.name +
|
|
883
|
+
". EmilyCSS will scan the recommended files automatically.",
|
|
884
|
+
),
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
detectedProject.sourceGlobs.forEach(function (glob) {
|
|
888
|
+
console.log(chalk.gray(" - " + glob));
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
console.log(chalk.bold("\n" + chalk.magenta("→") + " CSS output"));
|
|
892
|
+
console.log(chalk.gray(" Output: " + detectedProject.outputPath));
|
|
893
|
+
|
|
894
|
+
// =========================================================================
|
|
895
|
+
// BUILD
|
|
896
|
+
// =========================================================================
|
|
897
|
+
|
|
898
|
+
const generatedDefaults = createDefaultConfig({
|
|
899
|
+
name: projectName.trim(),
|
|
900
|
+
colours,
|
|
901
|
+
headingFont,
|
|
902
|
+
bodyFont,
|
|
903
|
+
baseUnit,
|
|
904
|
+
baseFontSize,
|
|
905
|
+
detectedProject,
|
|
906
|
+
});
|
|
907
|
+
const config = mergeWithDefaults(generatedDefaults, existingConfig);
|
|
908
|
+
config.name = projectName.trim();
|
|
909
|
+
|
|
910
|
+
if (!existingConfig || !existingConfig.description) {
|
|
911
|
+
config.description = config.name + " design system";
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
config.baseUnit = baseUnit;
|
|
915
|
+
config.baseFontSize = baseFontSize || "16px";
|
|
866
916
|
config.fontFamily = {
|
|
867
917
|
heading: headingFont,
|
|
868
918
|
body: bodyFont,
|
|
869
919
|
};
|
|
870
920
|
config.colours = colours;
|
|
871
921
|
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const build = crossSpawn("npx", ["emily-css", "build"], {
|
|
880
|
-
cwd: process.cwd(),
|
|
881
|
-
stdio: "pipe",
|
|
882
|
-
shell: process.platform === "win32",
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
let stderr = "";
|
|
886
|
-
|
|
887
|
-
build.stderr.on("data", function (data) {
|
|
888
|
-
stderr += data.toString();
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
build.on("close", async function (code) {
|
|
892
|
-
if (code === 0) {
|
|
893
|
-
buildSpinner.succeed("EmilyUI CSS built successfully.");
|
|
894
|
-
|
|
895
|
-
const scriptsAdded = addEmilyScriptsToPackageJson();
|
|
896
|
-
|
|
897
|
-
console.log(
|
|
898
|
-
"\n" +
|
|
899
|
-
boxen(
|
|
900
|
-
chalk.green.bold("Setup complete") +
|
|
901
|
-
"\n\nConfig: " +
|
|
902
|
-
chalk.cyan("emily.config.json") +
|
|
903
|
-
"\nOutput: " +
|
|
904
|
-
chalk.cyan(config.output.css) +
|
|
905
|
-
"\nProject: " +
|
|
906
|
-
chalk.cyan(detectedProject.name) +
|
|
907
|
-
"\nScan:\n " +
|
|
908
|
-
chalk.cyan(config.purge.sourceGlobs.join("\n ")) +
|
|
909
|
-
"\n\nNext: add this stylesheet to your project:" +
|
|
910
|
-
"\n" +
|
|
911
|
-
chalk.yellow(" " + detectedProject.linkHint) +
|
|
912
|
-
(scriptsAdded
|
|
913
|
-
? "\n\nScripts added:\n" +
|
|
914
|
-
chalk.cyan(" npm run emily:build\n") +
|
|
915
|
-
chalk.cyan(" npm run emily:watch\n") +
|
|
916
|
-
chalk.cyan(" npm run emily:doctor\n") +
|
|
917
|
-
chalk.cyan(" npm run emily:migrate\n") +
|
|
918
|
-
chalk.cyan(" npm run emily:info\n") +
|
|
919
|
-
chalk.cyan(" npm run emily:manifest\n") +
|
|
920
|
-
chalk.cyan(" npm run emily:version\n") +
|
|
921
|
-
chalk.cyan(" npm run emily:showcase\n") +
|
|
922
|
-
chalk.cyan(" npm run emily:help")
|
|
923
|
-
: ""),
|
|
924
|
-
{
|
|
925
|
-
padding: 1,
|
|
926
|
-
margin: 1,
|
|
927
|
-
borderStyle: "round",
|
|
928
|
-
borderColor: "magenta",
|
|
929
|
-
},
|
|
930
|
-
),
|
|
931
|
-
);
|
|
932
|
-
|
|
933
|
-
const startWatch = await new Confirm({
|
|
934
|
-
name: "startWatch",
|
|
935
|
-
message: "Start the file watcher now?",
|
|
936
|
-
initial: true,
|
|
937
|
-
}).run();
|
|
938
|
-
|
|
939
|
-
if (startWatch) {
|
|
940
|
-
console.log(
|
|
941
|
-
chalk.cyan("\nStarting watcher. Press Ctrl+C to stop.\n"),
|
|
942
|
-
);
|
|
943
|
-
|
|
944
|
-
const watcher = crossSpawn("npx", ["emily-css", "watch"], {
|
|
945
|
-
cwd: process.cwd(),
|
|
946
|
-
stdio: "inherit",
|
|
947
|
-
shell: process.platform === "win32",
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
watcher.on("close", function (c) {
|
|
951
|
-
process.exit(c || 0);
|
|
952
|
-
});
|
|
953
|
-
} else {
|
|
954
|
-
console.log(
|
|
955
|
-
chalk.gray(
|
|
956
|
-
"\nRun the watcher any time with: npm run emily:watch\n",
|
|
957
|
-
),
|
|
958
|
-
);
|
|
959
|
-
process.exit(0);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
buildSpinner.fail("Automatic build failed.");
|
|
966
|
-
console.log("\nYour config was created, but CSS was not built.");
|
|
967
|
-
console.log("\nRun manually:\n");
|
|
968
|
-
console.log(chalk.cyan(" npx emily-css build"));
|
|
969
|
-
|
|
970
|
-
if (stderr.trim()) {
|
|
971
|
-
console.log(chalk.gray("\nBuild error:\n"));
|
|
972
|
-
console.log(stderr.trim());
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
process.exit(1);
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
build.on("error", function (error) {
|
|
979
|
-
buildSpinner.fail("Automatic build failed.");
|
|
980
|
-
console.log("\nYour config was created, but CSS was not built.");
|
|
981
|
-
console.log("Reason: " + error.message);
|
|
982
|
-
console.log("\nRun manually:\n");
|
|
983
|
-
console.log(chalk.cyan(" npx emily-css build\n"));
|
|
922
|
+
const finalValidation = validateConfigShape(config);
|
|
923
|
+
if (!finalValidation.valid) {
|
|
924
|
+
console.log(chalk.red("\n✗ Config validation failed. emily.config.json was not written.\n"));
|
|
925
|
+
finalValidation.errors.forEach(function (error) {
|
|
926
|
+
console.log(chalk.red(" - " + error));
|
|
927
|
+
});
|
|
984
928
|
process.exit(1);
|
|
985
|
-
});
|
|
986
|
-
} catch (error) {
|
|
987
|
-
console.log(chalk.red("\nSetup cancelled or failed."));
|
|
988
|
-
|
|
989
|
-
if (error && error.message) {
|
|
990
|
-
console.log(chalk.gray(error.message));
|
|
991
929
|
}
|
|
992
930
|
|
|
993
|
-
process.
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
931
|
+
const configPath = path.join(process.cwd(), "emily.config.json");
|
|
932
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
933
|
+
|
|
934
|
+
console.log("");
|
|
935
|
+
|
|
936
|
+
const buildSpinner = ora("Building EmilyUI CSS...").start();
|
|
937
|
+
|
|
938
|
+
const build = crossSpawn("npx", ["emily-css", "build"], {
|
|
939
|
+
cwd: process.cwd(),
|
|
940
|
+
stdio: "pipe",
|
|
941
|
+
shell: process.platform === "win32",
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
let stderr = "";
|
|
945
|
+
|
|
946
|
+
build.stderr.on("data", function (data) {
|
|
947
|
+
stderr += data.toString();
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
build.on("close", async function (code) {
|
|
951
|
+
if (code === 0) {
|
|
952
|
+
buildSpinner.succeed("EmilyUI CSS built successfully.");
|
|
953
|
+
|
|
954
|
+
const scriptsAdded = addEmilyScriptsToPackageJson();
|
|
955
|
+
|
|
956
|
+
console.log(
|
|
957
|
+
"\n" +
|
|
958
|
+
boxen(
|
|
959
|
+
chalk.green.bold("Setup complete") +
|
|
960
|
+
"\n\nConfig: " +
|
|
961
|
+
chalk.cyan("emily.config.json") +
|
|
962
|
+
"\nOutput: " +
|
|
963
|
+
chalk.cyan(config.output.css) +
|
|
964
|
+
"\nProject: " +
|
|
965
|
+
chalk.cyan(detectedProject.name) +
|
|
966
|
+
"\nScan:\n " +
|
|
967
|
+
chalk.cyan(config.purge.sourceGlobs.join("\n ")) +
|
|
968
|
+
"\n\nNext: add this stylesheet to your project:" +
|
|
969
|
+
"\n" +
|
|
970
|
+
chalk.yellow(" " + detectedProject.linkHint) +
|
|
971
|
+
(scriptsAdded
|
|
972
|
+
? "\n\nScripts added:\n" +
|
|
973
|
+
chalk.cyan(" npm run emily:build\n") +
|
|
974
|
+
chalk.cyan(" npm run emily:watch\n") +
|
|
975
|
+
chalk.cyan(" npm run emily:doctor\n") +
|
|
976
|
+
chalk.cyan(" npm run emily:migrate\n") +
|
|
977
|
+
chalk.cyan(" npm run emily:info\n") +
|
|
978
|
+
chalk.cyan(" npm run emily:manifest\n") +
|
|
979
|
+
chalk.cyan(" npm run emily:version\n") +
|
|
980
|
+
chalk.cyan(" npm run emily:showcase\n") +
|
|
981
|
+
chalk.cyan(" npm run emily:help")
|
|
982
|
+
: ""),
|
|
983
|
+
{
|
|
984
|
+
padding: 1,
|
|
985
|
+
margin: 1,
|
|
986
|
+
borderStyle: "round",
|
|
987
|
+
borderColor: "magenta",
|
|
988
|
+
},
|
|
989
|
+
),
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
const startWatch = await new Confirm({
|
|
993
|
+
name: "startWatch",
|
|
994
|
+
message: "Start the file watcher now?",
|
|
995
|
+
initial: true,
|
|
996
|
+
}).run();
|
|
997
|
+
|
|
998
|
+
if (startWatch) {
|
|
999
|
+
console.log(
|
|
1000
|
+
chalk.cyan("\nStarting watcher. Press Ctrl+C to stop.\n"),
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
const watcher = crossSpawn("npx", ["emily-css", "watch"], {
|
|
1004
|
+
cwd: process.cwd(),
|
|
1005
|
+
stdio: "inherit",
|
|
1006
|
+
shell: process.platform === "win32",
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
watcher.on("close", function (c) {
|
|
1010
|
+
process.exit(c || 0);
|
|
1011
|
+
});
|
|
1012
|
+
} else {
|
|
1013
|
+
console.log(
|
|
1014
|
+
chalk.gray(
|
|
1015
|
+
"\nRun the watcher any time with: npm run emily:watch\n",
|
|
1016
|
+
),
|
|
1017
|
+
);
|
|
1018
|
+
process.exit(0);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
buildSpinner.fail("Automatic build failed.");
|
|
1025
|
+
console.log("\nYour config was created, but CSS was not built.");
|
|
1026
|
+
console.log("\nRun manually:\n");
|
|
1027
|
+
console.log(chalk.cyan(" npx emily-css build"));
|
|
1028
|
+
|
|
1029
|
+
if (stderr.trim()) {
|
|
1030
|
+
console.log(chalk.gray("\nBuild error:\n"));
|
|
1031
|
+
console.log(stderr.trim());
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
build.on("error", function (error) {
|
|
1038
|
+
buildSpinner.fail("Automatic build failed.");
|
|
1039
|
+
console.log("\nYour config was created, but CSS was not built.");
|
|
1040
|
+
console.log("Reason: " + error.message);
|
|
1041
|
+
console.log("\nRun manually:\n");
|
|
1042
|
+
console.log(chalk.cyan(" npx emily-css build\n"));
|
|
1043
|
+
process.exit(1);
|
|
1044
|
+
});
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
console.log(chalk.red("\nSetup cancelled or failed."));
|
|
1047
|
+
|
|
1048
|
+
if (error && error.message) {
|
|
1049
|
+
console.log(chalk.gray(error.message));
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
init();
|