@dune2/cli 0.0.3

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.
@@ -0,0 +1,60 @@
1
+ // src/shared/config.ts
2
+ import JoyCon from "joycon";
3
+
4
+ // src/shared/resolveSheetId.ts
5
+ import { match } from "path-to-regexp";
6
+ function resolveSheetId(str) {
7
+ var _a;
8
+ if (!str || !(str == null ? void 0 : str.startsWith("http"))) {
9
+ return str;
10
+ }
11
+ const url = new URL(str);
12
+ const matched = match("/spreadsheets/d/:id*", {
13
+ decode: decodeURIComponent
14
+ })(url.pathname);
15
+ return matched ? (_a = matched.params.id) == null ? void 0 : _a[0] : str;
16
+ }
17
+
18
+ // src/shared/config.ts
19
+ var joycon = new JoyCon();
20
+ function defineConfig(config) {
21
+ return config;
22
+ }
23
+ var configName = "dune.config.js";
24
+ async function getConfig() {
25
+ const res = await joycon.load([configName]);
26
+ const defaultConfig = {
27
+ i18nDir: "./i18n",
28
+ i18nFileName: "{locale}.i18n.json",
29
+ cwd: process.cwd(),
30
+ position: {
31
+ key: "B",
32
+ zh: "C",
33
+ en: "D",
34
+ in: "E"
35
+ },
36
+ parseStartIndex: 2
37
+ };
38
+ if (res.data) {
39
+ const config = Array.isArray(res.data) ? res.data : [res.data];
40
+ return config.map((item) => {
41
+ item = Object.assign({}, defaultConfig, item);
42
+ item.locales = Object.keys(item.position).filter(
43
+ (item2) => item2 !== "key"
44
+ );
45
+ item.sheetId = resolveSheetId(item.sheetId);
46
+ item.keySorter = (a, b) => {
47
+ return a.localeCompare(b);
48
+ };
49
+ return item;
50
+ });
51
+ }
52
+ return [defaultConfig];
53
+ }
54
+
55
+ export {
56
+ defineConfig,
57
+ configName,
58
+ getConfig
59
+ };
60
+ //# sourceMappingURL=chunk-Q6SDTDV5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/config.ts","../src/shared/resolveSheetId.ts"],"sourcesContent":["import JoyCon from \"joycon\";\nimport { resolveSheetId } from \"./resolveSheetId\";\n\nconst joycon = new JoyCon();\n\nexport interface Config {\n // 语言文件存放目录,默认为项目根目录的 \"./i18n\"\n i18nDir?: string;\n // 语言文件名,默认为 \"{locale}.i18n.json\",其中 {locale} 会被替换为 locales 中的语言\n i18nFileName?: string;\n // 默认语言,默认为 \"zh\",影响到提出出来的文案的默认值\n defaultLocale?: string;\n /**\n * 可以是 id 或者 url,如果是 url 则会自动解析出 id\n * id: 1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk\n * url: https://docs.google.com/spreadsheets/d/1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk/edit#gid=1740568548\n */\n sheetId?: string;\n // google sheets 工作表名称\n sheetRange?: string;\n /**\n * 对应 语言、i18nKey 所在的列标识符 如(A,B,C...)\n * 传入时将忽略默认的配置\n */\n position?: {\n key: string;\n [key: string]: string;\n };\n\n /**\n * 从 Google Sheets 哪一行开始解析\n * 默认从第2行开始,模板文件第一行是表头\n * @default 2\n */\n parseStartIndex?: number;\n}\nexport interface InternalConfig extends Config {\n // 默认是 命令运行的目录,一般是项目根目录\n cwd?: string;\n // 支持的语言列表,从 position 中解析出来\n locales?: string[];\n\n keySorter?: (a: string, b: string) => number;\n}\n\nexport function defineConfig(config: Config | Config[]) {\n return config;\n}\n\nexport const configName = \"dune.config.js\";\n\nexport async function getConfig(): Promise<InternalConfig[]> {\n const res = await joycon.load([configName]);\n\n const defaultConfig = {\n i18nDir: \"./i18n\",\n i18nFileName: \"{locale}.i18n.json\",\n cwd: process.cwd(),\n position: {\n key: \"B\",\n zh: \"C\",\n en: \"D\",\n in: \"E\",\n },\n parseStartIndex: 2,\n };\n\n if (res.data) {\n const config: InternalConfig[] = Array.isArray(res.data)\n ? res.data\n : [res.data];\n return config.map((item) => {\n item = Object.assign({}, defaultConfig, item);\n item.locales = Object.keys(item.position).filter(\n (item) => item !== \"key\"\n );\n\n item.sheetId = resolveSheetId(item.sheetId);\n\n item.keySorter = (a, b) => {\n return a.localeCompare(b);\n };\n\n return item;\n });\n }\n\n return [defaultConfig];\n}\n","/**\n * input:\n * \"https://docs.google.com/spreadsheets/d/1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk/edit#gid=1740568548\"\n * output:\n * \"1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk\"\n * --\n * input: \"1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk\"\n * output: \"1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk\"\n */\nimport { match } from \"path-to-regexp\";\n\nexport function resolveSheetId(str?: string) {\n if (!str || !str?.startsWith(\"http\")) {\n return str;\n }\n const url = new URL(str);\n const matched = match<{ id: string[] }>(\"/spreadsheets/d/:id*\", {\n decode: decodeURIComponent,\n })(url.pathname);\n return matched ? matched.params.id?.[0] : str;\n}\n"],"mappings":";AAAA,OAAO,YAAY;;;ACSnB,SAAS,aAAa;AAEf,SAAS,eAAe,KAAc;AAX7C;AAYE,MAAI,CAAC,OAAO,EAAC,2BAAK,WAAW,UAAS;AACpC,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI,IAAI,GAAG;AACvB,QAAM,UAAU,MAAwB,wBAAwB;AAAA,IAC9D,QAAQ;AAAA,EACV,CAAC,EAAE,IAAI,QAAQ;AACf,SAAO,WAAU,aAAQ,OAAO,OAAf,mBAAoB,KAAK;AAC5C;;;ADjBA,IAAM,SAAS,IAAI,OAAO;AA0CnB,SAAS,aAAa,QAA2B;AACtD,SAAO;AACT;AAEO,IAAM,aAAa;AAE1B,eAAsB,YAAuC;AAC3D,QAAM,MAAM,MAAM,OAAO,KAAK,CAAC,UAAU,CAAC;AAE1C,QAAM,gBAAgB;AAAA,IACpB,SAAS;AAAA,IACT,cAAc;AAAA,IACd,KAAK,QAAQ,IAAI;AAAA,IACjB,UAAU;AAAA,MACR,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAAA,IACA,iBAAiB;AAAA,EACnB;AAEA,MAAI,IAAI,MAAM;AACZ,UAAM,SAA2B,MAAM,QAAQ,IAAI,IAAI,IACnD,IAAI,OACJ,CAAC,IAAI,IAAI;AACb,WAAO,OAAO,IAAI,CAAC,SAAS;AAC1B,aAAO,OAAO,OAAO,CAAC,GAAG,eAAe,IAAI;AAC5C,WAAK,UAAU,OAAO,KAAK,KAAK,QAAQ,EAAE;AAAA,QACxC,CAACA,UAASA,UAAS;AAAA,MACrB;AAEA,WAAK,UAAU,eAAe,KAAK,OAAO;AAE1C,WAAK,YAAY,CAAC,GAAG,MAAM;AACzB,eAAO,EAAE,cAAc,CAAC;AAAA,MAC1B;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO,CAAC,aAAa;AACvB;","names":["item"]}
package/dist/cli.js ADDED
@@ -0,0 +1,677 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configName,
4
+ getConfig
5
+ } from "./chunk-Q6SDTDV5.js";
6
+
7
+ // src/commands/extract.ts
8
+ import fs3 from "fs-extra";
9
+ import { globby } from "globby";
10
+ import pMap2 from "p-map";
11
+
12
+ // src/shared/index.ts
13
+ import cac from "cac";
14
+ import debug from "debug";
15
+ import path from "path";
16
+ import fs from "fs-extra";
17
+ var cli = cac("dune");
18
+ var createLogger = (name) => {
19
+ const info = debug(`${cli.name}:${name}`);
20
+ return {
21
+ ...info,
22
+ info,
23
+ error: info.extend("error")
24
+ };
25
+ };
26
+ debug.enable(`${cli.name}:*`);
27
+ var homeConfigDir = (() => {
28
+ const isWin = process.platform === "win32";
29
+ const USER_HOME_DIR = isWin ? process.env.USERPROFILE : process.env.HOME;
30
+ const configDir = path.join(USER_HOME_DIR, ".config", cli.name);
31
+ if (!fs.existsSync(configDir)) {
32
+ fs.mkdirSync(configDir);
33
+ }
34
+ return homeConfigDir2;
35
+ function homeConfigDir2(p) {
36
+ if (!p) {
37
+ return configDir;
38
+ }
39
+ return path.join(configDir, p);
40
+ }
41
+ })();
42
+
43
+ // src/shared/i18nData.ts
44
+ import pMap from "p-map";
45
+ import fs2 from "fs-extra";
46
+ import pc from "picocolors";
47
+ import path2 from "path";
48
+ import Table from "cli-table3";
49
+ var log = createLogger("i18nData");
50
+ var I18nData = class {
51
+ constructor(locale, config) {
52
+ this.locale = locale;
53
+ this.config = config;
54
+ this.data = {};
55
+ this.filePath = path2.join(
56
+ config.cwd,
57
+ config.i18nDir,
58
+ config.i18nFileName.replace("{locale}", locale)
59
+ );
60
+ }
61
+ async loadData() {
62
+ try {
63
+ if (await fs2.pathExists(this.filePath)) {
64
+ this.data = await fs2.readJSON(this.filePath);
65
+ } else {
66
+ log.info("File '%s' does not exist", pc.dim(this.filePath));
67
+ }
68
+ } catch (e) {
69
+ log.error(
70
+ "Error loading i18n data for locale '%s' from file '%s'",
71
+ pc.yellow(this.locale),
72
+ pc.yellow(this.filePath)
73
+ );
74
+ log.error("Error: %s", e.message);
75
+ }
76
+ return this.data;
77
+ }
78
+ async updateByExtractedData(extractedData, shouldUseDefault = false) {
79
+ await this.loadData();
80
+ extractedData.forEach((value, key) => {
81
+ this.data[key] = shouldUseDefault ? value.defaults || key : this.data[key] || "";
82
+ });
83
+ }
84
+ async updateFromSheetData(sheetData) {
85
+ await this.loadData();
86
+ this.data = { ...this.data, ...sheetData };
87
+ }
88
+ sortData() {
89
+ const keys = Object.keys(this.data).sort(this.config.keySorter);
90
+ const newData = {};
91
+ keys.forEach((key) => {
92
+ newData[key] = this.data[key];
93
+ });
94
+ this.data = newData;
95
+ }
96
+ async saveToDisk() {
97
+ this.sortData();
98
+ log.info(
99
+ "Saving i18n data for locale '%s' to file '%s'",
100
+ pc.green(this.locale),
101
+ pc.dim(this.filePath)
102
+ );
103
+ try {
104
+ await fs2.ensureFile(this.filePath);
105
+ await fs2.writeJson(this.filePath, this.data, { spaces: 2 });
106
+ log.info(
107
+ "Saved i18n data for locale '%s' to file '%s'",
108
+ pc.green(this.locale),
109
+ pc.dim(this.filePath)
110
+ );
111
+ } catch (e) {
112
+ log.error(
113
+ "Error saving i18n data for locale '%s' to file '%s'",
114
+ pc.green(this.locale),
115
+ pc.dim(this.filePath)
116
+ );
117
+ log.error("Error: %s", pc.red(e.message));
118
+ }
119
+ }
120
+ async statistic() {
121
+ const total = Object.keys(this.data).length;
122
+ const missing = Object.values(this.data).filter((v) => !v).length;
123
+ return {
124
+ locale: this.locale,
125
+ total,
126
+ missing
127
+ };
128
+ }
129
+ async trySaveToGoogle(sheetData) {
130
+ await pMap(Object.entries(this.data), async ([key, value]) => {
131
+ if (sheetData.hasI18nItem(this.locale, key)) {
132
+ await sheetData.getI18nItem(this.locale, key).tryUpdate(value);
133
+ } else {
134
+ log.error(
135
+ "%s range %s not found key %s",
136
+ pc.bold(this.config.sheetRange),
137
+ pc.bold(this.locale),
138
+ pc.bold(key)
139
+ );
140
+ }
141
+ });
142
+ }
143
+ static printStatistic(label, statistics) {
144
+ const table = new Table({
145
+ head: ["Language", "Total count", "Missing"],
146
+ colAligns: ["left", "center", "center"],
147
+ style: {
148
+ head: ["green"],
149
+ border: [],
150
+ compact: true
151
+ }
152
+ });
153
+ statistics.forEach((statistic) => {
154
+ table.push([
155
+ statistic.locale,
156
+ statistic.total,
157
+ statistic.missing > 0 ? pc.red(statistic.missing) : statistic.missing
158
+ ]);
159
+ });
160
+ console.log(pc.bold(label));
161
+ console.log(table.toString());
162
+ }
163
+ };
164
+
165
+ // src/commands/extract.ts
166
+ import pc2 from "picocolors";
167
+ var log2 = createLogger("extract");
168
+ async function extract() {
169
+ const config = await getConfig();
170
+ const { extract: extract2 } = await import("@dune2/wasm");
171
+ for (const configItem of config) {
172
+ const files = await globby(
173
+ [`**/**.{js,jsx,ts,tsx}`, "!**/node_modules/**", "!**.d.ts"],
174
+ {
175
+ cwd: configItem.cwd
176
+ }
177
+ );
178
+ log2.info("\u9884\u8BA1\u5171\u89E3\u6790 %s \u4E2A\u6587\u4EF6", pc2.green(files.length));
179
+ const extractedI18nDataMap = /* @__PURE__ */ new Map();
180
+ await pMap2(
181
+ files,
182
+ async (file) => {
183
+ const content = await fs3.readFile(file, "utf-8");
184
+ const res = await extract2(content);
185
+ log2.info(
186
+ "\u4ECE %s \u4E2D\u63D0\u53D6\u5230 %s \u6761\u6587\u6848",
187
+ pc2.dim(file),
188
+ res.size ? pc2.green(res.size) : res.size
189
+ );
190
+ res.forEach((value, key) => {
191
+ const cur = extractedI18nDataMap.get(key);
192
+ if (!(cur == null ? void 0 : cur.defaults)) {
193
+ extractedI18nDataMap.set(key, value);
194
+ }
195
+ });
196
+ },
197
+ { concurrency: 20 }
198
+ );
199
+ log2.info(
200
+ "\u603B\u7EDF\u63D0\u53D6\u5230 %s \u6761\u6587\u6848 %O",
201
+ pc2.green(extractedI18nDataMap.size),
202
+ extractedI18nDataMap
203
+ );
204
+ const statistics = await pMap2(configItem.locales, async (locale, index) => {
205
+ const i18nData = new I18nData(locale, configItem);
206
+ await i18nData.updateByExtractedData(extractedI18nDataMap, index === 0);
207
+ await i18nData.saveToDisk();
208
+ return await i18nData.statistic();
209
+ });
210
+ I18nData.printStatistic("\u63D0\u53D6\u7ED3\u679C: ", statistics);
211
+ }
212
+ }
213
+
214
+ // src/commands/generate.ts
215
+ import pMap3 from "p-map";
216
+
217
+ // src/shared/google/sheet.ts
218
+ import { google as google2 } from "googleapis";
219
+
220
+ // src/shared/letters.ts
221
+ function letterToNumber(letters) {
222
+ if (typeof letters === "number" || !Number.isNaN(+letters)) {
223
+ return +letters;
224
+ }
225
+ let n = 0;
226
+ letters = letters.toUpperCase();
227
+ for (let p = 0; p < letters.length; p++) {
228
+ n = letters[p].charCodeAt(0) - 64 + n * 26;
229
+ }
230
+ return Math.max(0, n - 1);
231
+ }
232
+ var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
233
+ var base = alphabet.length;
234
+ function numberToLetter(n) {
235
+ const digits = [];
236
+ do {
237
+ const v = n % base;
238
+ digits.push(v);
239
+ n = Math.floor(n / base);
240
+ } while (n-- > 0);
241
+ const chars = [];
242
+ while (digits.length) {
243
+ chars.push(alphabet[digits.pop()]);
244
+ }
245
+ return chars.join("");
246
+ }
247
+
248
+ // src/shared/google/auth.ts
249
+ import fs4 from "fs-extra";
250
+ import { google } from "googleapis";
251
+
252
+ // src/shared/google/shared.ts
253
+ import connect from "connect";
254
+ import open from "open";
255
+ import { URL } from "url";
256
+ var log3 = createLogger("google:shared");
257
+ function openBrowser(url) {
258
+ log3.info(`\u5982\u672A\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u53EF\u8BBF\u95EE\u4EE5\u4E0B\u5730\u5740\uFF1A${url}`);
259
+ open(url, { app: { name: open.apps.chrome } });
260
+ }
261
+ function openAndWaitReturnQuery(toOpenUrl, port, queryKey) {
262
+ return new Promise((resolve) => {
263
+ openBrowser(toOpenUrl);
264
+ const app = connect();
265
+ app.use((req, res, next) => {
266
+ const parsedUrl = new URL(req.url, `http://localhost:${port}`);
267
+ const target = parsedUrl.searchParams.get(queryKey);
268
+ if (target) {
269
+ res.writeHead(200, {
270
+ "content-type": "text/html;charset=utf8"
271
+ });
272
+ res.end(
273
+ `<div>\u5F53\u524D\u7A97\u53E3\u53EF\u76F4\u63A5\u5173\u95ED</div>
274
+ <script>
275
+ window.open("", "_self", "");
276
+ window.close();
277
+ <\/script>`
278
+ );
279
+ server.close();
280
+ server.unref();
281
+ resolve(target);
282
+ }
283
+ next();
284
+ });
285
+ const server = app.listen(port);
286
+ });
287
+ }
288
+
289
+ // src/shared/google/auth.ts
290
+ var log4 = createLogger("google:auth");
291
+ var GoogleAuth = class {
292
+ constructor(options) {
293
+ this.options = options;
294
+ this.tokens = null;
295
+ this.tokensCachePath = homeConfigDir("chromeAuthToken.json");
296
+ this.oauth2Client = new google.auth.OAuth2(
297
+ this.options.client_id,
298
+ this.options.client_secret,
299
+ "http://localhost:12345"
300
+ );
301
+ this.initCredentialsPromise = null;
302
+ this.options.scope = this.options.scope ?? ["spreadsheets"];
303
+ const scopePrefix = `https://www.googleapis.com/auth/`;
304
+ this.scope = this.options.scope.map((item) => {
305
+ if (item.startsWith(scopePrefix)) {
306
+ return item;
307
+ }
308
+ return `${scopePrefix}${item}`;
309
+ });
310
+ try {
311
+ this.tokens = fs4.readJsonSync(this.tokensCachePath);
312
+ } catch (e) {
313
+ log4.info("\u672A\u83B7\u53D6\u5230\u7F13\u5B58\u7684token");
314
+ this.tokens = null;
315
+ }
316
+ }
317
+ async saveTokens(tokens) {
318
+ this.tokens = tokens;
319
+ await fs4.writeJSON(this.tokensCachePath, tokens);
320
+ log4.info("\u4FDD\u5B58token\u6210\u529F");
321
+ }
322
+ async getCode() {
323
+ log4.info("\u5C06\u6253\u5F00chrome\u6D4F\u89C8\u5668\u8FDB\u884C\u6388\u6743");
324
+ const authUrl = this.oauth2Client.generateAuthUrl({
325
+ access_type: "offline",
326
+ scope: this.scope
327
+ });
328
+ return await openAndWaitReturnQuery(authUrl, 12345, "code");
329
+ }
330
+ async initCredentialsImpl() {
331
+ if (!this.tokens) {
332
+ const code = await this.getCode();
333
+ const { tokens } = await this.oauth2Client.getToken(code);
334
+ await this.saveTokens(tokens);
335
+ }
336
+ this.oauth2Client.setCredentials(this.tokens);
337
+ }
338
+ async initCredentials() {
339
+ if (!this.initCredentialsPromise) {
340
+ this.initCredentialsPromise = this.initCredentialsImpl();
341
+ }
342
+ return await this.initCredentialsPromise;
343
+ }
344
+ };
345
+ var clientSecret = {
346
+ client_id: "728508473485-fgl11a3op1ucd0lef9hs49mkliuaipqa.apps.googleusercontent.com",
347
+ project_id: "dune-cli",
348
+ auth_uri: "https://accounts.google.com/o/oauth2/auth",
349
+ token_uri: "https://oauth2.googleapis.com/token",
350
+ auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
351
+ client_secret: "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",
352
+ redirect_uris: ["http://localhost"]
353
+ };
354
+ var googleAuth = new GoogleAuth(clientSecret);
355
+
356
+ // src/shared/google/sheet.ts
357
+ var log5 = createLogger("google:sheets");
358
+ var GoogleSheet = class {
359
+ constructor() {
360
+ this.initPromise = null;
361
+ this.get = async (options) => {
362
+ await this.init();
363
+ return this.sheets.spreadsheets.values.get(options);
364
+ };
365
+ }
366
+ async initImpl() {
367
+ if (!this.sheets) {
368
+ await googleAuth.initCredentials();
369
+ this.sheets = google2.sheets({
370
+ version: "v4",
371
+ auth: googleAuth.oauth2Client
372
+ });
373
+ }
374
+ }
375
+ async init() {
376
+ if (!this.initPromise) {
377
+ this.initPromise = this.initImpl();
378
+ }
379
+ await this.initPromise;
380
+ }
381
+ async update(spreadsheetId, range, colIndex, rowIndex, values) {
382
+ await this.init();
383
+ const colLetter = numberToLetter(colIndex);
384
+ const sheetNeedRange = `${range}!${colLetter}${rowIndex}`;
385
+ const res = await this.sheets.spreadsheets.values.update({
386
+ spreadsheetId,
387
+ range: sheetNeedRange,
388
+ valueInputOption: "USER_ENTERED",
389
+ requestBody: { values }
390
+ });
391
+ if (res.status === 200) {
392
+ log5.info(`\u66F4\u65B0 ${sheetNeedRange} \u7684\u503C\u6210\u529F`);
393
+ } else {
394
+ log5.error("some thing wrong", res);
395
+ }
396
+ return res;
397
+ }
398
+ async updateCell(spreadsheetId, range, colIndex, rowIndex, values) {
399
+ return this.update(spreadsheetId, range, colIndex, rowIndex, [[values]]);
400
+ }
401
+ async updateColumn(spreadsheetId, range, colIndex, rowIndex, values) {
402
+ return this.update(spreadsheetId, range, colIndex, rowIndex, [
403
+ ...values.map((v) => [v])
404
+ ]);
405
+ }
406
+ };
407
+ var googleSheet = new GoogleSheet();
408
+
409
+ // src/shared/resolveSheetData.ts
410
+ var log6 = createLogger("shared:resolveSheetData");
411
+ var SheetI18nItem = class {
412
+ constructor(spreadsheetId, range, key, colIndex, row, rowIndex) {
413
+ this.spreadsheetId = spreadsheetId;
414
+ this.range = range;
415
+ this.key = key;
416
+ this.row = row;
417
+ this.rowIndex = rowIndex;
418
+ this.colIndex = letterToNumber(colIndex);
419
+ this.value = this.row[this.colIndex] || "";
420
+ }
421
+ async tryUpdate(newValue) {
422
+ if (newValue === this.value) {
423
+ return;
424
+ }
425
+ await googleSheet.updateCell(
426
+ this.spreadsheetId,
427
+ this.range,
428
+ this.colIndex,
429
+ this.rowIndex,
430
+ newValue
431
+ );
432
+ }
433
+ };
434
+ async function resolveSheetData(config) {
435
+ const { position } = config;
436
+ const parseStartIndex = config.parseStartIndex ?? 2;
437
+ log6.info(`\u8BFB\u53D6 ${config.sheetRange} sheet\u6570\u636E`);
438
+ const res = await googleSheet.get({
439
+ spreadsheetId: config.sheetId,
440
+ range: config.sheetRange
441
+ });
442
+ if (!res.data.values) {
443
+ log6.error(
444
+ `${config.sheetRange} sheet\u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5\uFF0C\u5E94\u8BE5\u6700\u5C11\u6709\u4E00\u884C\u5934\u90E8\u6570\u636E`
445
+ );
446
+ return new SheetData(config, 2);
447
+ }
448
+ const sheetData = new SheetData(config, res.data.values.length + 1);
449
+ res.data.values.forEach((row, rowIndex) => {
450
+ var _a;
451
+ const trulyRowIndex = rowIndex + 1;
452
+ if (parseStartIndex > trulyRowIndex) {
453
+ return;
454
+ }
455
+ const i18nKey = (_a = row[letterToNumber(position.key)]) == null ? void 0 : _a.trim();
456
+ if (i18nKey) {
457
+ if (sheetData.keySet.has(i18nKey)) {
458
+ log6.error(
459
+ `\u8868\u683C ${config.sheetRange} \u53D1\u73B0\u91CD\u590D\u7684i18nKey: ${i18nKey}, \u5C06\u8986\u76D6\u524D\u9762\u7684\u503C`
460
+ );
461
+ } else {
462
+ sheetData.keySet.add(i18nKey);
463
+ }
464
+ config.locales.forEach((lang) => {
465
+ sheetData.setI18nItem(
466
+ lang,
467
+ new SheetI18nItem(
468
+ config.sheetId,
469
+ config.sheetRange,
470
+ i18nKey,
471
+ position[lang],
472
+ row,
473
+ trulyRowIndex
474
+ )
475
+ );
476
+ });
477
+ }
478
+ });
479
+ return sheetData;
480
+ }
481
+ var SheetData = class {
482
+ constructor(config, nextAppendRowIndex) {
483
+ this.config = config;
484
+ this.nextAppendRowIndex = nextAppendRowIndex;
485
+ this.keySet = /* @__PURE__ */ new Set();
486
+ this.value = new Map(
487
+ this.config.locales.map((lang) => {
488
+ return [lang, /* @__PURE__ */ new Map()];
489
+ })
490
+ );
491
+ }
492
+ getI18nItem(lang, i18nKey) {
493
+ return this.value.get(lang).get(i18nKey);
494
+ }
495
+ setI18nItem(lang, i18nItem) {
496
+ this.value.get(lang).set(i18nItem.key, i18nItem);
497
+ }
498
+ hasI18nItem(lang, i18nKey) {
499
+ return this.value.get(lang).has(i18nKey);
500
+ }
501
+ getLangDataJSON(lang) {
502
+ const res = {};
503
+ this.value.get(lang).forEach((i18nItem) => {
504
+ res[i18nItem.key] = i18nItem.value;
505
+ });
506
+ return res;
507
+ }
508
+ };
509
+
510
+ // src/commands/generate.ts
511
+ var log7 = createLogger("generate");
512
+ async function generate() {
513
+ const config = await getConfig();
514
+ let i = 0;
515
+ for (const configItem of config) {
516
+ i++;
517
+ if (!configItem.sheetId || !configItem.sheetRange) {
518
+ log7.error(`config.${i} sheetId \u6216 sheetRange \u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5`);
519
+ continue;
520
+ }
521
+ const sheetData = await resolveSheetData(configItem);
522
+ const statistics = await pMap3(configItem.locales, async (locale, index) => {
523
+ const i18nData = new I18nData(locale, configItem);
524
+ await i18nData.updateFromSheetData(sheetData.getLangDataJSON(locale));
525
+ await i18nData.saveToDisk();
526
+ return await i18nData.statistic();
527
+ });
528
+ I18nData.printStatistic(`${configItem.sheetRange} \u751F\u6210\u7ED3\u679C: `, statistics);
529
+ }
530
+ }
531
+
532
+ // src/commands/initConfig.ts
533
+ import fs5 from "fs-extra";
534
+ import path3 from "path";
535
+ var log8 = createLogger("initConfig");
536
+ var tpl = `
537
+ /**
538
+ * @template {import('@dune2/cli').Config} T
539
+ * @param {T | T[]} config - A generic parameter that flows through to the return type
540
+ * @constraint {{import('@dune2/cli').Config}}
541
+ */
542
+ function defineConfig(config) {
543
+ return config;
544
+ }
545
+ module.exports = defineConfig([]);
546
+ `;
547
+ var initConfig = async () => {
548
+ const config = await getConfig();
549
+ const configPath = path3.join(config[0].cwd, configName);
550
+ log8.info(`config file path: ${configPath}`);
551
+ if (await fs5.pathExists(configPath)) {
552
+ log8.info(`config file already exists, skip`);
553
+ return;
554
+ }
555
+ await fs5.writeFile(configPath, tpl);
556
+ log8.info(`config file created`);
557
+ };
558
+
559
+ // src/commands/upload.ts
560
+ import pMap4 from "p-map";
561
+ var log9 = createLogger("upload");
562
+ async function batchUpdateKeys(config) {
563
+ let sheetData = await resolveSheetData(config);
564
+ const allKeys = [];
565
+ const i18nDataMap = /* @__PURE__ */ new Map();
566
+ await pMap4(config.locales, async (locale) => {
567
+ const i18nData = new I18nData(locale, config);
568
+ i18nDataMap.set(locale, i18nData);
569
+ const data = await i18nData.loadData();
570
+ allKeys.push(...Object.keys(data));
571
+ });
572
+ let needSyncKeys = Array.from(new Set(allKeys)).filter((key) => {
573
+ return !sheetData.keySet.has(key);
574
+ });
575
+ needSyncKeys = needSyncKeys.sort(config.keySorter);
576
+ if (needSyncKeys.length) {
577
+ log9.info(`\u5373\u5C06\u540C\u6B65\u672C\u5730\u65B0\u589E\u52A0 ${needSyncKeys.length} \u4E2A i18nKey \u5230\u4E91\u7AEF`);
578
+ await googleSheet.updateColumn(
579
+ config.sheetId,
580
+ config.sheetRange,
581
+ letterToNumber(config.position.key),
582
+ sheetData.nextAppendRowIndex,
583
+ needSyncKeys
584
+ );
585
+ log9.info("\u540C\u6B65\u65B0\u589E\u52A0i18nKey\u6210\u529F");
586
+ for (const lang of config.locales) {
587
+ const langFile = i18nDataMap.get(lang);
588
+ const needSyncValues = [];
589
+ needSyncKeys.forEach((k) => {
590
+ needSyncValues.push(langFile.data[k]);
591
+ });
592
+ log9.info(
593
+ `\u5373\u5C06\u540C\u6B65\u672C\u5730 ${lang} \u65B0\u589E\u52A0 ${needSyncValues.length} \u4E2A\u503C\u5230\u4E91\u7AEF`
594
+ );
595
+ await googleSheet.updateColumn(
596
+ config.sheetId,
597
+ config.sheetRange,
598
+ letterToNumber(config.position[lang]),
599
+ sheetData.nextAppendRowIndex,
600
+ needSyncValues
601
+ );
602
+ }
603
+ await sleep(1e3);
604
+ sheetData = await resolveSheetData(config);
605
+ log9.info(`\u5DF2\u91CD\u65B0\u83B7\u53D6 ${config.sheetRange} \u7684\u6570\u636E`);
606
+ }
607
+ return { sheetData, i18nDataMap };
608
+ }
609
+ function sleep(ms) {
610
+ return new Promise((resolve) => {
611
+ setTimeout(resolve, ms);
612
+ });
613
+ }
614
+ async function upload() {
615
+ const config = await getConfig();
616
+ for (const configItem of config) {
617
+ const { sheetData, i18nDataMap } = await batchUpdateKeys(configItem);
618
+ await pMap4(configItem.locales, async (locale) => {
619
+ const i18nData = i18nDataMap.get(locale);
620
+ await i18nData.trySaveToGoogle(sheetData);
621
+ });
622
+ }
623
+ log9.info("\u4E0A\u4F20\u6210\u529F");
624
+ }
625
+
626
+ // package.json
627
+ var version = "0.0.2";
628
+
629
+ // src/commands/interactive.ts
630
+ import prompts from "prompts";
631
+ var log10 = createLogger("interactive");
632
+ var interactive = async (args) => {
633
+ const commands = cli.commands.filter((command2) => {
634
+ if (command2.name) {
635
+ return !command2.name.startsWith("@@") && command2.name !== "interactive";
636
+ }
637
+ return false;
638
+ });
639
+ const commandMap = /* @__PURE__ */ new Map();
640
+ const res = await prompts({
641
+ type: "select",
642
+ name: "command",
643
+ message: "\u9009\u62E9\u8981\u6267\u884C\u7684\u547D\u4EE4\uFF1A",
644
+ choices: commands.map((command2) => {
645
+ commandMap.set(command2.name, command2);
646
+ return {
647
+ title: command2.name,
648
+ value: command2.name,
649
+ description: command2.description
650
+ };
651
+ })
652
+ });
653
+ const command = commandMap.get(res.command);
654
+ if (!command) {
655
+ log10.error("\u672A\u627E\u5230\u547D\u4EE4 %s", res.command);
656
+ return;
657
+ }
658
+ command.commandAction.apply(cli, args);
659
+ };
660
+
661
+ // src/cli.ts
662
+ cli.command("generate", "\u751F\u6210\u7FFB\u8BD1\u6587\u4EF6").alias("gen").example("dune generate").example("dune gen").action(generate);
663
+ cli.command("upload", "\u4E0A\u4F20\u7FFB\u8BD1\u6587\u4EF6").example("dune upload").action(upload);
664
+ cli.command("extract", "\u63D0\u53D6\u4EE3\u7801\u4E2D\u7684\u6587\u6848").example("dune extract").action(extract);
665
+ cli.command("init", "\u521D\u59CB\u5316\u914D\u7F6E\u6587\u4EF6").example("dune init").action(initConfig);
666
+ cli.command("interactive", "\u4EA4\u4E92\u5F0F\u64CD\u4F5C").example("dune interactive").alias("i").action(interactive);
667
+ cli.command("").action(interactive);
668
+ cli.version(version);
669
+ cli.help();
670
+ (async () => {
671
+ try {
672
+ cli.parse(process.argv, { run: false });
673
+ await cli.runMatchedCommand();
674
+ } catch (error) {
675
+ }
676
+ })();
677
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/extract.ts","../src/shared/index.ts","../src/shared/i18nData.ts","../src/commands/generate.ts","../src/shared/google/sheet.ts","../src/shared/letters.ts","../src/shared/google/auth.ts","../src/shared/google/shared.ts","../src/shared/resolveSheetData.ts","../src/commands/initConfig.ts","../src/commands/upload.ts","../src/commands/interactive.ts","../src/cli.ts"],"sourcesContent":["import fs from \"fs-extra\";\nimport { globby } from \"globby\";\nimport pMap from \"p-map\";\nimport { createLogger } from \"../shared\";\nimport { getConfig } from \"../shared/config\";\nimport { I18nData, ExtractedMap } from \"../shared/i18nData\";\nimport pc from \"picocolors\";\n\nconst log = createLogger(\"extract\");\n\nexport async function extract() {\n const config = await getConfig();\n const { extract } = await import(\"@dune2/wasm\");\n\n // 这是使得任务串行,方便看日志\n for (const configItem of config) {\n const files = await globby(\n [`**/**.{js,jsx,ts,tsx}`, \"!**/node_modules/**\", \"!**.d.ts\"],\n {\n cwd: configItem.cwd,\n }\n );\n log.info(\"预计共解析 %s 个文件\", pc.green(files.length));\n const extractedI18nDataMap: ExtractedMap = new Map();\n await pMap(\n files,\n async (file) => {\n const content = await fs.readFile(file, \"utf-8\");\n const res: ExtractedMap = await extract(content);\n log.info(\n \"从 %s 中提取到 %s 条文案\",\n pc.dim(file),\n res.size ? pc.green(res.size) : res.size\n );\n res.forEach((value, key) => {\n const cur = extractedI18nDataMap.get(key);\n if (!cur?.defaults) {\n extractedI18nDataMap.set(key, value);\n }\n });\n },\n { concurrency: 20 }\n );\n log.info(\n \"总统提取到 %s 条文案 %O\",\n pc.green(extractedI18nDataMap.size),\n extractedI18nDataMap\n );\n\n const statistics = await pMap(configItem.locales, async (locale, index) => {\n const i18nData = new I18nData(locale, configItem);\n await i18nData.updateByExtractedData(extractedI18nDataMap, index === 0);\n await i18nData.saveToDisk();\n return await i18nData.statistic();\n });\n I18nData.printStatistic(\"提取结果: \", statistics);\n }\n}\n","import cac from \"cac\";\nimport debug from \"debug\";\nimport path from \"path\";\nimport fs from \"fs-extra\";\n\nexport const cli = cac(\"dune\");\n\nexport const createLogger = (name: string) => {\n const info = debug(`${cli.name}:${name}`);\n return {\n ...info,\n info,\n error: info.extend(\"error\"),\n };\n};\n\ndebug.enable(`${cli.name}:*`);\n\n/**\n * same with chacheDir\n * homeDir();\n * //=> '~/name'\n *\n * homeDir('bar.js')\n * //=> '~/name/bar.js'\n */\n// eslint-disable-next-line @typescript-eslint/explicit-function-return-type\nexport const homeConfigDir = (() => {\n const isWin = process.platform === \"win32\";\n const USER_HOME_DIR = isWin ? process.env.USERPROFILE : process.env.HOME;\n const configDir = path.join(USER_HOME_DIR, \".config\", cli.name);\n\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir);\n }\n return homeConfigDir;\n\n function homeConfigDir(p?: string): string {\n if (!p) {\n return configDir;\n }\n return path.join(configDir, p);\n }\n})();\n","import pMap from \"p-map\";\nimport { InternalConfig } from \"./config\";\nimport { createLogger } from \"./index\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\nimport path from \"path\";\nimport Table from \"cli-table3\";\nimport { SheetData } from \"./resolveSheetData\";\n\nconst log = createLogger(\"i18nData\");\nexport type ExtractedMap = Map<string, { id: string; defaults: string }>;\n\nexport class I18nData {\n data: Record<string, string> = {};\n\n filePath: string;\n\n constructor(public locale: string, public config: InternalConfig) {\n this.filePath = path.join(\n config.cwd,\n config.i18nDir,\n config.i18nFileName.replace(\"{locale}\", locale)\n );\n }\n\n async loadData() {\n try {\n if (await fs.pathExists(this.filePath)) {\n this.data = await fs.readJSON(this.filePath);\n } else {\n log.info(\"File '%s' does not exist\", pc.dim(this.filePath));\n }\n } catch (e) {\n log.error(\n \"Error loading i18n data for locale '%s' from file '%s'\",\n pc.yellow(this.locale),\n pc.yellow(this.filePath)\n );\n log.error(\"Error: %s\", e.message);\n }\n return this.data;\n }\n\n async updateByExtractedData(\n extractedData: ExtractedMap,\n shouldUseDefault = false\n ) {\n await this.loadData();\n extractedData.forEach((value, key) => {\n this.data[key] = shouldUseDefault\n ? value.defaults || key\n : this.data[key] || \"\";\n });\n }\n\n async updateFromSheetData(sheetData: Record<string, string>) {\n await this.loadData();\n this.data = { ...this.data, ...sheetData };\n }\n\n private sortData() {\n //region 语言key按一定规则排序\n const keys = Object.keys(this.data).sort(this.config.keySorter);\n const newData = {};\n keys.forEach((key) => {\n newData[key] = this.data[key];\n });\n this.data = newData;\n //endregion\n }\n\n async saveToDisk() {\n this.sortData();\n log.info(\n \"Saving i18n data for locale '%s' to file '%s'\",\n pc.green(this.locale),\n pc.dim(this.filePath)\n );\n try {\n await fs.ensureFile(this.filePath);\n await fs.writeJson(this.filePath, this.data, { spaces: 2 });\n log.info(\n \"Saved i18n data for locale '%s' to file '%s'\",\n pc.green(this.locale),\n pc.dim(this.filePath)\n );\n } catch (e) {\n log.error(\n \"Error saving i18n data for locale '%s' to file '%s'\",\n pc.green(this.locale),\n pc.dim(this.filePath)\n );\n log.error(\"Error: %s\", pc.red(e.message));\n }\n }\n\n async statistic() {\n const total = Object.keys(this.data).length;\n const missing = Object.values(this.data).filter((v) => !v).length;\n\n return {\n locale: this.locale,\n total,\n missing,\n };\n }\n\n async trySaveToGoogle(sheetData: SheetData) {\n await pMap(Object.entries(this.data), async ([key, value]) => {\n if (sheetData.hasI18nItem(this.locale, key)) {\n // 更新已有的\n await sheetData.getI18nItem(this.locale, key).tryUpdate(value);\n } else {\n log.error(\n \"%s range %s not found key %s\",\n pc.bold(this.config.sheetRange),\n pc.bold(this.locale),\n pc.bold(key)\n );\n }\n });\n }\n\n static printStatistic(\n label: string,\n statistics: { locale: string; total: number; missing: number }[]\n ) {\n const table = new Table({\n head: [\"Language\", \"Total count\", \"Missing\"],\n colAligns: [\"left\", \"center\", \"center\"],\n style: {\n head: [\"green\"],\n border: [],\n compact: true,\n },\n });\n statistics.forEach((statistic) => {\n table.push([\n statistic.locale,\n statistic.total,\n statistic.missing > 0 ? pc.red(statistic.missing) : statistic.missing,\n ]);\n });\n console.log(pc.bold(label));\n console.log(table.toString());\n }\n}\n","import pMap from \"p-map\";\nimport { createLogger } from \"../shared\";\nimport { getConfig } from \"../shared/config\";\nimport { I18nData } from \"../shared/i18nData\";\nimport { resolveSheetData } from \"../shared/resolveSheetData\";\n\nconst log = createLogger(\"generate\");\n\nexport async function generate() {\n const config = await getConfig();\n let i = 0;\n for (const configItem of config) {\n i++;\n if (!configItem.sheetId || !configItem.sheetRange) {\n log.error(`config.${i} sheetId 或 sheetRange 为空,请检查`);\n continue;\n }\n const sheetData = await resolveSheetData(configItem);\n\n const statistics = await pMap(configItem.locales, async (locale, index) => {\n const i18nData = new I18nData(locale, configItem);\n await i18nData.updateFromSheetData(sheetData.getLangDataJSON(locale));\n await i18nData.saveToDisk();\n return await i18nData.statistic();\n });\n I18nData.printStatistic(`${configItem.sheetRange} 生成结果: `, statistics);\n }\n}\n","import { GaxiosResponse } from \"gaxios\";\nimport type { sheets_v4 } from \"googleapis\";\nimport { google } from \"googleapis\";\nimport { createLogger } from \"../index\";\nimport { numberToLetter } from \"../letters\";\nimport { googleAuth } from \"./auth\";\n\nconst log = createLogger(\"google:sheets\");\n\nclass GoogleSheet {\n private sheets: sheets_v4.Sheets;\n\n private initPromise: Promise<void> | null = null;\n private async initImpl() {\n if (!this.sheets) {\n await googleAuth.initCredentials();\n this.sheets = google.sheets({\n version: \"v4\",\n auth: googleAuth.oauth2Client,\n });\n }\n }\n /**\n * 调用sheets其他方法的时候需要先初始化\n */\n async init() {\n if (!this.initPromise) {\n this.initPromise = this.initImpl();\n }\n await this.initPromise;\n }\n\n get = async (options: sheets_v4.Params$Resource$Spreadsheets$Values$Get) => {\n await this.init();\n return this.sheets.spreadsheets.values.get(options);\n };\n\n private async update(\n spreadsheetId: string,\n range: string,\n colIndex: number,\n rowIndex: number,\n values: string[][]\n ): Promise<GaxiosResponse<sheets_v4.Schema$UpdateValuesResponse>> {\n await this.init();\n\n const colLetter = numberToLetter(colIndex);\n const sheetNeedRange = `${range}!${colLetter}${rowIndex}`;\n\n const res = await this.sheets.spreadsheets.values.update({\n spreadsheetId,\n range: sheetNeedRange,\n valueInputOption: \"USER_ENTERED\",\n requestBody: { values },\n });\n\n if (res.status === 200) {\n log.info(`更新 ${sheetNeedRange} 的值成功`);\n } else {\n log.error(\"some thing wrong\", res);\n }\n return res;\n }\n\n /**\n * 更新某一个单元格\n */\n async updateCell(\n spreadsheetId: string,\n range: string,\n colIndex: number,\n rowIndex: number,\n values: string\n ) {\n return this.update(spreadsheetId, range, colIndex, rowIndex, [[values]]);\n }\n\n /**\n * 更新某一列\n */\n async updateColumn(\n spreadsheetId: string,\n range: string,\n colIndex: number,\n rowIndex: number,\n values: string[]\n ) {\n return this.update(spreadsheetId, range, colIndex, rowIndex, [\n ...values.map((v) => [v]),\n ]);\n }\n}\n\nexport const googleSheet = new GoogleSheet();\n","/**\n *\n * - input : A\n * output : 0\n * - input : Z\n * output : 25\n * - input : AA\n * output : 26\n * - input : a\n * output : 0\n */\nexport function letterToNumber(letters: string | number) {\n if (typeof letters === \"number\" || !Number.isNaN(+letters)) {\n return +letters;\n }\n\n let n = 0;\n letters = letters.toUpperCase();\n for (let p = 0; p < letters.length; p++) {\n n = letters[p].charCodeAt(0) - 64 + n * 26;\n }\n\n return Math.max(0, n - 1);\n}\n\n/**\n * fork from https://github.com/matthewmueller/number-to-letter\n */\nconst alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\nconst base = alphabet.length;\n/**\n * numberToLetter(0) // A\n * numberToLetter(25) // Z\n * numberToLetter(26) // AA\n * numberToLetter(51) // AZ\n * numberToLetter(52) // BA\n * numberToLetter(676) // ZA\n * numberToLetter(701) // ZZ\n * numberToLetter(702) // AAA\n */\nexport function numberToLetter(n: number) {\n const digits = [];\n\n do {\n const v = n % base;\n digits.push(v);\n n = Math.floor(n / base);\n } while (n-- > 0);\n\n const chars = [];\n while (digits.length) {\n chars.push(alphabet[digits.pop()]);\n }\n\n return chars.join(\"\");\n}\n","import fs from \"fs-extra\";\nimport { OAuth2Client } from \"google-auth-library\";\nimport { google } from \"googleapis\";\nimport { homeConfigDir, createLogger } from \"../index\";\nimport { openAndWaitReturnQuery } from \"./shared\";\n\ninterface AuthOptions {\n client_id: string;\n client_secret: string;\n /**\n * @default ['spreadsheets']\n */\n scope?: string[];\n}\n\nconst log = createLogger(\"google:auth\");\n\nclass GoogleAuth {\n private tokens: any = null;\n\n private tokensCachePath = homeConfigDir(\"chromeAuthToken.json\");\n\n private readonly scope: string[];\n\n oauth2Client: OAuth2Client = new google.auth.OAuth2(\n this.options.client_id,\n this.options.client_secret,\n \"http://localhost:12345\"\n );\n\n constructor(private options: AuthOptions) {\n this.options.scope = this.options.scope ?? [\"spreadsheets\"];\n const scopePrefix = `https://www.googleapis.com/auth/`;\n this.scope = this.options.scope.map((item) => {\n if (item.startsWith(scopePrefix)) {\n return item;\n }\n return `${scopePrefix}${item}`;\n });\n\n try {\n this.tokens = fs.readJsonSync(this.tokensCachePath);\n } catch (e) {\n log.info(\"未获取到缓存的token\");\n this.tokens = null;\n }\n }\n\n private async saveTokens(tokens: any): Promise<void> {\n this.tokens = tokens;\n await fs.writeJSON(this.tokensCachePath, tokens);\n log.info(\"保存token成功\");\n }\n\n async getCode(): Promise<string> {\n log.info(\"将打开chrome浏览器进行授权\");\n const authUrl = this.oauth2Client.generateAuthUrl({\n access_type: \"offline\",\n scope: this.scope,\n });\n return await openAndWaitReturnQuery(authUrl, 12345, \"code\");\n }\n\n private async initCredentialsImpl(): Promise<void> {\n if (!this.tokens) {\n const code = await this.getCode();\n const { tokens } = await this.oauth2Client.getToken(code);\n await this.saveTokens(tokens);\n }\n this.oauth2Client.setCredentials(this.tokens);\n }\n private initCredentialsPromise: Promise<void> | null = null;\n async initCredentials() {\n if (!this.initCredentialsPromise) {\n this.initCredentialsPromise = this.initCredentialsImpl();\n }\n return await this.initCredentialsPromise;\n }\n}\n\n// see in https://console.cloud.google.com/apis/credentials?organizationId=286245507762&orgonly=true&project=dune-cli&supportedpurview=organizationId\nconst clientSecret = {\n client_id:\n \"728508473485-fgl11a3op1ucd0lef9hs49mkliuaipqa.apps.googleusercontent.com\",\n project_id: \"dune-cli\",\n auth_uri: \"https://accounts.google.com/o/oauth2/auth\",\n token_uri: \"https://oauth2.googleapis.com/token\",\n auth_provider_x509_cert_url: \"https://www.googleapis.com/oauth2/v1/certs\",\n client_secret: \"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\",\n redirect_uris: [\"http://localhost\"],\n};\nexport const googleAuth = new GoogleAuth(clientSecret);\n","import connect from \"connect\";\nimport open from \"open\";\nimport { URL } from \"url\";\nimport { createLogger } from \"../index\";\n\nconst log = createLogger(\"google:shared\");\n\nfunction openBrowser(url: string) {\n log.info(`如未自动打开浏览器,可访问以下地址:${url}`);\n open(url, { app: { name: open.apps.chrome } });\n}\n\nexport function openAndWaitReturnQuery(\n toOpenUrl: string,\n port: number,\n queryKey: string\n) {\n return new Promise<string>((resolve) => {\n openBrowser(toOpenUrl);\n\n const app = connect();\n app.use((req, res, next) => {\n const parsedUrl = new URL(req.url, `http://localhost:${port}`);\n const target = parsedUrl.searchParams.get(queryKey);\n if (target) {\n res.writeHead(200, {\n \"content-type\": \"text/html;charset=utf8\",\n });\n res.end(\n `<div>当前窗口可直接关闭</div>\n <script>\n window.open(\"\", \"_self\", \"\");\n window.close();\n </script>`\n );\n server.close();\n server.unref();\n resolve(target);\n }\n next();\n });\n\n const server = app.listen(port);\n });\n}\n","import { InternalConfig } from \"./config\";\nimport { googleSheet } from \"./google/sheet\";\nimport { createLogger } from \"./index\";\nimport { letterToNumber } from \"./letters\";\n\nconst log = createLogger(\"shared:resolveSheetData\");\n\nclass SheetI18nItem {\n value: string;\n\n colIndex: number;\n\n constructor(\n private spreadsheetId: string,\n private range: string,\n public key: string,\n colIndex: number | string,\n public row: string[],\n private rowIndex: number\n ) {\n this.colIndex = letterToNumber(colIndex);\n // TODO: 待确定 无值的key是否要生成\n // value = undefined 即不会生成\n this.value = this.row[this.colIndex] || \"\";\n }\n\n /**\n * 尝试更新 单元格的值\n * 如果一样则不更新\n */\n async tryUpdate(newValue: string) {\n if (newValue === this.value) {\n // 云端的值跟本地一样 不需要同步\n return;\n }\n\n await googleSheet.updateCell(\n this.spreadsheetId,\n this.range,\n this.colIndex,\n this.rowIndex,\n newValue\n );\n }\n}\n\nexport async function resolveSheetData(\n config: InternalConfig\n): Promise<SheetData> {\n const { position } = config;\n const parseStartIndex = config.parseStartIndex ?? 2;\n\n log.info(`读取 ${config.sheetRange} sheet数据`);\n\n const res = await googleSheet.get({\n spreadsheetId: config.sheetId,\n range: config.sheetRange,\n });\n if (!res.data.values) {\n log.error(\n `${config.sheetRange} sheet数据为空,请检查,应该最少有一行头部数据`\n );\n return new SheetData(config, 2);\n }\n\n const sheetData = new SheetData(config, res.data.values.length + 1);\n\n res.data.values.forEach((row: string[], rowIndex) => {\n // excel 的开始索引是1,所以要 +1\n const trulyRowIndex = rowIndex + 1;\n if (parseStartIndex > trulyRowIndex) {\n // 还未到可以开始解析的行数\n return;\n }\n\n const i18nKey = row[letterToNumber(position.key)]?.trim();\n\n if (i18nKey) {\n if (sheetData.keySet.has(i18nKey)) {\n log.error(\n `表格 ${config.sheetRange} 发现重复的i18nKey: ${i18nKey}, 将覆盖前面的值`\n );\n } else {\n sheetData.keySet.add(i18nKey);\n }\n\n config.locales.forEach((lang) => {\n sheetData.setI18nItem(\n lang,\n new SheetI18nItem(\n config.sheetId,\n config.sheetRange,\n i18nKey,\n position[lang],\n row,\n trulyRowIndex\n )\n );\n });\n }\n });\n\n return sheetData;\n}\n\nexport class SheetData {\n keySet = new Set<string>();\n\n constructor(\n private config: InternalConfig,\n // 当前有多少行 新增的时候往后面添加\n public nextAppendRowIndex: number\n ) {}\n\n private value = new Map(\n this.config.locales.map((lang) => {\n return [lang, new Map<string, SheetI18nItem>()];\n })\n );\n\n getI18nItem(lang: string, i18nKey: string) {\n return this.value.get(lang).get(i18nKey);\n }\n\n setI18nItem(lang: string, i18nItem: SheetI18nItem) {\n this.value.get(lang).set(i18nItem.key, i18nItem);\n }\n\n hasI18nItem(lang: string, i18nKey: string) {\n return this.value.get(lang).has(i18nKey);\n }\n\n getLangDataJSON(lang: string) {\n const res = {} as Record<string, string>;\n this.value.get(lang).forEach((i18nItem) => {\n res[i18nItem.key] = i18nItem.value;\n });\n return res;\n }\n}\n","import { createLogger } from \"../shared\";\nimport { getConfig, configName } from \"../shared/config\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\n\nconst log = createLogger(\"initConfig\");\n\nconst tpl = `\n/**\n * @template {import('@dune2/cli').Config} T\n * @param {T | T[]} config - A generic parameter that flows through to the return type\n * @constraint {{import('@dune2/cli').Config}}\n */\nfunction defineConfig(config) {\n return config;\n}\nmodule.exports = defineConfig([]);\n`;\n\nexport const initConfig = async () => {\n const config = await getConfig();\n const configPath = path.join(config[0].cwd, configName);\n log.info(`config file path: ${configPath}`);\n if (await fs.pathExists(configPath)) {\n log.info(`config file already exists, skip`);\n return;\n }\n await fs.writeFile(configPath, tpl);\n log.info(`config file created`);\n};\n","import pMap from \"p-map\";\nimport { createLogger } from \"../shared\";\nimport { getConfig, InternalConfig } from \"../shared/config\";\nimport { googleSheet } from \"../shared/google/sheet\";\nimport { I18nData } from \"../shared/i18nData\";\nimport { letterToNumber } from \"../shared/letters\";\nimport { resolveSheetData } from \"../shared/resolveSheetData\";\n\nconst log = createLogger(\"upload\");\n\n/**\n * 检测有本地有新增的key 则将新增的key更新到云端\n * 无则跳过\n */\nasync function batchUpdateKeys(config: InternalConfig) {\n let sheetData = await resolveSheetData(config);\n\n const allKeys = [];\n const i18nDataMap = new Map<string, I18nData>();\n await pMap(config.locales, async (locale) => {\n const i18nData = new I18nData(locale, config);\n i18nDataMap.set(locale, i18nData);\n const data = await i18nData.loadData();\n allKeys.push(...Object.keys(data));\n });\n\n let needSyncKeys = Array.from(new Set(allKeys)).filter((key) => {\n // 本地有新的key 服务器没有\n return !sheetData.keySet.has(key);\n });\n needSyncKeys = needSyncKeys.sort(config.keySorter);\n\n if (needSyncKeys.length) {\n log.info(`即将同步本地新增加 ${needSyncKeys.length} 个 i18nKey 到云端`);\n await googleSheet.updateColumn(\n config.sheetId,\n config.sheetRange,\n letterToNumber(config.position.key),\n sheetData.nextAppendRowIndex,\n needSyncKeys\n );\n log.info(\"同步新增加i18nKey成功\");\n\n // 批量同步新增的key的values到云端\n for (const lang of config.locales) {\n const langFile = i18nDataMap.get(lang);\n const needSyncValues = [];\n needSyncKeys.forEach((k) => {\n needSyncValues.push(langFile.data[k]);\n });\n log.info(\n `即将同步本地 ${lang} 新增加 ${needSyncValues.length} 个值到云端`\n );\n await googleSheet.updateColumn(\n config.sheetId,\n config.sheetRange,\n letterToNumber(config.position[lang]),\n sheetData.nextAppendRowIndex,\n needSyncValues\n );\n }\n\n await sleep(1000);\n sheetData = await resolveSheetData(config);\n log.info(`已重新获取 ${config.sheetRange} 的数据`);\n }\n\n return { sheetData, i18nDataMap };\n}\nfunction sleep(ms: number) {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport async function upload() {\n const config = await getConfig();\n\n for (const configItem of config) {\n const { sheetData, i18nDataMap } = await batchUpdateKeys(configItem);\n await pMap(configItem.locales, async (locale) => {\n const i18nData = i18nDataMap.get(locale);\n await i18nData.trySaveToGoogle(sheetData);\n });\n }\n\n log.info(\"上传成功\");\n}\n","import { cli, createLogger } from \"../shared\";\nimport prompts from \"prompts\";\n\nconst log = createLogger(\"interactive\");\n\nexport const interactive = async (args: any) => {\n const commands = cli.commands.filter((command) => {\n if (command.name) {\n return !command.name.startsWith(\"@@\") && command.name !== \"interactive\";\n }\n return false;\n });\n\n const commandMap = new Map<string, typeof commands[number]>();\n const res = await prompts({\n type: \"select\",\n name: \"command\",\n message: \"选择要执行的命令:\",\n choices: commands.map((command) => {\n commandMap.set(command.name, command);\n return {\n title: command.name,\n value: command.name,\n description: command.description,\n };\n }),\n });\n\n const command = commandMap.get(res.command);\n if (!command) {\n log.error(\"未找到命令 %s\", res.command);\n return;\n }\n command.commandAction.apply(cli, args);\n};\n","#!/usr/bin/env node\nimport { extract } from \"./commands/extract\";\nimport { generate } from \"./commands/generate\";\nimport { initConfig } from \"./commands/initConfig\";\nimport { upload } from \"./commands/upload\";\nimport { cli } from \"./shared\";\nimport { version } from \"../package.json\";\nimport { interactive } from \"./commands/interactive\";\n\ncli\n .command(\"generate\", \"生成翻译文件\")\n .alias(\"gen\")\n .example(\"dune generate\")\n .example(\"dune gen\")\n .action(generate);\n\ncli.command(\"upload\", \"上传翻译文件\").example(\"dune upload\").action(upload);\n\ncli\n .command(\"extract\", \"提取代码中的文案\")\n .example(\"dune extract\")\n .action(extract);\n\ncli.command(\"init\", \"初始化配置文件\").example(\"dune init\").action(initConfig);\n\ncli\n .command(\"interactive\", \"交互式操作\")\n .example(\"dune interactive\")\n .alias(\"i\")\n .action(interactive);\n// make default command run interactive\ncli.command(\"\").action(interactive);\n\ncli.version(version);\ncli.help();\n\n(async () => {\n try {\n // Parse CLI args without running the command\n cli.parse(process.argv, { run: false });\n // Run the command yourself\n // You only need `await` when your command action returns a Promise\n await cli.runMatchedCommand();\n } catch (error) {\n // Handle error here..\n // e.g.\n // console.error(error.stack)\n // process.exit(1)\n }\n})();\n"],"mappings":"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","names":["fs","pMap","homeConfigDir","fs","path","pc","log","extract","pMap","fs","pMap","google","fs","log","log","fs","log","google","log","log","pMap","fs","path","log","pMap","log","pMap","log","command"]}
@@ -0,0 +1,29 @@
1
+ interface Config {
2
+ i18nDir?: string;
3
+ i18nFileName?: string;
4
+ defaultLocale?: string;
5
+ /**
6
+ * 可以是 id 或者 url,如果是 url 则会自动解析出 id
7
+ * id: 1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk
8
+ * url: https://docs.google.com/spreadsheets/d/1C9-Dol3oO20W9_FhiVlxNsDhOaaejJIgAZYRkonGmfk/edit#gid=1740568548
9
+ */
10
+ sheetId?: string;
11
+ sheetRange?: string;
12
+ /**
13
+ * 对应 语言、i18nKey 所在的列标识符 如(A,B,C...)
14
+ * 传入时将忽略默认的配置
15
+ */
16
+ position?: {
17
+ key: string;
18
+ [key: string]: string;
19
+ };
20
+ /**
21
+ * 从 Google Sheets 哪一行开始解析
22
+ * 默认从第2行开始,模板文件第一行是表头
23
+ * @default 2
24
+ */
25
+ parseStartIndex?: number;
26
+ }
27
+ declare function defineConfig(config: Config | Config[]): Config | Config[];
28
+
29
+ export { Config, defineConfig };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ defineConfig
3
+ } from "./chunk-Q6SDTDV5.js";
4
+ export {
5
+ defineConfig
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@dune2/cli",
3
+ "version": "0.0.3",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/index.js"
8
+ },
9
+ "./cli": {
10
+ "import": "./dist/cli.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "types": "dist/index.d.ts",
15
+ "bin": {
16
+ "dune": "./dist/cli.js"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "@dune2/wasm": "0.0.3",
23
+ "cac": "^6.7.14",
24
+ "cli-table3": "^0.6.3",
25
+ "connect": "^3.7.0",
26
+ "debug": "^4.3.4",
27
+ "fs-extra": "^10.1.0",
28
+ "globby": "^13.1.2",
29
+ "googleapis": "^108.0.0",
30
+ "joycon": "^3.1.1",
31
+ "open": "^8.4.0",
32
+ "p-map": "^5.5.0",
33
+ "path-to-regexp": "^6.2.1",
34
+ "picocolors": "^1.0.0",
35
+ "prompts": "^2.4.2"
36
+ },
37
+ "devDependencies": {
38
+ "@types/debug": "^4.1.7",
39
+ "@types/prompts": "^2.4.0",
40
+ "gaxios": "*",
41
+ "google-auth-library": "*",
42
+ "tsup": "^6.2.3"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup src/cli.ts src/index.ts --clean --splitting",
49
+ "dev": "pnpm run build --watch",
50
+ "test": "vitest run",
51
+ "test:u": "vitest -u"
52
+ }
53
+ }
package/readme.md ADDED
@@ -0,0 +1,23 @@
1
+ ## cli
2
+
3
+ - 提取翻译文案到`json`文件, 运行 `dune extract`
4
+ - 同步文件到`google sheet`(必须要有配置文件), 运行 `dune sync`
5
+ - 从`google sheet`生成文件(必须要有配置文件), 运行 `dune generate`
6
+ - 初始化配置文件, 运行 `dune init`
7
+
8
+ ## 支持的命令
9
+
10
+ ```shell
11
+ Commands:
12
+ generate 生成翻译文件
13
+ upload 上传翻译文件
14
+ extract 提取代码中的文案
15
+ init 初始化配置文件
16
+ interactive 交互式操作
17
+ ```
18
+
19
+ ## 交互式选择
20
+
21
+ 运行`dune` 或者 `dune interactive`,会进入交互式选择,上下键选择要执行的命令,回车键确认
22
+
23
+ ![](.readme_images/interactive.png)