@elliemae/ds-codemods 3.31.0-next.3 → 3.31.0-next.4

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.
@@ -0,0 +1,55 @@
1
+ // eslint-disable-next-line max-len, max-len
2
+ // /^import\s*((?:[\S]*(?:\s*as\s*[\S]*)?(?:\s*,\s*(?:(?:[\S]*\s*as\s*[\S]*)|(?:\{(?:[\s\S](?!{))*?\})))?)|(?:\{(?:[\s\S](?!{))*?\}))\s*from\s*['"](@elliemae\/ds-\S+)['"]/gms
3
+ /*
4
+ reg-exp divided into units for readability:
5
+ ^import\s* - matches "import" followed by any number of spaces,
6
+ constraints the match to the beginning of the line
7
+ ( - start of the first capturing group
8
+ // this one captures:
9
+ - the default import (e.g. import Button from 'wherever')
10
+ - the named import (e.g. import Button as Btn from 'wherever')
11
+ - the default import followed by a comma (e.g. import Button, { Input } from 'wherever')
12
+ (?:
13
+ [\S]* - matches any non-whitespace character any number of times
14
+ this is meant to catch the default import (e.g. import Button from 'wherever')
15
+ (?:\s*as\s*[\S]*)? - optionally matches the named import (e.g. import Button as Btn from 'wherever')
16
+
17
+ (?: - optionally matches imports with comma after them
18
+ (e.g. import Button, { Input } from 'wherever')
19
+ \s*,\s* - matches a comma, with any number of spaces before and after it
20
+ - in ESM after a comma you have a named default import or a destructured import...
21
+ (?:
22
+ (?:[\S]*\s*as\s*[\S]*) - the catching of "default" imports as above,
23
+ no optional named import because after comma you can't repeat the default import
24
+ |
25
+ (?:\{ - between curly braces START
26
+ (?:[\s\S] - anything (including new lines)
27
+ (?!{) - as long as it's not followed by another curly brace
28
+ (avoid matching import from other packages followed by import from @elliemae/ds-* )
29
+ )*?
30
+ \}) - between curly braces END
31
+ )
32
+ )?
33
+ )
34
+ | - OR
35
+ // this one captures:
36
+ - the destructured import (e.g. import { Button } from 'wherever')
37
+ (?:\{ - between curly braces START
38
+ (?:[\s\S] - anything (including new lines)
39
+ (?!{) - as long as it's not followed by another curly brace
40
+ (avoid matching import from other packages followed by import from @elliemae/ds-* )
41
+ )*?
42
+ \}) - between curly braces END
43
+
44
+ )
45
+ \s*from\s* - matches "from" with any number of spaces before and after it
46
+ - matches a string between single or double quotes that starts with "@elliemae/ds-*"
47
+ ['"]
48
+ (@elliemae\/ds-\S+)
49
+ ['"]
50
+
51
+ https://regex101.com/r/7BsJPF/1
52
+ */
53
+ export const dimsumImportStatementsRegExp =
54
+ // eslint-disable-next-line max-len
55
+ /^import\s*((?:[\S]*(?:\s*as\s*[\S]*)?(?:\s*,\s*(?:(?:[\S]*\s*as\s*[\S]*)|(?:\{(?:[\s\S](?!{))*?\})))?)|(?:\{(?:[\s\S](?!{))*?\}))\s*from\s*['"](@elliemae\/ds-\S+)['"]/gms;
@@ -0,0 +1,46 @@
1
+ import fs from 'fs';
2
+ import { createCSVReport } from './utils.mjs';
3
+
4
+ const writeReport = (options, csvRows) => {
5
+ const { outputPath, append } = options;
6
+ if (!append) {
7
+ fs.writeFileSync(outputPath, csvRows.join('\n'));
8
+ } else {
9
+ // when we append we do not want to repeat the header, instead we want to add a new line
10
+ // we check if the file exists and if it does we check if the first line is the header
11
+ const fileExists = fs.existsSync(outputPath);
12
+ let shouldAddHeader = true;
13
+ if (fileExists) {
14
+ const fileContent = fs.readFileSync(outputPath, 'utf8');
15
+ const firstLine = fileContent.split('\n')[0];
16
+ if (firstLine === csvRows[0]) {
17
+ shouldAddHeader = false;
18
+ }
19
+ }
20
+ if (!shouldAddHeader) {
21
+ // since we are appending, the first line is left empty, this way the csvRows.join('\n') will add a new line
22
+ csvRows[0] = '';
23
+ }
24
+ fs.appendFileSync(outputPath, csvRows.join('\n'));
25
+ }
26
+ };
27
+
28
+ /**
29
+ * generates a report of deprecated components usage
30
+ * @param {object} options - options *
31
+ * @param {string} options.outputPath - path to the .csv file to write the report to
32
+ * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
33
+ * @param {string} options.gitIgnorePath - path to the .gitignore file
34
+ * @param {string} options.org - optional org, if present an extra column will be added to the report
35
+ * @param {string} options.repo - optional repo, if present an extra column will be added to the report
36
+ * @param {boolean} options.debug - debug flag
37
+ * @param {boolean} options.append - whether to append to the file or override it
38
+ *
39
+ * @returns {void}
40
+ */
41
+ export const componentsUsageReport = (options) => {
42
+ const csvRows = createCSVReport(options);
43
+ writeReport(options, csvRows);
44
+ };
45
+
46
+ export default componentsUsageReport;
@@ -0,0 +1,320 @@
1
+ /* eslint-disable max-params, max-lines, max-statements, max-len */
2
+ import fs from 'fs';
3
+ import { filePathsWithGitIgnore } from '../../../utils/filepathsMatchers.mjs';
4
+ import { dimsumImportStatementsRegExp } from './dimsumImportStatementsRegExp.mjs';
5
+
6
+ /**
7
+ * @typedef {Object} ImportAllAsResults
8
+ * @property {string} fullMatch
9
+ * @property {string} renamedAs
10
+ *
11
+ * @typedef {Object} DefaultImportResults
12
+ * @property {string} fullMatch
13
+ * @property {string} renamedAs
14
+ *
15
+ * @typedef {Object} PartialNamedImport
16
+ * @property {string} namedImportWithRename[number].partialFullMatch
17
+ * @property {string} namedImportWithRename[number].component
18
+ * @property {string|undefined} namedImportWithRename[number].renamedTo
19
+ *
20
+ * @typedef {PartialNamedImport[]} NamedImportResults
21
+ *
22
+ * @typedef { object } ImportsFlags
23
+ * @property {boolean} hasRelationWithDimsum
24
+ *
25
+ *
26
+ * @typedef {Object} fileImportsInfos
27
+ * @property {ImportAllAsResults[]} globalRenameImports
28
+ * @property {DefaultImportResults[]} importedDefaults
29
+ * @property {namedImportWithoutRename[]} importedNamedComponentsWithoutRename
30
+ * @property {namedImportWithRename[]} importedNamedComponentsWithRename
31
+ *
32
+ * @typedef {ImportsFlags & fileImportsInfos & Object} FileReport
33
+ * @property {string} filePath
34
+ */
35
+
36
+ // specific regexp to distinguish 'import * as X from "Y"'
37
+ /**
38
+ * given an ESM import statement as a string, it returns the renamed all as alias if present
39
+ * @param {string} importStatement - the import statement to parse
40
+ * @returns {ImportAllAsResults | undefined}
41
+ * if renamed all as alias is present returns an object with the full match and the renamed all as alias
42
+ *
43
+ * if no renamed all as alias is present returns undefined
44
+ * @example
45
+ * renameAllAs('import * as X from "Y"') // { fullMatch: 'import * as X from "Y"', renamedAs: 'X' }
46
+ * renameAllAs('import X from "Y"') // undefined
47
+ */
48
+ const renameAllAs = (importStatement) => {
49
+ const renameAllAsRegex = /\*\s+as\s+(\w+)/gms;
50
+ // using matchAll because match doesn't preserve the capturing groups...
51
+ const matches = importStatement.matchAll(renameAllAsRegex);
52
+ const match = [...matches][0];
53
+ if (!match) return undefined;
54
+ return {
55
+ fullMatch: importStatement,
56
+ renamedAs: match[1],
57
+ };
58
+ };
59
+
60
+ /**
61
+ * given an ESM import statement as a string, it returns the default import alias if present
62
+ * @param {string} importStatement - the import statement to parse
63
+ * @returns {DefaultImportResults | undefined}
64
+ * if default import alias is present returns an object with the full match and the default import alias
65
+ *
66
+ * if no default import alias is present returns undefined
67
+ * @example
68
+ * defaultImport('import X from "Y"') // { fullMatch: 'import X from "Y"', renamedAs: 'X' }
69
+ * defaultImport('import X, { A, B } from "Y"') // { fullMatch: 'import X, { A, B } from "Y"', renamedAs: 'X' }
70
+ * defaultImport('import { A, B } from "Y"') // undefined
71
+ */
72
+ const defaultImport = (importStatement) => {
73
+ const defaultImportRegex = /^import\s*(\w+)(?:,\s(?:\{(?:[\s\S](?!{))*?\}))*/gms;
74
+ // using matchAll because match doesn't preserve the capturing groups...
75
+ const matches = importStatement.matchAll(defaultImportRegex);
76
+ const match = [...matches][0];
77
+ if (!match) return undefined;
78
+ /** @type {DefaultImportResults} */
79
+ return {
80
+ fullMatch: importStatement,
81
+ renamedAs: match[1],
82
+ };
83
+ };
84
+
85
+ /**
86
+ * given the curly braces part of an ESM import statement, it returns the named imports with and without rename
87
+ * @param {string} curlyBracesImportString - the curly braces part of an ESM import statement
88
+ * @returns {NamedImportResults}
89
+ * an array with the named imports info
90
+ *
91
+ * if not named imports are present in the curly braces block the array will be empty (length 0)
92
+ * @example
93
+ * namedImports('{ A, B, C }')
94
+ * // [
95
+ * // { partialFullMatch: 'A', component: 'A', renamedTo: undefined},
96
+ * // { partialFullMatch: 'B', component: 'B', renamedTo: undefined},
97
+ * // { partialFullMatch: 'C', component: 'C', renamedTo: undefined}
98
+ * // ]
99
+ * namedImports('{ A as X, B as Y, C as Z }')
100
+ * // [
101
+ * // { partialFullMatch: 'A as X', component: 'A', renamedTo: 'X' },
102
+ * // { partialFullMatch: 'B as Y', component: 'B', renamedTo: 'Y' },
103
+ * // { partialFullMatch: 'C as Z', component: 'C', renamedTo: 'Z' }
104
+ * // ]
105
+ * namedImports('{ A, B as Y, C }')
106
+ * // [
107
+ * // { partialFullMatch: 'B as Y', component: 'B', renamedTo: 'Y' }
108
+ * // { partialFullMatch: 'A', component: 'A', renamedTo: undefined},
109
+ * // { partialFullMatch: 'C', component: 'C', renamedTo: undefined}
110
+ * // ]
111
+ */
112
+ const namedImports = (curlyBracesImportString) => {
113
+ // the following two regexps must be used in a previously "between braces" match to work as expected
114
+ const namedWithRenameRegex = /(\w+)\sas\s+(\w+)/gms;
115
+ const namedImportsWithoutRenameRegex =
116
+ /(?<=^[{\s]+)\w+\b(?!\b\s+as\s+\w+)|(?<=,\s+)\b\w+\b(?=\s*,)|(?<!(?:as)|(?:type)\s+)\b\w+(?=\s+})/gms;
117
+ // ----------
118
+ // using matchAll because match doesn't preserve the capturing groups...
119
+ const namedImportWithRenameMatches = [...curlyBracesImportString.matchAll(namedWithRenameRegex)];
120
+ const namedImportWithRename = namedImportWithRenameMatches.map((match) => ({
121
+ partialFullMatch: match[0],
122
+ component: match[1],
123
+ renamedTo: match[2],
124
+ }));
125
+ const namedImportWithoutRenameMatches = [...curlyBracesImportString.matchAll(namedImportsWithoutRenameRegex)];
126
+ const namedImportWithoutRename = namedImportWithoutRenameMatches.map((match) => ({
127
+ partialFullMatch: match[0],
128
+ component: match[0],
129
+ renamedTo: undefined,
130
+ }));
131
+ return [...namedImportWithRename, ...namedImportWithoutRename];
132
+ };
133
+
134
+ // const fileReset = (filePath) => {
135
+ // if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
136
+ // };
137
+
138
+ // const fileUpsert = (filePath, content) => {
139
+ // const fileExists = fs.existsSync(filePath);
140
+ // if (!fileExists) fs.writeFileSync(filePath, content);
141
+ // else fs.appendFileSync(filePath, content);
142
+ // };
143
+
144
+ /**
145
+ * given a file path, it checks if the file contains any dimsum components
146
+ * @param {string} filePath - path to the file to check
147
+ * @returns {FileReport} - a report of the file
148
+ */
149
+ export const getFileReport = (filePath) => {
150
+ const fileAsString = fs.readFileSync(filePath, 'utf8');
151
+ const matches = fileAsString.matchAll(dimsumImportStatementsRegExp);
152
+ const importStatements = [...matches]; // convert iterator to array - matchAll returns an iterator...
153
+
154
+ /** @type {FileReport} */
155
+ const fileReport = {
156
+ filePath,
157
+ hasRelationWithDimsum: importStatements.length > 0,
158
+ globalRenameImports: [],
159
+ importedDefaults: [],
160
+ importedNamedComponentsWithoutRename: [],
161
+ importedNamedComponentsWithRename: [],
162
+ };
163
+
164
+ importStatements.forEach((match) => {
165
+ const fullMatch = match[0];
166
+ const importedComponent = match[1];
167
+ const packageName = match[2];
168
+ const importAllAsInfo = renameAllAs(fullMatch);
169
+ const defaultImportInfo = defaultImport(fullMatch);
170
+ const partialNamedInfoData = namedImports(importedComponent);
171
+ if (importAllAsInfo) fileReport.globalRenameImports.push({ ...importAllAsInfo, packageName, fullMatch });
172
+ if (defaultImportInfo) fileReport.importedDefaults.push({ ...defaultImportInfo, packageName, fullMatch });
173
+ if (partialNamedInfoData.length !== 0)
174
+ partialNamedInfoData.forEach((partialNamedInfo) => {
175
+ const namedImportItem = {
176
+ component: partialNamedInfo.component,
177
+ packageName,
178
+ fullMatch,
179
+ };
180
+ if (partialNamedInfo.renamedTo)
181
+ fileReport.importedNamedComponentsWithRename.push({
182
+ ...namedImportItem,
183
+ renamedTo: partialNamedInfo.renamedTo,
184
+ });
185
+ else fileReport.importedNamedComponentsWithoutRename.push(namedImportItem);
186
+ });
187
+ });
188
+
189
+ return fileReport;
190
+ };
191
+ /**
192
+ * given a datum and a file path and options, generates a csv row
193
+ */
194
+ const generateCSVRow = (datum, filePath, options) => {
195
+ // "Org(?),Imported Component,Repository(?),Package Name,Renamed Alias,File Path
196
+ const { importedComponent, packageName, renamedAs } = datum;
197
+ return `${options.org ? `${options.org},` : ''}${importedComponent},${
198
+ options.repo ? `${options.repo},` : ''
199
+ }${packageName},${renamedAs},${filePath}`;
200
+ };
201
+
202
+ /**
203
+ * given a file report, generates related csv rows (one or more)
204
+ * @param {FileReport} fileReport - the file report to generate the csv rows from
205
+ * @returns {string[]} - the csv rows flattened per relevant columns distinguishers
206
+ *
207
+ */
208
+ const generateCSVRows = (fileReport, options) => {
209
+ const {
210
+ filePath,
211
+ hasRelationWithDimsum,
212
+ globalRenameImports,
213
+ importedDefaults,
214
+ importedNamedComponentsWithoutRename,
215
+ importedNamedComponentsWithRename,
216
+ } = fileReport;
217
+ const csvRows = [];
218
+ if (hasRelationWithDimsum) {
219
+ // opinionated way to describe a global rename import in the csv
220
+ // the opinion is: Imported Component: *, Renamed Alias: <the renamed alias>
221
+ if (globalRenameImports.length > 0) {
222
+ globalRenameImports.forEach((globalRenameImport) => {
223
+ csvRows.push(
224
+ generateCSVRow(
225
+ {
226
+ importedComponent: '*',
227
+ packageName: globalRenameImport.packageName,
228
+ renamedAs: globalRenameImport.renamedAs,
229
+ },
230
+ filePath,
231
+ options,
232
+ ),
233
+ );
234
+ });
235
+ }
236
+ // opinionated way to describe a default import in the csv
237
+ // the opinion is: Imported Component: 'default', Renamed Alias: <the renamed alias>
238
+ if (importedDefaults.length > 0) {
239
+ importedDefaults.forEach((importedDefault) => {
240
+ csvRows.push(
241
+ generateCSVRow(
242
+ {
243
+ importedComponent: 'default',
244
+ packageName: importedDefault.packageName,
245
+ renamedAs: importedDefault.renamedAs,
246
+ },
247
+ filePath,
248
+ options,
249
+ ),
250
+ );
251
+ });
252
+ }
253
+ // opinionated way to describe a named import that wasn't renamed in the csv
254
+ // the opinion is: Imported Component: <the named import>, Renamed Alias: ''
255
+ if (importedNamedComponentsWithoutRename.length > 0) {
256
+ importedNamedComponentsWithoutRename.forEach((importedNamedComponent) => {
257
+ csvRows.push(
258
+ generateCSVRow(
259
+ {
260
+ importedComponent: importedNamedComponent.component,
261
+ packageName: importedNamedComponent.packageName,
262
+ renamedAs: '',
263
+ },
264
+ filePath,
265
+ options,
266
+ ),
267
+ );
268
+ });
269
+ }
270
+
271
+ if (importedNamedComponentsWithRename.length > 0) {
272
+ importedNamedComponentsWithRename.forEach((importedNamedComponent) => {
273
+ csvRows.push(
274
+ generateCSVRow(
275
+ {
276
+ importedComponent: importedNamedComponent.component,
277
+ packageName: importedNamedComponent.packageName,
278
+ renamedAs: importedNamedComponent.renamedTo,
279
+ },
280
+ filePath,
281
+ options,
282
+ ),
283
+ );
284
+ });
285
+ }
286
+ }
287
+ return csvRows;
288
+ };
289
+ /**
290
+ * generates a report of components usage
291
+ * @param {object} options - options *
292
+ * @param {string} options.outputPath - path to the .csv file to write the report to
293
+ * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
294
+ * @param {string} options.gitIgnorePath - path to the .gitignore file
295
+ * @param {string} options.org - optional org, if present an extra column will be added to the report
296
+ * @param {string} options.repo - optional repo, if present an extra column will be added to the report
297
+ * @param {boolean} options.debug - debug flag
298
+ * @param {boolean} options.append - whether to append to the file or override it
299
+ *
300
+ * @returns {string[]} - the csv rows, always starting with the header
301
+ */
302
+ export const createCSVReport = (options) => {
303
+ const filesToParse = filePathsWithGitIgnore(options);
304
+
305
+ let csvRows = [
306
+ generateCSVRow(
307
+ { importedComponent: 'Imported Component', packageName: 'Package Name', renamedAs: 'Renamed Alias' },
308
+ 'File Path',
309
+ { ...options, org: options.org ? 'Org' : undefined, repo: options.repo ? 'Repository' : undefined },
310
+ ),
311
+ ];
312
+
313
+ filesToParse.forEach((filePath) => {
314
+ const fileReport = getFileReport(filePath);
315
+ const fileCSVRows = generateCSVRows(fileReport, options);
316
+ csvRows.push(...fileCSVRows);
317
+ });
318
+
319
+ return csvRows;
320
+ };
@@ -4,6 +4,7 @@ import { checkMissingPackages } from './check-missing-packages/index.mjs';
4
4
  import { checkDeprecatedPackages } from './check-deprecated-packages/index.mjs';
5
5
  import { helpMigrateToV3 } from './help-migrate-to-v3/index.mjs';
6
6
  import { deprecatedComponentsUsageReport } from './deprecated-components-usage-report/index.mjs';
7
+ import { componentsUsageReport } from './components-usage-report/index.mjs';
7
8
  import { COMMANDS } from '../commands.mjs';
8
9
 
9
10
  export async function executeCommandsMap(args, options) {
@@ -29,6 +30,9 @@ export async function executeCommandsMap(args, options) {
29
30
  case COMMANDS.DEPRECATED_PACKAGES_USAGE_REPORT:
30
31
  deprecatedComponentsUsageReport(options);
31
32
  break;
33
+ case COMMANDS.COMPONENT_USAGE_REPORT:
34
+ componentsUsageReport(options);
35
+ break;
32
36
  default:
33
37
  break;
34
38
  }
@@ -6,6 +6,7 @@ export const COMMANDS = {
6
6
  CHECK_MISSING_PACKAGES: 'check-missing-packages',
7
7
  HELP_MIGRATE_TO_V3: 'help-migrate-to-v3',
8
8
  DEPRECATED_PACKAGES_USAGE_REPORT: 'deprecated-components-usage-report',
9
+ COMPONENT_USAGE_REPORT: 'components-usage-report',
9
10
  EXIT: 'exit',
10
11
  };
11
12
 
@@ -27,6 +27,7 @@ async function promptForMissingScriptSpecificOptions({ originalOptions, promptOp
27
27
  case COMMANDS.HELP_MIGRATE_TO_V3:
28
28
  scriptQuestions.push(...getHelpMigrateToV3Questions(originalOptions));
29
29
  break;
30
+ case COMMANDS.COMPONENT_USAGE_REPORT:
30
31
  case COMMANDS.DEPRECATED_PACKAGES_USAGE_REPORT:
31
32
  scriptQuestions.push(...getDeprecatedUsageReportQuestions(originalOptions));
32
33
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-codemods",
3
- "version": "3.31.0-next.3",
3
+ "version": "3.31.0-next.4",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Code Mods",
6
6
  "files": [
@@ -47,12 +47,15 @@
47
47
  "typeSafety": false
48
48
  },
49
49
  "scripts": {
50
- "try-codemods": "npx ./",
51
50
  "dev:install": "pnpm --filter {.}... i --no-lockfile",
52
51
  "eslint:fix": "eslint --ext='.js,.jsx,.test.js,.ts,.tsx' --fix --config='../../.eslintrc.js' bin/",
53
- "try-check-deprecated-packages": "npx ./ check-deprecated-packages --cwd=\"test-ables/check-deprecated-packages/with-deprecated\"",
54
- "try-check-deprecated-packages-no-dimsum": "npx ./ check-deprecated-packages --cwd=\"test-ables/check-deprecated-packages/without-dimsum-packages\"",
55
- "try-help-migrate-to-v3": "npx ./ help-migrate-to-v3 --globPattern=\"test-ables/help-migrate-to-v3/**/*.js,./**/*.jsx,./**/*.ts,./**/*.tsx\" --globPatternIgnore=\"**/node_modules/**/*\"",
52
+ "test-codemods": "npx ./",
53
+ "test-check-deprecated-packages": "npx ./ check-deprecated-packages --cwd=\"test-ables/check-deprecated-packages/with-deprecated\"",
54
+ "test-check-deprecated-packages-no-dimsum": "npx ./ check-deprecated-packages --cwd=\"test-ables/check-deprecated-packages/without-dimsum-packages\"",
55
+ "test-components-usage-report": "npx ./ components-usage-report --outputPath=\"./test.csv\" --startingDirPath=\"../../../environments\" --gitIgnorePath=\"../../../.gitignore\"",
56
+ "test-components-usage-report-complete": "npx ./ components-usage-report --outputPath=\"./test.csv\" --startingDirPath=\"../../../environments\" --gitIgnorePath=\"../../../.gitignore\" --repo=\"Dimsum\" --org=\"ICE\"",
57
+ "test-components-usage-report-complete-append": "npx ./ components-usage-report --outputPath=\"./test.csv\" --startingDirPath=\"../../../environments\" --gitIgnorePath=\"../../../.gitignore\" --repo=\"Dimsum2\" --org=\"ICE2\"",
58
+ "test-help-migrate-to-v3": "npx ./ help-migrate-to-v3 --globPattern=\"test-ables/help-migrate-to-v3/**/*.js,./**/*.jsx,./**/*.ts,./**/*.tsx\" --globPatternIgnore=\"**/node_modules/**/*\"",
56
59
  "create-package": "hygen package new",
57
60
  "create-part": "hygen part new",
58
61
  "checkDeps": "exit 0 | echo"