@tilua/compiler 8.1.0 → 9.0.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.
- package/README.md +115 -13
- package/dist/{chunk-WOBHMGGC.js → chunk-3RJVN4KC.js} +447 -59
- package/dist/cli.cjs +733 -98
- package/dist/cli.js +279 -24
- package/dist/index.cjs +453 -66
- package/dist/index.d.cts +52 -9
- package/dist/index.d.ts +52 -9
- package/dist/index.js +14 -5
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var import_node_path3 = require("path");
|
|
|
13
13
|
var import_node_fs3 = require("fs");
|
|
14
14
|
var import_node_path2 = require("path");
|
|
15
15
|
var import_parser7 = require("@tilua/parser");
|
|
16
|
-
var
|
|
16
|
+
var import_luau_parser3 = require("luau-parser");
|
|
17
17
|
|
|
18
18
|
// src/config.ts
|
|
19
19
|
var import_node_path = require("path");
|
|
@@ -50,10 +50,10 @@ var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expressi
|
|
|
50
50
|
var table = (fields) => ({ type: "TableExpression", fields, ...span() });
|
|
51
51
|
var field = (key, value) => (0, import_parser2.isLuauName)(key) ? { type: "TableFieldNamed", name: identifier(key), value } : { type: "TableFieldComputed", key: string(key), value };
|
|
52
52
|
var block = (statements) => ({ type: "Block", statements, ...span() });
|
|
53
|
-
var local = (names,
|
|
53
|
+
var local = (names, init2) => ({
|
|
54
54
|
type: "LocalStatement",
|
|
55
55
|
names: names.map((name) => ({ type: "TypedIdentifier", name, ...span() })),
|
|
56
|
-
init,
|
|
56
|
+
init: init2,
|
|
57
57
|
...span()
|
|
58
58
|
});
|
|
59
59
|
var assign = (targets, values) => ({ type: "AssignmentStatement", targets, values, ...span() });
|
|
@@ -401,6 +401,10 @@ var SharedRuntime = class {
|
|
|
401
401
|
return { declare, define };
|
|
402
402
|
}
|
|
403
403
|
};
|
|
404
|
+
function moduleArguments(context, name) {
|
|
405
|
+
const loader2 = context.loader?.(name);
|
|
406
|
+
return loader2 ? [string(name), loader2] : [string(name)];
|
|
407
|
+
}
|
|
404
408
|
function lower(program2, scopes, options = {}) {
|
|
405
409
|
const result2 = new Lowerer(program2, scopes, options).run();
|
|
406
410
|
dropUnreachable(result2.statements);
|
|
@@ -442,9 +446,9 @@ var Lowerer = class {
|
|
|
442
446
|
if (Array.isArray(value)) return void value.forEach(visit);
|
|
443
447
|
const node = value;
|
|
444
448
|
if (node.type === "VariableDeclaration" && node.name?.type === "IdentifierPattern" && node.name.name) {
|
|
445
|
-
let
|
|
446
|
-
while (
|
|
447
|
-
if (
|
|
449
|
+
let init2 = node.init;
|
|
450
|
+
while (init2?.type === "ParenthesizedExpression") init2 = init2.expression;
|
|
451
|
+
if (init2?.type === "ClassExpression") this.classExpressionNames.set(init2, node.name.name);
|
|
448
452
|
}
|
|
449
453
|
for (const [key, child] of Object.entries(value)) if (key !== "line" && key !== "column") visit(child);
|
|
450
454
|
};
|
|
@@ -717,6 +721,7 @@ var Lowerer = class {
|
|
|
717
721
|
const plugin3 = lowerings[i].plugin;
|
|
718
722
|
if (!plugin3.methodCall) continue;
|
|
719
723
|
const answer = plugin3.methodCall({
|
|
724
|
+
options: lowerings[i].options ?? {},
|
|
720
725
|
method,
|
|
721
726
|
receiver,
|
|
722
727
|
receiverGlobal,
|
|
@@ -748,6 +753,7 @@ var Lowerer = class {
|
|
|
748
753
|
const plugin3 = lowerings[i].plugin;
|
|
749
754
|
if (!plugin3.globalCall) continue;
|
|
750
755
|
const answer = plugin3.globalCall({
|
|
756
|
+
options: lowerings[i].options ?? {},
|
|
751
757
|
name,
|
|
752
758
|
at: this.callSite(node.callee),
|
|
753
759
|
get arguments() {
|
|
@@ -767,6 +773,7 @@ var Lowerer = class {
|
|
|
767
773
|
if (name === void 0) return void 0;
|
|
768
774
|
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
769
775
|
const answer = lowerings[i].plugin.globalValue?.({
|
|
776
|
+
options: lowerings[i].options ?? {},
|
|
770
777
|
name,
|
|
771
778
|
at: this.callSite(node),
|
|
772
779
|
use: (runtime2) => this.loweringRuntime(i, runtime2)
|
|
@@ -1020,12 +1027,12 @@ end`).body.statements, out2);
|
|
|
1020
1027
|
local2 = this.names.fresh(moduleName(key));
|
|
1021
1028
|
moduleLocals.set(key, local2);
|
|
1022
1029
|
locals.push(local2);
|
|
1023
|
-
requires.push(assign([identifier(local2)], [call(context.require,
|
|
1030
|
+
requires.push(assign([identifier(local2)], [call(context.require, moduleArguments(context, key))]));
|
|
1024
1031
|
}
|
|
1025
1032
|
return local2;
|
|
1026
1033
|
};
|
|
1027
1034
|
const load = (key) => {
|
|
1028
|
-
if (!moduleLocals.has(key)) requires.push(callStatement(call(context.require,
|
|
1035
|
+
if (!moduleLocals.has(key)) requires.push(callStatement(call(context.require, moduleArguments(context, key))));
|
|
1029
1036
|
};
|
|
1030
1037
|
const exportBinding = (node, exported) => {
|
|
1031
1038
|
const binding = node && this.bindingByDeclaration.get(node);
|
|
@@ -1134,20 +1141,22 @@ end`).body.statements, out2);
|
|
|
1134
1141
|
continue;
|
|
1135
1142
|
}
|
|
1136
1143
|
if (declaration.type === "ClassDeclaration") {
|
|
1137
|
-
body.push(...this.classDeclaration(declaration, "assign"));
|
|
1144
|
+
body.push(...this.classDeclaration(declaration, "assign").map((s) => this.from(s, declaration)));
|
|
1138
1145
|
continue;
|
|
1139
1146
|
}
|
|
1140
1147
|
switch (declaration.type) {
|
|
1148
|
+
// Stamped with where they were written, as any statement is:
|
|
1149
|
+
// an error in a top-level `const` names its line.
|
|
1141
1150
|
case "VariableDeclaration":
|
|
1142
|
-
body.push(...this.variableDeclaration(declaration, "assign"));
|
|
1151
|
+
body.push(...this.variableDeclaration(declaration, "assign").map((s) => this.from(s, declaration)));
|
|
1143
1152
|
break;
|
|
1144
1153
|
case "ExportDefaultStatement": {
|
|
1145
1154
|
const exported = declaration.declaration;
|
|
1146
1155
|
if (exported.type === "ClassDeclaration") {
|
|
1147
|
-
body.push(...this.classDeclaration(exported, "assign"));
|
|
1148
|
-
body.push(assign([member(exportsTable(), "default")], [this.reference(exported.name)]));
|
|
1156
|
+
body.push(...this.classDeclaration(exported, "assign").map((s) => this.from(s, exported)));
|
|
1157
|
+
body.push(this.from(assign([member(exportsTable(), "default")], [this.reference(exported.name)]), declaration));
|
|
1149
1158
|
} else {
|
|
1150
|
-
body.push(assign([member(exportsTable(), "default")], [this.expression(exported)]));
|
|
1159
|
+
body.push(this.from(assign([member(exportsTable(), "default")], [this.expression(exported)]), declaration));
|
|
1151
1160
|
}
|
|
1152
1161
|
break;
|
|
1153
1162
|
}
|
|
@@ -1185,7 +1194,7 @@ end`).body.statements, out2);
|
|
|
1185
1194
|
this.report(source, `Cannot find module '${source.value}'`);
|
|
1186
1195
|
return nil();
|
|
1187
1196
|
}
|
|
1188
|
-
return call(context.import ?? context.require,
|
|
1197
|
+
return call(context.import ?? context.require, moduleArguments(context, key));
|
|
1189
1198
|
}
|
|
1190
1199
|
isRewritten(node) {
|
|
1191
1200
|
const binding = this.bindingByDeclaration.get(node);
|
|
@@ -1384,9 +1393,9 @@ end`).body.statements, out2);
|
|
|
1384
1393
|
return this.assignment(node);
|
|
1385
1394
|
case "CompoundAssignmentStatement": {
|
|
1386
1395
|
if (node.operator === "??=") return this.nullishAssignment(node);
|
|
1387
|
-
const
|
|
1388
|
-
if (
|
|
1389
|
-
const operator =
|
|
1396
|
+
const join3 = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
|
|
1397
|
+
if (join3 === "dynamic") return this.dynamicAddition(node);
|
|
1398
|
+
const operator = join3 === "concat" ? "..=" : node.operator;
|
|
1390
1399
|
if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
|
|
1391
1400
|
return [{
|
|
1392
1401
|
type: "CompoundAssignmentStatement",
|
|
@@ -1650,11 +1659,11 @@ end`).body.statements, out2);
|
|
|
1650
1659
|
* carries neither. */
|
|
1651
1660
|
classRuntimeStatements() {
|
|
1652
1661
|
if (!this.classHelpers) return [];
|
|
1653
|
-
const { build, accessors } = this.classHelpers;
|
|
1662
|
+
const { build: build2, accessors } = this.classHelpers;
|
|
1654
1663
|
const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
|
|
1655
1664
|
const out2 = [];
|
|
1656
|
-
this.define([
|
|
1657
|
-
local function ${
|
|
1665
|
+
this.define([build2, accessors], (0, import_luau_parser.parse)(`
|
|
1666
|
+
local function ${build2}(base, name)
|
|
1658
1667
|
local class = { __getters = {}, __setters = {} }
|
|
1659
1668
|
class.__index = class
|
|
1660
1669
|
class.ClassObject = class
|
|
@@ -1713,9 +1722,9 @@ end
|
|
|
1713
1722
|
/** `declare` names the local the class table goes in; without it the
|
|
1714
1723
|
* table is assigned to `self`, which a module has already declared. */
|
|
1715
1724
|
classStatements(node, self, declare) {
|
|
1716
|
-
const { build, accessors } = this.classRuntime();
|
|
1725
|
+
const { build: build2, accessors } = this.classRuntime();
|
|
1717
1726
|
const called = node.name?.name ?? (node.type === "ClassExpression" ? this.classExpressionNames.get(node) : void 0) ?? "";
|
|
1718
|
-
const create = call(identifier(
|
|
1727
|
+
const create = call(identifier(build2), [
|
|
1719
1728
|
node.superclass ? this.reference(node.superclass) : nil(),
|
|
1720
1729
|
string(called)
|
|
1721
1730
|
]);
|
|
@@ -2072,9 +2081,9 @@ end
|
|
|
2072
2081
|
return call(this.helper("instanceof"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
2073
2082
|
}
|
|
2074
2083
|
if (node.operator === "+") {
|
|
2075
|
-
const
|
|
2076
|
-
if (
|
|
2077
|
-
if (
|
|
2084
|
+
const join3 = this.options.types?.joins.get(node);
|
|
2085
|
+
if (join3 === "concat") return binary("..", left, right);
|
|
2086
|
+
if (join3 === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
|
|
2078
2087
|
}
|
|
2079
2088
|
if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
|
|
2080
2089
|
return binary(node.operator, left, right);
|
|
@@ -3041,23 +3050,29 @@ function ownExport(f, name) {
|
|
|
3041
3050
|
// src/linemap.ts
|
|
3042
3051
|
var import_parser6 = require("@tilua/parser");
|
|
3043
3052
|
function statementsOf(node) {
|
|
3044
|
-
|
|
3045
|
-
|
|
3053
|
+
return statementTree(node).statements;
|
|
3054
|
+
}
|
|
3055
|
+
function statementTree(node) {
|
|
3056
|
+
const statements = [];
|
|
3057
|
+
const parents = [];
|
|
3058
|
+
const visit = (value, parent) => {
|
|
3046
3059
|
if (!value || typeof value !== "object") return;
|
|
3047
|
-
if (Array.isArray(value)) return void value.forEach(visit);
|
|
3060
|
+
if (Array.isArray(value)) return void value.forEach((item) => visit(item, parent));
|
|
3048
3061
|
const record = value;
|
|
3062
|
+
let inside = parent;
|
|
3049
3063
|
if (typeof record.type === "string" && record.type.endsWith("Statement")) {
|
|
3050
|
-
|
|
3064
|
+
inside = statements.push(value) - 1;
|
|
3065
|
+
parents.push(parent);
|
|
3051
3066
|
}
|
|
3052
3067
|
for (const [key, child] of Object.entries(record)) {
|
|
3053
|
-
if (key !== "line" && key !== "column") visit(child);
|
|
3068
|
+
if (key !== "line" && key !== "column") visit(child, inside);
|
|
3054
3069
|
}
|
|
3055
3070
|
};
|
|
3056
|
-
visit(node);
|
|
3057
|
-
return
|
|
3071
|
+
visit(node, -1);
|
|
3072
|
+
return { statements, parents };
|
|
3058
3073
|
}
|
|
3059
3074
|
function buildLineMap(emitted, printed, origins) {
|
|
3060
|
-
const before =
|
|
3075
|
+
const { statements: before, parents } = statementTree(emitted);
|
|
3061
3076
|
const after = statementsOf(printed);
|
|
3062
3077
|
if (before.length !== after.length) return /* @__PURE__ */ new Map();
|
|
3063
3078
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3068,6 +3083,18 @@ function buildLineMap(emitted, printed, origins) {
|
|
|
3068
3083
|
const line = after[i].line.start;
|
|
3069
3084
|
if (!map.has(line)) map.set(line, origin);
|
|
3070
3085
|
}
|
|
3086
|
+
const holds = /* @__PURE__ */ new Set();
|
|
3087
|
+
for (let i = 0; i < before.length; i++) {
|
|
3088
|
+
if (!origins.has(before[i])) continue;
|
|
3089
|
+
for (let up = parents[i]; up >= 0 && !holds.has(up); up = parents[up]) holds.add(up);
|
|
3090
|
+
}
|
|
3091
|
+
for (let i = 0; i < before.length; i++) {
|
|
3092
|
+
const origin = origins.get(before[i]);
|
|
3093
|
+
if (!origin || holds.has(i)) continue;
|
|
3094
|
+
for (let line = after[i].line.start + 1; line <= after[i].line.end; line++) {
|
|
3095
|
+
if (!map.has(line)) map.set(line, origin);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3071
3098
|
return map;
|
|
3072
3099
|
}
|
|
3073
3100
|
var VLQ_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -3099,6 +3126,34 @@ function lineMapSource(map, unpack) {
|
|
|
3099
3126
|
return `${unpack}({${files.map(import_parser6.luauString).join(",")}},${(0, import_parser6.luauString)(data)})`;
|
|
3100
3127
|
}
|
|
3101
3128
|
|
|
3129
|
+
// src/output.ts
|
|
3130
|
+
var import_luau_parser2 = require("luau-parser");
|
|
3131
|
+
var WRITTEN = /* @__PURE__ */ new Set([
|
|
3132
|
+
"compoundAssignment",
|
|
3133
|
+
"continue",
|
|
3134
|
+
"ifExpression",
|
|
3135
|
+
"floorDivision",
|
|
3136
|
+
"attributes",
|
|
3137
|
+
"numberLiterals"
|
|
3138
|
+
]);
|
|
3139
|
+
function printOptions(syntax, format = {}) {
|
|
3140
|
+
return {
|
|
3141
|
+
syntax: [...syntax].some((s) => WRITTEN.has(s)) ? "luau" : "lua51",
|
|
3142
|
+
compact: format.minify ?? false,
|
|
3143
|
+
...format.indent !== void 0 && { indent: format.indent }
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
function printOutput(node, options) {
|
|
3147
|
+
try {
|
|
3148
|
+
return { code: (0, import_luau_parser2.print)(node, options) };
|
|
3149
|
+
} catch (error) {
|
|
3150
|
+
if (!(error instanceof import_luau_parser2.PrintError)) throw error;
|
|
3151
|
+
return {
|
|
3152
|
+
problem: `The output cannot be written as Lua 5.1: ${error.message.replace(/ \(\d+:\d+\)$/, "")}. Nothing in 'types' says the project's Lua has it \u2014 "luau" does, and "roblox" brings "luau"`
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3102
3157
|
// src/lowering.ts
|
|
3103
3158
|
var import_node_url = require("url");
|
|
3104
3159
|
async function loadLowerings(modules) {
|
|
@@ -3115,7 +3170,7 @@ async function loadLowerings(modules) {
|
|
|
3115
3170
|
});
|
|
3116
3171
|
continue;
|
|
3117
3172
|
}
|
|
3118
|
-
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library });
|
|
3173
|
+
lowerings.push({ plugin: plugin3, from: module2.from, library: module2.library, options: module2.options });
|
|
3119
3174
|
} catch (error) {
|
|
3120
3175
|
problems.push({
|
|
3121
3176
|
file: module2.file,
|
|
@@ -3127,17 +3182,30 @@ async function loadLowerings(modules) {
|
|
|
3127
3182
|
}
|
|
3128
3183
|
|
|
3129
3184
|
// src/bundle.ts
|
|
3185
|
+
var WRITTEN_BY_TILUA = "-- Written by tilua";
|
|
3130
3186
|
async function bundle(options) {
|
|
3131
|
-
const entry2 = (0, import_node_path2.resolve)(options.entry);
|
|
3132
3187
|
const diagnostics = [];
|
|
3188
|
+
const from = options.entry !== void 0 ? (0, import_node_path2.resolve)(options.entry) : (0, import_node_path2.resolve)(options.root ?? process.cwd(), "tilua.config.json");
|
|
3133
3189
|
const inline = options.config !== void 0 && typeof options.config !== "string";
|
|
3134
|
-
const { config: config2, problems } = resolveConfig(options.config,
|
|
3190
|
+
const { config: config2, problems } = resolveConfig(options.config, from, options.root ?? (inline ? (0, import_node_path2.dirname)(from) : process.cwd()));
|
|
3135
3191
|
for (const p of problems) diagnostics.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
3136
|
-
const root = (0, import_node_path2.resolve)(options.root ?? config2?.directory ?? (0, import_node_path2.dirname)(
|
|
3192
|
+
const root = (0, import_node_path2.resolve)(options.root ?? config2?.directory ?? (0, import_node_path2.dirname)(from));
|
|
3137
3193
|
const nameOf = (file) => (0, import_node_path2.relative)(root, file).replace(/\\/g, "/").replace(/\.tilua$/, "");
|
|
3194
|
+
const mode = options.output ?? config2?.output ?? "single";
|
|
3195
|
+
const perFile = mode === "files";
|
|
3196
|
+
const rootDir = (0, import_node_path2.resolve)(config2?.rootDir ?? root);
|
|
3197
|
+
const entry2 = options.entry !== void 0 ? (0, import_node_path2.resolve)(options.entry) : void 0;
|
|
3198
|
+
const anchor = entry2 ?? config2?.path ?? from;
|
|
3199
|
+
const stop = (message, file = anchor) => ({
|
|
3200
|
+
files: [],
|
|
3201
|
+
modules: [],
|
|
3202
|
+
diagnostics: [...diagnostics, { file, message, line: 1, column: 1, category: "config" }]
|
|
3203
|
+
});
|
|
3204
|
+
if (!perFile && entry2 === void 0) return stop('No entry to build: only "output": "files" builds without one');
|
|
3138
3205
|
const sources = /* @__PURE__ */ new Map();
|
|
3139
3206
|
const directives = /* @__PURE__ */ new Map();
|
|
3140
|
-
const queue = [entry2];
|
|
3207
|
+
const queue = perFile ? tiluaFiles(rootDir) : [entry2];
|
|
3208
|
+
if (perFile && !queue.length) return stop(`No .tilua file under ${(0, import_node_path2.relative)(process.cwd(), rootDir) || "."}`);
|
|
3141
3209
|
while (queue.length) {
|
|
3142
3210
|
const file = queue.shift();
|
|
3143
3211
|
if (sources.has(file)) continue;
|
|
@@ -3155,6 +3223,22 @@ async function bundle(options) {
|
|
|
3155
3223
|
if (target && !target.endsWith(".d.tilua")) queue.push(target);
|
|
3156
3224
|
}
|
|
3157
3225
|
}
|
|
3226
|
+
if (perFile) {
|
|
3227
|
+
const outside = [...sources.keys()].filter((file) => isOutside(rootDir, file));
|
|
3228
|
+
if (outside.length) {
|
|
3229
|
+
return {
|
|
3230
|
+
files: [],
|
|
3231
|
+
modules: [],
|
|
3232
|
+
diagnostics: [...diagnostics, ...outside.map((file) => ({
|
|
3233
|
+
file,
|
|
3234
|
+
message: `This file is imported, but it is outside rootDir (${(0, import_node_path2.relative)(root, rootDir) || "."}), where every file of "files" output has to be`,
|
|
3235
|
+
line: 1,
|
|
3236
|
+
column: 1,
|
|
3237
|
+
category: "module"
|
|
3238
|
+
}))]
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3158
3242
|
const analysis = analyzeModules([...sources.values()], config2);
|
|
3159
3243
|
const { lowerings, problems: loweringProblems } = await loadLowerings(analysis.lowerings);
|
|
3160
3244
|
for (const p of loweringProblems) {
|
|
@@ -3165,24 +3249,61 @@ async function bundle(options) {
|
|
|
3165
3249
|
const G = names.fresh("G");
|
|
3166
3250
|
const requireExpression = member(identifier(G), "require");
|
|
3167
3251
|
const importExpression = member(identifier(G), "import");
|
|
3168
|
-
const mode = options.output ?? config2?.output ?? "single";
|
|
3169
3252
|
const byPath = syntaxOf(lowerings).has("requireByPath");
|
|
3253
|
+
if (perFile && !byPath) {
|
|
3254
|
+
return stop(`"output": "files" needs a Lua that requires a file by its path, which Lua 5.1 does not \u2014 add "luau" to 'types' ("roblox" loads it)`, config2?.path ?? anchor);
|
|
3255
|
+
}
|
|
3256
|
+
const outputPath = (file) => (0, import_node_path2.relative)(rootDir, file).replace(/\\/g, "/").replace(/\.tilua$/, ".luau");
|
|
3257
|
+
const fileOfModule = new Map([...sources.values()].map((s) => [s.name, s.file]));
|
|
3258
|
+
const outDir = (0, import_node_path2.resolve)(options.outDir ?? config2?.outDir ?? rootDir);
|
|
3259
|
+
const written = (file) => (0, import_node_path2.join)(outDir, outputPath(file));
|
|
3260
|
+
let host;
|
|
3261
|
+
let runtimeFile = (0, import_node_path2.join)(outDir, "_tilua.luau");
|
|
3262
|
+
if (perFile) {
|
|
3263
|
+
const outputs = [...sources.keys()].map(written);
|
|
3264
|
+
const taken = new Set(outputs.map(pathKey));
|
|
3265
|
+
for (let n = 2; taken.has(pathKey(runtimeFile)); n++) runtimeFile = (0, import_node_path2.join)(outDir, `_tilua${n}.luau`);
|
|
3266
|
+
for (let i = lowerings.length - 1; i >= 0 && !host; i--) {
|
|
3267
|
+
const { plugin: plugin3, options: own } = lowerings[i];
|
|
3268
|
+
host = plugin3.files?.({ options: own ?? {}, directory: config2?.directory ?? root, outDir, files: outputs, runtime: runtimeFile });
|
|
3269
|
+
}
|
|
3270
|
+
for (const problem of host?.problems ?? []) {
|
|
3271
|
+
diagnostics.push({
|
|
3272
|
+
file: problem.file ?? config2?.path ?? anchor,
|
|
3273
|
+
message: problem.message,
|
|
3274
|
+
line: 1,
|
|
3275
|
+
column: 1,
|
|
3276
|
+
category: "config",
|
|
3277
|
+
...problem.severity === "warning" && { severity: "warning" }
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
if (host?.runtime !== void 0) {
|
|
3281
|
+
if (isOutside(outDir, (0, import_node_path2.resolve)(host.runtime))) return stop(`The runtime has to go inside outDir, not at ${host.runtime}`);
|
|
3282
|
+
if (taken.has(pathKey(host.runtime))) return stop(`The runtime cannot go at ${host.runtime}: a module's file is there`);
|
|
3283
|
+
runtimeFile = (0, import_node_path2.resolve)(host.runtime);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
const placeOf = (file) => host?.place?.(file) ?? placeInFolder((0, import_node_path2.relative)(outDir, file));
|
|
3287
|
+
const requireFrom = (from2, to) => pathBetween(placeOf(from2), placeOf(to), host?.root);
|
|
3288
|
+
const isScript = (file) => host?.script?.(file) ?? false;
|
|
3170
3289
|
const modules = /* @__PURE__ */ new Map();
|
|
3171
3290
|
const origins = /* @__PURE__ */ new WeakMap();
|
|
3172
3291
|
const shared = new SharedRuntime(names);
|
|
3173
3292
|
const shaking = options.treeShaking ?? config2?.treeShaking ?? false;
|
|
3174
|
-
const
|
|
3293
|
+
const format = { indent: options.indent ?? config2?.indent, minify: options.minify ?? config2?.minify ?? false };
|
|
3294
|
+
const printing = printOptions(syntaxOf(lowerings), format);
|
|
3295
|
+
const mapping = !format.minify && (options.lineMap ?? config2?.lineMap ?? true);
|
|
3175
3296
|
const resolveIn = (file) => (specifier) => {
|
|
3176
3297
|
const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
3177
3298
|
return found !== void 0 && sources.has(found) ? found : void 0;
|
|
3178
3299
|
};
|
|
3179
|
-
const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry2) : void 0;
|
|
3180
|
-
const pending = [entry2];
|
|
3300
|
+
const shaken = shaking && entry2 !== void 0 && !perFile ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry2) : void 0;
|
|
3301
|
+
const pending = perFile ? [...sources.keys()] : [entry2];
|
|
3181
3302
|
while (pending.length) {
|
|
3182
3303
|
const file = pending.shift();
|
|
3183
3304
|
if (modules.has(file)) continue;
|
|
3184
3305
|
const source = sources.get(file);
|
|
3185
|
-
const
|
|
3306
|
+
const shown2 = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
|
|
3186
3307
|
const imports = /* @__PURE__ */ new Set();
|
|
3187
3308
|
const dynamicImports = /* @__PURE__ */ new Set();
|
|
3188
3309
|
const lowered2 = lower(source.program, source.scopes, {
|
|
@@ -3191,7 +3312,7 @@ async function bundle(options) {
|
|
|
3191
3312
|
lowerings,
|
|
3192
3313
|
noLib: config2?.noLib,
|
|
3193
3314
|
source: source.text,
|
|
3194
|
-
file:
|
|
3315
|
+
file: shown2,
|
|
3195
3316
|
shared,
|
|
3196
3317
|
shaken: shaken && {
|
|
3197
3318
|
...shaken.modules.get(file),
|
|
@@ -3213,6 +3334,13 @@ async function bundle(options) {
|
|
|
3213
3334
|
lines: !mapping ? void 0 : mode === "single" ? `${G}.lines` : `${G}.maps[${(0, import_parser7.luauString)(source.name)}]`,
|
|
3214
3335
|
fail: mapping ? `${G}.fail` : void 0,
|
|
3215
3336
|
arrays: `${G}.arrays`,
|
|
3337
|
+
// The file a module is in, required from this one by its
|
|
3338
|
+
// path, written out.
|
|
3339
|
+
loader: !perFile ? void 0 : (name) => {
|
|
3340
|
+
const target = fileOfModule.get(name);
|
|
3341
|
+
if (target === void 0) return void 0;
|
|
3342
|
+
return (0, import_luau_parser3.parseExpressionFromSource)(`function() return require(${(0, import_parser7.luauString)(requireFrom(written(file), written(target)))}) end`);
|
|
3343
|
+
},
|
|
3216
3344
|
resolve: (specifier, dynamic) => {
|
|
3217
3345
|
const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
|
|
3218
3346
|
if (!found || found.endsWith(".d.tilua") || !sources.has(found)) return void 0;
|
|
@@ -3226,7 +3354,7 @@ async function bundle(options) {
|
|
|
3226
3354
|
for (const d of lowered2.diagnostics) diagnostics.push({ file, ...d, category: "module" });
|
|
3227
3355
|
for (const statement of allStatements(lowered2.statements)) {
|
|
3228
3356
|
const line = lowered2.origins.get(statement);
|
|
3229
|
-
if (line !== void 0) origins.set(statement, { file:
|
|
3357
|
+
if (line !== void 0) origins.set(statement, { file: shown2, line });
|
|
3230
3358
|
}
|
|
3231
3359
|
modules.set(file, {
|
|
3232
3360
|
name: source.name,
|
|
@@ -3264,6 +3392,102 @@ async function bundle(options) {
|
|
|
3264
3392
|
return { files: [], modules: moduleNames, diagnostics };
|
|
3265
3393
|
}
|
|
3266
3394
|
if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
|
|
3395
|
+
const unwritable = (problem) => ({
|
|
3396
|
+
files: [],
|
|
3397
|
+
modules: moduleNames,
|
|
3398
|
+
diagnostics: [...diagnostics, { file: anchor, message: problem, line: 1, column: 1, category: "config" }]
|
|
3399
|
+
});
|
|
3400
|
+
const write = (node) => {
|
|
3401
|
+
const printed2 = printOutput(node, printing);
|
|
3402
|
+
return "problem" in printed2 ? printed2 : printed2.code;
|
|
3403
|
+
};
|
|
3404
|
+
if (perFile) {
|
|
3405
|
+
const imported = [];
|
|
3406
|
+
for (const [file, source] of sources) {
|
|
3407
|
+
for (const { specifier, line, column } of importSites(source.program)) {
|
|
3408
|
+
const target = resolveIn(file)(specifier);
|
|
3409
|
+
if (target === void 0 || !isScript(written(target))) continue;
|
|
3410
|
+
imported.push({
|
|
3411
|
+
file,
|
|
3412
|
+
line,
|
|
3413
|
+
column,
|
|
3414
|
+
category: "module",
|
|
3415
|
+
message: `'${specifier}' is a script the game runs, which nothing can import: move what it shares into a module of its own`
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
if (imported.length) return { files: [], modules: moduleNames, diagnostics: [...diagnostics, ...imported] };
|
|
3420
|
+
const runtimePath = (0, import_node_path2.relative)(outDir, runtimeFile).replace(/\\/g, "/");
|
|
3421
|
+
const files2 = [];
|
|
3422
|
+
for (const [file, module2] of modules) {
|
|
3423
|
+
const path = outputPath(file);
|
|
3424
|
+
const at = written(file);
|
|
3425
|
+
const script = isScript(at);
|
|
3426
|
+
const prelude = host?.prelude?.(at);
|
|
3427
|
+
const body = [
|
|
3428
|
+
...prelude !== void 0 ? (0, import_luau_parser3.parse)(prelude).body.statements : [],
|
|
3429
|
+
...(0, import_luau_parser3.parse)(`local ${G} = require(${(0, import_parser7.luauString)(requireFrom(at, runtimeFile))})`).body.statements
|
|
3430
|
+
];
|
|
3431
|
+
for (let i = 0; i < module2.shared.length; i += 100) {
|
|
3432
|
+
const some = module2.shared.slice(i, i + 100);
|
|
3433
|
+
body.push(local(some, some.map((name) => member(member(identifier(G), "shared"), name))));
|
|
3434
|
+
}
|
|
3435
|
+
if (module2.usesScriptArgs) body.push(...(0, import_luau_parser3.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
3436
|
+
body.push(assign(
|
|
3437
|
+
[index(member(identifier(G), "modules"), string(module2.name))],
|
|
3438
|
+
[moduleTable(module2)]
|
|
3439
|
+
));
|
|
3440
|
+
const value = `${G}.${mapping ? "run" : "require"}(${(0, import_parser7.luauString)(module2.name)})`;
|
|
3441
|
+
const returned2 = module2.info.exports && !script ? entryReturn(lowerings, module2.name, value, () => analysis.exportsOf(file)) : { statements: [], value };
|
|
3442
|
+
if ("problem" in returned2) return stop(returned2.problem.message, file);
|
|
3443
|
+
const ending = (0, import_luau_parser3.parse)(`${returned2.statements.join("\n")}
|
|
3444
|
+
return ${returned2.value}`).body.statements;
|
|
3445
|
+
const chunk = program(body);
|
|
3446
|
+
if (mapping) {
|
|
3447
|
+
const text = write(chunk);
|
|
3448
|
+
if (typeof text !== "string") return unwritable(text.problem);
|
|
3449
|
+
const lines = buildLineMap(chunk, (0, import_luau_parser3.parse)(text), origins);
|
|
3450
|
+
const named = host?.place?.(at) ? `${placeOf(at).join("/")}.luau` : path;
|
|
3451
|
+
body.push(...mapRegistration(G, { path: named, modules: [module2] }, lineMapSource(lines, `${G}.lineMap`)));
|
|
3452
|
+
}
|
|
3453
|
+
body.push(...ending);
|
|
3454
|
+
const code = write(chunk);
|
|
3455
|
+
if (typeof code !== "string") return unwritable(code.problem);
|
|
3456
|
+
const from2 = (0, import_node_path2.relative)((0, import_node_path2.dirname)(at), file).replace(/\\/g, "/");
|
|
3457
|
+
files2.push({ path, code: `${code}
|
|
3458
|
+
${WRITTEN_BY_TILUA} from ${from2}: edit that file, not this one.
|
|
3459
|
+
`, modules: [module2.name] });
|
|
3460
|
+
}
|
|
3461
|
+
const runtimeProgram = (0, import_luau_parser3.parse)(runtime(G, {
|
|
3462
|
+
several: true,
|
|
3463
|
+
perFile: true,
|
|
3464
|
+
byPath,
|
|
3465
|
+
files: /* @__PURE__ */ new Map(),
|
|
3466
|
+
imports: lowered.some((m) => m.dynamicImports.size > 0),
|
|
3467
|
+
mapping
|
|
3468
|
+
}));
|
|
3469
|
+
const helpers2 = shared.chunk(new Set(lowered.flatMap((m) => m.shared)));
|
|
3470
|
+
const helperNames = helpers2.declare.flatMap((statement) => statement.type === "LocalStatement" ? statement.names.map((n) => n.name) : []);
|
|
3471
|
+
runtimeProgram.body.statements.unshift(...helpers2.declare);
|
|
3472
|
+
runtimeProgram.body.statements.push(...helpers2.define);
|
|
3473
|
+
if (helperNames.length) {
|
|
3474
|
+
runtimeProgram.body.statements.push(assign(
|
|
3475
|
+
[member(identifier(G), "shared")],
|
|
3476
|
+
[table(helperNames.map((name) => field(name, identifier(name))))]
|
|
3477
|
+
));
|
|
3478
|
+
}
|
|
3479
|
+
if (lowered.some((m) => m.usesScriptArgs)) {
|
|
3480
|
+
runtimeProgram.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.scriptArgs = {}
|
|
3481
|
+
${G}.arrays[${G}.scriptArgs] = true`).body.statements);
|
|
3482
|
+
}
|
|
3483
|
+
runtimeProgram.body.statements.push(returns([identifier(G)]));
|
|
3484
|
+
const runtimeCode = write(runtimeProgram);
|
|
3485
|
+
if (typeof runtimeCode !== "string") return unwritable(runtimeCode.problem);
|
|
3486
|
+
files2.push({ path: runtimePath, code: `${runtimeCode}
|
|
3487
|
+
${WRITTEN_BY_TILUA}: the runtime every file of the project requires.
|
|
3488
|
+
`, modules: [] });
|
|
3489
|
+
return { code: files2[0].code, files: files2, modules: moduleNames, diagnostics };
|
|
3490
|
+
}
|
|
3267
3491
|
const several = mode !== "single";
|
|
3268
3492
|
const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
|
|
3269
3493
|
const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
|
|
@@ -3271,7 +3495,7 @@ async function bundle(options) {
|
|
|
3271
3495
|
for (const file of layout.slice(1)) {
|
|
3272
3496
|
for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
|
|
3273
3497
|
}
|
|
3274
|
-
const program2 = (0,
|
|
3498
|
+
const program2 = (0, import_luau_parser3.parse)(runtime(G, {
|
|
3275
3499
|
several,
|
|
3276
3500
|
byPath,
|
|
3277
3501
|
files: elsewhere,
|
|
@@ -3281,8 +3505,8 @@ async function bundle(options) {
|
|
|
3281
3505
|
const assignment = program2.body.statements[1];
|
|
3282
3506
|
if (usesScriptArgs) {
|
|
3283
3507
|
program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
|
|
3284
|
-
program2.body.statements.push(...(0,
|
|
3285
|
-
if (several) program2.body.statements.push(...(0,
|
|
3508
|
+
program2.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.arrays[scriptArgs] = true`).body.statements);
|
|
3509
|
+
if (several) program2.body.statements.push(...(0, import_luau_parser3.parse)(`${G}.scriptArgs = scriptArgs`).body.statements);
|
|
3286
3510
|
}
|
|
3287
3511
|
const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
|
|
3288
3512
|
if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
|
|
@@ -3295,31 +3519,40 @@ async function bundle(options) {
|
|
|
3295
3519
|
const result2 = names.fresh("result");
|
|
3296
3520
|
const entryName = (0, import_parser7.luauString)(sources.get(entry2).name);
|
|
3297
3521
|
const returnsExports = modules.get(entry2).info.exports;
|
|
3298
|
-
const
|
|
3522
|
+
const returned = returnsExports ? entryReturn(lowerings, sources.get(entry2).name, mapping ? result2 : `${G}.require(${entryName})`, () => analysis.exportsOf(entry2)) : void 0;
|
|
3523
|
+
if (returned && "problem" in returned) return stop(returned.problem.message);
|
|
3524
|
+
const declarations = returned?.statements.join("\n") ?? "";
|
|
3525
|
+
const entryStatements = (0, import_luau_parser3.parse)(!mapping ? `${declarations}
|
|
3526
|
+
${returned ? `return ${returned.value}` : `${G}.require(${entryName})`}` : `
|
|
3299
3527
|
local ${ok}, ${result2} = xpcall(function()
|
|
3300
3528
|
return ${G}.require(${entryName})
|
|
3301
3529
|
end, ${G}.fail)
|
|
3302
3530
|
if not ${ok} then
|
|
3303
3531
|
error(${result2}, 0)
|
|
3304
3532
|
end
|
|
3305
|
-
${
|
|
3533
|
+
${declarations}
|
|
3534
|
+
${returned ? `return ${returned.value}` : ""}
|
|
3306
3535
|
`).body.statements;
|
|
3307
3536
|
program2.body.statements.push(...entryStatements);
|
|
3308
|
-
|
|
3537
|
+
let printed = write(program2);
|
|
3538
|
+
if (typeof printed !== "string") return unwritable(printed.problem);
|
|
3539
|
+
const map = mapping ? buildLineMap(program2, (0, import_luau_parser3.parse)(printed), origins) : /* @__PURE__ */ new Map();
|
|
3309
3540
|
if (mapping && (map.size || several)) {
|
|
3310
3541
|
const mapStatements = [assign(
|
|
3311
3542
|
[member(identifier(G), "lines")],
|
|
3312
|
-
[(0,
|
|
3543
|
+
[(0, import_luau_parser3.parseExpressionFromSource)(lineMapSource(map, `${G}.lineMap`))]
|
|
3313
3544
|
)];
|
|
3314
3545
|
if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
|
|
3315
3546
|
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
|
|
3316
3547
|
}
|
|
3317
3548
|
program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
|
|
3318
|
-
|
|
3549
|
+
printed = write(program2);
|
|
3550
|
+
if (typeof printed !== "string") return unwritable(printed.problem);
|
|
3551
|
+
const files = [{ path: layout[0].path, code: printed + "\n", modules: layout[0].modules.map((m) => m.name) }];
|
|
3319
3552
|
for (const file of layout.slice(1)) {
|
|
3320
3553
|
const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
|
|
3321
3554
|
const body = [...fileHelpers.declare];
|
|
3322
|
-
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0,
|
|
3555
|
+
if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser3.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
|
|
3323
3556
|
for (const module2 of file.modules) {
|
|
3324
3557
|
body.push(assign(
|
|
3325
3558
|
[index(member(identifier(G), "modules"), string(module2.name))],
|
|
@@ -3328,14 +3561,34 @@ ${returnsExports ? `return ${result2}` : ""}
|
|
|
3328
3561
|
}
|
|
3329
3562
|
const chunk = program([returns([functionExpression(functionBody([G], body))])]);
|
|
3330
3563
|
if (mapping) {
|
|
3331
|
-
const lines = buildLineMap(chunk, (0,
|
|
3564
|
+
const lines = buildLineMap(chunk, (0, import_luau_parser3.parse)((0, import_luau_parser3.print)(chunk, printing)), origins);
|
|
3332
3565
|
body.push(...mapRegistration(G, file, lineMapSource(lines, `${G}.lineMap`)));
|
|
3333
3566
|
}
|
|
3334
3567
|
body.push(...fileHelpers.define);
|
|
3335
|
-
|
|
3568
|
+
const code = write(chunk);
|
|
3569
|
+
if (typeof code !== "string") return unwritable(code.problem);
|
|
3570
|
+
files.push({ path: file.path, code: code + "\n", modules: file.modules.map((m) => m.name) });
|
|
3336
3571
|
}
|
|
3337
3572
|
return { code: files[0].code, files, modules: moduleNames, diagnostics };
|
|
3338
3573
|
}
|
|
3574
|
+
function entryReturn(lowerings, module2, value, exports2) {
|
|
3575
|
+
const syntax = [...syntaxOf(lowerings)];
|
|
3576
|
+
for (let i = lowerings.length - 1; i >= 0; i--) {
|
|
3577
|
+
const { plugin: plugin3, from, options } = lowerings[i];
|
|
3578
|
+
if (!plugin3.entryExports) continue;
|
|
3579
|
+
const answer = plugin3.entryExports({ options: options ?? {}, module: module2, value, exports: exports2(), syntax });
|
|
3580
|
+
if (!answer) continue;
|
|
3581
|
+
const statements = [...answer.statements ?? []];
|
|
3582
|
+
try {
|
|
3583
|
+
(0, import_luau_parser3.parse)(`${statements.join("\n")}
|
|
3584
|
+
return ${answer.value}`);
|
|
3585
|
+
} catch (error) {
|
|
3586
|
+
return { problem: { message: `'${from}' answered entryExports with Luau that does not parse: ${error.message}` } };
|
|
3587
|
+
}
|
|
3588
|
+
return { statements, value: answer.value };
|
|
3589
|
+
}
|
|
3590
|
+
return { statements: [], value };
|
|
3591
|
+
}
|
|
3339
3592
|
function moduleTable(module2) {
|
|
3340
3593
|
const { names: exported, links, stars } = module2.info;
|
|
3341
3594
|
const fields = [];
|
|
@@ -3355,10 +3608,6 @@ function layoutOf(mode, entry2, modules, outFile) {
|
|
|
3355
3608
|
const entryStem = outFile !== void 0 ? (0, import_node_path2.basename)(outFile, (0, import_node_path2.extname)(outFile)) : import_node_path2.posix.basename(entry2.name);
|
|
3356
3609
|
const others = modules.filter((m) => m !== entry2);
|
|
3357
3610
|
if (mode === "single") return [{ path: `${entryStem}${extension}`, modules: [entry2, ...others] }];
|
|
3358
|
-
if (mode === "modules") {
|
|
3359
|
-
const pathOf = (m) => `${m.name.split("/").map((part) => part === ".." ? "__" : part).join("/")}${extension}`;
|
|
3360
|
-
return [entry2, ...others].map((m) => ({ path: pathOf(m), modules: [m] }));
|
|
3361
|
-
}
|
|
3362
3611
|
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
3363
3612
|
const reach = (root) => {
|
|
3364
3613
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3394,16 +3643,75 @@ function layoutOf(mode, entry2, modules, outFile) {
|
|
|
3394
3643
|
return files;
|
|
3395
3644
|
}
|
|
3396
3645
|
function loadPath(entryPath, file, byPath) {
|
|
3397
|
-
|
|
3398
|
-
|
|
3646
|
+
return byPath ? requirePath(entryPath, file) : import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
|
|
3647
|
+
}
|
|
3648
|
+
function placeInFolder(path) {
|
|
3649
|
+
const parts = path.replace(/\\/g, "/").replace(/\.luau?$/, "").split("/").filter((part) => part !== "");
|
|
3650
|
+
if (parts.at(-1) === "init") parts.pop();
|
|
3651
|
+
return parts;
|
|
3652
|
+
}
|
|
3653
|
+
function pathBetween(from, to, root) {
|
|
3654
|
+
if (root !== void 0 && from[0] !== to[0]) return `${root}/${to.join("/")}`;
|
|
3655
|
+
if (to.length > from.length && from.every((part, i) => to[i] === part)) return `@self/${to.slice(from.length).join("/")}`;
|
|
3656
|
+
const base = from.slice(0, -1);
|
|
3657
|
+
let shared = 0;
|
|
3658
|
+
while (shared < base.length && shared < to.length - 1 && base[shared] === to[shared]) shared++;
|
|
3659
|
+
const up = base.length - shared;
|
|
3660
|
+
return `${up === 0 ? "./" : "../".repeat(up)}${to.slice(shared).join("/")}`;
|
|
3661
|
+
}
|
|
3662
|
+
function importSites(program2) {
|
|
3663
|
+
const sites = [];
|
|
3664
|
+
for (const statement of program2.body.statements) {
|
|
3665
|
+
if (statement.type === "ImportStatement" || statement.type === "ExportAllStatement" || statement.type === "ExportNamedStatement" && statement.source) {
|
|
3666
|
+
const source = statement.source;
|
|
3667
|
+
sites.push({ specifier: source.value, line: source.line.start, column: source.column.start });
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
for (const node of (0, import_parser7.importExpressions)(program2)) {
|
|
3671
|
+
if (node.source.type === "StringLiteral") sites.push({ specifier: node.source.value, line: node.source.line.start, column: node.source.column.start });
|
|
3672
|
+
}
|
|
3673
|
+
return sites;
|
|
3674
|
+
}
|
|
3675
|
+
function pathKey(path) {
|
|
3676
|
+
const full = (0, import_node_path2.resolve)(path);
|
|
3677
|
+
return process.platform === "win32" ? full.toLowerCase() : full;
|
|
3678
|
+
}
|
|
3679
|
+
function requirePath(from, to) {
|
|
3680
|
+
const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(from), to);
|
|
3399
3681
|
const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
|
|
3400
3682
|
return bare.startsWith("../") ? bare : `./${bare}`;
|
|
3401
3683
|
}
|
|
3684
|
+
function tiluaFiles(folder) {
|
|
3685
|
+
const found = [];
|
|
3686
|
+
const walk2 = (at) => {
|
|
3687
|
+
let entries;
|
|
3688
|
+
try {
|
|
3689
|
+
entries = (0, import_node_fs3.readdirSync)(at, { withFileTypes: true });
|
|
3690
|
+
} catch {
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
for (const item of entries) {
|
|
3694
|
+
if (item.name.startsWith(".")) continue;
|
|
3695
|
+
const path = (0, import_node_path2.join)(at, item.name);
|
|
3696
|
+
if (item.isDirectory()) {
|
|
3697
|
+
if (item.name !== "node_modules") walk2(path);
|
|
3698
|
+
} else if (item.name.endsWith(".tilua") && !item.name.endsWith(".d.tilua")) {
|
|
3699
|
+
found.push(path);
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3703
|
+
walk2(folder);
|
|
3704
|
+
return found.sort();
|
|
3705
|
+
}
|
|
3706
|
+
function isOutside(folder, file) {
|
|
3707
|
+
const path = (0, import_node_path2.relative)(folder, file);
|
|
3708
|
+
return path === "" || path.startsWith("..") || (0, import_node_path2.isAbsolute)(path);
|
|
3709
|
+
}
|
|
3402
3710
|
function mapRegistration(G, file, map) {
|
|
3403
3711
|
const extension = import_node_path2.posix.extname(file.path);
|
|
3404
3712
|
const parts = file.path.slice(0, file.path.length - extension.length).split(/[/.]/).filter((part) => part !== "");
|
|
3405
3713
|
const entry2 = `${G}.sources[#${G}.sources + 1]`;
|
|
3406
|
-
return (0,
|
|
3714
|
+
return (0, import_luau_parser3.parse)([
|
|
3407
3715
|
`${entry2} = { { ${parts.map(import_parser7.luauString).join(", ")} }, ${map}, ${(0, import_parser7.luauString)(extension.slice(1))} }`,
|
|
3408
3716
|
...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
|
|
3409
3717
|
].join("\n")).body.statements;
|
|
@@ -3430,6 +3738,7 @@ function loader(G, byPath) {
|
|
|
3430
3738
|
}
|
|
3431
3739
|
function runtime(G, options) {
|
|
3432
3740
|
const { several, mapping } = options;
|
|
3741
|
+
const perFile = options.perFile ?? false;
|
|
3433
3742
|
const files = [...options.files].map(([name, file]) => `[${(0, import_parser7.luauString)(name)}] = ${(0, import_parser7.luauString)(file)}`).join(", ");
|
|
3434
3743
|
return `
|
|
3435
3744
|
local ${G}
|
|
@@ -3641,32 +3950,60 @@ ${several ? `
|
|
|
3641
3950
|
return map[key]
|
|
3642
3951
|
end })
|
|
3643
3952
|
end,` : ""}
|
|
3644
|
-
records = {},${several ? `
|
|
3953
|
+
records = {},${several && !perFile ? `
|
|
3645
3954
|
-- The file each module outside this one is in, as \`load\` is handed it.
|
|
3646
3955
|
files = { ${files} },
|
|
3647
|
-
loaded = {}
|
|
3956
|
+
loaded = {},` : ""}${several && mapping ? `
|
|
3648
3957
|
-- Every file's line map: by the module, and by the file's name.
|
|
3649
3958
|
maps = {},
|
|
3650
|
-
sources = {},` : ""}
|
|
3959
|
+
sources = {},` : ""}${several && !perFile ? `
|
|
3651
3960
|
-- Another of the bundle's files: a function that adds its modules here.
|
|
3652
3961
|
load = function(file)
|
|
3653
3962
|
if ${G}.loaded[file] then
|
|
3654
3963
|
return
|
|
3655
3964
|
end
|
|
3656
3965
|
${G}.loaded[file] = true${loader(G, options.byPath)}
|
|
3966
|
+
end,` : ""}${perFile && mapping ? `
|
|
3967
|
+
-- What a file returns: its module's exports. The first file asked for
|
|
3968
|
+
-- runs under \`xpcall\`, so an error nothing caught is reported in the
|
|
3969
|
+
-- project's files; the files it requires in turn are inside that already.
|
|
3970
|
+
running = false,
|
|
3971
|
+
run = function(name)
|
|
3972
|
+
if ${G}.running then
|
|
3973
|
+
return ${G}.require(name)
|
|
3974
|
+
end
|
|
3975
|
+
${G}.running = true
|
|
3976
|
+
local ok, result = xpcall(${G}.require, ${G}.fail, name)
|
|
3977
|
+
${G}.running = false
|
|
3978
|
+
if not ok then
|
|
3979
|
+
error(result, 0)
|
|
3980
|
+
end
|
|
3981
|
+
return result
|
|
3657
3982
|
end,` : ""}${options.imports ? `
|
|
3658
3983
|
-- \`import("./x")\`: the module's exports, asked for where the code runs
|
|
3659
3984
|
-- rather than before its module does. \`require\` does the rest \u2014 and
|
|
3660
3985
|
-- loads the file the module is in, if it is not this one.
|
|
3661
|
-
import = function(name)
|
|
3662
|
-
return ${G}.require(name)
|
|
3986
|
+
import = function(name${perFile ? ", load" : ""})
|
|
3987
|
+
return ${G}.require(name${perFile ? ", load" : ""})
|
|
3663
3988
|
end,` : ""}
|
|
3664
|
-
require = function(name)
|
|
3989
|
+
require = function(name${perFile ? ", load" : ""})
|
|
3665
3990
|
local record = ${G}.records[name]
|
|
3666
3991
|
if record ~= nil then
|
|
3667
3992
|
return record.exports
|
|
3668
3993
|
end
|
|
3669
|
-
local module = ${G}.modules[name]${
|
|
3994
|
+
local module = ${G}.modules[name]${perFile ? `
|
|
3995
|
+
-- Its file has not run: requiring it adds the module, and runs it.
|
|
3996
|
+
if module == nil and load ~= nil then
|
|
3997
|
+
load()
|
|
3998
|
+
record = ${G}.records[name]
|
|
3999
|
+
if record ~= nil then
|
|
4000
|
+
return record.exports
|
|
4001
|
+
end
|
|
4002
|
+
module = ${G}.modules[name]
|
|
4003
|
+
end
|
|
4004
|
+
if module == nil then
|
|
4005
|
+
error(("Cannot find module '%s': nothing has loaded the file it is in"):format(name), 2)
|
|
4006
|
+
end` : several ? `
|
|
3670
4007
|
if module == nil then
|
|
3671
4008
|
${G}.load(${G}.files[name])
|
|
3672
4009
|
module = ${G}.modules[name]
|
|
@@ -3754,6 +4091,35 @@ function importedSpecifiers(program2) {
|
|
|
3754
4091
|
return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
|
|
3755
4092
|
}
|
|
3756
4093
|
var libraryCache = /* @__PURE__ */ new Map();
|
|
4094
|
+
var treeCache;
|
|
4095
|
+
function instanceTree(config2, libs, noLib) {
|
|
4096
|
+
const tree = (0, import_parser7.sourceMapOf)(config2);
|
|
4097
|
+
if (!tree) return void 0;
|
|
4098
|
+
if ("problem" in tree) return tree.named ? { problem: { file: config2.path, message: tree.problem } } : void 0;
|
|
4099
|
+
const declares = (name) => libs.some((lib) => lib.body.statements.some((s) => s.type === "DeclareStatement" && s.name === name));
|
|
4100
|
+
if (!tree.named && !declares("game")) return void 0;
|
|
4101
|
+
if (treeCache && treeCache.text === tree.text && treeCache.libs.length === libs.length && treeCache.libs.every((lib, i) => lib === libs[i])) {
|
|
4102
|
+
return { types: treeCache.types };
|
|
4103
|
+
}
|
|
4104
|
+
const empty = (0, import_parser7.parse)("");
|
|
4105
|
+
const aliases = (0, import_parser7.analyzeTypes)(empty, (0, import_parser7.analyzeScopes)(empty, { noLib }), { libs, noLib, diagnostics: false }).aliases;
|
|
4106
|
+
const membersOf = (className) => {
|
|
4107
|
+
const type = aliases.get(className);
|
|
4108
|
+
return new Set(type?.kind === "object" ? type.properties.keys() : ["Name", "ClassName", "Parent", "Archivable"]);
|
|
4109
|
+
};
|
|
4110
|
+
const { types, problem } = (0, import_parser7.sourceMapTypes)(tree.text, tree.path, { classes: new Set(aliases.keys()), membersOf });
|
|
4111
|
+
if (!types) return tree.named && problem ? { problem: { file: tree.path, message: problem } } : void 0;
|
|
4112
|
+
const scripts = /* @__PURE__ */ new Map();
|
|
4113
|
+
const cached = {
|
|
4114
|
+
program: types.program,
|
|
4115
|
+
scriptFor: (file) => {
|
|
4116
|
+
if (!scripts.has(file)) scripts.set(file, types.scriptFor(file));
|
|
4117
|
+
return scripts.get(file);
|
|
4118
|
+
}
|
|
4119
|
+
};
|
|
4120
|
+
treeCache = { text: tree.text, libs: [...libs], types: cached, scripts };
|
|
4121
|
+
return { types: cached };
|
|
4122
|
+
}
|
|
3757
4123
|
function parsedLibrary(file) {
|
|
3758
4124
|
let stamp;
|
|
3759
4125
|
try {
|
|
@@ -3772,11 +4138,15 @@ var moduleAnalyses = /* @__PURE__ */ new Map();
|
|
|
3772
4138
|
function analyzeModules(modules, config2) {
|
|
3773
4139
|
const out2 = [];
|
|
3774
4140
|
const analyses = /* @__PURE__ */ new Map();
|
|
3775
|
-
const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], problems: [] };
|
|
4141
|
+
const libraries = config2 ? (0, import_parser7.resolveTypeLibraries)(config2) : { files: [], lowerings: [], options: [], problems: [] };
|
|
3776
4142
|
for (const p of libraries.problems) out2.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
|
|
3777
4143
|
const libs = libraries.files.map(parsedLibrary);
|
|
3778
4144
|
const noLib = config2?.noLib ?? false;
|
|
3779
4145
|
const checked = libs.length > 0 || !noLib;
|
|
4146
|
+
const tree = config2 ? instanceTree(config2, libs, noLib) : void 0;
|
|
4147
|
+
if (tree?.problem) out2.push({ file: tree.problem.file, message: tree.problem.message, line: 1, column: 1, category: "config" });
|
|
4148
|
+
if (tree?.types) libs.push(tree.types.program);
|
|
4149
|
+
const scriptOf = (file) => tree?.types && config2 ? tree.types.scriptFor((0, import_parser7.outputPathOf)(config2, file)) : void 0;
|
|
3780
4150
|
const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
|
|
3781
4151
|
const byFile = new Map(modules.map((m) => [m.file, m]));
|
|
3782
4152
|
const exportsCache = /* @__PURE__ */ new Map();
|
|
@@ -3789,7 +4159,9 @@ function analyzeModules(modules, config2) {
|
|
|
3789
4159
|
const done = analyzed.get(file);
|
|
3790
4160
|
if (done) return done;
|
|
3791
4161
|
const remembered = moduleAnalyses.get(file);
|
|
3792
|
-
|
|
4162
|
+
const script = scriptOf(file);
|
|
4163
|
+
const ownLibs = script ? [...libs, script] : libs;
|
|
4164
|
+
if (remembered && remembered.setting === settingFor && ownLibs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === ownLibs[i]) && remembered.program === program2 && [...remembered.read].every(([read3, stamp]) => stampOf(read3) === stamp)) {
|
|
3793
4165
|
analyzed.set(file, remembered.result);
|
|
3794
4166
|
readWhile.set(file, remembered.read);
|
|
3795
4167
|
return remembered.result;
|
|
@@ -3802,7 +4174,7 @@ function analyzeModules(modules, config2) {
|
|
|
3802
4174
|
}
|
|
3803
4175
|
const before = partials;
|
|
3804
4176
|
const types = (0, import_parser7.analyzeTypes)(program2, scopes, {
|
|
3805
|
-
libs,
|
|
4177
|
+
libs: ownLibs,
|
|
3806
4178
|
resolveModule: resolverFor(file),
|
|
3807
4179
|
noLib,
|
|
3808
4180
|
reportUnknownTypes: checked
|
|
@@ -3811,7 +4183,7 @@ function analyzeModules(modules, config2) {
|
|
|
3811
4183
|
const at = d.node;
|
|
3812
4184
|
said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type", severity: d.severity });
|
|
3813
4185
|
}
|
|
3814
|
-
const result2 = { types, diagnostics: said };
|
|
4186
|
+
const result2 = { types, diagnostics: said, scopes };
|
|
3815
4187
|
if (partials === before) {
|
|
3816
4188
|
analyzed.set(file, result2);
|
|
3817
4189
|
const read3 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
|
|
@@ -3819,7 +4191,7 @@ function analyzeModules(modules, config2) {
|
|
|
3819
4191
|
if (own !== void 0) read3.set(file, own);
|
|
3820
4192
|
readWhile.set(file, read3);
|
|
3821
4193
|
if ([...read3.values()].every((stamp) => stamp !== void 0)) {
|
|
3822
|
-
moduleAnalyses.set(file, { setting: settingFor, libs: [...
|
|
4194
|
+
moduleAnalyses.set(file, { setting: settingFor, libs: [...ownLibs], program: program2, read: read3, result: result2 });
|
|
3823
4195
|
}
|
|
3824
4196
|
}
|
|
3825
4197
|
return result2;
|
|
@@ -3868,7 +4240,16 @@ function analyzeModules(modules, config2) {
|
|
|
3868
4240
|
analyses.set(module2.file, types);
|
|
3869
4241
|
out2.push(...diagnostics);
|
|
3870
4242
|
}
|
|
3871
|
-
|
|
4243
|
+
const exportsOf = (file) => {
|
|
4244
|
+
const cached = exportsCache.get(file);
|
|
4245
|
+
if (cached) return cached;
|
|
4246
|
+
const module2 = byFile.get(file);
|
|
4247
|
+
const { types, scopes } = analyzeModule(file, module2.program);
|
|
4248
|
+
const exports2 = (0, import_parser7.moduleExports)(module2.program, scopes, types, resolverFor(file));
|
|
4249
|
+
exportsCache.set(file, exports2);
|
|
4250
|
+
return exports2;
|
|
4251
|
+
};
|
|
4252
|
+
return { types: analyses, diagnostics: out2, lowerings: [...libraries.lowerings], exportsOf };
|
|
3872
4253
|
}
|
|
3873
4254
|
function allStatements(statements) {
|
|
3874
4255
|
const out2 = [];
|
|
@@ -3888,27 +4269,36 @@ function allStatements(statements) {
|
|
|
3888
4269
|
}
|
|
3889
4270
|
|
|
3890
4271
|
// src/cli.ts
|
|
3891
|
-
var
|
|
4272
|
+
var import_parser8 = require("@tilua/parser");
|
|
4273
|
+
var usage = "usage: tilua [<entry>] [--out <file>] [--output single|chunks|files] [--config <tilua.config.json>] [--noCheck] [--noLineMap] [--minify] [--watch]\n tilua init";
|
|
3892
4274
|
var args = process.argv.slice(2);
|
|
3893
4275
|
var entry;
|
|
3894
4276
|
var out;
|
|
3895
4277
|
var config;
|
|
3896
4278
|
var typeCheck = true;
|
|
3897
4279
|
var lineMap;
|
|
4280
|
+
var minify;
|
|
3898
4281
|
var output;
|
|
4282
|
+
var watching = false;
|
|
3899
4283
|
for (let i = 0; i < args.length; i++) {
|
|
3900
4284
|
const arg = args[i];
|
|
3901
4285
|
if (arg === "--out") out = args[++i];
|
|
3902
4286
|
else if (arg === "--config") config = args[++i];
|
|
3903
4287
|
else if (arg === "--noCheck") typeCheck = false;
|
|
3904
4288
|
else if (arg === "--noLineMap") lineMap = false;
|
|
4289
|
+
else if (arg === "--minify") minify = true;
|
|
4290
|
+
else if (arg === "--watch" || arg === "-w") watching = true;
|
|
3905
4291
|
else if (arg === "--target") {
|
|
3906
4292
|
console.error(`--target is gone: the output runs on Lua 5.1 to 5.4, LuaJIT and Luau, and a library says what more its Lua has \u2014 add "luau" to the config's 'types'`);
|
|
3907
4293
|
process.exit(2);
|
|
3908
4294
|
} else if (arg === "--output") {
|
|
3909
4295
|
const value = args[++i];
|
|
3910
|
-
if (value
|
|
3911
|
-
console.error(
|
|
4296
|
+
if (value === "modules") {
|
|
4297
|
+
console.error("--output modules is gone: --output files writes one file per module, each of which returns its exports");
|
|
4298
|
+
process.exit(2);
|
|
4299
|
+
}
|
|
4300
|
+
if (value !== "single" && value !== "chunks" && value !== "files") {
|
|
4301
|
+
console.error(`--output must be 'single', 'chunks' or 'files', not '${value}'`);
|
|
3912
4302
|
process.exit(2);
|
|
3913
4303
|
}
|
|
3914
4304
|
output = value;
|
|
@@ -3917,39 +4307,284 @@ for (let i = 0; i < args.length; i++) {
|
|
|
3917
4307
|
process.exit(0);
|
|
3918
4308
|
} else entry = arg;
|
|
3919
4309
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
4310
|
+
var shown = (path) => (0, import_node_path3.relative)(process.cwd(), path) || ".";
|
|
4311
|
+
var OUT = "out";
|
|
4312
|
+
function init() {
|
|
4313
|
+
const folder = process.cwd();
|
|
4314
|
+
let existing;
|
|
4315
|
+
for (const name of ["tilua.config.json", "tilua.config.jsonc"]) {
|
|
4316
|
+
const path = (0, import_node_path3.join)(folder, name);
|
|
4317
|
+
if (!(0, import_node_fs4.existsSync)(path)) continue;
|
|
4318
|
+
let options2 = {};
|
|
4319
|
+
try {
|
|
4320
|
+
options2 = JSON.parse((0, import_parser8.stripJsonComments)((0, import_node_fs4.readFileSync)(path, "utf8")));
|
|
4321
|
+
} catch (error) {
|
|
4322
|
+
console.error(`${name}: ${error.message}`);
|
|
4323
|
+
process.exit(1);
|
|
4324
|
+
}
|
|
4325
|
+
if (options2.output !== "files" || options2.outDir !== void 0 && options2.outDir !== options2.rootDir) {
|
|
4326
|
+
console.error(`${name} is already here`);
|
|
4327
|
+
process.exit(1);
|
|
4328
|
+
}
|
|
4329
|
+
existing = { path, options: options2 };
|
|
4330
|
+
}
|
|
4331
|
+
const project = (0, import_node_path3.join)(folder, "default.project.json");
|
|
4332
|
+
if (!(0, import_node_fs4.existsSync)(project)) {
|
|
4333
|
+
console.error("No default.project.json here: run `tilua init` in the folder of a Rojo project (`rojo init` makes one)");
|
|
4334
|
+
process.exit(1);
|
|
4335
|
+
}
|
|
4336
|
+
const text = (0, import_node_fs4.readFileSync)(project, "utf8");
|
|
4337
|
+
const paths = [];
|
|
4338
|
+
const walk2 = (node) => {
|
|
4339
|
+
if (!node || typeof node !== "object") return;
|
|
4340
|
+
const path = node.$path;
|
|
4341
|
+
const named = typeof path === "string" ? path : path?.optional;
|
|
4342
|
+
if (typeof named === "string") paths.push(named);
|
|
4343
|
+
for (const [key, child] of Object.entries(node)) if (!key.startsWith("$")) walk2(child);
|
|
4344
|
+
};
|
|
4345
|
+
try {
|
|
4346
|
+
walk2(JSON.parse(text).tree);
|
|
4347
|
+
} catch (error) {
|
|
4348
|
+
console.error(`default.project.json: ${error.message}`);
|
|
4349
|
+
process.exit(1);
|
|
4350
|
+
}
|
|
4351
|
+
const split = paths.map((path) => path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "").split("/"));
|
|
4352
|
+
const shared = [];
|
|
4353
|
+
for (let i = 0; split.length && split.every((parts) => parts.length > i && parts[i] === split[0][i]); i++) shared.push(split[0][i]);
|
|
4354
|
+
if (split.length === 1 && /\.\w+$/.test(shared.at(-1) ?? "")) shared.pop();
|
|
4355
|
+
const rootDir = shared.join("/");
|
|
4356
|
+
if (!rootDir) {
|
|
4357
|
+
console.error("The project's $paths share no folder to compile from: put the code under one (src), as `rojo init` does");
|
|
4358
|
+
process.exit(1);
|
|
4359
|
+
}
|
|
4360
|
+
if (rootDir === OUT || rootDir.startsWith(`${OUT}/`)) {
|
|
4361
|
+
console.error(`The project already reads from ${OUT}: set tilua.config.json up by hand`);
|
|
4362
|
+
process.exit(1);
|
|
4363
|
+
}
|
|
4364
|
+
const out2 = (0, import_node_path3.join)(folder, OUT);
|
|
4365
|
+
if ((0, import_node_fs4.existsSync)(out2) && (0, import_node_fs4.readdirSync)(out2).length) {
|
|
4366
|
+
console.error(`${OUT} is already here and not empty: the build would replace what is in it`);
|
|
4367
|
+
process.exit(1);
|
|
4368
|
+
}
|
|
4369
|
+
const options = existing ? { ...existing.options, rootDir: existing.options.rootDir ?? rootDir, outDir: OUT } : { types: ["roblox"], output: "files", rootDir, outDir: OUT };
|
|
4370
|
+
(0, import_node_fs4.writeFileSync)(existing?.path ?? (0, import_node_path3.join)(folder, "tilua.config.json"), JSON.stringify(options, null, 4) + "\n");
|
|
4371
|
+
console.log(`${(0, import_node_path3.basename)(existing?.path ?? "tilua.config.json")}: ${String(options.rootDir)} compiled to ${OUT}`);
|
|
4372
|
+
const escaped = rootDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4373
|
+
let changed = 0;
|
|
4374
|
+
const pointed = text.replace(new RegExp(`("\\$path"\\s*:\\s*")(\\./)?${escaped}(?=/|")`, "g"), (_, head) => {
|
|
4375
|
+
changed++;
|
|
4376
|
+
return `${head}${OUT}`;
|
|
4377
|
+
});
|
|
4378
|
+
(0, import_node_fs4.writeFileSync)(project, pointed);
|
|
4379
|
+
console.log(`default.project.json: ${changed} $path${changed === 1 ? "" : "s"} now read from ${OUT}`);
|
|
4380
|
+
const ignore = (0, import_node_path3.join)(folder, ".gitignore");
|
|
4381
|
+
const ignored = (0, import_node_fs4.existsSync)(ignore) ? (0, import_node_fs4.readFileSync)(ignore, "utf8") : "";
|
|
4382
|
+
if (!ignored.split(/\r?\n/).some((line) => [OUT, `/${OUT}`, `${OUT}/`, `/${OUT}/`].includes(line.trim()))) {
|
|
4383
|
+
(0, import_node_fs4.writeFileSync)(ignore, `${ignored}${ignored && !ignored.endsWith("\n") ? "\n" : ""}
|
|
4384
|
+
# What tilua builds from ${rootDir}
|
|
4385
|
+
/${OUT}
|
|
4386
|
+
`);
|
|
4387
|
+
console.log(`.gitignore: /${OUT}`);
|
|
4388
|
+
}
|
|
4389
|
+
console.log("Next: npm i -D @tilua/compiler @tilua-types/roblox, then run `npx tilua --watch` beside `rojo serve`.");
|
|
4390
|
+
process.exit(0);
|
|
4391
|
+
}
|
|
4392
|
+
function isInside(folder, path) {
|
|
4393
|
+
const below = (0, import_node_path3.relative)(folder, path);
|
|
4394
|
+
return below === "" || !below.startsWith("..") && !(0, import_node_path3.isAbsolute)(below);
|
|
3923
4395
|
}
|
|
3924
|
-
|
|
3925
|
-
|
|
4396
|
+
function filesUnder(folder) {
|
|
4397
|
+
return under(folder).files;
|
|
4398
|
+
}
|
|
4399
|
+
function under(folder) {
|
|
4400
|
+
const files = [];
|
|
4401
|
+
const folders = [];
|
|
4402
|
+
const walk2 = (at) => {
|
|
4403
|
+
let items;
|
|
4404
|
+
try {
|
|
4405
|
+
items = (0, import_node_fs4.readdirSync)(at, { withFileTypes: true });
|
|
4406
|
+
} catch {
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4409
|
+
for (const item of items) {
|
|
4410
|
+
if (item.name === "node_modules" || item.name === ".git") continue;
|
|
4411
|
+
const path = (0, import_node_path3.join)(at, item.name);
|
|
4412
|
+
if (item.isDirectory()) {
|
|
4413
|
+
folders.push(path);
|
|
4414
|
+
walk2(path);
|
|
4415
|
+
} else files.push(path);
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
walk2(folder);
|
|
4419
|
+
return { files, folders };
|
|
4420
|
+
}
|
|
4421
|
+
function removeEmpty(folder, keep) {
|
|
4422
|
+
for (const item of (0, import_node_fs4.readdirSync)(folder, { withFileTypes: true })) {
|
|
4423
|
+
if (!item.isDirectory()) continue;
|
|
4424
|
+
const path = (0, import_node_path3.join)(folder, item.name);
|
|
4425
|
+
removeEmpty(path, keep);
|
|
4426
|
+
if (!(0, import_node_fs4.readdirSync)(path).length && !keep.has((0, import_node_path3.resolve)(path))) (0, import_node_fs4.rmdirSync)(path);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
function writtenByTilua(folder) {
|
|
4430
|
+
return filesUnder(folder).filter((path) => /\.luau?$/.test(path) && isTilua(path));
|
|
4431
|
+
}
|
|
4432
|
+
function isTilua(path) {
|
|
4433
|
+
try {
|
|
4434
|
+
const text = (0, import_node_fs4.readFileSync)(path, "utf8").trimEnd();
|
|
4435
|
+
return text.slice(text.lastIndexOf("\n") + 1).startsWith(WRITTEN_BY_TILUA);
|
|
4436
|
+
} catch {
|
|
4437
|
+
return false;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
async function build(from) {
|
|
4441
|
+
const found = resolveConfig(config, (0, import_node_path3.resolve)(from ?? "tilua.config.json")).config;
|
|
3926
4442
|
const mode = output ?? found?.output ?? "single";
|
|
3927
|
-
|
|
3928
|
-
|
|
4443
|
+
if (mode !== "files" && from === void 0) {
|
|
4444
|
+
console.error(usage);
|
|
4445
|
+
process.exit(2);
|
|
4446
|
+
}
|
|
4447
|
+
const perFile = mode === "files";
|
|
4448
|
+
const entryFile = perFile || from === void 0 ? void 0 : out ?? from.replace(/\.tilua$/, "") + ".luau";
|
|
4449
|
+
const folder = (0, import_node_path3.resolve)(entryFile !== void 0 ? (0, import_node_path3.dirname)(entryFile) : out ?? found?.outDir ?? process.cwd());
|
|
4450
|
+
const rootDir = (0, import_node_path3.resolve)(found?.rootDir ?? process.cwd());
|
|
4451
|
+
const mirror = perFile && (0, import_node_path3.resolve)(folder) !== rootDir;
|
|
4452
|
+
if (mirror && (isInside(folder, rootDir) || isInside(rootDir, folder) || isInside(folder, found?.directory ?? process.cwd()))) {
|
|
4453
|
+
console.error(`outDir (${shown(folder)}) has to be a folder of its own: not ${isInside(rootDir, folder) ? "under" : "around"} rootDir (${shown(rootDir)}), and not around the project`);
|
|
4454
|
+
return false;
|
|
4455
|
+
}
|
|
3929
4456
|
const result2 = await bundle({
|
|
3930
|
-
entry: from,
|
|
4457
|
+
entry: perFile ? void 0 : from,
|
|
3931
4458
|
config,
|
|
3932
4459
|
typeCheck,
|
|
3933
4460
|
output: mode,
|
|
3934
4461
|
lineMap,
|
|
4462
|
+
minify,
|
|
4463
|
+
outDir: perFile ? out : void 0,
|
|
3935
4464
|
outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
|
|
3936
4465
|
});
|
|
3937
4466
|
for (const d of result2.diagnostics) {
|
|
3938
|
-
console.error(`${(
|
|
4467
|
+
console.error(`${shown(d.file)}:${d.line}:${d.column} ${d.severity === "warning" ? "warning: " : ""}${d.message}`);
|
|
3939
4468
|
}
|
|
3940
4469
|
if (result2.code === void 0) {
|
|
3941
|
-
console.error("no
|
|
3942
|
-
|
|
3943
|
-
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
(
|
|
3947
|
-
|
|
3948
|
-
|
|
4470
|
+
console.error("no output written");
|
|
4471
|
+
return false;
|
|
4472
|
+
}
|
|
4473
|
+
const targets = result2.files.map((file) => ({ ...file, target: (0, import_node_path3.join)(folder, file.path) }));
|
|
4474
|
+
if (mirror) {
|
|
4475
|
+
const compiled = new Map(targets.map((file) => [(0, import_node_path3.resolve)(file.target), file]));
|
|
4476
|
+
const copies = [];
|
|
4477
|
+
let clash = false;
|
|
4478
|
+
const leftovers = [];
|
|
4479
|
+
for (const source of filesUnder(rootDir)) {
|
|
4480
|
+
if (source.endsWith(".tilua")) continue;
|
|
4481
|
+
if (/\.luau?$/.test(source) && isTilua(source)) {
|
|
4482
|
+
leftovers.push(source);
|
|
4483
|
+
continue;
|
|
4484
|
+
}
|
|
4485
|
+
const target = (0, import_node_path3.resolve)(folder, (0, import_node_path3.relative)(rootDir, source));
|
|
4486
|
+
if (compiled.has(target)) {
|
|
4487
|
+
console.error(`${shown(source)}: a .tilua compiles to the same file \u2014 rename or remove one of them`);
|
|
4488
|
+
clash = true;
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
copies.push({ source, target });
|
|
4492
|
+
}
|
|
4493
|
+
if (clash) return false;
|
|
4494
|
+
for (const leftover of leftovers) {
|
|
4495
|
+
(0, import_node_fs4.rmSync)(leftover);
|
|
4496
|
+
console.log(`${shown(leftover)}: removed \u2014 tilua compiled it beside its .tilua, and the output is in ${shown(folder)} now`);
|
|
4497
|
+
}
|
|
4498
|
+
for (const file of targets) {
|
|
4499
|
+
if ((0, import_node_fs4.existsSync)(file.target) && (0, import_node_fs4.readFileSync)(file.target, "utf8") === file.code) continue;
|
|
4500
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(file.target), { recursive: true });
|
|
4501
|
+
(0, import_node_fs4.writeFileSync)(file.target, file.code);
|
|
4502
|
+
console.log(`${shown(file.target)}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
4503
|
+
}
|
|
4504
|
+
for (const { source, target } of copies) {
|
|
4505
|
+
const bytes = (0, import_node_fs4.readFileSync)(source);
|
|
4506
|
+
if ((0, import_node_fs4.existsSync)(target) && (0, import_node_fs4.readFileSync)(target).equals(bytes)) continue;
|
|
4507
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(target), { recursive: true });
|
|
4508
|
+
(0, import_node_fs4.copyFileSync)(source, target);
|
|
4509
|
+
console.log(`${shown(target)}: copied`);
|
|
4510
|
+
}
|
|
4511
|
+
const kept = /* @__PURE__ */ new Set([...compiled.keys(), ...copies.map((copy) => copy.target)]);
|
|
4512
|
+
for (const stale of filesUnder(folder)) {
|
|
4513
|
+
if (kept.has((0, import_node_path3.resolve)(stale))) continue;
|
|
4514
|
+
(0, import_node_fs4.rmSync)(stale);
|
|
4515
|
+
console.log(`${shown(stale)}: removed`);
|
|
4516
|
+
}
|
|
4517
|
+
const folders = /* @__PURE__ */ new Set([folder, ...under(rootDir).folders.map((path) => (0, import_node_path3.resolve)(folder, (0, import_node_path3.relative)(rootDir, path)))]);
|
|
4518
|
+
for (const path of folders) (0, import_node_fs4.mkdirSync)(path, { recursive: true });
|
|
4519
|
+
removeEmpty(folder, folders);
|
|
4520
|
+
return !result2.diagnostics.some((d) => d.severity !== "warning");
|
|
4521
|
+
}
|
|
4522
|
+
if (perFile) {
|
|
4523
|
+
const theirs = targets.filter((file) => (0, import_node_fs4.existsSync)(file.target) && !isTilua(file.target));
|
|
4524
|
+
for (const file of theirs) {
|
|
4525
|
+
console.error(`${shown(file.target)}: this file was not written by tilua, and a .tilua would replace it \u2014 rename or remove one of them`);
|
|
4526
|
+
}
|
|
4527
|
+
if (theirs.length) return false;
|
|
4528
|
+
}
|
|
4529
|
+
for (const file of targets) {
|
|
4530
|
+
if ((0, import_node_fs4.existsSync)(file.target) && (0, import_node_fs4.readFileSync)(file.target, "utf8") === file.code) continue;
|
|
4531
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(file.target), { recursive: true });
|
|
4532
|
+
(0, import_node_fs4.writeFileSync)(file.target, file.code);
|
|
4533
|
+
console.log(`${shown(file.target)}: ${file.modules.length} module${file.modules.length === 1 ? "" : "s"}`);
|
|
4534
|
+
}
|
|
4535
|
+
if (perFile) {
|
|
4536
|
+
const kept = new Set(targets.map((file) => (0, import_node_path3.resolve)(file.target)));
|
|
4537
|
+
for (const stale of writtenByTilua(folder)) {
|
|
4538
|
+
if (kept.has((0, import_node_path3.resolve)(stale))) continue;
|
|
4539
|
+
(0, import_node_fs4.rmSync)(stale);
|
|
4540
|
+
console.log(`${shown(stale)}: removed, its .tilua is gone`);
|
|
4541
|
+
}
|
|
3949
4542
|
}
|
|
3950
|
-
|
|
4543
|
+
return !result2.diagnostics.some((d) => d.severity !== "warning");
|
|
4544
|
+
}
|
|
4545
|
+
async function watchAndBuild(from) {
|
|
4546
|
+
const found = resolveConfig(config, (0, import_node_path3.resolve)(from ?? "tilua.config.json")).config;
|
|
4547
|
+
const folder = found?.directory ?? (from !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(from)) : process.cwd());
|
|
4548
|
+
const rootDir = found?.rootDir;
|
|
4549
|
+
const outDir = (0, import_node_path3.resolve)(out ?? found?.outDir ?? folder);
|
|
4550
|
+
const mirrored = (output ?? found?.output) === "files" && rootDir !== void 0 && outDir !== (0, import_node_path3.resolve)(rootDir);
|
|
4551
|
+
let running = false;
|
|
4552
|
+
let again = false;
|
|
4553
|
+
let timer;
|
|
4554
|
+
const run = async () => {
|
|
4555
|
+
if (running) {
|
|
4556
|
+
again = true;
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4559
|
+
running = true;
|
|
4560
|
+
const ok = await build(from).catch((error) => {
|
|
4561
|
+
console.error(error instanceof Error ? error.message : error);
|
|
4562
|
+
return false;
|
|
4563
|
+
});
|
|
4564
|
+
console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${ok ? "built" : "built with errors"}; watching ${shown(folder)}`);
|
|
4565
|
+
running = false;
|
|
4566
|
+
if (again) {
|
|
4567
|
+
again = false;
|
|
4568
|
+
await run();
|
|
4569
|
+
}
|
|
4570
|
+
};
|
|
4571
|
+
await run();
|
|
4572
|
+
(0, import_node_fs4.watch)(folder, { recursive: true }, (_event, name) => {
|
|
4573
|
+
if (!name) return;
|
|
4574
|
+
const path = (0, import_node_path3.resolve)(folder, name.toString());
|
|
4575
|
+
if ((0, import_node_path3.relative)(folder, path).split(/[\\/]/).includes("node_modules") || isInside(outDir, path) && mirrored) return;
|
|
4576
|
+
const read3 = /\.tilua$|tilua\.config\.jsonc?$|\.project\.json$/.test(path) || mirrored && isInside(rootDir, path);
|
|
4577
|
+
if (!read3) return;
|
|
4578
|
+
clearTimeout(timer);
|
|
4579
|
+
timer = setTimeout(() => void run(), 100);
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
async function main() {
|
|
4583
|
+
if (entry === "init") init();
|
|
4584
|
+
if (watching) return watchAndBuild(entry);
|
|
4585
|
+
process.exit(await build(entry) ? 0 : 1);
|
|
3951
4586
|
}
|
|
3952
|
-
main(
|
|
4587
|
+
main().catch((error) => {
|
|
3953
4588
|
console.error(error instanceof Error ? error.message : error);
|
|
3954
4589
|
process.exit(1);
|
|
3955
4590
|
});
|