@vlandoss/localproxy 0.0.1-git-9eaaf74.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 +50 -0
- package/bin.ts +9 -0
- package/config/Caddyfile.example +3 -0
- package/package.json +37 -0
- package/src/commands/clean.ts +27 -0
- package/src/commands/setup.ts +88 -0
- package/src/commands/start.ts +20 -0
- package/src/commands/status.ts +36 -0
- package/src/commands/stop.ts +20 -0
- package/src/main.ts +51 -0
- package/src/services/caddy.ts +121 -0
- package/src/services/hosts.ts +60 -0
- package/src/services/logger.ts +5 -0
- package/src/services/shell.ts +15 -0
- package/src/types.ts +13 -0
- package/src/ui.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 🛠️ localproxy
|
|
2
|
+
|
|
3
|
+
**Simple local development proxy automation with Caddy + hosts management**
|
|
4
|
+
|
|
5
|
+
Stop remembering ports! localproxy automatically maps your projects to clean `.localhost` domains with automatic HTTPS and hosts file management.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Instead of this mess:
|
|
9
|
+
http://localhost:3000 # Which project was this?
|
|
10
|
+
http://localhost:4000 # And this?
|
|
11
|
+
http://localhost:5431 # 🤔
|
|
12
|
+
|
|
13
|
+
# Get this:
|
|
14
|
+
http://frontend.localhost
|
|
15
|
+
http://api.localhost
|
|
16
|
+
http://admin.localhost
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Bun >= 1.2.4
|
|
22
|
+
- Caddy >= 2.8.4
|
|
23
|
+
- hosts >= 3.6.4
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pnpm add -g @vlandoss/localproxy
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
It will adds the `localproxy` to your global workspace
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
> [!NOTE]
|
|
36
|
+
> The documentation is WIP
|
|
37
|
+
|
|
38
|
+
Run the help command:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
localproxy help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Troubleshooting
|
|
45
|
+
|
|
46
|
+
To enable debug mode, set the `DEBUG` environment variable to `localproxy:*` before running *any* command.
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
DEBUG=localproxy:* localproxy <command>
|
|
50
|
+
```
|
package/bin.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vlandoss/localproxy",
|
|
3
|
+
"version": "0.0.1-git-9eaaf74.0",
|
|
4
|
+
"description": "Simple local development proxy automation",
|
|
5
|
+
"homepage": "https://github.com/variableland/dx/tree/main/packages/localproxy#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/variableland/dx/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/variableland/dx.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "rcrd <rcrd@variable.land>",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"localproxy": "./bin.ts",
|
|
18
|
+
"localp": "./bin.ts"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"src",
|
|
23
|
+
"config"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@inquirer/prompts": "^7.8.4",
|
|
27
|
+
"commander": "13.1.0",
|
|
28
|
+
"@vlandoss/loggy": "0.0.4-git-9eaaf74.0",
|
|
29
|
+
"@vlandoss/clibuddy": "0.0.5-git-9eaaf74.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"bun": ">=1.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createCommand } from "commander";
|
|
2
|
+
import { CaddyService } from "~/services/caddy";
|
|
3
|
+
import { HostsService } from "~/services/hosts";
|
|
4
|
+
import { logger } from "~/services/logger";
|
|
5
|
+
import type { Context } from "~/types";
|
|
6
|
+
|
|
7
|
+
type CommandOptions = {
|
|
8
|
+
verbose: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function createCleanCommand({ caddyfilePath }: Context) {
|
|
12
|
+
return createCommand("clean")
|
|
13
|
+
.description("clean up config files")
|
|
14
|
+
.option("--verbose", "verbose mode, show background output", false)
|
|
15
|
+
.action(async (options: CommandOptions) => {
|
|
16
|
+
const caddyService = new CaddyService(caddyfilePath);
|
|
17
|
+
await caddyService.stop(options);
|
|
18
|
+
|
|
19
|
+
const localDomains = caddyService.getLocalDomains();
|
|
20
|
+
const hosts = localDomains.map((d) => d.host);
|
|
21
|
+
|
|
22
|
+
const hostsService = new HostsService(hosts);
|
|
23
|
+
await hostsService.clean(options);
|
|
24
|
+
|
|
25
|
+
logger.success("localproxy clean completed");
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { editor } from "@inquirer/prompts";
|
|
4
|
+
import { createCommand } from "commander";
|
|
5
|
+
import { CaddyService } from "~/services/caddy";
|
|
6
|
+
import { HostsService } from "~/services/hosts";
|
|
7
|
+
import { logger } from "~/services/logger";
|
|
8
|
+
import { quietShell } from "~/services/shell";
|
|
9
|
+
import type { Context } from "~/types";
|
|
10
|
+
|
|
11
|
+
type CommandOptions = {
|
|
12
|
+
verbose: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const debug = logger.subdebug("setup");
|
|
16
|
+
|
|
17
|
+
async function printFile(filePath: string) {
|
|
18
|
+
const fileContent = (await fs.readFile(filePath)).toString();
|
|
19
|
+
|
|
20
|
+
console.log(`${filePath}:\n`);
|
|
21
|
+
console.log(fileContent.trim());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function checkInternalTools() {
|
|
25
|
+
const { $ } = quietShell;
|
|
26
|
+
|
|
27
|
+
const caddyVersion = await $`caddy --version`.nothrow();
|
|
28
|
+
|
|
29
|
+
if (caddyVersion.exitCode) {
|
|
30
|
+
logger.error("Caddy is not installed. Please install Caddy first:");
|
|
31
|
+
logger.info("macOS: brew install caddy");
|
|
32
|
+
logger.info("Linux: https://caddyserver.com/docs/install");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
debug("Caddy version: %s", caddyVersion.stdout.trim());
|
|
37
|
+
|
|
38
|
+
const hostsVersion = await $`hosts --version`.nothrow();
|
|
39
|
+
|
|
40
|
+
if (!hostsVersion) {
|
|
41
|
+
logger.error("hosts CLI tool is not installed. Please install hosts first:");
|
|
42
|
+
logger.info("macOS: brew tap xwmx/taps && brew install hosts");
|
|
43
|
+
logger.info("Linux: Check https://github.com/xwmx/hosts for installation");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
debug("hosts version: %s", hostsVersion.stdout.trim());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createSetupCommand({ binDir, installDir, caddyfilePath }: Context) {
|
|
51
|
+
return createCommand("setup")
|
|
52
|
+
.description("setup config files")
|
|
53
|
+
.option("--verbose", "verbose mode, show background output", false)
|
|
54
|
+
.action(async (options: CommandOptions) => {
|
|
55
|
+
debug("setup command options %o", options);
|
|
56
|
+
|
|
57
|
+
await checkInternalTools();
|
|
58
|
+
|
|
59
|
+
if (!(await fs.exists(installDir))) {
|
|
60
|
+
await fs.mkdir(installDir);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const exampleCaddyFilePath = path.join(binDir, "config", "Caddyfile.example");
|
|
64
|
+
const defaultContent = (await fs.exists(caddyfilePath))
|
|
65
|
+
? await fs.readFile(caddyfilePath, "utf-8")
|
|
66
|
+
: await fs.readFile(exampleCaddyFilePath, "utf-8");
|
|
67
|
+
|
|
68
|
+
const fileContent = await editor({
|
|
69
|
+
message: "Caddyfile",
|
|
70
|
+
default: defaultContent,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await fs.writeFile(caddyfilePath, fileContent);
|
|
74
|
+
|
|
75
|
+
await printFile(caddyfilePath);
|
|
76
|
+
|
|
77
|
+
const caddyService = new CaddyService(caddyfilePath);
|
|
78
|
+
await caddyService.reboot(options);
|
|
79
|
+
|
|
80
|
+
const localDomains = caddyService.getLocalDomains();
|
|
81
|
+
const hosts = localDomains.map((d) => d.host);
|
|
82
|
+
|
|
83
|
+
const hostsService = new HostsService(hosts);
|
|
84
|
+
await hostsService.setup(options);
|
|
85
|
+
|
|
86
|
+
logger.success("localproxy setup completed");
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createCommand } from "commander";
|
|
2
|
+
import { CaddyService } from "~/services/caddy";
|
|
3
|
+
import { logger } from "~/services/logger";
|
|
4
|
+
import type { Context } from "~/types";
|
|
5
|
+
|
|
6
|
+
type CommandOptions = {
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function createStartCommand({ caddyfilePath }: Context) {
|
|
11
|
+
return createCommand("start")
|
|
12
|
+
.description("start caddy server")
|
|
13
|
+
.option("--verbose", "verbose mode, show background output", false)
|
|
14
|
+
.action(async (options: CommandOptions) => {
|
|
15
|
+
const caddyService = new CaddyService(caddyfilePath);
|
|
16
|
+
await caddyService.start(options);
|
|
17
|
+
|
|
18
|
+
logger.success("localproxy start completed");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createCommand } from "commander";
|
|
2
|
+
import { CaddyService } from "~/services/caddy";
|
|
3
|
+
import { HostsService } from "~/services/hosts";
|
|
4
|
+
import { logger } from "~/services/logger";
|
|
5
|
+
import type { Context } from "~/types";
|
|
6
|
+
|
|
7
|
+
export function createStatusCommand({ caddyfilePath }: Context) {
|
|
8
|
+
return createCommand("status")
|
|
9
|
+
.description("show configured localhosts")
|
|
10
|
+
.option("--verbose", "verbose mode, show background output", false)
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const caddyService = new CaddyService(caddyfilePath);
|
|
13
|
+
|
|
14
|
+
if (await caddyService.isRunning()) {
|
|
15
|
+
logger.info("Caddy is running");
|
|
16
|
+
} else {
|
|
17
|
+
logger.warn("Caddy is not running");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const localDomains = caddyService.getLocalDomains();
|
|
21
|
+
const hosts = localDomains.map((d) => d.host);
|
|
22
|
+
|
|
23
|
+
const hostsService = new HostsService(hosts);
|
|
24
|
+
|
|
25
|
+
for (const domain of localDomains) {
|
|
26
|
+
const { host, port } = domain;
|
|
27
|
+
const found = await hostsService.findHost(host);
|
|
28
|
+
|
|
29
|
+
if (found) {
|
|
30
|
+
logger.success("%s is configured -> :%s", host, port);
|
|
31
|
+
} else {
|
|
32
|
+
logger.warn("%s is not configured -> :%s", host, port);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createCommand } from "commander";
|
|
2
|
+
import { CaddyService } from "~/services/caddy";
|
|
3
|
+
import { logger } from "~/services/logger";
|
|
4
|
+
import type { Context } from "~/types";
|
|
5
|
+
|
|
6
|
+
type CommandOptions = {
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function createStopCommand({ caddyfilePath }: Context) {
|
|
11
|
+
return createCommand("stop")
|
|
12
|
+
.description("stop caddy server")
|
|
13
|
+
.option("--verbose", "verbose mode, show background output", false)
|
|
14
|
+
.action(async (options: CommandOptions) => {
|
|
15
|
+
const caddyService = new CaddyService(caddyfilePath);
|
|
16
|
+
await caddyService.stop(options);
|
|
17
|
+
|
|
18
|
+
logger.success("localproxy stop completed");
|
|
19
|
+
});
|
|
20
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createPkgService, getVersion } from "@vlandoss/clibuddy";
|
|
3
|
+
import { createCommand } from "commander";
|
|
4
|
+
import { createCleanCommand } from "./commands/clean";
|
|
5
|
+
import { createSetupCommand } from "./commands/setup";
|
|
6
|
+
import { createStartCommand } from "./commands/start";
|
|
7
|
+
import { createStatusCommand } from "./commands/status";
|
|
8
|
+
import { createStopCommand } from "./commands/stop";
|
|
9
|
+
import { logger } from "./services/logger";
|
|
10
|
+
import type { Context, ProgramOptions } from "./types";
|
|
11
|
+
import { BANNER_TEXT, CREDITS_TEXT } from "./ui";
|
|
12
|
+
|
|
13
|
+
async function createContext({ binDir, installDir }: ProgramOptions) {
|
|
14
|
+
const binPkg = await createPkgService(binDir);
|
|
15
|
+
|
|
16
|
+
if (!binPkg) {
|
|
17
|
+
throw new Error("Could not find bin package.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
installDir,
|
|
22
|
+
caddyfilePath: path.join(installDir, "Caddyfile"),
|
|
23
|
+
binDir,
|
|
24
|
+
binPkg,
|
|
25
|
+
} satisfies Context;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function createProgram(options: ProgramOptions) {
|
|
29
|
+
const ctx = await createContext(options);
|
|
30
|
+
|
|
31
|
+
return createCommand("localproxy")
|
|
32
|
+
.alias("localp")
|
|
33
|
+
.version(getVersion(ctx.binPkg), "-v, --version")
|
|
34
|
+
.addHelpText("before", BANNER_TEXT)
|
|
35
|
+
.addHelpText("after", CREDITS_TEXT)
|
|
36
|
+
.addCommand(createSetupCommand(ctx))
|
|
37
|
+
.addCommand(createStatusCommand(ctx))
|
|
38
|
+
.addCommand(createStartCommand(ctx))
|
|
39
|
+
.addCommand(createStopCommand(ctx))
|
|
40
|
+
.addCommand(createCleanCommand(ctx));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function main(options: ProgramOptions) {
|
|
44
|
+
try {
|
|
45
|
+
const program = await createProgram(options);
|
|
46
|
+
await program.parseAsync();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.error("Cannot run main successfully", error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
import { quietShell, silentShell, verboseShell } from "./shell";
|
|
5
|
+
|
|
6
|
+
const debug = logger.subdebug("caddy");
|
|
7
|
+
|
|
8
|
+
type ExecOption = {
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type LocalDomain = {
|
|
13
|
+
host: string;
|
|
14
|
+
port: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class CaddyService {
|
|
18
|
+
#configPath: string;
|
|
19
|
+
#pidFilePath: string;
|
|
20
|
+
|
|
21
|
+
constructor(configPath: string) {
|
|
22
|
+
this.#configPath = configPath;
|
|
23
|
+
this.#pidFilePath = path.join(path.dirname(configPath), "caddy.pid");
|
|
24
|
+
this.#initCaddyPid();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#initCaddyPid() {
|
|
28
|
+
if (!this.#hasCaddyPid()) {
|
|
29
|
+
fs.writeFileSync(this.#pidFilePath, "");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#hasCaddyPid() {
|
|
34
|
+
return fs.existsSync(this.#pidFilePath);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#deleteCaddyPid() {
|
|
38
|
+
if (this.#hasCaddyPid()) {
|
|
39
|
+
fs.rmSync(this.#pidFilePath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#shell({ verbose }: ExecOption) {
|
|
44
|
+
return verbose ? verboseShell : silentShell;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async reboot(options: ExecOption) {
|
|
48
|
+
const isRunning = await this.isRunning();
|
|
49
|
+
if (isRunning) {
|
|
50
|
+
await this.stop(options);
|
|
51
|
+
}
|
|
52
|
+
await this.start(options);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async start({ verbose }: ExecOption) {
|
|
56
|
+
const { $ } = this.#shell({ verbose });
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
debug("Starting Caddy...");
|
|
60
|
+
|
|
61
|
+
await $`caddy start -c ${this.#configPath} --pidfile ${this.#pidFilePath} > /dev/null 2>&1`;
|
|
62
|
+
|
|
63
|
+
debug("Caddy started");
|
|
64
|
+
} catch {
|
|
65
|
+
logger.error("Can't start Caddy");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async stop({ verbose }: ExecOption) {
|
|
71
|
+
const { $ } = this.#shell({ verbose });
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
debug("Stopping Caddy...");
|
|
75
|
+
|
|
76
|
+
await $`caddy stop -c ${this.#configPath}`;
|
|
77
|
+
this.#deleteCaddyPid();
|
|
78
|
+
|
|
79
|
+
debug("Caddy stopped");
|
|
80
|
+
} catch {
|
|
81
|
+
logger.error("Can't stop Caddy");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async isRunning() {
|
|
87
|
+
if (!this.#hasCaddyPid()) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const pid = (await quietShell.$`cat ${this.#pidFilePath}`.text()).trim();
|
|
92
|
+
|
|
93
|
+
debug("Caddy PID: %d", pid);
|
|
94
|
+
|
|
95
|
+
const { exitCode } = await quietShell.$`kill -0 ${pid}`.nothrow();
|
|
96
|
+
const isRunning = exitCode === 0;
|
|
97
|
+
|
|
98
|
+
debug("Caddy is %s", isRunning ? "running" : "stopped");
|
|
99
|
+
|
|
100
|
+
return isRunning;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getLocalDomains(): LocalDomain[] {
|
|
104
|
+
const caddyfileContent = fs.readFileSync(this.#configPath, "utf-8");
|
|
105
|
+
|
|
106
|
+
const REGEX = /^(.+)\.localhost/gm;
|
|
107
|
+
const matches = caddyfileContent.matchAll(REGEX);
|
|
108
|
+
|
|
109
|
+
const hosts = Array.from(matches, (m) => m[0]).filter(Boolean) as string[];
|
|
110
|
+
|
|
111
|
+
const localDomains: LocalDomain[] = hosts.map((host) => {
|
|
112
|
+
const index = caddyfileContent.indexOf(host);
|
|
113
|
+
const port = caddyfileContent.slice(index).match(/localhost:(\d+)/)?.[1] || "80";
|
|
114
|
+
return { host, port };
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
debug("detected domains: %o", localDomains);
|
|
118
|
+
|
|
119
|
+
return localDomains;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { logger } from "./logger";
|
|
2
|
+
import { quietShell, silentShell, verboseShell } from "./shell";
|
|
3
|
+
|
|
4
|
+
type SetupOptions = {
|
|
5
|
+
verbose: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const debug = logger.subdebug("hosts");
|
|
9
|
+
|
|
10
|
+
export class HostsService {
|
|
11
|
+
#hosts: string[];
|
|
12
|
+
|
|
13
|
+
constructor(domains: string[]) {
|
|
14
|
+
this.#hosts = domains;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#shell({ verbose }: SetupOptions) {
|
|
18
|
+
return verbose ? verboseShell : silentShell;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async setup(options: SetupOptions) {
|
|
22
|
+
verboseShell.$`sudo hosts backups create`;
|
|
23
|
+
|
|
24
|
+
for (const host of this.#hosts) {
|
|
25
|
+
await this.addHost(host, options);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async clean(options: SetupOptions) {
|
|
30
|
+
for (const host of this.#hosts) {
|
|
31
|
+
await this.removeHost(host, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async findHost(host: string) {
|
|
36
|
+
const currentHost = await quietShell.$`hosts show "${host}"`.text();
|
|
37
|
+
debug("Host %s is %s", host, currentHost ? "present" : "absent");
|
|
38
|
+
return currentHost;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async addHost(host: string, options: SetupOptions) {
|
|
42
|
+
const { $ } = this.#shell(options);
|
|
43
|
+
|
|
44
|
+
const currentHost = await this.findHost(host);
|
|
45
|
+
|
|
46
|
+
if (!currentHost) {
|
|
47
|
+
await $`sudo hosts add 127.0.0.1 ${host}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async removeHost(host: string, options: SetupOptions) {
|
|
52
|
+
const { $ } = this.#shell(options);
|
|
53
|
+
|
|
54
|
+
const currentHost = await this.findHost(host);
|
|
55
|
+
|
|
56
|
+
if (!currentHost) {
|
|
57
|
+
await $`sudo hosts remove ${host}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createShellService } from "@vlandoss/clibuddy";
|
|
2
|
+
|
|
3
|
+
export const quietShell = createShellService({
|
|
4
|
+
quiet: true,
|
|
5
|
+
verbose: false,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const silentShell = quietShell.child({
|
|
9
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const verboseShell = quietShell.child({
|
|
13
|
+
quiet: false,
|
|
14
|
+
verbose: true,
|
|
15
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PkgService } from "@vlandoss/clibuddy";
|
|
2
|
+
|
|
3
|
+
export type ProgramOptions = {
|
|
4
|
+
binDir: string;
|
|
5
|
+
installDir: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Context = {
|
|
9
|
+
binDir: string;
|
|
10
|
+
installDir: string;
|
|
11
|
+
caddyfilePath: string;
|
|
12
|
+
binPkg: PkgService;
|
|
13
|
+
};
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { colors } from "@vlandoss/clibuddy";
|
|
2
|
+
|
|
3
|
+
export const UI_LOGO = `🛠️ ${colors.bold("localproxy")}`;
|
|
4
|
+
|
|
5
|
+
export const BANNER_TEXT = `${UI_LOGO}: Simple local development proxy automation\n`;
|
|
6
|
+
|
|
7
|
+
export const CREDITS_TEXT = `\nAcknowledgment:
|
|
8
|
+
- Caddy: for being a powerful proxy server
|
|
9
|
+
https://caddyserver.com
|
|
10
|
+
|
|
11
|
+
- hosts: making it easier to manage host file
|
|
12
|
+
https://github.com/xwmx/hosts`;
|