google-sheets-automation 0.1.0 → 0.1.1

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 CHANGED
@@ -1,26 +1,38 @@
1
1
 
2
- # google-sheets-automation
2
+ <div align="center">
3
+ <img src="https://www.gstatic.com/images/icons/material/system/2x/sheets_googlegreen_48dp.png" width="64" alt="Google Sheets Logo" />
4
+ <h1>google-sheets-automation</h1>
5
+ <p>Automate Google Sheets from Node.js: add, update, and read rows with a simple API.</p>
6
+ </div>
3
7
 
4
- Simplify populating sheets from inputs. Designed to be extensible for multiple sheet providers, but currently only supports Google Sheets. Usable in Node.js projects (backend or CLI).
8
+ ---
5
9
 
6
- # google-sheets-automation
7
- - Add rows, update cells, batch updates
8
- Automate populating and managing Google Sheets from Node.js. Usable in backend, CLI, or serverless environments.
9
- - Accepts objects, arrays, or form data
10
- - Simple, promise-based API
11
- - Add rows, update cells, batch updates
12
- - Google Sheets API support
13
- - Accepts objects, arrays, or form data
14
- - Simple, promise-based API
10
+ ## Overview
15
11
 
16
- ```js
17
- ## Usage Example
12
+ `google-sheets-automation` is a Node.js package for programmatically managing Google Sheets. Easily add, update, and read rows using service account credentials. Designed for backend, CLI, and serverless environments.
13
+
14
+ ## Features
15
+ - Add rows with optional header mapping
16
+ - Update cells in bulk (A1 notation)
17
+ - Read sheet data as objects
18
+ - Batch operations
19
+ - Promise-based API
20
+ - Secure authentication via Google service accounts
21
+
22
+ ## Installation
23
+
24
+ ```bash
18
25
  npm install google-sheets-automation
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```js
19
31
  import { GoogleSheetProvider } from 'google-sheets-automation';
20
32
 
21
33
  const provider = new GoogleSheetProvider({
22
34
  serviceAccount: {
23
- import { GoogleSheetProvider } from 'google-sheets-automation';
35
+ client_email: 'your-service-account-email@project.iam.gserviceaccount.com',
24
36
  private_key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n',
25
37
  },
26
38
  sheetId: 'your-google-sheet-id',
@@ -37,9 +49,91 @@ const rows = [
37
49
  { name: 'Bob', email: 'bob@example.com', message: 'Hi!' },
38
50
  ];
39
51
 
40
- await provider.addRows(rows, headerMap);
52
+ await provider.addRows({ spreadsheetId: 'your-google-sheet-id', sheetName: 'Sheet1', headerMap }, rows);
41
53
  ```
42
54
 
55
+ ## Environment Setup
56
+
57
+ 1. **Create a Google Cloud project** and enable the Google Sheets API.
58
+ 2. **Create a service account** and download the JSON key.
59
+ 3. **Share your target Google Sheet** with the service account email.
60
+ 4. Store credentials in your `.env`:
61
+ ```env
62
+ GOOGLE_SERVICE_ACCOUNT_EMAIL=your-service-account-email@project.iam.gserviceaccount.com
63
+ GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
64
+ GOOGLE_SHEET_ID=your-google-sheet-id
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ### GoogleSheetProvider
70
+
71
+ #### `addRows(options, rows)`
72
+ Add rows to a sheet. If `headerMap` is provided and the sheet is empty, headers are added automatically.
73
+
74
+ #### `updateCells(options)`
75
+ Update cells in a sheet using A1 notation and a 2D array of values.
76
+
77
+ #### `readData(options)`
78
+ Read all data from a sheet, returned as an array of objects (first row is used as keys).
79
+
80
+ #### `deleteRows(options)`
81
+ Delete rows by index (not yet implemented).
82
+
83
+ #### `createSheet(options)`
84
+ Create a new sheet (not yet implemented).
85
+
86
+ ### Types
87
+
88
+ - `AddRowsOptions`: `{ spreadsheetId, sheetName, headerMap? }`
89
+ - `UpdateCellsOptions`: `{ spreadsheetId, sheetName, range?, values }`
90
+ - `ReadDataOptions`: `{ spreadsheetId, sheetName, range? }`
91
+ - `DeleteRowsOptions`: `{ spreadsheetId, sheetName, rowIndexes }`
92
+ - `CreateSheetOptions`: `{ spreadsheetId, sheetName }`
93
+
94
+ ## Advanced Usage
95
+
96
+ ### Update Cells Example
97
+ ```js
98
+ await provider.updateCells({
99
+ spreadsheetId: 'your-google-sheet-id',
100
+ sheetName: 'Sheet1',
101
+ range: 'Sheet1!A2:B2',
102
+ values: [['Alice', 'alice@example.com']]
103
+ });
104
+ ```
105
+
106
+ ### Read Data Example
107
+ ```js
108
+ const data = await provider.readData({
109
+ spreadsheetId: 'your-google-sheet-id',
110
+ sheetName: 'Sheet1'
111
+ });
112
+ console.log(data);
113
+ ```
114
+
115
+ ## Testing
116
+
117
+ ### Unit Tests
118
+ Run all unit tests (mocks Google API calls):
119
+ ```bash
120
+ npm run test
121
+ ```
122
+
123
+ ### Integration Tests
124
+ Run integration tests against the real Google Sheets API (requires valid `.env` and sheet setup):
125
+ ```bash
126
+ npm run test test/providers/googleProvider.integration.test.ts
127
+ ```
128
+
129
+ ## Contributing
130
+
131
+ Pull requests and issues are welcome! Please open an issue for feature requests or bugs.
132
+
133
+ ## License
134
+
135
+ MIT
136
+
43
137
  ## Testing
44
138
  Unit tests are written with Jest and mock all Google API calls, so no credentials are required:
45
139
  ```bash
@@ -0,0 +1 @@
1
+ export * from './providers/google';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './providers/google';
@@ -0,0 +1,2 @@
1
+ export { GoogleSheetProvider, ISheetProvider } from './googleProvider';
2
+ export { SheetClient, ISheetClient } from './sheetClient';
@@ -0,0 +1,2 @@
1
+ export { GoogleSheetProvider } from './googleProvider';
2
+ export { SheetClient } from './sheetClient';
@@ -0,0 +1,26 @@
1
+ import { SheetRow, AddRowsOptions, UpdateCellsOptions, ReadDataOptions, DeleteRowsOptions, CreateSheetOptions } from './types';
2
+ export interface ISheetProvider {
3
+ addRows(options: AddRowsOptions, rows: SheetRow[]): Promise<void>;
4
+ updateCells(options: UpdateCellsOptions): Promise<void>;
5
+ readData(options: ReadDataOptions): Promise<SheetRow[]>;
6
+ deleteRows(options: DeleteRowsOptions): Promise<void>;
7
+ createSheet(options: CreateSheetOptions): Promise<void>;
8
+ }
9
+ export declare class GoogleSheetProvider implements ISheetProvider {
10
+ private getBaseUrl;
11
+ private buildUrl;
12
+ private readonly credentials;
13
+ private _accessToken;
14
+ private _tokenExpiry;
15
+ constructor(credentials: any);
16
+ /**
17
+ * Get a valid access token, either from credentials or by generating from service account.
18
+ */
19
+ private getAccessToken;
20
+ addRows(options: AddRowsOptions, rows: SheetRow[]): Promise<void>;
21
+ updateCells(options: UpdateCellsOptions): Promise<void>;
22
+ readData(options: ReadDataOptions): Promise<SheetRow[]>;
23
+ deleteRows(options: DeleteRowsOptions): Promise<void>;
24
+ createSheet(options: CreateSheetOptions): Promise<void>;
25
+ static mapHeaders(input: SheetRow, headerMap?: Record<string, string>): SheetRow;
26
+ }
@@ -0,0 +1,168 @@
1
+ // Only import google-auth-library in Node.js
2
+ let GoogleAuth = undefined;
3
+ if (typeof process !== 'undefined' && process.versions?.node) {
4
+ try {
5
+ GoogleAuth = require('google-auth-library').GoogleAuth;
6
+ }
7
+ catch { }
8
+ }
9
+ export class GoogleSheetProvider {
10
+ getBaseUrl() {
11
+ return 'https://sheets.googleapis.com/v4/spreadsheets';
12
+ }
13
+ buildUrl(path, params) {
14
+ let url = `${this.getBaseUrl()}/${path}`;
15
+ if (params) {
16
+ const query = Object.entries(params)
17
+ .filter(([_, v]) => v !== undefined)
18
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
19
+ .join('&');
20
+ if (query)
21
+ url += `?${query}`;
22
+ }
23
+ return url;
24
+ }
25
+ constructor(credentials) {
26
+ this._accessToken = null;
27
+ this._tokenExpiry = null;
28
+ this.credentials = credentials;
29
+ }
30
+ /**
31
+ * Get a valid access token, either from credentials or by generating from service account.
32
+ */
33
+ async getAccessToken() {
34
+ if (this.credentials.accessToken) {
35
+ return this.credentials.accessToken;
36
+ }
37
+ // If service account credentials are provided (Node.js only)
38
+ if (GoogleAuth && this.credentials.private_key && this.credentials.client_email) {
39
+ if (this._accessToken && this._tokenExpiry && Date.now() < this._tokenExpiry - 60000) {
40
+ return this._accessToken;
41
+ }
42
+ const auth = new GoogleAuth({
43
+ credentials: this.credentials,
44
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
45
+ });
46
+ const client = await auth.getClient();
47
+ const { token, res } = await client.getAccessToken();
48
+ let expiry = Date.now() + 50 * 60 * 1000;
49
+ if (res?.data?.expiry_date) {
50
+ expiry = res.data.expiry_date;
51
+ }
52
+ this._accessToken = token;
53
+ this._tokenExpiry = expiry;
54
+ return token;
55
+ }
56
+ throw new Error('No valid access token or service account credentials provided.');
57
+ }
58
+ async addRows(options, rows) {
59
+ const { spreadsheetId, sheetName, headerMap } = options;
60
+ const accessToken = await this.getAccessToken();
61
+ let values = [];
62
+ // If headerMap is passed and sheet is empty, add header row first
63
+ if (headerMap) {
64
+ // Check if sheet is empty by reading first row
65
+ const readUrl = this.buildUrl(`${spreadsheetId}/values/${encodeURIComponent(sheetName)}`, { majorDimension: 'ROWS', range: sheetName });
66
+ const readRes = await fetch(readUrl, {
67
+ method: 'GET',
68
+ headers: {
69
+ 'Authorization': `Bearer ${accessToken}`,
70
+ },
71
+ });
72
+ let sheetIsEmpty = true;
73
+ if (readRes.ok) {
74
+ const data = await readRes.json();
75
+ if (data.values && data.values.length > 0) {
76
+ sheetIsEmpty = false;
77
+ }
78
+ }
79
+ if (sheetIsEmpty) {
80
+ // Add header row
81
+ values.push(Object.values(headerMap));
82
+ }
83
+ }
84
+ // Map headers if needed
85
+ const mappedRows = headerMap
86
+ ? rows.map(row => GoogleSheetProvider.mapHeaders(row, headerMap))
87
+ : rows;
88
+ values = values.concat(mappedRows.map(row => Object.values(row)));
89
+ // Prepare API request
90
+ const url = this.buildUrl(`${spreadsheetId}/values/${encodeURIComponent(sheetName)}:append`, { valueInputOption: 'USER_ENTERED' });
91
+ const body = JSON.stringify({ values });
92
+ const res = await fetch(url, {
93
+ method: 'POST',
94
+ headers: {
95
+ 'Authorization': `Bearer ${accessToken}`,
96
+ 'Content-Type': 'application/json'
97
+ },
98
+ body
99
+ });
100
+ if (!res.ok) {
101
+ const err = await res.text();
102
+ throw new Error(`Google Sheets API error: ${res.status} ${err}`);
103
+ }
104
+ }
105
+ async updateCells(options) {
106
+ const { spreadsheetId, sheetName, range, values } = options;
107
+ const accessToken = await this.getAccessToken();
108
+ // values should be a 2D array matching the range
109
+ const url = this.buildUrl(`${spreadsheetId}/values/${encodeURIComponent(range || sheetName)}`, { valueInputOption: 'USER_ENTERED' });
110
+ const body = JSON.stringify({ values });
111
+ const res = await fetch(url, {
112
+ method: 'PUT',
113
+ headers: {
114
+ 'Authorization': `Bearer ${accessToken}`,
115
+ 'Content-Type': 'application/json'
116
+ },
117
+ body
118
+ });
119
+ if (!res.ok) {
120
+ const err = await res.text();
121
+ throw new Error(`Google Sheets API error: ${res.status} ${err}`);
122
+ }
123
+ }
124
+ async readData(options) {
125
+ const { spreadsheetId, sheetName, range } = options;
126
+ const accessToken = await this.getAccessToken();
127
+ const url = this.buildUrl(`${spreadsheetId}/values/${encodeURIComponent(range || sheetName)}`, { majorDimension: 'ROWS' });
128
+ const res = await fetch(url, {
129
+ method: 'GET',
130
+ headers: {
131
+ 'Authorization': `Bearer ${accessToken}`,
132
+ },
133
+ });
134
+ if (!res.ok) {
135
+ const err = await res.text();
136
+ throw new Error(`Google Sheets API error: ${res.status} ${err}`);
137
+ }
138
+ const data = await res.json();
139
+ if (!data.values || data.values.length === 0)
140
+ return [];
141
+ // Assume first row is header
142
+ const headers = data.values[0];
143
+ const rows = [];
144
+ for (let i = 1; i < data.values.length; i++) {
145
+ const row = {};
146
+ for (let j = 0; j < headers.length; j++) {
147
+ row[headers[j]] = data.values[i][j];
148
+ }
149
+ rows.push(row);
150
+ }
151
+ return rows;
152
+ }
153
+ async deleteRows(options) {
154
+ // TODO: Implement row deletion logic
155
+ }
156
+ async createSheet(options) {
157
+ // TODO: Implement sheet creation logic
158
+ }
159
+ static mapHeaders(input, headerMap) {
160
+ if (!headerMap)
161
+ return input;
162
+ const mapped = {};
163
+ for (const key in input) {
164
+ mapped[headerMap[key] || key] = input[key];
165
+ }
166
+ return mapped;
167
+ }
168
+ }
@@ -0,0 +1,21 @@
1
+ import { SheetRow, AddRowsOptions, UpdateCellsOptions, ReadDataOptions, DeleteRowsOptions, CreateSheetOptions } from './types';
2
+ export interface ISheetClient {
3
+ addRows(options: AddRowsOptions, rows: SheetRow[]): Promise<void>;
4
+ updateCells(options: UpdateCellsOptions): Promise<void>;
5
+ readData(options: ReadDataOptions): Promise<SheetRow[]>;
6
+ deleteRows(options: DeleteRowsOptions): Promise<void>;
7
+ createSheet(options: CreateSheetOptions): Promise<void>;
8
+ }
9
+ export declare class SheetClient implements ISheetClient {
10
+ private readonly provider;
11
+ constructor(config: {
12
+ provider: string;
13
+ credentials: any;
14
+ });
15
+ addRows(options: AddRowsOptions, rows: SheetRow[]): Promise<void>;
16
+ updateCells(options: UpdateCellsOptions): Promise<void>;
17
+ readData(options: ReadDataOptions): Promise<SheetRow[]>;
18
+ deleteRows(options: DeleteRowsOptions): Promise<void>;
19
+ createSheet(options: CreateSheetOptions): Promise<void>;
20
+ static mapHeaders(input: SheetRow, headerMap?: Record<string, string>): SheetRow;
21
+ }
@@ -0,0 +1,29 @@
1
+ import { GoogleSheetProvider } from './googleProvider';
2
+ export class SheetClient {
3
+ constructor(config) {
4
+ if (config.provider === 'google') {
5
+ this.provider = new GoogleSheetProvider(config.credentials);
6
+ }
7
+ else {
8
+ throw new Error('Provider not supported');
9
+ }
10
+ }
11
+ addRows(options, rows) {
12
+ return this.provider.addRows(options, rows);
13
+ }
14
+ updateCells(options) {
15
+ return this.provider.updateCells(options);
16
+ }
17
+ readData(options) {
18
+ return this.provider.readData(options);
19
+ }
20
+ deleteRows(options) {
21
+ return this.provider.deleteRows(options);
22
+ }
23
+ createSheet(options) {
24
+ return this.provider.createSheet(options);
25
+ }
26
+ static mapHeaders(input, headerMap) {
27
+ return GoogleSheetProvider.mapHeaders(input, headerMap);
28
+ }
29
+ }
@@ -0,0 +1,39 @@
1
+ export interface SheetRow {
2
+ [key: string]: string | number | boolean | null;
3
+ }
4
+ export interface SheetRange {
5
+ startRow: number;
6
+ endRow: number;
7
+ startCol: number;
8
+ endCol: number;
9
+ }
10
+ export interface SheetOptions {
11
+ spreadsheetId: string;
12
+ sheetName: string;
13
+ }
14
+ export interface AddRowsOptions extends SheetOptions {
15
+ headerMap?: Record<string, string>;
16
+ }
17
+ export interface UpdateCellsOptions extends SheetOptions {
18
+ /**
19
+ * A1 notation range to update, e.g. "Sheet1!A2:B3". If omitted, uses sheetName.
20
+ */
21
+ range?: string;
22
+ /**
23
+ * 2D array of values to write to the range.
24
+ */
25
+ values: (string | number | boolean | null)[][];
26
+ }
27
+ export interface ReadDataOptions extends SheetOptions {
28
+ /**
29
+ * A1 notation range to read, e.g. "Sheet1!A2:B3". If omitted, uses sheetName.
30
+ */
31
+ range?: string;
32
+ }
33
+ export interface DeleteRowsOptions extends SheetOptions {
34
+ rowIndexes: number[];
35
+ }
36
+ export interface CreateSheetOptions {
37
+ spreadsheetId: string;
38
+ sheetName: string;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function parseInput(input: any): any[];
@@ -0,0 +1,9 @@
1
+ // Utility to parse various input formats into rows for sheets
2
+ export function parseInput(input) {
3
+ if (Array.isArray(input))
4
+ return input;
5
+ if (typeof input === 'object')
6
+ return [input];
7
+ // Add more parsing logic as needed
8
+ throw new Error('Unsupported input format');
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-sheets-automation",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Automate Google Sheets from Node.js: add, update, and read rows with simple API. Supports service accounts, header mapping, and batch operations.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -16,18 +16,32 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "build": "tsc",
19
- "test": "node --experimental-vm-modules node_modules/.bin/jest"
19
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
20
+ "test:unit": "npx jest --testPathIgnorePatterns=integration",
21
+ "test:integration": "npx jest --testPathPattern=integration"
20
22
  },
21
23
  "keywords": [
22
24
  "google-sheets",
23
25
  "spreadsheet",
24
26
  "api",
25
27
  "automation",
26
- "nodejs",
28
+ "nodejs",
27
29
  "provider"
28
30
  ],
29
- "author": "CaesarSage",
31
+ "author": {
32
+ "name": "Caesar Sage",
33
+ "email": "caesarsage@gmail.com",
34
+ "url": "https://github.com/CaesarSage"
35
+ },
30
36
  "license": "MIT",
37
+ "homepage": "https://github.com/CaesarSage/google-sheets-automation#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/CaesarSage/google-sheets-automation/issues"
40
+ },
41
+ "funding": {
42
+ "type": "github",
43
+ "url": "https://github.com/sponsors/CaesarSage"
44
+ },
31
45
  "repository": {
32
46
  "type": "git",
33
47
  "url": "git+https://github.com/CaesarSage/google-sheets-automation.git"
@@ -35,17 +49,12 @@
35
49
  "devDependencies": {
36
50
  "@types/jest": "^30.0.0",
37
51
  "@types/node": "^25.0.9",
38
- "@types/react": "^19.2.9",
39
- "@types/react-dom": "^19.2.3",
40
52
  "dotenv": "^17.2.3",
41
53
  "jest": "^30.2.0",
42
54
  "ts-jest": "^29.4.6",
43
55
  "typescript": "^5.0.0"
44
56
  },
45
57
  "dependencies": {
46
- "google-auth-library": "^10.5.0",
47
- "next": "^16.1.4",
48
- "react": "^19.2.3",
49
- "react-dom": "^19.2.3"
58
+ "google-auth-library": "^10.5.0"
50
59
  }
51
60
  }