-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): Implement "add entity" command
- Loading branch information
1 parent
795b013
commit ad87531
Showing
33 changed files
with
423 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import fs from 'fs-extra'; | ||
import path from 'path'; | ||
|
||
// This build script copies all .template.ts files from the "src" directory to the "dist" directory. | ||
// This is necessary because the .template.ts files are used to generate the actual source files. | ||
const templateFiles = findFilesWithSuffix(path.join(__dirname, 'src'), '.template.ts'); | ||
for (const file of templateFiles) { | ||
// copy to the equivalent path in the "dist" rather than "src" directory | ||
const relativePath = path.relative(path.join(__dirname, 'src'), file); | ||
const distPath = path.join(__dirname, 'dist', relativePath); | ||
fs.ensureDirSync(path.dirname(distPath)); | ||
fs.copyFileSync(file, distPath); | ||
} | ||
|
||
function findFilesWithSuffix(directory: string, suffix: string): string[] { | ||
const files: string[] = []; | ||
|
||
function traverseDirectory(dir: string) { | ||
const dirContents = fs.readdirSync(dir); | ||
|
||
dirContents.forEach(item => { | ||
const itemPath = path.join(dir, item); | ||
const stats = fs.statSync(itemPath); | ||
|
||
if (stats.isDirectory()) { | ||
traverseDirectory(itemPath); | ||
} else { | ||
if (item.endsWith(suffix)) { | ||
files.push(itemPath); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
traverseDirectory(directory); | ||
|
||
return files; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { outro, spinner } from '@clack/prompts'; | ||
import { paramCase } from 'change-case'; | ||
import path from 'path'; | ||
|
||
import { getCustomEntityName, selectPluginClass } from '../../../shared/shared-prompts'; | ||
import { renderEntity } from '../../../shared/shared-scaffold/entity'; | ||
import { createSourceFileFromTemplate, getTsMorphProject } from '../../../utilities/ast-utils'; | ||
import { Scaffolder } from '../../../utilities/scaffolder'; | ||
|
||
import { addEntityToPlugin } from './codemods/add-entity-to-plugin/add-entity-to-plugin'; | ||
|
||
const cancelledMessage = 'Add entity cancelled'; | ||
|
||
export interface AddEntityTemplateContext { | ||
entity: { | ||
className: string; | ||
fileName: string; | ||
}; | ||
} | ||
|
||
export async function addEntity() { | ||
const projectSpinner = spinner(); | ||
projectSpinner.start('Analyzing project...'); | ||
await new Promise(resolve => setTimeout(resolve, 100)); | ||
const project = getTsMorphProject(); | ||
projectSpinner.stop('Project analyzed'); | ||
|
||
const pluginClass = await selectPluginClass(project, cancelledMessage); | ||
const customEntityName = await getCustomEntityName(cancelledMessage); | ||
const context: AddEntityTemplateContext = { | ||
entity: { | ||
className: customEntityName, | ||
fileName: paramCase(customEntityName) + '.entity', | ||
}, | ||
}; | ||
|
||
const entitiesDir = path.join(pluginClass.getSourceFile().getDirectory().getPath(), 'entities'); | ||
const entityTemplatePath = path.join(__dirname, 'scaffold/entity.template.ts'); | ||
const entityFile = createSourceFileFromTemplate(project, entityTemplatePath); | ||
entityFile.move(path.join(entitiesDir, `${context.entity.fileName}.ts`)); | ||
entityFile.getClasses()[0].rename(`${context.entity.className}CustomFields`); | ||
entityFile.getClasses()[1].rename(context.entity.className); | ||
|
||
addEntityToPlugin(pluginClass, entityFile); | ||
|
||
project.saveSync(); | ||
outro('✅ Done!'); | ||
} |
51 changes: 51 additions & 0 deletions
51
...es/cli/src/commands/add/entity/codemods/add-entity-to-plugin/add-entity-to-plugin.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import fs from 'fs-extra'; | ||
import path from 'path'; | ||
import { Project } from 'ts-morph'; | ||
import { describe, expect, it } from 'vitest'; | ||
|
||
import { defaultManipulationSettings } from '../../../../../constants'; | ||
import { createSourceFileFromTemplate, getPluginClasses } from '../../../../../utilities/ast-utils'; | ||
|
||
import { addEntityToPlugin } from './add-entity-to-plugin'; | ||
|
||
describe('addEntityToPlugin', () => { | ||
it('creates entity prop and imports', () => { | ||
const project = new Project({ | ||
manipulationSettings: defaultManipulationSettings, | ||
}); | ||
project.addSourceFileAtPath(path.join(__dirname, 'fixtures', 'no-entity-prop.fixture.ts')); | ||
const pluginClasses = getPluginClasses(project); | ||
expect(pluginClasses.length).toBe(1); | ||
const entityTemplatePath = path.join(__dirname, '../../scaffold/entity.template.ts'); | ||
const entityFile = createSourceFileFromTemplate(project, entityTemplatePath); | ||
entityFile.move(path.join(__dirname, 'fixtures/entity.ts')); | ||
addEntityToPlugin(pluginClasses[0], entityFile); | ||
|
||
const result = pluginClasses[0].getSourceFile().getText(); | ||
const expected = fs.readFileSync( | ||
path.join(__dirname, 'fixtures', 'no-entity-prop.expected'), | ||
'utf-8', | ||
); | ||
expect(result).toBe(expected); | ||
}); | ||
|
||
it('adds to existing entity prop and imports', () => { | ||
const project = new Project({ | ||
manipulationSettings: defaultManipulationSettings, | ||
}); | ||
project.addSourceFileAtPath(path.join(__dirname, 'fixtures', 'existing-entity-prop.fixture.ts')); | ||
const pluginClasses = getPluginClasses(project); | ||
expect(pluginClasses.length).toBe(1); | ||
const entityTemplatePath = path.join(__dirname, '../../scaffold/entity.template.ts'); | ||
const entityFile = createSourceFileFromTemplate(project, entityTemplatePath); | ||
entityFile.move(path.join(__dirname, 'fixtures/entity.ts')); | ||
addEntityToPlugin(pluginClasses[0], entityFile); | ||
|
||
const result = pluginClasses[0].getSourceFile().getText(); | ||
const expected = fs.readFileSync( | ||
path.join(__dirname, 'fixtures', 'existing-entity-prop.expected'), | ||
'utf-8', | ||
); | ||
expect(result).toBe(expected); | ||
}); | ||
}); |
39 changes: 39 additions & 0 deletions
39
packages/cli/src/commands/add/entity/codemods/add-entity-to-plugin/add-entity-to-plugin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { ClassDeclaration, Node, SourceFile, SyntaxKind } from 'ts-morph'; | ||
|
||
import { addImportsToFile } from '../../../../../utilities/ast-utils'; | ||
import { AddEntityTemplateContext } from '../../add-entity'; | ||
|
||
export function addEntityToPlugin(pluginClass: ClassDeclaration, entitySourceFile: SourceFile) { | ||
const pluginDecorator = pluginClass.getDecorator('VendurePlugin'); | ||
if (!pluginDecorator) { | ||
throw new Error('Could not find VendurePlugin decorator'); | ||
} | ||
const pluginOptions = pluginDecorator.getArguments()[0]; | ||
if (!pluginOptions) { | ||
throw new Error('Could not find VendurePlugin options'); | ||
} | ||
const entityClass = entitySourceFile.getClasses().find(c => !c.getName()?.includes('CustomFields')); | ||
if (!entityClass) { | ||
throw new Error('Could not find entity class'); | ||
} | ||
const entityClassName = entityClass.getName() as string; | ||
if (Node.isObjectLiteralExpression(pluginOptions)) { | ||
const entityProperty = pluginOptions.getProperty('entities'); | ||
if (entityProperty) { | ||
const entitiesArray = entityProperty.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression); | ||
if (entitiesArray) { | ||
entitiesArray.addElement(entityClassName); | ||
} | ||
} else { | ||
pluginOptions.addPropertyAssignment({ | ||
name: 'entities', | ||
initializer: `[${entityClassName}]`, | ||
}); | ||
} | ||
} | ||
|
||
addImportsToFile(pluginClass.getSourceFile(), { | ||
moduleSpecifier: entityClass.getSourceFile(), | ||
namedImports: [entityClassName], | ||
}); | ||
} |
18 changes: 18 additions & 0 deletions
18
.../commands/add/entity/codemods/add-entity-to-plugin/fixtures/existing-entity-prop.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { PluginCommonModule, Type, VendurePlugin, Product } from '@vendure/core'; | ||
import { ScaffoldEntity } from './entity'; | ||
|
||
type PluginInitOptions = any; | ||
|
||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
entities: [Product, ScaffoldEntity], | ||
compatibility: '^2.0.0', | ||
}) | ||
export class TestOnePlugin { | ||
static options: PluginInitOptions; | ||
|
||
static init(options: PluginInitOptions): Type<TestOnePlugin> { | ||
this.options = options; | ||
return TestOnePlugin; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ommands/add/entity/codemods/add-entity-to-plugin/fixtures/existing-entity-prop.fixture.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { PluginCommonModule, Type, VendurePlugin, Product } from '@vendure/core'; | ||
|
||
type PluginInitOptions = any; | ||
|
||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
entities: [Product], | ||
compatibility: '^2.0.0', | ||
}) | ||
export class TestOnePlugin { | ||
static options: PluginInitOptions; | ||
|
||
static init(options: PluginInitOptions): Type<TestOnePlugin> { | ||
this.options = options; | ||
return TestOnePlugin; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...li/src/commands/add/entity/codemods/add-entity-to-plugin/fixtures/no-entity-prop.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core'; | ||
import { ScaffoldEntity } from './entity'; | ||
|
||
type PluginInitOptions = any; | ||
|
||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
compatibility: '^2.0.0', | ||
entities: [ScaffoldEntity], | ||
}) | ||
export class TestOnePlugin { | ||
static options: PluginInitOptions; | ||
|
||
static init(options: PluginInitOptions): Type<TestOnePlugin> { | ||
this.options = options; | ||
return TestOnePlugin; | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
.../src/commands/add/entity/codemods/add-entity-to-plugin/fixtures/no-entity-prop.fixture.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core'; | ||
|
||
type PluginInitOptions = any; | ||
|
||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
compatibility: '^2.0.0', | ||
}) | ||
export class TestOnePlugin { | ||
static options: PluginInitOptions; | ||
|
||
static init(options: PluginInitOptions): Type<TestOnePlugin> { | ||
this.options = options; | ||
return TestOnePlugin; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/cli/src/commands/add/entity/scaffold/entity.template.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { VendureEntity, DeepPartial, HasCustomFields } from '@vendure/core'; | ||
import { Entity, Column } from 'typeorm'; | ||
|
||
export class ScaffoldEntityCustomFields {} | ||
|
||
@Entity() | ||
export class ScaffoldEntity extends VendureEntity implements HasCustomFields { | ||
constructor(input?: DeepPartial<ScaffoldEntity>) { | ||
super(input); | ||
} | ||
|
||
@Column() | ||
name: string; | ||
|
||
@Column(type => ScaffoldEntityCustomFields) | ||
customFields: ScaffoldEntityCustomFields; | ||
} |
Oops, something went wrong.