初始化项目版本

This commit is contained in:
Axhub Make
2026-07-29 16:04:39 +08:00
commit 4305a1082b
2629 changed files with 760590 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
# Stitch to React Components Skill
## Install
```bash
npx skills add google-labs-code/stitch-skills --skill react:components --global
```
## Example Prompt
```text
Convert my Landing Page screen in my Podcast Stitch Project to a React component system.
```
## Skill Structure
This repository follows the **Agent Skills** open standard. Each skill is self-contained with its own logic, validation scripts, and design tokens.
```text
skills/react-components/
├── SKILL.md — Core instructions & workflow
├── package.json — Validator dependencies
├── scripts/ — Networking & AST validation
├── resources/ — Style guides & API references
└── examples/ — Gold-standard code samples
```
## How it Works
When activated, the agent follows a high-fidelity engineering pipeline:
1. **Retrieval**: Uses a system-level `curl` script to bypass TLS/SNI issues on Google Cloud Storage.
2. **Mapping**: Cross-references Stitch metadata with the local `style-guide.json` to ensure token consistency.
3. **Generation**: Scaffolds components using a strict Atomic Design pattern.
4. **Validation**: Runs an automated AST check using `@swc/core` to prevent hardcoded hex values or missing interfaces.
5. **Audit**: Performs a final self-correction check against a 20-point architecture checklist.

View File

@@ -0,0 +1,110 @@
---
name: stitch::react-components
description: >-
Converts Stitch designs into modular Vite and React components, or syncs/updates
existing React components to align with the latest Stitch designs, using system-level
networking and AST-based validation.
allowed-tools:
- "stitch*:*"
- "Bash"
- "Read"
- "Write"
- "web_fetch"
---
# Stitch to React Components
You are a frontend engineer focused on transforming designs into clean React code or syncing/updating existing React components to align with the latest Stitch designs. You follow a modular approach and use automated tools to ensure code quality.
> **CRITICAL: Every step in this skill is MANDATORY. Do NOT skip any step or take shortcuts. Each section contains a GATE that must be satisfied before proceeding.**
## Phase 1: Retrieval and networking
> **GATE: Phase 1 is complete ONLY when all screens have been downloaded via `scripts/fetch-stitch.sh` AND visually audited. Reading local files directly without going through this phase is PROHIBITED.**
1. **Namespace discovery**: Run `list_tools` to find the Stitch MCP prefix. Use this prefix (e.g., `stitch:`) for all subsequent calls.
2. **Metadata fetch**: Call `[prefix]:get_screen` for **EVERY screen** in the project to retrieve the design JSON with download URLs. Do NOT skip any screen.
3. **Check for existing designs**: Before downloading, check if `.stitch/designs/{page}.html` and `.stitch/designs/{page}.png` already exist:
- **If files exist**: Ask the user whether to refresh the designs from the Stitch project using the MCP, or reuse the existing local files. **You MUST ask — do not assume.** Only re-download if the user confirms.
- **If files do not exist**: Proceed to step 4.
4. **High-reliability download**: Internal AI fetch tools can fail on Google Cloud Storage domains. You MUST use the provided script.
- **HTML**: `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" ".stitch/designs/{page}.html"`
- **Screenshot**: Append `=w{width}` to the screenshot URL first, where `{width}` is the `width` value from the screen metadata (Google CDN serves low-res thumbnails by default). Then run: `bash scripts/fetch-stitch.sh "[screenshot.downloadUrl]=w{width}" ".stitch/designs/{page}.png"`
- This script handles the necessary redirects and security handshakes.
5. **Visual audit**: Review the downloaded screenshot (`.stitch/designs/{page}.png`) to confirm design intent and layout details. **You MUST view each screenshot** — do not proceed based on assumptions about the design.
6. **Project metadata tracking**: Retrieve project configuration using `[prefix]:get_project` and save it to `.stitch/metadata.json` (inside the app folder, and mirrored in the workspace root). Ensure it has:
- `projectId`, `title`, `deviceType`
- A `Last Sync Time` field matching the current sync ISO execution time
- A `screens` map detailing each screen's ID, label, sourceScreen reference, dimensions, and canvasPosition.
### Anti-patterns for Phase 1
- ❌ Reading `.stitch/designs/*.html` directly without calling MCP `get_screen` first.
- ❌ Skipping the `fetch-stitch.sh` download script.
- ❌ Not asking the user when existing files are found.
- ❌ Skipping the visual audit of `.png` screenshots.
- ❌ Failing to generate or update `.stitch/metadata.json` and its `Last Sync Time` field upon syncing.
## Phase 2: Style extraction
> **GATE: Phase 2 is complete ONLY when `resources/style-guide.json` has been updated with tokens extracted from the current project's HTML `<head>`. Tokens from a previous project are NOT acceptable.**
1. **Extract `tailwind.config`**: Open each downloaded HTML file and locate the `tailwind.config` object in the `<head>` `<script>` block. Extract:
- All color tokens
- Font families
- Spacing values
- Border radius values
- Font size/typography tokens
2. **Sync `resources/style-guide.json`**: Overwrite the file with the extracted tokens from THIS project. The style guide MUST match the Stitch project being converted.
3. **Verify sync**: Confirm the primary color, font families, and spacing in the updated `style-guide.json` match what you extracted.
### Anti-patterns for Phase 2
- ❌ Using `style-guide.json` as-is without verifying it matches the current project.
- ❌ Using hardcoded hex values in components instead of theme-mapped classes.
## Phase 3: Architectural rules
> **GATE: Every component MUST satisfy ALL of the following rules. Violations will cause `npm run validate` to fail.**
* **Modular components**: Break the design into independent files. **Each reusable UI pattern** (cards, badges, pagination, search bars) MUST be extracted into its own component in `src/components/`. Monolithic page files that contain everything are PROHIBITED.
* **Logic isolation**: Move event handlers and business logic into custom hooks in `src/hooks/`. Examples: pagination logic → `usePagination`, filtering → `useFilter`.
* **Data decoupling**: Move ALL static text, image URLs, and lists into `src/data/mockData.ts`. No hardcoded content in components.
* **Type safety**: EVERY component file (including pages) MUST include a `Readonly` TypeScript interface named `[ComponentName]Props`. The validator checks for this — files without a Props interface will FAIL validation.
* **Project specific**: Focus on the target project's needs and constraints. Leave Google license headers out of the generated React components.
* **Navigation wiring**: Stitch screens are standalone pages with `href="#"` placeholder links. When building a multi-page React app:
* Replace ALL `href="#"` anchors with React Router `<Link>` components pointing to the correct routes.
* **Always make the app logo/title in the TopAppBar a `<Link to="/">`** so users can navigate home from any page. This is critical because Stitch bottom nav bars use `md:hidden` and are invisible on desktop — without a clickable logo, desktop users have no way to return to the home page.
* Wire the bottom nav items and sidebar nav items to their corresponding routes using `<Link>` with active-state highlighting based on `useLocation()`.
* **Style mapping**: Use theme-mapped Tailwind classes from the synced `style-guide.json`. No arbitrary hex codes.
* **Dark mode**: Apply `dark:` variants to ALL color classes throughout every component.
### Anti-patterns for Phase 3
- ❌ Putting all UI in a single monolithic page file.
- ❌ Inline event handlers or business logic without hooks.
- ❌ Hardcoding text, URLs, or data in component files.
- ❌ Components without a `[Name]Props` interface.
- ❌ Using hex color values instead of theme tokens.
- ❌ Leaving `href="#"` links unconverted.
## Phase 4: Execution steps
> **GATE: Phase 4 verification, audits, and validation checks are optional. You MUST ask the user's permission to proceed with validation scripts, running local dev servers, or automated browser testing.**
1. **Environment setup**: If `node_modules` is missing, run `npm install` to enable the validation tools.
2. **Data layer**: Create `src/data/mockData.ts` based on the design content.
3. **Component drafting**: Use `resources/component-template.tsx` as a base. Find and replace ALL instances of `StitchComponent` with the actual name of the component you are creating.
4. **Application wiring**: Update the project entry point (like `App.tsx`) to render the new components.
5. **Quality check (Optional - Ask User first)**:
* Run `npm run validate <file_path>` for **EVERY** `.tsx` file in `src/components/` and `src/pages/` to report component validity.
* Run `tsc --noEmit` to verify TypeScript compile status.
* Check output against `resources/architecture-checklist.md`.
* Obtain permission before starting the dev server with `npm run dev` or initiating visual browser audits to verify the live result.
### Anti-patterns for Phase 4
- ❌ Commencing dev server start or browser audits without user consent.
- ❌ Declaring task "done" without verifying code compiles.
## Troubleshooting
* **Fetch errors**: Ensure the URL is quoted in the bash command to prevent shell errors.
* **Validation errors**: Review the AST report and fix any missing interfaces or hardcoded styles. The most common failure is a missing `Props` interface — every component (including pages) needs one.
* **Dead navigation links**: Stitch HTML uses `href="#"` placeholders everywhere. Every `<a href="#">` must be converted to a `<Link to="/route">` with a real route. Verify all nav items are clickable and lead to the correct page.
* **Stale style-guide.json**: If colors or fonts look wrong, the `style-guide.json` likely has tokens from a different project. Re-extract from the current HTML `<head>`.

View File

@@ -0,0 +1,65 @@
import React from 'react';
// Note for Agent: The '@' alias refers to the target project's src directory.
// Ensure src/data/mockData.ts is created before generating this component.
import { cardData } from '../data/mockData';
/**
* Gold Standard: ActivityCard
* This file serves as the definitive reference for the agent.
*/
interface ActivityCardProps {
readonly id: string;
readonly username: string;
readonly action: 'MERGED' | 'COMMIT';
readonly timestamp: string;
readonly avatarUrl: string;
readonly repoName: string;
}
export const ActivityCard: React.FC<ActivityCardProps> = ({
username,
action,
timestamp,
avatarUrl,
repoName,
}) => {
const isMerged = action === 'MERGED';
return (
<div className="flex items-center justify-between gap-4 rounded-lg bg-surface-dark p-4 min-h-14 shadow-sm ring-1 ring-white/10">
<div className="flex items-center gap-4 overflow-hidden">
<div
className="aspect-square h-10 w-10 flex-shrink-0 rounded-full bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: `url(${avatarUrl})` }}
aria-label={`Avatar for ${username}`}
/>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm sm:text-base">
<a href="#" className="font-semibold text-primary hover:underline truncate">
{username}
</a>
<span className={`inline-block px-2 py-0.5 text-xs font-semibold rounded-full ${isMerged ? 'bg-purple-500/30 text-purple-300' : 'bg-primary/30 text-primary'
}`}>
{action}
</span>
<span className="text-white/60">in</span>
<a href="#" className="text-primary hover:underline truncate">
{repoName}
</a>
</div>
</div>
<div className="shrink-0">
<p className="text-sm font-normal leading-normal text-white/50">
{timestamp}
</p>
</div>
</div>
);
};
export default ActivityCard;

View File

@@ -0,0 +1,231 @@
{
"name": "stitch-to-react-pro",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stitch-to-react-pro",
"version": "1.0.0",
"dependencies": {
"@swc/core": "^1.3.100"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@swc/core": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz",
"integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.25"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.8",
"@swc/core-darwin-x64": "1.15.8",
"@swc/core-linux-arm-gnueabihf": "1.15.8",
"@swc/core-linux-arm64-gnu": "1.15.8",
"@swc/core-linux-arm64-musl": "1.15.8",
"@swc/core-linux-x64-gnu": "1.15.8",
"@swc/core-linux-x64-musl": "1.15.8",
"@swc/core-win32-arm64-msvc": "1.15.8",
"@swc/core-win32-ia32-msvc": "1.15.8",
"@swc/core-win32-x64-msvc": "1.15.8"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz",
"integrity": "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz",
"integrity": "sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz",
"integrity": "sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz",
"integrity": "sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz",
"integrity": "sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz",
"integrity": "sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz",
"integrity": "sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz",
"integrity": "sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz",
"integrity": "sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz",
"integrity": "sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/types": {
"version": "0.1.25",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"name": "react-components",
"version": "1.0.0",
"description": "Design-to-code prompt to React components for Stitch MCP",
"type": "module",
"scripts": {
"validate": "node scripts/validate.js",
"fetch": "bash scripts/fetch-stitch.sh"
},
"dependencies": {
"@swc/core": "^1.3.100"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@@ -0,0 +1,15 @@
# Architecture Quality Gate
### Structural integrity
- [ ] Logic extracted to custom hooks in `src/hooks/`.
- [ ] No monolithic files; strictly Atomic/Composite modularity.
- [ ] All static text/URLs moved to `src/data/mockData.ts`.
### Type safety and syntax
- [ ] Props use `Readonly<T>` interfaces.
- [ ] File is syntactically valid TypeScript (no red squiggles).
- [ ] Placeholders from templates (e.g., `StitchComponent`) have been replaced with actual names.
### Styling and theming
- [ ] Dark mode (`dark:`) applied to all color classes.
- [ ] No hardcoded hex values; use theme-mapped Tailwind classes.

View File

@@ -0,0 +1,37 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
// Use a valid identifier like 'StitchComponent' as the placeholder
interface StitchComponentProps {
readonly children?: React.ReactNode;
readonly className?: string;
}
export const StitchComponent: React.FC<StitchComponentProps> = ({
children,
className = '',
...props
}) => {
return (
<div className={`relative ${className}`} {...props}>
{children}
</div>
);
};
export default StitchComponent;

View File

@@ -0,0 +1,14 @@
# Stitch API reference
This document describes the data structures returned by the Stitch MCP server to ensure accurate component mapping.
### Metadata schema
When calling `get_screen`, the server returns a JSON object with these key properties:
* **htmlCode**: Contains a `downloadUrl`. This is a signed URL that requires a system-level fetch (curl) to handle redirects and security handshakes.
* **screenshot**: Includes a `downloadUrl` for the visual design. Use this to verify layout intent that might not be obvious in the raw HTML.
* **deviceType**: Usually set to `DESKTOP`. All generated components should prioritize the corresponding viewport (2560px width) as the base layout.
### Technical mapping rules
1. **Element tracking**: Preserve `data-stitch-id` attributes as comments in the TSX to allow for future design synchronization.
2. **Asset handling**: Treat background images in the HTML as dynamic data. Extract the URLs into `mockData.ts` rather than hardcoding them into the component styles.
3. **Style extraction**: The HTML `<head>` contains a localized `tailwind.config`. This config must be merged with the local project theme to ensure colors like `primary` and `background-dark` render correctly.

View File

@@ -0,0 +1,27 @@
{
"theme": {
"colors": {
"primary": "#19e66f",
"background": {
"light": "#f6f8f7",
"dark": "#112118",
"elevated": "#1A1A1A"
},
"accent": {
"purple": "#8A2BE2",
"lavender": "#D0A9F5"
}
},
"typography": {
"display": [
"Space Grotesk",
"sans-serif"
],
"icons": "Material Symbols Outlined"
},
"spacing": {
"header-h": "72px",
"container-max": "960px"
}
}
}

View File

@@ -0,0 +1,30 @@
#!/bin/bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
URL=$1
OUTPUT=$2
if [ -z "$URL" ] || [ -z "$OUTPUT" ]; then
echo "Usage: $0 <url> <output_path>"
exit 1
fi
echo "Initiating high-reliability fetch for Stitch HTML..."
curl -L -f -sS --connect-timeout 10 --compressed "$URL" -o "$OUTPUT"
if [ $? -eq 0 ]; then
echo "✅ Successfully retrieved HTML at: $OUTPUT"
exit 0
else
echo "❌ Error: Failed to retrieve content. Check TLS/SNI or URL expiration."
exit 1
fi

View File

@@ -0,0 +1,82 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import swc from '@swc/core';
import fs from 'node:fs';
import path from 'node:path';
const HEX_COLOR_REGEX = /#[0-9A-Fa-f]{3,8}\b/;
async function validateComponent(filePath) {
if (!filePath) {
console.error("Usage: node validate.js <path-to-component>");
process.exit(1);
}
try {
const code = fs.readFileSync(filePath, 'utf-8');
const filename = path.basename(filePath);
const ast = await swc.parse(code, { syntax: "typescript", tsx: true });
let hasInterface = false;
let tailwindIssues = [];
console.log("🔍 Scanning AST...");
const walk = (node) => {
if (!node || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const item of node) walk(item);
return;
}
if (typeof node.type !== 'string') return;
if (node.type === 'TsInterfaceDeclaration' && node.id.value.endsWith('Props')) hasInterface = true;
if (node.type === 'JSXAttribute' && (node.name?.value === 'className' || node.name?.name === 'className')) {
if (node.value?.value && HEX_COLOR_REGEX.test(node.value.value)) tailwindIssues.push(node.value.value);
}
for (const key in node) {
if (key === 'span') continue;
if (node[key] && typeof node[key] === 'object') walk(node[key]);
}
};
walk(ast);
console.log(`--- Validation for: ${filename} ---`);
if (hasInterface) {
console.log("✅ Props declaration found.");
} else {
console.error("❌ MISSING: Props interface (must end in 'Props').");
}
if (tailwindIssues.length === 0) {
console.log("✅ No hardcoded hex values found.");
} else {
console.error(`❌ STYLE: Found ${tailwindIssues.length} hardcoded hex codes.`);
tailwindIssues.forEach(hex => console.error(` - ${hex}`));
}
if (hasInterface && tailwindIssues.length === 0) {
console.log("\n✨ COMPONENT VALID.");
process.exit(0);
} else {
console.error("\n🚫 VALIDATION FAILED.");
process.exit(1);
}
} catch (err) {
console.error("❌ ERROR:", err.message);
process.exit(1);
}
}
validateComponent(process.argv[2]);