@jspicl/cli 4.0.0-alpha.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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Agron Kabashi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @jspicl/cli
2
+
3
+ A command-line tool that simplifies PICO-8 game development in JavaScript.
4
+
5
+ **Features:**
6
+
7
+ - Built-in build pipeline powered by esbuild
8
+ - JavaScript to PICO-8 Lua transpilation via [@jspicl/core](../core)
9
+ - Tree-shaking to minimize token count
10
+ - PNG spritesheet support - use your favorite image editor
11
+ - Live reloading with watch mode
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -D @jspicl/cli
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```bash
22
+ jspicl <input> <output> --config <config-file> [--watch]
23
+ ```
24
+
25
+ **Example:**
26
+
27
+ ```bash
28
+ jspicl src/game.js build/game.p8 --config jspicl.config.ts --watch
29
+ ```
30
+
31
+ ## Config File
32
+
33
+ Create a config file (JavaScript or TypeScript):
34
+
35
+ ```typescript
36
+ // jspicl.config.ts
37
+ import type {Config} from "@jspicl/cli/types";
38
+
39
+ const config: Config = {
40
+ spritesheetImagePath: "assets/sprites.png",
41
+ jsOutput: "build/game.js",
42
+ picoPath: "/Applications/PICO-8.app/Contents/MacOS/pico8",
43
+ reloadOnSave: true,
44
+ showStats: true
45
+ };
46
+
47
+ export default config;
48
+ ```
49
+
50
+ ### Config Options
51
+
52
+ | Name | Type | Required | Description |
53
+ | ---------------------- | -------- | -------- | -------------------------------------------------- |
54
+ | `spritesheetImagePath` | string | Yes | Path to PNG spritesheet |
55
+ | `jsOutput` | string | Yes | Path to output bundled JavaScript (for debugging) |
56
+ | `picoPath` | string | No | Custom path to PICO-8 executable |
57
+ | `includeBanner` | boolean | No | Include jspicl info comment in output |
58
+ | `luaOutput` | string | No | Path to output transpiled Lua (for debugging) |
59
+ | `pipeOutputToConsole` | boolean | No | Pipe console.log to terminal |
60
+ | `reloadOnSave` | boolean | No | Reload PICO-8 when cartridge is updated |
61
+ | `showStats` | boolean | No | Display build statistics |
62
+ | `jspicl` | object | No | Options passed to @jspicl/core (prettify, etc.) |
63
+
64
+ ## CLI Options
65
+
66
+ | Name | Description |
67
+ | ----------------- | ---------------------------------------- |
68
+ | `--config`, `-c` | Path to config file (required) |
69
+ | `--watch`, `-w` | Watch for changes and rebuild |
70
+
71
+ ## Watch Mode
72
+
73
+ With `--watch`, the CLI rebuilds automatically when source files change:
74
+
75
+ ![](https://i.imgur.com/QYj4Xga.gif)
76
+
77
+ This applies for the spritesheet as well. Simply save your image and your changes will be reloaded in PICO-8.
78
+
79
+ ![](https://github.com/AgronKabashi/assets/raw/814f6efe24bc9aca5d9d6ca6259279733529e300/rollup-plugin-jspicl/spritesheetLiveReload.gif)
80
+
81
+ **Note:** Automatic PICO-8 reload is currently only supported on macOS.
82
+
83
+ ## Requirements
84
+
85
+ - Node.js 22+
86
+
87
+ ## Related
88
+
89
+ - [@jspicl/core](../core) - The transpiler library
90
+ - [Games made with jspicl](https://github.com/topics/jspicl-sample)
91
+
92
+ ## License
93
+
94
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,530 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+ import path5 from "node:path";
7
+ import process2 from "process";
8
+ import esbuild from "esbuild";
9
+
10
+ // src/arguments.ts
11
+ import path from "node:path";
12
+ var cliArguments = {
13
+ watch: {
14
+ description: "Reload cartridge on rebuilds",
15
+ type: "boolean",
16
+ default: false,
17
+ alias: "w"
18
+ },
19
+ config: {
20
+ description: "Path to a config file",
21
+ type: "string",
22
+ alias: "c",
23
+ requiresArg: true,
24
+ default: {
25
+ jsOutput: "build/jsOutput.js"
26
+ },
27
+ coerce: async (p) => {
28
+ const configModule = await import(path.resolve(p));
29
+ return configModule.default;
30
+ }
31
+ }
32
+ };
33
+
34
+ // src/logging.ts
35
+ import fs from "node:fs";
36
+ import path2 from "node:path";
37
+ import { mkdirp } from "mkdirp";
38
+
39
+ // src/tokenCounter.ts
40
+ var tokens = [
41
+ /\"[^\"]*\"/,
42
+ // Strings
43
+ /\d+\.\d+/,
44
+ // floating numbers
45
+ /\w+/,
46
+ // words
47
+ /\d+/,
48
+ // numbers
49
+ /!=/,
50
+ // inequality
51
+ /==/,
52
+ // comparison
53
+ /\+=/,
54
+ // incrementing assignment
55
+ /-=/,
56
+ // decrementing assignment
57
+ /<=/,
58
+ // equal or less than
59
+ />=/,
60
+ // equal or greater than
61
+ /\.\./,
62
+ // string concatenation
63
+ /</,
64
+ // less than
65
+ />/,
66
+ // greater than
67
+ /\+/,
68
+ // addition
69
+ /-/,
70
+ // subtraction
71
+ /\//,
72
+ // division
73
+ /\*/,
74
+ // multiplication
75
+ /=/,
76
+ // equals
77
+ /\%/,
78
+ // percentage
79
+ /\(/,
80
+ // paranthesis
81
+ /\[/,
82
+ // left bracket
83
+ /\{/
84
+ // left curly brace
85
+ ].map((r) => r.source).join("|");
86
+ function getTokenCount(luaCode) {
87
+ const regex = new RegExp(`(${tokens})`, "gi");
88
+ return (luaCode.match(regex) || []).filter(
89
+ (token) => token !== "local" && token !== "end"
90
+ ).length;
91
+ }
92
+
93
+ // src/logging.ts
94
+ function logToFile(content, filePath) {
95
+ mkdirp.sync(path2.dirname(filePath));
96
+ fs.writeFileSync(path2.resolve(filePath), content);
97
+ }
98
+ function logInfo(content) {
99
+ logToConsole(content, "\x1B[34m\u2139" /* info */);
100
+ }
101
+ function logSuccess(content) {
102
+ logToConsole(content, "\x1B[32m\u2714" /* success */);
103
+ }
104
+ function logWarning(content) {
105
+ logToConsole(content, "\x1B[33m\u26A0" /* warning */);
106
+ }
107
+ function logError(content) {
108
+ logToConsole(content, "\x1B[31m\u2716" /* error */);
109
+ }
110
+ function logToConsole(content, icon) {
111
+ console.log(`${icon} ${content}\x1B[0m`);
112
+ }
113
+ function logStats(lua, polyfillOutput, code) {
114
+ const tokens2 = getTokenCount(lua);
115
+ const polyfillTokens = getTokenCount(polyfillOutput);
116
+ logInfo("Cartridge Statistics");
117
+ console.log("".padEnd(41, "\u2014"));
118
+ const stats = [
119
+ {
120
+ label: "Characters",
121
+ value: lua.length,
122
+ percent: `${~~(lua.length * 100 / 65535)}%`
123
+ },
124
+ {
125
+ label: "Tokens",
126
+ value: `~${tokens2}`,
127
+ percent: `${~~(tokens2 * 100 / 8192)}%`
128
+ },
129
+ {
130
+ label: " - Polyfills",
131
+ value: `~${polyfillTokens}`
132
+ },
133
+ {
134
+ label: "Filesize",
135
+ value: `${Math.ceil(code.length / 1024)} KB`
136
+ }
137
+ ];
138
+ stats.forEach((stats2) => {
139
+ const label = `${stats2.label}:`.padEnd(20, " ");
140
+ const value = `${stats2.value}`.padStart(15, " ");
141
+ const percent = stats2.percent ? `\x1B[33m${stats2.percent}`.padStart(10, " ") : "";
142
+ console.log(`${label}${value}${percent}\x1B[0m`);
143
+ });
144
+ }
145
+
146
+ // src/createPico8Launcher.ts
147
+ import path3 from "path";
148
+ import { spawn, exec } from "child_process";
149
+ import { fileURLToPath } from "url";
150
+ var __dirname = path3.dirname(fileURLToPath(import.meta.url));
151
+ var osMatrix = {
152
+ win32: {
153
+ execPath: `"C:\\Program Files (x86)\\PICO-8\\pico8.exe"`
154
+ },
155
+ darwin: {
156
+ execPath: "/Applications/PICO-8.app/Contents/MacOS/pico8",
157
+ reloadCommand: path3.resolve(__dirname, "../scripts/reload-pico8-bin")
158
+ },
159
+ linux: {
160
+ execPath: "~/pico-8/pico8"
161
+ }
162
+ };
163
+ function createPico8Launcher(watch, customPicoPath, reloadOnSave, pipeOutputToConsole) {
164
+ let picoProcess;
165
+ const { execPath, reloadCommand } = osMatrix[process.platform];
166
+ return (cartridgePath) => {
167
+ if (!watch || !cartridgePath) {
168
+ return;
169
+ }
170
+ if (picoProcess) {
171
+ if (!reloadOnSave) {
172
+ return;
173
+ }
174
+ if (reloadCommand) {
175
+ logSuccess("Reloading cartridge in PICO-8");
176
+ exec(reloadCommand);
177
+ } else {
178
+ logWarning(
179
+ "Autoreloading is currently not supported on your OS. Please press Ctrl+R in PICO-8 to see new changes."
180
+ );
181
+ }
182
+ } else {
183
+ logSuccess("Running cartridge in PICO-8");
184
+ picoProcess = launchPico8(
185
+ customPicoPath || execPath,
186
+ cartridgePath,
187
+ pipeOutputToConsole
188
+ );
189
+ picoProcess.on("close", (errorCode) => {
190
+ if (errorCode !== 0) {
191
+ logWarning(`PICO-8 process exited with code ${errorCode}`);
192
+ }
193
+ picoProcess = null;
194
+ });
195
+ }
196
+ };
197
+ }
198
+ function launchPico8(picoPath, cartridgePath, pipeOutputToConsole) {
199
+ return spawn(picoPath, ["-run", `"${path3.resolve(cartridgePath)}"`], {
200
+ shell: true,
201
+ stdio: pipeOutputToConsole ? "inherit" : "pipe"
202
+ });
203
+ }
204
+
205
+ // src/watchPlugin.ts
206
+ import fs4 from "node:fs";
207
+
208
+ // src/cartridge.ts
209
+ import fs2 from "fs";
210
+ import path4 from "path";
211
+ function generateCartridgeContent({
212
+ lua = "",
213
+ gff = "",
214
+ gfx = "",
215
+ music = "",
216
+ map = "",
217
+ sfx = ""
218
+ } = {}) {
219
+ return [
220
+ "pico-8 cartridge // http://www.pico-8.com",
221
+ "version 42",
222
+ "__lua__",
223
+ lua,
224
+ gfx && `__gfx__
225
+ ${gfx}`,
226
+ gff && `__gff__
227
+ ${gff}`,
228
+ map && `__map__
229
+ ${map}`,
230
+ sfx && `__sfx__
231
+ ${sfx}`,
232
+ music && `__music__
233
+ ${music}`,
234
+ "\n"
235
+ ].filter(Boolean).join("\n");
236
+ }
237
+ function getPicoSectionsFromCartridge(cartridgePath) {
238
+ try {
239
+ const resolvedCartridgePath = path4.resolve(cartridgePath);
240
+ const content = fs2.existsSync(resolvedCartridgePath) ? fs2.readFileSync(resolvedCartridgePath, "utf8") : "";
241
+ return parsePico8Cartridge(content);
242
+ } catch (error) {
243
+ throw new Error(
244
+ `Failed to read cartridge at ${cartridgePath}: ${error.message}`
245
+ );
246
+ }
247
+ }
248
+ function parsePico8Cartridge(content) {
249
+ const cartridgeSections = {
250
+ lua: "",
251
+ gff: "",
252
+ gfx: "",
253
+ music: "",
254
+ map: "",
255
+ sfx: ""
256
+ };
257
+ const regex = /__(\w+)__\n([\s\S]*?)(?=\n__\w+__\n|\n(\n|$))/g;
258
+ let result;
259
+ while ((result = regex.exec(content)) !== null) {
260
+ const [, section, content2] = result;
261
+ cartridgeSections[section] = content2;
262
+ }
263
+ return cartridgeSections;
264
+ }
265
+
266
+ // src/spritesheet.ts
267
+ import fs3 from "fs";
268
+ import pngjs from "pngjs";
269
+
270
+ // src/constants.ts
271
+ var JSPICL_BANNER = `--[[
272
+ generated with jspicl,
273
+ a javascript to pico-8 lua
274
+ transpiler.
275
+
276
+ please report any bugs at:
277
+ https://github.com/jspicl/jspicl/issues
278
+ ]]--
279
+ `;
280
+ var PICO8_PALETTE = [
281
+ {
282
+ r: 0,
283
+ g: 0,
284
+ b: 0
285
+ },
286
+ {
287
+ r: 29,
288
+ g: 43,
289
+ b: 83
290
+ },
291
+ {
292
+ r: 126,
293
+ g: 37,
294
+ b: 83
295
+ },
296
+ {
297
+ r: 0,
298
+ g: 135,
299
+ b: 81
300
+ },
301
+ {
302
+ r: 171,
303
+ g: 82,
304
+ b: 54
305
+ },
306
+ {
307
+ r: 95,
308
+ g: 87,
309
+ b: 79
310
+ },
311
+ {
312
+ r: 194,
313
+ g: 195,
314
+ b: 199
315
+ },
316
+ {
317
+ r: 255,
318
+ g: 241,
319
+ b: 232
320
+ },
321
+ {
322
+ r: 255,
323
+ g: 0,
324
+ b: 77
325
+ },
326
+ {
327
+ r: 255,
328
+ g: 163,
329
+ b: 0
330
+ },
331
+ {
332
+ r: 255,
333
+ g: 236,
334
+ b: 39
335
+ },
336
+ {
337
+ r: 0,
338
+ g: 228,
339
+ b: 54
340
+ },
341
+ {
342
+ r: 41,
343
+ g: 173,
344
+ b: 255
345
+ },
346
+ {
347
+ r: 131,
348
+ g: 118,
349
+ b: 156
350
+ },
351
+ {
352
+ r: 255,
353
+ g: 119,
354
+ b: 168
355
+ },
356
+ {
357
+ r: 255,
358
+ g: 204,
359
+ b: 170
360
+ }
361
+ ];
362
+
363
+ // src/spritesheet.ts
364
+ var spritesheetWidth = 128;
365
+ var spritesheetHeight = 128;
366
+ var hexBase = 16;
367
+ var pixelDataSize = 4;
368
+ var toClosestColor = (pixels) => (_, offset) => {
369
+ const pixelOffset = offset * pixelDataSize;
370
+ const pixel = {
371
+ r: pixels[pixelOffset],
372
+ g: pixels[pixelOffset + 1],
373
+ b: pixels[pixelOffset + 2]
374
+ };
375
+ let minDistance = Number.MAX_VALUE;
376
+ let closestPaletteColor = 0;
377
+ PICO8_PALETTE.forEach(
378
+ (color, i) => {
379
+ const diff = (color.r - pixel.r) ** 2 + (color.g - pixel.g) ** 2 + (color.b - pixel.b) ** 2;
380
+ if (diff < minDistance) {
381
+ minDistance = diff;
382
+ closestPaletteColor = i;
383
+ }
384
+ }
385
+ );
386
+ return closestPaletteColor.toString(hexBase);
387
+ };
388
+ function getSpritesheetFromImage(imagePath) {
389
+ if (!imagePath) {
390
+ throw new Error("Image path is missing");
391
+ }
392
+ const stream = fs3.createReadStream(imagePath).pipe(new pngjs.PNG());
393
+ return new Promise(
394
+ (resolve) => stream.on("parsed", () => {
395
+ if (stream.width !== spritesheetWidth || stream.height !== spritesheetHeight) {
396
+ throw new Error("The spritesheet must be a 128x128 png image");
397
+ }
398
+ const pixels = new Array(stream.width * stream.height).fill(0).map(toClosestColor(stream.data));
399
+ const pixelsAsString = new Array(stream.height).fill(0).map(
400
+ (_, offset) => (
401
+ // cut the strings so we get stacks of 128 characters
402
+ pixels.slice(
403
+ offset * spritesheetWidth,
404
+ offset * spritesheetWidth + spritesheetWidth
405
+ ).join("")
406
+ )
407
+ ).join("\n");
408
+ resolve(pixelsAsString);
409
+ })
410
+ );
411
+ }
412
+
413
+ // src/transpile.ts
414
+ import { jspicl } from "@jspicl/core";
415
+ function transpile(javascriptCode, config) {
416
+ const { includeBanner, polyfillTransform, jspicl: jspiclOptions = {} } = config;
417
+ const jspiclBanner = includeBanner && `${JSPICL_BANNER}` || "";
418
+ const { code, polyfills } = jspicl(javascriptCode, jspiclOptions);
419
+ const polyfillOutput = polyfillTransform ? polyfillTransform(polyfills) : Object.values(polyfills).join("\n");
420
+ const lua = `${jspiclBanner}${polyfillOutput}${code}`;
421
+ return {
422
+ lua,
423
+ polyfillOutput
424
+ };
425
+ }
426
+
427
+ // src/watchPlugin.ts
428
+ var watchPlugin = (options) => ({
429
+ name: "watcher",
430
+ setup(build) {
431
+ build.onEnd(async (result) => {
432
+ const { onBuildEnd, onBuildError, config, output } = options;
433
+ const { jsOutput, spritesheetImagePath } = config;
434
+ if (result.errors.length) {
435
+ const errors = result.errors.map((error) => error.text).join("\n");
436
+ onBuildError?.(errors);
437
+ return;
438
+ }
439
+ console.clear();
440
+ const jsContent = fs4.readFileSync(jsOutput, "utf-8");
441
+ const transpiledSource = transpile(jsContent, config);
442
+ const cartridgeSections = getPicoSectionsFromCartridge(output);
443
+ const gfxSection = await getSpritesheetFromImage(spritesheetImagePath);
444
+ const cartridgeContent = generateCartridgeContent({
445
+ ...cartridgeSections,
446
+ lua: transpiledSource.lua,
447
+ gfx: gfxSection
448
+ });
449
+ onBuildEnd?.(cartridgeContent, transpiledSource);
450
+ });
451
+ }
452
+ });
453
+
454
+ // src/index.ts
455
+ async function getCommandlineArguments() {
456
+ const argv = await yargs(hideBin(process2.argv)).options(cliArguments).usage("jspicl-cli input output [<args>]").demandCommand(2, "Please specify an input and output file").help(false).strict().wrap(null).parseAsync();
457
+ const {
458
+ _: [input, output],
459
+ ...restArguments
460
+ } = argv;
461
+ return {
462
+ input: path5.resolve(input.toString()),
463
+ output: path5.resolve(output.toString()),
464
+ options: restArguments
465
+ };
466
+ }
467
+ async function startBuildService(input, output, cliOptions) {
468
+ const { watch, config } = cliOptions;
469
+ const runPico = createPico8Launcher(
470
+ watch,
471
+ config.picoPath,
472
+ config.reloadOnSave,
473
+ config.pipeOutputToConsole
474
+ );
475
+ const jsOutput = path5.resolve(config.jsOutput || "build/jsOutput.js");
476
+ const buildConfig = {
477
+ entryPoints: [input],
478
+ bundle: true,
479
+ platform: "neutral",
480
+ treeShaking: false,
481
+ minify: false,
482
+ format: "esm",
483
+ outfile: jsOutput,
484
+ plugins: [
485
+ watchPlugin({
486
+ config,
487
+ output,
488
+ onBuildError: (errors) => {
489
+ logError(`Build failed with errors:
490
+ ${errors}`);
491
+ },
492
+ onBuildEnd: (cartridgeContent, transpiledSource) => {
493
+ config.luaOutput && logToFile(transpiledSource.lua, config.luaOutput);
494
+ logToFile(cartridgeContent, output);
495
+ logSuccess("Build completed");
496
+ runPico(output);
497
+ config.showStats && logStats(
498
+ transpiledSource.lua,
499
+ transpiledSource.polyfillOutput,
500
+ cartridgeContent
501
+ );
502
+ }
503
+ })
504
+ ]
505
+ };
506
+ if (watch) {
507
+ const context = await esbuild.context(buildConfig);
508
+ await context.watch();
509
+ } else {
510
+ await esbuild.build(buildConfig);
511
+ }
512
+ }
513
+ async function runCLI() {
514
+ const { input, output, options } = await getCommandlineArguments();
515
+ process2.on("SIGINT", () => {
516
+ logInfo("Shutting down...");
517
+ process2.exit(0);
518
+ });
519
+ try {
520
+ await startBuildService(input, output, options);
521
+ } catch (error) {
522
+ logError(String(error));
523
+ throw error;
524
+ }
525
+ }
526
+ runCLI();
527
+ export {
528
+ startBuildService
529
+ };
530
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/arguments.ts", "../src/logging.ts", "../src/tokenCounter.ts", "../src/createPico8Launcher.ts", "../src/watchPlugin.ts", "../src/cartridge.ts", "../src/spritesheet.ts", "../src/constants.ts", "../src/transpile.ts"],
4
+ "sourcesContent": ["import yargs from \"yargs\";\nimport {hideBin} from \"yargs/helpers\";\n\nimport path from \"node:path\";\n\nimport process from \"process\";\n\nimport esbuild, {type BuildOptions} from \"esbuild\";\nimport {cliArguments} from \"./arguments.js\";\nimport {logError, logInfo, logStats, logSuccess, logToFile} from \"./logging.js\";\nimport type {CommandLineOptions} from \"./types.js\";\nimport {createPico8Launcher} from \"./createPico8Launcher.js\";\nimport {watchPlugin} from \"./watchPlugin.js\";\n\nasync function getCommandlineArguments(): Promise<{\n input: string;\n output: string;\n options: CommandLineOptions;\n}> {\n const argv = await yargs(hideBin(process.argv))\n .options(cliArguments)\n .usage(\"jspicl-cli input output [<args>]\")\n .demandCommand(2, \"Please specify an input and output file\")\n .help(false)\n .strict()\n .wrap(null)\n .parseAsync();\n\n const {\n _: [input, output],\n ...restArguments\n } = argv;\n\n return {\n input: path.resolve(input.toString()),\n output: path.resolve(output.toString()),\n options: restArguments as CommandLineOptions\n };\n}\n\nexport async function startBuildService(\n input: string,\n output: string,\n cliOptions: CommandLineOptions\n) {\n const {watch, config} = cliOptions;\n const runPico = createPico8Launcher(\n watch,\n config.picoPath,\n config.reloadOnSave,\n config.pipeOutputToConsole\n );\n\n const jsOutput = path.resolve(config.jsOutput || \"build/jsOutput.js\");\n\n const buildConfig: BuildOptions = {\n entryPoints: [input],\n bundle: true,\n platform: \"neutral\",\n treeShaking: false,\n minify: false,\n format: \"esm\",\n outfile: jsOutput,\n plugins: [\n watchPlugin({\n config,\n output,\n\n onBuildError: (errors) => {\n logError(`Build failed with errors:\\n${errors}`);\n },\n\n onBuildEnd: (cartridgeContent, transpiledSource) => {\n config.luaOutput && logToFile(transpiledSource.lua, config.luaOutput);\n logToFile(cartridgeContent, output);\n\n logSuccess(\"Build completed\");\n runPico(output);\n\n // Statistics\n config.showStats &&\n logStats(\n transpiledSource.lua,\n transpiledSource.polyfillOutput,\n cartridgeContent\n );\n }\n })\n ]\n };\n\n if (watch) {\n const context = await esbuild.context(buildConfig);\n await context.watch();\n } else {\n await esbuild.build(buildConfig);\n }\n}\n\nasync function runCLI() {\n const {input, output, options} = await getCommandlineArguments();\n\n process.on(\"SIGINT\", () => {\n logInfo(\"Shutting down...\");\n process.exit(0);\n });\n\n try {\n await startBuildService(input, output, options);\n } catch (error) {\n logError(String(error));\n throw error;\n }\n}\n\nrunCLI();\n", "import path from \"node:path\";\nimport type {Options} from \"yargs\";\nimport type {CommandLineOptions, Config} from \"./types.js\";\n\nexport const cliArguments: Record<keyof CommandLineOptions, Options> = {\n watch: {\n description: \"Reload cartridge on rebuilds\",\n type: \"boolean\",\n default: false,\n alias: \"w\"\n },\n config: {\n description: \"Path to a config file\",\n type: \"string\",\n alias: \"c\",\n requiresArg: true,\n default: {\n jsOutput: \"build/jsOutput.js\"\n } as Config,\n coerce: async (p: string) => {\n const configModule = await import(path.resolve(p));\n return configModule.default;\n }\n }\n};\n", "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport {mkdirp} from \"mkdirp\";\nimport {getTokenCount} from \"./tokenCounter.js\";\n\nenum ICONS {\n info = \"\\x1b[34m\u2139\",\n success = \"\\x1b[32m\u2714\",\n warning = \"\\x1b[33m\u26A0\",\n error = \"\\x1b[31m\u2716\"\n}\n\nexport function logToFile(content: string, filePath: string) {\n mkdirp.sync(path.dirname(filePath));\n fs.writeFileSync(path.resolve(filePath), content);\n}\n\nexport function logInfo(content: string) {\n logToConsole(content, ICONS.info);\n}\n\nexport function logSuccess(content: string) {\n logToConsole(content, ICONS.success);\n}\n\nexport function logWarning(content: string) {\n logToConsole(content, ICONS.warning);\n}\n\nexport function logError(content: string) {\n logToConsole(content, ICONS.error);\n}\n\nfunction logToConsole(content: string, icon: string) {\n console.log(`${icon} ${content}\\x1b[0m`);\n}\n\nexport function logStats(lua: string, polyfillOutput: string, code: string) {\n const tokens = getTokenCount(lua);\n const polyfillTokens = getTokenCount(polyfillOutput);\n\n logInfo(\"Cartridge Statistics\");\n console.log(\"\".padEnd(41, \"\u2014\"));\n\n const stats = [\n {\n label: \"Characters\",\n value: lua.length,\n percent: `${~~((lua.length * 100) / 65535)}%`\n },\n {\n label: \"Tokens\",\n value: `~${tokens}`,\n percent: `${~~((tokens * 100) / 8192)}%`\n },\n {\n label: \" - Polyfills\",\n value: `~${polyfillTokens}`\n },\n {\n label: \"Filesize\",\n value: `${Math.ceil(code.length / 1024)} KB`\n }\n ];\n\n stats.forEach((stats) => {\n const label = `${stats.label}:`.padEnd(20, \" \");\n const value = `${stats.value}`.padStart(15, \" \");\n const percent = stats.percent\n ? `\\x1b[33m${stats.percent}`.padStart(10, \" \")\n : \"\";\n\n console.log(`${label}${value}${percent}\\x1b[0m`);\n });\n}\n", "// Each token is a word (e.g. variable name) or operator.\n// Pairs of brackets, and strings count as 1 token.\n// Commas, periods, LOCALs, semi-colons, ENDs, and comments are not counted.\nconst tokens = [\n /\\\"[^\\\"]*\\\"/, // Strings\n /\\d+\\.\\d+/, // floating numbers\n /\\w+/, // words\n /\\d+/, // numbers\n\n /!=/, // inequality\n /==/, // comparison\n /\\+=/, // incrementing assignment\n /-=/, // decrementing assignment\n /<=/, // equal or less than\n />=/, // equal or greater than\n /\\.\\./, // string concatenation\n\n /</, // less than\n />/, // greater than\n /\\+/, // addition\n /-/, // subtraction\n /\\//, // division\n /\\*/, // multiplication\n /=/, // equals\n /\\%/, // percentage\n /\\(/, // paranthesis\n /\\[/, // left bracket\n /\\{/ // left curly brace\n]\n .map((r) => r.source)\n .join(\"|\");\n\n/** Calculates the token count for the passed in Lua code*/\nexport function getTokenCount(luaCode: string) {\n const regex = new RegExp(`(${tokens})`, \"gi\");\n\n return (luaCode.match(regex) || []).filter(\n (token) => token !== \"local\" && token !== \"end\"\n ).length;\n}\n", "import path from \"path\";\nimport {spawn, exec, ChildProcess} from \"child_process\";\nimport {fileURLToPath} from \"url\";\nimport {logSuccess, logWarning} from \"./logging.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst osMatrix: Record<string, {execPath: string; reloadCommand?: string}> = {\n win32: {\n execPath: `\"C:\\\\Program Files (x86)\\\\PICO-8\\\\pico8.exe\"`\n },\n darwin: {\n execPath: \"/Applications/PICO-8.app/Contents/MacOS/pico8\",\n reloadCommand: path.resolve(__dirname, \"../scripts/reload-pico8-bin\")\n },\n linux: {\n execPath: \"~/pico-8/pico8\"\n }\n};\n\nexport function createPico8Launcher(\n watch: boolean,\n customPicoPath?: string,\n reloadOnSave?: boolean,\n pipeOutputToConsole?: boolean\n) {\n let picoProcess: ChildProcess | null;\n const {execPath, reloadCommand} = osMatrix[process.platform];\n\n return (cartridgePath: string) => {\n if (!watch || !cartridgePath) {\n return;\n }\n\n if (picoProcess) {\n if (!reloadOnSave) {\n return;\n }\n\n if (reloadCommand) {\n logSuccess(\"Reloading cartridge in PICO-8\");\n exec(reloadCommand);\n } else {\n logWarning(\n \"Autoreloading is currently not supported on your OS. Please press Ctrl+R in PICO-8 to see new changes.\"\n );\n }\n } else {\n logSuccess(\"Running cartridge in PICO-8\");\n // Use customized path if available, otherwise fallback to the default one for the current OS\n picoProcess = launchPico8(\n customPicoPath || execPath,\n cartridgePath,\n pipeOutputToConsole\n );\n\n picoProcess.on(\"close\", (errorCode) => {\n if (errorCode !== 0) {\n logWarning(`PICO-8 process exited with code ${errorCode}`);\n }\n picoProcess = null;\n });\n }\n };\n}\n\nfunction launchPico8(\n picoPath: string,\n cartridgePath: string,\n pipeOutputToConsole?: boolean\n) {\n return spawn(picoPath, [\"-run\", `\"${path.resolve(cartridgePath)}\"`], {\n shell: true,\n stdio: pipeOutputToConsole ? \"inherit\" : \"pipe\"\n });\n}\n", "import type {BuildResult, Plugin} from \"esbuild\";\nimport fs from \"node:fs\";\nimport {\n generateCartridgeContent,\n getPicoSectionsFromCartridge\n} from \"./cartridge.js\";\nimport {getSpritesheetFromImage} from \"./spritesheet.js\";\nimport {transpile} from \"./transpile.js\";\nimport type {Config} from \"./types.js\";\n\ntype WatchPluginOptions = {\n config: Config;\n output: string;\n onBuildEnd?: (\n cartridgeContent: string,\n transpiledSource: {lua: string; polyfillOutput: string}\n ) => void;\n onBuildError?: (errors: string) => void;\n};\n\nexport const watchPlugin = (options: WatchPluginOptions): Plugin => ({\n name: \"watcher\",\n setup(build) {\n build.onEnd(async (result: BuildResult) => {\n const {onBuildEnd, onBuildError, config, output} = options;\n\n const {jsOutput, spritesheetImagePath} = config;\n\n if (result.errors.length) {\n const errors = result.errors.map((error) => error.text).join(\"\\n\");\n onBuildError?.(errors);\n\n return;\n }\n\n console.clear();\n\n const jsContent = fs.readFileSync(jsOutput, \"utf-8\");\n const transpiledSource = transpile(jsContent, config);\n const cartridgeSections = getPicoSectionsFromCartridge(output);\n const gfxSection = await getSpritesheetFromImage(spritesheetImagePath);\n\n const cartridgeContent = generateCartridgeContent({\n ...cartridgeSections,\n lua: transpiledSource.lua,\n gfx: gfxSection\n });\n\n onBuildEnd?.(cartridgeContent, transpiledSource);\n });\n }\n});\n", "import fs from \"fs\";\nimport path from \"path\";\nimport type {PicoSections} from \"./types.js\";\n\nexport function generateCartridgeContent({\n lua = \"\",\n gff = \"\",\n gfx = \"\",\n music = \"\",\n map = \"\",\n sfx = \"\"\n}: Partial<PicoSections> = {}) {\n return [\n \"pico-8 cartridge // http://www.pico-8.com\",\n \"version 42\",\n \"__lua__\",\n lua,\n gfx && `__gfx__\\n${gfx}`,\n gff && `__gff__\\n${gff}`,\n map && `__map__\\n${map}`,\n sfx && `__sfx__\\n${sfx}`,\n music && `__music__\\n${music}`,\n \"\\n\"\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nexport function getPicoSectionsFromCartridge(\n cartridgePath: string\n): PicoSections {\n try {\n const resolvedCartridgePath = path.resolve(cartridgePath);\n const content = fs.existsSync(resolvedCartridgePath)\n ? fs.readFileSync(resolvedCartridgePath, \"utf8\")\n : \"\";\n return parsePico8Cartridge(content);\n } catch (error) {\n throw new Error(\n `Failed to read cartridge at ${cartridgePath}: ${(error as Error).message}`\n );\n }\n}\n\nfunction parsePico8Cartridge(content: string): PicoSections {\n const cartridgeSections: PicoSections = {\n lua: \"\",\n gff: \"\",\n gfx: \"\",\n music: \"\",\n map: \"\",\n sfx: \"\"\n };\n\n // Extract the contents of each section\n const regex = /__(\\w+)__\\n([\\s\\S]*?)(?=\\n__\\w+__\\n|\\n(\\n|$))/g;\n // /__(\\w+)__\\r?\\n([\\s\\S]*?)(?=__\\w+__|\\s*$)/g;\n\n let result;\n while ((result = regex.exec(content)) !== null) {\n const [, section, content] = result;\n cartridgeSections[section as keyof PicoSections] = content;\n }\n\n return cartridgeSections;\n}\n", "import fs from \"fs\";\nimport pngjs from \"pngjs\";\nimport {PICO8_PALETTE} from \"./constants.js\";\n\nconst spritesheetWidth = 128;\nconst spritesheetHeight = 128;\nconst hexBase = 16;\nconst pixelDataSize = 4; // red + green + blue + alpha\n\nconst toClosestColor = (pixels: Buffer) => (_: any, offset: number) => {\n const pixelOffset = offset * pixelDataSize;\n const pixel = {\n r: pixels[pixelOffset],\n g: pixels[pixelOffset + 1],\n b: pixels[pixelOffset + 2]\n };\n\n let minDistance = Number.MAX_VALUE;\n let closestPaletteColor = 0;\n PICO8_PALETTE.forEach(\n (color: {r: number; g: number; b: number}, i: number) => {\n const diff =\n (color.r - pixel.r) ** 2 +\n (color.g - pixel.g) ** 2 +\n (color.b - pixel.b) ** 2;\n\n if (diff < minDistance) {\n minDistance = diff;\n closestPaletteColor = i;\n }\n }\n );\n\n return closestPaletteColor.toString(hexBase);\n};\n\nexport function getSpritesheetFromImage(imagePath: string): Promise<string> {\n if (!imagePath) {\n throw new Error(\"Image path is missing\");\n }\n\n const stream = fs.createReadStream(imagePath).pipe(new pngjs.PNG());\n\n return new Promise((resolve) =>\n stream.on(\"parsed\", () => {\n if (\n stream.width !== spritesheetWidth ||\n stream.height !== spritesheetHeight\n ) {\n throw new Error(\"The spritesheet must be a 128x128 png image\");\n }\n\n const pixels = new Array(stream.width * stream.height)\n .fill(0)\n .map(toClosestColor(stream.data));\n\n const pixelsAsString = new Array(stream.height)\n .fill(0)\n .map((_: any, offset) =>\n // cut the strings so we get stacks of 128 characters\n pixels\n .slice(\n offset * spritesheetWidth,\n offset * spritesheetWidth + spritesheetWidth\n )\n .join(\"\")\n )\n .join(\"\\n\");\n\n resolve(pixelsAsString);\n })\n );\n}\n", "export const JSPICL_BANNER = `--[[\ngenerated with jspicl,\na javascript to pico-8 lua\ntranspiler.\n\nplease report any bugs at:\nhttps://github.com/jspicl/jspicl/issues\n]]--\n`;\n\nexport const PICO8_PALETTE = [\n {\n r: 0,\n g: 0,\n b: 0\n },\n {\n r: 29,\n g: 43,\n b: 83\n },\n {\n r: 126,\n g: 37,\n b: 83\n },\n {\n r: 0,\n g: 135,\n b: 81\n },\n {\n r: 171,\n g: 82,\n b: 54\n },\n {\n r: 95,\n g: 87,\n b: 79\n },\n {\n r: 194,\n g: 195,\n b: 199\n },\n {\n r: 255,\n g: 241,\n b: 232\n },\n {\n r: 255,\n g: 0,\n b: 77\n },\n {\n r: 255,\n g: 163,\n b: 0\n },\n {\n r: 255,\n g: 236,\n b: 39\n },\n {\n r: 0,\n g: 228,\n b: 54\n },\n {\n r: 41,\n g: 173,\n b: 255\n },\n {\n r: 131,\n g: 118,\n b: 156\n },\n {\n r: 255,\n g: 119,\n b: 168\n },\n {\n r: 255,\n g: 204,\n b: 170\n }\n];\n", "import {jspicl} from \"@jspicl/core\";\nimport {JSPICL_BANNER} from \"./constants.js\";\nimport type {Config} from \"./types.js\";\n\nexport function transpile(javascriptCode: string, config: Config) {\n const {includeBanner, polyfillTransform, jspicl: jspiclOptions = {}} = config;\n\n const jspiclBanner = (includeBanner && `${JSPICL_BANNER}`) || \"\";\n\n const {code, polyfills} = jspicl(javascriptCode, jspiclOptions);\n const polyfillOutput = polyfillTransform\n ? polyfillTransform(polyfills)\n : Object.values(polyfills).join(\"\\n\");\n\n const lua = `${jspiclBanner}${polyfillOutput}${code}`;\n\n return {\n lua,\n polyfillOutput\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["path", "process", "path", "path", "tokens", "stats", "path", "path", "fs", "fs", "path", "content", "fs", "fs", "fs", "process", "path"]
7
+ }
@@ -0,0 +1,25 @@
1
+ import type { Options as JspiclOptions } from "@jspicl/core/types";
2
+ export interface PicoSections extends Record<string, string> {
3
+ lua: string;
4
+ gff: string;
5
+ gfx: string;
6
+ music: string;
7
+ map: string;
8
+ sfx: string;
9
+ }
10
+ export interface Config {
11
+ spritesheetImagePath: string;
12
+ jsOutput: string;
13
+ picoPath?: string;
14
+ includeBanner?: boolean;
15
+ luaOutput?: string;
16
+ pipeOutputToConsole?: boolean;
17
+ polyfillTransform?: (polyfills: Record<string, string>) => string;
18
+ reloadOnSave?: boolean;
19
+ showStats?: boolean;
20
+ jspicl?: JspiclOptions;
21
+ }
22
+ export interface CommandLineOptions {
23
+ watch: boolean;
24
+ config: Config;
25
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@jspicl/cli",
3
+ "version": "4.0.0-alpha.0",
4
+ "description": "CLI for simplifying PICO-8 game development in JavaScript",
5
+ "author": "Agron Kabashi",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "jspicl": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ "./types": {
13
+ "types": "./dist/types.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "typecheck": "tsc --noEmit",
24
+ "build": "tsc --noEmit && tsc -p tsconfig.types.json && esbuild src/index.ts --bundle --platform=node --format=esm --packages=external --banner:js='#!/usr/bin/env node' --outfile=dist/cli.js --sourcemap",
25
+ "clean": "rm -rf dist",
26
+ "test": "echo \"No tests yet\"",
27
+ "prepublishOnly": "yarn clean && yarn test && yarn build"
28
+ },
29
+ "prettier": {
30
+ "arrowParens": "always",
31
+ "bracketSpacing": false,
32
+ "endOfLine": "lf",
33
+ "printWidth": 80,
34
+ "semi": true,
35
+ "singleQuote": false,
36
+ "tabWidth": 2,
37
+ "trailingComma": "none",
38
+ "useTabs": false
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/jspicl/jspicl"
43
+ },
44
+ "keywords": [
45
+ "jspicl",
46
+ "pico-8",
47
+ "cli",
48
+ "build-tool",
49
+ "javascript",
50
+ "lua",
51
+ "transpiler",
52
+ "game-development"
53
+ ],
54
+ "devDependencies": {
55
+ "@types/lodash.debounce": "^4.0.9",
56
+ "@types/node": "^22.0.0",
57
+ "@types/pngjs": "^6.0.5",
58
+ "@types/yargs": "^17.0.33",
59
+ "esbuild": "^0.25.0",
60
+ "typescript": "^5.7.0"
61
+ },
62
+ "dependencies": {
63
+ "@jspicl/core": "workspace:^",
64
+ "chokidar": "^5.0.0",
65
+ "lodash.debounce": "^4.0.8",
66
+ "mkdirp": "^3.0.1",
67
+ "pngjs": "^7.0.0",
68
+ "yargs": "^18.0.0"
69
+ },
70
+ "engines": {
71
+ "node": ">=22"
72
+ }
73
+ }