@dittowords/cli 4.0.0 → 4.1.0-alpha

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 (43) hide show
  1. package/README.md +29 -364
  2. package/bin/config.js +5 -3
  3. package/bin/config.js.map +1 -1
  4. package/bin/generate-swift-struct.js +6 -0
  5. package/bin/generate-swift-struct.js.map +1 -0
  6. package/bin/http/fetchComponentFolders.js +3 -3
  7. package/bin/http/fetchComponentFolders.js.map +1 -1
  8. package/bin/http/fetchComponents.js +13 -5
  9. package/bin/http/fetchComponents.js.map +1 -1
  10. package/bin/http/fetchVariants.js +3 -3
  11. package/bin/http/fetchVariants.js.map +1 -1
  12. package/bin/init/project.js +3 -3
  13. package/bin/init/project.js.map +1 -1
  14. package/bin/pull.js +82 -38
  15. package/bin/pull.js.map +1 -1
  16. package/bin/pull.test.js +26 -24
  17. package/bin/pull.test.js.map +1 -1
  18. package/bin/types.js +2 -2
  19. package/bin/types.js.map +1 -1
  20. package/bin/utils/determineModuleType.js +80 -0
  21. package/bin/utils/determineModuleType.js.map +1 -0
  22. package/bin/utils/generateIOSBundles.js +147 -0
  23. package/bin/utils/generateIOSBundles.js.map +1 -0
  24. package/bin/utils/generateJsDriver.js +117 -58
  25. package/bin/utils/generateJsDriver.js.map +1 -1
  26. package/bin/utils/generateJsDriverTypeFile.js +105 -0
  27. package/bin/utils/generateJsDriverTypeFile.js.map +1 -0
  28. package/bin/utils/generateSwiftDriver.js +93 -0
  29. package/bin/utils/generateSwiftDriver.js.map +1 -0
  30. package/lib/config.ts +4 -0
  31. package/lib/http/fetchComponentFolders.ts +1 -1
  32. package/lib/http/fetchComponents.ts +14 -9
  33. package/lib/http/fetchVariants.ts +1 -1
  34. package/lib/init/project.ts +1 -1
  35. package/lib/pull.test.ts +24 -22
  36. package/lib/pull.ts +106 -55
  37. package/lib/types.ts +4 -0
  38. package/lib/utils/determineModuleType.ts +57 -0
  39. package/lib/utils/generateIOSBundles.ts +122 -0
  40. package/lib/utils/generateJsDriver.ts +156 -51
  41. package/lib/utils/generateJsDriverTypeFile.ts +75 -0
  42. package/lib/utils/generateSwiftDriver.ts +48 -0
  43. package/package.json +1 -1
@@ -4,6 +4,9 @@ import consts from "../consts";
4
4
  import output from "../output";
5
5
  import { Source } from "../types";
6
6
  import { cleanFileName } from "./cleanFileName";
7
+ import { ModuleType, determineModuleType } from "./determineModuleType";
8
+ import { generateJsDriverTypeFile } from "./generateJsDriverTypeFile";
9
+ import { JSONFormat } from "../pull";
7
10
 
8
11
  // compatability with legacy method of specifying project ids
9
12
  // that is still used by the default format
@@ -20,77 +23,67 @@ const stringifySourceId = (projectId: string) =>
20
23
  * The generated file will have a unified format
21
24
  * independent of the CLI configuration used to fetch
22
25
  * data from Ditto.
26
+ *
23
27
  */
28
+ type DriverFile = Record<string, Record<string, string | object>>;
29
+ export function generateJsDriver(sources: Source[], format: JSONFormat) {
30
+ const moduleType = determineModuleType();
24
31
 
25
- // TODO: support ESM
26
- export function generateJsDriver(sources: Source[]) {
27
- const sourceIdsByName: Record<string, string> = sources.reduce(
28
- (agg, source) => {
29
- if (source.fileName) {
30
- return { ...agg, [cleanFileName(source.fileName)]: source.id };
31
- }
32
+ const fullyQualifiedSources = getFullyQualifiedJSONSources(sources);
32
33
 
33
- return agg;
34
- },
35
- {}
36
- );
34
+ const variableNameGenerator = createVariableNameGenerator();
35
+ const importStatements: string[] = [];
37
36
 
38
- const projectFileNames = fs
39
- .readdirSync(consts.TEXT_DIR)
40
- .filter(
41
- (fileName) => /\.json$/.test(fileName) && !/^components__/.test(fileName)
42
- );
37
+ const data: DriverFile = {};
38
+ const dataComponents: Record<string, string[]> = {};
43
39
 
44
- type DriverFile = Record<string, Record<string, string | object>>;
45
- const data: DriverFile = projectFileNames.reduce(
46
- (obj: Record<string, Record<string, string>>, fileName) => {
47
- const [sourceId, rest] = fileName.split("__");
48
- const [variantApiId] = rest.split(".");
40
+ fullyQualifiedSources.forEach((source) => {
41
+ let variableName: string;
42
+ if (source.type === "components") {
43
+ variableName = variableNameGenerator.generate(
44
+ source.fileName.split(".")[0]
45
+ );
46
+ } else {
47
+ const fileNameWithoutExtension = source.fileName.split(".")[0];
48
+ variableName = variableNameGenerator.generate(
49
+ fileNameWithoutExtension.split("__")[0]
50
+ );
51
+ }
49
52
 
50
- const projectId = sourceIdsByName[sourceId];
51
- const projectIdStr = stringifySourceId(projectId);
53
+ importStatements.push(
54
+ getImportStatement(source.fileName, variableName, moduleType)
55
+ );
52
56
 
53
- if (!obj[projectIdStr]) {
54
- obj[projectIdStr] = {};
55
- }
57
+ if (source.type === "project") {
58
+ const { variantApiId } = source;
59
+ const projectId = stringifySourceId(source.projectId);
60
+ data[projectId] ??= {};
61
+ data[projectId][variantApiId] = `{...${variableName}}`;
62
+ } else {
63
+ dataComponents[source.variantApiId] ??= [];
64
+ dataComponents[source.variantApiId].push(`...${variableName}`);
65
+ }
66
+ });
56
67
 
57
- obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
58
- return obj;
59
- },
60
- {}
61
- );
62
-
63
- // Create arrays of stringified "...require()" statements,
64
- // each of which corresponds to one of the component files
65
- // (which are created on a per-component-folder basis)
66
- const componentData: Record<string, string[]> = {};
67
- sources
68
- .filter((s) => s.type === "components")
69
- .forEach((componentSource) => {
70
- if (componentSource.type !== "components") return;
71
- componentData[componentSource.variant] ??= [];
72
- componentData[componentSource.variant].push(
73
- `...require('./${componentSource.fileName}')`
74
- );
75
- });
76
68
  // Convert each array of stringified "...require()" statements
77
69
  // into a unified string, and set it on the final data object
78
70
  // that will be written to the driver file
79
- Object.keys(componentData).forEach((key) => {
71
+ Object.keys(dataComponents).forEach((key) => {
80
72
  data.ditto_component_library ??= {};
81
73
 
82
74
  let str = "{";
83
- componentData[key].forEach((k, i) => {
75
+ dataComponents[key].forEach((k: any, i: any) => {
84
76
  str += k;
85
- if (i < componentData[key].length - 1) str += ", ";
77
+ if (i < dataComponents[key].length - 1) str += ", ";
86
78
  });
87
79
  str += "}";
88
80
  data.ditto_component_library[key] = str;
89
81
  });
90
82
 
91
- let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
92
- // remove quotes around require statements
93
- .replace(/"require\((.*)\)"/g, "require($1)")
83
+ let dataString = "";
84
+ dataString += importStatements.join("\n") + "\n\n";
85
+ dataString += `${getExportPrefix(moduleType)}`;
86
+ dataString += `${JSON.stringify(data, null, 2)}`
94
87
  // remove quotes around opening & closing curlies
95
88
  .replace(/"\{/g, "{")
96
89
  .replace(/\}"/g, "}");
@@ -98,5 +91,117 @@ export function generateJsDriver(sources: Source[]) {
98
91
  const filePath = path.resolve(consts.TEXT_DIR, "index.js");
99
92
  fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
100
93
 
94
+ generateJsDriverTypeFile({
95
+ format,
96
+ moduleType,
97
+ });
98
+
101
99
  return `Generated .js SDK driver at ${output.info(filePath)}`;
102
100
  }
101
+
102
+ type IFullyQualifiedJSONSource =
103
+ | {
104
+ type: "components";
105
+ variantApiId: string;
106
+ folderApiId: string;
107
+ fileName: string;
108
+ }
109
+ | {
110
+ type: "project";
111
+ projectId: string;
112
+ projectName: string;
113
+ variantApiId: string;
114
+ fileName: string;
115
+ };
116
+
117
+ /**
118
+ * Upstream source data is a mess - this function is an attempt at cleaning it up
119
+ * so that it can be used in a more straightforward way to generate the driver file.
120
+ */
121
+ function getFullyQualifiedJSONSources(
122
+ sources: Source[]
123
+ ): IFullyQualifiedJSONSource[] {
124
+ const projectIdsByCleanedFileName = new Map<string, string>();
125
+ sources.forEach((source) => {
126
+ if (!source.fileName || source.type === "components") {
127
+ return;
128
+ }
129
+ projectIdsByCleanedFileName.set(cleanFileName(source.fileName), source.id);
130
+ });
131
+
132
+ const fileNames = fs.readdirSync(consts.TEXT_DIR);
133
+ return fileNames
134
+ .filter((f) => path.extname(f) === ".json")
135
+ .map((fileName) => {
136
+ const parts = fileName.split("__");
137
+
138
+ if (parts.length === 3) {
139
+ const [, folderApiId, rest] = parts;
140
+ const [variantApiId] = rest.split(".");
141
+ return {
142
+ type: "components",
143
+ variantApiId,
144
+ folderApiId,
145
+ fileName,
146
+ };
147
+ }
148
+
149
+ if (parts.length === 2) {
150
+ const [projectName, rest] = parts;
151
+ const [variantApiId] = rest.split(".");
152
+ const key = cleanFileName(fileName.split("__")[0]);
153
+ const projectId = projectIdsByCleanedFileName.get(key) || "";
154
+ return {
155
+ type: "project",
156
+ projectId,
157
+ projectName,
158
+ variantApiId,
159
+ fileName,
160
+ };
161
+ }
162
+
163
+ throw new Error("Invalid JSON file generated: " + fileName);
164
+ });
165
+ }
166
+
167
+ function createVariableNameGenerator() {
168
+ const variableNames = new Set<string>();
169
+
170
+ return {
171
+ generate: (str: string) => {
172
+ const baseName = str.replace(/(\W|-)/g, "_");
173
+ let name = baseName;
174
+ let i = 1;
175
+ while (variableNames.has(name)) {
176
+ name = `${baseName}${i}`;
177
+ i++;
178
+ }
179
+ variableNames.add(name);
180
+ return name;
181
+ },
182
+ };
183
+ }
184
+
185
+ function getExportPrefix(moduleType: ModuleType) {
186
+ if (moduleType === "commonjs") {
187
+ return "module.exports = ";
188
+ }
189
+ if (moduleType === "module") {
190
+ return "export default ";
191
+ }
192
+ throw new Error("Unknown module type: " + moduleType);
193
+ }
194
+
195
+ function getImportStatement(
196
+ fileName: string,
197
+ variableName: string,
198
+ moduleType: ModuleType
199
+ ) {
200
+ if (moduleType === "commonjs") {
201
+ return `const ${variableName} = require('./${fileName}');`;
202
+ }
203
+ if (moduleType === "module") {
204
+ return `import ${variableName} from './${fileName}';`;
205
+ }
206
+ throw new Error("Unknown module type: " + moduleType);
207
+ }
@@ -0,0 +1,75 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ModuleType } from "./determineModuleType";
4
+ import { JSONFormat } from "../pull";
5
+ import consts from "../consts";
6
+
7
+ function getFormatString(format: JSONFormat) {
8
+ switch (format) {
9
+ case "flat":
10
+ return "IJSONFlat";
11
+ case "nested":
12
+ return "IJSONNested";
13
+ case "structured":
14
+ return "IJSONStructured";
15
+ case "icu":
16
+ return "IJSONICU";
17
+ default:
18
+ return "_JSON";
19
+ }
20
+ }
21
+
22
+ function getExportString(exportedValue: string, moduleType: ModuleType) {
23
+ if (moduleType === "commonjs") {
24
+ return `export = ${exportedValue};`;
25
+ }
26
+
27
+ return `export default ${exportedValue};`;
28
+ }
29
+
30
+ function getTypesString(options: IOptions) {
31
+ return `
32
+ interface IJSONFlat {
33
+ [key: string]: string;
34
+ }
35
+
36
+ interface IJSONStructured {
37
+ [key: string]: {
38
+ text: string;
39
+ status?: string;
40
+ notes?: string;
41
+ [property: string]: any;
42
+ };
43
+ }
44
+
45
+ interface IJSONNested {
46
+ [key: string]: string | IJSONNested;
47
+ }
48
+
49
+ type _JSON = IJSONFlat | IJSONStructured | IJSONNested;
50
+
51
+ interface IDriverFile {
52
+ [sourceKey: string]: {
53
+ [variantKey: string]: ${getFormatString(options.format)};
54
+ };
55
+ }
56
+
57
+ declare const driver: IDriverFile;
58
+
59
+ ${getExportString("driver", options.moduleType)}
60
+ `.trim();
61
+ }
62
+
63
+ interface IOptions {
64
+ format: JSONFormat;
65
+ moduleType: ModuleType;
66
+ }
67
+
68
+ export function generateJsDriverTypeFile(options: IOptions) {
69
+ const typeFileString = getTypesString(options);
70
+ fs.writeFileSync(
71
+ path.resolve(consts.TEXT_DIR, "index.d.ts"),
72
+ typeFileString + "\n",
73
+ "utf8"
74
+ );
75
+ }
@@ -0,0 +1,48 @@
1
+ import path from "path";
2
+ import { writeFile } from "../pull";
3
+ import { SourceInformation } from "../types";
4
+ import { createApiClient } from "../api";
5
+ import consts from "../consts";
6
+ import output from "../output";
7
+
8
+ interface IArg {
9
+ variants: boolean;
10
+ components?:
11
+ | boolean
12
+ | {
13
+ root?: boolean | { status?: string };
14
+ folders?: string[] | { id: string | null; status?: string }[];
15
+ };
16
+ projects?: string[] | { id: string; status?: string }[];
17
+ localeByVariantId?: Record<string, string>;
18
+ }
19
+
20
+ export async function generateSwiftDriver(source: SourceInformation) {
21
+ const client = createApiClient();
22
+
23
+ const body: IArg = {
24
+ variants: source.variants,
25
+ localeByVariantId: source.localeByVariantApiId,
26
+ };
27
+
28
+ if (source.componentFolders || source.componentRoot) {
29
+ body.components = {};
30
+ if (source.componentFolders) {
31
+ body.components.folders = source.componentFolders;
32
+ }
33
+ if (source.componentRoot) {
34
+ body.components.root = source.componentRoot;
35
+ }
36
+ } else if (source.shouldFetchComponentLibrary) {
37
+ body.components = true;
38
+ }
39
+
40
+ if (source.validProjects) body.projects = source.validProjects;
41
+
42
+ const { data } = await client.post<string>("/v1/ios/swift-driver", body);
43
+
44
+ const filePath = path.join(consts.TEXT_DIR, "Ditto.swift");
45
+ await writeFile(filePath, data);
46
+
47
+ return `Successfully saved Swift driver to ${output.info("Ditto.swift")}`;
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dittowords/cli",
3
- "version": "4.0.0",
3
+ "version": "4.1.0-alpha",
4
4
  "description": "Command Line Interface for Ditto (dittowords.com).",
5
5
  "license": "MIT",
6
6
  "main": "bin/index.js",