diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 03efd1d30d..e87590b976 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -37,3 +37,6 @@ c0c5b2ebdddbe8898ce2d5e5365f4931ff73b6bf # minor formatting fix in `user.py` f223bc02490902dfcc32892058f13f343d51fbaf + +# frappe.cache() -> frappe.cache +fa6dc03cc87ad74e11609e7373078366fdcb3e1b diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 39880e35e7..5a4d341a9b 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -54,7 +54,9 @@ fi echo "Starting Bench..." -bench start &> bench_start.log & +export FRAPPE_TUNE_GC=True + +bench start &> ~/frappe-bench/bench_start.log & if [ "$TYPE" == "server" ] then diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index c563f9e43f..481041ed68 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 200 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 check-latest: true - name: Check commit titles diff --git a/.github/workflows/on_release.yml b/.github/workflows/on_release.yml index 851b5b1d6a..c17a7c6639 100644 --- a/.github/workflows/on_release.yml +++ b/.github/workflows/on_release.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - uses: actions/setup-python@v4 with: diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 4b487d2aea..87cd530538 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -62,14 +62,16 @@ jobs: fi - name: Setup Python - uses: "gabrielfalcao/pyenv-action@v10" + uses: actions/setup-python@v4 with: - versions: 3.10:latest, 3.7:latest + python-version: | + 3.7 + 3.10 - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 check-latest: true - name: Add to Hosts @@ -100,7 +102,6 @@ jobs: run: | bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh pip install frappe-bench - pyenv global $(pyenv versions | grep '3.10') bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} @@ -120,26 +121,39 @@ jobs: function update_to_version() { version=$1 + py=$2 + branch_name="version-$version-hotfix" echo "Updating to v$version" git fetch --depth 1 upstream $branch_name:$branch_name git checkout -q -f $branch_name - pip install -U frappe-bench + pgrep honcho | xargs kill rm -rf ~/frappe-bench/env - bench -v setup env + bench -v setup env --python $py + bench start &> ~/frappe-bench/bench_start.log & + bench --site test_site migrate } - pyenv global $(pyenv versions | grep '3.7') - update_to_version 12 - update_to_version 13 + update_to_version 12 python3.7 + update_to_version 13 python3.7 - pyenv global $(pyenv versions | grep '3.10') - update_to_version 14 + update_to_version 14 python3.10 echo "Updating to last commit" - git checkout -q -f "$GITHUB_SHA" rm -rf ~/frappe-bench/env + git checkout -q -f "$GITHUB_SHA" bench -v setup env bench --site test_site migrate + + - name: Show bench output + if: ${{ always() }} + run: | + cd ~/frappe-bench + cat bench_start.log || true + cd logs + for f in ./*.log*; do + echo "Printing log: $f"; + cat $f + done diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index 4feaebe15d..f42c3bc55c 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -16,7 +16,7 @@ jobs: path: 'frappe' - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - uses: actions/setup-python@v4 with: python-version: '3.11' diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 3b76da1973..f5eac8e380 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -90,7 +90,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 check-latest: true - name: Add to Hosts @@ -136,6 +136,10 @@ jobs: BUILD_NUMBER: ${{ matrix.container }} TOTAL_BUILDS: 2 + - name: Show bench output + if: ${{ always() }} + run: cat ~/frappe-bench/bench_start.log || true + - name: Upload coverage data uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 1b88bc73ce..bea00748e9 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -78,7 +78,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 check-latest: true - name: Add to Hosts diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cypress.config.js b/cypress.config.js index bfd0bc0025..2fdf10ca14 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -8,6 +8,8 @@ module.exports = defineConfig({ pageLoadTimeout: 15000, video: true, videoUploadOnPasses: false, + viewportHeight: 960, + viewportWidth: 1400, retries: { runMode: 2, openMode: 2, diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 71e5e498cf..ecf8dcc718 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -7,50 +7,41 @@ context("Awesome Bar", () => { beforeEach(() => { cy.get(".navbar .navbar-home").click(); - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").clear(); + cy.findByPlaceholderText("Search or type a command (Ctrl + G)").as("awesome_bar"); + cy.get("@awesome_bar").type("{selectall}"); }); it("navigates to doctype list", () => { - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("todo", { - delay: 700, - }); + cy.get("@awesome_bar").type("todo"); + cy.wait(100); cy.get(".awesomplete").findByRole("listbox").should("be.visible"); - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("{enter}", { - delay: 700, - }); - + cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); - cy.location("pathname").should("eq", "/app/todo"); }); it("find text in doctype list", () => { - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type( - "test in todo{enter}", - { delay: 700 } - ); - + cy.get("@awesome_bar").type("test in todo"); + cy.wait(100); + cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); - - cy.findByPlaceholderText("ID").should("have.value", "%test%"); + cy.wait(200); + const name_filter = cy.findByPlaceholderText("ID"); + name_filter.should("have.value", "%test%"); cy.clear_filters(); }); it("navigates to new form", () => { - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type( - "new blog post{enter}", - { delay: 700 } - ); - + cy.get("@awesome_bar").type("new blog post"); + cy.wait(100); + cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text:visible").should("have.text", "New Blog Post"); }); it("calculates math expressions", () => { - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type( - "55 + 32{downarrow}{enter}", - { delay: 700 } - ); - + cy.get("@awesome_bar").type("55 + 32"); + cy.wait(100); + cy.get("@awesome_bar").type("{downarrow}{enter}"); cy.get(".modal-title").should("contain", "Result"); cy.get(".msgprint").should("contain", "55 + 32 = 87"); }); diff --git a/cypress/integration/control_currency.js b/cypress/integration/control_currency.js new file mode 100644 index 0000000000..5e6db86036 --- /dev/null +++ b/cypress/integration/control_currency.js @@ -0,0 +1,74 @@ +context("Control Currency", () => { + const fieldname = "currency_field"; + + before(() => { + cy.login(); + cy.visit("/app/website"); + }); + + function get_dialog_with_currency(df_options = {}) { + return cy.dialog({ + title: "Currency Check", + fields: [ + { + fieldname: fieldname, + fieldtype: "Currency", + Label: "Currency", + ...df_options, + }, + ], + }); + } + + it("check value changes", () => { + const TEST_CASES = [ + { + input: "10.101", + df_options: { precision: 1 }, + blur_expected: "10.1", + }, + { + input: "10.101", + df_options: { precision: "3" }, + blur_expected: "10.101", + }, + { + input: "10.101", + df_options: { precision: "" }, // default assumed to be 2; + blur_expected: "10.10", + }, + { + input: "10.101", + df_options: { precision: "0" }, + blur_expected: "10", + }, + { + input: "10.101", + df_options: { precision: 0 }, + blur_expected: "10", + }, + { + input: "10.101", + df_options: { precision: "" }, + blur_expected: "10.1", + default_precision: 1, + }, + ]; + + TEST_CASES.forEach((test_case) => { + cy.window() + .its("frappe") + .then((frappe) => { + frappe.boot.sysdefaults.currency = test_case.currency; + frappe.boot.sysdefaults.currency_precision = test_case.default_precision ?? 2; + }); + + get_dialog_with_currency(test_case.df_options).as("dialog"); + cy.get_field(fieldname, "Currency").clear(); + cy.wait(300); + cy.fill_field(fieldname, test_case.input, "Currency").blur(); + cy.get_field(fieldname, "Currency").should("have.value", test_case.blur_expected); + cy.hide_dialog(); + }); + }); +}); diff --git a/cypress/integration/control_icon.js b/cypress/integration/control_icon.js index a965ed0f9e..406e9f1162 100644 --- a/cypress/integration/control_icon.js +++ b/cypress/integration/control_icon.js @@ -42,14 +42,14 @@ context("Control Icon", () => { it("search for icon and clear search input", () => { let search_text = "ed"; - cy.get(".icon-picker").findByRole("searchbox").click().type(search_text); + cy.get(".icon-picker").get(".search-icons > input").click().type(search_text); cy.get(".icon-section .icon-wrapper:not(.hidden)").then((i) => { cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then((icons) => { expect(i.length).to.equal(icons.length); }); }); - cy.get(".icon-picker").findByRole("searchbox").clear().blur(); + cy.get(".icon-picker").get(".search-icons > input").clear().blur(); cy.get(".icon-section .icon-wrapper").should("not.have.class", "hidden"); }); }); diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js index d3462492f6..0746f4460e 100644 --- a/cypress/integration/control_link.js +++ b/cypress/integration/control_link.js @@ -133,8 +133,7 @@ context("Control Link", () => { true ); - cy.clear_cache(); - cy.wait(500); + cy.reload(); get_dialog_with_link().as("dialog"); cy.window() @@ -177,7 +176,7 @@ context("Control Link", () => { cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link"); cy.get(".frappe-control[data-fieldname=assigned_by] input").focus().as("input"); - cy.get("@input").type(cy.config("testUser"), { delay: 100 }).blur(); + cy.get("@input").clear().type(cy.config("testUser"), { delay: 300 }).blur(); cy.wait("@validate_link"); cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should( "contain", diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js index b56343c2d8..b5b29fe758 100644 --- a/cypress/integration/control_phone.js +++ b/cypress/integration/control_phone.js @@ -47,7 +47,7 @@ context("Control Phone", () => { it("case insensitive search for country and clear search", () => { let search_text = "india"; cy.get(".selected-phone").click().first(); - cy.get(".phone-picker").findByRole("searchbox").click().type(search_text); + cy.get(".phone-picker").get(".search-phones").click().type(search_text); cy.get(".phone-section .phone-wrapper:not(.hidden)").then((i) => { cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then( (countries) => { @@ -56,7 +56,7 @@ context("Control Phone", () => { ); }); - cy.get(".phone-picker").findByRole("searchbox").clear().blur(); + cy.get(".phone-picker").get(".search-phones").clear(); cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden"); }); diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index ba65454ef6..4a32c16516 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -11,9 +11,9 @@ context("Folder Navigation", () => { cy.click_filter_button(); cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click(); cy.get(".fieldname-select-area > .awesomplete > .form-control:last").type("Fol{enter}"); - cy.get( - ".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback" - ).type("Home{enter}"); + cy.get(".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback") + .first() + .type("Home{enter}"); cy.get(".filter-action-buttons > div > .btn-primary").findByText("Apply Filters").click(); //Adding folder (Test Folder) @@ -24,6 +24,7 @@ context("Folder Navigation", () => { it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => { //Navigating inside the Attachments folder + cy.clear_filters(); cy.wait(500); cy.get('[title="Attachments"] > span').click(); diff --git a/cypress/integration/form.js b/cypress/integration/form.js index 8186647a14..cdd6d7e9bd 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -59,11 +59,13 @@ context("Form", () => { .blur(); cy.click_listview_row_item_with_text("Test Form Contact 3"); + cy.scrollTo(0); cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist"); cy.get(".prev-doc").should("be.visible").click(); cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible"); cy.hide_dialog(); + cy.scrollTo(0); cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist"); cy.get(".next-doc").should("be.visible").click(); cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible"); diff --git a/cypress/integration/kanban.js b/cypress/integration/kanban.js index f14c991c7c..12c5e7e8bd 100644 --- a/cypress/integration/kanban.js +++ b/cypress/integration/kanban.js @@ -100,15 +100,15 @@ context("Kanban Board", () => { it("Checks if Kanban Board edits are blocked for non-System Manager and non-owner of the Board", () => { cy.switch_to_user("Administrator"); - const noSystemManager = "nosysmanager@example.com"; + const not_system_manager = "nosysmanager@example.com"; cy.call("frappe.tests.ui_test_helpers.create_test_user", { - username: noSystemManager, + username: not_system_manager, }); - cy.remove_role(noSystemManager, "System Manager"); + cy.remove_role(not_system_manager, "System Manager"); cy.call("frappe.tests.ui_test_helpers.create_todo", { description: "Frappe User ToDo" }); cy.call("frappe.tests.ui_test_helpers.create_admin_kanban"); - cy.switch_to_user(noSystemManager); + cy.switch_to_user(not_system_manager); cy.visit("/app/todo/view/kanban/Admin Kanban"); @@ -125,7 +125,7 @@ context("Kanban Board", () => { cy.get(".kanban .column-options").should("have.length", 0); cy.switch_to_user("Administrator"); - cy.call("frappe.client.delete", { doctype: "User", name: noSystemManager }); + cy.call("frappe.client.delete", { doctype: "User", name: not_system_manager }); }); after(() => { diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 3fa0758f0c..b07f18edc2 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -13,15 +13,8 @@ context("List View", () => { it("Keep checkbox checked after Refresh", { scrollBehavior: false }, () => { cy.go_to_list("ToDo"); cy.clear_filters(); - cy.get(".list-row-container .list-row-checkbox").click({ - multiple: true, - force: true, - }); - cy.get(".actions-btn-group button").contains("Actions").should("be.visible"); - cy.intercept("/api/method/frappe.desk.reportview.get").as("list-refresh"); - cy.wait(3000); // wait before you hit another refresh - cy.get('button[data-original-title="Refresh"]').click(); - cy.wait("@list-refresh"); + cy.get(".list-header-subject > .list-subject > .list-check-all").click(); + cy.get("button[data-original-title='Refresh']").click(); cy.get(".list-row-container .list-row-checkbox:checked").should("be.visible"); }); @@ -39,11 +32,8 @@ context("List View", () => { ]; cy.go_to_list("ToDo"); cy.clear_filters(); - cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ - multiple: true, - force: true, - }); - cy.get(".actions-btn-group button").contains("Actions").should("be.visible").click(); + cy.get(".list-header-subject > .list-subject > .list-check-all").click(); + cy.findByRole("button", { name: "Actions" }).click(); cy.get(".dropdown-menu li:visible .dropdown-item") .should("have.length", 9) .each((el, index) => { @@ -56,8 +46,7 @@ context("List View", () => { }).as("bulk-approval"); cy.wrap(elements).contains("Approve").click(); cy.wait("@bulk-approval"); - cy.wait(300); - cy.get_open_dialog().find(".btn-modal-close").click(); + cy.hide_dialog(); cy.reload(); cy.clear_filters(); cy.get(".list-row-container:visible").should("contain", "Approved"); diff --git a/cypress/integration/navigation.js b/cypress/integration/navigation.js index cf1b5dc89d..5961702ba5 100644 --- a/cypress/integration/navigation.js +++ b/cypress/integration/navigation.js @@ -1,21 +1,26 @@ context("Navigation", () => { before(() => { + cy.visit("/login"); cy.login(); + cy.visit("/app/website"); }); it("Navigate to route with hash in document name", () => { - cy.insert_doc("ToDo", { - __newname: "ABC#123", - description: "Test this", - ignore_duplicate: true, - }); - cy.visit("/app/todo/ABC#123"); + cy.insert_doc( + "ToDo", + { + __newname: "ABC#123", + description: "Test this", + }, + true + ); + cy.visit(`/app/todo/${encodeURIComponent("ABC#123")}`); cy.title().should("eq", "Test this - ABC#123"); cy.get_field("description", "Text Editor").contains("Test this"); cy.go("back"); cy.title().should("eq", "Website"); }); - it.only("Navigate to previous page after login", () => { + it("Navigate to previous page after login", () => { cy.visit("/app/todo"); cy.get(".page-head").findByTitle("To Do").should("be.visible"); cy.clear_filters(); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 4b44a24598..23b03549fa 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -34,7 +34,7 @@ Cypress.Commands.add("login", (email, password) => { if (!password) { password = Cypress.env("adminPassword"); } - cy.request({ + return cy.request({ url: "/api/method/login", method: "POST", body: { @@ -373,7 +373,9 @@ Cypress.Commands.add("update_doc", (doctype, docname, args) => { Cypress.Commands.add("switch_to_user", (user) => { cy.call("logout"); + cy.wait(200); cy.login(user); + cy.reload(); }); Cypress.Commands.add("add_role", (user, role) => { diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index 4804f0e25f..1476db3c20 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -60,6 +60,11 @@ const argv = yargs type: "boolean", description: "Run build command for apps", }) + .option("save-metafiles", { + type: "boolean", + description: + "Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile", + }) .example("node esbuild --apps frappe,erpnext", "Run build only for frappe and erpnext") .example( "node esbuild --files frappe/website.bundle.js,frappe/desk.bundle.js", @@ -89,7 +94,7 @@ execute() .then(() => RUN_BUILD_COMMAND && run_build_command_for_apps(APPS)) .catch((e) => { console.error(e); - throw e; + process.exit(1); }); if (WATCH_MODE) { @@ -401,6 +406,13 @@ async function write_assets_json(metafile) { await fs.promises.writeFile(assets_json_path, JSON.stringify(new_assets_json, null, 4)); await update_assets_json_in_cache(); + if (argv["save-metafiles"]) { + // use current timestamp in readable formate as a suffix for filename + let current_timestamp = new Date().getTime(); + const metafile_name = `meta-${current_timestamp}.json`; + await fs.promises.writeFile(`${metafile_name}`, JSON.stringify(metafile)); + log(`Saved metafile as ${metafile_name}`); + } return { new_assets_json, prev_assets_json, @@ -446,9 +458,9 @@ function run_build_command_for_apps(apps) { async function notify_redis({ error, success, changed_files }) { // notify redis which in turns tells socketio to publish this to browser - let subscriber = get_redis_subscriber("redis_socketio"); + let subscriber = get_redis_subscriber("redis_queue"); subscriber.on("error", (_) => { - log_warn("Cannot connect to redis_socketio for browser events"); + log_warn("Cannot connect to redis_queue for browser events"); }); let payload = null; @@ -482,9 +494,9 @@ async function notify_redis({ error, success, changed_files }) { } function open_in_editor() { - let subscriber = get_redis_subscriber("redis_socketio"); + let subscriber = get_redis_subscriber("redis_queue"); subscriber.on("error", (_) => { - log_warn("Cannot connect to redis_socketio for open_in_editor events"); + log_warn("Cannot connect to redis_queue for open_in_editor events"); }); subscriber.on("message", (event, file) => { if (event === "open_in_editor") { diff --git a/frappe/__init__.py b/frappe/__init__.py index 84ac41ff66..998d881a13 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -11,6 +11,7 @@ be used to build database driven apps. Read the documentation: https://frappeframework.com/docs """ import functools +import gc import importlib import inspect import json @@ -57,6 +58,7 @@ re._MAXCACHE = ( 50 # reduced from default 512 given we are already maintaining this on parent worker ) +_tune_gc = bool(os.environ.get("FRAPPE_TUNE_GC", False)) if _dev_server: warnings.simplefilter("always", DeprecationWarning) @@ -380,7 +382,7 @@ def errprint(msg: str) -> None: def print_sql(enable: bool = True) -> None: - return cache().set_value("flag_print_sql", enable) + return cache.set_value("flag_print_sql", enable) def log(msg: str) -> None: @@ -925,7 +927,6 @@ def has_permission( ptype="read", doc=None, user=None, - verbose=False, throw=False, *, parent_doctype=None, @@ -938,7 +939,6 @@ def has_permission( :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`. :param doc: [optional] Checks User permissions for given doc. :param user: [optional] Check for given user. Default: current user. - :param verbose: DEPRECATED, will be removed in a future release. :param parent_doctype: Required when checking permission for a child DocType (unless doc is specified). """ import frappe.permissions @@ -1016,7 +1016,7 @@ def is_table(doctype: str) -> bool: def get_tables(): return db.get_values("DocType", filters={"istable": 1}, order_by=None, pluck=True) - tables = cache().get_value("is_table", get_tables) + tables = cache.get_value("is_table", get_tables) return doctype in tables @@ -1043,7 +1043,7 @@ def generate_hash(txt: str | None = None, length: int = 56) -> str: def reset_metadata_version(): """Reset `metadata_version` (Client (Javascript) build ID) hash.""" v = generate_hash() - cache().set_value("metadata_version", v) + cache.set_value("metadata_version", v) return v @@ -1079,7 +1079,7 @@ def set_value(doctype, docname, fieldname, value=None): def get_cached_doc(*args, **kwargs) -> "Document": - if (key := can_cache_doc(args)) and (doc := cache().get_value(key)): + if (key := can_cache_doc(args)) and (doc := cache.get_value(key)): return doc # Not found in cache, fetch from DB @@ -1095,7 +1095,7 @@ def get_cached_doc(*args, **kwargs) -> "Document": def _set_document_in_cache(key: str, doc: "Document") -> None: - cache().set_value(key, doc) + cache.set_value(key, doc) def can_cache_doc(args) -> str | None: @@ -1122,9 +1122,9 @@ def get_document_cache_key(doctype: str, name: str): def clear_document_cache(doctype: str, name: str | None = None) -> None: def clear_in_redis(): if name is not None: - cache().delete_value(get_document_cache_key(doctype, name)) + cache.delete_value(get_document_cache_key(doctype, name)) else: - cache().delete_keys(get_document_cache_key(doctype, "")) + cache.delete_keys(get_document_cache_key(doctype, "")) clear_in_redis() if hasattr(db, "after_commit"): @@ -1214,7 +1214,7 @@ def get_doc(*args, **kwargs): doc = frappe.model.document.get_doc(*args, **kwargs) # Replace cache if stale one exists - if (key := can_cache_doc(args)) and cache().exists(key): + if (key := can_cache_doc(args)) and cache.exists(key): _set_document_in_cache(key, doc) return doc @@ -1428,7 +1428,7 @@ def get_all_apps(with_internal_apps=True, sites_path=None): @request_cache -def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False): +def get_installed_apps(*, _ensure_on_bench=False): """ Get list of installed apps in current site. @@ -1436,8 +1436,6 @@ def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False) :param frappe_last: [DEPRECATED] Keep frappe last. Do not use this, reverse the app list instead. :param ensure_on_bench: Only return apps that are present on bench. """ - from frappe.utils.deprecations import deprecation_warning - if getattr(flags, "in_install_db", True): return [] @@ -1446,23 +1444,10 @@ def get_installed_apps(sort=False, frappe_last=False, *, _ensure_on_bench=False) installed = json.loads(db.get_global("installed_apps") or "[]") - if sort: - if not local.all_apps: - local.all_apps = cache().get_value("all_apps", get_all_apps) - - deprecation_warning("`sort` argument is deprecated and will be removed in v15.") - installed = [app for app in local.all_apps if app in installed] - if _ensure_on_bench: - all_apps = cache().get_value("all_apps", get_all_apps) + all_apps = cache.get_value("all_apps", get_all_apps) installed = [app for app in installed if app in all_apps] - if frappe_last: - deprecation_warning("`frappe_last` argument is deprecated and will be removed in v15.") - if "frappe" in installed: - installed.remove("frappe") - installed.append("frappe") - return installed @@ -1525,7 +1510,7 @@ def get_hooks( if conf.developer_mode: hooks = _dict(_load_app_hooks()) else: - hooks = _dict(cache().get_value("app_hooks", _load_app_hooks)) + hooks = _dict(cache.get_value("app_hooks", _load_app_hooks)) if hook: return hooks.get(hook, ([] if default == "_KEEP_DEFAULT_LIST" else default)) @@ -1555,11 +1540,9 @@ def append_hook(target, key, value): def setup_module_map(): """Rebuild map of all modules (internal).""" - _cache = cache() - if conf.db_name: - local.app_modules = _cache.get_value("app_modules") - local.module_app = _cache.get_value("module_app") + local.app_modules = cache.get_value("app_modules") + local.module_app = cache.get_value("module_app") if not (local.app_modules and local.module_app): local.module_app, local.app_modules = {}, {} @@ -1571,8 +1554,8 @@ def setup_module_map(): local.app_modules[app].append(module) if conf.db_name: - _cache.set_value("app_modules", local.app_modules) - _cache.set_value("module_app", local.module_app) + cache.set_value("app_modules", local.app_modules) + cache.set_value("module_app", local.module_app) def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): @@ -1861,7 +1844,7 @@ def redirect_to_message(title, html, http_status_code=None, context=None, indica if indicator_color: message["context"].update({"indicator_color": indicator_color}) - cache().set_value(f"message_id:{message_id}", message, expires_in_sec=60) + cache.set_value(f"message_id:{message_id}", message, expires_in_sec=60) location = f"/message?id={message_id}" if not getattr(local, "is_ajax", False): @@ -2437,4 +2420,30 @@ def mock(type, size=1, locale="en"): return squashify(results) -from frappe.desk.search import validate_and_sanitize_search_inputs # noqa +def validate_and_sanitize_search_inputs(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + from frappe.desk.search import sanitize_searchfield + from frappe.utils import cint + + kwargs.update(dict(zip(fn.__code__.co_varnames, args))) + sanitize_searchfield(kwargs["searchfield"]) + kwargs["start"] = cint(kwargs["start"]) + kwargs["page_len"] = cint(kwargs["page_len"]) + + if kwargs["doctype"] and not db.exists("DocType", kwargs["doctype"]): + return [] + + return fn(**kwargs) + + return wrapper + + +if _tune_gc: + # generational GC gets triggered after certain allocs (g0) which is 700 by default. + # This number is quite small for frappe where a single query can potentially create 700+ + # objects easily. + # Bump this number higher, this will make GC less aggressive but that improves performance of + # everything else. + g0, g1, g2 = gc.get_threshold() # defaults are 700, 10, 10. + gc.set_threshold(g0 * 10, g1 * 2, g2 * 2) diff --git a/frappe/app.py b/frappe/app.py index 55855efaf9..5113c858a5 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import gc import logging import os @@ -30,6 +31,30 @@ _site = None _sites_path = os.environ.get("SITES_PATH", ".") +# If gc.freeze is done then importing modules before forking allows us to share the memory +if frappe._tune_gc: + import frappe.boot + import frappe.client + import frappe.core.doctype.user.user + import frappe.database.mariadb.database # Load database related utils + import frappe.database.query + import frappe.desk.desktop # workspace + import frappe.model.db_query + import frappe.query_builder + import frappe.utils.background_jobs # Enqueue is very common + import frappe.utils.data # common utils + import frappe.utils.jinja # web page rendering + import frappe.utils.jinja_globals + import frappe.utils.redis_wrapper # Exact redis_wrapper + import frappe.utils.safe_exec + import frappe.utils.typing_validations # any whitelisted method uses this + import frappe.website.path_resolver # all the page types and resolver + import frappe.website.router # Website router + import frappe.website.website_generator # web page doctypes + +# end: module pre-loading + + @local_manager.middleware @Request.application def application(request: Request): @@ -157,6 +182,8 @@ def log_request(request, response): { "site": get_site_name(request.host), "remote_addr": getattr(request, "remote_addr", "NOTFOUND"), + "pid": os.getpid(), + "user": getattr(frappe.local.session, "user", "NOTFOUND"), "base_url": getattr(request, "base_url", "NOTFOUND"), "full_path": getattr(request, "full_path", "NOTFOUND"), "method": getattr(request, "method", "NOTFOUND"), @@ -392,3 +419,17 @@ def serve( use_evalex=not in_test_env, threaded=not no_threading, ) + + +# Both Gunicorn and RQ use forking to spawn workers. In an ideal world, the fork should be sharing +# most of the memory if there are no writes made to data because of Copy on Write, however, +# python's GC is not CoW friendly and writes to data even if user-code doesn't. Specifically, the +# generational GC which stores and mutates every python object: `PyGC_Head` +# +# Calling gc.freeze() moves all the objects imported so far into permanant generation and hence +# doesn't mutate `PyGC_Head` +# +# Refer to issue for more info: https://github.com/frappe/frappe/issues/18927 +if frappe._tune_gc: + gc.collect() # clean up any garbage created so far before freeze + gc.freeze() diff --git a/frappe/auth.py b/frappe/auth.py index f1cdac52bd..29c3e41694 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -188,10 +188,10 @@ class LoginManager: frappe.response["full_name"] = self.full_name # redirect information - redirect_to = frappe.cache().hget("redirect_after_login", self.user) + redirect_to = frappe.cache.hget("redirect_after_login", self.user) if redirect_to: frappe.local.response["redirect_to"] = redirect_to - frappe.cache().hdel("redirect_after_login", self.user) + frappe.cache.hdel("redirect_after_login", self.user) frappe.local.cookie_manager.set_cookie("full_name", self.full_name) frappe.local.cookie_manager.set_cookie("user_id", self.user) @@ -482,15 +482,15 @@ class LoginAttemptTracker: @property def login_failed_count(self): - return frappe.cache().hget("login_failed_count", self.user_name) + return frappe.cache.hget("login_failed_count", self.user_name) @login_failed_count.setter def login_failed_count(self, count): - frappe.cache().hset("login_failed_count", self.user_name, count) + frappe.cache.hset("login_failed_count", self.user_name, count) @login_failed_count.deleter def login_failed_count(self): - frappe.cache().hdel("login_failed_count", self.user_name) + frappe.cache.hdel("login_failed_count", self.user_name) @property def login_failed_time(self): @@ -498,15 +498,15 @@ class LoginAttemptTracker: For every user we track only First failed login attempt time within lock interval of time. """ - return frappe.cache().hget("login_failed_time", self.user_name) + return frappe.cache.hget("login_failed_time", self.user_name) @login_failed_time.setter def login_failed_time(self, timestamp): - frappe.cache().hset("login_failed_time", self.user_name, timestamp) + frappe.cache.hset("login_failed_time", self.user_name, timestamp) @login_failed_time.deleter def login_failed_time(self): - frappe.cache().hdel("login_failed_time", self.user_name) + frappe.cache.hdel("login_failed_time", self.user_name) def add_failure_attempt(self): """Log user failure attempts into the system. diff --git a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py index 3242145bc4..4316edd1ca 100644 --- a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py @@ -9,7 +9,7 @@ class TestMilestoneTracker(FrappeTestCase): def test_milestone(self): frappe.db.delete("Milestone Tracker") - frappe.cache().delete_key("milestone_tracker_map") + frappe.cache.delete_key("milestone_tracker_map") milestone_tracker = frappe.get_doc( dict(doctype="Milestone Tracker", document_type="ToDo", track_field="status") diff --git a/frappe/boot.py b/frappe/boot.py index 37d89365c4..fb1bd3d7a2 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -21,7 +21,6 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p from frappe.social.doctype.energy_point_settings.energy_point_settings import ( is_energy_point_enabled, ) -from frappe.translate import get_lang_dict, get_messages_for_boot, get_translated_doctypes from frappe.utils import add_user_info, cstr, get_system_timezone from frappe.utils.change_log import get_versions from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled @@ -29,6 +28,8 @@ from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabl def get_bootinfo(): """build and return boot info""" + from frappe.translate import get_lang_dict, get_translated_doctypes + frappe.set_user_lang(frappe.session.user) bootinfo = frappe._dict() hooks = frappe.get_hooks() @@ -149,10 +150,8 @@ def get_allowed_report_names(cache=False) -> set[str]: def get_user_pages_or_reports(parent, cache=False): - _cache = frappe.cache() - if cache: - has_role = _cache.get_value("has_role:" + parent, user=frappe.session.user) + has_role = frappe.cache.get_value("has_role:" + parent, user=frappe.session.user) if has_role: return has_role @@ -254,11 +253,13 @@ def get_user_pages_or_reports(parent, cache=False): has_role.pop(r, None) # Expire every six hours - _cache.set_value("has_role:" + parent, has_role, frappe.session.user, 21600) + frappe.cache.set_value("has_role:" + parent, has_role, frappe.session.user, 21600) return has_role def load_translations(bootinfo): + from frappe.translate import get_messages_for_boot + bootinfo["lang"] = frappe.lang bootinfo["__messages"] = get_messages_for_boot() diff --git a/frappe/build.py b/frappe/build.py index b74afa5d06..5a9855ef16 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -9,9 +9,6 @@ from tempfile import mkdtemp, mktemp from urllib.parse import urlparse import click -import psutil -from requests import head -from requests.exceptions import HTTPError from semantic_version import Version import frappe @@ -27,7 +24,7 @@ class AssetsNotDownloadedError(Exception): pass -class AssetsDontExistError(HTTPError): +class AssetsDontExistError(Exception): pass @@ -78,6 +75,8 @@ def build_missing_files(): def get_assets_link(frappe_head) -> str: + import requests + tag = getoutput( r"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" r" refs/tags/,,' -e 's/\^{}//'" % frappe_head @@ -89,7 +88,7 @@ def get_assets_link(frappe_head) -> str: else: url = f"http://assets.frappeframework.com/{frappe_head}.tar.gz" - if not head(url): + if not requests.head(url): reference = f"Release {tag}" if tag else f"Commit {frappe_head}" raise AssetsDontExistError(f"Assets for {reference} don't exist") @@ -227,11 +226,10 @@ def bundle( mode, apps=None, hard_link=False, - make_copy=False, - restore=False, verbose=False, skip_frappe=False, files=None, + save_metafiles=False, ): """concat / minify js files""" setup() @@ -251,6 +249,9 @@ def bundle( command += " --run-build-command" + if save_metafiles: + command += " --save-metafiles" + check_node_executable() frappe_app_path = frappe.get_app_path("frappe", "..") frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env(), raise_err=True) @@ -277,8 +278,8 @@ def watch(apps=None): def check_node_executable(): node_version = Version(subprocess.getoutput("node -v")[1:]) warn = "⚠️ " - if node_version.major < 14: - click.echo(f"{warn} Please update your node version to 14") + if node_version.major < 18: + click.echo(f"{warn} Please update your node version to 18") if not shutil.which("yarn"): click.echo(f"{warn} Please install yarn using below command and try again.\nnpm install -g yarn") click.echo() @@ -290,6 +291,8 @@ def get_node_env(): def get_safe_max_old_space_size(): + import psutil + safe_max_old_space_size = 0 try: total_memory = psutil.virtual_memory().total / (1024 * 1024) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 9c1754148a..f47478d871 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -1,10 +1,7 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE -import json - import frappe -from frappe.desk.notifications import clear_notifications, delete_notification_count_for common_default_keys = ["__default", "__global"] @@ -79,7 +76,7 @@ doctype_cache_keys = ( def clear_user_cache(user=None): - cache = frappe.cache() + from frappe.desk.notifications import clear_notifications # this will automatically reload the global cache # so it is important to clear this first @@ -87,20 +84,19 @@ def clear_user_cache(user=None): if user: for name in user_cache_keys: - cache.hdel(name, user) - cache.delete_keys("user:" + user) + frappe.cache.hdel(name, user) + frappe.cache.delete_keys("user:" + user) clear_defaults_cache(user) else: for name in user_cache_keys: - cache.delete_key(name) + frappe.cache.delete_key(name) clear_defaults_cache() clear_global_cache() def clear_domain_cache(user=None): - cache = frappe.cache() domain_cache_keys = ("domain_restricted_doctypes", "domain_restricted_pages") - cache.delete_value(domain_cache_keys) + frappe.cache.delete_value(domain_cache_keys) def clear_global_cache(): @@ -108,17 +104,17 @@ def clear_global_cache(): clear_doctype_cache() clear_website_cache() - frappe.cache().delete_value(global_cache_keys) - frappe.cache().delete_value(bench_cache_keys) + frappe.cache.delete_value(global_cache_keys) + frappe.cache.delete_value(bench_cache_keys) frappe.setup_module_map() def clear_defaults_cache(user=None): if user: for p in [user] + common_default_keys: - frappe.cache().hdel("defaults", p) + frappe.cache.hdel("defaults", p) elif frappe.flags.in_install != "frappe": - frappe.cache().delete_key("defaults") + frappe.cache.delete_key("defaults") def clear_doctype_cache(doctype=None): @@ -131,15 +127,15 @@ def clear_doctype_cache(doctype=None): def _clear_doctype_cache_form_redis(doctype: str | None = None): - cache = frappe.cache() + from frappe.desk.notifications import delete_notification_count_for for key in ("is_table", "doctype_modules"): - cache.delete_value(key) + frappe.cache.delete_value(key) def clear_single(dt): frappe.clear_document_cache(dt) for name in doctype_cache_keys: - cache.hdel(name, dt) + frappe.cache.hdel(name, dt) if doctype: clear_single(doctype) @@ -163,8 +159,8 @@ def _clear_doctype_cache_form_redis(doctype: str | None = None): else: # clear all for name in doctype_cache_keys: - cache.delete_value(name) - cache.delete_keys("document_cache::") + frappe.cache.delete_value(name) + frappe.cache.delete_keys("document_cache::") def clear_controller_cache(doctype=None): @@ -177,7 +173,7 @@ def clear_controller_cache(doctype=None): def get_doctype_map(doctype, name, filters=None, order_by=None): - return frappe.cache().hget( + return frappe.cache.hget( get_doctype_map_key(doctype), name, lambda: frappe.get_all(doctype, filters=filters, order_by=order_by, ignore_ddl=True), @@ -185,7 +181,7 @@ def get_doctype_map(doctype, name, filters=None, order_by=None): def clear_doctype_map(doctype, name): - frappe.cache().hdel(frappe.scrub(doctype) + "_map", name) + frappe.cache.hdel(frappe.scrub(doctype) + "_map", name) def build_table_count_cache(): @@ -198,7 +194,6 @@ def build_table_count_cache(): ): return - _cache = frappe.cache() table_name = frappe.qb.Field("table_name").as_("name") table_rows = frappe.qb.Field("table_rows").as_("count") information_schema = frappe.qb.Schema("information_schema") @@ -207,7 +202,7 @@ def build_table_count_cache(): as_dict=True ) counts = {d.get("name").replace("tab", "", 1): d.get("count", None) for d in data} - _cache.set_value("information_schema:counts", counts) + frappe.cache.set_value("information_schema:counts", counts) return counts @@ -221,11 +216,10 @@ def build_domain_restriced_doctype_cache(*args, **kwargs): or frappe.flags.in_setup_wizard ): return - _cache = frappe.cache() active_domains = frappe.get_active_domains() doctypes = frappe.get_all("DocType", filters={"restrict_to_domain": ("IN", active_domains)}) doctypes = [doc.name for doc in doctypes] - _cache.set_value("domain_restricted_doctypes", doctypes) + frappe.cache.set_value("domain_restricted_doctypes", doctypes) return doctypes @@ -239,10 +233,9 @@ def build_domain_restriced_page_cache(*args, **kwargs): or frappe.flags.in_setup_wizard ): return - _cache = frappe.cache() active_domains = frappe.get_active_domains() pages = frappe.get_all("Page", filters={"restrict_to_domain": ("IN", active_domains)}) pages = [page.name for page in pages] - _cache.set_value("domain_restricted_pages", pages) + frappe.cache.set_value("domain_restricted_pages", pages) return pages diff --git a/frappe/commands/redis_utils.py b/frappe/commands/redis_utils.py index 1c3292b7fa..07ad8715c4 100644 --- a/frappe/commands/redis_utils.py +++ b/frappe/commands/redis_utils.py @@ -3,7 +3,6 @@ import os import click import frappe -from frappe.installer import update_site_config from frappe.utils.redis_queue import RedisQueue @@ -23,6 +22,8 @@ def create_rq_users(set_admin_password=False, use_rq_auth=False): acl config file will be used by redis server while starting the server and app config is used by app while connecting to redis server. """ + from frappe.installer import update_site_config + acl_file_path = os.path.abspath("../config/redis_queue.acl") with frappe.init_site(): diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 25c8c3159d..d606bb78cf 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -9,7 +9,6 @@ import click # imports - module imports import frappe from frappe.commands import get_site, pass_context -from frappe.core.doctype.log_settings.log_settings import LOG_DOCTYPES from frappe.exceptions import SiteNotSpecifiedError @@ -1199,11 +1198,12 @@ def build_search_index(context): @click.command("clear-log-table") -@click.option("--doctype", required=True, type=click.Choice(LOG_DOCTYPES), help="Log DocType") +@click.option("--doctype", required=True, type=str, help="Log DocType") @click.option("--days", type=int, help="Keep records for days") @click.option("--no-backup", is_flag=True, default=False, help="Do not backup the table") @pass_context def clear_log_table(context, doctype, days, no_backup): + """If any logtype table grows too large then clearing it with DELETE query is not feasible in reasonable time. This command copies recent data to new table and replaces current table with new smaller table. @@ -1211,6 +1211,7 @@ def clear_log_table(context, doctype, days, no_backup): ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table """ + from frappe.core.doctype.log_settings.log_settings import LOG_DOCTYPES from frappe.core.doctype.log_settings.log_settings import clear_log_table as clear_logs from frappe.utils.backups import scheduled_backup diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 69970d8d97..5042843405 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -102,10 +102,28 @@ def import_translations(context, lang, path): frappe.destroy() +@click.command("migrate-translations") +@click.argument("source-app") +@click.argument("target-app") +@pass_context +def migrate_translations(context, source_app, target_app): + "Migrate target-app-specific translations from source-app to target-app" + import frappe.translate + + site = get_site(context) + try: + frappe.init(site=site) + frappe.connect() + frappe.translate.migrate_translations(source_app, target_app) + finally: + frappe.destroy() + + commands = [ build_message_files, get_untranslated, import_translations, new_language, update_translations, + migrate_translations, ] diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index e44009a886..e77376b693 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -13,10 +13,6 @@ from frappe.exceptions import SiteNotSpecifiedError from frappe.utils import cint, update_progress_bar find_executable = which # backwards compatibility -DATA_IMPORT_DEPRECATION = ( - "[DEPRECATED] The `import-csv` command used 'Data Import Legacy' which has been deprecated.\n" - "Use `data-import` command instead to import data via 'Data Import'." -) EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True} @@ -30,32 +26,25 @@ EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True} help="Copy the files instead of symlinking", envvar="FRAPPE_HARD_LINK_ASSETS", ) -@click.option( - "--make-copy", - is_flag=True, - default=False, - help="[DEPRECATED] Copy the files instead of symlinking", -) -@click.option( - "--restore", - is_flag=True, - default=False, - help="[DEPRECATED] Copy the files instead of symlinking with force", -) @click.option("--production", is_flag=True, default=False, help="Build assets in production mode") @click.option("--verbose", is_flag=True, default=False, help="Verbose") @click.option( "--force", is_flag=True, default=False, help="Force build assets instead of downloading available" ) +@click.option( + "--save-metafiles", + is_flag=True, + default=False, + help="Saves esbuild metafiles for built assets. Useful for analyzing bundle size. More info: https://esbuild.github.io/api/#metafile", +) def build( app=None, apps=None, hard_link=False, - make_copy=False, - restore=False, production=False, verbose=False, force=False, + save_metafiles=False, ): "Compile JS and CSS source files" from frappe.build import bundle, download_frappe_assets @@ -80,14 +69,14 @@ def build( if production: mode = "production" - if make_copy or restore: - hard_link = make_copy or restore - click.secho( - "bench build: --make-copy and --restore options are deprecated in favour of --hard-link", - fg="yellow", - ) - - bundle(mode, apps=apps, hard_link=hard_link, verbose=verbose, skip_frappe=skip_frappe) + bundle( + mode, + apps=apps, + hard_link=hard_link, + verbose=verbose, + skip_frappe=skip_frappe, + save_metafiles=save_metafiles, + ) @click.command("watch") @@ -409,37 +398,16 @@ def import_doc(context, path, force=False): raise SiteNotSpecifiedError -@click.command("import-csv", help=DATA_IMPORT_DEPRECATION) -@click.argument("path") -@click.option( - "--only-insert", default=False, is_flag=True, help="Do not overwrite existing records" -) -@click.option( - "--submit-after-import", default=False, is_flag=True, help="Submit document after importing it" -) -@click.option( - "--ignore-encoding-errors", - default=False, - is_flag=True, - help="Ignore encoding errors while coverting to unicode", -) -@click.option("--no-email", default=True, is_flag=True, help="Send email if applicable") -@pass_context -def import_csv( - context, - path, - only_insert=False, - submit_after_import=False, - ignore_encoding_errors=False, - no_email=True, -): - click.secho(DATA_IMPORT_DEPRECATION, fg="yellow") - sys.exit(1) - - @click.command("data-import") @click.option( - "--file", "file_path", type=click.Path(), required=True, help="Path to import file (.csv, .xlsx)" + "--file", + "file_path", + type=click.Path(exists=True, dir_okay=False, resolve_path=True), + required=True, + help=( + "Path to import file (.csv, .xlsx)." + "Consider that relative paths will resolve from 'sites' directory" + ), ) @click.option("--doctype", type=str, required=True) @click.option( @@ -765,7 +733,6 @@ def transform_database(context, table, engine, row_format, failfast): help="Path to .txt file for list of doctypes. Example erpnext/tests/server/agriculture.txt", ) @click.option("--test", multiple=True, help="Specific test") -@click.option("--ui-tests", is_flag=True, default=False, help="Run UI Tests") @click.option("--module", help="Run tests in a module") @click.option("--profile", is_flag=True, default=False) @click.option("--coverage", is_flag=True, default=False) @@ -788,7 +755,6 @@ def run_tests( profile=False, coverage=False, junit_xml_output=False, - ui_tests=False, doctype_list_path=None, skip_test_records=False, skip_before_tests=False, @@ -827,7 +793,6 @@ def run_tests( force=context.force, profile=profile, junit_xml_output=junit_xml_output, - ui_tests=ui_tests, doctype_list_path=doctype_list_path, failfast=failfast, case=case, @@ -1063,20 +1028,11 @@ def create_patch(): "-g", "--global", "global_", is_flag=True, default=False, help="Set value in bench config" ) @click.option("-p", "--parse", is_flag=True, default=False, help="Evaluate as Python Object") -@click.option("--as-dict", is_flag=True, default=False, help="Legacy: Evaluate as Python Object") @pass_context -def set_config(context, key, value, global_=False, parse=False, as_dict=False): +def set_config(context, key, value, global_=False, parse=False): "Insert/Update a value in site_config.json" from frappe.installer import update_site_config - if as_dict: - from frappe.utils.commands import warn - - warn( - "--as-dict will be deprecated in v14. Use --parse instead", category=PendingDeprecationWarning - ) - parse = as_dict - if parse: import ast @@ -1200,7 +1156,6 @@ commands = [ export_fixtures, export_json, get_version, - import_csv, data_import, import_doc, make_app, diff --git a/frappe/core/doctype/amended_document_naming_settings/__init__.py b/frappe/core/doctype/amended_document_naming_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.json b/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.json new file mode 100644 index 0000000000..2892cc6091 --- /dev/null +++ b/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.json @@ -0,0 +1,44 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-06-16 17:57:36.604672", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "action" + ], + "fields": [ + { + "default": "Amend Counter", + "fieldname": "action", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Action", + "options": "Amend Counter\nDefault Naming", + "reqd": 1 + }, + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1, + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-06-16 18:26:16.247475", + "modified_by": "Administrator", + "module": "Core", + "name": "Amended Document Naming Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.py b/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.py new file mode 100644 index 0000000000..91b31350b0 --- /dev/null +++ b/frappe/core/doctype/amended_document_naming_settings/amended_document_naming_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class AmendedDocumentNamingSettings(Document): + pass diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 20a8e7db9b..6d6e34d97d 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -62,7 +62,7 @@ class Importer: def before_import(self): # set user lang for translations - frappe.cache().hdel("lang", frappe.session.user) + frappe.cache.hdel("lang", frappe.session.user) frappe.set_user_lang(frappe.session.user) # set flags @@ -579,6 +579,10 @@ class ImportFile: file_content = None + if self.console: + file_content = frappe.read_file(file_path, True) + return file_content, extn + file_name = frappe.db.get_value("File", {"file_url": file_path}) if file_name: file = frappe.get_doc("File", file_name) @@ -690,7 +694,7 @@ class Row: df = col.df if df.fieldtype == "Select": select_options = get_select_options(df) - if select_options and value not in select_options: + if select_options and cstr(value) not in select_options: options_string = ", ".join(frappe.bold(d) for d in select_options) msg = _("Value must be one of {0}").format(options_string) self.warnings.append( @@ -1207,7 +1211,7 @@ def get_df_for_column_header(doctype, header): def build_fields_dict_for_doctype(): return build_fields_dict_for_column_matching(doctype) - df_by_labels_and_fieldname = frappe.cache().hget( + df_by_labels_and_fieldname = frappe.cache.hget( "data_import_column_header_map", doctype, generator=build_fields_dict_for_doctype ) return df_by_labels_and_fieldname.get(header) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 90b1c6cb77..f297e0dbe6 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -21,6 +21,7 @@ "search_index", "column_break_18", "options", + "sort_options", "show_dashboard", "defaults_section", "default", @@ -102,7 +103,8 @@ "oldfieldtype": "Select", "options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, - "search_index": 1 + "search_index": 1, + "sort_options": 1 }, { "bold": 1, @@ -550,13 +552,20 @@ "fieldtype": "Data", "label": "Documentation URL", "options": "URL" + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype === 'Select'", + "fieldname": "sort_options", + "fieldtype": "Check", + "label": "Sort Options" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-02-20 12:07:29.552523", + "modified": "2023-06-08 19:05:10.778371", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/docshare/docshare.json b/frappe/core/doctype/docshare/docshare.json index ca10b05dac..e3581f516e 100644 --- a/frappe/core/doctype/docshare/docshare.json +++ b/frappe/core/doctype/docshare/docshare.json @@ -67,7 +67,8 @@ "default": "0", "fieldname": "everyone", "fieldtype": "Check", - "label": "Everyone" + "label": "Everyone", + "search_index": 1 }, { "default": "1", @@ -85,10 +86,11 @@ ], "in_create": 1, "links": [], - "modified": "2021-04-04 11:38:50.813312", + "modified": "2023-06-15 18:02:51.877533", "modified_by": "Administrator", "module": "Core", "name": "DocShare", + "naming_rule": "Random", "owner": "Administrator", "permissions": [ { @@ -106,5 +108,6 @@ "read_only": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/core/doctype/doctype/boilerplate/__init__.py b/frappe/core/doctype/doctype/boilerplate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 842898d064..5209a408eb 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -755,4 +755,4 @@ "states": [], "track_changes": 1, "translated_doctype": 1 -} \ No newline at end of file +} diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 12545adb4e..6a1887f14a 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -337,7 +337,7 @@ class DocType(Document): "DocField", "parent", dict(fieldtype=["in", frappe.model.table_fields], options=self.name) ) for p in parent_list: - frappe.db.set_value("DocType", p.parent, {}, for_update=False) + frappe.db.set_value("DocType", p.parent, {}) def scrub_field_names(self): """Sluggify fieldnames if not set from Label.""" @@ -1710,7 +1710,7 @@ def check_fieldname_conflicts(docfield): def clear_linked_doctype_cache(): - frappe.cache().delete_value("linked_doctypes_without_ignore_user_permissions_enabled") + frappe.cache.delete_value("linked_doctypes_without_ignore_user_permissions_enabled") def check_email_append_to(doc): diff --git a/frappe/core/doctype/document_naming_settings/document_naming_settings.js b/frappe/core/doctype/document_naming_settings/document_naming_settings.js index 2a9ec4aae5..f19e197249 100644 --- a/frappe/core/doctype/document_naming_settings/document_naming_settings.js +++ b/frappe/core/doctype/document_naming_settings/document_naming_settings.js @@ -2,6 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on("Document Naming Settings", { + setup: function (frm) { + frm.set_query("document_type", "amend_naming_override", () => { + return { + filters: { + is_submittable: 1, + }, + }; + }); + }, + refresh: function (frm) { frm.trigger("setup_transaction_autocomplete"); frm.disable_save(); diff --git a/frappe/core/doctype/document_naming_settings/document_naming_settings.json b/frappe/core/doctype/document_naming_settings/document_naming_settings.json index 9a12f3f77e..5a1991c14b 100644 --- a/frappe/core/doctype/document_naming_settings/document_naming_settings.json +++ b/frappe/core/doctype/document_naming_settings/document_naming_settings.json @@ -18,7 +18,11 @@ "update_series", "prefix", "current_value", - "update_series_start" + "update_series_start", + "amended_documents_section", + "default_amend_naming", + "amend_naming_override", + "update_amendment_naming" ], "fields": [ { @@ -105,13 +109,41 @@ "fieldtype": "Text", "label": "Preview of generated names", "read_only": 1 + }, + { + "collapsible": 1, + "description": "Configure how amended documents will be named.\n\nDefault behaviour is to follow an amend counter which adds a number to the end of the original name indicating the amended version. \n\nDefault Naming will make the amended document to behave same as new documents.", + "fieldname": "amended_documents_section", + "fieldtype": "Section Break", + "label": "Amended Documents" + }, + { + "default": "Amend Counter", + "fieldname": "default_amend_naming", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Default Amendment Naming", + "options": "Amend Counter\nDefault Naming", + "reqd": 1 + }, + { + "fieldname": "amend_naming_override", + "fieldtype": "Table", + "label": "Amendment Naming Override", + "options": "Amended Document Naming Settings" + }, + { + "fieldname": "update_amendment_naming", + "fieldtype": "Button", + "label": "Update Amendment Naming", + "options": "update_amendment_rule" } ], "hide_toolbar": 1, "icon": "fa fa-sort-by-order", "issingle": 1, "links": [], - "modified": "2023-02-20 13:11:56.662100", + "modified": "2023-06-20 17:47:52.204139", "modified_by": "Administrator", "module": "Core", "name": "Document Naming Settings", diff --git a/frappe/core/doctype/document_naming_settings/document_naming_settings.py b/frappe/core/doctype/document_naming_settings/document_naming_settings.py index f8647bd74a..625b7cdd50 100644 --- a/frappe/core/doctype/document_naming_settings/document_naming_settings.py +++ b/frappe/core/doctype/document_naming_settings/document_naming_settings.py @@ -169,6 +169,23 @@ class DocumentNamingSettings(Document): self.current_value = NamingSeries(self.prefix).get_current_value() return self.current_value + @frappe.whitelist() + def update_amendment_rule(self): + self.db_set("default_amend_naming", self.default_amend_naming) + + existing_overrides = frappe.db.get_all( + "Amended Document Naming Settings", + filters={"name": ["not in", [d.name for d in self.amend_naming_override]]}, + pluck="name", + ) + for override in existing_overrides: + frappe.delete_doc("Amended Document Naming Settings", override) + + for row in self.amend_naming_override: + row.save() + + frappe.msgprint(_("Amendment naming rules updated."), indicator="green", alert=True) + @frappe.whitelist() def update_series_start(self): frappe.only_for("System Manager") diff --git a/frappe/core/doctype/document_naming_settings/test_document_naming_settings.py b/frappe/core/doctype/document_naming_settings/test_document_naming_settings.py index bcd3197112..d1a6fbe90d 100644 --- a/frappe/core/doctype/document_naming_settings/test_document_naming_settings.py +++ b/frappe/core/doctype/document_naming_settings/test_document_naming_settings.py @@ -26,6 +26,7 @@ class TestNamingSeries(FrappeTestCase): } ], autoname="naming_series:", + is_submittable=1, ) .insert() .name @@ -82,3 +83,36 @@ class TestNamingSeries(FrappeTestCase): self.dns.update_series_start() self.assertEqual(self.dns.get_current(), new_count, f"Incorrect update for {series}") + + def test_amended_naming(self): + self.dns.amend_naming_override = [] + self.dns.default_amend_naming = "Amend Counter" + self.dns.update_amendment_rule() + + submittable_doc = frappe.get_doc( + dict(doctype=self.ns_doctype, some_fieldname="test doc with submit") + ).submit() + submittable_doc.cancel() + + amended_doc = frappe.get_doc( + dict( + doctype=self.ns_doctype, + some_fieldname="test doc with submit", + amended_from=submittable_doc.name, + ) + ).insert() + + self.assertIn(submittable_doc.name, amended_doc.name) + amended_doc.delete() + + self.dns.default_amend_naming = "Default Naming" + self.dns.update_amendment_rule() + + new_amended_doc = frappe.get_doc( + dict( + doctype=self.ns_doctype, + some_fieldname="test doc with submit", + amended_from=submittable_doc.name, + ) + ).insert() + self.assertNotIn(submittable_doc.name, new_amended_doc.name) diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py index 85b26f53dd..d963a14830 100644 --- a/frappe/core/doctype/domain_settings/domain_settings.py +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -73,7 +73,7 @@ def get_active_domains(): active_domains.append("") return active_domains - return frappe.cache().get_value("active_domains", _get_active_domains) + return frappe.cache.get_value("active_domains", _get_active_domains) def get_active_modules(): @@ -87,4 +87,4 @@ def get_active_modules(): active_modules.append(m.name) return active_modules - return frappe.cache().get_value("active_modules", _get_active_modules) + return frappe.cache.get_value("active_modules", _get_active_modules) diff --git a/frappe/core/doctype/error_log/test_error_log.py b/frappe/core/doctype/error_log/test_error_log.py index 59670de8d2..3f19a6dd0c 100644 --- a/frappe/core/doctype/error_log/test_error_log.py +++ b/frappe/core/doctype/error_log/test_error_log.py @@ -1,7 +1,10 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # License: MIT. See LICENSE +from ldap3.core.exceptions import LDAPException, LDAPInappropriateAuthenticationResult + import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils.error import _is_ldap_exception # test_records = frappe.get_test_records('Error Log') @@ -12,3 +15,9 @@ class TestErrorLog(FrappeTestCase): doc = frappe.new_doc("Error Log") error = doc.log_error("This is an error") self.assertEqual(error.doctype, "Error Log") + + def test_ldap_exceptions(self): + exc = [LDAPException, LDAPInappropriateAuthenticationResult] + + for e in exc: + self.assertTrue(_is_ldap_exception(e())) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index c4cefc7271..7770226e79 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -236,12 +236,19 @@ class File(Document): ): return - frappe.db.set_value( - self.attached_to_doctype, - self.attached_to_name, - self.attached_to_field, - self.file_url, - ) + if frappe.get_meta(self.attached_to_doctype).issingle: + frappe.db.set_single_value( + self.attached_to_doctype, + self.attached_to_field, + self.file_url, + ) + else: + frappe.db.set_value( + self.attached_to_doctype, + self.attached_to_name, + self.attached_to_field, + self.file_url, + ) def fetch_attached_to_field(self, old_file_url): if self.attached_to_field: diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 9b2a2ccc18..8cdbc24074 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -137,7 +137,7 @@ class Report(Document): if execution_time > threshold and not self.prepared_report: self.db_set("prepared_report", 1) - frappe.cache().hset("report_execution_time", self.name, execution_time) + frappe.cache.hset("report_execution_time", self.name, execution_time) return res diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index 31b82501cb..8e5ec269ea 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -24,7 +24,7 @@ class Role(Document): frappe.throw(frappe._("Standard roles cannot be renamed")) def after_insert(self): - frappe.cache().hdel("roles", "Administrator") + frappe.cache.hdel("roles", "Administrator") def validate(self): if self.disabled: diff --git a/frappe/core/doctype/rq_job/test_rq_job.py b/frappe/core/doctype/rq_job/test_rq_job.py index 09a90f7445..c39717cfd8 100644 --- a/frappe/core/doctype/rq_job/test_rq_job.py +++ b/frappe/core/doctype/rq_job/test_rq_job.py @@ -124,6 +124,20 @@ class TestRQJob(FrappeTestCase): frappe.db.commit() self.assertIsNone(get_job_status(job_id)) + @timeout(20) + def test_memory_usage(self): + job = frappe.enqueue("frappe.utils.data._get_rss_memory_usage") + self.check_status(job, "finished") + + rss = job.latest_result().return_value + msg = """Memory usage of simple background job increased. Potential root cause can be a newly added python module import. Check and move them to approriate file/function to avoid loading the module by default.""" + + # If this starts failing analyze memory usage using memray or some equivalent tool to find + # offending imports/function calls. + # Refer this PR: https://github.com/frappe/frappe/pull/21467 + LAST_MEASURED_USAGE = 40 + self.assertLessEqual(rss, LAST_MEASURED_USAGE * 1.05, msg) + def test_func(fail=False, sleep=0): if fail: diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index bbc92dfbc9..7edad24ac4 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -18,7 +18,7 @@ class TestScheduledJobType(FrappeTestCase): self.assertEqual(all_job.frequency, "All") daily_job = frappe.get_doc( - "Scheduled Job Type", dict(method="frappe.email.queue.set_expiry_for_email_queue") + "Scheduled Job Type", dict(method="frappe.desk.notifications.clear_notifications") ) self.assertEqual(daily_job.frequency, "Daily") @@ -37,7 +37,7 @@ class TestScheduledJobType(FrappeTestCase): def test_daily_job(self): job = frappe.get_doc( - "Scheduled Job Type", dict(method="frappe.email.queue.set_expiry_for_email_queue") + "Scheduled Job Type", dict(method="frappe.desk.notifications.clear_notifications") ) job.db_set("last_execution", "2019-01-01 00:00:00") self.assertTrue(job.is_event_due(get_datetime("2019-01-02 00:00:06"))) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 07808d619b..758bd46a76 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -19,7 +19,7 @@ class ServerScript(Document): self.check_if_compilable_in_restricted_context() def on_update(self): - frappe.cache().delete_value("server_script_map") + frappe.cache.delete_value("server_script_map") self.sync_scheduler_events() def on_trash(self): @@ -168,11 +168,11 @@ class ServerScript(Document): out.append([key, score]) return out - items = frappe.cache().get_value("server_script_autocompletion_items") + items = frappe.cache.get_value("server_script_autocompletion_items") if not items: items = get_keys(get_safe_globals()) items = [{"value": d[0], "score": d[1]} for d in items] - frappe.cache().set_value("server_script_autocompletion_items", items) + frappe.cache.set_value("server_script_autocompletion_items", items) return items diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py index b807b43d10..6ba65e7353 100644 --- a/frappe/core/doctype/server_script/server_script_utils.py +++ b/frappe/core/doctype/server_script/server_script_utils.py @@ -55,7 +55,7 @@ def get_server_script_map(): if frappe.flags.in_patch and not frappe.db.table_exists("Server Script"): return {} - script_map = frappe.cache().get_value("server_script_map") + script_map = frappe.cache.get_value("server_script_map") if script_map is None: script_map = {"permission_query": {}} enabled_server_scripts = frappe.get_all( @@ -73,6 +73,6 @@ def get_server_script_map(): else: script_map.setdefault("_api", {})[script.api_method] = script.name - frappe.cache().set_value("server_script_map", script_map) + frappe.cache.set_value("server_script_map", script_map) return script_map diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 4371806b32..af1352f02b 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -104,10 +104,10 @@ class TestServerScript(FrappeTestCase): def tearDownClass(cls): frappe.db.commit() frappe.db.truncate("Server Script") - frappe.cache().delete_value("server_script_map") + frappe.cache.delete_value("server_script_map") def setUp(self): - frappe.cache().delete_value("server_script_map") + frappe.cache.delete_value("server_script_map") def test_doctype_event(self): todo = frappe.get_doc(dict(doctype="ToDo", description="hello")).insert() diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index c4f35f3cc0..0c842f9c7d 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -5,14 +5,13 @@ import frappe from frappe import _ from frappe.model import no_value_fields from frappe.model.document import Document -from frappe.translate import set_default_language -from frappe.twofactor import toggle_two_factor_auth from frappe.utils import cint, today -from frappe.utils.momentjs import get_all_timezones class SystemSettings(Document): def validate(self): + from frappe.twofactor import toggle_two_factor_auth + enable_password_policy = cint(self.enable_password_policy) and True or False minimum_password_score = cint(getattr(self, "minimum_password_score", 0)) or 0 if enable_password_policy and minimum_password_score <= 0: @@ -64,13 +63,15 @@ class SystemSettings(Document): def on_update(self): self.set_defaults() - frappe.cache().delete_value("system_settings") - frappe.cache().delete_value("time_zone") + frappe.cache.delete_value("system_settings") + frappe.cache.delete_value("time_zone") if frappe.flags.update_last_reset_password_date: update_last_reset_password_date() def set_defaults(self): + from frappe.translate import set_default_language + for df in self.meta.get("fields"): if df.fieldtype not in no_value_fields and self.has_value_changed(df.fieldname): frappe.db.set_default(df.fieldname, self.get(df.fieldname)) @@ -92,6 +93,8 @@ def update_last_reset_password_date(): @frappe.whitelist() def load(): + from frappe.utils.momentjs import get_all_timezones + if not "System Manager" in frappe.get_roles(): frappe.throw(_("Not permitted"), frappe.PermissionError) diff --git a/frappe/core/doctype/translation/translation.py b/frappe/core/doctype/translation/translation.py index 6afad00fad..c8226db5b0 100644 --- a/frappe/core/doctype/translation/translation.py +++ b/frappe/core/doctype/translation/translation.py @@ -23,71 +23,7 @@ class Translation(Document): def on_trash(self): clear_user_translation_cache(self.language) - def contribute(self): - pass - - def get_contribution_status(self): - pass - - -@frappe.whitelist() -def create_translations(translation_map, language): - from frappe.frappeclient import FrappeClient - - translation_map = json.loads(translation_map) - translation_map_to_send = frappe._dict({}) - # first create / update local user translations - for source_id, translation_dict in translation_map.items(): - translation_dict = frappe._dict(translation_dict) - existing_doc_name = frappe.get_all( - "Translation", - { - "source_text": translation_dict.source_text, - "context": translation_dict.context or "", - "language": language, - }, - ) - translation_map_to_send[source_id] = translation_dict - if existing_doc_name: - frappe.db.set_value( - "Translation", - existing_doc_name[0].name, - { - "translated_text": translation_dict.translated_text, - "contributed": 1, - "contribution_status": "Pending", - }, - ) - translation_map_to_send[source_id].name = existing_doc_name[0].name - else: - doc = frappe.get_doc( - { - "doctype": "Translation", - "source_text": translation_dict.source_text, - "contributed": 1, - "contribution_status": "Pending", - "translated_text": translation_dict.translated_text, - "context": translation_dict.context, - "language": language, - } - ) - doc.insert() - translation_map_to_send[source_id].name = doc.name - - params = { - "language": language, - "contributor_email": frappe.session.user, - "contributor_name": frappe.utils.get_fullname(frappe.session.user), - "translation_map": json.dumps(translation_map_to_send), - } - - translator = FrappeClient(get_translator_url()) - added_translations = translator.post_api("translator.api.add_translations", params=params) - - for local_docname, remote_docname in added_translations.items(): - frappe.db.set_value("Translation", local_docname, "contribution_docname", remote_docname) - def clear_user_translation_cache(lang): - frappe.cache().hdel(USER_TRANSLATION_KEY, lang) - frappe.cache().hdel(MERGED_TRANSLATION_KEY, lang) + frappe.cache.hdel(USER_TRANSLATION_KEY, lang) + frappe.cache.hdel(MERGED_TRANSLATION_KEY, lang) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index d39d2062eb..b4d69d23d5 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -283,7 +283,7 @@ class TestUser(FrappeTestCase): # Clear rate limit tracker to start fresh key = f"rl:{data['cmd']}:{data['user']}" - frappe.cache().delete(key) + frappe.cache.delete(key) c = FrappeClient(url) res1 = c.session.post(url, data=data, verify=c.verify, headers=c.headers) @@ -330,7 +330,7 @@ class TestUser(FrappeTestCase): sign_up(random_user, random_user_name, "/welcome"), (1, "Please check your email for verification"), ) - self.assertEqual(frappe.cache().hget("redirect_after_login", random_user), "/welcome") + self.assertEqual(frappe.cache.hget("redirect_after_login", random_user), "/welcome") # re-register self.assertTupleEqual( diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 81d9715c32..9bcc9ebd3d 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -28,6 +28,7 @@ from frappe.utils import ( now_datetime, today, ) +from frappe.utils.deprecations import deprecated from frappe.utils.password import check_password, get_password_reset_limit from frappe.utils.password import update_password as _update_password from frappe.utils.user import get_system_managers @@ -60,8 +61,8 @@ class User(Document): def after_insert(self): create_notification_settings(self.name) - frappe.cache().delete_key("users_for_mentions") - frappe.cache().delete_key("enabled_users") + frappe.cache.delete_key("users_for_mentions") + frappe.cache.delete_key("enabled_users") def validate(self): # clear new password @@ -75,6 +76,7 @@ class User(Document): self.validate_email_type(self.email) self.validate_email_type(self.name) self.add_system_manager_role() + self.populate_role_profile_roles() self.check_roles_added() self.set_system_user() self.set_full_name() @@ -85,7 +87,6 @@ class User(Document): self.remove_disabled_roles() self.validate_user_email_inbox() ask_pass_update() - self.validate_roles() self.validate_allowed_modules() self.validate_user_image() self.set_time_zone() @@ -98,12 +99,16 @@ class User(Document): ): self.set_social_login_userid("frappe", frappe.generate_hash(length=39)) - def validate_roles(self): + def populate_role_profile_roles(self): if self.role_profile_name: role_profile = frappe.get_doc("Role Profile", self.role_profile_name) self.set("roles", []) self.append_roles(*[role.role for role in role_profile.roles]) + @deprecated + def validate_roles(self): + self.populate_role_profile_roles() + def validate_allowed_modules(self): if self.module_profile: module_profile = frappe.get_doc("Module Profile", self.module_profile) @@ -143,10 +148,10 @@ class User(Document): frappe.defaults.set_default("time_zone", self.time_zone, self.name) if self.has_value_changed("enabled"): - frappe.cache().delete_key("users_for_mentions") - frappe.cache().delete_key("enabled_users") + frappe.cache.delete_key("users_for_mentions") + frappe.cache.delete_key("enabled_users") elif self.has_value_changed("allow_in_mentions") or self.has_value_changed("user_type"): - frappe.cache().delete_key("users_for_mentions") + frappe.cache.delete_key("users_for_mentions") def has_website_permission(self, ptype, user, verbose=False): """Returns true if current user is the session user""" @@ -462,9 +467,9 @@ class User(Document): frappe.delete_doc("Notification Settings", self.name, ignore_permissions=True) if self.get("allow_in_mentions"): - frappe.cache().delete_key("users_for_mentions") + frappe.cache.delete_key("users_for_mentions") - frappe.cache().delete_key("enabled_users") + frappe.cache.delete_key("enabled_users") # delete user permissions frappe.db.delete("User Permission", {"user": self.name}) @@ -760,10 +765,10 @@ def update_password( user_doc, redirect_url = reset_user_data(user) # get redirect url from cache - redirect_to = frappe.cache().hget("redirect_after_login", user) + redirect_to = frappe.cache.hget("redirect_after_login", user) if redirect_to: redirect_url = redirect_to - frappe.cache().hdel("redirect_after_login", user) + frappe.cache.hdel("redirect_after_login", user) frappe.local.login_manager.login_as(user) @@ -921,7 +926,7 @@ def sign_up(email: str, full_name: str, redirect_to: str) -> tuple[int, str]: user.add_roles(default_role) if redirect_to: - frappe.cache().hset("redirect_after_login", user.name, redirect_to) + frappe.cache.hset("redirect_after_login", user.name, redirect_to) if user.flags.email_sent: return 1, _("Please check your email for verification") @@ -1234,4 +1239,4 @@ def get_enabled_users(): enabled_users = frappe.get_all("User", filters={"enabled": "1"}, pluck="name") return enabled_users - return frappe.cache().get_value("enabled_users", _get_enabled_users) + return frappe.cache.get_value("enabled_users", _get_enabled_users) diff --git a/frappe/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py index 812f230f7a..7acdec3aaa 100644 --- a/frappe/core/doctype/user_group/user_group.py +++ b/frappe/core/doctype/user_group/user_group.py @@ -9,7 +9,7 @@ from frappe.model.document import Document class UserGroup(Document): def after_insert(self): - frappe.cache().delete_key("user_groups") + frappe.cache.delete_key("user_groups") def on_trash(self): - frappe.cache().delete_key("user_groups") + frappe.cache.delete_key("user_groups") diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 8742d2e040..a38ec4d379 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -178,7 +178,7 @@ class TestUserPermission(FrappeTestCase): frappe.db.set_value( "User Permission", {"allow": "Person", "for_value": parent_record.name}, "hide_descendants", 1 ) - frappe.cache().delete_value("user_permissions") + frappe.cache.delete_value("user_permissions") # check if adding perm on a group record with hide_descendants enabled, # hides child records diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 63c1f40512..57214b82e2 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -17,11 +17,11 @@ class UserPermission(Document): self.validate_default_permission() def on_update(self): - frappe.cache().hdel("user_permissions", self.user) + frappe.cache.hdel("user_permissions", self.user) frappe.publish_realtime("update_user_permissions", user=self.user, after_commit=True) def on_trash(self): - frappe.cache().hdel("user_permissions", self.user) + frappe.cache.hdel("user_permissions", self.user) frappe.publish_realtime("update_user_permissions", user=self.user, after_commit=True) def validate_user_permission(self): @@ -74,7 +74,7 @@ def get_user_permissions(user=None): if not user or user in ("Administrator", "Guest"): return {} - cached_user_permissions = frappe.cache().hget("user_permissions", user) + cached_user_permissions = frappe.cache.hget("user_permissions", user) if cached_user_permissions is not None: return cached_user_permissions @@ -110,7 +110,7 @@ def get_user_permissions(user=None): add_doc_to_perm(perm, doc, False) out = frappe._dict(out) - frappe.cache().hset("user_permissions", user, out) + frappe.cache.hset("user_permissions", user, out) except frappe.db.SQLError as e: if frappe.db.is_table_missing(e): # called from patch diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 39d9133412..9660963c19 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -18,7 +18,7 @@ class UserType(Document): super().clear_cache() if not self.is_standard: - frappe.cache().delete_value("non_standard_user_types") + frappe.cache.delete_value("non_standard_user_types") def on_update(self): if self.is_standard: @@ -290,7 +290,7 @@ def apply_permissions_for_non_standard_user_type(doc, method=None): if not frappe.db.table_exists("User Type") or frappe.flags.in_migrate: return - user_types = frappe.cache().get_value( + user_types = frappe.cache.get_value( "non_standard_user_types", get_non_standard_user_types, ) diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 5ed3014778..fd879095c0 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -123,8 +123,15 @@ def update(doctype, role, permlevel, ptype, value=None): Returns: str: Refresh flag is permission is updated successfully """ + + def clear_cache(): + frappe.clear_cache(doctype=doctype) + frappe.only_for("System Manager") out = update_permission_property(doctype, role, permlevel, ptype, value) + + frappe.db.after_commit.add(clear_cache) + return "refresh" if out else None diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 63be70c644..b685d69192 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -21,6 +21,7 @@ "hide_seconds", "hide_days", "options", + "sort_options", "fetch_from", "fetch_if_empty", "options_help", @@ -126,7 +127,8 @@ "oldfieldname": "fieldtype", "oldfieldtype": "Select", "options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", - "reqd": 1 + "reqd": 1, + "sort_options": 1 }, { "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", @@ -435,13 +437,20 @@ "fieldtype": "Check", "label": "Is System Generated", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype === 'Select'", + "fieldname": "sort_options", + "fieldtype": "Check", + "label": "Sort Options" } ], "icon": "fa fa-glass", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-06-13 06:39:03.319667", + "modified": "2023-06-08 19:05:51.737234", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 8953153be6..ed6296b6f2 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -40,8 +40,9 @@ class CustomField(Document): # remove special characters from fieldname self.fieldname = "".join( - filter(lambda x: x.isdigit() or x.isalpha() or "_", cstr(label).replace(" ", "_")) + [c for c in cstr(label).replace(" ", "_") if c.isdigit() or c.isalpha() or c == "_"] ) + self.fieldname = f"custom_{self.fieldname}" # fieldnames should be lowercase self.fieldname = self.fieldname.lower() diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 8549c239e5..bd711c169d 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -49,14 +49,6 @@ frappe.ui.form.on("Customize Form", { grid_row.row.addClass("highlight"); } }); - - $(frm.wrapper).on("grid-make-sortable", function (e, frm) { - frm.trigger("setup_sortable"); - }); - - $(frm.wrapper).on("grid-move-row", function (e, frm) { - frm.trigger("setup_sortable"); - }); }, doc_type: function (frm) { @@ -71,7 +63,7 @@ frappe.ui.form.on("Customize Form", { frm.set_value("doc_type", ""); } else { frm.refresh(); - frm.trigger("setup_sortable"); + frm.trigger("add_customize_child_table_button"); frm.trigger("setup_default_views"); } } @@ -87,23 +79,16 @@ frappe.ui.form.on("Customize Form", { frm.trigger("setup_default_views"); }, - setup_sortable: function (frm) { + add_customize_child_table_button: function (frm) { frm.doc.fields.forEach(function (f) { - if (!f.is_custom_field || f.is_system_generated) { - f._sortable = false; - } + if (!in_list(["Table", "Table MultiSelect"], f.fieldtype)) return; - if (f.fieldtype == "Table") { - frm.add_custom_button( - f.options, - function () { - frm.set_value("doc_type", f.options); - }, - __("Customize Child Table") - ); - } + frm.add_custom_button( + f.options, + () => frm.set_value("doc_type", f.options), + __("Customize Child Table") + ); }); - frm.fields_dict.fields.grid.refresh(); }, refresh: function (frm) { @@ -125,6 +110,14 @@ frappe.ui.form.on("Customize Form", { __("Actions") ); + frm.add_custom_button( + __("Set Permissions"), + function () { + frappe.set_route("permission-manager", frm.doc.doc_type); + }, + __("Actions") + ); + frm.add_custom_button( __("Reload"), function () { @@ -134,17 +127,17 @@ frappe.ui.form.on("Customize Form", { ); frm.add_custom_button( - __("Reset to defaults"), - function () { - frappe.customize_form.confirm(__("Remove all customizations?"), frm); + __("Reset Layout"), + () => { + frm.trigger("reset_layout"); }, __("Actions") ); frm.add_custom_button( - __("Set Permissions"), + __("Reset All Customizations"), function () { - frappe.set_route("permission-manager", frm.doc.doc_type); + frappe.customize_form.confirm(__("Remove all customizations?"), frm); }, __("Actions") ); @@ -179,6 +172,27 @@ frappe.ui.form.on("Customize Form", { } }, + reset_layout(frm) { + frappe.confirm( + __("Layout will be reset to standard layout, are you sure you want to do this?"), + () => { + return frm.call({ + doc: frm.doc, + method: "reset_layout", + callback: function (r) { + if (!r.exc) { + frappe.show_alert({ + message: __("Layout Reset"), + indicator: "green", + }); + frappe.customize_form.clear_locals_and_refresh(frm); + } + }, + }); + } + ); + }, + setup_export(frm) { if (frappe.boot.developer_mode) { frm.add_custom_button( diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 9aa61869d3..868a913ce3 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -35,7 +35,7 @@ class CustomizeForm(Document): if not self.doc_type: return - meta = frappe.get_meta(self.doc_type) + meta = frappe.get_meta(self.doc_type, cached=False) self.validate_doctype(meta) @@ -214,11 +214,39 @@ class CustomizeForm(Document): # action and links self.set_property_setters_for_actions_and_links(meta) + def set_property_setter_for_field_order(self, meta): + new_order = [df.fieldname for df in self.fields] + existing_order = getattr(meta, "field_order", None) + default_order = [ + fieldname for fieldname, df in meta._fields.items() if not getattr(df, "is_custom_field", False) + ] + + if new_order == default_order: + if existing_order: + delete_property_setter(self.doc_type, "field_order") + + return + + if existing_order and new_order == json.loads(existing_order): + return + + frappe.make_property_setter( + { + "doctype": self.doc_type, + "doctype_or_field": "DocType", + "property": "field_order", + "value": json.dumps(new_order), + }, + is_system_generated=False, + ) + def set_property_setters_for_doctype(self, meta): for prop, prop_type in doctype_properties.items(): if self.get(prop) != meta.get(prop): self.make_property_setter(prop, self.get(prop), prop_type) + self.set_property_setter_for_field_order(meta) + def set_property_setters_for_docfield(self, meta, df, meta_df): for prop, prop_type in docfield_properties.items(): if prop != "idx" and (df.get(prop) or "") != (meta_df[0].get(prop) or ""): @@ -540,6 +568,24 @@ class CustomizeForm(Document): reset_customization(self.doc_type) self.fetch_to_customize() + @frappe.whitelist() + def reset_layout(self): + if not self.doc_type: + return + + property_setters = frappe.get_all( + "Property Setter", + filters={"doc_type": self.doc_type, "property": ("in", ("field_order", "insert_after"))}, + pluck="name", + ) + + if not property_setters: + return + + frappe.db.delete("Property Setter", {"name": ("in", property_setters)}) + frappe.clear_cache(doctype=self.doc_type) + self.fetch_to_customize() + @classmethod def allow_fieldtype_change(self, old_type: str, new_type: str) -> bool: """allow type change, if both old_type and new_type are in same field group. @@ -619,6 +665,7 @@ docfield_properties = { "label": "Data", "fieldtype": "Select", "options": "Text", + "sort_options": "Check", "fetch_from": "Small Text", "fetch_if_empty": "Check", "show_dashboard": "Check", diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 8d98dc4149..8a62d331be 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -14,10 +14,11 @@ test_dependencies = ["Custom Field", "Property Setter"] class TestCustomizeForm(FrappeTestCase): def insert_custom_field(self): - frappe.delete_doc_if_exists("Custom Field", "Event-test_custom_field") - frappe.get_doc( + frappe.delete_doc_if_exists("Custom Field", "Event-custom_test_field") + self.field = frappe.get_doc( { "doctype": "Custom Field", + "fieldname": "custom_test_field", "dt": "Event", "label": "Test Custom Field", "description": "A Custom Field for Testing", @@ -36,7 +37,7 @@ class TestCustomizeForm(FrappeTestCase): frappe.clear_cache(doctype="Event") def tearDown(self): - frappe.delete_doc("Custom Field", "Event-test_custom_field") + frappe.delete_doc("Custom Field", self.field.name) frappe.db.commit() frappe.clear_cache(doctype="Event") @@ -60,7 +61,7 @@ class TestCustomizeForm(FrappeTestCase): self.assertEqual(d.doc_type, "Event") self.assertEqual(len(d.get("fields")), len(frappe.get_doc("DocType", d.doc_type).fields) + 1) - self.assertEqual(d.get("fields")[-1].fieldname, "test_custom_field") + self.assertEqual(d.get("fields")[-1].fieldname, self.field.fieldname) self.assertEqual(d.get("fields", {"fieldname": "event_type"})[0].in_list_view, 1) return d @@ -129,21 +130,21 @@ class TestCustomizeForm(FrappeTestCase): def test_save_customization_custom_field_property(self): d = self.get_customize_form("Event") - self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0) + self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 0) - custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0] + custom_field = d.get("fields", {"fieldname": self.field.fieldname})[0] custom_field.reqd = 1 custom_field.no_copy = 1 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 1) - self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "no_copy"), 1) + self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 1) + self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "no_copy"), 1) custom_field = d.get("fields", {"is_custom_field": True})[0] custom_field.reqd = 0 custom_field.no_copy = 0 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0) - self.assertEqual(frappe.db.get_value("Custom Field", "Event-test_custom_field", "no_copy"), 0) + self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "reqd"), 0) + self.assertEqual(frappe.db.get_value("Custom Field", self.field.name, "no_copy"), 0) def test_save_customization_new_field(self): d = self.get_customize_form("Event") @@ -157,28 +158,24 @@ class TestCustomizeForm(FrappeTestCase): }, ) d.run_method("save_customization") + + custom_field_name = "Event-custom_test_add_custom_field_via_customize_form" self.assertEqual( - frappe.db.get_value( - "Custom Field", "Event-test_add_custom_field_via_customize_form", "fieldtype" - ), + frappe.db.get_value("Custom Field", custom_field_name, "fieldtype"), "Data", ) self.assertEqual( - frappe.db.get_value( - "Custom Field", "Event-test_add_custom_field_via_customize_form", "insert_after" - ), + frappe.db.get_value("Custom Field", custom_field_name, "insert_after"), last_fieldname, ) - frappe.delete_doc("Custom Field", "Event-test_add_custom_field_via_customize_form") - self.assertEqual( - frappe.db.get_value("Custom Field", "Event-test_add_custom_field_via_customize_form"), None - ) + frappe.delete_doc("Custom Field", custom_field_name) + self.assertEqual(frappe.db.get_value("Custom Field", custom_field_name), None) def test_save_customization_remove_field(self): d = self.get_customize_form("Event") - custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0] + custom_field = d.get("fields", {"fieldname": self.field.fieldname})[0] d.get("fields").remove(custom_field) d.run_method("save_customization") @@ -200,7 +197,7 @@ class TestCustomizeForm(FrappeTestCase): def test_set_allow_on_submit(self): d = self.get_customize_form("Event") d.get("fields", {"fieldname": "subject"})[0].allow_on_submit = 1 - d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit = 1 + d.get("fields", {"fieldname": "custom_test_field"})[0].allow_on_submit = 1 d.run_method("save_customization") d = self.get_customize_form("Event") @@ -209,7 +206,7 @@ class TestCustomizeForm(FrappeTestCase): self.assertEqual(d.get("fields", {"fieldname": "subject"})[0].allow_on_submit or 0, 0) # allow for custom field - self.assertEqual(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1) + self.assertEqual(d.get("fields", {"fieldname": "custom_test_field"})[0].allow_on_submit, 1) def test_title_field_pattern(self): d = self.get_customize_form("Web Form") @@ -406,7 +403,7 @@ class TestCustomizeForm(FrappeTestCase): def test_system_generated_fields(self): doctype = "Event" - custom_field_name = "test_custom_field" + custom_field_name = "custom_test_field" custom_field = frappe.get_doc("Custom Field", {"dt": doctype, "fieldname": custom_field_name}) custom_field.is_system_generated = 1 @@ -425,3 +422,15 @@ class TestCustomizeForm(FrappeTestCase): self.assertEqual( frappe.db.get_value("Property Setter", property_setter_filters, "value"), "Test Description" ) + + def test_custom_field_order(self): + # shuffle fields + customize_form = self.get_customize_form(doctype="ToDo") + customize_form.fields.insert(0, customize_form.fields.pop()) + customize_form.save_customization() + + field_order_property = json.loads( + frappe.db.get_value("Property Setter", {"doc_type": "ToDo", "property": "field_order"}, "value") + ) + + self.assertEqual(field_order_property, [df.fieldname for df in frappe.get_meta("ToDo").fields]) diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d8da44101b..4127598291 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -29,6 +29,7 @@ "precision", "length", "options", + "sort_options", "fetch_from", "fetch_if_empty", "show_dashboard", @@ -89,7 +90,8 @@ "oldfieldtype": "Select", "options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, - "search_index": 1 + "search_index": 1, + "sort_options": 1 }, { "fieldname": "fieldname", @@ -462,13 +464,20 @@ "fieldname": "ignore_xss_filter", "fieldtype": "Check", "label": "Ignore XSS Filter" + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype === 'Select'", + "fieldname": "sort_options", + "fieldtype": "Check", + "label": "Sort Options" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-02-20 12:07:40.242470", + "modified": "2023-06-08 19:05:37.767838", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/database.py b/frappe/database/database.py index 2bac5a1ffc..a264f39d47 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -17,7 +17,6 @@ from pypika.terms import Criterion, NullValue import frappe import frappe.defaults -import frappe.model.meta from frappe import _ from frappe.database.utils import ( DefaultOrderBy, @@ -33,7 +32,7 @@ from frappe.query_builder.functions import Count from frappe.utils import CallbackManager from frappe.utils import cast as cast_fieldtype from frappe.utils import cint, get_datetime, get_table_name, getdate, now, sbool -from frappe.utils.deprecations import deprecated, deprecation_warning +from frappe.utils.deprecations import deprecation_warning IFNULL_PATTERN = re.compile(r"ifnull\(", flags=re.IGNORECASE) INDEX_PATTERN = re.compile(r"\s*\([^)]+\)\s*") @@ -302,7 +301,7 @@ class Database: """Takes the query and logs it to various interfaces according to the settings.""" _query = None - if frappe.conf.allow_tests and frappe.cache().get_value("flag_print_sql"): + if frappe.conf.allow_tests and frappe.cache.get_value("flag_print_sql"): _query = _query or str(mogrified_query) print(_query) @@ -419,7 +418,7 @@ class Database: @staticmethod def clear_db_table_cache(query): if query and is_query_type(query, ("drop", "create")): - frappe.cache().delete_key("db_tables") + frappe.cache.delete_key("db_tables") def get_description(self): """Returns result metadata.""" @@ -874,7 +873,6 @@ class Database: modified_by=None, update_modified=True, debug=False, - for_update=True, ): """Set a single value in the database, do not call the ORM triggers but update the modified timestamp (unless specified not to). @@ -890,10 +888,11 @@ class Database: :param update_modified: default True. Set as false, if you don't want to update the timestamp. :param debug: Print the query in the developer / js console. """ + from frappe.model.utils import is_single_doctype - if _is_single_doctype := not (dn and dt != dn): + if (dn is None or dt == dn) and is_single_doctype(dt): deprecation_warning( - "Calling db.set_value on single doctype is deprecated. This behaviour will be removed in version 15. Use db.set_single_value instead." + "Calling db.set_value on single doctype is deprecated. This behaviour will be removed in future. Use db.set_single_value instead." ) self.set_single_value( doctype=dt, @@ -1067,7 +1066,7 @@ class Database: def count(self, dt, filters=None, debug=False, cache=False, distinct: bool = True): """Returns `COUNT(*)` for given DocType and filters.""" if cache and not filters: - cache_count = frappe.cache().get_value(f"doctype:count:{dt}") + cache_count = frappe.cache.get_value(f"doctype:count:{dt}") if cache_count is not None: return cache_count count = frappe.qb.get_query( @@ -1078,7 +1077,7 @@ class Database: validate_filters=True, ).run(debug=debug)[0][0] if not filters and cache: - frappe.cache().set_value(f"doctype:count:{dt}", count, expires_in_sec=86400) + frappe.cache.set_value(f"doctype:count:{dt}", count, expires_in_sec=86400) return count @staticmethod @@ -1109,7 +1108,7 @@ class Database: def get_db_table_columns(self, table) -> list[str]: """Returns list of column names from given table.""" - columns = frappe.cache().hget("table_columns", table) + columns = frappe.cache.hget("table_columns", table) if columns is None: information_schema = frappe.qb.Schema("information_schema") @@ -1121,7 +1120,7 @@ class Database: ) if columns: - frappe.cache().hset("table_columns", table, columns) + frappe.cache.hset("table_columns", table, columns) return columns diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index f14fce2710..6a89966ee5 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -435,7 +435,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database): to_query = not cached if cached: - tables = frappe.cache().get_value("db_tables") + tables = frappe.cache.get_value("db_tables") to_query = not tables if to_query: @@ -447,7 +447,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database): .where(information_schema.tables.table_schema != "information_schema") .run(pluck=True) ) - frappe.cache().set_value("db_tables", tables) + frappe.cache.set_value("db_tables", tables) return tables diff --git a/frappe/database/schema.py b/frappe/database/schema.py index e65d7b980b..ed7d1d16fc 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -40,7 +40,7 @@ class DBTable: if self.is_new(): self.create() else: - frappe.cache().hdel("table_columns", self.table_name) + frappe.cache.hdel("table_columns", self.table_name) self.alter() def create(self): diff --git a/frappe/defaults.py b/frappe/defaults.py index edbf784200..3bcfbec1ce 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -3,7 +3,6 @@ import frappe from frappe.cache_manager import clear_defaults_cache, common_default_keys -from frappe.desk.notifications import clear_notifications from frappe.query_builder import DocType # Note: DefaultValue records are identified by parent (e.g. __default, __global) @@ -230,7 +229,7 @@ def clear_default(key=None, value=None, parent=None, name=None, parenttype=None) def get_defaults_for(parent="__default"): """get all defaults""" - defaults = frappe.cache().hget("defaults", parent) + defaults = frappe.cache.hget("defaults", parent) if defaults is None: # sort descending because first default must get precedence @@ -256,7 +255,7 @@ def get_defaults_for(parent="__default"): elif d.defvalue is not None: defaults[d.defkey] = d.defvalue - frappe.cache().hset("defaults", parent, defaults) + frappe.cache.hset("defaults", parent, defaults) return defaults diff --git a/frappe/deferred_insert.py b/frappe/deferred_insert.py index 328d8dd555..5c7e7a7f0d 100644 --- a/frappe/deferred_insert.py +++ b/frappe/deferred_insert.py @@ -19,20 +19,20 @@ def deferred_insert(doctype: str, records: list[Union[dict, "Document"]] | str): _records = records try: - frappe.cache().rpush(f"{queue_prefix}{doctype}", _records) + frappe.cache.rpush(f"{queue_prefix}{doctype}", _records) except redis.exceptions.ConnectionError: for record in records: insert_record(record, doctype) def save_to_db(): - queue_keys = frappe.cache().get_keys(queue_prefix) + queue_keys = frappe.cache.get_keys(queue_prefix) for key in queue_keys: record_count = 0 queue_key = get_key_name(key) doctype = get_doctype_name(key) - while frappe.cache().llen(queue_key) > 0 and record_count <= 500: - records = frappe.cache().lpop(queue_key) + while frappe.cache.llen(queue_key) > 0 and record_count <= 500: + records = frappe.cache.lpop(queue_key) records = json.loads(records.decode("utf-8")) if isinstance(records, dict): record_count += 1 diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 46cda8fe5d..cf9f223d2a 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -62,10 +62,10 @@ class Workspace: self.table_counts = get_table_with_counts() self.restricted_doctypes = ( - frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() + frappe.cache.get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() ) self.restricted_pages = ( - frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() + frappe.cache.get_value("domain_restricted_pages") or build_domain_restriced_page_cache() ) def is_permitted(self): @@ -88,16 +88,14 @@ class Workspace: return True def get_cached(self, cache_key, fallback_fn): - _cache = frappe.cache() - - value = _cache.get_value(cache_key, user=frappe.session.user) + value = frappe.cache.get_value(cache_key, user=frappe.session.user) if value: return value value = fallback_fn() # Expire every six hour - _cache.set_value(cache_key, value, frappe.session.user, 21600) + frappe.cache.set_value(cache_key, value, frappe.session.user, 21600) return value def get_can_read_items(self): @@ -469,7 +467,7 @@ def get_workspace_sidebar_items(): def get_table_with_counts(): - counts = frappe.cache().get_value("information_schema:counts") + counts = frappe.cache.get_value("information_schema:counts") if not counts: counts = build_table_count_cache() diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 5cbeb06e33..16f4efea9d 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -340,7 +340,7 @@ def get_charts_for_user(doctype, txt, searchfield, start, page_len, filters): class DashboardChart(Document): def on_update(self): - frappe.cache().delete_key(f"chart-data:{self.name}") + frappe.cache.delete_key(f"chart-data:{self.name}") if frappe.conf.developer_mode and self.is_standard: export_to_files(record_list=[["Dashboard Chart", self.name]], record_module=self.module) diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 63fa12b8fb..0d6e5bb815 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -28,7 +28,7 @@ def get_desktop_icons(user=None): if not user: user = frappe.session.user - user_icons = frappe.cache().hget("desktop_icons", user) + user_icons = frappe.cache.hget("desktop_icons", user) if not user_icons: fields = [ @@ -120,7 +120,7 @@ def get_desktop_icons(user=None): if d.label: d.label = _(d.label) - frappe.cache().hset("desktop_icons", user, user_icons) + frappe.cache.hset("desktop_icons", user, user_icons) return user_icons @@ -313,8 +313,8 @@ def get_all_icons(): def clear_desktop_icons_cache(user=None): - frappe.cache().hdel("desktop_icons", user or frappe.session.user) - frappe.cache().hdel("bootinfo", user or frappe.session.user) + frappe.cache.hdel("desktop_icons", user or frappe.session.user) + frappe.cache.hdel("bootinfo", user or frappe.session.user) def get_user_copy(module_name, user=None): @@ -445,7 +445,7 @@ def get_module_icons(user=None): if not user: icons = frappe.get_all("Desktop Icon", fields="*", filters={"standard": 1}, order_by="idx") else: - frappe.cache().hdel("desktop_icons", user) + frappe.cache.hdel("desktop_icons", user) icons = get_user_icons(user) for icon in icons: diff --git a/frappe/desk/doctype/form_tour/form_tour.py b/frappe/desk/doctype/form_tour/form_tour.py index 6838f15d8f..bdfdcf7c79 100644 --- a/frappe/desk/doctype/form_tour/form_tour.py +++ b/frappe/desk/doctype/form_tour/form_tour.py @@ -34,13 +34,13 @@ class FormTour(Document): step.fieldtype = field_df.fieldtype def on_update(self): - frappe.cache().delete_key("bootinfo") + frappe.cache.delete_key("bootinfo") if frappe.conf.developer_mode and self.is_standard: export_to_files([["Form Tour", self.name]], self.module) def on_trash(self): - frappe.cache().delete_key("bootinfo") + frappe.cache.delete_key("bootinfo") @frappe.whitelist() @@ -51,7 +51,7 @@ def reset_tour(tour_name): frappe.db.set_value( "User", user, "onboarding_status", frappe.as_json(onboarding_status), update_modified=False ) - frappe.cache().hdel("bootinfo", user) + frappe.cache.hdel("bootinfo", user) frappe.msgprint(_("Successfully reset onboarding status for all users."), alert=True) @@ -72,7 +72,7 @@ def update_user_status(value, step): "User", frappe.session.user, "onboarding_status", value, update_modified=False ) - frappe.cache().hdel("bootinfo", frappe.session.user) + frappe.cache.hdel("bootinfo", frappe.session.user) def get_onboarding_ui_tours(): diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index 4e2b1e85f9..f0bf985550 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -28,7 +28,7 @@ class GlobalSearchSettings(Document): frappe.throw(_("Document Type {0} has been repeated.").format(repeated_dts)) # reset cache - frappe.cache().hdel("global_search", "search_priorities") + frappe.cache.hdel("global_search", "search_priorities") def get_doctypes_for_global_search(): @@ -36,7 +36,7 @@ def get_doctypes_for_global_search(): doctypes = frappe.get_all("Global Search DocType", fields=["document_type"], order_by="idx ASC") return [d.document_type for d in doctypes] or [] - return frappe.cache().hget("global_search", "search_priorities", get_from_db) + return frappe.cache.hget("global_search", "search_priorities", get_from_db) @frappe.whitelist() diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index e3257e25be..508407f76a 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -14,7 +14,7 @@ class KanbanBoard(Document): def on_change(self): frappe.clear_cache(doctype=self.reference_doctype) - frappe.cache().delete_keys("_user_settings") + frappe.cache.delete_keys("_user_settings") def before_insert(self): for column in self.columns: diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.js b/frappe/desk/doctype/module_onboarding/module_onboarding.js index 0e312025bf..831b29a660 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.js +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.js @@ -13,6 +13,10 @@ frappe.ui.form.on("Module Onboarding", { if (!frappe.boot.developer_mode) { frm.trigger("disable_form"); } + + frm.add_custom_button(__("Reset"), () => { + frm.call("reset_progress"); + }); }, disable_form: function (frm) { diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index ea02f5911d..94805d05b6 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import frappe +from frappe import _ from frappe.model.document import Document from frappe.modules.export_file import export_to_files @@ -37,6 +38,16 @@ class ModuleOnboarding(Document): return False + @frappe.whitelist() + def reset_progress(self): + self.db_set("is_complete", 0) + + for step in self.get_steps(): + step.db_set("is_complete", 0) + step.db_set("is_skipped", 0) + + frappe.msgprint(_("Module onboarding progress reset"), alert=True) + def before_export(self, doc): doc.is_complete = 0 diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index f24a6447b4..bafe28faf8 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -29,7 +29,8 @@ "fieldtype": "Link", "hidden": 1, "label": "For User", - "options": "User" + "options": "User", + "search_index": 1 }, { "fieldname": "type", @@ -64,8 +65,7 @@ "fieldtype": "Link", "hidden": 1, "label": "From User", - "options": "User", - "search_index": 1 + "options": "User" }, { "default": "0", @@ -96,7 +96,7 @@ "hide_toolbar": 1, "in_create": 1, "links": [], - "modified": "2022-09-13 16:08:48.153934", + "modified": "2023-06-14 21:20:51.197943", "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index e076f3384a..46a826688a 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -81,13 +81,21 @@ class ToDo(Document): ) assignments.reverse() - frappe.db.set_value( - self.reference_type, - self.reference_name, - "_assign", - json.dumps(assignments), - update_modified=False, - ) + if frappe.get_meta(self.reference_type).issingle: + frappe.db.set_single_value( + self.reference_type, + "_assign", + json.dumps(assignments), + update_modified=False, + ) + else: + frappe.db.set_value( + self.reference_type, + self.reference_name, + "_assign", + json.dumps(assignments), + update_modified=False, + ) except Exception as e: if frappe.db.is_table_missing(e) and frappe.flags.in_install: diff --git a/frappe/desk/doctype/workspace/workspace.json b/frappe/desk/doctype/workspace/workspace.json index 2759acd228..0769b2a81b 100644 --- a/frappe/desk/doctype/workspace/workspace.json +++ b/frappe/desk/doctype/workspace/workspace.json @@ -211,7 +211,7 @@ ], "in_create": 1, "links": [], - "modified": "2023-05-17 14:52:38.110224", + "modified": "2023-06-08 14:52:38.110224", "modified_by": "Administrator", "module": "Desk", "name": "Workspace", diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 9bc7b138dd..1ec604c34d 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -531,13 +531,13 @@ def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): {"Address": {"fieldname": "customer"}..} """ if without_ignore_user_permissions_enabled: - return frappe.cache().hget( + return frappe.cache.hget( "linked_doctypes_without_ignore_user_permissions_enabled", doctype, lambda: _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled), ) else: - return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype)) + return frappe.cache.hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype)) def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False): diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 42109f8863..e6f735e9d7 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -80,7 +80,7 @@ def get_meta_bundle(doctype): bundle = [frappe.desk.form.meta.get_meta(doctype)] for df in bundle[0].fields: if df.fieldtype in frappe.model.table_fields: - bundle.append(frappe.desk.form.meta.get_meta(df.options, not frappe.conf.developer_mode)) + bundle.append(frappe.desk.form.meta.get_meta(df.options)) return bundle @@ -202,11 +202,13 @@ def get_versions(doc): @frappe.whitelist() def get_communications(doctype, name, start=0, limit=20): + from frappe.utils import cint + doc = frappe.get_doc(doctype, name) if not doc.has_permission("read"): raise frappe.PermissionError - return _get_communications(doctype, name, start, limit) + return _get_communications(doctype, name, cint(start), cint(limit)) def get_comments( diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 62a9c89c81..6c338dbbbc 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -9,7 +9,6 @@ from frappe.build import scrub_html_template from frappe.model.meta import Meta from frappe.model.utils import render_include from frappe.modules import get_module_path, load_doctype_module, scrub -from frappe.translate import extract_messages_from_code, make_dict_from_messages from frappe.utils import get_html_format from frappe.utils.data import get_link_to_form @@ -34,13 +33,15 @@ ASSET_KEYS = ( ) -def get_meta(doctype, cached=True): +def get_meta(doctype, cached=True) -> "FormMeta": # don't cache for developer mode as js files, templates may be edited - if cached and not frappe.conf.developer_mode: - meta = frappe.cache().hget("doctype_form_meta", doctype) + cached = cached and not frappe.conf.developer_mode + if cached: + meta = frappe.cache.hget("doctype_form_meta", doctype) if not meta: - meta = FormMeta(doctype) - frappe.cache().hset("doctype_form_meta", doctype, meta) + # Cache miss - explicitly get meta from DB to avoid + meta = FormMeta(doctype, cached=False) + frappe.cache.hset("doctype_form_meta", doctype, meta) else: meta = FormMeta(doctype) @@ -51,8 +52,8 @@ def get_meta(doctype, cached=True): class FormMeta(Meta): - def __init__(self, doctype): - self.__dict__.update(frappe.get_meta(doctype).__dict__) + def __init__(self, doctype, *, cached=True): + self.__dict__.update(frappe.get_meta(doctype, cached=cached).__dict__) self.load_assets() def load_assets(self): @@ -258,6 +259,8 @@ class FormMeta(Meta): self.set("__form_grid_templates", templates) def set_translations(self, lang): + from frappe.translate import extract_messages_from_code, make_dict_from_messages + self.set("__messages", frappe.get_lang_dict("doctype", self.name)) # set translations for grid templates diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py index 75335cb1ce..180717da40 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -16,7 +16,7 @@ from frappe.utils.telemetry import capture_doc def savedocs(doc, action): """save / submit / update doclist""" doc = frappe.get_doc(json.loads(doc)) - capture_doc(doc) + capture_doc(doc, action) set_local_name(doc) # action @@ -47,6 +47,8 @@ def savedocs(doc, action): def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_state=None): """cancel a doclist""" doc = frappe.get_doc(doctype, name) + capture_doc(doc, "Cancel") + if workflow_state_fieldname and workflow_state: doc.set(workflow_state_fieldname, workflow_state) doc.cancel() diff --git a/frappe/desk/like.py b/frappe/desk/like.py index 0f297455e7..60eea3c525 100644 --- a/frappe/desk/like.py +++ b/frappe/desk/like.py @@ -52,7 +52,10 @@ def _toggle_like(doctype, name, add, user=None): liked_by.remove(user) remove_like(doctype, name) - frappe.db.set_value(doctype, name, "_liked_by", json.dumps(liked_by), update_modified=False) + if frappe.get_meta(doctype).issingle: + frappe.db.set_single_value(doctype, "_liked_by", json.dumps(liked_by), update_modified=False) + else: + frappe.db.set_value(doctype, name, "_liked_by", json.dumps(liked_by), update_modified=False) except frappe.db.ProgrammingError as e: if frappe.db.is_column_missing(e): diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 271f2b4074..6334b18d1c 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -34,13 +34,12 @@ def get_notifications(): return out groups = list(config.get("for_doctype")) + list(config.get("for_module")) - cache = frappe.cache() notification_count = {} notification_percent = {} for name in groups: - count = cache.hget("notification_count:" + name, frappe.session.user) + count = frappe.cache.hget("notification_count:" + name, frappe.session.user) if count is not None: notification_count[name] = count @@ -83,7 +82,7 @@ def get_notifications_for_doctypes(config, notification_count): else: open_count_doctype[d] = result - frappe.cache().hset("notification_count:" + d, frappe.session.user, result) + frappe.cache.hset("notification_count:" + d, frappe.session.user, result) return open_count_doctype @@ -139,7 +138,6 @@ def get_notifications_for_targets(config, notification_percent): def clear_notifications(user=None): if frappe.flags.in_install: return - cache = frappe.cache() config = get_notification_config() if not config: @@ -151,17 +149,17 @@ def clear_notifications(user=None): for name in groups: if user: - cache.hdel("notification_count:" + name, user) + frappe.cache.hdel("notification_count:" + name, user) else: - cache.delete_key("notification_count:" + name) + frappe.cache.delete_key("notification_count:" + name) def clear_notification_config(user): - frappe.cache().hdel("notification_config", user) + frappe.cache.hdel("notification_config", user) def delete_notification_count_for(doctype): - frappe.cache().delete_key("notification_count:" + doctype) + frappe.cache.delete_key("notification_count:" + doctype) def clear_doctype_notifications(doc, method=None, *args, **kwargs): @@ -230,7 +228,7 @@ def get_notification_config(): config[key].update(nc.get(key, {})) return config - return frappe.cache().hget("notification_config", user, _get) + return frappe.cache.hget("notification_config", user, _get) def get_filters_for(doctype): diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index cb869fb5fc..a50588bdca 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -325,8 +325,8 @@ def load_country(): @frappe.whitelist() def load_user_details(): return { - "full_name": frappe.cache().hget("full_name", "signup"), - "email": frappe.cache().hget("email", "signup"), + "full_name": frappe.cache.hget("full_name", "signup"), + "email": frappe.cache.hget("email", "signup"), } diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 69cdecb6dd..3d54520356 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -119,7 +119,7 @@ def generate_report_result( "report_summary": report_summary, "skip_total_row": skip_total_row or 0, "status": None, - "execution_time": frappe.cache().hget("report_execution_time", report.name) or 0, + "execution_time": frappe.cache.hget("report_execution_time", report.name) or 0, } @@ -170,7 +170,8 @@ def get_script(report_name): return { "script": render_include(script), "html_format": html_format, - "execution_time": frappe.cache().hget("report_execution_time", report_name) or 0, + "execution_time": frappe.cache.hget("report_execution_time", report_name) or 0, + "filters": report.filters, } @@ -348,6 +349,13 @@ def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=F datetime.timedelta, ) + if len(visible_idx) == len(data.result): + # It's not possible to have same length and different content. + ignore_visible_idx = True + else: + # Note: converted for faster lookups + visible_idx = set(visible_idx) + result = [[]] column_widths = [] diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 326e9bb864..071b6e7e61 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -479,6 +479,7 @@ def delete_items(): def delete_bulk(doctype, items): + undeleted_items = [] for i, d in enumerate(items): try: frappe.delete_doc(doctype, d) @@ -493,7 +494,11 @@ def delete_bulk(doctype, items): except Exception: # rollback if any record failed to delete # if not rollbacked, queries get committed on after_request method in app.py + undeleted_items.append(d) frappe.db.rollback() + if undeleted_items and len(items) != len(undeleted_items): + frappe.clear_messages() + delete_bulk(doctype, undeleted_items) @frappe.whitelist() diff --git a/frappe/desk/search.py b/frappe/desk/search.py index 67695e4e73..c4c11558dd 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -6,7 +6,9 @@ import json import re import frappe -from frappe import _, is_whitelisted + +# Backward compatbility +from frappe import _, is_whitelisted, validate_and_sanitize_search_inputs from frappe.database.schema import SPECIAL_CHAR_PATTERN from frappe.permissions import has_permission from frappe.utils import cint, cstr, unique @@ -293,26 +295,10 @@ def relevance_sorter(key, query, as_dict): return (cstr(value).casefold().startswith(query.casefold()) is not True, value) -def validate_and_sanitize_search_inputs(fn): - @functools.wraps(fn) - def wrapper(*args, **kwargs): - kwargs.update(dict(zip(fn.__code__.co_varnames, args))) - sanitize_searchfield(kwargs["searchfield"]) - kwargs["start"] = cint(kwargs["start"]) - kwargs["page_len"] = cint(kwargs["page_len"]) - - if kwargs["doctype"] and not frappe.db.exists("DocType", kwargs["doctype"]): - return [] - - return fn(**kwargs) - - return wrapper - - @frappe.whitelist() def get_names_for_mentions(search_term): - users_for_mentions = frappe.cache().get_value("users_for_mentions", get_users_for_mentions) - user_groups = frappe.cache().get_value("user_groups", get_user_groups) + users_for_mentions = frappe.cache.get_value("users_for_mentions", get_users_for_mentions) + user_groups = frappe.cache.get_value("user_groups", get_user_groups) filtered_mentions = [] for mention_data in users_for_mentions + user_groups: diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 486db2a784..5c4d6f4c72 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -96,7 +96,7 @@ def get_communication_doctype(doctype, txt, searchfield, start, page_len, filter def get_cached_contacts(txt): - contacts = frappe.cache().hget("contacts", frappe.session.user) or [] + contacts = frappe.cache.hget("contacts", frappe.session.user) or [] if not contacts: return @@ -113,9 +113,9 @@ def get_cached_contacts(txt): def update_contact_cache(contacts): - cached_contacts = frappe.cache().hget("contacts", frappe.session.user) or [] + cached_contacts = frappe.cache.hget("contacts", frappe.session.user) or [] uncached_contacts = [d for d in contacts if d not in cached_contacts] cached_contacts.extend(uncached_contacts) - frappe.cache().hset("contacts", frappe.session.user, cached_contacts) + frappe.cache.hset("contacts", frappe.session.user, cached_contacts) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 85241b8194..d61165b787 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -508,7 +508,7 @@ }, { "default": "0", - "depends_on": "eval:!doc.domain && doc.enable_outgoing", + "depends_on": "eval:!doc.domain && doc.enable_outgoing && doc.enable_incoming && doc.use_imap", "fieldname": "append_emails_to_sent_folder", "fieldtype": "Check", "hide_days": 1, @@ -616,7 +616,7 @@ "icon": "fa fa-inbox", "index_web_pages_for_search": 1, "links": [], - "modified": "2022-12-28 14:56:18.754804", + "modified": "2023-06-05 15:03:08.538819", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -639,4 +639,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index faf28afdb3..3f6051ffc8 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -176,7 +176,7 @@ class EmailAccount(Document): def get_incoming_server(self, in_receive=False, email_sync_rule="UNSEEN"): """Returns logged in POP3/IMAP connection object.""" - if frappe.cache().get_value("workers:no-internet") == True: + if frappe.cache.get_value("workers:no-internet") == True: return None oauth_token = self.get_oauth_token() @@ -253,7 +253,7 @@ class EmailAccount(Document): if self.no_failed > 2: self.handle_incoming_connect_error(description=description) else: - frappe.cache().set_value("workers:no-internet", True) + frappe.cache.set_value("workers:no-internet", True) return None else: raise @@ -384,6 +384,10 @@ class EmailAccount(Document): "name": {"conf_names": ("email_sender_name",), "default": "Frappe"}, "auth_method": {"conf_names": ("auth_method"), "default": "Basic"}, "from_site_config": {"default": True}, + "no_smtp_authentication": { + "conf_names": ("disable_mail_smtp_authentication",), + "default": 0, + }, } account_details = {} @@ -436,13 +440,13 @@ class EmailAccount(Document): else: self.set_failed_attempts_count(self.get_failed_attempts_count() + 1) else: - frappe.cache().set_value("workers:no-internet", True) + frappe.cache.set_value("workers:no-internet", True) def set_failed_attempts_count(self, value): - frappe.cache().set(f"{self.name}:email-account-failed-attempts", value) + frappe.cache.set(f"{self.name}:email-account-failed-attempts", value) def get_failed_attempts_count(self): - return cint(frappe.cache().get(f"{self.name}:email-account-failed-attempts")) + return cint(frappe.cache.get(f"{self.name}:email-account-failed-attempts")) def receive(self): """Called by scheduler to receive emails from this EMail account using POP3/IMAP.""" @@ -648,21 +652,16 @@ class EmailAccount(Document): frappe.throw(_("Automatic Linking can be activated only for one Email Account.")) def append_email_to_sent_folder(self, message): - email_server = None - try: - email_server = self.get_incoming_server(in_receive=True) - except Exception: - self.log_error("Email Connection Error") - - if not email_server: + if not (self.enable_incoming and self.use_imap): + # don't try appending if enable incoming and imap is not set return - if email_server.imap: - try: - message = safe_encode(message) - email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message) - except Exception: - self.log_error("Unable to add to Sent folder") + try: + email_server = self.get_incoming_server(in_receive=True) + message = safe_encode(message) + email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message) + except Exception: + self.log_error("Unable to add to Sent folder") def get_oauth_token(self): if self.auth_method == "OAuth": @@ -766,9 +765,9 @@ def pull(now=False): """Will be called via scheduler, pull emails from all enabled Email accounts.""" from frappe.integrations.doctype.connected_app.connected_app import has_token - if frappe.cache().get_value("workers:no-internet") == True: + if frappe.cache.get_value("workers:no-internet") == True: if test_internet(): - frappe.cache().set_value("workers:no-internet", False) + frappe.cache.set_value("workers:no-internet", False) return doctype = frappe.qb.DocType("Email Account") diff --git a/frappe/email/doctype/email_domain/email_domain.json b/frappe/email/doctype/email_domain/email_domain.json index c162060436..5cb4c19940 100644 --- a/frappe/email/doctype/email_domain/email_domain.json +++ b/frappe/email/doctype/email_domain/email_domain.json @@ -107,6 +107,7 @@ }, { "default": "0", + "depends_on": "eval:doc.use_imap", "fieldname": "append_emails_to_sent_folder", "fieldtype": "Check", "label": "Append Emails to Sent Folder" @@ -133,7 +134,7 @@ "link_fieldname": "domain" } ], - "modified": "2022-08-19 12:55:06.434541", + "modified": "2023-06-05 12:55:06.434541", "modified_by": "Administrator", "module": "Email", "name": "Email Domain", diff --git a/frappe/email/doctype/email_queue/email_queue.js b/frappe/email/doctype/email_queue/email_queue.js index 2ac4b6f7fe..b9a24342ba 100644 --- a/frappe/email/doctype/email_queue/email_queue.js +++ b/frappe/email/doctype/email_queue/email_queue.js @@ -3,7 +3,7 @@ frappe.ui.form.on("Email Queue", { refresh: function (frm) { - if (["Not Sent", "Partially Sent"].indexOf(frm.doc.status) != -1) { + if (["Not Sent", "Partially Sent"].includes(frm.doc.status)) { let button = frm.add_custom_button("Send Now", function () { frappe.call({ method: "frappe.email.doctype.email_queue.email_queue.send_now", @@ -16,20 +16,16 @@ frappe.ui.form.on("Email Queue", { }, }); }); - } - - if (["Error", "Partially Errored"].indexOf(frm.doc.status) != -1) { - let button = frm.add_custom_button("Retry Sending", function () { + } else if (frm.doc.status == "Error") { + frm.add_custom_button("Retry Sending", function () { frm.call({ method: "retry_sending", + doc: frm.doc, args: { name: frm.doc.name, }, - btn: button, - callback: function (r) { - if (!r.exc) { - frm.set_value("status", "Not Sent"); - } + callback: function () { + frm.reload_doc(); }, }); }); diff --git a/frappe/email/doctype/email_queue/email_queue.json b/frappe/email/doctype/email_queue/email_queue.json index ac8d656678..ea1389d8e9 100644 --- a/frappe/email/doctype/email_queue/email_queue.json +++ b/frappe/email/doctype/email_queue/email_queue.json @@ -55,12 +55,14 @@ "default": "Not Sent", "fieldname": "status", "fieldtype": "Select", + "hidden": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "\nNot Sent\nSending\nSent\nError\nExpired" + "options": "Not Sent\nSending\nSent\nPartially Sent\nError" }, { + "depends_on": "eval:doc.error", "fieldname": "error", "fieldtype": "Code", "label": "Error" @@ -152,7 +154,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-03-16 12:15:17.850292", + "modified": "2023-06-09 14:31:52.789186", "modified_by": "Administrator", "module": "Email", "name": "Email Queue", diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index d254c87a0a..895d8fbe01 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -3,14 +3,11 @@ import json import quopri -import smtplib import traceback from contextlib import suppress from email.parser import Parser from email.policy import SMTPUTF8 -from rq.timeouts import JobTimeoutException - import frappe from frappe import _, safe_encode, task from frappe.core.utils import html2text @@ -28,6 +25,7 @@ from frappe.utils import ( get_hook_method, get_string_between, get_url, + now, nowdate, sbool, split_emails, @@ -123,31 +121,33 @@ class EmailQueue(Document): return True - def send(self, is_background_task: bool = False, smtp_server_instance: SMTPServer = None): + def send(self, smtp_server_instance: SMTPServer = None): """Send emails to recipients.""" if not self.can_send_now(): return - with SendMailContext(self, is_background_task, smtp_server_instance) as ctx: + with SendMailContext(self, smtp_server_instance) as ctx: message = None for recipient in self.recipients: - if not recipient.is_mail_to_be_sent(): + if recipient.is_mail_sent(): continue message = ctx.build_message(recipient.recipient) - method = get_hook_method("override_email_send") - if method: + if method := get_hook_method("override_email_send"): method(self, self.sender, recipient.recipient, message) else: if not frappe.flags.in_test: - ctx.smtp_session.sendmail(from_addr=self.sender, to_addrs=recipient.recipient, msg=message) - ctx.add_to_sent_list(recipient) + ctx.smtp_server.session.sendmail( + from_addr=self.sender, to_addrs=recipient.recipient, msg=message + ) + + ctx.update_recipient_status_to_sent(recipient) if frappe.flags.in_test: frappe.flags.sent_mail = message return - if ctx.email_account_doc.append_emails_to_sent_folder and ctx.sent_to: + if ctx.email_account_doc.append_emails_to_sent_folder: ctx.email_account_doc.append_email_to_sent_folder(message) @staticmethod @@ -175,26 +175,30 @@ class EmailQueue(Document): .where(email_recipient.modified < (Now() - Interval(days=days))) ).run() + @frappe.whitelist() + def retry_sending(self): + if self.status == "Error": + self.status = "Not Sent" + self.save(ignore_permissions=True) + @task(queue="short") -def send_mail(email_queue_name, is_background_task=False, smtp_server_instance: SMTPServer = None): +def send_mail(email_queue_name, smtp_server_instance: SMTPServer = None): """This is equivalent to EmailQueue.send. This provides a way to make sending mail as a background job. """ record = EmailQueue.find(email_queue_name) - record.send(is_background_task=is_background_task, smtp_server_instance=smtp_server_instance) + record.send(smtp_server_instance=smtp_server_instance) class SendMailContext: def __init__( self, queue_doc: Document, - is_background_task: bool = False, smtp_server_instance: SMTPServer = None, ): self.queue_doc: EmailQueue = queue_doc - self.is_background_task = is_background_task self.email_account_doc = queue_doc.get_email_account() self.smtp_server = smtp_server_instance or self.email_account_doc.get_smtp_server() @@ -203,67 +207,37 @@ class SendMailContext: # Note: smtp session will have to be manually closed self.retain_smtp_session = bool(smtp_server_instance) - self.sent_to = [rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent()] + self.sent_to_atleast_one_recipient = any( + rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent() + ) def __enter__(self): self.queue_doc.update_status(status="Sending", commit=True) return self def __exit__(self, exc_type, exc_val, exc_tb): - exceptions = [ - smtplib.SMTPServerDisconnected, - smtplib.SMTPAuthenticationError, - smtplib.SMTPConnectError, - smtplib.SMTPHeloError, - JobTimeoutException, - ] - if not self.retain_smtp_session: self.smtp_server.quit() - self.log_exception(exc_type, exc_val, exc_tb) - - if exc_type in exceptions: - email_status = "Partially Sent" if self.sent_to else "Not Sent" - self.queue_doc.update_status(status=email_status, commit=True) - elif exc_type: - if self.queue_doc.retry < get_email_retry_limit(): - update_fields = {"status": "Not Sent", "retry": self.queue_doc.retry + 1} - else: - update_fields = {"status": (self.sent_to and "Partially Errored") or "Error"} - self.queue_doc.update_status(**update_fields, commit=True) - else: - email_status = self.is_mail_sent_to_all() and "Sent" - email_status = email_status or (self.sent_to and "Partially Sent") or "Not Sent" - - update_fields = { - "status": email_status, - "email_account": self.email_account_doc.name - if self.email_account_doc.is_exists_in_db() - else None, - } - self.queue_doc.update_status(**update_fields, commit=True) - - def log_exception(self, exc_type, exc_val, exc_tb): if exc_type: - traceback_string = "".join(traceback.format_tb(exc_tb)) - traceback_string += f"\n Queue Name: {self.queue_doc.name}" + update_fields = {"error": "".join(traceback.format_tb(exc_tb))} + if self.queue_doc.retry < get_email_retry_limit(): + update_fields.update( + { + "status": "Partially Sent" if self.sent_to_atleast_one_recipient else "Not Sent", + "retry": self.queue_doc.retry + 1, + } + ) + else: + update_fields.update({"status": "Error"}) + else: + update_fields = {"status": "Sent"} - self.queue_doc.log_error("Email sending failed", traceback_string) + self.queue_doc.update_status(**update_fields, commit=True) - @property - def smtp_session(self): - if frappe.flags.in_test: - return - return self.smtp_server.session - - def add_to_sent_list(self, recipient): - # Update recipient status + def update_recipient_status_to_sent(self, recipient): + self.sent_to_atleast_one_recipient = True recipient.update_db(status="Sent", commit=True) - self.sent_to.append(recipient.recipient) - - def is_mail_sent_to_all(self): - return sorted(self.sent_to) == sorted(rec.recipient for rec in self.queue_doc.recipients) def get_message_object(self, message): return Parser(policy=SMTPUTF8).parsestr(message) @@ -375,16 +349,26 @@ class SendMailContext: @frappe.whitelist() -def retry_sending(name): - doc = frappe.get_doc("Email Queue", name) - doc.check_permission() +def bulk_retry(queues): + frappe.only_for("System Manager") - if doc and (doc.status == "Error" or doc.status == "Partially Errored"): - doc.status = "Not Sent" - for d in doc.recipients: - if d.status != "Sent": - d.status = "Not Sent" - doc.save(ignore_permissions=True) + if isinstance(queues, str): + queues = json.loads(queues) + + if not queues: + return + + frappe.msgprint( + _("Updating Email Queue Statuses. The emails will be picked up in the next scheduled run."), + _("Processing..."), + ) + + email_queue = frappe.qb.DocType("Email Queue") + frappe.qb.update(email_queue).set(email_queue.status, "Not Sent").set( + email_queue.modified, now() + ).set(email_queue.modified_by, frappe.session.user).where( + email_queue.name.isin(queues) & email_queue.status == "Error" + ).run() @frappe.whitelist() diff --git a/frappe/email/doctype/email_queue/email_queue_list.js b/frappe/email/doctype/email_queue/email_queue_list.js index b00503b6f8..ea6869e265 100644 --- a/frappe/email/doctype/email_queue/email_queue_list.js +++ b/frappe/email/doctype/email_queue/email_queue_list.js @@ -9,7 +9,10 @@ frappe.listview_settings["Email Queue"] = { }; return [__(doc.status), colour[doc.status], "status,=," + doc.status]; }, - refresh: show_toggle_sending_button, + refresh: function (listview) { + show_toggle_sending_button(listview); + add_bulk_retry_button_to_actions(listview); + }, onload: function (list_view) { frappe.require("logtypes.bundle.js", () => { frappe.utils.logtypes.show_log_retention_message(list_view.doctype); @@ -39,3 +42,21 @@ function show_toggle_sending_button(list_view) { show_toggle_sending_button(list_view); }); } + +function add_bulk_retry_button_to_actions(list_view) { + if (!has_common(frappe.user_roles, ["Administrator", "System Manager"])) return; + + list_view.page.add_actions_menu_item(__("Retry Sending"), () => { + frappe.call({ + method: "frappe.email.doctype.email_queue.email_queue.bulk_retry", + args: { + queues: list_view.get_checked_items(true), + }, + callback: (r) => { + if (!r.exc) { + list_view.refresh(); + } + }, + }); + }); +} diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index aee68aa4e5..eb0868a91e 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -42,10 +42,10 @@ class Notification(Document): self.validate_forbidden_types() self.validate_condition() self.validate_standard() - frappe.cache().hdel("notifications", self.document_type) + frappe.cache.hdel("notifications", self.document_type) def on_update(self): - frappe.cache().hdel("notifications", self.document_type) + frappe.cache.hdel("notifications", self.document_type) path = export_module_json(self, self.is_standard, self.module) if path: # js @@ -378,7 +378,7 @@ def get_context(context): self.message = frappe.utils.md_to_html(self.message) def on_trash(self): - frappe.cache().hdel("notifications", self.document_type) + frappe.cache.hdel("notifications", self.document_type) @frappe.whitelist() diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 7d4b92baf1..0df88ebd5c 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -154,7 +154,6 @@ def flush(from_test=False): frappe.enqueue( method=send_mail, email_queue_name=row.name, - is_background_task=not from_test, now=from_test, job_name=job_name, queue="short", @@ -180,19 +179,3 @@ def get_queue(): {"now": now_datetime()}, as_dict=True, ) - - -def set_expiry_for_email_queue(): - """Mark emails as expire that has not sent for 7 days. - Called daily via scheduler. - """ - - frappe.db.sql( - """ - UPDATE `tabEmail Queue` - SET `status`='Expired' - WHERE `modified` < (NOW() - INTERVAL '7' DAY) - AND `status`='Not Sent' - AND (`send_after` IS NULL OR `send_after` < %(now)s)""", - {"now": now_datetime()}, - ) diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 3b22bc4ce4..7b15440ccf 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -13,36 +13,6 @@ class InvalidEmailCredentials(frappe.ValidationError): pass -def send(email, append_to=None, retry=1): - """Deprecated: Send the message or add it to Outbox Email""" - - def _send(retry): - from frappe.email.doctype.email_account.email_account import EmailAccount - - try: - email_account = EmailAccount.find_outgoing(match_by_doctype=append_to) - smtpserver = email_account.get_smtp_server() - - # validate is called in as_string - email_body = email.as_string() - - smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []), email_body) - except smtplib.SMTPSenderRefused: - frappe.throw(_("Invalid login or password"), title="Email Failed") - raise - except smtplib.SMTPRecipientsRefused: - frappe.msgprint(_("Invalid recipient address"), title="Email Failed") - raise - except (smtplib.SMTPServerDisconnected, smtplib.SMTPAuthenticationError): - if not retry: - raise - else: - retry = retry - 1 - _send(retry) - - _send(retry) - - class SMTPServer: def __init__( self, diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 3f7577fac6..7ad016828a 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -4,8 +4,6 @@ FrappeClient is a library that helps you connect with other frappe systems import base64 import json -import requests - import frappe from frappe.utils.data import cstr @@ -37,6 +35,8 @@ class FrappeClient: api_secret=None, frappe_authorization_source=None, ): + import requests + self.headers = { "Accept": "application/json", "content-type": "application/x-www-form-urlencoded", @@ -390,42 +390,13 @@ class FrappeClient: class FrappeOAuth2Client(FrappeClient): def __init__(self, url, access_token, verify=True): + import requests + self.access_token = access_token self.headers = { "Authorization": "Bearer " + access_token, "content-type": "application/x-www-form-urlencoded", } self.verify = verify - self.session = OAuth2Session(self.headers) + self.session = requests.session() self.url = url - - def get_request(self, params): - res = requests.get( - self.url, params=self.preprocess(params), headers=self.headers, verify=self.verify - ) - res = self.post_process(res) - return res - - def post_request(self, data): - res = requests.post( - self.url, data=self.preprocess(data), headers=self.headers, verify=self.verify - ) - res = self.post_process(res) - return res - - -class OAuth2Session: - def __init__(self, headers): - self.headers = headers - - def get(self, url, params, verify): - res = requests.get(url, params=params, headers=self.headers, verify=verify) - return res - - def post(self, url, data, verify): - res = requests.post(url, data=data, headers=self.headers, verify=verify) - return res - - def put(self, url, data, verify): - res = requests.put(url, data=data, headers=self.headers, verify=verify) - return res diff --git a/frappe/hooks.py b/frappe/hooks.py index edf572b642..ed2b25bc1b 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -134,7 +134,6 @@ jinja = { "filters": [ "frappe.utils.data.global_date_format", "frappe.utils.markdown", - "frappe.website.utils.get_shade", "frappe.website.utils.abs_url", ], } @@ -223,7 +222,6 @@ scheduler_events = { "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.process_data_deletion_request", ], "daily": [ - "frappe.email.queue.set_expiry_for_email_queue", "frappe.desk.notifications.clear_notifications", "frappe.desk.doctype.event.event.send_event_digest", "frappe.sessions.clear_expired_sessions", diff --git a/frappe/installer.py b/frappe/installer.py index 9c2807d7cd..4f02e207bd 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -287,6 +287,9 @@ def install_app(name, verbose=False, set_as_patched=True, force=False): if out is False: return + for fn in frappe.get_hooks("before_app_install"): + frappe.get_attr(fn)(name) + if name != "frappe": add_module_defs(name, ignore_if_duplicate=force) @@ -302,6 +305,9 @@ def install_app(name, verbose=False, set_as_patched=True, force=False): for after_install in app_hooks.after_install or []: frappe.get_attr(after_install)() + for fn in frappe.get_hooks("after_app_install"): + frappe.get_attr(fn)(name) + sync_jobs() sync_fixtures(name) sync_customizations(name) @@ -369,6 +375,9 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) for before_uninstall in app_hooks.before_uninstall or []: frappe.get_attr(before_uninstall)() + for fn in frappe.get_hooks("before_app_uninstall"): + frappe.get_attr(fn)(app_name) + modules = frappe.get_all("Module Def", filters={"app_name": app_name}, pluck="name") drop_doctypes = _delete_modules(modules, dry_run=dry_run) @@ -382,6 +391,9 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) for after_uninstall in app_hooks.after_uninstall or []: frappe.get_attr(after_uninstall)() + for fn in frappe.get_hooks("after_app_uninstall"): + frappe.get_attr(fn)(app_name) + click.secho(f"Uninstalled App {app_name} from Site {site}", fg="green") frappe.flags.in_uninstall = False diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index d1cdd0d9e7..136e39e3a3 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -119,7 +119,7 @@ def authorize_access(g_calendar, reauthorize=None): ) if not google_calendar.authorization_code or reauthorize: - frappe.cache().hset("google_calendar", "google_calendar", google_calendar.name) + frappe.cache.hset("google_calendar", "google_calendar", google_calendar.name) return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: @@ -163,7 +163,7 @@ def google_callback(code=None): """ Authorization code is sent to callback as per the API configuration """ - google_calendar = frappe.cache().hget("google_calendar", "google_calendar") + google_calendar = frappe.cache.hget("google_calendar", "google_calendar") frappe.db.set_value("Google Calendar", google_calendar, "authorization_code", code) frappe.db.commit() diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index b9c96190ca..dcad1c8b5c 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -20,7 +20,7 @@ def run_webhooks(doc, method): # TODO: remove this hazardous unnecessary cache in flags if frappe.flags.webhooks is None: # load webhooks from cache - webhooks = frappe.cache().get_value("webhooks") + webhooks = frappe.cache.get_value("webhooks") if webhooks is None: # query webhooks webhooks_list = frappe.get_all( @@ -33,7 +33,7 @@ def run_webhooks(doc, method): webhooks = {} for w in webhooks_list: webhooks.setdefault(w.webhook_doctype, []).append(w) - frappe.cache().set_value("webhooks", webhooks) + frappe.cache.set_value("webhooks", webhooks) frappe.flags.webhooks = webhooks diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index 2edf2fcf5c..d308ec95ab 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -3,6 +3,9 @@ import json from contextlib import contextmanager +import responses +from responses.matchers import json_params_matcher + import frappe from frappe.integrations.doctype.webhook.webhook import ( enqueue_webhook, @@ -94,15 +97,21 @@ class TestWebhook(FrappeTestCase): self.test_user.email = "user1@integration.webhooks.test.com" self.test_user.first_name = "user1" + self.responses = responses.RequestsMock() + self.responses.start() + def tearDown(self) -> None: self.user.delete() self.test_user.delete() + + self.responses.stop() + self.responses.reset() super().tearDown() def test_webhook_trigger_with_enabled_webhooks(self): """Test webhook trigger for enabled webhooks""" - frappe.cache().delete_value("webhooks") + frappe.cache.delete_value("webhooks") frappe.flags.webhooks = None # Insert the user to db @@ -172,6 +181,13 @@ class TestWebhook(FrappeTestCase): self.assertEqual(data, {"name": self.user.name}) def test_webhook_req_log_creation(self): + self.responses.add( + responses.POST, + "https://httpbin.org/post", + status=200, + json={}, + ) + if not frappe.db.get_value("User", "user2@integration.webhooks.test.com"): user = frappe.get_doc( {"doctype": "User", "email": "user2@integration.webhooks.test.com", "first_name": "user2"} @@ -185,6 +201,7 @@ class TestWebhook(FrappeTestCase): self.assertTrue(frappe.get_all("Webhook Request Log", pluck="name")) def test_webhook_with_array_body(self): + """Check if array request body are supported.""" wh_config = { "doctype": "Webhook", @@ -194,7 +211,7 @@ class TestWebhook(FrappeTestCase): "request_url": "https://httpbin.org/post", "request_method": "POST", "request_structure": "JSON", - "webhook_json": '[\r\n{% for n in range(3) %}\r\n {\r\n "title": "{{ doc.title }}",\r\n "n": {{ n }}\r\n }\r\n {%- if not loop.last -%}\r\n , \r\n {%endif%}\r\n{%endfor%}\r\n]', + "webhook_json": '[\r\n{% for n in range(3) %}\r\n {\r\n "title": "{{ doc.title }}" }\r\n {%- if not loop.last -%}\r\n , \r\n {%endif%}\r\n{%endfor%}\r\n]', "meets_condition": "Yes", "webhook_headers": [ { @@ -204,13 +221,22 @@ class TestWebhook(FrappeTestCase): ], } - with get_test_webhook(wh_config) as wh: - doc = frappe.new_doc("Note") - doc.title = "Test Webhook Note" + doc = frappe.new_doc("Note") + doc.title = "Test Webhook Note" + expected_req = [{"title": doc.title} for _ in range(3)] + self.responses.add( + responses.POST, + "https://httpbin.org/post", + status=200, + json=expected_req, + match=[json_params_matcher(expected_req)], + ) + + with get_test_webhook(wh_config) as wh: enqueue_webhook(doc, wh) log = frappe.get_last_doc("Webhook Request Log") - self.assertEqual(len(json.loads(log.response)["json"]), 3) + self.assertEqual(len(json.loads(log.response)), 3) def test_webhook_with_dynamic_url_enabled(self): wh_config = { @@ -232,12 +258,16 @@ class TestWebhook(FrappeTestCase): ], } + self.responses.add( + responses.POST, + "https://httpbin.org/anything/Note", + status=200, + ) + with get_test_webhook(wh_config) as wh: doc = frappe.new_doc("Note") doc.title = "Test Webhook Note" enqueue_webhook(doc, wh) - log = frappe.get_last_doc("Webhook Request Log") - self.assertEqual(json.loads(log.response)["url"], "https://httpbin.org/anything/Note") def test_webhook_with_dynamic_url_disabled(self): wh_config = { @@ -259,11 +289,13 @@ class TestWebhook(FrappeTestCase): ], } + self.responses.add( + responses.POST, + "https://httpbin.org/anything/{{doc.doctype}}", + status=200, + ) + with get_test_webhook(wh_config) as wh: doc = frappe.new_doc("Note") doc.title = "Test Webhook Note" enqueue_webhook(doc, wh) - log = frappe.get_last_doc("Webhook Request Log") - self.assertEqual( - json.loads(log.response)["url"], "https://httpbin.org/anything/{{doc.doctype}}" - ) diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json index 404e0be944..1728da97d7 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -17,6 +17,7 @@ "html_condition", "sb_webhook", "request_url", + "timeout", "is_dynamic_url", "cb_webhook", "request_method", @@ -204,6 +205,14 @@ "fieldname": "is_dynamic_url", "fieldtype": "Check", "label": "Is Dynamic URL?" + }, + { + "default": "5", + "description": "The number of seconds until the request expires", + "fieldname": "timeout", + "fieldtype": "Int", + "label": "Request Timeout", + "reqd": 1 } ], "links": [ @@ -212,7 +221,7 @@ "link_fieldname": "webhook" } ], - "modified": "2023-06-02 17:25:12.598232", + "modified": "2023-06-16 10:21:00.971833", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 1b56a1b129..6fa24bfb67 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -29,7 +29,7 @@ class Webhook(Document): self.preview_document = None def on_update(self): - frappe.cache().delete_value("webhooks") + frappe.cache.delete_value("webhooks") def validate_docevent(self): if self.webhook_doctype: @@ -129,7 +129,7 @@ def enqueue_webhook(doc, webhook) -> None: url=request_url, data=json.dumps(data, default=str), headers=headers, - timeout=5, + timeout=webhook.timeout or 5, ) r.raise_for_status() frappe.logger().debug({"webhook_success": r.text}) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 63188e749d..609bfa4b9e 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -646,7 +646,10 @@ class BaseDocument: def update_modified(self): """Update modified timestamp""" self.set("modified", now()) - frappe.db.set_value(self.doctype, self.name, "modified", self.modified, update_modified=False) + if getattr(self.meta, "issingle", False): + frappe.db.set_single_value(self.doctype, "modified", self.modified, update_modified=False) + else: + frappe.db.set_value(self.doctype, self.name, "modified", self.modified, update_modified=False) def _fix_numeric_types(self): for df in self.meta.get("fields"): diff --git a/frappe/model/document.py b/frappe/model/document.py index f944b28a49..e2a55065bb 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -204,12 +204,11 @@ class Document(BaseDocument): if not self.has_permission(permtype): self.raise_no_permission_to(permlevel or permtype) - def has_permission(self, permtype="read", verbose=False) -> bool: + def has_permission(self, permtype="read") -> bool: """ Call `frappe.permissions.has_permission` if `ignore_permissions` flag isn't truthy :param permtype: `read`, `write`, `submit`, `cancel`, `delete`, etc. - :param verbose: DEPRECATED, will be removed in a future release. """ if self.flags.ignore_permissions: @@ -959,9 +958,7 @@ class Document(BaseDocument): filters={"enabled": 1, "document_type": self.doctype}, ) - self.flags.notifications = frappe.cache().hget( - "notifications", self.doctype, _get_notifications - ) + self.flags.notifications = frappe.cache.hget("notifications", self.doctype, _get_notifications) if not self.flags.notifications: return @@ -1126,7 +1123,7 @@ class Document(BaseDocument): def reset_seen(self): """Clear _seen property and set current user as seen""" - if getattr(self.meta, "track_seen", False): + if getattr(self.meta, "track_seen", False) and not getattr(self.meta, "issingle", False): frappe.db.set_value( self.doctype, self.name, "_seen", json.dumps([frappe.session.user]), update_modified=False ) @@ -1185,15 +1182,25 @@ class Document(BaseDocument): if self.name is None: return - frappe.db.set_value( - self.doctype, - self.name, - fieldname, - value, - self.modified, - self.modified_by, - update_modified=update_modified, - ) + if self.meta.issingle: + frappe.db.set_single_value( + self.doctype, + fieldname, + value, + modified=self.modified, + modified_by=self.modified_by, + update_modified=update_modified, + ) + else: + frappe.db.set_value( + self.doctype, + self.name, + fieldname, + value, + self.modified, + self.modified_by, + update_modified=update_modified, + ) self.run_method("on_change") @@ -1378,7 +1385,7 @@ class Document(BaseDocument): if not user: user = frappe.session.user - if self.meta.track_seen and not frappe.flags.read_only: + if self.meta.track_seen and not frappe.flags.read_only and not self.meta.issingle: _seen = self.get("_seen") or [] _seen = frappe.parse_json(_seen) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 32c1326170..83c21f8502 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -56,14 +56,12 @@ DEFAULT_FIELD_LABELS = { def get_meta(doctype, cached=True) -> "Meta": - if not cached: - return Meta(doctype) - - if meta := frappe.cache().hget("doctype_meta", doctype): + cached = cached and isinstance(doctype, str) + if cached and (meta := frappe.cache.hget("doctype_meta", doctype)): return meta meta = Meta(doctype) - frappe.cache().hset("doctype_meta", doctype, meta) + frappe.cache.hset("doctype_meta", meta.name, meta) return meta @@ -134,13 +132,10 @@ class Meta(Document): self.init_field_caches() return - has_custom_fields = self.add_custom_fields() + self.add_custom_fields() self.apply_property_setters() self.init_field_caches() - - if has_custom_fields: - self.sort_fields() - + self.sort_fields() self.get_valid_columns() self.set_custom_permissions() self.add_custom_links_and_actions() @@ -361,7 +356,6 @@ class Meta(Document): return self.extend("fields", custom_fields) - return True def apply_property_setters(self): """ @@ -372,11 +366,11 @@ class Meta(Document): if not frappe.db.table_exists("Property Setter"): return - property_setters = frappe.db.sql( - """select * from `tabProperty Setter` where - doc_type=%s""", - (self.name,), - as_dict=1, + property_setters = frappe.db.get_values( + "Property Setter", + filters={"doc_type": self.name}, + fieldname="*", + as_dict=True, ) if not property_setters: @@ -452,14 +446,56 @@ class Meta(Document): self._table_fields = self.get("fields", {"fieldtype": ["in", table_fields]}) def sort_fields(self): - """Sort custom fields on the basis of insert_after""" + """ + Sort fields on the basis of following rules (priority descending): + - `field_order` property setter + - `insert_after` computed based on default order for standard fields + - `insert_after` property for custom fields + """ - field_order = [] + if field_order := getattr(self, "field_order", []): + field_order = [fieldname for fieldname in json.loads(field_order) if fieldname in self._fields] + + # all fields match, best case scenario + if len(field_order) == len(self.fields): + self._update_fields_based_on_order(field_order) + return + + # if the first few standard fields are not in the field order, prepare to prepend them + if self.fields[0].fieldname not in field_order: + fields_to_prepend = [] + standard_field_found = False + + for fieldname, field in self._fields.items(): + if getattr(field, "is_custom_field", False): + # all custom fields from here on + break + + if fieldname in field_order: + standard_field_found = True + break + + fields_to_prepend.append(fieldname) + + if standard_field_found: + field_order = fields_to_prepend + field_order + else: + # worst case scenario, invalidate field_order + field_order = fields_to_prepend + + existing_fields = set(field_order) if field_order else False insert_after_map = {} - for field in self.fields: + for index, field in enumerate(self.fields): + if existing_fields and field.fieldname in existing_fields: + continue + if not getattr(field, "is_custom_field", False): - field_order.append(field.fieldname) + if existing_fields: + # compute insert_after from previous field + insert_after_map.setdefault(self.fields[index - 1].fieldname, []).append(field.fieldname) + else: + field_order.append(field.fieldname) elif insert_after := getattr(field, "insert_after", None): insert_after_map.setdefault(insert_after, []).append(field.fieldname) @@ -471,6 +507,9 @@ class Meta(Document): if insert_after_map: _update_field_order_based_on_insert_after(field_order, insert_after_map) + self._update_fields_based_on_order(field_order) + + def _update_fields_based_on_order(self, field_order): sorted_fields = [] for idx, fieldname in enumerate(field_order, 1): @@ -814,7 +853,7 @@ def trim_tables(doctype=None, dry_run=False, quiet=False): def trim_table(doctype, dry_run=True): - frappe.cache().hdel("table_columns", f"tab{doctype}") + frappe.cache.hdel("table_columns", f"tab{doctype}") ignore_fields = default_fields + optional_fields + child_table_fields columns = frappe.db.get_table_columns(doctype) fields = frappe.get_meta(doctype, cached=False).get_fieldnames_with_value() diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 73b5930563..0b76d18cff 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -151,7 +151,8 @@ def set_new_name(doc): if getattr(doc, "amended_from", None): _set_amended_name(doc) - return + if doc.name: + return elif getattr(doc.meta, "issingle", False): doc.name = doc.doctype @@ -506,6 +507,17 @@ def append_number_if_name_exists(doctype, value, fieldname="name", separator="-" def _set_amended_name(doc): + amend_naming_rule = frappe.db.get_value( + "Amended Document Naming Settings", {"document_type": doc.doctype}, "action", cache=True + ) + if not amend_naming_rule: + amend_naming_rule = frappe.db.get_single_value( + "Document Naming Settings", "default_amend_naming", cache=True + ) + + if amend_naming_rule == "Default Naming": + return + am_id = 1 am_prefix = doc.amended_from if frappe.db.get_value(doc.doctype, doc.amended_from, "amended_from"): diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 3908365291..e8f5626af4 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -53,6 +53,7 @@ def update_document_title( # handle bad API usages merge = sbool(merge) enqueue = sbool(enqueue) + action_enqueued = enqueue and not is_scheduler_inactive() doc = frappe.get_doc(doctype, docname) doc.check_permission(permtype="write") @@ -65,7 +66,7 @@ def update_document_title( name_updated = updated_name and (updated_name != doc.name) if name_updated: - if enqueue and not is_scheduler_inactive(): + if action_enqueued: current_name = doc.name # before_name hook may have DocType specific validations or transformations @@ -90,18 +91,27 @@ def update_document_title( doc.rename(updated_name, merge=merge) if title_updated: - try: - setattr(doc, title_field, updated_title) - doc.save() - frappe.msgprint(_("Saved"), alert=True, indicator="green") - except Exception as e: - if frappe.db.is_duplicate_entry(e): - frappe.throw( - _("{0} {1} already exists").format(doctype, frappe.bold(docname)), - title=_("Duplicate Name"), - exc=frappe.DuplicateEntryError, - ) - raise + if action_enqueued and name_updated: + frappe.enqueue( + "frappe.client.set_value", + doctype=doc.doctype, + name=updated_name, + fieldname=title_field, + value=updated_title, + ) + else: + try: + setattr(doc, title_field, updated_title) + doc.save() + frappe.msgprint(_("Saved"), alert=True, indicator="green") + except Exception as e: + if frappe.db.is_duplicate_entry(e): + frappe.throw( + _("{0} {1} already exists").format(doctype, frappe.bold(docname)), + title=_("Duplicate Name"), + exc=frappe.DuplicateEntryError, + ) + raise return doc.name @@ -685,39 +695,3 @@ def bulk_rename( if not via_console: return rename_log - - -def update_linked_doctypes( - doctype: str, docname: str, linked_to: str, value: str, ignore_doctypes: list | None = None -) -> None: - from frappe.model.utils.rename_doc import update_linked_doctypes - - show_deprecation_warning("update_linked_doctypes") - - return update_linked_doctypes( - doctype=doctype, - docname=docname, - linked_to=linked_to, - value=value, - ignore_doctypes=ignore_doctypes, - ) - - -def get_fetch_fields( - doctype: str, linked_to: str, ignore_doctypes: list | None = None -) -> list[dict]: - from frappe.model.utils.rename_doc import get_fetch_fields - - show_deprecation_warning("get_fetch_fields") - - return get_fetch_fields(doctype=doctype, linked_to=linked_to, ignore_doctypes=ignore_doctypes) - - -def show_deprecation_warning(funct: str) -> None: - from click import secho - - message = ( - f"Function frappe.model.rename_doc.{funct} has been deprecated and " - "moved to the frappe.model.utils.rename_doc" - ) - secho(message, fg="yellow") diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index 2935872fc7..f8f5b21de4 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -133,3 +133,13 @@ def is_virtual_doctype(doctype: str): if frappe.db.has_column("DocType", "is_virtual"): return frappe.db.get_value("DocType", doctype, "is_virtual") return False + + +@site_cache() +def is_single_doctype(doctype: str) -> bool: + from frappe.model.base_document import DOCTYPES_FOR_DOCTYPE + + if doctype in DOCTYPES_FOR_DOCTYPE: + return False + + return frappe.db.get_value("DocType", doctype, "issingle") diff --git a/frappe/model/utils/link_count.py b/frappe/model/utils/link_count.py index 49ed0d5a6c..65b5092d46 100644 --- a/frappe/model/utils/link_count.py +++ b/frappe/model/utils/link_count.py @@ -23,7 +23,7 @@ def flush_local_link_count(): if not new_links: return - link_count = frappe.cache().get_value("_link_count") or {} + link_count = frappe.cache.get_value("_link_count") or {} for key, value in new_links.items(): if key in link_count: @@ -31,13 +31,13 @@ def flush_local_link_count(): else: link_count[key] = value - frappe.cache().set_value("_link_count", link_count) + frappe.cache.set_value("_link_count", link_count) new_links.clear() def update_link_count(): """increment link count in the `idx` column for the given document""" - link_count = frappe.cache().get_value("_link_count") + link_count = frappe.cache.get_value("_link_count") if link_count: for (doctype, name), count in link_count.items(): @@ -50,4 +50,4 @@ def update_link_count(): if not frappe.db.is_table_missing(e): # table not found, single raise e # reset the count - frappe.cache().delete_value("_link_count") + frappe.cache.delete_value("_link_count") diff --git a/frappe/model/utils/user_settings.py b/frappe/model/utils/user_settings.py index c12c7e27ba..02bc67b929 100644 --- a/frappe/model/utils/user_settings.py +++ b/frappe/model/utils/user_settings.py @@ -11,7 +11,7 @@ filter_dict = {"doctype": 0, "docfield": 1, "operator": 2, "value": 3} def get_user_settings(doctype, for_update=False): - user_settings = frappe.cache().hget("_user_settings", f"{doctype}::{frappe.session.user}") + user_settings = frappe.cache.hget("_user_settings", f"{doctype}::{frappe.session.user}") if user_settings is None: user_settings = frappe.db.sql( @@ -41,12 +41,12 @@ def update_user_settings(doctype, user_settings, for_update=False): current.update(user_settings) - frappe.cache().hset("_user_settings", f"{doctype}::{frappe.session.user}", json.dumps(current)) + frappe.cache.hset("_user_settings", f"{doctype}::{frappe.session.user}", json.dumps(current)) def sync_user_settings(): """Sync from cache to database (called asynchronously via the browser)""" - for key, data in frappe.cache().hgetall("_user_settings").items(): + for key, data in frappe.cache.hgetall("_user_settings").items(): key = safe_decode(key) doctype, user = key.split("::") # WTF? frappe.db.multisql( @@ -99,4 +99,4 @@ def update_user_settings_data( ) # clear that user settings from the redis cache - frappe.cache().hset("_user_settings", f"{user_setting.doctype}::{user_setting.user}", None) + frappe.cache.hset("_user_settings", f"{user_setting.doctype}::{user_setting.user}", None) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index d61d2b3a2b..0e345a6515 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -26,12 +26,12 @@ class WorkflowPermissionError(frappe.ValidationError): def get_workflow_name(doctype): - workflow_name = frappe.cache().hget("workflow", doctype) + workflow_name = frappe.cache.hget("workflow", doctype) if workflow_name is None: workflow_name = frappe.db.get_value( "Workflow", {"document_type": doctype, "is_active": 1}, "name" ) - frappe.cache().hset("workflow", doctype, workflow_name or "") + frappe.cache.hset("workflow", doctype, workflow_name or "") return workflow_name @@ -210,7 +210,7 @@ def validate_workflow(doc): def get_workflow(doctype) -> "Workflow": - return frappe.get_doc("Workflow", get_workflow_name(doctype)) + return frappe.get_cached_doc("Workflow", get_workflow_name(doctype)) def has_approval_access(user, doc, transition): @@ -228,10 +228,10 @@ def send_email_alert(workflow_name): def get_workflow_field_value(workflow_name, field): - value = frappe.cache().hget("workflow_" + workflow_name, field) + value = frappe.cache.hget("workflow_" + workflow_name, field) if value is None: value = frappe.db.get_value("Workflow", workflow_name, field) - frappe.cache().hset("workflow_" + workflow_name, field, value) + frappe.cache.hset("workflow_" + workflow_name, field, value) return value diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 36e329409a..8c9a209501 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -34,6 +34,7 @@ ignore_values = { "Print Style": ["disabled"], "Module Onboarding": ["is_complete"], "Onboarding Step": ["is_complete", "is_skipped"], + "Workspace": ["is_hidden"], } ignore_doctypes = [""] diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 57d3e8f7ad..40e3b32690 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -214,7 +214,7 @@ def export_doc(doctype, name, module=None): def get_doctype_module(doctype: str) -> str: """Returns **Module Def** name of given doctype.""" - doctype_module_map = frappe.cache().get_value( + doctype_module_map = frappe.cache.get_value( "doctype_modules", generator=lambda: dict(frappe.qb.from_("DocType").select("name", "module").run()), ) diff --git a/frappe/monitor.py b/frappe/monitor.py index b93ba1d3bb..da2deb859e 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -106,22 +106,22 @@ class Monitor: traceback.print_exc() def store(self): - if frappe.cache().llen(MONITOR_REDIS_KEY) > MONITOR_MAX_ENTRIES: - frappe.cache().ltrim(MONITOR_REDIS_KEY, 1, -1) + if frappe.cache.llen(MONITOR_REDIS_KEY) > MONITOR_MAX_ENTRIES: + frappe.cache.ltrim(MONITOR_REDIS_KEY, 1, -1) serialized = json.dumps(self.data, sort_keys=True, default=str, separators=(",", ":")) - frappe.cache().rpush(MONITOR_REDIS_KEY, serialized) + frappe.cache.rpush(MONITOR_REDIS_KEY, serialized) def flush(): try: # Fetch all the logs without removing from cache - logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1) + logs = frappe.cache.lrange(MONITOR_REDIS_KEY, 0, -1) if logs: logs = list(map(frappe.safe_decode, logs)) with open(log_file(), "a", os.O_NONBLOCK) as f: f.write("\n".join(logs)) f.write("\n") # Remove fetched entries from cache - frappe.cache().ltrim(MONITOR_REDIS_KEY, len(logs) - 1, -1) + frappe.cache.ltrim(MONITOR_REDIS_KEY, len(logs) - 1, -1) except Exception: traceback.print_exc() diff --git a/frappe/patches.txt b/frappe/patches.txt index d3d5e3ee15..c26b1a74d7 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -60,7 +60,6 @@ frappe.patches.v10_0.refactor_social_login_keys frappe.patches.v10_0.enable_chat_by_default_within_system_settings frappe.patches.v10_0.remove_custom_field_for_disabled_domain execute:frappe.delete_doc("Page", "chat") -frappe.patches.v10_0.migrate_passwords_passlib frappe.patches.v11_0.rename_standard_reply_to_email_template execute:frappe.delete_doc_if_exists('Page', 'user-permissions') frappe.patches.v10_0.set_no_copy_to_workflow_state @@ -183,7 +182,6 @@ frappe.patches.v13_0.reset_corrupt_defaults frappe.patches.v13_0.remove_share_for_std_users execute:frappe.reload_doc('custom', 'doctype', 'custom_field') frappe.email.doctype.email_queue.patches.drop_search_index_on_message_id -frappe.patches.v14_0.update_workspace2 # 20.09.2021 frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 frappe.patches.v14_0.transform_todo_schema frappe.patches.v14_0.remove_post_and_post_comment @@ -201,6 +199,7 @@ execute:frappe.reload_doc("desk", "doctype", "Form Tour") [post_model_sync] execute:frappe.get_doc('Role', 'Guest').save() # remove desk access frappe.core.doctype.role.patches.v13_set_default_desk_properties +frappe.patches.v14_0.update_workspace2 # 06.06.2023 frappe.patches.v14_0.drop_data_import_legacy frappe.patches.v14_0.copy_mail_data #08.03.21 frappe.patches.v14_0.update_github_endpoints #08-11-2021 @@ -226,4 +225,5 @@ frappe.patches.v15_0.remove_prepared_report_settings_from_system_settings frappe.patches.v14_0.remove_manage_subscriptions_from_navbar frappe.patches.v15_0.remove_background_jobs_from_dropdown frappe.desk.doctype.form_tour.patches.introduce_ui_tours -execute:frappe.delete_doc_if_exists("Workspace", "Customization") \ No newline at end of file +execute:frappe.delete_doc_if_exists("Workspace", "Customization") +execute:frappe.db.set_single_value("Document Naming Settings", "default_amend_naming", "Amend Counter") diff --git a/frappe/patches/v10_0/migrate_passwords_passlib.py b/frappe/patches/v10_0/migrate_passwords_passlib.py deleted file mode 100644 index b18581ee3e..0000000000 --- a/frappe/patches/v10_0/migrate_passwords_passlib.py +++ /dev/null @@ -1,23 +0,0 @@ -import frappe -from frappe.utils.password import LegacyPassword - - -def execute(): - all_auths = frappe.db.sql( - """SELECT `name`, `password`, `salt` FROM `__Auth` - WHERE doctype='User' AND `fieldname`='password'""", - as_dict=True, - ) - - for auth in all_auths: - if auth.salt and auth.salt != "": - pwd = LegacyPassword.hash(auth.password, salt=auth.salt.encode("UTF-8")) - frappe.db.sql( - """UPDATE `__Auth` SET `password`=%(pwd)s, `salt`=NULL - WHERE `doctype`='User' AND `fieldname`='password' AND `name`=%(user)s""", - {"pwd": pwd, "user": auth.name}, - ) - - frappe.reload_doctype("User") - - frappe.db.sql_ddl("""ALTER TABLE `__Auth` DROP COLUMN `salt`""") diff --git a/frappe/patches/v14_0/update_workspace2.py b/frappe/patches/v14_0/update_workspace2.py index a6c9db503f..d7f03b3184 100644 --- a/frappe/patches/v14_0/update_workspace2.py +++ b/frappe/patches/v14_0/update_workspace2.py @@ -5,28 +5,15 @@ from frappe import _ def execute(): - frappe.reload_doc("desk", "doctype", "workspace", force=True) - - child_tables = frappe.get_all( - "DocField", - pluck="options", - filters={"fieldtype": ["in", frappe.model.table_fields], "parent": "Workspace"}, - ) - - for child_table in child_tables: - if child_table != "Has Role": - frappe.reload_doc("desk", "doctype", child_table, force=True) - - for seq, workspace in enumerate(frappe.get_all("Workspace", order_by="name asc")): + for seq, workspace in enumerate(frappe.get_all("Workspace")): doc = frappe.get_doc("Workspace", workspace.name) content = create_content(doc) update_workspace(doc, seq, content) - frappe.db.commit() def create_content(doc): content = [] - if doc.onboarding: + if doc.get("onboarding"): content.append({"type": "onboarding", "data": {"onboarding_name": doc.onboarding, "col": 12}}) if doc.charts: invalid_links = [] @@ -44,7 +31,7 @@ def create_content(doc): content.append( { "type": "header", - "data": {"text": doc.shortcuts_label or _("Your Shortcuts"), "level": 4, "col": 12}, + "data": {"text": doc.get("shortcuts_label") or _("Your Shortcuts"), "level": 4, "col": 12}, } ) for s in doc.shortcuts: @@ -60,7 +47,7 @@ def create_content(doc): content.append( { "type": "header", - "data": {"text": doc.cards_label or _("Reports & Masters"), "level": 4, "col": 12}, + "data": {"text": doc.get("cards_label") or _("Reports & Masters"), "level": 4, "col": 12}, } ) for l in doc.links: @@ -74,11 +61,16 @@ def create_content(doc): def update_workspace(doc, seq, content): - if not doc.title and not doc.content and not doc.is_standard and not doc.public: + if ( + not doc.title + and (not doc.content or doc.content == "[]") + and not doc.get("is_standard") + and not doc.public + ): doc.sequence_id = seq + 1 doc.content = json.dumps(content) doc.public = 0 if doc.for_user else 1 - doc.title = doc.extends or doc.label + doc.title = doc.get("extends") or doc.get("label") doc.extends = "" doc.category = "" doc.onboarding = "" diff --git a/frappe/permissions.py b/frappe/permissions.py index 431132a0ae..633d0e278d 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -48,7 +48,6 @@ def has_permission( doctype, ptype="read", doc=None, - verbose=False, user=None, raise_exception=True, *, @@ -60,7 +59,6 @@ def has_permission( :param doctype: DocType to check permission for :param ptype: Permission Type to check :param doc: Check User Permissions for specified document. - :param verbose: DEPRECATED, will be removed in a future release. :param user: User to check permission for. Defaults to current user. :param raise_exception: DOES NOT raise an exception. @@ -97,6 +95,7 @@ def has_permission( if not perm: push_perm_check_log( _("User {0} does not have access to this document").format(frappe.bold(user)) + + f": {_(doc.doctype)} - {doc.name}" ) else: if ptype == "submit" and not cint(meta.is_submittable): @@ -433,7 +432,7 @@ def get_roles(user=None, with_standard=True): ) return roles + ["All", "Guest"] - roles = frappe.cache().hget("roles", user, get) + roles = frappe.cache.hget("roles", user, get) # filter standard if required if not with_standard: @@ -533,7 +532,7 @@ def update_permission_property(doctype, role, permlevel, ptype, value=None, vali out = setup_custom_perms(doctype) - name = frappe.get_value("Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel)) + name = frappe.db.get_value("Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel)) table = DocType("Custom DocPerm") frappe.qb.update(table).set(ptype, value).where(table.name == name).run() diff --git a/frappe/public/icons/timeless/icons.svg b/frappe/public/icons/timeless/icons.svg index f5a34a14c9..29aa428d99 100644 --- a/frappe/public/icons/timeless/icons.svg +++ b/frappe/public/icons/timeless/icons.svg @@ -84,13 +84,15 @@ - - + - - + + + + + diff --git a/frappe/public/js/form_builder/FormBuilder.vue b/frappe/public/js/form_builder/FormBuilder.vue index 2a1441c51a..c1ab90c4a4 100644 --- a/frappe/public/js/form_builder/FormBuilder.vue +++ b/frappe/public/js/form_builder/FormBuilder.vue @@ -166,8 +166,7 @@ onMounted(() => { } } - :deep([data-has-std-field="false"]), - :deep([data-is-custom="1"]) { + :deep([data-is-user-generated="1"]) { background-color: var(--yellow-highlight-color); } } @@ -175,7 +174,7 @@ onMounted(() => { :deep(.preview) { --field-placeholder-color: var(--fg-bg-color); - .tab, .column, .field, [data-is-custom="1"] { + .tab, .column, .field { background-color: var(--fg-color); } diff --git a/frappe/public/js/form_builder/components/Column.vue b/frappe/public/js/form_builder/components/Column.vue index acb1ff735e..54dd49d5bf 100644 --- a/frappe/public/js/form_builder/components/Column.vue +++ b/frappe/public/js/form_builder/components/Column.vue @@ -6,7 +6,7 @@ import { ref } from "vue"; import { useStore } from "../store"; import { move_children_to_parent, confirm_dialog } from "../utils"; -let props = defineProps(["section", "column"]); +const props = defineProps(["section", "column"]); let store = useStore(); let hovered = ref(false); @@ -148,8 +148,6 @@ function move_columns_to_section() { :style="{ backgroundColor: column.fields.length ? '' : 'var(--field-placeholder-color)' }" v-model="column.fields" group="fields" - filter="[data-is-custom='0']" - :prevent-on-filter="false" :animation="200" :easing="store.get_animation" item-key="id" @@ -159,7 +157,7 @@ function move_columns_to_section() { diff --git a/frappe/public/js/form_builder/components/EditableInput.vue b/frappe/public/js/form_builder/components/EditableInput.vue index 8964838f4a..21b517af3b 100644 --- a/frappe/public/js/form_builder/components/EditableInput.vue +++ b/frappe/public/js/form_builder/components/EditableInput.vue @@ -3,7 +3,7 @@ import { ref, nextTick, computed } from "vue"; import { useStore } from "../store"; let store = useStore(); -let props = defineProps({ +const props = defineProps({ text: { type: String }, @@ -46,7 +46,7 @@ function focus_on_label() { :disabled="store.read_only" type="text" :placeholder="placeholder" - v-model="text" + :value="text" :style="{ width: hidden_span_width }" @input="event => $emit('update:modelValue', event.target.value)" @keydown.enter="editing = false" diff --git a/frappe/public/js/form_builder/components/Field.vue b/frappe/public/js/form_builder/components/Field.vue index e0230765b5..b67d6db0c9 100644 --- a/frappe/public/js/form_builder/components/Field.vue +++ b/frappe/public/js/form_builder/components/Field.vue @@ -4,7 +4,7 @@ import { ref, computed } from "vue"; import { useStore } from "../store"; import { move_children_to_parent, clone_field } from "../utils"; -let props = defineProps(["column", "field"]); +const props = defineProps(["column", "field"]); let store = useStore(); let hovered = ref(false); diff --git a/frappe/public/js/form_builder/components/Section.vue b/frappe/public/js/form_builder/components/Section.vue index c97fe1e4d8..cd675e5958 100644 --- a/frappe/public/js/form_builder/components/Section.vue +++ b/frappe/public/js/form_builder/components/Section.vue @@ -6,7 +6,7 @@ import { ref } from "vue"; import { useStore } from "../store"; import { section_boilerplate, move_children_to_parent, confirm_dialog } from "../utils"; -let props = defineProps(["tab", "section"]); +const props = defineProps(["tab", "section"]); let store = useStore(); let hovered = ref(false); @@ -160,8 +160,6 @@ function move_sections_to_tab() { backgroundColor: section.columns.length ? null : 'var(--field-placeholder-color)' }" v-model="section.columns" - filter="[data-has-std-field='true']" - :prevent-on-filter="false" group="columns" item-key="id" :animation="200" @@ -172,8 +170,7 @@ function move_sections_to_tab() { diff --git a/frappe/public/js/form_builder/components/Tabs.vue b/frappe/public/js/form_builder/components/Tabs.vue index b587d9d37e..5c233dbd1b 100644 --- a/frappe/public/js/form_builder/components/Tabs.vue +++ b/frappe/public/js/form_builder/components/Tabs.vue @@ -114,8 +114,6 @@ function delete_tab(with_children) { class="tabs" v-model="store.form.layout.tabs" group="tabs" - filter="[data-has-std-field='true']" - :prevent-on-filter="false" :animation="200" :easing="store.get_animation" item-key="id" @@ -125,8 +123,7 @@ function delete_tab(with_children) { diff --git a/frappe/public/js/form_builder/components/controls/AttachControl.vue b/frappe/public/js/form_builder/components/controls/AttachControl.vue index 86cdf7c5ac..6d8718d5dc 100644 --- a/frappe/public/js/form_builder/components/controls/AttachControl.vue +++ b/frappe/public/js/form_builder/components/controls/AttachControl.vue @@ -1,6 +1,6 @@ diff --git a/frappe/public/js/form_builder/components/controls/ButtonControl.vue b/frappe/public/js/form_builder/components/controls/ButtonControl.vue index bb6d4b8388..46fc97052c 100644 --- a/frappe/public/js/form_builder/components/controls/ButtonControl.vue +++ b/frappe/public/js/form_builder/components/controls/ButtonControl.vue @@ -1,6 +1,6 @@ diff --git a/frappe/public/js/form_builder/components/controls/CheckControl.vue b/frappe/public/js/form_builder/components/controls/CheckControl.vue index 0d786de0b4..8052e979ba 100644 --- a/frappe/public/js/form_builder/components/controls/CheckControl.vue +++ b/frappe/public/js/form_builder/components/controls/CheckControl.vue @@ -3,7 +3,7 @@ import { useStore } from "../../store"; import { useSlots } from "vue"; let store = useStore(); -let props = defineProps(["df", "value"]); +const props = defineProps(["df", "value"]); let slots = useSlots(); diff --git a/frappe/public/js/form_builder/components/controls/CodeControl.vue b/frappe/public/js/form_builder/components/controls/CodeControl.vue index d42bc43f81..b8051a8f48 100644 --- a/frappe/public/js/form_builder/components/controls/CodeControl.vue +++ b/frappe/public/js/form_builder/components/controls/CodeControl.vue @@ -4,7 +4,7 @@ import { computed, onMounted, ref, useSlots, watch } from "vue"; import { useStore } from "../../store"; let store = useStore(); -let props = defineProps(["df", "modelValue"]); +const props = defineProps(["df", "modelValue"]); let emit = defineEmits(["update:modelValue"]); let slots = useSlots(); diff --git a/frappe/public/js/form_builder/components/controls/DataControl.vue b/frappe/public/js/form_builder/components/controls/DataControl.vue index 2052912130..fcbc5d9c77 100644 --- a/frappe/public/js/form_builder/components/controls/DataControl.vue +++ b/frappe/public/js/form_builder/components/controls/DataControl.vue @@ -4,7 +4,7 @@ import { useStore } from "../../store"; import { ref, useSlots } from "vue"; let store = useStore(); -let props = defineProps(["df", "value"]); +const props = defineProps(["df", "value"]); let slots = useSlots(); let time_zone = ref(""); let placeholder = ref(""); diff --git a/frappe/public/js/form_builder/components/controls/GeolocationControl.vue b/frappe/public/js/form_builder/components/controls/GeolocationControl.vue index b46f6aed33..8acc005fad 100644 --- a/frappe/public/js/form_builder/components/controls/GeolocationControl.vue +++ b/frappe/public/js/form_builder/components/controls/GeolocationControl.vue @@ -1,7 +1,7 @@ diff --git a/frappe/public/js/form_builder/components/controls/LinkControl.vue b/frappe/public/js/form_builder/components/controls/LinkControl.vue index ce58c9a81e..8c1f73f255 100644 --- a/frappe/public/js/form_builder/components/controls/LinkControl.vue +++ b/frappe/public/js/form_builder/components/controls/LinkControl.vue @@ -4,7 +4,7 @@ import { onMounted, ref, useSlots, computed, watch } from "vue"; import { useStore } from "../../store"; let store = useStore(); -let props = defineProps(["args", "df", "modelValue"]); +const props = defineProps(["args", "df", "modelValue"]); let emit = defineEmits(["update:modelValue"]); let slots = useSlots(); diff --git a/frappe/public/js/form_builder/components/controls/RatingControl.vue b/frappe/public/js/form_builder/components/controls/RatingControl.vue index 9247f3e925..4356dc0b2d 100644 --- a/frappe/public/js/form_builder/components/controls/RatingControl.vue +++ b/frappe/public/js/form_builder/components/controls/RatingControl.vue @@ -1,7 +1,7 @@ diff --git a/frappe/public/js/form_builder/components/controls/TableControl.vue b/frappe/public/js/form_builder/components/controls/TableControl.vue index 425997e7ea..78518f6fd6 100644 --- a/frappe/public/js/form_builder/components/controls/TableControl.vue +++ b/frappe/public/js/form_builder/components/controls/TableControl.vue @@ -2,7 +2,7 @@ import { get_table_columns } from "../../utils"; import { computedAsync } from "@vueuse/core"; -let props = defineProps(["df"]); +const props = defineProps(["df"]); let table_columns = computedAsync(async () => { let doctype = props.df.options; diff --git a/frappe/public/js/form_builder/components/controls/TextControl.vue b/frappe/public/js/form_builder/components/controls/TextControl.vue index 8a5dbdf947..91fe920bdb 100644 --- a/frappe/public/js/form_builder/components/controls/TextControl.vue +++ b/frappe/public/js/form_builder/components/controls/TextControl.vue @@ -5,7 +5,7 @@ import { useSlots, ref, computed, watch } from "vue"; import { computedAsync } from "@vueuse/core"; let store = useStore(); -let props = defineProps(["df", "value", "modelValue"]); +const props = defineProps(["df", "value", "modelValue"]); let emit = defineEmits(["update:modelValue"]); let slots = useSlots(); diff --git a/frappe/public/js/form_builder/components/controls/TextEditorControl.vue b/frappe/public/js/form_builder/components/controls/TextEditorControl.vue index a47a444836..6a3aa21046 100644 --- a/frappe/public/js/form_builder/components/controls/TextEditorControl.vue +++ b/frappe/public/js/form_builder/components/controls/TextEditorControl.vue @@ -1,7 +1,7 @@ diff --git a/frappe/public/js/frappe/file_uploader/ImageCropper.vue b/frappe/public/js/frappe/file_uploader/ImageCropper.vue index 2ff8444ac0..4e5a49a5f0 100644 --- a/frappe/public/js/frappe/file_uploader/ImageCropper.vue +++ b/frappe/public/js/frappe/file_uploader/ImageCropper.vue @@ -43,7 +43,7 @@ import { computed, onMounted, ref, watch } from "vue"; import Cropper from "cropperjs"; // props -let props = defineProps({ +const props = defineProps({ file: Object, fixed_aspect_ratio: Number, }); diff --git a/frappe/public/js/frappe/file_uploader/ProgressRing.vue b/frappe/public/js/frappe/file_uploader/ProgressRing.vue index eca87fd7fe..952459d8ac 100644 --- a/frappe/public/js/frappe/file_uploader/ProgressRing.vue +++ b/frappe/public/js/frappe/file_uploader/ProgressRing.vue @@ -44,7 +44,7 @@ import { computed, ref } from "vue"; // props -let props = defineProps({ +const props = defineProps({ primary: String, secondary: String, radius: Number, diff --git a/frappe/public/js/frappe/file_uploader/TreeNode.vue b/frappe/public/js/frappe/file_uploader/TreeNode.vue index a14a41a7c2..308bb2b825 100644 --- a/frappe/public/js/frappe/file_uploader/TreeNode.vue +++ b/frappe/public/js/frappe/file_uploader/TreeNode.vue @@ -35,7 +35,7 @@ import TreeNode from "./TreeNode.vue"; import { computed } from "vue"; // props -let props = defineProps({ +const props = defineProps({ node: Object, selected_node: Object, }); diff --git a/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js b/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js index 7834c8adb1..f9134f10fa 100644 --- a/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js +++ b/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js @@ -1,5 +1,6 @@ import { createApp } from "vue"; import FileUploaderComponent from "./FileUploader.vue"; +import { watch } from "vue"; class FileUploader { constructor({ @@ -52,8 +53,8 @@ class FileUploader { this.uploader.wrapper_ready = true; } - this.uploader.$watch( - "files", + watch( + () => this.uploader.files, (files) => { let all_private = files.every((file) => file.private); if (this.dialog) { @@ -65,27 +66,36 @@ class FileUploader { { deep: true } ); - this.uploader.$watch("trigger_upload", (trigger_upload) => { - if (trigger_upload) { - this.upload_files(); + watch( + () => this.uploader.trigger_upload, + (trigger_upload) => { + if (trigger_upload) { + this.upload_files(); + } } - }); + ); - this.uploader.$watch("close_dialog", (close_dialog) => { - if (close_dialog) { - this.dialog && this.dialog.hide(); + watch( + () => this.uploader.close_dialog, + (close_dialog) => { + if (close_dialog) { + this.dialog && this.dialog.hide(); + } } - }); + ); - this.uploader.$watch("hide_dialog_footer", (hide_dialog_footer) => { - if (hide_dialog_footer) { - this.dialog && this.dialog.footer.addClass("hide"); - this.dialog.$wrapper.data("bs.modal")._config.backdrop = "static"; - } else { - this.dialog && this.dialog.footer.removeClass("hide"); - this.dialog.$wrapper.data("bs.modal")._config.backdrop = true; + watch( + () => this.uploader.hide_dialog_footer, + (hide_dialog_footer) => { + if (hide_dialog_footer) { + this.dialog && this.dialog.footer.addClass("hide"); + this.dialog.$wrapper.data("bs.modal")._config.backdrop = "static"; + } else { + this.dialog && this.dialog.footer.removeClass("hide"); + this.dialog.$wrapper.data("bs.modal")._config.backdrop = true; + } } - }); + ); if (files && files.length) { this.uploader.add_files(files); diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index fe665cee06..90b314377d 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -117,6 +117,11 @@ frappe.ui.form.Control = class BaseControl { } let value = frappe.model.get_value(this.doctype, this.docname, this.df.fieldname); + + if (in_list(["Date", "Datetime"], this.df.fieldtype) && value) { + value = frappe.datetime.str_to_user(value); + } + value = this.get_parsed_value(value); // hide if no value diff --git a/frappe/public/js/frappe/form/controls/currency.js b/frappe/public/js/frappe/form/controls/currency.js index a7d30c071b..e2f79ba446 100644 --- a/frappe/public/js/frappe/form/controls/currency.js +++ b/frappe/public/js/frappe/form/controls/currency.js @@ -7,7 +7,7 @@ frappe.ui.form.ControlCurrency = class ControlCurrency extends frappe.ui.form.Co get_precision() { // always round based on field precision or currency's precision // this method is also called in this.parse() - if (!this.df.precision) { + if (typeof this.df.precision != "number" && !this.df.precision) { if (frappe.boot.sysdefaults.currency_precision) { this.df.precision = frappe.boot.sysdefaults.currency_precision; } else { diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index ada729fd66..84b1989400 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -60,7 +60,11 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f this.clear_editable_layers(); const data_layers = new L.FeatureGroup().addLayer( - L.geoJson(JSON.parse(value), { pointToLayer: this.point_to_layer }) + L.geoJson(JSON.parse(value), { + pointToLayer: this.point_to_layer, + style: this.set_style, + onEachFeature: this.on_each_feature, + }) ); this.add_non_group_layers(data_layers, this.editableLayers); this.editableLayers.addTo(this.map); @@ -70,6 +74,8 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f /** * Defines custom rules for how geoJSON data is rendered on the map. * + * Can be inherited in custom map controllers. + * * @param {Object} geoJsonPoint - The geoJSON object to be rendered on the map. * @param {Object} latlng - The latitude and longitude where the geoJSON data should be rendered on the map. * @returns {Object} - Returns the Leaflet layer object to be rendered on the map. @@ -85,6 +91,28 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f } } + /** + * Defines custom styles for how geoJSON Line and LineString data is rendered on the map. + * + * Can be inherited in custom map controllers. + * + * @param {Object} geoJsonFeature - The geoJSON object to be rendered on the map. + * @returns {Object} - Returns the style object for the geoJSON object. + */ + set_style(geoJsonFeature) { + return {}; + } + + /** + * Is called after each feature is rendered and styles, can be used to attache popups, tooltips and other events + * + * Can be inherited in custom map controllers. + * + * @param {Object} feature - The leaflet object representing a geojson feature. + * @param {Object} layer - The leaflet layer object. + */ + on_each_feature(feature, layer) {} + bind_leaflet_map() { const circleToGeoJSON = L.Circle.prototype.toGeoJSON; L.Circle.include({ diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index b187135181..cb6beadb7a 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -267,15 +267,17 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat r.results = me.merge_duplicates(r.results); // show filter description in awesomplete - if (args.filters) { - let filter_string = me.get_filter_description(args.filters); - if (filter_string) { - r.results.push({ - html: `${filter_string}`, - value: "", - action: () => {}, - }); - } + let filter_string = me.df.filter_description + ? me.df.filter_description + : args.filters + ? me.get_filter_description(args.filters) + : null; + if (filter_string) { + r.results.push({ + html: `${filter_string}`, + value: "", + action: () => {}, + }); } if (!me.df.only_select) { diff --git a/frappe/public/js/frappe/form/controls/select.js b/frappe/public/js/frappe/form/controls/select.js index 6a6f4ccf58..de96cd902c 100644 --- a/frappe/public/js/frappe/form/controls/select.js +++ b/frappe/public/js/frappe/form/controls/select.js @@ -74,7 +74,7 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro if (this.$input) { var selected = this.$input.find(":selected").val(); this.$input.empty(); - frappe.ui.form.add_options(this.$input, options || []); + frappe.ui.form.add_options(this.$input, options || [], this.df.sort_options); if (value === undefined && selected) { this.$input.val(selected); @@ -102,38 +102,27 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro } }; -frappe.ui.form.add_options = function (input, options_list) { +frappe.ui.form.add_options = function (input, options_list, sort) { let $select = $(input); if (!Array.isArray(options_list)) { return $select; } - // create options - for (var i = 0, j = options_list.length; i < j; i++) { - var v = options_list[i]; - var value = null; - var label = null; - if (!is_null(v)) { - var is_value_null = is_null(v.value); - var is_label_null = is_null(v.label); - var is_disabled = Boolean(v.disabled); - var is_selected = Boolean(v.selected); - if (is_value_null && is_label_null) { - value = v; - label = __(v); - } else { - value = is_value_null ? "" : v.value; - label = is_label_null ? __(value) : __(v.label); - } - } - - $("") - .html(cstr(label)) - .attr("value", value) - .prop("disabled", is_disabled) - .prop("selected", is_selected) - .appendTo($select.get(0)); + let options = options_list.map((raw_option) => parse_option(raw_option)); + if (sort) { + options = options.sort((a, b) => a.label.localeCompare(b.label)); } + + options + .map((option) => + $("") + .html(cstr(option.label)) + .attr("value", option.value) + .prop("disabled", option.is_disabled) + .prop("selected", option.is_selected) + ) + .forEach(($option) => $option.appendTo($select.get(0))); + // select the first option $select.get(0).selectedIndex = 0; $select.trigger("select-change"); @@ -142,8 +131,8 @@ frappe.ui.form.add_options = function (input, options_list) { // add list to (function ($) { - $.fn.add_options = function (options_list) { - return frappe.ui.form.add_options(this.get(0), options_list); + $.fn.add_options = function (options_list, sort) { + return frappe.ui.form.add_options(this.get(0), options_list, sort); }; $.fn.set_working = function () { this.prop("disabled", true); @@ -159,3 +148,32 @@ frappe.ui.form.add_options = function (input, options_list) { return result; }; })(jQuery); + +function parse_option(v) { + let value = null; + let label = null; + let is_disabled = false; + let is_selected = false; + + if (!is_null(v)) { + const is_value_null = is_null(v.value); + const is_label_null = is_null(v.label); + is_disabled = Boolean(v.disabled); + is_selected = Boolean(v.selected); + + if (is_value_null && is_label_null) { + value = v; + label = __(v); + } else { + value = is_value_null ? "" : v.value; + label = is_label_null ? __(value) : __(v.label); + } + } + + return { + value, + label, + is_disabled, + is_selected, + }; +} diff --git a/frappe/public/js/frappe/form/footer/base_timeline.js b/frappe/public/js/frappe/form/footer/base_timeline.js index af07b1cb95..d7ae78d748 100644 --- a/frappe/public/js/frappe/form/footer/base_timeline.js +++ b/frappe/public/js/frappe/form/footer/base_timeline.js @@ -82,6 +82,18 @@ class BaseTimeline { items.forEach((item) => this.add_timeline_item(item, append_at_the_end)); } + add_timeline_items_based_on_creation(items) { + items.forEach((item) => { + this.timeline_items_wrapper.find(".timeline-item").each((i, el) => { + let creation = $(el).attr("data-timestamp"); + if (creation && new Date(creation) < new Date(item.creation)) { + $(el).before(this.get_timeline_item(item)); + return false; + } + }); + }); + } + get_timeline_item(item) { // item can have content*, creation*, // timeline_badge, icon, icon_size, @@ -90,6 +102,7 @@ class BaseTimeline { timeline_item.attr({ "data-doctype": item.doctype, "data-name": item.name, + "data-timestamp": item.creation, }); if (item.icon) { timeline_item.append(` @@ -114,6 +127,23 @@ class BaseTimeline { if (item.id) { timeline_content.attr("id", item.id); } + + if (item.append_load_more) { + timeline_item.append( + ` + ${__( + "Load More Communications" + )} + ` + ); + } + + timeline_item.find(".btn-load-more").on("click", async () => { + let more_items = await this.get_more_communication_timeline_contents(); + timeline_item.find(".timeline-load-more").remove(); + this.add_timeline_items_based_on_creation(more_items); + }); + return timeline_item; } } diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index 5514693c05..4d2b8e8a1c 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -193,7 +193,7 @@ class FormTimeline extends BaseTimeline { return view_timeline_contents; } - get_communication_timeline_contents() { + get_communication_timeline_contents(more_items) { let communication_timeline_contents = []; let icon_set = { Email: "mail", @@ -201,7 +201,8 @@ class FormTimeline extends BaseTimeline { Meeting: "calendar", Other: "dot-horizontal", }; - (this.doc_info.communications || []).forEach((communication) => { + let items = more_items ? more_items : this.doc_info.communications || []; + items.forEach((communication) => { let medium = communication.communication_medium; communication_timeline_contents.push({ icon: icon_set[medium], @@ -214,9 +215,34 @@ class FormTimeline extends BaseTimeline { name: communication.name, }); }); + + if (communication_timeline_contents.length >= 20) { + communication_timeline_contents[ + communication_timeline_contents.length - 1 + ].append_load_more = true; + } + return communication_timeline_contents; } + async get_more_communication_timeline_contents() { + let more_items = []; + let response = await frappe.call({ + method: "frappe.desk.form.load.get_communications", + args: { + doctype: this.doc_info.doctype, + name: this.doc_info.name, + start: this.doc_info.communications.length, + limit: 20, + }, + }); + if (response.message) { + this.doc_info.communications.push(...response.message); + more_items = this.get_communication_timeline_contents(response.message); + } + return more_items; + } + get_communication_timeline_content(doc, allow_reply = true) { doc._url = frappe.utils.get_form_link("Communication", doc.name); this.set_communication_doc_status(doc); diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 4622c58155..0a60b00af2 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1414,8 +1414,13 @@ frappe.ui.form.Form = class FrappeForm { if (selector.length) { frappe.utils.scroll_to(selector); } - } else if (window.location.hash && $(window.location.hash).length) { - frappe.utils.scroll_to(window.location.hash, true, 200, null, null, true); + } else if (window.location.hash) { + if ($(window.location.hash).length) { + frappe.utils.scroll_to(window.location.hash, true, 200, null, null, true); + } else { + this.scroll_to_field(window.location.hash.replace("#", "")) && + history.replaceState(null, null, " "); + } } } @@ -1926,11 +1931,12 @@ frappe.ui.form.Form = class FrappeForm { } // highlight control inside field - let control_element = $el.find(".form-control"); + let control_element = $el.closest(".frappe-control"); control_element.addClass("highlight"); setTimeout(() => { control_element.removeClass("highlight"); }, 2000); + return true; } setup_docinfo_change_listener() { @@ -2108,19 +2114,3 @@ frappe.ui.form.Form = class FrappeForm { }; frappe.validated = 0; -// Proxy for frappe.validated -Object.defineProperty(window, "validated", { - get: function () { - console.warn( - "Please use `frappe.validated` instead of `validated`. It will be deprecated soon." - ); // eslint-disable-line - return frappe.validated; - }, - set: function (value) { - console.warn( - "Please use `frappe.validated` instead of `validated`. It will be deprecated soon." - ); // eslint-disable-line - frappe.validated = value; - return frappe.validated; - }, -}); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 637fd7063d..beddbf512d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -103,9 +103,15 @@ frappe.form.formatters = { }, Currency: function (value, docfield, options, doc) { var currency = frappe.meta.get_field_currency(docfield, doc); - var precision = cint( - docfield.precision ?? frappe.boot.sysdefaults.currency_precision ?? 2 - ); + + let precision; + if (typeof docfield.precision == "number") { + precision = docfield.precision; + } else { + precision = cint( + docfield.precision || frappe.boot.sysdefaults.currency_precision || 2 + ); + } // If you change anything below, it's going to hurt a company in UAE, a bit. if (precision > 2) { diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index d4fdd47a11..356c5f4d1d 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -963,7 +963,17 @@ export default class GridRow { } var col = this; let first_input_field = $(col).find('input[type="Text"]:first'); - first_input_field.trigger("focus"); + let input_in_focus = false; + + $(col) + .find("input[type='text']") + .each(function () { + if ($(this).is(":focus")) { + input_in_focus = true; + } + }); + + !input_in_focus && first_input_field.trigger("focus"); if (event.pointerType == "touch") { first_input_field.length && on_input_focus(first_input_field); diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index aea8d07f57..51e2f1317b 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -113,7 +113,7 @@ frappe.ui.form.QuickEntryForm = class QuickEntryForm { this.mandatory = [ { fieldname: "__newname", - label: __("{0} Name", [this.meta.name]), + label: __("{0} Name", [__(this.meta.name)]), reqd: 1, fieldtype: "Data", }, diff --git a/frappe/public/js/frappe/form/section.js b/frappe/public/js/frappe/form/section.js index b4908e749a..5acae84f46 100644 --- a/frappe/public/js/frappe/form/section.js +++ b/frappe/public/js/frappe/form/section.js @@ -84,7 +84,7 @@ export default class Section { add_field(fieldobj) { this.fields_list.push(fieldobj); - this.fields_dict[fieldobj.fieldname] = fieldobj; + this.fields_dict[fieldobj.df.fieldname] = fieldobj; fieldobj.section = this; } diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index dbe88f2306..6d9a3365c4 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -109,6 +109,7 @@ frappe.ui.form.Toolbar = class Toolbar { } let rename_document = () => { + if (input_name != docname) frappe.socketio.doctype_subscribe(doctype, input_name); return frappe .xcall("frappe.model.rename_doc.update_document_title", { doctype, @@ -129,9 +130,8 @@ frappe.ui.form.Toolbar = class Toolbar { }; // handle document renaming queued action - if (input_name && new_docname == docname) { - frappe.socketio.doc_subscribe(doctype, input_name); - frappe.realtime.on("doc_update", (data) => { + if (input_name != docname) { + frappe.realtime.on("list_update", (data) => { if (data.doctype == doctype && data.name == input_name) { reload_form(input_name); frappe.show_alert({ diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index f009593f6f..c0eef27b9d 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -456,22 +456,20 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { get_no_result_message() { let help_link = this.get_documentation_link(); let filters = this.filter_area && this.filter_area.get(); - let no_result_message = - filters && filters.length - ? __("No {0} found", [__(this.doctype)]) - : __("You haven't created a {0} yet", [__(this.doctype)]); - let new_button_label = - filters && filters.length - ? __( - "Create a new {0}", - [__(this.doctype)], - "Create a new document from list view" - ) - : __( - "Create your first {0}", - [__(this.doctype)], - "Create a new document from list view" - ); + + let has_filters_set = filters && filters.length; + let no_result_message = has_filters_set + ? __("No {0} found with matching filters. Clear filters to see all {0}.", [ + __(this.doctype), + ]) + : __("You haven't created a {0} yet", [__(this.doctype)]); + let new_button_label = has_filters_set + ? __("Create a new {0}", [__(this.doctype)], "Create a new document from list view") + : __( + "Create your first {0}", + [__(this.doctype)], + "Create a new document from list view" + ); let empty_state_image = this.settings.empty_state_image || "/assets/frappe/images/ui-states/list-empty-state.svg"; diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 523cb3b7a6..5deccaeb9b 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -163,7 +163,8 @@ $.extend(frappe.model, { if (!user_default) { user_default = frappe.defaults.get_user_default(df.fieldname); - } else if ( + } + if ( !user_default && df.remember_last_selected_value && frappe.boot.user.last_selected_values diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 57ab567c1b..355e0a9c5d 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -633,8 +633,7 @@ $.extend(frappe.model, { }, clear_table: function (doc, parentfield) { - for (var i = 0, l = (doc[parentfield] || []).length; i < l; i++) { - var d = doc[parentfield][i]; + for (const d of doc[parentfield] || []) { delete locals[d.doctype][d.name]; } doc[parentfield] = []; @@ -837,9 +836,9 @@ $.extend(frappe.model, { } if ( - (frm.doc.fields.find((i) => i.fieldname === "latitude") && - frm.doc.fields.find((i) => i.fieldname === "longitude")) || - frm.doc.fields.find( + (frm.doc.fields?.find((i) => i.fieldname === "latitude") && + frm.doc.fields?.find((i) => i.fieldname === "longitude")) || + frm.doc.fields?.find( (i) => i.fieldname === "location" && i.fieldtype == "Geolocation" ) ) { diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index def04cf37e..9ad917a57a 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -31,20 +31,21 @@ window.addEventListener("popstate", (e) => { return false; }); -// routing v2, capture all clicks so that the target is managed with push-state +// Capture all clicks so that the target is managed with push-state $("body").on("click", "a", function (e) { - let override = (route) => { + const target_element = e.currentTarget; + const href = target_element.getAttribute("href"); + const is_on_same_host = target_element.hostname === window.location.hostname; + + const override = (route) => { e.preventDefault(); frappe.set_route(route); return false; }; - const target_element = e.currentTarget; - const href = target_element.getAttribute("href"); - const is_on_same_host = target_element.hostname === window.location.hostname; - // click handled, but not by href if ( + !is_on_same_host || // external link target_element.getAttribute("onclick") || // has a handler e.ctrlKey || e.metaKey || // open in a new tab @@ -53,18 +54,13 @@ $("body").on("click", "a", function (e) { return; } - if (href === "") { - return override("/app"); - } - if (href && href.startsWith("#")) { // target startswith "#", this is a v1 style route, so remake it. return override(target_element.hash); } - if (is_on_same_host && frappe.router.is_app_route(target_element.pathname)) { + if (frappe.router.is_app_route(target_element.pathname)) { // target has "/app, this is a v2 style route. - if (target_element.search) { frappe.route_options = {}; let params = new URLSearchParams(target_element.search); @@ -72,7 +68,10 @@ $("body").on("click", "a", function (e) { frappe.route_options[key] = value; } } - return override(target_element.pathname + target_element.hash); + if (target_element.hash) { + frappe.route_hash = target_element.hash; + } + return override(target_element.pathname); } }); @@ -352,8 +351,8 @@ frappe.router = { route = this.get_route_from_arguments(route); route = this.convert_from_standard_route(route); let sub_path = this.make_url(route); - // replace each # occurrences in the URL with encoded character except for last - // sub_path = sub_path.replace(/[#](?=.*[#])/g, "%23"); + sub_path += frappe.route_hash || ""; + frappe.route_hash = null; if (frappe.open_in_new_tab) { localStorage["route_options"] = JSON.stringify(frappe.route_options); window.open(sub_path, "_blank"); diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index f48aba1b7f..dcfdd91d40 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -324,9 +324,9 @@ frappe.ui.GroupBy = class { ); if (this.aggregate_function === "sum") { - docfield.label = __("Sum of {0}", [docfield.label]); + docfield.label = __("Sum of {0}", [__(docfield.label)]); } else { - docfield.label = __("Average of {0}", [docfield.label]); + docfield.label = __("Average of {0}", [__(docfield.label)]); } } @@ -367,7 +367,9 @@ frappe.ui.GroupBy = class { ["Select", "Link", "Data", "Int", "Check"].includes(f.fieldtype) ); const tag_field = { fieldname: "_user_tags", fieldtype: "Data", label: __("Tags") }; - this.group_by_fields[this.doctype] = fields.concat(tag_field); + this.group_by_fields[this.doctype] = fields + .concat(tag_field) + .sort((a, b) => __(a.label).localeCompare(__(b.label))); this.all_fields[this.doctype] = this.report_view.meta.fields; const standard_fields_filter = (df) => @@ -379,7 +381,8 @@ frappe.ui.GroupBy = class { const cdt = df.options; const child_table_fields = frappe.meta .get_docfields(cdt) - .filter(standard_fields_filter); + .filter(standard_fields_filter) + .sort((a, b) => __(a.label).localeCompare(__(b.label))); this.group_by_fields[cdt] = child_table_fields; this.all_fields[cdt] = child_table_fields; }); diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index bbd74dba4a..16096e1845 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -376,6 +376,7 @@ frappe.show_progress = (title, count, total = 100, description, hide_on_completi // timeout to avoid abrupt hide setTimeout(frappe.hide_progress, 500); } + frappe.cur_progress.$wrapper.css("z-index", 2000); return dialog; }; @@ -458,13 +459,3 @@ frappe.show_alert = frappe.toast = function (message, seconds = 7, actions = {}) return div; }; - -// Proxy for frappe.show_alert -Object.defineProperty(window, "show_alert", { - get: function () { - console.warn( - "Please use `frappe.show_alert` instead of `show_alert`. It will be deprecated soon." - ); - return frappe.show_alert; - }, -}); diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js index 44ce1bb44d..0746b794f0 100644 --- a/frappe/public/js/frappe/utils/datetime.js +++ b/frappe/public/js/frappe/utils/datetime.js @@ -270,34 +270,3 @@ $.extend(frappe.datetime, { return moment.weekdays().indexOf(first_day_of_the_week); }, }); - -// Proxy for dateutil and get_today -Object.defineProperties(window, { - dateutil: { - get: function () { - console.warn( - "Please use `frappe.datetime` instead of `dateutil`. It will be deprecated soon." - ); - return frappe.datetime; - }, - configurable: true, - }, - date: { - get: function () { - console.warn( - "Please use `frappe.datetime` instead of `date`. It will be deprecated soon." - ); - return frappe.datetime; - }, - configurable: true, - }, - get_today: { - get: function () { - console.warn( - "Please use `frappe.datetime.get_today` instead of `get_today`. It will be deprecated soon." - ); - return frappe.datetime.get_today; - }, - configurable: true, - }, -}); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 095b04c931..b75d2981a5 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -340,9 +340,21 @@ Object.assign(frappe.utils, { scroll_top = 0; } + const highlight = () => { + if (highlight_element) { + $(element).addClass("highlight"); + document.addEventListener( + "click", + function () { + $(element).removeClass("highlight"); + }, + { once: true } + ); + } + }; // already there if (scroll_top == element_to_be_scrolled.scrollTop()) { - return; + return highlight(); } if (animate) { @@ -352,16 +364,7 @@ Object.assign(frappe.utils, { }) .promise() .then(() => { - if (highlight_element) { - $(element).addClass("highlight"); - document.addEventListener( - "click", - function () { - $(element).removeClass("highlight"); - }, - { once: true } - ); - } + highlight(); callback && callback(); }); } else { @@ -789,10 +792,6 @@ Object.assign(frappe.utils, { frappe.msgprint(__("Note: Changing the Page Name will break previous URL to this page.")); }, - notify: function (subject, body, route, onclick) { - console.log("push notifications are evil and deprecated"); - }, - set_title: function (title) { frappe._original_title = title; if (frappe._title_prefix) { diff --git a/frappe/public/js/frappe/views/kanban/kanban_settings.js b/frappe/public/js/frappe/views/kanban/kanban_settings.js index fb294be30c..076ab77547 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_settings.js +++ b/frappe/public/js/frappe/views/kanban/kanban_settings.js @@ -111,14 +111,14 @@ export default class KanbanSettings { fields_html.html(` - Fields + ${__("Fields")} ${fields} - + Add / Remove Fields + ${__("+ Add / Remove Fields")} diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index fb15131394..25b875c2f9 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -102,7 +102,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { this.menu_items.push({ label: __("Delete Kanban Board"), action: () => { - frappe.confirm("Are you sure you want to proceed?", () => { + frappe.confirm(__("Are you sure you want to proceed?"), () => { frappe.db.delete_doc("Kanban Board", this.board_name).then(() => { frappe.show_alert(`Kanban Board ${this.board_name} deleted.`); frappe.set_route("List", this.doctype, "List"); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 46c407cba1..a7a86a981f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1960,12 +1960,3 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return this.get_filter_values; } }; - -Object.defineProperty(frappe, "query_report_filters_by_name", { - get() { - console.warn( - "[Query Report] frappe.query_report_filters_by_name is deprecated. Please use the new api: frappe.query_report.get_filter_value(fieldname) and frappe.query_report.set_filter_value(fieldname, value)" - ); - return null; - }, -}); diff --git a/frappe/public/js/frappe/views/reports/report_utils.js b/frappe/public/js/frappe/views/reports/report_utils.js index 9713f8bb99..16b02c28a1 100644 --- a/frappe/public/js/frappe/views/reports/report_utils.js +++ b/frappe/public/js/frappe/views/reports/report_utils.js @@ -126,6 +126,13 @@ frappe.report_utils = { .then((r) => { frappe.dom.eval(r.script || ""); return frappe.after_ajax(() => { + if ( + frappe.query_reports[report_name] && + !frappe.query_reports[report_name].filters && + r.filters + ) { + return (frappe.query_reports[report_name].filters = r.filters); + } return ( frappe.query_reports[report_name] && frappe.query_reports[report_name].filters diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js index 894844497b..2bf717c1e7 100644 --- a/frappe/public/js/frappe/views/workspace/workspace.js +++ b/frappe/public/js/frappe/views/workspace/workspace.js @@ -22,7 +22,6 @@ frappe.views.Workspace = class Workspace { this.page = wrapper.page; this.blocks = frappe.workspace_block.blocks; this.is_read_only = true; - this.is_page_loaded = false; this.pages = {}; this.sorted_public_items = []; this.sorted_private_items = []; @@ -243,20 +242,17 @@ frappe.views.Workspace = class Workspace { } let page = this.get_page_to_show(); - this.page.set_title(__(page.name)); - - this.update_selected_sidebar(this.current_page, false); //remove selected from old page - this.update_selected_sidebar(page, true); //add selected on new page if (!frappe.router.current_route[0]) { - this.is_page_loaded = true; + frappe.route_flags.replace_route = true; frappe.set_route(frappe.router.slug(page.public ? page.name : "private/" + page.name)); + return; } - if (!this.is_page_loaded) { - this.show_page(page); - this.is_page_loaded = false; - } + this.page.set_title(__(page.name)); + this.update_selected_sidebar(this.current_page, false); //remove selected from old page + this.update_selected_sidebar(page, true); //add selected on new page + this.show_page(page); } update_selected_sidebar(page, add) { @@ -335,12 +331,9 @@ frappe.views.Workspace = class Workspace { default_page = { name: "Build", public: true }; } - let page = - (frappe.get_route()[1] == "private" ? frappe.get_route()[2] : frappe.get_route()[1]) || - default_page.name; - let is_public = frappe.get_route()[1] - ? frappe.get_route()[1] != "private" - : default_page.public; + const route = frappe.get_route(); + const page = (route[1] == "private" ? route[2] : route[1]) || default_page.name; + const is_public = route[1] ? route[1] != "private" : default_page.public; return { name: page, public: is_public }; } diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index af89f95197..26b1dabd35 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -238,7 +238,7 @@ export default class NumberCardWidget extends Widget { color_class = "green-stat"; } else { caret_html = ` - ${frappe.utils.icon("arrow-down-left", "xs")} + ${frappe.utils.icon("arrow-down-right", "xs")} `; color_class = "red-stat"; } diff --git a/frappe/public/js/lib/moment.js b/frappe/public/js/lib/moment.js index 7a817a36cd..dca0b1097a 100644 --- a/frappe/public/js/lib/moment.js +++ b/frappe/public/js/lib/moment.js @@ -1,5 +1,5 @@ // This file is used to make sure that `moment` is bound to the window // before the bundle finishes loading, due to imports (datetime.js) in the bundle // that depend on `moment`. -import momentTimezone from "moment-timezone/builds/moment-timezone-with-data.js"; +import momentTimezone from "moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js"; window.moment = momentTimezone; diff --git a/frappe/public/js/print_format_builder/ConfigureColumns.vue b/frappe/public/js/print_format_builder/ConfigureColumns.vue index 14d011e409..df30898294 100644 --- a/frappe/public/js/print_format_builder/ConfigureColumns.vue +++ b/frappe/public/js/print_format_builder/ConfigureColumns.vue @@ -70,7 +70,7 @@ import { computed } from "vue"; import draggable from "vuedraggable"; // props -let props = defineProps(["df"]); +const props = defineProps(["df"]); // methods function remove_column(column) { diff --git a/frappe/public/js/print_format_builder/Field.vue b/frappe/public/js/print_format_builder/Field.vue index 34aee108b8..8e9f17aab0 100644 --- a/frappe/public/js/print_format_builder/Field.vue +++ b/frappe/public/js/print_format_builder/Field.vue @@ -79,7 +79,7 @@ import ConfigureColumnsVue from "./ConfigureColumns.vue"; import { createApp, ref, nextTick, watch } from "vue"; // props -let props = defineProps(["df"]); +const props = defineProps(["df"]); // variables let editing = ref(false); diff --git a/frappe/public/js/print_format_builder/HTMLEditor.vue b/frappe/public/js/print_format_builder/HTMLEditor.vue index b84909ac4b..a6c0c71d47 100644 --- a/frappe/public/js/print_format_builder/HTMLEditor.vue +++ b/frappe/public/js/print_format_builder/HTMLEditor.vue @@ -17,7 +17,7 @@ import { ref } from "vue"; // props -let props = defineProps(["value", "button-label"]); +const props = defineProps(["value", "button-label"]); // emits let emit = defineEmits(["change"]); diff --git a/frappe/public/js/print_format_builder/PrintFormatBuilder.vue b/frappe/public/js/print_format_builder/PrintFormatBuilder.vue index b0610f30cf..c3452c6a7a 100644 --- a/frappe/public/js/print_format_builder/PrintFormatBuilder.vue +++ b/frappe/public/js/print_format_builder/PrintFormatBuilder.vue @@ -20,7 +20,7 @@ import { getStore } from "./store"; import { computed, ref, onMounted, provide } from "vue"; // props -let props = defineProps(["print_format_name"]); +const props = defineProps(["print_format_name"]); // variables let show_preview = ref(false); diff --git a/frappe/public/js/print_format_builder/PrintFormatSection.vue b/frappe/public/js/print_format_builder/PrintFormatSection.vue index 0c996a1f12..658137e5ce 100644 --- a/frappe/public/js/print_format_builder/PrintFormatSection.vue +++ b/frappe/public/js/print_format_builder/PrintFormatSection.vue @@ -83,7 +83,7 @@ import Field from "./Field.vue"; import { computed } from "vue"; // props -let props = defineProps(["section"]); +const props = defineProps(["section"]); // emits let emit = defineEmits(["add_section_above"]); diff --git a/frappe/public/js/print_format_builder/print_format_builder.bundle.js b/frappe/public/js/print_format_builder/print_format_builder.bundle.js index 62535fd1cf..45a6dd91cc 100644 --- a/frappe/public/js/print_format_builder/print_format_builder.bundle.js +++ b/frappe/public/js/print_format_builder/print_format_builder.bundle.js @@ -1,4 +1,4 @@ -import { createApp } from "vue"; +import { createApp, watch } from "vue"; import PrintFormatBuilderComponent from "./PrintFormatBuilder.vue"; class PrintFormatBuilder { @@ -32,8 +32,8 @@ class PrintFormatBuilder { SetVueGlobals(app); this.$component = app.mount(this.$wrapper.get(0)); - this.$component.$watch( - "$store.dirty", + watch( + () => this.$component.$store.dirty, (dirty) => { if (dirty.value) { this.page.set_indicator("Not Saved", "orange"); @@ -48,9 +48,12 @@ class PrintFormatBuilder { { deep: true } ); - this.$component.$watch("show_preview", (value) => { - $toggle_preview_btn.text(value ? __("Hide Preview") : __("Show Preview")); - }); + watch( + () => this.$component.show_preview, + (value) => { + $toggle_preview_btn.text(value ? __("Hide Preview") : __("Show Preview")); + } + ); } } diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index b355dbdec2..98aeacf55d 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -597,11 +597,25 @@ details > summary:focus { display: none; } -.highlight { +.highlight:not(.frappe-control) { transition: 0.5s ease background-color; box-shadow: var(--highlight-shadow) !important; } +.frappe-control.highlight { + --wrap-padding: calc(-1 * var(--padding-sm)); + &::after { + content: " "; + border-radius: 5px; + box-shadow: var(--highlight-shadow) !important; + top: var(--wrap-padding); + position: absolute; + bottom: var(--wrap-padding); + left: var(--wrap-padding); + right: var(--wrap-padding); + } +} + .dropdown-menu.small { font-size: var(--text-sm); min-width: 140px; diff --git a/frappe/public/scss/desk/timeline.scss b/frappe/public/scss/desk/timeline.scss index a99f2648e8..187fc4bc6c 100644 --- a/frappe/public/scss/desk/timeline.scss +++ b/frappe/public/scss/desk/timeline.scss @@ -95,6 +95,11 @@ $threshold: 34; .timeline-badge { @include timeline-badge(var(--timeline-item-icon-size)) } + .timeline-load-more { + margin-left: calc(var(--timeline-item-left-margin) + var(--padding-sm)); + width: var(--timeline-content-max-width); + text-align: center; + } .timeline-message-box { .content { diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py index af2c871e83..f2651904fb 100644 --- a/frappe/query_builder/utils.py +++ b/frappe/query_builder/utils.py @@ -106,27 +106,28 @@ def patch_query_execute(): def prepare_query(query): import inspect - from frappe.utils.safe_exec import check_safe_sql_query - param_collector = NamedParameterWrapper() query = query.get_sql(param_wrapper=param_collector) - if frappe.flags.in_safe_exec and not check_safe_sql_query(query, throw=False): - callstack = inspect.stack() - if len(callstack) >= 3 and ".py" in callstack[2].filename: - # ignore any query builder methods called from python files - # assumption is that those functions are whitelisted already. + if frappe.flags.in_safe_exec: + from frappe.utils.safe_exec import check_safe_sql_query - # since query objects are patched everywhere any query.run() - # will have callstack like this: - # frame0: this function prepare_query() - # frame1: execute_query() - # frame2: frame that called `query.run()` - # - # if frame2 is server script is set as the filename - # it shouldn't be allowed. - pass - else: - raise frappe.PermissionError("Only SELECT SQL allowed in scripting") + if not check_safe_sql_query(query, throw=False): + callstack = inspect.stack() + if len(callstack) >= 3 and ".py" in callstack[2].filename: + # ignore any query builder methods called from python files + # assumption is that those functions are whitelisted already. + + # since query objects are patched everywhere any query.run() + # will have callstack like this: + # frame0: this function prepare_query() + # frame1: execute_query() + # frame2: frame that called `query.run()` + # + # if frame2 is server script is set as the filename + # it shouldn't be allowed. + pass + else: + raise frappe.PermissionError("Only SELECT SQL allowed in scripting") return query, param_collector.get_parameters() builder_class = frappe.qb._BuilderClasss diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index 0717124ba9..0448d7ea92 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -38,8 +38,8 @@ class RateLimiter: timestamp = int(frappe.utils.now_datetime().timestamp()) self.window_number, self.spent = divmod(timestamp, self.window) - self.key = frappe.cache().make_key(f"rate-limit-counter-{self.window_number}") - self.counter = cint(frappe.cache().get(self.key)) + self.key = frappe.cache.make_key(f"rate-limit-counter-{self.window_number}") + self.counter = cint(frappe.cache.get(self.key)) self.remaining = max(self.limit - self.counter, 0) self.reset = self.window - self.spent @@ -59,7 +59,7 @@ class RateLimiter: self.end = datetime.utcnow() self.duration = int((self.end - self.start).total_seconds() * 1000000) - pipeline = frappe.cache().pipeline() + pipeline = frappe.cache.pipeline() pipeline.incrby(self.key, self.duration) pipeline.expire(self.key, self.window) pipeline.execute() @@ -137,11 +137,11 @@ def rate_limit( cache_key = f"rl:{frappe.form_dict.cmd}:{identity}" - value = frappe.cache().get(cache_key) or 0 + value = frappe.cache.get(cache_key) or 0 if not value: - frappe.cache().setex(cache_key, seconds, 0) + frappe.cache.setex(cache_key, seconds, 0) - value = frappe.cache().incrby(cache_key, 1) + value = frappe.cache.incrby(cache_key, 1) if value > _limit: frappe.throw( _("You hit the rate limit because of too many requests. Please try after sometime.") diff --git a/frappe/realtime.py b/frappe/realtime.py index fdb86546f3..410112b164 100644 --- a/frappe/realtime.py +++ b/frappe/realtime.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # License: MIT. See LICENSE -import os from contextlib import suppress import redis @@ -9,8 +8,6 @@ import redis import frappe from frappe.utils.data import cstr -redis_server = None - def publish_progress(percent, title=None, doctype=None, docname=None, description=None): publish_realtime( @@ -89,11 +86,12 @@ def flush_realtime_log(): for args in frappe.local._realtime_log: frappe.realtime.emit_via_redis(*args) - frappe.local._realtime_log = [] + clear_realtime_log() def clear_realtime_log(): - frappe.local._realtime_log = [] + if hasattr(frappe.local, "_realtime_log"): + del frappe.local._realtime_log def emit_via_redis(event, message, room): @@ -102,22 +100,13 @@ def emit_via_redis(event, message, room): :param event: Event name, like `task_progress` etc. :param message: JSON message object. For async must contain `task_id` :param room: name of the room""" + from frappe.utils.background_jobs import get_redis_conn with suppress(redis.exceptions.ConnectionError): - r = get_redis_server() + r = get_redis_conn() r.publish("events", frappe.as_json({"event": event, "message": message, "room": room})) -def get_redis_server(): - """returns redis_socketio connection.""" - global redis_server - if not redis_server: - from redis import Redis - - redis_server = Redis.from_url(frappe.conf.redis_socketio or "redis://localhost:12311") - return redis_server - - @frappe.whitelist(allow_guest=True) def can_subscribe_doc(doctype: str, docname: str) -> bool: from frappe.exceptions import PermissionError diff --git a/frappe/recorder.py b/frappe/recorder.py index 537d1ee996..1f31181d53 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -65,7 +65,7 @@ def get_current_stack_frames(): def record(force=False): if __debug__: - if frappe.cache().get_value(RECORDER_INTERCEPT_FLAG) or force: + if frappe.cache.get_value(RECORDER_INTERCEPT_FLAG) or force: frappe.local._recorder = Recorder() @@ -109,7 +109,7 @@ class Recorder: "duration": float(f"{(datetime.datetime.now() - self.time).total_seconds() * 1000:0.3f}"), "method": self.method, } - frappe.cache().hset(RECORDER_REQUEST_SPARSE_HASH, self.uuid, request_data) + frappe.cache.hset(RECORDER_REQUEST_SPARSE_HASH, self.uuid, request_data) frappe.publish_realtime( event="recorder-dump-event", message=json.dumps(request_data, default=str), @@ -121,7 +121,7 @@ class Recorder: request_data["calls"] = self.calls request_data["headers"] = self.headers request_data["form_dict"] = self.form_dict - frappe.cache().hset(RECORDER_REQUEST_HASH, self.uuid, request_data) + frappe.cache.hset(RECORDER_REQUEST_HASH, self.uuid, request_data) def mark_duplicates(self): counts = Counter([call["query"] for call in self.calls]) @@ -162,21 +162,21 @@ def administrator_only(function): @do_not_record @administrator_only def status(*args, **kwargs): - return bool(frappe.cache().get_value(RECORDER_INTERCEPT_FLAG)) + return bool(frappe.cache.get_value(RECORDER_INTERCEPT_FLAG)) @frappe.whitelist() @do_not_record @administrator_only def start(*args, **kwargs): - frappe.cache().set_value(RECORDER_INTERCEPT_FLAG, 1) + frappe.cache.set_value(RECORDER_INTERCEPT_FLAG, 1, expires_in_sec=60 * 60) @frappe.whitelist() @do_not_record @administrator_only def stop(*args, **kwargs): - frappe.cache().delete_value(RECORDER_INTERCEPT_FLAG) + frappe.cache.delete_value(RECORDER_INTERCEPT_FLAG) @frappe.whitelist() @@ -184,9 +184,9 @@ def stop(*args, **kwargs): @administrator_only def get(uuid=None, *args, **kwargs): if uuid: - result = frappe.cache().hget(RECORDER_REQUEST_HASH, uuid) + result = frappe.cache.hget(RECORDER_REQUEST_HASH, uuid) else: - result = list(frappe.cache().hgetall(RECORDER_REQUEST_SPARSE_HASH).values()) + result = list(frappe.cache.hgetall(RECORDER_REQUEST_SPARSE_HASH).values()) return result @@ -194,15 +194,15 @@ def get(uuid=None, *args, **kwargs): @do_not_record @administrator_only def export_data(*args, **kwargs): - return list(frappe.cache().hgetall(RECORDER_REQUEST_HASH).values()) + return list(frappe.cache.hgetall(RECORDER_REQUEST_HASH).values()) @frappe.whitelist() @do_not_record @administrator_only def delete(*args, **kwargs): - frappe.cache().delete_value(RECORDER_REQUEST_SPARSE_HASH) - frappe.cache().delete_value(RECORDER_REQUEST_HASH) + frappe.cache.delete_value(RECORDER_REQUEST_SPARSE_HASH) + frappe.cache.delete_value(RECORDER_REQUEST_HASH) def record_queries(func: Callable): diff --git a/frappe/sessions.py b/frappe/sessions.py index 9c739f3a96..64a1a6b663 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -85,8 +85,8 @@ def delete_session(sid=None, user=None, reason="Session Expired"): # we should just ignore it till database is back up again. return - frappe.cache().hdel("session", sid) - frappe.cache().hdel("last_db_session_update", sid) + frappe.cache.hdel("session", sid) + frappe.cache.hdel("last_db_session_update", sid) if sid and not user: table = DocType("Sessions") user_details = ( @@ -139,17 +139,17 @@ def get(): bootinfo = None if not getattr(frappe.conf, "disable_session_cache", None): # check if cache exists - bootinfo = frappe.cache().hget("bootinfo", frappe.session.user) + bootinfo = frappe.cache.hget("bootinfo", frappe.session.user) if bootinfo: bootinfo["from_cache"] = 1 - bootinfo["user"]["recent"] = json.dumps(frappe.cache().hget("user_recent", frappe.session.user)) + bootinfo["user"]["recent"] = json.dumps(frappe.cache.hget("user_recent", frappe.session.user)) if not bootinfo: # if not create it bootinfo = get_bootinfo() - frappe.cache().hset("bootinfo", frappe.session.user, bootinfo) + frappe.cache.hset("bootinfo", frappe.session.user, bootinfo) try: - frappe.cache().ping() + frappe.cache.ping() except redis.exceptions.ConnectionError: message = _("Redis cache server not running. Please contact Administrator / Tech support") if "messages" in bootinfo: @@ -161,7 +161,7 @@ def get(): if frappe.local.request: bootinfo["change_log"] = get_change_log() - bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version") + bootinfo["metadata_version"] = frappe.cache.get_value("metadata_version") if not bootinfo["metadata_version"]: bootinfo["metadata_version"] = frappe.reset_metadata_version() @@ -276,7 +276,7 @@ class Session: ) # also add to memcache - frappe.cache().hset("session", self.data.sid, self.data) + frappe.cache.hset("session", self.data.sid, self.data) def resume(self): """non-login request: load a session""" @@ -320,7 +320,7 @@ class Session: return data def get_session_data_from_cache(self): - data = frappe.cache().hget("session", self.sid) + data = frappe.cache.hget("session", self.sid) if data: data = frappe._dict(data) session_data = data.get("data", {}) @@ -377,7 +377,7 @@ class Session: self.data["data"]["lang"] = str(frappe.lang) # update session in db - last_updated = frappe.cache().hget("last_db_session_update", self.sid) + last_updated = frappe.cache.hget("last_db_session_update", self.sid) time_diff = frappe.utils.time_diff_in_seconds(now, last_updated) if last_updated else None # database persistence is secondary, don't update it too often @@ -397,11 +397,11 @@ class Session: ) frappe.db.commit() - frappe.cache().hset("last_db_session_update", self.sid, now) + frappe.cache.hset("last_db_session_update", self.sid, now) updated_in_db = True - frappe.cache().hset("session", self.sid, self.data) + frappe.cache.hset("session", self.sid, self.data) return updated_in_db diff --git a/frappe/share.py b/frappe/share.py index adae95ea23..6c2fb356a6 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -161,7 +161,7 @@ def get_shared(doctype, user=None, rights=None): or_filters += [["everyone", "=", 1]] shared_docs = frappe.get_all( - "DocShare", fields=["share_name"], filters=filters, or_filters=or_filters + "DocShare", fields=["share_name"], filters=filters, or_filters=or_filters, order_by=None ) return [doc.share_name for doc in shared_docs] diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 658d333c44..a9b013b0e1 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -38,7 +38,7 @@ class EnergyPointLog(Document): "energy_point_alert", message=alert_dict, user=self.user, after_commit=True ) - frappe.cache().hdel("energy_points", self.user) + frappe.cache.hdel("energy_points", self.user) if self.type != "Review" and frappe.get_cached_value( "Notification Settings", self.user, "energy_points_system_notifications" @@ -222,9 +222,6 @@ def add_review_points(user, points): @frappe.whitelist() def get_energy_points(user): - # points = frappe.cache().hget('energy_points', user, - # lambda: get_user_energy_and_review_points(user)) - # TODO: cache properly points = get_user_energy_and_review_points(user) return frappe._dict(points.get(user, {})) diff --git a/frappe/social/doctype/energy_point_log/test_energy_point_log.py b/frappe/social/doctype/energy_point_log/test_energy_point_log.py index c97e2a44e4..2b88d33500 100644 --- a/frappe/social/doctype/energy_point_log/test_energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/test_energy_point_log.py @@ -26,13 +26,13 @@ class TestEnergyPointLog(FrappeTestCase): settings.save() def setUp(self): - frappe.cache().delete_value("energy_point_rule_map") + frappe.cache.delete_value("energy_point_rule_map") def tearDown(self): frappe.set_user("Administrator") frappe.db.delete("Energy Point Log") frappe.db.delete("Energy Point Rule") - frappe.cache().delete_value("energy_point_rule_map") + frappe.cache.delete_value("energy_point_rule_map") def test_user_energy_point(self): frappe.set_user("test@example.com") diff --git a/frappe/templates/includes/head.html b/frappe/templates/includes/head.html index 8ce6a76e50..154aff7048 100644 --- a/frappe/templates/includes/head.html +++ b/frappe/templates/includes/head.html @@ -2,8 +2,9 @@ {{ head_html or "" }} {%- endif %} -{%- if theme.name != 'Standard' -%} +{%- if theme and theme.name != 'Standard' -%} + {%- else -%} {{ include_style('website.bundle.css') }} {%- endif -%} diff --git a/frappe/templates/includes/website_theme/navbar.css b/frappe/templates/includes/website_theme/navbar.css index 39117b6b39..0de9a4827e 100644 --- a/frappe/templates/includes/website_theme/navbar.css +++ b/frappe/templates/includes/website_theme/navbar.css @@ -3,7 +3,7 @@ background-repeat: repeat-x; background-image: none; border: none; - border-bottom: 1px solid {{ get_shade(theme.top_bar_color, 10) }}; + border-bottom: 1px solid {{ theme.top_bar_color }}; padding-top: 15px; padding-bottom: 15px; } @@ -34,7 +34,7 @@ .navbar .nav > .open > a:hover, .navbar .nav > .open > a:focus, .navbar .nav > .open > a:active { - color: {{ get_shade(theme.top_bar_text_color, 15) }}; + color: {{ theme.top_bar_text_color }}; background-color: transparent; -webkit-box-shadow: none; -moz-box-shadow: none; diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 3876d065b1..92b63b15f1 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -47,9 +47,7 @@ def main( force=False, profile=False, junit_xml_output=None, - ui_tests=False, doctype_list_path=None, - skip_test_records=False, failfast=False, case=None, ): @@ -60,11 +58,6 @@ def main( with open(frappe.get_app_path(app, doctype_list_path)) as f: doctype = f.read().strip().splitlines() - if ui_tests: - print( - "Selenium testing has been deprecated\nUse bench --site {site_name} run-ui-tests for Cypress tests" - ) - xmloutput_fh = None if junit_xml_output: xmloutput_fh = open(junit_xml_output, "wb") @@ -115,9 +108,7 @@ def main( case=case, ) else: - ret = run_all_tests( - app, verbose, profile, ui_tests, failfast=failfast, junit_xml_output=junit_xml_output - ) + ret = run_all_tests(app, verbose, profile, failfast=failfast, junit_xml_output=junit_xml_output) if not scheduler_disabled_by_user: frappe.utils.scheduler.enable_scheduler() @@ -159,9 +150,7 @@ class TimeLoggingTestResult(unittest.TextTestResult): super().addSuccess(test) -def run_all_tests( - app=None, verbose=False, profile=False, ui_tests=False, failfast=False, junit_xml_output=False -): +def run_all_tests(app=None, verbose=False, profile=False, failfast=False, junit_xml_output=False): import os apps = [app] if app else frappe.get_installed_apps() @@ -181,7 +170,7 @@ def run_all_tests( for filename in files: if filename.startswith("test_") and filename.endswith(".py") and filename != "test_runner.py": # print filename[:-3] - _add_test(app, path, filename, verbose, test_suite, ui_tests) + _add_test(app, path, filename, verbose, test_suite) if junit_xml_output: runner = unittest_runner(verbosity=1 + cint(verbose), failfast=failfast) @@ -316,7 +305,7 @@ def _run_unittest( return out -def _add_test(app, path, filename, verbose, test_suite=None, ui_tests=False): +def _add_test(app, path, filename, verbose, test_suite=None): import os if os.path.sep.join(["doctype", "doctype", "boilerplate"]) in path: @@ -338,11 +327,6 @@ def _add_test(app, path, filename, verbose, test_suite=None, ui_tests=False): for doctype in module.test_dependencies: make_test_records(doctype, verbose=verbose, commit=True) - is_ui_test = True if hasattr(module, "TestDriver") else False - - if is_ui_test != ui_tests: - return - if not test_suite: test_suite = unittest.TestSuite() diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index 4243bd080a..c299d235dc 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -23,16 +23,23 @@ class TestAssign(FrappeTestCase): if not frappe.db.exists("User", "test@example.com"): frappe.get_doc({"doctype": "User", "email": "test@example.com", "first_name": "Test"}).insert() - added = assign(todo, "test@example.com") + self._test_basic_assign_on_document(todo) + + def _test_basic_assign_on_document(self, doc): + added = assign(doc, "test@example.com") self.assertTrue("test@example.com" in [d.owner for d in added]) - frappe.desk.form.assign_to.remove(todo.doctype, todo.name, "test@example.com") + frappe.desk.form.assign_to.remove(doc.doctype, doc.name, "test@example.com") # assignment is cleared - assignments = frappe.desk.form.assign_to.get(dict(doctype=todo.doctype, name=todo.name)) + assignments = frappe.desk.form.assign_to.get(dict(doctype=doc.doctype, name=doc.name)) self.assertEqual(len(assignments), 0) + def test_assign_single(self): + c = frappe.get_doc("Contact Us Settings") + self._test_basic_assign_on_document(c) + def test_assignment_count(self): frappe.db.delete("ToDo") diff --git a/frappe/tests/test_auth.py b/frappe/tests/test_auth.py index fe8a290a04..d4cfe4451e 100644 --- a/frappe/tests/test_auth.py +++ b/frappe/tests/test_auth.py @@ -50,7 +50,7 @@ class TestAuth(FrappeTestCase): frappe.local.response["http_status_code"] = None def set_system_settings(self, k, v): - frappe.db.set_value("System Settings", "System Settings", k, v) + frappe.db.set_single_value("System Settings", k, v) frappe.clear_cache() frappe.db.commit() diff --git a/frappe/tests/test_boot.py b/frappe/tests/test_boot.py index 232c379e08..8ad2a94aeb 100644 --- a/frappe/tests/test_boot.py +++ b/frappe/tests/test_boot.py @@ -65,7 +65,7 @@ class TestBootData(FrappeTestCase): ).insert(ignore_permissions=True) get_user_pages_or_reports("Report") - allowed_reports = frappe.cache().get_value("has_role:Report", user=frappe.session.user) + allowed_reports = frappe.cache.get_value("has_role:Report", user=frappe.session.user) # Test user must not see admin user's report self.assertNotIn("Test Admin Report", allowed_reports) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index afc24ecf68..f026726cfa 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -192,7 +192,7 @@ class TestDB(FrappeTestCase): # test for inp in test_inputs: fieldname = f"test_{inp['fieldtype'].lower()}" - frappe.db.set_value("Print Settings", "Print Settings", fieldname, inp["value"]) + frappe.db.set_single_value("Print Settings", fieldname, inp["value"]) self.assertEqual(frappe.db.get_single_value("Print Settings", fieldname), inp["value"]) # teardown @@ -201,7 +201,7 @@ class TestDB(FrappeTestCase): def test_log_touched_tables(self): frappe.flags.in_migrate = True frappe.flags.touched_tables = set() - frappe.db.set_value("System Settings", "System Settings", "backup_limit", 5) + frappe.db.set_single_value("System Settings", "backup_limit", 5) self.assertIn("tabSingles", frappe.flags.touched_tables) frappe.flags.touched_tables = set() @@ -699,14 +699,12 @@ class TestDBSetValue(FrappeTestCase): value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions") changed_value = not value - frappe.db.set_value( - "System Settings", "System Settings", "deny_multiple_sessions", changed_value - ) + frappe.db.set_single_value("System Settings", "deny_multiple_sessions", changed_value) current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions") self.assertEqual(current_value, changed_value) changed_value = not current_value - frappe.db.set_value("System Settings", None, "deny_multiple_sessions", changed_value) + frappe.db.set_single_value("System Settings", "deny_multiple_sessions", changed_value) current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions") self.assertEqual(current_value, changed_value) @@ -720,6 +718,11 @@ class TestDBSetValue(FrappeTestCase): updated_value = frappe.db.get_value("ToDo", self.todo1.name, "description") self.assertEqual(updated_value, "test_set_value change 1") + @patch("frappe.db.set_single_value") + def test_set_single_value_with_set_value(self, single_set): + frappe.db.set_value("Contact Us Settings", None, "country", "India") + single_set.assert_called_once() + def test_update_single_row_multiple_columns(self): description, status = "Upated by test_update_single_row_multiple_columns", "Closed" diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py index 4e575528ab..cdd1d08c62 100644 --- a/frappe/tests/test_document.py +++ b/frappe/tests/test_document.py @@ -452,6 +452,13 @@ class TestDocument(FrappeTestCase): frappe.exceptions.InvalidDates, doc.validate_from_to_dates, "start_date", "end_date" ) + def test_db_set_singles(self): + c = frappe.get_doc("Contact Us Settings") + key, val = "email_id", "admin1@example.com" + c.db_set(key, val) + changed_val = frappe.db.get_single_value(c.doctype, key) + self.assertEqual(val, changed_val) + class TestDocumentWebView(FrappeTestCase): def get(self, path, user="Guest"): diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 84785b70f9..4cde7f9ace 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -205,31 +205,6 @@ class TestEmail(FrappeTestCase): self.assertTrue(verify_request()) break - def test_expired(self): - self.test_email_queue() - frappe.db.sql("UPDATE `tabEmail Queue` SET `modified`=(NOW() - INTERVAL '8' day)") - - from frappe.email.queue import set_expiry_for_email_queue - - set_expiry_for_email_queue() - - email_queue = frappe.db.sql( - """select name from `tabEmail Queue` where status='Expired'""", as_dict=1 - ) - self.assertEqual(len(email_queue), 1) - queue_recipients = [ - r.recipient - for r in frappe.db.sql( - """select recipient from `tabEmail Queue Recipient` - where parent = %s""", - email_queue[0].name, - as_dict=1, - ) - ] - self.assertTrue("test@example.com" in queue_recipients) - self.assertTrue("test1@example.com" in queue_recipients) - self.assertEqual(len(queue_recipients), 2) - def test_sender(self): def _patched_assertion(email_account, assertion): with patch.object(QueueBuilder, "get_outgoing_email_account", return_value=email_account): diff --git a/frappe/tests/test_frappe_client.py b/frappe/tests/test_frappe_client.py index 40ba854b82..c8f3bee3d9 100644 --- a/frappe/tests/test_frappe_client.py +++ b/frappe/tests/test_frappe_client.py @@ -107,7 +107,7 @@ class TestFrappeClient(FrappeTestCase): self.assertEqual( server.get_value("Website Settings", "title_prefix").get("title_prefix"), "test-prefix" ) - frappe.db.set_value("Website Settings", None, "title_prefix", "") + frappe.db.set_single_value("Website Settings", "title_prefix", "") def test_update_doc(self): server = FrappeClient(get_url(), "Administrator", self.PASSWORD, verify=False) diff --git a/frappe/tests/test_hooks.py b/frappe/tests/test_hooks.py index 3ecc2c2b89..41a734e7ad 100644 --- a/frappe/tests/test_hooks.py +++ b/frappe/tests/test_hooks.py @@ -26,7 +26,7 @@ class TestHooks(FrappeTestCase): hooks.override_doctype_class = {"ToDo": ["frappe.tests.test_hooks.CustomToDo"]} # Clear cache - frappe.cache().delete_value("app_hooks") + frappe.cache.delete_value("app_hooks") clear_controller_cache("ToDo") todo = frappe.get_doc(doctype="ToDo", description="asdf") @@ -45,7 +45,7 @@ class TestHooks(FrappeTestCase): hooks.has_permission["Address"] = address_has_permission_hook # Clear cache - frappe.cache().delete_value("app_hooks") + frappe.cache.delete_value("app_hooks") # Init User and Address username = "test@example.com" diff --git a/frappe/tests/test_monitor.py b/frappe/tests/test_monitor.py index e59ebcde31..74c8c07b9f 100644 --- a/frappe/tests/test_monitor.py +++ b/frappe/tests/test_monitor.py @@ -12,7 +12,7 @@ from frappe.utils.response import build_response class TestMonitor(FrappeTestCase): def setUp(self): frappe.conf.monitor = 1 - frappe.cache().delete_value(MONITOR_REDIS_KEY) + frappe.cache.delete_value(MONITOR_REDIS_KEY) def test_enable_monitor(self): set_request(method="GET", path="/api/method/frappe.ping") @@ -21,7 +21,7 @@ class TestMonitor(FrappeTestCase): frappe.monitor.start() frappe.monitor.stop(response) - logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1) + logs = frappe.cache.lrange(MONITOR_REDIS_KEY, 0, -1) self.assertEqual(len(logs), 1) log = frappe.parse_json(logs[0].decode()) @@ -39,7 +39,7 @@ class TestMonitor(FrappeTestCase): frappe.monitor.start() frappe.monitor.stop(response=None) - logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1) + logs = frappe.cache.lrange(MONITOR_REDIS_KEY, 0, -1) self.assertEqual(len(logs), 1) log = frappe.parse_json(logs[0].decode()) @@ -52,7 +52,7 @@ class TestMonitor(FrappeTestCase): frappe.local.site, "frappe.ping", None, None, {}, is_async=False ) - logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1) + logs = frappe.cache.lrange(MONITOR_REDIS_KEY, 0, -1) self.assertEqual(len(logs), 1) log = frappe.parse_json(logs[0].decode()) self.assertEqual(log.transaction_type, "job") @@ -79,4 +79,4 @@ class TestMonitor(FrappeTestCase): def tearDown(self): frappe.conf.monitor = 0 - frappe.cache().delete_value(MONITOR_REDIS_KEY) + frappe.cache.delete_value(MONITOR_REDIS_KEY) diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py index 8de652b888..fb064eb82d 100644 --- a/frappe/tests/test_oauth20.py +++ b/frappe/tests/test_oauth20.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING from urllib.parse import parse_qs, urljoin, urlparse -import jwt import requests from werkzeug.test import TestResponse @@ -107,7 +106,7 @@ class TestOAuth20(FrappeRequestTestCase): update_client_for_auth_code_grant(self.client_id) # Go to Authorize url - self.TEST_CLIENT.set_cookie(self.site, key="sid", value=self.sid) + self.TEST_CLIENT.set_cookie(key="sid", value=self.sid) resp = self.get( "/api/method/frappe.integrations.oauth2.authorize", { @@ -154,7 +153,7 @@ class TestOAuth20(FrappeRequestTestCase): update_client_for_auth_code_grant(self.client_id) # Go to Authorize url - self.TEST_CLIENT.set_cookie(self.site, key="sid", value=self.sid) + self.TEST_CLIENT.set_cookie(key="sid", value=self.sid) resp = self.get( "/api/method/frappe.integrations.oauth2.authorize", { @@ -203,7 +202,7 @@ class TestOAuth20(FrappeRequestTestCase): frappe.db.commit() # Go to Authorize url - self.TEST_CLIENT.set_cookie(self.site, key="sid", value=self.sid) + self.TEST_CLIENT.set_cookie(key="sid", value=self.sid) resp = self.get( "/api/method/frappe.integrations.oauth2.authorize", { @@ -321,7 +320,7 @@ class TestOAuth20(FrappeRequestTestCase): nonce = frappe.generate_hash() # Go to Authorize url - self.TEST_CLIENT.set_cookie(self.site, key="sid", value=self.sid) + self.TEST_CLIENT.set_cookie(key="sid", value=self.sid) resp = self.get( "/api/method/frappe.integrations.oauth2.authorize", { @@ -362,6 +361,8 @@ class TestOAuth20(FrappeRequestTestCase): self.assertTrue(payload.get("nonce") == nonce) def decode_id_token(self, id_token): + import jwt + return jwt.decode( id_token, audience=self.client_id, diff --git a/frappe/tests/test_pdf.py b/frappe/tests/test_pdf.py index 4a8fef253c..84004dc1f1 100644 --- a/frappe/tests/test_pdf.py +++ b/frappe/tests/test_pdf.py @@ -2,7 +2,7 @@ # License: MIT. See LICENSE import io -from PyPDF2 import PdfReader +from pypdf import PdfReader import frappe import frappe.utils.pdf as pdfgen @@ -43,7 +43,7 @@ class TestPdf(FrappeTestCase): password = "qwe" pdf = pdfgen.get_pdf(self.html, options={"password": password}) reader = PdfReader(io.BytesIO(pdf)) - self.assertTrue(reader.isEncrypted) + self.assertTrue(reader.is_encrypted) self.assertTrue(reader.decrypt(password)) def test_pdf_generation_as_a_user(self): diff --git a/frappe/tests/test_rate_limiter.py b/frappe/tests/test_rate_limiter.py index c8485d6c69..292a688484 100644 --- a/frappe/tests/test_rate_limiter.py +++ b/frappe/tests/test_rate_limiter.py @@ -20,7 +20,7 @@ class TestRateLimiter(FrappeTestCase): self.assertTrue(hasattr(frappe.local, "rate_limiter")) self.assertIsInstance(frappe.local.rate_limiter, RateLimiter) - frappe.cache().delete(frappe.local.rate_limiter.key) + frappe.cache.delete(frappe.local.rate_limiter.key) delattr(frappe.local, "rate_limiter") def test_apply_without_limit(self): @@ -53,8 +53,8 @@ class TestRateLimiter(FrappeTestCase): self.assertEqual(int(headers["X-RateLimit-Limit"]), 10000) self.assertEqual(int(headers["X-RateLimit-Remaining"]), 0) - frappe.cache().delete(limiter.key) - frappe.cache().delete(frappe.local.rate_limiter.key) + frappe.cache.delete(limiter.key) + frappe.cache.delete(frappe.local.rate_limiter.key) delattr(frappe.local, "rate_limiter") def test_respond_under_limit(self): @@ -64,7 +64,7 @@ class TestRateLimiter(FrappeTestCase): response = frappe.rate_limiter.respond() self.assertEqual(response, None) - frappe.cache().delete(frappe.local.rate_limiter.key) + frappe.cache.delete(frappe.local.rate_limiter.key) delattr(frappe.local, "rate_limiter") def test_headers_under_limit(self): @@ -79,7 +79,7 @@ class TestRateLimiter(FrappeTestCase): self.assertEqual(int(headers["X-RateLimit-Limit"]), 10000) self.assertEqual(int(headers["X-RateLimit-Remaining"]), 10000) - frappe.cache().delete(frappe.local.rate_limiter.key) + frappe.cache.delete(frappe.local.rate_limiter.key) delattr(frappe.local, "rate_limiter") def test_reject_over_limit(self): @@ -90,7 +90,7 @@ class TestRateLimiter(FrappeTestCase): limiter = RateLimiter(0.01, 86400) self.assertRaises(frappe.TooManyRequestsError, limiter.apply) - frappe.cache().delete(limiter.key) + frappe.cache.delete(limiter.key) def test_do_not_reject_under_limit(self): limiter = RateLimiter(0.01, 86400) @@ -100,13 +100,13 @@ class TestRateLimiter(FrappeTestCase): limiter = RateLimiter(0.02, 86400) self.assertEqual(limiter.apply(), None) - frappe.cache().delete(limiter.key) + frappe.cache.delete(limiter.key) def test_update_method(self): limiter = RateLimiter(0.01, 86400) time.sleep(0.01) limiter.update() - self.assertEqual(limiter.duration, cint(frappe.cache().get(limiter.key))) + self.assertEqual(limiter.duration, cint(frappe.cache.get(limiter.key))) - frappe.cache().delete(limiter.key) + frappe.cache.delete(limiter.key) diff --git a/frappe/tests/test_rename_doc.py b/frappe/tests/test_rename_doc.py index 3f67bc4a1f..c831e832fb 100644 --- a/frappe/tests/test_rename_doc.py +++ b/frappe/tests/test_rename_doc.py @@ -249,18 +249,6 @@ class TestRenameDoc(FrappeTestCase): doctype=self.test_doctype, ) - def test_deprecated_utils(self): - from frappe.model.rename_doc import get_fetch_fields, update_linked_doctypes - - stdout = StringIO() - - with redirect_stdout(stdout), patch_db(["set_value"]): - get_fetch_fields("User", "ToDo", ["Activity Log"]) - self.assertIn("Function frappe.model.rename_doc.get_fetch_fields", stdout.getvalue()) - - update_linked_doctypes("User", "ToDo", "str", "str") - self.assertIn("Function frappe.model.rename_doc.update_linked_doctypes", stdout.getvalue()) - def test_doc_rename_method(self): name = choice(self.available_documents) new_name = f"{name}-{frappe.generate_hash(length=4)}" diff --git a/frappe/tests/test_scheduler.py b/frappe/tests/test_scheduler.py index 4e4b67c603..f66b7da5ed 100644 --- a/frappe/tests/test_scheduler.py +++ b/frappe/tests/test_scheduler.py @@ -42,7 +42,7 @@ class TestScheduler(TestCase): enqueued_jobs = enqueue_events(site=frappe.local.site) - self.assertIn("frappe.email.queue.set_expiry_for_email_queue", enqueued_jobs) + self.assertIn("frappe.desk.notifications.clear_notifications", enqueued_jobs) self.assertIn("frappe.utils.change_log.check_for_update", enqueued_jobs) self.assertIn( "frappe.email.doctype.auto_email_report.auto_email_report.send_monthly", diff --git a/frappe/tests/test_twofactor.py b/frappe/tests/test_twofactor.py index 053b755b65..96c0a80ead 100644 --- a/frappe/tests/test_twofactor.py +++ b/frappe/tests/test_twofactor.py @@ -61,7 +61,7 @@ class TestTwoFactor(FrappeTestCase): self.assertTrue(verification_obj) self.assertTrue(tmp_id) for k in ["_usr", "_pwd", "_otp_secret"]: - self.assertTrue(frappe.cache().get(f"{tmp_id}{k}"), f"{k} not available") + self.assertTrue(frappe.cache.get(f"{tmp_id}{k}"), f"{k} not available") def test_two_factor_is_enabled(self): """ diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 2e83f8b8b6..59e942ef42 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -20,7 +20,7 @@ from PIL import Image import frappe from frappe.installer import parse_app_name from frappe.model.document import Document -from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.tests.utils import FrappeTestCase, MockedRequestTestCase, change_settings from frappe.utils import ( ceil, dict_to_str, @@ -815,8 +815,14 @@ class TestLinkTitle(FrappeTestCase): prop_setter.delete() -class TestAppParser(FrappeTestCase): +class TestAppParser(MockedRequestTestCase): def test_app_name_parser(self): + self.responses.add( + "HEAD", + "https://api.github.com/repos/frappe/healthcare", + status=200, + json={}, + ) bench_path = get_bench_path() frappe_app = os.path.join(bench_path, "apps", "frappe") self.assertEqual("frappe", parse_app_name(frappe_app)) @@ -1096,6 +1102,8 @@ class TestRounding(FrappeTestCase): rounding_method = "Banker's Rounding" self.assertEqual(rounded(0, 0, rounding_method=rounding_method), 0) + self.assertEqual(rounded(5.551115123125783e-17, 2, rounding_method=rounding_method), 0.0) + self.assertEqual(flt("0.5", 0, rounding_method=rounding_method), 0) self.assertEqual(flt("0.3", rounding_method=rounding_method), 0.3) diff --git a/frappe/tests/test_webform.py b/frappe/tests/test_webform.py index cde963a915..d8b9254a09 100644 --- a/frappe/tests/test_webform.py +++ b/frappe/tests/test_webform.py @@ -80,4 +80,4 @@ def set_webform_hook(key, value): delattr(hooks, hook) setattr(hooks, key, value) - frappe.cache().delete_key("app_hooks") + frappe.cache.delete_key("app_hooks") diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 01f6e4f7cc..6c319fff0a 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -43,20 +43,20 @@ class TestWebsite(FrappeTestCase): frappe.db.set_value("Role", "home-page-test", "home_page", "") # home page via portal settings - frappe.db.set_value("Portal Settings", None, "default_portal_home", "test-portal-home") + frappe.db.set_single_value("Portal Settings", "default_portal_home", "test-portal-home") frappe.set_user("test-user-for-home-page@example.com") - frappe.cache().hdel("home_page", frappe.session.user) + frappe.cache.hdel("home_page", frappe.session.user) self.assertEqual(get_home_page(), "test-portal-home") - frappe.db.set_value("Portal Settings", None, "default_portal_home", "") + frappe.db.set_single_value("Portal Settings", "default_portal_home", "") clear_website_cache() # home page via website settings - frappe.db.set_value("Website Settings", None, "home_page", "contact") + frappe.db.set_single_value("Website Settings", "home_page", "contact") self.assertEqual(get_home_page(), "contact") - frappe.db.set_value("Website Settings", None, "home_page", None) + frappe.db.set_single_value("Website Settings", "home_page", None) clear_website_cache() # fallback homepage @@ -210,7 +210,7 @@ class TestWebsite(FrappeTestCase): self.assertEqual(response.headers.get("Location"), "/courses/data") delattr(frappe.hooks, "website_redirects") - frappe.cache().delete_key("app_hooks") + frappe.cache.delete_key("app_hooks") def test_custom_page_renderer(self): from frappe import get_hooks diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index 07003d3b8c..6e6c72052a 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -93,6 +93,19 @@ class FrappeTestCase(unittest.TestCase): frappe.db.sql = orig_sql +class MockedRequestTestCase(FrappeTestCase): + def setUp(self): + import responses + + self.responses = responses.RequestsMock() + self.responses.start() + + self.addCleanup(self.responses.stop) + self.addCleanup(self.responses.reset) + + return super().setUp() + + def _commit_watcher(): import traceback diff --git a/frappe/translate.py b/frappe/translate.py index 041e983432..806ec8e66f 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -15,10 +15,8 @@ import operator import os import re from contextlib import contextmanager -from csv import reader +from csv import reader, writer -from babel.messages.extract import extract_python -from babel.messages.jslexer import Token, tokenize, unquote_string from pypika.terms import PseudoColumn import frappe @@ -125,7 +123,7 @@ def get_parent_language(lang: str) -> str: def get_user_lang(user: str = None) -> str: """Set frappe.local.lang from user preferences on session beginning or resumption""" user = user or frappe.session.user - lang = frappe.cache().hget("lang", user) + lang = frappe.cache.hget("lang", user) if not lang: # User.language => Session Defaults => frappe.local.lang => 'en' @@ -136,7 +134,7 @@ def get_user_lang(user: str = None) -> str: or "en" ) - frappe.cache().hset("lang", user, lang) + frappe.cache.hset("lang", user, lang) return lang @@ -168,9 +166,8 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: :param name: name of the document for which assets are to be returned. """ fortype = fortype.lower() - cache = frappe.cache() asset_key = fortype + ":" + (name or "-") - translation_assets = cache.hget("translation_assets", frappe.local.lang) or {} + translation_assets = frappe.cache.hget("translation_assets", frappe.local.lang) or {} if asset_key not in translation_assets: messages = [] @@ -210,7 +207,7 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: # remove untranslated message_dict = {k: v for k, v in message_dict.items() if k != v} translation_assets[asset_key] = message_dict - cache.hset("translation_assets", frappe.local.lang, translation_assets) + frappe.cache.hset("translation_assets", frappe.local.lang, translation_assets) translation_map: dict = translation_assets[asset_key] @@ -292,7 +289,7 @@ def get_all_translations(lang: str) -> dict[str, str]: return all_translations try: - return frappe.cache().hget(MERGED_TRANSLATION_KEY, lang, generator=_merge_translations) + return frappe.cache.hget(MERGED_TRANSLATION_KEY, lang, generator=_merge_translations) except Exception: # People mistakenly call translation function on global variables # where locals are not initalized, translations dont make much sense there @@ -361,19 +358,18 @@ def get_user_translations(lang): user_translations[key] = value return user_translations - return frappe.cache().hget(USER_TRANSLATION_KEY, lang, generator=_read_from_db) + return frappe.cache.hget(USER_TRANSLATION_KEY, lang, generator=_read_from_db) def clear_cache(): """Clear all translation assets from :meth:`frappe.cache`""" - cache = frappe.cache() - cache.delete_key("langinfo") + frappe.cache.delete_key("langinfo") # clear translations saved in boot cache - cache.delete_key("bootinfo") - cache.delete_key("translation_assets") - cache.delete_key(USER_TRANSLATION_KEY) - cache.delete_key(MERGED_TRANSLATION_KEY) + frappe.cache.delete_key("bootinfo") + frappe.cache.delete_key("translation_assets") + frappe.cache.delete_key(USER_TRANSLATION_KEY) + frappe.cache.delete_key(MERGED_TRANSLATION_KEY) def get_messages_for_app(app, deduplicate=True): @@ -739,6 +735,7 @@ def get_messages_from_file(path: str) -> list[tuple[str, str, str | None, int]]: def extract_messages_from_python_code(code: str) -> list[tuple[int, str, str | None]]: """Extracts translatable strings from Python code using babel.""" + from babel.messages.extract import extract_python messages = [] @@ -811,6 +808,8 @@ def extract_javascript(code, keywords=("__",), options=None): * `template_string` -- set to false to disable ES6 template string support. """ + from babel.messages.jslexer import Token, tokenize, unquote_string + if options is None: options = {} @@ -999,7 +998,6 @@ def write_csv_file(path, app_messages, lang_dict): :param lang_dict: Full translated dict. """ app_messages.sort(key=lambda x: x[1]) - from csv import writer with open(path, "w", newline="") as msgfile: w = writer(msgfile, lineterminator="\n") @@ -1120,6 +1118,50 @@ def import_translations(lang, path): write_translations_file(app, lang, full_dict) +def migrate_translations(source_app, target_app): + """Migrate target-app-specific translations from source-app to target-app""" + clear_cache() + strings_in_source_app = [m[1] for m in frappe.translate.get_messages_for_app(source_app)] + strings_in_target_app = [m[1] for m in frappe.translate.get_messages_for_app(target_app)] + + strings_in_target_app_but_not_in_source_app = list( + set(strings_in_target_app) - set(strings_in_source_app) + ) + + languages = frappe.translate.get_all_languages() + + source_app_translations_dir = os.path.join(frappe.get_pymodule_path(source_app), "translations") + target_app_translations_dir = os.path.join(frappe.get_pymodule_path(target_app), "translations") + + if not os.path.exists(target_app_translations_dir): + os.makedirs(target_app_translations_dir) + + for lang in languages: + source_csv = os.path.join(source_app_translations_dir, lang + ".csv") + + if not os.path.exists(source_csv): + continue + + target_csv = os.path.join(target_app_translations_dir, lang + ".csv") + temp_csv = os.path.join(source_app_translations_dir, "_temp.csv") + + with open(source_csv) as s, open(target_csv, "a+") as t, open(temp_csv, "a+") as temp: + source_reader = reader(s, lineterminator="\n") + target_writer = writer(t, lineterminator="\n") + temp_writer = writer(temp, lineterminator="\n") + + for row in source_reader: + if row[0] in strings_in_target_app_but_not_in_source_app: + target_writer.writerow(row) + else: + temp_writer.writerow(row) + + if not os.path.getsize(target_csv): + os.remove(target_csv) + os.remove(source_csv) + os.rename(temp_csv, source_csv) + + def rebuild_all_translation_files(): """Rebuild all translation files: `[app]/translations/[lang].csv`.""" for lang in get_all_languages(): @@ -1273,9 +1315,9 @@ def get_all_languages(with_language_name: bool = False) -> list: frappe.connect() if with_language_name: - return frappe.cache().get_value("languages_with_name", get_all_language_with_name) + return frappe.cache.get_value("languages_with_name", get_all_language_with_name) else: - return frappe.cache().get_value("languages", get_language_codes) + return frappe.cache.get_value("languages", get_language_codes) @frappe.whitelist(allow_guest=True) diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index d1f503be7c..f8bbd8e22e 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -1208,6 +1208,7 @@ Google Calendar ID,Google Kalender-ID, Google Font,Google Font, Google Services,Google-Dienste, Grant Type,Grant Typ, +Group By {0},Gruppieren nach {0}, Group Name,Gruppenname, Group name cannot be empty.,Der Gruppenname darf nicht leer sein., Groups of DocTypes,Gruppen von DocTypes, @@ -3238,6 +3239,7 @@ Change User,Benutzer wechseln, Check the Error Log for more information: {0},Überprüfen Sie das Fehlerprotokoll auf weitere Informationen: {0}, Clear Cache and Reload,Cache leeren und neu laden, Clear Filters,Filter löschen, +Clear all filters,Alle Filter löschen, Click on Authorize Google Drive Access to authorize Google Drive Access.,"Klicken Sie auf Google Drive Access autorisieren, um Google Drive Access zu autorisieren.", Click on a file to select it.,"Klicken Sie auf eine Datei, um sie auszuwählen.", Click on the link below to approve the request,"Klicken Sie auf den folgenden Link, um die Anfrage zu genehmigen", @@ -3559,7 +3561,7 @@ Select Field...,Feld auswählen ..., Select Filters,Wählen Sie Filter, Select Google Calendar to which event should be synced.,"Wählen Sie Google Kalender aus, mit dem das Ereignis synchronisiert werden soll.", Select Google Contacts to which contact should be synced.,"Wählen Sie Google-Kontakte aus, mit denen der Kontakt synchronisiert werden soll.", -Select Group By...,Wählen Sie Gruppieren nach ..., +Select Group By...,Gruppieren nach ..., Select Mandatory,Verpflichtende auswählen, Select atleast 2 actions,Wählen Sie mindestens 2 Aktionen aus, Select list item,Listenelement auswählen, diff --git a/frappe/twofactor.py b/frappe/twofactor.py index c4292b0533..bb37c2a593 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -5,7 +5,6 @@ from base64 import b32encode, b64encode from io import BytesIO import pyotp -from pyqrcode import create as qrcreate import frappe import frappe.defaults @@ -74,8 +73,8 @@ def get_cached_user_pass(): user = pwd = None tmp_id = frappe.form_dict.get("tmp_id") if tmp_id: - user = frappe.safe_decode(frappe.cache().get(tmp_id + "_usr")) - pwd = frappe.safe_decode(frappe.cache().get(tmp_id + "_pwd")) + user = frappe.safe_decode(frappe.cache.get(tmp_id + "_usr")) + pwd = frappe.safe_decode(frappe.cache.get(tmp_id + "_pwd")) return (user, pwd) @@ -101,13 +100,13 @@ def cache_2fa_data(user, token, otp_secret, tmp_id): # set increased expiry time for SMS and Email if verification_method in ["SMS", "Email"]: expiry_time = frappe.flags.token_expiry or 300 - frappe.cache().set(tmp_id + "_token", token) - frappe.cache().expire(tmp_id + "_token", expiry_time) + frappe.cache.set(tmp_id + "_token", token) + frappe.cache.expire(tmp_id + "_token", expiry_time) else: expiry_time = frappe.flags.otp_expiry or 180 for k, v in {"_usr": user, "_pwd": pwd, "_otp_secret": otp_secret}.items(): - frappe.cache().set(f"{tmp_id}{k}", v) - frappe.cache().expire(f"{tmp_id}{k}", expiry_time) + frappe.cache.set(f"{tmp_id}{k}", v) + frappe.cache.expire(f"{tmp_id}{k}", expiry_time) def two_factor_is_enabled_for_(user): @@ -160,8 +159,8 @@ def confirm_otp_token(login_manager, otp=None, tmp_id=None): return True if not tmp_id: tmp_id = frappe.form_dict.get("tmp_id") - hotp_token = frappe.cache().get(tmp_id + "_token") - otp_secret = frappe.cache().get(tmp_id + "_otp_secret") + hotp_token = frappe.cache.get(tmp_id + "_token") + otp_secret = frappe.cache.get(tmp_id + "_otp_secret") if not otp_secret: raise ExpiredLoginException(_("Login session expired, refresh page to retry")) @@ -170,7 +169,7 @@ def confirm_otp_token(login_manager, otp=None, tmp_id=None): hotp = pyotp.HOTP(otp_secret) if hotp_token: if hotp.verify(otp, int(hotp_token)): - frappe.cache().delete(tmp_id + "_token") + frappe.cache.delete(tmp_id + "_token") tracker.add_success_attempt() return True else: @@ -308,8 +307,8 @@ def get_link_for_qrcode(user, totp_uri): key_user = f"{key}_user" key_uri = f"{key}_uri" lifespan = int(frappe.db.get_single_value("System Settings", "lifespan_qrcode_image")) or 240 - frappe.cache().set_value(key_uri, totp_uri, expires_in_sec=lifespan) - frappe.cache().set_value(key_user, user, expires_in_sec=lifespan) + frappe.cache.set_value(key_uri, totp_uri, expires_in_sec=lifespan) + frappe.cache.set_value(key_user, user, expires_in_sec=lifespan) return get_url(f"/qrcode?k={key}") @@ -387,6 +386,8 @@ def send_token_via_email(user, token, otp_secret, otp_issuer, subject=None, mess def get_qr_svg_code(totp_uri): """Get SVG code to display Qrcode for OTP.""" + from pyqrcode import create as qrcreate + url = qrcreate(totp_uri) svg = "" stream = BytesIO() diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index b7dc565555..6ffa011ed9 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -20,7 +20,6 @@ from collections.abc import ( ) from email.header import decode_header, make_header from email.utils import formataddr, parseaddr -from gzip import GzipFile from typing import Any, Callable, Literal from urllib.parse import quote, urlparse @@ -873,6 +872,8 @@ def gzip_compress(data, compresslevel=9): """Compress data in one shot and return the compressed string. Optional argument is the compression level, in range of 0-9. """ + from gzip import GzipFile + buf = io.BytesIO() with GzipFile(fileobj=buf, mode="wb", compresslevel=compresslevel) as f: f.write(data) @@ -883,6 +884,8 @@ def gzip_decompress(data): """Decompress a gzip compressed string in one shot. Return the decompressed string. """ + from gzip import GzipFile + with GzipFile(fileobj=io.BytesIO(data)) as f: return f.read() @@ -970,7 +973,7 @@ def get_assets_json(): if not hasattr(frappe.local, "assets_json"): if not frappe.conf.developer_mode: - frappe.local.assets_json = frappe.cache().get_value( + frappe.local.assets_json = frappe.cache.get_value( "assets_json", _get_assets, shared=True, diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 6a203f8dc7..6b0249e720 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -1,3 +1,4 @@ +import gc import os import socket import time @@ -28,6 +29,9 @@ RQ_JOB_FAILURE_TTL = 7 * 24 * 60 * 60 # 7 days instead of 1 year (default) RQ_RESULTS_TTL = 10 * 60 +_redis_queue_conn = None + + @lru_cache def get_queues_timeout(): common_site_config = frappe.get_conf() @@ -47,9 +51,6 @@ def get_queues_timeout(): } -redis_connection = None - - def enqueue( method: str | Callable, queue: str = "default", @@ -234,6 +235,10 @@ def start_worker( """Wrapper to start rq worker. Connects to redis and monitors these queues.""" DEQUEUE_STRATEGIES = {"round_robin": RoundRobinWorker, "random": RandomWorker} + if frappe._tune_gc: + gc.collect() + gc.freeze() + with frappe.init_site(): # empty init is required to get redis_queue from common_site_config.json redis_connection = get_redis_conn(username=rq_username, password=rq_password) @@ -360,7 +365,7 @@ def get_redis_conn(username=None, password=None): elif not frappe.local.conf.redis_queue: raise Exception("redis_queue missing in common_site_config.json") - global redis_connection + global _redis_queue_conn cred = frappe._dict() if frappe.conf.get("use_rq_auth"): @@ -374,8 +379,14 @@ def get_redis_conn(username=None, password=None): elif os.environ.get("RQ_ADMIN_PASWORD"): cred["username"] = "default" cred["password"] = os.environ.get("RQ_ADMIN_PASWORD") + try: - redis_connection = RedisQueue.get_connection(**cred) + if not cred: + if not _redis_queue_conn: + _redis_queue_conn = RedisQueue.get_connection() + return _redis_queue_conn + else: + return RedisQueue.get_connection(**cred) except (redis.exceptions.AuthenticationError, redis.exceptions.ResponseError): log( f'Wrong credentials used for {cred.username or "default user"}. ' @@ -387,8 +398,6 @@ def get_redis_conn(username=None, password=None): log(f"Please make sure that Redis Queue runs @ {frappe.get_conf().redis_queue}", colour="red") raise - return redis_connection - def get_queues() -> list[Queue]: """Get all the queues linked to the current bench.""" diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index 0d786972fb..0e646f9992 100644 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -298,14 +298,13 @@ __version__ = '0.0.1' """ -hooks_template = """from . import __version__ as app_version - -app_name = "{app_name}" +hooks_template = """app_name = "{app_name}" app_title = "{app_title}" app_publisher = "{app_publisher}" app_description = "{app_description}" app_email = "{app_email}" app_license = "{app_license}" +# required_apps = [] # Includes in # ------------------ @@ -372,6 +371,22 @@ app_license = "{app_license}" # before_uninstall = "{app_name}.uninstall.before_uninstall" # after_uninstall = "{app_name}.uninstall.after_uninstall" +# Integration Setup +# ------------------ +# To set up dependencies/integrations with other apps +# Name of the app being installed is passed as an argument + +# before_app_install = "{app_name}.utils.before_app_install" +# after_app_install = "{app_name}.utils.after_app_install" + +# Integration Cleanup +# ------------------- +# To clean up dependencies/integrations with other apps +# Name of the app being uninstalled is passed as an argument + +# before_app_uninstall = "{app_name}.utils.before_app_uninstall" +# after_app_uninstall = "{app_name}.utils.after_app_uninstall" + # Desk Notifications # ------------------ # See frappe.core.notifications.get_notification_config @@ -558,10 +573,6 @@ jobs: image: redis:alpine ports: - 11000:6379 - redis-socketio: - image: redis:alpine - ports: - - 12000:6379 mariadb: image: mariadb:10.6 env: diff --git a/frappe/utils/caching.py b/frappe/utils/caching.py index 370227ea72..fbfbddbd88 100644 --- a/frappe/utils/caching.py +++ b/frappe/utils/caching.py @@ -143,7 +143,7 @@ def redis_cache(ttl: int | None = 3600, user: str | bool | None = None) -> Calla func_key = f"{func.__module__}.{func.__qualname__}" def clear_cache(): - frappe.cache().delete_keys(func_key) + frappe.cache.delete_keys(func_key) func.clear_cache = clear_cache func.ttl = ttl if not callable(ttl) else 3600 @@ -151,12 +151,12 @@ def redis_cache(ttl: int | None = 3600, user: str | bool | None = None) -> Calla @wraps(func) def redis_cache_wrapper(*args, **kwargs): func_call_key = func_key + "::" + str(__generate_request_cache_key(args, kwargs)) - if frappe.cache().exists(func_call_key): - return frappe.cache().get_value(func_call_key, user=user) + if frappe.cache.exists(func_call_key): + return frappe.cache.get_value(func_call_key, user=user) else: val = func(*args, **kwargs) ttl = getattr(func, "ttl", 3600) - frappe.cache().set_value(func_call_key, val, expires_in_sec=ttl, user=user) + frappe.cache.set_value(func_call_key, val, expires_in_sec=ttl, user=user) return val return redis_cache_wrapper diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py index a4b56686c2..3da9c5d757 100644 --- a/frappe/utils/change_log.py +++ b/frappe/utils/change_log.py @@ -5,7 +5,6 @@ import json import os import subprocess # nosec -import requests from semantic_version import Version import frappe @@ -231,6 +230,7 @@ def check_release_on_github(app: str): organization name, if the application exists, otherwise None. """ + import requests from giturlparse import parse from giturlparse.parser import ParserError @@ -267,19 +267,17 @@ def check_release_on_github(app: str): def add_message_to_redis(update_json): # "update-message" will store the update message string # "update-user-set" will be a set of users - cache = frappe.cache() - cache.set_value("update-info", json.dumps(update_json)) + frappe.cache.set_value("update-info", json.dumps(update_json)) user_list = [x.name for x in frappe.get_all("User", filters={"enabled": True})] system_managers = [user for user in user_list if "System Manager" in frappe.get_roles(user)] - cache.sadd("update-user-set", *system_managers) + frappe.cache.sadd("update-user-set", *system_managers) @frappe.whitelist() def show_update_popup(): - cache = frappe.cache() user = frappe.session.user - update_info = cache.get_value("update-info") + update_info = frappe.cache.get_value("update-info") if not update_info: return @@ -287,7 +285,7 @@ def show_update_popup(): # Check if user is int the set of users to send update message to update_message = "" - if cache.sismember("update-user-set", user): + if frappe.cache.sismember("update-user-set", user): for update_type in updates: release_links = "" for app in updates[update_type]: @@ -308,4 +306,4 @@ def show_update_popup(): if update_message: frappe.msgprint(update_message, title=_("New updates are available"), indicator="green") - cache.srem("update-user-set", user) + frappe.cache.srem("update-user-set", user) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py index 6660e6ce19..020bc8b97f 100644 --- a/frappe/utils/connections.py +++ b/frappe/utils/connections.py @@ -3,7 +3,7 @@ from urllib.parse import urlparse from frappe import get_conf -REDIS_KEYS = ("redis_cache", "redis_queue", "redis_socketio") +REDIS_KEYS = ("redis_cache", "redis_queue") def is_open(ip, port, timeout=10): diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 980107ce2b..9066f3172c 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -25,7 +25,7 @@ def cache_source(function): if int(kwargs.get("refresh") or 0): results = generate_and_cache_results(kwargs, function, cache_key, chart) else: - cached_results = frappe.cache().get_value(cache_key) + cached_results = frappe.cache.get_value(cache_key) if cached_results: results = frappe.parse_json(frappe.safe_decode(cached_results)) else: diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 3fe854bbf6..c42a741017 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -15,10 +15,12 @@ from typing import Any, Literal, Optional, TypeVar, Union from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlparse, urlunparse from click import secho +from dateutil import parser +from dateutil.parser import ParserError +from dateutil.relativedelta import relativedelta import frappe from frappe.desk.utils import slug -from frappe.utils.deprecations import deprecation_warning DateTimeLikeObject = Union[str, datetime.date, datetime.datetime] NumericType = Union[int, float] @@ -81,9 +83,6 @@ def getdate( Converts string date (yyyy-mm-dd) to datetime.date object. If no input is provided, current date is returned. """ - from dateutil import parser - from dateutil.parser._parser import ParserError - if not string_date: return get_datetime().date() if isinstance(string_date, datetime.datetime): @@ -106,7 +105,6 @@ def getdate( def get_datetime( datetime_str: Optional["DateTimeLikeObject"] = None, ) -> datetime.datetime | None: - from dateutil import parser if datetime_str is None: return now_datetime() @@ -142,9 +140,6 @@ def get_timedelta(time: str | None = None) -> datetime.timedelta | None: Returns: datetime.timedelta: Timedelta object equivalent of the passed `time` string """ - from dateutil import parser - from dateutil.parser import ParserError - time = time or "0:0:0" try: @@ -162,8 +157,6 @@ def get_timedelta(time: str | None = None) -> datetime.timedelta | None: def to_timedelta(time_str: str | datetime.time) -> datetime.timedelta: - from dateutil import parser - if isinstance(time_str, datetime.time): time_str = str(time_str) @@ -238,9 +231,6 @@ def add_to_date( as_datetime=False, ) -> DateTimeLikeObject: """Adds `days` to the given date""" - from dateutil import parser - from dateutil.parser._parser import ParserError - from dateutil.relativedelta import relativedelta if date is None: date = now_datetime() @@ -326,7 +316,7 @@ def get_system_timezone(): if frappe.local.flags.in_test: return _get_system_timezone() - return frappe.cache().get_value("time_zone", _get_system_timezone) + return frappe.cache.get_value("time_zone", _get_system_timezone) def convert_utc_to_timezone(utc_timestamp, time_zone): @@ -501,9 +491,6 @@ def get_year_ending(date) -> datetime.date: def get_time(time_str: str) -> datetime.time: - from dateutil import parser - from dateutil.parser import ParserError - if isinstance(time_str, datetime.datetime): return time_str.time() elif isinstance(time_str, datetime.time): @@ -1117,12 +1104,12 @@ def _round_away_from_zero(num, precision): def _bankers_rounding(num, precision): - if num == 0: - return 0.0 - multiplier = 10**precision num = round(num * multiplier, 12) + if num == 0: + return 0.0 + floor_num = math.floor(num) decimal_part = num - floor_num @@ -2235,3 +2222,11 @@ def add_trackers_to_url(url: str, source: str, campaign: str, medium: str = "ema url_parts[4] = urlencode(query) return urlunparse(url_parts) + + +# This is used in test to count memory overhead of default imports. +def _get_rss_memory_usage(): + import psutil + + rss = psutil.Process().memory_info().rss // (1024 * 1024) + return rss diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 2c450750e1..a9891cb532 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -1,19 +1,15 @@ # Copyright (c) 2015, Maxwell Morais and contributors # License: MIT. See LICENSE -import cgitb import datetime import functools import inspect import json import linecache import os -import pydoc import sys import traceback -from ldap3.core.exceptions import LDAPException - import frappe from frappe.utils import cstr, encode @@ -21,16 +17,31 @@ EXCLUDE_EXCEPTIONS = ( frappe.AuthenticationError, frappe.CSRFTokenError, # CSRF covers OAuth too frappe.SecurityException, - LDAPException, frappe.InReadOnlyMode, ) +LDAP_BASE_EXCEPTION = "LDAPException" + + +def _is_ldap_exception(e): + """Check if exception is from LDAP library. + + This is a hack but ensures that LDAP is not imported unless it's required. This is tested in + unittests in case the exception changes in future. + """ + + for t in type(e).__mro__: + if t.__name__ == LDAP_BASE_EXCEPTION: + return True + + return False + def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: return - if isinstance(exception, EXCLUDE_EXCEPTIONS): + if isinstance(exception, EXCLUDE_EXCEPTIONS) or _is_ldap_exception(exception): return logger = frappe.logger(with_more_info=True) @@ -57,6 +68,8 @@ def make_error_snapshot(exception): def get_snapshot(exception, context=10): + import pydoc + """ Return a dict describing a given traceback (based on cgitb.text) """ @@ -103,7 +116,7 @@ def get_snapshot(exception, context=10): finally: lnum[0] += 1 - vars = cgitb.scanvars(reader, frame, locals) + vars = _scanvars(reader, frame, locals) # if it is a view, replace with generated code # if file.endswith('html'): @@ -123,7 +136,7 @@ def get_snapshot(exception, context=10): for name, where, value in vars: if name in f["dump"]: continue - if value is not cgitb.__UNDEF__: + if value is not __UNDEF__: if where == "global": name = f"global {name:s}" elif where != "local": @@ -257,3 +270,56 @@ def raise_error_on_no_output(error_message, error_type=None, keep_quiet=None): return wrapper_raise_error_on_no_output return decorator_raise_error_on_no_output + + +# Vendored from cgitb standard library reused under PSF License: +# https://github.com/python/cpython/blob/main/LICENSE + + +import keyword +import tokenize + +__UNDEF__ = [] # a special sentinel object + + +def _scanvars(reader, frame, locals): + """Scan one logical line of Python and look up values of variables used.""" + vars, lasttoken, parent, prefix, value = [], None, None, "", __UNDEF__ + for ttype, token, start, end, line in tokenize.generate_tokens(reader): + if ttype == tokenize.NEWLINE: + break + if ttype == tokenize.NAME and token not in keyword.kwlist: + if lasttoken == ".": + if parent is not __UNDEF__: + value = getattr(parent, token, __UNDEF__) + vars.append((prefix + token, prefix, value)) + else: + where, value = _lookup(token, frame, locals) + vars.append((token, where, value)) + elif token == ".": + prefix += lasttoken + "." + parent = value + else: + parent, prefix = None, "" + lasttoken = token + return vars + + +def _lookup(name, frame, locals): + """Find the value for a given name in the given environment.""" + if name in locals: + return "local", locals[name] + if name in frame.f_globals: + return "global", frame.f_globals[name] + if "__builtins__" in frame.f_globals: + builtins = frame.f_globals["__builtins__"] + if type(builtins) is type({}): # noqa + if name in builtins: + return "builtin", builtins[name] + else: + if hasattr(builtins, name): + return "builtin", getattr(builtins, name) + return None, __UNDEF__ + + +# end: vendored code diff --git a/frappe/utils/fixtures.py b/frappe/utils/fixtures.py index 821b1a1187..ddd8650451 100644 --- a/frappe/utils/fixtures.py +++ b/frappe/utils/fixtures.py @@ -3,6 +3,8 @@ import os +import click + import frappe from frappe.core.doctype.data_import.data_import import export_json, import_doc @@ -17,29 +19,45 @@ def sync_fixtures(app=None): frappe.flags.in_fixtures = True for app in apps: - fixtures_path = frappe.get_app_path(app, "fixtures") - if os.path.exists(fixtures_path): - import_doc(fixtures_path) - + import_fixtures(app) import_custom_scripts(app) frappe.flags.in_fixtures = False +def import_fixtures(app): + fixtures_path = frappe.get_app_path(app, "fixtures") + if not os.path.exists(fixtures_path): + return + + fixture_files = os.listdir(fixtures_path) + + for fname in fixture_files: + if not fname.endswith(".json"): + continue + + file_path = frappe.get_app_path(app, "fixtures", fname) + try: + import_doc(file_path) + except (ImportError, frappe.DoesNotExistError) as e: + # fixture syncing for missing doctypes + print(f"Skipping fixture syncing from the file {fname}. Reason: {e}") + + def import_custom_scripts(app): """Import custom scripts from `[app]/fixtures/custom_scripts`""" - if os.path.exists(frappe.get_app_path(app, "fixtures", "custom_scripts")): - for fname in os.listdir(frappe.get_app_path(app, "fixtures", "custom_scripts")): - if fname.endswith(".js"): - with open(frappe.get_app_path(app, "fixtures", "custom_scripts") + os.path.sep + fname) as f: - doctype = fname.rsplit(".", 1)[0] - script = f.read() - if frappe.db.exists("Client Script", {"dt": doctype}): - custom_script = frappe.get_doc("Client Script", {"dt": doctype}) - custom_script.script = script - custom_script.save() - else: - frappe.get_doc({"doctype": "Client Script", "dt": doctype, "script": script}).insert() + scripts_folder = frappe.get_app_path(app, "fixtures", "custom_scripts") + if not os.path.exists(scripts_folder): + return + + for fname in os.listdir(scripts_folder): + if not fname.endswith(".js"): + continue + + click.secho( + f"Importing Client Script `{fname}` from `{scripts_folder}` is not supported. Convert the client script to fixture.", + fg="red", + ) def export_fixtures(app=None): diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 5e5c1da141..af01692b94 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -61,7 +61,7 @@ def get_doctypes_with_global_search(with_child_tables=True): return doctypes - return frappe.cache().get_value("doctypes_with_global_search", _get) + return frappe.cache.get_value("doctypes_with_global_search", _get) def rebuild_for_doctype(doctype): @@ -371,17 +371,17 @@ def sync_global_search(): :param flags: :return: """ - while frappe.cache().llen("global_search_queue") > 0: + while frappe.cache.llen("global_search_queue") > 0: # rpop to follow FIFO # Last one should override all previous contents of same document - value = json.loads(frappe.cache().rpop("global_search_queue").decode("utf-8")) + value = json.loads(frappe.cache.rpop("global_search_queue").decode("utf-8")) sync_value(value) def sync_value_in_queue(value): try: # append to search queue if connected - frappe.cache().lpush("global_search_queue", json.dumps(value)) + frappe.cache.lpush("global_search_queue", json.dumps(value)) except redis.exceptions.ConnectionError: # not connected, sync directly sync_value(value) diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 374851e019..01b7576876 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -221,9 +221,7 @@ def rebuild_node(doctype, parent, left, parent_field): # we've got the left value, and now that we've processed # the children of this node we also know the right value - frappe.db.set_value( - doctype, parent, {"lft": left, "rgt": right}, for_update=False, update_modified=False - ) + frappe.db.set_value(doctype, parent, {"lft": left, "rgt": right}, update_modified=False) # return the right value of this node + 1 return right + 1 @@ -326,7 +324,6 @@ class NestedSet(Document): {"old_parent": newdn}, {parent_field: newdn}, update_modified=False, - for_update=False, ) if merge: diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index d07011afd1..611ea2967c 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -5,8 +5,6 @@ import base64 import json from typing import TYPE_CHECKING, Callable -import jwt - import frappe import frappe.utils from frappe import _ @@ -126,6 +124,9 @@ def login_via_oauth2_id_token( def get_info_via_oauth( provider: str, code: str, decoder: Callable | None = None, id_token: bool = False ): + + import jwt + flow = get_oauth2_flow(provider) oauth2_providers = get_oauth2_providers() @@ -210,7 +211,7 @@ def login_oauth_user( if frappe.utils.cint(generate_login_token): login_token = frappe.generate_hash(length=32) - frappe.cache().set_value( + frappe.cache.set_value( f"login_token:{login_token}", frappe.local.session.sid, expires_in_sec=120 ) diff --git a/frappe/utils/password.py b/frappe/utils/password.py index fa2e03bde5..fe3ef27d6b 100644 --- a/frappe/utils/password.py +++ b/frappe/utils/password.py @@ -1,12 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE -import string - from cryptography.fernet import Fernet, InvalidToken from passlib.context import CryptContext -from passlib.hash import mysql41, pbkdf2_sha256 -from passlib.registry import register_crypt_handler from pypika.terms import Values import frappe @@ -17,30 +13,10 @@ from frappe.utils import cstr, encode Auth = Table("__Auth") -class LegacyPassword(pbkdf2_sha256): - name = "frappe_legacy" - ident = "$frappel$" - - def _calc_checksum(self, secret): - # check if this is a mysql hash - # it is possible that we will generate a false positive if the users password happens to be 40 hex chars proceeded - # by an * char, but this seems highly unlikely - if not ( - secret[0] == "*" and len(secret) == 41 and all(c in string.hexdigits for c in secret[1:]) - ): - secret = mysql41.hash(secret + self.salt.decode("utf-8")) - return super()._calc_checksum(secret) - - -register_crypt_handler(LegacyPassword, force=True) passlibctx = CryptContext( schemes=[ "pbkdf2_sha256", "argon2", - "frappe_legacy", - ], - deprecated=[ - "frappe_legacy", ], ) @@ -128,9 +104,9 @@ def check_password(user, pwd, doctype="User", fieldname="password", delete_track def delete_login_failed_cache(user): - frappe.cache().hdel("last_login_tried", user) - frappe.cache().hdel("login_failed_count", user) - frappe.cache().hdel("locked_account_time", user) + frappe.cache.hdel("last_login_tried", user) + frappe.cache.hdel("login_failed_count", user) + frappe.cache.hdel("locked_account_time", user) def update_password(user, pwd, doctype="User", fieldname="password", logout_all_sessions=False): @@ -219,7 +195,13 @@ def decrypt(txt, encryption_key=None): return cstr(cipher_suite.decrypt(encode(txt))) except InvalidToken: # encryption_key in site_config is changed and not valid - frappe.throw(_("Encryption key is invalid! Please check site_config.json")) + frappe.throw( + _("Encryption key is invalid! Please check site_config.json") + + "" + + _( + "If you have recently restored the site you may need to copy the site config contaning original Encryption Key." + ) + ) def get_encryption_key(): diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index a0ba4a6de1..721b061257 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -8,7 +8,7 @@ from distutils.version import LooseVersion import pdfkit from bs4 import BeautifulSoup -from PyPDF2 import PdfReader, PdfWriter +from pypdf import PdfReader, PdfWriter import frappe from frappe import _ @@ -273,13 +273,13 @@ def toggle_visible_pdf(soup): def get_wkhtmltopdf_version(): - wkhtmltopdf_version = frappe.cache().hget("wkhtmltopdf_version", None) + wkhtmltopdf_version = frappe.cache.hget("wkhtmltopdf_version", None) if not wkhtmltopdf_version: try: res = subprocess.check_output(["wkhtmltopdf", "--version"]) wkhtmltopdf_version = res.decode("utf-8").split(" ")[1] - frappe.cache().hset("wkhtmltopdf_version", None, wkhtmltopdf_version) + frappe.cache.hset("wkhtmltopdf_version", None, wkhtmltopdf_version) except Exception: pass diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index b96809f2c2..4faaf97780 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -1,6 +1,6 @@ import os -from PyPDF2 import PdfWriter +from pypdf import PdfWriter import frappe from frappe import _ diff --git a/frappe/utils/redis_queue.py b/frappe/utils/redis_queue.py index 89f8fe16f3..b045be3ab1 100644 --- a/frappe/utils/redis_queue.py +++ b/frappe/utils/redis_queue.py @@ -17,10 +17,7 @@ class RedisQueue: @classmethod def get_connection(cls, username=None, password=None): - rq_url = frappe.local.conf.redis_queue - domain = rq_url.split("redis://", 1)[-1] - url = (username and f"redis://{username}:{password or ''}@{domain}") or rq_url - conn = redis.from_url(url) + conn = redis.from_url(frappe.conf.redis_queue, username=username, password=password) conn.ping() return conn diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index c23763f2b6..45be0c63e8 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -251,7 +251,7 @@ class RedisWrapper(redis.Redis): def hdel_keys(self, name_starts_with, key): """Delete hash names with wildcard `*` and key""" - for name in frappe.cache().get_keys(name_starts_with): + for name in self.get_keys(name_starts_with): name = name.split("|", 1)[1] self.hdel(name, key) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 79a6b16d1a..b0f8fbc6e6 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -68,7 +68,6 @@ def build_response(response_type=None): def as_csv(): response = Response() response.mimetype = "text/csv" - response.charset = "utf-8" response.headers["Content-Disposition"] = ( 'attachment; filename="%s.csv"' % frappe.response["doctype"].replace(" ", "_") ).encode("utf-8") @@ -79,7 +78,6 @@ def as_csv(): def as_txt(): response = Response() response.mimetype = "text" - response.charset = "utf-8" response.headers["Content-Disposition"] = ( 'attachment; filename="%s.txt"' % frappe.response["doctype"].replace(" ", "_") ).encode("utf-8") @@ -109,7 +107,6 @@ def as_json(): del frappe.local.response["http_status_code"] response.mimetype = "application/json" - response.charset = "utf-8" response.data = json.dumps(frappe.local.response, default=json_handler, separators=(",", ":")) return response diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 8f73efd17c..f5ee095309 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -24,7 +24,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.rename_doc import rename_doc from frappe.modules import scrub from frappe.utils.background_jobs import enqueue, get_jobs -from frappe.website.utils import get_next_link, get_shade, get_toc +from frappe.website.utils import get_next_link, get_toc from frappe.www.printview import get_visible_columns @@ -197,7 +197,6 @@ def get_safe_globals(): get_toc=get_toc, get_next_link=get_next_link, _=frappe._, - get_shade=get_shade, scrub=scrub, guess_mimetype=mimetypes.guess_type, html2text=html2text, diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 529a3c7bf7..903a8dd081 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -15,7 +15,6 @@ from typing import NoReturn # imports - module imports import frappe -from frappe.installer import update_site_config from frappe.utils import cint, get_datetime, get_sites, now_datetime from frappe.utils.background_jobs import get_jobs @@ -176,6 +175,8 @@ def _get_last_modified_timestamp(doctype): @frappe.whitelist() def activate_scheduler(): + from frappe.installer import update_site_config + frappe.only_for("Administrator") if frappe.local.conf.maintenance_mode: diff --git a/frappe/utils/telemetry.py b/frappe/utils/telemetry.py index e15146c71d..1fa2480193 100644 --- a/frappe/utils/telemetry.py +++ b/frappe/utils/telemetry.py @@ -5,12 +5,12 @@ removed without any warning. """ from contextlib import suppress -from posthog import Posthog - import frappe from frappe.utils import getdate from frappe.utils.caching import site_cache +from posthog import Posthog # isort: skip + POSTHOG_PROJECT_FIELD = "posthog_project_id" POSTHOG_HOST_FIELD = "posthog_host" @@ -59,11 +59,13 @@ def capture(event, app, **kwargs): ph and ph.capture(distinct_id=frappe.local.site, event=f"{app}_{event}", **kwargs) -def capture_doc(doc): +def capture_doc(doc, action): with suppress(Exception): age = site_age() if not age or age > 15: return if doc.get("__islocal") or not doc.get("name"): - capture("document_created", "frappe", properties={"doctype": doc.doctype}) + capture("document_created", "frappe", properties={"doctype": doc.doctype, "action": "Insert"}) + else: + capture("document_modified", "frappe", properties={"doctype": doc.doctype, "action": action}) diff --git a/frappe/utils/user.py b/frappe/utils/user.py index f37e52f7be..35dec3aa60 100644 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -59,7 +59,7 @@ class UserPermissions: return user if not frappe.flags.in_install_db and not frappe.flags.in_test: - user_doc = frappe.cache().hget("user_doc", self.name, get_user_doc) + user_doc = frappe.cache.hget("user_doc", self.name, get_user_doc) if user_doc: self.doc = frappe.get_doc(user_doc) @@ -186,7 +186,7 @@ class UserPermissions: filters={"property": "allow_import", "value": "1"}, ) - frappe.cache().hset("can_import", frappe.session.user, self.can_import) + frappe.cache.hset("can_import", frappe.session.user, self.can_import) def get_defaults(self): import frappe.defaults diff --git a/frappe/website/doctype/help_article/help_article.py b/frappe/website/doctype/help_article/help_article.py index e70de07703..108f3cb615 100644 --- a/frappe/website/doctype/help_article/help_article.py +++ b/frappe/website/doctype/help_article/help_article.py @@ -93,7 +93,7 @@ def get_sidebar_items(): as_dict=True, ) - return frappe.cache().get_value("knowledge_base:category_sidebar", _get) + return frappe.cache.get_value("knowledge_base:category_sidebar", _get) def clear_cache(): @@ -105,8 +105,8 @@ def clear_cache(): def clear_website_cache(path=None): - frappe.cache().delete_value("knowledge_base:category_sidebar") - frappe.cache().delete_value("knowledge_base:faq") + frappe.cache.delete_value("knowledge_base:category_sidebar") + frappe.cache.delete_value("knowledge_base:faq") @frappe.whitelist(allow_guest=True) diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index ef48203cda..0295ac03e4 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -5,7 +5,6 @@ from urllib.parse import quote import frappe from frappe import _ -from frappe.integrations.google_oauth import GoogleOAuth from frappe.model.document import Document from frappe.utils import encode, get_request_site_address from frappe.website.utils import get_boot_data @@ -100,6 +99,8 @@ class WebsiteSettings(Document): frappe.clear_cache() def get_access_token(self): + from frappe.integrations.google_oauth import GoogleOAuth + if not self.indexing_refresh_token: button_label = frappe.bold(_("Allow API Indexing Access")) raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) diff --git a/frappe/website/page_renderers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py index 98aeb19057..704dca77d1 100644 --- a/frappe/website/page_renderers/not_found_page.py +++ b/frappe/website/page_renderers/not_found_page.py @@ -21,7 +21,7 @@ class NotFoundPage(TemplatePage): def render(self): if self.can_cache_404(): - frappe.cache().hset("website_404", self.request_url, True) + frappe.cache.hset("website_404", self.request_url, True) return super().render() def can_cache_404(self): diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index c7874b1671..37bfb3ee56 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -29,7 +29,7 @@ class PathResolver: request = frappe.local.request or request # check if the request url is in 404 list - if request.url and can_cache() and frappe.cache().hget("website_404", request.url): + if request.url and can_cache() and frappe.cache.hget("website_404", request.url): return self.path, NotFoundPage(self.path) try: @@ -110,7 +110,7 @@ def resolve_redirect(path, query_string=None): if not redirects: return - redirect_to = frappe.cache().hget("website_redirects", path) + redirect_to = frappe.cache.hget("website_redirects", path) if redirect_to: frappe.flags.redirect_location = redirect_to @@ -130,7 +130,7 @@ def resolve_redirect(path, query_string=None): if match: redirect_to = re.sub(pattern, rule["target"], path_to_match) frappe.flags.redirect_location = redirect_to - frappe.cache().hset("website_redirects", path_to_match, redirect_to) + frappe.cache.hset("website_redirects", path_to_match, redirect_to) raise frappe.Redirect @@ -177,4 +177,4 @@ def get_website_rules(): # dont cache in development return _get() - return frappe.cache().get_value("website_route_rules", _get) + return frappe.cache.get_value("website_route_rules", _get) diff --git a/frappe/website/router.py b/frappe/website/router.py index 98be1138e4..14648f15e9 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -100,7 +100,7 @@ def get_pages(app=None): return pages - return frappe.cache().get_value("website_pages", lambda: _build(app)) + return frappe.cache.get_value("website_pages", lambda: _build(app)) def get_pages_from_path(start, app, app_path): @@ -310,7 +310,7 @@ def get_doctypes_with_web_view(): ] return doctypes - return frappe.cache().get_value("doctypes_with_web_view", _get) + return frappe.cache.get_value("doctypes_with_web_view", _get) def get_start_folders(): diff --git a/frappe/website/utils.py b/frappe/website/utils.py index ff8c69639e..f5215e9440 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -12,7 +12,7 @@ from werkzeug.wrappers import Response import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, get_assets_json, get_system_timezone, md_to_html +from frappe.utils import cint, cstr, get_assets_json, get_system_timezone, md_to_html FRONTMATTER_PATTERN = re.compile(r"^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$", re.S | re.M) H1_TAG_PATTERN = re.compile("([^<]*)") @@ -23,15 +23,14 @@ CLEANUP_PATTERN_3 = re.compile(r"(-)\1+") def delete_page_cache(path): - cache = frappe.cache() - cache.delete_value("full_index") + frappe.cache.delete_value("full_index") groups = ("website_page", "page_context") if path: for name in groups: - cache.hdel(name, path) + frappe.cache.hdel(name, path) else: for name in groups: - cache.delete_key(name) + frappe.cache.delete_key(name) def find_first_image(html): @@ -127,7 +126,7 @@ def get_home_page(): # dont return cached homepage in development return _get_home_page() - return frappe.cache().hget("home_page", frappe.session.user, _get_home_page) + return frappe.cache.hget("home_page", frappe.session.user, _get_home_page) def get_home_page_via_hooks(): @@ -192,11 +191,6 @@ def cleanup_page_name(title: str) -> str: return name[:140] -def get_shade(color, percent=None): - frappe.msgprint(_("get_shade method has been deprecated.")) - return color - - def abs_url(path): """Deconstructs and Reconstructs a URL into an absolute URL or a URL relative from root '/'""" if not path: @@ -296,7 +290,7 @@ def get_full_index(route=None, app=None): return children_map - children_map = frappe.cache().get_value("website_full_index", _build) + children_map = frappe.cache.get_value("website_full_index", _build) frappe.local.flags.children_map = children_map @@ -363,13 +357,13 @@ def clear_cache(path=None): from frappe.website.router import clear_routing_cache for key in ("website_generator_routes", "website_pages", "website_full_index", "sitemap_routes"): - frappe.cache().delete_value(key) + frappe.cache.delete_value(key) clear_routing_cache() - frappe.cache().delete_value("website_404") + frappe.cache.delete_value("website_404") if path: - frappe.cache().hdel("website_redirects", path) + frappe.cache.hdel("website_redirects", path) delete_page_cache(path) else: clear_sitemap() @@ -383,7 +377,7 @@ def clear_cache(path=None): "page_context", "website_page", ): - frappe.cache().delete_value(key) + frappe.cache.delete_value(key) for method in frappe.get_hooks("website_clear_cache"): frappe.get_attr(method)(path) @@ -439,7 +433,7 @@ def get_sidebar_items(parent_sidebar, basepath=None): def get_portal_sidebar_items(): - sidebar_items = frappe.cache().hget("portal_menu_items", frappe.session.user) + sidebar_items = frappe.cache.hget("portal_menu_items", frappe.session.user) if sidebar_items is None: sidebar_items = [] roles = frappe.get_roles() @@ -462,7 +456,7 @@ def get_portal_sidebar_items(): i["enabled"] = 1 add_items(sidebar_items, items_via_hooks) - frappe.cache().hset("portal_menu_items", frappe.session.user, sidebar_items) + frappe.cache.hset("portal_menu_items", frappe.session.user, sidebar_items) return sidebar_items @@ -507,7 +501,7 @@ def cache_html(func): def cache_html_decorator(*args, **kwargs): if can_cache(): html = None - page_cache = frappe.cache().hget("website_page", args[0].path) + page_cache = frappe.cache.hget("website_page", args[0].path) if page_cache and frappe.local.lang in page_cache: html = page_cache[frappe.local.lang] if html: @@ -516,9 +510,9 @@ def cache_html(func): html = func(*args, **kwargs) context = args[0].context if can_cache(context.no_cache): - page_cache = frappe.cache().hget("website_page", args[0].path) or {} + page_cache = frappe.cache.hget("website_page", args[0].path) or {} page_cache[frappe.local.lang] = html - frappe.cache().hset("website_page", args[0].path, page_cache) + frappe.cache.hset("website_page", args[0].path, page_cache) return html @@ -530,14 +524,14 @@ def build_response(path, data, http_status_code, headers: dict | None = None): response = Response() response.data = set_content_type(response, data, path) response.status_code = http_status_code - response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") + response.headers["X-Page-Name"] = cstr(path.encode("ascii", errors="xmlcharrefreplace")) response.headers["X-From-Cache"] = frappe.local.response.from_cache or False add_preload_for_bundled_assets(response) if headers: for key, val in headers.items(): - response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") + response.headers[key] = cstr(val.encode("ascii", errors="xmlcharrefreplace")) return response @@ -545,12 +539,10 @@ def build_response(path, data, http_status_code, headers: dict | None = None): def set_content_type(response, data, path): if isinstance(data, dict): response.mimetype = "application/json" - response.charset = "utf-8" data = json.dumps(data) return data response.mimetype = "text/html" - response.charset = "utf-8" # ignore paths ending with .com to avoid unnecessary download # https://bugs.python.org/issue22347 diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py index b7740242c3..7da7f60109 100644 --- a/frappe/workflow/doctype/workflow/test_workflow.py +++ b/frappe/workflow/doctype/workflow/test_workflow.py @@ -33,7 +33,7 @@ class TestWorkflow(FrappeTestCase): "postgres": 'ALTER TABLE "tabWorkflow Action" ADD COLUMN "user" varchar(140)', } ) - frappe.cache().delete_value("table_columns") + frappe.cache.delete_value("table_columns") def tearDown(self): frappe.delete_doc("Workflow", "Test ToDo") @@ -49,7 +49,7 @@ class TestWorkflow(FrappeTestCase): "postgres": 'ALTER TABLE "tabWorkflow Action" DROP COLUMN "user"', } ) - frappe.cache().delete_value("table_columns") + frappe.cache.delete_value("table_columns") def test_default_condition(self): """test default condition is set""" diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 018b567ee9..56c17261b7 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -17,7 +17,7 @@ class Workflow(Document): def on_update(self): self.update_doc_status() frappe.clear_cache(doctype=self.document_type) - frappe.cache().delete_key("workflow_" + self.name) # clear cache created in model/workflow.py + frappe.cache.delete_key("workflow_" + self.name) # clear cache created in model/workflow.py def create_custom_field_for_workflow_state(self): frappe.clear_cache(doctype=self.document_type) diff --git a/frappe/www/login.py b/frappe/www/login.py index 3cd9edf7ce..7cf89e7c3c 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -5,7 +5,6 @@ import frappe import frappe.utils from frappe import _ from frappe.auth import LoginManager -from frappe.integrations.doctype.ldap_settings.ldap_settings import LDAPSettings from frappe.rate_limiter import rate_limit from frappe.utils import cint, get_url from frappe.utils.data import escape_html @@ -85,7 +84,10 @@ def get_context(context): ) context["social_login"] = True - context["ldap_settings"] = LDAPSettings.get_ldap_client_settings() + if cint(frappe.db.get_value("LDAP Settings", "LDAP Settings", "enabled")): + from frappe.integrations.doctype.ldap_settings.ldap_settings import LDAPSettings + + context["ldap_settings"] = LDAPSettings.get_ldap_client_settings() login_label = [_("Email")] @@ -104,7 +106,7 @@ def get_context(context): @frappe.whitelist(allow_guest=True) def login_via_token(login_token: str): - sid = frappe.cache().get_value(f"login_token:{login_token}", expires=True) + sid = frappe.cache.get_value(f"login_token:{login_token}", expires=True) if not sid: frappe.respond_as_web_page(_("Invalid Request"), _("Invalid Login Token"), http_status_code=417) return @@ -150,7 +152,7 @@ def _generate_temporary_login_link(email: str, expiry: int): _("User with email address {0} does not exist").format(email), frappe.DoesNotExistError ) key = frappe.generate_hash() - frappe.cache().set_value(f"one_time_login_key:{key}", email, expires_in_sec=expiry * 60) + frappe.cache.set_value(f"one_time_login_key:{key}", email, expires_in_sec=expiry * 60) return get_url(f"/api/method/frappe.www.login.login_via_key?key={key}") @@ -159,10 +161,10 @@ def _generate_temporary_login_link(email: str, expiry: int): @rate_limit(limit=5, seconds=60 * 60) def login_via_key(key: str): cache_key = f"one_time_login_key:{key}" - email = frappe.cache().get_value(cache_key) + email = frappe.cache.get_value(cache_key) if email: - frappe.cache().delete_value(cache_key) + frappe.cache.delete_value(cache_key) frappe.local.login_manager.login_as(email) diff --git a/frappe/www/message.py b/frappe/www/message.py index d8e359f07c..dbd6ac3863 100644 --- a/frappe/www/message.py +++ b/frappe/www/message.py @@ -20,7 +20,7 @@ def get_context(context): elif frappe.local.form_dict.id: message_id = frappe.local.form_dict.id key = f"message_id:{message_id}" - message = frappe.cache().get_value(key, expires=True) + message = frappe.cache.get_value(key, expires=True) if message: message_context.update(message.get("context", {})) if message.get("http_status_code"): diff --git a/frappe/www/qrcode.py b/frappe/www/qrcode.py index e76dc65540..e6d683694f 100644 --- a/frappe/www/qrcode.py +++ b/frappe/www/qrcode.py @@ -29,8 +29,8 @@ def get_query_key(): def get_user_svg_from_cache(): """Get User and SVG code from cache.""" key = get_query_key() - totp_uri = frappe.cache().get_value(f"{key}_uri") - user = frappe.cache().get_value(f"{key}_user") + totp_uri = frappe.cache.get_value(f"{key}_uri") + user = frappe.cache.get_value(f"{key}_user") if not totp_uri or not user: frappe.throw(_("Page has expired!"), frappe.PermissionError) if not frappe.db.exists("User", user): diff --git a/frappe/www/sitemap.py b/frappe/www/sitemap.py index 57e9d27049..47d809d384 100644 --- a/frappe/www/sitemap.py +++ b/frappe/www/sitemap.py @@ -69,4 +69,4 @@ def get_public_pages_from_doctypes(): return routes - return frappe.cache().get_value("sitemap_routes", get_sitemap_routes) + return frappe.cache.get_value("sitemap_routes", get_sitemap_routes) diff --git a/node_utils.js b/node_utils.js index 10744387ca..0b8c455875 100644 --- a/node_utils.js +++ b/node_utils.js @@ -38,7 +38,7 @@ function get_conf() { return conf; } -function get_redis_subscriber(kind = "redis_socketio", options = {}) { +function get_redis_subscriber(kind = "redis_queue", options = {}) { const conf = get_conf(); const host = conf[kind] || conf.redis_async_broker_port; return redis.createClient({ url: host, ...options }); diff --git a/package.json b/package.json index 6cf9d1d60b..fe0f1cd089 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/frappe/frappe/issues" }, "engines": { - "node": ">=14" + "node": ">=18" }, "homepage": "https://frappeframework.com", "dependencies": { @@ -56,7 +56,7 @@ "moment": "^2.29.4", "moment-timezone": "^0.5.35", "pinia": "^2.0.23", - "plyr": "^3.7.2", + "plyr": "^3.7.8", "popper.js": "^1.16.0", "postcss": "8", "quill": "2.0.0-dev.4", @@ -72,7 +72,7 @@ "sortablejs": "1.9.0", "superagent": "^3.8.2", "touch": "^3.1.0", - "vue": "3.2.39", + "vue": "^3.3.0", "vue-router": "^4.1.5", "vuedraggable": "^4.1.0", "vuex": "4.0.2", diff --git a/pyproject.toml b/pyproject.toml index dde346e7aa..a4d61d6845 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,65 +13,65 @@ dependencies = [ "Click~=8.1.3", "filelock~=3.8.0", "filetype~=1.2.0", - "GitPython~=3.1.30", + "GitPython~=3.1.31", "Jinja2~=3.1.2", - "Pillow~=9.3.0", - "PyJWT~=2.4.0", + "Pillow~=9.5.0", + "PyJWT~=2.7.0", "PyMySQL==1.0.3", - "PyPDF2~=2.1.0", + "pypdf~=3.9.1", "PyPika~=0.48.9", "PyQRCode~=1.2.1", "PyYAML~=6.0", "RestrictedPython~=6.0", - "WeasyPrint==52.5", - "Werkzeug~=2.2.2", + "WeasyPrint==59.0", + "Werkzeug~=2.3.4", "Whoosh~=2.7.4", - "beautifulsoup4~=4.9.3", + "beautifulsoup4~=4.12.2", "bleach-allowlist~=1.0.3", "bleach~=3.3.0", - "cairocffi==1.2.0", + "cairocffi==1.5.1", "chardet~=5.1.0", - "croniter~=1.3.5", - "cryptography~=39.0.1", + "croniter~=1.3.15", + "cryptography~=41.0.1", "email-reply-parser~=0.5.12", "git-url-parse~=1.2.2", "gunicorn~=20.1.0", "html5lib~=1.1", "ipython~=8.10.0", "ldap3~=2.9", - "markdown2~=2.4.0", + "markdown2~=2.4.8", "MarkupSafe>=2.1.0,<3", "maxminddb-geolite2==2018.703", - "num2words~=0.5.10", - "oauthlib~=3.2.1", - "openpyxl~=3.0.7", + "num2words~=0.5.12", + "oauthlib~=3.2.2", + "openpyxl~=3.1.2", "passlib~=1.7.4", "pdfkit~=1.0.0", - "phonenumbers==8.12.40", + "phonenumbers==8.13.13", "premailer~=3.8.0", - "psutil~=5.9.1", + "psutil~=5.9.5", "psycopg2-binary~=2.9.1", - "pyOpenSSL~=23.0.0", - "pycryptodome~=3.10.1", - "pydantic~=2.0b2", - "pyotp~=2.6.0", - "python-dateutil~=2.8.1", - "pytz==2022.1", + "pyOpenSSL~=23.2.0", + "pycryptodome~=3.18.0", + "pydantic~=2.0b3", + "pyotp~=2.8.0", + "python-dateutil~=2.8.2", + "pytz==2023.3", "rauth~=0.7.3", - "redis~=4.5.4", - "hiredis~=2.0.0", - "requests-oauthlib~=1.3.0", + "redis~=4.5.5", + "hiredis~=2.2.3", + "requests-oauthlib~=1.3.1", "requests~=2.31.0", - "rq~=1.11.1", + "rq~=1.15.1", "rsa>=4.1", "semantic-version~=2.10.0", - "sqlparse~=0.4.1", - "tenacity~=8.0.1", - "terminaltables~=3.1.0", + "sqlparse~=0.4.4", + "tenacity~=8.2.2", + "terminaltables~=3.1.10", "traceback-with-variables~=2.0.4", "xlrd~=2.0.1", "zxcvbn~=4.4.28", - "markdownify~=0.11.2", + "markdownify~=0.11.6", # integration dependencies "boto3~=1.18.49", @@ -100,8 +100,9 @@ indent = "\t" [tool.bench.dev-dependencies] coverage = "~=6.5.0" -Faker = "~=13.12.1" -pyngrok = "~=5.0.5" -unittest-xml-reporting = "~=3.0.4" -watchdog = "~=2.1.9" -hypothesis = "~=6.68.2" +Faker = "~=18.10.1" +pyngrok = "~=6.0.0" +unittest-xml-reporting = "~=3.2.0" +watchdog = "~=3.0.0" +hypothesis = "~=6.77.0" +responses = "~=0.23.1" diff --git a/setup.py b/setup.py deleted file mode 100644 index 7a90eed81a..0000000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -# TODO: Remove this file when bench >=v5.11.0 is adopted / v15.0.0 is released -from setuptools import setup - -name = "frappe" - -setup() diff --git a/socketio.js b/socketio.js index 8e87a0cce1..31be39c847 100644 --- a/socketio.js +++ b/socketio.js @@ -181,7 +181,7 @@ io.on("connection", function (socket) { }); socket.on("open_in_editor", (data) => { - let s = get_redis_subscriber("redis_socketio"); + let s = get_redis_subscriber("redis_queue"); s.publish("open_in_editor", JSON.stringify(data)); }); }); diff --git a/yarn.lock b/yarn.lock index 04ba608423..ec9dd19dc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,44 +2,44 @@ # yarn lockfile v1 -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@adobe/css-tools@^4.0.1": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" + integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== -"@babel/parser@^7.16.4": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" - integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/parser@^7.6.0", "@babel/parser@^7.9.6": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" - integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + +"@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" + integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== "@babel/types@^7.6.1", "@babel/types@^7.9.6": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" - integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" -"@codexteam/icons@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.4.tgz#8b72dcd3f3a1b0d880bdceb2abebd74b46d3ae13" - integrity sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q== - "@editorjs/editorjs@^2.26.3": - version "2.26.3" - resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.26.3.tgz#91b1a44003a5635aa29919ff1439d797e68a60e2" - integrity sha512-ztR+fwXagfAnlc86A3p8K0bI/XQTvnGjmKY0P+c18aUB4OW3BjkX6OAPb4PH/Ym8G16fBKtJG//sITck6CD1lA== - dependencies: - "@codexteam/icons" "^0.0.4" - codex-notifier "^1.1.2" - codex-tooltip "^1.0.5" - html-janitor "^2.0.4" - nanoid "^3.1.22" + version "2.27.0" + resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.27.0.tgz#ec2d6e2f42d1aa84d13eaea6a9c48b4324972c59" + integrity sha512-XyPnvu61Ynk4k7R/1gVYUJjrtuf9Tf0A+CZuvp2suW4z5TNHqNjBa9vRdr3ngHzhNSoZvgstrOO3zCVqVTezPg== + +"@esbuild/linux-loong64@0.14.54": + version "0.14.54" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" + integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== "@frappe/esbuild-plugin-postcss2@^0.1.3": version "0.1.3" @@ -55,25 +55,30 @@ stylus "^0.x" tmp "^0.2.1" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@jridgewell/sourcemap-codec@^1.4.13": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@nodelib/fs.stat" "2.0.4" + "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@socket.io/component-emitter@~3.1.0": @@ -86,11 +91,6 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/component-emitter@^1.2.10": - version "1.2.11" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506" - integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== - "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" @@ -104,129 +104,92 @@ "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": - version "18.11.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc" - integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g== + version "20.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== "@types/web-bluetooth@^0.0.16": version "0.0.16" resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8" integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ== +"@types/web-bluetooth@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz#5c9f3c617f64a9735d7b72a7cc671e166d900c40" + integrity sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA== + "@vue-flow/background@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@vue-flow/background/-/background-1.2.0.tgz#6b7c283fd679838df90dda8dd98f322c368b7e59" integrity sha512-ZqmYhOM/0aRmA5dA3KSHJNYpHnhmG2e6tzjmttoibo4JBI3KNbdyOX+OTlqt7Ic0LYUC6NWzRLAwv4gjJuc6Mg== "@vue-flow/core@^1.16.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@vue-flow/core/-/core-1.19.2.tgz#35b471b652165b8beb4933d94292a89f19250320" - integrity sha512-ThlxpN3n5CPgyfDTwnAZ5I4vv0v7zDbfl9dV3Wcx7Ryxl2U3n4R8feuvRzR7B3eb3Cqb2VokxSFHsLGfyyv4dQ== + version "1.20.2" + resolved "https://registry.yarnpkg.com/@vue-flow/core/-/core-1.20.2.tgz#f12cc5b76e93bbd3f35d664f486c88ebff05bb8b" + integrity sha512-bgcOjtaYC8/y9ZCpG8vBYEBNzUJmaXr0m1xnzSv2qFJKXgh1GeoldT8rDhqY0HXRyimEsaKbzkAf3kSl64pdsQ== dependencies: - "@vueuse/core" "^9.13.0" + "@vueuse/core" "^10.1.2" d3-drag "^3.0.0" d3-selection "^3.0.0" d3-zoom "^3.0.0" -"@vue/compiler-core@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.39.tgz#0d77e635f4bdb918326669155a2dc977c053943e" - integrity sha512-mf/36OWXqWn0wsC40nwRRGheR/qoID+lZXbIuLnr4/AngM0ov8Xvv8GHunC0rKRIkh60bTqydlqTeBo49rlbqw== +"@vue/compiler-core@3.3.4", "@vue/compiler-core@^3.2.26": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" + integrity sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g== dependencies: - "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.39" + "@babel/parser" "^7.21.3" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - source-map "^0.6.1" + source-map-js "^1.0.2" -"@vue/compiler-core@3.2.40", "@vue/compiler-core@^3.2.22": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.40.tgz#c785501f09536748121e937fb87605bbb1ada8e5" - integrity sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA== +"@vue/compiler-dom@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151" + integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w== dependencies: - "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.40" + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" + +"@vue/compiler-sfc@3.3.4", "@vue/compiler-sfc@^3.2.26": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df" + integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ== + dependencies: + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/compiler-dom" "3.3.4" + "@vue/compiler-ssr" "3.3.4" + "@vue/reactivity-transform" "3.3.4" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - source-map "^0.6.1" - -"@vue/compiler-dom@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.39.tgz#bd69d35c1a48fe2cea4ab9e96d2a3a735d146fdf" - integrity sha512-HMFI25Be1C8vLEEv1hgEO1dWwG9QQ8LTTPmCkblVJY/O3OvWx6r1+zsox5mKPMGvqYEZa6l8j+xgOfUspgo7hw== - dependencies: - "@vue/compiler-core" "3.2.39" - "@vue/shared" "3.2.39" - -"@vue/compiler-dom@3.2.40": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz#c225418773774db536174d30d3f25ba42a33e7e4" - integrity sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw== - dependencies: - "@vue/compiler-core" "3.2.40" - "@vue/shared" "3.2.40" - -"@vue/compiler-sfc@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.39.tgz#8fe29990f672805b7c5a2ecfa5b05e681c862ea2" - integrity sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA== - dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.39" - "@vue/compiler-dom" "3.2.39" - "@vue/compiler-ssr" "3.2.39" - "@vue/reactivity-transform" "3.2.39" - "@vue/shared" "3.2.39" - estree-walker "^2.0.2" - magic-string "^0.25.7" + magic-string "^0.30.0" postcss "^8.1.10" - source-map "^0.6.1" + source-map-js "^1.0.2" -"@vue/compiler-sfc@^3.2.22": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz#61823283efc84d25d9d2989458f305d32a2ed141" - integrity sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg== +"@vue/compiler-ssr@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777" + integrity sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ== dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.40" - "@vue/compiler-dom" "3.2.40" - "@vue/compiler-ssr" "3.2.40" - "@vue/reactivity-transform" "3.2.40" - "@vue/shared" "3.2.40" - estree-walker "^2.0.2" - magic-string "^0.25.7" - postcss "^8.1.10" - source-map "^0.6.1" - -"@vue/compiler-ssr@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.39.tgz#4f3bfb535cb98b764bee45e078700e03ccc60633" - integrity sha512-EoGCJ6lincKOZGW+0Ky4WOKsSmqL7hp1ZYgen8M7u/mlvvEQUaO9tKKOy7K43M9U2aA3tPv0TuYYQFrEbK2eFQ== - dependencies: - "@vue/compiler-dom" "3.2.39" - "@vue/shared" "3.2.39" - -"@vue/compiler-ssr@3.2.40": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz#67df95a096c63e9ec4b50b84cc6f05816793629c" - integrity sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ== - dependencies: - "@vue/compiler-dom" "3.2.40" - "@vue/shared" "3.2.40" + "@vue/compiler-dom" "3.3.4" + "@vue/shared" "3.3.4" "@vue/component-compiler-utils@^3.0.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz#8f85182ceed28e9b3c75313de669f83166d11e5d" - integrity sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz#f9f5fb53464b0c37b2c8d2f3fbfe44df60f61dc9" + integrity sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ== dependencies: consolidate "^0.15.1" hash-sum "^1.0.2" lru-cache "^4.1.2" merge-source-map "^1.1.0" - postcss "^7.0.14" + postcss "^7.0.36" postcss-selector-parser "^6.0.2" source-map "~0.6.1" vue-template-es2015-compiler "^1.9.0" optionalDependencies: - prettier "^1.18.2" + prettier "^1.18.2 || ^2.0.0" "@vue/component-compiler@^4.2.4": version "4.2.4" @@ -244,76 +207,70 @@ sass "^1.18.0" stylus "^0.54.5" -"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.1.4", "@vue/devtools-api@^6.4.4": - version "6.4.4" - resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.4.tgz#0b024fc8ca91bb4b6035abaf53c5aecc17119b3b" - integrity sha512-Ku31WzpOV/8cruFaXaEZKF81WkNnvCSlBY4eOGtz5WMSdJvX1v1WWlSMGZeqUwPtQ27ZZz7B62erEMq8JDjcXw== +"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" + integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== -"@vue/reactivity-transform@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.39.tgz#da6ae6c8fd77791b9ae21976720d116591e1c4aa" - integrity sha512-HGuWu864zStiWs9wBC6JYOP1E00UjMdDWIG5W+FpUx28hV3uz9ODOKVNm/vdOy/Pvzg8+OcANxAVC85WFBbl3A== +"@vue/reactivity-transform@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929" + integrity sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw== dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.39" - "@vue/shared" "3.2.39" + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" estree-walker "^2.0.2" - magic-string "^0.25.7" + magic-string "^0.30.0" -"@vue/reactivity-transform@3.2.40": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz#dc24b9074b26f0d9dd2034c6349f5bb2a51c86ac" - integrity sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw== +"@vue/reactivity@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.4.tgz#a27a29c6cd17faba5a0e99fbb86ee951653e2253" + integrity sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ== dependencies: - "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.40" - "@vue/shared" "3.2.40" - estree-walker "^2.0.2" - magic-string "^0.25.7" + "@vue/shared" "3.3.4" -"@vue/reactivity@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.39.tgz#e6e3615fe2288d4232b104640ddabd0729a78c80" - integrity sha512-vlaYX2a3qMhIZfrw3Mtfd+BuU+TZmvDrPMa+6lpfzS9k/LnGxkSuf0fhkP0rMGfiOHPtyKoU9OJJJFGm92beVQ== +"@vue/runtime-core@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz#4bb33872bbb583721b340f3088888394195967d1" + integrity sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA== dependencies: - "@vue/shared" "3.2.39" + "@vue/reactivity" "3.3.4" + "@vue/shared" "3.3.4" -"@vue/runtime-core@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.39.tgz#dc1faccab11b3e81197aba33fb30c9447c1d2c84" - integrity sha512-xKH5XP57JW5JW+8ZG1khBbuLakINTgPuINKL01hStWLTTGFOrM49UfCFXBcFvWmSbci3gmJyLl2EAzCaZWsx8g== +"@vue/runtime-dom@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz#992f2579d0ed6ce961f47bbe9bfe4b6791251566" + integrity sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ== dependencies: - "@vue/reactivity" "3.2.39" - "@vue/shared" "3.2.39" + "@vue/runtime-core" "3.3.4" + "@vue/shared" "3.3.4" + csstype "^3.1.1" -"@vue/runtime-dom@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.39.tgz#4a8cb132bcef316e8151c5ed07fc7272eb064614" - integrity sha512-4G9AEJP+sLhsqf5wXcyKVWQKUhI+iWfy0hWQgea+CpaTD7BR0KdQzvoQdZhwCY6B3oleSyNLkLAQwm0ya/wNoA== +"@vue/server-renderer@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz#ea46594b795d1536f29bc592dd0f6655f7ea4c4c" + integrity sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ== dependencies: - "@vue/runtime-core" "3.2.39" - "@vue/shared" "3.2.39" - csstype "^2.6.8" + "@vue/compiler-ssr" "3.3.4" + "@vue/shared" "3.3.4" -"@vue/server-renderer@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.39.tgz#4358292d925233b0d8b54cf0513eaece8b2351c5" - integrity sha512-1yn9u2YBQWIgytFMjz4f/t0j43awKytTGVptfd3FtBk76t1pd8mxbek0G/DrnjJhd2V7mSTb5qgnxMYt8Z5iSQ== +"@vue/shared@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780" + integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ== + +"@vueuse/core@^10.1.2": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.2.0.tgz#65cf5222bf0178ff5ddec20a113d7ca6c30481e1" + integrity sha512-aHBnoCteIS3hFu7ZZkVB93SanVDY6t4TIb7XDLxJT/HQdAZz+2RdIEJ8rj5LUoEJr7Damb5+sJmtpCwGez5ozQ== dependencies: - "@vue/compiler-ssr" "3.2.39" - "@vue/shared" "3.2.39" + "@types/web-bluetooth" "^0.0.17" + "@vueuse/metadata" "10.2.0" + "@vueuse/shared" "10.2.0" + vue-demi ">=0.14.5" -"@vue/shared@3.2.39": - version "3.2.39" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.39.tgz#302df167559a1a5156da162d8cc6760cef67f8e3" - integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw== - -"@vue/shared@3.2.40": - version "3.2.40" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.40.tgz#e57799da2a930b975321981fcee3d1e90ed257ae" - integrity sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ== - -"@vueuse/core@^9.13.0": +"@vueuse/core@^9.5.0": version "9.13.0" resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4" integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw== @@ -323,25 +280,22 @@ "@vueuse/shared" "9.13.0" vue-demi "*" -"@vueuse/core@^9.5.0": - version "9.5.0" - resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.5.0.tgz#6726e952e8f92b465457d3bc95deb385aacd9a41" - integrity sha512-6GsWBsJHEb3sYw15mbLrcbslAVY45pkzjJYTKYKCXv88z7srAF0VEW0q+oXKsl58tCbqooplInahXFg8Yo1m4w== - dependencies: - "@types/web-bluetooth" "^0.0.16" - "@vueuse/metadata" "9.5.0" - "@vueuse/shared" "9.5.0" - vue-demi "*" +"@vueuse/metadata@10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.2.0.tgz#a026425972786b83f5db6588d8252df42e07c247" + integrity sha512-IR7Mkq6QSgZ38q/2ZzOt+Zz1OpcEsnwE64WBumDQ+RGKrosFCtUA2zgRrOqDEzPBXrVB+4HhFkwDjQMu0fDBKw== "@vueuse/metadata@9.13.0": version "9.13.0" resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.13.0.tgz#bc25a6cdad1b1a93c36ce30191124da6520539ff" integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ== -"@vueuse/metadata@9.5.0": - version "9.5.0" - resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.5.0.tgz#b01c84230261ddee4d439ae5d9c21343dc5ae565" - integrity sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ== +"@vueuse/shared@10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.2.0.tgz#9309b8e1ed660e24643482065ef0e59d223c3f69" + integrity sha512-dIeA8+g9Av3H5iF4NXR/sft4V6vys76CpZ6hxwj8eMXybXk2WRl3scSsOVi+kQ9SX38COR7AH7WwY83UcuxbSg== + dependencies: + vue-demi ">=0.14.5" "@vueuse/shared@9.13.0": version "9.13.0" @@ -350,13 +304,6 @@ dependencies: vue-demi "*" -"@vueuse/shared@9.5.0": - version "9.5.0" - resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.5.0.tgz#f5306548af0dc9f2b3a0d4da74e62bfdd6211241" - integrity sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA== - dependencies: - vue-demi "*" - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -371,9 +318,9 @@ accepts@~1.3.4: negotiator "0.6.3" ace-builds@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.8.tgz#d14be41d30294a2a12581f0bcfee4b696481ffdd" - integrity sha512-8ZVAxwyCGAxQX8mOp9imSXH0hoSPkGfy8igJy+WO/7axL30saRhKgg1XPACSmxxPA7nfHVwM+ShWXT+vKsNuFg== + version "1.22.1" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.22.1.tgz#3d765d1cfffc79226e372b6353750fd997816154" + integrity sha512-o5RGTPBIiRxguWNors3pT6KuLqj0a2NvNLoqir7/2LLiFm34PJV3BMq4sl9kjPayo4+lmd99m6zAq+XPdhyHQA== acorn@^7.1.1: version "7.4.1" @@ -389,12 +336,7 @@ acorn@^7.1.1: ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^5.0.1: version "5.0.1" @@ -404,7 +346,7 @@ ansi-regex@^5.0.1: ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== ansi-styles@^3.2.1: version "3.2.1" @@ -413,30 +355,33 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" -anymatch@~3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -array-filter@^1.0.0: +array-buffer-byte-length@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== assert-never@^1.2.1: version "1.2.1" @@ -446,7 +391,7 @@ assert-never@^1.2.1: asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== at-least-node@^1.0.0: version "1.0.0" @@ -458,36 +403,22 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@10: - version "10.4.2" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b" - integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ== +autoprefixer@10, autoprefixer@^10.2.5: + version "10.4.14" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" + integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ== dependencies: - browserslist "^4.19.1" - caniuse-lite "^1.0.30001297" - fraction.js "^4.1.2" + browserslist "^4.21.5" + caniuse-lite "^1.0.30001464" + fraction.js "^4.2.0" normalize-range "^0.1.2" picocolors "^1.0.0" postcss-value-parser "^4.2.0" -autoprefixer@^10.2.5: - version "10.2.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.5.tgz#096a0337dbc96c0873526d7fef5de4428d05382d" - integrity sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA== - dependencies: - browserslist "^4.16.3" - caniuse-lite "^1.0.30001196" - colorette "^1.2.2" - fraction.js "^4.0.13" - normalize-range "^0.1.2" - postcss-value-parser "^4.1.0" - -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== awesomplete@^1.1.5: version "1.1.5" @@ -502,9 +433,9 @@ babel-walk@3.0.0-canary-5: "@babel/types" "^7.9.6" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64id@2.0.0, base64id@~2.0.0: version "2.0.0" @@ -516,20 +447,15 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bluebird@^3.1.1: - version "3.5.3" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" - integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== boolbase@^1.0.0: version "1.0.0" @@ -549,44 +475,22 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.16.6, browserslist@^4.20.3: - version "4.21.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" - integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== +browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.21.5: + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== dependencies: - caniuse-lite "^1.0.30001358" - electron-to-chromium "^1.4.164" - node-releases "^2.0.5" - update-browserslist-db "^1.0.0" - -browserslist@^4.16.3: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== - dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" - -browserslist@^4.19.1: - version "4.20.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.0.tgz#35951e3541078c125d36df76056e94738a52ebe9" - integrity sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ== - dependencies: - caniuse-lite "^1.0.30001313" - electron-to-chromium "^1.4.76" - escalade "^3.1.1" - node-releases "^2.0.2" - picocolors "^1.0.0" + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" @@ -606,15 +510,15 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001313, caniuse-lite@^1.0.30001358: - version "1.0.30001431" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz" - integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: + version "1.0.30001506" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001506.tgz#35bd814b310a487970c585430e9e80ee23faf14b" + integrity sha512-6XNEcpygZMCKaufIcgpQNZNf00GEqc7VQON+9Rd0K1bMYo8xhMZRAo5zpbnbMNizi4YNgIDAFrdykWsvY3H4Hw== chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -622,7 +526,7 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.3.2, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -631,14 +535,6 @@ chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - character-parser@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0" @@ -649,31 +545,31 @@ character-parser@^2.2.0: charenc@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== "chokidar@>=3.0.0 <4.0.0": - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" clean-css@^4.1.11: - version "4.2.1" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" - integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== + version "4.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" + integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== dependencies: source-map "~0.6.0" -cliui@^7.0.2, cliui@^7.0.4: +cliui@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== @@ -682,20 +578,19 @@ cliui@^7.0.2, cliui@^7.0.4: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone@^2.1.1, clone@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - -codex-notifier@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/codex-notifier/-/codex-notifier-1.1.2.tgz#a733079185f4c927fa296f1d71eb8753fe080895" - integrity sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg== - -codex-tooltip@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.5.tgz#ba25fd5b3a58ba2f73fd667c2b46987ffd1edef2" - integrity sha512-IuA8LeyLU5p1B+HyhOsqR6oxyFQ11k3i9e9aXw40CrHFTRO2Y1npNBVU3W1SvhKAbUU7R/YikUBdcYFP0RcJag== + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== color-convert@^1.9.0: version "1.9.3" @@ -722,14 +617,9 @@ color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colord@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" - integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== - -colorette@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" - integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== combined-stream@^1.0.6: version "1.0.8" @@ -744,11 +634,11 @@ commander@^7.2.0: integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commander@^9.0.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.3.0.tgz#f619114a5a2d2054e0d9ff1b31d5ccf89255e26b" - integrity sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw== + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -component-emitter@^1.2.0, component-emitter@~1.3.0: +component-emitter@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -756,7 +646,7 @@ component-emitter@^1.2.0, component-emitter@~1.3.0: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== consolidate@^0.15.1: version "0.15.1" @@ -784,21 +674,21 @@ cookiejar@^2.1.0: integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== copy-anything@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" - integrity sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ== + version "2.0.6" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== dependencies: - is-what "^3.12.0" + is-what "^3.14.1" -core-js@^3.22.0: - version "3.22.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.5.tgz#a5f5a58e663d5c0ebb4e680cd7be37536fb2a9cf" - integrity sha512-VP/xYuvJ0MJWRAobcmQ8F2H6Bsn+s7zqAAjFaHGBMc5AQm7zaelhD1LGduFn2EehEcQcU+br6t+fwbpQ5d1ZWA== +core-js@^3.26.1: + version "3.31.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.0.tgz#4471dd33e366c79d8c0977ed2d940821719db344" + integrity sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ== core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cors@~2.8.5: version "2.8.5" @@ -809,24 +699,24 @@ cors@~2.8.5: vary "^1" cropperjs@^1.5.12: - version "1.5.12" - resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.12.tgz#d9c0db2bfb8c0d769d51739e8f916bbc44e10f50" - integrity sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw== + version "1.5.13" + resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8" + integrity sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA== crypt@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -css-declaration-sorter@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz#72ebd995c8f4532ff0036631f7365cce9759df14" - integrity sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og== +css-declaration-sorter@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz#630618adc21724484b3e9505bce812def44000ad" + integrity sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew== css-parse@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" - integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= + integrity sha512-UNIFik2RgSbiTwIW1IsFwXWn6vs+bYdq83LKTSOsx7NJR7WII9dxewkHLltfTLVppoUApHV0118a4RZRI9FLwA== dependencies: css "^2.0.0" @@ -842,13 +732,12 @@ css-select@^4.1.3: nth-check "^2.0.1" css-selector-tokenizer@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d" - integrity sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA== + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" + cssesc "^3.0.0" + fastparse "^1.1.2" css-tree@^1.1.2, css-tree@^1.1.3: version "1.1.3" @@ -873,35 +762,30 @@ css@^2.0.0: source-map-resolve "^0.5.2" urix "^0.1.0" -cssesc@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" - integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.2.12: - version "5.2.12" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz#ebe6596ec7030e62c3eb2b3c09f533c0644a9a97" - integrity sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew== +cssnano-preset-default@^5.2.14: + version "5.2.14" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" + integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== dependencies: - css-declaration-sorter "^6.3.0" + css-declaration-sorter "^6.3.1" cssnano-utils "^3.1.0" postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.2" + postcss-colormin "^5.3.1" + postcss-convert-values "^5.1.3" postcss-discard-comments "^5.1.2" postcss-discard-duplicates "^5.1.0" postcss-discard-empty "^5.1.1" postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.6" - postcss-merge-rules "^5.1.2" + postcss-merge-longhand "^5.1.7" + postcss-merge-rules "^5.1.4" postcss-minify-font-values "^5.1.0" postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.3" + postcss-minify-params "^5.1.4" postcss-minify-selectors "^5.2.1" postcss-normalize-charset "^5.1.0" postcss-normalize-display-values "^5.1.0" @@ -909,11 +793,11 @@ cssnano-preset-default@^5.2.12: postcss-normalize-repeat-style "^5.1.1" postcss-normalize-string "^5.1.0" postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.0" + postcss-normalize-unicode "^5.1.1" postcss-normalize-url "^5.1.0" postcss-normalize-whitespace "^5.1.1" postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.0" + postcss-reduce-initial "^5.1.2" postcss-reduce-transforms "^5.1.0" postcss-svgo "^5.1.0" postcss-unique-selectors "^5.1.1" @@ -924,11 +808,11 @@ cssnano-utils@^3.1.0: integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== cssnano@^5.0.0: - version "5.1.12" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.12.tgz#bcd0b64d6be8692de79332c501daa7ece969816c" - integrity sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ== + version "5.1.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" + integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== dependencies: - cssnano-preset-default "^5.2.12" + cssnano-preset-default "^5.2.14" lilconfig "^2.0.3" yaml "^1.10.2" @@ -939,10 +823,10 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" -csstype@^2.6.8: - version "2.6.21" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" - integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== +csstype@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== custom-event-polyfill@^1.0.7: version "1.0.7" @@ -952,7 +836,7 @@ custom-event-polyfill@^1.0.7: cwd@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567" - integrity sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc= + integrity sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA== dependencies: find-pkg "^0.1.2" fs-exists-sync "^0.1.0" @@ -1026,6 +910,13 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^4.3.2, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -1033,64 +924,69 @@ debug@~3.1.0: dependencies: ms "2.0.0" -debug@~4.3.1, debug@~4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" deep-equal@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0" - integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.1.tgz#c72ab22f3a7d3503a4ca87dde976fe9978816739" + integrity sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ== dependencies: - es-abstract "^1.17.5" - es-get-iterator "^1.1.0" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.0.5" + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.0" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" isarray "^2.0.5" - object-is "^1.1.2" + object-is "^1.1.5" object-keys "^1.1.1" - object.assign "^4.1.0" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" - which-boxed-primitive "^1.0.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" which-collection "^1.0.1" - which-typed-array "^1.1.2" + which-typed-array "^1.1.9" -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: - object-keys "^1.0.12" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== denque@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" - integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== doctypes@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" - integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= + integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ== dom-serializer@^1.0.1: version "1.4.1" @@ -1132,15 +1028,10 @@ editorjs-undo@0.1.6: resolved "https://registry.yarnpkg.com/editorjs-undo/-/editorjs-undo-0.1.6.tgz#823349a1e9a78d8bc68ba8570a2b854063bc804a" integrity sha512-zVHPnBf2mcI8hWT9Eu8H3bGDEcMj4gppXbQjJW11Aa8Kdy2SVBGhM6fS59OUlBsm8iHWLxuoG2NUIfy9Rd30sw== -electron-to-chromium@^1.3.723, electron-to-chromium@^1.4.164: - version "1.4.169" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.169.tgz#d4b8cf9816566c7e9518128f1a97f39de9c7af9d" - integrity sha512-Yb7UFva1sLlAaRyCkgoFF3qWvwZacFDtsGKi44rZsk8vnhL0DMhsUdhI4Dz9CCJQfftncDMGSI3AYiDtg8mD/w== - -electron-to-chromium@^1.4.76: - version "1.4.84" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.84.tgz#2700befbcb49c42c4ee162e137ff392c07658249" - integrity sha512-b+DdcyOiZtLXHdgEG8lncYJdxbdJWJvclPNMg0eLUDcSOSO876WA/pYjdSblUTd7eJdIs4YdIxHWGazx7UPSJw== +electron-to-chromium@^1.4.431: + version "1.4.435" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.435.tgz#761c34300603b9f1234f0b6155870d3002435db6" + integrity sha512-B0CBWVFhvoQCW/XtjRzgrmqcgVWg6RXOEM/dK59+wFV93BFGR6AeNKc4OyhM+T3IhJaOOG8o/V+33Y2mwJWtzw== emoji-regex@^8.0.0: version "8.0.0" @@ -1150,33 +1041,28 @@ emoji-regex@^8.0.0: emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + integrity sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -engine.io-client@~6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.2.tgz#c6c5243167f5943dcd9c4abee1bfc634aa2cbdd0" - integrity sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ== +engine.io-client@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91" + integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" engine.io-parser "~5.0.3" - ws "~8.2.3" + ws "~8.11.0" xmlhttprequest-ssl "~2.0.0" engine.io-parser@~5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0" - integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== + version "5.0.7" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.7.tgz#ed5eae76c71f398284c578ab6deafd3ba7e4e4f6" + integrity sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ== -engine.io@~6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f" - integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA== +engine.io@~6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.4.2.tgz#ffeaf68f69b1364b0286badddf15ff633476473f" + integrity sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg== dependencies: "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" @@ -1187,7 +1073,7 @@ engine.io@~6.2.0: cors "~2.8.5" debug "~4.3.1" engine.io-parser "~5.0.3" - ws "~8.2.3" + ws "~8.11.0" entities@^2.0.0: version "2.2.0" @@ -1195,190 +1081,163 @@ entities@^2.0.0: integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== errno@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-get-iterator@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" +esbuild-android-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" + integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== -esbuild-android-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.29.tgz#c0960c84c9b832bade20831515e89d32549d4769" - integrity sha512-tJuaN33SVZyiHxRaVTo1pwW+rn3qetJX/SRuc/83rrKYtyZG0XfsQ1ao1nEudIt9w37ZSNXR236xEfm2C43sbw== +esbuild-android-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" + integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== -esbuild-android-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.29.tgz#8eceb3abe5abde5489d6a5cbe6a7c1044f71115f" - integrity sha512-D74dCv6yYnMTlofVy1JKiLM5JdVSQd60/rQfJSDP9qvRAI0laPXIG/IXY1RG6jobmFMUfL38PbFnCqyI/6fPXg== +esbuild-darwin-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" + integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== -esbuild-darwin-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz#26f3f14102310ecb8f2d9351c5b7a47a60d2cc8a" - integrity sha512-+CJaRvfTkzs9t+CjGa0Oa28WoXa7EeLutQhxus+fFcu0MHhsBhlmeWHac3Cc/Sf/xPi1b2ccDFfzGYJCfV0RrA== +esbuild-darwin-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" + integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== -esbuild-darwin-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.29.tgz#6d2d89dfd937992649239711ed5b86e51b13bd89" - integrity sha512-5Wgz/+zK+8X2ZW7vIbwoZ613Vfr4A8HmIs1XdzRmdC1kG0n5EG5fvKk/jUxhNlrYPx1gSY7XadQ3l4xAManPSw== +esbuild-freebsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" + integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== -esbuild-freebsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.29.tgz#2cb41a0765d0040f0838280a213c81bbe62d6394" - integrity sha512-VTfS7Bm9QA12JK1YXF8+WyYOfvD7WMpbArtDj6bGJ5Sy5xp01c/q70Arkn596aGcGj0TvQRplaaCIrfBG1Wdtg== +esbuild-freebsd-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" + integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== -esbuild-freebsd-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.29.tgz#e1b79fbb63eaeff324cf05519efa7ff12ce4586a" - integrity sha512-WP5L4ejwLWWvd3Fo2J5mlXvG3zQHaw5N1KxFGnUc4+2ZFZknP0ST63i0IQhpJLgEJwnQpXv2uZlU1iWZjFqEIg== +esbuild-linux-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" + integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== -esbuild-linux-32@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.29.tgz#a4a5a0b165b15081bc3227986e10dd4943edb7d6" - integrity sha512-4myeOvFmQBWdI2U1dEBe2DCSpaZyjdQtmjUY11Zu2eQg4ynqLb8Y5mNjNU9UN063aVsCYYfbs8jbken/PjyidA== +esbuild-linux-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" + integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== -esbuild-linux-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.29.tgz#4c450088c84f8bfd22c51d116f59416864b85481" - integrity sha512-iaEuLhssReAKE7HMwxwFJFn7D/EXEs43fFy5CJeA4DGmU6JHh0qVJD2p/UP46DvUXLRKXsXw0i+kv5TdJ1w5pg== +esbuild-linux-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" + integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== -esbuild-linux-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.29.tgz#d1a23993b26cb1f63f740329b2fc09218e498bd1" - integrity sha512-KYf7s8wDfUy+kjKymW3twyGT14OABjGHRkm9gPJ0z4BuvqljfOOUbq9qT3JYFnZJHOgkr29atT//hcdD0Pi7Mw== +esbuild-linux-arm@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" + integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== -esbuild-linux-arm@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.29.tgz#a7e2fea558525eab812b1fe27d7a2659cd1bb723" - integrity sha512-OXa9D9QL1hwrAnYYAHt/cXAuSCmoSqYfTW/0CEY0LgJNyTxJKtqc5mlwjAZAvgyjmha0auS/sQ0bXfGf2wAokQ== +esbuild-linux-mips64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" + integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== -esbuild-linux-mips64le@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.29.tgz#e708c527f0785574e400828cdbed3d9b17b5ddff" - integrity sha512-05jPtWQMsZ1aMGfHOvnR5KrTvigPbU35BtuItSSWLI2sJu5VrM8Pr9Owym4wPvA4153DFcOJ1EPN/2ujcDt54g== +esbuild-linux-ppc64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" + integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== -esbuild-linux-ppc64le@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.29.tgz#0137d1b38beae36a57176ef45e90740e734df502" - integrity sha512-FYhBqn4Ir9xG+f6B5VIQVbRuM4S6qwy29dDNYFPoxLRnwTEKToIYIUESN1qHyUmIbfO0YB4phG2JDV2JDN9Kgw== +esbuild-linux-riscv64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" + integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== -esbuild-linux-riscv64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.29.tgz#a2f73235347a58029dcacf0fb91c9eb8bebc8abb" - integrity sha512-eqZMqPehkb4nZcffnuOpXJQdGURGd6GXQ4ZsDHSWyIUaA+V4FpMBe+5zMPtXRD2N4BtyzVvnBko6K8IWWr36ew== +esbuild-linux-s390x@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" + integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== -esbuild-linux-s390x@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.29.tgz#0f7310ff1daec463ead9b9e26b7aa083a9f9f1ee" - integrity sha512-o7EYajF1rC/4ho7kpSG3gENVx0o2SsHm7cJ5fvewWB/TEczWU7teDgusGSujxCYcMottE3zqa423VTglNTYhjg== +esbuild-netbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" + integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== -esbuild-netbsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.29.tgz#ba9a0d9cb8aed73b684825126927f75d4fe44ff9" - integrity sha512-/esN6tb6OBSot6+JxgeOZeBk6P8V/WdR3GKBFeFpSqhgw4wx7xWUqPrdx4XNpBVO7X4Ipw9SAqgBrWHlXfddww== - -esbuild-openbsd-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.29.tgz#36dbe2c32d899106791b5f3af73f359213f71b8a" - integrity sha512-jUTdDzhEKrD0pLpjmk0UxwlfNJNg/D50vdwhrVcW/D26Vg0hVbthMfb19PJMatzclbK7cmgk1Nu0eNS+abzoHw== +esbuild-openbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" + integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== esbuild-plugin-vue3@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/esbuild-plugin-vue3/-/esbuild-plugin-vue3-0.3.0.tgz#5317252b8c9e474789ea570083a8b2f3618f99f4" - integrity sha512-b8ypadGLuxJ02cPUXn/2yKhwMsEQ/JZMO3lN871X12ZKigXymZStBCexVI4C7lr7lguxa+IpU94pRQ/BtInMsQ== + version "0.3.2" + resolved "https://registry.yarnpkg.com/esbuild-plugin-vue3/-/esbuild-plugin-vue3-0.3.2.tgz#06c8988832ac1fa89548cc7c00959296913cb800" + integrity sha512-KqZUPlIUS4vJLSexV3q5hgqIlsMWzlPtIuvZ1epZQvw/wJ/4vEPzEC1HQZduoHvUYvtnG703hsP1PsdpjTJ3ug== dependencies: - "@vue/compiler-core" "^3.2.22" - "@vue/compiler-sfc" "^3.2.22" - esbuild "^0.12.22" + "@vue/compiler-core" "^3.2.26" + "@vue/compiler-sfc" "^3.2.26" + esbuild "^0.14.8" + typescript "^4.7.4" -esbuild-sunos-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.29.tgz#e5f857c121441ec63bf9b399a2131409a7d344e5" - integrity sha512-EfhQN/XO+TBHTbkxwsxwA7EfiTHFe+MNDfxcf0nj97moCppD9JHPq48MLtOaDcuvrTYOcrMdJVeqmmeQ7doTcg== +esbuild-sunos-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" + integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== -esbuild-windows-32@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.29.tgz#9c2f1ab071a828f3901d1d79d205982a74bdda6e" - integrity sha512-uoyb0YAJ6uWH4PYuYjfGNjvgLlb5t6b3zIaGmpWPOjgpr1Nb3SJtQiK4YCPGhONgfg2v6DcJgSbOteuKXhwqAw== +esbuild-windows-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" + integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== -esbuild-windows-64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.29.tgz#85fbce7c2492521896451b98d649a7db93e52667" - integrity sha512-X9cW/Wl95QjsH8WUyr3NqbmfdU72jCp71cH3pwPvI4CgBM2IeOUDdbt6oIGljPu2bf5eGDIo8K3Y3vvXCCTd8A== +esbuild-windows-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" + integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== -esbuild-windows-arm64@0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.29.tgz#0aa7a9a1bc43a63350bcf574d94b639176f065b5" - integrity sha512-+O/PI+68fbUZPpl3eXhqGHTGK7DjLcexNnyJqtLZXOFwoAjaXlS5UBCvVcR3o2va+AqZTj8o6URaz8D2K+yfQQ== +esbuild-windows-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" + integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== -esbuild@^0.12.22: - version "0.12.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d" - integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g== - -esbuild@^0.14.29: - version "0.14.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.29.tgz#24ad09c0674cbcb4aa2fe761485524eb1f6b1419" - integrity sha512-SQS8cO8xFEqevYlrHt6exIhK853Me4nZ4aMW6ieysInLa0FMAL+AKs87HYNRtR2YWRcEIqoXAHh+Ytt5/66qpg== +esbuild@^0.14.29, esbuild@^0.14.8: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" + integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== optionalDependencies: - esbuild-android-64 "0.14.29" - esbuild-android-arm64 "0.14.29" - esbuild-darwin-64 "0.14.29" - esbuild-darwin-arm64 "0.14.29" - esbuild-freebsd-64 "0.14.29" - esbuild-freebsd-arm64 "0.14.29" - esbuild-linux-32 "0.14.29" - esbuild-linux-64 "0.14.29" - esbuild-linux-arm "0.14.29" - esbuild-linux-arm64 "0.14.29" - esbuild-linux-mips64le "0.14.29" - esbuild-linux-ppc64le "0.14.29" - esbuild-linux-riscv64 "0.14.29" - esbuild-linux-s390x "0.14.29" - esbuild-netbsd-64 "0.14.29" - esbuild-openbsd-64 "0.14.29" - esbuild-sunos-64 "0.14.29" - esbuild-windows-32 "0.14.29" - esbuild-windows-64 "0.14.29" - esbuild-windows-arm64 "0.14.29" + "@esbuild/linux-loong64" "0.14.54" + esbuild-android-64 "0.14.54" + esbuild-android-arm64 "0.14.54" + esbuild-darwin-64 "0.14.54" + esbuild-darwin-arm64 "0.14.54" + esbuild-freebsd-64 "0.14.54" + esbuild-freebsd-arm64 "0.14.54" + esbuild-linux-32 "0.14.54" + esbuild-linux-64 "0.14.54" + esbuild-linux-arm "0.14.54" + esbuild-linux-arm64 "0.14.54" + esbuild-linux-mips64le "0.14.54" + esbuild-linux-ppc64le "0.14.54" + esbuild-linux-riscv64 "0.14.54" + esbuild-linux-s390x "0.14.54" + esbuild-netbsd-64 "0.14.54" + esbuild-openbsd-64 "0.14.54" + esbuild-sunos-64 "0.14.54" + esbuild-windows-32 "0.14.54" + esbuild-windows-64 "0.14.54" + esbuild-windows-arm64 "0.14.54" escalade@^3.1.1: version "3.1.1" @@ -1398,31 +1257,31 @@ estree-walker@^2.0.2: eventemitter3@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" - integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== eventemitter3@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" - integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== expand-tilde@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= + integrity sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q== dependencies: os-homedir "^1.0.1" expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== dependencies: homedir-polyfill "^1.0.1" extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== dependencies: is-extendable "^0.1.0" @@ -1434,7 +1293,7 @@ extend@^3.0.0, extend@^3.0.2: fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== fast-diff@1.1.2: version "1.1.2" @@ -1447,26 +1306,25 @@ fast-diff@1.2.0: integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== fast-glob@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" -fastparse@^1.1.1: +fastparse@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" @@ -1480,7 +1338,7 @@ fill-range@^7.0.1: find-file-up@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0" - integrity sha1-z2gJG8+fMApA2kEbN9pczlovvqA= + integrity sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A== dependencies: fs-exists-sync "^0.1.0" resolve-dir "^0.1.0" @@ -1488,7 +1346,7 @@ find-file-up@^0.1.2: find-pkg@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557" - integrity sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc= + integrity sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw== dependencies: find-file-up "^0.1.2" @@ -1500,31 +1358,28 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" form-data@^2.3.1: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== dependencies: asynckit "^0.4.0" combined-stream "^1.0.6" mime-types "^2.1.12" formidable@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" - integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== + version "1.2.6" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" + integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== -fraction.js@^4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" - integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== - -fraction.js@^4.1.2: +fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== @@ -1544,14 +1399,14 @@ frappe-datatable@^1.17.2: sortablejs "^1.7.0" frappe-gantt@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/frappe-gantt/-/frappe-gantt-0.6.0.tgz#fece2fdecc0b8d6065d3c420d9681b719b827047" - integrity sha512-/P7s9edP6EoLD09EX2HPqsy1u0v4zYcurMy4ByExArvj7t0UDJlpxp88nISFtMkQe+aofaO9dR0fd61ZwX0O6g== + version "0.6.1" + resolved "https://registry.yarnpkg.com/frappe-gantt/-/frappe-gantt-0.6.1.tgz#57ae0b5f024101fc7cd5ba92f605de97dba9c9a1" + integrity sha512-1cSU9vLbwypjzaxnCfnEE03Xr3HlAV2S8dRtjxw62o+amkx1A8bBIFd2jp84mcDdTCM77Ij4LzZBslAKZB8oMg== fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg== fs-extra@^9.1.0: version "9.1.0" @@ -1566,9 +1421,9 @@ fs-extra@^9.1.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.1: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -1578,35 +1433,41 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + generic-names@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917" - integrity sha1-LXhqEhruUIh2eWk56OO/+DbCCRc= + integrity sha512-b6OHfQuKasIKM9b6YPkX+KUj/TLBTx3B/1aT1T5F12FEuEqyFMdr59OMS53aoaSw8eVtapdqieX6lbg5opaOhA== dependencies: loader-utils "^0.2.16" -generic-names@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-2.0.1.tgz#f8a378ead2ccaa7a34f0317b05554832ae41b872" - integrity sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ== +generic-names@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-4.0.0.tgz#0bd8a2fd23fe8ea16cbd0a279acd69c06933d9a3" + integrity sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A== dependencies: - loader-utils "^1.1.0" + loader-utils "^3.2.0" get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" + has-proto "^1.0.1" has-symbols "^1.0.3" -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1614,21 +1475,21 @@ glob-parent@^5.1.0, glob-parent@~5.1.0: is-glob "^4.0.1" glob@^7.1.3, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" global-modules@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= + integrity sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA== dependencies: global-prefix "^0.1.4" is-windows "^0.2.0" @@ -1636,60 +1497,71 @@ global-modules@^0.2.3: global-prefix@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= + integrity sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw== dependencies: homedir-polyfill "^1.0.0" ini "^1.3.4" is-windows "^0.2.0" which "^1.2.12" -graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" -graceful-fs@^4.2.0: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + integrity sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA== has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.0: +has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" -has-symbols@^1.0.1: +has-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -has-symbols@^1.0.3: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1700,12 +1572,12 @@ has@^1.0.3: hash-sum@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" - integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= + integrity sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA== highlight.js@^10.4.1: - version "10.4.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0" - integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg== + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" @@ -1714,32 +1586,27 @@ homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -html-janitor@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/html-janitor/-/html-janitor-2.0.4.tgz#ae5a115cdf3331cd5501edd7b5471b18ea44cdbb" - integrity sha512-92J5h9jNZRk30PMHapjHEJfkrBWKCOy0bq3oW2pBungky6lzYSoboBGPMvxl1XRKB2q+kniQmsLsPbdpY7RM2g== - html5-qrcode@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.0.11.tgz#2cc5f63e767be53dd6c6d56b6c4f180a12aa8075" - integrity sha512-cCrVOK2yJGPGSTjchqRhkBJIrxvojEwF/pDKLNxmTH1wiAsVc61ZnIqyApIVyNfn5dKRFax70Qpr7pZwbUNiUw== + version "2.3.8" + resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.3.8.tgz#0b0cdf7a9926cfd4be530e13a51db47592adfa0d" + integrity sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ== hyperlist@^1.0.0-beta: - version "1.0.0-beta" - resolved "https://registry.yarnpkg.com/hyperlist/-/hyperlist-1.0.0-beta.tgz#2cbbd77f4498c2ecc290b7f3c6745b3f0288247e" - integrity sha1-LLvXf0SYwuzCkLfzxnRbPwKIJH4= + version "1.0.0" + resolved "https://registry.yarnpkg.com/hyperlist/-/hyperlist-1.0.0.tgz#b211d41832643ca969e3087a86c912f93f82e5bb" + integrity sha512-1qAjO29EJW/mPyqY+9wFjruD2YWur1dPsPYmt9RvMX6P+8Cr2UmT75MCWjjK+1/4Jxc3sm/G3Kr8DzGgEDRG+Q== -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - safer-buffer ">= 2.1.2 < 3" + safer-buffer ">= 2.1.2 < 3.0.0" icss-replace-symbols@^1.0.2, icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" - integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + integrity sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg== icss-utils@^5.0.0: version "5.1.0" @@ -1749,22 +1616,22 @@ icss-utils@^5.0.0: image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== immutable@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" - integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw== + version "4.3.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" + integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -1779,15 +1646,38 @@ ini@^1.3.4: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== +internal-slot@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== +is-arguments@^1.0.4, is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" @@ -1796,49 +1686,37 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-callable@^1.1.5: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-core-module@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== +is-core-module@^2.11.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== dependencies: has "^1.0.3" -is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: - has "^1.0.3" - -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= - -is-date-object@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + has-tostringtag "^1.0.0" is-expression@^4.0.0: version "4.0.0" @@ -1851,12 +1729,12 @@ is-expression@^4.0.0: is-extendable@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -1864,21 +1742,23 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" @@ -1890,47 +1770,50 @@ is-promise@^2.0.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" - integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== +is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.1" + has-tostringtag "^1.0.0" -is-regex@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== - dependencies: - has-symbols "^1.0.1" +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-symbol@^1.0.2: +is-shared-array-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" - integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: - has-symbols "^1.0.0" + call-bind "^1.0.2" -is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" - foreach "^2.0.5" - has-symbols "^1.0.1" + has-tostringtag "^1.0.0" + +is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" is-weakmap@^2.0.1: version "2.0.1" @@ -1938,11 +1821,14 @@ is-weakmap@^2.0.1: integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" -is-what@^3.12.0: +is-what@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== @@ -1950,7 +1836,7 @@ is-what@^3.12.0: is-windows@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw= + integrity sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q== isarray@^2.0.5: version "2.0.5" @@ -1960,12 +1846,12 @@ isarray@^2.0.5: isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== jquery@3.6.0: version "3.6.0" @@ -1973,14 +1859,14 @@ jquery@3.6.0: integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== "jquery@>=2.0.0 <4.0.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" - integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ== + version "3.7.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612" + integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ== js-base64@^2.1.9: - version "2.5.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" - integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== + version "2.6.4" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== js-sha256@^0.9.0: version "0.9.0" @@ -1993,26 +1879,14 @@ js-stringify@^1.0.2: integrity sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g== jsbarcode@^3.9.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/jsbarcode/-/jsbarcode-3.11.0.tgz#20623e008b101ef45d0cce9c8022cdf49be28547" - integrity sha512-/ozCd7wsa+VIHo9sUc03HneVEQrH7cVWfJolUT/WOW1m8mJ2e3iYZje6C9X3LFHdczlesqFHRpxLtbVsNtjyow== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + version "3.11.5" + resolved "https://registry.yarnpkg.com/jsbarcode/-/jsbarcode-3.11.5.tgz#390b3efd0271f35b9d68c7b8af6e972445969014" + integrity sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA== json5@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" + integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== jsonfile@^6.0.1: version "6.1.0" @@ -2026,7 +1900,7 @@ jsonfile@^6.0.1: jstransformer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" - integrity sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM= + integrity sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A== dependencies: is-promise "^2.0.0" promise "^7.0.1" @@ -2034,22 +1908,22 @@ jstransformer@1.0.0: kind-of@^3.0.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== dependencies: is-buffer "^1.1.5" launch-editor@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.4.0.tgz#2849de434e4684da2298db48c4e5b8ca64291173" - integrity sha512-mZ0BHeSn/ohL+Ib+b+JnxC59vcNz6v5IR9d0CuM8f0x8ni8oK3IIG6G0vMkpxc0gFsmvINkztGOHiWTaX4BmAg== + version "2.6.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7" + integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ== dependencies: picocolors "^1.0.0" - shell-quote "^1.6.1" + shell-quote "^1.7.3" lazy-cache@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ= + integrity sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA== dependencies: set-getter "^0.1.0" @@ -2070,52 +1944,48 @@ less@^3.9.0: source-map "~0.6.0" less@^4.x: - version "4.1.1" - resolved "https://registry.yarnpkg.com/less/-/less-4.1.1.tgz#15bf253a9939791dc690888c3ff424f3e6c7edba" - integrity sha512-w09o8tZFPThBscl5d0Ggp3RcrKIouBoQscnOMgFH3n5V3kN/CXGHNfCkRPtxJk6nKryDXaV9aHLK55RXuH4sAw== + version "4.1.3" + resolved "https://registry.yarnpkg.com/less/-/less-4.1.3.tgz#175be9ddcbf9b250173e0a00b4d6920a5b770246" + integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA== dependencies: copy-anything "^2.0.1" parse-node-version "^1.0.1" - tslib "^1.10.0" + tslib "^2.3.0" optionalDependencies: errno "^0.1.1" graceful-fs "^4.1.2" image-size "~0.5.0" make-dir "^2.1.0" mime "^1.4.1" - needle "^2.5.2" + needle "^3.1.0" source-map "~0.6.0" lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== dependencies: immediate "~3.0.5" lilconfig@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" - integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== loader-utils@^0.2.16: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= + integrity sha512-tiv66G0SmiOx+pLWMtGEkfSEejxvb6N6uRrQjfWJIT79W9GMpgKeCAmm9aVBKtd4WEgntciI8CsGqjpDoCWJug== dependencies: big.js "^3.1.3" emojis-list "^2.0.0" json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" +loader-utils@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== loadjs@^4.2.0: version "4.2.0" @@ -2123,9 +1993,9 @@ loadjs@^4.2.0: integrity sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA== localforage@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" - integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== dependencies: lie "3.1.1" @@ -2139,7 +2009,7 @@ locate-path@^6.0.0: lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== lodash.memoize@^4.1.2: version "4.1.2" @@ -2164,12 +2034,12 @@ lru-cache@^4.1.2: pseudomap "^1.0.2" yallist "^2.1.2" -magic-string@^0.25.7: - version "0.25.9" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" - integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== +magic-string@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" + integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== dependencies: - sourcemap-codec "^1.4.8" + "@jridgewell/sourcemap-codec" "^1.4.13" make-dir@^2.1.0: version "2.1.0" @@ -2208,34 +2078,22 @@ merge2@^1.3.0: methods@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + braces "^3.0.2" + picomatch "^2.3.1" mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2247,31 +2105,26 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mkdirp@^1.0.4, mkdirp@~1.0.4: +mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== moment-timezone@^0.5.35: - version "0.5.35" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.35.tgz#6fa2631bdbe8ff04f6b8753f7199516be6dc9839" - integrity sha512-cY/pBOEXepQvlgli06ttCTKcIf8cD1nmNwOKQQAdHBqYApQSpAqotBMX0RJZNgMp6i0PlZuf1mFtnlyEkwyvFw== + version "0.5.43" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790" + integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== dependencies: - moment ">= 2.9.0" + moment "^2.29.4" -"moment@>= 2.9.0", moment@^2.29.4: +moment@^2.29.4: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -2279,40 +2132,35 @@ moment-timezone@^0.5.35: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanoid@^3.1.22, nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.1.23: - version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" - integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== - -nanoid@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" - integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== native-request@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb" - integrity sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag== + version "1.1.0" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.1.0.tgz#acdb30fe2eefa3e1bc8c54b3a6852e9c5c0d3cb0" + integrity sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw== -needle@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" - integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== +needle@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44" + integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== dependencies: debug "^3.2.6" - iconv-lite "^0.4.4" + iconv-lite "^0.6.3" sax "^1.2.4" negotiator@0.6.3: @@ -2320,25 +2168,15 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -node-releases@^1.1.71: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== - -node-releases@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== - -node-releases@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" - integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== +node-releases@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" + integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== dependencies: abbrev "1" @@ -2350,7 +2188,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== normalize-url@^4.5.0: version "4.5.1" @@ -2372,57 +2210,47 @@ nth-check@^2.0.1: object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== -object-is@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== +object-is@^1.0.1, object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -object-keys@^1.0.11, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-keys@^1.0.12: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" - integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== p-limit@^3.0.2: version "3.1.0" @@ -2456,7 +2284,7 @@ parse-node-version@^1.0.1: parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== path-exists@^4.0.0: version "4.0.0" @@ -2466,27 +2294,27 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-parse@^1.0.6, path-parse@^1.0.7: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== - -picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^4.0.1: version "4.0.1" @@ -2494,19 +2322,19 @@ pify@^4.0.1: integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== pinia@^2.0.23: - version "2.0.23" - resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.23.tgz#570f5f82160b656b412602789683faa95502d227" - integrity sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q== + version "2.1.4" + resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.1.4.tgz#a642adfe6208e10c36d3dc16184a91064788142a" + integrity sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ== dependencies: - "@vue/devtools-api" "^6.4.4" - vue-demi "*" + "@vue/devtools-api" "^6.5.0" + vue-demi ">=0.14.5" -plyr@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/plyr/-/plyr-3.7.2.tgz#183d2397e7401a577700c8319fe133692b4aff54" - integrity sha512-I0ZC/OI4oJ0iWG9s2rrnO0YFO6aLyrPiQBq9kum0FqITYljwTPBbYL3TZZu8UJQJUq7tUWN18Q7ACwNCkGKABQ== +plyr@^3.7.8: + version "3.7.8" + resolved "https://registry.yarnpkg.com/plyr/-/plyr-3.7.8.tgz#b79bccc23687705b5d9a283b2a88c124bf7471ed" + integrity sha512-yG/EHDobwbB/uP+4Bm6eUpJ93f8xxHjjk2dYcD1Oqpe1EcuQl5tzzw9Oq+uVAzd2lkM11qZfydSiyIpiB8pgdA== dependencies: - core-js "^3.22.0" + core-js "^3.26.1" custom-event-polyfill "^1.0.7" loadjs "^4.2.0" rangetouch "^2.0.1" @@ -2525,22 +2353,22 @@ postcss-calc@^8.2.3: postcss-selector-parser "^6.0.9" postcss-value-parser "^4.2.0" -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== +postcss-colormin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" + integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab" - integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g== +postcss-convert-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" + integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== dependencies: - browserslist "^4.20.3" + browserslist "^4.21.4" postcss-value-parser "^4.2.0" postcss-discard-comments@^5.1.2: @@ -2563,20 +2391,20 @@ postcss-discard-overridden@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-merge-longhand@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz#f378a8a7e55766b7b644f48e5d8c789ed7ed51ce" - integrity sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw== +postcss-merge-longhand@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" + integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== dependencies: postcss-value-parser "^4.2.0" - stylehacks "^5.1.0" + stylehacks "^5.1.1" -postcss-merge-rules@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5" - integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ== +postcss-merge-rules@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" + integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" @@ -2597,12 +2425,12 @@ postcss-minify-gradients@^5.1.1: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-minify-params@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9" - integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg== +postcss-minify-params@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" + integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" @@ -2621,15 +2449,15 @@ postcss-modules-extract-imports@^3.0.0: postcss-modules-local-by-default@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + integrity sha512-X4cquUPIaAd86raVrBwO8fwRfkIdbwFu7CTfEOjiZQHVQwlHRSkTgH5NLDmMm5+1hQO8u6dZ+TOOJDbay1hYpA== dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" @@ -2638,7 +2466,7 @@ postcss-modules-local-by-default@^4.0.0: postcss-modules-scope@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + integrity sha512-LTYwnA4C1He1BKZXIx1CYiHixdSe9LWYVKadq9lK5aCCMkoOkFyZ7aigt+srfjlRplJY3gIol6KUNefdMQJdlw== dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" @@ -2653,7 +2481,7 @@ postcss-modules-scope@^3.0.0: postcss-modules-sync@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-sync/-/postcss-modules-sync-1.0.0.tgz#619a719cf78dd16a4834135140b324cf77334be1" - integrity sha1-YZpxnPeN0WpINBNRQLMkz3czS+E= + integrity sha512-kIDk2NYmxHshqUbjtFf1WdBij08IsvRdgDT0nOGWhvwkr8/z1piLSzxVrPt56J4DU6ON986h2H+5xcBnFhT8UQ== dependencies: generic-names "^1.0.2" icss-replace-symbols "^1.0.2" @@ -2670,11 +2498,11 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-modules@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-4.0.0.tgz#2bc7f276ab88f3f1b0fadf6cbd7772d43b5f3b9b" - integrity sha512-ghS/ovDzDqARm4Zj6L2ntadjyQMoyJmi0JkLlYtH2QFLrvNlxH5OAVRPWPeKilB0pY7SbuhO173KOWkPAxRJcw== + version "4.3.1" + resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-4.3.1.tgz#517c06c09eab07d133ae0effca2c510abba18048" + integrity sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q== dependencies: - generic-names "^2.0.1" + generic-names "^4.0.0" icss-replace-symbols "^1.1.0" lodash.camelcase "^4.3.0" postcss-modules-extract-imports "^3.0.0" @@ -2723,12 +2551,12 @@ postcss-normalize-timing-functions@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" - integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== +postcss-normalize-unicode@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" + integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" postcss-value-parser "^4.2.0" postcss-normalize-url@^5.1.0: @@ -2754,12 +2582,12 @@ postcss-ordered-values@^5.1.3: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" - integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== +postcss-reduce-initial@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" + integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" postcss-reduce-transforms@^5.1.0: @@ -2770,9 +2598,9 @@ postcss-reduce-transforms@^5.1.0: postcss-value-parser "^4.2.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.10" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" - integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -2797,12 +2625,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8: - version "8.4.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032" - integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ== +postcss@8, postcss@^8.1.10, postcss@^8.3.11: + version "8.4.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" + integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -2825,37 +2653,18 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.14: - version "7.0.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" - integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== +postcss@^7.0.36: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== dependencies: - chalk "^2.4.2" + picocolors "^0.2.1" source-map "^0.6.1" - supports-color "^6.1.0" -postcss@^8.1.10: - version "8.4.17" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.17.tgz#f87863ec7cd353f81f7ab2dec5d67d861bbb1be5" - integrity sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.2.4: - version "8.3.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" - integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - -prettier@^1.18.2: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +"prettier@^1.18.2 || ^2.0.0": + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== process-nextick-args@~2.0.0: version "2.0.1" @@ -2872,12 +2681,12 @@ promise@^7.0.1: prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== pug-attrs@^3.0.0: version "3.0.0" @@ -2983,9 +2792,9 @@ pug@^3.0.1: pug-strip-comments "^2.0.0" qs@^6.5.1: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== dependencies: side-channel "^1.0.4" @@ -3054,9 +2863,9 @@ quill@^1.2.2: quill-delta "^3.6.2" qz-tray@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/qz-tray/-/qz-tray-2.0.8.tgz#5e15d102cf3a11a31ddb332891c7f8a6af8f6ad5" - integrity sha512-lncGYzz7/sTORZuC1S3ukNlMPCMOmsHWNvJF4FjMCZ2+0UV3txi6kgPd754B7kDFKm0J587sIODgxIlFY7qU4w== + version "2.2.2" + resolved "https://registry.yarnpkg.com/qz-tray/-/qz-tray-2.2.2.tgz#1ee812502afaa5132e4ecb4bccb8c42992ac53f4" + integrity sha512-v3oMIIu1UIkcKbEMH6bp/Ey0Ww9YsF2Cl5aJbKNckAh6Mo69rShi1N1nF+KjB4FURNxcD+8fokfgYL++RCOdPA== rangetouch@^2.0.1: version "2.0.1" @@ -3066,12 +2875,12 @@ rangetouch@^2.0.1: raw-loader@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" - integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= + integrity sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q== readable-stream@^2.3.5: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3081,10 +2890,10 @@ readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -3096,68 +2905,43 @@ redis-commands@^1.7.0: redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== dependencies: redis-errors "^1.0.0" redis@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.1.tgz#a44bee7c072dcf685e139048d6a1a4d3b00f5d01" - integrity sha512-QhkKhOuzhogR1NDJfBD34TQJz2ZJwDhhIC6ZmvpftlmfYShHHQXjjNspAJ+Z2HH5NwSBVYBVganbiZ8bgFMHjg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" + integrity sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw== dependencies: denque "^1.5.0" redis-commands "^1.7.0" redis-errors "^1.2.0" redis-parser "^3.0.0" -regenerate@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== - -regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexpu-core@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" - integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + functions-have-names "^1.2.3" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-dir@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4= + integrity sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA== dependencies: expand-tilde "^1.2.2" global-modules "^0.2.3" @@ -3165,7 +2949,7 @@ resolve-dir@^0.1.0: resolve-file@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/resolve-file/-/resolve-file-0.3.0.tgz#11e1fb464566d3a7c500cb7e9481e8f0b00a14ef" - integrity sha1-EeH7RkVm06fFAMt+lIHo8LAKFO8= + integrity sha512-9RXicAgDvLD272hZ3HwJv9MJUGxCBRRwwSBRdOGWgcO03MtC9UTGC6XG1VbS4T5MvDrb+tVZx2RhZ90uk3uczg== dependencies: cwd "^0.10.0" expand-tilde "^2.0.2" @@ -3178,25 +2962,17 @@ resolve-file@^0.3.0: resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== -resolve@^1.15.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.15.1, resolve@^1.2.0: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.11.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.2.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3210,14 +2986,13 @@ rimraf@^3.0.0: glob "^7.1.3" rtlcss@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.2.1.tgz#654e55ea2f46991f9738d952ba77ba0aa94a670d" - integrity sha512-S9bh35JXwPIhfun7nFu/HjlNrwELL5nvTJqA1suLfbnqY/mauIL5sBkrJNHziVppX9PA2rJ7NV82+RtzB71mJA== + version "3.5.0" + resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" + integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== dependencies: - chalk "^4.1.0" find-up "^5.0.0" - mkdirp "^1.0.4" - postcss "^8.2.4" + picocolors "^1.0.0" + postcss "^8.3.11" strip-json-comments "^3.1.1" run-parallel@^1.1.9: @@ -3232,22 +3007,15 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.2: +"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass@^1.18.0, sass@^1.x: - version "1.32.10" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.10.tgz#d40da4e20031b450359ee1c7e69bc8cc89569241" - integrity sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ== - dependencies: - chokidar ">=3.0.0 <4.0.0" - -sass@^1.53.0: - version "1.53.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.53.0.tgz#eab73a7baac045cc57ddc1d1ff501ad2659952eb" - integrity sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ== +sass@^1.18.0, sass@^1.53.0, sass@^1.x: + version "1.63.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.5.tgz#6e1900b12576e3e74a8ab0a9d8607cacbe584ef1" + integrity sha512-Q6c5gs482oezdAp+0fWF9cRisvpy7yfYb64knID0OE8AnMgtkluRPfpGMFjeD4/+M4+6QpJZCU6JRSxbjiktkg== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -3275,10 +3043,10 @@ set-getter@^0.1.0: dependencies: to-object-path "^0.3.0" -shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== +shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== showdown@^2.1.0: version "2.1.0" @@ -3287,7 +3055,7 @@ showdown@^2.1.0: dependencies: commander "^9.0.0" -side-channel@^1.0.2, side-channel@^1.0.4: +side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== @@ -3296,49 +3064,42 @@ side-channel@^1.0.2, side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -socket.io-adapter@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" - integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" socket.io-client@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.1.tgz#cab8da71976a300d3090414e28c2203a47884d84" - integrity sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA== + version "4.6.2" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.6.2.tgz#2bfde952e74625d54e622718a7cb1d591ee62fd6" + integrity sha512-OwWrMbbA8wSqhBAR0yoPK6EdQLERQAYjXb3A0zLpgxfM1ZGLKoxHx8gVmCHA6pcclRX5oA/zvQf7bghAS11jRA== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.2" - engine.io-client "~6.2.1" - socket.io-parser "~4.2.0" + engine.io-client "~6.4.0" + socket.io-parser "~4.2.4" -socket.io-parser@~4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.5.tgz#cb404382c32324cc962f27f3a44058cf6e0552df" - integrity sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig== - dependencies: - "@types/component-emitter" "^1.2.10" - component-emitter "~1.3.0" - debug "~4.3.1" - -socket.io-parser@~4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.0.tgz#3f01e5bc525d94aa52a97ed5cbc12e229bbc4d6b" - integrity sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" socket.io@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.1.tgz#aa7e73f8a6ce20ee3c54b2446d321bbb6b1a9029" - integrity sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ== + version "4.6.2" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.6.2.tgz#d597db077d4df9cbbdfaa7a9ed8ccc3d49439786" + integrity sha512-Vp+lSks5k0dewYTfwgPT9UeGGd+ht7sCpB7p0e83VgO4X/AHYWhXITMrNk/pg8syY2bpx23ptClCQuHhqi2BgQ== dependencies: accepts "~1.3.4" base64id "~2.0.0" debug "~4.3.2" - engine.io "~6.2.0" - socket.io-adapter "~2.4.0" - socket.io-parser "~4.0.4" + engine.io "~6.4.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" sortablejs@1.14.0: version "1.14.0" @@ -3351,20 +3112,15 @@ sortablejs@1.9.0: integrity sha512-Ot6bYJ6PoqPmpsqQYXjn1+RKrY2NWQvQt/o4jfd/UYwVWndyO5EPO8YHbnm5HIykf8ENsm4JUrdAvolPT86yYA== sortablejs@^1.7.0: - version "1.8.3" - resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.8.3.tgz#5ae908ef96300966e95440a143340f5dd565a0df" - integrity sha512-AftvD4hdKcR5QlGi7L/JST506zGNGrysE8/QohDpwKXJarHWqCt+TUlrtoMk/wkECB607Q019/OZlJViyWiD6A== + version "1.15.0" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a" + integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w== "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== - source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -3377,9 +3133,9 @@ source-map-resolve@^0.5.2: urix "^0.1.0" source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@0.6.*, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" @@ -3389,47 +3145,31 @@ source-map@0.6.*, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + string-hash@^1.1.0, string-hash@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" - integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= + integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== -string-width@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3438,40 +3178,6 @@ string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -3482,18 +3188,11 @@ string_decoder@~1.1.1: strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3505,15 +3204,15 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stylehacks@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" - integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== +stylehacks@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" + integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" postcss-selector-parser "^6.0.4" -stylus@^0.54.5, stylus@^0.x: +stylus@^0.54.5: version "0.54.8" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== @@ -3527,6 +3226,17 @@ stylus@^0.54.5, stylus@^0.x: semver "^6.3.0" source-map "^0.7.3" +stylus@^0.x: + version "0.59.0" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.59.0.tgz#a344d5932787142a141946536d6e24e6a6be7aa6" + integrity sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg== + dependencies: + "@adobe/css-tools" "^4.0.1" + debug "^4.3.2" + glob "^7.1.6" + sax "~1.2.4" + source-map "^0.7.3" + superagent@^3.8.2: version "3.8.3" resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" @@ -3546,12 +3256,12 @@ superagent@^3.8.2: supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + integrity sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A== dependencies: has-flag "^1.0.0" @@ -3562,20 +3272,6 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -3609,7 +3305,7 @@ to-fast-properties@^2.0.0: to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== dependencies: kind-of "^3.0.2" @@ -3633,19 +3329,29 @@ touch@^3.1.0: nopt "~1.0.10" tslib@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.3.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + +typescript@^4.7.4: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -update-browserslist-db@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" - integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -3653,7 +3359,7 @@ update-browserslist-db@^1.0.0: urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== url-polyfill@^1.1.12: version "1.1.12" @@ -3663,7 +3369,7 @@ url-polyfill@^1.1.12: util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== vary@^1: version "1.1.2" @@ -3675,33 +3381,33 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -vue-demi@*: - version "0.13.11" - resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99" - integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A== +vue-demi@*, vue-demi@>=0.14.5: + version "0.14.5" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9" + integrity sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA== vue-router@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.5.tgz#256f597e3f5a281a23352a6193aa6e342c8d9f9a" - integrity sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ== + version "4.2.2" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.2.tgz#b0097b66d89ca81c0986be03da244c7b32a4fd81" + integrity sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ== dependencies: - "@vue/devtools-api" "^6.1.4" + "@vue/devtools-api" "^6.5.0" vue-template-es2015-compiler@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== -vue@3.2.39: - version "3.2.39" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.39.tgz#de071c56c4c32c41cbd54e55f11404295c0dd62d" - integrity sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g== +vue@^3.3.0: + version "3.3.4" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6" + integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw== dependencies: - "@vue/compiler-dom" "3.2.39" - "@vue/compiler-sfc" "3.2.39" - "@vue/runtime-dom" "3.2.39" - "@vue/server-renderer" "3.2.39" - "@vue/shared" "3.2.39" + "@vue/compiler-dom" "3.3.4" + "@vue/compiler-sfc" "3.3.4" + "@vue/runtime-dom" "3.3.4" + "@vue/server-renderer" "3.3.4" + "@vue/shared" "3.3.4" vuedraggable@^4.1.0: version "4.1.0" @@ -3717,16 +3423,16 @@ vuex@4.0.2: dependencies: "@vue/devtools-api" "^6.0.0-beta.11" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-collection@^1.0.1: version "1.0.1" @@ -3738,17 +3444,17 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== dependencies: - available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" which@^1.2.12: version "1.3.1" @@ -3779,12 +3485,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@~8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== xmlhttprequest-ssl@~2.0.0: version "2.0.0" @@ -3799,30 +3505,30 @@ y18n@^5.0.5: yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^21.0.0: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.5.1: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: - cliui "^7.0.2" + cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^21.0.0" + yargs-parser "^21.1.1" yocto-queue@^0.1.0: version "0.1.0"
- + Add / Remove Fields + ${__("+ Add / Remove Fields")}