@hobble-env/cli 3.3.7 → 3.3.9
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/SKILL.md +12 -3
- package/dist/bin/hobble.js +1 -1
- package/dist/commands/init.js +7 -0
- package/dist/commands/switch.js +8 -4
- package/dist/lib/project-config.d.ts +21 -3
- package/dist/lib/project-config.js +38 -14
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Use when a repository contains a `.hobble` file, references the hob
|
|
|
7
7
|
|
|
8
8
|
> **Living document.** This file is the canonical agent-facing reference for the hobble CLI surface. Per the gate in `AGENTS.md`, any feature work that changes the CLI surface (commands, flags, arguments, output formats, exit behavior, env vars, or config files) MUST update this file in the same change, and it must be verified current before every PR. Source of truth: `cli/bin/hobble.ts` and `cli/commands/*.ts`.
|
|
9
9
|
|
|
10
|
-
<!-- documented-version: 3.
|
|
10
|
+
<!-- documented-version: 3.3.8 — keep in sync with cli/bin/hobble.ts -->
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
@@ -37,7 +37,7 @@ In this repo, the CLI runs from source: `npm run hobble -- <command>` (or `npx t
|
|
|
37
37
|
5. **Do not run interactive commands unprompted.** `hobble login`, `hobble init`, and `hobble switch` modify user or repo state. Ask the user to run them, or explicitly confirm before doing so.
|
|
38
38
|
6. **Never run `hobble env push -y` without showing the diff first.** Omit `-y` (or run once without it) so the user can review create/update/delete counts.
|
|
39
39
|
|
|
40
|
-
## Command Surface (v3.
|
|
40
|
+
## Command Surface (v3.3.8)
|
|
41
41
|
|
|
42
42
|
Global options: `-v, --verbose` (debug output on stderr), `-V, --version`.
|
|
43
43
|
|
|
@@ -118,11 +118,20 @@ All commands exit `0` on success and `1` on any failure (not found, auth, networ
|
|
|
118
118
|
|
|
119
119
|
Each data command needs a `project` and `environment`, resolved in order:
|
|
120
120
|
1. `--project` / `--env` flags on the command
|
|
121
|
-
2. `.hobble`
|
|
121
|
+
2. The nearest `.hobble` found by walking **up** from the current working
|
|
122
|
+
directory to the filesystem root (like `git`/`npm`/`eslint` root-config
|
|
123
|
+
discovery). The deepest ancestor wins, so running a command from a
|
|
124
|
+
subdirectory of a project resolves the project-root `.hobble`. An `apiUrl`
|
|
125
|
+
pin in that resolved `.hobble` is honored the same way.
|
|
122
126
|
3. Error: `No project configured. Run \`hobble init\` or pass \`--project\` and \`--env\`.`
|
|
123
127
|
|
|
124
128
|
If you hit that error, stop and ask the user. Do not run `hobble init` on their behalf — it is interactive and binds the directory to a project.
|
|
125
129
|
|
|
130
|
+
`hobble switch` rewrites the discovered ancestor `.hobble` in place (it does not
|
|
131
|
+
orphan a new config in a subdirectory). `hobble init`, by contrast, always
|
|
132
|
+
writes `.hobble` into the current directory — it is how you declare where the
|
|
133
|
+
project root is.
|
|
134
|
+
|
|
126
135
|
## Authentication
|
|
127
136
|
|
|
128
137
|
- Stored config: `~/.config/hobble/config.json` (written by `hobble login` / `hobble init`). Shape: `{ "apiUrl": "<default instance>", "credentials": { "<instance url>": { "type": "apiKey" | "session", ... } } }` — one credential per instance URL, so logins to multiple instances coexist. Older single-credential config files migrate automatically on first read.
|
package/dist/bin/hobble.js
CHANGED
|
@@ -20,7 +20,7 @@ const program = new Command();
|
|
|
20
20
|
program
|
|
21
21
|
.name("hobble")
|
|
22
22
|
.description("CLI for managing secrets via the secret-management-dash API")
|
|
23
|
-
.version("3.3.
|
|
23
|
+
.version("3.3.8")
|
|
24
24
|
.enablePositionalOptions()
|
|
25
25
|
.option("-v, --verbose", "Show debug output for troubleshooting");
|
|
26
26
|
program.hook("preAction", (thisCommand) => {
|
package/dist/commands/init.js
CHANGED
|
@@ -54,6 +54,13 @@ export const initCommand = new Command("init")
|
|
|
54
54
|
// Pin the instance in .hobble when --url named one explicitly, and
|
|
55
55
|
// preserve a pin a previous init/switch already wrote.
|
|
56
56
|
const pinnedUrl = opts.url ? apiUrl : existing?.apiUrl;
|
|
57
|
+
// init intentionally binds *this* directory: it writes .hobble (and the
|
|
58
|
+
// gitignore entry below) into process.cwd() via the default `dir`
|
|
59
|
+
// argument, even though loadProjectConfig/findProjectConfig now walk up
|
|
60
|
+
// to discover an ancestor config. This is deliberate — `init` is how a
|
|
61
|
+
// user declares "the project root is here", so it must not silently
|
|
62
|
+
// mutate an ancestor's .hobble. `switch`, by contrast, rewrites the
|
|
63
|
+
// discovered ancestor in place.
|
|
57
64
|
await saveProjectConfig({
|
|
58
65
|
project: selectedProject.name,
|
|
59
66
|
environment: envName,
|
package/dist/commands/switch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { api } from "../lib/api.js";
|
|
3
|
-
import {
|
|
3
|
+
import { findProjectConfig, saveProjectConfig } from "../lib/project-config.js";
|
|
4
4
|
import { success, error } from "../lib/output.js";
|
|
5
5
|
export const switchCommand = new Command("switch")
|
|
6
6
|
.description("Switch project and environment")
|
|
@@ -27,13 +27,17 @@ export const switchCommand = new Command("switch")
|
|
|
27
27
|
error(`Environment "${envName}" not found in project "${projectName}".`);
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
|
+
// Anchor the write on the nearest ancestor .hobble so a switch from a
|
|
31
|
+
// subdirectory rewrites the existing config in place instead of
|
|
32
|
+
// orphaning a new .hobble in cwd. Fall back to cwd when none exists.
|
|
30
33
|
// Keep an instance pin from a previous `init --url` intact.
|
|
31
|
-
const existing = await
|
|
34
|
+
const existing = await findProjectConfig();
|
|
35
|
+
const targetDir = existing?.dir ?? process.cwd();
|
|
32
36
|
await saveProjectConfig({
|
|
33
37
|
project: projectName,
|
|
34
38
|
environment: envName,
|
|
35
|
-
...(existing?.apiUrl ? { apiUrl: existing.apiUrl } : {}),
|
|
36
|
-
});
|
|
39
|
+
...(existing?.config.apiUrl ? { apiUrl: existing.config.apiUrl } : {}),
|
|
40
|
+
}, targetDir);
|
|
37
41
|
success(`Switched to ${projectName}/${envName}`);
|
|
38
42
|
}
|
|
39
43
|
catch (err) {
|
|
@@ -8,15 +8,33 @@ export interface ProjectConfig {
|
|
|
8
8
|
*/
|
|
9
9
|
apiUrl?: string;
|
|
10
10
|
}
|
|
11
|
+
export interface FoundProjectConfig {
|
|
12
|
+
config: ProjectConfig;
|
|
13
|
+
/** Absolute directory that contains the discovered .hobble. */
|
|
14
|
+
dir: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Walk up from `startDir` (default cwd) to the filesystem root, returning the
|
|
18
|
+
* nearest ancestor that contains a parseable .hobble plus the directory it
|
|
19
|
+
* lives in. The walk stops when `dirname(dir) === dir` (filesystem root) and
|
|
20
|
+
* returns null if no .hobble is found along the way — mirroring how git/npm/
|
|
21
|
+
* eslint discover their root config.
|
|
22
|
+
*
|
|
23
|
+
* A .hobble that exists but fails to read/parse is treated like "not here" and
|
|
24
|
+
* the walk continues upward, so the nearest *valid* ancestor wins. This keeps
|
|
25
|
+
* the historical swallow-and-return-null tolerance (an unparseable cwd .hobble
|
|
26
|
+
* previously yielded null) while being strictly more forgiving.
|
|
27
|
+
*/
|
|
28
|
+
export declare function findProjectConfig(startDir?: string): Promise<FoundProjectConfig | null>;
|
|
11
29
|
export declare function loadProjectConfig(): Promise<ProjectConfig | null>;
|
|
12
|
-
export declare function saveProjectConfig(config: ProjectConfig): Promise<void>;
|
|
30
|
+
export declare function saveProjectConfig(config: ProjectConfig, dir?: string): Promise<void>;
|
|
13
31
|
/**
|
|
14
32
|
* Returns true when the project's .gitignore already contains an entry for
|
|
15
33
|
* the project config file (PROJECT_CONFIG_FILE). Returns false when the
|
|
16
34
|
* .gitignore exists but does not mention it, or when .gitignore is missing.
|
|
17
35
|
*/
|
|
18
|
-
export declare function isHobbleInGitignore(): Promise<boolean>;
|
|
19
|
-
export declare function addToGitignore(): Promise<void>;
|
|
36
|
+
export declare function isHobbleInGitignore(dir?: string): Promise<boolean>;
|
|
37
|
+
export declare function addToGitignore(dir?: string): Promise<void>;
|
|
20
38
|
export interface ResolvedContext {
|
|
21
39
|
project: string;
|
|
22
40
|
environment: string;
|
|
@@ -1,25 +1,49 @@
|
|
|
1
1
|
import { readFile, writeFile, appendFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
3
|
const PROJECT_CONFIG_FILE = ".hobble";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Walk up from `startDir` (default cwd) to the filesystem root, returning the
|
|
6
|
+
* nearest ancestor that contains a parseable .hobble plus the directory it
|
|
7
|
+
* lives in. The walk stops when `dirname(dir) === dir` (filesystem root) and
|
|
8
|
+
* returns null if no .hobble is found along the way — mirroring how git/npm/
|
|
9
|
+
* eslint discover their root config.
|
|
10
|
+
*
|
|
11
|
+
* A .hobble that exists but fails to read/parse is treated like "not here" and
|
|
12
|
+
* the walk continues upward, so the nearest *valid* ancestor wins. This keeps
|
|
13
|
+
* the historical swallow-and-return-null tolerance (an unparseable cwd .hobble
|
|
14
|
+
* previously yielded null) while being strictly more forgiving.
|
|
15
|
+
*/
|
|
16
|
+
export async function findProjectConfig(startDir = process.cwd()) {
|
|
17
|
+
let dir = startDir;
|
|
18
|
+
for (;;) {
|
|
19
|
+
const candidate = join(dir, PROJECT_CONFIG_FILE);
|
|
20
|
+
try {
|
|
21
|
+
const raw = await readFile(candidate, "utf-8");
|
|
22
|
+
return { config: JSON.parse(raw), dir };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Not present (or unreadable/unparseable) here — keep walking up.
|
|
26
|
+
}
|
|
27
|
+
const parent = dirname(dir);
|
|
28
|
+
if (parent === dir)
|
|
29
|
+
return null; // reached filesystem root
|
|
30
|
+
dir = parent;
|
|
11
31
|
}
|
|
12
32
|
}
|
|
13
|
-
export async function
|
|
14
|
-
|
|
33
|
+
export async function loadProjectConfig() {
|
|
34
|
+
const found = await findProjectConfig();
|
|
35
|
+
return found?.config ?? null;
|
|
36
|
+
}
|
|
37
|
+
export async function saveProjectConfig(config, dir = process.cwd()) {
|
|
38
|
+
await writeFile(join(dir, PROJECT_CONFIG_FILE), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
15
39
|
}
|
|
16
40
|
/**
|
|
17
41
|
* Returns true when the project's .gitignore already contains an entry for
|
|
18
42
|
* the project config file (PROJECT_CONFIG_FILE). Returns false when the
|
|
19
43
|
* .gitignore exists but does not mention it, or when .gitignore is missing.
|
|
20
44
|
*/
|
|
21
|
-
export async function isHobbleInGitignore() {
|
|
22
|
-
const gitignorePath = join(
|
|
45
|
+
export async function isHobbleInGitignore(dir = process.cwd()) {
|
|
46
|
+
const gitignorePath = join(dir, ".gitignore");
|
|
23
47
|
try {
|
|
24
48
|
const content = await readFile(gitignorePath, "utf-8");
|
|
25
49
|
return content.includes(PROJECT_CONFIG_FILE);
|
|
@@ -28,8 +52,8 @@ export async function isHobbleInGitignore() {
|
|
|
28
52
|
return false;
|
|
29
53
|
}
|
|
30
54
|
}
|
|
31
|
-
export async function addToGitignore() {
|
|
32
|
-
const gitignorePath = join(
|
|
55
|
+
export async function addToGitignore(dir = process.cwd()) {
|
|
56
|
+
const gitignorePath = join(dir, ".gitignore");
|
|
33
57
|
try {
|
|
34
58
|
const content = await readFile(gitignorePath, "utf-8");
|
|
35
59
|
if (content.includes(PROJECT_CONFIG_FILE))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hobble-env/cli",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.9",
|
|
4
4
|
"description": "CLI for managing secrets via hobble",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"directory": "cli"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
|
-
"hobble": "
|
|
12
|
+
"hobble": "dist/bin/hobble.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|