@corva/ui 3.77.0-11 → 3.77.0-13

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/MCP_README.md CHANGED
@@ -215,12 +215,15 @@ This creates `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor), and `.codex
215
215
  Restart your IDE or reload MCP servers afterward.
216
216
 
217
217
  > If any of these files already exist, the setup command **extends** them — your existing MCP server entries are
218
- > preserved, and only the `corva-ui` entry (`corva_ui` in the Codex TOML) is added or updated. "Updated" means replaced
219
- > with the stock entry: anything you added to it by hand, such as an `env` block with
220
- > `CORVA_UI_MCP_TELEMETRY_DISABLED`, is dropped and has to be re-added. The files are re-serialized on write, so
221
- > comments and hand formatting in the TOML do not survive either. A file that fails to parse is overwritten from
222
- > scratch (the command warns when it does that). On native Windows the generated entry uses plain `npx`; if your client
223
- > cannot spawn it, apply the `cmd /c` form from the Native Windows note under [Local Setup](#local-setup-recommended).
218
+ > preserved, and the `corva-ui` entry (`corva_ui` in the Codex TOML) is added only when it is missing. An entry that
219
+ > already exists is kept exactly as it is, so anything you added by hand, such as an `env` block with
220
+ > `CORVA_UI_MCP_TELEMETRY_DISABLED`, a `startup_timeout_sec`, or the Windows `cmd /c` form, survives a re-run after an
221
+ > upgrade; the command prints `(kept)` for that file. Pass `--force` to replace the entry with the stock one. A write
222
+ > re-serializes the whole file, so when the command adds or replaces an entry in the Codex TOML, comments and hand
223
+ > formatting are lost (the command says so in its output). A file that fails to parse is never written: the command
224
+ > prints the parse error and the path, and exits with a non-zero code. On native Windows the generated entry uses plain
225
+ > `npx`; if your client cannot spawn it, apply the `cmd /c` form from the Native Windows note under
226
+ > [Local Setup](#local-setup-recommended).
224
227
 
225
228
  > **Monorepo:** Run the setup command from the monorepo root, not from individual
226
229
  > `apps/*` directories. See [Monorepo Setup](#monorepo-setup) for details.
@@ -248,7 +248,7 @@ const validateDsn = dsn => {
248
248
 
249
249
  const MCP_SERVER_VERSION = '1.5.0';
250
250
 
251
- var version = "3.77.0-11";
251
+ var version = "3.77.0-13";
252
252
 
253
253
  const CORVA_UI_VERSION = version;
254
254
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
3
3
  import { resolve, dirname } from 'path';
4
4
 
5
5
  /*!
@@ -1019,12 +1019,7 @@ function stringify(obj, {
1019
1019
  return str;
1020
1020
  }
1021
1021
 
1022
- /**
1023
- * corva-ui-mcp-setup CLI
1024
- *
1025
- * Creates/updates MCP config files so AI coding agents can discover the @corva/ui MCP server.
1026
- * Run from any project root: npx -p @corva/ui corva-ui-mcp-setup
1027
- */
1022
+ /** The stock entry the CLI writes. Anything a developer adds by hand is not part of it. */
1028
1023
  const SERVER_CONFIG = {
1029
1024
  command: 'npx',
1030
1025
  args: ['-p', '@corva/ui', 'corva-ui-mcp']
@@ -1053,38 +1048,68 @@ const TARGETS = [{
1053
1048
  path: '.codex/config.toml',
1054
1049
  format: TOML_FORMAT
1055
1050
  }];
1056
- const ACTION_SYMBOLS = {
1057
- created: '+',
1058
- updated: '~',
1059
- skipped: '='
1060
- };
1061
- const isRecord = value => typeof value === 'object' && value !== null;
1051
+
1052
+ const isRecord = value => typeof value === 'object' && value !== null && !Array.isArray(value);
1053
+ const errorMessage = error => error instanceof Error ? error.message : String(error);
1054
+
1055
+ /**
1056
+ * Adds the stock `corva-ui` entry to one config file.
1057
+ *
1058
+ * An entry that already exists is left as it is, whatever it contains: developers add `env`,
1059
+ * timeouts and the Windows `cmd /c` form by hand, and a re-run after an upgrade must not drop
1060
+ * them. Only `force` replaces it. A file that does not parse is never written.
1061
+ */
1062
1062
  const writeConfig = ({
1063
- path: filePath,
1064
- format
1063
+ target,
1064
+ force = false,
1065
+ cwd = process.cwd()
1065
1066
  }) => {
1066
- const absolutePath = resolve(filePath);
1067
- mkdirSync(dirname(absolutePath), {
1068
- recursive: true
1069
- });
1067
+ const {
1068
+ path: filePath,
1069
+ format
1070
+ } = target;
1071
+ const absolutePath = resolve(cwd, filePath);
1072
+ const fileExists = existsSync(absolutePath);
1070
1073
  let existing = {};
1071
- let action = 'created';
1072
- if (existsSync(absolutePath)) {
1073
- action = 'updated';
1074
+ if (fileExists) {
1074
1075
  const raw = readFileSync(absolutePath, 'utf-8');
1076
+ let parsed;
1075
1077
  try {
1076
- existing = format.parse(raw);
1077
- } catch {
1078
- console.warn(` Warning: ${filePath} contained invalid ${format.formatName} and will be overwritten.`);
1079
- existing = {};
1080
- action = 'created';
1078
+ parsed = format.parse(raw);
1079
+ } catch (error) {
1080
+ return {
1081
+ file: filePath,
1082
+ action: 'failed',
1083
+ note: `invalid ${format.formatName}, file left unchanged: ${errorMessage(error)}`
1084
+ };
1085
+ }
1086
+ if (!isRecord(parsed)) {
1087
+ return {
1088
+ file: filePath,
1089
+ action: 'failed',
1090
+ note: `invalid ${format.formatName}, file left unchanged: top level is not an object`
1091
+ };
1081
1092
  }
1093
+ existing = parsed;
1082
1094
  }
1083
- const servers = isRecord(existing[format.serversKey]) ? existing[format.serversKey] : {};
1084
- if (JSON.stringify(servers[format.serverName]) === JSON.stringify(SERVER_CONFIG)) {
1095
+ const serversValue = existing[format.serversKey];
1096
+
1097
+ // A servers block that is not a plain object (an array, a string, null) cannot hold the entry.
1098
+ // Writing over it would drop whatever it held, so refuse instead of guessing.
1099
+ if (serversValue !== undefined && !isRecord(serversValue)) {
1100
+ return {
1101
+ file: filePath,
1102
+ action: 'failed',
1103
+ note: `"${format.serversKey}" is not an object, file left unchanged`
1104
+ };
1105
+ }
1106
+ const servers = serversValue ?? {};
1107
+ const hasEntry = Object.hasOwn(servers, format.serverName);
1108
+ if (hasEntry && !force) {
1085
1109
  return {
1086
1110
  file: filePath,
1087
- action: 'skipped'
1111
+ action: 'kept',
1112
+ note: `entry "${format.serverName}" exists; pass --force to replace it with the stock entry`
1088
1113
  };
1089
1114
  }
1090
1115
  const merged = {
@@ -1094,30 +1119,90 @@ const writeConfig = ({
1094
1119
  [format.serverName]: SERVER_CONFIG
1095
1120
  }
1096
1121
  };
1122
+ mkdirSync(dirname(absolutePath), {
1123
+ recursive: true
1124
+ });
1097
1125
  writeFileSync(absolutePath, `${format.serialize(merged)}\n`);
1126
+ let action = 'created';
1127
+ if (hasEntry) {
1128
+ action = 'replaced';
1129
+ } else if (fileExists) {
1130
+ action = 'added';
1131
+ }
1132
+ const note = fileExists && format.formatName === 'TOML' ? 'the file was rewritten; TOML comments and hand formatting are removed' : undefined;
1098
1133
  return {
1099
1134
  file: filePath,
1100
- action
1135
+ action,
1136
+ note
1101
1137
  };
1102
1138
  };
1103
- const main = () => {
1104
- console.log('Setting up @corva/ui MCP server configs...\n');
1105
- const results = [];
1106
- for (const target of TARGETS) {
1139
+
1140
+ const ACTION_SYMBOLS = {
1141
+ created: '+',
1142
+ added: '+',
1143
+ replaced: '~',
1144
+ kept: '=',
1145
+ failed: '!'
1146
+ };
1147
+ const formatResult = ({
1148
+ file,
1149
+ action,
1150
+ note
1151
+ }) => ` ${ACTION_SYMBOLS[action]} ${file} (${action})${note ? `: ${note}` : ''}`;
1152
+
1153
+ /** Runs the setup over every target and collects the report. Never throws for a single target. */
1154
+ const runSetup = ({
1155
+ force = false,
1156
+ cwd = process.cwd()
1157
+ } = {}) => {
1158
+ const results = TARGETS.map(target => {
1107
1159
  try {
1108
- results.push(writeConfig(target));
1160
+ return writeConfig({
1161
+ target,
1162
+ force,
1163
+ cwd
1164
+ });
1109
1165
  } catch (error) {
1110
1166
  const message = error instanceof Error ? error.message : String(error);
1111
- console.error(` Error writing ${target.path}: ${message}`);
1167
+ return {
1168
+ file: target.path,
1169
+ action: 'failed',
1170
+ note: `write failed: ${message}`
1171
+ };
1112
1172
  }
1113
- }
1114
- console.log('');
1115
- for (const {
1116
- file,
1173
+ });
1174
+ const failed = results.filter(({
1117
1175
  action
1118
- } of results) {
1119
- console.log(` ${ACTION_SYMBOLS[action]} ${file} (${action})`);
1176
+ }) => action === 'failed');
1177
+ const lines = ['Setting up @corva/ui MCP server configs...', '', ...results.map(formatResult), '', failed.length > 0 ? `${failed.length} of ${results.length} config files were not written. Fix the errors above and run the command again.` : 'Done! The corva-ui MCP server is now configured for this project.'];
1178
+ return {
1179
+ lines,
1180
+ exitCode: failed.length > 0 ? 1 : 0,
1181
+ results
1182
+ };
1183
+ };
1184
+
1185
+ /**
1186
+ * corva-ui-mcp-setup CLI
1187
+ *
1188
+ * Adds the @corva/ui MCP server entry to the MCP config files of the current project, so AI
1189
+ * coding agents can discover it. Run from any project root: npx -p @corva/ui corva-ui-mcp-setup
1190
+ *
1191
+ * An entry that already exists is kept as it is; `--force` replaces it with the stock entry.
1192
+ * The logic lives in src/setup so the Jest suite can cover it; this file only parses argv,
1193
+ * prints the report and sets the exit code.
1194
+ */
1195
+ const main = () => {
1196
+ const force = process.argv.includes('--force');
1197
+ const {
1198
+ lines,
1199
+ exitCode
1200
+ } = runSetup({
1201
+ force
1202
+ });
1203
+ for (const line of lines) {
1204
+ console.log(line);
1120
1205
  }
1121
- console.log('\nDone! The corva-ui MCP server is now configured for this project.');
1206
+ process.exitCode = exitCode;
1122
1207
  };
1123
1208
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corva/ui",
3
- "version": "3.77.0-11",
3
+ "version": "3.77.0-13",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Shared components/utils for Corva ui projects",
6
6
  "repository": {