@triflare/fx 2.1.0-beta.4 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflare/fx",
3
- "version": "2.1.0-beta.4",
3
+ "version": "2.1.0",
4
4
  "description": "Code PenguinMod projects with text.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -51,8 +51,8 @@
51
51
  "commander": "^15.0.0",
52
52
  "cross-fetch": "^4.1.0",
53
53
  "decode-html": "2.0.0",
54
- "format-message": "6.2.1",
55
- "htmlparser2": "^3.10.0",
54
+ "format-message": "6.2.4",
55
+ "htmlparser2": "^12.0.0",
56
56
  "immutable": "^3.8.4",
57
57
  "js-yaml": "^5.4.3",
58
58
  "jszip": "^3.10.1",
package/product.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  # Identity
2
2
  name: "Triflare Fx"
3
3
  command: "fx"
4
- version: "2.1.0-beta.4"
4
+ version: "2.1.0"
5
5
  tagline: "Code PenguinMod projects with text."
6
6
  author: "Triflare"
7
7
  homepage: "https://github.com/triflare/fx"
@@ -34,7 +34,7 @@ export const vendorOutdatedCommand = {
34
34
  : result.status === "error"
35
35
  ? "danger"
36
36
  : "success";
37
- reporter.item(result.name, reporter.tone(tone, label), { width: 24 });
37
+ reporter.item(result.name, label, { width: 24, tone });
38
38
  if (result.error) reporter.warn(result.error);
39
39
  }
40
40
  if (outdated > 0)
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { styleText } from "node:util";
10
10
  import { brandText } from "../brand.js";
11
+ import { sanitizeTerminalText } from "../terminal.js";
11
12
  import { formatColumns } from "./format.js";
12
13
  import { createTheme } from "./theme.js";
13
14
 
@@ -60,7 +61,7 @@ export function createReporter({
60
61
  const glyph = mark(symbol);
61
62
  const stream = type === "success" ? stdout : stderr;
62
63
  const theme = themeFor(stream);
63
- const text = String(message);
64
+ const text = sanitizeTerminalText(message);
64
65
  const output = text
65
66
  .split("\n")
66
67
  .map((line, index) => {
@@ -102,48 +103,54 @@ export function createReporter({
102
103
 
103
104
  info(message) {
104
105
  if (!human) return;
105
- stdout.write(`${message}\n`);
106
+ stdout.write(`${sanitizeTerminalText(message)}\n`);
106
107
  },
107
108
 
108
109
  bullet(message) {
109
110
  if (!human) return;
110
111
  const theme = themeFor(stdout);
111
112
  const glyph = mark("bullet");
112
- stdout.write(` ${glyph ? `${theme.accent(glyph)} ` : ""}${message}\n`);
113
+ const text = sanitizeTerminalText(message);
114
+ stdout.write(` ${glyph ? `${theme.accent(glyph)} ` : ""}${text}\n`);
113
115
  },
114
116
 
115
117
  step(message) {
116
118
  if (!human) return;
117
119
  const theme = themeFor(stdout);
118
120
  const glyph = mark("progress");
119
- stdout.write(`${glyph ? `${theme.accent(glyph)} ` : ""}${message}\n`);
121
+ const text = sanitizeTerminalText(message);
122
+ stdout.write(`${glyph ? `${theme.accent(glyph)} ` : ""}${text}\n`);
120
123
  },
121
124
 
122
125
  heading(message) {
123
126
  if (!human) return;
124
- stdout.write(`${themeFor(stdout).heading(String(message))}\n`);
127
+ stdout.write(
128
+ `${themeFor(stdout).heading(sanitizeTerminalText(message))}\n`,
129
+ );
125
130
  },
126
131
 
127
132
  /** Aligned `label value` row used by command summaries. */
128
133
  detail(label, value, { width = 13 } = {}) {
129
134
  if (!human) return;
130
135
  const theme = themeFor(stdout);
131
- const text = String(value);
136
+ const text = sanitizeTerminalText(value);
132
137
  const rendered = text === "(none)" ? theme.muted(text) : theme.bold(text);
133
- stdout.write(
134
- ` ${theme.muted(String(label).padEnd(width))} ${rendered}\n`,
135
- );
138
+ const name = sanitizeTerminalText(label);
139
+ stdout.write(` ${theme.muted(name.padEnd(width))} ${rendered}\n`);
136
140
  },
137
141
 
138
142
  /** Aligned name + description row used by listings. */
139
- item(label, value = "", { width = 24, suffix = "" } = {}) {
143
+ item(label, value = "", { width = 24, suffix = "", tone } = {}) {
140
144
  if (!human) return;
141
145
  const theme = themeFor(stdout);
142
- const text = String(label);
146
+ const text = sanitizeTerminalText(label);
143
147
  const padded = text.padEnd(Math.max(width, text.length));
144
- const detail = value ? ` ${value}` : "";
145
- const note = suffix ? ` ${theme.muted(suffix)}` : "";
146
- stdout.write(` ${theme.accent(padded)}${detail}${note}\n`);
148
+ const detail = value ? ` ${sanitizeTerminalText(value)}` : "";
149
+ const note = suffix
150
+ ? ` ${theme.muted(sanitizeTerminalText(suffix))}`
151
+ : "";
152
+ const rendered = tone ? theme.tone(tone, detail) : detail;
153
+ stdout.write(` ${theme.accent(padded)}${rendered}${note}\n`);
147
154
  },
148
155
 
149
156
  /** Compact multi-column rows for long block and file listings. */
@@ -171,7 +178,9 @@ export function createReporter({
171
178
  /** Quiet, de-emphasized line for incidental notes. */
172
179
  note(message) {
173
180
  if (!human) return;
174
- stdout.write(`${themeFor(stdout).muted(message)}\n`);
181
+ stdout.write(
182
+ `${themeFor(stdout).muted(sanitizeTerminalText(message))}\n`,
183
+ );
175
184
  },
176
185
 
177
186
  warn(message) {
@@ -191,9 +200,8 @@ export function createReporter({
191
200
  hint(message) {
192
201
  if (!human) return;
193
202
  const theme = themeFor(stdout);
194
- stdout.write(
195
- ` ${theme.accent("↳")} ${theme.muted(`hint: ${message}`)}\n`,
196
- );
203
+ const text = sanitizeTerminalText(message);
204
+ stdout.write(` ${theme.accent("↳")} ${theme.muted(`hint: ${text}`)}\n`);
197
205
  },
198
206
 
199
207
  /** One JSON document, used for the command result envelope. */
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Terminal output safety.
3
+ *
4
+ * Names, paths and source text can come from a repository the user did not
5
+ * write, so untrusted text is rendered with visible `\xNN` escapes instead of
6
+ * letting the terminal interpret control characters and escape sequences.
7
+ */
8
+
9
+ /**
10
+ * Replace terminal control characters with visible `\xNN` escapes.
11
+ *
12
+ * Tabs and newlines are kept because callers print multi-line blocks.
13
+ * @param {unknown} value
14
+ */
15
+ export function sanitizeTerminalText(value) {
16
+ let clean = "";
17
+ for (const char of String(value)) {
18
+ const code = char.codePointAt(0);
19
+ const printable =
20
+ code === 0x09 ||
21
+ code === 0x0a ||
22
+ (code >= 0x20 && code <= 0x7e) ||
23
+ code >= 0xa0;
24
+ clean += printable ? char : `\\x${code.toString(16).padStart(2, "0")}`;
25
+ }
26
+ return clean;
27
+ }
package/src/vendor.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  readExtensionDataUri,
24
24
  resolveSource,
25
25
  } from "./extensions.js";
26
+ import { sanitizeTerminalText } from "./terminal.js";
26
27
 
27
28
  export const LOCK_FILENAME = "fx.lock";
28
29
  export const LOCK_VERSION = 1;
@@ -1146,13 +1147,14 @@ export async function loadVendorExtensions({
1146
1147
 
1147
1148
  /** A compact line diff, enough to review an extension or config change. */
1148
1149
  export function formatDiff(before, after, filename = "") {
1150
+ const header = sanitizeTerminalText(filename);
1149
1151
  const a = String(before).split(/\r?\n/);
1150
1152
  const b = String(after).split(/\r?\n/);
1151
1153
  const rows = a.length;
1152
1154
  const columns = b.length;
1153
1155
  // Keep very large extensions from allocating an enormous matrix.
1154
1156
  if (rows * columns > 4_000_000) {
1155
- return `--- ${filename}\n+++ ${filename}\n@@ source changed @@\n`;
1157
+ return `--- ${header}\n+++ ${header}\n@@ source changed @@\n`;
1156
1158
  }
1157
1159
  const table = Array.from(
1158
1160
  { length: rows + 1 },
@@ -1166,24 +1168,24 @@ export function formatDiff(before, after, filename = "") {
1166
1168
  : Math.max(table[i + 1][j], table[i][j + 1]);
1167
1169
  }
1168
1170
  }
1169
- const lines = [`--- ${filename}`, `+++ ${filename}`];
1171
+ const lines = [`--- ${header}`, `+++ ${header}`];
1170
1172
  let i = 0;
1171
1173
  let j = 0;
1172
1174
  while (i < rows && j < columns) {
1173
1175
  if (a[i] === b[j]) {
1174
- lines.push(` ${a[i]}`);
1176
+ lines.push(` ${sanitizeTerminalText(a[i])}`);
1175
1177
  i += 1;
1176
1178
  j += 1;
1177
1179
  } else if (table[i + 1][j] >= table[i][j + 1]) {
1178
- lines.push(`-${a[i]}`);
1180
+ lines.push(`-${sanitizeTerminalText(a[i])}`);
1179
1181
  i += 1;
1180
1182
  } else {
1181
- lines.push(`+${b[j]}`);
1183
+ lines.push(`+${sanitizeTerminalText(b[j])}`);
1182
1184
  j += 1;
1183
1185
  }
1184
1186
  }
1185
- while (i < rows) lines.push(`-${a[i++]}`);
1186
- while (j < columns) lines.push(`+${b[j++]}`);
1187
+ while (i < rows) lines.push(`-${sanitizeTerminalText(a[i++])}`);
1188
+ while (j < columns) lines.push(`+${sanitizeTerminalText(b[j++])}`);
1187
1189
  return `${lines.join("\n")}\n`;
1188
1190
  }
1189
1191
 
@@ -2,5 +2,5 @@
2
2
  "name": "@penguinmod/packager",
3
3
  "version": "1.9.2",
4
4
  "commit": "53f868b85ec21d2edab9ce00fd2d709da93da6d6",
5
- "builtAt": "2026-11-06T05:46:49.113Z"
5
+ "builtAt": "2026-11-11T19:06:00.040Z"
6
6
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "scratch-vm",
3
3
  "version": "0.2.0",
4
- "commit": "75ef6994e55270b9c2a2f3cb2edf6a5bf31cdf2a",
4
+ "commit": "50fddfed34f4eed9eef28ef32bc1022397d29f46",
5
5
  "inputHash": "9557b90be391ad2f1deaccfd034fddf0a9591c354da50b13e4cf142a8e2654fc",
6
- "builtAt": "2026-11-07T18:15:07.603Z"
6
+ "builtAt": "2026-11-11T19:08:05.605Z"
7
7
  }
@@ -2,5 +2,5 @@ This directory contains the PenguinMod VM.
2
2
 
3
3
  Upstream: https://github.com/PenguinMod/PenguinMod-Vm
4
4
  Version: 0.2.0
5
- Commit: 75ef6994e55270b9c2a2f3cb2edf6a5bf31cdf2a
5
+ Commit: 50fddfed34f4eed9eef28ef32bc1022397d29f46
6
6
  License: BSD-3-Clause (see LICENSE)