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,256 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { computed, reactive, ref, toValue, watch } from "vue";
3
+ import { useGoogleSheetsImport } from "./useGoogleSheetsImport.js";
4
+ export function useGoogleSheetsImportWorkflow(options = {}) {
5
+ const { getSheets, getValues, getCollectionType, writeFiles } = useGoogleSheetsImport();
6
+ const config = useRuntimeConfig();
7
+ const defaultContentDir = computed(
8
+ () => config.public.googleSheetsImport?.defaultContentDir ?? "content/data"
9
+ );
10
+ const sourceStatus = ref("idle");
11
+ const sourceError = ref("");
12
+ const sheetTitles = ref([]);
13
+ const source = reactive({
14
+ spreadsheetId: void 0,
15
+ sheetTitle: void 0,
16
+ range: void 0,
17
+ schema: void 0
18
+ });
19
+ const upperCaseRange = computed({
20
+ get: () => source.range,
21
+ set: (value) => {
22
+ source.range = value?.toUpperCase() || "";
23
+ }
24
+ });
25
+ const selectedSheetPreview = computed(
26
+ () => sheetTitles.value.find((sheet) => sheet.label === source.sheetTitle)
27
+ );
28
+ const sourceQuery = computed(() => {
29
+ if (!source.spreadsheetId || !source.sheetTitle || !source.range || !source.schema) {
30
+ return null;
31
+ }
32
+ return {
33
+ spreadsheetId: source.spreadsheetId,
34
+ sheetTitle: source.sheetTitle,
35
+ range: source.range,
36
+ schema: source.schema
37
+ };
38
+ });
39
+ const externalQuery = computed(() => {
40
+ if (!options.query) {
41
+ return null;
42
+ }
43
+ const value = toValue(options.query);
44
+ return value ?? null;
45
+ });
46
+ const activeQuery = computed(() => externalQuery.value ?? sourceQuery.value);
47
+ const canLoadValues = computed(() => Boolean(activeQuery.value));
48
+ const selectedCollectionType = ref("unknown");
49
+ const selectedCollectionTypeStatus = ref("idle");
50
+ async function refreshCollectionType() {
51
+ const schema = activeQuery.value?.schema?.trim();
52
+ if (!schema) {
53
+ selectedCollectionType.value = "unknown";
54
+ selectedCollectionTypeStatus.value = "idle";
55
+ return;
56
+ }
57
+ selectedCollectionTypeStatus.value = "pending";
58
+ try {
59
+ const resolved = await getCollectionType(schema);
60
+ selectedCollectionType.value = resolved.collectionType;
61
+ selectedCollectionTypeStatus.value = "success";
62
+ } catch {
63
+ selectedCollectionType.value = "unknown";
64
+ selectedCollectionTypeStatus.value = "error";
65
+ }
66
+ }
67
+ watch(() => activeQuery.value?.schema, async () => {
68
+ await refreshCollectionType();
69
+ }, { immediate: true });
70
+ async function loadSheetTitles(spreadsheetId) {
71
+ if (!spreadsheetId) {
72
+ return;
73
+ }
74
+ sourceStatus.value = "pending";
75
+ sourceError.value = "";
76
+ try {
77
+ sheetTitles.value = await getSheets(spreadsheetId);
78
+ sourceStatus.value = "success";
79
+ } catch (error) {
80
+ sourceStatus.value = "error";
81
+ sheetTitles.value = [];
82
+ sourceError.value = error instanceof Error ? error.message : "Failed to retrieve Google Sheet titles.";
83
+ }
84
+ }
85
+ watch(() => source.spreadsheetId, async (spreadsheetId, previousSpreadsheetId) => {
86
+ if (spreadsheetId !== previousSpreadsheetId) {
87
+ source.sheetTitle = void 0;
88
+ source.range = void 0;
89
+ source.schema = void 0;
90
+ await loadSheetTitles(spreadsheetId);
91
+ }
92
+ });
93
+ watch(() => source.sheetTitle, () => {
94
+ if (!source.range) {
95
+ source.range = sheetTitles.value.find((sheet) => sheet.label === source.sheetTitle)?.range;
96
+ }
97
+ if (source.sheetTitle && !source.schema) {
98
+ source.schema = source.sheetTitle.replace(/ /g, "_");
99
+ }
100
+ });
101
+ const status = ref("idle");
102
+ const writeStatus = ref("idle");
103
+ const importError = ref("");
104
+ const writeError = ref("");
105
+ const writeSummary = ref({
106
+ written: 0,
107
+ overwritten: 0,
108
+ skipped: 0
109
+ });
110
+ const values = ref([]);
111
+ const validationErrors = ref([]);
112
+ const logs = ref([]);
113
+ const writeFile = reactive({
114
+ folder: kebabize(activeQuery.value?.sheetTitle ?? source.sheetTitle ?? ""),
115
+ slug: void 0,
116
+ order: void 0,
117
+ outputFormat: "frontmatter",
118
+ overwriteMode: "overwrite"
119
+ });
120
+ const resolvedBaseContentDir = computed(() => {
121
+ if (selectedCollectionType.value === "page") {
122
+ return "content";
123
+ }
124
+ if (selectedCollectionType.value === "data") {
125
+ return "content/data";
126
+ }
127
+ return defaultContentDir.value;
128
+ });
129
+ const resolvedDestinationPath = computed(() => {
130
+ const folder = writeFile.folder?.trim();
131
+ return folder ? `${resolvedBaseContentDir.value}/${folder}` : resolvedBaseContentDir.value;
132
+ });
133
+ watch(activeQuery, (query) => {
134
+ if (query?.sheetTitle && !writeFile.folder) {
135
+ writeFile.folder = kebabize(query.sheetTitle);
136
+ }
137
+ }, { immediate: true });
138
+ const columnList = computed(() => {
139
+ const unique = /* @__PURE__ */ new Set();
140
+ for (const record of values.value) {
141
+ collectKeys(record).forEach((key) => unique.add(key));
142
+ }
143
+ return [...unique];
144
+ });
145
+ const productsLength = computed(() => values.value.length);
146
+ const logsLength = computed(() => logs.value.length);
147
+ const canWrite = computed(() => Boolean(values.value.length && writeFile.slug && writeFile.folder && writeStatus.value !== "pending"));
148
+ const previewRows = computed(() => values.value.slice(0, 5));
149
+ const shownValidationErrors = computed(() => validationErrors.value.slice(0, 20));
150
+ watch(columnList, (keys) => {
151
+ if (!keys.length) {
152
+ return;
153
+ }
154
+ if (!writeFile.slug) {
155
+ const slugCandidate = keys.find((key) => /(?:^|\.)(?:slug|id)$/i.test(key));
156
+ writeFile.slug = slugCandidate ?? keys[0];
157
+ }
158
+ if (!writeFile.order) {
159
+ const orderCandidate = keys.find((key) => /(?:^|\.)(?:order|pageOrder)$/i.test(key));
160
+ writeFile.order = orderCandidate;
161
+ }
162
+ }, { immediate: true });
163
+ async function loadValues(queryArg) {
164
+ const query = queryArg ?? activeQuery.value;
165
+ if (!query) {
166
+ return;
167
+ }
168
+ status.value = "pending";
169
+ validationErrors.value = [];
170
+ importError.value = "";
171
+ writeStatus.value = "idle";
172
+ writeError.value = "";
173
+ writeSummary.value = { written: 0, overwritten: 0, skipped: 0 };
174
+ logs.value = [];
175
+ try {
176
+ const response = await getValues(query);
177
+ values.value = response.records;
178
+ validationErrors.value = response.errors;
179
+ status.value = "success";
180
+ } catch (error) {
181
+ status.value = "error";
182
+ values.value = [];
183
+ importError.value = error instanceof Error ? error.message : "Could not load values from Google Sheets.";
184
+ }
185
+ }
186
+ async function writeValues() {
187
+ if (!canWrite.value || !writeFile.slug || !writeFile.folder) {
188
+ return;
189
+ }
190
+ writeStatus.value = "pending";
191
+ writeError.value = "";
192
+ try {
193
+ const response = await writeFiles({
194
+ records: values.value,
195
+ schema: activeQuery.value?.schema,
196
+ folder: writeFile.folder,
197
+ slugKey: writeFile.slug,
198
+ orderKey: writeFile.order,
199
+ outputFormat: writeFile.outputFormat,
200
+ overwriteMode: writeFile.overwriteMode
201
+ });
202
+ logs.value = response.logs;
203
+ writeSummary.value = response.summary;
204
+ writeStatus.value = "success";
205
+ } catch (error) {
206
+ writeStatus.value = "error";
207
+ writeError.value = error instanceof Error ? error.message : "Could not write files.";
208
+ }
209
+ }
210
+ return {
211
+ source,
212
+ sourceStatus,
213
+ sourceError,
214
+ sheetTitles,
215
+ upperCaseRange,
216
+ selectedSheetPreview,
217
+ sourceQuery,
218
+ activeQuery,
219
+ canLoadValues,
220
+ selectedCollectionType,
221
+ selectedCollectionTypeStatus,
222
+ resolvedBaseContentDir,
223
+ resolvedDestinationPath,
224
+ loadSheetTitles,
225
+ status,
226
+ writeStatus,
227
+ importError,
228
+ writeError,
229
+ writeSummary,
230
+ values,
231
+ validationErrors,
232
+ logs,
233
+ writeFile,
234
+ columnList,
235
+ productsLength,
236
+ logsLength,
237
+ canWrite,
238
+ previewRows,
239
+ shownValidationErrors,
240
+ loadValues,
241
+ writeValues
242
+ };
243
+ }
244
+ function kebabize(str) {
245
+ return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? "-" : "") + $.toLowerCase());
246
+ }
247
+ function collectKeys(obj, prefix = "") {
248
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
249
+ return [];
250
+ }
251
+ return Object.entries(obj).flatMap(([key, value]) => {
252
+ const next = prefix ? `${prefix}.${key}` : key;
253
+ const nested = collectKeys(value, next);
254
+ return nested.length ? nested : [next];
255
+ });
256
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ export declare const machinesSmoke: z.ZodObject<{
3
+ pageOrder: z.ZodNumber;
4
+ modelId: z.ZodString;
5
+ machineName: z.ZodString;
6
+ cutRate: z.ZodNumber;
7
+ featurePrimary: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ export declare const example: z.ZodObject<{
10
+ slug: z.ZodString;
11
+ pageOrder: z.ZodCoercedNumber<unknown>;
12
+ number: z.ZodCoercedNumber<unknown>;
13
+ string: z.ZodString;
14
+ enumString: z.ZodEnum<{
15
+ foo: "foo";
16
+ bar: "bar";
17
+ baz: "baz";
18
+ }>;
19
+ literalString: z.ZodLiteral<"foo">;
20
+ unionString: z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
21
+ unionStringArray: z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>>;
22
+ exclusiveUnionString: z.ZodXor<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
23
+ stringArray: z.ZodArray<z.ZodString>;
24
+ boolean: z.ZodCoercedBoolean<unknown>;
25
+ object: z.ZodObject<{
26
+ key1: z.ZodString;
27
+ key2: z.ZodString;
28
+ }, z.core.$strip>;
29
+ objectArray: z.ZodArray<z.ZodObject<{
30
+ keyA: z.ZodString;
31
+ keyB: z.ZodString;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export declare const schemas: {
35
+ machinesSmoke: z.ZodObject<{
36
+ pageOrder: z.ZodNumber;
37
+ modelId: z.ZodString;
38
+ machineName: z.ZodString;
39
+ cutRate: z.ZodNumber;
40
+ featurePrimary: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>;
42
+ example: z.ZodObject<{
43
+ slug: z.ZodString;
44
+ pageOrder: z.ZodCoercedNumber<unknown>;
45
+ number: z.ZodCoercedNumber<unknown>;
46
+ string: z.ZodString;
47
+ enumString: z.ZodEnum<{
48
+ foo: "foo";
49
+ bar: "bar";
50
+ baz: "baz";
51
+ }>;
52
+ literalString: z.ZodLiteral<"foo">;
53
+ unionString: z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
54
+ unionStringArray: z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>>;
55
+ exclusiveUnionString: z.ZodXor<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
56
+ stringArray: z.ZodArray<z.ZodString>;
57
+ boolean: z.ZodCoercedBoolean<unknown>;
58
+ object: z.ZodObject<{
59
+ key1: z.ZodString;
60
+ key2: z.ZodString;
61
+ }, z.core.$strip>;
62
+ objectArray: z.ZodArray<z.ZodObject<{
63
+ keyA: z.ZodString;
64
+ keyB: z.ZodString;
65
+ }, z.core.$strip>>;
66
+ }, z.core.$strip>;
67
+ };
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ export const machinesSmoke = z.object({
3
+ pageOrder: z.number(),
4
+ modelId: z.string().min(1),
5
+ machineName: z.string().min(1),
6
+ cutRate: z.number(),
7
+ featurePrimary: z.string().optional()
8
+ });
9
+ export const example = z.object({
10
+ slug: z.string().min(1).max(100),
11
+ pageOrder: z.coerce.number().int(),
12
+ number: z.coerce.number(),
13
+ string: z.string().min(1).max(100),
14
+ enumString: z.enum(["foo", "bar", "baz"]),
15
+ literalString: z.literal("foo"),
16
+ unionString: z.union([z.literal("foo"), z.literal("bar")]),
17
+ unionStringArray: z.union([z.literal("foo"), z.literal("bar")]).array(),
18
+ exclusiveUnionString: z.xor([z.literal("foo"), z.literal("bar")]),
19
+ stringArray: z.string().array(),
20
+ boolean: z.coerce.boolean(),
21
+ object: z.object({
22
+ key1: z.string().min(1),
23
+ key2: z.string().min(1)
24
+ }),
25
+ objectArray: z.array(
26
+ z.object({
27
+ keyA: z.string().min(1),
28
+ keyB: z.string().min(1)
29
+ })
30
+ )
31
+ });
32
+ export const schemas = {
33
+ machinesSmoke,
34
+ example
35
+ };
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,14 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ const sheetsList = ref([
4
+ { id: "1NKS0cTX6u5urtgQ3Q4Z2motiR2-9JmyPxcd05yVc1bc", label: "Metzner" },
5
+ { id: "1tGZCEoiikXfg3mOpfVWWTS1SSSsj18xv6Z3owrnnt4s", label: "Example Sheet" }
6
+ ]);
7
+ </script>
8
+
9
+ <template>
10
+ <UContainer>
11
+ <GoogleSheetsImportSchemaGuide />
12
+ <GoogleSheetsImportSource :google-sheets="sheetsList" />
13
+ </UContainer>
14
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,6 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ schema: string | null;
3
+ collectionType: "unknown" | ("page" | "data");
4
+ baseContentDir: string;
5
+ }>>;
6
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
3
+ import { defineEventHandler, getValidatedQuery } from "h3";
4
+ import { useRuntimeConfig } from "#imports";
5
+ const querySchema = z.object({
6
+ schema: z.string().optional()
7
+ });
8
+ export default defineEventHandler(async (event) => {
9
+ const { schema } = await getValidatedQuery(event, (query) => querySchema.parse(query));
10
+ const config = useRuntimeConfig(event);
11
+ const moduleConfig = config.googleSheetsImport;
12
+ const fromConfig = moduleConfig.collectionTypeBySchema ?? {};
13
+ const collectionType = await resolveCollectionTypeBySchema(schema, fromConfig);
14
+ const baseContentDir = collectionType === "page" ? "content" : collectionType === "data" ? "content/data" : moduleConfig.defaultContentDir;
15
+ return {
16
+ schema: schema ?? null,
17
+ collectionType,
18
+ baseContentDir
19
+ };
20
+ });
@@ -0,0 +1,14 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ schema: null;
3
+ schemas: string[];
4
+ columns: never[];
5
+ collectionType: string;
6
+ pageOverrideColumns: never[];
7
+ } | {
8
+ schema: string;
9
+ schemas: string[];
10
+ columns: string[];
11
+ collectionType: "unknown" | ("page" | "data");
12
+ pageOverrideColumns: string[];
13
+ }>>;
14
+ export default _default;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
3
+ import { getSchemaColumns, PAGE_SCHEMA_OVERRIDE_COLUMNS } from "../utils/schemaColumns.js";
4
+ import { getValidatedQuery, defineEventHandler, createError } from "h3";
5
+ import { useRuntimeConfig } from "#imports";
6
+ const querySchema = z.object({
7
+ schema: z.string().optional()
8
+ });
9
+ export default defineEventHandler(async (event) => {
10
+ const { schema } = await getValidatedQuery(event, (query) => querySchema.parse(query));
11
+ const config = useRuntimeConfig(event);
12
+ const moduleConfig = config.googleSheetsImport;
13
+ const schemaMap = {};
14
+ const availableSchemas = Object.keys(schemaMap).sort((left, right) => left.localeCompare(right));
15
+ if (!schema) {
16
+ return {
17
+ schema: null,
18
+ schemas: availableSchemas,
19
+ columns: [],
20
+ collectionType: "unknown",
21
+ pageOverrideColumns: []
22
+ };
23
+ }
24
+ const collectionType = await resolveCollectionTypeBySchema(schema, moduleConfig.collectionTypeBySchema ?? {});
25
+ const selectedSchema = schemaMap[schema];
26
+ if (!selectedSchema) {
27
+ throw createError({
28
+ statusCode: 400,
29
+ statusMessage: `Unknown schema: ${schema}`
30
+ });
31
+ }
32
+ return {
33
+ schema,
34
+ schemas: availableSchemas,
35
+ columns: getSchemaColumns(selectedSchema),
36
+ collectionType,
37
+ pageOverrideColumns: collectionType === "page" ? PAGE_SCHEMA_OVERRIDE_COLUMNS : []
38
+ };
39
+ });
@@ -0,0 +1,8 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ spreadsheetId: string;
3
+ sheets: {
4
+ label: string;
5
+ range: string;
6
+ }[];
7
+ }>>;
8
+ export default _default;
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { columnCountToRange } from "../utils/googleSheets.js";
3
+ import { getValidatedQuery, defineEventHandler, createError } from "h3";
4
+ import { useRuntimeConfig } from "#imports";
5
+ const querySchema = z.object({
6
+ spreadsheetId: z.string().length(44)
7
+ });
8
+ export default defineEventHandler(async (event) => {
9
+ const { googleSheetsImport } = useRuntimeConfig();
10
+ const apiKey = googleSheetsImport?.googleApiKeyRuntimeKey;
11
+ const { spreadsheetId } = await getValidatedQuery(event, (query) => querySchema.parse(query));
12
+ if (!apiKey || typeof apiKey !== "string") {
13
+ throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
14
+ }
15
+ const response = await $fetch(`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}?key=${apiKey}`);
16
+ return {
17
+ spreadsheetId,
18
+ sheets: (response.sheets ?? []).map((sheet) => {
19
+ const columnCount = sheet.properties?.gridProperties?.columnCount ?? 1;
20
+ return {
21
+ label: sheet.properties?.title ?? "",
22
+ range: columnCountToRange(columnCount)
23
+ };
24
+ }).filter((sheet) => sheet.label)
25
+ };
26
+ });
@@ -0,0 +1,6 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ headers: string[];
3
+ records: Record<string, unknown>[];
4
+ errors: string[];
5
+ }>>;
6
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { transformAndValidateRows } from "../utils/transform.js";
3
+ import { readBody, defineEventHandler, createError } from "h3";
4
+ import { useRuntimeConfig } from "#imports";
5
+ import { schemas } from "../../import/schemas.js";
6
+ const bodySchema = z.object({
7
+ spreadsheetId: z.string().length(44),
8
+ sheetTitle: z.string().min(1),
9
+ range: z.string().min(1),
10
+ schema: z.string().min(1)
11
+ });
12
+ export default defineEventHandler(async (event) => {
13
+ const body = bodySchema.parse(await readBody(event));
14
+ const { googleSheetsImport } = useRuntimeConfig();
15
+ const apiKey = googleSheetsImport?.googleApiKeyRuntimeKey;
16
+ if (!apiKey || typeof apiKey !== "string") {
17
+ throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
18
+ }
19
+ const encodedRange = encodeURIComponent(`${body.sheetTitle}!${body.range}`);
20
+ const googleResponse = await $fetch(`https://sheets.googleapis.com/v4/spreadsheets/${body.spreadsheetId}/values/${encodedRange}?key=${apiKey}`);
21
+ const values = googleResponse.values ?? [];
22
+ const schemaMap = schemas;
23
+ const schema = schemaMap[body.schema];
24
+ if (!schema) {
25
+ throw createError({ statusCode: 400, statusMessage: `Unknown schema: ${body.schema}` });
26
+ }
27
+ const transformed = transformAndValidateRows(values, schema);
28
+ return {
29
+ headers: values[0] ?? [],
30
+ records: transformed.records,
31
+ errors: transformed.errors
32
+ };
33
+ });
@@ -0,0 +1,11 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ count: number;
3
+ logs: string[];
4
+ contentDir: string;
5
+ summary: {
6
+ written: number;
7
+ overwritten: number;
8
+ skipped: number;
9
+ };
10
+ }>>;
11
+ export default _default;
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { writeRecordAsFrontmatter } from "../utils/writeFrontmatter.js";
3
+ import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
4
+ import { defineEventHandler, readBody } from "h3";
5
+ import { useRuntimeConfig } from "#imports";
6
+ const bodySchema = z.object({
7
+ records: z.array(z.record(z.string(), z.unknown())),
8
+ schema: z.string().optional(),
9
+ folder: z.string().min(1),
10
+ slugKey: z.string().min(1),
11
+ orderKey: z.string().optional(),
12
+ contentDir: z.string().optional(),
13
+ outputFormat: z.enum(["frontmatter", "json", "yaml"]).optional().default("frontmatter"),
14
+ overwriteMode: z.enum(["skip", "overwrite", "overwrite-frontmatter"]).optional().default("overwrite")
15
+ });
16
+ function resolveContentDirByCollectionType(collectionType, schemaKey, fallback) {
17
+ if (collectionType === "page") {
18
+ return "content";
19
+ }
20
+ if (collectionType === "data") {
21
+ return "content/data";
22
+ }
23
+ if (!schemaKey) {
24
+ return fallback;
25
+ }
26
+ const normalizedSchemaKey = schemaKey.trim();
27
+ console.warn(
28
+ `[google-sheets-import] No collection type mapping found for schema "${normalizedSchemaKey}". Using fallback content directory "${fallback}". Add googleSheetsImport.collectionTypeBySchema["` + normalizedSchemaKey + '"] in nuxt.config.ts to route writes automatically.'
29
+ );
30
+ return fallback;
31
+ }
32
+ export default defineEventHandler(async (event) => {
33
+ const body = bodySchema.parse(await readBody(event));
34
+ const config = useRuntimeConfig(event);
35
+ const moduleConfig = config.googleSheetsImport;
36
+ const mappedInConfig = moduleConfig.collectionTypeBySchema ?? {};
37
+ const resolvedCollectionType = await resolveCollectionTypeBySchema(body.schema, mappedInConfig);
38
+ const resolvedContentDir = body.contentDir ?? resolveContentDirByCollectionType(resolvedCollectionType, body.schema, moduleConfig.defaultContentDir);
39
+ const logs = [];
40
+ const summary = {
41
+ written: 0,
42
+ overwritten: 0,
43
+ skipped: 0
44
+ };
45
+ for (const record of body.records) {
46
+ const result = await writeRecordAsFrontmatter({
47
+ record,
48
+ baseContentDir: resolvedContentDir,
49
+ folder: body.folder,
50
+ slugKey: body.slugKey,
51
+ orderKey: body.orderKey,
52
+ outputFormat: body.outputFormat,
53
+ overwriteMode: body.overwriteMode
54
+ });
55
+ logs.push(result.filePath);
56
+ summary[result.action]++;
57
+ }
58
+ return {
59
+ count: logs.length,
60
+ logs,
61
+ contentDir: resolvedContentDir,
62
+ summary
63
+ };
64
+ });
@@ -0,0 +1,3 @@
1
+ type CollectionType = 'page' | 'data';
2
+ export declare function resolveCollectionTypeBySchema(schemaKey: string | undefined, collectionTypeBySchemaFromConfig: Record<string, CollectionType>): Promise<CollectionType | 'unknown'>;
3
+ export {};