pulse-rb 1.2.24
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/README.md +225 -0
- package/adapters/linoria.lua +233 -0
- package/adapters/windui.llms.txt +366 -0
- package/adapters/windui.lua +505 -0
- package/bin/rb.js +2 -0
- package/dist/index.js +3285 -0
- package/package.json +59 -0
- package/pulse/dev/debuggui.lua +1206 -0
- package/pulse/dev/devconfig.lua +81 -0
- package/pulse/dev/ui/9_DevPanel.lua +384 -0
- package/pulse/helpers/aim.lua +193 -0
- package/pulse/helpers/cache.lua +68 -0
- package/pulse/helpers/cleaner.lua +110 -0
- package/pulse/helpers/conn.lua +33 -0
- package/pulse/helpers/cooldown.lua +47 -0
- package/pulse/helpers/draw.lua +122 -0
- package/pulse/helpers/hitbox.lua +63 -0
- package/pulse/helpers/input.lua +24 -0
- package/pulse/helpers/log.lua +228 -0
- package/pulse/helpers/loop.lua +58 -0
- package/pulse/helpers/memory.lua +48 -0
- package/pulse/helpers/narrate.lua +160 -0
- package/pulse/helpers/notify.lua +51 -0
- package/pulse/helpers/perf.lua +48 -0
- package/pulse/helpers/remote.lua +128 -0
- package/pulse/helpers/restore.lua +59 -0
- package/pulse/helpers/store.lua +39 -0
- package/pulse/helpers/team.lua +83 -0
- package/pulse/helpers/testmode.lua +80 -0
- package/pulse/helpers/trace.lua +111 -0
- package/pulse/helpers/track.lua +85 -0
- package/pulse/helpers/vec.lua +52 -0
- package/pulse/helpers/world.lua +51 -0
- package/pulse/runtime.lua +343 -0
- package/pulse/ui/linoria_settings.lua +55 -0
- package/pulse/ui/windui_settings.lua +87 -0
- package/templates/AGENTS.md +177 -0
- package/templates/CLAUDE.md +424 -0
- package/templates/deploy_config.example +17 -0
- package/templates/example_esp.rblua +69 -0
- package/templates/example_fov.rblua +20 -0
- package/templates/example_speed.rblua +25 -0
- package/templates/gitignore +4 -0
- package/templates/globals.lua +66 -0
- package/templates/layout.rblua +28 -0
- package/templates/module.lua +7 -0
- package/templates/module.rblua +32 -0
- package/templates/page_home.rblua +9 -0
- package/templates/remote.lua +6 -0
- package/templates/remotes.lua +14 -0
- package/vscode/language-configuration.json +35 -0
- package/vscode/package.json +35 -0
- package/vscode/src/extension.js +397 -0
- package/vscode/syntaxes/rblua.tmLanguage.json +126 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3285 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var os = require('os');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var pc = require('picocolors');
|
|
6
|
+
var prompts = require('@clack/prompts');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
var pathe = require('pathe');
|
|
9
|
+
var ofetch = require('ofetch');
|
|
10
|
+
var child_process = require('child_process');
|
|
11
|
+
var chokidar = require('chokidar');
|
|
12
|
+
var execa = require('execa');
|
|
13
|
+
var globby = require('globby');
|
|
14
|
+
var promises = require('fs/promises');
|
|
15
|
+
var citty = require('citty');
|
|
16
|
+
|
|
17
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
|
|
19
|
+
var pc__default = /*#__PURE__*/_interopDefault(pc);
|
|
20
|
+
var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
|
|
21
|
+
|
|
22
|
+
var __defProp = Object.defineProperty;
|
|
23
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
24
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
25
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
26
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
27
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
28
|
+
}) : x)(function(x) {
|
|
29
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
30
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
31
|
+
});
|
|
32
|
+
var __esm = (fn, res) => function __init() {
|
|
33
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
34
|
+
};
|
|
35
|
+
var __export = (target, all) => {
|
|
36
|
+
for (var name in all)
|
|
37
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
38
|
+
};
|
|
39
|
+
var __copyProps = (to, from, except, desc) => {
|
|
40
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
41
|
+
for (let key of __getOwnPropNames(from))
|
|
42
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
43
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
44
|
+
}
|
|
45
|
+
return to;
|
|
46
|
+
};
|
|
47
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
48
|
+
|
|
49
|
+
// node_modules/.pnpm/tsup@8.5.1_postcss@8.5.15_tsx@4.22.4_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
|
|
50
|
+
var init_cjs_shims = __esm({
|
|
51
|
+
"node_modules/.pnpm/tsup@8.5.1_postcss@8.5.15_tsx@4.22.4_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"() {
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI_LIBS, OBFUSCATION_PRESETS, CDN_BASE_URL;
|
|
55
|
+
var init_constants = __esm({
|
|
56
|
+
"src/constants.ts"() {
|
|
57
|
+
init_cjs_shims();
|
|
58
|
+
RB_VERSION = "1.2.24";
|
|
59
|
+
RB_HOME = path.join(os.homedir(), ".rb");
|
|
60
|
+
path.join(RB_HOME, "bin");
|
|
61
|
+
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
62
|
+
IRONBREW2_REPO = "https://github.com/LorekeeperZinnia/IronBrew2.git";
|
|
63
|
+
ALWAYS_EXCLUDE = /* @__PURE__ */ new Set(["types.lua"]);
|
|
64
|
+
VALID_UI_LIBS = /* @__PURE__ */ new Set(["linoria", "windui"]);
|
|
65
|
+
OBFUSCATION_PRESETS = ["Low", "Medium", "Strong"];
|
|
66
|
+
CDN_BASE_URL = "https://cdn.pulse-rb.dev";
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// src/ui.ts
|
|
71
|
+
var ui_exports = {};
|
|
72
|
+
__export(ui_exports, {
|
|
73
|
+
bold: () => bold,
|
|
74
|
+
confirm: () => prompts.confirm,
|
|
75
|
+
cyan: () => cyan,
|
|
76
|
+
dim: () => dim,
|
|
77
|
+
gray: () => gray,
|
|
78
|
+
green: () => green,
|
|
79
|
+
isCancel: () => prompts.isCancel,
|
|
80
|
+
pDim: () => pDim,
|
|
81
|
+
pDone: () => pDone,
|
|
82
|
+
pFail: () => pFail,
|
|
83
|
+
pHeader: () => pHeader,
|
|
84
|
+
pHr: () => pHr,
|
|
85
|
+
pInfo: () => pInfo,
|
|
86
|
+
pKv: () => pKv,
|
|
87
|
+
pOk: () => pOk,
|
|
88
|
+
pSection: () => pSection,
|
|
89
|
+
pWarn: () => pWarn,
|
|
90
|
+
red: () => red,
|
|
91
|
+
select: () => prompts.select,
|
|
92
|
+
spinner: () => prompts.spinner,
|
|
93
|
+
text: () => prompts.text,
|
|
94
|
+
yellow: () => yellow
|
|
95
|
+
});
|
|
96
|
+
function pHeader(cmd) {
|
|
97
|
+
console.log(`
|
|
98
|
+
${cyan("\u25B6")} ${bold("rb " + cmd)}
|
|
99
|
+
`);
|
|
100
|
+
}
|
|
101
|
+
function pSection(title) {
|
|
102
|
+
console.log(`
|
|
103
|
+
${bold(title)}`);
|
|
104
|
+
}
|
|
105
|
+
function pOk(label, ...details) {
|
|
106
|
+
const tail = details.length ? ` ${gray(details.join(" \xB7 "))}` : "";
|
|
107
|
+
console.log(` ${green("\u2713")} ${label}${tail}`);
|
|
108
|
+
}
|
|
109
|
+
function pFail(msg, hint = "") {
|
|
110
|
+
console.log(` ${red("\u2717")} ${red(msg)}`);
|
|
111
|
+
if (hint) console.log(` ${dim(hint)}`);
|
|
112
|
+
}
|
|
113
|
+
function pInfo(msg) {
|
|
114
|
+
console.log(` ${cyan("\u2192")} ${msg}`);
|
|
115
|
+
}
|
|
116
|
+
function pWarn(msg) {
|
|
117
|
+
console.log(` ${yellow("!")} ${yellow(msg)}`);
|
|
118
|
+
}
|
|
119
|
+
function pDim(msg, indent = 7) {
|
|
120
|
+
console.log(`${" ".repeat(indent)}${dim(msg)}`);
|
|
121
|
+
}
|
|
122
|
+
function pKv(key, val, width = 12) {
|
|
123
|
+
console.log(` ${gray(key.padEnd(width))} ${val}`);
|
|
124
|
+
}
|
|
125
|
+
function pHr(width = 44) {
|
|
126
|
+
console.log(` ${dim("\u2500".repeat(width))}`);
|
|
127
|
+
}
|
|
128
|
+
function pDone(elapsed) {
|
|
129
|
+
console.log(`
|
|
130
|
+
${dim(`Done in ${elapsed.toFixed(1)}s`)}
|
|
131
|
+
`);
|
|
132
|
+
}
|
|
133
|
+
var isTTY, c, bold, dim, green, red, yellow, cyan, gray;
|
|
134
|
+
var init_ui = __esm({
|
|
135
|
+
"src/ui.ts"() {
|
|
136
|
+
init_cjs_shims();
|
|
137
|
+
isTTY = Boolean(process.stdout.isTTY);
|
|
138
|
+
c = (fn, t) => isTTY ? fn(t) : t;
|
|
139
|
+
bold = (t) => c(pc__default.default.bold, t);
|
|
140
|
+
dim = (t) => c(pc__default.default.dim, t);
|
|
141
|
+
green = (t) => c(pc__default.default.green, t);
|
|
142
|
+
red = (t) => c(pc__default.default.red, t);
|
|
143
|
+
yellow = (t) => c(pc__default.default.yellow, t);
|
|
144
|
+
cyan = (t) => c(pc__default.default.cyan, t);
|
|
145
|
+
gray = (t) => c(pc__default.default.gray, t);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
function findProjectRoot(start) {
|
|
149
|
+
let current = path.resolve(process.cwd());
|
|
150
|
+
while (true) {
|
|
151
|
+
if (fs.existsSync(path.join(current, "src"))) return current;
|
|
152
|
+
const parent = path.join(current, "..");
|
|
153
|
+
if (parent === current) return null;
|
|
154
|
+
current = parent;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function requireProjectRoot() {
|
|
158
|
+
const root = findProjectRoot();
|
|
159
|
+
if (!root) {
|
|
160
|
+
pFail("Not inside an rb project", "run from a directory containing src/");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
return root;
|
|
164
|
+
}
|
|
165
|
+
var init_project = __esm({
|
|
166
|
+
"src/project.ts"() {
|
|
167
|
+
init_cjs_shims();
|
|
168
|
+
init_ui();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/transpiler/helpers.ts
|
|
173
|
+
function extractBlock(text2, start) {
|
|
174
|
+
let depth = 1;
|
|
175
|
+
let i = start;
|
|
176
|
+
while (i < text2.length && depth > 0) {
|
|
177
|
+
if (text2[i] === "{") depth++;
|
|
178
|
+
else if (text2[i] === "}") depth--;
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
return [text2.slice(start, i - 1), i];
|
|
182
|
+
}
|
|
183
|
+
var EVENT_MAP, DEFAULT_DEBOUNCE, CHAR_REFS;
|
|
184
|
+
var init_helpers = __esm({
|
|
185
|
+
"src/transpiler/helpers.ts"() {
|
|
186
|
+
init_cjs_shims();
|
|
187
|
+
EVENT_MAP = {
|
|
188
|
+
Heartbeat: "_PulseRS.Heartbeat",
|
|
189
|
+
RenderStepped: "_PulseRS.RenderStepped",
|
|
190
|
+
Stepped: "_PulseRS.Stepped",
|
|
191
|
+
InputBegan: "_PulseUIS.InputBegan",
|
|
192
|
+
InputEnded: "_PulseUIS.InputEnded",
|
|
193
|
+
CharacterAdded: "_LocalPlayer.CharacterAdded",
|
|
194
|
+
CharacterRemoving: "_LocalPlayer.CharacterRemoving"
|
|
195
|
+
};
|
|
196
|
+
DEFAULT_DEBOUNCE = 0.2;
|
|
197
|
+
CHAR_REFS = {
|
|
198
|
+
hrp: "_PulseGetHRP()",
|
|
199
|
+
humanoid: "_PulseGetHumanoid()",
|
|
200
|
+
character: "_PulseGetChar()",
|
|
201
|
+
alive: "_PulseGetAlive()"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// src/transpiler/component.ts
|
|
207
|
+
function remotePathToChain(path) {
|
|
208
|
+
let chain = 'game:GetService("ReplicatedStorage")';
|
|
209
|
+
for (const part of path.split("/").map((p) => p.trim()).filter(Boolean)) {
|
|
210
|
+
chain += `:WaitForChild("${part}")`;
|
|
211
|
+
}
|
|
212
|
+
return chain;
|
|
213
|
+
}
|
|
214
|
+
function watchExprToLua(expr, charVar) {
|
|
215
|
+
const m = expr.trim().match(/^Character\.(\w+)$/);
|
|
216
|
+
if (m) return `${charVar} and ${charVar}:FindFirstChild("${m[1]}") or nil`;
|
|
217
|
+
return expr.trim();
|
|
218
|
+
}
|
|
219
|
+
function findComponentBlock(src) {
|
|
220
|
+
const m = src.match(/\bcomponent\s*\{/);
|
|
221
|
+
if (!m || m.index === void 0) return [null, -1, -1];
|
|
222
|
+
const [inner, end] = extractBlock(src, m.index + m[0].length);
|
|
223
|
+
return [inner, m.index + m[0].length, end];
|
|
224
|
+
}
|
|
225
|
+
function parseUiWidgets(body, name) {
|
|
226
|
+
const widgets = [];
|
|
227
|
+
for (const raw of body.split("\n")) {
|
|
228
|
+
const line = raw.trim();
|
|
229
|
+
if (!line || line.startsWith("--")) continue;
|
|
230
|
+
if (line === "separator") {
|
|
231
|
+
widgets.push({ type: "separator" });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
let m = line.match(/^label\s+"([^"]+)"$/);
|
|
235
|
+
if (m) {
|
|
236
|
+
widgets.push({ type: "label", label: m[1] });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
m = line.match(/^toggle\s+"([^"]+)"\s+->\s+(\w+)(?:\s+default=(\S+?))?(?:\s+tip="([^"]*)")?$/);
|
|
240
|
+
if (m) {
|
|
241
|
+
const w = { type: "toggle", id: `${name}_${m[2]}`, label: m[1], signal: m[2], tip: m[4] ?? "" };
|
|
242
|
+
if (m[3] !== void 0) w.default = m[3];
|
|
243
|
+
widgets.push(w);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
m = line.match(/^slider\s+"([^"]+)"\s+->\s+(\w+)\s+\[([^\],]+),\s*([^\]]+)\](?:\s+default=(\S+?))?(?:\s+tip="([^"]*)")?$/);
|
|
247
|
+
if (m) {
|
|
248
|
+
const w = { type: "slider", id: `${name}_${m[2]}`, label: m[1], signal: m[2], min: m[3].trim(), max: m[4].trim(), tip: m[6] ?? "" };
|
|
249
|
+
if (m[5] !== void 0) w.default = m[5];
|
|
250
|
+
widgets.push(w);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
m = line.match(/^dropdown\s+"([^"]+)"\s+->\s+(\w+)\s+(\[[^\]]+\])(?:\s+default=(\S+?))?(?:\s+tip="([^"]*)")?$/);
|
|
254
|
+
if (m) {
|
|
255
|
+
const w = { type: "dropdown", id: `${name}_${m[2]}`, label: m[1], signal: m[2], options: "{" + m[3].slice(1, -1) + "}", tip: m[5] ?? "" };
|
|
256
|
+
if (m[4] !== void 0) w.default = m[4];
|
|
257
|
+
widgets.push(w);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
m = line.match(/^multidropdown\s+"([^"]+)"\s+->\s+(\w+)\s+(\[[^\]]+\])(?:\s+tip="([^"]*)")?$/);
|
|
261
|
+
if (m) {
|
|
262
|
+
widgets.push({ type: "multidropdown", id: `${name}_${m[2]}`, label: m[1], signal: m[2], options: "{" + m[3].slice(1, -1) + "}", tip: m[4] ?? "" });
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
m = line.match(/^button\s+"([^"]+)"\s+->\s+(\w+)\(\)(?:\s+tip="([^"]*)")?$/);
|
|
266
|
+
if (m) {
|
|
267
|
+
widgets.push({ type: "button", label: m[1], func: m[2], tip: m[3] ?? "" });
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
m = line.match(/^keybind\s+"([^"]+)"\s+key="([^"]+)"\s+->\s+(\w+)\(\)$/);
|
|
271
|
+
if (m) {
|
|
272
|
+
widgets.push({ type: "keybind", id: `${name}_kb_${m[3]}`, label: m[1], key: m[2], func: m[3] });
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return widgets;
|
|
277
|
+
}
|
|
278
|
+
function parseModifiers(text2) {
|
|
279
|
+
const mods = {};
|
|
280
|
+
for (const m of text2.matchAll(/(when|every|debounce)\s+(\S+)/g)) {
|
|
281
|
+
mods[m[1]] = m[2];
|
|
282
|
+
}
|
|
283
|
+
return mods;
|
|
284
|
+
}
|
|
285
|
+
function escapeRe(s) {
|
|
286
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
287
|
+
}
|
|
288
|
+
var ComponentParser, ComponentGenerator;
|
|
289
|
+
var init_component = __esm({
|
|
290
|
+
"src/transpiler/component.ts"() {
|
|
291
|
+
init_cjs_shims();
|
|
292
|
+
init_helpers();
|
|
293
|
+
ComponentParser = class {
|
|
294
|
+
name;
|
|
295
|
+
inner;
|
|
296
|
+
signals = {};
|
|
297
|
+
computed = {};
|
|
298
|
+
shared = {};
|
|
299
|
+
configBody = null;
|
|
300
|
+
watches = {};
|
|
301
|
+
remotes = {};
|
|
302
|
+
invokes = {};
|
|
303
|
+
keybinds = [];
|
|
304
|
+
events = [];
|
|
305
|
+
localFuncs = [];
|
|
306
|
+
funcs = [];
|
|
307
|
+
handlers = [];
|
|
308
|
+
initBody = null;
|
|
309
|
+
respawnBody = null;
|
|
310
|
+
uiWidgets = [];
|
|
311
|
+
constructor(inner, name) {
|
|
312
|
+
this.inner = inner;
|
|
313
|
+
this.name = name;
|
|
314
|
+
}
|
|
315
|
+
parse() {
|
|
316
|
+
const text2 = this.inner;
|
|
317
|
+
let i = 0;
|
|
318
|
+
while (i < text2.length) {
|
|
319
|
+
if (" \r\n".includes(text2[i])) {
|
|
320
|
+
i++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
let m;
|
|
324
|
+
m = text2.slice(i).match(/^signal\s+(\w+)\s*=\s*/);
|
|
325
|
+
if (m) {
|
|
326
|
+
const nameI = i + m[0].length;
|
|
327
|
+
const endI = text2.indexOf("\n", nameI) === -1 ? text2.length : text2.indexOf("\n", nameI);
|
|
328
|
+
this.signals[m[1]] = text2.slice(nameI, endI).trim().replace(/,$/, "");
|
|
329
|
+
i = endI;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
m = text2.slice(i).match(/^computed\s+(\w+)\s*=\s*/);
|
|
333
|
+
if (m) {
|
|
334
|
+
const nameI = i + m[0].length;
|
|
335
|
+
const endI = text2.indexOf("\n", nameI) === -1 ? text2.length : text2.indexOf("\n", nameI);
|
|
336
|
+
this.computed[m[1]] = text2.slice(nameI, endI).trim().replace(/,$/, "");
|
|
337
|
+
i = endI;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
m = text2.slice(i).match(/^config\s*\{/);
|
|
341
|
+
if (m) {
|
|
342
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
343
|
+
this.configBody = body.trim();
|
|
344
|
+
i = end;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
m = text2.slice(i).match(/^watch\s*\{/);
|
|
348
|
+
if (m) {
|
|
349
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
350
|
+
for (const line of body.split("\n")) {
|
|
351
|
+
const wm = line.match(/^\s*(\w+)\s*=\s*(.+),?\s*$/);
|
|
352
|
+
if (wm) this.watches[wm[1]] = wm[2].trim().replace(/,$/, "");
|
|
353
|
+
}
|
|
354
|
+
i = end;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
m = text2.slice(i).match(/^shared\s*\{/);
|
|
358
|
+
if (m) {
|
|
359
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
360
|
+
for (const line of body.split("\n")) {
|
|
361
|
+
const sm = line.match(/^\s*(\w+)\s*=\s*(.+),?\s*$/);
|
|
362
|
+
if (sm) this.shared[sm[1]] = sm[2].trim().replace(/,$/, "");
|
|
363
|
+
}
|
|
364
|
+
i = end;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
m = text2.slice(i).match(/^invoke\s+(\w+)\s*=\s*["']([^"']+)["']/);
|
|
368
|
+
if (m) {
|
|
369
|
+
this.invokes[m[1]] = m[2];
|
|
370
|
+
i += m[0].length;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
m = text2.slice(i).match(/^remote\s+(\w+)\s*=\s*["']([^"']+)["']/);
|
|
374
|
+
if (m) {
|
|
375
|
+
this.remotes[m[1]] = m[2];
|
|
376
|
+
i += m[0].length;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
m = text2.slice(i).match(/^keybind\s+(\S+)\s*(?:→|->)\s*(\w+)/);
|
|
380
|
+
if (m) {
|
|
381
|
+
this.keybinds.push([m[1], m[2], "toggle"]);
|
|
382
|
+
i += m[0].length;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
m = text2.slice(i).match(/^event\s+(\w+)/);
|
|
386
|
+
if (m) {
|
|
387
|
+
this.events.push(m[1]);
|
|
388
|
+
i += m[0].length;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
m = text2.slice(i).match(/^local\s+func\s+(\w+)\s*\(([^)]*)\)\s*\{/);
|
|
392
|
+
if (m) {
|
|
393
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
394
|
+
this.localFuncs.push([m[1], m[2].trim(), body]);
|
|
395
|
+
i = end;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
m = text2.slice(i).match(/^func\s+(\w+)\s*\(([^)]*)\)\s*\{/);
|
|
399
|
+
if (m) {
|
|
400
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
401
|
+
this.funcs.push([m[1], m[2].trim(), body]);
|
|
402
|
+
i = end;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
m = text2.slice(i).match(/^ui\s*\{/);
|
|
406
|
+
if (m) {
|
|
407
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
408
|
+
this.uiWidgets = parseUiWidgets(body, this.name);
|
|
409
|
+
i = end;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
m = text2.slice(i).match(/^init\s*\{/);
|
|
413
|
+
if (m) {
|
|
414
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
415
|
+
this.initBody = body;
|
|
416
|
+
i = end;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
m = text2.slice(i).match(/^on\s+Respawn\s*\{/);
|
|
420
|
+
if (m) {
|
|
421
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
422
|
+
this.respawnBody = body;
|
|
423
|
+
i = end;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
m = text2.slice(i).match(/^on\s+(\w+)(?:\(([^)]*)\))?((?:\s+(?:when|every|debounce)\s+\S+)*)\s*\{/);
|
|
427
|
+
if (m) {
|
|
428
|
+
const mods = parseModifiers(m[3] ?? "");
|
|
429
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
430
|
+
this.handlers.push({ event: m[1], params: (m[2] ?? "").trim(), mods, body });
|
|
431
|
+
i = end;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
m = text2.slice(i).match(/^after\s+(\S+)\s*\{/);
|
|
435
|
+
if (m) {
|
|
436
|
+
const [body, end] = extractBlock(text2, i + m[0].length);
|
|
437
|
+
this.handlers.push({ event: "__after__", params: m[1], mods: {}, body });
|
|
438
|
+
i = end;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
i++;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
ComponentGenerator = class {
|
|
446
|
+
p;
|
|
447
|
+
name;
|
|
448
|
+
src;
|
|
449
|
+
constructor(parser, sourcePath) {
|
|
450
|
+
this.p = parser;
|
|
451
|
+
this.name = parser.name;
|
|
452
|
+
this.src = sourcePath;
|
|
453
|
+
}
|
|
454
|
+
generate() {
|
|
455
|
+
const p = this.p;
|
|
456
|
+
const name = this.name;
|
|
457
|
+
const lines = [];
|
|
458
|
+
lines.push(`-- [transpiled from ${this.src}]`);
|
|
459
|
+
lines.push(`${name} = Component("${name}")`);
|
|
460
|
+
for (const [sig, def] of Object.entries(p.signals)) {
|
|
461
|
+
lines.push(`${name}.${sig} = Signal(${def})`);
|
|
462
|
+
}
|
|
463
|
+
if ("enabled" in p.signals) {
|
|
464
|
+
lines.push(`${name}.onEnable = PulseEvent("${name}.onEnable")`);
|
|
465
|
+
lines.push(`${name}.onDisable = PulseEvent("${name}.onDisable")`);
|
|
466
|
+
}
|
|
467
|
+
for (const [cname, expr] of Object.entries(p.computed)) {
|
|
468
|
+
lines.push(`${name}.${cname} = Computed(function() return ${expr} end)`);
|
|
469
|
+
}
|
|
470
|
+
for (const ev of p.events) {
|
|
471
|
+
lines.push(`${name}.${ev} = PulseEvent("${name}.${ev}")`);
|
|
472
|
+
}
|
|
473
|
+
lines.push("");
|
|
474
|
+
if ("enabled" in p.signals) {
|
|
475
|
+
lines.push(`${name}.enabled:onChange(function(v)`);
|
|
476
|
+
lines.push(` if v then ${name}.onEnable:fire() else ${name}.onDisable:fire() end`);
|
|
477
|
+
lines.push("end)");
|
|
478
|
+
lines.push("");
|
|
479
|
+
}
|
|
480
|
+
lines.push(`local function _setup_${name}()`);
|
|
481
|
+
if (p.configBody) {
|
|
482
|
+
lines.push(" local cfg = {");
|
|
483
|
+
for (const ln of p.configBody.split("\n")) lines.push(" " + ln.trim());
|
|
484
|
+
lines.push(" }");
|
|
485
|
+
lines.push("");
|
|
486
|
+
}
|
|
487
|
+
const mirrorNames = {};
|
|
488
|
+
const watchNames = {};
|
|
489
|
+
const usedSignals = this.signalsUsedInHandlers();
|
|
490
|
+
for (const sig of Object.keys(p.signals)) {
|
|
491
|
+
if (usedSignals.has(sig)) {
|
|
492
|
+
const v = `_m_${sig}`;
|
|
493
|
+
mirrorNames[sig] = v;
|
|
494
|
+
lines.push(` local ${v} = ${p.signals[sig]}`);
|
|
495
|
+
lines.push(` ${name}:watch(${name}.${sig}, function(v) ${v} = v end)`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (Object.keys(mirrorNames).length) lines.push("");
|
|
499
|
+
for (const wname of Object.keys(p.watches)) {
|
|
500
|
+
watchNames[wname] = `_w_${wname}`;
|
|
501
|
+
lines.push(` local _w_${wname} = nil`);
|
|
502
|
+
}
|
|
503
|
+
if (Object.keys(p.watches).length) {
|
|
504
|
+
lines.push(" local function _refreshWatches()");
|
|
505
|
+
lines.push(' local _char = game:GetService("Players").LocalPlayer.Character');
|
|
506
|
+
for (const [wname, expr] of Object.entries(p.watches)) {
|
|
507
|
+
lines.push(` ${watchNames[wname]} = ${watchExprToLua(expr, "_char")}`);
|
|
508
|
+
}
|
|
509
|
+
lines.push(" end");
|
|
510
|
+
lines.push(" _refreshWatches()");
|
|
511
|
+
lines.push(` ${name}:onRespawn(_refreshWatches)`);
|
|
512
|
+
lines.push("");
|
|
513
|
+
}
|
|
514
|
+
for (const [rname, path] of Object.entries(p.remotes)) {
|
|
515
|
+
lines.push(` local _r_${rname} = ${remotePathToChain(path)}`);
|
|
516
|
+
lines.push(` local function ${rname}(...) _r_${rname}:FireServer(...) end`);
|
|
517
|
+
}
|
|
518
|
+
if (Object.keys(p.remotes).length) lines.push("");
|
|
519
|
+
for (const [iname, path] of Object.entries(p.invokes)) {
|
|
520
|
+
lines.push(` local _i_${iname} = ${remotePathToChain(path)}`);
|
|
521
|
+
lines.push(` local function ${iname}(...) return _i_${iname}:InvokeServer(...) end`);
|
|
522
|
+
}
|
|
523
|
+
if (Object.keys(p.invokes).length) lines.push("");
|
|
524
|
+
for (let idx = 0; idx < p.keybinds.length; idx++) {
|
|
525
|
+
const [keycode, sigName, action] = p.keybinds[idx];
|
|
526
|
+
const dbVar = `_kb_db_${idx}`;
|
|
527
|
+
const bindKey = `_keybind_${idx}`;
|
|
528
|
+
lines.push(` local ${dbVar} = false`);
|
|
529
|
+
lines.push(` ${name}:bind("${bindKey}", _PulseUIS.InputBegan:Connect(function(input, gpe)`);
|
|
530
|
+
lines.push(` if gpe then return end`);
|
|
531
|
+
lines.push(` if input.KeyCode ~= ${keycode} then return end`);
|
|
532
|
+
lines.push(` if ${dbVar} then return end`);
|
|
533
|
+
lines.push(` ${dbVar} = true`);
|
|
534
|
+
if (action === "toggle") lines.push(` ${name}.${sigName}(not ${name}.${sigName}())`);
|
|
535
|
+
lines.push(` task.delay(${DEFAULT_DEBOUNCE}, function() ${dbVar} = false end)`);
|
|
536
|
+
lines.push(" end))");
|
|
537
|
+
lines.push("");
|
|
538
|
+
}
|
|
539
|
+
if (p.initBody) {
|
|
540
|
+
lines.push(" -- init");
|
|
541
|
+
for (const ln of p.initBody.split("\n")) lines.push(" " + ln.trimEnd());
|
|
542
|
+
lines.push("");
|
|
543
|
+
}
|
|
544
|
+
for (const [lfname, lfparams, lfbody] of p.localFuncs) {
|
|
545
|
+
lines.push(` local function ${lfname}(${lfparams})`);
|
|
546
|
+
for (const ln of this.rewriteBody(lfbody, mirrorNames, watchNames).split("\n")) {
|
|
547
|
+
lines.push(" " + ln.trimEnd());
|
|
548
|
+
}
|
|
549
|
+
lines.push(" end");
|
|
550
|
+
lines.push("");
|
|
551
|
+
}
|
|
552
|
+
const funcMirrors = {};
|
|
553
|
+
for (const sig of Object.keys(p.signals)) funcMirrors[sig] = `${name}.${sig}()`;
|
|
554
|
+
for (const [fname, fparams, fbody] of p.funcs) {
|
|
555
|
+
const paramStr = fparams ? `, ${fparams}` : "";
|
|
556
|
+
lines.push(` ${name}.${fname} = function(self${paramStr})`);
|
|
557
|
+
for (const ln of this.rewriteBody(fbody, funcMirrors, watchNames).split("\n")) {
|
|
558
|
+
lines.push(" " + ln.trimEnd());
|
|
559
|
+
}
|
|
560
|
+
lines.push(" end");
|
|
561
|
+
lines.push("");
|
|
562
|
+
}
|
|
563
|
+
if ("enabled" in p.signals && !p.funcs.some(([fn]) => fn === "Toggle")) {
|
|
564
|
+
lines.push(` ${name}.Toggle = function(self)`);
|
|
565
|
+
lines.push(` ${name}.enabled(not ${name}.enabled())`);
|
|
566
|
+
lines.push(` end`);
|
|
567
|
+
lines.push("");
|
|
568
|
+
}
|
|
569
|
+
for (const { event, params, mods, body } of p.handlers) {
|
|
570
|
+
if (event === "__after__") {
|
|
571
|
+
lines.push(` ${name}:task(${params}, function()`);
|
|
572
|
+
for (const ln of this.rewriteBody(body, mirrorNames, watchNames).split("\n")) {
|
|
573
|
+
lines.push(" " + ln.trimEnd());
|
|
574
|
+
}
|
|
575
|
+
lines.push(" end)");
|
|
576
|
+
lines.push("");
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (event === "Respawn") continue;
|
|
580
|
+
if (event in p.signals) {
|
|
581
|
+
lines.push(` ${name}:watch(${name}.${event}, function(v)`);
|
|
582
|
+
for (const ln of this.rewriteBody(body, mirrorNames, watchNames).split("\n")) {
|
|
583
|
+
lines.push(" " + ln.trimEnd());
|
|
584
|
+
}
|
|
585
|
+
lines.push(" end)");
|
|
586
|
+
lines.push("");
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const serviceEvent = EVENT_MAP[event];
|
|
590
|
+
if (!serviceEvent) {
|
|
591
|
+
lines.push(` -- [Pulse] WARNING: unknown event '${event}' \u2014 skipped`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
const bindKey = `on${event}`;
|
|
595
|
+
const paramStr = params || "_";
|
|
596
|
+
const debounce = mods["debounce"];
|
|
597
|
+
const when = mods["when"];
|
|
598
|
+
const everyVal = mods["every"];
|
|
599
|
+
const everyThreshold = everyVal && everyVal in p.signals ? mirrorNames[everyVal] ?? `${name}.${everyVal}()` : everyVal;
|
|
600
|
+
if (debounce) lines.push(` local _db_${event} = false`);
|
|
601
|
+
if (everyVal) lines.push(` local _thr_${event} = 0`);
|
|
602
|
+
lines.push(` ${name}:bind("${bindKey}", ${serviceEvent}:Connect(function(${paramStr})`);
|
|
603
|
+
if (debounce) {
|
|
604
|
+
lines.push(` if _db_${event} then return end`);
|
|
605
|
+
lines.push(` _db_${event} = true`);
|
|
606
|
+
}
|
|
607
|
+
if (when) {
|
|
608
|
+
const whenVar = mirrorNames[when] ?? when;
|
|
609
|
+
lines.push(` if not ${whenVar} then return end`);
|
|
610
|
+
}
|
|
611
|
+
if (everyVal) {
|
|
612
|
+
lines.push(` local _now = tick()`);
|
|
613
|
+
lines.push(` if (_now - _thr_${event}) < ${everyThreshold} then return end`);
|
|
614
|
+
lines.push(` _thr_${event} = _now`);
|
|
615
|
+
}
|
|
616
|
+
lines.push(` local _ok, _err = pcall(function()`);
|
|
617
|
+
for (const ln of this.rewriteBody(body, mirrorNames, watchNames).split("\n")) {
|
|
618
|
+
lines.push(" " + ln.trimEnd());
|
|
619
|
+
}
|
|
620
|
+
lines.push(` end)`);
|
|
621
|
+
lines.push(` if not _ok then Pulse.Log.error("${name}", "${event} error", { err = tostring(_err) }) end`);
|
|
622
|
+
if (debounce) lines.push(` task.delay(${debounce}, function() _db_${event} = false end)`);
|
|
623
|
+
lines.push(" end))");
|
|
624
|
+
lines.push("");
|
|
625
|
+
}
|
|
626
|
+
if (p.respawnBody) {
|
|
627
|
+
lines.push(` ${name}:onRespawn(function()`);
|
|
628
|
+
for (const ln of this.rewriteBody(p.respawnBody, mirrorNames, watchNames).split("\n")) {
|
|
629
|
+
lines.push(" " + ln.trimEnd());
|
|
630
|
+
}
|
|
631
|
+
lines.push(" end)");
|
|
632
|
+
lines.push("");
|
|
633
|
+
}
|
|
634
|
+
lines.push("end");
|
|
635
|
+
for (const [key, def] of Object.entries(p.shared)) {
|
|
636
|
+
lines.push(`Pulse.Store.define("${key}", ${def})`);
|
|
637
|
+
}
|
|
638
|
+
if (Object.keys(p.shared).length) lines.push("");
|
|
639
|
+
lines.push(`_setup_${name}()`);
|
|
640
|
+
lines.push("");
|
|
641
|
+
const uiLines = this.generateUiTable();
|
|
642
|
+
if (uiLines.length) {
|
|
643
|
+
lines.push(...uiLines);
|
|
644
|
+
lines.push("");
|
|
645
|
+
}
|
|
646
|
+
const defaultLines = this.generateDefaultsBlock();
|
|
647
|
+
if (defaultLines.length) {
|
|
648
|
+
lines.push(...defaultLines);
|
|
649
|
+
lines.push("");
|
|
650
|
+
}
|
|
651
|
+
return lines.join("\n");
|
|
652
|
+
}
|
|
653
|
+
generateUiTable() {
|
|
654
|
+
const { uiWidgets: ws } = this.p;
|
|
655
|
+
if (!ws.length) return [];
|
|
656
|
+
const name = this.name;
|
|
657
|
+
const lines = [`${name}._ui = {`];
|
|
658
|
+
for (const w of ws) {
|
|
659
|
+
switch (w.type) {
|
|
660
|
+
case "separator":
|
|
661
|
+
lines.push(' { type = "separator" },');
|
|
662
|
+
break;
|
|
663
|
+
case "label":
|
|
664
|
+
lines.push(` { type = "label", label = "${w.label}" },`);
|
|
665
|
+
break;
|
|
666
|
+
case "toggle":
|
|
667
|
+
lines.push(` { type = "toggle", id = "${w.id}", label = "${w.label}", signal = "${w.signal}", tip = "${w.tip}" },`);
|
|
668
|
+
break;
|
|
669
|
+
case "slider":
|
|
670
|
+
lines.push(` { type = "slider", id = "${w.id}", label = "${w.label}", signal = "${w.signal}", min = ${w.min}, max = ${w.max}, tip = "${w.tip}" },`);
|
|
671
|
+
break;
|
|
672
|
+
case "dropdown":
|
|
673
|
+
lines.push(` { type = "dropdown", id = "${w.id}", label = "${w.label}", signal = "${w.signal}", options = ${w.options}, tip = "${w.tip}" },`);
|
|
674
|
+
break;
|
|
675
|
+
case "multidropdown":
|
|
676
|
+
lines.push(` { type = "multidropdown", id = "${w.id}", label = "${w.label}", signal = "${w.signal}", options = ${w.options}, tip = "${w.tip}" },`);
|
|
677
|
+
break;
|
|
678
|
+
case "button":
|
|
679
|
+
lines.push(` { type = "button", label = "${w.label}", action = function() ${name}:${w.func}() end, tip = "${w.tip}" },`);
|
|
680
|
+
break;
|
|
681
|
+
case "keybind":
|
|
682
|
+
lines.push(` { type = "keybind", id = "${w.id}", label = "${w.label}", key = "${w.key}", action = function() ${name}:${w.func}() end },`);
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
lines.push("}");
|
|
687
|
+
return lines;
|
|
688
|
+
}
|
|
689
|
+
generateDefaultsBlock() {
|
|
690
|
+
const lines = [];
|
|
691
|
+
for (const w of this.p.uiWidgets) {
|
|
692
|
+
if (!("default" in w) || !["toggle", "slider", "dropdown"].includes(w.type)) continue;
|
|
693
|
+
const kind = w.type === "toggle" ? "toggle" : "option";
|
|
694
|
+
lines.push(
|
|
695
|
+
`_PULSE_DEFAULTS[#_PULSE_DEFAULTS+1] = { type="${kind}", id="${w.id}", value=${w.default}, set=function(v) ${this.name}.${w.signal}(v) end }`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return lines;
|
|
699
|
+
}
|
|
700
|
+
// ── Body rewriting ──────────────────────────────────────────────────────────
|
|
701
|
+
signalsUsedInHandlers() {
|
|
702
|
+
const used = /* @__PURE__ */ new Set();
|
|
703
|
+
for (const { mods } of this.p.handlers) {
|
|
704
|
+
for (const key of ["when", "every"]) {
|
|
705
|
+
const val = mods[key];
|
|
706
|
+
if (val && val in this.p.signals) used.add(val);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const allBodies = [
|
|
710
|
+
...this.p.handlers.map((h) => h.body),
|
|
711
|
+
this.p.initBody ?? "",
|
|
712
|
+
this.p.respawnBody ?? "",
|
|
713
|
+
...this.p.localFuncs.map(([, , b]) => b)
|
|
714
|
+
].join("\n");
|
|
715
|
+
for (const sig of Object.keys(this.p.signals)) {
|
|
716
|
+
if (new RegExp(`\\b${escapeRe(sig)}\\b`).test(allBodies)) used.add(sig);
|
|
717
|
+
}
|
|
718
|
+
return used;
|
|
719
|
+
}
|
|
720
|
+
rewriteBody(body, mirrors, watches) {
|
|
721
|
+
return body.split("\n").map((ln) => this.rewriteLine(ln, mirrors, watches)).join("\n");
|
|
722
|
+
}
|
|
723
|
+
rewriteLine(line, mirrors, watches) {
|
|
724
|
+
const stripped = line.trimStart();
|
|
725
|
+
const indent = line.slice(0, line.length - stripped.length);
|
|
726
|
+
let m = stripped.match(/^guard\s+(\w+)\s*=\s*(.+)$/);
|
|
727
|
+
if (m) {
|
|
728
|
+
const expr = this.rewriteExpr(m[2].trim(), mirrors, watches);
|
|
729
|
+
return `${indent}local ${m[1]} = ${expr}
|
|
730
|
+
${indent}if not ${m[1]} then return end`;
|
|
731
|
+
}
|
|
732
|
+
m = stripped.match(/^guard\s+(.+)$/);
|
|
733
|
+
if (m) {
|
|
734
|
+
const expr = this.rewriteExpr(m[1].trim(), mirrors, watches);
|
|
735
|
+
return /^\w+$/.test(expr) ? `${indent}if not ${expr} then return end` : `${indent}if not (${expr}) then return end`;
|
|
736
|
+
}
|
|
737
|
+
return this.rewriteExpr(line, mirrors, watches);
|
|
738
|
+
}
|
|
739
|
+
rewriteExpr(text2, mirrors, watches) {
|
|
740
|
+
text2 = text2.replace(/\bself\b/g, this.name);
|
|
741
|
+
for (const [ref, replacement] of Object.entries(CHAR_REFS)) {
|
|
742
|
+
text2 = text2.replace(new RegExp(`\\b${escapeRe(ref)}\\b`, "g"), replacement);
|
|
743
|
+
}
|
|
744
|
+
for (const [wname, wvar] of Object.entries(watches)) {
|
|
745
|
+
text2 = text2.replace(new RegExp(`\\b${escapeRe(wname)}\\b`, "g"), wvar);
|
|
746
|
+
}
|
|
747
|
+
for (const [sig, mvar] of Object.entries(mirrors)) {
|
|
748
|
+
text2 = text2.replace(new RegExp(`(?<!\\.)\\b${escapeRe(sig)}\\s*\\(`, "g"), `${this.name}.${sig}(`);
|
|
749
|
+
text2 = text2.replace(new RegExp(`(?<!\\.)\\b${escapeRe(sig)}\\b(?!\\s*\\()`, "g"), mvar);
|
|
750
|
+
}
|
|
751
|
+
return text2;
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// src/transpiler/layout.ts
|
|
758
|
+
function findLayoutBlock(src) {
|
|
759
|
+
const m = src.match(/\blayout\s*\{/);
|
|
760
|
+
if (!m || m.index === void 0) return [null, -1, -1];
|
|
761
|
+
const [inner, end] = extractBlock(src, m.index + m[0].length);
|
|
762
|
+
return [inner, m.index + m[0].length, end];
|
|
763
|
+
}
|
|
764
|
+
function parseLayoutOptions(body) {
|
|
765
|
+
const opts = {
|
|
766
|
+
title: "Script Hub",
|
|
767
|
+
toggle_key: "RightControl",
|
|
768
|
+
size: [950, 600],
|
|
769
|
+
compat_exclude: [],
|
|
770
|
+
ui_library: "linoria",
|
|
771
|
+
theme: "Dark",
|
|
772
|
+
folder: "Hub",
|
|
773
|
+
notify_title: "Hub",
|
|
774
|
+
icon: "",
|
|
775
|
+
author: "",
|
|
776
|
+
transparency: 0.8,
|
|
777
|
+
acrylic: false,
|
|
778
|
+
themes: [],
|
|
779
|
+
open_button_mobile_only: true,
|
|
780
|
+
open_button_icon: ""
|
|
781
|
+
};
|
|
782
|
+
const compatM = body.match(/compat_exclude\s*=\s*\{([^}]*)\}/s);
|
|
783
|
+
if (compatM) opts.compat_exclude = [...compatM[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
784
|
+
const themesM = body.match(/\bthemes\s*=\s*\{/);
|
|
785
|
+
if (themesM) {
|
|
786
|
+
const [themesInner] = extractBlock(body, themesM.index + themesM[0].length);
|
|
787
|
+
const themesCode = themesInner.split("\n").filter((ln) => !ln.trim().startsWith("--")).join("\n");
|
|
788
|
+
let pos = 0;
|
|
789
|
+
while (pos < themesCode.length) {
|
|
790
|
+
const eM = themesCode.slice(pos).match(/\{/);
|
|
791
|
+
if (!eM) break;
|
|
792
|
+
const entryStart = pos + eM.index + 1;
|
|
793
|
+
const [entryInner, entryEnd] = extractBlock(themesCode, entryStart);
|
|
794
|
+
const tData = {};
|
|
795
|
+
for (let raw of entryInner.split("\n")) {
|
|
796
|
+
let ln = raw.trim().replace(/,$/, "");
|
|
797
|
+
const ci = ln.indexOf("--");
|
|
798
|
+
if (ci >= 0) ln = ln.slice(0, ci).trim();
|
|
799
|
+
const kv = ln.match(/^(\w+)\s*=\s*"([^"]*)"/);
|
|
800
|
+
if (kv) tData[kv[1]] = kv[2];
|
|
801
|
+
}
|
|
802
|
+
if (tData["name"]) opts.themes.push(tData);
|
|
803
|
+
pos = entryEnd;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
for (let raw of body.split("\n")) {
|
|
807
|
+
let line = raw.trim();
|
|
808
|
+
const ci = line.indexOf("--");
|
|
809
|
+
if (ci >= 0) line = line.slice(0, ci).trim();
|
|
810
|
+
line = line.replace(/,$/, "");
|
|
811
|
+
if (!line) continue;
|
|
812
|
+
const m = line.match(/^(\w+)\s*=\s*(.+)$/);
|
|
813
|
+
if (!m) continue;
|
|
814
|
+
const [, key, val] = m;
|
|
815
|
+
switch (key) {
|
|
816
|
+
case "title": {
|
|
817
|
+
const stripped = val.replace(/^["']|["']$/g, "");
|
|
818
|
+
if (stripped !== val) opts.title = stripped;
|
|
819
|
+
else opts.title_expr = val;
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case "toggle_key":
|
|
823
|
+
opts.toggle_key = val.replace(/^["']|["']$/g, "");
|
|
824
|
+
break;
|
|
825
|
+
case "size": {
|
|
826
|
+
const nums = [...val.matchAll(/\d+/g)].map((m2) => parseInt(m2[0]));
|
|
827
|
+
if (nums.length >= 2) opts.size = [nums[0], nums[1]];
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
case "ui_library": {
|
|
831
|
+
const lib = val.replace(/^["']|["']$/g, "");
|
|
832
|
+
if (lib === "linoria" || lib === "windui") opts.ui_library = lib;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case "theme":
|
|
836
|
+
opts.theme = val.replace(/^["']|["']$/g, "");
|
|
837
|
+
break;
|
|
838
|
+
case "folder":
|
|
839
|
+
opts.folder = val.replace(/^["']|["']$/g, "");
|
|
840
|
+
break;
|
|
841
|
+
case "notify_title":
|
|
842
|
+
opts.notify_title = val.replace(/^["']|["']$/g, "");
|
|
843
|
+
break;
|
|
844
|
+
case "icon":
|
|
845
|
+
opts.icon = val.replace(/^["']|["']$/g, "");
|
|
846
|
+
break;
|
|
847
|
+
case "author":
|
|
848
|
+
opts.author = val.replace(/^["']|["']$/g, "");
|
|
849
|
+
break;
|
|
850
|
+
case "transparency": {
|
|
851
|
+
const n = parseFloat(val);
|
|
852
|
+
if (!isNaN(n)) opts.transparency = n;
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
case "acrylic":
|
|
856
|
+
opts.acrylic = val.trim().toLowerCase() === "true";
|
|
857
|
+
break;
|
|
858
|
+
case "open_button_mobile_only":
|
|
859
|
+
opts.open_button_mobile_only = val.trim().toLowerCase() !== "false";
|
|
860
|
+
break;
|
|
861
|
+
case "open_button_icon":
|
|
862
|
+
opts.open_button_icon = val.replace(/^["']|["']$/g, "");
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return opts;
|
|
867
|
+
}
|
|
868
|
+
function transpileLayout(source, filePath) {
|
|
869
|
+
const [inner] = findLayoutBlock(source);
|
|
870
|
+
if (inner === null) throw new TranspileError(`${filePath}: no 'layout {}' block found.`);
|
|
871
|
+
const opts = parseLayoutOptions(inner);
|
|
872
|
+
const { toggle_key, folder, notify_title, icon, author, transparency, themes } = opts;
|
|
873
|
+
const [w, h] = opts.size;
|
|
874
|
+
const acrylic = opts.acrylic ? "true" : "false";
|
|
875
|
+
const openMobileOnly = opts.open_button_mobile_only ? "true" : "false";
|
|
876
|
+
const titleCode = opts.title_expr ?? `"${opts.title}"`;
|
|
877
|
+
const L = [];
|
|
878
|
+
L.push(`-- [transpiled from ${filePath}]`);
|
|
879
|
+
L.push(`local _PULSE_FOLDER = "${folder}"`);
|
|
880
|
+
L.push(`local _PULSE_NOTIFY_TITLE = "${notify_title}"`);
|
|
881
|
+
L.push(`local _PULSE_TOGGLE_KEY = "${toggle_key}"`);
|
|
882
|
+
L.push(`local _PULSE_DEFAULT_THEME = "${opts.theme}"`);
|
|
883
|
+
L.push(`local _PULSE_ICON = "${icon}"`);
|
|
884
|
+
L.push(`local _PULSE_AUTHOR = "${author}"`);
|
|
885
|
+
L.push(`local _PULSE_TRANSPARENCY = ${transparency}`);
|
|
886
|
+
L.push(`local _PULSE_ACRYLIC = ${acrylic}`);
|
|
887
|
+
L.push(`local _PULSE_OPEN_BTN_MOBILE_ONLY = ${openMobileOnly}`);
|
|
888
|
+
L.push(`local _PULSE_OPEN_BTN_ICON = "${opts.open_button_icon}"`);
|
|
889
|
+
for (const t of themes) {
|
|
890
|
+
const pairs = [` name = "${t["name"]}"`];
|
|
891
|
+
for (const fld of THEME_FIELDS) {
|
|
892
|
+
if (t[fld]) pairs.push(` ${fld} = "${t[fld]}"`);
|
|
893
|
+
}
|
|
894
|
+
L.push("_UIAdapter:RegisterTheme({");
|
|
895
|
+
L.push(pairs.join(",\n"));
|
|
896
|
+
L.push("})");
|
|
897
|
+
}
|
|
898
|
+
L.push(`local _Window = _UIAdapter:CreateWindow(${titleCode}, ${w}, ${h}, {`);
|
|
899
|
+
L.push(` theme = _PULSE_DEFAULT_THEME,`);
|
|
900
|
+
L.push(` folder = _PULSE_FOLDER,`);
|
|
901
|
+
L.push(` notify_title = _PULSE_NOTIFY_TITLE,`);
|
|
902
|
+
L.push(` icon = _PULSE_ICON,`);
|
|
903
|
+
L.push(` author = _PULSE_AUTHOR,`);
|
|
904
|
+
L.push(` transparency = _PULSE_TRANSPARENCY,`);
|
|
905
|
+
L.push(` acrylic = _PULSE_ACRYLIC,`);
|
|
906
|
+
L.push(` open_btn_mobile_only = _PULSE_OPEN_BTN_MOBILE_ONLY,`);
|
|
907
|
+
L.push(` open_btn_icon = _PULSE_OPEN_BTN_ICON,`);
|
|
908
|
+
L.push(`})`);
|
|
909
|
+
L.push(`local _Tabs = {}`);
|
|
910
|
+
L.push(`local _TabGroups = {}`);
|
|
911
|
+
L.push(``);
|
|
912
|
+
L.push(`local function _GetTab(name, icon)`);
|
|
913
|
+
L.push(` if not _Tabs[name] then`);
|
|
914
|
+
L.push(` _Tabs[name] = _Window:AddTab(name, icon)`);
|
|
915
|
+
L.push(` end`);
|
|
916
|
+
L.push(` return _Tabs[name]`);
|
|
917
|
+
L.push(`end`);
|
|
918
|
+
L.push(``);
|
|
919
|
+
L.push(`local function _GetGroupbox(tab, side, title_, icon)`);
|
|
920
|
+
L.push(` local key = tab .. "|" .. side .. "|" .. title_`);
|
|
921
|
+
L.push(` if not _TabGroups[key] then`);
|
|
922
|
+
L.push(` local t = _GetTab(tab)`);
|
|
923
|
+
L.push(` if side == "left" then`);
|
|
924
|
+
L.push(` _TabGroups[key] = t:AddLeftGroupbox(title_, icon)`);
|
|
925
|
+
L.push(` else`);
|
|
926
|
+
L.push(` _TabGroups[key] = t:AddRightGroupbox(title_, icon)`);
|
|
927
|
+
L.push(` end`);
|
|
928
|
+
L.push(` end`);
|
|
929
|
+
L.push(` return _TabGroups[key]`);
|
|
930
|
+
L.push(`end`);
|
|
931
|
+
L.push(``);
|
|
932
|
+
L.push(`_UIAdapter:SetToggleKey(Enum.KeyCode.${toggle_key})`);
|
|
933
|
+
return L.join("\n");
|
|
934
|
+
}
|
|
935
|
+
var THEME_FIELDS;
|
|
936
|
+
var init_layout = __esm({
|
|
937
|
+
"src/transpiler/layout.ts"() {
|
|
938
|
+
init_cjs_shims();
|
|
939
|
+
init_helpers();
|
|
940
|
+
init_transpiler();
|
|
941
|
+
THEME_FIELDS = ["accent", "background", "outline", "text", "placeholder", "button", "icon"];
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// src/transpiler/page.ts
|
|
946
|
+
function findPageBlock(src) {
|
|
947
|
+
let m = src.match(/\bpage\s+"([^"]+)"\s+icon="([^"]+)"\s*\{/);
|
|
948
|
+
if (m && m.index !== void 0) {
|
|
949
|
+
const [inner, end] = extractBlock(src, m.index + m[0].length);
|
|
950
|
+
return [m[1], m[2], inner, m.index + m[0].length, end];
|
|
951
|
+
}
|
|
952
|
+
m = src.match(/\bpage\s+"([^"]+)"\s*\{/);
|
|
953
|
+
if (m && m.index !== void 0) {
|
|
954
|
+
const [inner, end] = extractBlock(src, m.index + m[0].length);
|
|
955
|
+
return [m[1], null, inner, m.index + m[0].length, end];
|
|
956
|
+
}
|
|
957
|
+
return [null, null, null, -1, -1];
|
|
958
|
+
}
|
|
959
|
+
function transpilePage(source, filePath) {
|
|
960
|
+
const [tabName, tabIcon, inner] = findPageBlock(source);
|
|
961
|
+
if (inner === null) throw new TranspileError(`${filePath}: no 'page "Name" icon="..." {}' block found.`);
|
|
962
|
+
if (tabIcon === null) {
|
|
963
|
+
throw new TranspileError(
|
|
964
|
+
`${filePath}: page "${tabName}" is missing a tab icon.
|
|
965
|
+
Fix: page "${tabName}" icon="house" { ... }
|
|
966
|
+
Use any Lucide icon name (e.g. "house", "swords", "pickaxe", "coins", "map-pin")`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
const L = [`-- [transpiled from ${filePath}]`, "do"];
|
|
970
|
+
L.push(...transpilePageBody(tabName, tabIcon, inner, filePath));
|
|
971
|
+
L.push("end");
|
|
972
|
+
return L.join("\n");
|
|
973
|
+
}
|
|
974
|
+
function transpilePageBody(tabName, tabIcon, body, filePath) {
|
|
975
|
+
const lines = [];
|
|
976
|
+
lines.push(` _GetTab("${tabName}", "${tabIcon}")`);
|
|
977
|
+
let i = 0;
|
|
978
|
+
while (i < body.length) {
|
|
979
|
+
if (" \r\n".includes(body[i])) {
|
|
980
|
+
i++;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
let m = body.slice(i).match(/^groupbox\s+(left|right)\s+"([^"]+)"\s+icon="([^"]+)"\s*\{/);
|
|
984
|
+
if (m) {
|
|
985
|
+
const [side, title, icon] = [m[1], m[2], m[3]];
|
|
986
|
+
const gbVar = "_gb_" + title.replace(/[^a-zA-Z0-9]/g, "_");
|
|
987
|
+
const [gbBody, end] = extractBlock(body, i + m[0].length);
|
|
988
|
+
lines.push(` local ${gbVar} = _GetGroupbox("${tabName}", "${side}", "${title}", "${icon}")`);
|
|
989
|
+
lines.push(...transpileGroupboxBody(gbBody, gbVar, " "));
|
|
990
|
+
i = end;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
m = body.slice(i).match(/^groupbox\s+(left|right)\s+"([^"]+)"\s*\{/);
|
|
994
|
+
if (m) {
|
|
995
|
+
throw new TranspileError(
|
|
996
|
+
`${filePath}: groupbox "${m[2]}" in page "${tabName}" is missing an icon.
|
|
997
|
+
Fix: groupbox ${m[1]} "${m[2]}" icon="eye" { ... }
|
|
998
|
+
Use any Lucide icon name (e.g. "eye", "crosshair", "pickaxe", "hammer", "coins")`
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
i++;
|
|
1002
|
+
}
|
|
1003
|
+
return lines;
|
|
1004
|
+
}
|
|
1005
|
+
function transpileGroupboxBody(body, gbVar, indent) {
|
|
1006
|
+
const lines = [];
|
|
1007
|
+
for (const raw of body.split("\n")) {
|
|
1008
|
+
const line = raw.trim();
|
|
1009
|
+
if (!line || line.startsWith("--")) continue;
|
|
1010
|
+
let m = line.match(/^mount\s+(\w+)$/);
|
|
1011
|
+
if (m) {
|
|
1012
|
+
lines.push(`${indent}_UIAdapter:mount(${m[1]}, ${gbVar})`);
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (line === "separator") {
|
|
1016
|
+
lines.push(`${indent}_UIAdapter:addSeparator(${gbVar})`);
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
m = line.match(/^label\s+"([^"]+)"$/);
|
|
1020
|
+
if (m) {
|
|
1021
|
+
lines.push(`${indent}_UIAdapter:addLabel(${gbVar}, "${m[1]}")`);
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
m = line.match(/^paragraph\s+"([^"]+)"\s+"([^"]*)"$/);
|
|
1025
|
+
if (m) {
|
|
1026
|
+
lines.push(`${indent}_UIAdapter:addParagraph(${gbVar}, "${m[1]}", "${m[2]}")`);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
m = line.match(/^toggle\s+"([^"]+)"\s+->\s+(\w+)\.(\w+)(?:\s+tip="([^"]*)")?$/);
|
|
1030
|
+
if (m) {
|
|
1031
|
+
lines.push(`${indent}_UIAdapter:addToggle(${gbVar}, "${m[2]}_${m[3]}", { label = "${m[1]}", signal = ${m[2]}.${m[3]}, tip = "${m[4] ?? ""}" })`);
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
m = line.match(/^slider\s+"([^"]+)"\s+->\s+(\w+)\.(\w+)\s+\[([^\],]+),\s*([^\]]+)\](?:\s+tip="([^"]*)")?$/);
|
|
1035
|
+
if (m) {
|
|
1036
|
+
lines.push(`${indent}_UIAdapter:addSlider(${gbVar}, "${m[2]}_${m[3]}", { label = "${m[1]}", signal = ${m[2]}.${m[3]}, min = ${m[4].trim()}, max = ${m[5].trim()}, tip = "${m[6] ?? ""}" })`);
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
m = line.match(/^dropdown\s+"([^"]+)"\s+->\s+(\w+)\.(\w+)\s+(\[[^\]]+\])(?:\s+tip="([^"]*)")?$/);
|
|
1040
|
+
if (m) {
|
|
1041
|
+
const opts = "{" + m[4].slice(1, -1) + "}";
|
|
1042
|
+
lines.push(`${indent}_UIAdapter:addDropdown(${gbVar}, "${m[2]}_${m[3]}", { label = "${m[1]}", signal = ${m[2]}.${m[3]}, options = ${opts}, tip = "${m[5] ?? ""}" })`);
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
m = line.match(/^button\s+"([^"]+)"\s+->\s+(\w+):(\w+)\(\)(?:\s+tip="([^"]*)")?$/);
|
|
1046
|
+
if (m) {
|
|
1047
|
+
lines.push(`${indent}_UIAdapter:addButton(${gbVar}, { label = "${m[1]}", action = function() ${m[2]}:${m[3]}() end, tip = "${m[4] ?? ""}" })`);
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
m = line.match(/^keybind\s+"([^"]+)"\s+key="([^"]+)"\s+->\s+(\w+):(\w+)\(\)$/);
|
|
1051
|
+
if (m) {
|
|
1052
|
+
lines.push(`${indent}_UIAdapter:addKeybind(${gbVar}, "${m[3]}_${m[4]}_kb", { label = "${m[1]}", key = "${m[2]}", action = function() ${m[3]}:${m[4]}() end })`);
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return lines;
|
|
1057
|
+
}
|
|
1058
|
+
var init_page = __esm({
|
|
1059
|
+
"src/transpiler/page.ts"() {
|
|
1060
|
+
init_cjs_shims();
|
|
1061
|
+
init_helpers();
|
|
1062
|
+
init_transpiler();
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
function transpile(source, filePath) {
|
|
1066
|
+
if (/\bpage\s+"[^"]+"/.test(source)) return transpilePage(source, filePath);
|
|
1067
|
+
if (/\blayout\s*\{/.test(source)) return transpileLayout(source, filePath);
|
|
1068
|
+
const [inner] = findComponentBlock(source);
|
|
1069
|
+
if (inner === null) {
|
|
1070
|
+
throw new TranspileError(
|
|
1071
|
+
`${filePath}: no recognised top-level block found.
|
|
1072
|
+
Expected: component { }, layout { }, or page "Name" { }`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const stem = path.basename(filePath, ".rblua");
|
|
1076
|
+
const name = stem[0].toUpperCase() + stem.slice(1);
|
|
1077
|
+
const parser = new ComponentParser(inner, name);
|
|
1078
|
+
parser.parse();
|
|
1079
|
+
return new ComponentGenerator(parser, filePath).generate();
|
|
1080
|
+
}
|
|
1081
|
+
var TranspileError;
|
|
1082
|
+
var init_transpiler = __esm({
|
|
1083
|
+
"src/transpiler/index.ts"() {
|
|
1084
|
+
init_cjs_shims();
|
|
1085
|
+
init_component();
|
|
1086
|
+
init_layout();
|
|
1087
|
+
init_page();
|
|
1088
|
+
TranspileError = class extends Error {
|
|
1089
|
+
constructor(message) {
|
|
1090
|
+
super(message);
|
|
1091
|
+
this.name = "TranspileError";
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// src/commands/publish.ts
|
|
1098
|
+
var publish_exports = {};
|
|
1099
|
+
__export(publish_exports, {
|
|
1100
|
+
cmdPublish: () => cmdPublish,
|
|
1101
|
+
hasCdnConfig: () => hasCdnConfig,
|
|
1102
|
+
readPublishConfig: () => readPublishConfig
|
|
1103
|
+
});
|
|
1104
|
+
function readPublishConfig() {
|
|
1105
|
+
for (const p of CONFIG_CANDIDATES) {
|
|
1106
|
+
if (!fs.existsSync(p)) continue;
|
|
1107
|
+
const config = {};
|
|
1108
|
+
for (const line of fs.readFileSync(p, "utf8").split("\n")) {
|
|
1109
|
+
const trimmed = line.trim();
|
|
1110
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1111
|
+
const eq = trimmed.indexOf("=");
|
|
1112
|
+
if (eq < 0) continue;
|
|
1113
|
+
const key = trimmed.slice(0, eq).trim();
|
|
1114
|
+
config[key] = trimmed.slice(eq + 1).trim();
|
|
1115
|
+
}
|
|
1116
|
+
return config;
|
|
1117
|
+
}
|
|
1118
|
+
return {};
|
|
1119
|
+
}
|
|
1120
|
+
function hasCdnConfig(projectRoot) {
|
|
1121
|
+
const candidates = projectRoot ? [pathe.join(projectRoot, ".rb-publish"), ...CONFIG_CANDIDATES] : CONFIG_CANDIDATES;
|
|
1122
|
+
for (const p of candidates) {
|
|
1123
|
+
if (!fs.existsSync(p)) continue;
|
|
1124
|
+
const content = fs.readFileSync(p, "utf8");
|
|
1125
|
+
if (content.includes("R2_ACCOUNT_ID") && content.includes("R2_SECRET_TOKEN") && content.includes("R2_PUBLIC_URL")) return true;
|
|
1126
|
+
}
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
async function uploadFile(content, remotePath, config) {
|
|
1130
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${config.R2_ACCOUNT_ID}/r2/buckets/${config.R2_BUCKET}/objects/${remotePath}`;
|
|
1131
|
+
await ofetch.ofetch(url, {
|
|
1132
|
+
method: "PUT",
|
|
1133
|
+
body: content,
|
|
1134
|
+
headers: {
|
|
1135
|
+
Authorization: `Bearer ${config.R2_SECRET_TOKEN}`,
|
|
1136
|
+
"Content-Type": remotePath.endsWith(".lua") ? "text/plain; charset=utf-8" : "application/octet-stream"
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
return `${config.R2_PUBLIC_URL}/${remotePath}`;
|
|
1140
|
+
}
|
|
1141
|
+
async function cmdPublish(_args) {
|
|
1142
|
+
pHeader("publish");
|
|
1143
|
+
const config = readPublishConfig();
|
|
1144
|
+
const missing = ["R2_ACCOUNT_ID", "R2_SECRET_TOKEN", "R2_BUCKET", "R2_PUBLIC_URL"].filter((k) => !config[k]);
|
|
1145
|
+
if (missing.length) {
|
|
1146
|
+
pFail("R2 credentials not configured");
|
|
1147
|
+
console.log();
|
|
1148
|
+
pInfo(`Create ${bold(".rb-publish")} in your project root (or ~/.rb-publish):`);
|
|
1149
|
+
console.log();
|
|
1150
|
+
console.log(` ${dim("R2_ACCOUNT_ID")}=your_cloudflare_account_id`);
|
|
1151
|
+
console.log(` ${dim("R2_SECRET_TOKEN")}=cfat_your_api_token`);
|
|
1152
|
+
console.log(` ${dim("R2_BUCKET")}=pulse-runtime`);
|
|
1153
|
+
console.log(` ${dim("R2_PUBLIC_URL")}=https://pub-xxxx.r2.dev`);
|
|
1154
|
+
console.log();
|
|
1155
|
+
pInfo(`Get your token at: ${cyan("dash.cloudflare.com \u2192 R2 \u2192 Manage API Tokens")}`);
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
const PULSE_DIR2 = pathe.join(__dirname, "..", "pulse");
|
|
1159
|
+
const ADAPTERS = pathe.join(__dirname, "..", "adapters");
|
|
1160
|
+
const VERSION_PATH = `v${RB_VERSION}`;
|
|
1161
|
+
const runtimeFile = pathe.join(PULSE_DIR2, "runtime.lua");
|
|
1162
|
+
const helpersDir = pathe.join(PULSE_DIR2, "helpers");
|
|
1163
|
+
if (!fs.existsSync(runtimeFile)) {
|
|
1164
|
+
pFail("pulse/runtime.lua not found");
|
|
1165
|
+
process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
const bundleParts = [
|
|
1168
|
+
`-- Pulse v${RB_VERSION} bundle (runtime + helpers)`,
|
|
1169
|
+
fs.readFileSync(runtimeFile, "utf8").trimEnd()
|
|
1170
|
+
];
|
|
1171
|
+
if (fs.existsSync(helpersDir)) {
|
|
1172
|
+
for (const f of fs.readdirSync(helpersDir).filter((n) => n.endsWith(".lua")).sort()) {
|
|
1173
|
+
bundleParts.push(`-- ${f}`);
|
|
1174
|
+
bundleParts.push(fs.readFileSync(pathe.join(helpersDir, f), "utf8").trimEnd());
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
|
|
1178
|
+
const uploads = [
|
|
1179
|
+
{ remote: `${VERSION_PATH}/bundle.lua`, content: bundleContent }
|
|
1180
|
+
];
|
|
1181
|
+
for (const adapter of ["windui.lua", "linoria.lua"]) {
|
|
1182
|
+
const p = pathe.join(ADAPTERS, adapter);
|
|
1183
|
+
if (fs.existsSync(p)) uploads.push({
|
|
1184
|
+
remote: `${VERSION_PATH}/adapters/${adapter}`,
|
|
1185
|
+
content: fs.readFileSync(p)
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
|
|
1189
|
+
for (const { remote, content } of uploads) {
|
|
1190
|
+
try {
|
|
1191
|
+
await uploadFile(content, remote, config);
|
|
1192
|
+
pOk(remote, `${content.length.toLocaleString()} bytes`);
|
|
1193
|
+
} catch (e) {
|
|
1194
|
+
pFail(`Failed: ${remote}`, String(e?.data?.message ?? e?.message ?? "").split("\n")[0]);
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
console.log();
|
|
1199
|
+
pOk(`Published ${gray("v" + RB_VERSION)}`);
|
|
1200
|
+
pInfo(`CDN base: ${cyan(config.R2_PUBLIC_URL + "/" + VERSION_PATH)}`);
|
|
1201
|
+
console.log();
|
|
1202
|
+
}
|
|
1203
|
+
var CONFIG_CANDIDATES;
|
|
1204
|
+
var init_publish = __esm({
|
|
1205
|
+
"src/commands/publish.ts"() {
|
|
1206
|
+
init_cjs_shims();
|
|
1207
|
+
init_ui();
|
|
1208
|
+
init_constants();
|
|
1209
|
+
CONFIG_CANDIDATES = [
|
|
1210
|
+
pathe.join(process.cwd(), ".rb-publish"),
|
|
1211
|
+
pathe.join(process.env["USERPROFILE"] ?? process.env["HOME"] ?? "", ".rb-publish")
|
|
1212
|
+
];
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
function adapterPath(ui) {
|
|
1216
|
+
if (!VALID_UI_LIBS.has(ui)) throw new Error(`Unknown UI library '${ui}'. Valid: ${[...VALID_UI_LIBS].sort().join(", ")}`);
|
|
1217
|
+
return path.join(ADAPTERS_DIR, `${ui}.lua`);
|
|
1218
|
+
}
|
|
1219
|
+
function rglob(dir, exts) {
|
|
1220
|
+
if (!fs.existsSync(dir)) return [];
|
|
1221
|
+
const results = [];
|
|
1222
|
+
function walk(d) {
|
|
1223
|
+
for (const entry of fs.readdirSync(d)) {
|
|
1224
|
+
const full = path.join(d, entry);
|
|
1225
|
+
if (fs.statSync(full).isDirectory()) {
|
|
1226
|
+
walk(full);
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (exts.some((e) => full.endsWith(e))) results.push(full);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
walk(dir);
|
|
1233
|
+
return results;
|
|
1234
|
+
}
|
|
1235
|
+
var PULSE_DIR, PULSE_RUNTIME, PULSE_HELPERS, PULSE_DEV_DIR, PULSE_UI_DIR, ADAPTERS_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, DEFAULTS_RUNNER, Compiler;
|
|
1236
|
+
var init_compiler = __esm({
|
|
1237
|
+
"src/compiler.ts"() {
|
|
1238
|
+
init_cjs_shims();
|
|
1239
|
+
init_transpiler();
|
|
1240
|
+
init_layout();
|
|
1241
|
+
init_ui();
|
|
1242
|
+
init_constants();
|
|
1243
|
+
init_publish();
|
|
1244
|
+
PULSE_DIR = path.join(__dirname, "..", "pulse");
|
|
1245
|
+
PULSE_RUNTIME = path.join(PULSE_DIR, "runtime.lua");
|
|
1246
|
+
PULSE_HELPERS = path.join(PULSE_DIR, "helpers");
|
|
1247
|
+
PULSE_DEV_DIR = path.join(PULSE_DIR, "dev");
|
|
1248
|
+
PULSE_UI_DIR = path.join(PULSE_DIR, "ui");
|
|
1249
|
+
ADAPTERS_DIR = path.join(__dirname, "..", "adapters");
|
|
1250
|
+
REEXEC_GUARD = `-- Stop any previous instance before this one starts.
|
|
1251
|
+
-- Kills old Heartbeat loops, connections, and Linoria window.
|
|
1252
|
+
if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end
|
|
1253
|
+
`;
|
|
1254
|
+
DESTROY_REGISTRATION = `-- Register this instance so the next re-execution can clean it up.
|
|
1255
|
+
_G.__AOT_R_DESTROY = _PulseDestroy
|
|
1256
|
+
`;
|
|
1257
|
+
DEFAULTS_RUNNER = `task.spawn(function()
|
|
1258
|
+
task.wait(0.5)
|
|
1259
|
+
for _, d in ipairs(_PULSE_DEFAULTS) do
|
|
1260
|
+
local effectiveValue = d.value
|
|
1261
|
+
if d.type == "toggle" and Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1262
|
+
if not Pulse.TestMode.isTarget(d.id) then
|
|
1263
|
+
effectiveValue = false
|
|
1264
|
+
end
|
|
1265
|
+
end
|
|
1266
|
+
if d.set then pcall(d.set, effectiveValue) end
|
|
1267
|
+
if d.type == "toggle" then
|
|
1268
|
+
pcall(function()
|
|
1269
|
+
local t = Toggles and Toggles[d.id]
|
|
1270
|
+
if t then t:SetValue(effectiveValue) end
|
|
1271
|
+
end)
|
|
1272
|
+
else
|
|
1273
|
+
pcall(function()
|
|
1274
|
+
local t = Options and Options[d.id]
|
|
1275
|
+
if t then t:SetValue(d.value) end
|
|
1276
|
+
end)
|
|
1277
|
+
end
|
|
1278
|
+
task.wait(0.05)
|
|
1279
|
+
end
|
|
1280
|
+
if #_PULSE_DEFAULTS > 0 then
|
|
1281
|
+
local _notifyMsg = "AOT:Resistance Hub ready!"
|
|
1282
|
+
if Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1283
|
+
local _tt = Pulse.TestMode.getTargets()
|
|
1284
|
+
if #_tt > 0 then
|
|
1285
|
+
_notifyMsg = "Test mode: " .. table.concat(_tt, ", ")
|
|
1286
|
+
end
|
|
1287
|
+
end
|
|
1288
|
+
if _PulseNotify then
|
|
1289
|
+
_PulseNotify(_notifyMsg, 5)
|
|
1290
|
+
end
|
|
1291
|
+
end
|
|
1292
|
+
end)
|
|
1293
|
+
`;
|
|
1294
|
+
Compiler = class {
|
|
1295
|
+
root;
|
|
1296
|
+
srcDir;
|
|
1297
|
+
buildDir;
|
|
1298
|
+
constructor(root) {
|
|
1299
|
+
this.root = root;
|
|
1300
|
+
this.srcDir = path.join(root, "src");
|
|
1301
|
+
this.buildDir = path.join(root, "build");
|
|
1302
|
+
}
|
|
1303
|
+
getUiLibrary() {
|
|
1304
|
+
const layoutFile = path.join(this.srcDir, "ui", "layout.rblua");
|
|
1305
|
+
if (!fs.existsSync(layoutFile)) return "linoria";
|
|
1306
|
+
try {
|
|
1307
|
+
const [inner] = findLayoutBlock(fs.readFileSync(layoutFile, "utf8"));
|
|
1308
|
+
if (inner) return parseLayoutOptions(inner).ui_library ?? "linoria";
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
1311
|
+
return "linoria";
|
|
1312
|
+
}
|
|
1313
|
+
resolveUi(ui) {
|
|
1314
|
+
if (!ui) ui = this.getUiLibrary();
|
|
1315
|
+
if (!VALID_UI_LIBS.has(ui)) throw new Error(`Unknown UI library '${ui}'. Valid: ${[...VALID_UI_LIBS].sort().join(", ")}`);
|
|
1316
|
+
return ui;
|
|
1317
|
+
}
|
|
1318
|
+
getLoadOrder(opts = {}) {
|
|
1319
|
+
const ui = this.resolveUi(opts.ui);
|
|
1320
|
+
const src = this.srcDir;
|
|
1321
|
+
const uiDir = path.join(src, "ui");
|
|
1322
|
+
const devDir = path.join(src, "dev");
|
|
1323
|
+
const exclude = new Set(ALWAYS_EXCLUDE);
|
|
1324
|
+
if (opts.compat) {
|
|
1325
|
+
const layoutFile = path.join(src, "ui", "layout.rblua");
|
|
1326
|
+
if (fs.existsSync(layoutFile)) {
|
|
1327
|
+
try {
|
|
1328
|
+
const [inner] = findLayoutBlock(fs.readFileSync(layoutFile, "utf8"));
|
|
1329
|
+
if (inner) {
|
|
1330
|
+
for (const p of parseLayoutOptions(inner).compat_exclude ?? []) exclude.add(p);
|
|
1331
|
+
}
|
|
1332
|
+
} catch {
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const bootstrap = path.join(src, "misc", "helpers", "globals.lua");
|
|
1337
|
+
const remotes = path.join(src, "misc", "remotes.lua");
|
|
1338
|
+
const startup = path.join(src, "startup.lua");
|
|
1339
|
+
const first = [];
|
|
1340
|
+
const middle = [];
|
|
1341
|
+
let uiFiles = [];
|
|
1342
|
+
if (fs.existsSync(bootstrap)) first.push(bootstrap);
|
|
1343
|
+
if (fs.existsSync(remotes)) first.push(remotes);
|
|
1344
|
+
const allFiles = [
|
|
1345
|
+
...rglob(src, [".lua", ".rblua"])
|
|
1346
|
+
].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
1347
|
+
for (const f of allFiles) {
|
|
1348
|
+
if (f.startsWith(devDir + "\\") || f.startsWith(devDir + "/")) continue;
|
|
1349
|
+
const rel = path.relative(src, f).replace(/\\/g, "/");
|
|
1350
|
+
if (exclude.has(rel)) continue;
|
|
1351
|
+
if (f === bootstrap || f === remotes || f === startup) continue;
|
|
1352
|
+
if (path.basename(f) === path.basename(src)) continue;
|
|
1353
|
+
if (f.startsWith(uiDir + "\\") || f.startsWith(uiDir + "/")) {
|
|
1354
|
+
uiFiles.push(f);
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
path.join(f, "...");
|
|
1358
|
+
if (path.relative(src, f).split(/[/\\]/).length === 1) continue;
|
|
1359
|
+
middle.push(f);
|
|
1360
|
+
}
|
|
1361
|
+
if (opts.dev) {
|
|
1362
|
+
const fwDevUi = path.join(PULSE_DEV_DIR, "ui");
|
|
1363
|
+
if (fs.existsSync(fwDevUi)) {
|
|
1364
|
+
for (const f of rglob(fwDevUi, [".lua", ".rblua"]).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) {
|
|
1365
|
+
uiFiles.push(f);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
uiFiles = uiFiles.filter((f) => {
|
|
1370
|
+
const n = path.basename(f);
|
|
1371
|
+
return n !== "7_Settings.lua" && !path.basename(f, path.extname(f)).endsWith("_windui");
|
|
1372
|
+
});
|
|
1373
|
+
uiFiles.sort((a, b) => {
|
|
1374
|
+
const an = path.basename(a), bn = path.basename(b);
|
|
1375
|
+
const ai = an === "layout.rblua" ? 0 : 1;
|
|
1376
|
+
const bi = bn === "layout.rblua" ? 0 : 1;
|
|
1377
|
+
return ai !== bi ? ai - bi : an.toLowerCase().localeCompare(bn.toLowerCase());
|
|
1378
|
+
});
|
|
1379
|
+
if (fs.existsSync(PULSE_UI_DIR)) {
|
|
1380
|
+
for (const f of rglob(PULSE_UI_DIR, [".lua", ".rblua"]).sort((a, b) => path.basename(a).toLowerCase().localeCompare(path.basename(b).toLowerCase()))) {
|
|
1381
|
+
const stem = path.basename(f, path.extname(f));
|
|
1382
|
+
if (stem.startsWith("windui_") && ui !== "windui") continue;
|
|
1383
|
+
if (stem.startsWith("linoria_") && ui !== "linoria") continue;
|
|
1384
|
+
uiFiles.push(f);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
const last = [];
|
|
1388
|
+
if (fs.existsSync(startup)) last.push(startup);
|
|
1389
|
+
return [...first, ...middle, ...uiFiles, ...last];
|
|
1390
|
+
}
|
|
1391
|
+
compile(opts = {}) {
|
|
1392
|
+
const ui = this.resolveUi(opts.ui);
|
|
1393
|
+
const order = this.getLoadOrder({ ...opts, ui });
|
|
1394
|
+
const hasRblua = order.some((f) => f.endsWith(".rblua"));
|
|
1395
|
+
const srcUiDir = path.join(this.srcDir, "ui");
|
|
1396
|
+
const pulseDevUi = path.join(PULSE_DEV_DIR, "ui");
|
|
1397
|
+
const useCdn = !opts.dev && hasCdnConfig(this.root);
|
|
1398
|
+
const parts = [];
|
|
1399
|
+
parts.push("-- [re-execution guard]\n");
|
|
1400
|
+
parts.push(REEXEC_GUARD);
|
|
1401
|
+
parts.push("\n");
|
|
1402
|
+
if (opts.dev) {
|
|
1403
|
+
parts.push("-- [dev flag]\n");
|
|
1404
|
+
parts.push("local _PULSE_DEV = true\n\n");
|
|
1405
|
+
}
|
|
1406
|
+
if (hasRblua) {
|
|
1407
|
+
if (useCdn) {
|
|
1408
|
+
const base = `${CDN_BASE_URL}/v${RB_VERSION}`;
|
|
1409
|
+
parts.push(`-- Pulse v${RB_VERSION}
|
|
1410
|
+
`);
|
|
1411
|
+
parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
|
|
1412
|
+
`);
|
|
1413
|
+
parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))()
|
|
1414
|
+
`);
|
|
1415
|
+
parts.push("\n");
|
|
1416
|
+
} else {
|
|
1417
|
+
if (fs.existsSync(PULSE_RUNTIME)) {
|
|
1418
|
+
parts.push("-- [pulse/runtime.lua]\n");
|
|
1419
|
+
parts.push(fs.readFileSync(PULSE_RUNTIME, "utf8"));
|
|
1420
|
+
parts.push("\n");
|
|
1421
|
+
if (fs.existsSync(PULSE_HELPERS)) {
|
|
1422
|
+
for (const helper of fs.readdirSync(PULSE_HELPERS).filter((f) => f.endsWith(".lua")).sort()) {
|
|
1423
|
+
parts.push(`-- [pulse/helpers/${helper}]
|
|
1424
|
+
`);
|
|
1425
|
+
parts.push(fs.readFileSync(path.join(PULSE_HELPERS, helper), "utf8"));
|
|
1426
|
+
parts.push("\n");
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (opts.dev && fs.existsSync(PULSE_DEV_DIR)) {
|
|
1433
|
+
const rbRoot = path.join(__dirname, "..", "..");
|
|
1434
|
+
for (const f of fs.readdirSync(PULSE_DEV_DIR).filter((n) => n.endsWith(".lua")).sort()) {
|
|
1435
|
+
const full = path.join(PULSE_DEV_DIR, f);
|
|
1436
|
+
const rel = path.relative(rbRoot, full).replace(/\\/g, "/");
|
|
1437
|
+
parts.push(`-- [${rel}]
|
|
1438
|
+
`);
|
|
1439
|
+
parts.push(fs.readFileSync(full, "utf8"));
|
|
1440
|
+
parts.push("\n");
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const adapter = adapterPath(ui);
|
|
1444
|
+
if (!useCdn && fs.existsSync(srcUiDir) && fs.existsSync(adapter)) {
|
|
1445
|
+
parts.push(`-- [adapters/${ui}.lua]
|
|
1446
|
+
`);
|
|
1447
|
+
parts.push(fs.readFileSync(adapter, "utf8"));
|
|
1448
|
+
parts.push("\n");
|
|
1449
|
+
}
|
|
1450
|
+
const label = (p) => {
|
|
1451
|
+
try {
|
|
1452
|
+
return path.relative(this.root, p).replace(/\\/g, "/");
|
|
1453
|
+
} catch {
|
|
1454
|
+
return path.relative(path.join(__dirname, ".."), p).replace(/\\/g, "/");
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
const isUiFile = (p) => p.startsWith(srcUiDir + "\\") || p.startsWith(srcUiDir + "/") || p.startsWith(pulseDevUi + "\\") || p.startsWith(pulseDevUi + "/") || p.startsWith(PULSE_UI_DIR + "\\") || p.startsWith(PULSE_UI_DIR + "/");
|
|
1458
|
+
const shouldChunk = (p) => p.endsWith(".rblua") && !isUiFile(p);
|
|
1459
|
+
let exportedFunc = false;
|
|
1460
|
+
for (const f of order) {
|
|
1461
|
+
const lbl = label(f);
|
|
1462
|
+
if (shouldChunk(f) && !exportedFunc) {
|
|
1463
|
+
parts.push("-- [rb: chunks begin]\n");
|
|
1464
|
+
parts.push("_G.__rb_func = func\n");
|
|
1465
|
+
parts.push("_G.__rb_lp = _LocalPlayer\n");
|
|
1466
|
+
parts.push("local function _chunk(fn) fn() end\n\n");
|
|
1467
|
+
exportedFunc = true;
|
|
1468
|
+
}
|
|
1469
|
+
parts.push(`-- [${lbl}]
|
|
1470
|
+
`);
|
|
1471
|
+
if (shouldChunk(f)) {
|
|
1472
|
+
const source = fs.readFileSync(f, "utf8");
|
|
1473
|
+
let content;
|
|
1474
|
+
try {
|
|
1475
|
+
content = transpile(source, f);
|
|
1476
|
+
} catch (e) {
|
|
1477
|
+
if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
|
|
1478
|
+
throw e;
|
|
1479
|
+
}
|
|
1480
|
+
parts.push("_chunk(function()\n");
|
|
1481
|
+
parts.push("local func = _G.__rb_func\n");
|
|
1482
|
+
parts.push("local _LocalPlayer = _G.__rb_lp\n");
|
|
1483
|
+
parts.push(content);
|
|
1484
|
+
parts.push("\nend)\n");
|
|
1485
|
+
} else if (f.endsWith(".rblua")) {
|
|
1486
|
+
const source = fs.readFileSync(f, "utf8");
|
|
1487
|
+
try {
|
|
1488
|
+
parts.push(transpile(source, f));
|
|
1489
|
+
} catch (e) {
|
|
1490
|
+
if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
|
|
1491
|
+
throw e;
|
|
1492
|
+
}
|
|
1493
|
+
} else {
|
|
1494
|
+
parts.push(fs.readFileSync(f, "utf8"));
|
|
1495
|
+
}
|
|
1496
|
+
parts.push("\n");
|
|
1497
|
+
}
|
|
1498
|
+
if (order.some((f) => f.endsWith(".rblua"))) {
|
|
1499
|
+
parts.push("-- [generated: defaults runner]\n");
|
|
1500
|
+
parts.push(DEFAULTS_RUNNER);
|
|
1501
|
+
parts.push("\n");
|
|
1502
|
+
}
|
|
1503
|
+
parts.push("-- [generated: destroy registration]\n");
|
|
1504
|
+
parts.push(DESTROY_REGISTRATION);
|
|
1505
|
+
parts.push("\n");
|
|
1506
|
+
return parts.join("");
|
|
1507
|
+
}
|
|
1508
|
+
build(opts = {}) {
|
|
1509
|
+
fs.mkdirSync(this.buildDir, { recursive: true });
|
|
1510
|
+
const ui = this.resolveUi(opts.ui);
|
|
1511
|
+
const outputName = opts.dev ? "script.dev.lua" : opts.compat ? "script.compat.lua" : "script.lua";
|
|
1512
|
+
const outputPath = path.join(this.buildDir, outputName);
|
|
1513
|
+
const content = this.compile({ ...opts, ui });
|
|
1514
|
+
fs.writeFileSync(outputPath, content, "utf8");
|
|
1515
|
+
const lines = content.split("\n").length - 1;
|
|
1516
|
+
const chars = content.length;
|
|
1517
|
+
const modules = this.getLoadOrder({ ...opts, ui }).length;
|
|
1518
|
+
pOk(path.relative(this.root, outputPath).replace(/\\/g, "/"), `${chars.toLocaleString()} chars`, `${lines.toLocaleString()} lines`, `${modules} modules`);
|
|
1519
|
+
return outputPath;
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
function checkSyntax(source) {
|
|
1525
|
+
try {
|
|
1526
|
+
luaparse.parse(source, {
|
|
1527
|
+
luaVersion: "5.1",
|
|
1528
|
+
comments: false
|
|
1529
|
+
});
|
|
1530
|
+
return [];
|
|
1531
|
+
} catch (e) {
|
|
1532
|
+
return [{
|
|
1533
|
+
line: e.line ?? 0,
|
|
1534
|
+
column: e.column ?? 0,
|
|
1535
|
+
message: (e.message ?? String(e)).replace(/^\[.*?\]:\d+:\s*/, "")
|
|
1536
|
+
}];
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function syntaxCheckBuild(outputPath, order, root) {
|
|
1540
|
+
let source;
|
|
1541
|
+
try {
|
|
1542
|
+
source = fs.readFileSync(outputPath, "utf8");
|
|
1543
|
+
} catch {
|
|
1544
|
+
return [];
|
|
1545
|
+
}
|
|
1546
|
+
const errs = checkSyntax(source);
|
|
1547
|
+
if (!errs.length) return [];
|
|
1548
|
+
const lines = source.split("\n");
|
|
1549
|
+
let currentSrc = null;
|
|
1550
|
+
let srcLineOffset = 0;
|
|
1551
|
+
const lineToSrc = [];
|
|
1552
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1553
|
+
const m = lines[i].match(/^-- \[([^\]]+)\]$/);
|
|
1554
|
+
if (m && !m[1].startsWith("rb:") && !m[1].startsWith("generated:")) {
|
|
1555
|
+
currentSrc = m[1];
|
|
1556
|
+
srcLineOffset = i + 1;
|
|
1557
|
+
}
|
|
1558
|
+
lineToSrc.push({ src: currentSrc, offset: srcLineOffset });
|
|
1559
|
+
}
|
|
1560
|
+
return errs.map((e) => {
|
|
1561
|
+
const idx = Math.max(0, Math.min(e.line - 1, lineToSrc.length - 1));
|
|
1562
|
+
const info = lineToSrc[idx] ?? { src: null, offset: 0 };
|
|
1563
|
+
return {
|
|
1564
|
+
compiledLine: e.line,
|
|
1565
|
+
src: info.src ? path.resolve(root, info.src) : null,
|
|
1566
|
+
srcLine: e.line - info.offset + 1,
|
|
1567
|
+
message: e.message
|
|
1568
|
+
};
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
function handleCheckErrors(errors, root, t0) {
|
|
1572
|
+
const userErrors = errors.filter((e) => {
|
|
1573
|
+
if (!e.src) return false;
|
|
1574
|
+
try {
|
|
1575
|
+
return e.src.startsWith(root);
|
|
1576
|
+
} catch {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
if (!userErrors.length) return;
|
|
1581
|
+
for (const err of userErrors) {
|
|
1582
|
+
const rel = err.src ? path.relative(root, err.src).replace(/\\/g, "/") : "?";
|
|
1583
|
+
pFail(`${rel} line ${err.srcLine}`, err.message);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function checkLocalBudget(builds, root) {
|
|
1587
|
+
for (const build of builds) {
|
|
1588
|
+
try {
|
|
1589
|
+
const content = fs.readFileSync(build, "utf8");
|
|
1590
|
+
if (content.length > 5e5) {
|
|
1591
|
+
pWarn(`${path.relative(root, build).replace(/\\/g, "/")} is large (${(content.length / 1e3).toFixed(0)} KB) \u2014 check for local variable budget issues`);
|
|
1592
|
+
}
|
|
1593
|
+
} catch {
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
var luaparse;
|
|
1598
|
+
var init_checker = __esm({
|
|
1599
|
+
"src/checker.ts"() {
|
|
1600
|
+
init_cjs_shims();
|
|
1601
|
+
init_ui();
|
|
1602
|
+
luaparse = __require("luaparse");
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
function ensureIronbrew2() {
|
|
1606
|
+
const entry = path.join(IRONBREW2_DIR, "IronBrew2.lua");
|
|
1607
|
+
if (fs.existsSync(entry)) return IRONBREW2_DIR;
|
|
1608
|
+
pInfo("Downloading Ironbrew2 from GitHub...");
|
|
1609
|
+
fs.mkdirSync(path.dirname(IRONBREW2_DIR), { recursive: true });
|
|
1610
|
+
const result = child_process.spawnSync("git", ["clone", "--depth=1", IRONBREW2_REPO, IRONBREW2_DIR], {
|
|
1611
|
+
stdio: "inherit"
|
|
1612
|
+
});
|
|
1613
|
+
if (result.status !== 0) {
|
|
1614
|
+
pFail("Failed to clone Ironbrew2 \u2014 check internet / git install.");
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
pOk(`Ironbrew2 ready ${gray(IRONBREW2_DIR)}`);
|
|
1618
|
+
return IRONBREW2_DIR;
|
|
1619
|
+
}
|
|
1620
|
+
async function obfuscateSource(source, ib2Dir) {
|
|
1621
|
+
const { LuaFactory } = await import('wasmoon');
|
|
1622
|
+
const factory = new LuaFactory();
|
|
1623
|
+
const lua = await factory.createEngine();
|
|
1624
|
+
const tmpIn = path.join(os.tmpdir(), `rb_in_${Date.now()}.lua`);
|
|
1625
|
+
const tmpOut = path.join(os.tmpdir(), `rb_out_${Date.now()}.lua`);
|
|
1626
|
+
try {
|
|
1627
|
+
fs.writeFileSync(tmpIn, source, "utf8");
|
|
1628
|
+
const inPath = tmpIn.replace(/\\/g, "/");
|
|
1629
|
+
const outPath = tmpOut.replace(/\\/g, "/");
|
|
1630
|
+
await lua.doString(`arg = {[0]="IronBrew2.lua", [1]="${inPath}", [2]="${outPath}"}`);
|
|
1631
|
+
const ib2Source = fs.readFileSync(path.join(ib2Dir, "IronBrew2.lua"), "utf8");
|
|
1632
|
+
await lua.doString(ib2Source);
|
|
1633
|
+
if (!fs.existsSync(tmpOut)) throw new Error("Ironbrew2 produced no output");
|
|
1634
|
+
return fs.readFileSync(tmpOut, "utf8");
|
|
1635
|
+
} finally {
|
|
1636
|
+
lua.global.close();
|
|
1637
|
+
try {
|
|
1638
|
+
__require("fs").unlinkSync(tmpIn);
|
|
1639
|
+
} catch {
|
|
1640
|
+
}
|
|
1641
|
+
try {
|
|
1642
|
+
__require("fs").unlinkSync(tmpOut);
|
|
1643
|
+
} catch {
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function obfuscatePipeline(builds, root) {
|
|
1648
|
+
pSection("Obfuscating");
|
|
1649
|
+
const ib2Dir = ensureIronbrew2();
|
|
1650
|
+
const outputs = [];
|
|
1651
|
+
let failed = false;
|
|
1652
|
+
for (const source of builds) {
|
|
1653
|
+
const outPath = source.replace(/\.lua$/, ".obf.lua");
|
|
1654
|
+
try {
|
|
1655
|
+
pInfo(`Obfuscating ${__require("path").relative(root, source).replace(/\\/g, "/")}`);
|
|
1656
|
+
const content = fs.readFileSync(source, "utf8");
|
|
1657
|
+
const result = await obfuscateSource(content, ib2Dir);
|
|
1658
|
+
fs.writeFileSync(outPath, result, "utf8");
|
|
1659
|
+
pOk(__require("path").relative(root, outPath).replace(/\\/g, "/"), `${result.length.toLocaleString()} chars`);
|
|
1660
|
+
outputs.push(outPath);
|
|
1661
|
+
} catch (e) {
|
|
1662
|
+
pFail(`Ironbrew2 failed on ${__require("path").basename(source)}`);
|
|
1663
|
+
pWarn(String(e?.message ?? e));
|
|
1664
|
+
failed = true;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (failed) process.exit(1);
|
|
1668
|
+
return outputs;
|
|
1669
|
+
}
|
|
1670
|
+
function copyToClipboard(content, label = "") {
|
|
1671
|
+
const clip = process.platform === "win32" ? ["clip.exe"] : process.platform === "darwin" ? ["pbcopy"] : ["xclip", "-selection", "clipboard"];
|
|
1672
|
+
try {
|
|
1673
|
+
const r = child_process.spawnSync(clip[0], clip.slice(1), { input: content, encoding: "utf8" });
|
|
1674
|
+
if (r.status === 0) {
|
|
1675
|
+
pOk(`Copied to clipboard ${gray(label)}`, `${content.length.toLocaleString()} chars`);
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
pWarn("Could not find clipboard tool (clip.exe / pbcopy / xclip)");
|
|
1681
|
+
}
|
|
1682
|
+
var init_obfuscator = __esm({
|
|
1683
|
+
"src/obfuscator.ts"() {
|
|
1684
|
+
init_cjs_shims();
|
|
1685
|
+
init_ui();
|
|
1686
|
+
init_constants();
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
// src/commands/build.ts
|
|
1691
|
+
var build_exports = {};
|
|
1692
|
+
__export(build_exports, {
|
|
1693
|
+
cmdBuild: () => cmdBuild,
|
|
1694
|
+
cmdClean: () => cmdClean,
|
|
1695
|
+
cmdCopy: () => cmdCopy
|
|
1696
|
+
});
|
|
1697
|
+
async function cmdBuild(args) {
|
|
1698
|
+
const root = requireProjectRoot();
|
|
1699
|
+
const compiler = new Compiler(root);
|
|
1700
|
+
pHeader("build");
|
|
1701
|
+
const t0 = Date.now();
|
|
1702
|
+
pSection("Compiling");
|
|
1703
|
+
const builds = [];
|
|
1704
|
+
builds.push(compiler.build({ compat: false, ui: args.ui }));
|
|
1705
|
+
if (!args.noCompat) builds.push(compiler.build({ compat: true, ui: args.ui }));
|
|
1706
|
+
if (args.dev) builds.push(compiler.build({ dev: true, ui: args.ui }));
|
|
1707
|
+
pSection("Checking");
|
|
1708
|
+
let hasErrors = false;
|
|
1709
|
+
for (let i = 0; i < builds.length; i++) {
|
|
1710
|
+
const order = compiler.getLoadOrder({ compat: i === 1, dev: i === 2, ui: args.ui });
|
|
1711
|
+
const errors = syntaxCheckBuild(builds[i], order, root);
|
|
1712
|
+
if (errors.length) {
|
|
1713
|
+
handleCheckErrors(errors, root);
|
|
1714
|
+
hasErrors = true;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (!hasErrors) {
|
|
1718
|
+
const order = compiler.getLoadOrder({ compat: false, ui: args.ui });
|
|
1719
|
+
pOk(`Clean ${gray(String(order.length) + " modules")}`);
|
|
1720
|
+
}
|
|
1721
|
+
checkLocalBudget(builds, root);
|
|
1722
|
+
if (hasErrors) {
|
|
1723
|
+
pDone((Date.now() - t0) / 1e3);
|
|
1724
|
+
process.exit(1);
|
|
1725
|
+
}
|
|
1726
|
+
if (args.noObfuscate) {
|
|
1727
|
+
pDone((Date.now() - t0) / 1e3);
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const preset = args.preset || "Medium";
|
|
1731
|
+
if (!OBFUSCATION_PRESETS.map((p) => p.toLowerCase()).includes(preset.toLowerCase())) {
|
|
1732
|
+
pFail(`Unknown preset '${preset}'`, `Choose: ${OBFUSCATION_PRESETS.join(", ")}`);
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
1735
|
+
const obfBuilds = builds.filter((b) => !b.includes("script.dev"));
|
|
1736
|
+
const outputs = await obfuscatePipeline(obfBuilds, root);
|
|
1737
|
+
pDone((Date.now() - t0) / 1e3);
|
|
1738
|
+
if (args.copy && outputs.length) {
|
|
1739
|
+
const { readFileSync: readFileSync12 } = await import('fs');
|
|
1740
|
+
const toCopy = outputs.find((p) => !p.includes("compat")) ?? outputs[0];
|
|
1741
|
+
copyToClipboard(readFileSync12(toCopy, "utf8"), __require("path").basename(toCopy));
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async function cmdCopy(args) {
|
|
1745
|
+
const root = requireProjectRoot();
|
|
1746
|
+
const compiler = new Compiler(root);
|
|
1747
|
+
pHeader("copy");
|
|
1748
|
+
pSection("Compiling");
|
|
1749
|
+
const output = compiler.build({ compat: args.compat });
|
|
1750
|
+
const { readFileSync: readFileSync12 } = await import('fs');
|
|
1751
|
+
copyToClipboard(readFileSync12(output, "utf8"), __require("path").basename(output));
|
|
1752
|
+
console.log();
|
|
1753
|
+
}
|
|
1754
|
+
function cmdClean(_args) {
|
|
1755
|
+
const root = requireProjectRoot();
|
|
1756
|
+
const { readdirSync: readdirSync4, unlinkSync } = __require("fs");
|
|
1757
|
+
const { join: join16, relative: relative6 } = __require("path");
|
|
1758
|
+
pHeader("clean");
|
|
1759
|
+
const buildDir = join16(root, "build");
|
|
1760
|
+
if (!fs.existsSync(buildDir)) {
|
|
1761
|
+
(init_ui(), __toCommonJS(ui_exports)).pInfo("Nothing to clean (no build/ directory)");
|
|
1762
|
+
console.log();
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const files = readdirSync4(buildDir).filter((f) => f.endsWith(".lua"));
|
|
1766
|
+
if (!files.length) {
|
|
1767
|
+
(init_ui(), __toCommonJS(ui_exports)).pInfo("Nothing to clean");
|
|
1768
|
+
console.log();
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
for (const f of files) {
|
|
1772
|
+
unlinkSync(join16(buildDir, f));
|
|
1773
|
+
(init_ui(), __toCommonJS(ui_exports)).pOk(relative6(root, join16(buildDir, f)).replace(/\\/g, "/"));
|
|
1774
|
+
}
|
|
1775
|
+
console.log();
|
|
1776
|
+
}
|
|
1777
|
+
var init_build = __esm({
|
|
1778
|
+
"src/commands/build.ts"() {
|
|
1779
|
+
init_cjs_shims();
|
|
1780
|
+
init_ui();
|
|
1781
|
+
init_project();
|
|
1782
|
+
init_compiler();
|
|
1783
|
+
init_checker();
|
|
1784
|
+
init_obfuscator();
|
|
1785
|
+
init_constants();
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
// src/commands/watch.ts
|
|
1790
|
+
var watch_exports = {};
|
|
1791
|
+
__export(watch_exports, {
|
|
1792
|
+
cmdWatch: () => cmdWatch
|
|
1793
|
+
});
|
|
1794
|
+
function cmdWatch(args) {
|
|
1795
|
+
const root = requireProjectRoot();
|
|
1796
|
+
const compiler = new Compiler(root);
|
|
1797
|
+
pHeader("watch");
|
|
1798
|
+
pInfo(`Watching ${bold("src/")} for changes ${dim("Ctrl+C to stop")}`);
|
|
1799
|
+
console.log();
|
|
1800
|
+
const rebuild = () => {
|
|
1801
|
+
try {
|
|
1802
|
+
pSection("Compiling");
|
|
1803
|
+
compiler.build({ compat: false });
|
|
1804
|
+
if (args.compat) compiler.build({ compat: true });
|
|
1805
|
+
console.log();
|
|
1806
|
+
} catch (e) {
|
|
1807
|
+
pFail(`Build error: ${e?.message ?? e}`);
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
const srcDir = pathe.join(root, "src");
|
|
1811
|
+
const watcher = chokidar__default.default.watch([
|
|
1812
|
+
pathe.join(srcDir, "**", "*.lua"),
|
|
1813
|
+
pathe.join(srcDir, "**", "*.rblua")
|
|
1814
|
+
], { ignoreInitial: true, persistent: true });
|
|
1815
|
+
watcher.on("change", (p) => {
|
|
1816
|
+
pInfo(`changed ${gray(p.replace(root, "").replace(/\\/g, "/").replace(/^\//, ""))}`);
|
|
1817
|
+
rebuild();
|
|
1818
|
+
});
|
|
1819
|
+
watcher.on("add", (p) => {
|
|
1820
|
+
pInfo(`added ${gray(p.replace(root, "").replace(/\\/g, "/").replace(/^\//, ""))}`);
|
|
1821
|
+
rebuild();
|
|
1822
|
+
});
|
|
1823
|
+
watcher.on("unlink", (p) => {
|
|
1824
|
+
pInfo(`removed ${gray(p.replace(root, "").replace(/\\/g, "/").replace(/^\//, ""))}`);
|
|
1825
|
+
rebuild();
|
|
1826
|
+
});
|
|
1827
|
+
process.on("SIGINT", () => {
|
|
1828
|
+
console.log(`
|
|
1829
|
+
${dim("stopped.")}
|
|
1830
|
+
`);
|
|
1831
|
+
process.exit(0);
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
var init_watch = __esm({
|
|
1835
|
+
"src/commands/watch.ts"() {
|
|
1836
|
+
init_cjs_shims();
|
|
1837
|
+
init_ui();
|
|
1838
|
+
init_project();
|
|
1839
|
+
init_compiler();
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
// src/commands/check.ts
|
|
1844
|
+
var check_exports = {};
|
|
1845
|
+
__export(check_exports, {
|
|
1846
|
+
cmdCheck: () => cmdCheck
|
|
1847
|
+
});
|
|
1848
|
+
function cmdCheck(args) {
|
|
1849
|
+
const root = requireProjectRoot();
|
|
1850
|
+
const compiler = new Compiler(root);
|
|
1851
|
+
pHeader("check");
|
|
1852
|
+
const t0 = Date.now();
|
|
1853
|
+
const order = compiler.getLoadOrder({ compat: args.compat });
|
|
1854
|
+
const variant = args.compat ? "compat" : "standard";
|
|
1855
|
+
pSection(`Compiling ${gray(`(${variant} \xB7 ${order.length} modules)`)}`);
|
|
1856
|
+
const combined = compiler.compile({ compat: args.compat });
|
|
1857
|
+
const lines = combined.split("\n").length - 1;
|
|
1858
|
+
console.log(` ${dim(`${combined.length.toLocaleString()} chars \xB7 ${lines.toLocaleString()} lines`)}`);
|
|
1859
|
+
pSection("Syntax (luaparse)");
|
|
1860
|
+
const errors = checkSyntax(combined);
|
|
1861
|
+
if (errors.length) {
|
|
1862
|
+
for (const err of errors) {
|
|
1863
|
+
pFail(`line ${err.line}`, err.message);
|
|
1864
|
+
}
|
|
1865
|
+
} else {
|
|
1866
|
+
pOk(`No errors ${gray(`${order.length} modules \xB7 ${lines} lines`)}`);
|
|
1867
|
+
}
|
|
1868
|
+
const logPath = path.join(root, "build", "check.log");
|
|
1869
|
+
fs.mkdirSync(path.join(root, "build"), { recursive: true });
|
|
1870
|
+
const elapsed = (Date.now() - t0) / 1e3;
|
|
1871
|
+
const log = [
|
|
1872
|
+
`rb check \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1873
|
+
`variant: ${variant}`,
|
|
1874
|
+
`modules: ${order.length}`,
|
|
1875
|
+
`elapsed: ${elapsed.toFixed(2)}s`,
|
|
1876
|
+
errors.length ? `
|
|
1877
|
+
Errors:
|
|
1878
|
+
${errors.map((e) => ` line ${e.line}: ${e.message}`).join("\n")}` : "\nNo errors."
|
|
1879
|
+
].join("\n");
|
|
1880
|
+
fs.writeFileSync(logPath, log, "utf8");
|
|
1881
|
+
console.log();
|
|
1882
|
+
pInfo(`Log \u2192 ${gray(path.relative(root, logPath).replace(/\\/g, "/"))}`);
|
|
1883
|
+
console.log();
|
|
1884
|
+
if (errors.length) process.exit(1);
|
|
1885
|
+
}
|
|
1886
|
+
var init_check = __esm({
|
|
1887
|
+
"src/commands/check.ts"() {
|
|
1888
|
+
init_cjs_shims();
|
|
1889
|
+
init_ui();
|
|
1890
|
+
init_project();
|
|
1891
|
+
init_compiler();
|
|
1892
|
+
init_checker();
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
function read(name) {
|
|
1896
|
+
const p = path.join(DIR, name);
|
|
1897
|
+
return fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
|
|
1898
|
+
}
|
|
1899
|
+
function makeClaudeMd(name) {
|
|
1900
|
+
return read("CLAUDE.md").replace(/{NAME}/g, name);
|
|
1901
|
+
}
|
|
1902
|
+
function makeAgentsMd(name) {
|
|
1903
|
+
return read("AGENTS.md").replace(/{NAME}/g, name);
|
|
1904
|
+
}
|
|
1905
|
+
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, TEMPLATE_GITIGNORE, TEMPLATE_LAYOUT, TEMPLATE_PAGE_HOME, TEMPLATE_EX_SPEED, TEMPLATE_EX_FOV, TEMPLATE_EX_ESP, TEMPLATE_DEPLOY_EXAMPLE, MODULE_BOILERPLATE, MODULE_RBLUA_BOILERPLATE, REMOTE_BOILERPLATE;
|
|
1906
|
+
var init_templates = __esm({
|
|
1907
|
+
"src/templates.ts"() {
|
|
1908
|
+
init_cjs_shims();
|
|
1909
|
+
DIR = path.join(__dirname, "..", "templates");
|
|
1910
|
+
TEMPLATE_GLOBALS = read("globals.lua");
|
|
1911
|
+
TEMPLATE_REMOTES = read("remotes.lua");
|
|
1912
|
+
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1913
|
+
TEMPLATE_LAYOUT = read("layout.rblua");
|
|
1914
|
+
TEMPLATE_PAGE_HOME = read("page_home.rblua");
|
|
1915
|
+
TEMPLATE_EX_SPEED = read("example_speed.rblua");
|
|
1916
|
+
TEMPLATE_EX_FOV = read("example_fov.rblua");
|
|
1917
|
+
TEMPLATE_EX_ESP = read("example_esp.rblua");
|
|
1918
|
+
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1919
|
+
MODULE_BOILERPLATE = read("module.lua");
|
|
1920
|
+
MODULE_RBLUA_BOILERPLATE = read("module.rblua");
|
|
1921
|
+
REMOTE_BOILERPLATE = read("remote.lua");
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
// src/commands/docs.ts
|
|
1926
|
+
var docs_exports = {};
|
|
1927
|
+
__export(docs_exports, {
|
|
1928
|
+
cmdDocs: () => cmdDocs,
|
|
1929
|
+
generateDocs: () => generateDocs
|
|
1930
|
+
});
|
|
1931
|
+
function generateDocs() {
|
|
1932
|
+
const parts = [];
|
|
1933
|
+
for (const name of DOC_ORDER) {
|
|
1934
|
+
const p = pathe.join(DOCS_DIR, name);
|
|
1935
|
+
if (fs.existsSync(p)) parts.push(fs.readFileSync(p, "utf8").trim());
|
|
1936
|
+
}
|
|
1937
|
+
return parts.join("\n\n---\n\n");
|
|
1938
|
+
}
|
|
1939
|
+
function cmdDocs(args) {
|
|
1940
|
+
const out = args.output || "PULSE_DOCS.md";
|
|
1941
|
+
if (out === "-") {
|
|
1942
|
+
console.log(generateDocs());
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
pHeader("docs");
|
|
1946
|
+
const content = generateDocs();
|
|
1947
|
+
let dest = pathe.resolve(out);
|
|
1948
|
+
if (!out.startsWith("/") && !out.match(/^[A-Z]:/i)) {
|
|
1949
|
+
const root = findProjectRoot() ?? process.cwd();
|
|
1950
|
+
dest = pathe.join(root, out);
|
|
1951
|
+
}
|
|
1952
|
+
fs.writeFileSync(dest, content, "utf8");
|
|
1953
|
+
let label = dest;
|
|
1954
|
+
try {
|
|
1955
|
+
label = dest.replace(process.cwd().replace(/\\/g, "/"), ".").replace(/\\/g, "/");
|
|
1956
|
+
} catch {
|
|
1957
|
+
}
|
|
1958
|
+
pOk(label, `${content.length.toLocaleString()} chars`);
|
|
1959
|
+
console.log(`
|
|
1960
|
+
${cyan("\u2192")} add ${bold(out.split("/").pop())} to your LLM context for full Pulse reference
|
|
1961
|
+
`);
|
|
1962
|
+
}
|
|
1963
|
+
var DOC_ORDER, DOCS_DIR;
|
|
1964
|
+
var init_docs = __esm({
|
|
1965
|
+
"src/commands/docs.ts"() {
|
|
1966
|
+
init_cjs_shims();
|
|
1967
|
+
init_ui();
|
|
1968
|
+
init_project();
|
|
1969
|
+
DOC_ORDER = [
|
|
1970
|
+
"what-is-pulse.md",
|
|
1971
|
+
"getting-started.md",
|
|
1972
|
+
"components.md",
|
|
1973
|
+
"signals.md",
|
|
1974
|
+
"ui.md",
|
|
1975
|
+
"helpers.md",
|
|
1976
|
+
"syntax.md",
|
|
1977
|
+
"cli.md"
|
|
1978
|
+
];
|
|
1979
|
+
DOCS_DIR = pathe.join(__dirname, "..", "docs");
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// src/commands/init.ts
|
|
1984
|
+
var init_exports = {};
|
|
1985
|
+
__export(init_exports, {
|
|
1986
|
+
cmdInit: () => cmdInit
|
|
1987
|
+
});
|
|
1988
|
+
async function cmdInit(args) {
|
|
1989
|
+
const name = args.name;
|
|
1990
|
+
const dest = pathe.join(process.cwd(), name);
|
|
1991
|
+
if (fs.existsSync(dest) && !args.force) {
|
|
1992
|
+
pFail(`Directory '${name}' already exists`, "use --force to overwrite");
|
|
1993
|
+
process.exit(1);
|
|
1994
|
+
}
|
|
1995
|
+
pHeader("init");
|
|
1996
|
+
pSection(`Scaffolding ${bold(name + "/")}`);
|
|
1997
|
+
for (const dir of [
|
|
1998
|
+
"src/misc/helpers",
|
|
1999
|
+
"src/player",
|
|
2000
|
+
"src/visuals",
|
|
2001
|
+
"src/ui/pages",
|
|
2002
|
+
"build"
|
|
2003
|
+
]) {
|
|
2004
|
+
fs.mkdirSync(pathe.join(dest, dir), { recursive: true });
|
|
2005
|
+
}
|
|
2006
|
+
const files = [
|
|
2007
|
+
["src/misc/helpers/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
|
|
2008
|
+
["src/misc/remotes.lua", TEMPLATE_REMOTES],
|
|
2009
|
+
["src/ui/layout.rblua", TEMPLATE_LAYOUT.replace(/{NAME}/g, name)],
|
|
2010
|
+
["src/ui/pages/1_Home.rblua", TEMPLATE_PAGE_HOME],
|
|
2011
|
+
["src/player/SpeedHack.rblua", TEMPLATE_EX_SPEED],
|
|
2012
|
+
["src/player/FOVChanger.rblua", TEMPLATE_EX_FOV],
|
|
2013
|
+
["src/visuals/PlayerESP.rblua", TEMPLATE_EX_ESP],
|
|
2014
|
+
[".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
|
|
2015
|
+
[".gitignore", TEMPLATE_GITIGNORE],
|
|
2016
|
+
["CLAUDE.md", makeClaudeMd(name)],
|
|
2017
|
+
["AGENTS.md", makeAgentsMd(name)]
|
|
2018
|
+
];
|
|
2019
|
+
for (const [rel, content] of files) {
|
|
2020
|
+
const path = pathe.join(dest, rel);
|
|
2021
|
+
fs.mkdirSync(pathe.join(path, ".."), { recursive: true });
|
|
2022
|
+
fs.writeFileSync(path, content, "utf8");
|
|
2023
|
+
pOk(rel);
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
const docs = generateDocs();
|
|
2027
|
+
fs.writeFileSync(pathe.join(dest, "PULSE_DOCS.md"), docs, "utf8");
|
|
2028
|
+
pOk("PULSE_DOCS.md");
|
|
2029
|
+
} catch {
|
|
2030
|
+
pWarn("PULSE_DOCS.md (skipped \u2014 docs not available)");
|
|
2031
|
+
}
|
|
2032
|
+
const pkgJson = {
|
|
2033
|
+
name,
|
|
2034
|
+
private: true,
|
|
2035
|
+
scripts: {
|
|
2036
|
+
build: "rb build",
|
|
2037
|
+
watch: "rb watch",
|
|
2038
|
+
check: "rb check",
|
|
2039
|
+
deploy: "rb deploy",
|
|
2040
|
+
lint: "rb lint"
|
|
2041
|
+
},
|
|
2042
|
+
devDependencies: {
|
|
2043
|
+
"pulse-rb": `^${RB_VERSION}`,
|
|
2044
|
+
"@rb-pulse/core": `^${RB_VERSION}`,
|
|
2045
|
+
"@rb-pulse/ui": `^${RB_VERSION}`,
|
|
2046
|
+
"@rb-pulse/roblox": `^${RB_VERSION}`
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
fs.writeFileSync(pathe.join(dest, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
|
|
2050
|
+
pOk("package.json");
|
|
2051
|
+
try {
|
|
2052
|
+
await execa.execa("git", ["init"], { cwd: dest });
|
|
2053
|
+
await execa.execa("git", ["add", "-A"], { cwd: dest });
|
|
2054
|
+
await execa.execa("git", ["commit", "-m", `init: scaffold ${name}`], { cwd: dest });
|
|
2055
|
+
pOk("git init");
|
|
2056
|
+
} catch (e) {
|
|
2057
|
+
pWarn(`git init skipped ${gray(String(e?.message ?? "").split("\n")[0])}`);
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
pSection("Installing dependencies");
|
|
2061
|
+
await execa.execa("pnpm", ["install"], { cwd: dest, stdio: "inherit" });
|
|
2062
|
+
pOk("pnpm install");
|
|
2063
|
+
} catch {
|
|
2064
|
+
try {
|
|
2065
|
+
await execa.execa("npm", ["install"], { cwd: dest, stdio: "inherit" });
|
|
2066
|
+
pOk("npm install");
|
|
2067
|
+
} catch {
|
|
2068
|
+
pWarn("Could not run install \u2014 run pnpm install manually");
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
console.log();
|
|
2072
|
+
console.log(` ${dim("Created at: " + dest)}`);
|
|
2073
|
+
console.log(` ${cyan("\u2192")} cd ${name} && pnpm build`);
|
|
2074
|
+
console.log();
|
|
2075
|
+
}
|
|
2076
|
+
var init_init = __esm({
|
|
2077
|
+
"src/commands/init.ts"() {
|
|
2078
|
+
init_cjs_shims();
|
|
2079
|
+
init_ui();
|
|
2080
|
+
init_templates();
|
|
2081
|
+
init_docs();
|
|
2082
|
+
init_constants();
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
// src/commands/modules.ts
|
|
2087
|
+
var modules_exports = {};
|
|
2088
|
+
__export(modules_exports, {
|
|
2089
|
+
cmdList: () => cmdList,
|
|
2090
|
+
cmdNew: () => cmdNew,
|
|
2091
|
+
cmdRemove: () => cmdRemove,
|
|
2092
|
+
cmdStatus: () => cmdStatus
|
|
2093
|
+
});
|
|
2094
|
+
function toCamel(name) {
|
|
2095
|
+
const stem = pathe.basename(name, pathe.extname(name));
|
|
2096
|
+
if (/[_\-\s]/.test(stem)) {
|
|
2097
|
+
return stem.split(/[_\-\s]+/).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
|
|
2098
|
+
}
|
|
2099
|
+
return stem[0].toUpperCase() + stem.slice(1);
|
|
2100
|
+
}
|
|
2101
|
+
function cmdNew(args) {
|
|
2102
|
+
const root = requireProjectRoot();
|
|
2103
|
+
if (args.type === "module") {
|
|
2104
|
+
const plain = args.plain ?? false;
|
|
2105
|
+
let relPath = args.path;
|
|
2106
|
+
if (plain) {
|
|
2107
|
+
if (!relPath.endsWith(".lua")) relPath += ".lua";
|
|
2108
|
+
const dest = pathe.join(root, "src", relPath);
|
|
2109
|
+
const camel = toCamel(relPath);
|
|
2110
|
+
const lower = camel[0].toLowerCase() + camel.slice(1);
|
|
2111
|
+
const upper = camel.toUpperCase();
|
|
2112
|
+
const content = MODULE_BOILERPLATE.replace(/{UPPER}/g, upper).replace(/{Camel}/g, camel).replace(/{lower}/g, lower);
|
|
2113
|
+
if (fs.existsSync(dest)) {
|
|
2114
|
+
pFail(`src/${relPath} already exists`);
|
|
2115
|
+
process.exit(1);
|
|
2116
|
+
}
|
|
2117
|
+
fs.mkdirSync(pathe.join(dest, ".."), { recursive: true });
|
|
2118
|
+
fs.writeFileSync(dest, content, "utf8");
|
|
2119
|
+
pOk(`src/${relPath}`);
|
|
2120
|
+
console.log();
|
|
2121
|
+
pKv("File", `src/${relPath}`);
|
|
2122
|
+
pKv("Tip", "add to func for shared helpers; keep private state local");
|
|
2123
|
+
} else {
|
|
2124
|
+
if (!relPath.endsWith(".rblua")) relPath += ".rblua";
|
|
2125
|
+
const dest = pathe.join(root, "src", relPath);
|
|
2126
|
+
const camel = toCamel(relPath);
|
|
2127
|
+
if (fs.existsSync(dest)) {
|
|
2128
|
+
pFail(`src/${relPath} already exists`);
|
|
2129
|
+
process.exit(1);
|
|
2130
|
+
}
|
|
2131
|
+
fs.mkdirSync(pathe.join(dest, ".."), { recursive: true });
|
|
2132
|
+
fs.writeFileSync(dest, MODULE_RBLUA_BOILERPLATE, "utf8");
|
|
2133
|
+
pOk(`src/${relPath}`);
|
|
2134
|
+
console.log();
|
|
2135
|
+
pKv("Component", camel);
|
|
2136
|
+
pKv("Enabled", `Components.${camel}.enabled(true)`);
|
|
2137
|
+
pKv("Toggle", `Components.${camel}.Toggle()`);
|
|
2138
|
+
pKv("Listen", `Components.${camel}.onEnable:connect(...)`);
|
|
2139
|
+
}
|
|
2140
|
+
console.log();
|
|
2141
|
+
} else if (args.type === "remote") {
|
|
2142
|
+
const remoteName = args.path;
|
|
2143
|
+
const remotesPath = pathe.join(root, "src", "misc", "remotes.lua");
|
|
2144
|
+
if (!fs.existsSync(remotesPath)) {
|
|
2145
|
+
pFail("src/misc/remotes.lua not found");
|
|
2146
|
+
process.exit(1);
|
|
2147
|
+
}
|
|
2148
|
+
const lower = remoteName[0].toLowerCase() + remoteName.slice(1);
|
|
2149
|
+
const upper = remoteName.toUpperCase();
|
|
2150
|
+
const stub = REMOTE_BOILERPLATE.replace(/{UPPER}/g, upper).replace(/{lower}/g, lower).replace(/{Name}/g, remoteName);
|
|
2151
|
+
const existing = fs.readFileSync(remotesPath, "utf8");
|
|
2152
|
+
fs.writeFileSync(remotesPath, existing.trimEnd() + "\n" + stub, "utf8");
|
|
2153
|
+
pOk(`func.Remote_${remoteName}(...) ${gray("\u2192 remotes.lua")}`);
|
|
2154
|
+
console.log();
|
|
2155
|
+
} else {
|
|
2156
|
+
pFail(`Unknown type '${args.type}'`, "use: rb new module | rb new remote");
|
|
2157
|
+
process.exit(1);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
async function cmdRemove(args) {
|
|
2161
|
+
const root = requireProjectRoot();
|
|
2162
|
+
let relPath = args.path;
|
|
2163
|
+
if (!relPath.endsWith(".lua") && !relPath.endsWith(".rblua")) relPath += ".lua";
|
|
2164
|
+
const target = pathe.join(root, "src", relPath);
|
|
2165
|
+
if (!fs.existsSync(target)) {
|
|
2166
|
+
pFail(`src/${relPath} not found`);
|
|
2167
|
+
process.exit(1);
|
|
2168
|
+
}
|
|
2169
|
+
const ok = await prompts.confirm({ message: `Remove src/${relPath}?` });
|
|
2170
|
+
if (!ok || prompts.isCancel(ok)) {
|
|
2171
|
+
pDim("aborted");
|
|
2172
|
+
console.log();
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
const { rmSync } = await import('fs');
|
|
2176
|
+
rmSync(target);
|
|
2177
|
+
pOk(`Removed ${gray("src/" + relPath)}`);
|
|
2178
|
+
console.log();
|
|
2179
|
+
}
|
|
2180
|
+
function cmdList(args) {
|
|
2181
|
+
const root = requireProjectRoot();
|
|
2182
|
+
const compiler = new Compiler(root);
|
|
2183
|
+
const compat = args.compat ?? false;
|
|
2184
|
+
const order = compiler.getLoadOrder({ compat });
|
|
2185
|
+
const variant = compat ? "compat" : "standard";
|
|
2186
|
+
pHeader("list");
|
|
2187
|
+
pSection(`Load order ${gray("(" + variant + " \xB7 " + order.length + " modules)")}`);
|
|
2188
|
+
pHr();
|
|
2189
|
+
for (let i = 0; i < order.length; i++) {
|
|
2190
|
+
const num = gray(`${(i + 1).toString().padStart(3)}`);
|
|
2191
|
+
const rel = pathe.relative(root, order[i]).replace(/\\/g, "/");
|
|
2192
|
+
console.log(` ${num} ${rel}`);
|
|
2193
|
+
}
|
|
2194
|
+
console.log();
|
|
2195
|
+
}
|
|
2196
|
+
async function cmdStatus(_args) {
|
|
2197
|
+
const root = requireProjectRoot();
|
|
2198
|
+
const compiler = new Compiler(root);
|
|
2199
|
+
pHeader("status");
|
|
2200
|
+
console.log(` ${bold(root.split("/").pop())}`);
|
|
2201
|
+
pHr();
|
|
2202
|
+
const order = compiler.getLoadOrder({ compat: false });
|
|
2203
|
+
pKv("Root", root);
|
|
2204
|
+
pKv("Modules", String(order.length));
|
|
2205
|
+
const buildScript = pathe.join(root, "build", "script.lua");
|
|
2206
|
+
if (fs.existsSync(buildScript)) {
|
|
2207
|
+
const { statSync: statSync2 } = await import('fs');
|
|
2208
|
+
const content = fs.readFileSync(buildScript, "utf8");
|
|
2209
|
+
const mtime = new Date(statSync2(buildScript).mtimeMs).toISOString().slice(0, 16).replace("T", " ");
|
|
2210
|
+
pKv("Last build", `${mtime} ${gray(content.length.toLocaleString() + " chars")}`);
|
|
2211
|
+
} else {
|
|
2212
|
+
pKv("Last build", dim("none"));
|
|
2213
|
+
}
|
|
2214
|
+
try {
|
|
2215
|
+
const { stdout } = await execa.execa("git", ["status", "--short"], { cwd: root });
|
|
2216
|
+
const changed = stdout.trim().split("\n").filter(Boolean);
|
|
2217
|
+
pSection(`Git ${gray("(" + changed.length + " change" + (changed.length !== 1 ? "s" : "") + ")")}`);
|
|
2218
|
+
for (const line of changed.slice(0, 12)) {
|
|
2219
|
+
const flag = line.slice(0, 2).trim();
|
|
2220
|
+
const path = line.slice(3);
|
|
2221
|
+
const color = flag === "A" ? green : flag.startsWith("M") ? yellow : red;
|
|
2222
|
+
console.log(` ${color(flag.padEnd(2))} ${path}`);
|
|
2223
|
+
}
|
|
2224
|
+
if (changed.length > 12) pDim(`... and ${changed.length - 12} more`);
|
|
2225
|
+
} catch {
|
|
2226
|
+
pKv("Git", dim("not a repository"));
|
|
2227
|
+
}
|
|
2228
|
+
console.log();
|
|
2229
|
+
}
|
|
2230
|
+
var init_modules = __esm({
|
|
2231
|
+
"src/commands/modules.ts"() {
|
|
2232
|
+
init_cjs_shims();
|
|
2233
|
+
init_ui();
|
|
2234
|
+
init_project();
|
|
2235
|
+
init_compiler();
|
|
2236
|
+
init_templates();
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
|
|
2240
|
+
// src/commands/git.ts
|
|
2241
|
+
var git_exports = {};
|
|
2242
|
+
__export(git_exports, {
|
|
2243
|
+
cmdHistory: () => cmdHistory,
|
|
2244
|
+
cmdRestore: () => cmdRestore,
|
|
2245
|
+
cmdSave: () => cmdSave
|
|
2246
|
+
});
|
|
2247
|
+
async function cmdSave(args) {
|
|
2248
|
+
const root = requireProjectRoot();
|
|
2249
|
+
pHeader("save");
|
|
2250
|
+
const msg = args.message || `checkpoint: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ")}`;
|
|
2251
|
+
try {
|
|
2252
|
+
await execa.execa("git", ["add", "-A"], { cwd: root });
|
|
2253
|
+
const { stdout } = await execa.execa("git", ["commit", "-m", msg], { cwd: root });
|
|
2254
|
+
const lines = stdout.trim().split("\n");
|
|
2255
|
+
pOk(lines[0] ?? msg);
|
|
2256
|
+
for (const line of lines.slice(1)) pDim(line);
|
|
2257
|
+
} catch (e) {
|
|
2258
|
+
const out = String(e?.stdout ?? "") + String(e?.stderr ?? "");
|
|
2259
|
+
pInfo(out.trim() || "nothing to commit");
|
|
2260
|
+
}
|
|
2261
|
+
console.log();
|
|
2262
|
+
}
|
|
2263
|
+
async function cmdHistory(args) {
|
|
2264
|
+
const root = requireProjectRoot();
|
|
2265
|
+
pHeader("history");
|
|
2266
|
+
const n = args.n ?? 10;
|
|
2267
|
+
try {
|
|
2268
|
+
const { stdout } = await execa.execa("git", [
|
|
2269
|
+
"log",
|
|
2270
|
+
`-${n}`,
|
|
2271
|
+
"--format=%h %s %cd",
|
|
2272
|
+
"--date=format:%Y-%m-%d %H:%M"
|
|
2273
|
+
], { cwd: root });
|
|
2274
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
2275
|
+
if (!lines.length) {
|
|
2276
|
+
pInfo("No commits yet");
|
|
2277
|
+
} else {
|
|
2278
|
+
for (const line of lines) {
|
|
2279
|
+
const [sha, ...rest] = line.split(" ");
|
|
2280
|
+
console.log(` ${gray(sha)} ${rest.join(" ")}`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
} catch {
|
|
2284
|
+
pFail("Not a git repository");
|
|
2285
|
+
}
|
|
2286
|
+
console.log();
|
|
2287
|
+
}
|
|
2288
|
+
async function cmdRestore(args) {
|
|
2289
|
+
const root = requireProjectRoot();
|
|
2290
|
+
const ref = args.ref;
|
|
2291
|
+
const { confirm: confirm2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
|
|
2292
|
+
const ok = await confirm2({
|
|
2293
|
+
message: `Restore to '${ref}'? Unsaved changes will be lost.`
|
|
2294
|
+
});
|
|
2295
|
+
if (!ok || ok === /* @__PURE__ */ Symbol.for("clack:cancel")) {
|
|
2296
|
+
console.log(`
|
|
2297
|
+
${dim("aborted")}
|
|
2298
|
+
`);
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
try {
|
|
2302
|
+
await execa.execa("git", ["checkout", ref, "--", "."], { cwd: root });
|
|
2303
|
+
pOk(`Restored to ${bold(ref)}`);
|
|
2304
|
+
} catch (e) {
|
|
2305
|
+
pFail(String(e?.stderr ?? e?.message ?? e).split("\n")[0]);
|
|
2306
|
+
process.exit(1);
|
|
2307
|
+
}
|
|
2308
|
+
console.log();
|
|
2309
|
+
}
|
|
2310
|
+
var init_git = __esm({
|
|
2311
|
+
"src/commands/git.ts"() {
|
|
2312
|
+
init_cjs_shims();
|
|
2313
|
+
init_ui();
|
|
2314
|
+
init_project();
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
|
|
2318
|
+
// src/commands/deploy.ts
|
|
2319
|
+
var deploy_exports = {};
|
|
2320
|
+
__export(deploy_exports, {
|
|
2321
|
+
cmdDeploy: () => cmdDeploy
|
|
2322
|
+
});
|
|
2323
|
+
function readConfig(root) {
|
|
2324
|
+
const cfgPath = pathe.join(root, CONFIG_FILE);
|
|
2325
|
+
if (!fs.existsSync(cfgPath)) return {};
|
|
2326
|
+
const config = {};
|
|
2327
|
+
for (const line of fs.readFileSync(cfgPath, "utf8").split("\n")) {
|
|
2328
|
+
const trimmed = line.trim();
|
|
2329
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2330
|
+
const eq = trimmed.indexOf("=");
|
|
2331
|
+
if (eq < 0) continue;
|
|
2332
|
+
const key = trimmed.slice(0, eq).trim();
|
|
2333
|
+
config[key] = trimmed.slice(eq + 1).trim();
|
|
2334
|
+
}
|
|
2335
|
+
return config;
|
|
2336
|
+
}
|
|
2337
|
+
function printSetupHelp(root) {
|
|
2338
|
+
console.log();
|
|
2339
|
+
pInfo("To set up deployment:");
|
|
2340
|
+
console.log(` ${dim("1.")} Copy ${gray(".rb-deploy.example")} to ${gray(".rb-deploy")}`);
|
|
2341
|
+
console.log(` ${dim("2.")} Fill in GITHUB_TOKEN and GITHUB_REPO`);
|
|
2342
|
+
console.log(` ${dim("3.")} Run ${cyan("rb deploy")} again`);
|
|
2343
|
+
console.log();
|
|
2344
|
+
}
|
|
2345
|
+
async function cmdDeploy(args) {
|
|
2346
|
+
pHeader("deploy");
|
|
2347
|
+
const root = findProjectRoot();
|
|
2348
|
+
if (!root) {
|
|
2349
|
+
pFail("Not inside a Pulse project (no src/ directory found)");
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
const config = readConfig(root);
|
|
2353
|
+
const token = args.token || config.GITHUB_TOKEN || "";
|
|
2354
|
+
const repo = args.repo || config.GITHUB_REPO || "";
|
|
2355
|
+
const file = args.file || config.GITHUB_FILE || "script.obf.lua";
|
|
2356
|
+
const branch = args.branch || config.GITHUB_BRANCH || "main";
|
|
2357
|
+
if (!token || token === "ghp_your_token_here") {
|
|
2358
|
+
pFail("No GITHUB_TOKEN set", `Add it to ${CONFIG_FILE}`);
|
|
2359
|
+
printSetupHelp();
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
if (!repo || repo === "yourname/my-script-release") {
|
|
2363
|
+
pFail("No GITHUB_REPO set", `Add it to ${CONFIG_FILE}`);
|
|
2364
|
+
printSetupHelp();
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
const scriptPath = pathe.join(root, "build", "script.obf.lua");
|
|
2368
|
+
if (!fs.existsSync(scriptPath)) {
|
|
2369
|
+
pInfo("build/script.obf.lua not found \u2014 run rb build first to produce it");
|
|
2370
|
+
process.exit(1);
|
|
2371
|
+
}
|
|
2372
|
+
const contentB64 = Buffer.from(fs.readFileSync(scriptPath)).toString("base64");
|
|
2373
|
+
const apiUrl = `https://api.github.com/repos/${repo}/contents/${file}`;
|
|
2374
|
+
const headers = {
|
|
2375
|
+
Authorization: `token ${token}`,
|
|
2376
|
+
Accept: "application/vnd.github.v3+json",
|
|
2377
|
+
"Content-Type": "application/json",
|
|
2378
|
+
"User-Agent": "rb-deploy/1.0"
|
|
2379
|
+
};
|
|
2380
|
+
let sha;
|
|
2381
|
+
try {
|
|
2382
|
+
const existing = await ofetch.ofetch(`${apiUrl}?ref=${branch}`, { headers });
|
|
2383
|
+
sha = existing.sha;
|
|
2384
|
+
} catch {
|
|
2385
|
+
}
|
|
2386
|
+
pInfo(`Pushing ${gray(file)} \u2192 ${gray(repo)} (${branch})`);
|
|
2387
|
+
try {
|
|
2388
|
+
await ofetch.ofetch(apiUrl, {
|
|
2389
|
+
method: "PUT",
|
|
2390
|
+
headers,
|
|
2391
|
+
body: { message: "deploy: update script", content: contentB64, branch, ...sha ? { sha } : {} }
|
|
2392
|
+
});
|
|
2393
|
+
} catch (e) {
|
|
2394
|
+
pFail(String(e?.data?.message ?? e?.message ?? e));
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
const rawUrl = `https://raw.githubusercontent.com/${repo}/${branch}/${file}`;
|
|
2398
|
+
const loadstring = `loadstring(game:HttpGet("${rawUrl}"))()`;
|
|
2399
|
+
pOk(`Deployed ${gray(repo + "/" + file)}`);
|
|
2400
|
+
console.log();
|
|
2401
|
+
console.log(` ${bold("Loadstring URL:")}`);
|
|
2402
|
+
console.log(` ${cyan(loadstring)}`);
|
|
2403
|
+
console.log();
|
|
2404
|
+
}
|
|
2405
|
+
var CONFIG_FILE;
|
|
2406
|
+
var init_deploy = __esm({
|
|
2407
|
+
"src/commands/deploy.ts"() {
|
|
2408
|
+
init_cjs_shims();
|
|
2409
|
+
init_ui();
|
|
2410
|
+
init_project();
|
|
2411
|
+
CONFIG_FILE = ".rb-deploy";
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
|
|
2415
|
+
// src/commands/lint.ts
|
|
2416
|
+
var lint_exports = {};
|
|
2417
|
+
__export(lint_exports, {
|
|
2418
|
+
cmdLint: () => cmdLint
|
|
2419
|
+
});
|
|
2420
|
+
function stripStrings(src) {
|
|
2421
|
+
src = src.replace(/\[=*\[[\s\S]*?\]=*\]/g, '""');
|
|
2422
|
+
src = src.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
2423
|
+
src = src.replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
2424
|
+
return src;
|
|
2425
|
+
}
|
|
2426
|
+
function stripComments(src) {
|
|
2427
|
+
src = src.replace(/--\[=*\[[\s\S]*?\]=*\]/g, "");
|
|
2428
|
+
src = src.replace(/--[^\n]*/g, "");
|
|
2429
|
+
return src;
|
|
2430
|
+
}
|
|
2431
|
+
function clean(src) {
|
|
2432
|
+
return stripStrings(stripComments(src));
|
|
2433
|
+
}
|
|
2434
|
+
function parseComp(path) {
|
|
2435
|
+
const raw = fs.readFileSync(path, "utf8");
|
|
2436
|
+
const cleaned = clean(raw);
|
|
2437
|
+
const info = {
|
|
2438
|
+
name: path.split("/").pop().replace(/\.(rblua|lua)$/, ""),
|
|
2439
|
+
file: path,
|
|
2440
|
+
signals: /* @__PURE__ */ new Set(),
|
|
2441
|
+
uiSignals: /* @__PURE__ */ new Set(),
|
|
2442
|
+
handlers: [],
|
|
2443
|
+
whenGuards: /* @__PURE__ */ new Set(),
|
|
2444
|
+
everyRefs: /* @__PURE__ */ new Set(),
|
|
2445
|
+
funcDefs: /* @__PURE__ */ new Set(),
|
|
2446
|
+
localFuncs: /* @__PURE__ */ new Set(),
|
|
2447
|
+
publicFuncs: /* @__PURE__ */ new Set(),
|
|
2448
|
+
raw,
|
|
2449
|
+
cleaned
|
|
2450
|
+
};
|
|
2451
|
+
for (const m of cleaned.matchAll(/\bsignal\s+(\w+)\s*=/g)) info.signals.add(m[1]);
|
|
2452
|
+
for (const m of cleaned.matchAll(/->\s+(\w+)/g)) info.uiSignals.add(m[1]);
|
|
2453
|
+
for (const m of cleaned.matchAll(/\bon\s+(\w+)/g)) info.handlers.push(m[1]);
|
|
2454
|
+
for (const m of cleaned.matchAll(/\bwhen\s+(\w+)\b/g)) info.whenGuards.add(m[1]);
|
|
2455
|
+
for (const m of cleaned.matchAll(/\bevery\s+([A-Za-z_]\w*)\b/g)) info.everyRefs.add(m[1]);
|
|
2456
|
+
for (const m of cleaned.matchAll(/\bfunc\.(\w+)\s*=\s*(?!nil\b|false\b|true\b|\d)/g))
|
|
2457
|
+
info.funcDefs.add(m[1]);
|
|
2458
|
+
for (const m of cleaned.matchAll(/\bfunc\s+(\w+)\s*\(/g)) {
|
|
2459
|
+
const before = cleaned.slice(Math.max(0, m.index - 6), m.index);
|
|
2460
|
+
if (!before.includes("local")) info.publicFuncs.add(m[1]);
|
|
2461
|
+
}
|
|
2462
|
+
for (const m of cleaned.matchAll(/\blocal\s+func\s+(\w+)\s*\(/g))
|
|
2463
|
+
info.localFuncs.add(m[1]);
|
|
2464
|
+
return info;
|
|
2465
|
+
}
|
|
2466
|
+
function countRefs(pattern, text2) {
|
|
2467
|
+
return (text2.match(new RegExp(pattern, "g")) ?? []).length;
|
|
2468
|
+
}
|
|
2469
|
+
function cmdLint(_args) {
|
|
2470
|
+
const root = requireProjectRoot();
|
|
2471
|
+
const srcDir = pathe.join(root, "src");
|
|
2472
|
+
const uiDir = pathe.join(srcDir, "ui");
|
|
2473
|
+
pHeader("lint");
|
|
2474
|
+
const rblua = globby.globbySync("**/*.rblua", { cwd: srcDir, absolute: true }).filter((f) => !f.startsWith(uiDir + "/") && !f.startsWith(uiDir + "\\"));
|
|
2475
|
+
const components = {};
|
|
2476
|
+
for (const f of rblua) {
|
|
2477
|
+
const info = parseComp(f);
|
|
2478
|
+
components[info.name] = info;
|
|
2479
|
+
}
|
|
2480
|
+
const allFiles = globby.globbySync("**/*.{rblua,lua}", { cwd: srcDir, absolute: true });
|
|
2481
|
+
const allClean = allFiles.map((f) => {
|
|
2482
|
+
try {
|
|
2483
|
+
return clean(fs.readFileSync(f, "utf8"));
|
|
2484
|
+
} catch {
|
|
2485
|
+
return "";
|
|
2486
|
+
}
|
|
2487
|
+
}).join("\n");
|
|
2488
|
+
const issues = [];
|
|
2489
|
+
for (const info of Object.values(components)) {
|
|
2490
|
+
for (const sig of [...info.signals].sort()) {
|
|
2491
|
+
const reasons = [];
|
|
2492
|
+
if (info.handlers.includes(sig)) reasons.push("on handler");
|
|
2493
|
+
if (info.uiSignals.has(sig)) reasons.push("UI widget");
|
|
2494
|
+
if (info.whenGuards.has(sig)) reasons.push("when guard");
|
|
2495
|
+
if (info.everyRefs.has(sig)) reasons.push("every throttle");
|
|
2496
|
+
if (new RegExp(`\\b${sig}\\(\\)`).test(info.cleaned)) reasons.push("signal() call");
|
|
2497
|
+
if (countRefs(`\\b${sig}\\b`, info.cleaned) > 1) reasons.push("bare name in code");
|
|
2498
|
+
if (new RegExp(`\\b${info.name}\\.${sig}\\b`).test(allClean)) reasons.push("cross-component read");
|
|
2499
|
+
if (new RegExp(`\\bComponents\\.\\w+\\.${sig}\\b`).test(allClean)) reasons.push("Components.* read");
|
|
2500
|
+
if (!reasons.length) {
|
|
2501
|
+
issues.push({
|
|
2502
|
+
severity: "warn",
|
|
2503
|
+
kind: "dead_signal",
|
|
2504
|
+
comp: info.name,
|
|
2505
|
+
detail: `signal '${sig}' declared but never used`,
|
|
2506
|
+
file: info.file
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
for (const info of Object.values(components)) {
|
|
2512
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2513
|
+
for (const h of info.handlers) {
|
|
2514
|
+
if (seen.has(h) || SERVICE_EVENTS.has(h) || info.signals.has(h)) {
|
|
2515
|
+
seen.add(h);
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
seen.add(h);
|
|
2519
|
+
issues.push({
|
|
2520
|
+
severity: "warn",
|
|
2521
|
+
kind: "ghost_handler",
|
|
2522
|
+
comp: info.name,
|
|
2523
|
+
detail: `on ${h} {} \u2014 '${h}' is not a declared signal or known event`,
|
|
2524
|
+
file: info.file
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
const seenGhost = /* @__PURE__ */ new Set();
|
|
2529
|
+
for (const f of allFiles.filter((f2) => f2.endsWith(".rblua"))) {
|
|
2530
|
+
let src;
|
|
2531
|
+
try {
|
|
2532
|
+
src = clean(fs.readFileSync(f, "utf8"));
|
|
2533
|
+
} catch {
|
|
2534
|
+
continue;
|
|
2535
|
+
}
|
|
2536
|
+
for (const m of src.matchAll(/\bComponents\.(\w+)\.(\w+)/g)) {
|
|
2537
|
+
const [refComp, refAttr] = [m[1], m[2]];
|
|
2538
|
+
const key = `${refComp}.${refAttr}`;
|
|
2539
|
+
if (seenGhost.has(key)) continue;
|
|
2540
|
+
if (!components[refComp]) {
|
|
2541
|
+
seenGhost.add(key);
|
|
2542
|
+
issues.push({
|
|
2543
|
+
severity: "error",
|
|
2544
|
+
kind: "ghost_ref",
|
|
2545
|
+
comp: f.split("/").pop().replace(/\.(rblua|lua)$/, ""),
|
|
2546
|
+
detail: `Components.${refComp}.${refAttr} \u2014 component '${refComp}' not found`,
|
|
2547
|
+
file: f
|
|
2548
|
+
});
|
|
2549
|
+
} else {
|
|
2550
|
+
const c2 = components[refComp];
|
|
2551
|
+
if (!c2.signals.has(refAttr) && !c2.publicFuncs.has(refAttr) && !refAttr.startsWith("_")) {
|
|
2552
|
+
seenGhost.add(key);
|
|
2553
|
+
issues.push({
|
|
2554
|
+
severity: "error",
|
|
2555
|
+
kind: "ghost_ref",
|
|
2556
|
+
comp: f.split("/").pop().replace(/\.(rblua|lua)$/, ""),
|
|
2557
|
+
detail: `Components.${refComp}.${refAttr} \u2014 '${refAttr}' not a signal or func on ${refComp}`,
|
|
2558
|
+
file: f
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
const allFuncDefs = {};
|
|
2565
|
+
for (const f of allFiles) {
|
|
2566
|
+
let src;
|
|
2567
|
+
try {
|
|
2568
|
+
src = clean(fs.readFileSync(f, "utf8"));
|
|
2569
|
+
} catch {
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
for (const m of src.matchAll(/\bfunc\.(\w+)\s*=\s*(?!nil\b|false\b|true\b|\d)/g))
|
|
2573
|
+
allFuncDefs[m[1]] = f;
|
|
2574
|
+
}
|
|
2575
|
+
for (const [fname, defFile] of Object.entries(allFuncDefs)) {
|
|
2576
|
+
if (countRefs(`\\bfunc\\.${fname}\\b`, allClean) <= 1) {
|
|
2577
|
+
issues.push({
|
|
2578
|
+
severity: "warn",
|
|
2579
|
+
kind: "orphan_func",
|
|
2580
|
+
comp: defFile.split("/").pop().replace(/\.(rblua|lua)$/, ""),
|
|
2581
|
+
detail: `func.${fname} is defined but never called`,
|
|
2582
|
+
file: defFile
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
for (const info of Object.values(components)) {
|
|
2587
|
+
for (const fname of [...info.localFuncs].sort()) {
|
|
2588
|
+
if (countRefs(`\\b${fname}\\b`, info.cleaned) <= 1) {
|
|
2589
|
+
issues.push({
|
|
2590
|
+
severity: "warn",
|
|
2591
|
+
kind: "dead_local",
|
|
2592
|
+
comp: info.name,
|
|
2593
|
+
detail: `local func ${fname}() defined but never called`,
|
|
2594
|
+
file: info.file
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
const startup = pathe.join(srcDir, "startup.lua");
|
|
2600
|
+
if (fs.existsSync(startup)) {
|
|
2601
|
+
const startupClean = clean(fs.readFileSync(startup, "utf8"));
|
|
2602
|
+
const seenMissing = /* @__PURE__ */ new Set();
|
|
2603
|
+
for (const m of startupClean.matchAll(/\bfunc\.(\w+)\b/g)) {
|
|
2604
|
+
const fname = m[1];
|
|
2605
|
+
if (seenMissing.has(fname)) continue;
|
|
2606
|
+
if (!allFuncDefs[fname]) {
|
|
2607
|
+
seenMissing.add(fname);
|
|
2608
|
+
issues.push({
|
|
2609
|
+
severity: "error",
|
|
2610
|
+
kind: "missing_func",
|
|
2611
|
+
comp: "startup",
|
|
2612
|
+
detail: `startup.lua calls func.${fname} \u2014 never defined anywhere`,
|
|
2613
|
+
file: startup
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
if (!issues.length) {
|
|
2619
|
+
pSection("Results");
|
|
2620
|
+
pOk("No issues found", `${Object.keys(components).length} components analysed`);
|
|
2621
|
+
console.log();
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const KIND_LABELS = {
|
|
2625
|
+
dead_signal: "dead signal",
|
|
2626
|
+
ghost_handler: "unknown handler",
|
|
2627
|
+
ghost_ref: "ghost reference",
|
|
2628
|
+
orphan_func: "orphan func.*",
|
|
2629
|
+
dead_local: "dead local func",
|
|
2630
|
+
missing_func: "missing func.*"
|
|
2631
|
+
};
|
|
2632
|
+
pSection("Issues");
|
|
2633
|
+
const byComp = {};
|
|
2634
|
+
for (const issue of issues) {
|
|
2635
|
+
(byComp[issue.comp] ??= []).push(issue);
|
|
2636
|
+
}
|
|
2637
|
+
for (const compName of Object.keys(byComp).sort()) {
|
|
2638
|
+
const compIssues = byComp[compName].sort((a, b) => {
|
|
2639
|
+
const sa = a.severity === "error" ? 0 : 1;
|
|
2640
|
+
const sb = b.severity === "error" ? 0 : 1;
|
|
2641
|
+
return sa - sb || a.kind.localeCompare(b.kind) || a.detail.localeCompare(b.detail);
|
|
2642
|
+
});
|
|
2643
|
+
const fileTag = compIssues[0]?.file ? ` ${gray(pathe.relative(root, compIssues[0].file).replace(/\\/g, "/"))}` : "";
|
|
2644
|
+
console.log(`
|
|
2645
|
+
${bold(compName)}${fileTag}`);
|
|
2646
|
+
for (const issue of compIssues) {
|
|
2647
|
+
const icon = issue.severity === "error" ? red("\u2717") : yellow("!");
|
|
2648
|
+
const label = gray(`[${KIND_LABELS[issue.kind] ?? issue.kind}]`);
|
|
2649
|
+
console.log(` ${icon} ${label} ${issue.detail}`);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
pHr();
|
|
2653
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
2654
|
+
const warns = issues.filter((i) => i.severity === "warn").length;
|
|
2655
|
+
const parts = [];
|
|
2656
|
+
if (errors) parts.push(red(`${errors} error${errors !== 1 ? "s" : ""}`));
|
|
2657
|
+
if (warns) parts.push(yellow(`${warns} warning${warns !== 1 ? "s" : ""}`));
|
|
2658
|
+
console.log(`
|
|
2659
|
+
${parts.join(", ")} in ${Object.keys(byComp).length} component(s)
|
|
2660
|
+
`);
|
|
2661
|
+
}
|
|
2662
|
+
var SERVICE_EVENTS;
|
|
2663
|
+
var init_lint = __esm({
|
|
2664
|
+
"src/commands/lint.ts"() {
|
|
2665
|
+
init_cjs_shims();
|
|
2666
|
+
init_ui();
|
|
2667
|
+
init_project();
|
|
2668
|
+
SERVICE_EVENTS = /* @__PURE__ */ new Set([
|
|
2669
|
+
"Heartbeat",
|
|
2670
|
+
"RenderStepped",
|
|
2671
|
+
"Stepped",
|
|
2672
|
+
"InputBegan",
|
|
2673
|
+
"InputEnded",
|
|
2674
|
+
"Respawn",
|
|
2675
|
+
"CharacterAdded",
|
|
2676
|
+
"CharacterRemoving"
|
|
2677
|
+
]);
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
// src/commands/obfuscate.ts
|
|
2682
|
+
var obfuscate_exports = {};
|
|
2683
|
+
__export(obfuscate_exports, {
|
|
2684
|
+
cmdObfuscate: () => cmdObfuscate
|
|
2685
|
+
});
|
|
2686
|
+
async function cmdObfuscate(args) {
|
|
2687
|
+
const root = requireProjectRoot();
|
|
2688
|
+
const buildDir = pathe.join(root, "build");
|
|
2689
|
+
pHeader("obfuscate");
|
|
2690
|
+
if (!fs.existsSync(buildDir)) {
|
|
2691
|
+
pFail("No build/ directory", "run rb build --no-obfuscate first");
|
|
2692
|
+
process.exit(1);
|
|
2693
|
+
}
|
|
2694
|
+
const builds = fs.readdirSync(buildDir).filter((f) => f.endsWith(".lua") && !f.includes(".obf.") && !f.includes(".dev.")).map((f) => pathe.join(buildDir, f));
|
|
2695
|
+
if (!builds.length) {
|
|
2696
|
+
pFail("No build files found", "run rb build --no-obfuscate first");
|
|
2697
|
+
process.exit(1);
|
|
2698
|
+
}
|
|
2699
|
+
pSection(`Found ${gray(builds.map((b) => b.split("/").pop()).join(", "))}`);
|
|
2700
|
+
const outputs = await obfuscatePipeline(builds, root);
|
|
2701
|
+
if (args.copy && outputs.length) {
|
|
2702
|
+
const toCopy = outputs.find((p) => !p.includes("compat")) ?? outputs[0];
|
|
2703
|
+
copyToClipboard(fs.readFileSync(toCopy, "utf8"), toCopy.split("/").pop());
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
var init_obfuscate = __esm({
|
|
2707
|
+
"src/commands/obfuscate.ts"() {
|
|
2708
|
+
init_cjs_shims();
|
|
2709
|
+
init_ui();
|
|
2710
|
+
init_project();
|
|
2711
|
+
init_obfuscator();
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
|
|
2715
|
+
// src/commands/mcp.ts
|
|
2716
|
+
var mcp_exports = {};
|
|
2717
|
+
__export(mcp_exports, {
|
|
2718
|
+
cmdMcp: () => cmdMcp,
|
|
2719
|
+
printMcpSetup: () => printMcpSetup
|
|
2720
|
+
});
|
|
2721
|
+
async function cmdMcp(_args) {
|
|
2722
|
+
let McpServer;
|
|
2723
|
+
let StdioServerTransport;
|
|
2724
|
+
let z;
|
|
2725
|
+
try {
|
|
2726
|
+
;
|
|
2727
|
+
({ McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js'));
|
|
2728
|
+
({ StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js'));
|
|
2729
|
+
({ z } = await import('zod'));
|
|
2730
|
+
} catch (e) {
|
|
2731
|
+
pFail("MCP SDK not available", String(e?.message ?? ""));
|
|
2732
|
+
pInfo("Run: pnpm add @modelcontextprotocol/sdk zod");
|
|
2733
|
+
process.exit(1);
|
|
2734
|
+
}
|
|
2735
|
+
const server = new McpServer({
|
|
2736
|
+
name: "pulse-rb",
|
|
2737
|
+
version: RB_VERSION
|
|
2738
|
+
});
|
|
2739
|
+
server.tool(
|
|
2740
|
+
"build",
|
|
2741
|
+
"Compile TypeScript / .rblua sources, obfuscate with Ironbrew2, output to build/.\nReturns the output file paths and a summary of errors if any.",
|
|
2742
|
+
{
|
|
2743
|
+
noObfuscate: z.boolean().optional().default(false).describe("Skip obfuscation \u2014 output plain Lua (faster for iteration)"),
|
|
2744
|
+
noCompat: z.boolean().optional().default(false).describe("Skip the compat build (script.compat.lua)"),
|
|
2745
|
+
dev: z.boolean().optional().default(false).describe("Also emit a dev build with the debug overlay (script.dev.lua)"),
|
|
2746
|
+
preset: z.enum(["Low", "Medium", "Strong"]).optional().default("Medium").describe("Obfuscation strength")
|
|
2747
|
+
},
|
|
2748
|
+
async (args) => {
|
|
2749
|
+
const { cmdBuild: cmdBuild2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
2750
|
+
const origLog = console.log.bind(console);
|
|
2751
|
+
const origErr = console.error.bind(console);
|
|
2752
|
+
const lines = [];
|
|
2753
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2754
|
+
console.error = (...a) => lines.push(a.map(String).join(" "));
|
|
2755
|
+
let exitCode = 0;
|
|
2756
|
+
const origExit = process.exit.bind(process);
|
|
2757
|
+
process.exit = (code) => {
|
|
2758
|
+
exitCode = code ?? 0;
|
|
2759
|
+
};
|
|
2760
|
+
try {
|
|
2761
|
+
await cmdBuild2({
|
|
2762
|
+
noObfuscate: args.noObfuscate ?? false,
|
|
2763
|
+
noCompat: args.noCompat ?? false,
|
|
2764
|
+
dev: args.dev ?? false,
|
|
2765
|
+
preset: args.preset ?? "Medium",
|
|
2766
|
+
copy: false,
|
|
2767
|
+
ui: void 0
|
|
2768
|
+
});
|
|
2769
|
+
} finally {
|
|
2770
|
+
console.log = origLog;
|
|
2771
|
+
console.error = origErr;
|
|
2772
|
+
process.exit = origExit;
|
|
2773
|
+
}
|
|
2774
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2775
|
+
return {
|
|
2776
|
+
content: [{
|
|
2777
|
+
type: "text",
|
|
2778
|
+
text: exitCode === 0 ? `Build succeeded.
|
|
2779
|
+
|
|
2780
|
+
${text2}` : `Build failed (exit ${exitCode}).
|
|
2781
|
+
|
|
2782
|
+
${text2}`
|
|
2783
|
+
}],
|
|
2784
|
+
isError: exitCode !== 0
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
);
|
|
2788
|
+
server.tool(
|
|
2789
|
+
"check",
|
|
2790
|
+
"Syntax-check the compiled output with luaparse without writing build files.",
|
|
2791
|
+
{
|
|
2792
|
+
compat: z.boolean().optional().default(false).describe("Check the compat build instead of standard")
|
|
2793
|
+
},
|
|
2794
|
+
async (args) => {
|
|
2795
|
+
const { cmdCheck: cmdCheck2 } = await Promise.resolve().then(() => (init_check(), check_exports));
|
|
2796
|
+
const lines = [];
|
|
2797
|
+
const origLog = console.log.bind(console);
|
|
2798
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2799
|
+
let exitCode = 0;
|
|
2800
|
+
process.exit = (code) => {
|
|
2801
|
+
exitCode = code ?? 0;
|
|
2802
|
+
};
|
|
2803
|
+
try {
|
|
2804
|
+
cmdCheck2({ compat: args.compat ?? false });
|
|
2805
|
+
} finally {
|
|
2806
|
+
console.log = origLog;
|
|
2807
|
+
}
|
|
2808
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2809
|
+
return {
|
|
2810
|
+
content: [{ type: "text", text: text2 }],
|
|
2811
|
+
isError: exitCode !== 0
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
);
|
|
2815
|
+
server.tool(
|
|
2816
|
+
"lint",
|
|
2817
|
+
"Dead-code and connectivity analysis for .rblua component files.\nFinds: dead signals, ghost handlers, broken Components.* cross-references, orphaned func.* definitions.",
|
|
2818
|
+
{},
|
|
2819
|
+
async () => {
|
|
2820
|
+
const { cmdLint: cmdLint2 } = await Promise.resolve().then(() => (init_lint(), lint_exports));
|
|
2821
|
+
const lines = [];
|
|
2822
|
+
const origLog = console.log.bind(console);
|
|
2823
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2824
|
+
try {
|
|
2825
|
+
cmdLint2({});
|
|
2826
|
+
} finally {
|
|
2827
|
+
console.log = origLog;
|
|
2828
|
+
}
|
|
2829
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2830
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2831
|
+
}
|
|
2832
|
+
);
|
|
2833
|
+
server.tool(
|
|
2834
|
+
"status",
|
|
2835
|
+
"Project summary: module count, last build size + timestamp, git status.",
|
|
2836
|
+
{},
|
|
2837
|
+
async () => {
|
|
2838
|
+
const { cmdStatus: cmdStatus2 } = await Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
2839
|
+
const lines = [];
|
|
2840
|
+
const origLog = console.log.bind(console);
|
|
2841
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2842
|
+
try {
|
|
2843
|
+
await cmdStatus2({});
|
|
2844
|
+
} finally {
|
|
2845
|
+
console.log = origLog;
|
|
2846
|
+
}
|
|
2847
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2848
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2849
|
+
}
|
|
2850
|
+
);
|
|
2851
|
+
server.tool(
|
|
2852
|
+
"list",
|
|
2853
|
+
"Show the module load order \u2014 the exact sequence files are concatenated into the build.",
|
|
2854
|
+
{
|
|
2855
|
+
compat: z.boolean().optional().default(false).describe("Show compat load order")
|
|
2856
|
+
},
|
|
2857
|
+
async (args) => {
|
|
2858
|
+
const { cmdList: cmdList2 } = await Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
2859
|
+
const lines = [];
|
|
2860
|
+
const origLog = console.log.bind(console);
|
|
2861
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2862
|
+
try {
|
|
2863
|
+
cmdList2({ compat: args.compat ?? false });
|
|
2864
|
+
} finally {
|
|
2865
|
+
console.log = origLog;
|
|
2866
|
+
}
|
|
2867
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2868
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2869
|
+
}
|
|
2870
|
+
);
|
|
2871
|
+
server.tool(
|
|
2872
|
+
"new_module",
|
|
2873
|
+
"Scaffold a new .rblua component file or a plain .lua helper module.",
|
|
2874
|
+
{
|
|
2875
|
+
path: z.string().describe('Relative path from src/ (e.g. "player/KillAura" or "misc/helpers/utils")'),
|
|
2876
|
+
plain: z.boolean().optional().default(false).describe("Create a plain .lua file instead of a .rblua component")
|
|
2877
|
+
},
|
|
2878
|
+
async (args) => {
|
|
2879
|
+
const { cmdNew: cmdNew2 } = await Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
2880
|
+
const lines = [];
|
|
2881
|
+
const origLog = console.log.bind(console);
|
|
2882
|
+
console.log = (...a) => lines.push(a.map(String).join(" "));
|
|
2883
|
+
try {
|
|
2884
|
+
cmdNew2({ type: "module", path: args.path, plain: args.plain ?? false });
|
|
2885
|
+
} finally {
|
|
2886
|
+
console.log = origLog;
|
|
2887
|
+
}
|
|
2888
|
+
const text2 = lines.join("\n").replace(/\x1b\[[0-9;]*m/g, "");
|
|
2889
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2890
|
+
}
|
|
2891
|
+
);
|
|
2892
|
+
const transport = new StdioServerTransport();
|
|
2893
|
+
await server.connect(transport);
|
|
2894
|
+
}
|
|
2895
|
+
function printMcpSetup() {
|
|
2896
|
+
pHeader("mcp \xB7 setup");
|
|
2897
|
+
pSection("1 \xB7 Register with Claude Code (once per machine)");
|
|
2898
|
+
pHr();
|
|
2899
|
+
console.log(` ${cyan("claude mcp add pulse-rb -- rb mcp")}`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
pSection("2 \xB7 Available MCP tools");
|
|
2902
|
+
pHr();
|
|
2903
|
+
const tools = [
|
|
2904
|
+
["build", "Compile + obfuscate \u2014 runs full rb build pipeline"],
|
|
2905
|
+
["check", "Syntax-check output with luaparse"],
|
|
2906
|
+
["lint", "Dead-code analysis on component files"],
|
|
2907
|
+
["status", "Project summary: modules, last build, git"],
|
|
2908
|
+
["list", "Show module load order"],
|
|
2909
|
+
["new_module", "Scaffold a new component or helper file"]
|
|
2910
|
+
];
|
|
2911
|
+
for (const [name, desc] of tools) {
|
|
2912
|
+
console.log(` ${bold(name.padEnd(16))}${dim(desc)}`);
|
|
2913
|
+
}
|
|
2914
|
+
console.log();
|
|
2915
|
+
pSection("3 \xB7 How Claude uses it");
|
|
2916
|
+
pHr();
|
|
2917
|
+
pInfo(`Claude can call ${bold("build")}, ${bold("lint")}, ${bold("status")} directly \u2014 no terminal needed.`);
|
|
2918
|
+
pInfo(`Add this to ${bold("CLAUDE.md")} in your project:`);
|
|
2919
|
+
console.log();
|
|
2920
|
+
console.log(` ${dim("## CLI")}`);
|
|
2921
|
+
console.log(` ${dim("Run `rb mcp` via Claude Code MCP integration.")}`);
|
|
2922
|
+
console.log(` ${dim("Claude can build, check, lint, and deploy directly.")}`);
|
|
2923
|
+
console.log();
|
|
2924
|
+
}
|
|
2925
|
+
var init_mcp = __esm({
|
|
2926
|
+
"src/commands/mcp.ts"() {
|
|
2927
|
+
init_cjs_shims();
|
|
2928
|
+
init_ui();
|
|
2929
|
+
init_constants();
|
|
2930
|
+
}
|
|
2931
|
+
});
|
|
2932
|
+
|
|
2933
|
+
// src/commands/install.ts
|
|
2934
|
+
var install_exports = {};
|
|
2935
|
+
__export(install_exports, {
|
|
2936
|
+
cmdInstall: () => cmdInstall,
|
|
2937
|
+
cmdUpdate: () => cmdUpdate
|
|
2938
|
+
});
|
|
2939
|
+
function cmdInstall(_args) {
|
|
2940
|
+
pHeader("install");
|
|
2941
|
+
pInfo(`${bold("pulse-rb")} is now distributed via npm \u2014 no separate install step needed.`);
|
|
2942
|
+
console.log();
|
|
2943
|
+
console.log(` ${dim("Global install:")} pnpm add -g pulse-rb`);
|
|
2944
|
+
console.log(` ${dim("Project install:")} pnpm add -D pulse-rb`);
|
|
2945
|
+
console.log(` ${dim("Zero install:")} pnpm dlx pulse-rb init <name>`);
|
|
2946
|
+
console.log();
|
|
2947
|
+
pOk(`Current version ${gray("v" + RB_VERSION)}`);
|
|
2948
|
+
console.log();
|
|
2949
|
+
}
|
|
2950
|
+
async function cmdUpdate(_args) {
|
|
2951
|
+
pHeader("update");
|
|
2952
|
+
pInfo("Updating pulse-rb via pnpm...");
|
|
2953
|
+
const isGlobal = !process.env["npm_execpath"];
|
|
2954
|
+
try {
|
|
2955
|
+
if (isGlobal) {
|
|
2956
|
+
const { stdout } = await execa.execa("pnpm", ["add", "-g", `pulse-rb@latest`]);
|
|
2957
|
+
pOk(`Updated globally ${gray(stdout.trim().split("\n").at(-1) ?? "")}`);
|
|
2958
|
+
} else {
|
|
2959
|
+
const { stdout } = await execa.execa("pnpm", ["add", "-D", `pulse-rb@latest`]);
|
|
2960
|
+
pOk(`Updated locally ${gray(stdout.trim().split("\n").at(-1) ?? "")}`);
|
|
2961
|
+
}
|
|
2962
|
+
} catch (e) {
|
|
2963
|
+
pFail("pnpm update failed", String(e?.message ?? "").split("\n")[0]);
|
|
2964
|
+
pWarn("Try manually: pnpm add -g pulse-rb@latest");
|
|
2965
|
+
}
|
|
2966
|
+
console.log();
|
|
2967
|
+
}
|
|
2968
|
+
var init_install = __esm({
|
|
2969
|
+
"src/commands/install.ts"() {
|
|
2970
|
+
init_cjs_shims();
|
|
2971
|
+
init_ui();
|
|
2972
|
+
init_constants();
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
|
|
2976
|
+
// src/commands/ext.ts
|
|
2977
|
+
var ext_exports = {};
|
|
2978
|
+
__export(ext_exports, {
|
|
2979
|
+
cmdExt: () => cmdExt
|
|
2980
|
+
});
|
|
2981
|
+
function vscodeExtDir() {
|
|
2982
|
+
const home = process.env["USERPROFILE"] ?? process.env["HOME"] ?? __require("os").homedir();
|
|
2983
|
+
return pathe.join(home, ".vscode", "extensions");
|
|
2984
|
+
}
|
|
2985
|
+
async function doInstall() {
|
|
2986
|
+
pHeader("ext install");
|
|
2987
|
+
pSection("Installing VS Code extension");
|
|
2988
|
+
if (!fs.existsSync(EXT_SRC)) {
|
|
2989
|
+
pFail("Extension source not found", EXT_SRC);
|
|
2990
|
+
process.exit(1);
|
|
2991
|
+
}
|
|
2992
|
+
const extDir = vscodeExtDir();
|
|
2993
|
+
if (!fs.existsSync(extDir)) {
|
|
2994
|
+
pFail(
|
|
2995
|
+
"VS Code extensions directory not found",
|
|
2996
|
+
`Expected: ${extDir} \u2014 make sure VS Code is installed.`
|
|
2997
|
+
);
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
const oldDest = pathe.join(extDir, `rb-rblua-${EXT_VERSION}`);
|
|
3001
|
+
if (fs.existsSync(oldDest)) await promises.rm(oldDest, { recursive: true, force: true });
|
|
3002
|
+
const dest = pathe.join(extDir, EXT_ID);
|
|
3003
|
+
if (fs.existsSync(dest)) await promises.rm(dest, { recursive: true, force: true });
|
|
3004
|
+
await promises.cp(EXT_SRC, dest, { recursive: true });
|
|
3005
|
+
pOk(`${bold(EXT_ID)} \u2192 ${gray(dest)}`);
|
|
3006
|
+
console.log();
|
|
3007
|
+
pInfo("Reload VS Code to activate:");
|
|
3008
|
+
console.log(` Ctrl+Shift+P \u2192 ${dim("Developer: Reload Window")}
|
|
3009
|
+
`);
|
|
3010
|
+
}
|
|
3011
|
+
async function cmdExt(args) {
|
|
3012
|
+
if (args.sub === "install") {
|
|
3013
|
+
await doInstall();
|
|
3014
|
+
} else {
|
|
3015
|
+
pFail("Missing subcommand", "usage: rb ext install");
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
var EXT_SRC, EXT_VERSION, EXT_ID;
|
|
3020
|
+
var init_ext = __esm({
|
|
3021
|
+
"src/commands/ext.ts"() {
|
|
3022
|
+
init_cjs_shims();
|
|
3023
|
+
init_ui();
|
|
3024
|
+
EXT_SRC = pathe.join(__dirname, "..", "vscode");
|
|
3025
|
+
EXT_VERSION = "1.0.0";
|
|
3026
|
+
EXT_ID = `rb.rb-rblua-${EXT_VERSION}`;
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
// src/index.ts
|
|
3031
|
+
init_cjs_shims();
|
|
3032
|
+
init_constants();
|
|
3033
|
+
var main = citty.defineCommand({
|
|
3034
|
+
meta: {
|
|
3035
|
+
name: "rb",
|
|
3036
|
+
version: RB_VERSION,
|
|
3037
|
+
description: "rb \u2014 Pulse framework build tool for Roblox script projects"
|
|
3038
|
+
},
|
|
3039
|
+
subCommands: {
|
|
3040
|
+
// ── build / compile ───────────────────────────────────────────────────────
|
|
3041
|
+
build: citty.defineCommand({
|
|
3042
|
+
meta: { description: "Compile + obfuscate script.lua and script.compat.lua" },
|
|
3043
|
+
args: {
|
|
3044
|
+
"no-compat": { type: "boolean", default: false, description: "Skip compat build" },
|
|
3045
|
+
"no-obfuscate": { type: "boolean", default: false, description: "Skip obfuscation" },
|
|
3046
|
+
preset: { type: "string", default: "Medium", description: "Obfuscation preset: Low | Medium | Strong" },
|
|
3047
|
+
copy: { type: "boolean", default: false, description: "Copy standard build to clipboard after build" },
|
|
3048
|
+
dev: { type: "boolean", default: false, description: "Also emit a dev build (script.dev.lua)" },
|
|
3049
|
+
ui: { type: "string", description: "Override UI library (linoria | windui)" }
|
|
3050
|
+
},
|
|
3051
|
+
async run({ args }) {
|
|
3052
|
+
const { cmdBuild: cmdBuild2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
3053
|
+
await cmdBuild2({
|
|
3054
|
+
noCompat: Boolean(args["no-compat"]),
|
|
3055
|
+
noObfuscate: Boolean(args["no-obfuscate"]),
|
|
3056
|
+
preset: String(args.preset ?? "Medium"),
|
|
3057
|
+
copy: Boolean(args.copy),
|
|
3058
|
+
dev: Boolean(args.dev),
|
|
3059
|
+
ui: args.ui ? String(args.ui) : void 0
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
}),
|
|
3063
|
+
copy: citty.defineCommand({
|
|
3064
|
+
meta: { description: "Compile and copy output to clipboard" },
|
|
3065
|
+
args: {
|
|
3066
|
+
compat: { type: "boolean", default: false, description: "Copy the compat build" }
|
|
3067
|
+
},
|
|
3068
|
+
async run({ args }) {
|
|
3069
|
+
const { cmdCopy: cmdCopy2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
3070
|
+
await cmdCopy2({ compat: Boolean(args.compat) });
|
|
3071
|
+
}
|
|
3072
|
+
}),
|
|
3073
|
+
clean: citty.defineCommand({
|
|
3074
|
+
meta: { description: "Remove all built .lua files from build/" },
|
|
3075
|
+
run() {
|
|
3076
|
+
const { cmdClean: cmdClean2 } = (init_build(), __toCommonJS(build_exports));
|
|
3077
|
+
cmdClean2({});
|
|
3078
|
+
}
|
|
3079
|
+
}),
|
|
3080
|
+
// ── watch / check ─────────────────────────────────────────────────────────
|
|
3081
|
+
watch: citty.defineCommand({
|
|
3082
|
+
meta: { description: "Watch src/ and rebuild on changes (Ctrl+C to stop)" },
|
|
3083
|
+
args: {
|
|
3084
|
+
compat: { type: "boolean", default: false, description: "Also rebuild compat build on every change" }
|
|
3085
|
+
},
|
|
3086
|
+
run({ args }) {
|
|
3087
|
+
const { cmdWatch: cmdWatch2 } = (init_watch(), __toCommonJS(watch_exports));
|
|
3088
|
+
cmdWatch2({ compat: Boolean(args.compat) });
|
|
3089
|
+
}
|
|
3090
|
+
}),
|
|
3091
|
+
check: citty.defineCommand({
|
|
3092
|
+
meta: { description: "Syntax-check the compiled output without writing build files" },
|
|
3093
|
+
args: {
|
|
3094
|
+
compat: { type: "boolean", default: false, description: "Check the compat build" }
|
|
3095
|
+
},
|
|
3096
|
+
run({ args }) {
|
|
3097
|
+
const { cmdCheck: cmdCheck2 } = (init_check(), __toCommonJS(check_exports));
|
|
3098
|
+
cmdCheck2({ compat: Boolean(args.compat) });
|
|
3099
|
+
}
|
|
3100
|
+
}),
|
|
3101
|
+
// ── project scaffolding ───────────────────────────────────────────────────
|
|
3102
|
+
init: citty.defineCommand({
|
|
3103
|
+
meta: { description: "Scaffold a new Pulse project with package.json + git init" },
|
|
3104
|
+
args: {
|
|
3105
|
+
name: { type: "positional", required: true, description: "Project directory name" },
|
|
3106
|
+
force: { type: "boolean", default: false, description: "Overwrite existing directory" }
|
|
3107
|
+
},
|
|
3108
|
+
async run({ args }) {
|
|
3109
|
+
const { cmdInit: cmdInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
3110
|
+
await cmdInit2({ name: String(args.name), force: Boolean(args.force) });
|
|
3111
|
+
}
|
|
3112
|
+
}),
|
|
3113
|
+
// ── module / remote management ────────────────────────────────────────────
|
|
3114
|
+
new: citty.defineCommand({
|
|
3115
|
+
meta: { description: "Create a new component module or remote stub" },
|
|
3116
|
+
args: {
|
|
3117
|
+
type: { type: "positional", required: true, description: "module | remote" },
|
|
3118
|
+
path: { type: "positional", required: true, description: "Relative path (module) or name (remote)" },
|
|
3119
|
+
plain: { type: "boolean", default: false, description: "Plain .lua file instead of .rblua component" }
|
|
3120
|
+
},
|
|
3121
|
+
run({ args }) {
|
|
3122
|
+
const { cmdNew: cmdNew2 } = (init_modules(), __toCommonJS(modules_exports));
|
|
3123
|
+
cmdNew2({ type: String(args.type), path: String(args.path), plain: Boolean(args.plain) });
|
|
3124
|
+
}
|
|
3125
|
+
}),
|
|
3126
|
+
remove: citty.defineCommand({
|
|
3127
|
+
meta: { description: "Interactively delete a module from src/" },
|
|
3128
|
+
args: {
|
|
3129
|
+
path: { type: "positional", required: true, description: "Relative path from src/" }
|
|
3130
|
+
},
|
|
3131
|
+
async run({ args }) {
|
|
3132
|
+
const { cmdRemove: cmdRemove2 } = await Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
3133
|
+
await cmdRemove2({ path: String(args.path) });
|
|
3134
|
+
}
|
|
3135
|
+
}),
|
|
3136
|
+
list: citty.defineCommand({
|
|
3137
|
+
meta: { description: "Print the full module load order" },
|
|
3138
|
+
args: {
|
|
3139
|
+
compat: { type: "boolean", default: false, description: "Show compat load order" }
|
|
3140
|
+
},
|
|
3141
|
+
run({ args }) {
|
|
3142
|
+
const { cmdList: cmdList2 } = (init_modules(), __toCommonJS(modules_exports));
|
|
3143
|
+
cmdList2({ compat: Boolean(args.compat) });
|
|
3144
|
+
}
|
|
3145
|
+
}),
|
|
3146
|
+
status: citty.defineCommand({
|
|
3147
|
+
meta: { description: "Project summary: module count, last build, git status" },
|
|
3148
|
+
async run() {
|
|
3149
|
+
const { cmdStatus: cmdStatus2 } = await Promise.resolve().then(() => (init_modules(), modules_exports));
|
|
3150
|
+
await cmdStatus2({});
|
|
3151
|
+
}
|
|
3152
|
+
}),
|
|
3153
|
+
// ── git helpers ───────────────────────────────────────────────────────────
|
|
3154
|
+
save: citty.defineCommand({
|
|
3155
|
+
meta: { description: "git add -A && git commit (alias for quick checkpoints)" },
|
|
3156
|
+
args: {
|
|
3157
|
+
message: { type: "positional", required: false, description: "Commit message (defaults to timestamp)" }
|
|
3158
|
+
},
|
|
3159
|
+
async run({ args }) {
|
|
3160
|
+
const { cmdSave: cmdSave2 } = await Promise.resolve().then(() => (init_git(), git_exports));
|
|
3161
|
+
await cmdSave2({ message: args.message ? String(args.message) : void 0 });
|
|
3162
|
+
}
|
|
3163
|
+
}),
|
|
3164
|
+
history: citty.defineCommand({
|
|
3165
|
+
meta: { description: "Show recent git commits" },
|
|
3166
|
+
args: {
|
|
3167
|
+
n: { type: "string", default: "10", description: "Number of commits to show" }
|
|
3168
|
+
},
|
|
3169
|
+
async run({ args }) {
|
|
3170
|
+
const { cmdHistory: cmdHistory2 } = await Promise.resolve().then(() => (init_git(), git_exports));
|
|
3171
|
+
await cmdHistory2({ n: parseInt(String(args.n ?? "10"), 10) });
|
|
3172
|
+
}
|
|
3173
|
+
}),
|
|
3174
|
+
restore: citty.defineCommand({
|
|
3175
|
+
meta: { description: "git checkout <ref> -- . (restore files to a past commit)" },
|
|
3176
|
+
args: {
|
|
3177
|
+
ref: { type: "positional", required: true, description: "Git ref: commit hash, tag, or branch" }
|
|
3178
|
+
},
|
|
3179
|
+
async run({ args }) {
|
|
3180
|
+
const { cmdRestore: cmdRestore2 } = await Promise.resolve().then(() => (init_git(), git_exports));
|
|
3181
|
+
await cmdRestore2({ ref: String(args.ref) });
|
|
3182
|
+
}
|
|
3183
|
+
}),
|
|
3184
|
+
// ── deploy ────────────────────────────────────────────────────────────────
|
|
3185
|
+
deploy: citty.defineCommand({
|
|
3186
|
+
meta: { description: "Push script.obf.lua to GitHub and print the loadstring URL" },
|
|
3187
|
+
args: {
|
|
3188
|
+
token: { type: "string", description: "GitHub PAT (or set GITHUB_TOKEN in .rb-deploy)" },
|
|
3189
|
+
repo: { type: "string", description: "GitHub repo e.g. user/my-release" },
|
|
3190
|
+
file: { type: "string", description: "Filename in repo (default: script.obf.lua)" },
|
|
3191
|
+
branch: { type: "string", description: "Branch (default: main)" }
|
|
3192
|
+
},
|
|
3193
|
+
async run({ args }) {
|
|
3194
|
+
const { cmdDeploy: cmdDeploy2 } = await Promise.resolve().then(() => (init_deploy(), deploy_exports));
|
|
3195
|
+
await cmdDeploy2({
|
|
3196
|
+
token: args.token ? String(args.token) : void 0,
|
|
3197
|
+
repo: args.repo ? String(args.repo) : void 0,
|
|
3198
|
+
file: args.file ? String(args.file) : void 0,
|
|
3199
|
+
branch: args.branch ? String(args.branch) : void 0
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
}),
|
|
3203
|
+
// ── analysis ──────────────────────────────────────────────────────────────
|
|
3204
|
+
lint: citty.defineCommand({
|
|
3205
|
+
meta: { description: "Dead-code and connectivity analysis for .rblua component files" },
|
|
3206
|
+
run() {
|
|
3207
|
+
const { cmdLint: cmdLint2 } = (init_lint(), __toCommonJS(lint_exports));
|
|
3208
|
+
cmdLint2({});
|
|
3209
|
+
}
|
|
3210
|
+
}),
|
|
3211
|
+
// ── obfuscation ───────────────────────────────────────────────────────────
|
|
3212
|
+
obfuscate: citty.defineCommand({
|
|
3213
|
+
meta: { description: "Obfuscate existing build files without recompiling" },
|
|
3214
|
+
args: {
|
|
3215
|
+
copy: { type: "boolean", default: false, description: "Copy standard obfuscated build to clipboard" }
|
|
3216
|
+
},
|
|
3217
|
+
async run({ args }) {
|
|
3218
|
+
const { cmdObfuscate: cmdObfuscate2 } = await Promise.resolve().then(() => (init_obfuscate(), obfuscate_exports));
|
|
3219
|
+
await cmdObfuscate2({ copy: Boolean(args.copy) });
|
|
3220
|
+
}
|
|
3221
|
+
}),
|
|
3222
|
+
// ── docs / ai ─────────────────────────────────────────────────────────────
|
|
3223
|
+
docs: citty.defineCommand({
|
|
3224
|
+
meta: { description: "Generate PULSE_DOCS.md \u2014 full framework reference for LLMs" },
|
|
3225
|
+
args: {
|
|
3226
|
+
output: { type: "string", description: "Output path (default: PULSE_DOCS.md, use - for stdout)" }
|
|
3227
|
+
},
|
|
3228
|
+
run({ args }) {
|
|
3229
|
+
const { cmdDocs: cmdDocs2 } = (init_docs(), __toCommonJS(docs_exports));
|
|
3230
|
+
cmdDocs2({ output: args.output ? String(args.output) : void 0 });
|
|
3231
|
+
}
|
|
3232
|
+
}),
|
|
3233
|
+
mcp: citty.defineCommand({
|
|
3234
|
+
meta: { description: "Start MCP server (used by Claude Code) or print setup instructions" },
|
|
3235
|
+
args: {
|
|
3236
|
+
setup: { type: "boolean", default: false, description: "Print setup instructions instead of starting the server" }
|
|
3237
|
+
},
|
|
3238
|
+
async run({ args }) {
|
|
3239
|
+
if (args.setup) {
|
|
3240
|
+
const { printMcpSetup: printMcpSetup2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
3241
|
+
printMcpSetup2();
|
|
3242
|
+
} else {
|
|
3243
|
+
const { cmdMcp: cmdMcp2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
3244
|
+
await cmdMcp2({});
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}),
|
|
3248
|
+
// ── publish (R2 CDN) ──────────────────────────────────────────────────────
|
|
3249
|
+
publish: citty.defineCommand({
|
|
3250
|
+
meta: { description: "Upload Pulse runtime files to Cloudflare R2 (enables --cdn builds)" },
|
|
3251
|
+
async run() {
|
|
3252
|
+
const { cmdPublish: cmdPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
|
|
3253
|
+
await cmdPublish2({});
|
|
3254
|
+
}
|
|
3255
|
+
}),
|
|
3256
|
+
// ── toolchain ─────────────────────────────────────────────────────────────
|
|
3257
|
+
install: citty.defineCommand({
|
|
3258
|
+
meta: { description: "Show npm install instructions (pulse-rb is distributed via npm)" },
|
|
3259
|
+
run() {
|
|
3260
|
+
const { cmdInstall: cmdInstall2 } = (init_install(), __toCommonJS(install_exports));
|
|
3261
|
+
cmdInstall2({});
|
|
3262
|
+
}
|
|
3263
|
+
}),
|
|
3264
|
+
update: citty.defineCommand({
|
|
3265
|
+
meta: { description: "Update pulse-rb to the latest version via pnpm" },
|
|
3266
|
+
async run() {
|
|
3267
|
+
const { cmdUpdate: cmdUpdate2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
3268
|
+
await cmdUpdate2({});
|
|
3269
|
+
}
|
|
3270
|
+
}),
|
|
3271
|
+
ext: citty.defineCommand({
|
|
3272
|
+
meta: { description: "Install the rb VS Code extension (.rblua syntax highlighting)" },
|
|
3273
|
+
subCommands: {
|
|
3274
|
+
install: citty.defineCommand({
|
|
3275
|
+
meta: { description: "Copy extension to ~/.vscode/extensions/" },
|
|
3276
|
+
async run() {
|
|
3277
|
+
const { cmdExt: cmdExt2 } = await Promise.resolve().then(() => (init_ext(), ext_exports));
|
|
3278
|
+
await cmdExt2({ sub: "install" });
|
|
3279
|
+
}
|
|
3280
|
+
})
|
|
3281
|
+
}
|
|
3282
|
+
})
|
|
3283
|
+
}
|
|
3284
|
+
});
|
|
3285
|
+
citty.runMain(main);
|