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
@@ -0,0 +1,213 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { useGoogleSheetsImport } from "../composables/useGoogleSheetsImport";
4
+ const props = defineProps({
5
+ initialSchema: { type: String, required: false, default: "" }
6
+ });
7
+ const { getSchemaColumns } = useGoogleSheetsImport();
8
+ const toast = useToast();
9
+ const selectedSchema = ref("");
10
+ const availableSchemas = ref([]);
11
+ const columns = ref([]);
12
+ const collectionType = ref("unknown");
13
+ const pageOverrideColumns = ref([]);
14
+ const status = ref("idle");
15
+ const error = ref("");
16
+ const schemaOptions = computed(() => availableSchemas.value.map((schema) => ({
17
+ label: schema,
18
+ value: schema
19
+ })));
20
+ async function loadSchemas() {
21
+ status.value = "pending";
22
+ error.value = "";
23
+ try {
24
+ const response = await getSchemaColumns();
25
+ availableSchemas.value = response.schemas;
26
+ selectedSchema.value = props.initialSchema && response.schemas.includes(props.initialSchema) ? props.initialSchema : response.schemas[0] ?? "";
27
+ status.value = "success";
28
+ } catch (cause) {
29
+ status.value = "error";
30
+ error.value = cause instanceof Error ? cause.message : "Could not load schema list.";
31
+ }
32
+ }
33
+ async function loadColumns(schema) {
34
+ if (!schema) {
35
+ columns.value = [];
36
+ return;
37
+ }
38
+ status.value = "pending";
39
+ error.value = "";
40
+ try {
41
+ const response = await getSchemaColumns(schema);
42
+ columns.value = response.columns;
43
+ collectionType.value = response.collectionType;
44
+ pageOverrideColumns.value = response.pageOverrideColumns;
45
+ status.value = "success";
46
+ } catch (cause) {
47
+ columns.value = [];
48
+ collectionType.value = "unknown";
49
+ pageOverrideColumns.value = [];
50
+ status.value = "error";
51
+ error.value = cause instanceof Error ? cause.message : "Could not load schema columns.";
52
+ }
53
+ }
54
+ watch(selectedSchema, async (schema) => {
55
+ await loadColumns(schema);
56
+ });
57
+ onMounted(async () => {
58
+ await loadSchemas();
59
+ if (selectedSchema.value) {
60
+ await loadColumns(selectedSchema.value);
61
+ }
62
+ });
63
+ function copyColumns() {
64
+ const content = columns.value.join("\n");
65
+ if (!content) {
66
+ return;
67
+ }
68
+ navigator.clipboard.writeText(content);
69
+ toast.add({
70
+ title: "Copied",
71
+ description: "Column names copied to clipboard.",
72
+ color: "success"
73
+ });
74
+ }
75
+ function csvRow(values) {
76
+ return values.map((value) => {
77
+ const escaped = value.replaceAll('"', '""');
78
+ return `"${escaped}"`;
79
+ }).join(",");
80
+ }
81
+ function copyColumnsCsv() {
82
+ const content = csvRow(columns.value);
83
+ if (!content) {
84
+ return;
85
+ }
86
+ navigator.clipboard.writeText(content);
87
+ toast.add({
88
+ title: "Copied",
89
+ description: "Column names copied as a CSV row.",
90
+ color: "success"
91
+ });
92
+ }
93
+ function copyPageOverrideColumns() {
94
+ const content = pageOverrideColumns.value.join("\n");
95
+ if (!content) {
96
+ return;
97
+ }
98
+ navigator.clipboard.writeText(content);
99
+ toast.add({
100
+ title: "Copied",
101
+ description: "Page override column names copied to clipboard.",
102
+ color: "success"
103
+ });
104
+ }
105
+ function copyPageOverrideColumnsCsv() {
106
+ const content = csvRow(pageOverrideColumns.value);
107
+ if (!content) {
108
+ return;
109
+ }
110
+ navigator.clipboard.writeText(content);
111
+ toast.add({
112
+ title: "Copied",
113
+ description: "Page override column names copied as a CSV row.",
114
+ color: "success"
115
+ });
116
+ }
117
+ </script>
118
+
119
+ <template>
120
+ <div class="space-y-4">
121
+ <UAlert
122
+ title="Schema column helper"
123
+ description="Choose a schema to see the expected Google Sheet column names."
124
+ color="neutral"
125
+ variant="subtle"
126
+ />
127
+
128
+ <UAlert
129
+ v-if="error"
130
+ title="Could not load schema information"
131
+ :description="error"
132
+ color="error"
133
+ variant="subtle"
134
+ />
135
+
136
+ <UFormField
137
+ label="Collection schema"
138
+ name="schema"
139
+ description="Pick the schema you want to import."
140
+ >
141
+ <USelect
142
+ v-model="selectedSchema"
143
+ :items="schemaOptions"
144
+ value-key="value"
145
+ icon="i-heroicons-cube-20-solid"
146
+ class="w-full max-w-sm"
147
+ :loading="status === 'pending'"
148
+ />
149
+ </UFormField>
150
+
151
+ <UAlert
152
+ v-if="status === 'success' && selectedSchema"
153
+ title="Expected column names"
154
+ :description="`Use these exact headers in your Google Sheet for schema: ${selectedSchema}.`"
155
+ color="neutral"
156
+ variant="subtle"
157
+ />
158
+
159
+ <div
160
+ v-if="columns.length"
161
+ class="space-y-3"
162
+ >
163
+ <div class="flex flex-wrap gap-2">
164
+ <UButton
165
+ label="Copy column names"
166
+ icon="i-heroicons-clipboard-document-20-solid"
167
+ color="neutral"
168
+ variant="subtle"
169
+ @click="copyColumns"
170
+ />
171
+ <UButton
172
+ label="Copy as CSV row"
173
+ icon="i-heroicons-table-cells-20-solid"
174
+ color="neutral"
175
+ variant="subtle"
176
+ @click="copyColumnsCsv"
177
+ />
178
+ </div>
179
+ <pre class="text-xs whitespace-pre-wrap">{{ columns.join("\n") }}</pre>
180
+
181
+ <UAlert
182
+ v-if="collectionType === 'page' && pageOverrideColumns.length"
183
+ title="Nuxt Content page overrides"
184
+ description="Optional built-in page fields Nuxt Content adds automatically for page collections."
185
+ color="neutral"
186
+ variant="subtle"
187
+ />
188
+
189
+ <div
190
+ v-if="collectionType === 'page' && pageOverrideColumns.length"
191
+ class="space-y-3"
192
+ >
193
+ <div class="flex flex-wrap gap-2">
194
+ <UButton
195
+ label="Copy page override columns"
196
+ icon="i-heroicons-clipboard-document-list-20-solid"
197
+ color="neutral"
198
+ variant="subtle"
199
+ @click="copyPageOverrideColumns"
200
+ />
201
+ <UButton
202
+ label="Copy overrides as CSV row"
203
+ icon="i-heroicons-table-cells-20-solid"
204
+ color="neutral"
205
+ variant="subtle"
206
+ @click="copyPageOverrideColumnsCsv"
207
+ />
208
+ </div>
209
+ <pre class="text-xs whitespace-pre-wrap">{{ pageOverrideColumns.join("\n") }}</pre>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </template>
@@ -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;
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ googleSheets: {
3
+ id: string;
4
+ label: string;
5
+ }[];
6
+ }
7
+ 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>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,127 @@
1
+ <script setup>
2
+ import { z } from "zod";
3
+ const { googleSheets } = defineProps({
4
+ googleSheets: { type: Array, required: true }
5
+ });
6
+ const toast = useToast();
7
+ const googleSheetSchema = z.object({
8
+ spreadsheetId: z.string().length(44),
9
+ sheetTitle: z.string(),
10
+ range: z.string(),
11
+ schema: z.string()
12
+ });
13
+ const {
14
+ source,
15
+ sourceStatus,
16
+ sourceError,
17
+ sheetTitles,
18
+ upperCaseRange,
19
+ selectedSheetPreview,
20
+ canLoadValues,
21
+ sourceQuery
22
+ } = useGoogleSheetsImportWorkflow();
23
+ watch(sourceStatus, (value) => {
24
+ if (value === "success") {
25
+ toast.add({ title: "Success", description: "Google Sheet titles retrieved.", color: "success" });
26
+ }
27
+ });
28
+ watch(sourceError, (value) => {
29
+ if (value) {
30
+ toast.add({
31
+ title: "Error",
32
+ description: value,
33
+ color: "error"
34
+ });
35
+ }
36
+ });
37
+ </script>
38
+
39
+ <template>
40
+ <div class="space-y-4">
41
+ <UAlert
42
+ title="Step 1: Select source sheet"
43
+ description="Choose a spreadsheet and tab, then confirm range and schema key before loading rows."
44
+ color="neutral"
45
+ variant="subtle"
46
+ />
47
+
48
+ <UAlert
49
+ v-if="sourceError"
50
+ title="Could not load sheet tabs"
51
+ :description="sourceError"
52
+ color="error"
53
+ variant="subtle"
54
+ />
55
+
56
+ <UForm
57
+ :state="source"
58
+ :schema="googleSheetSchema"
59
+ class="space-y-4"
60
+ >
61
+ <UFormField
62
+ label="Spreadsheet"
63
+ name="spreadsheetId"
64
+ description="Select one configured Google Sheet source."
65
+ >
66
+ <USelect
67
+ v-model="source.spreadsheetId"
68
+ :items="googleSheets"
69
+ value-key="id"
70
+ label="Spreadsheet"
71
+ icon="i-heroicons-document-chart-bar-20-solid"
72
+ class="w-full max-w-sm"
73
+ :loading="sourceStatus === 'pending'"
74
+ />
75
+ </UFormField>
76
+
77
+ <UFormField
78
+ v-if="sheetTitles.length"
79
+ label="Sheet tab"
80
+ name="sheetTitle"
81
+ description="Tabs are fetched from the selected spreadsheet."
82
+ >
83
+ <USelect
84
+ v-model="source.sheetTitle"
85
+ :items="sheetTitles"
86
+ value-key="label"
87
+ label="Sheet tab"
88
+ icon="i-heroicons-table-cells-20-solid"
89
+ class="w-full max-w-sm"
90
+ />
91
+ </UFormField>
92
+
93
+ <UFormField
94
+ v-if="source.sheetTitle"
95
+ label="Range"
96
+ name="range"
97
+ :description="selectedSheetPreview ? `Default detected range: ${selectedSheetPreview.range}` : 'Enter Google Sheets A1 range (e.g. A:Z).'"
98
+ >
99
+ <UInput
100
+ v-model="upperCaseRange"
101
+ label="Range"
102
+ icon="i-heroicons-arrows-right-left-20-solid"
103
+ class="w-full max-w-sm"
104
+ />
105
+ </UFormField>
106
+
107
+ <UFormField
108
+ v-if="source.range"
109
+ label="Schema key"
110
+ name="schema"
111
+ description="Must match a key in your exported `schemas` map."
112
+ >
113
+ <UInput
114
+ v-model="source.schema"
115
+ label="Schema key"
116
+ icon="i-heroicons-cube-20-solid"
117
+ class="w-full max-w-sm"
118
+ />
119
+ </UFormField>
120
+ </UForm>
121
+
122
+ <GoogleSheetsImportExecute
123
+ v-if="canLoadValues && sourceQuery"
124
+ :query="sourceQuery"
125
+ />
126
+ </div>
127
+ </template>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ googleSheets: {
3
+ id: string;
4
+ label: string;
5
+ }[];
6
+ }
7
+ 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>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,50 @@
1
+ interface GoogleSheetOption {
2
+ label: string;
3
+ range: string;
4
+ }
5
+ interface GetValuesPayload {
6
+ spreadsheetId: string;
7
+ sheetTitle: string;
8
+ range: string;
9
+ schema: string;
10
+ }
11
+ interface WritePayload {
12
+ records: Record<string, unknown>[];
13
+ schema?: string;
14
+ folder: string;
15
+ slugKey: string;
16
+ orderKey?: string;
17
+ contentDir?: string;
18
+ outputFormat?: 'frontmatter' | 'json' | 'yaml';
19
+ overwriteMode?: 'skip' | 'overwrite' | 'overwrite-frontmatter';
20
+ }
21
+ export declare function useGoogleSheetsImport(): {
22
+ getSheets: (spreadsheetId: string) => Promise<GoogleSheetOption[]>;
23
+ getValues: (payload: GetValuesPayload) => Promise<{
24
+ headers: string[];
25
+ records: Record<string, unknown>[];
26
+ errors: string[];
27
+ }>;
28
+ getCollectionType: (schema: string) => Promise<{
29
+ collectionType: "page" | "data" | "unknown";
30
+ baseContentDir: string;
31
+ }>;
32
+ getSchemaColumns: (schema?: string) => Promise<{
33
+ schema: string | null;
34
+ schemas: string[];
35
+ columns: string[];
36
+ collectionType: "page" | "data" | "unknown";
37
+ pageOverrideColumns: string[];
38
+ }>;
39
+ writeFiles: (payload: WritePayload) => Promise<{
40
+ count: number;
41
+ logs: string[];
42
+ contentDir: string;
43
+ summary: {
44
+ written: number;
45
+ overwritten: number;
46
+ skipped: number;
47
+ };
48
+ }>;
49
+ };
50
+ export {};
@@ -0,0 +1,40 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ export function useGoogleSheetsImport() {
3
+ const config = useRuntimeConfig();
4
+ const apiBase = config.public.googleSheetsImport?.apiBase ?? "/api/google-sheets-import";
5
+ const getSheets = async (spreadsheetId) => {
6
+ const response = await $fetch(`${apiBase}/sheets`, {
7
+ query: { spreadsheetId }
8
+ });
9
+ return response.sheets;
10
+ };
11
+ const getValues = async (payload) => {
12
+ return await $fetch(`${apiBase}/values`, {
13
+ method: "POST",
14
+ body: payload
15
+ });
16
+ };
17
+ const getCollectionType = async (schema) => {
18
+ return await $fetch(`${apiBase}/collection-type`, {
19
+ query: { schema }
20
+ });
21
+ };
22
+ const getSchemaColumns = async (schema) => {
23
+ return await $fetch(`${apiBase}/schema-columns`, {
24
+ query: schema ? { schema } : void 0
25
+ });
26
+ };
27
+ const writeFiles = async (payload) => {
28
+ return await $fetch(`${apiBase}/write`, {
29
+ method: "POST",
30
+ body: payload
31
+ });
32
+ };
33
+ return {
34
+ getSheets,
35
+ getValues,
36
+ getCollectionType,
37
+ getSchemaColumns,
38
+ writeFiles
39
+ };
40
+ }
@@ -0,0 +1,82 @@
1
+ import type { MaybeRefOrGetter } from 'vue';
2
+ export interface GoogleSheetsImportQuery {
3
+ spreadsheetId: string;
4
+ sheetTitle: string;
5
+ range: string;
6
+ schema: string;
7
+ }
8
+ interface ImportRecord {
9
+ [key: string]: unknown;
10
+ }
11
+ interface WorkflowOptions {
12
+ query?: MaybeRefOrGetter<GoogleSheetsImportQuery>;
13
+ }
14
+ export declare function useGoogleSheetsImportWorkflow(options?: WorkflowOptions): {
15
+ source: {
16
+ spreadsheetId?: string | undefined;
17
+ sheetTitle?: string | undefined;
18
+ range?: string | undefined;
19
+ schema?: string | undefined;
20
+ };
21
+ sourceStatus: import("vue").Ref<"success" | "error" | "idle" | "pending", "success" | "error" | "idle" | "pending">;
22
+ sourceError: import("vue").Ref<string, string>;
23
+ sheetTitles: import("vue").Ref<{
24
+ label: string;
25
+ range: string;
26
+ }[], {
27
+ label: string;
28
+ range: string;
29
+ }[] | {
30
+ label: string;
31
+ range: string;
32
+ }[]>;
33
+ upperCaseRange: import("vue").WritableComputedRef<string | undefined, string | undefined>;
34
+ selectedSheetPreview: import("vue").ComputedRef<{
35
+ label: string;
36
+ range: string;
37
+ } | undefined>;
38
+ sourceQuery: import("vue").ComputedRef<GoogleSheetsImportQuery | null>;
39
+ activeQuery: import("vue").ComputedRef<GoogleSheetsImportQuery | null>;
40
+ canLoadValues: import("vue").ComputedRef<boolean>;
41
+ selectedCollectionType: import("vue").Ref<"page" | "data" | "unknown", "page" | "data" | "unknown">;
42
+ selectedCollectionTypeStatus: import("vue").Ref<"success" | "error" | "idle" | "pending", "success" | "error" | "idle" | "pending">;
43
+ resolvedBaseContentDir: import("vue").ComputedRef<any>;
44
+ resolvedDestinationPath: import("vue").ComputedRef<any>;
45
+ loadSheetTitles: (spreadsheetId?: string) => Promise<void>;
46
+ status: import("vue").Ref<"success" | "error" | "idle" | "pending", "success" | "error" | "idle" | "pending">;
47
+ writeStatus: import("vue").Ref<"success" | "error" | "idle" | "pending", "success" | "error" | "idle" | "pending">;
48
+ importError: import("vue").Ref<string, string>;
49
+ writeError: import("vue").Ref<string, string>;
50
+ writeSummary: import("vue").Ref<{
51
+ written: number;
52
+ overwritten: number;
53
+ skipped: number;
54
+ }, {
55
+ written: number;
56
+ overwritten: number;
57
+ skipped: number;
58
+ } | {
59
+ written: number;
60
+ overwritten: number;
61
+ skipped: number;
62
+ }>;
63
+ values: import("vue").Ref<ImportRecord[], ImportRecord[]>;
64
+ validationErrors: import("vue").Ref<string[], string[]>;
65
+ logs: import("vue").Ref<string[], string[]>;
66
+ writeFile: {
67
+ folder: string;
68
+ slug: string | undefined;
69
+ order: string | undefined;
70
+ outputFormat: "frontmatter" | "json" | "yaml";
71
+ overwriteMode: "skip" | "overwrite" | "overwrite-frontmatter";
72
+ };
73
+ columnList: import("vue").ComputedRef<string[]>;
74
+ productsLength: import("vue").ComputedRef<number>;
75
+ logsLength: import("vue").ComputedRef<number>;
76
+ canWrite: import("vue").ComputedRef<boolean>;
77
+ previewRows: import("vue").ComputedRef<ImportRecord[]>;
78
+ shownValidationErrors: import("vue").ComputedRef<string[]>;
79
+ loadValues: (queryArg?: GoogleSheetsImportQuery) => Promise<void>;
80
+ writeValues: () => Promise<void>;
81
+ };
82
+ export {};