├── .browserslistrc ├── .env.local.example ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-maskable-192x192.png │ │ ├── android-chrome-maskable-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── msapplication-icon-144x144.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg ├── index.html └── robots.txt ├── src ├── App.vue ├── assets │ ├── logo.png │ └── logo.svg ├── components │ ├── ColorPickerMenu.vue │ ├── Create.vue │ ├── Note.vue │ ├── NoteModal.vue │ └── Notes.vue ├── firebase │ ├── db.ts │ ├── firebase.ts │ └── index.ts ├── main.ts ├── plugins │ └── vuetify.ts ├── registerServiceWorker.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts └── store │ ├── index.ts │ ├── modules │ ├── global.ts │ └── notes.ts │ └── types.ts ├── tsconfig.json ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | VUE_APP_FB_API_KEY= 2 | VUE_APP_FB_AUTH_DOMAIN= 3 | VUE_APP_FB_DATABASE_URL= 4 | VUE_APP_FB_PROJECT_ID= 5 | VUE_APP_FB_STORAGE_BUCKET= 6 | VUE_APP_FB_MESSAGING_SENDER_ID= 7 | VUE_APP_FB_APP_ID= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Keep 2 | 3 | A Google Keep clone with Vue, Typescript and Firestore. 4 | 5 | Demo: https://vue-keep-sepia.vercel.app/ 6 | 7 | ## Usage 8 | 9 | Create a `.env.local` file at the root path and enter your firebase project creds: 10 | 11 | ``` 12 | VUE_APP_FB_API_KEY= 13 | VUE_APP_FB_AUTH_DOMAIN= 14 | VUE_APP_FB_DATABASE_URL= 15 | VUE_APP_FB_PROJECT_ID= 16 | VUE_APP_FB_STORAGE_BUCKET= 17 | VUE_APP_FB_MESSAGING_SENDER_ID= 18 | VUE_APP_FB_APP_ID= 19 | ``` 20 | 21 | ```bash 22 | # Install dependencies 23 | $ npm install 24 | 25 | # Compiles and hot-reloads for development 26 | $ npm run serve 27 | 28 | # Compiles and minifies for production 29 | $ npm run build 30 | 31 | # Lints and fixes files 32 | $ npm run lint 33 | ``` 34 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-keep", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "firebase": "^9.12.1", 13 | "register-service-worker": "^1.7.2", 14 | "vue": "^2.7.13", 15 | "vue-class-component": "^7.2.6", 16 | "vue-property-decorator": "^9.1.2", 17 | "vuetify": "^2.6.12", 18 | "vuex": "^3.4.0", 19 | "vuex-class": "^0.3.2" 20 | }, 21 | "devDependencies": { 22 | "@typescript-eslint/eslint-plugin": "^5.40.1", 23 | "@typescript-eslint/parser": "^5.40.1", 24 | "@vue/cli-plugin-babel": "~5.0.6", 25 | "@vue/cli-plugin-eslint": "~5.0.6", 26 | "@vue/cli-plugin-pwa": "~5.0.6", 27 | "@vue/cli-plugin-typescript": "~5.0.6", 28 | "@vue/cli-plugin-vuex": "~5.0.6", 29 | "@vue/cli-service": "~5.0.6", 30 | "@vue/eslint-config-typescript": "^11.0.2", 31 | "eslint": "^8.25.0", 32 | "eslint-plugin-vue": "^9.6.0", 33 | "sass": "^1.32.0", 34 | "sass-loader": "^13.1.0", 35 | "typescript": "~4.5.5", 36 | "vue-cli-plugin-vuetify": "~2.5.8", 37 | "vuetify-loader": "^1.9.2", 38 | "vuex-module-decorators": "^1.2.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 84 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wobsoriano/vue-keep/9594411ffc54a0c97d35c101915e70da7c5eef30/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/components/ColorPickerMenu.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 81 | -------------------------------------------------------------------------------- /src/components/Create.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 119 | -------------------------------------------------------------------------------- /src/components/Note.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 54 | -------------------------------------------------------------------------------- /src/components/NoteModal.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 128 | -------------------------------------------------------------------------------- /src/components/Notes.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 36 | -------------------------------------------------------------------------------- /src/firebase/db.ts: -------------------------------------------------------------------------------- 1 | import { db } from './firebase'; 2 | import { collection, doc, updateDoc, getDocs, addDoc, deleteDoc } from 'firebase/firestore/lite'; 3 | import { Note, NewNote } from '@/store/types'; 4 | 5 | const collectionName = 'notes' 6 | 7 | export const createNote = async (note: NewNote): Promise => { 8 | const newDoc = await addDoc(collection(db, collectionName), note); 9 | return { 10 | id: newDoc.id, 11 | ...note 12 | }; 13 | }; 14 | 15 | export const getNotes = async (): Promise => { 16 | const querySnapshot = await getDocs(collection(db, collectionName)); 17 | return querySnapshot.docs.map(doc => { 18 | const { title, content, color } = doc.data(); 19 | return { 20 | id: doc.id, 21 | title, 22 | content, 23 | color 24 | }; 25 | }); 26 | }; 27 | 28 | export const updateNote = async (note: Note): Promise => { 29 | const noteRef = doc(db, collectionName, note.id); 30 | await updateDoc(noteRef, { 31 | title: note.title, 32 | content: note.content, 33 | color: note.color 34 | }); 35 | return note; 36 | }; 37 | 38 | export const deleteNote = async (id: string): Promise => { 39 | const noteRef = doc(db, collectionName, id); 40 | await deleteDoc(noteRef); 41 | return id; 42 | }; 43 | -------------------------------------------------------------------------------- /src/firebase/firebase.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app'; 2 | import { getFirestore } from 'firebase/firestore/lite' 3 | 4 | const config = { 5 | apiKey: process.env.VUE_APP_FB_API_KEY, 6 | authDomain: process.env.VUE_APP_FB_AUTH_DOMAIN, 7 | databaseURL: process.env.VUE_APP_FB_DATABASE_URL, 8 | projectId: process.env.VUE_APP_FB_PROJECT_ID, 9 | storageBucket: process.env.VUE_APP_FB_STORAGE_BUCKET, 10 | messagingSenderId: process.env.VUE_APP_FB_MESSAGING_SENDER_ID, 11 | }; 12 | 13 | initializeApp(config) 14 | 15 | const db = getFirestore(); 16 | 17 | export { db }; 18 | -------------------------------------------------------------------------------- /src/firebase/index.ts: -------------------------------------------------------------------------------- 1 | import * as db from './db'; 2 | 3 | export { db }; 4 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './registerServiceWorker' 4 | import store from './store' 5 | import vuetify from './plugins/vuetify'; 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | store, 11 | vuetify, 12 | render: h => h(App) 13 | }).$mount('#app') 14 | -------------------------------------------------------------------------------- /src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify/lib'; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | }); 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker'; 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ); 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.'); 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.'); 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.'); 21 | }, 22 | updated() { 23 | console.log('New content is available; reloading...'); 24 | window.location.reload(); 25 | }, 26 | offline() { 27 | console.log( 28 | 'No internet connection found. App is running in offline mode.' 29 | ); 30 | }, 31 | error(error) { 32 | console.error('Error during service worker registration:', error); 33 | } 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import global from '@/store/modules/global'; 5 | import notes from '@/store/modules/notes'; 6 | 7 | Vue.use(Vuex); 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | global, 12 | notes 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/store/modules/global.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation } from 'vuex-module-decorators'; 2 | import { Note, Snackbar, EditNoteField } from '@/store/types'; 3 | import { SnackbarColorTypes, CardColorTypes } from '@/store/types'; 4 | 5 | @Module({ 6 | namespaced: true 7 | }) 8 | export default class GlobalModal extends VuexModule { 9 | selectedNote: Note = { 10 | id: '', 11 | title: '', 12 | content: '', 13 | color: CardColorTypes.Default 14 | }; 15 | showNoteDialog = false; 16 | snackbar: Snackbar = { 17 | open: false, 18 | text: '', 19 | color: SnackbarColorTypes.Success 20 | }; 21 | colorMenuOpen = false; 22 | 23 | @Mutation 24 | setSelectedNote(note: Note) { 25 | this.selectedNote = note; 26 | } 27 | 28 | @Mutation 29 | setNoteField(payload: EditNoteField) { 30 | this.selectedNote = { 31 | ...this.selectedNote, 32 | [payload.name]: payload.value 33 | }; 34 | } 35 | 36 | @Mutation 37 | setShowNoteDialog(payload: boolean) { 38 | this.showNoteDialog = payload; 39 | } 40 | 41 | @Mutation 42 | showSnackbar(payload: Snackbar) { 43 | this.snackbar = payload; 44 | } 45 | 46 | @Mutation 47 | setColorMenuOpen(payload: boolean) { 48 | this.colorMenuOpen = payload; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/store/modules/notes.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'; 2 | import { Note, NewNote } from '../types'; 3 | import { db } from '@/firebase'; 4 | import { CardColorTypes } from '@/store/types'; 5 | 6 | @Module({ 7 | namespaced: true 8 | }) 9 | export default class NotesModule extends VuexModule { 10 | notes: Note[] = []; 11 | 12 | @Mutation 13 | setNotes(notes: Note[]) { 14 | this.notes = notes; 15 | } 16 | 17 | @Mutation 18 | addNote(note: Note) { 19 | this.notes.unshift(note); 20 | } 21 | 22 | @Mutation 23 | removeNote(id: string) { 24 | const idx = this.notes.findIndex(i => i.id === id); 25 | if (idx !== -1) { 26 | this.notes.splice(idx, 1); 27 | } 28 | } 29 | 30 | @Mutation 31 | setNote(note: Note) { 32 | const idx = this.notes.findIndex(i => i.id === note.id); 33 | if (idx !== -1) { 34 | this.notes[idx].title = note.title; 35 | this.notes[idx].content = note.content; 36 | this.notes[idx].color = note.color || CardColorTypes.Default; 37 | } 38 | } 39 | 40 | @Action({ commit: 'setNotes' }) 41 | async getNotes() { 42 | const notes = await db.getNotes(); 43 | return notes; 44 | } 45 | 46 | @Action({ commit: 'addNote' }) 47 | async createNote(note: NewNote) { 48 | const data = await db.createNote(note); 49 | return data; 50 | } 51 | 52 | @Action({ commit: 'removeNote' }) 53 | async removeNoteAsync(id: string) { 54 | await db.deleteNote(id); 55 | return id; 56 | } 57 | 58 | @Action({ commit: 'setNote' }) 59 | async updateNote(note: Note) { 60 | await db.updateNote(note); 61 | return note; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | export enum SnackbarColorTypes { 2 | Success = 'success', 3 | Error = 'error', 4 | Info = 'info' 5 | } 6 | 7 | export enum CardColorTypes { 8 | Default = 'none', 9 | Red = 'red accent-1', 10 | Orange = 'orange accent-2', 11 | Yellow = 'yellow accent-1', 12 | Green = 'light-green accent-1', 13 | Blue = 'cyan lighten-4', 14 | Purple = 'purple lighten-4', 15 | Pink = 'pink lighten-4' 16 | } 17 | 18 | export interface Note { 19 | id: string; 20 | title: string; 21 | content: string; 22 | color: string; 23 | } 24 | 25 | export type NewNote = Omit; 26 | 27 | export interface Snackbar { 28 | open: boolean; 29 | text: string; 30 | color: SnackbarColorTypes; 31 | } 32 | 33 | export interface EditNoteField { 34 | name: string; 35 | value: string; 36 | } 37 | 38 | export type CardColors = keyof typeof CardColorTypes; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "typeRoots": ["./node_modules/@types", "./node_modules/vuetify/types"], 15 | "types": ["webpack-env", "vuetify"], 16 | "paths": { 17 | "@/*": ["src/*"] 18 | }, 19 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 20 | }, 21 | "include": [ 22 | "src/**/*.ts", 23 | "src/**/*.tsx", 24 | "src/**/*.vue", 25 | "tests/**/*.ts", 26 | "tests/**/*.tsx" 27 | ], 28 | "exclude": ["node_modules"], 29 | "vueCompilerOptions": { 30 | "target": 2.7 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("@vue/cli-service"); 2 | 3 | module.exports = defineConfig({ 4 | transpileDependencies: [ 5 | "vuetify" 6 | ], 7 | pwa: { 8 | "workboxOptions": { 9 | "skipWaiting": true 10 | } 11 | }, 12 | css: { 13 | loaderOptions: { 14 | sass: { 15 | sassOptions: { 16 | quietDeps: true, 17 | }, 18 | }, 19 | }, 20 | }, 21 | }); 22 | --------------------------------------------------------------------------------