cluaupp 0.2.3 → 1.0.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 (114) hide show
  1. package/CHANGELOG.md +10 -79
  2. package/README.md +42 -88
  3. package/docs/README.md +13 -21
  4. package/docs/architecture.md +13 -76
  5. package/docs/cli.md +12 -64
  6. package/docs/comparison.md +6 -24
  7. package/docs/config.md +6 -7
  8. package/docs/examples/combat.md +19 -227
  9. package/docs/examples/hud.md +13 -81
  10. package/docs/examples/index.md +9 -16
  11. package/docs/getting-started.md +20 -137
  12. package/docs/intellisense.md +5 -41
  13. package/docs/intro.md +16 -15
  14. package/docs/migration.md +38 -0
  15. package/docs/oop/file-tags.md +12 -36
  16. package/docs/oop/index.md +8 -14
  17. package/docs/syntax.md +54 -236
  18. package/editors/vscode/extension.js +1 -1
  19. package/editors/vscode/package.json +4 -4
  20. package/examples/README.md +5 -7
  21. package/examples/game/.vscode/extensions.json +0 -3
  22. package/examples/game/.vscode/settings.json +6 -14
  23. package/examples/game/src/client/hud.client.clpp +54 -0
  24. package/examples/game/src/client/init.client.clpp +6 -0
  25. package/examples/game/src/server/combat.server.clpp +85 -0
  26. package/examples/game/src/server/leaderstats.server.clpp +28 -0
  27. package/examples/game/src/shared/PlayerData.clh +20 -0
  28. package/examples/game/src/shared/config.clp +6 -0
  29. package/examples/game/src/shared/features.clp +88 -0
  30. package/generated/cli.js +40 -24
  31. package/generated/clpp/contract.js +5 -0
  32. package/generated/clpp/index.js +27 -0
  33. package/generated/clpp/paths.js +121 -0
  34. package/generated/clpp/postprocess.js +78 -0
  35. package/generated/clpp/runner.js +102 -0
  36. package/generated/compile.js +26 -138
  37. package/generated/intellisense.js +38 -157
  38. package/generated/libs.js +1 -1
  39. package/generated/lsp.js +1 -8
  40. package/generated/preprocess.js +8 -179
  41. package/generated/system-understander.js +1 -1
  42. package/generated/transpile.js +3 -50
  43. package/generated/utils/project.js +13 -28
  44. package/include/{cluaupp/datatypes.hpp → clpp/datatypes.clh} +1 -1
  45. package/include/{cluaupp/generated/instances.hpp → clpp/generated/instances.clh} +1 -1
  46. package/include/{cluaupp/libs/chrono.hpp → clpp/libs/chrono.clh} +9 -9
  47. package/include/{cluaupp/libs/cmdr.hpp → clpp/libs/cmdr.clh} +4 -4
  48. package/include/{cluaupp/libs/dataservice.hpp → clpp/libs/dataservice.clh} +8 -15
  49. package/include/{cluaupp/libs/display.hpp → clpp/libs/display.clh} +1 -2
  50. package/include/{cluaupp/libs/ezvisual.hpp → clpp/libs/ezvisual.clh} +1 -3
  51. package/include/{cluaupp/libs/formatnumber.hpp → clpp/libs/formatnumber.clh} +2 -3
  52. package/include/{cluaupp/libs/fusion.hpp → clpp/libs/fusion.clh} +5 -5
  53. package/include/{cluaupp/libs/iris.hpp → clpp/libs/iris.clh} +2 -2
  54. package/include/{cluaupp/libs/janitor.hpp → clpp/libs/janitor.clh} +1 -2
  55. package/include/{cluaupp/libs/module3d.hpp → clpp/libs/module3d.clh} +2 -4
  56. package/include/{cluaupp/libs/net.hpp → clpp/libs/net.clh} +2 -4
  57. package/include/{cluaupp/libs/promise.hpp → clpp/libs/promise.clh} +1 -4
  58. package/include/{cluaupp/libs/spring.hpp → clpp/libs/spring.clh} +1 -2
  59. package/include/{cluaupp/libs/statemachine.hpp → clpp/libs/statemachine.clh} +2 -3
  60. package/include/{cluaupp/libs/stickybillboard.hpp → clpp/libs/stickybillboard.clh} +1 -2
  61. package/include/{cluaupp/libs/topbarplus.hpp → clpp/libs/topbarplus.clh} +3 -4
  62. package/include/clpp/libs.clh +24 -0
  63. package/include/clpp/roblox.clh +31 -0
  64. package/package.json +7 -14
  65. package/src/cli.ts +39 -25
  66. package/src/clpp/contract.ts +39 -0
  67. package/src/clpp/index.ts +18 -0
  68. package/src/clpp/paths.ts +117 -0
  69. package/src/clpp/postprocess.ts +93 -0
  70. package/src/clpp/runner.ts +102 -0
  71. package/src/compile.ts +26 -149
  72. package/src/intellisense.ts +36 -165
  73. package/src/libs.ts +1 -1
  74. package/src/lsp.ts +1 -5
  75. package/src/preprocess.ts +8 -180
  76. package/src/system-understander.ts +1 -1
  77. package/src/transpile.ts +9 -52
  78. package/src/utils/project.ts +12 -28
  79. package/templates/game/.vscode/extensions.json +0 -3
  80. package/templates/game/.vscode/settings.json +6 -14
  81. package/templates/game/src/client/init.client.clpp +6 -0
  82. package/templates/game/src/server/leaderstats.server.clpp +29 -0
  83. package/templates/game/src/shared/config.clp +6 -0
  84. package/examples/game/.clangd +0 -25
  85. package/examples/game/compile_flags.txt +0 -6
  86. package/examples/game/src/client/hud.client.cpp +0 -12
  87. package/examples/game/src/client/init.client.cpp +0 -6
  88. package/examples/game/src/server/combat.server.cpp +0 -23
  89. package/examples/game/src/server/leaderstats.server.cpp +0 -29
  90. package/examples/game/src/shared/config.cpp +0 -5
  91. package/examples/game/src/shared/config.h +0 -5
  92. package/include/cluaupp/libs.hpp +0 -25
  93. package/include/cluaupp/roblox.hpp +0 -47
  94. package/src/architecture.ts +0 -500
  95. package/src/ast.ts +0 -37
  96. package/src/emit.ts +0 -609
  97. package/src/emitter/luau-codegen.ts +0 -187
  98. package/src/emitter/translators.ts +0 -168
  99. package/src/headers.ts +0 -233
  100. package/src/layout.ts +0 -114
  101. package/src/lex.ts +0 -158
  102. package/src/parse.ts +0 -729
  103. package/src/parser/collector.ts +0 -148
  104. package/src/parser/index.ts +0 -27
  105. package/src/tree-sitter-cpp.d.ts +0 -4
  106. package/src/understand.ts +0 -322
  107. package/templates/game/src/client/init.client.cpp +0 -6
  108. package/templates/game/src/server/leaderstats.server.cpp +0 -29
  109. package/templates/game/src/shared/config.cpp +0 -5
  110. package/templates/game/src/shared/config.h +0 -5
  111. /package/include/{cluaupp/generated/enums.hpp → clpp/generated/enums.clh} +0 -0
  112. /package/include/{cluaupp/libs/math.hpp → clpp/libs/math.clh} +0 -0
  113. /package/include/{cluaupp/libs/twinkle.hpp → clpp/libs/twinkle.clh} +0 -0
  114. /package/include/{cluaupp/libs/vfx.hpp → clpp/libs/vfx.clh} +0 -0
@@ -1,10 +1,9 @@
1
1
  #pragma once
2
2
 
3
- // 1ForeverHD/TopbarPlus — vendored into CluauppLibs.TopbarPlus (class Icon)
4
- class IconTheme {};
3
+ // 1ForeverHD/TopbarPlus — vendored into CluauppLibs.TopbarPlus (struct Icon)
4
+ struct IconTheme {};
5
5
 
6
- class Icon {
7
- public:
6
+ struct Icon {
8
7
  static Icon* new_();
9
8
  Icon* setName(string name);
10
9
  Icon* setLabel(string text);
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+ // Cluaupp libraries — CL++ IntelliSense. clpp emits
3
+ // require(ReplicatedStorage.CluauppLibs.*). Runtime lives in runtime/.
4
+
5
+ #include <clpp/generated/instances.clh>
6
+ #include <clpp/libs/janitor.clh>
7
+ #include <clpp/libs/promise.clh>
8
+ #include <clpp/libs/net.clh>
9
+ #include <clpp/libs/math.clh>
10
+ #include <clpp/libs/formatnumber.clh>
11
+ #include <clpp/libs/module3d.clh>
12
+ #include <clpp/libs/twinkle.clh>
13
+ #include <clpp/libs/dataservice.clh>
14
+ #include <clpp/libs/ezvisual.clh>
15
+ #include <clpp/libs/spring.clh>
16
+ #include <clpp/libs/display.clh>
17
+ #include <clpp/libs/stickybillboard.clh>
18
+ #include <clpp/libs/topbarplus.clh>
19
+ #include <clpp/libs/cmdr.clh>
20
+ #include <clpp/libs/chrono.clh>
21
+ #include <clpp/libs/iris.clh>
22
+ #include <clpp/libs/fusion.clh>
23
+ #include <clpp/libs/statemachine.clh>
24
+ #include <clpp/libs/vfx.clh>
@@ -0,0 +1,31 @@
1
+ #pragma once
2
+ // Cluaupp — Roblox API stubs for CL++ IntelliSense.
3
+ // Source: official client dump + datatypes from create.roblox.com
4
+ // clpp emits Luau (Vector3.new, Instance.new, :GetPlayers, ...).
5
+
6
+ #include <clpp/generated/enums.clh>
7
+ #include <clpp/datatypes.clh>
8
+ #include <clpp/generated/instances.clh>
9
+ #include <clpp/libs.clh>
10
+
11
+ template <typename T>
12
+ T* GetService();
13
+
14
+ template <typename T>
15
+ T* GetService(Instance* game_);
16
+
17
+ extern DataModel* game;
18
+ extern Workspace* workspace;
19
+ extern LuaSourceContainer* script;
20
+
21
+ void post(string message);
22
+ void warn(string message);
23
+ void report(string message);
24
+ void print(string message);
25
+ void error(string message);
26
+
27
+ double tick();
28
+ double time();
29
+ void wait(double seconds = 0);
30
+ void spawn(void (*callback)());
31
+ void delay(double seconds, void (*callback)());
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cluaupp",
3
- "version": "0.2.3",
4
- "description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
3
+ "version": "1.0.0",
4
+ "description": "Cluaupp toolchain — compile CL++ to Luau with Rojo and CluauppLibs.",
5
5
  "author": "KartzDev",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -34,7 +34,8 @@
34
34
  "build:cli": "tsc",
35
35
  "dev:cli": "tsx src/cli.ts",
36
36
  "prepare": "tsc",
37
- "test": "node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/security.test.js && node test/understander.test.js",
37
+ "test": "node test/security.test.js && node test/understander.test.js && node test/out.test.js && node test/types.test.js && node test/modules.test.js && node test/clpp.test.js",
38
+ "test:clpp-contract": "node test/clpp.test.js",
38
39
  "generate-api": "node scripts/generate-api.js",
39
40
  "check-highlight": "tsx web/src/check-content.ts",
40
41
  "vendor-libs": "node scripts/vendor-libs.js",
@@ -52,13 +53,12 @@
52
53
  "keywords": [
53
54
  "luau",
54
55
  "roblox",
55
- "cpp",
56
- "c++",
56
+ "clpp",
57
+ "cl++",
57
58
  "transpiler",
58
59
  "compiler",
59
60
  "rojo",
60
- "cluaupp",
61
- "cluau"
61
+ "cluaupp"
62
62
  ],
63
63
  "repository": {
64
64
  "type": "git",
@@ -69,18 +69,11 @@
69
69
  },
70
70
  "homepage": "https://github.com/KartzRbx/Cluaupp#readme",
71
71
  "dependencies": {
72
- "cluaupp": "^0.2.2",
73
72
  "commander": "^15.0.0",
74
73
  "comment-json": "^5.0.0",
75
- "tree-sitter": "0.21.1",
76
- "tree-sitter-c": "0.23.2",
77
- "tree-sitter-cpp": "0.23.4",
78
74
  "vscode-languageserver": "^10.1.1",
79
75
  "vscode-languageserver-textdocument": "^1.0.14"
80
76
  },
81
- "overrides": {
82
- "tree-sitter-c": "0.23.2"
83
- },
84
77
  "devDependencies": {
85
78
  "@types/node": "^22.20.3",
86
79
  "@types/react": "^19.3.0",
package/src/cli.ts CHANGED
@@ -3,11 +3,13 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { pkg } from "./package-info.js";
5
5
  import { start as startLsp } from "./lsp.js";
6
- import { toLuauPath } from "./preprocess.js";
6
+ import { collectSources, toLuauPath } from "./clpp/paths.js";
7
+ import { CLPP_INSTALL_HINT } from "./clpp/contract.js";
8
+ import { clppManifest, clppVersion, hasClpp, resolveClppBinary } from "./clpp/runner.js";
7
9
  import { installEditorSupport, syncEditorSupport } from "./intellisense.js";
8
10
  import { RojoMapper } from "./utils/rojo-mapper.js";
9
11
  import { ProcessOrchestrator } from "./utils/process-orchestrator.js";
10
- import { build as buildProject, init, loadConfig, watch, collectCpp } from "./utils/project.js";
12
+ import { build as buildProject, init, loadConfig, watch } from "./utils/project.js";
11
13
  import { transpileSource } from "./transpile.js";
12
14
  import type { BuildOptions, BuildResult } from "./types.js";
13
15
 
@@ -15,7 +17,7 @@ const program = new Command();
15
17
 
16
18
  program
17
19
  .name("cluaupp")
18
- .description("Definitive transpiler from C++ subset to structured Luau")
20
+ .description("Cluaupp toolchain — compile CL++ to Luau, Rojo, and CluauppLibs")
19
21
  .version(pkg.version, "-v, --version", "print version")
20
22
  .showHelpAfterError()
21
23
  .action(() => {
@@ -37,14 +39,14 @@ program
37
39
 
38
40
  program
39
41
  .command("build")
40
- .description("Transpila um arquivo, diretório ou projeto C++ para Luau")
42
+ .description("Compile a CL++ file, directory, or project to Luau")
41
43
  .argument("[folder]", "project folder", ".")
42
- .option("-i, --input <path>", "Arquivo ou diretório C++ de entrada")
43
- .option("-o, --output <path>", "Arquivo ou diretório Luau de saída")
44
- .option("-r, --rojo <path>", "Caminho para o default.project.json do Rojo", "./default.project.json")
45
- .option("--strict", "Emitir --!strict")
46
- .option("--format", "Rodar StyLua no output")
47
- .option("--analyze", "Rodar luau-analyze no output")
44
+ .option("-i, --input <path>", "CL++ file or directory")
45
+ .option("-o, --output <path>", "Luau file or directory")
46
+ .option("-r, --rojo <path>", "Path to Rojo default.project.json", "./default.project.json")
47
+ .option("--strict", "Emit --!strict")
48
+ .option("--format", "Run StyLua on output")
49
+ .option("--analyze", "Run luau-analyze on output")
48
50
  .action(async (folder: string, options: {
49
51
  input?: string;
50
52
  output?: string;
@@ -73,8 +75,8 @@ program
73
75
  .command("watch")
74
76
  .description("rebuild on save")
75
77
  .argument("[folder]", "project folder", ".")
76
- .option("-r, --rojo <path>", "Caminho para o default.project.json do Rojo", "./default.project.json")
77
- .option("--format", "Rodar StyLua no output")
78
+ .option("-r, --rojo <path>", "Path to Rojo default.project.json", "./default.project.json")
79
+ .option("--format", "Run StyLua on output")
78
80
  .action((folder: string, options: { rojo?: string; format?: boolean }) => {
79
81
  try {
80
82
  watch(path.resolve(process.cwd(), folder), {
@@ -89,7 +91,7 @@ program
89
91
 
90
92
  program
91
93
  .command("lsp")
92
- .description("Cluaupp subset diagnostics over stdio (JSON-RPC). C++ completion is clangd.")
94
+ .description("Language server stdio (JSON-RPC). Use `clpp install` for CL++ IntelliSense.")
93
95
  .argument("[folder]", "project folder", ".")
94
96
  .action((folder: string) => {
95
97
  startLsp({ projectRoot: path.resolve(process.cwd(), folder) });
@@ -98,18 +100,32 @@ program
98
100
  program
99
101
  .command("intellisense")
100
102
  .alias("intelisense")
101
- .description("install LLVM clangd and write compile_commands.json")
103
+ .description("Point the editor at CL++ (`clpp install`)")
102
104
  .argument("[folder]", "project folder", ".")
103
105
  .action(async (folder: string) => {
104
106
  try {
105
107
  const root = path.resolve(process.cwd(), folder);
106
108
  syncEditorSupport(root, loadConfig(root));
107
- const installed = await installEditorSupport();
108
- console.log("cluaupp: compile_commands.json, .clangd, and .vscode updated in", root);
109
- console.log("cluaupp: C++ completion is clangd (include/cluaupp/roblox.hpp)");
110
- if (installed.clangd || installed.llvm) {
111
- console.log("cluaupp: reload Cursor (Ctrl+Shift+P → Developer: Reload Window)");
109
+ await installEditorSupport();
110
+ console.log("cluaupp: editor associations written in", root);
111
+ console.log("cluaupp:", CLPP_INSTALL_HINT);
112
+ } catch (err) {
113
+ console.error(err instanceof Error ? err.message : err);
114
+ process.exit(1);
115
+ }
116
+ });
117
+
118
+ program
119
+ .command("language")
120
+ .description("CL++ manifest (`clpp api manifest`)")
121
+ .action(() => {
122
+ try {
123
+ resolveClppBinary();
124
+ const version = clppVersion();
125
+ if (version) {
126
+ console.log("clpp", version);
112
127
  }
128
+ console.log(JSON.stringify(clppManifest(), null, "\t"));
113
129
  } catch (err) {
114
130
  console.error(err instanceof Error ? err.message : err);
115
131
  process.exit(1);
@@ -139,17 +155,15 @@ async function buildInput(options: {
139
155
  process.exit(1);
140
156
  }
141
157
 
142
- console.log("🏁 Inicializando Pipeline do Compilador Cluaupp V2...");
143
-
144
158
  const mapper = new RojoMapper(path.resolve(options.rojo), path.dirname(inputPath));
145
159
  await mapper.load();
146
160
 
147
161
  const inputs = (await fs.stat(inputPath)).isDirectory()
148
- ? collectCpp(inputPath)
162
+ ? collectSources(inputPath)
149
163
  : [inputPath];
150
164
 
151
165
  if (inputs.length === 0) {
152
- console.error("no .cpp/.h/.hpp files in", inputPath);
166
+ console.error("no .clpp/.clp/.clh files in", inputPath);
153
167
  process.exit(1);
154
168
  }
155
169
 
@@ -184,7 +198,7 @@ async function buildInput(options: {
184
198
  console.log("\nRelatório de Análise Estática do Luau:\n", analysisReport);
185
199
  }
186
200
  }
187
- console.log(`Transpilação concluída! ${file} → ${dest}`);
201
+ console.log(`Compiled ${file} → ${dest}`);
188
202
  }
189
203
  }
190
204
 
@@ -192,7 +206,7 @@ export function build(root: string, options: BuildOptions = {}): BuildResult {
192
206
  return buildProject(root, options);
193
207
  }
194
208
 
195
- export { watch, init, program };
209
+ export { watch, init, program, hasClpp };
196
210
 
197
211
  export function dispatch(args: string[]): Promise<Command> {
198
212
  return program.parseAsync(["node", "cluaupp", ...args]);
@@ -0,0 +1,39 @@
1
+ /** Stable CL++ ↔ Cluaupp contract. Cluaupp invokes the `clpp` binary. */
2
+
3
+ export interface CompileRequest {
4
+ source: string;
5
+ fileName: string;
6
+ strict?: boolean;
7
+ cwd?: string;
8
+ }
9
+
10
+ export interface CompileArtifact {
11
+ ok: boolean;
12
+ luau: string;
13
+ fileName: string;
14
+ outputHint: string;
15
+ scriptKind: "server" | "client" | "plugin" | null;
16
+ isScript: boolean;
17
+ isHeader: boolean;
18
+ rojoClass: "Script" | "LocalScript" | "ModuleScript";
19
+ libraries: string[];
20
+ error?: string;
21
+ }
22
+
23
+ export interface LanguageManifest {
24
+ name: "CL++";
25
+ id: "clpp";
26
+ version: string;
27
+ extensions: string[];
28
+ tags: Array<{
29
+ pattern: string;
30
+ scriptKind: string;
31
+ rojoClass: string;
32
+ }>;
33
+ builtins: string[];
34
+ operators: Array<{ clpp: string; luau: string; meaning: string }>;
35
+ io: Array<{ clpp: string; luau: string }>;
36
+ }
37
+
38
+ export const CLPP_INSTALL_HINT =
39
+ "Install CL++ and put `clpp` on PATH: https://github.com/KartzRbx/CLPP (`cargo install --path .` then `clpp install`). Override with CLPP_PATH.";
@@ -0,0 +1,18 @@
1
+ export type { CompileArtifact, CompileRequest, LanguageManifest } from "./contract.js";
2
+ export { CLPP_INSTALL_HINT } from "./contract.js";
3
+ export { compileViaClpp, clppManifest, clppVersion, hasClpp, resolveClppBinary } from "./runner.js";
4
+ export { clppLuauToGame, shouldSkipInit } from "./postprocess.js";
5
+ export {
6
+ SOURCE_EXTS,
7
+ collectSources,
8
+ emitKind,
9
+ fileStem,
10
+ isEngineStub,
11
+ isHeaderFile,
12
+ isImplFile,
13
+ isSourceFile,
14
+ isTaggedScript,
15
+ scriptKind,
16
+ siblingHeader,
17
+ toLuauPath,
18
+ } from "./paths.js";
@@ -0,0 +1,117 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const SOURCE_EXTS = [".clpp", ".clp", ".clh"];
5
+
6
+ export function isSourceFile(fileName: string): boolean {
7
+ return SOURCE_EXTS.includes(path.extname(fileName).toLowerCase());
8
+ }
9
+
10
+ export function isEngineStub(filePath: string): boolean {
11
+ const normalized = filePath.replace(/\\/g, "/").toLowerCase();
12
+ return (
13
+ normalized.includes("/include/cluaupp/") ||
14
+ normalized.includes("/include/clpp/") ||
15
+ normalized.includes("/include/cluau/") ||
16
+ normalized.includes("/stdlib/") ||
17
+ normalized.endsWith("/roblox.hpp") ||
18
+ normalized.endsWith("/roblox.h") ||
19
+ normalized.endsWith("/roblox.clh")
20
+ );
21
+ }
22
+
23
+ export function isHeaderFile(fileName: string): boolean {
24
+ return path.extname(fileName).toLowerCase() === ".clh";
25
+ }
26
+
27
+ export function isImplFile(fileName: string): boolean {
28
+ const ext = path.extname(fileName).toLowerCase();
29
+ return ext === ".clp" || ext === ".clpp";
30
+ }
31
+
32
+ export function toLuauPath(filePath: string): string {
33
+ return String(filePath).replace(/\.(clpp|clp|clh)$/i, ".luau");
34
+ }
35
+
36
+ export function fileStem(fileName: string): string {
37
+ let name = path.basename(fileName);
38
+ const lower = name.toLowerCase();
39
+ for (const ext of [".clpp", ".clp", ".clh"]) {
40
+ if (lower.endsWith(ext)) {
41
+ name = name.slice(0, -ext.length);
42
+ break;
43
+ }
44
+ }
45
+ const tagged = name.toLowerCase();
46
+ for (const tag of [".legacy.server", ".legacy.client", ".legacy", ".server", ".client", ".plugin"]) {
47
+ if (tagged.endsWith(tag)) {
48
+ name = name.slice(0, -tag.length);
49
+ break;
50
+ }
51
+ }
52
+ return name;
53
+ }
54
+
55
+ export function scriptKind(fileName: string): "server" | "client" | "plugin" | "legacy" | "module" | "header" {
56
+ const name = path.basename(fileName).toLowerCase();
57
+ if (name.endsWith(".clh")) {
58
+ return "header";
59
+ }
60
+ if (name.includes(".legacy.")) {
61
+ return "legacy";
62
+ }
63
+ if (name.includes(".server.")) {
64
+ return "server";
65
+ }
66
+ if (name.includes(".client.")) {
67
+ return "client";
68
+ }
69
+ if (name.includes(".plugin.")) {
70
+ return "plugin";
71
+ }
72
+ return "module";
73
+ }
74
+
75
+ export function emitKind(fileName: string): "flat" | "module" | "legacy" {
76
+ const kind = scriptKind(fileName);
77
+ if (kind === "legacy") {
78
+ return "legacy";
79
+ }
80
+ if (kind === "module" || kind === "header") {
81
+ return "module";
82
+ }
83
+ return "flat";
84
+ }
85
+
86
+ export function isTaggedScript(fileName: string): boolean {
87
+ const kind = scriptKind(fileName);
88
+ return kind === "server" || kind === "client" || kind === "plugin" || kind === "legacy";
89
+ }
90
+
91
+ export function siblingHeader(implPath: string | null | undefined): string | null {
92
+ if (!implPath || isHeaderFile(implPath)) {
93
+ return null;
94
+ }
95
+ const dir = path.dirname(implPath);
96
+ const stem = fileStem(implPath);
97
+ const candidate = path.join(dir, `${stem}.clh`);
98
+ if (fs.existsSync(candidate)) {
99
+ return candidate;
100
+ }
101
+ return null;
102
+ }
103
+
104
+ export function collectSources(dir: string, files: string[] = []): string[] {
105
+ if (!fs.existsSync(dir)) {
106
+ return files;
107
+ }
108
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
109
+ const full = path.join(dir, entry.name);
110
+ if (entry.isDirectory()) {
111
+ collectSources(full, files);
112
+ } else if (isSourceFile(entry.name)) {
113
+ files.push(full);
114
+ }
115
+ }
116
+ return files;
117
+ }
@@ -0,0 +1,93 @@
1
+ import { insertPreamble, MODULES, requireCluauppLib } from "../libs.js";
2
+ import type { CompileArtifact } from "./contract.js";
3
+ import { isTaggedScript } from "./paths.js";
4
+
5
+ export type PostprocessOptions = {
6
+ relativeName: string;
7
+ outName: string;
8
+ srcDir?: string;
9
+ rootDir?: string;
10
+ strict?: boolean;
11
+ skipInit?: boolean;
12
+ };
13
+
14
+ function insertAfterHeader(luau: string, block: string): string {
15
+ const match = String(luau).match(/^(?:--[^\n]*\n)+/);
16
+ if (!match) {
17
+ return `${block}\n${luau}`;
18
+ }
19
+ const insertAt = match[0].length;
20
+ const rest = luau.slice(insertAt).replace(/^\n*/, "\n");
21
+ return `${luau.slice(0, insertAt)}\n${block}\n${rest}`;
22
+ }
23
+
24
+ function rewriteClppLibs(luau: string): string {
25
+ return String(luau).replace(/require\(\s*ClppLibs\.([A-Za-z0-9_]+)\s*\)/g, (_all, name: string) => {
26
+ return requireCluauppLib(name);
27
+ });
28
+ }
29
+
30
+ function ensureReplicatedStorage(luau: string): string {
31
+ if (!luau.includes("CluauppLibs")) {
32
+ return luau;
33
+ }
34
+ if (/GetService\(\s*"ReplicatedStorage"\s*\)/.test(luau)) {
35
+ return luau;
36
+ }
37
+ return insertAfterHeader(luau, 'const ReplicatedStorage = game:GetService("ReplicatedStorage")');
38
+ }
39
+
40
+ function stripInitCall(luau: string): string {
41
+ return String(luau).replace(/\ninit\(\)\s*\n?$/m, "\n");
42
+ }
43
+
44
+ function ensureStrict(luau: string, strict?: boolean): string {
45
+ if (!strict || /^\s*--!strict\b/m.test(luau)) {
46
+ return luau;
47
+ }
48
+ if (luau.startsWith("--")) {
49
+ return `--!strict\n${luau}`;
50
+ }
51
+ return `--!strict\n${luau}`;
52
+ }
53
+
54
+ function librarySpecs(names: string[] | undefined) {
55
+ const specs: Array<{ file: string; bind: string }> = [];
56
+ const seen = new Set<string>();
57
+ for (const name of names || []) {
58
+ const spec = (MODULES as Record<string, { file: string; bind: string }>)[name];
59
+ if (!spec || seen.has(spec.bind)) {
60
+ continue;
61
+ }
62
+ seen.add(spec.bind);
63
+ specs.push(spec);
64
+ }
65
+ return specs;
66
+ }
67
+
68
+ function missingLibraries(luau: string, names: string[] | undefined) {
69
+ return librarySpecs(names).filter((spec) => {
70
+ return !luau.includes(`CluauppLibs.${spec.file}`) && !new RegExp(`\\bconst ${spec.bind}\\b`).test(luau);
71
+ });
72
+ }
73
+
74
+ export function shouldSkipInit(fileName: string, hasSiblingHeader: boolean): boolean {
75
+ if (!hasSiblingHeader) {
76
+ return false;
77
+ }
78
+ return !isTaggedScript(fileName);
79
+ }
80
+
81
+ export function clppLuauToGame(artifact: CompileArtifact, options: PostprocessOptions): string {
82
+ let luau = rewriteClppLibs(artifact.luau || "");
83
+ luau = ensureReplicatedStorage(luau);
84
+ if (options.skipInit) {
85
+ luau = stripInitCall(luau);
86
+ }
87
+ luau = ensureStrict(luau, options.strict);
88
+ const missing = missingLibraries(luau, artifact.libraries);
89
+ if (missing.length > 0) {
90
+ luau = insertPreamble(luau, [], missing, options.outName);
91
+ }
92
+ return luau;
93
+ }
@@ -0,0 +1,102 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { CLPP_INSTALL_HINT, type CompileArtifact, type CompileRequest, type LanguageManifest } from "./contract.js";
3
+
4
+ const MISSING_CLPP = `cluaupp: clpp not found. ${CLPP_INSTALL_HINT}`;
5
+
6
+ function whichClpp(): string | null {
7
+ if (process.env.CLPP_PATH) {
8
+ return process.env.CLPP_PATH;
9
+ }
10
+ const finder = process.platform === "win32" ? "where" : "which";
11
+ const found = spawnSync(finder, ["clpp"], { encoding: "utf8", windowsHide: true });
12
+ if (found.status !== 0) {
13
+ return null;
14
+ }
15
+ const line = String(found.stdout || "")
16
+ .split(/\r?\n/)
17
+ .map((item) => item.trim())
18
+ .find((item) => item.length > 0);
19
+ return line || "clpp";
20
+ }
21
+
22
+ export function hasClpp(): boolean {
23
+ return Boolean(whichClpp());
24
+ }
25
+
26
+ export function resolveClppBinary(): string {
27
+ const bin = whichClpp();
28
+ if (!bin) {
29
+ throw new Error(MISSING_CLPP);
30
+ }
31
+ return bin;
32
+ }
33
+
34
+ function parseArtifact(stdout: string, fileName: string, fallbackError: string): CompileArtifact {
35
+ const text = String(stdout || "").trim();
36
+ try {
37
+ const parsed = JSON.parse(text) as CompileArtifact;
38
+ if (parsed && typeof parsed === "object") {
39
+ return parsed;
40
+ }
41
+ } catch {
42
+ // fall through
43
+ }
44
+ return {
45
+ ok: false,
46
+ luau: "",
47
+ fileName,
48
+ outputHint: "",
49
+ scriptKind: null,
50
+ isScript: false,
51
+ isHeader: false,
52
+ rojoClass: "ModuleScript",
53
+ libraries: [],
54
+ error: fallbackError || "clpp returned invalid JSON",
55
+ };
56
+ }
57
+
58
+ export function compileViaClpp(request: CompileRequest): CompileArtifact {
59
+ const bin = resolveClppBinary();
60
+ const payload = JSON.stringify({
61
+ source: request.source,
62
+ fileName: request.fileName,
63
+ strict: request.strict,
64
+ });
65
+ const result = spawnSync(bin, ["api", "compile"], {
66
+ input: payload,
67
+ encoding: "utf8",
68
+ cwd: request.cwd,
69
+ maxBuffer: 16 * 1024 * 1024,
70
+ windowsHide: true,
71
+ });
72
+ const stderr = String(result.stderr || "").trim();
73
+ const artifact = parseArtifact(result.stdout, request.fileName, stderr || result.error?.message || "clpp api compile failed");
74
+ if (!artifact.ok) {
75
+ throw new Error(artifact.error || stderr || `clpp failed (${result.status})`);
76
+ }
77
+ return artifact;
78
+ }
79
+
80
+ export function clppManifest(): LanguageManifest {
81
+ const bin = resolveClppBinary();
82
+ const result = spawnSync(bin, ["api", "manifest"], {
83
+ encoding: "utf8",
84
+ windowsHide: true,
85
+ });
86
+ if (result.status !== 0) {
87
+ throw new Error(String(result.stderr || result.error?.message || "clpp api manifest failed"));
88
+ }
89
+ return JSON.parse(result.stdout) as LanguageManifest;
90
+ }
91
+
92
+ export function clppVersion(): string | null {
93
+ const bin = whichClpp();
94
+ if (!bin) {
95
+ return null;
96
+ }
97
+ const result = spawnSync(bin, ["--version"], { encoding: "utf8", windowsHide: true });
98
+ if (result.status !== 0) {
99
+ return null;
100
+ }
101
+ return String(result.stdout || "").trim() || null;
102
+ }