@form8ion/project 22.0.0-beta.1 → 22.0.0-beta.11
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 +35 -24
- package/lib/index.js +119 -94
- package/lib/index.js.map +1 -1
- package/package.json +24 -26
- package/src/dependency-updater/prompt.js +12 -8
- package/src/dependency-updater/prompt.test.js +13 -16
- package/src/dependency-updater/scaffolder.js +2 -2
- package/src/dependency-updater/scaffolder.test.js +7 -11
- package/src/index.js +4 -6
- package/src/language/prompt.js +12 -9
- package/src/language/prompt.test.js +13 -16
- package/src/language/scaffolder.js +2 -2
- package/src/language/scaffolder.test.js +3 -3
- package/src/license/scaffolder.js +2 -3
- package/src/license/scaffolder.test.js +5 -8
- package/src/lift.js +1 -1
- package/src/options-validator.js +0 -2
- package/src/options-validator.test.js +1 -5
- package/src/prompts/index.js +21 -0
- package/src/prompts/questions.js +7 -3
- package/src/prompts/questions.test.js +5 -6
- package/src/scaffolder.js +11 -13
- package/src/scaffolder.test.js +22 -31
- package/src/vcs/git/remotes.js +6 -7
- package/src/vcs/git/remotes.test.js +36 -16
- package/src/vcs/host/prompt.js +12 -9
- package/src/vcs/host/prompt.test.js +27 -23
- package/src/vcs/host/scaffolder.js +2 -2
- package/src/vcs/host/scaffolder.test.js +5 -5
- package/src/vcs/prompt.js +8 -8
- package/src/vcs/prompt.test.js +12 -11
- package/src/vcs/scaffolder.js +9 -11
- package/src/vcs/scaffolder.test.js +10 -9
- package/src/options-schemas.js +0 -3
- package/src/options-schemas.test.js +0 -20
package/src/scaffolder.test.js
CHANGED
|
@@ -2,7 +2,6 @@ import deepmerge from 'deepmerge';
|
|
|
2
2
|
import {execa} from 'execa';
|
|
3
3
|
import {questionNames as coreQuestionNames} from '@form8ion/core';
|
|
4
4
|
import {scaffold as scaffoldReadme} from '@form8ion/readme';
|
|
5
|
-
import * as resultsReporter from '@form8ion/results-reporter';
|
|
6
5
|
|
|
7
6
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
8
7
|
import any from '@travi/any';
|
|
@@ -52,7 +51,8 @@ describe('project scaffolder', () => {
|
|
|
52
51
|
const tags = any.listOf(any.word);
|
|
53
52
|
const visibility = any.word();
|
|
54
53
|
const vcsIgnore = any.simpleObject();
|
|
55
|
-
const
|
|
54
|
+
const prompt = () => undefined;
|
|
55
|
+
const logger = {info: () => {}};
|
|
56
56
|
|
|
57
57
|
beforeEach(() => {
|
|
58
58
|
process.cwd = vi.fn();
|
|
@@ -95,9 +95,9 @@ describe('project scaffolder', () => {
|
|
|
95
95
|
]);
|
|
96
96
|
when(optionsValidator.validate)
|
|
97
97
|
.calledWith(options)
|
|
98
|
-
.thenReturn({
|
|
98
|
+
.thenReturn({plugins: {dependencyUpdaters, languages, vcsHosts}});
|
|
99
99
|
when(prompts.promptForBaseDetails)
|
|
100
|
-
.calledWith(projectPath,
|
|
100
|
+
.calledWith(projectPath, {prompt})
|
|
101
101
|
.thenResolve({
|
|
102
102
|
[coreQuestionNames.PROJECT_NAME]: projectName,
|
|
103
103
|
[coreQuestionNames.LICENSE]: license,
|
|
@@ -107,25 +107,20 @@ describe('project scaffolder', () => {
|
|
|
107
107
|
[coreQuestionNames.VISIBILITY]: visibility
|
|
108
108
|
});
|
|
109
109
|
when(scaffoldVcs)
|
|
110
|
-
.calledWith({projectRoot: projectPath, projectName,
|
|
110
|
+
.calledWith({projectRoot: projectPath, projectName, vcsHosts, visibility, description}, {prompt, logger})
|
|
111
111
|
.thenResolve(vcsResults);
|
|
112
112
|
when(licenseScaffolder.default)
|
|
113
|
-
.calledWith({projectRoot: projectPath, license, copyright})
|
|
113
|
+
.calledWith({projectRoot: projectPath, license, copyright}, {logger})
|
|
114
114
|
.thenResolve(licenseResults);
|
|
115
115
|
scaffoldLanguage.mockResolvedValue(languageResults);
|
|
116
116
|
when(dependencyUpdaterScaffolder.default)
|
|
117
|
-
.calledWith(dependencyUpdaters,
|
|
117
|
+
.calledWith(dependencyUpdaters, {projectRoot: projectPath}, {prompt})
|
|
118
118
|
.thenResolve(dependencyUpdaterResults);
|
|
119
119
|
when(scaffoldContributing).calledWith({visibility}).thenReturn(contributingResults);
|
|
120
120
|
|
|
121
|
-
await scaffold(options);
|
|
121
|
+
expect(await scaffold(options, {prompt, logger})).toEqual(mergedResults);
|
|
122
122
|
|
|
123
123
|
expect(scaffoldReadme).toHaveBeenCalledWith({projectName, projectRoot: projectPath, description});
|
|
124
|
-
expect(dependencyUpdaterScaffolder.default).toHaveBeenCalledWith(
|
|
125
|
-
dependencyUpdaters,
|
|
126
|
-
decisions,
|
|
127
|
-
{projectRoot: projectPath, vcs}
|
|
128
|
-
);
|
|
129
124
|
expect(scaffoldEditorconfig).toHaveBeenCalledWith({projectRoot: projectPath});
|
|
130
125
|
expect(lift).toHaveBeenCalledWith({
|
|
131
126
|
projectRoot: projectPath,
|
|
@@ -133,7 +128,6 @@ describe('project scaffolder', () => {
|
|
|
133
128
|
results: mergedResults,
|
|
134
129
|
enhancers: {...dependencyUpdaters, ...vcsHosts, ...languages}
|
|
135
130
|
});
|
|
136
|
-
expect(resultsReporter.reportResults).toHaveBeenCalledWith(mergedResults);
|
|
137
131
|
});
|
|
138
132
|
|
|
139
133
|
it('should pass the lists of badges from contributors to the readme', async () => {
|
|
@@ -160,7 +154,7 @@ describe('project scaffolder', () => {
|
|
|
160
154
|
const languageResults = {badges: languageBadges, vcsIgnore, documentation};
|
|
161
155
|
when(optionsValidator.validate).calledWith(options).thenReturn({plugins: {vcsHosts}});
|
|
162
156
|
when(prompts.promptForBaseDetails)
|
|
163
|
-
.calledWith(projectPath,
|
|
157
|
+
.calledWith(projectPath, {prompt})
|
|
164
158
|
.thenResolve({
|
|
165
159
|
[coreQuestionNames.DESCRIPTION]: description,
|
|
166
160
|
[questionNames.GIT_REPO]: true,
|
|
@@ -173,7 +167,7 @@ describe('project scaffolder', () => {
|
|
|
173
167
|
licenseScaffolder.default.mockResolvedValue({badges: licenseBadges});
|
|
174
168
|
scaffoldVcs.mockResolvedValue(vcsResults);
|
|
175
169
|
|
|
176
|
-
await scaffold(options);
|
|
170
|
+
await scaffold(options, {prompt});
|
|
177
171
|
|
|
178
172
|
expect(scaffoldReadme).toHaveBeenCalledWith({projectName, projectRoot: projectPath, description});
|
|
179
173
|
});
|
|
@@ -184,7 +178,7 @@ describe('project scaffolder', () => {
|
|
|
184
178
|
scaffoldReadme.mockResolvedValue();
|
|
185
179
|
scaffoldVcs.mockResolvedValue({});
|
|
186
180
|
|
|
187
|
-
await scaffold(options);
|
|
181
|
+
await scaffold(options, {prompt});
|
|
188
182
|
|
|
189
183
|
expect(dependencyUpdaterScaffolder.default).not.toHaveBeenCalled();
|
|
190
184
|
});
|
|
@@ -209,9 +203,7 @@ describe('project scaffolder', () => {
|
|
|
209
203
|
nextSteps: languageNextSteps,
|
|
210
204
|
tags
|
|
211
205
|
};
|
|
212
|
-
when(optionsValidator.validate)
|
|
213
|
-
.calledWith(options)
|
|
214
|
-
.thenReturn({decisions, plugins: {languages, vcsHosts}});
|
|
206
|
+
when(optionsValidator.validate).calledWith(options).thenReturn({plugins: {languages, vcsHosts}});
|
|
215
207
|
scaffoldVcs.mockResolvedValue(vcsResults);
|
|
216
208
|
prompts.promptForBaseDetails.mockResolvedValue({
|
|
217
209
|
[coreQuestionNames.PROJECT_NAME]: projectName,
|
|
@@ -220,30 +212,29 @@ describe('project scaffolder', () => {
|
|
|
220
212
|
[coreQuestionNames.LICENSE]: license,
|
|
221
213
|
[coreQuestionNames.DESCRIPTION]: description
|
|
222
214
|
});
|
|
223
|
-
when(scaffoldLanguage).calledWith(languages,
|
|
215
|
+
when(scaffoldLanguage).calledWith(languages, {
|
|
224
216
|
projectName,
|
|
225
217
|
projectRoot: projectPath,
|
|
226
218
|
visibility,
|
|
227
219
|
license,
|
|
228
220
|
vcs,
|
|
229
221
|
description
|
|
230
|
-
}).thenResolve(languageResults);
|
|
222
|
+
}, {prompt}).thenResolve(languageResults);
|
|
231
223
|
when(execa).calledWith(verificationCommand, {shell: true}).thenReturn({stdout: {pipe: execaPipe}});
|
|
232
224
|
dependencyUpdaterScaffolder.default.mockResolvedValue({});
|
|
233
225
|
licenseScaffolder.default.mockResolvedValue({});
|
|
234
226
|
scaffoldContributing.mockResolvedValue({});
|
|
235
227
|
|
|
236
|
-
await scaffold(options);
|
|
228
|
+
await scaffold(options, {prompt, logger});
|
|
237
229
|
|
|
238
230
|
expect(scaffoldReadme).toHaveBeenCalledWith({projectName, projectRoot: projectPath, description});
|
|
239
231
|
expect(execaPipe).toHaveBeenCalledWith(process.stdout);
|
|
240
|
-
expect(resultsReporter.reportResults).toHaveBeenCalledWith(deepmerge.all([languageResults, vcsResults]));
|
|
241
232
|
});
|
|
242
233
|
|
|
243
234
|
it('should consider the language details to be optional', async () => {
|
|
244
235
|
when(optionsValidator.validate)
|
|
245
236
|
.calledWith(options)
|
|
246
|
-
.thenReturn({vcsHosts,
|
|
237
|
+
.thenReturn({vcsHosts, plugins: {languages}});
|
|
247
238
|
scaffoldVcs.mockResolvedValue(vcsResults);
|
|
248
239
|
prompts.promptForBaseDetails.mockResolvedValue({
|
|
249
240
|
[coreQuestionNames.PROJECT_NAME]: projectName,
|
|
@@ -257,22 +248,21 @@ describe('project scaffolder', () => {
|
|
|
257
248
|
licenseScaffolder.default.mockResolvedValue({});
|
|
258
249
|
scaffoldContributing.mockResolvedValue({});
|
|
259
250
|
|
|
260
|
-
await scaffold(options);
|
|
251
|
+
await scaffold(options, {prompt});
|
|
261
252
|
|
|
262
253
|
expect(scaffoldReadme).toHaveBeenCalledWith({projectName, projectRoot: projectPath, description});
|
|
263
254
|
expect(execa).not.toHaveBeenCalled();
|
|
264
255
|
});
|
|
265
256
|
|
|
266
257
|
it('should pass the license to the language scaffolder as `UNLICENSED` when no license was chosen', async () => {
|
|
267
|
-
when(optionsValidator.validate).calledWith(options).thenReturn({plugins: {languages}
|
|
258
|
+
when(optionsValidator.validate).calledWith(options).thenReturn({plugins: {languages}});
|
|
268
259
|
prompts.promptForBaseDetails.mockResolvedValue({});
|
|
269
260
|
scaffoldVcs.mockResolvedValue(vcsResults);
|
|
270
261
|
|
|
271
|
-
await scaffold(options);
|
|
262
|
+
await scaffold(options, {prompt});
|
|
272
263
|
|
|
273
264
|
expect(scaffoldLanguage).toHaveBeenCalledWith(
|
|
274
265
|
languages,
|
|
275
|
-
decisions,
|
|
276
266
|
{
|
|
277
267
|
license: 'UNLICENSED',
|
|
278
268
|
description: undefined,
|
|
@@ -280,7 +270,8 @@ describe('project scaffolder', () => {
|
|
|
280
270
|
projectRoot: projectPath,
|
|
281
271
|
vcs,
|
|
282
272
|
visibility: undefined
|
|
283
|
-
}
|
|
273
|
+
},
|
|
274
|
+
{prompt}
|
|
284
275
|
);
|
|
285
276
|
});
|
|
286
277
|
|
|
@@ -290,7 +281,7 @@ describe('project scaffolder', () => {
|
|
|
290
281
|
scaffoldVcs.mockResolvedValue({});
|
|
291
282
|
scaffoldLanguage.mockResolvedValue({badges: {}, projectDetails: {}});
|
|
292
283
|
|
|
293
|
-
await scaffold(options);
|
|
284
|
+
await scaffold(options, {prompt});
|
|
294
285
|
|
|
295
286
|
expect(execa).not.toHaveBeenCalled();
|
|
296
287
|
});
|
package/src/vcs/git/remotes.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {simpleGit} from 'simple-git';
|
|
2
|
-
import
|
|
3
|
-
import {warn} from '@travi/cli-messages';
|
|
2
|
+
import parseGitUrl from 'git-url-parse';
|
|
4
3
|
|
|
5
4
|
async function getExistingRemotes(git) {
|
|
6
5
|
try {
|
|
@@ -17,14 +16,14 @@ async function getExistingRemotes(git) {
|
|
|
17
16
|
export async function determineExistingVcsDetails({projectRoot}) {
|
|
18
17
|
const git = simpleGit({baseDir: projectRoot});
|
|
19
18
|
const remoteOrigin = await git.remote(['get-url', 'origin']);
|
|
20
|
-
const {
|
|
19
|
+
const {owner, name, host} = parseGitUrl(remoteOrigin.trimEnd());
|
|
21
20
|
|
|
22
|
-
return {vcs: {owner
|
|
21
|
+
return {vcs: {owner, name, host: 'github.com' === host ? 'github' : host}};
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
export async function defineRemoteOrigin(projectRoot, sshUrl) {
|
|
24
|
+
export async function defineRemoteOrigin(projectRoot, sshUrl, {logger}) {
|
|
26
25
|
if (!sshUrl) {
|
|
27
|
-
warn('URL not available to configure remote `origin`');
|
|
26
|
+
logger.warn('URL not available to configure remote `origin`');
|
|
28
27
|
|
|
29
28
|
return {nextSteps: []};
|
|
30
29
|
}
|
|
@@ -33,7 +32,7 @@ export async function defineRemoteOrigin(projectRoot, sshUrl) {
|
|
|
33
32
|
const existingRemotes = await getExistingRemotes(git);
|
|
34
33
|
|
|
35
34
|
if (existingRemotes.includes('origin')) {
|
|
36
|
-
warn('The `origin` remote is already defined for this repository');
|
|
35
|
+
logger.warn('The `origin` remote is already defined for this repository');
|
|
37
36
|
|
|
38
37
|
return {nextSteps: []};
|
|
39
38
|
}
|
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
import {simpleGit} from 'simple-git';
|
|
2
|
-
import
|
|
2
|
+
import parseGitUrl from 'git-url-parse';
|
|
3
3
|
|
|
4
|
-
import {describe, vi, it, expect} from 'vitest';
|
|
4
|
+
import {describe, vi, it, expect, beforeEach} from 'vitest';
|
|
5
5
|
import {when} from 'vitest-when';
|
|
6
6
|
import any from '@travi/any';
|
|
7
7
|
|
|
8
8
|
import {determineExistingVcsDetails, defineRemoteOrigin} from './remotes.js';
|
|
9
9
|
|
|
10
10
|
vi.mock('simple-git');
|
|
11
|
-
vi.mock('
|
|
11
|
+
vi.mock('git-url-parse');
|
|
12
12
|
|
|
13
13
|
describe('Git remote', () => {
|
|
14
14
|
const projectRoot = any.string();
|
|
15
15
|
|
|
16
16
|
describe('determine', () => {
|
|
17
|
+
const remote = vi.fn();
|
|
18
|
+
const remoteOrigin = any.url();
|
|
19
|
+
const repoName = any.word();
|
|
20
|
+
const vcsHostAccount = any.word();
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({remote});
|
|
24
|
+
});
|
|
25
|
+
|
|
17
26
|
it('should determine existing vcs details from the remote origin definition of the local repository', async () => {
|
|
18
|
-
const remote = vi.fn();
|
|
19
|
-
const remoteOrigin = any.url();
|
|
20
|
-
const repoName = any.word();
|
|
21
27
|
const vcsHost = `F${any.word()})O${any.word()}O`;
|
|
22
|
-
|
|
23
|
-
when(
|
|
24
|
-
when(remote).calledWith(['get-url', 'origin']).thenResolve(remoteOrigin);
|
|
25
|
-
when(hostedGitInfo.fromUrl)
|
|
28
|
+
when(remote).calledWith(['get-url', 'origin']).thenResolve(`${remoteOrigin}\n`);
|
|
29
|
+
when(parseGitUrl)
|
|
26
30
|
.calledWith(remoteOrigin)
|
|
27
|
-
.thenReturn({
|
|
31
|
+
.thenReturn({owner: vcsHostAccount, name: repoName, host: vcsHost.toLowerCase()});
|
|
28
32
|
|
|
29
33
|
const {vcs: hostDetails} = await determineExistingVcsDetails({projectRoot});
|
|
30
34
|
|
|
31
35
|
expect(hostDetails).toEqual({host: vcsHost.toLowerCase(), owner: vcsHostAccount, name: repoName});
|
|
32
36
|
});
|
|
37
|
+
|
|
38
|
+
it(
|
|
39
|
+
'should return `github` when the host is determined to be `github.com` until that can be a breaking change',
|
|
40
|
+
async () => {
|
|
41
|
+
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({remote});
|
|
42
|
+
when(remote).calledWith(['get-url', 'origin']).thenResolve(`${remoteOrigin}\n`);
|
|
43
|
+
when(parseGitUrl)
|
|
44
|
+
.calledWith(remoteOrigin)
|
|
45
|
+
.thenReturn({owner: vcsHostAccount, name: repoName, host: 'github.com'});
|
|
46
|
+
|
|
47
|
+
const {vcs: hostDetails} = await determineExistingVcsDetails({projectRoot});
|
|
48
|
+
|
|
49
|
+
expect(hostDetails).toEqual({host: 'github', owner: vcsHostAccount, name: repoName});
|
|
50
|
+
}
|
|
51
|
+
);
|
|
33
52
|
});
|
|
34
53
|
|
|
35
54
|
describe('define', () => {
|
|
36
55
|
const sshUrl = any.url();
|
|
56
|
+
const logger = {warn: () => {}};
|
|
37
57
|
|
|
38
58
|
it('should define the remote origin', async () => {
|
|
39
59
|
const listRemote = vi.fn();
|
|
@@ -41,7 +61,7 @@ describe('Git remote', () => {
|
|
|
41
61
|
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({listRemote, addRemote});
|
|
42
62
|
when(listRemote).calledWith().thenResolve(any.listOf(any.word));
|
|
43
63
|
|
|
44
|
-
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl);
|
|
64
|
+
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl, {logger});
|
|
45
65
|
|
|
46
66
|
expect(addRemote).toHaveBeenCalledWith('origin', sshUrl);
|
|
47
67
|
expect(nextSteps).toEqual([{summary: 'Set local `master` branch to track upstream `origin/master`'}]);
|
|
@@ -53,7 +73,7 @@ describe('Git remote', () => {
|
|
|
53
73
|
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({listRemote, addRemote});
|
|
54
74
|
when(listRemote).calledWith().thenThrow(new Error('fatal: No remote configured to list refs from.\n'));
|
|
55
75
|
|
|
56
|
-
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl);
|
|
76
|
+
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl, {logger});
|
|
57
77
|
|
|
58
78
|
expect(nextSteps).toEqual([{summary: 'Set local `master` branch to track upstream `origin/master`'}]);
|
|
59
79
|
});
|
|
@@ -64,7 +84,7 @@ describe('Git remote', () => {
|
|
|
64
84
|
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({listRemote});
|
|
65
85
|
when(listRemote).calledWith().thenThrow(error);
|
|
66
86
|
|
|
67
|
-
await expect(defineRemoteOrigin(projectRoot, sshUrl)).rejects.toThrow(error);
|
|
87
|
+
await expect(defineRemoteOrigin(projectRoot, sshUrl, {logger})).rejects.toThrow(error);
|
|
68
88
|
});
|
|
69
89
|
|
|
70
90
|
it('should return no next-steps when the remote origin is already defined', async () => {
|
|
@@ -72,13 +92,13 @@ describe('Git remote', () => {
|
|
|
72
92
|
when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({listRemote});
|
|
73
93
|
when(listRemote).calledWith().thenResolve([...any.listOf(any.word), 'origin', ...any.listOf(any.word)]);
|
|
74
94
|
|
|
75
|
-
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl);
|
|
95
|
+
const {nextSteps} = await defineRemoteOrigin(projectRoot, sshUrl, {logger});
|
|
76
96
|
|
|
77
97
|
expect(nextSteps).toEqual([]);
|
|
78
98
|
});
|
|
79
99
|
|
|
80
100
|
it('should return no next-steps when no `sshUrl` is provided', async () => {
|
|
81
|
-
const {nextSteps} = await defineRemoteOrigin(projectRoot);
|
|
101
|
+
const {nextSteps} = await defineRemoteOrigin(projectRoot, undefined, {logger});
|
|
82
102
|
|
|
83
103
|
expect(nextSteps).toEqual([]);
|
|
84
104
|
});
|
package/src/vcs/host/prompt.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {prompt} from '@form8ion/overridable-prompts';
|
|
2
|
-
|
|
3
1
|
import {questionNames} from '../../prompts/question-names.js';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
export const REPOSITORY_HOST_PROMPT_ID = 'REPOSITORY_HOST';
|
|
4
|
+
|
|
5
|
+
export default async function promptForVcsHostChoice(hosts, {prompt}) {
|
|
6
|
+
const answers = await prompt({
|
|
7
|
+
id: REPOSITORY_HOST_PROMPT_ID,
|
|
8
|
+
questions: [{
|
|
9
|
+
name: questionNames.REPO_HOST,
|
|
10
|
+
type: 'list',
|
|
11
|
+
message: 'Where will the repository be hosted?',
|
|
12
|
+
choices: Object.keys(hosts)
|
|
13
|
+
}]
|
|
14
|
+
});
|
|
12
15
|
const host = hosts[answers[questionNames.REPO_HOST]];
|
|
13
16
|
|
|
14
17
|
return {...answers, ...host};
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
1
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
|
4
2
|
import any from '@travi/any';
|
|
5
3
|
import {when} from 'vitest-when';
|
|
6
4
|
|
|
7
5
|
import {questionNames} from '../../prompts/question-names.js';
|
|
8
|
-
import promptForVcsHostDetails from './prompt.js';
|
|
6
|
+
import promptForVcsHostDetails, {REPOSITORY_HOST_PROMPT_ID} from './prompt.js';
|
|
9
7
|
|
|
10
8
|
vi.mock('@form8ion/overridable-prompts');
|
|
11
9
|
vi.mock('../../prompts/conditionals');
|
|
12
10
|
|
|
13
11
|
describe('vcs host details prompt', () => {
|
|
12
|
+
let prompt;
|
|
14
13
|
const answers = any.simpleObject();
|
|
15
|
-
const decisions = any.simpleObject();
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
vi.
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
prompt = vi.fn();
|
|
19
17
|
});
|
|
20
18
|
|
|
21
19
|
it('should prompt for the vcs hosting details', async () => {
|
|
@@ -23,25 +21,31 @@ describe('vcs host details prompt', () => {
|
|
|
23
21
|
const hostNames = [...any.listOf(any.string), host];
|
|
24
22
|
const hosts = any.objectWithKeys(hostNames, {factory: () => ({})});
|
|
25
23
|
const answersWithHostChoice = {...answers, [questionNames.REPO_HOST]: host};
|
|
26
|
-
when(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
when(prompt).calledWith({
|
|
25
|
+
id: REPOSITORY_HOST_PROMPT_ID,
|
|
26
|
+
questions: [{
|
|
27
|
+
name: questionNames.REPO_HOST,
|
|
28
|
+
type: 'list',
|
|
29
|
+
message: 'Where will the repository be hosted?',
|
|
30
|
+
choices: hostNames
|
|
31
|
+
}]
|
|
32
|
+
}).thenResolve(answersWithHostChoice);
|
|
33
|
+
|
|
34
|
+
expect(await promptForVcsHostDetails(hosts, {prompt})).toEqual(answersWithHostChoice);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
it('should not throw an error when `Other` is chosen as the host', async () => {
|
|
37
38
|
const answersWithHostChoice = {...answers, [questionNames.REPO_HOST]: 'Other'};
|
|
38
|
-
when(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
when(prompt).calledWith({
|
|
40
|
+
id: REPOSITORY_HOST_PROMPT_ID,
|
|
41
|
+
questions: [{
|
|
42
|
+
name: questionNames.REPO_HOST,
|
|
43
|
+
type: 'list',
|
|
44
|
+
message: 'Where will the repository be hosted?',
|
|
45
|
+
choices: []
|
|
46
|
+
}]
|
|
47
|
+
}).thenResolve(answersWithHostChoice);
|
|
48
|
+
|
|
49
|
+
expect(await promptForVcsHostDetails({}, {prompt})).toEqual(answersWithHostChoice);
|
|
46
50
|
});
|
|
47
51
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {questionNames} from '../../prompts/question-names.js';
|
|
2
2
|
import promptForVcsHostDetails from './prompt.js';
|
|
3
3
|
|
|
4
|
-
export default async function scaffoldVcsHost(hosts,
|
|
5
|
-
const {[questionNames.REPO_HOST]: chosenHost} = await promptForVcsHostDetails(hosts,
|
|
4
|
+
export default async function scaffoldVcsHost(hosts, options, {prompt}) {
|
|
5
|
+
const {[questionNames.REPO_HOST]: chosenHost} = await promptForVcsHostDetails(hosts, {prompt});
|
|
6
6
|
|
|
7
7
|
const lowercasedHosts = Object.fromEntries(
|
|
8
8
|
Object.entries(hosts).map(([name, details]) => [name.toLowerCase(), details])
|
|
@@ -10,7 +10,7 @@ vi.mock('./prompt');
|
|
|
10
10
|
|
|
11
11
|
describe('vcs host scaffolder', () => {
|
|
12
12
|
const options = any.simpleObject();
|
|
13
|
-
const
|
|
13
|
+
const prompt = () => undefined;
|
|
14
14
|
|
|
15
15
|
it('should scaffold the chosen vcs host', async () => {
|
|
16
16
|
const chosenHost = `${any.word()}CAPITAL${any.word()}`;
|
|
@@ -19,19 +19,19 @@ describe('vcs host scaffolder', () => {
|
|
|
19
19
|
const hostPlugins = {...any.simpleObject(), [chosenHost.toLowerCase()]: {scaffold: chosenHostScaffolder}};
|
|
20
20
|
const owner = any.word;
|
|
21
21
|
when(promptForVcsHostDetails)
|
|
22
|
-
.calledWith(hostPlugins,
|
|
22
|
+
.calledWith(hostPlugins, {prompt})
|
|
23
23
|
.thenResolve({[questionNames.REPO_HOST]: chosenHost, [questionNames.REPO_OWNER]: owner});
|
|
24
24
|
when(chosenHostScaffolder).calledWith(options).thenResolve(results);
|
|
25
25
|
|
|
26
|
-
expect(await scaffoldVcsHost(hostPlugins,
|
|
26
|
+
expect(await scaffoldVcsHost(hostPlugins, options, {prompt})).toEqual(results);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('should return empty `vcs` results when no matching host is available', async () => {
|
|
30
30
|
const hostPlugins = any.simpleObject();
|
|
31
31
|
when(promptForVcsHostDetails)
|
|
32
|
-
.calledWith(hostPlugins,
|
|
32
|
+
.calledWith(hostPlugins, {prompt})
|
|
33
33
|
.thenResolve({[questionNames.REPO_HOST]: any.word()});
|
|
34
34
|
|
|
35
|
-
expect(await scaffoldVcsHost(hostPlugins,
|
|
35
|
+
expect(await scaffoldVcsHost(hostPlugins, options, {prompt})).toEqual({vcs: {}});
|
|
36
36
|
});
|
|
37
37
|
});
|
package/src/vcs/prompt.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {prompt} from '@form8ion/overridable-prompts';
|
|
2
|
-
|
|
3
1
|
import {questionNames} from '../prompts/question-names.js';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export const GIT_REPOSITORY_PROMPT_ID = 'GIT_REPOSITORY';
|
|
4
|
+
|
|
5
|
+
export default async function promptForRepoCreation({prompt}) {
|
|
6
|
+
const {[questionNames.GIT_REPO]: gitRepoShouldBeCreated} = await prompt({
|
|
7
|
+
id: GIT_REPOSITORY_PROMPT_ID,
|
|
8
|
+
questions: [{
|
|
8
9
|
name: questionNames.GIT_REPO,
|
|
9
10
|
type: 'confirm',
|
|
10
11
|
default: true,
|
|
11
12
|
message: 'Should a git repository be initialized?'
|
|
12
|
-
}]
|
|
13
|
-
|
|
14
|
-
);
|
|
13
|
+
}]
|
|
14
|
+
});
|
|
15
15
|
|
|
16
16
|
return gitRepoShouldBeCreated;
|
|
17
17
|
}
|
package/src/vcs/prompt.test.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import {prompt} from '@form8ion/overridable-prompts';
|
|
2
|
-
|
|
3
1
|
import {describe, vi, it, expect} from 'vitest';
|
|
4
2
|
import {when} from 'vitest-when';
|
|
5
3
|
import any from '@travi/any';
|
|
6
4
|
|
|
7
5
|
import {questionNames} from '../prompts/question-names.js';
|
|
8
|
-
import promptForRepoCreation from './prompt.js';
|
|
6
|
+
import promptForRepoCreation, {GIT_REPOSITORY_PROMPT_ID} from './prompt.js';
|
|
9
7
|
|
|
10
8
|
vi.mock('@form8ion/overridable-prompts');
|
|
11
9
|
|
|
12
10
|
describe('git prompt', () => {
|
|
13
11
|
it('should ask whether a repository should be created', async () => {
|
|
14
|
-
const
|
|
12
|
+
const prompt = vi.fn();
|
|
15
13
|
const repoShouldBeCreated = any.boolean();
|
|
16
14
|
when(prompt)
|
|
17
|
-
.calledWith(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
.calledWith({
|
|
16
|
+
id: GIT_REPOSITORY_PROMPT_ID,
|
|
17
|
+
questions: [{
|
|
18
|
+
name: questionNames.GIT_REPO,
|
|
19
|
+
type: 'confirm',
|
|
20
|
+
default: true,
|
|
21
|
+
message: 'Should a git repository be initialized?'
|
|
22
|
+
}]
|
|
23
|
+
})
|
|
23
24
|
.thenResolve({[questionNames.GIT_REPO]: repoShouldBeCreated});
|
|
24
25
|
|
|
25
|
-
expect(await promptForRepoCreation(
|
|
26
|
+
expect(await promptForRepoCreation({prompt})).toBe(repoShouldBeCreated);
|
|
26
27
|
});
|
|
27
28
|
});
|
package/src/vcs/scaffolder.js
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import {info} from '@travi/cli-messages';
|
|
2
1
|
import {scaffold as scaffoldGit, test as alreadyVersionedByGit} from '@form8ion/git';
|
|
3
2
|
|
|
4
3
|
import repositoryShouldBeCreated from './prompt.js';
|
|
5
4
|
import {determineExistingVcsDetails, defineRemoteOrigin} from './git/index.js';
|
|
6
5
|
import {scaffold as scaffoldVcsHost} from './host/index.js';
|
|
7
6
|
|
|
8
|
-
export default async function scaffoldVcs(
|
|
9
|
-
|
|
7
|
+
export default async function scaffoldVcs(
|
|
8
|
+
{projectRoot, projectName, vcsHosts, visibility, description},
|
|
9
|
+
{prompt, logger}
|
|
10
|
+
) {
|
|
11
|
+
if (await repositoryShouldBeCreated({prompt})) {
|
|
10
12
|
if (await alreadyVersionedByGit({projectRoot})) {
|
|
11
|
-
info('Git repository already exists');
|
|
13
|
+
logger.info('Git repository already exists');
|
|
12
14
|
|
|
13
|
-
return
|
|
15
|
+
return determineExistingVcsDetails({projectRoot});
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const [{vcs: {host, owner, name, sshUrl}}] = await Promise.all([
|
|
17
|
-
scaffoldVcsHost(
|
|
18
|
-
vcsHosts,
|
|
19
|
-
decisions,
|
|
20
|
-
{projectName, projectRoot, description, visibility}
|
|
21
|
-
),
|
|
19
|
+
scaffoldVcsHost(vcsHosts, {projectName, projectRoot, description, visibility}, {prompt}),
|
|
22
20
|
scaffoldGit({projectRoot})
|
|
23
21
|
]);
|
|
24
22
|
|
|
25
|
-
const remoteOriginResults = await defineRemoteOrigin(projectRoot, sshUrl);
|
|
23
|
+
const remoteOriginResults = await defineRemoteOrigin(projectRoot, sshUrl, {logger});
|
|
26
24
|
|
|
27
25
|
return {
|
|
28
26
|
vcs: {host, owner, name},
|
|
@@ -16,7 +16,8 @@ vi.mock('./prompt.js');
|
|
|
16
16
|
|
|
17
17
|
describe('vcs scaffolder', () => {
|
|
18
18
|
const projectRoot = any.string();
|
|
19
|
-
const
|
|
19
|
+
const prompt = () => undefined;
|
|
20
|
+
const logger = {info: () => {}};
|
|
20
21
|
|
|
21
22
|
it('should scaffold the repository and vcs host details', async () => {
|
|
22
23
|
const host = any.word();
|
|
@@ -29,14 +30,14 @@ describe('vcs scaffolder', () => {
|
|
|
29
30
|
const vcsHostDetails = {host, owner, name};
|
|
30
31
|
const sshUrl = any.url();
|
|
31
32
|
const remoteOriginNextSteps = any.listOf(any.simpleObject);
|
|
32
|
-
when(promptForRepoCreation).calledWith(
|
|
33
|
+
when(promptForRepoCreation).calledWith({prompt}).thenResolve(true);
|
|
33
34
|
when(alreadyVersionedByGit).calledWith({projectRoot}).thenResolve(false);
|
|
34
35
|
when(scaffoldVcsHost)
|
|
35
|
-
.calledWith(vcsHosts,
|
|
36
|
+
.calledWith(vcsHosts, {projectName, projectRoot, description, visibility}, {prompt})
|
|
36
37
|
.thenResolve({vcs: {...vcsHostDetails, sshUrl, ...any.simpleObject()}});
|
|
37
|
-
when(defineRemoteOrigin).calledWith(projectRoot, sshUrl).thenResolve({nextSteps: remoteOriginNextSteps});
|
|
38
|
+
when(defineRemoteOrigin).calledWith(projectRoot, sshUrl, {logger}).thenResolve({nextSteps: remoteOriginNextSteps});
|
|
38
39
|
|
|
39
|
-
expect(await scaffoldVcs({projectRoot, projectName,
|
|
40
|
+
expect(await scaffoldVcs({projectRoot, projectName, vcsHosts, visibility, description}, {prompt, logger})).toEqual({
|
|
40
41
|
vcs: vcsHostDetails,
|
|
41
42
|
nextSteps: [{summary: 'Commit scaffolded files'}, ...remoteOriginNextSteps]
|
|
42
43
|
});
|
|
@@ -45,16 +46,16 @@ describe('vcs scaffolder', () => {
|
|
|
45
46
|
|
|
46
47
|
it('should not scaffold a repository or vcs host details when the project is already versioned by git', async () => {
|
|
47
48
|
const existingVcsDetails = any.simpleObject();
|
|
48
|
-
when(promptForRepoCreation).calledWith(
|
|
49
|
+
when(promptForRepoCreation).calledWith({prompt}).thenResolve(true);
|
|
49
50
|
when(alreadyVersionedByGit).calledWith({projectRoot}).thenResolve(true);
|
|
50
51
|
when(determineExistingVcsDetails).calledWith({projectRoot}).thenResolve(existingVcsDetails);
|
|
51
52
|
|
|
52
|
-
expect(await scaffoldVcs({projectRoot,
|
|
53
|
+
expect(await scaffoldVcs({projectRoot}, {prompt, logger})).toEqual(existingVcsDetails);
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
it('should not scaffold a repository or vcs host details when a repository should not be created', async () => {
|
|
56
|
-
when(promptForRepoCreation).calledWith(
|
|
57
|
+
when(promptForRepoCreation).calledWith({prompt}).thenResolve(false);
|
|
57
58
|
|
|
58
|
-
expect(await scaffoldVcs({projectRoot,
|
|
59
|
+
expect(await scaffoldVcs({projectRoot}, {prompt})).toEqual({});
|
|
59
60
|
});
|
|
60
61
|
});
|
package/src/options-schemas.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {validateOptions} from '@form8ion/core';
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest';
|
|
4
|
-
import any from '@travi/any';
|
|
5
|
-
|
|
6
|
-
import {decisionsSchema} from './options-schemas.js';
|
|
7
|
-
|
|
8
|
-
describe('generic options schemas', () => {
|
|
9
|
-
describe('decisions', () => {
|
|
10
|
-
it('should return the validated options', () => {
|
|
11
|
-
const options = any.simpleObject();
|
|
12
|
-
|
|
13
|
-
expect(validateOptions(decisionsSchema, options)).toEqual(options);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should require the decisions to be defined as a map', () => {
|
|
17
|
-
expect(() => validateOptions(decisionsSchema, any.word())).toThrowError('must be of type object');
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
});
|