@spicemod/creator 0.0.34 → 0.0.36

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/dist/bin.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Command, Option } from "commander";
3
3
  import * as v from "valibot";
4
4
  import path, { basename, dirname, extname, join, relative, resolve } from "node:path";
5
- import { build, context, transform } from "esbuild";
5
+ import { build, context, formatMessages, transform } from "esbuild";
6
6
  import fs, { createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
7
7
  import { watchConfig } from "c12";
8
8
  import { globSync } from "tinyglobby";
@@ -28,6 +28,7 @@ import { lookup } from "node:dns/promises";
28
28
  import { mkdir, writeFile as writeFile$1 } from "fs/promises";
29
29
  import { dirname as dirname$1, join as join$1 } from "path";
30
30
  import { createServer } from "node:http";
31
+ import { createServer as createServer$1 } from "node:net";
31
32
  import { WebSocket, WebSocketServer } from "ws";
32
33
 
33
34
  //#region src/utils/replace.ts
@@ -145,7 +146,7 @@ const injectCSSFilePath = dist("templates/injectCSS.js", import.meta.url);
145
146
  //#endregion
146
147
  //#region package.json
147
148
  var name = "@spicemod/creator";
148
- var version = "0.0.34";
149
+ var version = "0.0.36";
149
150
 
150
151
  //#endregion
151
152
  //#region src/utils/common.ts
@@ -762,6 +763,59 @@ function formatBuildSummary(files) {
762
763
  }
763
764
  }
764
765
 
766
+ //#endregion
767
+ //#region src/esbuild/plugins/buildErrorReporter.ts
768
+ function formatErrorForFrontend(errors) {
769
+ return errors.map((err) => {
770
+ return {
771
+ id: err.id,
772
+ text: err.text,
773
+ location: err.location ? {
774
+ ...err.location,
775
+ lineText: err.location.lineText
776
+ } : null,
777
+ notes: err.notes,
778
+ pluginName: err.pluginName,
779
+ detail: err.detail
780
+ };
781
+ });
782
+ }
783
+ async function formatErrorForConsole(errors) {
784
+ return (await formatMessages(errors, {
785
+ kind: "error",
786
+ color: true
787
+ })).join("\n");
788
+ }
789
+ function buildErrorReporter({ server }) {
790
+ return {
791
+ name: "spice_internal__build-error-reporter",
792
+ setup(build) {
793
+ build.onEnd(async (result) => {
794
+ const errors = result.errors;
795
+ const warnings = result.warnings;
796
+ if (errors.length === 0) {
797
+ server?.broadcast({
798
+ type: "build-success",
799
+ errors: [],
800
+ warnings
801
+ });
802
+ return;
803
+ }
804
+ const formattedErrors = formatErrorForFrontend(errors);
805
+ logger$2.debug(pc.cyan("[buildErrorReporter] Broadcasting error to WebSocket clients"));
806
+ server?.broadcast({
807
+ type: "build-error",
808
+ errors: formattedErrors,
809
+ warnings
810
+ });
811
+ });
812
+ }
813
+ };
814
+ }
815
+ async function getFormattedErrors(errors) {
816
+ return formatErrorForConsole(errors);
817
+ }
818
+
765
819
  //#endregion
766
820
  //#region src/esbuild/plugins/buildLogger.ts
767
821
  const buildLogger = ({ cache }) => ({
@@ -776,17 +830,22 @@ const buildLogger = ({ cache }) => ({
776
830
  logger$2.info(pc.dim("Rebuilding..."));
777
831
  } else logger$2.info(pc.dim("Build started..."));
778
832
  });
779
- build.onEnd((result) => {
833
+ build.onEnd(async (result) => {
780
834
  if (result.errors.length > 0) {
781
- logger$2.info(pc.red("build failed."));
835
+ const formattedErrors = await getFormattedErrors(result.errors);
836
+ logger$2.error(pc.red("Build failed.\n") + formattedErrors);
782
837
  return;
783
838
  }
784
- const moduleCount = result.metafile ? Object.keys(result.metafile.inputs).length : 0;
839
+ let moduleCount = 0;
840
+ try {
841
+ if (result.metafile) {
842
+ result.metafile.outputs;
843
+ moduleCount = Object.keys(result.metafile.inputs).length;
844
+ const details = formatBuildSummary(cache.files);
845
+ logger$2.info(details);
846
+ }
847
+ } catch {}
785
848
  logger$2.info(`${CHECK} ${moduleCount} modules transformed.`);
786
- if (result.metafile) {
787
- const details = formatBuildSummary(cache.files);
788
- logger$2.info(details);
789
- }
790
849
  logger$2.info(pc.green(`${CHECK} built in ${getTime(buildStartTime)}.`));
791
850
  isFirstBuild = false;
792
851
  });
@@ -797,33 +856,6 @@ function getTime(start) {
797
856
  return ms > 1e3 ? `${(ms / 1e3).toFixed(2)}s` : `${Math.round(ms)}ms`;
798
857
  }
799
858
 
800
- //#endregion
801
- //#region src/esbuild/plugins/buildErrorReporter.ts
802
- function buildErrorReporter({ server }) {
803
- return {
804
- name: "spice_internal__build-error-reporter",
805
- setup(build) {
806
- build.onEnd(async (result) => {
807
- const errors = result.errors;
808
- const warnings = result.warnings;
809
- if (errors.length === 0) {
810
- server?.broadcast({
811
- type: "build-success",
812
- errors: [],
813
- warnings
814
- });
815
- return;
816
- }
817
- server?.broadcast({
818
- type: "build-error",
819
- errors,
820
- warnings
821
- });
822
- });
823
- }
824
- };
825
- }
826
-
827
859
  //#endregion
828
860
  //#region src/esbuild/plugins/clean.ts
829
861
  const clean = (cache, logger = createLogger("plugin:clean")) => ({
@@ -1467,7 +1499,8 @@ function createPackageJSON(options) {
1467
1499
  sc: "spicetify-creator",
1468
1500
  dev: "spicetify-creator dev",
1469
1501
  build: "spicetify-creator build",
1470
- "update-types": "spicetify-creator update-types"
1502
+ "update-types": "spicetify-creator update-types",
1503
+ "clean-spice": "spicetify-creator clean-spicetify --all"
1471
1504
  },
1472
1505
  dependencies: {},
1473
1506
  devDependencies: { "@spicetify/creator": "latest" }
@@ -2020,7 +2053,9 @@ const root = () => `<!DOCTYPE html>
2020
2053
 
2021
2054
  //#endregion
2022
2055
  //#region src/dev/server/index.ts
2023
- const WS_PATH = "/spicetify-creator";
2056
+ function getWSPath(port) {
2057
+ return `/spicetify-creator-${port}`;
2058
+ }
2024
2059
  async function createHmrServer(config, logger = createLogger("hmrServer")) {
2025
2060
  const { port = DEFAULT_PORT, serveDir = outDir } = config;
2026
2061
  let isRunning = false;
@@ -2036,6 +2071,30 @@ async function createHmrServer(config, logger = createLogger("hmrServer")) {
2036
2071
  ".svg": "image/svg+xml",
2037
2072
  ".ico": "image/x-icon"
2038
2073
  };
2074
+ async function isPortAvailable(port) {
2075
+ return new Promise((resolve) => {
2076
+ const server = createServer$1();
2077
+ server.once("error", () => resolve(false));
2078
+ server.once("listening", () => {
2079
+ server.close(() => resolve(true));
2080
+ });
2081
+ server.listen(port);
2082
+ });
2083
+ }
2084
+ let actualPort = port;
2085
+ const portsToCheck = Array.from({ length: 10 }, (_, i) => port + i);
2086
+ const results = await Promise.all(portsToCheck.map(async (p) => ({
2087
+ port: p,
2088
+ available: await isPortAvailable(p)
2089
+ })));
2090
+ for (const result of results) {
2091
+ if (result.available) {
2092
+ actualPort = result.port;
2093
+ break;
2094
+ }
2095
+ logger.debug(`Port ${result.port} is in use, trying ${result.port + 1}...`);
2096
+ }
2097
+ if (actualPort !== port) logger.info(pc.yellow(`Port ${port} is in use, using port ${actualPort} instead`));
2039
2098
  const httpServer = createServer((req, res) => {
2040
2099
  const corsHeaders = {
2041
2100
  "Access-Control-Allow-Origin": "*",
@@ -2076,7 +2135,7 @@ async function createHmrServer(config, logger = createLogger("hmrServer")) {
2076
2135
  const clients = /* @__PURE__ */ new Set();
2077
2136
  httpServer.on("upgrade", (req, socket, head) => {
2078
2137
  const { url } = req;
2079
- if (!url?.startsWith(WS_PATH)) {
2138
+ if (!url?.startsWith(getWSPath(actualPort))) {
2080
2139
  socket.destroy();
2081
2140
  return;
2082
2141
  }
@@ -2097,8 +2156,8 @@ async function createHmrServer(config, logger = createLogger("hmrServer")) {
2097
2156
  }
2098
2157
  return {
2099
2158
  start: async () => new Promise((resolve) => {
2100
- httpServer.listen(port, () => {
2101
- logger.debug(`${pc.bold("HTTP Server Started at")}: ${pc.cyan(`http://localhost:${port}/`)}`);
2159
+ httpServer.listen(actualPort, () => {
2160
+ logger.debug(`${pc.bold("HTTP Server Started at")}: ${pc.cyan(`http://localhost:${actualPort}/`)}`);
2102
2161
  isRunning = true;
2103
2162
  resolve();
2104
2163
  });
@@ -2120,24 +2179,24 @@ async function createHmrServer(config, logger = createLogger("hmrServer")) {
2120
2179
  }),
2121
2180
  broadcast,
2122
2181
  get port() {
2123
- return port;
2182
+ return actualPort;
2124
2183
  },
2125
2184
  get isRunning() {
2126
2185
  return isRunning;
2127
2186
  },
2128
2187
  get link() {
2129
- return `http://localhost:${port}`;
2188
+ return `http://localhost:${actualPort}`;
2130
2189
  },
2131
2190
  get wsLink() {
2132
- return `ws://localhost:${port}${WS_PATH}`;
2191
+ return `ws://localhost:${actualPort}${getWSPath(actualPort)}`;
2133
2192
  }
2134
2193
  };
2135
2194
  }
2136
2195
 
2137
2196
  //#endregion
2138
2197
  //#region src/utils/hmr.ts
2139
- const injectHMRExtension = async (rootLink, wsLink, outFiles, config) => {
2140
- const extName = `sc-live-reload-helper.js`;
2198
+ const injectHMRExtension = async (rootLink, wsLink, outFiles, config, port) => {
2199
+ const extName = port === 54321 ? `sc-live-reload-helper.js` : `sc-live-reload-helper-${port}.js`;
2141
2200
  const spiceConfig = await getSpicetifyConfig();
2142
2201
  const cleanup = () => {
2143
2202
  logger$2.info(`Removing Live reload extension...`);
@@ -2168,7 +2227,7 @@ const injectHMRExtension = async (rootLink, wsLink, outFiles, config) => {
2168
2227
  _HOT_RELOAD_LINK: JSON.stringify(wsLink),
2169
2228
  _JS_PATH: JSON.stringify(`/files/${outFiles.js}`),
2170
2229
  _CSS_PATH: JSON.stringify(outFiles.css ? `/files/${outFiles.css}` : `/files/app.css`),
2171
- _REMOVE_CMD: JSON.stringify(`spicetify config extensions sc-live-reload-helper.js- && spicetify apply`),
2230
+ _REMOVE_CMD: JSON.stringify(`spicetify config extensions ${extName}- && spicetify apply`),
2172
2231
  _CSS_ID: JSON.stringify(config.template === "extension" && config.cssId ? config.cssId : "sc-injected-css")
2173
2232
  }
2174
2233
  });
@@ -2186,19 +2245,20 @@ const injectHMRExtension = async (rootLink, wsLink, outFiles, config) => {
2186
2245
  logger$2.error(pc.red(`${CROSS} Failed to inject HMR helper: ${err instanceof Error ? err.message : String(err)}`));
2187
2246
  }
2188
2247
  };
2189
- const injectHMRCustomApp = async (rootLink, wsLink, outFiles, config) => {
2248
+ const injectHMRCustomApp = async (rootLink, wsLink, outFiles, config, port) => {
2190
2249
  if (config.template !== "custom-app") throw new Error("only supports custom-app templates");
2191
2250
  const identifier = getEnName(config.name);
2192
2251
  const spiceConfig = await getSpicetifyConfig();
2193
- const customAppId = urlSlugify(identifier);
2194
- runSpice([
2195
- "config",
2196
- "extensions",
2197
- "sc-live-reload-helper.js-"
2198
- ]);
2252
+ const customAppId = port === 54321 ? urlSlugify(identifier) : `${urlSlugify(identifier)}-${port}`;
2253
+ const extName = port === 54321 ? `sc-live-reload-helper.js` : `sc-live-reload-helper-${port}.js`;
2199
2254
  const cleanup = () => {
2200
2255
  logger$2.info(`Removing Live reload custom-app...`);
2201
2256
  try {
2257
+ runSpice([
2258
+ "config",
2259
+ "extensions",
2260
+ `${extName}-`
2261
+ ]);
2202
2262
  runSpice([
2203
2263
  "config",
2204
2264
  "custom_apps",
@@ -2285,8 +2345,8 @@ async function dev$1(options) {
2285
2345
  });
2286
2346
  await server.start();
2287
2347
  const outFiles = getOutFiles(config, true);
2288
- if (config.template === "custom-app") await injectHMRCustomApp(server.link, server.wsLink, outFiles, config);
2289
- else await injectHMRExtension(server.link, server.wsLink, outFiles, config);
2348
+ if (config.template === "custom-app") await injectHMRCustomApp(server.link, server.wsLink, outFiles, config, server.port);
2349
+ else await injectHMRExtension(server.link, server.wsLink, outFiles, config, server.port);
2290
2350
  ctx = await context(getJSDevOptions(config, {
2291
2351
  ...options,
2292
2352
  outFiles,
@@ -2350,6 +2410,59 @@ const dev = new Command("dev").description("Develop your spicetify project").opt
2350
2410
  await dev$1(safeParse(CLIOptionsSchema, opts));
2351
2411
  });
2352
2412
 
2413
+ //#endregion
2414
+ //#region src/commands/clean-spicetify.ts
2415
+ function getHmrExtensions(port) {
2416
+ return port === 54321 ? "sc-live-reload-helper.js" : `sc-live-reload-helper-${port}.js`;
2417
+ }
2418
+ function getHmrCustomAppId(name, port) {
2419
+ const slugified = urlSlugify(getEnName(name));
2420
+ return port === 54321 ? slugified : `${slugified}-${port}`;
2421
+ }
2422
+ const cleanSpicetify = new Command("clean-spicetify").description("Remove HMR extensions and custom apps added by Spicetify Creator from Spicetify").option("-a, --all", "Clean all possible HMR artifacts").action(async (opts) => {
2423
+ const all = opts.all ?? false;
2424
+ try {
2425
+ const spiceConfig = await getSpicetifyConfig();
2426
+ const extensions = new Set(spiceConfig.AdditionalOptions.extensions);
2427
+ const customApps = new Set(spiceConfig.AdditionalOptions.custom_apps.split("|").filter(Boolean));
2428
+ const toRemove = [];
2429
+ if (all) for (let port = 54321; port <= 54330; port++) {
2430
+ const extName = getHmrExtensions(port);
2431
+ if (extensions.has(extName)) toRemove.push(extName);
2432
+ }
2433
+ else await (await loadConfig(async (config) => {
2434
+ const serverPort = config.serverConfig?.port ?? 54321;
2435
+ const extName = getHmrExtensions(serverPort);
2436
+ if (extensions.has(extName)) toRemove.push(extName);
2437
+ if (config.template === "custom-app") {
2438
+ const customAppId = getHmrCustomAppId(getEnName(config.name), serverPort);
2439
+ if (customApps.has(customAppId)) toRemove.push(customAppId);
2440
+ }
2441
+ })).unwatch();
2442
+ if (toRemove.length === 0) {
2443
+ console.log(pc.blue("No HMR artifacts found to clean."));
2444
+ return;
2445
+ }
2446
+ console.log(pc.yellow(`Removing: ${toRemove.join(", ")}...`));
2447
+ for (const item of toRemove) if (extensions.has(item)) runSpice([
2448
+ "config",
2449
+ "extensions",
2450
+ `${item}-`
2451
+ ]);
2452
+ else runSpice([
2453
+ "config",
2454
+ "custom_apps",
2455
+ `${item}-`
2456
+ ]);
2457
+ runSpice(["apply"]);
2458
+ console.log(pc.green(`${CHECK} Cleanup successful.`));
2459
+ process.exit(0);
2460
+ } catch (e) {
2461
+ console.error(pc.red(`${CROSS} Cleanup failed: `), e);
2462
+ process.exit(1);
2463
+ }
2464
+ });
2465
+
2353
2466
  //#endregion
2354
2467
  //#region src/commands/update-types.ts
2355
2468
  const update_types = new Command("update-types").description("Update Spicetify Types").action(async () => {
@@ -2360,7 +2473,7 @@ const update_types = new Command("update-types").description("Update Spicetify T
2360
2473
  //#region src/bin.ts
2361
2474
  logger$2.debug(`Env: ${JSON.stringify(env, null, 2)}\n`);
2362
2475
  const command = new Command();
2363
- command.version(version).addCommand(create.alias("init")).addCommand(build$1).addCommand(dev).addCommand(update_types);
2476
+ command.version(version).addCommand(create.alias("init")).addCommand(build$1).addCommand(dev).addCommand(cleanSpicetify.alias("clean-spice")).addCommand(update_types);
2364
2477
  command.parse();
2365
2478
 
2366
2479
  //#endregion
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }
@@ -17,6 +17,84 @@
17
17
  warning: `⚠️`,
18
18
  };
19
19
 
20
+ const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
21
+
22
+ function stripAnsi(str) {
23
+ if (typeof str !== "string") return str;
24
+ return str.replace(ANSI_REGEX, "");
25
+ }
26
+
27
+ function parseAnsiColors(str) {
28
+ if (typeof str !== "string") return str;
29
+
30
+ const ansiMap = {
31
+ "1": "font-weight: bold",
32
+ "3": "font-style: italic",
33
+ "4": "text-decoration: underline",
34
+ "30": "color: #0",
35
+ "31": "color: #e91429",
36
+ "32": "color: #1ed760",
37
+ "33": "color: #f59b23",
38
+ "34": "color: #3b82f6",
39
+ "35": "color: #a855f7",
40
+ "36": "color: #06b6d4",
41
+ "37": "color: #fff",
42
+ "90": "color: #6b7280",
43
+ "91": "color: #f87171",
44
+ "92": "color: #4ade80",
45
+ "93": "color: #facc15",
46
+ "94": "color: #60a5fa",
47
+ "95": "color: #c084fc",
48
+ "96": "color: #22d3ee",
49
+ "97": "color: #f3f4f6",
50
+ };
51
+
52
+ let result = "";
53
+ let currentStyle = "";
54
+ let lastIndex = 0;
55
+
56
+ const regex = /\x1b\[([0-9;]*)m/g;
57
+ let match;
58
+
59
+ while ((match = regex.exec(str)) !== null) {
60
+ result += str.slice(lastIndex, match.index);
61
+ const code = match[1];
62
+
63
+ if (code === "0" || code === "") {
64
+ if (currentStyle) {
65
+ result += `</span>`;
66
+ currentStyle = "";
67
+ }
68
+ } else {
69
+ const codes = code.split(";").map(c => parseInt(c, 10));
70
+ const styles = [];
71
+ for (const c of codes) {
72
+ if (c >= 30 && c <= 37 || c >= 90 && c <= 97) {
73
+ const style = ansiMap[String(c)];
74
+ if (style) styles.push(style);
75
+ } else if (c === 1) {
76
+ styles.push("font-weight: bold");
77
+ } else if (c === 3) {
78
+ styles.push("font-style: italic");
79
+ } else if (c === 4) {
80
+ styles.push("text-decoration: underline");
81
+ }
82
+ }
83
+ if (styles.length > 0) {
84
+ if (currentStyle) result += `</span>`;
85
+ currentStyle = styles.join("; ");
86
+ result += `<span style="${currentStyle}">`;
87
+ }
88
+ }
89
+ lastIndex = match.index + match[0].length;
90
+ }
91
+
92
+ result += str.slice(lastIndex);
93
+ if (currentStyle) result += `</span>`;
94
+
95
+ return result || stripAnsi(str);
96
+ }
97
+
20
98
  class SpicetifyDevTools {
21
99
  constructor() {
22
100
  this.state = {
@@ -179,7 +257,8 @@
179
257
  const fragment = document.createDocumentFragment();
180
258
 
181
259
  errors.forEach((err, index) => {
182
- const msg = err.text || err.message || String(err);
260
+ const rawMsg = err.text || err.message || String(err);
261
+ const msg = parseAnsiColors(rawMsg);
183
262
  const item = document.createElement("div");
184
263
  item.className = "sc-err-item";
185
264
 
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spicemod/creator",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "Easily make Spicetify extensions and themes",
5
5
  "keywords": [
6
6
  "cli",
@@ -75,10 +75,10 @@
75
75
  "@types/ws": "^8.18.1",
76
76
  "jiti": "^2.6.1",
77
77
  "oxfmt": "^0.28.0",
78
- "oxlint": "^1.56.0",
78
+ "oxlint": "^1.57.0",
79
79
  "tsdown": "^0.20.3",
80
80
  "typescript": "^5.9.3",
81
- "vitest": "^4.1.0"
81
+ "vitest": "^4.1.2"
82
82
  },
83
83
  "packageManager": "bun@1.3.4"
84
84
  }
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }
@@ -17,6 +17,84 @@
17
17
  warning: `⚠️`,
18
18
  };
19
19
 
20
+ const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
21
+
22
+ function stripAnsi(str) {
23
+ if (typeof str !== "string") return str;
24
+ return str.replace(ANSI_REGEX, "");
25
+ }
26
+
27
+ function parseAnsiColors(str) {
28
+ if (typeof str !== "string") return str;
29
+
30
+ const ansiMap = {
31
+ "1": "font-weight: bold",
32
+ "3": "font-style: italic",
33
+ "4": "text-decoration: underline",
34
+ "30": "color: #0",
35
+ "31": "color: #e91429",
36
+ "32": "color: #1ed760",
37
+ "33": "color: #f59b23",
38
+ "34": "color: #3b82f6",
39
+ "35": "color: #a855f7",
40
+ "36": "color: #06b6d4",
41
+ "37": "color: #fff",
42
+ "90": "color: #6b7280",
43
+ "91": "color: #f87171",
44
+ "92": "color: #4ade80",
45
+ "93": "color: #facc15",
46
+ "94": "color: #60a5fa",
47
+ "95": "color: #c084fc",
48
+ "96": "color: #22d3ee",
49
+ "97": "color: #f3f4f6",
50
+ };
51
+
52
+ let result = "";
53
+ let currentStyle = "";
54
+ let lastIndex = 0;
55
+
56
+ const regex = /\x1b\[([0-9;]*)m/g;
57
+ let match;
58
+
59
+ while ((match = regex.exec(str)) !== null) {
60
+ result += str.slice(lastIndex, match.index);
61
+ const code = match[1];
62
+
63
+ if (code === "0" || code === "") {
64
+ if (currentStyle) {
65
+ result += `</span>`;
66
+ currentStyle = "";
67
+ }
68
+ } else {
69
+ const codes = code.split(";").map(c => parseInt(c, 10));
70
+ const styles = [];
71
+ for (const c of codes) {
72
+ if (c >= 30 && c <= 37 || c >= 90 && c <= 97) {
73
+ const style = ansiMap[String(c)];
74
+ if (style) styles.push(style);
75
+ } else if (c === 1) {
76
+ styles.push("font-weight: bold");
77
+ } else if (c === 3) {
78
+ styles.push("font-style: italic");
79
+ } else if (c === 4) {
80
+ styles.push("text-decoration: underline");
81
+ }
82
+ }
83
+ if (styles.length > 0) {
84
+ if (currentStyle) result += `</span>`;
85
+ currentStyle = styles.join("; ");
86
+ result += `<span style="${currentStyle}">`;
87
+ }
88
+ }
89
+ lastIndex = match.index + match[0].length;
90
+ }
91
+
92
+ result += str.slice(lastIndex);
93
+ if (currentStyle) result += `</span>`;
94
+
95
+ return result || stripAnsi(str);
96
+ }
97
+
20
98
  class SpicetifyDevTools {
21
99
  constructor() {
22
100
  this.state = {
@@ -179,7 +257,8 @@
179
257
  const fragment = document.createDocumentFragment();
180
258
 
181
259
  errors.forEach((err, index) => {
182
- const msg = err.text || err.message || String(err);
260
+ const rawMsg = err.text || err.message || String(err);
261
+ const msg = parseAnsiColors(rawMsg);
183
262
  const item = document.createElement("div");
184
263
  item.className = "sc-err-item";
185
264
 
@@ -1,32 +1,4 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "module": "ESNext",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "types": ["@spicetify/creator/client"],
8
- "skipLibCheck": true,
9
- "jsx": "react-jsx",
10
-
11
- /* Bundler mode */
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "moduleDetection": "force",
16
- "noEmit": true,
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "erasableSyntaxOnly": true,
23
- "noFallthroughCasesInSwitch": true,
24
-
25
- // Paths
26
- "baseUrl": ".",
27
- "paths": {
28
- "@/*": ["./src/*"]
29
- }
30
- },
31
- "include": ["src"]
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
32
4
  }