@intlayer/cli 3.5.4 → 3.5.5

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.
Files changed (47) hide show
  1. package/dist/cjs/audit.cjs +109 -0
  2. package/dist/cjs/audit.cjs.map +1 -0
  3. package/dist/cjs/cli.cjs +1 -1
  4. package/dist/cjs/cli.cjs.map +1 -1
  5. package/dist/cjs/config.cjs +1 -1
  6. package/dist/cjs/config.cjs.map +1 -1
  7. package/dist/cjs/listContentDeclaration.cjs +1 -1
  8. package/dist/cjs/listContentDeclaration.cjs.map +1 -1
  9. package/dist/cjs/pull.cjs +8 -5
  10. package/dist/cjs/pull.cjs.map +1 -1
  11. package/dist/cjs/push.cjs +21 -12
  12. package/dist/cjs/push.cjs.map +1 -1
  13. package/dist/esm/audit.mjs +74 -0
  14. package/dist/esm/audit.mjs.map +1 -0
  15. package/dist/esm/cli.mjs +1 -1
  16. package/dist/esm/cli.mjs.map +1 -1
  17. package/dist/esm/config.mjs +2 -2
  18. package/dist/esm/config.mjs.map +1 -1
  19. package/dist/esm/listContentDeclaration.mjs +2 -2
  20. package/dist/esm/listContentDeclaration.mjs.map +1 -1
  21. package/dist/esm/pull.mjs +9 -6
  22. package/dist/esm/pull.mjs.map +1 -1
  23. package/dist/esm/push.mjs +22 -13
  24. package/dist/esm/push.mjs.map +1 -1
  25. package/dist/types/{audit/index.d.ts → audit.d.ts} +2 -2
  26. package/dist/types/audit.d.ts.map +1 -0
  27. package/dist/types/push.d.ts.map +1 -1
  28. package/package.json +10 -11
  29. package/dist/cjs/audit/CJS_FORMAT.md +0 -23
  30. package/dist/cjs/audit/JSON_FORMAT.md +0 -26
  31. package/dist/cjs/audit/JSX_FORMAT.md +0 -28
  32. package/dist/cjs/audit/MJS_FORMAT.md +0 -23
  33. package/dist/cjs/audit/PROMPT.md +0 -146
  34. package/dist/cjs/audit/TSX_FORMAT.md +0 -28
  35. package/dist/cjs/audit/TS_FORMAT.md +0 -22
  36. package/dist/cjs/audit/index.cjs +0 -131
  37. package/dist/cjs/audit/index.cjs.map +0 -1
  38. package/dist/esm/audit/CJS_FORMAT.md +0 -23
  39. package/dist/esm/audit/JSON_FORMAT.md +0 -26
  40. package/dist/esm/audit/JSX_FORMAT.md +0 -28
  41. package/dist/esm/audit/MJS_FORMAT.md +0 -23
  42. package/dist/esm/audit/PROMPT.md +0 -146
  43. package/dist/esm/audit/TSX_FORMAT.md +0 -28
  44. package/dist/esm/audit/TS_FORMAT.md +0 -22
  45. package/dist/esm/audit/index.mjs +0 -96
  46. package/dist/esm/audit/index.mjs.map +0 -1
  47. package/dist/types/audit/index.d.ts.map +0 -1
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var audit_exports = {};
30
+ __export(audit_exports, {
31
+ audit: () => audit,
32
+ auditFile: () => auditFile
33
+ });
34
+ module.exports = __toCommonJS(audit_exports);
35
+ var import_fs = require("fs");
36
+ var import_path = require("path");
37
+ var import_config = require("@intlayer/config");
38
+ var import_libs = require("@intlayer/design-system/libs");
39
+ var import_p_limit = __toESM(require("p-limit"));
40
+ var import_listContentDeclaration = require('./listContentDeclaration.cjs');
41
+ const projectPath = process.cwd();
42
+ const auditFile = async (filePath, options) => {
43
+ try {
44
+ const { defaultLocale, locales } = (0, import_config.getConfiguration)().internationalization;
45
+ const relativePath = (0, import_path.relative)(projectPath, filePath);
46
+ (0, import_config.logger)(`Auditing file: ${relativePath}`);
47
+ const fileContent = (0, import_fs.readFileSync)(filePath, "utf-8");
48
+ const auditFileResult = await import_libs.intlayerAPI.ai.auditFile(
49
+ {
50
+ fileContent,
51
+ filePath,
52
+ locales,
53
+ defaultLocale,
54
+ model: options?.model,
55
+ openAiApiKey: options?.openAiApiKey,
56
+ customPrompt: options?.customPrompt
57
+ },
58
+ {
59
+ headers: options?.headers
60
+ }
61
+ );
62
+ if (!auditFileResult.data) {
63
+ throw new Error("Audit failed");
64
+ }
65
+ (0, import_fs.writeFileSync)(filePath, auditFileResult.data.fileContent);
66
+ (0, import_config.logger)(`File ${relativePath} updated`);
67
+ (0, import_config.logger)(`${auditFileResult.data.tokenUsed} tokens used in the request`);
68
+ } catch (error) {
69
+ (0, import_config.logger)(error, { level: "error" });
70
+ }
71
+ };
72
+ const getAbsolutePath = (filePath) => {
73
+ if (filePath.startsWith(".")) {
74
+ const absolutePath = (0, import_path.join)(process.cwd(), filePath);
75
+ return absolutePath;
76
+ }
77
+ return filePath;
78
+ };
79
+ const audit = async (options) => {
80
+ const { clientId, clientSecret } = (0, import_config.getConfiguration)().editor;
81
+ let headers = {};
82
+ if (clientId && clientSecret) {
83
+ const oAuth2TokenResult = await import_libs.intlayerAPI.auth.getOAuth2AccessToken();
84
+ const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
85
+ headers = { Authorization: `Bearer ${oAuth2AccessToken}` };
86
+ } else if (!options?.openAiApiKey) {
87
+ throw new Error(
88
+ "Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project. You can also provide your own OpenAI API key to audit the content declaration files. For that you need to provide the --openAiApiKey option."
89
+ );
90
+ }
91
+ let contentDeclarationFilesList = options?.files?.map(getAbsolutePath);
92
+ if (!contentDeclarationFilesList) {
93
+ const contentDeclarationFilesPath = (0, import_listContentDeclaration.getContentDeclaration)({
94
+ exclude: options?.excludedGlobs
95
+ });
96
+ contentDeclarationFilesList = contentDeclarationFilesPath;
97
+ }
98
+ const limit = (0, import_p_limit.default)(options?.asyncLimit ? Number(options?.asyncLimit) : 5);
99
+ const pushPromises = contentDeclarationFilesList.map(
100
+ (filePath) => limit(() => auditFile(filePath, { ...options, headers }))
101
+ );
102
+ await Promise.all(pushPromises);
103
+ };
104
+ // Annotate the CommonJS export names for ESM import in node:
105
+ 0 && (module.exports = {
106
+ audit,
107
+ auditFile
108
+ });
109
+ //# sourceMappingURL=audit.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/audit.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'fs';\nimport { join, relative } from 'path';\nimport { getConfiguration, logger } from '@intlayer/config';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport pLimit from 'p-limit';\nimport { getContentDeclaration } from './listContentDeclaration';\n\n// Depending on your implementation, you may need the OpenAI API client.\n// For instance, you can use `openai` npm package (https://www.npmjs.com/package/openai).\n\ntype AuditOptions = {\n files?: string[];\n model?: string;\n customPrompt?: string;\n asyncLimit?: number;\n openAiApiKey?: string;\n excludedGlobs?: string[];\n headers?: Record<string, string>;\n};\n\nconst projectPath = process.cwd();\n\n/**\n * Audits a content declaration file by constructing a prompt for ChatGPT.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies. It prints the prompt for each file,\n * and could be adapted to send requests to the ChatGPT model.\n *\n * @async\n * @function\n * @param filePath - The relative or absolute path to the target file.\n * @param options - Optional configuration for the audit process.\n * @returns This function returns a Promise that resolves once the audit is complete.\n */\nexport const auditFile = async (filePath: string, options?: AuditOptions) => {\n try {\n const { defaultLocale, locales } = getConfiguration().internationalization;\n\n const relativePath = relative(projectPath, filePath);\n logger(`Auditing file: ${relativePath}`);\n\n // Read the file's content.\n const fileContent = readFileSync(filePath, 'utf-8');\n\n // Example of how you might request a completion from ChatGPT:\n const auditFileResult = await intlayerAPI.ai.auditFile(\n {\n fileContent,\n filePath,\n locales,\n defaultLocale,\n model: options?.model,\n openAiApiKey: options?.openAiApiKey,\n customPrompt: options?.customPrompt,\n },\n {\n headers: options?.headers,\n }\n );\n\n if (!auditFileResult.data) {\n throw new Error('Audit failed');\n }\n\n writeFileSync(filePath, auditFileResult.data.fileContent);\n\n logger(`File ${relativePath} updated`);\n\n logger(`${auditFileResult.data.tokenUsed} tokens used in the request`);\n } catch (error) {\n logger(error, { level: 'error' });\n }\n};\n\nconst getAbsolutePath = (filePath: string): string => {\n if (filePath.startsWith('.')) {\n const absolutePath = join(process.cwd(), filePath);\n\n return absolutePath;\n }\n return filePath;\n};\n\n/**\n * Audits the content declaration files by constructing a prompt for ChatGPT.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies. It prints the prompt for each file,\n * and could be adapted to send requests to the ChatGPT model.\n *\n * @async\n * @function\n * @param options - Optional configuration for the audit process.\n * @returns This function returns a Promise that resolves once the audit is complete.\n */\nexport const audit = async (options: AuditOptions) => {\n const { clientId, clientSecret } = getConfiguration().editor;\n let headers: Record<string, string> = {};\n\n if (clientId && clientSecret) {\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n headers = { Authorization: `Bearer ${oAuth2AccessToken}` };\n } else if (!options?.openAiApiKey) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project. You can also provide your own OpenAI API key to audit the content declaration files. For that you need to provide the --openAiApiKey option.'\n );\n }\n\n let contentDeclarationFilesList = options?.files?.map(getAbsolutePath);\n\n if (!contentDeclarationFilesList) {\n // Retrieve all content declaration file paths using a helper function.\n const contentDeclarationFilesPath = getContentDeclaration({\n exclude: options?.excludedGlobs,\n });\n\n contentDeclarationFilesList = contentDeclarationFilesPath;\n }\n\n const limit = pLimit(options?.asyncLimit ? Number(options?.asyncLimit) : 5); // Limit the number of concurrent requests\n const pushPromises = contentDeclarationFilesList.map((filePath) =>\n limit(() => auditFile(filePath, { ...options, headers }))\n );\n await Promise.all(pushPromises);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAA4C;AAC5C,kBAA+B;AAC/B,oBAAyC;AACzC,kBAA4B;AAC5B,qBAAmB;AACnB,oCAAsC;AAetC,MAAM,cAAc,QAAQ,IAAI;AAczB,MAAM,YAAY,OAAO,UAAkB,YAA2B;AAC3E,MAAI;AACF,UAAM,EAAE,eAAe,QAAQ,QAAI,gCAAiB,EAAE;AAEtD,UAAM,mBAAe,sBAAS,aAAa,QAAQ;AACnD,8BAAO,kBAAkB,YAAY,EAAE;AAGvC,UAAM,kBAAc,wBAAa,UAAU,OAAO;AAGlD,UAAM,kBAAkB,MAAM,wBAAY,GAAG;AAAA,MAC3C;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,SAAS;AAAA,QAChB,cAAc,SAAS;AAAA,QACvB,cAAc,SAAS;AAAA,MACzB;AAAA,MACA;AAAA,QACE,SAAS,SAAS;AAAA,MACpB;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB,MAAM;AACzB,YAAM,IAAI,MAAM,cAAc;AAAA,IAChC;AAEA,iCAAc,UAAU,gBAAgB,KAAK,WAAW;AAExD,8BAAO,QAAQ,YAAY,UAAU;AAErC,8BAAO,GAAG,gBAAgB,KAAK,SAAS,6BAA6B;AAAA,EACvE,SAAS,OAAO;AACd,8BAAO,OAAO,EAAE,OAAO,QAAQ,CAAC;AAAA,EAClC;AACF;AAEA,MAAM,kBAAkB,CAAC,aAA6B;AACpD,MAAI,SAAS,WAAW,GAAG,GAAG;AAC5B,UAAM,mBAAe,kBAAK,QAAQ,IAAI,GAAG,QAAQ;AAEjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAaO,MAAM,QAAQ,OAAO,YAA0B;AACpD,QAAM,EAAE,UAAU,aAAa,QAAI,gCAAiB,EAAE;AACtD,MAAI,UAAkC,CAAC;AAEvC,MAAI,YAAY,cAAc;AAC5B,UAAM,oBAAoB,MAAM,wBAAY,KAAK,qBAAqB;AAEtE,UAAM,oBAAoB,kBAAkB,MAAM;AAElD,cAAU,EAAE,eAAe,UAAU,iBAAiB,GAAG;AAAA,EAC3D,WAAW,CAAC,SAAS,cAAc;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,8BAA8B,SAAS,OAAO,IAAI,eAAe;AAErE,MAAI,CAAC,6BAA6B;AAEhC,UAAM,kCAA8B,qDAAsB;AAAA,MACxD,SAAS,SAAS;AAAA,IACpB,CAAC;AAED,kCAA8B;AAAA,EAChC;AAEA,QAAM,YAAQ,eAAAA,SAAO,SAAS,aAAa,OAAO,SAAS,UAAU,IAAI,CAAC;AAC1E,QAAM,eAAe,4BAA4B;AAAA,IAAI,CAAC,aACpD,MAAM,MAAM,UAAU,UAAU,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;AAAA,EAC1D;AACA,QAAM,QAAQ,IAAI,YAAY;AAChC;","names":["pLimit"]}
package/dist/cjs/cli.cjs CHANGED
@@ -22,7 +22,7 @@ __export(cli_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(cli_exports);
24
24
  var import_commander = require("commander");
25
- var import_audit = require('./audit/index.cjs');
25
+ var import_audit = require('./audit.cjs');
26
26
  var import_build = require('./build.cjs');
27
27
  var import_config = require('./config.cjs');
28
28
  var import_listContentDeclaration = require('./listContentDeclaration.cjs');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { audit } from './audit/index';\nimport { build } from './build';\nimport { getConfig } from './config';\nimport { listContentDeclaration } from './listContentDeclaration';\nimport { pull } from './pull';\nimport { push } from './push';\n\n/**\n * Set the API for the CLI\n *\n * Example of commands:\n *\n * npm run intlayer build --watch\n * npm run intlayer push --dictionaries id1 id2 id3 --deleteLocaleDir\n */\nexport const setAPI = (): Command => {\n const program = new Command();\n\n program.version('1.0.0').description('Intlayer CLI');\n\n program\n .command('build')\n .description('Build the dictionaries')\n .option('-w, --watch', 'Watch for changes')\n .action(build);\n\n program\n .command('push')\n .description(\n 'Push all dictionaries. Create or update the pushed dictionaries'\n )\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to push')\n .option(\n '-r, --deleteLocaleDictionary',\n 'Delete the local dictionaries after pushing'\n )\n .option(\n '-k, --keepLocaleDictionary',\n 'Keep the local dictionaries after pushing'\n )\n .action(push);\n\n program\n .command('pull')\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to pull')\n .option('--newDictionariesPath [path]', 'Path to save the new dictionaries')\n .action(pull);\n\n program\n .command('get config')\n .description('Get the configuration')\n .action(getConfig);\n\n program\n .command('content list')\n .description('List the content declaration files')\n .action(listContentDeclaration);\n\n program\n .command('audit')\n .description('Audit the dictionaries')\n .option(\n '-f, --files [files...]',\n 'List of Content Declaration files to audit'\n )\n .option(\n '--exclude [excludedGlobs...]',\n 'Globs pattern to exclude from the audit'\n )\n .option('-m, --model [model]', 'Model')\n .option('-p, --custom-prompt [prompt]', 'Custom prompt')\n .option('-l, --async-limit [asyncLimit]', 'Async limit')\n .option('-k, --open-ai-api-key [openAiApiKey]', 'OpenAI API key')\n .action(audit);\n\n program.parse(process.argv);\n\n return program;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAwB;AACxB,mBAAsB;AACtB,mBAAsB;AACtB,oBAA0B;AAC1B,oCAAuC;AACvC,kBAAqB;AACrB,kBAAqB;AAUd,MAAM,SAAS,MAAe;AACnC,QAAM,UAAU,IAAI,yBAAQ;AAE5B,UAAQ,QAAQ,OAAO,EAAE,YAAY,cAAc;AAEnD,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC,OAAO,eAAe,mBAAmB,EACzC,OAAO,kBAAK;AAEf,UACG,QAAQ,MAAM,EACd;AAAA,IACC;AAAA,EACF,EACC,OAAO,+BAA+B,gCAAgC,EACtE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,gBAAI;AAEd,UACG,QAAQ,MAAM,EACd,OAAO,+BAA+B,gCAAgC,EACtE,OAAO,gCAAgC,mCAAmC,EAC1E,OAAO,gBAAI;AAEd,UACG,QAAQ,YAAY,EACpB,YAAY,uBAAuB,EACnC,OAAO,uBAAS;AAEnB,UACG,QAAQ,cAAc,EACtB,YAAY,oCAAoC,EAChD,OAAO,oDAAsB;AAEhC,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,uBAAuB,OAAO,EACrC,OAAO,gCAAgC,eAAe,EACtD,OAAO,kCAAkC,aAAa,EACtD,OAAO,wCAAwC,gBAAgB,EAC/D,OAAO,kBAAK;AAEf,UAAQ,MAAM,QAAQ,IAAI;AAE1B,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { audit } from './audit';\nimport { build } from './build';\nimport { getConfig } from './config';\nimport { listContentDeclaration } from './listContentDeclaration';\nimport { pull } from './pull';\nimport { push } from './push';\n\n/**\n * Set the API for the CLI\n *\n * Example of commands:\n *\n * npm run intlayer build --watch\n * npm run intlayer push --dictionaries id1 id2 id3 --deleteLocaleDir\n */\nexport const setAPI = (): Command => {\n const program = new Command();\n\n program.version('1.0.0').description('Intlayer CLI');\n\n program\n .command('build')\n .description('Build the dictionaries')\n .option('-w, --watch', 'Watch for changes')\n .action(build);\n\n program\n .command('push')\n .description(\n 'Push all dictionaries. Create or update the pushed dictionaries'\n )\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to push')\n .option(\n '-r, --deleteLocaleDictionary',\n 'Delete the local dictionaries after pushing'\n )\n .option(\n '-k, --keepLocaleDictionary',\n 'Keep the local dictionaries after pushing'\n )\n .action(push);\n\n program\n .command('pull')\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to pull')\n .option('--newDictionariesPath [path]', 'Path to save the new dictionaries')\n .action(pull);\n\n program\n .command('get config')\n .description('Get the configuration')\n .action(getConfig);\n\n program\n .command('content list')\n .description('List the content declaration files')\n .action(listContentDeclaration);\n\n program\n .command('audit')\n .description('Audit the dictionaries')\n .option(\n '-f, --files [files...]',\n 'List of Content Declaration files to audit'\n )\n .option(\n '--exclude [excludedGlobs...]',\n 'Globs pattern to exclude from the audit'\n )\n .option('-m, --model [model]', 'Model')\n .option('-p, --custom-prompt [prompt]', 'Custom prompt')\n .option('-l, --async-limit [asyncLimit]', 'Async limit')\n .option('-k, --open-ai-api-key [openAiApiKey]', 'OpenAI API key')\n .action(audit);\n\n program.parse(process.argv);\n\n return program;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAwB;AACxB,mBAAsB;AACtB,mBAAsB;AACtB,oBAA0B;AAC1B,oCAAuC;AACvC,kBAAqB;AACrB,kBAAqB;AAUd,MAAM,SAAS,MAAe;AACnC,QAAM,UAAU,IAAI,yBAAQ;AAE5B,UAAQ,QAAQ,OAAO,EAAE,YAAY,cAAc;AAEnD,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC,OAAO,eAAe,mBAAmB,EACzC,OAAO,kBAAK;AAEf,UACG,QAAQ,MAAM,EACd;AAAA,IACC;AAAA,EACF,EACC,OAAO,+BAA+B,gCAAgC,EACtE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,gBAAI;AAEd,UACG,QAAQ,MAAM,EACd,OAAO,+BAA+B,gCAAgC,EACtE,OAAO,gCAAgC,mCAAmC,EAC1E,OAAO,gBAAI;AAEd,UACG,QAAQ,YAAY,EACpB,YAAY,uBAAuB,EACnC,OAAO,uBAAS;AAEnB,UACG,QAAQ,cAAc,EACtB,YAAY,oCAAoC,EAChD,OAAO,oDAAsB;AAEhC,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,uBAAuB,OAAO,EACrC,OAAO,gCAAgC,eAAe,EACtD,OAAO,kCAAkC,aAAa,EACtD,OAAO,wCAAwC,gBAAgB,EAC/D,OAAO,kBAAK;AAEf,UAAQ,MAAM,QAAQ,IAAI;AAE1B,SAAO;AACT;","names":[]}
@@ -24,7 +24,7 @@ module.exports = __toCommonJS(config_exports);
24
24
  var import_config = require("@intlayer/config");
25
25
  const getConfig = (_options) => {
26
26
  const config = (0, import_config.getConfiguration)();
27
- console.info(config);
27
+ (0, import_config.logger)(config);
28
28
  };
29
29
  // Annotate the CommonJS export names for ESM import in node:
30
30
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config.ts"],"sourcesContent":["import { getConfiguration } from '@intlayer/config';\n\ntype ConfigOptions = {};\n\nexport const getConfig = (_options?: ConfigOptions) => {\n const config = getConfiguration();\n\n console.info(config);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAiC;AAI1B,MAAM,YAAY,CAAC,aAA6B;AACrD,QAAM,aAAS,gCAAiB;AAEhC,UAAQ,KAAK,MAAM;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../src/config.ts"],"sourcesContent":["import { getConfiguration, logger } from '@intlayer/config';\n\ntype ConfigOptions = {};\n\nexport const getConfig = (_options?: ConfigOptions) => {\n const config = getConfiguration();\n\n logger(config);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAyC;AAIlC,MAAM,YAAY,CAAC,aAA6B;AACrD,QAAM,aAAS,gCAAiB;AAEhC,4BAAO,MAAM;AACf;","names":[]}
@@ -48,7 +48,7 @@ const getContentDeclaration = (options) => {
48
48
  };
49
49
  const listContentDeclaration = (_options) => {
50
50
  const contentDeclarationFilesPath = getContentDeclaration();
51
- console.info("Content declaration files: ", contentDeclarationFilesPath);
51
+ (0, import_config.logger)(`Content declaration files: ${contentDeclarationFilesPath}`);
52
52
  };
53
53
  // Annotate the CommonJS export names for ESM import in node:
54
54
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { getConfiguration } from '@intlayer/config';\nimport fg from 'fast-glob';\n\ntype GetContentDeclarationOptions = {\n exclude?: string[];\n};\n\nexport const getContentDeclaration = (\n options?: GetContentDeclarationOptions\n): string[] => {\n const {\n content: { watchedFilesPatternWithPath },\n } = getConfiguration();\n\n const contentDeclarationFilesPath: string[] = fg.sync(\n watchedFilesPatternWithPath,\n {\n ignore: options?.exclude,\n }\n );\n\n return contentDeclarationFilesPath;\n};\n\ntype ListContentDeclarationOptions = {};\n\nexport const listContentDeclaration = (\n _options: ListContentDeclarationOptions\n) => {\n const contentDeclarationFilesPath = getContentDeclaration();\n\n console.info('Content declaration files: ', contentDeclarationFilesPath);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAiC;AACjC,uBAAe;AAMR,MAAM,wBAAwB,CACnC,YACa;AACb,QAAM;AAAA,IACJ,SAAS,EAAE,4BAA4B;AAAA,EACzC,QAAI,gCAAiB;AAErB,QAAM,8BAAwC,iBAAAA,QAAG;AAAA,IAC/C;AAAA,IACA;AAAA,MACE,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAIO,MAAM,yBAAyB,CACpC,aACG;AACH,QAAM,8BAA8B,sBAAsB;AAE1D,UAAQ,KAAK,+BAA+B,2BAA2B;AACzE;","names":["fg"]}
1
+ {"version":3,"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { getConfiguration, logger } from '@intlayer/config';\nimport fg from 'fast-glob';\n\ntype GetContentDeclarationOptions = {\n exclude?: string[];\n};\n\nexport const getContentDeclaration = (\n options?: GetContentDeclarationOptions\n): string[] => {\n const {\n content: { watchedFilesPatternWithPath },\n } = getConfiguration();\n\n const contentDeclarationFilesPath: string[] = fg.sync(\n watchedFilesPatternWithPath,\n {\n ignore: options?.exclude,\n }\n );\n\n return contentDeclarationFilesPath;\n};\n\ntype ListContentDeclarationOptions = {};\n\nexport const listContentDeclaration = (\n _options: ListContentDeclarationOptions\n) => {\n const contentDeclarationFilesPath = getContentDeclaration();\n\n logger(`Content declaration files: ${contentDeclarationFilesPath}`);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAyC;AACzC,uBAAe;AAMR,MAAM,wBAAwB,CACnC,YACa;AACb,QAAM;AAAA,IACJ,SAAS,EAAE,4BAA4B;AAAA,EACzC,QAAI,gCAAiB;AAErB,QAAM,8BAAwC,iBAAAA,QAAG;AAAA,IAC/C;AAAA,IACA;AAAA,MACE,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AACT;AAIO,MAAM,yBAAyB,CACpC,aACG;AACH,QAAM,8BAA8B,sBAAsB;AAE1D,4BAAO,8BAA8B,2BAA2B,EAAE;AACpE;","names":["fg"]}
package/dist/cjs/pull.cjs CHANGED
@@ -74,10 +74,10 @@ const pull = async (options) => {
74
74
  );
75
75
  }
76
76
  if (distantDictionariesKeys.length === 0) {
77
- console.error("No dictionaries to fetch");
77
+ (0, import_config.logger)("No dictionaries to fetch", { level: "error" });
78
78
  return;
79
79
  }
80
- console.info("Fetching dictionaries:");
80
+ (0, import_config.logger)("Fetching dictionaries:");
81
81
  const dictionariesStatuses = distantDictionariesKeys.map((dictionaryKey, index) => ({
82
82
  dictionaryKey,
83
83
  icon: getStatusIcon("pending"),
@@ -126,11 +126,11 @@ const pull = async (options) => {
126
126
  updateAllStatusLines(dictionariesStatuses);
127
127
  for (const statusObj of dictionariesStatuses) {
128
128
  if (statusObj.errorMessage) {
129
- console.error(statusObj.errorMessage);
129
+ (0, import_config.logger)(statusObj.errorMessage, { level: "error" });
130
130
  }
131
131
  }
132
132
  } catch (error) {
133
- console.error(error);
133
+ (0, import_config.logger)(error, { level: "error" });
134
134
  }
135
135
  };
136
136
  const getStatusIcon = (status) => {
@@ -146,6 +146,9 @@ const getStatusIcon = (status) => {
146
146
  return statusIcons[status] ?? "";
147
147
  };
148
148
  const getStatusLine = (statusObj) => {
149
+ const {
150
+ log: { prefix }
151
+ } = (0, import_config.getConfiguration)();
149
152
  let icon = getStatusIcon(statusObj.status);
150
153
  let colorStart = "";
151
154
  let colorEnd = "";
@@ -166,7 +169,7 @@ const getStatusLine = (statusObj) => {
166
169
  colorStart = GREY;
167
170
  colorEnd = RESET;
168
171
  }
169
- return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon} ${statusObj.status}${colorEnd}]`;
172
+ return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;
170
173
  };
171
174
  const updateAllStatusLines = (dictionariesStatuses) => {
172
175
  readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport * as fsPromises from 'fs/promises';\nimport { basename, dirname, extname } from 'path';\nimport * as readline from 'readline';\nimport { getConfiguration } from '@intlayer/config';\nimport { Dictionary } from '@intlayer/core';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport dictionariesRecord from '@intlayer/dictionaries-entry';\nimport _ from 'lodash';\nimport pLimit from 'p-limit';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n logPrefix?: string;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status:\n | 'pending'\n | 'fetching'\n | 'up-to-date'\n | 'updated'\n | 'fetched'\n | 'unknown'\n | 'error'\n | 'imported'\n | 'reimported in JSON'\n | 'reimported in new location';\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst YELLOW = '\\x1b[33m';\nconst GREY_DARK = '\\x1b[90m';\n\nconst DEFAULT_NEW_DICTIONARY_PATH = 'intlayer-dictionaries';\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n try {\n const {\n editor: { clientId, clientSecret },\n } = getConfiguration();\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.dictionary.getDictionariesKeys({\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesKeys: string[] = getDictionariesKeysResult.data;\n\n if (options?.dictionaries) {\n // Filter the dictionaries from the provided list of IDs\n distantDictionariesKeys = distantDictionariesKeys.filter(\n (dictionaryKey) => options.dictionaries!.includes(dictionaryKey)\n );\n }\n\n // Check if dictionaries list is empty\n if (distantDictionariesKeys.length === 0) {\n console.error('No dictionaries to fetch');\n return;\n }\n\n console.info('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] =\n distantDictionariesKeys.map((dictionaryKey, index) => ({\n dictionaryKey,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n }));\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'fetching';\n try {\n // Fetch the dictionary\n const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(\n statusObj.dictionaryKey,\n undefined,\n {\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n }\n );\n\n const distantDictionary = getDictionaryResult.data;\n\n if (!distantDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const writeStatus = await writeDictionary(distantDictionary, options);\n\n statusObj.status = writeStatus;\n\n successfullyFetchedDictionaries.push(distantDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n }\n };\n\n const fetchPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n\n await Promise.all(fetchPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n console.error(statusObj.errorMessage);\n }\n }\n } catch (error) {\n console.error(error);\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n fetching: '', // Spinner handled separately\n 'up-to-date': '✔',\n updated: '✔',\n fetched: '✔',\n error: '✖',\n };\n return statusIcons[status] ?? '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'fetching') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'fetched' ||\n statusObj.status === 'imported' ||\n statusObj.status === 'updated' ||\n statusObj.status === 'up-to-date'\n ) {\n colorStart = GREEN;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'reimported in JSON' ||\n statusObj.status === 'reimported in new location'\n ) {\n colorStart = YELLOW;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon} ${statusObj.status}${colorEnd}]`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'fetching') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n\nconst writeDictionary = async (\n distantDictionary: Dictionary,\n options?: PullOptions\n): Promise<DictionariesStatus['status']> => {\n const {\n content: { baseDir },\n } = getConfiguration();\n\n const newDictionaryRelativeLocationPath =\n options?.newDictionariesPath ?? DEFAULT_NEW_DICTIONARY_PATH;\n const newDictionaryLocationPath = `${baseDir}/${newDictionaryRelativeLocationPath}`;\n\n const existingDictionary = dictionariesRecord[distantDictionary.key];\n\n if (existingDictionary) {\n const { filePath } = existingDictionary;\n\n // Compare existing dictionary with distant dictionary\n if (_.isEqual(existingDictionary, distantDictionary)) {\n // Up to date, nothing to do\n return 'up-to-date';\n } else {\n if (filePath) {\n const isDictionaryJSON = filePath.endsWith('.json');\n\n if (isDictionaryJSON) {\n // Write the dictionary to the same location of the existing dictionary file\n await fsPromises.writeFile(\n `${baseDir}/${filePath}`,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'updated';\n } else {\n // Write the dictionary to the intlayer-dictionaries directory\n const dictionariesDirPath = dirname(filePath);\n const dictionariesFileName = basename(filePath, extname(filePath));\n\n const newFilePath = `${dictionariesDirPath}/${dictionariesFileName}.json`;\n\n await writeFileWithDirectories(\n newFilePath,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'reimported in JSON';\n }\n } else {\n // Write the dictionary to the intlayer-dictionaries directory\n const dictionaryPath = `${newDictionaryLocationPath}/${distantDictionary.key}.content.json`;\n await writeFileWithDirectories(\n dictionaryPath,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'reimported in new location';\n }\n }\n } else {\n // No existing dictionary, write to new location\n const dictionaryPath = `${newDictionaryLocationPath}/${distantDictionary.key}.content.json`;\n\n await writeFileWithDirectories(\n dictionaryPath,\n JSON.stringify(distantDictionary, null, 2)\n );\n\n return 'imported';\n }\n};\n\nconst writeFileWithDirectories = async (\n filePath: string,\n data: string | Buffer\n): Promise<void> => {\n try {\n // Extract the directory from the file path\n const dir = dirname(filePath);\n\n // Check if the directory exists\n const directoryExists = existsSync(dir);\n\n if (!directoryExists) {\n // Create the directory recursively\n await fsPromises.mkdir(dir, { recursive: true });\n }\n\n // Write the file\n await fsPromises.writeFile(filePath, data);\n } catch (error) {\n throw new Error(`Error writing file to ${filePath}: ${error}`);\n }\n};\n"],"mappings":"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","names":["pLimit","dictionariesRecord","_"]}
1
+ {"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport * as fsPromises from 'fs/promises';\nimport { basename, dirname, extname } from 'path';\nimport * as readline from 'readline';\nimport { getConfiguration, logger } from '@intlayer/config';\nimport { Dictionary } from '@intlayer/core';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport dictionariesRecord from '@intlayer/dictionaries-entry';\nimport _ from 'lodash';\nimport pLimit from 'p-limit';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n logPrefix?: string;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status:\n | 'pending'\n | 'fetching'\n | 'up-to-date'\n | 'updated'\n | 'fetched'\n | 'unknown'\n | 'error'\n | 'imported'\n | 'reimported in JSON'\n | 'reimported in new location';\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst YELLOW = '\\x1b[33m';\nconst GREY_DARK = '\\x1b[90m';\n\nconst DEFAULT_NEW_DICTIONARY_PATH = 'intlayer-dictionaries';\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n try {\n const {\n editor: { clientId, clientSecret },\n } = getConfiguration();\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.dictionary.getDictionariesKeys({\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesKeys: string[] = getDictionariesKeysResult.data;\n\n if (options?.dictionaries) {\n // Filter the dictionaries from the provided list of IDs\n distantDictionariesKeys = distantDictionariesKeys.filter(\n (dictionaryKey) => options.dictionaries!.includes(dictionaryKey)\n );\n }\n\n // Check if dictionaries list is empty\n if (distantDictionariesKeys.length === 0) {\n logger('No dictionaries to fetch', { level: 'error' });\n return;\n }\n\n logger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] =\n distantDictionariesKeys.map((dictionaryKey, index) => ({\n dictionaryKey,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n }));\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'fetching';\n try {\n // Fetch the dictionary\n const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(\n statusObj.dictionaryKey,\n undefined,\n {\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n }\n );\n\n const distantDictionary = getDictionaryResult.data;\n\n if (!distantDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const writeStatus = await writeDictionary(distantDictionary, options);\n\n statusObj.status = writeStatus;\n\n successfullyFetchedDictionaries.push(distantDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n }\n };\n\n const fetchPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n\n await Promise.all(fetchPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n logger(statusObj.errorMessage, { level: 'error' });\n }\n }\n } catch (error) {\n logger(error, { level: 'error' });\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n fetching: '', // Spinner handled separately\n 'up-to-date': '✔',\n updated: '✔',\n fetched: '✔',\n error: '✖',\n };\n return statusIcons[status] ?? '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n const {\n log: { prefix },\n } = getConfiguration();\n\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'fetching') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'fetched' ||\n statusObj.status === 'imported' ||\n statusObj.status === 'updated' ||\n statusObj.status === 'up-to-date'\n ) {\n colorStart = GREEN;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'reimported in JSON' ||\n statusObj.status === 'reimported in new location'\n ) {\n colorStart = YELLOW;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'fetching') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n\nconst writeDictionary = async (\n distantDictionary: Dictionary,\n options?: PullOptions\n): Promise<DictionariesStatus['status']> => {\n const {\n content: { baseDir },\n } = getConfiguration();\n\n const newDictionaryRelativeLocationPath =\n options?.newDictionariesPath ?? DEFAULT_NEW_DICTIONARY_PATH;\n const newDictionaryLocationPath = `${baseDir}/${newDictionaryRelativeLocationPath}`;\n\n const existingDictionary = dictionariesRecord[distantDictionary.key];\n\n if (existingDictionary) {\n const { filePath } = existingDictionary;\n\n // Compare existing dictionary with distant dictionary\n if (_.isEqual(existingDictionary, distantDictionary)) {\n // Up to date, nothing to do\n return 'up-to-date';\n } else {\n if (filePath) {\n const isDictionaryJSON = filePath.endsWith('.json');\n\n if (isDictionaryJSON) {\n // Write the dictionary to the same location of the existing dictionary file\n await fsPromises.writeFile(\n `${baseDir}/${filePath}`,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'updated';\n } else {\n // Write the dictionary to the intlayer-dictionaries directory\n const dictionariesDirPath = dirname(filePath);\n const dictionariesFileName = basename(filePath, extname(filePath));\n\n const newFilePath = `${dictionariesDirPath}/${dictionariesFileName}.json`;\n\n await writeFileWithDirectories(\n newFilePath,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'reimported in JSON';\n }\n } else {\n // Write the dictionary to the intlayer-dictionaries directory\n const dictionaryPath = `${newDictionaryLocationPath}/${distantDictionary.key}.content.json`;\n await writeFileWithDirectories(\n dictionaryPath,\n JSON.stringify(distantDictionary, null, 2)\n );\n return 'reimported in new location';\n }\n }\n } else {\n // No existing dictionary, write to new location\n const dictionaryPath = `${newDictionaryLocationPath}/${distantDictionary.key}.content.json`;\n\n await writeFileWithDirectories(\n dictionaryPath,\n JSON.stringify(distantDictionary, null, 2)\n );\n\n return 'imported';\n }\n};\n\nconst writeFileWithDirectories = async (\n filePath: string,\n data: string | Buffer\n): Promise<void> => {\n try {\n // Extract the directory from the file path\n const dir = dirname(filePath);\n\n // Check if the directory exists\n const directoryExists = existsSync(dir);\n\n if (!directoryExists) {\n // Create the directory recursively\n await fsPromises.mkdir(dir, { recursive: true });\n }\n\n // Write the file\n await fsPromises.writeFile(filePath, data);\n } catch (error) {\n throw new Error(`Error writing file to ${filePath}: ${error}`);\n }\n};\n"],"mappings":"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","names":["pLimit","dictionariesRecord","_"]}
package/dist/cjs/push.cjs CHANGED
@@ -44,6 +44,7 @@ const GREEN = "\x1B[32m";
44
44
  const RED = "\x1B[31m";
45
45
  const BLUE = "\x1B[34m";
46
46
  const GREY = "\x1B[90m";
47
+ const GREY_DARK = "\x1B[90m";
47
48
  const push = async (options) => {
48
49
  try {
49
50
  const { clientId, clientSecret } = (0, import_config.getConfiguration)().editor;
@@ -61,10 +62,11 @@ const push = async (options) => {
61
62
  (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)
62
63
  );
63
64
  if (noneExistingDictionariesOption.length > 0) {
64
- console.error(
65
+ (0, import_config.logger)(
65
66
  `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(
66
67
  ", "
67
- )} and have been ignored.`
68
+ )} and have been ignored.`,
69
+ { level: "error" }
68
70
  );
69
71
  }
70
72
  dictionaries = dictionaries.filter(
@@ -72,10 +74,10 @@ const push = async (options) => {
72
74
  );
73
75
  }
74
76
  if (dictionaries.length === 0) {
75
- console.error("No local dictionaries found");
77
+ (0, import_config.logger)("No local dictionaries found", { level: "error" });
76
78
  return;
77
79
  }
78
- console.info("Pushing dictionaries:");
80
+ (0, import_config.logger)("Pushing dictionaries:");
79
81
  const dictionariesStatuses = dictionaries.map(
80
82
  (dictionary, index) => ({
81
83
  dictionary,
@@ -129,7 +131,7 @@ const push = async (options) => {
129
131
  updateAllStatusLines(dictionariesStatuses);
130
132
  for (const statusObj of dictionariesStatuses) {
131
133
  if (statusObj.errorMessage) {
132
- console.error(statusObj.errorMessage);
134
+ (0, import_config.logger)(statusObj.errorMessage, { level: "error" });
133
135
  }
134
136
  }
135
137
  const deleteOption = options?.deleteLocaleDictionary;
@@ -151,7 +153,7 @@ const push = async (options) => {
151
153
  }
152
154
  }
153
155
  } catch (error) {
154
- console.error(error);
156
+ (0, import_config.logger)(error, { level: "error" });
155
157
  }
156
158
  };
157
159
  const askUser = (question) => {
@@ -172,7 +174,9 @@ const deleteLocalDictionaries = async (dictionariesToDelete) => {
172
174
  for (const dictionary of dictionariesToDelete) {
173
175
  const { filePath } = dictionary;
174
176
  if (!filePath) {
175
- console.error(`Dictionary ${dictionary.key} does not have a file path`);
177
+ (0, import_config.logger)(`Dictionary ${dictionary.key} does not have a file path`, {
178
+ level: "error"
179
+ });
176
180
  continue;
177
181
  }
178
182
  filePathsSet.add(filePath);
@@ -183,14 +187,16 @@ const deleteLocalDictionaries = async (dictionariesToDelete) => {
183
187
  const stats = await fsPromises.lstat(filePath);
184
188
  if (stats.isFile()) {
185
189
  await fsPromises.unlink(filePath);
186
- console.info(`Deleted file ${relativePath}`);
190
+ (0, import_config.logger)(`Deleted file ${relativePath}`);
187
191
  } else if (stats.isDirectory()) {
188
- console.warn(`Path is a directory ${relativePath}, skipping.`);
192
+ (0, import_config.logger)(`Path is a directory ${relativePath}, skipping.`);
189
193
  } else {
190
- console.warn(`Unknown file type for ${relativePath}, skipping.`);
194
+ (0, import_config.logger)(`Unknown file type for ${relativePath}, skipping.`);
191
195
  }
192
196
  } catch (err) {
193
- console.error(`Error deleting ${relativePath}:`, err);
197
+ (0, import_config.logger)(`Error deleting ${relativePath}: ${err}`, {
198
+ level: "error"
199
+ });
194
200
  }
195
201
  }
196
202
  };
@@ -206,6 +212,9 @@ const getStatusIcon = (status) => {
206
212
  return statusIcons[status] || "";
207
213
  };
208
214
  const getStatusLine = (statusObj) => {
215
+ const {
216
+ log: { prefix }
217
+ } = (0, import_config.getConfiguration)();
209
218
  let icon = getStatusIcon(statusObj.status);
210
219
  let colorStart = "";
211
220
  let colorEnd = "";
@@ -223,7 +232,7 @@ const getStatusLine = (statusObj) => {
223
232
  colorStart = GREY;
224
233
  colorEnd = RESET;
225
234
  }
226
- return `- ${statusObj.dictionary.key} [${colorStart}${icon} ${statusObj.status}${colorEnd}]`;
235
+ return `- ${statusObj.dictionary.key} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;
227
236
  };
228
237
  const updateAllStatusLines = (dictionariesStatuses) => {
229
238
  readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/push.ts"],"sourcesContent":["import * as fsPromises from 'fs/promises';\nimport { relative } from 'path';\nimport * as readline from 'readline';\nimport { getConfiguration } from '@intlayer/config';\nimport { Dictionary } from '@intlayer/core';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport dictionariesRecord from '@intlayer/dictionaries-entry';\nimport pLimit from 'p-limit';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status: 'pending' | 'pushing' | 'modified' | 'pushed' | 'unknown' | 'error';\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\n\n/**\n * Get all locale dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n try {\n const { clientId, clientSecret } = getConfiguration().editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n let dictionaries: Dictionary[] = Object.values(dictionariesRecord);\n const existingDictionariesKeys: string[] = Object.keys(dictionariesRecord);\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n console.error(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries!.includes(dictionary.key)\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n console.error('No local dictionaries found');\n return;\n }\n\n console.info('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary, index) => ({\n dictionary,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n })\n );\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries(\n [statusObj.dictionary],\n {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }\n );\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries || [];\n const newDictionaries = pushResult.data?.newDictionaries || [];\n\n if (updatedDictionaries.includes(statusObj.dictionary.key)) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n } else if (newDictionaries.includes(statusObj.dictionary.key)) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n const pushPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n await Promise.all(pushPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n console.error(statusObj.errorMessage);\n }\n }\n\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const answer = await askUser(\n 'Do you want to delete the local dictionaries that were successfully pushed? (yes/no): '\n );\n if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {\n await deleteLocalDictionaries(successfullyPushedDictionaries);\n }\n }\n } catch (error) {\n console.error(error);\n }\n};\n\nconst askUser = (question: string): Promise<string> => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise((resolve) => {\n rl.question(question, (answer: string) => {\n rl.close();\n resolve(answer);\n });\n });\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[]\n): Promise<void> => {\n const { baseDir } = getConfiguration().content;\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n console.error(`Dictionary ${dictionary.key} does not have a file path`);\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n const relativePath = relative(baseDir, filePath);\n\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n console.info(`Deleted file ${relativePath}`);\n } else if (stats.isDirectory()) {\n console.warn(`Path is a directory ${relativePath}, skipping.`);\n } else {\n console.warn(`Unknown file type for ${relativePath}, skipping.`);\n }\n } catch (err) {\n console.error(`Error deleting ${relativePath}:`, err);\n }\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n pushing: '', // Spinner handled separately\n modified: '✔',\n pushed: '✔',\n error: '✖',\n };\n return statusIcons[status] || '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'pushing') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (statusObj.status === 'pushed' || statusObj.status === 'modified') {\n colorStart = GREEN;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionary.key} [${colorStart}${icon} ${statusObj.status}${colorEnd}]`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'pushing') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n"],"mappings":"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","names":["dictionariesRecord","pLimit"]}
1
+ {"version":3,"sources":["../../src/push.ts"],"sourcesContent":["import * as fsPromises from 'fs/promises';\nimport { relative } from 'path';\nimport * as readline from 'readline';\nimport { getConfiguration, logger } from '@intlayer/config';\nimport { Dictionary } from '@intlayer/core';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport dictionariesRecord from '@intlayer/dictionaries-entry';\nimport pLimit from 'p-limit';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status: 'pending' | 'pushing' | 'modified' | 'pushed' | 'unknown' | 'error';\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst GREY_DARK = '\\x1b[90m';\n\n/**\n * Get all locale dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n try {\n const { clientId, clientSecret } = getConfiguration().editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n let dictionaries: Dictionary[] = Object.values(dictionariesRecord);\n const existingDictionariesKeys: string[] = Object.keys(dictionariesRecord);\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n logger(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`,\n { level: 'error' }\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries!.includes(dictionary.key)\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n logger('No local dictionaries found', { level: 'error' });\n return;\n }\n\n logger('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary, index) => ({\n dictionary,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n })\n );\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries(\n [statusObj.dictionary],\n {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }\n );\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries || [];\n const newDictionaries = pushResult.data?.newDictionaries || [];\n\n if (updatedDictionaries.includes(statusObj.dictionary.key)) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n } else if (newDictionaries.includes(statusObj.dictionary.key)) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n const pushPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n await Promise.all(pushPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n logger(statusObj.errorMessage, { level: 'error' });\n }\n }\n\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const answer = await askUser(\n 'Do you want to delete the local dictionaries that were successfully pushed? (yes/no): '\n );\n if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {\n await deleteLocalDictionaries(successfullyPushedDictionaries);\n }\n }\n } catch (error) {\n logger(error, { level: 'error' });\n }\n};\n\nconst askUser = (question: string): Promise<string> => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise((resolve) => {\n rl.question(question, (answer: string) => {\n rl.close();\n resolve(answer);\n });\n });\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[]\n): Promise<void> => {\n const { baseDir } = getConfiguration().content;\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n logger(`Dictionary ${dictionary.key} does not have a file path`, {\n level: 'error',\n });\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n const relativePath = relative(baseDir, filePath);\n\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n logger(`Deleted file ${relativePath}`);\n } else if (stats.isDirectory()) {\n logger(`Path is a directory ${relativePath}, skipping.`);\n } else {\n logger(`Unknown file type for ${relativePath}, skipping.`);\n }\n } catch (err) {\n logger(`Error deleting ${relativePath}: ${err}`, {\n level: 'error',\n });\n }\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n pushing: '', // Spinner handled separately\n modified: '✔',\n pushed: '✔',\n error: '✖',\n };\n return statusIcons[status] || '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n const {\n log: { prefix },\n } = getConfiguration();\n\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'pushing') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (statusObj.status === 'pushed' || statusObj.status === 'modified') {\n colorStart = GREEN;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionary.key} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'pushing') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n"],"mappings":"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","names":["dictionariesRecord","pLimit"]}
@@ -0,0 +1,74 @@
1
+ import { readFileSync, writeFileSync } from "fs";
2
+ import { join, relative } from "path";
3
+ import { getConfiguration, logger } from "@intlayer/config";
4
+ import { intlayerAPI } from "@intlayer/design-system/libs";
5
+ import pLimit from "p-limit";
6
+ import { getContentDeclaration } from './listContentDeclaration.mjs';
7
+ const projectPath = process.cwd();
8
+ const auditFile = async (filePath, options) => {
9
+ try {
10
+ const { defaultLocale, locales } = getConfiguration().internationalization;
11
+ const relativePath = relative(projectPath, filePath);
12
+ logger(`Auditing file: ${relativePath}`);
13
+ const fileContent = readFileSync(filePath, "utf-8");
14
+ const auditFileResult = await intlayerAPI.ai.auditFile(
15
+ {
16
+ fileContent,
17
+ filePath,
18
+ locales,
19
+ defaultLocale,
20
+ model: options?.model,
21
+ openAiApiKey: options?.openAiApiKey,
22
+ customPrompt: options?.customPrompt
23
+ },
24
+ {
25
+ headers: options?.headers
26
+ }
27
+ );
28
+ if (!auditFileResult.data) {
29
+ throw new Error("Audit failed");
30
+ }
31
+ writeFileSync(filePath, auditFileResult.data.fileContent);
32
+ logger(`File ${relativePath} updated`);
33
+ logger(`${auditFileResult.data.tokenUsed} tokens used in the request`);
34
+ } catch (error) {
35
+ logger(error, { level: "error" });
36
+ }
37
+ };
38
+ const getAbsolutePath = (filePath) => {
39
+ if (filePath.startsWith(".")) {
40
+ const absolutePath = join(process.cwd(), filePath);
41
+ return absolutePath;
42
+ }
43
+ return filePath;
44
+ };
45
+ const audit = async (options) => {
46
+ const { clientId, clientSecret } = getConfiguration().editor;
47
+ let headers = {};
48
+ if (clientId && clientSecret) {
49
+ const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();
50
+ const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
51
+ headers = { Authorization: `Bearer ${oAuth2AccessToken}` };
52
+ } else if (!options?.openAiApiKey) {
53
+ throw new Error(
54
+ "Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project. You can also provide your own OpenAI API key to audit the content declaration files. For that you need to provide the --openAiApiKey option."
55
+ );
56
+ }
57
+ let contentDeclarationFilesList = options?.files?.map(getAbsolutePath);
58
+ if (!contentDeclarationFilesList) {
59
+ const contentDeclarationFilesPath = getContentDeclaration({
60
+ exclude: options?.excludedGlobs
61
+ });
62
+ contentDeclarationFilesList = contentDeclarationFilesPath;
63
+ }
64
+ const limit = pLimit(options?.asyncLimit ? Number(options?.asyncLimit) : 5);
65
+ const pushPromises = contentDeclarationFilesList.map(
66
+ (filePath) => limit(() => auditFile(filePath, { ...options, headers }))
67
+ );
68
+ await Promise.all(pushPromises);
69
+ };
70
+ export {
71
+ audit,
72
+ auditFile
73
+ };
74
+ //# sourceMappingURL=audit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/audit.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'fs';\nimport { join, relative } from 'path';\nimport { getConfiguration, logger } from '@intlayer/config';\nimport { intlayerAPI } from '@intlayer/design-system/libs';\nimport pLimit from 'p-limit';\nimport { getContentDeclaration } from './listContentDeclaration';\n\n// Depending on your implementation, you may need the OpenAI API client.\n// For instance, you can use `openai` npm package (https://www.npmjs.com/package/openai).\n\ntype AuditOptions = {\n files?: string[];\n model?: string;\n customPrompt?: string;\n asyncLimit?: number;\n openAiApiKey?: string;\n excludedGlobs?: string[];\n headers?: Record<string, string>;\n};\n\nconst projectPath = process.cwd();\n\n/**\n * Audits a content declaration file by constructing a prompt for ChatGPT.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies. It prints the prompt for each file,\n * and could be adapted to send requests to the ChatGPT model.\n *\n * @async\n * @function\n * @param filePath - The relative or absolute path to the target file.\n * @param options - Optional configuration for the audit process.\n * @returns This function returns a Promise that resolves once the audit is complete.\n */\nexport const auditFile = async (filePath: string, options?: AuditOptions) => {\n try {\n const { defaultLocale, locales } = getConfiguration().internationalization;\n\n const relativePath = relative(projectPath, filePath);\n logger(`Auditing file: ${relativePath}`);\n\n // Read the file's content.\n const fileContent = readFileSync(filePath, 'utf-8');\n\n // Example of how you might request a completion from ChatGPT:\n const auditFileResult = await intlayerAPI.ai.auditFile(\n {\n fileContent,\n filePath,\n locales,\n defaultLocale,\n model: options?.model,\n openAiApiKey: options?.openAiApiKey,\n customPrompt: options?.customPrompt,\n },\n {\n headers: options?.headers,\n }\n );\n\n if (!auditFileResult.data) {\n throw new Error('Audit failed');\n }\n\n writeFileSync(filePath, auditFileResult.data.fileContent);\n\n logger(`File ${relativePath} updated`);\n\n logger(`${auditFileResult.data.tokenUsed} tokens used in the request`);\n } catch (error) {\n logger(error, { level: 'error' });\n }\n};\n\nconst getAbsolutePath = (filePath: string): string => {\n if (filePath.startsWith('.')) {\n const absolutePath = join(process.cwd(), filePath);\n\n return absolutePath;\n }\n return filePath;\n};\n\n/**\n * Audits the content declaration files by constructing a prompt for ChatGPT.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies. It prints the prompt for each file,\n * and could be adapted to send requests to the ChatGPT model.\n *\n * @async\n * @function\n * @param options - Optional configuration for the audit process.\n * @returns This function returns a Promise that resolves once the audit is complete.\n */\nexport const audit = async (options: AuditOptions) => {\n const { clientId, clientSecret } = getConfiguration().editor;\n let headers: Record<string, string> = {};\n\n if (clientId && clientSecret) {\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n headers = { Authorization: `Bearer ${oAuth2AccessToken}` };\n } else if (!options?.openAiApiKey) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project. You can also provide your own OpenAI API key to audit the content declaration files. For that you need to provide the --openAiApiKey option.'\n );\n }\n\n let contentDeclarationFilesList = options?.files?.map(getAbsolutePath);\n\n if (!contentDeclarationFilesList) {\n // Retrieve all content declaration file paths using a helper function.\n const contentDeclarationFilesPath = getContentDeclaration({\n exclude: options?.excludedGlobs,\n });\n\n contentDeclarationFilesList = contentDeclarationFilesPath;\n }\n\n const limit = pLimit(options?.asyncLimit ? Number(options?.asyncLimit) : 5); // Limit the number of concurrent requests\n const pushPromises = contentDeclarationFilesList.map((filePath) =>\n limit(() => auditFile(filePath, { ...options, headers }))\n );\n await Promise.all(pushPromises);\n};\n"],"mappings":"AAAA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,MAAM,gBAAgB;AAC/B,SAAS,kBAAkB,cAAc;AACzC,SAAS,mBAAmB;AAC5B,OAAO,YAAY;AACnB,SAAS,6BAA6B;AAetC,MAAM,cAAc,QAAQ,IAAI;AAczB,MAAM,YAAY,OAAO,UAAkB,YAA2B;AAC3E,MAAI;AACF,UAAM,EAAE,eAAe,QAAQ,IAAI,iBAAiB,EAAE;AAEtD,UAAM,eAAe,SAAS,aAAa,QAAQ;AACnD,WAAO,kBAAkB,YAAY,EAAE;AAGvC,UAAM,cAAc,aAAa,UAAU,OAAO;AAGlD,UAAM,kBAAkB,MAAM,YAAY,GAAG;AAAA,MAC3C;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,SAAS;AAAA,QAChB,cAAc,SAAS;AAAA,QACvB,cAAc,SAAS;AAAA,MACzB;AAAA,MACA;AAAA,QACE,SAAS,SAAS;AAAA,MACpB;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB,MAAM;AACzB,YAAM,IAAI,MAAM,cAAc;AAAA,IAChC;AAEA,kBAAc,UAAU,gBAAgB,KAAK,WAAW;AAExD,WAAO,QAAQ,YAAY,UAAU;AAErC,WAAO,GAAG,gBAAgB,KAAK,SAAS,6BAA6B;AAAA,EACvE,SAAS,OAAO;AACd,WAAO,OAAO,EAAE,OAAO,QAAQ,CAAC;AAAA,EAClC;AACF;AAEA,MAAM,kBAAkB,CAAC,aAA6B;AACpD,MAAI,SAAS,WAAW,GAAG,GAAG;AAC5B,UAAM,eAAe,KAAK,QAAQ,IAAI,GAAG,QAAQ;AAEjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAaO,MAAM,QAAQ,OAAO,YAA0B;AACpD,QAAM,EAAE,UAAU,aAAa,IAAI,iBAAiB,EAAE;AACtD,MAAI,UAAkC,CAAC;AAEvC,MAAI,YAAY,cAAc;AAC5B,UAAM,oBAAoB,MAAM,YAAY,KAAK,qBAAqB;AAEtE,UAAM,oBAAoB,kBAAkB,MAAM;AAElD,cAAU,EAAE,eAAe,UAAU,iBAAiB,GAAG;AAAA,EAC3D,WAAW,CAAC,SAAS,cAAc;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,8BAA8B,SAAS,OAAO,IAAI,eAAe;AAErE,MAAI,CAAC,6BAA6B;AAEhC,UAAM,8BAA8B,sBAAsB;AAAA,MACxD,SAAS,SAAS;AAAA,IACpB,CAAC;AAED,kCAA8B;AAAA,EAChC;AAEA,QAAM,QAAQ,OAAO,SAAS,aAAa,OAAO,SAAS,UAAU,IAAI,CAAC;AAC1E,QAAM,eAAe,4BAA4B;AAAA,IAAI,CAAC,aACpD,MAAM,MAAM,UAAU,UAAU,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;AAAA,EAC1D;AACA,QAAM,QAAQ,IAAI,YAAY;AAChC;","names":[]}
package/dist/esm/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Command } from "commander";
2
- import { audit } from './audit/index.mjs';
2
+ import { audit } from './audit.mjs';
3
3
  import { build } from './build.mjs';
4
4
  import { getConfig } from './config.mjs';
5
5
  import { listContentDeclaration } from './listContentDeclaration.mjs';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { audit } from './audit/index';\nimport { build } from './build';\nimport { getConfig } from './config';\nimport { listContentDeclaration } from './listContentDeclaration';\nimport { pull } from './pull';\nimport { push } from './push';\n\n/**\n * Set the API for the CLI\n *\n * Example of commands:\n *\n * npm run intlayer build --watch\n * npm run intlayer push --dictionaries id1 id2 id3 --deleteLocaleDir\n */\nexport const setAPI = (): Command => {\n const program = new Command();\n\n program.version('1.0.0').description('Intlayer CLI');\n\n program\n .command('build')\n .description('Build the dictionaries')\n .option('-w, --watch', 'Watch for changes')\n .action(build);\n\n program\n .command('push')\n .description(\n 'Push all dictionaries. Create or update the pushed dictionaries'\n )\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to push')\n .option(\n '-r, --deleteLocaleDictionary',\n 'Delete the local dictionaries after pushing'\n )\n .option(\n '-k, --keepLocaleDictionary',\n 'Keep the local dictionaries after pushing'\n )\n .action(push);\n\n program\n .command('pull')\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to pull')\n .option('--newDictionariesPath [path]', 'Path to save the new dictionaries')\n .action(pull);\n\n program\n .command('get config')\n .description('Get the configuration')\n .action(getConfig);\n\n program\n .command('content list')\n .description('List the content declaration files')\n .action(listContentDeclaration);\n\n program\n .command('audit')\n .description('Audit the dictionaries')\n .option(\n '-f, --files [files...]',\n 'List of Content Declaration files to audit'\n )\n .option(\n '--exclude [excludedGlobs...]',\n 'Globs pattern to exclude from the audit'\n )\n .option('-m, --model [model]', 'Model')\n .option('-p, --custom-prompt [prompt]', 'Custom prompt')\n .option('-l, --async-limit [asyncLimit]', 'Async limit')\n .option('-k, --open-ai-api-key [openAiApiKey]', 'OpenAI API key')\n .action(audit);\n\n program.parse(process.argv);\n\n return program;\n};\n"],"mappings":"AAAA,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B,SAAS,8BAA8B;AACvC,SAAS,YAAY;AACrB,SAAS,YAAY;AAUd,MAAM,SAAS,MAAe;AACnC,QAAM,UAAU,IAAI,QAAQ;AAE5B,UAAQ,QAAQ,OAAO,EAAE,YAAY,cAAc;AAEnD,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC,OAAO,eAAe,mBAAmB,EACzC,OAAO,KAAK;AAEf,UACG,QAAQ,MAAM,EACd;AAAA,IACC;AAAA,EACF,EACC,OAAO,+BAA+B,gCAAgC,EACtE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,IAAI;AAEd,UACG,QAAQ,MAAM,EACd,OAAO,+BAA+B,gCAAgC,EACtE,OAAO,gCAAgC,mCAAmC,EAC1E,OAAO,IAAI;AAEd,UACG,QAAQ,YAAY,EACpB,YAAY,uBAAuB,EACnC,OAAO,SAAS;AAEnB,UACG,QAAQ,cAAc,EACtB,YAAY,oCAAoC,EAChD,OAAO,sBAAsB;AAEhC,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,uBAAuB,OAAO,EACrC,OAAO,gCAAgC,eAAe,EACtD,OAAO,kCAAkC,aAAa,EACtD,OAAO,wCAAwC,gBAAgB,EAC/D,OAAO,KAAK;AAEf,UAAQ,MAAM,QAAQ,IAAI;AAE1B,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { audit } from './audit';\nimport { build } from './build';\nimport { getConfig } from './config';\nimport { listContentDeclaration } from './listContentDeclaration';\nimport { pull } from './pull';\nimport { push } from './push';\n\n/**\n * Set the API for the CLI\n *\n * Example of commands:\n *\n * npm run intlayer build --watch\n * npm run intlayer push --dictionaries id1 id2 id3 --deleteLocaleDir\n */\nexport const setAPI = (): Command => {\n const program = new Command();\n\n program.version('1.0.0').description('Intlayer CLI');\n\n program\n .command('build')\n .description('Build the dictionaries')\n .option('-w, --watch', 'Watch for changes')\n .action(build);\n\n program\n .command('push')\n .description(\n 'Push all dictionaries. Create or update the pushed dictionaries'\n )\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to push')\n .option(\n '-r, --deleteLocaleDictionary',\n 'Delete the local dictionaries after pushing'\n )\n .option(\n '-k, --keepLocaleDictionary',\n 'Keep the local dictionaries after pushing'\n )\n .action(push);\n\n program\n .command('pull')\n .option('-d, --dictionaries [ids...]', 'List of dictionary IDs to pull')\n .option('--newDictionariesPath [path]', 'Path to save the new dictionaries')\n .action(pull);\n\n program\n .command('get config')\n .description('Get the configuration')\n .action(getConfig);\n\n program\n .command('content list')\n .description('List the content declaration files')\n .action(listContentDeclaration);\n\n program\n .command('audit')\n .description('Audit the dictionaries')\n .option(\n '-f, --files [files...]',\n 'List of Content Declaration files to audit'\n )\n .option(\n '--exclude [excludedGlobs...]',\n 'Globs pattern to exclude from the audit'\n )\n .option('-m, --model [model]', 'Model')\n .option('-p, --custom-prompt [prompt]', 'Custom prompt')\n .option('-l, --async-limit [asyncLimit]', 'Async limit')\n .option('-k, --open-ai-api-key [openAiApiKey]', 'OpenAI API key')\n .action(audit);\n\n program.parse(process.argv);\n\n return program;\n};\n"],"mappings":"AAAA,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B,SAAS,8BAA8B;AACvC,SAAS,YAAY;AACrB,SAAS,YAAY;AAUd,MAAM,SAAS,MAAe;AACnC,QAAM,UAAU,IAAI,QAAQ;AAE5B,UAAQ,QAAQ,OAAO,EAAE,YAAY,cAAc;AAEnD,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC,OAAO,eAAe,mBAAmB,EACzC,OAAO,KAAK;AAEf,UACG,QAAQ,MAAM,EACd;AAAA,IACC;AAAA,EACF,EACC,OAAO,+BAA+B,gCAAgC,EACtE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,IAAI;AAEd,UACG,QAAQ,MAAM,EACd,OAAO,+BAA+B,gCAAgC,EACtE,OAAO,gCAAgC,mCAAmC,EAC1E,OAAO,IAAI;AAEd,UACG,QAAQ,YAAY,EACpB,YAAY,uBAAuB,EACnC,OAAO,SAAS;AAEnB,UACG,QAAQ,cAAc,EACtB,YAAY,oCAAoC,EAChD,OAAO,sBAAsB;AAEhC,UACG,QAAQ,OAAO,EACf,YAAY,wBAAwB,EACpC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,uBAAuB,OAAO,EACrC,OAAO,gCAAgC,eAAe,EACtD,OAAO,kCAAkC,aAAa,EACtD,OAAO,wCAAwC,gBAAgB,EAC/D,OAAO,KAAK;AAEf,UAAQ,MAAM,QAAQ,IAAI;AAE1B,SAAO;AACT;","names":[]}
@@ -1,7 +1,7 @@
1
- import { getConfiguration } from "@intlayer/config";
1
+ import { getConfiguration, logger } from "@intlayer/config";
2
2
  const getConfig = (_options) => {
3
3
  const config = getConfiguration();
4
- console.info(config);
4
+ logger(config);
5
5
  };
6
6
  export {
7
7
  getConfig
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config.ts"],"sourcesContent":["import { getConfiguration } from '@intlayer/config';\n\ntype ConfigOptions = {};\n\nexport const getConfig = (_options?: ConfigOptions) => {\n const config = getConfiguration();\n\n console.info(config);\n};\n"],"mappings":"AAAA,SAAS,wBAAwB;AAI1B,MAAM,YAAY,CAAC,aAA6B;AACrD,QAAM,SAAS,iBAAiB;AAEhC,UAAQ,KAAK,MAAM;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../src/config.ts"],"sourcesContent":["import { getConfiguration, logger } from '@intlayer/config';\n\ntype ConfigOptions = {};\n\nexport const getConfig = (_options?: ConfigOptions) => {\n const config = getConfiguration();\n\n logger(config);\n};\n"],"mappings":"AAAA,SAAS,kBAAkB,cAAc;AAIlC,MAAM,YAAY,CAAC,aAA6B;AACrD,QAAM,SAAS,iBAAiB;AAEhC,SAAO,MAAM;AACf;","names":[]}