diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a077b26 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +# Ignore node_modules directory +node_modules + +# Ignore npm debug logs +npm-debug.log + +# Ignore yarn debug logs +yarn-debug.log +yarn-error.log + +# Ignore .env file (contains sensitive information) +.env + +# Ignore any build artifacts or compiled code +dist +build +out + +# Ignore any temporary files or caches +*.tmp +*.swp +*.swo +*.bak +*.cache +*.log diff --git a/Dockerfile b/Dockerfile index c636d69..1c82314 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,32 @@ FROM ubuntu:20.04 +RUN apt-get update && apt-get install -y tzdata +ENV TZ=Asia/Taipei +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +ARG USERNAME=user RUN apt-get update && \ apt-get upgrade -y && \ - apt-get install -y curl gnupg2 + apt-get install -y curl gnupg2 git + +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y sudo \ + -o Dpkg::Options::="--force-confold" + +RUN useradd -ms /bin/bash ${USERNAME} && \ + echo "${USERNAME}:123456" | chpasswd && \ + echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER ${USERNAME} +WORKDIR /home/${USERNAME} -RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ - apt-get install -y nodejs +RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 +ENV PATH="/home/${USERNAME}/.asdf/bin:/home/${USERNAME}/.asdf/shims:$PATH" +RUN echo ". $HOME/.asdf/asdf.sh" >> ~/.bashrc && \ + echo ". $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc +RUN /bin/bash -c ". $HOME/.asdf/asdf.sh && \ + asdf plugin-add nodejs && \ + asdf install nodejs 18.18.0 && \ + asdf global nodejs 18.18.0" COPY . . diff --git a/packageData.json b/packageData.json index 00f57d3..e6d328b 100644 --- a/packageData.json +++ b/packageData.json @@ -140,14 +140,14 @@ "install": { "linux": "asdf plugin add nodejs && asdf install nodejs 16.20.2 && asdf global nodejs 16.20.2", "mac": "asdf plugin add nodejs && asdf install nodejs 18.18.0 && asdf global nodejs 18.18.0", - "windows": "choco install nvm -y; nvm install 18.18.0; nvm use 18.18.0" + "windows": "choco install nvm -y ; nvm install 18.18.0 ; nvm use 18.18.0" }, "type": "force" }, "OpenSSH": { "description": "", "install": { - "linux": "sudo apt install openssh-server -y", + "linux": "sudo apt install openssh-server -y && mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys", "mac": "", "windows": "" }, @@ -234,6 +234,24 @@ }, "type": "disable" }, + "TypeScript": { + "description": "", + "install": { + "linux": "npm install -g typescript ts-node", + "mac": "npm install -g typescript ts-node", + "windows": "npm install -g typescript ts-node" + }, + "type": "enable" + }, + "VSCode Extension Development": { + "description": "VSCode 擴充套件開發工具", + "install": { + "linux": "npm install -g yo generator-code vsce esbuild", + "mac": "npm install -g yo generator-code vsce esbuild", + "windows": "npm install -g yo generator-code vsce esbuild" + }, + "type": "disable" + }, "Virtual Box": { "description": "自由及開放原始碼的虛擬機器軟體", "install": { @@ -510,7 +528,7 @@ "description": "", "install": { "linux": "", - "mac": "brew install --cask microsoft-edge", + "mac": "brew install --cask microsoft-edge && echo 'export CHROME_EXECUTABLE=\"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"' >>~/.zshrc", "windows": "" }, "type": "enable" diff --git a/scripts/linux/.gitkeep b/scripts/linux/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/linux/basesetup.sh b/scripts/linux/basesetup.sh new file mode 100644 index 0000000..4d49876 --- /dev/null +++ b/scripts/linux/basesetup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo apt install -y curl software-properties-common apt-transport-https lsb-release +sudo apt update && sudo apt upgrade -y && sudo apt install -y git diff --git a/scripts/macos/.gitkeep b/scripts/macos/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/macos/homebrew.sh b/scripts/macos/homebrew.sh new file mode 100644 index 0000000..66c470d --- /dev/null +++ b/scripts/macos/homebrew.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +( + echo + echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' +) >>~/.zprofile +eval "$(/opt/homebrew/bin/brew shellenv)" +source ~/.zprofile diff --git a/scripts/macos/macsetup.sh b/scripts/macos/macsetup.sh new file mode 100644 index 0000000..34c589f --- /dev/null +++ b/scripts/macos/macsetup.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Reset Launchpad +# ////////////////////////////////////////////////// +defaults write com.apple.dock ResetLaunchPad -bool true +killall Dock + +# Display full path in Finder title bar +# ////////////////////////////////////////////////// +defaults write com.apple.finder _FXShowPosixPathInTitle -bool true + +# Show path bar in Finder +# ////////////////////////////////////////////////// +defaults write com.apple.finder ShowPathbar -bool true diff --git a/scripts/macos/zshsetup.sh b/scripts/macos/zshsetup.sh new file mode 100644 index 0000000..8887a9f --- /dev/null +++ b/scripts/macos/zshsetup.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "# Load version control information" >>~/.zshrc +echo "autoload -Uz vcs_info" >>~/.zshrc +echo "precmd() { vcs_info }" >>~/.zshrc +echo "" >>~/.zshrc +echo "# Format the vcs_info_msg_0_ variable" >>~/.zshrc +echo "zstyle ':vcs_info:git:*' formats ' %F{87}(%b)%f'" >>~/.zshrc +echo "" >>~/.zshrc +echo "# Set up the prompt (with git branch name)" >>~/.zshrc +echo "setopt PROMPT_SUBST" >>~/.zshrc +echo "PROMPT='[%F{82}%~%f]\${vcs_info_msg_0_} $ '" >>~/.zshrc +echo "" >>~/.zshrc +source ~/.zshrc diff --git a/scripts/npm.setup b/scripts/npm.setup index e76180b..71ad725 100644 --- a/scripts/npm.setup +++ b/scripts/npm.setup @@ -1,4 +1,4 @@ -npm install -g npm +npm install -g npm@latest npm install -g pnpm npm install -g npm-check-updates npm install -g @leoli0605/git-setup diff --git a/scripts/windows/.gitkeep b/scripts/windows/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/base_shell.mjs b/src/base_shell.mjs new file mode 100644 index 0000000..adad600 --- /dev/null +++ b/src/base_shell.mjs @@ -0,0 +1,23 @@ +class BaseShell { + constructor() { + this.commands = []; + this.environment = []; + this.scripts = []; + } + + addCommand(command) { + this.commands.push(command); + console.log(`Command added: ${command}`); + } + + addEnvironment(path) { + this.environment.push(path); + console.log(`Environment added: ${path}`); + } + + script() { + throw new Error('You have to implement the method script!'); + } +} + +export default BaseShell; diff --git a/src/bash.mjs b/src/bash.mjs new file mode 100644 index 0000000..6fe36b2 --- /dev/null +++ b/src/bash.mjs @@ -0,0 +1,31 @@ +import BaseShell from './base_shell.mjs'; + +class Bash extends BaseShell { + constructor(type = 'bash') { + super(); + this.type = type; + } + + script() { + for (const e of this.environment) { + if (this.type === 'bash') { + this.scripts.push(`echo ${e} >> ~/.bashrc\n`); + } else if (this.type === 'zsh') { + this.scripts.push(`echo ${e} >> ~/.zshrc\n`); + } + } + this.scripts.push('source ~/.bashrc\n'); + this.scripts.push('source ~/.zshrc\n'); + this.scripts.push(this.commands.join('\n')); + return ( + '#!/bin/bash\n' + + this.scripts + .toString() + .replace(/(^,)/gm, '') + .replace(/#!\/bin\/bash/g, '') + + '\n' + ); + } +} + +export default Bash; diff --git a/src/index.mjs b/src/index.mjs index b16c911..7a62039 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,7 +1,7 @@ import fs from 'fs'; import os from 'os'; -import path, { dirname } from 'path'; -import { fileURLToPath } from 'url'; +import path from 'path'; +import Bash from './bash.mjs'; import { cmd } from './cmd_process.mjs'; import { getAppDir } from './dirname.mjs'; import { selectPackages } from './package_selector.mjs'; @@ -11,53 +11,97 @@ const __dirname = getAppDir(); selectPackages() .then((selectedPackages) => { + let shell; let command = ''; let args = []; if (os.platform() === 'win32') { - const ps = new PowerShell(); + console.log('Windows detected'); + shell = new PowerShell(); + for (const p of selectedPackages) { if (p) { - ps.addCommand(p.installCommand); + shell.addCommand(p.installCommand); } } - ps.addCommand('refreshenv\n'); // for powershell to refresh environment variables + shell.addCommand('refreshenv\n'); if (selectedPackages.some((p) => p && p.packageName.startsWith('Python'))) { - ps.addEnvironment('$HOME\\AppData\\Roaming\\Python\\Scripts'); - ps.addCommand('python.exe -m pip install --upgrade pip'); // for pip - ps.addCommand('(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -'); // for poetry - ps.addCommand('poetry config virtualenvs.in-project true'); // for poetry - const pipSetup = fs.readFileSync(path.join(__dirname, 'scripts/pip.setup'), 'utf8').trim(); - ps.addCommand(pipSetup); + shell.addEnvironment('$HOME\\AppData\\Roaming\\Python\\Scripts'); + shell.addCommand('(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -'); + shell.addCommand('poetry config virtualenvs.in-project true'); + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/pip.setup'), 'utf8').trim()); } if (selectedPackages.some((p) => p && p.packageName.startsWith('Rust'))) { - ps.addEnvironment('$HOME\\leoli\\.cargo\\bin'); + shell.addEnvironment('$HOME\\.cargo\\bin'); } - ps.addCommand('refreshenv\n'); // for powershell to refresh environment variables + shell.addCommand('refreshenv\n'); if (selectedPackages.some((p) => p && p.packageName.startsWith('Node.js'))) { - const npmSetup = fs.readFileSync(path.join(__dirname, 'scripts/npm.setup'), 'utf8').trim(); - ps.addCommand(npmSetup); - ps.addCommand('npx @leoli0605/git-setup'); // for git + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/npm.setup'), 'utf8').trim()); } - ps.addCommand('refreshenv\n'); // for powershell to refresh environment variables + shell.addCommand('refreshenv\n'); if (selectedPackages.some((p) => p && p.packageName.startsWith('Sublime Text'))) { - let sublimeSetup = fs.readFileSync(path.join(__dirname, 'scripts/windows/SublimeSetup.ps1'), 'utf8').trim(); - sublimeSetup = sublimeSetup.replace('${PATH}', path.join(__dirname, 'scripts/windows/Preferences.sublime-settings')); - ps.addCommand(sublimeSetup); + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/windows/SublimeSetup.ps1'), 'utf8').trim().replace('${PATH}', path.join(__dirname, 'scripts/windows/Preferences.sublime-settings'))); } - ps.addCommand('refreshenv\n'); // for powershell to refresh environment variables - - const psScript = ps.getScripts(); - // fs.writeFileSync(path.join(__dirname, 'scripts.ps1.log'), psScript); + shell.addCommand('refreshenv\n'); + shell.addCommand('npx @leoli0605/git-setup'); + const script = shell.script(); - const encodedPsScript = Buffer.from(psScript, 'utf16le').toString('base64'); + const encodedScript = Buffer.from(script, 'utf16le').toString('base64'); command = 'powershell.exe'; - args = ['-NoProfile', '-EncodedCommand', encodedPsScript]; + args = ['-NoProfile', '-EncodedCommand', encodedScript]; } else if (os.platform() === 'darwin') { console.log('MacOS detected'); + shell = new Bash('zsh'); + + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/macos/homebrew.sh'), 'utf8').trim()); + shell.addCommand('brew install asdf'); + shell.addEnvironment('". /opt/homebrew/opt/asdf/libexec/asdf.sh"'); + + for (const p of selectedPackages) { + if (p) { + shell.addCommand(p.installCommand); + } + } + + if (selectedPackages.some((p) => p && p.packageName.startsWith('Python'))) { + shell.addCommand('curl -sSL https://install.python-poetry.org | python3'); + shell.addEnvironment('\'export PATH="$HOME/.local/bin:$PATH"\''); + shell.addCommand('poetry config virtualenvs.in-project true'); + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/pip.setup'), 'utf8').trim()); + } + if (selectedPackages.some((p) => p && p.packageName.startsWith('Node.js'))) { + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/npm.setup'), 'utf8').trim()); + } + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/macos/zshsetup.sh'), 'utf8').trim()); + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/macos/macsetup.sh'), 'utf8').trim()); + shell.addCommand('npx @leoli0605/git-setup'); + const script = shell.script(); + + command = 'sh'; + args = ['-c', script]; } else if (os.platform() === 'linux') { + shell = new Bash('bash'); + + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/linux/basesetup.sh'), 'utf8').trim()); + shell.addCommand('git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0'); + shell.addEnvironment('\'. "$HOME/.asdf/asdf.sh"\''); + shell.addEnvironment('\'. "$HOME/.asdf/completions/asdf.bash"\''); + + for (const p of selectedPackages) { + if (p) { + shell.addCommand(p.installCommand); + } + } + + if (selectedPackages.some((p) => p && p.packageName.startsWith('Python'))) { + shell.addCommand('curl -sSL https://install.python-poetry.org | python3'); + shell.addEnvironment('\'export PATH="$HOME/.local/bin:$PATH"\''); + shell.addCommand('poetry config virtualenvs.in-project true'); + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/pip.setup'), 'utf8').trim()); + } + const distro = os .release() .split('.') @@ -65,9 +109,20 @@ selectPackages() console.log('Linux distribution version:', distro); if (distro[0] >= 20 /* && distro[1] === 4 */) { console.log('Ubuntu 20.04 detected'); + shell.addCommand('asdf install nodejs 18.18.0'); + shell.addCommand('asdf global nodejs 18.18.0'); } else { console.log('Ubuntu 20.04 not detected'); } + + if (selectedPackages.some((p) => p && p.packageName.startsWith('Node.js'))) { + shell.addCommand(fs.readFileSync(path.join(__dirname, 'scripts/npm.setup'), 'utf8').trim()); + } + shell.addCommand('npx @leoli0605/git-setup'); + const script = shell.script(); + + command = 'sh'; + args = ['-c', script]; } console.log(`command: ${command}`); diff --git a/src/package_selector.mjs b/src/package_selector.mjs index e1a58c8..e008951 100644 --- a/src/package_selector.mjs +++ b/src/package_selector.mjs @@ -2,7 +2,7 @@ import chalk from 'chalk'; import fs from 'fs'; import inquirer from 'inquirer'; import os from 'os'; -import path, { dirname } from 'path'; +import path from 'path'; import { getAppDir } from './dirname.mjs'; export async function selectPackages() { diff --git a/src/powershell.mjs b/src/powershell.mjs index 72dc03d..528cfda 100644 --- a/src/powershell.mjs +++ b/src/powershell.mjs @@ -1,19 +1,18 @@ import fs from 'fs'; -import path, { dirname } from 'path'; -import { fileURLToPath } from 'url'; +import path from 'path'; +import BaseShell from './base_shell.mjs'; import { getAppDir } from './dirname.mjs'; -import ShellScript from './shell_script.mjs'; const __dirname = getAppDir(); -class PowerShell extends ShellScript { +class PowerShell extends BaseShell { constructor() { super(); this.scripts.push(fs.readFileSync(path.join(__dirname, 'scripts/windows/RunAsAdministrator.ps1'), 'utf8') + '\n'); this.scripts.push(fs.readFileSync(path.join(__dirname, 'scripts/windows/Chocolatey.ps1'), 'utf8') + '\n'); } - getScripts() { + script() { const paths = this.environment.map((e) => `"${e}"`).join(',\n '); let envSetup = fs.readFileSync(path.join(__dirname, 'scripts/windows/EnvSetup.ps1'), 'utf8'); envSetup = envSetup.replace('${PATHS}', paths); diff --git a/src/shell_script.mjs b/src/shell_script.mjs deleted file mode 100644 index 2c6f343..0000000 --- a/src/shell_script.mjs +++ /dev/null @@ -1,23 +0,0 @@ -class ShellScript { - constructor() { - this.commands = []; - this.environment = []; - this.scripts = []; - } - - addCommand(command) { - this.commands.push(command); - console.log(`Command added: ${command}`); - } - - addEnvironment(value) { - this.environment.push(value); - console.log(`Environment added: ${value}`); - } - - getScripts() { - throw new Error('You have to implement the method getScripts!'); - } -} - -export default ShellScript;