@luxmargos/ensure-hosts 0.1.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 ADDED
@@ -0,0 +1,285 @@
1
+ # @luxmargos/ensure-hosts
2
+
3
+ Manage local hosts-file entries on macOS, Linux, and Windows from small YAML profiles.
4
+
5
+ `ensure-hosts` expands a YAML file into hosts-file records, removes stale records for the same domains when requested, and appends the current records. It is useful for local development domains such as `api.myapp.test`, `admin.myapp.test`, and other repeatable project setup.
6
+
7
+ ## Why use it?
8
+
9
+ - Keep project hostnames in versioned YAML instead of editing `/etc/hosts` by hand.
10
+ - Re-run the same command safely when project IPs or domains change.
11
+ - Preview changes before writing with `--dry-run`.
12
+ - Remove a profile's entries when you no longer need them.
13
+ - Share one base config plus project-specific configs.
14
+
15
+ ## Requirements
16
+
17
+ - Node.js 20 or newer
18
+ - Permission to write the system hosts file when not using `--dry-run`
19
+
20
+ Default hosts-file locations:
21
+
22
+ - Linux/macOS: `/etc/hosts`
23
+ - Windows: `%SystemRoot%\System32\drivers\etc\hosts`
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ npm install -g @luxmargos/ensure-hosts
29
+ ```
30
+
31
+ Then check the CLI is available:
32
+
33
+ ```sh
34
+ ensure-hosts --version
35
+ ```
36
+
37
+ ## Quick start
38
+
39
+ 1. Create a config file, for example `hosts.local.yaml`:
40
+
41
+ ```yaml
42
+ profile: MYAPP_LOCAL
43
+ hosts:
44
+ - domain: myapp.test
45
+ address: 127.0.0.1
46
+ children:
47
+ - api
48
+ - admin
49
+ ```
50
+
51
+ 2. Preview what would be written:
52
+
53
+ ```sh
54
+ ensure-hosts --config ./hosts.local.yaml --dry-run
55
+ ```
56
+
57
+ 3. Apply the changes:
58
+
59
+ ```sh
60
+ ensure-hosts --config ./hosts.local.yaml
61
+ ```
62
+
63
+ On macOS and Windows, the CLI can prompt for administrator permission when needed. On Linux, run with `sudo` if your user cannot write `/etc/hosts`:
64
+
65
+ ```sh
66
+ sudo ensure-hosts --config ./hosts.local.yaml
67
+ ```
68
+
69
+ The example above writes records like:
70
+
71
+ ```txt
72
+ # MYAPP_LOCAL
73
+ 127.0.0.1 myapp.test
74
+ # MYAPP_LOCAL
75
+ 127.0.0.1 api.myapp.test
76
+ # MYAPP_LOCAL
77
+ 127.0.0.1 admin.myapp.test
78
+ ```
79
+
80
+ ## Common commands
81
+
82
+ ```sh
83
+ # Use one config
84
+ ensure-hosts --config ./hosts.local.yaml
85
+
86
+ # Layer multiple configs
87
+ ensure-hosts --config ./base.yaml --config ./project.yaml
88
+
89
+ # Preview the final hosts file without writing
90
+ ensure-hosts --config ./hosts.local.yaml --dry-run
91
+
92
+ # Print the expanded records only
93
+ ensure-hosts --config ./hosts.local.yaml --print-records
94
+
95
+ # Remove entries managed by rewrite:true records
96
+ ensure-hosts --config ./hosts.local.yaml --remove
97
+
98
+ # Remove every domain listed by the config, including rewrite:false records
99
+ ensure-hosts --config ./hosts.local.yaml --remove-force
100
+ ```
101
+
102
+ ## Configuration reference
103
+
104
+ A config file must be `.yaml` or `.yml` and contain:
105
+
106
+ ```yaml
107
+ profile: PROFILE_NAME
108
+ hosts:
109
+ - domain: some.domain.test
110
+ address: 192.168.1.111
111
+ rewrite: true
112
+ skipSelf: false
113
+ children:
114
+ - sitea
115
+ - domain: siteb
116
+ address: ::1
117
+ ```
118
+
119
+ Fields:
120
+
121
+ | Field | Required? | Description |
122
+ | --- | --- | --- |
123
+ | `profile` | Yes | Label written as a comment above generated entries, for example `# MYAPP_LOCAL`. |
124
+ | `hosts` | Yes | Array of host entries. |
125
+ | `domain` | Yes | Domain name. Inside `children`, this can be a relative subdomain such as `api`. |
126
+ | `address` | For records you want written | IPv4 or IPv6 address. Children inherit the parent address unless they set their own. |
127
+ | `rewrite` | No | Whether existing entries for the same domain may be cleaned before appending. Defaults to `true`. Children inherit the parent value. |
128
+ | `skipSelf` | No | Skip writing this node while still processing its children. Defaults to `false`. |
129
+ | `children` | No | Nested subdomains as strings or full objects. |
130
+
131
+ ### Child domains
132
+
133
+ Child strings inherit the parent address and `rewrite` value:
134
+
135
+ ```yaml
136
+ profile: LOCAL
137
+ hosts:
138
+ - domain: example.test
139
+ address: 127.0.0.1
140
+ children:
141
+ - api
142
+ - admin
143
+ ```
144
+
145
+ This writes:
146
+
147
+ - `example.test`
148
+ - `api.example.test`
149
+ - `admin.example.test`
150
+
151
+ If a child already contains the full parent domain, it is not duplicated. For example, `api.example.test` stays `api.example.test`.
152
+
153
+ ### `skipSelf` example
154
+
155
+ Use `skipSelf: true` when you only want child records:
156
+
157
+ ```yaml
158
+ profile: LOCAL
159
+ hosts:
160
+ - domain: example.test
161
+ address: 127.0.0.1
162
+ skipSelf: true
163
+ children:
164
+ - api
165
+ - admin
166
+ ```
167
+
168
+ This writes `api.example.test` and `admin.example.test`, but not `example.test`.
169
+
170
+ ## How rewriting works
171
+
172
+ By default, `rewrite` is `true`.
173
+
174
+ With `rewrite: true`, `ensure-hosts`:
175
+
176
+ 1. Removes existing hosts entries for the same domain.
177
+ 2. Removes stale adjacent `# PROFILE_NAME` comments left by previous runs.
178
+ 3. Appends the current generated entry when an address is available.
179
+
180
+ With `rewrite: false`, `ensure-hosts`:
181
+
182
+ 1. Leaves existing entries for the same domain untouched.
183
+ 2. Appends the generated entry only if that domain is not already present.
184
+
185
+ Entries without an effective `address` are not written. If their effective `rewrite` value is `true`, matching existing entries can still be cleaned.
186
+
187
+ ## Remove mode
188
+
189
+ Use remove mode when you want to uninstall entries from a profile instead of ensuring them.
190
+
191
+ ```sh
192
+ ensure-hosts --config ./hosts.local.yaml --remove
193
+ ensure-hosts --config ./hosts.local.yaml --remove-force
194
+ ensure-hosts --config ./hosts.local.yaml --remove --dry-run
195
+ ```
196
+
197
+ - `--remove` removes domains whose effective `rewrite` value is `true`. Domains marked `rewrite: false` are left untouched.
198
+ - `--remove-force` removes every domain listed by the config, including `rewrite: false` domains.
199
+
200
+ `--remove` and `--remove-force` cannot be used together, and neither can be combined with `--print-records`.
201
+
202
+ ## Environment variables
203
+
204
+ The CLI automatically loads `.env` from the current working directory. Missing `.env` files and missing environment variables are ignored.
205
+
206
+ You can provide config paths with `ENSURE_HOSTS_CONFIG` instead of `--config`:
207
+
208
+ ```dotenv
209
+ ENSURE_HOSTS_CONFIG=./hosts.local.yaml,./another.yaml
210
+ ```
211
+
212
+ You can also override the hosts file path, which is useful for tests or custom workflows:
213
+
214
+ ```dotenv
215
+ ENSURE_HOSTS_HOSTS_FILE=./tmp-hosts
216
+ ```
217
+
218
+ To disable macOS/Windows elevation prompts:
219
+
220
+ ```dotenv
221
+ ENSURE_HOSTS_NO_ELEVATE=true
222
+ ```
223
+
224
+ Use `--env-file <path>` if your dotenv file is not named `.env`.
225
+
226
+ ## CLI options
227
+
228
+ ```txt
229
+ --config <path> YAML/YML config file path (repeatable)
230
+ --env-file <path> dotenv file path (default: .env)
231
+ --hosts-file <path> override hosts file path
232
+ --dry-run print rewritten hosts content without writing
233
+ --print-records print expanded records and exit
234
+ --remove remove rewrite:true domains (respects rewrite:false)
235
+ --remove-force remove all listed domains, including rewrite:false
236
+ --no-elevate disable macOS/Windows privilege prompt
237
+ --help show help
238
+ --version show version
239
+ ```
240
+
241
+ Notes:
242
+
243
+ - On Linux, `--no-elevate` is effectively a no-op because Linux does not use an in-process elevation prompt. Run the command with `sudo` when needed.
244
+ - Use `--hosts-file` or `ENSURE_HOSTS_HOSTS_FILE` to test against a temporary file instead of the real hosts file.
245
+
246
+ ## Development
247
+
248
+ Install dependencies:
249
+
250
+ ```sh
251
+ npm install
252
+ ```
253
+
254
+ Run checks:
255
+
256
+ ```sh
257
+ npm test
258
+ npm run typecheck
259
+ npm run build
260
+ ```
261
+
262
+ The repository also includes a Docker-based Linux test harness (`Dockerfile.linux-test` and `compose.linux-test.yaml`). These files are for testing only, not production.
263
+
264
+ ```sh
265
+ # Run the unit test suite in a Linux container
266
+ docker compose -f compose.linux-test.yaml run --rm test
267
+
268
+ # Typecheck and build
269
+ docker compose -f compose.linux-test.yaml run --rm typecheck
270
+ docker compose -f compose.linux-test.yaml run --rm build
271
+
272
+ # Exercise CLI scenarios
273
+ docker compose -f compose.linux-test.yaml run --rm dry-run
274
+ docker compose -f compose.linux-test.yaml run --rm print-records
275
+ docker compose -f compose.linux-test.yaml run --rm root-write
276
+ docker compose -f compose.linux-test.yaml run --rm etc-hosts-write
277
+ docker compose -f compose.linux-test.yaml run --rm non-root-fail
278
+ docker compose -f compose.linux-test.yaml run --rm remove
279
+ ```
280
+
281
+ The `root-write`, `non-root-fail`, and `remove` services use `--hosts-file /tmp/test-hosts`, so the real `/etc/hosts` is not modified. The `etc-hosts-write` service writes to the container's ephemeral `/etc/hosts`, not your host machine.
282
+
283
+ ## License
284
+
285
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { buildElevationArgs, loadDefaultEnv, loadProfiles, parseCliOptions, resolveConfigPaths, resolveHostsFileOverride, resolveNoElevate, } from './config.js';
5
+ import { expandProfiles } from './domain-map.js';
6
+ import { removeHostsContent, rewriteHostsContent } from './hosts.js';
7
+ import { elevatedCommandHint, elevationHandled, notifyRootWrite, resolveDefaultHostsPath, tryElevate } from './platform.js';
8
+ async function main() {
9
+ const options = parseCliOptions(process.argv.slice(2));
10
+ if (options.outputFile) {
11
+ redirectConsoleToFile(options.outputFile);
12
+ }
13
+ loadDefaultEnv(options.envFile);
14
+ const configPaths = resolveConfigPaths(options);
15
+ const profiles = loadProfiles(configPaths);
16
+ const expandedProfiles = expandProfiles(profiles);
17
+ if (options.printRecords) {
18
+ printRecords(expandedProfiles);
19
+ return;
20
+ }
21
+ if (options.remove || options.removeForce) {
22
+ return runRemove(options, configPaths, expandedProfiles);
23
+ }
24
+ const hostsFile = resolveHostsFileOverride(options) ?? resolveDefaultHostsPath();
25
+ const hostsContent = readFileSync(hostsFile, 'utf8');
26
+ const result = rewriteHostsContent(hostsContent, expandedProfiles);
27
+ if (options.dryRun) {
28
+ process.stdout.write(result.content);
29
+ return;
30
+ }
31
+ if (result.content === hostsContent) {
32
+ console.log('[ensure-hosts] hosts file is already up to date.');
33
+ return;
34
+ }
35
+ try {
36
+ notifyRootWrite(hostsFile);
37
+ writeFileSync(hostsFile, result.content, 'utf8');
38
+ }
39
+ catch (error) {
40
+ if (isPermissionError(error)) {
41
+ const elevated = tryElevate({
42
+ scriptPath: fileURLToPath(import.meta.url),
43
+ args: buildElevationArgs(options, configPaths),
44
+ cwd: process.cwd(),
45
+ noElevate: resolveNoElevate(options),
46
+ elevated: options.elevated,
47
+ dryRun: options.dryRun,
48
+ printRecords: options.printRecords,
49
+ filePath: hostsFile,
50
+ content: result.content,
51
+ });
52
+ if (elevationHandled(elevated)) {
53
+ return;
54
+ }
55
+ throw new Error([
56
+ `Permission denied while writing hosts file: ${hostsFile}`,
57
+ 'Run again with administrator/root privileges.',
58
+ elevatedCommandHint(`ensure-hosts --config ${configPaths.join(' --config ')}`),
59
+ ].join('\n'));
60
+ }
61
+ throw error;
62
+ }
63
+ console.log(`[ensure-hosts] updated ${hostsFile}: appended ${result.appended.length}, cleaned ${result.removedDomains.length}.`);
64
+ }
65
+ function runRemove(options, configPaths, expandedProfiles) {
66
+ const force = options.removeForce;
67
+ const hostsFile = resolveHostsFileOverride(options) ?? resolveDefaultHostsPath();
68
+ const hostsContent = readFileSync(hostsFile, 'utf8');
69
+ const result = removeHostsContent(hostsContent, expandedProfiles, { force });
70
+ if (options.dryRun) {
71
+ process.stdout.write(result.content);
72
+ return;
73
+ }
74
+ if (result.content === hostsContent) {
75
+ console.log('[ensure-hosts] hosts file has no matching entries to remove.');
76
+ return;
77
+ }
78
+ try {
79
+ notifyRootWrite(hostsFile);
80
+ writeFileSync(hostsFile, result.content, 'utf8');
81
+ }
82
+ catch (error) {
83
+ if (isPermissionError(error)) {
84
+ const elevated = tryElevate({
85
+ scriptPath: fileURLToPath(import.meta.url),
86
+ args: buildElevationArgs(options, configPaths),
87
+ cwd: process.cwd(),
88
+ noElevate: resolveNoElevate(options),
89
+ elevated: options.elevated,
90
+ dryRun: options.dryRun,
91
+ printRecords: options.printRecords,
92
+ filePath: hostsFile,
93
+ content: result.content,
94
+ });
95
+ if (elevationHandled(elevated)) {
96
+ return;
97
+ }
98
+ const modeFlag = force ? '--remove-force' : '--remove';
99
+ throw new Error([
100
+ `Permission denied while writing hosts file: ${hostsFile}`,
101
+ 'Run again with administrator/root privileges.',
102
+ elevatedCommandHint(`ensure-hosts --config ${configPaths.join(' --config ')} ${modeFlag}`),
103
+ ].join('\n'));
104
+ }
105
+ throw error;
106
+ }
107
+ const modeLabel = force ? ' (force)' : '';
108
+ console.log(`[ensure-hosts] removed ${result.removedDomains.length} domain(s) from ${hostsFile}${modeLabel}.`);
109
+ }
110
+ function printRecords(expandedProfiles) {
111
+ for (const profile of expandedProfiles) {
112
+ for (const record of profile.records) {
113
+ console.log(`${record.address}\t${record.domain}\t# ${record.profile}\trewrite=${record.rewrite}`);
114
+ }
115
+ }
116
+ }
117
+ function redirectConsoleToFile(filePath) {
118
+ const write = (text) => appendFileSync(filePath, text);
119
+ const line = (args) => `${args.map(arg => (typeof arg === 'string' ? arg : String(arg))).join(' ')}\n`;
120
+ console.log = (...args) => write(line(args));
121
+ console.info = console.log;
122
+ console.warn = (...args) => write(line(args));
123
+ console.error = (...args) => write(line(args));
124
+ process.stdout.write = ((chunk) => {
125
+ write(typeof chunk === 'string' ? chunk : chunk.toString());
126
+ return true;
127
+ });
128
+ process.stderr.write = ((chunk) => {
129
+ write(typeof chunk === 'string' ? chunk : chunk.toString());
130
+ return true;
131
+ });
132
+ }
133
+ function isPermissionError(error) {
134
+ return (typeof error === 'object' &&
135
+ error !== null &&
136
+ 'code' in error &&
137
+ (error.code === 'EACCES' || error.code === 'EPERM'));
138
+ }
139
+ main().catch(error => {
140
+ const message = error instanceof Error ? error.message : String(error);
141
+ console.error(`[ensure-hosts] ${message}`);
142
+ process.exit(1);
143
+ });
144
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { CliOptions, ProfileConfig } from './types.js';
2
+ export declare function parseCliOptions(argv: string[]): CliOptions;
3
+ export declare function loadDefaultEnv(envFile: string): void;
4
+ export declare function resolveConfigPaths(options: CliOptions, env?: NodeJS.ProcessEnv): string[];
5
+ export declare function resolveHostsFileOverride(options: CliOptions, env?: NodeJS.ProcessEnv): string | undefined;
6
+ export declare function resolveNoElevate(options: CliOptions, env?: NodeJS.ProcessEnv): boolean;
7
+ /**
8
+ * Rebuild the CLI args for an elevated child process from already-resolved
9
+ * (absolute) paths. The elevated re-spawn runs in a different working
10
+ * directory than the parent (osascript's `do shell script` starts in `/`),
11
+ * so any relative path the user passed would fail to resolve in the child.
12
+ * Passing absolute paths fixes that without changing the public CLI surface.
13
+ *
14
+ * Elevation-only flags (`--no-elevate`, `--elevated`) and `--output-file`
15
+ * (Windows elevated-output plumbing) are intentionally omitted: the child is
16
+ * re-elevated/redirected by the elevation layer itself, not by these flags.
17
+ */
18
+ export declare function buildElevationArgs(options: CliOptions, configPaths: string[]): string[];
19
+ export declare function loadProfiles(configPaths: string[]): ProfileConfig[];
20
+ export declare function loadProfile(configPath: string): ProfileConfig;
21
+ export declare function usage(): string;