@form8ion/project 22.0.0-beta.2 → 22.0.0-beta.20

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.
Files changed (55) hide show
  1. package/README.md +70 -26
  2. package/lib/index.js +205 -104
  3. package/lib/index.js.map +1 -1
  4. package/package.json +27 -29
  5. package/src/ci-provider/index.js +1 -0
  6. package/src/ci-provider/prompt.js +17 -0
  7. package/src/ci-provider/prompt.test.js +27 -0
  8. package/src/ci-provider/scaffolder.js +27 -0
  9. package/src/ci-provider/scaffolder.test.js +68 -0
  10. package/src/ci-provider/schema.js +4 -0
  11. package/src/ci-provider/schema.test.js +43 -0
  12. package/src/contributing/scaffolder.js +2 -2
  13. package/src/contributing/scaffolder.test.js +14 -2
  14. package/src/dependency-updater/prompt.js +14 -8
  15. package/src/dependency-updater/prompt.test.js +16 -17
  16. package/src/dependency-updater/scaffolder.js +4 -2
  17. package/src/dependency-updater/scaffolder.test.js +18 -15
  18. package/src/editorconfig/index.js +1 -0
  19. package/src/editorconfig/scaffolder.js +1 -1
  20. package/src/editorconfig/tester.js +5 -0
  21. package/src/editorconfig/tester.test.js +25 -0
  22. package/src/index.js +1 -7
  23. package/src/language/prompt.js +14 -9
  24. package/src/language/prompt.test.js +15 -16
  25. package/src/language/scaffolder.js +4 -2
  26. package/src/language/scaffolder.test.js +13 -8
  27. package/src/license/lifter.js +1 -1
  28. package/src/license/scaffolder.js +2 -3
  29. package/src/license/scaffolder.test.js +5 -8
  30. package/src/license/tester.js +1 -1
  31. package/src/lift.js +6 -1
  32. package/src/lift.test.js +24 -12
  33. package/src/options-validator.js +3 -3
  34. package/src/options-validator.test.js +4 -6
  35. package/src/prompts/index.js +20 -0
  36. package/src/prompts/question-names.js +19 -5
  37. package/src/prompts/questions.js +7 -3
  38. package/src/prompts/questions.test.js +5 -6
  39. package/src/scaffolder.js +18 -17
  40. package/src/scaffolder.test.js +39 -31
  41. package/src/template-path.js +1 -1
  42. package/src/vcs/git/remotes.js +6 -7
  43. package/src/vcs/git/remotes.test.js +21 -16
  44. package/src/vcs/host/prompt.js +15 -10
  45. package/src/vcs/host/prompt.test.js +31 -25
  46. package/src/vcs/host/scaffolder.js +4 -2
  47. package/src/vcs/host/scaffolder.test.js +9 -7
  48. package/src/vcs/prompt.js +11 -9
  49. package/src/vcs/prompt.test.js +15 -12
  50. package/src/vcs/scaffolder.js +9 -11
  51. package/src/vcs/scaffolder.test.js +10 -9
  52. package/src/options-schemas.js +0 -3
  53. package/src/options-schemas.test.js +0 -20
  54. package/src/prompts/conditionals.js +0 -13
  55. package/src/prompts/conditionals.test.js +0 -51
@@ -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 decisions = any.simpleObject();
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({decisions, plugins: {dependencyUpdaters, languages, vcsHosts}});
104
+ .thenReturn({plugins: {dependencyUpdaters, ciProviders, languages, vcsHosts}});
99
105
  when(prompts.promptForBaseDetails)
100
- .calledWith(projectPath, decisions)
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, decisions, vcsHosts, visibility, description})
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, decisions, {projectRoot: projectPath})
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, undefined)
168
+ .calledWith(projectPath, {prompt})
159
169
  .thenResolve({
160
170
  [coreQuestionNames.DESCRIPTION]: description,
161
- [questionNames.GIT_REPO]: true,
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({[questionNames.GIT_REPO]: false});
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
- [questionNames.GIT_REPO]: true,
223
+ [GIT_REPO]: true,
215
224
  [coreQuestionNames.LICENSE]: license,
216
225
  [coreQuestionNames.DESCRIPTION]: description
217
226
  });
218
- when(scaffoldLanguage).calledWith(languages, decisions, {
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, decisions, plugins: {languages}});
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
- [questionNames.GIT_REPO]: true,
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}, decisions});
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
  });
@@ -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);
@@ -1,6 +1,5 @@
1
1
  import {simpleGit} from 'simple-git';
2
- import hostedGitInfo from 'hosted-git-info';
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 {user, project, type} = hostedGitInfo.fromUrl(remoteOrigin);
19
+ const {owner, name, host} = parseGitUrl(remoteOrigin.trimEnd());
21
20
 
22
- return {vcs: {owner: user, name: project, host: type}};
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 hostedGitInfo from 'hosted-git-info';
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('hosted-git-info');
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
- const vcsHostAccount = any.word();
23
- when(simpleGit).calledWith({baseDir: projectRoot}).thenReturn({remote});
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({user: vcsHostAccount, project: repoName, type: vcsHost.toLowerCase()});
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
  });
@@ -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 default async function (hosts, decisions) {
6
- const answers = await prompt([{
7
- name: questionNames.REPO_HOST,
8
- type: 'list',
9
- message: 'Where will the repository be hosted?',
10
- choices: Object.keys(hosts)
11
- }], decisions);
12
- const host = hosts[answers[questionNames.REPO_HOST]];
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 * as prompts from '@form8ion/overridable-prompts';
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
- afterEach(() => {
18
- vi.clearAllMocks();
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, [questionNames.REPO_HOST]: host};
26
- when(prompts.prompt).calledWith([{
27
- name: questionNames.REPO_HOST,
28
- type: 'list',
29
- message: 'Where will the repository be hosted?',
30
- choices: hostNames
31
- }], decisions).thenResolve(answersWithHostChoice);
32
-
33
- expect(await promptForVcsHostDetails(hosts, decisions)).toEqual(answersWithHostChoice);
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, [questionNames.REPO_HOST]: 'Other'};
38
- when(prompts.prompt).calledWith([{
39
- name: questionNames.REPO_HOST,
40
- type: 'list',
41
- message: 'Where will the repository be hosted?',
42
- choices: []
43
- }], decisions).thenResolve(answersWithHostChoice);
44
-
45
- expect(await promptForVcsHostDetails({}, decisions)).toEqual(answersWithHostChoice);
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
- export default async function scaffoldVcsHost(hosts, decisions, options) {
5
- const {[questionNames.REPO_HOST]: chosenHost} = await promptForVcsHostDetails(hosts, decisions);
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 decisions = any.simpleObject();
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, decisions)
23
- .thenResolve({[questionNames.REPO_HOST]: chosenHost, [questionNames.REPO_OWNER]: owner});
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, decisions, options)).toEqual(results);
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, decisions)
33
- .thenResolve({[questionNames.REPO_HOST]: any.word()});
34
+ .calledWith(hostPlugins, {prompt})
35
+ .thenResolve({[REPO_HOST]: any.word()});
34
36
 
35
- expect(await scaffoldVcsHost(hostPlugins, decisions, options)).toEqual({vcs: {}});
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 default async function promptForRepoCreation(decisions) {
6
- const {[questionNames.GIT_REPO]: gitRepoShouldBeCreated} = await prompt(
7
- [{
8
- name: questionNames.GIT_REPO,
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
- decisions
14
- );
15
+ }]
16
+ });
15
17
 
16
18
  return gitRepoShouldBeCreated;
17
19
  }
@@ -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 decisions = any.simpleObject();
14
+ const prompt = vi.fn();
15
15
  const repoShouldBeCreated = any.boolean();
16
16
  when(prompt)
17
- .calledWith([{
18
- name: questionNames.GIT_REPO,
19
- type: 'confirm',
20
- default: true,
21
- message: 'Should a git repository be initialized?'
22
- }], decisions)
23
- .thenResolve({[questionNames.GIT_REPO]: repoShouldBeCreated});
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(decisions)).toBe(repoShouldBeCreated);
28
+ expect(await promptForRepoCreation({prompt})).toBe(repoShouldBeCreated);
26
29
  });
27
30
  });
@@ -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({projectRoot, projectName, decisions, vcsHosts, visibility, description}) {
9
- if (await repositoryShouldBeCreated(decisions)) {
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 {vcs: await determineExistingVcsDetails({projectRoot})};
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},