├── .eslintrc.js
├── .github
└── screenshots
│ └── tiny-dashboard
│ ├── anamnisar.png
│ ├── ash.png
│ ├── cloud.png
│ ├── dull.png
│ ├── midnight.png
│ ├── nord.png
│ ├── nordlight.png
│ ├── pacific.png
│ ├── preview.png
│ ├── royal.png
│ ├── sandblue.png
│ ├── seablue.png
│ └── sin.png
├── .gitignore
├── README.md
├── babel-plugin
└── babel-transform-import-scriptable.js
├── babel.config.json
├── build
├── demo.js
├── mono-monthly-small.js
├── monobank.js
├── tiny-charts.js
├── tiny-dashboard.js
├── update-code.js
└── utils.js
├── package-lock.json
├── package.json
├── src
├── demo.ts
├── mono-monthly-small.ts
├── monobank.ts
├── monobank.types.ts
├── tiny-charts.ts
├── tiny-dashboard.ts
├── update-code.ts
├── utils.ts
└── utils.types.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-magic-numbers */
2 |
3 | module.exports = {
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | '@scriptable-ios',
8 | ],
9 |
10 | rules: {
11 | 'no-extra-parens': [
12 | 'error',
13 | 'all',
14 | {
15 | conditionalAssign: false,
16 | nestedBinaryExpressions: false,
17 | returnAssign: false,
18 | enforceForArrowConditionals: false,
19 | enforceForSequenceExpressions: false,
20 | enforceForNewInMemberExpressions: false,
21 | enforceForFunctionPrototypeMethods: false,
22 | },
23 | ],
24 | // https://eslint.org/docs/rules/no-invalid-regexp
25 | 'no-invalid-regexp': 'error',
26 |
27 | // https://eslint.org/docs/rules/no-unreachable
28 | 'no-unreachable': 'error',
29 |
30 | // https://eslint.org/docs/rules/no-unreachable-loop
31 | 'no-unreachable-loop': 'error',
32 |
33 | // https://eslint.org/docs/rules/valid-typeof
34 | 'valid-typeof': [
35 | 'error',
36 | {
37 | requireStringLiterals: true,
38 | },
39 | ],
40 |
41 | // https://eslint.org/docs/rules/dot-location
42 | 'dot-location': ['error', 'property'],
43 |
44 | // https://eslint.org/docs/rules/dot-notation
45 | 'dot-notation': [
46 | 'error',
47 | {
48 | allowKeywords: true,
49 | },
50 | ],
51 |
52 | // https://eslint.org/docs/rules/no-alert
53 | 'no-alert': 'error',
54 |
55 | // https://eslint.org/docs/rules/no-else-return
56 | 'no-else-return': [
57 | 'error',
58 | {
59 | allowElseIf: false,
60 | },
61 | ],
62 |
63 | // https://eslint.org/docs/rules/no-eval
64 | 'no-eval': 'error',
65 |
66 | // https://eslint.org/docs/rules/no-global-assign
67 | 'no-global-assign': [
68 | 'error',
69 | {
70 | exceptions: [],
71 | },
72 | ],
73 | // https://eslint.org/docs/rules/no-multi-spaces
74 | 'no-multi-spaces': 'error',
75 |
76 | // https://eslint.org/docs/rules/no-multi-str
77 | 'no-multi-str': 'error',
78 |
79 | // https://eslint.org/docs/rules/no-restricted-properties
80 | 'no-restricted-properties': [
81 | 'error',
82 | {
83 | object: 'arguments',
84 | property: 'callee',
85 | message: 'arguments.callee is deprecated',
86 | },
87 | {
88 | object: 'global',
89 | property: 'isFinite',
90 | message: 'Use Number.isFinite instead',
91 | },
92 | {
93 | object: 'window',
94 | property: 'isFinite',
95 | message: 'Use Number.isFinite instead',
96 | },
97 | {
98 | object: 'global',
99 | property: 'isNaN',
100 | message: 'Use Number.isNaN instead',
101 | },
102 | {
103 | object: 'window',
104 | property: 'isNaN',
105 | message: 'Use Number.isNaN instead',
106 | },
107 | {
108 | property: '__defineGetter__',
109 | message: 'Use Object.defineProperty instead',
110 | },
111 | {
112 | property: '__defineSetter__',
113 | message: 'Use Object.defineProperty instead',
114 | },
115 | {
116 | object: 'require',
117 | message: 'Please call require() directly.',
118 | },
119 | ],
120 |
121 | // https://eslint.org/docs/rules/no-useless-return
122 | 'no-useless-return': 'error',
123 |
124 | // https://eslint.org/docs/rules/require-await
125 | 'require-await': 'error',
126 |
127 | // https://eslint.org/docs/rules/vars-on-top
128 | 'vars-on-top': 'error',
129 |
130 | // https://eslint.org/docs/rules/no-compare-neg-zero
131 | 'no-compare-neg-zero': 'error',
132 |
133 | // https://eslint.org/docs/rules/no-cond-assign
134 | 'no-cond-assign': 'error',
135 |
136 | // https://eslint.org/docs/rules/no-console
137 | 'no-console': 'warn',
138 |
139 | // https://eslint.org/docs/rules/no-constant-condition
140 | 'no-constant-condition': 'error',
141 |
142 | // https://eslint.org/docs/rules/no-control-regex
143 | 'no-control-regex': 'error',
144 |
145 | // https://eslint.org/docs/rules/no-debugger
146 | 'no-debugger': 'error',
147 |
148 | // https://eslint.org/docs/rules/no-undefined
149 | 'no-undefined': 'off',
150 |
151 | 'eol-last': ['error', 'always'],
152 | 'no-mixed-spaces-and-tabs': 'error',
153 |
154 | // https://eslint.org/docs/rules/jsx-quotes
155 | 'jsx-quotes': ['error', 'prefer-double'],
156 |
157 | // https://eslint.org/docs/rules/quotes
158 | quotes: ['error', 'single'],
159 |
160 | // https://eslint.org/docs/rules/block-spacing
161 | 'block-spacing': ['error', 'always'],
162 |
163 | // https://eslint.org/docs/rules/array-bracket-spacing
164 | 'array-bracket-spacing': ['error', 'never'],
165 |
166 | // https://eslint.org/docs/rules/no-whitespace-before-property
167 | 'no-whitespace-before-property': 'error',
168 |
169 | // https://eslint.org/docs/rules/linebreak-style
170 | 'linebreak-style': ['error', 'unix'],
171 |
172 | // https://eslint.org/docs/rules/multiline-comment-style
173 | 'comma-spacing': [
174 | 'error',
175 | {
176 | before: false,
177 | after: true,
178 | },
179 | ],
180 |
181 | // https://eslint.org/docs/rules/brace-style
182 | 'brace-style': [
183 | 'error',
184 | '1tbs',
185 | {
186 | allowSingleLine: true,
187 | },
188 | ],
189 |
190 | // https://eslint.org/docs/rules/no-multiple-empty-lines
191 | 'no-multiple-empty-lines': [
192 | 'error',
193 | {
194 | max: 2,
195 | maxEOF: 0,
196 | maxBOF: 0,
197 | },
198 | ],
199 |
200 | // https://eslint.org/docs/rules/key-spacing
201 | 'key-spacing': [
202 | 'error',
203 | {
204 | beforeColon: false,
205 | afterColon: true,
206 | mode: 'strict',
207 | },
208 | ],
209 |
210 | // https://eslint.org/docs/rules/no-trailing-spaces
211 | 'no-trailing-spaces': [
212 | 'error',
213 | {
214 | skipBlankLines: false,
215 | ignoreComments: false,
216 | },
217 | ],
218 |
219 | // https://eslint.org/docs/rules/no-underscore-dangle
220 | 'no-underscore-dangle': [
221 | 'error',
222 | {
223 | allow: ['__', '__typename'],
224 | allowAfterThis: false,
225 | allowAfterSuper: false,
226 | enforceInMethodNames: false,
227 | },
228 | ],
229 | // https://eslint.org/docs/rules/object-curly-spacing
230 | 'object-curly-spacing': [
231 | 'error',
232 | 'always',
233 | {
234 | arraysInObjects: true,
235 | objectsInObjects: true,
236 | },
237 | ],
238 |
239 | // https://eslint.org/docs/rules/padding-line-between-statements
240 | 'padding-line-between-statements': [
241 | 'error',
242 | {
243 | blankLine: 'always',
244 | prev: 'directive',
245 | next: '*',
246 | },
247 | {
248 | blankLine: 'any',
249 | prev: 'directive',
250 | next: 'directive',
251 | },
252 |
253 | {
254 | blankLine: 'always',
255 | prev: ['const', 'let', 'var'],
256 | next: '*',
257 | },
258 | {
259 | blankLine: 'any',
260 | prev: ['const', 'let', 'var'],
261 | next: ['const', 'let', 'var'],
262 | },
263 |
264 | {
265 | blankLine: 'always',
266 | prev: '*',
267 | next: 'return',
268 | },
269 | ],
270 |
271 | '@typescript-eslint/explicit-function-return-type': 'off',
272 | '@typescript-eslint/ban-ts-comment': 'off',
273 | '@typescript-eslint/no-var-requires': 'off',
274 | // note you must disable the base rule as it can report incorrect errors
275 | 'comma-dangle': 'off',
276 | '@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
277 | // note you must disable the base rule as it can report incorrect errors
278 | 'no-unused-vars': 'off',
279 | '@typescript-eslint/no-unused-vars': 'warn',
280 |
281 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md
282 | '@typescript-eslint/member-delimiter-style': [
283 | 'error',
284 | {
285 | multiline: {
286 | delimiter: 'comma',
287 | requireLast: true,
288 | },
289 | singleline: {
290 | delimiter: 'comma',
291 | requireLast: false,
292 | },
293 | },
294 | ],
295 |
296 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-as-const.md
297 | '@typescript-eslint/prefer-as-const': 'warn',
298 |
299 | // note you must disable the base rule as it can report incorrect errors
300 | indent: 'off',
301 | '@typescript-eslint/indent': [
302 | 'error',
303 | 2,
304 | {
305 | SwitchCase: 1,
306 | VariableDeclarator: 1,
307 | outerIIFEBody: 1,
308 | MemberExpression: 0,
309 | FunctionDeclaration: {
310 | parameters: 1,
311 | body: 1,
312 | },
313 | FunctionExpression: {
314 | parameters: 1,
315 | body: 1,
316 | },
317 | CallExpression: {
318 | arguments: 1,
319 | },
320 | ArrayExpression: 1,
321 | ObjectExpression: 1,
322 | ImportDeclaration: 1,
323 | flatTernaryExpressions: false,
324 | offsetTernaryExpressions: false,
325 | ignoredNodes: [],
326 | ignoreComments: false,
327 | },
328 | ],
329 | // note you must disable the base rule as it can report incorrect errors
330 | semi: 'off',
331 | '@typescript-eslint/semi': ['error', 'never'],
332 | '@typescript-eslint/naming-convention': [
333 | 'error',
334 | {
335 | selector: 'typeAlias',
336 | format: ['PascalCase'],
337 | suffix: ['T'],
338 | },
339 | {
340 | selector: 'interface',
341 | format: ['PascalCase'],
342 | suffix: ['I'],
343 | },
344 | ],
345 | // note you must disable the base rule as it can report incorrect errors
346 | 'no-magic-numbers': 'off',
347 | '@typescript-eslint/no-magic-numbers': [
348 | 'error',
349 | {
350 | ignore: [-1, 2, 0, 1, 1e2, 1e3],
351 | ignoreArrayIndexes: true,
352 | enforceConst: true,
353 | detectObjects: false,
354 | ignoreNumericLiteralTypes: true,
355 | },
356 | ],
357 | // note you must disable the base rule as it can report incorrect errors
358 | 'keyword-spacing': 'off',
359 | '@typescript-eslint/keyword-spacing': [
360 | 'error',
361 | {
362 | before: true,
363 | after: true,
364 | overrides: {
365 | return: {
366 | after: true,
367 | },
368 | throw: {
369 | after: true,
370 | },
371 | case: {
372 | after: true,
373 | },
374 | },
375 | },
376 | ],
377 | },
378 | }
379 |
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/anamnisar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/anamnisar.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/ash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/ash.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/cloud.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/dull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/dull.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/midnight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/midnight.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/nord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/nord.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/nordlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/nordlight.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/pacific.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/pacific.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/preview.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/royal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/royal.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/sandblue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/sandblue.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/seablue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/seablue.png
--------------------------------------------------------------------------------
/.github/screenshots/tiny-dashboard/sin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/sin.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | log
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
9 | # Dependency directories
10 | node_modules/
11 |
12 | # MAC
13 | .DS_Store
14 |
15 | # VScode
16 | .vscode
17 | jsconfig.json
18 |
19 | # automation tests
20 | allure-results
21 |
22 | # Yarn lock file
23 | yarn.lock
24 |
25 | #IntelliJ *.orig files
26 | *.orig
27 |
28 | # Vim
29 | .vim/
30 | *.vim
31 |
32 | # releases
33 | *.zip
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scriptables
2 | my small collection of scripts to be used with [scriptable.app](https://scriptable.app)
3 |
4 | ### [tiny-dashboard](https://github.com/Nodman/scripables/blob/main/src/tiny-dashboard.ts):
5 |
6 |
7 |
8 | Small-size widget with several text fields, graph and icon, supports dynamic dark / light mode and has 12 builtin themes.
9 |
10 | Themes can be configured through widget params: ***dark=sandblue;light=royal***
11 |
12 |
13 | themes preview (expandable)
14 |
15 | *NOTE:*
16 |
17 |
18 | *most of the colors were taken from https://uigradients.com*
19 | *nord colors were taken from https://www.nordtheme.com*
20 |
21 |
22 | * seablue
23 |
24 | 
25 |
26 | * cloud
27 |
28 | 
29 |
30 | * midnight
31 |
32 | 
33 |
34 | * royal
35 |
36 | 
37 |
38 | * dull
39 |
40 | 
41 |
42 | * anamnisar
43 |
44 | 
45 |
46 | * ash
47 |
48 | 
49 |
50 | * pacific
51 |
52 | 
53 |
54 | * sin
55 |
56 | 
57 |
58 | * sandblue
59 |
60 | 
61 |
62 | * nord
63 |
64 | 
65 |
66 | * nordlight
67 |
68 | 
69 |
70 |
71 |
72 |
73 |
74 | ### [tiny-charts](https://github.com/Nodman/scripables/blob/main/src/tiny-charts.ts):
75 |
76 | Script for drawing charts with context, for now supports only area-type chart as shown on screenshot above
77 |
78 |
79 |
80 | ### [monobank](https://github.com/Nodman/scripables/blob/main/src/monobank.ts):
81 |
82 | *(WIP, expect breaking changes)*
83 |
84 | Monobank api client: fetch, record, filter and edit statement for current month, cache history for previous monthes.
85 |
86 | Provides UI for setting api-key, settting account per widget and and managing statement filters. Uses keychain to store api-key.
87 |
88 | ### [mono-monthly-small](https://github.com/Nodman/scripables/blob/main/src/mono-monthly-small.ts):
89 |
90 | Monobank script + tiny dashboard (as shown on the frist screenshot)
91 |
92 | ### [utils](https://github.com/Nodman/scripables/blob/main/src/utils.ts):
93 |
94 | Some common utils that are used across other scripts
95 |
96 | ### [update-code](https://github.com/Nodman/scripables/blob/main/src/update-code.ts):
97 |
98 | Update code of scripts from this repo
99 |
100 | ---
101 |
102 | this repos also contains custom bable plugin that transpiles es imprts to scriptable ***importModule*** api
103 |
--------------------------------------------------------------------------------
/babel-plugin/babel-transform-import-scriptable.js:
--------------------------------------------------------------------------------
1 | // transform ESModule imports to scriptable 'importModule' api call
2 |
3 | const syntaxErrorMessage =
4 | "Unsupported syntax error. Don't mix default and named imports";
5 |
6 | function transform({ types }) {
7 | return {
8 | visitor: {
9 | ImportDeclaration(path) {
10 | let variableDeclarator;
11 |
12 | const { specifiers, source } = path.node;
13 | const callExpression = types.callExpression(
14 | types.identifier("importModule"),
15 | [types.stringLiteral(source.value)]
16 | );
17 |
18 | if (specifiers[0].type === "ImportSpecifier") {
19 | const objectProperties = specifiers.map((item) => {
20 | if (item.type === "ImportDefaultSpecifier") {
21 | throw path.buildCodeFrameError(syntaxErrorMessage);
22 | }
23 |
24 | const identifier = types.identifier(item.local.name);
25 | return types.objectProperty(identifier, identifier, false, true);
26 | });
27 |
28 | variableDeclarator = types.variableDeclarator(
29 | types.objectPattern(objectProperties),
30 | callExpression
31 | );
32 | } else {
33 | if (specifiers.length > 1) {
34 | throw path.buildCodeFrameError(syntaxErrorMessage);
35 | }
36 | variableDeclarator = types.variableDeclarator(
37 | types.identifier(specifiers[0].local.name),
38 | callExpression
39 | );
40 | }
41 |
42 | path.replaceWith(
43 | types.variableDeclaration("const", [variableDeclarator])
44 | );
45 | },
46 | },
47 | };
48 | }
49 |
50 | module.exports = transform;
51 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "./babel-plugin/babel-transform-import-scriptable.js",
4 | "@babel/plugin-transform-modules-commonjs"
5 | ],
6 | "presets": ["@babel/preset-typescript"],
7 | "ignore": ["./src/*.types.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/build/demo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable @typescript-eslint/no-magic-numbers */
4 | const {
5 | createWidget
6 | } = importModule("./tiny-dashboard");
7 | const {
8 | numberFormatterShort
9 | } = importModule("./utils");
10 | const dateFormatter = new DateFormatter();
11 | dateFormatter.useShortTimeStyle();
12 |
13 | function exec() {
14 | const widget = createWidget({
15 | chartData: [89, 100, 69, 190, 59, 22, 40],
16 | subtitle1: `${numberFormatterShort(1400)} DAILY / ${numberFormatterShort(12780)} MONTHLY`,
17 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`,
18 | value: numberFormatterShort(2888),
19 | subValue: `/ ${numberFormatterShort(190)}`,
20 | headerSymbol: 'hryvniasign.circle',
21 | header: 'CURRENT MONTH:'
22 | }, {
23 | dark: 'midnight'
24 | });
25 | widget.presentSmall();
26 | }
27 |
28 | exec();
29 | Script.complete();
--------------------------------------------------------------------------------
/build/mono-monthly-small.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: blue; icon-glyph: hand-holding-usd;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * track expanses from monobank account with small widget
8 | */
9 | 'use strict';
10 |
11 | const {
12 | createWidget
13 | } = importModule("./tiny-dashboard");
14 | const {
15 | numberFormatterShort,
16 | parseWidgetParams
17 | } = importModule("./utils");
18 | const {
19 | Monobank
20 | } = importModule("./monobank");
21 | const SCRIPT_NAME = 'mono-monthly-small';
22 | const dateFormatter = new DateFormatter();
23 | dateFormatter.useShortTimeStyle();
24 |
25 | async function exec() {
26 | const {
27 | name = SCRIPT_NAME
28 | } = parseWidgetParams(args.widgetParameter);
29 | const monobank = new Monobank(name);
30 | await monobank.setup();
31 | const {
32 | currentPeriod,
33 | history
34 | } = await monobank.fetchLatestStatement();
35 | const todaysValue = Monobank.getTodaysExpanses({
36 | currentPeriod
37 | });
38 | const {
39 | monthlyHistory,
40 | daily,
41 | monthly
42 | } = Monobank.getStatsForPeriod({
43 | history
44 | });
45 | monthlyHistory.push(currentPeriod.total);
46 | const widget = createWidget({
47 | chartData: monthlyHistory,
48 | subtitle1: `${numberFormatterShort(daily / 100)} DAILY / ${numberFormatterShort(monthly / 100)} MONTHLY`,
49 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`,
50 | value: numberFormatterShort(currentPeriod.total / 100),
51 | subValue: `/ ${numberFormatterShort(todaysValue / 100)}`,
52 | headerSymbol: 'hryvniasign.circle',
53 | header: 'CURRENT MONTH:'
54 | });
55 | Script.setWidget(widget);
56 | return widget;
57 | }
58 |
59 | if (config.runsInApp) {
60 | const widget = await exec();
61 | await widget.presentSmall();
62 | } else {
63 | await exec();
64 | }
65 |
66 | Script.complete();
--------------------------------------------------------------------------------
/build/monobank.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-gray; icon-glyph: university;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * Monobank api module
8 | */
9 | // NOTE: This script uses the Cache script (https://github.com/yaylinda/scriptable/blob/main/Cache.js)
10 | 'use strict';
11 |
12 | Object.defineProperty(exports, "__esModule", {
13 | value: true
14 | });
15 | exports.Monobank = void 0;
16 | const {
17 | fetchJson,
18 | createAlert,
19 | getDaysInMonth,
20 | Logger
21 | } = importModule("./utils");
22 | const Cache = importModule('cache');
23 | const DEFAULT_PERIOD = 12;
24 | const ACCOUNT_MASK_LEN = 4;
25 | const SCRIPT_NAME = 'monobank';
26 | const CACHE_KEY_SETTINGS = 'settings.json';
27 | const MONOBANK_TOKEN_KEY = 'monobank_token@scriptable';
28 | const MONOBANK_API_URL = 'https://api.monobank.ua';
29 | const API_PATH = {
30 | PERSONAL_INFO: '/personal/client-info',
31 | STATEMENT: '/personal/statement',
32 | CURRENCIES: '/bank/currency'
33 | };
34 | const CURRENCIES = {
35 | 978: {
36 | id: 978,
37 | name: 'EUR',
38 | sign: '€'
39 | },
40 | 980: {
41 | id: 980,
42 | name: 'UAH',
43 | sign: '₴'
44 | },
45 | 840: {
46 | id: 840,
47 | name: 'USD',
48 | sign: '$'
49 | }
50 | };
51 | const cache = new Cache(SCRIPT_NAME);
52 | const logger = new Logger('Mono');
53 |
54 | class Monobank {
55 | constructor(parentScriptName) {
56 | this.parentScriptName = parentScriptName;
57 | this.readSettings();
58 | }
59 |
60 | static getTodaysExpanses({
61 | currentPeriod
62 | }) {
63 | if (!currentPeriod || !currentPeriod.statement) {
64 | return 0;
65 | }
66 |
67 | const date = new Date().getDate();
68 | const todaysStatement = currentPeriod.statement[date];
69 |
70 | if (!todaysStatement || !todaysStatement.length) {
71 | return 0;
72 | }
73 |
74 | return todaysStatement.reduce((acc, item) => {
75 | return acc - (item?.amount ?? 0);
76 | }, 0);
77 | }
78 |
79 | static getStatsForPeriod({
80 | history
81 | }, period = DEFAULT_PERIOD) {
82 | const yeatsAvailable = Object.keys(history).map(Number).sort((a, b) => a - b);
83 | const monthlyHistory = [];
84 | const dailyHistory = [];
85 |
86 | while (monthlyHistory.length < period && yeatsAvailable.length) {
87 | const [year] = yeatsAvailable.splice(-1);
88 | const yearPeriod = history[year] ?? {};
89 | Object.keys(yearPeriod).map(Number).sort((a, b) => b - a).slice(-period + monthlyHistory.length).forEach(month => {
90 | const daysInMonth = getDaysInMonth(year, month);
91 | const monthValue = yearPeriod[month];
92 | monthlyHistory.unshift(monthValue);
93 | dailyHistory.unshift(monthValue / daysInMonth);
94 | });
95 | }
96 |
97 | return {
98 | monthlyHistory,
99 | dailyHistory,
100 | monthly: monthlyHistory.reduce((acc, item) => acc + item, 0) / (monthlyHistory.length || 1),
101 | daily: dailyHistory.reduce((acc, item) => acc + item, 0) / (dailyHistory.length || 1)
102 | };
103 | }
104 |
105 | async getStatementItemsFilter() {
106 | const settings = await this.settings;
107 | const filter = this.parentScriptName && settings ? settings[this.parentScriptName]?.filter : undefined;
108 | return statementItem => {
109 | const isExpanse = statementItem.amount < 0 || statementItem.description.toLowerCase().startsWith('скасування');
110 | const isExcluded = filter ? filter.split(';').some(filterString => statementItem.description.match(filterString)) : false;
111 | return isExpanse && !isExcluded;
112 | };
113 | }
114 |
115 | async readSettings() {
116 | this.settings = cache.read(CACHE_KEY_SETTINGS) ?? {};
117 | return await this.settings;
118 | }
119 |
120 | async writeSettings(nextSettings) {
121 | const prevSettings = await this.readSettings();
122 | const settings = { ...prevSettings,
123 | ...nextSettings
124 | };
125 | cache.write(CACHE_KEY_SETTINGS, settings);
126 | this.settings = Promise.resolve(settings);
127 | }
128 |
129 | get requestHeaders() {
130 | const token = Keychain.get(MONOBANK_TOKEN_KEY);
131 |
132 | if (!token) {
133 | throw new Error('No token was found in keychain');
134 | }
135 |
136 | return {
137 | 'X-Token': token
138 | };
139 | }
140 |
141 | async getAccountId(parentScriptName) {
142 | const parentScript = parentScriptName ?? this.parentScriptName;
143 | const settings = await this.settings;
144 | let accountId = parentScript && settings ? settings[parentScript]?.accountId : undefined;
145 |
146 | if (!accountId) {
147 | accountId = await this.setupAccount();
148 | }
149 |
150 | if (!accountId) {
151 | throw new Error('Account ID is required');
152 | }
153 |
154 | return accountId;
155 | }
156 |
157 | fetchData(path) {
158 | if (this.isTokenProvided()) {
159 | const url = `${MONOBANK_API_URL}${path}`;
160 | return fetchJson(url, this.requestHeaders);
161 | }
162 |
163 | throw new Error('No token provided');
164 | }
165 |
166 | isTokenProvided() {
167 | return Keychain.contains(MONOBANK_TOKEN_KEY);
168 | }
169 |
170 | async getAccountCacheKey(parentScriptName) {
171 | const accountId = await this.getAccountId(parentScriptName);
172 | return `mono-${accountId}.json`;
173 | }
174 |
175 | async setupToken() {
176 | if (this.isTokenProvided()) {
177 | const tokenExistsAlert = createAlert('Monobank api token', 'Token already exists in your Keychain. Do you wish to change it?', 'Cancel');
178 | tokenExistsAlert.addAction('Change token');
179 | tokenExistsAlert.addDestructiveAction('Delete token');
180 | const actionIndex = await tokenExistsAlert.presentSheet();
181 |
182 | switch (actionIndex) {
183 | case -1:
184 | return;
185 |
186 | case 1:
187 | {
188 | Keychain.remove(MONOBANK_TOKEN_KEY);
189 | await createAlert('Token removed', '', 'Ok').present();
190 | return;
191 | }
192 | }
193 | }
194 |
195 | const setupAlert = createAlert('Monobank API token is required', `In order to fetch data from Monobank you need to provide your generated API token.
196 | If you don't have one - please visit https://api.monobank.ua and follow the instructions.`, 'Cancel');
197 | setupAlert.addAction('I have token');
198 | setupAlert.addAction('https://api.monobank.ua');
199 | const setupActionIndex = await setupAlert.presentSheet();
200 |
201 | const presentInputAlert = async () => {
202 | const inputAlert = createAlert('Insert token', 'Your token will be saved in Keychain', 'Cancel');
203 | inputAlert.addAction('Save');
204 | inputAlert.addSecureTextField('token:');
205 | const inputActionIndex = await inputAlert.present();
206 | const tokenValue = inputAlert.textFieldValue(0);
207 |
208 | if (inputActionIndex === -1) {
209 | return;
210 | }
211 |
212 | if (tokenValue) {
213 | Keychain.set(MONOBANK_TOKEN_KEY, tokenValue);
214 | await createAlert('Token saved', '', 'Ok').present();
215 | } else {
216 | await createAlert('Invalid token value', '', 'Ok').present();
217 | }
218 | };
219 |
220 | switch (setupActionIndex) {
221 | case 0:
222 | {
223 | await presentInputAlert();
224 | break;
225 | }
226 |
227 | case 1:
228 | await Safari.openInApp('https://api.monobank.ua');
229 | await presentInputAlert();
230 | break;
231 | }
232 | }
233 |
234 | async setupFilters(parentScriptName) {
235 | const scriptName = parentScriptName ?? this.parentScriptName;
236 |
237 | if (!scriptName) {
238 | return;
239 | }
240 |
241 | const settings = (await this.settings) ?? {};
242 | const setupFiltersAlert = createAlert('Set statement items filter', 'Enter semicolon separated values, e.g. "rent;netflix;spotify". Items with one of those words in description will be excluded from accounting', 'Cancel');
243 | setupFiltersAlert.addAction('Save');
244 | const filter = settings ? settings[scriptName]?.filter : undefined;
245 | setupFiltersAlert.addTextField('filter:', filter ?? '');
246 | const actionIndex = await setupFiltersAlert.present();
247 |
248 | if (actionIndex === -1) {
249 | return;
250 | }
251 |
252 | const nextFilter = setupFiltersAlert.textFieldValue(0);
253 | settings[scriptName] = settings[scriptName] ?? {};
254 | settings[scriptName].filter = nextFilter;
255 | await this.writeSettings(settings);
256 | await createAlert('Filters saved', '', 'Ok').present();
257 | }
258 |
259 | async setupAccount(parentScriptName) {
260 | const scriptName = parentScriptName ?? this.parentScriptName;
261 | const setupAccountsAlert = createAlert('Select account', 'ID of selected account will be copied to clipboard and used for this script.', 'Cancel');
262 | const accounts = await this.fetchAccounts();
263 | accounts.forEach(item => {
264 | const {
265 | name,
266 | type
267 | } = item;
268 | setupAccountsAlert.addAction(`[${type}] ****${name}`);
269 | });
270 | const actionIndex = await setupAccountsAlert.presentSheet();
271 |
272 | if (actionIndex === -1) {
273 | return;
274 | }
275 |
276 | const id = accounts[actionIndex].id;
277 | Pasteboard.copy(id);
278 |
279 | if (scriptName) {
280 | await this.writeSettings({
281 | [scriptName]: {
282 | accountId: id
283 | }
284 | });
285 | }
286 |
287 | return id;
288 | }
289 |
290 | async fetchAccounts() {
291 | const response = await this.fetchData(API_PATH.PERSONAL_INFO);
292 | const accounts = response.accounts.map(item => {
293 | const name = (item.maskedPan[0] ?? item.iban).slice(-ACCOUNT_MASK_LEN);
294 | return {
295 | id: item.id,
296 | type: item.type,
297 | name
298 | };
299 | });
300 | return accounts;
301 | }
302 |
303 | async fetchAccountStatement(fromArg, toArg) {
304 | const from = Math.round(Number(fromArg) / 1e3);
305 | const to = Math.round(Number(toArg ?? new Date()) / 1e3);
306 | const accountId = await this.getAccountId();
307 | const url = `${API_PATH.STATEMENT}/${accountId}/${from}/${to}`;
308 | return this.fetchData(url);
309 | }
310 |
311 | fetchTodaysStatement() {
312 | const to = new Date();
313 | const from = new Date();
314 | from.setHours(0, 0, 0, 0);
315 | return this.fetchAccountStatement(from, to);
316 | }
317 |
318 | async fetchLatestStatement() {
319 | const accountCacheKey = await this.getAccountCacheKey();
320 | const {
321 | currentPeriod,
322 | history = {}
323 | } = (await cache.read(accountCacheKey)) ?? {};
324 |
325 | if (!currentPeriod?.lastUpdatedAt) {
326 | return this.fetchInitialPeriod();
327 | }
328 |
329 | const {
330 | lastUpdatedAt,
331 | lastOperationId
332 | } = currentPeriod;
333 | const to = new Date();
334 | const response = await this.fetchAccountStatement(new Date(lastUpdatedAt), to);
335 |
336 | if (!response.length || response[0].id === lastOperationId) {
337 | logger.log('nothing to process, skipping...');
338 | return {
339 | currentPeriod,
340 | history
341 | };
342 | }
343 |
344 | const filter = await this.getStatementItemsFilter();
345 |
346 | for (let index = response.length - 1; index >= 0; index--) {
347 | const item = response[index];
348 |
349 | if (filter(item)) {
350 | const {
351 | id,
352 | time,
353 | amount,
354 | description,
355 | mcc,
356 | cashbackAmount
357 | } = item;
358 | const opDate = new Date(item.time * 1e3);
359 | const month = opDate.getMonth() + 1;
360 | const year = opDate.getFullYear();
361 | const date = opDate.getDate();
362 |
363 | if (month > currentPeriod.month) {
364 | history[currentPeriod.year] = history[currentPeriod.year] ?? {};
365 | history[currentPeriod.year][currentPeriod.month] = currentPeriod.total;
366 | currentPeriod.year = year;
367 | currentPeriod.month = month;
368 | currentPeriod.total = 0;
369 | currentPeriod.statement = [null];
370 | } else if (month < currentPeriod.month) {
371 | continue;
372 | }
373 |
374 | currentPeriod.statement[date] = currentPeriod.statement[date] ?? []; // @ts-ignore
375 |
376 | currentPeriod.statement[date].push({
377 | id,
378 | time,
379 | amount,
380 | description,
381 | mcc,
382 | cashbackAmount
383 | });
384 | currentPeriod.total = currentPeriod.total += -1 * amount;
385 | }
386 | }
387 |
388 | currentPeriod.lastUpdatedAt = Number(to);
389 | currentPeriod.lastOperationId = response[0].id;
390 | cache.write(accountCacheKey, {
391 | currentPeriod,
392 | history
393 | });
394 | return {
395 | currentPeriod,
396 | history
397 | };
398 | }
399 |
400 | async fetchInitialPeriod() {
401 | logger.log('fetching initial period...');
402 | const to = new Date();
403 | const from = new Date();
404 | const currentMonth = from.getMonth() + 1;
405 | const currentYear = from.getFullYear();
406 | from.setDate(1);
407 | from.setHours(0, 0, 0, 0);
408 | const response = await this.fetchAccountStatement(from, to);
409 | const filter = await this.getStatementItemsFilter();
410 | const currentPeriod = response.reduce((acc, item, index) => {
411 | if (filter(item)) {
412 | const {
413 | id,
414 | time,
415 | amount,
416 | description,
417 | mcc,
418 | cashbackAmount
419 | } = item;
420 | const date = new Date(item.time * 1e3).getDate();
421 | const currentValue = acc.statement[date] ?? [];
422 | acc.statement[date] = [{
423 | id,
424 | time,
425 | amount,
426 | description,
427 | mcc,
428 | cashbackAmount
429 | }, ...currentValue];
430 | acc.total += -1 * amount;
431 |
432 | if (!index) {
433 | acc.lastOperationId = id;
434 | }
435 | }
436 |
437 | return acc;
438 | }, {
439 | month: currentMonth,
440 | year: currentYear,
441 | total: 0,
442 | statement: [],
443 | lastOperationId: '',
444 | lastUpdatedAt: Number(to)
445 | });
446 | const statement = {
447 | history: {},
448 | currentPeriod
449 | };
450 | const accountCacheKey = await this.getAccountCacheKey();
451 | await cache.write(accountCacheKey, statement);
452 | return statement;
453 | }
454 |
455 | async editOP({
456 | date,
457 | day,
458 | amount: editAmount
459 | }, parentScriptName) {
460 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName);
461 | const statement = await cache.read(accountCacheKey);
462 | const item = statement.currentPeriod.statement?.[date]?.[day];
463 |
464 | if (item == null) {
465 | throw new Error(`OP not found: ${date}-${day}`);
466 | }
467 |
468 | if (editAmount === null) {
469 | item.amount = item.originalAmount ?? item.amount;
470 | item.originalAmount = undefined;
471 | } else {
472 | item.originalAmount = item.amount;
473 | item.amount = item.amount + editAmount;
474 | }
475 |
476 | const nextTotal = statement.currentPeriod.statement.reduce((total, day) => {
477 | return day ? total + day.reduce((acc, item) => item ? -1 * item.amount + acc : acc, 0) : total;
478 | }, 0);
479 | statement.currentPeriod.total = nextTotal;
480 | await cache.write(accountCacheKey, statement);
481 | await createAlert('Successfuly updated', '', 'Ok').present();
482 | }
483 |
484 | async editStatementItem(parentScriptName) {
485 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName);
486 | const {
487 | currentPeriod: {
488 | statement
489 | }
490 | } = await cache.read(accountCacheKey);
491 | const editAlert = createAlert('Select operation to edit', '', 'Cancel');
492 | const actions = [];
493 |
494 | for (let dateIndex = statement.length - 1; dateIndex > 0; dateIndex--) {
495 | const date = statement[dateIndex];
496 |
497 | if (date) {
498 | for (let opIndex = date.length - 1; opIndex >= 0; opIndex--) {
499 | const opItem = date[opIndex];
500 |
501 | if (!opItem) {
502 | continue;
503 | }
504 |
505 | const {
506 | amount,
507 | description,
508 | originalAmount
509 | } = opItem;
510 | const summary = `${amount / 100}${CURRENCIES[980].sign}${originalAmount ? '[E]' : ''}: "${description.replace('\n', '_')}"`;
511 | editAlert.addAction(summary);
512 | actions.push({
513 | summary,
514 | dateIndex,
515 | opIndex,
516 | originalAmount,
517 | amount
518 | });
519 | }
520 | }
521 | }
522 |
523 | const opActionIndex = await editAlert.presentSheet();
524 |
525 | if (opActionIndex === -1) {
526 | return;
527 | }
528 |
529 | let editActionIndex = -1;
530 | let amount = 0;
531 | const {
532 | originalAmount,
533 | amount: currentAmount
534 | } = actions[opActionIndex];
535 | const valueAlert = createAlert('Edit operation', `Current amount is: ${currentAmount / 1e2}.\nEnter either positive or negative value to add:`, 'Cancel');
536 | valueAlert.addTextField('Amount:');
537 | valueAlert.addAction('Submit');
538 |
539 | if (originalAmount) {
540 | valueAlert.addDestructiveAction('Restore this operation');
541 | } else {
542 | valueAlert.addDestructiveAction('Delete this operation');
543 | }
544 |
545 | const valueActionIndex = await valueAlert.present();
546 | editActionIndex = valueActionIndex;
547 |
548 | switch (valueActionIndex) {
549 | case -1:
550 | break;
551 |
552 | case 0:
553 | amount = Number(valueAlert.textFieldValue(0)) * 1e2;
554 | break;
555 |
556 | case 1:
557 | amount = originalAmount ? null : -1 * currentAmount;
558 | break;
559 | }
560 |
561 | if (editActionIndex === -1) {
562 | return;
563 | }
564 |
565 | const {
566 | dateIndex,
567 | opIndex
568 | } = actions[opActionIndex];
569 | const actionSummary = amount === null ? `restored to ${(originalAmount ?? 0) / 1e2}` : `edited for ${amount / 1e2}.\nNew value: ${currentAmount / 1e2 + amount / 1e2}`;
570 | const confirmationAlert = createAlert('Are you sure?', `${actions[opActionIndex].summary} will be ${actionSummary}.`, 'Cancel');
571 | confirmationAlert.addDestructiveAction('Submit');
572 | const confirmationIndex = await confirmationAlert.presentSheet();
573 |
574 | if (confirmationIndex === -1) {
575 | return;
576 | }
577 |
578 | await this.editOP({
579 | date: dateIndex,
580 | day: opIndex,
581 | amount
582 | }, parentScriptName);
583 | }
584 |
585 | async setup() {
586 | if (!this.parentScriptName) {
587 | return;
588 | }
589 |
590 | if (!this.isTokenProvided()) {
591 | await this.setupToken();
592 | }
593 |
594 | const settings = (await this.readSettings()) ?? {};
595 | const scriptSettings = settings[this.parentScriptName];
596 |
597 | if (!scriptSettings || !scriptSettings.accountId) {
598 | await this.setupAccount();
599 | await this.setupFilters();
600 | }
601 | }
602 |
603 | async pickScript() {
604 | if (this.parentScriptName) {
605 | return;
606 | }
607 |
608 | const settngs = await this.readSettings();
609 | const keys = Object.keys(settngs ?? {});
610 | const selectScriptAlert = createAlert('Select script to modify', '', 'Cancel');
611 |
612 | if (!keys.length) {
613 | return;
614 | }
615 |
616 | keys.forEach(item => {
617 | selectScriptAlert.addAction(item);
618 | });
619 | const keyIndex = await selectScriptAlert.presentSheet();
620 | return keys[keyIndex];
621 | }
622 |
623 | async showSettings() {
624 | const settingsAlert = createAlert('Monobank settngs', '', 'Cancel');
625 | settingsAlert.addAction('Manage auth token');
626 | settingsAlert.addAction('Change account number');
627 | settingsAlert.addAction('Set filters');
628 | settingsAlert.addAction('Edit statement');
629 | const actionIndex = await settingsAlert.present();
630 |
631 | switch (actionIndex) {
632 | case -1:
633 | break;
634 |
635 | case 0:
636 | await this.setupToken();
637 | break;
638 |
639 | case 1:
640 | {
641 | const scriptName = await this.pickScript();
642 |
643 | if (scriptName) {
644 | await this.setupAccount(scriptName);
645 | }
646 |
647 | break;
648 | }
649 |
650 | case 2:
651 | {
652 | const scriptName = await this.pickScript();
653 |
654 | if (scriptName) {
655 | await this.setupFilters(scriptName);
656 | }
657 |
658 | break;
659 | }
660 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers
661 |
662 | case 3:
663 | {
664 | const scriptName = await this.pickScript();
665 |
666 | if (scriptName) {
667 | await this.editStatementItem(scriptName);
668 | }
669 |
670 | break;
671 | }
672 | }
673 | }
674 |
675 | }
676 |
677 | exports.Monobank = Monobank;
678 |
679 | if (config.runsInApp && Script.name() === SCRIPT_NAME) {
680 | const monobank = new Monobank();
681 | monobank.showSettings();
682 | }
--------------------------------------------------------------------------------
/build/tiny-charts.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: teal; icon-glyph: chart-line;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * small chart, for now supports only area chart
8 | */
9 | 'use strict';
10 |
11 | Object.defineProperty(exports, "__esModule", {
12 | value: true
13 | });
14 | exports.TinyCharts = void 0;
15 | const SCRIPT_NAME = 'tiny-charts';
16 |
17 | class TinyCharts {
18 | constructor(width, height) {
19 | this.width = width;
20 | this.height = height;
21 | }
22 |
23 | createContext() {
24 | this.context = new DrawContext();
25 | this.context.size = new Size(this.width, this.height);
26 | this.context.opaque = false;
27 | this.context.respectScreenScale = true;
28 | this.context.setLineWidth(1);
29 | }
30 |
31 | normalizeData(data) {
32 | const maxValue = Math.max(...data);
33 | const normalized = data.map(value => value / (maxValue / 1e2));
34 | return normalized;
35 | }
36 |
37 | getCoordinates(data, index) {
38 | const y = this.height - this.height * data[index] / 100;
39 | const x = index * (this.width / (data.length - 1));
40 | return {
41 | x,
42 | y
43 | };
44 | }
45 |
46 | setFillColor(color) {
47 | this.fillColor = color;
48 | }
49 |
50 | setStrokeColor(color) {
51 | this.strokeColor = color;
52 | }
53 |
54 | drawAreaChart(rawData) {
55 | this.createContext();
56 | const data = this.normalizeData(rawData);
57 | const startingPoint = this.getCoordinates(data, 0);
58 | const path = new Path();
59 | path.move(new Point(startingPoint.x, startingPoint.y));
60 |
61 | for (let index = 0; index < data.length - 1; index++) {
62 | const point = this.getCoordinates(data, index);
63 | const nextPoint = this.getCoordinates(data, index + 1);
64 | const pointX = (point.x + nextPoint.x) / 2;
65 | const pointY = (point.y + nextPoint.y) / 2;
66 | const controlx1 = (pointX + point.x) / 2;
67 | const controlx2 = (pointX + nextPoint.x) / 2;
68 | path.addQuadCurve(new Point(pointX, pointY), new Point(controlx1, point.y));
69 | path.addQuadCurve(new Point(nextPoint.x, nextPoint.y), new Point(controlx2, nextPoint.y));
70 | }
71 |
72 | path.addLine(new Point(this.width, this.height));
73 | path.addLine(new Point(0, this.height));
74 | path.closeSubpath();
75 |
76 | if (this.strokeColor) {
77 | this.context.setStrokeColor(this.strokeColor);
78 | this.context.addPath(path);
79 | this.context.strokePath();
80 | }
81 |
82 | if (this.fillColor) {
83 | this.context.setFillColor(this.fillColor);
84 | this.context.addPath(path);
85 | this.context.fillPath();
86 | }
87 | }
88 |
89 | getContext() {
90 | return this.context;
91 | }
92 |
93 | getImage() {
94 | return this.context.getImage();
95 | }
96 |
97 | }
98 |
99 | exports.TinyCharts = TinyCharts;
100 | Script.complete();
--------------------------------------------------------------------------------
/build/tiny-dashboard.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: blue; icon-glyph: columns;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * small widget layout with graph
8 | */
9 | 'use strict';
10 |
11 | Object.defineProperty(exports, "__esModule", {
12 | value: true
13 | });
14 | exports.PALETTES = void 0;
15 | exports.createWidget = createWidget;
16 | const {
17 | getDynamicGradient,
18 | getDynamicColor,
19 | parseWidgetParams,
20 | getDeviceAppearance
21 | } = importModule("./utils");
22 | const {
23 | TinyCharts
24 | } = importModule("./tiny-charts");
25 | const SCRIPT_NAME = 'tiny-dashboard';
26 | const GAP = 10;
27 | const SYMBOL_SIZE = 16;
28 | const GRAPH_OPACITY = 0.3;
29 | const GRAPH_WIDTH = 510;
30 | const GRAPH_HEIGHT = 150;
31 | /*
32 | * most of the colors taken from https://uigradients.com/
33 | */
34 |
35 | const PALETTES = {
36 | // darker ? palettes
37 | seablue: {
38 | bgStart: '#2b5876',
39 | bgEnd: '#4e4376',
40 | primary: '#ece9e6',
41 | secondary: '#7d7795'
42 | },
43 | cloud: {
44 | bgStart: '#141E30',
45 | bgEnd: '#243B55',
46 | primary: '#d0d0da',
47 | secondary: '#141E30'
48 | },
49 | midnight: {
50 | bgStart: '#232526',
51 | bgEnd: '#414345',
52 | primary: '#ece9e6',
53 | secondary: '#232526'
54 | },
55 | // lighter palettes, kinda. I am bad with colors
56 | royal: {
57 | bgStart: '#ece9e6',
58 | bgEnd: '#ffffff',
59 | primary: '#141E30',
60 | secondary: '#c2bbb5'
61 | },
62 | dull: {
63 | bgStart: '#c9d6ff',
64 | bgEnd: '#e2e2e2',
65 | primary: '#000c40',
66 | secondary: '#99b1fe'
67 | },
68 | anamnisar: {
69 | bgStart: '#9796f0',
70 | bgEnd: '#fbc7d4',
71 | primary: '#1D2B64',
72 | secondary: '#9796f0'
73 | },
74 | ash: {
75 | bgStart: '#606c88',
76 | bgEnd: '#3f4c6b',
77 | primary: '#c9d6ff',
78 | secondary: '#c9d6ff'
79 | },
80 | // other color presets
81 | pacific: {
82 | bgStart: '#0f3443',
83 | bgEnd: '#34e89e',
84 | primary: '#BDFFF3',
85 | secondary: '#0f3443'
86 | },
87 | sin: {
88 | bgStart: '#93291E',
89 | bgEnd: '#ED213A',
90 | primary: '#340707',
91 | secondary: '#333333'
92 | },
93 | sandblue: {
94 | bgStart: '#DECBA4',
95 | bgEnd: '#3E5151',
96 | primary: '#243737',
97 | secondary: '#3E5151'
98 | },
99 | // nord colors taken from https://www.nordtheme.com
100 | nord: {
101 | bgStart: '#2E3440',
102 | bgEnd: '#2E3440',
103 | primary: '#81a1c1',
104 | accent: '#d8dee9',
105 | secondary: '#d8dee9'
106 | },
107 | nordlight: {
108 | bgStart: '#d8dee9',
109 | bgEnd: '#d8dee9',
110 | primary: '#4c566a',
111 | accent: '#5e81ac',
112 | secondary: '#81a1c1'
113 | }
114 | };
115 | exports.PALETTES = PALETTES;
116 | const TYPOGRAPHY = {
117 | title: 40,
118 | subtitle: 15,
119 | body: 10,
120 | caption: 8
121 | };
122 |
123 | function getPalette(palette = {}) {
124 | const {
125 | light,
126 | dark
127 | } = parseWidgetParams(args.widgetParameter);
128 | return {
129 | light: PALETTES[palette.light ?? light] ?? PALETTES.dull,
130 | dark: PALETTES[palette.dark ?? dark] ?? PALETTES.sandblue
131 | };
132 | }
133 |
134 | function createWidget(args, theme) {
135 | const {
136 | value,
137 | chartData,
138 | subtitle1,
139 | subtitle2,
140 | headerSymbol: headerSymbolProp,
141 | header,
142 | subValue
143 | } = args;
144 | const appearence = getDeviceAppearance();
145 | const palette = getPalette(theme);
146 | const listWidget = new ListWidget();
147 | const textColor = getDynamicColor(palette, 'primary');
148 | const titleColor = getDynamicColor(palette, 'accent');
149 | const gradient = getDynamicGradient(palette);
150 | const opacity = palette[appearence].bgStart === palette[appearence].bgEnd ? 1 : GRAPH_OPACITY;
151 | const fillColor = getDynamicColor(palette, 'secondary', opacity);
152 | listWidget.setPadding(GAP, 0, 0, 0);
153 | gradient.locations = [0.0, 1];
154 | listWidget.backgroundGradient = gradient; // HEADER
155 |
156 | const headerStack = listWidget.addStack();
157 | const headerText = headerStack.addText(header);
158 | headerStack.setPadding(0, GAP, 0, GAP);
159 | headerText.textColor = textColor;
160 | headerText.font = Font.regularSystemFont(TYPOGRAPHY.body);
161 | headerText.minimumScaleFactor = 0.50;
162 |
163 | if (headerSymbolProp) {
164 | const headerSymbol = SFSymbol.named(headerSymbolProp);
165 | headerSymbol.applyFont(Font.systemFont(SYMBOL_SIZE));
166 | headerSymbol.applySemiboldWeight;
167 | headerStack.addSpacer();
168 | const currencySymbolImg = headerStack.addImage(headerSymbol.image);
169 | currencySymbolImg.resizable = false;
170 | currencySymbolImg.tintColor = textColor;
171 | currencySymbolImg.imageSize = new Size(SYMBOL_SIZE, SYMBOL_SIZE);
172 | }
173 |
174 | listWidget.addSpacer(); // VALUE
175 |
176 | const valueStack = listWidget.addStack();
177 | valueStack.setPadding(0, GAP, 1, GAP);
178 | valueStack.bottomAlignContent();
179 | valueStack.spacing = 2;
180 | const valueText = valueStack.addText(value);
181 | valueText.textColor = titleColor;
182 | valueText.font = Font.boldSystemFont(TYPOGRAPHY.title);
183 | valueText.minimumScaleFactor = 0.50;
184 |
185 | if (subValue) {
186 | const subValueStack = valueStack.addStack();
187 | const subValueText = subValueStack.addText(subValue); // hack to proper align text
188 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers
189 |
190 | subValueStack.setPadding(0, 0, 6, 0);
191 | subValueText.textColor = titleColor;
192 | subValueText.font = Font.boldSystemFont(TYPOGRAPHY.body);
193 | subValueText.minimumScaleFactor = 0.50;
194 | } // FOOTER
195 |
196 |
197 | const footerStack = listWidget.addStack();
198 | const footerStackBottomPadding = (2 - [subtitle1, subtitle2].reduce((acc, item) => acc + (item ? 1 : 0), 0)) * GAP;
199 | footerStack.setPadding(0, GAP, footerStackBottomPadding, 0);
200 | footerStack.layoutVertically();
201 | footerStack.spacing = 2;
202 |
203 | if (subtitle1) {
204 | const subtitle1Text = footerStack.addText(subtitle1);
205 | subtitle1Text.textColor = textColor;
206 | subtitle1Text.font = Font.lightSystemFont(TYPOGRAPHY.caption);
207 | }
208 |
209 | if (subtitle2) {
210 | const subtitle2Text = footerStack.addText(subtitle2);
211 | subtitle2Text.textColor = textColor;
212 | subtitle2Text.font = Font.lightSystemFont(TYPOGRAPHY.caption);
213 | }
214 |
215 | listWidget.addSpacer(GAP / 2); // GRAPH
216 |
217 | const graphStack = listWidget.addStack();
218 | graphStack.setPadding(0, 0, 0, 0);
219 | graphStack.backgroundColor = Color.clear();
220 |
221 | if (chartData) {
222 | const chart = new TinyCharts(GRAPH_WIDTH, GRAPH_HEIGHT);
223 | chart.setFillColor(fillColor);
224 | chart.drawAreaChart(chartData);
225 | graphStack.addImage(chart.getImage());
226 | } else {
227 | listWidget.addSpacer(GAP);
228 | listWidget.addSpacer();
229 | }
230 |
231 | return listWidget;
232 | }
233 |
234 | Script.complete();
--------------------------------------------------------------------------------
/build/update-code.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: red; icon-glyph: check-circle;
4 | 'use strict';
5 |
6 | Object.defineProperty(exports, "__esModule", {
7 | value: true
8 | });
9 | const SCRIPTS = ['utils', 'tiny-dashboard', 'tiny-charts', 'mono-monthly-small', 'update-code', 'monobank'];
10 | const URL = 'https://raw.githubusercontent.com/Nodman/scriptables/main/build'; // taken from Max Zeryck blur script
11 |
12 | const updateCode = async scriptName => {
13 | const alert = new Alert();
14 | const moduleName = `${scriptName}.js`;
15 | const url = `${URL}/${moduleName}`;
16 | const path = [...module.filename.split('/').slice(0, -1), moduleName].join('/');
17 | alert.title = `Update "${moduleName}" code?`;
18 | alert.message = 'This will overwrite any of your local changes!';
19 | alert.addCancelAction('Nope');
20 | alert.addDestructiveAction('Yesh');
21 | const actionIndex = await alert.present();
22 |
23 | if (actionIndex === -1) {
24 | return;
25 | } // Determine if the user is using iCloud.
26 |
27 |
28 | let files = FileManager.local();
29 | const iCloudInUse = files.isFileStoredIniCloud(path); // If so, use an iCloud file manager.
30 |
31 | files = iCloudInUse ? FileManager.iCloud() : files;
32 | let message = ''; // Try to download the file.
33 |
34 | try {
35 | const req = new Request(url);
36 | const codeString = await req.loadString();
37 | files.writeString(path, codeString);
38 | message = 'The code has been updated. If the script is open, close it for the change to take effect.';
39 | } catch {
40 | message = 'The update failed. Please try again later.';
41 | }
42 |
43 | const confirmAlert = new Alert();
44 | confirmAlert.title = 'Update code';
45 | confirmAlert.message = message;
46 | confirmAlert.addCancelAction('Ok');
47 | await confirmAlert.present();
48 | };
49 |
50 | const selectScriptAlert = new Alert();
51 | selectScriptAlert.title = 'Select script to update';
52 | selectScriptAlert.addCancelAction('Cancel');
53 | SCRIPTS.forEach(item => {
54 | selectScriptAlert.addAction(item);
55 | });
56 | const actionIndex = await selectScriptAlert.presentSheet();
57 |
58 | if (actionIndex !== -1) {
59 | await updateCode(SCRIPTS[actionIndex]);
60 | }
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-purple; icon-glyph: wrench;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * few common utils I use across scriptable widgets
8 | */
9 | 'use strict';
10 |
11 | Object.defineProperty(exports, "__esModule", {
12 | value: true
13 | });
14 | exports.currencyFormatter = exports.createAlert = exports.Logger = void 0;
15 | exports.fetchJson = fetchJson;
16 | exports.sendNotification = exports.sendErrorNotification = exports.parseWidgetParams = exports.numberFormatterShort = exports.getDynamicGradient = exports.getDynamicColor = exports.getDeviceAppearance = exports.getDaysInMonth = void 0;
17 | const SCRIPT_NAME = 'utils';
18 | const DEBUG = true;
19 | const ERROR_NOTIFICATION_ID = '_error';
20 |
21 | class Logger {
22 | constructor(prefix) {
23 | this.prefix = prefix ?? '';
24 | this.separator = prefix ? ': ' : '';
25 | }
26 |
27 | print(method, message) {
28 | if (!DEBUG) {
29 | return;
30 | } // eslint-disable-next-line no-console
31 |
32 |
33 | console[method](`${this.prefix}${this.separator}${message}`);
34 | }
35 |
36 | log(message) {
37 | this.print('log', message);
38 | }
39 |
40 | warn(message) {
41 | this.print('warn', message);
42 | }
43 |
44 | error(message) {
45 | this.print('error', message);
46 | }
47 |
48 | }
49 |
50 | exports.Logger = Logger;
51 | const requestLogger = new Logger('Request');
52 |
53 | async function fetchJson(url, headers) {
54 | try {
55 | const req = new Request(url);
56 | req.headers = headers ?? {};
57 | requestLogger.log(url);
58 | return await req.loadJSON();
59 | } catch (error) {
60 | requestLogger.error(JSON.stringify(error));
61 | throw new Error(error);
62 | }
63 | }
64 | /**
65 | * Send notification
66 | *
67 | * @param {string} title Notification title
68 | * @param {string} message Notification body
69 | * @param {string} id Notification id
70 | * @param {*} userInfo Notification userInfo object
71 | * @param {Date} date Schedule date, if not provided - will be scheduled asap
72 | * @param {*} actions Array of actions, [[title, action]]
73 | * @param {string} scriptName Explicit scriptName
74 | * @param {string} openURL url to open when click on notification
75 | */
76 |
77 |
78 | const sendNotification = ({
79 | title,
80 | message,
81 | id = 'notification',
82 | userInfo,
83 | date,
84 | actions = [],
85 | scriptName,
86 | openURL
87 | }) => {
88 | const notification = new Notification();
89 | notification.title = title;
90 | notification.identifier = `${Script.name()}${id}`;
91 | notification.threadIdentifier = `${Script.name()}${id}_thread`;
92 |
93 | if (userInfo) {
94 | notification.userInfo = {
95 | error: true
96 | };
97 | }
98 |
99 | if (date) {
100 | notification.setTriggerDate(date);
101 | }
102 |
103 | if (openURL) {
104 | notification.openURL = openURL;
105 | }
106 |
107 | actions.forEach(action => {
108 | notification.addAction(...action);
109 | });
110 | notification.scriptName = scriptName || Script.name();
111 | notification.body = message;
112 | notification.schedule();
113 | };
114 |
115 | exports.sendNotification = sendNotification;
116 |
117 | const sendErrorNotification = (err, params) => {
118 | sendNotification({ ...params,
119 | title: `Something went wrong with ${Script.name()}`,
120 | id: ERROR_NOTIFICATION_ID,
121 | message: err.message
122 | });
123 | };
124 |
125 | exports.sendErrorNotification = sendErrorNotification;
126 |
127 | const createAlert = (title, message, cancelAction) => {
128 | const alert = new Alert();
129 | alert.message = message;
130 | alert.title = title;
131 |
132 | if (cancelAction) {
133 | alert.addCancelAction(cancelAction);
134 | }
135 |
136 | return alert;
137 | }; // starts from 1
138 |
139 |
140 | exports.createAlert = createAlert;
141 |
142 | const getDaysInMonth = (year, month) => {
143 | return new Date(year, month, 0).getDate();
144 | };
145 |
146 | exports.getDaysInMonth = getDaysInMonth;
147 |
148 | const getDynamicColor = ({
149 | light,
150 | dark
151 | }, key, opacity) => {
152 | const lightColor = light[key] ?? light.primary;
153 | const darkColor = dark[key] ?? dark.primary;
154 | return Color.dynamic(new Color(lightColor, opacity), new Color(darkColor, opacity));
155 | };
156 |
157 | exports.getDynamicColor = getDynamicColor;
158 |
159 | const getDynamicGradient = ({
160 | dark,
161 | light
162 | }) => {
163 | const startColor = Color.dynamic(new Color(light.bgStart), new Color(dark.bgStart));
164 | const endColor = Color.dynamic(new Color(light.bgEnd), new Color(dark.bgEnd));
165 | const gradient = new LinearGradient();
166 | gradient.colors = [startColor, endColor];
167 | return gradient;
168 | };
169 |
170 | exports.getDynamicGradient = getDynamicGradient;
171 |
172 | const parseWidgetParams = params => {
173 | if (typeof params !== 'string') {
174 | return {};
175 | }
176 |
177 | return params.split(';').reduce((acc, item) => {
178 | const [key, value = ''] = item.split('=');
179 | return { ...acc,
180 | [key.trim()]: value.trim() ?? true
181 | };
182 | }, {});
183 | };
184 |
185 | exports.parseWidgetParams = parseWidgetParams;
186 | const numberFormatterShort = new Intl.NumberFormat('ua', {
187 | notation: 'compact',
188 | compactDisplay: 'short',
189 | maximumFractionDigits: 1,
190 | minimumFractionDigits: 0
191 | }).format;
192 | exports.numberFormatterShort = numberFormatterShort;
193 | const currencyFormatter = new Intl.NumberFormat('uk-UA', {
194 | minimumFractionDigits: 2
195 | }).format;
196 | exports.currencyFormatter = currencyFormatter;
197 |
198 | const getDeviceAppearance = () => {
199 | return Device.isUsingDarkAppearance() ? 'dark' : 'light';
200 | };
201 |
202 | exports.getDeviceAppearance = getDeviceAppearance;
203 | Script.complete();
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mono-widgets",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/cli": {
8 | "version": "7.16.8",
9 | "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz",
10 | "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==",
11 | "dev": true,
12 | "requires": {
13 | "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
14 | "chokidar": "^3.4.0",
15 | "commander": "^4.0.1",
16 | "convert-source-map": "^1.1.0",
17 | "fs-readdir-recursive": "^1.1.0",
18 | "glob": "^7.0.0",
19 | "make-dir": "^2.1.0",
20 | "slash": "^2.0.0",
21 | "source-map": "^0.5.0"
22 | }
23 | },
24 | "@babel/code-frame": {
25 | "version": "7.16.7",
26 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
27 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
28 | "dev": true,
29 | "requires": {
30 | "@babel/highlight": "^7.16.7"
31 | }
32 | },
33 | "@babel/compat-data": {
34 | "version": "7.16.8",
35 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz",
36 | "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==",
37 | "dev": true
38 | },
39 | "@babel/core": {
40 | "version": "7.16.12",
41 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz",
42 | "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==",
43 | "dev": true,
44 | "requires": {
45 | "@babel/code-frame": "^7.16.7",
46 | "@babel/generator": "^7.16.8",
47 | "@babel/helper-compilation-targets": "^7.16.7",
48 | "@babel/helper-module-transforms": "^7.16.7",
49 | "@babel/helpers": "^7.16.7",
50 | "@babel/parser": "^7.16.12",
51 | "@babel/template": "^7.16.7",
52 | "@babel/traverse": "^7.16.10",
53 | "@babel/types": "^7.16.8",
54 | "convert-source-map": "^1.7.0",
55 | "debug": "^4.1.0",
56 | "gensync": "^1.0.0-beta.2",
57 | "json5": "^2.1.2",
58 | "semver": "^6.3.0",
59 | "source-map": "^0.5.0"
60 | },
61 | "dependencies": {
62 | "semver": {
63 | "version": "6.3.0",
64 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
65 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
66 | "dev": true
67 | }
68 | }
69 | },
70 | "@babel/generator": {
71 | "version": "7.16.8",
72 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
73 | "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
74 | "dev": true,
75 | "requires": {
76 | "@babel/types": "^7.16.8",
77 | "jsesc": "^2.5.1",
78 | "source-map": "^0.5.0"
79 | }
80 | },
81 | "@babel/helper-annotate-as-pure": {
82 | "version": "7.16.7",
83 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
84 | "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
85 | "dev": true,
86 | "requires": {
87 | "@babel/types": "^7.16.7"
88 | }
89 | },
90 | "@babel/helper-compilation-targets": {
91 | "version": "7.16.7",
92 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz",
93 | "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==",
94 | "dev": true,
95 | "requires": {
96 | "@babel/compat-data": "^7.16.4",
97 | "@babel/helper-validator-option": "^7.16.7",
98 | "browserslist": "^4.17.5",
99 | "semver": "^6.3.0"
100 | },
101 | "dependencies": {
102 | "semver": {
103 | "version": "6.3.0",
104 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
105 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
106 | "dev": true
107 | }
108 | }
109 | },
110 | "@babel/helper-create-class-features-plugin": {
111 | "version": "7.16.10",
112 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz",
113 | "integrity": "sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==",
114 | "dev": true,
115 | "requires": {
116 | "@babel/helper-annotate-as-pure": "^7.16.7",
117 | "@babel/helper-environment-visitor": "^7.16.7",
118 | "@babel/helper-function-name": "^7.16.7",
119 | "@babel/helper-member-expression-to-functions": "^7.16.7",
120 | "@babel/helper-optimise-call-expression": "^7.16.7",
121 | "@babel/helper-replace-supers": "^7.16.7",
122 | "@babel/helper-split-export-declaration": "^7.16.7"
123 | }
124 | },
125 | "@babel/helper-environment-visitor": {
126 | "version": "7.16.7",
127 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
128 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
129 | "dev": true,
130 | "requires": {
131 | "@babel/types": "^7.16.7"
132 | }
133 | },
134 | "@babel/helper-function-name": {
135 | "version": "7.16.7",
136 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
137 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
138 | "dev": true,
139 | "requires": {
140 | "@babel/helper-get-function-arity": "^7.16.7",
141 | "@babel/template": "^7.16.7",
142 | "@babel/types": "^7.16.7"
143 | }
144 | },
145 | "@babel/helper-get-function-arity": {
146 | "version": "7.16.7",
147 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
148 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
149 | "dev": true,
150 | "requires": {
151 | "@babel/types": "^7.16.7"
152 | }
153 | },
154 | "@babel/helper-hoist-variables": {
155 | "version": "7.16.7",
156 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
157 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
158 | "dev": true,
159 | "requires": {
160 | "@babel/types": "^7.16.7"
161 | }
162 | },
163 | "@babel/helper-member-expression-to-functions": {
164 | "version": "7.16.7",
165 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz",
166 | "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==",
167 | "dev": true,
168 | "requires": {
169 | "@babel/types": "^7.16.7"
170 | }
171 | },
172 | "@babel/helper-module-imports": {
173 | "version": "7.16.7",
174 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
175 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
176 | "dev": true,
177 | "requires": {
178 | "@babel/types": "^7.16.7"
179 | }
180 | },
181 | "@babel/helper-module-transforms": {
182 | "version": "7.16.7",
183 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
184 | "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
185 | "dev": true,
186 | "requires": {
187 | "@babel/helper-environment-visitor": "^7.16.7",
188 | "@babel/helper-module-imports": "^7.16.7",
189 | "@babel/helper-simple-access": "^7.16.7",
190 | "@babel/helper-split-export-declaration": "^7.16.7",
191 | "@babel/helper-validator-identifier": "^7.16.7",
192 | "@babel/template": "^7.16.7",
193 | "@babel/traverse": "^7.16.7",
194 | "@babel/types": "^7.16.7"
195 | }
196 | },
197 | "@babel/helper-optimise-call-expression": {
198 | "version": "7.16.7",
199 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz",
200 | "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==",
201 | "dev": true,
202 | "requires": {
203 | "@babel/types": "^7.16.7"
204 | }
205 | },
206 | "@babel/helper-plugin-utils": {
207 | "version": "7.16.7",
208 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz",
209 | "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==",
210 | "dev": true
211 | },
212 | "@babel/helper-replace-supers": {
213 | "version": "7.16.7",
214 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz",
215 | "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==",
216 | "dev": true,
217 | "requires": {
218 | "@babel/helper-environment-visitor": "^7.16.7",
219 | "@babel/helper-member-expression-to-functions": "^7.16.7",
220 | "@babel/helper-optimise-call-expression": "^7.16.7",
221 | "@babel/traverse": "^7.16.7",
222 | "@babel/types": "^7.16.7"
223 | }
224 | },
225 | "@babel/helper-simple-access": {
226 | "version": "7.16.7",
227 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz",
228 | "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==",
229 | "dev": true,
230 | "requires": {
231 | "@babel/types": "^7.16.7"
232 | }
233 | },
234 | "@babel/helper-split-export-declaration": {
235 | "version": "7.16.7",
236 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
237 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
238 | "dev": true,
239 | "requires": {
240 | "@babel/types": "^7.16.7"
241 | }
242 | },
243 | "@babel/helper-validator-identifier": {
244 | "version": "7.16.7",
245 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
246 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
247 | "dev": true
248 | },
249 | "@babel/helper-validator-option": {
250 | "version": "7.16.7",
251 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
252 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
253 | "dev": true
254 | },
255 | "@babel/helpers": {
256 | "version": "7.16.7",
257 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz",
258 | "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==",
259 | "dev": true,
260 | "requires": {
261 | "@babel/template": "^7.16.7",
262 | "@babel/traverse": "^7.16.7",
263 | "@babel/types": "^7.16.7"
264 | }
265 | },
266 | "@babel/highlight": {
267 | "version": "7.16.10",
268 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
269 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
270 | "dev": true,
271 | "requires": {
272 | "@babel/helper-validator-identifier": "^7.16.7",
273 | "chalk": "^2.0.0",
274 | "js-tokens": "^4.0.0"
275 | }
276 | },
277 | "@babel/parser": {
278 | "version": "7.16.12",
279 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
280 | "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==",
281 | "dev": true
282 | },
283 | "@babel/plugin-syntax-typescript": {
284 | "version": "7.16.7",
285 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
286 | "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
287 | "dev": true,
288 | "requires": {
289 | "@babel/helper-plugin-utils": "^7.16.7"
290 | }
291 | },
292 | "@babel/plugin-transform-modules-commonjs": {
293 | "version": "7.16.8",
294 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz",
295 | "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==",
296 | "dev": true,
297 | "requires": {
298 | "@babel/helper-module-transforms": "^7.16.7",
299 | "@babel/helper-plugin-utils": "^7.16.7",
300 | "@babel/helper-simple-access": "^7.16.7",
301 | "babel-plugin-dynamic-import-node": "^2.3.3"
302 | }
303 | },
304 | "@babel/plugin-transform-typescript": {
305 | "version": "7.16.8",
306 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
307 | "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
308 | "dev": true,
309 | "requires": {
310 | "@babel/helper-create-class-features-plugin": "^7.16.7",
311 | "@babel/helper-plugin-utils": "^7.16.7",
312 | "@babel/plugin-syntax-typescript": "^7.16.7"
313 | }
314 | },
315 | "@babel/preset-typescript": {
316 | "version": "7.16.7",
317 | "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
318 | "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
319 | "dev": true,
320 | "requires": {
321 | "@babel/helper-plugin-utils": "^7.16.7",
322 | "@babel/helper-validator-option": "^7.16.7",
323 | "@babel/plugin-transform-typescript": "^7.16.7"
324 | }
325 | },
326 | "@babel/template": {
327 | "version": "7.16.7",
328 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
329 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
330 | "dev": true,
331 | "requires": {
332 | "@babel/code-frame": "^7.16.7",
333 | "@babel/parser": "^7.16.7",
334 | "@babel/types": "^7.16.7"
335 | }
336 | },
337 | "@babel/traverse": {
338 | "version": "7.16.10",
339 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
340 | "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
341 | "dev": true,
342 | "requires": {
343 | "@babel/code-frame": "^7.16.7",
344 | "@babel/generator": "^7.16.8",
345 | "@babel/helper-environment-visitor": "^7.16.7",
346 | "@babel/helper-function-name": "^7.16.7",
347 | "@babel/helper-hoist-variables": "^7.16.7",
348 | "@babel/helper-split-export-declaration": "^7.16.7",
349 | "@babel/parser": "^7.16.10",
350 | "@babel/types": "^7.16.8",
351 | "debug": "^4.1.0",
352 | "globals": "^11.1.0"
353 | }
354 | },
355 | "@babel/types": {
356 | "version": "7.16.8",
357 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
358 | "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
359 | "dev": true,
360 | "requires": {
361 | "@babel/helper-validator-identifier": "^7.16.7",
362 | "to-fast-properties": "^2.0.0"
363 | }
364 | },
365 | "@eslint/eslintrc": {
366 | "version": "1.0.5",
367 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
368 | "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
369 | "dev": true,
370 | "requires": {
371 | "ajv": "^6.12.4",
372 | "debug": "^4.3.2",
373 | "espree": "^9.2.0",
374 | "globals": "^13.9.0",
375 | "ignore": "^4.0.6",
376 | "import-fresh": "^3.2.1",
377 | "js-yaml": "^4.1.0",
378 | "minimatch": "^3.0.4",
379 | "strip-json-comments": "^3.1.1"
380 | },
381 | "dependencies": {
382 | "globals": {
383 | "version": "13.12.0",
384 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
385 | "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
386 | "dev": true,
387 | "requires": {
388 | "type-fest": "^0.20.2"
389 | }
390 | },
391 | "ignore": {
392 | "version": "4.0.6",
393 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
394 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
395 | "dev": true
396 | }
397 | }
398 | },
399 | "@humanwhocodes/config-array": {
400 | "version": "0.9.3",
401 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz",
402 | "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==",
403 | "dev": true,
404 | "requires": {
405 | "@humanwhocodes/object-schema": "^1.2.1",
406 | "debug": "^4.1.1",
407 | "minimatch": "^3.0.4"
408 | }
409 | },
410 | "@humanwhocodes/object-schema": {
411 | "version": "1.2.1",
412 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
413 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
414 | "dev": true
415 | },
416 | "@nicolo-ribaudo/chokidar-2": {
417 | "version": "2.1.8-no-fsevents.3",
418 | "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
419 | "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
420 | "dev": true,
421 | "optional": true
422 | },
423 | "@nodelib/fs.scandir": {
424 | "version": "2.1.5",
425 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
426 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
427 | "dev": true,
428 | "requires": {
429 | "@nodelib/fs.stat": "2.0.5",
430 | "run-parallel": "^1.1.9"
431 | }
432 | },
433 | "@nodelib/fs.stat": {
434 | "version": "2.0.5",
435 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
436 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
437 | "dev": true
438 | },
439 | "@nodelib/fs.walk": {
440 | "version": "1.2.8",
441 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
442 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
443 | "dev": true,
444 | "requires": {
445 | "@nodelib/fs.scandir": "2.1.5",
446 | "fastq": "^1.6.0"
447 | }
448 | },
449 | "@scriptable-ios/eslint-config": {
450 | "version": "1.6.4",
451 | "resolved": "https://registry.npmjs.org/@scriptable-ios/eslint-config/-/eslint-config-1.6.4.tgz",
452 | "integrity": "sha512-7cCSZvL/WUyeBNsjVjYrN5Uc+tB17goEm3z2e8TUW3oWVSJ8Z3pUnllwY0HOkHaOQiZyxhuRNDLbQYimOlwh/Q==",
453 | "dev": true
454 | },
455 | "@types/json-schema": {
456 | "version": "7.0.9",
457 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
458 | "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
459 | "dev": true
460 | },
461 | "@types/scriptable-ios": {
462 | "version": "1.6.5",
463 | "resolved": "https://registry.npmjs.org/@types/scriptable-ios/-/scriptable-ios-1.6.5.tgz",
464 | "integrity": "sha512-8HvyHVIwmCdXt509c7xIvcccVnIstRMJ9DXVU5SEVjF3EJl3T9/BEF4lNjrJ+uyZPLIOacLeJ2nMgohl8NLk8A==",
465 | "dev": true
466 | },
467 | "@typescript-eslint/eslint-plugin": {
468 | "version": "5.11.0",
469 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz",
470 | "integrity": "sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw==",
471 | "dev": true,
472 | "requires": {
473 | "@typescript-eslint/scope-manager": "5.11.0",
474 | "@typescript-eslint/type-utils": "5.11.0",
475 | "@typescript-eslint/utils": "5.11.0",
476 | "debug": "^4.3.2",
477 | "functional-red-black-tree": "^1.0.1",
478 | "ignore": "^5.1.8",
479 | "regexpp": "^3.2.0",
480 | "semver": "^7.3.5",
481 | "tsutils": "^3.21.0"
482 | },
483 | "dependencies": {
484 | "semver": {
485 | "version": "7.3.5",
486 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
487 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
488 | "dev": true,
489 | "requires": {
490 | "lru-cache": "^6.0.0"
491 | }
492 | }
493 | }
494 | },
495 | "@typescript-eslint/parser": {
496 | "version": "5.11.0",
497 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz",
498 | "integrity": "sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ==",
499 | "dev": true,
500 | "requires": {
501 | "@typescript-eslint/scope-manager": "5.11.0",
502 | "@typescript-eslint/types": "5.11.0",
503 | "@typescript-eslint/typescript-estree": "5.11.0",
504 | "debug": "^4.3.2"
505 | }
506 | },
507 | "@typescript-eslint/scope-manager": {
508 | "version": "5.11.0",
509 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz",
510 | "integrity": "sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA==",
511 | "dev": true,
512 | "requires": {
513 | "@typescript-eslint/types": "5.11.0",
514 | "@typescript-eslint/visitor-keys": "5.11.0"
515 | }
516 | },
517 | "@typescript-eslint/type-utils": {
518 | "version": "5.11.0",
519 | "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz",
520 | "integrity": "sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA==",
521 | "dev": true,
522 | "requires": {
523 | "@typescript-eslint/utils": "5.11.0",
524 | "debug": "^4.3.2",
525 | "tsutils": "^3.21.0"
526 | }
527 | },
528 | "@typescript-eslint/types": {
529 | "version": "5.11.0",
530 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.11.0.tgz",
531 | "integrity": "sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ==",
532 | "dev": true
533 | },
534 | "@typescript-eslint/typescript-estree": {
535 | "version": "5.11.0",
536 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz",
537 | "integrity": "sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg==",
538 | "dev": true,
539 | "requires": {
540 | "@typescript-eslint/types": "5.11.0",
541 | "@typescript-eslint/visitor-keys": "5.11.0",
542 | "debug": "^4.3.2",
543 | "globby": "^11.0.4",
544 | "is-glob": "^4.0.3",
545 | "semver": "^7.3.5",
546 | "tsutils": "^3.21.0"
547 | },
548 | "dependencies": {
549 | "semver": {
550 | "version": "7.3.5",
551 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
552 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
553 | "dev": true,
554 | "requires": {
555 | "lru-cache": "^6.0.0"
556 | }
557 | }
558 | }
559 | },
560 | "@typescript-eslint/utils": {
561 | "version": "5.11.0",
562 | "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.11.0.tgz",
563 | "integrity": "sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw==",
564 | "dev": true,
565 | "requires": {
566 | "@types/json-schema": "^7.0.9",
567 | "@typescript-eslint/scope-manager": "5.11.0",
568 | "@typescript-eslint/types": "5.11.0",
569 | "@typescript-eslint/typescript-estree": "5.11.0",
570 | "eslint-scope": "^5.1.1",
571 | "eslint-utils": "^3.0.0"
572 | },
573 | "dependencies": {
574 | "eslint-scope": {
575 | "version": "5.1.1",
576 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
577 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
578 | "dev": true,
579 | "requires": {
580 | "esrecurse": "^4.3.0",
581 | "estraverse": "^4.1.1"
582 | }
583 | },
584 | "estraverse": {
585 | "version": "4.3.0",
586 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
587 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
588 | "dev": true
589 | }
590 | }
591 | },
592 | "@typescript-eslint/visitor-keys": {
593 | "version": "5.11.0",
594 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz",
595 | "integrity": "sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA==",
596 | "dev": true,
597 | "requires": {
598 | "@typescript-eslint/types": "5.11.0",
599 | "eslint-visitor-keys": "^3.0.0"
600 | }
601 | },
602 | "acorn": {
603 | "version": "8.7.0",
604 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
605 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
606 | "dev": true
607 | },
608 | "acorn-jsx": {
609 | "version": "5.3.2",
610 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
611 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
612 | "dev": true
613 | },
614 | "ajv": {
615 | "version": "6.12.6",
616 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
617 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
618 | "dev": true,
619 | "requires": {
620 | "fast-deep-equal": "^3.1.1",
621 | "fast-json-stable-stringify": "^2.0.0",
622 | "json-schema-traverse": "^0.4.1",
623 | "uri-js": "^4.2.2"
624 | }
625 | },
626 | "ansi-regex": {
627 | "version": "5.0.1",
628 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
629 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
630 | "dev": true
631 | },
632 | "ansi-styles": {
633 | "version": "3.2.1",
634 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
635 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
636 | "dev": true,
637 | "requires": {
638 | "color-convert": "^1.9.0"
639 | }
640 | },
641 | "anymatch": {
642 | "version": "3.1.2",
643 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
644 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
645 | "dev": true,
646 | "optional": true,
647 | "requires": {
648 | "normalize-path": "^3.0.0",
649 | "picomatch": "^2.0.4"
650 | }
651 | },
652 | "argparse": {
653 | "version": "2.0.1",
654 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
655 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
656 | "dev": true
657 | },
658 | "array-union": {
659 | "version": "2.1.0",
660 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
661 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
662 | "dev": true
663 | },
664 | "babel-plugin-dynamic-import-node": {
665 | "version": "2.3.3",
666 | "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
667 | "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
668 | "dev": true,
669 | "requires": {
670 | "object.assign": "^4.1.0"
671 | }
672 | },
673 | "balanced-match": {
674 | "version": "1.0.2",
675 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
676 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
677 | "dev": true
678 | },
679 | "binary-extensions": {
680 | "version": "2.2.0",
681 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
682 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
683 | "dev": true,
684 | "optional": true
685 | },
686 | "brace-expansion": {
687 | "version": "1.1.11",
688 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
689 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
690 | "dev": true,
691 | "requires": {
692 | "balanced-match": "^1.0.0",
693 | "concat-map": "0.0.1"
694 | }
695 | },
696 | "braces": {
697 | "version": "3.0.2",
698 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
699 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
700 | "dev": true,
701 | "requires": {
702 | "fill-range": "^7.0.1"
703 | }
704 | },
705 | "browserslist": {
706 | "version": "4.19.1",
707 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
708 | "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
709 | "dev": true,
710 | "requires": {
711 | "caniuse-lite": "^1.0.30001286",
712 | "electron-to-chromium": "^1.4.17",
713 | "escalade": "^3.1.1",
714 | "node-releases": "^2.0.1",
715 | "picocolors": "^1.0.0"
716 | }
717 | },
718 | "call-bind": {
719 | "version": "1.0.2",
720 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
721 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
722 | "dev": true,
723 | "requires": {
724 | "function-bind": "^1.1.1",
725 | "get-intrinsic": "^1.0.2"
726 | }
727 | },
728 | "callsites": {
729 | "version": "3.1.0",
730 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
731 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
732 | "dev": true
733 | },
734 | "caniuse-lite": {
735 | "version": "1.0.30001303",
736 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz",
737 | "integrity": "sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==",
738 | "dev": true
739 | },
740 | "chalk": {
741 | "version": "2.4.2",
742 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
743 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
744 | "dev": true,
745 | "requires": {
746 | "ansi-styles": "^3.2.1",
747 | "escape-string-regexp": "^1.0.5",
748 | "supports-color": "^5.3.0"
749 | }
750 | },
751 | "chokidar": {
752 | "version": "3.5.3",
753 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
754 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
755 | "dev": true,
756 | "optional": true,
757 | "requires": {
758 | "anymatch": "~3.1.2",
759 | "braces": "~3.0.2",
760 | "fsevents": "~2.3.2",
761 | "glob-parent": "~5.1.2",
762 | "is-binary-path": "~2.1.0",
763 | "is-glob": "~4.0.1",
764 | "normalize-path": "~3.0.0",
765 | "readdirp": "~3.6.0"
766 | }
767 | },
768 | "color-convert": {
769 | "version": "1.9.3",
770 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
771 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
772 | "dev": true,
773 | "requires": {
774 | "color-name": "1.1.3"
775 | }
776 | },
777 | "color-name": {
778 | "version": "1.1.3",
779 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
780 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
781 | "dev": true
782 | },
783 | "commander": {
784 | "version": "4.1.1",
785 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
786 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
787 | "dev": true
788 | },
789 | "concat-map": {
790 | "version": "0.0.1",
791 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
792 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
793 | "dev": true
794 | },
795 | "convert-source-map": {
796 | "version": "1.8.0",
797 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
798 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
799 | "dev": true,
800 | "requires": {
801 | "safe-buffer": "~5.1.1"
802 | }
803 | },
804 | "cross-spawn": {
805 | "version": "7.0.3",
806 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
807 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
808 | "dev": true,
809 | "requires": {
810 | "path-key": "^3.1.0",
811 | "shebang-command": "^2.0.0",
812 | "which": "^2.0.1"
813 | }
814 | },
815 | "debug": {
816 | "version": "4.3.3",
817 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
818 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
819 | "dev": true,
820 | "requires": {
821 | "ms": "2.1.2"
822 | }
823 | },
824 | "deep-is": {
825 | "version": "0.1.4",
826 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
827 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
828 | "dev": true
829 | },
830 | "define-properties": {
831 | "version": "1.1.3",
832 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
833 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
834 | "dev": true,
835 | "requires": {
836 | "object-keys": "^1.0.12"
837 | }
838 | },
839 | "dir-glob": {
840 | "version": "3.0.1",
841 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
842 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
843 | "dev": true,
844 | "requires": {
845 | "path-type": "^4.0.0"
846 | }
847 | },
848 | "doctrine": {
849 | "version": "3.0.0",
850 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
851 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
852 | "dev": true,
853 | "requires": {
854 | "esutils": "^2.0.2"
855 | }
856 | },
857 | "electron-to-chromium": {
858 | "version": "1.4.56",
859 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.56.tgz",
860 | "integrity": "sha512-0k/S0FQqRRpJbX7YUjwCcLZ8D42RqGKtaiq90adXBOYgTIWwLA/g3toO8k9yEpqU8iC4QyaWYYWSTBIna8WV4g==",
861 | "dev": true
862 | },
863 | "escalade": {
864 | "version": "3.1.1",
865 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
866 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
867 | "dev": true
868 | },
869 | "escape-string-regexp": {
870 | "version": "1.0.5",
871 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
872 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
873 | "dev": true
874 | },
875 | "eslint": {
876 | "version": "8.8.0",
877 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz",
878 | "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==",
879 | "dev": true,
880 | "requires": {
881 | "@eslint/eslintrc": "^1.0.5",
882 | "@humanwhocodes/config-array": "^0.9.2",
883 | "ajv": "^6.10.0",
884 | "chalk": "^4.0.0",
885 | "cross-spawn": "^7.0.2",
886 | "debug": "^4.3.2",
887 | "doctrine": "^3.0.0",
888 | "escape-string-regexp": "^4.0.0",
889 | "eslint-scope": "^7.1.0",
890 | "eslint-utils": "^3.0.0",
891 | "eslint-visitor-keys": "^3.2.0",
892 | "espree": "^9.3.0",
893 | "esquery": "^1.4.0",
894 | "esutils": "^2.0.2",
895 | "fast-deep-equal": "^3.1.3",
896 | "file-entry-cache": "^6.0.1",
897 | "functional-red-black-tree": "^1.0.1",
898 | "glob-parent": "^6.0.1",
899 | "globals": "^13.6.0",
900 | "ignore": "^5.2.0",
901 | "import-fresh": "^3.0.0",
902 | "imurmurhash": "^0.1.4",
903 | "is-glob": "^4.0.0",
904 | "js-yaml": "^4.1.0",
905 | "json-stable-stringify-without-jsonify": "^1.0.1",
906 | "levn": "^0.4.1",
907 | "lodash.merge": "^4.6.2",
908 | "minimatch": "^3.0.4",
909 | "natural-compare": "^1.4.0",
910 | "optionator": "^0.9.1",
911 | "regexpp": "^3.2.0",
912 | "strip-ansi": "^6.0.1",
913 | "strip-json-comments": "^3.1.0",
914 | "text-table": "^0.2.0",
915 | "v8-compile-cache": "^2.0.3"
916 | },
917 | "dependencies": {
918 | "ansi-styles": {
919 | "version": "4.3.0",
920 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
921 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
922 | "dev": true,
923 | "requires": {
924 | "color-convert": "^2.0.1"
925 | }
926 | },
927 | "chalk": {
928 | "version": "4.1.2",
929 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
930 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
931 | "dev": true,
932 | "requires": {
933 | "ansi-styles": "^4.1.0",
934 | "supports-color": "^7.1.0"
935 | }
936 | },
937 | "color-convert": {
938 | "version": "2.0.1",
939 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
940 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
941 | "dev": true,
942 | "requires": {
943 | "color-name": "~1.1.4"
944 | }
945 | },
946 | "color-name": {
947 | "version": "1.1.4",
948 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
949 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
950 | "dev": true
951 | },
952 | "escape-string-regexp": {
953 | "version": "4.0.0",
954 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
955 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
956 | "dev": true
957 | },
958 | "glob-parent": {
959 | "version": "6.0.2",
960 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
961 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
962 | "dev": true,
963 | "requires": {
964 | "is-glob": "^4.0.3"
965 | }
966 | },
967 | "globals": {
968 | "version": "13.12.0",
969 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
970 | "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
971 | "dev": true,
972 | "requires": {
973 | "type-fest": "^0.20.2"
974 | }
975 | },
976 | "has-flag": {
977 | "version": "4.0.0",
978 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
979 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
980 | "dev": true
981 | },
982 | "supports-color": {
983 | "version": "7.2.0",
984 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
985 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
986 | "dev": true,
987 | "requires": {
988 | "has-flag": "^4.0.0"
989 | }
990 | }
991 | }
992 | },
993 | "eslint-scope": {
994 | "version": "7.1.0",
995 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
996 | "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
997 | "dev": true,
998 | "requires": {
999 | "esrecurse": "^4.3.0",
1000 | "estraverse": "^5.2.0"
1001 | }
1002 | },
1003 | "eslint-utils": {
1004 | "version": "3.0.0",
1005 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
1006 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
1007 | "dev": true,
1008 | "requires": {
1009 | "eslint-visitor-keys": "^2.0.0"
1010 | },
1011 | "dependencies": {
1012 | "eslint-visitor-keys": {
1013 | "version": "2.1.0",
1014 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
1015 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
1016 | "dev": true
1017 | }
1018 | }
1019 | },
1020 | "eslint-visitor-keys": {
1021 | "version": "3.2.0",
1022 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz",
1023 | "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==",
1024 | "dev": true
1025 | },
1026 | "espree": {
1027 | "version": "9.3.0",
1028 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
1029 | "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==",
1030 | "dev": true,
1031 | "requires": {
1032 | "acorn": "^8.7.0",
1033 | "acorn-jsx": "^5.3.1",
1034 | "eslint-visitor-keys": "^3.1.0"
1035 | }
1036 | },
1037 | "esquery": {
1038 | "version": "1.4.0",
1039 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
1040 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
1041 | "dev": true,
1042 | "requires": {
1043 | "estraverse": "^5.1.0"
1044 | }
1045 | },
1046 | "esrecurse": {
1047 | "version": "4.3.0",
1048 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
1049 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
1050 | "dev": true,
1051 | "requires": {
1052 | "estraverse": "^5.2.0"
1053 | }
1054 | },
1055 | "estraverse": {
1056 | "version": "5.3.0",
1057 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
1058 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
1059 | "dev": true
1060 | },
1061 | "esutils": {
1062 | "version": "2.0.3",
1063 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
1064 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
1065 | "dev": true
1066 | },
1067 | "fast-deep-equal": {
1068 | "version": "3.1.3",
1069 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1070 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1071 | "dev": true
1072 | },
1073 | "fast-glob": {
1074 | "version": "3.2.11",
1075 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
1076 | "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
1077 | "dev": true,
1078 | "requires": {
1079 | "@nodelib/fs.stat": "^2.0.2",
1080 | "@nodelib/fs.walk": "^1.2.3",
1081 | "glob-parent": "^5.1.2",
1082 | "merge2": "^1.3.0",
1083 | "micromatch": "^4.0.4"
1084 | }
1085 | },
1086 | "fast-json-stable-stringify": {
1087 | "version": "2.1.0",
1088 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
1089 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
1090 | "dev": true
1091 | },
1092 | "fast-levenshtein": {
1093 | "version": "2.0.6",
1094 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
1095 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
1096 | "dev": true
1097 | },
1098 | "fastq": {
1099 | "version": "1.13.0",
1100 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
1101 | "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
1102 | "dev": true,
1103 | "requires": {
1104 | "reusify": "^1.0.4"
1105 | }
1106 | },
1107 | "file-entry-cache": {
1108 | "version": "6.0.1",
1109 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
1110 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
1111 | "dev": true,
1112 | "requires": {
1113 | "flat-cache": "^3.0.4"
1114 | }
1115 | },
1116 | "fill-range": {
1117 | "version": "7.0.1",
1118 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
1119 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
1120 | "dev": true,
1121 | "requires": {
1122 | "to-regex-range": "^5.0.1"
1123 | }
1124 | },
1125 | "flat-cache": {
1126 | "version": "3.0.4",
1127 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
1128 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
1129 | "dev": true,
1130 | "requires": {
1131 | "flatted": "^3.1.0",
1132 | "rimraf": "^3.0.2"
1133 | }
1134 | },
1135 | "flatted": {
1136 | "version": "3.2.5",
1137 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
1138 | "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
1139 | "dev": true
1140 | },
1141 | "fs-readdir-recursive": {
1142 | "version": "1.1.0",
1143 | "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
1144 | "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
1145 | "dev": true
1146 | },
1147 | "fs.realpath": {
1148 | "version": "1.0.0",
1149 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1150 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
1151 | "dev": true
1152 | },
1153 | "fsevents": {
1154 | "version": "2.3.2",
1155 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
1156 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
1157 | "dev": true,
1158 | "optional": true
1159 | },
1160 | "function-bind": {
1161 | "version": "1.1.1",
1162 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
1163 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
1164 | "dev": true
1165 | },
1166 | "functional-red-black-tree": {
1167 | "version": "1.0.1",
1168 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
1169 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
1170 | "dev": true
1171 | },
1172 | "gensync": {
1173 | "version": "1.0.0-beta.2",
1174 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1175 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1176 | "dev": true
1177 | },
1178 | "get-intrinsic": {
1179 | "version": "1.1.1",
1180 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
1181 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
1182 | "dev": true,
1183 | "requires": {
1184 | "function-bind": "^1.1.1",
1185 | "has": "^1.0.3",
1186 | "has-symbols": "^1.0.1"
1187 | }
1188 | },
1189 | "glob": {
1190 | "version": "7.2.0",
1191 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
1192 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
1193 | "dev": true,
1194 | "requires": {
1195 | "fs.realpath": "^1.0.0",
1196 | "inflight": "^1.0.4",
1197 | "inherits": "2",
1198 | "minimatch": "^3.0.4",
1199 | "once": "^1.3.0",
1200 | "path-is-absolute": "^1.0.0"
1201 | }
1202 | },
1203 | "glob-parent": {
1204 | "version": "5.1.2",
1205 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1206 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1207 | "dev": true,
1208 | "requires": {
1209 | "is-glob": "^4.0.1"
1210 | }
1211 | },
1212 | "globals": {
1213 | "version": "11.12.0",
1214 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
1215 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
1216 | "dev": true
1217 | },
1218 | "globby": {
1219 | "version": "11.1.0",
1220 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
1221 | "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
1222 | "dev": true,
1223 | "requires": {
1224 | "array-union": "^2.1.0",
1225 | "dir-glob": "^3.0.1",
1226 | "fast-glob": "^3.2.9",
1227 | "ignore": "^5.2.0",
1228 | "merge2": "^1.4.1",
1229 | "slash": "^3.0.0"
1230 | },
1231 | "dependencies": {
1232 | "slash": {
1233 | "version": "3.0.0",
1234 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
1235 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
1236 | "dev": true
1237 | }
1238 | }
1239 | },
1240 | "has": {
1241 | "version": "1.0.3",
1242 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
1243 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
1244 | "dev": true,
1245 | "requires": {
1246 | "function-bind": "^1.1.1"
1247 | }
1248 | },
1249 | "has-flag": {
1250 | "version": "3.0.0",
1251 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
1252 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
1253 | "dev": true
1254 | },
1255 | "has-symbols": {
1256 | "version": "1.0.2",
1257 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
1258 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
1259 | "dev": true
1260 | },
1261 | "ignore": {
1262 | "version": "5.2.0",
1263 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
1264 | "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
1265 | "dev": true
1266 | },
1267 | "import-fresh": {
1268 | "version": "3.3.0",
1269 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
1270 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
1271 | "dev": true,
1272 | "requires": {
1273 | "parent-module": "^1.0.0",
1274 | "resolve-from": "^4.0.0"
1275 | }
1276 | },
1277 | "imurmurhash": {
1278 | "version": "0.1.4",
1279 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
1280 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
1281 | "dev": true
1282 | },
1283 | "inflight": {
1284 | "version": "1.0.6",
1285 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1286 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
1287 | "dev": true,
1288 | "requires": {
1289 | "once": "^1.3.0",
1290 | "wrappy": "1"
1291 | }
1292 | },
1293 | "inherits": {
1294 | "version": "2.0.4",
1295 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1296 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1297 | "dev": true
1298 | },
1299 | "is-binary-path": {
1300 | "version": "2.1.0",
1301 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1302 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1303 | "dev": true,
1304 | "optional": true,
1305 | "requires": {
1306 | "binary-extensions": "^2.0.0"
1307 | }
1308 | },
1309 | "is-extglob": {
1310 | "version": "2.1.1",
1311 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1312 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
1313 | "dev": true
1314 | },
1315 | "is-glob": {
1316 | "version": "4.0.3",
1317 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1318 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1319 | "dev": true,
1320 | "requires": {
1321 | "is-extglob": "^2.1.1"
1322 | }
1323 | },
1324 | "is-number": {
1325 | "version": "7.0.0",
1326 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1327 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1328 | "dev": true
1329 | },
1330 | "isexe": {
1331 | "version": "2.0.0",
1332 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1333 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
1334 | "dev": true
1335 | },
1336 | "js-tokens": {
1337 | "version": "4.0.0",
1338 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1339 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1340 | "dev": true
1341 | },
1342 | "js-yaml": {
1343 | "version": "4.1.0",
1344 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
1345 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
1346 | "dev": true,
1347 | "requires": {
1348 | "argparse": "^2.0.1"
1349 | }
1350 | },
1351 | "jsesc": {
1352 | "version": "2.5.2",
1353 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
1354 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
1355 | "dev": true
1356 | },
1357 | "json-schema-traverse": {
1358 | "version": "0.4.1",
1359 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
1360 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
1361 | "dev": true
1362 | },
1363 | "json-stable-stringify-without-jsonify": {
1364 | "version": "1.0.1",
1365 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
1366 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
1367 | "dev": true
1368 | },
1369 | "json5": {
1370 | "version": "2.2.0",
1371 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
1372 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
1373 | "dev": true,
1374 | "requires": {
1375 | "minimist": "^1.2.5"
1376 | }
1377 | },
1378 | "levn": {
1379 | "version": "0.4.1",
1380 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
1381 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
1382 | "dev": true,
1383 | "requires": {
1384 | "prelude-ls": "^1.2.1",
1385 | "type-check": "~0.4.0"
1386 | }
1387 | },
1388 | "lodash.merge": {
1389 | "version": "4.6.2",
1390 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
1391 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
1392 | "dev": true
1393 | },
1394 | "lru-cache": {
1395 | "version": "6.0.0",
1396 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
1397 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
1398 | "dev": true,
1399 | "requires": {
1400 | "yallist": "^4.0.0"
1401 | }
1402 | },
1403 | "make-dir": {
1404 | "version": "2.1.0",
1405 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
1406 | "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
1407 | "dev": true,
1408 | "requires": {
1409 | "pify": "^4.0.1",
1410 | "semver": "^5.6.0"
1411 | }
1412 | },
1413 | "merge2": {
1414 | "version": "1.4.1",
1415 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1416 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1417 | "dev": true
1418 | },
1419 | "micromatch": {
1420 | "version": "4.0.4",
1421 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
1422 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
1423 | "dev": true,
1424 | "requires": {
1425 | "braces": "^3.0.1",
1426 | "picomatch": "^2.2.3"
1427 | }
1428 | },
1429 | "minimatch": {
1430 | "version": "3.0.4",
1431 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
1432 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
1433 | "dev": true,
1434 | "requires": {
1435 | "brace-expansion": "^1.1.7"
1436 | }
1437 | },
1438 | "minimist": {
1439 | "version": "1.2.5",
1440 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
1441 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
1442 | "dev": true
1443 | },
1444 | "ms": {
1445 | "version": "2.1.2",
1446 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1447 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1448 | "dev": true
1449 | },
1450 | "natural-compare": {
1451 | "version": "1.4.0",
1452 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
1453 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
1454 | "dev": true
1455 | },
1456 | "node-releases": {
1457 | "version": "2.0.1",
1458 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
1459 | "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
1460 | "dev": true
1461 | },
1462 | "normalize-path": {
1463 | "version": "3.0.0",
1464 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1465 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1466 | "dev": true,
1467 | "optional": true
1468 | },
1469 | "object-keys": {
1470 | "version": "1.1.1",
1471 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
1472 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
1473 | "dev": true
1474 | },
1475 | "object.assign": {
1476 | "version": "4.1.2",
1477 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
1478 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
1479 | "dev": true,
1480 | "requires": {
1481 | "call-bind": "^1.0.0",
1482 | "define-properties": "^1.1.3",
1483 | "has-symbols": "^1.0.1",
1484 | "object-keys": "^1.1.1"
1485 | }
1486 | },
1487 | "once": {
1488 | "version": "1.4.0",
1489 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1490 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1491 | "dev": true,
1492 | "requires": {
1493 | "wrappy": "1"
1494 | }
1495 | },
1496 | "optionator": {
1497 | "version": "0.9.1",
1498 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
1499 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
1500 | "dev": true,
1501 | "requires": {
1502 | "deep-is": "^0.1.3",
1503 | "fast-levenshtein": "^2.0.6",
1504 | "levn": "^0.4.1",
1505 | "prelude-ls": "^1.2.1",
1506 | "type-check": "^0.4.0",
1507 | "word-wrap": "^1.2.3"
1508 | }
1509 | },
1510 | "parent-module": {
1511 | "version": "1.0.1",
1512 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
1513 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
1514 | "dev": true,
1515 | "requires": {
1516 | "callsites": "^3.0.0"
1517 | }
1518 | },
1519 | "path-is-absolute": {
1520 | "version": "1.0.1",
1521 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1522 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1523 | "dev": true
1524 | },
1525 | "path-key": {
1526 | "version": "3.1.1",
1527 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1528 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1529 | "dev": true
1530 | },
1531 | "path-type": {
1532 | "version": "4.0.0",
1533 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
1534 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
1535 | "dev": true
1536 | },
1537 | "picocolors": {
1538 | "version": "1.0.0",
1539 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
1540 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
1541 | "dev": true
1542 | },
1543 | "picomatch": {
1544 | "version": "2.3.1",
1545 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1546 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1547 | "dev": true
1548 | },
1549 | "pify": {
1550 | "version": "4.0.1",
1551 | "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
1552 | "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
1553 | "dev": true
1554 | },
1555 | "prelude-ls": {
1556 | "version": "1.2.1",
1557 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
1558 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
1559 | "dev": true
1560 | },
1561 | "punycode": {
1562 | "version": "2.1.1",
1563 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1564 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
1565 | "dev": true
1566 | },
1567 | "queue-microtask": {
1568 | "version": "1.2.3",
1569 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1570 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1571 | "dev": true
1572 | },
1573 | "readdirp": {
1574 | "version": "3.6.0",
1575 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1576 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1577 | "dev": true,
1578 | "optional": true,
1579 | "requires": {
1580 | "picomatch": "^2.2.1"
1581 | }
1582 | },
1583 | "regexpp": {
1584 | "version": "3.2.0",
1585 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
1586 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
1587 | "dev": true
1588 | },
1589 | "resolve-from": {
1590 | "version": "4.0.0",
1591 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1592 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1593 | "dev": true
1594 | },
1595 | "reusify": {
1596 | "version": "1.0.4",
1597 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1598 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1599 | "dev": true
1600 | },
1601 | "rimraf": {
1602 | "version": "3.0.2",
1603 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
1604 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
1605 | "dev": true,
1606 | "requires": {
1607 | "glob": "^7.1.3"
1608 | }
1609 | },
1610 | "run-parallel": {
1611 | "version": "1.2.0",
1612 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1613 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1614 | "dev": true,
1615 | "requires": {
1616 | "queue-microtask": "^1.2.2"
1617 | }
1618 | },
1619 | "safe-buffer": {
1620 | "version": "5.1.2",
1621 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1622 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1623 | "dev": true
1624 | },
1625 | "semver": {
1626 | "version": "5.7.1",
1627 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1628 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
1629 | "dev": true
1630 | },
1631 | "shebang-command": {
1632 | "version": "2.0.0",
1633 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1634 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1635 | "dev": true,
1636 | "requires": {
1637 | "shebang-regex": "^3.0.0"
1638 | }
1639 | },
1640 | "shebang-regex": {
1641 | "version": "3.0.0",
1642 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1643 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1644 | "dev": true
1645 | },
1646 | "slash": {
1647 | "version": "2.0.0",
1648 | "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
1649 | "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
1650 | "dev": true
1651 | },
1652 | "source-map": {
1653 | "version": "0.5.7",
1654 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
1655 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
1656 | "dev": true
1657 | },
1658 | "strip-ansi": {
1659 | "version": "6.0.1",
1660 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1661 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1662 | "dev": true,
1663 | "requires": {
1664 | "ansi-regex": "^5.0.1"
1665 | }
1666 | },
1667 | "strip-json-comments": {
1668 | "version": "3.1.1",
1669 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
1670 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
1671 | "dev": true
1672 | },
1673 | "supports-color": {
1674 | "version": "5.5.0",
1675 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1676 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1677 | "dev": true,
1678 | "requires": {
1679 | "has-flag": "^3.0.0"
1680 | }
1681 | },
1682 | "text-table": {
1683 | "version": "0.2.0",
1684 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
1685 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
1686 | "dev": true
1687 | },
1688 | "to-fast-properties": {
1689 | "version": "2.0.0",
1690 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
1691 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
1692 | "dev": true
1693 | },
1694 | "to-regex-range": {
1695 | "version": "5.0.1",
1696 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1697 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1698 | "dev": true,
1699 | "requires": {
1700 | "is-number": "^7.0.0"
1701 | }
1702 | },
1703 | "tslib": {
1704 | "version": "1.14.1",
1705 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
1706 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
1707 | "dev": true
1708 | },
1709 | "tsutils": {
1710 | "version": "3.21.0",
1711 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
1712 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
1713 | "dev": true,
1714 | "requires": {
1715 | "tslib": "^1.8.1"
1716 | }
1717 | },
1718 | "type-check": {
1719 | "version": "0.4.0",
1720 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
1721 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
1722 | "dev": true,
1723 | "requires": {
1724 | "prelude-ls": "^1.2.1"
1725 | }
1726 | },
1727 | "type-fest": {
1728 | "version": "0.20.2",
1729 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
1730 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
1731 | "dev": true
1732 | },
1733 | "typescript": {
1734 | "version": "4.5.5",
1735 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
1736 | "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
1737 | "dev": true
1738 | },
1739 | "uri-js": {
1740 | "version": "4.4.1",
1741 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1742 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1743 | "dev": true,
1744 | "requires": {
1745 | "punycode": "^2.1.0"
1746 | }
1747 | },
1748 | "v8-compile-cache": {
1749 | "version": "2.3.0",
1750 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
1751 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
1752 | "dev": true
1753 | },
1754 | "which": {
1755 | "version": "2.0.2",
1756 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1757 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1758 | "dev": true,
1759 | "requires": {
1760 | "isexe": "^2.0.0"
1761 | }
1762 | },
1763 | "word-wrap": {
1764 | "version": "1.2.3",
1765 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
1766 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
1767 | "dev": true
1768 | },
1769 | "wrappy": {
1770 | "version": "1.0.2",
1771 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1772 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1773 | "dev": true
1774 | },
1775 | "yallist": {
1776 | "version": "4.0.0",
1777 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1778 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
1779 | "dev": true
1780 | }
1781 | }
1782 | }
1783 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scriptables",
3 | "version": "1.0.1",
4 | "description": "collection of my scriptable projects",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel ./src -d build --extensions '.ts'",
9 | "copy": "cp -Rv ./build/. ~/Library/Mobile\\ Documents/iCloud~dk~simonbs~Scriptable/Documents",
10 | "copyi": "cp -Riv ./build/. ~/Library/Mobile\\ Documents/iCloud~dk~simonbs~Scriptable/Documents",
11 | "dist": "npm run build; npm run copyi",
12 | "distall": "npm run build; npm run copy"
13 | },
14 | "author": "Dmytro Burmistrov (Nodman). https://github.com/Nodman",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "@babel/cli": "^7.16.8",
18 | "@babel/core": "^7.16.12",
19 | "@babel/plugin-transform-modules-commonjs": "^7.16.8",
20 | "@babel/preset-typescript": "^7.16.7",
21 | "@scriptable-ios/eslint-config": "^1.6.4",
22 | "@types/scriptable-ios": "^1.6.5",
23 | "@typescript-eslint/eslint-plugin": "^5.11.0",
24 | "@typescript-eslint/parser": "^5.11.0",
25 | "eslint": "^8.8.0",
26 | "typescript": "^4.5.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/demo.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-magic-numbers */
2 |
3 | import { createWidget } from './tiny-dashboard'
4 | import { numberFormatterShort } from './utils'
5 |
6 |
7 | const dateFormatter = new DateFormatter()
8 |
9 | dateFormatter.useShortTimeStyle()
10 |
11 | function exec() {
12 | const widget = createWidget({
13 | chartData: [89, 100, 69, 190, 59, 22, 40],
14 | subtitle1: `${numberFormatterShort(1400)} DAILY / ${numberFormatterShort(12780)} MONTHLY`,
15 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`,
16 | value: numberFormatterShort(2888),
17 | subValue: `/ ${numberFormatterShort(190)}`,
18 | headerSymbol: 'hryvniasign.circle',
19 | header: 'CURRENT MONTH:',
20 | }, { dark: 'midnight' })
21 |
22 | widget.presentSmall()
23 | }
24 |
25 |
26 | exec()
27 |
28 | Script.complete()
29 |
--------------------------------------------------------------------------------
/src/mono-monthly-small.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: blue; icon-glyph: hand-holding-usd;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * track expanses from monobank account with small widget
8 | */
9 |
10 | 'use strict'
11 |
12 | import { createWidget } from './tiny-dashboard'
13 | import { numberFormatterShort, parseWidgetParams } from './utils'
14 | import { Monobank } from './monobank'
15 |
16 | const SCRIPT_NAME = 'mono-monthly-small'
17 |
18 | const dateFormatter = new DateFormatter()
19 |
20 | dateFormatter.useShortTimeStyle()
21 |
22 | async function exec() {
23 | const { name = SCRIPT_NAME } = parseWidgetParams(args.widgetParameter)
24 | const monobank = new Monobank(name)
25 |
26 | await monobank.setup()
27 |
28 | const { currentPeriod, history } = await monobank.fetchLatestStatement()
29 |
30 | const todaysValue = Monobank.getTodaysExpanses({ currentPeriod })
31 |
32 | const { monthlyHistory, daily, monthly } = Monobank.getStatsForPeriod({ history })
33 |
34 | monthlyHistory.push(currentPeriod.total)
35 |
36 | const widget = createWidget({
37 | chartData: monthlyHistory,
38 | subtitle1: `${numberFormatterShort(daily / 100)} DAILY / ${numberFormatterShort(monthly / 100)} MONTHLY`,
39 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`,
40 | value: numberFormatterShort(currentPeriod.total / 100),
41 | subValue: `/ ${numberFormatterShort(todaysValue / 100)}`,
42 | headerSymbol: 'hryvniasign.circle',
43 | header: 'CURRENT MONTH:',
44 | })
45 |
46 | Script.setWidget(widget)
47 |
48 | return widget
49 | }
50 |
51 |
52 | if (config.runsInApp) {
53 | const widget = await exec()
54 |
55 | await widget.presentSmall()
56 | } else {
57 | await exec()
58 | }
59 |
60 | Script.complete()
61 |
--------------------------------------------------------------------------------
/src/monobank.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-gray; icon-glyph: university;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * Monobank api module
8 | */
9 |
10 | // NOTE: This script uses the Cache script (https://github.com/yaylinda/scriptable/blob/main/Cache.js)
11 |
12 | 'use strict'
13 |
14 | import { fetchJson, createAlert, getDaysInMonth, Logger } from './utils'
15 |
16 | const Cache = importModule('cache')
17 |
18 | import type { PersonalInfoT, StatementItemT, StatementCacheT, SettingsT } from './monobank.types'
19 |
20 | const DEFAULT_PERIOD = 12
21 | const ACCOUNT_MASK_LEN = 4
22 | const SCRIPT_NAME = 'monobank'
23 | const CACHE_KEY_SETTINGS = 'settings.json'
24 | const MONOBANK_TOKEN_KEY = 'monobank_token@scriptable'
25 | const MONOBANK_API_URL = 'https://api.monobank.ua'
26 | const API_PATH = {
27 | PERSONAL_INFO: '/personal/client-info',
28 | STATEMENT: '/personal/statement',
29 | CURRENCIES: '/bank/currency',
30 | }
31 | const CURRENCIES: Record = {
32 | 978: { id: 978, name: 'EUR', sign: '€' },
33 | 980: { id: 980, name: 'UAH', sign: '₴' },
34 | 840: { id: 840, name: 'USD', sign: '$' },
35 | }
36 |
37 | const cache = new Cache(SCRIPT_NAME)
38 | const logger = new Logger('Mono')
39 |
40 | export class Monobank {
41 | private parentScriptName?: string
42 | private settings: Promise
43 |
44 | constructor(parentScriptName?: string) {
45 | this.parentScriptName = parentScriptName
46 | this.readSettings()
47 | }
48 |
49 | static getTodaysExpanses({ currentPeriod }: { currentPeriod: StatementCacheT['currentPeriod'] }) {
50 | if (!currentPeriod || !currentPeriod.statement) {
51 | return 0
52 | }
53 |
54 | const date = (new Date()).getDate()
55 | const todaysStatement = currentPeriod.statement[date]
56 |
57 | if (!todaysStatement || !todaysStatement.length) {
58 | return 0
59 | }
60 |
61 | return todaysStatement.reduce((acc, item) => {
62 | return acc - (item?.amount ?? 0)
63 | }, 0)
64 | }
65 |
66 | static getStatsForPeriod({ history }: { history: StatementCacheT['history'] }, period: number = DEFAULT_PERIOD) {
67 | const yeatsAvailable = Object.keys(history).map(Number).sort((a, b) => a - b)
68 | const monthlyHistory: number[] = []
69 | const dailyHistory: number[] = []
70 |
71 | while (monthlyHistory.length < period && yeatsAvailable.length) {
72 | const [year] = yeatsAvailable.splice(-1)
73 |
74 | const yearPeriod = history[year] ?? {}
75 |
76 | Object
77 | .keys(yearPeriod)
78 | .map(Number)
79 | .sort((a, b) => b - a)
80 | .slice(-period + monthlyHistory.length)
81 | .forEach((month) => {
82 | const daysInMonth = getDaysInMonth(year, month)
83 | const monthValue = yearPeriod[month]
84 |
85 | monthlyHistory.unshift(monthValue)
86 | dailyHistory.unshift(monthValue / daysInMonth)
87 | })
88 | }
89 |
90 | return {
91 | monthlyHistory,
92 | dailyHistory,
93 | monthly: monthlyHistory.reduce(( acc, item ) => acc + item, 0) / (monthlyHistory.length || 1),
94 | daily: dailyHistory.reduce(( acc, item ) => acc + item, 0) / (dailyHistory.length || 1),
95 | }
96 | }
97 |
98 | private async getStatementItemsFilter() {
99 | const settings = await this.settings
100 | const filter = this.parentScriptName && settings ? settings[this.parentScriptName]?.filter : undefined
101 |
102 | return (statementItem: StatementItemT) => {
103 | const isExpanse = statementItem.amount < 0 || statementItem.description.toLowerCase().startsWith('скасування')
104 | const isExcluded = filter ? filter.split(';').some(filterString => statementItem.description.match(filterString)) : false
105 |
106 | return isExpanse && !isExcluded
107 | }
108 | }
109 |
110 | async readSettings() {
111 | this.settings = cache.read(CACHE_KEY_SETTINGS) ?? {}
112 |
113 | return await this.settings
114 | }
115 |
116 | private async writeSettings(nextSettings: SettingsT) {
117 | const prevSettings = await this.readSettings()
118 | const settings = { ...prevSettings, ...nextSettings }
119 |
120 | cache.write(CACHE_KEY_SETTINGS, settings)
121 |
122 | this.settings = Promise.resolve(settings)
123 | }
124 |
125 | private get requestHeaders(): Record {
126 | const token = Keychain.get(MONOBANK_TOKEN_KEY)
127 |
128 | if (!token) {
129 | throw new Error('No token was found in keychain')
130 | }
131 |
132 | return {
133 | 'X-Token': token,
134 | }
135 | }
136 |
137 | private async getAccountId(parentScriptName?: string) {
138 | const parentScript = parentScriptName ?? this.parentScriptName
139 | const settings = await this.settings
140 | let accountId = parentScript && settings ? settings[parentScript]?.accountId : undefined
141 |
142 | if (!accountId) {
143 | accountId = await this.setupAccount()
144 | }
145 |
146 | if (!accountId) {
147 | throw new Error('Account ID is required')
148 | }
149 |
150 | return accountId
151 | }
152 |
153 | private fetchData(path: string): Promise {
154 | if (this.isTokenProvided()) {
155 | const url = `${MONOBANK_API_URL}${path}`
156 |
157 | return fetchJson(url, this.requestHeaders)
158 | }
159 |
160 | throw new Error('No token provided')
161 | }
162 |
163 | private isTokenProvided(): boolean {
164 | return Keychain.contains(MONOBANK_TOKEN_KEY)
165 | }
166 |
167 | private async getAccountCacheKey(parentScriptName?: string) {
168 | const accountId = await this.getAccountId(parentScriptName)
169 |
170 | return `mono-${accountId}.json`
171 | }
172 |
173 | async setupToken() {
174 | if (this.isTokenProvided()) {
175 | const tokenExistsAlert = createAlert('Monobank api token', 'Token already exists in your Keychain. Do you wish to change it?', 'Cancel')
176 |
177 | tokenExistsAlert.addAction('Change token')
178 | tokenExistsAlert.addDestructiveAction('Delete token')
179 |
180 | const actionIndex = await tokenExistsAlert.presentSheet()
181 |
182 | switch (actionIndex) {
183 | case -1:
184 | return
185 | case 1: {
186 | Keychain.remove(MONOBANK_TOKEN_KEY)
187 | await createAlert('Token removed', '', 'Ok').present()
188 |
189 | return
190 | }
191 | }
192 | }
193 |
194 | const setupAlert = createAlert(
195 | 'Monobank API token is required',
196 | `In order to fetch data from Monobank you need to provide your generated API token.
197 | If you don't have one - please visit https://api.monobank.ua and follow the instructions.`,
198 | 'Cancel')
199 |
200 | setupAlert.addAction('I have token')
201 | setupAlert.addAction('https://api.monobank.ua')
202 |
203 | const setupActionIndex = await setupAlert.presentSheet()
204 |
205 | const presentInputAlert = async () => {
206 | const inputAlert = createAlert('Insert token', 'Your token will be saved in Keychain', 'Cancel')
207 |
208 | inputAlert.addAction('Save')
209 | inputAlert.addSecureTextField('token:')
210 |
211 | const inputActionIndex = await inputAlert.present()
212 | const tokenValue = inputAlert.textFieldValue(0)
213 |
214 | if (inputActionIndex === -1) {
215 | return
216 | }
217 |
218 | if (tokenValue) {
219 | Keychain.set(MONOBANK_TOKEN_KEY, tokenValue)
220 | await createAlert('Token saved', '', 'Ok').present()
221 | } else {
222 | await createAlert('Invalid token value', '', 'Ok').present()
223 | }
224 | }
225 |
226 | switch (setupActionIndex) {
227 | case 0: {
228 | await presentInputAlert()
229 | break
230 | }
231 | case 1:
232 | await Safari.openInApp('https://api.monobank.ua')
233 | await presentInputAlert()
234 | break
235 | }
236 | }
237 |
238 | async setupFilters(parentScriptName?: string) {
239 | const scriptName = parentScriptName ?? this.parentScriptName
240 |
241 | if (!scriptName) {
242 | return
243 | }
244 |
245 | const settings = await this.settings ?? {}
246 | const setupFiltersAlert = createAlert(
247 | 'Set statement items filter',
248 | 'Enter semicolon separated values, e.g. "rent;netflix;spotify". Items with one of those words in description will be excluded from accounting',
249 | 'Cancel',
250 | )
251 |
252 | setupFiltersAlert.addAction('Save')
253 |
254 | const filter = settings ? settings[scriptName]?.filter : undefined
255 |
256 | setupFiltersAlert.addTextField('filter:', filter ?? '')
257 |
258 | const actionIndex = await setupFiltersAlert.present()
259 |
260 | if (actionIndex === -1) {
261 | return
262 | }
263 |
264 | const nextFilter = setupFiltersAlert.textFieldValue(0)
265 |
266 | settings[scriptName] = settings[scriptName] ?? {}
267 | settings[scriptName].filter = nextFilter
268 |
269 | await this.writeSettings(settings)
270 |
271 |
272 | await createAlert('Filters saved', '', 'Ok').present()
273 | }
274 |
275 | async setupAccount(parentScriptName?: string) {
276 | const scriptName = parentScriptName ?? this.parentScriptName
277 |
278 | const setupAccountsAlert = createAlert(
279 | 'Select account',
280 | 'ID of selected account will be copied to clipboard and used for this script.',
281 | 'Cancel',
282 | )
283 |
284 | const accounts = await this.fetchAccounts()
285 |
286 | accounts.forEach((item) => {
287 | const { name, type } = item
288 |
289 | setupAccountsAlert.addAction(`[${type}] ****${name}`)
290 | })
291 |
292 | const actionIndex = await setupAccountsAlert.presentSheet()
293 |
294 | if (actionIndex === -1) {
295 | return
296 | }
297 |
298 | const id = accounts[actionIndex].id
299 |
300 | Pasteboard.copy(id)
301 |
302 | if (scriptName) {
303 | await this.writeSettings({ [scriptName]: { accountId: id } })
304 | }
305 |
306 | return id
307 | }
308 |
309 | async fetchAccounts() {
310 | const response: PersonalInfoT = await this.fetchData(API_PATH.PERSONAL_INFO)
311 |
312 | const accounts = response.accounts.map((item) => {
313 | const name = (item.maskedPan[0] ?? item.iban).slice(-ACCOUNT_MASK_LEN)
314 |
315 | return {
316 | id: item.id,
317 | type: item.type,
318 | name,
319 | }
320 | })
321 |
322 | return accounts
323 | }
324 |
325 | async fetchAccountStatement(fromArg: Date, toArg?: Date): Promise {
326 | const from = Math.round(Number(fromArg) / 1e3)
327 | const to = Math.round(Number(toArg ?? new Date()) / 1e3)
328 | const accountId = await this.getAccountId()
329 |
330 | const url = `${API_PATH.STATEMENT}/${accountId}/${from}/${to}`
331 |
332 | return this.fetchData(url)
333 | }
334 |
335 | fetchTodaysStatement() {
336 | const to = new Date()
337 | const from = new Date()
338 |
339 | from.setHours(0, 0, 0, 0)
340 |
341 | return this.fetchAccountStatement(from, to)
342 | }
343 |
344 | async fetchLatestStatement(): Promise {
345 | const accountCacheKey = await this.getAccountCacheKey()
346 | const { currentPeriod, history = {} }: StatementCacheT = await cache.read(accountCacheKey) ?? {}
347 |
348 | if (!currentPeriod?.lastUpdatedAt) {
349 | return this.fetchInitialPeriod()
350 | }
351 |
352 | const { lastUpdatedAt, lastOperationId } = currentPeriod
353 | const to = new Date()
354 |
355 | const response = await this.fetchAccountStatement(new Date(lastUpdatedAt), to)
356 |
357 | if (!response.length || (response[0].id === lastOperationId)) {
358 | logger.log('nothing to process, skipping...')
359 |
360 | return { currentPeriod, history }
361 | }
362 |
363 | const filter = await this.getStatementItemsFilter()
364 |
365 | for (let index = response.length - 1; index >= 0; index--) {
366 | const item = response[index]
367 |
368 | if (filter(item)) {
369 | const { id, time, amount, description, mcc, cashbackAmount } = item
370 | const opDate = new Date(item.time * 1e3)
371 | const month = opDate.getMonth() + 1
372 | const year = opDate.getFullYear()
373 | const date = opDate.getDate()
374 |
375 | if (month > currentPeriod.month) {
376 | history[currentPeriod.year] = history[currentPeriod.year] ?? {}
377 | history[currentPeriod.year][currentPeriod.month] = currentPeriod.total
378 | currentPeriod.year = year
379 | currentPeriod.month = month
380 | currentPeriod.total = 0
381 | currentPeriod.statement = [null]
382 | } else if (month < currentPeriod.month) {
383 | continue
384 | }
385 |
386 | currentPeriod.statement[date] = currentPeriod.statement[date] ?? []
387 |
388 | // @ts-ignore
389 | currentPeriod.statement[date].push({ id, time, amount, description, mcc, cashbackAmount })
390 | currentPeriod.total = currentPeriod.total += -1 * amount
391 | }
392 | }
393 |
394 | currentPeriod.lastUpdatedAt = Number(to)
395 | currentPeriod.lastOperationId = response[0].id
396 |
397 | cache.write(accountCacheKey, { currentPeriod, history })
398 |
399 | return { currentPeriod, history }
400 | }
401 |
402 | async fetchInitialPeriod() {
403 | logger.log('fetching initial period...')
404 |
405 | const to = new Date()
406 | const from = new Date()
407 | const currentMonth = from.getMonth() + 1
408 | const currentYear = from.getFullYear()
409 |
410 | from.setDate(1)
411 | from.setHours(0, 0, 0, 0)
412 |
413 | const response = await this.fetchAccountStatement(from, to)
414 | const filter = await this.getStatementItemsFilter()
415 |
416 | const currentPeriod = response.reduce((acc, item, index) => {
417 | if (filter(item)) {
418 | const { id, time, amount, description, mcc, cashbackAmount } = item
419 | const date = new Date(item.time * 1e3).getDate()
420 | const currentValue = acc.statement[date] ?? []
421 |
422 | acc.statement[date] = [{ id, time, amount, description, mcc, cashbackAmount }, ...currentValue]
423 | acc.total += -1 * amount
424 |
425 | if (!index) {
426 | acc.lastOperationId = id
427 | }
428 | }
429 |
430 | return acc
431 | }, { month: currentMonth, year: currentYear, total: 0, statement: [], lastOperationId: '', lastUpdatedAt: Number(to) } as StatementCacheT['currentPeriod'])
432 |
433 | const statement: StatementCacheT = {
434 | history: {},
435 | currentPeriod,
436 | }
437 |
438 | const accountCacheKey = await this.getAccountCacheKey()
439 |
440 | await cache.write(accountCacheKey, statement)
441 |
442 | return statement
443 | }
444 |
445 | async editOP({ date, day, amount: editAmount }: {date: number, day: number, amount: number | null}, parentScriptName?: string) {
446 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName)
447 | const statement: StatementCacheT = await cache.read(accountCacheKey)
448 | const item = statement.currentPeriod.statement?.[date]?.[day]
449 |
450 | if (item == null) {
451 | throw new Error(`OP not found: ${date}-${day}`)
452 | }
453 |
454 | if (editAmount === null) {
455 | item.amount = item.originalAmount ?? item.amount
456 | item.originalAmount = undefined
457 | } else {
458 | item.originalAmount = item.amount
459 | item.amount = item.amount + editAmount
460 | }
461 |
462 | const nextTotal = statement.currentPeriod.statement.reduce((total, day) => {
463 | return day ? total + day.reduce((acc, item) => item ? -1 * item.amount + acc : acc, 0) : total
464 | }, 0)
465 |
466 | statement.currentPeriod.total = nextTotal
467 |
468 | await cache.write(accountCacheKey, statement)
469 |
470 | await createAlert('Successfuly updated', '', 'Ok').present()
471 | }
472 |
473 | async editStatementItem(parentScriptName?: string) {
474 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName)
475 | const { currentPeriod: { statement } }: StatementCacheT = await cache.read(accountCacheKey)
476 |
477 | const editAlert = createAlert('Select operation to edit', '', 'Cancel')
478 | const actions: {summary: string, dateIndex: number, opIndex: number, originalAmount?: number, amount: number}[] = []
479 |
480 | for (let dateIndex = statement.length - 1; dateIndex > 0; dateIndex--) {
481 | const date = statement[dateIndex]
482 |
483 | if (date) {
484 | for (let opIndex = date.length - 1; opIndex >= 0; opIndex--) {
485 | const opItem = date[opIndex]
486 |
487 | if (!opItem) {
488 | continue
489 | }
490 |
491 | const { amount, description, originalAmount } = opItem
492 | const summary = `${amount / 100}${CURRENCIES[980].sign}${originalAmount ? '[E]' : ''}: "${description.replace('\n', '_')}"`
493 |
494 | editAlert.addAction(summary)
495 | actions.push({ summary, dateIndex, opIndex, originalAmount, amount })
496 | }
497 | }
498 | }
499 |
500 | const opActionIndex = await editAlert.presentSheet()
501 |
502 | if (opActionIndex === -1) {
503 | return
504 | }
505 |
506 | let editActionIndex = -1
507 | let amount: number | null = 0
508 |
509 | const { originalAmount, amount: currentAmount } = actions[opActionIndex]
510 | const valueAlert = createAlert('Edit operation', `Current amount is: ${currentAmount / 1e2}.\nEnter either positive or negative value to add:`, 'Cancel')
511 |
512 | valueAlert.addTextField('Amount:')
513 | valueAlert.addAction('Submit')
514 |
515 | if (originalAmount) {
516 | valueAlert.addDestructiveAction('Restore this operation')
517 | } else {
518 | valueAlert.addDestructiveAction('Delete this operation')
519 | }
520 |
521 | const valueActionIndex = await valueAlert.present()
522 |
523 | editActionIndex = valueActionIndex
524 |
525 | switch (valueActionIndex) {
526 | case -1: break
527 | case 0: amount = Number(valueAlert.textFieldValue(0)) * 1e2; break
528 | case 1: amount = originalAmount ? null : -1 * currentAmount; break
529 | }
530 |
531 | if (editActionIndex === -1) {
532 | return
533 | }
534 |
535 | const { dateIndex, opIndex } = actions[opActionIndex]
536 | const actionSummary = amount === null ? `restored to ${(originalAmount ?? 0) / 1e2}` : `edited for ${amount / 1e2}.\nNew value: ${currentAmount / 1e2 + amount / 1e2}`
537 | const confirmationAlert = createAlert('Are you sure?', `${actions[opActionIndex].summary} will be ${actionSummary}.`, 'Cancel')
538 |
539 | confirmationAlert.addDestructiveAction('Submit')
540 |
541 | const confirmationIndex = await confirmationAlert.presentSheet()
542 |
543 | if (confirmationIndex === -1) {
544 | return
545 | }
546 |
547 | await this.editOP({ date: dateIndex, day: opIndex, amount }, parentScriptName)
548 | }
549 |
550 | async setup() {
551 | if (!this.parentScriptName) {
552 | return
553 | }
554 |
555 | if (!this.isTokenProvided()) {
556 | await this.setupToken()
557 | }
558 |
559 | const settings = await this.readSettings() ?? {}
560 | const scriptSettings = settings[this.parentScriptName]
561 |
562 | if (!scriptSettings || !scriptSettings.accountId) {
563 | await this.setupAccount()
564 | await this.setupFilters()
565 | }
566 | }
567 |
568 | async pickScript() {
569 | if (this.parentScriptName) {
570 | return
571 | }
572 |
573 | const settngs = await this.readSettings()
574 | const keys = Object.keys(settngs ?? {})
575 | const selectScriptAlert = createAlert('Select script to modify', '', 'Cancel')
576 |
577 | if (!keys.length) {
578 | return
579 | }
580 |
581 | keys.forEach(item => {
582 | selectScriptAlert.addAction(item)
583 | })
584 |
585 | const keyIndex = await selectScriptAlert.presentSheet()
586 |
587 | return keys[keyIndex]
588 | }
589 |
590 | async showSettings() {
591 | const settingsAlert = createAlert('Monobank settngs', '', 'Cancel')
592 |
593 | settingsAlert.addAction('Manage auth token')
594 | settingsAlert.addAction('Change account number')
595 | settingsAlert.addAction('Set filters')
596 | settingsAlert.addAction('Edit statement')
597 |
598 | const actionIndex = await settingsAlert.present()
599 |
600 | switch (actionIndex) {
601 | case -1: break
602 | case 0: await this.setupToken(); break
603 | case 1: {
604 | const scriptName = await this.pickScript()
605 |
606 | if (scriptName) {
607 | await this.setupAccount(scriptName)
608 | }
609 |
610 | break
611 | }
612 | case 2: {
613 | const scriptName = await this.pickScript()
614 |
615 | if (scriptName) {
616 | await this.setupFilters(scriptName)
617 | }
618 |
619 | break
620 | }
621 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers
622 | case 3: {
623 | const scriptName = await this.pickScript()
624 |
625 | if (scriptName) {
626 | await this.editStatementItem(scriptName)
627 | }
628 |
629 | break
630 | }
631 | }
632 | }
633 | }
634 |
635 | if (config.runsInApp && Script.name() === SCRIPT_NAME) {
636 | const monobank = new Monobank()
637 |
638 | monobank.showSettings()
639 | }
640 |
--------------------------------------------------------------------------------
/src/monobank.types.ts:
--------------------------------------------------------------------------------
1 | export type AccountT = {
2 | balance: number,
3 | cashbackType: string,
4 | creditLimit: number,
5 | currencyCode: number,
6 | iban: string,
7 | id: string,
8 | maskedPan: string[],
9 | sendId: string,
10 | type: string,
11 | }
12 |
13 | export type PersonalInfoT = {
14 | accounts: AccountT[],
15 | clientId: string,
16 | name: string,
17 | permissions: string,
18 | webHookUrl: string,
19 | }
20 |
21 | export type AccountsMapT = Record
22 |
23 | export type StatementItemT = {
24 | id: string,
25 | time: number,
26 | description: string,
27 | mcc: number,
28 | hold: boolean,
29 | amount: number,
30 | operationAmount: number,
31 | currencyCode: number,
32 | commissionRate: number,
33 | cashbackAmount: number,
34 | balance: number,
35 | comment: string,
36 | receiptId: string,
37 | counterEdrpou: string,
38 | counterIban: string,
39 | }
40 |
41 | export type StatementItemShortT = Pick & {originalAmount?: number}
42 |
43 | export type StatementCacheT = {
44 | history: Record>,
45 | currentPeriod: {
46 | total: number,
47 | month: number,
48 | year: number,
49 | statement: ((StatementItemShortT | null)[] | null)[],
50 | lastUpdatedAt: number,
51 | lastOperationId: string,
52 | },
53 | }
54 |
55 | export type SettingsT = Record
56 |
--------------------------------------------------------------------------------
/src/tiny-charts.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: teal; icon-glyph: chart-line;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * small chart, for now supports only area chart
8 | */
9 |
10 | 'use strict'
11 |
12 | const SCRIPT_NAME = 'tiny-charts'
13 |
14 | export class TinyCharts {
15 | context: DrawContext
16 | width: number
17 | height: number
18 | strokeColor?: Color
19 | fillColor?: Color
20 |
21 | constructor(width: number, height: number) {
22 | this.width = width
23 | this.height = height
24 | }
25 |
26 | private createContext() {
27 | this.context = new DrawContext()
28 | this.context.size = new Size(this.width, this.height)
29 | this.context.opaque = false
30 | this.context.respectScreenScale = true
31 | this.context.setLineWidth(1)
32 | }
33 |
34 | private normalizeData(data: number[]) {
35 | const maxValue = Math.max(...data)
36 | const normalized = data.map(value => value / (maxValue / 1e2))
37 |
38 | return normalized
39 | }
40 |
41 | private getCoordinates(data: number[], index: number) {
42 | const y = this.height - (this.height * data[index]) / 100
43 | const x = index * (this.width / (data.length - 1))
44 |
45 | return { x, y }
46 | }
47 |
48 | setFillColor(color: Color) {
49 | this.fillColor = color
50 | }
51 |
52 | setStrokeColor(color: Color) {
53 | this.strokeColor = color
54 | }
55 |
56 | drawAreaChart(rawData: number[]) {
57 | this.createContext()
58 |
59 | const data = this.normalizeData(rawData)
60 | const startingPoint = this.getCoordinates(data, 0)
61 | const path = new Path()
62 |
63 | path.move(new Point(startingPoint.x, startingPoint.y))
64 |
65 | for (let index = 0; index < data.length - 1; index++) {
66 | const point = this.getCoordinates(data, index)
67 | const nextPoint = this.getCoordinates(data, index + 1)
68 |
69 | const pointX = (point.x + nextPoint.x) / 2
70 | const pointY = (point.y + nextPoint.y) / 2
71 | const controlx1 = (pointX + point.x) / 2
72 | const controlx2 = (pointX + nextPoint.x) / 2
73 |
74 | path.addQuadCurve(new Point(pointX, pointY), new Point(controlx1, point.y))
75 | path.addQuadCurve(new Point(nextPoint.x, nextPoint.y), new Point(controlx2, nextPoint.y))
76 | }
77 |
78 | path.addLine(new Point(this.width, this.height))
79 | path.addLine(new Point(0, this.height))
80 | path.closeSubpath()
81 |
82 | if (this.strokeColor) {
83 | this.context.setStrokeColor(this.strokeColor)
84 | this.context.addPath(path)
85 | this.context.strokePath()
86 | }
87 |
88 | if (this.fillColor) {
89 | this.context.setFillColor(this.fillColor)
90 | this.context.addPath(path)
91 | this.context.fillPath()
92 | }
93 | }
94 |
95 | getContext() {
96 | return this.context
97 | }
98 |
99 | getImage() {
100 | return this.context.getImage()
101 | }
102 | }
103 |
104 | Script.complete()
105 |
--------------------------------------------------------------------------------
/src/tiny-dashboard.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: blue; icon-glyph: columns;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * small widget layout with graph
8 | */
9 |
10 | 'use strict'
11 |
12 | import { getDynamicGradient, getDynamicColor, parseWidgetParams, getDeviceAppearance } from './utils'
13 | import type { PaletteT } from './utils.types'
14 | import { TinyCharts } from './tiny-charts'
15 |
16 | const SCRIPT_NAME = 'tiny-dashboard'
17 | const GAP = 10
18 | const SYMBOL_SIZE = 16
19 | const GRAPH_OPACITY = 0.3
20 | const GRAPH_WIDTH = 510
21 | const GRAPH_HEIGHT = 150
22 |
23 | /*
24 | * most of the colors taken from https://uigradients.com/
25 | */
26 | export const PALETTES: Record = {
27 | // darker ? palettes
28 | seablue: {
29 | bgStart: '#2b5876',
30 | bgEnd: '#4e4376',
31 | primary: '#ece9e6',
32 | secondary: '#7d7795',
33 | },
34 | cloud: {
35 | bgStart: '#141E30',
36 | bgEnd: '#243B55',
37 | primary: '#d0d0da',
38 | secondary: '#141E30',
39 | },
40 | midnight: {
41 | bgStart: '#232526',
42 | bgEnd: '#414345',
43 | primary: '#ece9e6',
44 | secondary: '#232526',
45 | },
46 | // lighter palettes, kinda. I am bad with colors
47 | royal: {
48 | bgStart: '#ece9e6',
49 | bgEnd: '#ffffff',
50 | primary: '#141E30',
51 | secondary: '#c2bbb5',
52 | },
53 | dull: {
54 | bgStart: '#c9d6ff',
55 | bgEnd: '#e2e2e2',
56 | primary: '#000c40',
57 | secondary: '#99b1fe',
58 | },
59 | anamnisar: {
60 | bgStart: '#9796f0',
61 | bgEnd: '#fbc7d4',
62 | primary: '#1D2B64',
63 | secondary: '#9796f0',
64 | },
65 | ash: {
66 | bgStart: '#606c88',
67 | bgEnd: '#3f4c6b',
68 | primary: '#c9d6ff',
69 | secondary: '#c9d6ff',
70 | },
71 | // other color presets
72 | pacific: {
73 | bgStart: '#0f3443',
74 | bgEnd: '#34e89e',
75 | primary: '#BDFFF3',
76 | secondary: '#0f3443',
77 | },
78 | sin: {
79 | bgStart: '#93291E',
80 | bgEnd: '#ED213A',
81 | primary: '#340707',
82 | secondary: '#333333',
83 | },
84 | sandblue: {
85 | bgStart: '#DECBA4',
86 | bgEnd: '#3E5151',
87 | primary: '#243737',
88 | secondary: '#3E5151',
89 | },
90 |
91 | // nord colors taken from https://www.nordtheme.com
92 | nord: {
93 | bgStart: '#2E3440',
94 | bgEnd: '#2E3440',
95 | primary: '#81a1c1',
96 | accent: '#d8dee9',
97 | secondary: '#d8dee9',
98 | },
99 | nordlight: {
100 | bgStart: '#d8dee9',
101 | bgEnd: '#d8dee9',
102 | primary: '#4c566a',
103 | accent: '#5e81ac',
104 | secondary: '#81a1c1',
105 | },
106 | }
107 |
108 | const TYPOGRAPHY = {
109 | title: 40,
110 | subtitle: 15,
111 | body: 10,
112 | caption: 8,
113 | }
114 |
115 | function getPalette(palette: { light?: string, dark?: string } = {}) {
116 | const { light, dark } = parseWidgetParams(args.widgetParameter)
117 |
118 | return {
119 | light: PALETTES[palette.light ?? light] ?? PALETTES.dull,
120 | dark: PALETTES[palette.dark ?? dark] ?? PALETTES.sandblue,
121 | }
122 | }
123 |
124 | type ArgsT = {
125 | header: string,
126 | headerSymbol?: string,
127 | value: string,
128 | subValue?: string,
129 | subtitle1?: string,
130 | subtitle2?: string,
131 | chartData?: number[],
132 | }
133 |
134 | export function createWidget(args: ArgsT, theme?: { light?: string, dark?: string }) {
135 | const {
136 | value,
137 | chartData,
138 | subtitle1,
139 | subtitle2,
140 | headerSymbol: headerSymbolProp,
141 | header,
142 | subValue,
143 | } = args
144 |
145 | const appearence = getDeviceAppearance()
146 | const palette = getPalette(theme)
147 | const listWidget = new ListWidget()
148 | const textColor = getDynamicColor(palette, 'primary')
149 | const titleColor = getDynamicColor(palette, 'accent')
150 | const gradient = getDynamicGradient(palette)
151 | const opacity = palette[appearence].bgStart === palette[appearence].bgEnd ? 1 : GRAPH_OPACITY
152 | const fillColor = getDynamicColor(palette, 'secondary', opacity)
153 |
154 | listWidget.setPadding(GAP, 0, 0, 0)
155 |
156 | gradient.locations = [0.0, 1]
157 | listWidget.backgroundGradient = gradient
158 |
159 | // HEADER
160 | const headerStack = listWidget.addStack()
161 | const headerText = headerStack.addText(header)
162 |
163 | headerStack.setPadding(0, GAP, 0, GAP)
164 | headerText.textColor = textColor
165 | headerText.font = Font.regularSystemFont(TYPOGRAPHY.body)
166 | headerText.minimumScaleFactor = 0.50
167 |
168 | if (headerSymbolProp) {
169 | const headerSymbol = SFSymbol.named(headerSymbolProp)
170 |
171 | headerSymbol.applyFont(Font.systemFont(SYMBOL_SIZE))
172 | headerSymbol.applySemiboldWeight
173 |
174 | headerStack.addSpacer()
175 |
176 | const currencySymbolImg = headerStack.addImage(headerSymbol.image)
177 |
178 | currencySymbolImg.resizable = false
179 | currencySymbolImg.tintColor = textColor
180 | currencySymbolImg.imageSize = new Size(SYMBOL_SIZE, SYMBOL_SIZE)
181 | }
182 |
183 | listWidget.addSpacer()
184 |
185 | // VALUE
186 | const valueStack = listWidget.addStack()
187 |
188 | valueStack.setPadding(0, GAP, 1, GAP)
189 | valueStack.bottomAlignContent()
190 | valueStack.spacing = 2
191 |
192 | const valueText = valueStack.addText(value)
193 |
194 | valueText.textColor = titleColor
195 | valueText.font = Font.boldSystemFont(TYPOGRAPHY.title)
196 | valueText.minimumScaleFactor = 0.50
197 |
198 | if (subValue) {
199 | const subValueStack = valueStack.addStack()
200 | const subValueText = subValueStack.addText(subValue)
201 |
202 | // hack to proper align text
203 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers
204 | subValueStack.setPadding(0, 0, 6, 0)
205 |
206 | subValueText.textColor = titleColor
207 | subValueText.font = Font.boldSystemFont(TYPOGRAPHY.body)
208 | subValueText.minimumScaleFactor = 0.50
209 | }
210 |
211 | // FOOTER
212 | const footerStack = listWidget.addStack()
213 |
214 | const footerStackBottomPadding = (2 - [subtitle1, subtitle2].reduce((acc, item) => acc + (item ? 1 : 0), 0)) * GAP
215 |
216 | footerStack.setPadding(0, GAP, footerStackBottomPadding, 0)
217 | footerStack.layoutVertically()
218 | footerStack.spacing = 2
219 |
220 | if (subtitle1) {
221 | const subtitle1Text = footerStack.addText(subtitle1)
222 |
223 | subtitle1Text.textColor = textColor
224 | subtitle1Text.font = Font.lightSystemFont(TYPOGRAPHY.caption)
225 | }
226 |
227 | if (subtitle2) {
228 | const subtitle2Text = footerStack.addText(subtitle2)
229 |
230 | subtitle2Text.textColor = textColor
231 | subtitle2Text.font = Font.lightSystemFont(TYPOGRAPHY.caption)
232 | }
233 |
234 |
235 | listWidget.addSpacer(GAP / 2)
236 |
237 | // GRAPH
238 | const graphStack = listWidget.addStack()
239 |
240 | graphStack.setPadding(0, 0, 0, 0)
241 | graphStack.backgroundColor = Color.clear()
242 |
243 | if (chartData) {
244 | const chart = new TinyCharts(GRAPH_WIDTH, GRAPH_HEIGHT)
245 |
246 | chart.setFillColor(fillColor)
247 | chart.drawAreaChart(chartData)
248 |
249 | graphStack.addImage(chart.getImage())
250 | } else {
251 | listWidget.addSpacer(GAP)
252 | listWidget.addSpacer()
253 | }
254 |
255 | return listWidget
256 | }
257 |
258 | Script.complete()
259 |
--------------------------------------------------------------------------------
/src/update-code.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: red; icon-glyph: check-circle;
4 |
5 | 'use strict'
6 |
7 | const SCRIPTS = [
8 | 'utils',
9 | 'tiny-dashboard',
10 | 'tiny-charts',
11 | 'mono-monthly-small',
12 | 'update-code',
13 | 'monobank',
14 | ]
15 |
16 | const URL = 'https://raw.githubusercontent.com/Nodman/scriptables/main/build'
17 |
18 | // taken from Max Zeryck blur script
19 | const updateCode = async (scriptName: string) => {
20 | const alert = new Alert()
21 | const moduleName = `${scriptName}.js`
22 | const url = `${URL}/${moduleName}`
23 | const path = [...module.filename.split('/').slice(0, -1), moduleName].join('/')
24 |
25 | alert.title = `Update "${moduleName}" code?`
26 | alert.message = 'This will overwrite any of your local changes!'
27 | alert.addCancelAction('Nope')
28 |
29 | alert.addDestructiveAction('Yesh')
30 |
31 | const actionIndex = await alert.present()
32 |
33 | if (actionIndex === -1) {
34 | return
35 | }
36 |
37 | // Determine if the user is using iCloud.
38 | let files = FileManager.local()
39 | const iCloudInUse = files.isFileStoredIniCloud(path)
40 |
41 | // If so, use an iCloud file manager.
42 | files = iCloudInUse ? FileManager.iCloud() : files
43 |
44 | let message = ''
45 |
46 | // Try to download the file.
47 | try {
48 | const req = new Request(url)
49 | const codeString = await req.loadString()
50 |
51 | files.writeString(path, codeString)
52 | message = 'The code has been updated. If the script is open, close it for the change to take effect.'
53 | } catch {
54 | message = 'The update failed. Please try again later.'
55 | }
56 |
57 | const confirmAlert = new Alert()
58 |
59 | confirmAlert.title = 'Update code'
60 | confirmAlert.message = message
61 | confirmAlert.addCancelAction('Ok')
62 |
63 | await confirmAlert.present()
64 | }
65 |
66 | const selectScriptAlert = new Alert()
67 |
68 | selectScriptAlert.title = 'Select script to update'
69 | selectScriptAlert.addCancelAction('Cancel')
70 |
71 | SCRIPTS.forEach(item => {
72 | selectScriptAlert.addAction(item)
73 | })
74 |
75 | const actionIndex = await selectScriptAlert.presentSheet()
76 |
77 | if (actionIndex !== -1) {
78 | await updateCode(SCRIPTS[actionIndex])
79 | }
80 |
81 | export {}
82 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-purple; icon-glyph: wrench;
4 |
5 | /*
6 | * author: https://github.com/Nodman
7 | * few common utils I use across scriptable widgets
8 | */
9 |
10 | 'use strict'
11 |
12 | import type { PaletteT, DynamicPaletteT } from './utils.types'
13 |
14 | const SCRIPT_NAME = 'utils'
15 | const DEBUG = true
16 | const ERROR_NOTIFICATION_ID = '_error'
17 |
18 | export class Logger {
19 | private prefix?: string
20 | private separator: string
21 |
22 | constructor(prefix?: string) {
23 | this.prefix = prefix ?? ''
24 | this.separator = prefix ? ': ' : ''
25 | }
26 |
27 | private print(method: 'warn' | 'log' | 'error', message: string) {
28 | if (!DEBUG) {
29 | return
30 | }
31 |
32 | // eslint-disable-next-line no-console
33 | console[method](`${this.prefix}${this.separator}${message}`)
34 | }
35 |
36 | log(message: string): void {
37 | this.print('log', message)
38 | }
39 |
40 | warn(message: string): void {
41 | this.print('warn', message)
42 | }
43 |
44 | error(message: string): void {
45 | this.print('error', message)
46 | }
47 | }
48 |
49 | const requestLogger = new Logger('Request')
50 |
51 | export async function fetchJson(url: string, headers?: Record): Promise {
52 | try {
53 | const req = new Request(url)
54 |
55 | req.headers = headers ?? {}
56 |
57 | requestLogger.log(url)
58 |
59 | return await req.loadJSON()
60 | } catch (error) {
61 | requestLogger.error(JSON.stringify(error))
62 |
63 | throw new Error(error)
64 | }
65 | }
66 |
67 | /**
68 | * Send notification
69 | *
70 | * @param {string} title Notification title
71 | * @param {string} message Notification body
72 | * @param {string} id Notification id
73 | * @param {*} userInfo Notification userInfo object
74 | * @param {Date} date Schedule date, if not provided - will be scheduled asap
75 | * @param {*} actions Array of actions, [[title, action]]
76 | * @param {string} scriptName Explicit scriptName
77 | * @param {string} openURL url to open when click on notification
78 | */
79 | type SendNotificationT = (params: {
80 | title: string,
81 | message: string,
82 | id?: string,
83 | userInfo?: Record,
84 | date?: Date,
85 | actions?: [title: string, action: string, destructive?: boolean][],
86 | scriptName?: string,
87 | openURL?: string,
88 | }) => void
89 |
90 | export const sendNotification: SendNotificationT = ({
91 | title,
92 | message,
93 | id = 'notification',
94 | userInfo,
95 | date,
96 | actions = [],
97 | scriptName,
98 | openURL,
99 | }) => {
100 | const notification = new Notification()
101 |
102 | notification.title = title
103 | notification.identifier = `${Script.name()}${id}`
104 | notification.threadIdentifier = `${Script.name()}${id}_thread`
105 |
106 | if (userInfo) {
107 | notification.userInfo = { error: true }
108 | }
109 |
110 | if (date) {
111 | notification.setTriggerDate(date)
112 | }
113 |
114 | if (openURL) {
115 | notification.openURL = openURL
116 | }
117 |
118 | actions.forEach(action => {
119 | notification.addAction(...action)
120 | })
121 |
122 | notification.scriptName = scriptName || Script.name()
123 | notification.body = message
124 | notification.schedule()
125 | }
126 |
127 | export const sendErrorNotification = (err: Error, params: Parameters[0]) => {
128 | sendNotification({
129 | ...params,
130 | title: `Something went wrong with ${Script.name()}`,
131 | id: ERROR_NOTIFICATION_ID,
132 | message: err.message,
133 | })
134 | }
135 |
136 | export const createAlert = (title: string, message: string, cancelAction?: string): Alert => {
137 | const alert = new Alert()
138 |
139 | alert.message = message
140 | alert.title = title
141 |
142 | if (cancelAction) {
143 | alert.addCancelAction(cancelAction)
144 | }
145 |
146 | return alert
147 | }
148 |
149 | // starts from 1
150 | export const getDaysInMonth = (year: number, month: number) => {
151 | return new Date(year, month, 0).getDate()
152 | }
153 |
154 | export const getDynamicColor = ({ light, dark }: DynamicPaletteT, key: keyof PaletteT, opacity?: number) => {
155 | const lightColor = light[key] ?? light.primary
156 | const darkColor = dark[key] ?? dark.primary
157 |
158 | return Color.dynamic(new Color(lightColor, opacity), new Color(darkColor, opacity))
159 | }
160 |
161 | export const getDynamicGradient = ({ dark, light }: DynamicPaletteT) => {
162 | const startColor = Color.dynamic(new Color(light.bgStart), new Color(dark.bgStart))
163 | const endColor = Color.dynamic(new Color(light.bgEnd), new Color(dark.bgEnd))
164 |
165 | const gradient = new LinearGradient()
166 |
167 | gradient.colors = [startColor, endColor]
168 |
169 | return gradient
170 | }
171 |
172 | export const parseWidgetParams = (params: unknown): Record => {
173 | if (typeof params !== 'string') {
174 | return {}
175 | }
176 |
177 | return params.split(';').reduce((acc, item) => {
178 | const [key, value = ''] = item.split('=')
179 |
180 | return { ...acc, [key.trim()]: value.trim() ?? true }
181 | }, {})
182 | }
183 |
184 | export const numberFormatterShort = new Intl.NumberFormat('ua', {
185 | notation: 'compact',
186 | compactDisplay: 'short',
187 | maximumFractionDigits: 1,
188 | minimumFractionDigits: 0,
189 | }).format
190 |
191 | export const currencyFormatter = new Intl.NumberFormat('uk-UA', {
192 | minimumFractionDigits: 2,
193 | }).format
194 |
195 | export const getDeviceAppearance = (): 'dark' | 'light' => {
196 | return Device.isUsingDarkAppearance() ? 'dark' : 'light'
197 | }
198 |
199 | Script.complete()
200 |
--------------------------------------------------------------------------------
/src/utils.types.ts:
--------------------------------------------------------------------------------
1 | export type PaletteT = {
2 | bgStart: string,
3 | bgEnd: string,
4 | primary: string,
5 | secondary: string,
6 | accent?: string,
7 | }
8 |
9 | export type DynamicPaletteT = {
10 | dark: PaletteT,
11 | light: PaletteT,
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "lib": ["ES2020"],
5 | "target": "ES2020",
6 | "noImplicitAny": true,
7 | "noImplicitThis": true,
8 | "strictFunctionTypes": true,
9 | "strictNullChecks": true,
10 | "typeRoots": ["node_modules/@types"],
11 | "types": ["scriptable-ios"],
12 | "forceConsistentCasingInFileNames": true,
13 | "outDir": "./build",
14 | "esModuleInterop": false
15 | },
16 | "include": ["src/*"],
17 | "exclude": ["src/**/*.types.ts"]
18 | }
19 |
--------------------------------------------------------------------------------