@travetto/scaffold 3.0.0-rc.9 → 3.0.1

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 CHANGED
@@ -3,11 +3,6 @@
3
3
  # App Scaffold
4
4
  ## App Scaffold for the Travetto framework
5
5
 
6
- **Install: @travetto/scaffold**
7
- ```bash
8
- npm install @travetto/scaffold
9
- ```
10
-
11
6
  A simple tool for scaffolding a reference project. To get started, you need to make sure:
12
7
 
13
8
  **Install: Setting up the necessary config**
@@ -15,11 +10,15 @@ A simple tool for scaffolding a reference project. To get started, you need to
15
10
  $ git config --global.username <Username> #Set your git username
16
11
  ```
17
12
 
18
- Once installed you can invoke the scaffolding by running
13
+ Once the necessary configuration is setup, you can invoke the scaffolding by running
19
14
 
20
15
  **Terminal: Running Generator**
21
16
  ```bash
22
17
  $ npx @travetto/scaffold
18
+
19
+ # or
20
+
21
+ $ npx @travetto/scaffold@<version-or-tag>
23
22
  ```
24
23
 
25
24
  The generator will ask about enabling the following features:
@@ -37,12 +36,12 @@ The code will establish some basic routes, specifically, `GET /` as the root end
37
36
  ### Additional Rest Features
38
37
  In addition to the core functionality, the `rest` feature has some useful sub-features. Specifically:
39
38
 
40
- [OpenAPI Specification](https://github.com/travetto/travetto/tree/main/module/openapi#readme "OpenAPI integration support for the travetto framework") support for the restful api. This will automatically expose a `openapi.yml` endpoint, and provide the necessary plumbing to support client generation.
39
+ [OpenAPI Specification](https://github.com/travetto/travetto/tree/main/module/openapi#readme "OpenAPI integration support for the Travetto framework") support for the restful api. This will automatically expose a `openapi.yml` endpoint, and provide the necessary plumbing to support client generation.
41
40
 
42
41
  [Logging](https://github.com/travetto/travetto/tree/main/module/log#readme "Logging framework that integrates at the console.log level.") support for better formatting, [debug](https://www.npmjs.com/package/debug) like support, and colorized output. This is generally useful for server logs, especially during development.
43
42
 
44
43
  ## Authentication
45
- Authentication is also supported on the Restful endpoints by selecting [Rest Auth](https://github.com/travetto/travetto/tree/main/module/auth-rest#readme "Rest authentication integration support for the travetto framework") during setup. This will support basic authentication running out of local memory, with user [REST Session](https://github.com/travetto/travetto/tree/main/module/rest-session#readme "Session provider for the travetto rest module.")s.
44
+ Authentication is also supported on the Restful endpoints by selecting [Rest Auth](https://github.com/travetto/travetto/tree/main/module/auth-rest#readme "Rest authentication integration support for the Travetto framework") during setup. This will support basic authentication running out of local memory, with user [REST Session](https://github.com/travetto/travetto/tree/main/module/rest-session#readme "Session provider for the travetto rest module.")s.
46
45
 
47
46
  ## Testing
48
47
  [Testing](https://github.com/travetto/travetto/tree/main/module/test#readme "Declarative test framework") can also be configured out of the box to provide simple test cases for the data model.
@@ -1,3 +1,17 @@
1
1
  #!/usr/bin/env node
2
- process.argv = [...process.argv.slice(0, 2), 'scaffold', ...process.argv.slice(2)];
3
- require('@travetto/compiler/bin/trv');
2
+
3
+ (async function () {
4
+ const args = [...process.argv.slice(0, 2), 'scaffold', ...process.argv.slice(2)];
5
+ if (process.env.npm_lifecycle_script === 'trv-scaffold') { // Is npx run
6
+ const path = await import('path');
7
+ const parts = process.env.PATH.split(path.delimiter);
8
+ const loc = parts.find(p => p.includes('npx') && p.includes('.bin'));
9
+ if (loc) {
10
+ const final = loc.split('/node_modules')[0];
11
+ args.push('-c', process.cwd());
12
+ process.chdir(final);
13
+ }
14
+ }
15
+ process.argv = args;
16
+ await import('@travetto/compiler/bin/trv.js');
17
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/scaffold",
3
- "version": "3.0.0-rc.9",
3
+ "version": "3.0.1",
4
4
  "description": "App Scaffold for the Travetto framework",
5
5
  "keywords": [
6
6
  "generator",
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "files": [
18
18
  "bin",
19
- "support"
19
+ "support",
20
+ "resources"
20
21
  ],
21
22
  "repository": {
22
23
  "url": "https://github.com/travetto/travetto.git",
@@ -26,8 +27,9 @@
26
27
  "trv-scaffold": "./bin/trv-scaffold.js"
27
28
  },
28
29
  "dependencies": {
29
- "@travetto/base": "^3.0.0-rc.8",
30
- "@travetto/cli": "^3.0.0-rc.9",
30
+ "@travetto/base": "^3.0.0",
31
+ "@travetto/compiler": "^3.0.0",
32
+ "@travetto/cli": "^3.0.0",
31
33
  "enquirer": "^2.3.6",
32
34
  "mustache": "^4.2.0"
33
35
  },
@@ -9,12 +9,6 @@
9
9
  "gitignore.txt": {
10
10
  "rename": ".gitignore"
11
11
  },
12
- "eslintrc.js.txt": {
13
- "rename": ".eslintrc.js",
14
- "requires": [
15
- "@travetto/eslint-plugin"
16
- ]
17
- },
18
12
  "resources/application.yml": {},
19
13
  "src/model/todo.ts": {
20
14
  "requires": [
@@ -4,11 +4,17 @@
4
4
  "name": "{{{author.name}}}"
5
5
  },
6
6
  "dependencies": {
7
- // {{#frameworkDependencies}}
7
+ // {{#dependencies}}
8
8
  "{{{.}}}": "^{{{frameworkVersion}}}",
9
- // {{/frameworkDependencies}}
9
+ // {{/dependencies}}
10
10
  "@travetto/app": "^{{{frameworkVersion}}}",
11
- "@travetto/cli": "^{{{frameworkVersion}}}",
11
+ "@travetto/cli": "^{{{frameworkVersion}}}"
12
+ },
13
+ "devDependencies": {
14
+ // {{#devDependencies}}
15
+ "{{{.}}}": "^{{{frameworkVersion}}}",
16
+ // {{/devDependencies}}
17
+ "@travetto/compiler": "^{{{frameworkVersion}}}",
12
18
  "@travetto/pack": "^{{{frameworkVersion}}}"
13
19
  },
14
20
  "description": "A Travetto-based project",
@@ -20,10 +26,11 @@
20
26
  ],
21
27
  "license": "MIT",
22
28
  "scripts": {
29
+ "watch": "npx trv watch",
23
30
  "build": "npx trv build",
24
31
  "start": "npx trv run rest",
25
32
  "test": "npx trv test",
26
- "lint": "npx eslint --ext .ts --ext .js ."
33
+ "lint": "npx trv lint"
27
34
  },
28
35
  "files": [
29
36
  "src",
@@ -1,7 +1,7 @@
1
1
  # {{#modules.rest}}
2
2
  rest:
3
3
  ssl:
4
- active: true
4
+ active: false
5
5
  context:
6
6
  active: true
7
7
  cors:
@@ -5,7 +5,7 @@ export class Todo implements ModelType {
5
5
  id: string;
6
6
  text: string;
7
7
  completed?: boolean;
8
- // {{#modules.auth-rest}} // @doc-exclude
8
+ // {{#modules.auth_rest}} // @doc-exclude
9
9
  userId?: string; // @doc-exclude
10
- // {{/modules.auth-rest}} // @doc-exclude
10
+ // {{/modules.auth_rest}} // @doc-exclude
11
11
  }
@@ -1,6 +1,8 @@
1
1
  import { Authorizer, Authenticator } from '@travetto/auth';
2
+ import { SessionModelⲐ } from '@travetto/rest-session';
2
3
  import { InjectableFactory } from '@travetto/di';
3
4
  import { AppError } from '@travetto/base';
5
+ import { ModelExpirySupport, MemoryModelService } from '@travetto/model';
4
6
 
5
7
  export const BasicAuthⲐ = Symbol.for('AUTH_BASIC');
6
8
 
@@ -12,6 +14,11 @@ class AuthConfig {
12
14
  return { authorize: p => p };
13
15
  }
14
16
 
17
+ @InjectableFactory(SessionModelⲐ)
18
+ static getStore(svc: MemoryModelService): ModelExpirySupport {
19
+ return svc;
20
+ }
21
+
15
22
  @InjectableFactory(BasicAuthⲐ)
16
23
  static getAuthenticator(): Authenticator<User> {
17
24
  return {
@@ -1,23 +1,24 @@
1
1
  import { Controller, Get, Put, Post, Delete } from '@travetto/rest';
2
2
  import { NotFoundError } from '@travetto/model';
3
3
  import { Inject } from '@travetto/di';
4
- import { ModelQuery, ModelQueryCrudSupport } from '@travetto/model-query';
4
+ import { ModelQuery } from '@travetto/model-query';
5
5
  import { Schema } from '@travetto/schema';
6
- // {{#modules.auth-rest}}
6
+ // {{#modules.auth_rest}}
7
7
  import { Authenticated } from '@travetto/auth-rest';
8
- // {{/modules.auth-rest}}
9
- // {{#modules.auth-rest-context}}
8
+ // {{/modules.auth_rest}}
9
+ // {{#modules.auth_rest_context}}
10
10
  import { AuthContextService } from '@travetto/auth-rest-context';
11
- // {{/modules.auth-rest-context}}
11
+ // {{/modules.auth_rest_context}}
12
+ import { $_modelService_$ } from '$_modelImport_$';
12
13
 
13
14
  import { Todo } from '../model/todo';
14
15
 
15
16
  @Schema()
16
17
  class Query {
17
18
  q: {
18
- // {{#modules.auth-rest-context}}
19
+ // {{#modules.auth_rest_context}}
19
20
  userId?: string;
20
- // {{/modules.auth-rest-context}}
21
+ // {{/modules.auth_rest_context}}
21
22
  } = {};
22
23
  }
23
24
 
@@ -25,18 +26,18 @@ class Query {
25
26
  * Controller for managing all aspects of the Todo lifecycle
26
27
  */
27
28
  @Controller('/todo')
28
- // {{#modules.auth-rest}}
29
+ // {{#modules.auth_rest}}
29
30
  @Authenticated()
30
- // {{/modules.auth-rest}}
31
+ // {{/modules.auth_rest}}
31
32
  export class TodoController {
32
33
 
33
34
  @Inject()
34
- source: ModelQueryCrudSupport;
35
+ source: $_modelService_$;
35
36
 
36
- // {{#modules.auth-rest-context}}
37
+ // {{#modules.auth_rest_context}}
37
38
  @Inject()
38
39
  auth: AuthContextService;
39
- // {{/modules.auth-rest-context}}
40
+ // {{/modules.auth_rest_context}}
40
41
 
41
42
  /**
42
43
  * Get all Todos
@@ -44,9 +45,9 @@ export class TodoController {
44
45
  @Get('/')
45
46
  async getAll(query: Query): Promise<Todo[]> {
46
47
  query.q ??= {};
47
- // {{#modules.auth-rest-context}}
48
+ // {{#modules.auth_rest_context}}
48
49
  query.q.userId = this.auth.get()?.id;
49
- // {{/modules.auth-rest-context}}
50
+ // {{/modules.auth_rest_context}}
50
51
  return this.source.query(Todo, { where: query.q });
51
52
  }
52
53
 
@@ -56,11 +57,11 @@ export class TodoController {
56
57
  @Get('/:id')
57
58
  async getOne(id: string): Promise<Todo> {
58
59
  const q: ModelQuery<Todo> = { where: { id } };
59
- // {{#modules.auth-rest-context}}
60
+ // {{#modules.auth_rest_context}}
60
61
  if (typeof q.where !== 'string') {
61
62
  q.where!.userId = this.auth.get()?.id;
62
63
  }
63
- // {{/modules.auth-rest-context}}
64
+ // {{/modules.auth_rest_context}}
64
65
  return this.source.queryOne(Todo, q);
65
66
  }
66
67
 
@@ -69,9 +70,9 @@ export class TodoController {
69
70
  */
70
71
  @Post('/')
71
72
  async save(todo: Todo): Promise<Todo> {
72
- // {{#modules.auth-rest-context}}
73
+ // {{#modules.auth_rest_context}}
73
74
  todo.userId = this.auth.get()?.id;
74
- // {{/modules.auth-rest-context}}
75
+ // {{/modules.auth_rest_context}}
75
76
  return this.source.create(Todo, todo);
76
77
  }
77
78
 
@@ -80,9 +81,9 @@ export class TodoController {
80
81
  */
81
82
  @Put('/:id')
82
83
  async update(todo: Todo): Promise<Todo> {
83
- // {{#modules.auth-rest-context}}
84
+ // {{#modules.auth_rest_context}}
84
85
  todo.userId = this.auth.get()?.id;
85
- // {{/modules.auth-rest-context}}
86
+ // {{/modules.auth_rest_context}}
86
87
  return this.source.update(Todo, todo);
87
88
  }
88
89
 
@@ -92,11 +93,11 @@ export class TodoController {
92
93
  @Delete('/:id')
93
94
  async remove(id: string): Promise<void> {
94
95
  const q: ModelQuery<Todo> = { where: { id } };
95
- // {{#modules.auth-rest-context}}
96
+ // {{#modules.auth_rest_context}}
96
97
  if (typeof q.where !== 'string') {
97
98
  q.where!.userId = this.auth.get()?.id;
98
99
  }
99
- // {{/modules.auth-rest-context}}
100
+ // {{/modules.auth_rest_context}}
100
101
  if (await this.source.deleteByQuery(Todo, q) !== 1) {
101
102
  throw new NotFoundError(Todo, id);
102
103
  }
@@ -3,12 +3,16 @@ import assert from 'assert';
3
3
  import { Suite, Test } from '@travetto/test';
4
4
  import { ModelCrudSupport } from '@travetto/model';
5
5
  import { BaseModelSuite } from '@travetto/model/support/test/base';
6
+ import { $_modelConfig_$, $_modelService_$ } from '$_modelImport_$';
6
7
 
7
8
  import { Todo } from '../../src/model/todo';
8
9
 
9
10
  @Suite('Simple CRUD')
10
11
  class TestCRUD extends BaseModelSuite<ModelCrudSupport> {
11
12
 
13
+ serviceClass = $_modelService_$;
14
+ configClass = $_modelConfig_$;
15
+
12
16
  @Test('save it')
13
17
  async save(): Promise<void> {
14
18
  const svc = await this.service;
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@travetto/compiler/tsconfig.trv.json"
3
+ }
@@ -1,24 +1,31 @@
1
1
  import fs from 'fs/promises';
2
2
  import mustache from 'mustache';
3
3
 
4
- import { path, RootIndex } from '@travetto/manifest';
4
+ import { cliTpl } from '@travetto/cli';
5
+ import { ManifestContext, path, RootIndex } from '@travetto/manifest';
5
6
  import { ExecUtil, ExecutionResult } from '@travetto/base';
7
+ import { GlobalTerminal } from '@travetto/terminal';
6
8
 
7
9
  import { Feature } from './features';
8
10
 
9
11
  type ListingEntry = { requires?: string[], rename?: string };
10
12
  type Listing = Record<string, ListingEntry>;
11
13
 
14
+ const DEV_DEPS = new Set([
15
+ '@travetto/test',
16
+ '@travetto/pack',
17
+ '@travetto/compiler',
18
+ '@travetto/command',
19
+ '@travetto/transformer',
20
+ '@travetto/eslint',
21
+ ]);
22
+
12
23
  export class Context {
13
24
 
14
25
  static #meetsRequirement(modules: string[], desired: string[]): boolean {
15
26
  let valid = true;
16
27
  for (const mod of desired) {
17
- if (mod.endsWith('-')) {
18
- valid = valid && !!modules.find(m => m.startsWith(mod));
19
- } else {
20
- valid = valid && modules.includes(mod);
21
- }
28
+ valid = valid && modules.includes(mod);
22
29
  if (!valid) {
23
30
  break;
24
31
  }
@@ -28,40 +35,38 @@ export class Context {
28
35
 
29
36
  #template: string;
30
37
  #targetDir: string;
31
- #frameworkDependencies: string[] = [];
38
+ #dependencies: string[] = [];
39
+ #devDependencies: string[] = [];
32
40
  #peerDependencies: string[] = [];
33
- #modules: Record<string, boolean>;
41
+ #featureContexts: Record<string, unknown>[] = [];
42
+
43
+ packageManager: ManifestContext['packageManager'] = 'npm';
34
44
 
35
45
  readonly name: string;
36
- readonly frameworkVersion = RootIndex.mainDigest().framework.replace(/[.]\d+$/, '.0');
37
46
 
38
47
  constructor(name: string, template: string, targetDir: string) {
39
- this.name = name;
48
+ this.name = name.replace(/[^a-zA-Z0-9]+/, '-').replace(/-+$/, '');
40
49
  this.#template = template;
41
50
  this.#targetDir = path.resolve(targetDir);
42
51
  }
43
52
 
44
- get modules(): Record<string, boolean> {
45
- if (!this.#modules) {
46
- this.#modules = this.#frameworkDependencies.map(x => x.split('/')).reduce((acc, [, v]) => ({ ...acc, [v]: true }), {});
47
- }
48
- return this.#modules;
49
- }
50
-
51
- get frameworkDependencies(): string[] {
52
- return this.#frameworkDependencies;
53
- }
53
+ #exec(cmd: string, args: string[]): Promise<ExecutionResult> {
54
+ const res = ExecUtil.spawn(cmd, args, {
55
+ cwd: this.destination(),
56
+ stdio: [0, 'pipe', 'pipe'],
57
+ isolatedEnv: true,
58
+ onStdErrorLine: line => GlobalTerminal.writeLines(cliTpl` ${{ identifier: [cmd, ...args].join(' ') }}: ${line}`)
59
+ }).result;
54
60
 
55
- get peerDependencies(): string[] {
56
- return this.#peerDependencies;
61
+ return res;
57
62
  }
58
63
 
59
- get moduleNames(): string[] {
60
- return [...Object.keys(this.modules)].filter(x => !x.includes('-'));
64
+ get selfPath(): string {
65
+ return RootIndex.getModule('@travetto/scaffold')!.sourcePath;
61
66
  }
62
67
 
63
68
  source(file?: string): string {
64
- return path.resolve('resources', 'templates', this.#template, ...file ? [file] : []);
69
+ return path.resolve(this.selfPath, 'resources', 'templates', this.#template, ...file ? [file] : []);
65
70
  }
66
71
 
67
72
  destination(file?: string): string {
@@ -73,8 +78,11 @@ export class Context {
73
78
  }
74
79
 
75
80
  async resolvedSourceListing(): Promise<[string, ListingEntry][]> {
76
- return Object.entries(await this.sourceListing)
77
- .filter(([, conf]) => !conf.requires || Context.#meetsRequirement(this.#frameworkDependencies, conf.requires));
81
+ const res = Object.entries(await this.sourceListing)
82
+ .filter(([, conf]) => !conf.requires ||
83
+ Context.#meetsRequirement([...this.#dependencies, ...this.#devDependencies], conf.requires));
84
+
85
+ return res;
78
86
  }
79
87
 
80
88
  async initialize(): Promise<void> {
@@ -91,10 +99,35 @@ export class Context {
91
99
  }
92
100
  }
93
101
 
102
+ templateContext(): Record<string, unknown> {
103
+ const modules = [...this.#dependencies, ...this.#devDependencies]
104
+ .map(x => path.basename(x)).reduce((acc, v) => ({ ...acc, [v.replace(/[-]/g, '_')]: true }), {});
105
+ const moduleNames = [...Object.keys(modules)];
106
+
107
+ const context = Object.assign({
108
+ frameworkVersion: RootIndex.mainDigest().framework.replace(/[.]\d+$/, '.0'),
109
+ name: this.name,
110
+ modules,
111
+ moduleNames,
112
+ dependencies: [...new Set(this.#dependencies)].sort((a, b) => a.localeCompare(b)),
113
+ devDependencies: [...new Set(this.#devDependencies)].sort((a, b) => a.localeCompare(b)),
114
+ }, ...this.#featureContexts);
115
+
116
+ return context;
117
+ }
118
+
94
119
  async template(file: string, { rename }: ListingEntry): Promise<void> {
95
120
  const contents = await fs.readFile(this.source(file), 'utf-8');
96
121
  const out = this.destination(rename ?? file);
97
- const rendered = mustache.render(contents, this).replace(/^\s*(\/\/|#)\s*\n/gsm, '');
122
+ const rendered = mustache.render(
123
+ contents
124
+ .replaceAll('$_', '{{{')
125
+ .replaceAll('_$', '}}}'),
126
+ this.templateContext(),
127
+ )
128
+ .replace(/(\/\/.*@doc-exclude.*)$/g, '')
129
+ .replace(/\s*(\/\/.*@doc-exclude.*)/g, '')
130
+ .replace(/^\s*(\/\/\s*)\n/gsm, '');
98
131
  await fs.mkdir(path.dirname(out), { recursive: true });
99
132
  await fs.writeFile(out, rendered, 'utf8');
100
133
  }
@@ -105,19 +138,59 @@ export class Context {
105
138
  }
106
139
  }
107
140
 
108
- async addDependency(feat: Feature): Promise<void> {
109
- if (feat.npm.startsWith('@travetto')) {
110
- this.#frameworkDependencies.push(feat.npm);
111
- } else {
112
- this.#peerDependencies.push(feat.npm);
141
+ async resolveFeature(feat: Feature): Promise<void> {
142
+ if (feat.package) {
143
+ if (feat.package.startsWith('@travetto')) {
144
+ if (DEV_DEPS.has(feat.package)) {
145
+ this.#devDependencies.push(feat.package);
146
+ } else {
147
+ this.#dependencies.push(feat.package);
148
+ }
149
+ } else {
150
+ this.#peerDependencies.push(feat.package);
151
+ }
152
+ }
153
+ if (feat.field) {
154
+ // @ts-expect-error
155
+ this[feat.field] = feat.value!;
156
+ }
157
+ if (feat.context) {
158
+ this.#featureContexts.push(feat.context);
113
159
  }
114
160
 
115
161
  for (const addon of (feat.addons ?? [])) {
116
- this.addDependency(addon);
162
+ this.resolveFeature(addon);
117
163
  }
118
164
  }
119
165
 
120
- exec(cmd: string, args: string[]): Promise<ExecutionResult> {
121
- return ExecUtil.spawn(cmd, args, { cwd: this.destination(), stdio: [0, 1, 2], isolatedEnv: true }).result;
166
+ async * install(): AsyncIterable<string | undefined> {
167
+
168
+ yield cliTpl`${{
169
+ type: 'Templating files'
170
+ }}`;
171
+ await this.templateResolvedFiles();
172
+
173
+ yield cliTpl`${{ type: 'Installing dependencies' }} `;
174
+ switch (this.packageManager) {
175
+ case 'npm': await this.#exec('npm', ['i']); break;
176
+ case 'yarn': await this.#exec('yarn', []); break;
177
+ default: throw new Error(`Unknown package manager: ${this.packageManager} `);
178
+ }
179
+
180
+ yield cliTpl`${{ type: 'Ensuring latest dependencies' }} `;
181
+ switch (this.packageManager) {
182
+ case 'npm': await this.#exec('npm', ['update', '-S']); break;
183
+ case 'yarn': await this.#exec('yarn', ['upgrade']); break;
184
+ default: throw new Error(`Unknown package manager: ${this.packageManager} `);
185
+ }
186
+
187
+ yield cliTpl`${{ type: 'Initial Build' }} `;
188
+ await this.#exec('npx', ['trv', 'build']);
189
+ if (this.#devDependencies.includes('@travetto/eslint')) {
190
+ yield cliTpl`${{ type: 'ESLint Registration' }} `;
191
+ await this.#exec('npx', ['trv', 'lint:register']);
192
+ }
193
+
194
+ yield cliTpl`${{ success: 'Successfully created' }} at ${{ path: this.#targetDir }} `;
122
195
  }
123
196
  }
@@ -1,6 +1,9 @@
1
1
  export type Feature = {
2
2
  title?: string;
3
- npm: string;
3
+ package?: string;
4
+ field?: string;
5
+ value?: string;
6
+ required?: boolean;
4
7
  version?: string;
5
8
  addons?: Feature[];
6
9
  choices?: Feature[];
@@ -10,42 +13,70 @@ export type Feature = {
10
13
  };
11
14
 
12
15
  export const FEATURES: Feature[] = [
16
+ {
17
+ title: 'Package Manager',
18
+ choices: [
19
+ { title: 'NPM', field: 'packageManager', value: 'npm' },
20
+ { title: 'Yarn', field: 'packageManager', value: 'yarn' }
21
+ ],
22
+ required: true,
23
+ default: 'npm'
24
+ },
13
25
  {
14
26
  title: 'Rest Framework',
15
- npm: '@travetto/rest',
27
+ package: '@travetto/rest',
16
28
  choices: [
17
- { title: 'Express.js', npm: '@travetto/rest-express' },
18
- { title: 'Express.js Lambda', npm: '@travetto/rest-express-lambda' },
19
- { title: 'KOA', npm: '@travetto/rest-koa' },
20
- { title: 'KOA Lambda', npm: '@travetto/rest-koa-lambda' },
21
- { title: 'Fastify', npm: '@travetto/rest-fastify' },
22
- { title: 'Fastify Lambda', npm: '@travetto/rest-fastify-lambda' },
29
+ { title: 'Express.js', package: '@travetto/rest-express' },
30
+ { title: 'Express.js Lambda', package: '@travetto/rest-express-lambda' },
31
+ { title: 'KOA', package: '@travetto/rest-koa' },
32
+ { title: 'KOA Lambda', package: '@travetto/rest-koa-lambda' },
33
+ { title: 'Fastify', package: '@travetto/rest-fastify' },
34
+ { title: 'Fastify Lambda', package: '@travetto/rest-fastify-lambda' },
23
35
  ],
24
36
  addons: [
25
- { title: 'OpenAPI', npm: '@travetto/openapi' },
26
- { title: 'Logging', npm: '@travetto/log' }
37
+ { title: 'OpenAPI', package: '@travetto/openapi' },
38
+ { title: 'Logging', package: '@travetto/log' }
27
39
  ],
28
40
  default: 'Express.js'
29
41
  },
30
- { title: 'Test Framework', npm: '@travetto/test' },
31
- { title: 'ESLint Support', npm: '@travetto/eslint-plugin' },
42
+ { title: 'Test Framework', package: '@travetto/test' },
43
+ { title: 'ESLint Support', package: '@travetto/eslint' },
32
44
  {
33
45
  title: 'Rest Authentication',
34
- npm: '@travetto/auth-rest',
46
+ package: '@travetto/auth-rest',
35
47
  addons: [
36
- { title: 'Rest Session', npm: '@travetto/rest-session', addons: [{ npm: '@travetto/auth-rest-session' }] },
37
- { title: 'Context', npm: '@travetto/auth-rest-session' }
48
+ { title: 'Rest Session', package: '@travetto/rest-session' },
49
+ { title: 'Auth Rest Session', package: '@travetto/auth-rest-session' },
50
+ { title: 'Auth Rest Context', package: '@travetto/auth-rest-context' }
38
51
  ]
39
52
  },
40
53
  {
41
54
  title: 'Data Modelling',
42
- npm: '@travetto/model',
55
+ package: '@travetto/model',
43
56
  choices: [
44
- { title: 'Elasticsearch', npm: '@travetto/model-elasticsearch' },
45
- { title: 'MongoDB', npm: '@travetto/model-mongo' },
46
- { title: 'MySQL', npm: '@travetto/model-mysql' },
47
- { title: 'PostgreSQL', npm: '@travetto/model-postgres' },
48
- { title: 'SQLite', npm: '@travetto/model-sqlite' }
57
+ {
58
+ title: 'Elasticsearch', package: '@travetto/model-elasticsearch',
59
+ context: { modelService: 'ElasticsearchModelService', modelConfig: 'ElasticsearchModelConfig', modelImport: '@travetto/model-elasticsearch' }
60
+ },
61
+ {
62
+ title: 'MongoDB', package: '@travetto/model-mongo',
63
+ context: { modelService: 'MongoModelService', modelConfig: 'MongoModelConfig', modelImport: '@travetto/model-mongo' }
64
+ },
65
+ {
66
+ title: 'MySQL', package: '@travetto/model-mysql',
67
+ context: { modelService: 'SQLModelService', modelConfig: 'SQLModelConfig', modelImport: '@travetto/model-sql' }
68
+ },
69
+ {
70
+ title: 'PostgreSQL', package: '@travetto/model-postgres',
71
+ context: { modelService: 'SQLModelService', modelConfig: 'SQLModelConfig', modelImport: '@travetto/model-sql' }
72
+ },
73
+ {
74
+ title: 'SQLite', package: '@travetto/model-sqlite',
75
+ context: { modelService: 'SQLModelService', modelConfig: 'SQLModelConfig', modelImport: '@travetto/model-sql' }
76
+ }
77
+ ],
78
+ addons: [
79
+ { title: 'Command Support', package: '@travetto/command' }
49
80
  ],
50
81
  default: 'MongoDB'
51
82
  },
@@ -1,13 +1,15 @@
1
1
  import enquirer from 'enquirer';
2
2
 
3
3
  import { path } from '@travetto/manifest';
4
- import { CliCommand, OptionConfig } from '@travetto/cli';
4
+ import { CliCommand, cliTpl, OptionConfig } from '@travetto/cli';
5
+ import { GlobalTerminal } from '@travetto/terminal';
5
6
 
6
7
  import { Context } from './bin/context';
7
8
  import { Feature, FEATURES } from './bin/features';
8
9
 
9
10
  type Options = {
10
11
  template: OptionConfig<string>;
12
+ cwd: OptionConfig<string>;
11
13
  dir: OptionConfig<string>;
12
14
  force: OptionConfig<boolean>;
13
15
  };
@@ -21,6 +23,7 @@ export class ScaffoldCommand extends CliCommand<Options> {
21
23
  getOptions(): Options {
22
24
  return {
23
25
  template: this.option({ def: 'todo', desc: 'Template' }),
26
+ cwd: this.option({ desc: 'Current Working Directory override' }),
24
27
  dir: this.option({ desc: 'Target Directory' }),
25
28
  force: this.boolOption({ desc: 'Force writing into an existing directory', def: false })
26
29
  };
@@ -59,7 +62,7 @@ export class ScaffoldCommand extends CliCommand<Options> {
59
62
 
60
63
  async * #resolveFeatures(features: Feature[], chosen = false): AsyncGenerator<Feature> {
61
64
  for (const feat of features) {
62
- if (!chosen) {
65
+ if (!chosen && !feat.required) {
63
66
  const ans = await enquirer.prompt<{ choice: boolean | string }>([{
64
67
  type: 'confirm',
65
68
  name: 'choice',
@@ -95,17 +98,26 @@ export class ScaffoldCommand extends CliCommand<Options> {
95
98
  return this.exit(1);
96
99
  }
97
100
 
98
- const ctx = new Context(
99
- name, this.cmd.template, path.resolve(this.cmd.dir ?? name)
100
- );
101
+ this.cmd.cwd ??= path.cwd();
102
+
103
+ if (!name && this.cmd.dir) {
104
+ name = path.basename(this.cmd.dir);
105
+ } else if (name && !this.cmd.dir) {
106
+ this.cmd.dir = path.resolve(this.cmd.cwd, name);
107
+ } else if (!name && !this.cmd.dir) {
108
+ console.error('Either a name or a target directory are required');
109
+ return this.exit(1);
110
+ }
111
+
112
+ const ctx = new Context(name, this.cmd.template, path.resolve(this.cmd.cwd, this.cmd.dir));
101
113
 
102
114
  if (!this.cmd.force) {
103
115
  await ctx.initialize();
104
116
  }
105
117
 
106
118
  try {
107
- for await (const dep of this.#resolveFeatures(FEATURES)) {
108
- await ctx.addDependency(dep);
119
+ for await (const feature of this.#resolveFeatures(FEATURES)) {
120
+ await ctx.resolveFeature(feature);
109
121
  }
110
122
  } catch (err) {
111
123
  if (err instanceof Error) {
@@ -114,9 +126,13 @@ export class ScaffoldCommand extends CliCommand<Options> {
114
126
  return this.exit(1);
115
127
  }
116
128
 
117
- await ctx.templateResolvedFiles();
129
+ console.log(cliTpl`\n${{ title: 'Creating Application' }}\n${'-'.repeat(30)}`);
118
130
 
119
- await ctx.exec('npm', ['i']);
120
- await ctx.exec('npm', ['run', 'build']);
131
+ await GlobalTerminal.streamLinesWithWaiting(ctx.install(), {
132
+ position: 'inline',
133
+ end: false,
134
+ committedPrefix: '>',
135
+ cycleDelay: 100
136
+ });
121
137
  }
122
138
  }
@@ -1,14 +0,0 @@
1
- {
2
- "ignorePatterns": [
3
- "resources/templates"
4
- ],
5
- "overrides": [
6
- {
7
- "files": "resources/**/*.ts",
8
- "rules": {
9
- "@typescript-eslint/consistent-type-assertions": 0,
10
- "@typescript-eslint/explicit-function-return-type": 0
11
- }
12
- }
13
- ]
14
- }
@@ -1 +0,0 @@
1
- module.exports = require('@travetto/eslint-plugin/bin/init');
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "./node_modules/@travetto/compiler/tsconfig.trv.json"
3
- }