refactor: simplify release process by removing deprecated functions and streamlining core release logic

This commit is contained in:
Brian Joseph Petro 2026-01-24 11:05:13 -05:00
parent 7cf288b191
commit 72a8d35aca
2 changed files with 5 additions and 220 deletions

View file

@ -22,51 +22,9 @@ const dist_dir = path.resolve(process.cwd());
build_smart_env_config(dist_dir, roots);
/**
* @deprecated Use esbuild's "loader" to load as text
* Import CSS as text:
* import sheet from './style.css';
*/
export function css_with_plugin() {
return {
name: 'css-with-plugin',
setup(build) {
// Intercept all .css files
build.onLoad({ filter: /\.css$/ }, async (args) => {
// Check for the "with" import attribute and that its type is 'css'
if (args.with && args.with.type === 'css') {
// Read the CSS file contents
const fs = await import('fs/promises');
let css_content = await fs.readFile(args.path, 'utf8');
// Optionally transform (minify) the CSS if minification is enabled
const should_minify = build.initialOptions.minify || false;
if (should_minify) {
const result = await esbuild.transform(css_content, {
loader: 'css',
minify: true,
});
css_content = result.code;
}
// Escape any backticks in the CSS content to avoid breaking the template literal
const escaped_css = css_content.replace(/`/g, '\\`');
// Create a JavaScript module that creates a CSSStyleSheet and exports it
const js_module = `
const css_sheet = new CSSStyleSheet();
css_sheet.replaceSync(\`${escaped_css}\`);
export default css_sheet;
`;
return {
contents: js_module,
loader: 'js',
};
}
// If the "with" attribute is not present or not type "css",
// return undefined so that other loaders/plugins can process it.
});
},
};
}
// markdown import plugin (matches smart-connections-obsidian)
const markdown_plugin = {
@ -133,7 +91,7 @@ esbuild.build({
loader: {
'.css': 'text',
},
plugins: [css_with_plugin(), markdown_plugin],
plugins: [markdown_plugin],
}).then(() => {
console.log('Build complete');
const release_file_paths = [manifest_path, styles_path, main_path];

View file

@ -1,194 +1,21 @@
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import readline from 'readline';
import archiver from 'archiver';
import axios from 'axios';
import { exec } from 'child_process';
import { fileURLToPath } from 'url';
import { remove_existing_release_and_tag } from '../obsidian-smart-env/build/github_release_utils.js';
import { run_core_release } from '../obsidian-smart-env/build/release_runner.js';
import {
build_combined_notes,
latest_release_file,
semver_compare,
parse_cli_options,
write_plugin_release_notes
} from '../obsidian-smart-env/build/release_notes.js';
/* -------------------------------------------------------------------------- */
/* Runtime */
/* -------------------------------------------------------------------------- */
const is_main = path.resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url);
if (is_main) {
run_release().catch((err) => {
run_core_release().catch((err) => {
console.error('Error in release process:', err);
process.exit(1);
});
}
async function run_release() {
const cli_options = parse_cli_options(process.argv.slice(2));
// Read package & manifest
const package_json = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const manifest_json = JSON.parse(fs.readFileSync('./manifest.json', 'utf8'));
const version = package_json.version;
const manifest_id = manifest_json.id;
if (version !== manifest_json.version) {
console.error('Version mismatch between package.json and manifest.json');
process.exit(1);
}
// Readline only if we need user input
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = (q) => new Promise((res) => rl.question(q, res));
const confirmed_version =
(await ask(`Confirm release version (${version}): `))?.trim() || version;
console.log(`Creating release for ${confirmed_version}`);
const releases_dir = './releases';
const release_file = path.join(releases_dir, `${confirmed_version}.md`);
let version_notes = '';
let user_desc = '';
if (fs.existsSync(release_file)) {
version_notes = fs.readFileSync(release_file, 'utf8').trim();
} else {
const prior_file = latest_release_file(releases_dir, confirmed_version);
let prior_notes = prior_file ? fs.readFileSync(prior_file, 'utf8').trim() : '';
if (prior_notes.includes('## next patch')) {
prior_notes = prior_notes.replace('## next patch', `## patch \`v${confirmed_version}\`\n`);
version_notes = prior_notes;
} else {
user_desc = await ask('Enter additional release description (optional): ');
version_notes = build_combined_notes(confirmed_version, prior_notes, user_desc);
}
// Persist the updated canonical notes file (mirrors smart-connections behavior).
if (prior_file) {
fs.writeFileSync(prior_file, version_notes);
} else {
fs.mkdirSync(releases_dir, { recursive: true });
fs.writeFileSync(release_file, version_notes);
}
}
// Generate releases/latest_release.md (for in-plugin ReleaseNotesView)
const target_file = fs.existsSync(release_file)
? release_file
: latest_release_file(releases_dir, confirmed_version);
if (target_file) {
write_plugin_release_notes({
release_path: target_file,
output_path: './releases/latest_release.md',
version: confirmed_version,
});
} else {
console.warn('No release notes file found to format into latest_release.md');
}
rl.close();
await new Promise((res) => setTimeout(res, 500)); // allow write to flush
// re-run npm run build and wait for it to finish
await new Promise((resolve, reject) => {
exec('npm run build', (err, stdout, stderr) => {
if (err) reject(err);
if (stdout) console.log(stdout);
console.log('build finished');
resolve();
});
});
// GitHub release body
const release_body = version_notes;
// GH env
const github_token = process.env.GH_TOKEN;
const github_repo = process.env.GH_REPO;
if (!github_token || !github_repo) {
console.error('GH_TOKEN or GH_REPO missing from .env');
process.exit(1);
}
if (cli_options.replace_existing) {
console.log(
`--replace-existing set; removing any existing release and tag for ${confirmed_version}`,
);
await remove_existing_release_and_tag({
github_repo,
github_token,
tag_name: confirmed_version,
});
}
// Create release via GH API
const release_data = {
tag_name: confirmed_version,
name: confirmed_version,
body: release_body,
draft: cli_options.draft,
prerelease: false,
};
const release_resp = await axios.post(
`https://api.github.com/repos/${github_repo}/releases`,
release_data,
{ headers: { Authorization: `Bearer ${github_token}`, 'Content-Type': 'application/json' } },
);
const { upload_url, html_url } = release_resp.data;
console.log('Release created:', html_url);
// Asset upload helper (curl avoids axios chunk issues)
const upload_asset_curl = (asset_path, asset_name) =>
new Promise((resolve, reject) => {
const url = `${upload_url.split('{')[0]}?name=${encodeURIComponent(asset_name)}`;
const cmd = [
'curl',
'-X', 'POST',
'-H', `"Authorization: Bearer ${github_token}"`,
'-H', '"Content-Type: application/octet-stream"',
'--data-binary', `@${asset_path}`,
`"${url}"`,
].join(' ');
exec(cmd, (err, stdout, stderr) => {
if (err) return reject(err);
if (stdout) console.log(stdout);
if (stderr) console.log(stderr);
console.log(`Uploaded ${asset_name}`);
resolve();
});
});
// Zip dist & upload assets
const zip_name = `${manifest_id}-${confirmed_version}.zip`;
const zip_stream = fs.createWriteStream(`./${zip_name}`);
const archive = archiver('zip', { zlib: { level: 0 } });
await new Promise((res, rej) => {
archive.pipe(zip_stream);
archive.directory('dist/', false);
archive.on('error', rej);
zip_stream.on('close', res);
archive.finalize();
});
console.log(`Archive wrote ${archive.pointer()} bytes`);
await upload_asset_curl(`./${zip_name}`, zip_name);
// Upload each dist file
for (const file of fs.readdirSync('./dist')) {
await upload_asset_curl(`./dist/${file}`, file);
}
// Clean up zip after a short delay
setTimeout(() => fs.unlinkSync(`./${zip_name}`), 3000);
}
/* -------------------------------------------------------------------------- */
/* Exports for unit tests */
/* -------------------------------------------------------------------------- */