polybundle 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/bin/polybundle.js +5 -0
- package/package.json +19 -0
- package/src/bundler.js +154 -0
- package/src/cli.js +138 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# PolyBundle
|
|
2
|
+
|
|
3
|
+
Simple (heavily clanker-made) Lua script bundler for Polytoria.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
install from this repo:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install
|
|
11
|
+
npm link
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Initialize a project (creates init_scripts.json and a dev sample):
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
polybundle init
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Bundle using the current folder's init_scripts.json:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
polybundle
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Specify an output file:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
polybundle --out dist/bundle.lua
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## init_scripts.json format
|
|
35
|
+
|
|
36
|
+
An array of entry script paths (order matters):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
[
|
|
40
|
+
"./dev/init1.lua",
|
|
41
|
+
"./dev/init2.lua"
|
|
42
|
+
]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example Output:
|
|
46
|
+
```lua
|
|
47
|
+
local __module_env = {}
|
|
48
|
+
|
|
49
|
+
-- polybundle: module scripts/lib/library1.module.lua
|
|
50
|
+
__module_env["scripts/lib/library1.module.lua"] = (function()
|
|
51
|
+
local returnedLib = {}
|
|
52
|
+
|
|
53
|
+
returnedLib.Add = function(a, b)
|
|
54
|
+
return a + b
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return returnedLib
|
|
58
|
+
end)()
|
|
59
|
+
|
|
60
|
+
-- polybundle: begin scripts\init1.lua
|
|
61
|
+
coroutine.wrap(function()
|
|
62
|
+
local lib = __module_env["scripts/lib/library1.module.lua"]
|
|
63
|
+
|
|
64
|
+
local addResult = lib.Add(2, 50)
|
|
65
|
+
|
|
66
|
+
print(addResult)
|
|
67
|
+
print("added successfully in init1")
|
|
68
|
+
end)()
|
|
69
|
+
-- polybundle: end scripts\init1.lua
|
|
70
|
+
|
|
71
|
+
-- polybundle: begin scripts\init2.lua
|
|
72
|
+
coroutine.wrap(function()
|
|
73
|
+
local addLib = __module_env["scripts/lib/library1.module.lua"]
|
|
74
|
+
|
|
75
|
+
local addResult = addLib.Add(9, 10)
|
|
76
|
+
|
|
77
|
+
local res = "add result is "
|
|
78
|
+
|
|
79
|
+
local final = res .. tostring(addResult)
|
|
80
|
+
|
|
81
|
+
print(final)
|
|
82
|
+
end)()
|
|
83
|
+
-- polybundle: end scripts\init2.lua
|
|
84
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polybundle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lua script bundler for Polytoria.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"polybundle": "./bin/polybundle.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/bundler.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const requireRegex = /require\s*\(\s*(['"])(.*?)\1\s*\)/g;
|
|
5
|
+
|
|
6
|
+
function normalizeLuaSource(source) {
|
|
7
|
+
return source.endsWith("\n") ? source : source + "\n";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isModuleFile(filePath) {
|
|
11
|
+
return filePath.endsWith(".module.lua");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toLuaKey(filePath, rootDir) {
|
|
15
|
+
const relative = path.relative(rootDir, filePath);
|
|
16
|
+
return relative.split(path.sep).join("/");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveModulePath(request, fromDir, rootDir) {
|
|
20
|
+
const hasExtension = request.endsWith(".lua") || request.endsWith(".module.lua");
|
|
21
|
+
const withExtension = hasExtension ? request : `${request}.module.lua`;
|
|
22
|
+
|
|
23
|
+
let candidate;
|
|
24
|
+
if (path.isAbsolute(withExtension)) {
|
|
25
|
+
candidate = withExtension;
|
|
26
|
+
} else if (withExtension.startsWith("./") || withExtension.startsWith("../")) {
|
|
27
|
+
candidate = path.resolve(fromDir, withExtension);
|
|
28
|
+
} else if (withExtension.startsWith("/")) {
|
|
29
|
+
candidate = path.resolve(rootDir, withExtension.slice(1));
|
|
30
|
+
} else {
|
|
31
|
+
candidate = path.resolve(rootDir, withExtension);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!hasExtension) {
|
|
39
|
+
const fallback = candidate.replace(/\.module\.lua$/, ".lua");
|
|
40
|
+
if (fs.existsSync(fallback)) {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error(`Module not found for require("${request}")`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function collectModulesFromSource(source, filePath, rootDir, moduleList, moduleSet, stack) {
|
|
49
|
+
return source.replace(requireRegex, (match, quote, request) => {
|
|
50
|
+
const modulePath = resolveModulePath(request, path.dirname(filePath), rootDir);
|
|
51
|
+
if (stack.includes(modulePath)) {
|
|
52
|
+
const cycle = [...stack, modulePath]
|
|
53
|
+
.map((entry) => path.relative(rootDir, entry))
|
|
54
|
+
.join(" -> ");
|
|
55
|
+
throw new Error(`Circular require detected: ${cycle}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!moduleSet.has(modulePath)) {
|
|
59
|
+
moduleSet.add(modulePath);
|
|
60
|
+
moduleList.push(modulePath);
|
|
61
|
+
const moduleSource = fs.readFileSync(modulePath, "utf8");
|
|
62
|
+
collectModulesFromSource(moduleSource, modulePath, rootDir, moduleList, moduleSet, stack.concat(modulePath));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return match;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function replaceRequiresWithModuleRefs(source, filePath, rootDir, moduleKeyMap) {
|
|
70
|
+
return source.replace(requireRegex, (match, quote, request) => {
|
|
71
|
+
const modulePath = resolveModulePath(request, path.dirname(filePath), rootDir);
|
|
72
|
+
const moduleKey = moduleKeyMap.get(modulePath);
|
|
73
|
+
if (!moduleKey) {
|
|
74
|
+
throw new Error(`Missing module entry for require(\"${request}\")`);
|
|
75
|
+
}
|
|
76
|
+
return `__module_env[\"${moduleKey}\"]`;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function bundleModuleSource(source, filePath, rootDir, moduleKeyMap, cache, stack) {
|
|
81
|
+
if (cache.has(filePath)) {
|
|
82
|
+
return cache.get(filePath);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (stack.includes(filePath)) {
|
|
86
|
+
const cycle = [...stack, filePath].map((entry) => path.relative(rootDir, entry)).join(" -> ");
|
|
87
|
+
throw new Error(`Circular require detected: ${cycle}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const nextStack = stack.concat(filePath);
|
|
91
|
+
const replaced = replaceRequiresWithModuleRefs(source, filePath, rootDir, moduleKeyMap);
|
|
92
|
+
cache.set(filePath, replaced);
|
|
93
|
+
return replaced;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function bundleEntries(entries, rootDir) {
|
|
97
|
+
const moduleList = [];
|
|
98
|
+
const moduleSet = new Set();
|
|
99
|
+
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
const entryPath = path.resolve(rootDir, entry);
|
|
102
|
+
if (isModuleFile(entryPath)) {
|
|
103
|
+
if (!moduleSet.has(entryPath)) {
|
|
104
|
+
moduleSet.add(entryPath);
|
|
105
|
+
moduleList.push(entryPath);
|
|
106
|
+
const moduleSource = fs.readFileSync(entryPath, "utf8");
|
|
107
|
+
collectModulesFromSource(moduleSource, entryPath, rootDir, moduleList, moduleSet, [entryPath]);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const source = fs.readFileSync(entryPath, "utf8");
|
|
113
|
+
collectModulesFromSource(source, entryPath, rootDir, moduleList, moduleSet, [entryPath]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const moduleKeyMap = new Map();
|
|
117
|
+
for (const modulePath of moduleList) {
|
|
118
|
+
moduleKeyMap.set(modulePath, toLuaKey(modulePath, rootDir));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const moduleCache = new Map();
|
|
122
|
+
const chunks = [];
|
|
123
|
+
|
|
124
|
+
chunks.push("local __module_env = {}\n");
|
|
125
|
+
|
|
126
|
+
for (const modulePath of moduleList) {
|
|
127
|
+
const moduleSource = fs.readFileSync(modulePath, "utf8");
|
|
128
|
+
const bundledModule = bundleModuleSource(moduleSource, modulePath, rootDir, moduleKeyMap, moduleCache, []);
|
|
129
|
+
const moduleKey = moduleKeyMap.get(modulePath);
|
|
130
|
+
const header = `-- polybundle: module ${moduleKey}\n`;
|
|
131
|
+
const assignment = `__module_env[\"${moduleKey}\"] = (function()\n${normalizeLuaSource(bundledModule)}end)()\n`;
|
|
132
|
+
chunks.push(header + assignment);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const entryPath = path.resolve(rootDir, entry);
|
|
137
|
+
if (isModuleFile(entryPath)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const entrySource = fs.readFileSync(entryPath, "utf8");
|
|
142
|
+
const replaced = replaceRequiresWithModuleRefs(entrySource, entryPath, rootDir, moduleKeyMap);
|
|
143
|
+
const wrapped = `coroutine.wrap(function()\n${normalizeLuaSource(replaced)}end)()`;
|
|
144
|
+
const header = `-- polybundle: begin ${path.relative(rootDir, entryPath)}\n`;
|
|
145
|
+
const footer = `-- polybundle: end ${path.relative(rootDir, entryPath)}\n`;
|
|
146
|
+
chunks.push(header + normalizeLuaSource(wrapped) + footer);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return chunks.join("\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
bundleEntries
|
|
154
|
+
};
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { bundleEntries } = require("./bundler");
|
|
4
|
+
|
|
5
|
+
function printHelp() {
|
|
6
|
+
console.log("Usage: polybundle [init] [--out <file>]");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function initScriptTree(scriptTreePath) {
|
|
10
|
+
const example = [
|
|
11
|
+
"./dev/init1.lua",
|
|
12
|
+
"./dev/init2.lua"
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(scriptTreePath)) {
|
|
16
|
+
fs.writeFileSync(scriptTreePath, JSON.stringify(example, null, 2) + "\n", "utf8");
|
|
17
|
+
console.log("Created init_scripts.json with example entries.");
|
|
18
|
+
} else {
|
|
19
|
+
console.log("init_scripts.json already exists.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const devDir = path.resolve(path.dirname(scriptTreePath), "dev");
|
|
23
|
+
const libDir = path.join(devDir, "lib");
|
|
24
|
+
|
|
25
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const init1Path = path.join(devDir, "init1.lua");
|
|
28
|
+
const init2Path = path.join(devDir, "init2.lua");
|
|
29
|
+
const libraryPath = path.join(libDir, "library1.module.lua");
|
|
30
|
+
|
|
31
|
+
const init1Content = "local lib = require(\"./lib/library1.module.lua\")\n\n" +
|
|
32
|
+
"local addResult = lib.Add(2, 50)\n\n" +
|
|
33
|
+
"print(addResult)\n" +
|
|
34
|
+
"print(\"added successfully in init1\")\n";
|
|
35
|
+
|
|
36
|
+
const init2Content = "local addLib = require(\"./lib/library1.module.lua\")\n\n" +
|
|
37
|
+
"local addResult = addLib.Add(9, 10)\n\n" +
|
|
38
|
+
"local res = \"add result is \"\n\n" +
|
|
39
|
+
"local final = res .. tostring(addResult)\n\n" +
|
|
40
|
+
"print(final)\n";
|
|
41
|
+
|
|
42
|
+
const libraryContent = "local returnedLib = {}\n\n" +
|
|
43
|
+
"returnedLib.Add = function(a, b)\n" +
|
|
44
|
+
" return a + b\n" +
|
|
45
|
+
"end\n\n" +
|
|
46
|
+
"return returnedLib\n";
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(init1Path)) {
|
|
49
|
+
fs.writeFileSync(init1Path, init1Content, "utf8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(init2Path)) {
|
|
53
|
+
fs.writeFileSync(init2Path, init2Content, "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(libraryPath)) {
|
|
57
|
+
fs.writeFileSync(libraryPath, libraryContent, "utf8");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseArgs(args) {
|
|
62
|
+
const result = {
|
|
63
|
+
command: null,
|
|
64
|
+
outFile: null,
|
|
65
|
+
help: false
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
69
|
+
const arg = args[i];
|
|
70
|
+
if (arg === "--help" || arg === "-h") {
|
|
71
|
+
result.help = true;
|
|
72
|
+
} else if (arg === "--out") {
|
|
73
|
+
result.outFile = args[i + 1] || null;
|
|
74
|
+
i += 1;
|
|
75
|
+
} else if (!result.command) {
|
|
76
|
+
result.command = arg;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function run(args) {
|
|
84
|
+
const options = parseArgs(args);
|
|
85
|
+
|
|
86
|
+
if (options.help) {
|
|
87
|
+
printHelp();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const cwd = process.cwd();
|
|
92
|
+
const scriptTreePath = path.resolve(cwd, "init_scripts.json");
|
|
93
|
+
|
|
94
|
+
if (options.command === "init") {
|
|
95
|
+
initScriptTree(scriptTreePath);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(scriptTreePath)) {
|
|
100
|
+
console.log("init_scripts.json not found. Run `polybundle init` first.");
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
const raw = fs.readFileSync(scriptTreePath, "utf8");
|
|
108
|
+
entries = JSON.parse(raw);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("Failed to read init_scripts.json:", error.message);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!Array.isArray(entries) || !entries.every((value) => typeof value === "string")) {
|
|
116
|
+
console.error("init_scripts.json must be an array of string paths.");
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const outFile = options.outFile
|
|
122
|
+
? path.resolve(cwd, options.outFile)
|
|
123
|
+
: path.resolve(cwd, "dist", "bundle.lua");
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const bundled = bundleEntries(entries, cwd);
|
|
127
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
128
|
+
fs.writeFileSync(outFile, bundled, "utf8");
|
|
129
|
+
console.log(`Bundled ${entries.length} entries into ${outFile}`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Bundling failed:", error.message);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
run
|
|
138
|
+
};
|