@ui5/webcomponents-tools 0.0.0-f651a470c → 0.0.0-f79db712b

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 (46) hide show
  1. package/CHANGELOG.md +405 -0
  2. package/README.md +2 -1
  3. package/assets-meta.js +16 -7
  4. package/components-package/eslint.js +2 -0
  5. package/components-package/nps.js +37 -30
  6. package/components-package/postcss.components.js +1 -21
  7. package/components-package/postcss.themes.js +1 -26
  8. package/components-package/wdio.js +15 -3
  9. package/components-package/wdio.sync.js +9 -1
  10. package/icons-collection/nps.js +8 -6
  11. package/lib/cem/custom-elements-manifest.config.mjs +499 -0
  12. package/lib/cem/event.mjs +135 -0
  13. package/lib/cem/schema-internal.json +1354 -0
  14. package/lib/cem/schema.json +1098 -0
  15. package/lib/cem/types-internal.d.ts +801 -0
  16. package/lib/cem/types.d.ts +736 -0
  17. package/lib/cem/utils.mjs +354 -0
  18. package/lib/cem/validate.js +67 -0
  19. package/lib/create-illustrations/index.js +32 -28
  20. package/lib/create-new-component/index.js +28 -58
  21. package/lib/create-new-component/jsFileContentTemplate.js +0 -4
  22. package/lib/create-new-component/tsFileContentTemplate.js +2 -15
  23. package/lib/css-processors/css-processor-components.mjs +77 -0
  24. package/lib/css-processors/css-processor-themes.mjs +79 -0
  25. package/lib/css-processors/scope-variables.mjs +46 -0
  26. package/lib/{postcss-css-to-esm/index.js → css-processors/shared.mjs} +36 -50
  27. package/lib/dev-server/custom-hot-update-plugin.js +39 -0
  28. package/lib/esm-abs-to-rel/index.js +4 -1
  29. package/lib/generate-custom-elements-manifest/index.js +1 -1
  30. package/lib/generate-js-imports/illustrations.js +78 -64
  31. package/lib/generate-json-imports/i18n.js +10 -5
  32. package/lib/generate-json-imports/themes.js +10 -5
  33. package/lib/hbs2lit/src/compiler.js +9 -6
  34. package/lib/hbs2lit/src/litVisitor2.js +42 -17
  35. package/lib/hbs2lit/src/svgProcessor.js +12 -5
  36. package/lib/hbs2ui5/RenderTemplates/LitRenderer.js +32 -4
  37. package/lib/hbs2ui5/index.js +21 -4
  38. package/lib/jsdoc/preprocess.js +1 -1
  39. package/lib/postcss-combine-duplicated-selectors/index.js +12 -5
  40. package/lib/scoping/get-all-tags.js +1 -1
  41. package/lib/scoping/scope-test-pages.js +2 -1
  42. package/lib/test-runner/test-runner.js +2 -2
  43. package/package.json +10 -9
  44. package/lib/postcss-css-to-json/index.js +0 -47
  45. package/lib/postcss-new-files/index.js +0 -36
  46. package/lib/postcss-p/postcss-p.mjs +0 -14
@@ -0,0 +1,354 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ let JSDocErrors = [];
5
+
6
+ const getDeprecatedStatus = (jsdocComment) => {
7
+ const deprecatedTag = findTag(jsdocComment, "deprecated");
8
+ return deprecatedTag?.name
9
+ ? deprecatedTag.description
10
+ ? `${deprecatedTag.name} ${deprecatedTag.description}`
11
+ : deprecatedTag.name
12
+ : deprecatedTag
13
+ ? true
14
+ : undefined;
15
+ };
16
+
17
+ const normalizeDescription = (description) => {
18
+ return typeof description === 'string' ? description.replaceAll(/^-\s+|^(\n)+|(\n)+$/g, ""): description;
19
+ }
20
+
21
+ const getTypeRefs = (ts, node, member) => {
22
+ const extractTypeRefs = (type) => {
23
+ if (type?.kind === ts.SyntaxKind.TypeReference) {
24
+ return type.typeArguments?.length
25
+ ? type.typeArguments.map((typeRef) => typeRef.typeName?.text)
26
+ : [type.typeName?.text];
27
+ } else if (type?.kind === ts.SyntaxKind.UnionType) {
28
+ return type.types
29
+ .map((type) => extractTypeRefs(type))
30
+ .flat(1);
31
+ } else if (type?.kind === ts.SyntaxKind.TemplateLiteralType) {
32
+ if (member?.type) {
33
+ member.type.text = member.type.text.replaceAll?.(/`|\${|}/g, "");
34
+ }
35
+
36
+ return type.templateSpans?.length
37
+ ? type.templateSpans.map((typeRef) => typeRef.type?.typeName?.text)
38
+ : [type.typeName?.text];
39
+ }
40
+ };
41
+
42
+ let typeRefs = extractTypeRefs(node.type) || node?.typeArguments?.map(n => extractTypeRefs(n)).flat(2);
43
+
44
+ if (typeRefs) {
45
+ typeRefs = typeRefs.filter((e) => !!e);
46
+ }
47
+
48
+ return typeRefs?.length ? typeRefs : undefined;
49
+ };
50
+
51
+ const getSinceStatus = (jsdocComment) => {
52
+ const sinceTag = findTag(jsdocComment, "since");
53
+ return sinceTag
54
+ ? sinceTag.description
55
+ ? `${sinceTag.name} ${sinceTag.description}`
56
+ : sinceTag.name
57
+ : undefined;
58
+ };
59
+
60
+ const getPrivacyStatus = (jsdocComment) => {
61
+ const privacyTag = findTag(jsdocComment, ["public", "private", "protected"]);
62
+ return privacyTag?.tag || "private";
63
+ };
64
+
65
+ const findPackageName = (ts, sourceFile, typeName) => {
66
+ const localStatements = [
67
+ ts.SyntaxKind.EnumDeclaration,
68
+ ts.SyntaxKind.InterfaceDeclaration,
69
+ ts.SyntaxKind.ClassDeclaration,
70
+ ts.SyntaxKind.TypeAliasDeclaration,
71
+ ];
72
+
73
+ const isLocalDeclared = sourceFile.statements.some(
74
+ (statement) =>
75
+ localStatements.includes(statement.kind) && statement?.name?.text === typeName
76
+ );
77
+
78
+ if (isLocalDeclared) {
79
+ return packageJSON?.name;
80
+ } else {
81
+ const importStatements = sourceFile.statements?.filter(
82
+ (statement) => statement.kind === ts.SyntaxKind.ImportDeclaration
83
+ );
84
+ const currentModuleSpecifier = importStatements.find((statement) => {
85
+ if (statement.importClause?.name?.text === typeName) {
86
+ return true;
87
+ }
88
+
89
+ return statement.importClause?.namedBindings?.elements?.some(
90
+ (element) => element.name?.text === typeName
91
+ );
92
+ })?.moduleSpecifier;
93
+
94
+ if (currentModuleSpecifier?.text?.startsWith(".")) {
95
+ return packageJSON?.name;
96
+ } else {
97
+ return Object.keys(packageJSON?.dependencies || {}).find(
98
+ (dependency) =>
99
+ currentModuleSpecifier?.text?.startsWith(`${dependency}/`)
100
+ );
101
+ }
102
+ }
103
+ };
104
+
105
+ const findImportPath = (ts, sourceFile, typeName, modulePath) => {
106
+ const localStatements = [
107
+ ts.SyntaxKind.EnumDeclaration,
108
+ ts.SyntaxKind.InterfaceDeclaration,
109
+ ts.SyntaxKind.ClassDeclaration,
110
+ ts.SyntaxKind.TypeAliasDeclaration,
111
+ ];
112
+
113
+ const isLocalDeclared = sourceFile.statements.some(
114
+ (statement) =>
115
+ localStatements.includes(statement.kind) && statement?.name?.text === typeName
116
+ );
117
+
118
+ if (isLocalDeclared) {
119
+ return (
120
+ modulePath?.replace("src", "dist")?.replace(".ts", ".js") || undefined
121
+ );
122
+ } else {
123
+ const importStatements = sourceFile.statements?.filter(
124
+ (statement) => statement.kind === ts.SyntaxKind.ImportDeclaration
125
+ );
126
+ const currentModuleSpecifier = importStatements.find((statement) => {
127
+ if (statement.importClause?.name?.text === typeName) {
128
+ return true;
129
+ }
130
+
131
+ return statement.importClause?.namedBindings?.elements?.some(
132
+ (element) => element.name?.text === typeName
133
+ );
134
+ })?.moduleSpecifier;
135
+
136
+ if (currentModuleSpecifier?.text?.startsWith(".")) {
137
+ return (
138
+ path.join(path.dirname(modulePath), currentModuleSpecifier.text)
139
+ ?.replace("src", "dist")?.replace(".ts", ".js") || undefined
140
+ );
141
+ } else {
142
+ const packageName = Object.keys(packageJSON?.dependencies || {}).find(
143
+ (dependency) =>
144
+ currentModuleSpecifier?.text?.startsWith(dependency)
145
+ );
146
+ return currentModuleSpecifier?.text
147
+ ?.replace(`${packageName}/`, "") || undefined;
148
+ }
149
+ }
150
+ };
151
+
152
+
153
+ const isClass = text => {
154
+ return text.includes("@abstract") || text.includes("@class") || text.includes("@constructor");
155
+ };
156
+
157
+ const normalizeTagType = (type) => {
158
+ return type?.trim();
159
+ }
160
+
161
+ const packageJSON = JSON.parse(fs.readFileSync("./package.json"));
162
+
163
+ const getReference = (ts, type, classNode, modulePath) => {
164
+ let sourceFile = classNode.parent;
165
+
166
+ while (sourceFile && sourceFile.kind !== ts.SyntaxKind.SourceFile) {
167
+ sourceFile = sourceFile.parent;
168
+ }
169
+
170
+ const typeName =
171
+ typeof type === "string"
172
+ ? normalizeTagType(type)
173
+ : type.class?.expression?.text ||
174
+ type.typeExpression?.type?.getText() ||
175
+ type.typeExpression?.type?.elementType?.typeName?.text;
176
+ const packageName = findPackageName(ts, sourceFile, typeName);
177
+ const importPath = findImportPath(
178
+ ts,
179
+ sourceFile,
180
+ typeName,
181
+ modulePath
182
+ )?.replace(`${packageName}/`, "");
183
+
184
+ return packageName && {
185
+ name: typeName,
186
+ package: packageName,
187
+ module: importPath,
188
+ };
189
+ };
190
+
191
+ const getType = (type) => {
192
+ const typeName = typeof type === "string" ? normalizeTagType(type) : type?.type;
193
+
194
+ const multiple =
195
+ typeName?.endsWith("[]") || typeName?.startsWith("Array<");
196
+ const name = multiple
197
+ ? typeName?.replace("[]", "")?.replace("Array<", "")?.replace(">", "")
198
+ : typeName;
199
+
200
+ return typeName ? { typeName: multiple ? `Array<${name}>` : typeName, name, multiple } : undefined;
201
+ };
202
+
203
+ const commonTags = ["public", "protected", "private", "since", "deprecated"];
204
+
205
+ const allowedTags = {
206
+ field: [...commonTags, "formEvents", "formProperty", "default"],
207
+ slot: [...commonTags, "default"],
208
+ event: [...commonTags, "param", "allowPreventDefault", "native"],
209
+ eventParam: [...commonTags],
210
+ method: [...commonTags, "param", "returns", "override"],
211
+ class: [...commonTags, "constructor", "class", "abstract", "implements", "extends", "slot", "csspart"],
212
+ enum: [...commonTags],
213
+ enumMember: [...commonTags],
214
+ interface: [...commonTags],
215
+ };
216
+ allowedTags.getter = [...allowedTags.field, "override"]
217
+
218
+ const tagMatchCallback = (tag, tagName) => {
219
+ const currentTagName = tag.tag;
220
+
221
+ return typeof tagName === "string"
222
+ ? currentTagName === tagName
223
+ : tagName.includes(currentTagName);
224
+ };
225
+
226
+ const findDecorator = (node, decoratorName) => {
227
+ return node?.decorators?.find(
228
+ (decorator) =>
229
+ decorator?.expression?.expression?.text === decoratorName
230
+ );
231
+ };
232
+
233
+ const findAllDecorators = (node, decoratorName) => {
234
+ return (
235
+ node?.decorators?.filter(
236
+ (decorator) =>
237
+ decorator?.expression?.expression?.text === decoratorName
238
+ ) || []
239
+ );
240
+ };
241
+
242
+ const hasTag = (jsDoc, tagName) => {
243
+ if (!jsDoc) {
244
+ return;
245
+ }
246
+
247
+ return jsDoc?.tags?.some((tag) => tagMatchCallback(tag, tagName));
248
+ };
249
+
250
+ const findTag = (jsDoc, tagName) => {
251
+ if (!jsDoc) {
252
+ return;
253
+ }
254
+
255
+ return jsDoc?.tags?.find((tag) => tagMatchCallback(tag, tagName));
256
+ };
257
+
258
+ const findAllTags = (jsDoc, tagName) => {
259
+ if (!jsDoc) {
260
+ return [];
261
+ }
262
+
263
+ const foundTags = jsDoc?.tags?.filter((tag) => tagMatchCallback(tag, tagName));
264
+
265
+ return foundTags || [];
266
+ };
267
+
268
+ const validateJSDocTag = (tag) => {
269
+ const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "native", "formProperty", "constructor", "override"];
270
+ let tagName = tag.tag;
271
+
272
+ if (booleanTags.includes(tag.tag)) {
273
+ tagName = "boolean";
274
+ }
275
+
276
+ switch (tagName) {
277
+ case "boolean":
278
+ return !tag.name && !tag.type && !tag.description;
279
+ case "deprecated":
280
+ return !tag.type;
281
+ case "extends":
282
+ return !tag.type && tag.name && !tag.description;
283
+ case "implements":
284
+ return tag.type && !tag.name && !tag.description;
285
+ case "slot":
286
+ return tag.type && tag.name && tag.description;
287
+ case "csspart":
288
+ return !tag.type && tag.name && tag.description;
289
+ case "since":
290
+ return !tag.type && tag.name;
291
+ case "returns":
292
+ return !tag.type && tag.name;
293
+ case "default":
294
+ return !tag.type && !tag.description;
295
+ case "class":
296
+ return !tag.type;
297
+ case "param":
298
+ return !tag.type && tag.name;
299
+ case "eventparam":
300
+ return tag.type && tag.name;
301
+ case "formEvents":
302
+ return !tag.type && tag.name;
303
+ default:
304
+ return false;
305
+ }
306
+ };
307
+
308
+ const validateJSDocComment = (fieldType, jsdocComment, node, moduleDoc) => {
309
+ return !!jsdocComment?.tags?.every((tag) => {
310
+ let isValid = false
311
+
312
+ if (fieldType === "event" && tag?.tag === "param") {
313
+ isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag({...tag, tag: "eventparam"});
314
+ } else {
315
+ isValid = allowedTags[fieldType]?.includes(tag.tag) && validateJSDocTag(tag);
316
+ }
317
+
318
+ if (!isValid) {
319
+ JSDocErrors.push(
320
+ `=== ERROR: Problem found with ${node}'s JSDoc comment in ${moduleDoc.path}: \n\t- @${tag.tag} tag is being used wrong or it's not part of ${fieldType} JSDoc tags`
321
+ );
322
+ }
323
+
324
+ return !!isValid;
325
+ });
326
+ };
327
+
328
+ const getJSDocErrors = () => {
329
+ return JSDocErrors;
330
+ };
331
+
332
+ const formatArrays = (typeText) => {
333
+ return typeText.replaceAll(/(\S+)\[\]/g, "Array<$1>")
334
+ }
335
+
336
+ export {
337
+ getPrivacyStatus,
338
+ getSinceStatus,
339
+ getDeprecatedStatus,
340
+ getType,
341
+ getReference,
342
+ validateJSDocComment,
343
+ findDecorator,
344
+ findAllDecorators,
345
+ hasTag,
346
+ findTag,
347
+ findAllTags,
348
+ getJSDocErrors,
349
+ getTypeRefs,
350
+ normalizeDescription,
351
+ formatArrays,
352
+ isClass,
353
+ normalizeTagType
354
+ };
@@ -0,0 +1,67 @@
1
+ const fs = require('fs');
2
+ const Ajv = require('ajv');
3
+ const path = require('path');
4
+ const yargs = require('yargs/yargs')
5
+ const { hideBin } = require('yargs/helpers')
6
+ const argv = yargs(hideBin(process.argv))
7
+ .argv;
8
+
9
+ // Load your JSON schema
10
+ const extenalSchema = require('./schema.json');
11
+ const internalSchema = require('./schema-internal.json');
12
+
13
+ // Load your JSON data from the input file
14
+ const inputFilePath = path.join(process.cwd(), "dist/custom-elements.json"); // Update with your file path
15
+ const customManifest = fs.readFileSync(inputFilePath, 'utf8');
16
+ const inputDataInternal = JSON.parse(customManifest);
17
+
18
+ const clearProps = (data) => {
19
+ if (Array.isArray(data)) {
20
+ for (let i = 0; i < data.length; i++) {
21
+ if (typeof data[i] === "object") {
22
+ if (["enum", "interface"].includes(data[i].kind)) {
23
+ data.splice(i, 1);
24
+ i--;
25
+ } else {
26
+ clearProps(data[i]);
27
+ }
28
+ }
29
+ }
30
+ } else if (typeof data === "object") {
31
+ Object.keys(data).forEach(prop => {
32
+ if (prop.startsWith("_ui5")) {
33
+ delete data[prop];
34
+ } else if (typeof data[prop] === "object") {
35
+ clearProps(data[prop]);
36
+ }
37
+ });
38
+ }
39
+
40
+ return data;
41
+ }
42
+
43
+ const ajv = new Ajv({ allowUnionTypes: true, allError: true })
44
+ let validate = ajv.compile(internalSchema)
45
+
46
+ // Validate the JSON data against the schema
47
+ if (argv.dev) {
48
+ if (validate(inputDataInternal)) {
49
+ console.log('Internal custom element manifest is validated successfully');
50
+ } else {
51
+ throw new Error(`Validation of internal custom elements manifest failed: ${validate.errors}`);
52
+ }
53
+ }
54
+
55
+ const inputDataExternal = clearProps(JSON.parse(JSON.stringify(inputDataInternal)));
56
+ validate = ajv.compile(extenalSchema)
57
+
58
+ // Validate the JSON data against the schema
59
+ if (validate(inputDataExternal)) {
60
+ console.log('Custom element manifest is validated successfully');
61
+ fs.writeFileSync(inputFilePath, JSON.stringify(inputDataExternal, null, 2), 'utf8');
62
+ fs.writeFileSync(inputFilePath.replace("custom-elements", "custom-elements-internal"), JSON.stringify(inputDataInternal, null, 2), 'utf8');
63
+ } else {
64
+ if (argv.dev) {
65
+ throw new Error(`Validation of public custom elements manifest failed: ${validate.errors}`);
66
+ }
67
+ }
@@ -57,15 +57,25 @@ const generate = async () => {
57
57
  const illustrationsPrefix = process.argv[4];
58
58
  const illustrationSet = process.argv[5];
59
59
  const destPath = process.argv[6];
60
+ const collection = process.argv[7];
60
61
  const fileNamePattern = new RegExp(`${illustrationsPrefix}-.+-(.+).svg`);
61
- // collect each illustration name because each one should have Sample.js file
62
+ // collect each illustration name because each one should have Sample.js file
62
63
  const fileNames = new Set();
63
64
 
65
+ try {
66
+ await fs.access(srcPath);
67
+ } catch (error) {
68
+ console.log(`The path ${srcPath} does not exist.`);
69
+ return Promise.resolve(null);
70
+ }
71
+
72
+ console.log(`Generating illustrations from ${srcPath} to ${destPath}`)
73
+
64
74
  const svgImportTemplate = svgContent => {
65
75
  return `export default \`${svgContent}\`;`
66
76
  };
67
77
  const svgToJs = async fileName => {
68
- const svg = await fs.readFile(path.join(srcPath, fileName), {encoding: "utf-8"});
78
+ const svg = await fs.readFile(path.join(srcPath, fileName), { encoding: "utf-8" });
69
79
  const fileContent = svgImportTemplate(svg);
70
80
  fileName = fileName.replace(/\.svg$/, ".js");
71
81
 
@@ -84,54 +94,47 @@ const generate = async () => {
84
94
 
85
95
  const illustrationNameUpperCase = illustrationNameForTranslation.toUpperCase();
86
96
 
87
- return defaultText ? `import { registerIllustration } from "@ui5/webcomponents-base/dist/asset-registries/Illustrations.js";
97
+ return `import { registerIllustration } from "@ui5/webcomponents-base/dist/asset-registries/Illustrations.js";
88
98
  import dialogSvg from "./${illustrationsPrefix}-Dialog-${illustrationName}.js";
89
99
  import sceneSvg from "./${illustrationsPrefix}-Scene-${illustrationName}.js";
90
- import spotSvg from "./${illustrationsPrefix}-Spot-${illustrationName}.js";
91
- import {
100
+ import spotSvg from "./${illustrationsPrefix}-Spot-${illustrationName}.js";${
101
+ defaultText ? `import {
92
102
  IM_TITLE_${illustrationNameUpperCase},
93
103
  IM_SUBTITLE_${illustrationNameUpperCase},
94
- } from "../generated/i18n/i18n-defaults.js";
104
+ } from "../generated/i18n/i18n-defaults.js";` : ``}
95
105
 
96
106
  const name = "${illustrationName}";
97
107
  const set = "${illustrationSet}";
108
+ const collection = "${collection}";${defaultText ? `
98
109
  const title = IM_TITLE_${illustrationNameUpperCase};
99
- const subtitle = IM_SUBTITLE_${illustrationNameUpperCase};
110
+ const subtitle = IM_SUBTITLE_${illustrationNameUpperCase};` : ``}
100
111
 
101
112
  registerIllustration(name, {
102
113
  dialogSvg,
103
114
  sceneSvg,
104
- spotSvg,
115
+ spotSvg,${defaultText ? `
105
116
  title,
106
- subtitle,
117
+ subtitle,` : ``}
107
118
  set,
119
+ collection,
108
120
  });
109
121
 
122
+ export default "${illustrationSet === "fiori" ? "" : `${illustrationSet}/`}${illustrationName}";
110
123
  export {
111
124
  dialogSvg,
112
125
  sceneSvg,
113
126
  spotSvg,
114
- };` :
115
- `import { registerIllustration } from "@ui5/webcomponents-base/dist/asset-registries/Illustrations.js";
116
- import dialogSvg from "./${illustrationsPrefix}-Dialog-${illustrationName}.js";
117
- import sceneSvg from "./${illustrationsPrefix}-Scene-${illustrationName}.js";
118
- import spotSvg from "./${illustrationsPrefix}-Spot-${illustrationName}.js";
119
-
120
- const name = "${illustrationName}";
121
- const set = "${illustrationSet}";
127
+ };`
128
+ };
122
129
 
123
- registerIllustration(name, {
124
- dialogSvg,
125
- sceneSvg,
126
- spotSvg,
127
- set,
128
- });
130
+ const illustrationTypeDefinition = illustrationName => {
131
+ return `declare const dialogSvg: string;
132
+ declare const sceneSvg: string;
133
+ declare const spotSvg: string;
134
+ declare const _default: "${illustrationSet === "fiori" ? "" : `${illustrationSet}/`}${illustrationName}";
129
135
 
130
- export {
131
- dialogSvg,
132
- sceneSvg,
133
- spotSvg,
134
- };`
136
+ export default _default;
137
+ export { dialogSvg, sceneSvg, spotSvg };`
135
138
  };
136
139
 
137
140
  await fs.mkdir(destPath, { recursive: true });
@@ -151,6 +154,7 @@ export {
151
154
 
152
155
  for (let illustrationName of fileNames) {
153
156
  promises.push(fs.writeFile(path.join(destPath, `${illustrationName}.js`), illustrationImportTemplate(illustrationName)));
157
+ promises.push(fs.writeFile(path.join(destPath, `${illustrationName}.d.ts`), illustrationTypeDefinition(illustrationName)));
154
158
  }
155
159
 
156
160
  return Promise.all(promises);
@@ -1,8 +1,31 @@
1
1
  const fs = require("fs");
2
+ const path = require("path");
2
3
  const prompts = require("prompts");
3
4
  const jsFileContentTemplate = require("./jsFileContentTemplate.js");
4
5
  const tsFileContentTemplate = require("./tsFileContentTemplate.js");
5
6
 
7
+ /**
8
+ * Hyphanates the given PascalCase string, f.e.:
9
+ * Foo -> "my-foo" (adds preffix)
10
+ * FooBar -> "foo-bar"
11
+ */
12
+ const hyphaneteComponentName = (componentName) => {
13
+ const result = componentName.replace(/([a-z])([A-Z])/g, '$1-$2' ).toLowerCase();
14
+
15
+ return result.includes("-") ? result : `my-${result}`;
16
+ };
17
+
18
+ /**
19
+ * Capitalizes first letter of string.
20
+ */
21
+ const capitalizeFirstLetter = string => string.charAt(0).toUpperCase() + string.slice(1);
22
+
23
+ /**
24
+ * Validates component name, enforcing PascalCase pattern - Button, MyButton.
25
+ */
26
+ const PascalCasePattern = /^[A-Z][A-Za-z0-9]+$/;
27
+ const isNameValid = name => typeof name === "string" && PascalCasePattern.test(name);
28
+
6
29
  const getPackageName = () => {
7
30
  if (!fs.existsSync("./package.json")) {
8
31
  throw("The current directory doesn't contain package.json file.");
@@ -35,13 +58,6 @@ const getLibraryName = packageName => {
35
58
  return packageName.substr("webcomponents-".length);
36
59
  };
37
60
 
38
- // String manipulation
39
- const capitalizeFirstLetter = string => string.charAt(0).toUpperCase() + string.slice(1);
40
-
41
- // Validation of user input
42
- const isNameValid = name => typeof name === "string" && name.match(/^[a-zA-Z][a-zA-Z0-9_-]*$/);
43
- const isTagNameValid = tagName => tagName.match(/^([a-z][a-z0-9]*-)([a-z0-9]+(-[a-z0-9]+)*)$/);
44
-
45
61
  const generateFiles = (componentName, tagName, library, packageName, isTypeScript) => {
46
62
  componentName = capitalizeFirstLetter(componentName);
47
63
  const filePaths = {
@@ -67,7 +83,7 @@ const generateFiles = (componentName, tagName, library, packageName, isTypeScrip
67
83
  // Change the color of the output
68
84
  console.warn('\x1b[33m%s\x1b[0m', `
69
85
  Make sure to import the component in your bundle by using:
70
- import ${componentName} from "./dist/${componentName}.js";`);
86
+ import "./dist/${componentName}.js";`);
71
87
  }
72
88
 
73
89
  // Main function
@@ -77,21 +93,9 @@ const createWebComponent = async () => {
77
93
 
78
94
  const consoleArguments = process.argv.slice(2);
79
95
  let componentName = consoleArguments[0];
80
- let tagName = consoleArguments[1];
81
- let language = consoleArguments[2];
82
- let isTypeScript;
83
-
84
96
 
85
97
  if (componentName && !isNameValid(componentName)) {
86
- throw new Error("Invalid component name. Please use only letters, numbers, dashes and underscores. The first character must be a letter.");
87
- }
88
-
89
- if (tagName && !isTagNameValid(tagName)) {
90
- throw new Error("Invalid tag name. The tag name should only contain lowercase letters, numbers, dashes, and underscores. The first character must be a letter, and it should follow the pattern 'tag-name'.");
91
- }
92
-
93
- if (language && language !== "typescript" && language !== "ts" && language !== "javascript" && language !== "js") {
94
- throw new Error("Invalid language. Please use 'typescript','javascript' or their respective 'ts','js'.");
98
+ throw new Error(`${componentName} is invalid component name. Use only letters (at least two) and start with capital one: Button, MyButton, etc.`);
95
99
  }
96
100
 
97
101
  if (!componentName) {
@@ -99,7 +103,7 @@ const createWebComponent = async () => {
99
103
  type: "text",
100
104
  name: "componentName",
101
105
  message: "Please enter a component name:",
102
- validate: (value) => isNameValid(value),
106
+ validate: (value) => isNameValid(value) ? true : "Component name should follow PascalCase naming convention (f.e. Button, MyButton, etc.).",
103
107
  });
104
108
  componentName = response.componentName;
105
109
 
@@ -108,42 +112,8 @@ const createWebComponent = async () => {
108
112
  }
109
113
  }
110
114
 
111
- if (!tagName) {
112
- const response = await prompts({
113
- type: "text",
114
- name: "tagName",
115
- message: "Please enter a tag name:",
116
- validate: (value) => isTagNameValid(value),
117
- });
118
- tagName = response.tagName;
119
-
120
- if (!tagName) {
121
- process.exit();
122
- }
123
- }
124
-
125
- if (!language) {
126
- const response = await prompts({
127
- type: "select",
128
- name: "isTypeScript",
129
- message: "Please select a language:",
130
- choices: [
131
- {
132
- title: "TypeScript (recommended)",
133
- value: true,
134
- },
135
- {
136
- title: "JavaScript",
137
- value: false,
138
- },
139
- ],
140
- });
141
- isTypeScript = response.isTypeScript;
142
- } else if (language === "typescript" || language === "ts") {
143
- isTypeScript = true;
144
- } else {
145
- isTypeScript = false;
146
- }
115
+ const isTypeScript = fs.existsSync(path.join(process.cwd(), "tsconfig.json"));
116
+ const tagName = hyphaneteComponentName(componentName);
147
117
 
148
118
  generateFiles(componentName, tagName, library, packageName, isTypeScript);
149
119
  };
@@ -62,10 +62,6 @@ class ${componentName} extends UI5Element {
62
62
  static get dependencies() {
63
63
  return [];
64
64
  }
65
-
66
- static async onDefine() {
67
-
68
- }
69
65
  }
70
66
 
71
67
  ${componentName}.define();