ember-codemod-remove-global-styles 0.9.0 → 0.11.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.
- package/README.md +7 -10
- package/dist/bin/ember-codemod-remove-global-styles.js +4 -4
- package/dist/src/index.js +3 -3
- package/dist/src/steps/analyze-project/analyze-components/task.js +15 -0
- package/dist/src/steps/analyze-project/analyze-components/worker.js +11 -0
- package/dist/src/steps/analyze-project/analyze-components.js +14 -18
- package/dist/src/steps/analyze-project/analyze-routes/task.js +15 -0
- package/dist/src/steps/analyze-project/analyze-routes/worker.js +11 -0
- package/dist/src/steps/analyze-project/analyze-routes.js +13 -16
- package/dist/src/steps/analyze-project.js +3 -3
- package/dist/src/steps/create-options.js +2 -2
- package/dist/src/steps/update-project/create-stylesheets.js +1 -3
- package/dist/src/steps/update-project/update-classes/task.js +8 -0
- package/dist/src/steps/update-project/update-classes/worker.js +11 -0
- package/dist/src/steps/update-project/update-classes.js +11 -13
- package/dist/src/steps/update-project/update-templates/task.js +8 -0
- package/dist/src/steps/update-project/update-templates/worker.js +11 -0
- package/dist/src/steps/update-project/update-templates.js +11 -13
- package/dist/src/steps/update-project.js +3 -3
- package/dist/src/utils/analyze-project/get-pattern.js +23 -0
- package/dist/src/utils/analyze-project/index.js +1 -0
- package/dist/src/utils/update-project/update-class.js +1 -2
- package/dist/src/utils/update-project/update-template.js +1 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ pnpx ember-codemod-remove-global-styles --src app/assets/app.css
|
|
|
47
47
|
|
|
48
48
|
<details>
|
|
49
49
|
|
|
50
|
-
<summary>Optional: Limit
|
|
50
|
+
<summary>Optional: Limit type of files to consider</summary>
|
|
51
51
|
|
|
52
52
|
By default, the codemod considers components and routes. Pass `--convert` to consider a subset of these.
|
|
53
53
|
|
|
@@ -63,19 +63,16 @@ pnpx ember-codemod-remove-global-styles --convert routes
|
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
|
|
66
|
-
<summary>Optional: Limit
|
|
66
|
+
<summary>Optional: Limit entity to consider</summary>
|
|
67
67
|
|
|
68
|
-
By default, the codemod considers all files and folders for components and
|
|
68
|
+
By default, the codemod considers all files and folders for components, routes, and tests. Pass `--entity` to limit the search to 1 entity and its sub-entities (if any). You may use curly braces to specify multiple entities.
|
|
69
69
|
|
|
70
70
|
```sh
|
|
71
|
-
# `ui`
|
|
72
|
-
pnpx ember-codemod-remove-global-styles --
|
|
71
|
+
# `ui/form` only
|
|
72
|
+
pnpx ember-codemod-remove-global-styles --entity ui/form
|
|
73
73
|
|
|
74
|
-
# `
|
|
75
|
-
pnpx ember-codemod-remove-global-styles --
|
|
76
|
-
|
|
77
|
-
# `route1` and `route2` folders only (search `app/templates/{route1,route2}`)
|
|
78
|
-
pnpx ember-codemod-remove-global-styles --convert routes --folder "{route1,route2}"
|
|
74
|
+
# `route1` and `route2` only
|
|
75
|
+
pnpx ember-codemod-remove-global-styles --convert routes --entity "{route1,route2}"
|
|
79
76
|
```
|
|
80
77
|
|
|
81
78
|
</details>
|
|
@@ -12,8 +12,8 @@ const argv = yargs(hideBin(process.argv))
|
|
|
12
12
|
describe: 'Which type of files to consider',
|
|
13
13
|
type: 'array',
|
|
14
14
|
})
|
|
15
|
-
.option('
|
|
16
|
-
describe: 'Which
|
|
15
|
+
.option('entity', {
|
|
16
|
+
describe: 'Which entity to consider',
|
|
17
17
|
type: 'string',
|
|
18
18
|
})
|
|
19
19
|
.option('root', {
|
|
@@ -29,8 +29,8 @@ const argv = yargs(hideBin(process.argv))
|
|
|
29
29
|
const DEFAULT_FOR_CONVERT = ['components', 'routes'];
|
|
30
30
|
const codemodOptions = {
|
|
31
31
|
convert: new Set(argv['convert'] ?? DEFAULT_FOR_CONVERT),
|
|
32
|
-
|
|
32
|
+
entity: argv['entity'],
|
|
33
33
|
projectRoot: argv['root'] ?? process.cwd(),
|
|
34
34
|
src: argv['src'],
|
|
35
35
|
};
|
|
36
|
-
runCodemod(codemodOptions);
|
|
36
|
+
void runCodemod(codemodOptions);
|
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { analyzeProject, createOptions, updateProject } from './steps/index.js';
|
|
2
|
-
export function runCodemod(codemodOptions) {
|
|
2
|
+
export async function runCodemod(codemodOptions) {
|
|
3
3
|
const options = createOptions(codemodOptions);
|
|
4
|
-
const project = analyzeProject(options);
|
|
5
|
-
updateProject(project, options);
|
|
4
|
+
const project = await analyzeProject(options);
|
|
5
|
+
await updateProject(project, options);
|
|
6
6
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getEntityData } from '../get-entity-data.js';
|
|
4
|
+
export function task(filePath, classNameToStyles, options) {
|
|
5
|
+
const { projectRoot } = options;
|
|
6
|
+
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
7
|
+
const entityData = getEntityData(file, {
|
|
8
|
+
classNameToStyles,
|
|
9
|
+
isHbs: filePath.endsWith('.hbs'),
|
|
10
|
+
});
|
|
11
|
+
if (entityData.localStyles.length === 0) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return [filePath, entityData];
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { runTask } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './task.js';
|
|
4
|
+
const { datasets } = workerData;
|
|
5
|
+
runTask(task, datasets)
|
|
6
|
+
.then((result) => {
|
|
7
|
+
parentPort?.postMessage(result);
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { findFiles } from '@codemod-utils/files';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
2
|
+
import { parallelize } from '@codemod-utils/threads';
|
|
3
|
+
import { getPatternForComponents } from '../../utils/analyze-project/index.js';
|
|
4
|
+
import { task } from './analyze-components/task.js';
|
|
5
|
+
export async function analyzeComponents(classNameToStyles, options) {
|
|
6
|
+
const { convert, projectRoot } = options;
|
|
9
7
|
if (!convert.components) {
|
|
10
|
-
return
|
|
8
|
+
return new Map();
|
|
11
9
|
}
|
|
12
|
-
const filePaths = findFiles(
|
|
10
|
+
const filePaths = findFiles(getPatternForComponents(options), {
|
|
13
11
|
projectRoot,
|
|
14
12
|
});
|
|
13
|
+
const datasets = [];
|
|
15
14
|
filePaths.forEach((filePath) => {
|
|
16
|
-
|
|
17
|
-
const entityData = getEntityData(file, {
|
|
18
|
-
classNameToStyles,
|
|
19
|
-
isHbs: filePath.endsWith('.hbs'),
|
|
20
|
-
});
|
|
21
|
-
if (entityData.localStyles.length > 0) {
|
|
22
|
-
components.set(filePath, entityData);
|
|
23
|
-
}
|
|
15
|
+
datasets.push([filePath, classNameToStyles, options]);
|
|
24
16
|
});
|
|
25
|
-
|
|
17
|
+
const entries = await parallelize(task, datasets, {
|
|
18
|
+
importMetaUrl: import.meta.url,
|
|
19
|
+
workerFilePath: './analyze-components/worker.js',
|
|
20
|
+
});
|
|
21
|
+
return new Map(entries.filter((entry) => entry !== undefined));
|
|
26
22
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getEntityData } from '../get-entity-data.js';
|
|
4
|
+
export function task(filePath, classNameToStyles, options) {
|
|
5
|
+
const { projectRoot } = options;
|
|
6
|
+
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
7
|
+
const entityData = getEntityData(file, {
|
|
8
|
+
classNameToStyles,
|
|
9
|
+
isHbs: filePath.endsWith('.hbs'),
|
|
10
|
+
});
|
|
11
|
+
if (entityData.localStyles.length === 0) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return [filePath, entityData];
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { runTask } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './task.js';
|
|
4
|
+
const { datasets } = workerData;
|
|
5
|
+
runTask(task, datasets)
|
|
6
|
+
.then((result) => {
|
|
7
|
+
parentPort?.postMessage(result);
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
1
|
import { findFiles } from '@codemod-utils/files';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { parallelize } from '@codemod-utils/threads';
|
|
3
|
+
import { getPatternForRoutes } from '../../utils/analyze-project/index.js';
|
|
4
|
+
import { task } from './analyze-routes/task.js';
|
|
5
|
+
export async function analyzeRoutes(classNameToStyles, options) {
|
|
6
|
+
const { convert, projectRoot } = options;
|
|
8
7
|
const routes = new Map();
|
|
9
8
|
if (!convert.routes) {
|
|
10
9
|
return routes;
|
|
11
10
|
}
|
|
12
|
-
const filePaths = findFiles(
|
|
11
|
+
const filePaths = findFiles(getPatternForRoutes(options), {
|
|
13
12
|
projectRoot,
|
|
14
13
|
});
|
|
14
|
+
const datasets = [];
|
|
15
15
|
filePaths.forEach((filePath) => {
|
|
16
|
-
|
|
17
|
-
const entityData = getEntityData(file, {
|
|
18
|
-
classNameToStyles,
|
|
19
|
-
isHbs: filePath.endsWith('.hbs'),
|
|
20
|
-
});
|
|
21
|
-
if (entityData.localStyles.length > 0) {
|
|
22
|
-
routes.set(filePath, entityData);
|
|
23
|
-
}
|
|
16
|
+
datasets.push([filePath, classNameToStyles, options]);
|
|
24
17
|
});
|
|
25
|
-
|
|
18
|
+
const entries = await parallelize(task, datasets, {
|
|
19
|
+
importMetaUrl: import.meta.url,
|
|
20
|
+
workerFilePath: './analyze-routes/worker.js',
|
|
21
|
+
});
|
|
22
|
+
return new Map(entries.filter((entry) => entry !== undefined));
|
|
26
23
|
}
|
|
@@ -2,12 +2,12 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { getClassNameToStyles } from '../utils/css/index.js';
|
|
4
4
|
import { analyzeComponents, analyzeRoutes } from './analyze-project/index.js';
|
|
5
|
-
export function analyzeProject(options) {
|
|
5
|
+
export async function analyzeProject(options) {
|
|
6
6
|
const { projectRoot, src } = options;
|
|
7
7
|
const stylesheet = readFileSync(join(projectRoot, src), 'utf8');
|
|
8
8
|
const classNameToStyles = getClassNameToStyles(stylesheet);
|
|
9
|
-
const components = analyzeComponents(classNameToStyles, options);
|
|
10
|
-
const routes = analyzeRoutes(classNameToStyles, options);
|
|
9
|
+
const components = await analyzeComponents(classNameToStyles, options);
|
|
10
|
+
const routes = await analyzeRoutes(classNameToStyles, options);
|
|
11
11
|
return {
|
|
12
12
|
components,
|
|
13
13
|
routes,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export function createOptions(codemodOptions) {
|
|
2
|
-
const { convert,
|
|
2
|
+
const { convert, entity, projectRoot, src } = codemodOptions;
|
|
3
3
|
return {
|
|
4
4
|
convert: {
|
|
5
5
|
components: convert.has('components'),
|
|
6
6
|
routes: convert.has('routes'),
|
|
7
7
|
},
|
|
8
|
-
|
|
8
|
+
entity,
|
|
9
9
|
projectRoot,
|
|
10
10
|
src,
|
|
11
11
|
};
|
|
@@ -16,9 +16,7 @@ function logErrors(cssModuleFilePath, errors) {
|
|
|
16
16
|
if (errors.length === 0) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
console.
|
|
20
|
-
console.warn(errors.map((error) => `- ${error}`).join(EOL));
|
|
21
|
-
console.log();
|
|
19
|
+
console.log(`WARNING: ${cssModuleFilePath} may be incorrect.`);
|
|
22
20
|
}
|
|
23
21
|
export function createStylesheets(project, options) {
|
|
24
22
|
const fileMap = new Map();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { updateClass } from '../../../utils/update-project/index.js';
|
|
2
|
+
export function task(templateFilePath, options) {
|
|
3
|
+
const { output, status } = updateClass(templateFilePath, options);
|
|
4
|
+
if (status === 'error') {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
return [output.classFilePath, output.classFile];
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { runTask } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './task.js';
|
|
4
|
+
const { datasets } = workerData;
|
|
5
|
+
runTask(task, datasets)
|
|
6
|
+
.then((result) => {
|
|
7
|
+
parentPort?.postMessage(result);
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { createFiles } from '@codemod-utils/files';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { parallelize } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './update-classes/task.js';
|
|
4
|
+
export async function updateClasses(project, options) {
|
|
5
|
+
const datasets = [];
|
|
5
6
|
project.components.forEach((_data, templateFilePath) => {
|
|
6
|
-
|
|
7
|
-
if (status === 'error') {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
fileMap.set(output.classFilePath, output.classFile);
|
|
7
|
+
datasets.push([templateFilePath, options]);
|
|
11
8
|
});
|
|
12
9
|
project.routes.forEach((_data, templateFilePath) => {
|
|
13
|
-
|
|
14
|
-
if (status === 'error') {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
fileMap.set(output.classFilePath, output.classFile);
|
|
10
|
+
datasets.push([templateFilePath, options]);
|
|
18
11
|
});
|
|
12
|
+
const entries = await parallelize(task, datasets, {
|
|
13
|
+
importMetaUrl: import.meta.url,
|
|
14
|
+
workerFilePath: './update-classes/worker.js',
|
|
15
|
+
});
|
|
16
|
+
const fileMap = new Map(entries.filter((entry) => entry !== undefined));
|
|
19
17
|
createFiles(fileMap, options);
|
|
20
18
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { updateTemplate } from '../../../utils/update-project/index.js';
|
|
2
|
+
export function task(templateFilePath, options) {
|
|
3
|
+
const { output, status } = updateTemplate(templateFilePath, options);
|
|
4
|
+
if (status === 'error') {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
return [templateFilePath, output.templateFile];
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { runTask } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './task.js';
|
|
4
|
+
const { datasets } = workerData;
|
|
5
|
+
runTask(task, datasets)
|
|
6
|
+
.then((result) => {
|
|
7
|
+
parentPort?.postMessage(result);
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { createFiles } from '@codemod-utils/files';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { parallelize } from '@codemod-utils/threads';
|
|
3
|
+
import { task } from './update-templates/task.js';
|
|
4
|
+
export async function updateTemplates(project, options) {
|
|
5
|
+
const datasets = [];
|
|
5
6
|
project.components.forEach((_data, templateFilePath) => {
|
|
6
|
-
|
|
7
|
-
if (status === 'error') {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
fileMap.set(templateFilePath, output.templateFile);
|
|
7
|
+
datasets.push([templateFilePath, options]);
|
|
11
8
|
});
|
|
12
9
|
project.routes.forEach((_data, templateFilePath) => {
|
|
13
|
-
|
|
14
|
-
if (status === 'error') {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
fileMap.set(templateFilePath, output.templateFile);
|
|
10
|
+
datasets.push([templateFilePath, options]);
|
|
18
11
|
});
|
|
12
|
+
const entries = await parallelize(task, datasets, {
|
|
13
|
+
importMetaUrl: import.meta.url,
|
|
14
|
+
workerFilePath: './update-templates/worker.js',
|
|
15
|
+
});
|
|
16
|
+
const fileMap = new Map(entries.filter((entry) => entry !== undefined));
|
|
19
17
|
createFiles(fileMap, options);
|
|
20
18
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createStylesheets, updateClasses, updateTemplates, } from './update-project/index.js';
|
|
2
|
-
export function updateProject(project, options) {
|
|
2
|
+
export async function updateProject(project, options) {
|
|
3
3
|
createStylesheets(project, options);
|
|
4
|
-
updateClasses(project, options);
|
|
5
|
-
updateTemplates(project, options);
|
|
4
|
+
await updateClasses(project, options);
|
|
5
|
+
await updateTemplates(project, options);
|
|
6
6
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { normalizedJoin } from '../files/index.js';
|
|
2
|
+
export function getPatternForComponents(options) {
|
|
3
|
+
const source = 'app';
|
|
4
|
+
const { entity } = options;
|
|
5
|
+
if (entity === undefined) {
|
|
6
|
+
return [`${source}/components/**/*.{gjs,gts,hbs}`];
|
|
7
|
+
}
|
|
8
|
+
return [
|
|
9
|
+
normalizedJoin(source, 'components', `${entity}.{gjs,gts,hbs}`),
|
|
10
|
+
normalizedJoin(source, 'components', entity, '**/*.{gjs,gts,hbs}'),
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
export function getPatternForRoutes(options) {
|
|
14
|
+
const source = 'app';
|
|
15
|
+
const { entity } = options;
|
|
16
|
+
if (entity === undefined) {
|
|
17
|
+
return [`${source}/templates/**/*.{gjs,gts,hbs}`];
|
|
18
|
+
}
|
|
19
|
+
return [
|
|
20
|
+
normalizedJoin(source, 'templates', `${entity}.{gjs,gts,hbs}`),
|
|
21
|
+
normalizedJoin(source, 'templates', entity, '**/*.{gjs,gts,hbs}'),
|
|
22
|
+
];
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './get-pattern.js';
|
|
@@ -35,8 +35,7 @@ export function updateClass(templateFilePath, options) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
console.log(`WARNING: ${classFilePath} could not be updated
|
|
39
|
-
console.log(error.message);
|
|
38
|
+
console.log(`WARNING: ${classFilePath} could not be updated. (${error.message})`);
|
|
40
39
|
return {
|
|
41
40
|
output: undefined,
|
|
42
41
|
status: 'error',
|
|
@@ -31,8 +31,7 @@ export function updateTemplate(templateFilePath, options) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
|
-
console.log(`WARNING: ${templateFilePath} could not be updated
|
|
35
|
-
console.log(error.message);
|
|
34
|
+
console.log(`WARNING: ${templateFilePath} could not be updated. ${error.message}`);
|
|
36
35
|
return {
|
|
37
36
|
output: undefined,
|
|
38
37
|
status: 'error',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-codemod-remove-global-styles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Codemod to localize global styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codemod",
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
"@codemod-utils/ast-template": "^3.0.0",
|
|
29
29
|
"@codemod-utils/ast-template-tag": "^2.0.0",
|
|
30
30
|
"@codemod-utils/files": "^4.0.0",
|
|
31
|
+
"@codemod-utils/threads": "^0.2.1",
|
|
31
32
|
"css-selector-parser": "^3.3.0",
|
|
32
|
-
"postcss": "^8.5.
|
|
33
|
+
"postcss": "^8.5.8",
|
|
33
34
|
"yargs": "^18.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|