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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
let cachedCollectionTypeBySchema = null;
|
|
4
|
+
function normalizeSchemaKey(key) {
|
|
5
|
+
const trimmed = key.trim();
|
|
6
|
+
const lower = trimmed.toLowerCase();
|
|
7
|
+
const snake = trimmed.replace(/[-\s]+/g, "_").toLowerCase();
|
|
8
|
+
return Array.from(/* @__PURE__ */ new Set([trimmed, lower, snake]));
|
|
9
|
+
}
|
|
10
|
+
function setCollectionType(map, key, type) {
|
|
11
|
+
for (const normalized of normalizeSchemaKey(key)) {
|
|
12
|
+
map[normalized] = type;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function readCollectionTypeBySchemaFromContentConfig() {
|
|
16
|
+
if (cachedCollectionTypeBySchema) {
|
|
17
|
+
return cachedCollectionTypeBySchema;
|
|
18
|
+
}
|
|
19
|
+
const result = {};
|
|
20
|
+
try {
|
|
21
|
+
const contentConfigPath = path.resolve(process.cwd(), "content.config.ts");
|
|
22
|
+
const source = await readFile(contentConfigPath, "utf-8");
|
|
23
|
+
const collectionTypeMatches = source.matchAll(/(\w+)\s*:\s*defineCollection\([\s\S]{0,900}?type:\s*['"](page|data)['"]/g);
|
|
24
|
+
for (const match of collectionTypeMatches) {
|
|
25
|
+
const collectionName = match[1];
|
|
26
|
+
const collectionType = match[2];
|
|
27
|
+
if (collectionName) {
|
|
28
|
+
setCollectionType(result, collectionName, collectionType);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const schemaTypeMatches = source.matchAll(/type:\s*['"](page|data)['"][\s\S]{0,900}?schema:\s*schemas\.(\w+)/g);
|
|
32
|
+
for (const match of schemaTypeMatches) {
|
|
33
|
+
const collectionType = match[1];
|
|
34
|
+
const schemaKey = match[2];
|
|
35
|
+
if (schemaKey) {
|
|
36
|
+
setCollectionType(result, schemaKey, collectionType);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
cachedCollectionTypeBySchema = {};
|
|
41
|
+
return cachedCollectionTypeBySchema;
|
|
42
|
+
}
|
|
43
|
+
cachedCollectionTypeBySchema = result;
|
|
44
|
+
return cachedCollectionTypeBySchema;
|
|
45
|
+
}
|
|
46
|
+
export async function resolveCollectionTypeBySchema(schemaKey, collectionTypeBySchemaFromConfig) {
|
|
47
|
+
if (!schemaKey) {
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
const mappedInContentConfig = await readCollectionTypeBySchemaFromContentConfig();
|
|
51
|
+
const map = {
|
|
52
|
+
...mappedInContentConfig,
|
|
53
|
+
...collectionTypeBySchemaFromConfig
|
|
54
|
+
};
|
|
55
|
+
const normalizedSchemaKey = schemaKey.trim();
|
|
56
|
+
const collectionType = normalizeSchemaKey(normalizedSchemaKey).map((key) => map[key]).find((value) => value === "page" || value === "data");
|
|
57
|
+
return collectionType ?? "unknown";
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function columnCountToRange(columnCount: number): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function columnCountToRange(columnCount) {
|
|
2
|
+
let dividend = columnCount;
|
|
3
|
+
let columnName = "";
|
|
4
|
+
while (dividend > 0) {
|
|
5
|
+
const modulo = (dividend - 1) % 26;
|
|
6
|
+
columnName = String.fromCharCode(65 + modulo) + columnName;
|
|
7
|
+
dividend = Math.floor((dividend - modulo) / 26);
|
|
8
|
+
}
|
|
9
|
+
return `A:${columnName}`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const PAGE_SCHEMA_OVERRIDE_COLUMNS = [
|
|
3
|
+
"path",
|
|
4
|
+
"title",
|
|
5
|
+
"description",
|
|
6
|
+
"seo.title",
|
|
7
|
+
"seo.description",
|
|
8
|
+
"seo.meta",
|
|
9
|
+
"seo.link",
|
|
10
|
+
"body.type",
|
|
11
|
+
"body.children",
|
|
12
|
+
"body.toc",
|
|
13
|
+
"navigation",
|
|
14
|
+
"navigation.title",
|
|
15
|
+
"navigation.description",
|
|
16
|
+
"navigation.icon"
|
|
17
|
+
];
|
|
18
|
+
function isZodType(value) {
|
|
19
|
+
return typeof value === "object" && value !== null && "_def" in value;
|
|
20
|
+
}
|
|
21
|
+
function getDefValue(schema, key) {
|
|
22
|
+
if (!schema || typeof schema !== "object") {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
const def = schema._def;
|
|
26
|
+
if (!def || typeof def !== "object") {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
return def[key];
|
|
30
|
+
}
|
|
31
|
+
function nextInnerSchema(schema) {
|
|
32
|
+
const candidate = getDefValue(schema, "innerType") ?? getDefValue(schema, "schema") ?? getDefValue(schema, "type") ?? getDefValue(schema, "in");
|
|
33
|
+
return isZodType(candidate) ? candidate : null;
|
|
34
|
+
}
|
|
35
|
+
function unwrapSchema(schema) {
|
|
36
|
+
let current = schema;
|
|
37
|
+
for (let index = 0; index < 20; index++) {
|
|
38
|
+
const next = nextInnerSchema(current);
|
|
39
|
+
if (!next || next === current) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
current = next;
|
|
43
|
+
}
|
|
44
|
+
return isZodType(current) ? current : null;
|
|
45
|
+
}
|
|
46
|
+
function getObjectShape(schema) {
|
|
47
|
+
const unwrapped = unwrapSchema(schema);
|
|
48
|
+
if (!unwrapped || !(unwrapped instanceof z.ZodObject)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const maybeShape = unwrapped.shape;
|
|
52
|
+
if (typeof maybeShape === "function") {
|
|
53
|
+
return maybeShape();
|
|
54
|
+
}
|
|
55
|
+
if (maybeShape && typeof maybeShape === "object") {
|
|
56
|
+
return maybeShape;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function collectSchemaColumns(schema, prefix = "") {
|
|
61
|
+
const unwrapped = unwrapSchema(schema);
|
|
62
|
+
if (!unwrapped) {
|
|
63
|
+
return prefix ? [prefix] : [];
|
|
64
|
+
}
|
|
65
|
+
if (unwrapped instanceof z.ZodArray) {
|
|
66
|
+
const arrayPrefix = `${prefix}[0]`;
|
|
67
|
+
const elementCandidate = getDefValue(unwrapped, "element");
|
|
68
|
+
const arrayElement = isZodType(elementCandidate) ? elementCandidate : null;
|
|
69
|
+
const nested = arrayElement ? collectSchemaColumns(arrayElement, arrayPrefix) : [];
|
|
70
|
+
return nested.length ? nested : [arrayPrefix];
|
|
71
|
+
}
|
|
72
|
+
const objectShape = getObjectShape(unwrapped);
|
|
73
|
+
if (objectShape) {
|
|
74
|
+
return Object.entries(objectShape).flatMap(([key, childSchema]) => {
|
|
75
|
+
const childPrefix = prefix ? `${prefix}.${key}` : key;
|
|
76
|
+
return collectSchemaColumns(childSchema, childPrefix);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return prefix ? [prefix] : [];
|
|
80
|
+
}
|
|
81
|
+
export function getSchemaColumns(schema) {
|
|
82
|
+
const columns = collectSchemaColumns(schema);
|
|
83
|
+
return Array.from(new Set(columns)).sort((left, right) => left.localeCompare(right));
|
|
84
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function isZodType(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "_def" in value;
|
|
4
|
+
}
|
|
5
|
+
function getDefValue(schema, key) {
|
|
6
|
+
if (!schema || typeof schema !== "object") {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
const def = schema._def;
|
|
10
|
+
if (!def || typeof def !== "object") {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
return def[key];
|
|
14
|
+
}
|
|
15
|
+
function nextInnerSchema(schema) {
|
|
16
|
+
const candidate = getDefValue(schema, "innerType") ?? getDefValue(schema, "schema") ?? getDefValue(schema, "type") ?? getDefValue(schema, "in");
|
|
17
|
+
return isZodType(candidate) ? candidate : null;
|
|
18
|
+
}
|
|
19
|
+
function unwrapSchema(schema) {
|
|
20
|
+
let current = schema;
|
|
21
|
+
for (let index = 0; index < 20; index++) {
|
|
22
|
+
const next = nextInnerSchema(current);
|
|
23
|
+
if (!next || next === current) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
current = next;
|
|
27
|
+
}
|
|
28
|
+
return isZodType(current) ? current : null;
|
|
29
|
+
}
|
|
30
|
+
function hasWrapper(schema, wrapper) {
|
|
31
|
+
let current = schema;
|
|
32
|
+
for (let index = 0; index < 20; index++) {
|
|
33
|
+
if (!current) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (wrapper === "optional" && current instanceof z.ZodOptional) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (wrapper === "nullable" && current instanceof z.ZodNullable) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const next = nextInnerSchema(current);
|
|
43
|
+
if (!next || next === current) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
current = next;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function parseHeaderPath(header) {
|
|
51
|
+
const tokens = [];
|
|
52
|
+
const parts = header.split(".");
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
const matches = part.matchAll(/([^[]+)|(\[(\d+)\])/g);
|
|
55
|
+
for (const match of matches) {
|
|
56
|
+
if (match[1]) {
|
|
57
|
+
tokens.push(match[1]);
|
|
58
|
+
}
|
|
59
|
+
if (match[3]) {
|
|
60
|
+
tokens.push(Number.parseInt(match[3], 10));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return tokens;
|
|
65
|
+
}
|
|
66
|
+
function setDeep(target, path, value) {
|
|
67
|
+
if (!path.length) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let cursor = target;
|
|
71
|
+
for (let index = 0; index < path.length; index++) {
|
|
72
|
+
const key = path[index];
|
|
73
|
+
if (key === void 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const isLast = index === path.length - 1;
|
|
77
|
+
const nextKey = path[index + 1];
|
|
78
|
+
if (isLast) {
|
|
79
|
+
cursor[key] = value;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (cursor[key] === void 0) {
|
|
83
|
+
cursor[key] = typeof nextKey === "number" ? [] : {};
|
|
84
|
+
}
|
|
85
|
+
const nextCursor = cursor[key];
|
|
86
|
+
if (!nextCursor || typeof nextCursor !== "object") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
cursor = nextCursor;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function getObjectShape(schema) {
|
|
93
|
+
const unwrapped = unwrapSchema(schema);
|
|
94
|
+
if (!unwrapped) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!(unwrapped instanceof z.ZodObject)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const maybeShape = unwrapped.shape;
|
|
101
|
+
if (typeof maybeShape === "function") {
|
|
102
|
+
const shape = maybeShape();
|
|
103
|
+
return shape;
|
|
104
|
+
}
|
|
105
|
+
if (maybeShape && typeof maybeShape === "object") {
|
|
106
|
+
return maybeShape;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function getSchemaAtPath(rootSchema, path) {
|
|
111
|
+
let current = rootSchema;
|
|
112
|
+
for (const segment of path) {
|
|
113
|
+
if (!current) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const unwrapped = unwrapSchema(current);
|
|
117
|
+
if (!unwrapped) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (typeof segment === "number") {
|
|
121
|
+
const fallbackElement = getDefValue(unwrapped, "element");
|
|
122
|
+
const arrayElement = unwrapped instanceof z.ZodArray ? unwrapped.element : isZodType(fallbackElement) ? fallbackElement : null;
|
|
123
|
+
if (arrayElement) {
|
|
124
|
+
current = arrayElement;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const shape = getObjectShape(unwrapped);
|
|
130
|
+
if (!shape || !(segment in shape)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
current = shape[segment] ?? null;
|
|
134
|
+
}
|
|
135
|
+
return current;
|
|
136
|
+
}
|
|
137
|
+
function toTypedCellValue(rawValue, schema) {
|
|
138
|
+
if (rawValue === void 0) {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
const value = rawValue.trim();
|
|
142
|
+
const isOptional = hasWrapper(schema, "optional");
|
|
143
|
+
const isNullable = hasWrapper(schema, "nullable");
|
|
144
|
+
if (value === "") {
|
|
145
|
+
if (isOptional) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
if (isNullable) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const unwrapped = schema ? unwrapSchema(schema) : null;
|
|
153
|
+
if (unwrapped instanceof z.ZodArray) {
|
|
154
|
+
if (value === "") {
|
|
155
|
+
return isOptional ? void 0 : isNullable ? null : [];
|
|
156
|
+
}
|
|
157
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
158
|
+
}
|
|
159
|
+
if (unwrapped instanceof z.ZodBoolean) {
|
|
160
|
+
if (/^(?:true|1|yes)$/i.test(value)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (/^(?:false|0|no)$/i.test(value)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (unwrapped instanceof z.ZodNumber) {
|
|
168
|
+
const parsed = Number(value);
|
|
169
|
+
if (!Number.isNaN(parsed)) {
|
|
170
|
+
return parsed;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function formatZodError(error) {
|
|
176
|
+
return error.issues.map((issue) => {
|
|
177
|
+
const path = issue.path.join(".");
|
|
178
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
179
|
+
}).join("; ");
|
|
180
|
+
}
|
|
181
|
+
export function transformAndValidateRows(values, schema) {
|
|
182
|
+
if (!values?.length) {
|
|
183
|
+
return { records: [], errors: [] };
|
|
184
|
+
}
|
|
185
|
+
const headers = values[0] ?? [];
|
|
186
|
+
const rows = values.slice(1);
|
|
187
|
+
if (!headers.length) {
|
|
188
|
+
return { records: [], errors: [] };
|
|
189
|
+
}
|
|
190
|
+
const records = [];
|
|
191
|
+
const errors = [];
|
|
192
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
193
|
+
const row = rows[rowIndex] ?? [];
|
|
194
|
+
const candidate = {};
|
|
195
|
+
for (let columnIndex = 0; columnIndex < headers.length; columnIndex++) {
|
|
196
|
+
const header = headers[columnIndex];
|
|
197
|
+
if (!header) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const path = parseHeaderPath(header);
|
|
201
|
+
if (!path.length) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const cellSchema = getSchemaAtPath(schema, path);
|
|
205
|
+
const cellValue = toTypedCellValue(row[columnIndex], cellSchema);
|
|
206
|
+
if (cellValue !== void 0) {
|
|
207
|
+
setDeep(candidate, path, cellValue);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const parsed = schema.safeParse(candidate);
|
|
211
|
+
if (!parsed.success) {
|
|
212
|
+
errors.push(`Row ${rowIndex + 2}: ${formatZodError(parsed.error)}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
records.push(parsed.data);
|
|
216
|
+
}
|
|
217
|
+
return { records, errors };
|
|
218
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type WriteOutputFormat = 'frontmatter' | 'json' | 'yaml';
|
|
2
|
+
export type WriteOverwriteMode = 'skip' | 'overwrite' | 'overwrite-frontmatter';
|
|
3
|
+
export type WriteAction = 'written' | 'overwritten' | 'skipped';
|
|
4
|
+
export interface WriteRecordInput {
|
|
5
|
+
record: Record<string, unknown>;
|
|
6
|
+
baseContentDir: string;
|
|
7
|
+
folder: string;
|
|
8
|
+
slugKey: string;
|
|
9
|
+
orderKey?: string;
|
|
10
|
+
outputFormat?: WriteOutputFormat;
|
|
11
|
+
overwriteMode?: WriteOverwriteMode;
|
|
12
|
+
}
|
|
13
|
+
export interface WriteRecordResult {
|
|
14
|
+
filePath: string;
|
|
15
|
+
action: WriteAction;
|
|
16
|
+
}
|
|
17
|
+
export declare function writeRecordAsFrontmatter(input: WriteRecordInput): Promise<WriteRecordResult>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
function sanitizePathSegment(segment) {
|
|
5
|
+
return segment.replace(/(^\/+|\/+$)/g, "").replace(/\.\./g, "");
|
|
6
|
+
}
|
|
7
|
+
function getByPath(object, keyPath) {
|
|
8
|
+
return keyPath.split(".").reduce((acc, key) => {
|
|
9
|
+
if (acc && typeof acc === "object") {
|
|
10
|
+
return acc[key];
|
|
11
|
+
}
|
|
12
|
+
return void 0;
|
|
13
|
+
}, object);
|
|
14
|
+
}
|
|
15
|
+
async function fileExists(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
await access(filePath);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function extractMarkdownBody(existingFileContent) {
|
|
24
|
+
const frontmatterMatch = existingFileContent.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
25
|
+
if (!frontmatterMatch) {
|
|
26
|
+
return existingFileContent;
|
|
27
|
+
}
|
|
28
|
+
return existingFileContent.slice(frontmatterMatch[0].length);
|
|
29
|
+
}
|
|
30
|
+
function getFileExtension(format) {
|
|
31
|
+
if (format === "json") {
|
|
32
|
+
return "json";
|
|
33
|
+
}
|
|
34
|
+
if (format === "yaml") {
|
|
35
|
+
return "yml";
|
|
36
|
+
}
|
|
37
|
+
return "md";
|
|
38
|
+
}
|
|
39
|
+
function stringifyRecord(record, format) {
|
|
40
|
+
if (format === "json") {
|
|
41
|
+
return `${JSON.stringify(record, null, 2)}
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
const yamlBody = yaml.dump(record, { lineWidth: 360 });
|
|
45
|
+
if (format === "yaml") {
|
|
46
|
+
return yamlBody;
|
|
47
|
+
}
|
|
48
|
+
return `---
|
|
49
|
+
${yamlBody}---
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
export async function writeRecordAsFrontmatter(input) {
|
|
53
|
+
const projectRoot = process.cwd();
|
|
54
|
+
const contentRoot = path.resolve(projectRoot, sanitizePathSegment(input.baseContentDir));
|
|
55
|
+
const targetFolder = path.resolve(contentRoot, sanitizePathSegment(input.folder));
|
|
56
|
+
const outputFormat = input.outputFormat ?? "frontmatter";
|
|
57
|
+
const overwriteMode = input.overwriteMode ?? "overwrite";
|
|
58
|
+
if (!targetFolder.startsWith(contentRoot)) {
|
|
59
|
+
throw new Error("Invalid destination folder");
|
|
60
|
+
}
|
|
61
|
+
await mkdir(targetFolder, { recursive: true });
|
|
62
|
+
const slug = String(getByPath(input.record, input.slugKey) ?? "").trim();
|
|
63
|
+
if (!slug) {
|
|
64
|
+
throw new Error(`Missing slug value for key "${input.slugKey}"`);
|
|
65
|
+
}
|
|
66
|
+
const order = input.orderKey ? String(getByPath(input.record, input.orderKey) ?? "").trim() : "";
|
|
67
|
+
const extension = getFileExtension(outputFormat);
|
|
68
|
+
const fileName = `${order ? `${order}.` : ""}${slug}.${extension}`;
|
|
69
|
+
const filePath = path.resolve(targetFolder, fileName);
|
|
70
|
+
const exists = await fileExists(filePath);
|
|
71
|
+
if (exists && overwriteMode === "skip") {
|
|
72
|
+
return {
|
|
73
|
+
filePath,
|
|
74
|
+
action: "skipped"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (exists && overwriteMode === "overwrite-frontmatter" && outputFormat === "frontmatter") {
|
|
78
|
+
const existingFileContent = await readFile(filePath, "utf-8");
|
|
79
|
+
const markdownBody = extractMarkdownBody(existingFileContent);
|
|
80
|
+
const nextFrontmatter = stringifyRecord(input.record, "frontmatter");
|
|
81
|
+
await writeFile(filePath, `${nextFrontmatter}${markdownBody}`, "utf-8");
|
|
82
|
+
return {
|
|
83
|
+
filePath,
|
|
84
|
+
action: "overwritten"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
await writeFile(filePath, stringifyRecord(input.record, outputFormat), "utf-8");
|
|
88
|
+
return {
|
|
89
|
+
filePath,
|
|
90
|
+
action: exists ? "overwritten" : "written"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export type GoogleSheetsApiValues = string[][];
|
|
2
|
+
export interface GoogleSheetsApiValuesResponse {
|
|
3
|
+
range: string;
|
|
4
|
+
majorDimension: string;
|
|
5
|
+
values: GoogleSheetsApiValues;
|
|
6
|
+
}
|
|
7
|
+
export type GoogleSheetsApiSheet = {
|
|
8
|
+
properties: {
|
|
9
|
+
sheetId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
index: number;
|
|
12
|
+
sheetType: string;
|
|
13
|
+
gridProperties: {
|
|
14
|
+
rowCount: number;
|
|
15
|
+
columnCount: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export interface GoogleSheetsApiSheetResponse {
|
|
20
|
+
spreadsheetId: string;
|
|
21
|
+
properties: {
|
|
22
|
+
title: string;
|
|
23
|
+
locale: string;
|
|
24
|
+
autoRecalc: string;
|
|
25
|
+
timeZone: string;
|
|
26
|
+
defaultFormat: {
|
|
27
|
+
backgroundColor: {
|
|
28
|
+
red: number;
|
|
29
|
+
green: number;
|
|
30
|
+
blue: number;
|
|
31
|
+
};
|
|
32
|
+
padding: {
|
|
33
|
+
top: number;
|
|
34
|
+
right: number;
|
|
35
|
+
bottom: number;
|
|
36
|
+
left: number;
|
|
37
|
+
};
|
|
38
|
+
verticalAlignment: string;
|
|
39
|
+
wrapStrategy: string;
|
|
40
|
+
textFormat: {
|
|
41
|
+
foregroundColorStyle: {
|
|
42
|
+
rgbColor: {
|
|
43
|
+
red: number;
|
|
44
|
+
green: number;
|
|
45
|
+
blue: number;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
foregroundColor: {
|
|
49
|
+
red: number;
|
|
50
|
+
green: number;
|
|
51
|
+
blue: number;
|
|
52
|
+
};
|
|
53
|
+
fontSize: number;
|
|
54
|
+
bold: boolean;
|
|
55
|
+
italic: boolean;
|
|
56
|
+
strikethrough: boolean;
|
|
57
|
+
underline: boolean;
|
|
58
|
+
fontFamily: string;
|
|
59
|
+
};
|
|
60
|
+
backgroundColorStyle: {
|
|
61
|
+
rgbColor: {
|
|
62
|
+
red: number;
|
|
63
|
+
green: number;
|
|
64
|
+
blue: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
sheets: GoogleSheetsApiSheet[];
|
|
70
|
+
spreadsheetUrl: string;
|
|
71
|
+
}
|
|
72
|
+
export interface ProductObject {
|
|
73
|
+
[key: string]: string;
|
|
74
|
+
}
|
|
75
|
+
export type TransformedGoogleSheetsApiResult = ProductObject[];
|
|
File without changes
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-google-sheets-import",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Schema-driven Google Sheets import module for Nuxt Content",
|
|
5
|
+
"repository": "tribeweb/nuxt-google-sheets-import",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types.d.mts",
|
|
11
|
+
"import": "./dist/module.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/module.mjs",
|
|
15
|
+
"typesVersions": {
|
|
16
|
+
"*": {
|
|
17
|
+
".": [
|
|
18
|
+
"./dist/types.d.mts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"workspaces": [
|
|
26
|
+
"playground"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"prepack": "nuxt-module-build build",
|
|
30
|
+
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
31
|
+
"dev:build": "nuxt build playground",
|
|
32
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
|
|
33
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"lint:fix": "eslint . --fix",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest watch",
|
|
38
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@nuxt/content": "^3.12.0",
|
|
42
|
+
"@nuxt/kit": "^4.3.1",
|
|
43
|
+
"@nuxt/ui": "^4.5.1",
|
|
44
|
+
"better-sqlite3": "^12.6.2",
|
|
45
|
+
"js-yaml": "^4.1.1",
|
|
46
|
+
"tailwindcss": "^4.2.1",
|
|
47
|
+
"zod": "^4.3.6"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@iconify-json/heroicons": "^1.2.3",
|
|
51
|
+
"@iconify-json/lucide": "^1.2.95",
|
|
52
|
+
"@nuxt/devtools": "^3.2.1",
|
|
53
|
+
"@nuxt/eslint-config": "^1.15.1",
|
|
54
|
+
"@nuxt/module-builder": "^1.0.2",
|
|
55
|
+
"@nuxt/schema": "^4.3.1",
|
|
56
|
+
"@nuxt/test-utils": "^4.0.0",
|
|
57
|
+
"@types/js-yaml": "^4.0.9",
|
|
58
|
+
"@types/node": "latest",
|
|
59
|
+
"changelogen": "^0.6.2",
|
|
60
|
+
"eslint": "^10.0.1",
|
|
61
|
+
"nuxt": "^4.3.1",
|
|
62
|
+
"typescript": "~5.9.3",
|
|
63
|
+
"vitest": "^4.0.18",
|
|
64
|
+
"vue-tsc": "^3.2.5"
|
|
65
|
+
}
|
|
66
|
+
}
|