@form8ion/project 21.0.1 → 21.0.3
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/lib/index.js +75 -67
- package/lib/index.js.map +1 -1
- package/package.json +17 -14
- package/src/contributing/index.js +1 -0
- package/src/contributing/scaffolder.js +17 -0
- package/src/contributing/scaffolder.test.js +22 -0
- package/src/dependency-updater/prompt.js +11 -0
- package/src/dependency-updater/prompt.test.js +30 -0
- package/src/dependency-updater/scaffolder.js +14 -0
- package/src/dependency-updater/scaffolder.test.js +42 -0
- package/src/dependency-updater/schema.js +4 -0
- package/src/dependency-updater/schema.test.js +43 -0
- package/src/editorconfig/index.js +1 -0
- package/src/editorconfig/scaffolder.js +7 -0
- package/src/editorconfig/scaffolder.test.js +26 -0
- package/src/index.js +9 -0
- package/src/language/index.js +2 -0
- package/src/language/prompt.js +12 -0
- package/src/language/prompt.test.js +30 -0
- package/src/language/scaffolder.js +12 -0
- package/src/language/scaffolder.test.js +32 -0
- package/src/language/schema.js +4 -0
- package/src/language/schema.test.js +43 -0
- package/src/license/index.js +3 -0
- package/src/license/lifter.js +19 -0
- package/src/license/lifter.test.js +30 -0
- package/src/license/scaffolder.js +24 -0
- package/src/license/scaffolder.test.js +60 -0
- package/src/license/tester.js +5 -0
- package/src/license/tester.test.js +25 -0
- package/src/lift.js +18 -0
- package/src/lift.test.js +40 -0
- package/src/options-schemas.js +3 -0
- package/src/options-schemas.test.js +20 -0
- package/src/options-validator.js +18 -0
- package/src/options-validator.test.js +50 -0
- package/src/prompts/conditionals.js +13 -0
- package/src/prompts/conditionals.test.js +51 -0
- package/src/prompts/question-names.js +7 -0
- package/src/prompts/questions.js +6 -0
- package/src/prompts/questions.test.js +25 -0
- package/src/prompts/terminal-prompt.js +5 -0
- package/src/prompts/terminal-prompt.test.js +20 -0
- package/src/scaffolder.js +76 -0
- package/src/scaffolder.test.js +297 -0
- package/src/template-path.js +8 -0
- package/src/template-path.test.js +14 -0
- package/src/vcs/git/index.js +1 -0
- package/src/vcs/git/remotes.js +51 -0
- package/src/vcs/git/remotes.test.js +86 -0
- package/src/vcs/host/index.js +1 -0
- package/src/vcs/host/prompt.js +15 -0
- package/src/vcs/host/prompt.test.js +47 -0
- package/src/vcs/host/scaffolder.js +16 -0
- package/src/vcs/host/scaffolder.test.js +41 -0
- package/src/vcs/host/schema.js +4 -0
- package/src/vcs/host/schema.test.js +46 -0
- package/src/vcs/index.js +1 -0
- package/src/vcs/prompt.js +17 -0
- package/src/vcs/prompt.test.js +27 -0
- package/src/vcs/scaffolder.js +34 -0
- package/src/vcs/scaffolder.test.js +60 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {promises as fs} from 'node:fs';
|
|
2
|
+
import {resolve} from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import scaffoldEditorconfig from './scaffolder.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('fs');
|
|
10
|
+
|
|
11
|
+
describe('editorconfig scaffolder', () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should create the config file', async () => {
|
|
17
|
+
const projectRoot = any.string();
|
|
18
|
+
|
|
19
|
+
await scaffoldEditorconfig({projectRoot});
|
|
20
|
+
|
|
21
|
+
expect(fs.copyFile).toHaveBeenCalledWith(
|
|
22
|
+
resolve(__dirname, '..', '..', 'templates', 'editorconfig.ini'),
|
|
23
|
+
`${projectRoot}/.editorconfig`
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {questionNames as coreQuestionNames} from '@form8ion/core';
|
|
2
|
+
import {questionNames as projectScaffolderQuestionNames} from './prompts/question-names.js';
|
|
3
|
+
|
|
4
|
+
export * from './scaffolder.js';
|
|
5
|
+
export {default as lift} from './lift.js';
|
|
6
|
+
export const questionNames = {
|
|
7
|
+
...coreQuestionNames,
|
|
8
|
+
...projectScaffolderQuestionNames
|
|
9
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {prompt} from '@form8ion/overridable-prompts';
|
|
2
|
+
|
|
3
|
+
import {questionNames} from '../prompts/question-names.js';
|
|
4
|
+
|
|
5
|
+
export default function (languages, decisions) {
|
|
6
|
+
return prompt([{
|
|
7
|
+
name: questionNames.PROJECT_LANGUAGE,
|
|
8
|
+
type: 'list',
|
|
9
|
+
message: 'What type of project is this?',
|
|
10
|
+
choices: [...Object.keys(languages), 'Other']
|
|
11
|
+
}], decisions);
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {prompt} from '@form8ion/overridable-prompts';
|
|
2
|
+
|
|
3
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import any from '@travi/any';
|
|
5
|
+
import {when} from 'vitest-when';
|
|
6
|
+
|
|
7
|
+
import {questionNames} from '../prompts/question-names.js';
|
|
8
|
+
import promptForLanguageDetails from './prompt.js';
|
|
9
|
+
|
|
10
|
+
vi.mock('@form8ion/overridable-prompts');
|
|
11
|
+
|
|
12
|
+
describe('language prompt', () => {
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should prompt for the language details', async () => {
|
|
18
|
+
const answers = any.simpleObject();
|
|
19
|
+
const decisions = any.simpleObject();
|
|
20
|
+
const languages = any.simpleObject();
|
|
21
|
+
when(prompt).calledWith([{
|
|
22
|
+
name: questionNames.PROJECT_LANGUAGE,
|
|
23
|
+
type: 'list',
|
|
24
|
+
message: 'What type of project is this?',
|
|
25
|
+
choices: [...Object.keys(languages), 'Other']
|
|
26
|
+
}], decisions).thenResolve(answers);
|
|
27
|
+
|
|
28
|
+
expect(await promptForLanguageDetails(languages, decisions)).toEqual(answers);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {questionNames} from '../prompts/question-names.js';
|
|
2
|
+
import promptForLanguageDetails from './prompt.js';
|
|
3
|
+
|
|
4
|
+
export default async function (languagePlugins, decisions, options) {
|
|
5
|
+
const {[questionNames.PROJECT_LANGUAGE]: chosenLanguage} = await promptForLanguageDetails(languagePlugins, decisions);
|
|
6
|
+
|
|
7
|
+
const plugin = languagePlugins[chosenLanguage];
|
|
8
|
+
|
|
9
|
+
if (plugin) return plugin.scaffold(options);
|
|
10
|
+
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
import {when} from 'vitest-when';
|
|
4
|
+
|
|
5
|
+
import * as languagePrompt from './prompt.js';
|
|
6
|
+
import {questionNames} from '../prompts/question-names.js';
|
|
7
|
+
import scaffold from './scaffolder.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('./prompt.js');
|
|
10
|
+
|
|
11
|
+
describe('language scaffolder', () => {
|
|
12
|
+
it('should scaffold the chosen language', async () => {
|
|
13
|
+
const options = any.simpleObject();
|
|
14
|
+
const chosenLanguage = any.word();
|
|
15
|
+
const scaffolderResult = any.simpleObject();
|
|
16
|
+
const decisions = any.simpleObject();
|
|
17
|
+
const chosenLanguageScaffolder = vi.fn();
|
|
18
|
+
const plugins = {...any.simpleObject(), [chosenLanguage]: {scaffold: chosenLanguageScaffolder}};
|
|
19
|
+
when(languagePrompt.default)
|
|
20
|
+
.calledWith(plugins, decisions)
|
|
21
|
+
.thenResolve({[questionNames.PROJECT_LANGUAGE]: chosenLanguage});
|
|
22
|
+
when(chosenLanguageScaffolder).calledWith(options).thenResolve(scaffolderResult);
|
|
23
|
+
|
|
24
|
+
expect(await scaffold(plugins, decisions, options)).toEqual(scaffolderResult);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should not result in an error when choosing a language without a defined scaffolder', async () => {
|
|
28
|
+
languagePrompt.default.mockResolvedValue({[questionNames.PROJECT_LANGUAGE]: any.word()});
|
|
29
|
+
|
|
30
|
+
await scaffold(any.simpleObject(), any.simpleObject(), any.simpleObject());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {validateOptions} from '@form8ion/core';
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import any from '@travi/any';
|
|
5
|
+
|
|
6
|
+
import languageSchema from './schema.js';
|
|
7
|
+
|
|
8
|
+
describe('language plugins schema', () => {
|
|
9
|
+
const key = any.word();
|
|
10
|
+
|
|
11
|
+
it('should return the validated options', () => {
|
|
12
|
+
const options = any.objectWithKeys(
|
|
13
|
+
any.listOf(any.string),
|
|
14
|
+
{factory: () => ({scaffold: foo => foo})}
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
expect(validateOptions(languageSchema, options)).toEqual(options);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should require options to be provided as an object', () => {
|
|
21
|
+
expect(() => validateOptions(languageSchema, {[key]: []}))
|
|
22
|
+
.toThrowError(`"${key}" must be of type object`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should require a `scaffold` property to be included', () => {
|
|
26
|
+
expect(() => validateOptions(languageSchema, {[key]: {}}))
|
|
27
|
+
.toThrowError(`"${key}.scaffold" is required`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should require `scaffold` to be a function', () => {
|
|
31
|
+
expect(() => validateOptions(languageSchema, {[key]: {scaffold: any.word()}}))
|
|
32
|
+
.toThrowError(`"${key}.scaffold" must be of type function`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should require the scaffolder to accept a single argument', () => {
|
|
36
|
+
expect(() => validateOptions(languageSchema, {[key]: {scaffold: () => undefined}}))
|
|
37
|
+
.toThrowError(`"${key}.scaffold" must have an arity greater or equal to 1`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should default to an empty map when no updaters are provided', () => {
|
|
41
|
+
expect(validateOptions(languageSchema)).toEqual({});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function repositoryIsHostedOnGithub(vcs) {
|
|
2
|
+
return vcs && 'github' === vcs.host;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default function ({vcs}) {
|
|
6
|
+
return {
|
|
7
|
+
...repositoryIsHostedOnGithub(vcs) && {
|
|
8
|
+
badges: {
|
|
9
|
+
consumer: {
|
|
10
|
+
license: {
|
|
11
|
+
link: 'LICENSE',
|
|
12
|
+
img: `https://img.shields.io/github/license/${vcs.owner}/${vcs.name}.svg?logo=opensourceinitiative`,
|
|
13
|
+
text: 'license'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import any from '@travi/any';
|
|
2
|
+
import {describe, expect, it} from 'vitest';
|
|
3
|
+
|
|
4
|
+
import lift from './lifter.js';
|
|
5
|
+
|
|
6
|
+
describe('license lifter', () => {
|
|
7
|
+
it('should provide the badge if the repository is hosted on GitHub', async () => {
|
|
8
|
+
const vcsOwner = any.word();
|
|
9
|
+
const vcsName = any.word();
|
|
10
|
+
const {badges} = await lift({vcs: {host: 'github', owner: vcsOwner, name: vcsName}});
|
|
11
|
+
|
|
12
|
+
expect(badges.consumer.license).toEqual({
|
|
13
|
+
link: 'LICENSE',
|
|
14
|
+
img: `https://img.shields.io/github/license/${vcsOwner}/${vcsName}.svg?logo=opensourceinitiative`,
|
|
15
|
+
text: 'license'
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should not provide the badge if the repository is not hosted on GitHub', async () => {
|
|
20
|
+
const {badges} = await lift({vcs: {host: any.word()}});
|
|
21
|
+
|
|
22
|
+
expect(badges).toBe(undefined);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should not provide the badge if the project is not under version control', async () => {
|
|
26
|
+
const {badges} = await lift({});
|
|
27
|
+
|
|
28
|
+
expect(badges).toBe(undefined);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {promises as fs} from 'fs';
|
|
2
|
+
import wrap from 'word-wrap';
|
|
3
|
+
import mustache from 'mustache';
|
|
4
|
+
// eslint-disable-next-line import/extensions
|
|
5
|
+
import spdxLicenseList from 'spdx-license-list/full.js';
|
|
6
|
+
import {info} from '@travi/cli-messages';
|
|
7
|
+
|
|
8
|
+
export default async function ({projectRoot, license, copyright}) {
|
|
9
|
+
if (license) {
|
|
10
|
+
info('Generating License');
|
|
11
|
+
|
|
12
|
+
const licenseContent = spdxLicenseList[license].licenseText;
|
|
13
|
+
|
|
14
|
+
await fs.writeFile(
|
|
15
|
+
`${projectRoot}/LICENSE`,
|
|
16
|
+
`${wrap(
|
|
17
|
+
mustache.render(licenseContent, {year: copyright.year, 'copyright holders': copyright.holder}, {}, ['<', '>']),
|
|
18
|
+
{width: 80, indent: ''}
|
|
19
|
+
)}\n`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {promises as fs} from 'fs';
|
|
2
|
+
import wrap from 'word-wrap';
|
|
3
|
+
import spdxLicenseListWithContent from 'spdx-license-list/full';
|
|
4
|
+
import spdxLicenseList from 'spdx-license-list/simple';
|
|
5
|
+
|
|
6
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
7
|
+
import any from '@travi/any';
|
|
8
|
+
|
|
9
|
+
import scaffoldLicense from './scaffolder.js';
|
|
10
|
+
|
|
11
|
+
vi.mock('fs');
|
|
12
|
+
|
|
13
|
+
describe('license', () => {
|
|
14
|
+
const license = any.fromList(Array.from(spdxLicenseList));
|
|
15
|
+
const year = any.word();
|
|
16
|
+
const copyrightHolders = any.sentence();
|
|
17
|
+
const copyright = {year, holder: copyrightHolders};
|
|
18
|
+
const projectRoot = any.string();
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should not generate a license file when no license was chosen', async () => {
|
|
25
|
+
await scaffoldLicense({});
|
|
26
|
+
|
|
27
|
+
expect(fs.writeFile).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should write the contents for the chosen license to LICENSE', async () => {
|
|
31
|
+
expect(await scaffoldLicense({projectRoot, license, copyright, vcs: {}})).toEqual({});
|
|
32
|
+
|
|
33
|
+
expect(fs.writeFile).toHaveBeenCalledWith(
|
|
34
|
+
`${projectRoot}/LICENSE`,
|
|
35
|
+
`${wrap(
|
|
36
|
+
`${spdxLicenseListWithContent[license].licenseText}\n`
|
|
37
|
+
.replace(/<\s*year\s*>/gm, year)
|
|
38
|
+
.replace(/<copyright holders>/gm, copyrightHolders)
|
|
39
|
+
.replace(/<(.+?)>/gm, ''),
|
|
40
|
+
{width: 80, indent: ''}
|
|
41
|
+
)}\n`
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should write the common version of the MIT license to LICENSE, when chosen', async () => {
|
|
46
|
+
expect(await scaffoldLicense({projectRoot, license: 'MIT', copyright, vcs: {}})).toEqual({});
|
|
47
|
+
|
|
48
|
+
expect(fs.writeFile).toHaveBeenCalledWith(
|
|
49
|
+
`${projectRoot}/LICENSE`,
|
|
50
|
+
`${wrap(
|
|
51
|
+
spdxLicenseListWithContent.MIT.licenseText
|
|
52
|
+
.replace('(including the next paragraph) ', '')
|
|
53
|
+
.replace(/<\s*year\s*>/gm, year)
|
|
54
|
+
.replace(/<copyright holders>/gm, copyrightHolders)
|
|
55
|
+
.replace(/<(.+?)>/gm, ''),
|
|
56
|
+
{width: 80, indent: ''}
|
|
57
|
+
)}\n`
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {fileExists} from '@form8ion/core';
|
|
2
|
+
|
|
3
|
+
import {when} from 'vitest-when';
|
|
4
|
+
import {expect, it, describe, vi} from 'vitest';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import projectIsLicensed from './tester.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@form8ion/core');
|
|
10
|
+
|
|
11
|
+
describe('license predicate', () => {
|
|
12
|
+
const projectRoot = any.string();
|
|
13
|
+
|
|
14
|
+
it('should return `true` when a `LICENSE` file exists', async () => {
|
|
15
|
+
when(fileExists).calledWith(`${projectRoot}/LICENSE`).thenResolve(true);
|
|
16
|
+
|
|
17
|
+
expect(await projectIsLicensed({projectRoot})).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return `false` when a `LICENSE` does not file exist', async () => {
|
|
21
|
+
when(fileExists).calledWith(`${projectRoot}/LICENSE`).thenResolve(false);
|
|
22
|
+
|
|
23
|
+
expect(await projectIsLicensed({projectRoot})).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
package/src/lift.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {applyEnhancers} from '@form8ion/core';
|
|
2
|
+
import {lift as liftReadme} from '@form8ion/readme';
|
|
3
|
+
import * as gitPlugin from '@form8ion/git';
|
|
4
|
+
|
|
5
|
+
import * as licensePlugin from './license/index.js';
|
|
6
|
+
|
|
7
|
+
export default async function ({projectRoot, results, enhancers, vcs, dependencies}) {
|
|
8
|
+
const enhancerResults = await applyEnhancers({
|
|
9
|
+
results,
|
|
10
|
+
enhancers: {...enhancers, gitPlugin, licensePlugin},
|
|
11
|
+
options: {projectRoot, vcs},
|
|
12
|
+
dependencies
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await liftReadme({projectRoot, results: enhancerResults});
|
|
16
|
+
|
|
17
|
+
return enhancerResults;
|
|
18
|
+
}
|
package/src/lift.test.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as core from '@form8ion/core';
|
|
2
|
+
import * as readme from '@form8ion/readme';
|
|
3
|
+
import * as gitPlugin from '@form8ion/git';
|
|
4
|
+
|
|
5
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
6
|
+
import any from '@travi/any';
|
|
7
|
+
import {when} from 'vitest-when';
|
|
8
|
+
|
|
9
|
+
import * as licensePlugin from './license/index.js';
|
|
10
|
+
import lift from './lift.js';
|
|
11
|
+
|
|
12
|
+
vi.mock('deepmerge');
|
|
13
|
+
vi.mock('@form8ion/core');
|
|
14
|
+
vi.mock('@form8ion/readme');
|
|
15
|
+
|
|
16
|
+
describe('lift', () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should lift the README based on the provided results', async () => {
|
|
22
|
+
const projectRoot = any.string();
|
|
23
|
+
const enhancers = any.simpleObject();
|
|
24
|
+
const dependencies = any.simpleObject();
|
|
25
|
+
const vcs = any.simpleObject();
|
|
26
|
+
const results = any.simpleObject();
|
|
27
|
+
const enhancerResults = any.simpleObject();
|
|
28
|
+
when(core.applyEnhancers)
|
|
29
|
+
.calledWith({
|
|
30
|
+
results,
|
|
31
|
+
enhancers: {...enhancers, gitPlugin, licensePlugin},
|
|
32
|
+
options: {projectRoot, vcs},
|
|
33
|
+
dependencies
|
|
34
|
+
})
|
|
35
|
+
.thenResolve(enhancerResults);
|
|
36
|
+
|
|
37
|
+
expect(await lift({projectRoot, results, enhancers, vcs, dependencies})).toEqual(enhancerResults);
|
|
38
|
+
expect(readme.lift).toHaveBeenCalledWith({projectRoot, results: enhancerResults});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {validateOptions} from '@form8ion/core';
|
|
2
|
+
import joi from 'joi';
|
|
3
|
+
|
|
4
|
+
import languagePluginsSchema from './language/schema.js';
|
|
5
|
+
import vcsHostPluginsSchema from './vcs/host/schema.js';
|
|
6
|
+
import dependencyUpdaterPluginsSchema from './dependency-updater/schema.js';
|
|
7
|
+
import {decisionsSchema} from './options-schemas.js';
|
|
8
|
+
|
|
9
|
+
export function validate(options) {
|
|
10
|
+
return validateOptions(joi.object({
|
|
11
|
+
decisions: decisionsSchema,
|
|
12
|
+
plugins: joi.object({
|
|
13
|
+
dependencyUpdaters: dependencyUpdaterPluginsSchema,
|
|
14
|
+
languages: languagePluginsSchema,
|
|
15
|
+
vcsHosts: vcsHostPluginsSchema
|
|
16
|
+
})
|
|
17
|
+
}), options) || {};
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import joi from 'joi';
|
|
2
|
+
import * as core from '@form8ion/core';
|
|
3
|
+
|
|
4
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
import {when} from 'vitest-when';
|
|
7
|
+
|
|
8
|
+
import languagePluginsSchema from './language/schema.js';
|
|
9
|
+
import {decisionsSchema} from './options-schemas.js';
|
|
10
|
+
import vcsHostPluginsSchema from './vcs/host/schema.js';
|
|
11
|
+
import dependencyUpdaterPluginsSchema from './dependency-updater/schema.js';
|
|
12
|
+
import {validate} from './options-validator.js';
|
|
13
|
+
|
|
14
|
+
vi.mock('@form8ion/core', async () => ({
|
|
15
|
+
validateOptions: vi.fn(),
|
|
16
|
+
optionsSchemas: {form8ionPlugin: joi.object()}
|
|
17
|
+
}));
|
|
18
|
+
vi.mock('./vcs/host/schema.js');
|
|
19
|
+
|
|
20
|
+
describe('options validator', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.spyOn(joi, 'object');
|
|
23
|
+
vi.spyOn(joi, 'string');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should build the full schema and call the base validator', () => {
|
|
31
|
+
const options = any.simpleObject();
|
|
32
|
+
const pluginsSchema = any.simpleObject();
|
|
33
|
+
const fullSchema = any.simpleObject();
|
|
34
|
+
const validatedOptions = any.simpleObject();
|
|
35
|
+
when(joi.object)
|
|
36
|
+
.calledWith({
|
|
37
|
+
dependencyUpdaters: dependencyUpdaterPluginsSchema,
|
|
38
|
+
languages: languagePluginsSchema,
|
|
39
|
+
vcsHosts: vcsHostPluginsSchema
|
|
40
|
+
})
|
|
41
|
+
.thenReturn(pluginsSchema);
|
|
42
|
+
when(joi.object).calledWith({
|
|
43
|
+
decisions: decisionsSchema,
|
|
44
|
+
plugins: pluginsSchema
|
|
45
|
+
}).thenReturn(fullSchema);
|
|
46
|
+
when(core.validateOptions).calledWith(fullSchema, options).thenReturn(validatedOptions);
|
|
47
|
+
|
|
48
|
+
expect(validate(options)).toEqual(validatedOptions);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {questionNames} from './question-names.js';
|
|
2
|
+
|
|
3
|
+
export function unlicensedConfirmationShouldBePresented(answers) {
|
|
4
|
+
return 'Private' === answers[questionNames.VISIBILITY];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function licenseChoicesShouldBePresented(answers) {
|
|
8
|
+
return 'Public' === answers[questionNames.VISIBILITY] || !answers[questionNames.UNLICENSED];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function copyrightInformationShouldBeRequested(answers) {
|
|
12
|
+
return !!answers[questionNames.LICENSE];
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
|
|
4
|
+
import {questionNames} from './question-names.js';
|
|
5
|
+
import {
|
|
6
|
+
copyrightInformationShouldBeRequested,
|
|
7
|
+
licenseChoicesShouldBePresented,
|
|
8
|
+
unlicensedConfirmationShouldBePresented
|
|
9
|
+
} from './conditionals.js';
|
|
10
|
+
|
|
11
|
+
describe('prompt conditionals', () => {
|
|
12
|
+
describe('unlicensed confirmation', () => {
|
|
13
|
+
it('should show the unlicensed confirmation for a private project', () => {
|
|
14
|
+
expect(unlicensedConfirmationShouldBePresented({[questionNames.VISIBILITY]: 'Private'})).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should not show the unlicensed confirmation for a public project', () => {
|
|
18
|
+
expect(unlicensedConfirmationShouldBePresented({[questionNames.VISIBILITY]: 'Public'})).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('license choices', () => {
|
|
23
|
+
it('should show the license choices for a public project', () => {
|
|
24
|
+
expect(licenseChoicesShouldBePresented({[questionNames.VISIBILITY]: 'Public'})).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should show the license choices for a private project that is not unlicensed', () => {
|
|
28
|
+
expect(licenseChoicesShouldBePresented({
|
|
29
|
+
[questionNames.VISIBILITY]: 'Private',
|
|
30
|
+
[questionNames.UNLICENSED]: false
|
|
31
|
+
})).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should not show the license choices for a private project that is unlicensed', () => {
|
|
35
|
+
expect(licenseChoicesShouldBePresented({
|
|
36
|
+
[questionNames.VISIBILITY]: 'Private',
|
|
37
|
+
[questionNames.UNLICENSED]: true
|
|
38
|
+
})).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('copyright', () => {
|
|
43
|
+
it('should request the copyright information when the project is licensed', () => {
|
|
44
|
+
expect(copyrightInformationShouldBeRequested({[questionNames.LICENSE]: any.string()})).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not request the copyright information when the project is not licensed', () => {
|
|
48
|
+
expect(copyrightInformationShouldBeRequested({[questionNames.LICENSE]: undefined})).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as core from '@form8ion/core';
|
|
2
|
+
import * as prompts from '@form8ion/overridable-prompts';
|
|
3
|
+
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
import {when} from 'vitest-when';
|
|
7
|
+
|
|
8
|
+
import {promptForBaseDetails} from './questions.js';
|
|
9
|
+
|
|
10
|
+
vi.mock('@form8ion/core');
|
|
11
|
+
vi.mock('@form8ion/overridable-prompts');
|
|
12
|
+
|
|
13
|
+
describe('base details prompt', () => {
|
|
14
|
+
const projectPath = any.string();
|
|
15
|
+
const answers = any.simpleObject();
|
|
16
|
+
const decisions = any.simpleObject();
|
|
17
|
+
const questions = any.listOf(any.simpleObject);
|
|
18
|
+
|
|
19
|
+
it('should prompt for the necessary details', async () => {
|
|
20
|
+
when(core.questionsForBaseDetails).calledWith(decisions, projectPath).thenReturn(questions);
|
|
21
|
+
when(prompts.prompt).calledWith(questions, decisions).thenResolve(answers);
|
|
22
|
+
|
|
23
|
+
expect(await promptForBaseDetails(projectPath, decisions)).toEqual(answers);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {prompt as promptWithInquirer} from '@form8ion/overridable-prompts';
|
|
2
|
+
|
|
3
|
+
import {when} from 'vitest-when';
|
|
4
|
+
import {describe, it, vi, expect} from 'vitest';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import prompt from './terminal-prompt.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@form8ion/overridable-prompts');
|
|
10
|
+
|
|
11
|
+
describe('terminal prompt', () => {
|
|
12
|
+
it('should present the provided questions using inquirer', async () => {
|
|
13
|
+
const questions = any.listOf(any.simpleObject);
|
|
14
|
+
const decisions = any.simpleObject();
|
|
15
|
+
const answers = any.simpleObject();
|
|
16
|
+
when(promptWithInquirer).calledWith(questions, decisions).thenResolve(answers);
|
|
17
|
+
|
|
18
|
+
expect(await prompt(decisions)({questions})).toEqual(answers);
|
|
19
|
+
});
|
|
20
|
+
});
|