cluaupp 0.1.2 → 0.1.4

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +5 -8
  3. package/docs/README.md +13 -10
  4. package/docs/architecture.md +18 -11
  5. package/docs/cli.md +2 -2
  6. package/docs/config.md +1 -1
  7. package/docs/cpp-advanced.md +10 -6
  8. package/docs/cpp-organization.md +3 -1
  9. package/docs/cpp-safety.md +11 -7
  10. package/docs/cpp-types.md +1 -1
  11. package/docs/examples/_category_.json +5 -0
  12. package/docs/examples/combat.md +239 -0
  13. package/docs/examples/data-boot.md +98 -0
  14. package/docs/examples/hud.md +96 -0
  15. package/docs/examples/index.md +43 -0
  16. package/docs/examples/leaderstats.md +140 -0
  17. package/docs/examples/shop.md +122 -0
  18. package/docs/examples/sword.md +108 -0
  19. package/docs/getting-started.md +12 -6
  20. package/docs/intellisense.md +31 -0
  21. package/docs/intro.md +2 -2
  22. package/docs/libraries/_category_.json +5 -0
  23. package/docs/libraries/dataservice.md +104 -0
  24. package/docs/libraries/index.md +22 -0
  25. package/docs/libraries/janitor.md +65 -0
  26. package/docs/libraries/more.md +79 -0
  27. package/docs/libraries/net.md +35 -0
  28. package/docs/libraries/promise.md +27 -0
  29. package/docs/oop/_category_.json +5 -0
  30. package/docs/oop/file-tags.md +49 -0
  31. package/docs/oop/index.md +22 -0
  32. package/docs/oop/modules.md +86 -0
  33. package/docs/oop/services.md +83 -0
  34. package/docs/print-cout.md +91 -0
  35. package/docs/syntax.md +59 -5
  36. package/editors/vscode/extension.js +146 -0
  37. package/editors/vscode/package.json +25 -0
  38. package/include/cluaupp/libs/janitor.hpp +7 -3
  39. package/include/cluaupp/roblox.hpp +19 -0
  40. package/package.json +66 -65
  41. package/runtime/Janitor/init.luau +4 -34
  42. package/src/architecture.js +108 -48
  43. package/src/cli.js +85 -10
  44. package/src/client/init.client.cpp +5 -0
  45. package/src/compile.js +20 -4
  46. package/src/editor-install.js +218 -0
  47. package/src/emit.js +320 -8
  48. package/src/intellisense.js +1368 -0
  49. package/src/layout.js +129 -0
  50. package/src/lex.js +17 -9
  51. package/src/libs.js +95 -16
  52. package/src/lsp.js +227 -0
  53. package/src/parse.js +187 -6
  54. package/src/preprocess.js +71 -3
  55. package/src/server/leaderstats.server.cpp +28 -0
  56. package/src/shared/config.cpp +5 -0
  57. package/src/shared/config.h +3 -0
  58. package/src/understand.js +64 -6
  59. package/templates/game/.clangd +11 -0
  60. package/templates/game/.vscode/c_cpp_properties.json +6 -2
  61. package/templates/game/.vscode/extensions.json +6 -0
  62. package/templates/game/.vscode/settings.json +16 -2
  63. package/templates/game/compile_flags.txt +2 -0
  64. package/docs/libraries.md +0 -80
@@ -3,6 +3,7 @@
3
3
  const path = require("path");
4
4
  const { emit } = require("./emit");
5
5
  const { collectLibraries, insertRequires, requireCluauppLib } = require("./libs");
6
+ const { organizeDecls, janitorNames, emitSection, joinBlocks, SECTION, ENTRY_FN } = require("./layout");
6
7
  const {
7
8
  VALUE_CLASSES,
8
9
  analyze,
@@ -27,9 +28,9 @@ function cluauppLib(name) {
27
28
 
28
29
  function emitTypes(plan) {
29
30
  const lines = [header(plan).trimEnd(), ""];
30
- lines.push('local ReplicatedStorage = game:GetService("ReplicatedStorage")');
31
- lines.push(`local Occlude = ${cluauppLib("Occlude")}`);
32
- lines.push(`local ArrayIndexer = ${cluauppLib("ArrayIndexer")}`);
31
+ lines.push('const ReplicatedStorage = game:GetService("ReplicatedStorage")');
32
+ lines.push(`const Occlude = ${cluauppLib("Occlude")}`);
33
+ lines.push(`const ArrayIndexer = ${cluauppLib("ArrayIndexer")}`);
33
34
  lines.push("");
34
35
 
35
36
  if (plan.stats.length > 0) {
@@ -60,6 +61,7 @@ function emitTypes(plan) {
60
61
  manifest.push(" Get: (player: Player) -> PlayerCache?,");
61
62
  manifest.push(" Ensure: (player: Player) -> PlayerCache,");
62
63
  manifest.push(" Clear: (player: Player) -> (),");
64
+ manifest.push(" ClearAll: () -> (),");
63
65
  manifest.push(" },");
64
66
  }
65
67
  if (plan.roles.players) {
@@ -100,23 +102,26 @@ function emitTypes(plan) {
100
102
  }
101
103
 
102
104
  function emitPlayersManager(plan) {
103
- return `${header(plan)}
104
- local Players = game:GetService("Players")
105
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
106
- local Janitor = ${cluauppLib("Janitor")}
107
- type Janitor = Janitor.Janitor
105
+ return `${header(plan).trimEnd()}
106
+
107
+ const Players = game:GetService("Players")
108
+ const ReplicatedStorage = game:GetService("ReplicatedStorage")
109
+ const Janitor = ${cluauppLib("Janitor")}
108
110
 
111
+ type Janitor = Janitor.Janitor
109
112
  export type PlayerHandler = (player: Player) -> ()
110
113
 
111
114
  local PlayersManager = {}
112
115
  local lifetime: Janitor? = nil
116
+ local leaveHandler: PlayerHandler? = nil
113
117
 
114
118
  function PlayersManager.Start(onPlayer: PlayerHandler, onLeave: PlayerHandler?)
115
119
  PlayersManager.Stop()
116
120
  local janitor = Janitor.new()
117
121
  lifetime = janitor
122
+ leaveHandler = onLeave
118
123
 
119
- local function accept(player: Player)
124
+ const function accept(player: Player)
120
125
  onPlayer(player)
121
126
  end
122
127
 
@@ -134,6 +139,13 @@ function PlayersManager.Stop()
134
139
  if lifetime == nil then
135
140
  return
136
141
  end
142
+ local onLeave = leaveHandler
143
+ leaveHandler = nil
144
+ if onLeave then
145
+ for _, player in Players:GetPlayers() do
146
+ onLeave(player)
147
+ end
148
+ end
137
149
  lifetime:Cleanup()
138
150
  lifetime = nil
139
151
  end
@@ -146,11 +158,11 @@ function emitCacheController(plan) {
146
158
  const types = plan.typesName;
147
159
  const folder = plan.folderName || "leaderstats";
148
160
  const lines = [header(plan).trimEnd(), ""];
149
- lines.push(`local Types = require(script.Parent.${types})`);
161
+ lines.push(`const Types = require(script.Parent.${types})`);
150
162
  lines.push("");
151
- lines.push(`local FOLDER_NAME = "${folder}"`);
152
- lines.push("local cache: { [Player]: Types.PlayerCache } = {}");
163
+ lines.push(`const FOLDER_NAME = "${folder}"`);
153
164
  lines.push("");
165
+ lines.push("local cache: { [Player]: Types.PlayerCache } = {}");
154
166
  lines.push("local CacheController = {}");
155
167
  lines.push("");
156
168
  lines.push("function CacheController.Get(player: Player): Types.PlayerCache?");
@@ -201,6 +213,10 @@ function emitCacheController(plan) {
201
213
  lines.push(" cache[player] = nil");
202
214
  lines.push("end");
203
215
  lines.push("");
216
+ lines.push("function CacheController.ClearAll()");
217
+ lines.push(" table.clear(cache)");
218
+ lines.push("end");
219
+ lines.push("");
204
220
  lines.push("return CacheController");
205
221
  lines.push("");
206
222
  return lines.join("\n");
@@ -209,13 +225,13 @@ function emitCacheController(plan) {
209
225
  function emitMain(plan) {
210
226
  const lines = [header(plan).trimEnd(), ""];
211
227
  if (plan.roles.players) {
212
- lines.push("local PlayersManager = require(script.Parent.PlayersManager)");
228
+ lines.push("const PlayersManager = require(script.Parent.PlayersManager)");
213
229
  }
214
230
  if (plan.roles.cache) {
215
- lines.push("local CacheController = require(script.Parent.CacheController)");
231
+ lines.push("const CacheController = require(script.Parent.CacheController)");
216
232
  }
217
233
  if (plan.roles.domain) {
218
- lines.push(`local ${plan.roles.domain} = require(script.Parent.${plan.roles.domain})`);
234
+ lines.push(`const ${plan.roles.domain} = require(script.Parent.${plan.roles.domain})`);
219
235
  }
220
236
  lines.push("");
221
237
  lines.push("local Main = {}");
@@ -242,9 +258,15 @@ function emitMain(plan) {
242
258
  if (plan.roles.domain) {
243
259
  lines.push(` ${plan.roles.domain}.Stop()`);
244
260
  }
261
+ if (plan.roles.cache) {
262
+ lines.push(" CacheController.ClearAll()");
263
+ }
245
264
  if (plan.roles.players) {
246
265
  lines.push(" PlayersManager.Stop()");
247
266
  }
267
+ if (!plan.roles.domain && !plan.roles.cache && !plan.roles.players) {
268
+ lines.push(" return");
269
+ }
248
270
  lines.push("end");
249
271
  lines.push("");
250
272
  lines.push("return Main");
@@ -258,6 +280,10 @@ function emitBootstrap(plan) {
258
280
  }
259
281
 
260
282
  function emitConfig(plan, ast, options) {
283
+ const owned = (ast.body || []).some((decl) => decl.owner);
284
+ if (owned) {
285
+ return `${header(plan).trimEnd()}\n\n${emit(ast, { ...options, skipHeader: true, skipInit: true }).trimEnd()}\n`;
286
+ }
261
287
  const lines = [];
262
288
  if (plan.onlyConsts) {
263
289
  lines.push(header(plan).trimEnd());
@@ -282,48 +308,54 @@ function emitConfig(plan, ast, options) {
282
308
  }
283
309
  if ((ast.body || []).some((decl) => decl.type === "function" && decl.name === "init")) {
284
310
  lines.push(" Start = init,");
311
+ lines.push(" Init = init,");
312
+ lines.push(" init = init,");
285
313
  }
286
314
  lines.push("}");
287
315
  lines.push("");
288
316
  return lines.join("\n");
289
317
  }
290
318
 
319
+ function emitDecls(decls, options) {
320
+ if (!decls || decls.length === 0) {
321
+ return "";
322
+ }
323
+ return emit({ type: "program", body: decls }, { ...options, skipHeader: true, skipInit: true }).trimEnd();
324
+ }
325
+
291
326
  function emitDomainController(plan, ast, options) {
292
- const absorbCache = plan.roles.cache;
293
327
  const keep = (ast.body || []).filter((decl) => {
294
- if (decl.type === "decl" || decl.type === "proto") {
295
- return true;
296
- }
297
- if (decl.type !== "function") {
298
- return false;
299
- }
300
- if (decl.name === "init") {
301
- return true;
302
- }
303
- const classified = plan.functions.find((item) => item.name === decl.name);
304
- if (absorbCache && classified && classified.absorb === "cache") {
305
- return false;
306
- }
307
- return true;
328
+ return decl.type === "decl" || decl.type === "proto" || decl.type === "function" || decl.type === "expr";
308
329
  });
330
+ const groups = organizeDecls(keep);
309
331
  const subset = { type: "program", body: keep };
310
- const code = emit(subset, { ...options, skipHeader: true, skipInit: true }).trimEnd();
311
332
  const name = plan.roles.domain;
312
333
  const libs = collectLibraries(options.source || "", subset);
313
- const body = `${header(plan)}${code}
314
-
315
- local ${name} = {}
316
-
317
- function ${name}.Start()
318
- ${keep.some((decl) => decl.type === "function" && decl.name === "init") ? "init()" : "return"}
319
- end
320
-
321
- function ${name}.Stop()
322
- return
323
- end
324
-
325
- return ${name}
326
- `;
334
+ const entry = keep.find((decl) => decl.type === "function" && ENTRY_FN.test(decl.name || ""));
335
+ const bootExprs = keep.filter((decl) => decl.type === "expr");
336
+ const janitors = janitorNames(keep);
337
+ const closer = keep.find((decl) => decl.type === "function" && /^(OnClose|Cleanup|Shutdown)$/i.test(decl.name));
338
+ const stopLines = [];
339
+ if (janitors.length > 0) {
340
+ for (const janitor of janitors) {
341
+ stopLines.push(` ${janitor}:Cleanup()`);
342
+ }
343
+ } else if (closer) {
344
+ stopLines.push(` ${closer.name}()`);
345
+ } else {
346
+ stopLines.push(" return");
347
+ }
348
+ const boot = entry
349
+ ? ` ${entry.name}()`
350
+ : bootExprs.length
351
+ ? emitDecls(bootExprs, options)
352
+ .split("\n")
353
+ .map((line) => (line ? `\t${line}` : line))
354
+ .join("\n")
355
+ : " return";
356
+ const startFn = `function ${name}.Start()\n ${name}.Stop()\n${boot}\nend`;
357
+ const stopFn = `function ${name}.Stop()\n${stopLines.join("\n")}\nend`;
358
+ const body = `${header(plan).trimEnd()}\n\n${emitSection(SECTION.api, emitDecls(groups.api, options))}${emitSection(SECTION.constants, emitDecls(groups.constants, options))}${emitSection(SECTION.variables, joinBlocks([emitDecls(groups.variables, options), `local ${name} = {}`]))}${emitSection(SECTION.support, emitDecls(groups.support, options))}${emitSection(SECTION.principal, joinBlocks([emitDecls(groups.principal, options), startFn]))}${emitSection(SECTION.cleanup, joinBlocks([emitDecls(groups.cleanup, options), stopFn]))}${emitSection(SECTION.returns, `return ${name}`)}`;
327
359
  return insertRequires(body, libs);
328
360
  }
329
361
 
@@ -331,10 +363,34 @@ function emitModule(plan, ast, options) {
331
363
  return emitConfig(plan, ast, options);
332
364
  }
333
365
 
366
+ function emitScriptMeta(runContext) {
367
+ return `${JSON.stringify(
368
+ {
369
+ className: "Script",
370
+ properties: {
371
+ RunContext: `Enum.RunContext.${runContext}`,
372
+ },
373
+ },
374
+ null,
375
+ "\t",
376
+ )}\n`;
377
+ }
378
+
379
+ function serviceBootName(plan) {
380
+ if (plan.isClient) {
381
+ return "init.client.luau";
382
+ }
383
+ return "init.luau";
384
+ }
385
+
334
386
  function serviceFiles(plan, ast, options, dir) {
335
387
  const folder = `${dir}/${plan.serviceName}`;
336
- const boot = plan.isClient ? "init.client.luau" : "init.server.luau";
337
- const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(plan) }, { name: `${folder}/Main.luau`, contents: emitMain(plan) }];
388
+ const boot = serviceBootName(plan);
389
+ const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(plan) }];
390
+ if (!plan.isClient && plan.runContext) {
391
+ files.push({ name: `${folder}/init.meta.json`, contents: emitScriptMeta(plan.runContext) });
392
+ }
393
+ files.push({ name: `${folder}/Main.luau`, contents: emitMain(plan) });
338
394
  if (plan.roles.players) {
339
395
  files.push({ name: `${folder}/PlayersManager.luau`, contents: emitPlayersManager(plan) });
340
396
  }
@@ -365,11 +421,15 @@ function planOutput(ast, fileName, options = {}) {
365
421
 
366
422
  if (plan.kind === "service") {
367
423
  const serviceDir = outDir || (plan.isClient ? "client" : "server");
424
+ const folder = `${serviceDir}/${plan.serviceName}`;
425
+ const stale = plan.isClient
426
+ ? [`${folder}/init.luau`, `${folder}/init.server.luau`, `${folder}/init.meta.json`]
427
+ : [`${folder}/init.server.luau`, `${folder}/init.client.luau`];
368
428
  return {
369
429
  kind: "service",
370
430
  plan,
371
431
  files: serviceFiles(plan, ast, options, serviceDir),
372
- stale: [rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, ".luau")],
432
+ stale,
373
433
  };
374
434
  }
375
435
 
package/src/cli.js CHANGED
@@ -3,7 +3,8 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
  const { compileService } = require("./compile");
6
- const { isSourceFile, isHeaderFile, toLuauPath } = require("./preprocess");
6
+ const { isSourceFile, isHeaderFile, toLuauPath, siblingImplementation } = require("./preprocess");
7
+ const { syncEditorSupport, installEditorSupport } = require("./intellisense");
7
8
  const pkg = require("../package.json");
8
9
 
9
10
  function printHelp() {
@@ -13,6 +14,8 @@ Usage:
13
14
  cluaupp init [folder] create a game (src/server, src/client, src/shared)
14
15
  cluaupp build [folder] transpile src → out (PascalCase services)
15
16
  cluaupp watch [folder] rebuild on save
17
+ cluaupp lsp [folder] language server (stdio JSON-RPC)
18
+ cluaupp intellisense install Cursor/VS Code IntelliSense
16
19
  cluaupp --version print version
17
20
  cluaupp --help this help
18
21
 
@@ -144,7 +147,7 @@ function collectFiles(dir, files = []) {
144
147
  }
145
148
 
146
149
  function siblingCpp(file) {
147
- return file.replace(/\.(h|hpp|hh)$/i, ".cpp");
150
+ return siblingImplementation(file) || file.replace(/\.(h|hpp|hh)$/i, ".cpp");
148
151
  }
149
152
 
150
153
  function resolveKey(file) {
@@ -238,6 +241,7 @@ function compileProject(root, config) {
238
241
  outName: toLuauPath(rel),
239
242
  architecture: config.architecture !== false,
240
243
  includeDirs: [path.dirname(file), srcDir, path.join(root, "include")],
244
+ srcDir,
241
245
  });
242
246
  jobs.push({ rel, files: result.files, stale: result.stale || [] });
243
247
  } catch (err) {
@@ -293,12 +297,8 @@ function ensureVendor(root) {
293
297
  function build(root, options = {}) {
294
298
  const exitOnError = options.exitOnError !== false;
295
299
  const holdOnError = options.holdOnError === true;
296
- const syncVendor = options.syncVendor === true;
297
300
  const config = loadConfig(root);
298
- if (syncVendor) {
299
- copyRuntime(root);
300
- copyHeaders(root);
301
- } else {
301
+ if (options.syncVendor !== false) {
302
302
  ensureVendor(root);
303
303
  }
304
304
  const srcDir = path.join(root, config.rootDir);
@@ -320,6 +320,11 @@ function build(root, options = {}) {
320
320
  }
321
321
  if (holdOnError) {
322
322
  console.error(`cluaupp: out not updated (${compiled.errors.length} compile error${compiled.errors.length === 1 ? "" : "s"})`);
323
+ try {
324
+ syncEditorSupport(root, config);
325
+ } catch (err) {
326
+ console.error("cluaupp: intellisense sync failed", err.message);
327
+ }
323
328
  return { failed: compiled.errors.length, written: new Set() };
324
329
  }
325
330
  }
@@ -327,24 +332,74 @@ function build(root, options = {}) {
327
332
  const written = writeJobs(root, config, compiled.jobs);
328
333
  const failedPrefixes = compiled.errors.flatMap((err) => err.prefixes);
329
334
  pruneOut(root, config, written, failedPrefixes);
335
+ try {
336
+ syncEditorSupport(root, config);
337
+ } catch (err) {
338
+ console.error("cluaupp: intellisense sync failed", err.message);
339
+ }
330
340
  if (compiled.errors.length > 0 && exitOnError) {
331
341
  process.exit(1);
332
342
  }
333
343
  return { failed: compiled.errors.length, written };
334
344
  }
335
345
 
336
- function init(dest) {
346
+ async function init(dest) {
337
347
  const template = path.join(__dirname, "..", "templates", "game");
338
348
  const include = path.join(__dirname, "..", "include");
339
349
  copyDir(template, dest);
340
350
  copyDir(include, path.join(dest, "include"));
341
351
  copyRuntime(dest);
352
+ syncEditorSupport(dest);
353
+ const installed = await installEditorSupport();
342
354
  console.log("Cluaupp ready in", dest);
343
355
  console.log(" cluaupp build");
344
356
  console.log(" rojo serve");
357
+ if (installed.local.length || (installed.cpp && installed.cpp.status === "installed")) {
358
+ console.log(" reload Cursor (Ctrl+Shift+P → Developer: Reload Window)");
359
+ }
360
+ }
361
+
362
+ function pidAlive(pid) {
363
+ try {
364
+ process.kill(pid, 0);
365
+ return true;
366
+ } catch {
367
+ return false;
368
+ }
369
+ }
370
+
371
+ function acquireWatchLock(root) {
372
+ const file = path.join(root, ".cluaupp-watch.lock");
373
+ if (fs.existsSync(file)) {
374
+ const pid = Number(fs.readFileSync(file, "utf8").trim());
375
+ if (Number.isFinite(pid) && pid !== process.pid && pidAlive(pid)) {
376
+ console.error(`cluaupp: watch already running (pid ${pid}). Stop it first — two watchers make Rojo crash on libs/.`);
377
+ process.exit(1);
378
+ }
379
+ }
380
+ fs.writeFileSync(file, String(process.pid), "utf8");
381
+ const release = () => {
382
+ try {
383
+ if (fs.existsSync(file) && fs.readFileSync(file, "utf8").trim() === String(process.pid)) {
384
+ fs.unlinkSync(file);
385
+ }
386
+ } catch {
387
+ // ignore
388
+ }
389
+ };
390
+ process.on("exit", release);
391
+ process.on("SIGINT", () => {
392
+ release();
393
+ process.exit(0);
394
+ });
395
+ process.on("SIGTERM", () => {
396
+ release();
397
+ process.exit(0);
398
+ });
345
399
  }
346
400
 
347
401
  function watch(root) {
402
+ acquireWatchLock(root);
348
403
  build(root, { exitOnError: false, syncVendor: false, holdOnError: true });
349
404
  const config = loadConfig(root);
350
405
  const dir = path.join(root, config.rootDir);
@@ -371,7 +426,7 @@ function watch(root) {
371
426
  }
372
427
  };
373
428
 
374
- console.log("watching", dir);
429
+ console.log("cluaupp", pkg.version, "watching", dir, "(libs untouched)");
375
430
  if (!fs.existsSync(dir)) {
376
431
  fs.mkdirSync(dir, { recursive: true });
377
432
  }
@@ -385,11 +440,31 @@ function dispatch(args) {
385
440
  const cmd = args[0] || "help";
386
441
  const cwd = process.cwd();
387
442
  if (cmd === "init") {
388
- init(path.resolve(cwd, args[1] || "."));
443
+ return init(path.resolve(cwd, args[1] || ".")).catch((err) => {
444
+ console.error(err.message || err);
445
+ process.exit(1);
446
+ });
389
447
  } else if (cmd === "build") {
390
448
  build(path.resolve(cwd, args[1] || "."));
391
449
  } else if (cmd === "watch") {
392
450
  watch(path.resolve(cwd, args[1] || "."));
451
+ } else if (cmd === "lsp") {
452
+ const { start } = require("./lsp");
453
+ start({ projectRoot: path.resolve(cwd, args[1] || ".") });
454
+ } else if (cmd === "intellisense" || cmd === "intelisense") {
455
+ const root = path.resolve(cwd, args[1] || ".");
456
+ syncEditorSupport(root, loadConfig(root));
457
+ return installEditorSupport().then((installed) => {
458
+ console.log("cluaupp: compile_commands.json, .clangd, and .vscode updated in", root);
459
+ if (installed.local.length) {
460
+ for (const dest of installed.local) {
461
+ console.log("cluaupp: Cluaupp IntelliSense", dest);
462
+ }
463
+ }
464
+ }).catch((err) => {
465
+ console.error(err.message || err);
466
+ process.exit(1);
467
+ });
393
468
  } else if (cmd === "--version" || cmd === "-v" || cmd === "version") {
394
469
  console.log(pkg.version);
395
470
  } else {
@@ -0,0 +1,5 @@
1
+ #include <cluaupp/roblox.hpp>
2
+
3
+ void init() {
4
+ print("Cluaupp client ok");
5
+ }
package/src/compile.js CHANGED
@@ -3,17 +3,27 @@
3
3
  const { parse } = require("./parse");
4
4
  const { emit } = require("./emit");
5
5
  const { preprocess } = require("./preprocess");
6
- const { collectLibraries, insertRequires } = require("./libs");
6
+ const { collectLibraries, insertRequires, insertModuleRequires } = require("./libs");
7
7
  const { planOutput } = require("./architecture");
8
8
 
9
+ function attachRequires(contents, source, ast, options, outName) {
10
+ let next = contents;
11
+ if (!next.includes("CluauppLibs")) {
12
+ next = insertRequires(next, collectLibraries(source, ast));
13
+ }
14
+ return insertModuleRequires(next, options.moduleIncludes || [], outName);
15
+ }
16
+
9
17
  function compileSource(source, fileName, options = {}) {
18
+ options.moduleIncludes = options.moduleIncludes || [];
10
19
  const prepared = options.filePath ? preprocess(source, options.filePath, options) : source;
11
20
  const ast = parse(prepared, fileName || "input.cpp");
12
21
  const luau = emit(ast, options);
13
- return insertRequires(luau, collectLibraries(source, ast));
22
+ return attachRequires(luau, source, ast, options, options.outName || fileName);
14
23
  }
15
24
 
16
25
  function compileService(source, fileName, options = {}) {
26
+ options.moduleIncludes = options.moduleIncludes || [];
17
27
  const prepared = options.filePath ? preprocess(source, options.filePath, options) : source;
18
28
  const ast = parse(prepared, fileName || "input.cpp");
19
29
  const planned = planOutput(ast, fileName || "input.cpp", {
@@ -22,9 +32,15 @@ function compileService(source, fileName, options = {}) {
22
32
  source: prepared,
23
33
  });
24
34
  if (planned.files) {
25
- return planned;
35
+ return {
36
+ ...planned,
37
+ files: planned.files.map((file) => ({
38
+ ...file,
39
+ contents: attachRequires(file.contents, source, ast, options, file.name),
40
+ })),
41
+ };
26
42
  }
27
- const luau = insertRequires(emit(ast, options), collectLibraries(source, ast));
43
+ const luau = attachRequires(emit(ast, options), source, ast, options, planned.outName || options.outName || fileName);
28
44
  const name =
29
45
  planned.outName ||
30
46
  options.outName ||