diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0557f8b..39952b9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -88,23 +88,23 @@ jobs:
run: |
if [[ "${{ matrix.platform }}" == "ubuntu-latest" ]]; then
if [[ "${{ matrix.arch }}" == "x64" ]]; then
- mkdir -p ./bin/linux/x64
- cp target/x86_64-unknown-linux-gnu/release/todoctor ./bin/linux/x64/todoctor
- chmod +x ./bin/linux/x64/todoctor
+ mkdir -p ./packages/linux-x64
+ cp target/x86_64-unknown-linux-gnu/release/todoctor ./packages/linux-x64/todoctor
+ chmod +x ./packages/linux-x64/todoctor
elif [[ "${{ matrix.arch }}" == "arm64" ]]; then
- mkdir -p ./bin/linux/arm64
- cp target/aarch64-unknown-linux-gnu/release/todoctor ./bin/linux/arm64/todoctor
- chmod +x ./bin/linux/arm64/todoctor
+ mkdir -p ./packages/linux-arm64
+ cp target/aarch64-unknown-linux-gnu/release/todoctor ./packages/linux-arm64/todoctor
+ chmod +x ./packages/linux-arm64/todoctor
fi
elif [[ "${{ matrix.platform }}" == "macos-latest" ]]; then
if [[ "${{ matrix.arch }}" == "x64" ]]; then
- mkdir -p ./bin/macos/x64
- cp target/x86_64-apple-darwin/release/todoctor ./bin/macos/x64/todoctor
- chmod +x ./bin/macos/x64/todoctor
+ mkdir -p ./packages/darwin-x64
+ cp target/x86_64-apple-darwin/release/todoctor ./packages/darwin-x64/todoctor
+ chmod +x ./packages/darwin-x64/todoctor
elif [[ "${{ matrix.arch }}" == "arm64" ]]; then
- mkdir -p ./bin/macos/arm64
- cp target/aarch64-apple-darwin/release/todoctor ./bin/macos/arm64/todoctor
- chmod +x ./bin/macos/arm64/todoctor
+ mkdir -p ./packages/darwin-arm64
+ cp target/aarch64-apple-darwin/release/todoctor ./packages/darwin-arm64/todoctor
+ chmod +x ./packages/darwin-arm64/todoctor
fi
fi
shell: bash
@@ -112,8 +112,8 @@ jobs:
- name: Move Binaries to Bin Folder (Windows)
if: runner.os == 'Windows'
run: |
- mkdir bin\windows\x64
- copy target\x86_64-pc-windows-msvc\release\todoctor.exe bin\windows\x64\todoctor.exe
+ mkdir packages\win32-x64
+ copy target\x86_64-pc-windows-msvc\release\todoctor.exe packages\win32-x64\todoctor.exe
shell: cmd
- name: Upload Binaries
@@ -142,6 +142,9 @@ jobs:
- name: Build Static Assets
run: pnpm run build:preview
+ - name: Build Package Structure
+ run: node ./scripts/create-packages.js
+
- name: Download Binaries for Linux x64
uses: actions/download-artifact@v4
with:
@@ -174,17 +177,17 @@ jobs:
- name: Set Execute Permissions on Binaries
run: |
- chmod +x ./bin/linux/x64/todoctor
- chmod +x ./bin/linux/arm64/todoctor
- chmod +x ./bin/macos/x64/todoctor
- chmod +x ./bin/macos/arm64/todoctor
+ chmod +x ./packages/linux-x64/todoctor
+ chmod +x ./packages/linux-arm64/todoctor
+ chmod +x ./packages/darwin-x64/todoctor
+ chmod +x ./packages/darwin-arm64/todoctor
- name: Verify Binary Permissions
run: |
- ls -l ./bin/linux/x64/todoctor
- ls -l ./bin/linux/arm64/todoctor
- ls -l ./bin/macos/x64/todoctor
- ls -l ./bin/macos/arm64/todoctor
+ ls -l ./packages/linux-x64/todoctor
+ ls -l ./packages/linux-arm64/todoctor
+ ls -l ./packages/darwin-x64/todoctor
+ ls -l ./packages/darwin-arm64/todoctor
- name: Create GitHub Release
run: pnpm run ci:changelog
@@ -199,3 +202,16 @@ jobs:
- name: Publish to NPM
run: npm publish --access public --no-git-checks --provenance
+
+ - name: Publish Packages to NPM
+ run: |
+ for pkg in packages/*; do
+ if [ -d "$pkg" ]; then
+ echo "Publishing package $pkg"
+ cd "$pkg"
+ npm publish --access public --no-git-checks --provenance
+ cd -
+ fi
+ done
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 1f7d82a..762bb23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ node_modules/
target/
# Build
-!bin/todoctor.js
+packages/
dist/
bin/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..1fdad1f
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,3 @@
+link-workspace-packages = true
+prefer-workspace-packages = true
+recursive-install = true
diff --git a/bin/todoctor.js b/bin/todoctor.js
index 9518d27..ac52210 100755
--- a/bin/todoctor.js
+++ b/bin/todoctor.js
@@ -1,42 +1,37 @@
#!/usr/bin/env node
+import { createRequire } from 'node:module'
import { spawn } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import os from 'node:os'
-import fs from 'node:fs'
+let require = createRequire(import.meta.url)
let filename = fileURLToPath(import.meta.url)
let dirname = path.dirname(filename)
let platform = os.platform()
let arch = os.arch()
-export let binaries = {
- 'win32:x64': 'windows/x64/todoctor.exe',
- 'darwin:arm64': 'macos/arm64/todoctor',
- 'linux:arm64': 'linux/arm64/todoctor',
- 'darwin:x64': 'macos/x64/todoctor',
- 'linux:x64': 'linux/x64/todoctor',
-}
-
-let key = `${platform}:${arch}`
-let relativePath = binaries[key]
-
-if (!relativePath) {
- console.error(`Unsupported platform or architecture: ${platform}, ${arch}`)
- process.exit(1)
-}
-
-let binaryPath = path.join(dirname, relativePath)
-
-if (!fs.existsSync(binaryPath)) {
- console.error(`Binary not found: ${binaryPath}`)
+let packageName = `@todoctor/${platform}-${arch}`
+let binaryPath
+
+try {
+ let packageBinaryFile = 'todoctor'
+ if (platform === 'win32') {
+ packageBinaryFile += '.exe'
+ }
+ binaryPath = require.resolve(`${packageName}/${packageBinaryFile}`)
+} catch (error) {
+ console.error(`Failed to find binary for ${packageName}`)
+ console.error(error)
process.exit(1)
}
+let distPath = path.join(dirname, '../dist')
let args = process.argv.slice(2)
-let child = spawn(binaryPath, args, { stdio: 'inherit' })
+let env = { ...process.env, DIST_PATH: distPath }
+let child = spawn(binaryPath, args, { stdio: 'inherit', env })
child.on('exit', code => {
process.exit(code)
diff --git a/package.json b/package.json
index 99282b5..2f725de 100644
--- a/package.json
+++ b/package.json
@@ -23,12 +23,19 @@
"start": "pnpm run /^start:/",
"start:preview": "vite",
"build": "pnpm run /^build:/",
- "build:lib": "cargo build --release && node ./scripts/build.js",
+ "build:lib": "cargo build --release && node \"./scripts/create-packages.js\" && node \"./scripts/build.js\"",
"build:preview": "vite build",
"docs:build": "DOCS=true vite build",
- "release": "pnpm release:check && pnpm release:version",
+ "release": "pnpm release:check && pnpm release:version && pnpm release:git",
"release:check": "pnpm test && pnpm run build",
- "release:version": "changelogen --output changelog.md --release --push",
+ "release:git": "pnpm release:git:add && pnpm release:git:commit && pnpm release:git:tag && pnpm release:git:push",
+ "release:git:add": "git add .",
+ "release:git:commit": "git commit -m \"build: publish v$(node -p \"require('./package.json').version\")\"",
+ "release:git:tag": "git tag v$(node -p \"require('./package.json').version\")",
+ "release:git:push": "git push --follow-tags",
+ "release:version": "pnpm release:version:root && pnpm release:version:packages",
+ "release:version:root": "changelogen --output changelog.md --release --no-commit --no-tag",
+ "release:version:packages": "node ./scripts/version.js && pnpm install",
"ci:changelog": "changelogithub",
"ci:clear": "clear-package-json package.json --output package.json",
"test": "pnpm run /^test:/",
@@ -59,6 +66,13 @@
"./build",
"./dist"
],
+ "optionalDependencies": {
+ "@todoctor/darwin-arm64": "^1.2.0",
+ "@todoctor/darwin-x64": "^1.2.0",
+ "@todoctor/linux-arm64": "^1.2.0",
+ "@todoctor/linux-x64": "^1.2.0",
+ "@todoctor/win32-x64": "^1.2.0"
+ },
"devDependencies": {
"@azat-io/eslint-config-typescript": "^1.10.0",
"@azat-io/stylelint-config": "^0.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 00115c2..6d14506 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,6 +7,22 @@ settings:
importers:
.:
+ optionalDependencies:
+ '@todoctor/darwin-arm64':
+ specifier: ^1.2.0
+ version: link:packages/darwin-arm64
+ '@todoctor/darwin-x64':
+ specifier: ^1.2.0
+ version: link:packages/darwin-x64
+ '@todoctor/linux-arm64':
+ specifier: ^1.2.0
+ version: link:packages/linux-arm64
+ '@todoctor/linux-x64':
+ specifier: ^1.2.0
+ version: link:packages/linux-x64
+ '@todoctor/win32-x64':
+ specifier: ^1.2.0
+ version: link:packages/win32-x64
devDependencies:
'@azat-io/eslint-config-typescript':
specifier: ^1.10.0
@@ -168,6 +184,16 @@ importers:
specifier: ^2.0.2
version: 2.0.2(rollup@4.24.0)(vite@5.4.10(@types/node@22.7.9)(lightningcss@1.27.0)(terser@5.36.0))
+ packages/darwin-arm64: {}
+
+ packages/darwin-x64: {}
+
+ packages/linux-arm64: {}
+
+ packages/linux-x64: {}
+
+ packages/win32-x64: {}
+
packages:
'@ampproject/remapping@2.3.0':
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..f10aa33
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - packages/**
diff --git a/scripts/build.js b/scripts/build.js
index 574df2a..a340294 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -1,37 +1,33 @@
-import { fileURLToPath } from 'node:url'
import path from 'node:path'
import fs from 'node:fs'
import os from 'node:os'
+import { platforms } from './platforms.js'
+
let platform = os.platform()
let arch = os.arch()
-let platformMap = {
- win32: 'windows',
- darwin: 'macos',
- linux: 'linux',
-}
-
-let archMap = {
- arm64: 'arm64',
- x64: 'x64',
-}
+let userPlatform = platforms.find(p => p.name === platform)
-let filename = fileURLToPath(import.meta.url)
-let dirname = path.dirname(filename)
-
-let platformName = platformMap[platform]
-let archName = archMap[arch]
-
-if (!platformName || !archName) {
+if (!userPlatform?.arch.includes(arch)) {
console.error(`Unsupported platform or architecture: ${platform}, ${arch}`)
process.exit(1)
}
-let binaryName = platform === 'win32' ? 'todoctor.exe' : 'todoctor'
-
-let sourcePath = path.join(dirname, '..', 'target', 'release', binaryName)
-let destDir = path.join(dirname, '../bin/', platformName, archName)
+let binaryName = ['todoctor', userPlatform.extension].filter(Boolean).join('.')
+
+let sourcePath = path.join(
+ import.meta.dirname,
+ '..',
+ 'target',
+ 'release',
+ binaryName,
+)
+let destDir = path.join(
+ import.meta.dirname,
+ '../packages/',
+ `${platform}-${arch}`,
+)
let destPath = path.join(destDir, binaryName)
if (!fs.existsSync(destDir)) {
diff --git a/scripts/create-packages.js b/scripts/create-packages.js
new file mode 100644
index 0000000..b678585
--- /dev/null
+++ b/scripts/create-packages.js
@@ -0,0 +1,73 @@
+/* eslint-disable perfectionist/sort-objects */
+
+import fs from 'node:fs/promises'
+import path from 'node:path'
+
+import { getPackageJson } from './get-package-json.js'
+import { platforms } from './platforms.js'
+
+let packagesDir = path.join(import.meta.dirname, '../packages')
+let rootPackageJson = await getPackageJson()
+
+for (let platform of platforms) {
+ for (let arch of platform.arch) {
+ let packageDir = path.join(packagesDir, `${platform.name}-${arch}`)
+
+ await fs.mkdir(packageDir, {
+ recursive: true,
+ })
+
+ let name = `@todoctor/${platform.name}-${arch}`
+
+ fs.writeFile(
+ path.join(packageDir, 'package.json'),
+ JSON.stringify(
+ {
+ name,
+ description: `Platform-specific binary for Todoctor (${platform.name}, ${arch})`,
+ version: rootPackageJson.version,
+ repository: rootPackageJson.repository,
+ author: rootPackageJson.author,
+ license: rootPackageJson.license,
+ keywords: rootPackageJson.keywords,
+ os: [platform.name],
+ cpu: [arch],
+ },
+ null,
+ 2,
+ ),
+ )
+
+ fs.writeFile(
+ path.join(packageDir, 'readme.md'),
+ [
+ `# Todoctor (${platform.name[0].toUpperCase() + platform.name.slice(1)}, ${arch.toUpperCase()})`,
+ '',
+ '',
+ '',
+ `[![Version](https://img.shields.io/npm/v/${name}.svg?color=2c7f50&labelColor=353c3c)](https://npmjs.com/package/${name})`,
+ '[![GitHub License](https://img.shields.io/badge/license-MIT-232428.svg?color=2c7f50&labelColor=353c3c)](https://github.com/azat-io/todoctor/blob/main/license)',
+ '',
+ 'This is a platform-specific binary package for Todoctor. It contains the pre-compiled binary executable for your operating system and architecture.',
+ '',
+ '**Note:** This package is not meant to be installed directly by users. Instead, please install the main Todoctor package, which will automatically download the correct binary for your platform.',
+ '',
+ '## Usage',
+ '',
+ '```sh',
+ 'npx todoctor',
+ '```',
+ '',
+ '## License',
+ '',
+ 'MIT © [Azat S.](https://azat.io)',
+ ].join('\n'),
+ )
+ }
+}
diff --git a/scripts/get-package-json.js b/scripts/get-package-json.js
new file mode 100644
index 0000000..e4273e7
--- /dev/null
+++ b/scripts/get-package-json.js
@@ -0,0 +1,7 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+
+export let getPackageJson = async () => {
+ let rootPackageJsonPath = path.join(import.meta.dirname, '../package.json')
+ return JSON.parse(await fs.readFile(rootPackageJsonPath, 'utf-8'))
+}
diff --git a/scripts/platforms.js b/scripts/platforms.js
new file mode 100644
index 0000000..1ccb0fd
--- /dev/null
+++ b/scripts/platforms.js
@@ -0,0 +1,17 @@
+export let platforms = [
+ {
+ arch: ['arm64', 'x64'],
+ extension: null,
+ name: 'darwin',
+ },
+ {
+ arch: ['arm64', 'x64'],
+ extension: null,
+ name: 'linux',
+ },
+ {
+ extension: 'exe',
+ arch: ['x64'],
+ name: 'win32',
+ },
+]
diff --git a/scripts/version.js b/scripts/version.js
new file mode 100644
index 0000000..f8f1c29
--- /dev/null
+++ b/scripts/version.js
@@ -0,0 +1,20 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+
+import { getPackageJson } from './get-package-json.js'
+
+let rootPackageJson = await getPackageJson()
+
+let { version } = rootPackageJson
+
+rootPackageJson.optionalDependencies = Object.fromEntries(
+ Object.keys(rootPackageJson.optionalDependencies).map(key => [
+ key,
+ `^${version}`,
+ ]),
+)
+
+await fs.writeFile(
+ path.join(import.meta.dirname, '../package.json'),
+ JSON.stringify(rootPackageJson, null, 2),
+)
diff --git a/src/fs/get_dist_path.rs b/src/fs/get_dist_path.rs
index ead79a4..5848839 100644
--- a/src/fs/get_dist_path.rs
+++ b/src/fs/get_dist_path.rs
@@ -1,29 +1,8 @@
-use std::{env, fs, path::PathBuf};
+use std::{env, path::PathBuf};
pub fn get_dist_path() -> Option {
- let exe_path = match env::current_exe() {
- Ok(path) => path,
- Err(e) => {
- eprintln!("Error getting current exe path: {:?}", e);
- return None;
- }
- };
- let real_exe_path = fs::canonicalize(&exe_path).unwrap_or(exe_path);
-
- fn ascend_path(mut path: PathBuf, levels: usize) -> Option {
- for _ in 0..levels {
- path = path.parent()?.to_path_buf();
- }
- Some(path)
- }
-
- let levels_up = 4;
-
- let project_root = ascend_path(real_exe_path, levels_up)?;
- let dist_path = project_root.join("dist");
- if dist_path.exists() {
- return Some(dist_path);
- }
-
- None
+ env::var("DIST_PATH")
+ .ok()
+ .map(PathBuf::from)
+ .filter(|p| p.exists())
}