0ac4beaaa8
- 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)
129 lines
3.7 KiB
JavaScript
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.`);
|