cluaupp 1.5.2 → 2.0.0-beta.2

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 (210) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +29 -41
  3. package/api/generated/bench-latest.json +43 -21
  4. package/api/generated/lsp-index.json +1 -1
  5. package/api/manifests/roblox-target.manifest.json +5 -5
  6. package/api/normalized/roblox-target.profile.json +8729 -267
  7. package/api/roblox/luau-builtins.d.luau +185 -0
  8. package/api/roblox/surface.json +1 -0
  9. package/api/roblox-api.lock.json +11 -2
  10. package/bin/cluaupp.js +38 -0
  11. package/editors/vscode/README.md +1 -1
  12. package/editors/vscode/package.json +50 -15
  13. package/editors/vscode/snippets/flare.json +1 -1
  14. package/editors/vscode/snippets/hive.json +1 -1
  15. package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
  16. package/examples/README.md +9 -9
  17. package/examples/game/cluaupp.config.json +2 -2
  18. package/examples/game/default.project.json +4 -15
  19. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  20. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  21. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  22. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  23. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  24. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  25. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  26. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  27. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  28. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  29. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  30. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  31. package/generated/api/build-profile.js +1 -1
  32. package/generated/api/loader.js +2 -2
  33. package/generated/api/surface/build.js +328 -0
  34. package/generated/api/surface/luau-defs.js +595 -0
  35. package/generated/api/surface/model.js +8 -0
  36. package/generated/api-cli.js +2 -2
  37. package/generated/cli.js +18 -17
  38. package/generated/clpp/contract.js +6 -3
  39. package/generated/clpp/handshake.js +36 -0
  40. package/generated/clpp/modules.js +20 -9
  41. package/generated/clpp/runner.js +62 -31
  42. package/generated/compile.js +18 -1
  43. package/generated/compiler/ast.js +3 -0
  44. package/generated/compiler/checker/calls.js +999 -0
  45. package/generated/compiler/checker/checker.js +1215 -0
  46. package/generated/compiler/checker/exprs.js +791 -0
  47. package/generated/compiler/checker/flow.js +168 -0
  48. package/generated/compiler/checker/members.js +763 -0
  49. package/generated/compiler/checker/statements.js +792 -0
  50. package/generated/compiler/checker/symbols.js +90 -0
  51. package/generated/compiler/checker/typeops.js +348 -0
  52. package/generated/compiler/checker/yields.js +115 -0
  53. package/generated/compiler/diagnostics.js +138 -0
  54. package/generated/compiler/emit/emitter.js +2356 -0
  55. package/generated/compiler/emit/luau-types.js +137 -0
  56. package/generated/compiler/emit/names.js +130 -0
  57. package/generated/compiler/emit/runtime.js +432 -0
  58. package/generated/compiler/lexer.js +403 -0
  59. package/generated/compiler/parser.js +1881 -0
  60. package/generated/compiler/project.js +332 -0
  61. package/generated/compiler/roblox/api.js +388 -0
  62. package/generated/compiler/roblox/context.js +100 -0
  63. package/generated/compiler/source.js +59 -0
  64. package/generated/compiler/stdlib.js +232 -0
  65. package/generated/compiler/types.js +250 -0
  66. package/generated/intellisense.js +1 -15
  67. package/generated/lsp.js +1 -1
  68. package/generated/target/build-cache.js +4 -4
  69. package/generated/target/datamodel-generate.js +12 -0
  70. package/generated/target/gen-rojo-tree.js +15 -0
  71. package/generated/target/source-map.js +61 -8
  72. package/generated/target/studio-bridge.js +1 -0
  73. package/generated/utils/project.js +104 -31
  74. package/include/README.md +5 -0
  75. package/native/bin/.gitkeep +1 -0
  76. package/package.json +17 -6
  77. package/plugins/CluauppNav/Plugin.luau +28 -4
  78. package/rust/cluaupp/Cargo.lock +795 -0
  79. package/rust/cluaupp/Cargo.toml +17 -0
  80. package/rust/cluaupp/src/main.rs +717 -0
  81. package/scripts/postinstall-native.js +56 -0
  82. package/skills/README.md +3 -3
  83. package/skills/clpp-hive-flare/SKILL.md +30 -26
  84. package/skills/clpp-language/SKILL.md +67 -74
  85. package/skills/clpp-language/compiler.md +24 -36
  86. package/skills/clpp-style/SKILL.md +31 -44
  87. package/src/api/build-profile.ts +1 -1
  88. package/src/api/loader.ts +2 -2
  89. package/src/api/surface/build.ts +374 -0
  90. package/src/api/surface/luau-defs.ts +635 -0
  91. package/src/api/surface/model.ts +96 -0
  92. package/src/api-cli.ts +2 -2
  93. package/src/cli.ts +19 -18
  94. package/src/clpp/contract.ts +20 -3
  95. package/src/clpp/handshake.ts +25 -0
  96. package/src/clpp/modules.ts +17 -10
  97. package/src/clpp/runner.ts +52 -34
  98. package/src/compile.ts +20 -2
  99. package/src/compiler/ast.ts +336 -0
  100. package/src/compiler/checker/calls.ts +1025 -0
  101. package/src/compiler/checker/checker.ts +1352 -0
  102. package/src/compiler/checker/exprs.ts +805 -0
  103. package/src/compiler/checker/flow.ts +174 -0
  104. package/src/compiler/checker/members.ts +793 -0
  105. package/src/compiler/checker/statements.ts +802 -0
  106. package/src/compiler/checker/symbols.ts +219 -0
  107. package/src/compiler/checker/typeops.ts +355 -0
  108. package/src/compiler/checker/yields.ts +120 -0
  109. package/src/compiler/diagnostics.ts +163 -0
  110. package/src/compiler/emit/emitter.ts +2486 -0
  111. package/src/compiler/emit/luau-types.ts +143 -0
  112. package/src/compiler/emit/names.ts +130 -0
  113. package/src/compiler/emit/runtime.ts +438 -0
  114. package/src/compiler/lexer.ts +444 -0
  115. package/src/compiler/parser.ts +1943 -0
  116. package/src/compiler/project.ts +354 -0
  117. package/src/compiler/roblox/api.ts +408 -0
  118. package/src/compiler/roblox/context.ts +108 -0
  119. package/src/compiler/source.ts +67 -0
  120. package/src/compiler/stdlib.ts +256 -0
  121. package/src/compiler/types.ts +313 -0
  122. package/src/intellisense.ts +1 -13
  123. package/src/lsp.ts +1 -1
  124. package/src/target/build-cache.ts +5 -8
  125. package/src/target/datamodel-generate.ts +12 -1
  126. package/src/target/gen-rojo-tree.ts +15 -0
  127. package/src/target/source-map.ts +65 -27
  128. package/src/target/studio-bridge.ts +3 -0
  129. package/src/types.ts +3 -0
  130. package/src/utils/project.ts +99 -37
  131. package/templates/README.md +7 -0
  132. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  133. package/templates/{game → game-modern}/package.json +1 -2
  134. package/templates/game-modern/rokit.toml +2 -0
  135. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  136. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  137. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  138. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  139. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  140. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  141. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  142. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  143. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  144. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  145. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  146. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  147. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  148. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  149. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  150. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  151. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  152. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  153. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  154. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  155. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  156. package/examples/game/src/Client/Main.client.clpp +0 -4
  157. package/examples/game/src/Declarations/Net.flare +0 -5
  158. package/examples/game/src/Include/GameTypes.clh +0 -40
  159. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  160. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  161. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  162. package/examples/game/src/Server/Main.server.clpp +0 -4
  163. package/templates/game/.clangd +0 -25
  164. package/templates/game/.vscode/extensions.json +0 -8
  165. package/templates/game/.vscode/settings.json +0 -17
  166. package/templates/game/Packages/.gitkeep +0 -0
  167. package/templates/game/compile_flags.txt +0 -6
  168. package/templates/game/default.project.json +0 -41
  169. package/templates/game/include/clpp/datatypes.clh +0 -575
  170. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  171. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  172. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  173. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  174. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  175. package/templates/game/include/clpp/libs/coil.clh +0 -37
  176. package/templates/game/include/clpp/libs/crest.clh +0 -22
  177. package/templates/game/include/clpp/libs/echo.clh +0 -52
  178. package/templates/game/include/clpp/libs/ember.clh +0 -17
  179. package/templates/game/include/clpp/libs/flare.clh +0 -65
  180. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  181. package/templates/game/include/clpp/libs/guide.clh +0 -42
  182. package/templates/game/include/clpp/libs/helm.clh +0 -31
  183. package/templates/game/include/clpp/libs/hive.clh +0 -20
  184. package/templates/game/include/clpp/libs/keep.clh +0 -158
  185. package/templates/game/include/clpp/libs/lens.clh +0 -24
  186. package/templates/game/include/clpp/libs/mint.clh +0 -28
  187. package/templates/game/include/clpp/libs/net.clh +0 -31
  188. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  189. package/templates/game/include/clpp/libs/pin.clh +0 -28
  190. package/templates/game/include/clpp/libs/promise.clh +0 -24
  191. package/templates/game/include/clpp/libs/roster.clh +0 -123
  192. package/templates/game/include/clpp/libs/shift.clh +0 -22
  193. package/templates/game/include/clpp/libs/spark.clh +0 -28
  194. package/templates/game/include/clpp/libs/stage.clh +0 -23
  195. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  196. package/templates/game/include/clpp/libs/trace.clh +0 -18
  197. package/templates/game/include/clpp/libs/ward.clh +0 -19
  198. package/templates/game/include/clpp/libs.clh +0 -31
  199. package/templates/game/include/clpp/roblox.clh +0 -41
  200. package/templates/game/rokit.toml +0 -5
  201. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  202. package/templates/game/src/Client/Main.client.clpp +0 -4
  203. package/templates/game/src/Declarations/Net.flare +0 -5
  204. package/templates/game/src/Include/GameTypes.clh +0 -40
  205. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  206. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  207. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  208. package/templates/game/src/Server/Main.server.clpp +0 -4
  209. package/templates/game/stylua.toml +0 -9
  210. package/templates/game/wally.toml +0 -12
@@ -6,6 +6,7 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import type { DatamodelNode, DatamodelProfile } from "./datamodel.js";
8
8
  import { buildDatamodelProfile } from "./datamodel.js";
9
+ import { collectSources } from "../clpp/paths.js";
9
10
 
10
11
  function safeIdent(name: string): string {
11
12
  const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_");
@@ -58,7 +59,7 @@ export function profileWithoutIndex(profile: DatamodelProfile): Omit<DatamodelPr
58
59
  export function writeDatamodelArtifacts(
59
60
  projectRoot: string,
60
61
  opts?: { projectJson?: string; outDir?: string; headerPath?: string },
61
- ): { profile: DatamodelProfile; profilePath: string; headerPath: string } | null {
62
+ ): { profile: DatamodelProfile; profilePath: string; headerPath: string | null } | null {
62
63
  const profile = buildDatamodelProfile(projectRoot, opts?.projectJson);
63
64
  if (!profile) {
64
65
  return null;
@@ -68,6 +69,16 @@ export function writeDatamodelArtifacts(
68
69
  const profilePath = path.join(outDir, "datamodel-profile.json");
69
70
  fs.writeFileSync(profilePath, JSON.stringify(profileWithoutIndex(profile), null, "\t") + "\n", "utf8");
70
71
 
72
+ let sourceRoot = "src";
73
+ const configPath = path.join(projectRoot, "cluaupp.config.json");
74
+ if (fs.existsSync(configPath)) {
75
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
76
+ if (typeof config.rootDir === "string") sourceRoot = config.rootDir;
77
+ }
78
+ const sources = collectSources(path.resolve(projectRoot, sourceRoot));
79
+ const modern = sources.some(file => /\.clp$/i.test(file)) && !sources.some(file => /\.clpp$/i.test(file));
80
+ if (modern && !opts?.headerPath) return { profile, profilePath, headerPath: null };
81
+
71
82
  const headerPath =
72
83
  opts?.headerPath ||
73
84
  path.join(projectRoot, "include", "clpp", "generated", "datamodel.clh");
@@ -101,6 +101,21 @@ export function buildRojoTree(root: string, rootDir: string, outDir: string): Ro
101
101
  const sourceRoot = fs.existsSync(outRoot) ? outRoot : srcRoot;
102
102
  const tree = baseTree();
103
103
  const relRoot = path.relative(root, sourceRoot).replace(/\\/g, "/");
104
+ const projectSources = walkFiles(srcRoot);
105
+ const modernOnly = projectSources.some((file) => /\.clp$/i.test(file)) && !projectSources.some((file) => /\.(clpp|clh)$/i.test(file));
106
+ if (modernOnly) {
107
+ for (const service of ["ServerScriptService", "ServerStorage", "ReplicatedStorage", "ReplicatedFirst", "Workspace", "StarterGui", "StarterPack", "Lighting", "SoundService", "Teams"]) {
108
+ if (projectSources.some((file) => path.relative(srcRoot, file).replace(/\\/g, "/").startsWith(`${service}/`))) {
109
+ withPath(ensureNode(tree, [service]), `${outDir}/${service}`);
110
+ }
111
+ }
112
+ for (const nested of ["StarterPlayerScripts", "StarterCharacterScripts"]) {
113
+ if (projectSources.some((file) => path.relative(srcRoot, file).replace(/\\/g, "/").startsWith(`StarterPlayer/${nested}/`))) {
114
+ withPath(ensureNode(tree, ["StarterPlayer", nested]), `${outDir}/StarterPlayer/${nested}`);
115
+ }
116
+ }
117
+ return tree;
118
+ }
104
119
 
105
120
  withPath(ensureNode(tree, ["ServerScriptService"]), `${relRoot}/Server`);
106
121
  withPath(ensureNode(tree, ["ServerScriptService", "Parallel"]), `${relRoot}/Parallel`);
@@ -1,36 +1,74 @@
1
- /** Minimal source map: CL++ path ↔ emitted Luau (1:1 file mapping for v1). */
1
+ /** Compiler coordinates are retained only for lines that survive host transformations. */
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import type { SourceMapLine } from "../clpp/contract.js";
4
5
 
5
6
  export interface CluauppSourceMap {
6
- version: 1;
7
- file: string;
8
- sourceRoot: string;
9
- sources: string[];
10
- mappings: string;
11
- cluaupp: {
12
- source: string;
13
- generated: string;
14
- };
7
+ version: 1;
8
+ file: string;
9
+ sourceRoot: string;
10
+ sources: string[];
11
+ cluaupp: { source: string; generated: string; lines: SourceMapLine[] };
12
+ }
13
+
14
+ /** LCS alignment: inserted headers and removed lines never become fake source locations. */
15
+ export function remapSourceLines(original: string, generated: string, mappings: SourceMapLine[] = []): SourceMapLine[] {
16
+ const a = original.split(/\r?\n/);
17
+ const b = generated.split(/\r?\n/);
18
+ if (original === generated) return mappings.map(m => ({...m}));
19
+ // Avoid unbounded memory for generated bundles. An absent map is safer than a wrong one.
20
+ if (a.length * b.length > 4_000_000) return [];
21
+ const widths = b.length + 1;
22
+ const dp = new Uint32Array((a.length + 1) * widths);
23
+ for (let i = a.length - 1; i >= 0; i--) for (let j = b.length - 1; j >= 0; j--) {
24
+ dp[i * widths + j] = a[i].trim() === b[j].trim() ? 1 + dp[(i+1)*widths+j+1] : Math.max(dp[(i+1)*widths+j], dp[i*widths+j+1]);
25
+ }
26
+ const translated = new Map<number, number>();
27
+ let i = 0, j = 0;
28
+ while (i < a.length && j < b.length) {
29
+ if (a[i].trim() === b[j].trim()) {translated.set(i+1, j+1); i++; j++;}
30
+ else if (dp[(i+1)*widths+j] >= dp[i*widths+j+1]) i++;
31
+ else j++;
32
+ }
33
+ return mappings.flatMap(m => {
34
+ const line = translated.get(m.luauLine);
35
+ if (!line || !Number.isInteger(m.clppLine) || m.clppLine < 1) return [];
36
+ // Formatting can move columns; do not invent a column in that case.
37
+ const exact = a[m.luauLine-1] === b[line-1];
38
+ const {luauColumn, ...rest} = m;
39
+ return [{...rest, luauLine: line, ...(exact && luauColumn ? {luauColumn} : {})}];
40
+ });
41
+ }
42
+
43
+ export function embedSourceMap(luau: string, lines: SourceMapLine[]): { contents: string; lines: SourceMapLine[] } {
44
+ if (!lines.length) return {contents: luau, lines: []};
45
+ const shifted = lines.map(m => ({...m, luauLine: m.luauLine+1}));
46
+ // Preserve Luau directives at the start of the file.
47
+ const parts = luau.split(/\r?\n/);
48
+ let index = 0;
49
+ while (index < parts.length && parts[index].startsWith("--!")) index++;
50
+ const adjusted = shifted.map((m, i) => ({...m, luauLine: lines[i].luauLine > index ? m.luauLine : lines[i].luauLine}));
51
+ parts.splice(index, 0, "-- cluaupp-map: " + JSON.stringify(adjusted));
52
+ return {contents: parts.join("\n"), lines: adjusted};
15
53
  }
16
54
 
17
55
  export function writeSourceMap(opts: {
18
- outLuauPath: string;
19
- sourceClppRel: string;
20
- projectRoot: string;
56
+ outLuauPath: string;
57
+ sourceClppRel: string;
58
+ projectRoot: string;
59
+ lines?: SourceMapLine[];
21
60
  }): string {
22
- const map: CluauppSourceMap = {
23
- version: 1,
24
- file: path.basename(opts.outLuauPath),
25
- sourceRoot: opts.projectRoot.replace(/\\/g, "/"),
26
- sources: [opts.sourceClppRel.replace(/\\/g, "/")],
27
- mappings: "AAAA", // identity stub — refine when clpp emits VLQ maps
28
- cluaupp: {
29
- source: opts.sourceClppRel.replace(/\\/g, "/"),
30
- generated: path.relative(opts.projectRoot, opts.outLuauPath).replace(/\\/g, "/"),
31
- },
32
- };
33
- const mapPath = opts.outLuauPath + ".map.json";
34
- fs.writeFileSync(mapPath, JSON.stringify(map, null, "\t") + "\n", "utf8");
35
- return mapPath;
61
+ const lines = opts.lines || [];
62
+ const source = opts.sourceClppRel.replace(/\\/g, "/");
63
+ const map: CluauppSourceMap = {
64
+ version: 1,
65
+ file: path.basename(opts.outLuauPath),
66
+ sourceRoot: opts.projectRoot.replace(/\\/g, "/"),
67
+ sources: [...new Set([source, ...lines.map(m => m.file)])],
68
+ cluaupp: { source, generated: path.relative(opts.projectRoot, opts.outLuauPath).replace(/\\/g, "/"), lines },
69
+ };
70
+ const mapPath = opts.outLuauPath + ".map.json";
71
+ const contents = JSON.stringify(map, null, "\t") + "\n";
72
+ if (!fs.existsSync(mapPath) || fs.readFileSync(mapPath, "utf8") !== contents) fs.writeFileSync(mapPath, contents, "utf8");
73
+ return mapPath;
36
74
  }
@@ -16,6 +16,7 @@ export interface BridgeEvent {
16
16
  type: "open" | "select" | "error" | "ping";
17
17
  path?: string;
18
18
  line?: number;
19
+ column?: number;
19
20
  message?: string;
20
21
  instancePath?: string;
21
22
  }
@@ -86,6 +87,7 @@ export function startStudioBridge(opts: {
86
87
  const data = JSON.parse(body || "{}") as {
87
88
  path?: string;
88
89
  line?: number;
90
+ column?: number;
89
91
  instancePath?: string;
90
92
  type?: BridgeEvent["type"];
91
93
  message?: string;
@@ -106,6 +108,7 @@ export function startStudioBridge(opts: {
106
108
  type: data.type || "open",
107
109
  path: rel || undefined,
108
110
  line: data.line,
111
+ column: data.column,
109
112
  instancePath: data.instancePath,
110
113
  message: data.message,
111
114
  });
package/src/types.ts CHANGED
@@ -78,6 +78,9 @@ export interface BuildResult {
78
78
  export interface CompiledArtifact {
79
79
  name: string;
80
80
  contents: string;
81
+ /** Compiler coordinates before host output transforms. */
82
+ sourceMap?: import("./clpp/contract.js").SourceMapLine[];
83
+ generatedSource?: string;
81
84
  }
82
85
 
83
86
  export interface CompileServiceResult {
@@ -1,17 +1,19 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import type { BuildOptions, BuildResult, ProjectConfig } from "../types.js";
4
- import { pkg } from "../package-info.js";
4
+ import { pkg, projectRoot as toolRoot } from "../package-info.js";
5
+ import { clppCacheIdentity } from "../clpp/runner.js";
6
+ import type { CompiledArtifact } from "../types.js";
5
7
  import { rewriteClppEmit } from "../clpp/postprocess.js";
6
8
  import { collectSources, toLuauPath } from "../clpp/paths.js";
7
9
  import { collectFlareFiles, compileFlareFile } from "../flare/index.js";
8
10
  import { collectNativeSchemaFiles, compileNativeSchemaFile } from "../native/index.js";
9
- import { installEditorExtension, syncEditorSupport } from "../intellisense.js";
11
+ import { syncEditorSupport } from "../intellisense.js";
10
12
  import { ProcessOrchestrator } from "./process-orchestrator.js";
11
13
  import { RojoMapper } from "./rojo-mapper.js";
12
14
  import { transpileSource, transpileSourceAsync } from "../transpile.js";
13
15
  import { assertInitDest, assertInside, isInside, safeProjectSubdir, safeRelPath } from "./safe-paths.js";
14
- import { writeSourceMap } from "../target/source-map.js";
16
+ import { writeSourceMap, remapSourceLines, embedSourceMap } from "../target/source-map.js";
15
17
  import { stampLuauFromClpp } from "../target/luau-stamp.js";
16
18
  import { collectSoaPlansFromProject, writeSoaArtifacts } from "../target/optimizer-soa.js";
17
19
  import { verifyLock } from "../api/index.js";
@@ -28,6 +30,8 @@ import {
28
30
  writeCachedJob,
29
31
  } from "../target/build-cache.js";
30
32
  import { fingerprintSourceWithDeps } from "../clpp/modules.js";
33
+ import { ProjectCompiler } from "../compiler/project.js";
34
+ import { formatDiagnostic } from "../compiler/diagnostics.js";
31
35
 
32
36
  function readProjectConfig(file: string): Partial<ProjectConfig> {
33
37
  const raw = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -334,8 +338,13 @@ function compileOptions(root: string, config: ProjectConfig, file: string, rel:
334
338
  };
335
339
  }
336
340
 
341
+ function needsLegacyHeaders(srcDir: string): boolean {
342
+ const sources = collectSources(srcDir);
343
+ return sources.some(file => /\.clpp$/i.test(file)) || !sources.some(file => /\.clp$/i.test(file));
344
+ }
345
+
337
346
  function compileSchemaJobs(root: string, config: ProjectConfig, srcDir: string) {
338
- const jobs: Array<{ rel: string; files: Array<{ name: string; contents: string }>; stale: string[] }> = [];
347
+ const jobs: Array<{ rel: string; files: CompiledArtifact[]; stale: string[] }> = [];
339
348
  const errors: Array<{ rel: string; prefixes: string[]; message: string }> = [];
340
349
  const flare = compileFlareJobs(root, config, srcDir);
341
350
  jobs.push(...flare.jobs);
@@ -348,7 +357,7 @@ function compileSchemaJobs(root: string, config: ProjectConfig, srcDir: string)
348
357
  if (!isInside(path.resolve(root, config.rootDir), headerDest) && !isInside(srcDir, headerDest)) {
349
358
  throw new Error(`cluaupp schema: skip unsafe header ${emit.headerRel}`);
350
359
  }
351
- writeTextIfChanged(headerDest, emit.header);
360
+ if (needsLegacyHeaders(srcDir)) writeTextIfChanged(headerDest, emit.header);
352
361
  jobs.push({
353
362
  rel,
354
363
  files: [{ name: emit.luauRel, contents: emit.luau }],
@@ -366,7 +375,7 @@ function compileSchemaJobs(root: string, config: ProjectConfig, srcDir: string)
366
375
  }
367
376
 
368
377
  function compileFlareJobs(root: string, config: ProjectConfig, srcDir: string) {
369
- const jobs: Array<{ rel: string; files: Array<{ name: string; contents: string }>; stale: string[] }> = [];
378
+ const jobs: Array<{ rel: string; files: CompiledArtifact[]; stale: string[] }> = [];
370
379
  const errors: Array<{ rel: string; prefixes: string[]; message: string }> = [];
371
380
  for (const file of collectFlareFiles(srcDir)) {
372
381
  const rel = posixRel(srcDir, file);
@@ -376,7 +385,7 @@ function compileFlareJobs(root: string, config: ProjectConfig, srcDir: string) {
376
385
  if (!isInside(path.resolve(root, config.rootDir), headerDest) && !isInside(srcDir, headerDest)) {
377
386
  throw new Error(`cluaupp flare: skip unsafe header ${emit.headerRel}`);
378
387
  }
379
- writeTextIfChanged(headerDest, emit.header);
388
+ if (needsLegacyHeaders(srcDir)) writeTextIfChanged(headerDest, emit.header);
380
389
  jobs.push({
381
390
  rel,
382
391
  files: [{ name: emit.luauRel, contents: emit.luau }],
@@ -389,27 +398,58 @@ function compileFlareJobs(root: string, config: ProjectConfig, srcDir: string) {
389
398
  return { jobs, errors };
390
399
  }
391
400
 
392
- function configFingerprint(config: ProjectConfig): string {
401
+ function configFingerprint(root: string, config: ProjectConfig, rojoPath?: string): string {
402
+ const inputs = [path.resolve(root, rojoPath || "default.project.json"), path.join(root, "cluaupp.platform.json"), path.join(root, "cluaupp.profile.json"), path.join(toolRoot, "api", "normalized", "roblox-target.profile.json"), path.join(toolRoot, "api", "generated", "capability-profile.json"), path.join(toolRoot, "api", "roblox-api.lock.json")];
393
403
  return JSON.stringify({
394
404
  strict: config.strict === true,
395
405
  architecture: config.architecture === true,
396
406
  rootDir: config.rootDir,
397
407
  outDir: config.outDir,
398
408
  v: pkg.version,
409
+ rules: config.rules,
410
+ components: config.components,
411
+ inputs: inputs.map(file => [file, fs.existsSync(file) ? hashText(fs.readFileSync(file, "utf8")) : null]),
399
412
  });
400
413
  }
401
414
 
415
+ function compileClpJobs(root: string, config: ProjectConfig, srcDir: string) {
416
+ const compiler = new ProjectCompiler({ root, srcDir, strict: config.strict === true, version: pkg.version });
417
+ const jobs: Array<{ rel: string; files: CompiledArtifact[]; stale: string[] }> = [];
418
+ const errors: Array<{ rel: string; prefixes: string[]; message: string }> = [];
419
+ for (const result of compiler.compileAll()) {
420
+ if (result.luau === null) {
421
+ const diagnostics = result.diagnostics.filter((d) => d.severity === "error");
422
+ errors.push({
423
+ rel: result.rel,
424
+ prefixes: sourcePrefixes(result.rel),
425
+ message: diagnostics.map((d) => formatDiagnostic(d, result.source)).join("\n") || `${result.rel}: Luau emission failed`,
426
+ });
427
+ continue;
428
+ }
429
+ jobs.push({ rel: result.rel, files: [{ name: result.outRel, contents: result.luau, sourceMap: result.sourceMap, generatedSource: result.luau }], stale: [] });
430
+ }
431
+ return { jobs, errors };
432
+ }
433
+
402
434
  function compileProject(root: string, config: ProjectConfig, mapper: RojoMapper, options: BuildOptions = {}) {
403
435
  const srcDir = path.join(root, config.rootDir);
404
436
  const schema = compileSchemaJobs(root, config, srcDir);
405
437
  const files = collectSources(srcDir);
406
- const jobs = [...schema.jobs];
407
- const errors = [...schema.errors];
438
+ const legacyFiles = files.filter((file) => !file.toLowerCase().endsWith(".clp"));
439
+ const clp = compileClpJobs(root, config, srcDir);
440
+ const jobs = [...schema.jobs, ...clp.jobs];
441
+ const errors = [...schema.errors, ...clp.errors];
408
442
  const incremental = options.incremental !== false;
409
- const cfgKey = configFingerprint(config);
443
+ let cfgKey = configFingerprint(root, config, options.rojo);
444
+ if (legacyFiles.length) {
445
+ try {cfgKey += clppCacheIdentity();} catch (err) {
446
+ for (const file of legacyFiles) {const rel = posixRel(srcDir, file); errors.push({rel, prefixes: sourcePrefixes(rel), message: (err as Error).message});}
447
+ return {files, jobs, errors};
448
+ }
449
+ }
410
450
  let cacheHits = 0;
411
451
 
412
- for (const file of files) {
452
+ for (const file of legacyFiles) {
413
453
  const source = fs.readFileSync(file, "utf8");
414
454
  const rel = posixRel(srcDir, file);
415
455
  const sourceHash = fingerprintSourceWithDeps(source, file, cfgKey, srcDir, hashText);
@@ -438,7 +478,7 @@ function compileProject(root: string, config: ProjectConfig, mapper: RojoMapper,
438
478
  }
439
479
 
440
480
  if (cacheHits > 0) {
441
- console.log(`cluaupp: incremental cache hit ${cacheHits}/${files.length}`);
481
+ console.log(`cluaupp: incremental cache hit ${cacheHits}/${legacyFiles.length}`);
442
482
  }
443
483
  return { files, jobs, errors };
444
484
  }
@@ -447,14 +487,22 @@ async function compileProjectAsync(root: string, config: ProjectConfig, mapper:
447
487
  const srcDir = path.join(root, config.rootDir);
448
488
  const schema = compileSchemaJobs(root, config, srcDir);
449
489
  const files = collectSources(srcDir);
450
- const jobs = [...schema.jobs];
451
- const errors = [...schema.errors];
490
+ const legacyFiles = files.filter((file) => !file.toLowerCase().endsWith(".clp"));
491
+ const clp = compileClpJobs(root, config, srcDir);
492
+ const jobs = [...schema.jobs, ...clp.jobs];
493
+ const errors = [...schema.errors, ...clp.errors];
452
494
  const incremental = options.incremental !== false;
453
- const cfgKey = configFingerprint(config);
495
+ let cfgKey = configFingerprint(root, config, options.rojo);
496
+ if (legacyFiles.length) {
497
+ try {cfgKey += clppCacheIdentity();} catch (err) {
498
+ for (const file of legacyFiles) {const rel = posixRel(srcDir, file); errors.push({rel, prefixes: sourcePrefixes(rel), message: (err as Error).message});}
499
+ return {files, jobs, errors};
500
+ }
501
+ }
454
502
  const concurrency = options.jobs ?? defaultJobCount();
455
503
  let cacheHits = 0;
456
504
 
457
- const compiled = await mapPool(files, concurrency, async (file) => {
505
+ const compiled = await mapPool(legacyFiles, concurrency, async (file) => {
458
506
  const source = fs.readFileSync(file, "utf8");
459
507
  const rel = posixRel(srcDir, file);
460
508
  const sourceHash = fingerprintSourceWithDeps(source, file, cfgKey, srcDir, hashText);
@@ -510,8 +558,8 @@ async function compileProjectAsync(root: string, config: ProjectConfig, mapper:
510
558
  }
511
559
 
512
560
  if (cacheHits > 0) {
513
- console.log(`cluaupp: incremental cache hit ${cacheHits}/${files.length} (jobs=${concurrency})`);
514
- } else if (files.length > 1 && concurrency > 1) {
561
+ console.log(`cluaupp: incremental cache hit ${cacheHits}/${legacyFiles.length} (jobs=${concurrency})`);
562
+ } else if (legacyFiles.length > 1 && concurrency > 1) {
515
563
  console.log(`cluaupp: parallel compile jobs=${concurrency}`);
516
564
  }
517
565
  return { files, jobs, errors };
@@ -541,7 +589,7 @@ function outDest(root: string, config: ProjectConfig, rel: string): string | nul
541
589
  return dest;
542
590
  }
543
591
 
544
- function writeJobs(root: string, config: ProjectConfig, jobs: Array<{ rel: string; files: Array<{ name: string; contents: string }>; stale: string[] }>, format: boolean): Set<string> {
592
+ function writeJobs(root: string, config: ProjectConfig, jobs: Array<{ rel: string; files: CompiledArtifact[]; stale: string[] }>, format: boolean): Set<string> {
545
593
  const written = new Set<string>();
546
594
  for (const job of jobs) {
547
595
  for (const artifact of job.files) {
@@ -553,7 +601,18 @@ function writeJobs(root: string, config: ProjectConfig, jobs: Array<{ rel: strin
553
601
  if (/\.luau$/i.test(dest)) {
554
602
  const srcFile = path.join(root, config.rootDir, job.rel);
555
603
  const clppSource = fs.existsSync(srcFile) ? fs.readFileSync(srcFile, "utf8") : undefined;
556
- contents = stampLuauFromClpp(rewriteClppEmit(contents), job.rel, clppSource);
604
+ contents = stampLuauFromClpp(/\.clp$/i.test(job.rel) ? contents : rewriteClppEmit(contents), posixRel(root, srcFile), clppSource);
605
+ }
606
+ let lines = (artifact.sourceMap || []).map(m => ({...m, file: path.isAbsolute(m.file) ? posixRel(root, m.file) : path.posix.join(config.rootDir.replace(/\\/g, "/"), m.file)}));
607
+ if (format && /\.luau$/i.test(dest)) {
608
+ writeTextIfChanged(dest, contents);
609
+ ProcessOrchestrator.formatWithStyLuaSync(dest);
610
+ contents = fs.readFileSync(dest, "utf8");
611
+ }
612
+ if (/\.luau$/i.test(dest)) {
613
+ lines = remapSourceLines(artifact.generatedSource || artifact.contents, contents, lines);
614
+ const embedded = embedSourceMap(contents, lines);
615
+ contents = embedded.contents; lines = embedded.lines;
557
616
  }
558
617
  const changed = writeTextIfChanged(dest, contents);
559
618
  written.add(resolveKey(dest));
@@ -563,14 +622,12 @@ function writeJobs(root: string, config: ProjectConfig, jobs: Array<{ rel: strin
563
622
  if (/\.luau$/i.test(dest)) {
564
623
  const mapPath = writeSourceMap({
565
624
  outLuauPath: dest,
566
- sourceClppRel: job.rel,
625
+ sourceClppRel: path.posix.join(config.rootDir.replace(/\\/g, "/"), job.rel),
567
626
  projectRoot: root,
627
+ lines,
568
628
  });
569
629
  written.add(resolveKey(mapPath));
570
630
  }
571
- if (format && /\.luau$/i.test(dest)) {
572
- ProcessOrchestrator.formatWithStyLuaSync(dest);
573
- }
574
631
  }
575
632
  for (const stale of job.stale || []) {
576
633
  const dest = outDest(root, config, stale);
@@ -586,13 +643,16 @@ function writeJobs(root: string, config: ProjectConfig, jobs: Array<{ rel: strin
586
643
 
587
644
  function ensureVendor(root: string): void {
588
645
  copyRuntime(root);
589
- copyHeaders(root);
646
+ const config = loadConfig(root);
647
+ if (collectSources(path.join(root, config.rootDir)).some(file => /\.clpp$/i.test(file))) {
648
+ copyHeaders(root);
649
+ }
590
650
  }
591
651
 
592
652
  function finishBuild(
593
653
  root: string,
594
654
  config: ProjectConfig,
595
- compiled: { jobs: Array<{ rel: string; files: Array<{ name: string; contents: string }>; stale: string[] }>; errors: Array<{ rel: string; prefixes: string[]; message: string }> },
655
+ compiled: { jobs: Array<{ rel: string; files: CompiledArtifact[]; stale: string[] }>; errors: Array<{ rel: string; prefixes: string[]; message: string }> },
596
656
  options: BuildOptions,
597
657
  ): BuildResult {
598
658
  const exitOnError = options.exitOnError !== false;
@@ -726,10 +786,15 @@ export async function buildAsync(root: string, options: BuildOptions = {}): Prom
726
786
 
727
787
  export async function init(dest: string): Promise<void> {
728
788
  const target = assertInitDest(dest);
729
- const template = path.join(__dirname, "..", "..", "templates", "game");
730
- const include = path.join(__dirname, "..", "..", "include");
789
+ if (fs.existsSync(path.join(target, "cluaupp.config.json")) || fs.existsSync(path.join(target, "src"))) {
790
+ throw new Error("cluaupp init: project already exists; choose a new folder. Existing .clpp/.clh sources require migration, not another init.");
791
+ }
792
+ const template = path.join(__dirname, "..", "..", "templates", "game-modern");
731
793
  copyDir(template, target);
732
- copyDir(include, path.join(target, "include"));
794
+ const packageFile = path.join(target, "package.json");
795
+ const projectPackage = JSON.parse(fs.readFileSync(packageFile, "utf8"));
796
+ projectPackage.devDependencies = { ...projectPackage.devDependencies, cluaupp: pkg.version };
797
+ fs.writeFileSync(packageFile, JSON.stringify(projectPackage, null, "\t") + "\n");
733
798
  const skills = path.join(__dirname, "..", "..", "skills");
734
799
  if (fs.existsSync(skills)) {
735
800
  copyDir(skills, path.join(target, ".cursor", "skills"));
@@ -738,16 +803,13 @@ export async function init(dest: string): Promise<void> {
738
803
  const initConfig = loadConfig(target);
739
804
  syncRojoTree(target, initConfig);
740
805
  syncEditorSupport(target);
741
- try {
742
- installEditorExtension();
743
- } catch {
744
- // editor homes may be missing in CI
745
- }
806
+ // CL++ 0.10 owns its editor extension; project init only writes associations.
746
807
  console.log("Cluaupp ready in", target);
747
- console.log(" clpp install (CL++ highlighting + IntelliSense)");
808
+ console.log(" CL++ 0.10 editor extension provides syntax highlighting; Cluaupp compiles Roblox .clp files");
748
809
  console.log(" cluaupp intellisense (schema .flare .hive .mint .bloom .helm .shift .axiom)");
749
810
  console.log(" rokit install");
750
- console.log(" cluaupp build");
811
+ console.log(" npm install");
812
+ console.log(" npm run build");
751
813
  console.log(" rojo serve");
752
814
  }
753
815
 
@@ -0,0 +1,7 @@
1
+ # Project templates
2
+
3
+ | Template | Use |
4
+ | --- | --- |
5
+ | [`game-modern/`](game-modern/) | **`cluaupp init`** — CL++ 0.10 Roblox target: `src/`, `.clp`, `link`, top-level scripts |
6
+
7
+ Legacy `.clpp` / `Src/` / `#include` layouts are not scaffolded anymore. For migration, see [migration](https://kartzrbx.github.io/Cluaupp/migration/). Integration tests keep a frozen copy under [`test/fixtures/legacy-clpp-game/`](../test/fixtures/legacy-clpp-game/).
@@ -1,6 +1,6 @@
1
1
  {
2
- "rootDir": "Src",
2
+ "rootDir": "src",
3
3
  "outDir": "out",
4
- "strict": false,
4
+ "strict": true,
5
5
  "architecture": false
6
6
  }
@@ -3,7 +3,6 @@
3
3
  "private": true,
4
4
  "scripts": {
5
5
  "build": "cluaupp build",
6
- "watch": "cluaupp watch",
7
- "dev": "cluaupp watch"
6
+ "watch": "cluaupp watch"
8
7
  }
9
8
  }
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ rojo = "rojo-rbx/rojo@7.7.0"
@@ -0,0 +1 @@
1
+ const GAME_NAME = "My CL++ game";
@@ -0,0 +1,4 @@
1
+ link @clpp.roblox;
2
+ link @clpp.libs.ward as Ward;
3
+
4
+ Ward.Start();
@@ -0,0 +1,8 @@
1
+ link @clpp.roblox;
2
+ link @ReplicatedStorage.Shared.GameInfo as GameInfo;
3
+
4
+ Players players = GetService<Players>();
5
+ post(GameInfo.GAME_NAME .: " server ready");
6
+ players.PlayerAdded ~> func (Player player) {
7
+ post(player.Name .: " joined");
8
+ };
@@ -0,0 +1,4 @@
1
+ link @clpp.roblox;
2
+ link @ReplicatedStorage.Shared.GameInfo as GameInfo;
3
+
4
+ post(GameInfo.GAME_NAME .: " client ready");
@@ -1,29 +0,0 @@
1
- --!nonstrict
2
- -- cluaupp-source: ReplicatedStorage/Shared/Constants/Datas/TemplateData.clh
3
- -- Compiled by CL++ — C++ × Luau
4
-
5
- -- cluaupp: Roster.new / GetService ReplicatedStorage / self.janitor
6
-
7
- export type PlayerTemplate = {
8
- Currencies: Currencies,
9
- Rebirths: number,
10
- Rank: number,
11
- Inventory: PlayerInventory,
12
- Trade: PlayerTrade,
13
- Settings: PlayerSettings,
14
- Codes: PlayerCodes,
15
- }
16
-
17
- const function PlayerTemplate()
18
- return {
19
- Currencies = { Coins = 0, Gems = 0 },
20
- Rebirths = 0,
21
- Rank = 0,
22
- Inventory = { Slots = 20 },
23
- Trade = { DayKey = nil, TradesToday = 0, LastTradeAt = 0, ActiveTradeId = nil },
24
- Settings = { GameMusic = 100, Effects = 100 },
25
- Codes = { RedeemedCount = 0 },
26
- }
27
- end
28
-
29
- return PlayerTemplate
@@ -1,13 +0,0 @@
1
- {
2
- "version": 1,
3
- "file": "TemplateData.luau",
4
- "sourceRoot": "C:/Users/gtofr/OneDrive/Documentos/GitHub/Cluaupp/examples/game",
5
- "sources": [
6
- "ReplicatedStorage/Shared/Constants/Datas/TemplateData.clh"
7
- ],
8
- "mappings": "AAAA",
9
- "cluaupp": {
10
- "source": "ReplicatedStorage/Shared/Constants/Datas/TemplateData.clh",
11
- "generated": "out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau"
12
- }
13
- }