Error Fix and Console Logging format.

This commit is contained in:
2026-07-17 11:39:58 +01:00
parent 7e45c40c8a
commit 26702cec4f
7 changed files with 261 additions and 107 deletions
+27 -24
View File
@@ -1,7 +1,28 @@
const fs = require('fs');
const path = require('path');
const {google} = require('googleapis');
const {applyRateLimitBuffer} = require('./rate');
const {fetchSpreadsheetValuesBatch} = require('./sheets');
function getConfigBaseDir(config) {
const configPath = config && (config.__configPath || config.configPath);
if (configPath) {
return path.dirname(configPath);
}
return process.cwd();
}
function resolveConfigPath(config, candidatePath) {
if (!candidatePath) {
return '';
}
if (path.isAbsolute(candidatePath)) {
return candidatePath;
}
return path.resolve(getConfigBaseDir(config), candidatePath);
}
function loadApiKeys(config) {
const apiKeys = [];
@@ -60,7 +81,7 @@ function createServiceAccountBatchFetcher(config) {
// selecting a credential whose slot is available now. If none are
// immediately available, wait the minimum required time.
const fetchers = accounts.map(({path: credentialsPath, rateLimitPerMinute}) => {
const resolvedPath = path.isAbsolute(credentialsPath) ? credentialsPath : path.resolve(process.cwd(), credentialsPath);
const resolvedPath = resolveConfigPath(config, credentialsPath);
if (!fs.existsSync(resolvedPath)) {
throw new Error(`Service account credentials not found: ${resolvedPath}`);
}
@@ -84,16 +105,7 @@ function createServiceAccountBatchFetcher(config) {
const interval = Math.ceil(60000 / bufferedRateLimit);
const fetchRaw = async (documentId, sheetNames) => {
const sheets = google.sheets({version: 'v4', auth: authClient});
const request = {spreadsheetId: documentId, ranges: sheetNames};
const response = await sheets.spreadsheets.values.batchGet(request);
const valueRanges = response.data.valueRanges || [];
const rowsBySheet = {};
for (let i = 0; i < sheetNames.length; i += 1) {
const sheetName = sheetNames[i];
rowsBySheet[sheetName] = valueRanges[i]?.values || [];
}
return rowsBySheet;
return fetchSpreadsheetValuesBatch({authClient}, documentId, sheetNames);
};
return {authClient, fetchRaw, interval, nextAvailable: 0};
@@ -148,16 +160,7 @@ function createApiKeyBatchFetcher(config) {
const interval = Math.ceil(60000 / bufferedRateLimit);
const fetchRaw = async (documentId, sheetNames) => {
const sheets = google.sheets({version: 'v4'});
const request = {spreadsheetId: documentId, ranges: sheetNames, key};
const response = await sheets.spreadsheets.values.batchGet(request);
const valueRanges = response.data.valueRanges || [];
const rowsBySheet = {};
for (let i = 0; i < sheetNames.length; i += 1) {
const sheetName = sheetNames[i];
rowsBySheet[sheetName] = valueRanges[i]?.values || [];
}
return rowsBySheet;
return fetchSpreadsheetValuesBatch({apiKey: key}, documentId, sheetNames);
};
return {key, fetchRaw, interval, nextAvailable: 0};
@@ -215,7 +218,7 @@ function getFirstServiceAccountAuthClient(config) {
const credentialsPath = entryObj.path || entryObj.credentialsPath;
if (!credentialsPath) return null;
const resolvedPath = path.isAbsolute(credentialsPath) ? credentialsPath : path.resolve(process.cwd(), credentialsPath);
const resolvedPath = resolveConfigPath(config, credentialsPath);
if (!fs.existsSync(resolvedPath)) return null;
try {
@@ -235,7 +238,7 @@ function loadAuth(config) {
if (config.credentialsPath) {
const credentialsPath = config.credentialsPath;
const resolvedPath = path.isAbsolute(credentialsPath) ? credentialsPath : path.resolve(process.cwd(), credentialsPath);
const resolvedPath = resolveConfigPath(config, credentialsPath);
if (!fs.existsSync(resolvedPath)) {
throw new Error(`Credentials file not found: ${resolvedPath}`);
}
+6
View File
@@ -118,6 +118,12 @@ function loadConfig() {
const json = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(json);
Object.defineProperty(config, '__configPath', {
value: configPath,
enumerable: false,
configurable: true,
writable: true,
});
validateConfig(config);
return config;
}
+7 -21
View File
@@ -1,7 +1,7 @@
const readline = require('readline');
const {google} = require('googleapis');
const {createStarterConfig, getDefaultConfigPath, writeConfigFile} = require('./config');
const {getFirstServiceAccountAuthClient} = require('./auth');
const {fetchSpreadsheetTitle} = require('./sheets');
function createPrompt() {
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
@@ -82,26 +82,6 @@ async function pauseForEnter(ask) {
await ask('Press Enter to close this window...');
}
async function fetchSpreadsheetTitle(context, documentId) {
const sheetOptions = {version: 'v4'};
if (context.authClient) {
sheetOptions.auth = context.authClient;
}
const sheets = google.sheets(sheetOptions);
const request = {
spreadsheetId: documentId,
fields: 'properties/title',
};
if (context.apiKey) {
request.key = context.apiKey;
}
const response = await sheets.spreadsheets.get(request);
return response.data.properties?.title || documentId;
}
function buildTitleContext(authMode, config) {
if (authMode === 'apiKeys' && Array.isArray(config.apiKeys) && config.apiKeys.length > 0) {
return {apiKey: config.apiKeys[0].key};
@@ -162,6 +142,12 @@ async function runOnboarding() {
serviceAccounts: [],
documents: [],
};
Object.defineProperty(config, '__configPath', {
value: configPath,
enumerable: false,
configurable: true,
writable: true,
});
if (authMode === 'apiKeys') {
for (let index = 0; index < credentialCount; index += 1) {
+149
View File
@@ -0,0 +1,149 @@
const https = require('https');
const {URL} = require('url');
function parseResponseBody(body) {
if (!body) {
return {};
}
try {
return JSON.parse(body);
} catch (_) {
return body;
}
}
function extractErrorMessage(data) {
if (!data) {
return '';
}
if (typeof data === 'string') {
return data;
}
if (data.error && typeof data.error === 'object') {
if (typeof data.error.message === 'string' && data.error.message) {
return data.error.message;
}
if (Array.isArray(data.error.errors) && data.error.errors.length > 0) {
const firstError = data.error.errors[0];
if (firstError && typeof firstError.message === 'string' && firstError.message) {
return firstError.message;
}
}
}
if (typeof data.message === 'string' && data.message) {
return data.message;
}
return '';
}
function requestJson(urlString, headers = {}) {
return new Promise((resolve, reject) => {
const url = new URL(urlString);
const request = https.request(
url,
{
method: 'GET',
headers: Object.assign({Accept: 'application/json'}, headers),
},
response => {
let body = '';
response.setEncoding('utf8');
response.on('data', chunk => {
body += chunk;
});
response.on('end', () => {
const data = parseResponseBody(body);
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve({status: response.statusCode, headers: response.headers, data});
return;
}
const message = extractErrorMessage(data) || `Request failed with status ${response.statusCode}`;
const error = new Error(message);
error.response = {status: response.statusCode, headers: response.headers, data};
reject(error);
});
}
);
request.on('error', reject);
request.end();
});
}
async function requestSheetsJson(pathname, context, queryParams = {}) {
const url = new URL(`https://sheets.googleapis.com${pathname}`);
for (const [key, value] of Object.entries(queryParams)) {
if (Array.isArray(value)) {
for (const item of value) {
url.searchParams.append(key, item);
}
} else if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value));
}
}
const headers = {};
if (context && context.authClient) {
if (typeof context.authClient.getRequestHeaders === 'function') {
const authHeaders = await context.authClient.getRequestHeaders(url.toString());
Object.assign(headers, authHeaders || {});
} else if (typeof context.authClient.getAccessToken === 'function') {
const token = await context.authClient.getAccessToken();
const accessToken = token && typeof token === 'object' ? token.token || token.access_token : token;
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
}
}
}
if (context && context.apiKey) {
url.searchParams.set('key', context.apiKey);
}
return requestJson(url.toString(), headers);
}
async function fetchSpreadsheetTitle(context, documentId) {
const response = await requestSheetsJson(
`/v4/spreadsheets/${encodeURIComponent(documentId)}`,
context,
{fields: 'properties/title'}
);
return response.data && response.data.properties && response.data.properties.title || documentId;
}
async function fetchSpreadsheetValuesBatch(context, documentId, sheetNames) {
const response = await requestSheetsJson(
`/v4/spreadsheets/${encodeURIComponent(documentId)}/values:batchGet`,
context,
{ranges: sheetNames}
);
const valueRanges = response.data.valueRanges || [];
const rowsBySheet = {};
for (let i = 0; i < sheetNames.length; i += 1) {
const sheetName = sheetNames[i];
rowsBySheet[sheetName] = valueRanges[i] && valueRanges[i].values ? valueRanges[i].values : [];
}
return rowsBySheet;
}
module.exports = {
fetchSpreadsheetTitle,
fetchSpreadsheetValuesBatch,
};