@dittowords/cli 3.6.0 → 3.7.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 (72) hide show
  1. package/.idea/cli.iml +13 -0
  2. package/.idea/codeStyles/Project.xml +65 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.idea/workspace.xml +101 -0
  8. package/bin/ditto.js +10 -2
  9. package/bin/ditto.js.map +1 -1
  10. package/bin/generate-suggestions.js +53 -41
  11. package/bin/generate-suggestions.js.map +1 -1
  12. package/bin/http/fetchComponents.js +15 -3
  13. package/bin/http/fetchComponents.js.map +1 -1
  14. package/bin/pull.js +4 -1
  15. package/bin/pull.js.map +1 -1
  16. package/lib/ditto.ts +10 -3
  17. package/lib/generate-suggestions.test.ts +109 -18
  18. package/lib/generate-suggestions.ts +85 -64
  19. package/lib/http/fetchComponents.ts +20 -10
  20. package/package.json +1 -3
  21. package/testfiles/test1.jsx +5 -5
  22. package/testfiles/test2.jsx +1 -1
  23. package/.eslintrc.js +0 -17
  24. package/bin/lib/add-project.js +0 -36
  25. package/bin/lib/add-project.js.map +0 -1
  26. package/bin/lib/api.js +0 -20
  27. package/bin/lib/api.js.map +0 -1
  28. package/bin/lib/config.js +0 -202
  29. package/bin/lib/config.js.map +0 -1
  30. package/bin/lib/consts.js +0 -21
  31. package/bin/lib/consts.js.map +0 -1
  32. package/bin/lib/ditto.js +0 -121
  33. package/bin/lib/ditto.js.map +0 -1
  34. package/bin/lib/generate-suggestions.js +0 -71
  35. package/bin/lib/generate-suggestions.js.map +0 -1
  36. package/bin/lib/http/fetchComponents.js +0 -13
  37. package/bin/lib/http/fetchComponents.js.map +0 -1
  38. package/bin/lib/http/fetchVariants.js +0 -26
  39. package/bin/lib/http/fetchVariants.js.map +0 -1
  40. package/bin/lib/init/init.js +0 -50
  41. package/bin/lib/init/init.js.map +0 -1
  42. package/bin/lib/init/project.js +0 -108
  43. package/bin/lib/init/project.js.map +0 -1
  44. package/bin/lib/init/token.js +0 -91
  45. package/bin/lib/init/token.js.map +0 -1
  46. package/bin/lib/output.js +0 -34
  47. package/bin/lib/output.js.map +0 -1
  48. package/bin/lib/pull.js +0 -264
  49. package/bin/lib/pull.js.map +0 -1
  50. package/bin/lib/remove-project.js +0 -35
  51. package/bin/lib/remove-project.js.map +0 -1
  52. package/bin/lib/replace.js +0 -107
  53. package/bin/lib/replace.js.map +0 -1
  54. package/bin/lib/types.js +0 -3
  55. package/bin/lib/types.js.map +0 -1
  56. package/bin/lib/utils/cleanFileName.js +0 -11
  57. package/bin/lib/utils/cleanFileName.js.map +0 -1
  58. package/bin/lib/utils/generateJsDriver.js +0 -56
  59. package/bin/lib/utils/generateJsDriver.js.map +0 -1
  60. package/bin/lib/utils/getSelectedProjects.js +0 -61
  61. package/bin/lib/utils/getSelectedProjects.js.map +0 -1
  62. package/bin/lib/utils/processMetaOption.js +0 -15
  63. package/bin/lib/utils/processMetaOption.js.map +0 -1
  64. package/bin/lib/utils/projectsToText.js +0 -25
  65. package/bin/lib/utils/projectsToText.js.map +0 -1
  66. package/bin/lib/utils/promptForProject.js +0 -43
  67. package/bin/lib/utils/promptForProject.js.map +0 -1
  68. package/bin/lib/utils/quit.js +0 -10
  69. package/bin/lib/utils/quit.js.map +0 -1
  70. package/bin/lib/utils/sourcesToText.js +0 -25
  71. package/bin/lib/utils/sourcesToText.js.map +0 -1
  72. package/bin/package.json +0 -76
@@ -1,6 +1,6 @@
1
1
  import fs from "fs/promises";
2
2
  import path from "path";
3
- import { findTextInJSXFiles } from "./generate-suggestions";
3
+ import { findComponentsInJSXFiles } from "./generate-suggestions";
4
4
 
5
5
  describe("findTextInJSXFiles", () => {
6
6
  async function createTempFile(filename, content) {
@@ -15,9 +15,10 @@ describe("findTextInJSXFiles", () => {
15
15
  }
16
16
 
17
17
  it("should return an empty obj when no files are found", async () => {
18
- const result = await findTextInJSXFiles(".", {
19
- text: "searchString",
20
- } as any);
18
+ const result = await findComponentsInJSXFiles({
19
+ directory: ".",
20
+ components: {},
21
+ });
21
22
 
22
23
  expect(result).toEqual({});
23
24
  });
@@ -26,9 +27,17 @@ describe("findTextInJSXFiles", () => {
26
27
  const file1 = await createTempFile("file1.jsx", "<div>No match</div>");
27
28
  const file2 = await createTempFile("file2.tsx", "<div>No match</div>");
28
29
 
29
- const result = await findTextInJSXFiles(".", {
30
- text: "searchString",
31
- } as any);
30
+ const result = await findComponentsInJSXFiles({
31
+ directory: ".",
32
+ components: {
33
+ acomponent: {
34
+ name: "A Component",
35
+ text: "A Component",
36
+ status: "NONE",
37
+ folder: null,
38
+ },
39
+ },
40
+ });
32
41
 
33
42
  expect(result).toEqual({});
34
43
 
@@ -43,24 +52,106 @@ describe("findTextInJSXFiles", () => {
43
52
  );
44
53
 
45
54
  const expectedResult = {
46
- [file1]: [
47
- {
48
- lineNumber: 1,
49
- preview: "<div>Test searchString and another searchString</div>",
55
+ "search-string": {
56
+ apiId: "search-string",
57
+ folder: null,
58
+ name: "Search String",
59
+ occurrences: {
60
+ [file1]: [
61
+ {
62
+ lineNumber: 1,
63
+ preview: "<div>Test searchString and another searchString</div>",
64
+ },
65
+ {
66
+ lineNumber: 1,
67
+ preview: "<div>Test searchString and another searchString</div>",
68
+ },
69
+ ],
50
70
  },
51
- {
52
- lineNumber: 1,
53
- preview: "<div>Test searchString and another searchString</div>",
71
+ status: "NONE",
72
+ text: "searchString",
73
+ },
74
+ };
75
+
76
+ const result = await findComponentsInJSXFiles({
77
+ directory: ".",
78
+ components: {
79
+ "search-string": {
80
+ name: "Search String",
81
+ text: "searchString",
82
+ status: "NONE",
83
+ folder: null,
84
+ },
85
+ },
86
+ });
87
+
88
+ expect(result).toEqual(expectedResult);
89
+
90
+ await deleteTempFile(file1);
91
+ });
92
+
93
+ it("-f flag works", async () => {
94
+ const file1 = await createTempFile(
95
+ "file1.jsx",
96
+ `<div>Test searchString and another searchString</div>`
97
+ );
98
+
99
+ const expectedResult = {
100
+ "search-string": {
101
+ apiId: "search-string",
102
+ folder: null,
103
+ name: "Search String",
104
+ occurrences: {
105
+ [file1]: [
106
+ {
107
+ lineNumber: 1,
108
+ preview: "<div>Test searchString and another searchString</div>",
109
+ },
110
+ {
111
+ lineNumber: 1,
112
+ preview: "<div>Test searchString and another searchString</div>",
113
+ },
114
+ ],
54
115
  },
55
- ],
116
+ status: "NONE",
117
+ text: "searchString",
118
+ },
56
119
  };
57
120
 
58
- const result = await findTextInJSXFiles(".", {
59
- text: "searchString",
60
- } as any);
121
+ const result = await findComponentsInJSXFiles({
122
+ directory: ".",
123
+ files: ["file1.jsx"],
124
+ components: {
125
+ "search-string": {
126
+ name: "Search String",
127
+ text: "searchString",
128
+ status: "NONE",
129
+ folder: null,
130
+ },
131
+ },
132
+ });
61
133
 
62
134
  expect(result).toEqual(expectedResult);
63
135
 
136
+ try {
137
+ const result2 = await findComponentsInJSXFiles({
138
+ directory: ".",
139
+ files: ["file2.jsx"],
140
+ components: {
141
+ "search-string": {
142
+ name: "Search String",
143
+ text: "searchString",
144
+ status: "NONE",
145
+ folder: null,
146
+ },
147
+ },
148
+ });
149
+
150
+ expect(false).toEqual(true);
151
+ } catch {
152
+ expect(true).toEqual(true);
153
+ }
154
+
64
155
  await deleteTempFile(file1);
65
156
  });
66
157
  });
@@ -4,10 +4,16 @@ import { parse } from "@babel/parser";
4
4
  import traverse from "@babel/traverse";
5
5
 
6
6
  import {
7
+ FetchComponentResponse,
7
8
  FetchComponentResponseComponent,
8
9
  fetchComponents,
9
10
  } from "./http/fetchComponents";
10
11
 
12
+ interface Occurrence {
13
+ lineNumber: number;
14
+ preview: string;
15
+ }
16
+
11
17
  interface Result extends FetchComponentResponseComponent {
12
18
  apiId: string;
13
19
  occurrences: {
@@ -15,48 +21,41 @@ interface Result extends FetchComponentResponseComponent {
15
21
  };
16
22
  }
17
23
 
18
- interface Occurrence {
19
- lineNumber: number;
20
- preview: string;
21
- }
22
-
23
- async function generateSuggestions(flags: { directory?: string }) {
24
- const components = await fetchComponents();
25
- const results: { [apiId: string]: Result } = {};
26
-
27
- for (const [compApiId, component] of Object.entries(components)) {
28
- if (!results[compApiId]) {
29
- results[compApiId] = { apiId: compApiId, ...component, occurrences: {} };
30
- }
31
-
32
- const directory = flags.directory || ".";
33
- const result = await findTextInJSXFiles(directory, component);
34
- results[compApiId].occurrences = result;
24
+ async function generateSuggestions(flags: {
25
+ directory?: string;
26
+ files?: string[];
27
+ componentFolder?: string;
28
+ }) {
29
+ const components = await fetchComponents({
30
+ ...(flags.componentFolder ? { componentFolder: flags.componentFolder} : {})
31
+ });
32
+ const directory = flags.directory || ".";
35
33
 
36
- // Remove if there the length is zero
37
- if (Object.keys(results[compApiId].occurrences).length === 0) {
38
- delete results[compApiId];
39
- }
40
- }
34
+ const results: { [apiId: string]: Result } = await findComponentsInJSXFiles({
35
+ directory,
36
+ files: flags.files,
37
+ components,
38
+ });
41
39
 
42
40
  // Display results to user
43
41
  console.log(JSON.stringify(results, null, 2));
44
42
  }
45
43
 
46
- async function findTextInJSXFiles(
47
- path: string,
48
- component: FetchComponentResponseComponent
49
- ) {
50
- const result: Result["occurrences"] = {};
51
- const files = glob.sync(`${path}/**/*.+(jsx|tsx)`, {
52
- ignore: "**/node_modules/**",
53
- });
44
+ async function findComponentsInJSXFiles(params: {
45
+ directory: string;
46
+ files?: string[];
47
+ components: FetchComponentResponse;
48
+ }): Promise<{ [apiId: string]: Result }> {
49
+ const result: { [apiId: string]: Result } = {};
50
+ const files =
51
+ params.files ||
52
+ glob.sync(`${params.directory}/**/*.+(jsx|tsx)`, {
53
+ ignore: "**/node_modules/**",
54
+ });
54
55
 
55
56
  const promises: Promise<any>[] = [];
56
57
 
57
58
  for (const file of files) {
58
- result[file] = [];
59
-
60
59
  promises.push(
61
60
  fs.readFile(file, "utf-8").then((code) => {
62
61
  const ast = parse(code, {
@@ -64,46 +63,68 @@ async function findTextInJSXFiles(
64
63
  plugins: ["jsx", "typescript"],
65
64
  });
66
65
 
67
- const occurrences: Occurrence[] = [];
68
-
69
66
  traverse(ast, {
70
67
  JSXText(path) {
71
- if (path.node.value.includes(component.text)) {
72
- const escapedText = component.text.replace(
73
- /[.*+?^${}()|[\]\\]/g,
74
- "\\$&"
75
- );
76
- const regex = new RegExp(escapedText, "g");
77
- let match;
78
- while ((match = regex.exec(path.node.value)) !== null) {
79
- const lines = path.node.value.slice(0, match.index).split("\n");
80
-
81
- if (!path.node.loc) {
82
- continue;
83
- }
84
-
85
- const lineNumber = path.node.loc.start.line + lines.length - 1;
68
+ for (const [compApiId, component] of Object.entries(
69
+ params.components
70
+ )) {
71
+ // If we haven't seen this component before, add it to the result
72
+ if (!result[compApiId]) {
73
+ result[compApiId] = {
74
+ apiId: compApiId,
75
+ ...component,
76
+ occurrences: {},
77
+ };
78
+ }
86
79
 
87
- const codeLines = code.split("\n");
88
- const line = codeLines[lineNumber - 1];
89
- const preview = replaceAt(
90
- line,
91
- match.index,
92
- component.text,
93
- `${component.text}`
80
+ if (path.node.value.includes(component.text)) {
81
+ // Escape all special characters from the text so we can use it in a regex
82
+ const escapedText = component.text.replace(
83
+ /[.*+?^${}()|[\]\\]/g,
84
+ "\\$&"
94
85
  );
86
+ const regex = new RegExp(escapedText, "g");
87
+ let match;
88
+ while ((match = regex.exec(path.node.value)) !== null) {
89
+ const lines = path.node.value
90
+ .slice(0, match.index)
91
+ .split("\n");
92
+
93
+ if (!path.node.loc) {
94
+ continue;
95
+ }
96
+
97
+ const lineNumber =
98
+ path.node.loc.start.line + lines.length - 1;
99
+
100
+ const codeLines = code.split("\n");
101
+ const line = codeLines[lineNumber - 1];
102
+ const preview = replaceAt(
103
+ line,
104
+ match.index,
105
+ component.text,
106
+ `${component.text}`
107
+ );
108
+
109
+ // Initialize the occurrences array if it doesn't exist
110
+ if (!result[compApiId]["occurrences"][file]) {
111
+ result[compApiId]["occurrences"][file] = [];
112
+ }
113
+
114
+ result[compApiId]["occurrences"][file].push({
115
+ lineNumber,
116
+ preview,
117
+ });
118
+ }
119
+ }
95
120
 
96
- occurrences.push({ lineNumber, preview });
121
+ // Remove from result if no occurrences were found
122
+ if (Object.keys(result[compApiId]["occurrences"]).length === 0) {
123
+ delete result[compApiId];
97
124
  }
98
125
  }
99
126
  },
100
127
  });
101
-
102
- if (occurrences.length > 0) {
103
- result[file] = occurrences;
104
- } else {
105
- delete result[file];
106
- }
107
128
  })
108
129
  );
109
130
  }
@@ -125,4 +146,4 @@ function replaceAt(
125
146
  );
126
147
  }
127
148
 
128
- export { findTextInJSXFiles, generateSuggestions };
149
+ export { findComponentsInJSXFiles, generateSuggestions };
@@ -11,16 +11,26 @@ export interface FetchComponentResponse {
11
11
  [compApiId: string]: FetchComponentResponseComponent;
12
12
  }
13
13
 
14
- export async function fetchComponents(): Promise<FetchComponentResponse> {
14
+ export async function fetchComponents(options: {
15
+ componentFolder?: string;
16
+ }): Promise<FetchComponentResponse> {
15
17
  const api = createApiClient();
16
- const { data } = await api.get<{
17
- [compApiId: string]: {
18
- name: string;
19
- text: string;
20
- status: "NONE" | "WIP" | "REVIEW" | "FINAL";
21
- folder: "string" | null;
22
- };
23
- }>("/components", {});
24
18
 
25
- return data;
19
+ if (options.componentFolder) {
20
+ try {
21
+ const { data } = await api.get<FetchComponentResponse>(`/component-folders/${options.componentFolder}/components`, {});
22
+
23
+ return data;
24
+ }
25
+ catch (e) {
26
+ console.log(`Failed to get components for ${options.componentFolder}. Please verify the folder's API ID.`)
27
+ return {}
28
+ }
29
+
30
+ }
31
+ else {
32
+ const { data } = await api.get<FetchComponentResponse>("/components", {});
33
+
34
+ return data;
35
+ }
26
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dittowords/cli",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Command Line Interface for Ditto (dittowords.com).",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -40,8 +40,6 @@
40
40
  "@types/js-yaml": "^4.0.5",
41
41
  "@types/node": "^18.0.0",
42
42
  "babel-jest": "^29.3.1",
43
- "eslint": "^8.27.0",
44
- "eslint-config-airbnb-base": "^14.2.0",
45
43
  "husky": "^7.0.4",
46
44
  "jest": "^29.3.1",
47
45
  "lint-staged": "^11.2.4",
@@ -2,15 +2,15 @@ import React from "react";
2
2
  function HelloWorld() {
3
3
  return (
4
4
  <div>
5
- <p>hello world</p>
5
+ <p>hello!</p>
6
6
  <p>Some additional text.</p>
7
- <p>hello world</p>
7
+ <p>hello!</p>
8
8
  <p>More additional text.</p>
9
- <p>ahhh hello world iaewofjaowiejfoiwjfoffj</p>
9
+ <p>ahhh hello! iaewofjaowiejfoiwjfoffj</p>
10
10
  <p>Even more additional text.</p>
11
- <p>hello world</p>
11
+ <p>hello!</p>
12
12
  <p>Final additional text.</p>
13
- <p>hello world</p>
13
+ <p>hello!</p>
14
14
  <p>good bye</p>
15
15
  </div>
16
16
  );
@@ -2,7 +2,7 @@ import React from "react";
2
2
  function HelloWorld() {
3
3
  return (
4
4
  <div>
5
- <p>good bye</p>
5
+ <p>good bye?</p>
6
6
  </div>
7
7
  );
8
8
  }
package/.eslintrc.js DELETED
@@ -1,17 +0,0 @@
1
- module.exports = {
2
- env: {
3
- browser: true,
4
- commonjs: true,
5
- es2020: true,
6
- "jest/globals": true,
7
- },
8
- extends: ["airbnb-base"],
9
- parserOptions: {
10
- ecmaVersion: 11,
11
- },
12
- plugins: ["jest"],
13
- rules: {
14
- "no-console": "off",
15
- "no-underscore-dangle": ["error", { allow: ["_id", "__get__"] }],
16
- },
17
- };
@@ -1,36 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const project_1 = require("./init/project");
7
- const projectsToText_1 = __importDefault(require("./utils/projectsToText"));
8
- const getSelectedProjects_1 = require("./utils/getSelectedProjects");
9
- const output_1 = __importDefault(require("./output"));
10
- const quit_1 = require("./utils/quit");
11
- const addProject = async () => {
12
- const projects = (0, getSelectedProjects_1.getSelectedProjects)();
13
- const usingComponents = (0, getSelectedProjects_1.getIsUsingComponents)();
14
- try {
15
- if (usingComponents) {
16
- if (projects.length) {
17
- console.log(`\nYou're currently syncing text from the ${output_1.default.info("Component Library")} and from the following projects: ${(0, projectsToText_1.default)(projects)}`);
18
- }
19
- else {
20
- console.log(`\nYou're currently only syncing text from the ${output_1.default.info("Component Library")}`);
21
- }
22
- }
23
- else if (projects.length) {
24
- console.log(`\nYou're currently set up to sync text from the following projects: ${(0, projectsToText_1.default)(projects)}`);
25
- }
26
- await (0, project_1.collectAndSaveSource)({
27
- components: false,
28
- });
29
- }
30
- catch (error) {
31
- console.log(`\nSorry, there was an error adding a project to your workspace: `, error);
32
- (0, quit_1.quit)("Project selection was not updated.");
33
- }
34
- };
35
- exports.default = addProject;
36
- //# sourceMappingURL=add-project.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"add-project.js","sourceRoot":"","sources":["../../lib/add-project.ts"],"names":[],"mappings":";;;;;AAAA,4CAAsD;AACtD,4EAAoD;AACpD,qEAGqC;AACrC,sDAA8B;AAC9B,uCAAoC;AAEpC,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;IAC5B,MAAM,QAAQ,GAAG,IAAA,yCAAmB,GAAE,CAAC;IACvC,MAAM,eAAe,GAAG,IAAA,0CAAoB,GAAE,CAAC;IAE/C,IAAI;QACF,IAAI,eAAe,EAAE;YACnB,IAAI,QAAQ,CAAC,MAAM,EAAE;gBACnB,OAAO,CAAC,GAAG,CACT,4CAA4C,gBAAM,CAAC,IAAI,CACrD,mBAAmB,CACpB,qCAAqC,IAAA,wBAAc,EAAC,QAAQ,CAAC,EAAE,CACjE,CAAC;aACH;iBAAM;gBACL,OAAO,CAAC,GAAG,CACT,iDAAiD,gBAAM,CAAC,IAAI,CAC1D,mBAAmB,CACpB,EAAE,CACJ,CAAC;aACH;SACF;aAAM,IAAI,QAAQ,CAAC,MAAM,EAAE;YAC1B,OAAO,CAAC,GAAG,CACT,uEAAuE,IAAA,wBAAc,EACnF,QAAQ,CACT,EAAE,CACJ,CAAC;SACH;QACD,MAAM,IAAA,8BAAoB,EAAC;YACzB,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;KACJ;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,GAAG,CACT,kEAAkE,EAClE,KAAK,CACN,CAAC;QACF,IAAA,WAAI,EAAC,oCAAoC,CAAC,CAAC;KAC5C;AACH,CAAC,CAAC;AAEF,kBAAe,UAAU,CAAC"}
package/bin/lib/api.js DELETED
@@ -1,20 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.create = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
- const config_1 = __importDefault(require("./config"));
9
- const consts_1 = __importDefault(require("./consts"));
10
- const create = (token) => {
11
- return axios_1.default.create({
12
- baseURL: consts_1.default.API_HOST,
13
- headers: {
14
- Authorization: `token ${token}`,
15
- },
16
- });
17
- };
18
- exports.create = create;
19
- exports.default = (0, exports.create)(config_1.default.getToken(consts_1.default.CONFIG_FILE, consts_1.default.API_HOST));
20
- //# sourceMappingURL=api.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../lib/api.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAE1B,sDAA8B;AAC9B,sDAA8B;AAEvB,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,EAAE;IACvC,OAAO,eAAK,CAAC,MAAM,CAAC;QAClB,OAAO,EAAE,gBAAM,CAAC,QAAQ;QACxB,OAAO,EAAE;YACP,aAAa,EAAE,SAAS,KAAK,EAAE;SAChC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAPW,QAAA,MAAM,UAOjB;AAEF,kBAAe,IAAA,cAAM,EAAC,gBAAM,CAAC,QAAQ,CAAC,gBAAM,CAAC,WAAW,EAAE,gBAAM,CAAC,QAAQ,CAAC,CAAC,CAAC"}