@benny_gebeya/gebeya-whatsapp-otp
v1.0.1
Published
React WhatsApp OTP verification component library with Supabase integration for phone number authentication
Maintainers
Readme
Gebeya WhatsApp OTP
A React component library for WhatsApp-based OTP verification with Supabase integration. This package provides a complete solution for phone number authentication using WhatsApp OTP, including a provider pattern, modal interface, and comprehensive error handling.
Features
- 🔐 WhatsApp OTP Verification: Send and verify OTP codes via WhatsApp
- ⚛️ React Context Provider: Easy state management with React Context
- 🎨 Pre-built UI Components: Ready-to-use modal and button components
- 📱 Country Selection: Built-in country code selector with flags
- 🔄 Retry Logic: Automatic attempt tracking and suspension handling
- 🎯 TypeScript Support: Full TypeScript definitions included
- 🎨 Tailwind CSS: Styled with Tailwind CSS classes
- 🔧 Customizable: Flexible configuration and callback system
Installation
npm install @benny_gebeya/gebeya-whatsapp-otpPeer Dependencies
This package requires React 16.8.0 or higher:
npm install react react-domQuick Start
1. Wrap your app with the provider
import React from "react";
import { WhatsAppOTPProvider } from "@benny_gebeya/gebeya-whatsapp-otp";
const config = {
supabaseUrl: "https://your-project.supabase.co",
supabaseKey: "your-anon-key",
defaultCountry: "+251", // Optional: Default to Ethiopia
};
const callbacks = {
onSuccess: (phoneNumber: string) => {
console.log("Phone verified:", phoneNumber);
},
onError: (error: string) => {
console.error("Verification error:", error);
},
};
function App() {
return (
<WhatsAppOTPProvider config={config} callbacks={callbacks}>
<YourAppContent />
</WhatsAppOTPProvider>
);
}2. Add the verification components
import React from "react";
import {
VerifyWithWhatsAppButton,
WhatsAppOTPModal,
} from "@benny_gebeya/gebeya-whatsapp-otp";
function YourAppContent() {
return (
<div>
<h1>Welcome to our app</h1>
<VerifyWithWhatsAppButton />
<WhatsAppOTPModal />
</div>
);
}API Reference
WhatsAppOTPProvider
The main provider component that manages OTP verification state.
Props
| Prop | Type | Required | Description |
| ----------- | ---------------------- | -------- | ---------------------- |
| children | React.ReactNode | ✅ | Child components |
| config | WhatsAppOTPConfig | ✅ | Supabase configuration |
| callbacks | WhatsAppOTPCallbacks | ❌ | Event callbacks |
WhatsAppOTPConfig
interface WhatsAppOTPConfig {
/** Supabase project URL */
supabaseUrl: string;
/** Supabase anon/service role key */
supabaseKey: string;
/** Default country code (e.g., "+251" for Ethiopia) */
defaultCountry?: string;
}WhatsAppOTPCallbacks
interface WhatsAppOTPCallbacks {
/** Called when OTP verification is successful */
onSuccess?: (phoneNumber: string) => void;
/** Called when an error occurs during the process */
onError?: (error: string) => void;
/** Called when the verification step changes */
onStepChange?: (step: VerificationStep) => void;
/** Called when a phone number gets suspended */
onSuspension?: (message: string) => void;
}VerifyWithWhatsAppButton
A button component that opens the verification modal.
Props
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------------------------------------------- | ------------------------ | -------------------------- |
| children | React.ReactNode | "Verify with WhatsApp" | Button text |
| className | string | "" | Additional CSS classes |
| variant | "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| "link" | "default" | Button style variant |
| size | "default" \| "sm" \| "lg" \| "icon" | "default" | Button size |
| disabled | boolean | false | Whether button is disabled |
Example
<VerifyWithWhatsAppButton
variant="outline"
size="lg"
className="my-custom-class"
>
Start Verification
</VerifyWithWhatsAppButton>WhatsAppOTPModal
The modal component that handles the verification flow.
Props
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------------ | ---------------------- |
| countries | CountryOption[] | Default list | Custom country options |
CountryOption
interface CountryOption {
/** Country calling code (e.g., "+251") */
code: string;
/** Country name (e.g., "Ethiopia") */
country: string;
/** Country flag emoji (e.g., "🇪🇹") */
flag: string;
}Example with custom countries
const customCountries = [
{ code: "+1", country: "United States", flag: "🇺🇸" },
{ code: "+44", country: "United Kingdom", flag: "🇬🇧" },
{ code: "+251", country: "Ethiopia", flag: "🇪🇹" },
];
<WhatsAppOTPModal countries={customCountries} />;useWhatsAppOTP Hook
Access the OTP verification state and actions directly.
import { useWhatsAppOTPContext } from "@benny_gebeya/gebeya-whatsapp-otp";
function CustomComponent() {
const { step, phoneNumber, isLoading, sendOTP, verifyOTP, openModal } =
useWhatsAppOTPContext();
return (
<div>
<p>Current step: {step}</p>
<button onClick={openModal}>Open Verification</button>
</div>
);
}Hook Return Value
interface WhatsAppOTPContextType {
// State properties
step: VerificationStep;
countryCode: string;
phoneNumber: string;
otpCode: string;
isLoading: boolean;
attemptsLeft: number;
maxAttemptsReached: boolean;
isSuspended: boolean;
suspensionMessage: string;
isModalOpen: boolean;
// Action functions
setCountryCode: (code: string) => void;
setPhoneNumber: (number: string) => void;
setOtpCode: (code: string) => void;
sendOTP: () => Promise<void>;
verifyOTP: () => Promise<void>;
resetFlow: () => void;
openModal: () => void;
closeModal: () => void;
}TypeScript Usage
The package includes full TypeScript support. Import types as needed:
import {
WhatsAppOTPConfig,
WhatsAppOTPCallbacks,
VerificationStep,
CountryOption,
WhatsAppOTPContextType,
} from "@benny_gebeya/gebeya-whatsapp-otp";
// Example: Type-safe configuration
const config: WhatsAppOTPConfig = {
supabaseUrl: process.env.REACT_APP_SUPABASE_URL!,
supabaseKey: process.env.REACT_APP_SUPABASE_ANON_KEY!,
defaultCountry: "+251",
};
// Example: Type-safe callbacks
const callbacks: WhatsAppOTPCallbacks = {
onSuccess: (phoneNumber: string) => {
// Handle successful verification
console.log(`Verified: ${phoneNumber}`);
},
onError: (error: string) => {
// Handle errors
console.error("Verification failed:", error);
},
onStepChange: (step: VerificationStep) => {
// Track verification progress
console.log(`Step changed to: ${step}`);
},
};Advanced Examples
Custom Error Handling
import React, { useState } from "react";
import {
WhatsAppOTPProvider,
VerifyWithWhatsAppButton,
WhatsAppOTPModal,
} from "@benny_gebeya/gebeya-whatsapp-otp";
function App() {
const [error, setError] = useState<string>("");
const [isVerified, setIsVerified] = useState(false);
const config = {
supabaseUrl: "https://your-project.supabase.co",
supabaseKey: "your-anon-key",
};
const callbacks = {
onSuccess: (phoneNumber: string) => {
setIsVerified(true);
setError("");
// Redirect or update UI
},
onError: (error: string) => {
setError(error);
},
onSuspension: (message: string) => {
setError(`Account suspended: ${message}`);
},
};
return (
<WhatsAppOTPProvider config={config} callbacks={callbacks}>
<div className="p-4">
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{isVerified ? (
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
Phone number verified successfully!
</div>
) : (
<VerifyWithWhatsAppButton />
)}
<WhatsAppOTPModal />
</div>
</WhatsAppOTPProvider>
);
}Integration with Form Libraries
import React from "react";
import { useForm } from "react-hook-form";
import {
WhatsAppOTPProvider,
useWhatsAppOTPContext,
} from "@benny_gebeya/gebeya-whatsapp-otp";
interface FormData {
name: string;
email: string;
phoneVerified: boolean;
}
function RegistrationForm() {
const { register, handleSubmit, setValue, watch } = useForm<FormData>();
const { step } = useWhatsAppOTPContext();
const phoneVerified = watch("phoneVerified");
const onSubmit = (data: FormData) => {
if (!data.phoneVerified) {
alert("Please verify your phone number first");
return;
}
// Submit form
console.log("Form submitted:", data);
};
const callbacks = {
onSuccess: () => {
setValue("phoneVerified", true);
},
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<input
{...register("name", { required: true })}
placeholder="Full Name"
className="w-full p-2 border rounded"
/>
<input
{...register("email", { required: true })}
type="email"
placeholder="Email"
className="w-full p-2 border rounded"
/>
<div className="flex items-center gap-2">
<VerifyWithWhatsAppButton size="sm" />
{phoneVerified && <span className="text-green-600">✓ Verified</span>}
</div>
<button
type="submit"
disabled={!phoneVerified}
className="w-full bg-blue-600 text-white p-2 rounded disabled:opacity-50"
>
Register
</button>
<WhatsAppOTPModal />
</form>
);
}Styling
The components use Tailwind CSS classes and are designed to work with your existing Tailwind setup. The components include:
- Responsive design
- Dark mode support
- Accessible focus states
- Consistent spacing and typography
Custom Styling
You can override styles by passing custom className props:
<VerifyWithWhatsAppButton className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-full">
Custom Styled Button
</VerifyWithWhatsAppButton>Requirements
- React 16.8.0 or higher
- Supabase project with WhatsApp OTP function deployed
- Tailwind CSS (for styling)
- WhatsApp Business API access (Facebook/Meta)
Supabase Setup
This package requires a Supabase edge function and database tables to handle WhatsApp OTP functionality. Follow these steps to set up your Supabase project:
1. Create Database Tables
Run these SQL commands in your Supabase SQL editor:
Complete Database Schema
Run this complete SQL script in your Supabase SQL editor to set up all required tables, indexes, and security policies:
-- Set up database configuration
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
-- Create extensions if they don't exist
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
-- Create utility function for updating timestamps
CREATE OR REPLACE FUNCTION "public"."update_updated_at_column"()
RETURNS "trigger"
LANGUAGE "plpgsql"
SET "search_path" TO ''
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
-- Create OTP codes table
CREATE TABLE IF NOT EXISTS "public"."otp_codes" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"phone_number" "text" NOT NULL,
"code" "text" NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"used" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"attempts_left" integer DEFAULT 3 NOT NULL,
"suspended_until" timestamp with time zone,
"suspension_reason" "text"
);
-- Add table comment
COMMENT ON COLUMN "public"."otp_codes"."expires_at" IS 'OTP codes should expire within 2-3 minutes for security';
-- Create primary key
ALTER TABLE ONLY "public"."otp_codes"
ADD CONSTRAINT "otp_codes_pkey" PRIMARY KEY ("id");
-- Create indexes for better performance
CREATE INDEX "idx_otp_codes_expires_at" ON "public"."otp_codes" USING "btree" ("expires_at");
CREATE INDEX "idx_otp_codes_phone_number" ON "public"."otp_codes" USING "btree" ("phone_number");
CREATE INDEX "idx_otp_codes_phone_suspended" ON "public"."otp_codes" USING "btree" ("phone_number", "suspended_until");
-- Create Row Level Security policies
CREATE POLICY "Anyone can insert OTP codes" ON "public"."otp_codes" FOR INSERT WITH CHECK (true);
CREATE POLICY "Anyone can select OTP codes" ON "public"."otp_codes" FOR SELECT USING (true);
CREATE POLICY "Anyone can update OTP codes" ON "public"."otp_codes" FOR UPDATE USING (true);
-- Enable Row Level Security
ALTER TABLE "public"."otp_codes" ENABLE ROW LEVEL SECURITY;
-- Grant necessary permissions
GRANT ALL ON TABLE "public"."otp_codes" TO "anon";
GRANT ALL ON TABLE "public"."otp_codes" TO "authenticated";
GRANT ALL ON TABLE "public"."otp_codes" TO "service_role";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "anon";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "authenticated";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "service_role";2. Deploy the Edge Function
Step 1: Install Supabase CLI
npm install -g supabaseStep 2: Login to Supabase
supabase loginStep 3: Initialize Supabase in your project (if not already done)
supabase initStep 4: Create the edge function
supabase functions new otp_whatsappStep 5: Replace the function code
Replace the contents of supabase/functions/otp_whatsapp/index.ts with:
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
// Handle CORS preflight requests
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_ANON_KEY") ?? ""
);
// Create admin client for user creation
const supabaseAdmin = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
);
const { action, phone_number, code } = await req.json();
if (action === "send_otp") {
// Check if phone number is currently suspended
const { data: suspensionCheck } = await supabase
.from("otp_codes")
.select("suspended_until, suspension_reason")
.eq("phone_number", phone_number)
.not("suspended_until", "is", null)
.gt("suspended_until", new Date().toISOString())
.order("suspended_until", { ascending: false })
.limit(1);
if (suspensionCheck && suspensionCheck.length > 0) {
const suspension = suspensionCheck[0];
const remainingTime = Math.ceil(
(new Date(suspension.suspended_until).getTime() -
new Date().getTime()) /
1000
);
const minutes = Math.ceil(remainingTime / 60);
return new Response(
JSON.stringify({
error: `Phone number is temporarily suspended. Please try again in ${minutes} minute${
minutes !== 1 ? "s" : ""
}.`,
}),
{
status: 429,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
// Generate 6-digit OTP
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date();
expiresAt.setMinutes(expiresAt.getMinutes() + 2); // 2 minutes expiry
console.log(`Generating OTP for phone ${phone_number}: ${otpCode}`);
// Store OTP in database
const { error: otpError } = await supabase.from("otp_codes").insert({
phone_number,
code: otpCode,
expires_at: expiresAt.toISOString(),
suspended_until: null,
suspension_reason: null,
});
if (otpError) {
console.error("Error storing OTP:", otpError);
return new Response(
JSON.stringify({ error: "Failed to generate OTP" }),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
// Send WhatsApp message using Facebook Graph API
const facebookToken = Deno.env.get("FACEBOOK_ACCESS_TOKEN");
const phoneNumberId = Deno.env.get("WHATSAPP_PHONE_NUMBER_ID");
const whatsappPayload = {
messaging_product: "whatsapp",
to: phone_number,
type: "template",
template: {
name: "otp_verification_code",
language: { code: "en_US" },
components: [
{
type: "BODY",
parameters: [{ type: "text", text: otpCode }],
},
{
type: "BUTTON",
sub_type: "URL",
index: "0",
parameters: [{ type: "text", text: otpCode }],
},
],
},
};
const whatsappResponse = await fetch(
`https://graph.facebook.com/v22.0/${phoneNumberId}/messages`,
{
method: "POST",
headers: {
Authorization: `Bearer ${facebookToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(whatsappPayload),
}
);
const whatsappResult = await whatsappResponse.json();
if (!whatsappResponse.ok) {
console.error("WhatsApp API error:", whatsappResult);
return new Response(
JSON.stringify({
error: "Failed to send WhatsApp message",
details: whatsappResult,
}),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
return new Response(
JSON.stringify({
success: true,
message: "OTP sent successfully",
message_id: whatsappResult.messages?.[0]?.id,
}),
{
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
} else if (action === "verify_otp") {
// Check if phone number is currently suspended
const { data: suspensionCheck } = await supabase
.from("otp_codes")
.select("suspended_until, suspension_reason")
.eq("phone_number", phone_number)
.not("suspended_until", "is", null)
.gt("suspended_until", new Date().toISOString())
.order("suspended_until", { ascending: false })
.limit(1);
if (suspensionCheck && suspensionCheck.length > 0) {
const suspension = suspensionCheck[0];
const remainingTime = Math.ceil(
(new Date(suspension.suspended_until).getTime() -
new Date().getTime()) /
1000
);
const minutes = Math.ceil(remainingTime / 60);
return new Response(
JSON.stringify({
error: `Phone number is temporarily suspended. Please try again in ${minutes} minute${
minutes !== 1 ? "s" : ""
}.`,
}),
{
status: 429,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
// Check if there's a valid OTP for this phone number
const { data: otpRecord, error: fetchError } = await supabase
.from("otp_codes")
.select("*")
.eq("phone_number", phone_number)
.eq("used", false)
.gt("expires_at", new Date().toISOString())
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
if (fetchError) {
console.error("Error fetching OTP:", fetchError);
return new Response(JSON.stringify({ error: "Verification failed" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (!otpRecord) {
return new Response(
JSON.stringify({ error: "Invalid or expired OTP code" }),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
// Check if the code matches
if (otpRecord.code !== code) {
const newAttemptsLeft = otpRecord.attempts_left - 1;
await supabase
.from("otp_codes")
.update({ attempts_left: newAttemptsLeft })
.eq("id", otpRecord.id);
if (newAttemptsLeft <= 0) {
// Suspend the phone number for 3 minutes
const suspendedUntil = new Date();
suspendedUntil.setMinutes(suspendedUntil.getMinutes() + 3);
await supabase
.from("otp_codes")
.update({
used: true,
suspended_until: suspendedUntil.toISOString(),
suspension_reason: "max_attempts_reached",
})
.eq("id", otpRecord.id);
return new Response(
JSON.stringify({
error:
"Maximum attempts reached. Your phone number has been temporarily suspended for 3 minutes.",
}),
{
status: 429,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
return new Response(
JSON.stringify({
error: `Invalid OTP code. ${newAttemptsLeft} attempts remaining.`,
}),
{
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
// Mark OTP as used
const { error: updateError } = await supabase
.from("otp_codes")
.update({ used: true })
.eq("id", otpRecord.id);
if (updateError) {
console.error("Error updating OTP:", updateError);
return new Response(JSON.stringify({ error: "Verification failed" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// Handle user authentication
const { data: existingUsers } =
await supabaseAdmin.auth.admin.listUsers();
const existingUser = existingUsers.users?.find(
(u) => u.phone === phone_number
);
let user;
if (existingUser) {
user = existingUser;
} else {
// Create new user with phone number
const { data: newUserData, error: createError } =
await supabaseAdmin.auth.admin.createUser({
phone: phone_number,
phone_confirm: true,
email_confirm: true,
user_metadata: {
phone: phone_number,
provider: "phone",
},
});
if (createError) {
console.error("Error creating user:", createError);
return new Response(
JSON.stringify({ error: "Failed to create user account" }),
{
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
user = newUserData.user;
}
// Send success template
const facebookToken = Deno.env.get("FACEBOOK_ACCESS_TOKEN");
const phoneNumberId = Deno.env.get("WHATSAPP_PHONE_NUMBER_ID");
const successPayload = {
messaging_product: "whatsapp",
to: phone_number,
type: "template",
template: {
name: "otp_success",
language: { code: "en_US" },
},
};
await fetch(
`https://graph.facebook.com/v22.0/${phoneNumberId}/messages`,
{
method: "POST",
headers: {
Authorization: `Bearer ${facebookToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(successPayload),
}
);
return new Response(
JSON.stringify({
success: true,
message: "Phone number verified and user authenticated successfully",
verified: true,
user: user,
}),
{
headers: { ...corsHeaders, "Content-Type": "application/json" },
}
);
}
return new Response(JSON.stringify({ error: "Invalid action" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error in otp_whatsapp function:", error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});Step 6: Set up environment variables
# Set your environment variables
supabase secrets set FACEBOOK_ACCESS_TOKEN=your_facebook_access_tokenStep 7: Deploy the function
supabase functions deploy otp_whatsapp3. WhatsApp Business API Setup
- Create a Meta Business Account at https://business.facebook.com
- Set up WhatsApp Business API through Meta Business
- Create message templates in your WhatsApp Business Manager:
- Template name:
otp_verification_code - Template name:
otp_success
- Template name:
- Get your access token and phone number ID from the Meta Developer Console
- Configure webhooks (optional) for delivery status
4. Update Your App Configuration
Make sure your app configuration points to your Supabase project:
const config = {
supabaseUrl: "https://your-project.supabase.co",
supabaseKey: "your-anon-key", // This should be your anon/public key
};5. Test Your Setup
You can test your edge function directly:
curl -X POST 'https://your-project.supabase.co/functions/v1/otp_whatsapp' \
-H 'Authorization: Bearer YOUR_ANON_KEY' \
-H 'Content-Type: application/json' \
-d '{
"action": "send_otp",
"phone_number": "+1234567890"
}'License
MIT
Support
For issues and questions, please visit our GitHub repository.
Development
Package Validation
This package includes a comprehensive validation script to ensure the build is correct and ready for publishing. The validation script tests:
- Build file existence and formats
- Package.json configuration
- Import functionality in both JavaScript and TypeScript
- Peer dependency resolution
- Basic component functionality
Running Validation
# Run the full package validation
npm run validate
# Run just the build validation
npm run validate-buildWhat Gets Validated
Build Files: Checks that all required build outputs exist
dist/index.cjs(CommonJS build)dist/index.esm.js(ES modules build)dist/index.d.ts(TypeScript declarations)- Component-specific declaration files
Package Configuration: Validates package.json settings
- Entry points (main, module, types)
- Peer dependencies configuration
- File inclusion settings
Import Testing: Tests imports in different environments
- CommonJS require() syntax
- ES modules import syntax
- TypeScript type checking and compilation
Peer Dependencies: Verifies React is properly externalized
- Checks that React is not bundled in the output
- Validates peer dependency resolution
Component Functionality: Basic validation of exported components
- Ensures all exports are available
- Validates component types and structure
Validation Output
The validation script provides detailed feedback:
🔍 Comprehensive Package Validation for gebeya-whatsapp-otp
1. Checking build output files...
✓ dist/index.cjs exists
✓ dist/index.esm.js exists
✓ dist/index.d.ts exists
...
✅ Package validation PASSED
📦 Package is ready for publishingBuilding the Package
# Build the package
npm run build
# Build and validate
npm run build && npm run validateThe build process:
- Compiles TypeScript to generate declaration files
- Bundles with Rollup to create CommonJS and ES module outputs
- Generates source maps for debugging
- Excludes peer dependencies from the bundle
