Merge branch 'release-1.10.3' into cache-build-before-deploy

This commit is contained in:
filipenevola
2020-07-27 13:27:53 -04:00
55 changed files with 701 additions and 137 deletions

View File

@@ -1,3 +1,43 @@
## vNEXT, unreleased
### Breaking changes
N/A
### Migration steps
N/A
### Changes
* `email` package now exposes `hookSend` that runs before emails are sent.
## v1.10.3, TBD
### Breaking changes
* `email` package dependencies have been update and package version has been bumped to 2.0.0
There is a potential breaking change as the underlying package started to use `dns.resolve()`
instead of `dns.lookup()` which might be breaking on some environments.
See [nodemailer changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) for more information.
### Migration steps
N/A
### Changes
* Fixes error when removing cordova plugin that depends on cli variables. PR [#10976](https://github.com/meteor/meteor/pull/11052)
* `email` package now exposes `hookSend` that runs before emails are send.
* The version of MongoDB used by Meteor in development has been updated
from 4.2.5 to 4.2.8
* Node.js has been updated to version
[12.18.2](https://nodejs.org/en/blog/release/v12.18.2/)
* Updated npm to version 6.14.5
## v1.10.2, 2020-04-21

2
meteor
View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
BUNDLE_VERSION=12.16.1.8
BUNDLE_VERSION=12.18.2.1
# OS Check. Put here because here is where we download the precompiled
# bundles that are arch specific.

View File

@@ -71,9 +71,9 @@ export class AccountsServer extends AccountsCommon {
resetPassword: token => Meteor.absoluteUrl(`#/reset-password/${token}`),
verifyEmail: token => Meteor.absoluteUrl(`#/verify-email/${token}`),
enrollAccount: token => Meteor.absoluteUrl(`#/enroll-account/${token}`),
}
};
this.addDefaultRateLimit()
this.addDefaultRateLimit();
}
///
@@ -117,6 +117,19 @@ export class AccountsServer extends AccountsCommon {
this._validateNewUserHooks.push(func);
}
/**
* @summary Validate login from external service
* @locus Server
* @param {Function} func Called whenever login/user creation from external service is attempted. Login or user creation based on this login can be aborted by passing a falsy value or throwing an exception.
*/
beforeExternalLogin(func) {
if (this._beforeExternalLoginHook) {
throw new Error("Can only call beforeExternalLogin once");
}
this._beforeExternalLoginHook = func;
}
///
/// CREATE USER HOOKS
///
@@ -1211,6 +1224,11 @@ export class AccountsServer extends AccountsCommon {
let user = this.users.findOne(selector, {fields: this._options.defaultFieldSelector});
// Before continuing, run user hook to see if we should continue
if (this._beforeExternalLoginHook && !this._beforeExternalLoginHook(serviceName, serviceData, user)) {
throw new Meteor.Error(403, "Login forbidden");
}
// When creating a new user we pass through all options. When updating an
// existing user, by default we only process/pass through the serviceData
// (eg, so that we keep an unexpired access token and don't cache old email

View File

@@ -623,3 +623,44 @@ Tinytest.add(
Accounts._options = accountsOptions;
}
);
Tinytest.add(
'accounts - verify beforeExternalLogin hook can stop user login',
test => {
// Verify user data is saved properly when not using the
// beforeExternalLogin hook.
let facebookId = Random.id();
const uid1 = Accounts.updateOrCreateUserFromExternalService(
'facebook',
{ id: facebookId },
{ profile: { foo: 1 } },
).userId;
const ignoreFieldName = "bigArray";
const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}});
let users =
Meteor.users.find({ 'services.facebook.id': facebookId }).fetch();
test.length(users, 1);
test.equal(users[0].profile.foo, 1);
test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields');
// Verify that when beforeExternalLogin returns false
// that an error throws and user is not saved
Accounts.beforeExternalLogin((serviceName, serviceData, user) => {
// Check that we get the correct data
test.equal(serviceName, 'facebook');
test.equal(serviceData, { id: facebookId });
test.equal(user._id, uid1);
return false
});
test.throws(() => Accounts.updateOrCreateUserFromExternalService(
'facebook',
{ id: facebookId },
{ profile: { foo: 1 } },
));
// Cleanup
Meteor.users.remove(uid1);
Accounts._beforeExternalLoginHook = null;
}
);

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "A user account system",
version: "1.6.0",
version: "1.7.0-beta1103.6",
});
Package.onUse(api => {

View File

@@ -20,7 +20,7 @@ Accounts.emailTemplates.headers = {
'My-Custom-Header' : 'Cool'
};
EmailTest.hookSend(options => {
Email.hookSend(options => {
const { to } = options;
if (!to || !to.toUpperCase().includes('INTERCEPT')) {
return true; // go ahead and send

View File

@@ -5,7 +5,7 @@ Package.describe({
// 2.2.x in the future. The version was also bumped to 2.0.0 temporarily
// during the Meteor 1.5.1 release process, so versions 2.0.0-beta.2
// through -beta.5 and -rc.0 have already been published.
version: "1.6.1"
version: "1.6.2-beta1103.6"
});
Package.onUse(api => {

View File

@@ -20,7 +20,7 @@ const reportError = (error, callback) => {
/**
* @summary Log the user in with a password.
* @locus Client
* @param {Object | String} user
* @param {Object | String} selector
* Either a string interpreted as a username or an email; or an object with a
* single key: `email`, `username` or `id`. Username or email match in a case
* insensitive manner.

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 1,
"dependencies": {
"lolex": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz",
"integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw=="
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz",
"integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng=="
}
}
}

View File

@@ -1,6 +1,6 @@
Package.describe({
name: 'ddp-rate-limiter',
version: '1.0.7',
version: '1.0.9-beta1103.6',
// Brief, one-line summary of the package.
summary: 'The DDPRateLimiter allows users to add rate limits to DDP' +
' methods and subscriptions.',

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 1,
"dependencies": {
"core-js": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
}
}
}

View File

@@ -1,13 +1,13 @@
Package.describe({
name: "ecmascript-runtime-client",
version: "0.10.0",
version: "0.11.0-beta1103.6",
summary: "Polyfills for new ECMAScript 2015 APIs like Map and Set",
git: "https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-client",
documentation: "README.md"
});
Npm.depends({
"core-js": "3.2.1"
"core-js": "3.6.5"
});
Package.onUse(function(api) {

View File

@@ -2,9 +2,9 @@
"lockfileVersion": 1,
"dependencies": {
"core-js": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
}
}
}

View File

@@ -1,13 +1,13 @@
Package.describe({
name: "ecmascript-runtime-server",
version: "0.9.0",
version: "0.10.0-beta1103.6",
summary: "Polyfills for new ECMAScript 2015 APIs like Map and Set",
git: "https://github.com/meteor/meteor/tree/devel/packages/ecmascript-runtime-client",
documentation: "README.md"
});
Npm.depends({
"core-js": "3.2.1"
"core-js": "3.6.5"
});
Package.onUse(function(api) {

View File

@@ -1,15 +1,15 @@
{
"lockfileVersion": 1,
"dependencies": {
"node4mailer": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/node4mailer/-/node4mailer-4.0.3.tgz",
"integrity": "sha1-jwx6ZzdSehKFMBhaFMLoeZiaiRA="
"nodemailer": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.6.tgz",
"integrity": "sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA=="
},
"stream-buffers": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-0.2.5.tgz",
"integrity": "sha1-+TBTnTzwjXSKNArWE5+Vss60jwU="
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz",
"integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ=="
}
}
}

View File

@@ -1,6 +1,6 @@
var Future = Npm.require('fibers/future');
var urlModule = Npm.require('url');
var nodemailer = Npm.require('node4mailer');
var nodemailer = Npm.require('nodemailer');
Email = {};
EmailTest = {};
@@ -8,12 +8,12 @@ EmailTest = {};
EmailInternals = {
NpmModules: {
mailcomposer: {
version: Npm.require('node4mailer/package.json').version,
module: Npm.require('node4mailer/lib/mail-composer')
version: Npm.require('nodemailer/package.json').version,
module: Npm.require('nodemailer/lib/mail-composer')
},
nodemailer: {
version: Npm.require('node4mailer/package.json').version,
module: Npm.require('node4mailer')
version: Npm.require('nodemailer/package.json').version,
module: Npm.require('nodemailer')
}
}
};
@@ -98,15 +98,15 @@ var smtpSend = function (transport, mail) {
transport._syncSendMail(mail);
};
var sendHooks = [];
/**
* Mock out email sending (eg, during a test.) This is private for now.
* Hook that runs before email is sent.
*
* f receives the arguments to Email.send and should return true to go
* @param f {function} receives the arguments to Email.send and should return true to go
* ahead and send the email (or at least, try subsequent hooks), or
* false to skip sending.
*/
var sendHooks = [];
EmailTest.hookSend = function (f) {
Email.hookSend = function (f) {
sendHooks.push(f);
};
@@ -118,9 +118,9 @@ EmailTest.hookSend = function (f) {
* If the `MAIL_URL` environment variable is set, actually sends the email.
* Otherwise, prints the contents of the email to standard out.
*
* Note that this package is based on **mailcomposer 4**, so make sure to refer to
* [the documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md)
* for that version when using the `attachments` or `mailComposer` options.
* Note that this package is based on **nodemailer**, so make sure to refer to
* [the documentation](http://nodemailer.com/)
* when using the `attachments` or `mailComposer` options.
*
* @locus Server
* @param {Object} options
@@ -136,7 +136,7 @@ EmailTest.hookSend = function (f) {
* @param {String} [options.icalEvent] iCalendar event attachment
* @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`.
* @param {Object[]} [options.attachments] Array of attachment objects, as
* described in the [mailcomposer documentation](https://github.com/nodemailer/mailcomposer/blob/v4.0.1/README.md#attachments).
* described in the [nodemailer documentation](https://nodemailer.com/message/attachments/).
* @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields)
* object representing the message to be sent. Overrides all other options.
* You can create a `MailComposer` object via

View File

@@ -1,11 +1,11 @@
Package.describe({
summary: "Send email messages",
version: "1.2.3"
version: "2.0.0-beta1103.6"
});
Npm.depends({
node4mailer: "4.0.3",
"stream-buffers": "0.2.5"
nodemailer: "6.4.6",
"stream-buffers": "3.0.2"
});
Package.onUse(function (api) {

View File

@@ -1,6 +1,6 @@
Package.describe({
summary: "The Meteor command-line tool",
version: '1.10.2'
version: '1.10.3-beta.6'
});
Package.includeTool();

View File

@@ -1,6 +1,6 @@
{
"track": "METEOR",
"version": "1.10.2-rc.0",
"version": "1.10.3-beta.6",
"recommended": false,
"official": false,
"description": "Meteor"

View File

@@ -5,10 +5,10 @@ set -u
UNAME=$(uname)
ARCH=$(uname -m)
NODE_VERSION=12.16.1
NODE_VERSION=12.18.2
MONGO_VERSION_64BIT=4.2.5
MONGO_VERSION_32BIT=3.2.22
NPM_VERSION=6.14.0
NPM_VERSION=6.14.6
# If we built Node from source on Jenkins, this is the build number.
NODE_BUILD_NUMBER=

View File

@@ -10,7 +10,7 @@ var packageJson = {
dependencies: {
// Explicit dependency because we are replacing it with a bundled version
// and we want to make sure there are no dependencies on a higher version
npm: "6.14.0",
npm: "6.14.6",
pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8",
"node-gyp": "6.0.1",
"node-pre-gyp": "0.14.0",

View File

@@ -517,6 +517,7 @@ main.registerCommand({
minimal: { type: Boolean },
full: { type: Boolean },
react: { type: Boolean },
vue: { type: Boolean },
typescript: { type: Boolean },
},
catalogRefresh: new catalog.Refresh.Never()
@@ -770,6 +771,8 @@ main.registerCommand({
skelName += "-full";
} else if (options.react) {
skelName += "-react";
} else if (options.vue) {
skelName += "-vue";
} else if (options.typescript) {
skelName += "-typescript";
}
@@ -886,8 +889,9 @@ main.registerCommand({
! options.minimal &&
! options.full &&
! options.react &&
! options.vue &&
! options.typescript) {
// Notify people about --bare, --minimal, --full, --react, and --typescript.
// Notify people about --bare, --minimal, --full, --react, --vue, and --typescript.
Console.info([
"",
"To start with a different app template, try one of the following:",
@@ -898,6 +902,7 @@ main.registerCommand({
cmd("meteor create --minimal # to create an app with as few Meteor packages as possible");
cmd("meteor create --full # to create a more complete scaffolded app");
cmd("meteor create --react # to create a basic React-based app");
cmd("meteor create --vue # to create a basic Vue-based app");
cmd("meteor create --typescript # to create an app using TypeScript and React");
}

View File

@@ -74,6 +74,34 @@ const pinnedPluginVersions = {
"cordova-plugin-wkwebview-engine": "1.1.3"
}
/**
* To fix Cordova error: Variable(s) missing we convert the cli_variables
* when removing plugins we want to convert for each plugin, for instance,
* cordova-plugin-facebook4:
* commandOptions {
* ...
* cli_variables: {
* 'cordova-plugin-googleplus': {
* REVERSED_CLIENT_ID: 'com.googleusercontent.apps.11111111-xxkodsuusaiusixuaix'
* },
* 'cordova-plugin-facebook4': { APP_ID: '1111111111111111', APP_NAME: 'appname' }
* }
* }
* into this
* commandOptions {
* ...
* cli_variables: { APP_ID: '1111111111111111', APP_NAME: 'appname' }
* }
*
* @param plugin
* @param commandOptions
*/
const getCommandOptionsForPlugin = (plugin, commandOptions = {}) => {
const cli_variables = commandOptions && commandOptions.cli_variables
&& commandOptions.cli_variables[plugin] || {};
return {...commandOptions, cli_variables};
}
export class CordovaProject {
constructor(projectContext, options = {}) {
@@ -521,7 +549,7 @@ from Cordova project`, async () => {
buildmessage.assertInJob();
if (utils.isUrlWithSha(version)) {
return convertToGitUrl(version);
return `${id}@${convertToGitUrl(version)}`;
} else if (utils.isUrlWithFileScheme(version)) {
// Strip file:// and resolve the path relative to the cordova-build
// directory
@@ -560,18 +588,28 @@ from Cordova project`, async () => {
{ cli_variables: config, link: utils.isUrlWithFileScheme(version) });
this.runCommands(`adding plugin ${target} \
to Cordova project`, cordova_lib.plugin.bind(undefined, 'add', [target], commandOptions));
to Cordova project`, cordova_lib.plugin.bind(undefined, 'add', [target],
commandOptions));
}
}
// plugins is an array of plugin IDs.
removePlugins(plugins) {
removePlugins(plugins, config = {}) {
if (_.isEmpty(plugins)) {
return;
}
this.runCommands(`removing plugins ${plugins} \
from Cordova project`, cordova_lib.plugin.bind(undefined, 'rm', plugins, this.defaultOptions));
const commandOptions = _.extend(this.defaultOptions,
{ cli_variables: config });
plugins.forEach(plugin => {
const commandOptionsPlugin = getCommandOptionsForPlugin(plugin,
commandOptions);
this.runCommands(`removing plugin ${plugin} \
from Cordova project`, cordova_lib.plugin.bind(undefined, 'rm --force', [plugin],
commandOptionsPlugin));
});
}
// Ensures that the Cordova plugins are synchronized with the app-level
@@ -699,7 +737,7 @@ perform cordova plugins reinstall`);
Object.keys(installedPluginVersions));
}
this.removePlugins(pluginsToRemove);
this.removePlugins(pluginsToRemove, pluginsConfiguration);
let pluginVersionsToInstall;
@@ -735,7 +773,7 @@ perform cordova plugins reinstall`);
// cordova-plugin-whitelist@1.3.2 => { 'cordova-plugin-whitelist': '1.3.2' }
// com.cordova.plugin@file://.cordova-plugins/plugin => { 'com.cordova.plugin': 'file://.cordova-plugins/plugin' }
// @scope/plugin@1.0.0 => { 'com.cordova.plugin': 'scope/plugin' }
const installed = this.listInstalledPluginVersions(true);
const installed = this.listInstalledPluginVersions();
const installedPluginsNames = Object.keys(installed);
const installedPluginsVersions = Object.values(installed);
const missingPlugins = {};

View File

@@ -5,7 +5,7 @@
///
import assert from "assert";
import fs, { PathLike, Stats } from "fs";
import fs, { PathLike, Stats, Dirent } from "fs";
import path from "path";
import os from "os";
import { spawn, execFile } from "child_process";
@@ -1757,6 +1757,14 @@ wrapFsFunc<[string], string[]>("readdir", fs.readdirSync, [0], {
},
});
export const readdirWithTypes = wrapFsFunc<[string], Dirent[]>("readdirWithTypes", (dir) => {
return fs.readdirSync(dir, {
withFileTypes: true
});
}, [0], {
cached: true
});
export const appendFile = wrapDestructiveFsFunc("appendFile", fs.appendFileSync);
export const chmod = wrapDestructiveFsFunc("chmod", fs.chmodSync);
export const close = wrapFsFunc("close", fs.closeSync, []);

View File

@@ -35,6 +35,14 @@ var NO_WATCHER_POLLING_INTERVAL =
// file watchers, but it's to our advantage if they survive restarts.
const WATCHER_CLEANUP_DELAY_MS = 30000;
// Pathwatcher complains (using console.error, ugh) if you try to watch
// two files with the same stat.ino number but different paths on linux, so we have
// to deduplicate files by ino.
const DEDUPLICATE_BY_INO = process.platform !== "win32";
const entriesByIno = new Map;
export type SafeWatcher = {
close: () => void;
}
@@ -49,11 +57,6 @@ interface Entry extends SafeWatcher {
const entries: Record<string, Entry | null> = Object.create(null);
// Pathwatcher complains (using console.error, ugh) if you try to watch
// two files with the same stat.ino number but different paths, so we have
// to deduplicate files by ino.
const entriesByIno = new Map;
// Set of paths for which a change event has been fired, watched with
// watchLibrary.watch if available. This could be an LRU cache, but in
// practice it should never grow large enough for that to matter.
@@ -86,10 +89,19 @@ function acquireWatcher(absPath: string, callback: EntryCallback) {
}
function startNewWatcher(absPath: string): Entry {
const stat = statOrNull(absPath);
if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) {
const entry = entriesByIno.get(stat.ino);
if (entries[absPath] === entry) {
let stat: Stats | null = null;
if (DEDUPLICATE_BY_INO) {
stat = statOrNull(absPath);
if (stat && stat.ino > 0 && entriesByIno.has(stat.ino)) {
const entry = entriesByIno.get(stat.ino);
if (entries[absPath] === entry) {
return entry;
}
}
} else {
let entry = entries[absPath];
if (entry) {
return entry;
}
}

View File

@@ -1,4 +1,4 @@
import { Stats, FSWatcher } from "fs";
import { Stats, FSWatcher, Dirent } from "fs";
import * as files from "./files";
import * as safeWatcher from "./safe-watcher";
import { createHash } from "crypto";
@@ -339,7 +339,7 @@ export const sha512 = Profile("sha512", function (...args: (string | Buffer)[])
function readAndStatDirectory(absPath: string) {
// Read the directory.
try {
var contents = files.readdir(absPath);
var contents = files.readdirWithTypes(absPath);
} catch (e) {
// If the path is not a directory, return null; let other errors through.
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) {
@@ -351,9 +351,14 @@ function readAndStatDirectory(absPath: string) {
// Add slashes to the end of directories.
const contentsWithSlashes: string[] = [];
contents.forEach(entry => {
// We do stat instead of lstat here, so that we treat symlinks to
// directories just like directories themselves.
const stat = optimisticStatOrNull(files.pathJoin(absPath, entry));
let stat: Dirent | Stats | null = entry;
let name = entry.name;
if (entry.isSymbolicLink()) {
// We do stat instead of lstat here, so that we treat symlinks to
// directories just like directories themselves.
stat = optimisticStatOrNull(files.pathJoin(absPath, entry.name));
}
if (! stat) {
// Disappeared after the readdir (or a dangling symlink)?
// Eh, pretend it was never there in the first place.
@@ -361,10 +366,10 @@ function readAndStatDirectory(absPath: string) {
}
if (stat.isDirectory()) {
entry += '/';
name += '/';
}
contentsWithSlashes.push(entry);
contentsWithSlashes.push(name);
});
return contentsWithSlashes;

View File

@@ -1,7 +1,7 @@
import assert from "assert";
import {WatchSet, readAndWatchFile, sha1} from '../fs/watch';
import files, {
symlinkWithOverwrite,
symlinkWithOverwrite, realpath,
} from '../fs/files';
import NpmDiscards from './npm-discards.js';
import {Profile} from '../tool-env/profile';
@@ -540,7 +540,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
// as well as node_modules/meteor and the parent directories of any
// scoped npm packages.
this._ensureAllNonPackageDirectories(
files.realpath(options.from),
realpath(options.from),
options.to
);
}
@@ -637,7 +637,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
});
}
const rootDir = files.realpath(from);
const rootDir = realpath(from);
const walk = (absFrom, relTo) => {
if (symlink && ! (relTo in this.usedAsFile)) {
@@ -661,7 +661,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
return;
}
// Returns files.realpath(thisAbsFrom), iff it is external to
// Returns files.realpath(thisAbsFrom), if it is external to
// rootDir, using caching because this function might be called
// more than once.
let cachedExternalPath;
@@ -671,7 +671,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
}
try {
var real = files.realpath(thisAbsFrom);
var real = realpath(thisAbsFrom);
} catch (e) {
if (e.code !== "ENOENT" &&
e.code !== "ELOOP") {

View File

@@ -173,6 +173,7 @@ import { loadIsopackage } from '../tool-env/isopackets.js';
import { CORDOVA_PLATFORM_VERSIONS } from '../cordova';
import { gzipSync } from "zlib";
import { PackageRegistry } from "../../packages/meteor/define-package.js";
import { optimisticLStatOrNull } from '../fs/optimistic';
const SOURCE_URL_PREFIX = "meteor://\u{1f4bb}app";
@@ -486,12 +487,11 @@ export class NodeModulesDirectory {
return true;
}
const real = files.realpathOrNull(path);
if (typeof real === "string" &&
real !== path) {
const fileStatus = optimisticLStatOrNull(path);
if (fileStatus && fileStatus.isSymbolicLink()) {
// If node_modules/.bin/command is a symlink, determine the
// answer by calling isWithinProdPackage(real).
return isWithinProdPackage(real);
return isWithinProdPackage(files.realpathOrNull(path));
}
// If node_modules/.bin/command is not a symlink, then it's hard
@@ -2844,20 +2844,18 @@ var writeTargetToPath = Profile(
previousBuilder = null,
buildMode,
minifyMode,
forceInPlaceBuild
}) {
var builder = new Builder({
outputPath: files.pathJoin(outputPath, 'programs', name),
previousBuilder,
// We do not force an in-place build for individual targets like
// .meteor/local/build/programs/web.browser.legacy, because they
// tend to be written atomically, and it's important on Windows to
// avoid overwriting files that might be open currently in the build
// or server process.
// Server builds do use an in-place build since the server is always stopped
// during the build.
// If client in-place builds were safer on Windows, they
// would be much quicker than from-scratch rebuilds.
forceInPlaceBuild: name === 'server',
// We do not force an in-place build for individual targets
// like .meteor/local/build/programs/web.browser.legacy, because they tend
// to be written atomically, and it's important on Windows to avoid
// overwriting files that might be open currently in the server
// process. There are some exceptions when we know the server process
// is not using the files, such as during a full build when it is stopped.
forceInPlaceBuild
});
var targetBuild = target.write(builder, {
@@ -2910,6 +2908,7 @@ var writeSiteArchive = Profile("bundler writeSiteArchive", function (
buildMode,
minifyMode,
sourceRoot,
forceInPlaceBuild,
}) {
const builders = {};
@@ -3004,7 +3003,8 @@ Find out more about Meteor at meteor.com.
releaseName,
previousBuilder: previousBuilders[name] || null,
buildMode,
minifyMode
minifyMode,
forceInPlaceBuild
});
builders[name] = targetBuilder;
@@ -3083,6 +3083,10 @@ Find out more about Meteor at meteor.com.
* - hasCachedBundle: true if we already have a cached bundle stored in
* /build. When true, we only build the new client targets in the bundle.
*
* - forceInPlaceBuild On Windows, in place builds are disabled by default
* since they are only safe when the output files from the previous build
* are not being used. This can be set to true when it is safe.
*
* Returns an object with keys:
* - errors: A buildmessage.MessageSet, or falsy if bundling succeeded.
* - serverWatchSet: Information about server files and paths that were
@@ -3117,6 +3121,7 @@ function bundle({
previousBuilders = Object.create(null),
hasCachedBundle,
allowDelayedClientBuilds = false,
forceInPlaceBuild,
}) {
buildOptions = buildOptions || {};
@@ -3271,6 +3276,7 @@ function bundle({
builtBy,
releaseName,
minifyMode,
forceInPlaceBuild,
};
function writeClientTarget(target) {

View File

@@ -1714,7 +1714,7 @@ export class PackageSourceBatch {
if (cacheFilename) {
let diskCached = null;
try {
diskCached = optimisticReadJsonOrNull(cacheFilename);
diskCached = files.readJSONOrNull(cacheFilename);
} catch (e) {
// Ignore JSON parse errors; pretend there was no cache.
if (!(e instanceof SyntaxError)) {

View File

@@ -25,6 +25,7 @@ import {
convertToPosixPath,
realpathOrNull,
writeFileAtomically,
readFile,
} from "../fs/files";
const { SourceNode, SourceMapConsumer } = require("source-map");
@@ -74,9 +75,18 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function (
source,
_hash,
bundleArch,
cacheFilePath,
) {
if (cacheFilePath) {
try {
return readFile(cacheFilePath, "utf8");
} catch (e) {
if (e.code !== "ENOENT") throw e;
}
}
const isLegacy = isLegacyArch(bundleArch);
return reifyCompile(stripHashBang(source), {
let result = reifyCompile(stripHashBang(source), {
parse: reifyBabelParse,
generateLetDeclarations: !isLegacy,
avoidModernSyntax: isLegacy,
@@ -84,6 +94,14 @@ const reifyCompileWithCache = Profile("reifyCompileWithCache", wrap(function (
dynamicImport: true,
ast: false,
}).code;
if (cacheFilePath) {
Promise.resolve().then(
() => writeFileAtomically(cacheFilePath, result),
);
}
return result;
}, {
makeCacheKey(_source, hash, bundleArch) {
return JSON.stringify([hash, bundleArch]);
@@ -132,29 +150,16 @@ class DefaultHandlers {
}
}
if (this.cacheDir) {
const cacheFileName = this.getCacheFileName(file)!;
try {
return optimisticReadFile(cacheFileName, "utf8");
} catch (e) {
if (e.code !== "ENOENT") throw e;
const code = reifyCompileWithCache(
file.dataString,
file.hash,
this.bundleArch,
);
Promise.resolve().then(
() => writeFileAtomically(cacheFileName, code),
);
return code;
}
} else {
return reifyCompileWithCache(
file.dataString,
file.hash,
this.bundleArch,
);
}
const cacheFileName = this.cacheDir ?
this.getCacheFileName(file) :
null;
return reifyCompileWithCache(
file.dataString,
file.hash,
this.bundleArch,
cacheFileName
)
}
// Files with an .mjs extension are just JavaScript plus module syntax.

View File

@@ -79,6 +79,9 @@ var loadOrderSort = function (sourceProcessorSet, arch) {
});
return function (a, b) {
const aBasename = files.pathBasename(a);
const bBasename = files.pathBasename(b);
// XXX MODERATELY SIZED HACK --
// push template files ahead of everything else. this is
// important because the user wants to be able to say
@@ -87,15 +90,15 @@ var loadOrderSort = function (sourceProcessorSet, arch) {
// before the corresponding .html file.
//
// maybe all of the templates should go in one file?
var isTemplate_a = isTemplate(files.pathBasename(a));
var isTemplate_b = isTemplate(files.pathBasename(b));
var isTemplate_a = isTemplate(aBasename);
var isTemplate_b = isTemplate(bBasename);
if (isTemplate_a !== isTemplate_b) {
return (isTemplate_a ? -1 : 1);
}
// main.* loaded last
var ismain_a = (files.pathBasename(a).indexOf('main.') === 0);
var ismain_b = (files.pathBasename(b).indexOf('main.') === 0);
var ismain_a = (aBasename.indexOf('main.') === 0);
var ismain_b = (bBasename.indexOf('main.') === 0);
if (ismain_a !== ismain_b) {
return (ismain_a ? 1 : -1);
}
@@ -213,14 +216,44 @@ var getExcerptFromReadme = function (text) {
class SymlinkLoopChecker {
constructor(sourceRoot) {
this.sourceRoot = sourceRoot;
this._realSourceRoot = files.realpath(sourceRoot);
this._seenPaths = {};
this._cache = new Map();
}
// Avoids running realpath unless necessary
// since it is relatively slow on windows
_realpath = Profile('_realpath', function (relDir) {
const absPath = files.pathJoin(this._realSourceRoot, relDir);
if (files.lstat(absPath).isSymbolicLink()) {
const result = files.realpath(absPath);
this._cache.set(relDir, result);
return result;
}
let result;
const parentDir = files.pathDirname(relDir);
const parentEntry = this._cache.get(parentDir);
if (parentDir === '.') {
result = absPath;
} else if (parentEntry) {
result = files.pathJoin(parentEntry, files.pathBasename(relDir));
} else {
// The parent dir was never checked, which prevents us from
// skipping realpath
result = files.realpath(absPath);
}
this._cache.set(relDir, result);
return result;
})
check(relDir, quietly = true) {
const absPath = files.pathJoin(this.sourceRoot, relDir);
try {
var realPath = files.realpath(absPath);
var realPath = this._realpath(relDir);
} catch (e) {
if (!e || e.code !== 'ELOOP') {
throw e;
@@ -834,10 +867,21 @@ _.extend(PackageSource.prototype, {
}),
_readAndWatchDirectory(relDir, watchSet, {include, exclude, names}) {
return watch.readAndWatchDirectory(watchSet, {
const options = {
absPath: files.pathJoin(this.sourceRoot, relDir),
include, exclude, names
}).map(name => files.pathJoin(relDir, name));
};
const contents = watch.readDirectory(options);
if (watchSet) {
watchSet.addDirectory({
contents,
...options
});
}
return contents.map(name => files.pathJoin(relDir, name));
},
// Initialize a package from an application directory (has .meteor/packages).
@@ -1099,7 +1143,7 @@ _.extend(PackageSource.prototype, {
// complete list of source files for directories within node_modules.
_findSourcesCache: Object.create(null),
_findSources: Profile("PackageSource#_findSources", function ({
_findSources: Profile(({ sourceArch }) => `PackageSource#_findSources for ${sourceArch.arch}`, function ({
sourceProcessorSet,
watchSet,
isApp,
@@ -1216,9 +1260,10 @@ _.extend(PackageSource.prototype, {
const baseCacheKey = JSON.stringify({
isApp,
arch,
sourceRoot: self.sourceRoot,
excludes: anyLevelExcludes,
names: sourceReadOptions.names,
include: sourceReadOptions.include
}, (key, value) => {
if (_.isRegExp(value)) {
return [value.source, value.flags];
@@ -1260,13 +1305,13 @@ _.extend(PackageSource.prototype, {
return array;
}
function find(dir, depth, inNodeModules) {
function find(dir, depth, { inNodeModules = false, cache = false } = {}) {
// Remove trailing slash.
dir = dir.replace(/\/$/, "");
// If we're in a node_modules directory, cache the results of the
// find function for the duration of the process.
let cacheKey = inNodeModules && makeCacheKey(dir);
let cacheKey = inNodeModules && cache && makeCacheKey(dir);
if (cacheKey &&
cacheKey in self._findSourcesCache) {
return self._findSourcesCache[cacheKey];
@@ -1305,13 +1350,16 @@ _.extend(PackageSource.prototype, {
}
const sources = _.difference(
self._readAndWatchDirectory(dir, watchSet, readOptions),
self._readAndWatchDirectory(dir, inNodeModules ? null : watchSet, readOptions),
depth > 0 ? [] : controlFiles
);
const subdirectories = self._readAndWatchDirectory(dir, watchSet, {
include: [/\/$/],
exclude: depth > 0
const subdirectories = self._readAndWatchDirectory(
dir,
inNodeModules ? null : watchSet,
{
include: [/\/$/],
exclude: depth > 0
? anyLevelExcludes
: topLevelExcludes
});
@@ -1342,7 +1390,7 @@ _.extend(PackageSource.prototype, {
}
} else {
sources.push(...find(subdir, depth + 1, inNodeModules));
sources.push(...find(subdir, depth + 1, { inNodeModules, cache: !inNodeModules }));
}
});
@@ -1353,7 +1401,7 @@ _.extend(PackageSource.prototype, {
// subdirectories, continue searching this node_modules directory,
// so that any non-.js(on) files it contains can be imported by
// the app (#6037).
sources.push(...find(nodeModulesDir, depth + 1, true));
sources.push(...find(nodeModulesDir, depth + 1, { inNodeModules: true, cache: !inNodeModules}));
}
delete dotMeteorIgnoreFiles[dir];

View File

@@ -579,6 +579,10 @@ _.extend(AppRunner.prototype, {
// Permit delayed bundling of client architectures if the
// console is interactive.
allowDelayedClientBuilds: ! Console.isHeadless(),
// None of the targets are used during full rebuilds
// so we can safely build in place on Windows
forceInPlaceBuild: !cachedServerWatchSet
});
});

View File

@@ -938,6 +938,10 @@ _.extend(MRp, {
"Looks like you are out of free disk space under .meteor/local.";
} else if (explanation) {
message += "\n" + explanation.longText;
} else if (process.platform === 'win32') {
message += "\n\n" +
"Check how to troubleshoot here " +
"https://docs.meteor.com/windows.html#cant-start-mongo-server";
}
if (explanation && explanation.symbol === 'EXIT_NET_ERROR') {

View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -0,0 +1 @@
local

View File

@@ -0,0 +1,24 @@
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base # Packages every Meteor app needs to have
mobile-experience # Packages for a great mobile UX
mongo # The database Meteor supports right now
reactive-var # Reactive variable for tracker
standard-minifier-css # CSS minifier run for production mode
standard-minifier-js # JS minifier run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers
ecmascript # Enable ECMAScript2015+ syntax in app code
typescript # Enable TypeScript syntax in .ts and .tsx modules
shell-server # Server-side component of the `meteor shell` command
tracker # Dependency tracker to allow reactive callbacks
static-html # Define static page content in .html files
akryum:vue-component # Vue-CLI template to publish components
meteortesting:mocha # A package for writing and running your meteor app and package tests with mocha
johanbrook:publication-collector # Test a Meteor publication by collecting its output

View File

@@ -0,0 +1,2 @@
server
browser

View File

@@ -0,0 +1,23 @@
{
"name": "skel",
"private": true,
"scripts": {
"start": "meteor run",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
"dependencies": {
"@babel/runtime": "^7.8.3",
"meteor-node-stubs": "^1.0.0",
"vue": "^2.6.11",
"vue-meteor-tracker": "^2.0.0-beta.5"
},
"meteor": {
"mainModule": {
"client": "src/client.js",
"server": "src/server.js"
},
"testModule": "tests/main.js"
}
}

View File

@@ -0,0 +1,26 @@
<template>
<div>
<h1>Welcome to Meteor!</h1>
<hello/>
<info/>
</div>
</template>
<script>
import Hello from './components/Hello.vue'
import Info from './components/Info.vue'
export default {
components: {
Hello,
Info,
},
}
</script>
<style>
body {
font-family: sans-serif;
padding: 10px;
}
</style>

View File

@@ -0,0 +1,12 @@
import Vue from 'vue'
import './plugins'
import App from './App.vue'
Meteor.startup(() => {
new Vue({
el: '#app',
...App,
})
})

View File

@@ -0,0 +1,3 @@
import { Mongo } from 'meteor/mongo';
export default new Mongo.Collection('links');

View File

@@ -0,0 +1,24 @@
// Tests for the behavior of the links collection
//
// https://guide.meteor.com/testing.html
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import Links from './links.js';
if (Meteor.isServer) {
describe('links collection', function () {
it('insert correctly', function () {
const linkId = Links.insert({
title: 'meteor homepage',
url: 'https://www.meteor.com',
});
const added = Links.find({ _id: linkId });
const collectionName = added._getCollectionName();
const count = added.count();
assert.equal(collectionName, 'links');
assert.equal(count, 1);
});
});
}

View File

@@ -0,0 +1,27 @@
<template>
<div>
<button @click="increment">Click Me</button>
<p>You've pressed the button {{counter}} times.</p>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
}
},
methods: {
increment() {
this.counter += 1
}
},
}
</script>
<style scoped>
p {
font-family: serif;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div>
<h2>Learn Meteor!</h2>
<ul>
<li>
<form class="info-link-add">
<input type="text" v-model="title" name="title" placeholder="Title" required>
<input type="url" v-model="url" name="url" placeholder="Url" required>
<input type="submit" name="submit" @click="submit($event)" value="Add new link">
</form>
</li>
<li v-for="link in links"><a :href="link.url" target="_blank">{{link.title}}</a></li>
</ul>
</div>
</template>
<script>
import Links from '../collections/Links'
export default {
data() {
return {
title: "",
url: "",
}
},
meteor: {
$subscribe: {
'links': [],
},
links () {
return Links.find({})
},
},
methods: {
submit(event) {
event.preventDefault()
Meteor.call('createLink', this.title, this.url, (error) => {
if (error) {
alert(error.error)
} else {
this.title = ''
this.url = ''
}
})
}
},
}
</script>
<style scoped>
ul {
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,32 @@
import { Meteor } from 'meteor/meteor';
import Links from './collections/Links.js';
Meteor.startup(() => {
// if the Links collection is empty
if (Links.find().count() === 0) {
const data = [
{
title: 'Do the Tutorial',
url: 'https://www.meteor.com/try',
createdAt: new Date(),
},
{
title: 'Follow the Guide',
url: 'http://guide.meteor.com',
createdAt: new Date(),
},
{
title: 'Read the Docs',
url: 'https://docs.meteor.com',
createdAt: new Date(),
},
{
title: 'Discussions',
url: 'https://forums.meteor.com',
createdAt: new Date(),
},
];
data.forEach(link => Links.insert(link));
}
});

View File

@@ -0,0 +1,7 @@
<head>
<title>~name~</title>
</head>
<body>
<div id="app"></div>
</body>

View File

@@ -0,0 +1,16 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Links from '../collections/Links.js';
Meteor.methods({
'createLink'(title, url) {
check(url, String);
check(title, String);
return Links.insert({
url,
title,
createdAt: new Date(),
});
},
});

View File

@@ -0,0 +1,20 @@
import { Meteor } from 'meteor/meteor';
import { assert } from 'chai';
import Links from '../collections/Links.js';
import './methods.js';
if (Meteor.isServer) {
describe('method: createLink', function () {
beforeEach(function () {
Links.remove({});
});
it('can add a new link', function () {
const addLink = Meteor.server.method_handlers['createLink'];
addLink.apply({}, ['meteor.com', 'https://www.meteor.com']);
assert.equal(Links.find().count(), 1);
});
});
}

View File

@@ -0,0 +1 @@
import './createLink'

View File

@@ -0,0 +1,4 @@
import Vue from 'vue'
import VueMeteorTracker from 'vue-meteor-tracker'
Vue.use(VueMeteorTracker)

View File

@@ -0,0 +1 @@
import './links'

View File

@@ -0,0 +1,6 @@
import { Meteor } from 'meteor/meteor';
import Links from '../collections/Links.js';
Meteor.publish('links', function () {
return Links.find();
});

View File

@@ -0,0 +1,22 @@
import { assert } from 'chai'
import { PublicationCollector } from 'meteor/johanbrook:publication-collector'
import Links from '../collections/Links.js'
import './publications.js'
describe('Publish links', function () {
beforeEach(function () {
Links.remove({})
Links.insert({
title: 'meteor homepage',
url: 'https://www.meteor.com'
})
})
it('sends all links', function (done) {
const collector = new PublicationCollector()
collector.collect('links', (collections) => {
assert.equal(collections.links.length, 1)
done()
})
})
})

View File

@@ -0,0 +1,3 @@
import './fixtures'
import './methods'
import './publications'

View File

@@ -0,0 +1,20 @@
import assert from "assert";
describe("skel", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
assert.strictEqual(name, "skel");
});
if (Meteor.isClient) {
it("client is not server", function () {
assert.strictEqual(Meteor.isServer, false);
});
}
if (Meteor.isServer) {
it("server is not client", function () {
assert.strictEqual(Meteor.isClient, false);
});
}
});