Skip to content

Commit d820637

Browse files
authored
fix(cli): rename blueprint to template and update related functionality (#259)
This commit refactors the CLI to replace the term "blueprint" with "template" for better clarity and consistency. Fixes WDX-111
1 parent 1affc9e commit d820637

File tree

6 files changed

+165
-139
lines changed

6 files changed

+165
-139
lines changed

packages/cli/src/commands/create/actions.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
33
import { vol } from 'memfs';
44
import { beforeEach, describe, expect, it, type MockedFunction, vi } from 'vitest';
55
import open from 'open';
6-
import { createEnvFile, extractPortFromTopics, fetchBlueprintRepositories, generateProject, generateSpaceUrl, openSpaceInBrowser, repositoryToBlueprint } from './actions';
6+
import { createEnvFile, extractPortFromTopics, fetchBlueprintRepositories, generateProject, generateSpaceUrl, openSpaceInBrowser, repositoryToTemplate } from './actions';
77
import * as filesystem from '../../utils/filesystem';
88

99
// Mock external dependencies
@@ -309,8 +309,8 @@ describe('extractPortFromTopics', () => {
309309
});
310310
});
311311

312-
describe('repositoryToBlueprint', () => {
313-
it('should convert a repo object to a DynamicBlueprint', () => {
312+
describe('repositoryToTemplate', () => {
313+
it('should convert a repo object to a DynamicTemplate', () => {
314314
// Mock repo object with expected fields
315315
const repo = {
316316
name: 'blueprint-core-vue',
@@ -319,8 +319,8 @@ describe('repositoryToBlueprint', () => {
319319
description: 'A Vue starter',
320320
updated_at: '2024-01-01T00:00:00Z',
321321
};
322-
const blueprint = repositoryToBlueprint(repo);
323-
expect(blueprint).toEqual({
322+
const template = repositoryToTemplate(repo);
323+
expect(template).toEqual({
324324
name: 'Vue',
325325
value: 'vue',
326326
template: 'https://github.com/storyblok/blueprint-core-vue.git',
@@ -338,8 +338,8 @@ describe('repositoryToBlueprint', () => {
338338
description: 'A React starter',
339339
updated_at: '2024-01-01T00:00:00Z',
340340
};
341-
const blueprint = repositoryToBlueprint(repo);
342-
expect(blueprint.location).toBe('https://localhost:3000/');
341+
const template = repositoryToTemplate(repo);
342+
expect(template.location).toBe('https://localhost:3000/');
343343
});
344344
});
345345

packages/cli/src/commands/create/actions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FileSystemError, handleFileSystemError } from '../../utils/error/filesy
77
import open from 'open';
88
import { createOctokit } from '../../github';
99
import { handleAPIError } from '../../utils';
10-
import type { DynamicBlueprint } from './constants';
10+
import type { DynamicTemplate } from './constants';
1111
/**
1212
* Generates a new project from a Storyblok blueprint template
1313
* @param blueprint - The blueprint name (react, vue, svelte, etc.)
@@ -159,7 +159,7 @@ export const extractPortFromTopics = (topics: string[]): string => {
159159
* @param repo - GitHub repository data
160160
* @returns Formatted blueprint object
161161
*/
162-
export const repositoryToBlueprint = (repo: any): DynamicBlueprint => {
162+
export const repositoryToTemplate = (repo: any): DynamicTemplate => {
163163
const technology = repo.name.replace('blueprint-core-', '');
164164
const port = extractPortFromTopics(repo.topics || []);
165165

@@ -188,7 +188,7 @@ export const fetchBlueprintRepositories = async () => {
188188
// Filter and convert repositories to blueprints
189189
const blueprints = data.items
190190
.filter(repo => repo.name.startsWith('blueprint-core-'))
191-
.map(repositoryToBlueprint)
191+
.map(repositoryToTemplate)
192192
.sort((a, b) => a.name.localeCompare(b.name));
193193

194194
return blueprints;

packages/cli/src/commands/create/constants.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
export interface CreateOptions {
2-
blueprint?: string;
2+
template?: string;
3+
blueprint?: string; // Deprecated, use template instead
34
skipSpace?: boolean;
45
}
56

6-
export const blueprints = {
7+
export const templates = {
78
REACT: {
89
name: 'React',
910
value: 'react',
@@ -49,9 +50,9 @@ export const blueprints = {
4950
} as const;
5051

5152
/**
52-
* Interface for dynamic blueprints
53+
* Interface for dynamic templates
5354
*/
54-
export interface DynamicBlueprint {
55+
export interface DynamicTemplate {
5556
name: string;
5657
value: string;
5758
template: string;

packages/cli/src/commands/create/index.test.ts

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createSpace } from '../spaces';
77
import type { Space } from '../spaces/actions';
88
import { mapiClient } from '../../api';
99
import { createEnvFile, fetchBlueprintRepositories, generateProject, generateSpaceUrl, openSpaceInBrowser } from './actions';
10-
import { blueprints } from './constants';
10+
import { templates } from './constants';
1111

1212
// Mock all dependencies
1313
vi.mock('./actions', () => ({
@@ -119,8 +119,8 @@ describe('createCommand', () => {
119119
);
120120
});
121121

122-
describe('blueprint validation', () => {
123-
it('should accept valid blueprint via --blueprint flag', async () => {
122+
describe('template validation', () => {
123+
it('should accept valid template via --template flag', async () => {
124124
const mockSpace = createMockSpace();
125125

126126
vi.mocked(generateProject).mockResolvedValue(undefined);
@@ -132,19 +132,80 @@ describe('createCommand', () => {
132132
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
133133
]);
134134

135-
await createCommand.parseAsync(['node', 'test', './my-project', '--blueprint', 'react']);
135+
await createCommand.parseAsync(['node', 'test', './my-project', '--template', 'react']);
136136

137137
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', expect.any(String));
138138

139139
// Check if createSpace was called (this is failing with 0 calls)
140140
expect(createSpace).toHaveBeenCalled();
141141
expect(createSpace).toHaveBeenCalledWith({
142142
name: 'My Project',
143-
domain: blueprints.REACT.location,
143+
domain: templates.REACT.location,
144144
});
145145
});
146146

147-
it('should warn and show interactive selection for invalid blueprint', async () => {
147+
it('should warn and show interactive selection for invalid template', async () => {
148+
vi.mocked(select).mockResolvedValue('vue');
149+
vi.mocked(input).mockResolvedValue('./my-vue-project');
150+
151+
const mockSpace = createMockSpace();
152+
153+
vi.mocked(generateProject).mockResolvedValue(undefined);
154+
vi.mocked(createSpace).mockResolvedValue(mockSpace);
155+
vi.mocked(createEnvFile).mockResolvedValue(undefined);
156+
vi.mocked(openSpaceInBrowser).mockResolvedValue(undefined);
157+
vi.mocked(fetchBlueprintRepositories).mockResolvedValue([
158+
{ name: 'React', value: 'react', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
159+
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
160+
]);
161+
162+
await createCommand.parseAsync(['node', 'test', '--template', 'invalid-template']);
163+
164+
expect(konsola.warn).toHaveBeenCalledWith(
165+
expect.stringContaining('Invalid template "invalid-template"'),
166+
);
167+
expect(select).toHaveBeenCalledWith(expect.objectContaining({
168+
message: 'Please select the technology you would like to use:',
169+
}));
170+
});
171+
172+
it('should accept valid template via deprecated --blueprint flag with warning', async () => {
173+
const mockSpace = createMockSpace();
174+
175+
vi.mocked(generateProject).mockResolvedValue(undefined);
176+
vi.mocked(createSpace).mockResolvedValue(mockSpace);
177+
vi.mocked(createEnvFile).mockResolvedValue(undefined);
178+
vi.mocked(openSpaceInBrowser).mockResolvedValue(undefined);
179+
vi.mocked(fetchBlueprintRepositories).mockResolvedValue([
180+
{ name: 'React', value: 'react', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
181+
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
182+
]);
183+
184+
await createCommand.parseAsync(['node', 'test', './my-project', '--blueprint', 'react']);
185+
186+
expect(konsola.warn).toHaveBeenCalledWith('The --blueprint flag is deprecated. Please use --template instead.');
187+
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', expect.any(String));
188+
});
189+
190+
it('should prioritize --template over --blueprint when both are provided', async () => {
191+
const mockSpace = createMockSpace();
192+
193+
vi.mocked(generateProject).mockResolvedValue(undefined);
194+
vi.mocked(createSpace).mockResolvedValue(mockSpace);
195+
vi.mocked(createEnvFile).mockResolvedValue(undefined);
196+
vi.mocked(openSpaceInBrowser).mockResolvedValue(undefined);
197+
vi.mocked(fetchBlueprintRepositories).mockResolvedValue([
198+
{ name: 'React', value: 'react', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
199+
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
200+
]);
201+
202+
await createCommand.parseAsync(['node', 'test', './my-project', '--blueprint', 'react', '--template', 'vue']);
203+
204+
expect(konsola.warn).toHaveBeenCalledWith('Both --blueprint and --template provided. Using --template and ignoring --blueprint.');
205+
expect(generateProject).toHaveBeenCalledWith('vue', 'my-project', expect.any(String));
206+
});
207+
208+
it('should warn about deprecated --blueprint flag and show interactive selection for invalid value', async () => {
148209
vi.mocked(select).mockResolvedValue('vue');
149210
vi.mocked(input).mockResolvedValue('./my-vue-project');
150211

@@ -161,8 +222,9 @@ describe('createCommand', () => {
161222

162223
await createCommand.parseAsync(['node', 'test', '--blueprint', 'invalid-blueprint']);
163224

225+
expect(konsola.warn).toHaveBeenCalledWith('The --blueprint flag is deprecated. Please use --template instead.');
164226
expect(konsola.warn).toHaveBeenCalledWith(
165-
expect.stringContaining('Invalid blueprint "invalid-blueprint"'),
227+
expect.stringContaining('Invalid template "invalid-blueprint"'),
166228
);
167229
expect(select).toHaveBeenCalledWith(expect.objectContaining({
168230
message: 'Please select the technology you would like to use:',
@@ -171,7 +233,7 @@ describe('createCommand', () => {
171233
});
172234

173235
describe('interactive mode', () => {
174-
it('should prompt for blueprint selection when none provided', async () => {
236+
it('should prompt for template selection when none provided', async () => {
175237
vi.mocked(select).mockResolvedValue('react');
176238
vi.mocked(input).mockResolvedValue('./my-react-project');
177239

@@ -266,7 +328,7 @@ describe('createCommand', () => {
266328
vi.mocked(createEnvFile).mockResolvedValue(undefined);
267329
vi.mocked(openSpaceInBrowser).mockResolvedValue(undefined);
268330

269-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
331+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
270332

271333
// Verify project generation
272334
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', expect.any(String));
@@ -278,7 +340,7 @@ describe('createCommand', () => {
278340
// Verify space creation
279341
expect(createSpace).toHaveBeenCalledWith({
280342
name: 'My Project',
281-
domain: blueprints.REACT.location,
343+
domain: templates.REACT.location,
282344
});
283345

284346
// Verify .env file creation
@@ -304,7 +366,7 @@ describe('createCommand', () => {
304366
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
305367
]);
306368

307-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
369+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
308370

309371
expect(handleError).toHaveBeenCalledWith(generateError, undefined);
310372
});
@@ -319,7 +381,7 @@ describe('createCommand', () => {
319381
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
320382
]);
321383

322-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
384+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
323385

324386
expect(generateProject).toHaveBeenCalled();
325387
expect(handleError).toHaveBeenCalledWith(spaceError, undefined);
@@ -339,7 +401,7 @@ describe('createCommand', () => {
339401
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
340402
]);
341403

342-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
404+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
343405

344406
expect(konsola.warn).toHaveBeenCalledWith('Failed to create .env file: Permission denied');
345407
expect(konsola.info).toHaveBeenCalledWith(
@@ -364,7 +426,7 @@ describe('createCommand', () => {
364426
vi.mocked(openSpaceInBrowser).mockRejectedValue(browserError);
365427
vi.mocked(generateSpaceUrl).mockReturnValue('https://app.storyblok.com/#/me/spaces/12345/dashboard');
366428

367-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
429+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
368430

369431
expect(konsola.warn).toHaveBeenCalledWith('Failed to open browser: Failed to open browser');
370432
expect(generateSpaceUrl).toHaveBeenCalledWith(12345, 'eu');
@@ -383,7 +445,7 @@ describe('createCommand', () => {
383445
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
384446
]);
385447

386-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
448+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
387449

388450
expect(generateProject).not.toHaveBeenCalled();
389451
expect(createSpace).not.toHaveBeenCalled();
@@ -401,7 +463,7 @@ describe('createCommand', () => {
401463
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
402464
]);
403465

404-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react']);
466+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react']);
405467

406468
expect(mapiClient).toHaveBeenCalledWith({
407469
token: 'test-token',
@@ -410,14 +472,14 @@ describe('createCommand', () => {
410472
});
411473
});
412474

413-
describe('different blueprints', () => {
475+
describe('different templates', () => {
414476
it.each([
415477
['react', 'REACT'],
416478
['vue', 'VUE'],
417479
['svelte', 'SVELTE'],
418480
['nuxt', 'NUXT'],
419481
['next', 'NEXT'],
420-
])('should handle %s blueprint correctly', async (blueprint, blueprintKey) => {
482+
])('should handle %s template correctly', async (template, templateKey) => {
421483
// Use createMockSpace to ensure the mock matches the Space type
422484
const mockSpace = createMockSpace({ id: 12345, first_token: 'space-token-123' });
423485

@@ -433,12 +495,12 @@ describe('createCommand', () => {
433495
{ name: 'Next', value: 'next', template: '', location: 'https://localhost:3000/', description: '', updated_at: '' },
434496
]);
435497

436-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', blueprint]);
498+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', template]);
437499

438-
expect(generateProject).toHaveBeenCalledWith(blueprint, 'my-project', expect.any(String));
500+
expect(generateProject).toHaveBeenCalledWith(template, 'my-project', expect.any(String));
439501
expect(createSpace).toHaveBeenCalledWith({
440502
name: 'My Project',
441-
domain: blueprints[blueprintKey as keyof typeof blueprints].location,
503+
domain: templates[templateKey as keyof typeof templates].location,
442504
});
443505
});
444506
});
@@ -457,7 +519,7 @@ describe('createCommand', () => {
457519
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
458520
]);
459521

460-
await createCommand.parseAsync(['node', 'test', './projects/my-project', '--blueprint', 'react']);
522+
await createCommand.parseAsync(['node', 'test', './projects/my-project', '--template', 'react']);
461523

462524
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', expect.stringContaining('projects'));
463525
expect(createEnvFile).toHaveBeenCalledWith(expect.stringContaining('my-project'), 'space-token-123');
@@ -476,7 +538,7 @@ describe('createCommand', () => {
476538
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
477539
]);
478540

479-
await createCommand.parseAsync(['node', 'test', '/absolute/path/my-project', '--blueprint', 'react']);
541+
await createCommand.parseAsync(['node', 'test', '/absolute/path/my-project', '--template', 'react']);
480542

481543
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', '/absolute/path');
482544
});
@@ -490,7 +552,7 @@ describe('createCommand', () => {
490552
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
491553
]);
492554

493-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react', '--skip-space']);
555+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react', '--skip-space']);
494556

495557
// Verify project generation still happens
496558
expect(generateProject).toHaveBeenCalledWith('react', 'my-project', expect.any(String));
@@ -545,7 +607,7 @@ describe('createCommand', () => {
545607
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
546608
]);
547609

548-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react', '--skip-space']);
610+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react', '--skip-space']);
549611

550612
// Should show project creation success
551613
expect(konsola.ok).toHaveBeenCalledWith(
@@ -574,7 +636,7 @@ describe('createCommand', () => {
574636
{ name: 'Vue', value: 'vue', template: '', location: 'https://localhost:5173/', description: '', updated_at: '' },
575637
]);
576638

577-
await createCommand.parseAsync(['node', 'test', 'my-project', '--blueprint', 'react', '--skip-space']);
639+
await createCommand.parseAsync(['node', 'test', 'my-project', '--template', 'react', '--skip-space']);
578640

579641
// Should still handle the error properly
580642
expect(handleError).toHaveBeenCalledWith(generateError, undefined);

0 commit comments

Comments
 (0)