@gengjiawen/os-init 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ pnpm lint-staged
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.1](https://github.com/gengjiawen/os-init/compare/v1.7.0...v1.7.1) (2025-11-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * android CI ([#12](https://github.com/gengjiawen/os-init/issues/12)) ([535b011](https://github.com/gengjiawen/os-init/commit/535b0115b513902ceb6fd9ef98bcb8338ac9972e))
9
+
10
+ ## [1.7.0](https://github.com/gengjiawen/os-init/compare/v1.6.0...v1.7.0) (2025-11-09)
11
+
12
+
13
+ ### Features
14
+
15
+ * add setup android sdk ([cb18d40](https://github.com/gengjiawen/os-init/commit/cb18d40e53e312b0aedc66a9cde6738bc2f4f1ba))
16
+
3
17
  ## [1.6.0](https://github.com/gengjiawen/os-init/compare/v1.5.1...v1.6.0) (2025-10-25)
4
18
 
5
19
 
package/README.md CHANGED
@@ -54,6 +54,29 @@ Example:
54
54
  pnpx @gengjiawen/os-init set-dev "ssh-rsa AAAAB3NzaC1yc2..."
55
55
  ```
56
56
 
57
+ ### Setup Android Development Environment
58
+
59
+ ```bash
60
+ pnpx @gengjiawen/os-init set-android
61
+ ```
62
+
63
+ Sets up a complete Android development environment on macOS and Linux. This command will:
64
+ - Install Android SDK to `~/Android` (or custom path with `--android-home <path>`)
65
+ - Download and install Android SDK Command-line Tools, Platform Tools, Build Tools, CMake, and NDK
66
+ - Automatically accept Android SDK licenses
67
+ - Configure environment variables in your shell config (~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish)
68
+ - Works on both macOS (Intel and Apple Silicon) and Linux
69
+ - No sudo access required
70
+
71
+ Example:
72
+ ```bash
73
+ # Custom installation path
74
+ pnpx @gengjiawen/os-init set-android --android-home ~/my-android-sdk
75
+
76
+ # Skip environment variable configuration
77
+ pnpx @gengjiawen/os-init set-android --skip-env-vars
78
+ ```
79
+
57
80
  ---
58
81
 
59
82
  Project generated by [gengjiawen/ts-scaffold](https://github.com/gengjiawen/ts-scaffold)
@@ -0,0 +1,128 @@
1
+ # Android Development Environment Setup
2
+
3
+ This command sets up a complete Android development environment on macOS and Linux systems.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ os-init set-android [options]
9
+ ```
10
+
11
+ ## What It Does
12
+
13
+ The `set-android` command:
14
+
15
+ 1. **Creates Android SDK directory** at `~/Android` (or custom path)
16
+ 2. **Downloads and installs Android SDK** command-line tools (automatically detects macOS or Linux)
17
+ 3. **Accepts Android SDK licenses** automatically
18
+ 4. **Installs essential components**:
19
+ - Platform tools (adb, fastboot, etc.)
20
+ - Android 36 platform
21
+ - Build tools 36.0.0
22
+ - CMake 3.30.5
23
+ - NDK 29.0.14206865
24
+ 5. **Configures environment variables** in your shell configuration file (~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish)
25
+
26
+ ## Options
27
+
28
+ - `--android-home <path>` - Custom Android SDK installation path (default: `~/Android`)
29
+ - `--skip-env-vars` - Skip adding environment variables to shell config
30
+
31
+ ## Examples
32
+
33
+ ### Basic Installation
34
+
35
+ ```bash
36
+ os-init set-android
37
+ ```
38
+
39
+ This installs the Android SDK to `~/Android` and adds environment variables to your shell config.
40
+
41
+ ### Custom Installation Path
42
+
43
+ ```bash
44
+ os-init set-android --android-home /home/user/android-sdk
45
+ ```
46
+
47
+ ### Skip Environment Variables
48
+
49
+ If you prefer to manage environment variables manually:
50
+
51
+ ```bash
52
+ os-init set-android --skip-env-vars
53
+ ```
54
+
55
+ ## Requirements
56
+
57
+ - **macOS or Linux operating system**
58
+ - Internet connection to download Android SDK
59
+ - Node.js 22.12.0 or higher
60
+
61
+ The script uses `@compilets/unzip-url` package to download and extract the Android SDK, so no external `curl` or `unzip` tools are required. No sudo access is needed as the SDK is installed in your home directory.
62
+
63
+ ## Environment Variables
64
+
65
+ After installation, the following environment variables will be set:
66
+
67
+ ```bash
68
+ export ANDROID_HOME=~/Android
69
+ export ANDROID_SDK_ROOT=${ANDROID_HOME}
70
+ export ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/29.0.14206865
71
+ export PATH=${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/emulator:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${PATH}
72
+ ```
73
+
74
+ ## Activating the Environment
75
+
76
+ After installation, activate the environment in your current shell:
77
+
78
+ ```bash
79
+ source ~/.bashrc # or ~/.zshrc, or ~/.config/fish/config.fish
80
+ ```
81
+
82
+ Or simply open a new terminal session.
83
+
84
+ ## Verifying Installation
85
+
86
+ Check that the Android SDK is properly installed:
87
+
88
+ ```bash
89
+ adb --version
90
+ sdkmanager --list
91
+ ```
92
+
93
+ ## Platform-Specific Notes
94
+
95
+ ### macOS
96
+ - The SDK is downloaded from Google's macOS-specific distribution
97
+ - Works on both Intel and Apple Silicon Macs
98
+ - No sudo permissions required
99
+
100
+ ### Linux
101
+ - The SDK is downloaded from Google's Linux-specific distribution
102
+ - Tested on Ubuntu and Debian-based systems
103
+ - No sudo permissions required when installing to home directory
104
+
105
+ ## Troubleshooting
106
+
107
+ ### Command Not Found After Installation
108
+
109
+ Make sure you've sourced your shell configuration file or opened a new terminal:
110
+
111
+ ```bash
112
+ source ~/.bashrc # or your shell's config file
113
+ ```
114
+
115
+ ### Download Failures
116
+
117
+ If the Android SDK download fails, check your internet connection and try again. The script will show detailed error messages.
118
+
119
+ ## What Gets Installed
120
+
121
+ - **Android SDK Command-line Tools** (version 11076708)
122
+ - **Platform Tools** - adb, fastboot, etc.
123
+ - **Android Platform 36** - Latest Android API level
124
+ - **Build Tools 36.0.0** - Tools for building Android apps
125
+ - **CMake 3.30.5** - For native code compilation
126
+ - **NDK 29.0.14206865** - Native Development Kit for C/C++ code
127
+
128
+
package/bin/bin.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  installCodexDeps,
9
9
  writeRaycastConfig,
10
10
  setupDevEnvironment,
11
+ setupAndroidEnvironment,
11
12
  } = require('../build')
12
13
 
13
14
  const program = new Command()
@@ -102,4 +103,29 @@ program
102
103
  }
103
104
  })
104
105
 
106
+ program
107
+ .command('set-android')
108
+ .description('setup Android development environment (macOS and Linux)')
109
+ .option('--android-home <path>', 'Custom Android SDK installation path')
110
+ .option(
111
+ '--skip-env-vars',
112
+ 'Skip adding environment variables to shell config'
113
+ )
114
+ .action(async (options) => {
115
+ try {
116
+ const { androidHome, envVarsAdded, shellRcFile } =
117
+ await setupAndroidEnvironment({
118
+ androidHome: options.androidHome,
119
+ skipEnvVars: options.skipEnvVars,
120
+ })
121
+ console.log(`\nāœ… Android SDK installed at: ${androidHome}`)
122
+ if (envVarsAdded && shellRcFile) {
123
+ console.log(`āœ… Environment variables added to: ${shellRcFile}`)
124
+ }
125
+ } catch (err) {
126
+ console.error('Failed to setup Android environment:', err.message)
127
+ process.exit(1)
128
+ }
129
+ })
130
+
105
131
  program.parse(process.argv)
@@ -0,0 +1,8 @@
1
+ export declare function setupAndroidEnvironment(options?: {
2
+ androidHome?: string;
3
+ skipEnvVars?: boolean;
4
+ }): Promise<{
5
+ androidHome: string;
6
+ envVarsAdded: boolean;
7
+ shellRcFile?: string;
8
+ }>;
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupAndroidEnvironment = setupAndroidEnvironment;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const os = require("os");
7
+ const execa_1 = require("execa");
8
+ const unzip_url_1 = require("@gengjiawen/unzip-url");
9
+ const fish_shell_utils_1 = require("./fish-shell-utils");
10
+ const ANDROID_CONFIG = {
11
+ sdkVersion: '11076708',
12
+ platformVersion: 'android-36',
13
+ buildToolsVersion: '36.0.0',
14
+ cmakeVersion: '3.30.5',
15
+ ndkVersion: '29.0.14206865',
16
+ };
17
+ function getDefaultAndroidHome() {
18
+ return path.join(os.homedir(), 'Android');
19
+ }
20
+ function getSdkDownloadUrl(sdkVersion) {
21
+ const platform = os.platform();
22
+ if (platform === 'darwin') {
23
+ return `https://dl.google.com/android/repository/commandlinetools-mac-${sdkVersion}_latest.zip`;
24
+ }
25
+ else if (platform === 'linux') {
26
+ return `https://dl.google.com/android/repository/commandlinetools-linux-${sdkVersion}_latest.zip`;
27
+ }
28
+ else {
29
+ throw new Error(`Unsupported platform: ${platform}. Only macOS and Linux are supported.`);
30
+ }
31
+ }
32
+ function getAndroidEnvVars(androidHome, ndkVersion) {
33
+ return `
34
+ # ===== Android development environment - START (2025-11-09) =====
35
+ export ANDROID_HOME=${androidHome}
36
+ export ANDROID_SDK_ROOT=\${ANDROID_HOME}
37
+ export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
38
+ export PATH=\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
39
+ # ===== Android development environment - END =====
40
+ `;
41
+ }
42
+ function getShellRcFile() {
43
+ const shell = process.env.SHELL || '';
44
+ const homeDir = os.homedir();
45
+ if (shell.includes('zsh')) {
46
+ return path.join(homeDir, '.zshrc');
47
+ }
48
+ else if (shell.includes('fish')) {
49
+ return path.join(homeDir, '.config', 'fish', 'config.fish');
50
+ }
51
+ else {
52
+ return path.join(homeDir, '.bashrc');
53
+ }
54
+ }
55
+ function hasAndroidEnvVars(rcFile) {
56
+ if (!fs.existsSync(rcFile)) {
57
+ return false;
58
+ }
59
+ const content = fs.readFileSync(rcFile, 'utf-8');
60
+ return content.includes('ANDROID_HOME');
61
+ }
62
+ function appendEnvVarsToShellConfig(rcFile, envVars) {
63
+ const shell = process.env.SHELL || '';
64
+ const homeDir = os.homedir();
65
+ const bashrcFile = path.join(homeDir, '.bashrc');
66
+ if (!fs.existsSync(bashrcFile) ||
67
+ !fs.readFileSync(bashrcFile, 'utf-8').includes('ANDROID_HOME')) {
68
+ fs.appendFileSync(bashrcFile, envVars);
69
+ console.log(`Environment variables added to: ${bashrcFile}`);
70
+ }
71
+ else {
72
+ console.log(`Environment variables already exist in: ${bashrcFile}`);
73
+ }
74
+ if (shell.includes('fish')) {
75
+ (0, fish_shell_utils_1.appendFishImportScript)(rcFile);
76
+ }
77
+ }
78
+ async function setupAndroidEnvironment(options) {
79
+ const androidHome = options?.androidHome || getDefaultAndroidHome();
80
+ const { sdkVersion, platformVersion, buildToolsVersion, cmakeVersion, ndkVersion, } = ANDROID_CONFIG;
81
+ console.log('Starting Android development environment setup...\n');
82
+ const platform = os.platform();
83
+ if (platform !== 'linux' && platform !== 'darwin') {
84
+ throw new Error(`Unsupported platform: ${platform}. Only macOS and Linux are supported.`);
85
+ }
86
+ console.log(`Platform: ${platform === 'darwin' ? 'macOS' : 'Linux'}`);
87
+ console.log(`Creating Android SDK directory at: ${androidHome}`);
88
+ try {
89
+ if (!fs.existsSync(androidHome)) {
90
+ fs.mkdirSync(androidHome, { recursive: true });
91
+ }
92
+ }
93
+ catch (error) {
94
+ throw new Error(`Failed to create Android directory: ${error instanceof Error ? error.message : String(error)}`);
95
+ }
96
+ console.log('\nDownloading and setting up Android SDK...');
97
+ const sdkUrl = getSdkDownloadUrl(sdkVersion);
98
+ const cmdlineToolsPath = path.join(androidHome, 'cmdline-tools');
99
+ const latestPath = path.join(cmdlineToolsPath, 'latest');
100
+ const latestBin = path.join(latestPath, 'bin');
101
+ try {
102
+ if (!fs.existsSync(latestBin)) {
103
+ console.log(`Downloading and extracting from: ${sdkUrl} to ${androidHome}`);
104
+ await (0, unzip_url_1.unzip)(sdkUrl, cmdlineToolsPath);
105
+ const tmp_toolchain = path.join(cmdlineToolsPath, 'cmdline-tools');
106
+ fs.renameSync(tmp_toolchain, latestPath);
107
+ console.log('Android SDK extracted successfully');
108
+ }
109
+ }
110
+ catch (error) {
111
+ throw new Error(`Failed to download/extract Android SDK: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ const sdkmanagerBinary = path.join(latestBin, 'sdkmanager');
114
+ const sdkmanagerEnv = {
115
+ ...process.env,
116
+ ANDROID_HOME: androidHome,
117
+ ANDROID_SDK_ROOT: androidHome,
118
+ PATH: `${latestBin}:${process.env.PATH}`,
119
+ };
120
+ console.log('\nAccepting Android SDK licenses...');
121
+ try {
122
+ await (0, execa_1.execa)('bash', [
123
+ '-c',
124
+ `yes | "${sdkmanagerBinary}" --sdk_root=${androidHome} --licenses`,
125
+ ], {
126
+ env: sdkmanagerEnv,
127
+ stdio: 'inherit',
128
+ });
129
+ }
130
+ catch (error) {
131
+ console.warn('Warning: License acceptance may have failed, but continuing...');
132
+ }
133
+ console.log('\nInstalling Android SDK components...');
134
+ const components = [
135
+ 'platform-tools',
136
+ `platforms;${platformVersion}`,
137
+ `build-tools;${buildToolsVersion}`,
138
+ `cmake;${cmakeVersion}`,
139
+ `ndk;${ndkVersion}`,
140
+ ];
141
+ try {
142
+ await (0, execa_1.execa)(sdkmanagerBinary, [`--sdk_root=${androidHome}`, ...components], {
143
+ env: sdkmanagerEnv,
144
+ stdio: 'inherit',
145
+ });
146
+ console.log('Android SDK components installed successfully');
147
+ }
148
+ catch (error) {
149
+ throw new Error(`Failed to install SDK components: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ const androidConfigDir = path.join(androidHome, '.android');
152
+ if (fs.existsSync(androidConfigDir)) {
153
+ fs.rmSync(androidConfigDir, { recursive: true, force: true });
154
+ }
155
+ if (platform === 'linux' && androidHome.startsWith('/opt')) {
156
+ try {
157
+ await (0, execa_1.execa)('chmod', ['-R', '777', androidHome], { stdio: 'inherit' });
158
+ }
159
+ catch (error) {
160
+ console.warn('Warning: Failed to set permissions, but continuing...');
161
+ }
162
+ }
163
+ console.log('\nāœ… Android SDK installation completed!');
164
+ let envVarsAdded = false;
165
+ let shellRcFile;
166
+ if (!options?.skipEnvVars) {
167
+ shellRcFile = getShellRcFile();
168
+ console.log(`\nConfiguring shell environment in: ${shellRcFile}`);
169
+ if (hasAndroidEnvVars(shellRcFile)) {
170
+ console.log('Android environment variables already exist in shell config, skipping...');
171
+ }
172
+ else {
173
+ const envVars = getAndroidEnvVars(androidHome, ndkVersion);
174
+ appendEnvVarsToShellConfig(shellRcFile, envVars);
175
+ envVarsAdded = true;
176
+ }
177
+ console.log('\nšŸ“‹ To activate the environment in your current shell, run:');
178
+ console.log(` source ${shellRcFile}`);
179
+ }
180
+ console.log('\nāœ… Android development environment setup completed successfully!');
181
+ return {
182
+ androidHome,
183
+ envVarsAdded,
184
+ shellRcFile,
185
+ };
186
+ }
@@ -0,0 +1,2 @@
1
+ export declare function getFishImportBashExports(): string;
2
+ export declare function appendFishImportScript(fishConfigPath: string): void;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFishImportBashExports = getFishImportBashExports;
4
+ exports.appendFishImportScript = appendFishImportScript;
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ function getFishImportBashExports() {
8
+ return `
9
+ # ===== Import environment variables from .bashrc - START (2025-11-09) =====
10
+ egrep "^export " ~/.bashrc | while read e
11
+ set var (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\1/")
12
+ set value (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\2/")
13
+
14
+ # remove surrounding quotes if existing
15
+ set value (echo $value | sed -E "s/^\\"(.*)\\"\\$/\\1/")
16
+
17
+ # convert Bash \${VAR} to fish {\$VAR} before eval
18
+ set value (printf '%s' "$value" | sed -E 's/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/{$\\1}/g')
19
+
20
+ if test $var = "PATH"
21
+ # replace ":" by spaces. this is how PATH looks for Fish
22
+ set value (echo $value | sed -E "s/:/ /g")
23
+
24
+ # use eval because we need to expand the value
25
+ eval set -xg $var $value
26
+
27
+ continue
28
+ end
29
+
30
+ # evaluate variables. we can use eval because we most likely just used "$var"
31
+ set value (eval echo $value)
32
+
33
+ #echo "set -xg '$var' '$value' (via '$e')"
34
+ set -xg $var $value
35
+ end
36
+ # ===== Import environment variables from .bashrc - END =====
37
+ `;
38
+ }
39
+ function appendFishImportScript(fishConfigPath) {
40
+ const configDir = path.dirname(fishConfigPath);
41
+ if (!fs.existsSync(configDir)) {
42
+ fs.mkdirSync(configDir, { recursive: true });
43
+ }
44
+ const fishContent = fs.existsSync(fishConfigPath)
45
+ ? fs.readFileSync(fishConfigPath, 'utf-8')
46
+ : '';
47
+ if (!fishContent.includes('Import environment variables from .bashrc - START')) {
48
+ const fishScript = getFishImportBashExports();
49
+ fs.appendFileSync(fishConfigPath, fishScript);
50
+ console.log(`Fish import script added to: ${fishConfigPath}`);
51
+ }
52
+ else {
53
+ console.log(`Fish import script already exists in: ${fishConfigPath}`);
54
+ }
55
+ }
package/build/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export declare function writeRaycastConfig(apiKey: string): {
12
12
  configPath: string;
13
13
  };
14
14
  export { setupDevEnvironment } from './dev-setup';
15
+ export { setupAndroidEnvironment } from './android-setup';
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupDevEnvironment = void 0;
3
+ exports.setupAndroidEnvironment = exports.setupDevEnvironment = void 0;
4
4
  exports.writeClaudeConfig = writeClaudeConfig;
5
5
  exports.installDeps = installDeps;
6
6
  exports.writeCodexConfig = writeCodexConfig;
@@ -165,3 +165,5 @@ function writeRaycastConfig(apiKey) {
165
165
  }
166
166
  var dev_setup_1 = require("./dev-setup");
167
167
  Object.defineProperty(exports, "setupDevEnvironment", { enumerable: true, get: function () { return dev_setup_1.setupDevEnvironment; } });
168
+ var android_setup_1 = require("./android-setup");
169
+ Object.defineProperty(exports, "setupAndroidEnvironment", { enumerable: true, get: function () { return android_setup_1.setupAndroidEnvironment; } });
@@ -14,4 +14,3 @@ services:
14
14
  - SYS_PTRACE
15
15
  volumes:
16
16
  - .:/pwd
17
- - $HOME/.local:/root/.local
@@ -0,0 +1,263 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import * as os from 'os'
4
+ import { execa } from 'execa'
5
+ import { unzip } from '@gengjiawen/unzip-url'
6
+ import { appendFishImportScript } from './fish-shell-utils'
7
+
8
+ /** Android SDK configuration */
9
+ const ANDROID_CONFIG = {
10
+ sdkVersion: '11076708',
11
+ platformVersion: 'android-36',
12
+ buildToolsVersion: '36.0.0',
13
+ cmakeVersion: '3.30.5',
14
+ ndkVersion: '29.0.14206865',
15
+ } as const
16
+
17
+ /** Get default Android home directory */
18
+ function getDefaultAndroidHome(): string {
19
+ return path.join(os.homedir(), 'Android')
20
+ }
21
+
22
+ /** Get SDK download URL based on platform */
23
+ function getSdkDownloadUrl(sdkVersion: string): string {
24
+ const platform = os.platform()
25
+ if (platform === 'darwin') {
26
+ return `https://dl.google.com/android/repository/commandlinetools-mac-${sdkVersion}_latest.zip`
27
+ } else if (platform === 'linux') {
28
+ return `https://dl.google.com/android/repository/commandlinetools-linux-${sdkVersion}_latest.zip`
29
+ } else {
30
+ throw new Error(
31
+ `Unsupported platform: ${platform}. Only macOS and Linux are supported.`
32
+ )
33
+ }
34
+ }
35
+
36
+ /** Get Android environment variables */
37
+ function getAndroidEnvVars(androidHome: string, ndkVersion: string): string {
38
+ return `
39
+ # ===== Android development environment - START (2025-11-09) =====
40
+ export ANDROID_HOME=${androidHome}
41
+ export ANDROID_SDK_ROOT=\${ANDROID_HOME}
42
+ export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
43
+ export PATH=\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
44
+ # ===== Android development environment - END =====
45
+ `
46
+ }
47
+
48
+ /** Detect shell configuration file */
49
+ function getShellRcFile(): string {
50
+ const shell = process.env.SHELL || ''
51
+ const homeDir = os.homedir()
52
+
53
+ if (shell.includes('zsh')) {
54
+ return path.join(homeDir, '.zshrc')
55
+ } else if (shell.includes('fish')) {
56
+ return path.join(homeDir, '.config', 'fish', 'config.fish')
57
+ } else {
58
+ return path.join(homeDir, '.bashrc')
59
+ }
60
+ }
61
+
62
+ /** Check if Android environment variables already exist in shell config */
63
+ function hasAndroidEnvVars(rcFile: string): boolean {
64
+ if (!fs.existsSync(rcFile)) {
65
+ return false
66
+ }
67
+ const content = fs.readFileSync(rcFile, 'utf-8')
68
+ return content.includes('ANDROID_HOME')
69
+ }
70
+
71
+ /** Append Android environment variables to shell config */
72
+ function appendEnvVarsToShellConfig(rcFile: string, envVars: string): void {
73
+ const shell = process.env.SHELL || ''
74
+ const homeDir = os.homedir()
75
+ const bashrcFile = path.join(homeDir, '.bashrc')
76
+
77
+ // Write to bashrc
78
+ if (
79
+ !fs.existsSync(bashrcFile) ||
80
+ !fs.readFileSync(bashrcFile, 'utf-8').includes('ANDROID_HOME')
81
+ ) {
82
+ fs.appendFileSync(bashrcFile, envVars)
83
+ console.log(`Environment variables added to: ${bashrcFile}`)
84
+ } else {
85
+ console.log(`Environment variables already exist in: ${bashrcFile}`)
86
+ }
87
+
88
+ // For fish shell, always write to bashrc first, then add import script to fish config
89
+ if (shell.includes('fish')) {
90
+ appendFishImportScript(rcFile)
91
+ }
92
+ }
93
+
94
+ /** Setup Android development environment */
95
+ export async function setupAndroidEnvironment(options?: {
96
+ androidHome?: string
97
+ skipEnvVars?: boolean
98
+ }): Promise<{
99
+ androidHome: string
100
+ envVarsAdded: boolean
101
+ shellRcFile?: string
102
+ }> {
103
+ const androidHome = options?.androidHome || getDefaultAndroidHome()
104
+ const {
105
+ sdkVersion,
106
+ platformVersion,
107
+ buildToolsVersion,
108
+ cmakeVersion,
109
+ ndkVersion,
110
+ } = ANDROID_CONFIG
111
+
112
+ console.log('Starting Android development environment setup...\n')
113
+
114
+ // Check if running on supported platform
115
+ const platform = os.platform()
116
+ if (platform !== 'linux' && platform !== 'darwin') {
117
+ throw new Error(
118
+ `Unsupported platform: ${platform}. Only macOS and Linux are supported.`
119
+ )
120
+ }
121
+
122
+ console.log(`Platform: ${platform === 'darwin' ? 'macOS' : 'Linux'}`)
123
+
124
+ // Create Android directory
125
+ console.log(`Creating Android SDK directory at: ${androidHome}`)
126
+ try {
127
+ if (!fs.existsSync(androidHome)) {
128
+ fs.mkdirSync(androidHome, { recursive: true })
129
+ }
130
+ } catch (error) {
131
+ throw new Error(
132
+ `Failed to create Android directory: ${error instanceof Error ? error.message : String(error)}`
133
+ )
134
+ }
135
+
136
+ // Download and setup Android SDK
137
+ console.log('\nDownloading and setting up Android SDK...')
138
+ const sdkUrl = getSdkDownloadUrl(sdkVersion)
139
+
140
+ const cmdlineToolsPath = path.join(androidHome, 'cmdline-tools')
141
+ const latestPath = path.join(cmdlineToolsPath, 'latest')
142
+ const latestBin = path.join(latestPath, 'bin')
143
+
144
+ try {
145
+ // Download and extract SDK using unzip-url
146
+ if (!fs.existsSync(latestBin)) {
147
+ console.log(
148
+ `Downloading and extracting from: ${sdkUrl} to ${androidHome}`
149
+ )
150
+ await unzip(sdkUrl, cmdlineToolsPath)
151
+ const tmp_toolchain = path.join(cmdlineToolsPath, 'cmdline-tools')
152
+ fs.renameSync(tmp_toolchain, latestPath)
153
+ console.log('Android SDK extracted successfully')
154
+ }
155
+ } catch (error) {
156
+ throw new Error(
157
+ `Failed to download/extract Android SDK: ${error instanceof Error ? error.message : String(error)}`
158
+ )
159
+ }
160
+ const sdkmanagerBinary = path.join(latestBin, 'sdkmanager')
161
+
162
+ const sdkmanagerEnv = {
163
+ ...process.env,
164
+ ANDROID_HOME: androidHome,
165
+ ANDROID_SDK_ROOT: androidHome,
166
+ PATH: `${latestBin}:${process.env.PATH}`,
167
+ }
168
+
169
+ // Accept licenses
170
+ console.log('\nAccepting Android SDK licenses...')
171
+ try {
172
+ await execa(
173
+ 'bash',
174
+ [
175
+ '-c',
176
+ `yes | "${sdkmanagerBinary}" --sdk_root=${androidHome} --licenses`,
177
+ ],
178
+ {
179
+ env: sdkmanagerEnv,
180
+ stdio: 'inherit',
181
+ }
182
+ )
183
+ } catch (error) {
184
+ console.warn(
185
+ 'Warning: License acceptance may have failed, but continuing...'
186
+ )
187
+ }
188
+
189
+ // Install SDK components
190
+ console.log('\nInstalling Android SDK components...')
191
+ const components = [
192
+ 'platform-tools',
193
+ `platforms;${platformVersion}`,
194
+ `build-tools;${buildToolsVersion}`,
195
+ `cmake;${cmakeVersion}`,
196
+ `ndk;${ndkVersion}`,
197
+ ]
198
+
199
+ try {
200
+ await execa(
201
+ sdkmanagerBinary,
202
+ [`--sdk_root=${androidHome}`, ...components],
203
+ {
204
+ env: sdkmanagerEnv,
205
+ stdio: 'inherit',
206
+ }
207
+ )
208
+ console.log('Android SDK components installed successfully')
209
+ } catch (error) {
210
+ throw new Error(
211
+ `Failed to install SDK components: ${error instanceof Error ? error.message : String(error)}`
212
+ )
213
+ }
214
+
215
+ // Clean up .android directory
216
+ const androidConfigDir = path.join(androidHome, '.android')
217
+ if (fs.existsSync(androidConfigDir)) {
218
+ fs.rmSync(androidConfigDir, { recursive: true, force: true })
219
+ }
220
+
221
+ // Set permissions (only needed on Linux for system-wide installs)
222
+ if (platform === 'linux' && androidHome.startsWith('/opt')) {
223
+ try {
224
+ await execa('chmod', ['-R', '777', androidHome], { stdio: 'inherit' })
225
+ } catch (error) {
226
+ console.warn('Warning: Failed to set permissions, but continuing...')
227
+ }
228
+ }
229
+
230
+ console.log('\nāœ… Android SDK installation completed!')
231
+
232
+ // Handle environment variables
233
+ let envVarsAdded = false
234
+ let shellRcFile: string | undefined
235
+
236
+ if (!options?.skipEnvVars) {
237
+ shellRcFile = getShellRcFile()
238
+ console.log(`\nConfiguring shell environment in: ${shellRcFile}`)
239
+
240
+ if (hasAndroidEnvVars(shellRcFile)) {
241
+ console.log(
242
+ 'Android environment variables already exist in shell config, skipping...'
243
+ )
244
+ } else {
245
+ const envVars = getAndroidEnvVars(androidHome, ndkVersion)
246
+ appendEnvVarsToShellConfig(shellRcFile, envVars)
247
+ envVarsAdded = true
248
+ }
249
+
250
+ console.log('\nšŸ“‹ To activate the environment in your current shell, run:')
251
+ console.log(` source ${shellRcFile}`)
252
+ }
253
+
254
+ console.log(
255
+ '\nāœ… Android development environment setup completed successfully!'
256
+ )
257
+
258
+ return {
259
+ androidHome,
260
+ envVarsAdded,
261
+ shellRcFile,
262
+ }
263
+ }
@@ -0,0 +1,64 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+
4
+ /** Get Fish shell script to import bash exports */
5
+ export function getFishImportBashExports(): string {
6
+ return `
7
+ # ===== Import environment variables from .bashrc - START (2025-11-09) =====
8
+ egrep "^export " ~/.bashrc | while read e
9
+ set var (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\1/")
10
+ set value (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\2/")
11
+
12
+ # remove surrounding quotes if existing
13
+ set value (echo $value | sed -E "s/^\\"(.*)\\"\\$/\\1/")
14
+
15
+ # convert Bash \${VAR} to fish {\$VAR} before eval
16
+ set value (printf '%s' "$value" | sed -E 's/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/{$\\1}/g')
17
+
18
+ if test $var = "PATH"
19
+ # replace ":" by spaces. this is how PATH looks for Fish
20
+ set value (echo $value | sed -E "s/:/ /g")
21
+
22
+ # use eval because we need to expand the value
23
+ eval set -xg $var $value
24
+
25
+ continue
26
+ end
27
+
28
+ # evaluate variables. we can use eval because we most likely just used "$var"
29
+ set value (eval echo $value)
30
+
31
+ #echo "set -xg '$var' '$value' (via '$e')"
32
+ set -xg $var $value
33
+ end
34
+ # ===== Import environment variables from .bashrc - END =====
35
+ `
36
+ }
37
+
38
+ /**
39
+ * Append bash import script to Fish shell config file
40
+ * This function adds the import script to fish config.fish to automatically
41
+ * source environment variables from .bashrc
42
+ */
43
+ export function appendFishImportScript(fishConfigPath: string): void {
44
+ // Ensure fish config directory exists
45
+ const configDir = path.dirname(fishConfigPath)
46
+ if (!fs.existsSync(configDir)) {
47
+ fs.mkdirSync(configDir, { recursive: true })
48
+ }
49
+
50
+ // Check if import script already exists
51
+ const fishContent = fs.existsSync(fishConfigPath)
52
+ ? fs.readFileSync(fishConfigPath, 'utf-8')
53
+ : ''
54
+
55
+ if (
56
+ !fishContent.includes('Import environment variables from .bashrc - START')
57
+ ) {
58
+ const fishScript = getFishImportBashExports()
59
+ fs.appendFileSync(fishConfigPath, fishScript)
60
+ console.log(`Fish import script added to: ${fishConfigPath}`)
61
+ } else {
62
+ console.log(`Fish import script already exists in: ${fishConfigPath}`)
63
+ }
64
+ }
package/libs/index.ts CHANGED
@@ -210,3 +210,6 @@ export function writeRaycastConfig(apiKey: string): { configPath: string } {
210
210
 
211
211
  // Re-export dev-setup functionality
212
212
  export { setupDevEnvironment } from './dev-setup'
213
+
214
+ // Re-export android-setup functionality
215
+ export { setupAndroidEnvironment } from './android-setup'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gengjiawen/os-init",
3
3
  "private": false,
4
- "version": "1.6.0",
4
+ "version": "1.7.1",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "scripts": {
11
11
  "dev": "tsc -w",
12
12
  "server": "nodemon --exec ts-node libs/index.ts",
13
- "prepare": "husky install",
13
+ "prepare": "husky",
14
14
  "clean": "rimraf build",
15
15
  "format": "prettier --write \"{examples,libs,script,bin}/**/*.{js,ts}\" \"**/*.yml\"",
16
16
  "format:check": "prettier --list-different \"{examples,libs,script,bin}/**/*.{js,ts}\" \"**/*.yml\"",
@@ -19,6 +19,7 @@
19
19
  "postbuild": "cpy '**/*' '!**/*.ts' ../build/ --cwd=libs --parents"
20
20
  },
21
21
  "dependencies": {
22
+ "@gengjiawen/unzip-url": "^1.0.0",
22
23
  "commander": "^12.1.0",
23
24
  "ip": "^2.0.1",
24
25
  "execa": "^8.0.1"