@aerogel/cli 0.0.0 → 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 (72) hide show
  1. package/bin/gel +4 -0
  2. package/dist/aerogel-cli.d.ts +2 -2
  3. package/dist/aerogel-cli.js +790 -0
  4. package/dist/aerogel-cli.js.map +1 -0
  5. package/package.json +28 -33
  6. package/src/cli.ts +28 -6
  7. package/src/commands/Command.ts +14 -9
  8. package/src/commands/create.test.ts +4 -9
  9. package/src/commands/create.ts +40 -26
  10. package/src/commands/generate-component.test.ts +5 -7
  11. package/src/commands/generate-component.ts +114 -37
  12. package/src/commands/generate-model.test.ts +17 -6
  13. package/src/commands/generate-model.ts +47 -22
  14. package/src/commands/generate-service.test.ts +21 -0
  15. package/src/commands/generate-service.ts +151 -0
  16. package/src/commands/info.ts +15 -0
  17. package/src/commands/install.test.ts +90 -0
  18. package/src/commands/install.ts +47 -0
  19. package/src/lib/App.ts +76 -12
  20. package/src/lib/Editor.ts +57 -0
  21. package/src/lib/File.mock.ts +13 -11
  22. package/src/lib/File.ts +43 -3
  23. package/src/lib/Log.mock.ts +11 -6
  24. package/src/lib/Log.test.ts +22 -6
  25. package/src/lib/Log.ts +43 -27
  26. package/src/lib/Shell.mock.ts +7 -3
  27. package/src/lib/Shell.ts +2 -2
  28. package/src/lib/Template.ts +27 -19
  29. package/src/lib/utils/app.ts +15 -0
  30. package/src/lib/utils/edit.ts +44 -0
  31. package/src/lib/utils/paths.ts +46 -0
  32. package/src/plugins/LocalFirst.ts +9 -0
  33. package/src/plugins/Plugin.ts +176 -0
  34. package/src/plugins/Solid.ts +72 -0
  35. package/src/plugins/Soukai.ts +20 -0
  36. package/src/testing/setup.ts +62 -25
  37. package/src/utils/package.ts +23 -0
  38. package/templates/app/README.md +3 -0
  39. package/templates/service/[service.name].ts +8 -0
  40. package/.eslintrc.js +0 -7
  41. package/bin/ag +0 -4
  42. package/dist/aerogel-cli.cjs.js +0 -2
  43. package/dist/aerogel-cli.cjs.js.map +0 -1
  44. package/dist/aerogel-cli.esm.js +0 -2
  45. package/dist/aerogel-cli.esm.js.map +0 -1
  46. package/noeldemartin.config.js +0 -4
  47. package/src/lib/utils.test.ts +0 -33
  48. package/src/lib/utils.ts +0 -44
  49. package/templates/app/.github/workflows/ci.yml +0 -17
  50. package/templates/app/.nvmrc +0 -1
  51. package/templates/app/cypress/e2e/app.cy.ts +0 -9
  52. package/templates/app/cypress/support/e2e.ts +0 -3
  53. package/templates/app/cypress/tsconfig.json +0 -12
  54. package/templates/app/cypress.config.ts +0 -8
  55. package/templates/app/index.html +0 -12
  56. package/templates/app/package.json +0 -44
  57. package/templates/app/postcss.config.js +0 -6
  58. package/templates/app/src/App.vue +0 -10
  59. package/templates/app/src/assets/styles.css +0 -3
  60. package/templates/app/src/lang/en.yaml +0 -3
  61. package/templates/app/src/main.test.ts +0 -9
  62. package/templates/app/src/main.ts +0 -9
  63. package/templates/app/src/types/globals.d.ts +0 -3
  64. package/templates/app/src/types/shims.d.ts +0 -7
  65. package/templates/app/src/types/ts-reset.d.ts +0 -1
  66. package/templates/app/tailwind.config.js +0 -5
  67. package/templates/app/tsconfig.json +0 -18
  68. package/templates/app/vite.config.ts +0 -21
  69. package/templates/component-story/[component.name].story.vue +0 -7
  70. package/tsconfig.json +0 -11
  71. package/vite.config.ts +0 -14
  72. /package/src/{main.ts → index.ts} +0 -0
@@ -0,0 +1,46 @@
1
+ import { URL, fileURLToPath } from 'node:url';
2
+ import { stringMatch } from '@noeldemartin/utils';
3
+ import { resolve } from 'node:path';
4
+
5
+ import File from '@aerogel/cli/lib/File';
6
+ import Log from '@aerogel/cli/lib/Log';
7
+
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
+
13
+ if (
14
+ File.contains(
15
+ fileURLToPath(new URL(/* @vite-ignore */ '../../../package.json', import.meta.url)),
16
+ '"packages/create-aerogel"',
17
+ )
18
+ ) {
19
+ return resolve(fileURLToPath(new URL(/* @vite-ignore */ '../', import.meta.url)), path);
20
+ }
21
+
22
+ const packageJson = File.read(
23
+ fileURLToPath(new URL(/* @vite-ignore */ '../../../../package.json', import.meta.url)),
24
+ );
25
+ const matches = stringMatch<2>(packageJson ?? '', /"@aerogel\/core": "file:(.*)\/aerogel-core-[\d.]*\.tgz"/);
26
+ const cliPath = matches?.[1] ?? Log.fail<string>('Could not determine base path');
27
+
28
+ return resolve(cliPath, path);
29
+ }
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ export function packNotFound(packageName: string): any {
33
+ return Log.fail(`Could not find ${packageName} pack file, did you run 'npm pack'?`);
34
+ }
35
+
36
+ export function packagePackPath(packageName: string): string | null {
37
+ return File.getFiles(packagePath(packageName)).find((file) => file.endsWith('.tgz')) ?? null;
38
+ }
39
+
40
+ export function packagePath(packageName: string): string {
41
+ return basePath(`../${packageName}`);
42
+ }
43
+
44
+ export function templatePath(name: string): string {
45
+ return fileURLToPath(new URL(/* @vite-ignore */ `../templates/${name}`, import.meta.url));
46
+ }
@@ -0,0 +1,9 @@
1
+ import Plugin from '@aerogel/cli/plugins/Plugin';
2
+
3
+ export default class LocalFirst extends Plugin {
4
+
5
+ constructor() {
6
+ super('local-first');
7
+ }
8
+
9
+ }
@@ -0,0 +1,176 @@
1
+ import { Node, SyntaxKind } from 'ts-morph';
2
+ import { stringToCamelCase } from '@noeldemartin/utils';
3
+ import type { ArrayLiteralExpression, ImportDeclarationStructure, OptionalKind, SourceFile } from 'ts-morph';
4
+
5
+ import Log from '@aerogel/cli/lib/Log';
6
+ import Shell from '@aerogel/cli/lib/Shell';
7
+ import File from '@aerogel/cli/lib/File';
8
+ import { app, isLinkedLocalApp, isLocalApp } from '@aerogel/cli/lib/utils/app';
9
+ import { addNpmDependency } from '@aerogel/cli/utils/package';
10
+ import { editFiles, findDescendant, when } from '@aerogel/cli/lib/utils/edit';
11
+ import { packNotFound, packagePackPath, packagePath } from '@aerogel/cli/lib/utils/paths';
12
+ import type { Editor } from '@aerogel/cli/lib/Editor';
13
+
14
+ export default abstract class Plugin {
15
+
16
+ public readonly name: string;
17
+
18
+ constructor(name: string) {
19
+ this.name = name;
20
+ }
21
+
22
+ public async install(options: { skipInstall?: boolean } = {}): Promise<void> {
23
+ this.assertNotInstalled();
24
+
25
+ await this.beforeInstall();
26
+ await this.addDependencies();
27
+
28
+ if (!options.skipInstall) {
29
+ await this.installDependencies();
30
+ }
31
+
32
+ if (editFiles()) {
33
+ const editor = app().edit();
34
+
35
+ editor.addSourceFile('package.json');
36
+
37
+ await this.updateFiles(editor);
38
+ await editor.format();
39
+ }
40
+
41
+ await this.afterInstall();
42
+
43
+ Log.info(`Plugin ${this.name} installed!`);
44
+ }
45
+
46
+ protected assertNotInstalled(): void {
47
+ if (File.contains('package.json', `"${this.getNpmPackageName()}"`)) {
48
+ Log.fail(`${this.name} is already installed!`);
49
+ }
50
+ }
51
+
52
+ protected async beforeInstall(): Promise<void> {
53
+ // Placeholder for overrides, don't place any functionality here.
54
+ }
55
+
56
+ protected async afterInstall(): Promise<void> {
57
+ // Placeholder for overrides, don't place any functionality here.
58
+ }
59
+
60
+ protected async addDependencies(): Promise<void> {
61
+ Log.info('Adding plugin dependencies');
62
+ this.addNpmDependencies();
63
+ }
64
+
65
+ protected async installDependencies(): Promise<void> {
66
+ await Log.animate('Installing plugin dependencies', async () => {
67
+ await Shell.run('pnpm install --no-save');
68
+ });
69
+ }
70
+
71
+ protected async updateFiles(editor: Editor): Promise<void> {
72
+ if (this.isForDevelopment()) {
73
+ return;
74
+ }
75
+
76
+ await this.updateBootstrapConfig(editor);
77
+ }
78
+
79
+ protected addNpmDependencies(): void {
80
+ if (isLinkedLocalApp()) {
81
+ addNpmDependency(
82
+ this.getNpmPackageName(),
83
+ `file:${packagePath(this.getLocalPackageName())}`,
84
+ this.isForDevelopment(),
85
+ );
86
+
87
+ return;
88
+ }
89
+
90
+ if (isLocalApp()) {
91
+ const packPath = packagePackPath(this.getLocalPackageName()) ?? packNotFound(this.getLocalPackageName());
92
+
93
+ addNpmDependency(this.getNpmPackageName(), `file:${packPath}`, this.isForDevelopment());
94
+
95
+ return;
96
+ }
97
+
98
+ addNpmDependency(this.getNpmPackageName(), 'next', this.isForDevelopment());
99
+ }
100
+
101
+ protected async updateBootstrapConfig(editor: Editor): Promise<void> {
102
+ await Log.animate('Injecting plugin in bootstrap configuration', async () => {
103
+ const mainConfig = editor.requireSourceFile('src/main.ts');
104
+ const pluginsArray = this.getBootstrapPluginsDeclaration(mainConfig);
105
+
106
+ if (!pluginsArray) {
107
+ return Log.fail(`
108
+ Could not find plugins array in bootstrap config, please add the following manually:
109
+
110
+ ${this.getBootstrapConfig()}
111
+ `);
112
+ }
113
+
114
+ mainConfig.addImportDeclaration(this.getBootstrapImport());
115
+ pluginsArray.addElement(this.getBootstrapConfig());
116
+
117
+ await editor.save(mainConfig);
118
+ });
119
+ }
120
+
121
+ protected getBootstrapPluginsDeclaration(mainConfig: SourceFile): ArrayLiteralExpression | null {
122
+ const bootstrapAppCall = findDescendant(mainConfig, {
123
+ guard: Node.isCallExpression,
124
+ validate: (callExpression) => callExpression.getExpression().getText() === 'bootstrap',
125
+ skip: SyntaxKind.ImportDeclaration,
126
+ });
127
+ const bootstrapOptions = bootstrapAppCall?.getArguments()[1];
128
+ const pluginsOption = when(bootstrapOptions, Node.isObjectLiteralExpression)?.getProperty('plugins');
129
+ const pluginsArray = when(pluginsOption, Node.isPropertyAssignment)?.getInitializer();
130
+
131
+ if (!Node.isArrayLiteralExpression(pluginsArray)) {
132
+ return null;
133
+ }
134
+
135
+ return pluginsArray;
136
+ }
137
+
138
+ protected getTailwindContentArray(tailwindConfig: SourceFile): ArrayLiteralExpression | null {
139
+ const contentAssignment = findDescendant(tailwindConfig, {
140
+ guard: Node.isPropertyAssignment,
141
+ validate: (propertyAssignment) => propertyAssignment.getName() === 'content',
142
+ skip: SyntaxKind.JSDoc,
143
+ });
144
+ const contentArray = contentAssignment?.getInitializer();
145
+
146
+ if (!Node.isArrayLiteralExpression(contentArray)) {
147
+ return null;
148
+ }
149
+
150
+ return contentArray;
151
+ }
152
+
153
+ protected getBootstrapImport(): OptionalKind<ImportDeclarationStructure> {
154
+ return {
155
+ defaultImport: stringToCamelCase(this.name),
156
+ moduleSpecifier: `@aerogel/plugin-${this.name}`,
157
+ };
158
+ }
159
+
160
+ protected getNpmPackageName(): string {
161
+ return `@aerogel/${this.getLocalPackageName()}`;
162
+ }
163
+
164
+ protected getLocalPackageName(): string {
165
+ return `plugin-${this.name}`;
166
+ }
167
+
168
+ protected isForDevelopment(): boolean {
169
+ return false;
170
+ }
171
+
172
+ protected getBootstrapConfig(): string {
173
+ return `${stringToCamelCase(this.name)}()`;
174
+ }
175
+
176
+ }
@@ -0,0 +1,72 @@
1
+ import File from '@aerogel/cli/lib/File';
2
+ import Log from '@aerogel/cli/lib/Log';
3
+ import Plugin from '@aerogel/cli/plugins/Plugin';
4
+ import { addNpmDependency } from '@aerogel/cli/utils/package';
5
+ import type { Editor } from '@aerogel/cli/lib/Editor';
6
+
7
+ export default class Solid extends Plugin {
8
+
9
+ constructor() {
10
+ super('solid');
11
+ }
12
+
13
+ protected override async updateFiles(editor: Editor): Promise<void> {
14
+ await this.updateNpmScripts(editor);
15
+ await this.updateGitIgnore();
16
+ await super.updateFiles(editor);
17
+ }
18
+
19
+ protected override addNpmDependencies(): void {
20
+ addNpmDependency('soukai-solid', 'next');
21
+ addNpmDependency('@noeldemartin/solid-utils', 'next');
22
+ addNpmDependency('@solid/community-server', '7.1.6', true);
23
+
24
+ super.addNpmDependencies();
25
+ }
26
+
27
+ protected async updateNpmScripts(editor: Editor): Promise<void> {
28
+ Log.info('Updating npm scripts...');
29
+
30
+ const packageJson = File.read('package.json');
31
+
32
+ if (!packageJson) {
33
+ return Log.fail('Could not find package.json file');
34
+ }
35
+
36
+ File.write(
37
+ 'package.json',
38
+ packageJson
39
+ .replace(
40
+ '"cy:dev": "concurrently --kill-others \\"npm run test:serve-app\\" \\"npm run cy:open\\"",',
41
+ '"cy:dev": "concurrently --kill-others ' +
42
+ '\\"npm run test:serve-app\\" \\"npm run test:serve-pod\\" \\"npm run cy:open\\"",',
43
+ )
44
+ .replace(
45
+ '"cy:test": "start-server-and-test test:serve-app http-get://localhost:5001 cy:run",',
46
+ '"cy:test": "start-server-and-test ' +
47
+ 'test:serve-app http-get://localhost:5001 test:serve-pod http-get://localhost:3000 cy:run",',
48
+ )
49
+ .replace(
50
+ '"dev": "vite",',
51
+ '"dev": "vite",\n' +
52
+ '"dev:serve-pod": "community-solid-server -c @css:config/file.json -f ./solid",',
53
+ )
54
+ .replace(
55
+ '"test:serve-app": "vite --port 5001 --mode testing"',
56
+ '"test:serve-app": "vite --port 5001 --mode testing",\n' +
57
+ '"test:serve-pod": "community-solid-server -l warn"',
58
+ ),
59
+ );
60
+
61
+ editor.addModifiedFile('package.json');
62
+ }
63
+
64
+ protected async updateGitIgnore(): Promise<void> {
65
+ Log.info('Updating .gitignore');
66
+
67
+ const gitignore = File.read('.gitignore') ?? '';
68
+
69
+ File.write('.gitignore', `${gitignore}/solid\n`);
70
+ }
71
+
72
+ }
@@ -0,0 +1,20 @@
1
+ import Plugin from '@aerogel/cli/plugins/Plugin';
2
+ import { addNpmDependency } from '@aerogel/cli/utils/package';
3
+
4
+ export default class Soukai extends Plugin {
5
+
6
+ constructor() {
7
+ super('soukai');
8
+ }
9
+
10
+ protected override addNpmDependencies(): void {
11
+ addNpmDependency('soukai', 'next');
12
+
13
+ super.addNpmDependencies();
14
+ }
15
+
16
+ protected override getBootstrapConfig(): string {
17
+ return 'soukai({ models: import.meta.glob(\'@/models/*\', { eager: true }) })';
18
+ }
19
+
20
+ }
@@ -1,42 +1,79 @@
1
- import { beforeEach, vi } from 'vitest';
2
- import { resolve } from 'path';
3
- import { setTestingNamespace } from '@noeldemartin/utils';
1
+ import { beforeAll, beforeEach, vi } from 'vitest';
2
+ import { resolve } from 'node:path';
3
+ import { setupFacadeMocks } from '@noeldemartin/testing';
4
4
 
5
- import File from '@/lib/File';
6
- import FileMock from '@/lib/File.mock';
7
- import Log from '@/lib/Log';
8
- import LogMock from '@/lib/Log.mock';
9
- import Shell from '@/lib/Shell';
10
- import ShellMock from '@/lib/Shell.mock';
5
+ import ShellMock from '@aerogel/cli/lib/Shell.mock';
6
+ import File from '@aerogel/cli/lib/File';
7
+ import FileMock from '@aerogel/cli/lib/File.mock';
8
+ import Log from '@aerogel/cli/lib/Log';
9
+ import LogMock from '@aerogel/cli/lib/Log.mock';
10
+ import Shell from '@aerogel/cli/lib/Shell';
11
11
 
12
- setTestingNamespace(vi);
13
-
14
- File.setMockInstance(FileMock);
15
- Log.setMockInstance(LogMock);
16
- Shell.setMockInstance(ShellMock);
12
+ beforeAll(() => {
13
+ File.setMockFacade(FileMock);
14
+ Log.setMockFacade(LogMock);
15
+ Shell.setMockFacade(ShellMock);
16
+ });
17
17
 
18
18
  beforeEach(() => {
19
- FileMock.reset();
20
19
  File.mock();
21
20
  Log.mock();
22
21
  Shell.mock();
23
22
  });
24
23
 
25
- // TODO find out why these need to be mocked
24
+ vi.mock('@noeldemartin/utils', async () => {
25
+ const original = (await vi.importActual('@noeldemartin/utils')) as object;
26
+
27
+ setupFacadeMocks();
28
+
29
+ return original;
30
+ });
31
+
32
+ vi.mock('@aerogel/cli/lib/utils/app', async () => {
33
+ const original = (await vi.importActual('@aerogel/cli/lib/utils/app')) as object;
34
+
35
+ return {
36
+ ...original,
37
+ isLocalApp: () => false,
38
+ isLinkedLocalApp: () => false,
39
+ };
40
+ });
26
41
 
27
- vi.mock('@/lib/utils', async () => {
28
- const utils = (await vi.importActual('@/lib/utils')) as object;
42
+ vi.mock('@aerogel/cli/lib/utils/edit', async () => {
43
+ const original = (await vi.importActual('@aerogel/cli/lib/utils/edit')) as object;
29
44
 
30
45
  return {
31
- ...utils,
32
- basePath(path: string) {
33
- return resolve(__dirname, '../../', path);
34
- },
46
+ ...original,
47
+ editFiles: () => false,
35
48
  };
36
49
  });
37
50
 
38
- vi.mock('mustache', async () => {
39
- const mustache = (await vi.importActual('mustache')) as { default: unknown };
51
+ vi.mock('simple-git', () => ({
52
+ simpleGit: () => ({
53
+ clone: () => Promise.resolve(),
54
+ }),
55
+ }));
56
+
57
+ // TODO find out why these need to be mocked
58
+ vi.mock('@aerogel/cli/lib/utils/paths', async () => {
59
+ const original = (await vi.importActual('@aerogel/cli/lib/utils/paths')) as object;
60
+
61
+ function basePath(path: string = '') {
62
+ return resolve(__dirname, '../../', path);
63
+ }
40
64
 
41
- return mustache.default;
65
+ function packagePath(packageName: string) {
66
+ return basePath(`../${packageName}`);
67
+ }
68
+
69
+ function templatePath(name: string = '') {
70
+ return resolve(__dirname, `../../templates/${name}`);
71
+ }
72
+
73
+ return {
74
+ ...original,
75
+ basePath,
76
+ packagePath,
77
+ templatePath,
78
+ };
42
79
  });
@@ -0,0 +1,23 @@
1
+ import { arraySorted } from '@noeldemartin/utils';
2
+
3
+ import File from '@aerogel/cli/lib/File';
4
+
5
+ export function addNpmDependency(name: string, version: string, development: boolean = false): void {
6
+ const packageJson = (JSON.parse(File.read('package.json') ?? '{}') ?? {}) as {
7
+ dependencies?: Record<string, string>;
8
+ devDependencies?: Record<string, string>;
9
+ };
10
+ const dependencies = (development ? packageJson?.devDependencies : packageJson?.dependencies) ?? {};
11
+ const dependencyNames = arraySorted(Object.keys(dependencies).concat(name));
12
+ const index = dependencyNames.indexOf(name);
13
+ const previousDependency = dependencyNames[index - 1] ?? '';
14
+
15
+ File.replace(
16
+ 'package.json',
17
+ `"${previousDependency}": "${dependencies[previousDependency]}",`,
18
+ `
19
+ "${previousDependency}": "${dependencies[previousDependency]}",
20
+ "${name}": "${version}",
21
+ `,
22
+ );
23
+ }
@@ -0,0 +1,3 @@
1
+ # <% app.name %>
2
+
3
+ App created with [AerogelJS](https://aerogel.js.org).
@@ -0,0 +1,8 @@
1
+ import { Service } from '@aerogel/core';
2
+ import { facade } from '@noeldemartin/utils';
3
+
4
+ export class <% service.name %>Service extends Service {
5
+
6
+ }
7
+
8
+ export default facade(<% service.name %>Service);
package/.eslintrc.js DELETED
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- env: {
3
- browser: true,
4
- es2021: true,
5
- },
6
- extends: ['@noeldemartin/eslint-config-typescript'],
7
- };
package/bin/ag DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- const { CLI } = require('../dist/aerogel-cli.cjs.js');
3
-
4
- CLI.run();
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("commander"),t=require("@babel/runtime/helpers/defineProperty"),s=require("@noeldemartin/utils"),n=require("fs"),r=require("path");require("core-js/modules/esnext.async-iterator.for-each.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.for-each.js");var i=require("chalk"),o=require("readline");require("core-js/modules/esnext.async-iterator.reduce.js"),require("core-js/modules/esnext.iterator.reduce.js");var a=require("mustache"),c=require("child_process");function l(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}require("core-js/modules/esnext.async-iterator.map.js"),require("core-js/modules/esnext.iterator.map.js");var d=l(t);var u=s.facade(new class{exists(e){return n.existsSync(e)}read(e){return this.isFile(e)?n.readFileSync(e).toString():null}getFiles(e){const t=n.readdirSync(e,{withFileTypes:!0}),s=[];for(const n of t){const t=r.resolve(e,n.name);n.isDirectory()?s.push(...this.getFiles(t)):s.push(t)}return s}isDirectory(e){return this.exists(e)&&n.lstatSync(e).isDirectory()}isFile(e){return this.exists(e)&&n.lstatSync(e).isFile()}isEmptyDirectory(e){return!!this.isDirectory(e)&&0===this.getFiles(e).length}makeDirectory(e){n.mkdirSync(e,{recursive:!0})}write(e,t){n.existsSync(r.dirname(e))||n.mkdirSync(r.dirname(e),{recursive:!0}),n.writeFileSync(e,t)}});var p=s.facade(new class{constructor(){d.default(this,"renderInfo",i.hex("#00ffff")),d.default(this,"renderSuccess",i.hex("#00ff00")),d.default(this,"renderError",i.hex("#ff0000"))}async animate(e,t){var s=this;const n=function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";const n=s.renderInfo(s.renderMarkdown(e)+".".repeat(r%4))+t;s.stdout(n)};let r=0;n();const i=setInterval((()=>(r++,n())),1e3),o=await t();return clearInterval(i),n("\n"),o}info(e){s.arrayFrom(e).forEach((e=>{this.log(this.renderInfo(this.renderMarkdown(e)))}))}error(e){s.arrayFrom(e).forEach((e=>{this.log(this.renderError(this.renderMarkdown(e)))}))}fail(e){this.error(e),process.exit(1)}success(e){s.arrayFrom(e).forEach((e=>{this.log(this.renderSuccess(this.renderMarkdown(e)))}))}renderMarkdown(e){const t=s.stringMatchAll(e,/\*\*(.*)\*\*/g);for(const s of t)e=e.replace(s[0],i.bold(s[1]));return e}log(e){s.arrayFrom(e).forEach((e=>console.log(e)))}stdout(e){o.cursorTo(process.stdout,0),o.clearLine(process.stdout,0),process.stdout.write(e)}});class m{static instantiate(e,t,s){return new m(e).instantiate(t,s)}constructor(e){d.default(this,"path",void 0),this.path=e}instantiate(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const s=this.getFilenameReplacements(t),r=[];e=`${e}/`.replace(/\/\//,"/");for(const i of u.getFiles(this.path)){const o=Object.entries(s).reduce(((e,t)=>{let[s,n]=t;return e.replaceAll(s,n)}),i.substring(this.path.length+1)),c=n.readFileSync(i).toString();u.write(e+o,a.render(c,t,void 0,["<%","%>"])),r.push(e+o)}return r}getFilenameReplacements(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return Object.entries(e).reduce(((e,n)=>{let[r,i]=n;return"object"==typeof i?Object.assign(e,this.getFilenameReplacements(i,`${r}.`)):e[`[${t}${r}]`]=s.toString(i),e}),{})}}function h(e){return r.resolve(__dirname,"../",e)}class f{constructor(e){d.default(this,"name",void 0),this.name=e}create(e){!u.exists(e)||u.isDirectory(e)&&u.isEmptyDirectory(e)||p.fail(`Folder at '${e}' already exists!`),m.instantiate(h("templates/app"),e,{app:{name:this.name,slug:s.stringToSlug(this.name)}})}}class g{static define(e){var t=this;e=e.command(this.command).description(this.description);for(const[t,s]of this.parameters)e=e.argument(`<${t}>`,s);for(const[t,s]of Object.entries(this.options)){const n="string"==typeof s?s:s.description,r="string"==typeof s?"string":s.type??"string";e=e.option("boolean"===r?`--${t}`:`--${t} <${r}>`,n)}e=e.action((function(){for(var e=arguments.length,s=new Array(e),n=0;n<e;n++)s[n]=arguments[n];return t.run.call(t,...s)}))}static async run(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];const n=new this(...t);await n.run()}async run(){}assertAerogelOrDirectory(e){const t=u.read("package.json");if(t?.includes("@aerogel/core"))return;if(e&&u.isDirectory(e))return;const s=e?`${e} folder does not exist.`:"package.json does not contain @aerogel/core.";p.fail(`${s} Are you sure this is an Aerogel app?`)}}d.default(g,"command",""),d.default(g,"description",""),d.default(g,"parameters",[]),d.default(g,"options",{});var y=s.facade(new class{constructor(){d.default(this,"cwd",null)}setWorkingDirectory(e){this.cwd=e}async run(e){await new Promise(((t,s)=>{c.exec(e,{cwd:this.cwd??void 0},(e=>{e?s(e):t()}))}))}});class w extends g{constructor(e,t){super(),d.default(this,"path",void 0),d.default(this,"options",void 0),this.path=e,this.options=t}async run(){const e=this.path,t=this.options.name??"Aerogel App";y.setWorkingDirectory(e),await this.createApp(t,e),await this.installDependencies(),await this.initializeGit(),p.success(["",`That's it! You can start working on **${t}** doing the following:`,` cd ${e}`," npm run dev","","Have fun!"])}async createApp(e,t){p.info(`Creating **${e}**...`),new f(e).create(t)}async installDependencies(){await p.animate("Installing dependencies",(async()=>{await y.run("npm install")}))}async initializeGit(){await p.animate("Initializing git",(async()=>{await y.run("git init"),await y.run("git add ."),await y.run('git commit -m "Start"')}))}}d.default(w,"command","create"),d.default(w,"description","Create AerogelJS app"),d.default(w,"parameters",[["path","Application path"]]),d.default(w,"options",{name:"Application name"});class v extends g{constructor(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};super(),d.default(this,"name",void 0),d.default(this,"options",void 0),this.name=e,this.options=t}async run(){this.assertAerogelOrDirectory("src/components"),this.options.story&&this.assertHistoireInstalled(),u.exists(`src/components/${this.name}.vue`)&&p.fail(`${this.name} component already exists!`);const e=m.instantiate(h("templates/component"),"src/components",{component:{name:this.name}});if(this.options.story){const t=m.instantiate(h("templates/component-story"),"src/components",{component:{name:this.name}});e.push(...t)}const t=e.map((e=>`- ${e}`)).join("\n");p.info(`${this.name} component created successfully! The following files were created:\n\n${t}`)}assertHistoireInstalled(){u.exists("src/main.histoire.ts")||p.fail("Histoire is not installed yet!")}}d.default(v,"command","generate:component"),d.default(v,"description","Generate an AerogelJS component"),d.default(v,"parameters",[["name","Component name"]]),d.default(v,"options",{story:{description:"Create component story using Histoire",type:"boolean"}});class x extends g{constructor(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};super(),d.default(this,"name",void 0),d.default(this,"options",void 0),this.name=e,this.options=t}async run(){this.assertAerogelOrDirectory("src/models"),u.exists(`src/models/${this.name}.ts`)&&p.fail(`${this.name} model already exists!`);const e=m.instantiate(h("templates/model"),"src/models",{model:{name:this.name,fieldsDefinition:this.getFieldsDefinition()},soukaiImports:this.options.fields?"FieldType, defineModelSchema":"defineModelSchema"}).map((e=>`- ${e}`)).join("\n");p.info(`${this.name} model created successfully! The following files were created:\n\n${e}`)}getFieldsDefinition(){if(!this.options.fields)return" //";return function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const s=e.split("\n"),n=t.indent??0;let r=0,i="";for(const e of s){const t=e.trim(),s=0===t.length;if(0===i.length){if(s)continue;r=e.indexOf(t[0]??""),i+=`${" ".repeat(n)}${t}\n`;continue}if(s){i+="\n";continue}const o=e.indexOf(t[0]??"");i+=`${" ".repeat(n+o-r)}${t}\n`}return i.trimEnd()}(this.options.fields.split(",").map((e=>{const[t,n]=e.split(":");return{name:t,type:s.stringToStudlyCase(n??"string")}})).reduce(((e,t)=>e+`\n${t.name}: FieldType.${t.type},`),""),{indent:8})}}d.default(x,"command","generate:model"),d.default(x,"description","Generate an AerogelJS model"),d.default(x,"parameters",[["name","Model name"]]),d.default(x,"options",{fields:"Create model with the given fields"});var $=s.facade(new class{run(t){const s=new e.Command;s.name("ag").description("AerogelJS CLI").version("0.0.0"),w.define(s),v.define(s),x.define(s),s.parse(t)}});exports.CLI=$;
2
- //# sourceMappingURL=aerogel-cli.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"aerogel-cli.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- import{Command as e}from"commander";import t from"@babel/runtime/helpers/esm/defineProperty";import{facade as s,arrayFrom as n,stringMatchAll as i,toString as r,stringToSlug as o,stringToStudlyCase as a}from"@noeldemartin/utils";import{existsSync as c,readFileSync as l,readdirSync as m,lstatSync as p,mkdirSync as d,writeFileSync as h}from"fs";import{resolve as u,dirname as f}from"path";import"core-js/modules/esnext.async-iterator.for-each.js";import"core-js/modules/esnext.iterator.constructor.js";import"core-js/modules/esnext.iterator.for-each.js";import{hex as g,bold as y}from"chalk";import{cursorTo as w,clearLine as v}from"readline";import"core-js/modules/esnext.async-iterator.reduce.js";import"core-js/modules/esnext.iterator.reduce.js";import{render as $}from"mustache";import{exec as x}from"child_process";import"core-js/modules/esnext.async-iterator.map.js";import"core-js/modules/esnext.iterator.map.js";var j=s(new class{exists(e){return c(e)}read(e){return this.isFile(e)?l(e).toString():null}getFiles(e){const t=m(e,{withFileTypes:!0}),s=[];for(const n of t){const t=u(e,n.name);n.isDirectory()?s.push(...this.getFiles(t)):s.push(t)}return s}isDirectory(e){return this.exists(e)&&p(e).isDirectory()}isFile(e){return this.exists(e)&&p(e).isFile()}isEmptyDirectory(e){return!!this.isDirectory(e)&&0===this.getFiles(e).length}makeDirectory(e){d(e,{recursive:!0})}write(e,t){c(f(e))||d(f(e),{recursive:!0}),h(e,t)}});var D=s(new class{constructor(){t(this,"renderInfo",g("#00ffff")),t(this,"renderSuccess",g("#00ff00")),t(this,"renderError",g("#ff0000"))}async animate(e,t){var s=this;const n=function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";const n=s.renderInfo(s.renderMarkdown(e)+".".repeat(i%4))+t;s.stdout(n)};let i=0;n();const r=setInterval((()=>(i++,n())),1e3),o=await t();return clearInterval(r),n("\n"),o}info(e){n(e).forEach((e=>{this.log(this.renderInfo(this.renderMarkdown(e)))}))}error(e){n(e).forEach((e=>{this.log(this.renderError(this.renderMarkdown(e)))}))}fail(e){this.error(e),process.exit(1)}success(e){n(e).forEach((e=>{this.log(this.renderSuccess(this.renderMarkdown(e)))}))}renderMarkdown(e){const t=i(e,/\*\*(.*)\*\*/g);for(const s of t)e=e.replace(s[0],y(s[1]));return e}log(e){n(e).forEach((e=>console.log(e)))}stdout(e){w(process.stdout,0),v(process.stdout,0),process.stdout.write(e)}});class A{static instantiate(e,t,s){return new A(e).instantiate(t,s)}constructor(e){t(this,"path",void 0),this.path=e}instantiate(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const s=this.getFilenameReplacements(t),n=[];e=`${e}/`.replace(/\/\//,"/");for(const i of j.getFiles(this.path)){const r=Object.entries(s).reduce(((e,t)=>{let[s,n]=t;return e.replaceAll(s,n)}),i.substring(this.path.length+1)),o=l(i).toString();j.write(e+r,$(o,t,void 0,["<%","%>"])),n.push(e+r)}return n}getFilenameReplacements(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return Object.entries(e).reduce(((e,s)=>{let[n,i]=s;return"object"==typeof i?Object.assign(e,this.getFilenameReplacements(i,`${n}.`)):e[`[${t}${n}]`]=r(i),e}),{})}}function F(e){return u(__dirname,"../",e)}class k{constructor(e){t(this,"name",void 0),this.name=e}create(e){!j.exists(e)||j.isDirectory(e)&&j.isEmptyDirectory(e)||D.fail(`Folder at '${e}' already exists!`),A.instantiate(F("templates/app"),e,{app:{name:this.name,slug:o(this.name)}})}}class I{static define(e){var t=this;e=e.command(this.command).description(this.description);for(const[t,s]of this.parameters)e=e.argument(`<${t}>`,s);for(const[t,s]of Object.entries(this.options)){const n="string"==typeof s?s:s.description,i="string"==typeof s?"string":s.type??"string";e=e.option("boolean"===i?`--${t}`:`--${t} <${i}>`,n)}e=e.action((function(){for(var e=arguments.length,s=new Array(e),n=0;n<e;n++)s[n]=arguments[n];return t.run.call(t,...s)}))}static async run(){for(var e=arguments.length,t=new Array(e),s=0;s<e;s++)t[s]=arguments[s];const n=new this(...t);await n.run()}async run(){}assertAerogelOrDirectory(e){const t=j.read("package.json");if(t?.includes("@aerogel/core"))return;if(e&&j.isDirectory(e))return;const s=e?`${e} folder does not exist.`:"package.json does not contain @aerogel/core.";D.fail(`${s} Are you sure this is an Aerogel app?`)}}t(I,"command",""),t(I,"description",""),t(I,"parameters",[]),t(I,"options",{});var S=s(new class{constructor(){t(this,"cwd",null)}setWorkingDirectory(e){this.cwd=e}async run(e){await new Promise(((t,s)=>{x(e,{cwd:this.cwd??void 0},(e=>{e?s(e):t()}))}))}});class b extends I{constructor(e,s){super(),t(this,"path",void 0),t(this,"options",void 0),this.path=e,this.options=s}async run(){const e=this.path,t=this.options.name??"Aerogel App";S.setWorkingDirectory(e),await this.createApp(t,e),await this.installDependencies(),await this.initializeGit(),D.success(["",`That's it! You can start working on **${t}** doing the following:`,` cd ${e}`," npm run dev","","Have fun!"])}async createApp(e,t){D.info(`Creating **${e}**...`),new k(e).create(t)}async installDependencies(){await D.animate("Installing dependencies",(async()=>{await S.run("npm install")}))}async initializeGit(){await D.animate("Initializing git",(async()=>{await S.run("git init"),await S.run("git add ."),await S.run('git commit -m "Start"')}))}}t(b,"command","create"),t(b,"description","Create AerogelJS app"),t(b,"parameters",[["path","Application path"]]),t(b,"options",{name:"Application name"});class E extends I{constructor(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};super(),t(this,"name",void 0),t(this,"options",void 0),this.name=e,this.options=s}async run(){this.assertAerogelOrDirectory("src/components"),this.options.story&&this.assertHistoireInstalled(),j.exists(`src/components/${this.name}.vue`)&&D.fail(`${this.name} component already exists!`);const e=A.instantiate(F("templates/component"),"src/components",{component:{name:this.name}});if(this.options.story){const t=A.instantiate(F("templates/component-story"),"src/components",{component:{name:this.name}});e.push(...t)}const t=e.map((e=>`- ${e}`)).join("\n");D.info(`${this.name} component created successfully! The following files were created:\n\n${t}`)}assertHistoireInstalled(){j.exists("src/main.histoire.ts")||D.fail("Histoire is not installed yet!")}}t(E,"command","generate:component"),t(E,"description","Generate an AerogelJS component"),t(E,"parameters",[["name","Component name"]]),t(E,"options",{story:{description:"Create component story using Histoire",type:"boolean"}});class O extends I{constructor(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};super(),t(this,"name",void 0),t(this,"options",void 0),this.name=e,this.options=s}async run(){this.assertAerogelOrDirectory("src/models"),j.exists(`src/models/${this.name}.ts`)&&D.fail(`${this.name} model already exists!`);const e=A.instantiate(F("templates/model"),"src/models",{model:{name:this.name,fieldsDefinition:this.getFieldsDefinition()},soukaiImports:this.options.fields?"FieldType, defineModelSchema":"defineModelSchema"}).map((e=>`- ${e}`)).join("\n");D.info(`${this.name} model created successfully! The following files were created:\n\n${e}`)}getFieldsDefinition(){if(!this.options.fields)return" //";return function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const s=e.split("\n"),n=t.indent??0;let i=0,r="";for(const e of s){const t=e.trim(),s=0===t.length;if(0===r.length){if(s)continue;i=e.indexOf(t[0]??""),r+=`${" ".repeat(n)}${t}\n`;continue}if(s){r+="\n";continue}const o=e.indexOf(t[0]??"");r+=`${" ".repeat(n+o-i)}${t}\n`}return r.trimEnd()}(this.options.fields.split(",").map((e=>{const[t,s]=e.split(":");return{name:t,type:a(s??"string")}})).reduce(((e,t)=>e+`\n${t.name}: FieldType.${t.type},`),""),{indent:8})}}t(O,"command","generate:model"),t(O,"description","Generate an AerogelJS model"),t(O,"parameters",[["name","Model name"]]),t(O,"options",{fields:"Create model with the given fields"});var C=s(new class{run(t){const s=new e;s.name("ag").description("AerogelJS CLI").version("0.0.0"),b.define(s),E.define(s),O.define(s),s.parse(t)}});export{C as CLI};
2
- //# sourceMappingURL=aerogel-cli.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"aerogel-cli.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,4 +0,0 @@
1
- /** @type {import('@noeldemartin/scripts').Config} */
2
- module.exports = {
3
- external: ['fs', '@noeldemartin/utils', 'chalk', 'child_process', 'commander', 'mustache', 'path', 'readline'],
4
- };
@@ -1,33 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { formatCodeBlock } from './utils';
4
-
5
- describe('Utils', () => {
6
-
7
- it('Formats code blocks', () => {
8
- // Arrange
9
- const raw = `
10
-
11
- const foo = 'bar';
12
-
13
- if (foo) {
14
- doSomething();
15
- }
16
-
17
- `;
18
- const formatted = [
19
- 'const foo = \'bar\';', //
20
- '', //
21
- 'if (foo) {', //
22
- ' doSomething();', //
23
- '}', //
24
- ].join('\n');
25
-
26
- // Act
27
- const actual = formatCodeBlock(raw);
28
-
29
- // Assert
30
- expect(actual).toEqual(formatted);
31
- });
32
-
33
- });
package/src/lib/utils.ts DELETED
@@ -1,44 +0,0 @@
1
- import { resolve } from 'path';
2
-
3
- export interface FormatCodeBlockOptions {
4
- indent?: number;
5
- }
6
-
7
- export function basePath(path: string): string {
8
- return resolve(__dirname, '../', path);
9
- }
10
-
11
- export function formatCodeBlock(code: string, options: FormatCodeBlockOptions = {}): string {
12
- const lines = code.split('\n');
13
- const indent = options.indent ?? 0;
14
- let originalIndent = 0;
15
- let formatted = '';
16
-
17
- for (const line of lines) {
18
- const trimmedLine = line.trim();
19
- const isEmptyLine = trimmedLine.length === 0;
20
-
21
- if (formatted.length === 0) {
22
- if (isEmptyLine) {
23
- continue;
24
- }
25
-
26
- originalIndent = line.indexOf(trimmedLine[0] ?? '');
27
- formatted += `${' '.repeat(indent)}${trimmedLine}\n`;
28
-
29
- continue;
30
- }
31
-
32
- if (isEmptyLine) {
33
- formatted += '\n';
34
-
35
- continue;
36
- }
37
-
38
- const lineIndent = line.indexOf(trimmedLine[0] ?? '');
39
-
40
- formatted += `${' '.repeat(indent + lineIndent - originalIndent)}${trimmedLine}\n`;
41
- }
42
-
43
- return formatted.trimEnd();
44
- }
@@ -1,17 +0,0 @@
1
- name: CI
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- ci:
7
- runs-on: ubuntu-latest
8
- steps:
9
- - uses: actions/checkout@v3
10
- - uses: actions/setup-node@v3
11
- with:
12
- node-version-file: '.nvmrc'
13
- - run: npm ci
14
- - run: npm run lint
15
- - run: npm run test:ci
16
- - run: npm run cy:test
17
- - run: npm run build
@@ -1 +0,0 @@
1
- lts/hydrogen
@@ -1,9 +0,0 @@
1
- describe('App', () => {
2
-
3
- beforeEach(() => cy.visit('/'));
4
-
5
- it('Shows get started link', () => {
6
- cy.see('Get started');
7
- });
8
-
9
- });
@@ -1,3 +0,0 @@
1
- import install from '@aerogel/cypress';
2
-
3
- install();