@quonfig/cli 0.0.45 → 0.0.46

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.
@@ -42,6 +42,15 @@ export declare const spawnGit: (args: string[], options?: SpawnGitOptions) => Pr
42
42
  export declare const gitClone: (repoUrl: string, dir: string) => Promise<void>;
43
43
  export declare const isGitRepo: (dir: string) => Promise<boolean>;
44
44
  export declare const getRemoteUrl: (dir: string) => Promise<string | null>;
45
+ /**
46
+ * Return every configured remote URL (one per remote name). Walks the
47
+ * output of `git remote` and resolves each name's URL via `remote get-url`.
48
+ *
49
+ * Returns an empty array when the dir isn't a git repo or has no remotes.
50
+ * Multi-remote support (qfg-glrd.3): the identity check accepts as long as
51
+ * any configured remote points at the backend's repo URL.
52
+ */
53
+ export declare const getAllRemoteUrls: (dir: string) => Promise<string[]>;
45
54
  export declare const isWorkingTreeClean: (dir: string) => Promise<boolean>;
46
55
  /**
47
56
  * Returns true if `file` (relative to `dir`) has any working-tree or
@@ -81,6 +81,44 @@ export const getRemoteUrl = async (dir) => {
81
81
  return null;
82
82
  }
83
83
  };
84
+ /**
85
+ * Return every configured remote URL (one per remote name). Walks the
86
+ * output of `git remote` and resolves each name's URL via `remote get-url`.
87
+ *
88
+ * Returns an empty array when the dir isn't a git repo or has no remotes.
89
+ * Multi-remote support (qfg-glrd.3): the identity check accepts as long as
90
+ * any configured remote points at the backend's repo URL.
91
+ */
92
+ export const getAllRemoteUrls = async (dir) => {
93
+ let names;
94
+ try {
95
+ const { stdout } = await runGit(['-C', dir, 'remote']);
96
+ names = stdout
97
+ .split('\n')
98
+ .map((s) => s.trim())
99
+ .filter(Boolean);
100
+ }
101
+ catch {
102
+ return [];
103
+ }
104
+ const urls = [];
105
+ for (const name of names) {
106
+ try {
107
+ // Sequential by design: each `git remote get-url` spawns its own git
108
+ // process against the same repo; running them serially keeps git's
109
+ // index/lock access predictable and the remote count is tiny.
110
+ // eslint-disable-next-line no-await-in-loop
111
+ const { stdout } = await runGit(['-C', dir, 'remote', 'get-url', name]);
112
+ const url = stdout.trim();
113
+ if (url.length > 0)
114
+ urls.push(url);
115
+ }
116
+ catch {
117
+ /* skip remotes whose URL we can't read */
118
+ }
119
+ }
120
+ return urls;
121
+ };
84
122
  export const isWorkingTreeClean = async (dir) => {
85
123
  const { stdout } = await runGit(['-C', dir, 'status', '--porcelain']);
86
124
  return stdout.trim() === '';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Resolve the workspace directory for `qfg push` / `qfg pull`.
3
+ *
4
+ * Resolution order:
5
+ * 1. `--dir <path>` flag (explicit, wins everything)
6
+ * 2. `QUONFIG_DIR` env var
7
+ * 3. Walk up from cwd looking for `quonfig.json`. Stop at the first hit,
8
+ * the user's home dir, or the filesystem root.
9
+ * 4. Error: `No Quonfig workspace dir found. ...`
10
+ *
11
+ * Pure — the caller wires `process.cwd()` and `process.env.QUONFIG_DIR` in.
12
+ * Tests drive the function with explicit values so the cwd walk can be
13
+ * exercised inside a tmp dir without `process.chdir`.
14
+ *
15
+ * Empty-string flag / env values are treated as "absent" because shells
16
+ * routinely emit `EXPORT_FOO=` when a var is declared but has no value;
17
+ * silently picking the empty string would resolve to cwd and produce a
18
+ * confusing wrong-target failure downstream.
19
+ */
20
+ export declare const NO_WORKSPACE_DIR_ERROR = "No Quonfig workspace dir found. Run from inside a workspace, or pass --dir, or set QUONFIG_DIR.";
21
+ export interface ResolveWorkspaceDirInput {
22
+ /** Current working directory — the search root for the walk. */
23
+ cwd: string;
24
+ /** Value of `QUONFIG_DIR` env var (or undefined). */
25
+ envDir: string | undefined;
26
+ /** Value of `--dir` flag (or undefined). */
27
+ flagDir: string | undefined;
28
+ /**
29
+ * The user's home directory. The cwd walk stops here (without checking
30
+ * it) so we never pick up a stray `quonfig.json` parked in `~`. Defaults
31
+ * to `os.homedir()` when omitted; tests pass an explicit value so they
32
+ * can pretend a tmp dir is "home".
33
+ */
34
+ homeDir?: string;
35
+ }
36
+ export type ResolveWorkspaceDirResult = {
37
+ kind: 'ok';
38
+ dir: string;
39
+ source: 'flag' | 'env' | 'cwd-walk';
40
+ } | {
41
+ kind: 'error';
42
+ message: string;
43
+ };
44
+ export declare const resolveWorkspaceDir: (input: ResolveWorkspaceDirInput) => ResolveWorkspaceDirResult;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resolve the workspace directory for `qfg push` / `qfg pull`.
3
+ *
4
+ * Resolution order:
5
+ * 1. `--dir <path>` flag (explicit, wins everything)
6
+ * 2. `QUONFIG_DIR` env var
7
+ * 3. Walk up from cwd looking for `quonfig.json`. Stop at the first hit,
8
+ * the user's home dir, or the filesystem root.
9
+ * 4. Error: `No Quonfig workspace dir found. ...`
10
+ *
11
+ * Pure — the caller wires `process.cwd()` and `process.env.QUONFIG_DIR` in.
12
+ * Tests drive the function with explicit values so the cwd walk can be
13
+ * exercised inside a tmp dir without `process.chdir`.
14
+ *
15
+ * Empty-string flag / env values are treated as "absent" because shells
16
+ * routinely emit `EXPORT_FOO=` when a var is declared but has no value;
17
+ * silently picking the empty string would resolve to cwd and produce a
18
+ * confusing wrong-target failure downstream.
19
+ */
20
+ import * as fs from 'node:fs';
21
+ import * as os from 'node:os';
22
+ import * as path from 'node:path';
23
+ const QUONFIG_JSON = 'quonfig.json';
24
+ export const NO_WORKSPACE_DIR_ERROR = 'No Quonfig workspace dir found. Run from inside a workspace, or pass --dir, or set QUONFIG_DIR.';
25
+ const blank = (s) => s === undefined || s === '';
26
+ export const resolveWorkspaceDir = (input) => {
27
+ if (!blank(input.flagDir)) {
28
+ return { kind: 'ok', dir: path.resolve(input.cwd, input.flagDir), source: 'flag' };
29
+ }
30
+ if (!blank(input.envDir)) {
31
+ return { kind: 'ok', dir: path.resolve(input.cwd, input.envDir), source: 'env' };
32
+ }
33
+ const homeDir = input.homeDir ?? os.homedir();
34
+ const found = walkUpForWorkspace(input.cwd, homeDir);
35
+ if (found !== undefined) {
36
+ return { kind: 'ok', dir: found, source: 'cwd-walk' };
37
+ }
38
+ return { kind: 'error', message: NO_WORKSPACE_DIR_ERROR };
39
+ };
40
+ /**
41
+ * Walk from `start` toward the filesystem root, returning the first ancestor
42
+ * dir that contains a `quonfig.json`. Stops without checking when the walk
43
+ * reaches `homeDir` or the filesystem root — `~` is not a workspace.
44
+ */
45
+ const walkUpForWorkspace = (start, homeDir) => {
46
+ let current = path.resolve(start);
47
+ // Normalize homeDir the same way so the equality check survives a
48
+ // trailing slash or symlink-resolved path on the caller's side.
49
+ const stop = path.resolve(homeDir);
50
+ // Walk up until the filesystem root: path.dirname() of the root is the
51
+ // root itself, so `parent === current` is the terminal condition.
52
+ for (let parent = path.dirname(current);; current = parent, parent = path.dirname(current)) {
53
+ if (current === stop)
54
+ return undefined;
55
+ if (fs.existsSync(path.join(current, QUONFIG_JSON))) {
56
+ return current;
57
+ }
58
+ if (parent === current)
59
+ return undefined;
60
+ }
61
+ };
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const _default: "0.0.45";
1
+ declare const _default: "0.0.46";
2
2
  export default _default;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED from package.json by scripts/generate-version.mjs — do not edit.
2
- export default '0.0.45';
2
+ export default '0.0.46';