diff --git a/.circleci/config.yml b/.circleci/config.yml index 62b5e0a3aa..6736ff3860 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -76,10 +76,10 @@ run_save_node_bin: &run_save_node_bin fi # This environment is set to every job (and the initial build). -build_machine_environment: &build_machine_environment - # Specify that we want an actual machine (ala Circle 1.0), not a Docker image. +build_machine_environment: + &build_machine_environment # Specify that we want an actual machine (ala Circle 1.0), not a Docker image. docker: - - image: meteor/circleci:2024.09.11-android-34-node-20 + - image: meteor/circleci:2025.07.8-android-35-node-22 resource_class: large environment: # This multiplier scales the waitSecs for selftests. @@ -104,8 +104,8 @@ build_machine_environment: &build_machine_environment # These will be evaled before each command. PRE_TEST_COMMANDS: |- - ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit. - ulimit -a # Display all ulimit settings for transparency. + ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit. + ulimit -a # Display all ulimit settings for transparency. # This is only to make Meteor self-test not remind us that we can set # this argument for self-tests. @@ -115,6 +115,9 @@ build_machine_environment: &build_machine_environment NUM_GROUPS: 12 RUNNING_AVG_LENGTH: 6 + # Force modern bundler test + METEOR_MODERN: true + jobs: Get Ready: <<: *build_machine_environment @@ -178,7 +181,7 @@ jobs: command: | eval $PRE_TEST_COMMANDS; cd dev_bundle/lib - ../../meteor npm install @types/node@20.10.5 --save-dev + ../../meteor npm install @types/node@22.7.4 --save-dev # Ensure that meteor/tools has no TypeScript errors. ../../meteor npm install -g typescript cd ../../ @@ -753,7 +756,7 @@ jobs: Docs: docker: # This Node version should match that in the meteor/docs CircleCI config. - - image: meteor/circleci:2024.09.11-android-34-node-20 + - image: meteor/circleci:2025.07.8-android-35-node-22 resource_class: large environment: CHECKOUT_METEOR_DOCS: /home/circleci/test_docs @@ -765,7 +768,9 @@ jobs: if [[ -n "$CIRCLE_PULL_REQUEST" ]]; then PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | sed 's|.*/pull/\([0-9]*\)|\1|') PR_BRANCH=$(curl -s https://api.github.com/repos/meteor/meteor/pulls/$PR_NUMBER | jq -r .head.ref) - git clone --branch $PR_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS} + git clone https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS} + cd ${CHECKOUT_METEOR_DOCS} + git fetch origin pull/$PR_NUMBER/head:$PR_BRANCH else git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS} fi diff --git a/.envrc b/.envrc index 7909587fdd..e023176dc5 100644 --- a/.envrc +++ b/.envrc @@ -15,6 +15,10 @@ function @meteor { "$ROOT_DIR/meteor" "$@" } +function @get-ready { + @meteor --get-ready +} + function @test-packages { TINYTEST_FILTER="$1" @meteor test-packages --exclude-archs=web.browser.legacy,web.cordova } @@ -23,6 +27,10 @@ function @test-self { @meteor self-test "$@" } +function @test-in-console { + "$ROOT_DIR/packages/test-in-console/run.sh" "$@" +} + function @check-syntax { node "$ROOT_DIR/scripts/admin/check-legacy-syntax/check-syntax.js" } @@ -46,4 +54,60 @@ function @docs-start { function @docs-migration-start { npm run docs:dev --prefix "$ROOT_DIR/v3-docs/v3-migration-docs" -} \ No newline at end of file +} + +function @get-changes { + git diff --numstat HEAD~1 HEAD | awk '($1 + $2) <= 5000 {print $3}' +} + +function @summarize-changes { + changes=$(@get-changes) + + if [ -n "$changes" ]; then + changes=$(git diff HEAD~1 HEAD -- $(echo "$changes" | tr '\n' ' ')) + else + changes=$(git diff HEAD~1 HEAD) + fi + + echo "$changes" | llm -s "Summarize the following changes in a few sentences:" +} + +function @packages-bumped { + git diff --name-only devel...$(git branch --show-current) | grep "packages/.*/package.js$" | while IFS= read -r file; do + if ! git show devel:$file > /dev/null 2>&1; then + continue + fi + + old=$(git show devel:$file | grep -o "version: *['\"][^'\"]*['\"]" | sed "s/version: *.['\"]//;s/['\"].*//") + version=$(grep -o "version: *['\"][^'\"]*['\"]" "$file" | sed "s/version: *.['\"]//;s/['\"].*//") + name=$(grep -o "name: *['\"][^'\"]*['\"]" "$file" | sed "s/name: *.['\"]//;s/['\"].*//") + + pkg_name=$(echo "$file" | sed -E 's|packages/([^/]*/)?([^/]*)/package\.js|\2|') + + version_in_red=$(tput setaf 1)$version$(tput sgr0) + + if [[ "$version" != "$old" ]]; then + echo "- $pkg_name@$version_in_red" + fi + done +} + +function @packages-bumped-npm { + git diff --name-only devel...$(git branch --show-current) | grep "npm-packages/.*/package.json$" | while IFS= read -r file; do + if ! git show devel:$file > /dev/null 2>&1; then + continue + fi + + old=$(git show devel:$file | grep -o "version: *['\"][^'\"]*['\"]" | sed "s/version: *.['\"]//;s/['\"].*//") + version=$(grep -o "\"version\": *['\"][^'\"]*['\"]" "$file" | sed "s/\"version\": *.['\"]//;s/['\"].*//") + name=$(grep -o "\"name\": *['\"][^'\"]*['\"]" "$file" | sed "s/\"name\": *.['\"]//;s/['\"].*//") + + pkg_name=$(echo "$file" | sed -E 's|npm-packages/([^/]*/)?([^/]*)/package\.json|\2|') + + version_in_red=$(tput setaf 1)$version$(tput sgr0) + + if [[ "$version" != "$old" ]]; then + echo "- $pkg_name@$version_in_red" + fi + done +} diff --git a/.github/labeler.yml b/.github/labeler.yml index 51963d8eed..343cc5ee6a 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,124 +1,178 @@ Project:Accounts:Password: - - packages/accounts-password/**/* + - changed-files: + - any-glob-to-any-file: packages/accounts-password/**/* Project:Accounts:UI: - - packages/meteor-developer-config-ui/**/* - - packages/github-config-ui/**/* - - packages/google-config-ui/**/* - - packages/twitter-config-ui/**/* - - packages/facebook-config-ui/**/* - - packages/accounts-ui/**/* - - packages/accounts-ui-unstyled/**/* + - changed-files: + - any-glob-to-any-file: + - packages/meteor-developer-config-ui/**/* + - packages/github-config-ui/**/* + - packages/google-config-ui/**/* + - packages/twitter-config-ui/**/* + - packages/facebook-config-ui/**/* + - packages/accounts-ui/**/* + - packages/accounts-ui-unstyled/**/* Project:CSS: - - packages/non-core/less/**/* - - packages/minifier-css/**/* - - packages/standard-minifier-css/**/* + - changed-files: + - any-glob-to-any-file: + - packages/non-core/less/**/* + - packages/minifier-css/**/* + - packages/standard-minifier-css/**/* Project:DDP: - - packages/ddp-common/**/* - - packages/ddp-rate-limiter/**/* - - packages/ddp-server/**/* - - packages/ddp-client/**/* - - packages/ddp/**/* - - packages/socket-stream-client/**/* + - changed-files: + - any-glob-to-any-file: + - packages/ddp-common/**/* + - packages/ddp-rate-limiter/**/* + - packages/ddp-server/**/* + - packages/ddp-client/**/* + - packages/ddp/**/* + - packages/socket-stream-client/**/* Project:EJSON: - - packages/ejson/**/* + - changed-files: + - any-glob-to-any-file: packages/ejson/**/* Project:HMR: - - packages/hot-code-push/**/* - - packages/hot-module-replacement/**/* + - changed-files: + - any-glob-to-any-file: + - packages/hot-code-push/**/* + - packages/hot-module-replacement/**/* Project:Isobuild:Minifiers: - - packages/minifier-css/**/* - - packages/minifier-js/**/* - - packages/standard-minifier-js/**/* - - packages/standard-minifier-css/**/* - - packages/standard-minifiers/**/* + - changed-files: + - any-glob-to-any-file: + - packages/minifier-css/**/* + - packages/minifier-js/**/* + - packages/standard-minifier-js/**/* + - packages/standard-minifier-css/**/* + - packages/standard-minifiers/**/* Project:Isobuild: - - tools/isobuild/**/* + - changed-files: + - any-glob-to-any-file: + - tools/isobuild/**/* Project:JS Environment:Typescript: - - packages/typescript/**/* + - changed-files: + - any-glob-to-any-file: + - packages/typescript/**/* Project:JS Environment: - - packages/babel-compiler/**/* - - packages/babel-runtime/**/* - - packages/ecmascript/**/* - - packages/ecmascript-runtime/**/* - - packages/ecmascript-runtime-client/**/* - - packages/ecmascript-runtime-server/**/* - - packages/es5-shim/**/* - - packages/jshint/**/* + - changed-files: + - any-glob-to-any-file: + - packages/babel-compiler/**/* + - packages/babel-runtime/**/* + - packages/ecmascript/**/* + - packages/ecmascript-runtime/**/* + - packages/ecmascript-runtime-client/**/* + - packages/ecmascript-runtime-server/**/* + - packages/es5-shim/**/* + - packages/jshint/**/* Project:Livequery: - - packages/livedata/**/* + - changed-files: + - any-glob-to-any-file: + - packages/livedata/**/* Project:Minimongo: - - packages/minimongo + - changed-files: + - any-glob-to-any-file: + - packages/minimongo Project:Mobile: - - tools/cordova/**/* - - packages/launch-screen/**/* - - packages/mobile-experience/**/* - - packages/mobile-status-bar/**/* + - changed-files: + - any-glob-to-any-file: + - tools/cordova/**/* + - packages/launch-screen/**/* + - packages/mobile-experience/**/* + - packages/mobile-status-bar/**/* Project:Mongo Driver: - - packages/mongo/**/* - - packages/mongo-dev-server/**/* - - packages/mongo-id/**/* - - packages/mongo-livedata/**/* - - packages/disable-oplog/**/* - - packages/non-core/mongo-decimal/**/* + - changed-files: + - any-glob-to-any-file: + - packages/mongo/**/* + - packages/mongo-dev-server/**/* + - packages/mongo-id/**/* + - packages/mongo-livedata/**/* + - packages/disable-oplog/**/* + - packages/non-core/mongo-decimal/**/* Project:NPM: - - npm-packages/**/* + - changed-files: + - any-glob-to-any-file: + - npm-packages/**/* Project:Release Process: - - scripts/**/* + - changed-files: + - any-glob-to-any-file: + - scripts/**/* Project:Tool: - - tools/**/* - - packages/meteor-tool/**/* + - changed-files: + - any-glob-to-any-file: + - tools/**/* + - packages/meteor-tool/**/* Project:Tool:Shell: - - tools/console/**/* + - changed-files: + - any-glob-to-any-file: + - tools/console/**/* Project:Utilities:Email: - - packages/email/**/* + - changed-files: + - any-glob-to-any-file: + - packages/email/**/* Project:Utilities:HTTP: - - packages/deprecated/http/**/* - - packages/fetch/**/* - - packages/url/**/* + - changed-files: + - any-glob-to-any-file: + - packages/deprecated/http/**/* + - packages/fetch/**/* + - packages/url/**/* Project:Webapp: - - packages/webapp/**/* - - packages/webapp-hashing/**/* + - changed-files: + - any-glob-to-any-file: + - packages/webapp/**/* + - packages/webapp-hashing/**/* Project:Windows: - - scripts/windows/**/* + - changed-files: + - any-glob-to-any-file: + - scripts/windows/**/* Project:Webapp:Browser Policy: - - packages/browser-policy/**/* - - packages/browser-policy-common/**/* - - packages/browser-policy-content/**/* - - packages/browser-policy-framing/**/* + - changed-files: + - any-glob-to-any-file: + - packages/browser-policy/**/* + - packages/browser-policy-common/**/* + - packages/browser-policy-content/**/* + - packages/browser-policy-framing/**/* Project:Examples: - - tools/cli/example-repositories.js + - changed-files: + - any-glob-to-any-file: + - tools/cli/example-repositories.js Project:Dynamic Import: - - packages/dynamic-import/**/* + - changed-files: + - any-glob-to-any-file: + - packages/dynamic-import/**/* Project:Docs: - - docs/**/* - - v3-docs/**/* + - changed-files: + - any-glob-to-any-file: + - docs/**/* + - v3-docs/**/* Project:Guide: - - guide/**/* + - changed-files: + - any-glob-to-any-file: + - guide/**/* github_actions: - - ./github/**/* + - changed-files: + - any-glob-to-any-file: + - ./github/**/* diff --git a/.github/scripts/__tests__/inactive-issues.test.js b/.github/scripts/__tests__/inactive-issues.test.js new file mode 100644 index 0000000000..91f9ddf60f --- /dev/null +++ b/.github/scripts/__tests__/inactive-issues.test.js @@ -0,0 +1,198 @@ +// Tests for inactive-issues.js using Node's built-in test runner (node:test) +// We only care about the last comments (per user instruction), so we don't need pagination logic. + +const { test, beforeEach } = require('node:test'); +const assert = require('node:assert'); +const path = require('node:path'); + +// Load the script dynamically so we can pass mocks. +const scriptPath = path.join(__dirname, '..', 'inactive-issues.js'); + +// Helper to advance days +const daysAgo = (days) => { + const d = new Date(); + d.setUTCDate(d.getUTCDate() - days); + return d.toISOString(); +}; + +// Factory for github REST mock structure +function buildGithubMock({ issues, commentsByIssue }) { + return { + rest: { + issues: { + listForRepo: async ({ page, per_page }) => { + // simple pagination slice + const start = (page - 1) * per_page; + const end = start + per_page; + return { data: issues.slice(start, end) }; + }, + listComments: async ({ issue_number }) => { + return { data: commentsByIssue[issue_number] || [] }; + }, + createComment: async ({ issue_number, body }) => { + // push comment to structure to allow assertions on side effects if needed + const arr = commentsByIssue[issue_number] || (commentsByIssue[issue_number] = []); + arr.push({ + id: Math.random(), + body, + created_at: new Date().toISOString(), + user: { login: 'github-actions[bot]', type: 'Bot' } + }); + return {}; + }, + addLabels: async ({ issue_number, labels }) => { + const issue = issues.find(i => i.number === issue_number); + if (issue) { + (issue.labels || (issue.labels = [])).push(...labels.map(l => ({ name: l }))); + } + return {}; + } + } + } + }; +} + +// Wrap script invocation for reuse +async function runScript({ issues, commentsByIssue }) { + delete require.cache[require.resolve(scriptPath)]; + const fn = require(scriptPath); + const github = buildGithubMock({ issues, commentsByIssue }); + await fn({ github, context: { repo: { owner: 'meteor', repo: 'meteor' } } }); + return { issues, commentsByIssue }; +} + +let baseIssueNumber = 1000; +function makeIssue({ daysSinceHumanActivity, isPR = false, labels = [], user = 'user1' }) { + // We'll simulate by setting created_at to the human activity date if no comments. + const updated_at = daysAgo(daysSinceHumanActivity); + return { + number: baseIssueNumber++, + pull_request: isPR ? {} : undefined, + labels: labels.map(n => ({ name: n })), + user: { login: user }, + created_at: daysAgo(daysSinceHumanActivity), + updated_at + }; +} + +// TESTS + +beforeEach(() => { + baseIssueNumber = 1000; +}); + +test('60 days inactivity -> adds reminder comment (no prior reminder)', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 60 }); + const issues = [issue]; + const commentsByIssue = { [issue.number]: [] }; // no comments + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 1, 'Should have 1 reminder comment'); + assert.match(botComments[0].body, /60 days/); + assert.ok(!issue.labels.some(l => l.name === 'idle'), 'Should not label yet'); +}); + +test('60 days inactivity but already reminded -> no duplicate comment', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 65 }); + const issues = [issue]; + const commentsByIssue = { + [issue.number]: [ + { + id: 1, + body: '👋 @user1 This issue has been open with no human activity for 60 days. Is this issue still relevant? If there is no human response or activity within the next 30 days, this issue will be labeled as `idle`.', + created_at: daysAgo(5), // 5 days ago bot comment (means last human is 65 days, bot after human) + user: { login: 'github-actions[bot]', type: 'Bot' } + } + ] + }; + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 1, 'Should still have only the existing reminder'); +}); + +test('90 days inactivity -> label + comment', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 95 }); + const issues = [issue]; + const commentsByIssue = { [issue.number]: [] }; + + await runScript({ issues, commentsByIssue }); + + assert.ok(issue.labels.some(l => l.name === 'idle'), 'Should add idle label'); + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 1, 'Should comment when labeling'); + assert.match(botComments[0].body, /90 days/i); +}); + +test('90 days inactivity but already labeled -> no action', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 100, labels: ['idle'] }); + const issues = [issue]; + const commentsByIssue = { [issue.number]: [] }; + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 0, 'Should not comment again'); +}); + +test('90 days inactivity but already labeled `in-development` -> no action', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 100, labels: ['in-development'] }); + const issues = [issue]; + const commentsByIssue = { [issue.number]: [] }; + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 0, 'Should not comment again'); +}); + + +test('Human reply after reminder resets cycle (no immediate labeling)', async () => { + // Scenario: last human activity 10 days ago, bot commented 40 days ago (which was 50 days after original). Should NOT comment again or label. + const issue = makeIssue({ daysSinceHumanActivity: 10 }); + const issues = [issue]; + const commentsByIssue = { + [issue.number]: [ + { + id: 1, + body: '👋 @user1 This issue has been open with no human activity for 60 days... ', + created_at: daysAgo(50), + user: { login: 'github-actions[bot]', type: 'Bot' } + }, + { + id: 2, + body: 'I am still seeing this problem', + created_at: daysAgo(10), + user: { login: 'some-human', type: 'User' } + } + ] + }; + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 1, 'Should not add a new bot comment'); + assert.ok(!issue.labels.some(l => l.name === 'idle'), 'Should not label'); +}); + +test('Only bot comments (no human ever) counts from creation date', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 61 }); + const issues = [issue]; + const commentsByIssue = { + [issue.number]: [ + { + id: 1, + body: 'Automated maintenance notice', + created_at: daysAgo(30), + user: { login: 'github-actions[bot]', type: 'Bot' } + } + ] + }; + + await runScript({ issues, commentsByIssue }); + const botComments = commentsByIssue[issue.number].filter(c => /60 days/.test(c.body)); + assert.equal(botComments.length, 1, 'Should add a 60-day reminder'); +}); diff --git a/.github/scripts/inactive-issues.js b/.github/scripts/inactive-issues.js new file mode 100644 index 0000000000..a75f5b81a1 --- /dev/null +++ b/.github/scripts/inactive-issues.js @@ -0,0 +1,200 @@ +/** +* Mark issues as idle after a period of inactivity +* and post reminders after a shorter period of inactivity. +* +* 1. Issues with no human activity for 60 days get a reminder comment. +* 2. Issues with no human activity for 90 days get labeled as "idle" and get a comment. +* +* Human activity is defined as any comment from a non-bot user. +* +* This script is intended to be run as a GitHub Action on a schedule (e.g., daily). + */ +module.exports = async ({ github, context }) => { + const daysToComment = 60; + const daysToLabel = 90; + + const idleTimeComment = daysToComment * 24 * 60 * 60 * 1000; + const idleTimeLabel = daysToLabel * 24 * 60 * 60 * 1000; + const now = new Date(); + + const BOT_LOGIN = 'github-actions[bot]'; + const REMINDER_PHRASE = 'Is this issue still relevant?'; + + const COMMENT_60_TEMPLATE = (login) => + `👋 @${login} This issue has been open with no human activity for ${daysToComment} days. Is this issue still relevant? If there is no human response or activity within the next ${daysToLabel - daysToComment} days, this issue will be labeled as \`idle\`.`; + + const COMMENT_90_TEXT = + 'This issue has been automatically labeled as `idle` due to 90 days of inactivity (no human interaction). If this is still relevant or if someone is working on it, please comment or add `in-development` label.'; + + // Fetch all open issues + async function fetchAllIssues() { + let page = 1; + const per_page = 100; + const results = []; + let keepGoing = true; + + while (keepGoing) { + const { data } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page, + page, + sort: 'updated', + direction: 'asc' + }); + + if (!data.length) break; + results.push(...data); + + if (data.length < per_page) { + keepGoing = false; + } else { + page++; + await new Promise((r) => setTimeout(r, 120)); + } + } + return results; + } + // analyse comments to find last human activity and if a reminder was already posted after that + async function analyzeComments(issueNumber, issueCreatedAt) { + const commentsResp = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + per_page: 100 + }); + + const comments = commentsResp.data; + let lastHumanActivity = null; + + for (let i = comments.length - 1; i >= 0; i--) { + const c = comments[i]; + const isBot = c.user?.type === 'Bot' || c.user?.login === BOT_LOGIN; + if (!isBot) { + lastHumanActivity = new Date(c.created_at); + break; + } + } + + if (!lastHumanActivity) { + lastHumanActivity = new Date(issueCreatedAt); + } + + const hasReminderAfterLastHuman = comments.some( + (c) => + c.user?.login === BOT_LOGIN && + c.body?.includes(REMINDER_PHRASE) && + new Date(c.created_at) > lastHumanActivity + ); + + return { lastHumanActivity, hasReminderAfterLastHuman }; + } + + const issues = await fetchAllIssues(); + + let processed = 0; + let commented = 0; + let labeled = 0; + let skippedPR = 0; + + for (const issue of issues) { + processed++; + + if (issue.pull_request) { + skippedPR++; + continue; + } + + if (issue.labels.some((l) => l.name === 'idle' || l.name === 'in-development')) { + continue; + } + + let analysis; + try { + analysis = await analyzeComments(issue.number, issue.created_at); + } catch (err) { + continue; // fail to get comments, skip + } + + const { lastHumanActivity, hasReminderAfterLastHuman } = analysis; + const inactivityMs = now.getTime() - lastHumanActivity.getTime(); + + // 90+ days => label + comment + if (inactivityMs >= idleTimeLabel) { + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['idle'] + }); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: COMMENT_90_TEXT + }); + labeled++; + continue; + } catch (err) { + // retry simples + try { + await new Promise((r) => setTimeout(r, 5000)); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['idle'] + }); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: COMMENT_90_TEXT + }); + labeled++; + } catch {} + continue; + } + } + + // 60-89 days => comment (once) + if ( + inactivityMs >= idleTimeComment && + inactivityMs < idleTimeLabel && + !hasReminderAfterLastHuman + ) { + const body = COMMENT_60_TEMPLATE(issue.user.login); + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body + }); + commented++; + } catch (err) { + try { + await new Promise((r) => setTimeout(r, 5000)); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body + }); + commented++; + } catch {} + } + } + } + + // Log summary for CI + console.log( + JSON.stringify( + { processed, commented, labeled, skippedPR }, + null, + 2 + ) + ); +}; diff --git a/.github/workflows/check-code-style.yml b/.github/workflows/check-code-style.yml index 5b7dbbac4c..8b24944b3f 100644 --- a/.github/workflows/check-code-style.yml +++ b/.github/workflows/check-code-style.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - run: npm ci - name: Run ESLint@8 run: npx eslint@8 "./npm-packages/meteor-installer/**/*.js" diff --git a/.github/workflows/check-syntax.yml b/.github/workflows/check-syntax.yml index 1f9f151c94..a20bb011ca 100644 --- a/.github/workflows/check-syntax.yml +++ b/.github/workflows/check-syntax.yml @@ -1,6 +1,5 @@ name: Check legacy syntax on: - - push - pull_request jobs: check-code-style: @@ -9,7 +8,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - run: cd scripts/admin/check-legacy-syntax && npm ci - name: Check syntax run: cd scripts/admin/check-legacy-syntax && node check-syntax.js diff --git a/.github/workflows/guide.yml b/.github/workflows/guide.yml index f485145f18..bd3d801966 100644 --- a/.github/workflows/guide.yml +++ b/.github/workflows/guide.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 12.x + node-version: 22.x - name: Build the Guide run: npm ci && npm run build - name: Deploy to Netlify for preview diff --git a/.github/workflows/inactive-issues.yml b/.github/workflows/inactive-issues.yml new file mode 100644 index 0000000000..2d8cba7a3f --- /dev/null +++ b/.github/workflows/inactive-issues.yml @@ -0,0 +1,24 @@ +name: Inactive Issues Management + +on: + schedule: + # “At 01:00 on Saturday.” + - cron: '0 1 * * 6' + # Allows to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + manage-inactive-issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Manage inactive issues + uses: actions/github-script@v6 + with: + script: | + const script = require('./.github/scripts/inactive-issues.js') + await script({github, context}) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index a9d25b1e47..da6690818c 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,6 +17,6 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/meteor-selftest-windows.yml b/.github/workflows/meteor-selftest-windows.yml index d78f633ec3..c328e7bf2d 100644 --- a/.github/workflows/meteor-selftest-windows.yml +++ b/.github/workflows/meteor-selftest-windows.yml @@ -18,19 +18,27 @@ env: TIMEOUT_SCALE_FACTOR: 20 METEOR_HEADLESS: true SELF_TEST_EXCLUDE: '^NULL-LEAVE-THIS-HERE-NULL$' + METEOR_MODERN: true jobs: test: runs-on: windows-2019-meteor + concurrency: + group: ${{ github.head_ref }}-meteor-selftest-windows + cancel-in-progress: true steps: + - name: cleanup + shell: powershell + run: Remove-Item -Recurse -Force ${{ github.workspace }}\* + - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: 20.x + node-version: 22.x - name: Install dependencies shell: pwsh @@ -45,7 +53,7 @@ jobs: .\scripts\windows\ci\test.ps1 - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | .\dev_bundle diff --git a/.github/workflows/npm-eslint-plugin-meteor.yml b/.github/workflows/npm-eslint-plugin-meteor.yml index 29a72f0005..ff51a2e847 100644 --- a/.github/workflows/npm-eslint-plugin-meteor.yml +++ b/.github/workflows/npm-eslint-plugin-meteor.yml @@ -21,7 +21,7 @@ jobs: - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: npm - run: npm ci - run: npm test diff --git a/.github/workflows/run-profiler.yml b/.github/workflows/run-profiler.yml new file mode 100644 index 0000000000..c19ff83a7d --- /dev/null +++ b/.github/workflows/run-profiler.yml @@ -0,0 +1,46 @@ +name: Run Profiler + +on: + issue_comment: + types: [created] + +jobs: + run-profiler: + if: github.event.issue.pull_request && contains(github.event.comment.body , '/profile') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Checkout Pull Request + run: gh pr checkout ${{ github.event.issue.number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Set ENVs + run: | + value="VALUE" + echo "Key=$value" >> $GITHUB_ENV + + PR_NUMBER="${{ github.event.issue.number }}" + echo "PrNumber=$PR_NUMBER" >> $GITHUB_ENV + + - name: Run CI + run: | + echo "Running meteor profiler..." + echo $PR_NUMBER + git status + ls + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v3 + with: + message: | + Hello world !!!! :wave: + this is pr number: #${{ env.PrNumber }} + testing value: ${{ env.Key }} + pr-number: ${{ github.event.issue.number }} diff --git a/.github/workflows/test-deprecated-packages.yml b/.github/workflows/test-deprecated-packages.yml new file mode 100644 index 0000000000..e4ed12fe92 --- /dev/null +++ b/.github/workflows/test-deprecated-packages.yml @@ -0,0 +1,52 @@ +name: Test Deprecated Packages + +# Disabled until we figure out how to fix the error from puppeteer +# Runs on Travis CI for now +# +#on: +# push: +# branches: +# - main +# pull_request: + +jobs: + build: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.head_ref }}-test-deprecated-packages + cancel-in-progress: true + timeout-minutes: 60 + + env: + PUPPETEER_DOWNLOAD_PATH: /home/runner/.npm/chromium + + steps: + - name: Update and install dependencies + run: sudo apt-get update && sudo apt-get install -y libnss3 g++-12 + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.15.1 + + - name: Cache Node.js modules + uses: actions/cache@v3 + with: + path: | + ~/.npm + .meteor + .babel-cache + dev_bundle + /home/runner/.npm/chromium + key: ${{ runner.os }}-node-${{ hashFiles('meteor', '**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies + run: npm install + + - name: Run tests + run: ./packages/test-in-console/run.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index fe19b9b3e9..4742e13056 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ packages/**/.npm # doc files should not be committed packages/**/*.docs.js + +#cursor +.cursorignore +.cursorrules diff --git a/.travis.yml b/.travis.yml index 4d9997a4d7..3e63726f4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ dist: jammy sudo: required services: xvfb node_js: - - "20.15.1" + - "22.17.0" cache: directories: - ".meteor" @@ -16,6 +16,8 @@ env: - CXX=g++-12 - phantom=false - PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium + - TEST_PACKAGES_EXCLUDE=stylus + - METEOR_MODERN=true addons: apt: sources: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 92ef3e891f..fa910d2ee5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -19,7 +19,7 @@ To report an issue in one of the projects listed below, please email code-of-con The Code of Conduct panel is a moderation team that handle code of conduct issues. The makeup of this team is as follows: * CEO at Meteor Software - Frederico Maia Arantes -* Software Engineer at Meteor Software - Denilson Silva +* CTO at Meteor Software - Henrique Albert * CEO at High Impact Tech - Alim S. Gafar Members of the CoCP team will be added for a 1-year term and will be re-confirmed on a yearly basis. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1694ca521f..c646fad457 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ There are many ways to contribute to the Meteor Project. Here’s a list of tech There are also several ways to contribute to the Meteor Project outside of GitHub, like organizing or speaking at [Meetups](https://forums.meteor.com/c/meetups) and events and helping to moderate our [forums](https://forums.meteor.com/). -If you can think of any changes to the project, [documentation](https://github.com/meteor/meteor/tree/devel/docs), or [guide](https://github.com/meteor/meteor/tree/devel/guide) that would improve the contributor experience, let us know by opening an issue! +If you can think of any changes to the project, [documentation](https://github.com/meteor/meteor/tree/devel/v3-docs), or [guide](https://github.com/meteor/meteor/tree/devel/guide) that would improve the contributor experience, let us know by opening an issue! ### Finding work @@ -43,15 +43,14 @@ Reviewers are members of the community who help with Pull Requests reviews. Current Reviewers: - [meteor](https://github.com/meteor/meteor) - - [@denihs](https://github.com/denihs) - [@fredmaiaarantes](https://github.com/fredmaiaarantes) - [@henriquealbert](https://github.com/henriquealbert) - [@aquinoit](https://github.com/aquinoit) - [@Grubba27](https://github.com/Grubba27) -- [@filipenevola](https://github.com/filipenevola) + - [@italojs](https://github.com/italojs) + - [@nachocodoner](https://github.com/nachocodoner) - [@StorytellerCZ](https://github.com/StorytellerCZ) - [@zodern](https://github.com/zodern) -- [@CaptainN](https://github.com/CaptainN) - [@radekmie](https://github.com/radekmie) #### Core Committer @@ -59,16 +58,15 @@ Current Reviewers: The contributors with commit access to meteor/meteor are employees of Meteor Software LP or community members who have distinguished themselves in other contribution areas or members of partner companies. If you want to become a core committer, please start writing PRs. Current Core Team: -- [@denihs](https://github.com/denihs) -- [@zodern](https://github.com/zodern) -- [@filipenevola](https://github.com/filipenevola) -- [@fredmaiaarantes](https://github.com/fredmaiaarantes) -- [@henriquealbert](https://github.com/henriquealbert) -- [@Grubba27](https://github.com/Grubba27) +- [meteor](https://github.com/meteor/meteor) + - [@fredmaiaarantes](https://github.com/fredmaiaarantes) + - [@henriquealbert](https://github.com/henriquealbert) + - [@Grubba27](https://github.com/Grubba27) + - [@italojs](https://github.com/italojs) + - [@nachocodoner](https://github.com/nachocodoner) - [@StorytellerCZ](https://github.com/StorytellerCZ) -- [@CaptainN](https://github.com/CaptainN) +- [@zodern](https://github.com/zodern) - [@radekmie](https://github.com/radekmie) -- [@matheusccastroo](https://github.com/matheusccastroo) ### Tracking project work @@ -157,7 +155,7 @@ Learn how we use GitHub labels [here](LABELS.md) ## Documentation -If you'd like to contribute to Meteor's documentation, head over to https://docs.meteor.com or https://guide.meteor.com and if you find something that could be better click in "Edit on GitHub" footer to edit and submit a PR. +If you'd like to contribute to Meteor's documentation, head over to https://docs.meteor.com/about/contributing.html for guidelines. ## Blaze diff --git a/README.md b/README.md index a6249e4157..11aa1c4c6c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@
- + + + + Meteor logo +
-
-
[![Travis CI Status](https://api.travis-ci.com/meteor/meteor.svg?branch=devel)](https://app.travis-ci.com/github/meteor/meteor) [![CircleCI Status](https://circleci.com/gh/meteor/meteor.svg?style=svg)](https://app.circleci.com/pipelines/github/meteor/meteor?branch=devel) -[![built with Meteor](https://img.shields.io/badge/Meteor-3.0.3-green?logo=meteor&logoColor=white)](https://meteor.com) +[![built with Meteor](https://img.shields.io/badge/Meteor-3.2.2-green?logo=meteor&logoColor=white)](https://meteor.com) ![node-current](https://img.shields.io/node/v/meteor) ![Discord](https://img.shields.io/discord/1247973371040239676) ![Twitter Follow](https://img.shields.io/twitter/follow/meteorjs?style=social) @@ -54,23 +56,20 @@ How about trying a tutorial to get started with your favorite technology? | [ React](https://docs.meteor.com/tutorials/react/) | | - | | [ Blaze](https://blaze-tutorial.meteor.com/) | -| [ Vue](https://vue-tutorial.meteor.com/) | -| [ Svelte](https://svelte-tutorial.meteor.com/) | - -Next, read the [documentation](https://docs.meteor.com/) and get some [examples](https://github.com/meteor/examples). +| [ Vue](https://docs.meteor.com/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker.html) | # 🚀 Quick Start On your platform, use this line: ```shell -> npm install -g meteor +> npx meteor ``` 🚀 To create a project: ```shell -> meteor create my-app +> meteor create ``` ☄️ Run it: @@ -84,10 +83,9 @@ meteor **Building an application with Meteor?** -* Deploy on [Meteor Cloud](https://www.meteor.com/cloud) +* Deploy on [Galaxy](https://galaxycloud.app) * Discuss on [Forums](https://forums.meteor.com/) -* Join the Meteor Discord by clicking this [invite link](https://discord.gg/hZkTCaVjmT). -* Announcement list. Subscribe in the [footer](https://www.meteor.com/). +* Join the [Meteor Discord](https://discord.gg/hZkTCaVjmT) Interested in helping or contributing to Meteor? These resources will help: @@ -96,15 +94,3 @@ Interested in helping or contributing to Meteor? These resources will help: * [Contribution guidelines](CONTRIBUTING.md) * [Feature requests](https://github.com/meteor/meteor/discussions/) * [Issue tracker](https://github.com/meteor/meteor/issues) - -To uninstall Meteor: - - If installed via npm, run: - ```shell - meteor-installer uninstall - ``` - - If installed via curl, run: - ```shell - rm -rf ~/.meteor - sudo rm /usr/local/bin/meteor - ``` -To find more information about installation, [read here](https://docs.meteor.com/about/install.html#uninstall). diff --git a/SECURITY.md b/SECURITY.md index 278969b992..aaf7b92c93 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,8 @@ | Version | Support Status | ------- | -------------- -| 2.x.y | ✅ all security issues +| 3.x.y | ✅ all security issues +| 2.x.y | ⚠️ only major security issues (Until 2025-07) | <= 1.12.x | ❌ no longer supported ## Reporting a Vulnerability diff --git a/docs/_config.yml b/docs/_config.yml index 0556242aac..395aaca44c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -88,7 +88,6 @@ sidebar_categories: - packages/server-render - packages/spacebars - packages/standard-minifier-css - - packages/underscore - packages/url - packages/webapp - packages/packages-listing @@ -214,6 +213,7 @@ redirects: /#/full/accounts-setusername: 'api/passwords.html#accounts-setusername' /#/full/accounts-addemail: 'api/passwords.html#accounts-addemail' /#/full/accounts-removeemail: 'api/passwords.html#accounts-removeemail' + /#/full/accounts_replaceemail: 'api/passwords.html#Accounts-replaceEmail' /#/full/accounts_verifyemail: 'api/passwords.html#Accounts-verifyEmail' /#/full/accounts-finduserbyusername: 'api/passwords.html#accounts-finduserbyusername' /#/full/accounts-finduserbyemail: 'api/passwords.html#accounts-finduserbyemail' @@ -393,7 +393,6 @@ redirects: /#/full/oauth-encryption: 'packages/oauth-encryption.html' /#/full/random: 'packages/random.html' /#/full/spiderable: 'packages/spiderable.html' - /#/full/underscore: 'packages/underscore.html' /#/full/webapp: 'packages/webapp.html' '#meteor_isclient': 'api/core.html#Meteor-isClient' '#meteor_isserver': 'api/core.html#Meteor-isServer' @@ -669,6 +668,5 @@ redirects: '#oauth-encryption': 'packages/oauth-encryption.html' '#random': 'packages/random.html' '#spiderable': 'packages/spiderable.html' - '#underscore': 'packages/underscore.html' '#webapp': 'packages/webapp.html' '#pkg_spacebars': 'packages/spacebars.html' diff --git a/docs/generators/changelog/versions/0-before-2.10.md b/docs/generators/changelog/versions/0-before-2.10.md index 1774e87739..54c856eaa8 100644 --- a/docs/generators/changelog/versions/0-before-2.10.md +++ b/docs/generators/changelog/versions/0-before-2.10.md @@ -2935,6 +2935,7 @@ N/A setMinimumBrowserVersions({ chrome: 49, firefox: 45, + firefoxIOS: 100, edge: 12, ie: Infinity, // Sorry, IE11. mobile_safari: [9, 2], // 9.2.0+ diff --git a/docs/generators/changelog/versions/3.0.0.md b/docs/generators/changelog/versions/3.0.0.md index c5fa1f1a1e..4c863db6ad 100644 --- a/docs/generators/changelog/versions/3.0.0.md +++ b/docs/generators/changelog/versions/3.0.0.md @@ -55,6 +55,7 @@ - `Accounts.sendVerificationEmail` - `Accounts.addEmail` - `Accounts.removeEmail` + - `Accounts.replaceEmailAsync` - `Accounts.verifyEmail` - `Accounts.createUserVerifyingEmail` - `Accounts.createUser` diff --git a/docs/history.md b/docs/history.md index 5f9df5f8d7..3d5da68a9d 100644 --- a/docs/history.md +++ b/docs/history.md @@ -8,6 +8,308 @@ [//]: # (go to meteor/docs/generators/changelog/docs) +## v3.3.2, 01-09-2025 + +### Highlights + +- Async-compatible account URLs and email-sending coverage [#13740](https://github.com/meteor/meteor/pull/13740) +- Move `findUserByEmail` method from `accounts-password` to `accounts-base` [#13859](https://github.com/meteor/meteor/pull/13859) +- Return `insertedId` on client `upsert` to match Meteor 2.x behavior [#13891](https://github.com/meteor/meteor/pull/13891) +- Unrecognized operator bug fixed [#13895](https://github.com/meteor/meteor/pull/13895) +- Security fix for `sha.js` [#13908](https://github.com/meteor/meteor/pull/13908) + + +All Merged PRs@[GitHub PRs 3.3.2](https://github.com/meteor/meteor/pulls?q=is%3Apr+is%3Amerged+base%3Arelease-3.3.2) + +#### Breaking Changes + +N/A + +##### Cordova Upgrade + +- Enable modern browser support for Cordova unless explicitly disabled [#13896](https://github.com/meteor/meteor/pull/13896) + +#### Internal API changes + +- lodash.template dependency was removed [#13898](https://github.com/meteor/meteor/pull/13898) + +#### Migration Steps + +Please run the following command to update your project: + +```bash +meteor update --release 3.3.2 +``` + +--- + +If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). + +#### Bumped Meteor Packages + +- accounts-base@3.1.2 +- accounts-password@3.2.1 +- accounts-passwordless@3.0.2 +- meteor-node-stubs@1.2.24 +- babel-compiler@7.12.2 +- boilerplate-generator@2.0.2 +- ecmascript@0.16.13 +- minifier@3.0.4 +- minimongo@2.0.4 +- mongo@2.1.4 +- coffeescript-compiler@2.4.3 +- npm-mongo@6.16.1 +- shell-server@0.6.2 +- typescript@5.6.6 + +#### Bumped NPM Packages + +- meteor-node-stubs@1.2.23 + +#### Special thanks to + +✨✨✨ + +- [@italojs](https://github.com/italojs) +- [@nachocodoner](https://github.com/nachocodoner) +- [@graemian](https://github.com/graemian) +- [@Grubba27](https://github.com/Grubba27) +- [@copleykj](https://github.com/copleykj) + +✨✨✨ + + +## v3.3.1, 05-08-2025 + +### Highlights + +- **MongoDB Driver Upgrades** + - Upgraded core MongoDB driver to `6.16.0` to address latest issues reported [#13710](https://github.com/meteor/meteor/pull/13710) + - Introduced `npm-mongo-legacy` to maintain compatibility with MongoDB 3.6 via `mongodb@6.9.0` [#13736](https://github.com/meteor/meteor/pull/13736) + - Mitigated a cursor leak issue by synchronizing `next()` and `close()` operations [#13786](https://github.com/meteor/meteor/pull/13786) + +- **Improved SWC integration** + - Fixed edge cases in config cache invalidation [#13809](https://github.com/meteor/meteor/pull/13809) + - Ensured `@swc/helpers` is consistently used for better bundle size and performance [#13820](https://github.com/meteor/meteor/pull/13820) + - Updated to SWC `1.12.14` [#13851](https://github.com/meteor/meteor/pull/13851) + +- **Tooling and Build System** + - Fixed regression affecting rebuild behavior [#13810](https://github.com/meteor/meteor/pull/13810) + - Addressed issues getting performance profiles in mounted volumes [#13827](https://github.com/meteor/meteor/pull/13827) + - Fallback to Babel parser when Acorn fails to parse source code [#13844](https://github.com/meteor/meteor/pull/13844) + +- **Mobile Support** + - Upgraded Cordova platform to version 14 [#13837](https://github.com/meteor/meteor/pull/13837) + +- **Developer Experience** + - Added TypeScript types for `isModern` and `getMinimumBrowserVersions` functions [#13704](https://github.com/meteor/meteor/pull/13704) + - Enhanced CLI help output and documented admin commands [#13826](https://github.com/meteor/meteor/pull/13826) + +- **Vite Tooling** + - Updated official Meteor + Vite skeletons [#13835](https://github.com/meteor/meteor/pull/13835) + +- **Runtime & Dependencies** + - Updated to Node.js `22.18.0` and NPM `10.9.3` [#13877](https://github.com/meteor/meteor/pull/13877) + - Bumped `meteor-node-stubs` to `1.2.21` [#13825](https://github.com/meteor/meteor/pull/13825) + +All Merged PRs@[GitHub PRs 3.3.1](https://github.com/meteor/meteor/pulls?q=is%3Apr+is%3Amerged+base%3Arelease-3.3.1) + +#### Breaking Changes + +##### MongoDB Driver Upgrades + +If you're using MongoDB 3.6 or earlier, install the new legacy package: + +```bash +meteor add npm-mongo-legacy +``` +This will pin the MongoDB driver to 6.9.0 for compatibility. + +If you’re on MongoDB 4+, the default [MongoDB driver 6.16.0](https://github.com/mongodb/node-mongodb-native/releases/tag/v6.16.0) is applied automatically. + +Please migrate your database as soon as possible to MongoDB 5 onward, as [MongoDB driver 6.17.0](https://github.com/mongodb/node-mongodb-native/releases/tag/v6.17.0) will drop MongoDB 4 support. We’ll keep offering `npm-mongo-legacy` so you can keep getting Meteor updates with your existing MongoDB legacy version. + +##### Cordova Upgrade + +The Cordova platform has been upgraded to version 14. Refer to the [Cordova Changelog](https://cordova.apache.org/announcements/2025/03/26/cordova-android-14.0.0.html) for more details on the changes and migration steps. + +#### Internal API changes + +N/A + +#### Migration Steps + +Please run the following command to update your project: + +```bash +meteor update --release 3.3.1 +``` + +--- + +While this is a patch release, Meteor 3.3, a recent minor update, introduced a modern build stack that’s now the default for new apps. Here’s how you can migrate to it. + +**Add this to your `package.json` to enable the new modern build stack:** + +```json +"meteor": { + "modern": true +} +``` + +Check the docs for help with the SWC migration, especially if your project uses many Babel plugins. + +[Modern Transpiler: SWC docs](https://docs.meteor.com/about/modern-build-stack/transpiler-swc.html) + +If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). + +#### Bumped Meteor Packages + +- babel-compiler@7.12.1 +- callback-hook@1.6.1 +- ecmascript@0.16.12 +- minifier-js@3.0.3 +- minimongo@2.0.3 +- modern-browsers@0.2.3 +- mongo@2.1.3 +- npm-mongo-legacy@6.9.0 +- npm-mongo@6.16.0 +- standard-minifier-js@3.1.1 +- tinytest@1.3.2 +- typescript@5.6.5 +- meteor-tool@3.3.1 + +#### Bumped NPM Packages + +- meteor-node-stubs@1.2.21 + +#### Special thanks to + +✨✨✨ + +- [@nachocodoner](https://github.com/nachocodoner) +- [@italojs](https://github.com/italojs) +- [@StorytellerCZ](https://github.com/StorytellerCZ) +- [@JorgenVatle](https://github.com/JorgenVatle) +- [@welkinwong](https://github.com/welkinwong) +- [@Saksham-Goel1107](https://github.com/Saksham-Goel1107) + +✨✨✨ + +## v3.3.0, 2025-06-11 + +### Highlights + +- Support SWC transpiler and minifier for faster dev and builds [PR#13657](https://github.com/meteor/meteor/pull/13657), [PR#13715](https://github.com/meteor/meteor/pull/13715) +- Switch to `@parcel/watcher` for improved native file watching [PR#13699](https://github.com/meteor/meteor/pull/13699), [#13707](https://github.com/meteor/meteor/pull/13707) +- Default to modern architecture, skip legacy processing [PR#13665](https://github.com/meteor/meteor/pull/13665), [PR#13698](https://github.com/meteor/meteor/pull/13698) +- Optimize SQLite for faster startup and better performance [PR#13702](https://github.com/meteor/meteor/pull/13702) +- Support CPU profiling in Meteor 3 bundler [PR#13650](https://github.com/meteor/meteor/pull/13650) +- Improve `meteor profile`: show rebuild steps and total, support `--build` [PR#16](https://github.com/meteor/performance/pull/16), [PR#13694](https://github.com/meteor/meteor/pull/13694) +- Improve `useFind` and `useSubscribe` React hooks +- Add `replaceEmailAsync` helper to Accounts [PR#13677](https://github.com/meteor/meteor/pull/13677) +- Fix user agent detection and oplog collection filtering +- Refine type definitions for Meteor methods and SSR's ServerSink +- Allow opting out of usage stats with `DO_NOT_TRACK` +- Update Node to 22.16.0 and Express to 5.1.0 + +All Merged PRs@[GitHub PRs 3.3](https://github.com/meteor/meteor/pulls?q=is%3Apr+is%3Amerged+base%3Arelease-3.3) + +React Packages Changelog: [react-meteor-data@4.0.0](https://github.com/meteor/react-packages/tree/master/packages/react-meteor-data/CHANGELOG.md#v400-2025-06-11) + +#### Breaking Changes + +- File watching strategy switched to `@parcel/watcher` + - Most setups should be fine, but if issues appear, like when using WSL with host, volumes, or remote setups—switch to polling. + - Set `METEOR_WATCH_FORCE_POLLING=true` to enable polling. + - Set `METEOR_WATCH_POLLING_INTERVAL_MS=1000` to adjust the interval. + +- `react-meteor-data@4.0.0` + - Independent from the core, only applies if upgraded manually. + - useFind describes no deps by default [PR#431](https://github.com/meteor/react-packages/pull/431) + +#### Internal API changes + +- `express@5.1.0` - Depends on Meteor’s `webapp` package. + - Deprecates non-native promise usage [#154](https://github.com/pillarjs/router/pull/154) + - Use `async/await` or `Promise.resolve` when defining endpoints to avoid deprecation warnings. + +#### Migration Steps + +Please run the following command to update your project: + +```bash +meteor update --release 3.3 +``` + +To apply react-meteor-data changes: + +```bash +meteor add react-meteor-data@4.0.0 +``` + +**Add this to your `package.json` to enable the new modern build stack:** + +```json +"meteor": { + "modern": true +} +``` + +> These settings are on by default for new apps. + +On activate `modern` your app will be updated to use SWC transpiler. It will automatically fallback to Babel if your code can't be transpiled with SWC. + +Check the docs for help with the SWC migration, especially if your project uses many Babel plugins. + +[Modern Transpiler: SWC docs](https://docs.meteor.com/about/modern-build-stack/transpiler-swc.html) + +If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). + +#### Bumped Meteor Packages + +- accounts-base@3.1.1 +- accounts-password@3.2.0 +- autoupdate@2.0.1 +- babel-compiler@7.12.0 +- boilerplate-generator@2.0.1 +- ddp-client@3.1.1 +- ecmascript@0.16.11 +- ejson@1.1.5 +- meteor@2.1.1 +- minifier-js@3.0.2 +- modern-browsers@0.2.2 +- mongo@2.1.2 +- server-render@0.4.3 +- socket-stream-client@0.6.1 +- standard-minifier-js@3.1.0 +- typescript@5.6.4 +- webapp@2.0.7 +- meteor-tool@3.3.0 + +#### Bumped NPM Packages + +- meteor-node-stubs@1.2.17 + +#### Special thanks to + +✨✨✨ + +- [@nachocodoner](https://github.com/nachocodoner) +- [@italojs](https://github.com/italojs) +- [@Grubba27](https://github.com/Grubba27) +- [@zodern](https://github.com/zodern) +- [@9Morello](https://github.com/9Morello) +- [@welkinwong](https://github.com/welkinwong) +- [@Poyoman39](https://github.com/Poyoman39) +- [@PedroMarianoAlmeida](https://github.com/PedroMarianoAlmeida) +- [@harryadel](https://github.com/harryadel) +- [@ericm546](https://github.com/ericm546) +- [@StorytellerCZ](https://github.com/StorytellerCZ) + +✨✨✨ + + ## v3.0.1, 2024-07-16 ### Highlights @@ -4651,6 +4953,7 @@ N/A setMinimumBrowserVersions({ chrome: 49, firefox: 45, + firefoxIOS: 100, edge: 12, ie: Infinity, // Sorry, IE11. mobile_safari: [9, 2], // 9.2.0+ diff --git a/docs/jsdoc/jsdoc-conf.json b/docs/jsdoc/jsdoc-conf.json index 82ed30dd10..4fd2956fc1 100644 --- a/docs/jsdoc/jsdoc-conf.json +++ b/docs/jsdoc/jsdoc-conf.json @@ -4,7 +4,6 @@ "packages/ddp/sockjs-0.3.4.js", "packages/test-in-browser/diff_match_patch_uncompressed.js", "packages/jquery/jquery.js", - "packages/underscore/underscore.js", "packages/json/json2.js", "packages/minimongo/minimongo_tests.js", "tools/node_modules", diff --git a/docs/package-lock.json b/docs/package-lock.json index 7965d6e9ba..9d4a412012 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -4,16 +4,41 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, + "@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true + }, + "@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "requires": { + "@babel/types": "^7.28.0" + } + }, + "@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + } + }, "@jsdoc/salty": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.6.tgz", - "integrity": "sha512-aA+awb5yoml8TQ3CzI5Ue7sM3VMRC4l1zJJW4fgZ8OCL1wshJZhNzaf0PL85DSnOUw6QuFgeHGD/eq/xwwAF2g==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", "dev": true, "requires": { "lodash": "^4.17.21" @@ -26,31 +51,31 @@ "dev": true }, "@meteorjs/meteor-theme-hexo": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@meteorjs/meteor-theme-hexo/-/meteor-theme-hexo-2.0.8.tgz", - "integrity": "sha512-LQIFN05wBMjX7SXgW5CFVTfolDWMuknoypwQ0czl/44LYRBR4/LYZUgX6c+1vLjloJb+5+2HTvMGlVN9Wo1MKA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@meteorjs/meteor-theme-hexo/-/meteor-theme-hexo-2.0.9.tgz", + "integrity": "sha512-8ncpsN8MAe1F7cJBtcPgH3JE36WV03oo5mPkA1yMdRmv2kq8AQpKnd4ok0U1cr5NIIBMupLtsHDLm8PhTQcUdw==", "dev": true }, "@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true }, "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, "JSONStream": { @@ -75,16 +100,6 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "acorn": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", @@ -239,9 +254,9 @@ "optional": true }, "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, "optional": true }, @@ -479,9 +494,9 @@ } }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -506,9 +521,9 @@ "dev": true }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, "cache-base": { @@ -537,17 +552,38 @@ } }, "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "optional": true, "requires": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "optional": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "camel-case": { @@ -801,18 +837,26 @@ } }, "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "concat-map": { @@ -1040,6 +1084,18 @@ "domelementtype": "1" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "optional": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1092,14 +1148,11 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "optional": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "optional": true }, "es-errors": { "version": "1.3.0", @@ -1108,6 +1161,16 @@ "dev": true, "optional": true }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "optional": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1323,17 +1386,33 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "optional": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "optional": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, "get-value": { @@ -1401,14 +1480,11 @@ "dev": true }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "optional": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "optional": true }, "graceful-fs": { "version": "4.2.11", @@ -1480,17 +1556,10 @@ "es-define-property": "^1.0.0" } }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "optional": true - }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "optional": true }, @@ -2390,9 +2459,9 @@ }, "dependencies": { "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -2416,12 +2485,6 @@ "striptags": "^3.1.1" } }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true - }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -2652,12 +2715,12 @@ "dev": true }, "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "requires": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" } }, "is-data-descriptor": { @@ -2839,21 +2902,21 @@ "optional": true }, "jsdoc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", - "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, "requires": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", @@ -2861,12 +2924,6 @@ "underscore": "~1.13.2" }, "dependencies": { - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -2886,9 +2943,9 @@ "dev": true }, "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "dev": true } } @@ -2901,13 +2958,14 @@ "optional": true }, "json-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", - "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", "dev": true, "optional": true, "requires": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" @@ -3005,12 +3063,12 @@ } }, "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "locate-path": { @@ -3157,16 +3215,17 @@ } }, "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "requires": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "dependencies": { "argparse": { @@ -3176,9 +3235,9 @@ "dev": true }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true } } @@ -3189,6 +3248,19 @@ "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "optional": true + }, "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", @@ -3196,9 +3268,9 @@ "dev": true }, "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "micromatch": { @@ -3239,6 +3311,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "optional": true, "requires": { "mime-db": "1.52.0" } @@ -3302,25 +3375,25 @@ "dev": true }, "moment-timezone": { - "version": "0.5.45", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", - "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "dev": true, "requires": { "moment": "^2.29.4" } }, "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", "dev": true, "requires": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "on-headers": "~1.1.0" } }, "ms": { @@ -3342,9 +3415,9 @@ } }, "nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "dev": true, "optional": true }, @@ -3395,9 +3468,9 @@ "optional": true }, "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true }, "neo-async": { @@ -3561,9 +3634,9 @@ } }, "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true }, "once": { @@ -3754,6 +3827,12 @@ "dev": true, "optional": true }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true + }, "qs": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.1.tgz", @@ -4124,12 +4203,12 @@ } }, "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "requires": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -4201,9 +4280,9 @@ "dev": true }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "requires": { "debug": "2.6.9", @@ -4245,15 +4324,23 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true + } } }, "set-blocking": { @@ -4789,15 +4876,15 @@ "optional": true }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "optional": true }, diff --git a/docs/package.json b/docs/package.json index 79c7f3bf31..f8aea85ac7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -6,6 +6,8 @@ "version": "3.9.0" }, "devDependencies": { + "@meteorjs/meteor-hexo-config": "1.0.14", + "@meteorjs/meteor-theme-hexo": "^2.0.9", "canonical-json": "0.0.4", "chexo": "1.0.7", "handlebars": "4.7.7", @@ -17,15 +19,13 @@ "hexo-server": "1.0.0", "hexo-versioned-netlify-redirects": "1.1.0", "jsdoc": "^4.0.2", - "@meteorjs/meteor-hexo-config": "1.0.14", - "@meteorjs/meteor-theme-hexo": "2.0.8", "showdown": "1.9.1", "underscore": "1.13.1" }, "scripts": { "list-core-packages": "node ./generators/packages-listing/script.js", "generate-history": "node ./generators/changelog/script.js", - "build": "npm run list-core-packages && npm run generate-history && jsdoc/jsdoc.sh && chexo @meteorjs/meteor-hexo-config -- generate", + "build": "npm run list-core-packages && jsdoc/jsdoc.sh && chexo @meteorjs/meteor-hexo-config -- generate", "clean": "hexo clean; rm data/data.js data/names.json", "test": "npm run clean; npm run build", "predeploy": "npm run build", diff --git a/docs/source/api/accounts-multi.md b/docs/source/api/accounts-multi.md index 5dbc358c66..a008b7d807 100644 --- a/docs/source/api/accounts-multi.md +++ b/docs/source/api/accounts-multi.md @@ -151,7 +151,8 @@ password-based users or from an external service login flow. `options` may come from an untrusted client so make sure to validate any values you read from it. The `user` argument is created on the server and contains a proposed user object with all the automatically generated fields -required for the user to log in, including the `_id`. +required for the user to log in, including a temporary `_id` (the final _id is +generated upon document insertion and not available in this function). The function should return the user document (either the one passed in or a newly-created object) with whatever modifications are desired. The returned diff --git a/docs/source/api/collections.md b/docs/source/api/collections.md index a90c3e24ae..355686e820 100644 --- a/docs/source/api/collections.md +++ b/docs/source/api/collections.md @@ -201,10 +201,10 @@ changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass `{reactive: false}` as an option to `find`. -Note that when `fields` are specified, only changes to the included +Note that when `projection` are specified, only changes to the included fields will trigger callbacks in `observe`, `observeChanges` and invalidations in reactive computations using this cursor. Careful use -of `fields` allows for more fine-grained reactivity for computations +of `projection` allows for more fine-grained reactivity for computations that don't depend on an entire document. On the client, there will be a period of time between when the page loads and @@ -216,6 +216,8 @@ collections will be empty. Equivalent to [`find`](#find)`(selector, options).`[`fetch`](#fetch)`()[0]` with `options.limit = 1`. +> **Note**: The `fields` option is deprecated in favor of `projection`, which aligns with MongoDB's official terminology and driver. Using `projection` ensures consistency and clarity in specifying which fields to include or exclude in query results. + {% apibox "Mongo.Collection#findOneAsync" %} Async version of [`findOne`](#findOne) that return a `Promise`. @@ -950,30 +952,30 @@ document objects, and returns -1 if the first document comes first in order, 1 if the second document comes first, or 0 if neither document comes before the other. This is a Minimongo extension to MongoDB. -

Field Specifiers

+

Projection Specifiers

Queries can specify a particular set of fields to include or exclude from the -result object. +result object using the `projection` option. -To exclude specific fields from the result objects, the field specifier is a +To exclude specific fields from the result objects, the projection specifier is a dictionary whose keys are field names and whose values are `0`. All unspecified fields are included. ```js -Users.find({}, { fields: { password: 0, hash: 0 } }); +Users.find({}, { projection: { password: 0, hash: 0 } }); ``` To include only specific fields in the result documents, use `1` as the value. The `_id` field is still included in the result. ```js -Users.find({}, { fields: { firstname: 1, lastname: 1 } }); +Users.find({}, { projection: { firstname: 1, lastname: 1 } }); ``` With one exception, it is not possible to mix inclusion and exclusion styles: the keys must either be all 1 or all 0. The exception is that you may specify `_id: 0` in an inclusion specifier, which will leave `_id` out of the result -object as well. However, such field specifiers can not be used with +object as well. However, such projection specifiers can not be used with [`observeChanges`](#observe_changes), [`observe`](#observe), cursors returned from a [publish function](#meteor_publish), or cursors used in `{% raw %}{{#each}}{% endraw %}` in a template. They may be used with [`fetch`](#fetch), @@ -994,10 +996,12 @@ Users.insert({ name: 'Yagami Light', }); -Users.findOne({}, { fields: { 'alterEgos.name': 1, _id: 0 } }); +Users.findOne({}, { projection: { 'alterEgos.name': 1, _id: 0 } }); // Returns { alterEgos: [{ name: 'Kira' }, { name: 'L' }] } ``` +> Note: The `fields` option is deprecated in favor of `projection`, which is the standard term used by MongoDB. Using `projection` ensures compatibility with MongoDB's documentation and drivers. + See the MongoDB docs for details of the nested field rules and array behavior. diff --git a/docs/source/api/passwords.md b/docs/source/api/passwords.md index bb0ddf6b17..908617aa3e 100644 --- a/docs/source/api/passwords.md +++ b/docs/source/api/passwords.md @@ -59,6 +59,8 @@ By default, an email address is added with `{ verified: false }`. Use [`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an email with a link the user can use to verify their email address. +{% apibox "Accounts.replaceEmailAsync" %} + {% apibox "Accounts.removeEmail" %} {% apibox "Accounts.verifyEmail" %} diff --git a/docs/source/commandline.md b/docs/source/commandline.md index 11138250d5..e1494f5a7c 100644 --- a/docs/source/commandline.md +++ b/docs/source/commandline.md @@ -677,7 +677,7 @@ Your project should be a git repository as the commit hash is going to be used t The `cache-build` option is available since Meteor 1.11. {% endpullquote %} -With the argument `--container-size` you can change your app's container size using the deploy command. The valid arguments are: `tiny`, `compact`, `standard`, `double`, `quad`, `octa`, and `dozen`. One more thing to note here is that the `--container-size` flag can only be used when the `--plan` option is already specified, otherwise using the `--container-size` option will throw an error with the message : `Error deploying application: Internal error`. To see more about the difference and prices of each one you can check [here](https://www.meteor.com/cloud#pricing-section). +With the argument `--container-size` you can change your app's container size using the deploy command. The valid arguments are: `tiny`, `compact`, `standard`, `double`, `quad`, `octa`, and `dozen`. One more thing to note here is that the `--container-size` flag can only be used when the `--plan` option is already specified, otherwise using the `--container-size` option will throw an error with the message : `Error deploying application: Internal error`. To see more about the difference and prices of each one you can check [here](https://galaxycloud.app/meteorjs/pricing). {% pullquote warning %} The `--container-size` option is available since Meteor 2.4.1. diff --git a/docs/source/index.md b/docs/source/index.md index 441b4dbe2b..208309701d 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -2,7 +2,7 @@ title: Docs --- -> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/). +> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://v3-docs.meteor.com).

What is Meteor?

diff --git a/docs/source/install.md b/docs/source/install.md index 1db8b8f1c6..6a58d65cfd 100644 --- a/docs/source/install.md +++ b/docs/source/install.md @@ -8,7 +8,7 @@ You need to install the Meteor command line tool to create, run, and manage your

Node.js version

-> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/). +> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://v3-docs.meteor.com). - Node.js version >= 10 and <= 14 is required. - We recommend you using [nvm](https://github.com/nvm-sh/nvm) or [Volta](https://volta.sh/) for managing Node.js versions. @@ -30,7 +30,8 @@ You need to install the Meteor command line tool to create, run, and manage your Install the latest official version of Meteor.js from your terminal by running one of the commands below. You can check our [changelog](https://docs.meteor.com/changelog.html) for the release notes. -> Run `node -v` to ensure you are using Node.js 14. Meteor 3.0, currently in its Release Candidate version, runs on Node.js v20. +> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3 is released with support for the latest Node.js LTS version. +> For more information, please consult our [migration guide](https://guide.meteor.com/3.0-migration.html) and the [new docs](https://docs.meteor.com/). For Windows, Linux and OS X, you can run the following command: diff --git a/guide/source/deployment.md b/guide/source/deployment.md index c254c1a196..fa9d8f08de 100644 --- a/guide/source/deployment.md +++ b/guide/source/deployment.md @@ -194,7 +194,7 @@ MONGO_URL=mongodb://localhost:27017/myapp ROOT_URL=http://my-app.com PORT=3000 n ``` * `ROOT_URL` is the base URL for your Meteor project -* `PORT` is the port at which the application is running +* `PORT` is the port at which the application is running * `MONGO_URL` is a [Mongo connection string URI](https://docs.mongodb.com/manual/reference/connection-string/) supplied by the MongoDB provider. @@ -322,7 +322,7 @@ Galaxy's UI provides a detailed logging system, which can be invaluable to deter If you really want to understand the ins and outs of running your Meteor application, you should use an Application Performance Monitoring (APM) service. There are multiple services designed for Meteor apps: -- [Meteor APM](https://www.meteor.com/cloud) +- [Meteor APM](https://galaxycloud.app/) - [Monti APM](https://montiapm.com/) - [Meteor Elastic APM](https://github.com/Meteor-Community-Packages/meteor-elastic-apm) diff --git a/guide/source/index.md b/guide/source/index.md index 427c62ae52..0f801b9b88 100644 --- a/guide/source/index.md +++ b/guide/source/index.md @@ -3,7 +3,7 @@ title: Introduction description: This is the guide for using Meteor, a full-stack JavaScript platform for developing modern web and mobile applications. --- -> Meteor 2.x uses the deprecated Node.js 14. Meteor 3.0 has been released and runs on Node.js 20. Check out our [v3 docs](https://v3-docs.meteor.com) and [migration guide](https://v3-migration-docs.meteor.com/). +> Meteor 2.x runs on a deprecated Node.js version (14). Meteor 3.x has been released with support for the latest Node.js LTS version. For more information, please consult our [migration guide](https://v3-migration-docs.meteor.com/) and the [latest docs](https://docs.meteor.com).

What is Meteor?

diff --git a/guide/source/methods.md b/guide/source/methods.md index a9689a2ef1..3db5ca7c3c 100644 --- a/guide/source/methods.md +++ b/guide/source/methods.md @@ -171,35 +171,35 @@ updateText.run.call({ userId: 'abcd' }, { As you can see, this approach to calling Methods results in a better development workflow - you can more easily deal with the different parts of the Method separately and test your code without having to deal with Meteor internals. But this approach requires you to write a lot of boilerplate on the Method definition side. -

Advanced Methods with mdg:validated-method

+

Advanced Methods with jam:method

-To alleviate some of the boilerplate that's involved in correct Method definitions, we've published a wrapper package called `mdg:validated-method` that does most of this for you. Here's the same Method as above, but defined with the package: +To alleviate some of the boilerplate that's involved in correct Method definitions, you can use a package called `jam:method` that does most of this for you. Here's the same Method as above, but defined with the package: ```js -import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { createMethod } from 'meteor/jam:method'; -export const updateText = new ValidatedMethod({ +export const updateText = createMethod({ name: 'todos.updateText', - validate: new SimpleSchema({ + schema: new SimpleSchema({ todoId: { type: String }, newText: { type: String } - }).validator(), - run({ todoId, newText }) { - const todo = Todos.findOne(todoId); - + }), + async run({ todoId, newText }) { + const todo = await Todos.findOneAsync(todoId); + if (!todo.editableBy(this.userId)) { throw new Meteor.Error('todos.updateText.unauthorized', 'Cannot edit todos in a private list that is not yours'); } - Todos.update(todoId, { + Todos.updateAsync(todoId, { $set: { text: newText } }); } }); ``` -You call it the same way you call the advanced Method above, but the Method definition is significantly simpler. We believe this style of Method lets you clearly see the important parts - the name of the Method sent over the wire, the format of the expected arguments, and the JavaScript namespace by which the Method can be referenced. Validated methods only accept a single argument and a callback function. +You call it the same way you call the advanced Method above, but the Method definition is significantly simpler. We believe this style of Method lets you clearly see the important parts - the name of the Method sent over the wire, the format of the expected arguments, and the JavaScript namespace by which the Method can be referenced.

Error handling

@@ -227,17 +227,13 @@ When the server was not able to complete the user's desired action because of a When a Method call fails because the arguments are of the wrong type, it's good to throw a `ValidationError`. This works like `Meteor.Error`, but is a custom constructor that enforces a standard error format that can be read by different form and validation libraries. In particular, if you are calling this Method from a form, throwing a `ValidationError` will make it possible to display nice error messages next to particular fields in the form. -When you use `mdg:validated-method` with `simpl-schema` as demonstrated above, this type of error is thrown for you. - -Read more about the error format in the [`mdg:validation-error` docs](https://atmospherejs.com/mdg/validation-error). -

Handling errors

When you call a Method, any errors thrown by it will be returned in the callback. At this point, you should identify which error type it is and display the appropriate message to the user. In this case, it is unlikely that the Method will throw a `ValidationError` or an internal server error, so we will only handle the unauthorized error: ```js // Call the Method -updateText.call({ +updateText({ todoId: '12345', newText: 'This is a todo item.' }, (err, res) => { @@ -261,7 +257,7 @@ We'll talk about how to handle the `ValidationError` in the section on forms bel

Errors in Method simulation

-When a Method is called, it usually runs twice---once on the client to simulate the result for Optimistic UI, and again on the server to make the actual change to the database. This means that if your Method throws an error, it will likely fail on the client _and_ the server. For this reason, `ValidatedMethod` turns on undocumented option in Meteor to avoid calling the server-side implementation if the simulation throws an error. +When a Method is called, it usually runs twice---once on the client to simulate the result for Optimistic UI, and again on the server to make the actual change to the database. This means that if your Method throws an error, it will likely fail on the client _and_ the server. For this reason, `jam:method` turns on [an option](https://github.com/jamauro/method#options-for-meteorapplyasync) in Meteor to avoid calling the server-side implementation if the simulation throws an error. While this behavior is good for saving server resources in cases where a Method will certainly fail, it's important to make sure that the simulation doesn't throw an error in cases where the server Method would have succeeded (for example, if you didn't load some data on the client that the Method needs to do the simulation properly). In this case, you can wrap server-side-only logic in a block that checks for a method simulation: @@ -283,13 +279,13 @@ const amountRegEx = /^\d*\.(\d\d)?$/; // This Method encodes the form validation requirements. // By defining them in the Method, we do client and server-side // validation in one place. -export const insert = new ValidatedMethod({ +export const insert = createMethod({ name: 'Invoices.methods.insert', - validate: new SimpleSchema({ + schema: new SimpleSchema({ email: { type: String, regEx: emailRegEx }, description: { type: String, min: 5 }, amount: { type: String, regEx: amountRegEx } - }).validator(), + }), run(newInvoice) { // In here, we can be sure that the newInvoice argument is // validated. @@ -299,7 +295,7 @@ export const insert = new ValidatedMethod({ 'Must be logged in to create an invoice.'); } - Invoices.insert(newInvoice) + Invoices.insertAsync(newInvoice) } }); ``` @@ -355,7 +351,7 @@ Template.Invoices_newInvoice.events({ amount: event.target.amount.value }; - insert.call(data, (err, res) => { + insert(data, (err, res) => { if (err) { if (err.error === 'validation-error') { // Initialize error object @@ -434,9 +430,9 @@ If we defined this Method in client and server code, as all Methods should be, a The client enters a special mode where it tracks all changes made to client-side collections, so that they can be rolled back later. When this step is complete, the user of your app sees their UI update instantly with the new content of the client-side database, but the server hasn't received any data yet. -If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2). If you are using `ValidatedMethod` or pass a special `throwStubExceptions` option to `Meteor.apply`, then an exception thrown from the simulation will stop the server-side Method from running at all. +If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2). If you are using `jam:method` or pass a special `throwStubExceptions` [option](https://github.com/jamauro/method#options-for-meteorapplyasync) to `Meteor.apply`, then an exception thrown from the simulation will stop the server-side Method from running at all. -The return value of the Method simulation is discarded, unless the `returnStubValue` option is passed when calling the Method, in which case it is returned to the Method caller. ValidatedMethod passes this option by default. +The return value of the Method simulation is discarded, unless the `returnStubValue` option is passed when calling the Method, in which case it is returned to the Method caller. `jam:method` passes this option by default.

2. A `method` DDP message is sent to the server

diff --git a/guide/source/performance-improvement.md b/guide/source/performance-improvement.md index f6721550f7..28298e8232 100644 --- a/guide/source/performance-improvement.md +++ b/guide/source/performance-improvement.md @@ -3,10 +3,10 @@ title: Performance improvements description: How to optimize your Meteor application for higher performance when you start growing. --- -This guide focuses on providing you tips and common practices on how to improve performance of your Meteor app (sometimes also called scaling). -It is important to note that at the end of the day Meteor is a Node.js app tied closely to MongoDB, -so a lot of the problems you are going to encounter are common to other Node.js and MongoDB apps. -Also do note that every app is different so there are unique challenges to each, therefore +This guide focuses on providing you tips and common practices on how to improve performance of your Meteor app (sometimes also called scaling). +It is important to note that at the end of the day Meteor is a Node.js app tied closely to MongoDB, +so a lot of the problems you are going to encounter are common to other Node.js and MongoDB apps. +Also do note that every app is different so there are unique challenges to each, therefore practices describe in this guide should be used as a guiding posts rather than absolutes. This guide has been heavily inspired by [Marcin Szuster's Vazco article](https://www.vazco.eu/blog/how-to-optimize-and-scale-meteor-projects), the official [Meteor Galaxy guide](https://galaxy-guide.meteor.com/), @@ -15,11 +15,11 @@ and talk by Paulo Mogollón's talk at Impact 2022 titled ["First steps on scalin

Performance monitoring

Before any optimization can take place we need to know what is our problem. This is where APM (Application Performance Monitor) comes in. -If you are hosting on Galaxy then this is automatically included in the [Professional plan](https://www.meteor.com/cloud/pricing) -and you can learn more about in its [own dedicated guide article](https://cloud-guide.meteor.com/apm-getting-started.html). -For those hosting outside of Galaxy the most popular solution is to go with [Monti APM](https://montiapm.com/) which shares -all the main functionality with Galaxy APM. You can also choose other APM for Node.js, but they will not show you Meteor -specific data that Galaxy APM and Monti APM specialize in. For this guide we will focus on showing how to work with Galaxy APM, +If you are hosting on Galaxy then this is automatically included in the [Professional plan](https://galaxycloud.app/meteorjs/pricing) +and you can learn more about in its [own dedicated guide article](https://cloud-guide.meteor.com/apm-getting-started.html). +For those hosting outside of Galaxy the most popular solution is to go with [Monti APM](https://montiapm.com/) which shares +all the main functionality with Galaxy APM. You can also choose other APM for Node.js, but they will not show you Meteor +specific data that Galaxy APM and Monti APM specialize in. For this guide we will focus on showing how to work with Galaxy APM, which is the same as with Monti APM, for simplicity. Once you setup either of those APMs you will need to add a package to your Meteor app to start sending them data. @@ -37,9 +37,9 @@ meteor add montiapm:agent ```

Finding issues in APM

-APM will start with providing you with an overview of how your app is performing. You can then dive deep into details of -publications, methods, errors happening (both on client and server) and more. You will spend a lot of time in the detailed -tabs looking for methods and publications to improve and analyzing the impact of your actions. The process, for example for +APM will start with providing you with an overview of how your app is performing. You can then dive deep into details of +publications, methods, errors happening (both on client and server) and more. You will spend a lot of time in the detailed +tabs looking for methods and publications to improve and analyzing the impact of your actions. The process, for example for optimizing methods, will look like this: 1. Go to the detailed view under the Methods tab. @@ -52,14 +52,14 @@ Not every long-performing method has to be improved. Take a look at the followin * methodX - mean response time 1 515 ms, throughput 100,05/min * methodY - mean response time 34 000 ms, throughput 0,03/min -At first glance, the 34 seconds response time can catch your attention, and it may seem that the methodY -is more relevant to improvement. But don’t ignore the fact that this method is being used only once in +At first glance, the 34 seconds response time can catch your attention, and it may seem that the methodY +is more relevant to improvement. But don’t ignore the fact that this method is being used only once in a few hours by the system administrators or scheduled cron action. -And now, let’s take a look at the methodX. Its response time is evidently lower BUT compared to the frequency +And now, let’s take a look at the methodX. Its response time is evidently lower BUT compared to the frequency of use, it is still high, and without any doubt should be optimized first. -It’s also absolutely vital to remember that you shouldn't optimize everything as it goes. +It’s also absolutely vital to remember that you shouldn't optimize everything as it goes. The key is to think strategically and match the most critical issues with your product priorities. For more information about all the things you can find in Galaxy APM take a look at the Meteor APM section in [Galaxy Guide](https://galaxy-guide.meteor.com/apm-getting-started.html). @@ -71,75 +71,75 @@ At the same this is the most resource intensive part of a Meteor application. Under the hood WebSockets are being used with additional abilities provided by DDP.

Proper use of publications

-Since publications can get resource intensive they should be reserved for usage that requires up to date, live data or +Since publications can get resource intensive they should be reserved for usage that requires up to date, live data or that are changing frequently and you need the users to see that. -You will need to evaluate your app to figure out which situations these are. As a rule of thumb any data that are not -required to be live or are not changing frequently can be fetched once via other means and re-fetched as needed, +You will need to evaluate your app to figure out which situations these are. As a rule of thumb any data that are not +required to be live or are not changing frequently can be fetched once via other means and re-fetched as needed, in most cases the re-fetching shouldn't be necessary. -But even before you proceed any further there are a few improvements that you can make here. +But even before you proceed any further there are a few improvements that you can make here. First make sure that you only get the fields you need, limit the number of documents you send to the client to what you need (aka always set the `limit` option) and ensure that you have set all your indexes.

Methods over publications

-The first easiest replacement is to use Meteor methods instead of publications. In this case you can use the existing publication -and instead of returning a cursor you will call `.fetchAsync()` and return the actual data. The same performance improvements +The first easiest replacement is to use Meteor methods instead of publications. In this case you can use the existing publication +and instead of returning a cursor you will call `.fetchAsync()` and return the actual data. The same performance improvements to get the method work faster apply here, but once called it sends the data and you don't have the overhead of a publication. -What is crucial here is to ensure that your choice of a front-end framework doesn't call the method every time, but only once +What is crucial here is to ensure that your choice of a front-end framework doesn't call the method every time, but only once to load the data or when specifically needed (for example when the data gets updated due to user action or when the user requests it).

Publication replacements

Using methods has its limitations and there are other tools that you might want to evaluate as a potential replacement. -[Grapher](https://github.com/cult-of-coders/grapher) is a favorite answer and allows you to easily blend with another -replacement which is [GraphQL](https://graphql.org/) and in particular [Apollo GraphQL](https://www.apollographql.com/), +[Grapher](https://github.com/cult-of-coders/grapher) is a favorite answer and allows you to easily blend with another +replacement which is [GraphQL](https://graphql.org/) and in particular [Apollo GraphQL](https://www.apollographql.com/), which also has an integration [package](https://atmospherejs.com/meteor/apollo) with Meteor. Finally, you can also go back to using REST as well. Do note, that you can mix all of these based on your needs.

Low observer reuse

-Observers are among the key components of Meteor. They take care of observing documents on MongoDB and they notify changes. +Observers are among the key components of Meteor. They take care of observing documents on MongoDB and they notify changes. Creating them is an expensive operations, so you want to make sure that Meteor reuses them as much as possible. > [Learn more about observers](https://galaxy-guide.meteor.com/apm-know-your-observers.html) -The key for observer reuse is to make sure that the queries requested are identical. This means that user given values -should be standardised and so should any dynamic input like time. Publications for users should check if user is signed in +The key for observer reuse is to make sure that the queries requested are identical. This means that user given values +should be standardised and so should any dynamic input like time. Publications for users should check if user is signed in first before returning publication and if user is not signed in, then it should instead call `this.ready();`. > [Learn more on improving observer reuse](https://galaxy-guide.meteor.com/apm-improve-cpu-and-network-usage)

Redis Oplog

-[Redis Oplog](https://atmospherejs.com/cultofcoders/redis-oplog) is a popular solution to Meteor's Oplog tailing -(which ensures the reactivity, but has some severe limitations that especially impact performance). Redis Oplog as name -suggests uses [redis](https://redis.io/) to track changes to data that you only need and cache them. This reduces load on +[Redis Oplog](https://atmospherejs.com/cultofcoders/redis-oplog) is a popular solution to Meteor's Oplog tailing +(which ensures the reactivity, but has some severe limitations that especially impact performance). Redis Oplog as name +suggests uses [redis](https://redis.io/) to track changes to data that you only need and cache them. This reduces load on the server and database, allows you to track only the data that you want and only publish the changes you need.

Methods

-While methods are listed as one of the possible replacements for publications, they themselves can be made more performant, -after all it really depends on what you put inside them and APM will provide you with the necessary insight on which +While methods are listed as one of the possible replacements for publications, they themselves can be made more performant, +after all it really depends on what you put inside them and APM will provide you with the necessary insight on which methods are the problem.

Heavy actions

-In general heavy tasks that take a lot of resources or take long and block the server for that time should be taken out -and instead be run in its own server that focuses just on running those heavy tasks. This can be another Meteor server +In general heavy tasks that take a lot of resources or take long and block the server for that time should be taken out +and instead be run in its own server that focuses just on running those heavy tasks. This can be another Meteor server or even better something specifically optimized for that given task.

Reoccurring jobs

-Reoccurring jobs are another prime candidate to be taken out into its own application. What this means is that you will have -an independent server that is going to be tasked with running the reoccurring jobs and the main application will only add to +Reoccurring jobs are another prime candidate to be taken out into its own application. What this means is that you will have +an independent server that is going to be tasked with running the reoccurring jobs and the main application will only add to the list and be recipient of the results, most likely via database results.

Rate limiting

-Rate limit your methods to reduce effectiveness of DDOS attack and spare your server. This is also a good practice to -ensure that you don't accidentally DDOS your self. For example a user who clicks multiple time on a button that triggers -an expensive function. In this example you should also in general ensure that any button that triggers a server event +Rate limit your methods to reduce effectiveness of DDOS attack and spare your server. This is also a good practice to +ensure that you don't accidentally DDOS your self. For example a user who clicks multiple time on a button that triggers +an expensive function. In this example you should also in general ensure that any button that triggers a server event should be disabled until there is a response from the server that the event has finished. You can and should rate limit both methods and collections. @@ -154,15 +154,15 @@ These are all applicable, and you should spend some time researching into them a

IP whitelisting

-If your MongoDB hosting provider allows it, you should make sure that you whitelist the IPs of your application servers. -If you don't then your database servers are likely to come under attack from hackers trying to brute force their way in. +If your MongoDB hosting provider allows it, you should make sure that you whitelist the IPs of your application servers. +If you don't then your database servers are likely to come under attack from hackers trying to brute force their way in. Besides the security risk this also impacts performance as authentication is not a cheap operation and it will impact performance. See [Galaxy guide](https://galaxy-guide.meteor.com/container-environment.html#network-outgoing) on IP whitelisting to get IPs for your Galaxy servers.

Indexes

-While single indexes on one field are helpful on simple query calls, you will most likely have more advance queries with +While single indexes on one field are helpful on simple query calls, you will most likely have more advance queries with multiple variables. To cover those you will need to create compound indexes. For example: ```javascript @@ -177,7 +177,7 @@ Statistics.createIndexAsync( ``` When creating indexes you should sort the variables in ESR (equity, sort, range) style. Meaning, first you put variables that will be equal to something specific. Second you put variables that sort things, -and third variables that provide range for that query. +and third variables that provide range for that query. Further you should order these variables in a way that the fields that filter the most should be first. Make sure that all the indexes are used and remove unused indexes as leaving unused indexes will have negative impact @@ -187,7 +187,7 @@ on performance as the database will have to still keep track on all the indexed To optimize finds ensure that all queries have are indexed. Meaning that any `.find()` variables should be indexed as described above. -All your finds should have a limit on the return so that the database stops going through the data once it has reached +All your finds should have a limit on the return so that the database stops going through the data once it has reached the limit, and you only return the limited number of results instead of the whole database. Beware of queries with `n + 1` issue. For example in a database that has cars and car owners. You don't want to get cars, @@ -202,14 +202,14 @@ If you still have issues make sure that you read data from secondaries.

Beware of collection hooks

-While collection hooks can help in many cases beware of them and make sure that you understand how they work as they might -create additional queries that you might not know about. Make sure to review packages that use them so that they won't +While collection hooks can help in many cases beware of them and make sure that you understand how they work as they might +create additional queries that you might not know about. Make sure to review packages that use them so that they won't create additional queries.

Caching

Once your user base increases you want to invest into query caching like using Redis, Redis Oplog and other. -For more complex queries or when you are retrieving data from multiple collections, then you want to use [aggregation](https://www.mongodb.com/docs/manual/aggregation/) +For more complex queries or when you are retrieving data from multiple collections, then you want to use [aggregation](https://www.mongodb.com/docs/manual/aggregation/) and save their results.

Scaling

@@ -229,13 +229,13 @@ Galaxy has these as well. Learn more about [setting triggers for scaling on Gala Setting this is vital, so that your application can keep on running when you have extra people come and then saves you money by scaling down when the containers are not in use. When initially setting these pay a close attention to the performance of your app. you need to learn when is the right time to scale your app so it has enough time to spin up new containers before the existing one get overwhelmed by traffic and so on. -There are other points to pay attention to as well. For example if your app is used by corporation you might want to setup that on weekdays the minimum number of containers is going to increase just before the start of working hours and the then decrease the minimum to 1 for after hours and on weekends. +There are other points to pay attention to as well. For example if your app is used by corporation you might want to setup that on weekdays the minimum number of containers is going to increase just before the start of working hours and the then decrease the minimum to 1 for after hours and on weekends. Usually when you are working on performance issues you will have higher numbers of containers as you optimize your app. It is therefore vital to revisit your scaling setting after each rounds of improvements to ensure that scaling triggers are properly optimized.

Packages

-During development, it is very tempting to add packages to solve issue or support some features. -This should be done carefully and each package should be wetted carefully if it is a good fit for the application. -Besides security and maintenance issues you also want to know which dependencies given package introduces and +During development, it is very tempting to add packages to solve issue or support some features. +This should be done carefully and each package should be wetted carefully if it is a good fit for the application. +Besides security and maintenance issues you also want to know which dependencies given package introduces and as a whole what will be the impact on performance. diff --git a/guide/source/security.md b/guide/source/security.md index e50420b98d..157c60dd01 100644 --- a/guide/source/security.md +++ b/guide/source/security.md @@ -36,9 +36,9 @@ Each of these points will have their own section below.

Avoid allow/deny

-In this guide, we're going to take a strong position that using [allow](http://docs.meteor.com/#/full/allow) or [deny](http://docs.meteor.com/#/full/deny) to run MongoDB queries directly from the client is not a good idea. The main reason is that it is hard to follow the principles outlined above. It's extremely difficult to validate the complete space of possible MongoDB operators, which could potentially grow over time with new versions of MongoDB. +In this guide, we're going to take a strong position that using [allow](https://docs.meteor.com/api/collections.html#Mongo-Collection-allow) or [deny](https://docs.meteor.com/api/collections.html#Mongo-Collection-deny) to run MongoDB queries directly from the client is not a good idea. The main reason is that it is hard to follow the principles outlined above. It's extremely difficult to validate the complete space of possible MongoDB operators, which could potentially grow over time with new versions of MongoDB. -There have been several articles about the potential pitfalls of accepting MongoDB update operators from the client, in particular the [Allow & Deny Security Challenge](https://www.discovermeteor.com/blog/allow-deny-security-challenge/) and its [results](https://www.discovermeteor.com/blog/allow-deny-challenge-results/), both on the Discover Meteor blog. +There have been several articles about the potential pitfalls of accepting MongoDB update operators from the client, in particular the [Allow & Deny Security Challenge](https://web.archive.org/web/20220705130732/https://www.discovermeteor.com/blog/allow-deny-security-challenge/) and its [results](https://web.archive.org/web/20220819163744/https://www.discovermeteor.com/blog/allow-deny-challenge-results/), both on the Discover Meteor blog. Given the points above, we recommend that all Meteor apps should use Methods to accept data input from the client, and restrict the arguments accepted by each Method as tightly as possible. @@ -80,9 +80,9 @@ Meteor.methods({ If someone comes along and passes a non-ID selector like `{}`, they will end up deleting the entire collection. -

mdg:validated-method

+

jam:method

-To help you write good Methods that exhaustively validate their arguments, we've written a wrapper package for Methods that enforces argument validation. Read more about how to use it in the [Methods article](methods.html#validated-method). The rest of the code samples in this article will assume that you are using this package. If you aren't, you can still apply the same principles but the code will look a little different. +To help you write good Methods that exhaustively validate their arguments, you can use a community package for Methods that enforces argument validation. Read more about how to use it in the [Methods article](methods.html#jam-method). The rest of the code samples in this article will assume that you are using this package. If you aren't, you can still apply the same principles but the code will look a little different.

Don't pass userId from the client

@@ -116,25 +116,25 @@ The _only_ times you should be passing any user ID as an argument are the follow The best way to make your app secure is to understand all of the possible inputs that could come from an untrusted source, and make sure that they are all handled correctly. The easiest way to understand what inputs can come from the client is to restrict them to as small of a space as possible. This means your Methods should all be specific actions, and shouldn't take a multitude of options that change the behavior in significant ways. The end goal is that you can look at each Method in your app and validate or test that it is secure. Here's a secure example Method from the Todos example app: ```js -export const makePrivate = new ValidatedMethod({ +export const makePrivate = new createMethod({ name: 'lists.makePrivate', validate: new SimpleSchema({ listId: { type: String } }).validator(), - run({ listId }) { + async run({ listId }) { if (!this.userId) { throw new Meteor.Error('lists.makePrivate.notLoggedIn', 'Must be logged in to make private lists.'); } - const list = Lists.findOne(listId); + const list = await Lists.findOneAsync(listId); if (list.isLastPublicList()) { throw new Meteor.Error('lists.makePrivate.lastPublicList', 'Cannot make the last public list private.'); } - Lists.update(listId, { + await Lists.updateAsync(listId, { $set: { userId: this.userId } }); @@ -148,16 +148,16 @@ You can see that this Method does a _very specific thing_ - it makes a single li However, this doesn't mean you can't have any flexibility in your Methods. Let's look at an example: ```js -Meteor.users.methods.setUserData = new ValidatedMethod({ +Meteor.users.methods.setUserData = new createMethod({ name: 'Meteor.users.methods.setUserData', validate: new SimpleSchema({ fullName: { type: String, optional: true }, dateOfBirth: { type: Date, optional: true }, }).validator(), - run(fieldsToSet) { - Meteor.users.update(this.userId, { + async run(fieldsToSet) { + return (await Meteor.users.updateAsync(this.userId, { $set: fieldsToSet - }); + })); } }); ``` @@ -200,7 +200,8 @@ if (Meteor.isServer) { This will make every Method only callable 5 times per second per connection. This is a rate limit that shouldn't be noticeable by the user at all, but will prevent a malicious script from totally flooding the server with requests. You will need to tune the limit parameters to match your app's needs. -If you're using validated methods, there's an available [ddp-rate-limiter-mixin](https://github.com/nlhuykhang/ddp-rate-limiter-mixin). +If you're using `jam:method`, it comes with built in [rate-limiting](https://github.com/jamauro/method#rate-limiting). +

Publications

@@ -274,10 +275,10 @@ Publications are not reactive, and they only re-run when the currently logged in ```js // #1: Bad! If the owner of the list changes, the old owner will still see it -Meteor.publish('list', function (listId) { +Meteor.publish('list', async function (listId) { check(listId, String); - const list = Lists.findOne(listId); + const list = await Lists.findOneAsync(listId); if (list.userId !== this.userId) { throw new Meteor.Error('list.unauthorized', @@ -351,7 +352,7 @@ export const MMR = { ```js // In a file loaded on client and server -Meteor.users.methods.updateMMR = new ValidatedMethod({ +Meteor.users.methods.updateMMR = new createMethod({ name: 'Meteor.users.methods.updateMMR', validate: null, run() { diff --git a/guide/source/testing.md b/guide/source/testing.md index f7500c2db4..7dfffb4a51 100644 --- a/guide/source/testing.md +++ b/guide/source/testing.md @@ -238,9 +238,11 @@ import { Tracker } from 'meteor/tracker'; const withDiv = function withDiv(callback) { const el = document.createElement('div'); document.body.appendChild(el); + let view = null try { - callback(el); + view = callback(el); } finally { + if (view) Blaze.remove(view) document.body.removeChild(el); } }; @@ -248,9 +250,10 @@ const withDiv = function withDiv(callback) { export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) { withDiv((el) => { const ourTemplate = isString(template) ? Template[template] : template; - Blaze.renderWithData(ourTemplate, data, el); + const view = Blaze.renderWithData(ourTemplate, data, el); Tracker.flush(); callback(el); + return view }); }; ``` diff --git a/guide/source/ui-ux.md b/guide/source/ui-ux.md index 4578cced7b..d4235c0f1e 100644 --- a/guide/source/ui-ux.md +++ b/guide/source/ui-ux.md @@ -11,9 +11,9 @@ Meteor supports many view layers. The most popular are: - [React](react.html): official [page](http://reactjs.org/) - [Blaze](blaze.html): official [page](http://blazejs.org/) -- [Angular](http://www.angular-meteor.com): official [page](https://angular.io/) +- [Angular](angular.html): official [page](https://angular.io/) - [Vue](vue.html): official [page](https://vuejs.org/) -- [Svelte](https://www.meteor.com/tutorials/svelte/creating-an-app): official [page](https://svelte.dev/) +- [Svelte](svelte.html): official [page](https://svelte.dev/) If you are starting with web development we recommend that you use Blaze as it's very simple to learn. diff --git a/meteor b/meteor index 47016dc625..517e4bd0fe 100755 --- a/meteor +++ b/meteor @@ -1,7 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=20.17.0.6 - +BUNDLE_VERSION=22.18.0.3 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. @@ -123,6 +122,7 @@ fi DEV_BUNDLE="$SCRIPT_DIR/dev_bundle" METEOR="$SCRIPT_DIR/tools/index.js" +PROCESS_REQUIRES="$SCRIPT_DIR/tools/node-process-warnings.js" # Set the nofile ulimit as high as permitted by the hard-limit/kernel if [ "$(ulimit -Sn)" != "unlimited" ]; then @@ -148,5 +148,6 @@ fi exec "$DEV_BUNDLE/bin/node" \ --max-old-space-size=4096 \ --no-wasm-code-gc \ + --require="$PROCESS_REQUIRES"\ ${TOOL_NODE_FLAGS} \ "$METEOR" "$@" diff --git a/meteor.bat b/meteor.bat index aa6b05d750..1f0f75d081 100644 --- a/meteor.bat +++ b/meteor.bat @@ -48,6 +48,7 @@ SET BABEL_CACHE_DIR=%~dp0\.babel-cache "%~dp0\dev_bundle\bin\node.exe" ^ --no-wasm-code-gc ^ + --require="%~dp0\tools\node-process-warnings.js" ^ %TOOL_NODE_FLAGS% ^ "%~dp0\tools\index.js" %* diff --git a/npm-packages/eslint-plugin-meteor/README.md b/npm-packages/eslint-plugin-meteor/README.md index fd322c5f93..fff5044a51 100644 --- a/npm-packages/eslint-plugin-meteor/README.md +++ b/npm-packages/eslint-plugin-meteor/README.md @@ -56,7 +56,7 @@ meteor Building an application with Meteor? -* Deploy on Galaxy hosting: https://www.meteor.com/cloud +* Deploy on Galaxy hosting: https://galaxycloud.app/ * Announcement list: sign up at https://www.meteor.com/ * Discussion forums: https://forums.meteor.com/ * Join the Meteor community Slack by clicking this [invite link](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc). diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-server-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-server-package.js index 1108a6ef0d..b29072cb17 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-server-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-server-package.js @@ -9,7 +9,7 @@ var packageJson = { private: true, dependencies: { promise: "8.1.0", - "@meteorjs/reify": "0.24.0", + "@meteorjs/reify": "0.25.3", "@babel/parser": "7.17.0", "@types/underscore": "1.11.4", underscore: "1.13.6", diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js index 09adbc5e91..aedf49c8c0 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js @@ -10,13 +10,13 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "10.8.2", + npm: "10.9.3", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", "node-gyp": "9.4.0", "@mapbox/node-pre-gyp": "1.0.11", - typescript: "5.4.5", - "@meteorjs/babel": "7.19.0-beta.3", - "@meteorjs/reify": "0.24.0", + typescript: "5.6.3", + "@meteorjs/babel": "7.20.0", + "@meteorjs/reify": "0.25.3", // So that Babel can emit require("@babel/runtime/helpers/...") calls. "@babel/runtime": "7.15.3", // For backwards compatibility with isopackets that still depend on diff --git a/npm-packages/meteor-babel/package-lock.json b/npm-packages/meteor-babel/package-lock.json index 2bca50faa9..3ded2f7c42 100644 --- a/npm-packages/meteor-babel/package-lock.json +++ b/npm-packages/meteor-babel/package-lock.json @@ -1,6 +1,6 @@ { "name": "@meteorjs/babel", - "version": "7.20.0-beta.5", + "version": "7.20.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -786,9 +786,9 @@ } }, "@meteorjs/reify": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.2.tgz", - "integrity": "sha512-mkaSPyzovKf86wSA4ouCmXUQkASA8qNCXp71/Tbm0tD/bpiaja3measRB1HPA+yLXq9Xq3+8GLh8ytJu98cwIQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.4.tgz", + "integrity": "sha512-/HwynJK85QtS2Rm26M9TS8aEMnqVJ2TIzJNJTGAQz+G6cTYmJGWaU4nFH96oxiDIBbnT6Y3TfX92HDuS9TtNRg==", "requires": { "acorn": "^8.8.1", "magic-string": "^0.25.3", @@ -797,21 +797,21 @@ }, "dependencies": { "semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==" + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" } } }, "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==" }, "ansi-colors": { "version": "3.2.3", diff --git a/npm-packages/meteor-babel/package.json b/npm-packages/meteor-babel/package.json index e065b0f555..24771f96c0 100644 --- a/npm-packages/meteor-babel/package.json +++ b/npm-packages/meteor-babel/package.json @@ -1,7 +1,7 @@ { "name": "@meteorjs/babel", "author": "Meteor ", - "version": "7.20.0-beta.5", + "version": "7.20.1", "license": "MIT", "type": "commonjs", "description": "Babel wrapper package for use with Meteor", @@ -42,7 +42,7 @@ "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", "@babel/types": "^7.17.0", - "@meteorjs/reify": "0.25.2", + "@meteorjs/reify": "0.25.4", "babel-preset-meteor": "^7.10.0", "babel-preset-minify": "^0.5.1", "convert-source-map": "^1.6.0", diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index 6ad22281f3..1430bcab86 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -1,105 +1,106 @@ -## Meteor Installer - -### Recommended Versions - -- For Meteor 2 (Legacy) - - Use Node.js 14.x - - Use npm 6.x -- For Meteor 3 - - Use Node.js 20.x or higher - - Use npm 9.x or higher - -### Installation - -To install Meteor, run the following command: - -```bash -npx meteor -``` - -It will install Meteor's latest version, alternatively you can install a specific version by running: - -```bash -npx meteor@ -``` - -This command will execute the Meteor installer without adding it permanently to your global npm packages. - -For more information, visit: - -- [Meteor 2 Installation Guide (Legacy)](https://v2-docs.meteor.com/install.html) -- [**Meteor 3 Installation Guide**](https://v3-docs.meteor.com/about/install.html) - - - - - -### Important Note - -This npm package is not the Meteor framework itself; it is just an installer. Do not include it as a dependency in your project, as doing so may break your deployment. - -### Path Management - -By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running: - -```bash -npm install -g meteor --ignore-meteor-setup-exec-path -``` - -(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`) - -### Proxy Configuration - -Set the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL to download Meteor files through the configured proxy. - -### Meteor Version Compatibility - -| NPM Package | Meteor Official Release | -|-------------|-------------------------| -| 3.0.3 | 3.0.3 | -| 3.0.2 | 3.0.2 | -| 3.0.1 | 3.0.1 | -| 3.0.0 | 3.0 | -| 2.16.0 | 2.16.0 | -| 2.15.0 | 2.15.0 | -| 2.14.0 | 2.14.0 | -| 2.13.3 | 2.13.3 | -| 2.13.1 | 2.13.1 | -| 2.13.0 | 2.13.0 | -| 2.12.1 | 2.12.0 | -| 2.12.0 | 2.12.0 | -| 2.11.0 | 2.11.0 | -| 2.10.0 | 2.10.0 | -| 2.9.1 | 2.9.1 | -| 2.9.0 | 2.9.0 | -| 2.8.2 | 2.8.1 | -| 2.8.1 | 2.8.1 | -| 2.8.0 | 2.8.0 | -| 2.7.5 | 2.7.3 | -| 2.7.4 | 2.7.3 | -| 2.7.3 | 2.7.2 | -| 2.7.2 | 2.7.1 | -| 2.7.1 | 2.7 | -| 2.7.0 | 2.7 | -| 2.6.2 | 2.6.1 | -| 2.6.1 | 2.6 | -| 2.6.0 | 2.6 | -| 2.5.9 | 2.5.8 | -| 2.5.8 | 2.5.7 | -| 2.5.7 | 2.5.6 | -| 2.5.6 | 2.5.5 | -| 2.5.5 | 2.5.4 | -| 2.5.4 | 2.5.3 | -| 2.5.3 | 2.5.2 | -| 2.5.2 | 2.5.1 | -| 2.5.1 | 2.5.1 | -| 2.5.0 | 2.5 | -| 2.4.1 | 2.4 | -| 2.4.0 | 2.4 | -| 2.3.7 | 2.3.6 | -| 2.3.6 | 2.3.5 | -| 2.3.5 | 2.3.5 | -| 2.3.4 | 2.3.4 | -| 2.3.3 | 2.3.2 | -| 2.3.2 | 2.3.1 | -| 2.3.1 | 2.2.1 | +## Meteor Installer + +### Recommended Versions + +- For Meteor 2 (Legacy) + - Use Node.js 14.x + - Use npm 6.x +- For Meteor 3 + - Use Node.js 20.x or higher + - Use npm 9.x or higher + +### Installation + +To install Meteor, run the following command: + +```bash +npx meteor +``` + +It will install Meteor's latest version, alternatively you can install a specific version by running: + +```bash +npx meteor@ +``` + +This command will execute the Meteor installer without adding it permanently to your global npm packages. + +For more information, visit: + +- [Meteor 2 Installation Guide (Legacy)](https://v2-docs.meteor.com/install.html) +- [**Meteor 3 Installation Guide**](https://v3-docs.meteor.com/about/install.html) + + + + + +### Important Note + +This npm package is not the Meteor framework itself; it is just an installer. Do not include it as a dependency in your project, as doing so may break your deployment. + +### Path Management + +By default, the Meteor installer adds its install path (by default, `~/.meteor/`) to your PATH by updating either your `.bashrc`, `.bash_profile`, or `.zshrc` as appropriate. To disable this behavior, install Meteor by running: + +```bash +npm install -g meteor --ignore-meteor-setup-exec-path +``` + +(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`) + +### Proxy Configuration + +Set the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL to download Meteor files through the configured proxy. + +### Meteor Version Compatibility + +| NPM Package | Meteor Official Release | +|-------------|-------------------------| +| 3.0.4 | 3.0.4 | +| 3.0.3 | 3.0.3 | +| 3.0.2 | 3.0.2 | +| 3.0.1 | 3.0.1 | +| 3.0.0 | 3.0 | +| 2.16.0 | 2.16.0 | +| 2.15.0 | 2.15.0 | +| 2.14.0 | 2.14.0 | +| 2.13.3 | 2.13.3 | +| 2.13.1 | 2.13.1 | +| 2.13.0 | 2.13.0 | +| 2.12.1 | 2.12.0 | +| 2.12.0 | 2.12.0 | +| 2.11.0 | 2.11.0 | +| 2.10.0 | 2.10.0 | +| 2.9.1 | 2.9.1 | +| 2.9.0 | 2.9.0 | +| 2.8.2 | 2.8.1 | +| 2.8.1 | 2.8.1 | +| 2.8.0 | 2.8.0 | +| 2.7.5 | 2.7.3 | +| 2.7.4 | 2.7.3 | +| 2.7.3 | 2.7.2 | +| 2.7.2 | 2.7.1 | +| 2.7.1 | 2.7 | +| 2.7.0 | 2.7 | +| 2.6.2 | 2.6.1 | +| 2.6.1 | 2.6 | +| 2.6.0 | 2.6 | +| 2.5.9 | 2.5.8 | +| 2.5.8 | 2.5.7 | +| 2.5.7 | 2.5.6 | +| 2.5.6 | 2.5.5 | +| 2.5.5 | 2.5.4 | +| 2.5.4 | 2.5.3 | +| 2.5.3 | 2.5.2 | +| 2.5.2 | 2.5.1 | +| 2.5.1 | 2.5.1 | +| 2.5.0 | 2.5 | +| 2.4.1 | 2.4 | +| 2.4.0 | 2.4 | +| 2.3.7 | 2.3.6 | +| 2.3.6 | 2.3.5 | +| 2.3.5 | 2.3.5 | +| 2.3.4 | 2.3.4 | +| 2.3.3 | 2.3.2 | +| 2.3.2 | 2.3.1 | +| 2.3.1 | 2.2.1 | diff --git a/npm-packages/meteor-installer/config.js b/npm-packages/meteor-installer/config.js index b683d5a53c..4f96ac3ad4 100644 --- a/npm-packages/meteor-installer/config.js +++ b/npm-packages/meteor-installer/config.js @@ -1,7 +1,7 @@ const os = require('os'); const path = require('path'); -const METEOR_LATEST_VERSION = '3.0.3'; +const METEOR_LATEST_VERSION = '3.3.2'; const sudoUser = process.env.SUDO_USER || ''; function isRoot() { return process.getuid && process.getuid() === 0; diff --git a/npm-packages/meteor-installer/install.js b/npm-packages/meteor-installer/install.js index 486b4207b8..0c3d7b7922 100644 --- a/npm-packages/meteor-installer/install.js +++ b/npm-packages/meteor-installer/install.js @@ -360,7 +360,7 @@ Or see the docs at: Deploy and host your app with Cloud: - www.meteor.com/cloud + https://galaxycloud.app/ *************************************** You might need to open a new terminal window to have access to the meteor command, or run this in your terminal: diff --git a/npm-packages/meteor-installer/package-lock.json b/npm-packages/meteor-installer/package-lock.json index 2c21581f61..95184ff90f 100644 --- a/npm-packages/meteor-installer/package-lock.json +++ b/npm-packages/meteor-installer/package-lock.json @@ -1,12 +1,12 @@ { "name": "meteor", - "version": "3.0.3", + "version": "3.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "meteor", - "version": "3.0.3", + "version": "3.3.2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -194,9 +194,10 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 04d61922a0..e9fc928561 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -1,30 +1,30 @@ -{ - "name": "meteor", - "version": "3.0.3", - "description": "Install Meteor", - "main": "install.js", - "scripts": { - "install": "node cli.js install" - }, - "author": "zodern", - "license": "MIT", - "type": "commonjs", - "dependencies": { - "7zip-bin": "^5.2.0", - "cli-progress": "^3.11.1", - "https-proxy-agent": "^5.0.1", - "node-7z": "^2.1.2", - "node-downloader-helper": "^2.1.9", - "rimraf": "^6.0.1", - "semver": "^7.3.7", - "tar": "^6.1.11", - "tmp": "^0.2.1" - }, - "bin": { - "meteor-installer": "cli.js" - }, - "engines": { - "node": ">=20.x", - "npm": ">=10.x" - } -} +{ + "name": "meteor", + "version": "3.3.2", + "description": "Install Meteor", + "main": "install.js", + "scripts": { + "install": "node cli.js install" + }, + "author": "zodern", + "license": "MIT", + "type": "commonjs", + "dependencies": { + "7zip-bin": "^5.2.0", + "cli-progress": "^3.11.1", + "https-proxy-agent": "^5.0.1", + "node-7z": "^2.1.2", + "node-downloader-helper": "^2.1.9", + "rimraf": "^6.0.1", + "semver": "^7.3.7", + "tar": "^6.1.11", + "tmp": "^0.2.1" + }, + "bin": { + "meteor-installer": "cli.js" + }, + "engines": { + "node": ">=20.x", + "npm": ">=10.x" + } +} diff --git a/npm-packages/meteor-node-stubs/.npmignore b/npm-packages/meteor-node-stubs/.npmignore index 07e6e472cc..e69de29bb2 100644 --- a/npm-packages/meteor-node-stubs/.npmignore +++ b/npm-packages/meteor-node-stubs/.npmignore @@ -1 +0,0 @@ -/node_modules diff --git a/npm-packages/meteor-node-stubs/CHANGELOG.md b/npm-packages/meteor-node-stubs/CHANGELOG.md index 775ab2be53..c510d86ea7 100644 --- a/npm-packages/meteor-node-stubs/CHANGELOG.md +++ b/npm-packages/meteor-node-stubs/CHANGELOG.md @@ -1,3 +1,15 @@ +v1.2.13 - 2025-02-27 + +* Update `elliptic` to v6.6.1 to address a security vulnerability. + +v1.2.12 - 2024-10-31 + +* Update `elliptic` to v6.6.0 to address a security vulnerability. + +v1.2.11 - 2024-10-25 + +* Update `rimraf` to v5 to remove vulnerable `inflight` dependency. + v1.2.8 - 2024-04-01 * Add new dependency `@meteorjs/crypto-browserify` to replace `crypto-browserify` as it had unsafe dependencies. diff --git a/npm-packages/meteor-node-stubs/package-lock.json b/npm-packages/meteor-node-stubs/package-lock.json index 4fba62f619..960afee14a 100644 --- a/npm-packages/meteor-node-stubs/package-lock.json +++ b/npm-packages/meteor-node-stubs/package-lock.json @@ -1,12 +1,12 @@ { "name": "meteor-node-stubs", - "version": "1.2.10", + "version": "1.2.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "meteor-node-stubs", - "version": "1.2.10", + "version": "1.2.23", "bundleDependencies": [ "@meteorjs/crypto-browserify", "assert", @@ -41,7 +41,6 @@ "console-browserify": "^1.2.0", "constants-browserify": "^1.0.0", "domain-browser": "^4.23.0", - "elliptic": "^6.5.7", "events": "^3.3.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", @@ -50,6 +49,7 @@ "punycode": "^1.4.1", "querystring-es3": "^0.2.1", "readable-stream": "^3.6.2", + "sha.js": "^2.4.12", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", @@ -60,18 +60,133 @@ "vm-browserify": "^1.1.2" }, "devDependencies": { - "rimraf": "^2.7.1" + "rimraf": "^5.0.10" } }, - "node_modules/@meteorjs/crypto-browserify": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@meteorjs/crypto-browserify/-/crypto-browserify-3.12.1.tgz", - "integrity": "sha512-ku23zGjNb1XJXPSPlt4QCPetc4s/cPrSahhSF11mFeQff5wBuu7QVVn/MusdHh+l3aKl6L1XMLV+OYLXvNDQ6Q==", - "inBundle": true, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@meteorjs/browserify-sign": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@meteorjs/browserify-sign/-/browserify-sign-4.2.6.tgz", + "integrity": "sha512-xnQRbIrjHxaVbOEbzbcdav4QDRTnfRAVHi21SPosnGNiIHTdTeGQGmTF/f7GwntxqynabSifdBHeGA7W8lIKSQ==", + "inBundle": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "brorand": "^1.1.0", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash-base": "~3.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@meteorjs/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@meteorjs/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/@meteorjs/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@meteorjs/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/@meteorjs/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@meteorjs/create-ecdh": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@meteorjs/create-ecdh/-/create-ecdh-4.0.5.tgz", + "integrity": "sha512-dhn3AICsDlIZ5qY/Qu+QOL+ZGKaHcGss4PQ3CfmAF3f+o5fPJ2aDJcxd5f2au2k6sxyNqvCsLAFYFHXxHoH9yQ==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/@meteorjs/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@meteorjs/crypto-browserify": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@meteorjs/crypto-browserify/-/crypto-browserify-3.12.4.tgz", + "integrity": "sha512-K5Sgvxef93Zrw5T9cJxKuNVgpl1C2W8cfcicN6HKy98G6RoIrx6hikwWnq8FlagvOzdIQEC2s+SMn7UFNSK0eA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@meteorjs/browserify-sign": "^4.2.3", + "@meteorjs/create-ecdh": "^4.0.4", "browserify-cipher": "^1.0.1", - "browserify-sign": "^4.2.3", - "create-ecdh": "^4.0.4", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "diffie-hellman": "^5.0.3", @@ -89,17 +204,41 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@meteorjs/crypto-browserify/node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "inBundle": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=4" + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/asn1.js": { @@ -107,6 +246,7 @@ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "inBundle": true, + "license": "MIT", "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -114,16 +254,18 @@ } }, "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "inBundle": true, + "license": "MIT" }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "inBundle": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -133,10 +275,14 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "inBundle": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -148,7 +294,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -168,35 +315,39 @@ "url": "https://feross.org/support" } ], - "inBundle": true + "inBundle": true, + "license": "MIT" }, "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "inBundle": true + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "inBundle": true, + "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "inBundle": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "inBundle": true, + "license": "MIT" }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "inBundle": true, + "license": "MIT", "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -211,6 +362,7 @@ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "inBundle": true, + "license": "MIT", "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -222,6 +374,7 @@ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "inBundle": true, + "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -230,96 +383,26 @@ } }, "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "inBundle": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", "inBundle": true, + "license": "MIT", "dependencies": { "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", - "readable-stream": "^2.3.8", + "randombytes": "^2.1.0", "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 0.12" + "node": ">= 0.10" } }, - "node_modules/browserify-sign/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "inBundle": true - }, - "node_modules/browserify-sign/node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "inBundle": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "inBundle": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "inBundle": true - }, - "node_modules/browserify-sign/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "inBundle": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "inBundle": true - }, "node_modules/browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "inBundle": true, + "license": "MIT", "dependencies": { "pako": "~1.0.5" } @@ -343,6 +426,7 @@ } ], "inBundle": true, + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -351,27 +435,59 @@ "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "inBundle": true + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "inBundle": true, + "license": "MIT" }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "inBundle": true + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "inBundle": true, + "license": "MIT" }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "inBundle": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -381,20 +497,38 @@ } }, "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "inBundle": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/console-browserify": { "version": "1.2.0", @@ -405,36 +539,23 @@ "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "inBundle": true + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "inBundle": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "inBundle": true - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "inBundle": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "license": "MIT" }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "inBundle": true, + "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -448,6 +569,7 @@ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "inBundle": true, + "license": "MIT", "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -457,6 +579,21 @@ "sha.js": "^2.4.8" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -480,6 +617,7 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "inBundle": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -493,10 +631,11 @@ } }, "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -507,6 +646,7 @@ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "inBundle": true, + "license": "MIT", "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -514,16 +654,18 @@ } }, "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "inBundle": true, + "license": "MIT" }, "node_modules/domain-browser": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", "integrity": "sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==", "inBundle": true, + "license": "Artistic-2.0", "engines": { "node": ">=10" }, @@ -531,37 +673,41 @@ "url": "https://bevry.me/fund" } }, - "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "inBundle": true, "license": "MIT", "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "inBundle": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -576,11 +722,25 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "inBundle": true, + "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -590,47 +750,20 @@ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "inBundle": true, + "license": "MIT", "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "inBundle": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "inBundle": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "inBundle": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -639,33 +772,101 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "*" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "inBundle": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -684,23 +885,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "inBundle": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "inBundle": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -709,12 +899,13 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "inBundle": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -724,17 +915,17 @@ } }, "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, "node_modules/hash.js": { @@ -742,16 +933,18 @@ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "inBundle": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -762,8 +955,9 @@ "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "inBundle": true, + "license": "MIT", "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -773,8 +967,9 @@ "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "inBundle": true + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "inBundle": true, + "license": "MIT" }, "node_modules/ieee754": { "version": "1.2.1", @@ -794,32 +989,25 @@ "url": "https://feross.org/support" } ], - "inBundle": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } + "inBundle": true, + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "inBundle": true + "inBundle": true, + "license": "ISC" }, "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "inBundle": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -833,6 +1021,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "inBundle": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -840,13 +1029,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "inBundle": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -860,6 +1063,7 @@ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "inBundle": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -871,13 +1075,33 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "inBundle": true, + "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -887,16 +1111,58 @@ } }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "inBundle": true + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "inBundle": true, + "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -908,6 +1174,7 @@ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "inBundle": true, + "license": "MIT", "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -917,39 +1184,56 @@ } }, "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "inBundle": true, + "license": "MIT" }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "inBundle": true + "inBundle": true, + "license": "ISC" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "inBundle": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "inBundle": true, + "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "inBundle": true, "license": "MIT", "engines": { @@ -960,13 +1244,14 @@ } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "inBundle": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -980,19 +1265,23 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "inBundle": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "inBundle": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -1002,32 +1291,33 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "inBundle": true + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "inBundle": true + "inBundle": true, + "license": "(MIT AND Zlib)" }, "node_modules/parse-asn1": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", "inBundle": true, + "license": "ISC", "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", @@ -1040,55 +1330,108 @@ "node": ">= 0.10" } }, - "node_modules/parse-asn1/node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "inBundle": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "inBundle": true + "inBundle": true, + "license": "MIT" }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", "inBundle": true, + "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" }, "engines": { "node": ">=0.12" } }, + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "inBundle": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -1097,13 +1440,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "inBundle": true + "inBundle": true, + "license": "MIT" }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "inBundle": true, + "license": "MIT", "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -1114,25 +1459,27 @@ } }, "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "inBundle": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "inBundle": true, + "license": "MIT" }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "inBundle": true + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "inBundle": true, + "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "inBundle": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1144,7 +1491,7 @@ "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", "inBundle": true, "engines": { "node": ">=0.4.x" @@ -1155,6 +1502,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "inBundle": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -1164,6 +1512,7 @@ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "inBundle": true, + "license": "MIT", "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -1174,6 +1523,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1184,15 +1534,19 @@ } }, "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ripemd160": { @@ -1200,6 +1554,7 @@ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "inBundle": true, + "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -1223,7 +1578,26 @@ "url": "https://feross.org/support" } ], - "inBundle": true + "inBundle": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/set-function-length": { "version": "1.2.2", @@ -1246,33 +1620,66 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "inBundle": true + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "inBundle": true, + "license": "MIT" }, "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "inBundle": true, + "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "inBundle": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1281,11 +1688,81 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" @@ -1296,6 +1773,7 @@ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", "inBundle": true, + "license": "MIT", "dependencies": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", @@ -1308,15 +1786,121 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "inBundle": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "inBundle": true, + "license": "MIT", "dependencies": { "setimmediate": "^1.0.4" }, @@ -1324,11 +1908,42 @@ "node": ">=0.6.0" } }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "inBundle": true + "inBundle": true, + "license": "MIT" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/url": { "version": "0.11.4", @@ -1349,6 +1964,7 @@ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "inBundle": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -1361,25 +1977,46 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "inBundle": true + "inBundle": true, + "license": "MIT" }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "inBundle": true + "inBundle": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "inBundle": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1388,17 +2025,110 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "inBundle": true, + "license": "MIT", "engines": { "node": ">=0.4" } diff --git a/npm-packages/meteor-node-stubs/package.json b/npm-packages/meteor-node-stubs/package.json index 2068c222c4..3e2867429e 100644 --- a/npm-packages/meteor-node-stubs/package.json +++ b/npm-packages/meteor-node-stubs/package.json @@ -2,7 +2,7 @@ "name": "meteor-node-stubs", "author": "Ben Newman ", "description": "Stub implementations of Node built-in modules, a la Browserify", - "version": "1.2.10", + "version": "1.2.24", "main": "index.js", "license": "MIT", "homepage": "https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-node-stubs/README.md", @@ -18,7 +18,6 @@ "console-browserify": "^1.2.0", "constants-browserify": "^1.0.0", "domain-browser": "^4.23.0", - "elliptic": "^6.5.7", "events": "^3.3.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", @@ -27,6 +26,7 @@ "punycode": "^1.4.1", "querystring-es3": "^0.2.1", "readable-stream": "^3.6.2", + "sha.js": "^2.4.12", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", @@ -62,7 +62,7 @@ "vm-browserify" ], "devDependencies": { - "rimraf": "^2.7.1" + "rimraf": "^5.0.10" }, "repository": { "type": "git", @@ -81,5 +81,30 @@ ], "bugs": { "url": "https://github.com/meteor/node-stubs/issues" - } + }, + "bundleDependencies": [ + "@meteorjs/crypto-browserify", + "assert", + "browserify-zlib", + "buffer", + "console-browserify", + "constants-browserify", + "domain-browser", + "events", + "https-browserify", + "os-browserify", + "path-browserify", + "process", + "punycode", + "querystring-es3", + "readable-stream", + "stream-browserify", + "stream-http", + "string_decoder", + "timers-browserify", + "tty-browserify", + "url", + "util", + "vm-browserify" + ] } diff --git a/npm-packages/meteor-node-stubs/scripts/build-deps.js b/npm-packages/meteor-node-stubs/scripts/build-deps.js index e85d8d7c1f..7c6808f6ec 100644 --- a/npm-packages/meteor-node-stubs/scripts/build-deps.js +++ b/npm-packages/meteor-node-stubs/scripts/build-deps.js @@ -2,6 +2,7 @@ var fs = require("fs"); var path = require("path"); var depsDir = path.join(__dirname, "..", "deps"); var map = require("../map.json"); +var rr = require("rimraf"); // Each file in the `deps` directory expresses the dependencies of a stub. // For example, `deps/http.js` calls `require("http-browserify")` to @@ -14,16 +15,15 @@ var map = require("../map.json"); // bundled. Note that these modules should not be `require`d at runtime, // but merely scanned at bundling time. -fs.mkdir(depsDir, function () { - require("rimraf")("deps/*.js", function (error) { - if (error) throw error; - Object.keys(map).forEach(function (id) { - fs.writeFileSync( - path.join(depsDir, id + ".js"), - typeof map[id] === "string" - ? "require(" + JSON.stringify(map[id]) + ");\n" - : "" - ); - }); - }); +rr.rimrafSync(depsDir); + +fs.mkdirSync(depsDir); + +Object.keys(map).forEach(function (id) { + fs.writeFileSync( + path.join(depsDir, id + ".js"), + typeof map[id] === "string" + ? "require(" + JSON.stringify(map[id]) + ");\n" + : "" + ); }); diff --git a/package-lock.json b/package-lock.json index 1ca8bd21d9..ea3a5d33e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,10 @@ "@babel/eslint-parser": "^7.21.3", "@babel/eslint-plugin": "^7.19.1", "@babel/preset-react": "^7.18.6", + "@types/lodash.isempty": "^4.4.9", "@types/node": "^18.16.18", + "@types/sockjs": "^0.3.36", + "@types/sockjs-client": "^1.5.4", "@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/parser": "^5.56.0", "eslint": "^8.36.0", @@ -25,7 +28,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^2.8.6", + "prettier": "^2.8.8", "typescript": "^5.4.5" } }, @@ -1096,6 +1099,21 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==", + "dev": true + }, + "node_modules/@types/lodash.isempty": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/lodash.isempty/-/lodash.isempty-4.4.9.tgz", + "integrity": "sha512-DPSFfnT2JmZiAWNWOU8IRZws/Ha6zyGF5m06TydfsY+0dVoQqby2J61Na2QU4YtwiZ+moC6cJS6zWYBJq4wBVw==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "18.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", @@ -1111,6 +1129,21 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sockjs-client": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.4.tgz", + "integrity": "sha512-zk+uFZeWyvJ5ZFkLIwoGA/DfJ+pYzcZ8eH4H/EILCm2OBZyHH6Hkdna1/UWL/CFruh5wj6ES7g75SvUB0VsH5w==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -1817,6 +1850,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", @@ -2992,39 +3038,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/fast-glob/node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -3038,18 +3051,6 @@ "node": ">=8.6" } }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3083,6 +3084,19 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -3626,6 +3640,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -4236,6 +4260,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -4695,6 +4720,19 @@ "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index 16de72f280..ff1baa3e4c 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,25 @@ { "name": "meteor", "version": "0.0.1", - "description": "Used to apply Prettier and ESLint manually", + "description": "Meteor's main repository, containing the Meteor tool, core packages, and documentation.", "repository": { "type": "git", "url": "git+https://github.com/meteor/meteor.git" }, - "author": "Filipe Névola", "license": "MIT", "bugs": { "url": "https://github.com/meteor/meteor/issues" }, - "homepage": "https://github.com/meteor/meteor#readme", + "homepage": "https://www.meteor.com/", "devDependencies": { "@babel/core": "^7.21.3", "@babel/eslint-parser": "^7.21.3", "@babel/eslint-plugin": "^7.19.1", "@babel/preset-react": "^7.18.6", + "@types/lodash.isempty": "^4.4.9", "@types/node": "^18.16.18", + "@types/sockjs": "^0.3.36", + "@types/sockjs-client": "^1.5.4", "@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/parser": "^5.56.0", "eslint": "^8.36.0", @@ -29,9 +31,12 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^2.8.6", + "prettier": "^2.8.8", "typescript": "^5.4.5" }, + "scripts": { + "test:idle-bot": "node --test .github/scripts/__tests__/inactive-issues.test.js" + }, "jshintConfig": { "esversion": 11 }, diff --git a/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json b/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json deleted file mode 100644 index 93c418735a..0000000000 --- a/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "lockfileVersion": 4, - "dependencies": { - "@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==" - }, - "@types/notp": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/notp/-/notp-2.0.5.tgz", - "integrity": "sha512-ZsZS0PYUa6ZE4K3yOGerBvaxCp4ePf6ZmkFbPeilcqz2Ui/lmXox7KlRt7XZkXzqUgXhFLkc09ixyVmFLCU3gQ==" - }, - "node-2fa": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-2fa/-/node-2fa-2.0.3.tgz", - "integrity": "sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==" - }, - "notp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", - "integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ==" - }, - "qrcode-svg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/qrcode-svg/-/qrcode-svg-1.1.0.tgz", - "integrity": "sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw==" - }, - "thirty-two": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", - "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" - }, - "tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, - "undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - } - } -} diff --git a/packages/accounts-base/accounts-base.d.ts b/packages/accounts-base/accounts-base.d.ts index ce91667fa0..6aa2a58e2a 100644 --- a/packages/accounts-base/accounts-base.d.ts +++ b/packages/accounts-base/accounts-base.d.ts @@ -6,6 +6,7 @@ import { DDP } from 'meteor/ddp'; export interface URLS { resetPassword: (token: string) => string; verifyEmail: (token: string) => string; + loginToken: (token: string) => string; enrollAccount: (token: string) => string; } @@ -47,7 +48,7 @@ export namespace Accounts { profile?: Meteor.UserProfile | undefined; }, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): string; + ): Promise; function createUserAsync( options: { @@ -82,6 +83,11 @@ export namespace Accounts { passwordEnrollTokenExpirationInDays?: number | undefined; ambiguousErrorMessages?: boolean | undefined; bcryptRounds?: number | undefined; + argon2Enabled?: string | false; + argon2Type?: string | undefined; + argon2TimeCost: number | undefined; + argon2MemoryCost: number | undefined; + argon2Parallelism: number | undefined; defaultFieldSelector?: { [key: string]: 0 | 1 } | undefined; collection?: string | undefined; loginTokenExpirationHours?: number | undefined; @@ -113,23 +119,23 @@ export namespace Accounts { oldPassword: string, newPassword: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function forgotPassword( options: { email?: string | undefined }, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function resetPassword( token: string, newPassword: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function verifyEmail( token: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function onEmailVerificationLink(callback: Function): void; @@ -143,11 +149,11 @@ export namespace Accounts { function logout( callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function logoutOtherClients( callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; type PasswordSignupField = 'USERNAME_AND_EMAIL' | 'USERNAME_AND_OPTIONAL_EMAIL' | 'USERNAME_ONLY' | 'EMAIL_ONLY'; type PasswordlessSignupField = 'USERNAME_AND_EMAIL' | 'EMAIL_ONLY'; @@ -179,9 +185,11 @@ export interface EmailTemplates { export namespace Accounts { var emailTemplates: EmailTemplates; - function addEmail(userId: string, newEmail: string, verified?: boolean): void; + function addEmailAsync(userId: string, newEmail: string, verified?: boolean): Promise; - function removeEmail(userId: string, email: string): void; + function removeEmail(userId: string, email: string): Promise; + + function replaceEmailAsync(userId: string, oldEmail: string, newEmail: string, verified?: boolean): Promise; function onCreateUser( func: (options: { profile?: {} | undefined }, user: Meteor.User) => void @@ -190,35 +198,52 @@ export namespace Accounts { function findUserByEmail( email: string, options?: { fields?: Mongo.FieldSpecifier | undefined } - ): Meteor.User | null | undefined; + ): Promise; function findUserByUsername( username: string, options?: { fields?: Mongo.FieldSpecifier | undefined } - ): Meteor.User | null | undefined; + ): Promise; + + interface SendEmailOptions { + from: string; + to: string; + subject: string; + text: string; + html: string; + headers?: Header | undefined; + } + + interface SendEmailResult { + email: string; + user: Meteor.User; + token: string; + url: string; + options: SendEmailOptions; + } function sendEnrollmentEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; function sendResetPasswordEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; function sendVerificationEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; - function setUsername(userId: string, newUsername: string): void; + function setUsername(userId: string, newUsername: string): Promise; function setPasswordAsync( userId: string, @@ -353,10 +378,10 @@ export namespace Accounts { /** * - * Check whether the provided password matches the bcrypt'ed password in + * Check whether the provided password matches the encrypted password in * the database user record. `password` can be a string (in which case - * it will be run through SHA256 before bcrypt) or an object with - * properties `digest` and `algorithm` (in which case we bcrypt + * it will be run through SHA256 before bcrypt or argon2) or an object with + * properties `digest` and `algorithm` (in which case we bcrypt/argon2 * `password.digest`). */ function _checkPasswordAsync( diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 33073f2a6c..aef200ed39 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -362,18 +362,19 @@ export class AccountsClient extends AccountsCommon { // Note that we need to call this even if _suppressLoggingIn is true, // because it could be matching a _setLoggingIn(true) from a // half-completed pre-reconnect login method. - this._setLoggingIn(false); if (error || !result) { error = error || new Error( `No result from call to ${options.methodName}` ); loginCallbacks({ error }); + this._setLoggingIn(false); return; } try { options.validateResult(result); } catch (e) { loginCallbacks({ error: e }); + this._setLoggingIn(false); return; } @@ -381,13 +382,15 @@ export class AccountsClient extends AccountsCommon { this.makeClientLoggedIn(result.id, result.token, result.tokenExpires); // use Tracker to make we sure have a user before calling the callbacks - Tracker.autorun(async function (computation) { + Tracker.autorun(async (computation) => { const user = await Tracker.withComputation(computation, () => Meteor.userAsync(), ); if (user) { - loginCallbacks({ loginDetails: result }) + loginCallbacks({ loginDetails: result }); + this._setLoggingIn(false); + computation.stop(); } }); @@ -399,7 +402,7 @@ export class AccountsClient extends AccountsCommon { this.connection.applyAsync( options.methodName, options.methodArguments, - { wait: true, onResultReceived: onResultReceived }, + { wait: true, onResultReceived }, loggedInAndDataReadyCallback); } @@ -686,7 +689,7 @@ export class AccountsClient extends AccountsCommon { /** * @summary Register a function to call when a reset password link is clicked * in an email sent by - * [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail). + * [`Accounts.sendResetPasswordEmail`](#Accounts-sendResetPasswordEmail). * This function should be called in top-level code, not inside * `Meteor.startup()`. * @memberof! Accounts @@ -694,7 +697,7 @@ export class AccountsClient extends AccountsCommon { * @param {Function} callback The function to call. It is given two arguments: * * 1. `token`: A password reset token that can be passed to - * [`Accounts.resetPassword`](#accounts_resetpassword). + * [`Accounts.resetPassword`](#Accounts-resetPassword). * 2. `done`: A function to call when the password reset UI flow is complete. The normal * login process is suspended until this function is called, so that the * password for user A can be reset even if user B was logged in. @@ -712,7 +715,7 @@ export class AccountsClient extends AccountsCommon { /** * @summary Register a function to call when an email verification link is * clicked in an email sent by - * [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). + * [`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail). * This function should be called in top-level code, not inside * `Meteor.startup()`. * @memberof! Accounts @@ -720,7 +723,7 @@ export class AccountsClient extends AccountsCommon { * @param {Function} callback The function to call. It is given two arguments: * * 1. `token`: An email verification token that can be passed to - * [`Accounts.verifyEmail`](#accounts_verifyemail). + * [`Accounts.verifyEmail`](#Accounts-verifyEmail). * 2. `done`: A function to call when the email verification UI flow is complete. * The normal login process is suspended until this function is called, so * that the user can be notified that they are verifying their email before @@ -739,7 +742,7 @@ export class AccountsClient extends AccountsCommon { /** * @summary Register a function to call when an account enrollment link is * clicked in an email sent by - * [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). + * [`Accounts.sendEnrollmentEmail`](#Accounts-sendEnrollmentEmail). * This function should be called in top-level code, not inside * `Meteor.startup()`. * @memberof! Accounts @@ -747,7 +750,7 @@ export class AccountsClient extends AccountsCommon { * @param {Function} callback The function to call. It is given two arguments: * * 1. `token`: A password reset token that can be passed to - * [`Accounts.resetPassword`](#accounts_resetpassword) to give the newly + * [`Accounts.resetPassword`](#Accounts-resetPassword) to give the newly * enrolled account a password. * 2. `done`: A function to call when the enrollment UI flow is complete. * The normal login process is suspended until this function is called, so that diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index fdd45a2890..48234064d8 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -14,6 +14,11 @@ const VALID_CONFIG_KEYS = [ 'passwordEnrollTokenExpiration', 'ambiguousErrorMessages', 'bcryptRounds', + 'argon2Enabled', + 'argon2Type', + 'argon2TimeCost', + 'argon2MemoryCost', + 'argon2Parallelism', 'defaultFieldSelector', 'collection', 'loginTokenExpirationHours', @@ -194,48 +199,13 @@ export class AccountsCommon { ? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options)) : null; } - // Set up config for the accounts system. Call this on both the client - // and the server. - // - // Note that this method gets overridden on AccountsServer.prototype, but - // the overriding method calls the overridden method. - // - // XXX we should add some enforcement that this is called on both the - // client and the server. Otherwise, a user can - // 'forbidClientAccountCreation' only on the client and while it looks - // like their app is secure, the server will still accept createUser - // calls. https://github.com/meteor/meteor/issues/828 - // - // @param options {Object} an object with fields: - // - sendVerificationEmail {Boolean} - // Send email address verification emails to new users created from - // client signups. - // - forbidClientAccountCreation {Boolean} - // Do not allow clients to create accounts directly. - // - restrictCreationByEmailDomain {Function or String} - // Require created users to have an email matching the function or - // having the string as domain. - // - loginExpirationInDays {Number} - // Number of days since login until a user is logged out (login token - // expires). - // - collection {String|Mongo.Collection} - // A collection name or a Mongo.Collection object to hold the users. - // - passwordResetTokenExpirationInDays {Number} - // Number of days since password reset token creation until the - // token can't be used any longer (password reset token expires). - // - ambiguousErrorMessages {Boolean} - // Return ambiguous error messages from login failures to prevent - // user enumeration. - // - bcryptRounds {Number} - // Allows override of number of bcrypt rounds (aka work factor) used - // to store passwords. /** * @summary Set global accounts options. You can also set these in `Meteor.settings.packages.accounts` without the need to call this function. * @locus Anywhere * @param {Object} options * @param {Boolean} options.sendVerificationEmail New users with an email address will receive an address verification email. - * @param {Boolean} options.forbidClientAccountCreation Calls to [`createUser`](#accounts_createuser) from the client will be rejected. In addition, if you are using [accounts-ui](#accountsui), the "Create account" link will not be available. + * @param {Boolean} options.forbidClientAccountCreation Calls to [`createUser`](#accounts_createuser) from the client will be rejected. In addition, if you are using [accounts-ui](#accountsui), the "Create account" link will not be available. **Important**: This option must be set on both the client and server to take full effect. If only set on the server, account creation will be blocked but the UI will still show the "Create account" link. * @param {String | Function} options.restrictCreationByEmailDomain If set to a string, only allows new users if the domain part of their email address matches the string. If set to a function, only allows new users if the function returns true. The function is passed the full email address of the proposed new user. Works with password-based sign-in and external services that expose email addresses (Google, Facebook, GitHub). All existing users still can log in after enabling this option. Example: `Accounts.config({ restrictCreationByEmailDomain: 'school.edu' })`. * @param {Number} options.loginExpiration The number of milliseconds from when a user logs in until their token expires and they are logged out, for a more granular control. If `loginExpirationInDays` is set, it takes precedent. * @param {Number} options.loginExpirationInDays The number of days from when a user logs in until their token expires and they are logged out. Defaults to 90. Set to `null` to disable login expiration. @@ -244,13 +214,31 @@ export class AccountsCommon { * @param {Number} options.passwordResetTokenExpiration The number of milliseconds from when a link to reset password is sent until token expires and user can't reset password with the link anymore. If `passwordResetTokenExpirationInDays` is set, it takes precedent. * @param {Number} options.passwordEnrollTokenExpirationInDays The number of days from when a link to set initial password is sent until token expires and user can't set password with the link anymore. Defaults to 30. * @param {Number} options.passwordEnrollTokenExpiration The number of milliseconds from when a link to set initial password is sent until token expires and user can't set password with the link anymore. If `passwordEnrollTokenExpirationInDays` is set, it takes precedent. - * @param {Boolean} options.ambiguousErrorMessages Return ambiguous error messages from login failures to prevent user enumeration. Defaults to `false`, but in production environments it is recommended it defaults to `true`. + * @param {Boolean} options.ambiguousErrorMessages Return ambiguous error messages from login failures to prevent user enumeration. Defaults to `true`. * @param {Number} options.bcryptRounds Allows override of number of bcrypt rounds (aka work factor) used to store passwords. The default is 10. + * @param {Boolean} options.argon2Enabled Enable argon2 algorithm usage in replacement for bcrypt. The default is `false`. + * @param {'argon2id' | 'argon2i' | 'argon2d'} options.argon2Type Allows override of the argon2 algorithm type. The default is `argon2id`. + * @param {Number} options.argon2TimeCost Allows override of number of argon2 iterations (aka time cost) used to store passwords. The default is 2. + * @param {Number} options.argon2MemoryCost Allows override of the amount of memory (in KiB) used by the argon2 algorithm. The default is 19456 (19MB). + * @param {Number} options.argon2Parallelism Allows override of the number of threads used by the argon2 algorithm. The default is 1. * @param {MongoFieldSpecifier} options.defaultFieldSelector To exclude by default large custom fields from `Meteor.user()` and `Meteor.findUserBy...()` functions when called without a field selector, and all `onLogin`, `onLoginFailure` and `onLogout` callbacks. Example: `Accounts.config({ defaultFieldSelector: { myBigArray: 0 }})`. Beware when using this. If, for instance, you do not include `email` when excluding the fields, you can have problems with functions like `forgotPassword` that will break because they won't have the required data available. It's recommend that you always keep the fields `_id`, `username`, and `email`. * @param {String|Mongo.Collection} options.collection A collection name or a Mongo.Collection object to hold the users. * @param {Number} options.loginTokenExpirationHours When using the package `accounts-2fa`, use this to set the amount of time a token sent is valid. As it's just a number, you can use, for example, 0.5 to make the token valid for just half hour. The default is 1 hour. * @param {Number} options.tokenSequenceLength When using the package `accounts-2fa`, use this to the size of the token sequence generated. The default is 6. * @param {'session' | 'local'} options.clientStorage By default login credentials are stored in local storage, setting this to true will switch to using session storage. + * + * @example + * // For UI-related options like forbidClientAccountCreation, call Accounts.config on both client and server + * // Create a shared configuration file (e.g., lib/accounts-config.js): + * import { Accounts } from 'meteor/accounts-base'; + * + * Accounts.config({ + * forbidClientAccountCreation: true, + * sendVerificationEmail: true, + * }); + * + * // Then import this file in both client/main.js and server/main.js: + * // import '../lib/accounts-config.js'; */ config(options) { // We don't want users to accidentally only call Accounts.config on the diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index cf8f573b1d..6f0ba2098e 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -84,6 +84,11 @@ export class AccountsServer extends AccountsCommon { this._skipCaseInsensitiveChecksForTest = {}; + // Helper function to resolve promises if needed + this._resolvePromise = async (value) => { + return Meteor._isPromise(value) ? await value : value; + }; + this.urls = { resetPassword: (token, extraParams) => this.buildEmailUrl(`#/reset-password/${token}`, extraParams), verifyEmail: (token, extraParams) => this.buildEmailUrl(`#/verify-email/${token}`, extraParams), @@ -333,6 +338,32 @@ export class AccountsServer extends AccountsCommon { return user; } + /** + * @summary Find a user by one of their email addresses. + * @locus Server + * @param {String} email The email address to look for + * @param {Object} [options] + * @param {Object} options.fields Limit the fields to return from the user document + * @returns {Promise} A user if found, else null + * @memberof Accounts + * @importFromPackage accounts-base + */ + findUserByEmail = async (email, options) => + await this._findUserByQuery({ email }, options); + + /** + * @summary Find a user by their username. + * @locus Server + * @param {String} username The username to look for + * @param {Object} [options] + * @param {Object} options.fields Limit the fields to return from the user document + * @returns {Promise} A user if found, else null + * @memberof Accounts + * @importFromPackage accounts-base + */ + findUserByUsername = async (username, options) => + await this._findUserByQuery({ username }, options); + /// /// LOGIN METHODS /// @@ -1806,21 +1837,6 @@ const setupUsersCollection = async users => { return true; }, - updateAsync: (userId, user, fields, modifier) => { - // make sure it is our record - if (user._id !== userId) { - return false; - } - - // user can only modify the 'profile' field. sets to multiple - // sub-keys (eg profile.foo and profile.bar) are merged into entry - // in the fields list. - if (fields.length !== 1 || fields[0] !== 'profile') { - return false; - } - - return true; - }, fetch: ['_id'] // we only look at _id. }); @@ -1861,4 +1877,3 @@ const generateCasePermutationsForString = string => { } return permutations; } - diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index f6cdf49c71..1ad449e928 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -730,7 +730,7 @@ if (Meteor.isServer) { // create same user in two different collections - should pass const email = "test-collection@testdomain.com" - const collection0 = new Mongo.Collection('test1'); + const collection0 = new Mongo.Collection(`test1_${Random.id()}`); Accounts.config({ collection: collection0, @@ -738,7 +738,7 @@ if (Meteor.isServer) { const uid0 = await Accounts.createUser({email}) await Meteor.users.removeAsync(uid0); - const collection1 = new Mongo.Collection('test2'); + const collection1 = new Mongo.Collection(`test2_${Random.id()}`); Accounts.config({ collection: collection1, }) @@ -757,13 +757,13 @@ if (Meteor.isServer) { const email = "test-collection@testdomain.com" Accounts.config({ - collection: 'collection0', + collection: `collection0_${Random.id()}`, }) const uid0 = await Accounts.createUser({email}) await Meteor.users.removeAsync(uid0); Accounts.config({ - collection: 'collection1', + collection: `collection1_${Random.id()}`, }) const uid1 = await Accounts.createUser({email}) await Meteor.users.removeAsync(uid1); @@ -775,8 +775,8 @@ if (Meteor.isServer) { }); }); - Tinytest.add( - 'accounts - make sure that extra params to accounts urls are added', + Tinytest.addAsync( + 'accounts - urls work with sync resolution', async test => { // No extra params const verifyEmailURL = new URL(Accounts.urls.verifyEmail('test')); @@ -790,6 +790,49 @@ if (Meteor.isServer) { test.equal(enrollAccountURL.searchParams.get('test'), extraParams.test); } ); + + Tinytest.addAsync( + 'accounts - urls work with async resolution', + async test => { + // Save original urls + const originalUrls = Accounts.urls; + try { + // Override urls methods to return Promises + Accounts.urls = { + resetPassword: (token, extraParams) => + new Promise(resolve => resolve(originalUrls.resetPassword(token, extraParams))), + verifyEmail: (token, extraParams) => + new Promise(resolve => resolve(originalUrls.verifyEmail(token, extraParams))), + loginToken: (selector, token, extraParams) => + new Promise(resolve => resolve(originalUrls.loginToken(selector, token, extraParams))), + enrollAccount: (token, extraParams) => + new Promise(resolve => resolve(originalUrls.enrollAccount(token, extraParams))), + }; + + // Test with no extra params + const verifyEmailUrl = await Accounts.urls.verifyEmail('test'); + const verifyEmailURL = new URL(verifyEmailUrl); + test.equal(verifyEmailURL.searchParams.toString(), ""); + + // Test with extra params + const extraParams = { test: 'async-success' }; + const resetPasswordUrl = await Accounts.urls.resetPassword('test', extraParams); + const resetPasswordURL = new URL(resetPasswordUrl); + test.equal(resetPasswordURL.searchParams.get('test'), extraParams.test); + + const enrollAccountUrl = await Accounts.urls.enrollAccount('test', extraParams); + const enrollAccountURL = new URL(enrollAccountUrl); + test.equal(enrollAccountURL.searchParams.get('test'), extraParams.test); + + const loginTokenUrl = await Accounts.urls.loginToken('email', 'token', extraParams); + const loginTokenURL = new URL(loginTokenUrl); + test.equal(loginTokenURL.searchParams.get('test'), extraParams.test); + } finally { + // Restore original urls + Accounts.urls = originalUrls; + } + } + ); } Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index f77c6e7c99..0e211828c0 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -14,6 +14,8 @@ const getTokenFromSecret = async ({ selector, secret: secretParam }) => { return token; }; +Accounts.config({ ambiguousErrorMessages: false }); + Meteor.methods({ async removeAccountsTestUser(username) { await Meteor.users.removeAsync({ username }); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 2fc423d2bd..4190011694 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "A user account system", - version: "3.0.2", + version: "3.1.2", }); Package.onUse((api) => { diff --git a/packages/accounts-base/server_main.js b/packages/accounts-base/server_main.js index dde1781f82..a9ed15c950 100644 --- a/packages/accounts-base/server_main.js +++ b/packages/accounts-base/server_main.js @@ -7,6 +7,7 @@ import { AccountsServer } from "./accounts_server.js"; Accounts = new AccountsServer(Meteor.server, { ...Meteor.settings.packages?.accounts, ...Meteor.settings.packages?.['accounts-base'] }); // TODO[FIBERS]: I need TLA Accounts.init().then(); + // Users table. Don't use the normal autopublish, since we want to hide // some fields. Code to autopublish this is in accounts_server.js. // XXX Allow users to configure this collection name. diff --git a/packages/accounts-oauth/oauth_client.js b/packages/accounts-oauth/oauth_client.js index d47736cc84..ad28401437 100644 --- a/packages/accounts-oauth/oauth_client.js +++ b/packages/accounts-oauth/oauth_client.js @@ -74,34 +74,38 @@ Meteor.startup(() => { Accounts.oauth.tryLoginAfterPopupClosed = ( credentialToken, callback, - shouldRetry = true + timeout = 1000 ) => { - const credentialSecret = - OAuth._retrieveCredentialSecret(credentialToken); + let startTime = Date.now(); + let calledOnce = false; + let intervalId; + const checkForCredentialSecret = (clearInterval = false) => { + const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken); + if (!calledOnce && (credentialSecret || clearInterval)) { + calledOnce = true; + Meteor.clearInterval(intervalId); + Accounts.callLoginMethod({ + methodArguments: [{ oauth: { credentialToken, credentialSecret } }], + userCallback: callback ? err => callback(convertError(err)) : () => {}, + }); + } else if (clearInterval) { + Meteor.clearInterval(intervalId); + } + }; + // Check immediately + checkForCredentialSecret(); + + // Then check on an interval // In some case the function OAuth._retrieveCredentialSecret() can return null, because the local storage might not // be ready. So we retry after a timeout. - - if (!credentialSecret) { - if (!shouldRetry) { - return; + intervalId = Meteor.setInterval(() => { + if (Date.now() - startTime > timeout) { + checkForCredentialSecret(true); + } else { + checkForCredentialSecret(); } - Meteor.setTimeout( - () => - Accounts.oauth.tryLoginAfterPopupClosed( - credentialToken, - callback, - false - ), - 500 - ); - return; - } - // continue with the rest of the function - Accounts.callLoginMethod({ - methodArguments: [{ oauth: { credentialToken, credentialSecret } }], - userCallback: callback && (err => callback(convertError(err))), - }); + }, 250); }; Accounts.oauth.credentialRequestCompleteHandler = callback => @@ -112,4 +116,3 @@ Accounts.oauth.credentialRequestCompleteHandler = callback => Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback); } } - diff --git a/packages/accounts-oauth/package.js b/packages/accounts-oauth/package.js index e8d1716706..890762bd9b 100644 --- a/packages/accounts-oauth/package.js +++ b/packages/accounts-oauth/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Common code for OAuth-based login services", - version: '1.4.5', + version: '1.4.6', }); Package.onUse(api => { diff --git a/packages/accounts-password/.npm/package/npm-shrinkwrap.json b/packages/accounts-password/.npm/package/npm-shrinkwrap.json deleted file mode 100644 index 4960480477..0000000000 --- a/packages/accounts-password/.npm/package/npm-shrinkwrap.json +++ /dev/null @@ -1,306 +0,0 @@ -{ - "lockfileVersion": 4, - "dependencies": { - "@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==" - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "bcrypt": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", - "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==" - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==" - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==" - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==" - }, - "minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==" - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==" - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==" - }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" - }, - "tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==" - }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } -} diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index eb63b6182c..f41e5b8127 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -1,49 +1,51 @@ -Package.describe({ - summary: "Password support for accounts", - // Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease - // process, so it might be best to skip to 2.3.x instead of reusing - // 2.2.x in the future. The version was also bumped to 2.0.0 temporarily - // during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2 - // through -beta.5 and -rc.0 have already been published. - version: "3.0.2", -}); - -Npm.depends({ - bcrypt: "5.0.1", -}); - -Package.onUse((api) => { - api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]); - - // Export Accounts (etc) to packages using this one. - api.imply("accounts-base", ["client", "server"]); - - api.use("email", "server"); - api.use("random", "server"); - api.use("check", "server"); - api.use("ecmascript"); - - api.addFiles("email_templates.js", "server"); - api.addFiles("password_server.js", "server"); - api.addFiles("password_client.js", "client"); -}); - -Package.onTest((api) => { - api.use([ - "accounts-password", - "sha", - "tinytest", - "test-helpers", - "tracker", - "accounts-base", - "random", - "email", - "check", - "ddp", - "ecmascript", - ]); - api.addFiles("password_tests_setup.js", "server"); - api.addFiles("password_tests.js", ["client", "server"]); - api.addFiles("email_tests_setup.js", "server"); - api.addFiles("email_tests.js", "client"); -}); +Package.describe({ + summary: "Password support for accounts", + // Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease + // process, so it might be best to skip to 2.3.x instead of reusing + // 2.2.x in the future. The version was also bumped to 2.0.0 temporarily + // during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2 + // through -beta.5 and -rc.0 have already been published. + version: "3.2.1", +}); + +Npm.depends({ + bcrypt: "5.0.1", + argon2: "0.41.1", +}); + +Package.onUse((api) => { + api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]); + + // Export Accounts (etc) to packages using this one. + api.imply("accounts-base", ["client", "server"]); + + api.use("email", "server"); + api.use("random", "server"); + api.use("check", "server"); + api.use("ecmascript"); + + api.addFiles("email_templates.js", "server"); + api.addFiles("password_server.js", "server"); + api.addFiles("password_client.js", "client"); +}); + +Package.onTest((api) => { + api.use([ + "accounts-password", + "sha", + "tinytest", + "test-helpers", + "tracker", + "accounts-base", + "random", + "email", + "check", + "ddp", + "ecmascript" + ]); + api.addFiles("password_tests_setup.js", "server"); + api.addFiles("password_tests.js", ["client", "server"]); + api.addFiles("email_tests_setup.js", "server"); + api.addFiles("email_tests.js", "client"); + api.addFiles("password_argon_tests.js", ["client", "server"]); +}); diff --git a/packages/accounts-password/password_argon_tests.js b/packages/accounts-password/password_argon_tests.js new file mode 100644 index 0000000000..c1fb806d9a --- /dev/null +++ b/packages/accounts-password/password_argon_tests.js @@ -0,0 +1,221 @@ +if (Meteor.isServer) { + Tinytest.addAsync("passwords Argon - migration from bcrypt encryption to argon2", async (test) => { + Accounts._options.argon2Enabled = false; + const username = Random.id(); + const email = `${username}@bcrypt.com`; + const password = "password"; + const userId = await Accounts.createUser( + { + username: username, + email: email, + password: password + } + ); + Accounts._options.argon2Enabled = true; + let user = await Meteor.users.findOneAsync(userId); + const isValid = await Accounts._checkPasswordAsync(user, password); + test.equal(isValid.userId, userId, "checkPassword with bcrypt - User ID should be returned"); + test.equal(typeof isValid.error, "undefined", "checkPassword with bcrypt - No error should be returned"); + + // wait for the migration to happen + await waitUntil( + async () => { + user = await Meteor.users.findOneAsync(userId); + return ( + typeof user.services.password.bcrypt === "undefined" && + typeof user.services.password.argon2 === "string" + ); + }, + { description: "bcrypt should be unset and argon2 should be set" } + ); + + // password is still valid using argon2 + const isValidArgon = await Accounts._checkPasswordAsync(user, password); + test.equal(isValidArgon.userId, userId, "checkPassword with argon2 - User ID should be returned"); + test.equal(typeof isValidArgon.error, "undefined", "checkPassword with argon2 - No error should be returned"); + + // cleanup + Accounts._options.argon2Enabled = false; + await Meteor.users.removeAsync(userId); + }); + + + Tinytest.addAsync("passwords Argon - setPassword", async (test) => { + Accounts._options.argon2Enabled = true; + const username = Random.id(); + const email = `${username}-intercept@example.com`; + + const userId = await Accounts.createUser({ username: username, email: email }); + + let user = await Meteor.users.findOneAsync(userId); + // no services yet. + test.equal(user.services.password, undefined); + + // set a new password. + await Accounts.setPasswordAsync(userId, "new password"); + user = await Meteor.users.findOneAsync(userId); + const oldSaltedHash = user.services.password.argon2; + test.isTrue(oldSaltedHash); + // Send a reset password email (setting a reset token) and insert a login + // token. + await Accounts.sendResetPasswordEmail(userId, email); + await Accounts._insertLoginToken(userId, Accounts._generateStampedLoginToken()); + const user2 = await Meteor.users.findOneAsync(userId); + test.isTrue(user2.services.password.reset); + test.isTrue(user2.services.resume.loginTokens); + + // reset with the same password, see we get a different salted hash + await Accounts.setPasswordAsync(userId, "new password", { logout: false }); + user = await Meteor.users.findOneAsync(userId); + const newSaltedHash = user.services.password.argon2; + test.isTrue(newSaltedHash); + test.notEqual(oldSaltedHash, newSaltedHash); + // No more reset token. + const user3 = await Meteor.users.findOneAsync(userId); + test.isFalse(user3.services.password.reset); + // But loginTokens are still here since we did logout: false. + test.isTrue(user3.services.resume.loginTokens); + + // reset again, see that the login tokens are gone. + await Accounts.setPasswordAsync(userId, "new password"); + user = await Meteor.users.findOneAsync(userId); + const newerSaltedHash = user.services.password.argon2; + test.isTrue(newerSaltedHash); + test.notEqual(oldSaltedHash, newerSaltedHash); + test.notEqual(newSaltedHash, newerSaltedHash); + // No more tokens. + const user4 = await Meteor.users.findOneAsync(userId); + test.isFalse(user4.services.password.reset); + test.isFalse(user4.services.resume.loginTokens); + + // cleanup + Accounts._options.argon2Enabled = false; + await Meteor.users.removeAsync(userId); + }); + + Tinytest.addAsync("passwords Argon - migration from argon2 encryption to bcrypt", async (test) => { + Accounts._options.argon2Enabled = true; + const username = Random.id(); + const email = `${username}@bcrypt.com`; + const password = "password"; + const userId = await Accounts.createUser( + { + username: username, + email: email, + password: password + } + ); + Accounts._options.argon2Enabled = false; + let user = await Meteor.users.findOneAsync(userId); + const isValidArgon = await Accounts._checkPasswordAsync(user, password); + test.equal(isValidArgon.userId, userId, "checkPassword with argon2 - User ID should be returned"); + test.equal(typeof isValidArgon.error, "undefined", "checkPassword with argon2 - No error should be returned"); + + // wait for the migration to happen + await waitUntil( + async () => { + user = await Meteor.users.findOneAsync(userId); + return ( + typeof user.services.password.bcrypt === "string" && + typeof user.services.password.argon2 === "undefined" + ); + }, + { description: "bcrypt should be string and argon2 should be undefined" } + ); + + // password is still valid using bcrypt + const isValidBcrypt = await Accounts._checkPasswordAsync(user, password); + test.equal(isValidBcrypt.userId, userId, "checkPassword with argon2 - User ID should be returned"); + test.equal(typeof isValidBcrypt.error, "undefined", "checkPassword with argon2 - No error should be returned"); + + // cleanup + await Meteor.users.removeAsync(userId); + }); + + const getUserHashArgon2Params = function (user) { + const hash = user?.services?.password?.argon2; + return Accounts._getArgon2Params(hash); + } + const hashPasswordWithSha = function (password) { + return { + digest: SHA256(password), + algorithm: "sha-256" + }; + } + + testAsyncMulti("passwords Argon - allow custom argon2 Params and ensure migration if changed", [ + async function(test) { + Accounts._options.argon2Enabled = true; + // Verify that a argon2 hash generated for a new account uses the + // default params. + let username = Random.id(); + this.password = hashPasswordWithSha("abc123"); + this.userId1 = await Accounts.createUserAsync({ username, password: this.password }); + this.user1 = await Meteor.users.findOneAsync(this.userId1); + let argon2Params = getUserHashArgon2Params(this.user1); + test.equal(argon2Params.type, Accounts._argon2Type()); + test.equal(argon2Params.memoryCost, Accounts._argon2MemoryCost()); + test.equal(argon2Params.timeCost, Accounts._argon2TimeCost()); + test.equal(argon2Params.parallelism, Accounts._argon2Parallelism()); + + + // When a custom number of argon2 TimeCost is set via Accounts.config, + // and an account was already created using the default number of TimeCost, + // make sure that a new hash is created (and stored) using the new number + // of TimeCost, the next time the password is checked. + this.customType = "argon2d"; // argon2.argon2d = 2 + this.customTimeCost = 4; + this.customMemoryCost = 32768; + this.customParallelism = 1; + Accounts._options.argon2Type = this.customType; + Accounts._options.argon2TimeCost = this.customTimeCost; + Accounts._options.argon2MemoryCost = this.customMemoryCost; + Accounts._options.argon2Parallelism = this.customParallelism; + + await Accounts._checkPasswordAsync(this.user1, this.password); + }, + async function(test) { + const defaultType = Accounts._argon2Type(); + const defaultTimeCost = Accounts._argon2TimeCost(); + const defaultMemoryCost = Accounts._argon2MemoryCost(); + const defaultParallelism = Accounts._argon2Parallelism(); + let params; + let username; + + let resolve; + const promise = new Promise(res => resolve = res); + + Meteor.setTimeout(async () => { + this.user1 = await Meteor.users.findOneAsync(this.userId1); + params = getUserHashArgon2Params(this.user1); + test.equal(params.type, 2); + test.equal(params.timeCost, this.customTimeCost); + test.equal(params.memoryCost, this.customMemoryCost); + test.equal(params.parallelism, this.customParallelism); + + // When a custom number of argon2 TimeCost is set, make sure it's + // used for new argon2 password hashes. + username = Random.id(); + const userId2 = await Accounts.createUser({ username, password: this.password }); + const user2 = await Meteor.users.findOneAsync(userId2); + params = getUserHashArgon2Params(user2); + test.equal(params.type, 2); + test.equal(params.timeCost, this.customTimeCost); + test.equal(params.memoryCost, this.customMemoryCost); + test.equal(params.parallelism, this.customParallelism); + + // Cleanup + Accounts._options.argon2Enabled = false; + Accounts._options.argon2Type = defaultType; + Accounts._options.argon2TimeCost = defaultTimeCost; + Accounts._options.argon2MemoryCost = defaultMemoryCost; + Accounts._options.argon2Parallelism = defaultParallelism; + await Meteor.users.removeAsync(this.userId1); + await Meteor.users.removeAsync(userId2); + resolve(); + }, 1000); + + return promise; + } + ]); +} diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index e2537f2c5c..151f9c8d5f 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -1,4 +1,5 @@ -import { hash as bcryptHash, compare as bcryptCompare } from 'bcrypt'; +import argon2 from "argon2"; +import { hash as bcryptHash, compare as bcryptCompare } from "bcrypt"; import { Accounts } from "meteor/accounts-base"; // Utility for grabbing user @@ -6,8 +7,9 @@ const getUserById = async (id, options) => await Meteor.users.findOneAsync(id, Accounts._addDefaultFieldSelector(options)); -// User records have a 'services.password.bcrypt' field on them to hold -// their hashed passwords. +// User records have two fields that are used for password-based login: +// - 'services.password.bcrypt', which stores the bcrypt password, which will be deprecated +// - 'services.password.argon2', which stores the argon2 password // // When the client sends a password to the server, it can either be a // string (the plaintext password) or an object with keys 'digest' and @@ -17,127 +19,274 @@ const getUserById = // strings. // // When the server receives a plaintext password as a string, it always -// hashes it with SHA256 before passing it into bcrypt. When the server +// hashes it with SHA256 before passing it into bcrypt / argon2. When the server // receives a password as an object, it asserts that the algorithm is -// "sha-256" and then passes the digest to bcrypt. - +// "sha-256" and then passes the digest to bcrypt / argon2. Accounts._bcryptRounds = () => Accounts._options.bcryptRounds || 10; -// Given a 'password' from the client, extract the string that we should -// bcrypt. 'password' can be one of: -// - String (the plaintext password) -// - Object with 'digest' and 'algorithm' keys. 'algorithm' must be "sha-256". -// +Accounts._argon2Enabled = () => Accounts._options.argon2Enabled || false; + +const ARGON2_TYPES = { + argon2i: argon2.argon2i, + argon2d: argon2.argon2d, + argon2id: argon2.argon2id +}; + +Accounts._argon2Type = () => ARGON2_TYPES[Accounts._options.argon2Type] || argon2.argon2id; +Accounts._argon2TimeCost = () => Accounts._options.argon2TimeCost || 2; +Accounts._argon2MemoryCost = () => Accounts._options.argon2MemoryCost || 19456; +Accounts._argon2Parallelism = () => Accounts._options.argon2Parallelism || 1; + +/** + * Extracts the string to be encrypted using bcrypt or Argon2 from the given `password`. + * + * @param {string|Object} password - The password provided by the client. It can be: + * - A plaintext string password. + * - An object with the following properties: + * @property {string} digest - The hashed password. + * @property {string} algorithm - The hashing algorithm used. Must be "sha-256". + * + * @returns {string} - The resulting password string to encrypt. + * + * @throws {Error} - If the `algorithm` in the password object is not "sha-256". + */ const getPasswordString = password => { if (typeof password === "string") { password = SHA256(password); - } else { // 'password' is an object + } + else { // 'password' is an object if (password.algorithm !== "sha-256") { throw new Error("Invalid password hash algorithm. " + - "Only 'sha-256' is allowed."); + "Only 'sha-256' is allowed."); } password = password.digest; } return password; }; -// Use bcrypt to hash the password for storage in the database. -// `password` can be a string (in which case it will be run through -// SHA256 before bcrypt) or an object with properties `digest` and -// `algorithm` (in which case we bcrypt `password.digest`). -// -const hashPassword = async password => { +/** + * Encrypt the given `password` using either bcrypt or Argon2. + * @param password can be a string (in which case it will be run through SHA256 before encryption) or an object with properties `digest` and `algorithm` (in which case we bcrypt or Argon2 `password.digest`). + * @returns {Promise} The encrypted password. + */ +const hashPassword = async (password) => { password = getPasswordString(password); - return await bcryptHash(password, Accounts._bcryptRounds()); + if (Accounts._argon2Enabled() === true) { + return await argon2.hash(password, { + type: Accounts._argon2Type(), + timeCost: Accounts._argon2TimeCost(), + memoryCost: Accounts._argon2MemoryCost(), + parallelism: Accounts._argon2Parallelism() + }); + } + else { + return await bcryptHash(password, Accounts._bcryptRounds()); + } }; // Extract the number of rounds used in the specified bcrypt hash. -const getRoundsFromBcryptHash = hash => { +const getRoundsFromBcryptHash = (hash) => { let rounds; if (hash) { - const hashSegments = hash.split('$'); + const hashSegments = hash.split("$"); if (hashSegments.length > 2) { rounds = parseInt(hashSegments[2], 10); } } return rounds; }; +Accounts._getRoundsFromBcryptHash = getRoundsFromBcryptHash; -// Check whether the provided password matches the bcrypt'ed password in -// the database user record. `password` can be a string (in which case -// it will be run through SHA256 before bcrypt) or an object with -// properties `digest` and `algorithm` (in which case we bcrypt -// `password.digest`). -// -// The user parameter needs at least user._id and user.services -Accounts._checkPasswordUserFields = {_id: 1, services: 1}; -// + +/** + * Extract readable parameters from an Argon2 hash string. + * @param {string} hash - The Argon2 hash string. + * @returns {object} An object containing the parsed parameters. + * @throws {Error} If the hash format is invalid. + */ +function getArgon2Params(hash) { + const regex = /^\$(argon2(?:i|d|id))\$v=\d+\$m=(\d+),t=(\d+),p=(\d+)/; + + const match = hash.match(regex); + + if (!match) { + throw new Error("Invalid Argon2 hash format."); + } + + const [, type, memoryCost, timeCost, parallelism] = match; + + return { + type: ARGON2_TYPES[type], + timeCost: parseInt(timeCost, 10), + memoryCost: parseInt(memoryCost, 10), + parallelism: parseInt(parallelism, 10) + }; +} + +Accounts._getArgon2Params = getArgon2Params; + +const getUserPasswordHash = user => { + return user.services?.password?.argon2 || user.services?.password?.bcrypt; +}; + +Accounts._checkPasswordUserFields = { _id: 1, services: 1 }; + +const isBcrypt = (hash) => { + // bcrypt hashes start with $2a$ or $2b$ + return hash.startsWith("$2"); +}; + +const isArgon = (hash) => { + // argon2 hashes start with $argon2i$, $argon2d$ or $argon2id$ + return hash.startsWith("$argon2"); +} + +const updateUserPasswordDefered = (user, formattedPassword) => { + Meteor.defer(async () => { + await updateUserPassword(user, formattedPassword); + }); +}; + +/** + * Hashes the provided password and returns an object that can be used to update the user's password. + * @param formattedPassword + * @returns {Promise<{$set: {"services.password.bcrypt": string}}|{$unset: {"services.password.bcrypt": number}, $set: {"services.password.argon2": string}}>} + */ +const getUpdatorForUserPassword = async (formattedPassword) => { + const encryptedPassword = await hashPassword(formattedPassword); + if (Accounts._argon2Enabled() === false) { + return { + $set: { + "services.password.bcrypt": encryptedPassword + }, + $unset: { + "services.password.argon2": 1 + } + }; + } + else if (Accounts._argon2Enabled() === true) { + return { + $set: { + "services.password.argon2": encryptedPassword + }, + $unset: { + "services.password.bcrypt": 1 + } + }; + } +}; + +const updateUserPassword = async (user, formattedPassword) => { + const updator = await getUpdatorForUserPassword(formattedPassword); + await Meteor.users.updateAsync({ _id: user._id }, updator); +}; + +/** + * Checks whether the provided password matches the hashed password stored in the user's database record. + * + * @param {Object} user - The user object containing at least: + * @property {string} _id - The user's unique identifier. + * @property {Object} services - The user's services data. + * @property {Object} services.password - The user's password object. + * @property {string} [services.password.argon2] - The Argon2 hashed password. + * @property {string} [services.password.bcrypt] - The bcrypt hashed password, deprecated + * + * @param {string|Object} password - The password provided by the client. It can be: + * - A plaintext string password. + * - An object with the following properties: + * @property {string} digest - The hashed password. + * @property {string} algorithm - The hashing algorithm used. Must be "sha-256". + * + * @returns {Promise} - A result object with the following properties: + * @property {string} userId - The user's unique identifier. + * @property {Object} [error] - An error object if the password does not match or an error occurs. + * + * @throws {Error} - If an unexpected error occurs during the process. + */ const checkPasswordAsync = async (user, password) => { const result = { userId: user._id }; const formattedPassword = getPasswordString(password); - const hash = user.services.password.bcrypt; - const hashRounds = getRoundsFromBcryptHash(hash); + const hash = getUserPasswordHash(user); - if (! await bcryptCompare(formattedPassword, hash)) { - result.error = Accounts._handleError("Incorrect password", false); - } else if (hash && Accounts._bcryptRounds() != hashRounds) { - // The password checks out, but the user's bcrypt hash needs to be updated. - Meteor.defer(async () => { - await Meteor.users.updateAsync({ _id: user._id }, { - $set: { - 'services.password.bcrypt': - await bcryptHash(formattedPassword, Accounts._bcryptRounds()) + const argon2Enabled = Accounts._argon2Enabled(); + if (argon2Enabled === false) { + if (isArgon(hash)) { + // this is a rollback feature, enabling to switch back from argon2 to bcrypt if needed + // TODO : deprecate this + console.warn("User has an argon2 password and argon2 is not enabled, rolling back to bcrypt encryption"); + const match = await argon2.verify(hash, formattedPassword); + if (!match) { + result.error = Accounts._handleError("Incorrect password", false); + } + else{ + // The password checks out, but the user's stored password needs to be updated to argon2 + updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" }); + } + } + else { + const hashRounds = getRoundsFromBcryptHash(hash); + const match = await bcryptCompare(formattedPassword, hash); + if (!match) { + result.error = Accounts._handleError("Incorrect password", false); + } + else if (hash) { + const paramsChanged = hashRounds !== Accounts._bcryptRounds(); + // The password checks out, but the user's bcrypt hash needs to be updated + // to match current bcrypt settings + if (paramsChanged === true) { + updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" }); } - }); - }); + } + } } + else if (argon2Enabled === true) { + if (isBcrypt(hash)) { + // migration code from bcrypt to argon2 + const match = await bcryptCompare(formattedPassword, hash); + if (!match) { + result.error = Accounts._handleError("Incorrect password", false); + } + else { + // The password checks out, but the user's stored password needs to be updated to argon2 + updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" }); + } + } + else { + // argon2 password + const argon2Params = getArgon2Params(hash); + const match = await argon2.verify(hash, formattedPassword); + if (!match) { + result.error = Accounts._handleError("Incorrect password", false); + } + else if (hash) { + const paramsChanged = argon2Params.memoryCost !== Accounts._argon2MemoryCost() || + argon2Params.timeCost !== Accounts._argon2TimeCost() || + argon2Params.parallelism !== Accounts._argon2Parallelism() || + argon2Params.type !== Accounts._argon2Type(); + if (paramsChanged === true) { + // The password checks out, but the user's argon2 hash needs to be updated with the right params + updateUserPasswordDefered(user, { digest: formattedPassword, algorithm: "sha-256" }); + } + } + } + } + return result; }; -Accounts._checkPasswordAsync = checkPasswordAsync; +Accounts._checkPasswordAsync = checkPasswordAsync; /// /// LOGIN /// -/** - * @summary Finds the user asynchronously with the specified username. - * First tries to match username case sensitively; if that fails, it - * tries case insensitively; but if more than one user matches the case - * insensitive search, it returns null. - * @locus Server - * @param {String} username The username to look for - * @param {Object} [options] - * @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude. - * @returns {Promise} A user if found, else null - * @importFromPackage accounts-base - */ -Accounts.findUserByUsername = - async (username, options) => - await Accounts._findUserByQuery({ username }, options); - -/** - * @summary Finds the user asynchronously with the specified email. - * First tries to match email case sensitively; if that fails, it - * tries case insensitively; but if more than one user matches the case - * insensitive search, it returns null. - * @locus Server - * @param {String} email The email address to look for - * @param {Object} [options] - * @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude. - * @returns {Promise} A user if found, else null - * @importFromPackage accounts-base - */ -Accounts.findUserByEmail = - async (email, options) => - await Accounts._findUserByQuery({ email }, options); // XXX maybe this belongs in the check package const NonEmptyString = Match.Where(x => { @@ -185,9 +334,7 @@ Accounts.registerLoginHandler("password", async options => { Accounts._handleError("User not found"); } - - if (!user.services || !user.services.password || - !user.services.password.bcrypt) { + if (!getUserPasswordHash(user)) { Accounts._handleError("User has no password set"); } @@ -267,51 +414,54 @@ Accounts.setUsername = // `digest` and `algorithm` (representing the SHA256 of the password). Meteor.methods( { - changePassword: async function (oldPassword, newPassword) { - check(oldPassword, passwordValidator); - check(newPassword, passwordValidator); + changePassword: async function(oldPassword, newPassword) { + check(oldPassword, passwordValidator); + check(newPassword, passwordValidator); - if (!this.userId) { - throw new Meteor.Error(401, "Must be logged in"); - } + if (!this.userId) { + throw new Meteor.Error(401, "Must be logged in"); + } - const user = await getUserById(this.userId, {fields: { - services: 1, - ...Accounts._checkPasswordUserFields, - }}); - if (!user) { - Accounts._handleError("User not found"); - } + const user = await getUserById(this.userId, { + fields: { + services: 1, + ...Accounts._checkPasswordUserFields + } + }); + if (!user) { + Accounts._handleError("User not found"); + } - if (!user.services || !user.services.password || !user.services.password.bcrypt) { - Accounts._handleError("User has no password set"); - } + if (!getUserPasswordHash(user)) { + Accounts._handleError("User has no password set"); + } - const result = await checkPasswordAsync(user, oldPassword); - if (result.error) { - throw result.error; - } + const result = await checkPasswordAsync(user, oldPassword); + if (result.error) { + throw result.error; + } - const hashed = await hashPassword(newPassword); + // It would be better if this removed ALL existing tokens and replaced + // the token for the current connection with a new one, but that would + // be tricky, so we'll settle for just replacing all tokens other than + // the one for the current connection. + const currentToken = Accounts._getLoginToken(this.connection.id); + const updator = await getUpdatorForUserPassword(newPassword); - // It would be better if this removed ALL existing tokens and replaced - // the token for the current connection with a new one, but that would - // be tricky, so we'll settle for just replacing all tokens other than - // the one for the current connection. - const currentToken = Accounts._getLoginToken(this.connection.id); - await Meteor.users.updateAsync( - { _id: this.userId }, - { - $set: { 'services.password.bcrypt': hashed }, - $pull: { - 'services.resume.loginTokens': { hashedToken: { $ne: currentToken } } - }, - $unset: { 'services.password.reset': 1 } + await Meteor.users.updateAsync( + { _id: this.userId }, + { + $set: updator.$set, + $pull: { + "services.resume.loginTokens": { hashedToken: { $ne: currentToken } } + }, + $unset: { "services.password.reset": 1, ...updator.$unset } + } + ); + + return { passwordChanged: true }; } - ); - - return {passwordChanged: true}; -}}); + }); // Force change the users password. @@ -320,37 +470,34 @@ Meteor.methods( * @summary Forcibly change the password for a user. * @locus Server * @param {String} userId The id of the user to update. - * @param {String} newPassword A new password for the user. + * @param {String} newPlaintextPassword A new password for the user. * @param {Object} [options] * @param {Object} options.logout Logout all current connections with this userId (default: true) * @importFromPackage accounts-base */ Accounts.setPasswordAsync = async (userId, newPlaintextPassword, options) => { - check(userId, String); - check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)); - check(options, Match.Maybe({ logout: Boolean })); - options = { logout: true , ...options }; + check(userId, String); + check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)); + check(options, Match.Maybe({ logout: Boolean })); + options = { logout: true, ...options }; - const user = await getUserById(userId, { fields: { _id: 1 } }); - if (!user) { - throw new Meteor.Error(403, "User not found"); - } + const user = await getUserById(userId, { fields: { _id: 1 } }); + if (!user) { + throw new Meteor.Error(403, "User not found"); + } - const update = { - $unset: { - 'services.password.reset': 1 - }, - $set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)} + let updator = await getUpdatorForUserPassword(newPlaintextPassword); + updator.$unset = updator.$unset || {}; + updator.$unset["services.password.reset"] = 1; + + if (options.logout) { + updator.$unset["services.resume.loginTokens"] = 1; + } + + await Meteor.users.updateAsync({ _id: user._id }, updator); }; - if (options.logout) { - update.$unset['services.resume.loginTokens'] = 1; - } - - await Meteor.users.updateAsync({_id: user._id}, update); -}; - /// /// RESETTING VIA EMAIL /// @@ -430,25 +577,32 @@ Accounts.generateResetToken = // if this method is called from the enroll account work-flow then // store the token record in 'services.password.enroll' db field // else store the token record in in 'services.password.reset' db field - if(reason === 'enrollAccount') { - await Meteor.users.updateAsync({_id: user._id}, { - $set : { - 'services.password.enroll': tokenRecord + if (reason === "enrollAccount") { + await Meteor.users.updateAsync( + { _id: user._id }, + { + $set: { + "services.password.enroll": tokenRecord + } } - }); + ); // before passing to template, update user object with new token - Meteor._ensure(user, 'services', 'password').enroll = tokenRecord; - } else { - await Meteor.users.updateAsync({_id: user._id}, { - $set : { - 'services.password.reset': tokenRecord + Meteor._ensure(user, "services", "password").enroll = tokenRecord; + } + else { + await Meteor.users.updateAsync( + { _id: user._id }, + { + $set: { + "services.password.reset": tokenRecord + } } - }); + ); // before passing to template, update user object with new token - Meteor._ensure(user, 'services', 'password').reset = tokenRecord; + Meteor._ensure(user, "services", "password").reset = tokenRecord; } - return {email, user, token}; + return { email, user, token }; }; /** @@ -530,11 +684,11 @@ Accounts.sendResetPasswordEmail = async (userId, email, extraTokenData, extraParams) => { const { email: realEmail, user, token } = await Accounts.generateResetToken(userId, email, 'resetPassword', extraTokenData); - const url = Accounts.urls.resetPassword(token, extraParams); + const url = await Accounts._resolvePromise(Accounts.urls.resetPassword(token, extraParams)); const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'resetPassword'); await Email.sendAsync(options); - if (Meteor.isDevelopment) { + if (Meteor.isDevelopment && !Meteor.isPackageTest) { console.log(`\nReset password URL: ${ url }`); } return { email: realEmail, user, token, url, options }; @@ -564,13 +718,13 @@ Accounts.sendEnrollmentEmail = const { email: realEmail, user, token } = await Accounts.generateResetToken(userId, email, 'enrollAccount', extraTokenData); - const url = Accounts.urls.enrollAccount(token, extraParams); + const url = await Accounts._resolvePromise(Accounts.urls.enrollAccount(token, extraParams)); const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'enrollAccount'); await Email.sendAsync(options); - if (Meteor.isDevelopment) { + if (Meteor.isDevelopment && !Meteor.isPackageTest) { console.log(`\nEnrollment email URL: ${ url }`); } return { email: realEmail, user, token, url, options }; @@ -642,8 +796,6 @@ Meteor.methods( error: new Meteor.Error(403, "Token has invalid email address") }; - const hashed = await hashPassword(newPassword); - // NOTE: We're about to invalidate tokens on the user, who we might be // logged in as. Make sure to avoid logging ourselves out if this // happens. But also make sure not to leave the connection in a state @@ -653,6 +805,8 @@ Meteor.methods( const resetToOldToken = () => Accounts._setLoginToken(user._id, this.connection, oldToken); + const updator = await getUpdatorForUserPassword(newPassword); + try { // Update the user record by: // - Changing the password to the new one @@ -664,29 +818,36 @@ Meteor.methods( affectedRecords = await Meteor.users.updateAsync( { _id: user._id, - 'emails.address': email, - 'services.password.enroll.token': token + "emails.address": email, + "services.password.enroll.token": token }, { $set: { - 'services.password.bcrypt': hashed, - 'emails.$.verified': true + "emails.$.verified": true, + ...updator.$set }, - $unset: { 'services.password.enroll': 1 } + $unset: { + "services.password.enroll": 1, + ...updator.$unset + } }); - } else { + } + else { affectedRecords = await Meteor.users.updateAsync( { _id: user._id, - 'emails.address': email, - 'services.password.reset.token': token + "emails.address": email, + "services.password.reset.token": token }, { $set: { - 'services.password.bcrypt': hashed, - 'emails.$.verified': true + "emails.$.verified": true, + ...updator.$set }, - $unset: { 'services.password.reset': 1 } + $unset: { + "services.password.reset": 1, + ...updator.$unset + } }); } if (affectedRecords !== 1) @@ -704,15 +865,16 @@ Meteor.methods( await Accounts._clearAllLoginTokens(user._id); if (Accounts._check2faEnabled?.(user)) { - return { - userId: user._id, - error: Accounts._handleError( - 'Changed password, but user not logged in because 2FA is enabled', - false, - '2fa-enabled' - ), - }; - }return { userId: user._id }; + return { + userId: user._id, + error: Accounts._handleError( + 'Changed password, but user not logged in because 2FA is enabled', + false, + '2fa-enabled' + ), + }; + } + return { userId: user._id }; } ); } @@ -745,10 +907,10 @@ Accounts.sendVerificationEmail = const { email: realEmail, user, token } = await Accounts.generateVerificationToken(userId, email, extraTokenData); - const url = Accounts.urls.verifyEmail(token, extraParams); + const url = await Accounts._resolvePromise(Accounts.urls.verifyEmail(token, extraParams)); const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'verifyEmail'); await Email.sendAsync(options); - if (Meteor.isDevelopment) { + if (Meteor.isDevelopment && !Meteor.isPackageTest) { console.log(`\nVerification email URL: ${ url }`); } return { email: realEmail, user, token, url, options }; @@ -829,6 +991,52 @@ Meteor.methods( } }); + +/** + * @summary Asynchronously replace an email address for a user. Use this instead of directly + * updating the database. The operation will fail if there is a different user + * with an email only differing in case. If the specified user has an existing + * email only differing in case however, we replace it. + * @locus Server + * @param {String} userId The ID of the user to update. + * @param {String} oldEmail The email address to replace. + * @param {String} newEmail The new email address to use. + * @param {Boolean} [verified] Optional - whether the new email address should + * be marked as verified. Defaults to false. + * @importFromPackage accounts-base + */ +Accounts.replaceEmailAsync = async (userId, oldEmail, newEmail, verified) => { + check(userId, NonEmptyString); + check(oldEmail, NonEmptyString); + check(newEmail, NonEmptyString); + check(verified, Match.Optional(Boolean)); + + if (verified === void 0) { + verified = false; + } + + const user = await getUserById(userId, { fields: { _id: 1 } }); + if (!user) + throw new Meteor.Error(403, "User not found"); + + // Ensure no user already has this new email + await Accounts._checkForCaseInsensitiveDuplicates( + "emails.address", + "Email", + newEmail, + user._id + ); + + const result = await Meteor.users.updateAsync( + { _id: user._id, 'emails.address': oldEmail }, + { $set: { 'emails.$.address': newEmail, 'emails.$.verified': verified } } + ); + + if (result.modifiedCount === 0) { + throw new Meteor.Error(404, "No user could be found with old email"); + } +}; + /** * @summary Asynchronously add an email address for a user. Use this instead of directly * updating the database. The operation will fail if there is a different user @@ -990,7 +1198,13 @@ const createUser = const user = { services: {} }; if (password) { const hashed = await hashPassword(password); - user.services.password = { bcrypt: hashed }; + const argon2Enabled = Accounts._argon2Enabled(); + if (argon2Enabled === false) { + user.services.password = { bcrypt: hashed }; + } + else { + user.services.password = { argon2: hashed }; + } } return await Accounts._createUserCheckingDuplicates({ user, email, username, options }); @@ -1074,17 +1288,7 @@ Accounts.createUserVerifyingEmail = // method calling Accounts.createUser could set? // -Accounts.createUserAsync = - async (options, callback) => { - options = { ...options }; - - // XXX allow an optional callback? - if (callback) { - throw new Error("Accounts.createUser with callback not supported on the server yet."); - } - - return createUser(options); - }; +Accounts.createUserAsync = createUser // Create user directly on the server. // @@ -1110,4 +1314,3 @@ await Meteor.users.createIndexAsync('services.password.reset.token', { unique: true, sparse: true }); await Meteor.users.createIndexAsync('services.password.enroll.token', { unique: true, sparse: true }); - diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index 88461e7d91..49f94544a0 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -10,7 +10,7 @@ const makeTestConnAsync = }) const simplePollAsync = (fn) => new Promise((resolve, reject) => simplePoll(fn,resolve,reject)) -function hashPassword(password) { +function hashPasswordWithSha(password) { return { digest: SHA256(password), algorithm: "sha-256" @@ -54,23 +54,37 @@ if (Meteor.isClient) (() => { const removeSkipCaseInsensitiveChecksForTest = (value, test, expect) => Meteor.call('removeSkipCaseInsensitiveChecksForTest', value); - const createUserStep = function (test, expect) { + // Make logout steps awaitable so subsequent test steps don't race. + const logoutStep = async (test, expect) => + new Promise(resolve => { + Meteor.logout(err => { + if (err) { + // keep original behavior: fail the test if logout errored + test.fail(err.message); + // still resolve so test runner can continue + return resolve(); + } + test.equal(Meteor.user(), null); + resolve(); + }); + }); + + // Create user only after a confirmed logout to avoid races between + // tests that do login/logout operations. + const createUserStep = async function (test, expect) { + // Wait for the logout to complete synchronously. + await logoutStep(test, expect); + // Hack because Tinytest does not clean the database between tests/runs this.randomSuffix = Random.id(10); this.username = `AdaLovelace${ this.randomSuffix }`; this.email = `Ada-intercept@lovelace.com${ this.randomSuffix }`; this.password = 'password'; - Accounts.createUser( - { username: this.username, email: this.email, password: this.password }, - loggedInAs(this.username, test, expect)); - }; - const logoutStep = (test, expect) => - Meteor.logout(expect(error => { - if (error) { - test.fail(error.message); - } - test.equal(Meteor.user(), null); - })); + + Accounts.createUser( + { username: this.username, email: this.email, password: this.password }, + loggedInAs(this.username, test, expect)); + }; const loggedInAs = (someUsername, test, expect) => { return expect(error => { if (error) { @@ -79,18 +93,7 @@ if (Meteor.isClient) (() => { test.equal(Meteor.userId() && Meteor.user().username, someUsername); }); }; - const loggedInUserHasEmail = (someEmail, test, expect) => { - return expect(error => { - if (error) { - test.fail(error.message); - } - const user = Meteor.user(); - test.isTrue(user && user.emails.reduce( - (prev, email) => prev || email.address === someEmail, - false - )); - }); - }; + const expectError = (expectedError, test, expect) => expect(actualError => { test.equal(actualError && actualError.error, expectedError.error); test.equal(actualError && actualError.reason, expectedError.reason); @@ -486,7 +489,7 @@ if (Meteor.isClient) (() => { function (test, expect) { this.secondConn = DDP.connect(Meteor.absoluteUrl()); this.secondConn.call('login', - { user: { username: this.username }, password: hashPassword(this.password) }, + { user: { username: this.username }, password: hashPasswordWithSha(this.password) }, expect((err, result) => { test.isFalse(err); this.secondConn.setUserId(result.id); @@ -802,7 +805,7 @@ if (Meteor.isClient) (() => { // Can update own profile using ID. await Meteor.users.updateAsync( this.userId, { $set: { 'profile.updated': 42 } }, - ); + ); test.equal(42, Meteor.user().profile.updated); }, logoutStep @@ -1212,10 +1215,10 @@ if (Meteor.isServer) (() => { // This test properly belongs in accounts-base/accounts_tests.js, but // this is where the tests that actually log in are. - Tinytest.addAsync('accounts - user() out of context', async test => { + Tinytest.addAsync('accounts - userAsync() out of context', async test => { await test.throwsAsync( async () => - await Meteor.user() + await Meteor.userAsync() ); await Meteor.users.removeAsync({}); }); @@ -1230,7 +1233,7 @@ if (Meteor.isServer) (() => { const username = Random.id(); const id = await Accounts.createUser({ username: username, - password: hashPassword('password') + password: hashPasswordWithSha('password') }); const { @@ -1245,7 +1248,7 @@ if (Meteor.isServer) (() => { const result = await clientConn.callAsync('login', { user: { username: username }, - password: hashPassword('password') + password: hashPasswordWithSha('password') }); test.isTrue(result); @@ -1278,7 +1281,7 @@ if (Meteor.isServer) (() => { const userId = await Accounts.createUser({ username: username, email: email, - password: hashPassword("old-password") + password: hashPasswordWithSha("old-password") }); const user = await Meteor.users.findOneAsync(userId); @@ -1297,16 +1300,17 @@ if (Meteor.isServer) (() => { await test.throwsAsync( async () => - await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password")), + await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password")), /Token has invalid email address/ ); + Accounts._options.ambiguousErrorMessages = true; await test.throwsAsync( async () => await Meteor.callAsync( "login", { user: { username: username }, - password: hashPassword("new-password") + password: hashPasswordWithSha("new-password") } ), /Something went wrong. Please check your credentials./); @@ -1321,7 +1325,7 @@ if (Meteor.isServer) (() => { const userId = await Accounts.createUser({ username: username, email: email, - password: hashPassword("old-password") + password: hashPasswordWithSha("old-password") }); const user = await Meteor.users.findOneAsync(userId); @@ -1338,11 +1342,11 @@ if (Meteor.isServer) (() => { test.isTrue(await clientConn.callAsync( "resetPassword", resetPasswordToken, - hashPassword("new-password") + hashPasswordWithSha("new-password") )); test.isTrue(await clientConn.callAsync("login", { user: { username }, - password: hashPassword("new-password") + password: hashPasswordWithSha("new-password") })); }); @@ -1355,7 +1359,7 @@ if (Meteor.isServer) (() => { const userId = await Accounts.createUser({ username: username, email: email, - password: hashPassword("old-password") + password: hashPasswordWithSha("old-password") }); const user = await Meteor.users.findOneAsync(userId); @@ -1373,19 +1377,20 @@ if (Meteor.isServer) (() => { await Meteor.users.updateAsync(userId, { $set: { "services.password.reset.when": new Date(Date.now() + -5 * 24 * 3600 * 1000) } }); try { - await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password")) + await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password")) } catch (e) { test.throws(() => { throw e; }) } + Accounts._options.ambiguousErrorMessages = true; await test.throwsAsync( async () => await Meteor.callAsync( "login", { user: { username: username }, - password: hashPassword("new-password") + password: hashPasswordWithSha("new-password") } ), /Something went wrong. Please check your credentials./); @@ -1405,7 +1410,7 @@ if (Meteor.isServer) (() => { { username: username, email: email, - password: hashPassword(password) + password: hashPasswordWithSha(password) }, ); @@ -1432,7 +1437,7 @@ if (Meteor.isServer) (() => { await Accounts.createUser( { email: email, - password: hashPassword('password') + password: hashPasswordWithSha('password') } ); await Accounts.sendResetPasswordEmail(userId, email); @@ -1452,7 +1457,7 @@ if (Meteor.isServer) (() => { await Accounts.createUser( { email: email, - password: hashPassword('password') + password: hashPasswordWithSha('password') } ); await Accounts.sendResetPasswordEmail(userId, email); @@ -1498,12 +1503,12 @@ if (Meteor.isServer) (() => { await clientConn.callAsync( "resetPassword", enrollPasswordToken, - hashPassword("new-password")) + hashPasswordWithSha("new-password")) ); test.isTrue( await clientConn.callAsync("login", { user: { username }, - password: hashPassword("new-password") + password: hashPasswordWithSha("new-password") }) ); @@ -1535,7 +1540,7 @@ if (Meteor.isServer) (() => { await Meteor.users.updateAsync(userId, { $set: { "services.password.enroll.when": new Date(Date.now() + -35 * 24 * 3600 * 1000) } }); await test.throwsAsync( - async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPassword("new-password")), + async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPasswordWithSha("new-password")), /Token expired/ ); }); @@ -1544,7 +1549,7 @@ if (Meteor.isServer) (() => { async test => { const email = `${ test.id }-intercept@example.com`; const userId = - await Accounts.createUser({ email: email, password: hashPassword('password') }); + await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') }); await Accounts.sendEnrollmentEmail(userId, email); const user1 = await Meteor.users.findOneAsync(userId); @@ -1561,7 +1566,7 @@ if (Meteor.isServer) (() => { const userId = await Accounts.createUser({ email: email, - password: hashPassword('password') + password: hashPasswordWithSha('password') }); await Accounts.sendEnrollmentEmail(userId, email); @@ -1580,7 +1585,7 @@ if (Meteor.isServer) (() => { async test => { const email = `${ test.id }-intercept@example.com`; const userId = - await Accounts.createUser({ email: email, password: hashPassword('password') }); + await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') }); await Accounts.sendResetPasswordEmail(userId, email); const user1 = await Meteor.users.findOneAsync(userId); @@ -1669,6 +1674,7 @@ if (Meteor.isServer) (() => { test.isTrue(userId1); test.isTrue(userId2); + Accounts._options.ambiguousErrorMessages = false; await test.throwsAsync( async () => await Accounts.setUsername(userId2, usernameUpper), /Username already exists/ @@ -1727,108 +1733,131 @@ if (Meteor.isServer) (() => { }); Tinytest.addAsync("passwords - add email when user has not an existing email", - async test => { - const userId = await Accounts.createUser({ - username: `user${ Random.id() }` - }); + async test => { + const userId = await Accounts.createUser({ + username: `user${ Random.id() }` + }); - const newEmail = `${ Random.id() }@turing.com`; - await Accounts.addEmailAsync(userId, newEmail); - const u1 = await Accounts._findUserByQuery({ id: userId }) - test.equal(u1.emails, [ - { address: newEmail, verified: false }, - ]); - }); + const newEmail = `${ Random.id() }@turing.com`; + await Accounts.addEmailAsync(userId, newEmail); + const u1 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u1.emails, [ + { address: newEmail, verified: false }, + ]); + }); Tinytest.addAsync("passwords - add email when the user has an existing email " + "only differing in case", async test => { - const origEmail = `${ Random.id() }@turing.com`; - const userId = await Accounts.createUser({ - email: origEmail + const origEmail = `${ Random.id() }@turing.com`; + const userId = await Accounts.createUser({ + email: origEmail + }); + + const newEmail = `${ Random.id() }@turing.com`; + await Accounts.addEmailAsync(userId, newEmail); + + const thirdEmail = origEmail.toUpperCase(); + await Accounts.addEmailAsync(userId, thirdEmail, true); + const u1 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u1.emails, [ + { address: thirdEmail, verified: true }, + { address: newEmail, verified: false } + ]); }); - const newEmail = `${ Random.id() }@turing.com`; - await Accounts.addEmailAsync(userId, newEmail); - - const thirdEmail = origEmail.toUpperCase(); - await Accounts.addEmailAsync(userId, thirdEmail, true); - const u1 = await Accounts._findUserByQuery({ id: userId }) - test.equal(u1.emails, [ - { address: thirdEmail, verified: true }, - { address: newEmail, verified: false } - ]); - }); - Tinytest.addAsync("passwords - add email should fail when there is an existing " + "user with an email only differing in case", async test => { - const user1Email = `${ Random.id() }@turing.com`; - const userId1 = await Accounts.createUser({ - email: user1Email + const user1Email = `${ Random.id() }@turing.com`; + const userId1 = await Accounts.createUser({ + email: user1Email + }); + + const user2Email = `${ Random.id() }@turing.com`; + const userId2 = await Accounts.createUser({ + email: user2Email + }); + + const dupEmail = user1Email.toUpperCase(); + await test.throwsAsync( + async () => await Accounts.addEmailAsync(userId2, dupEmail), + /Email already exists/ + ); + + const u1 = await Accounts._findUserByQuery({ id: userId1 }) + test.equal(u1.emails, [ + { address: user1Email, verified: false } + ]); + const u2 = await Accounts._findUserByQuery({ id: userId2 }) + test.equal(u2.emails, [ + { address: user2Email, verified: false } + ]); }); - const user2Email = `${ Random.id() }@turing.com`; - const userId2 = await Accounts.createUser({ - email: user2Email - }); - const dupEmail = user1Email.toUpperCase(); - await test.throwsAsync( - async () => await Accounts.addEmailAsync(userId2, dupEmail), - /Email already exists/ - ); - const u1 = await Accounts._findUserByQuery({ id: userId1 }) - test.equal(u1.emails, [ - { address: user1Email, verified: false } - ]); - const u2 = await Accounts._findUserByQuery({ id: userId2 }) - test.equal(u2.emails, [ - { address: user2Email, verified: false } - ]); +Tinytest.addAsync("accounts emails - replace email", async test => { + const origEmail = `originalemail@test.com`; + const userId = await Accounts.createUserAsync({ + email: origEmail, + password: 'password' }); - Tinytest.addAsync("passwords - remove email", + const newEmail = `newemail@test.com`; + + const u1 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u1.emails, [ + { address: origEmail, verified: false } + ]); + + await Accounts.replaceEmailAsync(userId, origEmail, newEmail); + const u2 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u2.emails, [ + { address: newEmail, verified: false } + ]); +}) + + Tinytest.addAsync("passwords - remove email", async test => { - const origEmail = `${ Random.id() }@turing.com`; - const userId = await Accounts.createUser({ - email: origEmail + const origEmail = `${ Random.id() }@turing.com`; + const userId = await Accounts.createUser({ + email: origEmail + }); + + const newEmail = `${ Random.id() }@turing.com`; + await Accounts.addEmailAsync(userId, newEmail); + + const thirdEmail = `${ Random.id() }@turing.com`; + await Accounts.addEmailAsync(userId, thirdEmail, true); + const u1 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u1.emails, [ + { address: origEmail, verified: false }, + { address: newEmail, verified: false }, + { address: thirdEmail, verified: true } + ]); + + await Accounts.removeEmail(userId, newEmail); + const u2 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u2.emails, [ + { address: origEmail, verified: false }, + { address: thirdEmail, verified: true } + ]); + + await Accounts.removeEmail(userId, origEmail); + const u3 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u3.emails, [ + { address: thirdEmail, verified: true } + ]); }); - const newEmail = `${ Random.id() }@turing.com`; - await Accounts.addEmailAsync(userId, newEmail); - - const thirdEmail = `${ Random.id() }@turing.com`; - await Accounts.addEmailAsync(userId, thirdEmail, true); - const u1 = await Accounts._findUserByQuery({ id: userId }) - test.equal(u1.emails, [ - { address: origEmail, verified: false }, - { address: newEmail, verified: false }, - { address: thirdEmail, verified: true } - ]); - - await Accounts.removeEmail(userId, newEmail); - const u2 = await Accounts._findUserByQuery({ id: userId }) - test.equal(u2.emails, [ - { address: origEmail, verified: false }, - { address: thirdEmail, verified: true } - ]); - - await Accounts.removeEmail(userId, origEmail); - const u3 = await Accounts._findUserByQuery({ id: userId }) - test.equal(u3.emails, [ - { address: thirdEmail, verified: true } - ]); - }); - const getUserHashRounds = user => Number(user.services.password.bcrypt.substring(4, 6)); testAsyncMulti("passwords - allow custom bcrypt rounds",[ async function (test) { // Verify that a bcrypt hash generated for a new account uses the let username = Random.id(); - this.password = hashPassword('abc123'); + this.password = hashPasswordWithSha('abc123'); this.userId1 = await Accounts.createUser({ username, password: this.password }); this.user1 = await Meteor.users.findOneAsync(this.userId1); let rounds = getUserHashRounds(this.user1); @@ -1876,24 +1905,153 @@ if (Meteor.isServer) (() => { Tinytest.addAsync('passwords - extra params in email urls', async (test) => { - const username = Random.id(); - const email = `${ username }-intercept@example.com`; + const username = Random.id(); + const email = `${ username }-intercept@example.com`; - const userId = await Accounts.createUser({ - username: username, - email: email + const userId = await Accounts.createUser({ + username: username, + email: email + }); + + const extraParams = { test: 'success' }; + await Accounts.sendEnrollmentEmail(userId, email, null, extraParams); + + const [enrollPasswordEmailOptions] = + await Meteor.callAsync("getInterceptedEmails", email); + + const re = new RegExp(`${Meteor.absoluteUrl()}(\\S*)`); + const match = enrollPasswordEmailOptions.text.match(re); + const url = new URL(match) + test.equal(url.searchParams.get('test'), extraParams.test); }); - const extraParams = { test: 'success' }; - await Accounts.sendEnrollmentEmail(userId, email, null, extraParams); + Tinytest.addAsync('passwords - createUserAsync', async test => { + const username = Random.id(); + const email = `${username}-intercept@example.com`; + const password = 'password'; - const [enrollPasswordEmailOptions] = - await Meteor.callAsync("getInterceptedEmails", email); + const userId = await Accounts.createUserAsync({ + username: username, + email: email, + password: password + }); - const re = new RegExp(`${Meteor.absoluteUrl()}(\\S*)`); - const match = enrollPasswordEmailOptions.text.match(re); - const url = new URL(match) - test.equal(url.searchParams.get('test'), extraParams.test); + test.isTrue(userId, 'User ID should be returned'); + const user = await Meteor.users.findOneAsync(userId); + test.equal(user.username, username, 'Username should match'); + test.equal(user.emails[0].address, email, 'Email should match'); + + Accounts.config({ + ambiguousErrorMessages: false, + }) + + await test.throwsAsync(async () => { + await Accounts.createUserAsync({ + username: username, + email: email, + password: password + }); + }, 'already exists'); }); + Tinytest.addAsync('passwords - send email functions', async test => { + // Create a user with an unverified email + const username = Random.id(); + const email = `${username}-intercept@example.com`; + const password = 'password'; + + const userId = await Accounts.createUserAsync({ + username: username, + email: email, + password: password + }); + + test.isTrue(userId, 'User ID should be returned'); + + // Mock Email.sendAsync to track if it was called + const originalSendAsync = Email.sendAsync; + let emailSent = 0; + Email.sendAsync = async (options) => { + emailSent++; + return originalSendAsync(options); + }; + + try { + // Test sendVerificationEmail + const verificationResult = await Accounts.sendVerificationEmail(userId, email); + + // Verify the result contains expected properties + test.isTrue(verificationResult, 'Result should be returned for verification email'); + test.equal(verificationResult.email, email, 'Email in verification result should match'); + test.isTrue(verificationResult.user, 'User object should be in verification result'); + test.isTrue(verificationResult.token, 'Token should be in verification result'); + test.isTrue(verificationResult.url, 'URL should be in verification result'); + test.isTrue(verificationResult.options, 'Email options should be in verification result'); + + // Test sendEnrollmentEmail + const enrollmentResult = await Accounts.sendEnrollmentEmail(userId, email); + + // Verify the result contains expected properties + test.isTrue(enrollmentResult, 'Result should be returned for enrollment email'); + test.equal(enrollmentResult.email, email, 'Email in enrollment result should match'); + test.isTrue(enrollmentResult.user, 'User object should be in enrollment result'); + test.isTrue(enrollmentResult.token, 'Token should be in enrollment result'); + test.isTrue(enrollmentResult.url, 'URL should be in enrollment result'); + test.isTrue(enrollmentResult.options, 'Email options should be in enrollment result'); + + // Test sendResetPasswordEmail + const resetResult = await Accounts.sendResetPasswordEmail(userId, email); + + // Verify the result contains expected properties + test.isTrue(resetResult, 'Result should be returned for reset password email'); + test.equal(resetResult.email, email, 'Email in reset result should match'); + test.isTrue(resetResult.user, 'User object should be in reset result'); + test.isTrue(resetResult.token, 'Token should be in reset result'); + test.isTrue(resetResult.url, 'URL should be in reset result'); + test.isTrue(resetResult.options, 'Email options should be in reset result'); + + // Verify Email.sendAsync was called for all three emails + test.equal(emailSent, 3, 'Email.sendAsync should have been called three times'); + + // Get the intercepted emails + const interceptedEmails = await Meteor.callAsync("getInterceptedEmails", email); + test.equal(interceptedEmails.length, 3, 'Three emails should have been intercepted'); + + // Verify the verification email content + const verificationEmailOptions = interceptedEmails[0]; + test.isTrue(verificationEmailOptions, 'Verification email should have been intercepted'); + const verificationRe = new RegExp(`${Meteor.absoluteUrl()}#/verify-email/(\\S*)`); + const verificationMatch = verificationEmailOptions.text.match(verificationRe); + test.isTrue(verificationMatch, 'Verification email should contain verification URL'); + const verificationTokenFromUrl = verificationMatch[1]; + test.isTrue(verificationResult.url.includes(verificationTokenFromUrl), 'Verification URL in result should contain the token'); + + // Verify the enrollment email content + const enrollmentEmailOptions = interceptedEmails[1]; + test.isTrue(enrollmentEmailOptions, 'Enrollment email should have been intercepted'); + const enrollmentRe = new RegExp(`${Meteor.absoluteUrl()}#/enroll-account/(\\S*)`); + const enrollmentMatch = enrollmentEmailOptions.text.match(enrollmentRe); + test.isTrue(enrollmentMatch, 'Enrollment email should contain enrollment URL'); + const enrollmentTokenFromUrl = enrollmentMatch[1]; + test.isTrue(enrollmentResult.url.includes(enrollmentTokenFromUrl), 'Enrollment URL in result should contain the token'); + + // Verify the reset password email content + const resetEmailOptions = interceptedEmails[2]; + test.isTrue(resetEmailOptions, 'Reset password email should have been intercepted'); + const resetRe = new RegExp(`${Meteor.absoluteUrl()}#/reset-password/(\\S*)`); + const resetMatch = resetEmailOptions.text.match(resetRe); + test.isTrue(resetMatch, 'Reset password email should contain reset URL'); + const resetTokenFromUrl = resetMatch[1]; + test.isTrue(resetResult.url.includes(resetTokenFromUrl), 'Reset URL in result should contain the token'); + + // Verify email headers and from address for all emails + for (const emailOptions of interceptedEmails) { + test.equal(emailOptions.from, 'test@meteor.com', 'From address should match'); + test.equal(emailOptions.headers['My-Custom-Header'], 'Cool', 'Custom header should be present'); + } + } finally { + // Restore the original Email.sendAsync + Email.sendAsync = originalSendAsync; + } + }); })(); diff --git a/packages/accounts-password/password_tests_setup.js b/packages/accounts-password/password_tests_setup.js index 48a34c8766..50a1e95862 100644 --- a/packages/accounts-password/password_tests_setup.js +++ b/packages/accounts-password/password_tests_setup.js @@ -121,7 +121,7 @@ Accounts.config({ Meteor.methods( { testMeteorUser: - async () => await Meteor.user(), + async () => await Meteor.userAsync(), clearUsernameAndProfile: async function () { diff --git a/packages/accounts-passwordless/package.js b/packages/accounts-passwordless/package.js index 902897a01d..060171cd54 100644 --- a/packages/accounts-passwordless/package.js +++ b/packages/accounts-passwordless/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'No-password login/sign-up support for accounts', - version: '3.0.0', + version: '3.0.2', }); Package.onUse(api => { diff --git a/packages/accounts-passwordless/passwordless_server.js b/packages/accounts-passwordless/passwordless_server.js index 33925be23a..20c23e17e8 100644 --- a/packages/accounts-passwordless/passwordless_server.js +++ b/packages/accounts-passwordless/passwordless_server.js @@ -220,7 +220,7 @@ Meteor.methods({ */ Accounts.sendLoginTokenEmail = async ({ userId, sequence, email, extra = {} }) => { const user = await getUserById(userId); - const url = Accounts.urls.loginToken(email, sequence); + const url = await Accounts._resolvePromise(Accounts.urls.loginToken(email, sequence, extra)); const options = await Accounts.generateOptionsForEmail( email, user, diff --git a/packages/allow-deny/allow-deny.js b/packages/allow-deny/allow-deny.js index faf7f774c9..802cd467dd 100644 --- a/packages/allow-deny/allow-deny.js +++ b/packages/allow-deny/allow-deny.js @@ -49,7 +49,7 @@ const CollectionPrototype = AllowDeny.CollectionPrototype; * @memberOf Mongo.Collection * @instance * @param {Object} options - * @param {Function} options.insertAsync,updateAsync,removeAsync Functions that look at a proposed modification to the database and return true if it should be allowed. + * @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be allowed. * @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions. * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation. */ @@ -64,7 +64,7 @@ CollectionPrototype.allow = function(options) { * @memberOf Mongo.Collection * @instance * @param {Object} options - * @param {Function} options.insertAsync,updateAsync,removeAsync Functions that look at a proposed modification to the database and return true if it should be denied, even if an [allow](#allow) rule says otherwise. + * @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be denied, even if an [allow](#allow) rule says otherwise. * @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions. * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation. */ @@ -174,9 +174,14 @@ CollectionPrototype._defineMutationMethods = function(options) { // single-ID selectors. if (!isInsert(method)) throwIfSelectorIsNotId(args[0], method); + const syncMethodName = method.replace('Async', ''); + const syncValidatedMethodName = '_validated' + method.charAt(0).toUpperCase() + syncMethodName.slice(1); + // it forces to use async validated behavior + const validatedMethodName = syncValidatedMethodName + 'Async'; + if (self._restricted) { // short circuit if there is no way it will pass. - if (self._validators[method].allow.length === 0) { + if (self._validators[syncMethodName].allow.length === 0) { throw new Meteor.Error( 403, 'Access denied. No allow validators set on restricted ' + @@ -186,11 +191,6 @@ CollectionPrototype._defineMutationMethods = function(options) { ); } - const syncMethodName = method.replace('Async', ''); - const syncValidatedMethodName = '_validated' + method.charAt(0).toUpperCase() + syncMethodName.slice(1); - // it forces to use async validated behavior on the server - const validatedMethodName = Meteor.isServer ? syncValidatedMethodName + 'Async' : syncValidatedMethodName; - args.unshift(this.userId); isInsert(method) && args.push(generatedId); return self[validatedMethodName].apply(self, args); @@ -292,7 +292,7 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc, const self = this; // call user validators. // Any deny returns true means denied. - if (await asyncSome(self._validators.insertAsync.deny, async (validator) => { + if (await asyncSome(self._validators.insert.deny, async (validator) => { const result = validator(userId, docToValidate(validator, doc, generatedId)); return Meteor._isPromise(result) ? await result : result; })) { @@ -300,7 +300,7 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc, } // Any allow returns true means proceed. Throw error if they all fail. - if (await asyncEvery(self._validators.insertAsync.allow, async (validator) => { + if (await asyncEvery(self._validators.insert.allow, async (validator) => { const result = validator(userId, docToValidate(validator, doc, generatedId)); return !(Meteor._isPromise(result) ? await result : result); })) { @@ -315,36 +315,6 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc, return self._collection.insertAsync.call(self._collection, doc); }; -CollectionPrototype._validatedInsert = function (userId, doc, - generatedId) { - const self = this; - - // call user validators. - // Any deny returns true means denied. - if (self._validators.insert.deny.some((validator) => { - return validator(userId, docToValidate(validator, doc, generatedId)); - })) { - throw new Meteor.Error(403, "Access denied"); - } - // Any allow returns true means proceed. Throw error if they all fail. - - if (self._validators.insert.allow.every((validator) => { - return !validator(userId, docToValidate(validator, doc, generatedId)); - })) { - throw new Meteor.Error(403, "Access denied"); - } - - // If we generated an ID above, insert it now: after the validation, but - // before actually inserting. - if (generatedId !== null) - doc._id = generatedId; - - return (Meteor.isServer - ? self._collection.insertAsync - : self._collection.insert - ).call(self._collection, doc); -}; - // Simulate a mongo `update` operation while validating that the access // control rules set by calls to `allow/deny` are satisfied. If all // pass, rewrite the mongo operation to use $in to set the list of @@ -414,7 +384,7 @@ CollectionPrototype._validatedUpdateAsync = async function( // call user validators. // Any deny returns true means denied. - if (await asyncSome(self._validators.updateAsync.deny, async (validator) => { + if (await asyncSome(self._validators.update.deny, async (validator) => { const factoriedDoc = transformDoc(validator, doc); const result = validator(userId, factoriedDoc, @@ -424,8 +394,9 @@ CollectionPrototype._validatedUpdateAsync = async function( })) { throw new Meteor.Error(403, "Access denied"); } + // Any allow returns true means proceed. Throw error if they all fail. - if (await asyncEvery(self._validators.updateAsync.allow, async (validator) => { + if (await asyncEvery(self._validators.update.allow, async (validator) => { const factoriedDoc = transformDoc(validator, doc); const result = validator(userId, factoriedDoc, @@ -447,102 +418,6 @@ CollectionPrototype._validatedUpdateAsync = async function( self._collection, selector, mutator, options); }; -CollectionPrototype._validatedUpdate = function( - userId, selector, mutator, options) { - const self = this; - - check(mutator, Object); - - options = Object.assign(Object.create(null), options); - - if (!LocalCollection._selectorIsIdPerhapsAsObject(selector)) - throw new Error("validated update should be of a single ID"); - - // We don't support upserts because they don't fit nicely into allow/deny - // rules. - if (options.upsert) - throw new Meteor.Error(403, "Access denied. Upserts not " + - "allowed in a restricted collection."); - - const noReplaceError = "Access denied. In a restricted collection you can only" + - " update documents, not replace them. Use a Mongo update operator, such " + - "as '$set'."; - - const mutatorKeys = Object.keys(mutator); - - // compute modified fields - const modifiedFields = {}; - - if (mutatorKeys.length === 0) { - throw new Meteor.Error(403, noReplaceError); - } - mutatorKeys.forEach((op) => { - const params = mutator[op]; - if (op.charAt(0) !== '$') { - throw new Meteor.Error(403, noReplaceError); - } else if (!hasOwn.call(ALLOWED_UPDATE_OPERATIONS, op)) { - throw new Meteor.Error( - 403, "Access denied. Operator " + op + " not allowed in a restricted collection."); - } else { - Object.keys(params).forEach((field) => { - // treat dotted fields as if they are replacing their - // top-level part - if (field.indexOf('.') !== -1) - field = field.substring(0, field.indexOf('.')); - - // record the field we are trying to change - modifiedFields[field] = true; - }); - } - }); - - const fields = Object.keys(modifiedFields); - - const findOptions = {transform: null}; - if (!self._validators.fetchAllFields) { - findOptions.fields = {}; - self._validators.fetch.forEach((fieldName) => { - findOptions.fields[fieldName] = 1; - }); - } - - const doc = self._collection.findOne(selector, findOptions); - if (!doc) // none satisfied! - return 0; - - // call user validators. - // Any deny returns true means denied. - if (self._validators.update.deny.some((validator) => { - const factoriedDoc = transformDoc(validator, doc); - return validator(userId, - factoriedDoc, - fields, - mutator); - })) { - throw new Meteor.Error(403, "Access denied"); - } - // Any allow returns true means proceed. Throw error if they all fail. - if (self._validators.update.allow.every((validator) => { - const factoriedDoc = transformDoc(validator, doc); - return !validator(userId, - factoriedDoc, - fields, - mutator); - })) { - throw new Meteor.Error(403, "Access denied"); - } - - options._forbidReplace = true; - - // Back when we supported arbitrary client-provided selectors, we actually - // rewrote the selector to include an _id clause before passing to Mongo to - // avoid races, but since selector is guaranteed to already just be an ID, we - // don't have to any more. - - return self._collection.update.call( - self._collection, selector, mutator, options); -}; - // Only allow these operations in validated updates. Specifically // whitelist operations, rather than blacklist, so new complex // operations that are added aren't automatically allowed. A complex @@ -573,14 +448,14 @@ CollectionPrototype._validatedRemoveAsync = async function(userId, selector) { // call user validators. // Any deny returns true means denied. - if (await asyncSome(self._validators.removeAsync.deny, async (validator) => { + if (await asyncSome(self._validators.remove.deny, async (validator) => { const result = validator(userId, transformDoc(validator, doc)); return Meteor._isPromise(result) ? await result : result; })) { throw new Meteor.Error(403, "Access denied"); } // Any allow returns true means proceed. Throw error if they all fail. - if (await asyncEvery(self._validators.removeAsync.allow, async (validator) => { + if (await asyncEvery(self._validators.remove.allow, async (validator) => { const result = validator(userId, transformDoc(validator, doc)); return !(Meteor._isPromise(result) ? await result : result); })) { @@ -595,43 +470,6 @@ CollectionPrototype._validatedRemoveAsync = async function(userId, selector) { return self._collection.removeAsync.call(self._collection, selector); }; -CollectionPrototype._validatedRemove = function(userId, selector) { - const self = this; - - const findOptions = {transform: null}; - if (!self._validators.fetchAllFields) { - findOptions.fields = {}; - self._validators.fetch.forEach((fieldName) => { - findOptions.fields[fieldName] = 1; - }); - } - - const doc = self._collection.findOne(selector, findOptions); - if (!doc) - return 0; - - // call user validators. - // Any deny returns true means denied. - if (self._validators.remove.deny.some((validator) => { - return validator(userId, transformDoc(validator, doc)); - })) { - throw new Meteor.Error(403, "Access denied"); - } - // Any allow returns true means proceed. Throw error if they all fail. - if (self._validators.remove.allow.every((validator) => { - return !validator(userId, transformDoc(validator, doc)); - })) { - throw new Meteor.Error(403, "Access denied"); - } - - // Back when we supported arbitrary client-provided selectors, we actually - // rewrote the selector to {_id: {$in: [ids that we found]}} before passing to - // Mongo to avoid races, but since selector is guaranteed to already just be - // an ID, we don't have to any more. - - return self._collection.remove.call(self._collection, selector); -}; - CollectionPrototype._callMutatorMethodAsync = function _callMutatorMethodAsync(name, args, options = {}) { // For two out of three mutator methods, the first argument is a selector @@ -711,6 +549,13 @@ function addValidator(collection, allowOrDeny, options) { Object.keys(options).forEach((key) => { if (!validKeysRegEx.test(key)) throw new Error(allowOrDeny + ": Invalid key: " + key); + + // TODO deprecated async config on future versions + const isAsyncKey = key.includes('Async'); + if (isAsyncKey) { + const syncKey = key.replace('Async', ''); + Meteor.deprecate(allowOrDeny + `: The "${key}" key is deprecated. Use "${syncKey}" instead.`); + } }); collection._restricted = true; @@ -740,7 +585,9 @@ function addValidator(collection, allowOrDeny, options) { options.transform ); } - collection._validators[name][allowOrDeny].push(options[name]); + const isAsyncName = name.includes('Async'); + const validatorSyncName = isAsyncName ? name.replace('Async', '') : name; + collection._validators[validatorSyncName][allowOrDeny].push(options[name]); } }); diff --git a/packages/allow-deny/package.js b/packages/allow-deny/package.js index 98a3d427a9..c4dc9d3e78 100644 --- a/packages/allow-deny/package.js +++ b/packages/allow-deny/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'allow-deny', - version: '2.0.0', + version: '2.1.0', // Brief, one-line summary of the package. summary: 'Implements functionality for allow/deny and client-side db operations', // URL to the Git repository containing the source code for this package. diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index 7d20443c27..82af4c65e6 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -25,6 +25,7 @@ // The ID of each document is the client architecture, and the fields of // the document are the versions described above. +import { onMessage } from "meteor/inter-process-messaging"; import { ClientVersions } from "./client_versions.js"; export const Autoupdate = __meteor_runtime_config__.autoupdate = { @@ -152,7 +153,6 @@ function enqueueVersionsRefresh() { const setupListeners = () => { // Listen for messages pertaining to the client-refresh topic. - import { onMessage } from "meteor/inter-process-messaging"; onMessage("client-refresh", enqueueVersionsRefresh); // Another way to tell the process to refresh: send SIGHUP signal diff --git a/packages/autoupdate/package.js b/packages/autoupdate/package.js index 4769c9e31b..514439ce1c 100644 --- a/packages/autoupdate/package.js +++ b/packages/autoupdate/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'Update the client when new client code is available', - version: '2.0.0', + version: '2.0.1', }); Package.onUse(function(api) { diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 11cf63f923..5606e78665 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -1,14 +1,21 @@ var semver = Npm.require("semver"); var JSON5 = Npm.require("json5"); +var SWC = Npm.require("@meteorjs/swc-core"); +const reifyCompile = Npm.require("@meteorjs/reify/lib/compiler").compile; +const reifyAcornParse = Npm.require("@meteorjs/reify/lib/parsers/acorn").parse; +var fs = Npm.require('fs'); +var path = Npm.require('path'); +var vm = Npm.require('vm'); +var crypto = Npm.require('crypto'); /** * A compiler that can be instantiated with features and used inside * Plugin.registerCompiler * @param {Object} extraFeatures The same object that getDefaultOptions takes */ -BabelCompiler = function BabelCompiler(extraFeatures, modifyBabelConfig) { +BabelCompiler = function BabelCompiler(extraFeatures, modifyConfig) { this.extraFeatures = extraFeatures; - this.modifyBabelConfig = modifyBabelConfig; + this.modifyConfig = modifyConfig; this._babelrcCache = null; this._babelrcWarnings = Object.create(null); this.cacheDirectory = null; @@ -18,18 +25,180 @@ var BCp = BabelCompiler.prototype; var excludedFileExtensionPattern = /\.(es5|min)\.js$/i; var hasOwn = Object.prototype.hasOwnProperty; +function getMeteorConfig() { + return Plugin?.getMeteorConfig() || {}; +} + +// Check if verbose mode is enabled either in the provided config or in extraFeatures +BCp.isVerbose = function(config = getMeteorConfig()) { + if (config?.modern?.transpiler?.verbose) { + return true; + } + if (config?.modern?.verbose) { + return true; + } + if (config?.verbose) { + return true; + } + return !!this.extraFeatures?.verbose; +}; + // There's no way to tell the current Meteor version, but we can infer // whether it's Meteor 1.4.4 or earlier by checking the Node version. var isMeteorPre144 = semver.lt(process.version, "4.8.1"); var enableClientTLA = process.env.METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT === 'true'; +function compileWithBabel(source, babelOptions, cacheOptions) { + return profile('Babel.compile', function () { + return Babel.compile(source, babelOptions, cacheOptions); + }); +} + +function compileWithSwc(source, swcOptions = {}, { features }) { + return profile('SWC.compile', function () { + // Perform SWC transformation. + const transformed = SWC.transformSync(source, swcOptions); + + let content = transformed.code; + + // Preserve Meteor-specific features: reify modules, nested imports, and top-level await support. + const result = reifyCompile(content, { + parse: reifyAcornParse, + generateLetDeclarations: false, + ast: false, + // Enforce reify options for proper compatibility. + avoidModernSyntax: true, + enforceStrictMode: false, + dynamicImport: true, + ...(features.topLevelAwait && { topLevelAwait: true }), + ...(features.compileForShell && { moduleAlias: 'module' }), + ...((features.modernBrowsers || features.nodeMajorVersion >= 8) && { + avoidModernSyntax: false, + generateLetDeclarations: true, + }), + }); + content = result.code; + + return { + code: content, + map: JSON.parse(transformed.map), + sourceType: 'module', + }; + }); +} + +BCp.initializeMeteorAppConfig = function () { + const meteorConfig = getMeteorConfig(); + if (this.isVerbose()) { + logConfigBlock('Meteor Config', meteorConfig); + } + return meteorConfig; +}; + +let lastModifiedSwcConfig; +let lastModifiedSwcConfigTime; +BCp.initializeMeteorAppSwcrc = function () { + const hasSwcRc = fs.existsSync(`${getMeteorAppDir()}/.swcrc`); + const hasSwcJs = !hasSwcRc && fs.existsSync(`${getMeteorAppDir()}/swc.config.js`); + if (!lastModifiedSwcConfig && !hasSwcRc && !hasSwcJs) { + return; + } + const swcFile = hasSwcJs ? 'swc.config.js' : '.swcrc'; + const filePath = `${getMeteorAppDir()}/${swcFile}`; + const fileStats = fs.statSync(filePath); + const fileModTime = fileStats?.mtime?.getTime(); + + let currentLastModifiedConfigTime; + if (hasSwcJs) { + // For dynamic JS files, first get the resolved configuration + const resolvedConfig = lastModifiedSwcConfigTime?.includes(`${fileModTime}`) + ? lastModifiedSwcConfig || getMeteorAppSwcrc(swcFile) + : getMeteorAppSwcrc(swcFile); + // Calculate a hash of the resolved configuration to detect changes + const contentHash = crypto + .createHash('sha256') + .update(JSON.stringify(resolvedConfig)) + .digest('hex'); + // Combine file modification time and content hash to create a unique identifier + currentLastModifiedConfigTime = `${fileModTime}-${contentHash}`; + // Store the resolved configuration + lastModifiedSwcConfig = resolvedConfig; + } else { + // For static JSON files, just use the file modification time + currentLastModifiedConfigTime = fileModTime; + } + + if (currentLastModifiedConfigTime !== lastModifiedSwcConfigTime) { + lastModifiedSwcConfigTime = currentLastModifiedConfigTime; + lastModifiedSwcConfig = getMeteorAppSwcrc(swcFile); + + if (this.isVerbose()) { + logConfigBlock('SWC Custom Config', lastModifiedSwcConfig); + } + + this._swcIncompatible = {}; + } + return lastModifiedSwcConfig; +}; + +let lastModifiedSwcLegacyConfig; +BCp.initializeMeteorAppLegacyConfig = function () { + const swcLegacyConfig = convertBabelTargetsForSwc(Babel.getMinimumModernBrowserVersions()); + if (this.isVerbose() && !lastModifiedSwcLegacyConfig) { + logConfigBlock('SWC Legacy Config', swcLegacyConfig); + } + lastModifiedSwcLegacyConfig = swcLegacyConfig; + return lastModifiedSwcConfig; +}; + +// Helper function to check if @swc/helpers is available +function hasSwcHelpers() { + return fs.existsSync(`${getMeteorAppDir()}/node_modules/@swc/helpers`); +} + +// Helper function to log friendly messages about SWC helpers +function logSwcHelpersStatus(isAvailable) { + const label = color('[SWC Helpers]', 36); + + if (isAvailable) { + // Green message for when helpers are available + console.log(`${label} ${color('✓ @swc/helpers is available in your project!', 32)}`); + console.log(` ${color('Benefits:', 32)}`); + console.log(` ${color('• Smaller bundle size: External helpers reduce code duplication', 32)}`); + console.log(` ${color('• Faster loads: less code to parse on first download', 32)}`); + console.log(` ${color('• Optional caching: separate vendor chunk can be cached by browsers', 32)}`); + } else { + // Yellow message for when helpers are not available + console.log(`${label} ${color('⚠ @swc/helpers is not available in your project', 33)}`); + console.log(` ${color('Suggestion:', 33)}`); + console.log(` ${color('• Add @swc/helpers to your project:', 33)}`); + console.log(` ${color('meteor npm install --save @swc/helpers', 33)}`); + console.log(` ${color('• This will reduce bundle size and improve performance', 33)}`); + } + console.log(); +} + +let hasSwcHelpersAvailable = false; +BCp.initializeMeteorAppSwcHelpersAvailable = function () { + hasSwcHelpersAvailable = hasSwcHelpers(); + if (this.isVerbose()) { + logSwcHelpersStatus(hasSwcHelpersAvailable); + } + return hasSwcHelpersAvailable; +}; + BCp.processFilesForTarget = function (inputFiles) { var compiler = this; // Reset this cache for each batch processed. this._babelrcCache = null; + this.initializeMeteorAppConfig(); + this.initializeMeteorAppSwcrc(); + this.initializeMeteorAppLegacyConfig(); + this.initializeMeteorAppSwcHelpersAvailable(); + inputFiles.forEach(function (inputFile) { if (inputFile.supportsLazyCompilation) { inputFile.addJavaScript({ @@ -51,6 +220,8 @@ BCp.processFilesForTarget = function (inputFiles) { // null to indicate there was an error, and nothing should be added. BCp.processOneFileForTarget = function (inputFile, source) { this._babelrcCache = this._babelrcCache || Object.create(null); + this._swcCache = this._swcCache || Object.create(null); + this._swcIncompatible = this._swcIncompatible || Object.create(null); if (typeof source !== "string") { // Other compiler plugins can call processOneFileForTarget with a @@ -92,6 +263,8 @@ BCp.processOneFileForTarget = function (inputFile, source) { features.nodeMajorVersion = parseInt(process.versions.node, 10); } else if (arch === "web.browser") { features.modernBrowsers = true; + } else if (arch === "web.cordova") { + features.modernBrowsers = ! getMeteorConfig()?.cordova?.disableModern; } features.topLevelAwait = inputFile.supportsTopLevelAwait && @@ -121,32 +294,202 @@ BCp.processOneFileForTarget = function (inputFile, source) { }, }; - this.inferTypeScriptConfig( - features, inputFile, cacheOptions.cacheDeps); - - var babelOptions = Babel.getDefaultOptions(features); - babelOptions.caller = { name: "meteor", arch }; - - this.inferExtraBabelOptions( - inputFile, - babelOptions, - cacheOptions.cacheDeps - ); - - babelOptions.sourceMaps = true; - babelOptions.filename = - babelOptions.sourceFileName = packageName - ? "packages/" + packageName + "/" + inputFilePath + const filename = packageName + ? `packages/${packageName}/${inputFilePath}` : inputFilePath; - if (this.modifyBabelConfig) { - this.modifyBabelConfig(babelOptions, inputFile); - } + const setupBabelOptions = () => { + this.inferTypeScriptConfig(features, inputFile, cacheOptions.cacheDeps); + var babelOptions = Babel.getDefaultOptions(features); + babelOptions.caller = { name: "meteor", arch }; + + babelOptions.sourceMaps = true; + babelOptions.filename = babelOptions.sourceFileName = filename; + + this.inferExtraBabelOptions(inputFile, babelOptions, cacheOptions.cacheDeps); + + if (this.modifyConfig) { + this.modifyConfig(babelOptions, inputFile); + } + + return babelOptions; + }; + + const setupSWCOptions = () => { + const isTypescriptSyntax = inputFilePath.endsWith('.ts') || inputFilePath.endsWith('.tsx'); + const hasTSXSupport = inputFilePath.endsWith('.tsx'); + const hasJSXSupport = inputFilePath.endsWith('.jsx'); + const isLegacyWebArch = arch.includes('legacy'); + + var swcOptions = { + jsc: { + ...(!isLegacyWebArch && { target: 'es2015' }), + parser: { + syntax: isTypescriptSyntax ? 'typescript' : 'ecmascript', + jsx: hasJSXSupport, + tsx: hasTSXSupport, + }, + ...(hasSwcHelpersAvailable && + (packageName == null || + !['modules-runtime'].includes(packageName)) && { + externalHelpers: true, + }), + }, + module: { type: 'es6' }, + minify: false, + sourceMaps: true, + filename, + sourceFileName: filename, + ...(isLegacyWebArch && { + env: { targets: lastModifiedSwcLegacyConfig || {} }, + }), + }; + + // Merge with app-level SWC config + if (lastModifiedSwcConfig) { + swcOptions = deepMerge(swcOptions, lastModifiedSwcConfig, [ + 'env.targets', + 'module.type', + ]); + } + + this.inferExtraSWCOptions(inputFile, swcOptions, cacheOptions.cacheDeps); + + if (!!this.extraFeatures?.swc && this.modifyConfig) { + this.modifyConfig(swcOptions, inputFile); + } + + // Resolve custom baseUrl to an absolute path pointing to the project root + if (swcOptions.jsc && swcOptions.jsc.baseUrl) { + swcOptions.jsc.baseUrl = path.resolve(process.cwd(), swcOptions.jsc.baseUrl); + } + + return swcOptions; + }; + + var babelOptions = { filename }; try { - var result = profile('Babel.compile', function () { - return Babel.compile(source, babelOptions, cacheOptions); - }); + var result = (() => { + const isNodeModulesCode = packageName == null && inputFilePath.includes("node_modules/"); + const isAppCode = packageName == null && !isNodeModulesCode; + const isPackageCode = packageName != null; + const isLegacyWebArch = arch.includes('legacy'); + + const transpConfig = getMeteorConfig()?.modern?.transpiler; + const hasModernTranspiler = transpConfig != null && transpConfig !== false; + const shouldSkipSwc = + !hasModernTranspiler || + (isAppCode && transpConfig?.excludeApp === true) || + (isNodeModulesCode && transpConfig?.excludeNodeModules === true) || + (isPackageCode && transpConfig?.excludePackages === true) || + (isLegacyWebArch && transpConfig?.excludeLegacy === true) || + (isAppCode && + Array.isArray(transpConfig?.excludeApp) && + isExcludedConfig(inputFilePath, transpConfig?.excludeApp || [])) || + (isNodeModulesCode && + Array.isArray(transpConfig?.excludeNodeModules) && + (isExcludedConfig(inputFilePath, transpConfig?.excludeNodeModules || []) || + isExcludedConfig( + inputFilePath.replace('node_modules/', ''), + transpConfig?.excludeNodeModules || [], + true, + ))) || + (isPackageCode && + Array.isArray(transpConfig?.excludePackages) && + (isExcludedConfig(packageName, transpConfig?.excludePackages || []) || + isExcludedConfig( + `${packageName}/${inputFilePath}`, + transpConfig?.excludePackages || [], + ))); + + const cacheKey = [ + toBeAdded.hash, + lastModifiedSwcConfigTime, + isLegacyWebArch ? 'legacy' : '', + hasSwcHelpersAvailable, + ] + .filter(Boolean) + .join('-'); + // Determine if SWC should be used based on package and file criteria. + const shouldUseSwc = + (!shouldSkipSwc || this.extraFeatures?.swc) && + !this._swcIncompatible[cacheKey]; + let compilation; + try { + let usedSwc = false; + if (shouldUseSwc) { + // Create a cache key based on the source hash and the compiler used + // Check cache + compilation = this.readFromSwcCache({ cacheKey }); + // Return cached result if found. + if (compilation) { + if (this.isVerbose()) { + logTranspilation({ + usedSwc: true, + inputFilePath, + packageName, + isNodeModulesCode, + cacheHit: true, + arch, + }); + } + return compilation; + } + + const swcOptions = setupSWCOptions(); + compilation = compileWithSwc( + source, + swcOptions, + { features }, + ); + // Save result in cache + this.writeToSwcCache({ cacheKey, compilation }); + usedSwc = true; + } else { + // Set up Babel options only when compiling with Babel + babelOptions = setupBabelOptions(); + + compilation = compileWithBabel(source, babelOptions, cacheOptions); + usedSwc = false; + } + + if (this.isVerbose()) { + logTranspilation({ + usedSwc, + inputFilePath, + packageName, + isNodeModulesCode, + cacheHit: false, + arch, + }); + } + } catch (e) { + this._swcIncompatible[cacheKey] = true; + // If SWC fails, fall back to Babel + + babelOptions = setupBabelOptions(); + compilation = compileWithBabel(source, babelOptions, cacheOptions); + if (this.isVerbose()) { + logTranspilation({ + usedSwc: false, + inputFilePath, + packageName, + isNodeModulesCode, + cacheHit: false, + arch, + errorMessage: e?.message, + ...(e?.message?.includes( + 'cannot be used outside of module code', + ) && { + tip: 'Remove nested imports or replace them with require to support SWC and improve speed.', + }), + }); + } + } + + return compilation; + })(); } catch (e) { if (e.loc) { // Error is from @babel/parser. @@ -256,6 +599,15 @@ BCp.inferExtraBabelOptions = function (inputFile, babelOptions, cacheDeps) { ); }; +BCp.inferExtraSWCOptions = function (inputFile, swcOptions, cacheDeps) { + if (! inputFile.require || + ! inputFile.findControlFile || + ! inputFile.readAndWatchFile) { + return false; + } + return this._inferFromSwcRc(inputFile, swcOptions, cacheDeps); +}; + BCp._inferFromBabelRc = function (inputFile, babelOptions, cacheDeps) { var babelrcPath = inputFile.findControlFile(".babelrc"); if (babelrcPath) { @@ -308,6 +660,65 @@ BCp._inferFromPackageJson = function (inputFile, babelOptions, cacheDeps) { } }; +BCp._inferFromSwcRc = function (inputFile, swcOptions, cacheDeps) { + var swcrcPath = inputFile.findControlFile(".swcrc"); + if (swcrcPath) { + if (! hasOwn.call(this._babelrcCache, swcrcPath)) { + try { + this._babelrcCache[swcrcPath] = { + controlFilePath: swcrcPath, + controlFileData: JSON.parse( + inputFile.readAndWatchFile(swcrcPath)), + deps: Object.create(null), + }; + } catch (e) { + if (e instanceof SyntaxError) { + e.message = ".swcrc is not a valid JSON file: " + e.message; + } + throw e; + } + } + + const cacheEntry = this._babelrcCache[swcrcPath]; + + if (this._inferHelperForSwc(inputFile, cacheEntry)) { + deepMerge(swcOptions, cacheEntry.controlFileData); + Object.assign(cacheDeps, cacheEntry.deps); + return true; + } + } +}; + +BCp._inferHelperForSwc = function (inputFile, cacheEntry) { + if (! cacheEntry.controlFileData) { + return false; + } + + if (hasOwn.call(cacheEntry, "finalInferHelperForSwcResult")) { + // We've already run _inferHelperForSwc and populated + // cacheEntry.controlFileData, so we can return early here. + return cacheEntry.finalInferHelperForSwcResult; + } + + // First, ensure that the current file path is not excluded. + if (cacheEntry.controlFileData.exclude) { + const exclude = cacheEntry.controlFileData.exclude; + const path = inputFile.getPathInPackage(); + + if (exclude instanceof Array) { + for (let i = 0; i < exclude.length; ++i) { + if (path.match(exclude[i])) { + return cacheEntry.finalInferHelperForSwcResult = false; + } + } + } else if (path.match(exclude)) { + return cacheEntry.finalInferHelperForSwcResult = false; + } + } + + return cacheEntry.finalInferHelperForSwcResult = true; +}; + BCp._inferHelper = function (inputFile, cacheEntry) { if (! cacheEntry.controlFileData) { return false; @@ -564,3 +975,245 @@ function packageNameFromTopLevelModuleId(id) { } return parts[0]; } + +const SwcCacheContext = '.swc-cache'; + +BCp.readFromSwcCache = function({ cacheKey }) { + // Check in-memory cache. + let compilation = this._swcCache[cacheKey]; + // If not found, try file system cache if enabled. + if (!compilation && this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, SwcCacheContext, `${cacheKey}.json`); + if (fs.existsSync(cacheFilePath)) { + try { + compilation = JSON.parse(fs.readFileSync(cacheFilePath, 'utf8')); + // Save back to in-memory cache. + this._swcCache[cacheKey] = compilation; + } catch (err) { + // Ignore any errors reading/parsing the cache. + } + } + } + return compilation; +}; + +BCp.writeToSwcCache = function({ cacheKey, compilation }) { + // Save to in-memory cache. + this._swcCache[cacheKey] = compilation; + // If file system caching is enabled, write asynchronously. + if (this.cacheDirectory) { + const cacheFilePath = path.join(this.cacheDirectory, SwcCacheContext, `${cacheKey}.json`); + try { + const writeFileCache = async () => { + await fs.promises.mkdir(path.dirname(cacheFilePath), { recursive: true }); + await fs.promises.writeFile(cacheFilePath, JSON.stringify(compilation), 'utf8'); + }; + // Invoke without blocking the main flow. + writeFileCache(); + } catch (err) { + // If writing fails, ignore the error. + } + } +}; + +function getMeteorAppDir() { + return process.cwd(); +} + +function getMeteorAppPackageJson() { + return JSON.parse( + fs.readFileSync(`${getMeteorAppDir()}/package.json`, 'utf-8'), + ); +} + +function getMeteorAppSwcrc(file = '.swcrc') { + try { + const filePath = `${getMeteorAppDir()}/${file}`; + if (file.endsWith('.js')) { + let content = fs.readFileSync(filePath, 'utf-8'); + // Check if the content uses ES module syntax (export default) + if (content.includes('export default')) { + // Transform ES module syntax to CommonJS + content = content.replace(/export\s+default\s+/, 'module.exports = '); + } + const script = new vm.Script(` + (function() { + const module = {}; + module.exports = {}; + (function(exports, module) { + ${content} + })(module.exports, module); + return module.exports; + })() + `); + const context = vm.createContext({ process }); + return script.runInContext(context); + } else { + // For .swcrc and other JSON files, parse as JSON + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } + } catch (e) { + console.error(`Error parsing ${file} file`, e); + } +} + +const _regexCache = new Map(); + +function isRegexLike(str) { + return /[.*+?^${}()|[\]\\]/.test(str); +} + +function isExcludedConfig(name, excludeList = [], startsWith) { + if (!name || !excludeList?.length) return false; + return excludeList.some(rule => { + if (name === rule) return true; + if (startsWith && name.startsWith(rule)) return true; + if (isRegexLike(rule)) { + let regex = _regexCache.get(rule); + if (!regex) { + try { + regex = new RegExp(rule); + _regexCache.set(rule, regex); + } catch (err) { + console.warn(`Invalid regex in exclude list: "${rule}"`); + return false; + } + } + return regex.test(name); + } + + return false; + }); +} + +const disableTextColors = Boolean(JSON.parse(process.env.METEOR_DISABLE_COLORS || "false")); + +function color(text, code) { + return disableTextColors ? text : `\x1b[${code}m${text}\x1b[0m`; +} + +function logTranspilation({ + packageName, + inputFilePath, + usedSwc, + cacheHit, + isNodeModulesCode, + arch, + errorMessage = '', + tip = '', +}) { + const transpiler = usedSwc ? 'SWC' : 'Babel'; + const transpilerColor = usedSwc ? 32 : 33; + const label = color('[Transpiler]', 36); + const transpilerPart = `${label} Used ${color( + transpiler, + transpilerColor, + )} for`; + const filePathPadded = `${ + packageName ? `${packageName}/` : '' + }${inputFilePath}`.padEnd(50); + let rawOrigin = ''; + if (packageName) { + rawOrigin = `(package)`; + } else { + rawOrigin = isNodeModulesCode ? '(node_modules)' : '(app)'; + } + const originPaddedRaw = rawOrigin.padEnd(35); + const originPaddedColored = packageName + ? originPaddedRaw + : isNodeModulesCode + ? color(originPaddedRaw, 90) + : color(originPaddedRaw, 35); + const cacheStatus = errorMessage + ? color('⚠️ Fallback', 33) + : usedSwc + ? cacheHit + ? color('🟢 Cache hit', 32) + : color('🔴 Cache miss', 31) + : ''; + const archPart = arch ? color(` (${arch})`, 90) : ''; + console.log( + `${transpilerPart} ${filePathPadded}${originPaddedColored}${cacheStatus}${archPart}`, + ); + if (errorMessage) { + console.log(); + console.log(` ↳ ${color('Error:', 31)} ${errorMessage}`); + if (tip) { + console.log(); + console.log(` ${color('💡 Tip:', 33)} ${tip}`); + } + console.log(); + } +} + +function logConfigBlock(description, configObject) { + const label = color('[Config]', 36); + const descriptionColor = color(description, 90); + + console.log(`${label} ${descriptionColor}`); + + const configLines = JSON.stringify(configObject, null, 2) + .replace(/"([^"]+)":/g, '$1:') + .split('\n') + .map(line => ' ' + line); + + configLines.forEach(line => console.log(line)); + console.log(); +} + +function deepMerge(target, source, preservePaths = [], inPath = '') { + for (const key in source) { + const fullPath = inPath ? `${inPath}.${key}` : key; + + // Skip preserved paths + if (preservePaths.includes(fullPath)) continue; + + if ( + typeof source[key] === 'object' && + source[key] !== null && + !Array.isArray(source[key]) + ) { + target[key] = deepMerge( + target[key] || {}, + source[key], + preservePaths, + fullPath, + ); + } else { + target[key] = source[key]; + } + } + return target; +} + +function convertBabelTargetsForSwc(babelTargets) { + const allowedEnvs = new Set([ + 'chrome', 'opera', 'edge', 'firefox', 'safari', + 'ie', 'ios', 'android', 'node', 'electron' + ]); + + const filteredTargets = {}; + for (const [env, version] of Object.entries(babelTargets)) { + if (allowedEnvs.has(env)) { + // Convert an array version (e.g., [10, 3]) into "10.3", otherwise convert to string. + filteredTargets[env] = Array.isArray(version) ? version.join('.') : version.toString(); + } + } + + return filteredTargets; +} + +/** + * A compiler that extends BabelCompiler but always uses SWC + * @param {Object} extraFeatures Additional features to pass to BabelCompiler + * @param {Function} modifyConfig Function to modify the configuration + */ +SwcCompiler = function SwcCompiler(extraFeatures, modifyConfig) { + extraFeatures = extraFeatures || {}; + extraFeatures.swc = true; + BabelCompiler.call(this, extraFeatures, modifyConfig); +}; + +// Inherit from BabelCompiler +SwcCompiler.prototype = Object.create(BabelCompiler.prototype); +SwcCompiler.prototype.constructor = SwcCompiler; diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index eb539fb22c..2e69cdc028 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -1,13 +1,14 @@ Package.describe({ name: "babel-compiler", summary: "Parser/transpiler for ECMAScript 2015+ syntax", - version: '7.11.0', + version: '7.12.2', }); Npm.depends({ - '@meteorjs/babel': '7.20.0-beta.4', - 'json5': '2.1.1', - 'semver': '7.3.8' + '@meteorjs/babel': '7.20.1', + 'json5': '2.2.3', + 'semver': '7.6.3', + "@meteorjs/swc-core": "1.12.14", }); Package.onUse(function (api) { @@ -22,4 +23,5 @@ Package.onUse(function (api) { api.export('Babel', 'server'); api.export('BabelCompiler', 'server'); + api.export('SwcCompiler', 'server'); }); diff --git a/packages/boilerplate-generator-tests/.npm/package/npm-shrinkwrap.json b/packages/boilerplate-generator-tests/.npm/package/npm-shrinkwrap.json deleted file mode 100644 index d749aa7131..0000000000 --- a/packages/boilerplate-generator-tests/.npm/package/npm-shrinkwrap.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "lockfileVersion": 4, - "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - } - } -} diff --git a/packages/boilerplate-generator-tests/package.js b/packages/boilerplate-generator-tests/package.js index 98a6db887d..8c95f5f034 100644 --- a/packages/boilerplate-generator-tests/package.js +++ b/packages/boilerplate-generator-tests/package.js @@ -2,7 +2,7 @@ Package.describe({ // These tests are in a separate package so that we can Npm.depend on // parse5, a html parsing library. summary: "Tests for the boilerplate-generator package", - version: '1.5.2', + version: '1.5.3', documentation: null }); @@ -13,7 +13,6 @@ Npm.depends({ Package.onTest(function (api) { api.use('ecmascript'); api.use([ - 'underscore', 'tinytest', 'boilerplate-generator' ], 'server'); diff --git a/packages/boilerplate-generator-tests/web.browser-tests.js b/packages/boilerplate-generator-tests/web.browser-tests.js index 915e2f6ebf..48c0d06fd8 100644 --- a/packages/boilerplate-generator-tests/web.browser-tests.js +++ b/packages/boilerplate-generator-tests/web.browser-tests.js @@ -1,6 +1,5 @@ import { parse, serialize } from 'parse5'; import { generateHTMLForArch } from './test-lib'; -import { _ } from 'meteor/underscore'; Tinytest.addAsync( "boilerplate-generator-tests - web.browser - basic output", @@ -66,10 +65,6 @@ Tinytest.addAsync( async function (test) { const newHtml = await generateHTMLForArch("web.browser", false); - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - test.matches(newHtml, /foo="foobar"/); test.matches(newHtml, /]*href="[^<>]*bootstrap[^<>]*">/); test.matches(newHtml, /]*src="[^<>]*templating[^<>]*">/); diff --git a/packages/boilerplate-generator-tests/web.cordova-tests.js b/packages/boilerplate-generator-tests/web.cordova-tests.js index 04eb249ac5..ffe954105e 100644 --- a/packages/boilerplate-generator-tests/web.cordova-tests.js +++ b/packages/boilerplate-generator-tests/web.cordova-tests.js @@ -1,6 +1,5 @@ import { parse, serialize } from 'parse5'; import { generateHTMLForArch } from './test-lib'; -import { _ } from 'meteor/underscore'; Tinytest.addAsync( "boilerplate-generator-tests - web.cordova - basic output", @@ -60,9 +59,7 @@ Tinytest.addAsync( async function (test) { const newHtml = await generateHTMLForArch('web.cordova', false); - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; + test.matches(newHtml, /]*href="[^<>]*bootstrap[^<>]*">/); test.matches(newHtml, /]*src="[^<>]*templating[^<>]*">/); test.matches(newHtml, /'; Tinytest.add("webapp - npm modules", function (test) { // Make sure the version number looks like a version number. - test.matches(WebAppInternals.NpmModules.express.version, /^4\.(\d+)\.(\d+)/); + test.matches(WebAppInternals.NpmModules.express.version, /^5\.(\d+)\.(\d+)/); test.equal(typeof(WebAppInternals.NpmModules.express.module), 'function'); }); @@ -365,3 +382,53 @@ Tinytest.addAsync( test.isTrue(/__meteor_runtime_config__ = (.*customKey[^"].*customValue.*)/.test(html)); } ); + +Tinytest.addAsync("webapp - parse url queries", async function (test) { + WebApp.handlers.get("/queries", async (req, res) => { + res.json(req.query); + }); + + const queriesTestCases = [ + 'planet=Mars', + 'galaxy=Andromeda&star=Betelgeuse', + 'spacecraft=Voyager%202', + 'meteor=Perseid&meteor=Leonid', + 'astronaut[name]=Neil&astronaut[mission]=Apollo%2011', + 'galaxy[name]=Milky%20Way&galaxy[diameter]=105700', + 'constellation[name]=Orion&constellation[stars][]=Betelgeuse&constellation[stars][]=Rigel', + 'galaxy[name]=Andromeda&galaxy[age]=10&meteors[]=Perseid&meteors[]=Geminid', + 'astronaut[name]=Buzz&astronaut[missions][first]=Apollo%2011&astronaut[missions][second]=Apollo%2022', + 'spacecraft[]=Voyager&spacecraft[]=Pioneer&spacecraft[0][type]=orbiter', + 'comet=Halley&status=active%20comet', + 'planet=&galaxy=' + ]; + const queryResults = [ + { planet: 'Mars' }, + { galaxy: 'Andromeda', star: 'Betelgeuse' }, + { spacecraft: 'Voyager 2' }, + { meteor: ['Perseid', 'Leonid'] }, + { astronaut: { name: 'Neil', mission: 'Apollo 11' } }, + { galaxy: { name: 'Milky Way', diameter: '105700' } }, + { constellation: { name: 'Orion', stars: ['Betelgeuse', 'Rigel'] } }, + { + galaxy: { name: 'Andromeda', age: '10' }, + meteors: ['Perseid', 'Geminid'] + }, + { + astronaut: { + name: 'Buzz', + missions: { first: 'Apollo 11', second: 'Apollo 22' } + } + }, + { spacecraft: ['Voyager', 'Pioneer', { type: 'orbiter' }] }, + { comet: 'Halley', status: 'active comet' }, + { planet: '', galaxy: '' } + ]; + let i = 0; + for await (const queriesTestCase of queriesTestCases) { + const resp = await asyncGet(`${Meteor.absoluteUrl()}/queries?${queriesTestCase}`); + const queryParsed = JSON.parse(resp.content); + test.equal(queryParsed, queryResults[i]); + i++; + } +}); diff --git a/scripts/admin/bump-all-version-numbers.js b/scripts/admin/bump-all-version-numbers.js index 915c999296..a4e45f0d12 100644 --- a/scripts/admin/bump-all-version-numbers.js +++ b/scripts/admin/bump-all-version-numbers.js @@ -1,21 +1,20 @@ // run as node scripts/admin/bump-all-version-numbers.js -var fs = require("fs"); -var _ = require("../../packages/underscore/underscore.js")._; +const fs = require("fs"); -var packageNames = _.rest(process.argv, 2); +const packageNames = process.argv.slice(2); -_.each(packageNames, function (name) { +packageNames.forEach(name => { // name = "packages/" + name + "/package.js"; - var content = fs.readFileSync(name, {encoding: "utf-8"}); + const content = fs.readFileSync(name, {encoding: "utf-8"}); - var match = content.match(/\d+\.\d+\.\d+-winr.\d+/); + const match = content.match(/\d+\.\d+\.\d+-winr.\d+/); if (match) { - var versionNumber = match[0]; - var s = versionNumber.split("."); - s[3] = (parseInt(s[3], 10) + 1); - var incremented = s.join("."); + const versionNumber = match[0]; + const s = versionNumber.split("."); + s[3] = (parseInt(s[3], 10) + 1).toString(); + const incremented = s.join("."); content = content.replace(versionNumber, incremented); console.log(match[0], match[1], incremented); diff --git a/scripts/admin/check-legacy-syntax/check-syntax.js b/scripts/admin/check-legacy-syntax/check-syntax.js index 09fd3acd3a..58b1ce1284 100644 --- a/scripts/admin/check-legacy-syntax/check-syntax.js +++ b/scripts/admin/check-legacy-syntax/check-syntax.js @@ -34,6 +34,7 @@ const packages = { // Ignored server files that has a features > 2016 ignoredFiles: [ "async_helpers.js", + "asynchronous_queue.js", ] }, "accounts-ui": {}, @@ -41,6 +42,7 @@ const packages = { autopublish: {}, "babel-compiler": { serverFiles: ["babel.js", "babel-compiler.js"], + ignoredFiles: ["babel-compiler.js"], }, "babel-runtime": {}, "browser-policy": {}, @@ -83,7 +85,7 @@ const packages = { "logic-solver": { // TODO: Fibers - Legacy // Revisit when we remove fibers, this may break for legacy - ignoredFiles: ["logic.js", "optimize.js"], + ignoredFiles: ["logic.js", "optimize.js", "minisat.js"], }, "meteor-base": {}, "mobile-experience": {}, @@ -118,7 +120,6 @@ const packages = { typescript: { serverFiles: ["plugin.js"], }, - underscore: {}, url: {}, }; diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 6ae89cb992..2432e8857d 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "3.0.3-rc.0", + "version": "3.3.1-rc.2", "recommended": false, "official": false, "description": "Meteor experimental release" diff --git a/scripts/admin/meteor-release-official.json b/scripts/admin/meteor-release-official.json index d65d9e176b..7b89d5c0a6 100644 --- a/scripts/admin/meteor-release-official.json +++ b/scripts/admin/meteor-release-official.json @@ -1,7 +1,7 @@ -{ - "track": "METEOR", - "version": "3.0.3", - "recommended": false, - "official": true, - "description": "The Official Meteor Distribution" -} +{ + "track": "METEOR", + "version": "3.3.2", + "recommended": false, + "official": true, + "description": "The Official Meteor Distribution" +} diff --git a/scripts/admin/update-semver/package-lock.json b/scripts/admin/update-semver/package-lock.json index 80901d6328..10b77a0c9f 100644 --- a/scripts/admin/update-semver/package-lock.json +++ b/scripts/admin/update-semver/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "semver": "^7.3.8" + "semver": "^7.5.2" } }, "node_modules/lru-cache": { @@ -24,9 +24,10 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -53,9 +54,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "requires": { "lru-cache": "^6.0.0" } diff --git a/scripts/admin/update-semver/package.json b/scripts/admin/update-semver/package.json index 3ba0bf697b..4f875a48cf 100644 --- a/scripts/admin/update-semver/package.json +++ b/scripts/admin/update-semver/package.json @@ -11,6 +11,6 @@ "author": "grubba27", "license": "MIT", "dependencies": { - "semver": "^7.3.8" + "semver": "^7.5.2" } } diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index ef59103451..6f06d676a6 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,10 +5,10 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=20.17.0 -MONGO_VERSION_64BIT=7.0.5 +NODE_VERSION=22.18.0 +MONGO_VERSION_64BIT=7.0.16 MONGO_VERSION_32BIT=3.2.22 -NPM_VERSION=10.8.2 +NPM_VERSION=10.9.3 if [ "$UNAME" == "Linux" ] ; then diff --git a/scripts/dev-bundle-server-package.js b/scripts/dev-bundle-server-package.js index 929cda5243..ef92f51625 100644 --- a/scripts/dev-bundle-server-package.js +++ b/scripts/dev-bundle-server-package.js @@ -11,7 +11,7 @@ var packageJson = { // Keep the versions of these packages consistent with the versions // found in dev-bundle-tool-package.js. promise: "8.3.0", - "@meteorjs/reify": "0.25.2", + "@meteorjs/reify": "0.25.4", "@babel/parser": "7.25.0", "lru-cache": "6.0.0", underscore: "1.13.7", diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index ea944379b7..b8043936a5 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -10,14 +10,14 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "10.8.2", - "node-gyp": "9.4.0", + npm: "10.9.3", + "node-gyp": "10.2.0", "@mapbox/node-pre-gyp": "1.0.11", - typescript: "5.4.5", - "@meteorjs/babel": "7.20.0-beta.5", + typescript: "5.6.3", + "@meteorjs/babel": "7.20.0", // Keep the versions of these packages consistent with the versions // found in dev-bundle-server-package.js. - "@meteorjs/reify": "0.25.2", + "@meteorjs/reify": "0.25.4", // So that Babel can emit require("@babel/runtime/helpers/...") calls. "@babel/runtime": "7.25.0", // For backwards compatibility with isopackets that still depend on @@ -59,6 +59,7 @@ var packageJson = { multipipe: "2.0.1", pathwatcher: "8.1.2", "vscode-nsfw": "2.1.8", + "@parcel/watcher": "2.5.1", // The @wry/context package version must be compatible with the // version constraint imposed by optimism/package.json. optimism: "0.16.1", @@ -67,7 +68,8 @@ var packageJson = { "anser": "2.1.1", 'xmlbuilder2': '1.8.1', "ws": "7.4.5", - "open":"8.4.2" + "open":"8.4.2", + "acorn": "8.14.1", } }; diff --git a/scripts/generate-dev-bundle.ps1 b/scripts/generate-dev-bundle.ps1 index 2223d04b06..f96509b205 100644 --- a/scripts/generate-dev-bundle.ps1 +++ b/scripts/generate-dev-bundle.ps1 @@ -185,9 +185,8 @@ Function Add-NodeAndNpm { # Let's install the npm version we really want. Write-Host "Installing npm@${NPM_VERSION}..." -ForegroundColor Magenta - & "$tempNpmCmd" install --prefix="$dirLib" --no-bin-links --save ` - --cache="$dirNpmCache" --nodedir="$dirTempNode" npm@${NPM_VERSION} | - Write-Debug + Write-Host (& "$tempNpmCmd" install --prefix="$dirLib" --no-bin-links --save ` + --cache="$dirNpmCache" --nodedir="$dirTempNode" npm@${NPM_VERSION} 2>&1) if ($LASTEXITCODE -ne 0) { throw "Couldn't install npm@${NPM_VERSION}." @@ -378,10 +377,6 @@ $npmToolArgs = @{ } Add-NpmModulesFromJsBundleFile @npmToolArgs -# Leaving these probably doesn't hurt, but are removed for consistency w/ Unix. -Remove-Item $(Join-Path $dirLib 'package.json') -Remove-Item $(Join-Path $dirLib 'package-lock.json') - Write-Host "Done writing node_modules build(s)..." -ForegroundColor Magenta Write-Host "Removing temp scratch $dirTemp" -ForegroundColor Magenta diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 8e0de641e8..792539fe66 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -70,9 +70,8 @@ case $OS in ;; esac - if [ $OS = "macos" ] && [ "$(uname -m)" = "arm64" ] ; then - MONGO_NAME="mongodb-${OS}-x86_64-${MONGO_VERSION}" + MONGO_NAME="mongodb-${OS}-arm64-${MONGO_VERSION}" elif [ $OS = "linux" ] && [ "$ARCH" = "aarch64" ] ; then MONGO_NAME="mongodb-linux-aarch64-ubuntu2204-${MONGO_VERSION}" else @@ -84,6 +83,13 @@ MONGO_URL="${MONGO_BASE_URL}/${MONGO_TGZ}" echo "Downloading Mongo from ${MONGO_URL}" curl -L "${MONGO_URL}" | tar zx +# The tarball outputs as folder name "mongodb-macos-aarch64-X.X.X" even though the URL and the tarball name suggest "mongodb-macos-arm64-X.X.X" +# So we need to rename the folder to match the expected folder name +# Watch out for newer versions of the tarball that might already be named correctly +if [ $OS = "macos" ] && [ "$(uname -m)" = "arm64" ] ; then + MONGO_NAME=$(echo "$MONGO_NAME" | sed 's/arm64/aarch64/g') +fi + # Put Mongo binaries in the right spot (mongodb/bin) mkdir -p "mongodb/bin" mv "${MONGO_NAME}/bin/mongod" "mongodb/bin" diff --git a/scripts/windows/ci/install.ps1 b/scripts/windows/ci/install.ps1 index ecb7c9194c..3624e4c11d 100644 --- a/scripts/windows/ci/install.ps1 +++ b/scripts/windows/ci/install.ps1 @@ -19,6 +19,12 @@ If ($LASTEXITCODE -ne 0) { throw "Updating submodules failed." } +# The `meteor npm install` subcommand should work +& "$meteorBat" npm install +If ($LASTEXITCODE -ne 0) { + throw "'meteor npm install' failed." +} + # The `meteor --get-ready` command is susceptible to EPERM errors, so # we attempt it three times. $attempt = 3 diff --git a/tools/PERFORMANCE.md b/tools/PERFORMANCE.md index 7a8864bd84..635f84f78e 100644 --- a/tools/PERFORMANCE.md +++ b/tools/PERFORMANCE.md @@ -167,6 +167,94 @@ The Profiler is activated using `Profile.run(.)`, which will cause a report to b In addition to tool code, you can also use `Profile` in compiler plugins. If you want to use `Profile` in packages that are loaded into the tool (e.g. packages depended on by compiler plugins, or specially loaded into the tool as isopackets), you should always test `(typeof Profile !== 'undefined')` before accessing `Profile`, or pass it in from the tool as an option. +## Inspector Profiling + +In addition to the standard profiler, Meteor includes a more advanced profiling system based on Node.js's `inspector` module. This system generates `.cpuprofile` files that provide much more detailed performance data. + +### How to Use Inspector Profiling + +To enable inspector profiling, you need to specify which functions you want to profile through the `METEOR_INSPECT` environment variable: + +```bash +# Profile multiple functions +METEOR_INSPECT=bundler.bundle,compiler.compile meteor build ./output-build +``` + +Complete list for METEOR_INSPECT: +- bundler.bundle +- compiler.compile +- Babel.compile +- _readProjectMetadata +- initializeCatalog +- _downloadMissingPackages +- _saveChangeMetadata +- _realpath +- package-client + + +### Additional Configuration Options + +You can customize the inspector profiling behavior with the following environment variables: + +```bash +# Identifier for profile files +METEOR_INSPECT_CONTEXT=context_name + +# Directory where .cpuprofile files will be saved (default: ./profiling) +METEOR_INSPECT_OUTPUT=/path/to/directory + +# Sampling interval in ms - lower values = more details but uses more memory +METEOR_INSPECT_INTERVAL=500 + +# Maximum profile size in MB (default: 2000) +METEOR_INSPECT_MAX_SIZE=1000 +``` + +### Viewing the Results + +#### Chrome DevTools +The generated `.cpuprofile` files can be visualized in Chrome DevTools: + +1. Open Chrome DevTools +2. Go to the "Performance" or "Profiler" tab +3. Click "Load Profile" and select the generated .cpuprofile file + +#### Discoveryjs cpupro + +For a opensource interactive analysis of your `.cpuprofile` files, you can use [cpupro](https://discoveryjs.github.io/cpupro/), an open-source CPU profile viewer: + +1. Visit https://discoveryjs.github.io/cpupro/ in your browser +2. Drag and drop your `.cpuprofile` file onto the interface +3. Use the interactive visualization to explore your profile data + +cpupro offers several advantages over Chrome DevTools: +- Better handling of large profiles +- More flexible filtering options +- Advanced search capabilities +- Multiple visualization modes +- Ability to compare different profiles + +You can also run cpupro locally by installing it via npm: + + +### Important Considerations + +- Inspector profiling consumes more memory than the standard profiler +- To avoid out-of-memory (OOM) errors, consider increasing Node's memory limit: + ```bash + NODE_OPTIONS="--max-old-space-size=4096" METEOR_INSPECT=bundler.bundle meteor ... + ``` +- Very large profiles (>2GB) will be automatically truncated to avoid OOM errors + +### When to Use Inspector Profiling + +Use inspector profiling when: +- You need more detailed analysis than the standard profiler provides +- You're investigating complex performance issues +- You want to identify specific bottlenecks in heavy functions like bundler or compiler + +The standard profiler (`METEOR_PROFILE`) remains the best option for quick and general analyses, while inspector profiling is more suitable for detailed diagnostics. + ## Known Areas for Improvement These are areas to improve or investigate, along with what's known, in no diff --git a/tools/README.md b/tools/README.md index c8c68fdb8c..d41096b175 100644 --- a/tools/README.md +++ b/tools/README.md @@ -105,6 +105,34 @@ Internally, every profiled function should be wrapped into a `Profile(fn)` call. The entry point should be started explicitly with the `Profile.run()` call. Otherwise, it won't start measuring anything. +### Inspector Profiling + +In addition to the standard profiler, you can use the more advanced inspector profiling: + +```bash +# Profile a specific function (e.g. bundler.bundle) +METEOR_INSPECT=bundler.bundle meteor run + +# Profile multiple functions +METEOR_INSPECT=bundler.bundle,compiler.compile meteor build +``` + +Additional options: +```bash +# Output directory for .cpuprofile files +METEOR_INSPECT_OUTPUT=/path/to/directory + +# Name to identify profile files +METEOR_INSPECT_CONTEXT=project_xyz + +# Sampling interval (lower = more details, more memory) +METEOR_INSPECT_INTERVAL=500 +``` + +The generated `.cpuprofile` files can be opened in Chrome DevTools through the "Performance" or "Profiler" tab. + +For more details, see the `PERFORMANCE.md` file. + ## Debugging Currently, to debug the tool with `node-inspector`, you can set the ` @@ -164,4 +192,4 @@ a custom try/catch/finally system with recovery. See ## More information For more information about a particular part of Meteor Tool, see subdirectories' -README.md files and the top-level intro comments in the bigger files. +README.md files and the top-level intro comments in the bigger files. \ No newline at end of file diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index e580246c48..408654581b 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -2851,7 +2851,7 @@ main.registerCommand({ } try { - var conn = packageClient.loggedInPackagesConnection(); + var conn = await packageClient.loggedInPackagesConnection(); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; @@ -2961,16 +2961,16 @@ main.registerCommand({ try { var status = options.success ? "successfully" : "unsuccessfully"; // XXX: This should probably use progress bars instead. - _.each(versions, function (version) { + for (const version of versions) { Console.rawInfo( "Setting " + name + "@" + version + " as " + status + " migrated ...\n"); - packageClient.callPackageServer( + await packageClient.callPackageServer( conn, '_changeVersionMigrationStatus', name, version, !options.success); Console.info("done."); - }); + } } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1; diff --git a/tools/cli/commands.js b/tools/cli/commands.js index cc79168e02..bb4bbea6cc 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -1,3 +1,5 @@ +import { getMeteorConfig } from "../tool-env/meteor-config"; + var main = require('./main.js'); var _ = require('underscore'); var files = require('../fs/files'); @@ -20,6 +22,7 @@ const { yellow } = require('../console/console.js').colors; const inquirer = require('inquirer'); +const semver = require("semver"); var projectContextModule = require('../project-context.js'); var release = require('../packaging/release.js'); @@ -27,7 +30,7 @@ var release = require('../packaging/release.js'); const { Profile } = require("../tool-env/profile"); const open = require('open') -const { exec } = require("child_process"); +const { exec, spawn } = require("child_process"); /** * Run a command in the shell. * @param command @@ -64,6 +67,59 @@ const bash = (text, ...values) => tryRun(() => runCommand(String.raw({ raw: text }, ...values))); +/** + * Run a command in the shell and stream output in real-time. + * @param {string} command The command to execute. + * @param {string[]} args Arguments for the command. + * @return {Promise} Resolves with the exit code. + */ +const runLiveCommand = (command, args = []) => { + return new Promise((resolve, reject) => { + const childProcess = spawn(command, args, { + shell: true, + env: { ...process.env, FORCE_COLOR: "1", TERM: "xterm-256color" }, + stdio: "inherit", + }); + + const cleanup = () => { + childProcess.removeAllListeners(); + }; + + childProcess.on("close", (code) => { + cleanup(); + resolve(code); + }); + + childProcess.on("error", (error) => { + Console.error(error.message); + cleanup(); + reject(error); + }); + }); +}; + +/** + * Executes an async function and captures success or error. + * @param {() => Promise} fn The async function to execute. + * @returns {Promise<[T, null] | [null, Error]>} Result or Error tuple. + */ +const tryRunLive = async (fn) => { + try { + return [await fn(), null]; + } catch (e) { + return [null, e]; + } +}; + +/** + * Runs a Bash command with live logging. + * @param {string} text The bash command to execute. + * @param {...string} values Additional arguments. + * @returns {Promise<[number, null] | [null, Error]>} Exit code or Error. + */ +const bashLive = (text, ...values) => + tryRunLive(() => runLiveCommand(String.raw({ raw: text }, ...values))); + import { ensureDevBundleDependencies } from '../cordova/index.js'; import { CordovaRunner } from '../cordova/runner.js'; import { iOSRunTarget, AndroidRunTarget } from '../cordova/run-targets.js'; @@ -207,13 +263,49 @@ export function parseRunTargets(targets) { }); }; -const excludableWebArchs = ['web.browser', 'web.browser.legacy', 'web.cordova']; -function filterWebArchs(webArchs, excludeArchsOption) { - if (excludeArchsOption) { - const excludeArchs = excludeArchsOption.trim().split(/\s*,\s*/) - .filter(arch => excludableWebArchs.includes(arch)); - webArchs = webArchs.filter(arch => !excludeArchs.includes(arch)); +function filterWebArchs(webArchs, excludeArchsOption, appDir, options) { + const platforms = (options.platforms || []); + const isBuildMode = platforms?.length > 0; + if (isBuildMode) { + // Build Mode + const isModernOnlyPlatform = platforms.includes('modern') && !platforms.includes('legacy'); + if (isModernOnlyPlatform) { + webArchs = webArchs.filter(arch => arch !== 'web.browser.legacy'); + } + const hasCordovaPlatforms = platforms.includes('android') || platforms.includes('ios'); + if (!hasCordovaPlatforms) { + webArchs = webArchs.filter(arch => arch !== 'web.cordova'); + } + } else { + // Dev & Test Mode + const isCordovaDev = (options.args || []).some(arg => ['ios', 'ios-device', 'android', 'android-device'].includes(arg)); + if (!isCordovaDev) { + const excludeArchsOptions = excludeArchsOption ? excludeArchsOption.trim().split(/\s*,\s*/) : []; + const hasExcludeArchsOptions = (excludeArchsOptions?.length || 0) > 0; + const hasModernArchsOnlyEnabled = appDir && getMeteorConfig()?.modern?.webArchOnly !== false; + if (hasExcludeArchsOptions && hasModernArchsOnlyEnabled) { + console.warn('modern.webArchOnly and --exclude-archs are both active. If both are set, --exclude-archs takes priority.'); + } + const automaticallyIgnoredLegacyArchs = (!hasExcludeArchsOptions && hasModernArchsOnlyEnabled) ? ['web.browser.legacy', 'web.cordova'] : []; + if (hasExcludeArchsOptions || automaticallyIgnoredLegacyArchs.length) { + const excludeArchs = [...excludeArchsOptions, ...automaticallyIgnoredLegacyArchs]; + webArchs = webArchs.filter(arch => !excludeArchs.includes(arch)); + } + } } + + const forcedInclArchs = process.env.METEOR_FORCE_INCLUDE_ARCHS; + if (forcedInclArchs != null) { + const nextInclArchs = forcedInclArchs.trim().split(/\s*,\s*/); + webArchs = Array.from(new Set([...webArchs, ...nextInclArchs])); + } + + const forcedExclArchs = process.env.METEOR_FORCE_EXCLUDE_ARCHS; + if (forcedExclArchs != null) { + const nextExclArchs = forcedExclArchs.trim().split(/\s*,\s*/); + webArchs = webArchs.filter(_webArch => !nextExclArchs.includes(_webArch)); + } + return webArchs; } @@ -359,7 +451,8 @@ var runCommandOptions = { ...inspectOptions, 'no-release-check': { type: Boolean }, production: { type: Boolean }, - 'raw-logs': { type: Boolean }, + 'raw-logs': { type: Boolean, default: true }, + timestamps: { type: Boolean, default: false }, // opposite of --raw-logs settings: { type: String, short: "s" }, verbose: { type: Boolean, short: "v" }, // With --once, meteor does not re-run the project if it crashes @@ -443,10 +536,7 @@ async function doRunCommand(options) { ); } - if (options['raw-logs']) { - runLog.setRawLogs(true); - } - + runLog.setRawLogs(options['raw-logs'] && !options.timestamps); let webArchs = projectContext.platformList.getWebArchs(); if (! _.isEmpty(runTargets) || @@ -455,11 +545,16 @@ async function doRunCommand(options) { webArchs.push("web.cordova"); } } - webArchs = filterWebArchs(webArchs, options['exclude-archs']); + + webArchs = filterWebArchs(webArchs, options['exclude-archs'], options.appDir, options); + // Set the webArchs to include for compilation later + global.includedWebArchs = webArchs; + const buildMode = options.production ? 'production' : 'development'; let cordovaRunner; - if (!_.isEmpty(runTargets)) { + const shouldDisableCordova = Boolean(JSON.parse(process.env.METEOR_CORDOVA_DISABLE || 'false')); + if (!shouldDisableCordova && !_.isEmpty(runTargets)) { async function prepareCordovaProject() { import { CordovaProject } from '../cordova/project.js'; @@ -516,7 +611,7 @@ async function doRunCommand(options) { open(`http://localhost:${options.port}`) } } - }, + }, open: options.open, }); } @@ -1030,7 +1125,7 @@ main.registerCommand({ "If you are new to Meteor, try some of the learning resources here:" ); Console.info( - Console.url("https://www.meteor.com/tutorials"), + Console.url("https://docs.meteor.com/"), Console.options({ indent: 2 }) ); @@ -1039,7 +1134,7 @@ main.registerCommand({ "When you’re ready to deploy and host your new Meteor application, check out Cloud:" ); Console.info( - Console.url("https://www.meteor.com/cloud"), + Console.url("https://galaxycloud.app/"), Console.options({ indent: 2 }) ); @@ -1058,7 +1153,7 @@ main.registerCommand({ process.env.GIT_TERMINAL_PROMPT = 0; const gitCommand = isWindows - ? `git clone --progress ${url} ${files.convertToOSPath(appPath)}` + ? `git clone --progress ${url} "${files.convertToOSPath(appPath)}"` : `git clone --progress ${url} ${appPath}`; const [okClone, errClone] = await bash`${gitCommand}`; const errorMessage = errClone && typeof errClone === "string" ? errClone : errClone?.message; @@ -1106,71 +1201,85 @@ main.registerCommand({ toIgnore.push(/(\.html|\.js|\.css)/); } - try { - // Prototype option should use local skeleton. - // Maybe we should use a different skeleton for prototype - if (options.prototype) throw new Error("Using prototype option"); - // if using the release option we should use the default skeleton - // using it as it was before 2.x - if (release.explicit) throw new Error("Using release option"); + const copyFromLocalSkeleton = async () => { + await files.cp_r( + skeletonPath, + appPath, + { + transformFilename: function (f) { + return transform(f); + }, + transformContents: function (contents, f) { + // check if this app is just for prototyping if it is then we need to add autopublish and insecure in the packages file + if (/packages/.test(f)) { + const prototypePackages = () => + "autopublish # Publish all data to the clients (for prototyping)\n" + + "insecure # Allow all DB writes from clients (for prototyping)"; - await setupExampleByURL(`https://github.com/meteor/skel-${skeleton}`); - } catch (e) { + // XXX: if there is the need to add more options maybe we should have a better abstraction for this if-else + if (options.prototype) { + return Buffer.from( + contents.toString().replace(/~prototype~/g, prototypePackages()) + ); + } else { + return Buffer.from(contents.toString().replace(/~prototype~/g, "")); + } + } + if (/(\.html|\.[jt]sx?|\.css)/.test(f)) { + return Buffer.from(transform(contents.toString())); + } else { + return contents; + } + }, + ignore: toIgnore, + preserveSymlinks: true, + } + ); + }; - if ( - e.message !== "Using prototype option" && - e.message !== "Using release option" - ) { - // something has happened while creating the app using git clone - Console.error( - `Something has happened while creating your app using git clone. + // Check if the local skeleton path exists + const skeletonPath = files.pathJoin( + __dirnameConverted, + "..", + "static-assets", + `skel-${skeleton}` + ); + + const useLocalSkeleton = files.exists(skeletonPath) || + options.prototype || + release.explicit; + if (useLocalSkeleton) { + // Use local skeleton + await copyFromLocalSkeleton(); + } else { + try { + // Prototype option should use local skeleton. + // Maybe we should use a different skeleton for prototype + if (options.prototype) throw new Error("Using prototype option"); + // if using the release option we should use the default skeleton + // using it as it was before 2.x + if (release.explicit) throw new Error("Using release option"); + + // If local skeleton doesn't exist, use setupExampleByURL + await setupExampleByURL(`https://github.com/meteor/skel-${skeleton}`); + } catch (e) { + if ( + e.message !== "Using prototype option" && + e.message !== "Using release option" + ) { + // something has happened while creating the app using git clone + Console.error( + `Something has happened while creating your app using git clone. Will use cached version of skeletons. Error message: `, - e.message - ); + e.message + ); + } + // For prototype or release options, use local skeleton + await copyFromLocalSkeleton(); } - - // TODO: decide if this should stay here or not. - await files.cp_r( - files.pathJoin( - __dirnameConverted, - "..", - "static-assets", - `skel-${skeleton}` - ), - appPath, - { - transformFilename: function (f) { - return transform(f); - }, - transformContents: function (contents, f) { - // check if this app is just for prototyping if it is then we need to add autopublish and insecure in the packages file - if (/packages/.test(f)) { - const prototypePackages = () => - "autopublish # Publish all data to the clients (for prototyping)\n" + - "insecure # Allow all DB writes from clients (for prototyping)"; - - // XXX: if there is the need to add more options maybe we should have a better abstraction for this if-else - if (options.prototype) { - return Buffer.from( - contents.toString().replace(/~prototype~/g, prototypePackages()) - ); - } else { - return Buffer.from(contents.toString().replace(/~prototype~/g, "")); - } - } - if (/(\.html|\.[jt]sx?|\.css)/.test(f)) { - return Buffer.from(transform(contents.toString())); - } else { - return contents; - } - }, - ignore: toIgnore, - preserveSymlinks: true, - } - ); - await setupMessages(); } + await setupMessages(); Console.info(""); }); @@ -1290,7 +1399,7 @@ var buildCommand = async function (options) { let selectedPlatforms = null; if (options.platforms) { const platformsArray = options.platforms.split(","); - + const excludableWebArchs = ['web.browser', 'web.browser.legacy', 'web.cordova']; platformsArray.forEach(plat => { if (![...excludableWebArchs, 'android', 'ios'].includes(plat)) { throw new Error(`Not allowed platform on '--platforms' flag: ${plat}`) @@ -1337,9 +1446,9 @@ on an OS X system."); // For example, if we want to build only android, there is no need to build // web.browser. let webArchs; + const baseWebArchs = projectContext.platformList.getWebArchs(); if (selectedPlatforms) { - const filteredArchs = projectContext.platformList - .getWebArchs() + const filteredArchs = baseWebArchs .filter(arch => selectedPlatforms.includes(arch)); if ( @@ -1350,6 +1459,11 @@ on an OS X system."); } webArchs = filteredArchs.length ? filteredArchs : undefined; + } else { + webArchs = filterWebArchs(baseWebArchs, options['exclude-archs'], options.appDir, { + ...options, + platforms: projectContext.platformList.getPlatforms(), + }); } var buildDir = projectContext.getProjectLocalDirectory('build_tar'); @@ -1374,7 +1488,7 @@ ${Console.command("meteor build ../output")}`, files.pathJoin(outputPath, 'bundle')) : files.pathJoin(buildDir, 'bundle'); - await stats.recordPackages({ + stats.recordPackages({ what: "sdk.bundle", projectContext: projectContext }); @@ -1507,7 +1621,7 @@ https://guide.meteor.com/cordova.html#submitting-android }); } - await files.rm_recursive(buildDir); + await files.rm_recursive_deferred(buildDir); const npmShrinkwrapFilePath = files.pathJoin(bundlePath, 'programs/server/npm-shrinkwrap.json'); if (files.exists(npmShrinkwrapFilePath)) { @@ -1713,6 +1827,7 @@ main.registerCommand({ maxArgs: 1, options: { db: { type: Boolean }, + 'skip-cache': { type: Boolean }, }, requiresApp: true, catalogRefresh: new catalog.Refresh.Never() @@ -1739,6 +1854,10 @@ main.registerCommand({ "MONGO_URL will NOT be reset."); } + const resetMeteorNmCachePromise = options['skip-cache'] ? Promise.resolve() : files.rm_recursive_async( + files.pathJoin(options.appDir, "node_modules", ".cache", "meteor") + ); + if (options.db) { // XXX detect the case where Meteor is running the app, but // MONGO_URL was set, so we don't see a Mongo process @@ -1753,9 +1872,13 @@ main.registerCommand({ return 1; } - await files.rm_recursive_async( - files.pathJoin(options.appDir, '.meteor', 'local') - ); + await Promise.all([ + files.rm_recursive_async( + files.pathJoin(options.appDir, ".meteor", "local") + ), + resetMeteorNmCachePromise, + ]); + Console.info("Project reset."); return; } @@ -1767,9 +1890,12 @@ main.registerCommand({ return !path.includes('.meteor/local/db'); }); - var allRemovePromises = allExceptDb.map(_path => files.rm_recursive_async( - files.pathJoin(options.appDir, _path) - )); + var allRemovePromises = [ + ...allExceptDb.map((_path) => + files.rm_recursive_async(files.pathJoin(options.appDir, _path)) + ), + resetMeteorNmCachePromise + ]; await Promise.all(allRemovePromises); Console.info("Project reset."); }); @@ -1997,7 +2123,8 @@ testCommandOptions = { // like progress bars and spinners are unimportant. headless: { type: Boolean }, verbose: { type: Boolean, short: "v" }, - 'raw-logs': { type: Boolean }, + 'raw-logs': { type: Boolean, default: true }, + timestamps: { type: Boolean, default: false }, // opposite of --raw-logs // Undocumented. See #Once once: { type: Boolean }, @@ -2047,7 +2174,10 @@ testCommandOptions = { 'extra-packages': { type: String }, - 'exclude-archs': { type: String } + 'exclude-archs': { type: String }, + + // Same as TINYTEST_FILTER + filter: { type: String, short: 'f' }, } }; @@ -2068,6 +2198,9 @@ main.registerCommand(Object.assign( }); async function doTestCommand(options) { + if (options.filter) { + process.env.TINYTEST_FILTER = options.filter; + } // This "metadata" is accessed in a few places. Using a global // variable here was more expedient than navigating the many layers // of abstraction across the build process. @@ -2126,9 +2259,8 @@ async function doTestCommand(options) { serverArchitectures.push(DEPLOY_ARCH); } - if (options['raw-logs']) { - runLog.setRawLogs(true); - } + runLog.setRawLogs(options['raw-logs'] && !options.timestamps); + var includePackages = []; if (options['extra-packages']) { @@ -2399,7 +2531,9 @@ var runTestAppForPackages = async function (projectContext, options) { if (options.cordovaRunner) { webArchs.push("web.cordova"); } - buildOptions.webArchs = filterWebArchs(webArchs, options['exclude-archs']); + buildOptions.webArchs = filterWebArchs(webArchs, options['exclude-archs'], projectContext.appDirectory, options); + // Set the webArchs to include for compilation later + global.includedWebArchs = buildOptions.webArchs; if (options.deploy) { // Run the constraint solver and build local packages. @@ -3266,3 +3400,129 @@ main.registerCommand({ }, function () { throw new Error("testing stack traces!"); // #StackTraceTest this line is found in tests/source-maps.js }); + +const setupBenchmarkSuite = async (profilingPath) => { + if (await files.exists(profilingPath)) { + return; + } + + // Check git availability and version + const [okGitVersion, errGitVersion] = await bash`git --version`; + if (errGitVersion) throw new Error("git is not installed"); + + const parsedGitVersion = semver.coerce(okGitVersion.match(/\d+\.\d+\.\d+/)?.[0] || '')?.version; + if (!parsedGitVersion || semver.lt(parsedGitVersion, '2.25.0')) { + throw new Error("git version is too old. Please upgrade to at least 2.25"); + } + + // Check tar availability + const [okTar, errTar] = await bash`tar --version`; + const hasTar = !errTar; + + // Disable interactive git prompts + process.env.GIT_TERMINAL_PROMPT = 0; + + const repoUrl = "https://github.com/meteor/performance"; + const branch = "v3.3.0"; + + let tarFailed = false; + + // If tar is available, prefer tar-based extraction + if (hasTar) { + const tempDir = "/tmp/meteor-performance-benchmark-suite"; + const tarCommand = [ + `rm -rf ${tempDir}`, + `git clone --no-checkout --depth 1 --filter=tree:0 --sparse --progress --branch ${branch} --single-branch ${repoUrl} ${tempDir}`, + `cd ${tempDir}`, + `git sparse-checkout init --cone`, + `git sparse-checkout set scripts`, + `git checkout ${branch}`, + `mkdir -p ${profilingPath}/scripts`, + `tar -czf /tmp/scripts.tar.gz -C ./scripts .`, + `tar -xzf /tmp/scripts.tar.gz -C ${profilingPath}/scripts`, + `rm -rf ${tempDir}`, + `rm -f /tmp/scripts.tar.gz` + ].join(" && "); + + const [okTarClone, errTarClone] = await bash`${tarCommand}`; + if (!errTarClone) { + Console.info("Meteor profiling suite cloned to: " + Console.path(profilingPath)); + return; + } else { + Console.warn("Tar-based cloning failed. Will attempt standard git clone..."); + tarFailed = errTarClone; + } + } else { + Console.warn("Tar not available. Will use standard git clone..."); + } + + // Fallback to plain git clone + const gitCommand = [ + `mkdir -p ${profilingPath}`, + `git clone --no-checkout --depth 1 --filter=tree:0 --sparse --progress --branch ${branch} --single-branch ${repoUrl} ${profilingPath}`, + `cd ${profilingPath}`, + `git sparse-checkout init --cone`, + `git sparse-checkout set scripts`, + `git checkout ${branch}`, + `find ${profilingPath} -maxdepth 1 -type f -delete` + ].join(" && "); + + const [okClone, errClone] = await bash`${gitCommand}`; + if (errClone) { + let combinedMessage = "Git clone failed."; + if (tarFailed) { + combinedMessage = `Tar-based cloning also failed:\n${tarFailed}\n\nGit fallback failed:\n${errClone}`; + } + throw new Error(combinedMessage); + } + + // Remove .git folder if present + await files.rm_recursive_async(files.pathJoin(profilingPath, ".git")); + + Console.info("Meteor profiling suite cloned to: " + Console.path(profilingPath)); +}; + +async function doBenchmarkCommand(options) { + const isWindows = process.platform === "win32"; + if (isWindows) { + throw new Error('Profiling is not supported on Windows'); + } + + const args = process.argv.slice(3); + var projectContext = new projectContextModule.ProjectContext({ + projectDir: options.appDir, + allowIncompatibleUpdate: options['allow-incompatible-update'], + lintAppAndLocalPackages: !options['no-lint'], + }); + const profilingPath = `${projectContext.projectDir}/node_modules/.cache/meteor/performance`; + await setupBenchmarkSuite(profilingPath); + + const meteorSizeEnvs = [ + !!options['size-only'] && 'METEOR_BUNDLE_SIZE_ONLY=true', + !!options['size'] && 'METEOR_BUNDLE_SIZE=true', + !!options['build'] && 'METEOR_BUNDLE_BUILD=true', + ].filter(Boolean); + const meteorOptions = args.filter(arg => !['--size-only', '--size', '--build'].includes(arg)); + + const profilingCommand = [ + `${meteorSizeEnvs.join(' ')} ${profilingPath}/scripts/monitor-bundler.sh ${projectContext.projectDir} ${new Date().getTime()} ${meteorOptions.join(' ')}`.trim(), + ].join(" && "); + const [, errBenchmark] = await bashLive`${profilingCommand}`; + if (errBenchmark) { + throw new Error(errBenchmark); + } +} + +main.registerCommand( +{ + name: 'profile', + maxArgs: Infinity, + options: { + ...buildCommands.options || {}, + ...runCommandOptions.options || {}, + 'size': { type: Boolean }, + 'size-only': { type: Boolean }, + 'build': { type: Boolean }, + }, + catalogRefresh: new catalog.Refresh.Never(), +}, doBenchmarkCommand); diff --git a/tools/cli/dev-bundle-bin-commands.js b/tools/cli/dev-bundle-bin-commands.js index a3b980533b..d7f0722ce8 100644 --- a/tools/cli/dev-bundle-bin-commands.js +++ b/tools/cli/dev-bundle-bin-commands.js @@ -31,9 +31,10 @@ async function getChildProcess({ isFirstTry }) { return null; } - const child = require("child_process").spawn(cmd, args, { - stdio: "inherit", - env: env + const child = require('child_process').spawn(cmd, args, { + stdio: 'inherit', + env: env, + shell: process.platform === 'win32' && ['.cmd', '.bat'].some(_extension => cmd.endsWith(_extension)), }); require("./flush-buffers-on-exit-in-windows"); child.on("error", function (error) { diff --git a/tools/cli/help.txt b/tools/cli/help.txt index 20d5dc5f84..96e611feca 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -90,7 +90,8 @@ Options: Meteor app source code as by default the port is generated using the id inside .meteor/.id file. --production Simulate production mode. Minify and bundle CSS and JS files. - --raw-logs Run without parsing logs from stdout and stderr. + --raw-logs Run without parsing logs from stdout and stderr (default: true). + --timestamps Run with timestamps in logs, the same as passing `--raw-logs=false`. --settings, -s Set optional data for Meteor.settings on the server. --release Specify the release of Meteor to use. --verbose Print all output from builds logs. @@ -492,7 +493,7 @@ Note that you must have mongosh installed to use this option. Options: --url, -U return a Mongo database URL --verbose, -v to show the errors that have occurred while connecting to the - database + database Currently, this feature can only be used when developing locally. The opened Mongo shell connects to the current project's local @@ -747,7 +748,9 @@ Options: important when multiple Cordova apps are build from the same Meteor app source code as by default the port is generated using the id inside .meteor/.id file. - --raw-logs Run without parsing logs from stdout and stderr. + --raw-logs Run without parsing logs from stdout and stderr (default: true). + --timestamps Run with timestamps in logs, the same as passing `--raw-logs=false`. + --settings, -s Set optional data for Meteor.settings on the server --ios, Run tests in an emulator or on a mobile device. All of @@ -816,7 +819,16 @@ Usage: meteor admin [args] Rarely used commands for administering official Meteor services. Commands: -{{commands}} + + make-bootstrap-tarballs + recommend-release + change-homepage + set-unmigrated + set-banners + list-organizations + members + set-latest-readme + get-machine See 'meteor help admin ' for details on an admin command. @@ -875,12 +887,12 @@ for replacing the names, we offer $$PascalName$$, $$camelName$$, $$name$$. This is a MeteorJS project command. Options: - --help Show help. - --path The path to the folder where the files will be generated. Default is the current folder. - --templatePath Path to the template file check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. - --replaceFn Replace function to replace the names in the template. Check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. - --methods Generate methods. - --publications Generate publications. + --help Show help. + --path The path to the folder where the files will be generated. Default is the current folder. + --templatePath Path to the template file check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. + --replaceFn Replace function to replace the names in the template. Check https://docs.meteor.com/commandline.html#meteorgenerate-templating for more info. + --methods Generate methods. + --publications Generate publications. >>> publish-release @@ -1151,3 +1163,30 @@ To see the requirements for this compilation step, consult the platform requirements for 'node-gyp': https://github.com/nodejs/node-gyp + +>>> profile +Run performance profiling for the Meteor application. +Usage: meteor profile [...] + +This command runs a performance profile for the Meteor application, monitoring the +bundler process and tracking key performance metrics to help analyze the build and +bundling performance. + +Use METEOR_IDLE_TIMEOUT= to set a timeout for profiling. The default time (90s) +is usually enough for each build step to complete. If you encounter errors due to +early exits, adjust the environment variable accordingly. + +Use METEOR_CLIENT_ENTRYPOINT= to set a custom client entrypoint, and +METEOR_SERVER_ENTRYPOINT= to set a custom server entrypoint. By default, +it uses the server and client entrypoints specified in your package.json. + +Use METEOR_LOG_DIR= to set a custom log directory. + +Options: + --size monitor both bundle runtime and size + --size-only monitor only the bundle size + --build monitor build time + +The rest of options for this command are the same as those for the meteor run command. +You can pass typical runtime options (such as --settings, --exclude-archs, etc.) +to customize the profiling process. diff --git a/tools/cli/main.js b/tools/cli/main.js index 998e2bfc63..b73e2b7868 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -287,12 +287,16 @@ main.captureAndExit = async function (header, title, f) { // NB: files required up to this point may not define commands +const { initMeteorConfig } = require('../tool-env/meteor-config'); require('./commands.js'); require('./commands-packages.js'); require('./commands-packages-query.js'); require('./commands-cordova.js'); require('./commands-aliases.js'); +// Initialize meteorConfig globally +initMeteorConfig(); + /////////////////////////////////////////////////////////////////////////////// // Record all the top-level commands as JSON /////////////////////////////////////////////////////////////////////////////// @@ -865,6 +869,9 @@ makeGlobalAsyncLocalStorage().run({}, async function () { var appDir = files.findAppDir(); if (appDir) { appDir = files.pathResolve(appDir); + + // Renitialize meteorConfig globally when having appDir context + initMeteorConfig(appDir); } await require('../tool-env/isopackets.js').ensureIsopacketsLoadable(); @@ -1543,6 +1550,9 @@ makeGlobalAsyncLocalStorage().run({}, async function () { }); } + // Set the currentCommand in the global object to spread context + global.currentCommand = { name: command.name, options }; + var ret = await command.func(options, { rawOptions }); } catch (e) { diff --git a/tools/cordova/index.js b/tools/cordova/index.js index 1230faba92..e1acabacb6 100644 --- a/tools/cordova/index.js +++ b/tools/cordova/index.js @@ -13,11 +13,11 @@ export const CORDOVA_ARCH = "web.cordova"; export const CORDOVA_PLATFORMS = ['ios', 'android']; -const CORDOVA_ANDROID_VERSION = "13.0.0"; +const CORDOVA_ANDROID_VERSION = "14.0.1"; export const CORDOVA_DEV_BUNDLE_VERSIONS = { - 'cordova-lib': '12.0.1', - 'cordova-common': '5.0.0', + 'cordova-lib': '12.0.2', + 'cordova-common': '5.0.1', 'cordova-create': '2.0.0', 'cordova-registry-mapper': '1.1.15', 'cordova-android': CORDOVA_ANDROID_VERSION, diff --git a/tools/cordova/project.js b/tools/cordova/project.js index 001871cf70..27a6891bfd 100644 --- a/tools/cordova/project.js +++ b/tools/cordova/project.js @@ -11,6 +11,7 @@ import { Profile } from '../tool-env/profile'; import buildmessage from '../utils/buildmessage.js'; import main from '../cli/main.js'; import { execFileAsync } from '../utils/processes'; +var meteorNpm = require('../isobuild/meteor-npm'); import { cordova as cordova_lib, events as cordova_events, CordovaError } from 'cordova-lib'; @@ -22,6 +23,8 @@ import { CORDOVA_PLATFORMS, CORDOVA_PLATFORM_VERSIONS, displayNameForPlatform, d newPluginId, convertPluginVersions, convertToGitUrl } from './index.js'; import { CordovaBuilder } from './builder.js'; +const cordovaPackagesFile = 'cordova-packages.json'; + cordova_events.on('verbose', logIfVerbose); cordova_events.on('log', logIfVerbose); cordova_events.on('info', logIfVerbose); @@ -429,13 +432,15 @@ to build apps for ${displayNameForPlatform(platform)}.`); } async installedVersionForPlatform(platform) { - const command = files.convertToOSPath(files.pathJoin( + const file = files.convertToOSPath(files.pathJoin( this.projectRoot, 'platforms', platform, 'cordova', 'version')); + const command = process.platform === "win32" ? process.execPath : file; + const args = process.platform === "win32" ? [file] : []; // Make sure the command exists before trying to execute it if (files.exists(command)) { return this.runCommands( `getting installed version for platform ${platform} in Cordova project`, - execFileAsync(command, { + execFileAsync(command, args, { env: this.defaultEnvWithPathsAdded(), cwd: this.projectRoot}), null, null); } else { @@ -458,30 +463,41 @@ to Cordova project`, async () => { let platformSpec = version ? `${platform}@${version}` : platform; await cordova_lib.platform('add', platformSpec, this.defaultOptions); + const installedPlugins = this.listInstalledPluginVersions(); + // As per Npm 8, we need now do inject a package.json file // with the dependencies so that when running any npm command // it keeps the dependencies installed. - const packageLock = JSON.parse(files.readFile( - files.pathJoin(self.projectRoot, 'node_modules/.package-lock.json') - )); + const packageLock = files.exists('node_modules/.package-lock.json') ? JSON.parse(files.readFile( + files.pathJoin(self.projectRoot, 'node_modules/.package-lock.json') + )) : { packages: { [`cordova-${platform}`]: { version } } }; + // Accumulated dependencies from plugins + const cordovaPackagesPath = files.pathJoin(self.projectRoot, cordovaPackagesFile); + const cordovaPackageLock = files.exists(cordovaPackagesPath) ? JSON.parse(files.readFile(cordovaPackagesPath)) : {}; + + // Ensure all packages are kept installed + const packages = { ...(cordovaPackageLock?.packages || {}), ...(packageLock?.packages || {}) }; const getPackageName = (pkgPath) => { const split = pkgPath.split("node_modules/"); return split[split.length - 1]; }; - const packageJsonObj = Object.entries(packageLock.packages).reduce((acc, [key, value]) => { + const packageJsonObj = Object.entries(packages).reduce((acc, [key, value]) => { const name = getPackageName(key); + const originalPluginVersion = installedPlugins[name]; return ({ dependencies: { ...acc.dependencies, - [name]: value.version, + [name]: originalPluginVersion || value.version, } }); - }, { dependencies: {} }); + }, { dependencies: { [`cordova-${platform}`]: version } }); files.writeFile( files.pathJoin(self.projectRoot, "package.json"), JSON.stringify(packageJsonObj, null, 2) + "\n" ); + + await meteorNpm.runNpmCommand(["install"], self.projectRoot); }); } @@ -625,6 +641,26 @@ from Cordova project`, async () => { await this.runCommands(`adding plugin ${target} \ to Cordova project`, cordova_lib.plugin.bind(undefined, 'add', [target], commandOptions)); + + // Accumulate dependencies from plugins to later installation + const cordovaPackagesPath = files.pathJoin(this.projectRoot, cordovaPackagesFile); + const packageLock = JSON.parse(files.readFile( + files.pathJoin(this.projectRoot, 'node_modules/.package-lock.json') + )); + const existingPackageLock = files.exists(cordovaPackagesPath) ? + JSON.parse(files.readFile(cordovaPackagesPath)) + : {}; + const accumulatedCordovaPackages = { + packages: { + ...existingPackageLock.packages, + ...packageLock.packages, + } + }; + files.writeFile( + cordovaPackagesPath, + JSON.stringify(accumulatedCordovaPackages, null, 2) + "\n" + ); + } catch (error) { if (retry && utils.isUrlWithSha(version)) { Console.warn(`Cordova plugin add for ${id} failed with plugin id diff --git a/tools/fs/files.ts b/tools/fs/files.ts index cc9e0c8ee7..1babe78416 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -358,6 +358,20 @@ export const rm_recursive = Profile("files.rm_recursive", async (path: string) = } }); +export const rm_recursive_deferred = Profile("files.rm_recursive_deferred", async (path: string) => { + // Generate a temp path name for the old build directory + const oldBuildPath = path + '.old-' + Math.floor(Math.random() * 999999); + // If the original buildPath exists, rename it first + if (exists(path)) { + await rename(path, oldBuildPath); + // Start deletion of old directory asynchronously without awaiting + rm_recursive(oldBuildPath).catch(e => { + // Log error but don't fail the build + console.error(`Error removing old build directory ${oldBuildPath}:`, e); + }); + } +}); + // Returns the base64 SHA256 of the given file. export function fileHash(filename: string) { const crypto = require('crypto'); diff --git a/tools/fs/safe-watcher-legacy.ts b/tools/fs/safe-watcher-legacy.ts new file mode 100644 index 0000000000..f9f108afc8 --- /dev/null +++ b/tools/fs/safe-watcher-legacy.ts @@ -0,0 +1,476 @@ +import { FSWatcher, Stats, BigIntStats } from "fs"; +import { Profile } from "../tool-env/profile"; +import { + statOrNull, + convertToOSPath, + watchFile, + unwatchFile, + toPosixPath, + pathRelative +} from "./files"; +import { + join as nativeJoin +} from 'path'; +import nsfw from 'vscode-nsfw'; + +const pathwatcher = require('pathwatcher'); + +// Default to prioritizing changed files, but disable that behavior (and +// thus prioritize all files equally) if METEOR_WATCH_PRIORITIZE_CHANGED +// is explicitly set to a string that parses to a falsy value. +var PRIORITIZE_CHANGED = true; +if (process.env.METEOR_WATCH_PRIORITIZE_CHANGED && + ! JSON.parse(process.env.METEOR_WATCH_PRIORITIZE_CHANGED)) { + PRIORITIZE_CHANGED = false; +} + +var DEFAULT_POLLING_INTERVAL = + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000); + +var NO_WATCHER_POLLING_INTERVAL = + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 500); + +// This may seems like a long time to wait before actually closing the +// file watchers, but it's to our advantage if they survive restarts. +const WATCHER_CLEANUP_DELAY_MS = 30000; + +// Since linux doesn't have recursive file watching, nsfw has to walk the +// watched folder and create a separate watcher for each subfolder. Until it has a +// way for us to filter which folders it walks we will continue to use +// pathwatcher to avoid having too many watchers. +let watcherLibrary = process.env.METEOR_WATCHER_LIBRARY || + (process.platform === 'linux' ? 'pathwatcher' : 'nsfw'); + +// Pathwatcher complains (using console.error, ugh) if you try to watch +// two files with the same stat.ino number but different paths on linux, so we have +// to deduplicate files by ino. +const DEDUPLICATE_BY_INO = watcherLibrary === 'pathwatcher'; +// Set METEOR_WATCH_FORCE_POLLING environment variable to a truthy value to +// force the use of files.watchFile instead of watchLibrary.watch. +let watcherEnabled = ! JSON.parse( + process.env.METEOR_WATCH_FORCE_POLLING || "false" +); + +const entriesByIno = new Map; + +export type SafeWatcher = { + close: () => void; +} + +type EntryCallback = (event: string) => void; + +interface Entry extends SafeWatcher { + callbacks: Set; + rewatch: () => void; + release: (callback: EntryCallback) => void; + _fire: (event: string) => void; +} + +const entries: Record = Object.create(null); + +// Folders that are watched recursively +let watchRoots = new Set(); + +// Set of paths for which a change event has been fired, watched with +// watchLibrary.watch if available. This could be an LRU cache, but in +// practice it should never grow large enough for that to matter. +const changedPaths = new Set; + +function hasPriority(absPath: string) { + // If we're not prioritizing changed files, then all files have + // priority, which means they should be watched with native file + // watchers if the platform supports them. If we are prioritizing + // changed files, then only changed files get priority. + return PRIORITIZE_CHANGED + ? changedPaths.has(absPath) + : true; +} + +function acquireWatcher(absPath: string, callback: EntryCallback) { + const entry = entries[absPath] || ( + entries[absPath] = startNewWatcher(absPath)); + + // Watches successfully established in the past may have become invalid + // because the watched file was deleted or renamed, so we need to make + // sure we're still watching every time we call safeWatcher.watch. + entry.rewatch(); + + // The size of the entry.callbacks Set also serves as a reference count + // for this watcher. + entry.callbacks.add(callback); + + return entry; +} + +function startNewWatcher(absPath: string): Entry { + let stat: Stats | BigIntStats | null | undefined = null; + + if (DEDUPLICATE_BY_INO) { + stat = statOrNull(absPath); + if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) { + const entry = entriesByIno.get(stat.ino); + if (entries[absPath] === entry) { + return entry; + } + } + } else { + let entry = entries[absPath]; + if (entry) { + return entry; + } + } + + function safeUnwatch() { + if (watcher) { + watcher.close(); + watcher = null; + if (stat && stat.ino > 0) { + entriesByIno.delete(stat.ino); + } + } + } + + let lastWatcherEventTime = Date.now(); + const callbacks = new Set(); + let watcherCleanupTimer: ReturnType | null = null; + let watcher: FSWatcher | null = null; + + // Determines the polling interval to be used for the fs.watchFile-based + // safety net that works on all platforms and file systems. + function getPollingInterval() { + if (hasPriority(absPath)) { + // Regardless of whether we have a native file watcher and it works + // correctly on this file system, poll prioritized files (that is, + // files that have been changed at least once) at a higher frequency + // (every 500ms by default). + return NO_WATCHER_POLLING_INTERVAL; + } + + if (watcherEnabled || PRIORITIZE_CHANGED) { + // As long as native file watching is enabled (even if it doesn't + // work correctly) and the developer hasn't explicitly opted out of + // the file watching priority system, poll unchanged files at a + // lower frequency (every 5000ms by default). + return DEFAULT_POLLING_INTERVAL; + } + + // If native file watching is disabled and the developer has + // explicitly opted out of the priority system, poll everything at the + // higher frequency (every 500ms by default). Note that this leads to + // higher idle CPU usage, so the developer may want to adjust the + // METEOR_WATCH_POLLING_INTERVAL_MS environment variable. + return NO_WATCHER_POLLING_INTERVAL; + } + + function fire(event: string) { + if (event !== "change") { + // When we receive a "delete" or "rename" event, the watcher is + // probably not going to generate any more notifications for this + // file, so we close and nullify the watcher to ensure that + // entry.rewatch() will attempt to reestablish the watcher the next + // time we call safeWatcher.watch. + safeUnwatch(); + + // Make sure we don't throttle the watchFile callback after a + // "delete" or "rename" event, since it is now our only reliable + // source of file change notifications. + lastWatcherEventTime = 0; + + } else { + changedPaths.add(absPath); + rewatch(); + } + + callbacks.forEach(cb => cb(event)); + } + + function watchWrapper(event: string) { + lastWatcherEventTime = Date.now(); + fire(event); + + // It's tempting to call unwatchFile(absPath, watchFileWrapper) here, + // but previous watcher success is no guarantee of future watcher + // reliability. For example, watchLibrary.watch works just fine when file + // changes originate from within a Vagrant VM, but changes to shared + // files made outside the VM are invisible to watcher, so our only + // hope of catching them is to continue polling. + } + + function rewatch() { + if (hasPriority(absPath)) { + if (watcher) { + // Already watching; nothing to do. + return; + } + watcher = watchLibraryWatch(absPath, watchWrapper); + } else if (watcher) { + safeUnwatch(); + } + + // Since we're about to restart the stat-based file watcher, we don't + // want to miss any of its events because of the lastWatcherEventTime + // throttling that it attempts to do. + lastWatcherEventTime = 0; + + // We use files.watchFile in addition to watcher.watch as a fail-safe + // to detect file changes even on network file systems. However + // (unless the user disabled watcher or this watcher call failed), we + // use a relatively long default polling interval of 5000ms to save + // CPU cycles. + statWatch(absPath, getPollingInterval(), watchFileWrapper); + } + + function watchFileWrapper(newStat: Stats, oldStat: Stats) { + if (newStat.ino === 0 && + oldStat.ino === 0 && + +newStat.mtime === +oldStat.mtime) { + // Node calls the watchFile listener once with bogus identical stat + // objects, which should not trigger a file change event. + return; + } + + // If a watcher event fired in the last polling interval, ignore + // this event. + if (Date.now() - lastWatcherEventTime > getPollingInterval()) { + fire("change"); + } + } + + const entry = { + callbacks, + rewatch, + + release(callback: EntryCallback) { + if (! entries[absPath]) { + return; + } + + callbacks.delete(callback); + if (callbacks.size > 0) { + return; + } + + // Once there are no more callbacks in the Set, close both watchers + // and nullify the shared data. + if (watcherCleanupTimer) { + clearTimeout(watcherCleanupTimer); + } + + watcherCleanupTimer = setTimeout(() => { + if (callbacks.size > 0) { + // If another callback was added while the timer was pending, we + // can avoid tearing anything down. + return; + } + entry.close(); + }, WATCHER_CLEANUP_DELAY_MS); + }, + + close() { + if (entries[absPath] !== entry) return; + entries[absPath] = null; + + if (watcherCleanupTimer) { + clearTimeout(watcherCleanupTimer); + watcherCleanupTimer = null; + } + + safeUnwatch(); + + unwatchFile(absPath, watchFileWrapper); + }, + _fire: fire + }; + + if (stat && stat.ino > 0) { + entriesByIno.set(stat.ino, entry); + } + + return entry; +} + +export function closeAllWatchers() { + Object.keys(entries).forEach(absPath => { + const entry = entries[absPath]; + if (entry) { + entry.close(); + } + }); +} + +const statWatchers = Object.create(null); + +function statWatch( + absPath: string, + interval: number, + callback: (current: Stats, previous: Stats) => void, +) { + let statWatcher = statWatchers[absPath]; + + if (!statWatcher) { + statWatcher = { + interval, + changeListeners: [], + stat: null + }; + statWatchers[absPath] = statWatcher; + } + + // If the interval needs to be changed, replace the watcher. + // Node will only recreate the watcher with the new interval if all old + // watchers are stopped (which unwatchFile does when not passed a + // specific listener) + if (statWatcher.interval !== interval && statWatcher.stat) { + // This stops all stat watchers for the file, not just those created by + // statWatch + unwatchFile(absPath); + statWatcher.stat = null; + statWatcher.interval = interval; + } + + if (!statWatcher.changeListeners.includes(callback)) { + statWatcher.changeListeners.push(callback); + } + + if (!statWatcher.stat) { + const newStat = watchFile(absPath, { + persistent: false, // never persistent + interval, + }, (newStat, oldStat) => { + statWatcher.changeListeners.forEach(( + listener: (newStat: Stats, oldStat: Stats) => void + ) => { + listener(newStat, oldStat); + }); + }); + + newStat.on("stop", () => { + if (statWatchers[absPath] === statWatch) { + delete statWatchers[absPath]; + } + }); + + statWatcher.stat = newStat; + } + + return statWatcher; +} + +function watchLibraryWatch(absPath: string, callback: EntryCallback) { + if (watcherEnabled && watcherLibrary === 'pathwatcher') { + try { + return pathwatcher.watch(convertToOSPath(absPath), callback); + } catch (e: any) { + maybeSuggestRaisingWatchLimit(e); + // ... ignore the error. We'll still have watchFile, which is good + // enough. + } + } + + return null; +} + +let suggestedRaisingWatchLimit = false; + +function maybeSuggestRaisingWatchLimit(error: Error & { errno: number }) { + var constants = require('constants'); + var archinfo = require('../utils/archinfo'); + if (! suggestedRaisingWatchLimit && + // Note: the not-super-documented require('constants') maps from + // strings to SYSTEM errno values. System errno values aren't the same + // as the numbers used internally by libuv! Once we're upgraded + // to Node 0.12, we'll have the system errno as a string (on 'code'), + // but the support for that wasn't in Node 0.10's uv. + // See our PR https://github.com/atom/node-pathwatcher/pull/53 + // (and make sure to read the final commit message, not the original + // proposed PR, which had a slightly different interface). + error.errno === constants.ENOSPC && + // The only suggestion we currently have is for Linux. + archinfo.matches(archinfo.host(), 'os.linux')) { + + // Check suggestedRaisingWatchLimit again because archinfo.host() may + // have yielded. + if (suggestedRaisingWatchLimit) return; + suggestedRaisingWatchLimit = true; + + var Console = require('../console/console.js').Console; + if (! Console.isHeadless()) { + Console.arrowWarn( + "It looks like a simple tweak to your system's configuration will " + + "make many tools (including this Meteor command) more efficient. " + + "To learn more, see " + + Console.url("https://github.com/meteor/docs/blob/master/long-form/file-change-watcher-efficiency.md")); + } + } +} + +export const watch = Profile( + "safeWatcher.watchLegacy", + (absPath: string, callback: EntryCallback) => { + const entry = acquireWatcher(absPath, callback); + return { + close() { + entry.release(callback); + } + } as SafeWatcher; + } +); + +const fireNames = { + [nsfw.actions.CREATED]: 'change', + [nsfw.actions.MODIFIED]: 'change', + [nsfw.actions.DELETED]: 'delete' +} + +export function addWatchRoot(absPath: string) { + if (watchRoots.has(absPath) || watcherLibrary !== 'nsfw' || !watcherEnabled) { + return; + } + + watchRoots.add(absPath); + + // If there already is a watcher for a parent directory, there is no need + // to create this watcher. + for (const path of watchRoots) { + let relativePath = pathRelative(path, absPath); + if ( + path !== absPath && + !relativePath.startsWith('..') && + !relativePath.startsWith('/') + ) { + return; + } + } + + // TODO: check if there are any existing watchers that are children of this + // watcher and stop them + + nsfw( + convertToOSPath(absPath), + (events) => { + events.forEach(event => { + if(event.action === nsfw.actions.RENAMED) { + let oldPath = nativeJoin(event.directory, event.oldFile); + let oldEntry = entries[toPosixPath(oldPath)]; + if (oldEntry) { + oldEntry._fire('rename'); + } + + let path = nativeJoin(event.newDirectory, event.newFile); + let newEntry = entries[toPosixPath(path)]; + if (newEntry) { + newEntry._fire('change'); + } + } else { + let path = nativeJoin(event.directory, event.file); + let entry = entries[toPosixPath(path)]; + if (entry) { + entry._fire(fireNames[event.action]); + } + } + }) + } + ).then(watcher => { + watcher.start() + }); +} diff --git a/tools/fs/safe-watcher.ts b/tools/fs/safe-watcher.ts index cea3d74d7b..07fe605fb7 100644 --- a/tools/fs/safe-watcher.ts +++ b/tools/fs/safe-watcher.ts @@ -1,476 +1,624 @@ -import { FSWatcher, Stats, BigIntStats } from "fs"; +import { Stats } from 'fs'; +import ParcelWatcher from "@parcel/watcher"; +import { watch as watchLegacy, addWatchRoot as addWatchRootLegacy, closeAllWatchers as closeAllWatchersLegacy } from './safe-watcher-legacy'; + import { Profile } from "../tool-env/profile"; -import { - statOrNull, - convertToOSPath, - watchFile, - unwatchFile, - toPosixPath, - pathRelative -} from "./files"; -import { - join as nativeJoin -} from 'path'; -import nsfw from 'vscode-nsfw'; +import { statOrNull, lstat, toPosixPath, convertToOSPath, pathRelative, watchFile, unwatchFile, pathResolve, pathDirname } from "./files"; +import { getMeteorConfig } from "../tool-env/meteor-config"; -const pathwatcher = require('pathwatcher'); +// Register process exit handlers to ensure subscriptions are properly cleaned up +const registerExitHandlers = () => { + + // For SIGINT and SIGTERM, we need to handle the async cleanup before the process exits + const cleanupAndExit = (signal: string) => { + // Clear the timeout if cleanup completes successfully + closeAllWatchers().then(() => { + process.exit(0); + }).catch(err => { + console.error(`Error closing watchers on ${signal}:`, err); + process.exit(1); + }); + }; + + // Handle SIGINT (Ctrl+C) + process.on('SIGINT', () => cleanupAndExit('SIGINT')); + + // Handle SIGTERM + process.on('SIGTERM', () => cleanupAndExit('SIGTERM')); + + // Handle 'exit' event + process.on('exit', () => { + try { + for (const root of Array.from(watchRoots)) { + const sub = dirSubscriptions.get(root); + if (sub) { + sub.unsubscribe(); + dirSubscriptions.delete(root); + watchRoots.delete(root); + } + } + } catch (err) { + console.error('Error during synchronous cleanup on exit:', err); + } + }); +}; + +export type SafeWatcher = { close: () => void; }; + +type ChangeCallback = (event: string) => void; + +interface Entry extends SafeWatcher { + callbacks: Set; + _fire(event: string): void; +} + +// Registry mapping normalized absolute paths to their watcher entry. +const entries = new Map(); + +// Registry mapping normalized absolute paths to their polling watchers. +// Each path can have multiple callbacks, but only one active watcher. +interface PollingWatcherInfo { + callbacks: Set; + pollCallback: (curr: Stats, prev: Stats) => void; +} +const pollingWatchers = new Map(); + +function getEntry(path: string): Entry | null | undefined { + return entries.get(path); +} + +function setEntry(path: string, entry: Entry | null): void { + entries.set(path, entry); +} + +function deleteEntry(path: string): void { + entries.delete(path); +} + +function findNearestEntry(startPath: string): Entry | null { + let currentPath = pathResolve(startPath); + + while (true) { + const entry = getEntry(currentPath); + if (entry) { + return entry; // Found it! + } + + const parentPath = pathDirname(currentPath); + if (parentPath === currentPath) { + // Reached root + break; + } + + currentPath = parentPath; + } + + return null; +} + +// Watch roots are directories for which we have an active ParcelWatcher subscription. +const watchRoots = new Set(); +// For each watch root, store its active subscription. +const dirSubscriptions = new Map(); +// A set of roots that are known to be unwatchable. +const ignoredWatchRoots = new Set(); + +// A set of roots that are known to be symbolic links. +const symlinkRoots = new Set(); + +// Set METEOR_WATCH_FORCE_POLLING environment variable to a truthy value to +// force the use of files.watchFile instead of ParcelWatcher. +let watcherEnabled = !JSON.parse(process.env.METEOR_WATCH_FORCE_POLLING || "false"); + +/** + * Polling fallback globals. + * The legacy Meteor strategy used polling for cases where native watchers failed. + * We keep track of files that changed, so that we can poll them faster. + */ +var DEFAULT_POLLING_INTERVAL = + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000); + +var NO_WATCHER_POLLING_INTERVAL = + +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 500); -// Default to prioritizing changed files, but disable that behavior (and -// thus prioritize all files equally) if METEOR_WATCH_PRIORITIZE_CHANGED -// is explicitly set to a string that parses to a falsy value. var PRIORITIZE_CHANGED = true; if (process.env.METEOR_WATCH_PRIORITIZE_CHANGED && ! JSON.parse(process.env.METEOR_WATCH_PRIORITIZE_CHANGED)) { PRIORITIZE_CHANGED = false; } -var DEFAULT_POLLING_INTERVAL = - +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 5000); - -var NO_WATCHER_POLLING_INTERVAL = - +(process.env.METEOR_WATCH_POLLING_INTERVAL_MS || 500); - -// This may seems like a long time to wait before actually closing the -// file watchers, but it's to our advantage if they survive restarts. -const WATCHER_CLEANUP_DELAY_MS = 30000; - -// Since linux doesn't have recursive file watching, nsfw has to walk the -// watched folder and create a separate watcher for each subfolder. Until it has a -// way for us to filter which folders it walks we will continue to use -// pathwatcher to avoid having too many watchers. -let watcherLibrary = process.env.METEOR_WATCHER_LIBRARY || - (process.platform === 'linux' ? 'pathwatcher' : 'nsfw'); - -// Pathwatcher complains (using console.error, ugh) if you try to watch -// two files with the same stat.ino number but different paths on linux, so we have -// to deduplicate files by ino. -const DEDUPLICATE_BY_INO = watcherLibrary === 'pathwatcher'; -// Set METEOR_WATCH_FORCE_POLLING environment variable to a truthy value to -// force the use of files.watchFile instead of watchLibrary.watch. -let watcherEnabled = ! JSON.parse( - process.env.METEOR_WATCH_FORCE_POLLING || "false" -); - -const entriesByIno = new Map; - -export type SafeWatcher = { - close: () => void; -} - -type EntryCallback = (event: string) => void; - -interface Entry extends SafeWatcher { - callbacks: Set; - rewatch: () => void; - release: (callback: EntryCallback) => void; - _fire: (event: string) => void; -} - -const entries: Record = Object.create(null); - -// Folders that are watched recursively -let watchRoots = new Set(); - // Set of paths for which a change event has been fired, watched with -// watchLibrary.watch if available. This could be an LRU cache, but in -// practice it should never grow large enough for that to matter. +// watchLibrary.watch if available. const changedPaths = new Set; +function shouldIgnorePath(absPath: string): boolean { + const posixPath = toPosixPath(absPath); + const parts = posixPath.split('/'); + + const cwd = toPosixPath(process.cwd()); + const isWithinCwd = absPath.startsWith(cwd); + + if (isWithinCwd && absPath.includes(`${cwd}/.meteor/local`)) { + return true; + } + + // Check for .meteor: allow the .meteor directory itself, + // but ignore its "local" subdirectory (or any immediate child folder that indicates cache). + const meteorIndex = parts.indexOf(".meteor"); + if (meteorIndex !== -1) { + const nextPart = parts[meteorIndex + 1]; + if (nextPart && nextPart === "local") { + // Ignore anything inside .meteor/local + return true; + } + // Otherwise, do not automatically ignore .meteor (which includes .meteor/packages, etc). + } + + // For project node_modules: check if it's a direct node_modules/ + if (isWithinCwd) { + // Check if it's the project node_modules + if (absPath.includes(`${cwd}/node_modules`)) { + // Check if it's a direct node_modules/ path + const relPath = absPath.substring(cwd.length + 1); // +1 for the slash + const relParts = relPath.split('/'); + if (relParts.length >= 2 && relParts[0] === 'node_modules') { + // If it's a direct node_modules/, check if it's a symlink + // We'll return false here (don't ignore) so that the code can later decide to use polling + // based on isSymbolicLink check in the watch function + if (relParts.length === 2 && isSymbolicLink(absPath)) { + return false; + } + // Check if it's within a symlink root to not ignore + if (isWithinSymlinkRoot(absPath)) { + return false; + } + } + return true; + } else { + // Otherwise, don't ignore non-npm node_modules + return false; + } + } + + // For external node_modules: check if it's a direct node_modules/ + const nmIndex = parts.indexOf("node_modules"); + if (nmIndex !== -1) { + // Don't ignore node_modules within .npm/package/ paths + const npmPackageIndex = parts.indexOf(".npm"); + if (npmPackageIndex !== -1 && parts[npmPackageIndex + 1] === "package" && + nmIndex > npmPackageIndex && parts[nmIndex - 1] === "package") { + return false; + } + return true; + } + + return false; +} + +/** + * Check if a path is a symbolic link. + * + * Symbolic links are not supported natively in some operating systems, + * so we need to use polling for them to ensure they are properly watched. + * This function is used to determine if a path is a symbolic link, + * so we can use polling instead of native watching for it. + * + * If a path is a symbolic link, its root is added to the symlinkRoots set. + */ +function isSymbolicLink(absPath: string, addToRoots = true): boolean { + try { + const osPath = convertToOSPath(absPath); + const stat = lstat(osPath); + if (stat?.isSymbolicLink()) { + if (addToRoots) { + // Add the directory containing the symlink to the symlinkRoots set + const symlinkRoot = toPosixPath(absPath); + symlinkRoots.add(symlinkRoot); + // Rewatch using polling any existing watchers under this symlink root + rewatchPolling(symlinkRoot); + } + return true; + } + return false; + } catch (e) { + // If we can't stat the file, assume it's not a symlink + return false; + } +} + +/** + * Check if a path is within any symlink root. + * + * This is used to determine if a path should use polling instead of native watching, + * even if it's not a symlink itself. + */ +function isWithinSymlinkRoot(absPath: string): boolean { + for (const root of symlinkRoots) { + // Check if absPath starts with root + '/' + if (absPath === root || (absPath.startsWith(root) && absPath.charAt(root.length) === '/')) { + return true; + } + } + return false; +} + +/** + * Ensure that the given directory is being watched by @parcel/watcher. + * If it is not a directory or is unwatchable, it is immediately added to an ignore set. + */ +async function ensureWatchRoot(dirPath: string): Promise { + if (!watcherEnabled || watchRoots.has(dirPath) || ignoredWatchRoots.has(dirPath)) { + return; + } + + // If an ancestor is already watched, skip this one. + for (const root of watchRoots) { + const rel = pathRelative(root, dirPath); + if (root !== dirPath && !rel.startsWith("..") && !rel.startsWith("/")) { + return; + } + } + + // Remove any existing roots that are now encompassed by the new one. + for (const root of Array.from(watchRoots)) { + const rel = pathRelative(dirPath, root); + if (root !== dirPath && !rel.startsWith("..") && !rel.startsWith("/")) { + const sub = dirSubscriptions.get(root); + if (sub) { + try { + await sub.unsubscribe(); + } catch (_) { + /* ignore errors */ + } + } + dirSubscriptions.delete(root); + watchRoots.delete(root); + } + } + + const osDirPath = convertToOSPath(dirPath); + // Check that osDirPath is indeed a directory. + try { + const stats = statOrNull(osDirPath); + if (!stats?.isDirectory()) { + console.warn(`Skipping watcher for ${osDirPath}: not a directory`); + ignoredWatchRoots.add(dirPath); + return; + } + } catch (e) { + console.error(`Failed to stat ${osDirPath}:`, e); + ignoredWatchRoots.add(dirPath); + return; + } + + // Set up ignore patterns to skip node_modules and .meteor/local cache + const cwd = toPosixPath(process.cwd()); + const isWithinCwd = dirPath.startsWith(cwd); + const ignPrefix = isWithinCwd ? "" : "**/"; + const ignorePatterns = [`${ignPrefix}node_modules/**`, `${ignPrefix}.meteor/local/**`]; + try { + watchRoots.add(dirPath); + const subscription = await ParcelWatcher.subscribe( + osDirPath, + (err, events) => { + if (err) { + if (/Events were dropped/.test(err.message)) { + return; + } + console.error(`Parcel watcher error on ${osDirPath}:`, err); + // Only disable native watching for critical errors (like ENOSPC). + // @ts-ignore + if (err.code === "ENOSPC" || err.errno === require("constants").ENOSPC) { + fallbackToPolling(); + } + watchRoots.delete(dirPath); + return; + } + // Dispatch each event to any registered entries. + for (const event of events) { + const changedPath = toPosixPath(event.path); + const entry = findNearestEntry(changedPath); + if (!entry) continue; + // In Meteor's safe-watcher API, both create/update trigger "change" events. + const evtType = event.type === "delete" ? "delete" : "change"; + entry._fire(evtType); + } + }, + { ignore: ignorePatterns } + ); + dirSubscriptions.set(dirPath, subscription); + } catch (e: any) { + if ( + e && + (e.code === "ENOTDIR" || + /Not a directory/.test(e.message) || + e.code === "EBADF" || + /Bad file descriptor/.test(e.message)) + ) { + console.warn(`Skipping watcher for ${osDirPath}: not a directory`); + ignoredWatchRoots.add(dirPath); + } else { + console.error(`Failed to start watcher for ${osDirPath}:`, e); + if (e.code === "ENOSPC" || e.errno === require("constants").ENOSPC) { + fallbackToPolling(); + } + } + watchRoots.delete(dirPath); + } +} + +/** + * Creates a new watch entry for a specific file (or directory) and + * holds its registered callbacks. + */ +function startNewEntry(absPath: string): Entry { + const callbacks = new Set(); + let closed = false; + const entry: Entry = { + callbacks, + close() { + if (closed) return; + closed = true; + deleteEntry(absPath); + }, + _fire(event: string) { + callbacks.forEach(cb => { + try { + cb(event); + } catch (e) { + // Ignore callback errors. + } + }); + } + }; + return entry; +} + +/** + * The primary API function to watch a file or directory. + * This registers the callback on the internally managed entry and + * ensures that a Parcel watcher is subscribed to a covering directory. + */ +export function watch (absPath: string, callback: ChangeCallback): SafeWatcher { + // @ts-ignore + if (!getMeteorConfig()?.modern?.watcher) { + // @ts-ignore + return watchLegacy(absPath, callback); + } + // @ts-ignore + return watchModern(absPath, callback); +}; + +const watchModern = + Profile( + "safeWatcher.watchModern", + ( + absPath: string, + callback: ChangeCallback + ): SafeWatcher => { + absPath = toPosixPath(absPath); + + // If the path should be ignored, immediately return a noop SafeWatcher. + if (shouldIgnorePath(absPath)) { + return { close() {} }; + } + // If native watching is disabled, the path is a symbolic link, or the path is within a symlink root, + // use the polling strategy. Symbolic links are not supported natively in some operating systems, + // and paths within symlink roots should also use polling for consistency. + if (!watcherEnabled || isWithinSymlinkRoot(absPath) || isSymbolicLink(absPath)) { + return startPolling(absPath, callback); + } + // Try to reuse an existing entry if one was created before. + let entry = getEntry(absPath); + if (!entry) { + entry = startNewEntry(absPath); + setEntry(absPath, entry); + // Determine the directory that should be watched. + let watchTarget: string; + try { + const st = statOrNull(convertToOSPath(absPath)); + watchTarget = st?.isDirectory() ? absPath : toPosixPath(pathDirname(convertToOSPath(absPath))); + } catch (e) { + watchTarget = toPosixPath(pathDirname(convertToOSPath(absPath))); + } + // Set up a watcher on the parent directory (or the directory itself) if not already active. + ensureWatchRoot(watchTarget); + } + // Register the callback for this file. + entry.callbacks.add(callback); + return { + close() { + const entry = getEntry(absPath); + if (entry) { + entry.callbacks.delete(callback); + if (entry.callbacks.size === 0) { + entry.close(); + } + } + } + }; +}); + +/** + * Externally force a directory to be watched. + * If the provided path is a file, its parent directory is used. + */ +export function addWatchRoot(absPath: string) { + // @ts-ignore + if (!getMeteorConfig()?.modern?.watcher) { + // @ts-ignore + return addWatchRootLegacy(absPath); + } + + absPath = toPosixPath(absPath); + let watchTarget = absPath; + try { + const st = statOrNull(convertToOSPath(absPath)); + if (!st?.isDirectory()) { + watchTarget = toPosixPath(pathDirname(convertToOSPath(absPath))); + } + } catch (e) { + watchTarget = toPosixPath(pathDirname(convertToOSPath(absPath))); + } + ensureWatchRoot(watchTarget); +} + +async function safeUnsubscribeSub(root: string) { + const sub = dirSubscriptions.get(root); + if (!sub) return; // Already unsubscribed. + // Remove from our maps immediately to prevent further unsubscribe calls. + dirSubscriptions.delete(root); + watchRoots.delete(root); + try { + await sub.unsubscribe(); + } catch (e) { + console.error(`Error during unsubscribe for ${root}:`, e); + } +} + +export async function closeAllWatchers() { + // @ts-ignore + if (!getMeteorConfig()?.modern?.watcher) { + // @ts-ignore + return closeAllWatchersLegacy(); + } + for (const root of Array.from(watchRoots)) { + await safeUnsubscribeSub(root); + } +} + function hasPriority(absPath: string) { // If we're not prioritizing changed files, then all files have // priority, which means they should be watched with native file // watchers if the platform supports them. If we are prioritizing // changed files, then only changed files get priority. return PRIORITIZE_CHANGED - ? changedPaths.has(absPath) - : true; + ? changedPaths.has(absPath) + : true; } -function acquireWatcher(absPath: string, callback: EntryCallback) { - const entry = entries[absPath] || ( - entries[absPath] = startNewWatcher(absPath)); - - // Watches successfully established in the past may have become invalid - // because the watched file was deleted or renamed, so we need to make - // sure we're still watching every time we call safeWatcher.watch. - entry.rewatch(); - - // The size of the entry.callbacks Set also serves as a reference count - // for this watcher. - entry.callbacks.add(callback); - - return entry; -} - -function startNewWatcher(absPath: string): Entry { - let stat: Stats | BigIntStats | null | undefined = null; - - if (DEDUPLICATE_BY_INO) { - stat = statOrNull(absPath); - if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) { - const entry = entriesByIno.get(stat.ino); - if (entries[absPath] === entry) { - return entry; - } - } - } else { - let entry = entries[absPath]; - if (entry) { - return entry; - } - } - - function safeUnwatch() { - if (watcher) { - watcher.close(); - watcher = null; - if (stat && stat.ino > 0) { - entriesByIno.delete(stat.ino); - } - } - } - - let lastWatcherEventTime = Date.now(); - const callbacks = new Set(); - let watcherCleanupTimer: ReturnType | null = null; - let watcher: FSWatcher | null = null; - - // Determines the polling interval to be used for the fs.watchFile-based - // safety net that works on all platforms and file systems. - function getPollingInterval() { - if (hasPriority(absPath)) { - // Regardless of whether we have a native file watcher and it works - // correctly on this file system, poll prioritized files (that is, - // files that have been changed at least once) at a higher frequency - // (every 500ms by default). - return NO_WATCHER_POLLING_INTERVAL; - } - - if (watcherEnabled || PRIORITIZE_CHANGED) { - // As long as native file watching is enabled (even if it doesn't - // work correctly) and the developer hasn't explicitly opted out of - // the file watching priority system, poll unchanged files at a - // lower frequency (every 5000ms by default). - return DEFAULT_POLLING_INTERVAL; - } - - // If native file watching is disabled and the developer has - // explicitly opted out of the priority system, poll everything at the - // higher frequency (every 500ms by default). Note that this leads to - // higher idle CPU usage, so the developer may want to adjust the - // METEOR_WATCH_POLLING_INTERVAL_MS environment variable. +// Determines the polling interval to be used for the fs.watchFile-based +// safety net that works on all platforms and file systems. +function getPollingInterval(absPath: string): number { + if (hasPriority(absPath)) { + // Regardless of whether we have a native file watcher and it works + // correctly on this file system, poll prioritized files (that is, + // files that have been changed at least once) at a higher frequency + // (every 500ms by default). return NO_WATCHER_POLLING_INTERVAL; } - function fire(event: string) { - if (event !== "change") { - // When we receive a "delete" or "rename" event, the watcher is - // probably not going to generate any more notifications for this - // file, so we close and nullify the watcher to ensure that - // entry.rewatch() will attempt to reestablish the watcher the next - // time we call safeWatcher.watch. - safeUnwatch(); - - // Make sure we don't throttle the watchFile callback after a - // "delete" or "rename" event, since it is now our only reliable - // source of file change notifications. - lastWatcherEventTime = 0; - - } else { - changedPaths.add(absPath); - rewatch(); - } - - callbacks.forEach(cb => cb(event)); + if (watcherEnabled || PRIORITIZE_CHANGED) { + // As long as native file watching is enabled (even if it doesn't + // work correctly) and the developer hasn't explicitly opted out of + // the file watching priority system, poll unchanged files at a + // lower frequency (every 5000ms by default). + return DEFAULT_POLLING_INTERVAL; } - function watchWrapper(event: string) { - lastWatcherEventTime = Date.now(); - fire(event); + // If native file watching is disabled and the developer has + // explicitly opted out of the priority system, poll everything at the + // higher frequency (every 500ms by default). Note that this leads to + // higher idle CPU usage, so the developer may want to adjust the + // METEOR_WATCH_POLLING_INTERVAL_MS environment variable. + return NO_WATCHER_POLLING_INTERVAL; +} - // It's tempting to call unwatchFile(absPath, watchFileWrapper) here, - // but previous watcher success is no guarantee of future watcher - // reliability. For example, watchLibrary.watch works just fine when file - // changes originate from within a Vagrant VM, but changes to shared - // files made outside the VM are invisible to watcher, so our only - // hope of catching them is to continue polling. - } +function startPolling(absPath: string, callback: ChangeCallback): SafeWatcher { + const osPath = convertToOSPath(absPath); + // Initial polling interval. + let interval = getPollingInterval(absPath); - function rewatch() { - if (hasPriority(absPath)) { - if (watcher) { - // Already watching; nothing to do. - return; - } - watcher = watchLibraryWatch(absPath, watchWrapper); - } else if (watcher) { - safeUnwatch(); - } + // Check if we already have a polling watcher for this path + let watcherInfo = pollingWatchers.get(absPath); - // Since we're about to restart the stat-based file watcher, we don't - // want to miss any of its events because of the lastWatcherEventTime - // throttling that it attempts to do. - lastWatcherEventTime = 0; - - // We use files.watchFile in addition to watcher.watch as a fail-safe - // to detect file changes even on network file systems. However - // (unless the user disabled watcher or this watcher call failed), we - // use a relatively long default polling interval of 5000ms to save - // CPU cycles. - statWatch(absPath, getPollingInterval(), watchFileWrapper); - } - - function watchFileWrapper(newStat: Stats, oldStat: Stats) { - if (newStat.ino === 0 && - oldStat.ino === 0 && - +newStat.mtime === +oldStat.mtime) { - // Node calls the watchFile listener once with bogus identical stat - // objects, which should not trigger a file change event. - return; - } - - // If a watcher event fired in the last polling interval, ignore - // this event. - if (Date.now() - lastWatcherEventTime > getPollingInterval()) { - fire("change"); - } - } - - const entry = { - callbacks, - rewatch, - - release(callback: EntryCallback) { - if (! entries[absPath]) { - return; - } - - callbacks.delete(callback); - if (callbacks.size > 0) { - return; - } - - // Once there are no more callbacks in the Set, close both watchers - // and nullify the shared data. - if (watcherCleanupTimer) { - clearTimeout(watcherCleanupTimer); - } - - watcherCleanupTimer = setTimeout(() => { - if (callbacks.size > 0) { - // If another callback was added while the timer was pending, we - // can avoid tearing anything down. - return; + if (watcherInfo) { + // Add this callback to the existing watcher + watcherInfo.callbacks.add(callback); + } else { + // Create a new polling watcher + const pollCallback = (curr: Stats, prev: Stats) => { + // Compare modification times to detect a change. + if (+curr.mtime !== +prev.mtime) { + changedPaths.add(absPath); + // Notify all callbacks registered for this path + const info = pollingWatchers.get(absPath); + if (info) { + for (const cb of info.callbacks) { + cb("change"); + } } - entry.close(); - }, WATCHER_CLEANUP_DELAY_MS); - }, - - close() { - if (entries[absPath] !== entry) return; - entries[absPath] = null; - - if (watcherCleanupTimer) { - clearTimeout(watcherCleanupTimer); - watcherCleanupTimer = null; } - - safeUnwatch(); - - unwatchFile(absPath, watchFileWrapper); - }, - _fire: fire - }; - - if (stat && stat.ino > 0) { - entriesByIno.set(stat.ino, entry); - } - - return entry; -} - -export function closeAllWatchers() { - Object.keys(entries).forEach(absPath => { - const entry = entries[absPath]; - if (entry) { - entry.close(); - } - }); -} - -const statWatchers = Object.create(null); - -function statWatch( - absPath: string, - interval: number, - callback: (current: Stats, previous: Stats) => void, -) { - let statWatcher = statWatchers[absPath]; - - if (!statWatcher) { - statWatcher = { - interval, - changeListeners: [], - stat: null }; - statWatchers[absPath] = statWatcher; + + watchFile(osPath, { interval }, pollCallback); + + // Store the new watcher info + watcherInfo = { + callbacks: new Set([callback]), + pollCallback + }; + pollingWatchers.set(absPath, watcherInfo); } - // If the interval needs to be changed, replace the watcher. - // Node will only recreate the watcher with the new interval if all old - // watchers are stopped (which unwatchFile does when not passed a - // specific listener) - if (statWatcher.interval !== interval && statWatcher.stat) { - // This stops all stat watchers for the file, not just those created by - // statWatch - unwatchFile(absPath); - statWatcher.stat = null; - statWatcher.interval = interval; - } + return { + close() { + const info = pollingWatchers.get(absPath); + if (info) { + // Remove this callback + info.callbacks.delete(callback); - if (!statWatcher.changeListeners.includes(callback)) { - statWatcher.changeListeners.push(callback); - } - - if (!statWatcher.stat) { - const newStat = watchFile(absPath, { - persistent: false, // never persistent - interval, - }, (newStat, oldStat) => { - statWatcher.changeListeners.forEach(( - listener: (newStat: Stats, oldStat: Stats) => void - ) => { - listener(newStat, oldStat); - }); - }); - - newStat.on("stop", () => { - if (statWatchers[absPath] === statWatch) { - delete statWatchers[absPath]; - } - }); - - statWatcher.stat = newStat; - } - - return statWatcher; -} - -function watchLibraryWatch(absPath: string, callback: EntryCallback) { - if (watcherEnabled && watcherLibrary === 'pathwatcher') { - try { - return pathwatcher.watch(convertToOSPath(absPath), callback); - } catch (e: any) { - maybeSuggestRaisingWatchLimit(e); - // ... ignore the error. We'll still have watchFile, which is good - // enough. - } - } - - return null; -} - -let suggestedRaisingWatchLimit = false; - -function maybeSuggestRaisingWatchLimit(error: Error & { errno: number }) { - var constants = require('constants'); - var archinfo = require('../utils/archinfo'); - if (! suggestedRaisingWatchLimit && - // Note: the not-super-documented require('constants') maps from - // strings to SYSTEM errno values. System errno values aren't the same - // as the numbers used internally by libuv! Once we're upgraded - // to Node 0.12, we'll have the system errno as a string (on 'code'), - // but the support for that wasn't in Node 0.10's uv. - // See our PR https://github.com/atom/node-pathwatcher/pull/53 - // (and make sure to read the final commit message, not the original - // proposed PR, which had a slightly different interface). - error.errno === constants.ENOSPC && - // The only suggestion we currently have is for Linux. - archinfo.matches(archinfo.host(), 'os.linux')) { - - // Check suggestedRaisingWatchLimit again because archinfo.host() may - // have yielded. - if (suggestedRaisingWatchLimit) return; - suggestedRaisingWatchLimit = true; - - var Console = require('../console/console.js').Console; - if (! Console.isHeadless()) { - Console.arrowWarn( - "It looks like a simple tweak to your system's configuration will " + - "make many tools (including this Meteor command) more efficient. " + - "To learn more, see " + - Console.url("https://github.com/meteor/docs/blob/master/long-form/file-change-watcher-efficiency.md")); - } - } -} - -export const watch = Profile( - "safeWatcher.watch", - (absPath: string, callback: EntryCallback) => { - const entry = acquireWatcher(absPath, callback); - return { - close() { - entry.release(callback); - } - } as SafeWatcher; - } -); - -const fireNames = { - [nsfw.actions.CREATED]: 'change', - [nsfw.actions.MODIFIED]: 'change', - [nsfw.actions.DELETED]: 'delete' -} - -export function addWatchRoot(absPath: string) { - if (watchRoots.has(absPath) || watcherLibrary !== 'nsfw' || !watcherEnabled) { - return; - } - - watchRoots.add(absPath); - - // If there already is a watcher for a parent directory, there is no need - // to create this watcher. - for (const path of watchRoots) { - let relativePath = pathRelative(path, absPath); - if ( - path !== absPath && - !relativePath.startsWith('..') && - !relativePath.startsWith('/') - ) { - return; - } - } - - // TODO: check if there are any existing watchers that are children of this - // watcher and stop them - - nsfw( - convertToOSPath(absPath), - (events) => { - events.forEach(event => { - if(event.action === nsfw.actions.RENAMED) { - let oldPath = nativeJoin(event.directory, event.oldFile); - let oldEntry = entries[toPosixPath(oldPath)]; - if (oldEntry) { - oldEntry._fire('rename'); - } - - let path = nativeJoin(event.newDirectory, event.newFile); - let newEntry = entries[toPosixPath(path)]; - if (newEntry) { - newEntry._fire('change'); - } - } else { - let path = nativeJoin(event.directory, event.file); - let entry = entries[toPosixPath(path)]; - if (entry) { - entry._fire(fireNames[event.action]); - } + // If no callbacks remain, remove the watcher + if (info.callbacks.size === 0) { + unwatchFile(osPath, info.pollCallback); + pollingWatchers.delete(absPath); + changedPaths.delete(absPath); } - }) + } } - ).then(watcher => { - watcher.start() - }); + }; } + +/** + * Rewatch entries under a symlink root from native watchers to polling watchers. + * This is called when a new symlink root is discovered. + */ +function rewatchPolling(root: string) { + for (const [watchedPath, entry] of entries) { + // if it lives under the new symlink root... + if (watchedPath === root || + (watchedPath.startsWith(root) && watchedPath.charAt(root.length) === '/')) { + // Skip if entry is null or already closed + if (!entry) continue; + + // Store the callbacks before closing the entry + const callbacks = Array.from(entry.callbacks); + + // Tear down the old native watcher + entry.close(); + + // Remove it from the map + entries.delete(watchedPath); + + // Re-watch via polling for each callback + for (const cb of callbacks) { + startPolling(watchedPath, cb); + } + } + } +} + +/** + * Fall back to polling. If a critical error occurs, + * we disable native watching and close all existing native watchers. + */ +function fallbackToPolling() { + if (watcherEnabled) { + console.error("Critical native watcher error encountered. Falling back to polling for all entries."); + watcherEnabled = false; + closeAllWatchers(); + } +} + +// Register exit handlers to ensure proper cleanup of subscriptions +registerExitHandlers(); diff --git a/tools/index.js b/tools/index.js index cbb86ac7d1..82876cae67 100644 --- a/tools/index.js +++ b/tools/index.js @@ -1,23 +1,25 @@ -const { getChildProcess } = require('./cli/dev-bundle-bin-commands') +const { getChildProcess } = require("./cli/dev-bundle-bin-commands"); -getChildProcess({ isFirstTry: true }).then((child) => { - if (! child) { - // Use process.nextTick here to prevent the Promise from swallowing - // errors from the rest of the setup code. - process.nextTick(continueSetup); +getChildProcess({ isFirstTry: true }).then( + (child) => { + if (!child) { + // Use process.nextTick here to prevent the Promise from swallowing + // errors from the rest of the setup code. + process.nextTick(continueSetup); + } + // If we spawned a process to handle a dev_bundle/bin command like + // `meteor npm` or `meteor node`, then don't run any other tool code. + }, + (error) => { + process.nextTick(function () { + throw error; + }); } - // If we spawned a process to handle a dev_bundle/bin command like - // `meteor npm` or `meteor node`, then don't run any other tool code. -}, (error) => { - process.nextTick(function () { - throw error; - }); -}); +); function continueSetup() { // Set up the Babel transpiler - require('./tool-env/install-babel'); + require("./tool-env/install-babel"); // Run the Meteor command line tool - require('./cli/main'); + require("./cli/main"); } - diff --git a/tools/isobuild/builder.js b/tools/isobuild/builder.js index 563d47bf01..8472f15f16 100644 --- a/tools/isobuild/builder.js +++ b/tools/isobuild/builder.js @@ -1,7 +1,7 @@ import assert from "assert"; import {WatchSet, readAndWatchFile, sha1} from '../fs/watch'; import files, { - symlinkWithOverwrite, realpath, + symlinkWithOverwrite, realpath, rm_recursive_deferred, } from '../fs/files'; import NpmDiscards from './npm-discards'; import {Profile} from '../tool-env/profile'; @@ -126,7 +126,8 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` async init() { // Build the output from scratch if (this.resetBuildPath) { - await files.rm_recursive(this.buildPath); + await files.rm_recursive_deferred(this.buildPath); + // Create the new build directory immediately without waiting for deletion await files.mkdir_p(this.buildPath, 0o755); } this.watchSet = new WatchSet(); @@ -881,7 +882,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` removed[path] = true; } else { // directory - await files.rm_recursive(absPath); + await files.rm_recursive_deferred(absPath); // mark all sub-paths as removed, too paths.forEach((anotherPath) => { @@ -904,7 +905,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // Delete the partially-completed bundle. Do not disturb outputPath. abort() { - return files.rm_recursive(this.buildPath); + return files.rm_recursive_deferred(this.buildPath); } // Returns a WatchSet representing all files that were read from disk by the @@ -936,7 +937,7 @@ async function atomicallyRewriteFile(path, data, options) { // replacing a directory with a file; this is rare (so it can // be a slow path) but can legitimately happen if e.g. a developer // puts a file where there used to be a directory in their app. - await files.rm_recursive(path); + await files.rm_recursive_deferred(path); files.rename(rpath, path); } else { throw e; diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index d70f31c6a5..5d42c1b61e 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -1223,13 +1223,16 @@ class Target { continue; } + var data = await resource.data; + var hash = await resource.hash; + const fileOptions = { info: 'unbuild ' + resource, arch: this.arch, - data: await resource.data, + data, cacheable: false, - hash: await resource.hash, - skipSri: !!await resource.hash + hash, + skipSri: !!hash }; const file = new File(fileOptions); @@ -1290,15 +1293,17 @@ class Target { continue; } + var data = await resource.data; + var hash = await resource.hash; let sourcePath; - if ((await resource.data) && resource.sourceRoot && resource.sourcePath) { + if (data && resource.sourceRoot && resource.sourcePath) { sourcePath = files.pathJoin(resource.sourceRoot, resource.sourcePath); } const f = new File({ info: 'resource ' + resource.servePath, arch: this.arch, - data: await resource.data, - hash: await resource.hash, + data, + hash, cacheable: false, replaceable: resource.type === 'js' && sourceBatch.hmrAvailable, sourcePath @@ -1323,14 +1328,15 @@ class Target { }); } + var sourceMap = await resource.sourceMap; // Both CSS and JS files can have source maps - if (await resource.sourceMap) { + if (sourceMap) { // XXX we used to set sourceMapRoot to // files.pathDirname(relPath) but it's unclear why. With the // currently generated source map file names, it works without it // and doesn't work well with it... maybe? we were getting // 'packages/packages/foo/bar.js' - f.setSourceMap(await resource.sourceMap, null); + f.setSourceMap(sourceMap, null); } this[resource.type].push(f); @@ -1540,6 +1546,9 @@ class Target { // with the original sources. rewriteSourceMaps() { const rewriteSourceMap = function (sm) { + if (!sm.sources) { + return sm; + } sm.sources = sm.sources.map(function (path) { const prefix = SOURCE_URL_PREFIX; if (path.slice(0, prefix.length) === prefix) { diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 6db7ef7062..6eaa2f332c 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -1720,21 +1720,21 @@ export class PackageSourceBatch { const fileHashes = []; const cacheKeyPrefix = sha1(JSON.stringify({ linkerOptions, - files: await jsResources.reduce(async (acc, inputFile) => { - const resolvedAcc = await acc; - - fileHashes.push(await inputFile.hash); - return [...resolvedAcc, { - meteorInstallOptions: inputFile.meteorInstallOptions, - absModuleId: inputFile.absModuleId, - sourceMap: !! await inputFile.sourceMap, - mainModule: inputFile.mainModule, - imported: inputFile.imported, - alias: inputFile.alias, - lazy: inputFile.lazy, - bare: inputFile.bare, - }]; - }, Promise.resolve([])) + files: await Promise.all( + jsResources.map(async (inputFile) => { + fileHashes.push(await inputFile.hash); + return { + meteorInstallOptions: inputFile.meteorInstallOptions, + absModuleId: inputFile.absModuleId, + sourceMap: !!(await inputFile.sourceMap), + mainModule: inputFile.mainModule, + imported: inputFile.imported, + alias: inputFile.alias, + lazy: inputFile.lazy, + bare: inputFile.bare, + }; + }) + ) })); const cacheKeySuffix = sha1(JSON.stringify({ LINKER_CACHE_SALT, @@ -1832,7 +1832,7 @@ export class PackageSourceBatch { if (cacheFilename) { // Write asynchronously. try { - await files.rm_recursive(wildcardCacheFilename); + await files.rm_recursive_deferred(wildcardCacheFilename); } finally { await files.writeFileAtomically(cacheFilename, retAsJSON); } diff --git a/tools/isobuild/compiler.js b/tools/isobuild/compiler.js index 66cb49e2b0..8e03f3d8ca 100644 --- a/tools/isobuild/compiler.js +++ b/tools/isobuild/compiler.js @@ -150,6 +150,7 @@ compiler.compile = Profile(function (packageSource, options) { // Isopack#initFromPath). var isobuildFeatures = []; packageSource.architectures.forEach((sourceArch) => { + if (global.includedWebArchs != null && ![...global.includedWebArchs, 'os'].includes(sourceArch.arch)) return; sourceArch.uses.forEach((use) => { if (!use.weak && isIsobuildFeaturePackage(use.package) && isobuildFeatures.indexOf(use.package) === -1) { @@ -181,6 +182,7 @@ compiler.compile = Profile(function (packageSource, options) { if (architecture.arch === 'web.cordova' && ! includeCordovaUnibuild) { continue; } + if (global.includedWebArchs != null && ![...global.includedWebArchs, 'os'].includes(architecture.arch)) continue; // TODO -> Maybe this withCache will bring some problems in other commands. await files.withCache(async () => { @@ -226,6 +228,7 @@ compiler.lint = Profile(function (packageSource, options) { && architecture.arch === 'web.cordova') { continue; } + if (global.includedWebArchs != null && ![...global.includedWebArchs, 'os'].includes(architecture.arch)) continue; const unibuildWarnings = await lintUnibuild({ isopack: options.isopack, @@ -246,6 +249,8 @@ compiler.getMinifiers = async function (packageSource, options) { var minifiers = []; for (const architecture of packageSource.architectures) { + if (global.includedWebArchs != null && ![...global.includedWebArchs, 'os'].includes(architecture.arch)) continue; + var activePluginPackages = await getActivePluginPackages(options.isopack, { isopackCache: options.isopackCache, uses: architecture.uses @@ -791,8 +796,9 @@ async function runLinters({inputSourceArch, isopackCache, sources, const absPath = files.pathResolve(inputSourceArch.sourceRoot, relPath); const hash = optimisticHashOrNull(absPath); - const contents = optimisticReadFile(absPath); - watchSet.addFile(absPath, hash); + if (!watchSet.hasFile(absPath)) { + watchSet.addFile(absPath, hash); + } if (classification.type === "meteor-ignore") { // Return after watching .meteorignore files but before adding them @@ -801,6 +807,7 @@ async function runLinters({inputSourceArch, isopackCache, sources, return; } + const contents = optimisticReadFile(absPath); const wrappedSource = { relPath, contents, hash, fileOptions, arch: inputSourceArch.arch, diff --git a/tools/isobuild/import-scanner.ts b/tools/isobuild/import-scanner.ts index fe6d9639bc..7aff1ee0e6 100644 --- a/tools/isobuild/import-scanner.ts +++ b/tools/isobuild/import-scanner.ts @@ -45,6 +45,7 @@ import { import { wrap } from "optimism"; const { compile: reifyCompile } = require("@meteorjs/reify/lib/compiler"); +const { parse: reifyAcornParse } = require("@meteorjs/reify/lib/parsers/acorn"); const { parse: reifyBabelParse } = require("@meteorjs/reify/lib/parsers/babel"); import Resolver, { Resolution } from "./resolver"; @@ -87,14 +88,32 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function ( } const isLegacy = isLegacyArch(bundleArch); - let result = reifyCompile(stripHashBang(source), { - parse: reifyBabelParse, + const reifyOptions = { generateLetDeclarations: !isLegacy, avoidModernSyntax: isLegacy, enforceStrictMode: false, dynamicImport: true, ast: false, - }).code; + }; + + let result; + try { + // First attempt: use Acorn + result = reifyCompile(stripHashBang(source), { + ...reifyOptions, + parse: reifyAcornParse, + }).code; + } catch (acornError) { + // Fallback: use Babel parser + // acorn may throw SyntaxError due to the lack of support for + // some features, but babel should still be able to parse the file + // For example, acorn don’t support JSX, only with acorn-jsx, + // but it isn’t included in Reify. + result = reifyCompile(stripHashBang(source), { + ...reifyOptions, + parse: reifyBabelParse, + }).code; + } if (cacheFilePath) { Promise.resolve().then( @@ -977,7 +996,7 @@ export default class ImportScanner { private async findImportedModuleIdentifiers( file: File, ): Promise> { - const fileHash = file.hash; + const fileHash = file.hash instanceof Promise ? await file.hash : file.hash; if (IMPORT_SCANNER_CACHE.has(fileHash)) { return IMPORT_SCANNER_CACHE.get(fileHash) as Record; } @@ -988,8 +1007,8 @@ export default class ImportScanner { ); // there should always be file.hash, but better safe than sorry - if (file.hash) { - IMPORT_SCANNER_CACHE.set(file.hash, result); + if (fileHash) { + IMPORT_SCANNER_CACHE.set(fileHash, result); } return result; diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index 3ae3104dab..850db04166 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -83,19 +83,19 @@ export class IsopackCache { // Wipe specific packages. for (const packageName of packages) { if (self.cacheDir) { - await files.rm_recursive(self._isopackDir(packageName)); + await files.rm_recursive_deferred(self._isopackDir(packageName)); } if (self._pluginCacheDirRoot) { - await files.rm_recursive(self._pluginCacheDirForPackage(packageName)); + await files.rm_recursive_deferred(self._pluginCacheDirForPackage(packageName)); } } } else { // Wipe all packages. if (self.cacheDir) { - await files.rm_recursive(self.cacheDir); + await files.rm_recursive_deferred(self.cacheDir); } if (self._pluginCacheDirRoot) { - await files.rm_recursive(self._pluginCacheDirRoot); + await files.rm_recursive_deferred(self._pluginCacheDirRoot); } } } @@ -351,7 +351,7 @@ export class IsopackCache { } else { // Nope! Compile it again. Give it a fresh plugin cache. if (pluginCacheDir) { - await files.rm_recursive(pluginCacheDir); + await files.rm_recursive_deferred(pluginCacheDir); files.mkdir_p(pluginCacheDir); } diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 99549c5d25..aa397e0a80 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -1,3 +1,5 @@ +import { getMeteorConfig } from "../tool-env/meteor-config"; + var compiler = require('./compiler.js'); var archinfo = require('../utils/archinfo'); var _ = require('underscore'); @@ -514,6 +516,9 @@ Object.assign(Isopack.prototype, { var Plugin = { name: pluginName, + // Share the meteorConfig object as part of plugin API + getMeteorConfig: getMeteorConfig, + // 'extension' is a file extension without the separation dot // (eg 'js', 'coffee', 'coffee.md') // diff --git a/tools/isobuild/js-analyze.js b/tools/isobuild/js-analyze.js index 35a929c083..2de961be63 100644 --- a/tools/isobuild/js-analyze.js +++ b/tools/isobuild/js-analyze.js @@ -4,6 +4,7 @@ import LRUCache from "lru-cache"; import { Profile } from '../tool-env/profile'; import Visitor from "@meteorjs/reify/lib/visitor.js"; import { findPossibleIndexes } from "@meteorjs/reify/lib/utils.js"; +import acorn from 'acorn'; const hasOwn = Object.prototype.hasOwnProperty; const objToStr = Object.prototype.toString @@ -15,7 +16,9 @@ function isRegExp(value) { var AST_CACHE = new LRUCache({ max: Math.pow(2, 12), length(ast) { - return ast.loc.end.line; + // Estimate cached lines based on average length per character + const avgCharsPerLine = 40; + return Math.ceil(ast.end / avgCharsPerLine); } }); @@ -28,20 +31,32 @@ function tryToParse(source, hash) { let ast; try { Profile.time('jsAnalyze.parse', () => { - ast = parse(source, { - strictMode: false, - sourceType: 'module', - allowImportExportEverywhere: true, - allowReturnOutsideFunction: true, - allowUndeclaredExports: true, - plugins: [ - // Only plugins for stage 3 features are enabled - // Enabling some plugins significantly affects parser performance - 'importAttributes', - 'explicitResourceManagement', - 'decorators' - ] - }); + try { + ast = acorn.parse(source, { + ecmaVersion: 'latest', + sourceType: 'script', + allowAwaitOutsideFunction: true, + allowImportExportEverywhere: true, + allowReturnOutsideFunction: true, + allowHashBang: true, + checkPrivateFields: false, + }); + } catch (error) { + ast = parse(source, { + strictMode: false, + sourceType: 'module', + allowImportExportEverywhere: true, + allowReturnOutsideFunction: true, + allowUndeclaredExports: true, + plugins: [ + // Only plugins for stage 3 features are enabled + // Enabling some plugins significantly affects parser performance + 'importAttributes', + 'explicitResourceManagement', + 'decorators' + ] + }); + } }); } catch (e) { if (typeof e.loc === 'object') { diff --git a/tools/isobuild/meteor-npm.js b/tools/isobuild/meteor-npm.js index f051abc634..d1ebe42341 100644 --- a/tools/isobuild/meteor-npm.js +++ b/tools/isobuild/meteor-npm.js @@ -87,7 +87,7 @@ meteorNpm.updateDependencies = async function (packageName, // It didn't exist, which is exactly what we wanted. return false; } - await files.rm_recursive(newPackageNpmDir); + await files.rm_recursive_deferred(newPackageNpmDir); return false; } @@ -102,7 +102,7 @@ meteorNpm.updateDependencies = async function (packageName, // proceed. if (files.exists(packageNpmDir) && ! files.exists(files.pathJoin(packageNpmDir, 'npm-shrinkwrap.json'))) { - await files.rm_recursive(packageNpmDir); + await files.rm_recursive_deferred(packageNpmDir); } // with the changes on npm 8, where there were changes to how the packages metadata is given @@ -114,7 +114,7 @@ meteorNpm.updateDependencies = async function (packageName, files.pathJoin(packageNpmDir, 'npm-shrinkwrap.json') )); if (shrinkwrap.lockfileVersion !== LOCK_FILE_VERSION) { - await files.rm_recursive(packageNpmDir); + await files.rm_recursive_deferred(packageNpmDir); } } catch (e) {} } @@ -143,7 +143,7 @@ meteorNpm.updateDependencies = async function (packageName, throw e; } finally { if (files.exists(newPackageNpmDir)) { - await files.rm_recursive(newPackageNpmDir); + await files.rm_recursive_deferred(newPackageNpmDir); } tmpDirs = _.without(tmpDirs, newPackageNpmDir); } @@ -384,7 +384,7 @@ Profile("meteorNpm.rebuildIfNonPortable", async function (nodeModulesDir) { const rebuildResult = await runNpmCommand(getRebuildArgs(), tempDir); if (! rebuildResult.success) { buildmessage.error(rebuildResult.error); - await files.rm_recursive(tempDir); + await files.rm_recursive_deferred(tempDir); return false; } @@ -420,7 +420,7 @@ Profile("meteorNpm.rebuildIfNonPortable", async function (nodeModulesDir) { await files.renameDirAlmostAtomically(tempPkgDirs[pkgPath], pkgPath); } - await files.rm_recursive(tempDir); + await files.rm_recursive_deferred(tempDir); return true; }); @@ -644,7 +644,7 @@ var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir, } if (oldNodeVersion !== currentNodeCompatibilityVersion()) { - await files.rm_recursive(nodeModulesDir); + await files.rm_recursive_deferred(nodeModulesDir); } } diff --git a/tools/meteor-services/stats.js b/tools/meteor-services/stats.js index f324df7a34..c9c8439d72 100644 --- a/tools/meteor-services/stats.js +++ b/tools/meteor-services/stats.js @@ -22,7 +22,7 @@ var packageList = function (projectContext) { name: name, version: info.version, local: info.kind === 'local', - direct: !! projectContext.projectConstraintsFile.getConstraint(name) + direct: !!projectContext.projectConstraintsFile.getConstraint(name) }); }); return versions; @@ -39,7 +39,8 @@ var recordPackages = async function (options) { // Before doing anything, look at the app's dependencies to see if the // opt-out package is there; if present, we don't record any stats. var packages = packageList(options.projectContext); - if (_.findWhere(packages, { name: OPT_OUT_PACKAGE_NAME })) { + if (_.findWhere(packages, { name: OPT_OUT_PACKAGE_NAME }) || + process.env.DO_NOT_TRACK) { // Print some output for the 'report-stats' self-test. if (process.env.METEOR_PACKAGE_STATS_TEST_OUTPUT) { process.stdout.write("PACKAGE STATS NOT SENT\n"); @@ -86,9 +87,9 @@ var recordPackages = async function (options) { } var result = await conn.call("recordAppPackages", - appIdentifier, - packages, - details); + appIdentifier, + packages, + details); // If the stats server sent us a new session, save it for use on // subsequent requests. @@ -112,7 +113,7 @@ var recordPackages = async function (options) { var logErrorIfInCheckout = function (err) { if ((Console.isInteractive() && files.inCheckout()) - || process.env.METEOR_PACKAGE_STATS_TEST_OUTPUT) { + || process.env.METEOR_PACKAGE_STATS_TEST_OUTPUT) { Console.warn("Failed to record package usage."); Console.warn( "(This error is hidden when you are not running Meteor from a", @@ -145,8 +146,8 @@ var getPackagesForAppIdInTest = function (projectContext) { var connectToPackagesStatsServer = async function () { const sc = new ServiceConnection( - config.getPackageStatsServerUrl(), - {_dontPrintErrors: true} + config.getPackageStatsServerUrl(), + { _dontPrintErrors: true } ); await sc.init(); diff --git a/tools/node-process-warnings.js b/tools/node-process-warnings.js new file mode 100644 index 0000000000..9dbc7919dd --- /dev/null +++ b/tools/node-process-warnings.js @@ -0,0 +1,22 @@ +const originalEmitWarning = process.emitWarning; + +process.emitWarning = function (message) { + /* + * A warning was introduced in Node 22: + * + * "The `punycode` module is deprecated. Please use a userland alternative instead." + * + * The problem is that punycode is deeply integrated in the Node system. It's not a + * simple direct dependency. + * + * Check these issues for more details: + * https://github.com/mathiasbynens/punycode.js/issues/137 + * https://stackoverflow.com/questions/68774489/punycode-is-deprecated-in-npm-what-should-i-replace-it-with/78946745 + * + * This warning was, besides being annoying, breaking our tests. + */ + if (message.includes("punycode")) { + return; + } + return originalEmitWarning(message); +}; diff --git a/tools/packaging/catalog/catalog-remote.js b/tools/packaging/catalog/catalog-remote.js index e40be5086c..85b0fefc12 100644 --- a/tools/packaging/catalog/catalog-remote.js +++ b/tools/packaging/catalog/catalog-remote.js @@ -441,9 +441,9 @@ Object.assign(Table.prototype, { return "(" + _.times(n, function () { return "?" }).join(",") + ")"; }, - find: async function (txn, id) { + find: async function (db, id) { var self = this; - var rows = await txn.query(self._selectQuery, [ id ]); + var rows = await db._query(self._selectQuery, [ id ]); if (rows.length !== 0) { if (rows.length !== 1) { throw new Error("Corrupt database (PK violation)"); @@ -813,23 +813,23 @@ Object.assign(RemoteCatalog.prototype, { // track, sorted by their orderKey. Returns the empty array if the release // track does not exist or does not have any recommended versions. getSortedRecommendedReleaseRecords: async function (track, laterThanOrderKey) { - var self = this; - // XXX releaseVersions content objects are kinda big; if we put - // 'recommended' and 'orderKey' in their own columns this could be faster - var result = await self._contentQuery( - "SELECT content FROM releaseVersions WHERE track=?", track); + const hasMinKey = laterThanOrderKey != null; - var recommended = _.filter(result, function (v) { - if (!v.recommended) - return false; - return !laterThanOrderKey || v.orderKey > laterThanOrderKey; - }); + // Always use JSON1 to filter & sort directly in SQL + const sql = ` + SELECT content + FROM releaseVersions + WHERE track = ? + AND json_extract(content, '$.recommended') = 1 + ${hasMinKey ? "AND json_extract(content, '$.orderKey') > ?" : ""} + ORDER BY json_extract(content, '$.orderKey') DESC + `; + const params = hasMinKey + ? [track, laterThanOrderKey] + : [track]; - var recSort = _.sortBy(recommended, function (rec) { - return rec.orderKey; - }); - recSort.reverse(); - return recSort; + // _contentQuery will JSON.parse(content) for you + return this._contentQuery(sql, params); }, // Given a release track, returns all version records for this track. @@ -893,9 +893,7 @@ Object.assign(RemoteCatalog.prototype, { // No JSON parsing is performed. _columnsQuery: async function (query, params) { var self = this; - var rows = await self.db.runInTransaction(function (txn) { - return txn.query(query, params); - }); + var rows = await self.db._query(query, params); return rows; }, @@ -946,9 +944,7 @@ Object.assign(RemoteCatalog.prototype, { getMetadata: async function(key) { var self = this; - var row = await self.db.runInTransaction(function (txn) { - return self.tableMetadata.find(txn, key); - }); + var row = await self.tableMetadata.find(self.db, key); if (row) { return JSON.parse(row['content']); } @@ -970,9 +966,7 @@ Object.assign(RemoteCatalog.prototype, { shouldShowBanner: async function (releaseName, bannerDate) { var self = this; - var row = await self.db.runInTransaction(function (txn) { - return self.tableBannersShown.find(txn, releaseName); - }); + var row = await self.tableBannersShown.find(self.db, releaseName); // We've never printed a banner for this release. if (! row) return true; diff --git a/tools/packaging/package-client.js b/tools/packaging/package-client.js index 0a10007e06..c79a2463df 100644 --- a/tools/packaging/package-client.js +++ b/tools/packaging/package-client.js @@ -466,7 +466,7 @@ exports.handlePackageServerConnectionError = function (error) { }; -// Update the package metdata in the server catalog. Chane the docs, +// Update the package metadata in the server catalog. Change the docs, // descriptions and the Git URL to new values. // // options: @@ -537,7 +537,7 @@ exports.updatePackageMetadata = async function (options) { // Upload the new Readme. await buildmessage.enterJob('uploading documentation', async function () { - var readmePath = saveReadmeToTmp(readmeInfo); + var readmePath = await saveReadmeToTmp(readmeInfo); var uploadInfo = await callPackageServerBM(conn, "createReadme", versionIdentifier); if (!uploadInfo) { diff --git a/tools/project-context.js b/tools/project-context.js index 60f5ae794a..f114f3a38a 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -1,3 +1,4 @@ +import { normalizeModernConfig, setMeteorConfig } from "./tool-env/meteor-config"; var assert = require("assert"); var _ = require('underscore'); @@ -318,7 +319,7 @@ Object.assign(ProjectContext.prototype, { * * @return {Promise<*|undefined>} */ - readProjectMetadata: function () { + readProjectMetadata: async function () { // don't generate a profiling report for this stage (Profile.run), // because all we do here is read a handful of files. return this._completeStagesThrough(STAGE.READ_PROJECT_METADATA); @@ -328,7 +329,7 @@ Object.assign(ProjectContext.prototype, { * @return {Promise<*|undefined>} */ initializeCatalog: function () { - return Profile.run('ProjectContext initializeCatalog', () => { + return Profile.run('ProjectContext initializeCatalog', async () => { return this._completeStagesThrough(STAGE.INITIALIZE_CATALOG); }); }, @@ -337,7 +338,7 @@ Object.assign(ProjectContext.prototype, { * @return {Promise<*|undefined>} */ resolveConstraints: function () { - return Profile.run('ProjectContext resolveConstraints', () => { + return Profile.run('ProjectContext resolveConstraints', async () => { return this._completeStagesThrough(STAGE.RESOLVE_CONSTRAINTS); }); }, @@ -347,7 +348,7 @@ Object.assign(ProjectContext.prototype, { * @return {Promise<*|undefined>} */ downloadMissingPackages: function () { - return Profile.run('ProjectContext downloadMissingPackages', () => { + return Profile.run('ProjectContext downloadMissingPackages', async () => { return this._completeStagesThrough(STAGE.DOWNLOAD_MISSING_PACKAGES); }); }, @@ -356,7 +357,7 @@ Object.assign(ProjectContext.prototype, { * @return {Promise<*|undefined>} */ buildLocalPackages: function () { - return Profile.run('ProjectContext buildLocalPackages', () => { + return Profile.run('ProjectContext buildLocalPackages', async () => { return this._completeStagesThrough(STAGE.BUILD_LOCAL_PACKAGES); }); }, @@ -365,7 +366,7 @@ Object.assign(ProjectContext.prototype, { * @return {Promise<*|undefined>} */ saveChangedMetadata: function () { - return Profile.run('ProjectContext saveChangedMetadata', () => { + return Profile.run('ProjectContext saveChangedMetadata', async () => { return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, @@ -376,7 +377,7 @@ Object.assign(ProjectContext.prototype, { prepareProjectForBuild: function () { // This is the same as saveChangedMetadata, but if we insert stages after // that one it will continue to mean "fully finished". - return Profile.run('ProjectContext prepareProjectForBuild', () => { + return Profile.run('ProjectContext prepareProjectForBuild', async () => { return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, @@ -492,6 +493,8 @@ Object.assign(ProjectContext.prototype, { self.meteorConfig = new MeteorConfig({ appDirectory: self.projectDir, }); + self.meteorConfig._ensureInitialized(); + if (buildmessage.jobHasMessages()) { return; } @@ -1217,7 +1220,16 @@ Object.assign(exports.ProjectConstraintsFile.prototype, { constraint: constraintToAdd, trailingSpaceAndComment: '' }; - self._constraintLines.push(lineRecord); + if (constraintToAdd.package === 'npm-mongo-legacy') { + const mongoIdx = self._constraintLines.findIndex(lr => lr.constraint && lr.constraint.package === 'mongo'); + if (mongoIdx > -1) { + self._constraintLines.splice(mongoIdx, 0, lineRecord); + } else { + self._constraintLines.push(lineRecord); + } + } else { + self._constraintLines.push(lineRecord); + } self._constraintMap[constraintToAdd.package] = lineRecord; self._modified = true; return; @@ -1664,7 +1676,7 @@ Object.assign(exports.ReleaseFile.prototype, { if (this.isCheckout()) { // Only create .meteor/local/dev_bundle if .meteor/release refers to // an actual release, and remove it otherwise. - await files.rm_recursive(devBundleLink); + await files.rm_recursive_deferred(devBundleLink); return; } @@ -1811,6 +1823,13 @@ export class MeteorConfig { }, }), } : this._config; + const modernForced = JSON.parse(process.env.METEOR_MODERN || "false"); + // Reinitialize meteorConfig globally for project context + // Updates config when package.json changes trigger rebuilds + setMeteorConfig({ + ...(this._config || {}), + modern: normalizeModernConfig(modernForced || this._config?.modern || false), + }); return this._config; } @@ -1818,16 +1837,14 @@ export class MeteorConfig { // General utility for querying the "meteor" section of package.json. // TODO Implement an API for setting these values? get(...keys) { - let config = this._ensureInitialized(); - if (config) { - keys.every(key => { - if (config && _.has(config, key)) { - config = config[key]; - return true; - } - }); - return config; - } + const config = this._ensureInitialized(); + if (!config) return undefined; + + return keys.reduce((cur, key) => { + return (cur != null && _.has(cur, key)) + ? cur[key] + : undefined; + }, config); } getNodeModulesToRecompileByArch() { diff --git a/tools/runners/run-app.js b/tools/runners/run-app.js index 9f5bb79ecd..9d92b57780 100644 --- a/tools/runners/run-app.js +++ b/tools/runners/run-app.js @@ -1,4 +1,3 @@ -var _ = require('underscore'); var files = require('../fs/files'); var watch = require('../fs/watch'); var bundler = require('../isobuild/bundler.js'); @@ -11,15 +10,15 @@ var Profile = require('../tool-env/profile').Profile; var release = require('../packaging/release.js'); import { pluginVersionsFromStarManifest } from '../cordova/index.js'; import { closeAllWatchers } from "../fs/safe-watcher"; -import { eachline } from "../utils/eachline"; import { loadIsopackage } from '../tool-env/isopackets.js'; +import { eachline } from "../utils/eachline"; // Parse out s as if it were a bash command line. var bashParse = function (s) { if (s.search("\"") !== -1 || s.search("'") !== -1) { throw new Error("Meteor cannot currently handle quoted SERVER_NODE_OPTIONS"); } - return _.without(s.split(/\s+/), ''); + return s.split(/\s+/).filter(Boolean); }; var getNodeOptionsFromEnvironment = function () { @@ -238,7 +237,7 @@ Object.assign(AppProcess.prototype, { files.pathJoin(self.bundlePath, 'main.js')); // Setting options - var opts = _.clone(self.nodeOptions); + var opts = JSON.parse(JSON.stringify(self.nodeOptions)); if (self.inspect) { // Always use --inspect rather than --inspect-brk, even when @@ -251,6 +250,8 @@ Object.assign(AppProcess.prototype, { opts.push("--inspect=" + self.inspect.port); } + opts.push(`--require=${files.convertToOSPath(files.pathJoin(__dirname, '../node-process-warnings.js'))}`) + opts.push(entryPoint); // Call node @@ -575,7 +576,7 @@ Object.assign(AppRunner.prototype, { if (self.recordPackageUsage) { // Maybe this doesn't need to be awaited for? - await stats.recordPackages({ + stats.recordPackages({ what: "sdk.run", projectContext: self.projectContext }); @@ -912,7 +913,7 @@ Object.assign(AppRunner.prototype, { var oldPromise = self.runPromise = self._makePromise("run"); - await refreshClient(); + refreshClient(); // Establish a watcher on the new files. setupClientWatcher(); diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index ea319eb0de..c9a7de7334 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -1,3 +1,4 @@ +import { loadIsopackage } from '../tool-env/isopackets.js'; import { MongoExitCodes } from '../utils/mongo-exit-codes'; var files = require('../fs/files'); var utils = require('../utils/utils.js'); @@ -5,7 +6,6 @@ var fiberHelpers = require('../utils/fiber-helpers.js'); var runLog = require('./run-log.js'); var child_process = require('child_process'); var _ = require('underscore'); -import { loadIsopackage } from '../tool-env/isopackets.js'; var Console = require('../console/console.js').Console; // Given a Mongo URL, open an interactive Mongo shell on this terminal @@ -28,6 +28,17 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { mongodPath = files.convertToOSPath(mongodPath); dbPath = files.convertToOSPath(dbPath); + // Because we're spawning the process with shell: true on win32, the + // command string and args array are effectively concatenated to + // a single string and passed into the shell. If any of those paths + // contain spaces, these will get broken up on white-spaces and treated + // as separate tokens. If they are quoted, they will be parsed + // correctly by the shell. + if (process.platform === 'win32') { + mongodPath = '"' + mongodPath + '"' + dbPath = '"' + dbPath + '"' + } + let args = [ // nb: cli-test.sh and findMongoPids make strong assumptions about the // order of the arguments! Check them before changing any arguments. @@ -51,11 +62,6 @@ function spawnMongod(mongodPath, port, dbPath, replSetName) { args.push('--storageEngine', 'mmapv1', '--smallfiles'); } - // run with rosetta on mac m1 - if (process.platform === 'darwin' && process.arch === 'arm64') { - args = ['-x86_64', mongodPath, ...args]; - mongodPath = 'arch'; - } return child_process.spawn(mongodPath, args, { // Apparently in some contexts, Mongo crashes if your locale isn't set up // right. I wasn't able to reproduce it, but many people on #4019 @@ -448,11 +454,7 @@ var launchMongo = async function(options) { var yieldingMethod = async function(object, methodName, ...args) { return await Promise.race([ stopPromise, - new Promise((resolve, reject) => { - object[methodName](...args, (err, res) => { - err ? reject(err) : resolve(res); - }); - }), + object[methodName](...args), ]); }; @@ -472,7 +474,7 @@ var launchMongo = async function(options) { if (options.multiple) { // This is only for testing, so we're OK with incurring the replset // setup on each startup. - await files.rm_recursive(dbPath); + await files.rm_recursive_deferred(dbPath); files.mkdir_p(dbPath, 0o755); } else if (portFile) { var portFileExists = false; diff --git a/tools/static-assets/server/boot.js b/tools/static-assets/server/boot.js index e6d0a7eb89..43c2668bde 100644 --- a/tools/static-assets/server/boot.js +++ b/tools/static-assets/server/boot.js @@ -498,12 +498,10 @@ var runMain = Profile("Run main()", async function () { global.__METEOR_ASYNC_LOCAL_STORAGE = new AsyncLocalStorage(); } - await Profile.run('Server startup', function() { - return global.__METEOR_ASYNC_LOCAL_STORAGE.run({}, async () => { - await loadServerBundles(); - await callStartupHooks(); - await runMain(); - }); + await Profile.run('Server startup', async function() { + await loadServerBundles(); + await callStartupHooks(); + await runMain(); }); })().catch(e => { console.log('error on boot.js', e ) diff --git a/tools/static-assets/server/npm-rebuild.js b/tools/static-assets/server/npm-rebuild.js index e0a5624d3c..ca1a3be7bf 100644 --- a/tools/static-assets/server/npm-rebuild.js +++ b/tools/static-assets/server/npm-rebuild.js @@ -28,11 +28,10 @@ var binDir = path.dirname(process.execPath); process.env.PATH = binDir + path.delimiter + process.env.PATH; var npmCmd = "npm"; +var shell = false; if (process.platform === "win32") { - var npmCmdPath = path.join(binDir, "npm.cmd"); - if (fs.existsSync(npmCmdPath)) { - npmCmd = npmCmdPath; - } + npmCmd = "npm.cmd"; + shell = true; } function rebuild(i) { @@ -41,7 +40,8 @@ function rebuild(i) { if (! dir) { // Print Node/V8/etc. versions for diagnostic purposes. spawn(npmCmd, ["version", "--json"], { - stdio: "inherit" + stdio: "inherit", + shell, }); return; @@ -49,7 +49,8 @@ function rebuild(i) { spawn(npmCmd, rebuildArgs, { cwd: path.join(__dirname, dir), - stdio: "inherit" + stdio: "inherit", + shell, }).on("exit", function (code) { if (code !== 0) { process.exit(code); diff --git a/tools/static-assets/server/runtime.js b/tools/static-assets/server/runtime.js index 2b0f800940..3c15a6f9d1 100644 --- a/tools/static-assets/server/runtime.js +++ b/tools/static-assets/server/runtime.js @@ -47,14 +47,27 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { }; const reifyVersion = require("@meteorjs/reify/package.json").version; + const reifyAcornParse = require("@meteorjs/reify/lib/parsers/acorn").parse; const reifyBabelParse = require("@meteorjs/reify/lib/parsers/babel").parse; const reifyCompile = require("@meteorjs/reify/lib/compiler").compile; function compileContent (content) { let identical = true; + let result; try { - const result = reifyCompile(content, { + result = reifyCompile(content, { + parse: reifyAcornParse, + generateLetDeclarations: false, + ast: false, + }); + if (!result.identical) { + identical = false; + content = result.code; + } + } catch (acornError) { + // Fallback: Babel + result = reifyCompile(content, { parse: reifyBabelParse, generateLetDeclarations: false, ast: false, @@ -63,9 +76,9 @@ module.exports = function enable ({ cachePath, createLoader = true } = {}) { identical = false; content = result.code; } - } finally { - return { content, identical }; } + + return { content, identical }; } const _compile = Mp._compile; diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index 9bf478f474..8af983a7cb 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -11,8 +11,9 @@ "@apollo/client": "^3.9.2", "@apollo/server": "^4.10.0", "@babel/runtime": "^7.23.9", + "@swc/helpers": "^0.5.17", "graphql": "^16.8.1", - "meteor-node-stubs": "^1.2.10", + "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -21,6 +22,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-bare/package.json b/tools/static-assets/skel-bare/package.json index 7db49f3411..f2cb0b71a7 100644 --- a/tools/static-assets/skel-bare/package.json +++ b/tools/static-assets/skel-bare/package.json @@ -6,6 +6,6 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", - "meteor-node-stubs": "^1.2.10" + "meteor-node-stubs": "^1.2.12" } } diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index 2761af2093..e197964537 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -9,14 +9,16 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "jquery": "^3.7.1", - "meteor-node-stubs": "^1.2.10" + "meteor-node-stubs": "^1.2.12" }, "meteor": { "mainModule": { "client": "client/main.js", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index ce630e2918..99b3e13d76 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -9,13 +9,14 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "@chakra-ui/icons": "^1.1.7", "@chakra-ui/react": "^1.8.8", "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", "@react-icons/all-files": "^4.1.0", "framer-motion": "^6.4.2", - "meteor-node-stubs": "^1.2.10", + "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -24,6 +25,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index 28d1d84d2e..1abdbdd683 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -7,10 +7,18 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "jquery": "^3.7.1", - "meteor-node-stubs": "^1.2.10" + "meteor-node-stubs": "^1.2.12" }, "devDependencies": { "chai": "^4.2.0" + }, + "meteor": { + "mainModule": { + "client": "client/main.js", + "server": "server/main.js" + }, + "modern": true } } diff --git a/tools/static-assets/skel-minimal/.meteor/packages b/tools/static-assets/skel-minimal/.meteor/packages index d0998cd7ad..9f6770b9e9 100644 --- a/tools/static-assets/skel-minimal/.meteor/packages +++ b/tools/static-assets/skel-minimal/.meteor/packages @@ -13,6 +13,7 @@ ecmascript # Enable ECMAScript2015+ syntax in app code typescript # Enable TypeScript syntax in .ts and .tsx modules shell-server # Server-side component of the `meteor shell` command webapp # Serves a Meteor app over HTTP +ddp # The protocol and client/server libraries that Meteor uses to send data server-render # Support for server-side rendering hot-module-replacement # Rebuilds the client if there is a change on the client without restarting the server ~prototype~ diff --git a/tools/static-assets/skel-minimal/package.json b/tools/static-assets/skel-minimal/package.json index 187111707d..74d5a9b271 100644 --- a/tools/static-assets/skel-minimal/package.json +++ b/tools/static-assets/skel-minimal/package.json @@ -9,13 +9,15 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", - "meteor-node-stubs": "^1.2.10" + "@swc/helpers": "^0.5.17", + "meteor-node-stubs": "^1.2.12" }, "meteor": { "mainModule": { "client": "client/main.js", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index 9f5e7f9bc8..ee33ff775c 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -9,7 +9,8 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", - "meteor-node-stubs": "^1.2.10", + "@swc/helpers": "^0.5.17", + "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -18,6 +19,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-solid/.meteor/packages b/tools/static-assets/skel-solid/.meteor/packages index f00629b1a1..665679a90c 100644 --- a/tools/static-assets/skel-solid/.meteor/packages +++ b/tools/static-assets/skel-solid/.meteor/packages @@ -19,4 +19,4 @@ hot-module-replacement # Update client in development without reloading the pag ~prototype~ static-html # Define static page content in .html files -jorgenvatle:vite-bundler +jorgenvatle:vite diff --git a/tools/static-assets/skel-solid/client/entry-meteor.js b/tools/static-assets/skel-solid/client/entry-meteor.js new file mode 100644 index 0000000000..0ec3ddc121 --- /dev/null +++ b/tools/static-assets/skel-solid/client/entry-meteor.js @@ -0,0 +1,14 @@ +/** + * Entrypoint for the Meteor client + * + * Generally, this file can be left empty. Vite will add imports for + * lazy-loaded Meteor packages to this file to ensure they aren't omitted from + * the final production bundle. + * + * Use ./main.js as the primary entrypoint for your client code to take full + * advantage of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports which Vite doesn't support. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-solid/client/main.js b/tools/static-assets/skel-solid/client/main.js new file mode 100644 index 0000000000..44bcc64d55 --- /dev/null +++ b/tools/static-assets/skel-solid/client/main.js @@ -0,0 +1 @@ +import '../imports/ui/main'; \ No newline at end of file diff --git a/tools/static-assets/skel-solid/client/main.jsx b/tools/static-assets/skel-solid/client/main.jsx deleted file mode 100644 index 97d382a9bd..0000000000 --- a/tools/static-assets/skel-solid/client/main.jsx +++ /dev/null @@ -1 +0,0 @@ -// main entry point is in imports/ui/main.jsx diff --git a/tools/static-assets/skel-solid/client/main.css b/tools/static-assets/skel-solid/imports/ui/main.css similarity index 100% rename from tools/static-assets/skel-solid/client/main.css rename to tools/static-assets/skel-solid/imports/ui/main.css diff --git a/tools/static-assets/skel-solid/imports/ui/main.jsx b/tools/static-assets/skel-solid/imports/ui/main.jsx index 99eb6c43d7..044f1aee69 100644 --- a/tools/static-assets/skel-solid/imports/ui/main.jsx +++ b/tools/static-assets/skel-solid/imports/ui/main.jsx @@ -2,6 +2,7 @@ import { render } from 'solid-js/web'; import { App } from './App'; import { Meteor } from "meteor/meteor"; +import './main.css'; Meteor.startup(() => { render(() => , document.getElementById('root')); diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index 7b575928e5..17401d9c1b 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -9,21 +9,24 @@ }, "dependencies": { "@babel/runtime": "^7.23.9", - "meteor-node-stubs": "^1.2.10", - "solid-js": "^1.8.15" + "@swc/helpers": "^0.5.17", + "meteor-node-stubs": "^1.2.12", + "picocolors": "^1.1.1", + "solid-js": "^1.9.4" }, "meteor": { "mainModule": { - "client": "client/main.jsx", - "server": "server/main.js" + "client": "client/entry-meteor.js", + "server": "server/entry-meteor.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true }, "devDependencies": { "babel-preset-solid": "^1.8.15", - "meteor-vite": "^1.10.2", - "vite": "^4.5.2", - "vite-plugin-solid": "^2.10.1", - "vite-plugin-solid-svg": "^0.8.0" + "meteor-vite": "^3.2.1", + "vite": "^6.0.11", + "vite-plugin-solid": "^2.11.0", + "vite-plugin-solid-svg": "^0.8.1" } } diff --git a/tools/static-assets/skel-solid/server/entry-meteor.js b/tools/static-assets/skel-solid/server/entry-meteor.js new file mode 100644 index 0000000000..8a066f8e94 --- /dev/null +++ b/tools/static-assets/skel-solid/server/entry-meteor.js @@ -0,0 +1,12 @@ +/** + * Entrypoint for the Meteor server + * Generally, this file can be left empty. Vite will add imports for your app's + * server bundle here during both development and production build. + * + * Use ./main.js as the primary entrypoint for your app to take full advantage + * of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-solid/vite.config.js b/tools/static-assets/skel-solid/vite.config.js deleted file mode 100644 index c49fa70b16..0000000000 --- a/tools/static-assets/skel-solid/vite.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vite'; -import solidPlugin from 'vite-plugin-solid'; -import solidSvg from "vite-plugin-solid-svg"; - -export default defineConfig({ - plugins: [solidPlugin(), solidSvg({ - defaultExport: 'component', - })], - meteor: { - clientEntry: 'imports/ui/main.jsx', - }, -}); diff --git a/tools/static-assets/skel-solid/vite.config.mjs b/tools/static-assets/skel-solid/vite.config.mjs new file mode 100644 index 0000000000..e0215dbb10 --- /dev/null +++ b/tools/static-assets/skel-solid/vite.config.mjs @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import solidSvg from "vite-plugin-solid-svg"; +import { meteor } from 'meteor-vite/plugin'; + +export default defineConfig({ + plugins: [ + solidPlugin(), + solidSvg({ defaultExport: 'component' }), + meteor({ + clientEntry: 'client/main.js', + serverEntry: 'server/main.js', + enableExperimentalFeatures: true, + stubValidation: { + ignorePackages: ['meteor/mongo'], + }, + }), + ], +}); diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index 1be6779d00..0929124049 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -9,7 +9,8 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", - "meteor-node-stubs": "^1.2.10", + "@swc/helpers": "^0.5.17", + "meteor-node-stubs": "^1.2.12", "svelte": "^3.59.2" }, "devDependencies": { @@ -27,6 +28,7 @@ ] } }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index e738792743..73971202ba 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -9,8 +9,9 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", + "@swc/helpers": "^0.5.17", "autoprefixer": "^10.4.4", - "meteor-node-stubs": "^1.2.10", + "meteor-node-stubs": "^1.2.12", "postcss": "^8.4.12", "postcss-load-config": "^3.1.4", "react": "^17.0.2", @@ -22,6 +23,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, - "testModule": "tests/main.js" + "testModule": "tests/main.js", + "modern": true } } diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index 0746e8368a..77ff7934bf 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -9,13 +9,14 @@ }, "dependencies": { "@babel/runtime": "^7.23.5", - "meteor-node-stubs": "^1.2.10", + "@swc/helpers": "^0.5.17", + "meteor-node-stubs": "^1.2.12", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/mocha": "^8.2.3", - "@types/node": "^18.16.5", + "@types/node": "^22.10.6", "@types/react": "^18.2.5", "@types/react-dom": "^18.2.4", "typescript": "^5.4.5" @@ -25,6 +26,7 @@ "client": "client/main.tsx", "server": "server/main.ts" }, - "testModule": "tests/main.ts" + "testModule": "tests/main.ts", + "modern": true } } diff --git a/tools/static-assets/skel-vue/.meteor/packages b/tools/static-assets/skel-vue/.meteor/packages index 3ae6a18b5f..b5e90db564 100644 --- a/tools/static-assets/skel-vue/.meteor/packages +++ b/tools/static-assets/skel-vue/.meteor/packages @@ -18,6 +18,5 @@ shell-server # Server-side component of the `meteor shell` com hot-module-replacement # Update client in development without reloading the page static-html # Define static page content in .html files -jorgenvatle:vite-bundler +jorgenvatle:vite ~prototype~ - diff --git a/tools/static-assets/skel-vue/client/entry-meteor.js b/tools/static-assets/skel-vue/client/entry-meteor.js new file mode 100644 index 0000000000..0ec3ddc121 --- /dev/null +++ b/tools/static-assets/skel-vue/client/entry-meteor.js @@ -0,0 +1,14 @@ +/** + * Entrypoint for the Meteor client + * + * Generally, this file can be left empty. Vite will add imports for + * lazy-loaded Meteor packages to this file to ensure they aren't omitted from + * the final production bundle. + * + * Use ./main.js as the primary entrypoint for your client code to take full + * advantage of Vite's plugin and build system. + * + * This can also be a good place to put code that you don't want Vite to + * process, for example, if you run into a compatibility issue or need to use + * nested imports which Vite doesn't support. + */ \ No newline at end of file diff --git a/tools/static-assets/skel-vue/client/main.css b/tools/static-assets/skel-vue/client/main.css deleted file mode 100644 index b5c61c9567..0000000000 --- a/tools/static-assets/skel-vue/client/main.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/tools/static-assets/skel-vue/client/main.js b/tools/static-assets/skel-vue/client/main.js index 97d382a9bd..403d8a2d2a 100644 --- a/tools/static-assets/skel-vue/client/main.js +++ b/tools/static-assets/skel-vue/client/main.js @@ -1 +1 @@ -// main entry point is in imports/ui/main.jsx +import '../imports/ui/main' \ No newline at end of file diff --git a/tools/static-assets/skel-vue/imports/ui/App.vue b/tools/static-assets/skel-vue/imports/ui/App.vue index 7a775391cb..19a68a1ea1 100644 --- a/tools/static-assets/skel-vue/imports/ui/App.vue +++ b/tools/static-assets/skel-vue/imports/ui/App.vue @@ -1,5 +1,5 @@