rs3-api-ts 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark7625
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # rs3-api-ts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `rs3-plugin` command: scaffold, build and check RuneScape 3 plugins written in TypeScript.
4
+
5
+ ```sh
6
+ rs3-plugin init my-plugin
7
+ cd my-plugin
8
+ npm install
9
+ npm run build
10
+ ```
11
+
12
+ `build` compiles `src/` to a `dist/` folder of plain Lua, copies in your `plugin.json` and the `rs3-api-extended` runtime, and validates the result before letting it through: permissions against the APIs you actually call, the manifest against what the client accepts, and the output against the plugin sandbox.
13
+
14
+ | Command | What it does |
15
+ | --- | --- |
16
+ | `rs3-plugin init [name]` | Scaffold a project. Prompts for anything not given as a flag. |
17
+ | `rs3-plugin build` | Release build: one readable Lua file per source file. |
18
+ | `rs3-plugin build --dev` | Dev build: single bundle with source maps. |
19
+ | `rs3-plugin dev` | Dev build on every change. |
20
+ | `--out <dir>` | Copy the finished plugin to `<dir>/<plugin name>/`. |
21
+
22
+ Run `rs3-plugin --help` for the full flag list.
23
+
24
+ Setup, project layout and the rest of the documentation live in the [repository README](https://github.com/Rs3-Plugins/rs3-api-ts#readme).
25
+
26
+ ## License
27
+
28
+ [MIT](https://github.com/Rs3-Plugins/rs3-api-ts/blob/main/LICENSE)
@@ -0,0 +1,21 @@
1
+ {
2
+ "$comment": "Written by npm run deps:dump. The library plugins that rs3-plugin init offers.",
3
+ "generatedAt": "2026-10-11T15:37:04.104Z",
4
+ "source": "https://mod.io/g/runescape/m?tags=Library+for+other+Plugins",
5
+ "game": {
6
+ "id": 13295,
7
+ "name_id": "runescape"
8
+ },
9
+ "tag": "Library for other Plugins",
10
+ "libraries": [
11
+ {
12
+ "id": 6398181,
13
+ "name_id": "prettyui",
14
+ "name": "PrettyUI",
15
+ "summary": "PrettyUI is a developer library that makes it trivial to create user interfaces with a look and feel that matches RuneScape's UI",
16
+ "profile_url": "https://mod.io/g/runescape/m/prettyui",
17
+ "author": "rsnJ3",
18
+ "date_updated": "2026-10-10T10:00:41.000Z"
19
+ }
20
+ ]
21
+ }
package/dist/build.js ADDED
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.build = build;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const ts = __importStar(require("typescript"));
40
+ const tstl = __importStar(require("typescript-to-lua"));
41
+ const checks_1 = require("./checks");
42
+ const postprocess_1 = require("./postprocess");
43
+ const project_1 = require("./project");
44
+ function build(options) {
45
+ const dev = options.dev === true;
46
+ const project = (0, project_1.loadProject)(options.cwd);
47
+ const problems = [];
48
+ const notes = [];
49
+ syncManifestVersion(project, notes);
50
+ problems.push(...(0, checks_1.checkManifest)(project.manifest, project.manifestError, project.manifestPath));
51
+ const extend = dev
52
+ ? {
53
+ luaBundle: "main.lua",
54
+ luaBundleEntry: project.entryFile,
55
+ luaLibImport: tstl.LuaLibImportKind.Require,
56
+ sourceMapTraceback: true,
57
+ }
58
+ : {};
59
+ fs.rmSync(project.outDir, { recursive: true, force: true });
60
+ const written = [];
61
+ const emit = tstl.transpileProject(project.tsconfigPath, extend, (fileName, text) => {
62
+ const isLua = fileName.endsWith(".lua");
63
+ const code = isLua && !dev ? (0, postprocess_1.makeReadable)(text) : text;
64
+ fs.mkdirSync(path.dirname(fileName), { recursive: true });
65
+ fs.writeFileSync(fileName, code);
66
+ if (isLua) {
67
+ written.push({ path: fileName, code });
68
+ }
69
+ });
70
+ for (const diagnostic of emit.diagnostics) {
71
+ problems.push(toProblem(diagnostic));
72
+ }
73
+ const compiled = !problems.some((p) => p.level === "error");
74
+ if (compiled) {
75
+ copyStaticFiles(project);
76
+ problems.push(...(0, checks_1.checkOutput)(written, project.manifest, { srcDir: project.srcDir, outDir: project.outDir, dev }));
77
+ }
78
+ const ok = !problems.some((p) => p.level === "error");
79
+ let deployedTo;
80
+ if (ok && options.deployTo !== undefined && project.manifest !== undefined) {
81
+ deployedTo = path.resolve(options.cwd, options.deployTo, project.manifest.name);
82
+ fs.rmSync(deployedTo, { recursive: true, force: true });
83
+ fs.cpSync(project.outDir, deployedTo, { recursive: true });
84
+ }
85
+ return {
86
+ ok,
87
+ dev,
88
+ problems,
89
+ notes,
90
+ files: written.map((f) => f.path),
91
+ outDir: project.outDir,
92
+ deployedTo,
93
+ project,
94
+ };
95
+ }
96
+ /** Rewrites the source plugin.json too, so `npm version patch` is the only bump needed. */
97
+ function syncManifestVersion(project, notes) {
98
+ const target = project.packageJson?.version;
99
+ if (!project.syncVersion || project.manifest === undefined || typeof target !== "string") {
100
+ return;
101
+ }
102
+ if (project.manifest.version === target) {
103
+ return;
104
+ }
105
+ const previous = project.manifest.version;
106
+ project.manifest.version = target;
107
+ const raw = JSON.parse(fs.readFileSync(project.manifestPath, "utf8"));
108
+ raw.version = target;
109
+ fs.writeFileSync(project.manifestPath, `${JSON.stringify(raw, null, 2)}\n`);
110
+ notes.push(`plugin.json version ${String(previous)} -> ${target} (follows package.json)`);
111
+ }
112
+ function copyStaticFiles(project) {
113
+ fs.mkdirSync(project.outDir, { recursive: true });
114
+ if (fs.existsSync(project.manifestPath)) {
115
+ fs.copyFileSync(project.manifestPath, path.join(project.outDir, "plugin.json"));
116
+ }
117
+ const resources = path.join(project.cwd, "resources");
118
+ if (fs.existsSync(resources) && fs.statSync(resources).isDirectory()) {
119
+ fs.cpSync(resources, path.join(project.outDir, "resources"), { recursive: true });
120
+ }
121
+ }
122
+ function toProblem(diagnostic) {
123
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
124
+ const level = diagnostic.category === ts.DiagnosticCategory.Error ? "error" : "warning";
125
+ if (diagnostic.file !== undefined && diagnostic.start !== undefined) {
126
+ const { line } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
127
+ return { level, file: diagnostic.file.fileName, line: line + 1, message: `TS${diagnostic.code}: ${message}` };
128
+ }
129
+ return { level, message: `TS${diagnostic.code}: ${message}` };
130
+ }
package/dist/checks.js ADDED
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.checkManifest = checkManifest;
37
+ exports.checkOutput = checkOutput;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const libraries_1 = require("./libraries");
41
+ const permissions_1 = require("./permissions");
42
+ const DEPENDENCY_SHAPE_HINT = (key) => [
43
+ "name each dependency and give its mod.io id:",
44
+ "",
45
+ ` "${key}": { "prettyui": 6398181 }`,
46
+ "",
47
+ "the id is the number on the plugin's mod.io page",
48
+ ];
49
+ /** Standard library pieces the sandbox removes or replaces. */
50
+ const FORBIDDEN_GLOBALS = [
51
+ { pattern: /\bos\.\w+/, what: "the os library" },
52
+ { pattern: /\bio\.\w+/, what: "the io library" },
53
+ { pattern: /\bdebug\.\w+/, what: "the debug library" },
54
+ { pattern: /\bloadstring\s*\(/, what: "loadstring" },
55
+ { pattern: /\bloadfile\s*\(/, what: "loadfile" },
56
+ { pattern: /\bdofile\s*\(/, what: "dofile" },
57
+ { pattern: /(?<![\w.])load\s*\(/, what: "load" },
58
+ ];
59
+ function checkManifest(manifest, manifestError, file) {
60
+ if (manifest === undefined) {
61
+ // No file prefix: these messages already name plugin.json.
62
+ return [
63
+ {
64
+ level: "error",
65
+ message: manifestError ?? "plugin.json could not be read",
66
+ hint: fs.existsSync(file)
67
+ ? ["the position above is where the parser gave up, usually a trailing comma or a missing quote"]
68
+ : ["every plugin needs a plugin.json beside its package.json", "rs3-plugin init writes one for you"],
69
+ },
70
+ ];
71
+ }
72
+ const problems = [];
73
+ const error = (message) => problems.push({ level: "error", file, message });
74
+ const warning = (message) => problems.push({ level: "warning", file, message });
75
+ if (typeof manifest.name !== "string" || manifest.name.trim() === "") {
76
+ error('"name" is required and must be a non-empty string');
77
+ }
78
+ if (typeof manifest.version !== "string" || manifest.version.trim() === "") {
79
+ error('"version" is required and must be a string such as "1.0.0"');
80
+ }
81
+ if (manifest.dependencies === undefined) {
82
+ problems.push({
83
+ level: "error",
84
+ file,
85
+ message: '"dependencies" is required',
86
+ hint: ['write {} when the plugin depends on nothing:', "", ' "dependencies": {}'],
87
+ });
88
+ }
89
+ else if (!isPlainObject(manifest.dependencies) && !Array.isArray(manifest.dependencies)) {
90
+ error('"dependencies" must be an object mapping identifiers to mod.io ids');
91
+ }
92
+ if (Array.isArray(manifest.dependencies) && manifest.dependencies.length === 0) {
93
+ warning('"dependencies" is []; the client expects an object, so write {} instead');
94
+ }
95
+ for (const key of ["requiredDependencies", "optionalDependencies"]) {
96
+ const value = manifest[key];
97
+ if (value === undefined) {
98
+ continue;
99
+ }
100
+ if (Array.isArray(value)) {
101
+ if (value.length === 0) {
102
+ warning(`"${key}" is []; the client expects an object, so write {} instead`);
103
+ }
104
+ else {
105
+ problems.push({
106
+ level: "error",
107
+ file,
108
+ message: `"${key}" is a list of names, which the client no longer accepts`,
109
+ hint: DEPENDENCY_SHAPE_HINT(key),
110
+ });
111
+ }
112
+ continue;
113
+ }
114
+ if (!isPlainObject(value)) {
115
+ problems.push({
116
+ level: "error",
117
+ file,
118
+ message: `"${key}" must be an object mapping identifiers to mod.io ids`,
119
+ hint: DEPENDENCY_SHAPE_HINT(key),
120
+ });
121
+ continue;
122
+ }
123
+ for (const [name, id] of Object.entries(value)) {
124
+ if (typeof id !== "number" || !Number.isInteger(id) || id <= 0) {
125
+ error(`"${key}.${name}" must be a positive mod.io id`);
126
+ continue;
127
+ }
128
+ const known = (0, libraries_1.findLibrary)(name);
129
+ if (known === undefined) {
130
+ problems.push({
131
+ level: "warning",
132
+ file,
133
+ message: `"${key}.${name}" is not in the known library list`,
134
+ hint: ["expected if the library is new or private", "otherwise refresh the list with: npm run deps:dump"],
135
+ });
136
+ }
137
+ else if (known.id !== id) {
138
+ problems.push({
139
+ level: "warning",
140
+ file,
141
+ message: `"${key}.${name}" has id ${id} but the known library list says ${known.id}`,
142
+ hint: ["check the id on the plugin's mod.io page; the client resolves dependencies by id, not by name"],
143
+ });
144
+ }
145
+ }
146
+ }
147
+ for (const key of ["requiredPermissions", "optionalPermissions"]) {
148
+ const value = manifest[key];
149
+ if (value === undefined) {
150
+ continue;
151
+ }
152
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
153
+ problems.push({
154
+ level: "error",
155
+ file,
156
+ message: `"${key}" must be a list of permission names`,
157
+ hint: ["", ` "${key}": [${(0, permissions_1.permissionNames)().map((p) => JSON.stringify(p)).join(", ")}]`, "", "use [] to ask for none"],
158
+ });
159
+ continue;
160
+ }
161
+ for (const permission of value) {
162
+ if ((0, permissions_1.findPermission)(permission) === undefined) {
163
+ problems.push({
164
+ level: "error",
165
+ file,
166
+ message: `"${key}" lists unknown permission "${permission}"`,
167
+ hint: [
168
+ "the client refuses to load a plugin that asks for a permission it does not know",
169
+ `valid permissions: ${(0, permissions_1.permissionNames)().join(", ")}`,
170
+ ].join("\n"),
171
+ });
172
+ }
173
+ }
174
+ }
175
+ if (manifest.urls !== undefined) {
176
+ if (!isPlainObject(manifest.urls)) {
177
+ problems.push({
178
+ level: "error",
179
+ file,
180
+ message: '"urls" must be an object mapping names to URLs',
181
+ hint: ["", ' "urls": { "api": "https://example.com/" }'],
182
+ });
183
+ }
184
+ else {
185
+ for (const [name, url] of Object.entries(manifest.urls)) {
186
+ if (typeof url !== "string" || !isHttpUrl(url)) {
187
+ problems.push({
188
+ level: "error",
189
+ file,
190
+ message: `"urls.${name}" is not an http or https URL`,
191
+ hint: ["the client only accepts absolute http(s) URLs, such as https://example.com/"],
192
+ });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ for (const key of ["autoEnable", "hasSettingsIcon", "hasRibbonIcon"]) {
198
+ if (manifest[key] !== undefined && typeof manifest[key] !== "boolean") {
199
+ error(`"${key}" must be true or false`);
200
+ }
201
+ }
202
+ if (manifest.autoEnable !== undefined) {
203
+ warning('"autoEnable" is no longer read by the client and can be deleted');
204
+ }
205
+ if (manifest.contactEmailAddress !== undefined && typeof manifest.contactEmailAddress !== "string") {
206
+ error('"contactEmailAddress" must be a string');
207
+ }
208
+ return problems;
209
+ }
210
+ function checkOutput(files, manifest, context) {
211
+ const problems = [];
212
+ const dev = context.dev;
213
+ const pluginFiles = files.filter((f) => !/lualib_bundle\.lua$/.test(f.path));
214
+ // Only the plugin's own files count. The bundled runtime always contains the assignment,
215
+ // inside definePlugin, so including it here would make this check pass for every plugin.
216
+ const ownFiles = pluginFiles.filter((f) => !f.path.split(path.sep).includes("lua_modules"));
217
+ const hasEntry = ownFiles.some((f) => /\bPluginLogic\s*=|\bfunction\s+PluginLogic\b|\bdefinePlugin\s*\(/.test(f.code));
218
+ if (!hasEntry) {
219
+ problems.push({
220
+ level: "error",
221
+ message: "nothing in this plugin sets up a tick handler, so the client would load it and never run it",
222
+ hint: [
223
+ "call definePlugin from src/main.ts:",
224
+ "",
225
+ ' import { definePlugin } from "rs3-api-extended";',
226
+ "",
227
+ " definePlugin({ modules: [] });",
228
+ "",
229
+ "or assign the PluginLogic global yourself",
230
+ ],
231
+ });
232
+ }
233
+ if (!dev) {
234
+ for (const file of pluginFiles) {
235
+ const helpers = new Set();
236
+ for (const match of file.code.matchAll(/__TS__(\w+)/g)) {
237
+ helpers.add(match[1]);
238
+ }
239
+ if (helpers.size > 0) {
240
+ problems.push({
241
+ level: "error",
242
+ file: sourceOf(file.path, context),
243
+ message: `needs the TypeScriptToLua helper library for ${[...helpers].join(", ")}, which a release build does not ship`,
244
+ hint: [
245
+ "something here has no direct Lua equivalent, usually an array, string or object",
246
+ "method, a class, spread, Map or Set",
247
+ "use plain loops, tables and functions instead",
248
+ ],
249
+ });
250
+ }
251
+ }
252
+ }
253
+ const required = stringArray(manifest?.requiredPermissions);
254
+ const optional = stringArray(manifest?.optionalPermissions);
255
+ for (const permission of (0, permissions_1.loadPermissions)()) {
256
+ const users = pluginFiles.filter((f) => permission.gates.some((gate) => gate.test(f.code)));
257
+ if (users.length === 0) {
258
+ continue;
259
+ }
260
+ if (!required.includes(permission.name) && !optional.includes(permission.name)) {
261
+ const hint = [];
262
+ if (!dev) {
263
+ hint.push(`called from ${describeCallers(users, context)}`);
264
+ }
265
+ const merged = [...required.filter((p) => (0, permissions_1.findPermission)(p) !== undefined), permission.name];
266
+ hint.push(`add it to requiredPermissions in plugin.json:`, "", ` "requiredPermissions": [${merged.map((p) => JSON.stringify(p)).join(", ")}]`);
267
+ problems.push({
268
+ level: "error",
269
+ message: `${permission.gateLabel} needs the "${permission.name}" permission, which plugin.json does not ask for`,
270
+ hint: hint.join("\n"),
271
+ });
272
+ }
273
+ if (permission.needsUrls && manifest !== undefined) {
274
+ const urls = isPlainObject(manifest.urls) ? Object.keys(manifest.urls) : [];
275
+ if (urls.length === 0) {
276
+ problems.push({
277
+ level: "warning",
278
+ message: `${permission.gateLabel} can only reach hosts that plugin.json lists under "urls", and it lists none`,
279
+ hint: ['name the hosts you call in plugin.json:', "", ' "urls": { "api": "https://example.com/" }'].join("\n"),
280
+ });
281
+ }
282
+ }
283
+ }
284
+ for (const file of ownFiles) {
285
+ for (const forbidden of FORBIDDEN_GLOBALS) {
286
+ if (forbidden.pattern.test(file.code)) {
287
+ problems.push({
288
+ level: "warning",
289
+ file: sourceOf(file.path, context),
290
+ message: `uses ${forbidden.what}, which the plugin sandbox does not provide`,
291
+ hint: ["the call will fail at runtime; there is no replacement inside the sandbox"],
292
+ });
293
+ }
294
+ }
295
+ }
296
+ return problems;
297
+ }
298
+ function describeCallers(users, context) {
299
+ const named = users.map((file) => path.relative(process.cwd(), sourceOf(file.path, context)));
300
+ const shown = named.slice(0, 3).join(", ");
301
+ return named.length > 3 ? `${shown} and ${named.length - 3} more` : shown;
302
+ }
303
+ // A release build writes one Lua file per source file, so dist/modules/login.lua came from
304
+ // src/modules/login.ts. Naming the Lua would send the reader into generated code.
305
+ function sourceOf(file, context) {
306
+ const relative = path.relative(context.outDir, file);
307
+ if (relative.startsWith("..") || relative.split(path.sep).includes("lua_modules")) {
308
+ return file;
309
+ }
310
+ return path.join(context.srcDir, relative.replace(/\.lua$/, ".ts"));
311
+ }
312
+ function firstLine(code, pattern) {
313
+ const lines = code.split("\n");
314
+ for (let i = 0; i < lines.length; i++) {
315
+ if (pattern.test(lines[i])) {
316
+ return i + 1;
317
+ }
318
+ }
319
+ return undefined;
320
+ }
321
+ function stringArray(value) {
322
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
323
+ }
324
+ function isPlainObject(value) {
325
+ return typeof value === "object" && value !== null && !Array.isArray(value);
326
+ }
327
+ function isHttpUrl(value) {
328
+ try {
329
+ const url = new URL(value);
330
+ return url.protocol === "http:" || url.protocol === "https:";
331
+ }
332
+ catch {
333
+ return false;
334
+ }
335
+ }
package/dist/dev.js ADDED
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.dev = dev;
37
+ const fs = __importStar(require("node:fs"));
38
+ const build_1 = require("./build");
39
+ const project_1 = require("./project");
40
+ const report_1 = require("./report");
41
+ /** Rebuilds the dev bundle whenever a source file or plugin.json changes. Runs until interrupted. */
42
+ function dev(options) {
43
+ const project = (0, project_1.loadProject)(options.cwd);
44
+ let pending;
45
+ let building = false;
46
+ let queued = false;
47
+ const run = () => {
48
+ if (building) {
49
+ queued = true;
50
+ return;
51
+ }
52
+ building = true;
53
+ try {
54
+ const result = (0, build_1.build)({ cwd: options.cwd, dev: true, deployTo: options.deployTo });
55
+ (0, report_1.printProblems)(result.problems);
56
+ (0, report_1.printSummary)(result);
57
+ }
58
+ catch (error) {
59
+ (0, report_1.printError)(error);
60
+ }
61
+ finally {
62
+ building = false;
63
+ }
64
+ if (queued) {
65
+ queued = false;
66
+ schedule();
67
+ }
68
+ };
69
+ const schedule = () => {
70
+ if (pending !== undefined) {
71
+ clearTimeout(pending);
72
+ }
73
+ pending = setTimeout(run, 150);
74
+ };
75
+ run();
76
+ console.log(`watching ${project.srcDir} for changes (ctrl+c to stop)`);
77
+ fs.watch(project.srcDir, { recursive: true }, schedule);
78
+ if (fs.existsSync(project.manifestPath)) {
79
+ fs.watch(project.manifestPath, schedule);
80
+ }
81
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliError = void 0;
4
+ /** The hint prints indented under the message, matching how build problems look. */
5
+ class CliError extends Error {
6
+ hint;
7
+ constructor(message, hint) {
8
+ super(message);
9
+ this.name = "CliError";
10
+ this.hint = Array.isArray(hint) ? hint.join("\n") : hint;
11
+ }
12
+ }
13
+ exports.CliError = CliError;