@aerogel/cli 0.0.0-next.0dddfa5a33f1886a059ceb0950867ddcfa86480d → 0.0.0-next.0e2d8a67669c6e544c855fc0bbdb8c845fdfb340
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/dist/aerogel-cli.js +292 -349
- package/dist/aerogel-cli.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +0 -2
- package/src/commands/create.test.ts +0 -23
- package/src/commands/create.ts +4 -8
- package/src/commands/generate-component.test.ts +0 -60
- package/src/commands/generate-component.ts +3 -79
- package/src/commands/install.test.ts +69 -6
- package/src/commands/install.ts +21 -6
- package/src/lib/App.ts +40 -41
- package/src/lib/Editor.ts +0 -1
- package/src/lib/File.mock.ts +6 -0
- package/src/lib/File.ts +32 -2
- package/src/lib/Shell.mock.ts +4 -0
- package/src/lib/utils/paths.ts +4 -0
- package/src/plugins/LocalFirst.ts +9 -0
- package/src/plugins/Plugin.ts +30 -32
- package/src/plugins/Solid.ts +8 -15
- package/src/plugins/Soukai.ts +6 -5
- package/src/testing/setup.ts +6 -0
- package/src/utils/package.ts +23 -0
- package/src/commands/generate-overrides.ts +0 -85
- package/templates/app/.github/workflows/ci.yml +0 -29
- package/templates/app/.gitignore.template +0 -2
- package/templates/app/.nvmrc +0 -1
- package/templates/app/.vscode/launch.json +0 -17
- package/templates/app/.vscode/settings.json +0 -10
- package/templates/app/cypress/cypress.config.ts +0 -14
- package/templates/app/cypress/e2e/app.cy.ts +0 -9
- package/templates/app/cypress/support/e2e.ts +0 -1
- package/templates/app/cypress/tsconfig.json +0 -11
- package/templates/app/index.html +0 -13
- package/templates/app/package.json +0 -66
- package/templates/app/postcss.config.js +0 -6
- package/templates/app/src/App.vue +0 -12
- package/templates/app/src/assets/css/main.css +0 -3
- package/templates/app/src/assets/public/robots.txt +0 -2
- package/templates/app/src/lang/en.yaml +0 -3
- package/templates/app/src/main.test.ts +0 -9
- package/templates/app/src/main.ts +0 -13
- package/templates/app/src/types/globals.d.ts +0 -2
- package/templates/app/src/types/shims.d.ts +0 -7
- package/templates/app/src/types/ts-reset.d.ts +0 -1
- package/templates/app/tailwind.config.js +0 -5
- package/templates/app/tsconfig.json +0 -12
- package/templates/app/vite.config.ts +0 -31
- package/templates/component-button/[component.name].vue +0 -42
- package/templates/component-button-story/[component.name].story.vue +0 -77
- package/templates/component-checkbox/[component.name].vue +0 -34
- package/templates/component-checkbox-story/[component.name].story.vue +0 -63
- package/templates/component-input/[component.name].vue +0 -17
- package/templates/component-input-story/[component.name].story.vue +0 -63
- package/templates/component-story/[component.name].story.vue +0 -7
- package/templates/overrides/components/index.ts +0 -15
- package/templates/overrides/components/overrides/AlertModal.vue +0 -11
- package/templates/overrides/components/overrides/ConfirmModal.vue +0 -20
- package/templates/overrides/components/overrides/ErrorReportModal.vue +0 -35
- package/templates/overrides/components/overrides/LoadingModal.vue +0 -12
- package/templates/overrides/components/overrides/ModalWrapper.vue +0 -22
- package/templates/overrides/components/overrides/SnackbarNotification.vue +0 -34
- package/templates/overrides-story/Overrides.story.vue +0 -86
- package/templates/postcss-pseudo-classes/postcss.config.js +0 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { arrayFrom, stringToSlug } from '@noeldemartin/utils';
|
|
2
2
|
import { Node, SyntaxKind } from 'ts-morph';
|
|
3
3
|
import type { ArrayLiteralExpression, CallExpression, SourceFile } from 'ts-morph';
|
|
4
4
|
|
|
@@ -9,7 +9,6 @@ import Template from '@aerogel/cli/lib/Template';
|
|
|
9
9
|
import { app } from '@aerogel/cli/lib/utils/app';
|
|
10
10
|
import { editFiles, findDescendant } from '@aerogel/cli/lib/utils/edit';
|
|
11
11
|
import { templatePath } from '@aerogel/cli/lib/utils/paths';
|
|
12
|
-
import type { CommandOptions } from '@aerogel/cli/commands/Command';
|
|
13
12
|
|
|
14
13
|
export interface Options {
|
|
15
14
|
button?: boolean;
|
|
@@ -26,52 +25,21 @@ export class GenerateComponentCommand extends Command {
|
|
|
26
25
|
['path', 'Component path (relative to components folder; extension not necessary)'],
|
|
27
26
|
];
|
|
28
27
|
|
|
29
|
-
protected static override options: CommandOptions = {
|
|
30
|
-
button: {
|
|
31
|
-
description: 'Create a custom button',
|
|
32
|
-
type: 'boolean',
|
|
33
|
-
},
|
|
34
|
-
checkbox: {
|
|
35
|
-
description: 'Create a custom checkbox',
|
|
36
|
-
type: 'boolean',
|
|
37
|
-
},
|
|
38
|
-
input: {
|
|
39
|
-
description: 'Create a custom input',
|
|
40
|
-
type: 'boolean',
|
|
41
|
-
},
|
|
42
|
-
story: {
|
|
43
|
-
description: 'Create component story using Histoire',
|
|
44
|
-
type: 'boolean',
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
28
|
private path: string;
|
|
49
|
-
private options: Options;
|
|
50
29
|
|
|
51
|
-
constructor(path: string
|
|
30
|
+
constructor(path: string) {
|
|
52
31
|
super();
|
|
53
32
|
|
|
54
33
|
this.path = path;
|
|
55
|
-
this.options = options;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
protected override async validate(): Promise<void> {
|
|
59
|
-
const components = arrayFilter([this.options.button, this.options.input, this.options.checkbox]).length;
|
|
60
|
-
|
|
61
|
-
if (components > 1) {
|
|
62
|
-
Log.fail('Can only use one of \'button\', \'input\', or \'checkbox\' flags!');
|
|
63
|
-
}
|
|
64
34
|
}
|
|
65
35
|
|
|
66
36
|
protected override async run(): Promise<void> {
|
|
67
37
|
this.assertAerogelOrDirectory('src/components');
|
|
68
|
-
this.assertHistoireInstalled();
|
|
69
38
|
|
|
70
39
|
const files = new Set<string>();
|
|
71
40
|
const [directoryName, componentName] = this.parsePathComponents();
|
|
72
41
|
|
|
73
42
|
await this.createComponent(directoryName, componentName, files);
|
|
74
|
-
await this.createStory(directoryName, componentName, files);
|
|
75
43
|
await this.declareComponents();
|
|
76
44
|
|
|
77
45
|
const filesList = arrayFrom(files)
|
|
@@ -81,33 +49,13 @@ export class GenerateComponentCommand extends Command {
|
|
|
81
49
|
Log.info(`${componentName} component created successfully! The following files were created:\n\n${filesList}`);
|
|
82
50
|
}
|
|
83
51
|
|
|
84
|
-
protected assertHistoireInstalled(): void {
|
|
85
|
-
if (!this.options.story) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!File.contains('package.json', '"histoire"') && !File.contains('package.json', '"@aerogel/histoire"')) {
|
|
90
|
-
Log.fail(`
|
|
91
|
-
Histoire is not installed yet! You can install it running:
|
|
92
|
-
npx gel install histoire
|
|
93
|
-
`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
52
|
protected async createComponent(directoryName: string, componentName: string, files: Set<string>): Promise<void> {
|
|
98
53
|
await Log.animate('Creating component', async () => {
|
|
99
54
|
if (File.exists(`src/components/${this.path}.vue`)) {
|
|
100
55
|
Log.fail(`${this.path} component already exists!`);
|
|
101
56
|
}
|
|
102
57
|
|
|
103
|
-
const
|
|
104
|
-
? 'component-input'
|
|
105
|
-
: this.options.button
|
|
106
|
-
? 'component-button'
|
|
107
|
-
: this.options.checkbox
|
|
108
|
-
? 'component-checkbox'
|
|
109
|
-
: 'component';
|
|
110
|
-
const componentFiles = Template.instantiate(templatePath(templateName), `src/components/${directoryName}`, {
|
|
58
|
+
const componentFiles = Template.instantiate(templatePath('component'), `src/components/${directoryName}`, {
|
|
111
59
|
component: {
|
|
112
60
|
name: componentName,
|
|
113
61
|
slug: stringToSlug(componentName),
|
|
@@ -118,30 +66,6 @@ export class GenerateComponentCommand extends Command {
|
|
|
118
66
|
});
|
|
119
67
|
}
|
|
120
68
|
|
|
121
|
-
protected async createStory(directoryName: string, componentName: string, files: Set<string>): Promise<void> {
|
|
122
|
-
if (!this.options.story) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
await Log.animate('Creating story', async () => {
|
|
127
|
-
const templateName = this.options.input
|
|
128
|
-
? 'component-input-story'
|
|
129
|
-
: this.options.button
|
|
130
|
-
? 'component-button-story'
|
|
131
|
-
: this.options.checkbox
|
|
132
|
-
? 'component-checkbox-story'
|
|
133
|
-
: 'component-story';
|
|
134
|
-
const storyFiles = Template.instantiate(templatePath(templateName), `src/components/${directoryName}`, {
|
|
135
|
-
component: {
|
|
136
|
-
name: componentName,
|
|
137
|
-
slug: stringToSlug(componentName),
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
storyFiles.forEach((file) => files.add(file));
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
69
|
protected async declareComponents(): Promise<void> {
|
|
146
70
|
if (!editFiles()) {
|
|
147
71
|
return;
|
|
@@ -1,27 +1,90 @@
|
|
|
1
|
-
import { describe, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import ShellMock from '@aerogel/cli/lib/Shell.mock';
|
|
4
|
+
import FileMock from '@aerogel/cli/lib/File.mock';
|
|
4
5
|
|
|
5
6
|
import { InstallCommand } from './install';
|
|
6
7
|
|
|
7
8
|
describe('Install plugin command', () => {
|
|
8
9
|
|
|
9
10
|
it('installs solid', async () => {
|
|
11
|
+
// Arrange
|
|
12
|
+
stubPackageJson();
|
|
13
|
+
|
|
10
14
|
// Act
|
|
11
15
|
await InstallCommand.run('solid');
|
|
12
16
|
|
|
13
17
|
// Assert
|
|
14
|
-
ShellMock.expectRan('
|
|
15
|
-
|
|
18
|
+
ShellMock.expectRan('pnpm install --no-save');
|
|
19
|
+
|
|
20
|
+
expectPackageJson({
|
|
21
|
+
dependencies: {
|
|
22
|
+
'@aerogel/core': 'next',
|
|
23
|
+
'@aerogel/plugin-solid': 'next',
|
|
24
|
+
'@noeldemartin/solid-utils': 'next',
|
|
25
|
+
'soukai-solid': 'next',
|
|
26
|
+
'vue': '^3.5.13',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
16
29
|
});
|
|
17
30
|
|
|
18
31
|
it('installs soukai', async () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
stubPackageJson();
|
|
34
|
+
|
|
19
35
|
// Act
|
|
20
|
-
await InstallCommand.run('soukai');
|
|
36
|
+
await InstallCommand.run('soukai', { skipInstall: true });
|
|
21
37
|
|
|
22
38
|
// Assert
|
|
23
|
-
ShellMock.
|
|
24
|
-
|
|
39
|
+
ShellMock.expectNotRan('pnpm install --no-save');
|
|
40
|
+
|
|
41
|
+
expectPackageJson({
|
|
42
|
+
dependencies: {
|
|
43
|
+
'@aerogel/core': 'next',
|
|
44
|
+
'@aerogel/plugin-soukai': 'next',
|
|
45
|
+
'soukai': 'next',
|
|
46
|
+
'vue': '^3.5.13',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('installs local-first', async () => {
|
|
52
|
+
// Arrange
|
|
53
|
+
stubPackageJson();
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
await InstallCommand.run('local-first');
|
|
57
|
+
|
|
58
|
+
// Assert
|
|
59
|
+
ShellMock.expectRan('pnpm install --no-save');
|
|
60
|
+
|
|
61
|
+
expectPackageJson({
|
|
62
|
+
dependencies: {
|
|
63
|
+
'@aerogel/core': 'next',
|
|
64
|
+
'@aerogel/plugin-local-first': 'next',
|
|
65
|
+
'vue': '^3.5.13',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
25
68
|
});
|
|
26
69
|
|
|
27
70
|
});
|
|
71
|
+
|
|
72
|
+
function stubPackageJson(): void {
|
|
73
|
+
FileMock.stub(
|
|
74
|
+
'package.json',
|
|
75
|
+
JSON.stringify(
|
|
76
|
+
{
|
|
77
|
+
dependencies: {
|
|
78
|
+
'@aerogel/core': 'next',
|
|
79
|
+
'vue': '^3.5.13',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
null,
|
|
83
|
+
2,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function expectPackageJson(packageJson: object): void {
|
|
89
|
+
expect(JSON.parse(FileMock.read('package.json') ?? '{}')).toEqual(packageJson);
|
|
90
|
+
}
|
package/src/commands/install.ts
CHANGED
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
import Command from '@aerogel/cli/commands/Command';
|
|
2
|
+
import LocalFirst from '@aerogel/cli/plugins/LocalFirst';
|
|
2
3
|
import Log from '@aerogel/cli/lib/Log';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import Solid from '@aerogel/cli/plugins/Solid';
|
|
5
|
+
import Soukai from '@aerogel/cli/plugins/Soukai';
|
|
5
6
|
import type Plugin from '@aerogel/cli/plugins/Plugin';
|
|
7
|
+
import type { CommandOptions } from '@aerogel/cli/commands/Command';
|
|
6
8
|
|
|
7
|
-
const plugins = [new Soukai(), new Solid()].reduce(
|
|
9
|
+
const plugins = [new Soukai(), new Solid(), new LocalFirst()].reduce(
|
|
8
10
|
(pluginsObject, plugin) => Object.assign(pluginsObject, { [plugin.name]: plugin }),
|
|
9
11
|
{} as Record<string, Plugin>,
|
|
10
12
|
);
|
|
13
|
+
export interface Options {
|
|
14
|
+
skipInstall?: boolean;
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
export class InstallCommand extends Command {
|
|
13
18
|
|
|
14
19
|
protected static override command: string = 'install';
|
|
15
20
|
protected static override description: string = 'Install an AerogelJS plugin';
|
|
16
21
|
protected static override parameters: [string, string][] = [['plugin', 'Plugin to install']];
|
|
17
|
-
|
|
22
|
+
protected static override options: CommandOptions = {
|
|
23
|
+
skipInstall: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
description: 'Skip installing dependencies, just add them to package.json',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
private options: Options;
|
|
18
30
|
private plugin: Plugin;
|
|
19
31
|
|
|
20
|
-
constructor(plugin: string) {
|
|
32
|
+
constructor(plugin: string, options: Options = {}) {
|
|
21
33
|
super();
|
|
22
34
|
|
|
35
|
+
this.options = options;
|
|
23
36
|
this.plugin =
|
|
24
37
|
plugins[plugin] ??
|
|
25
38
|
Log.fail(`Plugin '${plugin}' doesn't exist. Available plugins: ${Object.keys(plugins).join(', ')}`);
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
protected override async run(): Promise<void> {
|
|
29
|
-
await this.plugin.install(
|
|
42
|
+
await this.plugin.install({
|
|
43
|
+
skipInstall: this.options.skipInstall,
|
|
44
|
+
});
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
}
|
package/src/lib/App.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
2
|
|
|
3
3
|
import File from '@aerogel/cli/lib/File';
|
|
4
4
|
import Log from '@aerogel/cli/lib/Log';
|
|
5
5
|
import Template from '@aerogel/cli/lib/Template';
|
|
6
6
|
import { packNotFound, packagePackPath, packagePath, templatePath } from '@aerogel/cli/lib/utils/paths';
|
|
7
7
|
import { Editor } from '@aerogel/cli/lib/Editor';
|
|
8
|
-
|
|
9
|
-
interface Dependencies {
|
|
10
|
-
aerogelCli: string;
|
|
11
|
-
aerogelCore: string;
|
|
12
|
-
aerogelCypress: string;
|
|
13
|
-
aerogelPluginI18n: string;
|
|
14
|
-
aerogelPluginSoukai: string;
|
|
15
|
-
aerogelVite: string;
|
|
16
|
-
}
|
|
8
|
+
import { simpleGit } from 'simple-git';
|
|
17
9
|
|
|
18
10
|
export interface Options {
|
|
19
11
|
local?: boolean;
|
|
@@ -27,28 +19,42 @@ export default class App {
|
|
|
27
19
|
protected options: Options = {},
|
|
28
20
|
) {}
|
|
29
21
|
|
|
30
|
-
public create(path: string): void {
|
|
22
|
+
public async create(path: string): Promise<void> {
|
|
31
23
|
if (File.exists(path) && (!File.isDirectory(path) || !File.isEmptyDirectory(path))) {
|
|
32
24
|
Log.fail(`Folder at '${path}' already exists!`);
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
slug: stringToSlug(this.name),
|
|
39
|
-
},
|
|
40
|
-
dependencies: this.getDependencies(),
|
|
41
|
-
contentPath: this.options.linkedLocal
|
|
42
|
-
? `${packagePath('core')}/dist/**/*.js`
|
|
43
|
-
: './node_modules/@aerogel/core/dist/**/*.js',
|
|
27
|
+
// Clone repository
|
|
28
|
+
await simpleGit().clone('https://github.com/NoelDeMartin/aerogel-template.git', path, {
|
|
29
|
+
'--depth': 1,
|
|
44
30
|
});
|
|
31
|
+
|
|
32
|
+
File.delete(resolve(path, '.git'));
|
|
33
|
+
|
|
34
|
+
// Apply replacements
|
|
35
|
+
const dependencies = this.getDependencies();
|
|
36
|
+
|
|
37
|
+
File.replace(
|
|
38
|
+
resolve(path, 'vite.config.ts'),
|
|
39
|
+
'Aerogel({ name: \'Aerogel\' })',
|
|
40
|
+
`Aerogel({ name: '${this.name}' })`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
File.replace(resolve(path, 'src/lang/en.yaml'), 'title: \'App\'', `title: '${this.name}'`);
|
|
44
|
+
|
|
45
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
46
|
+
File.replace(resolve(path, 'package.json'), new RegExp(`"${name}": ".*?"`, 'g'), `"${name}": "${version}"`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Copy template
|
|
50
|
+
Template.instantiate(templatePath('app'), path, { app: { name: this.name } });
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
public edit(): Editor {
|
|
48
54
|
return new Editor();
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
protected getDependencies():
|
|
57
|
+
protected getDependencies(): Record<string, string> {
|
|
52
58
|
const withFilePrefix = <T extends Record<string, string>>(paths: T) =>
|
|
53
59
|
Object.entries(paths).reduce(
|
|
54
60
|
(pathsWithFile, [name, path]) => Object.assign(pathsWithFile, { [name]: `file:${path}` }) as T,
|
|
@@ -57,34 +63,27 @@ export default class App {
|
|
|
57
63
|
|
|
58
64
|
if (this.options.linkedLocal) {
|
|
59
65
|
return withFilePrefix({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
'@aerogel/cli': packagePath('cli'),
|
|
67
|
+
'@aerogel/core': packagePath('core'),
|
|
68
|
+
'@aerogel/cypress': packagePath('cypress'),
|
|
69
|
+
'@aerogel/plugin-i18n': packagePath('plugin-i18n'),
|
|
70
|
+
'@aerogel/plugin-soukai': packagePath('plugin-soukai'),
|
|
71
|
+
'@aerogel/vite': packagePath('vite'),
|
|
66
72
|
});
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
if (this.options.local) {
|
|
70
76
|
return withFilePrefix({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
'@aerogel/cli': packagePackPath('cli') ?? packNotFound('cli'),
|
|
78
|
+
'@aerogel/core': packagePackPath('core') ?? packNotFound('core'),
|
|
79
|
+
'@aerogel/cypress': packagePackPath('cypress') ?? packNotFound('cypress'),
|
|
80
|
+
'@aerogel/plugin-i18n': packagePackPath('plugin-i18n') ?? packNotFound('plugin-i18n'),
|
|
81
|
+
'@aerogel/plugin-soukai': packagePackPath('plugin-soukai') ?? packNotFound('plugin-soukai'),
|
|
82
|
+
'@aerogel/vite': packagePackPath('vite') ?? packNotFound('vite'),
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
return {
|
|
81
|
-
aerogelCli: 'next',
|
|
82
|
-
aerogelCore: 'next',
|
|
83
|
-
aerogelCypress: 'next',
|
|
84
|
-
aerogelPluginI18n: 'next',
|
|
85
|
-
aerogelPluginSoukai: 'next',
|
|
86
|
-
aerogelVite: 'next',
|
|
87
|
-
};
|
|
86
|
+
return {};
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
}
|
package/src/lib/Editor.ts
CHANGED
|
@@ -16,7 +16,6 @@ export class Editor {
|
|
|
16
16
|
this.modifiedFiles = new Set();
|
|
17
17
|
|
|
18
18
|
this.project.addSourceFilesAtPaths('src/**/*.ts');
|
|
19
|
-
this.project.addSourceFilesAtPaths('tailwind.config.js');
|
|
20
19
|
this.project.addSourceFilesAtPaths('vite.config.ts');
|
|
21
20
|
this.project.addSourceFilesAtPaths('package.json');
|
|
22
21
|
}
|
package/src/lib/File.mock.ts
CHANGED
|
@@ -8,6 +8,12 @@ export class FileMockService extends FileService {
|
|
|
8
8
|
|
|
9
9
|
private virtualFilesystem: Record<string, string | { directory: true }> = {};
|
|
10
10
|
|
|
11
|
+
public override delete(path: string): void {
|
|
12
|
+
if (path in this.virtualFilesystem) {
|
|
13
|
+
delete this.virtualFilesystem[path];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
public override exists(path: string): boolean {
|
|
12
18
|
return super.exists(path) || path in this.virtualFilesystem;
|
|
13
19
|
}
|
package/src/lib/File.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
3
11
|
import { dirname, resolve } from 'node:path';
|
|
4
12
|
|
|
13
|
+
import { facade } from '@noeldemartin/utils';
|
|
14
|
+
|
|
5
15
|
export class FileService {
|
|
6
16
|
|
|
7
17
|
public contains(path: string, contents: string): boolean {
|
|
@@ -12,6 +22,16 @@ export class FileService {
|
|
|
12
22
|
return existsSync(path);
|
|
13
23
|
}
|
|
14
24
|
|
|
25
|
+
public delete(path: string): void {
|
|
26
|
+
if (this.isDirectory(path)) {
|
|
27
|
+
rmSync(path, { recursive: true });
|
|
28
|
+
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
unlinkSync(path);
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
public isSymlink(path: string): boolean {
|
|
16
36
|
const stats = lstatSync(path);
|
|
17
37
|
|
|
@@ -26,6 +46,16 @@ export class FileService {
|
|
|
26
46
|
return readFileSync(path).toString();
|
|
27
47
|
}
|
|
28
48
|
|
|
49
|
+
public replace(path: string, search: string | RegExp, replacement: string): void {
|
|
50
|
+
const contents = this.read(path);
|
|
51
|
+
|
|
52
|
+
if (!contents) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.write(path, contents.replaceAll(search, replacement));
|
|
57
|
+
}
|
|
58
|
+
|
|
29
59
|
public getFiles(directoryPath: string): string[] {
|
|
30
60
|
const children = readdirSync(directoryPath, { withFileTypes: true });
|
|
31
61
|
const files: string[] = [];
|
package/src/lib/Shell.mock.ts
CHANGED
|
@@ -15,6 +15,10 @@ export class ShellServiceMock extends ShellService {
|
|
|
15
15
|
expect(this.history, `expected '${command}' command to have been executed`).toContain(command);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
public expectNotRan(command: string): void {
|
|
19
|
+
expect(this.history, `expected '${command}' command to not have been executed`).not.toContain(command);
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export default facade(ShellServiceMock);
|
package/src/lib/utils/paths.ts
CHANGED
|
@@ -6,6 +6,10 @@ import File from '@aerogel/cli/lib/File';
|
|
|
6
6
|
import Log from '@aerogel/cli/lib/Log';
|
|
7
7
|
|
|
8
8
|
export function basePath(path: string = ''): string {
|
|
9
|
+
if (process.env.AEROGEL_BASE_PATH) {
|
|
10
|
+
return resolve(process.env.AEROGEL_BASE_PATH, path);
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
if (
|
|
10
14
|
File.contains(
|
|
11
15
|
fileURLToPath(new URL(/* @vite-ignore */ '../../../package.json', import.meta.url)),
|
package/src/plugins/Plugin.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import { stringToCamelCase } from '@noeldemartin/utils';
|
|
2
3
|
import type { ArrayLiteralExpression, ImportDeclarationStructure, OptionalKind, SourceFile } from 'ts-morph';
|
|
3
4
|
|
|
4
5
|
import Log from '@aerogel/cli/lib/Log';
|
|
5
6
|
import Shell from '@aerogel/cli/lib/Shell';
|
|
6
7
|
import File from '@aerogel/cli/lib/File';
|
|
7
8
|
import { app, isLinkedLocalApp, isLocalApp } from '@aerogel/cli/lib/utils/app';
|
|
9
|
+
import { addNpmDependency } from '@aerogel/cli/utils/package';
|
|
8
10
|
import { editFiles, findDescendant, when } from '@aerogel/cli/lib/utils/edit';
|
|
9
11
|
import { packNotFound, packagePackPath, packagePath } from '@aerogel/cli/lib/utils/paths';
|
|
10
12
|
import type { Editor } from '@aerogel/cli/lib/Editor';
|
|
@@ -17,15 +19,21 @@ export default abstract class Plugin {
|
|
|
17
19
|
this.name = name;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
public async install(): Promise<void> {
|
|
22
|
+
public async install(options: { skipInstall?: boolean } = {}): Promise<void> {
|
|
21
23
|
this.assertNotInstalled();
|
|
22
24
|
|
|
23
25
|
await this.beforeInstall();
|
|
24
|
-
await this.
|
|
26
|
+
await this.addDependencies();
|
|
27
|
+
|
|
28
|
+
if (!options.skipInstall) {
|
|
29
|
+
await this.installDependencies();
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
if (editFiles()) {
|
|
27
33
|
const editor = app().edit();
|
|
28
34
|
|
|
35
|
+
editor.addSourceFile('package.json');
|
|
36
|
+
|
|
29
37
|
await this.updateFiles(editor);
|
|
30
38
|
await editor.format();
|
|
31
39
|
}
|
|
@@ -49,23 +57,32 @@ export default abstract class Plugin {
|
|
|
49
57
|
// Placeholder for overrides, don't place any functionality here.
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
protected async addDependencies(): Promise<void> {
|
|
61
|
+
Log.info('Adding plugin dependencies');
|
|
62
|
+
this.addNpmDependencies();
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
protected async installDependencies(): Promise<void> {
|
|
53
66
|
await Log.animate('Installing plugin dependencies', async () => {
|
|
54
|
-
await
|
|
67
|
+
await Shell.run('pnpm install --no-save');
|
|
55
68
|
});
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
protected async updateFiles(editor: Editor): Promise<void> {
|
|
59
|
-
if (
|
|
60
|
-
|
|
72
|
+
if (this.isForDevelopment()) {
|
|
73
|
+
return;
|
|
61
74
|
}
|
|
62
|
-
}
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
await this.updateBootstrapConfig(editor);
|
|
77
|
+
}
|
|
66
78
|
|
|
79
|
+
protected addNpmDependencies(): void {
|
|
67
80
|
if (isLinkedLocalApp()) {
|
|
68
|
-
|
|
81
|
+
addNpmDependency(
|
|
82
|
+
this.getNpmPackageName(),
|
|
83
|
+
`file:${packagePath(this.getLocalPackageName())}`,
|
|
84
|
+
this.isForDevelopment(),
|
|
85
|
+
);
|
|
69
86
|
|
|
70
87
|
return;
|
|
71
88
|
}
|
|
@@ -73,12 +90,12 @@ export default abstract class Plugin {
|
|
|
73
90
|
if (isLocalApp()) {
|
|
74
91
|
const packPath = packagePackPath(this.getLocalPackageName()) ?? packNotFound(this.getLocalPackageName());
|
|
75
92
|
|
|
76
|
-
|
|
93
|
+
addNpmDependency(this.getNpmPackageName(), `file:${packPath}`, this.isForDevelopment());
|
|
77
94
|
|
|
78
95
|
return;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
addNpmDependency(this.getNpmPackageName(), 'next', this.isForDevelopment());
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
protected async updateBootstrapConfig(editor: Editor): Promise<void> {
|
|
@@ -101,25 +118,6 @@ export default abstract class Plugin {
|
|
|
101
118
|
});
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
protected async updateTailwindConfig(editor: Editor, options: { content: string }): Promise<void> {
|
|
105
|
-
await Log.animate('Updating tailwind configuration', async () => {
|
|
106
|
-
const tailwindConfig = editor.requireSourceFile('tailwind.config.js');
|
|
107
|
-
const contentArray = this.getTailwindContentArray(tailwindConfig);
|
|
108
|
-
|
|
109
|
-
if (!contentArray) {
|
|
110
|
-
return Log.fail(`
|
|
111
|
-
Could not find content array in tailwind config, please add the following manually:
|
|
112
|
-
|
|
113
|
-
${options.content}
|
|
114
|
-
`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
contentArray.addElement(options.content);
|
|
118
|
-
|
|
119
|
-
await editor.save(tailwindConfig);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
121
|
protected getBootstrapPluginsDeclaration(mainConfig: SourceFile): ArrayLiteralExpression | null {
|
|
124
122
|
const bootstrapAppCall = findDescendant(mainConfig, {
|
|
125
123
|
guard: Node.isCallExpression,
|
|
@@ -154,7 +152,7 @@ export default abstract class Plugin {
|
|
|
154
152
|
|
|
155
153
|
protected getBootstrapImport(): OptionalKind<ImportDeclarationStructure> {
|
|
156
154
|
return {
|
|
157
|
-
defaultImport: this.name,
|
|
155
|
+
defaultImport: stringToCamelCase(this.name),
|
|
158
156
|
moduleSpecifier: `@aerogel/plugin-${this.name}`,
|
|
159
157
|
};
|
|
160
158
|
}
|
|
@@ -172,7 +170,7 @@ export default abstract class Plugin {
|
|
|
172
170
|
}
|
|
173
171
|
|
|
174
172
|
protected getBootstrapConfig(): string {
|
|
175
|
-
return `${this.name}()`;
|
|
173
|
+
return `${stringToCamelCase(this.name)}()`;
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
}
|