mirror of
https://github.com/meteor/meteor.git
synced 2026-01-09 07:38:15 -05:00
Merge branch 'devel' into patch-2
This commit is contained in:
@@ -76,10 +76,10 @@ run_save_node_bin: &run_save_node_bin
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# This environment is set to every job (and the initial build).
|
# This environment is set to every job (and the initial build).
|
||||||
build_machine_environment: &build_machine_environment
|
build_machine_environment:
|
||||||
# Specify that we want an actual machine (ala Circle 1.0), not a Docker image.
|
&build_machine_environment # Specify that we want an actual machine (ala Circle 1.0), not a Docker image.
|
||||||
docker:
|
docker:
|
||||||
- image: meteor/circleci:2024.09.11-android-34-node-20
|
- image: meteor/circleci:2025.07.8-android-35-node-22
|
||||||
resource_class: large
|
resource_class: large
|
||||||
environment:
|
environment:
|
||||||
# This multiplier scales the waitSecs for selftests.
|
# This multiplier scales the waitSecs for selftests.
|
||||||
@@ -104,8 +104,8 @@ build_machine_environment: &build_machine_environment
|
|||||||
|
|
||||||
# These will be evaled before each command.
|
# These will be evaled before each command.
|
||||||
PRE_TEST_COMMANDS: |-
|
PRE_TEST_COMMANDS: |-
|
||||||
ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit.
|
ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit.
|
||||||
ulimit -a # Display all ulimit settings for transparency.
|
ulimit -a # Display all ulimit settings for transparency.
|
||||||
|
|
||||||
# This is only to make Meteor self-test not remind us that we can set
|
# This is only to make Meteor self-test not remind us that we can set
|
||||||
# this argument for self-tests.
|
# this argument for self-tests.
|
||||||
@@ -115,6 +115,9 @@ build_machine_environment: &build_machine_environment
|
|||||||
NUM_GROUPS: 12
|
NUM_GROUPS: 12
|
||||||
RUNNING_AVG_LENGTH: 6
|
RUNNING_AVG_LENGTH: 6
|
||||||
|
|
||||||
|
# Force modern bundler test
|
||||||
|
METEOR_MODERN: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Get Ready:
|
Get Ready:
|
||||||
<<: *build_machine_environment
|
<<: *build_machine_environment
|
||||||
@@ -178,7 +181,7 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
eval $PRE_TEST_COMMANDS;
|
eval $PRE_TEST_COMMANDS;
|
||||||
cd dev_bundle/lib
|
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.
|
# Ensure that meteor/tools has no TypeScript errors.
|
||||||
../../meteor npm install -g typescript
|
../../meteor npm install -g typescript
|
||||||
cd ../../
|
cd ../../
|
||||||
@@ -753,7 +756,7 @@ jobs:
|
|||||||
Docs:
|
Docs:
|
||||||
docker:
|
docker:
|
||||||
# This Node version should match that in the meteor/docs CircleCI config.
|
# 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
|
resource_class: large
|
||||||
environment:
|
environment:
|
||||||
CHECKOUT_METEOR_DOCS: /home/circleci/test_docs
|
CHECKOUT_METEOR_DOCS: /home/circleci/test_docs
|
||||||
@@ -765,7 +768,9 @@ jobs:
|
|||||||
if [[ -n "$CIRCLE_PULL_REQUEST" ]]; then
|
if [[ -n "$CIRCLE_PULL_REQUEST" ]]; then
|
||||||
PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | sed 's|.*/pull/\([0-9]*\)|\1|')
|
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)
|
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
|
else
|
||||||
git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
|
git clone --branch $CIRCLE_BRANCH https://github.com/meteor/meteor.git ${CHECKOUT_METEOR_DOCS}
|
||||||
fi
|
fi
|
||||||
|
|||||||
66
.envrc
66
.envrc
@@ -15,6 +15,10 @@ function @meteor {
|
|||||||
"$ROOT_DIR/meteor" "$@"
|
"$ROOT_DIR/meteor" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function @get-ready {
|
||||||
|
@meteor --get-ready
|
||||||
|
}
|
||||||
|
|
||||||
function @test-packages {
|
function @test-packages {
|
||||||
TINYTEST_FILTER="$1" @meteor test-packages --exclude-archs=web.browser.legacy,web.cordova
|
TINYTEST_FILTER="$1" @meteor test-packages --exclude-archs=web.browser.legacy,web.cordova
|
||||||
}
|
}
|
||||||
@@ -23,6 +27,10 @@ function @test-self {
|
|||||||
@meteor self-test "$@"
|
@meteor self-test "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function @test-in-console {
|
||||||
|
"$ROOT_DIR/packages/test-in-console/run.sh" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
function @check-syntax {
|
function @check-syntax {
|
||||||
node "$ROOT_DIR/scripts/admin/check-legacy-syntax/check-syntax.js"
|
node "$ROOT_DIR/scripts/admin/check-legacy-syntax/check-syntax.js"
|
||||||
}
|
}
|
||||||
@@ -46,4 +54,60 @@ function @docs-start {
|
|||||||
|
|
||||||
function @docs-migration-start {
|
function @docs-migration-start {
|
||||||
npm run docs:dev --prefix "$ROOT_DIR/v3-docs/v3-migration-docs"
|
npm run docs:dev --prefix "$ROOT_DIR/v3-docs/v3-migration-docs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
192
.github/labeler.yml
vendored
192
.github/labeler.yml
vendored
@@ -1,124 +1,178 @@
|
|||||||
Project:Accounts:Password:
|
Project:Accounts:Password:
|
||||||
- packages/accounts-password/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: packages/accounts-password/**/*
|
||||||
|
|
||||||
Project:Accounts:UI:
|
Project:Accounts:UI:
|
||||||
- packages/meteor-developer-config-ui/**/*
|
- changed-files:
|
||||||
- packages/github-config-ui/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/google-config-ui/**/*
|
- packages/meteor-developer-config-ui/**/*
|
||||||
- packages/twitter-config-ui/**/*
|
- packages/github-config-ui/**/*
|
||||||
- packages/facebook-config-ui/**/*
|
- packages/google-config-ui/**/*
|
||||||
- packages/accounts-ui/**/*
|
- packages/twitter-config-ui/**/*
|
||||||
- packages/accounts-ui-unstyled/**/*
|
- packages/facebook-config-ui/**/*
|
||||||
|
- packages/accounts-ui/**/*
|
||||||
|
- packages/accounts-ui-unstyled/**/*
|
||||||
|
|
||||||
Project:CSS:
|
Project:CSS:
|
||||||
- packages/non-core/less/**/*
|
- changed-files:
|
||||||
- packages/minifier-css/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/standard-minifier-css/**/*
|
- packages/non-core/less/**/*
|
||||||
|
- packages/minifier-css/**/*
|
||||||
|
- packages/standard-minifier-css/**/*
|
||||||
|
|
||||||
Project:DDP:
|
Project:DDP:
|
||||||
- packages/ddp-common/**/*
|
- changed-files:
|
||||||
- packages/ddp-rate-limiter/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/ddp-server/**/*
|
- packages/ddp-common/**/*
|
||||||
- packages/ddp-client/**/*
|
- packages/ddp-rate-limiter/**/*
|
||||||
- packages/ddp/**/*
|
- packages/ddp-server/**/*
|
||||||
- packages/socket-stream-client/**/*
|
- packages/ddp-client/**/*
|
||||||
|
- packages/ddp/**/*
|
||||||
|
- packages/socket-stream-client/**/*
|
||||||
|
|
||||||
Project:EJSON:
|
Project:EJSON:
|
||||||
- packages/ejson/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: packages/ejson/**/*
|
||||||
|
|
||||||
Project:HMR:
|
Project:HMR:
|
||||||
- packages/hot-code-push/**/*
|
- changed-files:
|
||||||
- packages/hot-module-replacement/**/*
|
- any-glob-to-any-file:
|
||||||
|
- packages/hot-code-push/**/*
|
||||||
|
- packages/hot-module-replacement/**/*
|
||||||
|
|
||||||
Project:Isobuild:Minifiers:
|
Project:Isobuild:Minifiers:
|
||||||
- packages/minifier-css/**/*
|
- changed-files:
|
||||||
- packages/minifier-js/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/standard-minifier-js/**/*
|
- packages/minifier-css/**/*
|
||||||
- packages/standard-minifier-css/**/*
|
- packages/minifier-js/**/*
|
||||||
- packages/standard-minifiers/**/*
|
- packages/standard-minifier-js/**/*
|
||||||
|
- packages/standard-minifier-css/**/*
|
||||||
|
- packages/standard-minifiers/**/*
|
||||||
|
|
||||||
Project:Isobuild:
|
Project:Isobuild:
|
||||||
- tools/isobuild/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- tools/isobuild/**/*
|
||||||
|
|
||||||
Project:JS Environment:Typescript:
|
Project:JS Environment:Typescript:
|
||||||
- packages/typescript/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/typescript/**/*
|
||||||
|
|
||||||
Project:JS Environment:
|
Project:JS Environment:
|
||||||
- packages/babel-compiler/**/*
|
- changed-files:
|
||||||
- packages/babel-runtime/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/ecmascript/**/*
|
- packages/babel-compiler/**/*
|
||||||
- packages/ecmascript-runtime/**/*
|
- packages/babel-runtime/**/*
|
||||||
- packages/ecmascript-runtime-client/**/*
|
- packages/ecmascript/**/*
|
||||||
- packages/ecmascript-runtime-server/**/*
|
- packages/ecmascript-runtime/**/*
|
||||||
- packages/es5-shim/**/*
|
- packages/ecmascript-runtime-client/**/*
|
||||||
- packages/jshint/**/*
|
- packages/ecmascript-runtime-server/**/*
|
||||||
|
- packages/es5-shim/**/*
|
||||||
|
- packages/jshint/**/*
|
||||||
|
|
||||||
Project:Livequery:
|
Project:Livequery:
|
||||||
- packages/livedata/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/livedata/**/*
|
||||||
|
|
||||||
Project:Minimongo:
|
Project:Minimongo:
|
||||||
- packages/minimongo
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/minimongo
|
||||||
|
|
||||||
Project:Mobile:
|
Project:Mobile:
|
||||||
- tools/cordova/**/*
|
- changed-files:
|
||||||
- packages/launch-screen/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/mobile-experience/**/*
|
- tools/cordova/**/*
|
||||||
- packages/mobile-status-bar/**/*
|
- packages/launch-screen/**/*
|
||||||
|
- packages/mobile-experience/**/*
|
||||||
|
- packages/mobile-status-bar/**/*
|
||||||
|
|
||||||
Project:Mongo Driver:
|
Project:Mongo Driver:
|
||||||
- packages/mongo/**/*
|
- changed-files:
|
||||||
- packages/mongo-dev-server/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/mongo-id/**/*
|
- packages/mongo/**/*
|
||||||
- packages/mongo-livedata/**/*
|
- packages/mongo-dev-server/**/*
|
||||||
- packages/disable-oplog/**/*
|
- packages/mongo-id/**/*
|
||||||
- packages/non-core/mongo-decimal/**/*
|
- packages/mongo-livedata/**/*
|
||||||
|
- packages/disable-oplog/**/*
|
||||||
|
- packages/non-core/mongo-decimal/**/*
|
||||||
|
|
||||||
Project:NPM:
|
Project:NPM:
|
||||||
- npm-packages/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- npm-packages/**/*
|
||||||
|
|
||||||
Project:Release Process:
|
Project:Release Process:
|
||||||
- scripts/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- scripts/**/*
|
||||||
|
|
||||||
Project:Tool:
|
Project:Tool:
|
||||||
- tools/**/*
|
- changed-files:
|
||||||
- packages/meteor-tool/**/*
|
- any-glob-to-any-file:
|
||||||
|
- tools/**/*
|
||||||
|
- packages/meteor-tool/**/*
|
||||||
|
|
||||||
Project:Tool:Shell:
|
Project:Tool:Shell:
|
||||||
- tools/console/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- tools/console/**/*
|
||||||
|
|
||||||
Project:Utilities:Email:
|
Project:Utilities:Email:
|
||||||
- packages/email/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/email/**/*
|
||||||
|
|
||||||
Project:Utilities:HTTP:
|
Project:Utilities:HTTP:
|
||||||
- packages/deprecated/http/**/*
|
- changed-files:
|
||||||
- packages/fetch/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/url/**/*
|
- packages/deprecated/http/**/*
|
||||||
|
- packages/fetch/**/*
|
||||||
|
- packages/url/**/*
|
||||||
|
|
||||||
Project:Webapp:
|
Project:Webapp:
|
||||||
- packages/webapp/**/*
|
- changed-files:
|
||||||
- packages/webapp-hashing/**/*
|
- any-glob-to-any-file:
|
||||||
|
- packages/webapp/**/*
|
||||||
|
- packages/webapp-hashing/**/*
|
||||||
|
|
||||||
Project:Windows:
|
Project:Windows:
|
||||||
- scripts/windows/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- scripts/windows/**/*
|
||||||
|
|
||||||
Project:Webapp:Browser Policy:
|
Project:Webapp:Browser Policy:
|
||||||
- packages/browser-policy/**/*
|
- changed-files:
|
||||||
- packages/browser-policy-common/**/*
|
- any-glob-to-any-file:
|
||||||
- packages/browser-policy-content/**/*
|
- packages/browser-policy/**/*
|
||||||
- packages/browser-policy-framing/**/*
|
- packages/browser-policy-common/**/*
|
||||||
|
- packages/browser-policy-content/**/*
|
||||||
|
- packages/browser-policy-framing/**/*
|
||||||
|
|
||||||
Project:Examples:
|
Project:Examples:
|
||||||
- tools/cli/example-repositories.js
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- tools/cli/example-repositories.js
|
||||||
|
|
||||||
Project:Dynamic Import:
|
Project:Dynamic Import:
|
||||||
- packages/dynamic-import/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/dynamic-import/**/*
|
||||||
|
|
||||||
Project:Docs:
|
Project:Docs:
|
||||||
- docs/**/*
|
- changed-files:
|
||||||
- v3-docs/**/*
|
- any-glob-to-any-file:
|
||||||
|
- docs/**/*
|
||||||
|
- v3-docs/**/*
|
||||||
|
|
||||||
Project:Guide:
|
Project:Guide:
|
||||||
- guide/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- guide/**/*
|
||||||
|
|
||||||
github_actions:
|
github_actions:
|
||||||
- ./github/**/*
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- ./github/**/*
|
||||||
|
|||||||
198
.github/scripts/__tests__/inactive-issues.test.js
vendored
Normal file
198
.github/scripts/__tests__/inactive-issues.test.js
vendored
Normal file
@@ -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');
|
||||||
|
});
|
||||||
200
.github/scripts/inactive-issues.js
vendored
Normal file
200
.github/scripts/inactive-issues.js
vendored
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
2
.github/workflows/check-code-style.yml
vendored
2
.github/workflows/check-code-style.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 22.x
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Run ESLint@8
|
- name: Run ESLint@8
|
||||||
run: npx eslint@8 "./npm-packages/meteor-installer/**/*.js"
|
run: npx eslint@8 "./npm-packages/meteor-installer/**/*.js"
|
||||||
|
|||||||
3
.github/workflows/check-syntax.yml
vendored
3
.github/workflows/check-syntax.yml
vendored
@@ -1,6 +1,5 @@
|
|||||||
name: Check legacy syntax
|
name: Check legacy syntax
|
||||||
on:
|
on:
|
||||||
- push
|
|
||||||
- pull_request
|
- pull_request
|
||||||
jobs:
|
jobs:
|
||||||
check-code-style:
|
check-code-style:
|
||||||
@@ -9,7 +8,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 22.x
|
||||||
- run: cd scripts/admin/check-legacy-syntax && npm ci
|
- run: cd scripts/admin/check-legacy-syntax && npm ci
|
||||||
- name: Check syntax
|
- name: Check syntax
|
||||||
run: cd scripts/admin/check-legacy-syntax && node check-syntax.js
|
run: cd scripts/admin/check-legacy-syntax && node check-syntax.js
|
||||||
|
|||||||
2
.github/workflows/guide.yml
vendored
2
.github/workflows/guide.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 22.x
|
||||||
- name: Build the Guide
|
- name: Build the Guide
|
||||||
run: npm ci && npm run build
|
run: npm ci && npm run build
|
||||||
- name: Deploy to Netlify for preview
|
- name: Deploy to Netlify for preview
|
||||||
|
|||||||
24
.github/workflows/inactive-issues.yml
vendored
Normal file
24
.github/workflows/inactive-issues.yml
vendored
Normal file
@@ -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})
|
||||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -17,6 +17,6 @@ jobs:
|
|||||||
label:
|
label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v4
|
- uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|||||||
12
.github/workflows/meteor-selftest-windows.yml
vendored
12
.github/workflows/meteor-selftest-windows.yml
vendored
@@ -18,19 +18,27 @@ env:
|
|||||||
TIMEOUT_SCALE_FACTOR: 20
|
TIMEOUT_SCALE_FACTOR: 20
|
||||||
METEOR_HEADLESS: true
|
METEOR_HEADLESS: true
|
||||||
SELF_TEST_EXCLUDE: '^NULL-LEAVE-THIS-HERE-NULL$'
|
SELF_TEST_EXCLUDE: '^NULL-LEAVE-THIS-HERE-NULL$'
|
||||||
|
METEOR_MODERN: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: windows-2019-meteor
|
runs-on: windows-2019-meteor
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref }}-meteor-selftest-windows
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: cleanup
|
||||||
|
shell: powershell
|
||||||
|
run: Remove-Item -Recurse -Force ${{ github.workspace }}\*
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 22.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -45,7 +53,7 @@ jobs:
|
|||||||
.\scripts\windows\ci\test.ps1
|
.\scripts\windows\ci\test.ps1
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.\dev_bundle
|
.\dev_bundle
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 22.x
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|||||||
46
.github/workflows/run-profiler.yml
vendored
Normal file
46
.github/workflows/run-profiler.yml
vendored
Normal file
@@ -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 }}
|
||||||
52
.github/workflows/test-deprecated-packages.yml
vendored
Normal file
52
.github/workflows/test-deprecated-packages.yml
vendored
Normal file
@@ -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
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,3 +35,7 @@ packages/**/.npm
|
|||||||
|
|
||||||
# doc files should not be committed
|
# doc files should not be committed
|
||||||
packages/**/*.docs.js
|
packages/**/*.docs.js
|
||||||
|
|
||||||
|
#cursor
|
||||||
|
.cursorignore
|
||||||
|
.cursorrules
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ dist: jammy
|
|||||||
sudo: required
|
sudo: required
|
||||||
services: xvfb
|
services: xvfb
|
||||||
node_js:
|
node_js:
|
||||||
- "20.15.1"
|
- "22.17.0"
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- ".meteor"
|
- ".meteor"
|
||||||
@@ -16,6 +16,8 @@ env:
|
|||||||
- CXX=g++-12
|
- CXX=g++-12
|
||||||
- phantom=false
|
- phantom=false
|
||||||
- PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
- PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium
|
||||||
|
- TEST_PACKAGES_EXCLUDE=stylus
|
||||||
|
- METEOR_MODERN=true
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
@@ -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:
|
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
|
* 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
|
* 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.
|
Members of the CoCP team will be added for a 1-year term and will be re-confirmed on a yearly basis.
|
||||||
|
|||||||
@@ -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/).
|
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
|
### Finding work
|
||||||
|
|
||||||
@@ -43,15 +43,14 @@ Reviewers are members of the community who help with Pull Requests reviews.
|
|||||||
|
|
||||||
Current Reviewers:
|
Current Reviewers:
|
||||||
- [meteor](https://github.com/meteor/meteor)
|
- [meteor](https://github.com/meteor/meteor)
|
||||||
- [@denihs](https://github.com/denihs)
|
|
||||||
- [@fredmaiaarantes](https://github.com/fredmaiaarantes)
|
- [@fredmaiaarantes](https://github.com/fredmaiaarantes)
|
||||||
- [@henriquealbert](https://github.com/henriquealbert)
|
- [@henriquealbert](https://github.com/henriquealbert)
|
||||||
- [@aquinoit](https://github.com/aquinoit)
|
- [@aquinoit](https://github.com/aquinoit)
|
||||||
- [@Grubba27](https://github.com/Grubba27)
|
- [@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)
|
- [@StorytellerCZ](https://github.com/StorytellerCZ)
|
||||||
- [@zodern](https://github.com/zodern)
|
- [@zodern](https://github.com/zodern)
|
||||||
- [@CaptainN](https://github.com/CaptainN)
|
|
||||||
- [@radekmie](https://github.com/radekmie)
|
- [@radekmie](https://github.com/radekmie)
|
||||||
|
|
||||||
#### Core Committer
|
#### 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.
|
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:
|
Current Core Team:
|
||||||
- [@denihs](https://github.com/denihs)
|
- [meteor](https://github.com/meteor/meteor)
|
||||||
- [@zodern](https://github.com/zodern)
|
- [@fredmaiaarantes](https://github.com/fredmaiaarantes)
|
||||||
- [@filipenevola](https://github.com/filipenevola)
|
- [@henriquealbert](https://github.com/henriquealbert)
|
||||||
- [@fredmaiaarantes](https://github.com/fredmaiaarantes)
|
- [@Grubba27](https://github.com/Grubba27)
|
||||||
- [@henriquealbert](https://github.com/henriquealbert)
|
- [@italojs](https://github.com/italojs)
|
||||||
- [@Grubba27](https://github.com/Grubba27)
|
- [@nachocodoner](https://github.com/nachocodoner)
|
||||||
- [@StorytellerCZ](https://github.com/StorytellerCZ)
|
- [@StorytellerCZ](https://github.com/StorytellerCZ)
|
||||||
- [@CaptainN](https://github.com/CaptainN)
|
- [@zodern](https://github.com/zodern)
|
||||||
- [@radekmie](https://github.com/radekmie)
|
- [@radekmie](https://github.com/radekmie)
|
||||||
- [@matheusccastroo](https://github.com/matheusccastroo)
|
|
||||||
|
|
||||||
### Tracking project work
|
### Tracking project work
|
||||||
|
|
||||||
@@ -157,7 +155,7 @@ Learn how we use GitHub labels [here](LABELS.md)
|
|||||||
|
|
||||||
## Documentation
|
## 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
|
## Blaze
|
||||||
|
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -1,16 +1,18 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://www.meteor.com" target="_blank">
|
<a href="https://www.meteor.com" target="_blank">
|
||||||
<img align="center" width="225" src="https://user-images.githubusercontent.com/841294/26841702-0902bbee-4af3-11e7-9805-0618da66a246.png">
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://dmtgy0px4zdqn.cloudfront.net/images/meteor-logo.webp">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/0467afb6-4f36-4cad-9d78-237150d5d881">
|
||||||
|
<img alt="Meteor logo" src="https://github.com/user-attachments/assets/0467afb6-4f36-4cad-9d78-237150d5d881" width="300">
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://app.travis-ci.com/github/meteor/meteor)
|
[](https://app.travis-ci.com/github/meteor/meteor)
|
||||||
[](https://app.circleci.com/pipelines/github/meteor/meteor?branch=devel)
|
[](https://app.circleci.com/pipelines/github/meteor/meteor?branch=devel)
|
||||||
[](https://meteor.com)
|
[](https://meteor.com)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -54,23 +56,20 @@ How about trying a tutorial to get started with your favorite technology?
|
|||||||
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"> React](https://docs.meteor.com/tutorials/react/) |
|
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"> React](https://docs.meteor.com/tutorials/react/) |
|
||||||
| - |
|
| - |
|
||||||
| [<img align="left" width="25" src="https://progsoft.net/images/blaze-css-icon-3e80acb3996047afd09f1150f53fcd78e98c1e1b.png"> Blaze](https://blaze-tutorial.meteor.com/) |
|
| [<img align="left" width="25" src="https://progsoft.net/images/blaze-css-icon-3e80acb3996047afd09f1150f53fcd78e98c1e1b.png"> Blaze](https://blaze-tutorial.meteor.com/) |
|
||||||
| [<img align="left" width="25" src="https://vuejs.org/images/logo.png"> Vue](https://vue-tutorial.meteor.com/) |
|
| [<img align="left" width="25" src="https://vuejs.org/images/logo.png"> Vue](https://docs.meteor.com/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker.html) |
|
||||||
| [<img align="left" width="25" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Svelte_Logo.svg/1200px-Svelte_Logo.svg.png"> Svelte](https://svelte-tutorial.meteor.com/) |
|
|
||||||
|
|
||||||
Next, read the [documentation](https://docs.meteor.com/) and get some [examples](https://github.com/meteor/examples).
|
|
||||||
|
|
||||||
# 🚀 Quick Start
|
# 🚀 Quick Start
|
||||||
|
|
||||||
On your platform, use this line:
|
On your platform, use this line:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> npm install -g meteor
|
> npx meteor
|
||||||
```
|
```
|
||||||
|
|
||||||
🚀 To create a project:
|
🚀 To create a project:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> meteor create my-app
|
> meteor create
|
||||||
```
|
```
|
||||||
|
|
||||||
☄️ Run it:
|
☄️ Run it:
|
||||||
@@ -84,10 +83,9 @@ meteor
|
|||||||
|
|
||||||
**Building an application with 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/)
|
* Discuss on [Forums](https://forums.meteor.com/)
|
||||||
* Join the Meteor Discord by clicking this [invite link](https://discord.gg/hZkTCaVjmT).
|
* Join the [Meteor Discord](https://discord.gg/hZkTCaVjmT)
|
||||||
* Announcement list. Subscribe in the [footer](https://www.meteor.com/).
|
|
||||||
|
|
||||||
|
|
||||||
Interested in helping or contributing to Meteor? These resources will help:
|
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)
|
* [Contribution guidelines](CONTRIBUTING.md)
|
||||||
* [Feature requests](https://github.com/meteor/meteor/discussions/)
|
* [Feature requests](https://github.com/meteor/meteor/discussions/)
|
||||||
* [Issue tracker](https://github.com/meteor/meteor/issues)
|
* [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).
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
| Version | Support Status
|
| 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
|
| <= 1.12.x | ❌ no longer supported
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ sidebar_categories:
|
|||||||
- packages/server-render
|
- packages/server-render
|
||||||
- packages/spacebars
|
- packages/spacebars
|
||||||
- packages/standard-minifier-css
|
- packages/standard-minifier-css
|
||||||
- packages/underscore
|
|
||||||
- packages/url
|
- packages/url
|
||||||
- packages/webapp
|
- packages/webapp
|
||||||
- packages/packages-listing
|
- packages/packages-listing
|
||||||
@@ -214,6 +213,7 @@ redirects:
|
|||||||
/#/full/accounts-setusername: 'api/passwords.html#accounts-setusername'
|
/#/full/accounts-setusername: 'api/passwords.html#accounts-setusername'
|
||||||
/#/full/accounts-addemail: 'api/passwords.html#accounts-addemail'
|
/#/full/accounts-addemail: 'api/passwords.html#accounts-addemail'
|
||||||
/#/full/accounts-removeemail: 'api/passwords.html#accounts-removeemail'
|
/#/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_verifyemail: 'api/passwords.html#Accounts-verifyEmail'
|
||||||
/#/full/accounts-finduserbyusername: 'api/passwords.html#accounts-finduserbyusername'
|
/#/full/accounts-finduserbyusername: 'api/passwords.html#accounts-finduserbyusername'
|
||||||
/#/full/accounts-finduserbyemail: 'api/passwords.html#accounts-finduserbyemail'
|
/#/full/accounts-finduserbyemail: 'api/passwords.html#accounts-finduserbyemail'
|
||||||
@@ -393,7 +393,6 @@ redirects:
|
|||||||
/#/full/oauth-encryption: 'packages/oauth-encryption.html'
|
/#/full/oauth-encryption: 'packages/oauth-encryption.html'
|
||||||
/#/full/random: 'packages/random.html'
|
/#/full/random: 'packages/random.html'
|
||||||
/#/full/spiderable: 'packages/spiderable.html'
|
/#/full/spiderable: 'packages/spiderable.html'
|
||||||
/#/full/underscore: 'packages/underscore.html'
|
|
||||||
/#/full/webapp: 'packages/webapp.html'
|
/#/full/webapp: 'packages/webapp.html'
|
||||||
'#meteor_isclient': 'api/core.html#Meteor-isClient'
|
'#meteor_isclient': 'api/core.html#Meteor-isClient'
|
||||||
'#meteor_isserver': 'api/core.html#Meteor-isServer'
|
'#meteor_isserver': 'api/core.html#Meteor-isServer'
|
||||||
@@ -669,6 +668,5 @@ redirects:
|
|||||||
'#oauth-encryption': 'packages/oauth-encryption.html'
|
'#oauth-encryption': 'packages/oauth-encryption.html'
|
||||||
'#random': 'packages/random.html'
|
'#random': 'packages/random.html'
|
||||||
'#spiderable': 'packages/spiderable.html'
|
'#spiderable': 'packages/spiderable.html'
|
||||||
'#underscore': 'packages/underscore.html'
|
|
||||||
'#webapp': 'packages/webapp.html'
|
'#webapp': 'packages/webapp.html'
|
||||||
'#pkg_spacebars': 'packages/spacebars.html'
|
'#pkg_spacebars': 'packages/spacebars.html'
|
||||||
|
|||||||
@@ -2935,6 +2935,7 @@ N/A
|
|||||||
setMinimumBrowserVersions({
|
setMinimumBrowserVersions({
|
||||||
chrome: 49,
|
chrome: 49,
|
||||||
firefox: 45,
|
firefox: 45,
|
||||||
|
firefoxIOS: 100,
|
||||||
edge: 12,
|
edge: 12,
|
||||||
ie: Infinity, // Sorry, IE11.
|
ie: Infinity, // Sorry, IE11.
|
||||||
mobile_safari: [9, 2], // 9.2.0+
|
mobile_safari: [9, 2], // 9.2.0+
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
- `Accounts.sendVerificationEmail`
|
- `Accounts.sendVerificationEmail`
|
||||||
- `Accounts.addEmail`
|
- `Accounts.addEmail`
|
||||||
- `Accounts.removeEmail`
|
- `Accounts.removeEmail`
|
||||||
|
- `Accounts.replaceEmailAsync`
|
||||||
- `Accounts.verifyEmail`
|
- `Accounts.verifyEmail`
|
||||||
- `Accounts.createUserVerifyingEmail`
|
- `Accounts.createUserVerifyingEmail`
|
||||||
- `Accounts.createUser`
|
- `Accounts.createUser`
|
||||||
|
|||||||
303
docs/history.md
303
docs/history.md
@@ -8,6 +8,308 @@
|
|||||||
|
|
||||||
[//]: # (go to meteor/docs/generators/changelog/docs)
|
[//]: # (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
|
## v3.0.1, 2024-07-16
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
@@ -4651,6 +4953,7 @@ N/A
|
|||||||
setMinimumBrowserVersions({
|
setMinimumBrowserVersions({
|
||||||
chrome: 49,
|
chrome: 49,
|
||||||
firefox: 45,
|
firefox: 45,
|
||||||
|
firefoxIOS: 100,
|
||||||
edge: 12,
|
edge: 12,
|
||||||
ie: Infinity, // Sorry, IE11.
|
ie: Infinity, // Sorry, IE11.
|
||||||
mobile_safari: [9, 2], // 9.2.0+
|
mobile_safari: [9, 2], // 9.2.0+
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"packages/ddp/sockjs-0.3.4.js",
|
"packages/ddp/sockjs-0.3.4.js",
|
||||||
"packages/test-in-browser/diff_match_patch_uncompressed.js",
|
"packages/test-in-browser/diff_match_patch_uncompressed.js",
|
||||||
"packages/jquery/jquery.js",
|
"packages/jquery/jquery.js",
|
||||||
"packages/underscore/underscore.js",
|
|
||||||
"packages/json/json2.js",
|
"packages/json/json2.js",
|
||||||
"packages/minimongo/minimongo_tests.js",
|
"packages/minimongo/minimongo_tests.js",
|
||||||
"tools/node_modules",
|
"tools/node_modules",
|
||||||
|
|||||||
421
docs/package-lock.json
generated
421
docs/package-lock.json
generated
@@ -4,16 +4,41 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": {
|
"@babel/helper-string-parser": {
|
||||||
"version": "7.23.3",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"dev": true
|
"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": {
|
"@jsdoc/salty": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
|
||||||
"integrity": "sha512-aA+awb5yoml8TQ3CzI5Ue7sM3VMRC4l1zJJW4fgZ8OCL1wshJZhNzaf0PL85DSnOUw6QuFgeHGD/eq/xwwAF2g==",
|
"integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
@@ -26,31 +51,31 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@meteorjs/meteor-theme-hexo": {
|
"@meteorjs/meteor-theme-hexo": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@meteorjs/meteor-theme-hexo/-/meteor-theme-hexo-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@meteorjs/meteor-theme-hexo/-/meteor-theme-hexo-2.0.9.tgz",
|
||||||
"integrity": "sha512-LQIFN05wBMjX7SXgW5CFVTfolDWMuknoypwQ0czl/44LYRBR4/LYZUgX6c+1vLjloJb+5+2HTvMGlVN9Wo1MKA==",
|
"integrity": "sha512-8ncpsN8MAe1F7cJBtcPgH3JE36WV03oo5mPkA1yMdRmv2kq8AQpKnd4ok0U1cr5NIIBMupLtsHDLm8PhTQcUdw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/linkify-it": {
|
"@types/linkify-it": {
|
||||||
"version": "3.0.5",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
"integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==",
|
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/markdown-it": {
|
"@types/markdown-it": {
|
||||||
"version": "12.2.3",
|
"version": "14.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||||
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/linkify-it": "*",
|
"@types/linkify-it": "^5",
|
||||||
"@types/mdurl": "*"
|
"@types/mdurl": "^2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/mdurl": {
|
"@types/mdurl": {
|
||||||
"version": "1.0.5",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"JSONStream": {
|
"JSONStream": {
|
||||||
@@ -75,16 +100,6 @@
|
|||||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||||
"dev": true
|
"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": {
|
"acorn": {
|
||||||
"version": "6.4.2",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
|
||||||
@@ -239,9 +254,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"aws4": {
|
"aws4": {
|
||||||
"version": "1.12.0",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
|
||||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
|
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -479,9 +494,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@@ -506,9 +521,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bytes": {
|
"bytes": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cache-base": {
|
"cache-base": {
|
||||||
@@ -537,17 +552,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"call-bind": {
|
"call-bind": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
"es-define-property": "^1.0.0",
|
"es-define-property": "^1.0.0",
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-intrinsic": "^1.2.4",
|
"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": {
|
"camel-case": {
|
||||||
@@ -801,18 +837,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compression": {
|
"compression": {
|
||||||
"version": "1.7.4",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
|
||||||
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
|
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"accepts": "~1.3.5",
|
"bytes": "3.1.2",
|
||||||
"bytes": "3.0.0",
|
"compressible": "~2.0.18",
|
||||||
"compressible": "~2.0.16",
|
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"on-headers": "~1.0.2",
|
"negotiator": "~0.6.4",
|
||||||
"safe-buffer": "5.1.2",
|
"on-headers": "~1.1.0",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
"vary": "~1.1.2"
|
"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": {
|
"concat-map": {
|
||||||
@@ -1040,6 +1084,18 @@
|
|||||||
"domelementtype": "1"
|
"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": {
|
"ecc-jsbn": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
@@ -1092,14 +1148,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"es-define-property": {
|
"es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true
|
||||||
"requires": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"es-errors": {
|
"es-errors": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -1108,6 +1161,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": 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": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -1323,17 +1386,33 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"get-intrinsic": {
|
"get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"get-proto": "^1.0.1",
|
||||||
"has-symbols": "^1.0.3",
|
"gopd": "^1.2.0",
|
||||||
"hasown": "^2.0.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": {
|
"get-value": {
|
||||||
@@ -1401,14 +1480,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"gopd": {
|
"gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true
|
||||||
"requires": {
|
|
||||||
"get-intrinsic": "^1.1.3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
@@ -1480,17 +1556,10 @@
|
|||||||
"es-define-property": "^1.0.0"
|
"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": {
|
"has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -2390,9 +2459,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "6.0.5",
|
"version": "6.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
|
||||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
"integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"nice-try": "^1.0.4",
|
"nice-try": "^1.0.4",
|
||||||
@@ -2416,12 +2485,6 @@
|
|||||||
"striptags": "^3.1.1"
|
"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": {
|
"strip-indent": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||||
@@ -2652,12 +2715,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-core-module": {
|
"is-core-module": {
|
||||||
"version": "2.13.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"hasown": "^2.0.0"
|
"hasown": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-data-descriptor": {
|
"is-data-descriptor": {
|
||||||
@@ -2839,21 +2902,21 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"jsdoc": {
|
"jsdoc": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz",
|
||||||
"integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==",
|
"integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.20.15",
|
"@babel/parser": "^7.20.15",
|
||||||
"@jsdoc/salty": "^0.2.1",
|
"@jsdoc/salty": "^0.2.1",
|
||||||
"@types/markdown-it": "^12.2.3",
|
"@types/markdown-it": "^14.1.1",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"catharsis": "^0.9.0",
|
"catharsis": "^0.9.0",
|
||||||
"escape-string-regexp": "^2.0.0",
|
"escape-string-regexp": "^2.0.0",
|
||||||
"js2xmlparser": "^4.0.2",
|
"js2xmlparser": "^4.0.2",
|
||||||
"klaw": "^3.0.0",
|
"klaw": "^3.0.0",
|
||||||
"markdown-it": "^12.3.2",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-anchor": "^8.4.1",
|
"markdown-it-anchor": "^8.6.7",
|
||||||
"marked": "^4.0.10",
|
"marked": "^4.0.10",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"requizzle": "^0.2.3",
|
"requizzle": "^0.2.3",
|
||||||
@@ -2861,12 +2924,6 @@
|
|||||||
"underscore": "~1.13.2"
|
"underscore": "~1.13.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"escape-string-regexp": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||||
@@ -2886,9 +2943,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"underscore": {
|
"underscore": {
|
||||||
"version": "1.13.6",
|
"version": "1.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
||||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2901,13 +2958,14 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"json-stable-stringify": {
|
"json-stable-stringify": {
|
||||||
"version": "1.1.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
|
||||||
"integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==",
|
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"call-bind": "^1.0.5",
|
"call-bind": "^1.0.8",
|
||||||
|
"call-bound": "^1.0.4",
|
||||||
"isarray": "^2.0.5",
|
"isarray": "^2.0.5",
|
||||||
"jsonify": "^0.0.1",
|
"jsonify": "^0.0.1",
|
||||||
"object-keys": "^1.1.1"
|
"object-keys": "^1.1.1"
|
||||||
@@ -3005,12 +3063,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
"version": "3.0.3",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
|
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"uc.micro": "^1.0.1"
|
"uc.micro": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
@@ -3157,16 +3215,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markdown-it": {
|
"markdown-it": {
|
||||||
"version": "12.3.2",
|
"version": "14.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
"entities": "~2.1.0",
|
"entities": "^4.4.0",
|
||||||
"linkify-it": "^3.0.1",
|
"linkify-it": "^5.0.0",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^2.0.0",
|
||||||
"uc.micro": "^1.0.5"
|
"punycode.js": "^2.3.1",
|
||||||
|
"uc.micro": "^2.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": {
|
"argparse": {
|
||||||
@@ -3176,9 +3235,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"entities": {
|
"entities": {
|
||||||
"version": "2.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3189,6 +3248,19 @@
|
|||||||
"integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
|
"integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
|
||||||
"dev": true
|
"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": {
|
"math-random": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
||||||
@@ -3196,9 +3268,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mdurl": {
|
"mdurl": {
|
||||||
"version": "1.0.1",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"micromatch": {
|
"micromatch": {
|
||||||
@@ -3239,6 +3311,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
}
|
}
|
||||||
@@ -3302,25 +3375,25 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"moment-timezone": {
|
"moment-timezone": {
|
||||||
"version": "0.5.45",
|
"version": "0.5.48",
|
||||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz",
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
"integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==",
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"moment": "^2.29.4"
|
"moment": "^2.29.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"morgan": {
|
"morgan": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||||
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
|
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"basic-auth": "~2.0.1",
|
"basic-auth": "~2.0.1",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "~2.0.0",
|
"depd": "~2.0.0",
|
||||||
"on-finished": "~2.3.0",
|
"on-finished": "~2.3.0",
|
||||||
"on-headers": "~1.0.2"
|
"on-headers": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
@@ -3342,9 +3415,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.19.0",
|
"version": "2.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
|
||||||
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
|
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -3395,9 +3468,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"negotiator": {
|
"negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
|
||||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"neo-async": {
|
"neo-async": {
|
||||||
@@ -3561,9 +3634,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"on-headers": {
|
"on-headers": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"once": {
|
"once": {
|
||||||
@@ -3754,6 +3827,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": 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": {
|
"qs": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.1.tgz",
|
||||||
@@ -4124,12 +4203,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -4201,9 +4280,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"version": "0.18.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
@@ -4245,15 +4324,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serve-static": {
|
"serve-static": {
|
||||||
"version": "1.15.0",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"parseurl": "~1.3.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": {
|
"set-blocking": {
|
||||||
@@ -4789,15 +4876,15 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"uc.micro": {
|
"uc.micro": {
|
||||||
"version": "1.0.6",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.17.4",
|
"version": "3.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||||
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
|
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"version": "3.9.0"
|
"version": "3.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@meteorjs/meteor-hexo-config": "1.0.14",
|
||||||
|
"@meteorjs/meteor-theme-hexo": "^2.0.9",
|
||||||
"canonical-json": "0.0.4",
|
"canonical-json": "0.0.4",
|
||||||
"chexo": "1.0.7",
|
"chexo": "1.0.7",
|
||||||
"handlebars": "4.7.7",
|
"handlebars": "4.7.7",
|
||||||
@@ -17,15 +19,13 @@
|
|||||||
"hexo-server": "1.0.0",
|
"hexo-server": "1.0.0",
|
||||||
"hexo-versioned-netlify-redirects": "1.1.0",
|
"hexo-versioned-netlify-redirects": "1.1.0",
|
||||||
"jsdoc": "^4.0.2",
|
"jsdoc": "^4.0.2",
|
||||||
"@meteorjs/meteor-hexo-config": "1.0.14",
|
|
||||||
"@meteorjs/meteor-theme-hexo": "2.0.8",
|
|
||||||
"showdown": "1.9.1",
|
"showdown": "1.9.1",
|
||||||
"underscore": "1.13.1"
|
"underscore": "1.13.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"list-core-packages": "node ./generators/packages-listing/script.js",
|
"list-core-packages": "node ./generators/packages-listing/script.js",
|
||||||
"generate-history": "node ./generators/changelog/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",
|
"clean": "hexo clean; rm data/data.js data/names.json",
|
||||||
"test": "npm run clean; npm run build",
|
"test": "npm run clean; npm run build",
|
||||||
"predeploy": "npm run build",
|
"predeploy": "npm run build",
|
||||||
|
|||||||
@@ -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
|
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
|
it. The `user` argument is created on the server and contains a
|
||||||
proposed user object with all the automatically generated fields
|
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
|
The function should return the user document (either the one passed in or a
|
||||||
newly-created object) with whatever modifications are desired. The returned
|
newly-created object) with whatever modifications are desired. The returned
|
||||||
|
|||||||
@@ -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
|
disable this behavior, pass `{reactive: false}` as an option to
|
||||||
`find`.
|
`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
|
fields will trigger callbacks in `observe`, `observeChanges` and
|
||||||
invalidations in reactive computations using this cursor. Careful use
|
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.
|
that don't depend on an entire document.
|
||||||
|
|
||||||
On the client, there will be a period of time between when the page loads and
|
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
|
Equivalent to [`find`](#find)`(selector, options).`[`fetch`](#fetch)`()[0]` with
|
||||||
`options.limit = 1`.
|
`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" %}
|
{% apibox "Mongo.Collection#findOneAsync" %}
|
||||||
|
|
||||||
Async version of [`findOne`](#findOne) that return a `Promise`.
|
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
|
1 if the second document comes first, or 0 if neither document comes before
|
||||||
the other. This is a Minimongo extension to MongoDB.
|
the other. This is a Minimongo extension to MongoDB.
|
||||||
|
|
||||||
<h2 id="fieldspecifiers">Field Specifiers</h2>
|
<h2 id="fieldspecifiers">Projection Specifiers</h2>
|
||||||
|
|
||||||
Queries can specify a particular set of fields to include or exclude from the
|
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
|
dictionary whose keys are field names and whose values are `0`. All unspecified
|
||||||
fields are included.
|
fields are included.
|
||||||
|
|
||||||
```js
|
```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
|
To include only specific fields in the result documents, use `1` as
|
||||||
the value. The `_id` field is still included in the result.
|
the value. The `_id` field is still included in the result.
|
||||||
|
|
||||||
```js
|
```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:
|
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
|
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
|
`_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
|
[`observeChanges`](#observe_changes), [`observe`](#observe), cursors returned
|
||||||
from a [publish function](#meteor_publish), or cursors used in
|
from a [publish function](#meteor_publish), or cursors used in
|
||||||
`{% raw %}{{#each}}{% endraw %}` in a template. They may be used with [`fetch`](#fetch),
|
`{% raw %}{{#each}}{% endraw %}` in a template. They may be used with [`fetch`](#fetch),
|
||||||
@@ -994,10 +996,12 @@ Users.insert({
|
|||||||
name: 'Yagami Light',
|
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' }] }
|
// 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 <a href="http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/#projection">
|
See <a href="http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/#projection">
|
||||||
the MongoDB docs</a> for details of the nested field rules and array behavior.
|
the MongoDB docs</a> for details of the nested field rules and array behavior.
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ By default, an email address is added with `{ verified: false }`. Use
|
|||||||
[`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an
|
[`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an
|
||||||
email with a link the user can use to verify their email address.
|
email with a link the user can use to verify their email address.
|
||||||
|
|
||||||
|
{% apibox "Accounts.replaceEmailAsync" %}
|
||||||
|
|
||||||
{% apibox "Accounts.removeEmail" %}
|
{% apibox "Accounts.removeEmail" %}
|
||||||
|
|
||||||
{% apibox "Accounts.verifyEmail" %}
|
{% apibox "Accounts.verifyEmail" %}
|
||||||
|
|||||||
@@ -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.
|
The `cache-build` option is available since Meteor 1.11.
|
||||||
{% endpullquote %}
|
{% 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 %}
|
{% pullquote warning %}
|
||||||
The `--container-size` option is available since Meteor 2.4.1.
|
The `--container-size` option is available since Meteor 2.4.1.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: Docs
|
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).
|
||||||
|
|
||||||
<!-- XXX: note that this content is somewhat duplicated on the guide, and should be updated in parallel -->
|
<!-- XXX: note that this content is somewhat duplicated on the guide, and should be updated in parallel -->
|
||||||
<h2 id="what-is-meteor">What is Meteor?</h2>
|
<h2 id="what-is-meteor">What is Meteor?</h2>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ You need to install the Meteor command line tool to create, run, and manage your
|
|||||||
|
|
||||||
<h3 id="prereqs-node">Node.js version</h3>
|
<h3 id="prereqs-node">Node.js version</h3>
|
||||||
|
|
||||||
> 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.
|
- 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.
|
- 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.
|
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:
|
For Windows, Linux and OS X, you can run the following command:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* `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.
|
* `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:
|
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/)
|
- [Monti APM](https://montiapm.com/)
|
||||||
- [Meteor Elastic APM](https://github.com/Meteor-Community-Packages/meteor-elastic-apm)
|
- [Meteor Elastic APM](https://github.com/Meteor-Community-Packages/meteor-elastic-apm)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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).
|
||||||
|
|
||||||
<!-- XXX: note that this content is somewhat duplicated on the docs, and should be updated in parallel -->
|
<!-- XXX: note that this content is somewhat duplicated on the docs, and should be updated in parallel -->
|
||||||
<h2 id="what-is-meteor">What is Meteor?</h2>
|
<h2 id="what-is-meteor">What is Meteor?</h2>
|
||||||
|
|||||||
@@ -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.
|
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.
|
||||||
|
|
||||||
<h3 id="validated-method">Advanced Methods with mdg:validated-method</h3>
|
<h3 id="jam-method">Advanced Methods with jam:method</h3>
|
||||||
|
|
||||||
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
|
```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',
|
name: 'todos.updateText',
|
||||||
validate: new SimpleSchema({
|
schema: new SimpleSchema({
|
||||||
todoId: { type: String },
|
todoId: { type: String },
|
||||||
newText: { type: String }
|
newText: { type: String }
|
||||||
}).validator(),
|
}),
|
||||||
run({ todoId, newText }) {
|
async run({ todoId, newText }) {
|
||||||
const todo = Todos.findOne(todoId);
|
const todo = await Todos.findOneAsync(todoId);
|
||||||
|
|
||||||
if (!todo.editableBy(this.userId)) {
|
if (!todo.editableBy(this.userId)) {
|
||||||
throw new Meteor.Error('todos.updateText.unauthorized',
|
throw new Meteor.Error('todos.updateText.unauthorized',
|
||||||
'Cannot edit todos in a private list that is not yours');
|
'Cannot edit todos in a private list that is not yours');
|
||||||
}
|
}
|
||||||
|
|
||||||
Todos.update(todoId, {
|
Todos.updateAsync(todoId, {
|
||||||
$set: { text: newText }
|
$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.
|
||||||
|
|
||||||
<h2 id="errors">Error handling</h2>
|
<h2 id="errors">Error handling</h2>
|
||||||
|
|
||||||
@@ -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 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).
|
|
||||||
|
|
||||||
<h3 id="handling-errors">Handling errors</h3>
|
<h3 id="handling-errors">Handling errors</h3>
|
||||||
|
|
||||||
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:
|
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
|
```js
|
||||||
// Call the Method
|
// Call the Method
|
||||||
updateText.call({
|
updateText({
|
||||||
todoId: '12345',
|
todoId: '12345',
|
||||||
newText: 'This is a todo item.'
|
newText: 'This is a todo item.'
|
||||||
}, (err, res) => {
|
}, (err, res) => {
|
||||||
@@ -261,7 +257,7 @@ We'll talk about how to handle the `ValidationError` in the section on forms bel
|
|||||||
|
|
||||||
<h3 id="throw-stub-exceptions">Errors in Method simulation</h3>
|
<h3 id="throw-stub-exceptions">Errors in Method simulation</h3>
|
||||||
|
|
||||||
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:
|
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.
|
// This Method encodes the form validation requirements.
|
||||||
// By defining them in the Method, we do client and server-side
|
// By defining them in the Method, we do client and server-side
|
||||||
// validation in one place.
|
// validation in one place.
|
||||||
export const insert = new ValidatedMethod({
|
export const insert = createMethod({
|
||||||
name: 'Invoices.methods.insert',
|
name: 'Invoices.methods.insert',
|
||||||
validate: new SimpleSchema({
|
schema: new SimpleSchema({
|
||||||
email: { type: String, regEx: emailRegEx },
|
email: { type: String, regEx: emailRegEx },
|
||||||
description: { type: String, min: 5 },
|
description: { type: String, min: 5 },
|
||||||
amount: { type: String, regEx: amountRegEx }
|
amount: { type: String, regEx: amountRegEx }
|
||||||
}).validator(),
|
}),
|
||||||
run(newInvoice) {
|
run(newInvoice) {
|
||||||
// In here, we can be sure that the newInvoice argument is
|
// In here, we can be sure that the newInvoice argument is
|
||||||
// validated.
|
// validated.
|
||||||
@@ -299,7 +295,7 @@ export const insert = new ValidatedMethod({
|
|||||||
'Must be logged in to create an invoice.');
|
'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
|
amount: event.target.amount.value
|
||||||
};
|
};
|
||||||
|
|
||||||
insert.call(data, (err, res) => {
|
insert(data, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.error === 'validation-error') {
|
if (err.error === 'validation-error') {
|
||||||
// Initialize error object
|
// 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.
|
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.
|
||||||
|
|
||||||
<h4 id="lifecycle-ddp-message">2. A `method` DDP message is sent to the server</h4>
|
<h4 id="lifecycle-ddp-message">2. A `method` DDP message is sent to the server</h4>
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ title: Performance improvements
|
|||||||
description: How to optimize your Meteor application for higher performance when you start growing.
|
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).
|
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,
|
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.
|
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
|
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.
|
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/),
|
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
|
|||||||
<h2 id="performance-monitoring">Performance monitoring</h2>
|
<h2 id="performance-monitoring">Performance monitoring</h2>
|
||||||
|
|
||||||
Before any optimization can take place we need to know what is our problem. This is where APM (Application Performance Monitor) comes in.
|
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)
|
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).
|
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
|
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
|
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,
|
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.
|
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.
|
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
|
|||||||
```
|
```
|
||||||
|
|
||||||
<h3 id="find-issues-apm">Finding issues in APM</h3>
|
<h3 id="find-issues-apm">Finding issues in APM</h3>
|
||||||
APM will start with providing you with an overview of how your app is performing. You can then dive deep into details of
|
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
|
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
|
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:
|
optimizing methods, will look like this:
|
||||||
|
|
||||||
1. Go to the detailed view under the Methods tab.
|
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
|
* methodX - mean response time 1 515 ms, throughput 100,05/min
|
||||||
* methodY - mean response time 34 000 ms, throughput 0,03/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
|
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
|
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.
|
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.
|
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.
|
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).
|
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.
|
Under the hood WebSockets are being used with additional abilities provided by DDP.
|
||||||
|
|
||||||
<h3 id="publications-proper-use">Proper use of publications</h3>
|
<h3 id="publications-proper-use">Proper use of publications</h3>
|
||||||
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.
|
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
|
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,
|
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.
|
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
|
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.
|
(aka always set the `limit` option) and ensure that you have set all your indexes.
|
||||||
|
|
||||||
<h4 id="publications-methods">Methods over publications</h3>
|
<h4 id="publications-methods">Methods over publications</h3>
|
||||||
The first easiest replacement is to use Meteor methods instead of publications. In this case you can use the existing publication
|
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
|
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.
|
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).
|
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).
|
||||||
|
|
||||||
<h4 id="publications-replacements">Publication replacements</h4>
|
<h4 id="publications-replacements">Publication replacements</h4>
|
||||||
Using methods has its limitations and there are other tools that you might want to evaluate as a potential replacement.
|
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
|
[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/),
|
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.
|
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.
|
Do note, that you can mix all of these based on your needs.
|
||||||
|
|
||||||
<h3 id="low-observer-reuse">Low observer reuse</h3>
|
<h3 id="low-observer-reuse">Low observer reuse</h3>
|
||||||
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.
|
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)
|
> [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
|
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
|
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();`.
|
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)
|
> [Learn more on improving observer reuse](https://galaxy-guide.meteor.com/apm-improve-cpu-and-network-usage)
|
||||||
|
|
||||||
<h3 id="redis-oplog">Redis Oplog</h3>
|
<h3 id="redis-oplog">Redis Oplog</h3>
|
||||||
|
|
||||||
[Redis Oplog](https://atmospherejs.com/cultofcoders/redis-oplog) is a popular solution to Meteor's Oplog tailing
|
[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
|
(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
|
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.
|
the server and database, allows you to track only the data that you want and only publish the changes you need.
|
||||||
|
|
||||||
<h2 id="methods">Methods</h2>
|
<h2 id="methods">Methods</h2>
|
||||||
|
|
||||||
While methods are listed as one of the possible replacements for publications, they themselves can be made more performant,
|
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
|
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.
|
methods are the problem.
|
||||||
|
|
||||||
<h3 id="heavy-actions">Heavy actions</h3>
|
<h3 id="heavy-actions">Heavy actions</h3>
|
||||||
|
|
||||||
In general heavy tasks that take a lot of resources or take long and block the server for that time should be taken out
|
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
|
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.
|
or even better something specifically optimized for that given task.
|
||||||
|
|
||||||
<h3 id="reoccurring-jobs">Reoccurring jobs</h3>
|
<h3 id="reoccurring-jobs">Reoccurring jobs</h3>
|
||||||
|
|
||||||
Reoccurring jobs are another prime candidate to be taken out into its own application. What this means is that you will have
|
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
|
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.
|
the list and be recipient of the results, most likely via database results.
|
||||||
|
|
||||||
<h3 id="rate-limiting">Rate limiting</h3>
|
<h3 id="rate-limiting">Rate limiting</h3>
|
||||||
|
|
||||||
Rate limit your methods to reduce effectiveness of DDOS attack and spare your server. This is also a good practice to
|
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
|
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
|
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.
|
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.
|
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
|
|||||||
|
|
||||||
<h3 id="mongo-ip-whitelisting">IP whitelisting</h3>
|
<h3 id="mongo-ip-whitelisting">IP whitelisting</h3>
|
||||||
|
|
||||||
If your MongoDB hosting provider allows it, you should make sure that you whitelist the IPs of your application servers.
|
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 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.
|
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.
|
See [Galaxy guide](https://galaxy-guide.meteor.com/container-environment.html#network-outgoing) on IP whitelisting to get IPs for your Galaxy servers.
|
||||||
|
|
||||||
<h3 id="mongodb-indexes">Indexes</h3>
|
<h3 id="mongodb-indexes">Indexes</h3>
|
||||||
|
|
||||||
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:
|
multiple variables. To cover those you will need to create compound indexes. For example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -177,7 +177,7 @@ Statistics.createIndexAsync(
|
|||||||
```
|
```
|
||||||
When creating indexes you should sort the variables in ESR (equity, sort, range) style.
|
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,
|
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.
|
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
|
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.
|
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.
|
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,
|
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.
|
|||||||
|
|
||||||
<h3 id="beware-of-collection-hooks">Beware of collection hooks</h3>
|
<h3 id="beware-of-collection-hooks">Beware of collection hooks</h3>
|
||||||
|
|
||||||
While collection hooks can help in many cases beware of them and make sure that you understand how they work as they might
|
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 that you might not know about. Make sure to review packages that use them so that they won't
|
||||||
create additional queries.
|
create additional queries.
|
||||||
|
|
||||||
<h3 id="mongodb-caching">Caching</h3>
|
<h3 id="mongodb-caching">Caching</h3>
|
||||||
|
|
||||||
Once your user base increases you want to invest into query caching like using Redis, Redis Oplog and other.
|
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.
|
and save their results.
|
||||||
|
|
||||||
<h2 id="scaling">Scaling</h2>
|
<h2 id="scaling">Scaling</h2>
|
||||||
@@ -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.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
<h2 id="packages">Packages</h2>
|
<h2 id="packages">Packages</h2>
|
||||||
|
|
||||||
During development, it is very tempting to add packages to solve issue or support some features.
|
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.
|
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
|
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.
|
as a whole what will be the impact on performance.
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ Each of these points will have their own section below.
|
|||||||
|
|
||||||
<h3 id="allow-deny">Avoid allow/deny</h3>
|
<h3 id="allow-deny">Avoid allow/deny</h3>
|
||||||
|
|
||||||
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.
|
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.
|
If someone comes along and passes a non-ID selector like `{}`, they will end up deleting the entire collection.
|
||||||
|
|
||||||
<h3 id="validated-method">mdg:validated-method</h3>
|
<h3 id="jam-method">jam:method</h3>
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
<h3 id="user-id-client">Don't pass userId from the client</h3>
|
<h3 id="user-id-client">Don't pass userId from the client</h3>
|
||||||
|
|
||||||
@@ -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:
|
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
|
```js
|
||||||
export const makePrivate = new ValidatedMethod({
|
export const makePrivate = new createMethod({
|
||||||
name: 'lists.makePrivate',
|
name: 'lists.makePrivate',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
listId: { type: String }
|
listId: { type: String }
|
||||||
}).validator(),
|
}).validator(),
|
||||||
run({ listId }) {
|
async run({ listId }) {
|
||||||
if (!this.userId) {
|
if (!this.userId) {
|
||||||
throw new Meteor.Error('lists.makePrivate.notLoggedIn',
|
throw new Meteor.Error('lists.makePrivate.notLoggedIn',
|
||||||
'Must be logged in to make private lists.');
|
'Must be logged in to make private lists.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = Lists.findOne(listId);
|
const list = await Lists.findOneAsync(listId);
|
||||||
|
|
||||||
if (list.isLastPublicList()) {
|
if (list.isLastPublicList()) {
|
||||||
throw new Meteor.Error('lists.makePrivate.lastPublicList',
|
throw new Meteor.Error('lists.makePrivate.lastPublicList',
|
||||||
'Cannot make the last public list private.');
|
'Cannot make the last public list private.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Lists.update(listId, {
|
await Lists.updateAsync(listId, {
|
||||||
$set: { userId: this.userId }
|
$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:
|
However, this doesn't mean you can't have any flexibility in your Methods. Let's look at an example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Meteor.users.methods.setUserData = new ValidatedMethod({
|
Meteor.users.methods.setUserData = new createMethod({
|
||||||
name: 'Meteor.users.methods.setUserData',
|
name: 'Meteor.users.methods.setUserData',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
fullName: { type: String, optional: true },
|
fullName: { type: String, optional: true },
|
||||||
dateOfBirth: { type: Date, optional: true },
|
dateOfBirth: { type: Date, optional: true },
|
||||||
}).validator(),
|
}).validator(),
|
||||||
run(fieldsToSet) {
|
async run(fieldsToSet) {
|
||||||
Meteor.users.update(this.userId, {
|
return (await Meteor.users.updateAsync(this.userId, {
|
||||||
$set: fieldsToSet
|
$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.
|
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).
|
||||||
|
|
||||||
|
|
||||||
<h2 id="publications">Publications</h2>
|
<h2 id="publications">Publications</h2>
|
||||||
|
|
||||||
@@ -274,10 +275,10 @@ Publications are not reactive, and they only re-run when the currently logged in
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// #1: Bad! If the owner of the list changes, the old owner will still see it
|
// #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);
|
check(listId, String);
|
||||||
|
|
||||||
const list = Lists.findOne(listId);
|
const list = await Lists.findOneAsync(listId);
|
||||||
|
|
||||||
if (list.userId !== this.userId) {
|
if (list.userId !== this.userId) {
|
||||||
throw new Meteor.Error('list.unauthorized',
|
throw new Meteor.Error('list.unauthorized',
|
||||||
@@ -351,7 +352,7 @@ export const MMR = {
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// In a file loaded on client and server
|
// 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',
|
name: 'Meteor.users.methods.updateMMR',
|
||||||
validate: null,
|
validate: null,
|
||||||
run() {
|
run() {
|
||||||
|
|||||||
@@ -238,9 +238,11 @@ import { Tracker } from 'meteor/tracker';
|
|||||||
const withDiv = function withDiv(callback) {
|
const withDiv = function withDiv(callback) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
|
let view = null
|
||||||
try {
|
try {
|
||||||
callback(el);
|
view = callback(el);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (view) Blaze.remove(view)
|
||||||
document.body.removeChild(el);
|
document.body.removeChild(el);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -248,9 +250,10 @@ const withDiv = function withDiv(callback) {
|
|||||||
export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) {
|
export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) {
|
||||||
withDiv((el) => {
|
withDiv((el) => {
|
||||||
const ourTemplate = isString(template) ? Template[template] : template;
|
const ourTemplate = isString(template) ? Template[template] : template;
|
||||||
Blaze.renderWithData(ourTemplate, data, el);
|
const view = Blaze.renderWithData(ourTemplate, data, el);
|
||||||
Tracker.flush();
|
Tracker.flush();
|
||||||
callback(el);
|
callback(el);
|
||||||
|
return view
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ Meteor supports many view layers.
|
|||||||
The most popular are:
|
The most popular are:
|
||||||
- [React](react.html): official [page](http://reactjs.org/)
|
- [React](react.html): official [page](http://reactjs.org/)
|
||||||
- [Blaze](blaze.html): official [page](http://blazejs.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/)
|
- [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.
|
If you are starting with web development we recommend that you use Blaze as it's very simple to learn.
|
||||||
|
|
||||||
|
|||||||
5
meteor
5
meteor
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
# OS Check. Put here because here is where we download the precompiled
|
||||||
# bundles that are arch specific.
|
# bundles that are arch specific.
|
||||||
@@ -123,6 +122,7 @@ fi
|
|||||||
|
|
||||||
DEV_BUNDLE="$SCRIPT_DIR/dev_bundle"
|
DEV_BUNDLE="$SCRIPT_DIR/dev_bundle"
|
||||||
METEOR="$SCRIPT_DIR/tools/index.js"
|
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
|
# Set the nofile ulimit as high as permitted by the hard-limit/kernel
|
||||||
if [ "$(ulimit -Sn)" != "unlimited" ]; then
|
if [ "$(ulimit -Sn)" != "unlimited" ]; then
|
||||||
@@ -148,5 +148,6 @@ fi
|
|||||||
exec "$DEV_BUNDLE/bin/node" \
|
exec "$DEV_BUNDLE/bin/node" \
|
||||||
--max-old-space-size=4096 \
|
--max-old-space-size=4096 \
|
||||||
--no-wasm-code-gc \
|
--no-wasm-code-gc \
|
||||||
|
--require="$PROCESS_REQUIRES"\
|
||||||
${TOOL_NODE_FLAGS} \
|
${TOOL_NODE_FLAGS} \
|
||||||
"$METEOR" "$@"
|
"$METEOR" "$@"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ SET BABEL_CACHE_DIR=%~dp0\.babel-cache
|
|||||||
|
|
||||||
"%~dp0\dev_bundle\bin\node.exe" ^
|
"%~dp0\dev_bundle\bin\node.exe" ^
|
||||||
--no-wasm-code-gc ^
|
--no-wasm-code-gc ^
|
||||||
|
--require="%~dp0\tools\node-process-warnings.js" ^
|
||||||
%TOOL_NODE_FLAGS% ^
|
%TOOL_NODE_FLAGS% ^
|
||||||
"%~dp0\tools\index.js" %*
|
"%~dp0\tools\index.js" %*
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ meteor
|
|||||||
|
|
||||||
Building an application with 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/
|
* Announcement list: sign up at https://www.meteor.com/
|
||||||
* Discussion forums: https://forums.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).
|
* Join the Meteor community Slack by clicking this [invite link](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc).
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var packageJson = {
|
|||||||
private: true,
|
private: true,
|
||||||
dependencies: {
|
dependencies: {
|
||||||
promise: "8.1.0",
|
promise: "8.1.0",
|
||||||
"@meteorjs/reify": "0.24.0",
|
"@meteorjs/reify": "0.25.3",
|
||||||
"@babel/parser": "7.17.0",
|
"@babel/parser": "7.17.0",
|
||||||
"@types/underscore": "1.11.4",
|
"@types/underscore": "1.11.4",
|
||||||
underscore: "1.13.6",
|
underscore: "1.13.6",
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ var packageJson = {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
// Explicit dependency because we are replacing it with a bundled version
|
// 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
|
// 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",
|
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
|
||||||
"node-gyp": "9.4.0",
|
"node-gyp": "9.4.0",
|
||||||
"@mapbox/node-pre-gyp": "1.0.11",
|
"@mapbox/node-pre-gyp": "1.0.11",
|
||||||
typescript: "5.4.5",
|
typescript: "5.6.3",
|
||||||
"@meteorjs/babel": "7.19.0-beta.3",
|
"@meteorjs/babel": "7.20.0",
|
||||||
"@meteorjs/reify": "0.24.0",
|
"@meteorjs/reify": "0.25.3",
|
||||||
// So that Babel can emit require("@babel/runtime/helpers/...") calls.
|
// So that Babel can emit require("@babel/runtime/helpers/...") calls.
|
||||||
"@babel/runtime": "7.15.3",
|
"@babel/runtime": "7.15.3",
|
||||||
// For backwards compatibility with isopackets that still depend on
|
// For backwards compatibility with isopackets that still depend on
|
||||||
|
|||||||
26
npm-packages/meteor-babel/package-lock.json
generated
26
npm-packages/meteor-babel/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@meteorjs/babel",
|
"name": "@meteorjs/babel",
|
||||||
"version": "7.20.0-beta.5",
|
"version": "7.20.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -786,9 +786,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@meteorjs/reify": {
|
"@meteorjs/reify": {
|
||||||
"version": "0.25.2",
|
"version": "0.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.2.tgz",
|
"resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.25.4.tgz",
|
||||||
"integrity": "sha512-mkaSPyzovKf86wSA4ouCmXUQkASA8qNCXp71/Tbm0tD/bpiaja3measRB1HPA+yLXq9Xq3+8GLh8ytJu98cwIQ==",
|
"integrity": "sha512-/HwynJK85QtS2Rm26M9TS8aEMnqVJ2TIzJNJTGAQz+G6cTYmJGWaU4nFH96oxiDIBbnT6Y3TfX92HDuS9TtNRg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"acorn": "^8.8.1",
|
"acorn": "^8.8.1",
|
||||||
"magic-string": "^0.25.3",
|
"magic-string": "^0.25.3",
|
||||||
@@ -797,21 +797,21 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.6.2",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/estree": {
|
"@types/estree": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "8.11.3",
|
"version": "8.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
|
||||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
|
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w=="
|
||||||
},
|
},
|
||||||
"ansi-colors": {
|
"ansi-colors": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@meteorjs/babel",
|
"name": "@meteorjs/babel",
|
||||||
"author": "Meteor <dev@meteor.com>",
|
"author": "Meteor <dev@meteor.com>",
|
||||||
"version": "7.20.0-beta.5",
|
"version": "7.20.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"description": "Babel wrapper package for use with Meteor",
|
"description": "Babel wrapper package for use with Meteor",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"@babel/template": "^7.16.7",
|
"@babel/template": "^7.16.7",
|
||||||
"@babel/traverse": "^7.17.0",
|
"@babel/traverse": "^7.17.0",
|
||||||
"@babel/types": "^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-meteor": "^7.10.0",
|
||||||
"babel-preset-minify": "^0.5.1",
|
"babel-preset-minify": "^0.5.1",
|
||||||
"convert-source-map": "^1.6.0",
|
"convert-source-map": "^1.6.0",
|
||||||
|
|||||||
@@ -1,105 +1,106 @@
|
|||||||
## Meteor Installer
|
## Meteor Installer
|
||||||
|
|
||||||
### Recommended Versions
|
### Recommended Versions
|
||||||
|
|
||||||
- For Meteor 2 (Legacy)
|
- For Meteor 2 (Legacy)
|
||||||
- Use Node.js 14.x
|
- Use Node.js 14.x
|
||||||
- Use npm 6.x
|
- Use npm 6.x
|
||||||
- For Meteor 3
|
- For Meteor 3
|
||||||
- Use Node.js 20.x or higher
|
- Use Node.js 20.x or higher
|
||||||
- Use npm 9.x or higher
|
- Use npm 9.x or higher
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
To install Meteor, run the following command:
|
To install Meteor, run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx meteor
|
npx meteor
|
||||||
```
|
```
|
||||||
|
|
||||||
It will install Meteor's latest version, alternatively you can install a specific version by running:
|
It will install Meteor's latest version, alternatively you can install a specific version by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx meteor@<version>
|
npx meteor@<version>
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will execute the Meteor installer without adding it permanently to your global npm packages.
|
This command will execute the Meteor installer without adding it permanently to your global npm packages.
|
||||||
|
|
||||||
For more information, visit:
|
For more information, visit:
|
||||||
|
|
||||||
- [Meteor 2 Installation Guide (Legacy)](https://v2-docs.meteor.com/install.html)
|
- [Meteor 2 Installation Guide (Legacy)](https://v2-docs.meteor.com/install.html)
|
||||||
- [**Meteor 3 Installation Guide**](https://v3-docs.meteor.com/about/install.html)
|
- [**Meteor 3 Installation Guide**](https://v3-docs.meteor.com/about/install.html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Important Note
|
### 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.
|
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
|
### 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:
|
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
|
```bash
|
||||||
npm install -g meteor --ignore-meteor-setup-exec-path
|
npm install -g meteor --ignore-meteor-setup-exec-path
|
||||||
```
|
```
|
||||||
|
|
||||||
(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`)
|
(or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`)
|
||||||
|
|
||||||
### Proxy Configuration
|
### Proxy Configuration
|
||||||
|
|
||||||
Set the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL to download Meteor files through the configured proxy.
|
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
|
### Meteor Version Compatibility
|
||||||
|
|
||||||
| NPM Package | Meteor Official Release |
|
| NPM Package | Meteor Official Release |
|
||||||
|-------------|-------------------------|
|
|-------------|-------------------------|
|
||||||
| 3.0.3 | 3.0.3 |
|
| 3.0.4 | 3.0.4 |
|
||||||
| 3.0.2 | 3.0.2 |
|
| 3.0.3 | 3.0.3 |
|
||||||
| 3.0.1 | 3.0.1 |
|
| 3.0.2 | 3.0.2 |
|
||||||
| 3.0.0 | 3.0 |
|
| 3.0.1 | 3.0.1 |
|
||||||
| 2.16.0 | 2.16.0 |
|
| 3.0.0 | 3.0 |
|
||||||
| 2.15.0 | 2.15.0 |
|
| 2.16.0 | 2.16.0 |
|
||||||
| 2.14.0 | 2.14.0 |
|
| 2.15.0 | 2.15.0 |
|
||||||
| 2.13.3 | 2.13.3 |
|
| 2.14.0 | 2.14.0 |
|
||||||
| 2.13.1 | 2.13.1 |
|
| 2.13.3 | 2.13.3 |
|
||||||
| 2.13.0 | 2.13.0 |
|
| 2.13.1 | 2.13.1 |
|
||||||
| 2.12.1 | 2.12.0 |
|
| 2.13.0 | 2.13.0 |
|
||||||
| 2.12.0 | 2.12.0 |
|
| 2.12.1 | 2.12.0 |
|
||||||
| 2.11.0 | 2.11.0 |
|
| 2.12.0 | 2.12.0 |
|
||||||
| 2.10.0 | 2.10.0 |
|
| 2.11.0 | 2.11.0 |
|
||||||
| 2.9.1 | 2.9.1 |
|
| 2.10.0 | 2.10.0 |
|
||||||
| 2.9.0 | 2.9.0 |
|
| 2.9.1 | 2.9.1 |
|
||||||
| 2.8.2 | 2.8.1 |
|
| 2.9.0 | 2.9.0 |
|
||||||
| 2.8.1 | 2.8.1 |
|
| 2.8.2 | 2.8.1 |
|
||||||
| 2.8.0 | 2.8.0 |
|
| 2.8.1 | 2.8.1 |
|
||||||
| 2.7.5 | 2.7.3 |
|
| 2.8.0 | 2.8.0 |
|
||||||
| 2.7.4 | 2.7.3 |
|
| 2.7.5 | 2.7.3 |
|
||||||
| 2.7.3 | 2.7.2 |
|
| 2.7.4 | 2.7.3 |
|
||||||
| 2.7.2 | 2.7.1 |
|
| 2.7.3 | 2.7.2 |
|
||||||
| 2.7.1 | 2.7 |
|
| 2.7.2 | 2.7.1 |
|
||||||
| 2.7.0 | 2.7 |
|
| 2.7.1 | 2.7 |
|
||||||
| 2.6.2 | 2.6.1 |
|
| 2.7.0 | 2.7 |
|
||||||
| 2.6.1 | 2.6 |
|
| 2.6.2 | 2.6.1 |
|
||||||
| 2.6.0 | 2.6 |
|
| 2.6.1 | 2.6 |
|
||||||
| 2.5.9 | 2.5.8 |
|
| 2.6.0 | 2.6 |
|
||||||
| 2.5.8 | 2.5.7 |
|
| 2.5.9 | 2.5.8 |
|
||||||
| 2.5.7 | 2.5.6 |
|
| 2.5.8 | 2.5.7 |
|
||||||
| 2.5.6 | 2.5.5 |
|
| 2.5.7 | 2.5.6 |
|
||||||
| 2.5.5 | 2.5.4 |
|
| 2.5.6 | 2.5.5 |
|
||||||
| 2.5.4 | 2.5.3 |
|
| 2.5.5 | 2.5.4 |
|
||||||
| 2.5.3 | 2.5.2 |
|
| 2.5.4 | 2.5.3 |
|
||||||
| 2.5.2 | 2.5.1 |
|
| 2.5.3 | 2.5.2 |
|
||||||
| 2.5.1 | 2.5.1 |
|
| 2.5.2 | 2.5.1 |
|
||||||
| 2.5.0 | 2.5 |
|
| 2.5.1 | 2.5.1 |
|
||||||
| 2.4.1 | 2.4 |
|
| 2.5.0 | 2.5 |
|
||||||
| 2.4.0 | 2.4 |
|
| 2.4.1 | 2.4 |
|
||||||
| 2.3.7 | 2.3.6 |
|
| 2.4.0 | 2.4 |
|
||||||
| 2.3.6 | 2.3.5 |
|
| 2.3.7 | 2.3.6 |
|
||||||
| 2.3.5 | 2.3.5 |
|
| 2.3.6 | 2.3.5 |
|
||||||
| 2.3.4 | 2.3.4 |
|
| 2.3.5 | 2.3.5 |
|
||||||
| 2.3.3 | 2.3.2 |
|
| 2.3.4 | 2.3.4 |
|
||||||
| 2.3.2 | 2.3.1 |
|
| 2.3.3 | 2.3.2 |
|
||||||
| 2.3.1 | 2.2.1 |
|
| 2.3.2 | 2.3.1 |
|
||||||
|
| 2.3.1 | 2.2.1 |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const METEOR_LATEST_VERSION = '3.0.3';
|
const METEOR_LATEST_VERSION = '3.3.2';
|
||||||
const sudoUser = process.env.SUDO_USER || '';
|
const sudoUser = process.env.SUDO_USER || '';
|
||||||
function isRoot() {
|
function isRoot() {
|
||||||
return process.getuid && process.getuid() === 0;
|
return process.getuid && process.getuid() === 0;
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ Or see the docs at:
|
|||||||
|
|
||||||
Deploy and host your app with Cloud:
|
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:
|
You might need to open a new terminal window to have access to the meteor command, or run this in your terminal:
|
||||||
|
|||||||
11
npm-packages/meteor-installer/package-lock.json
generated
11
npm-packages/meteor-installer/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "meteor",
|
"name": "meteor",
|
||||||
"version": "3.0.3",
|
"version": "3.3.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "meteor",
|
"name": "meteor",
|
||||||
"version": "3.0.3",
|
"version": "3.3.2",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -194,9 +194,10 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
"shebang-command": "^2.0.0",
|
"shebang-command": "^2.0.0",
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "meteor",
|
"name": "meteor",
|
||||||
"version": "3.0.3",
|
"version": "3.3.2",
|
||||||
"description": "Install Meteor",
|
"description": "Install Meteor",
|
||||||
"main": "install.js",
|
"main": "install.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "node cli.js install"
|
"install": "node cli.js install"
|
||||||
},
|
},
|
||||||
"author": "zodern",
|
"author": "zodern",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"7zip-bin": "^5.2.0",
|
"7zip-bin": "^5.2.0",
|
||||||
"cli-progress": "^3.11.1",
|
"cli-progress": "^3.11.1",
|
||||||
"https-proxy-agent": "^5.0.1",
|
"https-proxy-agent": "^5.0.1",
|
||||||
"node-7z": "^2.1.2",
|
"node-7z": "^2.1.2",
|
||||||
"node-downloader-helper": "^2.1.9",
|
"node-downloader-helper": "^2.1.9",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"tmp": "^0.2.1"
|
"tmp": "^0.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"meteor-installer": "cli.js"
|
"meteor-installer": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.x",
|
"node": ">=20.x",
|
||||||
"npm": ">=10.x"
|
"npm": ">=10.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/node_modules
|
|
||||||
|
|||||||
@@ -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
|
v1.2.8 - 2024-04-01
|
||||||
* Add new dependency `@meteorjs/crypto-browserify` to replace `crypto-browserify` as it had unsafe dependencies.
|
* Add new dependency `@meteorjs/crypto-browserify` to replace `crypto-browserify` as it had unsafe dependencies.
|
||||||
|
|
||||||
|
|||||||
1544
npm-packages/meteor-node-stubs/package-lock.json
generated
1544
npm-packages/meteor-node-stubs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"name": "meteor-node-stubs",
|
"name": "meteor-node-stubs",
|
||||||
"author": "Ben Newman <ben@meteor.com>",
|
"author": "Ben Newman <ben@meteor.com>",
|
||||||
"description": "Stub implementations of Node built-in modules, a la Browserify",
|
"description": "Stub implementations of Node built-in modules, a la Browserify",
|
||||||
"version": "1.2.10",
|
"version": "1.2.24",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-node-stubs/README.md",
|
"homepage": "https://github.com/meteor/meteor/blob/devel/npm-packages/meteor-node-stubs/README.md",
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
"console-browserify": "^1.2.0",
|
"console-browserify": "^1.2.0",
|
||||||
"constants-browserify": "^1.0.0",
|
"constants-browserify": "^1.0.0",
|
||||||
"domain-browser": "^4.23.0",
|
"domain-browser": "^4.23.0",
|
||||||
"elliptic": "^6.5.7",
|
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
"punycode": "^1.4.1",
|
"punycode": "^1.4.1",
|
||||||
"querystring-es3": "^0.2.1",
|
"querystring-es3": "^0.2.1",
|
||||||
"readable-stream": "^3.6.2",
|
"readable-stream": "^3.6.2",
|
||||||
|
"sha.js": "^2.4.12",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"stream-http": "^3.2.0",
|
"stream-http": "^3.2.0",
|
||||||
"string_decoder": "^1.3.0",
|
"string_decoder": "^1.3.0",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"vm-browserify"
|
"vm-browserify"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"rimraf": "^2.7.1"
|
"rimraf": "^5.0.10"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -81,5 +81,30 @@
|
|||||||
],
|
],
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/meteor/node-stubs/issues"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ var fs = require("fs");
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
var depsDir = path.join(__dirname, "..", "deps");
|
var depsDir = path.join(__dirname, "..", "deps");
|
||||||
var map = require("../map.json");
|
var map = require("../map.json");
|
||||||
|
var rr = require("rimraf");
|
||||||
|
|
||||||
// Each file in the `deps` directory expresses the dependencies of a stub.
|
// Each file in the `deps` directory expresses the dependencies of a stub.
|
||||||
// For example, `deps/http.js` calls `require("http-browserify")` to
|
// 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,
|
// bundled. Note that these modules should not be `require`d at runtime,
|
||||||
// but merely scanned at bundling time.
|
// but merely scanned at bundling time.
|
||||||
|
|
||||||
fs.mkdir(depsDir, function () {
|
rr.rimrafSync(depsDir);
|
||||||
require("rimraf")("deps/*.js", function (error) {
|
|
||||||
if (error) throw error;
|
fs.mkdirSync(depsDir);
|
||||||
Object.keys(map).forEach(function (id) {
|
|
||||||
fs.writeFileSync(
|
Object.keys(map).forEach(function (id) {
|
||||||
path.join(depsDir, id + ".js"),
|
fs.writeFileSync(
|
||||||
typeof map[id] === "string"
|
path.join(depsDir, id + ".js"),
|
||||||
? "require(" + JSON.stringify(map[id]) + ");\n"
|
typeof map[id] === "string"
|
||||||
: ""
|
? "require(" + JSON.stringify(map[id]) + ");\n"
|
||||||
);
|
: ""
|
||||||
});
|
);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
130
package-lock.json
generated
130
package-lock.json
generated
@@ -13,7 +13,10 @@
|
|||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/eslint-parser": "^7.21.3",
|
||||||
"@babel/eslint-plugin": "^7.19.1",
|
"@babel/eslint-plugin": "^7.19.1",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
|
"@types/lodash.isempty": "^4.4.9",
|
||||||
"@types/node": "^18.16.18",
|
"@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/eslint-plugin": "^5.56.0",
|
||||||
"@typescript-eslint/parser": "^5.56.0",
|
"@typescript-eslint/parser": "^5.56.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
@@ -25,7 +28,7 @@
|
|||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"prettier": "^2.8.6",
|
"prettier": "^2.8.8",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1096,6 +1099,21 @@
|
|||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.19.3",
|
"version": "18.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
|
||||||
@@ -1111,6 +1129,21 @@
|
|||||||
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
|
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.62.0",
|
"version": "5.62.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
||||||
@@ -1817,6 +1850,19 @@
|
|||||||
"concat-map": "0.0.1"
|
"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": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.22.2",
|
"version": "4.22.2",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||||
@@ -2992,39 +3038,6 @@
|
|||||||
"node": ">=8.6.0"
|
"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": {
|
"node_modules/fast-glob/node_modules/micromatch": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||||
@@ -3038,18 +3051,6 @@
|
|||||||
"node": ">=8.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": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"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": "^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": {
|
"node_modules/flat-cache": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
|
||||||
@@ -3626,6 +3640,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/is-number-object": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin-prettier.js"
|
"prettier": "bin-prettier.js"
|
||||||
},
|
},
|
||||||
@@ -4695,6 +4720,19 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,23 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "meteor",
|
"name": "meteor",
|
||||||
"version": "0.0.1",
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/meteor/meteor.git"
|
"url": "git+https://github.com/meteor/meteor.git"
|
||||||
},
|
},
|
||||||
"author": "Filipe Névola",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/meteor/meteor/issues"
|
"url": "https://github.com/meteor/meteor/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/meteor/meteor#readme",
|
"homepage": "https://www.meteor.com/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.3",
|
"@babel/core": "^7.21.3",
|
||||||
"@babel/eslint-parser": "^7.21.3",
|
"@babel/eslint-parser": "^7.21.3",
|
||||||
"@babel/eslint-plugin": "^7.19.1",
|
"@babel/eslint-plugin": "^7.19.1",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
|
"@types/lodash.isempty": "^4.4.9",
|
||||||
"@types/node": "^18.16.18",
|
"@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/eslint-plugin": "^5.56.0",
|
||||||
"@typescript-eslint/parser": "^5.56.0",
|
"@typescript-eslint/parser": "^5.56.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
@@ -29,9 +31,12 @@
|
|||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"prettier": "^2.8.6",
|
"prettier": "^2.8.8",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test:idle-bot": "node --test .github/scripts/__tests__/inactive-issues.test.js"
|
||||||
|
},
|
||||||
"jshintConfig": {
|
"jshintConfig": {
|
||||||
"esversion": 11
|
"esversion": 11
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
61
packages/accounts-base/accounts-base.d.ts
vendored
61
packages/accounts-base/accounts-base.d.ts
vendored
@@ -6,6 +6,7 @@ import { DDP } from 'meteor/ddp';
|
|||||||
export interface URLS {
|
export interface URLS {
|
||||||
resetPassword: (token: string) => string;
|
resetPassword: (token: string) => string;
|
||||||
verifyEmail: (token: string) => string;
|
verifyEmail: (token: string) => string;
|
||||||
|
loginToken: (token: string) => string;
|
||||||
enrollAccount: (token: string) => string;
|
enrollAccount: (token: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ export namespace Accounts {
|
|||||||
profile?: Meteor.UserProfile | undefined;
|
profile?: Meteor.UserProfile | undefined;
|
||||||
},
|
},
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): string;
|
): Promise<string>;
|
||||||
|
|
||||||
function createUserAsync(
|
function createUserAsync(
|
||||||
options: {
|
options: {
|
||||||
@@ -82,6 +83,11 @@ export namespace Accounts {
|
|||||||
passwordEnrollTokenExpirationInDays?: number | undefined;
|
passwordEnrollTokenExpirationInDays?: number | undefined;
|
||||||
ambiguousErrorMessages?: boolean | undefined;
|
ambiguousErrorMessages?: boolean | undefined;
|
||||||
bcryptRounds?: number | 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;
|
defaultFieldSelector?: { [key: string]: 0 | 1 } | undefined;
|
||||||
collection?: string | undefined;
|
collection?: string | undefined;
|
||||||
loginTokenExpirationHours?: number | undefined;
|
loginTokenExpirationHours?: number | undefined;
|
||||||
@@ -113,23 +119,23 @@ export namespace Accounts {
|
|||||||
oldPassword: string,
|
oldPassword: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
function forgotPassword(
|
function forgotPassword(
|
||||||
options: { email?: string | undefined },
|
options: { email?: string | undefined },
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
function resetPassword(
|
function resetPassword(
|
||||||
token: string,
|
token: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
function verifyEmail(
|
function verifyEmail(
|
||||||
token: string,
|
token: string,
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
function onEmailVerificationLink(callback: Function): void;
|
function onEmailVerificationLink(callback: Function): void;
|
||||||
|
|
||||||
@@ -143,11 +149,11 @@ export namespace Accounts {
|
|||||||
|
|
||||||
function logout(
|
function logout(
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
function logoutOtherClients(
|
function logoutOtherClients(
|
||||||
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void
|
||||||
): void;
|
): Promise<void>;
|
||||||
|
|
||||||
type PasswordSignupField = 'USERNAME_AND_EMAIL' | 'USERNAME_AND_OPTIONAL_EMAIL' | 'USERNAME_ONLY' | 'EMAIL_ONLY';
|
type PasswordSignupField = 'USERNAME_AND_EMAIL' | 'USERNAME_AND_OPTIONAL_EMAIL' | 'USERNAME_ONLY' | 'EMAIL_ONLY';
|
||||||
type PasswordlessSignupField = 'USERNAME_AND_EMAIL' | 'EMAIL_ONLY';
|
type PasswordlessSignupField = 'USERNAME_AND_EMAIL' | 'EMAIL_ONLY';
|
||||||
@@ -179,9 +185,11 @@ export interface EmailTemplates {
|
|||||||
export namespace Accounts {
|
export namespace Accounts {
|
||||||
var emailTemplates: EmailTemplates;
|
var emailTemplates: EmailTemplates;
|
||||||
|
|
||||||
function addEmail(userId: string, newEmail: string, verified?: boolean): void;
|
function addEmailAsync(userId: string, newEmail: string, verified?: boolean): Promise<void>;
|
||||||
|
|
||||||
function removeEmail(userId: string, email: string): void;
|
function removeEmail(userId: string, email: string): Promise<void>;
|
||||||
|
|
||||||
|
function replaceEmailAsync(userId: string, oldEmail: string, newEmail: string, verified?: boolean): Promise<void>;
|
||||||
|
|
||||||
function onCreateUser(
|
function onCreateUser(
|
||||||
func: (options: { profile?: {} | undefined }, user: Meteor.User) => void
|
func: (options: { profile?: {} | undefined }, user: Meteor.User) => void
|
||||||
@@ -190,35 +198,52 @@ export namespace Accounts {
|
|||||||
function findUserByEmail(
|
function findUserByEmail(
|
||||||
email: string,
|
email: string,
|
||||||
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
||||||
): Meteor.User | null | undefined;
|
): Promise<Meteor.User | null | undefined>;
|
||||||
|
|
||||||
function findUserByUsername(
|
function findUserByUsername(
|
||||||
username: string,
|
username: string,
|
||||||
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
options?: { fields?: Mongo.FieldSpecifier | undefined }
|
||||||
): Meteor.User | null | undefined;
|
): Promise<Meteor.User | null | undefined>;
|
||||||
|
|
||||||
|
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(
|
function sendEnrollmentEmail(
|
||||||
userId: string,
|
userId: string,
|
||||||
email?: string,
|
email?: string,
|
||||||
extraTokenData?: Record<string, unknown>,
|
extraTokenData?: Record<string, unknown>,
|
||||||
extraParams?: Record<string, unknown>
|
extraParams?: Record<string, unknown>
|
||||||
): void;
|
): Promise<SendEmailResult>;
|
||||||
|
|
||||||
function sendResetPasswordEmail(
|
function sendResetPasswordEmail(
|
||||||
userId: string,
|
userId: string,
|
||||||
email?: string,
|
email?: string,
|
||||||
extraTokenData?: Record<string, unknown>,
|
extraTokenData?: Record<string, unknown>,
|
||||||
extraParams?: Record<string, unknown>
|
extraParams?: Record<string, unknown>
|
||||||
): void;
|
): Promise<SendEmailResult>;
|
||||||
|
|
||||||
function sendVerificationEmail(
|
function sendVerificationEmail(
|
||||||
userId: string,
|
userId: string,
|
||||||
email?: string,
|
email?: string,
|
||||||
extraTokenData?: Record<string, unknown>,
|
extraTokenData?: Record<string, unknown>,
|
||||||
extraParams?: Record<string, unknown>
|
extraParams?: Record<string, unknown>
|
||||||
): void;
|
): Promise<SendEmailResult>;
|
||||||
|
|
||||||
function setUsername(userId: string, newUsername: string): void;
|
function setUsername(userId: string, newUsername: string): Promise<void>;
|
||||||
|
|
||||||
function setPasswordAsync(
|
function setPasswordAsync(
|
||||||
userId: string,
|
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
|
* the database user record. `password` can be a string (in which case
|
||||||
* it will be run through SHA256 before bcrypt) or an object with
|
* it will be run through SHA256 before bcrypt or argon2) or an object with
|
||||||
* properties `digest` and `algorithm` (in which case we bcrypt
|
* properties `digest` and `algorithm` (in which case we bcrypt/argon2
|
||||||
* `password.digest`).
|
* `password.digest`).
|
||||||
*/
|
*/
|
||||||
function _checkPasswordAsync(
|
function _checkPasswordAsync(
|
||||||
|
|||||||
@@ -362,18 +362,19 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
// Note that we need to call this even if _suppressLoggingIn is true,
|
// Note that we need to call this even if _suppressLoggingIn is true,
|
||||||
// because it could be matching a _setLoggingIn(true) from a
|
// because it could be matching a _setLoggingIn(true) from a
|
||||||
// half-completed pre-reconnect login method.
|
// half-completed pre-reconnect login method.
|
||||||
this._setLoggingIn(false);
|
|
||||||
if (error || !result) {
|
if (error || !result) {
|
||||||
error = error || new Error(
|
error = error || new Error(
|
||||||
`No result from call to ${options.methodName}`
|
`No result from call to ${options.methodName}`
|
||||||
);
|
);
|
||||||
loginCallbacks({ error });
|
loginCallbacks({ error });
|
||||||
|
this._setLoggingIn(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
options.validateResult(result);
|
options.validateResult(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loginCallbacks({ error: e });
|
loginCallbacks({ error: e });
|
||||||
|
this._setLoggingIn(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,13 +382,15 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
this.makeClientLoggedIn(result.id, result.token, result.tokenExpires);
|
this.makeClientLoggedIn(result.id, result.token, result.tokenExpires);
|
||||||
|
|
||||||
// use Tracker to make we sure have a user before calling the callbacks
|
// 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, () =>
|
const user = await Tracker.withComputation(computation, () =>
|
||||||
Meteor.userAsync(),
|
Meteor.userAsync(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (user) {
|
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(
|
this.connection.applyAsync(
|
||||||
options.methodName,
|
options.methodName,
|
||||||
options.methodArguments,
|
options.methodArguments,
|
||||||
{ wait: true, onResultReceived: onResultReceived },
|
{ wait: true, onResultReceived },
|
||||||
loggedInAndDataReadyCallback);
|
loggedInAndDataReadyCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +689,7 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
/**
|
/**
|
||||||
* @summary Register a function to call when a reset password link is clicked
|
* @summary Register a function to call when a reset password link is clicked
|
||||||
* in an email sent by
|
* in an email sent by
|
||||||
* [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail).
|
* [`Accounts.sendResetPasswordEmail`](#Accounts-sendResetPasswordEmail).
|
||||||
* This function should be called in top-level code, not inside
|
* This function should be called in top-level code, not inside
|
||||||
* `Meteor.startup()`.
|
* `Meteor.startup()`.
|
||||||
* @memberof! Accounts
|
* @memberof! Accounts
|
||||||
@@ -694,7 +697,7 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
* @param {Function} callback The function to call. It is given two arguments:
|
* @param {Function} callback The function to call. It is given two arguments:
|
||||||
*
|
*
|
||||||
* 1. `token`: A password reset token that can be passed to
|
* 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
|
* 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
|
* 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.
|
* 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
|
* @summary Register a function to call when an email verification link is
|
||||||
* clicked in an email sent by
|
* 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
|
* This function should be called in top-level code, not inside
|
||||||
* `Meteor.startup()`.
|
* `Meteor.startup()`.
|
||||||
* @memberof! Accounts
|
* @memberof! Accounts
|
||||||
@@ -720,7 +723,7 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
* @param {Function} callback The function to call. It is given two arguments:
|
* @param {Function} callback The function to call. It is given two arguments:
|
||||||
*
|
*
|
||||||
* 1. `token`: An email verification token that can be passed to
|
* 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.
|
* 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
|
* 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
|
* 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
|
* @summary Register a function to call when an account enrollment link is
|
||||||
* clicked in an email sent by
|
* 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
|
* This function should be called in top-level code, not inside
|
||||||
* `Meteor.startup()`.
|
* `Meteor.startup()`.
|
||||||
* @memberof! Accounts
|
* @memberof! Accounts
|
||||||
@@ -747,7 +750,7 @@ export class AccountsClient extends AccountsCommon {
|
|||||||
* @param {Function} callback The function to call. It is given two arguments:
|
* @param {Function} callback The function to call. It is given two arguments:
|
||||||
*
|
*
|
||||||
* 1. `token`: A password reset token that can be passed to
|
* 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.
|
* enrolled account a password.
|
||||||
* 2. `done`: A function to call when the enrollment UI flow is complete.
|
* 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
|
* The normal login process is suspended until this function is called, so that
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ const VALID_CONFIG_KEYS = [
|
|||||||
'passwordEnrollTokenExpiration',
|
'passwordEnrollTokenExpiration',
|
||||||
'ambiguousErrorMessages',
|
'ambiguousErrorMessages',
|
||||||
'bcryptRounds',
|
'bcryptRounds',
|
||||||
|
'argon2Enabled',
|
||||||
|
'argon2Type',
|
||||||
|
'argon2TimeCost',
|
||||||
|
'argon2MemoryCost',
|
||||||
|
'argon2Parallelism',
|
||||||
'defaultFieldSelector',
|
'defaultFieldSelector',
|
||||||
'collection',
|
'collection',
|
||||||
'loginTokenExpirationHours',
|
'loginTokenExpirationHours',
|
||||||
@@ -194,48 +199,13 @@ export class AccountsCommon {
|
|||||||
? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options))
|
? this.users.findOneAsync(userId, this._addDefaultFieldSelector(options))
|
||||||
: null;
|
: 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.
|
* @summary Set global accounts options. You can also set these in `Meteor.settings.packages.accounts` without the need to call this function.
|
||||||
* @locus Anywhere
|
* @locus Anywhere
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {Boolean} options.sendVerificationEmail New users with an email address will receive an address verification email.
|
* @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 {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.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.
|
* @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.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.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 {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 {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 {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 {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.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 {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.
|
* @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) {
|
config(options) {
|
||||||
// We don't want users to accidentally only call Accounts.config on the
|
// We don't want users to accidentally only call Accounts.config on the
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ export class AccountsServer extends AccountsCommon {
|
|||||||
|
|
||||||
this._skipCaseInsensitiveChecksForTest = {};
|
this._skipCaseInsensitiveChecksForTest = {};
|
||||||
|
|
||||||
|
// Helper function to resolve promises if needed
|
||||||
|
this._resolvePromise = async (value) => {
|
||||||
|
return Meteor._isPromise(value) ? await value : value;
|
||||||
|
};
|
||||||
|
|
||||||
this.urls = {
|
this.urls = {
|
||||||
resetPassword: (token, extraParams) => this.buildEmailUrl(`#/reset-password/${token}`, extraParams),
|
resetPassword: (token, extraParams) => this.buildEmailUrl(`#/reset-password/${token}`, extraParams),
|
||||||
verifyEmail: (token, extraParams) => this.buildEmailUrl(`#/verify-email/${token}`, extraParams),
|
verifyEmail: (token, extraParams) => this.buildEmailUrl(`#/verify-email/${token}`, extraParams),
|
||||||
@@ -333,6 +338,32 @@ export class AccountsServer extends AccountsCommon {
|
|||||||
return user;
|
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<Object>} 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<Object>} A user if found, else null
|
||||||
|
* @memberof Accounts
|
||||||
|
* @importFromPackage accounts-base
|
||||||
|
*/
|
||||||
|
findUserByUsername = async (username, options) =>
|
||||||
|
await this._findUserByQuery({ username }, options);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// LOGIN METHODS
|
/// LOGIN METHODS
|
||||||
///
|
///
|
||||||
@@ -1806,21 +1837,6 @@ const setupUsersCollection = async users => {
|
|||||||
|
|
||||||
return true;
|
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.
|
fetch: ['_id'] // we only look at _id.
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1861,4 +1877,3 @@ const generateCasePermutationsForString = string => {
|
|||||||
}
|
}
|
||||||
return permutations;
|
return permutations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -730,7 +730,7 @@ if (Meteor.isServer) {
|
|||||||
// create same user in two different collections - should pass
|
// create same user in two different collections - should pass
|
||||||
const email = "test-collection@testdomain.com"
|
const email = "test-collection@testdomain.com"
|
||||||
|
|
||||||
const collection0 = new Mongo.Collection('test1');
|
const collection0 = new Mongo.Collection(`test1_${Random.id()}`);
|
||||||
|
|
||||||
Accounts.config({
|
Accounts.config({
|
||||||
collection: collection0,
|
collection: collection0,
|
||||||
@@ -738,7 +738,7 @@ if (Meteor.isServer) {
|
|||||||
const uid0 = await Accounts.createUser({email})
|
const uid0 = await Accounts.createUser({email})
|
||||||
await Meteor.users.removeAsync(uid0);
|
await Meteor.users.removeAsync(uid0);
|
||||||
|
|
||||||
const collection1 = new Mongo.Collection('test2');
|
const collection1 = new Mongo.Collection(`test2_${Random.id()}`);
|
||||||
Accounts.config({
|
Accounts.config({
|
||||||
collection: collection1,
|
collection: collection1,
|
||||||
})
|
})
|
||||||
@@ -757,13 +757,13 @@ if (Meteor.isServer) {
|
|||||||
const email = "test-collection@testdomain.com"
|
const email = "test-collection@testdomain.com"
|
||||||
|
|
||||||
Accounts.config({
|
Accounts.config({
|
||||||
collection: 'collection0',
|
collection: `collection0_${Random.id()}`,
|
||||||
})
|
})
|
||||||
const uid0 = await Accounts.createUser({email})
|
const uid0 = await Accounts.createUser({email})
|
||||||
await Meteor.users.removeAsync(uid0);
|
await Meteor.users.removeAsync(uid0);
|
||||||
|
|
||||||
Accounts.config({
|
Accounts.config({
|
||||||
collection: 'collection1',
|
collection: `collection1_${Random.id()}`,
|
||||||
})
|
})
|
||||||
const uid1 = await Accounts.createUser({email})
|
const uid1 = await Accounts.createUser({email})
|
||||||
await Meteor.users.removeAsync(uid1);
|
await Meteor.users.removeAsync(uid1);
|
||||||
@@ -775,8 +775,8 @@ if (Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Tinytest.add(
|
Tinytest.addAsync(
|
||||||
'accounts - make sure that extra params to accounts urls are added',
|
'accounts - urls work with sync resolution',
|
||||||
async test => {
|
async test => {
|
||||||
// No extra params
|
// No extra params
|
||||||
const verifyEmailURL = new URL(Accounts.urls.verifyEmail('test'));
|
const verifyEmailURL = new URL(Accounts.urls.verifyEmail('test'));
|
||||||
@@ -790,6 +790,49 @@ if (Meteor.isServer) {
|
|||||||
test.equal(enrollAccountURL.searchParams.get('test'), extraParams.test);
|
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 => {
|
Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const getTokenFromSecret = async ({ selector, secret: secretParam }) => {
|
|||||||
return token;
|
return token;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Accounts.config({ ambiguousErrorMessages: false });
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
async removeAccountsTestUser(username) {
|
async removeAccountsTestUser(username) {
|
||||||
await Meteor.users.removeAsync({ username });
|
await Meteor.users.removeAsync({ username });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "A user account system",
|
summary: "A user account system",
|
||||||
version: "3.0.2",
|
version: "3.1.2",
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse((api) => {
|
Package.onUse((api) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AccountsServer } from "./accounts_server.js";
|
|||||||
Accounts = new AccountsServer(Meteor.server, { ...Meteor.settings.packages?.accounts, ...Meteor.settings.packages?.['accounts-base'] });
|
Accounts = new AccountsServer(Meteor.server, { ...Meteor.settings.packages?.accounts, ...Meteor.settings.packages?.['accounts-base'] });
|
||||||
// TODO[FIBERS]: I need TLA
|
// TODO[FIBERS]: I need TLA
|
||||||
Accounts.init().then();
|
Accounts.init().then();
|
||||||
|
|
||||||
// Users table. Don't use the normal autopublish, since we want to hide
|
// Users table. Don't use the normal autopublish, since we want to hide
|
||||||
// some fields. Code to autopublish this is in accounts_server.js.
|
// some fields. Code to autopublish this is in accounts_server.js.
|
||||||
// XXX Allow users to configure this collection name.
|
// XXX Allow users to configure this collection name.
|
||||||
|
|||||||
@@ -74,34 +74,38 @@ Meteor.startup(() => {
|
|||||||
Accounts.oauth.tryLoginAfterPopupClosed = (
|
Accounts.oauth.tryLoginAfterPopupClosed = (
|
||||||
credentialToken,
|
credentialToken,
|
||||||
callback,
|
callback,
|
||||||
shouldRetry = true
|
timeout = 1000
|
||||||
) => {
|
) => {
|
||||||
const credentialSecret =
|
let startTime = Date.now();
|
||||||
OAuth._retrieveCredentialSecret(credentialToken);
|
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
|
// In some case the function OAuth._retrieveCredentialSecret() can return null, because the local storage might not
|
||||||
// be ready. So we retry after a timeout.
|
// be ready. So we retry after a timeout.
|
||||||
|
intervalId = Meteor.setInterval(() => {
|
||||||
if (!credentialSecret) {
|
if (Date.now() - startTime > timeout) {
|
||||||
if (!shouldRetry) {
|
checkForCredentialSecret(true);
|
||||||
return;
|
} else {
|
||||||
|
checkForCredentialSecret();
|
||||||
}
|
}
|
||||||
Meteor.setTimeout(
|
}, 250);
|
||||||
() =>
|
|
||||||
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))),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Accounts.oauth.credentialRequestCompleteHandler = callback =>
|
Accounts.oauth.credentialRequestCompleteHandler = callback =>
|
||||||
@@ -112,4 +116,3 @@ Accounts.oauth.credentialRequestCompleteHandler = callback =>
|
|||||||
Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback);
|
Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "Common code for OAuth-based login services",
|
summary: "Common code for OAuth-based login services",
|
||||||
version: '1.4.5',
|
version: '1.4.6',
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
|||||||
@@ -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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +1,51 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "Password support for accounts",
|
summary: "Password support for accounts",
|
||||||
// Note: 2.2.0-beta.3 was published during the Meteor 1.6 prerelease
|
// 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
|
// 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
|
// 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
|
// 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.
|
// through -beta.5 and -rc.0 have already been published.
|
||||||
version: "3.0.2",
|
version: "3.2.1",
|
||||||
});
|
});
|
||||||
|
|
||||||
Npm.depends({
|
Npm.depends({
|
||||||
bcrypt: "5.0.1",
|
bcrypt: "5.0.1",
|
||||||
});
|
argon2: "0.41.1",
|
||||||
|
});
|
||||||
Package.onUse((api) => {
|
|
||||||
api.use(["accounts-base", "sha", "ejson", "ddp"], ["client", "server"]);
|
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"]);
|
// Export Accounts (etc) to packages using this one.
|
||||||
|
api.imply("accounts-base", ["client", "server"]);
|
||||||
api.use("email", "server");
|
|
||||||
api.use("random", "server");
|
api.use("email", "server");
|
||||||
api.use("check", "server");
|
api.use("random", "server");
|
||||||
api.use("ecmascript");
|
api.use("check", "server");
|
||||||
|
api.use("ecmascript");
|
||||||
api.addFiles("email_templates.js", "server");
|
|
||||||
api.addFiles("password_server.js", "server");
|
api.addFiles("email_templates.js", "server");
|
||||||
api.addFiles("password_client.js", "client");
|
api.addFiles("password_server.js", "server");
|
||||||
});
|
api.addFiles("password_client.js", "client");
|
||||||
|
});
|
||||||
Package.onTest((api) => {
|
|
||||||
api.use([
|
Package.onTest((api) => {
|
||||||
"accounts-password",
|
api.use([
|
||||||
"sha",
|
"accounts-password",
|
||||||
"tinytest",
|
"sha",
|
||||||
"test-helpers",
|
"tinytest",
|
||||||
"tracker",
|
"test-helpers",
|
||||||
"accounts-base",
|
"tracker",
|
||||||
"random",
|
"accounts-base",
|
||||||
"email",
|
"random",
|
||||||
"check",
|
"email",
|
||||||
"ddp",
|
"check",
|
||||||
"ecmascript",
|
"ddp",
|
||||||
]);
|
"ecmascript"
|
||||||
api.addFiles("password_tests_setup.js", "server");
|
]);
|
||||||
api.addFiles("password_tests.js", ["client", "server"]);
|
api.addFiles("password_tests_setup.js", "server");
|
||||||
api.addFiles("email_tests_setup.js", "server");
|
api.addFiles("password_tests.js", ["client", "server"]);
|
||||||
api.addFiles("email_tests.js", "client");
|
api.addFiles("email_tests_setup.js", "server");
|
||||||
});
|
api.addFiles("email_tests.js", "client");
|
||||||
|
api.addFiles("password_argon_tests.js", ["client", "server"]);
|
||||||
|
});
|
||||||
|
|||||||
221
packages/accounts-password/password_argon_tests.js
Normal file
221
packages/accounts-password/password_argon_tests.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -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";
|
import { Accounts } from "meteor/accounts-base";
|
||||||
|
|
||||||
// Utility for grabbing user
|
// Utility for grabbing user
|
||||||
@@ -6,8 +7,9 @@ const getUserById =
|
|||||||
async (id, options) =>
|
async (id, options) =>
|
||||||
await Meteor.users.findOneAsync(id, Accounts._addDefaultFieldSelector(options));
|
await Meteor.users.findOneAsync(id, Accounts._addDefaultFieldSelector(options));
|
||||||
|
|
||||||
// User records have a 'services.password.bcrypt' field on them to hold
|
// User records have two fields that are used for password-based login:
|
||||||
// their hashed passwords.
|
// - '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
|
// 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
|
// string (the plaintext password) or an object with keys 'digest' and
|
||||||
@@ -17,127 +19,274 @@ const getUserById =
|
|||||||
// strings.
|
// strings.
|
||||||
//
|
//
|
||||||
// When the server receives a plaintext password as a string, it always
|
// 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
|
// 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;
|
Accounts._bcryptRounds = () => Accounts._options.bcryptRounds || 10;
|
||||||
|
|
||||||
// Given a 'password' from the client, extract the string that we should
|
Accounts._argon2Enabled = () => Accounts._options.argon2Enabled || false;
|
||||||
// bcrypt. 'password' can be one of:
|
|
||||||
// - String (the plaintext password)
|
const ARGON2_TYPES = {
|
||||||
// - Object with 'digest' and 'algorithm' keys. 'algorithm' must be "sha-256".
|
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 => {
|
const getPasswordString = password => {
|
||||||
if (typeof password === "string") {
|
if (typeof password === "string") {
|
||||||
password = SHA256(password);
|
password = SHA256(password);
|
||||||
} else { // 'password' is an object
|
}
|
||||||
|
else { // 'password' is an object
|
||||||
if (password.algorithm !== "sha-256") {
|
if (password.algorithm !== "sha-256") {
|
||||||
throw new Error("Invalid password hash algorithm. " +
|
throw new Error("Invalid password hash algorithm. " +
|
||||||
"Only 'sha-256' is allowed.");
|
"Only 'sha-256' is allowed.");
|
||||||
}
|
}
|
||||||
password = password.digest;
|
password = password.digest;
|
||||||
}
|
}
|
||||||
return password;
|
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
|
* Encrypt the given `password` using either bcrypt or Argon2.
|
||||||
// SHA256 before bcrypt) or an object with properties `digest` and
|
* @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`).
|
||||||
// `algorithm` (in which case we bcrypt `password.digest`).
|
* @returns {Promise<string>} The encrypted password.
|
||||||
//
|
*/
|
||||||
const hashPassword = async password => {
|
const hashPassword = async (password) => {
|
||||||
password = getPasswordString(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.
|
// Extract the number of rounds used in the specified bcrypt hash.
|
||||||
const getRoundsFromBcryptHash = hash => {
|
const getRoundsFromBcryptHash = (hash) => {
|
||||||
let rounds;
|
let rounds;
|
||||||
if (hash) {
|
if (hash) {
|
||||||
const hashSegments = hash.split('$');
|
const hashSegments = hash.split("$");
|
||||||
if (hashSegments.length > 2) {
|
if (hashSegments.length > 2) {
|
||||||
rounds = parseInt(hashSegments[2], 10);
|
rounds = parseInt(hashSegments[2], 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rounds;
|
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
|
* Extract readable parameters from an Argon2 hash string.
|
||||||
// properties `digest` and `algorithm` (in which case we bcrypt
|
* @param {string} hash - The Argon2 hash string.
|
||||||
// `password.digest`).
|
* @returns {object} An object containing the parsed parameters.
|
||||||
//
|
* @throws {Error} If the hash format is invalid.
|
||||||
// The user parameter needs at least user._id and user.services
|
*/
|
||||||
Accounts._checkPasswordUserFields = {_id: 1, services: 1};
|
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<Object>} - 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 checkPasswordAsync = async (user, password) => {
|
||||||
const result = {
|
const result = {
|
||||||
userId: user._id
|
userId: user._id
|
||||||
};
|
};
|
||||||
|
|
||||||
const formattedPassword = getPasswordString(password);
|
const formattedPassword = getPasswordString(password);
|
||||||
const hash = user.services.password.bcrypt;
|
const hash = getUserPasswordHash(user);
|
||||||
const hashRounds = getRoundsFromBcryptHash(hash);
|
|
||||||
|
|
||||||
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 () => {
|
const argon2Enabled = Accounts._argon2Enabled();
|
||||||
await Meteor.users.updateAsync({ _id: user._id }, {
|
if (argon2Enabled === false) {
|
||||||
$set: {
|
if (isArgon(hash)) {
|
||||||
'services.password.bcrypt':
|
// this is a rollback feature, enabling to switch back from argon2 to bcrypt if needed
|
||||||
await bcryptHash(formattedPassword, Accounts._bcryptRounds())
|
// 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;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
Accounts._checkPasswordAsync = checkPasswordAsync;
|
Accounts._checkPasswordAsync = checkPasswordAsync;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// LOGIN
|
/// 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<Object>} 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<Object>} 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
|
// XXX maybe this belongs in the check package
|
||||||
const NonEmptyString = Match.Where(x => {
|
const NonEmptyString = Match.Where(x => {
|
||||||
@@ -185,9 +334,7 @@ Accounts.registerLoginHandler("password", async options => {
|
|||||||
Accounts._handleError("User not found");
|
Accounts._handleError("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!getUserPasswordHash(user)) {
|
||||||
if (!user.services || !user.services.password ||
|
|
||||||
!user.services.password.bcrypt) {
|
|
||||||
Accounts._handleError("User has no password set");
|
Accounts._handleError("User has no password set");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,51 +414,54 @@ Accounts.setUsername =
|
|||||||
// `digest` and `algorithm` (representing the SHA256 of the password).
|
// `digest` and `algorithm` (representing the SHA256 of the password).
|
||||||
Meteor.methods(
|
Meteor.methods(
|
||||||
{
|
{
|
||||||
changePassword: async function (oldPassword, newPassword) {
|
changePassword: async function(oldPassword, newPassword) {
|
||||||
check(oldPassword, passwordValidator);
|
check(oldPassword, passwordValidator);
|
||||||
check(newPassword, passwordValidator);
|
check(newPassword, passwordValidator);
|
||||||
|
|
||||||
if (!this.userId) {
|
if (!this.userId) {
|
||||||
throw new Meteor.Error(401, "Must be logged in");
|
throw new Meteor.Error(401, "Must be logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getUserById(this.userId, {fields: {
|
const user = await getUserById(this.userId, {
|
||||||
services: 1,
|
fields: {
|
||||||
...Accounts._checkPasswordUserFields,
|
services: 1,
|
||||||
}});
|
...Accounts._checkPasswordUserFields
|
||||||
if (!user) {
|
}
|
||||||
Accounts._handleError("User not found");
|
});
|
||||||
}
|
if (!user) {
|
||||||
|
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");
|
Accounts._handleError("User has no password set");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await checkPasswordAsync(user, oldPassword);
|
const result = await checkPasswordAsync(user, oldPassword);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw 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
|
await Meteor.users.updateAsync(
|
||||||
// the token for the current connection with a new one, but that would
|
{ _id: this.userId },
|
||||||
// be tricky, so we'll settle for just replacing all tokens other than
|
{
|
||||||
// the one for the current connection.
|
$set: updator.$set,
|
||||||
const currentToken = Accounts._getLoginToken(this.connection.id);
|
$pull: {
|
||||||
await Meteor.users.updateAsync(
|
"services.resume.loginTokens": { hashedToken: { $ne: currentToken } }
|
||||||
{ _id: this.userId },
|
},
|
||||||
{
|
$unset: { "services.password.reset": 1, ...updator.$unset }
|
||||||
$set: { 'services.password.bcrypt': hashed },
|
}
|
||||||
$pull: {
|
);
|
||||||
'services.resume.loginTokens': { hashedToken: { $ne: currentToken } }
|
|
||||||
},
|
return { passwordChanged: true };
|
||||||
$unset: { 'services.password.reset': 1 }
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return {passwordChanged: true};
|
|
||||||
}});
|
|
||||||
|
|
||||||
|
|
||||||
// Force change the users password.
|
// Force change the users password.
|
||||||
@@ -320,37 +470,34 @@ Meteor.methods(
|
|||||||
* @summary Forcibly change the password for a user.
|
* @summary Forcibly change the password for a user.
|
||||||
* @locus Server
|
* @locus Server
|
||||||
* @param {String} userId The id of the user to update.
|
* @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]
|
||||||
* @param {Object} options.logout Logout all current connections with this userId (default: true)
|
* @param {Object} options.logout Logout all current connections with this userId (default: true)
|
||||||
* @importFromPackage accounts-base
|
* @importFromPackage accounts-base
|
||||||
*/
|
*/
|
||||||
Accounts.setPasswordAsync =
|
Accounts.setPasswordAsync =
|
||||||
async (userId, newPlaintextPassword, options) => {
|
async (userId, newPlaintextPassword, options) => {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
|
check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256));
|
||||||
check(options, Match.Maybe({ logout: Boolean }));
|
check(options, Match.Maybe({ logout: Boolean }));
|
||||||
options = { logout: true , ...options };
|
options = { logout: true, ...options };
|
||||||
|
|
||||||
const user = await getUserById(userId, { fields: { _id: 1 } });
|
const user = await getUserById(userId, { fields: { _id: 1 } });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Meteor.Error(403, "User not found");
|
throw new Meteor.Error(403, "User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = {
|
let updator = await getUpdatorForUserPassword(newPlaintextPassword);
|
||||||
$unset: {
|
updator.$unset = updator.$unset || {};
|
||||||
'services.password.reset': 1
|
updator.$unset["services.password.reset"] = 1;
|
||||||
},
|
|
||||||
$set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)}
|
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
|
/// RESETTING VIA EMAIL
|
||||||
///
|
///
|
||||||
@@ -430,25 +577,32 @@ Accounts.generateResetToken =
|
|||||||
// if this method is called from the enroll account work-flow then
|
// if this method is called from the enroll account work-flow then
|
||||||
// store the token record in 'services.password.enroll' db field
|
// store the token record in 'services.password.enroll' db field
|
||||||
// else store the token record in in 'services.password.reset' db field
|
// else store the token record in in 'services.password.reset' db field
|
||||||
if(reason === 'enrollAccount') {
|
if (reason === "enrollAccount") {
|
||||||
await Meteor.users.updateAsync({_id: user._id}, {
|
await Meteor.users.updateAsync(
|
||||||
$set : {
|
{ _id: user._id },
|
||||||
'services.password.enroll': tokenRecord
|
{
|
||||||
|
$set: {
|
||||||
|
"services.password.enroll": tokenRecord
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
// before passing to template, update user object with new token
|
// before passing to template, update user object with new token
|
||||||
Meteor._ensure(user, 'services', 'password').enroll = tokenRecord;
|
Meteor._ensure(user, "services", "password").enroll = tokenRecord;
|
||||||
} else {
|
}
|
||||||
await Meteor.users.updateAsync({_id: user._id}, {
|
else {
|
||||||
$set : {
|
await Meteor.users.updateAsync(
|
||||||
'services.password.reset': tokenRecord
|
{ _id: user._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
"services.password.reset": tokenRecord
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
// before passing to template, update user object with new token
|
// 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) => {
|
async (userId, email, extraTokenData, extraParams) => {
|
||||||
const { email: realEmail, user, token } =
|
const { email: realEmail, user, token } =
|
||||||
await Accounts.generateResetToken(userId, email, 'resetPassword', extraTokenData);
|
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');
|
const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'resetPassword');
|
||||||
await Email.sendAsync(options);
|
await Email.sendAsync(options);
|
||||||
|
|
||||||
if (Meteor.isDevelopment) {
|
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||||
console.log(`\nReset password URL: ${ url }`);
|
console.log(`\nReset password URL: ${ url }`);
|
||||||
}
|
}
|
||||||
return { email: realEmail, user, token, url, options };
|
return { email: realEmail, user, token, url, options };
|
||||||
@@ -564,13 +718,13 @@ Accounts.sendEnrollmentEmail =
|
|||||||
const { email: realEmail, user, token } =
|
const { email: realEmail, user, token } =
|
||||||
await Accounts.generateResetToken(userId, email, 'enrollAccount', extraTokenData);
|
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 =
|
const options =
|
||||||
await Accounts.generateOptionsForEmail(realEmail, user, url, 'enrollAccount');
|
await Accounts.generateOptionsForEmail(realEmail, user, url, 'enrollAccount');
|
||||||
|
|
||||||
await Email.sendAsync(options);
|
await Email.sendAsync(options);
|
||||||
if (Meteor.isDevelopment) {
|
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||||
console.log(`\nEnrollment email URL: ${ url }`);
|
console.log(`\nEnrollment email URL: ${ url }`);
|
||||||
}
|
}
|
||||||
return { email: realEmail, user, token, url, options };
|
return { email: realEmail, user, token, url, options };
|
||||||
@@ -642,8 +796,6 @@ Meteor.methods(
|
|||||||
error: new Meteor.Error(403, "Token has invalid email address")
|
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
|
// 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
|
// 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
|
// happens. But also make sure not to leave the connection in a state
|
||||||
@@ -653,6 +805,8 @@ Meteor.methods(
|
|||||||
const resetToOldToken = () =>
|
const resetToOldToken = () =>
|
||||||
Accounts._setLoginToken(user._id, this.connection, oldToken);
|
Accounts._setLoginToken(user._id, this.connection, oldToken);
|
||||||
|
|
||||||
|
const updator = await getUpdatorForUserPassword(newPassword);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the user record by:
|
// Update the user record by:
|
||||||
// - Changing the password to the new one
|
// - Changing the password to the new one
|
||||||
@@ -664,29 +818,36 @@ Meteor.methods(
|
|||||||
affectedRecords = await Meteor.users.updateAsync(
|
affectedRecords = await Meteor.users.updateAsync(
|
||||||
{
|
{
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
'emails.address': email,
|
"emails.address": email,
|
||||||
'services.password.enroll.token': token
|
"services.password.enroll.token": token
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$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(
|
affectedRecords = await Meteor.users.updateAsync(
|
||||||
{
|
{
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
'emails.address': email,
|
"emails.address": email,
|
||||||
'services.password.reset.token': token
|
"services.password.reset.token": token
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$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)
|
if (affectedRecords !== 1)
|
||||||
@@ -704,15 +865,16 @@ Meteor.methods(
|
|||||||
await Accounts._clearAllLoginTokens(user._id);
|
await Accounts._clearAllLoginTokens(user._id);
|
||||||
|
|
||||||
if (Accounts._check2faEnabled?.(user)) {
|
if (Accounts._check2faEnabled?.(user)) {
|
||||||
return {
|
return {
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
error: Accounts._handleError(
|
error: Accounts._handleError(
|
||||||
'Changed password, but user not logged in because 2FA is enabled',
|
'Changed password, but user not logged in because 2FA is enabled',
|
||||||
false,
|
false,
|
||||||
'2fa-enabled'
|
'2fa-enabled'
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}return { userId: user._id };
|
}
|
||||||
|
return { userId: user._id };
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -745,10 +907,10 @@ Accounts.sendVerificationEmail =
|
|||||||
|
|
||||||
const { email: realEmail, user, token } =
|
const { email: realEmail, user, token } =
|
||||||
await Accounts.generateVerificationToken(userId, email, extraTokenData);
|
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');
|
const options = await Accounts.generateOptionsForEmail(realEmail, user, url, 'verifyEmail');
|
||||||
await Email.sendAsync(options);
|
await Email.sendAsync(options);
|
||||||
if (Meteor.isDevelopment) {
|
if (Meteor.isDevelopment && !Meteor.isPackageTest) {
|
||||||
console.log(`\nVerification email URL: ${ url }`);
|
console.log(`\nVerification email URL: ${ url }`);
|
||||||
}
|
}
|
||||||
return { email: realEmail, user, token, url, options };
|
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
|
* @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
|
* updating the database. The operation will fail if there is a different user
|
||||||
@@ -990,7 +1198,13 @@ const createUser =
|
|||||||
const user = { services: {} };
|
const user = { services: {} };
|
||||||
if (password) {
|
if (password) {
|
||||||
const hashed = await hashPassword(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 });
|
return await Accounts._createUserCheckingDuplicates({ user, email, username, options });
|
||||||
@@ -1074,17 +1288,7 @@ Accounts.createUserVerifyingEmail =
|
|||||||
// method calling Accounts.createUser could set?
|
// method calling Accounts.createUser could set?
|
||||||
//
|
//
|
||||||
|
|
||||||
Accounts.createUserAsync =
|
Accounts.createUserAsync = createUser
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create user directly on the server.
|
// Create user directly on the server.
|
||||||
//
|
//
|
||||||
@@ -1110,4 +1314,3 @@ await Meteor.users.createIndexAsync('services.password.reset.token',
|
|||||||
{ unique: true, sparse: true });
|
{ unique: true, sparse: true });
|
||||||
await Meteor.users.createIndexAsync('services.password.enroll.token',
|
await Meteor.users.createIndexAsync('services.password.enroll.token',
|
||||||
{ unique: true, sparse: true });
|
{ unique: true, sparse: true });
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const makeTestConnAsync =
|
|||||||
})
|
})
|
||||||
const simplePollAsync = (fn) =>
|
const simplePollAsync = (fn) =>
|
||||||
new Promise((resolve, reject) => simplePoll(fn,resolve,reject))
|
new Promise((resolve, reject) => simplePoll(fn,resolve,reject))
|
||||||
function hashPassword(password) {
|
function hashPasswordWithSha(password) {
|
||||||
return {
|
return {
|
||||||
digest: SHA256(password),
|
digest: SHA256(password),
|
||||||
algorithm: "sha-256"
|
algorithm: "sha-256"
|
||||||
@@ -54,23 +54,37 @@ if (Meteor.isClient) (() => {
|
|||||||
const removeSkipCaseInsensitiveChecksForTest = (value, test, expect) =>
|
const removeSkipCaseInsensitiveChecksForTest = (value, test, expect) =>
|
||||||
Meteor.call('removeSkipCaseInsensitiveChecksForTest', value);
|
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
|
// Hack because Tinytest does not clean the database between tests/runs
|
||||||
this.randomSuffix = Random.id(10);
|
this.randomSuffix = Random.id(10);
|
||||||
this.username = `AdaLovelace${ this.randomSuffix }`;
|
this.username = `AdaLovelace${ this.randomSuffix }`;
|
||||||
this.email = `Ada-intercept@lovelace.com${ this.randomSuffix }`;
|
this.email = `Ada-intercept@lovelace.com${ this.randomSuffix }`;
|
||||||
this.password = 'password';
|
this.password = 'password';
|
||||||
Accounts.createUser(
|
|
||||||
{ username: this.username, email: this.email, password: this.password },
|
Accounts.createUser(
|
||||||
loggedInAs(this.username, test, expect));
|
{ 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);
|
|
||||||
}));
|
|
||||||
const loggedInAs = (someUsername, test, expect) => {
|
const loggedInAs = (someUsername, test, expect) => {
|
||||||
return expect(error => {
|
return expect(error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -79,18 +93,7 @@ if (Meteor.isClient) (() => {
|
|||||||
test.equal(Meteor.userId() && Meteor.user().username, someUsername);
|
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 => {
|
const expectError = (expectedError, test, expect) => expect(actualError => {
|
||||||
test.equal(actualError && actualError.error, expectedError.error);
|
test.equal(actualError && actualError.error, expectedError.error);
|
||||||
test.equal(actualError && actualError.reason, expectedError.reason);
|
test.equal(actualError && actualError.reason, expectedError.reason);
|
||||||
@@ -486,7 +489,7 @@ if (Meteor.isClient) (() => {
|
|||||||
function (test, expect) {
|
function (test, expect) {
|
||||||
this.secondConn = DDP.connect(Meteor.absoluteUrl());
|
this.secondConn = DDP.connect(Meteor.absoluteUrl());
|
||||||
this.secondConn.call('login',
|
this.secondConn.call('login',
|
||||||
{ user: { username: this.username }, password: hashPassword(this.password) },
|
{ user: { username: this.username }, password: hashPasswordWithSha(this.password) },
|
||||||
expect((err, result) => {
|
expect((err, result) => {
|
||||||
test.isFalse(err);
|
test.isFalse(err);
|
||||||
this.secondConn.setUserId(result.id);
|
this.secondConn.setUserId(result.id);
|
||||||
@@ -802,7 +805,7 @@ if (Meteor.isClient) (() => {
|
|||||||
// Can update own profile using ID.
|
// Can update own profile using ID.
|
||||||
await Meteor.users.updateAsync(
|
await Meteor.users.updateAsync(
|
||||||
this.userId, { $set: { 'profile.updated': 42 } },
|
this.userId, { $set: { 'profile.updated': 42 } },
|
||||||
);
|
);
|
||||||
test.equal(42, Meteor.user().profile.updated);
|
test.equal(42, Meteor.user().profile.updated);
|
||||||
},
|
},
|
||||||
logoutStep
|
logoutStep
|
||||||
@@ -1212,10 +1215,10 @@ if (Meteor.isServer) (() => {
|
|||||||
|
|
||||||
// This test properly belongs in accounts-base/accounts_tests.js, but
|
// This test properly belongs in accounts-base/accounts_tests.js, but
|
||||||
// this is where the tests that actually log in are.
|
// 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(
|
await test.throwsAsync(
|
||||||
async () =>
|
async () =>
|
||||||
await Meteor.user()
|
await Meteor.userAsync()
|
||||||
);
|
);
|
||||||
await Meteor.users.removeAsync({});
|
await Meteor.users.removeAsync({});
|
||||||
});
|
});
|
||||||
@@ -1230,7 +1233,7 @@ if (Meteor.isServer) (() => {
|
|||||||
const username = Random.id();
|
const username = Random.id();
|
||||||
const id = await Accounts.createUser({
|
const id = await Accounts.createUser({
|
||||||
username: username,
|
username: username,
|
||||||
password: hashPassword('password')
|
password: hashPasswordWithSha('password')
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -1245,7 +1248,7 @@ if (Meteor.isServer) (() => {
|
|||||||
|
|
||||||
const result = await clientConn.callAsync('login', {
|
const result = await clientConn.callAsync('login', {
|
||||||
user: { username: username },
|
user: { username: username },
|
||||||
password: hashPassword('password')
|
password: hashPasswordWithSha('password')
|
||||||
});
|
});
|
||||||
|
|
||||||
test.isTrue(result);
|
test.isTrue(result);
|
||||||
@@ -1278,7 +1281,7 @@ if (Meteor.isServer) (() => {
|
|||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
username: username,
|
username: username,
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword("old-password")
|
password: hashPasswordWithSha("old-password")
|
||||||
});
|
});
|
||||||
const user = await Meteor.users.findOneAsync(userId);
|
const user = await Meteor.users.findOneAsync(userId);
|
||||||
|
|
||||||
@@ -1297,16 +1300,17 @@ if (Meteor.isServer) (() => {
|
|||||||
|
|
||||||
await test.throwsAsync(
|
await test.throwsAsync(
|
||||||
async () =>
|
async () =>
|
||||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password")),
|
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password")),
|
||||||
/Token has invalid email address/
|
/Token has invalid email address/
|
||||||
);
|
);
|
||||||
|
Accounts._options.ambiguousErrorMessages = true;
|
||||||
await test.throwsAsync(
|
await test.throwsAsync(
|
||||||
async () =>
|
async () =>
|
||||||
await Meteor.callAsync(
|
await Meteor.callAsync(
|
||||||
"login",
|
"login",
|
||||||
{
|
{
|
||||||
user: { username: username },
|
user: { username: username },
|
||||||
password: hashPassword("new-password")
|
password: hashPasswordWithSha("new-password")
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
/Something went wrong. Please check your credentials./);
|
/Something went wrong. Please check your credentials./);
|
||||||
@@ -1321,7 +1325,7 @@ if (Meteor.isServer) (() => {
|
|||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
username: username,
|
username: username,
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword("old-password")
|
password: hashPasswordWithSha("old-password")
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await Meteor.users.findOneAsync(userId);
|
const user = await Meteor.users.findOneAsync(userId);
|
||||||
@@ -1338,11 +1342,11 @@ if (Meteor.isServer) (() => {
|
|||||||
test.isTrue(await clientConn.callAsync(
|
test.isTrue(await clientConn.callAsync(
|
||||||
"resetPassword",
|
"resetPassword",
|
||||||
resetPasswordToken,
|
resetPasswordToken,
|
||||||
hashPassword("new-password")
|
hashPasswordWithSha("new-password")
|
||||||
));
|
));
|
||||||
test.isTrue(await clientConn.callAsync("login", {
|
test.isTrue(await clientConn.callAsync("login", {
|
||||||
user: { username },
|
user: { username },
|
||||||
password: hashPassword("new-password")
|
password: hashPasswordWithSha("new-password")
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1355,7 +1359,7 @@ if (Meteor.isServer) (() => {
|
|||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
username: username,
|
username: username,
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword("old-password")
|
password: hashPasswordWithSha("old-password")
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await Meteor.users.findOneAsync(userId);
|
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) } });
|
await Meteor.users.updateAsync(userId, { $set: { "services.password.reset.when": new Date(Date.now() + -5 * 24 * 3600 * 1000) } });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPassword("new-password"))
|
await Meteor.callAsync("resetPassword", resetPasswordToken, hashPasswordWithSha("new-password"))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
test.throws(() => {
|
test.throws(() => {
|
||||||
throw e;
|
throw e;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Accounts._options.ambiguousErrorMessages = true;
|
||||||
await test.throwsAsync(
|
await test.throwsAsync(
|
||||||
async () => await Meteor.callAsync(
|
async () => await Meteor.callAsync(
|
||||||
"login",
|
"login",
|
||||||
{
|
{
|
||||||
user: { username: username },
|
user: { username: username },
|
||||||
password: hashPassword("new-password")
|
password: hashPasswordWithSha("new-password")
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
/Something went wrong. Please check your credentials./);
|
/Something went wrong. Please check your credentials./);
|
||||||
@@ -1405,7 +1410,7 @@ if (Meteor.isServer) (() => {
|
|||||||
{
|
{
|
||||||
username: username,
|
username: username,
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword(password)
|
password: hashPasswordWithSha(password)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1432,7 +1437,7 @@ if (Meteor.isServer) (() => {
|
|||||||
await Accounts.createUser(
|
await Accounts.createUser(
|
||||||
{
|
{
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword('password')
|
password: hashPasswordWithSha('password')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await Accounts.sendResetPasswordEmail(userId, email);
|
await Accounts.sendResetPasswordEmail(userId, email);
|
||||||
@@ -1452,7 +1457,7 @@ if (Meteor.isServer) (() => {
|
|||||||
await Accounts.createUser(
|
await Accounts.createUser(
|
||||||
{
|
{
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword('password')
|
password: hashPasswordWithSha('password')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await Accounts.sendResetPasswordEmail(userId, email);
|
await Accounts.sendResetPasswordEmail(userId, email);
|
||||||
@@ -1498,12 +1503,12 @@ if (Meteor.isServer) (() => {
|
|||||||
await clientConn.callAsync(
|
await clientConn.callAsync(
|
||||||
"resetPassword",
|
"resetPassword",
|
||||||
enrollPasswordToken,
|
enrollPasswordToken,
|
||||||
hashPassword("new-password"))
|
hashPasswordWithSha("new-password"))
|
||||||
);
|
);
|
||||||
test.isTrue(
|
test.isTrue(
|
||||||
await clientConn.callAsync("login", {
|
await clientConn.callAsync("login", {
|
||||||
user: { username },
|
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 Meteor.users.updateAsync(userId, { $set: { "services.password.enroll.when": new Date(Date.now() + -35 * 24 * 3600 * 1000) } });
|
||||||
|
|
||||||
await test.throwsAsync(
|
await test.throwsAsync(
|
||||||
async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPassword("new-password")),
|
async () => await Meteor.callAsync("resetPassword", enrollPasswordToken, hashPasswordWithSha("new-password")),
|
||||||
/Token expired/
|
/Token expired/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1544,7 +1549,7 @@ if (Meteor.isServer) (() => {
|
|||||||
async test => {
|
async test => {
|
||||||
const email = `${ test.id }-intercept@example.com`;
|
const email = `${ test.id }-intercept@example.com`;
|
||||||
const userId =
|
const userId =
|
||||||
await Accounts.createUser({ email: email, password: hashPassword('password') });
|
await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') });
|
||||||
|
|
||||||
await Accounts.sendEnrollmentEmail(userId, email);
|
await Accounts.sendEnrollmentEmail(userId, email);
|
||||||
const user1 = await Meteor.users.findOneAsync(userId);
|
const user1 = await Meteor.users.findOneAsync(userId);
|
||||||
@@ -1561,7 +1566,7 @@ if (Meteor.isServer) (() => {
|
|||||||
const userId =
|
const userId =
|
||||||
await Accounts.createUser({
|
await Accounts.createUser({
|
||||||
email: email,
|
email: email,
|
||||||
password: hashPassword('password')
|
password: hashPasswordWithSha('password')
|
||||||
});
|
});
|
||||||
|
|
||||||
await Accounts.sendEnrollmentEmail(userId, email);
|
await Accounts.sendEnrollmentEmail(userId, email);
|
||||||
@@ -1580,7 +1585,7 @@ if (Meteor.isServer) (() => {
|
|||||||
async test => {
|
async test => {
|
||||||
const email = `${ test.id }-intercept@example.com`;
|
const email = `${ test.id }-intercept@example.com`;
|
||||||
const userId =
|
const userId =
|
||||||
await Accounts.createUser({ email: email, password: hashPassword('password') });
|
await Accounts.createUser({ email: email, password: hashPasswordWithSha('password') });
|
||||||
|
|
||||||
await Accounts.sendResetPasswordEmail(userId, email);
|
await Accounts.sendResetPasswordEmail(userId, email);
|
||||||
const user1 = await Meteor.users.findOneAsync(userId);
|
const user1 = await Meteor.users.findOneAsync(userId);
|
||||||
@@ -1669,6 +1674,7 @@ if (Meteor.isServer) (() => {
|
|||||||
test.isTrue(userId1);
|
test.isTrue(userId1);
|
||||||
test.isTrue(userId2);
|
test.isTrue(userId2);
|
||||||
|
|
||||||
|
Accounts._options.ambiguousErrorMessages = false;
|
||||||
await test.throwsAsync(
|
await test.throwsAsync(
|
||||||
async () => await Accounts.setUsername(userId2, usernameUpper),
|
async () => await Accounts.setUsername(userId2, usernameUpper),
|
||||||
/Username already exists/
|
/Username already exists/
|
||||||
@@ -1727,108 +1733,131 @@ if (Meteor.isServer) (() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Tinytest.addAsync("passwords - add email when user has not an existing email",
|
Tinytest.addAsync("passwords - add email when user has not an existing email",
|
||||||
async test => {
|
async test => {
|
||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
username: `user${ Random.id() }`
|
username: `user${ Random.id() }`
|
||||||
});
|
});
|
||||||
|
|
||||||
const newEmail = `${ Random.id() }@turing.com`;
|
const newEmail = `${ Random.id() }@turing.com`;
|
||||||
await Accounts.addEmailAsync(userId, newEmail);
|
await Accounts.addEmailAsync(userId, newEmail);
|
||||||
const u1 = await Accounts._findUserByQuery({ id: userId })
|
const u1 = await Accounts._findUserByQuery({ id: userId })
|
||||||
test.equal(u1.emails, [
|
test.equal(u1.emails, [
|
||||||
{ address: newEmail, verified: false },
|
{ address: newEmail, verified: false },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
Tinytest.addAsync("passwords - add email when the user has an existing email " +
|
Tinytest.addAsync("passwords - add email when the user has an existing email " +
|
||||||
"only differing in case",
|
"only differing in case",
|
||||||
async test => {
|
async test => {
|
||||||
const origEmail = `${ Random.id() }@turing.com`;
|
const origEmail = `${ Random.id() }@turing.com`;
|
||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
email: origEmail
|
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 " +
|
Tinytest.addAsync("passwords - add email should fail when there is an existing " +
|
||||||
"user with an email only differing in case",
|
"user with an email only differing in case",
|
||||||
async test => {
|
async test => {
|
||||||
const user1Email = `${ Random.id() }@turing.com`;
|
const user1Email = `${ Random.id() }@turing.com`;
|
||||||
const userId1 = await Accounts.createUser({
|
const userId1 = await Accounts.createUser({
|
||||||
email: user1Email
|
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 })
|
Tinytest.addAsync("accounts emails - replace email", async test => {
|
||||||
test.equal(u1.emails, [
|
const origEmail = `originalemail@test.com`;
|
||||||
{ address: user1Email, verified: false }
|
const userId = await Accounts.createUserAsync({
|
||||||
]);
|
email: origEmail,
|
||||||
const u2 = await Accounts._findUserByQuery({ id: userId2 })
|
password: 'password'
|
||||||
test.equal(u2.emails, [
|
|
||||||
{ address: user2Email, verified: false }
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 => {
|
async test => {
|
||||||
const origEmail = `${ Random.id() }@turing.com`;
|
const origEmail = `${ Random.id() }@turing.com`;
|
||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
email: origEmail
|
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 =>
|
const getUserHashRounds = user =>
|
||||||
Number(user.services.password.bcrypt.substring(4, 6));
|
Number(user.services.password.bcrypt.substring(4, 6));
|
||||||
testAsyncMulti("passwords - allow custom bcrypt rounds",[
|
testAsyncMulti("passwords - allow custom bcrypt rounds",[
|
||||||
async function (test) {
|
async function (test) {
|
||||||
// Verify that a bcrypt hash generated for a new account uses the
|
// Verify that a bcrypt hash generated for a new account uses the
|
||||||
let username = Random.id();
|
let username = Random.id();
|
||||||
this.password = hashPassword('abc123');
|
this.password = hashPasswordWithSha('abc123');
|
||||||
this.userId1 = await Accounts.createUser({ username, password: this.password });
|
this.userId1 = await Accounts.createUser({ username, password: this.password });
|
||||||
this.user1 = await Meteor.users.findOneAsync(this.userId1);
|
this.user1 = await Meteor.users.findOneAsync(this.userId1);
|
||||||
let rounds = getUserHashRounds(this.user1);
|
let rounds = getUserHashRounds(this.user1);
|
||||||
@@ -1876,24 +1905,153 @@ if (Meteor.isServer) (() => {
|
|||||||
|
|
||||||
Tinytest.addAsync('passwords - extra params in email urls',
|
Tinytest.addAsync('passwords - extra params in email urls',
|
||||||
async (test) => {
|
async (test) => {
|
||||||
const username = Random.id();
|
const username = Random.id();
|
||||||
const email = `${ username }-intercept@example.com`;
|
const email = `${ username }-intercept@example.com`;
|
||||||
|
|
||||||
const userId = await Accounts.createUser({
|
const userId = await Accounts.createUser({
|
||||||
username: username,
|
username: username,
|
||||||
email: email
|
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' };
|
Tinytest.addAsync('passwords - createUserAsync', async test => {
|
||||||
await Accounts.sendEnrollmentEmail(userId, email, null, extraParams);
|
const username = Random.id();
|
||||||
|
const email = `${username}-intercept@example.com`;
|
||||||
|
const password = 'password';
|
||||||
|
|
||||||
const [enrollPasswordEmailOptions] =
|
const userId = await Accounts.createUserAsync({
|
||||||
await Meteor.callAsync("getInterceptedEmails", email);
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
});
|
||||||
|
|
||||||
const re = new RegExp(`${Meteor.absoluteUrl()}(\\S*)`);
|
test.isTrue(userId, 'User ID should be returned');
|
||||||
const match = enrollPasswordEmailOptions.text.match(re);
|
const user = await Meteor.users.findOneAsync(userId);
|
||||||
const url = new URL(match)
|
test.equal(user.username, username, 'Username should match');
|
||||||
test.equal(url.searchParams.get('test'), extraParams.test);
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ Accounts.config({
|
|||||||
Meteor.methods(
|
Meteor.methods(
|
||||||
{
|
{
|
||||||
testMeteorUser:
|
testMeteorUser:
|
||||||
async () => await Meteor.user(),
|
async () => await Meteor.userAsync(),
|
||||||
|
|
||||||
clearUsernameAndProfile:
|
clearUsernameAndProfile:
|
||||||
async function () {
|
async function () {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: 'No-password login/sign-up support for accounts',
|
summary: 'No-password login/sign-up support for accounts',
|
||||||
version: '3.0.0',
|
version: '3.0.2',
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ Meteor.methods({
|
|||||||
*/
|
*/
|
||||||
Accounts.sendLoginTokenEmail = async ({ userId, sequence, email, extra = {} }) => {
|
Accounts.sendLoginTokenEmail = async ({ userId, sequence, email, extra = {} }) => {
|
||||||
const user = await getUserById(userId);
|
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(
|
const options = await Accounts.generateOptionsForEmail(
|
||||||
email,
|
email,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const CollectionPrototype = AllowDeny.CollectionPrototype;
|
|||||||
* @memberOf Mongo.Collection
|
* @memberOf Mongo.Collection
|
||||||
* @instance
|
* @instance
|
||||||
* @param {Object} options
|
* @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 {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.
|
* @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
|
* @memberOf Mongo.Collection
|
||||||
* @instance
|
* @instance
|
||||||
* @param {Object} options
|
* @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 {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.
|
* @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.
|
// single-ID selectors.
|
||||||
if (!isInsert(method)) throwIfSelectorIsNotId(args[0], method);
|
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) {
|
if (self._restricted) {
|
||||||
// short circuit if there is no way it will pass.
|
// 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(
|
throw new Meteor.Error(
|
||||||
403,
|
403,
|
||||||
'Access denied. No allow validators set on restricted ' +
|
'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);
|
args.unshift(this.userId);
|
||||||
isInsert(method) && args.push(generatedId);
|
isInsert(method) && args.push(generatedId);
|
||||||
return self[validatedMethodName].apply(self, args);
|
return self[validatedMethodName].apply(self, args);
|
||||||
@@ -292,7 +292,7 @@ CollectionPrototype._validatedInsertAsync = async function(userId, doc,
|
|||||||
const self = this;
|
const self = this;
|
||||||
// call user validators.
|
// call user validators.
|
||||||
// Any deny returns true means denied.
|
// 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));
|
const result = validator(userId, docToValidate(validator, doc, generatedId));
|
||||||
return Meteor._isPromise(result) ? await result : result;
|
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.
|
// 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));
|
const result = validator(userId, docToValidate(validator, doc, generatedId));
|
||||||
return !(Meteor._isPromise(result) ? await result : result);
|
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);
|
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
|
// Simulate a mongo `update` operation while validating that the access
|
||||||
// control rules set by calls to `allow/deny` are satisfied. If all
|
// 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
|
// pass, rewrite the mongo operation to use $in to set the list of
|
||||||
@@ -414,7 +384,7 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
|||||||
|
|
||||||
// call user validators.
|
// call user validators.
|
||||||
// Any deny returns true means denied.
|
// 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 factoriedDoc = transformDoc(validator, doc);
|
||||||
const result = validator(userId,
|
const result = validator(userId,
|
||||||
factoriedDoc,
|
factoriedDoc,
|
||||||
@@ -424,8 +394,9 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
|||||||
})) {
|
})) {
|
||||||
throw new Meteor.Error(403, "Access denied");
|
throw new Meteor.Error(403, "Access denied");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any allow returns true means proceed. Throw error if they all fail.
|
// 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 factoriedDoc = transformDoc(validator, doc);
|
||||||
const result = validator(userId,
|
const result = validator(userId,
|
||||||
factoriedDoc,
|
factoriedDoc,
|
||||||
@@ -447,102 +418,6 @@ CollectionPrototype._validatedUpdateAsync = async function(
|
|||||||
self._collection, selector, mutator, options);
|
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
|
// Only allow these operations in validated updates. Specifically
|
||||||
// whitelist operations, rather than blacklist, so new complex
|
// whitelist operations, rather than blacklist, so new complex
|
||||||
// operations that are added aren't automatically allowed. A complex
|
// operations that are added aren't automatically allowed. A complex
|
||||||
@@ -573,14 +448,14 @@ CollectionPrototype._validatedRemoveAsync = async function(userId, selector) {
|
|||||||
|
|
||||||
// call user validators.
|
// call user validators.
|
||||||
// Any deny returns true means denied.
|
// 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));
|
const result = validator(userId, transformDoc(validator, doc));
|
||||||
return Meteor._isPromise(result) ? await result : result;
|
return Meteor._isPromise(result) ? await result : result;
|
||||||
})) {
|
})) {
|
||||||
throw new Meteor.Error(403, "Access denied");
|
throw new Meteor.Error(403, "Access denied");
|
||||||
}
|
}
|
||||||
// Any allow returns true means proceed. Throw error if they all fail.
|
// 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));
|
const result = validator(userId, transformDoc(validator, doc));
|
||||||
return !(Meteor._isPromise(result) ? await result : result);
|
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);
|
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 = {}) {
|
CollectionPrototype._callMutatorMethodAsync = function _callMutatorMethodAsync(name, args, options = {}) {
|
||||||
|
|
||||||
// For two out of three mutator methods, the first argument is a selector
|
// 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) => {
|
Object.keys(options).forEach((key) => {
|
||||||
if (!validKeysRegEx.test(key))
|
if (!validKeysRegEx.test(key))
|
||||||
throw new Error(allowOrDeny + ": Invalid key: " + 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;
|
collection._restricted = true;
|
||||||
@@ -740,7 +585,9 @@ function addValidator(collection, allowOrDeny, options) {
|
|||||||
options.transform
|
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]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'allow-deny',
|
name: 'allow-deny',
|
||||||
version: '2.0.0',
|
version: '2.1.0',
|
||||||
// Brief, one-line summary of the package.
|
// Brief, one-line summary of the package.
|
||||||
summary: 'Implements functionality for allow/deny and client-side db operations',
|
summary: 'Implements functionality for allow/deny and client-side db operations',
|
||||||
// URL to the Git repository containing the source code for this package.
|
// URL to the Git repository containing the source code for this package.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
// The ID of each document is the client architecture, and the fields of
|
// The ID of each document is the client architecture, and the fields of
|
||||||
// the document are the versions described above.
|
// the document are the versions described above.
|
||||||
|
|
||||||
|
import { onMessage } from "meteor/inter-process-messaging";
|
||||||
import { ClientVersions } from "./client_versions.js";
|
import { ClientVersions } from "./client_versions.js";
|
||||||
|
|
||||||
export const Autoupdate = __meteor_runtime_config__.autoupdate = {
|
export const Autoupdate = __meteor_runtime_config__.autoupdate = {
|
||||||
@@ -152,7 +153,6 @@ function enqueueVersionsRefresh() {
|
|||||||
|
|
||||||
const setupListeners = () => {
|
const setupListeners = () => {
|
||||||
// Listen for messages pertaining to the client-refresh topic.
|
// Listen for messages pertaining to the client-refresh topic.
|
||||||
import { onMessage } from "meteor/inter-process-messaging";
|
|
||||||
onMessage("client-refresh", enqueueVersionsRefresh);
|
onMessage("client-refresh", enqueueVersionsRefresh);
|
||||||
|
|
||||||
// Another way to tell the process to refresh: send SIGHUP signal
|
// Another way to tell the process to refresh: send SIGHUP signal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: 'Update the client when new client code is available',
|
summary: 'Update the client when new client code is available',
|
||||||
version: '2.0.0',
|
version: '2.0.1',
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
Package.onUse(function(api) {
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
var semver = Npm.require("semver");
|
var semver = Npm.require("semver");
|
||||||
var JSON5 = Npm.require("json5");
|
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
|
* A compiler that can be instantiated with features and used inside
|
||||||
* Plugin.registerCompiler
|
* Plugin.registerCompiler
|
||||||
* @param {Object} extraFeatures The same object that getDefaultOptions takes
|
* @param {Object} extraFeatures The same object that getDefaultOptions takes
|
||||||
*/
|
*/
|
||||||
BabelCompiler = function BabelCompiler(extraFeatures, modifyBabelConfig) {
|
BabelCompiler = function BabelCompiler(extraFeatures, modifyConfig) {
|
||||||
this.extraFeatures = extraFeatures;
|
this.extraFeatures = extraFeatures;
|
||||||
this.modifyBabelConfig = modifyBabelConfig;
|
this.modifyConfig = modifyConfig;
|
||||||
this._babelrcCache = null;
|
this._babelrcCache = null;
|
||||||
this._babelrcWarnings = Object.create(null);
|
this._babelrcWarnings = Object.create(null);
|
||||||
this.cacheDirectory = null;
|
this.cacheDirectory = null;
|
||||||
@@ -18,18 +25,180 @@ var BCp = BabelCompiler.prototype;
|
|||||||
var excludedFileExtensionPattern = /\.(es5|min)\.js$/i;
|
var excludedFileExtensionPattern = /\.(es5|min)\.js$/i;
|
||||||
var hasOwn = Object.prototype.hasOwnProperty;
|
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
|
// 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.
|
// whether it's Meteor 1.4.4 or earlier by checking the Node version.
|
||||||
var isMeteorPre144 = semver.lt(process.version, "4.8.1");
|
var isMeteorPre144 = semver.lt(process.version, "4.8.1");
|
||||||
|
|
||||||
var enableClientTLA = process.env.METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT === 'true';
|
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) {
|
BCp.processFilesForTarget = function (inputFiles) {
|
||||||
var compiler = this;
|
var compiler = this;
|
||||||
|
|
||||||
// Reset this cache for each batch processed.
|
// Reset this cache for each batch processed.
|
||||||
this._babelrcCache = null;
|
this._babelrcCache = null;
|
||||||
|
|
||||||
|
this.initializeMeteorAppConfig();
|
||||||
|
this.initializeMeteorAppSwcrc();
|
||||||
|
this.initializeMeteorAppLegacyConfig();
|
||||||
|
this.initializeMeteorAppSwcHelpersAvailable();
|
||||||
|
|
||||||
inputFiles.forEach(function (inputFile) {
|
inputFiles.forEach(function (inputFile) {
|
||||||
if (inputFile.supportsLazyCompilation) {
|
if (inputFile.supportsLazyCompilation) {
|
||||||
inputFile.addJavaScript({
|
inputFile.addJavaScript({
|
||||||
@@ -51,6 +220,8 @@ BCp.processFilesForTarget = function (inputFiles) {
|
|||||||
// null to indicate there was an error, and nothing should be added.
|
// null to indicate there was an error, and nothing should be added.
|
||||||
BCp.processOneFileForTarget = function (inputFile, source) {
|
BCp.processOneFileForTarget = function (inputFile, source) {
|
||||||
this._babelrcCache = this._babelrcCache || Object.create(null);
|
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") {
|
if (typeof source !== "string") {
|
||||||
// Other compiler plugins can call processOneFileForTarget with a
|
// Other compiler plugins can call processOneFileForTarget with a
|
||||||
@@ -92,6 +263,8 @@ BCp.processOneFileForTarget = function (inputFile, source) {
|
|||||||
features.nodeMajorVersion = parseInt(process.versions.node, 10);
|
features.nodeMajorVersion = parseInt(process.versions.node, 10);
|
||||||
} else if (arch === "web.browser") {
|
} else if (arch === "web.browser") {
|
||||||
features.modernBrowsers = true;
|
features.modernBrowsers = true;
|
||||||
|
} else if (arch === "web.cordova") {
|
||||||
|
features.modernBrowsers = ! getMeteorConfig()?.cordova?.disableModern;
|
||||||
}
|
}
|
||||||
|
|
||||||
features.topLevelAwait = inputFile.supportsTopLevelAwait &&
|
features.topLevelAwait = inputFile.supportsTopLevelAwait &&
|
||||||
@@ -121,32 +294,202 @@ BCp.processOneFileForTarget = function (inputFile, source) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.inferTypeScriptConfig(
|
const filename = packageName
|
||||||
features, inputFile, cacheOptions.cacheDeps);
|
? `packages/${packageName}/${inputFilePath}`
|
||||||
|
|
||||||
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
|
|
||||||
: inputFilePath;
|
: inputFilePath;
|
||||||
|
|
||||||
if (this.modifyBabelConfig) {
|
const setupBabelOptions = () => {
|
||||||
this.modifyBabelConfig(babelOptions, inputFile);
|
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 {
|
try {
|
||||||
var result = profile('Babel.compile', function () {
|
var result = (() => {
|
||||||
return Babel.compile(source, babelOptions, cacheOptions);
|
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) {
|
} catch (e) {
|
||||||
if (e.loc) {
|
if (e.loc) {
|
||||||
// Error is from @babel/parser.
|
// 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) {
|
BCp._inferFromBabelRc = function (inputFile, babelOptions, cacheDeps) {
|
||||||
var babelrcPath = inputFile.findControlFile(".babelrc");
|
var babelrcPath = inputFile.findControlFile(".babelrc");
|
||||||
if (babelrcPath) {
|
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) {
|
BCp._inferHelper = function (inputFile, cacheEntry) {
|
||||||
if (! cacheEntry.controlFileData) {
|
if (! cacheEntry.controlFileData) {
|
||||||
return false;
|
return false;
|
||||||
@@ -564,3 +975,245 @@ function packageNameFromTopLevelModuleId(id) {
|
|||||||
}
|
}
|
||||||
return parts[0];
|
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;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
name: "babel-compiler",
|
name: "babel-compiler",
|
||||||
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
|
summary: "Parser/transpiler for ECMAScript 2015+ syntax",
|
||||||
version: '7.11.0',
|
version: '7.12.2',
|
||||||
});
|
});
|
||||||
|
|
||||||
Npm.depends({
|
Npm.depends({
|
||||||
'@meteorjs/babel': '7.20.0-beta.4',
|
'@meteorjs/babel': '7.20.1',
|
||||||
'json5': '2.1.1',
|
'json5': '2.2.3',
|
||||||
'semver': '7.3.8'
|
'semver': '7.6.3',
|
||||||
|
"@meteorjs/swc-core": "1.12.14",
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
@@ -22,4 +23,5 @@ Package.onUse(function (api) {
|
|||||||
|
|
||||||
api.export('Babel', 'server');
|
api.export('Babel', 'server');
|
||||||
api.export('BabelCompiler', 'server');
|
api.export('BabelCompiler', 'server');
|
||||||
|
api.export('SwcCompiler', 'server');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ Package.describe({
|
|||||||
// These tests are in a separate package so that we can Npm.depend on
|
// These tests are in a separate package so that we can Npm.depend on
|
||||||
// parse5, a html parsing library.
|
// parse5, a html parsing library.
|
||||||
summary: "Tests for the boilerplate-generator package",
|
summary: "Tests for the boilerplate-generator package",
|
||||||
version: '1.5.2',
|
version: '1.5.3',
|
||||||
documentation: null
|
documentation: null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ Npm.depends({
|
|||||||
Package.onTest(function (api) {
|
Package.onTest(function (api) {
|
||||||
api.use('ecmascript');
|
api.use('ecmascript');
|
||||||
api.use([
|
api.use([
|
||||||
'underscore',
|
|
||||||
'tinytest',
|
'tinytest',
|
||||||
'boilerplate-generator'
|
'boilerplate-generator'
|
||||||
], 'server');
|
], 'server');
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { parse, serialize } from 'parse5';
|
import { parse, serialize } from 'parse5';
|
||||||
import { generateHTMLForArch } from './test-lib';
|
import { generateHTMLForArch } from './test-lib';
|
||||||
import { _ } from 'meteor/underscore';
|
|
||||||
|
|
||||||
Tinytest.addAsync(
|
Tinytest.addAsync(
|
||||||
"boilerplate-generator-tests - web.browser - basic output",
|
"boilerplate-generator-tests - web.browser - basic output",
|
||||||
@@ -66,10 +65,6 @@ Tinytest.addAsync(
|
|||||||
async function (test) {
|
async function (test) {
|
||||||
const newHtml = await generateHTMLForArch("web.browser", false);
|
const newHtml = await generateHTMLForArch("web.browser", false);
|
||||||
|
|
||||||
_.templateSettings = {
|
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
|
||||||
};
|
|
||||||
|
|
||||||
test.matches(newHtml, /foo="foobar"/);
|
test.matches(newHtml, /foo="foobar"/);
|
||||||
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
||||||
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { parse, serialize } from 'parse5';
|
import { parse, serialize } from 'parse5';
|
||||||
import { generateHTMLForArch } from './test-lib';
|
import { generateHTMLForArch } from './test-lib';
|
||||||
import { _ } from 'meteor/underscore';
|
|
||||||
|
|
||||||
Tinytest.addAsync(
|
Tinytest.addAsync(
|
||||||
"boilerplate-generator-tests - web.cordova - basic output",
|
"boilerplate-generator-tests - web.cordova - basic output",
|
||||||
@@ -60,9 +59,7 @@ Tinytest.addAsync(
|
|||||||
async function (test) {
|
async function (test) {
|
||||||
const newHtml = await generateHTMLForArch('web.cordova', false);
|
const newHtml = await generateHTMLForArch('web.cordova', false);
|
||||||
|
|
||||||
_.templateSettings = {
|
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
|
||||||
};
|
|
||||||
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
test.matches(newHtml, /<link[^<>]*href="[^<>]*bootstrap[^<>]*">/);
|
||||||
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
test.matches(newHtml, /<script[^<>]*src="[^<>]*templating[^<>]*">/);
|
||||||
test.matches(newHtml, /<script>var a/);
|
test.matches(newHtml, /<script>var a/);
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"lockfileVersion": 4,
|
|
||||||
"dependencies": {
|
|
||||||
"bluebird": {
|
|
||||||
"version": "2.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
|
||||||
"integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ=="
|
|
||||||
},
|
|
||||||
"combined-stream2": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream2/-/combined-stream2-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-sVqUHJmbdVm+HZWy4l34BPLczxI4fltN4Bm2vcvASsqBIXW4xFb4TRkwM8bw/UUXK9/OdHdAwi2cRYVEKrxzbg=="
|
|
||||||
},
|
|
||||||
"debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="
|
|
||||||
},
|
|
||||||
"lodash._reinterpolate": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA=="
|
|
||||||
},
|
|
||||||
"lodash.template": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
|
||||||
"integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A=="
|
|
||||||
},
|
|
||||||
"lodash.templatesettings": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ=="
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
|
||||||
},
|
|
||||||
"stream-length": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import {readFileSync} from 'fs';
|
import {readFileSync} from 'fs';
|
||||||
import { create as createStream } from "combined-stream2";
|
import { create as createStream } from "combined-stream2";
|
||||||
|
|
||||||
import WebBrowserTemplate from './template-web.browser';
|
import { headTemplate as modernHeadTemplate, closeTemplate as modernCloseTemplate } from './template-web.browser';
|
||||||
import WebCordovaTemplate from './template-web.cordova';
|
import { headTemplate as cordovaHeadTemplate, closeTemplate as cordovaCloseTemplate } from './template-web.cordova';
|
||||||
|
|
||||||
// Copied from webapp_server
|
// Copied from webapp_server
|
||||||
const readUtf8FileSync = filename => readFileSync(filename, 'utf8');
|
const readUtf8FileSync = filename => readFileSync(filename, 'utf8');
|
||||||
@@ -151,11 +151,11 @@ function getTemplate(arch) {
|
|||||||
const prefix = arch.split(".", 2).join(".");
|
const prefix = arch.split(".", 2).join(".");
|
||||||
|
|
||||||
if (prefix === "web.browser") {
|
if (prefix === "web.browser") {
|
||||||
return WebBrowserTemplate;
|
return { headTemplate: modernHeadTemplate, closeTemplate: modernCloseTemplate };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefix === "web.cordova") {
|
if (prefix === "web.cordova") {
|
||||||
return WebCordovaTemplate;
|
return { headTemplate: cordovaHeadTemplate, closeTemplate: cordovaCloseTemplate };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Unsupported arch: " + arch);
|
throw new Error("Unsupported arch: " + arch);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "Generates the boilerplate html from program's manifest",
|
summary: "Generates the boilerplate html from program's manifest",
|
||||||
version: '2.0.0',
|
version: '2.0.2',
|
||||||
});
|
});
|
||||||
|
|
||||||
Npm.depends({
|
Npm.depends({
|
||||||
"combined-stream2": "1.1.2",
|
"combined-stream2": "1.1.2"
|
||||||
"lodash.template": "4.5.0"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
|||||||
@@ -1,14 +1,134 @@
|
|||||||
import lodashTemplate from 'lodash.template';
|
/**
|
||||||
|
* Internal full-featured implementation of lodash.template (inspired by v4.5.0)
|
||||||
|
* embedded to eliminate the external dependency while preserving functionality.
|
||||||
|
*
|
||||||
|
* MIT License (c) JS Foundation and other contributors <https://js.foundation/>
|
||||||
|
* Adapted for Meteor boilerplate-generator (only the pieces required by template were extracted).
|
||||||
|
*/
|
||||||
|
|
||||||
// As identified in issue #9149, when an application overrides the default
|
// ---------------------------------------------------------------------------
|
||||||
// _.template settings using _.templateSettings, those new settings are
|
// Utility & regex definitions (mirroring lodash pieces used by template)
|
||||||
// used anywhere _.template is used, including within the
|
// ---------------------------------------------------------------------------
|
||||||
// boilerplate-generator. To handle this, _.template settings that have
|
|
||||||
// been verified to work are overridden here on each _.template call.
|
const reEmptyStringLeading = /\b__p \+= '';/g;
|
||||||
export default function template(text) {
|
const reEmptyStringMiddle = /\b(__p \+=) '' \+/g;
|
||||||
return lodashTemplate(text, null, {
|
const reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
|
||||||
evaluate : /<%([\s\S]+?)%>/g,
|
|
||||||
interpolate : /<%=([\s\S]+?)%>/g,
|
const reEscape = /<%-([\s\S]+?)%>/g; // escape delimiter
|
||||||
escape : /<%-([\s\S]+?)%>/g,
|
const reEvaluate = /<%([\s\S]+?)%>/g; // evaluate delimiter
|
||||||
|
const reInterpolate = /<%=([\s\S]+?)%>/g; // interpolate delimiter
|
||||||
|
const reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; // ES6 template literal capture
|
||||||
|
const reUnescapedString = /['\\\n\r\u2028\u2029]/g; // string literal escapes
|
||||||
|
|
||||||
|
// HTML escape
|
||||||
|
const htmlEscapes = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
||||||
|
const reHasUnescapedHtml = /[&<>"']/;
|
||||||
|
|
||||||
|
function escapeHtml(string) {
|
||||||
|
return string && reHasUnescapedHtml.test(string)
|
||||||
|
? string.replace(/[&<>"']/g, chr => htmlEscapes[chr])
|
||||||
|
: (string || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape characters for inclusion into a string literal
|
||||||
|
const escapes = { "'": "'", '\\': '\\', '\n': 'n', '\r': 'r', '\u2028': 'u2028', '\u2029': 'u2029' };
|
||||||
|
function escapeStringChar(match) { return '\\' + escapes[match]; }
|
||||||
|
|
||||||
|
// Basic Object helpers ------------------------------------------------------
|
||||||
|
function isObject(value) { return value != null && typeof value === 'object'; }
|
||||||
|
function toStringSafe(value) { return value == null ? '' : (value + ''); }
|
||||||
|
function baseValues(object, props) { return props.map(k => object[k]); }
|
||||||
|
|
||||||
|
|
||||||
|
function attempt(fn) {
|
||||||
|
try { return fn(); } catch (e) { return e; }
|
||||||
|
}
|
||||||
|
function isError(value) { return value instanceof Error || (isObject(value) && value.name === 'Error'); }
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Main template implementation
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
let templateCounter = -1; // used for sourceURL generation
|
||||||
|
|
||||||
|
function _template(string) {
|
||||||
|
string = toStringSafe(string);
|
||||||
|
|
||||||
|
const imports = { '_': { escape: escapeHtml } };
|
||||||
|
const importKeys = Object.keys(imports);
|
||||||
|
const importValues = baseValues(imports, importKeys);
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
let isEscaping;
|
||||||
|
let isEvaluating;
|
||||||
|
let source = "__p += '";
|
||||||
|
|
||||||
|
|
||||||
|
// Build combined regex of delimiters
|
||||||
|
const reDelimiters = RegExp(
|
||||||
|
reEscape.source + '|' +
|
||||||
|
reInterpolate.source + '|' +
|
||||||
|
reEsTemplate.source + '|' +
|
||||||
|
reEvaluate.source + '|$'
|
||||||
|
, 'g');
|
||||||
|
|
||||||
|
const sourceURL = `//# sourceURL=lodash.templateSources[${++templateCounter}]\n`;
|
||||||
|
|
||||||
|
// Tokenize
|
||||||
|
string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
|
||||||
|
interpolateValue || (interpolateValue = esTemplateValue);
|
||||||
|
// Append preceding string portion with escaped literal chars
|
||||||
|
source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
|
||||||
|
if (escapeValue) {
|
||||||
|
isEscaping = true;
|
||||||
|
source += "' +\n__e(" + escapeValue + ") +\n'";
|
||||||
|
}
|
||||||
|
if (evaluateValue) {
|
||||||
|
isEvaluating = true;
|
||||||
|
source += "';\n" + evaluateValue + ";\n__p += '";
|
||||||
|
}
|
||||||
|
if (interpolateValue) {
|
||||||
|
source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
|
||||||
|
}
|
||||||
|
index = offset + match.length;
|
||||||
|
return match;
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
source += "';\n";
|
||||||
|
|
||||||
|
source = 'with (obj) {\n' + source + '\n}\n';
|
||||||
|
|
||||||
|
// Remove unnecessary concatenations
|
||||||
|
source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
|
||||||
|
.replace(reEmptyStringMiddle, '$1')
|
||||||
|
.replace(reEmptyStringTrailing, '$1;');
|
||||||
|
|
||||||
|
// Frame as function body
|
||||||
|
source = 'function(obj) {\n' +
|
||||||
|
'obj || (obj = {});\n' +
|
||||||
|
"var __t, __p = ''" +
|
||||||
|
(isEscaping ? ', __e = _.escape' : '') +
|
||||||
|
(isEvaluating
|
||||||
|
? ', __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, \'\') }\n'
|
||||||
|
: ';\n'
|
||||||
|
) +
|
||||||
|
source +
|
||||||
|
'return __p\n}';
|
||||||
|
|
||||||
|
// Actual compile step
|
||||||
|
const result = attempt(function() {
|
||||||
|
return Function(importKeys, sourceURL + 'return ' + source).apply(undefined, importValues); // eslint-disable-line no-new-func
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isError(result)) {
|
||||||
|
result.source = source; // expose for debugging if error
|
||||||
|
throw result;
|
||||||
|
}
|
||||||
|
// Expose compiled source
|
||||||
|
result.source = source;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function template(text) {
|
||||||
|
return _template(text);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'caching-compiler',
|
name: 'caching-compiler',
|
||||||
version: '2.0.0',
|
version: '2.0.1',
|
||||||
summary: 'An easy way to make compiler plugins cache',
|
summary: 'An easy way to make compiler plugins cache',
|
||||||
documentation: 'README.md'
|
documentation: 'README.md'
|
||||||
});
|
});
|
||||||
|
|
||||||
Npm.depends({
|
Npm.depends({
|
||||||
'lru-cache': '6.0.0'
|
'lru-cache': '6.0.0'
|
||||||
})
|
});
|
||||||
|
|
||||||
Package.onUse(function(api) {
|
Package.onUse(function(api) {
|
||||||
api.use(['ecmascript', 'random']);
|
api.use(['ecmascript', 'random']);
|
||||||
|
|||||||
@@ -124,20 +124,6 @@ export class Hook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async forEachAsync(iterator) {
|
|
||||||
const ids = Object.keys(this.callbacks);
|
|
||||||
for (let i = 0; i < ids.length; ++i) {
|
|
||||||
const id = ids[i];
|
|
||||||
// check to see if the callback was removed during iteration
|
|
||||||
if (hasOwn.call(this.callbacks, id)) {
|
|
||||||
const callback = this.callbacks[id];
|
|
||||||
if (!await iterator(callback)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each registered callback, call the passed iterator function with the callback.
|
* For each registered callback, call the passed iterator function with the callback.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "Register callbacks on a hook",
|
summary: "Register callbacks on a hook",
|
||||||
version: '1.6.0',
|
version: '1.6.1',
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
|
|||||||
@@ -482,13 +482,18 @@ const testSubtree = (value, pattern, collectErrors = false, errors = [], path =
|
|||||||
|
|
||||||
const keys = Object.keys(requiredPatterns);
|
const keys = Object.keys(requiredPatterns);
|
||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
const result = {
|
const createMissingError = key => ({
|
||||||
message: `Missing key '${keys[0]}'`,
|
message: `Missing key '${key}'`,
|
||||||
path: '',
|
path: collectErrors ? path : '',
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!collectErrors) return result;
|
if (!collectErrors) {
|
||||||
errors.push(result);
|
return createMissingError(keys[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
errors.push(createMissingError(key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!collectErrors) return false;
|
if (!collectErrors) return false;
|
||||||
|
|||||||
@@ -573,7 +573,8 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
|
|||||||
int: { i: 1.2, a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
int: { i: 1.2, a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
||||||
oneOf: { f: 'm', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
oneOf: { f: 'm', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
||||||
where: { w: 'a', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
where: { w: 'a', a: [1, '2'], b: [{x: 1, y: '1'}, {x: '2', y: 2}, {x: '3', y: '3'}] },
|
||||||
whereArr: [1, 2, 3]
|
whereArr: [1, 2, 3],
|
||||||
|
embedded: { thing: '1' }
|
||||||
};
|
};
|
||||||
|
|
||||||
const pattern = {
|
const pattern = {
|
||||||
@@ -599,7 +600,10 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
|
|||||||
whereArr: Match.Where((x) => {
|
whereArr: Match.Where((x) => {
|
||||||
check(x, [String]);
|
check(x, [String]);
|
||||||
return x.length === 1;
|
return x.length === 1;
|
||||||
})
|
}),
|
||||||
|
missing1: String,
|
||||||
|
missing2: String,
|
||||||
|
embedded: { thing: String, another: String }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -609,7 +613,8 @@ Tinytest.add('check - check throw all errors deeply nested', test => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.isTrue(error);
|
test.isTrue(error);
|
||||||
test.equal(error.length, 37);
|
test.equal(error.length, 40);
|
||||||
|
test.equal(error.filter(e => e.message.includes('Missing key')).map(e => e.message), [`Match error: Missing key 'another' in field embedded`, `Match error: Missing key 'missing1'`, `Match error: Missing key 'missing2'`]);
|
||||||
error.every(e => test.instanceOf(e, Match.Error));
|
error.every(e => test.instanceOf(e, Match.Error));
|
||||||
test.isFalse(Match.test(value, pattern));
|
test.isFalse(Match.test(value, pattern));
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Package.describe({
|
Package.describe({
|
||||||
summary: 'Check whether a value matches a pattern',
|
summary: 'Check whether a value matches a pattern',
|
||||||
version: '1.4.2',
|
version: '1.4.4',
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"lockfileVersion": 4,
|
|
||||||
"dependencies": {
|
|
||||||
"lodash.groupby": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
|
|
||||||
"integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="
|
|
||||||
},
|
|
||||||
"lodash.has": {
|
|
||||||
"version": "4.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
|
|
||||||
"integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g=="
|
|
||||||
},
|
|
||||||
"lodash.isempty": {
|
|
||||||
"version": "4.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
|
||||||
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
|
||||||
},
|
|
||||||
"lodash.isequal": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
|
||||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
|
||||||
},
|
|
||||||
"lodash.isobject": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA=="
|
|
||||||
},
|
|
||||||
"lodash.isstring": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
|
||||||
},
|
|
||||||
"lodash.memoize": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
|
|
||||||
},
|
|
||||||
"lodash.zip": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
packages/ddp-client/.npm/package/npm-shrinkwrap.json
generated
20
packages/ddp-client/.npm/package/npm-shrinkwrap.json
generated
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"lockfileVersion": 4,
|
|
||||||
"dependencies": {
|
|
||||||
"@sinonjs/commons": {
|
|
||||||
"version": "1.8.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
|
|
||||||
"integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ=="
|
|
||||||
},
|
|
||||||
"@sinonjs/fake-timers": {
|
|
||||||
"version": "7.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz",
|
|
||||||
"integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw=="
|
|
||||||
},
|
|
||||||
"type-detect": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
|
||||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user