cluaupp 1.5.0 → 1.5.1

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 (323) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +10 -5
  3. package/api/CHANGELOG.md +3 -0
  4. package/api/chat.js +74 -0
  5. package/api/fixtures/roblox/README.md +6 -0
  6. package/api/fixtures/roblox/aux/sample.d.luau +6 -0
  7. package/api/fixtures/roblox/mini/Mini-API-Dump.json +72 -0
  8. package/api/generated/bench-latest.json +39 -0
  9. package/api/generated/capability-profile.json +76 -0
  10. package/api/generated/lsp-index.json +1 -0
  11. package/api/generated/luau/classes.d.luau +11221 -0
  12. package/api/generated/luau/datatypes.d.luau +327 -0
  13. package/api/generated/luau/enums.d.luau +4918 -0
  14. package/api/generated/luau/globals.d.luau +12 -0
  15. package/api/manifests/roblox-target.manifest.json +22 -0
  16. package/api/normalized/roblox-target.profile.json +237461 -0
  17. package/api/overrides/classes.json +8 -0
  18. package/api/overrides/compatibility.json +9 -0
  19. package/api/overrides/datatypes.json +1579 -0
  20. package/api/overrides/enums.json +8 -0
  21. package/api/overrides/globals.json +8 -0
  22. package/api/overrides/type-mappings.json +9 -0
  23. package/api/roblox-api.lock.json +9 -0
  24. package/data/Mini-API-Dump.json +1 -0
  25. package/editors/vscode/README.md +12 -10
  26. package/editors/vscode/extension.js +82 -0
  27. package/editors/vscode/icons/axiom.svg +4 -0
  28. package/editors/vscode/icons/bloom.svg +5 -0
  29. package/editors/vscode/icons/helm.svg +4 -0
  30. package/editors/vscode/icons/hive.svg +5 -0
  31. package/editors/vscode/icons/mint.svg +4 -0
  32. package/editors/vscode/icons/shift.svg +6 -0
  33. package/editors/vscode/language-configuration-schema.json +24 -0
  34. package/editors/vscode/package.json +172 -12
  35. package/editors/vscode/snippets/axiom.json +12 -0
  36. package/editors/vscode/snippets/bloom.json +12 -0
  37. package/editors/vscode/snippets/helm.json +12 -0
  38. package/editors/vscode/snippets/hive.json +12 -0
  39. package/editors/vscode/snippets/mint.json +12 -0
  40. package/editors/vscode/snippets/shift.json +17 -0
  41. package/editors/vscode/syntaxes/axiom.tmLanguage.json +5 -0
  42. package/editors/vscode/syntaxes/bloom.tmLanguage.json +5 -0
  43. package/editors/vscode/syntaxes/helm.tmLanguage.json +5 -0
  44. package/editors/vscode/syntaxes/hive.tmLanguage.json +5 -0
  45. package/editors/vscode/syntaxes/mint.tmLanguage.json +5 -0
  46. package/editors/vscode/syntaxes/schema.tmLanguage.json +89 -0
  47. package/editors/vscode/syntaxes/shift.tmLanguage.json +5 -0
  48. package/examples/game/.cluaupp/compile-cache/ReplicatedStorage__Shared__Constants__Datas__TemplateData.clh.json +1 -0
  49. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Boot__DataBoot.server.clpp.json +1 -0
  50. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Configuration__ServerConfig.clp.json +1 -0
  51. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__Guard.server.clpp.json +1 -0
  52. package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__PlayerHandler.server.clpp.json +1 -0
  53. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterCharacterScripts__Character__CharacterReady.client.clpp.json +1 -0
  54. package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterPlayerScripts__Controllers__DataController.client.clpp.json +1 -0
  55. package/examples/game/.cluaupp/compile-cache/index.json +12 -0
  56. package/examples/game/.cluaupp/datamodel-profile.json +94 -0
  57. package/examples/game/.vscode/settings.json +12 -1
  58. package/examples/game/include/clpp/datatypes.clh +337 -373
  59. package/examples/game/include/clpp/generated/datamodel.clh +25 -0
  60. package/examples/game/include/clpp/generated/enums.clh +1 -1
  61. package/examples/game/include/clpp/generated/instances.clh +41135 -41064
  62. package/examples/game/include/clpp/libs/axiom.clh +1 -1
  63. package/examples/game/include/clpp/libs/math.clh +14 -0
  64. package/examples/game/include/clpp/libs/spring.clh +17 -0
  65. package/examples/game/include/clpp/libs/ward.clh +1 -0
  66. package/examples/game/include/clpp/roblox.clh +3 -4
  67. package/examples/game/libs/ArrayIndexer/init.luau +209 -0
  68. package/examples/game/libs/Axiom/init.luau +608 -0
  69. package/examples/game/libs/Bloom/init.luau +433 -0
  70. package/examples/game/libs/Coil/init.luau +215 -0
  71. package/examples/game/libs/Crest/init.luau +426 -0
  72. package/examples/game/libs/Echo/init.luau +417 -0
  73. package/examples/game/libs/Ember/init.luau +365 -0
  74. package/examples/game/libs/Flare/init.luau +717 -0
  75. package/examples/game/libs/Gleam/init.luau +729 -0
  76. package/examples/game/libs/Guide/init.luau +215 -0
  77. package/examples/game/libs/Helm/init.luau +377 -0
  78. package/examples/game/libs/Hive/init.luau +241 -0
  79. package/examples/game/libs/Keep/Client.luau +114 -0
  80. package/examples/game/libs/Keep/Data.luau +883 -0
  81. package/examples/game/libs/Keep/Engine.luau +211 -0
  82. package/examples/game/libs/Keep/Enum.luau +6 -0
  83. package/examples/game/libs/Keep/Enums.luau +10 -0
  84. package/examples/game/libs/Keep/Path.luau +236 -0
  85. package/examples/game/libs/Keep/Replicator.luau +298 -0
  86. package/examples/game/libs/Keep/Server.luau +251 -0
  87. package/examples/game/libs/Keep/Store.luau +652 -0
  88. package/examples/game/libs/Keep/Trade.luau +455 -0
  89. package/examples/game/libs/Keep/init.luau +205 -0
  90. package/examples/game/libs/Lens/init.luau +609 -0
  91. package/examples/game/libs/Mint/init.luau +279 -0
  92. package/examples/game/libs/Net/init.luau +156 -0
  93. package/examples/game/libs/Net/src/codec.luau +224 -0
  94. package/examples/game/libs/Occlude/init.luau +117 -0
  95. package/examples/game/libs/Pin/init.luau +309 -0
  96. package/examples/game/libs/Promise/LICENSE +21 -0
  97. package/examples/game/libs/Promise/_impl/LICENSE +21 -0
  98. package/examples/game/libs/Promise/_impl/init.lua +1991 -0
  99. package/examples/game/libs/Promise/init.lua +1990 -0
  100. package/examples/game/libs/Promise/init.luau +13 -0
  101. package/examples/game/libs/Promise/types.luau +46 -0
  102. package/examples/game/libs/Roster/init.luau +686 -0
  103. package/examples/game/libs/SOURCES.md +31 -0
  104. package/examples/game/libs/Shift/init.luau +238 -0
  105. package/examples/game/libs/Spark/init.luau +487 -0
  106. package/examples/game/libs/Stage/init.luau +178 -0
  107. package/examples/game/libs/Sweep/init.luau +259 -0
  108. package/examples/game/libs/Trace/init.luau +213 -0
  109. package/examples/game/libs/Ward/init.luau +211 -0
  110. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +29 -0
  111. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +13 -0
  112. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +83 -0
  113. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +13 -0
  114. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +18 -0
  115. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +13 -0
  116. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +14 -0
  117. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +13 -0
  118. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +15 -0
  119. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +13 -0
  120. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +50 -0
  121. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +13 -0
  122. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +12 -0
  123. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +13 -0
  124. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +16 -0
  125. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +13 -0
  126. package/examples/game/src/ReplicatedStorage/Shared/Net/Net.clh +26 -0
  127. package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
  128. package/examples/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  129. package/examples/game/stylua.toml +9 -0
  130. package/generated/api/build-profile.js +115 -0
  131. package/generated/api/cache.js +44 -0
  132. package/generated/api/coverage.js +32 -0
  133. package/generated/api/diff.js +125 -0
  134. package/generated/api/generators/clpp-headers.js +353 -0
  135. package/generated/api/generators/docs-reference.js +7 -0
  136. package/generated/api/generators/lsp-index.js +81 -0
  137. package/generated/api/generators/luau-defs.js +94 -0
  138. package/generated/api/ide.js +76 -0
  139. package/generated/api/identifier.js +47 -0
  140. package/generated/api/index.js +324 -0
  141. package/generated/api/loader.js +57 -0
  142. package/generated/api/merge.js +88 -0
  143. package/generated/api/model.js +5 -0
  144. package/generated/api/normalize.js +231 -0
  145. package/generated/api/overrides-validate.js +58 -0
  146. package/generated/api/perf.js +12 -0
  147. package/generated/api/registry.js +136 -0
  148. package/generated/api/release.js +44 -0
  149. package/generated/api/resolver.js +250 -0
  150. package/generated/api/sources/aux-types-diff.js +75 -0
  151. package/generated/api/sources/client-tracker.js +11 -0
  152. package/generated/api/sources/creator-docs.js +42 -0
  153. package/generated/api/sources/luau-types.js +34 -0
  154. package/generated/api/sources/mini-dump.js +17 -0
  155. package/generated/api/sources/studio-api.js +8 -0
  156. package/generated/api/sources/studio-apiv2.js +11 -0
  157. package/generated/api/sources/studio-full-api.js +8 -0
  158. package/generated/api/studio-smoke.js +80 -0
  159. package/generated/api/type-parse.js +49 -0
  160. package/generated/api/understander.js +51 -0
  161. package/generated/api/validate.js +135 -0
  162. package/generated/api-cli.js +421 -0
  163. package/generated/cli.js +16 -5
  164. package/generated/clpp/contract.js +3 -3
  165. package/generated/clpp/format-luau.js +236 -0
  166. package/generated/clpp/index.js +13 -1
  167. package/generated/clpp/modules.js +227 -0
  168. package/generated/clpp/postprocess.js +86 -5
  169. package/generated/clpp/rewrite-janitor.js +145 -0
  170. package/generated/clpp/runner.js +14 -2
  171. package/generated/compile.js +82 -0
  172. package/generated/flare/parse.js +12 -1
  173. package/generated/intellisense.js +57 -1
  174. package/generated/native/index.js +217 -16
  175. package/generated/target/authority-check.js +136 -0
  176. package/generated/target/authority-graph.js +74 -0
  177. package/generated/target/build-cache.js +112 -0
  178. package/generated/target/capabilities.js +89 -0
  179. package/generated/target/capability-profile.js +30 -0
  180. package/generated/target/component-contracts.js +60 -0
  181. package/generated/target/context-check.js +160 -0
  182. package/generated/target/datamodel-check.js +170 -0
  183. package/generated/target/datamodel-generate.js +74 -0
  184. package/generated/target/datamodel.js +233 -0
  185. package/generated/target/diagnostics.js +63 -0
  186. package/generated/target/doctor.js +152 -0
  187. package/generated/target/flare-version.js +58 -0
  188. package/generated/target/get-service.js +69 -0
  189. package/generated/target/lifetime-check.js +47 -0
  190. package/generated/target/luau-stamp.js +39 -0
  191. package/generated/target/optimizer-advise.js +88 -0
  192. package/generated/target/optimizer-apply.js +44 -0
  193. package/generated/target/optimizer-soa.js +254 -0
  194. package/generated/target/parallel-check.js +136 -0
  195. package/generated/target/pgo.js +148 -0
  196. package/generated/target/project-docs.js +69 -0
  197. package/generated/target/project-graph.js +71 -0
  198. package/generated/target/roblox.js +63 -0
  199. package/generated/target/security-check.js +41 -0
  200. package/generated/target/source-map.js +25 -0
  201. package/generated/target/studio-bridge.js +112 -0
  202. package/generated/utils/project.js +178 -8
  203. package/generated/utils/rojo-mapper.js +11 -3
  204. package/include/clpp/datatypes.clh +337 -373
  205. package/include/clpp/generated/enums.clh +1 -1
  206. package/include/clpp/generated/instances.clh +41135 -41064
  207. package/include/clpp/libs/axiom.clh +1 -1
  208. package/include/clpp/libs/hive.clh +1 -0
  209. package/include/clpp/libs/keep.clh +2 -0
  210. package/include/clpp/libs/math.clh +14 -0
  211. package/include/clpp/libs/spring.clh +17 -0
  212. package/include/clpp/libs/ward.clh +1 -0
  213. package/include/clpp/roblox.clh +3 -4
  214. package/package.json +10 -3
  215. package/plugins/CluauppNav/Plugin.luau +207 -0
  216. package/plugins/CluauppNav/ProfileLogger.luau +27 -0
  217. package/plugins/CluauppNav/README.md +24 -0
  218. package/runtime/Axiom/init.luau +10 -7
  219. package/runtime/Coil/init.luau +8 -2
  220. package/runtime/Crest/init.luau +1 -0
  221. package/runtime/Flare/init.luau +2 -1
  222. package/runtime/Gleam/init.luau +15 -4
  223. package/runtime/Hive/init.luau +36 -14
  224. package/runtime/Pin/init.luau +5 -2
  225. package/runtime/Roster/init.luau +2 -0
  226. package/runtime/Spark/init.luau +3 -0
  227. package/runtime/Sweep/init.luau +2 -0
  228. package/runtime/Trace/init.luau +3 -0
  229. package/runtime/Ward/init.luau +38 -5
  230. package/scripts/postinstall-editor.js +17 -0
  231. package/skills/README.md +13 -0
  232. package/skills/clpp-hive-flare/SKILL.md +79 -0
  233. package/skills/clpp-language/SKILL.md +123 -0
  234. package/skills/clpp-language/compiler.md +89 -0
  235. package/skills/clpp-style/SKILL.md +95 -0
  236. package/src/api/build-profile.ts +138 -0
  237. package/src/api/cache.ts +45 -0
  238. package/src/api/coverage.ts +46 -0
  239. package/src/api/diff.ts +143 -0
  240. package/src/api/generators/clpp-headers.ts +377 -0
  241. package/src/api/generators/docs-reference.ts +11 -0
  242. package/src/api/generators/lsp-index.ts +106 -0
  243. package/src/api/generators/luau-defs.ts +98 -0
  244. package/src/api/ide.ts +87 -0
  245. package/src/api/identifier.ts +47 -0
  246. package/src/api/index.ts +269 -0
  247. package/src/api/loader.ts +60 -0
  248. package/src/api/merge.ts +126 -0
  249. package/src/api/model.ts +283 -0
  250. package/src/api/normalize.ts +257 -0
  251. package/src/api/overrides-validate.ts +58 -0
  252. package/src/api/perf.ts +15 -0
  253. package/src/api/registry.ts +162 -0
  254. package/src/api/release.ts +47 -0
  255. package/src/api/resolver.ts +255 -0
  256. package/src/api/sources/aux-types-diff.ts +81 -0
  257. package/src/api/sources/client-tracker.ts +10 -0
  258. package/src/api/sources/creator-docs.ts +37 -0
  259. package/src/api/sources/luau-types.ts +30 -0
  260. package/src/api/sources/mini-dump.ts +50 -0
  261. package/src/api/sources/studio-api.ts +7 -0
  262. package/src/api/sources/studio-apiv2.ts +10 -0
  263. package/src/api/sources/studio-full-api.ts +7 -0
  264. package/src/api/studio-smoke.ts +82 -0
  265. package/src/api/type-parse.ts +48 -0
  266. package/src/api/understander.ts +72 -0
  267. package/src/api/validate.ts +160 -0
  268. package/src/api-cli.ts +493 -0
  269. package/src/api.generated.js +37 -22
  270. package/src/cli.ts +21 -6
  271. package/src/clpp/contract.ts +34 -3
  272. package/src/clpp/format-luau.ts +262 -0
  273. package/src/clpp/index.ts +13 -2
  274. package/src/clpp/modules.ts +252 -0
  275. package/src/clpp/postprocess.ts +100 -5
  276. package/src/clpp/rewrite-janitor.ts +156 -0
  277. package/src/clpp/runner.ts +14 -2
  278. package/src/compile.ts +87 -0
  279. package/src/flare/parse.ts +14 -1
  280. package/src/flare/types.ts +2 -0
  281. package/src/intellisense.ts +58 -1
  282. package/src/native/index.ts +225 -16
  283. package/src/target/authority-check.ts +162 -0
  284. package/src/target/authority-graph.ts +80 -0
  285. package/src/target/build-cache.ts +129 -0
  286. package/src/target/capabilities.ts +115 -0
  287. package/src/target/capability-profile.ts +25 -0
  288. package/src/target/component-contracts.ts +64 -0
  289. package/src/target/context-check.ts +177 -0
  290. package/src/target/datamodel-check.ts +188 -0
  291. package/src/target/datamodel-generate.ts +78 -0
  292. package/src/target/datamodel.ts +261 -0
  293. package/src/target/diagnostics.ts +67 -0
  294. package/src/target/doctor.ts +171 -0
  295. package/src/target/flare-version.ts +61 -0
  296. package/src/target/get-service.ts +85 -0
  297. package/src/target/lifetime-check.ts +45 -0
  298. package/src/target/luau-stamp.ts +33 -0
  299. package/src/target/optimizer-advise.ts +93 -0
  300. package/src/target/optimizer-apply.ts +63 -0
  301. package/src/target/optimizer-soa.ts +270 -0
  302. package/src/target/parallel-check.ts +135 -0
  303. package/src/target/pgo.ts +157 -0
  304. package/src/target/project-docs.ts +68 -0
  305. package/src/target/project-graph.ts +75 -0
  306. package/src/target/roblox.ts +78 -0
  307. package/src/target/security-check.ts +40 -0
  308. package/src/target/source-map.ts +36 -0
  309. package/src/target/studio-bridge.ts +140 -0
  310. package/src/types.ts +14 -0
  311. package/src/utils/project.ts +183 -9
  312. package/src/utils/rojo-mapper.ts +12 -3
  313. package/stylua.toml +9 -0
  314. package/templates/game/.vscode/settings.json +7 -1
  315. package/templates/game/include/clpp/datatypes.clh +337 -373
  316. package/templates/game/include/clpp/generated/enums.clh +1 -1
  317. package/templates/game/include/clpp/generated/instances.clh +41135 -41064
  318. package/templates/game/include/clpp/libs/axiom.clh +1 -1
  319. package/templates/game/include/clpp/libs/ward.clh +1 -0
  320. package/templates/game/include/clpp/roblox.clh +3 -4
  321. package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
  322. package/templates/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
  323. package/templates/game/stylua.toml +9 -0
@@ -280,15 +280,14 @@ function compileHelm(source: string, file: string, defaultName: string): NativeE
280
280
  id += 1;
281
281
  }
282
282
  const { headerRel, luauRel } = emitPaths(file, name);
283
- const headerFns = commands
284
- .map((cmd) => {
285
- const params = cmd.args.map((arg) => `${arg.type === "i32" ? "int" : arg.type} ${arg.name}`).join(", ");
286
- return `\tvoid ${cmd.name}(${params});`;
287
- })
283
+ const headerCmds = commands
284
+ .map((cmd, index) => `\t// command ${cmd.name} permission ${cmd.permission}\n\tconst int ${cmd.name} = ${index + 1};`)
288
285
  .join("\n");
289
286
  const header = `${headerBanner("helm")}
287
+ #include <clpp/libs/helm.clh>
290
288
  namespace ${name} {
291
- ${headerFns}
289
+ ${headerCmds}
290
+ HelmSession Open(Player player);
292
291
  }
293
292
  `;
294
293
  const rows = commands
@@ -417,7 +416,7 @@ function compileHive(source: string, file: string, defaultName: string): NativeE
417
416
  #include <clpp/libs/hive.clh>
418
417
  namespace ${name} {
419
418
  ${components.map((item, index) => `\tconst int ${item.name} = ${index + 1};`).join("\n")}
420
- HiveWorld New();
419
+ auto New();
421
420
  }
422
421
  `;
423
422
  const rows = components
@@ -443,32 +442,242 @@ return ${name}
443
442
  return { headerRel, headerPath: headerRel, header, luauRel, luau };
444
443
  }
445
444
 
445
+ /** Mirrors Axiom.Groups + axiom.clh prototypes for IntelliSense on selected namespaces. */
446
+ const AXIOM_GROUP_DECLS: Record<string, string[]> = {
447
+ Scalar: [
448
+ "double Clamp(double value, double min, double max);",
449
+ "double Map(double value, double inMin, double inMax, double outMin, double outMax);",
450
+ "double Wrap(double value, double min, double max);",
451
+ "double Sign(double value);",
452
+ "double Round(double value);",
453
+ "double Round(double value, int digits);",
454
+ "double Snap(double value, double step);",
455
+ "double PingPong(double t, double length);",
456
+ "double Saturate(double value);",
457
+ "double Fract(double value);",
458
+ "double InvLerp(double from, double to, double value);",
459
+ "double Approach(double current, double target, double maxDelta);",
460
+ "bool IsFinite(double value);",
461
+ "double Abs(double value);",
462
+ "double Min(double a, double b);",
463
+ "double Max(double a, double b);",
464
+ "double Pow(double base, double exp);",
465
+ "double Sqrt(double value);",
466
+ "double Cbrt(double value);",
467
+ "double Hypot(double a, double b);",
468
+ "double Log(double value);",
469
+ "double Exp(double value);",
470
+ "double Smoothstep(double edge0, double edge1, double x);",
471
+ "double Smootherstep(double edge0, double edge1, double x);",
472
+ "double Gcd(double a, double b);",
473
+ "double Lcm(double a, double b);",
474
+ "bool IsEven(double value);",
475
+ "bool IsOdd(double value);",
476
+ "double Factorial(double n);",
477
+ "double Scale(double value, int index);",
478
+ ],
479
+ Lerp: [
480
+ "double Lerp(double from, double to, double alpha);",
481
+ "double LerpClamped(double from, double to, double alpha);",
482
+ "Vector2 LerpVector2(Vector2 from, Vector2 to, double alpha);",
483
+ "Vector3 LerpVector3(Vector3 from, Vector3 to, double alpha);",
484
+ "Vector3 LerpVector(Vector3 from, Vector3 to, double alpha);",
485
+ "Color3 LerpColor3(Color3 from, Color3 to, double alpha);",
486
+ "CFrame LerpCFrame(CFrame from, CFrame to, double alpha);",
487
+ "UDim2 LerpUDim2(UDim2 from, UDim2 to, double alpha);",
488
+ "double LerpAngle(double from, double to, double alpha);",
489
+ "double Inverse(double from, double to, double value);",
490
+ ],
491
+ Vector: [
492
+ "Vector3 Project(Vector3 a, Vector3 b);",
493
+ "Vector3 Reject(Vector3 a, Vector3 b);",
494
+ "Vector3 Reflect(Vector3 incident, Vector3 normal);",
495
+ "double Angle(Vector3 a, Vector3 b);",
496
+ "double Distance(Vector3 a, Vector3 b);",
497
+ "double Distance2(Vector2 a, Vector2 b);",
498
+ "CFrame Orthonormal(Vector3 forward);",
499
+ "CFrame Orthonormal(Vector3 forward, Vector3 up);",
500
+ "Vector3 Slerp(Vector3 a, Vector3 b, double t);",
501
+ "CFrame LookAt(Vector3 from, Vector3 look);",
502
+ ],
503
+ CFrame: ["CFrame SlerpCFrame(CFrame a, CFrame b, double t);", "CFrame Flat(CFrame cf);"],
504
+ Easing: [
505
+ "double InSine(double t);",
506
+ "double OutSine(double t);",
507
+ "double InOutSine(double t);",
508
+ "double InQuad(double t);",
509
+ "double OutQuad(double t);",
510
+ "double InOutQuad(double t);",
511
+ "double InCubic(double t);",
512
+ "double OutCubic(double t);",
513
+ "double InOutCubic(double t);",
514
+ "double InQuart(double t);",
515
+ "double OutQuart(double t);",
516
+ "double InOutQuart(double t);",
517
+ "double InQuint(double t);",
518
+ "double OutQuint(double t);",
519
+ "double InOutQuint(double t);",
520
+ "double InExpo(double t);",
521
+ "double OutExpo(double t);",
522
+ "double InOutExpo(double t);",
523
+ "double InCirc(double t);",
524
+ "double OutCirc(double t);",
525
+ "double InOutCirc(double t);",
526
+ "double InBack(double t);",
527
+ "double OutBack(double t);",
528
+ "double InOutBack(double t);",
529
+ "double InElastic(double t);",
530
+ "double OutElastic(double t);",
531
+ "double InOutElastic(double t);",
532
+ "double InBounce(double t);",
533
+ "double OutBounce(double t);",
534
+ "double InOutBounce(double t);",
535
+ "double Linear(double t);",
536
+ ],
537
+ Bezier: [
538
+ "Vector3 CubicBezier(double t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3);",
539
+ "Vector3 QuadraticBezier(double t, Vector3 p0, Vector3 p1, Vector3 p2);",
540
+ "CFrame Hover(double elapsed, Vector3 anchor, Vector3 look);",
541
+ "CFrame Float(double elapsed, Vector3 anchor, Vector3 look);",
542
+ "CFrame FloatSpin(double elapsed, CFrame anchor);",
543
+ ],
544
+ Geometry: [
545
+ "bool AabbContains(Vector3 point, Vector3 min, Vector3 max);",
546
+ "bool SphereContains(Vector3 point, Vector3 center, double radius);",
547
+ "Vector3 RayPlane(Vector3 origin, Vector3 dir, Vector3 point, Vector3 normal);",
548
+ "Vector3 Barycentric(Vector3 p, Vector3 a, Vector3 b, Vector3 c);",
549
+ "Vector3 ClosestPointOnSegment(Vector3 p, Vector3 a, Vector3 b);",
550
+ ],
551
+ Trig: [
552
+ "double Deg(double rad);",
553
+ "double Rad(double deg);",
554
+ "double DeltaAngle(double from, double to);",
555
+ "double AngleDiff(double from, double to);",
556
+ "double DeltaAngleDegrees(double from, double to);",
557
+ "double NormalizeAngle(double angle);",
558
+ "double Sin(double x);",
559
+ "double Cos(double x);",
560
+ "double Tan(double x);",
561
+ "double Asin(double x);",
562
+ "double Acos(double x);",
563
+ "double Atan2(double y, double x);",
564
+ ],
565
+ Probability: [
566
+ "double RandomRange(double min, double max);",
567
+ "double Random(double min, double max);",
568
+ "int Weighted(LuaArray<double> weights);",
569
+ "double Gaussian();",
570
+ "double Gaussian(double mean, double std);",
571
+ "double Average(LuaArray<double> values);",
572
+ "double Sum(LuaArray<double> values);",
573
+ ],
574
+ Noise: [
575
+ "double HashU32(double n);",
576
+ "double Value1(double x);",
577
+ "double Value2(double x, double y);",
578
+ "double Value3(double x, double y, double z);",
579
+ ],
580
+ Color: [
581
+ "Color3 FromHSV(double h, double s, double v);",
582
+ "Color3 Contrast(Color3 color, double amount);",
583
+ "Color3 LerpHSV(Color3 a, Color3 b, double t);",
584
+ ],
585
+ };
586
+
587
+ function axiomFnDecls(): Record<string, string[]> {
588
+ const byFn: Record<string, string[]> = {};
589
+ for (const decls of Object.values(AXIOM_GROUP_DECLS)) {
590
+ for (const decl of decls) {
591
+ const match = decl.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
592
+ if (!match) {
593
+ continue;
594
+ }
595
+ const fn = match[1];
596
+ if (!byFn[fn]) {
597
+ byFn[fn] = [];
598
+ }
599
+ byFn[fn].push(decl);
600
+ }
601
+ }
602
+ return byFn;
603
+ }
604
+
605
+ const AXIOM_FN_DECLS = axiomFnDecls();
606
+
607
+ function axiomHeaderBody(groups: string[], fns: string[], file: string, lineNo: number): string {
608
+ const decls: string[] = [];
609
+ const seen = new Set<string>();
610
+ const pushDecl = (decl: string) => {
611
+ if (seen.has(decl)) {
612
+ return;
613
+ }
614
+ seen.add(decl);
615
+ decls.push(`\t${decl}`);
616
+ };
617
+ for (const group of groups) {
618
+ const key = Object.keys(AXIOM_GROUP_DECLS).find((name) => name.toLowerCase() === group.toLowerCase());
619
+ if (!key) {
620
+ fail(
621
+ file,
622
+ lineNo,
623
+ `unknown group "${group}" — use Scalar Lerp Vector CFrame Easing Bezier Geometry Trig Probability Noise Color`,
624
+ );
625
+ }
626
+ decls.push(`\t// group ${key}`);
627
+ for (const decl of AXIOM_GROUP_DECLS[key]) {
628
+ pushDecl(decl);
629
+ }
630
+ }
631
+ for (const fn of fns) {
632
+ const key = Object.keys(AXIOM_FN_DECLS).find((name) => name.toLowerCase() === fn.toLowerCase());
633
+ if (!key) {
634
+ fail(file, lineNo, `unknown fn "${fn}" — use a name from axiom.clh (e.g. fn LerpVector2)`);
635
+ }
636
+ decls.push(`\t// fn ${key}`);
637
+ for (const decl of AXIOM_FN_DECLS[key]) {
638
+ pushDecl(decl);
639
+ }
640
+ }
641
+ return decls.join("\n");
642
+ }
643
+
446
644
  function compileAxiom(source: string, file: string, defaultName: string): NativeEmit {
447
645
  const { name, lines } = parseOptName(source, file, defaultName);
448
646
  const groups: string[] = [];
647
+ const fns: string[] = [];
648
+ let lastLine = 1;
449
649
  for (const tagged of lines) {
450
650
  const [lineNoRaw, line] = tagged.split("\t");
451
651
  const lineNo = Number(lineNoRaw);
452
- const match = line.match(new RegExp(`^group\\s+(${IDENT})\\s*$`, "i"));
453
- if (!match) {
454
- fail(file, lineNo, `expected group Scalar`);
652
+ lastLine = lineNo;
653
+ const groupMatch = line.match(new RegExp(`^group\\s+(${IDENT})\\s*$`, "i"));
654
+ if (groupMatch) {
655
+ groups.push(groupMatch[1]);
656
+ continue;
657
+ }
658
+ const fnMatch = line.match(new RegExp(`^fn\\s+(${IDENT})\\s*$`, "i"));
659
+ if (fnMatch) {
660
+ fns.push(fnMatch[1]);
661
+ continue;
455
662
  }
456
- groups.push(match[1]);
663
+ fail(file, lineNo, `expected group Lerp or fn LerpVector2`);
457
664
  }
458
- if (groups.length === 0) {
459
- fail(file, 1, "schema is empty — add group Scalar");
665
+ if (groups.length === 0 && fns.length === 0) {
666
+ fail(file, 1, "schema is empty — add group Lerp or fn LerpVector2");
460
667
  }
668
+ const body = axiomHeaderBody(groups, fns, file, lastLine);
669
+ const selectArgs = [...groups, ...fns].map((item) => `"${item}"`).join(", ");
461
670
  const { headerRel, luauRel } = emitPaths(file, name);
462
671
  const header = `${headerBanner("axiom")}
463
672
  #include <clpp/libs/axiom.clh>
464
673
  namespace ${name} {
465
- ${groups.map((group) => `\t// group ${group}`).join("\n")}
674
+ ${body}
466
675
  }
467
676
  `;
468
677
  const luau = `${luauBanner()}local ReplicatedStorage = game:GetService("ReplicatedStorage")
469
678
  local Axiom = require(ReplicatedStorage.CluauppLibs.Axiom)
470
- local ${name} = Axiom.Select({ ${groups.map((group) => `"${group}"`).join(", ")} })
471
- -- out/ only binds groups: ${groups.join(", ")}
679
+ local ${name} = Axiom.Select({ ${selectArgs} })
680
+ -- out/ binds: ${[...groups.map((g) => `group ${g}`), ...fns.map((f) => `fn ${f}`)].join(", ")}
472
681
  return ${name}
473
682
  `;
474
683
  return { headerRel, headerPath: headerRel, header, luauRel, luau };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Authority / architecture rules — import boundaries (CLUAU-AUTH*).
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { runContextFromFileName, type RunContext } from "./roblox.js";
8
+ import { type PlatformDiagnostic, lineColAt, stripCommentsKeepStrings } from "./diagnostics.js";
9
+
10
+ export interface ArchitectureRules {
11
+ clientCannotImport?: string[];
12
+ sharedCannotUse?: string[];
13
+ serverCannotImport?: string[];
14
+ }
15
+
16
+ export interface ComponentContract {
17
+ /** Instance path suffix or exact path, e.g. "Enemy" or "Workspace.Mobs.Enemy" */
18
+ path: string;
19
+ children: Record<string, string>; // name → expected className (or BasePart etc.)
20
+ }
21
+
22
+ export interface PlatformPolicy {
23
+ rules?: ArchitectureRules;
24
+ components?: ComponentContract[];
25
+ }
26
+
27
+ export function loadPlatformPolicy(projectRoot: string): PlatformPolicy {
28
+ const file = path.join(projectRoot, "cluaupp.config.json");
29
+ if (!fs.existsSync(file)) {
30
+ return defaultPolicy();
31
+ }
32
+ try {
33
+ const raw = JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
34
+ const rules = (raw.rules as ArchitectureRules) || {};
35
+ const components = Array.isArray(raw.components) ? (raw.components as ComponentContract[]) : [];
36
+ return {
37
+ rules: { ...defaultPolicy().rules, ...rules },
38
+ components: components.length ? components : defaultPolicy().components,
39
+ };
40
+ } catch {
41
+ return defaultPolicy();
42
+ }
43
+ }
44
+
45
+ function defaultPolicy(): PlatformPolicy {
46
+ return {
47
+ rules: {
48
+ clientCannotImport: ["ServerScriptService", "ServerStorage"],
49
+ sharedCannotUse: ["DataStoreService", "MessagingService", "ServerStorage"],
50
+ serverCannotImport: [],
51
+ },
52
+ components: [],
53
+ };
54
+ }
55
+
56
+ const INCLUDE_RE = /#include\s*[<"]([^>"]+)[>"]/g;
57
+ const IMPORT_RE = /\bimport\s*\{[^}]*\}\s*from\s*"([^"]+)"/g;
58
+ const REQUIRE_RE = /require\s*\(\s*([^)]+)\)/g;
59
+ const GET_SERVICE = /GetService\s*(?:<\s*([A-Za-z_]\w*)\s*>|\(\s*"([A-Za-z_]\w*)"\s*\))/g;
60
+
61
+ export function checkAuthority(
62
+ source: string,
63
+ fileName: string,
64
+ policy: PlatformPolicy = defaultPolicy(),
65
+ ): PlatformDiagnostic[] {
66
+ const ctx = runContextFromFileName(fileName);
67
+ const clean = stripCommentsKeepStrings(source);
68
+ const out: PlatformDiagnostic[] = [];
69
+ const rules = policy.rules || {};
70
+
71
+ const bannedImports =
72
+ ctx === "Client"
73
+ ? rules.clientCannotImport || []
74
+ : ctx === "Shared"
75
+ ? []
76
+ : ctx === "Server"
77
+ ? rules.serverCannotImport || []
78
+ : [];
79
+
80
+ const checkToken = (token: string, index: number, code: string, message: string) => {
81
+ const { line, column } = lineColAt(clean, index);
82
+ out.push({ code, message, line, column, severity: "error", file: fileName });
83
+ };
84
+
85
+ for (const banned of bannedImports) {
86
+ const re = new RegExp(`\\b${banned}\\b`);
87
+ INCLUDE_RE.lastIndex = 0;
88
+ let m: RegExpExecArray | null;
89
+ while ((m = INCLUDE_RE.exec(clean))) {
90
+ if (re.test(m[1])) {
91
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not import/include path involving ${banned}`);
92
+ }
93
+ }
94
+ IMPORT_RE.lastIndex = 0;
95
+ while ((m = IMPORT_RE.exec(clean))) {
96
+ if (re.test(m[1])) {
97
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not import path involving ${banned}`);
98
+ }
99
+ }
100
+ REQUIRE_RE.lastIndex = 0;
101
+ while ((m = REQUIRE_RE.exec(clean))) {
102
+ if (re.test(m[1])) {
103
+ checkToken(m[1], m.index, "CLUAU_AUTH001", `Architecture: ${ctx} must not require ${banned}`);
104
+ }
105
+ }
106
+ if (re.test(clean) && (clean.includes(`"${banned}`) || clean.includes(`'${banned}`) || clean.includes(`<${banned}`))) {
107
+ /* covered above */
108
+ }
109
+ }
110
+
111
+ if (ctx === "Shared" || ctx === "Client") {
112
+ const forbiddenServices = rules.sharedCannotUse || [];
113
+ GET_SERVICE.lastIndex = 0;
114
+ let m: RegExpExecArray | null;
115
+ while ((m = GET_SERVICE.exec(clean))) {
116
+ const svc = m[1] || m[2];
117
+ if (svc && forbiddenServices.includes(svc) && ctx === "Shared") {
118
+ const { line, column } = lineColAt(clean, m.index);
119
+ out.push({
120
+ code: "CLUAU_AUTH002",
121
+ message: `Architecture: Shared must not use ${svc}`,
122
+ line,
123
+ column,
124
+ severity: "error",
125
+ file: fileName,
126
+ });
127
+ }
128
+ }
129
+ }
130
+
131
+ // Client writing Keep/DataStore style authority smells
132
+ if (ctx === "Client") {
133
+ const smells = [
134
+ { re: /\bDataStoreService\b/, msg: "Client must not touch DataStoreService" },
135
+ { re: /Keep\.Server\.(Set|SetPersisted|Trade)/, msg: "Client must not mutate Keep.Server persistence APIs" },
136
+ ];
137
+ for (const smell of smells) {
138
+ const m = smell.re.exec(clean);
139
+ if (m) {
140
+ const { line, column } = lineColAt(clean, m.index);
141
+ out.push({
142
+ code: "CLUAU_AUTH003",
143
+ message: smell.msg,
144
+ line,
145
+ column,
146
+ severity: "error",
147
+ file: fileName,
148
+ });
149
+ }
150
+ }
151
+ }
152
+
153
+ return out;
154
+ }
155
+
156
+ export function assertAuthoritySafe(source: string, fileName: string, policy?: PlatformPolicy): void {
157
+ const errors = checkAuthority(source, fileName, policy).filter((d) => d.severity === "error");
158
+ if (!errors.length) return;
159
+ throw new Error(`Authority:\n${errors.map((e) => ` ${e.line}:${e.column} ${e.code} ${e.message}`).join("\n")}`);
160
+ }
161
+
162
+ export type { RunContext };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Call-graph authority — transitive Client → banned Server paths via include graph.
3
+ * CLUAU_AUTH004 when a client/shared module reaches ServerScriptService/ServerStorage.
4
+ */
5
+
6
+ import { buildProjectGraph, type ProjectGraph } from "./project-graph.js";
7
+ import { runContextFromFileName } from "./roblox.js";
8
+ import type { PlatformDiagnostic } from "./diagnostics.js";
9
+ import { loadPlatformPolicy, type PlatformPolicy } from "./authority-check.js";
10
+
11
+ function pathLooksBanned(rel: string, banned: string[]): boolean {
12
+ const norm = rel.replace(/\\/g, "/");
13
+ return banned.some((b) => norm.includes(b));
14
+ }
15
+
16
+ function reachable(graph: ProjectGraph, from: string): Set<string> {
17
+ const adj = new Map<string, string[]>();
18
+ for (const n of graph.nodes) adj.set(n, []);
19
+ for (const e of graph.edges) {
20
+ if (!adj.has(e.from)) adj.set(e.from, []);
21
+ adj.get(e.from)!.push(e.to);
22
+ }
23
+ const seen = new Set<string>();
24
+ const stack = [from];
25
+ while (stack.length) {
26
+ const n = stack.pop()!;
27
+ if (seen.has(n)) continue;
28
+ seen.add(n);
29
+ for (const to of adj.get(n) || []) {
30
+ if (!seen.has(to)) stack.push(to);
31
+ }
32
+ }
33
+ seen.delete(from);
34
+ return seen;
35
+ }
36
+
37
+ export function checkAuthorityGraph(
38
+ projectRoot: string,
39
+ rootDir = "src",
40
+ policy: PlatformPolicy = loadPlatformPolicy(projectRoot),
41
+ ): PlatformDiagnostic[] {
42
+ const graph = buildProjectGraph(projectRoot, rootDir);
43
+ const out: PlatformDiagnostic[] = [];
44
+ const clientBanned = policy.rules?.clientCannotImport || ["ServerScriptService", "ServerStorage"];
45
+
46
+ for (const node of graph.nodes) {
47
+ const ctx = runContextFromFileName(node);
48
+ if (ctx !== "Client" && ctx !== "Shared") continue;
49
+
50
+ const banned = ctx === "Client" ? clientBanned : ["ServerScriptService", "ServerStorage"];
51
+
52
+ if (pathLooksBanned(node, banned)) {
53
+ out.push({
54
+ code: "CLUAU_AUTH004",
55
+ message: `Architecture call-graph: ${ctx} file lives under banned path (${node})`,
56
+ line: 1,
57
+ column: 1,
58
+ severity: "error",
59
+ file: node,
60
+ });
61
+ continue;
62
+ }
63
+
64
+ for (const dep of reachable(graph, node)) {
65
+ if (pathLooksBanned(dep, banned)) {
66
+ out.push({
67
+ code: "CLUAU_AUTH004",
68
+ message: `Architecture call-graph: ${ctx} \`${node}\` transitively reaches banned \`${dep}\``,
69
+ line: 1,
70
+ column: 1,
71
+ severity: "error",
72
+ file: node,
73
+ });
74
+ break;
75
+ }
76
+ }
77
+ }
78
+
79
+ return out;
80
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Incremental compile cache + bounded parallel job runner.
3
+ * Cache lives under `.cluaupp/compile-cache/` (gitignored via `.cluaupp/`).
4
+ */
5
+
6
+ import crypto from "node:crypto";
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+
11
+ export const COMPILE_CACHE_SCHEMA = 1 as const;
12
+
13
+ export interface CachedArtifact {
14
+ name: string;
15
+ contents: string;
16
+ }
17
+
18
+ export interface CachedJob {
19
+ rel: string;
20
+ sourceHash: string;
21
+ files: CachedArtifact[];
22
+ stale: string[];
23
+ }
24
+
25
+ export interface CompileCacheIndex {
26
+ schemaVersion: typeof COMPILE_CACHE_SCHEMA;
27
+ /** rel → sourceHash */
28
+ entries: Record<string, string>;
29
+ }
30
+
31
+ export function compileCacheDir(projectRoot: string): string {
32
+ return path.join(projectRoot, ".cluaupp", "compile-cache");
33
+ }
34
+
35
+ export function hashText(text: string): string {
36
+ return crypto.createHash("sha256").update(text, "utf8").digest("hex");
37
+ }
38
+
39
+ export function fingerprintSource(source: string, configKey: string): string {
40
+ return hashText(`${configKey}\n${source}`);
41
+ }
42
+
43
+ function indexPath(projectRoot: string): string {
44
+ return path.join(compileCacheDir(projectRoot), "index.json");
45
+ }
46
+
47
+ function jobPath(projectRoot: string, rel: string): string {
48
+ const safe = rel.replace(/[\\/]/g, "__");
49
+ return path.join(compileCacheDir(projectRoot), `${safe}.json`);
50
+ }
51
+
52
+ export function loadCompileCacheIndex(projectRoot: string): CompileCacheIndex {
53
+ const file = indexPath(projectRoot);
54
+ if (!fs.existsSync(file)) {
55
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
56
+ }
57
+ try {
58
+ const raw = JSON.parse(fs.readFileSync(file, "utf8")) as CompileCacheIndex;
59
+ if (!raw || typeof raw.entries !== "object") {
60
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
61
+ }
62
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: raw.entries };
63
+ } catch {
64
+ return { schemaVersion: COMPILE_CACHE_SCHEMA, entries: {} };
65
+ }
66
+ }
67
+
68
+ export function saveCompileCacheIndex(projectRoot: string, index: CompileCacheIndex): void {
69
+ const dir = compileCacheDir(projectRoot);
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ fs.writeFileSync(indexPath(projectRoot), JSON.stringify(index, null, "\t") + "\n", "utf8");
72
+ }
73
+
74
+ export function readCachedJob(projectRoot: string, rel: string, sourceHash: string): CachedJob | null {
75
+ const index = loadCompileCacheIndex(projectRoot);
76
+ if (index.entries[rel] !== sourceHash) {
77
+ return null;
78
+ }
79
+ const file = jobPath(projectRoot, rel);
80
+ if (!fs.existsSync(file)) {
81
+ return null;
82
+ }
83
+ try {
84
+ const job = JSON.parse(fs.readFileSync(file, "utf8")) as CachedJob;
85
+ if (!job || job.sourceHash !== sourceHash || !Array.isArray(job.files)) {
86
+ return null;
87
+ }
88
+ return job;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ export function writeCachedJob(projectRoot: string, job: CachedJob): void {
95
+ const dir = compileCacheDir(projectRoot);
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ fs.writeFileSync(jobPath(projectRoot, job.rel), JSON.stringify(job) + "\n", "utf8");
98
+ const index = loadCompileCacheIndex(projectRoot);
99
+ index.entries[job.rel] = job.sourceHash;
100
+ saveCompileCacheIndex(projectRoot, index);
101
+ }
102
+
103
+ /** Bounded concurrency over async work items. */
104
+ export async function mapPool<T, R>(
105
+ items: T[],
106
+ concurrency: number,
107
+ fn: (item: T, index: number) => Promise<R>,
108
+ ): Promise<R[]> {
109
+ const limit = Math.max(1, Math.min(concurrency, items.length || 1));
110
+ const results = new Array<R>(items.length);
111
+ let next = 0;
112
+ async function worker(): Promise<void> {
113
+ while (true) {
114
+ const i = next++;
115
+ if (i >= items.length) return;
116
+ results[i] = await fn(items[i], i);
117
+ }
118
+ }
119
+ await Promise.all(Array.from({ length: limit }, () => worker()));
120
+ return results;
121
+ }
122
+
123
+ export function defaultJobCount(): number {
124
+ const env = Number(process.env.CLUAUPP_JOBS || process.env.CLPP_JOBS || "");
125
+ if (Number.isFinite(env) && env > 0) {
126
+ return Math.floor(env);
127
+ }
128
+ return Math.max(1, Math.min(8, os.cpus()?.length || 4));
129
+ }