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