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.
- package/README.md +223 -0
- package/dist/module.d.mts +12 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +115 -0
- package/dist/runtime/assets/css/main.css +1 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.d.vue.ts +7 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.vue +282 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.vue.d.ts +7 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.d.vue.ts +6 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue +213 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue.d.ts +6 -0
- package/dist/runtime/components/GoogleSheetsImportSource.d.vue.ts +9 -0
- package/dist/runtime/components/GoogleSheetsImportSource.vue +127 -0
- package/dist/runtime/components/GoogleSheetsImportSource.vue.d.ts +9 -0
- package/dist/runtime/composables/useGoogleSheetsImport.d.ts +50 -0
- package/dist/runtime/composables/useGoogleSheetsImport.js +40 -0
- package/dist/runtime/composables/useGoogleSheetsImportWorkflow.d.ts +82 -0
- package/dist/runtime/composables/useGoogleSheetsImportWorkflow.js +256 -0
- package/dist/runtime/import/schemas.d.ts +67 -0
- package/dist/runtime/import/schemas.js +35 -0
- package/dist/runtime/pages/google-sheets-import.d.vue.ts +3 -0
- package/dist/runtime/pages/google-sheets-import.vue +14 -0
- package/dist/runtime/pages/google-sheets-import.vue.d.ts +3 -0
- package/dist/runtime/server/api/collection-type.get.d.ts +6 -0
- package/dist/runtime/server/api/collection-type.get.js +20 -0
- package/dist/runtime/server/api/schema-columns.get.d.ts +14 -0
- package/dist/runtime/server/api/schema-columns.get.js +39 -0
- package/dist/runtime/server/api/sheets.get.d.ts +8 -0
- package/dist/runtime/server/api/sheets.get.js +26 -0
- package/dist/runtime/server/api/values.post.d.ts +6 -0
- package/dist/runtime/server/api/values.post.js +33 -0
- package/dist/runtime/server/api/write.post.d.ts +11 -0
- package/dist/runtime/server/api/write.post.js +64 -0
- package/dist/runtime/server/utils/collectionType.d.ts +3 -0
- package/dist/runtime/server/utils/collectionType.js +58 -0
- package/dist/runtime/server/utils/googleSheets.d.ts +1 -0
- package/dist/runtime/server/utils/googleSheets.js +10 -0
- package/dist/runtime/server/utils/schemaColumns.d.ts +3 -0
- package/dist/runtime/server/utils/schemaColumns.js +84 -0
- package/dist/runtime/server/utils/transform.d.ts +5 -0
- package/dist/runtime/server/utils/transform.js +218 -0
- package/dist/runtime/server/utils/writeFrontmatter.d.ts +17 -0
- package/dist/runtime/server/utils/writeFrontmatter.js +92 -0
- package/dist/runtime/types/googleSheetsApi.d.ts +75 -0
- package/dist/runtime/types/googleSheetsApi.js +0 -0
- package/dist/types.d.mts +3 -0
- 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 };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -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;
|