From 03d144198165b92008b3a5e1584088b8b245fc24 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:52:44 +0900 Subject: [PATCH 1/3] audit(toolify): read-only indexability check for the exact 12 Google inspection URLs --- scripts/toolify-live-index-gate.mjs | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts/toolify-live-index-gate.mjs diff --git a/scripts/toolify-live-index-gate.mjs b/scripts/toolify-live-index-gate.mjs new file mode 100644 index 00000000..636098ea --- /dev/null +++ b/scripts/toolify-live-index-gate.mjs @@ -0,0 +1,74 @@ +#!/usr/bin/env node +/** + * Read-only exact live indexability diagnosis for the fixed 12 URL cohort + * Google URL Inspection inspected on 2026-10-26. + * It NEVER submits URLs to Google, updates robots, or touches credentials. + * Google cached 4 noindex statuses from July; the source's October policy + * intends all 12 to be indexable. Verify what is really PUBLIC today. + */ +export const targets = [ + ["en", "workdays-calculator", "core"], + ["en", "date-calculator", "core"], + ["en", "pressure-converter", "core"], + ["en", "unit-price-calculator", "core"], + ["ja", "workdays-calculator", "core"], + ["ja", "pressure-converter", "core"], + ["th", "date-calculator", "exception"], + ["ru", "pressure-converter", "exception"], + ["tr", "grade-calculator", "exception"], + ["ar", "length-converter", "exception"], + ["fr", "speed-converter", "exception"], + ["es", "due-date-calculator", "exception"], +]; +export const base="https://toolify365.com"; +export const urlFor=([loc,slug])=>base+"/"+loc+"/tools/"+slug; +export function attr(tag,name){ + const m=tag.match(new RegExp("\\b"+name+"\\s*=\\s*([\"'])(.*?)\\1","i")); + if(m)return m[2]; + const loose=tag.match(new RegExp("\\b"+name+"\\s*=\\s*([^\\s>]+)","i")); + return loose?.[1]??null; +} +export function inspectHTML(html,headers,url,status,finalUrl){ + const head=html.split(/<\/head>/i)[0]??""; + const metaTags=[...head.matchAll(/]*>/gi)].map(x=>x[0]); + const robotsMeta=metaTags.filter(t=>["robots","googlebot"].includes((attr(t,"name")??"").toLowerCase())) + .map(t=>attr(t,"content")??"").join(","); + const xRobots=headers?.get?.("x-robots-tag")??null; + const tags=[...head.matchAll(/]*>/gi)].map(x=>x[0]); + const canonicals=tags.filter(t=>(attr(t,"rel")??"").toLowerCase().split(/\s+/).includes("canonical")) + .map(t=>attr(t,"href")); + const noindex=/(^|[\s,;])noindex($|[\s,;])/i.test(robotsMeta+","+String(xRobots??"")); + const canonical=canonicals[0]??null; + return {url,httpStatus:status,finalUrl,canonical,canonicalCount:canonicals.length, + robotsMeta,xRobots,noindex,pass:status===200&&finalUrl===url&&canonicals.length===1&& + canonical===url&&!noindex&&/]*>[^<]+<\/title>/i.test(head)}; +} +export async function main(){ + const results=await Promise.all(targets.map(async t=>{ + const url=urlFor(t); + try { + const r=await fetch(url,{redirect:"follow", + headers:{"Cache-Control":"no-cache","Pragma":"no-cache","User-Agent":"ToolifyIndexSmoke/1.0"}, + signal:AbortSignal.timeout(20000)}); + return {...inspectHTML(await r.text(),r.headers,url,r.status,r.url),locale:t[0],slug:t[1],indexIntent:t[2]}; + }catch(e){return {url,locale:t[0],slug:t[1],indexIntent:t[2],pass:false,error:String(e.message??e)};} + })); + const bad=results.filter(r=>!r.pass); + const summary={checked:results.length,liveIndexable:results.length-bad.length,problems:bad.length, + note:"Source intended indexability and live HTML are distinct from Google's cached index verdict. All 12 URL Inspection rows are a selected test cohort, not the whole site.", + checkedAtUtc:new Date().toISOString(),results}; + console.log(JSON.stringify(summary,null,2)); + if (process.env.GITHUB_STEP_SUMMARY) { + const {appendFileSync}=await import("node:fs"); + appendFileSync(process.env.GITHUB_STEP_SUMMARY, + "## Toolify 12URL live indexability\n\n"+ + "- Pass: "+(results.length-bad.length)+"/12\n"+ + "- Live failures: "+bad.length+"\n\n"+ + "| URL | HTTP | canonical | noindex | Result |\n|---|---|---|---|---|\n"+ + results.map(x=>"| "+x.url+" | "+(x.httpStatus??"ERROR")+" | "+(x.canonical??"missing")+ + " | "+(x.noindex??"unknown")+" | "+(x.pass?"PASS":"FAIL")+" |").join("\n")+"\n"); + } + if(bad.length)process.exitCode=1; +} +if(process.argv[1]?.endsWith("toolify-live-index-gate.mjs")) + main().catch(e=>{console.error(e);process.exitCode=2}); From 2bfd71fddc3eec2acb6a8f272aeb6d20a6bfb097 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:52:49 +0900 Subject: [PATCH 2/3] audit(toolify): read-only indexability check for the exact 12 Google inspection URLs --- .../tests/toolify-live-index-gate.test.mjs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts/tests/toolify-live-index-gate.test.mjs diff --git a/scripts/tests/toolify-live-index-gate.test.mjs b/scripts/tests/toolify-live-index-gate.test.mjs new file mode 100644 index 00000000..4c9113c0 --- /dev/null +++ b/scripts/tests/toolify-live-index-gate.test.mjs @@ -0,0 +1,38 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import {readFileSync} from "node:fs"; +import {fileURLToPath} from "node:url"; +import {targets,urlFor,inspectHTML} from "../toolify-live-index-gate.mjs"; + +const source=readFileSync(fileURLToPath(new URL("../../site/src/lib/tools/registry.ts",import.meta.url)),"utf8"); +function sourceBlock(start,end) { + const a=source.indexOf(start),b=source.indexOf(end,a+start.length); + assert.ok(a>=0&&b>a,"registry source declarations absent"); + return source.slice(a,b); +} +test("12 nonduplicated source-indexable official-inspection URLs",()=>{ + assert.equal(targets.length,12); + assert.equal(new Set(targets.map(urlFor)).size,12); + const core=sourceBlock("export const CORE_TOOLS_BY_DEMAND =","] as const;"); + const exceptions=sourceBlock("export const INDEX_EXCEPTIONS:", "\n};"); + for(const [loc,slug,type] of targets) { + if(type==="core") assert.ok(core.includes('"'+slug+'"'),loc+"/"+slug+" missing core"); + else assert.ok(new RegExp("\\b"+loc+":\\s*\\[[^\\]]*\\\""+slug+"\\\"").test(exceptions), + loc+"/"+slug+" missing locale exception"); + } +}); +test("existing old Google noindex status must not override valid current HTTP+canonical",()=>{ + const url=urlFor(targets[6]); + const html='Date CalculatorTool'; + assert.equal(inspectHTML(html,{get:()=>null},url,200,url).pass,true); +}); +test("reject noindex, missing canonical, bad HTTP and redirect",()=>{ + const url=urlFor(targets[0]); + const html='Workdays'; + assert.ok(inspectHTML(html,{get:()=>null},url,200,url).pass); + assert.ok(!inspectHTML(html.replace('',''),{get:()=>null},url,200,url).pass); + assert.ok(!inspectHTML(html,{get:()=> "noindex"},url,200,url).pass); + assert.ok(!inspectHTML(html,{get:()=>null},url,404,url).pass); + assert.ok(!inspectHTML(html,{get:()=>null},url,200,url+"/").pass); +}); From 2ab3237c58f288cca11ce30c0aea1b1f88b9ad1c Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:52:52 +0900 Subject: [PATCH 3/3] audit(toolify): read-only indexability check for the exact 12 Google inspection URLs --- .github/workflows/toolify-live-index.yml | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/toolify-live-index.yml diff --git a/.github/workflows/toolify-live-index.yml b/.github/workflows/toolify-live-index.yml new file mode 100644 index 00000000..81f9a69e --- /dev/null +++ b/.github/workflows/toolify-live-index.yml @@ -0,0 +1,38 @@ +name: Toolify live indexability recovery (12 URLs) +on: + pull_request: + paths: + - 'scripts/toolify-live-index-gate.mjs' + - 'scripts/tests/toolify-live-index-gate.test.mjs' + - '.github/workflows/toolify-live-index.yml' + push: + branches: [main] + paths: + - 'scripts/toolify-live-index-gate.mjs' + - '.github/workflows/toolify-live-index.yml' + workflow_dispatch: +jobs: + source: + runs-on: ubuntu-latest + timeout-minutes: 4 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - run: node --test scripts/tests/toolify-live-index-gate.test.mjs + live: + needs: source + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - run: node scripts/toolify-live-index-gate.mjs