create-instantsearch-app 4.10.2 → 5.0.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +1 -0
  3. package/package.json +4 -3
  4. package/src/api/__tests__/__snapshots__/index.test.js.snap +3 -3
  5. package/src/cli/__tests__/getAnswersDefaultValues.js +15 -0
  6. package/src/cli/__tests__/getAttributesFromIndex.test.js +18 -24
  7. package/src/cli/__tests__/getConfiguration.test.js +1 -22
  8. package/src/cli/__tests__/getFacetsFromIndex.test.js +39 -0
  9. package/src/cli/__tests__/getInformationFromIndex.js +68 -0
  10. package/src/cli/__tests__/isQuestionAsked.test.js +32 -10
  11. package/src/cli/__tests__/postProcessAnswers.js +111 -0
  12. package/src/cli/getAnswersDefaultValues.js +13 -0
  13. package/src/cli/getAttributesFromIndex.js +6 -4
  14. package/src/cli/getConfiguration.js +1 -38
  15. package/src/cli/getFacetsFromIndex.js +18 -0
  16. package/src/cli/getInformationFromIndex.js +40 -0
  17. package/src/cli/index.js +191 -113
  18. package/src/cli/isQuestionAsked.js +9 -16
  19. package/src/cli/postProcessAnswers.js +77 -0
  20. package/src/templates/Angular InstantSearch/angular.json +1 -0
  21. package/src/templates/Angular InstantSearch/package.json +25 -25
  22. package/src/templates/Angular InstantSearch/src/index.html +1 -0
  23. package/src/templates/Angular InstantSearch/src/styles.css +10 -2
  24. package/src/templates/Angular InstantSearch/tsconfig.json +1 -0
  25. package/src/templates/Autocomplete/.editorconfig +9 -0
  26. package/src/templates/Autocomplete/.eslintignore +3 -0
  27. package/src/templates/Autocomplete/.eslintrc.js +20 -0
  28. package/src/templates/Autocomplete/.gitignore.template +22 -0
  29. package/src/templates/Autocomplete/.prettierrc +5 -0
  30. package/src/templates/Autocomplete/.template.js +14 -0
  31. package/src/templates/Autocomplete/README.md +21 -0
  32. package/src/templates/Autocomplete/app.js.hbs +53 -0
  33. package/src/templates/Autocomplete/favicon.png +0 -0
  34. package/src/templates/Autocomplete/index.html.hbs +19 -0
  35. package/src/templates/Autocomplete/manifest.webmanifest +15 -0
  36. package/src/templates/Autocomplete/package.json +36 -0
  37. package/src/templates/Autocomplete/style.css +20 -0
  38. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/.editorconfig +0 -0
  39. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/.eslintignore +0 -0
  40. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/.eslintrc.js +0 -0
  41. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/.gitignore.template +0 -0
  42. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/.prettierrc +0 -0
  43. package/src/templates/{Autocomplete.js/.template.js → Autocomplete.js 0/.template.js } +1 -1
  44. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/README.md +0 -0
  45. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/favicon.png +0 -0
  46. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/index.html.hbs +0 -0
  47. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/manifest.webmanifest +0 -0
  48. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/package.json +0 -0
  49. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/src/app.css +0 -0
  50. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/src/app.js.hbs +0 -0
  51. package/src/templates/{Autocomplete.js → Autocomplete.js 0}/src/index.css +0 -0
  52. package/src/templates/InstantSearch.js/.template.js +3 -0
  53. package/src/templates/InstantSearch.js/index.html.hbs +4 -0
  54. package/src/templates/InstantSearch.js/src/app.js.hbs +25 -1
  55. package/src/templates/React InstantSearch/.template.js +4 -0
  56. package/src/templates/React InstantSearch/src/App.js.hbs +13 -0
  57. package/src/templates/Vue InstantSearch/.template.js +4 -1
  58. package/src/templates/Vue InstantSearch/src/App.vue +9 -0
  59. package/src/utils/__tests__/__snapshots__/index.test.js.snap +0 -4
  60. package/src/utils/__tests__/index.test.js +31 -3
  61. package/src/utils/index.js +14 -1
  62. package/src/cli/__tests__/getOptionsFromArguments.test.js +0 -72
  63. package/src/cli/getOptionsFromArguments.js +0 -21
@@ -0,0 +1,40 @@
1
+ const algoliasearch = require('algoliasearch');
2
+ const { createInMemoryCache } = require('@algolia/cache-in-memory');
3
+
4
+ const clients = new Map();
5
+ function getClient(appId, apiKey) {
6
+ const key = [appId, apiKey].join('__');
7
+ let client = clients.get(key);
8
+ if (!client) {
9
+ client = algoliasearch(appId, apiKey, {
10
+ responsesCache: createInMemoryCache(),
11
+ requestsCache: createInMemoryCache(),
12
+ });
13
+
14
+ clients.set(key, client);
15
+ }
16
+
17
+ return client;
18
+ }
19
+
20
+ async function getInformationFromIndex({ appId, apiKey, indexName }) {
21
+ try {
22
+ const client = getClient(appId, apiKey);
23
+ return await client
24
+ .search([
25
+ {
26
+ indexName,
27
+ params: {
28
+ hitsPerPage: 1,
29
+ facets: '*',
30
+ maxValuesPerFacet: 1,
31
+ },
32
+ },
33
+ ])
34
+ .then(({ results: [result] }) => result);
35
+ } catch (err) {
36
+ return {};
37
+ }
38
+ }
39
+
40
+ module.exports = getInformationFromIndex;
package/src/cli/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  const path = require('path');
3
3
  const process = require('process');
4
+ const os = require('os');
4
5
  const program = require('commander');
5
6
  const inquirer = require('inquirer');
6
7
  const chalk = require('chalk');
7
8
  const latestSemver = require('latest-semver');
8
- const os = require('os');
9
+ const semver = require('semver');
9
10
 
10
11
  const createInstantSearchApp = require('../api');
11
12
  const {
@@ -15,22 +16,22 @@ const {
15
16
  fetchLibraryVersions,
16
17
  getTemplatesByCategory,
17
18
  getTemplatePath,
19
+ splitArray,
18
20
  } = require('../utils');
19
- const getOptionsFromArguments = require('./getOptionsFromArguments');
20
21
  const getAttributesFromIndex = require('./getAttributesFromIndex');
22
+ const getFacetsFromIndex = require('./getFacetsFromIndex');
23
+ const getAnswersDefaultValues = require('./getAnswersDefaultValues');
21
24
  const isQuestionAsked = require('./isQuestionAsked');
22
- const {
23
- getConfiguration,
24
- getLibraryVersion,
25
- createNameAlternatives,
26
- } = require('./getConfiguration');
25
+ const getConfiguration = require('./getConfiguration');
26
+ const postProcessAnswers = require('./postProcessAnswers');
27
27
  const { version } = require('../../package.json');
28
28
 
29
29
  let appPathFromArgument;
30
- let options = {};
31
30
 
32
31
  program
32
+ .storeOptionsAsProperties(false)
33
33
  .version(version, '-v, --version')
34
+ .name('create-instantsearch-app')
34
35
  .arguments('<project-directory>')
35
36
  .usage(`${chalk.green('<project-directory>')} [options]`)
36
37
  .option('--name <name>', 'The name of the application or widget')
@@ -39,27 +40,29 @@ program
39
40
  .option('--index-name <indexName>', 'The main index of your search')
40
41
  .option(
41
42
  '--attributes-to-display <attributesToDisplay>',
42
- 'The attributes of your index to display'
43
+ 'The attributes of your index to display in hits',
44
+ splitArray
43
45
  )
44
46
  .option(
45
47
  '--attributes-for-faceting <attributesForFaceting>',
46
- 'The attributes for faceting'
48
+ 'The attributes to display as filters',
49
+ splitArray
47
50
  )
48
51
  .option('--template <template>', 'The InstantSearch template to use')
49
52
  .option('--library-version <version>', 'The version of the library')
50
53
  .option('--config <config>', 'The configuration file to get the options from')
51
54
  .option('--no-installation', 'Ignore dependency installation')
52
- .action((dest, opts) => {
55
+ .option('--no-interactive', 'Ask no interactive questions')
56
+ .action(dest => {
53
57
  appPathFromArgument = dest;
54
- options = opts;
55
58
  })
56
59
  .parse(process.argv);
57
60
 
58
- const optionsFromArguments = getOptionsFromArguments(options.rawArgs || []);
59
- const attributesToDisplay = (optionsFromArguments.attributesToDisplay || '')
60
- .split(',')
61
- .filter(Boolean)
62
- .map(x => x.trim());
61
+ const optionsFromArguments = program.opts();
62
+ const {
63
+ attributesToDisplay = [],
64
+ attributesForFaceting = [],
65
+ } = optionsFromArguments;
63
66
 
64
67
  const getQuestions = ({ appName }) => ({
65
68
  application: [
@@ -153,7 +156,49 @@ const getQuestions = ({ appName }) => ({
153
156
  ],
154
157
  filter: attributes => attributes.filter(Boolean),
155
158
  when: ({ appId, apiKey, indexName }) =>
156
- !attributesToDisplay.length > 0 && appId && apiKey && indexName,
159
+ attributesToDisplay.length === 0 && appId && apiKey && indexName,
160
+ },
161
+ {
162
+ type: 'checkbox',
163
+ name: 'attributesForFaceting',
164
+ message: 'Attributes to display',
165
+ suffix: `\n ${chalk.gray('Used to filter the search interface')}`,
166
+ pageSize: 10,
167
+ choices: async answers => {
168
+ const templatePath = getTemplatePath(answers.template);
169
+ const templateConfig = getAppTemplateConfig(templatePath);
170
+
171
+ const selectedLibraryVersion = answers.libraryVersion;
172
+ const requiredLibraryVersion =
173
+ templateConfig.flags && templateConfig.flags.dynamicWidgets;
174
+ const supportsDynamicWidgets =
175
+ selectedLibraryVersion &&
176
+ requiredLibraryVersion &&
177
+ semver.satisfies(selectedLibraryVersion, requiredLibraryVersion, {
178
+ includePrerelease: true,
179
+ });
180
+
181
+ const dynamicWidgets = supportsDynamicWidgets
182
+ ? [
183
+ {
184
+ name: 'Dynamic widgets',
185
+ value: 'ais.dynamicWidgets',
186
+ checked: true,
187
+ },
188
+ new inquirer.Separator(),
189
+ ]
190
+ : [];
191
+
192
+ return [
193
+ ...dynamicWidgets,
194
+ new inquirer.Separator('From your index'),
195
+ ...(await getFacetsFromIndex(answers)),
196
+ new inquirer.Separator(),
197
+ ];
198
+ },
199
+ filter: attributes => attributes.filter(Boolean),
200
+ when: ({ appId, apiKey, indexName }) =>
201
+ attributesForFaceting.length === 0 && appId && apiKey && indexName,
157
202
  },
158
203
  ],
159
204
  widget: [
@@ -179,95 +224,141 @@ const getQuestions = ({ appName }) => ({
179
224
  },
180
225
  },
181
226
  ],
182
- });
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();
183
291
 
184
- async function run() {
185
- let appPath = appPathFromArgument;
186
- if (!appPath) {
187
- const answers = await inquirer.prompt([
188
- {
189
- type: 'input',
190
- name: 'appPath',
191
- message: 'Project directory',
292
+ return Object.entries(templatesByCategory).reduce(
293
+ (templates, [category, values]) => [
294
+ ...templates,
295
+ new inquirer.Separator(category),
296
+ ...values,
297
+ ],
298
+ []
299
+ );
192
300
  },
193
- ]);
194
- appPath = answers.appPath;
195
- }
196
- if (appPath.startsWith('~/')) {
197
- appPath = path.join(os.homedir(), appPath.slice(2));
198
- }
199
- try {
200
- checkAppPath(appPath);
201
- } catch (err) {
202
- console.error(err.message);
203
- console.log();
301
+ validate(input) {
302
+ // if a config is given, path is optional
303
+ if (optionsFromArguments.config) {
304
+ return true;
305
+ }
204
306
 
205
- process.exit(1);
206
- }
307
+ const isValid = Boolean(input);
308
+ if (!isValid) {
309
+ console.log('template is required');
310
+ }
311
+ return isValid;
312
+ },
313
+ when(answers) {
314
+ return answers.interactive && !answers.config && !answers.template;
315
+ },
316
+ },
317
+ ],
318
+ });
207
319
 
208
- let appName = optionsFromArguments.name;
209
- if (!appName) {
210
- appName = (
211
- await inquirer.prompt([
212
- {
213
- type: 'input',
214
- name: 'appName',
215
- message: 'The name of the application or widget',
216
- default: path.basename(appPath),
217
- },
218
- ])
219
- ).appName;
220
- }
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
+ };
221
331
 
222
- try {
223
- checkAppName(appName);
224
- } catch (err) {
225
- console.error(err.message);
226
- console.log();
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
+ });
227
342
 
228
- process.exit(1);
343
+ if (initialAnswers.appPath.startsWith('~/')) {
344
+ initialAnswers.appPath = path.join(
345
+ os.homedir(),
346
+ initialAnswers.appPath.slice(2)
347
+ );
229
348
  }
230
349
 
350
+ const { appPath, appName, template } = initialAnswers;
351
+
231
352
  console.log();
232
353
  console.log(`Creating a new InstantSearch app in ${chalk.green(appPath)}.`);
233
354
  console.log();
234
355
 
235
- const { template = optionsFromArguments.template } = await inquirer.prompt(
236
- [
237
- {
238
- type: 'list',
239
- pageSize: 10,
240
- name: 'template',
241
- message: 'InstantSearch template',
242
- choices: () => {
243
- const templatesByCategory = getTemplatesByCategory();
244
-
245
- return Object.entries(templatesByCategory).reduce(
246
- (templates, [category, values]) => [
247
- ...templates,
248
- new inquirer.Separator(category),
249
- ...values,
250
- ],
251
- []
252
- );
253
- },
254
- validate(input) {
255
- return Boolean(input);
256
- },
257
- },
258
- ].filter(question =>
259
- isQuestionAsked({ question, args: optionsFromArguments })
260
- ),
261
- optionsFromArguments
262
- );
263
-
264
356
  const configuration = await getConfiguration({
265
357
  options: {
266
358
  ...optionsFromArguments,
267
359
  name: appName,
268
- attributesToDisplay,
269
360
  },
270
- answers: { template },
361
+ answers: initialAnswers,
271
362
  });
272
363
 
273
364
  const templatePath = getTemplatePath(configuration.template);
@@ -277,34 +368,21 @@ async function run() {
277
368
  templateConfig.category === 'Widget' ? 'widget' : 'application';
278
369
 
279
370
  const answers = await inquirer.prompt(
280
- getQuestions({ appName })[implementationType].filter(question =>
281
- isQuestionAsked({ question, args: optionsFromArguments })
371
+ getQuestions({ appName })[implementationType].filter(
372
+ question => !isQuestionAsked({ question, args: optionsFromArguments })
282
373
  ),
283
- { ...optionsFromArguments, template }
284
- );
285
-
286
- const alternativeNames = createNameAlternatives({
287
- ...configuration,
288
- ...answers,
289
- templateConfig,
290
- });
291
-
292
- const libraryVersion = await getLibraryVersion(
293
- { ...configuration, ...answers },
294
- templateConfig
374
+ getAnswersDefaultValues(optionsFromArguments, configuration, template)
295
375
  );
296
376
 
297
377
  const app = createInstantSearchApp(
298
378
  appPath,
299
- {
300
- ...configuration,
301
- ...answers,
302
- ...alternativeNames,
303
- libraryVersion,
304
- template: templatePath,
305
- installation: program.installation,
306
- currentYear: new Date().getFullYear(),
307
- },
379
+ await postProcessAnswers({
380
+ configuration,
381
+ answers,
382
+ optionsFromArguments,
383
+ templatePath,
384
+ templateConfig,
385
+ }),
308
386
  templateConfig.tasks
309
387
  );
310
388
 
@@ -1,22 +1,15 @@
1
1
  module.exports = function isQuestionAsked({ question, args }) {
2
- if (args.config) {
3
- return false;
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
- for (const optionName in args) {
7
- if (question.name === optionName) {
8
- // Skip if the arg in the command is valid
9
- if (
10
- !question.validate ||
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 true;
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;
@@ -94,6 +94,7 @@
94
94
  "src/assets"
95
95
  ],
96
96
  "styles": [
97
+ "node_modules/instantsearch.css/themes/satellite.css",
97
98
  "src/styles.css"
98
99
  ],
99
100
  "scripts": []
@@ -10,33 +10,33 @@
10
10
  },
11
11
  "private": true,
12
12
  "dependencies": {
13
- "@angular/animations": "~12.2.0",
14
- "@angular/common": "~12.2.0",
15
- "@angular/compiler": "~12.2.0",
16
- "@angular/core": "~12.2.0",
17
- "@angular/forms": "~12.2.0",
18
- "@angular/platform-browser": "~12.2.0",
19
- "@angular/platform-browser-dynamic": "~12.2.0",
20
- "@angular/router": "~12.2.0",
21
- "algoliasearch": "^4.10.5",
13
+ "@angular/animations": "12.2.0",
14
+ "@angular/common": "12.2.0",
15
+ "@angular/compiler": "12.2.0",
16
+ "@angular/core": "12.2.0",
17
+ "@angular/forms": "12.2.0",
18
+ "@angular/platform-browser": "12.2.0",
19
+ "@angular/platform-browser-dynamic": "12.2.0",
20
+ "@angular/router": "12.2.0",
21
+ "algoliasearch": "4.10.5",
22
22
  "angular-instantsearch": "{{libraryVersion}}",
23
- "rxjs": "~6.6.0",
24
- "tslib": "^2.3.0",
25
- "zone.js": "~0.11.4"
23
+ "rxjs": "6.6.0",
24
+ "tslib": "2.3.0",
25
+ "zone.js": "0.11.4"
26
26
  },
27
27
  "devDependencies": {
28
- "@angular-devkit/build-angular": "~12.2.3",
29
- "@angular/cli": "~12.2.3",
30
- "@angular/compiler-cli": "~12.2.0",
31
- "@types/algoliasearch": "^4.0.0",
32
- "@types/jasmine": "~3.8.0",
33
- "@types/node": "^12.11.1",
34
- "jasmine-core": "~3.8.0",
35
- "karma": "~6.3.0",
36
- "karma-chrome-launcher": "~3.1.0",
37
- "karma-coverage": "~2.0.3",
38
- "karma-jasmine": "~4.0.0",
39
- "karma-jasmine-html-reporter": "~1.7.0",
40
- "typescript": "~4.3.5"
28
+ "@angular-devkit/build-angular": "12.2.3",
29
+ "@angular/cli": "12.2.3",
30
+ "@angular/compiler-cli": "12.2.0",
31
+ "@types/algoliasearch": "4.0.0",
32
+ "@types/jasmine": "3.8.0",
33
+ "@types/node": "12.11.1",
34
+ "jasmine-core": "3.8.0",
35
+ "karma": "6.3.0",
36
+ "karma-chrome-launcher": "3.1.0",
37
+ "karma-coverage": "2.0.3",
38
+ "karma-jasmine": "4.0.0",
39
+ "karma-jasmine-html-reporter": "1.7.0",
40
+ "typescript": "4.3.5"
41
41
  }
42
42
  }
@@ -6,6 +6,7 @@
6
6
  <base href="/">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
8
  <link rel="icon" type="image/x-icon" href="favicon.ico">
9
+ <link href="https://cdn.jsdelivr.net/npm/instantsearch.css@7/themes/satellite.css" rel="stylesheet" />
9
10
  </head>
10
11
  <body>
11
12
  <app-root></app-root>
@@ -1,3 +1,11 @@
1
1
  /* You can add global styles to this file, and also import other style files */
2
- @import "instantsearch.css/themes/reset.css";
3
- @import "instantsearch.css/themes/satellite.css";
2
+ body,
3
+ h1 {
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
10
+ Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
11
+ }
@@ -13,6 +13,7 @@
13
13
  "downlevelIteration": true,
14
14
  "experimentalDecorators": true,
15
15
  "moduleResolution": "node",
16
+ "esModuleInterop": true,
16
17
  "importHelpers": true,
17
18
  "target": "es2017",
18
19
  "module": "es2020",
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
@@ -0,0 +1,3 @@
1
+ /node_modules
2
+ /dist
3
+ /.cache
@@ -0,0 +1,20 @@
1
+ /* eslint-disable import/no-commonjs */
2
+
3
+ module.exports = {
4
+ extends: ['algolia', 'algolia/react'],
5
+ settings: {
6
+ react: {
7
+ pragma: 'React',
8
+ version: 'preact',
9
+ },
10
+ },
11
+ rules: {
12
+ 'jsdoc/check-tag-names': [
13
+ 'error',
14
+ {
15
+ jsxTags: true,
16
+ },
17
+ ],
18
+ 'react/jsx-filename-extension': 'off',
19
+ },
20
+ };