@power-seo/search-console 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,377 +1,405 @@
1
- # @power-seo/search-console — Google Search Console API Client for TypeScript — OAuth2, Service Account, URL Inspection & Auto-Paginated Analytics
2
-
3
- [![npm version](https://img.shields.io/npm/v/@power-seo/search-console?style=flat-square)](https://www.npmjs.com/package/@power-seo/search-console)
4
- [![npm downloads](https://img.shields.io/npm/dm/@power-seo/search-console?style=flat-square)](https://www.npmjs.com/package/@power-seo/search-console)
5
- [![MIT License](https://img.shields.io/npm/l/@power-seo/search-console?style=flat-square)](../../LICENSE)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?style=flat-square)](https://www.typescriptlang.org/)
7
- [![Tree-shakeable](https://img.shields.io/badge/tree--shakeable-yes-brightgreen?style=flat-square)](#)
8
-
9
- ---
10
-
11
- ## Overview
12
-
13
- **@power-seo/search-console** is a production-ready Google Search Console API client for TypeScript that helps you query search analytics, inspect URLs, and manage sitemaps — without writing OAuth2 token refresh boilerplate or pagination loops.
14
-
15
- **What it does**
16
- - ✅ **OAuth2 and service account auth** — `createTokenManager()` handles token refresh cycles and JWT signing automatically
17
- - ✅ **Search analytics queries** — clicks, impressions, CTR, and position by query, page, country, device, date
18
- - ✅ **Auto-paginated full fetch** — `querySearchAnalyticsAll()` transparently pages through the 25,000-row GSC API limit
19
- - ✅ **URL inspection** — indexing verdict, last crawl time, canonical URL, mobile usability, rich result status
20
- - ✅ **Sitemap management** — list, submit, and delete sitemaps from verified GSC properties
21
-
22
- **What it is not**
23
- - ❌ **Not a reporting dashboard** — returns raw data; use `@power-seo/analytics` to merge and visualize
24
- - ❌ **Not a site verification tool** — requires a property already verified in Google Search Console
25
-
26
- **Recommended for**
27
- - **SEO automation pipelines**, **CI/CD keyword tracking**, **SaaS analytics dashboards**, and **reporting scripts** using GSC data
28
-
29
- ---
30
-
31
- ## Why @power-seo/search-console Matters
32
-
33
- **The problem**
34
- - **OAuth2 token refresh is complex** — access tokens expire every hour; service account JWT signing requires crypto operations
35
- - **GSC returns only 25,000 rows per request** — sites with many queries need pagination loops that every team reimplements
36
- - **URL inspection data is inaccessible without auth** — no simple way to check indexing status programmatically
37
-
38
- **Why developers care**
39
- - **SEO:** Programmatic access to real click/impression data enables data-driven content decisions
40
- - **Performance:** Auto-paginated `querySearchAnalyticsAll` eliminates boilerplate that slows down analytics pipelines
41
- - **UX:** URL inspection in CI enables instant indexing health checks after deployments
42
-
43
- ---
44
-
45
- ## Key Features
46
-
47
- - **OAuth2 authentication** — `createTokenManager({ type: 'oauth' })` handles token refresh automatically
48
- - **Service account JWT authentication** — `createTokenManager({ type: 'service-account' })` signs JWTs for server-to-server access
49
- - **Low-level auth primitives** — `exchangeRefreshToken()` and `getServiceAccountToken()` for custom auth flows
50
- - **Typed GSC client** — `createGSCClient(config)` returns a `GSCClient` scoped to a specific site URL
51
- - **Search analytics** — `querySearchAnalytics()` supports all 6 dimensions: `query`, `page`, `country`, `device`, `date`, `searchAppearance`
52
- - **All search types** — `web`, `image`, `video`, and `news` search types
53
- - **Auto-paginated full fetch** — `querySearchAnalyticsAll()` merges all pages into a single `SearchAnalyticsRow[]` array
54
- - **URL inspection** — `inspectUrl()` returns verdict, indexing state, last crawl time, mobile usability, and rich result status
55
- - **Direct URL inspection** — `inspectUrlDirect()` for the direct URL inspection endpoint
56
- - **Sitemap listing** — `listSitemaps()` with status, last download time, and error counts
57
- - **Sitemap submission and deletion** — `submitSitemap()` and `deleteSitemap()`
58
- - **Typed error handling** — `GSCApiError` class with `status`, `code`, and `message`
59
- - **Type-safe throughout** — full TypeScript types for all request and response shapes
60
-
61
- ---
62
-
63
- ## Benefits of Using @power-seo/search-console
64
-
65
- - **Faster analytics pipelines**: No token refresh boilerplate; token manager handles expiry transparently
66
- - **Complete datasets**: `querySearchAnalyticsAll` fetches all rows regardless of API page limit
67
- - **Safer integration**: `GSCApiError` structured error class enables reliable error handling in production
68
- - **Faster delivery**: Connect to GSC in minutes; no OAuth2 library research required
69
-
70
- ---
71
-
72
- ## Quick Start
73
-
74
- ```ts
75
- import { createTokenManager, createGSCClient, querySearchAnalyticsAll } from '@power-seo/search-console';
76
-
77
- // 1. Create a token manager (OAuth2)
78
- const tokenManager = createTokenManager({
79
- type: 'oauth',
80
- clientId: process.env.GSC_CLIENT_ID!,
81
- clientSecret: process.env.GSC_CLIENT_SECRET!,
82
- refreshToken: process.env.GSC_REFRESH_TOKEN!,
83
- });
84
-
85
- // 2. Create a client scoped to your site
86
- const client = createGSCClient({
87
- siteUrl: 'https://example.com',
88
- tokenManager,
89
- });
90
-
91
- // 3. Fetch all search analytics data (auto-paginated)
92
- const rows = await querySearchAnalyticsAll(client, {
93
- startDate: '2026-01-01',
94
- endDate: '2026-01-31',
95
- dimensions: ['query', 'page'],
96
- });
97
-
98
- rows.forEach(({ keys, clicks, impressions, ctr, position }) => {
99
- console.log(`Query: "${keys[0]}", Page: ${keys[1]}`);
100
- console.log(` ${clicks} clicks, ${impressions} impressions, pos ${position.toFixed(1)}`);
101
- });
102
- ```
103
-
104
- **What you should see**
105
- - A merged `SearchAnalyticsRow[]` array with all query-page combinations for the date range
106
- - `clicks`, `impressions`, `ctr`, and `position` for each row
107
-
108
- ---
109
-
110
- ## Installation
111
-
112
- ```bash
113
- npm i @power-seo/search-console
114
- # or
115
- yarn add @power-seo/search-console
116
- # or
117
- pnpm add @power-seo/search-console
118
- # or
119
- bun add @power-seo/search-console
120
- ```
121
-
122
- ---
123
-
124
- ## Framework Compatibility
125
-
126
- **Supported**
127
- - Next.js (App Router / Pages Router) — use in API routes or server actions
128
- - ✅ Remix — use in loader functions and server-side actions
129
- - Node.js 18+ — pure TypeScript client with `fetch` API
130
- - ✅ CI/CD pipelines — run as standalone Node scripts in GitHub Actions or similar
131
-
132
- **Environment notes**
133
- - **SSR/SSG:** Fully supported — all operations are server-side
134
- - **Edge runtime:** Not supported (requires crypto for JWT signing)
135
- - **Browser-only usage:** Not supported — exposes credentials; server-side only
136
-
137
- ---
138
-
139
- ## Use Cases
140
-
141
- - **Automated keyword ranking reports** fetch all queries weekly and diff against previous week
142
- - **Indexing health monitoring** — `inspectUrl` after deployments to verify new pages are indexed
143
- - **Content gap analysis** — merge GSC data with `@power-seo/analytics` to find high-impression, low-click pages
144
- - **Sitemap automation** — submit new sitemaps programmatically after content migrations
145
- - **CI/CD SEO checks**fail pipelines when key pages drop below position threshold
146
- - **Multi-site SaaS dashboards** aggregate GSC data across multiple client properties
147
- - **Image and news search analytics** query `image` and `news` search types separately
148
- - **Country and device breakdowns** — segment click data by country or device for regional SEO
149
-
150
- ---
151
-
152
- ## Example (Before / After)
153
-
154
- ```text
155
- Before:
156
- - Manual OAuth2 token refresh: 50+ lines of token exchange code per project
157
- - Pagination loop: separate rowOffset counter, multiple fetch calls, manual array merging
158
- - URL inspection: no programmatic access check Google Search Console UI manually
159
-
160
- After (@power-seo/search-console):
161
- - createTokenManager({ type: 'oauth', ... }) → tokens auto-refresh before every request
162
- - querySearchAnalyticsAll(client, { dimensions: ['query'] }) → one call, complete dataset
163
- - inspectUrl(client, url) → { verdict: 'PASS', lastCrawlTime: '...', mobileUsabilityResult: {...} }
164
- ```
165
-
166
- ---
167
-
168
- ## Implementation Best Practices
169
-
170
- - **Use service accounts for CI/CD** service accounts don't expire and don't require user interaction
171
- - **Cache `querySearchAnalyticsAll` results** — GSC data is delayed by ~2 days; daily fetches are sufficient
172
- - **Use `sc-domain:` prefix for domain properties** — e.g. `sc-domain:example.com` for domain-level verification
173
- - **Add service account email as GSC user** — Settings Users and permissions Add user
174
- - **Handle `GSCApiError` status 429** implement exponential backoff for rate-limited requests
175
-
176
- ---
177
-
178
- ## Architecture Overview
179
-
180
- **Where it runs**
181
- - **Build-time**: Fetch GSC data for static site revalidation or build-time content ranking
182
- - **Runtime**: Query analytics in serverless functions for real-time dashboard data
183
- - **CI/CD**: Check indexing status and keyword positions in automated pipelines
184
-
185
- **Data flow**
186
- 1. **Input**: OAuth2 credentials or service account key + site URL + query parameters
187
- 2. **Analysis**: Token manager handles auth; client sends typed requests to GSC API
188
- 3. **Output**: Typed `SearchAnalyticsRow[]`, `InspectionResult`, `SitemapListResponse`
189
- 4. **Action**: Feed into `@power-seo/analytics` for dashboards, or export as CSV/JSON reports
190
-
191
- ---
192
-
193
- ## Features Comparison with Popular Packages
194
-
195
- | Capability | google-auth-library | googleapis | custom fetch | @power-seo/search-console |
196
- |---|---:|---:|---:|---:|
197
- | OAuth2 token auto-refresh | ✅ | ✅ | ❌ | ✅ |
198
- | Service account JWT signing | ✅ | ✅ | ❌ | ✅ |
199
- | Auto-paginated analytics fetch | ❌ | ❌ | ❌ | ✅ |
200
- | Typed GSC-specific response shapes | | ⚠️ | | |
201
- | URL inspection support | | ⚠️ | | |
202
- | Sitemap management | | ⚠️ | ❌ | ✅ |
203
-
204
- ---
205
-
206
- ## @power-seo Ecosystem
207
-
208
- All 17 packages are independently installable — use only what you need.
209
-
210
- | Package | Install | Description |
211
- |---------|---------|-------------|
212
- | [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants |
213
- | [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs |
214
- | [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
215
- | [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 20 builders + 18 React components |
216
- | [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components |
217
- | [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
218
- | [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators |
219
- | [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation |
220
- | [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters |
221
- | [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis — orphan detection, suggestions, equity scoring |
222
- | [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine meta, content, structure, performance rules |
223
- | [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO — alt text, lazy loading, format analysis, image sitemaps |
224
- | [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks |
225
- | [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard |
226
- | [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection |
227
- | [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination |
228
- | [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom scripts + consent management |
229
-
230
- ### Ecosystem vs alternatives
231
-
232
- | Need | Common approach | @power-seo approach |
233
- |---|---|---|
234
- | GSC search analytics | `googleapis` (verbose) | `@power-seo/search-console` typed, auto-paginated |
235
- | Analytics dashboards | Google Looker Studio | `@power-seo/analytics` — merge GSC + audit data |
236
- | Sitemap submission | Manual GSC UI | `@power-seo/search-console` — `submitSitemap()` |
237
- | SEO auditing | Third-party tools | `@power-seo/audit` — in-code, CI-friendly |
238
-
239
- ---
240
-
241
- ## Enterprise Integration
242
-
243
- **Multi-tenant SaaS**
244
- - **Per-client GSC properties**: Instantiate one `GSCClient` per client site; use service accounts for automation
245
- - **Aggregated dashboards**: Loop over multiple `siteUrl` values and merge analytics into a multi-site report
246
- - **Scheduled data sync**: Run `querySearchAnalyticsAll` on a daily cron for all client properties
247
-
248
- **ERP / internal portals**
249
- - Use URL inspection to verify that portal public pages are correctly indexed
250
- - Track keyword positions for internal knowledge base articles
251
- - Automate sitemap submission after content management system publishes
252
-
253
- **Recommended integration pattern**
254
- - Use **service accounts** in CI/CD — no user interaction, no token expiry
255
- - Run `querySearchAnalyticsAll` in **weekly scheduled jobs**
256
- - Feed data into `@power-seo/analytics` for **trend analysis** and **ranking insights**
257
- - Export results as **JSON** to Jira/Notion/Slack for content team review
258
-
259
- ---
260
-
261
- ## Scope and Limitations
262
-
263
- **This package does**
264
- - ✅ Authenticate with GSC API via OAuth2 and service account
265
- - Fetch search analytics data with auto-pagination
266
- - Inspect URL indexing status, mobile usability, and rich result status
267
- - Submit, list, and delete sitemaps from verified GSC properties
268
-
269
- **This package does not**
270
- - ❌ Verify properties in GSC — property must already be verified
271
- - Provide reporting UIuse `@power-seo/analytics` for dashboards
272
- - Access Google Analytics data — use `@power-seo/tracking` for GA4 API access
273
-
274
- ---
275
-
276
- ## API Reference
277
-
278
- ### `createTokenManager(config)`
279
-
280
- | Parameter | Type | Description |
281
- |-----------|------|-------------|
282
- | `config.type` | `'oauth' \| 'service-account'` | Authentication method |
283
- | `config.clientId` | `string` | Google OAuth2 client ID (OAuth2 only) |
284
- | `config.clientSecret` | `string` | Google OAuth2 client secret (OAuth2 only) |
285
- | `config.refreshToken` | `string` | OAuth2 refresh token (OAuth2 only) |
286
- | `config.clientEmail` | `string` | Service account email (service account only) |
287
- | `config.privateKey` | `string` | Service account private key PEM (service account only) |
288
- | `config.scopes` | `string[]` | OAuth2 scopes to request (service account only) |
289
-
290
- Returns `TokenManager`: `{ getAccessToken(): Promise<TokenResult> }`.
291
-
292
- ### `createGSCClient(config)`
293
-
294
- | Parameter | Type | Description |
295
- |-----------|------|-------------|
296
- | `config.siteUrl` | `string` | Verified GSC property URL (`sc-domain:` prefix for domain properties) |
297
- | `config.tokenManager` | `TokenManager` | Token manager from `createTokenManager` |
298
-
299
- Returns `GSCClient`.
300
-
301
- ### `querySearchAnalytics(client, request)`
302
-
303
- | Parameter | Type | Default | Description |
304
- |-----------|------|---------|-------------|
305
- | `request.startDate` | `string` | required | `YYYY-MM-DD` |
306
- | `request.endDate` | `string` | required | `YYYY-MM-DD` |
307
- | `request.dimensions` | `Dimension[]` | `[]` | `'query'`, `'page'`, `'country'`, `'device'`, `'date'`, `'searchAppearance'` |
308
- | `request.searchType` | `SearchType` | `'web'` | `'web'`, `'image'`, `'video'`, `'news'` |
309
- | `request.rowLimit` | `number` | `1000` | Rows per request (max 25,000) |
310
- | `request.dimensionFilterGroups` | `object[]` | `[]` | Filter groups to narrow results |
311
-
312
- ### `querySearchAnalyticsAll(client, request)`
313
-
314
- Same as `querySearchAnalytics` but `rowLimit` and `startRow` are managed automatically. Returns `Promise<SearchAnalyticsRow[]>`.
315
-
316
- ### `inspectUrl(client, url)` / `inspectUrlDirect(client, url)`
317
-
318
- Returns `Promise<InspectionResult>`: `{ verdict, indexingState, lastCrawlTime, mobileUsabilityResult, richResultsResult, ... }`.
319
-
320
- ### `listSitemaps(client)` / `submitSitemap(client, url)` / `deleteSitemap(client, url)`
321
-
322
- `listSitemaps` returns `Promise<SitemapListResponse>`. `submitSitemap` and `deleteSitemap` return `Promise<void>`.
323
-
324
- ### Types
325
-
326
- ```ts
327
- import type {
328
- OAuthCredentials, ServiceAccountCredentials, TokenResult, TokenManager,
329
- GSCClientConfig, GSCClient,
330
- SearchType, Dimension,
331
- SearchAnalyticsRequest, SearchAnalyticsRow, SearchAnalyticsResponse,
332
- InspectionResult,
333
- SitemapEntry, SitemapListResponse,
334
- } from '@power-seo/search-console';
335
- ```
336
-
337
- ---
338
-
339
- ## Contributing
340
-
341
- - Issues: [github.com/cybercraftbd/power-seo/issues](https://github.com/cybercraftbd/power-seo/issues)
342
- - PRs: [github.com/cybercraftbd/power-seo/pulls](https://github.com/cybercraftbd/power-seo/pulls)
343
- - Development setup:
344
- 1. `pnpm i`
345
- 2. `pnpm build`
346
- 3. `pnpm test`
347
-
348
- **Release workflow**
349
- - `npm version patch|minor|major`
350
- - `npm publish --access public`
351
-
352
- ---
353
-
354
- ## About CyberCraft Bangladesh
355
-
356
- **CyberCraft Bangladesh** is a Bangladesh-based enterprise-grade software engineering company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies.
357
-
358
- | | |
359
- |---|---|
360
- | **Website** | [ccbd.dev](https://ccbd.dev) |
361
- | **GitHub** | [github.com/cybercraftbd](https://github.com/cybercraftbd) |
362
- | **npm Organization** | [npmjs.com/org/power-seo](https://www.npmjs.com/org/power-seo) |
363
- | **Email** | [info@ccbd.dev](mailto:info@ccbd.dev) |
364
-
365
- ---
366
-
367
- ## License
368
-
369
- **MIT**
370
-
371
- ---
372
-
373
- ## Keywords
374
-
375
- ```text
376
- seo, google-search-console, gsc, search-analytics, url-inspection, sitemap, oauth2, service-account, typescript, nextjs, analytics, keyword-tracking, click-through-rate, impressions
377
- ```
1
+ # @power-seo/search-console — Google Search Console API Client for TypeScript — OAuth2, Service Account, URL Inspection & Auto-Paginated Analytics
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@power-seo/search-console?style=flat-square)](https://www.npmjs.com/package/@power-seo/search-console)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@power-seo/search-console?style=flat-square)](https://www.npmjs.com/package/@power-seo/search-console)
5
+ [![MIT License](https://img.shields.io/npm/l/@power-seo/search-console?style=flat-square)](../../LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?style=flat-square)](https://www.typescriptlang.org/)
7
+ [![Tree-shakeable](https://img.shields.io/badge/tree--shakeable-yes-brightgreen?style=flat-square)](#)
8
+
9
+ ---
10
+
11
+ ## Overview
12
+
13
+ **@power-seo/search-console** is a production-ready Google Search Console API client for TypeScript that helps you query search analytics, inspect URLs, and manage sitemaps — without writing OAuth2 token refresh boilerplate or pagination loops.
14
+
15
+ **What it does**
16
+
17
+ - ✅ **OAuth2 and service account auth** — `createTokenManager()` handles token refresh cycles and JWT signing automatically
18
+ - ✅ **Search analytics queries** — clicks, impressions, CTR, and position by query, page, country, device, date
19
+ - ✅ **Auto-paginated full fetch** — `querySearchAnalyticsAll()` transparently pages through the 25,000-row GSC API limit
20
+ - ✅ **URL inspection** — indexing verdict, last crawl time, canonical URL, mobile usability, rich result status
21
+ - ✅ **Sitemap management** — list, submit, and delete sitemaps from verified GSC properties
22
+
23
+ **What it is not**
24
+
25
+ - ❌ **Not a reporting dashboard** — returns raw data; use `@power-seo/analytics` to merge and visualize
26
+ - ❌ **Not a site verification tool** — requires a property already verified in Google Search Console
27
+
28
+ **Recommended for**
29
+
30
+ - **SEO automation pipelines**, **CI/CD keyword tracking**, **SaaS analytics dashboards**, and **reporting scripts** using GSC data
31
+
32
+ ---
33
+
34
+ ## Why @power-seo/search-console Matters
35
+
36
+ **The problem**
37
+
38
+ - **OAuth2 token refresh is complex** — access tokens expire every hour; service account JWT signing requires crypto operations
39
+ - **GSC returns only 25,000 rows per request** sites with many queries need pagination loops that every team reimplements
40
+ - **URL inspection data is inaccessible without auth** no simple way to check indexing status programmatically
41
+
42
+ **Why developers care**
43
+
44
+ - **SEO:** Programmatic access to real click/impression data enables data-driven content decisions
45
+ - **Performance:** Auto-paginated `querySearchAnalyticsAll` eliminates boilerplate that slows down analytics pipelines
46
+ - **UX:** URL inspection in CI enables instant indexing health checks after deployments
47
+
48
+ ---
49
+
50
+ ## Key Features
51
+
52
+ - **OAuth2 authentication** — `createTokenManager({ type: 'oauth' })` handles token refresh automatically
53
+ - **Service account JWT authentication** — `createTokenManager({ type: 'service-account' })` signs JWTs for server-to-server access
54
+ - **Low-level auth primitives** — `exchangeRefreshToken()` and `getServiceAccountToken()` for custom auth flows
55
+ - **Typed GSC client** — `createGSCClient(config)` returns a `GSCClient` scoped to a specific site URL
56
+ - **Search analytics** — `querySearchAnalytics()` supports all 6 dimensions: `query`, `page`, `country`, `device`, `date`, `searchAppearance`
57
+ - **All search types** — `web`, `image`, `video`, and `news` search types
58
+ - **Auto-paginated full fetch** — `querySearchAnalyticsAll()` merges all pages into a single `SearchAnalyticsRow[]` array
59
+ - **URL inspection** — `inspectUrl()` returns verdict, indexing state, last crawl time, mobile usability, and rich result status
60
+ - **Direct URL inspection** — `inspectUrlDirect()` for the direct URL inspection endpoint
61
+ - **Sitemap listing** — `listSitemaps()` with status, last download time, and error counts
62
+ - **Sitemap submission and deletion** — `submitSitemap()` and `deleteSitemap()`
63
+ - **Typed error handling** — `GSCApiError` class with `status`, `code`, and `message`
64
+ - **Type-safe throughout** — full TypeScript types for all request and response shapes
65
+
66
+ ---
67
+
68
+ ## Benefits of Using @power-seo/search-console
69
+
70
+ - **Faster analytics pipelines**: No token refresh boilerplate; token manager handles expiry transparently
71
+ - **Complete datasets**: `querySearchAnalyticsAll` fetches all rows regardless of API page limit
72
+ - **Safer integration**: `GSCApiError` structured error class enables reliable error handling in production
73
+ - **Faster delivery**: Connect to GSC in minutes; no OAuth2 library research required
74
+
75
+ ---
76
+
77
+ ## Quick Start
78
+
79
+ ```ts
80
+ import {
81
+ createTokenManager,
82
+ createGSCClient,
83
+ querySearchAnalyticsAll,
84
+ } from '@power-seo/search-console';
85
+
86
+ // 1. Create a token manager (OAuth2)
87
+ const tokenManager = createTokenManager({
88
+ type: 'oauth',
89
+ clientId: process.env.GSC_CLIENT_ID!,
90
+ clientSecret: process.env.GSC_CLIENT_SECRET!,
91
+ refreshToken: process.env.GSC_REFRESH_TOKEN!,
92
+ });
93
+
94
+ // 2. Create a client scoped to your site
95
+ const client = createGSCClient({
96
+ siteUrl: 'https://example.com',
97
+ tokenManager,
98
+ });
99
+
100
+ // 3. Fetch all search analytics data (auto-paginated)
101
+ const rows = await querySearchAnalyticsAll(client, {
102
+ startDate: '2026-01-01',
103
+ endDate: '2026-01-31',
104
+ dimensions: ['query', 'page'],
105
+ });
106
+
107
+ rows.forEach(({ keys, clicks, impressions, ctr, position }) => {
108
+ console.log(`Query: "${keys[0]}", Page: ${keys[1]}`);
109
+ console.log(` ${clicks} clicks, ${impressions} impressions, pos ${position.toFixed(1)}`);
110
+ });
111
+ ```
112
+
113
+ **What you should see**
114
+
115
+ - A merged `SearchAnalyticsRow[]` array with all query-page combinations for the date range
116
+ - `clicks`, `impressions`, `ctr`, and `position` for each row
117
+
118
+ ---
119
+
120
+ ## Installation
121
+
122
+ ```bash
123
+ npm i @power-seo/search-console
124
+ # or
125
+ yarn add @power-seo/search-console
126
+ # or
127
+ pnpm add @power-seo/search-console
128
+ # or
129
+ bun add @power-seo/search-console
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Framework Compatibility
135
+
136
+ **Supported**
137
+
138
+ - ✅ Next.js (App Router / Pages Router) — use in API routes or server actions
139
+ - Remix — use in loader functions and server-side actions
140
+ - ✅ Node.js 18+ — pure TypeScript client with `fetch` API
141
+ - CI/CD pipelinesrun as standalone Node scripts in GitHub Actions or similar
142
+
143
+ **Environment notes**
144
+
145
+ - **SSR/SSG:** Fully supportedall operations are server-side
146
+ - **Edge runtime:** Not supported (requires crypto for JWT signing)
147
+ - **Browser-only usage:** Not supportedexposes credentials; server-side only
148
+
149
+ ---
150
+
151
+ ## Use Cases
152
+
153
+ - **Automated keyword ranking reports** — fetch all queries weekly and diff against previous week
154
+ - **Indexing health monitoring** — `inspectUrl` after deployments to verify new pages are indexed
155
+ - **Content gap analysis** — merge GSC data with `@power-seo/analytics` to find high-impression, low-click pages
156
+ - **Sitemap automation** submit new sitemaps programmatically after content migrations
157
+ - **CI/CD SEO checks** fail pipelines when key pages drop below position threshold
158
+ - **Multi-site SaaS dashboards** aggregate GSC data across multiple client properties
159
+ - **Image and news search analytics** — query `image` and `news` search types separately
160
+ - **Country and device breakdowns** — segment click data by country or device for regional SEO
161
+
162
+ ---
163
+
164
+ ## Example (Before / After)
165
+
166
+ ```text
167
+ Before:
168
+ - Manual OAuth2 token refresh: 50+ lines of token exchange code per project
169
+ - Pagination loop: separate rowOffset counter, multiple fetch calls, manual array merging
170
+ - URL inspection: no programmatic access check Google Search Console UI manually
171
+
172
+ After (@power-seo/search-console):
173
+ - createTokenManager({ type: 'oauth', ... })tokens auto-refresh before every request
174
+ - querySearchAnalyticsAll(client, { dimensions: ['query'] }) one call, complete dataset
175
+ - inspectUrl(client, url) → { verdict: 'PASS', lastCrawlTime: '...', mobileUsabilityResult: {...} }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Implementation Best Practices
181
+
182
+ - **Use service accounts for CI/CD** service accounts don't expire and don't require user interaction
183
+ - **Cache `querySearchAnalyticsAll` results** GSC data is delayed by ~2 days; daily fetches are sufficient
184
+ - **Use `sc-domain:` prefix for domain properties** — e.g. `sc-domain:example.com` for domain-level verification
185
+ - **Add service account email as GSC user** — Settings → Users and permissions → Add user
186
+ - **Handle `GSCApiError` status 429** implement exponential backoff for rate-limited requests
187
+
188
+ ---
189
+
190
+ ## Architecture Overview
191
+
192
+ **Where it runs**
193
+
194
+ - **Build-time**: Fetch GSC data for static site revalidation or build-time content ranking
195
+ - **Runtime**: Query analytics in serverless functions for real-time dashboard data
196
+ - **CI/CD**: Check indexing status and keyword positions in automated pipelines
197
+
198
+ **Data flow**
199
+
200
+ 1. **Input**: OAuth2 credentials or service account key + site URL + query parameters
201
+ 2. **Analysis**: Token manager handles auth; client sends typed requests to GSC API
202
+ 3. **Output**: Typed `SearchAnalyticsRow[]`, `InspectionResult`, `SitemapListResponse`
203
+ 4. **Action**: Feed into `@power-seo/analytics` for dashboards, or export as CSV/JSON reports
204
+
205
+ ---
206
+
207
+ ## Features Comparison with Popular Packages
208
+
209
+ | Capability | google-auth-library | googleapis | custom fetch | @power-seo/search-console |
210
+ | ---------------------------------- | ------------------: | ---------: | -----------: | ------------------------: |
211
+ | OAuth2 token auto-refresh | ✅ | ✅ | ❌ | ✅ |
212
+ | Service account JWT signing | ✅ | ✅ | | ✅ |
213
+ | Auto-paginated analytics fetch | | ❌ | ❌ | |
214
+ | Typed GSC-specific response shapes | ❌ | ⚠️ | | ✅ |
215
+ | URL inspection support | | ⚠️ | ❌ | |
216
+ | Sitemap management | | ⚠️ | ❌ | |
217
+
218
+ ---
219
+
220
+ ## @power-seo Ecosystem
221
+
222
+ All 17 packages are independently installableuse only what you need.
223
+
224
+ | Package | Install | Description |
225
+ | ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------- |
226
+ | [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants |
227
+ | [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components meta, Open Graph, Twitter Card, breadcrumbs |
228
+ | [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
229
+ | [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 20 builders + 18 React components |
230
+ | [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components |
231
+ | [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
232
+ | [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators |
233
+ | [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation |
234
+ | [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters |
235
+ | [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis orphan detection, suggestions, equity scoring |
236
+ | [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine meta, content, structure, performance rules |
237
+ | [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO alt text, lazy loading, format analysis, image sitemaps |
238
+ | [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks |
239
+ | [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard |
240
+ | [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection |
241
+ | [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination |
242
+ | [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management |
243
+
244
+ ### Ecosystem vs alternatives
245
+
246
+ | Need | Common approach | @power-seo approach |
247
+ | -------------------- | ---------------------- | --------------------------------------------------- |
248
+ | GSC search analytics | `googleapis` (verbose) | `@power-seo/search-console` typed, auto-paginated |
249
+ | Analytics dashboards | Google Looker Studio | `@power-seo/analytics` merge GSC + audit data |
250
+ | Sitemap submission | Manual GSC UI | `@power-seo/search-console` `submitSitemap()` |
251
+ | SEO auditing | Third-party tools | `@power-seo/audit` in-code, CI-friendly |
252
+
253
+ ---
254
+
255
+ ## Enterprise Integration
256
+
257
+ **Multi-tenant SaaS**
258
+
259
+ - **Per-client GSC properties**: Instantiate one `GSCClient` per client site; use service accounts for automation
260
+ - **Aggregated dashboards**: Loop over multiple `siteUrl` values and merge analytics into a multi-site report
261
+ - **Scheduled data sync**: Run `querySearchAnalyticsAll` on a daily cron for all client properties
262
+
263
+ **ERP / internal portals**
264
+
265
+ - Use URL inspection to verify that portal public pages are correctly indexed
266
+ - Track keyword positions for internal knowledge base articles
267
+ - Automate sitemap submission after content management system publishes
268
+
269
+ **Recommended integration pattern**
270
+
271
+ - Use **service accounts** in CI/CD no user interaction, no token expiry
272
+ - Run `querySearchAnalyticsAll` in **weekly scheduled jobs**
273
+ - Feed data into `@power-seo/analytics` for **trend analysis** and **ranking insights**
274
+ - Export results as **JSON** to Jira/Notion/Slack for content team review
275
+
276
+ ---
277
+
278
+ ## Scope and Limitations
279
+
280
+ **This package does**
281
+
282
+ - Authenticate with GSC API via OAuth2 and service account
283
+ - Fetch search analytics data with auto-pagination
284
+ - Inspect URL indexing status, mobile usability, and rich result status
285
+ - Submit, list, and delete sitemaps from verified GSC properties
286
+
287
+ **This package does not**
288
+
289
+ - ❌ Verify properties in GSC — property must already be verified
290
+ - Provide reporting UI — use `@power-seo/analytics` for dashboards
291
+ - ❌ Access Google Analytics data — use `@power-seo/tracking` for GA4 API access
292
+
293
+ ---
294
+
295
+ ## API Reference
296
+
297
+ ### `createTokenManager(config)`
298
+
299
+ | Parameter | Type | Description |
300
+ | --------------------- | ------------------------------ | ------------------------------------------------------ |
301
+ | `config.type` | `'oauth' \| 'service-account'` | Authentication method |
302
+ | `config.clientId` | `string` | Google OAuth2 client ID (OAuth2 only) |
303
+ | `config.clientSecret` | `string` | Google OAuth2 client secret (OAuth2 only) |
304
+ | `config.refreshToken` | `string` | OAuth2 refresh token (OAuth2 only) |
305
+ | `config.clientEmail` | `string` | Service account email (service account only) |
306
+ | `config.privateKey` | `string` | Service account private key PEM (service account only) |
307
+ | `config.scopes` | `string[]` | OAuth2 scopes to request (service account only) |
308
+
309
+ Returns `TokenManager`: `{ getAccessToken(): Promise<TokenResult> }`.
310
+
311
+ ### `createGSCClient(config)`
312
+
313
+ | Parameter | Type | Description |
314
+ | --------------------- | -------------- | --------------------------------------------------------------------- |
315
+ | `config.siteUrl` | `string` | Verified GSC property URL (`sc-domain:` prefix for domain properties) |
316
+ | `config.tokenManager` | `TokenManager` | Token manager from `createTokenManager` |
317
+
318
+ Returns `GSCClient`.
319
+
320
+ ### `querySearchAnalytics(client, request)`
321
+
322
+ | Parameter | Type | Default | Description |
323
+ | ------------------------------- | ------------- | -------- | ---------------------------------------------------------------------------- |
324
+ | `request.startDate` | `string` | required | `YYYY-MM-DD` |
325
+ | `request.endDate` | `string` | required | `YYYY-MM-DD` |
326
+ | `request.dimensions` | `Dimension[]` | `[]` | `'query'`, `'page'`, `'country'`, `'device'`, `'date'`, `'searchAppearance'` |
327
+ | `request.searchType` | `SearchType` | `'web'` | `'web'`, `'image'`, `'video'`, `'news'` |
328
+ | `request.rowLimit` | `number` | `1000` | Rows per request (max 25,000) |
329
+ | `request.dimensionFilterGroups` | `object[]` | `[]` | Filter groups to narrow results |
330
+
331
+ ### `querySearchAnalyticsAll(client, request)`
332
+
333
+ Same as `querySearchAnalytics` but `rowLimit` and `startRow` are managed automatically. Returns `Promise<SearchAnalyticsRow[]>`.
334
+
335
+ ### `inspectUrl(client, url)` / `inspectUrlDirect(client, url)`
336
+
337
+ Returns `Promise<InspectionResult>`: `{ verdict, indexingState, lastCrawlTime, mobileUsabilityResult, richResultsResult, ... }`.
338
+
339
+ ### `listSitemaps(client)` / `submitSitemap(client, url)` / `deleteSitemap(client, url)`
340
+
341
+ `listSitemaps` returns `Promise<SitemapListResponse>`. `submitSitemap` and `deleteSitemap` return `Promise<void>`.
342
+
343
+ ### Types
344
+
345
+ ```ts
346
+ import type {
347
+ OAuthCredentials,
348
+ ServiceAccountCredentials,
349
+ TokenResult,
350
+ TokenManager,
351
+ GSCClientConfig,
352
+ GSCClient,
353
+ SearchType,
354
+ Dimension,
355
+ SearchAnalyticsRequest,
356
+ SearchAnalyticsRow,
357
+ SearchAnalyticsResponse,
358
+ InspectionResult,
359
+ SitemapEntry,
360
+ SitemapListResponse,
361
+ } from '@power-seo/search-console';
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Contributing
367
+
368
+ - Issues: [github.com/cybercraftbd/power-seo/issues](https://github.com/cybercraftbd/power-seo/issues)
369
+ - PRs: [github.com/cybercraftbd/power-seo/pulls](https://github.com/cybercraftbd/power-seo/pulls)
370
+ - Development setup:
371
+ 1. `pnpm i`
372
+ 2. `pnpm build`
373
+ 3. `pnpm test`
374
+
375
+ **Release workflow**
376
+
377
+ - `npm version patch|minor|major`
378
+ - `npm publish --access public`
379
+
380
+ ---
381
+
382
+ ## About CyberCraft Bangladesh
383
+
384
+ **CyberCraft Bangladesh** is a Bangladesh-based enterprise-grade software engineering company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies.
385
+
386
+ | | |
387
+ | -------------------- | -------------------------------------------------------------- |
388
+ | **Website** | [ccbd.dev](https://ccbd.dev) |
389
+ | **GitHub** | [github.com/cybercraftbd](https://github.com/cybercraftbd) |
390
+ | **npm Organization** | [npmjs.com/org/power-seo](https://www.npmjs.com/org/power-seo) |
391
+ | **Email** | [info@ccbd.dev](mailto:info@ccbd.dev) |
392
+
393
+ ---
394
+
395
+ ## License
396
+
397
+ **MIT**
398
+
399
+ ---
400
+
401
+ ## Keywords
402
+
403
+ ```text
404
+ seo, google-search-console, gsc, search-analytics, url-inspection, sitemap, oauth2, service-account, typescript, nextjs, analytics, keyword-tracking, click-through-rate, impressions
405
+ ```
package/dist/index.cjs CHANGED
@@ -175,10 +175,10 @@ async function querySearchAnalytics(client, request) {
175
175
  ...request,
176
176
  rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT)
177
177
  };
178
- return client.request(
179
- `/sites/${siteUrl}/searchAnalytics/query`,
180
- { method: "POST", body }
181
- );
178
+ return client.request(`/sites/${siteUrl}/searchAnalytics/query`, {
179
+ method: "POST",
180
+ body
181
+ });
182
182
  }
183
183
  async function querySearchAnalyticsAll(client, request) {
184
184
  const allRows = [];
@@ -207,13 +207,10 @@ async function inspectUrl(client, request) {
207
207
  siteUrl: client.siteUrl,
208
208
  languageCode: request.languageCode ?? "en"
209
209
  };
210
- const token = await client.request(
211
- "",
212
- {
213
- method: "POST",
214
- body
215
- }
216
- );
210
+ const token = await client.request("", {
211
+ method: "POST",
212
+ body
213
+ });
217
214
  return token.inspectionResult;
218
215
  }
219
216
  async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode) {
@@ -241,26 +238,18 @@ async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode)
241
238
  // src/sitemaps.ts
242
239
  async function listSitemaps(client) {
243
240
  const siteUrl = encodeURIComponent(client.siteUrl);
244
- const response = await client.request(
245
- `/sites/${siteUrl}/sitemaps`
246
- );
241
+ const response = await client.request(`/sites/${siteUrl}/sitemaps`);
247
242
  return response.sitemap ?? [];
248
243
  }
249
244
  async function submitSitemap(client, feedpath) {
250
245
  const siteUrl = encodeURIComponent(client.siteUrl);
251
246
  const encodedFeed = encodeURIComponent(feedpath);
252
- await client.request(
253
- `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
254
- { method: "PUT" }
255
- );
247
+ await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "PUT" });
256
248
  }
257
249
  async function deleteSitemap(client, feedpath) {
258
250
  const siteUrl = encodeURIComponent(client.siteUrl);
259
251
  const encodedFeed = encodeURIComponent(feedpath);
260
- await client.request(
261
- `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
262
- { method: "DELETE" }
263
- );
252
+ await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "DELETE" });
264
253
  }
265
254
 
266
255
  exports.GSCApiError = GSCApiError;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":["createTokenBucket","getWaitTime","sleep","consumeToken"],"mappings":"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","file":"index.cjs","sourcesContent":["// ============================================================================\r\n// @power-seo/search-console — Types\r\n// ============================================================================\r\n\r\n// --- Auth Types ---\r\n\r\nexport interface OAuthCredentials {\r\n clientId: string;\r\n clientSecret: string;\r\n refreshToken: string;\r\n}\r\n\r\nexport interface ServiceAccountCredentials {\r\n clientEmail: string;\r\n privateKeyId: string;\r\n signJwt: (payload: JwtPayload) => Promise<string>;\r\n}\r\n\r\nexport interface JwtPayload {\r\n iss: string;\r\n scope: string;\r\n aud: string;\r\n exp: number;\r\n iat: number;\r\n}\r\n\r\nexport interface TokenResult {\r\n accessToken: string;\r\n expiresAt: number;\r\n}\r\n\r\nexport interface TokenManager {\r\n getToken: () => Promise<string>;\r\n invalidate: () => void;\r\n}\r\n\r\n// --- Client Types ---\r\n\r\nexport interface GSCClientConfig {\r\n auth: TokenManager;\r\n siteUrl: string;\r\n rateLimitPerMinute?: number;\r\n maxRetries?: number;\r\n baseUrl?: string;\r\n}\r\n\r\nexport interface GSCClient {\r\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\r\n siteUrl: string;\r\n}\r\n\r\nexport interface RequestOptions {\r\n method?: string;\r\n body?: unknown;\r\n signal?: globalThis.AbortSignal;\r\n}\r\n\r\n// --- Error ---\r\n\r\nexport class GSCApiError extends Error {\r\n readonly status: number;\r\n readonly code: string;\r\n readonly retryable: boolean;\r\n\r\n constructor(message: string, status: number, code: string) {\r\n super(message);\r\n this.name = 'GSCApiError';\r\n this.status = status;\r\n this.code = code;\r\n this.retryable = status === 429 || status >= 500;\r\n }\r\n}\r\n\r\n// --- Analytics Types ---\r\n\r\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\r\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\r\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\r\nexport type DataState = 'all' | 'final';\r\n\r\nexport interface DimensionFilter {\r\n dimension: Dimension;\r\n operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';\r\n expression: string;\r\n}\r\n\r\nexport interface DimensionFilterGroup {\r\n groupType?: 'and';\r\n filters: DimensionFilter[];\r\n}\r\n\r\nexport interface SearchAnalyticsRequest {\r\n startDate: string;\r\n endDate: string;\r\n dimensions?: Dimension[];\r\n searchType?: SearchType;\r\n dimensionFilterGroups?: DimensionFilterGroup[];\r\n aggregationType?: AggregationType;\r\n rowLimit?: number;\r\n startRow?: number;\r\n dataState?: DataState;\r\n}\r\n\r\nexport interface SearchAnalyticsRow {\r\n keys: string[];\r\n clicks: number;\r\n impressions: number;\r\n ctr: number;\r\n position: number;\r\n}\r\n\r\nexport interface SearchAnalyticsResponse {\r\n rows: SearchAnalyticsRow[];\r\n responseAggregationType: string;\r\n}\r\n\r\n// --- Inspection Types ---\r\n\r\nexport interface InspectionRequest {\r\n inspectionUrl: string;\r\n languageCode?: string;\r\n}\r\n\r\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\r\nexport type CrawlState = 'SUCCESSFUL' | 'NOT_FOUND' | 'SERVER_ERROR' | 'ROBOTS_BLOCKED' | 'REDIRECT_ERROR' | 'ACCESS_DENIED' | 'ACCESS_FORBIDDEN';\r\nexport type IndexState = 'INDEXED' | 'NOT_INDEXED' | 'SUBMITTED_AND_INDEXED' | 'CRAWLED_NOT_INDEXED' | 'DISCOVERED_NOT_INDEXED' | 'PAGE_WITH_REDIRECT';\r\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\r\n\r\nexport interface IndexStatusResult {\r\n verdict: VerdictState;\r\n coverageState: IndexState;\r\n robotsTxtState: RobotsTxtState;\r\n indexingState: string;\r\n lastCrawlTime?: string;\r\n pageFetchState?: CrawlState;\r\n referringUrls?: string[];\r\n sitemap?: string[];\r\n}\r\n\r\nexport interface MobileUsabilityResult {\r\n verdict: VerdictState;\r\n issues?: Array<{ issueType: string; severity: string; message: string }>;\r\n}\r\n\r\nexport interface RichResultsResult {\r\n verdict: VerdictState;\r\n detectedItems?: Array<{ richResultType: string; items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }> }>;\r\n}\r\n\r\nexport interface InspectionResult {\r\n inspectionResultLink: string;\r\n indexStatusResult: IndexStatusResult;\r\n mobileUsabilityResult?: MobileUsabilityResult;\r\n richResultsResult?: RichResultsResult;\r\n}\r\n\r\n// --- Sitemap Types ---\r\n\r\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\r\n\r\nexport interface SitemapEntry {\r\n path: string;\r\n lastSubmitted?: string;\r\n isPending: boolean;\r\n isSitemapsIndex: boolean;\r\n type: SitemapType;\r\n lastDownloaded?: string;\r\n warnings: number;\r\n errors: number;\r\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\r\n}\r\n\r\nexport interface SitemapListResponse {\r\n sitemap: SitemapEntry[];\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Auth\r\n// ============================================================================\r\n\r\nimport type {\r\n OAuthCredentials,\r\n ServiceAccountCredentials,\r\n TokenResult,\r\n TokenManager,\r\n} from './types.js';\r\nimport { GSCApiError } from './types.js';\r\n\r\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\r\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\r\nconst TOKEN_BUFFER_MS = 60_000;\r\n\r\nexport async function exchangeRefreshToken(\r\n credentials: OAuthCredentials,\r\n): Promise<TokenResult> {\r\n const body = new globalThis.URLSearchParams({\r\n client_id: credentials.clientId,\r\n client_secret: credentials.clientSecret,\r\n refresh_token: credentials.refreshToken,\r\n grant_type: 'refresh_token',\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Token exchange failed: ${text}`,\r\n response.status,\r\n 'TOKEN_EXCHANGE_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport async function getServiceAccountToken(\r\n credentials: ServiceAccountCredentials,\r\n): Promise<TokenResult> {\r\n const now = Math.floor(Date.now() / 1000);\r\n const payload = {\r\n iss: credentials.clientEmail,\r\n scope: GSC_SCOPE,\r\n aud: TOKEN_URL,\r\n exp: now + 3600,\r\n iat: now,\r\n };\r\n\r\n const assertion = await credentials.signJwt(payload);\r\n\r\n const body = new globalThis.URLSearchParams({\r\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\r\n assertion,\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Service account token failed: ${text}`,\r\n response.status,\r\n 'SERVICE_ACCOUNT_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport function createTokenManager(\r\n fetchToken: () => Promise<TokenResult>,\r\n): TokenManager {\r\n let cached: TokenResult | null = null;\r\n let pending: Promise<string> | null = null;\r\n\r\n return {\r\n async getToken(): Promise<string> {\r\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\r\n return cached.accessToken;\r\n }\r\n\r\n if (pending) {\r\n return pending;\r\n }\r\n\r\n pending = fetchToken().then((result) => {\r\n cached = result;\r\n pending = null;\r\n return result.accessToken;\r\n });\r\n\r\n return pending;\r\n },\r\n\r\n invalidate(): void {\r\n cached = null;\r\n pending = null;\r\n },\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Client\r\n// ============================================================================\r\n\r\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\r\nimport { GSCApiError } from './types.js';\r\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\r\n\r\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\r\nconst DEFAULT_RATE_LIMIT = 1200;\r\nconst DEFAULT_MAX_RETRIES = 3;\r\n\r\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\r\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\r\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\r\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\r\n\r\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\r\n const waitTime = getWaitTime(bucket);\r\n if (waitTime > 0) {\r\n await sleep(waitTime);\r\n }\r\n consumeToken(bucket);\r\n\r\n const token = await config.auth.getToken();\r\n const url = `${baseUrl}${path}`;\r\n\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n const response = await globalThis.fetch(url, {\r\n method: options?.method ?? 'GET',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: options?.body ? JSON.stringify(options.body) : undefined,\r\n signal: options?.signal,\r\n });\r\n\r\n if (response.ok) {\r\n return (await response.json()) as T;\r\n }\r\n\r\n const text = await response.text();\r\n const error = new GSCApiError(\r\n `GSC API error: ${response.status} ${text}`,\r\n response.status,\r\n `HTTP_${response.status}`,\r\n );\r\n\r\n if (!error.retryable || attempt === maxRetries) {\r\n throw error;\r\n }\r\n\r\n lastError = error;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } catch (err) {\r\n if (err instanceof GSCApiError) {\r\n if (!err.retryable || attempt === maxRetries) {\r\n throw err;\r\n }\r\n lastError = err;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\r\n }\r\n\r\n return {\r\n request,\r\n siteUrl: config.siteUrl,\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Analytics\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SearchAnalyticsRequest,\r\n SearchAnalyticsResponse,\r\n SearchAnalyticsRow,\r\n} from './types.js';\r\n\r\nconst MAX_ROW_LIMIT = 25000;\r\nconst DEFAULT_ROW_LIMIT = 1000;\r\n\r\nexport async function querySearchAnalytics(\r\n client: GSCClient,\r\n request: SearchAnalyticsRequest,\r\n): Promise<SearchAnalyticsResponse> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const body = {\r\n ...request,\r\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\r\n };\r\n\r\n return client.request<SearchAnalyticsResponse>(\r\n `/sites/${siteUrl}/searchAnalytics/query`,\r\n { method: 'POST', body },\r\n );\r\n}\r\n\r\nexport async function querySearchAnalyticsAll(\r\n client: GSCClient,\r\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\r\n): Promise<SearchAnalyticsRow[]> {\r\n const allRows: SearchAnalyticsRow[] = [];\r\n let startRow = 0;\r\n\r\n while (true) {\r\n const response = await querySearchAnalytics(client, {\r\n ...request,\r\n rowLimit: MAX_ROW_LIMIT,\r\n startRow,\r\n });\r\n\r\n const rows = response.rows ?? [];\r\n allRows.push(...rows);\r\n\r\n if (rows.length < MAX_ROW_LIMIT) {\r\n break;\r\n }\r\n\r\n startRow += rows.length;\r\n }\r\n\r\n return allRows;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — URL Inspection\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n InspectionRequest,\r\n InspectionResult,\r\n} from './types.js';\r\n\r\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\r\n\r\nexport async function inspectUrl(\r\n client: GSCClient,\r\n request: InspectionRequest,\r\n): Promise<InspectionResult> {\r\n const body = {\r\n inspectionUrl: request.inspectionUrl,\r\n siteUrl: client.siteUrl,\r\n languageCode: request.languageCode ?? 'en',\r\n };\r\n\r\n const token = await client.request<{ inspectionResult: InspectionResult }>(\r\n '',\r\n {\r\n method: 'POST',\r\n body,\r\n },\r\n );\r\n\r\n // The URL Inspection API has a different base URL, so we use a direct fetch.\r\n // However, since the client handles auth and retries, we override for the standard path approach.\r\n // The API endpoint doesn't follow the webmasters/v3 pattern.\r\n // We'll use the client's request method with a custom approach.\r\n\r\n // Actually, let's do a direct fetch since the base URL differs:\r\n return token.inspectionResult;\r\n}\r\n\r\nexport async function inspectUrlDirect(\r\n getToken: () => Promise<string>,\r\n siteUrl: string,\r\n inspectionUrl: string,\r\n languageCode?: string,\r\n): Promise<InspectionResult> {\r\n const token = await getToken();\r\n\r\n const response = await globalThis.fetch(INSPECTION_BASE, {\r\n method: 'POST',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n inspectionUrl,\r\n siteUrl,\r\n languageCode: languageCode ?? 'en',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\r\n }\r\n\r\n const data = (await response.json()) as { inspectionResult: InspectionResult };\r\n return data.inspectionResult;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Sitemaps\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SitemapEntry,\r\n SitemapListResponse,\r\n} from './types.js';\r\n\r\nexport async function listSitemaps(\r\n client: GSCClient,\r\n): Promise<SitemapEntry[]> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const response = await client.request<SitemapListResponse>(\r\n `/sites/${siteUrl}/sitemaps`,\r\n );\r\n return response.sitemap ?? [];\r\n}\r\n\r\nexport async function submitSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'PUT' },\r\n );\r\n}\r\n\r\nexport async function deleteSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'DELETE' },\r\n );\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":["createTokenBucket","getWaitTime","sleep","consumeToken"],"mappings":"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","file":"index.cjs","sourcesContent":["// ============================================================================\n// @power-seo/search-console — Types\n// ============================================================================\n\n// --- Auth Types ---\n\nexport interface OAuthCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface ServiceAccountCredentials {\n clientEmail: string;\n privateKeyId: string;\n signJwt: (payload: JwtPayload) => Promise<string>;\n}\n\nexport interface JwtPayload {\n iss: string;\n scope: string;\n aud: string;\n exp: number;\n iat: number;\n}\n\nexport interface TokenResult {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface TokenManager {\n getToken: () => Promise<string>;\n invalidate: () => void;\n}\n\n// --- Client Types ---\n\nexport interface GSCClientConfig {\n auth: TokenManager;\n siteUrl: string;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n baseUrl?: string;\n}\n\nexport interface GSCClient {\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\n siteUrl: string;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: globalThis.AbortSignal;\n}\n\n// --- Error ---\n\nexport class GSCApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'GSCApiError';\n this.status = status;\n this.code = code;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Analytics Types ---\n\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\nexport type DataState = 'all' | 'final';\n\nexport interface DimensionFilter {\n dimension: Dimension;\n operator:\n | 'equals'\n | 'notEquals'\n | 'contains'\n | 'notContains'\n | 'includingRegex'\n | 'excludingRegex';\n expression: string;\n}\n\nexport interface DimensionFilterGroup {\n groupType?: 'and';\n filters: DimensionFilter[];\n}\n\nexport interface SearchAnalyticsRequest {\n startDate: string;\n endDate: string;\n dimensions?: Dimension[];\n searchType?: SearchType;\n dimensionFilterGroups?: DimensionFilterGroup[];\n aggregationType?: AggregationType;\n rowLimit?: number;\n startRow?: number;\n dataState?: DataState;\n}\n\nexport interface SearchAnalyticsRow {\n keys: string[];\n clicks: number;\n impressions: number;\n ctr: number;\n position: number;\n}\n\nexport interface SearchAnalyticsResponse {\n rows: SearchAnalyticsRow[];\n responseAggregationType: string;\n}\n\n// --- Inspection Types ---\n\nexport interface InspectionRequest {\n inspectionUrl: string;\n languageCode?: string;\n}\n\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\nexport type CrawlState =\n | 'SUCCESSFUL'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'ROBOTS_BLOCKED'\n | 'REDIRECT_ERROR'\n | 'ACCESS_DENIED'\n | 'ACCESS_FORBIDDEN';\nexport type IndexState =\n | 'INDEXED'\n | 'NOT_INDEXED'\n | 'SUBMITTED_AND_INDEXED'\n | 'CRAWLED_NOT_INDEXED'\n | 'DISCOVERED_NOT_INDEXED'\n | 'PAGE_WITH_REDIRECT';\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\n\nexport interface IndexStatusResult {\n verdict: VerdictState;\n coverageState: IndexState;\n robotsTxtState: RobotsTxtState;\n indexingState: string;\n lastCrawlTime?: string;\n pageFetchState?: CrawlState;\n referringUrls?: string[];\n sitemap?: string[];\n}\n\nexport interface MobileUsabilityResult {\n verdict: VerdictState;\n issues?: Array<{ issueType: string; severity: string; message: string }>;\n}\n\nexport interface RichResultsResult {\n verdict: VerdictState;\n detectedItems?: Array<{\n richResultType: string;\n items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }>;\n }>;\n}\n\nexport interface InspectionResult {\n inspectionResultLink: string;\n indexStatusResult: IndexStatusResult;\n mobileUsabilityResult?: MobileUsabilityResult;\n richResultsResult?: RichResultsResult;\n}\n\n// --- Sitemap Types ---\n\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\n\nexport interface SitemapEntry {\n path: string;\n lastSubmitted?: string;\n isPending: boolean;\n isSitemapsIndex: boolean;\n type: SitemapType;\n lastDownloaded?: string;\n warnings: number;\n errors: number;\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\n}\n\nexport interface SitemapListResponse {\n sitemap: SitemapEntry[];\n}\n","// ============================================================================\n// @power-seo/search-console — Auth\n// ============================================================================\n\nimport type {\n OAuthCredentials,\n ServiceAccountCredentials,\n TokenResult,\n TokenManager,\n} from './types.js';\nimport { GSCApiError } from './types.js';\n\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\nconst TOKEN_BUFFER_MS = 60_000;\n\nexport async function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult> {\n const body = new globalThis.URLSearchParams({\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: credentials.refreshToken,\n grant_type: 'refresh_token',\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Token exchange failed: ${text}`,\n response.status,\n 'TOKEN_EXCHANGE_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport async function getServiceAccountToken(\n credentials: ServiceAccountCredentials,\n): Promise<TokenResult> {\n const now = Math.floor(Date.now() / 1000);\n const payload = {\n iss: credentials.clientEmail,\n scope: GSC_SCOPE,\n aud: TOKEN_URL,\n exp: now + 3600,\n iat: now,\n };\n\n const assertion = await credentials.signJwt(payload);\n\n const body = new globalThis.URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Service account token failed: ${text}`,\n response.status,\n 'SERVICE_ACCOUNT_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager {\n let cached: TokenResult | null = null;\n let pending: Promise<string> | null = null;\n\n return {\n async getToken(): Promise<string> {\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\n return cached.accessToken;\n }\n\n if (pending) {\n return pending;\n }\n\n pending = fetchToken().then((result) => {\n cached = result;\n pending = null;\n return result.accessToken;\n });\n\n return pending;\n },\n\n invalidate(): void {\n cached = null;\n pending = null;\n },\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Client\n// ============================================================================\n\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\nimport { GSCApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\nconst DEFAULT_RATE_LIMIT = 1200;\nconst DEFAULT_MAX_RETRIES = 3;\n\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n const token = await config.auth.getToken();\n const url = `${baseUrl}${path}`;\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await globalThis.fetch(url, {\n method: options?.method ?? 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: options?.signal,\n });\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new GSCApiError(\n `GSC API error: ${response.status} ${text}`,\n response.status,\n `HTTP_${response.status}`,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n if (err instanceof GSCApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\n }\n\n return {\n request,\n siteUrl: config.siteUrl,\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Analytics\n// ============================================================================\n\nimport type {\n GSCClient,\n SearchAnalyticsRequest,\n SearchAnalyticsResponse,\n SearchAnalyticsRow,\n} from './types.js';\n\nconst MAX_ROW_LIMIT = 25000;\nconst DEFAULT_ROW_LIMIT = 1000;\n\nexport async function querySearchAnalytics(\n client: GSCClient,\n request: SearchAnalyticsRequest,\n): Promise<SearchAnalyticsResponse> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const body = {\n ...request,\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\n };\n\n return client.request<SearchAnalyticsResponse>(`/sites/${siteUrl}/searchAnalytics/query`, {\n method: 'POST',\n body,\n });\n}\n\nexport async function querySearchAnalyticsAll(\n client: GSCClient,\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\n): Promise<SearchAnalyticsRow[]> {\n const allRows: SearchAnalyticsRow[] = [];\n let startRow = 0;\n\n while (true) {\n const response = await querySearchAnalytics(client, {\n ...request,\n rowLimit: MAX_ROW_LIMIT,\n startRow,\n });\n\n const rows = response.rows ?? [];\n allRows.push(...rows);\n\n if (rows.length < MAX_ROW_LIMIT) {\n break;\n }\n\n startRow += rows.length;\n }\n\n return allRows;\n}\n","// ============================================================================\n// @power-seo/search-console — URL Inspection\n// ============================================================================\n\nimport type { GSCClient, InspectionRequest, InspectionResult } from './types.js';\n\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\n\nexport async function inspectUrl(\n client: GSCClient,\n request: InspectionRequest,\n): Promise<InspectionResult> {\n const body = {\n inspectionUrl: request.inspectionUrl,\n siteUrl: client.siteUrl,\n languageCode: request.languageCode ?? 'en',\n };\n\n const token = await client.request<{ inspectionResult: InspectionResult }>('', {\n method: 'POST',\n body,\n });\n\n // The URL Inspection API has a different base URL, so we use a direct fetch.\n // However, since the client handles auth and retries, we override for the standard path approach.\n // The API endpoint doesn't follow the webmasters/v3 pattern.\n // We'll use the client's request method with a custom approach.\n\n // Actually, let's do a direct fetch since the base URL differs:\n return token.inspectionResult;\n}\n\nexport async function inspectUrlDirect(\n getToken: () => Promise<string>,\n siteUrl: string,\n inspectionUrl: string,\n languageCode?: string,\n): Promise<InspectionResult> {\n const token = await getToken();\n\n const response = await globalThis.fetch(INSPECTION_BASE, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n inspectionUrl,\n siteUrl,\n languageCode: languageCode ?? 'en',\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\n }\n\n const data = (await response.json()) as { inspectionResult: InspectionResult };\n return data.inspectionResult;\n}\n","// ============================================================================\n// @power-seo/search-console — Sitemaps\n// ============================================================================\n\nimport type { GSCClient, SitemapEntry, SitemapListResponse } from './types.js';\n\nexport async function listSitemaps(client: GSCClient): Promise<SitemapEntry[]> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const response = await client.request<SitemapListResponse>(`/sites/${siteUrl}/sitemaps`);\n return response.sitemap ?? [];\n}\n\nexport async function submitSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'PUT' });\n}\n\nexport async function deleteSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'DELETE' });\n}\n"]}
package/dist/index.js CHANGED
@@ -173,10 +173,10 @@ async function querySearchAnalytics(client, request) {
173
173
  ...request,
174
174
  rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT)
175
175
  };
176
- return client.request(
177
- `/sites/${siteUrl}/searchAnalytics/query`,
178
- { method: "POST", body }
179
- );
176
+ return client.request(`/sites/${siteUrl}/searchAnalytics/query`, {
177
+ method: "POST",
178
+ body
179
+ });
180
180
  }
181
181
  async function querySearchAnalyticsAll(client, request) {
182
182
  const allRows = [];
@@ -205,13 +205,10 @@ async function inspectUrl(client, request) {
205
205
  siteUrl: client.siteUrl,
206
206
  languageCode: request.languageCode ?? "en"
207
207
  };
208
- const token = await client.request(
209
- "",
210
- {
211
- method: "POST",
212
- body
213
- }
214
- );
208
+ const token = await client.request("", {
209
+ method: "POST",
210
+ body
211
+ });
215
212
  return token.inspectionResult;
216
213
  }
217
214
  async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode) {
@@ -239,26 +236,18 @@ async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode)
239
236
  // src/sitemaps.ts
240
237
  async function listSitemaps(client) {
241
238
  const siteUrl = encodeURIComponent(client.siteUrl);
242
- const response = await client.request(
243
- `/sites/${siteUrl}/sitemaps`
244
- );
239
+ const response = await client.request(`/sites/${siteUrl}/sitemaps`);
245
240
  return response.sitemap ?? [];
246
241
  }
247
242
  async function submitSitemap(client, feedpath) {
248
243
  const siteUrl = encodeURIComponent(client.siteUrl);
249
244
  const encodedFeed = encodeURIComponent(feedpath);
250
- await client.request(
251
- `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
252
- { method: "PUT" }
253
- );
245
+ await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "PUT" });
254
246
  }
255
247
  async function deleteSitemap(client, feedpath) {
256
248
  const siteUrl = encodeURIComponent(client.siteUrl);
257
249
  const encodedFeed = encodeURIComponent(feedpath);
258
- await client.request(
259
- `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
260
- { method: "DELETE" }
261
- );
250
+ await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "DELETE" });
262
251
  }
263
252
 
264
253
  export { GSCApiError, createGSCClient, createTokenManager, deleteSitemap, exchangeRefreshToken, getServiceAccountToken, inspectUrl, inspectUrlDirect, listSitemaps, querySearchAnalytics, querySearchAnalyticsAll, submitSitemap };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ============================================================================\r\n// @power-seo/search-console — Types\r\n// ============================================================================\r\n\r\n// --- Auth Types ---\r\n\r\nexport interface OAuthCredentials {\r\n clientId: string;\r\n clientSecret: string;\r\n refreshToken: string;\r\n}\r\n\r\nexport interface ServiceAccountCredentials {\r\n clientEmail: string;\r\n privateKeyId: string;\r\n signJwt: (payload: JwtPayload) => Promise<string>;\r\n}\r\n\r\nexport interface JwtPayload {\r\n iss: string;\r\n scope: string;\r\n aud: string;\r\n exp: number;\r\n iat: number;\r\n}\r\n\r\nexport interface TokenResult {\r\n accessToken: string;\r\n expiresAt: number;\r\n}\r\n\r\nexport interface TokenManager {\r\n getToken: () => Promise<string>;\r\n invalidate: () => void;\r\n}\r\n\r\n// --- Client Types ---\r\n\r\nexport interface GSCClientConfig {\r\n auth: TokenManager;\r\n siteUrl: string;\r\n rateLimitPerMinute?: number;\r\n maxRetries?: number;\r\n baseUrl?: string;\r\n}\r\n\r\nexport interface GSCClient {\r\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\r\n siteUrl: string;\r\n}\r\n\r\nexport interface RequestOptions {\r\n method?: string;\r\n body?: unknown;\r\n signal?: globalThis.AbortSignal;\r\n}\r\n\r\n// --- Error ---\r\n\r\nexport class GSCApiError extends Error {\r\n readonly status: number;\r\n readonly code: string;\r\n readonly retryable: boolean;\r\n\r\n constructor(message: string, status: number, code: string) {\r\n super(message);\r\n this.name = 'GSCApiError';\r\n this.status = status;\r\n this.code = code;\r\n this.retryable = status === 429 || status >= 500;\r\n }\r\n}\r\n\r\n// --- Analytics Types ---\r\n\r\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\r\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\r\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\r\nexport type DataState = 'all' | 'final';\r\n\r\nexport interface DimensionFilter {\r\n dimension: Dimension;\r\n operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';\r\n expression: string;\r\n}\r\n\r\nexport interface DimensionFilterGroup {\r\n groupType?: 'and';\r\n filters: DimensionFilter[];\r\n}\r\n\r\nexport interface SearchAnalyticsRequest {\r\n startDate: string;\r\n endDate: string;\r\n dimensions?: Dimension[];\r\n searchType?: SearchType;\r\n dimensionFilterGroups?: DimensionFilterGroup[];\r\n aggregationType?: AggregationType;\r\n rowLimit?: number;\r\n startRow?: number;\r\n dataState?: DataState;\r\n}\r\n\r\nexport interface SearchAnalyticsRow {\r\n keys: string[];\r\n clicks: number;\r\n impressions: number;\r\n ctr: number;\r\n position: number;\r\n}\r\n\r\nexport interface SearchAnalyticsResponse {\r\n rows: SearchAnalyticsRow[];\r\n responseAggregationType: string;\r\n}\r\n\r\n// --- Inspection Types ---\r\n\r\nexport interface InspectionRequest {\r\n inspectionUrl: string;\r\n languageCode?: string;\r\n}\r\n\r\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\r\nexport type CrawlState = 'SUCCESSFUL' | 'NOT_FOUND' | 'SERVER_ERROR' | 'ROBOTS_BLOCKED' | 'REDIRECT_ERROR' | 'ACCESS_DENIED' | 'ACCESS_FORBIDDEN';\r\nexport type IndexState = 'INDEXED' | 'NOT_INDEXED' | 'SUBMITTED_AND_INDEXED' | 'CRAWLED_NOT_INDEXED' | 'DISCOVERED_NOT_INDEXED' | 'PAGE_WITH_REDIRECT';\r\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\r\n\r\nexport interface IndexStatusResult {\r\n verdict: VerdictState;\r\n coverageState: IndexState;\r\n robotsTxtState: RobotsTxtState;\r\n indexingState: string;\r\n lastCrawlTime?: string;\r\n pageFetchState?: CrawlState;\r\n referringUrls?: string[];\r\n sitemap?: string[];\r\n}\r\n\r\nexport interface MobileUsabilityResult {\r\n verdict: VerdictState;\r\n issues?: Array<{ issueType: string; severity: string; message: string }>;\r\n}\r\n\r\nexport interface RichResultsResult {\r\n verdict: VerdictState;\r\n detectedItems?: Array<{ richResultType: string; items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }> }>;\r\n}\r\n\r\nexport interface InspectionResult {\r\n inspectionResultLink: string;\r\n indexStatusResult: IndexStatusResult;\r\n mobileUsabilityResult?: MobileUsabilityResult;\r\n richResultsResult?: RichResultsResult;\r\n}\r\n\r\n// --- Sitemap Types ---\r\n\r\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\r\n\r\nexport interface SitemapEntry {\r\n path: string;\r\n lastSubmitted?: string;\r\n isPending: boolean;\r\n isSitemapsIndex: boolean;\r\n type: SitemapType;\r\n lastDownloaded?: string;\r\n warnings: number;\r\n errors: number;\r\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\r\n}\r\n\r\nexport interface SitemapListResponse {\r\n sitemap: SitemapEntry[];\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Auth\r\n// ============================================================================\r\n\r\nimport type {\r\n OAuthCredentials,\r\n ServiceAccountCredentials,\r\n TokenResult,\r\n TokenManager,\r\n} from './types.js';\r\nimport { GSCApiError } from './types.js';\r\n\r\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\r\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\r\nconst TOKEN_BUFFER_MS = 60_000;\r\n\r\nexport async function exchangeRefreshToken(\r\n credentials: OAuthCredentials,\r\n): Promise<TokenResult> {\r\n const body = new globalThis.URLSearchParams({\r\n client_id: credentials.clientId,\r\n client_secret: credentials.clientSecret,\r\n refresh_token: credentials.refreshToken,\r\n grant_type: 'refresh_token',\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Token exchange failed: ${text}`,\r\n response.status,\r\n 'TOKEN_EXCHANGE_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport async function getServiceAccountToken(\r\n credentials: ServiceAccountCredentials,\r\n): Promise<TokenResult> {\r\n const now = Math.floor(Date.now() / 1000);\r\n const payload = {\r\n iss: credentials.clientEmail,\r\n scope: GSC_SCOPE,\r\n aud: TOKEN_URL,\r\n exp: now + 3600,\r\n iat: now,\r\n };\r\n\r\n const assertion = await credentials.signJwt(payload);\r\n\r\n const body = new globalThis.URLSearchParams({\r\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\r\n assertion,\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Service account token failed: ${text}`,\r\n response.status,\r\n 'SERVICE_ACCOUNT_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport function createTokenManager(\r\n fetchToken: () => Promise<TokenResult>,\r\n): TokenManager {\r\n let cached: TokenResult | null = null;\r\n let pending: Promise<string> | null = null;\r\n\r\n return {\r\n async getToken(): Promise<string> {\r\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\r\n return cached.accessToken;\r\n }\r\n\r\n if (pending) {\r\n return pending;\r\n }\r\n\r\n pending = fetchToken().then((result) => {\r\n cached = result;\r\n pending = null;\r\n return result.accessToken;\r\n });\r\n\r\n return pending;\r\n },\r\n\r\n invalidate(): void {\r\n cached = null;\r\n pending = null;\r\n },\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Client\r\n// ============================================================================\r\n\r\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\r\nimport { GSCApiError } from './types.js';\r\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\r\n\r\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\r\nconst DEFAULT_RATE_LIMIT = 1200;\r\nconst DEFAULT_MAX_RETRIES = 3;\r\n\r\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\r\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\r\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\r\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\r\n\r\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\r\n const waitTime = getWaitTime(bucket);\r\n if (waitTime > 0) {\r\n await sleep(waitTime);\r\n }\r\n consumeToken(bucket);\r\n\r\n const token = await config.auth.getToken();\r\n const url = `${baseUrl}${path}`;\r\n\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n const response = await globalThis.fetch(url, {\r\n method: options?.method ?? 'GET',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: options?.body ? JSON.stringify(options.body) : undefined,\r\n signal: options?.signal,\r\n });\r\n\r\n if (response.ok) {\r\n return (await response.json()) as T;\r\n }\r\n\r\n const text = await response.text();\r\n const error = new GSCApiError(\r\n `GSC API error: ${response.status} ${text}`,\r\n response.status,\r\n `HTTP_${response.status}`,\r\n );\r\n\r\n if (!error.retryable || attempt === maxRetries) {\r\n throw error;\r\n }\r\n\r\n lastError = error;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } catch (err) {\r\n if (err instanceof GSCApiError) {\r\n if (!err.retryable || attempt === maxRetries) {\r\n throw err;\r\n }\r\n lastError = err;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\r\n }\r\n\r\n return {\r\n request,\r\n siteUrl: config.siteUrl,\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Analytics\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SearchAnalyticsRequest,\r\n SearchAnalyticsResponse,\r\n SearchAnalyticsRow,\r\n} from './types.js';\r\n\r\nconst MAX_ROW_LIMIT = 25000;\r\nconst DEFAULT_ROW_LIMIT = 1000;\r\n\r\nexport async function querySearchAnalytics(\r\n client: GSCClient,\r\n request: SearchAnalyticsRequest,\r\n): Promise<SearchAnalyticsResponse> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const body = {\r\n ...request,\r\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\r\n };\r\n\r\n return client.request<SearchAnalyticsResponse>(\r\n `/sites/${siteUrl}/searchAnalytics/query`,\r\n { method: 'POST', body },\r\n );\r\n}\r\n\r\nexport async function querySearchAnalyticsAll(\r\n client: GSCClient,\r\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\r\n): Promise<SearchAnalyticsRow[]> {\r\n const allRows: SearchAnalyticsRow[] = [];\r\n let startRow = 0;\r\n\r\n while (true) {\r\n const response = await querySearchAnalytics(client, {\r\n ...request,\r\n rowLimit: MAX_ROW_LIMIT,\r\n startRow,\r\n });\r\n\r\n const rows = response.rows ?? [];\r\n allRows.push(...rows);\r\n\r\n if (rows.length < MAX_ROW_LIMIT) {\r\n break;\r\n }\r\n\r\n startRow += rows.length;\r\n }\r\n\r\n return allRows;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — URL Inspection\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n InspectionRequest,\r\n InspectionResult,\r\n} from './types.js';\r\n\r\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\r\n\r\nexport async function inspectUrl(\r\n client: GSCClient,\r\n request: InspectionRequest,\r\n): Promise<InspectionResult> {\r\n const body = {\r\n inspectionUrl: request.inspectionUrl,\r\n siteUrl: client.siteUrl,\r\n languageCode: request.languageCode ?? 'en',\r\n };\r\n\r\n const token = await client.request<{ inspectionResult: InspectionResult }>(\r\n '',\r\n {\r\n method: 'POST',\r\n body,\r\n },\r\n );\r\n\r\n // The URL Inspection API has a different base URL, so we use a direct fetch.\r\n // However, since the client handles auth and retries, we override for the standard path approach.\r\n // The API endpoint doesn't follow the webmasters/v3 pattern.\r\n // We'll use the client's request method with a custom approach.\r\n\r\n // Actually, let's do a direct fetch since the base URL differs:\r\n return token.inspectionResult;\r\n}\r\n\r\nexport async function inspectUrlDirect(\r\n getToken: () => Promise<string>,\r\n siteUrl: string,\r\n inspectionUrl: string,\r\n languageCode?: string,\r\n): Promise<InspectionResult> {\r\n const token = await getToken();\r\n\r\n const response = await globalThis.fetch(INSPECTION_BASE, {\r\n method: 'POST',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n inspectionUrl,\r\n siteUrl,\r\n languageCode: languageCode ?? 'en',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\r\n }\r\n\r\n const data = (await response.json()) as { inspectionResult: InspectionResult };\r\n return data.inspectionResult;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Sitemaps\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SitemapEntry,\r\n SitemapListResponse,\r\n} from './types.js';\r\n\r\nexport async function listSitemaps(\r\n client: GSCClient,\r\n): Promise<SitemapEntry[]> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const response = await client.request<SitemapListResponse>(\r\n `/sites/${siteUrl}/sitemaps`,\r\n );\r\n return response.sitemap ?? [];\r\n}\r\n\r\nexport async function submitSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'PUT' },\r\n );\r\n}\r\n\r\nexport async function deleteSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'DELETE' },\r\n );\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ============================================================================\n// @power-seo/search-console — Types\n// ============================================================================\n\n// --- Auth Types ---\n\nexport interface OAuthCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface ServiceAccountCredentials {\n clientEmail: string;\n privateKeyId: string;\n signJwt: (payload: JwtPayload) => Promise<string>;\n}\n\nexport interface JwtPayload {\n iss: string;\n scope: string;\n aud: string;\n exp: number;\n iat: number;\n}\n\nexport interface TokenResult {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface TokenManager {\n getToken: () => Promise<string>;\n invalidate: () => void;\n}\n\n// --- Client Types ---\n\nexport interface GSCClientConfig {\n auth: TokenManager;\n siteUrl: string;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n baseUrl?: string;\n}\n\nexport interface GSCClient {\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\n siteUrl: string;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: globalThis.AbortSignal;\n}\n\n// --- Error ---\n\nexport class GSCApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'GSCApiError';\n this.status = status;\n this.code = code;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Analytics Types ---\n\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\nexport type DataState = 'all' | 'final';\n\nexport interface DimensionFilter {\n dimension: Dimension;\n operator:\n | 'equals'\n | 'notEquals'\n | 'contains'\n | 'notContains'\n | 'includingRegex'\n | 'excludingRegex';\n expression: string;\n}\n\nexport interface DimensionFilterGroup {\n groupType?: 'and';\n filters: DimensionFilter[];\n}\n\nexport interface SearchAnalyticsRequest {\n startDate: string;\n endDate: string;\n dimensions?: Dimension[];\n searchType?: SearchType;\n dimensionFilterGroups?: DimensionFilterGroup[];\n aggregationType?: AggregationType;\n rowLimit?: number;\n startRow?: number;\n dataState?: DataState;\n}\n\nexport interface SearchAnalyticsRow {\n keys: string[];\n clicks: number;\n impressions: number;\n ctr: number;\n position: number;\n}\n\nexport interface SearchAnalyticsResponse {\n rows: SearchAnalyticsRow[];\n responseAggregationType: string;\n}\n\n// --- Inspection Types ---\n\nexport interface InspectionRequest {\n inspectionUrl: string;\n languageCode?: string;\n}\n\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\nexport type CrawlState =\n | 'SUCCESSFUL'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'ROBOTS_BLOCKED'\n | 'REDIRECT_ERROR'\n | 'ACCESS_DENIED'\n | 'ACCESS_FORBIDDEN';\nexport type IndexState =\n | 'INDEXED'\n | 'NOT_INDEXED'\n | 'SUBMITTED_AND_INDEXED'\n | 'CRAWLED_NOT_INDEXED'\n | 'DISCOVERED_NOT_INDEXED'\n | 'PAGE_WITH_REDIRECT';\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\n\nexport interface IndexStatusResult {\n verdict: VerdictState;\n coverageState: IndexState;\n robotsTxtState: RobotsTxtState;\n indexingState: string;\n lastCrawlTime?: string;\n pageFetchState?: CrawlState;\n referringUrls?: string[];\n sitemap?: string[];\n}\n\nexport interface MobileUsabilityResult {\n verdict: VerdictState;\n issues?: Array<{ issueType: string; severity: string; message: string }>;\n}\n\nexport interface RichResultsResult {\n verdict: VerdictState;\n detectedItems?: Array<{\n richResultType: string;\n items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }>;\n }>;\n}\n\nexport interface InspectionResult {\n inspectionResultLink: string;\n indexStatusResult: IndexStatusResult;\n mobileUsabilityResult?: MobileUsabilityResult;\n richResultsResult?: RichResultsResult;\n}\n\n// --- Sitemap Types ---\n\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\n\nexport interface SitemapEntry {\n path: string;\n lastSubmitted?: string;\n isPending: boolean;\n isSitemapsIndex: boolean;\n type: SitemapType;\n lastDownloaded?: string;\n warnings: number;\n errors: number;\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\n}\n\nexport interface SitemapListResponse {\n sitemap: SitemapEntry[];\n}\n","// ============================================================================\n// @power-seo/search-console — Auth\n// ============================================================================\n\nimport type {\n OAuthCredentials,\n ServiceAccountCredentials,\n TokenResult,\n TokenManager,\n} from './types.js';\nimport { GSCApiError } from './types.js';\n\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\nconst TOKEN_BUFFER_MS = 60_000;\n\nexport async function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult> {\n const body = new globalThis.URLSearchParams({\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: credentials.refreshToken,\n grant_type: 'refresh_token',\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Token exchange failed: ${text}`,\n response.status,\n 'TOKEN_EXCHANGE_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport async function getServiceAccountToken(\n credentials: ServiceAccountCredentials,\n): Promise<TokenResult> {\n const now = Math.floor(Date.now() / 1000);\n const payload = {\n iss: credentials.clientEmail,\n scope: GSC_SCOPE,\n aud: TOKEN_URL,\n exp: now + 3600,\n iat: now,\n };\n\n const assertion = await credentials.signJwt(payload);\n\n const body = new globalThis.URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Service account token failed: ${text}`,\n response.status,\n 'SERVICE_ACCOUNT_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager {\n let cached: TokenResult | null = null;\n let pending: Promise<string> | null = null;\n\n return {\n async getToken(): Promise<string> {\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\n return cached.accessToken;\n }\n\n if (pending) {\n return pending;\n }\n\n pending = fetchToken().then((result) => {\n cached = result;\n pending = null;\n return result.accessToken;\n });\n\n return pending;\n },\n\n invalidate(): void {\n cached = null;\n pending = null;\n },\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Client\n// ============================================================================\n\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\nimport { GSCApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\nconst DEFAULT_RATE_LIMIT = 1200;\nconst DEFAULT_MAX_RETRIES = 3;\n\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n const token = await config.auth.getToken();\n const url = `${baseUrl}${path}`;\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await globalThis.fetch(url, {\n method: options?.method ?? 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: options?.signal,\n });\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new GSCApiError(\n `GSC API error: ${response.status} ${text}`,\n response.status,\n `HTTP_${response.status}`,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n if (err instanceof GSCApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\n }\n\n return {\n request,\n siteUrl: config.siteUrl,\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Analytics\n// ============================================================================\n\nimport type {\n GSCClient,\n SearchAnalyticsRequest,\n SearchAnalyticsResponse,\n SearchAnalyticsRow,\n} from './types.js';\n\nconst MAX_ROW_LIMIT = 25000;\nconst DEFAULT_ROW_LIMIT = 1000;\n\nexport async function querySearchAnalytics(\n client: GSCClient,\n request: SearchAnalyticsRequest,\n): Promise<SearchAnalyticsResponse> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const body = {\n ...request,\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\n };\n\n return client.request<SearchAnalyticsResponse>(`/sites/${siteUrl}/searchAnalytics/query`, {\n method: 'POST',\n body,\n });\n}\n\nexport async function querySearchAnalyticsAll(\n client: GSCClient,\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\n): Promise<SearchAnalyticsRow[]> {\n const allRows: SearchAnalyticsRow[] = [];\n let startRow = 0;\n\n while (true) {\n const response = await querySearchAnalytics(client, {\n ...request,\n rowLimit: MAX_ROW_LIMIT,\n startRow,\n });\n\n const rows = response.rows ?? [];\n allRows.push(...rows);\n\n if (rows.length < MAX_ROW_LIMIT) {\n break;\n }\n\n startRow += rows.length;\n }\n\n return allRows;\n}\n","// ============================================================================\n// @power-seo/search-console — URL Inspection\n// ============================================================================\n\nimport type { GSCClient, InspectionRequest, InspectionResult } from './types.js';\n\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\n\nexport async function inspectUrl(\n client: GSCClient,\n request: InspectionRequest,\n): Promise<InspectionResult> {\n const body = {\n inspectionUrl: request.inspectionUrl,\n siteUrl: client.siteUrl,\n languageCode: request.languageCode ?? 'en',\n };\n\n const token = await client.request<{ inspectionResult: InspectionResult }>('', {\n method: 'POST',\n body,\n });\n\n // The URL Inspection API has a different base URL, so we use a direct fetch.\n // However, since the client handles auth and retries, we override for the standard path approach.\n // The API endpoint doesn't follow the webmasters/v3 pattern.\n // We'll use the client's request method with a custom approach.\n\n // Actually, let's do a direct fetch since the base URL differs:\n return token.inspectionResult;\n}\n\nexport async function inspectUrlDirect(\n getToken: () => Promise<string>,\n siteUrl: string,\n inspectionUrl: string,\n languageCode?: string,\n): Promise<InspectionResult> {\n const token = await getToken();\n\n const response = await globalThis.fetch(INSPECTION_BASE, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n inspectionUrl,\n siteUrl,\n languageCode: languageCode ?? 'en',\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\n }\n\n const data = (await response.json()) as { inspectionResult: InspectionResult };\n return data.inspectionResult;\n}\n","// ============================================================================\n// @power-seo/search-console — Sitemaps\n// ============================================================================\n\nimport type { GSCClient, SitemapEntry, SitemapListResponse } from './types.js';\n\nexport async function listSitemaps(client: GSCClient): Promise<SitemapEntry[]> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const response = await client.request<SitemapListResponse>(`/sites/${siteUrl}/sitemaps`);\n return response.sitemap ?? [];\n}\n\nexport async function submitSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'PUT' });\n}\n\nexport async function deleteSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'DELETE' });\n}\n"]}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@power-seo/search-console",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Google Search Console API client with OAuth2/service account auth, rate limiting, and retry",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": {
9
+ "types": "./dist/index.d.ts",
9
10
  "import": "./dist/index.js",
10
- "require": "./dist/index.cjs",
11
- "types": "./dist/index.d.ts"
11
+ "require": "./dist/index.cjs"
12
12
  }
13
13
  },
14
14
  "main": "./dist/index.cjs",
@@ -47,7 +47,7 @@
47
47
  "author": "CyberCraft Bangladesh <info@ccbd.dev>",
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "git+https://github.com/cybercraftbd/power-seo.git",
50
+ "url": "git+https://github.com/CyberCraftBD/power-seo.git",
51
51
  "directory": "packages/search-console"
52
52
  },
53
53
  "publishConfig": {