create-instantsearch-app 4.11.0 → 5.1.0
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/CHANGELOG.md +45 -0
- package/README.md +1 -0
- package/package.json +2 -2
- package/src/cli/__tests__/getConfiguration.test.js +1 -22
- package/src/cli/__tests__/isQuestionAsked.test.js +32 -10
- package/src/cli/__tests__/postProcessAnswers.js +111 -0
- package/src/cli/getAnswersDefaultValues.js +3 -5
- package/src/cli/getConfiguration.js +1 -38
- package/src/cli/index.js +146 -122
- package/src/cli/isQuestionAsked.js +9 -16
- package/src/cli/postProcessAnswers.js +77 -0
- package/src/templates/InstantSearch.js/index.html.hbs +4 -2
- package/src/templates/React InstantSearch/.template.js +1 -1
- package/src/templates/React InstantSearch/src/App.js.hbs +4 -5
- package/src/templates/Vue InstantSearch/.template.js +1 -1
- package/src/templates/Vue InstantSearch/src/App.vue +2 -4
- package/src/utils/__tests__/__snapshots__/index.test.js.snap +0 -4
- package/src/utils/__tests__/index.test.js +31 -3
- package/src/utils/index.js +14 -1
- package/src/cli/__tests__/getOptionsFromArguments.test.js +0 -72
- package/src/cli/getOptionsFromArguments.js +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
# [5.1.0](https://github.com/algolia/create-instantsearch-app/compare/5.0.1...5.1.0) (2021-12-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **templates:** use non-experimental dynamic widgets ([#544](https://github.com/algolia/create-instantsearch-app/issues/544)) ([efda539](https://github.com/algolia/create-instantsearch-app/commit/efda5395cef7865c7b4cebe741e7ab5e8ea4b2f8))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [5.0.1](https://github.com/algolia/create-instantsearch-app/compare/5.0.0...5.0.1) (2021-12-02)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **dynamicWidgets:** allow only dynamicWidgets as attribute ([3717de6](https://github.com/algolia/create-instantsearch-app/commit/3717de6fc5811c191629d366988dcc2e05773a9c))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# [5.0.0](https://github.com/algolia/create-instantsearch-app/compare/4.11.1...5.0.0) (2021-12-02)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **dynamicWidgets:** prevent "ais.dynamicWidgets" attribute showing up ([#542](https://github.com/algolia/create-instantsearch-app/issues/542)) ([559f705](https://github.com/algolia/create-instantsearch-app/commit/559f705ebcef48a3794efd7a588abd187d6642c9))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Code Refactoring
|
|
28
|
+
|
|
29
|
+
* **index:** rewrite answer parsing ([#541](https://github.com/algolia/create-instantsearch-app/issues/541)) ([efd2799](https://github.com/algolia/create-instantsearch-app/commit/efd279943e23545c3c0806f8e9632ae32c83c0d6))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### BREAKING CHANGES
|
|
33
|
+
|
|
34
|
+
the program now asks questions if some of the parameters are sent via arguments. Before this, giving an argument would cause it not to ask any questions anymore, even if they still would be useful. You can avoid this behaviour by passing --config or --no-interactive
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## [4.11.1](https://github.com/algolia/create-instantsearch-app/compare/4.11.0...4.11.1) (2021-11-18)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
* **react:** add missing configure widget import ([#540](https://github.com/algolia/create-instantsearch-app/issues/540)) ([626a568](https://github.com/algolia/create-instantsearch-app/commit/626a56859c48e756429da66195d7da1a6c79e39e))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
1
46
|
# [4.11.0](https://github.com/algolia/create-instantsearch-app/compare/4.10.3...4.11.0) (2021-11-16)
|
|
2
47
|
|
|
3
48
|
|
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ $ create-instantsearch-app --help
|
|
|
71
71
|
--library-version <libraryVersion> The version of the library
|
|
72
72
|
--config <config> The configuration file to get the options from
|
|
73
73
|
--no-installation Ignore dependency installation
|
|
74
|
+
--no-interactive Do not ask any interactive questions
|
|
74
75
|
-h, --help output usage information
|
|
75
76
|
```
|
|
76
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-instantsearch-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "⚡️ Build InstantSearch apps at the speed of thought",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@algolia/cache-in-memory": "4.11.0",
|
|
40
40
|
"algoliasearch": "4.11.0",
|
|
41
41
|
"chalk": "3.0.0",
|
|
42
|
-
"commander": "4.
|
|
42
|
+
"commander": "4.1.1",
|
|
43
43
|
"inquirer": "8.0.0",
|
|
44
44
|
"jstransformer-handlebars": "1.1.0",
|
|
45
45
|
"latest-semver": "2.0.0",
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const loadJsonFile = require('load-json-file');
|
|
2
|
-
const
|
|
3
|
-
const { getConfiguration, getLibraryVersion } = require('../getConfiguration');
|
|
2
|
+
const getConfiguration = require('../getConfiguration');
|
|
4
3
|
|
|
5
4
|
jest.mock('load-json-file');
|
|
6
5
|
|
|
7
|
-
jest.mock('../../utils', () => ({
|
|
8
|
-
...require.requireActual('../../utils'),
|
|
9
|
-
fetchLibraryVersions: jest.fn(() => Promise.resolve(['1.0.0'])),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
6
|
test('without template throws', async () => {
|
|
13
7
|
expect.assertions(1);
|
|
14
8
|
|
|
@@ -36,21 +30,6 @@ test('with options from arguments and prompt merge', async () => {
|
|
|
36
30
|
);
|
|
37
31
|
});
|
|
38
32
|
|
|
39
|
-
test('without stable version available', async () => {
|
|
40
|
-
utils.fetchLibraryVersions.mockImplementationOnce(() =>
|
|
41
|
-
Promise.resolve(['1.0.0-beta.0'])
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const libraryVersion = await getLibraryVersion(
|
|
45
|
-
{
|
|
46
|
-
template: 'InstantSearch.js',
|
|
47
|
-
},
|
|
48
|
-
utils.getAppTemplateConfig(utils.getTemplatePath('InstantSearch.js'))
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
expect(libraryVersion).toBe('1.0.0-beta.0');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
33
|
test('with config file overrides all options', async () => {
|
|
55
34
|
loadJsonFile.mockImplementationOnce(x => Promise.resolve(x));
|
|
56
35
|
const ignoredOptions = {
|
|
@@ -4,30 +4,30 @@ test('with appId undefined should ask', () => {
|
|
|
4
4
|
expect(
|
|
5
5
|
isQuestionAsked({
|
|
6
6
|
question: { name: 'appId', validate: input => Boolean(input) },
|
|
7
|
-
args: { appId: undefined },
|
|
7
|
+
args: { appId: undefined, interactive: true },
|
|
8
8
|
})
|
|
9
|
-
).toBe(
|
|
9
|
+
).toBe(false);
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
test('with appId defined should not ask', () => {
|
|
13
13
|
expect(
|
|
14
14
|
isQuestionAsked({
|
|
15
15
|
question: { name: 'appId', validate: input => Boolean(input) },
|
|
16
|
-
args: { appId: 'APP_ID' },
|
|
16
|
+
args: { appId: 'APP_ID', interactive: true },
|
|
17
17
|
})
|
|
18
|
-
).toBe(
|
|
18
|
+
).toBe(true);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test('with
|
|
21
|
+
test('with invalid template should ask', () => {
|
|
22
22
|
expect(
|
|
23
23
|
isQuestionAsked({
|
|
24
24
|
question: {
|
|
25
25
|
name: 'template',
|
|
26
26
|
validate: () => false,
|
|
27
27
|
},
|
|
28
|
-
args: { template: 'Unvalid' },
|
|
28
|
+
args: { template: 'Unvalid', interactive: true },
|
|
29
29
|
})
|
|
30
|
-
).toBe(
|
|
30
|
+
).toBe(false);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test('with valid template should not ask', () => {
|
|
@@ -37,9 +37,9 @@ test('with valid template should not ask', () => {
|
|
|
37
37
|
name: 'template',
|
|
38
38
|
validate: () => true,
|
|
39
39
|
},
|
|
40
|
-
args: { template: 'InstantSearch.js' },
|
|
40
|
+
args: { template: 'InstantSearch.js', interactive: true },
|
|
41
41
|
})
|
|
42
|
-
).toBe(
|
|
42
|
+
).toBe(true);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
test('with indexName should ask attributesToDisplay', () => {
|
|
@@ -48,7 +48,29 @@ test('with indexName should ask attributesToDisplay', () => {
|
|
|
48
48
|
question: {
|
|
49
49
|
name: 'attributesToDisplay',
|
|
50
50
|
},
|
|
51
|
-
args: { indexName: 'INDEX_NAME' },
|
|
51
|
+
args: { indexName: 'INDEX_NAME', interactive: true },
|
|
52
52
|
})
|
|
53
53
|
).toBe(false);
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
test('with config it does not ask', () => {
|
|
57
|
+
expect(
|
|
58
|
+
isQuestionAsked({
|
|
59
|
+
question: {
|
|
60
|
+
name: 'attributesToDisplay',
|
|
61
|
+
},
|
|
62
|
+
args: { config: '' },
|
|
63
|
+
})
|
|
64
|
+
).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('with --no-interactive it does not ask', () => {
|
|
68
|
+
expect(
|
|
69
|
+
isQuestionAsked({
|
|
70
|
+
question: {
|
|
71
|
+
name: 'attributesToDisplay',
|
|
72
|
+
},
|
|
73
|
+
args: { interactive: false },
|
|
74
|
+
})
|
|
75
|
+
).toBe(true);
|
|
76
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const postProcessAnswers = require('../postProcessAnswers');
|
|
2
|
+
const utils = require('../../utils');
|
|
3
|
+
|
|
4
|
+
jest.mock('../../utils', () => ({
|
|
5
|
+
...require.requireActual('../../utils'),
|
|
6
|
+
fetchLibraryVersions: jest.fn(() => Promise.resolve(['1.0.0'])),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
test('merges configuration and answers', async () => {
|
|
10
|
+
expect(
|
|
11
|
+
await postProcessAnswers({
|
|
12
|
+
configuration: { attributesForFaceting: ['test'], b: 1 },
|
|
13
|
+
answers: { attributesForFaceting: [] },
|
|
14
|
+
templateConfig: {},
|
|
15
|
+
optionsFromArguments: {},
|
|
16
|
+
})
|
|
17
|
+
).toEqual(expect.objectContaining({ attributesForFaceting: [], b: 1 }));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('libraryVersion', () => {
|
|
21
|
+
test('library version from answers is used', async () => {
|
|
22
|
+
const { libraryVersion } = await postProcessAnswers({
|
|
23
|
+
configuration: {},
|
|
24
|
+
answers: {
|
|
25
|
+
template: 'InstantSearch.js',
|
|
26
|
+
attributesForFaceting: [],
|
|
27
|
+
libraryVersion: '0.1.2',
|
|
28
|
+
},
|
|
29
|
+
optionsFromArguments: {},
|
|
30
|
+
templateConfig: utils.getAppTemplateConfig(
|
|
31
|
+
utils.getTemplatePath('InstantSearch.js')
|
|
32
|
+
),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(libraryVersion).toBe('0.1.2');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('library version falls back to beta if it is the only available', async () => {
|
|
39
|
+
utils.fetchLibraryVersions.mockImplementationOnce(() =>
|
|
40
|
+
Promise.resolve(['1.0.0-beta.0'])
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const { libraryVersion } = await postProcessAnswers({
|
|
44
|
+
configuration: {},
|
|
45
|
+
answers: {
|
|
46
|
+
template: 'InstantSearch.js',
|
|
47
|
+
attributesForFaceting: [],
|
|
48
|
+
},
|
|
49
|
+
optionsFromArguments: {},
|
|
50
|
+
templateConfig: utils.getAppTemplateConfig(
|
|
51
|
+
utils.getTemplatePath('InstantSearch.js')
|
|
52
|
+
),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(libraryVersion).toBe('1.0.0-beta.0');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('creates alternative names', async () => {
|
|
60
|
+
expect(
|
|
61
|
+
await postProcessAnswers({
|
|
62
|
+
configuration: {},
|
|
63
|
+
answers: {
|
|
64
|
+
attributesForFaceting: [],
|
|
65
|
+
organization: 'algolia',
|
|
66
|
+
name: 'date-range',
|
|
67
|
+
},
|
|
68
|
+
templateConfig: {
|
|
69
|
+
packageNamePrefix: 'instantsearch-widget-',
|
|
70
|
+
},
|
|
71
|
+
optionsFromArguments: {},
|
|
72
|
+
})
|
|
73
|
+
).toEqual(
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
packageName: '@algolia/instantsearch-widget-date-range',
|
|
76
|
+
widgetType: 'algolia.date-range',
|
|
77
|
+
camelCaseName: 'dateRange',
|
|
78
|
+
pascalCaseName: 'DateRange',
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('detects dynamic widgets', async () => {
|
|
84
|
+
expect(
|
|
85
|
+
await postProcessAnswers({
|
|
86
|
+
configuration: {},
|
|
87
|
+
templateConfig: {},
|
|
88
|
+
optionsFromArguments: {},
|
|
89
|
+
answers: { attributesForFaceting: ['ais.dynamicWidgets', 'test'] },
|
|
90
|
+
})
|
|
91
|
+
).toEqual(
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
attributesForFaceting: ['test'],
|
|
94
|
+
flags: { dynamicWidgets: true },
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
await postProcessAnswers({
|
|
100
|
+
configuration: {},
|
|
101
|
+
templateConfig: {},
|
|
102
|
+
optionsFromArguments: {},
|
|
103
|
+
answers: { attributesForFaceting: ['test'] },
|
|
104
|
+
})
|
|
105
|
+
).toEqual(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
attributesForFaceting: ['test'],
|
|
108
|
+
flags: { dynamicWidgets: false },
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
});
|
|
@@ -4,12 +4,10 @@ module.exports = function getAnswersDefaultValues(
|
|
|
4
4
|
template
|
|
5
5
|
) {
|
|
6
6
|
return {
|
|
7
|
+
...configuration,
|
|
7
8
|
...optionsFromArguments,
|
|
8
9
|
template,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
configuration.attributesToDisplay.length > 0
|
|
12
|
-
? configuration.attributesToDisplay
|
|
13
|
-
: undefined,
|
|
10
|
+
// name has a default of '', as it's a special case in Commander
|
|
11
|
+
name: optionsFromArguments.name || configuration.name,
|
|
14
12
|
};
|
|
15
13
|
};
|
|
@@ -1,37 +1,4 @@
|
|
|
1
|
-
const latestSemver = require('latest-semver');
|
|
2
1
|
const loadJsonFile = require('load-json-file');
|
|
3
|
-
const camelCase = require('lodash.camelcase');
|
|
4
|
-
|
|
5
|
-
const { fetchLibraryVersions } = require('../utils');
|
|
6
|
-
|
|
7
|
-
function capitalize(str) {
|
|
8
|
-
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function createNameAlternatives({ organization, name, templateConfig }) {
|
|
12
|
-
return {
|
|
13
|
-
packageName: `@${organization}/${templateConfig.packageNamePrefix ||
|
|
14
|
-
''}${name}`,
|
|
15
|
-
widgetType: `${organization}.${name}`,
|
|
16
|
-
camelCaseName: camelCase(name),
|
|
17
|
-
pascalCaseName: capitalize(camelCase(name)),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function getLibraryVersion(config, templateConfig) {
|
|
22
|
-
const { libraryName } = templateConfig;
|
|
23
|
-
const { libraryVersion } = config;
|
|
24
|
-
|
|
25
|
-
if (libraryName && !libraryVersion) {
|
|
26
|
-
const versions = await fetchLibraryVersions(libraryName);
|
|
27
|
-
|
|
28
|
-
// Return the latest available version when
|
|
29
|
-
// the stable version is not available
|
|
30
|
-
return latestSemver(versions) || versions[0];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return libraryVersion;
|
|
34
|
-
}
|
|
35
2
|
|
|
36
3
|
async function getConfiguration({ options = {}, answers = {} } = {}) {
|
|
37
4
|
const config = options.config
|
|
@@ -45,8 +12,4 @@ async function getConfiguration({ options = {}, answers = {} } = {}) {
|
|
|
45
12
|
return config;
|
|
46
13
|
}
|
|
47
14
|
|
|
48
|
-
module.exports =
|
|
49
|
-
getConfiguration,
|
|
50
|
-
getLibraryVersion,
|
|
51
|
-
createNameAlternatives,
|
|
52
|
-
};
|
|
15
|
+
module.exports = getConfiguration;
|
package/src/cli/index.js
CHANGED
|
@@ -16,24 +16,22 @@ const {
|
|
|
16
16
|
fetchLibraryVersions,
|
|
17
17
|
getTemplatesByCategory,
|
|
18
18
|
getTemplatePath,
|
|
19
|
+
splitArray,
|
|
19
20
|
} = require('../utils');
|
|
20
|
-
const getOptionsFromArguments = require('./getOptionsFromArguments');
|
|
21
21
|
const getAttributesFromIndex = require('./getAttributesFromIndex');
|
|
22
22
|
const getFacetsFromIndex = require('./getFacetsFromIndex');
|
|
23
23
|
const getAnswersDefaultValues = require('./getAnswersDefaultValues');
|
|
24
24
|
const isQuestionAsked = require('./isQuestionAsked');
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
getLibraryVersion,
|
|
28
|
-
createNameAlternatives,
|
|
29
|
-
} = require('./getConfiguration');
|
|
25
|
+
const getConfiguration = require('./getConfiguration');
|
|
26
|
+
const postProcessAnswers = require('./postProcessAnswers');
|
|
30
27
|
const { version } = require('../../package.json');
|
|
31
28
|
|
|
32
29
|
let appPathFromArgument;
|
|
33
|
-
let options = {};
|
|
34
30
|
|
|
35
31
|
program
|
|
32
|
+
.storeOptionsAsProperties(false)
|
|
36
33
|
.version(version, '-v, --version')
|
|
34
|
+
.name('create-instantsearch-app')
|
|
37
35
|
.arguments('<project-directory>')
|
|
38
36
|
.usage(`${chalk.green('<project-directory>')} [options]`)
|
|
39
37
|
.option('--name <name>', 'The name of the application or widget')
|
|
@@ -42,31 +40,29 @@ program
|
|
|
42
40
|
.option('--index-name <indexName>', 'The main index of your search')
|
|
43
41
|
.option(
|
|
44
42
|
'--attributes-to-display <attributesToDisplay>',
|
|
45
|
-
'The attributes of your index to display'
|
|
43
|
+
'The attributes of your index to display in hits',
|
|
44
|
+
splitArray
|
|
46
45
|
)
|
|
47
46
|
.option(
|
|
48
47
|
'--attributes-for-faceting <attributesForFaceting>',
|
|
49
|
-
'The attributes
|
|
48
|
+
'The attributes to display as filters',
|
|
49
|
+
splitArray
|
|
50
50
|
)
|
|
51
51
|
.option('--template <template>', 'The InstantSearch template to use')
|
|
52
52
|
.option('--library-version <version>', 'The version of the library')
|
|
53
53
|
.option('--config <config>', 'The configuration file to get the options from')
|
|
54
54
|
.option('--no-installation', 'Ignore dependency installation')
|
|
55
|
-
.
|
|
55
|
+
.option('--no-interactive', 'Ask no interactive questions')
|
|
56
|
+
.action(dest => {
|
|
56
57
|
appPathFromArgument = dest;
|
|
57
|
-
options = opts;
|
|
58
58
|
})
|
|
59
59
|
.parse(process.argv);
|
|
60
60
|
|
|
61
|
-
const optionsFromArguments =
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const attributesForFaceting = (optionsFromArguments.attributesForFaceting || '')
|
|
67
|
-
.split(',')
|
|
68
|
-
.filter(Boolean)
|
|
69
|
-
.map(x => x.trim());
|
|
61
|
+
const optionsFromArguments = program.opts();
|
|
62
|
+
const {
|
|
63
|
+
attributesToDisplay = [],
|
|
64
|
+
attributesForFaceting = [],
|
|
65
|
+
} = optionsFromArguments;
|
|
70
66
|
|
|
71
67
|
const getQuestions = ({ appName }) => ({
|
|
72
68
|
application: [
|
|
@@ -160,7 +156,7 @@ const getQuestions = ({ appName }) => ({
|
|
|
160
156
|
],
|
|
161
157
|
filter: attributes => attributes.filter(Boolean),
|
|
162
158
|
when: ({ appId, apiKey, indexName }) =>
|
|
163
|
-
|
|
159
|
+
attributesToDisplay.length === 0 && appId && apiKey && indexName,
|
|
164
160
|
},
|
|
165
161
|
{
|
|
166
162
|
type: 'checkbox',
|
|
@@ -202,7 +198,7 @@ const getQuestions = ({ appName }) => ({
|
|
|
202
198
|
},
|
|
203
199
|
filter: attributes => attributes.filter(Boolean),
|
|
204
200
|
when: ({ appId, apiKey, indexName }) =>
|
|
205
|
-
|
|
201
|
+
attributesForFaceting.length === 0 && appId && apiKey && indexName,
|
|
206
202
|
},
|
|
207
203
|
],
|
|
208
204
|
widget: [
|
|
@@ -228,95 +224,141 @@ const getQuestions = ({ appName }) => ({
|
|
|
228
224
|
},
|
|
229
225
|
},
|
|
230
226
|
],
|
|
231
|
-
|
|
227
|
+
initial: [
|
|
228
|
+
{
|
|
229
|
+
type: 'input',
|
|
230
|
+
name: 'appPath',
|
|
231
|
+
message: 'Project directory',
|
|
232
|
+
askAnswered: true,
|
|
233
|
+
validate(input) {
|
|
234
|
+
try {
|
|
235
|
+
return checkAppPath(input);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.log();
|
|
238
|
+
console.error(err.message);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
when(answers) {
|
|
243
|
+
try {
|
|
244
|
+
return (
|
|
245
|
+
answers.interactive &&
|
|
246
|
+
!answers.config &&
|
|
247
|
+
!checkAppPath(answers.appPath)
|
|
248
|
+
);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
type: 'input',
|
|
256
|
+
name: 'appName',
|
|
257
|
+
message: 'The name of the application or widget',
|
|
258
|
+
askAnswered: true,
|
|
259
|
+
default(answers) {
|
|
260
|
+
return path.basename(answers.appPath);
|
|
261
|
+
},
|
|
262
|
+
validate(input) {
|
|
263
|
+
try {
|
|
264
|
+
return checkAppName(input);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.log();
|
|
267
|
+
console.error(err.message);
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
when(answers) {
|
|
272
|
+
try {
|
|
273
|
+
return (
|
|
274
|
+
answers.interactive &&
|
|
275
|
+
!answers.config &&
|
|
276
|
+
!checkAppName(answers.appName)
|
|
277
|
+
);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'list',
|
|
285
|
+
pageSize: 10,
|
|
286
|
+
name: 'template',
|
|
287
|
+
message: 'InstantSearch template',
|
|
288
|
+
askAnswered: true,
|
|
289
|
+
choices: () => {
|
|
290
|
+
const templatesByCategory = getTemplatesByCategory();
|
|
291
|
+
|
|
292
|
+
return Object.entries(templatesByCategory).reduce(
|
|
293
|
+
(templates, [category, values]) => [
|
|
294
|
+
...templates,
|
|
295
|
+
new inquirer.Separator(category),
|
|
296
|
+
...values,
|
|
297
|
+
],
|
|
298
|
+
[]
|
|
299
|
+
);
|
|
300
|
+
},
|
|
301
|
+
validate(input) {
|
|
302
|
+
// if a config is given, path is optional
|
|
303
|
+
if (optionsFromArguments.config) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
232
306
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
type: 'input',
|
|
239
|
-
name: 'appPath',
|
|
240
|
-
message: 'Project directory',
|
|
307
|
+
const isValid = Boolean(input);
|
|
308
|
+
if (!isValid) {
|
|
309
|
+
console.log('template is required');
|
|
310
|
+
}
|
|
311
|
+
return isValid;
|
|
241
312
|
},
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
checkAppPath(appPath);
|
|
250
|
-
} catch (err) {
|
|
251
|
-
console.error(err.message);
|
|
252
|
-
console.log();
|
|
313
|
+
when(answers) {
|
|
314
|
+
return answers.interactive && !answers.config && !answers.template;
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
});
|
|
253
319
|
|
|
254
|
-
|
|
255
|
-
|
|
320
|
+
async function run() {
|
|
321
|
+
const args = {
|
|
322
|
+
...optionsFromArguments,
|
|
323
|
+
appPath: appPathFromArgument,
|
|
324
|
+
appName: optionsFromArguments.name,
|
|
325
|
+
};
|
|
326
|
+
const initialQuestions = getQuestions({}).initial;
|
|
327
|
+
const initialAnswers = {
|
|
328
|
+
...args,
|
|
329
|
+
...(await inquirer.prompt(initialQuestions, args)),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
initialQuestions.forEach(question => {
|
|
333
|
+
// .default doesn't get executed when "when" returns false
|
|
334
|
+
if (!initialAnswers[question.name] && question.default) {
|
|
335
|
+
const defaultValue = question.default(initialAnswers);
|
|
336
|
+
initialAnswers[question.name] = defaultValue;
|
|
337
|
+
}
|
|
338
|
+
if (!question.validate(initialAnswers[question.name])) {
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
256
342
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
type: 'input',
|
|
263
|
-
name: 'appName',
|
|
264
|
-
message: 'The name of the application or widget',
|
|
265
|
-
default: path.basename(appPath),
|
|
266
|
-
},
|
|
267
|
-
])
|
|
268
|
-
).appName;
|
|
343
|
+
if (initialAnswers.appPath.startsWith('~/')) {
|
|
344
|
+
initialAnswers.appPath = path.join(
|
|
345
|
+
os.homedir(),
|
|
346
|
+
initialAnswers.appPath.slice(2)
|
|
347
|
+
);
|
|
269
348
|
}
|
|
270
349
|
|
|
271
|
-
|
|
272
|
-
checkAppName(appName);
|
|
273
|
-
} catch (err) {
|
|
274
|
-
console.error(err.message);
|
|
275
|
-
console.log();
|
|
276
|
-
|
|
277
|
-
process.exit(1);
|
|
278
|
-
}
|
|
350
|
+
const { appPath, appName, template } = initialAnswers;
|
|
279
351
|
|
|
280
352
|
console.log();
|
|
281
353
|
console.log(`Creating a new InstantSearch app in ${chalk.green(appPath)}.`);
|
|
282
354
|
console.log();
|
|
283
355
|
|
|
284
|
-
const { template = optionsFromArguments.template } = await inquirer.prompt(
|
|
285
|
-
[
|
|
286
|
-
{
|
|
287
|
-
type: 'list',
|
|
288
|
-
pageSize: 10,
|
|
289
|
-
name: 'template',
|
|
290
|
-
message: 'InstantSearch template',
|
|
291
|
-
choices: () => {
|
|
292
|
-
const templatesByCategory = getTemplatesByCategory();
|
|
293
|
-
|
|
294
|
-
return Object.entries(templatesByCategory).reduce(
|
|
295
|
-
(templates, [category, values]) => [
|
|
296
|
-
...templates,
|
|
297
|
-
new inquirer.Separator(category),
|
|
298
|
-
...values,
|
|
299
|
-
],
|
|
300
|
-
[]
|
|
301
|
-
);
|
|
302
|
-
},
|
|
303
|
-
validate(input) {
|
|
304
|
-
return Boolean(input);
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
].filter(question =>
|
|
308
|
-
isQuestionAsked({ question, args: optionsFromArguments })
|
|
309
|
-
),
|
|
310
|
-
optionsFromArguments
|
|
311
|
-
);
|
|
312
|
-
|
|
313
356
|
const configuration = await getConfiguration({
|
|
314
357
|
options: {
|
|
315
358
|
...optionsFromArguments,
|
|
316
359
|
name: appName,
|
|
317
|
-
attributesToDisplay,
|
|
318
360
|
},
|
|
319
|
-
answers:
|
|
361
|
+
answers: initialAnswers,
|
|
320
362
|
});
|
|
321
363
|
|
|
322
364
|
const templatePath = getTemplatePath(configuration.template);
|
|
@@ -326,39 +368,21 @@ async function run() {
|
|
|
326
368
|
templateConfig.category === 'Widget' ? 'widget' : 'application';
|
|
327
369
|
|
|
328
370
|
const answers = await inquirer.prompt(
|
|
329
|
-
getQuestions({ appName })[implementationType].filter(
|
|
330
|
-
isQuestionAsked({ question, args: optionsFromArguments })
|
|
371
|
+
getQuestions({ appName })[implementationType].filter(
|
|
372
|
+
question => !isQuestionAsked({ question, args: optionsFromArguments })
|
|
331
373
|
),
|
|
332
374
|
getAnswersDefaultValues(optionsFromArguments, configuration, template)
|
|
333
375
|
);
|
|
334
376
|
|
|
335
|
-
const alternativeNames = createNameAlternatives({
|
|
336
|
-
...configuration,
|
|
337
|
-
...answers,
|
|
338
|
-
templateConfig,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const libraryVersion = await getLibraryVersion(
|
|
342
|
-
{ ...configuration, ...answers },
|
|
343
|
-
templateConfig
|
|
344
|
-
);
|
|
345
|
-
|
|
346
377
|
const app = createInstantSearchApp(
|
|
347
378
|
appPath,
|
|
348
|
-
{
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
answers.attributesForFaceting.includes('ais.dynamicWidgets'),
|
|
356
|
-
},
|
|
357
|
-
libraryVersion,
|
|
358
|
-
template: templatePath,
|
|
359
|
-
installation: program.installation,
|
|
360
|
-
currentYear: new Date().getFullYear(),
|
|
361
|
-
},
|
|
379
|
+
await postProcessAnswers({
|
|
380
|
+
configuration,
|
|
381
|
+
answers,
|
|
382
|
+
optionsFromArguments,
|
|
383
|
+
templatePath,
|
|
384
|
+
templateConfig,
|
|
385
|
+
}),
|
|
362
386
|
templateConfig.tasks
|
|
363
387
|
);
|
|
364
388
|
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
module.exports = function isQuestionAsked({ question, args }) {
|
|
2
|
-
if
|
|
3
|
-
|
|
2
|
+
// if there's a config, ask no questions, even if it would be invalid
|
|
3
|
+
if (args.config || !args.interactive) {
|
|
4
|
+
return true;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
(question.validate && question.validate(args[optionName]))
|
|
12
|
-
) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
} else if (!question.validate) {
|
|
16
|
-
// Skip if the question is optional and not given in the command
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
7
|
+
const argument = args[question.name];
|
|
8
|
+
|
|
9
|
+
// Skip if the arg in the command is valid
|
|
10
|
+
if (question.validate) {
|
|
11
|
+
return question.validate(argument);
|
|
19
12
|
}
|
|
20
13
|
|
|
21
|
-
return
|
|
14
|
+
return argument !== undefined;
|
|
22
15
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const camelCase = require('lodash.camelcase');
|
|
2
|
+
const latestSemver = require('latest-semver');
|
|
3
|
+
|
|
4
|
+
const { fetchLibraryVersions } = require('../utils');
|
|
5
|
+
|
|
6
|
+
function capitalize(str) {
|
|
7
|
+
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createNameAlternatives({ organization, name, templateConfig }) {
|
|
11
|
+
return {
|
|
12
|
+
packageName: `@${organization}/${templateConfig.packageNamePrefix ||
|
|
13
|
+
''}${name}`,
|
|
14
|
+
widgetType: `${organization}.${name}`,
|
|
15
|
+
camelCaseName: camelCase(name),
|
|
16
|
+
pascalCaseName: capitalize(camelCase(name)),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getLibraryVersion(config, templateConfig) {
|
|
21
|
+
const { libraryName } = templateConfig;
|
|
22
|
+
const { libraryVersion } = config;
|
|
23
|
+
|
|
24
|
+
if (libraryName && !libraryVersion) {
|
|
25
|
+
const versions = await fetchLibraryVersions(libraryName);
|
|
26
|
+
|
|
27
|
+
// Return the latest available version when
|
|
28
|
+
// the stable version is not available
|
|
29
|
+
return latestSemver(versions) || versions[0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return libraryVersion;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function postProcessAnswers({
|
|
36
|
+
configuration,
|
|
37
|
+
answers,
|
|
38
|
+
optionsFromArguments,
|
|
39
|
+
templatePath,
|
|
40
|
+
templateConfig,
|
|
41
|
+
}) {
|
|
42
|
+
const combinedAnswers = {
|
|
43
|
+
...configuration,
|
|
44
|
+
...answers,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const alternativeNames = createNameAlternatives({
|
|
48
|
+
...combinedAnswers,
|
|
49
|
+
templateConfig,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const libraryVersion = await getLibraryVersion(
|
|
53
|
+
combinedAnswers,
|
|
54
|
+
templateConfig
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...combinedAnswers,
|
|
59
|
+
...alternativeNames,
|
|
60
|
+
libraryVersion,
|
|
61
|
+
template: templatePath,
|
|
62
|
+
installation: optionsFromArguments.installation,
|
|
63
|
+
currentYear: new Date().getFullYear(),
|
|
64
|
+
attributesForFaceting:
|
|
65
|
+
Array.isArray(combinedAnswers.attributesForFaceting) &&
|
|
66
|
+
combinedAnswers.attributesForFaceting.filter(
|
|
67
|
+
attribute => attribute !== 'ais.dynamicWidgets'
|
|
68
|
+
),
|
|
69
|
+
flags: {
|
|
70
|
+
dynamicWidgets:
|
|
71
|
+
Array.isArray(combinedAnswers.attributesForFaceting) &&
|
|
72
|
+
combinedAnswers.attributesForFaceting.includes('ais.dynamicWidgets'),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = postProcessAnswers;
|
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
|
|
32
32
|
<div class="container">
|
|
33
33
|
<div class="search-panel">
|
|
34
|
-
{{#if attributesForFaceting}}
|
|
35
34
|
<div class="search-panel__filters">
|
|
35
|
+
{{#if flags.dynamicWidgets}}
|
|
36
|
+
<div id="dynamic-widgets"></div>
|
|
37
|
+
{{else}}
|
|
36
38
|
{{#each attributesForFaceting}}
|
|
37
39
|
<div id="{{this}}-list"></div>
|
|
38
40
|
{{/each}}
|
|
41
|
+
{{/if}}
|
|
39
42
|
</div>
|
|
40
43
|
|
|
41
|
-
{{/if}}
|
|
42
44
|
<div class="search-panel__results">
|
|
43
45
|
<div id="searchbox"></div>
|
|
44
46
|
<div id="hits"></div>
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
Hits,
|
|
6
6
|
SearchBox,
|
|
7
7
|
{{#if flags.dynamicWidgets}}
|
|
8
|
-
|
|
8
|
+
Configure,
|
|
9
|
+
DynamicWidgets,
|
|
9
10
|
{{/if}}
|
|
10
11
|
{{#if attributesForFaceting}}
|
|
11
12
|
RefinementList,
|
|
@@ -38,15 +39,14 @@ function App() {
|
|
|
38
39
|
<div className="container">
|
|
39
40
|
<InstantSearch searchClient={searchClient} indexName="{{indexName}}">
|
|
40
41
|
<div className="search-panel">
|
|
41
|
-
{{#if attributesForFaceting}}
|
|
42
42
|
<div className="search-panel__filters">
|
|
43
43
|
{{#if flags.dynamicWidgets}}
|
|
44
44
|
<Configure facets={['*']} maxValuesPerFacet={20} />
|
|
45
|
-
<
|
|
45
|
+
<DynamicWidgets fallbackWidget={RefinementList}>
|
|
46
46
|
{{#each attributesForFaceting}}
|
|
47
47
|
<RefinementList attribute="{{this}}" />
|
|
48
48
|
{{/each}}
|
|
49
|
-
</
|
|
49
|
+
</DynamicWidgets>
|
|
50
50
|
{{else}}
|
|
51
51
|
{{#each attributesForFaceting}}
|
|
52
52
|
<RefinementList attribute="{{this}}" />
|
|
@@ -54,7 +54,6 @@ function App() {
|
|
|
54
54
|
{{/if}}
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
|
-
{{/if}}
|
|
58
57
|
<div className="search-panel__results">
|
|
59
58
|
<SearchBox
|
|
60
59
|
className="searchbox"
|
|
@@ -17,15 +17,14 @@
|
|
|
17
17
|
<div class="container">
|
|
18
18
|
<ais-instant-search :search-client="searchClient" index-name="{{indexName}}">
|
|
19
19
|
<div class="search-panel">
|
|
20
|
-
{{#if attributesForFaceting.length}}
|
|
21
20
|
<div class="search-panel__filters">
|
|
22
21
|
{{#if flags.dynamicWidgets}}
|
|
23
22
|
<ais-configure :facets="['*']" :max-values-per-facet.camel="20" />
|
|
24
|
-
<ais-
|
|
23
|
+
<ais-dynamic-widgets>
|
|
25
24
|
{{#each attributesForFaceting}}
|
|
26
25
|
<ais-refinement-list attribute="{{this}}" />
|
|
27
26
|
{{/each}}
|
|
28
|
-
</ais-
|
|
27
|
+
</ais-dynamic-widgets>
|
|
29
28
|
{{else}}
|
|
30
29
|
{{#each attributesForFaceting}}
|
|
31
30
|
<ais-refinement-list attribute="{{this}}" />
|
|
@@ -33,7 +32,6 @@
|
|
|
33
32
|
{{/if}}
|
|
34
33
|
</div>
|
|
35
34
|
|
|
36
|
-
{{/if}}
|
|
37
35
|
<div class="search-panel__results">
|
|
38
36
|
<div class="searchbox">
|
|
39
37
|
<ais-search-box placeholder="{{searchPlaceholder}}" />
|
|
@@ -5,7 +5,3 @@ exports[`checkAppName throws with correct error message 1`] = `
|
|
|
5
5
|
- name cannot start with a period
|
|
6
6
|
- name can only contain URL-friendly characters"
|
|
7
7
|
`;
|
|
8
|
-
|
|
9
|
-
exports[`checkAppPath with existing file as path should throw with correct error 1`] = `"Could not create project at path [31mpath[39m because a file of the same name already exists."`;
|
|
10
|
-
|
|
11
|
-
exports[`checkAppPath with non empty directory as path should throw with correct error 1`] = `"Could not create project in destination folder \\"[31mpath[39m\\" because it is not empty."`;
|
|
@@ -23,7 +23,7 @@ describe('checkAppName', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
describe('checkAppPath', () => {
|
|
26
|
-
describe('with non
|
|
26
|
+
describe('with non existing directory as path', () => {
|
|
27
27
|
beforeAll(() => {
|
|
28
28
|
mockExistsSync.mockImplementation(() => false);
|
|
29
29
|
});
|
|
@@ -45,7 +45,11 @@ describe('checkAppPath', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('should throw with correct error', () => {
|
|
48
|
-
expect(() =>
|
|
48
|
+
expect(() =>
|
|
49
|
+
utils.checkAppPath('path')
|
|
50
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
51
|
+
`"Could not create project in destination folder \\"[31mpath[39m\\" because it is not empty."`
|
|
52
|
+
);
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
afterAll(() => {
|
|
@@ -64,7 +68,31 @@ describe('checkAppPath', () => {
|
|
|
64
68
|
});
|
|
65
69
|
|
|
66
70
|
test('should throw with correct error', () => {
|
|
67
|
-
expect(() =>
|
|
71
|
+
expect(() =>
|
|
72
|
+
utils.checkAppPath('path')
|
|
73
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
74
|
+
`"Could not create project at path [31mpath[39m because a file of the same name already exists."`
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterAll(() => {
|
|
79
|
+
mockExistsSync.mockReset();
|
|
80
|
+
mockLstatSync.mockReset();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('with empty string as path', () => {
|
|
85
|
+
beforeAll(() => {
|
|
86
|
+
mockExistsSync.mockImplementation(() => false);
|
|
87
|
+
mockLstatSync.mockImplementation(() => ({
|
|
88
|
+
isDirectory: () => false,
|
|
89
|
+
}));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should throw with correct error', () => {
|
|
93
|
+
expect(() => utils.checkAppPath('')).toThrowErrorMatchingInlineSnapshot(
|
|
94
|
+
`"Could not create project without directory"`
|
|
95
|
+
);
|
|
68
96
|
});
|
|
69
97
|
|
|
70
98
|
afterAll(() => {
|
package/src/utils/index.js
CHANGED
|
@@ -56,6 +56,10 @@ function checkAppPath(appPath) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
if (!appPath) {
|
|
60
|
+
throw new Error('Could not create project without directory');
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -126,7 +130,9 @@ function getTemplatePath(templateName) {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
async function fetchLibraryVersions(libraryName) {
|
|
129
|
-
const library = await index.getObject(libraryName
|
|
133
|
+
const library = await index.getObject(libraryName, {
|
|
134
|
+
attributesToRetrieve: ['versions'],
|
|
135
|
+
});
|
|
130
136
|
|
|
131
137
|
return Object.keys(library.versions).reverse();
|
|
132
138
|
}
|
|
@@ -147,6 +153,12 @@ async function getEarliestLibraryVersion(...args) {
|
|
|
147
153
|
return await getLibraryVersion(...args)(semver.minSatisfying);
|
|
148
154
|
}
|
|
149
155
|
|
|
156
|
+
const splitArray = string =>
|
|
157
|
+
string
|
|
158
|
+
.split(',')
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
.map(x => x.trim());
|
|
161
|
+
|
|
150
162
|
module.exports = {
|
|
151
163
|
checkAppName,
|
|
152
164
|
checkAppPath,
|
|
@@ -158,4 +170,5 @@ module.exports = {
|
|
|
158
170
|
getAllTemplates,
|
|
159
171
|
getTemplatePath,
|
|
160
172
|
getTemplatesByCategory,
|
|
173
|
+
splitArray,
|
|
161
174
|
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
const getOptionsFromArguments = require('../getOptionsFromArguments');
|
|
2
|
-
|
|
3
|
-
test('with a single option', () => {
|
|
4
|
-
expect(getOptionsFromArguments('cmd --appId APP_ID'.split(' '))).toEqual({
|
|
5
|
-
appId: 'APP_ID',
|
|
6
|
-
});
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
test('with multiple options', () => {
|
|
10
|
-
expect(
|
|
11
|
-
getOptionsFromArguments([
|
|
12
|
-
'cmd',
|
|
13
|
-
'--appId',
|
|
14
|
-
'APP_ID',
|
|
15
|
-
'--apiKey',
|
|
16
|
-
'API_KEY',
|
|
17
|
-
'--indexName',
|
|
18
|
-
'INDEX_NAME',
|
|
19
|
-
'--template',
|
|
20
|
-
'Vue InstantSearch',
|
|
21
|
-
])
|
|
22
|
-
).toEqual({
|
|
23
|
-
appId: 'APP_ID',
|
|
24
|
-
apiKey: 'API_KEY',
|
|
25
|
-
indexName: 'INDEX_NAME',
|
|
26
|
-
template: 'Vue InstantSearch',
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('with different commands', () => {
|
|
31
|
-
expect(
|
|
32
|
-
getOptionsFromArguments(['yarn', 'start', '--appId', 'APP_ID'])
|
|
33
|
-
).toEqual({
|
|
34
|
-
appId: 'APP_ID',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
expect(
|
|
38
|
-
getOptionsFromArguments(['node', 'index', '--appId', 'APP_ID'])
|
|
39
|
-
).toEqual({
|
|
40
|
-
appId: 'APP_ID',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
getOptionsFromArguments([
|
|
45
|
-
'npm',
|
|
46
|
-
'init',
|
|
47
|
-
'instantsearch-app',
|
|
48
|
-
'--appId',
|
|
49
|
-
'APP_ID',
|
|
50
|
-
])
|
|
51
|
-
).toEqual({
|
|
52
|
-
appId: 'APP_ID',
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(
|
|
56
|
-
getOptionsFromArguments([
|
|
57
|
-
'yarn',
|
|
58
|
-
'create',
|
|
59
|
-
'instantsearch-app',
|
|
60
|
-
'--appId',
|
|
61
|
-
'APP_ID',
|
|
62
|
-
])
|
|
63
|
-
).toEqual({
|
|
64
|
-
appId: 'APP_ID',
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
expect(
|
|
68
|
-
getOptionsFromArguments(['create-instantsearch-app', '--appId', 'APP_ID'])
|
|
69
|
-
).toEqual({
|
|
70
|
-
appId: 'APP_ID',
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const camelCase = require('lodash.camelcase');
|
|
2
|
-
|
|
3
|
-
module.exports = function getOptionsFromArguments(rawArgs) {
|
|
4
|
-
let argIndex = 0;
|
|
5
|
-
|
|
6
|
-
return rawArgs.reduce((allArgs, currentArg) => {
|
|
7
|
-
argIndex++;
|
|
8
|
-
|
|
9
|
-
if (!currentArg.startsWith('--') || currentArg.startsWith('--no-')) {
|
|
10
|
-
return allArgs;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const argumentName = camelCase(currentArg.split('--')[1]);
|
|
14
|
-
const argumentValue = rawArgs[argIndex];
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
...allArgs,
|
|
18
|
-
[argumentName]: argumentValue,
|
|
19
|
-
};
|
|
20
|
-
}, {});
|
|
21
|
-
};
|