@spicetify/kit 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +82 -0
  2. package/dist/args.js +15 -0
  3. package/dist/cli.js +4 -1
  4. package/dist/create.js +9 -5
  5. package/dist/dev.js +86 -22
  6. package/dist/install.js +4 -6
  7. package/dist/launch.js +24 -24
  8. package/dist/push.js +187 -44
  9. package/dist/remove.js +37 -0
  10. package/dist/stdlib-boundary.js +2 -2
  11. package/dist/version.js +1 -1
  12. package/package.json +6 -4
  13. package/vendor/classmaps/1030000/classmap-19f856aefd5.json +109 -0
  14. package/vendor/shims/platform.d.ts +1640 -3235
  15. package/vendor/stdlib/metadata.json +1 -1
  16. package/vendor/stdlib/query.ts +31 -0
  17. package/vendor/stdlib/src/client.ts +90 -1
  18. package/vendor/stdlib/src/events.mix.ts +9 -0
  19. package/vendor/stdlib/src/events.ts +1 -1
  20. package/vendor/stdlib/src/registers/menu-order.ts +17 -0
  21. package/vendor/stdlib/src/registers/menu.ts +8 -4
  22. package/vendor/stdlib/src/registers/mount.ts +35 -9
  23. package/vendor/stdlib/src/registers/navlink.tsx +3 -50
  24. package/vendor/stdlib/src/registers/playbarButton.tsx +0 -19
  25. package/vendor/stdlib/src/registers/playbarWidget.tsx +0 -19
  26. package/vendor/stdlib/src/registers/root.ts +1 -1
  27. package/vendor/stdlib/src/registers/route.ts +1 -23
  28. package/vendor/stdlib/src/registers/topbarLeftButton.tsx +0 -24
  29. package/vendor/stdlib/src/registers/topbarRightButton.tsx +0 -24
  30. package/vendor/stdlib/src/storage.ts +1 -1
  31. package/vendor/stdlib/src/webpack/ReactComponents.xpui.ts +3 -9
  32. package/vendor/stdlib/src/webpack/URI.xpui.ts +1 -1
  33. package/vendor/stdlib/src/webpack/capture-readiness.ts +5 -3
  34. package/vendor/stdlib/src/webpack/index.ts +10 -4
  35. package/vendor/stdlib/src/webpack/playlist-menu.ts +54 -0
  36. package/vendor/stdlib/src/wpunpk.mix.ts +1 -1
  37. package/vendor/classmaps/1020096/classmap-19f856aefd5.json +0 -109
@@ -3,18 +3,20 @@
3
3
  * SPDX-License-Identifier: GPL-3.0-or-later
4
4
  */
5
5
 
6
+ type TimeoutHandle = ReturnType<typeof setTimeout> | number;
7
+
6
8
  export interface CaptureReadinessOptions {
7
9
  timeoutMs: number;
8
10
  onTimeout?: () => void;
9
- scheduleTimeout?: typeof setTimeout;
10
- clearScheduledTimeout?: typeof clearTimeout;
11
+ scheduleTimeout?: (callback: () => void, delay: number) => TimeoutHandle;
12
+ clearScheduledTimeout?: (timer: TimeoutHandle) => void;
11
13
  }
12
14
 
13
15
  export function createCaptureReadiness(options: CaptureReadinessOptions) {
14
16
  const settled = Promise.withResolvers<void>();
15
17
  let released = false;
16
18
  let analyzed = false;
17
- let timer: ReturnType<typeof setTimeout> | undefined;
19
+ let timer: TimeoutHandle | undefined;
18
20
  const scheduleTimeout = options.scheduleTimeout ?? setTimeout;
19
21
  const clearScheduledTimeout = options.clearScheduledTimeout ?? clearTimeout;
20
22
  const release = () => {
@@ -64,6 +64,12 @@ export const analyzeWebpackRequire = (webpackRequire: WebpackRequire) => {
64
64
  };
65
65
  };
66
66
 
67
+ function resolveChunk(path: string): void {
68
+ const chunk = (CHUNKS[path] ??= Promise.withResolvers());
69
+ if (!chunk.resolve) throw new TypeError(`Chunk ${path} has no resolver`);
70
+ chunk.resolve(undefined);
71
+ }
72
+
67
73
  CHUNKS["/vendor~xpui.js"] ??= Promise.withResolvers();
68
74
  CHUNKS["/xpui.js"] ??= Promise.withResolvers();
69
75
  Object.assign(CHUNKS, {
@@ -84,7 +90,7 @@ postWebpackRequireHooks.push((wpr: any) => {
84
90
  load(
85
91
  url,
86
92
  (event: unknown) => {
87
- (CHUNKS[new URL(url, location.href).pathname] ??= Promise.withResolvers()).resolve(undefined);
93
+ resolveChunk(new URL(url, location.href).pathname);
88
94
  return done(event);
89
95
  },
90
96
  key,
@@ -98,7 +104,7 @@ postWebpackRequireHooks.push((wpr: any) => {
98
104
  const url = new URL(entry.name, location.href);
99
105
  if (url.pathname === "/vendor~xpui.js" || url.pathname === "/xpui.js") continue;
100
106
  if (url.origin === location.origin && url.pathname.endsWith(".js")) {
101
- (CHUNKS[url.pathname] ??= Promise.withResolvers()).resolve(undefined);
107
+ resolveChunk(url.pathname);
102
108
  }
103
109
  }
104
110
  // Capture can fire while boot is still registering modules (xpui-modules
@@ -115,8 +121,8 @@ postWebpackRequireHooks.push((wpr: any) => {
115
121
  last = count;
116
122
  if (stable >= 3 || ++ticks > 100) {
117
123
  clearInterval(settle);
118
- CHUNKS["/vendor~xpui.js"].resolve(undefined);
119
- CHUNKS["/xpui.js"].resolve(undefined);
124
+ resolveChunk("/vendor~xpui.js");
125
+ resolveChunk("/xpui.js");
120
126
  }
121
127
  }, 100);
122
128
  });
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright (C) 2026 Spicetify
3
+ * SPDX-License-Identifier: GPL-3.0-or-later
4
+ */
5
+
6
+ function source(value: unknown): string {
7
+ return typeof value === "function" ? Function.prototype.toString.call(value) : "";
8
+ }
9
+
10
+ function hasPlaylistMenuActions(text: string): boolean {
11
+ return (
12
+ text.includes("contextmenu.share.copy-playlist-link") &&
13
+ text.includes("canView") &&
14
+ text.includes("permissions")
15
+ );
16
+ }
17
+
18
+ export function isPlaylistMenuFactory(value: unknown): boolean {
19
+ return hasPlaylistMenuActions(source(value));
20
+ }
21
+
22
+ function isMenuComponent(value: unknown): boolean {
23
+ let render = value;
24
+ if (typeof render === "object" && render !== null && "$$typeof" in render) {
25
+ if (render.$$typeof === Symbol.for("react.memo") && "type" in render) render = render.type;
26
+ else if (render.$$typeof === Symbol.for("react.forward_ref") && "render" in render) render = render.render;
27
+ }
28
+ const text = source(render);
29
+ if (/^async\b/.test(text)) return false;
30
+ if (hasPlaylistMenuActions(text)) return /\b(?:jsx|jsxs|createElement)\s*\)?\s*\(/.test(text);
31
+ // Current clients export a thin JSX wrapper around the unexported memo.
32
+ // Match the whole wrapper, not JSX nested inside a page root.
33
+ return /^\(?([$\w]+)\)?=>\(0,[$\w]+\.jsx\)\([$\w]+,\{\.\.\.\1\}\)$/.test(text.replace(/\s/g, ""));
34
+ }
35
+
36
+ /** Match the menu factory, then its renderable export; never a playlist data mapper. */
37
+ export function findPlaylistMenu(
38
+ modules: Iterable<readonly [PropertyKey, unknown]>,
39
+ requireModule: (id: PropertyKey) => unknown,
40
+ ): unknown {
41
+ const components = new Set<unknown>();
42
+ try {
43
+ for (const [id, factory] of modules) {
44
+ if (!isPlaylistMenuFactory(factory)) continue;
45
+ const exports = requireModule(id);
46
+ if (typeof exports !== "object" || exports === null) continue;
47
+ for (const value of Object.values(exports)) if (isMenuComponent(value)) components.add(value);
48
+ }
49
+ } catch {
50
+ // An unreadable client export leaves uniqueness unproven; degrade only this menu.
51
+ return undefined;
52
+ }
53
+ return components.size === 1 ? components.values().next().value : undefined;
54
+ }
@@ -42,7 +42,7 @@ export { Subject, BehaviorSubject };
42
42
 
43
43
  export const chunkLoadedSubjectPre = new Subject<unknown>();
44
44
  export const chunkLoadedSubjectPost = new Subject<unknown>();
45
- export const moduleLoadedSubject = new Subject<unknown>();
45
+ export const moduleLoadedSubject = new Subject<[string, WebpackModule]>();
46
46
 
47
47
  const pendingHooks: Array<(wpr: any) => void> = [];
48
48
  export const postWebpackRequireHooks = {
@@ -1,109 +0,0 @@
1
- {
2
- "main": {
3
- "topbar": {
4
- "wrapper": "NXttmW3KPwE1r4Am",
5
- "left": {
6
- "button": {
7
- "wrapper": "kUHE42xvQVzWqabl",
8
- "icon": {
9
- "wrapper": "IYDlXmBmmUKHveMzIPCF"
10
- }
11
- },
12
- "button_t": {
13
- "wrapper": "_Bg_zSvFrEutyacG"
14
- }
15
- },
16
- "right": {
17
- "button": {
18
- "wrapper": "kUHE42xvQVzWqabl"
19
- },
20
- "button_t": {
21
- "wrapper": "sXjzYBob5Y4psogB"
22
- },
23
- "upgrade_button": {
24
- "wrapper": "Upqw01TOXETOmR5Td7Dj"
25
- }
26
- }
27
- },
28
- "playbar": {
29
- "widget": {},
30
- "controls": {},
31
- "buttons": {
32
- "button": {
33
- "wrapper": "_EuXSrqTA94N1Evn",
34
- "wrapper__active": "v1kNYVvW5P8Wa4jS",
35
- "wrapper__indicator": "EHxL6K_6WWDlTCZP6x5w"
36
- }
37
- }
38
- },
39
- "navbar": {
40
- "link": {
41
- "wrapper": "kUHE42xvQVzWqabl",
42
- "container": "kUHE42xvQVzWqabl",
43
- "container__active": "kUHE42xvQVzWqabl"
44
- }
45
- },
46
- "global_navbar": {},
47
- "sidebar": {},
48
- "panel": {}
49
- },
50
- "tracklist": {
51
- "column_header": "F_VvNCRKZ2cKj1a9"
52
- },
53
- "search_chips": {
54
- "wrapper_wrapper": "qZHfdckfFVY5AURF",
55
- "wrapper": "x_HLN829yDsvJDgl",
56
- "container": "dxlv5wfAu8zgm7HY",
57
- "chip": "U4idT8HUFwfj7PWP"
58
- },
59
- "modal": {
60
- "widget_generator": {
61
- "container": "gx9qsprOAO4ubEFc",
62
- "header": {
63
- "container": "g9sFYudWkUldbuNa",
64
- "close": "Pk4GI8WfBMjS5R2J"
65
- },
66
- "content": {
67
- "container": "ywtSYUL2xRwVlvSo"
68
- }
69
- },
70
- "track_credits": {
71
- "container": "p5LJBmlswjcRT458",
72
- "header": {
73
- "container": "idsrX6VAUY49Eczw",
74
- "close": "_18qFNPzv7Eyyck8"
75
- },
76
- "content": {
77
- "container": "yKG9lWaUWIBUyVCu"
78
- }
79
- }
80
- },
81
- "settings": {
82
- "header": {
83
- "container": "fEEfljGMIEhhmWVi"
84
- },
85
- "section": {
86
- "container": "nWR7PbHjFXQJiYNL"
87
- },
88
- "button": {
89
- "wrapper": "nHgEqWYM1q0omZ6y"
90
- },
91
- "text_input": "e-10451-form-input e-10451-form-control encore-text-body-medium"
92
- },
93
- "scrollable_text": {
94
- "container": "MyaStf5D5Im3gt9M",
95
- "wrapper": "dxlv5wfAu8zgm7HY"
96
- },
97
- "sort_box": {
98
- "list": {
99
- "button": "LQ6y2jhZZtMS7xCr"
100
- }
101
- },
102
- "search_box": {
103
- "container": "S0nU7XTXYMOBcASz",
104
- "expand_button": "eu6ObgEHE5owtzi_"
105
- },
106
- "context_menu": {
107
- "menu_item": "yqG4N5YYcY4uEujt"
108
- }
109
- }