@gengjiawen/os-init 1.7.0 → 1.8.0
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.
- package/CHANGELOG.md +15 -0
- package/README.md +40 -36
- package/build/android-setup.js +54 -26
- package/build/fish-shell-utils.d.ts +2 -0
- package/build/fish-shell-utils.js +62 -0
- package/libs/android-setup.ts +76 -38
- package/libs/fish-shell-utils.ts +75 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.0](https://github.com/gengjiawen/os-init/compare/v1.7.1...v1.8.0) (2025-11-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* bump andoid tool version ([e54ef83](https://github.com/gengjiawen/os-init/commit/e54ef835b9a4baa03f89cfca651b0d032364734b))
|
|
9
|
+
* refine default sdk home ([690d4ef](https://github.com/gengjiawen/os-init/commit/690d4efdeec1b96da2296e1275cfa2898382fea2))
|
|
10
|
+
|
|
11
|
+
## [1.7.1](https://github.com/gengjiawen/os-init/compare/v1.7.0...v1.7.1) (2025-11-09)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* android CI ([#12](https://github.com/gengjiawen/os-init/issues/12)) ([535b011](https://github.com/gengjiawen/os-init/commit/535b0115b513902ceb6fd9ef98bcb8338ac9972e))
|
|
17
|
+
|
|
3
18
|
## [1.7.0](https://github.com/gengjiawen/os-init/compare/v1.6.0...v1.7.0) (2025-11-09)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -2,41 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@gengjiawen/os-init)
|
|
4
4
|
|
|
5
|
-
A CLI tool to quickly configure
|
|
5
|
+
A CLI tool to quickly configure development tools and environments.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
|
-
### Configure Claude Code
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pnpx @gengjiawen/os-init set-cc <API_KEY>
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Configures Claude Code Router with your API key. This command will:
|
|
16
|
-
- Write `~/.claude-code-router/config.json`
|
|
17
|
-
- Write `~/.claude/settings.json`
|
|
18
|
-
- Install global tools: `@anthropic-ai/claude-code`, `@musistudio/claude-code-router`
|
|
19
|
-
|
|
20
|
-
### Configure Codex CLI
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
pnpx @gengjiawen/os-init set-codex <API_KEY>
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Configures Codex CLI with your API key. This command will:
|
|
27
|
-
- Write `~/.codex/config.toml`
|
|
28
|
-
- Write `~/.codex/auth.json`
|
|
29
|
-
- Install global tool: `@openai/codex`
|
|
30
|
-
|
|
31
|
-
### Configure Raycast AI
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
pnpx @gengjiawen/os-init set-raycast-ai <API_KEY>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Configures Raycast AI providers with your API key. This command will:
|
|
38
|
-
- Write `~/.config/raycast/ai/providers.yaml`
|
|
39
|
-
|
|
40
9
|
### Setup Dev Environment
|
|
41
10
|
|
|
42
11
|
```bash
|
|
@@ -44,12 +13,14 @@ pnpx @gengjiawen/os-init set-dev <SSH_PUBLIC_KEY>
|
|
|
44
13
|
```
|
|
45
14
|
|
|
46
15
|
Sets up a Docker-based development environment with SSH access. This command will:
|
|
16
|
+
|
|
47
17
|
- Copy `dev-setup` directory to your current directory (or specify with `-t, --target <dir>`)
|
|
48
18
|
- Configure SSH public key in Dockerfile
|
|
49
19
|
- Automatically run `docker-compose build && docker-compose up -d` (if docker-compose is available)
|
|
50
20
|
- Display SSH connection command with your local IP address
|
|
51
21
|
|
|
52
22
|
Example:
|
|
23
|
+
|
|
53
24
|
```bash
|
|
54
25
|
pnpx @gengjiawen/os-init set-dev "ssh-rsa AAAAB3NzaC1yc2..."
|
|
55
26
|
```
|
|
@@ -57,10 +28,11 @@ pnpx @gengjiawen/os-init set-dev "ssh-rsa AAAAB3NzaC1yc2..."
|
|
|
57
28
|
### Setup Android Development Environment
|
|
58
29
|
|
|
59
30
|
```bash
|
|
60
|
-
pnpx @gengjiawen/os-init set-android
|
|
31
|
+
pnpx @gengjiawen/os-init set-android
|
|
61
32
|
```
|
|
62
33
|
|
|
63
34
|
Sets up a complete Android development environment on macOS and Linux. This command will:
|
|
35
|
+
|
|
64
36
|
- Install Android SDK to `~/Android` (or custom path with `--android-home <path>`)
|
|
65
37
|
- Download and install Android SDK Command-line Tools, Platform Tools, Build Tools, CMake, and NDK
|
|
66
38
|
- Automatically accept Android SDK licenses
|
|
@@ -69,10 +41,8 @@ Sets up a complete Android development environment on macOS and Linux. This comm
|
|
|
69
41
|
- No sudo access required
|
|
70
42
|
|
|
71
43
|
Example:
|
|
72
|
-
```bash
|
|
73
|
-
# Basic installation
|
|
74
|
-
pnpx @gengjiawen/os-init set-android
|
|
75
44
|
|
|
45
|
+
```bash
|
|
76
46
|
# Custom installation path
|
|
77
47
|
pnpx @gengjiawen/os-init set-android --android-home ~/my-android-sdk
|
|
78
48
|
|
|
@@ -82,4 +52,38 @@ pnpx @gengjiawen/os-init set-android --skip-env-vars
|
|
|
82
52
|
|
|
83
53
|
---
|
|
84
54
|
|
|
55
|
+
### Configure Claude Code
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpx @gengjiawen/os-init set-cc <API_KEY>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Configures Claude Code Router with your API key. This command will:
|
|
62
|
+
|
|
63
|
+
- Write `~/.claude-code-router/config.json`
|
|
64
|
+
- Write `~/.claude/settings.json`
|
|
65
|
+
- Install global tools: `@anthropic-ai/claude-code`, `@musistudio/claude-code-router`
|
|
66
|
+
|
|
67
|
+
### Configure Codex CLI
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pnpx @gengjiawen/os-init set-codex <API_KEY>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Configures Codex CLI with your API key. This command will:
|
|
74
|
+
|
|
75
|
+
- Write `~/.codex/config.toml`
|
|
76
|
+
- Write `~/.codex/auth.json`
|
|
77
|
+
- Install global tool: `@openai/codex`
|
|
78
|
+
|
|
79
|
+
### Configure Raycast AI
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pnpx @gengjiawen/os-init set-raycast-ai <API_KEY>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Configures Raycast AI providers with your API key. This command will:
|
|
86
|
+
|
|
87
|
+
- Write `~/.config/raycast/ai/providers.yaml`
|
|
88
|
+
|
|
85
89
|
Project generated by [gengjiawen/ts-scaffold](https://github.com/gengjiawen/ts-scaffold)
|
package/build/android-setup.js
CHANGED
|
@@ -5,16 +5,34 @@ const fs = require("fs");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const os = require("os");
|
|
7
7
|
const execa_1 = require("execa");
|
|
8
|
-
const unzip_url_1 = require("@
|
|
8
|
+
const unzip_url_1 = require("@gengjiawen/unzip-url");
|
|
9
|
+
const fish_shell_utils_1 = require("./fish-shell-utils");
|
|
9
10
|
const ANDROID_CONFIG = {
|
|
10
11
|
sdkVersion: '11076708',
|
|
11
12
|
platformVersion: 'android-36',
|
|
12
|
-
buildToolsVersion: '36.
|
|
13
|
-
cmakeVersion: '
|
|
13
|
+
buildToolsVersion: '36.1.0',
|
|
14
|
+
cmakeVersion: '4.1.2',
|
|
14
15
|
ndkVersion: '29.0.14206865',
|
|
15
16
|
};
|
|
16
17
|
function getDefaultAndroidHome() {
|
|
17
|
-
|
|
18
|
+
const { ANDROID_SDK_ROOT, ANDROID_HOME, LOCALAPPDATA } = process.env;
|
|
19
|
+
if (ANDROID_SDK_ROOT && ANDROID_SDK_ROOT.trim())
|
|
20
|
+
return ANDROID_SDK_ROOT;
|
|
21
|
+
if (ANDROID_HOME && ANDROID_HOME.trim())
|
|
22
|
+
return ANDROID_HOME;
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
switch (process.platform) {
|
|
25
|
+
case 'darwin':
|
|
26
|
+
return path.join(home, 'Library', 'Android', 'sdk');
|
|
27
|
+
case 'linux':
|
|
28
|
+
return path.join(home, 'Android', 'Sdk');
|
|
29
|
+
case 'win32':
|
|
30
|
+
return LOCALAPPDATA
|
|
31
|
+
? path.join(LOCALAPPDATA, 'Android', 'Sdk')
|
|
32
|
+
: path.join(home, 'AppData', 'Local', 'Android', 'Sdk');
|
|
33
|
+
default:
|
|
34
|
+
return path.join(home, 'Android', 'Sdk');
|
|
35
|
+
}
|
|
18
36
|
}
|
|
19
37
|
function getSdkDownloadUrl(sdkVersion) {
|
|
20
38
|
const platform = os.platform();
|
|
@@ -30,11 +48,12 @@ function getSdkDownloadUrl(sdkVersion) {
|
|
|
30
48
|
}
|
|
31
49
|
function getAndroidEnvVars(androidHome, ndkVersion) {
|
|
32
50
|
return `
|
|
33
|
-
# Android development environment
|
|
51
|
+
# ===== Android development environment - START (2025-11-09) =====
|
|
34
52
|
export ANDROID_HOME=${androidHome}
|
|
35
53
|
export ANDROID_SDK_ROOT=\${ANDROID_HOME}
|
|
36
54
|
export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
|
|
37
|
-
export PATH=\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
|
|
55
|
+
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}
|
|
56
|
+
# ===== Android development environment - END =====
|
|
38
57
|
`;
|
|
39
58
|
}
|
|
40
59
|
function getShellRcFile() {
|
|
@@ -58,12 +77,20 @@ function hasAndroidEnvVars(rcFile) {
|
|
|
58
77
|
return content.includes('ANDROID_HOME');
|
|
59
78
|
}
|
|
60
79
|
function appendEnvVarsToShellConfig(rcFile, envVars) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
const shell = process.env.SHELL || '';
|
|
81
|
+
const homeDir = os.homedir();
|
|
82
|
+
const bashrcFile = path.join(homeDir, '.bashrc');
|
|
83
|
+
if (!fs.existsSync(bashrcFile) ||
|
|
84
|
+
!fs.readFileSync(bashrcFile, 'utf-8').includes('ANDROID_HOME')) {
|
|
85
|
+
fs.appendFileSync(bashrcFile, envVars);
|
|
86
|
+
console.log(`Environment variables added to: ${bashrcFile}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(`Environment variables already exist in: ${bashrcFile}`);
|
|
90
|
+
}
|
|
91
|
+
if (shell.includes('fish')) {
|
|
92
|
+
(0, fish_shell_utils_1.appendFishImportScript)();
|
|
64
93
|
}
|
|
65
|
-
fs.appendFileSync(rcFile, envVars);
|
|
66
|
-
console.log(`Environment variables added to: ${rcFile}`);
|
|
67
94
|
}
|
|
68
95
|
async function setupAndroidEnvironment(options) {
|
|
69
96
|
const androidHome = options?.androidHome || getDefaultAndroidHome();
|
|
@@ -85,32 +112,34 @@ async function setupAndroidEnvironment(options) {
|
|
|
85
112
|
}
|
|
86
113
|
console.log('\nDownloading and setting up Android SDK...');
|
|
87
114
|
const sdkUrl = getSdkDownloadUrl(sdkVersion);
|
|
115
|
+
const cmdlineToolsPath = path.join(androidHome, 'cmdline-tools');
|
|
116
|
+
const latestPath = path.join(cmdlineToolsPath, 'latest');
|
|
117
|
+
const latestBin = path.join(latestPath, 'bin');
|
|
88
118
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const tempToolsPath = path.join(cmdlineToolsPath, 'cmdline-tools');
|
|
96
|
-
const latestToolsPath = path.join(cmdlineToolsPath, 'latest');
|
|
97
|
-
if (fs.existsSync(tempToolsPath)) {
|
|
98
|
-
fs.renameSync(tempToolsPath, latestToolsPath);
|
|
119
|
+
if (!fs.existsSync(latestBin)) {
|
|
120
|
+
console.log(`Downloading and extracting from: ${sdkUrl} to ${androidHome}`);
|
|
121
|
+
await (0, unzip_url_1.unzip)(sdkUrl, cmdlineToolsPath);
|
|
122
|
+
const tmp_toolchain = path.join(cmdlineToolsPath, 'cmdline-tools');
|
|
123
|
+
fs.renameSync(tmp_toolchain, latestPath);
|
|
124
|
+
console.log('Android SDK extracted successfully');
|
|
99
125
|
}
|
|
100
|
-
console.log('Android SDK extracted successfully');
|
|
101
126
|
}
|
|
102
127
|
catch (error) {
|
|
103
128
|
throw new Error(`Failed to download/extract Android SDK: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
129
|
}
|
|
130
|
+
const sdkmanagerBinary = path.join(latestBin, 'sdkmanager');
|
|
105
131
|
const sdkmanagerEnv = {
|
|
106
132
|
...process.env,
|
|
107
133
|
ANDROID_HOME: androidHome,
|
|
108
134
|
ANDROID_SDK_ROOT: androidHome,
|
|
109
|
-
PATH: `${
|
|
135
|
+
PATH: `${latestBin}:${process.env.PATH}`,
|
|
110
136
|
};
|
|
111
137
|
console.log('\nAccepting Android SDK licenses...');
|
|
112
138
|
try {
|
|
113
|
-
await (0, execa_1.execa)('bash', [
|
|
139
|
+
await (0, execa_1.execa)('bash', [
|
|
140
|
+
'-c',
|
|
141
|
+
`yes | "${sdkmanagerBinary}" --sdk_root=${androidHome} --licenses`,
|
|
142
|
+
], {
|
|
114
143
|
env: sdkmanagerEnv,
|
|
115
144
|
stdio: 'inherit',
|
|
116
145
|
});
|
|
@@ -127,8 +156,7 @@ async function setupAndroidEnvironment(options) {
|
|
|
127
156
|
`ndk;${ndkVersion}`,
|
|
128
157
|
];
|
|
129
158
|
try {
|
|
130
|
-
|
|
131
|
-
await (0, execa_1.execa)('bash', ['-c', `yes | sdkmanager ${componentsList}`], {
|
|
159
|
+
await (0, execa_1.execa)(sdkmanagerBinary, [`--sdk_root=${androidHome}`, ...components], {
|
|
132
160
|
env: sdkmanagerEnv,
|
|
133
161
|
stdio: 'inherit',
|
|
134
162
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
const os = require("os");
|
|
8
|
+
function getFishImportBashExports() {
|
|
9
|
+
return `
|
|
10
|
+
# ===== Import environment variables from .bashrc - START (2025-11-09) =====
|
|
11
|
+
egrep "^export " ~/.bashrc | while read e
|
|
12
|
+
set var (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\1/")
|
|
13
|
+
set value (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\2/")
|
|
14
|
+
|
|
15
|
+
# remove surrounding quotes if existing
|
|
16
|
+
set value (echo $value | sed -E "s/^\\"(.*)\\"\\$/\\1/")
|
|
17
|
+
|
|
18
|
+
# convert Bash \${VAR} to fish {\$VAR} before eval
|
|
19
|
+
set value (printf '%s' "$value" | sed -E 's/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/{$\\1}/g')
|
|
20
|
+
|
|
21
|
+
if test $var = "PATH"
|
|
22
|
+
# replace ":" by spaces. this is how PATH looks for Fish
|
|
23
|
+
set value (echo $value | sed -E "s/:/ /g")
|
|
24
|
+
|
|
25
|
+
# use eval because we need to expand the value
|
|
26
|
+
eval set -xg $var $value
|
|
27
|
+
|
|
28
|
+
continue
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# evaluate variables. we can use eval because we most likely just used "$var"
|
|
32
|
+
set value (eval echo $value)
|
|
33
|
+
|
|
34
|
+
#echo "set -xg '$var' '$value' (via '$e')"
|
|
35
|
+
set -xg $var $value
|
|
36
|
+
end
|
|
37
|
+
# ===== Import environment variables from .bashrc - END =====
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
function appendFishImportScript() {
|
|
41
|
+
const homeDir = os.homedir();
|
|
42
|
+
const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish');
|
|
43
|
+
if (!fs.existsSync(fishConfigPath)) {
|
|
44
|
+
console.log('Fish shell config not found, skipping fish setup');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const configDir = path.dirname(fishConfigPath);
|
|
48
|
+
if (!fs.existsSync(configDir)) {
|
|
49
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
const fishContent = fs.existsSync(fishConfigPath)
|
|
52
|
+
? fs.readFileSync(fishConfigPath, 'utf-8')
|
|
53
|
+
: '';
|
|
54
|
+
if (!fishContent.includes('Import environment variables from .bashrc - START')) {
|
|
55
|
+
const fishScript = getFishImportBashExports();
|
|
56
|
+
fs.appendFileSync(fishConfigPath, fishScript);
|
|
57
|
+
console.log(`Fish import script added to: ${fishConfigPath}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(`Fish import script already exists in: ${fishConfigPath}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
package/libs/android-setup.ts
CHANGED
|
@@ -2,20 +2,39 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import { execa } from 'execa'
|
|
5
|
-
import { unzip } from '@
|
|
5
|
+
import { unzip } from '@gengjiawen/unzip-url'
|
|
6
|
+
import { appendFishImportScript } from './fish-shell-utils'
|
|
6
7
|
|
|
7
8
|
/** Android SDK configuration */
|
|
8
9
|
const ANDROID_CONFIG = {
|
|
9
10
|
sdkVersion: '11076708',
|
|
10
11
|
platformVersion: 'android-36',
|
|
11
|
-
buildToolsVersion: '36.
|
|
12
|
-
cmakeVersion: '
|
|
12
|
+
buildToolsVersion: '36.1.0',
|
|
13
|
+
cmakeVersion: '4.1.2',
|
|
13
14
|
ndkVersion: '29.0.14206865',
|
|
14
15
|
} as const
|
|
15
16
|
|
|
16
17
|
/** Get default Android home directory */
|
|
17
18
|
function getDefaultAndroidHome(): string {
|
|
18
|
-
|
|
19
|
+
const { ANDROID_SDK_ROOT, ANDROID_HOME, LOCALAPPDATA } = process.env
|
|
20
|
+
|
|
21
|
+
if (ANDROID_SDK_ROOT && ANDROID_SDK_ROOT.trim()) return ANDROID_SDK_ROOT
|
|
22
|
+
if (ANDROID_HOME && ANDROID_HOME.trim()) return ANDROID_HOME
|
|
23
|
+
|
|
24
|
+
const home = os.homedir()
|
|
25
|
+
switch (process.platform) {
|
|
26
|
+
case 'darwin':
|
|
27
|
+
return path.join(home, 'Library', 'Android', 'sdk')
|
|
28
|
+
case 'linux':
|
|
29
|
+
return path.join(home, 'Android', 'Sdk')
|
|
30
|
+
case 'win32':
|
|
31
|
+
return LOCALAPPDATA
|
|
32
|
+
? path.join(LOCALAPPDATA, 'Android', 'Sdk')
|
|
33
|
+
: path.join(home, 'AppData', 'Local', 'Android', 'Sdk')
|
|
34
|
+
default:
|
|
35
|
+
// Reasonable fallback for other POSIX-like environments
|
|
36
|
+
return path.join(home, 'Android', 'Sdk')
|
|
37
|
+
}
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
/** Get SDK download URL based on platform */
|
|
@@ -35,11 +54,12 @@ function getSdkDownloadUrl(sdkVersion: string): string {
|
|
|
35
54
|
/** Get Android environment variables */
|
|
36
55
|
function getAndroidEnvVars(androidHome: string, ndkVersion: string): string {
|
|
37
56
|
return `
|
|
38
|
-
# Android development environment
|
|
57
|
+
# ===== Android development environment - START (2025-11-09) =====
|
|
39
58
|
export ANDROID_HOME=${androidHome}
|
|
40
59
|
export ANDROID_SDK_ROOT=\${ANDROID_HOME}
|
|
41
60
|
export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
|
|
42
|
-
export PATH=\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
|
|
61
|
+
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}
|
|
62
|
+
# ===== Android development environment - END =====
|
|
43
63
|
`
|
|
44
64
|
}
|
|
45
65
|
|
|
@@ -68,13 +88,25 @@ function hasAndroidEnvVars(rcFile: string): boolean {
|
|
|
68
88
|
|
|
69
89
|
/** Append Android environment variables to shell config */
|
|
70
90
|
function appendEnvVarsToShellConfig(rcFile: string, envVars: string): void {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
const shell = process.env.SHELL || ''
|
|
92
|
+
const homeDir = os.homedir()
|
|
93
|
+
const bashrcFile = path.join(homeDir, '.bashrc')
|
|
94
|
+
|
|
95
|
+
// Write to bashrc
|
|
96
|
+
if (
|
|
97
|
+
!fs.existsSync(bashrcFile) ||
|
|
98
|
+
!fs.readFileSync(bashrcFile, 'utf-8').includes('ANDROID_HOME')
|
|
99
|
+
) {
|
|
100
|
+
fs.appendFileSync(bashrcFile, envVars)
|
|
101
|
+
console.log(`Environment variables added to: ${bashrcFile}`)
|
|
102
|
+
} else {
|
|
103
|
+
console.log(`Environment variables already exist in: ${bashrcFile}`)
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
// For fish shell, always write to bashrc first, then add import script to fish config
|
|
107
|
+
if (shell.includes('fish')) {
|
|
108
|
+
appendFishImportScript()
|
|
109
|
+
}
|
|
78
110
|
}
|
|
79
111
|
|
|
80
112
|
/** Setup Android development environment */
|
|
@@ -123,46 +155,49 @@ export async function setupAndroidEnvironment(options?: {
|
|
|
123
155
|
console.log('\nDownloading and setting up Android SDK...')
|
|
124
156
|
const sdkUrl = getSdkDownloadUrl(sdkVersion)
|
|
125
157
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!fs.existsSync(cmdlineToolsPath)) {
|
|
130
|
-
fs.mkdirSync(cmdlineToolsPath, { recursive: true })
|
|
131
|
-
}
|
|
158
|
+
const cmdlineToolsPath = path.join(androidHome, 'cmdline-tools')
|
|
159
|
+
const latestPath = path.join(cmdlineToolsPath, 'latest')
|
|
160
|
+
const latestBin = path.join(latestPath, 'bin')
|
|
132
161
|
|
|
162
|
+
try {
|
|
133
163
|
// Download and extract SDK using unzip-url
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
164
|
+
if (!fs.existsSync(latestBin)) {
|
|
165
|
+
console.log(
|
|
166
|
+
`Downloading and extracting from: ${sdkUrl} to ${androidHome}`
|
|
167
|
+
)
|
|
168
|
+
await unzip(sdkUrl, cmdlineToolsPath)
|
|
169
|
+
const tmp_toolchain = path.join(cmdlineToolsPath, 'cmdline-tools')
|
|
170
|
+
fs.renameSync(tmp_toolchain, latestPath)
|
|
171
|
+
console.log('Android SDK extracted successfully')
|
|
142
172
|
}
|
|
143
|
-
|
|
144
|
-
console.log('Android SDK extracted successfully')
|
|
145
173
|
} catch (error) {
|
|
146
174
|
throw new Error(
|
|
147
175
|
`Failed to download/extract Android SDK: ${error instanceof Error ? error.message : String(error)}`
|
|
148
176
|
)
|
|
149
177
|
}
|
|
178
|
+
const sdkmanagerBinary = path.join(latestBin, 'sdkmanager')
|
|
150
179
|
|
|
151
|
-
// Set environment variables for sdkmanager
|
|
152
180
|
const sdkmanagerEnv = {
|
|
153
181
|
...process.env,
|
|
154
182
|
ANDROID_HOME: androidHome,
|
|
155
183
|
ANDROID_SDK_ROOT: androidHome,
|
|
156
|
-
PATH: `${
|
|
184
|
+
PATH: `${latestBin}:${process.env.PATH}`,
|
|
157
185
|
}
|
|
158
186
|
|
|
159
187
|
// Accept licenses
|
|
160
188
|
console.log('\nAccepting Android SDK licenses...')
|
|
161
189
|
try {
|
|
162
|
-
await execa(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
await execa(
|
|
191
|
+
'bash',
|
|
192
|
+
[
|
|
193
|
+
'-c',
|
|
194
|
+
`yes | "${sdkmanagerBinary}" --sdk_root=${androidHome} --licenses`,
|
|
195
|
+
],
|
|
196
|
+
{
|
|
197
|
+
env: sdkmanagerEnv,
|
|
198
|
+
stdio: 'inherit',
|
|
199
|
+
}
|
|
200
|
+
)
|
|
166
201
|
} catch (error) {
|
|
167
202
|
console.warn(
|
|
168
203
|
'Warning: License acceptance may have failed, but continuing...'
|
|
@@ -180,11 +215,14 @@ export async function setupAndroidEnvironment(options?: {
|
|
|
180
215
|
]
|
|
181
216
|
|
|
182
217
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
218
|
+
await execa(
|
|
219
|
+
sdkmanagerBinary,
|
|
220
|
+
[`--sdk_root=${androidHome}`, ...components],
|
|
221
|
+
{
|
|
222
|
+
env: sdkmanagerEnv,
|
|
223
|
+
stdio: 'inherit',
|
|
224
|
+
}
|
|
225
|
+
)
|
|
188
226
|
console.log('Android SDK components installed successfully')
|
|
189
227
|
} catch (error) {
|
|
190
228
|
throw new Error(
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
|
|
5
|
+
/** Get Fish shell script to import bash exports */
|
|
6
|
+
export function getFishImportBashExports(): string {
|
|
7
|
+
return `
|
|
8
|
+
# ===== Import environment variables from .bashrc - START (2025-11-09) =====
|
|
9
|
+
egrep "^export " ~/.bashrc | while read e
|
|
10
|
+
set var (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\1/")
|
|
11
|
+
set value (echo $e | sed -E "s/^export ([A-Za-z_0-9]+)=(.*)\\$/\\2/")
|
|
12
|
+
|
|
13
|
+
# remove surrounding quotes if existing
|
|
14
|
+
set value (echo $value | sed -E "s/^\\"(.*)\\"\\$/\\1/")
|
|
15
|
+
|
|
16
|
+
# convert Bash \${VAR} to fish {\$VAR} before eval
|
|
17
|
+
set value (printf '%s' "$value" | sed -E 's/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/{$\\1}/g')
|
|
18
|
+
|
|
19
|
+
if test $var = "PATH"
|
|
20
|
+
# replace ":" by spaces. this is how PATH looks for Fish
|
|
21
|
+
set value (echo $value | sed -E "s/:/ /g")
|
|
22
|
+
|
|
23
|
+
# use eval because we need to expand the value
|
|
24
|
+
eval set -xg $var $value
|
|
25
|
+
|
|
26
|
+
continue
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# evaluate variables. we can use eval because we most likely just used "$var"
|
|
30
|
+
set value (eval echo $value)
|
|
31
|
+
|
|
32
|
+
#echo "set -xg '$var' '$value' (via '$e')"
|
|
33
|
+
set -xg $var $value
|
|
34
|
+
end
|
|
35
|
+
# ===== Import environment variables from .bashrc - END =====
|
|
36
|
+
`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Append bash import script to Fish shell config file
|
|
41
|
+
* This function adds the import script to fish config.fish to automatically
|
|
42
|
+
* source environment variables from .bashrc
|
|
43
|
+
* It detects fish shell by checking if ~/.config/fish/config.fish exists
|
|
44
|
+
*/
|
|
45
|
+
export function appendFishImportScript(): void {
|
|
46
|
+
const homeDir = os.homedir()
|
|
47
|
+
const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish')
|
|
48
|
+
|
|
49
|
+
// Detect if fish shell exists by checking config file
|
|
50
|
+
if (!fs.existsSync(fishConfigPath)) {
|
|
51
|
+
console.log('Fish shell config not found, skipping fish setup')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Ensure fish config directory exists
|
|
56
|
+
const configDir = path.dirname(fishConfigPath)
|
|
57
|
+
if (!fs.existsSync(configDir)) {
|
|
58
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if import script already exists
|
|
62
|
+
const fishContent = fs.existsSync(fishConfigPath)
|
|
63
|
+
? fs.readFileSync(fishConfigPath, 'utf-8')
|
|
64
|
+
: ''
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
!fishContent.includes('Import environment variables from .bashrc - START')
|
|
68
|
+
) {
|
|
69
|
+
const fishScript = getFishImportBashExports()
|
|
70
|
+
fs.appendFileSync(fishConfigPath, fishScript)
|
|
71
|
+
console.log(`Fish import script added to: ${fishConfigPath}`)
|
|
72
|
+
} else {
|
|
73
|
+
console.log(`Fish import script already exists in: ${fishConfigPath}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gengjiawen/os-init",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"postbuild": "cpy '**/*' '!**/*.ts' ../build/ --cwd=libs --parents"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@
|
|
22
|
+
"@gengjiawen/unzip-url": "^1.0.0",
|
|
23
23
|
"commander": "^12.1.0",
|
|
24
24
|
"ip": "^2.0.1",
|
|
25
25
|
"execa": "^8.0.1"
|