ember-codemod-remove-ember-css-modules 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +222 -0
  3. package/bin/ember-codemod-remove-ember-css-modules.js +34 -0
  4. package/package.json +58 -0
  5. package/src/blueprints/ember-cli/component/javascript.js +7 -0
  6. package/src/blueprints/ember-cli/component/typescript.ts +26 -0
  7. package/src/blueprints/ember-cli/controller/javascript.js +7 -0
  8. package/src/blueprints/ember-cli/controller/typescript.ts +7 -0
  9. package/src/index.js +5 -0
  10. package/src/migration/ember-app/index.js +37 -0
  11. package/src/migration/ember-app/steps/analyze-app.js +155 -0
  12. package/src/migration/ember-app/steps/create-options.js +44 -0
  13. package/src/migration/ember-app/steps/import-styles-in-components.js +43 -0
  14. package/src/migration/ember-app/steps/import-styles-in-routes.js +39 -0
  15. package/src/migration/ember-app/steps/index.js +9 -0
  16. package/src/migration/ember-app/steps/move-stylesheets.js +51 -0
  17. package/src/migration/ember-app/steps/shared/import-styles.js +211 -0
  18. package/src/migration/ember-app/steps/shared/index.js +2 -0
  19. package/src/migration/ember-app/steps/shared/update-templates.js +437 -0
  20. package/src/migration/ember-app/steps/update-app-app-js.js +64 -0
  21. package/src/migration/ember-app/steps/update-component-templates.js +28 -0
  22. package/src/migration/ember-app/steps/update-package-json.js +58 -0
  23. package/src/migration/ember-app/steps/update-route-templates.js +22 -0
  24. package/src/migration/index.js +1 -0
  25. package/src/utils/abstract-syntax-tree/handlebars.js +20 -0
  26. package/src/utils/abstract-syntax-tree/javascript.js +109 -0
  27. package/src/utils/abstract-syntax-tree.js +2 -0
  28. package/src/utils/blueprints/blueprint-root.js +13 -0
  29. package/src/utils/blueprints/decide-version.js +25 -0
  30. package/src/utils/blueprints/process-template.js +12 -0
  31. package/src/utils/blueprints.js +3 -0
  32. package/src/utils/files/copy-files.js +19 -0
  33. package/src/utils/files/create-files.js +17 -0
  34. package/src/utils/files/find-files.js +28 -0
  35. package/src/utils/files/map-file-paths.js +25 -0
  36. package/src/utils/files/move-files.js +22 -0
  37. package/src/utils/files/remove-directory-if-empty.js +19 -0
  38. package/src/utils/files/remove-files.js +15 -0
  39. package/src/utils/files.js +7 -0
  40. package/src/utils/json/convert-to-map.js +3 -0
  41. package/src/utils/json/convert-to-object.js +5 -0
  42. package/src/utils/json.js +2 -0
  43. package/src/utils/string/ember-cli-string-utils/camelize.js +12 -0
  44. package/src/utils/string/ember-cli-string-utils/classify.js +14 -0
  45. package/src/utils/string/entity-name/classify.js +6 -0
  46. package/src/utils/string/entity-name/double-colonize.js +10 -0
  47. package/src/utils/string/entity-name/parse.js +19 -0
  48. package/src/utils/string.js +5 -0
@@ -0,0 +1,39 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { importStyles } from './shared/index.js';
4
+
5
+ function getBlueprintFilePaths(options) {
6
+ const { project } = options;
7
+
8
+ if (project.hasTypeScript) {
9
+ return ['ember-cli/controller/typescript.ts'];
10
+ }
11
+
12
+ return ['ember-cli/controller/javascript.js'];
13
+ }
14
+
15
+ function getFilePath(options) {
16
+ const { project } = options;
17
+
18
+ return function (entityName) {
19
+ let filePath = join('app/controllers', entityName);
20
+
21
+ if (project.hasTypeScript) {
22
+ filePath += '.ts';
23
+ } else {
24
+ filePath += '.js';
25
+ }
26
+
27
+ return filePath;
28
+ };
29
+ }
30
+
31
+ export function importStylesInRoutes(context, options) {
32
+ const customizations = {
33
+ blueprintFilePaths: getBlueprintFilePaths(options),
34
+ entities: context.routes,
35
+ getFilePath: getFilePath(options),
36
+ };
37
+
38
+ importStyles(customizations, options);
39
+ }
@@ -0,0 +1,9 @@
1
+ export * from './analyze-app.js';
2
+ export * from './create-options.js';
3
+ export * from './import-styles-in-components.js';
4
+ export * from './import-styles-in-routes.js';
5
+ export * from './move-stylesheets.js';
6
+ export * from './update-app-app-js.js';
7
+ export * from './update-component-templates.js';
8
+ export * from './update-package-json.js';
9
+ export * from './update-route-templates.js';
@@ -0,0 +1,51 @@
1
+ import {
2
+ createFiles,
3
+ findFiles,
4
+ mapFilePaths,
5
+ moveFiles,
6
+ } from '../../../utils/files.js';
7
+
8
+ function moveRouteStylesheets(options) {
9
+ const { projectRoot } = options;
10
+
11
+ const filePaths = findFiles('app/styles/**/*.css', {
12
+ cwd: projectRoot,
13
+ ignoreList: ['app/styles/app.css'],
14
+ });
15
+
16
+ const pathMapping = mapFilePaths(filePaths, {
17
+ from: 'app/styles',
18
+ to: 'app/controllers',
19
+ });
20
+
21
+ moveFiles(pathMapping, options);
22
+ }
23
+
24
+ function moveAppCssToAssets(options) {
25
+ const { projectRoot } = options;
26
+
27
+ const filePaths = findFiles('app/styles/app.css', {
28
+ cwd: projectRoot,
29
+ });
30
+
31
+ const pathMapping = mapFilePaths(filePaths, {
32
+ from: 'app/styles',
33
+ to: 'app/assets',
34
+ });
35
+
36
+ moveFiles(pathMapping, options);
37
+
38
+ const fileMapping = new Map([
39
+ [
40
+ 'app/styles/app.css',
41
+ '/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */\n',
42
+ ],
43
+ ]);
44
+
45
+ createFiles(fileMapping, options);
46
+ }
47
+
48
+ export function moveStylesheets(options) {
49
+ moveRouteStylesheets(options);
50
+ moveAppCssToAssets(options);
51
+ }
@@ -0,0 +1,211 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, parse } from 'node:path';
3
+
4
+ import { ASTJavaScript as AST } from '../../../../utils/abstract-syntax-tree.js';
5
+ import {
6
+ blueprintRoot,
7
+ processTemplate,
8
+ } from '../../../../utils/blueprints.js';
9
+ import { createFiles } from '../../../../utils/files.js';
10
+ import { parseEntityName } from '../../../../utils/string.js';
11
+
12
+ function removeTemplateOnlyComponentMethod(file, data) {
13
+ const traverse = AST.traverse(data.hasTypeScript);
14
+
15
+ const ast = traverse(file, {
16
+ visitCallExpression(path) {
17
+ if (path.value.callee.name !== 'templateOnlyComponent') {
18
+ return false;
19
+ }
20
+
21
+ const superClass = AST.builders.identifier('Component');
22
+
23
+ if (data.hasTypeScript) {
24
+ superClass.typeAnnotation = path.value.typeParameters;
25
+ }
26
+
27
+ return AST.builders.classExpression(
28
+ null,
29
+ AST.builders.classBody([
30
+ AST.builders.classProperty(
31
+ AST.builders.identifier(data.__styles__),
32
+ AST.builders.identifier(data.__styles__)
33
+ ),
34
+ ]),
35
+ superClass
36
+ );
37
+ },
38
+
39
+ visitImportDeclaration(path) {
40
+ if (path.value.source.value !== '@ember/component/template-only') {
41
+ return false;
42
+ }
43
+
44
+ const defaultImport = path.value.specifiers.find(
45
+ (specifier) => specifier.type === 'ImportDefaultSpecifier'
46
+ );
47
+
48
+ if (defaultImport?.local?.name !== 'templateOnlyComponent') {
49
+ return false;
50
+ }
51
+
52
+ return AST.builders.importDeclaration(
53
+ [
54
+ AST.builders.importDefaultSpecifier(
55
+ AST.builders.identifier('Component')
56
+ ),
57
+ ],
58
+ AST.builders.literal('@glimmer/component')
59
+ );
60
+ },
61
+ });
62
+
63
+ return AST.print(ast);
64
+ }
65
+
66
+ function importStylesInClass(file, data) {
67
+ const traverse = AST.traverse(data.hasTypeScript);
68
+
69
+ // Find the last import statement
70
+ let lastImportDeclarationPath;
71
+
72
+ const ast = traverse(file, {
73
+ visitImportDeclaration(path) {
74
+ if (!lastImportDeclarationPath) {
75
+ lastImportDeclarationPath = path;
76
+ } else if (path.node.start > lastImportDeclarationPath.node.start) {
77
+ lastImportDeclarationPath = path;
78
+ }
79
+
80
+ return false;
81
+ },
82
+ });
83
+
84
+ // Append the styles import
85
+ const nodes = ast.program.body;
86
+ const index = lastImportDeclarationPath?.name ?? -1;
87
+
88
+ nodes.splice(
89
+ index + 1,
90
+ 0,
91
+ AST.builders.importDeclaration(
92
+ [
93
+ AST.builders.importDefaultSpecifier(
94
+ AST.builders.identifier(data.__styles__)
95
+ ),
96
+ ],
97
+ AST.builders.literal(`./${data.fileName}.css`)
98
+ )
99
+ );
100
+
101
+ return AST.print(ast);
102
+ }
103
+
104
+ function addStylesAsClassProperty(file, data) {
105
+ const traverse = AST.traverse(data.hasTypeScript);
106
+
107
+ const ast = traverse(file, {
108
+ visitClassDeclaration(path) {
109
+ const { body } = path.node.body;
110
+
111
+ const nodesToAdd = [
112
+ AST.builders.classProperty(
113
+ AST.builders.identifier(data.__styles__),
114
+ AST.builders.identifier(data.__styles__)
115
+ ),
116
+ ];
117
+
118
+ if (body.length > 0) {
119
+ nodesToAdd.push(AST.builders.noop());
120
+ }
121
+
122
+ body.unshift(...nodesToAdd);
123
+
124
+ return false;
125
+ },
126
+ });
127
+
128
+ return AST.print(ast);
129
+ }
130
+
131
+ function createClass(customizations, options) {
132
+ const { blueprintFilePaths, entity, filePath } = customizations;
133
+
134
+ const fileMapping = new Map(
135
+ blueprintFilePaths.map((blueprintFilePath) => {
136
+ const blueprintFile = readFileSync(
137
+ join(blueprintRoot, blueprintFilePath),
138
+ 'utf8'
139
+ );
140
+
141
+ const file = processTemplate(blueprintFile, {
142
+ entity,
143
+ options,
144
+ });
145
+
146
+ return [filePath, file];
147
+ })
148
+ );
149
+
150
+ createFiles(fileMapping, options);
151
+ }
152
+
153
+ function updateClass(customizations, options) {
154
+ const { filePath } = customizations;
155
+ const { __styles__, projectRoot } = options;
156
+
157
+ const { ext: fileExtension, name: fileName } = parse(filePath);
158
+ const hasTypeScript = fileExtension === '.ts';
159
+
160
+ let file = readFileSync(join(projectRoot, filePath), 'utf8');
161
+
162
+ try {
163
+ file = removeTemplateOnlyComponentMethod(file, {
164
+ __styles__,
165
+ hasTypeScript,
166
+ });
167
+
168
+ file = importStylesInClass(file, {
169
+ __styles__,
170
+ fileName,
171
+ hasTypeScript,
172
+ });
173
+
174
+ file = addStylesAsClassProperty(file, {
175
+ __styles__,
176
+ hasTypeScript,
177
+ });
178
+
179
+ const fileMapping = new Map([[filePath, file]]);
180
+
181
+ createFiles(fileMapping, options);
182
+ } catch (e) {
183
+ console.warn(
184
+ `WARNING: updateClass could not update \`${filePath}\`. Please update the file manually. (${e.message})\n`
185
+ );
186
+ }
187
+ }
188
+
189
+ export function importStyles(customizations, options) {
190
+ const { blueprintFilePaths, entities, getFilePath } = customizations;
191
+
192
+ for (const [entityName, extensions] of entities) {
193
+ const hasClass = extensions.has('.js') || extensions.has('.ts');
194
+ const hasStylesheet = extensions.has('.css');
195
+
196
+ if (!hasStylesheet) {
197
+ continue;
198
+ }
199
+
200
+ const entity = parseEntityName(entityName);
201
+ const filePath = getFilePath(entityName);
202
+
203
+ if (!hasClass) {
204
+ createClass({ blueprintFilePaths, entity, filePath }, options);
205
+
206
+ continue;
207
+ }
208
+
209
+ updateClass({ filePath }, options);
210
+ }
211
+ }
@@ -0,0 +1,2 @@
1
+ export * from './import-styles.js';
2
+ export * from './update-templates.js';