@spicemod/creator 0.0.21 → 0.0.23
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/client.d.ts +47 -0
- package/dist/bin.mjs +682 -242
- package/dist/index.d.mts +697 -0
- package/dist/{client/index.mjs → index.mjs} +1 -1
- package/dist/templates/custom-app/js/react/eslint.config.ts +29 -0
- package/dist/templates/custom-app/js/react/src/app.jsx +22 -0
- package/dist/templates/custom-app/js/react/src/components/Onboarding.jsx +82 -0
- package/dist/templates/custom-app/js/react/src/extension/index.jsx +23 -0
- package/dist/templates/custom-app/meta.json +4 -0
- package/dist/templates/custom-app/shared/DOT-gitignore +34 -0
- package/dist/templates/custom-app/shared/DOT-oxlintrc.json +36 -0
- package/dist/templates/custom-app/shared/README.template.md +53 -0
- package/dist/templates/custom-app/shared/app.css +163 -0
- package/dist/templates/custom-app/shared/biome.json +36 -0
- package/dist/templates/custom-app/shared/css/app.module.scss +58 -0
- package/dist/templates/custom-app/shared/icon-active.svg +7 -0
- package/dist/templates/custom-app/shared/icon.svg +7 -0
- package/dist/templates/custom-app/shared/jsconfig.json +32 -0
- package/dist/templates/custom-app/shared/spice.config.js +9 -0
- package/dist/templates/custom-app/shared/spice.config.ts +9 -0
- package/dist/templates/custom-app/shared/tsconfig.json +32 -0
- package/dist/templates/custom-app/ts/react/eslint.config.ts +29 -0
- package/dist/templates/custom-app/ts/react/src/app.tsx +23 -0
- package/dist/templates/custom-app/ts/react/src/components/Onboarding.tsx +105 -0
- package/dist/templates/custom-app/ts/react/src/extension/index.tsx +27 -0
- package/dist/templates/extension/js/vanilla/src/components/Onboarding.js +71 -0
- package/dist/templates/extension/shared/DOT-gitignore +34 -0
- package/dist/templates/extension/shared/DOT-oxlintrc.json +36 -0
- package/dist/templates/extension/shared/spice.config.js +1 -1
- package/dist/templates/extension/shared/spice.config.ts +1 -1
- package/dist/templates/liveReload.js +0 -1
- package/dist/templates/theme/shared/DOT-gitignore +34 -0
- package/dist/templates/theme/shared/DOT-oxlintrc.json +36 -0
- package/dist/templates/theme/shared/spice.config.js +1 -1
- package/dist/templates/theme/shared/spice.config.ts +1 -1
- package/dist/templates/wrapper.js +6 -9
- package/package.json +7 -3
- package/templates/custom-app/js/react/eslint.config.ts +29 -0
- package/templates/custom-app/js/react/src/app.jsx +22 -0
- package/templates/custom-app/js/react/src/components/Onboarding.jsx +82 -0
- package/templates/custom-app/js/react/src/extension/index.jsx +23 -0
- package/templates/custom-app/meta.json +4 -0
- package/templates/custom-app/shared/DOT-gitignore +34 -0
- package/templates/custom-app/shared/DOT-oxlintrc.json +36 -0
- package/templates/custom-app/shared/README.template.md +53 -0
- package/templates/custom-app/shared/app.css +163 -0
- package/templates/custom-app/shared/biome.json +36 -0
- package/templates/custom-app/shared/css/app.module.scss +58 -0
- package/templates/custom-app/shared/icon-active.svg +7 -0
- package/templates/custom-app/shared/icon.svg +7 -0
- package/templates/custom-app/shared/jsconfig.json +32 -0
- package/templates/custom-app/shared/spice.config.js +9 -0
- package/templates/custom-app/shared/spice.config.ts +9 -0
- package/templates/custom-app/shared/tsconfig.json +32 -0
- package/templates/custom-app/ts/react/eslint.config.ts +29 -0
- package/templates/custom-app/ts/react/src/app.tsx +23 -0
- package/templates/custom-app/ts/react/src/components/Onboarding.tsx +105 -0
- package/templates/custom-app/ts/react/src/extension/index.tsx +27 -0
- package/templates/extension/js/vanilla/src/components/Onboarding.js +71 -0
- package/templates/extension/shared/DOT-gitignore +34 -0
- package/templates/extension/shared/DOT-oxlintrc.json +36 -0
- package/templates/extension/shared/spice.config.js +1 -1
- package/templates/extension/shared/spice.config.ts +1 -1
- package/templates/liveReload.js +0 -1
- package/templates/theme/shared/DOT-gitignore +34 -0
- package/templates/theme/shared/DOT-oxlintrc.json +36 -0
- package/templates/theme/shared/spice.config.js +1 -1
- package/templates/theme/shared/spice.config.ts +1 -1
- package/templates/wrapper.js +6 -9
- package/dist/client/index.d.mts +0 -2183
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 { context, transform } from "esbuild";
|
|
5
|
+
import { build, context, transform } from "esbuild";
|
|
6
6
|
import { createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { watchConfig } from "c12";
|
|
8
8
|
import { globSync } from "tinyglobby";
|
|
@@ -10,7 +10,7 @@ import { URL as URL$1, fileURLToPath } from "node:url";
|
|
|
10
10
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import readline, { createInterface } from "node:readline";
|
|
12
12
|
import * as p from "@clack/prompts";
|
|
13
|
-
import { cancel, log } from "@clack/prompts";
|
|
13
|
+
import { cancel, log, spinner } from "@clack/prompts";
|
|
14
14
|
import pc from "picocolors";
|
|
15
15
|
import "dotenv/config";
|
|
16
16
|
import { gzipSync } from "node:zlib";
|
|
@@ -22,8 +22,11 @@ import postcssImport from "postcss-import";
|
|
|
22
22
|
import postcssPresetEnv from "postcss-preset-env";
|
|
23
23
|
import { readFile, writeFile } from "node:fs/promises";
|
|
24
24
|
import { parse } from "ini";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
25
26
|
import { chdir } from "node:process";
|
|
26
27
|
import { lookup } from "node:dns/promises";
|
|
28
|
+
import { mkdir, writeFile as writeFile$1 } from "fs/promises";
|
|
29
|
+
import { dirname as dirname$1 } from "path";
|
|
27
30
|
import { createServer } from "node:http";
|
|
28
31
|
import { WebSocket, WebSocketServer } from "ws";
|
|
29
32
|
|
|
@@ -57,7 +60,11 @@ const toOptions = (metadata) => metadata.map((m) => ({
|
|
|
57
60
|
label: m.title,
|
|
58
61
|
hint: m.description
|
|
59
62
|
}));
|
|
60
|
-
const templateTypes = [
|
|
63
|
+
const templateTypes = [
|
|
64
|
+
"extension",
|
|
65
|
+
"theme",
|
|
66
|
+
"custom-app"
|
|
67
|
+
];
|
|
61
68
|
const templates = templateTypes.map((dir) => {
|
|
62
69
|
const meta_file = dist(`templates/${dir}/meta.json`, import.meta.url);
|
|
63
70
|
const { title, description } = JSON.parse(readFileSync(meta_file, "utf8"));
|
|
@@ -130,12 +137,13 @@ const frameworks = frameworkTypes.map((name) => ({
|
|
|
130
137
|
}));
|
|
131
138
|
const frameworkOptions = toOptions(frameworks);
|
|
132
139
|
const liveReloadFilePath = dist(`templates/liveReload.js`, import.meta.url);
|
|
133
|
-
const
|
|
140
|
+
const templateWrapperFilePath = dist("templates/wrapper.js", import.meta.url);
|
|
141
|
+
const templateCustomAppWrapperFilePath = dist("templates/customAppWrapper.js", import.meta.url);
|
|
134
142
|
|
|
135
143
|
//#endregion
|
|
136
144
|
//#region package.json
|
|
137
145
|
var name = "@spicemod/creator";
|
|
138
|
-
var version = "0.0.
|
|
146
|
+
var version = "0.0.23";
|
|
139
147
|
|
|
140
148
|
//#endregion
|
|
141
149
|
//#region src/utils/common.ts
|
|
@@ -229,57 +237,6 @@ async function installPackages(packageManager, isOnline) {
|
|
|
229
237
|
});
|
|
230
238
|
}
|
|
231
239
|
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/config/schema.ts
|
|
234
|
-
const ServerConfigSchema = v.object({
|
|
235
|
-
port: v.optional(v.number()),
|
|
236
|
-
serveDir: v.string(),
|
|
237
|
-
hmrPath: v.optional(v.string())
|
|
238
|
-
});
|
|
239
|
-
const EntryFileSchema = v.string();
|
|
240
|
-
const ThemeEntrySchema = v.object({
|
|
241
|
-
js: EntryFileSchema,
|
|
242
|
-
css: EntryFileSchema
|
|
243
|
-
});
|
|
244
|
-
const TemplateSpecificOptionalSchema = v.variant("template", [v.partial(v.object({
|
|
245
|
-
template: v.literal("extension"),
|
|
246
|
-
entry: EntryFileSchema
|
|
247
|
-
})), v.partial(v.object({
|
|
248
|
-
template: v.literal("theme"),
|
|
249
|
-
entry: ThemeEntrySchema
|
|
250
|
-
}))]);
|
|
251
|
-
const TemplateSpecificSchema = v.variant("template", [v.object({
|
|
252
|
-
template: v.literal("extension"),
|
|
253
|
-
entry: EntryFileSchema
|
|
254
|
-
}), v.object({
|
|
255
|
-
template: v.literal("theme"),
|
|
256
|
-
entry: ThemeEntrySchema
|
|
257
|
-
})]);
|
|
258
|
-
const CommonSchema = v.object({
|
|
259
|
-
name: v.string(),
|
|
260
|
-
outDir: v.string(),
|
|
261
|
-
linter: v.picklist(linterTypes),
|
|
262
|
-
framework: v.picklist(frameworkTypes),
|
|
263
|
-
packageManager: v.picklist(packageManagers),
|
|
264
|
-
esbuildOptions: v.record(v.string(), v.any()),
|
|
265
|
-
devModeVarName: v.optional(v.string()),
|
|
266
|
-
serverConfig: v.partial(ServerConfigSchema),
|
|
267
|
-
version: v.string()
|
|
268
|
-
});
|
|
269
|
-
const FileOptionsSchema = v.intersect([v.partial(CommonSchema), TemplateSpecificOptionalSchema]);
|
|
270
|
-
const OptionsSchema$1 = v.pipe(v.intersect([v.required(CommonSchema), TemplateSpecificSchema]), v.check((input) => !!input.name, "Name is required"));
|
|
271
|
-
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/env.ts
|
|
274
|
-
const isInternal = process.env.SPICE_INTERNAL === "true";
|
|
275
|
-
const isDev = process.env.IS_DEV === "true";
|
|
276
|
-
const spicetifyBin = process.env.SPICETIFY_BIN || process.env.SPICE_BIN || "spicetify";
|
|
277
|
-
const env = {
|
|
278
|
-
isInternal,
|
|
279
|
-
isDev,
|
|
280
|
-
spicetifyBin
|
|
281
|
-
};
|
|
282
|
-
|
|
283
240
|
//#endregion
|
|
284
241
|
//#region src/constants.ts
|
|
285
242
|
const GITHUB_LINK = "https://github.com/sanoojes/spicetify-creator";
|
|
@@ -291,6 +248,7 @@ const DEV_MODE_VAR_NAME = `__SPICE_CREATOR_DEV__`;
|
|
|
291
248
|
const CHECK = pc.bold(pc.green("✔"));
|
|
292
249
|
const CROSS = pc.bold(pc.red("✖"));
|
|
293
250
|
const WARN = pc.bold(pc.yellow("⚠"));
|
|
251
|
+
const SKIP_SPICETIFY = process.env.SPICETIFY_SKIP === "true" || process.env.CI === "true";
|
|
294
252
|
const VALID_PROJECT_FILES = new Set([
|
|
295
253
|
".DS_Store",
|
|
296
254
|
".git",
|
|
@@ -313,6 +271,150 @@ const VALID_PROJECT_FILES = new Set([
|
|
|
313
271
|
"yarnrc.yml",
|
|
314
272
|
".yarn"
|
|
315
273
|
]);
|
|
274
|
+
const CUSTOM_APP_NAME_LOCALES = [
|
|
275
|
+
"ms",
|
|
276
|
+
"gu",
|
|
277
|
+
"ko",
|
|
278
|
+
"pa-IN",
|
|
279
|
+
"az",
|
|
280
|
+
"ru",
|
|
281
|
+
"uk",
|
|
282
|
+
"nb",
|
|
283
|
+
"sv",
|
|
284
|
+
"sw",
|
|
285
|
+
"ur",
|
|
286
|
+
"bho",
|
|
287
|
+
"pa-PK",
|
|
288
|
+
"te",
|
|
289
|
+
"ro",
|
|
290
|
+
"vi",
|
|
291
|
+
"am",
|
|
292
|
+
"bn",
|
|
293
|
+
"en",
|
|
294
|
+
"id",
|
|
295
|
+
"bg",
|
|
296
|
+
"da",
|
|
297
|
+
"es-419",
|
|
298
|
+
"mr",
|
|
299
|
+
"ml",
|
|
300
|
+
"th",
|
|
301
|
+
"tr",
|
|
302
|
+
"is",
|
|
303
|
+
"fa",
|
|
304
|
+
"or",
|
|
305
|
+
"he",
|
|
306
|
+
"hi",
|
|
307
|
+
"zh-TW",
|
|
308
|
+
"sr",
|
|
309
|
+
"pt-BR",
|
|
310
|
+
"zu",
|
|
311
|
+
"nl",
|
|
312
|
+
"es",
|
|
313
|
+
"lt",
|
|
314
|
+
"ja",
|
|
315
|
+
"st",
|
|
316
|
+
"it",
|
|
317
|
+
"el",
|
|
318
|
+
"pt-PT",
|
|
319
|
+
"kn",
|
|
320
|
+
"de",
|
|
321
|
+
"fr",
|
|
322
|
+
"ne",
|
|
323
|
+
"ar",
|
|
324
|
+
"af",
|
|
325
|
+
"et",
|
|
326
|
+
"pl",
|
|
327
|
+
"ta",
|
|
328
|
+
"sl",
|
|
329
|
+
"pk",
|
|
330
|
+
"hr",
|
|
331
|
+
"sk",
|
|
332
|
+
"fi",
|
|
333
|
+
"lv",
|
|
334
|
+
"fil",
|
|
335
|
+
"fr-CA",
|
|
336
|
+
"cs",
|
|
337
|
+
"zh-CN",
|
|
338
|
+
"hu"
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/config/schema.ts
|
|
343
|
+
const ServerConfigSchema = v.object({
|
|
344
|
+
port: v.optional(v.number()),
|
|
345
|
+
serveDir: v.string(),
|
|
346
|
+
hmrPath: v.optional(v.string())
|
|
347
|
+
});
|
|
348
|
+
const EntryFileSchema = v.string();
|
|
349
|
+
const AssetEntrySchema = v.object({
|
|
350
|
+
js: EntryFileSchema,
|
|
351
|
+
css: EntryFileSchema
|
|
352
|
+
});
|
|
353
|
+
const CustomAppEntrySchema = v.object({
|
|
354
|
+
extension: EntryFileSchema,
|
|
355
|
+
app: EntryFileSchema
|
|
356
|
+
});
|
|
357
|
+
const LocaleNameSchema = v.intersect([v.object({ en: v.string() }), v.record(v.picklist(CUSTOM_APP_NAME_LOCALES), v.string())]);
|
|
358
|
+
const ExtensionTemplateSchema = v.object({
|
|
359
|
+
name: v.string(),
|
|
360
|
+
template: v.literal("extension"),
|
|
361
|
+
entry: EntryFileSchema
|
|
362
|
+
});
|
|
363
|
+
const ThemeTemplateSchema = v.object({
|
|
364
|
+
name: v.string(),
|
|
365
|
+
template: v.literal("theme"),
|
|
366
|
+
entry: AssetEntrySchema
|
|
367
|
+
});
|
|
368
|
+
const CustomAppTemplateSchema = v.object({
|
|
369
|
+
name: v.union([v.string(), LocaleNameSchema]),
|
|
370
|
+
icon: v.object({
|
|
371
|
+
default: v.string(),
|
|
372
|
+
active: v.optional(v.string())
|
|
373
|
+
}),
|
|
374
|
+
template: v.literal("custom-app"),
|
|
375
|
+
entry: CustomAppEntrySchema
|
|
376
|
+
});
|
|
377
|
+
const TemplateSpecificSchema = v.variant("template", [
|
|
378
|
+
ExtensionTemplateSchema,
|
|
379
|
+
ThemeTemplateSchema,
|
|
380
|
+
CustomAppTemplateSchema
|
|
381
|
+
]);
|
|
382
|
+
const TemplateSpecificOptionalSchema = v.variant("template", [
|
|
383
|
+
v.partial(ExtensionTemplateSchema),
|
|
384
|
+
v.partial(ThemeTemplateSchema),
|
|
385
|
+
v.partial(CustomAppTemplateSchema)
|
|
386
|
+
]);
|
|
387
|
+
const RequiredCommonSchema = v.object({
|
|
388
|
+
outDir: v.string(),
|
|
389
|
+
linter: v.picklist(linterTypes),
|
|
390
|
+
framework: v.picklist(frameworkTypes),
|
|
391
|
+
packageManager: v.picklist(packageManagers),
|
|
392
|
+
esbuildOptions: v.record(v.string(), v.any()),
|
|
393
|
+
serverConfig: v.partial(ServerConfigSchema),
|
|
394
|
+
version: v.string()
|
|
395
|
+
});
|
|
396
|
+
const OptionalCommonSchema = v.object({ devModeVarName: v.optional(v.string()) });
|
|
397
|
+
const FileOptionsSchema = v.intersect([
|
|
398
|
+
v.partial(RequiredCommonSchema),
|
|
399
|
+
OptionalCommonSchema,
|
|
400
|
+
TemplateSpecificOptionalSchema
|
|
401
|
+
]);
|
|
402
|
+
const OptionsSchema$1 = v.intersect([
|
|
403
|
+
v.required(RequiredCommonSchema),
|
|
404
|
+
OptionalCommonSchema,
|
|
405
|
+
TemplateSpecificSchema
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/env.ts
|
|
410
|
+
const isInternal = process.env.SPICE_INTERNAL === "true";
|
|
411
|
+
const isDev = process.env.IS_DEV === "true";
|
|
412
|
+
const spicetifyBin = process.env.SPICETIFY_BIN || process.env.SPICE_BIN || "spicetify";
|
|
413
|
+
const env = {
|
|
414
|
+
isInternal,
|
|
415
|
+
isDev,
|
|
416
|
+
spicetifyBin
|
|
417
|
+
};
|
|
316
418
|
|
|
317
419
|
//#endregion
|
|
318
420
|
//#region src/utils/logger.ts
|
|
@@ -350,8 +452,15 @@ var Logger = class {
|
|
|
350
452
|
this.add(console.warn, args);
|
|
351
453
|
}
|
|
352
454
|
error(...errors) {
|
|
353
|
-
|
|
354
|
-
for (const err of errors) if (err instanceof Error
|
|
455
|
+
const formatted = [];
|
|
456
|
+
for (const err of errors) if (err instanceof Error) {
|
|
457
|
+
formatted.push(pc.red(err.message));
|
|
458
|
+
if (this.isDev && err.stack) {
|
|
459
|
+
const stack = err.stack.split("\n").slice(1).join("\n");
|
|
460
|
+
formatted.push(pc.dim(stack));
|
|
461
|
+
}
|
|
462
|
+
} else formatted.push(err);
|
|
463
|
+
this.add(console.error, formatted);
|
|
355
464
|
}
|
|
356
465
|
log(...args) {
|
|
357
466
|
if (!this.isDev) return;
|
|
@@ -384,21 +493,39 @@ function safeParse(schema, data, type = "CLI") {
|
|
|
384
493
|
process.exit(1);
|
|
385
494
|
}
|
|
386
495
|
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/config/globs.ts
|
|
498
|
+
const JS_EXTENSIONS = "{ts,tsx,js,jsx,mts,mjs,cts,cjs}";
|
|
499
|
+
const CSS_EXTENSIONS = "{css,scss,sass,less,styl,stylus,pcss,postcss}";
|
|
500
|
+
const withSrc = (dirs) => dirs.flatMap((dir) => [dir, `src/${dir}`]);
|
|
501
|
+
const createGlobs = (names, dirs, ext) => names.flatMap((name) => dirs.map((dir) => `${dir}${name}.${ext}`));
|
|
502
|
+
const JS_ENTRY_GLOBS = createGlobs(["app", "index"], withSrc([""]), JS_EXTENSIONS);
|
|
503
|
+
const JS_EXTENSION_ENTRY_GLOBS = createGlobs(["app", "index"], withSrc(["extension/"]), JS_EXTENSIONS);
|
|
504
|
+
const CSS_ENTRY_GLOBS = createGlobs(["app"], withSrc([
|
|
505
|
+
"",
|
|
506
|
+
"styles/",
|
|
507
|
+
"css/"
|
|
508
|
+
]), CSS_EXTENSIONS);
|
|
509
|
+
const ICON_GLOBS = createGlobs(["icon", "logo"], withSrc([
|
|
510
|
+
"",
|
|
511
|
+
"icons/",
|
|
512
|
+
"assets/"
|
|
513
|
+
]), "svg");
|
|
514
|
+
const ICON_ACTIVE_GLOBS = createGlobs(["icon-active", "logo-active"], withSrc([
|
|
515
|
+
"",
|
|
516
|
+
"icons/",
|
|
517
|
+
"assets/"
|
|
518
|
+
]), "svg");
|
|
519
|
+
const ENTRY_MAP = {
|
|
520
|
+
js: [...JS_ENTRY_GLOBS, ...JS_EXTENSION_ENTRY_GLOBS],
|
|
521
|
+
css: CSS_ENTRY_GLOBS,
|
|
522
|
+
"js-app-only": JS_ENTRY_GLOBS,
|
|
523
|
+
"js-extension-only": JS_EXTENSION_ENTRY_GLOBS
|
|
524
|
+
};
|
|
525
|
+
|
|
387
526
|
//#endregion
|
|
388
527
|
//#region src/config/index.ts
|
|
389
528
|
const logger$1 = createLogger("config");
|
|
390
|
-
const JS_ENTRY_GLOBS = [
|
|
391
|
-
"app.{ts,tsx,js,jsx,mts,mjs,cts,cjs}",
|
|
392
|
-
"extension/app.{ts,tsx,js,jsx,mts,mjs,cts,cjs}",
|
|
393
|
-
"src/app.{ts,tsx,js,jsx,mts,mjs,cts,cjs}",
|
|
394
|
-
"src/extension/app.{ts,tsx,js,jsx,mts,mjs,cts,cjs}"
|
|
395
|
-
];
|
|
396
|
-
const CSS_ENTRY_GLOBS = [
|
|
397
|
-
"app.{css,scss,sass,less,styl,stylus,pcss,postcss}",
|
|
398
|
-
"styles/app.{css,scss,sass,less,styl,stylus,pcss,postcss}",
|
|
399
|
-
"src/app.{css,scss,sass,less,styl,stylus,pcss,postcss}",
|
|
400
|
-
"src/styles/app.{css,scss,sass,less,styl,stylus,pcss,postcss}"
|
|
401
|
-
];
|
|
402
529
|
const CONFIG_DEFAULTS = {
|
|
403
530
|
outDir: "./dist",
|
|
404
531
|
linter: "biome",
|
|
@@ -435,45 +562,77 @@ async function getResolvedConfig(config) {
|
|
|
435
562
|
}
|
|
436
563
|
async function resolveContext(config) {
|
|
437
564
|
const cwd = process.cwd();
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return {};
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
const pkg = config.name && config.version ? {} : getPkg();
|
|
446
|
-
if (!config.name) config.name = pkg.name || basename(cwd);
|
|
447
|
-
const DEFAULT_VERSION = "0.0.1";
|
|
448
|
-
if (!config.version) config.version = pkg.version ?? DEFAULT_VERSION;
|
|
449
|
-
if (!config.entry) if (config.template === "theme") {
|
|
450
|
-
config.entry = {
|
|
451
|
-
js: resolveDefaultEntries(cwd, "js"),
|
|
452
|
-
css: resolveDefaultEntries(cwd, "css")
|
|
453
|
-
};
|
|
454
|
-
if (!config.entry.js || config.entry.js.length === 0) config.entry.js = resolveDefaultEntries(cwd, "js");
|
|
455
|
-
if (!config.entry.css || config.entry.css.length === 0) config.entry.css = resolveDefaultEntries(cwd, "css");
|
|
456
|
-
} else config.entry = resolveDefaultEntries(cwd, "js");
|
|
565
|
+
const pkg = config.name && config.version ? {} : getPackageMeta(cwd);
|
|
566
|
+
config.name ||= pkg.name || basename(cwd);
|
|
567
|
+
config.version ||= pkg.version || "0.0.1";
|
|
568
|
+
config.entry ||= resolveConfigEntries(config.template, cwd);
|
|
457
569
|
config.outDir = resolve(cwd, config.outDir || "./dist");
|
|
458
570
|
config.esbuildOptions ??= {};
|
|
459
571
|
config.serverConfig ??= {};
|
|
572
|
+
if (config.template === "custom-app") config.icon ??= {
|
|
573
|
+
default: resolveDefaultIcon(cwd),
|
|
574
|
+
active: resolveActiveIcon(cwd)
|
|
575
|
+
};
|
|
460
576
|
return config;
|
|
461
577
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
578
|
+
function getPackageMeta(cwd) {
|
|
579
|
+
try {
|
|
580
|
+
return JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
|
|
581
|
+
} catch {
|
|
582
|
+
return {};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function resolveConfigEntries(template, cwd) {
|
|
586
|
+
if (template === "theme") return {
|
|
587
|
+
js: resolveDefaultEntry(cwd, "js"),
|
|
588
|
+
css: resolveDefaultEntry(cwd, "css")
|
|
589
|
+
};
|
|
590
|
+
if (template === "custom-app") return {
|
|
591
|
+
app: resolveDefaultEntry(cwd, "js-app-only"),
|
|
592
|
+
extension: resolveDefaultEntry(cwd, "js-extension-only")
|
|
472
593
|
};
|
|
473
|
-
|
|
474
|
-
if (!firstEntry) throw new Error(type === "js" ? "No JavaScript entry found (src/app or src/index)." : "No CSS entry found (src/app, src/index, or src/styles).");
|
|
475
|
-
return firstEntry;
|
|
594
|
+
return resolveDefaultEntry(cwd, "js");
|
|
476
595
|
}
|
|
596
|
+
function resolveDefaultEntry(cwd, type) {
|
|
597
|
+
const globs = ENTRY_MAP[type];
|
|
598
|
+
for (const glob of globs) {
|
|
599
|
+
const matches = globSync(glob, {
|
|
600
|
+
cwd,
|
|
601
|
+
absolute: true
|
|
602
|
+
});
|
|
603
|
+
if (matches.length > 0 && matches[0]) return matches[0];
|
|
604
|
+
}
|
|
605
|
+
const displayType = {
|
|
606
|
+
js: "JavaScript/TypeScript",
|
|
607
|
+
css: "CSS/Stylesheet",
|
|
608
|
+
"js-app-only": "JavaScript/TypeScript (Custom App)",
|
|
609
|
+
"js-extension-only": "JavaScript/TypeScript (Extension)"
|
|
610
|
+
}[type];
|
|
611
|
+
const expectedFiles = globs.map((g) => ` - ${g}`).join("\n");
|
|
612
|
+
throw new Error(`No ${displayType} entry file found in your project.\nPlease create one of the following files:\n${expectedFiles}`);
|
|
613
|
+
}
|
|
614
|
+
function resolveDefaultIcon(cwd) {
|
|
615
|
+
for (const glob of ICON_GLOBS) {
|
|
616
|
+
const matches = globSync(glob, {
|
|
617
|
+
cwd,
|
|
618
|
+
absolute: true
|
|
619
|
+
});
|
|
620
|
+
if (matches.length > 0 && matches[0]) return readFileSync(matches[0]).toString();
|
|
621
|
+
}
|
|
622
|
+
const expectedFiles = ICON_GLOBS.map((g) => ` - ${g}`).join("\n");
|
|
623
|
+
throw new Error(`No icon file found in your project.\nPlease create one of the following files:\n${expectedFiles}`);
|
|
624
|
+
}
|
|
625
|
+
function resolveActiveIcon(cwd) {
|
|
626
|
+
for (const glob of ICON_ACTIVE_GLOBS) {
|
|
627
|
+
const matches = globSync(glob, {
|
|
628
|
+
cwd,
|
|
629
|
+
absolute: true
|
|
630
|
+
});
|
|
631
|
+
if (matches.length > 0 && matches[0]) return readFileSync(matches[0]).toString();
|
|
632
|
+
}
|
|
633
|
+
return "";
|
|
634
|
+
}
|
|
635
|
+
const getEnName = (configName) => typeof configName === "string" ? configName : configName.en;
|
|
477
636
|
|
|
478
637
|
//#endregion
|
|
479
638
|
//#region src/esbuild/format.ts
|
|
@@ -576,14 +735,14 @@ function css({ minify = false, inline = false, logger = createLogger("plugin:css
|
|
|
576
735
|
type,
|
|
577
736
|
transform: postcssModules({
|
|
578
737
|
getJSON: () => {},
|
|
579
|
-
generateScopedName: "[name]__[local]___[hash:base64:5]"
|
|
580
|
-
localsConvention: "camelCaseOnly"
|
|
738
|
+
generateScopedName: "[name]__[local]___[hash:base64:5]"
|
|
581
739
|
}, postCssPlugins)
|
|
582
740
|
}), sassPlugin({
|
|
583
741
|
filter: /\.(s[ac]ss|css)$/,
|
|
584
742
|
type,
|
|
585
743
|
async transform(css, _resolveDir, filePath) {
|
|
586
744
|
const start = performance.now();
|
|
745
|
+
logger.log("processing:", filePath, "type:", type);
|
|
587
746
|
const result = await postcss(postCssPlugins).process(css, { from: filePath });
|
|
588
747
|
logger.debug("Global CSS processed", {
|
|
589
748
|
filePath,
|
|
@@ -643,6 +802,7 @@ function runSpice(args) {
|
|
|
643
802
|
validateSpicetify(env.spicetifyBin);
|
|
644
803
|
return spawnSync(env.spicetifyBin, args, { encoding: "utf-8" });
|
|
645
804
|
}
|
|
805
|
+
const getCustomAppsDir = () => join(getSpiceDataPath(), "CustomApps");
|
|
646
806
|
const getExtensionDir = () => join(getSpiceDataPath(), "Extensions");
|
|
647
807
|
const getThemesDir = () => join(getSpiceDataPath(), "Themes");
|
|
648
808
|
async function getSpicetifyConfig() {
|
|
@@ -666,16 +826,16 @@ function validateSpicetify(bin) {
|
|
|
666
826
|
|
|
667
827
|
//#endregion
|
|
668
828
|
//#region src/esbuild/plugins/spicetifyHandlers.ts
|
|
669
|
-
const
|
|
670
|
-
const spicetifyHandler = ({ config, options, cache, logger = createLogger("plugin:spicetifyHandler") }) => ({
|
|
829
|
+
const spicetifyHandler = ({ config, options, cache, logger = createLogger("plugin:spicetify-handler") }) => ({
|
|
671
830
|
name: "spice_internal__spicetify-build-handler",
|
|
672
831
|
async setup(build) {
|
|
673
832
|
const { apply = true, copy = true, applyOnce = true, remove, outDir = "./dist" } = options;
|
|
674
833
|
let hasAppliedOnce = false;
|
|
675
834
|
const isExtension = config.template === "extension";
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
835
|
+
const isCustomApp = config.template === "custom-app";
|
|
836
|
+
const identifier = isExtension ? `${urlSlugify(config.name)}.js` : urlSlugify(getEnName(config.name));
|
|
837
|
+
if (SKIP_SPICETIFY) {
|
|
838
|
+
logger.info(pc.yellow("skipping spicetify operations"));
|
|
679
839
|
build.onEnd(async (result) => {
|
|
680
840
|
if (result.errors.length > 0) return;
|
|
681
841
|
if (!cache.hasChanges || cache.changed.size === 0) return;
|
|
@@ -701,38 +861,36 @@ const spicetifyHandler = ({ config, options, cache, logger = createLogger("plugi
|
|
|
701
861
|
const spiceConfig = await getSpicetifyConfig();
|
|
702
862
|
logger.debug(pc.green("Spicetify Config: "), spiceConfig);
|
|
703
863
|
if (apply) {
|
|
704
|
-
const defaultTheme = spiceConfig
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
]);
|
|
712
|
-
else runSpice([
|
|
713
|
-
"config",
|
|
714
|
-
"current_theme",
|
|
715
|
-
spiceIdentifier
|
|
716
|
-
]);
|
|
717
|
-
});
|
|
864
|
+
const defaultTheme = spiceConfig?.Setting?.current_theme || "SpicetifyDefault";
|
|
865
|
+
const spiceIdentifier = remove ? `${identifier}-` : identifier;
|
|
866
|
+
runSpice([
|
|
867
|
+
"config",
|
|
868
|
+
isExtension ? "extensions" : isCustomApp ? "custom_apps" : "current_theme",
|
|
869
|
+
spiceIdentifier
|
|
870
|
+
]);
|
|
718
871
|
if (!isExtension && !remove) {
|
|
719
|
-
const
|
|
720
|
-
runSpice([
|
|
872
|
+
const cleanup = () => {
|
|
873
|
+
if (isCustomApp) runSpice([
|
|
874
|
+
"config",
|
|
875
|
+
"custom_apps",
|
|
876
|
+
`${identifier}-`
|
|
877
|
+
]);
|
|
878
|
+
else runSpice([
|
|
721
879
|
"config",
|
|
722
880
|
"current_theme",
|
|
723
881
|
defaultTheme
|
|
724
882
|
]);
|
|
725
883
|
process.exit();
|
|
726
884
|
};
|
|
727
|
-
process.once("SIGINT",
|
|
728
|
-
process.once("SIGTERM",
|
|
885
|
+
process.once("SIGINT", cleanup);
|
|
886
|
+
process.once("SIGTERM", cleanup);
|
|
729
887
|
}
|
|
730
888
|
}
|
|
731
889
|
build.onEnd(async (result) => {
|
|
732
890
|
if (result.errors.length > 0) return;
|
|
733
891
|
if (!cache.hasChanges || cache.changed.size === 0) return;
|
|
734
892
|
const destDirs = [resolve(outDir)];
|
|
735
|
-
if (copy) destDirs.push(isExtension ? getExtensionDir() : resolve(getThemesDir(), identifier));
|
|
893
|
+
if (copy) destDirs.push(isExtension ? getExtensionDir() : isCustomApp ? resolve(getCustomAppsDir(), identifier) : resolve(getThemesDir(), identifier));
|
|
736
894
|
const tasks = [];
|
|
737
895
|
for (const filePath of cache.changed) {
|
|
738
896
|
const fileData = cache.files.get(filePath);
|
|
@@ -753,8 +911,8 @@ const spicetifyHandler = ({ config, options, cache, logger = createLogger("plugi
|
|
|
753
911
|
return;
|
|
754
912
|
}
|
|
755
913
|
if (apply && cache.hasChanges && (!applyOnce || !hasAppliedOnce)) {
|
|
756
|
-
const { stderr, status } = runSpice(["apply"]);
|
|
757
|
-
if (status !== 0) logger.error(pc.red(`${CROSS} Spicetify apply failed
|
|
914
|
+
const { stdout, stderr, status } = runSpice(["apply"]);
|
|
915
|
+
if (status !== 0) logger.error(pc.red(`${CROSS} Spicetify apply failed:`), stdout, stderr);
|
|
758
916
|
else hasAppliedOnce = true;
|
|
759
917
|
}
|
|
760
918
|
});
|
|
@@ -763,26 +921,88 @@ const spicetifyHandler = ({ config, options, cache, logger = createLogger("plugi
|
|
|
763
921
|
|
|
764
922
|
//#endregion
|
|
765
923
|
//#region src/esbuild/plugins/wrapWithLoader.ts
|
|
766
|
-
function wrapWithLoader({
|
|
924
|
+
function wrapWithLoader({ config, cache, outFiles, server, dev = false, logger = createLogger("plugin:wrapper") }) {
|
|
767
925
|
const namespace = "spice_internal__wrap-with-loader";
|
|
926
|
+
const name = typeof config.name === "string" ? config.name : config.name.en;
|
|
927
|
+
const { template: type, version } = config;
|
|
928
|
+
let previousManifestHash;
|
|
768
929
|
return {
|
|
769
930
|
name: namespace,
|
|
770
|
-
setup(build) {
|
|
771
|
-
if (build.initialOptions.write !== false) throw new Error(`[${namespace}] This plugin requires "write: false" in build options.`);
|
|
772
|
-
build.onEnd(async (res) => {
|
|
931
|
+
setup(build$3) {
|
|
932
|
+
if (build$3.initialOptions.write !== false) throw new Error(`[${namespace}] This plugin requires "write: false" in build options.`);
|
|
933
|
+
build$3.onEnd(async (res) => {
|
|
773
934
|
try {
|
|
774
935
|
if (res.errors.length > 0 || !res.outputFiles) return;
|
|
775
936
|
cache.changed.clear();
|
|
776
937
|
cache.hasChanges = false;
|
|
777
938
|
const filesChanged = [];
|
|
778
|
-
|
|
779
|
-
|
|
939
|
+
const outdir = resolve(build$3.initialOptions.outdir || "./dist");
|
|
940
|
+
const bundledCss = getBundledCss(res.outputFiles, outdir, type, dev);
|
|
941
|
+
const minify = build$3.initialOptions.minify;
|
|
942
|
+
const slug = varSlugify(`${name}_${version}`);
|
|
780
943
|
const transformPromises = res.outputFiles.map(async (file) => {
|
|
781
944
|
const isJs = file.path.endsWith(".js");
|
|
782
945
|
const isCss = file.path.endsWith(".css");
|
|
783
946
|
if (!dev && isCss && type === "extension") return;
|
|
784
|
-
const
|
|
785
|
-
const
|
|
947
|
+
const relPath = file.path.slice(outdir.length);
|
|
948
|
+
const isCustomAppExtension = type === "custom-app" && isExtensionDir(relPath);
|
|
949
|
+
let targetName;
|
|
950
|
+
if (isJs) targetName = isCustomAppExtension ? outFiles.jsExtension ?? "extension.js" : outFiles.js;
|
|
951
|
+
else if (isCss && !isCustomAppExtension) targetName = outFiles.css;
|
|
952
|
+
if (!targetName) {
|
|
953
|
+
logger.debug("Skipped file: ", file.path);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const renamedPath = join(build$3.initialOptions.outdir || "./dist/", targetName);
|
|
957
|
+
if (type === "custom-app" && targetName === outFiles.js && isJs) {
|
|
958
|
+
const globalName = varSlugify(getEnName(config.name));
|
|
959
|
+
const final = (await build({
|
|
960
|
+
bundle: true,
|
|
961
|
+
write: false,
|
|
962
|
+
minify,
|
|
963
|
+
platform: "browser",
|
|
964
|
+
format: "iife",
|
|
965
|
+
globalName,
|
|
966
|
+
stdin: {
|
|
967
|
+
contents: `
|
|
968
|
+
import App from "virtual:app";
|
|
969
|
+
import React from "react";
|
|
970
|
+
|
|
971
|
+
export default function render() {
|
|
972
|
+
return <App />;
|
|
973
|
+
}
|
|
974
|
+
`,
|
|
975
|
+
loader: "tsx",
|
|
976
|
+
sourcefile: "entry.tsx"
|
|
977
|
+
},
|
|
978
|
+
plugins: [plugins.externalGlobal({ react: "Spicetify.React" }), {
|
|
979
|
+
name: "virtual-modules",
|
|
980
|
+
setup(vBuild) {
|
|
981
|
+
vBuild.onResolve({ filter: /^virtual:app$/ }, () => ({
|
|
982
|
+
path: "app",
|
|
983
|
+
namespace: "virtual"
|
|
984
|
+
}));
|
|
985
|
+
vBuild.onLoad({
|
|
986
|
+
filter: /.*/,
|
|
987
|
+
namespace: "virtual"
|
|
988
|
+
}, () => ({
|
|
989
|
+
contents: file.text,
|
|
990
|
+
loader: "js"
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
}]
|
|
994
|
+
})).outputFiles?.[0];
|
|
995
|
+
if (!final) return;
|
|
996
|
+
let combinedCode = `${final.text}${dev ? `export default () => ${globalName}.default();` : `var render = () => ${globalName}.default();`}\n`;
|
|
997
|
+
cache.files.set(renamedPath, {
|
|
998
|
+
contents: Buffer.from(combinedCode),
|
|
999
|
+
name: targetName
|
|
1000
|
+
});
|
|
1001
|
+
cache.changed.add(renamedPath);
|
|
1002
|
+
cache.hasChanges = true;
|
|
1003
|
+
filesChanged.push(renamedPath);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
786
1006
|
if (!isJs) {
|
|
787
1007
|
cache.files.set(renamedPath, {
|
|
788
1008
|
name: targetName,
|
|
@@ -793,31 +1013,25 @@ function wrapWithLoader({ name, type, version, cache, outFiles, server, dev = fa
|
|
|
793
1013
|
filesChanged.push(renamedPath);
|
|
794
1014
|
return;
|
|
795
1015
|
}
|
|
796
|
-
const
|
|
797
|
-
const templateRaw = readFileSync(templateFilePath, "utf-8");
|
|
798
|
-
const minify = build.initialOptions.minify;
|
|
799
|
-
const { code: transformedTemp } = await transform(templateRaw, {
|
|
1016
|
+
const { code: transformedTemp } = await transform(readFileSync(templateWrapperFilePath, "utf-8"), {
|
|
800
1017
|
minify,
|
|
801
|
-
target: build.initialOptions.target || "es2020",
|
|
1018
|
+
target: build$3.initialOptions.target || "es2020",
|
|
802
1019
|
loader: "jsx",
|
|
803
1020
|
define: {
|
|
804
|
-
__ESBUILD__HAS_CSS: JSON.stringify(type
|
|
1021
|
+
__ESBUILD__HAS_CSS: JSON.stringify(type !== "theme"),
|
|
1022
|
+
__ESBUILD__INJECTED_CSS: JSON.stringify(bundledCss),
|
|
805
1023
|
__ESBUILD__APP_SLUG: JSON.stringify(slug),
|
|
806
1024
|
__ESBUILD__APP_TYPE: JSON.stringify(type),
|
|
807
1025
|
__ESBUILD__APP_ID: JSON.stringify(varSlugify(name)),
|
|
808
|
-
__ESBUILD__APP_VERSION: JSON.stringify(version)
|
|
809
|
-
__ESBUILD__APP_HASH: JSON.stringify("")
|
|
1026
|
+
__ESBUILD__APP_VERSION: JSON.stringify(version)
|
|
810
1027
|
}
|
|
811
1028
|
});
|
|
812
1029
|
const template = replace(transformedTemp, {
|
|
813
1030
|
"\"{{INJECT_START_COMMENT}}\"": minify ? "" : "/* --- START --- */",
|
|
814
1031
|
"\"{{INJECT_END_COMMENT}}\"": minify ? "" : "/* --- END --- */",
|
|
815
|
-
"{{INJECTED_CSS_HERE}}": bundledCss,
|
|
816
1032
|
"\"{{INJECTED_JS_HERE}}\"": file.text
|
|
817
1033
|
});
|
|
818
1034
|
const nextBuffer = Buffer.from(template);
|
|
819
|
-
const previous = cache.files.get(renamedPath);
|
|
820
|
-
if (previous?.contents && Buffer.compare(previous.contents, nextBuffer) === 0) return;
|
|
821
1035
|
cache.files.set(renamedPath, {
|
|
822
1036
|
name: targetName,
|
|
823
1037
|
contents: nextBuffer
|
|
@@ -827,6 +1041,29 @@ function wrapWithLoader({ name, type, version, cache, outFiles, server, dev = fa
|
|
|
827
1041
|
filesChanged.push(renamedPath);
|
|
828
1042
|
});
|
|
829
1043
|
await Promise.all(transformPromises);
|
|
1044
|
+
if (type === "custom-app") {
|
|
1045
|
+
const icon = config.icon;
|
|
1046
|
+
const manifestPath = join(outdir, "manifest.json");
|
|
1047
|
+
const manifest = {
|
|
1048
|
+
name: config.name,
|
|
1049
|
+
subfiles: [],
|
|
1050
|
+
subfiles_extension: ["extension.js"],
|
|
1051
|
+
icon: icon.default,
|
|
1052
|
+
"active-icon": icon.active ?? ""
|
|
1053
|
+
};
|
|
1054
|
+
const manifestString = JSON.stringify(manifest, null, 2);
|
|
1055
|
+
const currentHash = createHash("md5").update(manifestString).digest("hex");
|
|
1056
|
+
if (currentHash !== previousManifestHash) {
|
|
1057
|
+
previousManifestHash = currentHash;
|
|
1058
|
+
cache.files.set(manifestPath, {
|
|
1059
|
+
contents: Buffer.from(manifestString),
|
|
1060
|
+
name: "manifest.json"
|
|
1061
|
+
});
|
|
1062
|
+
cache.changed.add(manifestPath);
|
|
1063
|
+
cache.hasChanges = true;
|
|
1064
|
+
filesChanged.push(manifestPath);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
830
1067
|
if (filesChanged.length > 0) server?.broadcast(filesChanged);
|
|
831
1068
|
} catch (e) {
|
|
832
1069
|
logger.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -835,6 +1072,15 @@ function wrapWithLoader({ name, type, version, cache, outFiles, server, dev = fa
|
|
|
835
1072
|
}
|
|
836
1073
|
};
|
|
837
1074
|
}
|
|
1075
|
+
function isExtensionDir(relPath) {
|
|
1076
|
+
return relPath.startsWith("/extension/") || relPath.startsWith("\\extension\\");
|
|
1077
|
+
}
|
|
1078
|
+
function getBundledCss(files, outdir, type, dev) {
|
|
1079
|
+
const cssFiles = files.filter((f) => f.path.endsWith(".css"));
|
|
1080
|
+
if (!dev && type === "extension") return cssFiles.map((f) => f.text).join("");
|
|
1081
|
+
if (type === "custom-app") return cssFiles.filter((f) => isExtensionDir(f.path.slice(outdir.length))).map((f) => f.text).join("");
|
|
1082
|
+
return "";
|
|
1083
|
+
}
|
|
838
1084
|
|
|
839
1085
|
//#endregion
|
|
840
1086
|
//#region src/esbuild/plugins/index.ts
|
|
@@ -860,7 +1106,7 @@ const defaultBuildOptions = {
|
|
|
860
1106
|
target: ["es2022", "chrome120"]
|
|
861
1107
|
};
|
|
862
1108
|
const getCommonPlugins = (opts) => {
|
|
863
|
-
const { template, minify, cache,
|
|
1109
|
+
const { template, minify, cache, buildOptions, outFiles, server, dev } = opts;
|
|
864
1110
|
return [
|
|
865
1111
|
...plugins.css({
|
|
866
1112
|
minify,
|
|
@@ -874,9 +1120,7 @@ const getCommonPlugins = (opts) => {
|
|
|
874
1120
|
"react/jsx-runtime": "Spicetify.ReactJSX"
|
|
875
1121
|
}),
|
|
876
1122
|
plugins.wrapWithLoader({
|
|
877
|
-
|
|
878
|
-
version,
|
|
879
|
-
type: template,
|
|
1123
|
+
config: opts,
|
|
880
1124
|
cache,
|
|
881
1125
|
outFiles,
|
|
882
1126
|
server,
|
|
@@ -890,11 +1134,32 @@ const getCommonPlugins = (opts) => {
|
|
|
890
1134
|
plugins.buildLogger({ cache })
|
|
891
1135
|
];
|
|
892
1136
|
};
|
|
1137
|
+
function getEntryPoints(config) {
|
|
1138
|
+
if (config.template === "theme") return [config.entry.js, config.entry.css];
|
|
1139
|
+
if (config.template === "custom-app") return [config.entry.app, config.entry.extension];
|
|
1140
|
+
return [config.entry];
|
|
1141
|
+
}
|
|
1142
|
+
function getOutFiles(config) {
|
|
1143
|
+
switch (config.template) {
|
|
1144
|
+
case "custom-app": return {
|
|
1145
|
+
js: "index.js",
|
|
1146
|
+
css: "style.css",
|
|
1147
|
+
jsExtension: "extension.js",
|
|
1148
|
+
manifest: "manifest.json"
|
|
1149
|
+
};
|
|
1150
|
+
case "extension": return { js: `${urlSlugify(getEnName(config.name))}.js` };
|
|
1151
|
+
case "theme": return {
|
|
1152
|
+
js: "theme.js",
|
|
1153
|
+
css: "user.css"
|
|
1154
|
+
};
|
|
1155
|
+
default: throw new Error("Unknown template");
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
893
1158
|
|
|
894
1159
|
//#endregion
|
|
895
1160
|
//#region src/build/index.ts
|
|
896
1161
|
const logger = createLogger("build");
|
|
897
|
-
async function build$
|
|
1162
|
+
async function build$2(options) {
|
|
898
1163
|
logger.clear();
|
|
899
1164
|
logger.greeting(pc.green("Building for production..."));
|
|
900
1165
|
let ctx;
|
|
@@ -921,10 +1186,7 @@ async function build$1(options) {
|
|
|
921
1186
|
});
|
|
922
1187
|
}
|
|
923
1188
|
function getJSBuildOptions(config, options) {
|
|
924
|
-
const entryPoints = (
|
|
925
|
-
if (config.template === "theme") return [config.entry.js, config.entry.css];
|
|
926
|
-
return [config.entry];
|
|
927
|
-
})();
|
|
1189
|
+
const entryPoints = getEntryPoints(config);
|
|
928
1190
|
const minify = options.watch ? false : options.minify;
|
|
929
1191
|
const outDir = resolve(config.outDir);
|
|
930
1192
|
const cache = {
|
|
@@ -932,14 +1194,13 @@ function getJSBuildOptions(config, options) {
|
|
|
932
1194
|
changed: /* @__PURE__ */ new Set(),
|
|
933
1195
|
hasChanges: true
|
|
934
1196
|
};
|
|
935
|
-
const outFiles =
|
|
936
|
-
js: config.template === "extension" ? `${urlSlugify(config.name)}.js` : "theme.js",
|
|
937
|
-
css: config.template === "theme" ? "user.css" : null
|
|
938
|
-
};
|
|
1197
|
+
const outFiles = getOutFiles(config);
|
|
939
1198
|
const overrides = {
|
|
940
1199
|
...defaultBuildOptions,
|
|
941
1200
|
outdir: outDir,
|
|
1201
|
+
format: "esm",
|
|
942
1202
|
minify,
|
|
1203
|
+
globalName: varSlugify(getEnName(config.name)),
|
|
943
1204
|
sourcemap: false,
|
|
944
1205
|
external: [
|
|
945
1206
|
...config.esbuildOptions?.external ? config.esbuildOptions.external : [],
|
|
@@ -980,8 +1241,8 @@ const CLIOptionsSchema$1 = v.strictObject({
|
|
|
980
1241
|
apply: v.boolean(),
|
|
981
1242
|
copy: v.boolean()
|
|
982
1243
|
});
|
|
983
|
-
const build = new Command("build").description("Build your spicetify project").option("-a, --apply", "Apply to spicetify", false).option("-w, --watch", "Watch mode", false).option("--no-copy", "Do not copy files to spicetify").option("--no-minify", "Disable code minification").action(async (opts) => {
|
|
984
|
-
await build$
|
|
1244
|
+
const build$1 = new Command("build").description("Build your spicetify project").option("-a, --apply", "Apply to spicetify", false).option("-w, --watch", "Watch mode", false).option("--no-copy", "Do not copy files to spicetify").option("--no-minify", "Disable code minification").action(async (opts) => {
|
|
1245
|
+
await build$2(safeParse(CLIOptionsSchema$1, opts));
|
|
985
1246
|
});
|
|
986
1247
|
|
|
987
1248
|
//#endregion
|
|
@@ -1039,7 +1300,8 @@ function createPackageJSON(options) {
|
|
|
1039
1300
|
scripts: {
|
|
1040
1301
|
sc: "spicetify-creator",
|
|
1041
1302
|
dev: "spicetify-creator dev",
|
|
1042
|
-
build: "spicetify-creator build"
|
|
1303
|
+
build: "spicetify-creator build",
|
|
1304
|
+
"update-types": "spicetify-creator update-types"
|
|
1043
1305
|
},
|
|
1044
1306
|
dependencies: {},
|
|
1045
1307
|
devDependencies: { "@spicetify/creator": env.isInternal ? "link:@spicetify/creator" : "latest" }
|
|
@@ -1084,9 +1346,10 @@ function validateProjectName(name) {
|
|
|
1084
1346
|
|
|
1085
1347
|
//#endregion
|
|
1086
1348
|
//#region src/create/template.ts
|
|
1087
|
-
const ext = (lang) => lang === "ts" ?
|
|
1349
|
+
const ext = (lang, jsx = false) => lang === "ts" ? `ts${jsx ? "x" : ""}` : `js${jsx ? "x" : ""}`;
|
|
1088
1350
|
const kv = ({ name, language, framework, linter, packageManager, template }) => ({
|
|
1089
1351
|
"{{project-name}}": name,
|
|
1352
|
+
"{{project-url}}": `/${urlSlugify(name)}`,
|
|
1090
1353
|
"{{framework}}": framework,
|
|
1091
1354
|
"{{linter}}": linter,
|
|
1092
1355
|
"{{package-manager}}": packageManager,
|
|
@@ -1103,36 +1366,60 @@ const kv = ({ name, language, framework, linter, packageManager, template }) =>
|
|
|
1103
1366
|
const action = { modify(c, opts) {
|
|
1104
1367
|
return replace(c, kv(opts));
|
|
1105
1368
|
} };
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1369
|
+
const SHARED_FILES = (opts) => {
|
|
1370
|
+
const isShared = true;
|
|
1371
|
+
const files = [
|
|
1372
|
+
{
|
|
1373
|
+
from: "README.template.md",
|
|
1374
|
+
to: "README.md",
|
|
1375
|
+
action,
|
|
1376
|
+
isShared
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
from: "DOT-gitignore",
|
|
1380
|
+
to: ".gitignore",
|
|
1381
|
+
isShared: true
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
from: `spice.config.${ext(opts.language)}`,
|
|
1385
|
+
to: `spice.config.${ext(opts.language)}`,
|
|
1386
|
+
action,
|
|
1387
|
+
isShared
|
|
1388
|
+
}
|
|
1389
|
+
];
|
|
1390
|
+
if (opts.template === "custom-app") {
|
|
1391
|
+
files.push({
|
|
1392
|
+
from: `css/app.module.scss`,
|
|
1393
|
+
to: `src/css/app.module.scss`,
|
|
1394
|
+
action,
|
|
1395
|
+
isShared
|
|
1396
|
+
});
|
|
1397
|
+
files.push({
|
|
1398
|
+
from: `icon.svg`,
|
|
1399
|
+
to: `src/icon.svg`,
|
|
1400
|
+
action,
|
|
1401
|
+
isShared
|
|
1402
|
+
});
|
|
1403
|
+
files.push({
|
|
1404
|
+
from: `icon-active.svg`,
|
|
1405
|
+
to: `src/icon-active.svg`,
|
|
1406
|
+
action,
|
|
1407
|
+
isShared
|
|
1408
|
+
});
|
|
1409
|
+
files.push({
|
|
1410
|
+
from: "app.css",
|
|
1411
|
+
to: "src/extension/app.css",
|
|
1412
|
+
action,
|
|
1413
|
+
isShared
|
|
1414
|
+
});
|
|
1415
|
+
} else files.push({
|
|
1125
1416
|
from: "app.css",
|
|
1126
1417
|
to: "src/app.css",
|
|
1127
1418
|
action,
|
|
1128
|
-
isShared
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
to: "src/types/css.d.ts",
|
|
1133
|
-
isShared: true
|
|
1134
|
-
}] : []
|
|
1135
|
-
];
|
|
1419
|
+
isShared
|
|
1420
|
+
});
|
|
1421
|
+
return files;
|
|
1422
|
+
};
|
|
1136
1423
|
const LANGUAGE_FILES = {
|
|
1137
1424
|
js: [{
|
|
1138
1425
|
from: "jsconfig.json",
|
|
@@ -1146,24 +1433,36 @@ const LANGUAGE_FILES = {
|
|
|
1146
1433
|
}]
|
|
1147
1434
|
};
|
|
1148
1435
|
const FRAMEWORKS = {
|
|
1149
|
-
react: ({ language }) =>
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1436
|
+
react: ({ language, template }) => {
|
|
1437
|
+
const react = [{
|
|
1438
|
+
from: `src/app.${ext(language, true)}`,
|
|
1439
|
+
to: `src/app.${ext(language, true)}`,
|
|
1440
|
+
action
|
|
1441
|
+
}, {
|
|
1442
|
+
from: `src/components/Onboarding.${ext(language, true)}`,
|
|
1443
|
+
to: `src/components/Onboarding.${ext(language, true)}`,
|
|
1444
|
+
action
|
|
1445
|
+
}];
|
|
1446
|
+
if (template === "custom-app") react.push({
|
|
1447
|
+
from: `src/extension/index.${ext(language, true)}`,
|
|
1448
|
+
to: `src/extension/index.${ext(language, true)}`,
|
|
1449
|
+
action
|
|
1450
|
+
});
|
|
1451
|
+
return react;
|
|
1452
|
+
},
|
|
1453
|
+
vanilla: ({ language, template }) => {
|
|
1454
|
+
const vanilla = [{
|
|
1455
|
+
from: `src/app.${ext(language)}`,
|
|
1456
|
+
to: `src/app.${ext(language)}`,
|
|
1457
|
+
action
|
|
1458
|
+
}, {
|
|
1459
|
+
from: `src/components/Onboarding.${ext(language)}`,
|
|
1460
|
+
to: `src/components/Onboarding.${ext(language)}`,
|
|
1461
|
+
action
|
|
1462
|
+
}];
|
|
1463
|
+
if (template === "custom-app") throw new Error("vanilla doesn't exist for custom-app");
|
|
1464
|
+
return vanilla;
|
|
1465
|
+
}
|
|
1167
1466
|
};
|
|
1168
1467
|
const LINTERS = {
|
|
1169
1468
|
biome: [{
|
|
@@ -1176,23 +1475,25 @@ const LINTERS = {
|
|
|
1176
1475
|
to: `eslint.config.${ext(language)}`
|
|
1177
1476
|
}],
|
|
1178
1477
|
oxlint: [{
|
|
1179
|
-
from: "
|
|
1478
|
+
from: "DOT-oxlintrc.json",
|
|
1180
1479
|
to: ".oxlintrc.json",
|
|
1181
1480
|
isShared: true
|
|
1182
1481
|
}]
|
|
1183
1482
|
};
|
|
1483
|
+
const getFiles = (options) => {
|
|
1484
|
+
const resolve = (slice) => typeof slice === "function" ? slice(options) : slice ?? [];
|
|
1485
|
+
return [
|
|
1486
|
+
...resolve(SHARED_FILES),
|
|
1487
|
+
...resolve(LANGUAGE_FILES[options.language]),
|
|
1488
|
+
...resolve(FRAMEWORKS[options.framework]),
|
|
1489
|
+
...resolve(LINTERS[options.linter])
|
|
1490
|
+
];
|
|
1491
|
+
};
|
|
1184
1492
|
function setupTemplateFiles(options, targetDir) {
|
|
1185
|
-
const { template, language, framework
|
|
1493
|
+
const { template, language, framework } = options;
|
|
1186
1494
|
const templateRoot = dist(`templates/${template}`, import.meta.url);
|
|
1187
1495
|
const fromDir = join(templateRoot, language, framework);
|
|
1188
|
-
|
|
1189
|
-
const files = [
|
|
1190
|
-
...resolve(COMMON_FILES),
|
|
1191
|
-
...resolve(LANGUAGE_FILES[language]),
|
|
1192
|
-
...resolve(FRAMEWORKS[framework]),
|
|
1193
|
-
...resolve(LINTERS[linter])
|
|
1194
|
-
];
|
|
1195
|
-
for (const file of files) {
|
|
1496
|
+
for (const file of getFiles(options)) {
|
|
1196
1497
|
const src = (() => {
|
|
1197
1498
|
if (file.isGlobal) return join(templateRoot, file.from);
|
|
1198
1499
|
if (file.isShared) return join(templateRoot, "shared", file.from);
|
|
@@ -1290,6 +1591,35 @@ function tryGitInit(root) {
|
|
|
1290
1591
|
}
|
|
1291
1592
|
}
|
|
1292
1593
|
|
|
1594
|
+
//#endregion
|
|
1595
|
+
//#region src/utils/update-types.ts
|
|
1596
|
+
const downloads = { spicetify: {
|
|
1597
|
+
from: "https://raw.githubusercontent.com/spicetify/cli/main/globals.d.ts",
|
|
1598
|
+
to: "./src/types/spicetify.d.ts",
|
|
1599
|
+
action: (content) => content.replace("const React: any;", "const React: typeof import(\"react\");").replace("const ReactDOM: any;", "const ReactDOM: typeof import(\"react-dom/client\");").replace("const ReactDOMServer: any;", "const ReactDOMServer: typeof import(\"react-dom/server\");")
|
|
1600
|
+
} };
|
|
1601
|
+
async function updateTypes(isUpdating = true) {
|
|
1602
|
+
const s = spinner();
|
|
1603
|
+
s.start(`${isUpdating ? "Updating" : "Creating"} Types...`);
|
|
1604
|
+
await Promise.all(Object.entries(downloads).map(([name, download]) => downloadFile(name, download, isUpdating)));
|
|
1605
|
+
s.stop(`${isUpdating ? "Updated" : "Created"} Types!`);
|
|
1606
|
+
}
|
|
1607
|
+
async function downloadFile(name, { from, to, action }, isUpdating) {
|
|
1608
|
+
try {
|
|
1609
|
+
const res = await fetch(from);
|
|
1610
|
+
if (!res.ok) throw new Error(`HTTP Error: ${res.status} ${res.statusText}`);
|
|
1611
|
+
let text = await res.text();
|
|
1612
|
+
if (action) text = await action(text);
|
|
1613
|
+
await mkdir(dirname$1(to), { recursive: true });
|
|
1614
|
+
await writeFile$1(to, text, "utf8");
|
|
1615
|
+
const actionLog = isUpdating ? "updated" : "created";
|
|
1616
|
+
logger$2.log(`${name}.d.ts ${actionLog} (${from} -> ${to})`);
|
|
1617
|
+
} catch (e) {
|
|
1618
|
+
log.error(`${name} failed`);
|
|
1619
|
+
console.error(e);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1293
1623
|
//#endregion
|
|
1294
1624
|
//#region src/create/index.ts
|
|
1295
1625
|
const isOnline = await getOnline();
|
|
@@ -1336,7 +1666,8 @@ async function createProject(cwd, options) {
|
|
|
1336
1666
|
options: templateOptions
|
|
1337
1667
|
});
|
|
1338
1668
|
},
|
|
1339
|
-
framework: async () => {
|
|
1669
|
+
framework: async ({ results: { template } }) => {
|
|
1670
|
+
if (template === "custom-app") return "react";
|
|
1340
1671
|
if (options.framework) return options.framework;
|
|
1341
1672
|
return await p.select({
|
|
1342
1673
|
message: "Select which framework you want to chose",
|
|
@@ -1406,6 +1737,7 @@ async function create$1(cwd, options) {
|
|
|
1406
1737
|
try {
|
|
1407
1738
|
mkdirp(cwd);
|
|
1408
1739
|
setupTemplateFiles(options, cwd);
|
|
1740
|
+
await updateTypes(false);
|
|
1409
1741
|
const pkgJSON = createPackageJSON(options);
|
|
1410
1742
|
writePackageJSON(pkgJSON, cwd);
|
|
1411
1743
|
chdir(cwd);
|
|
@@ -1616,7 +1948,7 @@ const injectHMRExtension = async (rootLink, wsLink, outFiles) => {
|
|
|
1616
1948
|
const extName = `sc-live-reload-helper.js`;
|
|
1617
1949
|
const spiceConfig = await getSpicetifyConfig();
|
|
1618
1950
|
const cleanup = () => {
|
|
1619
|
-
|
|
1951
|
+
logger$2.info(`Removing Live reload extension...`);
|
|
1620
1952
|
try {
|
|
1621
1953
|
runSpice([
|
|
1622
1954
|
"config",
|
|
@@ -1624,18 +1956,16 @@ const injectHMRExtension = async (rootLink, wsLink, outFiles) => {
|
|
|
1624
1956
|
`${extName}-`
|
|
1625
1957
|
]);
|
|
1626
1958
|
runSpice(["apply"]);
|
|
1627
|
-
logger$2.
|
|
1959
|
+
logger$2.info(pc.green(`${CHECK} Cleanup successful.`));
|
|
1628
1960
|
} catch (e) {
|
|
1629
|
-
|
|
1961
|
+
logger$2.error(pc.red(`${CROSS} Cleanup failed: `), e);
|
|
1630
1962
|
}
|
|
1631
1963
|
process.exit();
|
|
1632
1964
|
};
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
process.on("SIGTERM", cleanup);
|
|
1636
|
-
}
|
|
1965
|
+
process.on("SIGINT", cleanup);
|
|
1966
|
+
process.on("SIGTERM", cleanup);
|
|
1637
1967
|
try {
|
|
1638
|
-
logger$2.debug(`
|
|
1968
|
+
logger$2.debug(`Preparing Live reload extension...`);
|
|
1639
1969
|
const destDir = getExtensionDir();
|
|
1640
1970
|
mkdirp(destDir);
|
|
1641
1971
|
const outDir = resolve(destDir, extName);
|
|
@@ -1662,6 +1992,114 @@ const injectHMRExtension = async (rootLink, wsLink, outFiles) => {
|
|
|
1662
1992
|
logger$2.error(pc.red(`${CROSS} Failed to inject HMR helper: ${err instanceof Error ? err.message : String(err)}`));
|
|
1663
1993
|
}
|
|
1664
1994
|
};
|
|
1995
|
+
const injectHMRCustomApp = async (rootLink, wsLink, outFiles, config) => {
|
|
1996
|
+
if (config.template !== "custom-app") throw new Error("only supports custom-app templates");
|
|
1997
|
+
const identifier = getEnName(config.name);
|
|
1998
|
+
const spiceConfig = await getSpicetifyConfig();
|
|
1999
|
+
const customAppId = urlSlugify(identifier);
|
|
2000
|
+
runSpice([
|
|
2001
|
+
"config",
|
|
2002
|
+
"extensions",
|
|
2003
|
+
"sc-live-reload-helper.js-"
|
|
2004
|
+
]);
|
|
2005
|
+
const cleanup = () => {
|
|
2006
|
+
logger$2.info(`Removing Live reload custom-app...`);
|
|
2007
|
+
try {
|
|
2008
|
+
runSpice([
|
|
2009
|
+
"config",
|
|
2010
|
+
"custom_apps",
|
|
2011
|
+
`${customAppId}-`
|
|
2012
|
+
]);
|
|
2013
|
+
runSpice(["apply"]);
|
|
2014
|
+
logger$2.info(pc.green(`${CHECK} Cleanup successful.`));
|
|
2015
|
+
} catch (e) {
|
|
2016
|
+
logger$2.error(pc.red(`${CROSS} Cleanup failed: `), e);
|
|
2017
|
+
}
|
|
2018
|
+
process.exit();
|
|
2019
|
+
};
|
|
2020
|
+
process.on("SIGINT", cleanup);
|
|
2021
|
+
process.on("SIGTERM", cleanup);
|
|
2022
|
+
try {
|
|
2023
|
+
logger$2.debug(`Preparing Live reload custom-app...`);
|
|
2024
|
+
const destDir = resolve(getCustomAppsDir(), customAppId);
|
|
2025
|
+
mkdirp(destDir);
|
|
2026
|
+
const indexJsPath = resolve(destDir, "index.js");
|
|
2027
|
+
const extensionJsPath = resolve(destDir, outFiles.jsExtension ?? "extension.js");
|
|
2028
|
+
writeFileSync(indexJsPath, `const React = Spicetify.React;
|
|
2029
|
+
const waitForImport = () => {
|
|
2030
|
+
return import("${rootLink}/files/${outFiles.js}")
|
|
2031
|
+
.then((mod) => mod.default || mod.render)
|
|
2032
|
+
.catch((err) => {
|
|
2033
|
+
console.error("Failed to import app:", err);
|
|
2034
|
+
return null;
|
|
2035
|
+
});
|
|
2036
|
+
};
|
|
2037
|
+
|
|
2038
|
+
const AppWrapper = ({ appPromise }) => {
|
|
2039
|
+
const [App, setApp] = React.useState(null);
|
|
2040
|
+
|
|
2041
|
+
React.useEffect(() => {
|
|
2042
|
+
let mounted = true;
|
|
2043
|
+
|
|
2044
|
+
appPromise.then((app) => {
|
|
2045
|
+
if (mounted && app) {
|
|
2046
|
+
setApp(() => app);
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
return () => {
|
|
2051
|
+
mounted = false;
|
|
2052
|
+
};
|
|
2053
|
+
}, [appPromise]);
|
|
2054
|
+
|
|
2055
|
+
if (!App) {
|
|
2056
|
+
return React.createElement(
|
|
2057
|
+
"div",
|
|
2058
|
+
{ className: "loading" },
|
|
2059
|
+
"Loading app..."
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
return React.createElement(App);
|
|
2064
|
+
};
|
|
2065
|
+
|
|
2066
|
+
const render = () => {
|
|
2067
|
+
const appPromise = waitForImport();
|
|
2068
|
+
return React.createElement(AppWrapper, { appPromise });
|
|
2069
|
+
};
|
|
2070
|
+
`);
|
|
2071
|
+
const { code: extensionCode } = await transform(readFileSync(liveReloadFilePath, "utf8"), {
|
|
2072
|
+
loader: "js",
|
|
2073
|
+
define: {
|
|
2074
|
+
_SERVER_URL: JSON.stringify(rootLink),
|
|
2075
|
+
_HOT_RELOAD_LINK: JSON.stringify(wsLink),
|
|
2076
|
+
_JS_PATH: JSON.stringify(`/files/${outFiles.jsExtension ?? "extension.js"}`),
|
|
2077
|
+
_CSS_PATH: JSON.stringify(outFiles.css ? `/files/${outFiles.css}` : `/files/app.css`)
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
writeFileSync(extensionJsPath, extensionCode);
|
|
2081
|
+
const manifestPath = resolve(destDir, "manifest.json");
|
|
2082
|
+
const manifest = {
|
|
2083
|
+
name: identifier,
|
|
2084
|
+
subfiles: [],
|
|
2085
|
+
subfiles_extension: [outFiles.jsExtension ?? "extension.js"],
|
|
2086
|
+
icon: config.icon.default,
|
|
2087
|
+
"active-icon": config.icon.active ?? config.icon.default
|
|
2088
|
+
};
|
|
2089
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
2090
|
+
if (spiceConfig) {
|
|
2091
|
+
runSpice([
|
|
2092
|
+
"config",
|
|
2093
|
+
"custom_apps",
|
|
2094
|
+
customAppId
|
|
2095
|
+
]);
|
|
2096
|
+
runSpice(["apply"]);
|
|
2097
|
+
}
|
|
2098
|
+
logger$2.debug(pc.green(`${CHECK} Live reload custom-app injected successfully.`));
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
logger$2.error(pc.red(`${CROSS} Failed to inject HMR custom-app: ${err instanceof Error ? err.message : String(err)}`));
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
1665
2103
|
|
|
1666
2104
|
//#endregion
|
|
1667
2105
|
//#region src/dev/index.ts
|
|
@@ -1684,11 +2122,9 @@ async function dev$1(options) {
|
|
|
1684
2122
|
port: options.port ?? config.serverConfig.port
|
|
1685
2123
|
});
|
|
1686
2124
|
await server.start();
|
|
1687
|
-
const outFiles =
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
};
|
|
1691
|
-
await injectHMRExtension(server.link, server.wsLink, outFiles);
|
|
2125
|
+
const outFiles = getOutFiles(config);
|
|
2126
|
+
if (config.template === "custom-app") await injectHMRCustomApp(server.link, server.wsLink, outFiles, config);
|
|
2127
|
+
else await injectHMRExtension(server.link, server.wsLink, outFiles);
|
|
1692
2128
|
ctx = await context(getJSDevOptions(config, {
|
|
1693
2129
|
...options,
|
|
1694
2130
|
outFiles,
|
|
@@ -1711,10 +2147,7 @@ async function dev$1(options) {
|
|
|
1711
2147
|
});
|
|
1712
2148
|
}
|
|
1713
2149
|
function getJSDevOptions(config, options) {
|
|
1714
|
-
const entryPoints = (
|
|
1715
|
-
if (config.template === "theme") return [config.entry.js, config.entry.css];
|
|
1716
|
-
return [config.entry];
|
|
1717
|
-
})();
|
|
2150
|
+
const entryPoints = getEntryPoints(config);
|
|
1718
2151
|
const minify = false;
|
|
1719
2152
|
const cache = {
|
|
1720
2153
|
files: /* @__PURE__ */ new Map(),
|
|
@@ -1742,7 +2175,9 @@ function getJSDevOptions(config, options) {
|
|
|
1742
2175
|
cache,
|
|
1743
2176
|
buildOptions: {
|
|
1744
2177
|
copy: true,
|
|
1745
|
-
|
|
2178
|
+
apply: false,
|
|
2179
|
+
applyOnce: false,
|
|
2180
|
+
remove: config.template !== "custom-app",
|
|
1746
2181
|
outDir
|
|
1747
2182
|
},
|
|
1748
2183
|
dev: true,
|
|
@@ -1764,12 +2199,17 @@ const dev = new Command("dev").description("Develop your spicetify project").opt
|
|
|
1764
2199
|
await dev$1(safeParse(CLIOptionsSchema, opts));
|
|
1765
2200
|
});
|
|
1766
2201
|
|
|
2202
|
+
//#endregion
|
|
2203
|
+
//#region src/commands/update-types.ts
|
|
2204
|
+
const update_types = new Command("update-types").description("Update Spicetify Types").action(async () => {
|
|
2205
|
+
await updateTypes();
|
|
2206
|
+
});
|
|
2207
|
+
|
|
1767
2208
|
//#endregion
|
|
1768
2209
|
//#region src/bin.ts
|
|
1769
2210
|
logger$2.debug(`Env: ${JSON.stringify(env, null, 2)}\n`);
|
|
1770
2211
|
const command = new Command();
|
|
1771
|
-
create.alias("init");
|
|
1772
|
-
command.addCommand(create).addCommand(build).addCommand(dev);
|
|
2212
|
+
command.addCommand(create.alias("init")).addCommand(build$1).addCommand(dev).addCommand(update_types);
|
|
1773
2213
|
command.parse();
|
|
1774
2214
|
|
|
1775
2215
|
//#endregion
|