Initializes git repository when creating an app
Signed-off-by: Leonardo Maier <leonarmaier@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': minor
|
||||
---
|
||||
|
||||
Initializes a git repository when creating an app using @packages/create-app
|
||||
@@ -32,6 +32,7 @@ const promptMock = jest.spyOn(inquirer, 'prompt');
|
||||
const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask');
|
||||
const templatingMock = jest.spyOn(tasks, 'templatingTask');
|
||||
const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask');
|
||||
const initGitRepositoryMock = jest.spyOn(tasks, 'initGitRepository');
|
||||
const createTemporaryAppFolderMock = jest.spyOn(
|
||||
tasks,
|
||||
'createTemporaryAppFolderTask',
|
||||
@@ -67,6 +68,7 @@ describe('command entrypoint', () => {
|
||||
await createApp(cmd);
|
||||
expect(checkAppExistsMock).toHaveBeenCalled();
|
||||
expect(createTemporaryAppFolderMock).toHaveBeenCalled();
|
||||
expect(initGitRepositoryMock).toHaveBeenCalled();
|
||||
expect(templatingMock).toHaveBeenCalled();
|
||||
expect(moveAppMock).toHaveBeenCalled();
|
||||
expect(buildAppMock).toHaveBeenCalled();
|
||||
@@ -76,6 +78,7 @@ describe('command entrypoint', () => {
|
||||
const cmd = { path: 'myDirectory' } as unknown as Command;
|
||||
await createApp(cmd);
|
||||
expect(checkPathExistsMock).toHaveBeenCalled();
|
||||
expect(initGitRepositoryMock).toHaveBeenCalled();
|
||||
expect(templatingMock).toHaveBeenCalled();
|
||||
expect(buildAppMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
createTemporaryAppFolderTask,
|
||||
moveAppTask,
|
||||
templatingTask,
|
||||
initGitRepository,
|
||||
} from './lib/tasks';
|
||||
|
||||
export default async (opts: OptionValues): Promise<void> => {
|
||||
@@ -79,6 +80,9 @@ export default async (opts: OptionValues): Promise<void> => {
|
||||
Task.section('Checking that supplied path exists');
|
||||
await checkPathExistsTask(appDir);
|
||||
|
||||
Task.section('Initializing git repository');
|
||||
await initGitRepository(appDir, answers);
|
||||
|
||||
Task.section('Preparing files');
|
||||
await templatingTask(templateDir, opts.path, answers);
|
||||
} else {
|
||||
@@ -90,6 +94,9 @@ export default async (opts: OptionValues): Promise<void> => {
|
||||
Task.section('Creating a temporary app directory');
|
||||
await createTemporaryAppFolderTask(tempDir);
|
||||
|
||||
Task.section('Initializing git repository');
|
||||
await initGitRepository(tempDir, answers);
|
||||
|
||||
Task.section('Preparing files');
|
||||
await templatingTask(templateDir, tempDir, answers);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
checkAppExistsTask,
|
||||
checkPathExistsTask,
|
||||
createTemporaryAppFolderTask,
|
||||
initGitRepository,
|
||||
moveAppTask,
|
||||
templatingTask,
|
||||
} from './tasks';
|
||||
@@ -86,28 +87,100 @@ jest.mock('./versions', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
describe('tasks', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'projects/my-module.ts': '',
|
||||
'projects/dir/my-file.txt': '',
|
||||
'tmp/mockApp/.gitignore': '',
|
||||
'tmp/mockApp/package.json': '',
|
||||
'tmp/mockApp/packages/app/package.json': '',
|
||||
// load templates into mock filesystem
|
||||
'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')),
|
||||
});
|
||||
const mockExec = child_process.exec as unknown as jest.MockedFunction<
|
||||
(
|
||||
command: string,
|
||||
callback: (error: null, stdout: any, stderr: any) => void,
|
||||
) => void
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'projects/my-module.ts': '',
|
||||
'projects/dir/my-file.txt': '',
|
||||
'tmp/mockApp/.gitignore': '',
|
||||
'tmp/mockApp/package.json': '',
|
||||
'tmp/mockApp/packages/app/package.json': '',
|
||||
// load templates into mock filesystem
|
||||
'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockExec.mockRestore();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('checkAppExistsTask', () => {
|
||||
it('should do nothing if the directory does not exist', async () => {
|
||||
const dir = 'projects/';
|
||||
const name = 'MyNewApp';
|
||||
await expect(checkAppExistsTask(dir, name)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('checkAppExistsTask', () => {
|
||||
it('should do nothing if the directory does not exist', async () => {
|
||||
const dir = 'projects/';
|
||||
const name = 'MyNewApp';
|
||||
await expect(checkAppExistsTask(dir, name)).resolves.not.toThrow();
|
||||
it('should throw an error when a directory of the same name exists', async () => {
|
||||
const dir = 'projects/';
|
||||
const name = 'dir';
|
||||
await expect(checkAppExistsTask(dir, name)).rejects.toThrow(
|
||||
'already exists',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkPathExistsTask', () => {
|
||||
it('should create a directory at the given path', async () => {
|
||||
const appDir = 'projects/newProject';
|
||||
await expect(checkPathExistsTask(appDir)).resolves.not.toThrow();
|
||||
expect(fs.existsSync(appDir)).toBe(true);
|
||||
});
|
||||
|
||||
it('should do nothing if a directory of the same name exists', async () => {
|
||||
const appDir = 'projects/dir';
|
||||
await expect(checkPathExistsTask(appDir)).resolves.not.toThrow();
|
||||
expect(fs.existsSync(appDir)).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail if a file of the same name exists', async () => {
|
||||
await expect(checkPathExistsTask('projects/my-module.ts')).rejects.toThrow(
|
||||
'already exists',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTemporaryAppFolderTask', () => {
|
||||
it('should create a directory at a given path', async () => {
|
||||
const tempDir = 'projects/tmpFolder';
|
||||
await expect(createTemporaryAppFolderTask(tempDir)).resolves.not.toThrow();
|
||||
expect(fs.existsSync(tempDir)).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail if a directory of the same name exists', async () => {
|
||||
const tempDir = 'projects/dir';
|
||||
await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow(
|
||||
'file already exists',
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if a file of the same name exists', async () => {
|
||||
const tempDir = 'projects/dir/my-file.txt';
|
||||
await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow(
|
||||
'file already exists',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildAppTask', () => {
|
||||
it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => {
|
||||
const mockChdir = jest.spyOn(process, 'chdir');
|
||||
|
||||
// requires callback implementation to support `promisify` wrapper
|
||||
// https://stackoverflow.com/a/60579617/10044859
|
||||
mockExec.mockImplementation((_command, callback) => {
|
||||
callback(null, 'standard out', 'standard error');
|
||||
});
|
||||
|
||||
it('should throw an error when a file of the same name exists', async () => {
|
||||
@@ -274,3 +347,36 @@ describe('tasks', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initGitRepository', () => {
|
||||
it('should initialize a git repository at the given path', async () => {
|
||||
const destinationDir = 'tmp/mockApp/';
|
||||
const context = {
|
||||
defaultBranch: '',
|
||||
};
|
||||
|
||||
mockExec.mockImplementation((_command, callback) => {
|
||||
callback(null, { stdout: 'main' }, 'standard error');
|
||||
});
|
||||
|
||||
await initGitRepository(destinationDir, context);
|
||||
|
||||
expect(context.defaultBranch).toBe('main');
|
||||
expect(mockExec).toHaveBeenCalledTimes(3);
|
||||
expect(mockExec).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'git init',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockExec).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'git commit --allow-empty -m "Initial commit"',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockExec).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'git branch --format="%(refname:short)"',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,3 +236,34 @@ export async function moveAppTask(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a git repository in the destination folder
|
||||
*
|
||||
* @param dir - source path to initialize git repository in
|
||||
* @param context - template parameters
|
||||
* @throws if `exec` fails
|
||||
*/
|
||||
export async function initGitRepository(dir: string, context: any) {
|
||||
const runCmd = async (cmd: string) => {
|
||||
let cmdResponse = { stdout: '', stderr: '' };
|
||||
await Task.forItem('executing', cmd, async () => {
|
||||
process.chdir(dir);
|
||||
cmdResponse = await exec(cmd).catch(error => {
|
||||
process.stdout.write(error.stderr);
|
||||
process.stdout.write(error.stdout);
|
||||
throw new Error(`Could not execute command ${chalk.cyan(cmd)}`);
|
||||
});
|
||||
});
|
||||
return cmdResponse;
|
||||
};
|
||||
|
||||
await runCmd('git init');
|
||||
await runCmd('git commit --allow-empty -m "Initial commit"');
|
||||
|
||||
const defaultBranch = await runCmd('git branch --format="%(refname:short)"');
|
||||
|
||||
context.defaultBranch = defaultBranch.stdout
|
||||
? defaultBranch.stdout.trim()
|
||||
: 'master';
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"clean": "backstage-cli repo clean",
|
||||
"test": "backstage-cli repo test",
|
||||
"test:all": "backstage-cli repo test --coverage",
|
||||
"lint": "backstage-cli repo lint --since origin/master",
|
||||
"test": "backstage-cli test",
|
||||
"test:all": "lerna run test -- --coverage",
|
||||
"lint": "backstage-cli repo lint --since origin/{{defaultBranch}}",
|
||||
"lint:all": "backstage-cli repo lint",
|
||||
"prettier:check": "prettier --check .",
|
||||
"create-plugin": "backstage-cli create-plugin --scope internal",
|
||||
|
||||
Reference in New Issue
Block a user