mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ae0c9749b | ||
|
|
10e40e038e | ||
|
|
4ba6828939 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ npm-debug.log
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
tools/release/config.json
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ WORKDIR /fredy
|
|||||||
RUN apk add --no-cache chromium curl
|
RUN apk add --no-cache chromium curl
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
|
IS_DOCKER=true \
|
||||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import { getSettings } from '../storage/settingsStorage.js';
|
|||||||
const deviceId = getUniqueId() || 'N/A';
|
const deviceId = getUniqueId() || 'N/A';
|
||||||
const version = await getPackageVersion();
|
const version = await getPackageVersion();
|
||||||
const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking';
|
const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking';
|
||||||
|
const isDocker = process.env.IS_DOCKER != null;
|
||||||
|
|
||||||
const staticTrackingData = {
|
const staticTrackingData = {
|
||||||
operatingSystem: os.platform(),
|
operatingSystem: os.platform(),
|
||||||
osVersion: os.release(),
|
osVersion: os.release(),
|
||||||
|
isDocker,
|
||||||
arch: process.arch,
|
arch: process.arch,
|
||||||
language: process.env.LANG || 'en',
|
language: process.env.LANG || 'en',
|
||||||
nodeVersion: process.version || 'N/A',
|
nodeVersion: process.version || 'N/A',
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fredy",
|
"name": "fredy",
|
||||||
"version": "19.3.6",
|
"version": "19.3.8",
|
||||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
"lint:fix": "yarn lint --fix",
|
"lint:fix": "yarn lint --fix",
|
||||||
"migratedb": "node lib/services/storage/migrations/migrate.js",
|
"migratedb": "node lib/services/storage/migrations/migrate.js",
|
||||||
"migratedb:overwrite": "x-var MIGRATION_ALLOW_CHECKSUM_UPDATE=true node lib/services/storage/migrations/migrate.js",
|
"migratedb:overwrite": "x-var MIGRATION_ALLOW_CHECKSUM_UPDATE=true node lib/services/storage/migrations/migrate.js",
|
||||||
"copyright": "node ./copyright.js"
|
"copyright": "node ./copyright.js",
|
||||||
|
"release": "node ./tools/release/release.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
@@ -59,11 +60,11 @@
|
|||||||
"Firefox ESR"
|
"Firefox ESR"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@douyinfe/semi-icons": "^2.90.13",
|
"@douyinfe/semi-icons": "^2.91.0",
|
||||||
"@douyinfe/semi-ui": "2.90.13",
|
"@douyinfe/semi-ui": "2.91.0",
|
||||||
"@douyinfe/semi-ui-19": "^2.90.13",
|
"@douyinfe/semi-ui-19": "^2.91.0",
|
||||||
"@sendgrid/mail": "8.1.6",
|
"@sendgrid/mail": "8.1.6",
|
||||||
"@vitejs/plugin-react": "5.1.3",
|
"@vitejs/plugin-react": "5.1.4",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"body-parser": "2.2.2",
|
"body-parser": "2.2.2",
|
||||||
@@ -72,14 +73,14 @@
|
|||||||
"cookie-session": "2.1.1",
|
"cookie-session": "2.1.1",
|
||||||
"handlebars": "4.7.8",
|
"handlebars": "4.7.8",
|
||||||
"lodash": "4.17.23",
|
"lodash": "4.17.23",
|
||||||
"maplibre-gl": "^5.17.0",
|
"maplibre-gl": "^5.18.0",
|
||||||
"nanoid": "5.1.6",
|
"nanoid": "5.1.6",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-mailjet": "6.0.11",
|
"node-mailjet": "6.0.11",
|
||||||
"p-throttle": "^8.1.0",
|
"p-throttle": "^8.1.0",
|
||||||
"package-up": "^5.0.0",
|
"package-up": "^5.0.0",
|
||||||
"puppeteer": "^24.36.1",
|
"puppeteer": "^24.37.2",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"query-string": "9.3.1",
|
"query-string": "9.3.1",
|
||||||
@@ -90,7 +91,7 @@
|
|||||||
"react-router": "7.13.0",
|
"react-router": "7.13.0",
|
||||||
"react-router-dom": "7.13.0",
|
"react-router-dom": "7.13.0",
|
||||||
"restana": "5.1.0",
|
"restana": "5.1.0",
|
||||||
"semver": "^7.7.3",
|
"semver": "^7.7.4",
|
||||||
"serve-static": "2.2.1",
|
"serve-static": "2.2.1",
|
||||||
"slack": "11.0.2",
|
"slack": "11.0.2",
|
||||||
"vite": "7.3.1",
|
"vite": "7.3.1",
|
||||||
@@ -103,7 +104,8 @@
|
|||||||
"@babel/preset-env": "7.29.0",
|
"@babel/preset-env": "7.29.0",
|
||||||
"@babel/preset-react": "7.28.5",
|
"@babel/preset-react": "7.28.5",
|
||||||
"chai": "6.2.2",
|
"chai": "6.2.2",
|
||||||
"eslint": "9.39.2",
|
"chalk": "^5.6.2",
|
||||||
|
"eslint": "10.0.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-react": "7.37.5",
|
"eslint-plugin-react": "7.37.5",
|
||||||
"esmock": "2.7.3",
|
"esmock": "2.7.3",
|
||||||
|
|||||||
196
tools/release/release.js
Normal file
196
tools/release/release.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 by Christian Kellner.
|
||||||
|
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { execSync, spawn } from 'child_process';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release Tool for Fredy
|
||||||
|
*
|
||||||
|
* This tool automates the process of creating a GitHub release.
|
||||||
|
* It fetches the latest release, compares it with the current master branch,
|
||||||
|
* allows manual editing of commit messages, and creates a new release on GitHub.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Define __dirname for ESM
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Configuration and Paths
|
||||||
|
const CONFIG_PATH = path.join(__dirname, 'config.json');
|
||||||
|
const PACKAGE_JSON_PATH = path.join(__dirname, '../../package.json');
|
||||||
|
const REPO = 'orangecoding/fredy';
|
||||||
|
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
||||||
|
const GITHUB_TOKEN = config.github_token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to execute the release process
|
||||||
|
*/
|
||||||
|
async function createRelease() {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
try {
|
||||||
|
console.log(chalk.cyan('🚀 Starting release process...'));
|
||||||
|
|
||||||
|
// 1. Load Configuration
|
||||||
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
|
console.error(chalk.red('❌ Error: config.json not found in tools/release/'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GITHUB_TOKEN) {
|
||||||
|
console.error(chalk.red('❌ Error: GitHub token not configured.'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get current version from package.json
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
|
||||||
|
const version = packageJson.version;
|
||||||
|
const tag = version; // Using version as tag
|
||||||
|
|
||||||
|
console.log(chalk.blue(`📦 Target version: ${version}`));
|
||||||
|
|
||||||
|
// 3. Check if release already exists
|
||||||
|
console.log(chalk.yellow('🔍 Checking if release already exists...'));
|
||||||
|
const existingReleaseResponse = await fetch(`https://api.github.com/repos/${REPO}/releases/tags/${tag}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_TOKEN}`,
|
||||||
|
Accept: 'application/vnd.github.v3+json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingReleaseResponse.status === 200) {
|
||||||
|
console.error(chalk.red(`❌ Error: A release with tag ${tag} already exists.`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fetch latest release to find the starting point for the diff
|
||||||
|
console.log(chalk.yellow('📡 Fetching latest release from GitHub...'));
|
||||||
|
const latestReleaseResponse = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_TOKEN}`,
|
||||||
|
Accept: 'application/vnd.github.v3+json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!latestReleaseResponse.ok) {
|
||||||
|
console.error(chalk.red('❌ Error fetching latest release.'));
|
||||||
|
const errorData = await latestReleaseResponse.json();
|
||||||
|
console.error(chalk.red(JSON.stringify(errorData)));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestRelease = await latestReleaseResponse.json();
|
||||||
|
const latestTag = latestRelease.tag_name;
|
||||||
|
console.log(chalk.green(`✅ Latest release found: ${latestTag}`));
|
||||||
|
|
||||||
|
// 5. Ensure the latest tag is available locally
|
||||||
|
console.log(chalk.yellow(`📡 Fetching tag ${latestTag} from remote...`));
|
||||||
|
try {
|
||||||
|
execSync(`git fetch origin tag ${latestTag} --no-tags`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red(`❌ Error fetching tag ${latestTag} from origin.`));
|
||||||
|
console.error(error.message);
|
||||||
|
// We don't exit here, maybe it's already there but fetch failed for some reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Get commit messages between latest tag and current HEAD
|
||||||
|
console.log(chalk.yellow(`Git diff: ${latestTag} .. HEAD`));
|
||||||
|
let commitMessages;
|
||||||
|
try {
|
||||||
|
commitMessages = execSync(`git log ${latestTag}..HEAD --pretty=format:"- %s"`).toString().trim();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('❌ Error running git log. Make sure the latest tag is available locally.'), error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commitMessages) {
|
||||||
|
console.log(chalk.magenta('⚠️ No new commits found since last release.'));
|
||||||
|
commitMessages = '- No changes recorded';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Open commit messages in editor for manual adjustment
|
||||||
|
const tempFilePath = path.join(__dirname, 'CHANGELOG_EDIT.tmp');
|
||||||
|
const initialContent = `# Release Notes for ${version}\n# Edit the messages below. Lines starting with # will be ignored.\n\n${commitMessages}`;
|
||||||
|
fs.writeFileSync(tempFilePath, initialContent);
|
||||||
|
|
||||||
|
console.log(chalk.blue('📝 Opening editor for release notes (using nano or $EDITOR)...'));
|
||||||
|
await openInEditor(tempFilePath);
|
||||||
|
|
||||||
|
// 8. Read edited content
|
||||||
|
let editedContent = fs
|
||||||
|
.readFileSync(tempFilePath, 'utf8')
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => !line.startsWith('#'))
|
||||||
|
.join('\n')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
fs.unlinkSync(tempFilePath); // Clean up temp file
|
||||||
|
|
||||||
|
if (!editedContent) {
|
||||||
|
console.error(chalk.red('❌ Release notes are empty. Aborting release.'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. Create the new release
|
||||||
|
console.log(chalk.cyan(`🚀 Creating release ${version} on GitHub...`));
|
||||||
|
const createResponse = await fetch(`https://api.github.com/repos/${REPO}/releases`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_TOKEN}`,
|
||||||
|
Accept: 'application/vnd.github.v3+json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
tag_name: tag,
|
||||||
|
name: version,
|
||||||
|
body: editedContent,
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createResponse.status === 201) {
|
||||||
|
const data = await createResponse.json();
|
||||||
|
console.log(chalk.green('🎉 Release successfully created!'));
|
||||||
|
console.log(chalk.green(`🔗 URL: ${data.html_url}`));
|
||||||
|
} else {
|
||||||
|
const errorData = await createResponse.json();
|
||||||
|
console.error(chalk.red('❌ Failed to create release.'));
|
||||||
|
console.error(chalk.red(JSON.stringify(errorData, null, 2)));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('💥 An unexpected error occurred:'));
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to open a file in a terminal editor
|
||||||
|
* @param {string} filePath
|
||||||
|
*/
|
||||||
|
function openInEditor(filePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const editor = process.env.EDITOR || 'nano';
|
||||||
|
const child = spawn(editor, [filePath], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Editor exited with code ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createRelease();
|
||||||
|
/* eslint-enable no-console */
|
||||||
Reference in New Issue
Block a user