pulse-rb 1.2.24 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +257 -212
- package/package.json +3 -2
- package/pulse/runtime.lua +154 -0
- package/templates/component_esp.ts +16 -0
- package/templates/component_fov.ts +17 -0
- package/templates/component_speed.ts +26 -0
- package/templates/layout.ts +20 -0
- package/templates/page_home.ts +8 -0
- package/templates/example_esp.rblua +0 -69
- package/templates/example_fov.rblua +0 -20
- package/templates/example_speed.rblua +0 -25
- package/templates/layout.rblua +0 -28
- package/templates/module.rblua +0 -32
- package/templates/page_home.rblua +0 -9
package/dist/index.js
CHANGED
|
@@ -5,11 +5,12 @@ var path = require('path');
|
|
|
5
5
|
var pc = require('picocolors');
|
|
6
6
|
var prompts = require('@clack/prompts');
|
|
7
7
|
var fs = require('fs');
|
|
8
|
-
var
|
|
9
|
-
var ofetch = require('ofetch');
|
|
8
|
+
var typescriptToLua = require('typescript-to-lua');
|
|
10
9
|
var child_process = require('child_process');
|
|
10
|
+
var pathe = require('pathe');
|
|
11
11
|
var chokidar = require('chokidar');
|
|
12
12
|
var execa = require('execa');
|
|
13
|
+
var ofetch = require('ofetch');
|
|
13
14
|
var globby = require('globby');
|
|
14
15
|
var promises = require('fs/promises');
|
|
15
16
|
var citty = require('citty');
|
|
@@ -55,15 +56,15 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
|
|
|
55
56
|
var init_constants = __esm({
|
|
56
57
|
"src/constants.ts"() {
|
|
57
58
|
init_cjs_shims();
|
|
58
|
-
RB_VERSION = "1.
|
|
59
|
+
RB_VERSION = "1.3.0";
|
|
59
60
|
RB_HOME = path.join(os.homedir(), ".rb");
|
|
60
61
|
path.join(RB_HOME, "bin");
|
|
61
62
|
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
62
|
-
IRONBREW2_REPO = "https://github.com/
|
|
63
|
+
IRONBREW2_REPO = "https://github.com/Trollicus/ironbrew-2.git";
|
|
63
64
|
ALWAYS_EXCLUDE = /* @__PURE__ */ new Set(["types.lua"]);
|
|
64
65
|
VALID_UI_LIBS = /* @__PURE__ */ new Set(["linoria", "windui"]);
|
|
65
66
|
OBFUSCATION_PRESETS = ["Low", "Medium", "Strong"];
|
|
66
|
-
CDN_BASE_URL = "https://
|
|
67
|
+
CDN_BASE_URL = "https://pub-2072661efb7e45a1ac7f2aea691b0627.r2.dev";
|
|
67
68
|
}
|
|
68
69
|
});
|
|
69
70
|
|
|
@@ -1093,129 +1094,6 @@ var init_transpiler = __esm({
|
|
|
1093
1094
|
};
|
|
1094
1095
|
}
|
|
1095
1096
|
});
|
|
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
1097
|
function rglob(dir, exts) {
|
|
1220
1098
|
if (!fs.existsSync(dir)) return [];
|
|
1221
1099
|
const results = [];
|
|
@@ -1232,7 +1110,7 @@ function rglob(dir, exts) {
|
|
|
1232
1110
|
walk(dir);
|
|
1233
1111
|
return results;
|
|
1234
1112
|
}
|
|
1235
|
-
var PULSE_DIR,
|
|
1113
|
+
var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, DEFAULTS_RUNNER, Compiler;
|
|
1236
1114
|
var init_compiler = __esm({
|
|
1237
1115
|
"src/compiler.ts"() {
|
|
1238
1116
|
init_cjs_shims();
|
|
@@ -1240,13 +1118,12 @@ var init_compiler = __esm({
|
|
|
1240
1118
|
init_layout();
|
|
1241
1119
|
init_ui();
|
|
1242
1120
|
init_constants();
|
|
1243
|
-
init_publish();
|
|
1244
1121
|
PULSE_DIR = path.join(__dirname, "..", "pulse");
|
|
1245
|
-
|
|
1246
|
-
|
|
1122
|
+
path.join(PULSE_DIR, "runtime.lua");
|
|
1123
|
+
path.join(PULSE_DIR, "helpers");
|
|
1247
1124
|
PULSE_DEV_DIR = path.join(PULSE_DIR, "dev");
|
|
1248
1125
|
PULSE_UI_DIR = path.join(PULSE_DIR, "ui");
|
|
1249
|
-
|
|
1126
|
+
path.join(__dirname, "..", "adapters");
|
|
1250
1127
|
REEXEC_GUARD = `-- Stop any previous instance before this one starts.
|
|
1251
1128
|
-- Kills old Heartbeat loops, connections, and Linoria window.
|
|
1252
1129
|
if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end
|
|
@@ -1301,6 +1178,12 @@ end)
|
|
|
1301
1178
|
this.buildDir = path.join(root, "build");
|
|
1302
1179
|
}
|
|
1303
1180
|
getUiLibrary() {
|
|
1181
|
+
const layoutTs = path.join(this.srcDir, "layout.ts");
|
|
1182
|
+
if (fs.existsSync(layoutTs)) {
|
|
1183
|
+
const content = fs.readFileSync(layoutTs, "utf8");
|
|
1184
|
+
const m = content.match(/uiLibrary\s*:\s*['"](\w+)['"]/);
|
|
1185
|
+
if (m && VALID_UI_LIBS.has(m[1])) return m[1];
|
|
1186
|
+
}
|
|
1304
1187
|
const layoutFile = path.join(this.srcDir, "ui", "layout.rblua");
|
|
1305
1188
|
if (!fs.existsSync(layoutFile)) return "linoria";
|
|
1306
1189
|
try {
|
|
@@ -1319,7 +1202,9 @@ end)
|
|
|
1319
1202
|
const ui = this.resolveUi(opts.ui);
|
|
1320
1203
|
const src = this.srcDir;
|
|
1321
1204
|
const uiDir = path.join(src, "ui");
|
|
1205
|
+
const pagesDir = path.join(src, "pages");
|
|
1322
1206
|
const devDir = path.join(src, "dev");
|
|
1207
|
+
const layoutTs = path.join(src, "layout.ts");
|
|
1323
1208
|
const exclude = new Set(ALWAYS_EXCLUDE);
|
|
1324
1209
|
if (opts.compat) {
|
|
1325
1210
|
const layoutFile = path.join(src, "ui", "layout.rblua");
|
|
@@ -1342,10 +1227,11 @@ end)
|
|
|
1342
1227
|
if (fs.existsSync(bootstrap)) first.push(bootstrap);
|
|
1343
1228
|
if (fs.existsSync(remotes)) first.push(remotes);
|
|
1344
1229
|
const allFiles = [
|
|
1345
|
-
...rglob(src, [".lua", ".rblua"])
|
|
1230
|
+
...rglob(src, [".lua", ".rblua", ".ts"])
|
|
1346
1231
|
].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
1347
1232
|
for (const f of allFiles) {
|
|
1348
1233
|
if (f.startsWith(devDir + "\\") || f.startsWith(devDir + "/")) continue;
|
|
1234
|
+
if (f === layoutTs) continue;
|
|
1349
1235
|
const rel = path.relative(src, f).replace(/\\/g, "/");
|
|
1350
1236
|
if (exclude.has(rel)) continue;
|
|
1351
1237
|
if (f === bootstrap || f === remotes || f === startup) continue;
|
|
@@ -1354,14 +1240,17 @@ end)
|
|
|
1354
1240
|
uiFiles.push(f);
|
|
1355
1241
|
continue;
|
|
1356
1242
|
}
|
|
1357
|
-
|
|
1243
|
+
if (f.startsWith(pagesDir + "\\") || f.startsWith(pagesDir + "/")) {
|
|
1244
|
+
uiFiles.push(f);
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1358
1247
|
if (path.relative(src, f).split(/[/\\]/).length === 1) continue;
|
|
1359
1248
|
middle.push(f);
|
|
1360
1249
|
}
|
|
1361
1250
|
if (opts.dev) {
|
|
1362
1251
|
const fwDevUi = path.join(PULSE_DEV_DIR, "ui");
|
|
1363
1252
|
if (fs.existsSync(fwDevUi)) {
|
|
1364
|
-
for (const f of rglob(fwDevUi, [".lua", ".rblua"]).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) {
|
|
1253
|
+
for (const f of rglob(fwDevUi, [".lua", ".rblua", ".ts"]).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) {
|
|
1365
1254
|
uiFiles.push(f);
|
|
1366
1255
|
}
|
|
1367
1256
|
}
|
|
@@ -1372,11 +1261,11 @@ end)
|
|
|
1372
1261
|
});
|
|
1373
1262
|
uiFiles.sort((a, b) => {
|
|
1374
1263
|
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;
|
|
1264
|
+
const ai = an === "layout.rblua" || an === "layout.ts" ? 0 : 1;
|
|
1265
|
+
const bi = bn === "layout.rblua" || bn === "layout.ts" ? 0 : 1;
|
|
1377
1266
|
return ai !== bi ? ai - bi : an.toLowerCase().localeCompare(bn.toLowerCase());
|
|
1378
1267
|
});
|
|
1379
|
-
if (fs.existsSync(PULSE_UI_DIR)) {
|
|
1268
|
+
if (!opts.cdn && fs.existsSync(PULSE_UI_DIR)) {
|
|
1380
1269
|
for (const f of rglob(PULSE_UI_DIR, [".lua", ".rblua"]).sort((a, b) => path.basename(a).toLowerCase().localeCompare(path.basename(b).toLowerCase()))) {
|
|
1381
1270
|
const stem = path.basename(f, path.extname(f));
|
|
1382
1271
|
if (stem.startsWith("windui_") && ui !== "windui") continue;
|
|
@@ -1390,44 +1279,31 @@ end)
|
|
|
1390
1279
|
}
|
|
1391
1280
|
compile(opts = {}) {
|
|
1392
1281
|
const ui = this.resolveUi(opts.ui);
|
|
1393
|
-
const order = this.getLoadOrder({ ...opts, ui });
|
|
1394
|
-
const
|
|
1282
|
+
const order = this.getLoadOrder({ ...opts, ui, cdn: true });
|
|
1283
|
+
const hasTs = order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"));
|
|
1395
1284
|
const srcUiDir = path.join(this.srcDir, "ui");
|
|
1396
1285
|
const pulseDevUi = path.join(PULSE_DEV_DIR, "ui");
|
|
1397
|
-
const useCdn = !opts.dev && hasCdnConfig(this.root);
|
|
1398
1286
|
const parts = [];
|
|
1399
1287
|
parts.push("-- [re-execution guard]\n");
|
|
1400
1288
|
parts.push(REEXEC_GUARD);
|
|
1401
1289
|
parts.push("\n");
|
|
1402
|
-
if (
|
|
1403
|
-
|
|
1404
|
-
parts.push(
|
|
1405
|
-
}
|
|
1406
|
-
if (hasRblua) {
|
|
1407
|
-
if (useCdn) {
|
|
1408
|
-
const base = `${CDN_BASE_URL}/v${RB_VERSION}`;
|
|
1409
|
-
parts.push(`-- Pulse v${RB_VERSION}
|
|
1290
|
+
if (hasTs) {
|
|
1291
|
+
const base = `${CDN_BASE_URL}/v${RB_VERSION}`;
|
|
1292
|
+
parts.push(`-- Pulse v${RB_VERSION}
|
|
1410
1293
|
`);
|
|
1411
|
-
|
|
1294
|
+
parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
|
|
1412
1295
|
`);
|
|
1413
|
-
|
|
1296
|
+
parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
|
|
1414
1297
|
`);
|
|
1415
|
-
|
|
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}]
|
|
1298
|
+
parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))()
|
|
1424
1299
|
`);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1300
|
+
parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
|
|
1301
|
+
`);
|
|
1302
|
+
parts.push(`local toggle,slider,dropdown,multidropdown=_P.toggle,_P.slider,_P.dropdown,_P.multidropdown
|
|
1303
|
+
`);
|
|
1304
|
+
parts.push(`local button,keybind,label,separator,groupbox,definePage=_P.button,_P.keybind,_P.label,_P.separator,_P.groupbox,_P.definePage
|
|
1305
|
+
`);
|
|
1306
|
+
parts.push("\n");
|
|
1431
1307
|
}
|
|
1432
1308
|
if (opts.dev && fs.existsSync(PULSE_DEV_DIR)) {
|
|
1433
1309
|
const rbRoot = path.join(__dirname, "..", "..");
|
|
@@ -1440,13 +1316,6 @@ end)
|
|
|
1440
1316
|
parts.push("\n");
|
|
1441
1317
|
}
|
|
1442
1318
|
}
|
|
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
1319
|
const label = (p) => {
|
|
1451
1320
|
try {
|
|
1452
1321
|
return path.relative(this.root, p).replace(/\\/g, "/");
|
|
@@ -1490,12 +1359,31 @@ end)
|
|
|
1490
1359
|
if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
|
|
1491
1360
|
throw e;
|
|
1492
1361
|
}
|
|
1362
|
+
} else if (f.endsWith(".ts")) {
|
|
1363
|
+
const source = fs.readFileSync(f, "utf8");
|
|
1364
|
+
try {
|
|
1365
|
+
const result = typescriptToLua.transpileString(source, {
|
|
1366
|
+
luaTarget: "Lua53",
|
|
1367
|
+
noImplicitSelf: true,
|
|
1368
|
+
noHeader: true,
|
|
1369
|
+
noCheck: true
|
|
1370
|
+
});
|
|
1371
|
+
if (result.diagnostics && result.diagnostics.length) {
|
|
1372
|
+
const diag = result.diagnostics[0];
|
|
1373
|
+
const msg = typeof diag.messageText === "string" ? diag.messageText : diag.messageText.messageText ?? String(diag.messageText);
|
|
1374
|
+
throw new Error(msg);
|
|
1375
|
+
}
|
|
1376
|
+
parts.push(result.file?.lua ?? "");
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
|
|
1379
|
+
throw new Error(`TypeScript error in ${lbl}: ${e.message}`);
|
|
1380
|
+
}
|
|
1493
1381
|
} else {
|
|
1494
1382
|
parts.push(fs.readFileSync(f, "utf8"));
|
|
1495
1383
|
}
|
|
1496
1384
|
parts.push("\n");
|
|
1497
1385
|
}
|
|
1498
|
-
if (order.some((f) => f.endsWith(".rblua"))) {
|
|
1386
|
+
if (order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"))) {
|
|
1499
1387
|
parts.push("-- [generated: defaults runner]\n");
|
|
1500
1388
|
parts.push(DEFAULTS_RUNNER);
|
|
1501
1389
|
parts.push("\n");
|
|
@@ -1603,8 +1491,12 @@ var init_checker = __esm({
|
|
|
1603
1491
|
}
|
|
1604
1492
|
});
|
|
1605
1493
|
function ensureIronbrew2() {
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1494
|
+
const exeDir = path.join(IRONBREW2_DIR, IB2_EXE_SUBDIR);
|
|
1495
|
+
const exePath = path.join(exeDir, IB2_EXE_NAME);
|
|
1496
|
+
if (fs.existsSync(exePath)) return IRONBREW2_DIR;
|
|
1497
|
+
if (fs.existsSync(IRONBREW2_DIR)) {
|
|
1498
|
+
fs.rmSync(IRONBREW2_DIR, { recursive: true, force: true });
|
|
1499
|
+
}
|
|
1608
1500
|
pInfo("Downloading Ironbrew2 from GitHub...");
|
|
1609
1501
|
fs.mkdirSync(path.dirname(IRONBREW2_DIR), { recursive: true });
|
|
1610
1502
|
const result = child_process.spawnSync("git", ["clone", "--depth=1", IRONBREW2_REPO, IRONBREW2_DIR], {
|
|
@@ -1618,30 +1510,29 @@ function ensureIronbrew2() {
|
|
|
1618
1510
|
return IRONBREW2_DIR;
|
|
1619
1511
|
}
|
|
1620
1512
|
async function obfuscateSource(source, ib2Dir) {
|
|
1621
|
-
const
|
|
1622
|
-
const
|
|
1623
|
-
const
|
|
1513
|
+
const exeDir = path.join(ib2Dir, IB2_EXE_SUBDIR);
|
|
1514
|
+
const exePath = path.join(exeDir, IB2_EXE_NAME);
|
|
1515
|
+
const outPath = path.join(exeDir, "out.lua");
|
|
1624
1516
|
const tmpIn = path.join(os.tmpdir(), `rb_in_${Date.now()}.lua`);
|
|
1625
|
-
const tmpOut = path.join(os.tmpdir(), `rb_out_${Date.now()}.lua`);
|
|
1626
1517
|
try {
|
|
1627
1518
|
fs.writeFileSync(tmpIn, source, "utf8");
|
|
1628
|
-
const
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1519
|
+
const dllPath = path.join(exeDir, "IronBrew2 CLI.dll");
|
|
1520
|
+
const [cmd, args] = fs.existsSync(dllPath) ? ["dotnet", ["--roll-forward", "Major", dllPath, tmpIn]] : [exePath, [tmpIn]];
|
|
1521
|
+
const result = child_process.spawnSync(cmd, args, {
|
|
1522
|
+
cwd: exeDir,
|
|
1523
|
+
stdio: "pipe"
|
|
1524
|
+
});
|
|
1525
|
+
const stdout = result.stdout?.toString().trim() ?? "";
|
|
1526
|
+
const stderr = result.stderr?.toString().trim() ?? "";
|
|
1527
|
+
if (stdout.startsWith("ERR:")) throw new Error(stdout.slice(4).trim());
|
|
1528
|
+
if (result.status !== 0) throw new Error(stderr || stdout || `exit ${result.status}`);
|
|
1529
|
+
if (!fs.existsSync(outPath)) throw new Error("IronBrew2 CLI produced no output file");
|
|
1530
|
+
return fs.readFileSync(outPath, "utf8");
|
|
1635
1531
|
} finally {
|
|
1636
|
-
lua.global.close();
|
|
1637
1532
|
try {
|
|
1638
1533
|
__require("fs").unlinkSync(tmpIn);
|
|
1639
1534
|
} catch {
|
|
1640
1535
|
}
|
|
1641
|
-
try {
|
|
1642
|
-
__require("fs").unlinkSync(tmpOut);
|
|
1643
|
-
} catch {
|
|
1644
|
-
}
|
|
1645
1536
|
}
|
|
1646
1537
|
}
|
|
1647
1538
|
async function obfuscatePipeline(builds, root) {
|
|
@@ -1660,7 +1551,8 @@ async function obfuscatePipeline(builds, root) {
|
|
|
1660
1551
|
outputs.push(outPath);
|
|
1661
1552
|
} catch (e) {
|
|
1662
1553
|
pFail(`Ironbrew2 failed on ${__require("path").basename(source)}`);
|
|
1663
|
-
pWarn(
|
|
1554
|
+
const { pWarn: pWarn7 } = (init_ui(), __toCommonJS(ui_exports));
|
|
1555
|
+
pWarn7(String(e?.message ?? e));
|
|
1664
1556
|
failed = true;
|
|
1665
1557
|
}
|
|
1666
1558
|
}
|
|
@@ -1677,13 +1569,17 @@ function copyToClipboard(content, label = "") {
|
|
|
1677
1569
|
}
|
|
1678
1570
|
} catch {
|
|
1679
1571
|
}
|
|
1680
|
-
pWarn
|
|
1572
|
+
const { pWarn: pWarn7 } = (init_ui(), __toCommonJS(ui_exports));
|
|
1573
|
+
pWarn7("Could not find clipboard tool (clip.exe / pbcopy / xclip)");
|
|
1681
1574
|
}
|
|
1575
|
+
var IB2_EXE_SUBDIR, IB2_EXE_NAME;
|
|
1682
1576
|
var init_obfuscator = __esm({
|
|
1683
1577
|
"src/obfuscator.ts"() {
|
|
1684
1578
|
init_cjs_shims();
|
|
1685
1579
|
init_ui();
|
|
1686
1580
|
init_constants();
|
|
1581
|
+
IB2_EXE_SUBDIR = path.join("IronBrew2 CLI", "bin", "Debug", "netcoreapp3.1");
|
|
1582
|
+
IB2_EXE_NAME = "IronBrew2 CLI.exe";
|
|
1687
1583
|
}
|
|
1688
1584
|
});
|
|
1689
1585
|
|
|
@@ -1902,7 +1798,7 @@ function makeClaudeMd(name) {
|
|
|
1902
1798
|
function makeAgentsMd(name) {
|
|
1903
1799
|
return read("AGENTS.md").replace(/{NAME}/g, name);
|
|
1904
1800
|
}
|
|
1905
|
-
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, TEMPLATE_GITIGNORE,
|
|
1801
|
+
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, MODULE_BOILERPLATE, MODULE_RBLUA_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_HOME_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS;
|
|
1906
1802
|
var init_templates = __esm({
|
|
1907
1803
|
"src/templates.ts"() {
|
|
1908
1804
|
init_cjs_shims();
|
|
@@ -1910,15 +1806,20 @@ var init_templates = __esm({
|
|
|
1910
1806
|
TEMPLATE_GLOBALS = read("globals.lua");
|
|
1911
1807
|
TEMPLATE_REMOTES = read("remotes.lua");
|
|
1912
1808
|
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1809
|
+
read("layout.rblua");
|
|
1810
|
+
read("page_home.rblua");
|
|
1811
|
+
read("example_speed.rblua");
|
|
1812
|
+
read("example_fov.rblua");
|
|
1813
|
+
read("example_esp.rblua");
|
|
1918
1814
|
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1919
1815
|
MODULE_BOILERPLATE = read("module.lua");
|
|
1920
1816
|
MODULE_RBLUA_BOILERPLATE = read("module.rblua");
|
|
1921
1817
|
REMOTE_BOILERPLATE = read("remote.lua");
|
|
1818
|
+
TEMPLATE_LAYOUT_TS = read("layout.ts");
|
|
1819
|
+
TEMPLATE_PAGE_HOME_TS = read("page_home.ts");
|
|
1820
|
+
TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
|
|
1821
|
+
TEMPLATE_EX_FOV_TS = read("component_fov.ts");
|
|
1822
|
+
TEMPLATE_EX_ESP_TS = read("component_esp.ts");
|
|
1922
1823
|
}
|
|
1923
1824
|
});
|
|
1924
1825
|
|
|
@@ -1995,22 +1896,23 @@ async function cmdInit(args) {
|
|
|
1995
1896
|
pHeader("init");
|
|
1996
1897
|
pSection(`Scaffolding ${bold(name + "/")}`);
|
|
1997
1898
|
for (const dir of [
|
|
1998
|
-
"src/misc
|
|
1999
|
-
"src/
|
|
1899
|
+
"src/misc",
|
|
1900
|
+
"src/pages",
|
|
1901
|
+
"src/combat",
|
|
2000
1902
|
"src/visuals",
|
|
2001
|
-
"src/ui/pages",
|
|
2002
1903
|
"build"
|
|
2003
1904
|
]) {
|
|
2004
1905
|
fs.mkdirSync(pathe.join(dest, dir), { recursive: true });
|
|
2005
1906
|
}
|
|
2006
1907
|
const files = [
|
|
2007
|
-
["src/misc/
|
|
1908
|
+
["src/misc/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
|
|
2008
1909
|
["src/misc/remotes.lua", TEMPLATE_REMOTES],
|
|
2009
|
-
["src/
|
|
2010
|
-
["src/
|
|
2011
|
-
["src/
|
|
2012
|
-
["src/
|
|
2013
|
-
["src/visuals/PlayerESP.
|
|
1910
|
+
["src/layout.ts", TEMPLATE_LAYOUT_TS.replace(/{NAME}/g, name)],
|
|
1911
|
+
["src/pages/1_Home.ts", TEMPLATE_PAGE_HOME_TS],
|
|
1912
|
+
["src/combat/SpeedHack.ts", TEMPLATE_EX_SPEED_TS],
|
|
1913
|
+
["src/combat/FOVChanger.ts", TEMPLATE_EX_FOV_TS],
|
|
1914
|
+
["src/visuals/PlayerESP.ts", TEMPLATE_EX_ESP_TS],
|
|
1915
|
+
["tsconfig.json", TSCONFIG],
|
|
2014
1916
|
[".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
|
|
2015
1917
|
[".gitignore", TEMPLATE_GITIGNORE],
|
|
2016
1918
|
["CLAUDE.md", makeClaudeMd(name)],
|
|
@@ -2073,6 +1975,7 @@ async function cmdInit(args) {
|
|
|
2073
1975
|
console.log(` ${cyan("\u2192")} cd ${name} && pnpm build`);
|
|
2074
1976
|
console.log();
|
|
2075
1977
|
}
|
|
1978
|
+
var TSCONFIG;
|
|
2076
1979
|
var init_init = __esm({
|
|
2077
1980
|
"src/commands/init.ts"() {
|
|
2078
1981
|
init_cjs_shims();
|
|
@@ -2080,6 +1983,18 @@ var init_init = __esm({
|
|
|
2080
1983
|
init_templates();
|
|
2081
1984
|
init_docs();
|
|
2082
1985
|
init_constants();
|
|
1986
|
+
TSCONFIG = JSON.stringify({
|
|
1987
|
+
compilerOptions: {
|
|
1988
|
+
strict: true,
|
|
1989
|
+
noEmit: true,
|
|
1990
|
+
skipLibCheck: true,
|
|
1991
|
+
target: "ESNext",
|
|
1992
|
+
moduleResolution: "bundler",
|
|
1993
|
+
types: ["@rb-pulse/core"]
|
|
1994
|
+
},
|
|
1995
|
+
include: ["src/**/*.ts"],
|
|
1996
|
+
exclude: ["node_modules"]
|
|
1997
|
+
}, null, 2) + "\n";
|
|
2083
1998
|
}
|
|
2084
1999
|
});
|
|
2085
2000
|
|
|
@@ -2172,8 +2087,8 @@ async function cmdRemove(args) {
|
|
|
2172
2087
|
console.log();
|
|
2173
2088
|
return;
|
|
2174
2089
|
}
|
|
2175
|
-
const { rmSync } = await import('fs');
|
|
2176
|
-
|
|
2090
|
+
const { rmSync: rmSync2 } = await import('fs');
|
|
2091
|
+
rmSync2(target);
|
|
2177
2092
|
pOk(`Removed ${gray("src/" + relPath)}`);
|
|
2178
2093
|
console.log();
|
|
2179
2094
|
}
|
|
@@ -2930,6 +2845,136 @@ var init_mcp = __esm({
|
|
|
2930
2845
|
}
|
|
2931
2846
|
});
|
|
2932
2847
|
|
|
2848
|
+
// src/commands/publish.ts
|
|
2849
|
+
var publish_exports = {};
|
|
2850
|
+
__export(publish_exports, {
|
|
2851
|
+
cmdPublish: () => cmdPublish,
|
|
2852
|
+
hasCdnConfig: () => hasCdnConfig,
|
|
2853
|
+
readPublishConfig: () => readPublishConfig
|
|
2854
|
+
});
|
|
2855
|
+
function readPublishConfig() {
|
|
2856
|
+
for (const p of CONFIG_CANDIDATES) {
|
|
2857
|
+
if (!fs.existsSync(p)) continue;
|
|
2858
|
+
const config = {};
|
|
2859
|
+
for (const line of fs.readFileSync(p, "utf8").split("\n")) {
|
|
2860
|
+
const trimmed = line.trim();
|
|
2861
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2862
|
+
const eq = trimmed.indexOf("=");
|
|
2863
|
+
if (eq < 0) continue;
|
|
2864
|
+
const key = trimmed.slice(0, eq).trim();
|
|
2865
|
+
config[key] = trimmed.slice(eq + 1).trim();
|
|
2866
|
+
}
|
|
2867
|
+
return config;
|
|
2868
|
+
}
|
|
2869
|
+
return {};
|
|
2870
|
+
}
|
|
2871
|
+
function hasCdnConfig(projectRoot) {
|
|
2872
|
+
const candidates = projectRoot ? [pathe.join(projectRoot, ".rb-publish"), ...CONFIG_CANDIDATES] : CONFIG_CANDIDATES;
|
|
2873
|
+
for (const p of candidates) {
|
|
2874
|
+
if (!fs.existsSync(p)) continue;
|
|
2875
|
+
const content = fs.readFileSync(p, "utf8");
|
|
2876
|
+
if (content.includes("R2_ACCOUNT_ID") && content.includes("R2_SECRET_TOKEN") && content.includes("R2_PUBLIC_URL")) return true;
|
|
2877
|
+
}
|
|
2878
|
+
return false;
|
|
2879
|
+
}
|
|
2880
|
+
async function uploadFile(content, remotePath, config) {
|
|
2881
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${config.R2_ACCOUNT_ID}/r2/buckets/${config.R2_BUCKET}/objects/${remotePath}`;
|
|
2882
|
+
await ofetch.ofetch(url, {
|
|
2883
|
+
method: "PUT",
|
|
2884
|
+
body: content,
|
|
2885
|
+
headers: {
|
|
2886
|
+
Authorization: `Bearer ${config.R2_SECRET_TOKEN}`,
|
|
2887
|
+
"Content-Type": remotePath.endsWith(".lua") ? "text/plain; charset=utf-8" : "application/octet-stream"
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
return `${config.R2_PUBLIC_URL}/${remotePath}`;
|
|
2891
|
+
}
|
|
2892
|
+
async function cmdPublish(_args) {
|
|
2893
|
+
pHeader("publish");
|
|
2894
|
+
const config = readPublishConfig();
|
|
2895
|
+
const missing = ["R2_ACCOUNT_ID", "R2_SECRET_TOKEN", "R2_BUCKET", "R2_PUBLIC_URL"].filter((k) => !config[k]);
|
|
2896
|
+
if (missing.length) {
|
|
2897
|
+
pFail("R2 credentials not configured");
|
|
2898
|
+
console.log();
|
|
2899
|
+
pInfo(`Create ${bold(".rb-publish")} in your project root (or ~/.rb-publish):`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
console.log(` ${dim("R2_ACCOUNT_ID")}=your_cloudflare_account_id`);
|
|
2902
|
+
console.log(` ${dim("R2_SECRET_TOKEN")}=cfat_your_api_token`);
|
|
2903
|
+
console.log(` ${dim("R2_BUCKET")}=pulse-runtime`);
|
|
2904
|
+
console.log(` ${dim("R2_PUBLIC_URL")}=https://pub-xxxx.r2.dev`);
|
|
2905
|
+
console.log();
|
|
2906
|
+
pInfo(`Get your token at: ${cyan("dash.cloudflare.com \u2192 R2 \u2192 Manage API Tokens")}`);
|
|
2907
|
+
process.exit(1);
|
|
2908
|
+
}
|
|
2909
|
+
const PULSE_DIR2 = pathe.join(__dirname, "..", "pulse");
|
|
2910
|
+
const ADAPTERS = pathe.join(__dirname, "..", "adapters");
|
|
2911
|
+
const VERSION_PATH = `v${RB_VERSION}`;
|
|
2912
|
+
const runtimeFile = pathe.join(PULSE_DIR2, "runtime.lua");
|
|
2913
|
+
const helpersDir = pathe.join(PULSE_DIR2, "helpers");
|
|
2914
|
+
if (!fs.existsSync(runtimeFile)) {
|
|
2915
|
+
pFail("pulse/runtime.lua not found");
|
|
2916
|
+
process.exit(1);
|
|
2917
|
+
}
|
|
2918
|
+
const bundleParts = [
|
|
2919
|
+
`-- Pulse v${RB_VERSION} bundle (runtime + helpers)`,
|
|
2920
|
+
fs.readFileSync(runtimeFile, "utf8").trimEnd()
|
|
2921
|
+
];
|
|
2922
|
+
if (fs.existsSync(helpersDir)) {
|
|
2923
|
+
for (const f of fs.readdirSync(helpersDir).filter((n) => n.endsWith(".lua")).sort()) {
|
|
2924
|
+
bundleParts.push(`-- ${f}`);
|
|
2925
|
+
bundleParts.push(fs.readFileSync(pathe.join(helpersDir, f), "utf8").trimEnd());
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
bundleParts.push(
|
|
2929
|
+
"return {signal=signal,computed=computed,defineComponent=defineComponent,on=on,toggle=toggle,slider=slider,dropdown=dropdown,multidropdown=multidropdown,button=button,keybind=keybind,label=label,separator=separator,groupbox=groupbox,definePage=definePage,Pulse=Pulse,Signal=Signal,Computed=Computed,Component=Component,Components=Components,Store=Store,PulseEvent=PulseEvent,_PulseGetChar=_PulseGetChar,_PulseGetHRP=_PulseGetHRP,_PulseGetHumanoid=_PulseGetHumanoid,_PulseGetAlive=_PulseGetAlive,_PulseRS=_PulseRS,_PulseUIS=_PulseUIS,_PulseDestroy=_PulseDestroy,_PULSE_DEFAULTS=_PULSE_DEFAULTS,_Pages=_Pages}"
|
|
2930
|
+
);
|
|
2931
|
+
const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
|
|
2932
|
+
const uploads = [
|
|
2933
|
+
{ remote: `${VERSION_PATH}/bundle.lua`, content: bundleContent }
|
|
2934
|
+
];
|
|
2935
|
+
const pulseUiDir = pathe.join(PULSE_DIR2, "ui");
|
|
2936
|
+
for (const adapter of ["windui", "linoria"]) {
|
|
2937
|
+
const p = pathe.join(ADAPTERS, `${adapter}.lua`);
|
|
2938
|
+
if (!fs.existsSync(p)) continue;
|
|
2939
|
+
const parts = [fs.readFileSync(p, "utf8").trimEnd()];
|
|
2940
|
+
const settings = pathe.join(pulseUiDir, `${adapter}_settings.lua`);
|
|
2941
|
+
if (fs.existsSync(settings)) {
|
|
2942
|
+
parts.push(`-- ${adapter}_settings`);
|
|
2943
|
+
parts.push(fs.readFileSync(settings, "utf8").trimEnd());
|
|
2944
|
+
}
|
|
2945
|
+
uploads.push({
|
|
2946
|
+
remote: `${VERSION_PATH}/adapters/${adapter}.lua`,
|
|
2947
|
+
content: Buffer.from(parts.join("\n") + "\n", "utf8")
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
|
|
2951
|
+
for (const { remote, content } of uploads) {
|
|
2952
|
+
try {
|
|
2953
|
+
await uploadFile(content, remote, config);
|
|
2954
|
+
pOk(remote, `${content.length.toLocaleString()} bytes`);
|
|
2955
|
+
} catch (e) {
|
|
2956
|
+
pFail(`Failed: ${remote}`, String(e?.data?.message ?? e?.message ?? "").split("\n")[0]);
|
|
2957
|
+
process.exit(1);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
console.log();
|
|
2961
|
+
pOk(`Published ${gray("v" + RB_VERSION)}`);
|
|
2962
|
+
pInfo(`CDN base: ${cyan(config.R2_PUBLIC_URL + "/" + VERSION_PATH)}`);
|
|
2963
|
+
console.log();
|
|
2964
|
+
}
|
|
2965
|
+
var CONFIG_CANDIDATES;
|
|
2966
|
+
var init_publish = __esm({
|
|
2967
|
+
"src/commands/publish.ts"() {
|
|
2968
|
+
init_cjs_shims();
|
|
2969
|
+
init_ui();
|
|
2970
|
+
init_constants();
|
|
2971
|
+
CONFIG_CANDIDATES = [
|
|
2972
|
+
pathe.join(process.cwd(), ".rb-publish"),
|
|
2973
|
+
pathe.join(process.env["USERPROFILE"] ?? process.env["HOME"] ?? "", ".rb-publish")
|
|
2974
|
+
];
|
|
2975
|
+
}
|
|
2976
|
+
});
|
|
2977
|
+
|
|
2933
2978
|
// src/commands/install.ts
|
|
2934
2979
|
var install_exports = {};
|
|
2935
2980
|
__export(install_exports, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulse-rb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "rb CLI — Pulse framework build tool for Roblox script projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rb": "./bin/rb.js"
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"ofetch": "^1.3.4",
|
|
29
29
|
"pathe": "^1.1.2",
|
|
30
30
|
"picocolors": "^1.1.0",
|
|
31
|
+
"typescript-to-lua": "^1.30.0",
|
|
31
32
|
"wasmoon": "^1.16.0",
|
|
32
33
|
"zod": "^4.4.3"
|
|
33
34
|
},
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"@types/node": "^22.0.0",
|
|
38
39
|
"tsup": "^8.3.0",
|
|
39
40
|
"tsx": "^4.19.0",
|
|
40
|
-
"typescript": "^5.
|
|
41
|
+
"typescript": "^5.8.3",
|
|
41
42
|
"vitest": "^2.1.0"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
package/pulse/runtime.lua
CHANGED
|
@@ -341,3 +341,157 @@ _PulseDestroy = _PulseDestroyAll
|
|
|
341
341
|
-- Ordered list of { type, id, value } entries populated by transpiler-generated code.
|
|
342
342
|
-- Consumed by the compiler-generated defaults runner at end of script.
|
|
343
343
|
_PULSE_DEFAULTS = {}
|
|
344
|
+
|
|
345
|
+
-- ── TypeScript-compatible API ─────────────────────────────────────────────────
|
|
346
|
+
-- SolidJS-style signals + Next.js-style pages compiled from TypeScript via TSTL.
|
|
347
|
+
-- These globals are available in .ts source files without any imports.
|
|
348
|
+
|
|
349
|
+
local _currentComponent = nil
|
|
350
|
+
local _sigCounter = 0
|
|
351
|
+
_Pages = {}
|
|
352
|
+
|
|
353
|
+
local function _signal(default)
|
|
354
|
+
_sigCounter = _sigCounter + 1
|
|
355
|
+
local s = Signal(default)
|
|
356
|
+
s._id = ((_currentComponent and _currentComponent._name) or "g") .. "_s" .. _sigCounter
|
|
357
|
+
s.set = function(_, v) s:set(v) end
|
|
358
|
+
s.toggle = function(_) s:set(not s:get()) end
|
|
359
|
+
s.watch = function(_, fn) return s:onChange(fn) end
|
|
360
|
+
s.update = function(_, fn) s:set(fn(s:get())) end
|
|
361
|
+
return s
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
local function _computed(fn) return Computed(fn) end
|
|
365
|
+
|
|
366
|
+
local function _widgetBase(t)
|
|
367
|
+
function t:bind(sig)
|
|
368
|
+
self.signal = sig
|
|
369
|
+
self.id = sig and (sig._id or "?") or nil
|
|
370
|
+
return self
|
|
371
|
+
end
|
|
372
|
+
function t:withTip(text) self.tip = text; return self end
|
|
373
|
+
function t:withDefault(v) self.default = v; return self end
|
|
374
|
+
return t
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
local function _toggle(lbl, opts)
|
|
378
|
+
return _widgetBase({ type="toggle", label=lbl, tip=(opts and opts.tip) or "" })
|
|
379
|
+
end
|
|
380
|
+
local function _slider(lbl, opts)
|
|
381
|
+
return _widgetBase({ type="slider", label=lbl,
|
|
382
|
+
min=(opts and opts.min) or 0, max=(opts and opts.max) or 100,
|
|
383
|
+
tip=(opts and opts.tip) or "", suffix=(opts and opts.suffix) or "" })
|
|
384
|
+
end
|
|
385
|
+
local function _dropdown(lbl, opts)
|
|
386
|
+
return _widgetBase({ type="dropdown", label=lbl,
|
|
387
|
+
options=(opts and opts.options) or {}, tip=(opts and opts.tip) or "" })
|
|
388
|
+
end
|
|
389
|
+
local function _multidropdown(lbl, opts)
|
|
390
|
+
return _widgetBase({ type="multidropdown", label=lbl,
|
|
391
|
+
options=(opts and opts.options) or {}, tip=(opts and opts.tip) or "" })
|
|
392
|
+
end
|
|
393
|
+
local function _button(lbl, fnOrOpts)
|
|
394
|
+
local fn, tip
|
|
395
|
+
if type(fnOrOpts) == "function" then fn = fnOrOpts
|
|
396
|
+
elseif type(fnOrOpts) == "table" then tip = fnOrOpts.tip end
|
|
397
|
+
local t = { type="button", label=lbl, tip=tip or "", action=fn }
|
|
398
|
+
function t:onClick(cb) self.action = cb; return self end
|
|
399
|
+
function t:bind() return self end
|
|
400
|
+
return t
|
|
401
|
+
end
|
|
402
|
+
local function _keybind(lbl, opts)
|
|
403
|
+
return _widgetBase({ type="keybind", label=lbl,
|
|
404
|
+
key=(opts and opts.key) or "None", tip=(opts and opts.tip) or "" })
|
|
405
|
+
end
|
|
406
|
+
local function _label(text)
|
|
407
|
+
local t = { type="label", label=text }; function t:bind() return self end; return t
|
|
408
|
+
end
|
|
409
|
+
local function _separator()
|
|
410
|
+
local t = { type="separator" }; function t:bind() return self end; return t
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
local function _needComp(name)
|
|
414
|
+
assert(_currentComponent, "on." .. name .. " must be called inside defineComponent()")
|
|
415
|
+
return _currentComponent
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
local function _uid() return tostring(math.random(100000,999999)) end
|
|
419
|
+
|
|
420
|
+
local function _hbBind(svc, optsOrFn, fn)
|
|
421
|
+
local comp = _needComp("event")
|
|
422
|
+
local opts, cb
|
|
423
|
+
if type(optsOrFn) == "function" then cb=optsOrFn; opts={}
|
|
424
|
+
else opts=optsOrFn; cb=fn end
|
|
425
|
+
local when=opts.when; local every=opts.every; local last=0
|
|
426
|
+
comp:bind("ev_".._uid(), svc:Connect(function(a,b)
|
|
427
|
+
if when and not when() then return end
|
|
428
|
+
if every then
|
|
429
|
+
local t=type(every)=="table" and every() or every
|
|
430
|
+
local now=tick(); if (now-last)<t then return end; last=now
|
|
431
|
+
end
|
|
432
|
+
local ok,err=pcall(cb,a,b)
|
|
433
|
+
if not ok then warn("[Pulse] "..comp._name.." event error: "..tostring(err)) end
|
|
434
|
+
end))
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
local _on = {}
|
|
438
|
+
_on.heartbeat = function(a,b) _hbBind(_PulseRS.Heartbeat, a,b) end
|
|
439
|
+
_on.renderStepped = function(a,b) _hbBind(_PulseRS.RenderStepped, a,b) end
|
|
440
|
+
_on.stepped = function(a,b) _hbBind(_PulseRS.Stepped, a,b) end
|
|
441
|
+
_on.inputBegan = function(fn) local c=_needComp("inputBegan"); c:bind("ib_".._uid(), _PulseUIS.InputBegan:Connect(fn)) end
|
|
442
|
+
_on.inputEnded = function(fn) local c=_needComp("inputEnded"); c:bind("ie_".._uid(), _PulseUIS.InputEnded:Connect(fn)) end
|
|
443
|
+
_on.characterAdded = function(fn) local c=_needComp("characterAdded"); c:bind("ca_".._uid(), _LocalPlayer.CharacterAdded:Connect(fn)) end
|
|
444
|
+
_on.characterRemoving = function(fn) local c=_needComp("characterRemoving"); c:bind("cr_".._uid(), _LocalPlayer.CharacterRemoving:Connect(fn)) end
|
|
445
|
+
_on.respawn = function(fn) _needComp("respawn"):onRespawn(fn) end
|
|
446
|
+
_on.signal = function(sig,fn) _needComp("signal"):watch(sig,fn) end
|
|
447
|
+
_on.after = function(s,fn) _needComp("after"):task(s,fn) end
|
|
448
|
+
|
|
449
|
+
local function _defineComponent(name, setup)
|
|
450
|
+
local comp = Component(name)
|
|
451
|
+
_currentComponent = comp
|
|
452
|
+
local ok, widgets = pcall(setup)
|
|
453
|
+
_currentComponent = nil
|
|
454
|
+
if not ok then warn("[Pulse] defineComponent('"..name.."') error: "..tostring(widgets)); widgets={} end
|
|
455
|
+
widgets = widgets or {}
|
|
456
|
+
comp._ui = widgets
|
|
457
|
+
for _, w in ipairs(widgets) do
|
|
458
|
+
if w.default ~= nil and w.signal then
|
|
459
|
+
local kind = w.type == "toggle" and "toggle" or "option"
|
|
460
|
+
_PULSE_DEFAULTS[#_PULSE_DEFAULTS+1] = {
|
|
461
|
+
type=kind, id=w.id or (name.."_?"), value=w.default,
|
|
462
|
+
set=function(v) if w.signal then w.signal.set(nil,v) end end
|
|
463
|
+
}
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
return comp
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
local function _groupbox(side, title, opts)
|
|
470
|
+
return { type="groupbox", side=side, title=title,
|
|
471
|
+
icon=(opts and opts.icon) or "",
|
|
472
|
+
mount=(opts and opts.mount) or nil,
|
|
473
|
+
widgets=(opts and opts.widgets) or {} }
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
local function _definePage(title, opts, layoutFn)
|
|
477
|
+
local page = { title=title, icon=(opts and opts.icon) or "circle",
|
|
478
|
+
layout=layoutFn and layoutFn() or {} }
|
|
479
|
+
table.insert(_Pages, page)
|
|
480
|
+
return page
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
-- Expose as globals (available in TypeScript-compiled Lua without imports)
|
|
484
|
+
signal = _signal
|
|
485
|
+
computed = _computed
|
|
486
|
+
defineComponent = _defineComponent
|
|
487
|
+
on = _on
|
|
488
|
+
toggle = _toggle
|
|
489
|
+
slider = _slider
|
|
490
|
+
dropdown = _dropdown
|
|
491
|
+
multidropdown = _multidropdown
|
|
492
|
+
button = _button
|
|
493
|
+
keybind = _keybind
|
|
494
|
+
label = _label
|
|
495
|
+
separator = _separator
|
|
496
|
+
groupbox = _groupbox
|
|
497
|
+
definePage = _definePage
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
defineComponent('PlayerESP', () => {
|
|
2
|
+
const enabled = signal(false)
|
|
3
|
+
const showName = signal(true)
|
|
4
|
+
const showBox = signal(true)
|
|
5
|
+
|
|
6
|
+
on.heartbeat({ when: enabled, every: 0.05 }, () => {
|
|
7
|
+
// Draw ESP — use Pulse.Draw helpers
|
|
8
|
+
// Pulse.Draw.box(character, { color: [255, 0, 0] })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
return [
|
|
12
|
+
toggle('Player ESP').bind(enabled),
|
|
13
|
+
toggle('Show Names').bind(showName),
|
|
14
|
+
toggle('Show Boxes').bind(showBox),
|
|
15
|
+
]
|
|
16
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
defineComponent('FOVChanger', () => {
|
|
2
|
+
const enabled = signal(false)
|
|
3
|
+
const fov = signal(70)
|
|
4
|
+
|
|
5
|
+
const applyFov = () => {
|
|
6
|
+
const cam = (workspace as any).CurrentCamera
|
|
7
|
+
if (cam) cam.FieldOfView = enabled() ? fov() : 70
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
on.signal(enabled, applyFov)
|
|
11
|
+
on.signal(fov, applyFov)
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
toggle('FOV Changer').bind(enabled),
|
|
15
|
+
slider('Field of View', { min: 1, max: 120, suffix: '°' }).bind(fov),
|
|
16
|
+
]
|
|
17
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SpeedHack — SolidJS-style reactive component.
|
|
2
|
+
// signal() is like SolidJS createSignal; on.* hooks are like createEffect.
|
|
3
|
+
|
|
4
|
+
defineComponent('SpeedHack', () => {
|
|
5
|
+
const enabled = signal(false)
|
|
6
|
+
const speed = signal(16)
|
|
7
|
+
|
|
8
|
+
on.heartbeat({ when: enabled }, () => {
|
|
9
|
+
const char = _PulseGetChar()
|
|
10
|
+
if (!char) return
|
|
11
|
+
const h = char.FindFirstChild('Humanoid') as any
|
|
12
|
+
if (h) h.WalkSpeed = speed()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
on.respawn(() => {
|
|
16
|
+
const char = _PulseGetChar()
|
|
17
|
+
if (!char) return
|
|
18
|
+
const h = char.FindFirstChild('Humanoid') as any
|
|
19
|
+
if (h) h.WalkSpeed = enabled() ? speed() : 16
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
toggle('Speed Hack').bind(enabled),
|
|
24
|
+
slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
|
|
25
|
+
]
|
|
26
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Pulse layout — configures the script window.
|
|
2
|
+
// Based on Next.js root layout convention.
|
|
3
|
+
// uiLibrary: 'windui' | 'linoria'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: '{NAME}',
|
|
7
|
+
author: '',
|
|
8
|
+
toggleKey: 'RightControl',
|
|
9
|
+
size: [850, 560] as [number, number],
|
|
10
|
+
uiLibrary: 'windui' as 'windui' | 'linoria',
|
|
11
|
+
theme: 'Indigo',
|
|
12
|
+
icon: 'code-2',
|
|
13
|
+
folder: '{NAME}',
|
|
14
|
+
acrylic: true,
|
|
15
|
+
transparency: 0.8,
|
|
16
|
+
openButtonMobileOnly: true,
|
|
17
|
+
openButtonIcon: 'code-2',
|
|
18
|
+
themes: [] as LayoutConfig['themes'],
|
|
19
|
+
compatExclude: [] as string[],
|
|
20
|
+
} satisfies LayoutConfig
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Home page tab — Next.js-style file-based routing.
|
|
2
|
+
// Filename prefix (1_) determines tab order.
|
|
3
|
+
|
|
4
|
+
definePage('Home', { icon: 'house' }, () => [
|
|
5
|
+
groupbox('left', 'Player', { icon: 'person', mount: 'SpeedHack' }),
|
|
6
|
+
groupbox('left', 'Player', { icon: 'person', mount: 'FOVChanger' }),
|
|
7
|
+
groupbox('right', 'Visuals', { icon: 'eye', mount: 'PlayerESP' }),
|
|
8
|
+
])
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
-- PlayerESP — example component. Customize or delete this file.
|
|
2
|
-
component {
|
|
3
|
-
signal enabled = false
|
|
4
|
-
|
|
5
|
-
init {
|
|
6
|
-
Pulse.Notify.onToggle(PlayerESP.enabled, "Player ESP")
|
|
7
|
-
|
|
8
|
-
local _CoreGui = game:GetService("CoreGui")
|
|
9
|
-
local _Players = game:GetService("Players")
|
|
10
|
-
local _highlights = {}
|
|
11
|
-
|
|
12
|
-
local function _addHighlight(player)
|
|
13
|
-
if not player.Character then return end
|
|
14
|
-
if _highlights[player] then return end
|
|
15
|
-
local hl = Instance.new("Highlight")
|
|
16
|
-
hl.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
|
|
17
|
-
hl.FillColor = Color3.fromRGB(255, 60, 60)
|
|
18
|
-
hl.FillTransparency = 0.4
|
|
19
|
-
hl.OutlineColor = Color3.fromRGB(255, 60, 60)
|
|
20
|
-
hl.Adornee = player.Character
|
|
21
|
-
hl.Parent = _CoreGui
|
|
22
|
-
_highlights[player] = hl
|
|
23
|
-
Pulse.Log.trace("PlayerESP", "highlighted", { name = player.Name })
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
local function _removeHighlight(player)
|
|
27
|
-
if _highlights[player] then
|
|
28
|
-
_highlights[player]:Destroy()
|
|
29
|
-
_highlights[player] = nil
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
local function _clearAll()
|
|
34
|
-
for _, hl in pairs(_highlights) do hl:Destroy() end
|
|
35
|
-
_highlights = {}
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
local _loop = Pulse.Loop.new(1.0, function()
|
|
39
|
-
for _, player in ipairs(func.GetCachedPlayers()) do
|
|
40
|
-
if player ~= _Players.LocalPlayer then
|
|
41
|
-
_addHighlight(player)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end)
|
|
45
|
-
|
|
46
|
-
local function espLoop(value)
|
|
47
|
-
if value then
|
|
48
|
-
_loop:start()
|
|
49
|
-
Pulse.Log.info("PlayerESP", "started")
|
|
50
|
-
else
|
|
51
|
-
_loop:stop()
|
|
52
|
-
_clearAll()
|
|
53
|
-
Pulse.Log.info("PlayerESP", "stopped")
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
on enabled { espLoop(v) }
|
|
59
|
-
|
|
60
|
-
on CharacterAdded {
|
|
61
|
-
if not enabled then return end
|
|
62
|
-
_clearAll()
|
|
63
|
-
_loop:start()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
ui {
|
|
67
|
-
toggle "Player ESP" -> enabled tip="Highlight all players through walls"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
-- FOVChanger — example component. Customize or delete this file.
|
|
2
|
-
component {
|
|
3
|
-
signal enabled = false
|
|
4
|
-
signal fov = 90
|
|
5
|
-
|
|
6
|
-
init {
|
|
7
|
-
Pulse.Notify.onToggle(FOVChanger.enabled, "Custom FOV")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
ui {
|
|
11
|
-
toggle "Custom FOV" -> enabled
|
|
12
|
-
slider "Field of View" -> fov [30, 120]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
on enabled, fov {
|
|
16
|
-
guard cam = camera
|
|
17
|
-
cam.FieldOfView = enabled and fov or 70
|
|
18
|
-
Pulse.Log.info("FOVChanger", "fov set", { fov = cam.FieldOfView })
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
-- SpeedHack — example component. Customize or delete this file.
|
|
2
|
-
component {
|
|
3
|
-
signal enabled = false
|
|
4
|
-
signal speed = 50
|
|
5
|
-
|
|
6
|
-
init {
|
|
7
|
-
Pulse.Notify.onToggle(SpeedHack.enabled, "Speed Hack")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
ui {
|
|
11
|
-
toggle "Speed Hack" -> enabled tip="Override character walk speed"
|
|
12
|
-
slider "Walk Speed" -> speed [16, 250]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
on CharacterAdded {
|
|
16
|
-
guard h = humanoid
|
|
17
|
-
h.WalkSpeed = enabled and speed or 16
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
on enabled, speed {
|
|
21
|
-
guard h = humanoid
|
|
22
|
-
h.WalkSpeed = enabled and speed or 16
|
|
23
|
-
Pulse.Log.info("SpeedHack", "walk speed set", { speed = h.WalkSpeed })
|
|
24
|
-
}
|
|
25
|
-
}
|
package/templates/layout.rblua
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
layout {
|
|
2
|
-
title = SCRIPT_NAME,
|
|
3
|
-
author = SCRIPT_AUTHOR, -- shown as subtitle under the title
|
|
4
|
-
toggle_key = "RightControl", -- Enum.KeyCode name — menu open/close bind
|
|
5
|
-
size = { 850, 560 }, -- window width × height in pixels
|
|
6
|
-
ui_library = "windui", -- "linoria" | "windui"
|
|
7
|
-
theme = "Indigo", -- Dark Light Rose Plant Red Indigo Sky Violet Amber Emerald Midnight Crimson MonokaiPro CottonCandy Mellowsi Rainbow
|
|
8
|
-
icon = "code-2", -- Lucide icon name, rbxassetid://, or URL
|
|
9
|
-
folder = "{NAME}", -- executor save folder for config persistence
|
|
10
|
-
notify_title = SCRIPT_NAME, -- notification header text
|
|
11
|
-
transparency = 0.8, -- background transparency: 0 = opaque, 1 = fully transparent
|
|
12
|
-
acrylic = true, -- blur effect behind window (executor must support it)
|
|
13
|
-
open_button_mobile_only = true, -- false = show floating open button on desktop too
|
|
14
|
-
open_button_icon = "code-2", -- Lucide icon for the floating open button
|
|
15
|
-
|
|
16
|
-
-- Custom WindUI themes — entries appear in the theme dropdown automatically.
|
|
17
|
-
-- Available fields: accent background outline text placeholder button icon
|
|
18
|
-
themes = {
|
|
19
|
-
-- { name = "MyTheme", accent = "#7c3aed", background = "#0e0c1a", outline = "#1e1b4b",
|
|
20
|
-
-- text = "#e8e3ff", placeholder = "#6d6d8a", button = "#1e1b4b", icon = "#a78bfa" },
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
-- compat_exclude lists modules dropped from build/script.compat.obf.lua
|
|
24
|
-
-- (the build for executors that don't support full UNC APIs).
|
|
25
|
-
compat_exclude = {
|
|
26
|
-
-- "player/UNC.rblua",
|
|
27
|
-
}
|
|
28
|
-
}
|
package/templates/module.rblua
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
component {
|
|
2
|
-
-- ── Signals ───────────────────────────────────────────────────────────────
|
|
3
|
-
signal enabled = false
|
|
4
|
-
|
|
5
|
-
-- ── Keybind (toggle on keypress) ──────────────────────────────────────────
|
|
6
|
-
-- keybind Enum.KeyCode.X → enabled
|
|
7
|
-
|
|
8
|
-
-- ── State change handler ──────────────────────────────────────────────────
|
|
9
|
-
on enabled {
|
|
10
|
-
if v then
|
|
11
|
-
-- feature activated
|
|
12
|
-
else
|
|
13
|
-
-- feature deactivated
|
|
14
|
-
end
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
-- ── Respawn handler ───────────────────────────────────────────────────────
|
|
18
|
-
on Respawn {
|
|
19
|
-
-- runs every time the player's character loads
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
-- ── Per-frame loop ────────────────────────────────────────────────────────
|
|
23
|
-
-- on Heartbeat when enabled {
|
|
24
|
-
-- guard hrp
|
|
25
|
-
-- -- 60fps logic here
|
|
26
|
-
-- }
|
|
27
|
-
|
|
28
|
-
-- ── Exported functions (callable from other components) ───────────────────
|
|
29
|
-
func Toggle() {
|
|
30
|
-
enabled(not enabled())
|
|
31
|
-
}
|
|
32
|
-
}
|