diff --git a/esbuild.js b/esbuild.js index 7e29ba9..35d17ba 100644 --- a/esbuild.js +++ b/esbuild.js @@ -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]; diff --git a/release.js b/release.js index c58e658..a239495 100644 --- a/release.js +++ b/release.js @@ -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 */ /* -------------------------------------------------------------------------- */