cluaupp 0.1.1 → 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.
- package/CHANGELOG.md +27 -1
- package/README.md +5 -8
- package/docs/README.md +13 -10
- package/docs/architecture.md +18 -11
- package/docs/cli.md +2 -2
- package/docs/config.md +1 -1
- package/docs/cpp-advanced.md +10 -6
- package/docs/cpp-organization.md +3 -1
- package/docs/cpp-safety.md +11 -7
- package/docs/cpp-types.md +1 -1
- package/docs/examples/_category_.json +5 -0
- package/docs/examples/combat.md +239 -0
- package/docs/examples/data-boot.md +98 -0
- package/docs/examples/hud.md +96 -0
- package/docs/examples/index.md +43 -0
- package/docs/examples/leaderstats.md +140 -0
- package/docs/examples/shop.md +122 -0
- package/docs/examples/sword.md +108 -0
- package/docs/getting-started.md +12 -6
- package/docs/intellisense.md +31 -0
- package/docs/intro.md +2 -2
- package/docs/libraries/_category_.json +5 -0
- package/docs/libraries/dataservice.md +104 -0
- package/docs/libraries/index.md +22 -0
- package/docs/libraries/janitor.md +65 -0
- package/docs/libraries/more.md +79 -0
- package/docs/libraries/net.md +35 -0
- package/docs/libraries/promise.md +27 -0
- package/docs/oop/_category_.json +5 -0
- package/docs/oop/file-tags.md +49 -0
- package/docs/oop/index.md +22 -0
- package/docs/oop/modules.md +86 -0
- package/docs/oop/services.md +83 -0
- package/docs/print-cout.md +91 -0
- package/docs/syntax.md +59 -5
- package/editors/vscode/extension.js +146 -0
- package/editors/vscode/package.json +25 -0
- package/include/cluaupp/libs/janitor.hpp +7 -3
- package/include/cluaupp/roblox.hpp +19 -0
- package/package.json +66 -65
- package/runtime/Janitor/init.luau +4 -34
- package/src/architecture.js +108 -48
- package/src/cli.js +198 -64
- package/src/client/init.client.cpp +5 -0
- package/src/compile.js +20 -4
- package/src/editor-install.js +218 -0
- package/src/emit.js +320 -8
- package/src/intellisense.js +1368 -0
- package/src/layout.js +129 -0
- package/src/lex.js +17 -9
- package/src/libs.js +95 -16
- package/src/lsp.js +227 -0
- package/src/parse.js +187 -6
- package/src/preprocess.js +71 -3
- package/src/server/leaderstats.server.cpp +28 -0
- package/src/shared/config.cpp +5 -0
- package/src/shared/config.h +3 -0
- package/src/understand.js +64 -6
- package/templates/game/.clangd +11 -0
- package/templates/game/.vscode/c_cpp_properties.json +6 -2
- package/templates/game/.vscode/extensions.json +6 -0
- package/templates/game/.vscode/settings.json +16 -2
- package/templates/game/compile_flags.txt +2 -0
- package/templates/game/default.project.json +5 -10
- package/docs/libraries.md +0 -80
package/src/architecture.js
CHANGED
|
@@ -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('
|
|
31
|
-
lines.push(`
|
|
32
|
-
lines.push(`
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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(`
|
|
161
|
+
lines.push(`const Types = require(script.Parent.${types})`);
|
|
150
162
|
lines.push("");
|
|
151
|
-
lines.push(`
|
|
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("
|
|
228
|
+
lines.push("const PlayersManager = require(script.Parent.PlayersManager)");
|
|
213
229
|
}
|
|
214
230
|
if (plan.roles.cache) {
|
|
215
|
-
lines.push("
|
|
231
|
+
lines.push("const CacheController = require(script.Parent.CacheController)");
|
|
216
232
|
}
|
|
217
233
|
if (plan.roles.domain) {
|
|
218
|
-
lines.push(`
|
|
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
|
-
|
|
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
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return
|
|
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
|
|
337
|
-
const files = [{ name: `${folder}/${boot}`, contents: emitBootstrap(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
|
|
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
|
|
|
@@ -47,8 +50,11 @@ function tryRm(target) {
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function copyFileIfChanged(src, dest) {
|
|
51
|
-
if (fs.existsSync(dest)) {
|
|
53
|
+
function copyFileIfChanged(src, dest, mode = "fill") {
|
|
54
|
+
if (mode === "fill" && fs.existsSync(dest)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (mode === "update" && fs.existsSync(dest)) {
|
|
52
58
|
const from = fs.statSync(src);
|
|
53
59
|
const to = fs.statSync(dest);
|
|
54
60
|
if (from.size === to.size && from.mtimeMs <= to.mtimeMs) {
|
|
@@ -67,7 +73,7 @@ function copyFileIfChanged(src, dest) {
|
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
function syncDir(from, to) {
|
|
76
|
+
function syncDir(from, to, mode = "fill") {
|
|
71
77
|
if (!fs.existsSync(from)) {
|
|
72
78
|
return;
|
|
73
79
|
}
|
|
@@ -76,9 +82,9 @@ function syncDir(from, to) {
|
|
|
76
82
|
const src = path.join(from, entry.name);
|
|
77
83
|
const dest = path.join(to, entry.name);
|
|
78
84
|
if (entry.isDirectory()) {
|
|
79
|
-
syncDir(src, dest);
|
|
85
|
+
syncDir(src, dest, mode);
|
|
80
86
|
} else {
|
|
81
|
-
copyFileIfChanged(src, dest);
|
|
87
|
+
copyFileIfChanged(src, dest, mode);
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
}
|
|
@@ -141,7 +147,7 @@ function collectFiles(dir, files = []) {
|
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
function siblingCpp(file) {
|
|
144
|
-
return file.replace(/\.(h|hpp|hh)$/i, ".cpp");
|
|
150
|
+
return siblingImplementation(file) || file.replace(/\.(h|hpp|hh)$/i, ".cpp");
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
function resolveKey(file) {
|
|
@@ -214,29 +220,13 @@ function pruneOut(root, config, written, failedPrefixes) {
|
|
|
214
220
|
removeEmptyOutDirs(outDir, root);
|
|
215
221
|
}
|
|
216
222
|
|
|
217
|
-
function
|
|
218
|
-
const exitOnError = options.exitOnError !== false;
|
|
219
|
-
const syncVendor = options.syncVendor !== false;
|
|
220
|
-
const config = loadConfig(root);
|
|
221
|
-
if (syncVendor) {
|
|
222
|
-
copyRuntime(root);
|
|
223
|
-
copyHeaders(root);
|
|
224
|
-
}
|
|
223
|
+
function compileProject(root, config) {
|
|
225
224
|
const srcDir = path.join(root, config.rootDir);
|
|
226
225
|
const files = collectCpp(srcDir);
|
|
227
|
-
if (files.length === 0) {
|
|
228
|
-
console.error("no .cpp/.h/.hpp files in", config.rootDir);
|
|
229
|
-
pruneOut(root, config, new Set(), []);
|
|
230
|
-
if (exitOnError) {
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
return { failed: 0, written: new Set() };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
226
|
const fileSet = new Set(files.map((file) => path.resolve(file)));
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
227
|
+
const jobs = [];
|
|
228
|
+
const errors = [];
|
|
229
|
+
|
|
240
230
|
for (const file of files) {
|
|
241
231
|
if (isHeaderFile(file) && fileSet.has(path.resolve(siblingCpp(file)))) {
|
|
242
232
|
continue;
|
|
@@ -251,48 +241,166 @@ function build(root, options = {}) {
|
|
|
251
241
|
outName: toLuauPath(rel),
|
|
252
242
|
architecture: config.architecture !== false,
|
|
253
243
|
includeDirs: [path.dirname(file), srcDir, path.join(root, "include")],
|
|
244
|
+
srcDir,
|
|
254
245
|
});
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
246
|
+
jobs.push({ rel, files: result.files, stale: result.stale || [] });
|
|
247
|
+
} catch (err) {
|
|
248
|
+
errors.push({ rel, prefixes: sourcePrefixes(rel), message: err.message });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { files, jobs, errors };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function writeTextIfChanged(dest, contents) {
|
|
256
|
+
if (fs.existsSync(dest) && fs.readFileSync(dest, "utf8") === contents) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
260
|
+
fs.writeFileSync(dest, contents, "utf8");
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function writeJobs(root, config, jobs) {
|
|
265
|
+
const written = new Set();
|
|
266
|
+
for (const job of jobs) {
|
|
267
|
+
for (const artifact of job.files) {
|
|
268
|
+
const dest = path.join(root, config.outDir, artifact.name);
|
|
269
|
+
const changed = writeTextIfChanged(dest, artifact.contents);
|
|
270
|
+
written.add(resolveKey(dest));
|
|
271
|
+
if (changed) {
|
|
272
|
+
console.log("cluaupp:", job.rel, "→", path.relative(root, dest));
|
|
261
273
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
}
|
|
275
|
+
for (const stale of job.stale) {
|
|
276
|
+
const dest = path.join(root, config.outDir, stale);
|
|
277
|
+
if (fs.existsSync(dest) && !written.has(resolveKey(dest))) {
|
|
278
|
+
if (tryRm(dest)) {
|
|
279
|
+
console.log("cluaupp: removed", path.relative(root, dest));
|
|
268
280
|
}
|
|
269
281
|
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return written;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function libsPresent(root) {
|
|
288
|
+
const libs = path.join(root, "libs");
|
|
289
|
+
return fs.existsSync(libs) && fs.readdirSync(libs).length > 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ensureVendor(root) {
|
|
293
|
+
copyRuntime(root);
|
|
294
|
+
copyHeaders(root);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function build(root, options = {}) {
|
|
298
|
+
const exitOnError = options.exitOnError !== false;
|
|
299
|
+
const holdOnError = options.holdOnError === true;
|
|
300
|
+
const config = loadConfig(root);
|
|
301
|
+
if (options.syncVendor !== false) {
|
|
302
|
+
ensureVendor(root);
|
|
303
|
+
}
|
|
304
|
+
const srcDir = path.join(root, config.rootDir);
|
|
305
|
+
if (!fs.existsSync(srcDir) || collectCpp(srcDir).length === 0) {
|
|
306
|
+
console.error("no .cpp/.h/.hpp files in", config.rootDir);
|
|
307
|
+
if (!holdOnError) {
|
|
308
|
+
pruneOut(root, config, new Set(), []);
|
|
309
|
+
}
|
|
310
|
+
if (exitOnError) {
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
return { failed: 0, written: new Set() };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const compiled = compileProject(root, config);
|
|
317
|
+
if (compiled.errors.length > 0) {
|
|
318
|
+
for (const err of compiled.errors) {
|
|
273
319
|
console.error(err.message);
|
|
274
320
|
}
|
|
321
|
+
if (holdOnError) {
|
|
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
|
+
}
|
|
328
|
+
return { failed: compiled.errors.length, written: new Set() };
|
|
329
|
+
}
|
|
275
330
|
}
|
|
331
|
+
|
|
332
|
+
const written = writeJobs(root, config, compiled.jobs);
|
|
333
|
+
const failedPrefixes = compiled.errors.flatMap((err) => err.prefixes);
|
|
276
334
|
pruneOut(root, config, written, failedPrefixes);
|
|
277
|
-
|
|
335
|
+
try {
|
|
336
|
+
syncEditorSupport(root, config);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error("cluaupp: intellisense sync failed", err.message);
|
|
339
|
+
}
|
|
340
|
+
if (compiled.errors.length > 0 && exitOnError) {
|
|
278
341
|
process.exit(1);
|
|
279
342
|
}
|
|
280
|
-
return { failed, written };
|
|
343
|
+
return { failed: compiled.errors.length, written };
|
|
281
344
|
}
|
|
282
345
|
|
|
283
|
-
function init(dest) {
|
|
346
|
+
async function init(dest) {
|
|
284
347
|
const template = path.join(__dirname, "..", "templates", "game");
|
|
285
348
|
const include = path.join(__dirname, "..", "include");
|
|
286
349
|
copyDir(template, dest);
|
|
287
350
|
copyDir(include, path.join(dest, "include"));
|
|
288
351
|
copyRuntime(dest);
|
|
352
|
+
syncEditorSupport(dest);
|
|
353
|
+
const installed = await installEditorSupport();
|
|
289
354
|
console.log("Cluaupp ready in", dest);
|
|
290
355
|
console.log(" cluaupp build");
|
|
291
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
|
+
});
|
|
292
399
|
}
|
|
293
400
|
|
|
294
401
|
function watch(root) {
|
|
295
|
-
|
|
402
|
+
acquireWatchLock(root);
|
|
403
|
+
build(root, { exitOnError: false, syncVendor: false, holdOnError: true });
|
|
296
404
|
const config = loadConfig(root);
|
|
297
405
|
const dir = path.join(root, config.rootDir);
|
|
298
406
|
let timer = null;
|
|
@@ -306,7 +414,7 @@ function watch(root) {
|
|
|
306
414
|
}
|
|
307
415
|
running = true;
|
|
308
416
|
try {
|
|
309
|
-
build(root, { exitOnError: false, syncVendor: false });
|
|
417
|
+
build(root, { exitOnError: false, syncVendor: false, holdOnError: true });
|
|
310
418
|
} catch (err) {
|
|
311
419
|
console.error(err.message);
|
|
312
420
|
} finally {
|
|
@@ -318,30 +426,56 @@ function watch(root) {
|
|
|
318
426
|
}
|
|
319
427
|
};
|
|
320
428
|
|
|
321
|
-
console.log("watching", dir);
|
|
429
|
+
console.log("cluaupp", pkg.version, "watching", dir, "(libs untouched)");
|
|
322
430
|
if (!fs.existsSync(dir)) {
|
|
323
431
|
fs.mkdirSync(dir, { recursive: true });
|
|
324
432
|
}
|
|
325
433
|
fs.watch(dir, { recursive: true }, () => {
|
|
326
434
|
clearTimeout(timer);
|
|
327
|
-
timer = setTimeout(run,
|
|
435
|
+
timer = setTimeout(run, 400);
|
|
328
436
|
});
|
|
329
437
|
}
|
|
330
438
|
|
|
331
|
-
|
|
332
|
-
const cmd = args[0] || "help";
|
|
333
|
-
const cwd = process.cwd();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
} else if (cmd === "
|
|
340
|
-
|
|
341
|
-
} else if (cmd === "
|
|
342
|
-
|
|
343
|
-
} else {
|
|
344
|
-
|
|
439
|
+
function dispatch(args) {
|
|
440
|
+
const cmd = args[0] || "help";
|
|
441
|
+
const cwd = process.cwd();
|
|
442
|
+
if (cmd === "init") {
|
|
443
|
+
return init(path.resolve(cwd, args[1] || ".")).catch((err) => {
|
|
444
|
+
console.error(err.message || err);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
});
|
|
447
|
+
} else if (cmd === "build") {
|
|
448
|
+
build(path.resolve(cwd, args[1] || "."));
|
|
449
|
+
} else if (cmd === "watch") {
|
|
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
|
+
});
|
|
468
|
+
} else if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
469
|
+
console.log(pkg.version);
|
|
470
|
+
} else {
|
|
471
|
+
printHelp();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const launchedAsCli =
|
|
476
|
+
require.main && ["cli.js", "cluaupp.js", "cluau.js"].includes(path.basename(require.main.filename));
|
|
477
|
+
if (launchedAsCli) {
|
|
478
|
+
dispatch(process.argv.slice(2));
|
|
345
479
|
}
|
|
346
480
|
|
|
347
|
-
module.exports = { build, watch };
|
|
481
|
+
module.exports = { build, watch, dispatch };
|