Skip to content

Commit cc4c783

Browse files
committed
v.1.0.0
0 parents  commit cc4c783

File tree

11 files changed

+3019
-0
lines changed

11 files changed

+3019
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# @razbakov/sourcy
2+
3+
> Synchronise data from Google Sheets
4+
5+
## Installation
6+
7+
```bash
8+
yarn add @razbakov/sourcy -D
9+
```
10+
11+
Add to package.json:
12+
13+
```json
14+
{
15+
"scripts": {
16+
"sourcy": "sourcy"
17+
}
18+
}
19+
```
20+
21+
Create **sourcy.config.js** in project root:
22+
23+
```js
24+
module.exports = {
25+
sources: [
26+
{
27+
spreadsheetId: "spreadsheet id",
28+
range: "sheet name",
29+
output: "./locales/",
30+
transformer: "i18n",
31+
format: "yaml", // or json
32+
},
33+
],
34+
};
35+
```
36+
37+
Execute and follow instructions:
38+
39+
```bash
40+
yarn sourcy
41+
```
42+
43+
## Transformer: i18n
44+
45+
**Input:**
46+
47+
| key | en | de | es | ru |
48+
| ---------- | ---- | ---------- | -------------- | ------- |
49+
| home.title | Home | Startseite | Página inicial | Главная |
50+
51+
**Output:**
52+
53+
```
54+
en.yml
55+
home.title: Home
56+
57+
de.yml
58+
home.title: Startseite
59+
60+
es.yml
61+
home.title: Página inicial
62+
63+
ru.yml
64+
home.title: Главная
65+
```

lib/auth.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { google } from "googleapis";
2+
import { promises as fs } from "fs";
3+
import inquirer from "inquirer";
4+
import mkdirp from "mkdirp";
5+
import { dirname } from "path";
6+
7+
async function loadJson(path) {
8+
let result = false;
9+
10+
try {
11+
const json = await fs.readFile(path);
12+
result = JSON.parse(json);
13+
} catch (err) {}
14+
15+
return result;
16+
}
17+
18+
async function getNewCredentials(config) {
19+
console.log(
20+
`Download credentials.json from https://developers.google.com/sheets/api/quickstart/nodejs`
21+
);
22+
23+
const { filePath } = await inquirer.prompt([
24+
{
25+
type: "input",
26+
name: "filePath",
27+
message: "Enter full path to downloaded file",
28+
},
29+
]);
30+
31+
const credentials = await loadJson(filePath);
32+
33+
if (!credentials) {
34+
throw Error("Unable to load credentials");
35+
}
36+
37+
return credentials;
38+
}
39+
40+
async function getNewToken(oAuth2Client, scope) {
41+
const authUrl = oAuth2Client.generateAuthUrl({
42+
access_type: "offline",
43+
scope,
44+
});
45+
46+
console.log("Authorize this app by visiting this url: ", authUrl);
47+
48+
const { code } = await inquirer.prompt([
49+
{
50+
type: "input",
51+
name: "code",
52+
message: "Enter the code from that page here: ",
53+
},
54+
]);
55+
56+
return await oAuth2Client.getToken(code);
57+
}
58+
59+
async function auth(config) {
60+
let credentials = await loadJson(config.credentialsPath);
61+
62+
if (!credentials) {
63+
credentials = await getNewCredentials(config);
64+
await mkdirp(dirname(config.credentialsPath));
65+
await fs.writeFile(config.credentialsPath, JSON.stringify(credentials));
66+
}
67+
68+
const { client_secret, client_id, redirect_uris } = credentials.installed;
69+
70+
const oAuth2Client = new google.auth.OAuth2(
71+
client_id,
72+
client_secret,
73+
redirect_uris[0]
74+
);
75+
76+
let token = await loadJson(config.tokenPath);
77+
78+
if (!token) {
79+
token = await getNewToken(oAuth2Client, config.scope);
80+
await fs.writeFile(config.tokenPath, JSON.stringify(token));
81+
}
82+
83+
oAuth2Client.setCredentials(token);
84+
85+
google.options({
86+
auth: oAuth2Client,
87+
});
88+
}
89+
90+
export default auth;

lib/config.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import path from "path";
2+
3+
const userConfigPath = "sourcy.config.js";
4+
5+
const defaultConfigPath = path.join(
6+
__dirname,
7+
"/../lib/config/default.config.js"
8+
);
9+
10+
function resolvePath(filePath) {
11+
if (filePath[0] === "~") {
12+
return path.join(process.env.HOME, filePath.slice(1));
13+
}
14+
15+
return path.resolve(filePath);
16+
}
17+
18+
function loadConfig() {
19+
const userConfig = require(resolvePath(userConfigPath));
20+
const defaultConfig = require(defaultConfigPath);
21+
const config = { ...defaultConfig, ...userConfig };
22+
23+
config.sources = config.sources.map((source) => ({
24+
...source,
25+
output: resolvePath(source.output),
26+
}));
27+
28+
config.credentialsPath = resolvePath(config.credentialsPath);
29+
config.tokenPath = resolvePath(config.tokenPath);
30+
31+
return config;
32+
}
33+
34+
export default loadConfig;

lib/config/default.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
credentialsPath: "~/config/sourcy/credentials.json",
3+
tokenPath: "~/config/sourcy/token.json",
4+
scope: "https://www.googleapis.com/auth/spreadsheets",
5+
};

lib/exporters/json.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { promises as fs } from "fs";
2+
3+
export default async (path, content) => {
4+
const filePath = path + ".json";
5+
await fs.writeFile(filePath, JSON.stringify(content));
6+
7+
return filePath;
8+
};

lib/exporters/yaml.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import YAML from "json2yaml";
2+
import { promises as fs } from "fs";
3+
4+
export default async (path, content) => {
5+
const filePath = path + ".yml";
6+
await fs.writeFile(filePath, YAML.stringify(content));
7+
8+
return filePath;
9+
};

lib/index.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env node
2+
3+
import { google } from "googleapis";
4+
import loadConfig from "./config";
5+
import auth from "./auth";
6+
import i18n from "./transformers/i18n";
7+
import json from "./exporters/json";
8+
import yaml from "./exporters/yaml";
9+
import mkdirp from "mkdirp";
10+
import path from "path";
11+
12+
function transform(rows, transformer) {
13+
switch (transformer) {
14+
case "i18n":
15+
return i18n(rows);
16+
default:
17+
return rows;
18+
}
19+
}
20+
21+
async function save(filePath, content, format) {
22+
switch (format) {
23+
case "yaml":
24+
return await yaml(filePath, content);
25+
case "json":
26+
default:
27+
return await json(filePath, content);
28+
}
29+
}
30+
31+
async function main() {
32+
const config = loadConfig();
33+
34+
await auth(config);
35+
36+
const sheets = google.sheets({ version: "v4" });
37+
38+
for (let source of config.sources) {
39+
const res = await sheets.spreadsheets.values.get({
40+
spreadsheetId: source.spreadsheetId,
41+
range: source.range,
42+
});
43+
44+
const content = transform(res.data.values, source.transformer);
45+
46+
await mkdirp(source.output);
47+
48+
const files = Object.keys(content.files);
49+
50+
for (let file of files) {
51+
const filePath = path.join(source.output, file);
52+
try {
53+
const savedFile = await save(
54+
filePath,
55+
content.files[file],
56+
source.format
57+
);
58+
59+
console.log("Saved ", savedFile);
60+
} catch (error) {
61+
console.error(error);
62+
}
63+
}
64+
}
65+
}
66+
67+
(async function () {
68+
await main();
69+
})();

lib/transformers/i18n.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default (rows) => {
2+
const result = {};
3+
const header = rows[0];
4+
5+
for (let j = 1; j < header.length; j++) {
6+
result[header[j]] = result[header[j]] || {};
7+
8+
for (let i = 1; i < rows.length; i++) {
9+
result[header[j]][rows[i][0]] = rows[i][j];
10+
}
11+
}
12+
13+
return {
14+
files: result,
15+
};
16+
};

package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "@razbakov/sourcy",
3+
"version": "1.0.0",
4+
"license": "MIT",
5+
"description": "Synchronise data from Google Sheets",
6+
"bin": {
7+
"sourcy": "dist/index.js"
8+
},
9+
"scripts": {
10+
"build": "babel lib --out-dir dist"
11+
},
12+
"dependencies": {
13+
"googleapis": "^47.0.0",
14+
"inquirer": "^7.3.3",
15+
"json2yaml": "^1.1.0",
16+
"mkdirp": "^1.0.4"
17+
},
18+
"devDependencies": {
19+
"@babel/cli": "^7.11.6",
20+
"@babel/core": "^7.11.6",
21+
"@babel/preset-env": "^7.11.5",
22+
"babel-plugin-module-resolver": "^4.0.0",
23+
"babel-plugin-root-import": "^6.5.0"
24+
},
25+
"babel": {
26+
"presets": [
27+
[
28+
"@babel/preset-env",
29+
{
30+
"targets": {
31+
"node": "8.9.0"
32+
}
33+
}
34+
]
35+
]
36+
},
37+
"engines": {
38+
"node": ">=8.9.0"
39+
}
40+
}

0 commit comments

Comments
 (0)