@elliemae/ds-codemods 3.80.0-next.2 → 3.80.0-next.20

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 (22) hide show
  1. package/bin/cli/code-mods/cli.mjs +6 -0
  2. package/bin/cli/code-mods/command-arguments/getLegacyDeprecationQuestions.mjs +26 -0
  3. package/bin/cli/code-mods/command-logics/deprecated-components-usage-report/index.mjs +7 -2
  4. package/bin/cli/code-mods/command-logics/deprecated-components-usage-report/utils.mjs +68 -5
  5. package/bin/cli/code-mods/command-logics/execute-commands-map.mjs +8 -4
  6. package/bin/cli/code-mods/command-logics/{legacy-deprecation-26-3 → legacy-deprecation/26-3}/packageJsonReplaceLogic.mjs +9 -7
  7. package/bin/cli/code-mods/command-logics/{legacy-deprecation-26-3/index.mjs → legacy-deprecation/26-3/run26dot3MigrationLogic.mjs} +3 -3
  8. package/bin/cli/code-mods/command-logics/{legacy-deprecation-27-1 → legacy-deprecation/27-1}/packageJsonReplaceLogic.mjs +9 -7
  9. package/bin/cli/code-mods/command-logics/{legacy-deprecation-27-1/index.mjs → legacy-deprecation/27-1/run27dot1MigrationLogic.mjs} +3 -3
  10. package/bin/cli/code-mods/command-logics/legacy-deprecation/27-2/legacy27dot2ImportMap.mjs +44 -0
  11. package/bin/cli/code-mods/command-logics/legacy-deprecation/27-2/packageJsonReplaceLogic.mjs +126 -0
  12. package/bin/cli/code-mods/command-logics/legacy-deprecation/27-2/run27dot2MigrationLogic.mjs +52 -0
  13. package/bin/cli/code-mods/command-logics/legacy-deprecation/AGENTS.md +67 -0
  14. package/bin/cli/code-mods/command-logics/legacy-deprecation/CLAUDE.md +14 -0
  15. package/bin/cli/code-mods/command-logics/legacy-deprecation/index.mjs +58 -0
  16. package/bin/cli/code-mods/commands.mjs +1 -0
  17. package/bin/cli/code-mods/inquirer-questions-prompter.mjs +4 -0
  18. package/bin/cli/constants/deprecatedPackages.mjs +24 -1
  19. package/bin/cli/constants/index.mjs +1 -0
  20. package/package.json +1 -1
  21. /package/bin/cli/code-mods/command-logics/{legacy-deprecation-26-3 → legacy-deprecation/26-3}/legacy26dot3ImportMap.mjs +0 -0
  22. /package/bin/cli/code-mods/command-logics/{legacy-deprecation-27-1 → legacy-deprecation/27-1}/legacy27dot1ImportMap.mjs +0 -0
@@ -13,7 +13,11 @@ function parseArgumentsIntoOptions(rawArgs) {
13
13
  '--ignoreFilesGlobPattern': String,
14
14
  '--outputPath': String,
15
15
  '--startingDirPath': String,
16
+ '--filesPath': String, // CLI flag, not available as a question, defaults to --startingDirPath if not passed.
16
17
  '--gitIgnorePath': String,
18
+ // CLI flag, not available as a question - comma separated (E.G. "26.3,27.1") or "all" (default),
19
+ // legacy-deprecation command only.
20
+ '--migrations': String,
17
21
  // CLI flag, not available as a question -> Boolean are not supported by the combo of arg and (dynamic) inquirer.
18
22
  // it's impossible to determine if the user has passed the flag or not because
19
23
  // for arg Boolean are either true or undefined.
@@ -36,12 +40,14 @@ function parseArgumentsIntoOptions(rawArgs) {
36
40
  ignoreFilesGlobPattern: args['--ignoreFilesGlobPattern'],
37
41
  outputPath: args['--outputPath'],
38
42
  startingDirPath: args['--startingDirPath'],
43
+ filesPath: args['--filesPath'], // CLI flag, not available as a question.
39
44
  debug: args['--debug'], // CLI flag, not available as a question.
40
45
  fileExtensions: args['--fileExtensions'], // CLI flag, not available as a question.
41
46
  append: args['--append'], // CLI flag, not available as a question.
42
47
  org: args['--org'], // CLI flag, not available as a question.
43
48
  repo: args['--repo'], // CLI flag, not available as a question.
44
49
  gitIgnorePath: args['--gitIgnorePath'],
50
+ migrations: args['--migrations'], // CLI flag, not available as a question.
45
51
  script: args._[0],
46
52
  };
47
53
  }
@@ -0,0 +1,26 @@
1
+ export const getLegacyDeprecationQuestions = (originalOptions) => {
2
+ const extraOptions = [];
3
+ const { startingDirPath, gitIgnorePath } = originalOptions;
4
+ if (!startingDirPath && startingDirPath !== '') {
5
+ extraOptions.push({
6
+ type: 'input',
7
+ name: 'startingDirPath',
8
+ message: 'Please provide the project root directory (where node_modules folder lives)',
9
+ default: './',
10
+ });
11
+ }
12
+ if (!gitIgnorePath && gitIgnorePath !== '') {
13
+ extraOptions.push({
14
+ type: 'input',
15
+ name: 'gitIgnorePath',
16
+ message:
17
+ 'Please provide the path to the .gitignore file' +
18
+ '(if a non-matching path is provided, node_modules, dist and build will be ignored)',
19
+ default: './.gitignore',
20
+ });
21
+ }
22
+ // --debug is a CLI flag, not available as a question.
23
+ // --migrations is a CLI flag, not available as a question - defaults to "all" when not passed.
24
+
25
+ return extraOptions;
26
+ };
@@ -8,6 +8,9 @@ import { generateCsvRows, checkFileForDeprecatedComponents } from './utils.mjs';
8
8
  * @param {object} options - options *
9
9
  * @param {string} options.outputPath - path to the .csv file to write the report to
10
10
  * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
11
+ * @param {string} [options.filesPath] - optional, CLI-only (not prompted for) path to scan for files to check,
12
+ * in case you want it to differ from startingDirPath (E.G. only checking a subfolder or a single file). Defaults
13
+ * to startingDirPath, i.e. today's behavior of scanning the whole project root, if not provided.
11
14
  * @param {string} options.gitIgnorePath - path to the .gitignore file
12
15
  * @param {string} options.org - optional org, if present an extra column will be added to the report
13
16
  * @param {string} options.repo - optional repo, if present an extra column will be added to the report
@@ -17,9 +20,11 @@ import { generateCsvRows, checkFileForDeprecatedComponents } from './utils.mjs';
17
20
  * @returns {void}
18
21
  */
19
22
  export const deprecatedComponentsUsageReport = (options) => {
20
- const { outputPath, append } = options;
23
+ const { outputPath, append, filesPath, startingDirPath } = options;
21
24
 
22
- const filesToParse = filePathsWithGitIgnore(options);
25
+ // filesPath lets a CLI-savvy caller scan a narrower path than the project root used for npm ls -
26
+ // if not provided we fall back to startingDirPath, i.e. today's behavior, unchanged.
27
+ const filesToParse = filePathsWithGitIgnore({ ...options, startingDirPath: filesPath || startingDirPath });
23
28
  // we have a lot more info than the one we use in the csv,
24
29
  // in case we want to expand it in the future we can store it in the map...
25
30
  // const problematicFiles = new Map();
@@ -2,6 +2,7 @@
2
2
  import fs from 'fs';
3
3
  import {
4
4
  DISMISSED_BEFORE_DEPRECATION_FLOWS_EXISTED,
5
+ DISMISSED_UNFUNDED_CASE_BY_CASE,
5
6
  DISMISSED_WITH_EXAMPLE,
6
7
  LEGACY_WITH_NEW_LARGE_MIGRATION_EFFORT,
7
8
  LEGACY_WITH_NEW_MEDIUM_MIGRATION_EFFORT,
@@ -21,6 +22,7 @@ import { dimsumImportStatementsRegExp } from './regexp.mjs';
21
22
  * @typedef {typeof DISMISSED_WITH_EXAMPLE[number]} dismissedWithExampleEntry
22
23
  * @typedef {typeof NEVER_INTENDED_FOR_EXTERNAL_USAGE[number]} reportingUsageOfInternalOnlyEntry
23
24
  * @typedef {typeof DISMISSED_BEFORE_DEPRECATION_FLOWS_EXISTED[number]} dismissedBeforeDeprecationFlowsExistedEntry
25
+ * @typedef {typeof DISMISSED_UNFUNDED_CASE_BY_CASE[number]} dismissedUnfundedCaseByCaseEntry
24
26
  *
25
27
  * @typedef { object } regexpInfo
26
28
  * @property { string } regexpInfo.fullMatch
@@ -42,12 +44,16 @@ import { dimsumImportStatementsRegExp } from './regexp.mjs';
42
44
  * @typedef { object } matchDeprecatedBeforeDeprecationFlowsExistedEntryPropeties
43
45
  * @property { dismissedBeforeDeprecationFlowsExistedEntry[] } matchDeprecatedBeforeDeprecationFlowsExistedEntryPropeties.matchingEffortsMetainfo
44
46
  *
47
+ * @typedef { object } matchDismissedUnfundedCaseByCaseEntryPropeties
48
+ * @property { dismissedUnfundedCaseByCaseEntry[] } matchDismissedUnfundedCaseByCaseEntryPropeties.matchingEffortsMetainfo
49
+ *
45
50
  * @typedef {matchSharedPropieties & matchWithEffortEntryPropeties} matchWithEffortMetaInfo
46
51
  * @typedef {matchSharedPropieties & matchDeprecatedEntryPropeties} matchDeprecatedWithExample
47
52
  * @typedef {matchSharedPropieties & matchInternalOnlyEntryPropeties} matchInternalOnlyUsedExternally
48
53
  * @typedef {matchSharedPropieties & matchDeprecatedBeforeDeprecationFlowsExistedEntryPropeties} matchDeprecatedBeforeDeprecationFlowsExisted
54
+ * @typedef {matchSharedPropieties & matchDismissedUnfundedCaseByCaseEntryPropeties} matchDismissedUnfundedCaseByCase
49
55
  *
50
- * @typedef {matchWithEffortMetaInfo | matchDeprecatedWithExample | matchInternalOnlyUsedExternally | matchDeprecatedBeforeDeprecationFlowsExisted} matchedEffortMapValue
56
+ * @typedef {matchWithEffortMetaInfo | matchDeprecatedWithExample | matchInternalOnlyUsedExternally | matchDeprecatedBeforeDeprecationFlowsExisted | matchDismissedUnfundedCaseByCase} matchedEffortMapValue
51
57
  *
52
58
  * @typedef {Map<string, matchedEffortMapValue>} matchedEffortMap
53
59
  *
@@ -59,13 +65,18 @@ import { dimsumImportStatementsRegExp } from './regexp.mjs';
59
65
  * @property {matchDeprecatedWithExample} fileReport.deprecatedWithExample
60
66
  * @property {matchInternalOnlyUsedExternally} fileReport.reportingUsageOfInternalOnly
61
67
  * @property {matchDeprecatedBeforeDeprecationFlowsExisted} fileReport.deprecatedBeforeDeprecationFlowsExisted
68
+ * @property {matchDismissedUnfundedCaseByCase} fileReport.dismissedUnfundedCaseByCase
62
69
  *
63
70
  */
64
71
 
65
72
  const subStringAdhocDSMatcher = (str, substr) => {
66
73
  if (str === substr || substr === '*') return true;
74
+ // for @elliemae/ds-* values we anchor the end so "@elliemae/ds-button" does not also match
75
+ // "@elliemae/ds-button-v1" (a different package entirely) - but we still allow it to match
76
+ // "@elliemae/ds-button/some/deep/subpath" (the *same* package, just a deep import), since a
77
+ // subpath continuation always starts with "/" right after the package name, never anything else.
67
78
  const substrRegExp = substr.startsWith('@elliemae/ds-')
68
- ? new RegExp(`@\\belliemae\/ds-${substr.split('@elliemae/ds-')[1]}`, 'g')
79
+ ? new RegExp(`^@\\belliemae\/ds-${substr.split('@elliemae/ds-')[1]}($|\\/)`, 'g')
69
80
  : new RegExp(`\\b${substr}\\b`, 'g');
70
81
  return str.match(substrRegExp) !== null;
71
82
  };
@@ -167,8 +178,24 @@ const checkDeprecatedEffort = (importStatements, deprecatedEffortMap, effortCach
167
178
  // const matchingEffortMetainfoIndx = deprecatedEffortMap.findIndex((effortMetainfo) =>
168
179
  // subStringAdhocDSMatcher(importedComponent, effortMetainfo.component),
169
180
  // );
170
- const matchingDeprecations = deprecatedEffortMap.filter((effortMetainfo) =>
171
- subStringAdhocDSMatcher(importedComponent, effortMetainfo.component),
181
+ // component name alone is not enough - the same component name can have multiple rows across different
182
+ // oldPackage spellings (E.G. DSButton from ds-button-v1 vs ds-legacy-button-v1), we only want the row(s)
183
+ // matching the package actually imported from, otherwise we'd report packages the file never imported.
184
+ const matchingDeprecationsBeforeDedup = deprecatedEffortMap.filter(
185
+ (effortMetainfo) =>
186
+ subStringAdhocDSMatcher(importedComponent, effortMetainfo.component) &&
187
+ subStringAdhocDSMatcher(packageName, effortMetainfo.oldPackage),
188
+ );
189
+ // some entries share the exact same component + oldPackage pair (E.G. DSSlider from ds-slider pointing at
190
+ // two different newPackage suggestions) - that is still one real match on the file, so we only want to
191
+ // report it once, not once per row.
192
+ const matchingDeprecations = matchingDeprecationsBeforeDedup.filter(
193
+ (effortMetainfo, indx) =>
194
+ matchingDeprecationsBeforeDedup.findIndex(
195
+ (otherEffortMetainfo) =>
196
+ otherEffortMetainfo.component === effortMetainfo.component &&
197
+ otherEffortMetainfo.oldPackage === effortMetainfo.oldPackage,
198
+ ) === indx,
172
199
  );
173
200
  if (matchingDeprecations.length < 1) return;
174
201
  // if (matchingEffortMetainfoIndx === -1) return;
@@ -276,6 +303,31 @@ export const checkDeprecatedBeforeDeprecationFlowExisted = (importStatements) =>
276
303
  return matchedEfforts;
277
304
  };
278
305
 
306
+ /**
307
+ * given a list of import statements, it checks if any of them is importing a component we are not
308
+ * actively investing in, that has no committed replacement, and has no known active consumers
309
+ * @param {string[][]} importStatements - list of import statements to check generated by the regex
310
+ * @returns {Map<string, matchDismissedUnfundedCaseByCase>} - a map of matched statements
311
+ */
312
+ export const checkDismissedUnfundedCaseByCase = (importStatements) => {
313
+ const matchedEfforts = new Map();
314
+ importStatements.forEach((importStatement) => {
315
+ const [fullMatch, importedComponent, packageName] = importStatement;
316
+ const matchingDeprecations = DISMISSED_UNFUNDED_CASE_BY_CASE.filter(
317
+ (effortMetainfo) =>
318
+ subStringAdhocDSMatcher(importedComponent, effortMetainfo.component) &&
319
+ // {DSGroupBox} from 'ds-mobile' is not (yet) deprecated, DSGroupBox is a repeated component name, we need the extra package check
320
+ subStringAdhocDSMatcher(packageName, effortMetainfo.oldPackage),
321
+ );
322
+ if (matchingDeprecations.length < 1) return;
323
+ matchedEfforts.set(fullMatch, {
324
+ regexpInfo: { fullMatch, importedComponent, packageName },
325
+ matchingEffortsMetainfo: matchingDeprecations,
326
+ });
327
+ });
328
+ return matchedEfforts;
329
+ };
330
+
279
331
  /**
280
332
  * given a file path, it checks if the file contains any deprecated components
281
333
  * @param {string} filePath - path to the file to check
@@ -291,6 +343,7 @@ export const checkFileForDeprecatedComponents = (filePath) => {
291
343
  const deprecatedWithExample = checkDeprecatedWithExample(importStatements);
292
344
  const reportingUsageOfInternalOnly = checkInternalUsageOnlyIntended(importStatements);
293
345
  const deprecatedBeforeDeprecationFlowsExisted = checkDeprecatedBeforeDeprecationFlowExisted(importStatements);
346
+ const dismissedUnfundedCaseByCase = checkDismissedUnfundedCaseByCase(importStatements);
294
347
 
295
348
  const isFileProblematic =
296
349
  deprecatedSmallEffort.size > 0 ||
@@ -298,7 +351,8 @@ export const checkFileForDeprecatedComponents = (filePath) => {
298
351
  deprecatedLargeEffort.size > 0 ||
299
352
  deprecatedWithExample.size > 0 ||
300
353
  reportingUsageOfInternalOnly.size > 0 ||
301
- deprecatedBeforeDeprecationFlowsExisted.size > 0;
354
+ deprecatedBeforeDeprecationFlowsExisted.size > 0 ||
355
+ dismissedUnfundedCaseByCase.size > 0;
302
356
 
303
357
  return {
304
358
  isFileProblematic,
@@ -308,6 +362,7 @@ export const checkFileForDeprecatedComponents = (filePath) => {
308
362
  deprecatedWithExample,
309
363
  reportingUsageOfInternalOnly,
310
364
  deprecatedBeforeDeprecationFlowsExisted,
365
+ dismissedUnfundedCaseByCase,
311
366
  };
312
367
  };
313
368
 
@@ -357,6 +412,7 @@ export const generateCsvRows = (fileReport, filePath, org, repo, packagesVersion
357
412
  deprecatedWithExample,
358
413
  reportingUsageOfInternalOnly,
359
414
  deprecatedBeforeDeprecationFlowsExisted,
415
+ dismissedUnfundedCaseByCase,
360
416
  } = fileReport;
361
417
  // csv structure = `Legacy Component,${options.org ? 'Org,' : ''}${options.repo ? 'Repository,' : ''}Legacy Package,Filename,File Path,Effort Size,Story Points,Version(s)`;
362
418
  // Legacy Component -> matchingEffortMetainfo.component
@@ -406,5 +462,12 @@ export const generateCsvRows = (fileReport, filePath, org, repo, packagesVersion
406
462
  });
407
463
  });
408
464
 
465
+ dismissedUnfundedCaseByCase.forEach((effort) => {
466
+ const { matchingEffortsMetainfo } = effort;
467
+ matchingEffortsMetainfo.forEach((effort) => {
468
+ csvRows.push(generateCsvRowFromMatchedEffort(effort, filePath, org, repo, packagesVersions));
469
+ });
470
+ });
471
+
409
472
  return csvRows;
410
473
  };
@@ -6,8 +6,9 @@ import { checkDeprecatedPackages } from './check-deprecated-packages/index.mjs';
6
6
  import { helpMigrateToV3 } from './help-migrate-to-v3/index.mjs';
7
7
  import { deprecatedComponentsUsageReport } from './deprecated-components-usage-report/index.mjs';
8
8
  import { componentsUsageReport } from './components-usage-report/index.mjs';
9
- import { legacyDeprecation26dot3 } from './legacy-deprecation-26-3/index.mjs';
10
- import { legacyDeprecation27dot1 } from './legacy-deprecation-27-1/index.mjs';
9
+ import { run26dot3MigrationLogic } from './legacy-deprecation/26-3/run26dot3MigrationLogic.mjs';
10
+ import { run27dot1MigrationLogic } from './legacy-deprecation/27-1/run27dot1MigrationLogic.mjs';
11
+ import { legacyDeprecation } from './legacy-deprecation/index.mjs';
11
12
  import { COMMANDS } from '../commands.mjs';
12
13
 
13
14
  export async function executeCommandsMap(args, options) {
@@ -37,10 +38,13 @@ export async function executeCommandsMap(args, options) {
37
38
  componentsUsageReport(options);
38
39
  break;
39
40
  case COMMANDS.LEGACY_DEPRECATION_26DOT3:
40
- legacyDeprecation26dot3(options);
41
+ run26dot3MigrationLogic(options);
41
42
  break;
42
43
  case COMMANDS.LEGACY_DEPRECATION_27DOT1:
43
- legacyDeprecation27dot1(options);
44
+ run27dot1MigrationLogic(options);
45
+ break;
46
+ case COMMANDS.LEGACY_DEPRECATION:
47
+ legacyDeprecation(options);
44
48
  break;
45
49
  default:
46
50
  break;
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-statements */
2
2
  import fs from 'fs';
3
- import { boldRedStr, yellowStr } from '../../../utils/CLI_COLORS.mjs';
4
- import { filePathsWithGitIgnore } from '../../../utils/filepathsMatchers.mjs';
3
+ import { boldRedStr, yellowStr } from '../../../../utils/CLI_COLORS.mjs';
4
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
5
5
  import { legacy26dot3ImportMap } from './legacy26dot3ImportMap.mjs';
6
6
 
7
7
  /**
@@ -9,7 +9,9 @@ import { legacy26dot3ImportMap } from './legacy26dot3ImportMap.mjs';
9
9
  * parse a package.json, checks the dependencies(dev and peer included) for any legacy component
10
10
  *
11
11
  * if any legacy component is found,
12
- * generates a new content for the file with the legacy component replaced with the new one and version set to "latest"
12
+ * generates a new content for the file with the legacy component replaced with the new one and version set to "*"
13
+ * ("latest" is an npm dist-tag, not a valid semver range - yarn/pnpm expect a real range, so we use the
14
+ * universally-supported wildcard instead)
13
15
  *
14
16
  * @param {string} path - the package.json file path
15
17
  * @param {object} options - options *
@@ -33,7 +35,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
33
35
  Object.keys(parsedContent.dependencies || {}).forEach((oldPackageString) => {
34
36
  if (legacy26dot3ImportMap[oldPackageString]) {
35
37
  const newPackageString = legacy26dot3ImportMap[oldPackageString].matchChange;
36
- newDeps[newPackageString] = 'latest';
38
+ newDeps[newPackageString] = '*';
37
39
  delete newDeps[oldPackageString];
38
40
  depsChanged = true;
39
41
  }
@@ -44,7 +46,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
44
46
  Object.keys(parsedContent.devDependencies || {}).forEach((oldPackageString) => {
45
47
  if (legacy26dot3ImportMap[oldPackageString]) {
46
48
  const newPackageString = legacy26dot3ImportMap[oldPackageString].matchChange;
47
- newDevDeps[newPackageString] = 'latest';
49
+ newDevDeps[newPackageString] = '*';
48
50
  delete newDevDeps[oldPackageString];
49
51
  devDepsChanged = true;
50
52
  }
@@ -54,7 +56,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
54
56
  Object.keys(parsedContent.peerDependencies || {}).forEach((oldPackageString) => {
55
57
  if (legacy26dot3ImportMap[oldPackageString]) {
56
58
  const newPackageString = legacy26dot3ImportMap[oldPackageString].matchChange;
57
- newPeerDeps[newPackageString] = 'latest';
59
+ newPeerDeps[newPackageString] = '*';
58
60
  delete newPeerDeps[oldPackageString];
59
61
  peerDepsChanged = true;
60
62
  }
@@ -104,7 +106,7 @@ export const packageJsonReplaceLogic = (options) => {
104
106
  `To make sure migration is successful manually:
105
107
  - check for any legacy components in your package.json
106
108
  - replace them with the new ones.
107
- - point them to "latest" version, the legacy library only get updated with critical security fixes and very rarely so.
109
+ - point them to "*", the legacy library only get updated with critical security fixes and very rarely so.
108
110
  - regenerate the lock file ( pnpm i --fix-lockfile or equivalent command/flow for your package manager)
109
111
 
110
112
  The legacy components to look for are:`,
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import fse from 'fs-extra';
3
- import { filePathsWithGitIgnore } from '../../../utils/filepathsMatchers.mjs';
3
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
4
4
  import { legacy26dot3ImportMap, executeMapReplacement } from './legacy26dot3ImportMap.mjs';
5
5
  import { packageJsonReplaceLogic } from './packageJsonReplaceLogic.mjs';
6
6
 
@@ -14,7 +14,7 @@ import { packageJsonReplaceLogic } from './packageJsonReplaceLogic.mjs';
14
14
  *
15
15
  * @returns {void}
16
16
  */
17
- export const legacyDeprecation26dot3 = (options) => {
17
+ export const run26dot3MigrationLogic = (options) => {
18
18
  // 1- get all the "raw" code file paths using gitignore blacklist and file extension filter
19
19
  const filesToParse = filePathsWithGitIgnore(options);
20
20
  if (options.debug) console.log('legacy26dot3ImportMap :>> ', legacy26dot3ImportMap);
@@ -49,4 +49,4 @@ export const legacyDeprecation26dot3 = (options) => {
49
49
  });
50
50
  };
51
51
 
52
- export default legacyDeprecation26dot3;
52
+ export default run26dot3MigrationLogic;
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-statements */
2
2
  import fs from 'fs';
3
- import { boldRedStr, yellowStr } from '../../../utils/CLI_COLORS.mjs';
4
- import { filePathsWithGitIgnore } from '../../../utils/filepathsMatchers.mjs';
3
+ import { boldRedStr, yellowStr } from '../../../../utils/CLI_COLORS.mjs';
4
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
5
5
  import { legacy27dot1ImportMap } from './legacy27dot1ImportMap.mjs';
6
6
 
7
7
  /**
@@ -9,7 +9,9 @@ import { legacy27dot1ImportMap } from './legacy27dot1ImportMap.mjs';
9
9
  * parse a package.json, checks the dependencies(dev and peer included) for any legacy component
10
10
  *
11
11
  * if any legacy component is found,
12
- * generates a new content for the file with the legacy component replaced with the new one and version set to "latest"
12
+ * generates a new content for the file with the legacy component replaced with the new one and version set to "*"
13
+ * ("latest" is an npm dist-tag, not a valid semver range - yarn/pnpm expect a real range, so we use the
14
+ * universally-supported wildcard instead)
13
15
  *
14
16
  * @param {string} path - the package.json file path
15
17
  * @param {object} options - options *
@@ -33,7 +35,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
33
35
  Object.keys(parsedContent.dependencies || {}).forEach((oldPackageString) => {
34
36
  if (legacy27dot1ImportMap[oldPackageString]) {
35
37
  const newPackageString = legacy27dot1ImportMap[oldPackageString].matchChange;
36
- newDeps[newPackageString] = 'latest';
38
+ newDeps[newPackageString] = '*';
37
39
  delete newDeps[oldPackageString];
38
40
  depsChanged = true;
39
41
  }
@@ -44,7 +46,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
44
46
  Object.keys(parsedContent.devDependencies || {}).forEach((oldPackageString) => {
45
47
  if (legacy27dot1ImportMap[oldPackageString]) {
46
48
  const newPackageString = legacy27dot1ImportMap[oldPackageString].matchChange;
47
- newDevDeps[newPackageString] = 'latest';
49
+ newDevDeps[newPackageString] = '*';
48
50
  delete newDevDeps[oldPackageString];
49
51
  devDepsChanged = true;
50
52
  }
@@ -54,7 +56,7 @@ const specificPackageJsonReplaceLogic = (path, options) => {
54
56
  Object.keys(parsedContent.peerDependencies || {}).forEach((oldPackageString) => {
55
57
  if (legacy27dot1ImportMap[oldPackageString]) {
56
58
  const newPackageString = legacy27dot1ImportMap[oldPackageString].matchChange;
57
- newPeerDeps[newPackageString] = 'latest';
59
+ newPeerDeps[newPackageString] = '*';
58
60
  delete newPeerDeps[oldPackageString];
59
61
  peerDepsChanged = true;
60
62
  }
@@ -104,7 +106,7 @@ export const packageJsonReplaceLogic = (options) => {
104
106
  `To make sure migration is successful manually:
105
107
  - check for any legacy components in your package.json
106
108
  - replace them with the new ones.
107
- - point them to "latest" version, the legacy library only get updated with critical security fixes and very rarely so.
109
+ - point them to "*", the legacy library only get updated with critical security fixes and very rarely so.
108
110
  - regenerate the lock file ( pnpm i --fix-lockfile or equivalent command/flow for your package manager)
109
111
 
110
112
  The legacy components to look for are:`,
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import fse from 'fs-extra';
3
- import { filePathsWithGitIgnore } from '../../../utils/filepathsMatchers.mjs';
3
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
4
4
  import { legacy27dot1ImportMap, executeMapReplacement } from './legacy27dot1ImportMap.mjs';
5
5
  import { packageJsonReplaceLogic } from './packageJsonReplaceLogic.mjs';
6
6
 
@@ -14,7 +14,7 @@ import { packageJsonReplaceLogic } from './packageJsonReplaceLogic.mjs';
14
14
  *
15
15
  * @returns {void}
16
16
  */
17
- export const legacyDeprecation27dot1 = (options) => {
17
+ export const run27dot1MigrationLogic = (options) => {
18
18
  // 1- get all the "raw" code file paths using gitignore blacklist and file extension filter
19
19
  const filesToParse = filePathsWithGitIgnore(options);
20
20
  if (options.debug) console.log('legacy27dot1ImportMap :>> ', legacy27dot1ImportMap);
@@ -49,4 +49,4 @@ export const legacyDeprecation27dot1 = (options) => {
49
49
  });
50
50
  };
51
51
 
52
- export default legacyDeprecation27dot1;
52
+ export default run27dot1MigrationLogic;
@@ -0,0 +1,44 @@
1
+ /*
2
+ we need an exact match of the imports, we can't do a lose match
3
+ why?
4
+ we have packages that are the old package name with a different append
5
+ E.G. @elliemae/ds-button (legacy) and @elliemae/ds-button-v2 (new)
6
+ we can't replace
7
+ @elliemae/ds-button-v2 to @elliemae/ds-legacy-button-v2
8
+ because @elliemae/ds-button is a substring of @elliemae/ds-button-v2
9
+ how do we solve this?
10
+ key: exact match of the imports
11
+ value:{
12
+ matchChange: string; // the new package name to replace with
13
+ regexp
14
+ regexp: RegExp; // new Regexp(/(['"])(${key})(['"])/)
15
+ }
16
+ this will allow regexp match and replace based on group capture, so we can keep the same quotes as the original import
17
+ E.G. we can replace
18
+ from '@elliemae/ds-truncated-tooltip-text' to '@elliemae/ds-legacy-truncated-tooltip-text'
19
+ but also
20
+ from "@elliemae/ds-truncated-tooltip-text" to "@elliemae/ds-legacy-truncated-tooltip-text"
21
+ */
22
+ export const legacy27dot2ImportMap = {
23
+ '@elliemae/ds-mobile': {
24
+ matchChange: '@elliemae/ds-legacy-mobile',
25
+ regexp: new RegExp(/(from ['"])(@elliemae\/ds-mobile)(['"])/g),
26
+ },
27
+ };
28
+
29
+ export const executeMapReplacement = (fullFileStringContent) => {
30
+ if (typeof fullFileStringContent !== 'string') {
31
+ const error = `expected string argument but received ${typeof fullFileStringContent}`;
32
+ console.error(error);
33
+ throw new Error(error);
34
+ }
35
+ let finalString = fullFileStringContent;
36
+ const matchesFound = [];
37
+ Object.entries(legacy27dot2ImportMap).forEach(([key, { matchChange, regexp }]) => {
38
+ if (regexp.test(finalString)) {
39
+ matchesFound.push(key);
40
+ finalString = finalString.replaceAll(regexp, `$1${matchChange}$3`);
41
+ }
42
+ });
43
+ return { finalString, matchesFound };
44
+ };
@@ -0,0 +1,126 @@
1
+ /* eslint-disable max-statements */
2
+ import fs from 'fs';
3
+ import { boldRedStr, yellowStr } from '../../../../utils/CLI_COLORS.mjs';
4
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
5
+ import { legacy27dot2ImportMap } from './legacy27dot2ImportMap.mjs';
6
+
7
+ /**
8
+ *
9
+ * parse a package.json, checks the dependencies(dev and peer included) for any legacy component
10
+ *
11
+ * if any legacy component is found,
12
+ * generates a new content for the file with the legacy component replaced with the new one and version set to "*"
13
+ * ("latest" is an npm dist-tag, not a valid semver range - yarn/pnpm expect a real range, so we use the
14
+ * universally-supported wildcard instead)
15
+ *
16
+ * @param {string} path - the package.json file path
17
+ * @param {object} options - options *
18
+ * @param {boolean} options.debug - debug flag
19
+ * @returns {string | -1} - the new content (json to string) if any change is needed,
20
+ * or -1 if no change is needed or in case of error
21
+ */
22
+ // eslint-disable-next-line complexity
23
+ const specificPackageJsonReplaceLogic = (path, options) => {
24
+ const fileContent = fs.readFileSync(path, {
25
+ encoding: 'utf8',
26
+ flag: 'r',
27
+ });
28
+ if (!fileContent || typeof fileContent !== 'string') {
29
+ if (options.debug) console.log(yellowStr(`Empty or invalid content in ${path}`));
30
+ return -1;
31
+ }
32
+ const parsedContent = JSON.parse(fileContent);
33
+ const newDeps = { ...parsedContent.dependencies };
34
+ let depsChanged = false;
35
+ Object.keys(parsedContent.dependencies || {}).forEach((oldPackageString) => {
36
+ if (legacy27dot2ImportMap[oldPackageString]) {
37
+ const newPackageString = legacy27dot2ImportMap[oldPackageString].matchChange;
38
+ newDeps[newPackageString] = '*';
39
+ delete newDeps[oldPackageString];
40
+ depsChanged = true;
41
+ }
42
+ });
43
+
44
+ const newDevDeps = { ...parsedContent.devDependencies };
45
+ let devDepsChanged = false;
46
+ Object.keys(parsedContent.devDependencies || {}).forEach((oldPackageString) => {
47
+ if (legacy27dot2ImportMap[oldPackageString]) {
48
+ const newPackageString = legacy27dot2ImportMap[oldPackageString].matchChange;
49
+ newDevDeps[newPackageString] = '*';
50
+ delete newDevDeps[oldPackageString];
51
+ devDepsChanged = true;
52
+ }
53
+ });
54
+ const newPeerDeps = { ...parsedContent.peerDependencies };
55
+ let peerDepsChanged = false;
56
+ Object.keys(parsedContent.peerDependencies || {}).forEach((oldPackageString) => {
57
+ if (legacy27dot2ImportMap[oldPackageString]) {
58
+ const newPackageString = legacy27dot2ImportMap[oldPackageString].matchChange;
59
+ newPeerDeps[newPackageString] = '*';
60
+ delete newPeerDeps[oldPackageString];
61
+ peerDepsChanged = true;
62
+ }
63
+ });
64
+ if (!depsChanged && !devDepsChanged && !peerDepsChanged) {
65
+ if (options.debug) console.log(yellowStr(`No legacy dependencies found in ${path}`));
66
+ return -1;
67
+ }
68
+ const newContent = {
69
+ ...parsedContent,
70
+ ...(depsChanged && { dependencies: newDeps }),
71
+ ...(devDepsChanged && { devDependencies: newDevDeps }),
72
+ ...(peerDepsChanged && { peerDependencies: newPeerDeps }),
73
+ };
74
+
75
+ return JSON.stringify(newContent, null, 2);
76
+ };
77
+
78
+ /**
79
+ * do something similar, but with opinionated "package.json" files and logic
80
+ *
81
+ * in particular:
82
+ * - we get the package.json files (multiple for monorepos?) and we parse them as json.
83
+ * - check the dependencies and devDependencies and peerDependencies for any legacy component
84
+ * (keyof legacy27dot2ImportMap) and replace it with the new one (value of legacy27dot2ImportMap)
85
+ * - if any change is needed, we return an object so that the caller can decide what to do with it
86
+ * (write it or just log it for the user to review)
87
+ * @param {object} options - options *
88
+ * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
89
+ * @param {string} options.gitIgnorePath - path to the .gitignore file
90
+ * @param {boolean} options.debug - debug flag
91
+ * @param {string} options.fileExtensions - comma separated list of file extensions to filter by
92
+ * @returns {object} - an object with the (package.json) file path as key and the new content (json to string) as value,
93
+ * for each package.json file that needs changes
94
+ */
95
+ export const packageJsonReplaceLogic = (options) => {
96
+ // 1- get the package.json files (multiple for monorepos?) paths
97
+ const jsonFilesPaths = filePathsWithGitIgnore({ ...options, fileExtensions: '.json' });
98
+ // the above function doesn't allow for specific filename matching,
99
+ // this is a one-off right now so not extending the function
100
+ // instead we filter the results with a simple string match, to get only the package.json files paths
101
+ const packageJsonFilesPaths = jsonFilesPaths.filter((path) => path.endsWith('package.json'));
102
+ if (packageJsonFilesPaths.length === 0) {
103
+ console.log(boldRedStr('No package.json files! Please check the startingDirPath and gitIgnorePath options.'));
104
+ console.log(
105
+ yellowStr(
106
+ `To make sure migration is successful manually:
107
+ - check for any legacy components in your package.json
108
+ - replace them with the new ones.
109
+ - point them to "*", the legacy library only get updated with critical security fixes and very rarely so.
110
+ - regenerate the lock file ( pnpm i --fix-lockfile or equivalent command/flow for your package manager)
111
+
112
+ The legacy components to look for are:`,
113
+ ),
114
+ );
115
+ console.log(legacy27dot2ImportMap);
116
+ return {};
117
+ }
118
+ const filesToChange = {};
119
+ // 2 - parse, check and generate new content for each package.json file
120
+ packageJsonFilesPaths.forEach((path) => {
121
+ if (options.debug) console.log('path :>> ', path);
122
+ const newContent = specificPackageJsonReplaceLogic(path, options);
123
+ if (newContent !== -1) filesToChange[path] = newContent;
124
+ });
125
+ return filesToChange;
126
+ };
@@ -0,0 +1,52 @@
1
+ import fs from 'fs';
2
+ import fse from 'fs-extra';
3
+ import { filePathsWithGitIgnore } from '../../../../utils/filepathsMatchers.mjs';
4
+ import { legacy27dot2ImportMap, executeMapReplacement } from './legacy27dot2ImportMap.mjs';
5
+ import { packageJsonReplaceLogic } from './packageJsonReplaceLogic.mjs';
6
+
7
+ /**
8
+ * generates a report of deprecated components usage
9
+ * @param {object} options - options *
10
+ * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
11
+ * @param {string} options.gitIgnorePath - path to the .gitignore file
12
+ * @param {boolean} options.debug - debug flag
13
+ * @param {string} options.fileExtensions - comma separated list of file extensions to filter by
14
+ *
15
+ * @returns {void}
16
+ */
17
+ export const run27dot2MigrationLogic = (options) => {
18
+ // 1- get all the "raw" code file paths using gitignore blacklist and file extension filter
19
+ const filesToParse = filePathsWithGitIgnore(options);
20
+ if (options.debug) console.log('legacy27dot2ImportMap :>> ', legacy27dot2ImportMap);
21
+ // 2- for each file, replace the imports from the legacy components with the new ones
22
+ filesToParse.forEach((path) => {
23
+ const fileContent = fs.readFileSync(path, {
24
+ encoding: 'utf8',
25
+ flag: 'r',
26
+ });
27
+ if (fileContent && typeof fileContent === 'string') {
28
+ const { finalString, matchesFound } = executeMapReplacement(fileContent);
29
+ const didChangeSomething = matchesFound.length > 0;
30
+ if (didChangeSomething) {
31
+ if (!options.debug) fse.outputFileSync(path, finalString);
32
+ else {
33
+ console.log(`File: ${path} - import(s) replacement(s) found!`);
34
+ console.log('matchesFound :>> ', matchesFound);
35
+ }
36
+ }
37
+ }
38
+ });
39
+ // 3- do something similar, but with opinionated "package.json" files and logic
40
+ // in particular:
41
+ // - we get the package.json files (multiple for monorepos?) and we parse them as json.
42
+ // - check the dependencies and devDependencies and peerDependencies for any legacy component
43
+ // (keyof legacy27dot2ImportMap) and replace it with the new one (value of legacy27dot2ImportMap)
44
+ const packageJsonPendingReplacements = packageJsonReplaceLogic(options);
45
+ if (options.debug) console.log('packageJsonPendingReplacements :>> ', packageJsonPendingReplacements);
46
+ else
47
+ Object.entries(packageJsonPendingReplacements).forEach(([path, newContent]) => {
48
+ fse.outputFileSync(path, newContent);
49
+ });
50
+ };
51
+
52
+ export default run27dot2MigrationLogic;
@@ -0,0 +1,67 @@
1
+ # `legacy-deprecation` — why this folder exists and how to extend it
2
+
3
+ ## History
4
+
5
+ Every time a package gets pulled out of live's normal publish set and only remains available
6
+ through the `-legacy-` prefixed gateway package, consumers importing the old plain package name
7
+ need a mechanical rename to the `-legacy-` prefixed one (imports + `package.json`
8
+ dependencies/devDependencies/peerDependencies, version set to `"*"`). Each of these used to get its
9
+ own dedicated command and its own top-level folder, named after the dot-release it shipped in:
10
+ `legacy-deprecation-26-3`, `legacy-deprecation-27-1`, then (briefly, within the same session that
11
+ created this folder) `legacy-deprecation-27-2`.
12
+
13
+ That doesn't scale - a new dedicated command, a new folder, a new `commands.mjs` entry, a new
14
+ `execute-commands-map.mjs` case, a new `inquirer-questions-prompter.mjs` case, and a new
15
+ `command-arguments/getXQuestions.mjs` file, every single time one more package needs this exact
16
+ same treatment. So starting with what would have been `27dot2` (the `@elliemae/ds-mobile` ->
17
+ `@elliemae/ds-legacy-mobile` rename), this became a single, all-encompassing command instead:
18
+ **`legacy-deprecation`**, version-agnostic on purpose - "regardless of the release version, give me
19
+ the latest migration(s)".
20
+
21
+ ## Why `26-3/` and `27-1/` still exist as their own subfolders here
22
+
23
+ `legacy-deprecation-26dot3` and `legacy-deprecation-27dot1` are still their own, separately
24
+ registered, separately invocable CLI commands (see `commands.mjs`,
25
+ `execute-commands-map.mjs`, `inquirer-questions-prompter.mjs`, and their own
26
+ `command-arguments/getLegacyDeprecationXXdotYQuestions.mjs` files - none of that changed). They are
27
+ referenced by name in Confluence pages and recorded walkthroughs, so the command names and their
28
+ CLI-visible behavior must keep working exactly as they did before, indefinitely.
29
+
30
+ What moved is only *where their implementation lives on disk*: their import maps,
31
+ `packageJsonReplaceLogic.mjs`, and core logic now live in `26-3/` and `27-1/` in this folder, each
32
+ exposing a `run26dot3MigrationLogic()` / `run27dot1MigrationLogic()` function.
33
+ `execute-commands-map.mjs`'s cases for those two commands call those functions directly - they are
34
+ not wrapped through this folder's `legacyDeprecation()` dispatcher, they're just physically
35
+ scaffolded alongside it now instead of living in their own now-deleted top-level folders.
36
+
37
+ ## How to extend this going forward
38
+
39
+ When another package needs this same `-legacy-` prefix rename treatment, **do not create a new
40
+ dedicated command**. Instead:
41
+
42
+ 1. Add a new subfolder here, named after the dot-release (E.G. `27-3/`).
43
+ 2. Give it its own `legacyXXdotYImportMap.mjs` (exact-match capture-group regex, same technique as
44
+ every other one here) and `packageJsonReplaceLogic.mjs`, mirroring `27-2/`'s exact shape - `27-2/`
45
+ is the reference pattern for a *fully self-contained* subfolder (not a thin wrapper around an
46
+ external, independently-registered command, unlike `26-3/`/`27-1/`).
47
+ 3. Expose a `runXXdotYMigrationLogic()` function from an `index.mjs`-equivalent file in that
48
+ subfolder (name it `runXXdotYMigrationLogic.mjs`, matching the existing convention).
49
+ 4. Wire it into this folder's `index.mjs`: import it, add its dot-release key to
50
+ `AVAILABLE_MIGRATIONS`, and add its conditional call in `legacyDeprecation()`.
51
+
52
+ No new `commands.mjs` entry, no new `execute-commands-map.mjs` case, no new
53
+ `inquirer-questions-prompter.mjs` case, no new CLI flag - the existing `legacy-deprecation` command
54
+ and its `--migrations` flag already cover it.
55
+
56
+ ## The `migrations` selection behavior
57
+
58
+ `options.migrations` is **CLI-flag only, deliberately not prompted for** (see
59
+ `command-arguments/getLegacyDeprecationQuestions.mjs` - it only prompts for `startingDirPath` and
60
+ `gitIgnorePath`). This is unlike `startingDirPath`/`gitIgnorePath`, which fall back to an interactive
61
+ prompt when omitted: picking specific migrations is expected to be rare and deliberate (someone who
62
+ already knows exactly which one(s) they want), so it shouldn't force every interactive run through an
63
+ extra prompt. Pass it as a comma separated string, E.G. `--migrations 26.3,27.1`. It defaults to
64
+ `"all"` when omitted. **`"all"` always wins**, even if the user also passes specific migrations
65
+ alongside it (E.G. `--migrations 26.3,all`) - this is intentional, not a bug: "all" existing in the
66
+ list at all means "run everything," full stop, so a consumer/CI script that defaults to `all` can't
67
+ be accidentally narrowed by a stray extra pick.
@@ -0,0 +1,14 @@
1
+ # `legacy-deprecation` — Claude Code Notes
2
+
3
+ This folder's rationale, its `26-3`/`27-1` vs `27-2`-and-onward folder shapes, and the extension
4
+ process for adding a new release's migration are documented in **`AGENTS.md`** in this same
5
+ directory. Read it before touching anything here.
6
+
7
+ Short version: `legacy-deprecation-26dot3` and `legacy-deprecation-27dot1` are still their own,
8
+ separately registered CLI commands and must keep working unchanged (Confluence/video references) -
9
+ only their implementation files moved into `26-3/`/`27-1/` here. Everything from `27-2/` onward is
10
+ reached exclusively through the single all-encompassing `legacy-deprecation` command (this folder's
11
+ `index.mjs`), selected via the CLI-only `--migrations` flag ("all" by default, not prompted for, and
12
+ "all" always wins). Adding a new dot-release migration means adding a new subfolder here, not a new
13
+ command - see
14
+ AGENTS.md's "How to extend this going forward" section.
@@ -0,0 +1,58 @@
1
+ import { run26dot3MigrationLogic } from './26-3/run26dot3MigrationLogic.mjs';
2
+ import { run27dot1MigrationLogic } from './27-1/run27dot1MigrationLogic.mjs';
3
+ import { run27dot2MigrationLogic } from './27-2/run27dot2MigrationLogic.mjs';
4
+
5
+ /**
6
+ * @type {object}
7
+ * the known migrations this command can run, plus the "all" pseudo-migration that always wins
8
+ * even if the user also fine-picked specific ones alongside it - see AGENTS.md in this folder
9
+ * for why this command exists instead of a new dedicated command per release.
10
+ */
11
+ export const AVAILABLE_MIGRATIONS = {
12
+ ALL: 'all',
13
+ DOT_26_3: '26.3',
14
+ DOT_27_1: '27.1',
15
+ DOT_27_2: '27.2',
16
+ };
17
+
18
+ /**
19
+ * normalizes options.migrations (a comma separated string from the CLI flag, or an array from the
20
+ * inquirer checkbox prompt) into an array of trimmed, non-empty migration keys.
21
+ * @param {string | string[] | undefined} migrations
22
+ * @returns {string[]}
23
+ */
24
+ const normalizeMigrations = (migrations) => {
25
+ if (Array.isArray(migrations)) return migrations.map((migration) => migration.trim()).filter(Boolean);
26
+ if (typeof migrations === 'string') {
27
+ return migrations
28
+ .split(',')
29
+ .map((migration) => migration.trim())
30
+ .filter(Boolean);
31
+ }
32
+ return [];
33
+ };
34
+
35
+ /**
36
+ * runs the requested legacy-to-legacy import/package.json rename migration(s).
37
+ * "all" is the default and always wins, even if specific migrations are also picked alongside it -
38
+ * see AGENTS.md in this folder for the rationale.
39
+ * @param {object} options - options *
40
+ * @param {string} options.startingDirPath - path to the project root directory (where node_modules folder lives)
41
+ * @param {string} options.gitIgnorePath - path to the .gitignore file
42
+ * @param {boolean} options.debug - debug flag
43
+ * @param {string} options.fileExtensions - comma separated list of file extensions to filter by
44
+ * @param {string | string[]} [options.migrations] - which migration(s) to run - "26.3", "27.1", "27.2", or "all"
45
+ * (default). Comma separated when passed as a CLI flag.
46
+ *
47
+ * @returns {void}
48
+ */
49
+ export const legacyDeprecation = (options) => {
50
+ const requestedMigrations = normalizeMigrations(options.migrations);
51
+ const runAll = requestedMigrations.length === 0 || requestedMigrations.includes(AVAILABLE_MIGRATIONS.ALL);
52
+
53
+ if (runAll || requestedMigrations.includes(AVAILABLE_MIGRATIONS.DOT_26_3)) run26dot3MigrationLogic(options);
54
+ if (runAll || requestedMigrations.includes(AVAILABLE_MIGRATIONS.DOT_27_1)) run27dot1MigrationLogic(options);
55
+ if (runAll || requestedMigrations.includes(AVAILABLE_MIGRATIONS.DOT_27_2)) run27dot2MigrationLogic(options);
56
+ };
57
+
58
+ export default legacyDeprecation;
@@ -9,6 +9,7 @@ export const COMMANDS = {
9
9
  COMPONENT_USAGE_REPORT: 'components-usage-report',
10
10
  LEGACY_DEPRECATION_26DOT3: 'legacy-deprecation-26dot3',
11
11
  LEGACY_DEPRECATION_27DOT1: 'legacy-deprecation-27dot1',
12
+ LEGACY_DEPRECATION: 'legacy-deprecation',
12
13
  EXIT: 'exit',
13
14
  };
14
15
 
@@ -8,6 +8,7 @@ import { getMissingPackagesQuestions } from './command-arguments/promptMissingPa
8
8
  import { getDeprecatedUsageReportQuestions } from './command-arguments/getDeprecatedUsageReportQuestions.mjs';
9
9
  import { getLegacyDeprecation26dot3Questions } from './command-arguments/getLegacyDeprecation26dot3Questions.mjs';
10
10
  import { getLegacyDeprecation27dot1Questions } from './command-arguments/getLegacyDeprecation27dot1Questions.mjs';
11
+ import { getLegacyDeprecationQuestions } from './command-arguments/getLegacyDeprecationQuestions.mjs';
11
12
 
12
13
  // eslint-disable-next-line complexity
13
14
  async function promptForMissingScriptSpecificOptions({ originalOptions, promptOptions }) {
@@ -40,6 +41,9 @@ async function promptForMissingScriptSpecificOptions({ originalOptions, promptOp
40
41
  case COMMANDS.LEGACY_DEPRECATION_27DOT1:
41
42
  scriptQuestions.push(...getLegacyDeprecation27dot1Questions(originalOptions));
42
43
  break;
44
+ case COMMANDS.LEGACY_DEPRECATION:
45
+ scriptQuestions.push(...getLegacyDeprecationQuestions(originalOptions));
46
+ break;
43
47
  default:
44
48
  break;
45
49
  }
@@ -492,6 +492,11 @@ export const LEGACY_WITH_NEW_MEDIUM_MIGRATION_EFFORT = [
492
492
  tags:[ SEVERITY_TAGS.A11Y_LIMITATIONS ], storyPoints: "250",
493
493
  },
494
494
 
495
+ { component: 'DSSlider', oldPackage: '@elliemae/ds-legacy-slider',
496
+ newComponent: 'DSSliderV2', newPackage: '@elliemae/ds-legacy-slider-v2',
497
+ tags:[ SEVERITY_TAGS.A11Y_LIMITATIONS ], storyPoints: "250",
498
+ },
499
+
495
500
  { component: 'DSZipCodeSearch', oldPackage: '@elliemae/ds-zipcode-search',
496
501
  newComponent: 'DSAutocomplete', newPackage: '@elliemae/ds-form-layout-autocomplete',
497
502
  tags:[ SEVERITY_TAGS.A11Y_LIMITATIONS ], storyPoints: "250",
@@ -698,7 +703,25 @@ export const NEVER_INTENDED_FOR_EXTERNAL_USAGE = [
698
703
  */
699
704
  /* prettier-ignore */
700
705
  export const DISMISSED_BEFORE_DEPRECATION_FLOWS_EXISTED = [
701
- { component: '*', oldPackage: '@elliemae/ds-imagelibrarymodal',
706
+ { component: '*', oldPackage: '@elliemae/ds-imagelibrarymodal',
707
+ storyPoints: "300",
708
+ },
709
+ ];
710
+ /**
711
+ * for packages we are not actively investing in and have no committed replacement (unlike
712
+ * LEGACY_WITH_NEW_*_MIGRATION_EFFORT) and that are not being "removed in favor of examples" either
713
+ * (unlike DISMISSED_WITH_EXAMPLE) - we still have no known active consumers, so there is nothing to
714
+ * migrate anyone to yet. If/when a real consumer shows up asking "what do I use instead", that becomes
715
+ * an ad-hoc, case-by-case discussion (potentially a dedicated effort for the specific components they
716
+ * need) rather than a pre-baked migration path here.
717
+ * @type {DeprecatedPackageMatch[]}
718
+ */
719
+ /* prettier-ignore */
720
+ export const DISMISSED_UNFUNDED_CASE_BY_CASE = [
721
+ { component: '*', oldPackage: '@elliemae/ds-mobile',
722
+ storyPoints: "300",
723
+ },
724
+ { component: '*', oldPackage: '@elliemae/ds-legacy-mobile',
702
725
  storyPoints: "300",
703
726
  },
704
727
  ];
@@ -6,5 +6,6 @@ export {
6
6
  DISMISSED_WITH_EXAMPLE,
7
7
  NEVER_INTENDED_FOR_EXTERNAL_USAGE,
8
8
  DISMISSED_BEFORE_DEPRECATION_FLOWS_EXISTED,
9
+ DISMISSED_UNFUNDED_CASE_BY_CASE,
9
10
  STORYPOINTS_TO_EFFORT,
10
11
  } from './deprecatedPackages.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-codemods",
3
- "version": "3.80.0-next.2",
3
+ "version": "3.80.0-next.20",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Code Mods",
6
6
  "files": [