Skip to content

Commit 3c6265b

Browse files
committed
feat(generate): accept type and crud options on the resource schematic
Backports the master-branch fix for #3229 (PR #3275) to v12.0.0. The `@nestjs/schematics` resource generator defines `--type` and `--crud` options in its schema.json, but the v12 CLI does not register either flag on the `generate` command. Running: nest g resource users --type rest --crud exits with "error: unknown option '--type'" even though the schematic itself would handle both flags correctly. - register `--type [type]` and `--crud` on the generate command - forward both into the generate action and on to the schematic - only forward `--crud` when explicitly passed (avoid toggling default behavior on users who never opted in) The master-branch PR noted that "no automated tests were added because there are no existing e2e tests for generate resource" — this PR adds a focused unit spec for `GenerateAction` covering forwarding of the new options individually and together.
1 parent 3257fbf commit 3c6265b

4 files changed

Lines changed: 142 additions & 0 deletions

File tree

actions/generate.action.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ const mapContextToSchematicOptions = (
161161
options.push(new SchematicOption('project', context.project));
162162
if (context.skipImport !== undefined)
163163
options.push(new SchematicOption('skipImport', context.skipImport));
164+
if (context.type !== undefined)
165+
options.push(new SchematicOption('type', context.type));
166+
if (context.crud === true)
167+
options.push(new SchematicOption('crud', true));
164168
// 'schematic', 'spec', 'flat', 'specFileSuffix' are handled separately
165169
return options;
166170
};

commands/context/generate.context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ export interface GenerateCommandContext {
1010
project?: string;
1111
skipImport: boolean;
1212
format: boolean;
13+
type?: string;
14+
crud?: boolean;
1315
}

commands/generate.command.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export class GenerateCommand extends AbstractCommand {
5252
'-c, --collection [collectionName]',
5353
'Schematics collection to use.',
5454
)
55+
.option(
56+
'--type [type]',
57+
'Transport layer type (rest, graphql-code-first, graphql-schema-first, microservice, ws).',
58+
)
59+
.option('--crud', 'Generate CRUD entry points for a resource.')
5560
.action(
5661
async (
5762
schematic: string,
@@ -71,6 +76,8 @@ export class GenerateCommand extends AbstractCommand {
7176
project: options.project,
7277
skipImport: options.skipImport,
7378
format: options.format === true,
79+
type: options.type,
80+
crud: options.crud === true ? true : undefined,
7481
};
7582

7683
await this.action.handle(context);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mockExecute = vi.fn().mockResolvedValue(undefined);
4+
vi.mock('../../lib/schematics/index.js', async () => {
5+
const original = await vi.importActual('../../lib/schematics/index.js');
6+
return {
7+
...original,
8+
CollectionFactory: {
9+
create: () => ({
10+
execute: mockExecute,
11+
}),
12+
},
13+
};
14+
});
15+
16+
vi.mock('../../lib/utils/load-configuration.js', () => ({
17+
loadConfiguration: vi.fn().mockResolvedValue({
18+
language: 'ts',
19+
sourceRoot: 'src',
20+
collection: '@nestjs/schematics',
21+
entryFile: 'main',
22+
exec: 'node',
23+
projects: {},
24+
monorepo: false,
25+
compilerOptions: {},
26+
generateOptions: {},
27+
}),
28+
}));
29+
30+
import { GenerateAction } from '../../actions/generate.action.js';
31+
import { GenerateCommandContext } from '../../commands/context/generate.context.js';
32+
import { SchematicOption } from '../../lib/schematics/index.js';
33+
34+
describe('GenerateAction', () => {
35+
let action: GenerateAction;
36+
37+
const baseContext = (
38+
overrides: Partial<GenerateCommandContext> = {},
39+
): GenerateCommandContext => ({
40+
schematic: 'resource',
41+
name: 'users',
42+
path: undefined,
43+
dryRun: false,
44+
flat: false,
45+
spec: { value: true, passedAsInput: false },
46+
specFileSuffix: undefined,
47+
collection: undefined,
48+
project: undefined,
49+
skipImport: false,
50+
format: false,
51+
...overrides,
52+
});
53+
54+
const findOption = (
55+
schematicOptions: SchematicOption[],
56+
name: string,
57+
): SchematicOption | undefined =>
58+
schematicOptions.find((opt) =>
59+
opt.toCommandString().startsWith(`--${name}=`) ||
60+
opt.toCommandString() === `--${name}`,
61+
);
62+
63+
beforeEach(() => {
64+
mockExecute.mockClear();
65+
action = new GenerateAction();
66+
});
67+
68+
describe('--type option', () => {
69+
it('should forward --type to the schematic when provided', async () => {
70+
await action.handle(baseContext({ type: 'rest' }));
71+
72+
expect(mockExecute).toHaveBeenCalledTimes(1);
73+
const [, schematicOptions] = mockExecute.mock.calls[0];
74+
const typeOption = findOption(schematicOptions, 'type');
75+
expect(typeOption).toBeDefined();
76+
expect(typeOption!.toCommandString()).toBe('--type="rest"');
77+
});
78+
79+
it('should not forward --type when undefined', async () => {
80+
await action.handle(baseContext({ type: undefined }));
81+
82+
const [, schematicOptions] = mockExecute.mock.calls[0];
83+
const typeOption = findOption(schematicOptions, 'type');
84+
expect(typeOption).toBeUndefined();
85+
});
86+
});
87+
88+
describe('--crud option', () => {
89+
it('should forward --crud to the schematic when true', async () => {
90+
await action.handle(baseContext({ crud: true }));
91+
92+
const [, schematicOptions] = mockExecute.mock.calls[0];
93+
const crudOption = findOption(schematicOptions, 'crud');
94+
expect(crudOption).toBeDefined();
95+
expect(crudOption!.toCommandString()).toBe('--crud');
96+
});
97+
98+
it('should not forward --crud when falsy', async () => {
99+
await action.handle(baseContext({ crud: false }));
100+
101+
const [, schematicOptions] = mockExecute.mock.calls[0];
102+
const crudOption = findOption(schematicOptions, 'crud');
103+
expect(crudOption).toBeUndefined();
104+
});
105+
106+
it('should not forward --crud when undefined', async () => {
107+
await action.handle(baseContext({ crud: undefined }));
108+
109+
const [, schematicOptions] = mockExecute.mock.calls[0];
110+
const crudOption = findOption(schematicOptions, 'crud');
111+
expect(crudOption).toBeUndefined();
112+
});
113+
});
114+
115+
it('should support --type and --crud together on the resource schematic', async () => {
116+
await action.handle(
117+
baseContext({ schematic: 'resource', type: 'rest', crud: true }),
118+
);
119+
120+
const [schematicName, schematicOptions] = mockExecute.mock.calls[0];
121+
expect(schematicName).toBe('resource');
122+
expect(findOption(schematicOptions, 'type')!.toCommandString()).toBe(
123+
'--type="rest"',
124+
);
125+
expect(findOption(schematicOptions, 'crud')!.toCommandString()).toBe(
126+
'--crud',
127+
);
128+
});
129+
});

0 commit comments

Comments
 (0)