@instructure/ui-codemods 11.7.6-snapshot-18 → 11.7.6-snapshot-22

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.
@@ -22,17 +22,87 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import prettier from 'prettier'
25
+ type PrettierOptions = Record<string, unknown>
26
26
 
27
- export default function formatSource(source: string, sourcePath: string) {
28
- let options = null
27
+ /**
28
+ * The subset of the Prettier API this module uses. Both Prettier 2 and 3 are
29
+ * supported: in v2 `format` is synchronous and `resolveConfig` returns a
30
+ * promise, in v3 both are asynchronous. Awaiting covers both shapes.
31
+ */
32
+ type PrettierModule = {
33
+ format: (
34
+ source: string,
35
+ options?: PrettierOptions
36
+ ) => string | Promise<string>
37
+ resolveConfig: (
38
+ path: string
39
+ ) => PrettierOptions | null | Promise<PrettierOptions | null>
40
+ }
41
+
42
+ const PRETTIER = 'prettier'
43
+
44
+ let prettierPromise: Promise<PrettierModule | undefined> | undefined
45
+ let warnedAboutMissingPrettier = false
46
+
47
+ /**
48
+ * jscodeshift registers `@babel/register` for `.mjs` and `.cjs` files too and
49
+ * rewrites their modules to CommonJS. That breaks Prettier 3, whose `index.cjs`
50
+ * loads the ESM `index.mjs` at runtime. Hiding the dynamic import in a
51
+ * `new Function` keeps Babel from compiling it to a `require` call, so Node
52
+ * loads Prettier itself.
53
+ */
54
+ const importModule = new Function('specifier', 'return import(specifier)') as (
55
+ specifier: string
56
+ ) => Promise<Record<string, unknown>>
57
+
58
+ async function importPrettier(): Promise<Record<string, unknown>> {
59
+ try {
60
+ return await importModule(PRETTIER)
61
+ } catch {
62
+ // Test runners that evaluate modules in their own context (Vitest) give
63
+ // `new Function` no dynamic import callback, but do handle a plain import.
64
+ return await import(PRETTIER)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Prettier is an optional peer dependency, so it is resolved from the
70
+ * consumer's `node_modules` and its version is whatever they installed.
71
+ */
72
+ function loadPrettier() {
73
+ if (!prettierPromise) {
74
+ prettierPromise = importPrettier()
75
+ .then((namespace) => {
76
+ const asDefault = namespace.default as PrettierModule | undefined
77
+ return typeof asDefault?.format === 'function'
78
+ ? asDefault
79
+ : (namespace as unknown as PrettierModule)
80
+ })
81
+ .catch(() => undefined)
82
+ }
83
+ return prettierPromise
84
+ }
85
+
86
+ export default async function formatSource(source: string, sourcePath: string) {
87
+ const prettier = await loadPrettier()
88
+ if (!prettier) {
89
+ if (!warnedAboutMissingPrettier) {
90
+ warnedAboutMissingPrettier = true
91
+ console.warn(
92
+ 'Prettier is not installed, the codemod result is not formatted. ' +
93
+ 'Install prettier (v2 or v3) or run the codemod with --usePrettier=false'
94
+ )
95
+ }
96
+ return source
97
+ }
98
+ let options: PrettierOptions | null = null
29
99
  const extension = sourcePath.split('.').pop()
30
100
  let parser = 'babel'
31
101
  if (extension === 'ts' || extension === 'tsx') {
32
102
  parser = 'typescript'
33
103
  }
34
104
  try {
35
- options = prettier.resolveConfig.sync(sourcePath)
105
+ options = await prettier.resolveConfig(sourcePath)
36
106
  if (options) {
37
107
  // Set the parser argument if the consumer did not set one to avoid a console warning
38
108
  options = {
@@ -45,7 +115,7 @@ export default function formatSource(source: string, sourcePath: string) {
45
115
  }
46
116
  let result = source
47
117
  try {
48
- result = prettier.format(
118
+ result = await prettier.format(
49
119
  source,
50
120
  options || {
51
121
  parser: parser,
@@ -49,10 +49,11 @@ export type InstUICodemod = (
49
49
  * written to this file.
50
50
  * @param options.usePrettier if `true` the transformed code will be run through
51
51
  * [Prettier](https://prettier.io/). You can customize this through a [Prettier
52
- * config file](https://prettier.io/docs/configuration.html)
53
- * @returns the modified file as `string` or `null`
52
+ * config file](https://prettier.io/docs/configuration.html). Prettier is an
53
+ * optional peer dependency; both Prettier 2 and 3 work.
54
+ * @returns a promise resolving to the modified file as `string` or to `null`
54
55
  */
55
- const instUICodemodExecutor = (
56
+ const instUICodemodExecutor = async (
56
57
  instUICodemods: InstUICodemod[] | InstUICodemod,
57
58
  file: FileInfo,
58
59
  api: API,
@@ -61,7 +62,7 @@ const instUICodemodExecutor = (
61
62
  usePrettier?: boolean
62
63
  quote?: 'single' | 'double' | 'auto'
63
64
  }
64
- ) => {
65
+ ): Promise<string | null> => {
65
66
  const j = api.jscodeshift.withParser('tsx')
66
67
  const root = j(file.source)
67
68
  let hasModifications = false
@@ -83,7 +84,7 @@ const instUICodemodExecutor = (
83
84
  if (hasModifications) {
84
85
  const shouldUsePrettier = options?.usePrettier !== false
85
86
  return shouldUsePrettier
86
- ? formatSource(root.toSource(), file.path)
87
+ ? await formatSource(root.toSource(), file.path)
87
88
  : root.toSource({ quote: options?.quote ?? 'double' })
88
89
  } else {
89
90
  return null
@@ -29,12 +29,17 @@ import type { InstUICodemod } from './utils/instUICodemodExecutor'
29
29
  /**
30
30
  * Prints a warning if a `CodeEditor` import is found
31
31
  */
32
- const warnCodeEditorRemoved: Transform = (
32
+ const warnCodeEditorRemoved: Transform = async (
33
33
  file,
34
34
  api,
35
35
  options?: { fileName?: string; usePrettier?: boolean }
36
36
  ) => {
37
- return instUICodemodExecutor(warnCodeEditorRemovedCodemod, file, api, options)
37
+ return await instUICodemodExecutor(
38
+ warnCodeEditorRemovedCodemod,
39
+ file,
40
+ api,
41
+ options
42
+ )
38
43
  }
39
44
 
40
45
  const warnCodeEditorRemovedCodemod: InstUICodemod = (
@@ -36,12 +36,12 @@ const TABLE_IMPORT_PATHS = ['@instructure/ui-table', '@instructure/ui']
36
36
  /**
37
37
  * Prints a warning if the `caption` prop is missing from a `<Table />`.
38
38
  */
39
- const warnTableCaptionMissing: Transform = (
39
+ const warnTableCaptionMissing: Transform = async (
40
40
  file,
41
41
  api,
42
42
  options?: { fileName?: string; usePrettier?: boolean }
43
43
  ) => {
44
- return instUICodemodExecutor(
44
+ return await instUICodemodExecutor(
45
45
  warnTableCaptionMissingCodemod,
46
46
  file,
47
47
  api,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-codemods",
3
- "version": "11.7.6-snapshot-18",
3
+ "version": "11.7.6-snapshot-22",
4
4
  "description": "Codemod scripts to help upgrade Instructure UI libraries.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./lib/index.ts",
@@ -13,16 +13,23 @@
13
13
  "bugs": "https://github.com/instructure/instructure-ui/issues",
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
- "jscodeshift": "^17.3.0",
17
- "prettier": "^2.8.8"
16
+ "jscodeshift": "^17.3.0"
17
+ },
18
+ "peerDependencies": {
19
+ "prettier": "^2.8.8 || ^3.0.0"
20
+ },
21
+ "peerDependenciesMeta": {
22
+ "prettier": {
23
+ "optional": true
24
+ }
18
25
  },
19
26
  "devDependencies": {
20
27
  "@types/jscodeshift": "^17.3.0",
21
28
  "@types/node": "24.1.0",
22
- "@types/prettier": "^2.7.3",
23
29
  "ast-types": "^0.16.1",
30
+ "prettier": "3.8.1",
24
31
  "vitest": "^4.1.9",
25
- "@instructure/ui-babel-preset": "11.7.6-snapshot-18"
32
+ "@instructure/ui-babel-preset": "11.7.6-snapshot-22"
26
33
  },
27
34
  "publishConfig": {
28
35
  "access": "public"
@@ -38,9 +45,6 @@
38
45
  "./package.json": "./package.json",
39
46
  "./src/*": "./src/*"
40
47
  },
41
- "//dependency-comments": {
42
- "prettier": "Prettier 3 is async, https://prettier.io/blog/2023/07/05/3.0.0.html#api-1"
43
- },
44
48
  "type": "module",
45
49
  "scripts": {
46
50
  "test:vitest": "vitest --watch=false",