nuxt-google-sheets-import 0.1.4

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.
Files changed (47) hide show
  1. package/README.md +223 -0
  2. package/dist/module.d.mts +12 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +115 -0
  5. package/dist/runtime/assets/css/main.css +1 -0
  6. package/dist/runtime/components/GoogleSheetsImportExecute.d.vue.ts +7 -0
  7. package/dist/runtime/components/GoogleSheetsImportExecute.vue +282 -0
  8. package/dist/runtime/components/GoogleSheetsImportExecute.vue.d.ts +7 -0
  9. package/dist/runtime/components/GoogleSheetsImportSchemaGuide.d.vue.ts +6 -0
  10. package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue +213 -0
  11. package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue.d.ts +6 -0
  12. package/dist/runtime/components/GoogleSheetsImportSource.d.vue.ts +9 -0
  13. package/dist/runtime/components/GoogleSheetsImportSource.vue +127 -0
  14. package/dist/runtime/components/GoogleSheetsImportSource.vue.d.ts +9 -0
  15. package/dist/runtime/composables/useGoogleSheetsImport.d.ts +50 -0
  16. package/dist/runtime/composables/useGoogleSheetsImport.js +40 -0
  17. package/dist/runtime/composables/useGoogleSheetsImportWorkflow.d.ts +82 -0
  18. package/dist/runtime/composables/useGoogleSheetsImportWorkflow.js +256 -0
  19. package/dist/runtime/import/schemas.d.ts +67 -0
  20. package/dist/runtime/import/schemas.js +35 -0
  21. package/dist/runtime/pages/google-sheets-import.d.vue.ts +3 -0
  22. package/dist/runtime/pages/google-sheets-import.vue +14 -0
  23. package/dist/runtime/pages/google-sheets-import.vue.d.ts +3 -0
  24. package/dist/runtime/server/api/collection-type.get.d.ts +6 -0
  25. package/dist/runtime/server/api/collection-type.get.js +20 -0
  26. package/dist/runtime/server/api/schema-columns.get.d.ts +14 -0
  27. package/dist/runtime/server/api/schema-columns.get.js +39 -0
  28. package/dist/runtime/server/api/sheets.get.d.ts +8 -0
  29. package/dist/runtime/server/api/sheets.get.js +26 -0
  30. package/dist/runtime/server/api/values.post.d.ts +6 -0
  31. package/dist/runtime/server/api/values.post.js +33 -0
  32. package/dist/runtime/server/api/write.post.d.ts +11 -0
  33. package/dist/runtime/server/api/write.post.js +64 -0
  34. package/dist/runtime/server/utils/collectionType.d.ts +3 -0
  35. package/dist/runtime/server/utils/collectionType.js +58 -0
  36. package/dist/runtime/server/utils/googleSheets.d.ts +1 -0
  37. package/dist/runtime/server/utils/googleSheets.js +10 -0
  38. package/dist/runtime/server/utils/schemaColumns.d.ts +3 -0
  39. package/dist/runtime/server/utils/schemaColumns.js +84 -0
  40. package/dist/runtime/server/utils/transform.d.ts +5 -0
  41. package/dist/runtime/server/utils/transform.js +218 -0
  42. package/dist/runtime/server/utils/writeFrontmatter.d.ts +17 -0
  43. package/dist/runtime/server/utils/writeFrontmatter.js +92 -0
  44. package/dist/runtime/types/googleSheetsApi.d.ts +75 -0
  45. package/dist/runtime/types/googleSheetsApi.js +0 -0
  46. package/dist/types.d.mts +3 -0
  47. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # @tribeweb/nuxt-google-sheets-import
2
+
3
+ Schema-driven Google Sheets importer for Nuxt Content.
4
+
5
+ ## Status
6
+
7
+ This package scaffold is extracted from a working local module and is ready for standalone hardening/publishing.
8
+
9
+ ## Features
10
+
11
+ - Fetches sheet list and values from Google Sheets
12
+ - Validates/transforms rows with Zod schemas
13
+ - Writes frontmatter markdown, JSON, or YAML output
14
+ - Supports overwrite strategies (`overwrite`, `skip`, `overwrite-frontmatter`)
15
+ - Exposes UI components and composables for import workflow
16
+
17
+ ## Install (workspace)
18
+
19
+ ```bash
20
+ pnpm add @tribeweb/nuxt-google-sheets-import
21
+ ```
22
+
23
+ ## Configure
24
+
25
+ ```ts
26
+ // nuxt.config.ts
27
+ export default defineNuxtConfig({
28
+ modules: ['@tribeweb/nuxt-google-sheets-import'],
29
+ googleSheetsImport: {
30
+ apiBase: '/api/google-sheets-import',
31
+ googleApiKeyRuntimeKey: 'googleApiKey',
32
+ schemaRegistryImport: '#imports',
33
+ schemaRegistryExport: 'schemas',
34
+ defaultContentDir: 'content',
35
+ collectionTypeBySchema: {
36
+ machines: 'page',
37
+ materials: 'data'
38
+ }
39
+ }
40
+ })
41
+ ```
42
+
43
+ ## Environment
44
+
45
+ ```bash
46
+ NUXT_GOOGLE_API_KEY=your_google_sheets_api_key
47
+ ```
48
+
49
+ ## Google setup (permissions + API key)
50
+
51
+ This module currently reads Google Sheets using an API key, so the sheet must be publicly readable.
52
+
53
+ > Security note:
54
+ > API-key access is best suited to non-sensitive sheets that are intentionally shared as `Anyone with the link`.
55
+ > For private or sensitive spreadsheets, prefer OAuth 2.0 or a service account flow instead of API-key access.
56
+
57
+ ### 1) Set sheet permissions (Google Sheets)
58
+
59
+ 1. Open your sheet in Google Sheets.
60
+ 2. Click `Share`.
61
+ 3. Under `General access`, set to `Anyone with the link`.
62
+ 4. Set role to `Viewer`.
63
+ 5. Copy the spreadsheet ID from the URL:
64
+ - `https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit`
65
+
66
+ If your Google Workspace policy blocks public link sharing, API key access will fail. In that case you need OAuth/service-account based auth (not part of this module yet).
67
+
68
+ ### 2) Create API key (Google Cloud Console)
69
+
70
+ 1. Open Google Cloud Console: https://console.cloud.google.com/
71
+ 2. Create/select a project.
72
+ 3. Enable Google Sheets API:
73
+ - https://console.cloud.google.com/apis/library/sheets.googleapis.com
74
+ 4. Create credentials (API key):
75
+ - https://console.cloud.google.com/apis/credentials
76
+ - Click `Create credentials` → `API key`
77
+ 5. Restrict the key (recommended):
78
+ - **API restrictions**: `Restrict key` → select `Google Sheets API`
79
+ - **Application restrictions**:
80
+ - Server usage: `IP addresses` (recommended for backend)
81
+ - Browser-only usage: `HTTP referrers` (if applicable)
82
+ 6. Put the key into `NUXT_GOOGLE_API_KEY`.
83
+
84
+ ### 3) Quick verification
85
+
86
+ Call your module endpoint with a known sheet ID and confirm it returns tab metadata:
87
+
88
+ `GET /api/google-sheets-import/sheets?spreadsheetId=<SPREADSHEET_ID>`
89
+
90
+ ### 4) Troubleshooting (common errors)
91
+
92
+ - `403 PERMISSION_DENIED` / `The caller does not have permission`
93
+ - The sheet is not publicly readable with link.
94
+ - Fix: set `Share` → `General access` → `Anyone with the link` + `Viewer`.
95
+
96
+ - `403 API key not valid` or `API has not been used in project`
97
+ - The key is wrong, restricted to the wrong API, or Sheets API is not enabled.
98
+ - Fix: enable `Google Sheets API` and ensure key restriction includes it.
99
+
100
+ - `403 Requests from this referrer/IP are blocked`
101
+ - Your key application restrictions do not match where requests come from.
102
+ - Fix: update key restrictions (`IP addresses` for server use is preferred).
103
+
104
+ - `404 Requested entity was not found`
105
+ - Spreadsheet ID is incorrect or malformed.
106
+ - Fix: copy ID from `https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit`.
107
+
108
+ - `400 Unable to parse range`
109
+ - Invalid A1 range (for example typo in sheet tab or columns).
110
+ - Fix: verify tab name and use ranges like `A:Z`.
111
+
112
+ ## Exported runtime
113
+
114
+ - Components: `GoogleSheetsImportSource`, `GoogleSheetsImportExecute`, `GoogleSheetsImportSchemaGuide`
115
+ - Composables: `useGoogleSheetsImport`, `useGoogleSheetsImportWorkflow`
116
+
117
+ ### Schema helper component
118
+
119
+ Use `GoogleSheetsImportSchemaGuide` to let editors choose a schema and see the expected Google Sheet column headers.
120
+
121
+ ```vue
122
+ <GoogleSheetsImportSchemaGuide />
123
+ ```
124
+
125
+ Benefits:
126
+
127
+ - Reduces import failures by giving editors exact header names before filling a sheet
128
+ - Supports nested/array header patterns used by schema mapping (for example `items[0].name`)
129
+ - For `page` collections, shows Nuxt Content built-in page override fields and allows copying them separately
130
+ - Supports two copy modes:
131
+ - line-by-line copy
132
+ - CSV-row copy (pastes horizontally into Google Sheets)
133
+
134
+ Optional prop:
135
+
136
+ - `initialSchema?: string`
137
+
138
+ ## Schema registry
139
+
140
+ The module resolves Zod schemas from a configurable module import so it does not depend on app-specific paths.
141
+
142
+ - `schemaRegistryImport` (default: `#imports`)
143
+ - `schemaRegistryExport` (default: `schemas`)
144
+
145
+ Expected shape:
146
+
147
+ ```ts
148
+ export const schemas = {
149
+ machines,
150
+ materials
151
+ }
152
+ ```
153
+
154
+ If your schemas are exported from a custom file, point the module to it:
155
+
156
+ ```ts
157
+ export default defineNuxtConfig({
158
+ modules: ['@tribeweb/nuxt-google-sheets-import'],
159
+ googleSheetsImport: {
160
+ schemaRegistryImport: '~/server/google-sheets/schemas',
161
+ schemaRegistryExport: 'schemas'
162
+ }
163
+ })
164
+ ```
165
+
166
+ ## Playground smoke test (`values` + `write`)
167
+
168
+ The playground includes:
169
+
170
+ - `playground/server/google-sheets/schemas.ts` (tiny local schema registry)
171
+ - `POST /api/google-sheets-import/values-smoke` (local transform/validation payload)
172
+ - `playground/scripts/smoke.mjs` (calls `values-smoke`, then module `write` for `frontmatter`, `json`, `yaml`)
173
+
174
+ Run with Nuxt dev server active:
175
+
176
+ ```bash
177
+ pnpm --dir packages/nuxt-google-sheets-import smoke:playground
178
+ ```
179
+
180
+ Optional custom base URL:
181
+
182
+ ```bash
183
+ SMOKE_BASE_URL=http://localhost:3000 pnpm --dir packages/nuxt-google-sheets-import smoke:playground
184
+ ```
185
+
186
+ ## Additional API endpoint
187
+
188
+ - `GET {apiBase}/schema-columns`
189
+ - Query: `schema?`
190
+ - Returns:
191
+ - `schemas`: available schema keys
192
+ - `columns`: expected import header names for selected schema
193
+ - `collectionType`: `page | data | unknown`
194
+ - `pageOverrideColumns`: Nuxt Content page override fields (when `collectionType === 'page'`)
195
+
196
+ ## Publish checklist
197
+
198
+ - Add playground integration tests for `/values` and `/write`
199
+ - Add CI (`lint`, `typecheck`, `build`) and release workflow
200
+ - Verify Nuxt 4 peer compatibility matrix
201
+
202
+ ## Publish (next step)
203
+
204
+ ```bash
205
+ pnpm --dir packages/nuxt-google-sheets-import release:check
206
+ ```
207
+
208
+ Then authenticate and publish:
209
+
210
+ ```bash
211
+ npm login
212
+ pnpm --dir packages/nuxt-google-sheets-import publish --access public
213
+ ```
214
+
215
+ Or use one-command release scripts (bumps version + checks + publishes):
216
+
217
+ ```bash
218
+ npm login
219
+ pnpm --dir packages/nuxt-google-sheets-import release:patch
220
+ # or: release:minor / release:major
221
+ ```
222
+
223
+ These scripts use `npm version --no-git-tag-version`, so they update `package.json` version without creating a git tag/commit.
@@ -0,0 +1,12 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ apiBase: string;
5
+ googleApiKeyRuntimeKey: string;
6
+ defaultContentDir: string;
7
+ collectionTypeBySchema: Record<string, 'page' | 'data'>;
8
+ }
9
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
10
+
11
+ export { _default as default };
12
+ export type { ModuleOptions };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "nuxt-google-sheets-import",
3
+ "configKey": "googleSheetsImport",
4
+ "version": "0.1.4",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "unknown"
8
+ }
9
+ }
@@ -0,0 +1,115 @@
1
+ import { defineNuxtModule, createResolver, extendPages, addServerHandler, addImportsDir, addComponentsDir, addImports } from '@nuxt/kit';
2
+
3
+ const module$1 = defineNuxtModule({
4
+ meta: {
5
+ name: "nuxt-google-sheets-import",
6
+ configKey: "googleSheetsImport"
7
+ },
8
+ moduleDependencies: {
9
+ "@nuxt/ui": {
10
+ version: ">=4"
11
+ }
12
+ },
13
+ defaults: {
14
+ apiBase: "/api/google-sheets-import",
15
+ googleApiKeyRuntimeKey: "googleApiKey",
16
+ defaultContentDir: "content/data",
17
+ collectionTypeBySchema: {}
18
+ // schemaRegistryImport: '#imports',
19
+ // schemaRegistryExport: 'schemas',
20
+ },
21
+ setup(options, nuxt) {
22
+ const resolver = createResolver(import.meta.url);
23
+ const normalizedCollectionTypeBySchema = Object.entries(options.collectionTypeBySchema).reduce((acc, [key, value]) => {
24
+ const trimmed = key.trim();
25
+ acc[trimmed] = value;
26
+ acc[trimmed.toLowerCase()] = value;
27
+ acc[trimmed.replace(/[-\s]+/g, "_").toLowerCase()] = value;
28
+ return acc;
29
+ }, {});
30
+ nuxt.options.runtimeConfig.googleSheetsImport = {
31
+ apiBase: options.apiBase,
32
+ googleApiKeyRuntimeKey: options.googleApiKeyRuntimeKey,
33
+ defaultContentDir: options.defaultContentDir,
34
+ collectionTypeBySchema: normalizedCollectionTypeBySchema
35
+ // schemaRegistryImport: options.schemaRegistryImport,
36
+ // schemaRegistryExport: options.schemaRegistryExport,
37
+ };
38
+ nuxt.options.runtimeConfig.public.googleSheetsImport = {
39
+ apiBase: options.apiBase,
40
+ defaultContentDir: options.defaultContentDir,
41
+ collectionTypeBySchema: normalizedCollectionTypeBySchema
42
+ };
43
+ nuxt.options.css.push(resolver.resolve("./runtime/assets/css/main.css"));
44
+ extendPages((pages) => {
45
+ pages.push({
46
+ name: "google-sheets-import",
47
+ path: "/google-sheets-import",
48
+ file: resolver.resolve("./runtime/pages/google-sheets-import.vue")
49
+ });
50
+ });
51
+ addServerHandler({
52
+ route: `${options.apiBase}/sheets`,
53
+ method: "get",
54
+ handler: resolver.resolve("./runtime/server/api/sheets.get")
55
+ });
56
+ addServerHandler({
57
+ route: `${options.apiBase}/values`,
58
+ method: "post",
59
+ handler: resolver.resolve("./runtime/server/api/values.post")
60
+ });
61
+ addServerHandler({
62
+ route: `${options.apiBase}/collection-type`,
63
+ method: "get",
64
+ handler: resolver.resolve("./runtime/server/api/collection-type.get")
65
+ });
66
+ addServerHandler({
67
+ route: `${options.apiBase}/schema-columns`,
68
+ method: "get",
69
+ handler: resolver.resolve("./runtime/server/api/schema-columns.get")
70
+ });
71
+ addServerHandler({
72
+ route: `${options.apiBase}/write`,
73
+ method: "post",
74
+ handler: resolver.resolve("./runtime/server/api/write.post")
75
+ });
76
+ addImportsDir(resolver.resolve("./runtime/composables"));
77
+ addComponentsDir({
78
+ path: resolver.resolve("./runtime/components")
79
+ });
80
+ addImports([
81
+ {
82
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
83
+ name: "GoogleSheetsApiValues",
84
+ type: true
85
+ },
86
+ {
87
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
88
+ name: "GoogleSheetsApiValuesResponse",
89
+ type: true
90
+ },
91
+ {
92
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
93
+ name: "GoogleSheetsApiSheet",
94
+ type: true
95
+ },
96
+ {
97
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
98
+ name: "GoogleSheetsApiSheetResponse",
99
+ type: true
100
+ },
101
+ {
102
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
103
+ name: "ProductObject",
104
+ type: true
105
+ },
106
+ {
107
+ from: resolver.resolve("./runtime/types/googleSheetsApi"),
108
+ name: "TransformedGoogleSheetsApiResult",
109
+ type: true
110
+ }
111
+ ]);
112
+ }
113
+ });
114
+
115
+ export { module$1 as default };
@@ -0,0 +1 @@
1
+ @import "tailwindcss";@import "@nuxt/ui";@source "../../../content";@theme static{--font-sans:"Public Sans",sans-serif;--font-display:"Nord",sans-serif;--color-metzner-50:#e5feff;--color-metzner-100:#c2fcff;--color-metzner-200:#7af8ff;--color-metzner-300:#33f4ff;--color-metzner-400:#00ddea;--color-metzner-500:#009aa3;--color-metzner-600:#007d84;--color-metzner-700:#006066;--color-metzner-800:#004347;--color-metzner-900:#002629;--color-metzner-950:#001819;--color-sky-50:oklch(0.977 0.013 236.62);--color-sky-100:oklch(0.951 0.026 236.824);--color-sky-200:oklch(0.901 0.058 230.902);--color-sky-300:oklch(0.828 0.111 230.318);--color-sky-400:oklch(0.746 0.16 232.661);--color-sky-500:oklch(0.685 0.169 237.323);--color-sky-600:oklch(0.588 0.158 241.966);--color-sky-700:oklch(0.5 0.134 242.749);--color-sky-800:oklch(0.443 0.11 240.79);--color-sky-900:oklch(0.391 0.09 240.876);--color-sky-950:oklch(0.293 0.066 243.157);--color-zinc-50:oklch(0.985 0 0);--color-zinc-100:oklch(0.967 0.001 286.375);--color-zinc-200:oklch(0.92 0.004 286.32);--color-zinc-300:oklch(0.871 0.006 286.286);--color-zinc-400:oklch(0.705 0.015 286.067);--color-zinc-500:oklch(0.552 0.016 285.938);--color-zinc-600:oklch(0.442 0.017 285.786);--color-zinc-700:oklch(0.37 0.013 285.805);--color-zinc-800:oklch(0.274 0.006 286.033);--color-zinc-900:oklch(0.21 0.006 285.885);--color-zinc-950:oklch(0.141 0.005 285.823)}:root{--ui-success:var(--ui-color-success-700);--ui-info:var(--ui-color-info-800);--ui-warning:var(--ui-color-warning-600)}.dark{--ui-success:var(--ui-color-success-300);--ui-info:var(--ui-color-info-300);--ui-warning:var(--ui-color-warning-300)}
@@ -0,0 +1,7 @@
1
+ import type { GoogleSheetsImportQuery } from '../composables/useGoogleSheetsImportWorkflow.js';
2
+ interface Props {
3
+ query: GoogleSheetsImportQuery;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,282 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ query: { type: Object, required: true }
4
+ });
5
+ const toast = useToast();
6
+ const outputFormatOptions = [
7
+ { label: "Markdown frontmatter (.md)", value: "frontmatter" },
8
+ { label: "JSON (.json)", value: "json" },
9
+ { label: "YAML (.yml)", value: "yaml" }
10
+ ];
11
+ const overwriteModeOptions = [
12
+ { label: "Overwrite existing files", value: "overwrite" },
13
+ { label: "Skip files that already exist", value: "skip" },
14
+ { label: "Overwrite frontmatter only (.md only)", value: "overwrite-frontmatter" }
15
+ ];
16
+ const {
17
+ status,
18
+ writeStatus,
19
+ importError,
20
+ writeError,
21
+ writeSummary,
22
+ values,
23
+ validationErrors,
24
+ logs,
25
+ writeFile,
26
+ selectedCollectionType,
27
+ selectedCollectionTypeStatus,
28
+ resolvedDestinationPath,
29
+ columnList,
30
+ productsLength,
31
+ logsLength,
32
+ canWrite,
33
+ previewRows,
34
+ shownValidationErrors,
35
+ loadValues,
36
+ writeValues
37
+ } = useGoogleSheetsImportWorkflow({
38
+ query: computed(() => props.query)
39
+ });
40
+ async function execute() {
41
+ await loadValues(props.query);
42
+ if (validationErrors.value.length) {
43
+ toast.add({
44
+ title: "Validation warnings",
45
+ description: `${validationErrors.value.length} row(s) failed schema validation.`,
46
+ color: "warning"
47
+ });
48
+ }
49
+ if (status.value === "success" && !validationErrors.value.length) {
50
+ toast.add({
51
+ title: "Import complete",
52
+ description: `${values.value.length} valid row(s) loaded.`,
53
+ color: "success"
54
+ });
55
+ }
56
+ if (importError.value) {
57
+ toast.add({
58
+ title: "Import failed",
59
+ description: importError.value,
60
+ color: "error"
61
+ });
62
+ }
63
+ }
64
+ async function start() {
65
+ await writeValues();
66
+ if (writeStatus.value === "success") {
67
+ toast.add({
68
+ title: "Files written",
69
+ description: `${writeSummary.value.written} written, ${writeSummary.value.overwritten} overwritten, ${writeSummary.value.skipped} skipped.`,
70
+ color: "success"
71
+ });
72
+ }
73
+ if (writeError.value) {
74
+ toast.add({
75
+ title: "Write failed",
76
+ description: writeError.value,
77
+ color: "error"
78
+ });
79
+ }
80
+ }
81
+ </script>
82
+
83
+ <template>
84
+ <div class="space-y-4">
85
+ <UAlert
86
+ title="Step 2: Validate rows and write files"
87
+ description="Load rows from Google Sheets, choose filename columns, then write frontmatter files."
88
+ color="neutral"
89
+ variant="subtle"
90
+ />
91
+
92
+ <UButton
93
+ label="Load rows"
94
+ icon="i-heroicons-document-arrow-down-20-solid"
95
+ :loading="status === 'pending'"
96
+ @click.prevent="execute()"
97
+ />
98
+
99
+ <UAlert
100
+ v-if="importError"
101
+ title="Import error"
102
+ :description="importError"
103
+ color="error"
104
+ variant="subtle"
105
+ />
106
+
107
+ <UAlert
108
+ v-if="status === 'success' && values.length"
109
+ title="Rows loaded"
110
+ :description="`${values.length} valid row(s) ready to write.`"
111
+ color="success"
112
+ variant="subtle"
113
+ />
114
+
115
+ <UAlert
116
+ v-if="values?.length"
117
+ title="Destination preview"
118
+ :description="selectedCollectionTypeStatus === 'pending' ? 'Resolving collection type...' : `Collection type: ${selectedCollectionType}. Files will be written to: ${resolvedDestinationPath}`"
119
+ :color="selectedCollectionType === 'unknown' ? 'warning' : 'neutral'"
120
+ variant="subtle"
121
+ />
122
+
123
+ <UForm
124
+ :state="writeFile"
125
+ class="space-y-4"
126
+ >
127
+ <UFormField
128
+ v-if="values?.length"
129
+ label="Destination folder"
130
+ name="folder"
131
+ description="Written to content/{folder} for page collections, or content/data/{folder} for data collections."
132
+ >
133
+ <UInput
134
+ v-model="writeFile.folder"
135
+ label="Folder"
136
+ icon="i-heroicons-folder-20-solid"
137
+ class="w-full max-w-sm"
138
+ />
139
+ </UFormField>
140
+ <UFormField
141
+ v-if="values?.length"
142
+ label="Choose column to use as filename stem (without extension)"
143
+ name="slug"
144
+ >
145
+ <USelect
146
+ v-model="writeFile.slug"
147
+ :items="columnList"
148
+ label="Column to use as filename stem (without extension)"
149
+ icon="i-heroicons-document-chart-bar-20-solid"
150
+ class="w-full max-w-sm"
151
+ />
152
+ </UFormField>
153
+ <UFormField
154
+ v-if="values?.length"
155
+ label="Choose column to use as numerical ordering filename prefix"
156
+ name="order"
157
+ >
158
+ <USelect
159
+ v-model="writeFile.order"
160
+ :items="columnList"
161
+ label="Column to use as numerical ordering filename prefix"
162
+ icon="i-heroicons-document-chart-bar-20-solid"
163
+ class="w-full max-w-sm"
164
+ />
165
+ </UFormField>
166
+ <UFormField
167
+ v-if="values?.length"
168
+ label="Output file format"
169
+ name="outputFormat"
170
+ >
171
+ <USelect
172
+ v-model="writeFile.outputFormat"
173
+ :items="outputFormatOptions"
174
+ value-key="value"
175
+ label="Output file format"
176
+ icon="i-heroicons-document-20-solid"
177
+ class="w-full max-w-sm"
178
+ />
179
+ </UFormField>
180
+ <UFormField
181
+ v-if="values?.length"
182
+ label="If file already exists"
183
+ name="overwriteMode"
184
+ description="For .md files, frontmatter-only mode retains existing markdown body content."
185
+ >
186
+ <USelect
187
+ v-model="writeFile.overwriteMode"
188
+ :items="overwriteModeOptions"
189
+ value-key="value"
190
+ label="If file already exists"
191
+ icon="i-heroicons-arrow-path-20-solid"
192
+ class="w-full max-w-sm"
193
+ />
194
+ </UFormField>
195
+ <UFormField>
196
+ <UButton
197
+ v-if="values?.length"
198
+ label="Write data to files"
199
+ icon="i-heroicons-document-plus-20-solid"
200
+ :loading="writeStatus === 'pending'"
201
+ :disabled="!canWrite"
202
+ @click.prevent="start()"
203
+ />
204
+ </UFormField>
205
+ </UForm>
206
+
207
+ <UAlert
208
+ v-if="writeError"
209
+ title="Write error"
210
+ :description="writeError"
211
+ color="error"
212
+ variant="subtle"
213
+ />
214
+
215
+ <UAlert
216
+ v-if="writeStatus === 'success' && logs.length"
217
+ title="Write complete"
218
+ :description="`${writeSummary.written} written, ${writeSummary.overwritten} overwritten, ${writeSummary.skipped} skipped.`"
219
+ color="success"
220
+ variant="subtle"
221
+ />
222
+
223
+ <UProgress
224
+ class="mt-2"
225
+ :model-value="logsLength"
226
+ :max="productsLength"
227
+ status
228
+ :get-value-label="((value, max) => value != null ? `${value} of ${max}` : void 0)"
229
+ />
230
+
231
+ <UCollapsible
232
+ v-if="validationErrors.length"
233
+ class="flex flex-col gap-2 w-full"
234
+ >
235
+ <UButton
236
+ :label="`Validation issues (${validationErrors.length})`"
237
+ color="warning"
238
+ variant="subtle"
239
+ trailing-icon="i-lucide-chevron-down"
240
+ block
241
+ />
242
+
243
+ <template #content>
244
+ <pre class="text-xs whitespace-pre-wrap">{{ shownValidationErrors }}</pre>
245
+ </template>
246
+ </UCollapsible>
247
+
248
+ <UCollapsible
249
+ v-if="previewRows.length"
250
+ class="flex flex-col gap-2 w-full"
251
+ >
252
+ <UButton
253
+ :label="`Preview first ${previewRows.length} row(s)`"
254
+ color="neutral"
255
+ variant="subtle"
256
+ trailing-icon="i-lucide-chevron-down"
257
+ block
258
+ />
259
+
260
+ <template #content>
261
+ <pre class="text-xs whitespace-pre-wrap">{{ previewRows }}</pre>
262
+ </template>
263
+ </UCollapsible>
264
+
265
+ <UCollapsible
266
+ v-if="logs.length"
267
+ class="flex flex-col gap-2 w-full"
268
+ >
269
+ <UButton
270
+ :label="`Writing progress (${logs.length})`"
271
+ color="neutral"
272
+ variant="subtle"
273
+ trailing-icon="i-lucide-chevron-down"
274
+ block
275
+ />
276
+
277
+ <template #content>
278
+ <pre class="text-xs whitespace-pre-wrap">{{ logs }}</pre>
279
+ </template>
280
+ </UCollapsible>
281
+ </div>
282
+ </template>
@@ -0,0 +1,7 @@
1
+ import type { GoogleSheetsImportQuery } from '../composables/useGoogleSheetsImportWorkflow.js';
2
+ interface Props {
3
+ query: GoogleSheetsImportQuery;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ initialSchema?: string;
3
+ }
4
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;