Merge branch 'release-3.4.1' into fix-rhel8-vscode-nsfw

This commit is contained in:
Nacho Codoñer
2026-01-07 13:05:54 +01:00
committed by GitHub
18 changed files with 548 additions and 64 deletions

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ node_modules
\#*\#
.\#*
.idea
!.idea/icon.svg
*.iml
*.sublime-project
*.sublime-workspace

21
.idea/icon.svg generated Executable file
View File

@@ -0,0 +1,21 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg2" preserveAspectRatio="xMidYMid meet" version="1.1" viewBox="0 0 160.10664 156.98515" height="156.98515" width="160.10664">
<metadata id="metadata26">
<rdf:rdf xmlns="http://www.w3.org/1999/xhtml">
<cc:work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"></dc:type>
<dc:title></dc:title>
</cc:work>
</rdf:rdf>
</metadata>
<defs id="defs24"/>
<g transform="matrix(0.62649123,0,0,0.62649123,-0.27477954,-0.27455194)" id="g6" style="fill:#de4f4f">
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 0.43860078,0.43823749 219.30039,232.26587 c 0,0 7.45622,5.25885 13.15803,-0.87647 5.70181,-6.13533 1.3158,-12.27065 1.3158,-12.27065 L 0.43860078,0.43823749 l 0,0 z" id="path8"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 69.737525,22.350112 236.40582,202.02749 c 0,0 7.45622,5.25884 13.15803,-0.87648 5.70181,-6.13533 1.3158,-12.27065 1.3158,-12.27065 L 69.737525,22.350112 l 0,0 z" id="path10"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 21.052838,69.241524 187.72114,248.9189 c 0,0 7.45621,5.25885 13.15802,-0.87648 5.70181,-6.13532 1.3158,-12.27065 1.3158,-12.27065 L 21.052838,69.241524 l 0,0 z" id="path12"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 128.32077,41.194324 244.76195,166.72418 c 0,0 5.20922,3.67404 9.19273,-0.61234 3.98351,-4.28639 0.91927,-8.57278 0.91927,-8.57278 L 128.32077,41.194324 l 0,0 z" id="path14"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="M 37.091803,123.58297 153.53299,249.11282 c 0,0 5.20921,3.67405 9.19273,-0.61234 3.98351,-4.28638 0.91927,-8.57277 0.91927,-8.57277 L 37.091803,123.58297 l 0,0 z" id="path16"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="m 188.15974,68.365049 52.77506,57.067161 c 0,0 2.57683,1.72156 4.54735,-0.28693 1.97051,-2.00849 0.45473,-4.01699 0.45473,-4.01699 l -57.77714,-52.763241 0,0 z" id="path18"/>
<path style="fill:#de4f4f; fill-opacity:1; stroke:none" d="m 66.228719,181.43032 52.775071,57.06716 c 0,0 2.57682,1.72156 4.54734,-0.28693 1.97051,-2.00849 0.45473,-4.01698 0.45473,-4.01698 l -57.777141,-52.76325 0,0 z" id="path20"/>
</g>
</svg>

View File

@@ -507,6 +507,7 @@ Meteor.methods({forgotPassword: async options => {
const user = await Accounts.findUserByEmail(options.email, { fields: { emails: 1 } });
if (!user) {
if (Accounts._options.ambiguousErrorMessages) return;
Accounts._handleError("User not found");
}

View File

@@ -1417,9 +1417,8 @@ if (Meteor.isServer) (() => {
);
Accounts._options.ambiguousErrorMessages = true;
await test.throwsAsync(
async () => await Meteor.callAsync('forgotPassword', wrongOptions),
'Something went wrong. Please check your credentials'
await test.doesNotThrowsAsync(
async () => await Meteor.callAsync("forgotPassword", wrongOptions)
);
Accounts._options.ambiguousErrorMessages = false;

View File

@@ -272,7 +272,13 @@ EXPERIMENTAL way to compare two strings that results in a nicer display in the t
### Assertions without optional fail messages
`test.throws(func, expected);`
`test.throws(func, expected[, message]);`
`test.throwsAsync(func, expected[, message]);`
`test.doesNotThrows(func[, failureMessage]);`
`test.doesNotThrowsAsync(func[, failureMessage]);`
`expected` can be:
@@ -281,6 +287,8 @@ EXPERIMENTAL way to compare two strings that results in a nicer display in the t
- `regexp`: pass if the exception message passes the regexp.
- `function`: call the function as a predicate with the exception.
`doesNotThrows` and `doesNotThrowsAsync` assert that the function does not throw. If the function throws, the assertion fails. The optional `failureMessage` is only used to annotate the failure.
Note: Node's `assert.throws` also accepts a constructor to test whether the error is of the expected class. But since JavaScript can't distinguish between constructors and plain functions and Node's `assert.throws` also accepts a predicate function, if the error fails the `instanceof` test with the constructor then the constructor is then treated as a predicate and called (!)
The upshot is, if you want to test whether an error is of a particular class, use a predicate function.

View File

@@ -244,6 +244,19 @@ export class TestCaseResults {
// The upshot is, if you want to test whether an error is of a
// particular class, use a predicate function.
//
/**
* Assert that `f` throws.
*
* `expected` can be:
* - undefined: accept any exception.
* - string: pass if the string is a substring of the exception message.
* - regexp: pass if the exception message passes the regexp.
* - function: call the function as a predicate with the exception.
*
* @param {Function} f
* @param {*} expected
* @param {String} message
*/
throws(f, expected, message) {
let actual;
const predicate = this._guessPredicate(expected);
@@ -258,10 +271,34 @@ export class TestCaseResults {
}
/**
* Same as throw, but accepts an async function as a parameter.
* @param f
* @param expected
* @param message
* Assert that `f` does not throw.
* @param {Function} f
* @param {String} failureMessage
*/
doesNotThrows(f, failureMessage) {
let actual;
try {
f();
} catch (exception) {
actual = exception;
}
if (!actual) {
this.ok();
} else {
this.fail({
type: "throws",
message: ("threw an error unexpectedly: " + actual.message) + (failureMessage ? ": " + failureMessage : ""),
});
}
}
/**
* Same as `throws`, but accepts an async function as a parameter.
* @param {Function} f
* @param {*} expected
* @param {String} message
* @returns {Promise<void>}
*/
async throwsAsync(f, expected, message) {
@@ -276,6 +313,31 @@ export class TestCaseResults {
this._assertActual(actual, predicate, message);
}
/**
* Same as `doesNotThrows`, but accepts an async function as a parameter.
* @param {Function} f
* @param {String} failureMessage
* @returns {Promise<void>}
*/
async doesNotThrowsAsync(f, failureMessage) {
let actual;
try {
await f();
} catch (exception) {
actual = exception;
}
if (!actual) {
this.ok();
} else {
this.fail({
type: "throws",
message: ("threw an error unexpectedly: " + actual.message) + (failureMessage ? ": " + failureMessage : ""),
});
}
}
isTrue(v, msg) {
if (v)
this.ok();

View File

@@ -312,9 +312,13 @@ async function ensureWatchRoot(dirPath: string): Promise<void> {
if (/Events were dropped/.test(err.message)) {
return;
}
if (/RootResolveError/.test(err.message) || /failed to resolve root/.test(err.message)) {
console.warn(`Parcel watcher root resolve error on ${osDirPath}, ignoring: ${err.message}`);
ignoredWatchRoots.add(dirPath);
watchRoots.delete(dirPath);
return;
}
console.error(`Parcel watcher error on ${osDirPath}:`, err);
// Only disable native watching for critical errors (like ENOSPC).
// @ts-ignore
if (err.code === "ENOSPC" || err.errno === require("constants").ENOSPC) {
fallbackToPolling();
}
@@ -340,9 +344,11 @@ async function ensureWatchRoot(dirPath: string): Promise<void> {
(e.code === "ENOTDIR" ||
/Not a directory/.test(e.message) ||
e.code === "EBADF" ||
/Bad file descriptor/.test(e.message))
/Bad file descriptor/.test(e.message) ||
/RootResolveError/.test(e.message) ||
/failed to resolve root/.test(e.message))
) {
console.warn(`Skipping watcher for ${osDirPath}: not a directory`);
console.warn(`Skipping watcher for ${osDirPath}: ${e.message || 'not watchable'}`);
ignoredWatchRoots.add(dirPath);
} else {
console.error(`Failed to start watcher for ${osDirPath}:`, e);

View File

@@ -1,9 +1,10 @@
import { createRoot } from 'react-dom/client';
import { Meteor } from 'meteor/meteor';
import { App } from '/imports/ui/App';
import { createRoot } from "react-dom/client";
import { Meteor } from "meteor/meteor";
import { App } from "/imports/ui/App";
import "/imports/ui/styles.css";
Meteor.startup(() => {
const container = document.getElementById('react-target');
const container = document.getElementById("react-target");
const root = createRoot(container);
root.render(<App />);
});

View File

@@ -1,10 +1,13 @@
import { Hello } from './Hello.jsx';
import { Info } from './Info.jsx';
import { Counter } from "./Counter.jsx";
import { Header } from "./Header.jsx";
import { Info } from "./Info.jsx";
export const App = () => (
<div>
<h1>Welcome to Meteor!</h1>
<Hello/>
<Info/>
<div className="page">
<Header />
<main className="main">
<Counter />
<Info />
</main>
</div>
);

View File

@@ -0,0 +1,24 @@
import { useState } from "react";
export const Counter = () => {
const [counter, setCounter] = useState(0);
const increment = () => {
setCounter(counter + 1);
};
return (
<div className="card counter-card">
<div className="counter-content">
<button className="button" onClick={increment}>
Click Me
</button>
<p className="counter-text">
You've pressed the button{" "}
<span className="counter-value">{counter}</span>{" "}
{counter === 1 ? "time" : "times"}.
</p>
</div>
</div>
);
};

View File

@@ -0,0 +1,14 @@
import MeteorLogo from "./meteor-logo.svg";
export const Header = () => {
return (
<div className="header">
<nav className="nav container">
<div className="logo-container">
<MeteorLogo className="logo" />
</div>
<h1 className="page-title">Welcome to Meteor!</h1>
</nav>
</div>
);
};

View File

@@ -1,16 +0,0 @@
import { useState } from 'react';
export const Hello = () => {
const [counter, setCounter] = useState(0);
const increment = () => {
setCounter(counter + 1);
};
return (
<div>
<button onClick={increment}>Click Me</button>
<p>You've pressed the button {counter} times.</p>
</div>
);
};

View File

@@ -1,22 +1,30 @@
import { useFind, useSubscribe } from 'meteor/react-meteor-data';
import { LinksCollection } from '../api/links';
import { useFind, useSubscribe } from "meteor/react-meteor-data";
import { LinksCollection } from "../api/links";
export const Info = () => {
const isLoading = useSubscribe('links');
const isLoading = useSubscribe("links");
const links = useFind(() => LinksCollection.find());
if(isLoading()) {
if (isLoading()) {
return <div>Loading...</div>;
}
return (
<div>
<h2>Learn Meteor!</h2>
<ul>{links.map(
link => <li key={link._id}>
<a href={link.url} target="_blank">{link.title}</a>
</li>
)}</ul>
</div>
<section>
<h2 className="section-title">Learn Meteor!</h2>
<ul className="resources-grid">
{links.map((link) => (
<li className="section" key={link._id}>
<a href={link.url} className="resource-link" target="_blank">
<div className="card resource-card">
<div className="resource-content">
<span className="resource-title">{link.title}</span>
</div>
</div>
</a>
</li>
))}
</ul>
</section>
);
};

View File

@@ -0,0 +1,18 @@
<svg id="c1" data-name="c 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 800">
<defs>
<style>
.cls-1 {
fill: url(#g3);
}
</style>
<linearGradient id="g3" data-name="g3" x1="149.34" y1="168.23" x2="650.29" y2="637.76" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#cb180a"/>
<stop offset=".37" stop-color="#cd1909"/>
<stop offset=".6" stop-color="#d61d07"/>
<stop offset=".79" stop-color="#e52404"/>
<stop offset=".97" stop-color="#f92d00"/>
<stop offset="1" stop-color="#ff3000"/>
</linearGradient>
</defs>
<path class="cls-1" d="m648.34,479.05c-5.33,32.62-17.15,61.33-35.44,86.12-18.31,24.8-41.19,44.1-68.64,57.91-27.46,13.81-57.66,20.72-90.61,20.72-28.29,0-54.17-4.74-77.63-14.23-22.26-9-41.18-21.63-56.84-37.82,10.14,7,21.23,12.96,33.3,17.84,23.46,9.49,49.34,14.23,77.63,14.23,32.95,0,63.15-6.9,90.61-20.72,27.46-13.81,50.34-33.11,68.64-57.91,18.3-24.79,30.12-53.5,35.44-86.12,4.99-28.96,4.07-55.49-2.75-79.63-6.83-24.13-18.31-44.93-34.45-62.4-.87-.95-1.8-1.83-2.7-2.76,9.59,6.63,18.35,14.2,26.24,22.73,16.14,17.47,27.62,38.28,34.45,62.4,6.82,24.13,7.74,50.67,2.75,79.63Zm-100,15.88c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87l-273.47-250.6,250.6,273.48m-18.63,48.73c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87L164.5,155.58l365.2,388.08m-47.3,18.63c.21.22.47.48.69.69,6.51,6.13,16.75,5.82,22.87-.69,6.12-6.51,5.81-16.75-.69-22.87l-273.47-250.6,250.6,273.48m-91.19,6.46c.1.11.21.22.32.32,2.19,2.01,5.6,1.87,7.62-.32,2.02-2.19,1.87-5.6-.32-7.62l-94.68-87.06,87.06,94.68m178.94-179.78c.1.1.21.22.32.32,2.19,2.01,5.61,1.87,7.62-.32,2.02-2.19,1.87-5.6-.32-7.62l-94.68-87.06,87.06,94.69m-323.6-16.23c4.87,5.85,184.93,198.16,188.58,201.81,3.65,3.65,9.58,3.65,13.23,0,3.65-3.65,3.65-9.58,0-13.23-3.65-3.65-196.06-183.61-201.81-188.58Zm141.5-128.6c4.87,5.85,178.77,192,182.43,195.65,3.65,3.65,9.58,3.65,13.23,0,3.65-3.65,3.65-9.58,0-13.23-3.65-3.65-189.91-177.46-195.65-182.43Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,305 @@
/* this file is imported in client/main.jsx */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
:root {
/* Colors */
--color-background: hsl(210, 20%, 98%);
--color-foreground: hsl(220, 20%, 15%);
--color-card: hsl(0, 0%, 100%);
--color-primary: hsl(4, 70%, 55%);
--color-primary-hover: hsl(4, 70%, 45%);
--color-muted: hsl(220, 10%, 50%);
--color-border: hsl(220, 14%, 90%);
/* Shadows */
--shadow-card: 0 1px 3px 0 hsl(220 20% 15% / 0.04),
0 1px 2px -1px hsl(220 20% 15% / 0.04);
--shadow-card-hover: 0 10px 15px -3px hsl(220 20% 15% / 0.08),
0 4px 6px -4px hsl(220 20% 15% / 0.04);
/* Spacing */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
/* Border radius */
--radius: 0.75rem;
--radius-sm: 0.5rem;
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 200ms ease;
--transition-slow: 250ms ease;
}
/* ============ Reset & Base Styles ============ */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;
background-color: var(--color-background);
color: var(--color-foreground);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
text-decoration: none;
color: inherit;
}
/* ============ Layout ============ */
.page {
min-height: 100vh;
background-color: var(--color-background);
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding-left: var(--spacing-md);
padding-right: var(--spacing-md);
}
@media (min-width: 768px) {
.container {
padding-left: var(--spacing-lg);
padding-right: var(--spacing-lg);
}
}
/* ============ Header / Navigation ============ */
.header {
border-bottom: 1px solid var(--color-border);
background-color: var(--color-card);
border-radius: var(--radius);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 4rem;
}
.logo-container {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.logo {
width: 4rem;
height: 4rem;
}
.logo-text {
font-weight: 600;
color: var(--color-foreground);
display: none;
}
@media (min-width: 640px) {
.logo-text {
display: inline;
}
}
.page-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-foreground);
letter-spacing: -0.025em;
}
@media (min-width: 768px) {
.page-title {
font-size: 1.5rem;
}
}
.nav-link {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-primary);
transition: opacity var(--transition-fast);
}
.nav-link:hover {
opacity: 0.8;
}
/* ============ Main Content ============ */
.main {
padding-top: var(--spacing-xl);
padding-bottom: var(--spacing-xl);
}
@media (min-width: 768px) {
.main {
padding-top: var(--spacing-2xl);
padding-bottom: var(--spacing-2xl);
}
}
/* ============ Card Component ============ */
.card {
background-color: var(--color-card);
border-radius: var(--radius);
box-shadow: var(--shadow-card);
border: 1px solid var(--color-border);
}
/* ============ Counter Section ============ */
.counter-card {
padding: var(--spacing-lg);
margin-bottom: 2.5rem;
}
@media (min-width: 768px) {
.counter-card {
padding: var(--spacing-xl);
margin-bottom: var(--spacing-2xl);
}
}
.counter-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
}
@media (min-width: 640px) {
.counter-content {
flex-direction: row;
}
}
.counter-text {
color: var(--color-muted);
text-align: center;
}
@media (min-width: 640px) {
.counter-text {
text-align: left;
}
}
.counter-value {
font-weight: 600;
color: var(--color-foreground);
}
/* ============ Button Component ============ */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 120px;
padding: 0.625rem 1.5rem;
font-size: 0.875rem;
font-weight: 500;
font-family: inherit;
color: white;
background-color: var(--color-primary);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background-color var(--transition-normal),
transform var(--transition-fast);
}
.button:hover {
background-color: var(--color-primary-hover);
}
.button:active {
transform: scale(0.98);
}
.button:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* ============ Resources Section ============ */
.section-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-foreground);
margin-bottom: var(--spacing-lg);
letter-spacing: -0.025em;
}
.resources-grid {
list-style-type: none;
display: grid;
grid-template-columns: 1fr;
gap: var(--spacing-md);
}
@media (min-width: 640px) {
.resources-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* ============ Resource Card ============ */
.resource-link {
display: block;
}
.resource-card {
padding: 1.25rem;
transition: box-shadow var(--transition-slow),
transform var(--transition-slow);
}
.resource-card:hover {
box-shadow: var(--shadow-card-hover);
transform: translateY(-2px);
}
.resource-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.resource-title {
font-weight: 500;
color: var(--color-foreground);
transition: color var(--transition-fast);
}
.resource-link:hover .resource-title {
color: var(--color-primary);
}
.resource-icon {
width: 1rem;
height: 1rem;
color: var(--color-muted);
opacity: 0;
transition: opacity var(--transition-fast), color var(--transition-fast);
}
.resource-link:hover .resource-icon {
opacity: 1;
color: var(--color-primary);
}

View File

@@ -20,6 +20,7 @@
"@rspack/cli": "^1.6.5",
"@rspack/core": "^1.6.5",
"@rspack/plugin-react-refresh": "^1.4.3",
"@svgr/webpack": "^8.1.0",
"react-refresh": "^0.17.0"
},
"meteor": {

View File

@@ -1,4 +1,4 @@
const { defineConfig } = require('@meteorjs/rspack');
const { defineConfig } = require("@meteorjs/rspack");
/**
* Rspack configuration for Meteor projects.
@@ -10,6 +10,17 @@ const { defineConfig } = require('@meteorjs/rspack');
*
* Use these flags to adjust your build settings based on environment.
*/
module.exports = defineConfig(Meteor => {
return {};
module.exports = defineConfig((Meteor) => {
return {
module: {
rules: [
// Add support for importing SVGs as React components
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ["@svgr/webpack"],
},
],
},
};
});

View File

@@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { LinksCollection } from '/imports/api/links';
import { Meteor } from "meteor/meteor";
import { LinksCollection } from "/imports/api/links";
import { Random } from "meteor/random";
async function insertLink({ title, url }) {
await LinksCollection.insertAsync({ title, url, createdAt: new Date() });
@@ -7,25 +8,35 @@ async function insertLink({ title, url }) {
Meteor.startup(async () => {
// If the Links collection is empty, add some data.
if (await LinksCollection.find().countAsync() === 0) {
if ((await LinksCollection.find().countAsync()) === 0) {
await insertLink({
title: 'Do the Tutorial',
url: 'https://react-tutorial.meteor.com/simple-todos/01-creating-app.html',
title: "Do the Tutorial",
url: "https://docs.meteor.com/tutorials/react/",
});
await insertLink({
title: 'Follow the Guide',
url: 'https://guide.meteor.com',
title: "Follow the Guide",
url: "https://docs.meteor.com/tutorials/application-structure/",
});
await insertLink({
title: 'Read the Docs',
url: 'https://docs.meteor.com',
title: "Read the Docs",
url: "https://docs.meteor.com",
});
await insertLink({
title: 'Discussions',
url: 'https://forums.meteor.com',
title: "Discussions",
url: "https://forums.meteor.com",
});
await insertLink({
title: "Join us on Discord",
url: "https://discord.gg/6mS3wHNg",
});
await insertLink({
title: "Deploying in Galaxy",
url: "https://www.meteor.com/hosting",
});
}
@@ -35,3 +46,9 @@ Meteor.startup(async () => {
return LinksCollection.find();
});
});
Meteor.methods({
about() {
return `This is a Meteor application running React with React Router. this is a generated id: ${Random.id()}`;
},
});