pi-roundtable 0.6.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.6.1] - 2026-10-02
9
+
10
+ ### Fixed
11
+
12
+ - `add plugin` and `add package` kept a one-line `plugins` list on one line however long it grew, so a project's `biome check` failed once the line passed 80 columns. A list that would pass 80 columns is now put one element a line, as the formatter writes it; a list already over several lines keeps its layout, and one holding a comment stays as it was.
13
+
8
14
  ## [0.6.0] - 2026-10-02
9
15
 
10
16
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A plugin-driven Pi agent server for Discord",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -115,6 +115,50 @@ function pluginList(object: Node): Node | undefined {
115
115
  interface Insertion {
116
116
  at: number;
117
117
  text: string;
118
+ /** How many characters from `at` the text replaces; none when it only inserts. */
119
+ replaces?: number;
120
+ }
121
+
122
+ /** The width the formatter gives a line: a tab counts as its default indent width of 2. */
123
+ const LINE_WIDTH = 80;
124
+ const width = (line: string): number => line.replaceAll("\t", " ").length;
125
+
126
+ /**
127
+ * The one-line list with `ident` added, put one element a line when the line it sits on would
128
+ * pass the formatter's width, as the project's formatter would; undefined when the list already
129
+ * spans lines, holds anything besides its elements and commas, or still fits.
130
+ */
131
+ function expandedList(
132
+ source: string,
133
+ list: Node,
134
+ elements: readonly Node[],
135
+ ident: string,
136
+ ): Insertion | undefined {
137
+ const inner = source.slice(list.start + 1, list.end - 1);
138
+ if (inner.includes("\n")) return undefined;
139
+ const items = elements.map((element) =>
140
+ source.slice(element.start, element.end),
141
+ );
142
+ // The text around and between the elements, which must be only commas and spaces.
143
+ const edges = [
144
+ list.start + 1,
145
+ ...elements.flatMap((element) => [element.start, element.end]),
146
+ list.end - 1,
147
+ ];
148
+ let gaps = "";
149
+ for (let index = 0; index < edges.length; index += 2)
150
+ gaps += source.slice(edges[index], edges[index + 1]);
151
+ if (!/^[\s,]*$/.test(gaps)) return undefined;
152
+ const lineStart = source.lastIndexOf("\n", list.start) + 1;
153
+ const lineEnd = source.indexOf("\n", list.end);
154
+ const line = `${source.slice(lineStart, list.start)}[${[...items, ident].join(", ")}]${source.slice(list.end, lineEnd === -1 ? undefined : lineEnd)}`;
155
+ if (width(line) <= LINE_WIDTH) return undefined;
156
+ const indent = /^[ \t]*/.exec(source.slice(lineStart, list.start))?.[0] ?? "";
157
+ return {
158
+ at: list.start,
159
+ replaces: list.end - list.start,
160
+ text: `[\n${[...items, ident].map((item) => `${indent}\t${item},\n`).join("")}${indent}]`,
161
+ };
118
162
  }
119
163
 
120
164
  /** The text to add so `ident` joins the list, in the list's own layout. */
@@ -122,6 +166,8 @@ function listInsertion(source: string, list: Node, ident: string): Insertion {
122
166
  const elements = (list.elements as (Node | null)[]).filter(
123
167
  (element): element is Node => element !== null,
124
168
  );
169
+ const expanded = expandedList(source, list, elements, ident);
170
+ if (expanded) return expanded;
125
171
  const last = elements.at(-1);
126
172
  if (!last) return { at: list.start + 1, text: ident };
127
173
  const between = source.slice(last.end, list.end - 1);
@@ -161,8 +207,9 @@ function importInsertion(
161
207
 
162
208
  /**
163
209
  * Adds the plugin's import line and lists it in the `plugins` array of the default export, in the
164
- * file's own layout. It refuses, changing nothing, a file that does not parse, a default export
165
- * with no `plugins` list it can find, and a name already bound.
210
+ * file's own layout; a one-line list that would pass 80 columns is put one element a line. It
211
+ * refuses, changing nothing, a file that does not parse, a default export with no `plugins` list
212
+ * it can find, and a name already bound.
166
213
  */
167
214
  export function addPluginToConfig(
168
215
  source: string,
@@ -192,7 +239,10 @@ export function addPluginToConfig(
192
239
  ].sort((a, b) => b.at - a.at);
193
240
  let edited = source;
194
241
  for (const edit of edits)
195
- edited = edited.slice(0, edit.at) + edit.text + edited.slice(edit.at);
242
+ edited =
243
+ edited.slice(0, edit.at) +
244
+ edit.text +
245
+ edited.slice(edit.at + (edit.replaces ?? 0));
196
246
  // A result that does not parse is a bug here; never write it.
197
247
  parseModule(edited, file);
198
248
  return edited;