@mercury-fw/utils 0.32.0 → 0.33.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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/cli-credentials.ts +138 -0
- package/dist/cli-credentials.d.ts +44 -0
- package/dist/index.d.ts +4 -2
- package/index.ts +12 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @mercury-fw/utils
|
|
2
2
|
|
|
3
|
+
## 0.33.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a493d9b: - A plugin whose CLI keeps its login in a folder declares it in its `package.json` (`mercury.cliCredentials`): `{ folder }` under `~/.config`, the default, or `{ path }` anywhere else under the home. Any plugin's CLI gets the login mechanism, not only the first-party ones.
|
|
8
|
+
- The core unpacks each declared login from its env variable onto the credentials volume (`~/.config`) at startup, only when the folder isn't there yet, for the service and the REPL alike; a folder declared elsewhere in the home lives on the volume under `~/.config/mercury-home`, linked from its usual place. It warns about a declared folder with neither the folder nor the variable.
|
|
9
|
+
- `mfw credentials set|reset <plugin>` names the plugin by its package or its CLI's folder (`@mercury-fw/plugin-jira` or `jira-cli`), read from the app's installed plugins; the short name (`jira`) is no longer accepted, and reset asks for the folder's name.
|
|
10
|
+
- `mfw create` no longer writes `docker-entrypoint.sh` or the credentials variables in the env example, and always mounts the `cli-credentials` volume; an existing app's entrypoint keeps working alongside.
|
|
11
|
+
- The generated README explains how a plugin's CLI gets its login without listing plugins.
|
|
12
|
+
- jira, bitbucket and atlassian-admin declare their CLI's login folder; their READMEs point to `mfw credentials set`.
|
|
13
|
+
|
|
3
14
|
## 0.32.0
|
|
4
15
|
|
|
5
16
|
## 0.31.1
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# @mercury-fw/utils
|
|
2
2
|
|
|
3
|
-
Helpers shared by [Mercury](https://github.com/lucabro81/mercury-fw) packages, today the download of a plugin's pinned CLI binary that the CLI-based plugins run when they install. You don't depend on it directly.
|
|
3
|
+
Helpers shared by [Mercury](https://github.com/lucabro81/mercury-fw) packages, today the download of a plugin's pinned CLI binary that the CLI-based plugins run when they install, and reading the CLI login folder a plugin declares (`mercury.cliCredentials`). You don't depend on it directly.
|
|
4
4
|
|
|
5
5
|
MIT
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI credentials folder a plugin declares in its own package.json
|
|
3
|
+
* (`mercury.cliCredentials`): where the plugin's CLI keeps its login and reads
|
|
4
|
+
* it back at runtime, either `{ folder }` under `~/.config` (the usual place)
|
|
5
|
+
* or `{ path }` anywhere under the home. The app's env file carries it packed
|
|
6
|
+
* into a variable (`mfw credentials set` writes it), and the core unpacks it
|
|
7
|
+
* onto the credentials volume at startup. Declared by the plugin rather than
|
|
8
|
+
* listed anywhere central, so a plugin from any author gets the same
|
|
9
|
+
* mechanism; read as data, so both `mfw` on the host and the core in the
|
|
10
|
+
* container find it without running the plugin's code.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
/** What a plugin declares: `name` as it was declared (the folder, or the
|
|
16
|
+
* path), which `mfw credentials` takes and the variable is named after, and
|
|
17
|
+
* `path` relative to the home. */
|
|
18
|
+
export type DeclaredCredentials = { name: string; path: string };
|
|
19
|
+
|
|
20
|
+
/** One dependency's declaration, with the env variable that carries it. */
|
|
21
|
+
export type CliCredentials = DeclaredCredentials & { package: string; variable: string };
|
|
22
|
+
|
|
23
|
+
/** What an app's dependencies declare: the usable declarations, and one line
|
|
24
|
+
* per dependency left out (not installed, malformed, sharing a variable). */
|
|
25
|
+
export type AppCliCredentials = { declared: CliCredentials[]; problems: string[] };
|
|
26
|
+
|
|
27
|
+
/** A single folder name: no separators, no `.`/`..`, no leading dot. */
|
|
28
|
+
const FOLDER = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29
|
+
|
|
30
|
+
/** One segment of a home-relative path, never starting with a dash (the last
|
|
31
|
+
* one is a tar member name, not an option); `.` and `..` are refused apart. */
|
|
32
|
+
const SEGMENT = /^[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
33
|
+
|
|
34
|
+
/** The credentials volume is mounted on `~/.config`; a login declared
|
|
35
|
+
* anywhere else in the home lives on it under this folder. */
|
|
36
|
+
const ELSEWHERE = ".config/mercury-home";
|
|
37
|
+
|
|
38
|
+
const INVALID =
|
|
39
|
+
"invalid mercury.cliCredentials in package.json: expected { folder } with a single folder name under ~/.config, or { path } relative to the home and outside ~/.config";
|
|
40
|
+
|
|
41
|
+
/** Whether `path` is a usable home-relative path for a login: inside the
|
|
42
|
+
* home and outside `~/.config`, the volume, where a login is a `folder`. */
|
|
43
|
+
function isHomePath(path: string): boolean {
|
|
44
|
+
const segments = path.split("/");
|
|
45
|
+
if (!segments.every((s) => SEGMENT.test(s) && s !== "." && s !== "..")) return false;
|
|
46
|
+
return path !== ".config" && !path.startsWith(".config/");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What `mercury.cliCredentials` of a package.json-shaped object declares, or
|
|
51
|
+
* undefined when the package declares nothing. A malformed declaration throws:
|
|
52
|
+
* a typo must surface, not silently leave the CLI without its login.
|
|
53
|
+
* Hand-validated, like `readPinnedBinary`, to keep this package dependency-free.
|
|
54
|
+
*/
|
|
55
|
+
export function readCliCredentials(pkg: unknown): DeclaredCredentials | undefined {
|
|
56
|
+
const mercury = (pkg as { mercury?: unknown } | undefined)?.mercury;
|
|
57
|
+
if (mercury === undefined) return undefined;
|
|
58
|
+
if (typeof mercury !== "object" || mercury === null || Array.isArray(mercury)) {
|
|
59
|
+
throw new Error("invalid mercury in package.json: expected an object");
|
|
60
|
+
}
|
|
61
|
+
if (!("cliCredentials" in mercury)) return undefined;
|
|
62
|
+
const declared = (mercury as { cliCredentials?: unknown }).cliCredentials;
|
|
63
|
+
if (typeof declared !== "object" || declared === null) throw new Error(INVALID);
|
|
64
|
+
const { folder, path } = declared as { folder?: unknown; path?: unknown };
|
|
65
|
+
if (folder !== undefined && path === undefined && typeof folder === "string" && FOLDER.test(folder)) {
|
|
66
|
+
return { name: folder, path: `.config/${folder}` };
|
|
67
|
+
}
|
|
68
|
+
if (path !== undefined && folder === undefined && typeof path === "string" && isHomePath(path)) {
|
|
69
|
+
return { name: path, path };
|
|
70
|
+
}
|
|
71
|
+
throw new Error(INVALID);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Where a home-relative login lives on the credentials volume, as a path
|
|
75
|
+
* relative to the home: where it is when it's under `~/.config` (the
|
|
76
|
+
* volume's mount, a declared `folder`), under `~/.config/mercury-home`
|
|
77
|
+
* otherwise, with the home path pointing there. */
|
|
78
|
+
export function volumePath(path: string): string {
|
|
79
|
+
return path.startsWith(".config/") ? path : `${ELSEWHERE}/${path}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The env variable carrying a declared login packed, named after what was
|
|
83
|
+
* declared: `jira-cli` → `JIRA_CLI_CONFIG_TAR_B64`, `.aws` → `AWS_CONFIG_TAR_B64`. */
|
|
84
|
+
export function credentialsVariable(name: string): string {
|
|
85
|
+
return `${name.toUpperCase().replace(/[^A-Z0-9]/g, "_").replace(/^_+/, "")}_CONFIG_TAR_B64`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The declarations of the app's dependencies (its package.json `dependencies`,
|
|
90
|
+
* read from its `node_modules`), in the manifest's order. A dependency that
|
|
91
|
+
* isn't installed or declares a malformed folder is left out with a problem
|
|
92
|
+
* line, and so are all the ones whose declarations map to the same variable (one
|
|
93
|
+
* variable can't carry two logins) or whose paths are one inside the other
|
|
94
|
+
* (one would be linked inside the other's copy on the volume): one bad plugin
|
|
95
|
+
* never costs the others their login. Throws only when the app's own
|
|
96
|
+
* package.json can't be read.
|
|
97
|
+
*/
|
|
98
|
+
export function appCliCredentials(appDir: string): AppCliCredentials {
|
|
99
|
+
const manifest = JSON.parse(readFileSync(join(appDir, "package.json"), "utf-8")) as {
|
|
100
|
+
dependencies?: Record<string, string>;
|
|
101
|
+
};
|
|
102
|
+
const found: CliCredentials[] = [];
|
|
103
|
+
const problems: string[] = [];
|
|
104
|
+
for (const name of Object.keys(manifest.dependencies ?? {})) {
|
|
105
|
+
const path = join(appDir, "node_modules", name, "package.json");
|
|
106
|
+
if (!existsSync(path)) {
|
|
107
|
+
problems.push(`${name} is not installed (no ${path}): run bun install`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const declared = readCliCredentials(JSON.parse(readFileSync(path, "utf-8")));
|
|
112
|
+
if (declared !== undefined) found.push({ package: name, ...declared, variable: credentialsVariable(declared.name) });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
problems.push(`${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const excluded = new Set<CliCredentials>();
|
|
118
|
+
for (const c of found) {
|
|
119
|
+
const sharing = found.filter((o) => o.variable === c.variable);
|
|
120
|
+
if (sharing.length === 1) continue;
|
|
121
|
+
sharing.forEach((o) => excluded.add(o));
|
|
122
|
+
if (sharing[0] === c) {
|
|
123
|
+
problems.push(
|
|
124
|
+
`${sharing.map((o) => o.package).join(" and ")} declare CLI credentials (${sharing.map((o) => o.name).join(", ")}) carried by the same variable ${c.variable}: neither is used`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const inside = (outer: string, inner: string) => inner.startsWith(`${outer}/`);
|
|
129
|
+
found.forEach((a, i) => {
|
|
130
|
+
for (const b of found.slice(i + 1)) {
|
|
131
|
+
if (!inside(a.path, b.path) && !inside(b.path, a.path)) continue;
|
|
132
|
+
excluded.add(a);
|
|
133
|
+
excluded.add(b);
|
|
134
|
+
problems.push(`${a.package} and ${b.package} declare CLI credentials (${a.name}, ${b.name}) one inside the other: neither is used`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return { declared: found.filter((c) => !excluded.has(c)), problems };
|
|
138
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** What a plugin declares: `name` as it was declared (the folder, or the
|
|
2
|
+
* path), which `mfw credentials` takes and the variable is named after, and
|
|
3
|
+
* `path` relative to the home. */
|
|
4
|
+
export type DeclaredCredentials = {
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
/** One dependency's declaration, with the env variable that carries it. */
|
|
9
|
+
export type CliCredentials = DeclaredCredentials & {
|
|
10
|
+
package: string;
|
|
11
|
+
variable: string;
|
|
12
|
+
};
|
|
13
|
+
/** What an app's dependencies declare: the usable declarations, and one line
|
|
14
|
+
* per dependency left out (not installed, malformed, sharing a variable). */
|
|
15
|
+
export type AppCliCredentials = {
|
|
16
|
+
declared: CliCredentials[];
|
|
17
|
+
problems: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* What `mercury.cliCredentials` of a package.json-shaped object declares, or
|
|
21
|
+
* undefined when the package declares nothing. A malformed declaration throws:
|
|
22
|
+
* a typo must surface, not silently leave the CLI without its login.
|
|
23
|
+
* Hand-validated, like `readPinnedBinary`, to keep this package dependency-free.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readCliCredentials(pkg: unknown): DeclaredCredentials | undefined;
|
|
26
|
+
/** Where a home-relative login lives on the credentials volume, as a path
|
|
27
|
+
* relative to the home: where it is when it's under `~/.config` (the
|
|
28
|
+
* volume's mount, a declared `folder`), under `~/.config/mercury-home`
|
|
29
|
+
* otherwise, with the home path pointing there. */
|
|
30
|
+
export declare function volumePath(path: string): string;
|
|
31
|
+
/** The env variable carrying a declared login packed, named after what was
|
|
32
|
+
* declared: `jira-cli` → `JIRA_CLI_CONFIG_TAR_B64`, `.aws` → `AWS_CONFIG_TAR_B64`. */
|
|
33
|
+
export declare function credentialsVariable(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* The declarations of the app's dependencies (its package.json `dependencies`,
|
|
36
|
+
* read from its `node_modules`), in the manifest's order. A dependency that
|
|
37
|
+
* isn't installed or declares a malformed folder is left out with a problem
|
|
38
|
+
* line, and so are all the ones whose declarations map to the same variable (one
|
|
39
|
+
* variable can't carry two logins) or whose paths are one inside the other
|
|
40
|
+
* (one would be linked inside the other's copy on the volume): one bad plugin
|
|
41
|
+
* never costs the others their login. Throws only when the app's own
|
|
42
|
+
* package.json can't be read.
|
|
43
|
+
*/
|
|
44
|
+
export declare function appCliCredentials(appDir: string): AppCliCredentials;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@mercury-fw/utils` — shared, plugin-agnostic helpers used across the monorepo.
|
|
3
|
-
* Currently the CLI-binary provisioning used by each plugin's postinstall
|
|
4
|
-
* more shared utilities land here
|
|
3
|
+
* Currently the CLI-binary provisioning used by each plugin's postinstall and
|
|
4
|
+
* the plugin-declared CLI credentials folder; more shared utilities land here
|
|
5
|
+
* as they're factored out.
|
|
5
6
|
*/
|
|
6
7
|
export { downloadPinnedBinary, resolvePlatform, binaryAssetUrl, readPinnedBinary, type Platform, type PinnedBinary, } from "./cli-binary.ts";
|
|
8
|
+
export { readCliCredentials, credentialsVariable, appCliCredentials, volumePath, type DeclaredCredentials, type CliCredentials, type AppCliCredentials, } from "./cli-credentials.ts";
|
package/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@mercury-fw/utils` — shared, plugin-agnostic helpers used across the monorepo.
|
|
3
|
-
* Currently the CLI-binary provisioning used by each plugin's postinstall
|
|
4
|
-
* more shared utilities land here
|
|
3
|
+
* Currently the CLI-binary provisioning used by each plugin's postinstall and
|
|
4
|
+
* the plugin-declared CLI credentials folder; more shared utilities land here
|
|
5
|
+
* as they're factored out.
|
|
5
6
|
*/
|
|
6
7
|
export {
|
|
7
8
|
downloadPinnedBinary,
|
|
@@ -11,3 +12,12 @@ export {
|
|
|
11
12
|
type Platform,
|
|
12
13
|
type PinnedBinary,
|
|
13
14
|
} from "./cli-binary.ts";
|
|
15
|
+
export {
|
|
16
|
+
readCliCredentials,
|
|
17
|
+
credentialsVariable,
|
|
18
|
+
appCliCredentials,
|
|
19
|
+
volumePath,
|
|
20
|
+
type DeclaredCredentials,
|
|
21
|
+
type CliCredentials,
|
|
22
|
+
type AppCliCredentials,
|
|
23
|
+
} from "./cli-credentials.ts";
|