pulse-rb 1.3.2 → 1.4.1
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/adapters/windui.lua +711 -6
- package/bin/rb.js +0 -0
- package/dist/index.js +117 -42
- package/package.json +11 -10
- package/pulse/helpers/log.lua +6 -1
- package/pulse/runtime.lua +112 -28
- package/templates/component.ts +11 -0
- package/templates/globals.lua +10 -26
- package/templates/page_home.ts +3 -6
package/bin/rb.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
|
|
|
56
56
|
var init_constants = __esm({
|
|
57
57
|
"src/constants.ts"() {
|
|
58
58
|
init_cjs_shims();
|
|
59
|
-
RB_VERSION = "1.
|
|
59
|
+
RB_VERSION = "1.4.1";
|
|
60
60
|
RB_HOME = path.join(os.homedir(), ".rb");
|
|
61
61
|
path.join(RB_HOME, "bin");
|
|
62
62
|
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
@@ -1193,6 +1193,59 @@ end)
|
|
|
1193
1193
|
}
|
|
1194
1194
|
return "windui";
|
|
1195
1195
|
}
|
|
1196
|
+
readLayoutConfig() {
|
|
1197
|
+
const layoutTs = path.join(this.srcDir, "layout.ts");
|
|
1198
|
+
const cfg = {
|
|
1199
|
+
title: "Script Hub",
|
|
1200
|
+
author: "",
|
|
1201
|
+
toggleKey: "RightControl",
|
|
1202
|
+
sizeW: 950,
|
|
1203
|
+
sizeH: 600,
|
|
1204
|
+
theme: "Dark",
|
|
1205
|
+
icon: "",
|
|
1206
|
+
folder: "Hub",
|
|
1207
|
+
acrylic: false,
|
|
1208
|
+
transparency: 0.8,
|
|
1209
|
+
openButtonMobileOnly: false,
|
|
1210
|
+
openButtonIcon: "",
|
|
1211
|
+
description: "",
|
|
1212
|
+
discord: "",
|
|
1213
|
+
version: ""
|
|
1214
|
+
};
|
|
1215
|
+
if (!fs.existsSync(layoutTs)) return cfg;
|
|
1216
|
+
const src = fs.readFileSync(layoutTs, "utf8");
|
|
1217
|
+
const str = (key) => {
|
|
1218
|
+
const m = src.match(new RegExp(key + `\\s*:\\s*['"]([^'"]+)['"]`));
|
|
1219
|
+
if (m) cfg[key] = m[1];
|
|
1220
|
+
};
|
|
1221
|
+
const bool = (key) => {
|
|
1222
|
+
const m = src.match(new RegExp(key + `\\s*:\\s*(true|false)`));
|
|
1223
|
+
if (m) cfg[key] = m[1] === "true";
|
|
1224
|
+
};
|
|
1225
|
+
const num = (key) => {
|
|
1226
|
+
const m = src.match(new RegExp(key + `\\s*:\\s*([\\d.]+)`));
|
|
1227
|
+
if (m) cfg[key] = parseFloat(m[1]);
|
|
1228
|
+
};
|
|
1229
|
+
str("title");
|
|
1230
|
+
str("author");
|
|
1231
|
+
str("toggleKey");
|
|
1232
|
+
str("theme");
|
|
1233
|
+
str("icon");
|
|
1234
|
+
str("folder");
|
|
1235
|
+
str("openButtonIcon");
|
|
1236
|
+
str("description");
|
|
1237
|
+
str("discord");
|
|
1238
|
+
str("version");
|
|
1239
|
+
bool("acrylic");
|
|
1240
|
+
bool("openButtonMobileOnly");
|
|
1241
|
+
num("transparency");
|
|
1242
|
+
const sz = src.match(/size\s*:\s*\[(\d+)\s*,\s*(\d+)\]/);
|
|
1243
|
+
if (sz) {
|
|
1244
|
+
cfg["sizeW"] = parseInt(sz[1]);
|
|
1245
|
+
cfg["sizeH"] = parseInt(sz[2]);
|
|
1246
|
+
}
|
|
1247
|
+
return cfg;
|
|
1248
|
+
}
|
|
1196
1249
|
resolveUi(ui) {
|
|
1197
1250
|
if (!ui) ui = this.getUiLibrary();
|
|
1198
1251
|
if (!VALID_UI_LIBS.has(ui)) throw new Error(`Unknown UI library '${ui}'. Valid: ${[...VALID_UI_LIBS].sort().join(", ")}`);
|
|
@@ -1247,7 +1300,7 @@ end)
|
|
|
1247
1300
|
if (path.relative(src, f).split(/[/\\]/).length === 1) continue;
|
|
1248
1301
|
middle.push(f);
|
|
1249
1302
|
}
|
|
1250
|
-
if (opts.dev) {
|
|
1303
|
+
if (opts.dev && !opts.cdn) {
|
|
1251
1304
|
const fwDevUi = path.join(PULSE_DEV_DIR, "ui");
|
|
1252
1305
|
if (fs.existsSync(fwDevUi)) {
|
|
1253
1306
|
for (const f of rglob(fwDevUi, [".lua", ".rblua", ".ts"]).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))) {
|
|
@@ -1288,13 +1341,54 @@ end)
|
|
|
1288
1341
|
parts.push("\n");
|
|
1289
1342
|
if (hasTs) {
|
|
1290
1343
|
const base = `${CDN_BASE_URL}/v${RB_VERSION}`;
|
|
1344
|
+
const lc = this.readLayoutConfig();
|
|
1345
|
+
const luaBool = (v) => v ? "true" : "false";
|
|
1291
1346
|
parts.push(`-- Pulse v${RB_VERSION}
|
|
1347
|
+
`);
|
|
1348
|
+
parts.push(`local _PULSE_LAYOUT={`);
|
|
1349
|
+
parts.push(`title=${JSON.stringify(lc["title"])},`);
|
|
1350
|
+
parts.push(`author=${JSON.stringify(lc["author"])},`);
|
|
1351
|
+
parts.push(`toggleKey=${JSON.stringify(lc["toggleKey"])},`);
|
|
1352
|
+
parts.push(`sizeW=${lc["sizeW"]},sizeH=${lc["sizeH"]},`);
|
|
1353
|
+
parts.push(`theme=${JSON.stringify(lc["theme"])},`);
|
|
1354
|
+
parts.push(`icon=${JSON.stringify(lc["icon"])},`);
|
|
1355
|
+
parts.push(`folder=${JSON.stringify(lc["folder"])},`);
|
|
1356
|
+
parts.push(`acrylic=${luaBool(lc["acrylic"])},`);
|
|
1357
|
+
parts.push(`transparency=${lc["transparency"]},`);
|
|
1358
|
+
parts.push(`openButtonMobileOnly=${luaBool(lc["openButtonMobileOnly"])},`);
|
|
1359
|
+
parts.push(`openButtonIcon=${JSON.stringify(lc["openButtonIcon"])}`);
|
|
1360
|
+
if (lc["description"]) parts.push(`,description=${JSON.stringify(lc["description"])}`);
|
|
1361
|
+
if (lc["discord"]) parts.push(`,discord=${JSON.stringify(lc["discord"])}`);
|
|
1362
|
+
if (lc["version"]) parts.push(`,version=${JSON.stringify(lc["version"])}`);
|
|
1363
|
+
if (opts.dev) parts.push(`,dev=true`);
|
|
1364
|
+
parts.push(`}
|
|
1292
1365
|
`);
|
|
1293
1366
|
parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
|
|
1294
1367
|
`);
|
|
1368
|
+
if (opts.dev) {
|
|
1369
|
+
parts.push(`_PULSE_DEV = true
|
|
1370
|
+
`);
|
|
1371
|
+
parts.push(`if _P.Pulse and _P.Pulse.Log then _P.Pulse.Log.enable() end
|
|
1372
|
+
`);
|
|
1373
|
+
}
|
|
1295
1374
|
parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
|
|
1296
1375
|
`);
|
|
1297
|
-
parts.push(
|
|
1376
|
+
parts.push(
|
|
1377
|
+
`do
|
|
1378
|
+
local _rs=game:GetService("RunService")
|
|
1379
|
+
local _lp_=game:GetService("Players").LocalPlayer
|
|
1380
|
+
local _uis_=game:GetService("UserInputService")
|
|
1381
|
+
_rs.Heartbeat:Connect(function(a,b) if _PulseRunHeartbeat then _PulseRunHeartbeat(a,b) end end)
|
|
1382
|
+
_rs.RenderStepped:Connect(function(a,b) if _PulseRunRenderStepped then _PulseRunRenderStepped(a,b) end end)
|
|
1383
|
+
_rs.Stepped:Connect(function(a,b) if _PulseRunStepped then _PulseRunStepped(a,b) end end)
|
|
1384
|
+
_lp_.CharacterAdded:Connect(function(c) if _PulseRunCharAdded then _PulseRunCharAdded(c) end end)
|
|
1385
|
+
_lp_.CharacterRemoving:Connect(function(c) if _PulseRunCharRemoving then _PulseRunCharRemoving(c) end end)
|
|
1386
|
+
_uis_.InputBegan:Connect(function(i,g) if _PulseRunInputBegan then _PulseRunInputBegan(i,g) end end)
|
|
1387
|
+
_uis_.InputEnded:Connect(function(i,g) if _PulseRunInputEnded then _PulseRunInputEnded(i,g) end end)
|
|
1388
|
+
end
|
|
1389
|
+
`
|
|
1390
|
+
);
|
|
1391
|
+
parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))(_P,_PULSE_LAYOUT)
|
|
1298
1392
|
`);
|
|
1299
1393
|
parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
|
|
1300
1394
|
`);
|
|
@@ -1304,17 +1398,6 @@ end)
|
|
|
1304
1398
|
`);
|
|
1305
1399
|
parts.push("\n");
|
|
1306
1400
|
}
|
|
1307
|
-
if (opts.dev && fs.existsSync(PULSE_DEV_DIR)) {
|
|
1308
|
-
const rbRoot = path.join(__dirname, "..", "..");
|
|
1309
|
-
for (const f of fs.readdirSync(PULSE_DEV_DIR).filter((n) => n.endsWith(".lua")).sort()) {
|
|
1310
|
-
const full = path.join(PULSE_DEV_DIR, f);
|
|
1311
|
-
const rel = path.relative(rbRoot, full).replace(/\\/g, "/");
|
|
1312
|
-
parts.push(`-- [${rel}]
|
|
1313
|
-
`);
|
|
1314
|
-
parts.push(fs.readFileSync(full, "utf8"));
|
|
1315
|
-
parts.push("\n");
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
1401
|
const label = (p) => {
|
|
1319
1402
|
try {
|
|
1320
1403
|
return path.relative(this.root, p).replace(/\\/g, "/");
|
|
@@ -1387,6 +1470,11 @@ end)
|
|
|
1387
1470
|
parts.push(DEFAULTS_RUNNER);
|
|
1388
1471
|
parts.push("\n");
|
|
1389
1472
|
}
|
|
1473
|
+
if (opts.dev) {
|
|
1474
|
+
parts.push(
|
|
1475
|
+
'-- [dev: log config]\nif Pulse and Pulse.Log then\n Pulse.Log.configure({ level = "debug", console = true })\n Pulse.Log.info("dev", "dev build active")\nend\n\n'
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1390
1478
|
parts.push("-- [generated: destroy registration]\n");
|
|
1391
1479
|
parts.push(DESTROY_REGISTRATION);
|
|
1392
1480
|
parts.push("\n");
|
|
@@ -1797,25 +1885,20 @@ function makeClaudeMd(name) {
|
|
|
1797
1885
|
function makeAgentsMd(name) {
|
|
1798
1886
|
return read("AGENTS.md").replace(/{NAME}/g, name);
|
|
1799
1887
|
}
|
|
1800
|
-
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES,
|
|
1888
|
+
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_HOME_TS, TEMPLATE_COMPONENT_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS;
|
|
1801
1889
|
var init_templates = __esm({
|
|
1802
1890
|
"src/templates.ts"() {
|
|
1803
1891
|
init_cjs_shims();
|
|
1804
1892
|
DIR = path.join(__dirname, "..", "templates");
|
|
1805
1893
|
TEMPLATE_GLOBALS = read("globals.lua");
|
|
1806
1894
|
TEMPLATE_REMOTES = read("remotes.lua");
|
|
1807
|
-
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1808
|
-
read("layout.rblua");
|
|
1809
|
-
read("page_home.rblua");
|
|
1810
|
-
read("example_speed.rblua");
|
|
1811
|
-
read("example_fov.rblua");
|
|
1812
|
-
read("example_esp.rblua");
|
|
1813
|
-
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1814
1895
|
MODULE_BOILERPLATE = read("module.lua");
|
|
1815
|
-
MODULE_RBLUA_BOILERPLATE = read("module.rblua");
|
|
1816
1896
|
REMOTE_BOILERPLATE = read("remote.lua");
|
|
1897
|
+
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1898
|
+
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1817
1899
|
TEMPLATE_LAYOUT_TS = read("layout.ts");
|
|
1818
1900
|
TEMPLATE_PAGE_HOME_TS = read("page_home.ts");
|
|
1901
|
+
TEMPLATE_COMPONENT_TS = read("component.ts");
|
|
1819
1902
|
TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
|
|
1820
1903
|
TEMPLATE_EX_FOV_TS = read("component_fov.ts");
|
|
1821
1904
|
TEMPLATE_EX_ESP_TS = read("component_esp.ts");
|
|
@@ -2015,9 +2098,8 @@ function toCamel(name) {
|
|
|
2015
2098
|
function cmdNew(args) {
|
|
2016
2099
|
const root = requireProjectRoot();
|
|
2017
2100
|
if (args.type === "module") {
|
|
2018
|
-
const plain = args.plain ?? false;
|
|
2019
2101
|
let relPath = args.path;
|
|
2020
|
-
if (plain) {
|
|
2102
|
+
if (args.plain) {
|
|
2021
2103
|
if (!relPath.endsWith(".lua")) relPath += ".lua";
|
|
2022
2104
|
const dest = pathe.join(root, "src", relPath);
|
|
2023
2105
|
const camel = toCamel(relPath);
|
|
@@ -2033,23 +2115,23 @@ function cmdNew(args) {
|
|
|
2033
2115
|
pOk(`src/${relPath}`);
|
|
2034
2116
|
console.log();
|
|
2035
2117
|
pKv("File", `src/${relPath}`);
|
|
2036
|
-
pKv("Tip", "add
|
|
2118
|
+
pKv("Tip", "add helpers onto func; keep state local");
|
|
2037
2119
|
} else {
|
|
2038
|
-
if (!relPath.endsWith(".
|
|
2120
|
+
if (!relPath.endsWith(".ts")) relPath += ".ts";
|
|
2039
2121
|
const dest = pathe.join(root, "src", relPath);
|
|
2040
2122
|
const camel = toCamel(relPath);
|
|
2123
|
+
const content = TEMPLATE_COMPONENT_TS.replace(/{Camel}/g, camel);
|
|
2041
2124
|
if (fs.existsSync(dest)) {
|
|
2042
2125
|
pFail(`src/${relPath} already exists`);
|
|
2043
2126
|
process.exit(1);
|
|
2044
2127
|
}
|
|
2045
2128
|
fs.mkdirSync(pathe.join(dest, ".."), { recursive: true });
|
|
2046
|
-
fs.writeFileSync(dest,
|
|
2129
|
+
fs.writeFileSync(dest, content, "utf8");
|
|
2047
2130
|
pOk(`src/${relPath}`);
|
|
2048
2131
|
console.log();
|
|
2049
2132
|
pKv("Component", camel);
|
|
2050
|
-
pKv("
|
|
2051
|
-
pKv("
|
|
2052
|
-
pKv("Listen", `Components.${camel}.onEnable:connect(...)`);
|
|
2133
|
+
pKv("Mount", `groupbox('left', '${camel}', { mount: '${camel}' })`);
|
|
2134
|
+
pKv("Tip", "add to a page groupbox in src/pages/*.ts to render it");
|
|
2053
2135
|
}
|
|
2054
2136
|
console.log();
|
|
2055
2137
|
} else if (args.type === "remote") {
|
|
@@ -2067,14 +2149,14 @@ function cmdNew(args) {
|
|
|
2067
2149
|
pOk(`func.Remote_${remoteName}(...) ${gray("\u2192 remotes.lua")}`);
|
|
2068
2150
|
console.log();
|
|
2069
2151
|
} else {
|
|
2070
|
-
pFail(`Unknown type '${args.type}'`, "use: rb new module | rb new remote");
|
|
2152
|
+
pFail(`Unknown type '${args.type}'`, "use: rb new module | rb new module --plain | rb new remote");
|
|
2071
2153
|
process.exit(1);
|
|
2072
2154
|
}
|
|
2073
2155
|
}
|
|
2074
2156
|
async function cmdRemove(args) {
|
|
2075
2157
|
const root = requireProjectRoot();
|
|
2076
2158
|
let relPath = args.path;
|
|
2077
|
-
if (!relPath.endsWith(".lua") && !relPath.endsWith(".
|
|
2159
|
+
if (!relPath.endsWith(".lua") && !relPath.endsWith(".ts")) relPath += ".ts";
|
|
2078
2160
|
const target = pathe.join(root, "src", relPath);
|
|
2079
2161
|
if (!fs.existsSync(target)) {
|
|
2080
2162
|
pFail(`src/${relPath} not found`);
|
|
@@ -2925,25 +3007,18 @@ async function cmdPublish(_args) {
|
|
|
2925
3007
|
}
|
|
2926
3008
|
}
|
|
2927
3009
|
bundleParts.push(
|
|
2928
|
-
"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}"
|
|
3010
|
+
"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,_PulseRunHeartbeat=_PulseRunHeartbeat,_PulseRunRenderStepped=_PulseRunRenderStepped,_PulseRunStepped=_PulseRunStepped,_PulseRunCharAdded=_PulseRunCharAdded,_PulseRunCharRemoving=_PulseRunCharRemoving,_PulseRunInputBegan=_PulseRunInputBegan,_PulseRunInputEnded=_PulseRunInputEnded}"
|
|
2929
3011
|
);
|
|
2930
3012
|
const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
|
|
2931
3013
|
const uploads = [
|
|
2932
3014
|
{ remote: `${VERSION_PATH}/bundle.lua`, content: bundleContent }
|
|
2933
3015
|
];
|
|
2934
|
-
const pulseUiDir = pathe.join(PULSE_DIR2, "ui");
|
|
2935
3016
|
for (const adapter of ["windui"]) {
|
|
2936
3017
|
const p = pathe.join(ADAPTERS, `${adapter}.lua`);
|
|
2937
3018
|
if (!fs.existsSync(p)) continue;
|
|
2938
|
-
const parts = [fs.readFileSync(p, "utf8").trimEnd()];
|
|
2939
|
-
const settings = pathe.join(pulseUiDir, `${adapter}_settings.lua`);
|
|
2940
|
-
if (fs.existsSync(settings)) {
|
|
2941
|
-
parts.push(`-- ${adapter}_settings`);
|
|
2942
|
-
parts.push(fs.readFileSync(settings, "utf8").trimEnd());
|
|
2943
|
-
}
|
|
2944
3019
|
uploads.push({
|
|
2945
3020
|
remote: `${VERSION_PATH}/adapters/${adapter}.lua`,
|
|
2946
|
-
content: Buffer.from(
|
|
3021
|
+
content: Buffer.from(fs.readFileSync(p, "utf8"), "utf8")
|
|
2947
3022
|
});
|
|
2948
3023
|
}
|
|
2949
3024
|
pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulse-rb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "rb CLI — Pulse framework build tool for Roblox script projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rb": "./bin/rb.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"prepublishOnly": "pnpm run build"
|
|
14
|
+
},
|
|
9
15
|
"files": [
|
|
10
16
|
"dist",
|
|
11
17
|
"bin",
|
|
@@ -33,6 +39,7 @@
|
|
|
33
39
|
"zod": "^4.4.3"
|
|
34
40
|
},
|
|
35
41
|
"devDependencies": {
|
|
42
|
+
"@rb-pulse/core": "workspace:*",
|
|
36
43
|
"@rbxts/types": "^1.0.924",
|
|
37
44
|
"@types/fs-extra": "^11.0.4",
|
|
38
45
|
"@types/luaparse": "^0.2.9",
|
|
@@ -40,8 +47,7 @@
|
|
|
40
47
|
"tsup": "^8.3.0",
|
|
41
48
|
"tsx": "^4.19.0",
|
|
42
49
|
"typescript": "^5.8.3",
|
|
43
|
-
"vitest": "^2.1.0"
|
|
44
|
-
"@rb-pulse/core": "1.3.2"
|
|
50
|
+
"vitest": "^2.1.0"
|
|
45
51
|
},
|
|
46
52
|
"engines": {
|
|
47
53
|
"node": ">=18.0.0"
|
|
@@ -53,10 +59,5 @@
|
|
|
53
59
|
"pulse",
|
|
54
60
|
"script"
|
|
55
61
|
],
|
|
56
|
-
"license": "MIT"
|
|
57
|
-
|
|
58
|
-
"build": "tsup",
|
|
59
|
-
"dev": "tsx src/index.ts",
|
|
60
|
-
"test": "vitest"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
62
|
+
"license": "MIT"
|
|
63
|
+
}
|
package/pulse/helpers/log.lua
CHANGED
|
@@ -73,7 +73,12 @@ do
|
|
|
73
73
|
if #_buf > _bufMax then table.remove(_buf, 1) end
|
|
74
74
|
|
|
75
75
|
if _console then
|
|
76
|
-
|
|
76
|
+
-- Defer print outside the caller's thread (e.g. Heartbeat) so the
|
|
77
|
+
-- output always reaches the developer console regardless of context.
|
|
78
|
+
local _ln, _nw = line, n >= 3
|
|
79
|
+
task.spawn(function()
|
|
80
|
+
if _nw then warn(_ln) else print(_ln) end
|
|
81
|
+
end)
|
|
77
82
|
end
|
|
78
83
|
for _, fn in ipairs(_subs) do pcall(fn, entry) end
|
|
79
84
|
if _file then
|
package/pulse/runtime.lua
CHANGED
|
@@ -364,13 +364,16 @@ end
|
|
|
364
364
|
local function _computed(fn) return Computed(fn) end
|
|
365
365
|
|
|
366
366
|
local function _widgetBase(t)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
-- TSTL compiles TypeScript `.bind(sig)` as Lua dot-call: `t.bind(sig)`.
|
|
368
|
+
-- Colon-style methods receive the wrong args (sig becomes self, actual sig is nil).
|
|
369
|
+
-- Close over t so the function works correctly regardless of call style.
|
|
370
|
+
t.bind = function(sig)
|
|
371
|
+
t.signal = sig
|
|
372
|
+
t.id = sig and (sig._id or "?") or nil
|
|
373
|
+
return t
|
|
371
374
|
end
|
|
372
|
-
|
|
373
|
-
|
|
375
|
+
t.withTip = function(text) t.tip = text; return t end
|
|
376
|
+
t.withDefault = function(v) t.default = v; return t end
|
|
374
377
|
return t
|
|
375
378
|
end
|
|
376
379
|
|
|
@@ -415,36 +418,116 @@ local function _needComp(name)
|
|
|
415
418
|
return _currentComponent
|
|
416
419
|
end
|
|
417
420
|
|
|
418
|
-
|
|
421
|
+
-- ── Sandboxing-safe event runner ─────────────────────────────────────────────
|
|
422
|
+
-- Executor loadstring() environments sandbox Roblox event :Connect() — calls
|
|
423
|
+
-- from inside the bundle never fire. Fix: store callbacks in plain Lua tables
|
|
424
|
+
-- here; the compiler emits user-scope Roblox connections that call _PulseRun*,
|
|
425
|
+
-- routing events into these runners from the non-sandboxed user script.
|
|
426
|
+
|
|
427
|
+
local _cbSeq = 0
|
|
428
|
+
local function _nextId() _cbSeq = _cbSeq + 1; return _cbSeq end
|
|
429
|
+
|
|
430
|
+
local _HbCbs = {} -- Heartbeat
|
|
431
|
+
local _RsCbs = {} -- RenderStepped
|
|
432
|
+
local _StCbs = {} -- Stepped
|
|
433
|
+
local _CaCbs = {} -- CharacterAdded / respawn
|
|
434
|
+
local _CrCbs = {} -- CharacterRemoving
|
|
435
|
+
local _IbCbs = {} -- InputBegan
|
|
436
|
+
local _IeCbs = {} -- InputEnded
|
|
437
|
+
|
|
438
|
+
-- Mock RBXScriptConnection: removing from the list on :Disconnect().
|
|
439
|
+
local function _mockConn(id, list)
|
|
440
|
+
return { Disconnect = function() list[id] = nil end }
|
|
441
|
+
end
|
|
419
442
|
|
|
420
|
-
local function
|
|
443
|
+
local function _runFrameCbs(list, a, b)
|
|
444
|
+
for _, e in pairs(list) do
|
|
445
|
+
if e and e.comp then
|
|
446
|
+
e.comp._rawHb = (e.comp._rawHb or 0) + 1
|
|
447
|
+
local pass = not e.when or e.when()
|
|
448
|
+
if pass and e.every then
|
|
449
|
+
local t = type(e.every) == "table" and e.every() or e.every
|
|
450
|
+
local now = tick()
|
|
451
|
+
if (now - e.last) < t then pass = false else e.last = now end
|
|
452
|
+
end
|
|
453
|
+
if pass then
|
|
454
|
+
e.comp._condHb = (e.comp._condHb or 0) + 1
|
|
455
|
+
if Pulse and Pulse.Log then
|
|
456
|
+
Pulse.Log.throttle(e.comp._name, 5, "debug", "tick (condition passed)")
|
|
457
|
+
end
|
|
458
|
+
local ok, err = pcall(e.cb, a, b)
|
|
459
|
+
if not ok then
|
|
460
|
+
warn("[Pulse] "..tostring(e.comp._name).." error: "..tostring(err))
|
|
461
|
+
if Pulse and Pulse.Log then
|
|
462
|
+
Pulse.Log.error(e.comp._name, "event error: "..tostring(err))
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
local function _runEventCbs(list, a, b)
|
|
471
|
+
for _, e in pairs(list) do
|
|
472
|
+
if e then pcall(e.cb, a, b) end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
-- Public runners — called by compiler-generated user-scope connections.
|
|
477
|
+
_PulseRunHeartbeat = function(a,b) _runFrameCbs(_HbCbs, a,b) end
|
|
478
|
+
_PulseRunRenderStepped = function(a,b) _runFrameCbs(_RsCbs, a,b) end
|
|
479
|
+
_PulseRunStepped = function(a,b) _runFrameCbs(_StCbs, a,b) end
|
|
480
|
+
_PulseRunCharAdded = function(c) _runEventCbs(_CaCbs, c) end
|
|
481
|
+
_PulseRunCharRemoving = function(c) _runEventCbs(_CrCbs, c) end
|
|
482
|
+
_PulseRunInputBegan = function(i,g) _runEventCbs(_IbCbs, i,g) end
|
|
483
|
+
_PulseRunInputEnded = function(i,g) _runEventCbs(_IeCbs, i,g) end
|
|
484
|
+
|
|
485
|
+
local function _hbBind(list, optsOrFn, fn)
|
|
421
486
|
local comp = _needComp("event")
|
|
422
487
|
local opts, cb
|
|
423
488
|
if type(optsOrFn) == "function" then cb=optsOrFn; opts={}
|
|
424
489
|
else opts=optsOrFn; cb=fn end
|
|
425
|
-
local
|
|
426
|
-
comp
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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))
|
|
490
|
+
local id = _nextId()
|
|
491
|
+
comp._rawHb = comp._rawHb or 0
|
|
492
|
+
comp._condHb = comp._condHb or 0
|
|
493
|
+
list[id] = { comp=comp, when=opts.when, every=opts.every, last=0, cb=cb }
|
|
494
|
+
comp:bind("ev_"..id, _mockConn(id, list))
|
|
435
495
|
end
|
|
436
496
|
|
|
437
497
|
local _on = {}
|
|
438
|
-
_on.heartbeat = function(a,b) _hbBind(
|
|
439
|
-
_on.renderStepped = function(a,b) _hbBind(
|
|
440
|
-
_on.stepped = function(a,b) _hbBind(
|
|
441
|
-
_on.inputBegan = function(fn)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
_on.
|
|
446
|
-
|
|
447
|
-
|
|
498
|
+
_on.heartbeat = function(a,b) _hbBind(_HbCbs, a,b) end
|
|
499
|
+
_on.renderStepped = function(a,b) _hbBind(_RsCbs, a,b) end
|
|
500
|
+
_on.stepped = function(a,b) _hbBind(_StCbs, a,b) end
|
|
501
|
+
_on.inputBegan = function(fn)
|
|
502
|
+
local c=_needComp("inputBegan"); local id=_nextId()
|
|
503
|
+
_IbCbs[id]={cb=fn}; c:bind("ib_"..id, _mockConn(id,_IbCbs))
|
|
504
|
+
end
|
|
505
|
+
_on.inputEnded = function(fn)
|
|
506
|
+
local c=_needComp("inputEnded"); local id=_nextId()
|
|
507
|
+
_IeCbs[id]={cb=fn}; c:bind("ie_"..id, _mockConn(id,_IeCbs))
|
|
508
|
+
end
|
|
509
|
+
_on.characterAdded = function(fn)
|
|
510
|
+
local c=_needComp("characterAdded"); local id=_nextId()
|
|
511
|
+
_CaCbs[id]={cb=fn}; c:bind("ca_"..id, _mockConn(id,_CaCbs))
|
|
512
|
+
end
|
|
513
|
+
_on.characterRemoving = function(fn)
|
|
514
|
+
local c=_needComp("characterRemoving"); local id=_nextId()
|
|
515
|
+
_CrCbs[id]={cb=fn}; c:bind("cr_"..id, _mockConn(id,_CrCbs))
|
|
516
|
+
end
|
|
517
|
+
_on.respawn = function(fn)
|
|
518
|
+
local c=_needComp("respawn"); local id=_nextId()
|
|
519
|
+
_CaCbs[id]={cb=fn}; c:bind("__respawn_"..c._name, _mockConn(id,_CaCbs))
|
|
520
|
+
end
|
|
521
|
+
_on.signal = function(sig,fn)
|
|
522
|
+
local comp = _needComp("signal")
|
|
523
|
+
comp:watch(sig, function(v)
|
|
524
|
+
if Pulse and Pulse.Log then
|
|
525
|
+
Pulse.Log.debug(comp._name, "signal "..tostring(sig._id or "?").." → "..tostring(v))
|
|
526
|
+
end
|
|
527
|
+
fn(v)
|
|
528
|
+
end)
|
|
529
|
+
end
|
|
530
|
+
_on.after = function(s,fn) _needComp("after"):task(s,fn) end
|
|
448
531
|
|
|
449
532
|
local function _defineComponent(name, setup)
|
|
450
533
|
local comp = Component(name)
|
|
@@ -470,6 +553,7 @@ local function _groupbox(side, title, opts)
|
|
|
470
553
|
return { type="groupbox", side=side, title=title,
|
|
471
554
|
icon=(opts and opts.icon) or "",
|
|
472
555
|
mount=(opts and opts.mount) or nil,
|
|
556
|
+
plain=(opts and opts.plain) or false,
|
|
473
557
|
widgets=(opts and opts.widgets) or {} }
|
|
474
558
|
end
|
|
475
559
|
|
package/templates/globals.lua
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
-- ── Script Config ─────────────────────────────────────────────────────────────
|
|
2
|
-
-- Edit these values. SCRIPT_NAME is used as the UI window title
|
|
2
|
+
-- Edit these values. SCRIPT_NAME is used as the UI window title.
|
|
3
3
|
|
|
4
4
|
local SCRIPT_NAME = "{NAME}"
|
|
5
5
|
local SCRIPT_VERSION = "1.0.0"
|
|
6
6
|
local SCRIPT_AUTHOR = "you"
|
|
7
|
-
local SCRIPT_DISCORD = "" -- optional: discord invite link
|
|
7
|
+
local SCRIPT_DISCORD = "" -- optional: discord invite link
|
|
8
8
|
|
|
9
9
|
-- ── Shared helpers ─────────────────────────────────────────────────────────────
|
|
10
|
-
-- Add
|
|
11
|
-
--
|
|
10
|
+
-- Add game-specific shared helpers here. Pulse runtime helpers (_PulseGet*)
|
|
11
|
+
-- and all TypeScript component globals are injected automatically.
|
|
12
12
|
|
|
13
13
|
local func = {}
|
|
14
14
|
|
|
15
15
|
-- ── Players ────────────────────────────────────────────────────────────────────
|
|
16
|
-
-- Event-driven cache
|
|
16
|
+
-- Event-driven player cache — stays up to date as players join/leave.
|
|
17
17
|
local _playerSet = {}
|
|
18
18
|
local _PS = game:GetService("Players")
|
|
19
19
|
local _LP = _PS.LocalPlayer
|
|
20
20
|
|
|
21
21
|
for _, p in ipairs(_PS:GetPlayers()) do _playerSet[p] = true end
|
|
22
|
-
_PS.PlayerAdded:Connect(function(p)
|
|
23
|
-
|
|
24
|
-
local n = 0; for _ in pairs(_playerSet) do n = n + 1 end
|
|
25
|
-
Pulse.Monitor.set("players", n)
|
|
26
|
-
end)
|
|
27
|
-
_PS.PlayerRemoving:Connect(function(p)
|
|
28
|
-
_playerSet[p] = nil
|
|
29
|
-
local n = 0; for _ in pairs(_playerSet) do n = n + 1 end
|
|
30
|
-
Pulse.Monitor.set("players", n)
|
|
31
|
-
end)
|
|
22
|
+
_PS.PlayerAdded:Connect(function(p) _playerSet[p] = true end)
|
|
23
|
+
_PS.PlayerRemoving:Connect(function(p) _playerSet[p] = nil end)
|
|
32
24
|
|
|
33
25
|
func.GetCachedPlayers = function()
|
|
34
26
|
local t = {}
|
|
@@ -36,24 +28,16 @@ func.GetCachedPlayers = function()
|
|
|
36
28
|
return t
|
|
37
29
|
end
|
|
38
30
|
|
|
39
|
-
-- ── Team monitor ──────────────────────────────────────────────────────────────
|
|
40
|
-
-- Tracks the local player's team in the dev overlay.
|
|
41
|
-
Pulse.Monitor.set("team", _LP.Team and _LP.Team.Name or "none")
|
|
42
|
-
_LP:GetPropertyChangedSignal("Team"):Connect(function()
|
|
43
|
-
Pulse.Monitor.set("team", _LP.Team and _LP.Team.Name or "none")
|
|
44
|
-
end)
|
|
45
|
-
|
|
46
31
|
-- ── Entity folder watcher ──────────────────────────────────────────────────────
|
|
47
|
-
-- Use _watchFolder to keep a set live as
|
|
48
|
-
--
|
|
32
|
+
-- Use _watchFolder to keep a set live as instances spawn/despawn.
|
|
33
|
+
-- Uncomment and adapt to your game's folder structure.
|
|
49
34
|
local function _watchFolder(folder, set)
|
|
50
35
|
for _, child in ipairs(folder:GetChildren()) do set[child] = true end
|
|
51
36
|
folder.ChildAdded:Connect(function(c) set[c] = true end)
|
|
52
37
|
folder.ChildRemoved:Connect(function(c) set[c] = nil end)
|
|
53
38
|
end
|
|
54
39
|
|
|
55
|
-
-- Example
|
|
56
|
-
--
|
|
40
|
+
-- Example:
|
|
57
41
|
-- local _enemySet = {}
|
|
58
42
|
-- task.spawn(function()
|
|
59
43
|
-- local folder = workspace:WaitForChild("Enemies", 30)
|
package/templates/page_home.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// Home page tab — Next.js-style file-based routing.
|
|
2
|
-
// Filename prefix (1_) determines tab order.
|
|
3
|
-
|
|
4
1
|
definePage('Home', { icon: 'house' }, () => [
|
|
5
|
-
groupbox('left', 'Player', { icon: 'person',
|
|
6
|
-
groupbox('left', 'Player', { icon: 'person',
|
|
7
|
-
groupbox('right', 'Visuals', { icon: 'eye',
|
|
2
|
+
groupbox('left', 'Player', { icon: 'person', mount: 'SpeedHack' }),
|
|
3
|
+
groupbox('left', 'Player', { icon: 'person', mount: 'FOVChanger' }),
|
|
4
|
+
groupbox('right', 'Visuals', { icon: 'eye', mount: 'PlayerESP' }),
|
|
8
5
|
])
|