-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsync-userscript.js
More file actions
179 lines (164 loc) · 8.24 KB
/
Copy pathsync-userscript.js
File metadata and controls
179 lines (164 loc) · 8.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { getUserscriptBasename, resolveUserscriptPath } = require('./scripts/repo-paths');
const REPO_ROOT = __dirname;
const EXTENSION_SOURCE = path.join(REPO_ROOT, 'extension', 'ytkit.js');
const USERSCRIPT_SOURCE = resolveUserscriptPath(REPO_ROOT);
const USERSCRIPT_BASENAME = getUserscriptBasename(REPO_ROOT);
const USERSCRIPT_RAW_URL = `https://raw.githubusercontent.com/SysAdminDoc/Astra-Deck/main/${USERSCRIPT_BASENAME}`;
// v4.20.0: bundle the v5.0.0 core modules into the userscript so the
// userscript path reaches feature parity with the MV3 extension. Each
// listed module is an IIFE that attaches to globalThis.YTKitCore or
// globalThis.YTKitFeatures — safe to concatenate in this order. The
// region between the BEGIN/END markers is replaced wholesale on every
// sync; do NOT hand-edit content between the markers in YTKit.user.js.
// If a manifest feature cannot ship in the userscript, classify the feature ID
// in scripts/check-userscript-drift.js instead of leaving silent parity drift.
const V5_BUNDLE_MODULES = [
'extension/core/styles.js',
'extension/core/settings-visual-system.js',
'extension/core/settings-schema.js',
'extension/core/feature-lifecycle.js',
'extension/core/policy-profile.js',
'extension/core/settings-controller.js',
// Bundled so the monolith settingsManager can run imports through the same
// snapshot/rollback/undo transaction the extension uses, instead of
// carrying a second implementation. Pure JS, no chrome.* and no DOM.
// NOTE: no apostrophes in comments inside this array — check-userscript-drift.js
// scans it with a bare quote regex and one stray quote truncates the list.
'extension/core/settings-import-transaction.js',
'extension/core/transcript-service.js',
'extension/core/transcript-index.js',
'extension/core/ai-summary-artifacts.js',
'extension/core/credential-vault.js',
'extension/core/userscript-ai-summary.js',
'extension/core/external-api-health.js',
'extension/core/selector-health.js',
'extension/core/companion-ports.js',
'extension/core/data-flow.js',
'extension/core/toast.js',
'extension/core/toast-dom.js',
'extension/core/navigation.js',
'extension/core/player.js',
'extension/core/resource-unlock.js',
'extension/core/text-metrics.js',
'extension/core/date-time.js',
'extension/core/runtime-flags.js',
'extension/core/capability-probe.js',
'extension/features/subtitles/index.js',
'extension/features/video-filters/index.js',
'extension/features/blue-light-filter/index.js',
'extension/features/theme-css/index.js',
'extension/features/wave-8-css/index.js',
'extension/features/home-subs-css/index.js',
'extension/features/chat-style-comments/index.js',
'extension/features/sticky-video/index.js',
'extension/features/sticky-chat/index.js',
'extension/features/video-hider/index.js',
'extension/features/video-notes/index.js',
'extension/features/subscription-groups/index.js',
'extension/features/digital-wellbeing/index.js',
'extension/features/settings-panel/index.js',
'extension/features/player-dock/index.js',
'extension/features/youtube-music-compat/index.js',
'extension/features/return-dislike/index.js',
'extension/features/sponsorblock/index.js',
'extension/features/dearrow/index.js',
'extension/core/lifecycle-route-bridge.js'
];
const BUNDLE_BEGIN_RE = /^[ \t]*\/\/ ── BEGIN v5\.0\.0 bundled core modules ──\r?\n[\s\S]*?^[ \t]*\/\/ ── END v5\.0\.0 bundled core modules ──/m;
function bundledModuleHeader(rel) {
return ' // ── bundled module: ' + rel + ' ──';
}
// Build the bundled-module region exactly as the userscript must contain it.
// check-userscript-drift.js recomputes this and compares it against the
// shipped bundle, so this function is the single source of truth for the
// transform. A fingerprint-substring check cannot see a stale module body —
// v4.51.2's settings-schema shipped stale through three releases that way.
function buildBundleRegion(repoRoot = REPO_ROOT) {
const parts = [' // ── BEGIN v5.0.0 bundled core modules ──'];
parts.push(' // Auto-bundled by sync-userscript.js — do NOT hand-edit. To refresh, run:');
parts.push(' // node sync-userscript.js');
parts.push(' //');
parts.push(' // The hardening test `v4.20.0 userscript bundles every v5.0.0 core module');
parts.push(' // verbatim` pins the parity contract.');
parts.push('');
for (const rel of V5_BUNDLE_MODULES) {
const full = path.join(repoRoot, rel);
if (!fs.existsSync(full)) {
const error = new Error('Module not found: ' + rel);
error.modulePath = rel;
throw error;
}
const moduleBody = fs.readFileSync(full, 'utf8').replace(/\s+$/, '');
// A module containing either bundle marker would truncate the region
// the next sync run's regex matches, silently corrupting the
// userscript. Refuse to bundle rather than write a poisoned bundle.
if (/── (?:BEGIN|END) v5\.0\.0 bundled core modules ──/.test(moduleBody)) {
const error = new Error('Refusing to bundle ' + rel + ': module source contains a v5.0.0 bundle marker, which would corrupt the next sync run.');
error.modulePath = rel;
throw error;
}
parts.push(bundledModuleHeader(rel));
// Indent each line by 4 spaces so the bundled module sits cleanly
// inside the userscript's outer IIFE (cosmetic — JS doesn't care).
parts.push(moduleBody.split('\n').map((line) => line.length ? ' ' + line : line).join('\n'));
parts.push('');
}
parts.push(' // ── END v5.0.0 bundled core modules ──');
return parts.join('\n');
}
function main() {
const extensionText = fs.readFileSync(EXTENSION_SOURCE, 'utf8');
const versionMatch = extensionText.match(/const YTKIT_VERSION = '([^']+)'/);
if (!versionMatch) {
console.error('Could not find YTKIT_VERSION in extension/ytkit.js');
process.exit(1);
}
const targetVersion = versionMatch[1];
let userscriptText = fs.readFileSync(USERSCRIPT_SOURCE, 'utf8');
const before = userscriptText;
const headerEnd = userscriptText.indexOf('// ==/UserScript==');
if (headerEnd === -1) {
console.error('Could not find userscript metadata header terminator');
process.exit(1);
}
const headerCloseEnd = headerEnd + '// ==/UserScript=='.length;
let headerText = userscriptText.slice(0, headerCloseEnd);
const bodyText = userscriptText.slice(headerCloseEnd);
headerText = headerText.replace(/^(\/\/ @name\s+)YTKit v[\d.]+/m,
(_match, prefix) => `${prefix}YTKit v${targetVersion}`);
headerText = headerText.replace(/^(\/\/ @version\s+)[\d.]+/m,
(_match, prefix) => `${prefix}${targetVersion}`);
headerText = headerText.replace(/^(\/\/ @updateURL\s+).+$/m,
(_match, prefix) => `${prefix}${USERSCRIPT_RAW_URL}`);
headerText = headerText.replace(/^(\/\/ @downloadURL\s+).+$/m,
(_match, prefix) => `${prefix}${USERSCRIPT_RAW_URL}`);
userscriptText = headerText + bodyText;
userscriptText = userscriptText.replace(/const YTKIT_VERSION = '[^']+';/,
() => `const YTKIT_VERSION = '${targetVersion}';`);
if (BUNDLE_BEGIN_RE.test(userscriptText)) {
let bundleRegion;
try {
bundleRegion = buildBundleRegion(REPO_ROOT);
} catch (error) {
console.error(error.message);
process.exit(1);
}
userscriptText = userscriptText.replace(BUNDLE_BEGIN_RE, () => bundleRegion);
} else {
console.warn('Userscript bundle markers not found — skipping bundle refresh.');
}
if (userscriptText === before) {
console.log(`Userscript already aligned to v${targetVersion}`);
process.exit(0);
}
fs.writeFileSync(USERSCRIPT_SOURCE, userscriptText, 'utf8');
console.log(`Userscript metadata synced to v${targetVersion} (${path.basename(USERSCRIPT_SOURCE)})`);
}
if (require.main === module) {
main();
}
module.exports = { V5_BUNDLE_MODULES, buildBundleRegion, bundledModuleHeader, BUNDLE_BEGIN_RE };