@tertium/hlpr 0.3.5 → 0.5.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/README.md +11 -1
- package/bin/commands/deploy/deploy/deploy.sh +41 -0
- package/bin/commands/deploy/deploy.js +93 -0
- package/bin/commands/deploy/deploy.service.js +111 -0
- package/bin/commands/deploy/deploy.sh +41 -0
- package/bin/commands/deploy/deploy.types.js +0 -0
- package/bin/commands/file/rename/rename.js +82 -3
- package/bin/commands/git/fodd.sh +3 -0
- package/bin/commands/git/precommit.sh +3 -0
- package/bin/commands/git/switch-clean.sh +68 -0
- package/bin/commands/hello/world.sh +3 -0
- package/bin/commands/help/help.js +0 -0
- package/bin/commands/nvm/install.sh +3 -0
- package/bin/commands/nvm/lts.sh +3 -0
- package/bin/commands/ssh/init-dir.sh +11 -1
- package/bin/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI utility for running shell scripts with variable substitution and TypeScript-based commands.
|
|
4
4
|
|
|
5
|
+
## Platform Support
|
|
6
|
+
|
|
7
|
+
hlpr works on all major operating systems:
|
|
8
|
+
|
|
9
|
+
- ✅ **Linux** - Fully supported
|
|
10
|
+
- ✅ **macOS** - Fully supported
|
|
11
|
+
- ✅ **Windows** - Requires [Git for Windows](https://git-scm.com/download/win) (includes Git Bash)
|
|
12
|
+
|
|
13
|
+
**Note for Windows users:** Shell script commands (`.sh` files) require Bash, which is included with Git for Windows. TypeScript commands work on all platforms.
|
|
14
|
+
|
|
5
15
|
## Installation
|
|
6
16
|
|
|
7
17
|
```bash
|
|
@@ -186,7 +196,7 @@ git clone https://github.com/tertiumnon/hlpr.git
|
|
|
186
196
|
npm install
|
|
187
197
|
|
|
188
198
|
# Build the project
|
|
189
|
-
|
|
199
|
+
bun run build
|
|
190
200
|
|
|
191
201
|
# Link for local development
|
|
192
202
|
npm link
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Deploy application to remote server with environment variables
|
|
3
|
+
|
|
4
|
+
SKIP_BUILD="{{SKIP_BUILD}}"
|
|
5
|
+
DEPLOY_USER="{{DEPLOY_USER}}"
|
|
6
|
+
DEPLOY_HOST="{{DEPLOY_HOST}}"
|
|
7
|
+
DEPLOY_PATH="{{DEPLOY_PATH}}"
|
|
8
|
+
APP_NAME="{{APP_NAME}}"
|
|
9
|
+
|
|
10
|
+
echo "Deploying to $DEPLOY_HOST:$DEPLOY_PATH"
|
|
11
|
+
echo "=========================================="
|
|
12
|
+
|
|
13
|
+
# Build phase (unless skip-build is set)
|
|
14
|
+
if [ "$SKIP_BUILD" != "true" ]; then
|
|
15
|
+
echo "Building..."
|
|
16
|
+
bun run build
|
|
17
|
+
if [ $? -ne 0 ]; then
|
|
18
|
+
echo "✗ Build failed"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Deploy phase
|
|
24
|
+
echo ""
|
|
25
|
+
echo "Copying files to remote server..."
|
|
26
|
+
scp -r dist/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/
|
|
27
|
+
if [ $? -ne 0 ]; then
|
|
28
|
+
echo "✗ SCP failed"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo ""
|
|
33
|
+
echo "Installing dependencies and restarting service..."
|
|
34
|
+
ssh $DEPLOY_USER@$DEPLOY_HOST "cd $DEPLOY_PATH && bun install --production && pm2 restart $APP_NAME --update-env"
|
|
35
|
+
if [ $? -ne 0 ]; then
|
|
36
|
+
echo "✗ Remote deployment failed"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ Deployment complete!"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/deploy/deploy.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
9
|
+
var __dirname2 = path.dirname(__filename2);
|
|
10
|
+
function loadEnv(projectDir) {
|
|
11
|
+
const envPath = path.join(projectDir, ".env");
|
|
12
|
+
if (!existsSync(envPath)) {
|
|
13
|
+
console.error("Error: .env file not found");
|
|
14
|
+
console.error(`Please copy .env.example to .env in ${projectDir}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const env = {};
|
|
18
|
+
const content = readFileSync(envPath, "utf-8");
|
|
19
|
+
content.split(`
|
|
20
|
+
`).forEach((line) => {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
23
|
+
return;
|
|
24
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
25
|
+
if (key && valueParts.length > 0) {
|
|
26
|
+
env[key.trim()] = valueParts.join("=").trim();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return env;
|
|
30
|
+
}
|
|
31
|
+
function validate(env) {
|
|
32
|
+
const required = ["DEPLOY_USER", "DEPLOY_HOST", "DEPLOY_PATH", "APP_NAME"];
|
|
33
|
+
const missing = required.filter((key) => !env[key]);
|
|
34
|
+
if (missing.length > 0) {
|
|
35
|
+
console.error(`Error: Missing environment variables: ${missing.join(", ")}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function executeScript(scriptContent, variables, cwd) {
|
|
40
|
+
let script = scriptContent;
|
|
41
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
42
|
+
script = script.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
43
|
+
}
|
|
44
|
+
const lines = script.split(`
|
|
45
|
+
`);
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("#!/"))
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
console.log(`→ ${trimmed}`);
|
|
52
|
+
execSync(trimmed, {
|
|
53
|
+
stdio: "inherit",
|
|
54
|
+
cwd,
|
|
55
|
+
shell: true
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`
|
|
59
|
+
✗ Command failed: ${trimmed}
|
|
60
|
+
`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function deploy() {
|
|
66
|
+
const skipBuild = process.argv[2] === "skip-build";
|
|
67
|
+
const projectDir = process.cwd();
|
|
68
|
+
const env = loadEnv(projectDir);
|
|
69
|
+
validate(env);
|
|
70
|
+
const scriptPath = path.join(__dirname2, "deploy.sh");
|
|
71
|
+
if (!existsSync(scriptPath)) {
|
|
72
|
+
console.error(`Error: Deploy script not found at ${scriptPath}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const scriptContent = readFileSync(scriptPath, "utf-8");
|
|
76
|
+
const variables = {
|
|
77
|
+
...env,
|
|
78
|
+
SKIP_BUILD: skipBuild ? "true" : "false"
|
|
79
|
+
};
|
|
80
|
+
console.log(`
|
|
81
|
+
Deploying to ${env.DEPLOY_HOST}:${env.DEPLOY_PATH}`);
|
|
82
|
+
console.log(`==========================================
|
|
83
|
+
`);
|
|
84
|
+
try {
|
|
85
|
+
executeScript(scriptContent, variables, projectDir);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`
|
|
88
|
+
✗ Deployment failed: ${error.message}
|
|
89
|
+
`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
deploy();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/commands/deploy/deploy.service.ts
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
class DeployService {
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
log(message) {
|
|
11
|
+
console.log(`[${new Date().toISOString()}] ${message}`);
|
|
12
|
+
}
|
|
13
|
+
async copyToServer() {
|
|
14
|
+
this.log("Copying dist folder to remote server...");
|
|
15
|
+
const { remoteHost, remoteUser, deployPath, localDist, envFile } = this.config;
|
|
16
|
+
if (!fs.existsSync(localDist)) {
|
|
17
|
+
throw new Error(`Local dist folder not found: ${localDist}`);
|
|
18
|
+
}
|
|
19
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
20
|
+
const tempPath = `${deployPath}_tmp`;
|
|
21
|
+
try {
|
|
22
|
+
this.log(`Creating temp directory on remote: ${tempPath}`);
|
|
23
|
+
execSync(`ssh ${sshHost} "mkdir -p ${tempPath} && rm -rf ${tempPath}/*"`, { stdio: "inherit" });
|
|
24
|
+
this.log(`Copying ${localDist} to ${sshHost}:${tempPath}/`);
|
|
25
|
+
execSync(`scp -r "${localDist}" ${sshHost}:${tempPath}/dist`, {
|
|
26
|
+
stdio: "inherit"
|
|
27
|
+
});
|
|
28
|
+
if (envFile && fs.existsSync(envFile)) {
|
|
29
|
+
this.log(`Copying ${envFile} to remote`);
|
|
30
|
+
execSync(`scp "${envFile}" ${sshHost}:${tempPath}/.env`, {
|
|
31
|
+
stdio: "inherit"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (fs.existsSync("package.json")) {
|
|
35
|
+
this.log("Copying package.json to remote");
|
|
36
|
+
execSync(`scp package.json ${sshHost}:${tempPath}/`, {
|
|
37
|
+
stdio: "inherit"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
this.log(`Moving files from temp to deploy path...`);
|
|
41
|
+
const moveCmd = `rm -rf ${deployPath}/dist && ` + `mv ${tempPath}/dist ${deployPath}/ && ` + `[ -f ${tempPath}/.env ] && mv ${tempPath}/.env ${deployPath}/.env || true && ` + `[ -f ${tempPath}/package.json ] && mv ${tempPath}/package.json ${deployPath}/ || true && ` + `rm -rf ${tempPath}`;
|
|
42
|
+
execSync(`ssh ${sshHost} "${moveCmd}"`, { stdio: "inherit" });
|
|
43
|
+
this.log("✓ Files copied successfully");
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Failed to copy files: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async installDependencies() {
|
|
49
|
+
if (this.config.skipInstall) {
|
|
50
|
+
this.log("Skipping dependency installation");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.log("Installing dependencies on server...");
|
|
54
|
+
const { remoteHost, remoteUser, deployPath } = this.config;
|
|
55
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
56
|
+
try {
|
|
57
|
+
const cmd = `cd ${deployPath} && bun install --production`;
|
|
58
|
+
execSync(`ssh ${sshHost} "${cmd}"`, { stdio: "inherit" });
|
|
59
|
+
this.log("✓ Dependencies installed");
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.log("⚠ Dependency installation skipped or failed");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async restartProcess() {
|
|
65
|
+
if (this.config.skipRestart) {
|
|
66
|
+
this.log("Skipping process restart");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.log("Restarting application via PM2...");
|
|
70
|
+
const { remoteHost, remoteUser, deployPath, appName, port } = this.config;
|
|
71
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
72
|
+
try {
|
|
73
|
+
const portOpt = port ? ` --env PORT=${port}` : "";
|
|
74
|
+
const cmd = `cd ${deployPath} && ` + `(pm2 restart ${appName} --update-env 2>/dev/null || ` + `pm2 start dist/index.js --name ${appName}${portOpt} 2>/dev/null) && ` + `pm2 save 2>/dev/null || true`;
|
|
75
|
+
execSync(`ssh ${sshHost} "bash -l -c '${cmd}'"`, { stdio: "inherit" });
|
|
76
|
+
this.log("✓ Process restart attempted");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.log("⚠ PM2 restart skipped - may need manual setup");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async deploy() {
|
|
82
|
+
const startTime = new Date().toISOString();
|
|
83
|
+
try {
|
|
84
|
+
this.log("Starting deployment...");
|
|
85
|
+
await this.copyToServer();
|
|
86
|
+
await this.installDependencies();
|
|
87
|
+
await this.restartProcess();
|
|
88
|
+
this.log("✅ Deployment completed successfully");
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
message: "Deployment completed successfully",
|
|
92
|
+
timestamp: startTime
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
96
|
+
this.log(`❌ Deployment failed: ${message}`);
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
message,
|
|
100
|
+
timestamp: startTime
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function createDeployService(config) {
|
|
106
|
+
return new DeployService(config);
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createDeployService,
|
|
110
|
+
DeployService
|
|
111
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Deploy application to remote server with environment variables
|
|
3
|
+
|
|
4
|
+
SKIP_BUILD="{{SKIP_BUILD}}"
|
|
5
|
+
DEPLOY_USER="{{DEPLOY_USER}}"
|
|
6
|
+
DEPLOY_HOST="{{DEPLOY_HOST}}"
|
|
7
|
+
DEPLOY_PATH="{{DEPLOY_PATH}}"
|
|
8
|
+
APP_NAME="{{APP_NAME}}"
|
|
9
|
+
|
|
10
|
+
echo "Deploying to $DEPLOY_HOST:$DEPLOY_PATH"
|
|
11
|
+
echo "=========================================="
|
|
12
|
+
|
|
13
|
+
# Build phase (unless skip-build is set)
|
|
14
|
+
if [ "$SKIP_BUILD" != "true" ]; then
|
|
15
|
+
echo "Building..."
|
|
16
|
+
bun run build
|
|
17
|
+
if [ $? -ne 0 ]; then
|
|
18
|
+
echo "✗ Build failed"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Deploy phase
|
|
24
|
+
echo ""
|
|
25
|
+
echo "Copying files to remote server..."
|
|
26
|
+
scp -r dist/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/
|
|
27
|
+
if [ $? -ne 0 ]; then
|
|
28
|
+
echo "✗ SCP failed"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo ""
|
|
33
|
+
echo "Installing dependencies and restarting service..."
|
|
34
|
+
ssh $DEPLOY_USER@$DEPLOY_HOST "cd $DEPLOY_PATH && bun install --production && pm2 restart $APP_NAME --update-env"
|
|
35
|
+
if [ $? -ne 0 ]; then
|
|
36
|
+
echo "✗ Remote deployment failed"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ Deployment complete!"
|
|
File without changes
|
|
@@ -87,8 +87,61 @@ async function safeRename(oldPath, newPath) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
async function updateFileReferences(filePath, renames) {
|
|
91
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
92
|
+
let updated = content;
|
|
93
|
+
for (const { from, to } of renames) {
|
|
94
|
+
const fromBasename = path.basename(from);
|
|
95
|
+
const toBasename = path.basename(to);
|
|
96
|
+
if (fromBasename === toBasename)
|
|
97
|
+
continue;
|
|
98
|
+
const fromWithoutExt = fromBasename.replace(/\.[^.]+$/, "");
|
|
99
|
+
const toWithoutExt = toBasename.replace(/\.[^.]+$/, "");
|
|
100
|
+
const importPattern1 = new RegExp(`(['"\`])([./]*(?:[^'"\`]*/)?)${escapeRegex(fromWithoutExt)}\\1`, "g");
|
|
101
|
+
updated = updated.replace(importPattern1, (_match, quote, pathPart) => {
|
|
102
|
+
return `${quote}${pathPart}${toWithoutExt}${quote}`;
|
|
103
|
+
});
|
|
104
|
+
const importPattern2 = new RegExp(`(['"\`])([./]*(?:[^'"\`]*/)?)${escapeRegex(fromBasename)}\\1`, "g");
|
|
105
|
+
updated = updated.replace(importPattern2, (_match, quote, pathPart) => {
|
|
106
|
+
return `${quote}${pathPart}${toBasename}${quote}`;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (updated !== content) {
|
|
110
|
+
await fs.writeFile(filePath, updated, "utf-8");
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function escapeRegex(str) {
|
|
116
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117
|
+
}
|
|
118
|
+
async function isTextFile(filePath) {
|
|
119
|
+
try {
|
|
120
|
+
const fd = await fs.open(filePath, "r");
|
|
121
|
+
const buffer = Buffer.alloc(512);
|
|
122
|
+
const { bytesRead } = await fd.read(buffer, 0, 512, 0);
|
|
123
|
+
await fd.close();
|
|
124
|
+
if (bytesRead === 0)
|
|
125
|
+
return true;
|
|
126
|
+
for (let i = 0;i < bytesRead; i++) {
|
|
127
|
+
if (buffer[i] === 0)
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
let printableCount = 0;
|
|
131
|
+
for (let i = 0;i < bytesRead; i++) {
|
|
132
|
+
const byte = buffer[i];
|
|
133
|
+
if (byte >= 32 && byte <= 126 || byte === 9 || byte === 10 || byte === 13) {
|
|
134
|
+
printableCount++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return printableCount / bytesRead > 0.85;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
90
142
|
async function renameRecursive(root, style = "title_underscore", options = {}) {
|
|
91
143
|
const performed = [];
|
|
144
|
+
const updateContentEnabled = options.updateContent ?? true;
|
|
92
145
|
async function walk(current) {
|
|
93
146
|
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
94
147
|
for (const e of entries) {
|
|
@@ -126,6 +179,28 @@ async function renameRecursive(root, style = "title_underscore", options = {}) {
|
|
|
126
179
|
}
|
|
127
180
|
}
|
|
128
181
|
await walk(root);
|
|
182
|
+
if (!options.dryRun && updateContentEnabled && performed.length > 0) {
|
|
183
|
+
const filesToUpdate = [];
|
|
184
|
+
async function collectFiles(dir) {
|
|
185
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
186
|
+
for (const e of entries) {
|
|
187
|
+
const full = path.join(dir, e.name);
|
|
188
|
+
if (e.isFile()) {
|
|
189
|
+
filesToUpdate.push(full);
|
|
190
|
+
} else if (e.isDirectory()) {
|
|
191
|
+
await collectFiles(full);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
await collectFiles(root);
|
|
196
|
+
for (const file of filesToUpdate) {
|
|
197
|
+
try {
|
|
198
|
+
if (await isTextFile(file)) {
|
|
199
|
+
await updateFileReferences(file, performed);
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
129
204
|
return performed;
|
|
130
205
|
}
|
|
131
206
|
if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
|
|
@@ -133,17 +208,21 @@ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
|
|
|
133
208
|
const rootArg = args[0];
|
|
134
209
|
const styleArg = args[1];
|
|
135
210
|
if (args.includes("--help") || args.includes("-h") || args.includes("/help") || args.includes("/h") || args.includes("/?")) {
|
|
136
|
-
console.log("Usage: rename <root> <style> [--dry|-n]");
|
|
211
|
+
console.log("Usage: rename <root> <style> [--dry|-n] [--no-update-content]");
|
|
137
212
|
console.log("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
|
|
213
|
+
console.log("Options:");
|
|
214
|
+
console.log(" --dry, -n Preview changes without applying them");
|
|
215
|
+
console.log(" --no-update-content Skip updating import/require statements in files");
|
|
138
216
|
process.exit(0);
|
|
139
217
|
}
|
|
140
218
|
const dryRun = args.includes("--dry") || args.includes("-n");
|
|
219
|
+
const updateContent = !args.includes("--no-update-content");
|
|
141
220
|
if (!rootArg || !styleArg) {
|
|
142
|
-
console.error("Usage: rename <root> <style> [--dry|-n]");
|
|
221
|
+
console.error("Usage: rename <root> <style> [--dry|-n] [--no-update-content]");
|
|
143
222
|
console.error("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
|
|
144
223
|
process.exit(1);
|
|
145
224
|
}
|
|
146
|
-
renameRecursive(rootArg, styleArg, { dryRun }).then((performed) => {
|
|
225
|
+
renameRecursive(rootArg, styleArg, { dryRun, updateContent }).then((performed) => {
|
|
147
226
|
if (dryRun) {
|
|
148
227
|
console.log(`Dry run - would rename ${performed.length} items:`);
|
|
149
228
|
} else {
|
package/bin/commands/git/fodd.sh
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Safely switch to a branch and delete the previous one (local & remote)
|
|
3
|
+
|
|
4
|
+
# Git switch and clean: Safely switch to a branch and delete the old one
|
|
5
|
+
# Usage: switch-clean <target-branch>
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# Get target branch from argument
|
|
10
|
+
TARGET_BRANCH="$1"
|
|
11
|
+
|
|
12
|
+
if [ -z "$TARGET_BRANCH" ]; then
|
|
13
|
+
echo "Error: Target branch is required"
|
|
14
|
+
echo "Usage: switch-clean <target-branch>"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Get current branch
|
|
19
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
20
|
+
|
|
21
|
+
if [ -z "$CURRENT_BRANCH" ]; then
|
|
22
|
+
echo "Error: Not on a branch (detached HEAD)"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Check if already on target branch
|
|
27
|
+
if [ "$CURRENT_BRANCH" = "$TARGET_BRANCH" ]; then
|
|
28
|
+
echo "Error: Already on branch '$TARGET_BRANCH'"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check for uncommitted changes
|
|
33
|
+
if ! git diff-index --quiet HEAD --; then
|
|
34
|
+
echo "Error: You have uncommitted changes. Please commit or stash them first."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Check if current branch has unpushed commits
|
|
39
|
+
UNPUSHED=$(git log @{u}.. --oneline 2>/dev/null || echo "")
|
|
40
|
+
if [ -n "$UNPUSHED" ]; then
|
|
41
|
+
echo "Error: Current branch '$CURRENT_BRANCH' has unpushed commits:"
|
|
42
|
+
echo "$UNPUSHED"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Fetch target branch from origin
|
|
47
|
+
echo "Fetching $TARGET_BRANCH from origin..."
|
|
48
|
+
git fetch origin "$TARGET_BRANCH:$TARGET_BRANCH" 2>/dev/null || {
|
|
49
|
+
echo "Error: Failed to fetch branch '$TARGET_BRANCH' from origin"
|
|
50
|
+
exit 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Checkout to target branch
|
|
54
|
+
echo "Switching to $TARGET_BRANCH..."
|
|
55
|
+
git checkout "$TARGET_BRANCH"
|
|
56
|
+
|
|
57
|
+
# Delete local branch
|
|
58
|
+
echo "Deleting local branch $CURRENT_BRANCH..."
|
|
59
|
+
git branch -d "$CURRENT_BRANCH"
|
|
60
|
+
|
|
61
|
+
# Delete remote branch if it exists
|
|
62
|
+
if git ls-remote --exit-code --heads origin "$CURRENT_BRANCH" > /dev/null 2>&1; then
|
|
63
|
+
echo "Deleting remote branch $CURRENT_BRANCH..."
|
|
64
|
+
git push origin --delete "$CURRENT_BRANCH"
|
|
65
|
+
echo "✓ Switched to '$TARGET_BRANCH' and deleted '$CURRENT_BRANCH' (local & remote)"
|
|
66
|
+
else
|
|
67
|
+
echo "✓ Switched to '$TARGET_BRANCH' and deleted '$CURRENT_BRANCH' (local only)"
|
|
68
|
+
fi
|
|
File without changes
|
package/bin/commands/nvm/lts.sh
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Initialize SSH directory with proper permissions
|
|
3
|
+
|
|
4
|
+
# Create .ssh directory if it doesn't exist
|
|
5
|
+
mkdir -p ~/.ssh
|
|
6
|
+
|
|
7
|
+
# Create files if they don't exist
|
|
2
8
|
touch ~/.ssh/known_hosts
|
|
3
9
|
touch ~/.ssh/config
|
|
10
|
+
|
|
11
|
+
# Set permissions (works on Unix/Linux/macOS and Git Bash on Windows)
|
|
4
12
|
chmod 700 ~/.ssh
|
|
5
13
|
chmod 644 ~/.ssh/known_hosts
|
|
6
14
|
chmod 644 ~/.ssh/config
|
|
15
|
+
|
|
16
|
+
echo "✓ SSH directory initialized at ~/.ssh"
|
package/bin/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tertium/hlpr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Windows and *Nix utility for typical programming activity",
|
|
5
5
|
"author": "Vitalii Balabanov",
|
|
6
6
|
"email": "tertiumnon@gmail.com",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "bun run build:main && bun run build:commands",
|
|
13
13
|
"test": "bun test",
|
|
14
14
|
"test:e2e": "node scripts/test-rename-e2e.cjs",
|
|
15
|
-
"prepublishOnly": "
|
|
15
|
+
"prepublishOnly": "bun run build",
|
|
16
16
|
"release:minor": "node node_modules/@tertium/js/scripts/release.js minor",
|
|
17
17
|
"release:patch": "node node_modules/@tertium/js/scripts/release.js patch",
|
|
18
18
|
"release:major": "node node_modules/@tertium/js/scripts/release.js major",
|