@gscdump/nuxt 0.22.4 → 1.5.3
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 +10 -137
- package/dist/module.d.mts +22 -0
- package/dist/module.mjs +30 -0
- package/dist/runtime-config.d.mts +12 -0
- package/dist/runtime-config.mjs +27 -0
- package/package.json +26 -176
- package/app/assets/css/main.css +0 -2
- package/app/components/GscAnalyzerPanel.vue +0 -94
- package/app/components/GscAnonymizationBanner.vue +0 -46
- package/app/components/GscBootProgress.vue +0 -297
- package/app/components/GscCommandPalette.vue +0 -77
- package/app/components/GscDashboardPage.vue +0 -26
- package/app/components/GscEngineBadge.vue +0 -28
- package/app/components/GscHero.vue +0 -214
- package/app/components/GscLazyTopResult.vue +0 -47
- package/app/components/GscPerformanceChart.vue +0 -532
- package/app/components/GscPositionDistributionChart.vue +0 -63
- package/app/components/GscQueryLabel.vue +0 -63
- package/app/components/GscSitePageHeader.vue +0 -40
- package/app/components/GscSourceDebugPanel.vue +0 -196
- package/app/components/GscSyncStatusCell.vue +0 -54
- package/app/components/GscTopRollupCard.vue +0 -90
- package/app/composables/_useGscBackfill.ts +0 -119
- package/app/composables/_useGscQueryDispatcher.ts +0 -123
- package/app/composables/_useGscResource.ts +0 -202
- package/app/composables/_useGscSharedSiteResource.ts +0 -136
- package/app/composables/useGscAnalytics.ts +0 -206
- package/app/composables/useGscAnalyticsClient.ts +0 -9
- package/app/composables/useGscAnalyticsConfig.ts +0 -8
- package/app/composables/useGscAnalyticsSourceInfo.ts +0 -165
- package/app/composables/useGscAnalyzer.ts +0 -460
- package/app/composables/useGscAnalyzerBatch.ts +0 -106
- package/app/composables/useGscAnalyzerDefs.ts +0 -119
- package/app/composables/useGscAuth.ts +0 -115
- package/app/composables/useGscConsoleUrl.ts +0 -45
- package/app/composables/useGscCountries.ts +0 -46
- package/app/composables/useGscCurrentSite.ts +0 -36
- package/app/composables/useGscInspectionHistory.ts +0 -42
- package/app/composables/useGscInspections.ts +0 -52
- package/app/composables/useGscPanelContext.ts +0 -31
- package/app/composables/useGscPeriod.ts +0 -243
- package/app/composables/useGscQuery.ts +0 -301
- package/app/composables/useGscQueryDispatcher.ts +0 -14
- package/app/composables/useGscRequestIndexingInspect.ts +0 -29
- package/app/composables/useGscResource.ts +0 -11
- package/app/composables/useGscRollup.ts +0 -237
- package/app/composables/useGscSearchAppearance.ts +0 -46
- package/app/composables/useGscSiteAnalyzer.ts +0 -456
- package/app/composables/useGscSitemapHistory.ts +0 -41
- package/app/composables/useGscSitemaps.ts +0 -45
- package/app/composables/useGscTableState.ts +0 -119
- package/app/plugins/analytics.ts +0 -57
- package/app/queries/gsc.ts +0 -163
- package/app/utils/anonymization.ts +0 -24
- package/app/utils/country-names.ts +0 -56
- package/app/utils/duckdb-wasm.ts +0 -166
- package/app/utils/gsc-constants.ts +0 -10
- package/app/utils/gsc-error.ts +0 -58
- package/app/utils/gsc-fetch.ts +0 -94
- package/app/utils/gsc-filters.ts +0 -32
- package/app/utils/gsc-rows.ts +0 -72
- package/app/utils/gsc-rpc.ts +0 -18
- package/app/utils/position.ts +0 -7
- package/module.ts +0 -81
- package/nuxt.config.ts +0 -61
- package/types.ts +0 -115
package/README.md
CHANGED
|
@@ -1,148 +1,21 @@
|
|
|
1
1
|
# @gscdump/nuxt
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://npm.chart.dev/@gscdump/nuxt)
|
|
5
|
-
[](https://github.com/harlan-zw/gscdump/blob/main/LICENSE)
|
|
3
|
+
Minimal Nuxt integration for gscdump applications.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
It owns two stable Nuxt seams:
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
subscription management. Use `@gscdump/sdk` from the host app for those
|
|
12
|
-
flows, then pass the linked `gscdumpSiteId` into the analytics composables
|
|
13
|
-
here.
|
|
7
|
+
- typed `runtimeConfig.public.analytics` defaults
|
|
8
|
+
- build-time analyzer registry injection as `$gscAnalyzers`
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| --- | --- | --- |
|
|
19
|
-
| `origin` | gscdump.com | Reads R2 data directly (binding) + DuckDB service binding. Owns the data. |
|
|
20
|
-
| `consumer` | nuxtseo.com | Proxies all reads to an origin over HTTP + an API key. No data locally. |
|
|
21
|
-
| `local` | `examples/nuxt-dashboard` | Reads a `gscdump sync` dump from `GSCDUMP_DATA_DIR`. Offline dev. |
|
|
22
|
-
|
|
23
|
-
## Install
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pnpm add @gscdump/nuxt
|
|
27
|
-
```
|
|
10
|
+
Views, authentication, data fetching, server routes, and product fallback
|
|
11
|
+
policy stay in the host application. Browser runtime primitives live in
|
|
12
|
+
`@gscdump/engine-duckdb-wasm`; hosted API access lives in `@gscdump/sdk`.
|
|
28
13
|
|
|
29
14
|
```ts
|
|
30
|
-
// nuxt.config.ts
|
|
31
15
|
export default defineNuxtConfig({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
analytics: {
|
|
36
|
-
apiBase: '', // same-origin by default; set to your analytics origin in consumer mode
|
|
37
|
-
},
|
|
38
|
-
},
|
|
16
|
+
modules: ['@gscdump/nuxt'],
|
|
17
|
+
gscdumpAnalytics: {
|
|
18
|
+
analyzers: '~/gscAnalyzers',
|
|
39
19
|
},
|
|
40
20
|
})
|
|
41
21
|
```
|
|
42
|
-
|
|
43
|
-
Nuxt auto-imports the layer composables. If you prefer explicit imports, use
|
|
44
|
-
per-composable entrypoints:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { setGscAuth } from '@gscdump/nuxt/composables/useGscAuth'
|
|
48
|
-
import { useGscRollup } from '@gscdump/nuxt/composables/useGscRollup'
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The primary query interface is `useGscQuery` / `useGscAnalyzer`.
|
|
52
|
-
|
|
53
|
-
## Register an auth provider
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
// server/plugins/register-auth.ts
|
|
57
|
-
export default defineNitroPlugin((nitro) => {
|
|
58
|
-
nitro.hooks.hook('analytics:register-auth-provider', (register) => {
|
|
59
|
-
register({
|
|
60
|
-
resolve: async (event) => {
|
|
61
|
-
const session = await getUserSession(event)
|
|
62
|
-
if (!session)
|
|
63
|
-
return null
|
|
64
|
-
return { userId: session.user.id, siteIds: session.accessibleSiteIds }
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Data access: which endpoint?
|
|
72
|
-
|
|
73
|
-
| Need | Endpoint | Client composable |
|
|
74
|
-
| --- | --- | --- |
|
|
75
|
-
| Pre-aggregated widgets (top pages, daily totals) | `GET /api/__gsc/sites/:siteId/rollup/:id` | `useGscRollup` / `useGscRollups` / `useGscRollupFanout` |
|
|
76
|
-
| Raw rows filtered by dimension/range (`where(eq(page, ...))`) | `POST /api/__gsc/sites/:siteId/rows` | `useGscQuery({ siteId, params })` |
|
|
77
|
-
| Analyzer result (striking-distance, CTR anomaly, …) | `POST /api/__gsc/sites/:siteId/analyze` | `useGscAnalyzer(siteId).analyze(...)` |
|
|
78
|
-
|
|
79
|
-
Rule of thumb: reach for `/rows` + `useGscQuery` for detail pages that
|
|
80
|
-
need a freeform slice of the data. Only fall back to `/analyze` when the
|
|
81
|
-
server runs an actual analyzer (anything in the registry); hitting it for
|
|
82
|
-
plain row lookups routes through analyzer-gating logic you don't need.
|
|
83
|
-
|
|
84
|
-
## Consumer mode (cross-origin)
|
|
85
|
-
|
|
86
|
-
Used when the Nuxt app authenticating the viewer is not the same origin as
|
|
87
|
-
the data — e.g. nuxtseo.com pro consuming gscdump.com. Two pieces:
|
|
88
|
-
|
|
89
|
-
1. **Point fetch at the origin**: set `runtimeConfig.public.analytics.apiBase`
|
|
90
|
-
in `nuxt.config.ts` (or via `NUXT_PUBLIC_ANALYTICS_API_BASE`). Empty =
|
|
91
|
-
same-origin (the default origin-mode shape).
|
|
92
|
-
|
|
93
|
-
2. **Hand the layer per-viewer auth**: register a client plugin that trades
|
|
94
|
-
the viewer's host session for an api key, then call
|
|
95
|
-
`setGscAuth({ apiKey: key })`. The layer's `useGscFetch`
|
|
96
|
-
attaches the header to every `/api/__gsc/*` call automatically. Browser
|
|
97
|
-
parquet reads use exact-object URLs minted by the source endpoint; runtime
|
|
98
|
-
preflights can include this header, while DuckDB-WASM range reads are
|
|
99
|
-
authorized by the token embedded in each URL.
|
|
100
|
-
|
|
101
|
-
Wire it from a `'pre'`-enforced client plugin so downstream layer plugins see
|
|
102
|
-
the auth on first read. The layer's `setGscAuth` accepts a value, a `Ref`,
|
|
103
|
-
or a getter — use a getter when credentials may refresh during the session:
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
// plugins/00.gscdump-auth.client.ts
|
|
107
|
-
export default defineNuxtPlugin({
|
|
108
|
-
name: 'gscdump-auth',
|
|
109
|
-
enforce: 'pre',
|
|
110
|
-
async setup() {
|
|
111
|
-
const creds = ref<{ apiKey: string, browserAnalyzerEnabled?: boolean } | null>(null)
|
|
112
|
-
creds.value = await $fetch('/api/me/gscdump-credentials').catch(() => null)
|
|
113
|
-
setGscAuth(() => ({
|
|
114
|
-
apiKey: creds.value?.apiKey ?? null,
|
|
115
|
-
browserAnalyzerEnabled: !!creds.value?.browserAnalyzerEnabled,
|
|
116
|
-
}))
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
`useGscQuery` mounts that race the credential fetch resolve once the
|
|
122
|
-
`'pre'` plugin's `await` settles — Nuxt blocks subsequent plugins on it.
|
|
123
|
-
|
|
124
|
-
Onboarding and lifecycle state stay outside this layer. A consumer app should
|
|
125
|
-
use `@gscdump/sdk` from its host backend to register users/sites, read
|
|
126
|
-
`GET /api/partner/users/:userId/lifecycle`, handle webhook or realtime
|
|
127
|
-
invalidation, and decide when a linked site is ready to render. Once it has a
|
|
128
|
-
linked site id, this layer handles query reads and analytics UI only.
|
|
129
|
-
|
|
130
|
-
### CORS
|
|
131
|
-
|
|
132
|
-
The origin app (`gscdump.com` or your own) must allow the consumer origin
|
|
133
|
-
on `/api/__gsc/*`, `/api/r2-data/*`, and any host endpoints the helper
|
|
134
|
-
calls. Allow-headers must include `x-api-key`, `Cache-Control`, and `Range`
|
|
135
|
-
(DuckDB-WASM uses partial reads); expose `Content-Range`, `Accept-Ranges`,
|
|
136
|
-
`Content-Length`. Preflight needs to cover `PATCH` for any settings calls.
|
|
137
|
-
|
|
138
|
-
## Related
|
|
139
|
-
|
|
140
|
-
- [`@gscdump/sdk`](../sdk) — framework-agnostic hosted API client used by host onboarding code and by this Nuxt layer internally.
|
|
141
|
-
- [`gscdump`](../gscdump) — local package and query builder.
|
|
142
|
-
- [`@gscdump/engine`](../engine) — Storage engine consumed in origin mode.
|
|
143
|
-
- [`@gscdump/engine-duckdb-wasm`](../engine-duckdb-wasm) — DuckDB-WASM browser runtime used by client-side widgets.
|
|
144
|
-
- [`@gscdump/analysis`](../analysis) — Analyzers powering `/analyze` endpoints.
|
|
145
|
-
|
|
146
|
-
## License
|
|
147
|
-
|
|
148
|
-
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GscdumpAnalyticsRuntimeConfig, GscdumpDefaultEngine, GscdumpRuntimeEnvironment, resolveGscdumpAnalyticsRuntimeConfig } from "./runtime-config.mjs";
|
|
2
|
+
import { NuxtModule } from "@nuxt/schema";
|
|
3
|
+
interface GscdumpNuxtModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Nuxt-resolvable module exporting an analyzer array as `ANALYZERS` or
|
|
6
|
+
* `default`.
|
|
7
|
+
*/
|
|
8
|
+
analyzers?: string;
|
|
9
|
+
}
|
|
10
|
+
declare module '@nuxt/schema' {
|
|
11
|
+
interface NuxtConfig {
|
|
12
|
+
gscdumpAnalytics?: GscdumpNuxtModuleOptions;
|
|
13
|
+
}
|
|
14
|
+
interface NuxtOptions {
|
|
15
|
+
gscdumpAnalytics: GscdumpNuxtModuleOptions;
|
|
16
|
+
}
|
|
17
|
+
interface PublicRuntimeConfig {
|
|
18
|
+
analytics: GscdumpAnalyticsRuntimeConfig;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
declare const gscdumpNuxtModule: NuxtModule<GscdumpNuxtModuleOptions>;
|
|
22
|
+
export { type GscdumpAnalyticsRuntimeConfig, type GscdumpDefaultEngine, GscdumpNuxtModuleOptions, type GscdumpRuntimeEnvironment, gscdumpNuxtModule as default, resolveGscdumpAnalyticsRuntimeConfig };
|
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveGscdumpAnalyticsRuntimeConfig } from "./runtime-config.mjs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { addPluginTemplate, defineNuxtModule } from "@nuxt/kit";
|
|
4
|
+
const gscdumpNuxtModule = defineNuxtModule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: "@gscdump/nuxt",
|
|
7
|
+
configKey: "gscdumpAnalytics",
|
|
8
|
+
compatibility: { nuxt: ">=4.0.0" }
|
|
9
|
+
},
|
|
10
|
+
defaults: {},
|
|
11
|
+
setup(options, nuxt) {
|
|
12
|
+
nuxt.options.runtimeConfig.public.analytics = resolveGscdumpAnalyticsRuntimeConfig(nuxt.options.runtimeConfig.public.analytics, process.env);
|
|
13
|
+
if (!options.analyzers) return;
|
|
14
|
+
addPluginTemplate({
|
|
15
|
+
filename: "gscdump-analyzers.plugin.mjs",
|
|
16
|
+
getContents: () => [
|
|
17
|
+
`import { defineNuxtPlugin } from '#app'`,
|
|
18
|
+
`import * as registry from ${JSON.stringify(options.analyzers)}`,
|
|
19
|
+
`const analyzers = registry.ANALYZERS ?? registry.default`,
|
|
20
|
+
`if (!Array.isArray(analyzers))`,
|
|
21
|
+
` throw new TypeError('gscdump analyzer registry must export an array')`,
|
|
22
|
+
`export default defineNuxtPlugin(() => ({`,
|
|
23
|
+
` provide: { gscAnalyzers: analyzers },`,
|
|
24
|
+
`}))`,
|
|
25
|
+
``
|
|
26
|
+
].join("\n")
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
export { gscdumpNuxtModule as default, resolveGscdumpAnalyticsRuntimeConfig };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type GscdumpDefaultEngine = 'auto' | 'client' | 'server';
|
|
2
|
+
interface GscdumpAnalyticsRuntimeConfig {
|
|
3
|
+
apiBase: string;
|
|
4
|
+
duckdbBundleBase: string;
|
|
5
|
+
duckdbThreadBundleBase: string;
|
|
6
|
+
timezone: string;
|
|
7
|
+
toastErrors: boolean;
|
|
8
|
+
defaultEngine: GscdumpDefaultEngine;
|
|
9
|
+
}
|
|
10
|
+
type GscdumpRuntimeEnvironment = Readonly<Record<string, string | undefined>>;
|
|
11
|
+
declare function resolveGscdumpAnalyticsRuntimeConfig(configured: Partial<GscdumpAnalyticsRuntimeConfig> | undefined, environment: GscdumpRuntimeEnvironment): GscdumpAnalyticsRuntimeConfig;
|
|
12
|
+
export { GscdumpAnalyticsRuntimeConfig, GscdumpDefaultEngine, GscdumpRuntimeEnvironment, resolveGscdumpAnalyticsRuntimeConfig };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const DEFAULT_ENGINES = /* @__PURE__ */ new Set([
|
|
2
|
+
"auto",
|
|
3
|
+
"client",
|
|
4
|
+
"server"
|
|
5
|
+
]);
|
|
6
|
+
function defaultEngine(value) {
|
|
7
|
+
return value && DEFAULT_ENGINES.has(value) ? value : "auto";
|
|
8
|
+
}
|
|
9
|
+
function resolveGscdumpAnalyticsRuntimeConfig(configured, environment) {
|
|
10
|
+
const defaults = {
|
|
11
|
+
apiBase: environment.GSCDUMP_ANALYTICS_API_BASE ?? "",
|
|
12
|
+
duckdbBundleBase: environment.GSCDUMP_DUCKDB_BUNDLE_BASE ?? "",
|
|
13
|
+
duckdbThreadBundleBase: environment.GSCDUMP_DUCKDB_THREAD_BUNDLE_BASE ?? "",
|
|
14
|
+
timezone: environment.GSCDUMP_ANALYTICS_TIMEZONE ?? "",
|
|
15
|
+
toastErrors: environment.GSCDUMP_ANALYTICS_TOAST_ERRORS === "true",
|
|
16
|
+
defaultEngine: defaultEngine(environment.GSCDUMP_ANALYTICS_DEFAULT_ENGINE)
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
apiBase: configured?.apiBase ?? defaults.apiBase,
|
|
20
|
+
duckdbBundleBase: configured?.duckdbBundleBase ?? defaults.duckdbBundleBase,
|
|
21
|
+
duckdbThreadBundleBase: configured?.duckdbThreadBundleBase ?? defaults.duckdbThreadBundleBase,
|
|
22
|
+
timezone: configured?.timezone ?? defaults.timezone,
|
|
23
|
+
toastErrors: configured?.toastErrors ?? defaults.toastErrors,
|
|
24
|
+
defaultEngine: configured?.defaultEngine ?? defaults.defaultEngine
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export { resolveGscdumpAnalyticsRuntimeConfig };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/nuxt",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "Nuxt
|
|
4
|
+
"version": "1.5.3",
|
|
5
|
+
"description": "Minimal Nuxt integration for gscdump analytics runtime config and analyzer registries.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
8
8
|
"email": "harlan@harlanzw.com",
|
|
@@ -17,191 +17,41 @@
|
|
|
17
17
|
"sideEffects": false,
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
20
|
-
"types": "./
|
|
21
|
-
"import": "./
|
|
22
|
-
"default": "./
|
|
23
|
-
},
|
|
24
|
-
"./
|
|
25
|
-
"types": "./
|
|
26
|
-
"import": "./
|
|
27
|
-
"default": "./
|
|
28
|
-
},
|
|
29
|
-
"./module": {
|
|
30
|
-
"types": "./module.ts",
|
|
31
|
-
"import": "./module.ts",
|
|
32
|
-
"default": "./module.ts"
|
|
33
|
-
},
|
|
34
|
-
"./queries/gsc": {
|
|
35
|
-
"types": "./app/queries/gsc.ts",
|
|
36
|
-
"import": "./app/queries/gsc.ts",
|
|
37
|
-
"default": "./app/queries/gsc.ts"
|
|
38
|
-
},
|
|
39
|
-
"./composables/useGscAnalytics": {
|
|
40
|
-
"types": "./app/composables/useGscAnalytics.ts",
|
|
41
|
-
"import": "./app/composables/useGscAnalytics.ts",
|
|
42
|
-
"default": "./app/composables/useGscAnalytics.ts"
|
|
43
|
-
},
|
|
44
|
-
"./composables/useGscAnalyticsClient": {
|
|
45
|
-
"types": "./app/composables/useGscAnalyticsClient.ts",
|
|
46
|
-
"import": "./app/composables/useGscAnalyticsClient.ts",
|
|
47
|
-
"default": "./app/composables/useGscAnalyticsClient.ts"
|
|
48
|
-
},
|
|
49
|
-
"./composables/useGscAnalyticsConfig": {
|
|
50
|
-
"types": "./app/composables/useGscAnalyticsConfig.ts",
|
|
51
|
-
"import": "./app/composables/useGscAnalyticsConfig.ts",
|
|
52
|
-
"default": "./app/composables/useGscAnalyticsConfig.ts"
|
|
53
|
-
},
|
|
54
|
-
"./composables/useGscAnalyticsSourceInfo": {
|
|
55
|
-
"types": "./app/composables/useGscAnalyticsSourceInfo.ts",
|
|
56
|
-
"import": "./app/composables/useGscAnalyticsSourceInfo.ts",
|
|
57
|
-
"default": "./app/composables/useGscAnalyticsSourceInfo.ts"
|
|
58
|
-
},
|
|
59
|
-
"./composables/useGscAnalyzer": {
|
|
60
|
-
"types": "./app/composables/useGscAnalyzer.ts",
|
|
61
|
-
"import": "./app/composables/useGscAnalyzer.ts",
|
|
62
|
-
"default": "./app/composables/useGscAnalyzer.ts"
|
|
63
|
-
},
|
|
64
|
-
"./composables/useGscAnalyzerBatch": {
|
|
65
|
-
"types": "./app/composables/useGscAnalyzerBatch.ts",
|
|
66
|
-
"import": "./app/composables/useGscAnalyzerBatch.ts",
|
|
67
|
-
"default": "./app/composables/useGscAnalyzerBatch.ts"
|
|
68
|
-
},
|
|
69
|
-
"./composables/useGscAnalyzerDefs": {
|
|
70
|
-
"types": "./app/composables/useGscAnalyzerDefs.ts",
|
|
71
|
-
"import": "./app/composables/useGscAnalyzerDefs.ts",
|
|
72
|
-
"default": "./app/composables/useGscAnalyzerDefs.ts"
|
|
73
|
-
},
|
|
74
|
-
"./composables/useGscAuth": {
|
|
75
|
-
"types": "./app/composables/useGscAuth.ts",
|
|
76
|
-
"import": "./app/composables/useGscAuth.ts",
|
|
77
|
-
"default": "./app/composables/useGscAuth.ts"
|
|
78
|
-
},
|
|
79
|
-
"./composables/useGscConsoleUrl": {
|
|
80
|
-
"types": "./app/composables/useGscConsoleUrl.ts",
|
|
81
|
-
"import": "./app/composables/useGscConsoleUrl.ts",
|
|
82
|
-
"default": "./app/composables/useGscConsoleUrl.ts"
|
|
83
|
-
},
|
|
84
|
-
"./composables/useGscCountries": {
|
|
85
|
-
"types": "./app/composables/useGscCountries.ts",
|
|
86
|
-
"import": "./app/composables/useGscCountries.ts",
|
|
87
|
-
"default": "./app/composables/useGscCountries.ts"
|
|
88
|
-
},
|
|
89
|
-
"./composables/useGscCurrentSite": {
|
|
90
|
-
"types": "./app/composables/useGscCurrentSite.ts",
|
|
91
|
-
"import": "./app/composables/useGscCurrentSite.ts",
|
|
92
|
-
"default": "./app/composables/useGscCurrentSite.ts"
|
|
93
|
-
},
|
|
94
|
-
"./composables/useGscInspectionHistory": {
|
|
95
|
-
"types": "./app/composables/useGscInspectionHistory.ts",
|
|
96
|
-
"import": "./app/composables/useGscInspectionHistory.ts",
|
|
97
|
-
"default": "./app/composables/useGscInspectionHistory.ts"
|
|
98
|
-
},
|
|
99
|
-
"./composables/useGscInspections": {
|
|
100
|
-
"types": "./app/composables/useGscInspections.ts",
|
|
101
|
-
"import": "./app/composables/useGscInspections.ts",
|
|
102
|
-
"default": "./app/composables/useGscInspections.ts"
|
|
103
|
-
},
|
|
104
|
-
"./composables/useGscPanelContext": {
|
|
105
|
-
"types": "./app/composables/useGscPanelContext.ts",
|
|
106
|
-
"import": "./app/composables/useGscPanelContext.ts",
|
|
107
|
-
"default": "./app/composables/useGscPanelContext.ts"
|
|
108
|
-
},
|
|
109
|
-
"./composables/useGscPeriod": {
|
|
110
|
-
"types": "./app/composables/useGscPeriod.ts",
|
|
111
|
-
"import": "./app/composables/useGscPeriod.ts",
|
|
112
|
-
"default": "./app/composables/useGscPeriod.ts"
|
|
113
|
-
},
|
|
114
|
-
"./composables/useGscQuery": {
|
|
115
|
-
"types": "./app/composables/useGscQuery.ts",
|
|
116
|
-
"import": "./app/composables/useGscQuery.ts",
|
|
117
|
-
"default": "./app/composables/useGscQuery.ts"
|
|
118
|
-
},
|
|
119
|
-
"./composables/useGscQueryDispatcher": {
|
|
120
|
-
"types": "./app/composables/useGscQueryDispatcher.ts",
|
|
121
|
-
"import": "./app/composables/useGscQueryDispatcher.ts",
|
|
122
|
-
"default": "./app/composables/useGscQueryDispatcher.ts"
|
|
123
|
-
},
|
|
124
|
-
"./composables/useGscRequestIndexingInspect": {
|
|
125
|
-
"types": "./app/composables/useGscRequestIndexingInspect.ts",
|
|
126
|
-
"import": "./app/composables/useGscRequestIndexingInspect.ts",
|
|
127
|
-
"default": "./app/composables/useGscRequestIndexingInspect.ts"
|
|
128
|
-
},
|
|
129
|
-
"./composables/useGscResource": {
|
|
130
|
-
"types": "./app/composables/useGscResource.ts",
|
|
131
|
-
"import": "./app/composables/useGscResource.ts",
|
|
132
|
-
"default": "./app/composables/useGscResource.ts"
|
|
133
|
-
},
|
|
134
|
-
"./composables/useGscRollup": {
|
|
135
|
-
"types": "./app/composables/useGscRollup.ts",
|
|
136
|
-
"import": "./app/composables/useGscRollup.ts",
|
|
137
|
-
"default": "./app/composables/useGscRollup.ts"
|
|
138
|
-
},
|
|
139
|
-
"./composables/useGscSearchAppearance": {
|
|
140
|
-
"types": "./app/composables/useGscSearchAppearance.ts",
|
|
141
|
-
"import": "./app/composables/useGscSearchAppearance.ts",
|
|
142
|
-
"default": "./app/composables/useGscSearchAppearance.ts"
|
|
143
|
-
},
|
|
144
|
-
"./composables/useGscSiteAnalyzer": {
|
|
145
|
-
"types": "./app/composables/useGscSiteAnalyzer.ts",
|
|
146
|
-
"import": "./app/composables/useGscSiteAnalyzer.ts",
|
|
147
|
-
"default": "./app/composables/useGscSiteAnalyzer.ts"
|
|
148
|
-
},
|
|
149
|
-
"./composables/useGscSitemapHistory": {
|
|
150
|
-
"types": "./app/composables/useGscSitemapHistory.ts",
|
|
151
|
-
"import": "./app/composables/useGscSitemapHistory.ts",
|
|
152
|
-
"default": "./app/composables/useGscSitemapHistory.ts"
|
|
153
|
-
},
|
|
154
|
-
"./composables/useGscSitemaps": {
|
|
155
|
-
"types": "./app/composables/useGscSitemaps.ts",
|
|
156
|
-
"import": "./app/composables/useGscSitemaps.ts",
|
|
157
|
-
"default": "./app/composables/useGscSitemaps.ts"
|
|
158
|
-
},
|
|
159
|
-
"./composables/useGscTableState": {
|
|
160
|
-
"types": "./app/composables/useGscTableState.ts",
|
|
161
|
-
"import": "./app/composables/useGscTableState.ts",
|
|
162
|
-
"default": "./app/composables/useGscTableState.ts"
|
|
20
|
+
"types": "./dist/module.d.mts",
|
|
21
|
+
"import": "./dist/module.mjs",
|
|
22
|
+
"default": "./dist/module.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./runtime-config": {
|
|
25
|
+
"types": "./dist/runtime-config.d.mts",
|
|
26
|
+
"import": "./dist/runtime-config.mjs",
|
|
27
|
+
"default": "./dist/runtime-config.mjs"
|
|
163
28
|
}
|
|
164
29
|
},
|
|
165
|
-
"main": "./
|
|
166
|
-
"types": "./
|
|
30
|
+
"main": "./dist/module.mjs",
|
|
31
|
+
"types": "./dist/module.d.mts",
|
|
167
32
|
"files": [
|
|
168
33
|
"README.md",
|
|
169
|
-
"
|
|
170
|
-
"module.ts",
|
|
171
|
-
"nuxt.config.ts",
|
|
172
|
-
"types.ts"
|
|
34
|
+
"dist"
|
|
173
35
|
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
174
39
|
"peerDependencies": {
|
|
175
|
-
"nuxt": "^4.
|
|
176
|
-
"vue": "^3.5.34"
|
|
40
|
+
"nuxt": "^4.5.0"
|
|
177
41
|
},
|
|
178
42
|
"dependencies": {
|
|
179
|
-
"@
|
|
180
|
-
"@nuxt/
|
|
181
|
-
"@unovis/ts": "^1.6.5",
|
|
182
|
-
"@unovis/vue": "^1.6.5",
|
|
183
|
-
"@vueuse/core": "^14.3.0",
|
|
184
|
-
"@vueuse/nuxt": "^14.3.0",
|
|
185
|
-
"defu": "^6.1.7",
|
|
186
|
-
"nuxt-use-query": "^0.2.4",
|
|
187
|
-
"ofetch": "^1.5.1",
|
|
188
|
-
"tailwindcss": "^4.3.0",
|
|
189
|
-
"zod": "^4.4.3",
|
|
190
|
-
"@gscdump/analysis": "0.22.4",
|
|
191
|
-
"@gscdump/contracts": "0.22.4",
|
|
192
|
-
"@gscdump/engine": "0.22.4",
|
|
193
|
-
"@gscdump/engine-duckdb-wasm": "0.22.4",
|
|
194
|
-
"@gscdump/sdk": "0.22.4",
|
|
195
|
-
"gscdump": "0.22.4"
|
|
43
|
+
"@nuxt/kit": "4.5.0",
|
|
44
|
+
"@nuxt/schema": "4.5.0"
|
|
196
45
|
},
|
|
197
46
|
"devDependencies": {
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"typescript": "^
|
|
201
|
-
"
|
|
47
|
+
"nuxt": "^4.5.0",
|
|
48
|
+
"obuild": "^0.4.38",
|
|
49
|
+
"typescript": "^7.0.2",
|
|
50
|
+
"vitest": "^4.1.10"
|
|
202
51
|
},
|
|
203
52
|
"scripts": {
|
|
204
|
-
"build": "
|
|
205
|
-
"typecheck": "
|
|
53
|
+
"build": "obuild",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test": "vitest"
|
|
206
56
|
}
|
|
207
57
|
}
|
package/app/assets/css/main.css
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// Unified shell for `/analyze` analyzer tabs. Drives the chrome that 11
|
|
3
|
-
// per-analyzer sections used to repeat: stat tiles, body, caption. The body
|
|
4
|
-
// component + tile projection + caption come from `def.capabilities.panel`;
|
|
5
|
-
// the shell owns the loading/error/empty gating unless `panel.ownsLifecycle`
|
|
6
|
-
// is set (pipeline panels manage their own phase state internally).
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
GscAnalyzerDefinition,
|
|
10
|
-
GscAnalyzerPanelResult,
|
|
11
|
-
} from '../composables/useGscAnalyzerDefs'
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
def: GscAnalyzerDefinition
|
|
15
|
-
rows: unknown[]
|
|
16
|
-
meta: Record<string, unknown> | null
|
|
17
|
-
queryMs?: number | null
|
|
18
|
-
loading?: boolean
|
|
19
|
-
error?: string | null
|
|
20
|
-
/** Forwarded to the body component as `range`. Optional. */
|
|
21
|
-
range?: { start: string, end: string } | null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const props = defineProps<Props>()
|
|
25
|
-
|
|
26
|
-
const panel = computed(() => props.def.capabilities?.panel)
|
|
27
|
-
|
|
28
|
-
const tiles = computed(() => {
|
|
29
|
-
const p = panel.value
|
|
30
|
-
if (!p?.summarize)
|
|
31
|
-
return []
|
|
32
|
-
const result: GscAnalyzerPanelResult = {
|
|
33
|
-
results: props.rows,
|
|
34
|
-
meta: props.meta ?? {},
|
|
35
|
-
queryMs: props.queryMs ?? null,
|
|
36
|
-
}
|
|
37
|
-
return p.summarize(result)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const showBody = computed(() => {
|
|
41
|
-
if (!panel.value)
|
|
42
|
-
return false
|
|
43
|
-
if (panel.value.ownsLifecycle)
|
|
44
|
-
return true
|
|
45
|
-
return !props.loading && !props.error && props.rows.length > 0
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const bodyProps = computed(() => ({
|
|
49
|
-
rows: props.rows,
|
|
50
|
-
meta: props.meta ?? {},
|
|
51
|
-
range: props.range ?? null,
|
|
52
|
-
}))
|
|
53
|
-
</script>
|
|
54
|
-
|
|
55
|
-
<template>
|
|
56
|
-
<section v-if="panel" class="gsc-analyzer-panel">
|
|
57
|
-
<div v-if="tiles.length > 0" class="gsc-analyzer-panel__tiles">
|
|
58
|
-
<div v-for="(t, i) in tiles" :key="`${t.label}-${i}`" class="gsc-analyzer-panel__tile">
|
|
59
|
-
<span class="gsc-analyzer-panel__tile-label">{{ t.label }}</span>
|
|
60
|
-
<span class="gsc-analyzer-panel__tile-value" :style="t.valueColor ? { color: t.valueColor } : undefined">{{ t.value }}</span>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<template v-if="!panel.ownsLifecycle">
|
|
65
|
-
<div v-if="error" class="gsc-analyzer-panel__error">
|
|
66
|
-
{{ error }}
|
|
67
|
-
</div>
|
|
68
|
-
<div v-else-if="loading" class="gsc-analyzer-panel__loading">
|
|
69
|
-
Running analyzer…
|
|
70
|
-
</div>
|
|
71
|
-
<div v-else-if="rows.length === 0" class="gsc-analyzer-panel__empty">
|
|
72
|
-
No rows.
|
|
73
|
-
</div>
|
|
74
|
-
</template>
|
|
75
|
-
|
|
76
|
-
<component :is="panel.component" v-if="showBody" v-bind="bodyProps" />
|
|
77
|
-
|
|
78
|
-
<p v-if="panel.caption" class="gsc-analyzer-panel__caption">
|
|
79
|
-
<slot name="caption">{{ panel.caption }}</slot>
|
|
80
|
-
</p>
|
|
81
|
-
</section>
|
|
82
|
-
</template>
|
|
83
|
-
|
|
84
|
-
<style scoped>
|
|
85
|
-
.gsc-analyzer-panel { background: #fff; border: 1px solid #ececef; border-top: 0; border-radius: 0 0 6px 6px; padding: 1rem 1rem 0.9rem; }
|
|
86
|
-
.gsc-analyzer-panel__tiles { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 0.85rem; padding: 0 0.15rem; }
|
|
87
|
-
.gsc-analyzer-panel__tile { display: flex; flex-direction: column; gap: 0.1rem; }
|
|
88
|
-
.gsc-analyzer-panel__tile-label { font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: #888; }
|
|
89
|
-
.gsc-analyzer-panel__tile-value { font-size: 1.3rem; font-weight: 600; color: #1d1d1f; font-variant-numeric: tabular-nums; }
|
|
90
|
-
.gsc-analyzer-panel__caption { margin: 0.8rem 0 0; font-size: 0.78rem; color: #666; font-style: italic; text-align: center; }
|
|
91
|
-
.gsc-analyzer-panel__caption :deep(code) { background: #f0f0f3; padding: 0.05rem 0.3rem; border-radius: 3px; font-size: 0.74rem; color: #4c3ca0; }
|
|
92
|
-
.gsc-analyzer-panel__error { padding: 1rem 1.25rem; color: #c00; background: #fff5f5; font-family: ui-monospace, monospace; font-size: 0.82rem; white-space: pre-wrap; }
|
|
93
|
-
.gsc-analyzer-panel__loading, .gsc-analyzer-panel__empty { padding: 2.5rem; text-align: center; color: var(--ui-text-dimmed); font-size: 0.9rem; }
|
|
94
|
-
</style>
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// Surfaces the trailing-28d impression-weighted anonymization % from the
|
|
3
|
-
// daily_totals rollup. GSC anonymises queries for low-volume or sensitive
|
|
4
|
-
// terms, so query-grained breakdowns (keywords, pages-by-query) undercount
|
|
5
|
-
// impressions by this amount. Hidden when data isn't ready or the gap is
|
|
6
|
-
// negligible (<1%).
|
|
7
|
-
|
|
8
|
-
import { useGscRollup } from '../composables/useGscRollup'
|
|
9
|
-
import { weightedAnonPct } from '../utils/anonymization'
|
|
10
|
-
|
|
11
|
-
interface DailyTotal {
|
|
12
|
-
impressions: number
|
|
13
|
-
anonymizedImpressionsPct: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const { siteIdentifier } = defineProps<{
|
|
17
|
-
siteIdentifier: string | null | undefined
|
|
18
|
-
}>()
|
|
19
|
-
|
|
20
|
-
const { data: daily } = useGscRollup<DailyTotal[]>(() => siteIdentifier ?? null, 'daily_totals')
|
|
21
|
-
|
|
22
|
-
const formatted = computed(() => {
|
|
23
|
-
if (!siteIdentifier)
|
|
24
|
-
return null
|
|
25
|
-
const pct = weightedAnonPct(daily.value)
|
|
26
|
-
if (pct == null)
|
|
27
|
-
return null
|
|
28
|
-
const percent = pct * 100
|
|
29
|
-
if (percent < 1)
|
|
30
|
-
return null
|
|
31
|
-
return percent.toFixed(percent < 10 ? 1 : 0)
|
|
32
|
-
})
|
|
33
|
-
</script>
|
|
34
|
-
|
|
35
|
-
<template>
|
|
36
|
-
<div
|
|
37
|
-
v-if="formatted"
|
|
38
|
-
class="flex items-start gap-2 border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-amber-200/90"
|
|
39
|
-
>
|
|
40
|
-
<UIcon name="i-ph-info" class="mt-0.5 flex-shrink-0 text-amber-400" />
|
|
41
|
-
<div>
|
|
42
|
-
<span class="font-semibold">~{{ formatted }}% of impressions are anonymised by Google.</span>
|
|
43
|
-
Query-grained breakdowns below exclude these, so the sum won't match site-level totals.
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</template>
|