From e5fdc7b6eee8e46eb1f396428a8fbf44247c1e48 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Mon, 1 Sep 2025 10:26:32 -0300 Subject: [PATCH 01/43] DEV: Update package.json metadata Revised the description and homepage fields in package.json to better reflect the Meteor project's repository and official website. --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 28c1cf54f4..9a3fc88d24 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,16 @@ { "name": "meteor", "version": "0.0.1", - "description": "Used to apply Prettier and ESLint manually", + "description": "Meteor's main repository, containing the Meteor tool, core packages, and documentation.", "repository": { "type": "git", "url": "git+https://github.com/meteor/meteor.git" }, - "author": "Filipe Névola", "license": "MIT", "bugs": { "url": "https://github.com/meteor/meteor/issues" }, - "homepage": "https://github.com/meteor/meteor#readme", + "homepage": "https://www.meteor.com/", "devDependencies": { "@babel/core": "^7.21.3", "@babel/eslint-parser": "^7.21.3", From 5db21f4df656fb3f717dd56dd64667c574cc586e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Mon, 29 Sep 2025 22:30:04 -0300 Subject: [PATCH 02/43] DEV: Flip `--raw-logs` to default true and add `--timestamps` option. --- tools/cli/commands.js | 17 +++++++---------- tools/cli/help.txt | 7 +++++-- v3-docs/docs/cli/index.md | 13 +++++++------ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tools/cli/commands.js b/tools/cli/commands.js index d81c03ffb5..756ec007ae 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -451,7 +451,8 @@ var runCommandOptions = { ...inspectOptions, 'no-release-check': { type: Boolean }, production: { type: Boolean }, - 'raw-logs': { type: Boolean }, + 'raw-logs': { type: Boolean, default: true }, + timestamps: { type: Boolean, default: false }, // opposite of --raw-logs settings: { type: String, short: "s" }, verbose: { type: Boolean, short: "v" }, // With --once, meteor does not re-run the project if it crashes @@ -535,10 +536,7 @@ async function doRunCommand(options) { ); } - if (options['raw-logs']) { - runLog.setRawLogs(true); - } - + runLog.setRawLogs(!options['raw-logs'] && options.timestamps); let webArchs = projectContext.platformList.getWebArchs(); if (! _.isEmpty(runTargets) || @@ -2125,7 +2123,8 @@ testCommandOptions = { // like progress bars and spinners are unimportant. headless: { type: Boolean }, verbose: { type: Boolean, short: "v" }, - 'raw-logs': { type: Boolean }, + 'raw-logs': { type: Boolean, default: true }, + timestamps: { type: Boolean, default: false }, // opposite of --raw-logs // Undocumented. See #Once once: { type: Boolean }, @@ -2176,7 +2175,7 @@ testCommandOptions = { 'extra-packages': { type: String }, 'exclude-archs': { type: String }, - + // Same as TINYTEST_FILTER filter: { type: String, short: 'f' }, } @@ -2260,9 +2259,7 @@ async function doTestCommand(options) { serverArchitectures.push(DEPLOY_ARCH); } - if (options['raw-logs']) { - runLog.setRawLogs(true); - } + runLog.setRawLogs(!options['raw-logs'] && options.timestamps); var includePackages = []; if (options['extra-packages']) { diff --git a/tools/cli/help.txt b/tools/cli/help.txt index 7f9e083861..96e611feca 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -90,7 +90,8 @@ Options: Meteor app source code as by default the port is generated using the id inside .meteor/.id file. --production Simulate production mode. Minify and bundle CSS and JS files. - --raw-logs Run without parsing logs from stdout and stderr. + --raw-logs Run without parsing logs from stdout and stderr (default: true). + --timestamps Run with timestamps in logs, the same as passing `--raw-logs=false`. --settings, -s Set optional data for Meteor.settings on the server. --release Specify the release of Meteor to use. --verbose Print all output from builds logs. @@ -747,7 +748,9 @@ Options: important when multiple Cordova apps are build from the same Meteor app source code as by default the port is generated using the id inside .meteor/.id file. - --raw-logs Run without parsing logs from stdout and stderr. + --raw-logs Run without parsing logs from stdout and stderr (default: true). + --timestamps Run with timestamps in logs, the same as passing `--raw-logs=false`. + --settings, -s Set optional data for Meteor.settings on the server --ios, Run tests in an emulator or on a mobile device. All of diff --git a/v3-docs/docs/cli/index.md b/v3-docs/docs/cli/index.md index c72bded0bc..00bdfa2916 100644 --- a/v3-docs/docs/cli/index.md +++ b/v3-docs/docs/cli/index.md @@ -53,7 +53,8 @@ This is the default command. Simply running `meteor` is the same as `meteor run` | `--mobile-server ` | Location where mobile builds connect (defaults to local IP and port). Can include URL scheme (e.g., https://example.com:443) | | `--cordova-server-port ` | Local port where Cordova will serve content | | `--production` | Simulate production mode. Minify and bundle CSS and JS files | -| `--raw-logs` | Run without parsing logs from stdout and stderr | +| `--raw-logs` | Run without parsing logs from stdout and stderr (default: true) | +| `--timestamps` | Run with timestamps in logs, the same as passing `--raw-logs=false`. | | `--settings`, `-s ` | Set optional data for Meteor.settings on the server | | `--release ` | Specify the release of Meteor to use | | `--verbose` | Print all output from builds logs | @@ -247,10 +248,10 @@ If you run `meteor create` without arguments, Meteor will launch an interactive Typescript # To create an app using TypeScript and React Vue # To create a basic Vue3-based app Svelte # To create a basic Svelte app - Tailwind # To create an app using React and Tailwind - Chakra-ui # To create an app Chakra UI and React - Solid # To create a basic Solid app - Apollo # To create a basic Apollo + React app + Tailwind # To create an app using React and Tailwind + Chakra-ui # To create an app Chakra UI and React + Solid # To create a basic Solid app + Apollo # To create a basic Apollo + React app Bare # To create an empty app ``` ::: @@ -359,7 +360,7 @@ To learn more about the recommended file structure for Meteor apps, check the [M ## meteor generate {meteorgenerate} -``meteor generate`` is a command to generate boilerplate for your current project. `meteor generate` receives a name as a parameter, and generates files containing code to create a [Collection](https://docs.meteor.com/api/collections.html) with that name, [Methods](https://docs.meteor.com/api/meteor.html#methods) to perform basic CRUD operations on that Collection, and a [Subscription](https://docs.meteor.com/api/meteor.html#Meteor-publish) to read its data with reactivity from the client. +``meteor generate`` is a command to generate boilerplate for your current project. `meteor generate` receives a name as a parameter, and generates files containing code to create a [Collection](https://docs.meteor.com/api/collections.html) with that name, [Methods](https://docs.meteor.com/api/meteor.html#methods) to perform basic CRUD operations on that Collection, and a [Subscription](https://docs.meteor.com/api/meteor.html#Meteor-publish) to read its data with reactivity from the client. If you run ``meteor generate`` without arguments, it will ask you for a name, and name the auto-generated Collection accordingly. It will also ask if you do want Methods for your API and Publications to be generated as well. From d0acf4f5f0162ed24ab3045f15eb0041989ce543 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Mon, 29 Sep 2025 22:36:49 -0300 Subject: [PATCH 03/43] DEV: flip the rawLogs option --- tools/cli/commands.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 756ec007ae..1e1b71914f 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -536,7 +536,7 @@ async function doRunCommand(options) { ); } - runLog.setRawLogs(!options['raw-logs'] && options.timestamps); + runLog.setRawLogs(options['raw-logs'] && !options.timestamps); let webArchs = projectContext.platformList.getWebArchs(); if (! _.isEmpty(runTargets) || @@ -2259,7 +2259,8 @@ async function doTestCommand(options) { serverArchitectures.push(DEPLOY_ARCH); } - runLog.setRawLogs(!options['raw-logs'] && options.timestamps); + runLog.setRawLogs(options['raw-logs'] && !options.timestamps); + var includePackages = []; if (options['extra-packages']) { From 7e4ff58af0c8df8823ae599c814f38001f8d86e5 Mon Sep 17 00:00:00 2001 From: italo jose Date: Thu, 2 Oct 2025 10:11:27 -0300 Subject: [PATCH 04/43] refactor: streamline inactive issues script and improve comment handling --- .github/scripts/inactive-issues.js | 326 +++++++++++++++-------------- 1 file changed, 168 insertions(+), 158 deletions(-) diff --git a/.github/scripts/inactive-issues.js b/.github/scripts/inactive-issues.js index cbca2f31aa..fe0e3f5270 100644 --- a/.github/scripts/inactive-issues.js +++ b/.github/scripts/inactive-issues.js @@ -1,190 +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 idleTimeComment = daysToComment * 24 * 60 * 60 * 1000; // 60 days in milliseconds - const idleTimeLabel = daysToLabel * 24 * 60 * 60 * 1000; // 90 days in milliseconds - - // Function to fetch issues until we find recently updated ones + + 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, please comment to reactivate it.'; + + // Fetch all open issues async function fetchAllIssues() { - let allIssues = []; let page = 1; - let hasNextPage = true; - const now = new Date(); - const minInactivity = idleTimeComment; // 60 days in milliseconds - - while (hasNextPage) { - const response = await github.rest.issues.listForRepo({ + 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: 100, - page: page, + per_page, + page, sort: 'updated', - direction: 'asc' // Oldest updated first + direction: 'asc' }); - - // Check if the most recently updated issue on this page is too recent - let recentIssueFound = false; - if (response.data.length > 0) { - // Check the last issue on the page (most recently updated) - const lastIssue = response.data[response.data.length - 1]; - const lastIssueUpdatedAt = new Date(lastIssue.updated_at); - const timeSinceLastIssueUpdate = now.getTime() - lastIssueUpdatedAt.getTime(); - - if (timeSinceLastIssueUpdate < minInactivity) { - // This page already has issues that are too recent, filter them out - const filteredIssues = response.data.filter(issue => { - const issueUpdatedAt = new Date(issue.updated_at); - const timeSinceUpdate = now.getTime() - issueUpdatedAt.getTime(); - return timeSinceUpdate >= minInactivity; - }); - - allIssues = allIssues.concat(filteredIssues); - recentIssueFound = true; - hasNextPage = false; - } else { - // All issues on this page are old enough, keep them all - allIssues = allIssues.concat(response.data); - } - } - - // Stop if we found recent issues or reached the end of pagination - if (recentIssueFound) { - hasNextPage = false; - } else if (response.data.length < 100) { - hasNextPage = false; + + if (!data.length) break; + results.push(...data); + + if (data.length < per_page) { + keepGoing = false; } else { page++; - // Small delay to avoid hitting rate limits - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((r) => setTimeout(r, 120)); } } - - return allIssues; + return results; } - - // Fetch all issues - const allIssues = await fetchAllIssues(); - - let processedCount = 0; - let commentedCount = 0; - let labeledCount = 0; - - for (const issue of allIssues) { - processedCount++; - - // Skip pull requests - if (issue.pull_request) { - continue; - } - - // Skip issues that already have the idle label - if (issue.labels.some(label => label.name === 'idle')) { - continue; - } - - // Get latest comment or update date - const issueUpdatedAt = new Date(issue.updated_at); - const timeSinceUpdate = now.getTime() - issueUpdatedAt.getTime(); - - // Handle 60-day idle issues (comment) - if (timeSinceUpdate > idleTimeComment && timeSinceUpdate <= idleTimeLabel) { - // Check if bot already commented to avoid duplicate comments - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - per_page: 100 - }); - - // Check if there's a recent bot comment - const botCommented = comments.data.some(comment => { - const commentDate = new Date(comment.created_at); - const timeSinceComment = now.getTime() - commentDate.getTime(); - const isBot = comment.user.login === 'github-actions[bot]'; - const isRecent = timeSinceComment < idleTimeComment; - const hasRightContent = comment.body.includes('Is this issue still relevant?'); - - return isBot && isRecent && hasRightContent; - }); - - if (!botCommented) { - try { - const result = await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `👋 @${issue.user.login} This issue has been open for 60 days with no activity. Is this issue still relevant? If there is no response or activity within the next 30 days, this issue will be labeled as \`idle\`.` - }); - commentedCount++; - } catch (error) { - // Add retry logic - try { - // Wait for 5 seconds before retrying - await new Promise(resolve => setTimeout(resolve, 5000)); - - const retryResult = await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `👋 @${issue.user.login} This issue has been open for 60 days with no activity. Is this issue still relevant? If there is no response or activity within the next 30 days, this issue will be labeled as \`idle\`.` - }); - commentedCount++; - } catch (retryError) { - // Failed retry, continue with other issues - } - } + // 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; } } - - // Handle 90-day idle issues (add label) - else if (timeSinceUpdate > idleTimeLabel) { - // Check if the issue has the idle label - if (!issue.labels.some(label => label.name === 'idle')) { + + 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')) { + 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 { - // Add the label - const labelResult = await github.rest.issues.addLabels({ + 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'] }); - - // Add a comment when labeling as idle - const commentResult = await github.rest.issues.createComment({ + await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, - body: `This issue has been automatically labeled as \`idle\` due to 90 days of inactivity. If this issue is still relevant, please comment to reactivate it.` + body: COMMENT_90_TEXT }); - labeledCount++; - } catch (error) { - // Add retry logic with exponential backoff - try { - // Wait for 5 seconds before retrying - await new Promise(resolve => setTimeout(resolve, 5000)); - - const retryLabelResult = await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: ['idle'] - }); - - // Retry adding comment - const retryCommentResult = await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `This issue has been automatically labeled as \`idle\` due to 90 days of inactivity. If this issue is still relevant, please comment to reactivate it.` - }); - labeledCount++; - } catch (retryError) { - // Continue with other issues if retry fails - } - } + 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 + ) + ); }; From 00d65a5368b12410266c8b816044935f04b74b91 Mon Sep 17 00:00:00 2001 From: italo jose Date: Thu, 2 Oct 2025 10:38:27 -0300 Subject: [PATCH 05/43] test: add tests for inactive issues script using Node's built-in test runner --- .../scripts/__tests__/inactive-issues.test.js | 175 ++++++++++++++++++ package.json | 3 + 2 files changed, 178 insertions(+) create mode 100644 .github/scripts/__tests__/inactive-issues.test.js diff --git a/.github/scripts/__tests__/inactive-issues.test.js b/.github/scripts/__tests__/inactive-issues.test.js new file mode 100644 index 0000000000..a4a4944938 --- /dev/null +++ b/.github/scripts/__tests__/inactive-issues.test.js @@ -0,0 +1,175 @@ +// 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('Human reply after reminder resets cycle (no immediate labeling)', async () => { + // Scenario: last human activity 10 days ago, bot commented 40 days ago (which was 50 days after original). Should NOT comment again or label. + const issue = makeIssue({ daysSinceHumanActivity: 10 }); + const issues = [issue]; + const commentsByIssue = { + [issue.number]: [ + { + id: 1, + body: '👋 @user1 This issue has been open with no human activity for 60 days... ', + created_at: daysAgo(50), + user: { login: 'github-actions[bot]', type: 'Bot' } + }, + { + id: 2, + body: 'I am still seeing this problem', + created_at: daysAgo(10), + user: { login: 'some-human', type: 'User' } + } + ] + }; + + await runScript({ issues, commentsByIssue }); + + const botComments = commentsByIssue[issue.number].filter(c => c.user.login === 'github-actions[bot]'); + assert.equal(botComments.length, 1, 'Should not add a new bot comment'); + assert.ok(!issue.labels.some(l => l.name === 'idle'), 'Should not label'); +}); + +test('Only bot comments (no human ever) counts from creation date', async () => { + const issue = makeIssue({ daysSinceHumanActivity: 61 }); + const issues = [issue]; + const commentsByIssue = { + [issue.number]: [ + { + id: 1, + body: 'Automated maintenance notice', + created_at: daysAgo(30), + user: { login: 'github-actions[bot]', type: 'Bot' } + } + ] + }; + + await runScript({ issues, commentsByIssue }); + const botComments = commentsByIssue[issue.number].filter(c => /60 days/.test(c.body)); + assert.equal(botComments.length, 1, 'Should add a 60-day reminder'); +}); diff --git a/package.json b/package.json index 28c1cf54f4..0df6e245d5 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "prettier": "^2.8.8", "typescript": "^5.4.5" }, + "scripts": { + "test:idle-bot": "node --test .github/scripts/__tests__/inactive-issues.test.js" + }, "jshintConfig": { "esversion": 11 }, From 7d069d05d0ea9c839e08a825d906e290f2066999 Mon Sep 17 00:00:00 2001 From: Aaron Rosenzweig Date: Sun, 5 Oct 2025 16:01:19 -0400 Subject: [PATCH 06/43] Added Blaze tutorial that is fiberless for Meteor 3 that is similar to the existing React and Vue tutorials --- .../tutorials/blaze/1.creating-the-app.md | 195 ++++++++ v3-docs/docs/tutorials/blaze/2.collections.md | 155 +++++++ .../tutorials/blaze/3.forms-and-events.md | 203 ++++++++ .../tutorials/blaze/4.update-and-remove.md | 170 +++++++ v3-docs/docs/tutorials/blaze/5.styles.md | 188 ++++++++ .../docs/tutorials/blaze/6.filter-tasks.md | 204 ++++++++ .../tutorials/blaze/7.adding-user-accounts.md | 438 ++++++++++++++++++ v3-docs/docs/tutorials/blaze/8.deploying.md | 98 ++++ v3-docs/docs/tutorials/blaze/9.next-steps.md | 17 + .../blaze/assets/collections-connect-db.png | Bin 0 -> 223133 bytes .../blaze/assets/collections-documents.png | Bin 0 -> 193576 bytes .../blaze/assets/collections-see-database.png | Bin 0 -> 156604 bytes .../blaze/assets/collections-tasks-list.png | Bin 0 -> 101360 bytes .../assets/step01-dev-tools-mobile-toggle.png | Bin 0 -> 10173 bytes .../assets/step01-mobile-with-meta-tags.png | Bin 0 -> 20982 bytes .../step01-mobile-without-meta-tags.png | Bin 0 -> 17652 bytes .../blaze/assets/step03-form-new-task.png | Bin 0 -> 181715 bytes .../blaze/assets/step03-new-task-on-list.png | Bin 0 -> 188068 bytes .../blaze/assets/step04-checkbox.png | Bin 0 -> 185879 bytes .../blaze/assets/step04-delete-button.png | Bin 0 -> 187157 bytes .../tutorials/blaze/assets/step05-styles.png | Bin 0 -> 213208 bytes .../tutorials/blaze/assets/step06-all.png | Bin 0 -> 222530 bytes .../blaze/assets/step06-ddp-messages.png | Bin 0 -> 427668 bytes .../blaze/assets/step06-extension.png | Bin 0 -> 220437 bytes .../blaze/assets/step06-filtered.png | Bin 0 -> 186238 bytes .../tutorials/blaze/assets/step07-login.png | Bin 0 -> 172493 bytes .../tutorials/blaze/assets/step07-logout.png | Bin 0 -> 219742 bytes v3-docs/docs/tutorials/blaze/index.md | 25 + 28 files changed, 1693 insertions(+) create mode 100644 v3-docs/docs/tutorials/blaze/1.creating-the-app.md create mode 100644 v3-docs/docs/tutorials/blaze/2.collections.md create mode 100644 v3-docs/docs/tutorials/blaze/3.forms-and-events.md create mode 100644 v3-docs/docs/tutorials/blaze/4.update-and-remove.md create mode 100644 v3-docs/docs/tutorials/blaze/5.styles.md create mode 100644 v3-docs/docs/tutorials/blaze/6.filter-tasks.md create mode 100644 v3-docs/docs/tutorials/blaze/7.adding-user-accounts.md create mode 100644 v3-docs/docs/tutorials/blaze/8.deploying.md create mode 100644 v3-docs/docs/tutorials/blaze/9.next-steps.md create mode 100644 v3-docs/docs/tutorials/blaze/assets/collections-connect-db.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/collections-documents.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/collections-see-database.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/collections-tasks-list.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step01-dev-tools-mobile-toggle.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step01-mobile-with-meta-tags.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step01-mobile-without-meta-tags.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step03-form-new-task.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step03-new-task-on-list.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step04-checkbox.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step04-delete-button.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step05-styles.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step06-all.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step06-ddp-messages.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step06-extension.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step06-filtered.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step07-login.png create mode 100644 v3-docs/docs/tutorials/blaze/assets/step07-logout.png create mode 100644 v3-docs/docs/tutorials/blaze/index.md diff --git a/v3-docs/docs/tutorials/blaze/1.creating-the-app.md b/v3-docs/docs/tutorials/blaze/1.creating-the-app.md new file mode 100644 index 0000000000..5344b0a410 --- /dev/null +++ b/v3-docs/docs/tutorials/blaze/1.creating-the-app.md @@ -0,0 +1,195 @@ +## 1: Creating the app + +### 1.1: Install Meteor + +First, we need to install Meteor. + +If you have Node installed but don't have Meteor installed, you can install it by running: + +```shell +npx meteor +``` + +Alternatively, if you are running some type of Unix OS such as Mac or Linux then you can install Meteor from the command line this way: + +```shell +curl https://install.meteor.com/ | sh +``` + +### 1.2: Create Meteor Project + +The easiest way to setup Meteor with Blaze is by using the command `meteor create` with the option `--blaze` and your project name: + +```shell +meteor create --blaze simple-todos-blaze +``` + +Meteor will create all the necessary files for you. + +The files located in the `client` directory are setting up your client side (web), you can see for example `client/main.html` where Meteor is rendering your App main component into the HTML. + +Also, check the `server` directory where Meteor is setting up the server side (Node.js), you can see the `server/main.js` which would be a good place to initialize your MongoDB database with some data. You don't need to install MongoDB as Meteor provides an embedded version of it ready for you to use. + +You can now run your Meteor app using: + +```shell +meteor +``` + +Don't worry, Meteor will keep your app in sync with all your changes from now on. + +Take a quick look at all the files created by Meteor, you don't need to understand them now but it's good to know where they are. + +Your Blaze code will be located inside the `imports/ui` directory, and the `App.html` and `App.js` files will be the root component of your Blaze To-do app. We haven't made those yet but will soon. + +### 1.3: Create Task Component + +To start working on our todo list app, let’s replace the code of the default starter app with the code below. From there, we’ll talk about what it does. + +First, let’s remove the body from our HTML entry-point (leaving just the `` tag): + +::: code-group + +```html [client/main.html] + + Simple todo + +``` +::: + +Create a new directory named `imports` inside the `simple-todos-blaze` folder. In the `imports` folder, create another directory with the name `ui` and add an `App.html` file inside of it with the content below: + +::: code-group + +```html [imports/ui/App.html] + + {{> mainContainer }} + + + + + +``` +::: + +We just created two templates, the `mainContainer`, which will be rendered in the body of our app, and it will show a header and a list of tasks that will render each item using the `task` template. Now, we need some data to present sample tasks on this page. + +### 1.4: Create Sample Tasks + +Create a new file called `App.js` in your `ui` folder. + +Inside your entry-point `main.js` file, remove all the previous content and just add the code below to import the new file `imports/ui/App.js`: + +::: code-group + +```js [client/main.js] +import '../imports/ui/App.js'; +``` +::: + +As you are not connecting to your server and database yet, let’s define some sample data, which we will use shortly to render a list of tasks. Add the code below to the `App.js` file: + +::: code-group + +```js [imports/ui/App.js] +import { Template } from 'meteor/templating'; + +import './App.html'; + +Template.mainContainer.helpers({ + tasks: [ + { text: 'This is task 1' }, + { text: 'This is task 2' }, + { text: 'This is task 3' }, + ], +}); +``` +::: + +Adding a helper to the `mainContainer` template, you are able to define the array of tasks. When the app starts, the client-side entry-point will import the `App.js` file, which will also import the `App.html` template we created in the previous step. + +At this point you can visit meteor should still be running on port 3000 so you can visit the running app and see your list with three tasks displayed at [http://localhost:3000/](http://localhost:3000/) - but if meteor is not running, go to your terminal and move to the top directory of your project and type `meteor` then press return to launch the app. + +All right! Let’s find out what all these bits of code are doing! + +### 1.5: Rendering Data + +Meteor parses HTML files and identifies three top-level tags: ``, ``, and `