cluaupp 0.1.4 → 0.2.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +12 -7
  3. package/bin/cluau.js +1 -1
  4. package/bin/cluaupp.js +1 -1
  5. package/docs/README.md +1 -1
  6. package/docs/architecture.md +52 -77
  7. package/docs/cli.md +29 -9
  8. package/docs/comparison.md +3 -3
  9. package/docs/config.md +6 -6
  10. package/docs/cpp-organization.md +2 -2
  11. package/docs/examples/index.md +2 -0
  12. package/docs/getting-started.md +7 -5
  13. package/docs/intellisense.md +49 -31
  14. package/docs/intro.md +2 -2
  15. package/docs/oop/file-tags.md +16 -17
  16. package/docs/oop/index.md +2 -2
  17. package/docs/oop/modules.md +3 -1
  18. package/docs/oop/services.md +7 -14
  19. package/docs/syntax.md +6 -5
  20. package/editors/vscode/extension.js +97 -146
  21. package/editors/vscode/package.json +6 -6
  22. package/examples/README.md +18 -0
  23. package/examples/game/.clangd +25 -0
  24. package/examples/game/.vscode/c_cpp_properties.json +27 -0
  25. package/examples/game/.vscode/extensions.json +5 -0
  26. package/examples/game/.vscode/settings.json +27 -0
  27. package/examples/game/Packages/.gitkeep +0 -0
  28. package/examples/game/cluaupp.config.json +6 -0
  29. package/examples/game/compile_flags.txt +6 -0
  30. package/examples/game/default.project.json +39 -0
  31. package/examples/game/package.json +9 -0
  32. package/examples/game/rokit.toml +5 -0
  33. package/examples/game/src/client/hud.client.cpp +12 -0
  34. package/{src → examples/game/src}/client/init.client.cpp +1 -0
  35. package/examples/game/src/server/combat.server.cpp +23 -0
  36. package/{src → examples/game/src}/server/leaderstats.server.cpp +1 -0
  37. package/examples/game/wally.toml +11 -0
  38. package/generated/api.js +51 -0
  39. package/generated/architecture.js +473 -0
  40. package/generated/ast.js +2 -0
  41. package/generated/cli.js +191 -0
  42. package/generated/compile.js +115 -0
  43. package/generated/editor-install.js +170 -0
  44. package/generated/emit.js +503 -0
  45. package/generated/emitter/luau-codegen.js +167 -0
  46. package/generated/emitter/translators.js +164 -0
  47. package/generated/headers.js +220 -0
  48. package/generated/index.html +49 -0
  49. package/generated/intellisense.js +277 -0
  50. package/generated/layout.js +112 -0
  51. package/generated/lex.js +146 -0
  52. package/generated/libs.js +484 -0
  53. package/generated/lsp.js +55 -0
  54. package/generated/package-info.js +11 -0
  55. package/generated/parse.js +607 -0
  56. package/generated/parser/collector.js +100 -0
  57. package/generated/parser/index.js +30 -0
  58. package/generated/preprocess.js +181 -0
  59. package/generated/system-understander.js +443 -0
  60. package/generated/transpile.js +66 -0
  61. package/generated/types.js +2 -0
  62. package/generated/understand.js +298 -0
  63. package/generated/utils/process-orchestrator.js +63 -0
  64. package/generated/utils/project.js +491 -0
  65. package/generated/utils/rojo-mapper.js +181 -0
  66. package/generated/utils/safe-paths.js +104 -0
  67. package/package.json +25 -8
  68. package/src/api.ts +53 -0
  69. package/src/{architecture.js → architecture.ts} +80 -33
  70. package/src/ast.ts +37 -0
  71. package/src/cli.ts +214 -0
  72. package/src/compile.ts +118 -0
  73. package/src/editor-install.ts +182 -0
  74. package/src/{emit.js → emit.ts} +5 -6
  75. package/src/emitter/luau-codegen.ts +187 -0
  76. package/src/emitter/translators.ts +168 -0
  77. package/src/headers.ts +233 -0
  78. package/src/intellisense.ts +286 -0
  79. package/src/{layout.js → layout.ts} +114 -129
  80. package/src/{lex.js → lex.ts} +4 -6
  81. package/src/{libs.js → libs.ts} +119 -46
  82. package/src/lsp.ts +60 -0
  83. package/src/package-info.ts +7 -0
  84. package/src/{parse.js → parse.ts} +3 -4
  85. package/src/parser/collector.ts +115 -0
  86. package/src/parser/index.ts +27 -0
  87. package/src/{preprocess.js → preprocess.ts} +64 -32
  88. package/src/system-understander.ts +501 -0
  89. package/src/transpile.ts +64 -0
  90. package/src/tree-sitter-cpp.d.ts +4 -0
  91. package/src/types.ts +81 -0
  92. package/src/{understand.js → understand.ts} +24 -213
  93. package/src/utils/process-orchestrator.ts +72 -0
  94. package/src/utils/project.ts +506 -0
  95. package/src/utils/rojo-mapper.ts +190 -0
  96. package/src/utils/safe-paths.ts +98 -0
  97. package/templates/game/.clangd +9 -0
  98. package/templates/game/.vscode/c_cpp_properties.json +1 -1
  99. package/templates/game/.vscode/extensions.json +5 -6
  100. package/templates/game/.vscode/settings.json +7 -6
  101. package/templates/game/cluaupp.config.json +2 -2
  102. package/templates/game/compile_flags.txt +1 -0
  103. package/templates/game/rokit.toml +5 -0
  104. package/templates/game/src/client/init.client.cpp +1 -0
  105. package/templates/game/src/server/leaderstats.server.cpp +1 -0
  106. package/src/api.js +0 -57
  107. package/src/cli.js +0 -481
  108. package/src/compile.js +0 -56
  109. package/src/editor-install.js +0 -218
  110. package/src/intellisense.js +0 -1368
  111. package/src/lsp.js +0 -227
  112. /package/{src → examples/game/src}/shared/config.cpp +0 -0
  113. /package/{src → examples/game/src}/shared/config.h +0 -0
@@ -0,0 +1,491 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ exports.collectCpp = collectCpp;
8
+ exports.build = build;
9
+ exports.init = init;
10
+ exports.watch = watch;
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const package_info_js_1 = require("../package-info.js");
14
+ const preprocess_js_1 = require("../preprocess.js");
15
+ const intellisense_js_1 = require("../intellisense.js");
16
+ const process_orchestrator_js_1 = require("./process-orchestrator.js");
17
+ const rojo_mapper_js_1 = require("./rojo-mapper.js");
18
+ const transpile_js_1 = require("../transpile.js");
19
+ const safe_paths_js_1 = require("./safe-paths.js");
20
+ function readProjectConfig(file) {
21
+ const raw = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
22
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
23
+ return {};
24
+ }
25
+ const data = raw;
26
+ const parsed = {};
27
+ if (typeof data.rootDir === "string") {
28
+ parsed.rootDir = data.rootDir;
29
+ }
30
+ if (typeof data.outDir === "string") {
31
+ parsed.outDir = data.outDir;
32
+ }
33
+ if (typeof data.strict === "boolean") {
34
+ parsed.strict = data.strict;
35
+ }
36
+ if (typeof data.architecture === "boolean") {
37
+ parsed.architecture = data.architecture;
38
+ }
39
+ return parsed;
40
+ }
41
+ function loadConfig(root) {
42
+ const defaults = { rootDir: "src", outDir: "out", strict: false, architecture: false };
43
+ const configNames = ["cluaupp.config.json", "cluau.config.json"];
44
+ let parsed = {};
45
+ for (const name of configNames) {
46
+ const file = node_path_1.default.join(root, name);
47
+ if (node_fs_1.default.existsSync(file)) {
48
+ parsed = readProjectConfig(file);
49
+ break;
50
+ }
51
+ }
52
+ const config = {
53
+ ...defaults,
54
+ ...parsed,
55
+ rootDir: (0, safe_paths_js_1.safeProjectSubdir)(parsed.rootDir || defaults.rootDir, defaults.rootDir, "rootDir"),
56
+ outDir: (0, safe_paths_js_1.safeProjectSubdir)(parsed.outDir || defaults.outDir, defaults.outDir, "outDir"),
57
+ };
58
+ if (config.rootDir === config.outDir) {
59
+ throw new Error("cluaupp: rootDir and outDir must be different");
60
+ }
61
+ const absRoot = node_path_1.default.resolve(root);
62
+ (0, safe_paths_js_1.assertInside)(absRoot, node_path_1.default.join(absRoot, config.rootDir), "rootDir");
63
+ (0, safe_paths_js_1.assertInside)(absRoot, node_path_1.default.join(absRoot, config.outDir), "outDir");
64
+ return config;
65
+ }
66
+ function copyDir(from, to) {
67
+ node_fs_1.default.mkdirSync(to, { recursive: true });
68
+ for (const entry of node_fs_1.default.readdirSync(from, { withFileTypes: true })) {
69
+ const src = node_path_1.default.join(from, entry.name);
70
+ const dest = node_path_1.default.join(to, entry.name);
71
+ if (entry.isDirectory()) {
72
+ copyDir(src, dest);
73
+ }
74
+ else {
75
+ node_fs_1.default.copyFileSync(src, dest);
76
+ }
77
+ }
78
+ }
79
+ function tryRm(target, root) {
80
+ if (root && !(0, safe_paths_js_1.isInside)(root, target)) {
81
+ console.error("cluaupp: skip remove outside project", target);
82
+ return false;
83
+ }
84
+ try {
85
+ node_fs_1.default.rmSync(target, { recursive: true, force: true });
86
+ return true;
87
+ }
88
+ catch (err) {
89
+ const error = err;
90
+ if (error.code === "EPERM" || error.code === "EBUSY" || error.code === "ENOTEMPTY") {
91
+ console.error("cluaupp: skip remove", target, `(${error.code})`);
92
+ return false;
93
+ }
94
+ throw err;
95
+ }
96
+ }
97
+ function copyFileIfChanged(src, dest, mode = "fill") {
98
+ if (mode === "fill" && node_fs_1.default.existsSync(dest)) {
99
+ return;
100
+ }
101
+ if (mode === "update" && node_fs_1.default.existsSync(dest)) {
102
+ const from = node_fs_1.default.statSync(src);
103
+ const to = node_fs_1.default.statSync(dest);
104
+ if (from.size === to.size && from.mtimeMs <= to.mtimeMs) {
105
+ return;
106
+ }
107
+ }
108
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(dest), { recursive: true });
109
+ try {
110
+ node_fs_1.default.copyFileSync(src, dest);
111
+ }
112
+ catch (err) {
113
+ const error = err;
114
+ if (error.code === "EPERM" || error.code === "EBUSY") {
115
+ console.error("cluaupp: skip copy", dest, `(${error.code})`);
116
+ return;
117
+ }
118
+ throw err;
119
+ }
120
+ }
121
+ function syncDir(from, to, mode = "fill") {
122
+ if (!node_fs_1.default.existsSync(from)) {
123
+ return;
124
+ }
125
+ node_fs_1.default.mkdirSync(to, { recursive: true });
126
+ for (const entry of node_fs_1.default.readdirSync(from, { withFileTypes: true })) {
127
+ const src = node_path_1.default.join(from, entry.name);
128
+ const dest = node_path_1.default.join(to, entry.name);
129
+ if (entry.isDirectory()) {
130
+ syncDir(src, dest, mode);
131
+ }
132
+ else {
133
+ copyFileIfChanged(src, dest, mode);
134
+ }
135
+ }
136
+ }
137
+ function copyRuntime(dest) {
138
+ const runtime = node_path_1.default.join(__dirname, "..", "..", "runtime");
139
+ if (!node_fs_1.default.existsSync(runtime)) {
140
+ return;
141
+ }
142
+ syncDir(runtime, node_path_1.default.join(dest, "libs"));
143
+ }
144
+ function copyHeaders(dest) {
145
+ const from = node_path_1.default.join(__dirname, "..", "..", "include", "cluaupp");
146
+ if (!node_fs_1.default.existsSync(from)) {
147
+ return;
148
+ }
149
+ syncDir(from, node_path_1.default.join(dest, "include", "cluaupp"));
150
+ }
151
+ function collectCpp(dir, files = []) {
152
+ if (!node_fs_1.default.existsSync(dir)) {
153
+ return files;
154
+ }
155
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
156
+ const full = node_path_1.default.join(dir, entry.name);
157
+ if (entry.isDirectory()) {
158
+ collectCpp(full, files);
159
+ }
160
+ else if ((0, preprocess_js_1.isSourceFile)(entry.name)) {
161
+ files.push(full);
162
+ }
163
+ }
164
+ return files;
165
+ }
166
+ function collectFiles(dir, files = []) {
167
+ if (!node_fs_1.default.existsSync(dir)) {
168
+ return files;
169
+ }
170
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
171
+ const full = node_path_1.default.join(dir, entry.name);
172
+ if (entry.isDirectory()) {
173
+ collectFiles(full, files);
174
+ }
175
+ else {
176
+ files.push(full);
177
+ }
178
+ }
179
+ return files;
180
+ }
181
+ function resolveKey(file) {
182
+ const resolved = node_path_1.default.resolve(file);
183
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
184
+ }
185
+ function posixRel(from, file) {
186
+ return node_path_1.default.relative(from, file).replace(/\\/g, "/");
187
+ }
188
+ function sourcePrefixes(rel) {
189
+ const noExt = rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, "");
190
+ const noTag = noExt.replace(/\.(server|client)$/i, "");
191
+ return [...new Set([noExt, noTag, (0, preprocess_js_1.toLuauPath)(rel).replace(/\\/g, "/")])];
192
+ }
193
+ function matchesPrefix(relOut, prefixes) {
194
+ const n = relOut.replace(/\\/g, "/").toLowerCase();
195
+ return prefixes.some((prefix) => {
196
+ const key = prefix.replace(/\\/g, "/").toLowerCase();
197
+ return n === key || n === `${key}.luau` || n.startsWith(`${key}/`) || n.startsWith(`${key}.`);
198
+ });
199
+ }
200
+ function removeEmptyOutDirs(outDir, projectRootDir) {
201
+ if (!node_fs_1.default.existsSync(outDir)) {
202
+ return;
203
+ }
204
+ const walk = (dir) => {
205
+ if (resolveKey(dir) === resolveKey(outDir)) {
206
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
207
+ if (entry.isDirectory()) {
208
+ walk(node_path_1.default.join(dir, entry.name));
209
+ }
210
+ }
211
+ return;
212
+ }
213
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
214
+ if (entry.isDirectory()) {
215
+ walk(node_path_1.default.join(dir, entry.name));
216
+ }
217
+ }
218
+ if (node_fs_1.default.existsSync(dir) && node_fs_1.default.readdirSync(dir).length === 0) {
219
+ if (tryRm(dir, projectRootDir)) {
220
+ console.log("cluaupp: removed", node_path_1.default.relative(projectRootDir, dir));
221
+ }
222
+ }
223
+ };
224
+ walk(outDir);
225
+ }
226
+ function pruneOut(root, config, written, failedPrefixes) {
227
+ const outDir = node_path_1.default.resolve(root, config.outDir);
228
+ if (!node_fs_1.default.existsSync(outDir) || !(0, safe_paths_js_1.isInside)(root, outDir)) {
229
+ return;
230
+ }
231
+ for (const file of collectFiles(outDir)) {
232
+ if (written.has(resolveKey(file))) {
233
+ continue;
234
+ }
235
+ if (!(0, safe_paths_js_1.isInside)(outDir, file)) {
236
+ continue;
237
+ }
238
+ const rel = posixRel(outDir, file);
239
+ if (matchesPrefix(rel, failedPrefixes)) {
240
+ continue;
241
+ }
242
+ if (tryRm(file, outDir)) {
243
+ console.log("cluaupp: removed", node_path_1.default.relative(root, file));
244
+ }
245
+ }
246
+ removeEmptyOutDirs(outDir, root);
247
+ }
248
+ function createMapper(root, config, rojoPath) {
249
+ const projectJson = node_path_1.default.resolve(root, rojoPath || "default.project.json");
250
+ const mapper = new rojo_mapper_js_1.RojoMapper(projectJson, node_path_1.default.join(root, config.rootDir), config.outDir);
251
+ mapper.loadSync();
252
+ return mapper;
253
+ }
254
+ function compileProject(root, config, mapper) {
255
+ const srcDir = node_path_1.default.join(root, config.rootDir);
256
+ const files = collectCpp(srcDir);
257
+ const jobs = [];
258
+ const errors = [];
259
+ for (const file of files) {
260
+ const source = node_fs_1.default.readFileSync(file, "utf8");
261
+ const rel = posixRel(srcDir, file);
262
+ try {
263
+ const result = (0, transpile_js_1.transpileSource)(source, rel, {
264
+ ...config,
265
+ filePath: file,
266
+ relativeName: rel,
267
+ outName: (0, preprocess_js_1.toLuauPath)(rel),
268
+ architecture: config.architecture === true,
269
+ includeDirs: [node_path_1.default.dirname(file), srcDir, node_path_1.default.join(root, "include")],
270
+ srcDir,
271
+ outDir: config.outDir,
272
+ }, mapper);
273
+ jobs.push({ rel, files: result.files, stale: result.stale || [] });
274
+ }
275
+ catch (err) {
276
+ errors.push({ rel, prefixes: sourcePrefixes(rel), message: err.message });
277
+ }
278
+ }
279
+ return { files, jobs, errors };
280
+ }
281
+ function writeTextIfChanged(dest, contents) {
282
+ if (node_fs_1.default.existsSync(dest) && node_fs_1.default.readFileSync(dest, "utf8") === contents) {
283
+ return false;
284
+ }
285
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(dest), { recursive: true });
286
+ node_fs_1.default.writeFileSync(dest, contents, "utf8");
287
+ return true;
288
+ }
289
+ function outDest(root, config, rel) {
290
+ const safe = (0, safe_paths_js_1.safeRelPath)(rel);
291
+ if (!safe) {
292
+ console.error("cluaupp: skip unsafe out path", rel);
293
+ return null;
294
+ }
295
+ const outRoot = node_path_1.default.resolve(root, config.outDir);
296
+ const dest = node_path_1.default.join(outRoot, safe);
297
+ if (!(0, safe_paths_js_1.isInside)(outRoot, dest) || !(0, safe_paths_js_1.isInside)(root, dest)) {
298
+ console.error("cluaupp: skip unsafe out path", rel);
299
+ return null;
300
+ }
301
+ return dest;
302
+ }
303
+ function writeJobs(root, config, jobs, format) {
304
+ const written = new Set();
305
+ for (const job of jobs) {
306
+ for (const artifact of job.files) {
307
+ const dest = outDest(root, config, artifact.name);
308
+ if (!dest) {
309
+ continue;
310
+ }
311
+ const changed = writeTextIfChanged(dest, artifact.contents);
312
+ written.add(resolveKey(dest));
313
+ if (changed) {
314
+ console.log("cluaupp:", job.rel, "→", node_path_1.default.relative(root, dest));
315
+ }
316
+ if (format && /\.luau$/i.test(dest)) {
317
+ process_orchestrator_js_1.ProcessOrchestrator.formatWithStyLuaSync(dest);
318
+ }
319
+ }
320
+ for (const stale of job.stale || []) {
321
+ const dest = outDest(root, config, stale);
322
+ if (dest && node_fs_1.default.existsSync(dest) && !written.has(resolveKey(dest))) {
323
+ if (tryRm(dest, node_path_1.default.resolve(root, config.outDir))) {
324
+ console.log("cluaupp: removed", node_path_1.default.relative(root, dest));
325
+ }
326
+ }
327
+ }
328
+ }
329
+ return written;
330
+ }
331
+ function ensureVendor(root) {
332
+ copyRuntime(root);
333
+ copyHeaders(root);
334
+ }
335
+ function build(root, options = {}) {
336
+ const exitOnError = options.exitOnError !== false;
337
+ const holdOnError = options.holdOnError === true;
338
+ const format = options.format === true;
339
+ const config = loadConfig(root);
340
+ if (options.syncVendor !== false) {
341
+ ensureVendor(root);
342
+ }
343
+ const srcDir = node_path_1.default.join(root, config.rootDir);
344
+ if (!node_fs_1.default.existsSync(srcDir) || collectCpp(srcDir).length === 0) {
345
+ console.error("no .cpp/.h/.hpp files in", config.rootDir);
346
+ if (!holdOnError) {
347
+ pruneOut(root, config, new Set(), []);
348
+ }
349
+ if (exitOnError) {
350
+ process.exit(1);
351
+ }
352
+ return { failed: 0, written: new Set() };
353
+ }
354
+ const mapper = createMapper(root, config, options.rojo);
355
+ const compiled = compileProject(root, config, mapper);
356
+ if (compiled.errors.length > 0) {
357
+ for (const err of compiled.errors) {
358
+ console.error(err.message);
359
+ }
360
+ if (holdOnError) {
361
+ console.error(`cluaupp: out not updated (${compiled.errors.length} compile error${compiled.errors.length === 1 ? "" : "s"})`);
362
+ try {
363
+ (0, intellisense_js_1.syncEditorSupport)(root, config);
364
+ }
365
+ catch (err) {
366
+ console.error("cluaupp: intellisense sync failed", err.message);
367
+ }
368
+ return { failed: compiled.errors.length, written: new Set() };
369
+ }
370
+ }
371
+ const written = writeJobs(root, config, compiled.jobs, format);
372
+ const failedPrefixes = compiled.errors.flatMap((err) => err.prefixes);
373
+ pruneOut(root, config, written, failedPrefixes);
374
+ try {
375
+ (0, intellisense_js_1.syncEditorSupport)(root, config);
376
+ }
377
+ catch (err) {
378
+ console.error("cluaupp: intellisense sync failed", err.message);
379
+ }
380
+ if (options.analyze) {
381
+ for (const file of written) {
382
+ if (/\.luau$/i.test(file)) {
383
+ const report = process_orchestrator_js_1.ProcessOrchestrator.analyzeWithLuauSync(file);
384
+ if (report) {
385
+ console.log("\n📊 Relatório de Análise Estática do Luau:\n", report);
386
+ }
387
+ }
388
+ }
389
+ }
390
+ if (compiled.errors.length > 0 && exitOnError) {
391
+ process.exit(1);
392
+ }
393
+ return { failed: compiled.errors.length, written };
394
+ }
395
+ async function init(dest) {
396
+ const target = (0, safe_paths_js_1.assertInitDest)(dest);
397
+ const template = node_path_1.default.join(__dirname, "..", "..", "templates", "game");
398
+ const include = node_path_1.default.join(__dirname, "..", "..", "include");
399
+ copyDir(template, target);
400
+ copyDir(include, node_path_1.default.join(target, "include"));
401
+ copyRuntime(target);
402
+ (0, intellisense_js_1.syncEditorSupport)(target);
403
+ const installed = await (0, intellisense_js_1.installEditorSupport)();
404
+ console.log("Cluaupp ready in", target);
405
+ console.log(" rokit install");
406
+ console.log(" cluaupp build");
407
+ console.log(" rojo serve");
408
+ if (installed.local.length ||
409
+ (installed.clangd && (installed.clangd.status === "installed" || installed.clangd.status === "already")) ||
410
+ (installed.llvm && (installed.llvm.status === "installed" || installed.llvm.status === "already"))) {
411
+ console.log(" reload Cursor (Ctrl+Shift+P → Developer: Reload Window)");
412
+ }
413
+ }
414
+ function pidAlive(pid) {
415
+ try {
416
+ process.kill(pid, 0);
417
+ return true;
418
+ }
419
+ catch {
420
+ return false;
421
+ }
422
+ }
423
+ function acquireWatchLock(root) {
424
+ const file = node_path_1.default.join(root, ".cluaupp-watch.lock");
425
+ if (node_fs_1.default.existsSync(file)) {
426
+ const pid = Number(node_fs_1.default.readFileSync(file, "utf8").trim());
427
+ if (Number.isFinite(pid) && pid !== process.pid && pidAlive(pid)) {
428
+ console.error(`cluaupp: watch already running (pid ${pid}). Stop it first — two watchers make Rojo crash on libs/.`);
429
+ process.exit(1);
430
+ }
431
+ }
432
+ node_fs_1.default.writeFileSync(file, String(process.pid), "utf8");
433
+ const release = () => {
434
+ try {
435
+ if (node_fs_1.default.existsSync(file) && node_fs_1.default.readFileSync(file, "utf8").trim() === String(process.pid)) {
436
+ node_fs_1.default.unlinkSync(file);
437
+ }
438
+ }
439
+ catch {
440
+ // ignore
441
+ }
442
+ };
443
+ process.on("exit", release);
444
+ process.on("SIGINT", () => {
445
+ release();
446
+ process.exit(0);
447
+ });
448
+ process.on("SIGTERM", () => {
449
+ release();
450
+ process.exit(0);
451
+ });
452
+ }
453
+ function watch(root, options = {}) {
454
+ acquireWatchLock(root);
455
+ build(root, { exitOnError: false, syncVendor: false, holdOnError: true, format: options.format, rojo: options.rojo });
456
+ const config = loadConfig(root);
457
+ const dir = node_path_1.default.join(root, config.rootDir);
458
+ let timer = null;
459
+ let running = false;
460
+ let queued = false;
461
+ const run = () => {
462
+ if (running) {
463
+ queued = true;
464
+ return;
465
+ }
466
+ running = true;
467
+ try {
468
+ build(root, { exitOnError: false, syncVendor: false, holdOnError: true, format: options.format, rojo: options.rojo });
469
+ }
470
+ catch (err) {
471
+ console.error(err.message);
472
+ }
473
+ finally {
474
+ running = false;
475
+ if (queued) {
476
+ queued = false;
477
+ run();
478
+ }
479
+ }
480
+ };
481
+ console.log("cluaupp", package_info_js_1.pkg.version, "watching", dir, "(libs untouched)");
482
+ if (!node_fs_1.default.existsSync(dir)) {
483
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
484
+ }
485
+ node_fs_1.default.watch(dir, { recursive: true }, () => {
486
+ if (timer) {
487
+ clearTimeout(timer);
488
+ }
489
+ timer = setTimeout(run, 400);
490
+ });
491
+ }
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RojoMapper = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const comment_json_1 = require("comment-json");
10
+ const libs_js_1 = require("../libs.js");
11
+ const preprocess_js_1 = require("../preprocess.js");
12
+ const SYSTEM_INCLUDES = new Set([
13
+ "iostream",
14
+ "string",
15
+ "string_view",
16
+ "vector",
17
+ "array",
18
+ "map",
19
+ "unordered_map",
20
+ "set",
21
+ "unordered_set",
22
+ "optional",
23
+ "memory",
24
+ "cstdint",
25
+ "cstddef",
26
+ "cmath",
27
+ "algorithm",
28
+ "utility",
29
+ "functional",
30
+ "span",
31
+ "tuple",
32
+ "type_traits",
33
+ ]);
34
+ function asRecord(value) {
35
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
36
+ return null;
37
+ }
38
+ return value;
39
+ }
40
+ function posix(filePath) {
41
+ return filePath.replace(/\\/g, "/");
42
+ }
43
+ class RojoMapper {
44
+ projectJsonPath;
45
+ srcDir;
46
+ outDir;
47
+ config = null;
48
+ bindings = [];
49
+ constructor(projectJsonPath, srcDir, outDir) {
50
+ this.projectJsonPath = projectJsonPath;
51
+ this.srcDir = srcDir;
52
+ this.outDir = outDir;
53
+ }
54
+ async load() {
55
+ this.loadSync();
56
+ }
57
+ loadSync() {
58
+ this.config = null;
59
+ this.bindings = [];
60
+ if (!node_fs_1.default.existsSync(this.projectJsonPath)) {
61
+ return;
62
+ }
63
+ const content = node_fs_1.default.readFileSync(this.projectJsonPath, "utf8");
64
+ this.config = (0, comment_json_1.parse)(content);
65
+ this.bindings = this.walkTree(this.config.tree, []);
66
+ }
67
+ resolveIncludeToRequire(includePath) {
68
+ const raw = includePath.replace(/[<>'"]/g, "").trim();
69
+ if (!raw) {
70
+ return null;
71
+ }
72
+ const cleanPath = posix(raw).replace(/\.h(pp|h)?$/i, "");
73
+ const baseName = node_path_1.default.posix.basename(cleanPath);
74
+ const key = baseName.toLowerCase();
75
+ if ((0, preprocess_js_1.isEngineStub)(raw) || SYSTEM_INCLUDES.has(key) || SYSTEM_INCLUDES.has(cleanPath.toLowerCase())) {
76
+ return null;
77
+ }
78
+ const library = this.resolveLibrary(cleanPath, key);
79
+ if (library) {
80
+ return library;
81
+ }
82
+ const fromTree = this.resolveFromRojoTree(cleanPath, baseName);
83
+ if (fromTree) {
84
+ return fromTree;
85
+ }
86
+ const segments = cleanPath.split("/").filter(Boolean);
87
+ if (segments[0] === "src") {
88
+ segments.shift();
89
+ }
90
+ const alias = this.safeAlias(baseName);
91
+ if (segments[0] === "shared") {
92
+ return { alias, path: `ReplicatedStorage.Cluaupp.${segments.slice(1).join(".")}` };
93
+ }
94
+ if (segments[0] === "server") {
95
+ return { alias, path: `ServerScriptService.Cluaupp.${segments.slice(1).join(".")}` };
96
+ }
97
+ if (segments[0] === "client") {
98
+ return { alias, path: `StarterPlayer.StarterPlayerScripts.Cluaupp.${segments.slice(1).join(".")}` };
99
+ }
100
+ return { alias, path: `ReplicatedStorage.${segments.join(".")}` };
101
+ }
102
+ resolveLibrary(cleanPath, key) {
103
+ const mapped = libs_js_1.INCLUDE_TO_MODULE[key]
104
+ ?? libs_js_1.INCLUDE_TO_MODULE[node_path_1.default.posix.basename(cleanPath).toLowerCase()];
105
+ if (!mapped) {
106
+ return null;
107
+ }
108
+ const spec = libs_js_1.MODULES[mapped];
109
+ const bind = spec?.bind || mapped;
110
+ const file = spec?.file || mapped;
111
+ const libsRoot = this.findBinding((binding) => binding.robloxPath.endsWith(".CluauppLibs") || binding.robloxPath === "ReplicatedStorage.CluauppLibs");
112
+ const packagesRoot = this.findBinding((binding) => binding.robloxPath.endsWith(".Packages") || binding.robloxPath === "ReplicatedStorage.Packages");
113
+ if (libsRoot) {
114
+ return { alias: bind, path: `${libsRoot.robloxPath}.${file}` };
115
+ }
116
+ if (packagesRoot) {
117
+ return { alias: bind, path: `${packagesRoot.robloxPath}.${file}` };
118
+ }
119
+ return { alias: bind, path: `ReplicatedStorage.CluauppLibs.${file}` };
120
+ }
121
+ resolveFromRojoTree(cleanPath, baseName) {
122
+ const outRel = this.guessOutRel(cleanPath);
123
+ if (!outRel || this.bindings.length === 0) {
124
+ return null;
125
+ }
126
+ let best = null;
127
+ for (const binding of this.bindings) {
128
+ const prefix = posix(binding.fsPath).replace(/\/$/, "");
129
+ if (outRel === prefix || outRel.startsWith(`${prefix}/`)) {
130
+ const rest = outRel === prefix ? [] : outRel.slice(prefix.length + 1).split("/");
131
+ if (!best || prefix.length > posix(best.binding.fsPath).length) {
132
+ best = { binding, rest };
133
+ }
134
+ }
135
+ }
136
+ if (!best) {
137
+ return null;
138
+ }
139
+ const tail = best.rest.join(".");
140
+ return {
141
+ alias: this.safeAlias(baseName),
142
+ path: tail ? `${best.binding.robloxPath}.${tail}` : best.binding.robloxPath,
143
+ };
144
+ }
145
+ guessOutRel(cleanPath) {
146
+ const relative = posix(cleanPath).replace(/^src\//, "");
147
+ const outName = relative.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, "");
148
+ if (this.outDir) {
149
+ return posix(node_path_1.default.join(this.outDir, outName));
150
+ }
151
+ return posix(node_path_1.default.join("out", outName));
152
+ }
153
+ walkTree(node, robloxPath) {
154
+ const record = asRecord(node);
155
+ if (!record) {
156
+ return [];
157
+ }
158
+ const found = [];
159
+ if (typeof record.$path === "string" && robloxPath.length > 0) {
160
+ found.push({
161
+ robloxPath: robloxPath.join("."),
162
+ fsPath: posix(record.$path),
163
+ });
164
+ }
165
+ for (const [key, child] of Object.entries(record)) {
166
+ if (key.startsWith("$")) {
167
+ continue;
168
+ }
169
+ found.push(...this.walkTree(child, [...robloxPath, key]));
170
+ }
171
+ return found;
172
+ }
173
+ findBinding(predicate) {
174
+ return this.bindings.find(predicate);
175
+ }
176
+ safeAlias(name) {
177
+ const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_");
178
+ return /^[A-Za-z_]/.test(cleaned) ? cleaned : `_${cleaned}`;
179
+ }
180
+ }
181
+ exports.RojoMapper = RojoMapper;