@dittowords/cli 2.5.1 → 2.6.0

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 (73) hide show
  1. package/.idea/workspace.xml +124 -0
  2. package/README.md +23 -10
  3. package/babel.config.js +2 -8
  4. package/bin/add-project.js +38 -0
  5. package/bin/add-project.js.map +1 -0
  6. package/bin/api.js +20 -0
  7. package/bin/api.js.map +1 -0
  8. package/bin/config.js +174 -0
  9. package/bin/config.js.map +1 -0
  10. package/bin/consts.js +21 -0
  11. package/bin/consts.js.map +1 -0
  12. package/bin/ditto.js +71 -74
  13. package/bin/ditto.js.map +1 -0
  14. package/bin/init/init.js +39 -0
  15. package/bin/init/init.js.map +1 -0
  16. package/bin/init/project.js +115 -0
  17. package/bin/init/project.js.map +1 -0
  18. package/bin/init/token.js +89 -0
  19. package/bin/init/token.js.map +1 -0
  20. package/bin/output.js +34 -0
  21. package/bin/output.js.map +1 -0
  22. package/bin/pull.js +305 -0
  23. package/bin/pull.js.map +1 -0
  24. package/bin/remove-project.js +40 -0
  25. package/bin/remove-project.js.map +1 -0
  26. package/bin/types.js +3 -0
  27. package/bin/types.js.map +1 -0
  28. package/bin/utils/getSelectedProjects.js +76 -0
  29. package/bin/utils/getSelectedProjects.js.map +1 -0
  30. package/bin/utils/processMetaOption.js +15 -0
  31. package/bin/utils/processMetaOption.js.map +1 -0
  32. package/bin/utils/projectsToText.js +16 -0
  33. package/bin/utils/projectsToText.js.map +1 -0
  34. package/bin/utils/promptForProject.js +44 -0
  35. package/bin/utils/promptForProject.js.map +1 -0
  36. package/bin/utils/sourcesToText.js +25 -0
  37. package/bin/utils/sourcesToText.js.map +1 -0
  38. package/jest.config.js +1 -1
  39. package/lib/{add-project.js → add-project.ts} +8 -8
  40. package/lib/api.ts +15 -0
  41. package/lib/{config.test.js → config.test.ts} +14 -25
  42. package/lib/config.ts +214 -0
  43. package/lib/consts.ts +20 -0
  44. package/lib/ditto.ts +97 -0
  45. package/lib/init/{init.js → init.ts} +11 -12
  46. package/lib/init/project.test.ts +49 -0
  47. package/lib/init/{project.js → project.ts} +31 -29
  48. package/lib/init/{token.test.js → token.test.ts} +3 -3
  49. package/lib/init/{token.js → token.ts} +23 -19
  50. package/lib/output.ts +21 -0
  51. package/lib/pull.test.ts +172 -0
  52. package/lib/{pull.js → pull.ts} +145 -98
  53. package/lib/{remove-project.js → remove-project.ts} +11 -15
  54. package/lib/types.ts +23 -0
  55. package/lib/utils/getSelectedProjects.ts +55 -0
  56. package/lib/utils/processMetaOption.test.ts +18 -0
  57. package/lib/utils/processMetaOption.ts +16 -0
  58. package/lib/utils/{projectsToText.js → projectsToText.ts} +5 -4
  59. package/lib/utils/{promptForProject.js → promptForProject.ts} +18 -9
  60. package/lib/utils/{sourcesToText.js → sourcesToText.ts} +6 -5
  61. package/package.json +20 -15
  62. package/testing/fixtures/project-config-pull.yml +0 -0
  63. package/tsconfig.json +12 -0
  64. package/yarn-error.log +4466 -0
  65. package/lib/api.js +0 -18
  66. package/lib/config.js +0 -169
  67. package/lib/consts.js +0 -14
  68. package/lib/init/project.test.js +0 -99
  69. package/lib/output.js +0 -21
  70. package/lib/pull.test.js +0 -173
  71. package/lib/utils/getSelectedProjects.js +0 -44
  72. package/lib/utils/processMetaOption.js +0 -16
  73. package/lib/utils/processMetaOption.test.js +0 -16
@@ -1,38 +1,38 @@
1
- const fs = require("fs");
1
+ import fs from "fs";
2
2
 
3
- const chalk = require("chalk");
3
+ import chalk from "chalk";
4
4
 
5
- const { prompt } = require("enquirer");
5
+ import { prompt } from "enquirer";
6
6
 
7
- const api = require("../api");
8
- const consts = require("../consts");
9
- const output = require("../output");
10
- const config = require("../config");
7
+ import { create } from "../api";
8
+ import consts from "../consts";
9
+ import output from "../output";
10
+ import config from "../config";
11
11
 
12
- function needsToken(configFile, host = consts.API_HOST) {
12
+ export const needsToken = (configFile?: string, host = consts.API_HOST) => {
13
13
  if (config.getTokenFromEnv()) {
14
14
  return false;
15
15
  }
16
16
 
17
17
  const file = configFile || consts.CONFIG_FILE;
18
18
  if (!fs.existsSync(file)) return true;
19
- const configData = config.readData(file);
19
+ const configData = config.readGlobalConfigData(file);
20
20
  if (
21
21
  !configData[config.justTheHost(host)] ||
22
22
  configData[config.justTheHost(host)][0].token === ""
23
23
  )
24
24
  return true;
25
25
  return false;
26
- }
26
+ };
27
27
 
28
28
  // Returns true if valid, otherwise an error message.
29
- async function checkToken(token) {
30
- const axios = api.create(token);
29
+ async function checkToken(token: string): Promise<any> {
30
+ const axios = create(token);
31
31
  const endpoint = "/token-check";
32
32
 
33
33
  const resOrError = await axios
34
34
  .get(endpoint)
35
- .catch((error) => {
35
+ .catch((error: any) => {
36
36
  if (error.code === "ENOTFOUND") {
37
37
  return output.errorText(
38
38
  `Can't connect to API: ${output.url(error.hostname)}`
@@ -48,7 +48,6 @@ async function checkToken(token) {
48
48
  .catch(() =>
49
49
  output.errorText("Sorry! We're having trouble reaching the Ditto API.")
50
50
  );
51
-
52
51
  if (typeof resOrError === "string") return resOrError;
53
52
 
54
53
  if (resOrError.status === 200) return true;
@@ -56,7 +55,7 @@ async function checkToken(token) {
56
55
  return output.errorText("This API key isn't valid. Please try another.");
57
56
  }
58
57
 
59
- async function collectToken(message) {
58
+ async function collectToken(message: string | null) {
60
59
  const blue = output.info;
61
60
  const apiUrl = output.url("https://app.dittowords.com/account/user");
62
61
  const breadcrumbs = `${blue("User")}`;
@@ -67,7 +66,7 @@ async function collectToken(message) {
67
66
  )}".`;
68
67
  console.log(tokenDescription);
69
68
 
70
- const response = await prompt({
69
+ const response = await prompt<{ token: string }>({
71
70
  type: "input",
72
71
  name: "token",
73
72
  message: "What is your API key?",
@@ -82,7 +81,12 @@ function quit(exitCode = 2) {
82
81
  process.exit();
83
82
  }
84
83
 
85
- async function collectAndSaveToken(message = null) {
84
+ /**
85
+ *
86
+ * @param {string | null} message
87
+ * @returns
88
+ */
89
+ export const collectAndSaveToken = async (message: string | null = null) => {
86
90
  try {
87
91
  const token = await collectToken(message);
88
92
  console.log(
@@ -97,6 +101,6 @@ async function collectAndSaveToken(message = null) {
97
101
  } catch (error) {
98
102
  quit();
99
103
  }
100
- }
104
+ };
101
105
 
102
- module.exports = { needsToken, collectAndSaveToken };
106
+ export default { needsToken, collectAndSaveToken };
package/lib/output.ts ADDED
@@ -0,0 +1,21 @@
1
+ import chalk from "chalk";
2
+
3
+ export const errorText = (msg: string) => chalk.magenta(msg);
4
+ export const warnText = (msg: string) => chalk.yellow(msg);
5
+ export const info = (msg: string) => chalk.blueBright(msg);
6
+ export const success = (msg: string) => chalk.green(msg);
7
+ export const url = (msg: string) => chalk.blueBright.underline(msg);
8
+ export const subtle = (msg: string) => chalk.grey(msg);
9
+ export const write = (msg: string) => chalk.white(msg);
10
+ export const nl = () => console.log("\n");
11
+
12
+ export default {
13
+ errorText,
14
+ warnText,
15
+ info,
16
+ success,
17
+ url,
18
+ subtle,
19
+ write,
20
+ nl,
21
+ };
@@ -0,0 +1,172 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import api from "./api";
4
+ import config from "./config";
5
+
6
+ const testProjects = [
7
+ {
8
+ id: "1",
9
+ name: "Project 1",
10
+ fileName: "Project 1",
11
+ },
12
+ { id: "2", name: "Project 2", fileName: "Project 2" },
13
+ ];
14
+
15
+ jest.mock("./api");
16
+
17
+ const mockApi = api as jest.Mocked<typeof api>;
18
+
19
+ jest.mock("./consts", () => ({
20
+ TEXT_DIR: ".testing",
21
+ API_HOST: "https://api.dittowords.com",
22
+ CONFIG_FILE: ".testing/ditto",
23
+ PROJECT_CONFIG_FILE: ".testing/config.yml",
24
+ TEXT_FILE: ".testing/text.json",
25
+ }));
26
+
27
+ import consts from "./consts";
28
+ import allPull from "./pull";
29
+
30
+ const {
31
+ _testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
32
+ } = allPull;
33
+ const variant = "english";
34
+
35
+ const cleanOutputDir = () => {
36
+ if (fs.existsSync(consts.TEXT_DIR))
37
+ fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
38
+
39
+ fs.mkdirSync(consts.TEXT_DIR);
40
+ };
41
+
42
+ afterAll(() => {
43
+ fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
44
+ });
45
+
46
+ describe("cleanOutputFiles", () => {
47
+ it("removes .js, .json, .xml, and .strings files", () => {
48
+ cleanOutputDir();
49
+
50
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.json"), "test");
51
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.js"), "test");
52
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.xml"), "test");
53
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.strings"), "test");
54
+ // this file shouldn't be deleted
55
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.txt"), "test");
56
+
57
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(5);
58
+
59
+ cleanOutputFiles();
60
+
61
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
62
+ });
63
+ });
64
+
65
+ // describe("downloadAndSaveVariant", () => {
66
+ // beforeAll(() => {
67
+ // if (!fs.existsSync(consts.TEXT_DIR)) {
68
+ // fs.mkdirSync(consts.TEXT_DIR);
69
+ // }
70
+ // });
71
+
72
+ // it("writes a single file for default format", async () => {
73
+ // cleanOutputDir();
74
+
75
+ // const output = await downloadAndSaveVariant(variant, testProjects, "");
76
+ // expect(/saved to.*english\.json/.test(output)).toEqual(true);
77
+ // expect(output.match(/saved to/g)?.length).toEqual(1);
78
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
79
+ // });
80
+
81
+ // it("writes multiple files for flat format", async () => {
82
+ // cleanOutputDir();
83
+
84
+ // const output = await downloadAndSaveVariant(variant, testProjects, "flat");
85
+
86
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
87
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
88
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
89
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
90
+ // });
91
+
92
+ // it("writes multiple files for structured format", async () => {
93
+ // cleanOutputDir();
94
+
95
+ // const output = await downloadAndSaveVariant(
96
+ // variant,
97
+ // testProjects,
98
+ // "structured"
99
+ // );
100
+
101
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
102
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
103
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
104
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
105
+ // });
106
+ // });
107
+
108
+ describe("downloadAndSaveBase", () => {
109
+ beforeAll(() => {
110
+ if (!fs.existsSync(consts.TEXT_DIR)) {
111
+ fs.mkdirSync(consts.TEXT_DIR);
112
+ }
113
+ });
114
+
115
+ it("writes to text.json for default format", async () => {
116
+ cleanOutputDir();
117
+
118
+ mockApi.get.mockResolvedValueOnce({ data: [] });
119
+ const output = await downloadAndSaveBase(testProjects, "");
120
+
121
+ expect(/saved to.*text\.json/.test(output)).toEqual(true);
122
+ expect(output.match(/saved to/g)?.length).toEqual(1);
123
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
124
+ });
125
+
126
+ it("writes multiple files for flat format", async () => {
127
+ cleanOutputDir();
128
+
129
+ mockApi.get.mockResolvedValue({ data: [] });
130
+ const output = await downloadAndSaveBase(testProjects, "flat");
131
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
132
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
133
+ expect(output.match(/saved to/g)?.length).toEqual(2);
134
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
135
+ });
136
+
137
+ it("writes multiple files for structured format", async () => {
138
+ cleanOutputDir();
139
+
140
+ mockApi.get.mockResolvedValue({ data: [] });
141
+ const output = await downloadAndSaveBase(testProjects, "structured");
142
+
143
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
144
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
145
+ expect(output.match(/saved to/g)?.length).toEqual(2);
146
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
147
+ });
148
+
149
+ it("writes .xml files for android", async () => {
150
+ cleanOutputDir();
151
+
152
+ mockApi.get.mockResolvedValue({ data: "hello" });
153
+ const output = await downloadAndSaveBase(testProjects, "android");
154
+
155
+ expect(/saved to.*Project 1\.xml/.test(output)).toEqual(true);
156
+ expect(/saved to.*Project 2\.xml/.test(output)).toEqual(true);
157
+ expect(output.match(/saved to/g)?.length).toEqual(2);
158
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
159
+ });
160
+
161
+ it("writes .strings files for ios-strings", async () => {
162
+ cleanOutputDir();
163
+
164
+ mockApi.get.mockResolvedValue({ data: "hello" });
165
+ const output = await downloadAndSaveBase(testProjects, "ios-strings");
166
+
167
+ expect(/saved to.*Project 1\.strings/.test(output)).toEqual(true);
168
+ expect(/saved to.*Project 2\.strings/.test(output)).toEqual(true);
169
+ expect(output.match(/saved to/g)?.length).toEqual(2);
170
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
171
+ });
172
+ });
@@ -1,20 +1,20 @@
1
- const fs = require("fs");
2
- const path = require("path");
1
+ import fs from "fs";
2
+ import path from "path";
3
3
 
4
- const ora = require("ora");
4
+ import ora from "ora";
5
5
 
6
- const api = require("./api").default;
7
- const config = require("./config");
8
- const consts = require("./consts");
9
- const output = require("./output");
10
- const { collectAndSaveToken } = require("./init/token");
11
- const projectsToText = require("./utils/projectsToText");
12
- const sourcesToText = require("./utils/sourcesToText");
6
+ import api from "./api";
7
+ import config from "./config";
8
+ import consts from "./consts";
9
+ import output from "./output";
10
+ import { collectAndSaveToken } from "./init/token";
11
+ import sourcesToText from "./utils/sourcesToText";
12
+ import { SourceInformation, Token, Project } from "./types";
13
13
 
14
- const NON_DEFAULT_FORMATS = ["flat", "structured"];
14
+ const NON_DEFAULT_FORMATS = ["flat", "structured", "android", "ios-strings"];
15
15
 
16
16
  const DEFAULT_FORMAT_KEYS = ["projects", "exported_at"];
17
- const hasVariantData = (data) => {
17
+ const hasVariantData = (data: any) => {
18
18
  const hasTopLevelKeys =
19
19
  Object.keys(data).filter((key) => !DEFAULT_FORMAT_KEYS.includes(key))
20
20
  .length > 0;
@@ -31,6 +31,16 @@ async function askForAnotherToken() {
31
31
  await collectAndSaveToken(message);
32
32
  }
33
33
 
34
+ function getExtension(format: string) {
35
+ if (format === "android") {
36
+ return ".xml";
37
+ }
38
+ if (format === "ios-strings") {
39
+ return ".strings";
40
+ }
41
+ return ".json";
42
+ }
43
+
34
44
  /**
35
45
  * For a given variant:
36
46
  * - if format is unspecified, fetch data for all projects from `/projects` and
@@ -38,15 +48,22 @@ async function askForAnotherToken() {
38
48
  * - if format is `flat` or `structured`, fetch data for each project from `/project/:project_id` and
39
49
  * save in `{projectName}-${variantApiId}.json`
40
50
  */
41
- async function downloadAndSaveVariant(variantApiId, projects, format, token) {
42
- const params = { variant: variantApiId };
51
+ async function downloadAndSaveVariant(
52
+ variantApiId: string | null,
53
+ projects: Project[],
54
+ format: string | undefined,
55
+ token?: Token
56
+ ) {
57
+ const params: Record<string, string | null> = {
58
+ variant: variantApiId,
59
+ };
43
60
  if (format) {
44
61
  params.format = format;
45
62
  }
46
63
 
47
- if (NON_DEFAULT_FORMATS.includes(format)) {
64
+ if (format && NON_DEFAULT_FORMATS.includes(format)) {
48
65
  const savedMessages = await Promise.all(
49
- projects.map(async ({ id, fileName }) => {
66
+ projects.map(async ({ id, fileName }: Project) => {
50
67
  const { data } = await api.get(`/projects/${id}`, {
51
68
  params,
52
69
  headers: { Authorization: `token ${token}` },
@@ -56,10 +73,16 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
56
73
  return "";
57
74
  }
58
75
 
59
- const filename = fileName + ("__" + (variantApiId || "base")) + ".json";
76
+ const extension = getExtension(format);
77
+
78
+ const filename =
79
+ fileName + ("__" + (variantApiId || "base")) + extension;
60
80
  const filepath = path.join(consts.TEXT_DIR, filename);
61
81
 
62
- const dataString = JSON.stringify(data, null, 2);
82
+ let dataString = data;
83
+ if (extension === ".json") {
84
+ dataString = JSON.stringify(data, null, 2);
85
+ }
63
86
 
64
87
  fs.writeFileSync(filepath, dataString);
65
88
 
@@ -89,10 +112,12 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
89
112
  }
90
113
  }
91
114
 
92
- /**
93
- * @param {{ meta: Object.<string, string> }} options
94
- */
95
- async function downloadAndSaveVariants(projects, format, token, options) {
115
+ async function downloadAndSaveVariants(
116
+ projects: Project[],
117
+ format: string | undefined,
118
+ token?: Token,
119
+ options?: PullOptions
120
+ ) {
96
121
  const meta = options ? options.meta : {};
97
122
 
98
123
  const { data: variants } = await api.get("/variants", {
@@ -105,7 +130,7 @@ async function downloadAndSaveVariants(projects, format, token, options) {
105
130
 
106
131
  const messages = await Promise.all([
107
132
  downloadAndSaveVariant(null, projects, format, token),
108
- ...variants.map(({ apiID }) =>
133
+ ...variants.map(({ apiID }: { apiID: string }) =>
109
134
  downloadAndSaveVariant(apiID, projects, format, token)
110
135
  ),
111
136
  ]);
@@ -113,10 +138,12 @@ async function downloadAndSaveVariants(projects, format, token, options) {
113
138
  return messages.join("");
114
139
  }
115
140
 
116
- /**
117
- @param {{ meta: Object.<string, string> }} options
118
- */
119
- async function downloadAndSaveBase(projects, format, token, options) {
141
+ async function downloadAndSaveBase(
142
+ projects: Project[],
143
+ format: string | undefined,
144
+ token?: Token,
145
+ options?: PullOptions
146
+ ) {
120
147
  const meta = options ? options.meta : {};
121
148
 
122
149
  const params = {
@@ -126,18 +153,22 @@ async function downloadAndSaveBase(projects, format, token, options) {
126
153
  params.format = format;
127
154
  }
128
155
 
129
- if (NON_DEFAULT_FORMATS.includes(format)) {
156
+ if (format && NON_DEFAULT_FORMATS.includes(format)) {
130
157
  const savedMessages = await Promise.all(
131
- projects.map(async ({ id, fileName }) => {
158
+ projects.map(async ({ id, fileName }: Project) => {
132
159
  const { data } = await api.get(`/projects/${id}`, {
133
160
  params,
134
161
  headers: { Authorization: `token ${token}` },
135
162
  });
136
163
 
137
- const filename = `${fileName}.json`;
164
+ const extension = getExtension(format);
165
+ const filename = `${fileName}${extension}`;
138
166
  const filepath = path.join(consts.TEXT_DIR, filename);
139
167
 
140
- const dataString = JSON.stringify(data, null, 2);
168
+ let dataString = data;
169
+ if (extension === ".json") {
170
+ dataString = JSON.stringify(data, null, 2);
171
+ }
141
172
 
142
173
  fs.writeFileSync(filepath, dataString);
143
174
 
@@ -160,7 +191,7 @@ async function downloadAndSaveBase(projects, format, token, options) {
160
191
  }
161
192
  }
162
193
 
163
- function getSavedMessage(file) {
194
+ function getSavedMessage(file: string) {
164
195
  return `Successfully saved to ${output.info(file)}\n`;
165
196
  }
166
197
 
@@ -171,7 +202,7 @@ function cleanOutputFiles() {
171
202
 
172
203
  const fileNames = fs.readdirSync(consts.TEXT_DIR);
173
204
  fileNames.forEach((fileName) => {
174
- if (/\.js(on)?$/.test(fileName)) {
205
+ if (/\.js(on)?|\.xml|\.strings$/.test(fileName)) {
175
206
  fs.unlinkSync(path.resolve(consts.TEXT_DIR, fileName));
176
207
  }
177
208
  });
@@ -181,7 +212,7 @@ function cleanOutputFiles() {
181
212
 
182
213
  // compatability with legacy method of specifying project ids
183
214
  // that is still used by the default format
184
- const stringifyProjectId = (projectId) =>
215
+ const stringifyProjectId = (projectId: string) =>
185
216
  projectId === "ditto_component_library" ? projectId : `project_${projectId}`;
186
217
 
187
218
  /**
@@ -195,74 +226,88 @@ const stringifyProjectId = (projectId) =>
195
226
  * independent of the CLI configuration used to fetch
196
227
  * data from Ditto.
197
228
  */
198
- function generateJsDriver(projects, variants, format) {
229
+ function generateJsDriver(
230
+ projects: Project[],
231
+ variants: boolean,
232
+ format: string | undefined
233
+ ) {
199
234
  const fileNames = fs
200
235
  .readdirSync(consts.TEXT_DIR)
201
236
  .filter((fileName) => /\.json$/.test(fileName));
202
237
 
203
- const projectIdsByName = projects.reduce(
204
- (agg, project) => ({ ...agg, [project.fileName]: project.id }),
238
+ const projectIdsByName: Record<string, string> = projects.reduce(
239
+ (agg, project) => {
240
+ if (project.fileName) {
241
+ return { ...agg, [project.fileName]: project.id };
242
+ }
243
+ return agg;
244
+ },
205
245
  {}
206
246
  );
207
247
 
208
- const data = fileNames.reduce((obj, fileName) => {
209
- // filename format: {project-name}__{variant-api-id}.json
210
- // file format: flat or structured
211
- if (variants && format) {
212
- const [projectName, rest] = fileName.split("__");
213
- const [variantApiId] = rest.split(".");
248
+ const data = fileNames.reduce(
249
+ (obj: Record<string, Record<string, string>>, fileName) => {
250
+ // filename format: {project-name}__{variant-api-id}.json
251
+ // file format: flat or structured
252
+ if (variants && format) {
253
+ const [projectName, rest] = fileName.split("__");
254
+ const [variantApiId] = rest.split(".");
255
+
256
+ const projectId = projectIdsByName[projectName];
257
+ if (!projectId) {
258
+ throw new Error(`Couldn't find id for ${projectName}`);
259
+ }
214
260
 
215
- const projectId = projectIdsByName[projectName];
216
- if (!projectId) {
217
- throw new Error(`Couldn't find id for ${projectName}`);
218
- }
261
+ const projectIdStr = stringifyProjectId(projectId);
219
262
 
220
- const projectIdStr = stringifyProjectId(projectId);
263
+ if (!obj[projectIdStr]) {
264
+ obj[projectIdStr] = {};
265
+ }
221
266
 
222
- if (!obj[projectIdStr]) {
223
- obj[projectIdStr] = {};
267
+ obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
224
268
  }
225
-
226
- obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
227
- }
228
- // filename format: {variant-api-id}.json
229
- // file format: default
230
- else if (variants) {
231
- const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
232
- const [variantApiId] = fileName.split(".");
233
-
234
- Object.keys(file.projects).forEach((projectId) => {
235
- if (!obj[projectId]) {
236
- obj[projectId] = {};
269
+ // filename format: {variant-api-id}.json
270
+ // file format: default
271
+ else if (variants) {
272
+ const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
273
+ const [variantApiId] = fileName.split(".");
274
+
275
+ Object.keys(file.projects).forEach((projectId) => {
276
+ if (!obj[projectId]) {
277
+ obj[projectId] = {};
278
+ }
279
+
280
+ const project = file.projects[projectId];
281
+ obj[projectId][variantApiId] = project.frames || project.components;
282
+ });
283
+ }
284
+ // filename format: {project-name}.json
285
+ // file format: flat or structured
286
+ else if (format) {
287
+ const [projectName] = fileName.split(".");
288
+ const projectId = projectIdsByName[projectName];
289
+ if (!projectId) {
290
+ throw new Error(`Couldn't find id for ${projectName}`);
237
291
  }
238
292
 
239
- const project = file.projects[projectId];
240
- obj[projectId][variantApiId] = project.frames || project.components;
241
- });
242
- }
243
- // filename format: {project-name}.json
244
- // file format: flat or structured
245
- else if (format) {
246
- const [projectName] = fileName.split(".");
247
- const projectId = projectIdsByName[projectName];
248
- if (!projectId) {
249
- throw new Error(`Couldn't find id for ${projectName}`);
293
+ obj[stringifyProjectId(projectId)] = {
294
+ base: `require('./${fileName}')`,
295
+ };
296
+ }
297
+ // filename format: text.json (single file)
298
+ // file format: default
299
+ else {
300
+ const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
301
+ Object.keys(file.projects).forEach((projectId) => {
302
+ const project = file.projects[projectId];
303
+ obj[projectId] = { base: project.frames || project.components };
304
+ });
250
305
  }
251
306
 
252
- obj[stringifyProjectId(projectId)] = { base: `require('./${fileName}')` };
253
- }
254
- // filename format: text.json (single file)
255
- // file format: default
256
- else {
257
- const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
258
- Object.keys(file.projects).forEach((projectId) => {
259
- const project = file.projects[projectId];
260
- obj[projectId] = { base: project.frames || project.components };
261
- });
262
- }
263
-
264
- return obj;
265
- }, {});
307
+ return obj;
308
+ },
309
+ {}
310
+ );
266
311
 
267
312
  let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
268
313
  // remove quotes around require statements
@@ -274,10 +319,11 @@ function generateJsDriver(projects, variants, format) {
274
319
  return `Generated .js SDK driver at ${output.info(filePath)}`;
275
320
  }
276
321
 
277
- /**
278
- * @param {{ meta: Object.<string, string> }} options
279
- */
280
- async function downloadAndSave(sourceInformation, token, options) {
322
+ async function downloadAndSave(
323
+ sourceInformation: SourceInformation,
324
+ token?: Token,
325
+ options?: PullOptions
326
+ ) {
281
327
  const { validProjects, variants, format, shouldFetchComponentLibrary } =
282
328
  sourceInformation;
283
329
 
@@ -314,7 +360,7 @@ async function downloadAndSave(sourceInformation, token, options) {
314
360
 
315
361
  spinner.stop();
316
362
  return console.log(msg);
317
- } catch (e) {
363
+ } catch (e: any) {
318
364
  spinner.stop();
319
365
  let error = e.message;
320
366
  if (e.response && e.response.status === 404) {
@@ -351,18 +397,19 @@ async function downloadAndSave(sourceInformation, token, options) {
351
397
  }
352
398
  }
353
399
 
354
- /**
355
- * @param {{ meta: Object.<string, string> }} options
356
- */
357
- function pull(options) {
400
+ interface PullOptions {
401
+ meta?: Record<string, string>;
402
+ }
403
+
404
+ export const pull = (options?: PullOptions) => {
358
405
  const meta = options ? options.meta : {};
359
406
  const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
360
407
  const sourceInformation = config.parseSourceInformation();
361
408
 
362
409
  return downloadAndSave(sourceInformation, token, { meta });
363
- }
410
+ };
364
411
 
365
- module.exports = {
412
+ export default {
366
413
  pull,
367
414
  _testing: {
368
415
  cleanOutputFiles,