Files
route-commerce/scripts/fix-button-has-type.js
Nora 0ac4beaaa8 fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call
- Convert getAdminUser() → requireAuth() across 73 admin action files
- Add getSession() to public/wholesale server actions
- Fix multi-line return type corruption from earlier auto-fixers
- Move FedEx token cache to non-'use server' module
- Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD,
  EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS
- Update Stripe API version 2026-05-27 → 2026-06-24
- Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient
- Fix 51 TypeScript errors (return type corruption, missing imports)
2026-06-25 23:49:37 -06:00

129 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* fix-button-has-type.js
*
* Bulk-fix the react-doctor/button-has-type warning by adding
* `type="button"` to every `<button>` JSX element that doesn't
* already have a `type` attribute. Skips elements that already
* declare `type="submit"`, `type="reset"`, `type="button"`, or
* the corresponding JSX expressions.
*
* Why default to "button": A bare `<button>` in HTML defaults to
* `type="submit"`, which accidentally submits the nearest form.
* The vast majority of buttons in admin UIs are click handlers
* (not form submitters), so "button" is the safer default. When
* a button IS a form submit (e.g. inside `<form onSubmit>`), the
* form handler typically uses its own submit semantics, but
* authors can always override by adding `type="submit"` explicitly.
*
* Idempotent: re-running this script is a no-op once applied.
*/
const fs = require("node:fs");
const path = require("node:path");
const { execSync } = require("node:child_process");
// Find all .tsx and .jsx files in src/ that are tracked by git
function listFiles() {
const out = execSync(
"git ls-files 'src/**/*.tsx' 'src/**/*.jsx' 'src/**/*.ts'",
{ encoding: "utf8" }
);
return out.split("\n").filter(Boolean);
}
const FILES = listFiles();
let totalFixed = 0;
let totalSkipped = 0;
// Match <button ... > opening tag (or self-closing) that doesn't have type=
// Supports multi-line tags.
// We do this in a stateful loop to properly skip <button> tags that DO have type=
function fixFile(file) {
const original = fs.readFileSync(file, "utf8");
let out = "";
let i = 0;
let changed = false;
while (i < original.length) {
// Look for "<button" (case-sensitive, lowercase to match JSX conventions)
const buttonIdx = original.indexOf("<button", i);
if (buttonIdx === -1) {
out += original.slice(i);
break;
}
// Copy everything up to the match
out += original.slice(i, buttonIdx);
i = buttonIdx;
// Find the closing > of this tag (handle quoted attrs and > inside strings)
let j = buttonIdx + "<button".length;
let inSingle = false;
let inDouble = false;
let inBrace = 0;
let tagEnd = -1;
let isSelfClosing = false;
while (j < original.length) {
const c = original[j];
if (inSingle) {
if (c === "'" && original[j - 1] !== "\\") inSingle = false;
} else if (inDouble) {
if (c === '"' && original[j - 1] !== "\\") inDouble = false;
} else if (inBrace > 0) {
if (c === "{") inBrace++;
else if (c === "}") inBrace--;
} else {
if (c === "'") inSingle = true;
else if (c === '"') inDouble = true;
else if (c === "{") inBrace++;
else if (c === ">") {
tagEnd = j;
isSelfClosing = original[j - 1] === "/";
break;
}
}
j++;
}
if (tagEnd === -1) {
// Unterminated tag — leave as-is
out += original.slice(i);
break;
}
const tagContent = original.slice(buttonIdx, tagEnd + 1);
// Already has type= ? skip
if (/\stype\s*=/.test(tagContent)) {
out += tagContent;
i = tagEnd + 1;
totalSkipped++;
continue;
}
// Insert type="button" right after "<button"
const insertAt = buttonIdx + "<button".length;
const newTag =
original.slice(buttonIdx, insertAt) +
' type="button"' +
original.slice(insertAt, tagEnd + 1);
out += newTag;
i = tagEnd + 1;
changed = true;
totalFixed++;
}
if (changed) {
fs.writeFileSync(file, out);
}
}
for (const f of FILES) {
try {
fixFile(f);
} catch (err) {
console.error(`error processing ${f}: ${err.message}`);
}
}
console.log(`Fixed ${totalFixed} <button> elements; skipped ${totalSkipped} already-typed.`);