pulse-rb 1.2.24 → 1.3.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/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 pathe = require('pathe');
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.2.24";
59
+ RB_VERSION = "1.3.1";
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/LorekeeperZinnia/IronBrew2.git";
63
+ IRONBREW2_REPO = "https://github.com/Trollicus/ironbrew-2.git";
63
64
  ALWAYS_EXCLUDE = /* @__PURE__ */ new Set(["types.lua"]);
64
- VALID_UI_LIBS = /* @__PURE__ */ new Set(["linoria", "windui"]);
65
+ VALID_UI_LIBS = /* @__PURE__ */ new Set(["windui"]);
65
66
  OBFUSCATION_PRESETS = ["Low", "Medium", "Strong"];
66
- CDN_BASE_URL = "https://cdn.pulse-rb.dev";
67
+ CDN_BASE_URL = "https://pub-2072661efb7e45a1ac7f2aea691b0627.r2.dev";
67
68
  }
68
69
  });
69
70
 
@@ -767,7 +768,7 @@ function parseLayoutOptions(body) {
767
768
  toggle_key: "RightControl",
768
769
  size: [950, 600],
769
770
  compat_exclude: [],
770
- ui_library: "linoria",
771
+ ui_library: "windui",
771
772
  theme: "Dark",
772
773
  folder: "Hub",
773
774
  notify_title: "Hub",
@@ -829,7 +830,7 @@ function parseLayoutOptions(body) {
829
830
  }
830
831
  case "ui_library": {
831
832
  const lib = val.replace(/^["']|["']$/g, "");
832
- if (lib === "linoria" || lib === "windui") opts.ui_library = lib;
833
+ if (lib === "windui") opts.ui_library = lib;
833
834
  break;
834
835
  }
835
836
  case "theme":
@@ -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, PULSE_RUNTIME, PULSE_HELPERS, PULSE_DEV_DIR, PULSE_UI_DIR, ADAPTERS_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, DEFAULTS_RUNNER, Compiler;
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
- PULSE_RUNTIME = path.join(PULSE_DIR, "runtime.lua");
1246
- PULSE_HELPERS = path.join(PULSE_DIR, "helpers");
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
- ADAPTERS_DIR = path.join(__dirname, "..", "adapters");
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,14 +1178,20 @@ 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
- if (!fs.existsSync(layoutFile)) return "linoria";
1188
+ if (!fs.existsSync(layoutFile)) return "windui";
1306
1189
  try {
1307
1190
  const [inner] = findLayoutBlock(fs.readFileSync(layoutFile, "utf8"));
1308
- if (inner) return parseLayoutOptions(inner).ui_library ?? "linoria";
1191
+ if (inner) return parseLayoutOptions(inner).ui_library ?? "windui";
1309
1192
  } catch {
1310
1193
  }
1311
- return "linoria";
1194
+ return "windui";
1312
1195
  }
1313
1196
  resolveUi(ui) {
1314
1197
  if (!ui) ui = this.getUiLibrary();
@@ -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
- path.join(f, "...");
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,15 +1261,14 @@ 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;
1383
- if (stem.startsWith("linoria_") && ui !== "linoria") continue;
1384
1272
  uiFiles.push(f);
1385
1273
  }
1386
1274
  }
@@ -1390,44 +1278,31 @@ end)
1390
1278
  }
1391
1279
  compile(opts = {}) {
1392
1280
  const ui = this.resolveUi(opts.ui);
1393
- const order = this.getLoadOrder({ ...opts, ui });
1394
- const hasRblua = order.some((f) => f.endsWith(".rblua"));
1281
+ const order = this.getLoadOrder({ ...opts, ui, cdn: true });
1282
+ const hasTs = order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"));
1395
1283
  const srcUiDir = path.join(this.srcDir, "ui");
1396
1284
  const pulseDevUi = path.join(PULSE_DEV_DIR, "ui");
1397
- const useCdn = !opts.dev && hasCdnConfig(this.root);
1398
1285
  const parts = [];
1399
1286
  parts.push("-- [re-execution guard]\n");
1400
1287
  parts.push(REEXEC_GUARD);
1401
1288
  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}
1289
+ if (hasTs) {
1290
+ const base = `${CDN_BASE_URL}/v${RB_VERSION}`;
1291
+ parts.push(`-- Pulse v${RB_VERSION}
1410
1292
  `);
1411
- parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
1293
+ parts.push(`local _P=loadstring(game:HttpGet("${base}/bundle.lua"))()
1412
1294
  `);
1413
- parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))()
1295
+ parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
1414
1296
  `);
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}]
1297
+ parts.push(`local _A=loadstring(game:HttpGet("${base}/adapters/${ui}.lua"))(_P)
1424
1298
  `);
1425
- parts.push(fs.readFileSync(path.join(PULSE_HELPERS, helper), "utf8"));
1426
- parts.push("\n");
1427
- }
1428
- }
1429
- }
1430
- }
1299
+ parts.push(`local signal,computed,defineComponent,on=_P.signal,_P.computed,_P.defineComponent,_P.on
1300
+ `);
1301
+ parts.push(`local toggle,slider,dropdown,multidropdown=_P.toggle,_P.slider,_P.dropdown,_P.multidropdown
1302
+ `);
1303
+ parts.push(`local button,keybind,label,separator,groupbox,definePage=_P.button,_P.keybind,_P.label,_P.separator,_P.groupbox,_P.definePage
1304
+ `);
1305
+ parts.push("\n");
1431
1306
  }
1432
1307
  if (opts.dev && fs.existsSync(PULSE_DEV_DIR)) {
1433
1308
  const rbRoot = path.join(__dirname, "..", "..");
@@ -1440,13 +1315,6 @@ end)
1440
1315
  parts.push("\n");
1441
1316
  }
1442
1317
  }
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
1318
  const label = (p) => {
1451
1319
  try {
1452
1320
  return path.relative(this.root, p).replace(/\\/g, "/");
@@ -1490,12 +1358,31 @@ end)
1490
1358
  if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
1491
1359
  throw e;
1492
1360
  }
1361
+ } else if (f.endsWith(".ts")) {
1362
+ const source = fs.readFileSync(f, "utf8");
1363
+ try {
1364
+ const result = typescriptToLua.transpileString(source, {
1365
+ luaTarget: "Lua53",
1366
+ noImplicitSelf: true,
1367
+ noHeader: true,
1368
+ noCheck: true
1369
+ });
1370
+ if (result.diagnostics && result.diagnostics.length) {
1371
+ const diag = result.diagnostics[0];
1372
+ const msg = typeof diag.messageText === "string" ? diag.messageText : diag.messageText.messageText ?? String(diag.messageText);
1373
+ throw new Error(msg);
1374
+ }
1375
+ parts.push(result.file?.lua ?? "");
1376
+ } catch (e) {
1377
+ if (e instanceof TranspileError) throw new Error(`Transpile error in ${lbl}: ${e.message}`);
1378
+ throw new Error(`TypeScript error in ${lbl}: ${e.message}`);
1379
+ }
1493
1380
  } else {
1494
1381
  parts.push(fs.readFileSync(f, "utf8"));
1495
1382
  }
1496
1383
  parts.push("\n");
1497
1384
  }
1498
- if (order.some((f) => f.endsWith(".rblua"))) {
1385
+ if (order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"))) {
1499
1386
  parts.push("-- [generated: defaults runner]\n");
1500
1387
  parts.push(DEFAULTS_RUNNER);
1501
1388
  parts.push("\n");
@@ -1603,8 +1490,12 @@ var init_checker = __esm({
1603
1490
  }
1604
1491
  });
1605
1492
  function ensureIronbrew2() {
1606
- const entry = path.join(IRONBREW2_DIR, "IronBrew2.lua");
1607
- if (fs.existsSync(entry)) return IRONBREW2_DIR;
1493
+ const exeDir = path.join(IRONBREW2_DIR, IB2_EXE_SUBDIR);
1494
+ const exePath = path.join(exeDir, IB2_EXE_NAME);
1495
+ if (fs.existsSync(exePath)) return IRONBREW2_DIR;
1496
+ if (fs.existsSync(IRONBREW2_DIR)) {
1497
+ fs.rmSync(IRONBREW2_DIR, { recursive: true, force: true });
1498
+ }
1608
1499
  pInfo("Downloading Ironbrew2 from GitHub...");
1609
1500
  fs.mkdirSync(path.dirname(IRONBREW2_DIR), { recursive: true });
1610
1501
  const result = child_process.spawnSync("git", ["clone", "--depth=1", IRONBREW2_REPO, IRONBREW2_DIR], {
@@ -1618,30 +1509,29 @@ function ensureIronbrew2() {
1618
1509
  return IRONBREW2_DIR;
1619
1510
  }
1620
1511
  async function obfuscateSource(source, ib2Dir) {
1621
- const { LuaFactory } = await import('wasmoon');
1622
- const factory = new LuaFactory();
1623
- const lua = await factory.createEngine();
1512
+ const exeDir = path.join(ib2Dir, IB2_EXE_SUBDIR);
1513
+ const exePath = path.join(exeDir, IB2_EXE_NAME);
1514
+ const outPath = path.join(exeDir, "out.lua");
1624
1515
  const tmpIn = path.join(os.tmpdir(), `rb_in_${Date.now()}.lua`);
1625
- const tmpOut = path.join(os.tmpdir(), `rb_out_${Date.now()}.lua`);
1626
1516
  try {
1627
1517
  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");
1518
+ const dllPath = path.join(exeDir, "IronBrew2 CLI.dll");
1519
+ const [cmd, args] = fs.existsSync(dllPath) ? ["dotnet", ["--roll-forward", "Major", dllPath, tmpIn]] : [exePath, [tmpIn]];
1520
+ const result = child_process.spawnSync(cmd, args, {
1521
+ cwd: exeDir,
1522
+ stdio: "pipe"
1523
+ });
1524
+ const stdout = result.stdout?.toString().trim() ?? "";
1525
+ const stderr = result.stderr?.toString().trim() ?? "";
1526
+ if (stdout.startsWith("ERR:")) throw new Error(stdout.slice(4).trim());
1527
+ if (result.status !== 0) throw new Error(stderr || stdout || `exit ${result.status}`);
1528
+ if (!fs.existsSync(outPath)) throw new Error("IronBrew2 CLI produced no output file");
1529
+ return fs.readFileSync(outPath, "utf8");
1635
1530
  } finally {
1636
- lua.global.close();
1637
1531
  try {
1638
1532
  __require("fs").unlinkSync(tmpIn);
1639
1533
  } catch {
1640
1534
  }
1641
- try {
1642
- __require("fs").unlinkSync(tmpOut);
1643
- } catch {
1644
- }
1645
1535
  }
1646
1536
  }
1647
1537
  async function obfuscatePipeline(builds, root) {
@@ -1660,7 +1550,8 @@ async function obfuscatePipeline(builds, root) {
1660
1550
  outputs.push(outPath);
1661
1551
  } catch (e) {
1662
1552
  pFail(`Ironbrew2 failed on ${__require("path").basename(source)}`);
1663
- pWarn(String(e?.message ?? e));
1553
+ const { pWarn: pWarn7 } = (init_ui(), __toCommonJS(ui_exports));
1554
+ pWarn7(String(e?.message ?? e));
1664
1555
  failed = true;
1665
1556
  }
1666
1557
  }
@@ -1677,13 +1568,17 @@ function copyToClipboard(content, label = "") {
1677
1568
  }
1678
1569
  } catch {
1679
1570
  }
1680
- pWarn("Could not find clipboard tool (clip.exe / pbcopy / xclip)");
1571
+ const { pWarn: pWarn7 } = (init_ui(), __toCommonJS(ui_exports));
1572
+ pWarn7("Could not find clipboard tool (clip.exe / pbcopy / xclip)");
1681
1573
  }
1574
+ var IB2_EXE_SUBDIR, IB2_EXE_NAME;
1682
1575
  var init_obfuscator = __esm({
1683
1576
  "src/obfuscator.ts"() {
1684
1577
  init_cjs_shims();
1685
1578
  init_ui();
1686
1579
  init_constants();
1580
+ IB2_EXE_SUBDIR = path.join("IronBrew2 CLI", "bin", "Debug", "netcoreapp3.1");
1581
+ IB2_EXE_NAME = "IronBrew2 CLI.exe";
1687
1582
  }
1688
1583
  });
1689
1584
 
@@ -1902,7 +1797,7 @@ function makeClaudeMd(name) {
1902
1797
  function makeAgentsMd(name) {
1903
1798
  return read("AGENTS.md").replace(/{NAME}/g, name);
1904
1799
  }
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;
1800
+ 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
1801
  var init_templates = __esm({
1907
1802
  "src/templates.ts"() {
1908
1803
  init_cjs_shims();
@@ -1910,15 +1805,20 @@ var init_templates = __esm({
1910
1805
  TEMPLATE_GLOBALS = read("globals.lua");
1911
1806
  TEMPLATE_REMOTES = read("remotes.lua");
1912
1807
  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");
1808
+ read("layout.rblua");
1809
+ read("page_home.rblua");
1810
+ read("example_speed.rblua");
1811
+ read("example_fov.rblua");
1812
+ read("example_esp.rblua");
1918
1813
  TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
1919
1814
  MODULE_BOILERPLATE = read("module.lua");
1920
1815
  MODULE_RBLUA_BOILERPLATE = read("module.rblua");
1921
1816
  REMOTE_BOILERPLATE = read("remote.lua");
1817
+ TEMPLATE_LAYOUT_TS = read("layout.ts");
1818
+ TEMPLATE_PAGE_HOME_TS = read("page_home.ts");
1819
+ TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
1820
+ TEMPLATE_EX_FOV_TS = read("component_fov.ts");
1821
+ TEMPLATE_EX_ESP_TS = read("component_esp.ts");
1922
1822
  }
1923
1823
  });
1924
1824
 
@@ -1995,22 +1895,23 @@ async function cmdInit(args) {
1995
1895
  pHeader("init");
1996
1896
  pSection(`Scaffolding ${bold(name + "/")}`);
1997
1897
  for (const dir of [
1998
- "src/misc/helpers",
1999
- "src/player",
1898
+ "src/misc",
1899
+ "src/pages",
1900
+ "src/combat",
2000
1901
  "src/visuals",
2001
- "src/ui/pages",
2002
1902
  "build"
2003
1903
  ]) {
2004
1904
  fs.mkdirSync(pathe.join(dest, dir), { recursive: true });
2005
1905
  }
2006
1906
  const files = [
2007
- ["src/misc/helpers/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
1907
+ ["src/misc/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
2008
1908
  ["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],
1909
+ ["src/layout.ts", TEMPLATE_LAYOUT_TS.replace(/{NAME}/g, name)],
1910
+ ["src/pages/1_Home.ts", TEMPLATE_PAGE_HOME_TS],
1911
+ ["src/combat/SpeedHack.ts", TEMPLATE_EX_SPEED_TS],
1912
+ ["src/combat/FOVChanger.ts", TEMPLATE_EX_FOV_TS],
1913
+ ["src/visuals/PlayerESP.ts", TEMPLATE_EX_ESP_TS],
1914
+ ["tsconfig.json", TSCONFIG],
2014
1915
  [".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
2015
1916
  [".gitignore", TEMPLATE_GITIGNORE],
2016
1917
  ["CLAUDE.md", makeClaudeMd(name)],
@@ -2043,7 +1944,7 @@ async function cmdInit(args) {
2043
1944
  "pulse-rb": `^${RB_VERSION}`,
2044
1945
  "@rb-pulse/core": `^${RB_VERSION}`,
2045
1946
  "@rb-pulse/ui": `^${RB_VERSION}`,
2046
- "@rb-pulse/roblox": `^${RB_VERSION}`
1947
+ "@rbxts/types": "^1.0.0"
2047
1948
  }
2048
1949
  };
2049
1950
  fs.writeFileSync(pathe.join(dest, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -2073,6 +1974,7 @@ async function cmdInit(args) {
2073
1974
  console.log(` ${cyan("\u2192")} cd ${name} && pnpm build`);
2074
1975
  console.log();
2075
1976
  }
1977
+ var TSCONFIG;
2076
1978
  var init_init = __esm({
2077
1979
  "src/commands/init.ts"() {
2078
1980
  init_cjs_shims();
@@ -2080,6 +1982,18 @@ var init_init = __esm({
2080
1982
  init_templates();
2081
1983
  init_docs();
2082
1984
  init_constants();
1985
+ TSCONFIG = JSON.stringify({
1986
+ compilerOptions: {
1987
+ strict: true,
1988
+ noEmit: true,
1989
+ skipLibCheck: true,
1990
+ target: "ESNext",
1991
+ moduleResolution: "bundler",
1992
+ types: ["@rb-pulse/core", "@rbxts/types"]
1993
+ },
1994
+ include: ["src/**/*.ts"],
1995
+ exclude: ["node_modules"]
1996
+ }, null, 2) + "\n";
2083
1997
  }
2084
1998
  });
2085
1999
 
@@ -2172,8 +2086,8 @@ async function cmdRemove(args) {
2172
2086
  console.log();
2173
2087
  return;
2174
2088
  }
2175
- const { rmSync } = await import('fs');
2176
- rmSync(target);
2089
+ const { rmSync: rmSync2 } = await import('fs');
2090
+ rmSync2(target);
2177
2091
  pOk(`Removed ${gray("src/" + relPath)}`);
2178
2092
  console.log();
2179
2093
  }
@@ -2930,6 +2844,136 @@ var init_mcp = __esm({
2930
2844
  }
2931
2845
  });
2932
2846
 
2847
+ // src/commands/publish.ts
2848
+ var publish_exports = {};
2849
+ __export(publish_exports, {
2850
+ cmdPublish: () => cmdPublish,
2851
+ hasCdnConfig: () => hasCdnConfig,
2852
+ readPublishConfig: () => readPublishConfig
2853
+ });
2854
+ function readPublishConfig() {
2855
+ for (const p of CONFIG_CANDIDATES) {
2856
+ if (!fs.existsSync(p)) continue;
2857
+ const config = {};
2858
+ for (const line of fs.readFileSync(p, "utf8").split("\n")) {
2859
+ const trimmed = line.trim();
2860
+ if (!trimmed || trimmed.startsWith("#")) continue;
2861
+ const eq = trimmed.indexOf("=");
2862
+ if (eq < 0) continue;
2863
+ const key = trimmed.slice(0, eq).trim();
2864
+ config[key] = trimmed.slice(eq + 1).trim();
2865
+ }
2866
+ return config;
2867
+ }
2868
+ return {};
2869
+ }
2870
+ function hasCdnConfig(projectRoot) {
2871
+ const candidates = projectRoot ? [pathe.join(projectRoot, ".rb-publish"), ...CONFIG_CANDIDATES] : CONFIG_CANDIDATES;
2872
+ for (const p of candidates) {
2873
+ if (!fs.existsSync(p)) continue;
2874
+ const content = fs.readFileSync(p, "utf8");
2875
+ if (content.includes("R2_ACCOUNT_ID") && content.includes("R2_SECRET_TOKEN") && content.includes("R2_PUBLIC_URL")) return true;
2876
+ }
2877
+ return false;
2878
+ }
2879
+ async function uploadFile(content, remotePath, config) {
2880
+ const url = `https://api.cloudflare.com/client/v4/accounts/${config.R2_ACCOUNT_ID}/r2/buckets/${config.R2_BUCKET}/objects/${remotePath}`;
2881
+ await ofetch.ofetch(url, {
2882
+ method: "PUT",
2883
+ body: content,
2884
+ headers: {
2885
+ Authorization: `Bearer ${config.R2_SECRET_TOKEN}`,
2886
+ "Content-Type": remotePath.endsWith(".lua") ? "text/plain; charset=utf-8" : "application/octet-stream"
2887
+ }
2888
+ });
2889
+ return `${config.R2_PUBLIC_URL}/${remotePath}`;
2890
+ }
2891
+ async function cmdPublish(_args) {
2892
+ pHeader("publish");
2893
+ const config = readPublishConfig();
2894
+ const missing = ["R2_ACCOUNT_ID", "R2_SECRET_TOKEN", "R2_BUCKET", "R2_PUBLIC_URL"].filter((k) => !config[k]);
2895
+ if (missing.length) {
2896
+ pFail("R2 credentials not configured");
2897
+ console.log();
2898
+ pInfo(`Create ${bold(".rb-publish")} in your project root (or ~/.rb-publish):`);
2899
+ console.log();
2900
+ console.log(` ${dim("R2_ACCOUNT_ID")}=your_cloudflare_account_id`);
2901
+ console.log(` ${dim("R2_SECRET_TOKEN")}=cfat_your_api_token`);
2902
+ console.log(` ${dim("R2_BUCKET")}=pulse-runtime`);
2903
+ console.log(` ${dim("R2_PUBLIC_URL")}=https://pub-xxxx.r2.dev`);
2904
+ console.log();
2905
+ pInfo(`Get your token at: ${cyan("dash.cloudflare.com \u2192 R2 \u2192 Manage API Tokens")}`);
2906
+ process.exit(1);
2907
+ }
2908
+ const PULSE_DIR2 = pathe.join(__dirname, "..", "pulse");
2909
+ const ADAPTERS = pathe.join(__dirname, "..", "adapters");
2910
+ const VERSION_PATH = `v${RB_VERSION}`;
2911
+ const runtimeFile = pathe.join(PULSE_DIR2, "runtime.lua");
2912
+ const helpersDir = pathe.join(PULSE_DIR2, "helpers");
2913
+ if (!fs.existsSync(runtimeFile)) {
2914
+ pFail("pulse/runtime.lua not found");
2915
+ process.exit(1);
2916
+ }
2917
+ const bundleParts = [
2918
+ `-- Pulse v${RB_VERSION} bundle (runtime + helpers)`,
2919
+ fs.readFileSync(runtimeFile, "utf8").trimEnd()
2920
+ ];
2921
+ if (fs.existsSync(helpersDir)) {
2922
+ for (const f of fs.readdirSync(helpersDir).filter((n) => n.endsWith(".lua")).sort()) {
2923
+ bundleParts.push(`-- ${f}`);
2924
+ bundleParts.push(fs.readFileSync(pathe.join(helpersDir, f), "utf8").trimEnd());
2925
+ }
2926
+ }
2927
+ 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}"
2929
+ );
2930
+ const bundleContent = Buffer.from(bundleParts.join("\n") + "\n", "utf8");
2931
+ const uploads = [
2932
+ { remote: `${VERSION_PATH}/bundle.lua`, content: bundleContent }
2933
+ ];
2934
+ const pulseUiDir = pathe.join(PULSE_DIR2, "ui");
2935
+ for (const adapter of ["windui"]) {
2936
+ const p = pathe.join(ADAPTERS, `${adapter}.lua`);
2937
+ 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
+ uploads.push({
2945
+ remote: `${VERSION_PATH}/adapters/${adapter}.lua`,
2946
+ content: Buffer.from(parts.join("\n") + "\n", "utf8")
2947
+ });
2948
+ }
2949
+ pSection(`Uploading to R2 ${gray("(v" + RB_VERSION + " \xB7 " + uploads.length + " files)")}`);
2950
+ for (const { remote, content } of uploads) {
2951
+ try {
2952
+ await uploadFile(content, remote, config);
2953
+ pOk(remote, `${content.length.toLocaleString()} bytes`);
2954
+ } catch (e) {
2955
+ pFail(`Failed: ${remote}`, String(e?.data?.message ?? e?.message ?? "").split("\n")[0]);
2956
+ process.exit(1);
2957
+ }
2958
+ }
2959
+ console.log();
2960
+ pOk(`Published ${gray("v" + RB_VERSION)}`);
2961
+ pInfo(`CDN base: ${cyan(config.R2_PUBLIC_URL + "/" + VERSION_PATH)}`);
2962
+ console.log();
2963
+ }
2964
+ var CONFIG_CANDIDATES;
2965
+ var init_publish = __esm({
2966
+ "src/commands/publish.ts"() {
2967
+ init_cjs_shims();
2968
+ init_ui();
2969
+ init_constants();
2970
+ CONFIG_CANDIDATES = [
2971
+ pathe.join(process.cwd(), ".rb-publish"),
2972
+ pathe.join(process.env["USERPROFILE"] ?? process.env["HOME"] ?? "", ".rb-publish")
2973
+ ];
2974
+ }
2975
+ });
2976
+
2933
2977
  // src/commands/install.ts
2934
2978
  var install_exports = {};
2935
2979
  __export(install_exports, {
@@ -3046,7 +3090,7 @@ var main = citty.defineCommand({
3046
3090
  preset: { type: "string", default: "Medium", description: "Obfuscation preset: Low | Medium | Strong" },
3047
3091
  copy: { type: "boolean", default: false, description: "Copy standard build to clipboard after build" },
3048
3092
  dev: { type: "boolean", default: false, description: "Also emit a dev build (script.dev.lua)" },
3049
- ui: { type: "string", description: "Override UI library (linoria | windui)" }
3093
+ ui: { type: "string", description: "Override UI library (windui)" }
3050
3094
  },
3051
3095
  async run({ args }) {
3052
3096
  const { cmdBuild: cmdBuild2 } = await Promise.resolve().then(() => (init_build(), build_exports));